[
  {
    "path": ".circleci/ci_increase_chart_version.sh",
    "content": "#!/bin/sh\n\n\"\"\"\nThis is the script that would send a dispatch event to the helm chart repo to auto increase chart version.\nYou can set the UPDATE_DEV flag to decide whether to update to dev too.\n\nFor master, we increase the chart version and update dev together. The app version should be short git sha with length 7.\nFor release, we increase the chart version only. The app version should be semver. (eg. 1.0.3-pre.0)\n\nRequired env vars:\n- CHART_NAME (eg. childchain, watcher, watcher-info)\n- APP_VERSION (eg. 3d75118 or 1.0.3-pre.0)\n- HELM_CHART_REPO (eg. helm-devlopement)\n- UPDATE_DEV (true/false)\n- GITHUB_API_TOKEN\n\"\"\"\n\nset -ex\n\n[ -z \"$CHART_NAME\" ] && echo \"CHART_NAME should be set\" && exit 1\n[ -z \"$APP_VERSION\" ] && echo \"APP_VERSION should be set\" && exit 1\n[ -z \"$HELM_CHART_REPO\" ] && echo \"HELM_CHART_REPO should be set\" && exit 1\n[ -z \"$UPDATE_DEV\" ] && echo \"HELM_CHART_REPO should be set\" && exit 1\n[ -z \"$GITHUB_API_TOKEN\" ] && echo \"GITHUB_API_TOKEN should be set\" && exit 1\n\n\necho \"increase chart version: chart [${CHART_NAME}], appVersion: [${APP_VERSION}], update_dev: [${UPDATE_DEV}]\"\n\ncurl --location --request POST \"https://api.github.com/repos/omgnetwork/${HELM_CHART_REPO}/dispatches\" \\\n--header \"Accept: application/vnd.github.v3+json\" \\\n--header \"authorization: token ${GITHUB_API_TOKEN}\" \\\n--header \"Content-Type: application/json\" \\\n--data-raw \" { \\\n    \\\"event_type\\\": \\\"increase-chart-version\\\", \\\n    \\\"client_payload\\\": { \\\n        \\\"chart_name\\\": \\\"${CHART_NAME}\\\", \\\n        \\\"app_version\\\": \\\"${APP_VERSION}\\\", \\\n        \\\"update_dev\\\": \\\"${UPDATE_DEV}\\\" \\\n    } \\\n}\"\n"
  },
  {
    "path": ".circleci/ci_publish.sh",
    "content": "#!/bin/sh\n\nset -e\n\necho_info() {\n    printf \"\\\\033[0;34m%s\\\\033[0;0m\\\\n\" \"$1\"\n}\n\necho_warn() {\n    printf \"\\\\033[0;33m%s\\\\033[0;0m\\\\n\" \"$1\"\n}\n\n\n## Sanity check\n##\n\nif [ -z \"$DOCKER_PASS\" ] || [ -z \"$DOCKER_USER\" ]; then\n    echo_warn \"Docker credentials is not present, skipping publish.\"\n    exit 0\nfi\n\nif [ -z \"$IMAGE_NAME\" ]; then\n    echo_warn \"IMAGE_NAME not present, failing.\"\n    exit 1\nfi\n\n\n## Generate tags\n##\n\nif [ -n \"$CIRCLE_SHA1\" ]; then\n    _image_tag=\"$(printf \"%s\" \"$CIRCLE_SHA1\" | head -c 7)\"\nfi\n\nif [ -n \"$CIRCLE_TAG\" ]; then\n    _ver=\"${CIRCLE_TAG#*v}\"\n\n    # Given a v1.0.0-pre.1 tag, this will generate:\n    # - 1.0\n    # - 1.0.0-pre\n    # - 1.0.0-pre.1\n    while true; do\n        case \"$_ver\" in\n            *.* )\n                _image_tag=\"$_ver $_image_tag\"\n                _ver=\"${_ver%.*}\"\n                ;;\n            * )\n                break;;\n        esac\n    done\n\n    # In case the commit is HEAD of latest version branch, also tag stable.\n    if [ -n \"$CIRCLE_REPOSITORY_URL\" ] && [ -n \"$CIRCLE_SHA1\" ]; then\n        _stable_head=\"$(\n            git ls-remote --heads \"$CIRCLE_REPOSITORY_URL\" \"v*\" |\n            awk '/refs\\/heads\\/v[0-9]+\\.[0-9]+$/ { LH=$1 } END { print LH }'\n        )\"\n\n        if [ \"$CIRCLE_SHA1\" = \"$_stable_head\" ]; then\n            _image_tag=\"$_image_tag stable\"\n        fi\n    fi\nelse\n    case \"$CIRCLE_BRANCH\" in\n        master ) _image_tag=\"$_image_tag latest\";;\n        v*     ) _image_tag=\"$_image_tag ${CIRCLE_BRANCH#*v}-dev\";;\n        *      ) ;;\n    esac\nfi\n\n\n## Publishing\n##\n\nif [ -f \"$HOME/caches/docker-layers.tar\" ]; then\n    docker load -i \"$HOME/caches/docker-layers.tar\"\nfi\n\nprintf \"%s\\\\n\" \"$DOCKER_PASS\" | docker login -u \"$DOCKER_USER\" --password-stdin\n\nfor tag in $_image_tag; do\n    echo_info \"Publishing Docker image as $IMAGE_NAME:$tag\"\n    docker tag \"$IMAGE_NAME\" \"$IMAGE_NAME:$tag\"\n    docker push \"$IMAGE_NAME:$tag\"\ndone\n"
  },
  {
    "path": ".circleci/config.yml",
    "content": "version: 2.1\n\nexecutors:\n  metal:\n    docker:\n      - image: circleci/elixir:1.10.2\n      - image: circleci/postgres:9.6\n        environment:\n          MIX_ENV: test\n          POSTGRES_USER: omisego_dev\n          POSTGRES_PASSWORD: omisego_dev\n          POSTGRES_DB: omisego_test\n          CIRLCECI: true\n    working_directory: ~/src\n\n  metal_macos:\n    macos:\n      xcode: \"11.0.0\"\n\n  builder:\n    docker:\n      - image: omisegoimages/elixir-omg-builder:stable-20201207\n    working_directory: ~/src\n\n  builder_pg:\n    docker:\n      - image: omisegoimages/elixir-omg-builder:stable-20201207\n      - image: circleci/postgres:9.6-alpine\n        environment:\n          POSTGRES_USER: omisego_dev\n          POSTGRES_PASSWORD: omisego_dev\n          POSTGRES_DB: omisego_test\n    working_directory: ~/src\n\n  builder_pg_geth:\n    docker:\n      - image: omisegoimages/elixir-omg-tester:stable-20201207\n      - image: circleci/postgres:9.6-alpine\n        environment:\n          POSTGRES_USER: omisego_dev\n          POSTGRES_PASSWORD: omisego_dev\n          POSTGRES_DB: omisego_test\n    working_directory: ~/src\n\n  deployer:\n    docker:\n      - image: omisegoimages/elixir-omg-deploy:stable-20201207\n    working_directory: ~/src\n\ncommands:\n  add_rust_to_path:\n    description: \"Add path to PATH env var\"\n    steps:\n      - run:\n          name: Add rust to PATH env\n          command: echo 'export PATH=~/.cargo/bin/:$PATH' >> $BASH_ENV\n  install_rust:\n    description: \"Install Rust\"\n    steps:\n      - run:\n          name: Install Rust\n          command: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y\n      - add_rust_to_path\n\n  setup_elixir-omg_workspace:\n    description: \"Setup workspace\"\n    steps:\n      - attach_workspace:\n          name: Attach workspace\n          at: .\n\n  docker_login:\n    description: login to dockerhub for private repo access\n    steps:\n      - run: printf \"%s\\\\n\" \"$DOCKER_PASS\" | docker login -u \"$DOCKER_USER\" --password-stdin\n\n  make_docker_images:\n    description: Builds docker images\n    steps:\n      - run: make docker-watcher\n      - run: make docker-watcher_info\n\n  check_docker_status:\n    description: Installs elixir and checks if docker is healthy\n    steps:\n      - run:\n          name: Print docker states\n          command: |\n            docker image ls\n            docker-compose ps\n\n  setup_childchain:\n    description: \"Setups Child chain for watcher tests\"\n    steps:\n      # otherwise docker compose down errors with ERROR: Couldn't find env file\n      - run: touch localchain_contract_addresses.env\n      - run: docker-compose down\n      - run: sudo rm -rf data/\n      - run:\n          name: Setup data dir\n          command: |\n            [ -d data ] || mkdir data && chmod 777 data\n      - run:\n          name: Pull down snapshot\n          command: SNAPSHOT=SNAPSHOT_MIX_EXIT_PERIOD_SECONDS_20 make init_test\n      - run: |\n          echo -e \"FEE_SPECS_FILE_PATH=/dev-artifacts/fee_specs.test.json\\n$(cat fees_setup.env)\" > fees_setup.env\n          echo \"FEE_SPECS_FILE_PATH=/dev-artifacts/fee_specs.test.json\" >> fees_setup.env\n          cat fees_setup.env\n      - run:\n          name: Standup Geth and Child Chain\n          command: docker-compose up geth childchain postgres\n          background: true\n      - run:\n          name: Has Childchain started?\n          command: |\n            attempt_counter=0\n            max_attempts=25\n            until $(curl --output /dev/null --silent --head --fail http://localhost:9656/alarm.get); do\n              if [ ${attempt_counter} -eq ${max_attempts} ];then\n                echo \"Max attempts reached\"\n                exit 1\n              fi\n              printf '.'\n              attempt_counter=$(($attempt_counter+1))\n              sleep 5\n            done\n\n  run_test_in_docker:\n    description: \"Quick test runs\"\n    parameters:\n      test_command:\n        type: string\n      test_name:\n        type: string\n    steps:\n      - run:\n          name: \"Docker run <<parameters.test_name>>\"\n          command: |\n            docker run --rm -it --network=chain_net -e DOCKER=true -e CHILD_CHAIN_URL=http://172.27.0.108:9656/ -e ETHEREUM_RPC_URL=http://172.27.0.108:80 -e DOCKER_GETH=true -e TEST_DATABASE_URL=postgresql://omisego_dev:omisego_dev@172.27.0.107:5432/omisego_test -e SHELL=/bin/sh -v $(pwd):/app --entrypoint /bin/sh omisegoimages/elixir-omg-builder:stable-20201207 -c \"cd /app && mix deps.get && <<parameters.test_command>>\"\n\n  install_elixir:\n    description: Installs elixir and checks if docker is healthy\n    steps:\n      - restore_cache:\n          key: v2-asdf-install\n      - run:\n          name: Install Erlang and Elixir\n          command: |\n            [ -d ~/.asdf-vm ] || git clone https://github.com/asdf-vm/asdf.git ~/.asdf-vm --branch v0.8.0\n            echo 'source ~/.asdf-vm/asdf.sh' >> $BASH_ENV\n            source $BASH_ENV\n            asdf plugin-add erlang || asdf plugin-update erlang\n            asdf plugin-add elixir || asdf plugin-update elixir\n            asdf plugin-add rust || asdf plugin-update rust\n            asdf install\n          no_output_timeout: 2400\n      - install_rust\n      - save_cache:\n          key: v2-asdf-install\n          paths:\n            - ~/.asdf\n            - ~/.asdf-vm\n      - run: make install-hex-rebar\n      - restore_cache:\n          key: v2-mix-specs-cache-{{ .Branch }}-{{ checksum \"mix.lock\" }}\n\n  install_deps:\n    description: Install linux dependencies\n    steps:\n      - run:\n          name: Install deps\n          command: |\n            set -e\n            sudo killall dpkg || true &&\n            sudo rm /var/lib/dpkg/lock || true &&\n            sudo rm /var/cache/apt/archives/lock || true &&\n            sudo dpkg --configure -a || true &&\n            sudo apt-get update &&\n            ./bin/setup\n          no_output_timeout: 2400\n  install_and_setup_gcloud:\n    description: Installs and sets up gcloud to fetch feefeed\n    steps:\n      - run: |\n          export LD_LIBRARY_PATH=/usr/local/lib\n          export CLOUDSDK_PYTHON=/usr/bin/python\n          wget https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-sdk-323.0.0-linux-x86_64.tar.gz -O gcloud-sdk.tar.gz\n            tar zxf gcloud-sdk.tar.gz google-cloud-sdk\n            mv google-cloud-sdk ~/.google-cloud-sdk\n            ~/.google-cloud-sdk/install.sh --quiet\n            echo $GCP_KEY_FILE | gcloud auth activate-service-account $GCP_SERVICE_EMAIL --key-file=-\n            gcloud --quiet config set project ${GCP_PROJECT}\n            gcloud --quiet config set compute/zone ${GCP_ZONE}\n            gcloud --quiet auth configure-docker\n\njobs:\n  barebuild:\n    executor: metal\n    environment:\n      MIX_ENV: test\n    steps:\n      - checkout\n      - run: make install-hex-rebar\n      - run: echo 'export PATH=~/.cargo/bin:$PATH' >> $BASH_ENV\n      - run:\n          command: ./bin/setup\n          no_output_timeout: 2400\n      - run: make deps-elixir-omg\n      - run: ERLANG_ROCKSDB_BUILDOPTS='-j 2' make build-test\n      - run: mix test\n      - run:\n          name: Integration Tests\n          command: |\n            # Slow, serial integration test, run nightly. Here to make sure the standard `mix test --only integration  ` works\n            export SHELL=/bin/bash\n            mix test --only integration\n          no_output_timeout: 30m\n  barebuild_macos:\n    executor: metal_macos\n    environment:\n      MIX_ENV: test\n    steps:\n      - checkout\n      - run: echo 'export PATH=~/.cargo/bin:$PATH' >> $BASH_ENV\n      - run: |\n          brew install postgres\n          initdb /usr/local/var/postgres/data\n          pg_ctl -D /usr/local/var/postgres/data -l /tmp/postgresql.log start\n          psql template1 \\<<EOF\n          CREATE USER omisego_dev WITH CREATEDB ENCRYPTED PASSWORD 'omisego_dev';\n          CREATE DATABASE omisego_dev OWNER 'omisego_dev';\n          EOF\n      - run: |\n          git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.7.4\n          echo -e '\\n. $HOME/.asdf/asdf.sh' >> ~/.bash_profile\n          echo -e '\\n. $HOME/.asdf/completions/asdf.bash' >> ~/.bash_profile\n          source ~/.bash_profile\n          asdf plugin-add elixir https://github.com/asdf-vm/asdf-elixir.git\n          asdf plugin-add erlang https://github.com/asdf-vm/asdf-erlang.git\n          asdf plugin-add rust || asdf plugin-update rust\n          asdf install\n      - run: make init_test\n      - install_rust\n      - run:\n          command: ./bin/setup\n          no_output_timeout: 2400\n      - run: make deps-elixir-omg\n      - run: ERLANG_ROCKSDB_BUILDOPTS='-j 2' make build-test\n      - run: mix test\n  build:\n    executor: builder\n    environment:\n      MIX_ENV: test\n    steps:\n      - checkout\n      - restore_cache:\n          key: v1-rocksdb-cache-{{ checksum \"mix.lock\" }}\n      - run: make init_test\n      - run: make deps-elixir-omg\n      - run: ERLANG_ROCKSDB_BUILDOPTS='-j 2' make build-test\n      - save_cache:\n          key: v2-mix-cache-test-compile-{{ checksum \"mix.lock\" }}-{{ .Branch }}-{{ .Revision }}\n          paths: \"_build_docker\"\n      - save_cache:\n          key: v1-rocksdb-cache-{{ checksum \"mix.lock\" }}\n          paths:\n            - \"deps_docker/\"\n            - \"deps_docker/rocksdb\"\n            - \"_build_docker/test/lib/rocksdb/\"\n            - \"_build_docker/test/dev/rocksdb/\"\n            - \"deps/\"\n            - \"_build/test/lib/rocksdb/\"\n            - \"_build/test/dev/rocksdb/\"\n      - persist_to_workspace:\n          name: Persist workspace\n          root: ~/src\n          paths:\n            - .circleci\n            - dialyzer.ignore-warnings\n            - .formatter.exs\n            - _build_docker\n            - .credo.exs\n            - apps\n            - bin\n            - config\n            - deps_docker\n            - doc\n            - mix.exs\n            - mix.lock\n            - deploy_and_populate.sh\n            - launcher.py\n            - docker-compose.yml\n            - rel/\n            - VERSION\n            - .git\n            - Makefile\n            - priv\n            - data\n            - snapshots.env\n            - snapshot_reorg.env\n            - nginx.conf\n            - contract_addresses_template.env\n            - localchain_contract_addresses.env\n\n\n  audit_deps:\n    executor: builder\n    environment:\n      MIX_ENV: test\n    steps:\n      - setup_elixir-omg_workspace\n      - run: mix deps.audit\n\n  lint:\n    executor: builder\n    environment:\n      MIX_ENV: test\n    steps:\n      - setup_elixir-omg_workspace\n      - run: make install-hex-rebar\n      - run: mix do compile --warnings-as-errors --force, credo --ignore-checks Credo.Check.Readability.SinglePipe, format --check-formatted --dry-run\n      - run:\n          command: |\n            export SHELL=/bin/bash\n            set +eo pipefail\n            _counter=$(mix credo --only Credo.Check.Readability.SinglePipe | grep -c \"Use a function call when a pipeline is only one function long\")\n            echo \"Current Credo.Check.Readability.SinglePipe occurrences:\"\n            echo $_counter\n            if [ $_counter -gt 273 ]; then\n              echo \"Have you been naughty or nice? Find out if Santa knows.\"\n              exit 1\n            fi\n\n  sobelow:\n    executor: builder_pg\n    environment:\n      MIX_ENV: test\n    steps:\n      - setup_elixir-omg_workspace\n      - run: mix archive.install hex sobelow --force\n      - run: mix sobelow --exit --skip --ignore Config.HTTPS -r .\n      - run: mix sobelow --exit --skip --ignore Config.HTTPS -r apps/omg\n      - run: mix sobelow --exit --skip --ignore Config.HTTPS -r apps/omg_bus\n      - run: mix sobelow --exit --skip --ignore Config.HTTPS -r apps/omg_db\n      - run: mix sobelow --exit --skip --ignore Config.HTTPS -r apps/omg_eth\n      - run: mix sobelow --exit --skip --ignore Config.HTTPS -r apps/omg_status\n      - run: mix sobelow --exit --skip --ignore Config.HTTPS -r apps/omg_utils\n      - run: mix sobelow --exit --skip --ignore Config.HTTPS -r apps/omg_watcher\n      - run: mix sobelow --exit --skip --ignore Config.HTTPS -r apps/omg_watcher_info\n      - run: mix sobelow --exit --skip --ignore Config.HTTPS -r apps/omg_watcher_rpc --router apps/omg_watcher_rpc/lib/web/router.ex\n\n  watcher_coveralls_and_integration_tests:\n    executor: builder_pg_geth\n    environment:\n      MIX_ENV: test\n    steps:\n      - setup_elixir-omg_workspace\n      - restore_cache:\n          keys:\n            - v2-mix-cache-test-compile-{{ checksum \"mix.lock\" }}-{{ .Branch }}-{{ .Revision }}\n      - run:\n          name: Compile\n          command: mix compile\n      - run:\n          name: Integration Tests & Coveralls Part Watcher\n          command: |\n            # Don't submit coverage report for forks, but let the build succeed\n            export SHELL=/bin/bash\n            if [[ -z \"$COVERALLS_REPO_TOKEN\" ]]; then\n              mix coveralls.html --parallel --umbrella --include watcher --exclude watcher_info  --exclude common --exclude test\n            else\n              mix coveralls.circle --parallel --umbrella --include watcher --exclude watcher_info  --exclude common --exclude test   ||\n                # if mix failed, then coveralls_report won't run, so signal done here and return original exit status\n                (retval=$? && curl -k https://coveralls.io/webhook?repo_token=$COVERALLS_REPO_TOKEN -d \"payload[build_num]=$CIRCLE_WORKFLOW_WORKSPACE_ID&payload[status]=done\" && exit $retval)\n            fi\n\n  watcher_mix_based_childchain:\n    machine:\n      image: ubuntu-2004:202010-01\n    environment:\n      MIX_ENV: test\n    steps:\n      - checkout\n      - restore_cache:\n          keys:\n            - v2-mix-cache-test-compile-watcher_mix_based_childchain-{{ checksum \"mix.lock\" }}-{{ .Branch }}\n      - run: rm -rf _build_docker/test/lib/omg*\n      - run:\n          name: Setup dirs\n          command: |\n            [ -d _build_docker ] || mkdir _build_docker && sudo chmod -R 777 _build_docker\n      - run:\n          name: Setup dirs deps_docker\n          command: |\n            [ -d deps_docker ] || mkdir deps_docker &&  sudo chmod -R 777 deps_docker\n      - setup_childchain\n      - run_test_in_docker:\n            test_command: \"mix test test/omg_watcher/integration/invalid_exit_1_test.exs --include mix_based_child_chain\"\n            test_name: \"invalid_exit_1_test.exs\"\n      - save_cache:\n          key: v2-mix-cache-test-compile-watcher_mix_based_childchain-{{ checksum \"mix.lock\" }}-{{ .Branch }}\n          paths:\n            - deps_docker\n            - _build_docker\n      - setup_childchain\n      - run_test_in_docker:\n            test_command: \"mix test test/omg_watcher/integration/in_flight_exit_test_3_test.exs --include mix_based_child_chain\"\n            test_name: \"in_flight_exit_test_3_test.exs\"\n      - setup_childchain\n      - run_test_in_docker:\n            test_command: \"mix test test/omg_watcher/integration/in_flight_exit_test_2_test.exs --include mix_based_child_chain\"\n            test_name: \"in_flight_exit_test_2_test.exs\"\n      - setup_childchain\n      - run_test_in_docker:\n            test_command: \"mix test test/omg_watcher/integration/in_flight_exit_test_1_test.exs --include mix_based_child_chain\"\n            test_name: \"in_flight_exit_test_1_test.exs\"\n      - setup_childchain\n      - run_test_in_docker:\n            test_command: \"mix test test/omg_watcher/integration/in_flight_exit_test_4_test.exs --include mix_based_child_chain\"\n            test_name: \"in_flight_exit_test_4_test.exs\"\n      - setup_childchain\n      - run_test_in_docker:\n            test_command: \"mix test test/omg_watcher/integration/in_flight_exit_test.exs --include mix_based_child_chain\"\n            test_name: \"in_flight_exit_test.exs\"\n      - setup_childchain\n      - run_test_in_docker:\n            test_command: \"mix test test/omg_watcher/integration/invalid_exit_2_test.exs --include mix_based_child_chain\"\n            test_name: \"invalid_exit_2_test.exs\"\n      - setup_childchain\n      - run_test_in_docker:\n            test_command: \"mix test test/omg_watcher/integration/block_getter_1_test.exs --include mix_based_child_chain\"\n            test_name: \"block_getter_1_test.exs\"\n      - setup_childchain\n      - run_test_in_docker:\n            test_command: \"mix test test/omg_watcher/integration/block_getter_2_test.exs --include mix_based_child_chain\"\n            test_name: \"block_getter_2_test.exs\"\n      - setup_childchain\n      - run_test_in_docker:\n            test_command: \"mix test test/omg_watcher/integration/block_getter_3_test.exs --include mix_based_child_chain\"\n            test_name: \"block_getter_3_test.exs\"\n      - setup_childchain\n      - run_test_in_docker:\n            test_command: \"mix test test/omg_watcher/integration/block_getter_4_test.exs --include mix_based_child_chain\"\n            test_name: \"block_getter_4_test.exs\"\n\n  watcher_info_coveralls_and_integration_tests:\n    executor: builder_pg_geth\n    environment:\n      MIX_ENV: test\n    steps:\n      - setup_elixir-omg_workspace\n      - restore_cache:\n          keys:\n            - v2-mix-cache-test-compile-{{ checksum \"mix.lock\" }}-{{ .Branch }}-{{ .Revision }}\n      - run:\n          name: Compile\n          command: mix compile\n      - run:\n          name: Integration Tests & Coveralls Part Watcher\n          command: |\n            # Don't submit coverage report for forks, but let the build succeed\n            export SHELL=/bin/bash\n            if [[ -z \"$COVERALLS_REPO_TOKEN\" ]]; then\n              mix coveralls.html --parallel --umbrella --include watcher_info --exclude watcher  --exclude common --exclude test\n            else\n              mix coveralls.circle --parallel --umbrella --include watcher_info --exclude watche  --exclude common --exclude test   ||\n                # if mix failed, then coveralls_report won't run, so signal done here and return original exit status\n                (retval=$? && curl -k https://coveralls.io/webhook?repo_token=$COVERALLS_REPO_TOKEN -d \"payload[build_num]=$CIRCLE_WORKFLOW_WORKSPACE_ID&payload[status]=done\" && exit $retval)\n            fi\n\n  common_coveralls_and_integration_tests:\n    executor: builder_pg_geth\n    environment:\n      MIX_ENV: test\n    steps:\n      - setup_elixir-omg_workspace\n      - restore_cache:\n          keys:\n            - v2-mix-cache-test-compile-{{ checksum \"mix.lock\" }}-{{ .Branch }}-{{ .Revision }}\n      - run:\n          name: Compile\n          command: mix compile\n      - run:\n          name: Integration Tests & Coveralls Part Common\n          command: |\n            # Don't submit coverage report for forks, but let the build succeed\n            export SHELL=/bin/bash\n            if [[ -z \"$COVERALLS_REPO_TOKEN\" ]]; then\n              mix coveralls.html --parallel --umbrella --include common --exclude watcher --exclude watcher_info  --exclude test\n            else\n              mix coveralls.circle --parallel --umbrella --include common --exclude watcher --exclude watcher_info  --exclude test   ||\n                # if mix failed, then coveralls_report won't run, so signal done here and return original exit status\n                (retval=$? && curl -k https://coveralls.io/webhook?repo_token=$COVERALLS_REPO_TOKEN -d \"payload[build_num]=$CIRCLE_WORKFLOW_WORKSPACE_ID&payload[status]=done\" && exit $retval)\n            fi\n\n  test:\n    executor: builder_pg\n    environment:\n      MIX_ENV: test\n    steps:\n      - setup_elixir-omg_workspace\n      - restore_cache:\n          keys:\n            - v2-mix-cache-test-compile-{{ checksum \"mix.lock\" }}-{{ .Branch }}-{{ .Revision }}\n      - run:\n          name: Compile\n          command: mix compile\n      - run:\n          name: Test\n          command: |\n            # Don't submit coverage report for forks, but let the build succeed\n            export SHELL=/bin/bash\n            if [[ -z \"$COVERALLS_REPO_TOKEN\" ]]; then\n              mix coveralls.html --parallel --umbrella   --exclude common --exclude watcher --exclude watcher_info\n            else\n              mix coveralls.circle --parallel --umbrella   --exclude common --exclude watcher --exclude watcher_info  ||\n                # if mix failed, then coveralls_report won't run, so signal done here and return original exit status\n                (retval=$? && curl -k https://coveralls.io/webhook?repo_token=$COVERALLS_REPO_TOKEN -d \"payload[build_num]=$CIRCLE_WORKFLOW_WORKSPACE_ID&payload[status]=done\" && exit $retval)\n            fi\n\n  property_tests:\n    executor: builder_pg_geth\n    environment:\n      MIX_ENV: test\n    steps:\n      - setup_elixir-omg_workspace\n      - restore_cache:\n          keys:\n            - v2-mix-cache-test-compile-{{ checksum \"mix.lock\" }}-{{ .Branch }}-{{ .Revision }}\n      - run:\n          name: Compile\n          command: mix compile\n      - run:\n          name: Property Test\n          command: |\n            export SHELL=/bin/bash\n            # no coverage calculation, coverage is on the other tests\n            mix test --only property\n\n  integration_tests:\n    executor: builder_pg_geth\n    environment:\n      MIX_ENV: test\n    steps:\n      - setup_elixir-omg_workspace\n      - restore_cache:\n          keys:\n            - v2-mix-cache-test-compile-{{ checksum \"mix.lock\" }}-{{ .Branch }}-{{ .Revision }}\n      - run:\n          name: Compile\n          command: mix compile\n      - install_rust\n      - run:\n          name: Integration Tests\n          command: |\n            # Slow, serial integration test, run nightly. Here to make sure the standard `mix test   --only integration` works\n            export SHELL=/bin/bash\n            mix test --only integration\n\n  dialyzer:\n    executor: builder_pg\n    steps:\n      - setup_elixir-omg_workspace\n      - restore_cache:\n          keys:\n            - v3-plt-cache-{{ \".tool-versions\" }}-{{ checksum \"mix.lock\" }}\n            - v3-plt-cache-{{ \".tool-versions\" }}-{{ checksum \"mix.exs\" }}\n            - v3-plt-cache-{{ \".tool-versions\" }}\n      - run:\n          name: Unpack PLT cache\n          command: |\n            mkdir -p _build_docker/test\n            cp plts/dialyxir*.plt _build_docker/test/ || true\n            mkdir -p ~/.mix\n            cp plts/dialyxir*.plt ~/.mix/ || true\n      - run: mix dialyzer --plt\n      - run:\n          name: Pack PLT cache\n          command: |\n            mkdir -p plts\n            cp _build_docker/test/dialyxir*.plt plts/\n            cp ~/.mix/dialyxir*.plt plts/\n      - save_cache:\n          key: v3-plt-cache-{{ \".tool-versions\" }}-{{ checksum \"mix.lock\" }}\n          paths:\n            - plts\n      - save_cache:\n          key: v3-plt-cache-{{ \".tool-versions\" }}-{{ checksum \"mix.exs\" }}\n          paths:\n            - plts\n      - save_cache:\n          key: v3-plt-cache-{{ \".tool-versions\" }}\n          paths:\n            - plts\n      - run: mix dialyzer --format short\n\n  test_docker_compose_release:\n    machine:\n      image: ubuntu-2004:202010-01\n    environment:\n      SNAPSHOT: SNAPSHOT_MIX_EXIT_PERIOD_SECONDS_120\n      LD_LIBRARY_PATH: /usr/local/lib\n      CLOUDSDK_PYTHON: /usr/bin/python\n      CHILD_CHAIN_URL: \"http://localhost:9656\"\n      FEE_CLAIMER_ADDRESS: \"0x3b9f4c1dd26e0be593373b1d36cee2008cbeb837\"\n    parallelism: 5\n    steps:\n      - checkout\n      - run:\n          name: \"Pull Submodules\"\n          command: |\n            git submodule init\n            git submodule update --remote\n      - run:\n          name: Setup data dir\n          command: |\n            [ -d data ] || mkdir data && chmod 777 data\n      - docker_login\n      - make_docker_images\n      - install_and_setup_gcloud\n      - run:\n          name: Start daemon services\n          command: make cabbage-start-services\n      - run:\n          name: Log daemon services\n          command: make cabbage-logs\n          background: true\n      - check_docker_status\n      - install_elixir\n      - run: sh .circleci/status.sh\n      - run:\n          name: Run specs\n          command: |\n            cd priv/cabbage\n            make install\n            make generate_api_code\n            mix deps.get\n      - run:\n          name: Run specs\n          working_directory: ~/project/priv/cabbage\n          environment:\n            MIX_ENV: test\n          command: |\n            mix compile\n      - run:\n          name: Run specs\n          working_directory: ~/project/priv/cabbage\n          command: |\n            TESTFILES=$(circleci tests glob \"apps/itest/test/itest/*_test.exs\" | circleci tests split --split-by=timings --show-counts)\n            echo ${TESTFILES}\n            mix test ${TESTFILES} --trace\n      - store_test_results:\n          path: ~/project/priv/cabbage/_build/test/lib/itest/\n\n  test_docker_compose_performance:\n    description: \"These are not actually performance tests, we're checking if the scripts work\"\n    machine:\n      image: ubuntu-2004:202010-01\n    environment:\n      PERF_IMAGE_NAME: \"omisego/perf:latest\"\n      STATIX_TAG: \"env:perf_circleci\"\n      LD_LIBRARY_PATH: /usr/local/lib\n      CLOUDSDK_PYTHON: /usr/bin/python\n    steps:\n      - checkout\n      - run:\n          name: Setup data dir\n          command: |\n            [ -d data ] || mkdir data && chmod 777 data\n      - docker_login\n      - make_docker_images\n      - install_and_setup_gcloud\n      - run:\n          name: Build perf docker image\n          command: make docker-perf IMAGE_NAME=$PERF_IMAGE_NAME\n      - install_elixir\n      - run:\n          name: Start daemon services\n          command: |\n            cd priv/perf\n            make start-services\n      - run:\n          name: docker services logs\n          background: true\n          command: |\n            cd priv/perf\n            make log-services\n      - run: sh .circleci/status.sh\n      - run:\n          name: Run load test\n          command: |\n            cd priv/perf\n            make init\n            export $(cat ../../localchain_contract_addresses.env | xargs)\n            make test\n      - run:\n          name: Show help information\n          command: docker run -it $PERF_IMAGE_NAME mix run -e \"LoadTest.TestRunner.run()\" -- help\n      - run:\n          name: Run perf smoke test (deposits)\n          command: |\n            docker run -it --env-file ./localchain_contract_addresses.env -e FEE_AMOUNT=1 --env DD_API_KEY --env DD_APP_KEY --env STATIX_TAG --network host $PERF_IMAGE_NAME mix run -e \"LoadTest.TestRunner.run()\" -- \"deposits\" 1 200\n      - run:\n          name: Run perf smoke test (transactions)\n          command: |\n            docker run -it --env-file ./localchain_contract_addresses.env -e FEE_AMOUNT=1 --env DD_API_KEY --env DD_APP_KEY --env STATIX_TAG --network host $PERF_IMAGE_NAME mix run -e \"LoadTest.TestRunner.run()\" -- \"transactions\" 1 200\n      - run:\n          name: (Perf) Format generated code and check for warnings\n          command: |\n            cd priv/perf\n            # run format ONLY on formatted code so that it cleans up quoted atoms because\n            # we cannot exclude folders to --warnings-as-errors\n            mix format apps/*_api/lib/*_api/model/*.ex\n            export $(cat ../../localchain_contract_addresses.env | xargs)\n            make format-code-check-warnings\n      - save_cache:\n          key: v2-mix-specs-cache-{{ .Branch }}-{{ checksum \"mix.lock\" }}\n          paths:\n            - \"priv/perf/deps\"\n\n      - run:\n          name: (Perf) Credo and formatting\n          command: |\n            cd priv/perf\n            mix do credo, format --check-formatted --dry-run\n\n  test_docker_compose_reorg:\n    machine:\n      image: ubuntu-2004:202010-01\n    environment:\n      REORG: true\n      LD_LIBRARY_PATH: /usr/local/lib\n      CLOUDSDK_PYTHON: /usr/bin/python\n    steps:\n      - checkout\n      - run:\n          name: \"Pull Submodules\"\n          command: |\n            git submodule init\n            git submodule update --remote\n      - run:\n          name: Setup data dir\n          command: |\n            [ -d data1 ] || mkdir data1 && chmod 777 data1\n            [ -d data2 ] || mkdir data2 && chmod 777 data2\n            [ -d data ] || mkdir data && chmod 777 data\n      - docker_login\n      - make_docker_images\n      - install_and_setup_gcloud\n      - run:\n          name: Start daemon services\n          command: |\n            make init_test_reorg\n            cp ./localchain_contract_addresses.env ./priv/cabbage/apps/itest/localchain_contract_addresses.env\n            docker-compose -f docker-compose.yml -f docker-compose.reorg.yml -f docker-compose.specs.yml up -d || (START_RESULT=$?; docker-compose logs; exit $START_RESULT;)\n      - run:\n          name: Log daemon services\n          command: make cabbage-logs-reorg\n          background: true\n      - check_docker_status\n      - install_elixir\n      - run: sh .circleci/status.sh\n      - run:\n          name: Print watcher logs\n          command: make cabbage-reorg-watcher-logs\n          background: true\n      - run:\n          name: Print watcher_info logs\n          command: make cabbage-reorg-watcher_info-logs\n          background: true\n      - run:\n          name: Print childchain logs\n          command: make cabbage-reorg-childchain-logs\n          background: true\n      - run:\n          name: Print geth logs\n          command: make cabbage-reorg-geth-logs\n          background: true\n      - run:\n          name: Print reorg logs\n          command: make cabbage-reorgs-logs\n          background: true\n      - run:\n          name: Run specs\n          command: |\n            cd priv/cabbage\n            make install\n            make generate_api_code\n            mix deps.get\n            mix test --only deposit --trace\n          no_output_timeout: 30m\n\n  test_barebone_release:\n    machine:\n      image: ubuntu-2004:202010-01\n    environment:\n      TERM: xterm-256color\n      LD_LIBRARY_PATH: /usr/local/lib\n      CLOUDSDK_PYTHON: /usr/bin/python\n    steps:\n      - checkout\n      - run:\n          name: \"Pull Submodules\"\n          command: |\n            git submodule init\n            git submodule update --remote\n      - run: echo 'export PATH=~/.cargo/bin:$PATH' >> $BASH_ENV\n      - install_and_setup_gcloud\n      - docker_login\n      - run:\n          name: Start geth, postgres, feefeed and pull in blockchain snapshot\n          command: make start-services\n          background: true\n      - run: echo 'export PATH=~/.cargo/bin:$PATH' >> $BASH_ENV\n      - install_elixir\n      - install_deps\n      - run: make install-hex-rebar\n      - restore_cache:\n          key: v1-dev-release-cache-{{ checksum \"mix.lock\" }}\n      - run:\n          name: Compile\n          command: |\n            set -e\n            make deps-elixir-omg\n            mix compile\n          no_output_timeout: 2400\n      - save_cache:\n          key: v1-dev-release-cache-{{ checksum \"mix.lock\" }}\n          paths:\n            - \"deps_docker/\"\n            - \"deps/\"\n            - \"_build/dev/\"\n            - \"_build/dev/\"\n      - run:\n          name: Run Watcher\n          command: |\n            set -e\n            make start-watcher OVERRIDING_START=start_iex OVERRIDING_VARIABLES=./bin/variables_test_barebone\n          background: true\n          no_output_timeout: 2400\n      - run:\n          name: Run Watcher Info\n          command: |\n            set -e\n            make start-watcher_info OVERRIDING_START=start_iex OVERRIDING_VARIABLES=./bin/variables_test_barebone\n          background: true\n          no_output_timeout: 2400\n      - run:\n          name: Print docker and process states\n          command: |\n            docker ps\n            ps axww | grep watcher\n            ps axww | grep watcher_info\n            ps axww | grep child_chain\n      - run:\n          name: Has Watcher started?\n          command: |\n            attempt_counter=0\n            max_attempts=25\n            until $(curl --output /dev/null --silent --head --fail http://localhost:7434/alarm.get); do\n              if [ ${attempt_counter} -eq ${max_attempts} ];then\n                echo \"Max attempts reached\"\n                exit 1\n              fi\n              printf '.'\n              attempt_counter=$(($attempt_counter+1))\n              sleep 5\n            done\n      - run:\n          name: Has Watcher Info started?\n          command: |\n            attempt_counter=0\n            max_attempts=25\n            until $(curl --output /dev/null --silent --head --fail http://localhost:7534/alarm.get); do\n              if [ ${attempt_counter} -eq ${max_attempts} ];then\n                echo \"Max attempts reached\"\n                exit 1\n              fi\n              printf '.'\n              attempt_counter=$(($attempt_counter+1))\n              sleep 5\n            done\n\n\n  publish_watcher:\n    machine:\n      image: ubuntu-2004:202010-01\n    environment:\n      WATCHER_IMAGE_NAME: \"omisego/watcher\"\n    steps:\n      - checkout\n      - run: make docker-watcher WATCHER_IMAGE_NAME=$WATCHER_IMAGE_NAME\n      - run:\n          name: \"cp release\"\n          command: |\n            mkdir current_release/\n            cp _build_docker/prod/watcher-$(git describe --tags).tar.gz current_release/\n            md5sum current_release/watcher-$(git describe --tags).tar.gz | awk '{print $1}' >> current_release/md5\n      - store_artifacts:\n          path: current_release/\n      - run: IMAGE_NAME=$WATCHER_IMAGE_NAME sh .circleci/ci_publish.sh\n\n  publish_perf:\n    machine:\n      image: ubuntu-2004:202010-01\n    environment:\n      PERF_IMAGE_NAME: \"omisego/perf\"\n    steps:\n      - checkout\n      - run: make docker-perf IMAGE_NAME=$PERF_IMAGE_NAME\n      - run: IMAGE_NAME=$PERF_IMAGE_NAME sh .circleci/ci_publish.sh\n\n  publish_watcher_info:\n    machine:\n      image: ubuntu-2004:202010-01\n    environment:\n      WATCHER_INFO_IMAGE_NAME: \"omisego/watcher_info\"\n    steps:\n      - checkout\n      - run: make docker-watcher_info WATCHER_INFO_IMAGE_NAME=$WATCHER_INFO_IMAGE_NAME\n      - run:\n          name: \"cp release\"\n          command: |\n            mkdir current_release/\n            cp _build_docker/prod/watcher_info-$(git describe --tags).tar.gz current_release/\n            md5sum current_release/watcher_info-$(git describe --tags).tar.gz | awk '{print $1}' >> current_release/md5\n      - store_artifacts:\n          path: current_release/\n      - run: IMAGE_NAME=$WATCHER_INFO_IMAGE_NAME sh .circleci/ci_publish.sh\n\n  increase_chart_version_watcher_master:\n    docker:\n      - image: cimg/base:2020.01\n    environment:\n      CHART_NAME: watcher\n      HELM_CHART_REPO: helm-development\n      UPDATE_DEV: true\n    steps:\n      - checkout\n      - run: APP_VERSION=\"$(echo \"$CIRCLE_SHA1\" | head -c 7)\" sh .circleci/ci_increase_chart_version.sh\n\n  increase_chart_version_watcher_info_master:\n    docker:\n      - image: cimg/base:2020.01\n    environment:\n      CHART_NAME: watcher-info\n      HELM_CHART_REPO: helm-development\n      UPDATE_DEV: true\n    steps:\n      - checkout\n      - run: APP_VERSION=\"$(echo \"$CIRCLE_SHA1\" | head -c 7)\" sh .circleci/ci_increase_chart_version.sh\n\n  increase_chart_version_watcher_release:\n    docker:\n      - image: cimg/base:2020.01\n    environment:\n      CHART_NAME: watcher\n      HELM_CHART_REPO: helm-development\n      UPDATE_DEV: false\n    steps:\n      - checkout\n      - run: APP_VERSION=\"${CIRCLE_TAG#*v}\" sh .circleci/ci_increase_chart_version.sh\n\n  increase_chart_version_watcher_info_release:\n    docker:\n      - image: cimg/base:2020.01\n    environment:\n      CHART_NAME: watcher-info\n      HELM_CHART_REPO: helm-development\n      UPDATE_DEV: false\n    steps:\n      - checkout\n      - run: APP_VERSION=\"${CIRCLE_TAG#*v}\" sh .circleci/ci_increase_chart_version.sh\n\n  release:\n    docker:\n      - image: node:15.2.1\n    steps:\n      - checkout\n      - run: npx -y semantic-release@17.2.3\n\n  coveralls_report:\n    docker:\n      - image: omisegoimages/elixir-omg-circleci:v1.8-20190129-02\n        environment:\n          MIX_ENV: test\n    steps:\n      - run:\n          name: Tell coveralls.io build is done\n          command: curl -k https://coveralls.io/webhook?repo_token=$COVERALLS_REPO_TOKEN -d \"payload[build_num]=$CIRCLE_WORKFLOW_WORKSPACE_ID&payload[status]=done\"\n\n  notify_services:\n    executor: builder_pg\n    steps:\n      - run:\n          name: Send development deployment markers\n          command: |\n            curl -X POST -H 'Content-type: application/json' -d '{\"title\": \"Starting Service\", \"text\": \"Starting with git SHA '\"$CIRCLE_SHA1\"'\", \"alert_type\": \"info\" }' 'https://app.datadoghq.com/api/v1/events?api_key='\"$DD_API_KEY\"''\n            curl -X POST -H 'Content-type: application/json' -H 'Authorization: Bearer '\"$SENTRY_TOKEN\"'' -d '{\"projects\": [\"elixir-omg\"], \"ref\": \"'\"$CIRCLE_SHA1\"'\", \"version\": \"Watcher-ChildChain-'\"$CIRCLE_SHA1\"'\"}' 'https://sentry.io/api/0/organizations/omisego/releases/'\n            GH_URL=\"https://github.com/omisego/elixir-omg/tree/${CIRCLE_BRANCH}\"\n            CIRCLE_URL=\"https://circleci.com/gh/omisego/elixir-omg/${CIRCLE_BUILD_NUM}\"\n            WORKFLOW_URL=\"https://circleci.com/workflow-run/${CIRCLE_WORKFLOW_ID}\"\n            MESSAGE=\"omisego/elixir-omg branch ${CIRCLE_BRANCH} has deployed a new version\"\n            RICH_MESSAGE=\"*omisego/elixir-omg* branch *${CIRCLE_BRANCH}* has been deployed\"\n            curl -X POST -H 'Content-Type: application/json' --data \"{ \\\n              \\\"attachments\\\": [ \\\n                { \\\n                  \\\"fallback\\\": \\\"${MESSAGE}\\\", \\\n                  \\\"text\\\": \\\"Deployment: ${RICH_MESSAGE}\\\", \\\n                  \\\"mrkdwn\\\": true, \\\n                  \\\"color\\\": \\\"#2ced49\\\", \\\n                  \\\"fields\\\": [ \\\n                    { \\\n                      \\\"title\\\": \\\"Git SHA\\\", \\\n                      \\\"value\\\": \\\"<$GH_URL|$CIRCLE_SHA1>\\\", \\\n                      \\\"short\\\": true \\\n                    }, { \\\n                      \\\"title\\\": \\\"Branch\\\", \\\n                      \\\"value\\\": \\\"<$GH_URL|$CIRCLE_BRANCH>\\\", \\\n                      \\\"short\\\": true \\\n                    }, { \\\n                      \\\"title\\\": \\\"Build\\\", \\\n                      \\\"value\\\": \\\"<$CIRCLE_URL|$CIRCLE_BUILD_NUM>\\\", \\\n                      \\\"short\\\": true \\\n                    } \\\n                  ] \\\n                } \\\n              ] \\\n            }\" ${SLACK_WEBHOOK}\n\nworkflows:\n  version: 2\n  nightly:\n    triggers:\n      - schedule:\n          cron: \"30 8 * * 1-5\"\n          filters:\n            branches:\n              only:\n                - master\n    jobs:\n      - build\n      - integration_tests:\n          requires: [build]\n      - barebuild_macos\n      #- test_barebone_release\n  build-test-deploy:\n    jobs:\n      - build:\n          filters: &all_branches_and_tags\n            branches:\n              only: /.+/\n            tags:\n              only: /.+/\n      # - test_barebone_release:\n      #     filters: *all_branches_and_tags\n      - notify_services:\n          requires:\n            - increase_chart_version_watcher_master\n            - increase_chart_version_watcher_info_master\n          filters:\n            branches:\n              only:\n                - master\n      - coveralls_report:\n          requires:\n            - watcher_coveralls_and_integration_tests\n            - watcher_info_coveralls_and_integration_tests\n            - common_coveralls_and_integration_tests\n            - test\n      - watcher_coveralls_and_integration_tests:\n          requires: [build]\n          filters: *all_branches_and_tags\n      - watcher_info_coveralls_and_integration_tests:\n          requires: [build]\n          filters: *all_branches_and_tags\n      - common_coveralls_and_integration_tests:\n          requires: [build]\n          filters: *all_branches_and_tags\n      - test_docker_compose_release:\n          filters: *all_branches_and_tags\n      # - test_docker_compose_performance:\n      #     filters: *all_branches_and_tags\n      - test_docker_compose_reorg:\n          filters:\n            branches:\n              only:\n                - master\n                - master-v2\n      - audit_deps:\n          requires: [build]\n          filters: *all_branches_and_tags\n      - lint:\n          requires: [build]\n          filters: *all_branches_and_tags\n      - sobelow:\n          requires: [build]\n          filters: *all_branches_and_tags\n      - dialyzer:\n          requires: [build]\n          filters: *all_branches_and_tags\n      - test:\n          requires: [build]\n          filters: *all_branches_and_tags\n      - property_tests:\n          requires: [build]\n          filters: &master_and_version_branches_and_all_tags\n            branches:\n              only:\n                - master\n                # vMAJOR.MINOR (e.g. v0.1, v0.2, v1.0, v2.1, etc.)\n                - /^v[0-9]+\\.[0-9]+/\n            tags:\n              only:\n                - /.+/\n      - watcher_mix_based_childchain:\n          filters: *all_branches_and_tags\n      - publish_watcher:\n          requires:\n            [\n             # test_barebone_release,\n              test_docker_compose_release,\n              watcher_coveralls_and_integration_tests,\n              watcher_info_coveralls_and_integration_tests,\n              common_coveralls_and_integration_tests,\n              test,\n           #   property_tests,\n              dialyzer,\n              lint,\n              audit_deps\n            ]\n          filters: &master_and_version_branches_and_all_tags\n            branches:\n              only:\n                - master\n                # vMAJOR.MINOR (e.g. v0.1, v0.2, v1.0, v2.1, etc.)\n                - /^v[0-9]+\\.[0-9]+/\n            tags:\n              only:\n                - /.+/\n      - publish_watcher_info:\n          requires:\n            [\n             # test_barebone_release,\n              test_docker_compose_release,\n              watcher_coveralls_and_integration_tests,\n              watcher_info_coveralls_and_integration_tests,\n              common_coveralls_and_integration_tests,\n              test,\n              property_tests,\n              dialyzer,\n              lint,\n              audit_deps\n            ]\n          filters: *master_and_version_branches_and_all_tags\n\n      # - publish_perf:\n      #     requires: [test_docker_compose_performance]\n      #     filters:\n      #       branches:\n      #         only:\n      #           - master\n      #           # vMAJOR.MINOR (e.g. v0.1, v0.2, v1.0, v2.1, etc.)\n      #           - /^v[0-9]+\\.[0-9]+/\n      #       tags:\n      #         only:\n      #           - /.+/\n      # Increase chart version for master, this will end up trigger deployment on dev\n      - increase_chart_version_watcher_master:\n          requires: [publish_watcher, publish_watcher_info]\n          filters:\n            branches:\n              only:\n                - master\n      - increase_chart_version_watcher_info_master:\n          requires: [publish_watcher, publish_watcher_info]\n          filters:\n            branches:\n              only:\n                - master\n      # Increase chart version for new release\n      - increase_chart_version_watcher_release:\n          requires: [publish_watcher, publish_watcher_info]\n          filters: &only_release_tag\n            branches:\n              ignore: /.*/\n            tags:\n              only:\n                # eg. v1.0.3-pre.0, v1.0.3, ...\n                - /^v[0-9]+\\.[0-9]+\\.[0-9]+.*/\n      - increase_chart_version_watcher_info_release:\n          requires: [publish_watcher, publish_watcher_info]\n          filters: *only_release_tag\n      - release:\n          requires: [\n           # test_barebone_release,\n            test_docker_compose_release,\n            watcher_coveralls_and_integration_tests,\n            watcher_info_coveralls_and_integration_tests,\n            common_coveralls_and_integration_tests,\n            test,\n            property_tests,\n            dialyzer,\n            lint,\n            audit_deps\n          ]\n          context:\n          - shared-semantic-release\n          filters:\n            branches:\n              only: /^master$/\n            tags:\n              ignore: /.*/\n"
  },
  {
    "path": ".circleci/status.sh",
    "content": "#!/bin/sh\n\nretries=0\nstatus=1\n\n# Retries roughly every 5 seconds up to 2 minutes\nwhile [ $retries -lt 24 ];  do\n  alarms=$(make get-alarms)\n  status=$?\n  echo ${alarms}\n\n  if [ \"$status\" -eq \"0\" ]; then\n    exit 0\n  fi\n\n  retries=$(( ${retries} + 1 ))\n  sleep 5\ndone\n\nexit ${status}\n"
  },
  {
    "path": ".circleci/test_runner.py",
    "content": "#!/usr/bin/python3\nimport logging\nimport os\nimport sys\nimport time\n\nimport requests\n\n\ndef create_job(test_runner: str) -> str:\n    ''' Create a job in the test runner. Returns the job ID\n    '''\n    payload = {\n        \"job\": {\n            \"command\": \"npm\",\n            \"args\": [\"run\", \"ci-test-fast\"],\n            \"cwd\": \"/home/omg/omg-js\"\n        }\n    }\n    try:\n        request = requests.post(test_runner + '/job', json=payload)\n    except ConnectionError:\n        logging.critical('Could not connect to the test runner')\n        sys.exit(1)  # Return a non-zero exit code so CircleCI fails\n\n    logging.info('Job created: {}'.format(\n        request.content.decode('utf-8'))\n    )\n    return request.content.decode('utf-8')\n\n\ndef check_job_completed(test_runner: str, job_id: str):\n    ''' Get the status of the job from the test runner\n    '''\n    start_time = int(time.time())\n    while True:\n        if start_time >= (start_time + 360):\n            logging.critical('Test runner did not complete within six minutes')\n            sys.exit(1)  # Return a non-zero exit code so CircleCI fails\n        try:\n            request = requests.get(\n                '{}/job/{}/status'.format(test_runner, job_id),\n                headers={'Cache-Control': 'no-cache'}\n            )\n        except ConnectionError:\n            logging.critical('Could not connect to the test runner')\n            sys.exit(1)  # Return a non-zero exit code so CircleCI fails\n        if 'Exited' in request.content.decode('utf-8'):\n            logging.info('Job completed successfully')\n            break\n\n\ndef check_job_result(test_runner: str, job_id: str):\n    ''' Check the result of the job. This is the result of the tests that are\n    executed against the push. If they all pass 'true' is returned.\n    '''\n    try:\n        request = requests.get(\n            test_runner + '/job/{}/success'.format(job_id),\n            headers={'Cache-Control': 'no-cache'}\n        )\n    except ConnectionError:\n        logging.critical('Could not connect to the test runner')\n        sys.exit(1)\n    if 'true' in request.content.decode('utf-8'):\n        logging.info('Tests completed successfully')\n\n\ndef get_envs() -> dict:\n    ''' Get the environment variables for the workflow\n    '''\n    envs = {}\n    test_runner = os.getenv('TEST_RUNNER_SERVICE')\n    if test_runner is None:\n        logging.critical('Test runner service ENV missing')\n        sys.exit(1)  # Return a non-zero exit code so CircleCI fails\n\n    envs['TEST_RUNNER_SERVICE'] = test_runner\n    return envs\n\n\ndef start_workflow():\n    ''' Get the party started\n    '''\n    logging.info('Workflow started')\n    envs = get_envs()\n    job_id = str(create_job(envs['TEST_RUNNER_SERVICE']))\n    check_job_completed(envs['TEST_RUNNER_SERVICE'], job_id)\n    check_job_result(envs['TEST_RUNNER_SERVICE'], job_id)\n\n\ndef set_logger():\n    ''' Sets the logging module parameters\n    '''\n    root = logging.getLogger('')\n    for handler in root.handlers:\n        root.removeHandler(handler)\n    format = '%(asctime)s %(levelname)-8s:%(message)s'\n    logging.basicConfig(format=format, level='INFO')\n\n\nif __name__ == '__main__':\n    set_logger()\n    start_workflow()\n"
  },
  {
    "path": ".formatter.exs",
    "content": "# Used by \"mix format\"\n[\n  inputs: [\n    \"config/*.exs\",\n    \"rel/config.exs\",\n    \"mix.exs\",\n    \"apps/*/mix.exs\",\n    \"apps/*/{lib,test,config}/**/*.{ex,exs}\",\n    \"priv/*/config/config.exs\",\n    \"priv/*/mix.exs\",\n  ]\n  ++ (Path.wildcard(\"priv/*/apps/*/mix.exs\") -- Enum.flat_map(\n    [\"priv/*/apps/watcher_info_api/mix.exs\", \"priv/*/apps/watcher_security_critical_api/mix.exs\"],\n    &Path.wildcard/1\n  ))\n  ++ (Path.wildcard(\"priv/*/apps/*/{lib,test,config}/**/*.{ex,exs}\") -- (Path.wildcard(\"priv/*/apps/watcher_info_api/{lib,test,config}/**/*.{ex,exs}\") ++ Path.wildcard(\"priv/*/apps/watcher_security_critical_api/{lib,test,config}/**/*.{ex,exs}\"))),\n  line_length: 120\n]\n"
  },
  {
    "path": ".githooks/pre-commit",
    "content": "#!/bin/sh\necho \"Is your code formatted?\"\nexec mix format --check-formatted\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": ":clipboard: Add associated issues, tickets, docs URL here.\n\n## Overview\n\nDescribe what your Pull Request is about in a few sentences.\n\n## Changes\n\nDescribe your changes and implementation choices. More details make PRs easier to review.\n\n- Change 1\n- Change 2\n- ...\n\n## Testing\n\nDescribe how to test your new feature/bug fix and if possible, a step by step guide on how to demo this.\n"
  },
  {
    "path": ".github/workflows/auto-merge-pr.yml",
    "content": "name: Auto Merge PR without conflicts\n\non:\n  pull_request:\n    branches: [ master-v2 ]\n\njobs:\n  auto-merge-pr:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v2\n      - name: Attach Label to PR\n        run: |\n          set -o xtrace\n\n          curl -X PATCH \"https://api.github.com/repos/omgnetwork/elixir-omg/issues/${{ github.event.pull_request.number }}\" \\\n          -H \"Accept: application/vnd.github.v3+json\" \\\n          -H \"Authorization: token ${{ secrets.HOUSE_KEEPER_BOT_TOKEN }}\" \\\n          --data \"{\\\"labels\\\": [\\\"sync master-v2\\\"]}\"\n\n      - name: Merge PR\n        if: github.head_ref == 'master'\n        run: |\n          set -o xtrace\n\n          function check_merge() {\n          curl \"https://api.github.com/repos/omgnetwork/elixir-omg/pulls/${{ github.event.pull_request.number }}\" \\\n          -H \"Accept: application/vnd.github.v3+json\" \\\n          -H \"Authorization: token ${{ secrets.HOUSE_KEEPER_BOT_TOKEN }}\" |\n          jq -r '.mergeable'\n          }  \n\n          until [ \"$(check_merge)\" = true -o \"$(check_merge)\" = false ]; do\n            echo -n 'waiting...'\n            sleep 10\n          done\n\n          if [ \"$(check_merge)\" = true ]; then\n             curl -X PUT \"https://api.github.com/repos/omgnetwork/elixir-omg/pulls/${{ github.event.pull_request.number }}/merge\" \\\n             -H \"Accept: application/vnd.github.v3+json\" \\\n             -H \"Authorization: token ${{ secrets.HOUSE_KEEPER_BOT_TOKEN }}\" \\\n             --data \"{\\\"commit_title\\\": \\\"auto-merged ${{ github.event.pull_request.number }}\\\"}\"\n          else\n             echo \"PR unmerged\"\n          fi\n"
  },
  {
    "path": ".github/workflows/auto-pr-for-branch-syncing.yml",
    "content": "name: Auto PR for syncing master to master-v2\n\non:\n  push:\n    branches: [master]\n\njobs:\n  auto-pr-for-branch-syncing:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v2\n      - name: Create Pull Request\n        run: |\n          set -o xtrace\n\n          readonly FROM_BRANCH=\"master\"\n          readonly TO_BRANCH=\"master-v2\"\n          readonly TITLE=\"sync: auto syncing from ${FROM_BRANCH} to ${TO_BRANCH}\"\n          readonly BODY=\"Time to sync \\`${TO_BRANCH}\\` with updates from \\`${FROM_BRANCH}\\`!\"\n\n          curl -X POST \"https://api.github.com/repos/omgnetwork/elixir-omg/pulls\" \\\n          -H \"Accept: application/vnd.github.v3+json\" \\\n          -H \"Authorization: token ${{ secrets.HOUSE_KEEPER_BOT_TOKEN }}\" \\\n          --data \"{\\\"title\\\": \\\"${TITLE}\\\", \\\"head\\\": \\\"${FROM_BRANCH}\\\", \\\"base\\\": \\\"${TO_BRANCH}\\\", \\\"body\\\": \\\"${BODY}\\\"}\"\n"
  },
  {
    "path": ".github/workflows/enforce-changelog-labels.yml",
    "content": "name: Enforce changelog labels\n\non:\n  pull_request:\n    types: [opened, labeled, unlabeled, synchronize, reopened]\n    branches: [master]\n\njobs:\n  enforce-changelog-label:\n    runs-on: ubuntu-latest\n    env:\n      # When updating the labels here, also update the `configure-sections` of the `.github_changelog_generator` file\n      ONE_OF_LABELS: \"api|enhancement|breaking|bug|chore|documentation\"\n    steps:\n      - name: Check the PR for a changelog label\n        id: check-changelog-label\n        run: |\n          set -o xtrace\n          # Using the issues API instead of pulls because it can return only the labels\n          curl \"${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/issues/${{ github.event.pull_request.number }}/labels\" \\\n          | grep -o '\"name\": \"[^\"]*' \\\n          | cut -d'\"' -f4 \\\n          | grep -E $ONE_OF_LABELS \\\n          || (echo \"::error::The PR is missing a valid changelog label. Label the PR with one of: ${ONE_OF_LABELS//|/, }.\" && exit 1)\n"
  },
  {
    "path": ".github_changelog_generator",
    "content": "# Issue/PR filter\nrelease-branch=master\nsince-tag=v0.4.8\nexclude-tags-regex=.*-pre.*\nunreleased=true\n\nissues=false\npull-requests=true\npr-wo-labels=true\n\n# Categories\npr-label=### Untagged pull requests\n\n# When updating the sections here, also update the `ONE_OF_LABELS` env vars in the `.github/workflows/enforce-changelog-labels.yml` file\nconfigure-sections={\"api\":{\"prefix\":\"### API changes\",\"labels\":[\"api\"]}, \"breaking\":{\"prefix\":\"### Breaking changes\",\"labels\":[\"breaking\"]}, \"enhancement\":{\"prefix\":\"### Enhancements\",\"labels\":[\"enhancement\"]}, \"bug\":{\"prefix\":\"### Bug fixes\",\"labels\":[\"bug\"]}, \"chore\":{\"prefix\":\"### Chores\",\"labels\":[\"chore\"]}, \"documentation\":{\"prefix\":\"### Documentation updates\",\"labels\":[\"documentation\"]}}\n\n"
  },
  {
    "path": ".gitignore",
    "content": "# The directory Mix will write compiled artifacts to.\n/_build/\n\n# The directory Mix will write compiled artifacts to when in docker.\n/_build_docker/\n\n# If you run \"mix test --cover\", coverage assets end up here.\n/cover/\n\n# app-specific coverage results go to their respective apps\n/apps/*/cover/\n\n# The directory Mix downloads your dependencies sources to when in docker.\n/deps_docker/\n\n# The directory Mix downloads your dependencies sources to.\n/deps/\n\n# Where 3rd-party dependencies like ExDoc output generated docs.\n/doc/\n\n# Ignore .fetch files in case you like to edit your project deps locally.\n/.fetch\n\n# If the VM crashes, it generates a dump, let's ignore it too.\nerl_crash.dump\n\n# Also ignore archive artifacts (built via \"mix archive.build\").\n*.ez\n\n# Dev sqllite db\n*ecto_simple.sqlite3*\n\n# Developers config file\nyour_config_file.exs\n\n# results of perftesting and profiling with default arguments\nfprof.trace\nperf_result_*\n\n*.statistics\n\n# Common virtualenv directory\nenv/\n\n# Elixirls data directory\n.elixir_ls\n\n# VS Code config file\n.vscode/\n\n# Docker\nsrc/\ndocker-compose.override.yml\n\n# Sobelow\n.sobelow\n\n#generated applications and source\npriv/apps/childchain_api\npriv/apps/watcher_info_api\npriv/apps/watcher_security_critical_api\n\n# where Geth gets it's snapshot data\ndata/\ndata1/\ndata2/\n# local test setup\nlocalchain_contract_addresses.env\n\n\n# the famous mac .DS_Store\n.DS_Store\n\n# IntelliJ files\n.idea/\n*.iml\n\n#vs code\n.tool_versions\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"priv/cabbage\"]\n  path = priv/cabbage\n  url = https://github.com/omgnetwork/specs.git\n  branch = master\n\n"
  },
  {
    "path": ".releaserc.yaml",
    "content": "plugins:\n- - '@semantic-release/commit-analyzer'\n  - preset: 'angular'\n    releaseRules:\n      - type: 'refactor'\n        release: 'patch'\n      - type: 'style'\n        release: 'patch'\n      - type: 'feat'\n        release: 'patch'\n      - type: 'chore'\n        release: 'patch'\n      - breaking: true\n        release: 'minor'\n- '@semantic-release/release-notes-generator'\n- '@semantic-release/github'\ntagFormat: 'v${version}'\ndryRun: true\n# observing that job would not be running as it was considered as from PR branch\n# looks that unfortunately that is how it is designed:\n# https://github.com/semantic-release/semantic-release/issues/1166#issuecomment-500094323\n# this workaround is from:\n# https://github.com/semantic-release/semantic-release/issues/1074#issuecomment-696922883\nci: false\n"
  },
  {
    "path": ".tool-versions",
    "content": "elixir 1.11.2\nerlang 23.1.4\nrust 1.46.0\n"
  },
  {
    "path": "AUTHORS",
    "content": "OMG Network Pte Ltd\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\n## [v1.0.5](https://github.com/omgnetwork/elixir-omg/tree/v1.0.5) (2020-10-01)\n\n[Full Changelog](https://github.com/omgnetwork/elixir-omg/compare/v1.0.4-pre.2...v1.0.5)\n\n### Enhancements\n\n- deposits performance tests bot [\\#1745](https://github.com/omgnetwork/elixir-omg/pull/1745) ([ayrat555](https://github.com/ayrat555))\n- feat: handle in-flight exits deletions [\\#1701](https://github.com/omgnetwork/elixir-omg/pull/1701) ([pgebal](https://github.com/pgebal))\n- feat: comply with new Infura API [\\#1754](https://github.com/omgnetwork/elixir-omg/pull/1754) ([pgebal](https://github.com/pgebal))\n\n### Bug fixes\n\n- fix: handle metrics for in flight exit deleted processor [\\#1742](https://github.com/omgnetwork/elixir-omg/pull/1742) ([pgebal](https://github.com/pgebal))\n- revert: reverts ife deletion commits [\\#1725](https://github.com/omgnetwork/elixir-omg/pull/1725) ([pgebal](https://github.com/pgebal))\n- fix: set :last\\_ife\\_exit\\_deleted\\_eth\\_height on deplyment if it's not set yet [\\#1720](https://github.com/omgnetwork/elixir-omg/pull/1720) ([pgebal](https://github.com/pgebal))\n- fix: fix in-flight exit deleted bug and add tests [\\#1714](https://github.com/omgnetwork/elixir-omg/pull/1714) ([pgebal](https://github.com/pgebal))\n- fix: block submission stall monitor should ignore block\\_submitting that are already mined [\\#1703](https://github.com/omgnetwork/elixir-omg/pull/1703) ([unnawut](https://github.com/unnawut))\n- fix: recheck PR label on synchronize and reopen [\\#1748](https://github.com/omgnetwork/elixir-omg/pull/1748) ([unnawut](https://github.com/unnawut))\n\n### Chores\n\n- Chore: parallelize tests by tags [\\#1744](https://github.com/omgnetwork/elixir-omg/pull/1744) ([ayrat555](https://github.com/ayrat555))\n- Chore: use exexec from upstream [\\#1743](https://github.com/omgnetwork/elixir-omg/pull/1743) ([ayrat555](https://github.com/ayrat555))\n- move dev env deployment job to helm repo [\\#1738](https://github.com/omgnetwork/elixir-omg/pull/1738) ([boolafish](https://github.com/boolafish))\n- Inomurko/remove child chain [\\#1737](https://github.com/omgnetwork/elixir-omg/pull/1737) ([InoMurko](https://github.com/InoMurko))\n- Kevsul/standard exit perf test [\\#1732](https://github.com/omgnetwork/elixir-omg/pull/1732) ([kevsul](https://github.com/kevsul))\n- update change log v1.0.4 [\\#1731](https://github.com/omgnetwork/elixir-omg/pull/1731) ([jarindr](https://github.com/jarindr))\n- chore: add test for 64\\_000 txs block hash [\\#1729](https://github.com/omgnetwork/elixir-omg/pull/1729) ([ayrat555](https://github.com/ayrat555))\n- Allow to run docker-compose without feefeed docker [\\#1726](https://github.com/omgnetwork/elixir-omg/pull/1726) ([boolafish](https://github.com/boolafish))\n- rm mix based chch part 1 [\\#1716](https://github.com/omgnetwork/elixir-omg/pull/1716) ([InoMurko](https://github.com/InoMurko))\n- feat: reintroduce automated changelog [\\#1708](https://github.com/omgnetwork/elixir-omg/pull/1708) ([unnawut](https://github.com/unnawut))\n- add feefeed docker to elixir-omg setup [\\#1700](https://github.com/omgnetwork/elixir-omg/pull/1700) ([boolafish](https://github.com/boolafish))\n- move omg\\_performance json rpc tests to perf project [\\#1691](https://github.com/omgnetwork/elixir-omg/pull/1691) ([ayrat555](https://github.com/ayrat555))\n- chore: bump version to 1.0.4 [\\#1751](https://github.com/omgnetwork/elixir-omg/pull/1751) ([boolafish](https://github.com/boolafish))\n- Chore: try to fix flaky reorg tests [\\#1739](https://github.com/omgnetwork/elixir-omg/pull/1739) ([ayrat555](https://github.com/ayrat555))\n- feat: transaction.create optimisation [\\#1683](https://github.com/omgnetwork/elixir-omg/pull/1683) ([okalouti](https://github.com/okalouti))\n\n## [v1.0.4](https://github.com/omgnetwork/elixir-omg/tree/v1.0.4) (2020-09-03)\n\n[Full Changelog](https://github.com/omgnetwork/elixir-omg/compare/v1.0.4-pre.1...v1.0.4)\n\n### API changes\n\n-  /block.validate endpoint [\\#1668](https://github.com/omgnetwork/elixir-omg/pull/1668) ([okalouti](https://github.com/okalouti))\n\n### Enhancements\n\n- Block Validation: New Checks [\\#1693](https://github.com/omgnetwork/elixir-omg/pull/1693) ([okalouti](https://github.com/okalouti))\n- feat: configurable DB pool size, queue target and queue interval [\\#1689](https://github.com/omgnetwork/elixir-omg/pull/1689) ([unnawut](https://github.com/unnawut))\n- feat: block queue metrics and stalled submission alarm [\\#1649](https://github.com/omgnetwork/elixir-omg/pull/1649) ([unnawut](https://github.com/unnawut))\n\n### Bug fixes\n\n- corrrectly serialize PIDs in alarms.get [\\#1678](https://github.com/omgnetwork/elixir-omg/pull/1678) ([ayrat555](https://github.com/ayrat555))\n- account.get\\_exitable\\_utxos is unaware of in-flight exited inputs [\\#1676](https://github.com/omgnetwork/elixir-omg/pull/1676) ([pnowosie](https://github.com/pnowosie))\n- Add missing clause on witness validation check [\\#1656](https://github.com/omgnetwork/elixir-omg/pull/1656) ([mederic-p](https://github.com/mederic-p))\n- fix: unexpected http method [\\#1651](https://github.com/omgnetwork/elixir-omg/pull/1651) ([ripzery](https://github.com/ripzery))\n\n### Chores\n\n- bump version 1.0.4 [\\#1722](https://github.com/omgnetwork/elixir-omg/pull/1722) ([jarindr](https://github.com/jarindr))\n- auto trigger chart version bump [\\#1695](https://github.com/omgnetwork/elixir-omg/pull/1695) ([boolafish](https://github.com/boolafish))\n- bump phoenix [\\#1680](https://github.com/omgnetwork/elixir-omg/pull/1680) ([InoMurko](https://github.com/InoMurko))\n- chore: increase timeouts for childchain healthchecks [\\#1671](https://github.com/omgnetwork/elixir-omg/pull/1671) ([ayrat555](https://github.com/ayrat555))\n- fix integration tests [\\#1654](https://github.com/omgnetwork/elixir-omg/pull/1654) ([ayrat555](https://github.com/ayrat555))\n- feat: pin elixir and erlang versions for asdf [\\#1648](https://github.com/omgnetwork/elixir-omg/pull/1648) ([unnawut](https://github.com/unnawut))\n- chore: change log and version file change for v1.0.3 \\(\\#1638\\) [\\#1639](https://github.com/omgnetwork/elixir-omg/pull/1639) ([boolafish](https://github.com/boolafish))\n- use cabbage tests from a separate repo [\\#1636](https://github.com/omgnetwork/elixir-omg/pull/1636) ([ayrat555](https://github.com/ayrat555))\n- set OMG.State GenServer timeout to 10s [\\#1517](https://github.com/omgnetwork/elixir-omg/pull/1517) ([achiurizo](https://github.com/achiurizo))\n\n### Documentation updates\n\n- v.1.0.4 change log [\\#1719](https://github.com/omgnetwork/elixir-omg/pull/1719) ([jarindr](https://github.com/jarindr))\n- docs: extend description of running cabbage tests [\\#1658](https://github.com/omgnetwork/elixir-omg/pull/1658) ([pnowosie](https://github.com/pnowosie))\n\n## [v1.0.3](https://github.com/omgnetwork/elixir-omg/tree/v1.0.3) (2020-07-09)\n\n[Full Changelog](https://github.com/omgnetwork/elixir-omg/compare/v1.0.3-pre.2...v1.0.3)\n\n### API changes\n\n- Add Transaction filter by end\\_datetime [\\#1595](https://github.com/omgnetwork/elixir-omg/pull/1595) ([jarindr](https://github.com/jarindr))\n\n### Enhancements\n\n- Add block processing queue to watcher info [\\#1560](https://github.com/omgnetwork/elixir-omg/pull/1560) ([mederic-p](https://github.com/mederic-p))\n\n### Bug fixes\n\n- remove trace decorator from OMG.WatcherInfo.DB.EthEvent.get/1 [\\#1640](https://github.com/omgnetwork/elixir-omg/pull/1640) ([ayrat555](https://github.com/ayrat555))\n- get call\\_data and rename it [\\#1635](https://github.com/omgnetwork/elixir-omg/pull/1635) ([InoMurko](https://github.com/InoMurko))\n- fix: handle \"transaction underpriced\" and other unknown server error responses [\\#1617](https://github.com/omgnetwork/elixir-omg/pull/1617) ([unnawut](https://github.com/unnawut))\n\n### Chores\n\n- chore: change log and version file change for v1.0.3 [\\#1638](https://github.com/omgnetwork/elixir-omg/pull/1638) ([boolafish](https://github.com/boolafish))\n- sync v1.0.2 back to master [\\#1626](https://github.com/omgnetwork/elixir-omg/pull/1626) ([boolafish](https://github.com/boolafish))\n- enable margin [\\#1622](https://github.com/omgnetwork/elixir-omg/pull/1622) ([InoMurko](https://github.com/InoMurko))\n- Auto PR with Auto merge for syncing master-v2 [\\#1604](https://github.com/omgnetwork/elixir-omg/pull/1604) ([souradeep-das](https://github.com/souradeep-das))\n- integrate spandex ecto [\\#1602](https://github.com/omgnetwork/elixir-omg/pull/1602) ([ayrat555](https://github.com/ayrat555))\n- Revert \"explain analyze updates \\(\\#1569\\)\" [\\#1601](https://github.com/omgnetwork/elixir-omg/pull/1601) ([boolafish](https://github.com/boolafish))\n- feat: sync v1.0.1 changes back to master [\\#1599](https://github.com/omgnetwork/elixir-omg/pull/1599) ([unnawut](https://github.com/unnawut))\n- release artifacts [\\#1597](https://github.com/omgnetwork/elixir-omg/pull/1597) ([InoMurko](https://github.com/InoMurko))\n- Add reorged docker compose [\\#1579](https://github.com/omgnetwork/elixir-omg/pull/1579) ([ayrat555](https://github.com/ayrat555))\n- Kevin/load test erc20 token [\\#1577](https://github.com/omgnetwork/elixir-omg/pull/1577) ([kevsul](https://github.com/kevsul))\n\n## [v1.0.2](https://github.com/omgnetwork/elixir-omg/tree/v1.0.2) (2020-06-30)\n\n[Full Changelog](https://github.com/omgnetwork/elixir-omg/compare/v1.0.2-pre.0...v1.0.2)\n\n### Enhancements\n\n- global block get interval [\\#1576](https://github.com/omgnetwork/elixir-omg/pull/1576) ([InoMurko](https://github.com/InoMurko))\n- install telemetry handler for authority balance [\\#1567](https://github.com/omgnetwork/elixir-omg/pull/1567) ([InoMurko](https://github.com/InoMurko))\n- restart strategy [\\#1565](https://github.com/omgnetwork/elixir-omg/pull/1565) ([InoMurko](https://github.com/InoMurko))\n\n### Bug fixes\n\n- async stream + timeout [\\#1593](https://github.com/omgnetwork/elixir-omg/pull/1593) ([InoMurko](https://github.com/InoMurko))\n- fix: error attempting to log txhash in binary [\\#1532](https://github.com/omgnetwork/elixir-omg/pull/1532) ([unnawut](https://github.com/unnawut))\n- use fixed version of ex\\_abi [\\#1519](https://github.com/omgnetwork/elixir-omg/pull/1519) ([ayrat555](https://github.com/ayrat555))\n\n### Chores\n\n- chore: bump version in VERSION file [\\#1613](https://github.com/omgnetwork/elixir-omg/pull/1613) ([boolafish](https://github.com/boolafish))\n- docs: v1.0.2 change logs [\\#1611](https://github.com/omgnetwork/elixir-omg/pull/1611) ([boolafish](https://github.com/boolafish))\n- chore: merge master back to v1.0.2 [\\#1606](https://github.com/omgnetwork/elixir-omg/pull/1606) ([boolafish](https://github.com/boolafish))\n- chore: minor fixes [\\#1584](https://github.com/omgnetwork/elixir-omg/pull/1584) ([boolafish](https://github.com/boolafish))\n- explain analyze updates [\\#1569](https://github.com/omgnetwork/elixir-omg/pull/1569) ([InoMurko](https://github.com/InoMurko))\n- Sync v1.0.0 [\\#1563](https://github.com/omgnetwork/elixir-omg/pull/1563) ([T-Dnzt](https://github.com/T-Dnzt))\n\n### Documentation updates\n\n- Update request body swagger [\\#1609](https://github.com/omgnetwork/elixir-omg/pull/1609) ([jarindr](https://github.com/jarindr))\n- Update README.md [\\#1564](https://github.com/omgnetwork/elixir-omg/pull/1564) ([InoMurko](https://github.com/InoMurko))\n\n## [v1.0.1](https://github.com/omgnetwork/elixir-omg/tree/v1.0.1) (2020-06-18)\n\n[Full Changelog](https://github.com/omgnetwork/elixir-omg/compare/v1.0.0-pre.2...v1.0.1)\n\n### Chores\n\n- feat: increase ExitProcessor timeouts [\\#1592](https://github.com/omgnetwork/elixir-omg/pull/1592) ([InoMurko](https://github.com/InoMurko))\n\n## [v1.0.0](https://github.com/omgnetwork/elixir-omg/tree/v1.0.0) (2020-06-12)\n\n[Full Changelog](https://github.com/omgnetwork/elixir-omg/compare/v1.0.0-pre.1...v1.0.0)\n\n### API changes\n\n- Add deposit.all endpoint and fetch eth\\_height retroactively [\\#1509](https://github.com/omgnetwork/elixir-omg/pull/1509) ([okalouti](https://github.com/okalouti))\n- Add timestamp and scheduled finalisation time to InvalidExit and UnchallengedExit events [\\#1495](https://github.com/omgnetwork/elixir-omg/pull/1495) ([okalouti](https://github.com/okalouti))\n- Introduce spending\\_txhash in invalid exit events [\\#1492](https://github.com/omgnetwork/elixir-omg/pull/1492) ([mederic-p](https://github.com/mederic-p))\n- \\[2\\] Add root chain transaction hash to InvalidExit and UnchallengedExit events [\\#1485](https://github.com/omgnetwork/elixir-omg/pull/1485) ([okalouti](https://github.com/okalouti))\n- Add root chain transaction hash to InvalidExit and UnchallengedExit events [\\#1479](https://github.com/omgnetwork/elixir-omg/pull/1479) ([okalouti](https://github.com/okalouti))\n- Input validation enhancements for endpoints [\\#1469](https://github.com/omgnetwork/elixir-omg/pull/1469) ([okalouti](https://github.com/okalouti))\n- account.get\\_utxo pagination [\\#1436](https://github.com/omgnetwork/elixir-omg/pull/1436) ([jarindr](https://github.com/jarindr))\n- Filtering Input Parameters to Childchain/Watcher API depending on HTTP Method [\\#1424](https://github.com/omgnetwork/elixir-omg/pull/1424) ([okalouti](https://github.com/okalouti))\n- Prevent split/merge creation in /transaction.create [\\#1416](https://github.com/omgnetwork/elixir-omg/pull/1416) ([T-Dnzt](https://github.com/T-Dnzt))\n\n### Enhancements\n\n- Inomurko/reorg block getter [\\#1554](https://github.com/omgnetwork/elixir-omg/pull/1554) ([InoMurko](https://github.com/InoMurko))\n- add: logging for ethereum tasks [\\#1550](https://github.com/omgnetwork/elixir-omg/pull/1550) ([okalouti](https://github.com/okalouti))\n- feat: env configurable block\\_submit\\_max\\_gas\\_price [\\#1548](https://github.com/omgnetwork/elixir-omg/pull/1548) ([unnawut](https://github.com/unnawut))\n- cache blocks into ets [\\#1547](https://github.com/omgnetwork/elixir-omg/pull/1547) ([InoMurko](https://github.com/InoMurko))\n- feat: add event type when consumer is spending utxos [\\#1538](https://github.com/omgnetwork/elixir-omg/pull/1538) ([pnowosie](https://github.com/pnowosie))\n- cache status get [\\#1535](https://github.com/omgnetwork/elixir-omg/pull/1535) ([InoMurko](https://github.com/InoMurko))\n- refactor: consistent log message for new events [\\#1534](https://github.com/omgnetwork/elixir-omg/pull/1534) ([unnawut](https://github.com/unnawut))\n- transaction rewrite, increase pg connection timeout [\\#1525](https://github.com/omgnetwork/elixir-omg/pull/1525) ([InoMurko](https://github.com/InoMurko))\n- Making Child-chain work with fee feed [\\#1500](https://github.com/omgnetwork/elixir-omg/pull/1500) ([pnowosie](https://github.com/pnowosie))\n- Papa/sec 27 watcher info ife support [\\#1496](https://github.com/omgnetwork/elixir-omg/pull/1496) ([pnowosie](https://github.com/pnowosie))\n- feat: make invalid piggyback cause unchallenged exit event when it's close to being finalized [\\#1493](https://github.com/omgnetwork/elixir-omg/pull/1493) ([pgebal](https://github.com/pgebal))\n- who monitors the monitor [\\#1488](https://github.com/omgnetwork/elixir-omg/pull/1488) ([InoMurko](https://github.com/InoMurko))\n- feat: system memory monitor that considers buffered and cached memory [\\#1474](https://github.com/omgnetwork/elixir-omg/pull/1474) ([unnawut](https://github.com/unnawut))\n- Break down incoming events to publish separately [\\#1472](https://github.com/omgnetwork/elixir-omg/pull/1472) ([souradeep-das](https://github.com/souradeep-das))\n- feat: add child chain metrics for transaction submissions, successes and failures [\\#1470](https://github.com/omgnetwork/elixir-omg/pull/1470) ([unnawut](https://github.com/unnawut))\n- feat: configurable fee specs path from env var [\\#1385](https://github.com/omgnetwork/elixir-omg/pull/1385) ([mederic-p](https://github.com/mederic-p))\n\n### Bug fixes\n\n- prevent race condition for status cache [\\#1558](https://github.com/omgnetwork/elixir-omg/pull/1558) ([InoMurko](https://github.com/InoMurko))\n- fix: add Ink's log\\_encoding\\_error config [\\#1512](https://github.com/omgnetwork/elixir-omg/pull/1512) ([unnawut](https://github.com/unnawut))\n- fix: exclude active exiting utxos from calls to /account.get\\_exitable\\_utxos [\\#1505](https://github.com/omgnetwork/elixir-omg/pull/1505) ([pgebal](https://github.com/pgebal))\n- feat: update ink to v1.1 to fix Mix module not found [\\#1504](https://github.com/omgnetwork/elixir-omg/pull/1504) ([unnawut](https://github.com/unnawut))\n- fix: MemoryMonitor breaking on OS that does not provide buffered and cached memory data [\\#1486](https://github.com/omgnetwork/elixir-omg/pull/1486) ([unnawut](https://github.com/unnawut))\n\n### Chores\n\n- Changelog for v1.0.0 [\\#1556](https://github.com/omgnetwork/elixir-omg/pull/1556) ([T-Dnzt](https://github.com/T-Dnzt))\n- updating httpoison [\\#1542](https://github.com/omgnetwork/elixir-omg/pull/1542) ([InoMurko](https://github.com/InoMurko))\n- use backport ex\\_plasma [\\#1537](https://github.com/omgnetwork/elixir-omg/pull/1537) ([achiurizo](https://github.com/achiurizo))\n- chore: sync v0.4.8 into master [\\#1531](https://github.com/omgnetwork/elixir-omg/pull/1531) ([unnawut](https://github.com/unnawut))\n- refactor: remove fixture-based start exit test [\\#1514](https://github.com/omgnetwork/elixir-omg/pull/1514) ([unnawut](https://github.com/unnawut))\n- test: watcher's /status.get cabbage test [\\#1508](https://github.com/omgnetwork/elixir-omg/pull/1508) ([unnawut](https://github.com/unnawut))\n- refactor: move exit info related functions to smaller responsibility module [\\#1503](https://github.com/omgnetwork/elixir-omg/pull/1503) ([boolafish](https://github.com/boolafish))\n- fix: lint\\_version compatibility with bash [\\#1502](https://github.com/omgnetwork/elixir-omg/pull/1502) ([unnawut](https://github.com/unnawut))\n- feat: merge latest v0.4 to master [\\#1499](https://github.com/omgnetwork/elixir-omg/pull/1499) ([unnawut](https://github.com/unnawut))\n- Kevin/load test cleanup [\\#1490](https://github.com/omgnetwork/elixir-omg/pull/1490) ([kevsul](https://github.com/kevsul))\n- Revert \"Add root chain transaction hash to InvalidExit and UnchallengedExit events\" [\\#1483](https://github.com/omgnetwork/elixir-omg/pull/1483) ([okalouti](https://github.com/okalouti))\n- refactor: add prerequisites for makefile targets involving docker-compose [\\#1476](https://github.com/omgnetwork/elixir-omg/pull/1476) ([pgebal](https://github.com/pgebal))\n- Move db storage out of docker containers [\\#1473](https://github.com/omgnetwork/elixir-omg/pull/1473) ([kevsul](https://github.com/kevsul))\n- Inomurko/macos nightly build fix [\\#1464](https://github.com/omgnetwork/elixir-omg/pull/1464) ([InoMurko](https://github.com/InoMurko))\n- fix: circleci to return the original start-services result after logging the failure [\\#1463](https://github.com/omgnetwork/elixir-omg/pull/1463) ([unnawut](https://github.com/unnawut))\n- Update alpine base image in Dockerfiles to v3.11 [\\#1450](https://github.com/omgnetwork/elixir-omg/pull/1450) ([arthurk](https://github.com/arthurk))\n\n### Documentation updates\n\n- Watcher configs [\\#1536](https://github.com/omgnetwork/elixir-omg/pull/1536) ([dmitrydao](https://github.com/dmitrydao))\n- Update README.md [\\#1468](https://github.com/omgnetwork/elixir-omg/pull/1468) ([dmitrydao](https://github.com/dmitrydao))\n- Update installation instructions [\\#1465](https://github.com/omgnetwork/elixir-omg/pull/1465) ([pnowosie](https://github.com/pnowosie))\n\n\n\n\\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*\n"
  },
  {
    "path": "CODEOWNERS",
    "content": "mix.lock @InoMurko\n"
  },
  {
    "path": "Dockerfile.watcher",
    "content": "FROM alpine:3.11\n\nLABEL maintainer=\"OMG Network Team <omg@omise.co>\"\nLABEL description=\"Official image for OMG Network (Watcher) Plasma Network\"\n\nENV LANG=C.UTF-8\n\n## S6\n##\n\nENV S6_VERSION=\"1.21.4.0\"\n\nRUN set -xe \\\n && apk add --update --no-cache --virtual .fetch-deps \\\n        curl \\\n        ca-certificates \\\n && S6_DOWNLOAD_URL=\"https://github.com/just-containers/s6-overlay/releases/download/v${S6_VERSION}/s6-overlay-amd64.tar.gz\" \\\n && S6_DOWNLOAD_SHA256=\"e903f138dea67e75afc0f61e79eba529212b311dc83accc1e18a449d58a2b10c\" \\\n && curl -fsL -o s6-overlay.tar.gz \"${S6_DOWNLOAD_URL}\" \\\n && echo \"${S6_DOWNLOAD_SHA256}  s6-overlay.tar.gz\" |sha256sum -c - \\\n && tar -xzC / -f s6-overlay.tar.gz \\\n && rm s6-overlay.tar.gz \\\n && apk del .fetch-deps\n\n## Application\n##\n\n#rocksdb need libstdc++\nRUN apk add --update --no-cache --virtual .watcher-runtime \\\n        bash \\\n        libressl \\\n        libressl-dev \\\n        libstdc++ \\\n        lksctp-tools\n\n#libsecp256k1\nRUN apk add --no-cache gmp-dev\n\nCOPY rootfs /\n\n# USER directive is not being used here since privileges are dropped via\n# s6-setuigid in /entrypoint. s6-overlay is required to be run as root.\nARG user=watcher\nARG group=watcher\nARG uid=10000\nARG gid=10000\n\nRUN set -xe \\\n && addgroup -g ${gid} ${group} \\\n && adduser -D -h /app -u ${uid} -G ${group} ${user} \\\n && chown \"${uid}:${gid}\" \"/app\" \\\n && chmod +x /watcher_entrypoint\n\n#rocksdb from builder image to deployer image\nRUN mkdir -p /usr/local/rocksdb/lib\nRUN mkdir /usr/local/rocksdb/include\nCOPY --from=omisegoimages/elixir-omg-builder:stable-20201207 /usr/local/rocksdb/ /usr/local/rocksdb/\n\nARG release_version\nADD _build_docker/prod/watcher-${release_version}.tar.gz /app\nRUN chown -R \"${uid}:${gid}\" /app\nWORKDIR /app\n\n# Watcher app is using PORT environment variable to determine which port to run\n# the application server.\nENV PORT 7434\n\nEXPOSE $PORT\n\n# These are ports required for clustering. The range is defined in vm.args\n# in inet_dist_listen_min and inet_dist_listen_max.\n#EXPOSE 4369 6900 6901 6902 6903 6904 6905 6906 6907 6908 6909\n# curl for healthchecks\nRUN apk add --no-cache curl\nENTRYPOINT [\"/init\", \"/watcher_entrypoint\"]\n\nCMD [\"foreground\"]\n"
  },
  {
    "path": "Dockerfile.watcher_info",
    "content": "FROM alpine:3.11\n\nLABEL maintainer=\"OMG Network Team <engineering@omg.network>\"\nLABEL description=\"Official image for OMG Network (WatcherInfo) Plasma Network\"\n\nENV LANG=C.UTF-8\n\n## S6\n##\n\nENV S6_VERSION=\"1.21.4.0\"\n\nRUN set -xe \\\n && apk add --update --no-cache --virtual .fetch-deps \\\n        curl \\\n        ca-certificates \\\n && S6_DOWNLOAD_URL=\"https://github.com/just-containers/s6-overlay/releases/download/v${S6_VERSION}/s6-overlay-amd64.tar.gz\" \\\n && S6_DOWNLOAD_SHA256=\"e903f138dea67e75afc0f61e79eba529212b311dc83accc1e18a449d58a2b10c\" \\\n && curl -fsL -o s6-overlay.tar.gz \"${S6_DOWNLOAD_URL}\" \\\n && echo \"${S6_DOWNLOAD_SHA256}  s6-overlay.tar.gz\" |sha256sum -c - \\\n && tar -xzC / -f s6-overlay.tar.gz \\\n && rm s6-overlay.tar.gz \\\n && apk del .fetch-deps\n\n## Application\n##\n\n#rocksdb need libstdc++\nRUN apk add --update --no-cache --virtual .watcher-runtime \\\n        bash \\\n        libressl \\\n        libressl-dev \\\n        libstdc++ \\\n        lksctp-tools\n\n#libsecp256k1\nRUN apk add --no-cache gmp-dev\n\nCOPY rootfs /\n\n# USER directive is not being used here since privileges are dropped via\n# s6-setuigid in /entrypoint. s6-overlay is required to be run as root.\nARG user=watcher\nARG group=watcher\nARG uid=10000\nARG gid=10000\n\nRUN set -xe \\\n && addgroup -g ${gid} ${group} \\\n && adduser -D -h /app -u ${uid} -G ${group} ${user} \\\n && chown \"${uid}:${gid}\" \"/app\" \\\n && chmod +x /watcher_info_entrypoint\n\n#rocksdb from builder image to deployer image\nRUN mkdir -p /usr/local/rocksdb/lib\nRUN mkdir /usr/local/rocksdb/include\nCOPY --from=omisegoimages/elixir-omg-builder:stable-20201207 /usr/local/rocksdb/ /usr/local/rocksdb/\n\nARG release_version\nADD _build_docker/prod/watcher_info-${release_version}.tar.gz /app\nRUN chown -R \"${uid}:${gid}\" /app\nWORKDIR /app\n\n# Watcher app is using PORT environment variable to determine which port to run\n# the application server.\nENV PORT 7534\n\nEXPOSE $PORT\n\n# These are ports required for clustering. The range is defined in vm.args\n# in inet_dist_listen_min and inet_dist_listen_max.\n#EXPOSE 4369 6900 6901 6902 6903 6904 6905 6906 6907 6908 6909\n# curl for healthchecks\nRUN apk add --no-cache curl\nENTRYPOINT [\"/init\", \"/watcher_info_entrypoint\"]\n\nCMD [\"foreground\"]\n"
  },
  {
    "path": "LICENSE",
    "content": "Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n1. Definitions.\n\n\"License\" shall mean the terms and conditions for use, reproduction,\nand distribution as defined by Sections 1 through 9 of this document.\n\n\"Licensor\" shall mean the copyright owner or entity authorized by\nthe copyright owner that is granting the License.\n\n\"Legal Entity\" shall mean the union of the acting entity and all\nother entities that control, are controlled by, or are under common\ncontrol with that entity. For the purposes of this definition,\n\"control\" means (i) the power, direct or indirect, to cause the\ndirection or management of such entity, whether by contract or\notherwise, or (ii) ownership of fifty percent (50%) or more of the\noutstanding shares, or (iii) beneficial ownership of such entity.\n\n\"You\" (or \"Your\") shall mean an individual or Legal Entity\nexercising permissions granted by this License.\n\n\"Source\" form shall mean the preferred form for making modifications,\nincluding but not limited to software source code, documentation\nsource, and configuration files.\n\n\"Object\" form shall mean any form resulting from mechanical\ntransformation or translation of a Source form, including but\nnot limited to compiled object code, generated documentation,\nand conversions to other media types.\n\n\"Work\" shall mean the work of authorship, whether in Source or\nObject form, made available under the License, as indicated by a\ncopyright notice that is included in or attached to the work\n(an example is provided in the Appendix below).\n\n\"Derivative Works\" shall mean any work, whether in Source or Object\nform, that is based on (or derived from) the Work and for which the\neditorial revisions, annotations, elaborations, or other modifications\nrepresent, as a whole, an original work of authorship. For the purposes\nof this License, Derivative Works shall not include works that remain\nseparable from, or merely link (or bind by name) to the interfaces of,\nthe Work and Derivative Works thereof.\n\n\"Contribution\" shall mean any work of authorship, including\nthe original version of the Work and any modifications or additions\nto that Work or Derivative Works thereof, that is intentionally\nsubmitted to Licensor for inclusion in the Work by the copyright owner\nor by an individual or Legal Entity authorized to submit on behalf of\nthe copyright owner. For the purposes of this definition, \"submitted\"\nmeans any form of electronic, verbal, or written communication sent\nto the Licensor or its representatives, including but not limited to\ncommunication on electronic mailing lists, source code control systems,\nand issue tracking systems that are managed by, or on behalf of, the\nLicensor for the purpose of discussing and improving the Work, but\nexcluding communication that is conspicuously marked or otherwise\ndesignated in writing by the copyright owner as \"Not a Contribution.\"\n\n\"Contributor\" shall mean Licensor and any individual or Legal Entity\non behalf of whom a Contribution has been received by Licensor and\nsubsequently incorporated within the Work.\n\n2. Grant of Copyright License. Subject to the terms and conditions of\nthis License, each Contributor hereby grants to You a perpetual,\nworldwide, non-exclusive, no-charge, royalty-free, irrevocable\ncopyright license to reproduce, prepare Derivative Works of,\npublicly display, publicly perform, sublicense, and distribute the\nWork and such Derivative Works in Source or Object form.\n\n3. Grant of Patent License. Subject to the terms and conditions of\nthis License, each Contributor hereby grants to You a perpetual,\nworldwide, non-exclusive, no-charge, royalty-free, irrevocable\n(except as stated in this section) patent license to make, have made,\nuse, offer to sell, sell, import, and otherwise transfer the Work,\nwhere such license applies only to those patent claims licensable\nby such Contributor that are necessarily infringed by their\nContribution(s) alone or by combination of their Contribution(s)\nwith the Work to which such Contribution(s) was submitted. If You\ninstitute patent litigation against any entity (including a\ncross-claim or counterclaim in a lawsuit) alleging that the Work\nor a Contribution incorporated within the Work constitutes direct\nor contributory patent infringement, then any patent licenses\ngranted to You under this License for that Work shall terminate\nas of the date such litigation is filed.\n\n4. Redistribution. You may reproduce and distribute copies of the\nWork or Derivative Works thereof in any medium, with or without\nmodifications, and in Source or Object form, provided that You\nmeet the following conditions:\n\n(a) You must give any other recipients of the Work or\nDerivative Works a copy of this License; and\n\n(b) You must cause any modified files to carry prominent notices\nstating that You changed the files; and\n\n(c) You must retain, in the Source form of any Derivative Works\nthat You distribute, all copyright, patent, trademark, and\nattribution notices from the Source form of the Work,\nexcluding those notices that do not pertain to any part of\nthe Derivative Works; and\n\n(d) If the Work includes a \"NOTICE\" text file as part of its\ndistribution, then any Derivative Works that You distribute must\ninclude a readable copy of the attribution notices contained\nwithin such NOTICE file, excluding those notices that do not\npertain to any part of the Derivative Works, in at least one\nof the following places: within a NOTICE text file distributed\nas part of the Derivative Works; within the Source form or\ndocumentation, if provided along with the Derivative Works; or,\nwithin a display generated by the Derivative Works, if and\nwherever such third-party notices normally appear. The contents\nof the NOTICE file are for informational purposes only and\ndo not modify the License. You may add Your own attribution\nnotices within Derivative Works that You distribute, alongside\nor as an addendum to the NOTICE text from the Work, provided\nthat such additional attribution notices cannot be construed\nas modifying the License.\n\nYou may add Your own copyright statement to Your modifications and\nmay provide additional or different license terms and conditions\nfor use, reproduction, or distribution of Your modifications, or\nfor any such Derivative Works as a whole, provided Your use,\nreproduction, and distribution of the Work otherwise complies with\nthe conditions stated in this License.\n\n5. Submission of Contributions. Unless You explicitly state otherwise,\nany Contribution intentionally submitted for inclusion in the Work\nby You to the Licensor shall be under the terms and conditions of\nthis License, without any additional terms or conditions.\nNotwithstanding the above, nothing herein shall supersede or modify\nthe terms of any separate license agreement you may have executed\nwith Licensor regarding such Contributions.\n\n6. Trademarks. This License does not grant permission to use the trade\nnames, trademarks, service marks, or product names of the Licensor,\nexcept as required for reasonable and customary use in describing the\norigin of the Work and reproducing the content of the NOTICE file.\n\n7. Disclaimer of Warranty. Unless required by applicable law or\nagreed to in writing, Licensor provides the Work (and each\nContributor provides its Contributions) on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\nimplied, including, without limitation, any warranties or conditions\nof TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\nPARTICULAR PURPOSE. You are solely responsible for determining the\nappropriateness of using or redistributing the Work and assume any\nrisks associated with Your exercise of permissions under this License.\n\n8. Limitation of Liability. In no event and under no legal theory,\nwhether in tort (including negligence), contract, or otherwise,\nunless required by applicable law (such as deliberate and grossly\nnegligent acts) or agreed to in writing, shall any Contributor be\nliable to You for damages, including any direct, indirect, special,\nincidental, or consequential damages of any character arising as a\nresult of this License or out of the use or inability to use the\nWork (including but not limited to damages for loss of goodwill,\nwork stoppage, computer failure or malfunction, or any and all\nother commercial damages or losses), even if such Contributor\nhas been advised of the possibility of such damages.\n\n9. Accepting Warranty or Additional Liability. While redistributing\nthe Work or Derivative Works thereof, You may choose to offer,\nand charge a fee for, acceptance of support, warranty, indemnity,\nor other liability obligations and/or rights consistent with this\nLicense. However, in accepting such obligations, You may act only\non Your own behalf and on Your sole responsibility, not on behalf\nof any other Contributor, and only if You agree to indemnify,\ndefend, and hold each Contributor harmless for any liability\nincurred by, or claims asserted against, such Contributor by reason\nof your accepting any such warranty or additional liability.\n\nEND OF TERMS AND CONDITIONS\n\nCopyright 2017-2019 OMG Network\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n"
  },
  {
    "path": "Makefile",
    "content": "MAKEFLAGS += --silent\nOVERRIDING_START ?= start_iex\nOVERRIDING_VARIABLES ?= bin/variables\nSNAPSHOT ?= SNAPSHOT_MIX_EXIT_PERIOD_SECONDS_20\nBAREBUILD_ENV ?= dev\nhelp:\n\t@echo \"Dont Fear the Makefile\"\n\t@echo \"\"\n\t@echo \"PRE-LUMPHINI\"\n\t@echo \"------------------\"\n\t@echo\n\t@echo \"If you want to connect to an existing network (Pre-Lumphini) with a Watcher \\c\"\n\t@echo \"and validate transactions. Run:\"\n\t@echo \"  - \\`make start-pre-lumphini-watcher\\` \\c\"\n\t@echo \"\"\n\t@echo\n\t@echo \"DOCKER CLUSTER USAGE\"\n\t@echo \"------------------\"\n\t@echo \"\"\n\t@echo \"  - \\`make docker-start-cluster\\`: start everything for you, but if there are no local images \\c\"\n\t@echo \"for Watcher and Child chain tagged with latest they will get pulled from our repository.\"\n\t@echo \"\"\n\t@echo \"  - \\`make docker-start-cluster-with-infura\\`: start everything but connect to Infura \\c\"\n\t@echo \"instead of your own local geth network. Note: you will need to configure the environment \\c\"\n\t@echo \"variables defined in docker-compose-infura.yml\"\n\t@echo \"\"\n\t@echo \"DOCKER DEVELOPMENT\"\n\t@echo \"------------------\"\n\t@echo \"\"\n\t@echo \"  - \\`make docker-build-start-cluster\\`: watcher and watcher_info images \\c\"\n\t@echo \"from your current code base, then start a cluster with these freshly built images.\"\n\t@echo \"\"\n\t@echo \" - \\`make docker-build\\`\" watcher and watcher_info images from your current code base\n\t@echo \"\"\n\t@echo \"  - \\`make docker-update-watcher\\`, \\`make docker-update-watcher_info\\`\"\n\t@echo \"replaces containers with your code changes\\c\"\n\t@echo \"for rapid development.\"\n\t@echo \"\"\n\t@echo \"  - \\`make docker-nuke\\`: wipe docker clean, including containers, images, networks \\c\"\n\t@echo \"and build cache.\"\n\t@echo \"\"\n\t@echo \"  - \\`make docker-remote-watcher\\`: remote console (IEx-style) into the watcher application.\"\n\t@echo \"\"\n\t@echo \"  - \\`make docker-remote-watcher_info\\`: remote console (IEx-style) into the \\c\"\n\t@echo \"watcher_info application.\"\n\t@echo \"\"\n\t@echo \"BARE METAL DEVELOPMENT\"\n\t@echo \"----------------------\"\n\t@echo\n\t@echo \"This presumes you want to run geth and postgres as containers \\c\"\n\t@echo \"but Watcher and Child Chain bare metal. You will need four terminal windows.\"\n\t@echo \"\"\n\t@echo \"1. In the first one, start geth, postgres:\"\n\t@echo \"    make start-services\"\n\t@echo \"\"\n\t@echo \"3. In the third terminal window, run:\"\n\t@echo \"    make start-watcher\"\n\t@echo \"\"\n\t@echo \"4. In the fourth terminal window, run:\"\n\t@echo \"    make start-watcher_info\"\n\t@echo \"\"\n\t@echo \"5. Wait until they all boot. And run in the fifth terminal window:\"\n\t@echo \"    make get-alarms\"\n\t@echo \"\"\n\t@echo \"    make remote-watcher\"\n\t@echo \"\"\n\t@echo \"or\"\n\t@echo \"    make remote-watcher_info\"\n\t@echo \"\"\n\t@echo \"MISCELLANEOUS\"\n\t@echo \"-------------\"\n\t@echo \"  - \\`make diagnostics\\`: generate comprehensive diagnostics info for troubleshooting\"\n\t@echo \"  - \\`make list\\`: list all available make targets\"\n\t@echo \"\"\n\n.PHONY: list\nlist:\n\t@$(MAKE) -pRrq -f $(lastword $(MAKEFILE_LIST)) : 2>/dev/null | awk -v RS= -F: '/^# File/,/^# Finished Make data base/ {if ($$1 !~ \"^[#.]\") {print $$1}}' | sort | egrep -v -e '^[^[:alnum:]]' -e '^$@$$'\n\nall: clean build-watcher-prod build-watcher_info-prod\n\nWATCHER_IMAGE_NAME      ?= \"omisego/watcher:latest\"\nWATCHER_INFO_IMAGE_NAME ?= \"omisego/watcher_info:latest\"\n\nIMAGE_BUILDER   ?= \"omisegoimages/elixir-omg-builder:stable-20201207\"\nIMAGE_BUILD_DIR ?= $(PWD)\n\nENV_DEV         ?= env MIX_ENV=dev\nENV_TEST        ?= env MIX_ENV=test\nENV_PROD        ?= env MIX_ENV=prod\n\nWATCHER_PORT ?= 7434\nWATCHER_INFO_PORT ?= 7534\n\nHEX_URL    ?= https://repo.hex.pm/installs/1.8.0/hex-0.20.5.ez\nHEX_SHA    ?= cb7fdddbc4e5051b403cfb5e874ceb5cb0ecbe981a2a1517b97f9f76c67d234692e901ff48ee10dc712f728ae6ed0a51b11b8bd65b5db5582896123de20e7d49\nREBAR_URL  ?= https://repo.hex.pm/installs/1.0.0/rebar-2.6.2\nREBAR_SHA  ?= ff1c5ddfce1fcfd73fd65b8bfc0ff1c13aefc2e98921d528cbc1f35e86c9caa1c9c4e848b9ce6404d9a81c50cfcf0e45dd0dddb23cd42708664c41fce6618900\nREBAR3_URL ?= https://repo.hex.pm/installs/1.0.0/rebar3-3.5.1\nREBAR3_SHA ?= 86e998642991d384e9a6d4f216552609496da0e6ec4eb235df5b8b637d078c1a118bc7cdab501d1d54d24e0b6642adf32cc0c43019d948304301ceef227bedfd\n\n#\n# Setting-up\n#\n\ndeps: deps-elixir-omg\n\ndeps-elixir-omg:\n\tHEX_HTTP_TIMEOUT=120 mix deps.get\n\n# Mimicks `mix local.hex --force && mix local.rebar --force` but with version pinning. See:\n# - https://github.com/elixir-lang/elixir/blob/master/lib/mix/lib/mix/tasks/local.hex.ex\n# - https://github.com/elixir-lang/elixir/blob/master/lib/mix/lib/mix/tasks/local.rebar.ex\ninstall-hex-rebar:\n\tmix archive.install ${HEX_URL} --force --sha512 ${HEX_SHA}\n\tmix local.rebar rebar ${REBAR_URL} --force --sha512 ${REBAR_SHA}\n\tmix local.rebar rebar3 ${REBAR3_URL} --force --sha512 ${REBAR3_SHA}\n\n.PHONY: deps deps-elixir-omg\n\n#\n# Cleaning\n#\n\nclean: clean-elixir-omg\n\nclean-elixir-omg:\n\trm -rf _build/*\n\trm -rf deps/*\n\trm -rf _build_docker/*\n\trm -rf deps_docker/*\n\nclean-contracts:\n\trm -rf data/*\n\n.PHONY: clean clean-elixir-omg clean-contracts\n\n#\n# Linting\n#\n\nformat:\n\tmix format\n\ncheck-format:\n\tmix format --check-formatted 2>&1\n\ncheck-credo:\n\t$(ENV_TEST) mix credo 2>&1\n\ncheck-dialyzer:\n\t$(ENV_TEST) mix dialyzer 2>&1\n\n.PHONY: format check-format check-credo\n\n#\n# Building\n#\n\nbuild-watcher-prod: deps-elixir-omg\n\t$(ENV_PROD) mix do compile, release watcher --overwrite\n\nbuild-watcher-dev: deps-elixir-omg\n\t$(ENV_DEV) mix do compile, release watcher --overwrite\n\nbuild-watcher_info-prod: deps-elixir-omg\n\t$(ENV_PROD) mix do compile, release watcher_info --overwrite\n\nbuild-watcher_info-dev: deps-elixir-omg\n\t$(ENV_DEV) mix do compile, release watcher_info --overwrite\n\nbuild-test: deps-elixir-omg\n\t$(ENV_TEST) mix compile\n\n.PHONY: build-prod build-dev build-test\n\n#\n# Contracts initialization\n#\n\n# Get the SNAPSHOT url from the snapshots file based on the SNAPSHOT env value\n# untar the snapshot and fetch values from the files in build dir that came from plasma-deployer\n# put these values into an localchain_contract_addresses.env via the script in bin\n# localchain_contract_addresses.env is used by docker, exunit tests and end2end tests\ninit-contracts: clean-contracts\n\tmkdir data/ || true && \\\n\tchmod 777 data && \\\n\tURL=$$(grep \"^$(SNAPSHOT)\" snapshots.env | cut -d'=' -f2-) && \\\n\tcurl -o data/snapshot.tar.gz $$URL && \\\n\tcd data && \\\n\ttar --strip-components 1 -zxvf snapshot.tar.gz data/geth && \\\n\ttar --exclude=data/* -xvzf snapshot.tar.gz && \\\n\tAUTHORITY_ADDRESS=$$(cat plasma-contracts/build/authority_address) && \\\n\tETH_VAULT=$$(cat plasma-contracts/build/eth_vault) && \\\n\tERC20_VAULT=$$(cat plasma-contracts/build/erc20_vault) && \\\n\tPAYMENT_EXIT_GAME=$$(cat plasma-contracts/build/payment_exit_game) && \\\n\tPLASMA_FRAMEWORK_TX_HASH=$$(cat plasma-contracts/build/plasma_framework_tx_hash) && \\\n\tPLASMA_FRAMEWORK=$$(cat plasma-contracts/build/plasma_framework) && \\\n\tPAYMENT_EIP712_LIBMOCK=$$(cat plasma-contracts/build/paymentEip712LibMock) && \\\n\tMERKLE_WRAPPER=$$(cat plasma-contracts/build/merkleWrapper) && \\\n\tERC20_MINTABLE=$$(cat plasma-contracts/build/erc20Mintable) && \\\n\tsh ../bin/generate-localchain-env AUTHORITY_ADDRESS=$$AUTHORITY_ADDRESS ETH_VAULT=$$ETH_VAULT \\\n\tERC20_VAULT=$$ERC20_VAULT PAYMENT_EXIT_GAME=$$PAYMENT_EXIT_GAME \\\n\tPLASMA_FRAMEWORK_TX_HASH=$$PLASMA_FRAMEWORK_TX_HASH PLASMA_FRAMEWORK=$$PLASMA_FRAMEWORK \\\n\tPAYMENT_EIP712_LIBMOCK=$$PAYMENT_EIP712_LIBMOCK MERKLE_WRAPPER=$$MERKLE_WRAPPER ERC20_MINTABLE=$$ERC20_MINTABLE\n\ninit-contracts-reorg: clean-contracts\n\tmkdir data1/ || true && \\\n\tmkdir data2/ || true && \\\n\tmkdir data/ || true && \\\n\tURL=$$(grep \"SNAPSHOT\" snapshot_reorg.env | cut -d'=' -f2-) && \\\n\tcurl -o data1/snapshot.tar.gz $$URL && \\\n\tcd data1 && \\\n\ttar --strip-components 1 -zxvf snapshot.tar.gz data/geth && \\\n\ttar --exclude=data/* -xvzf snapshot.tar.gz && \\\n        mv snapshot.tar.gz ../data2/snapshot.tar.gz && \\\n\tcd ../data2 && \\\n\ttar --strip-components 1 -zxvf snapshot.tar.gz data/geth && \\\n\ttar --exclude=data/* -xvzf snapshot.tar.gz && \\\n        mv snapshot.tar.gz ../data/snapshot.tar.gz && \\\n\tcd ../data && \\\n\ttar --strip-components 1 -zxvf snapshot.tar.gz data/geth && \\\n\ttar --exclude=data/* -xvzf snapshot.tar.gz && \\\n\tAUTHORITY_ADDRESS=$$(cat plasma-contracts/build/authority_address) && \\\n\tETH_VAULT=$$(cat plasma-contracts/build/eth_vault) && \\\n\tERC20_VAULT=$$(cat plasma-contracts/build/erc20_vault) && \\\n\tPAYMENT_EXIT_GAME=$$(cat plasma-contracts/build/payment_exit_game) && \\\n\tPLASMA_FRAMEWORK_TX_HASH=$$(cat plasma-contracts/build/plasma_framework_tx_hash) && \\\n\tPLASMA_FRAMEWORK=$$(cat plasma-contracts/build/plasma_framework) && \\\n\tPAYMENT_EIP712_LIBMOCK=$$(cat plasma-contracts/build/paymentEip712LibMock) && \\\n\tMERKLE_WRAPPER=$$(cat plasma-contracts/build/merkleWrapper) && \\\n\tERC20_MINTABLE=$$(cat plasma-contracts/build/erc20Mintable) && \\\n\tsh ../bin/generate-localchain-env AUTHORITY_ADDRESS=$$AUTHORITY_ADDRESS ETH_VAULT=$$ETH_VAULT \\\n\tERC20_VAULT=$$ERC20_VAULT PAYMENT_EXIT_GAME=$$PAYMENT_EXIT_GAME \\\n\tPLASMA_FRAMEWORK_TX_HASH=$$PLASMA_FRAMEWORK_TX_HASH PLASMA_FRAMEWORK=$$PLASMA_FRAMEWORK \\\n\tPAYMENT_EIP712_LIBMOCK=$$PAYMENT_EIP712_LIBMOCK MERKLE_WRAPPER=$$MERKLE_WRAPPER ERC20_MINTABLE=$$ERC20_MINTABLE\n\n.PHONY: init-contracts\n\n#\n# Testing\n#\n\ninit_test: init-contracts\n\ninit_test_reorg: init-contracts-reorg\n\ntest:\n\tmix test --include test --exclude common --exclude watcher --exclude watcher_info\n\ntest-watcher:\n\tmix test --include watcher --exclude watcher_info --exclude common --exclude test\n\ntest-watcher_info:\n\tmix test --include watcher_info --exclude watcher --exclude common --exclude test\n\ntest-common:\n\tmix test --include common --exclude watcher --exclude watcher_info --exclude test\n\n#\n# Documentation\n#\nchangelog:\n\tgithub_changelog_generator --user omgnetwork --project elixir-omg\n\n.PHONY: changelog\n\n###\nstart-integration-watcher:\n\tdocker-compose -f docker-compose-watcher.yml up\n###\n\n#\n# Docker\n#\n\ndocker-watcher-prod:\n\tdocker run --rm -it \\\n\t\t-v $(PWD):/app \\\n\t\t-u root \\\n\t\t--entrypoint /bin/sh \\\n\t\t$(IMAGE_BUILDER) \\\n\t\t-c \"cd /app && make build-watcher-prod\"\n\ndocker-watcher_info-prod:\n\tdocker run --rm -it \\\n\t\t-v $(PWD):/app \\\n\t\t-u root \\\n\t\t--entrypoint /bin/sh \\\n\t\t$(IMAGE_BUILDER) \\\n\t\t-c \"cd /app && make build-watcher_info-prod\"\n\ndocker-watcher-build:\n\tdocker build -f Dockerfile.watcher \\\n\t\t--build-arg release_version=$$(git describe --tags) \\\n\t\t--cache-from $(WATCHER_IMAGE_NAME) \\\n\t\t-t $(WATCHER_IMAGE_NAME) \\\n\t\t.\n\ndocker-watcher_info-build:\n\tdocker build -f Dockerfile.watcher_info \\\n\t\t--build-arg release_version=$$(git describe --tags) \\\n\t\t--cache-from $(WATCHER_INFO_IMAGE_NAME) \\\n\t\t-t $(WATCHER_INFO_IMAGE_NAME) \\\n\t\t.\n\ndocker-watcher: docker-watcher-prod docker-watcher-build\ndocker-watcher_info: docker-watcher_info-prod docker-watcher_info-build\n\ndocker-perf:\n\tdocker build -f ./priv/perf/Dockerfile -t $(IMAGE_NAME) .\n\ndocker-build: docker-watcher docker-watcher_info\n\ndocker-push: docker\n\tdocker push $(WATCHER_IMAGE_NAME)\n\tdocker push $(WATCHER_INFO_IMAGE_NAME)\n\n\n### Cabbage logs\n\ncabbage-logs:\n\tdocker-compose -f docker-compose.yml -f docker-compose.feefeed.yml -f docker-compose.specs.yml logs --follow\n\ncabbage-logs-reorg:\n\tdocker-compose -f docker-compose.yml -f docker-compose.feefeed.yml -f docker-compose.reorg.yml -f docker-compose.specs.yml logs --follow\n\n### Cabbage reorg docker logs\n\ncabbage-reorg-watcher-logs:\n\tdocker-compose -f docker-compose.yml -f docker-compose.reorg.yml -f docker-compose.specs.yml logs --follow watcher\n\ncabbage-reorg-watcher_info-logs:\n\tdocker-compose -f docker-compose.yml -f docker-compose.reorg.yml -f docker-compose.specs.yml logs --follow watcher_info\n\ncabbage-reorg-childchain-logs:\n\tdocker-compose -f docker-compose.yml -f docker-compose.reorg.yml -f docker-compose.specs.yml logs --follow childchain\n\ncabbage-reorg-geth-logs:\n\tdocker-compose -f docker-compose.yml -f docker-compose.reorg.yml -f docker-compose.specs.yml logs --follow | grep \"geth\"\n\ncabbage-reorgs-logs:\n\tdocker-compose -f docker-compose.yml -f docker-compose.reorg.yml -f docker-compose.specs.yml logs --follow | grep \"reorg\"\n\n### Cabbage service commands\n\ncabbage-start-services:\n\tmake init_test && \\\n        cp ./localchain_contract_addresses.env ./priv/cabbage/apps/itest/localchain_contract_addresses.env && \\\n\tdocker-compose -f docker-compose.yml -f docker-compose.specs.yml up -d || \\\n\t(START_RESULT=$?; docker-compose logs; exit $START_RESULT;)\n\ncabbage-start-services-reorg:\n\tmake init_test_reorg && \\\n\tcp ./localchain_contract_addresses.env ./priv/cabbage/apps/itest/localchain_contract_addresses.env && \\\n\tdocker-compose -f docker-compose.yml -f docker-compose.reorg.yml -f docker-compose.specs.yml up -d || \\\n\t(START_RESULT=$?; docker-compose logs; exit $START_RESULT;)\n\n###OTHER\ndocker-start-cluster:\n\tSNAPSHOT=SNAPSHOT_MIX_EXIT_PERIOD_SECONDS_120 make init_test && \\\n\tdocker-compose build --no-cache && docker-compose up\n\ndocker-build-start-cluster:\n\t$(MAKE) docker-build\n\tSNAPSHOT=SNAPSHOT_MIX_EXIT_PERIOD_SECONDS_120 make init_test && \\\n\tdocker-compose build --no-cache && docker-compose up\n\ndocker-stop-cluster: localchain_contract_addresses.env\n\tdocker-compose down\n\ndocker-update-watcher: localchain_contract_addresses.env\n\tdocker stop elixir-omg_watcher_1\n\t$(MAKE) docker-watcher\n\tdocker-compose up watcher\n\ndocker-update-watcher_info: localchain_contract_addresses.env\n\tdocker stop elixir-omg_watcher_info_1\n\t$(MAKE) docker-watcher_info\n\tdocker-compose up watcher_info\n\ndocker-start-cluster-with-infura: localchain_contract_addresses.env\n\tif [ -f ./docker-compose.override.yml ]; then \\\n\t\tdocker-compose -f docker-compose.yml -f docker-compose-infura.yml -f docker-compose.override.yml up; \\\n\telse \\\n\t\techo \"Starting infura requires overriding docker-compose-infura.yml values in a docker-compose.override.yml\"; \\\n\tfi\n\ndocker-start-cluster-with-datadog: localchain_contract_addresses.env\n\tdocker-compose -f docker-compose.yml -f docker-compose.dev.yml -f docker-compose.datadog.yml up watcher watcher_info childchain\n\ndocker-stop-cluster-with-datadog: localchain_contract_addresses.env\n\tdocker-compose -f docker-compose.yml -f docker-compose.dev.yml -f docker-compose.datadog.yml down\n\ndocker-nuke: localchain_contract_addresses.env\n\tdocker-compose down --remove-orphans --volumes\n\tdocker system prune --all\n\t$(MAKE) clean\n\t$(MAKE) init-contracts\n\ndocker-remote-watcher:\n\tdocker exec -it watcher /app/bin/watcher remote\n\ndocker-remote-watcher_info:\n\tdocker exec -ti watcher_info /app/bin/watcher_info remote\n\n.PHONY: docker-nuke docker-remote-watcher docker-remote-watcher_info\n\n###\n### barebone stuff\n###\nstart-services:\n\tSNAPSHOT=SNAPSHOT_MIX_EXIT_PERIOD_SECONDS_120 make init_test && \\\n\tdocker-compose -f ./docker-compose.yml -f ./docker-compose.feefeed.yml up feefeed geth nginx postgres\n\nstart-watcher:\n\t. ${OVERRIDING_VARIABLES} && \\\n\techo \"Building Watcher\" && \\\n\tmake build-watcher-${BAREBUILD_ENV} && \\\n\techo \"Potential cleanup\" && \\\n\trm -f ./_build/${BAREBUILD_ENV}/rel/watcher/var/sys.config || true && \\\n\techo \"Init Watcher DBs\" && \\\n\t_build/${BAREBUILD_ENV}/rel/watcher/bin/watcher eval \"OMG.DB.ReleaseTasks.InitKeyValueDB.run()\" && \\\n\t_build/${BAREBUILD_ENV}/rel/watcher/bin/watcher eval \"OMG.DB.ReleaseTasks.InitKeysWithValues.run()\" && \\\n\techo \"Run Watcher\" && \\\n\t. ${OVERRIDING_VARIABLES} && \\\n\tPORT=${WATCHER_PORT} _build/${BAREBUILD_ENV}/rel/watcher/bin/watcher $(OVERRIDING_START)\n\nstart-watcher_info:\n\t. ${OVERRIDING_VARIABLES} && \\\n\techo \"Building Watcher Info\" && \\\n\tmake build-watcher_info-${BAREBUILD_ENV} && \\\n\techo \"Potential cleanup\" && \\\n\trm -f ./_build/${BAREBUILD_ENV}/rel/watcher_info/var/sys.config || true && \\\n\techo \"Init Watcher Info DBs\" && \\\n\t_build/${BAREBUILD_ENV}/rel/watcher_info/bin/watcher_info eval \"OMG.DB.ReleaseTasks.InitKeyValueDB.run()\" && \\\n\t_build/${BAREBUILD_ENV}/rel/watcher_info/bin/watcher_info eval \"OMG.DB.ReleaseTasks.InitKeysWithValues.run()\" && \\\n\t_build/${BAREBUILD_ENV}/rel/watcher_info/bin/watcher_info eval \"OMG.WatcherInfo.ReleaseTasks.InitPostgresqlDB.migrate()\" && \\\n\techo \"Run Watcher Info\" && \\\n\t. ${OVERRIDING_VARIABLES} && \\\n\tPORT=${WATCHER_INFO_PORT} _build/${BAREBUILD_ENV}/rel/watcher_info/bin/watcher_info $(OVERRIDING_START)\n\nupdate-watcher:\n\t_build/dev/rel/watcher/bin/watcher stop ; \\\n\t$(ENV_DEV) mix do compile, release watcher --overwrite && \\\n\t. ${OVERRIDING_VARIABLES} && \\\n\texec PORT=${WATCHER_PORT} _build/dev/rel/watcher/bin/watcher $(OVERRIDING_START) &\n\nupdate-watcher_info:\n\t_build/dev/rel/watcher_info/bin/watcher_info stop ; \\\n\t$(ENV_DEV) mix do compile, release watcher_info --overwrite && \\\n\t. ${OVERRIDING_VARIABLES} && \\\n\texec PORT=${WATCHER_INFO_PORT} _build/dev/rel/watcher_info/bin/watcher_info $(OVERRIDING_START) &\n\nstop-watcher:\n\t. ${OVERRIDING_VARIABLES} && \\\n\t_build/dev/rel/watcher/bin/watcher stop\n\nstop-watcher_info:\n\t. ${OVERRIDING_VARIABLES} && \\\n\t_build/dev/rel/watcher_info/bin/watcher_info stop\n\nremote-watcher:\n\t. ${OVERRIDING_VARIABLES} && \\\n\t_build/dev/rel/watcher/bin/watcher remote\n\nremote-watcher_info:\n\t. ${OVERRIDING_VARIABLES} && \\\n\t_build/dev/rel/watcher_info/bin/watcher_info remote\n\nget-alarms:\n\techo \"Child Chain alarms\" ; \\\n\tcurl -s -X GET http://localhost:9656/alarm.get ; \\\n\techo \"\\nWatcher alarms\" ; \\\n\tcurl -s -X GET http://localhost:${WATCHER_PORT}/alarm.get ; \\\n\techo \"\\nWatcherInfo alarms\" ; \\\n\tcurl -s -X GET http://localhost:${WATCHER_INFO_PORT}/alarm.get\n\ncluster-stop: localchain_contract_addresses.env\n\t${MAKE} stop-watcher ; ${MAKE} stop-watcher_info ; docker-compose down\n\n### git setup\ninit:\n\tgit config core.hooksPath .githooks\n\n#old git\n#init:\n#  find .git/hooks -type l -exec rm {} \\;\n#  find .githooks -type f -exec ln -sf ../../{} .git/hooks/ \\;\n\n###\n### SWAGGER openapi\n###\nsecurity_critical_api_specs:\n\tswagger-cli bundle -r -t yaml -o apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs.yaml apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs/swagger.yaml\n\ninfo_api_specs:\n\tswagger-cli bundle -r -t yaml -o apps/omg_watcher_rpc/priv/swagger/info_api_specs.yaml apps/omg_watcher_rpc/priv/swagger/info_api_specs/swagger.yaml\n\napi_specs: security_critical_api_specs info_api_specs operator_api_specs\n\n###\n### Diagnostics report\n###\n\ndiagnostics: localchain_contract_addresses.env\n\techo \"---------- START OF DIAGNOSTICS REPORT ----------\"\n\techo \"\\n---------- CHILDCHAIN LOGS ----------\"\n\tdocker-compose logs childchain\n\techo \"\\n---------- WATCHER LOGS ----------\"\n\tdocker-compose logs watcher\n\techo \"\\n---------- WATCHER_INFO LOGS ----------\"\n\tdocker-compose logs watcher_info\n\techo \"\\n---------- GIT ----------\"\n\techo \"Git commit: $$(git rev-parse HEAD)\"\n\tgit status\n\techo \"\\n---------- DOCKER-COMPOSE CONTAINERS ----------\"\n\tdocker-compose ps\n\techo \"\\n---------- DOCKER CONTAINERS ----------\"\n\tdocker ps\n\techo \"\\n---------- DOCKER IMAGES ----------\"\n\tdocker image ls\n\techo \"\\n ---------- END OF DIAGNOSTICS REPORT ----------\"\n\n.PHONY: diagnostics\n\nlocalchain_contract_addresses.env:\n\t$(MAKE) init-contracts\n"
  },
  {
    "path": "README.md",
    "content": "<img src=\"docs/assets/logo.png\" width=\"100\" height=\"100\" align=\"right\" />\n\nThe `elixir-omg` repository contains OMG Network's Elixir implementation of Plasma and forms the basis for the OMG Network.\n\n[![Build Status](https://circleci.com/gh/omgnetwork/elixir-omg.svg?style=svg)](https://circleci.com/gh/omgnetwork/elixir-omg) [![Coverage Status](https://coveralls.io/repos/github/omisego/elixir-omg/badge.svg?branch=master)](https://coveralls.io/github/omisego/elixir-omg?branch=master)\n\n**IMPORTANT NOTICE: Heavily WIP, expect anything**\n\n**Table of Contents**\n\n<!--ts-->\n   * [Getting Started](#getting-started)\n      * [Service start up using Docker Compose](#service-start-up-using-docker-compose)\n         * [Troubleshooting Docker](#troubleshooting-docker)\n       * [Install on a Linux host](#install-on-a-linux-host)\n   * [Installing Plasma contract snapshots](#installing-plasma-contract-snapshots)\n   * [Testing &amp; development](#testing--development)\n   * [Working with API Spec's](#working-with-api-specs)\n\n<!-- Added by: user, at: 2019-04-03T18:13+02:00 -->\n\n<!--te-->\n\n<!-- Created by [gh-md-toc](https://github.com/ekalinin/github-markdown-toc) -->\n<!-- GH_TOC_TOKEN=75... ./gh-md-toc --insert ../omgnetwork/README.md -->\n\n# Getting Started\n\nA public testnet for the OMG Network is coming soon.\nHowever, if you are brave and want to test being a Plasma chain operator, read on!\n\n## Service start up using Docker Compose\nThis is the recommended method of starting the blockchain services, with the auxiliary services automatically provisioned through Docker.\n\nBefore attempting the start up please ensure that you are not running any services that are listening on the following TCP ports: 9656, 7434, 7534, 5000, 8545, 5432, 5433.\nAll commands should be run from the root of the repo.\n\nTo bring the entire system up you will first need to bring in the compatible Geth snapshot of plasma contracts:\n\n```sh\nmake init_test\n```\nIt creates a file `./localchain_contract_addresses.env`. It is required to have this file in current directory for running any `docker-compose` command.\n\n```sh\ndocker-compose up\n```\n\nTo bring only specific services up (eg: the childchain service, geth, etc...):\n\n```sh\ndocker-compose up childchain geth ...\n```\n\n_(Note: This will also bring up any services childchain depends on.)_\n\nTo run a Watcher only, first make sure you sent an ENV variable called with `INFURA_API_KEY` with your api key and then run:\n\n```sh\ndocker-compose -f docker-compose-watcher.yml up\n```\n\n### Troubleshooting Docker\nYou can view the running containers via `docker ps`\n\nIf service start up is unsuccessful, containers can be left hanging which impacts the start of services on the future attempts of `docker-compose up`.\nYou can stop all running containers via `docker kill $(docker ps -q)`.\n\nIf the blockchain services are not already present on the host, docker-compose will attempt to pull the latest build coming from master.\nIf you want Docker to use the latest commit from `elixir-omg` you can trigger a fresh build by building all three services with `make docker-childchain`, `make docker-watcher` and `make docker-watcher_info`.\n\n# Install on a Linux host\nFollow the guide to **[install](docs/install.md)** the Child Chain server, Watcher and Watcher Info.\n\n# Installing Plasma contract snapshots\n\nTo pull in the compatible snapshot for Geth:\n```bash\nmake init_test\n```\n\n# Testing & development\n\nDocker building of source code and dependencies used to directly use common `mix` folders like `_build` and `deps`. To support workflows that switch between bare metal and Docker containers we've introduced `_build_docker` and `deps_docker` folders:\n\n```sh\nsudo rm -rf _build_docker\nsudo rm -rf deps_docker\n\nmkdir _build_docker && chmod 777 _build_docker\nmkdir deps_docker && chmod 777 deps_docker\n```\n\nPull in the compatible Plasma contracts snapshot:\n```bash\nmake init_test\n```\n\nYou can setup the docker environment to run testing and development tasks:\n\n```sh\ndocker-compose -f docker-compose.yml -f docker-compose.dev.yml -f docker-compose.datadog.yml run --rm --entrypoint bash elixir-omg\n```\n\nOnce the shell has loaded, you can continue and run additional tasks.\n\nGet the necessary dependencies for building:\n```bash\ncd app && mix deps.get\n```\n\nQuick test (no integration tests):\n```bash\nmix test\n```\n\nLonger-running integration tests (requires compiling contracts):\n```bash\nmix test   --only integration\n```\n\nFor other kinds of checks, refer to the CI/CD pipeline (https://app.circleci.com/pipelines/github/omgnetwork/elixir-omg) or build steps (https://github.com/omgnetwork/elixir-omg/blob/master/.circleci/config.yml).\n\nTo run a development `iex` REPL with all code loaded:\n```bash\nMIX_ENV=test iex -S mix run --no-start\n```\n\n## Running integration cabbage tests\n\nIntegration tests are written using the [`cabbage`](https://github.com/cabbage-ex/cabbage) library and they are located in a separated repo - [specs](https://github.com/omgnetwork/specs). This repo is added to `elixir-omg` as a git submodule. So to fetch them run:\n```bash\ngit submodule init\ngit submodule update --remote\n```\n\nCreate a directory for geth:\n```bash\nmkdir data && chmod 777 data\n```\n\nMake services:\n```bash\nmake docker-watcher\nmake docker-watcher_info\n```\n\nStart geth and postgres:\n```bash\ncd priv/cabbage\nmake start_daemon_services-2\n```\n\nIf the above command fails with the message similar to:\n```\nCreating network \"omisego_chain_net\" with driver \"bridge\"\nERROR: Pool overlaps with other one on this address space\n```\n\ntry the following remedy and retry:\n```bash\nmake stop_daemon_services\nrm -rf ../../data/*\ndocker network prune\n```\n\n\nBuild the integration tests project and run tests:\n```bash\ncd priv/cabbage\nmake install\nmake generate_api_code\nmix deps.get\nmix test\n```\n\n## Running reorg cabbage tests\n\nReorg tests test different assumptions against chain reorgs. They also use the same submodule as regular integration cabbage tests.\n\nFetch submodule:\n```bash\ngit submodule init\ngit submodule update --remote\n```\n\nCreate a directory for geth nodes:\n```bash\nmkdir data1 && chmod 777 data1 && mkdir data2 && chmod 777 data2 && mkdir data && chmod 777 data\n```\n\nMake services:\n```bash\nmake docker-watcher\nmake docker-watcher_info\n```\n\nStart geth nodes and postgres:\n```bash\ncd priv/cabbage\nmake start_daemon_services_reorg-2\n```\n\nBuild the integration tests project and run reorg tests:\n```bash\ncd priv/cabbage\nmake install\nmake generate_api_code\nmix deps.get\nREORG=true mix test --only reorg\n```\n\n# Working with API Spec's\n\nThis repo contains `gh-pages` branch intended to host [Swagger-based](https://docs.omg.network/elixir-omg/) API specification.\nBranch `gh-pages` is totally diseparated from other development branches and contains just Slate generated page's files.\n\nSee [gh-pages README](https://github.com/omgnetwork/elixir-omg/tree/gh-pages) for more details.\n\n# More details about the design and architecture\n\nDetails about the repository, code, architecture and design decisions are available **[here](docs/details.md)**.\n"
  },
  {
    "path": "apps/omg_bus/lib/omg_bus/application.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Bus.Application do\n  @moduledoc false\n\n  use Application\n\n  def start(_type, _args) do\n    OMG.Bus.Supervisor.start_link()\n  end\nend\n"
  },
  {
    "path": "apps/omg_bus/lib/omg_bus/event.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Bus.Event do\n  @moduledoc \"\"\"\n  Representation of a single event to be published on OMG event bus\n  \"\"\"\n\n  @enforce_keys [:topic, :event, :payload]\n  @type topic_t() :: {atom(), binary()} | binary()\n  @type t() :: %__MODULE__{topic: binary(), event: atom, payload: any()}\n\n  defstruct [:topic, :event, :payload]\n\n  @spec new(__MODULE__.topic_t(), atom(), any()) :: __MODULE__.t()\n  def new({origin, topic}, event, payload) when is_atom(origin) and is_atom(event) do\n    %__MODULE__{topic: \"#{origin}:#{topic}\", event: event, payload: payload}\n  end\nend\n"
  },
  {
    "path": "apps/omg_bus/lib/omg_bus/pubsub.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Bus.PubSub do\n  @moduledoc \"\"\"\n  Thin wrapper around the pubsub mechanism allowing us to not repeat ourselves when starting/broadcasting/subscribing\n\n  All of the messages published will have `:internal_bus_event` prepended to the tuple to distinguish them\n\n  ### Topics and messages\n\n  #### `enqueue_block`\n\n  Is being broadcast on a local node whenever `OMG.Watcher.State` completes forming of a new child chain block\n\n  Message: {:internal_event_bus, :enqueue_block, OMG.Watcher.Block.t()}\n  \"\"\"\n  alias Phoenix.PubSub\n\n  def child_spec(args \\\\ []) do\n    args\n    |> Keyword.put_new(:name, __MODULE__)\n    |> Keyword.put_new(:adapter, PubSub.PG2)\n    |> PubSub.child_spec()\n  end\n\n  defmacro __using__(_) do\n    quote do\n      alias OMG.Bus.Event\n      alias Phoenix.PubSub\n\n      @doc \"\"\"\n      Fixes the name of the PubSub server and the variant of `Phoenix.PubSub` used\n      \"\"\"\n\n      @doc \"\"\"\n      Subscribes the current process to the internal bus topic\n      \"\"\"\n      def subscribe(topic, opts \\\\ [])\n\n      def subscribe({origin, topic}, opts) when is_atom(origin) do\n        PubSub.subscribe(OMG.Bus.PubSub, \"#{origin}:#{topic}\", opts)\n      end\n\n      def subscribe(topic, opts) do\n        PubSub.subscribe(OMG.Bus.PubSub, topic, opts)\n      end\n\n      @doc \"\"\"\n      Broadcast a message with a prefix indicating that it is originating from the internal event bus\n\n      Handle the message in the receiving process by e.g.\n      ```\n      def handle_info({:internal_bus_event, :some_event, my_payload}, state)\n      ```\n      \"\"\"\n      def broadcast(%Event{topic: topic, event: event, payload: payload}) when is_atom(event) do\n        PubSub.broadcast(OMG.Bus.PubSub, topic, {:internal_event_bus, event, payload})\n      end\n\n      @doc \"\"\"\n      Same as `broadcast/1`, but performed on the local node\n      \"\"\"\n      def direct_local_broadcast(%Event{topic: topic, event: event, payload: payload})\n          when is_atom(event) do\n        PubSub.local_broadcast(OMG.Bus.PubSub, topic, {:internal_event_bus, event, payload})\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_bus/lib/omg_bus/supervisor.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Bus.Supervisor do\n  @moduledoc \"\"\"\n   OMG Bus top level supervisor.\n  \"\"\"\n  use Supervisor\n  require Logger\n\n  def start_link() do\n    Supervisor.start_link(__MODULE__, :ok, name: __MODULE__)\n  end\n\n  def init(:ok) do\n    children = [{OMG.Bus.PubSub, []}]\n\n    opts = [strategy: :one_for_one]\n\n    _ = Logger.info(\"Starting #{inspect(__MODULE__)}\")\n    Supervisor.init(children, opts)\n  end\nend\n"
  },
  {
    "path": "apps/omg_bus/lib/omg_bus.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Bus do\n  @moduledoc \"\"\"\n  Modules purpose is to serve as a event bus, the implementation is in the `OMG.Bus.PubSub` macro.\n  \"\"\"\n  use OMG.Bus.PubSub\nend\n"
  },
  {
    "path": "apps/omg_bus/mix.exs",
    "content": "defmodule OMG.Bus.MixProject do\n  use Mix.Project\n\n  def project() do\n    [\n      app: :omg_bus,\n      version: version(),\n      build_path: \"../../_build\",\n      config_path: \"../../config/config.exs\",\n      deps_path: \"../../deps\",\n      lockfile: \"../../mix.lock\",\n      elixir: \"~> 1.8\",\n      elixirc_paths: elixirc_paths(Mix.env()),\n      start_permanent: Mix.env() == :prod,\n      deps: deps(),\n      test_coverage: [tool: ExCoveralls]\n    ]\n  end\n\n  def application() do\n    [\n      mod: {OMG.Bus.Application, []},\n      extra_applications: [:logger],\n      included_applications: []\n    ]\n  end\n\n  defp version() do\n    \"git\"\n    |> System.cmd([\"describe\", \"--tags\", \"--abbrev=0\"])\n    |> elem(0)\n    |> String.replace(\"v\", \"\")\n    |> String.replace(\"\\n\", \"\")\n  end\n\n  # Specifies which paths to compile per environment.\n  defp elixirc_paths(:prod), do: [\"lib\"]\n  defp elixirc_paths(:dev), do: [\"lib\"]\n  defp elixirc_paths(:test), do: [\"lib\", \"test/support\"]\n\n  defp deps(), do: [{:phoenix_pubsub, \"~> 2.0\"}]\nend\n"
  },
  {
    "path": "apps/omg_bus/test/omg_bus/event_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Bus.EventTest do\n  @moduledoc false\n\n  use ExUnit.Case\n\n  alias OMG.Bus.Event\n\n  test \"creates a root chain event\" do\n    topic = \"Deposit\"\n    event = :deposit\n    payload = [\"payload\"]\n\n    assert %Event{topic: \"root_chain:\" <> topic, event: event, payload: payload} ==\n             Event.new({:root_chain, topic}, event, payload)\n  end\n\n  test \"creates a child chain event\" do\n    topic = \"blocks\"\n    event = :deposit\n    payload = [\"payload\"]\n\n    assert %Event{topic: \"child_chain:\" <> topic, event: event, payload: payload} ==\n             Event.new({:child_chain, topic}, event, payload)\n  end\nend\n"
  },
  {
    "path": "apps/omg_bus/test/test_helper.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\nExUnit.start()\n"
  },
  {
    "path": "apps/omg_conformance/mix.exs",
    "content": "defmodule OMG.Conformance.MixProject do\n  use Mix.Project\n\n  def project() do\n    [\n      app: :omg_conformance,\n      version: version(),\n      build_path: \"../../_build\",\n      config_path: \"../../config/config.exs\",\n      deps_path: \"../../deps\",\n      lockfile: \"../../mix.lock\",\n      elixir: \"~> 1.8\",\n      elixirc_paths: elixirc_paths(Mix.env()),\n      start_permanent: Mix.env() == :prod,\n      deps: deps(),\n      test_coverage: [tool: ExCoveralls]\n    ]\n  end\n\n  def application() do\n    [\n      extra_applications: [:logger]\n    ]\n  end\n\n  defp version() do\n    \"git\"\n    |> System.cmd([\"describe\", \"--tags\", \"--abbrev=0\"])\n    |> elem(0)\n    |> String.replace(\"v\", \"\")\n    |> String.replace(\"\\n\", \"\")\n  end\n\n  defp elixirc_paths(:prod), do: [\"lib\"]\n  defp elixirc_paths(:dev), do: [\"lib\"]\n  defp elixirc_paths(:test), do: [\"lib\", \"test/support\"]\n\n  defp deps() do\n    [\n      {:propcheck, \"~> 1.1\", only: [:test]},\n      {:omg_watcher, in_umbrella: true}\n    ]\n  end\nend\n"
  },
  {
    "path": "apps/omg_conformance/test/omg_conformance/conformance/merkle_proof_property_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Conformance.MerkleProofPropertyTest do\n  @moduledoc \"\"\"\n  Checks if some properties about the merkle proving (proof generation and validation) are consistent across\n  implementations (currently `elixir-omg` and `plasma-contracts`, Elixir and Solidity)\n  \"\"\"\n\n  alias OMG.Watcher.Merkle\n  alias Support.Conformance.MerkleProofContext\n  alias Support.SnapshotContracts\n\n  import Support.Conformance.MerkleProofs, only: [solidity_proof_valid: 5]\n\n  use PropCheck\n  use ExUnit.Case, async: false\n\n  @moduletag :property\n  @moduletag timeout: 450_000\n\n  setup_all do\n    {:ok, exit_fn} = Support.DevNode.start()\n\n    contracts = SnapshotContracts.parse_contracts()\n    merkle_wrapper_address_hex = contracts[\"CONTRACT_ADDRESS_MERKLE_WRAPPER\"]\n\n    on_exit(exit_fn)\n\n    [contract: OMG.Eth.Encoding.from_hex(merkle_wrapper_address_hex)]\n  end\n\n  property \"any root hash and proof created by the Elixir implementation validates in the contract, for all leaves\",\n           [500, :verbose, max_size: 256, constraint_tries: 100_000],\n           %{contract: contract} do\n    forall leaves <- list(binary()) do\n      root_hash = Merkle.hash(leaves)\n\n      leaves\n      |> Enum.with_index()\n      |> Enum.all?(fn {leaf, txindex} ->\n        proof = Merkle.create_tx_proof(leaves, txindex)\n        solidity_proof_valid(leaf, txindex, root_hash, proof, contract)\n      end)\n    end\n  end\n\n  property \"no proof can prove a mutated leaf\",\n           [5000, :verbose, max_size: 256, constraint_tries: 100_000],\n           %{contract: contract} do\n    forall proof <- MerkleProofContext.correct() do\n      forall mutated <- MerkleProofContext.mutated_leaf(proof) do\n        not solidity_proof_valid(mutated.leaf, mutated.txindex, mutated.root_hash, mutated.proof, contract)\n      end\n    end\n  end\n\n  property \"no proof can prove at different index\",\n           [5000, :verbose, max_size: 256, constraint_tries: 100_000],\n           %{contract: contract} do\n    forall proof <- MerkleProofContext.correct() do\n      forall mutated <- MerkleProofContext.mutated_txindex(proof) do\n        not solidity_proof_valid(mutated.leaf, mutated.txindex, mutated.root_hash, mutated.proof, contract)\n      end\n    end\n  end\n\n  property \"no mutated proof bytes can prove anything that the original proved\",\n           [5000, :verbose, max_size: 256, constraint_tries: 100_000],\n           %{contract: contract} do\n    forall proof <- MerkleProofContext.correct() do\n      forall mutated <- MerkleProofContext.mutated_proof(proof) do\n        not solidity_proof_valid(mutated.leaf, mutated.txindex, mutated.root_hash, mutated.proof, contract)\n      end\n    end\n  end\n\n  property \"no proof can prove a different leaf/txindex if proof bytes mutated\",\n           [5000, :verbose, max_size: 256, constraint_tries: 100_000],\n           %{contract: contract} do\n    forall proof <- MerkleProofContext.correct() do\n      forall mutated <- MerkleProofContext.mutated_to_prove_sth_else(proof) do\n        not solidity_proof_valid(mutated.leaf, mutated.txindex, mutated.root_hash, mutated.proof, contract)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_conformance/test/omg_conformance/conformance/merkle_proof_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Conformance.MerkleProofTest do\n  @moduledoc \"\"\"\n  Checks if some particular cases of merkle proofs (proof generation and validation) behave consistently across\n  implementations (currently `elixir-omg` and `plasma-contracts`, Elixir and Solidity)\n  \"\"\"\n\n  alias OMG.Eth.Encoding\n  alias OMG.Watcher.Crypto\n  alias OMG.Watcher.Merkle\n  alias Support.SnapshotContracts\n\n  import Support.Conformance.MerkleProofs, only: [solidity_proof_valid: 5]\n\n  use ExUnit.Case, async: false\n\n  @moduletag :integration\n  @moduletag :common\n\n  @proof_length 16\n  @max_block_size trunc(:math.pow(2, @proof_length))\n\n  setup_all do\n    {:ok, exit_fn} = Support.DevNode.start()\n\n    contracts = SnapshotContracts.parse_contracts()\n    merkle_wrapper_address_hex = contracts[\"CONTRACT_ADDRESS_MERKLE_WRAPPER\"]\n\n    on_exit(exit_fn)\n\n    [contract: Encoding.from_hex(merkle_wrapper_address_hex)]\n  end\n\n  test \"a simple, 3-leaf merkle proof validates fine\", %{contract: contract} do\n    leaves = [<<1>>, <<0>>, <<>>]\n    root_hash = Merkle.hash(leaves)\n\n    leaves\n    |> Enum.with_index()\n    |> Enum.each(fn {leaf, txindex} ->\n      proof = Merkle.create_tx_proof(leaves, txindex)\n      assert solidity_proof_valid(leaf, txindex, root_hash, proof, contract)\n    end)\n  end\n\n  @tag timeout: 240_000\n  test \"a full-tree merkle proof validates fine\", %{contract: contract} do\n    # why?\n    # 1. we'd like to test all proofs on a full tree\n    # 2. that's 65K proofs\n    # 3. so we're pre-building the merkle tree by using raw `MerkleTree` calls instead of `OMG.Watcher.Merkle`\n    #    This is slightly inconsistent, but otherwise the test takes forever\n    full_leaves = Enum.map(1..@max_block_size, &:binary.encode_unsigned/1)\n    full_root_hash = Merkle.hash(full_leaves)\n\n    full_tree =\n      MerkleTree.build(full_leaves,\n        hash_function: &Crypto.hash/1,\n        height: 16,\n        default_data_block: <<0::256>>\n      )\n\n    full_leaves\n    |> Enum.with_index()\n    |> Enum.each(fn {leaf, txindex} ->\n      proof = full_tree |> MerkleTree.Proof.prove(txindex) |> Enum.reverse() |> Enum.join()\n      assert solidity_proof_valid(leaf, txindex, full_root_hash, proof, contract)\n    end)\n  end\nend\n"
  },
  {
    "path": "apps/omg_conformance/test/omg_conformance/conformance/signature_property_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Conformance.SignaturePropertyTest do\n  @moduledoc \"\"\"\n  Checks if some properties about the signatures (structural, EIP-712 hashes to be precise) hold for the Elixir and\n  Solidity implementations.\n\n  NOTE: if this fails with something like\n\n  ```\n  Assertion with == failed\n  code:  assert solidity_hash!(tx, contract) == elixir_hash(tx)\n  left:  <<8, 195, 121, 160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>>\n  ```\n\n  where `Support.Conformance.SignaturesHashes.signature_hash!/2` return an \"almost zero\" binary, it means the contract\n  unexpectedly refused to signhash a generated transaction.\n  \"\"\"\n\n  alias Support.Conformance.PropertyGenerators\n\n  import Support.Conformance.SignaturesHashes,\n    only: [verify: 2, verify_distinct: 3, verify_both_error: 2, verify_distinct_or_erroring: 3]\n\n  use PropCheck\n  use Support.Conformance.SignaturesHashesCase, async: false\n\n  @moduletag :property\n  @moduletag timeout: 450_000\n\n  property \"any tx hashes/signhashes the same in all implementations\",\n           [1000, :verbose, max_size: 100, constraint_tries: 100_000],\n           %{contract: contract} do\n    forall tx <- PropertyGenerators.payment_tx() do\n      # TODO: expand with verifying the non-signature-related hash, Transaction.raw_txhash\n      #       This occurs multiple times, wherever transaction/implementation identity/conformance is tested\n      verify(tx, contract)\n    end\n  end\n\n  property \"any 2 different txs hash/signhash differently, regardless of implementation\",\n           [1000, :verbose, max_size: 100, constraint_tries: 100_000],\n           %{contract: contract} do\n    forall [{tx1, tx2} <- PropertyGenerators.distinct_payment_txs()] do\n      verify_distinct(tx1, tx2, contract)\n    end\n  end\n\n  property \"any crude-mutated tx binary either fails to decode to a transaction object or is recognized as different\",\n           [1000, :verbose, max_size: 100, constraint_tries: 100_000],\n           %{contract: contract} do\n    forall {tx1_binary, tx2_binary} <- PropertyGenerators.tx_binary_with_mutation() do\n      verify_distinct_or_erroring(tx1_binary, tx2_binary, contract)\n    end\n  end\n\n  # this is by far the most interesting-case-yielding test, hence number of cases is set to x10 the others\n  property \"any rlp-mutated tx binary either fails to decode to a transaction object or is recognized as different\",\n           [10_000, :verbose, max_size: 100, constraint_tries: 100_000],\n           %{contract: contract} do\n    forall {tx1_binary, tx2_binary} <- PropertyGenerators.tx_binary_with_rlp_mutation() do\n      verify_distinct_or_erroring(tx1_binary, tx2_binary, contract)\n    end\n  end\n\n  property \"arbitrary binaries never decode\",\n           [1000, :verbose, max_size: 1000],\n           %{contract: contract} do\n    forall some_binary <- binary() do\n      verify_both_error(some_binary, contract)\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_conformance/test/omg_conformance/conformance/signature_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Conformance.SignatureTest do\n  @moduledoc \"\"\"\n  Tests that EIP-712-compliant signatures generated `somehow` (via Elixir code as it happens) are treated the same\n  by both Elixir signature code and contract signature code.\n  \"\"\"\n\n  alias OMG.Watcher.State.Transaction\n\n  import Support.Conformance.SignaturesHashes, only: [verify: 2, verify_distinct: 3]\n\n  use Support.Conformance.SignaturesHashesCase, async: false\n\n  @moduletag :integration\n  @moduletag :common\n\n  @good_metadata <<1::size(32)-unit(8)>>\n\n  describe \"elixir vs solidity conformance test\" do\n    test \"no inputs test\", %{contract: contract} do\n      tx = Transaction.Payment.new([], [{@alice, @eth, 100}])\n      verify(tx, contract)\n    end\n\n    test \"signature test - small tx\", %{contract: contract} do\n      tx = Transaction.Payment.new([{1, 0, 0}], [{@alice, @eth, 100}])\n      verify(tx, contract)\n    end\n\n    test \"signature test - full tx\", %{contract: contract} do\n      tx =\n        Transaction.Payment.new(\n          [{1, 0, 0}, {1000, 555, 3}, {2000, 333, 1}, {15_015, 0, 0}],\n          [{@alice, @eth, 100}, {@alice, @token, 50}, {@bob, @token, 75}, {@bob, @eth, 25}]\n        )\n\n      verify(tx, contract)\n    end\n\n    test \"signature test transaction with metadata\", %{contract: contract} do\n      tx =\n        Transaction.Payment.new(\n          [{1, 0, 0}, {1000, 555, 3}, {2000, 333, 1}, {15_015, 0, 0}],\n          [{@alice, @eth, 100}, {@alice, @eth, 50}, {@bob, @eth, 75}, {@bob, @eth, 25}],\n          @good_metadata\n        )\n\n      verify(tx, contract)\n    end\n  end\n\n  describe \"distinct transactions yield distinct sign hashes\" do\n    test \"different inputs - txs hash differently but same in both implementations\", %{contract: contract} do\n      tx1 = Transaction.Payment.new([{1, 0, 0}], [{@alice, @eth, 100}])\n      tx2 = Transaction.Payment.new([{2, 0, 0}], [{@alice, @eth, 100}])\n      verify_distinct(tx1, tx2, contract)\n    end\n\n    test \"different outputs - txs hash differently but same in both implementations\", %{contract: contract} do\n      tx1 = Transaction.Payment.new([{1, 0, 0}], [{@alice, @eth, 110}])\n      tx2 = Transaction.Payment.new([{1, 0, 0}], [{@alice, @eth, 100}])\n      verify_distinct(tx1, tx2, contract)\n    end\n\n    test \"different metadata - txs hash differently but same in both implementations\", %{contract: contract} do\n      tx1 = Transaction.Payment.new([{1, 0, 0}], [{@alice, @eth, 100}])\n      tx2 = Transaction.Payment.new([{1, 0, 0}], [{@alice, @eth, 100}], <<1::256>>)\n      verify_distinct(tx1, tx2, contract)\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_conformance/test/support/conformance/merkle_proof_context.ex",
    "content": "# Copyright 2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule Support.Conformance.MerkleProofContext do\n  @moduledoc \"\"\"\n  A package of data associated with a single proof to assert about. Contains the proof, what it proves, and the under-\n  -lying merkle tree leaves as well\n  \"\"\"\n  defstruct [:leaves, :root_hash, :leaf, :txindex, :proof]\n\n  alias OMG.Watcher.Merkle\n\n  use PropCheck\n\n  @doc \"\"\"\n  A correct context - a proof proves something it should\n  \"\"\"\n  def correct() do\n    let leaves <- such_that(leaves <- list(pragmatic_binary()), when: length(leaves) > 0) do\n      leaves_length = length(leaves)\n      root_hash = Merkle.hash(leaves)\n\n      let txindex <- integer(0, leaves_length - 1) do\n        proof = Merkle.create_tx_proof(leaves, txindex)\n        leaf = Enum.at(leaves, txindex)\n        %__MODULE__{leaves: leaves, root_hash: root_hash, leaf: leaf, txindex: txindex, proof: proof}\n      end\n    end\n  end\n\n  @doc \"\"\"\n  A mutated context where only the leaf is different from the original, correct proof\n  \"\"\"\n  def mutated_leaf(%__MODULE__{} = base) do\n    # TODO: add borrowing leaf from proof\n\n    # Some of the generators under `union/1` are only valid on certain conditions. Setting weight to 0 prevents them\n    # if the condition is not met\n    zero_out_leaf_weight = if base.leaf == <<0::256>>, do: 0, else: 1\n    trimmed_leaf_weight = if base.leaf == \"\", do: 0, else: 1\n    get_other_leaf_weight = if base.leaves |> Enum.uniq() |> length() < 2, do: 0, else: 1\n\n    weighted_union([\n      {zero_out_leaf_weight, zero_out_leaf(base)},\n      {1, random_leaf(base)},\n      {trimmed_leaf_weight, trimmed_leaf(base)},\n      {1, expanded_leaf(base)},\n      {get_other_leaf_weight, get_other_leaf(base)}\n    ])\n  end\n\n  @doc \"\"\"\n  A mutated context where only the txindex proven is different from the original, correct proof\n  \"\"\"\n  def mutated_txindex(%__MODULE__{} = base) do\n    # The trick here is that it can be any index (even beyond the scope of leaves list!), but can't point to an\n    # identical leaf, in case we have 2 in the leaves list.\n    # So this is slightly different from `distinct_leaf_index` in `get_other_leaf/1`\n    distinct_leaf_index = such_that(i <- non_neg_integer(), when: Enum.at(base.leaves, i) != base.leaf)\n\n    let other_txindex <- distinct_leaf_index do\n      %{base | txindex: other_txindex}\n    end\n  end\n\n  @doc \"\"\"\n  A mutated context where only the proof bytes are different from the original, correct proof\n  \"\"\"\n  def mutated_proof(%__MODULE__{} = base) do\n    union([\n      bitwise_modify_proof(base),\n      chunkwise_modify_proof(base)\n    ])\n  end\n\n  @doc \"\"\"\n  A mutated context where we're trying to alter both the proof and what we prove, aiming to \"reuse\" parts of a proof\n  that worked (the original, `base`) and produce a proof that works when it shouldn't\n  \"\"\"\n  def mutated_to_prove_sth_else(%__MODULE__{} = base) do\n    let [\n      other_proof <- mutated_proof(base),\n      proving_something_else <- union([mutated_leaf(base), mutated_txindex(base)])\n    ] do\n      # first get a context that's proving something else (other leaf or other index) and after that modify proof\n      %{proving_something_else | proof: other_proof.proof}\n    end\n  end\n\n  #\n  # leaf mutations\n\n  defp zero_out_leaf(%__MODULE__{} = base) do\n    %{base | leaf: <<0::256>>}\n  end\n\n  defp random_leaf(%__MODULE__{} = base) do\n    let b <- such_that(b <- pragmatic_binary(), when: b != base.leaf) do\n      %{base | leaf: b}\n    end\n  end\n\n  defp trimmed_leaf(%__MODULE__{} = base) do\n    length_leaf = byte_size(base.leaf)\n\n    let to_keep <- integer(0, length_leaf - 1) do\n      %{base | leaf: binary_part(base.leaf, 0, to_keep)}\n    end\n  end\n\n  defp expanded_leaf(%__MODULE__{} = base) do\n    let [b <- non_empty_binary(), append? <- boolean()] do\n      if append?, do: %{base | leaf: base.leaf <> b}, else: %{base | leaf: b <> base.leaf}\n    end\n  end\n\n  defp get_other_leaf(%__MODULE__{} = base) do\n    length_leaves = length(base.leaves)\n    distinct_leaf_index = such_that(i <- integer(0, length_leaves - 1), when: Enum.at(base.leaves, i) != base.leaf)\n\n    let other_index <- distinct_leaf_index do\n      %{base | leaf: Enum.at(base.leaves, other_index)}\n    end\n  end\n\n  #\n  # proof mutations\n\n  defp bitwise_modify_proof(%__MODULE__{} = base) do\n    # TODO: more cases pending\n    union([\n      bitwise_append(base)\n    ])\n  end\n\n  defp chunkwise_modify_proof(%__MODULE__{} = base) do\n    insert_leaf_chunk_weight = if base.leaf == \"\", do: 0, else: 1\n\n    weighted_union([\n      {1, insert_zero_chunk(base)},\n      {insert_leaf_chunk_weight, insert_leaf_chunk(base)},\n      {1, drop_chunk(base)},\n      {1, swap_neighbors(base)}\n    ])\n  end\n\n  defp insert_zero_chunk(%__MODULE__{} = base) do\n    # @proof length doesn't work for some reason\n    let position <- integer(0, 16) do\n      %{base | proof: base.proof |> chunk() |> List.insert_at(position, <<0::256>>) |> unchunk()}\n    end\n  end\n\n  defp insert_leaf_chunk(%__MODULE__{} = base) do\n    # @proof length doesn't work for some reason\n    let position <- integer(0, 16) do\n      %{base | proof: base.proof |> chunk() |> List.insert_at(position, base.leaf) |> unchunk()}\n    end\n  end\n\n  defp drop_chunk(%__MODULE__{} = base) do\n    # @proof length doesn't work for some reason\n    let position <- integer(0, 16 - 1) do\n      %{base | proof: base.proof |> chunk() |> List.delete_at(position) |> unchunk()}\n    end\n  end\n\n  defp swap_neighbors(%__MODULE__{} = base) do\n    # @proof length doesn't work for some reason\n    let position <- integer(0, 16 - 1 - 1) do\n      chunked_proof = chunk(base.proof)\n      [neighbor1, neighbor2] = Enum.slice(chunked_proof, position, 2)\n\n      swapped_proof =\n        chunked_proof\n        |> List.replace_at(position, neighbor2)\n        |> List.replace_at(position + 1, neighbor1)\n        |> unchunk\n\n      %{base | proof: swapped_proof}\n    end\n  end\n\n  defp bitwise_append(%__MODULE__{} = base) do\n    let to_append <- union([non_empty_binary(), <<0>>, <<0::256>>, <<0::512>>]) do\n      %{base | proof: base.proof <> to_append}\n    end\n  end\n\n  # `pragmatic_binary/0` generator is here to speed up the generator a bit and also to allow for more repetition in the\n  # explored domain\n  # TODO: rethink this again, and compare with discussions here:\n  # https://github.com/omgnetwork/elixir-omg/pull/1251\n  # Can the binaries be generated more efficiently and explore the cases interesting to us better?\n  @n_prescribed_binaries 20\n  @prescribed_binaries for i <- 0..@n_prescribed_binaries, do: :binary.encode_unsigned(i)\n\n  defp prescribed_binary(), do: union(@prescribed_binaries)\n  defp pragmatic_binary(), do: union([binary(), prescribed_binary()])\n\n  defp non_empty_binary(), do: such_that(b <- pragmatic_binary(), when: b != \"\")\n\n  #\n  # auxiliary helper functions\n\n  defp chunk(proof), do: for(<<chunk::256 <- proof>>, do: <<chunk::256>>)\n  defp unchunk(chunked_proof), do: Enum.join(chunked_proof)\nend\n"
  },
  {
    "path": "apps/omg_conformance/test/support/conformance/merkle_proofs.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule Support.Conformance.MerkleProofs do\n  @moduledoc \"\"\"\n  Utility functions used when testing Elixir vs Solidity implementation conformance\n  \"\"\"\n\n  import ExUnit.Assertions, only: [assert: 1]\n\n  alias OMG.Eth.Encoding\n\n  @doc \"\"\"\n  Checks if the provided proof data returns true (valid proof) in the contract\n  \"\"\"\n  def solidity_proof_valid(leaf, index, root_hash, proof, contract) do\n    signature = \"checkMembership(bytes,uint256,bytes32,bytes)\"\n    args = [leaf, index, root_hash, proof]\n    return_types = [:bool]\n\n    try do\n      {:ok, result} = call_contract(contract, signature, args, return_types)\n      result\n      # Some incorrect proofs throw, and end up returning something that the ABI decoder borks on, hence rescue\n    rescue\n      e in CaseClauseError ->\n        # this term holds the failure reason, but attempted to be decoded as a bool. It is a huge int\n        %{term: failed_decoding_reason} = e\n        # now we bring it back to binary form\n        binary_reason = :binary.encode_unsigned(failed_decoding_reason)\n        # it should contain 4 bytes of the function selector and then zeros\n        assert_contract_reverted(binary_reason)\n        false\n    end\n  end\n\n  # see similar function in `Support.Conformance.SignaturesHashes`\n  defp assert_contract_reverted(chopped_reason_binary_result) do\n    # only geth is supported for the merkle proof conformance tests for now\n    :geth = Application.fetch_env!(:omg_eth, :eth_node)\n\n    # revert from `call_contract` it returns something resembling a reason\n    # binary (beginning with 4-byte function selector). We need to assume that this is in fact a revert\n    assert <<0::size(28)-unit(8)>> = binary_part(chopped_reason_binary_result, 4, 28)\n  end\n\n  defp call_contract(contract, signature, args, return_types) do\n    data = ABI.encode(signature, args)\n\n    {:ok, return} =\n      Ethereumex.HttpClient.eth_call(%{\n        from: Encoding.to_hex(contract),\n        to: Encoding.to_hex(contract),\n        data: Encoding.to_hex(data)\n      })\n\n    decode_answer(return, return_types)\n  end\n\n  defp decode_answer(enc_return, return_types) do\n    single_return =\n      enc_return\n      |> Encoding.from_hex()\n      |> ABI.TypeDecoder.decode(return_types)\n      |> hd()\n\n    {:ok, single_return}\n  end\nend\n"
  },
  {
    "path": "apps/omg_conformance/test/support/conformance/property.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule Support.Conformance.PropertyGenerators do\n  @moduledoc \"\"\"\n  Utility functions (mainly `:propcheck` generators) useful for building property tests for conformance tests\n  \"\"\"\n\n  alias OMG.Watcher.State.Transaction\n\n  use PropCheck\n\n  require Transaction.Payment\n\n  @doc \"\"\"\n  Generates a payment transaction, as valid as possible\n  \"\"\"\n  def payment_tx() do\n    let [inputs <- valid_inputs_list(), outputs <- valid_outputs_list(), metadata <- hash()] do\n      Transaction.Payment.new(inputs, outputs, metadata)\n    end\n  end\n\n  @doc \"\"\"\n  Generates a pair of _distinct_ payment transactions, as valid as possible.\n\n  Mimicks the `payment_tx/0` generator, but uses mutations to generate the other transaction\n  \"\"\"\n  def distinct_payment_txs() do\n    proposition_result =\n      let [inputs <- valid_inputs_list(), outputs <- valid_outputs_list(), metadata <- hash()] do\n        tx1 = Transaction.Payment.new(inputs, outputs, metadata)\n\n        tx2 =\n          let [\n            inputs2 <- union([inputs, mutated_inputs(inputs), Enum.reverse(inputs)]),\n            outputs2 <- union([outputs, mutated_outputs(outputs), Enum.reverse(outputs)]),\n            metadata2 <- union([metadata, mutated_hash(metadata), hash()])\n          ] do\n            Transaction.Payment.new(inputs2, outputs2, metadata2)\n          end\n\n        {tx1, tx2}\n      end\n\n    such_that(pair <- proposition_result, when: is_pair_of_distinct_terms?(pair))\n  end\n\n  @doc \"\"\"\n  Generates a valid payment transaction using `payment_tx/0` then mutates it using a structure-blind binary mutation\n  \"\"\"\n  def tx_binary_with_mutation() do\n    proposition_result =\n      let [tx1 <- payment_tx()] do\n        tx1_binary = Transaction.raw_txbytes(tx1)\n        {tx1_binary, mutate_binary(tx1_binary)}\n      end\n\n    such_that(pair <- proposition_result, when: is_pair_of_distinct_terms?(pair))\n  end\n\n  @doc \"\"\"\n  Generates a valid payment transaction using `payment_tx/0` then mutates it using a RLP-aware mutation\n  \"\"\"\n  def tx_binary_with_rlp_mutation() do\n    proposition_result =\n      let [tx1 <- payment_tx()] do\n        tx1_binary = Transaction.raw_txbytes(tx1)\n        {tx1_binary, rlp_mutate_binary(tx1_binary)}\n      end\n\n    such_that(pair <- proposition_result, when: is_pair_of_distinct_terms?(pair))\n  end\n\n  defp is_pair_of_distinct_terms?({base_term, new_term}), do: base_term != new_term\n\n  defp non_zero_address(), do: union([exactly(<<1::160>>), binary(20)])\n  defp address(), do: union([exactly(<<0::160>>), exactly(<<1::160>>), binary(20)])\n  defp hash(), do: union([exactly(<<0::256>>), exactly(<<1::256>>), binary(32)])\n\n  defp injectable_binary() do\n    union([\n      binary(),\n      <<0::8>>,\n      <<1::8>>,\n      <<0::16>>,\n      <<1::16>>,\n      <<0::32>>,\n      <<1::32>>,\n      <<0::128>>,\n      <<1::128>>,\n      <<0::256>>,\n      <<1::256>>\n    ])\n  end\n\n  # taken from ex_plasma, where they have been taken from:\n  # Contract settings\n  # These are being hard-coded from the same values on the contracts.\n  # See: https://github.com/omgnetwork/plasma-contracts/blob/master/plasma_framework/contracts/src/utils/PosLib.sol#L16-L23\n  # TODO: when this moves to `ex_plasma`, fix this properly\n  @block_offset 1_000_000_000\n  @transaction_offset 10_000\n  @max_txindex trunc(:math.pow(2, 16) - 1)\n  @max_blknum trunc((:math.pow(2, 54) - 1 - @max_txindex) / (@block_offset / @transaction_offset))\n\n  defp valid_blknum(), do: integer(0, @max_blknum)\n  defp valid_txndex(), do: integer(0, @max_txindex)\n  defp valid_oindex(), do: integer(0, @transaction_offset - 1)\n\n  # TODO: revisit this to generate logic-wise invalid txs like zero inputs/outputs (H6)\n  defp valid_input_tuple() do\n    proposition_result =\n      let [blknum <- valid_blknum(), txindex <- valid_txndex(), oindex <- valid_oindex()] do\n        {blknum, txindex, oindex}\n      end\n\n    such_that({blknum, txindex, oindex} <- proposition_result, when: blknum + txindex + oindex > 0)\n  end\n\n  # TODO: revisit the case of negative amounts, funny things happen\n  defp valid_output_tuple() do\n    let [owner <- non_zero_address(), currency <- address(), amount <- pos_integer()] do\n      {owner, currency, amount}\n    end\n  end\n\n  defp valid_inputs_list() do\n    such_that(l <- list(valid_input_tuple()), when: length(l) <= Transaction.Payment.max_inputs())\n  end\n\n  defp valid_outputs_list() do\n    such_that(l <- list(valid_output_tuple()), when: length(l) > 0 && length(l) <= Transaction.Payment.max_outputs())\n  end\n\n  defp mutated_hash(base_hash) do\n    # TODO: provide more cases\n    OMG.Watcher.Crypto.hash(base_hash)\n  end\n\n  defp mutated_inputs(inputs) do\n    # TODO: provide more cases\n    if Enum.empty?(inputs), do: valid_inputs_list(), else: tl(inputs)\n  end\n\n  defp mutated_outputs(outputs) do\n    # TODO: provide more cases\n    if length(outputs) == 1, do: valid_outputs_list(), else: tl(outputs)\n  end\n\n  defp prepend_binary(base_binary) do\n    let(random_binary <- injectable_binary(), do: random_binary <> base_binary)\n  end\n\n  defp apend_binary(base_binary) do\n    let(random_binary <- injectable_binary(), do: base_binary <> random_binary)\n  end\n\n  defp substring_binary(base_binary) do\n    base_length = byte_size(base_binary)\n\n    let [from <- integer(0, base_length - 1)] do\n      max_substring_length = max(1, base_length - from)\n\n      let [substring_length <- integer(1, max_substring_length)] do\n        binary_part(base_binary, from, substring_length)\n      end\n    end\n  end\n\n  defp insert_into_binary(base_binary) do\n    base_length = byte_size(base_binary)\n\n    let [from <- integer(0, base_length - 1), random_binary <- injectable_binary()] do\n      binary_part(base_binary, 0, from) <> random_binary <> binary_part(base_binary, from, base_length - from)\n    end\n  end\n\n  defp mutate_binary(base_binary) do\n    union([\n      prepend_binary(base_binary),\n      apend_binary(base_binary),\n      substring_binary(base_binary),\n      insert_into_binary(base_binary)\n    ])\n  end\n\n  defp inject_extra_item(base_rlp_items) when is_list(base_rlp_items) do\n    rlp_items_length = length(base_rlp_items)\n\n    let [new_item <- rlp_item_generator(), index <- integer(0, rlp_items_length)] do\n      List.insert_at(base_rlp_items, index, new_item)\n    end\n  end\n\n  # base wasn't a list so we make one now!\n  defp inject_extra_item(base_rlp_items) do\n    union([[rlp_item_generator(), base_rlp_items], [base_rlp_items, rlp_item_generator()]])\n  end\n\n  defp try_reversing(rlp_item) when is_list(rlp_item) and length(rlp_item) > 1, do: Enum.reverse(rlp_item)\n  defp try_reversing(rlp_item), do: rlp_item\n\n  defp swap_in_rlp([]), do: rlp_item_generator()\n\n  defp swap_in_rlp(rlp_items) when is_list(rlp_items) do\n    rlp_items_length = length(rlp_items)\n\n    # first we pick were we _could_ change the list\n    let [index <- integer(0, rlp_items_length - 1)] do\n      to_swap = Enum.at(rlp_items, index)\n      # now we either go deeper to change it or change right here\n      let [mutated_rlp <- mutate_sub_rlp(to_swap)] do\n        List.replace_at(rlp_items, index, mutated_rlp)\n      end\n    end\n  end\n\n  defp swap_in_rlp(rlp_item) when is_integer(rlp_item) or is_binary(rlp_item), do: rlp_item_generator()\n\n  defp rlp_item_generator(),\n    do: union([[], injectable_binary(), non_neg_integer(), list(union([injectable_binary(), non_neg_integer()]))])\n\n  defp mutate_sub_rlp(base_rlp_items) do\n    union([\n      rlp_item_generator(),\n      swap_in_rlp(base_rlp_items),\n      try_reversing(base_rlp_items),\n      inject_extra_item(base_rlp_items)\n    ])\n  end\n\n  defp rlp_mutate_binary(base_binary) do\n    # TODO: these mutations used could use improving/extending\n    base_rlp_items = base_binary |> Transaction.decode!() |> Transaction.Protocol.get_data_for_rlp()\n\n    let([mutated_rlp <- mutate_sub_rlp(base_rlp_items)]) do\n      ExRLP.encode(mutated_rlp)\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_conformance/test/support/conformance/signatures_hashes.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule Support.Conformance.SignaturesHashes do\n  @moduledoc \"\"\"\n  Utility functions that used when testing Elixir vs Solidity implementation conformance\n  \"\"\"\n\n  import ExUnit.Assertions, only: [assert: 1]\n\n  alias OMG.Eth.Encoding\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.TypedDataHash\n\n  @doc \"\"\"\n  Check if both implementations treat distinct transactions as distinct but produce sign hashes consistently\n  \"\"\"\n  def verify_distinct(tx1, tx2, contract) do\n    # NOTE: those two verifies might be redundant, rethink sometimes. For now keeping to increase chance of picking up\n    # discrepancies\n    verify(tx1, contract)\n    verify(tx2, contract)\n    assert solidity_hash!(tx1, contract) != solidity_hash!(tx2, contract)\n    assert elixir_hash(tx1) != elixir_hash(tx2)\n  end\n\n  @doc \"\"\"\n  Check if both implementations product the same signature hash\n  \"\"\"\n  def verify(tx, contract) do\n    assert solidity_hash!(tx, contract) == elixir_hash(tx)\n  end\n\n  @doc \"\"\"\n  Check if both implementations error for a binary that's known to not be a validly decoding transaction\n  \"\"\"\n  def verify_both_error(some_binary, contract) do\n    # elixir implementation errors\n    assert {:error, _} = Transaction.decode(some_binary)\n\n    # solidity implementation errors\n    some_binary\n    |> solidity_hash(contract)\n    |> assert_contract_reverted()\n\n    true\n  end\n\n  @doc \"\"\"\n  Check if both implementations either:\n    - treat distinct transactions as distinct but produce sign hashes consistently\n    - both error\n  _under the condition that `tx2_binary` decodes fine in the \"native\" implementation in Elixir_\n  \"\"\"\n  def verify_distinct_or_erroring(tx1_binary, tx2_binary, contract) do\n    # TODO - think of a better approach to handling the different treatment of valid/admissible tx/output types\n    #      there shouldn't be that many cases, 2 (`{:ok, _}` and `{:error, _}`) should ideally do\n    case Transaction.decode(tx2_binary) do\n      # if the mutated transaction decodes fine, we check whether signature hashes match across impls and are distinct\n      {:ok, _} ->\n        verify_distinct(Transaction.decode!(tx1_binary), Transaction.decode!(tx2_binary), contract)\n\n      # NOTE: unrecognized tx/output type is never picked up in the contract, since there, decoding assumes already a\n      #       particular type (i.e. Payment) and only checks if delivered type (`1`, `2`, ...) is correct in later stage\n      #       when fetching and verifying the `ISpendingCondition`\n      {:error, :unrecognized_transaction_type} ->\n        true\n\n      {:error, :unrecognized_output_type} ->\n        true\n\n      # NOTE: another temporary special case handling, until a better idea comes. `tx_type` 3 is `Transaction.Fee`\n      #       transaction which pops out as `malformed` in `elixir-omg` and is accepted by contracts\n      {:error, :malformed_transaction} ->\n        case ExRLP.decode(tx2_binary) do\n          # first RLP item of the transaction specifies the tx type as `Transaction.Fee` - can't test further\n          [<<3>> | _] -> true\n          # in all other cases the contract should revert\n          _ -> verify_both_error(tx2_binary, contract)\n        end\n\n      # in other cases of errors, we check whether both implementations reject the mutated transaction\n      {:error, _} ->\n        verify_both_error(tx2_binary, contract)\n    end\n  end\n\n  # NOTE: `solidity_hash!/2` returns `<<8, 195, 121, 160, 0, 0, 0, more zeroes...>>` on revert, see note on\n  #       `solidity_hash/2`\n  defp solidity_hash!(tx, contract) do\n    {:ok, solidity_hash} = solidity_hash(tx, contract)\n    solidity_hash\n  end\n\n  defp solidity_hash(%{} = tx, contract), do: tx |> Transaction.raw_txbytes() |> solidity_hash(contract)\n\n  # NOTE: `solidity_hash/2` returns something like `{:ok, <<8, 195, 121, 160, 0, 0, 0, more zeroes...>>}`, on contract\n  #       revert, when using `:geth` Ethereum node. If an assertion fails with such a result, it indicates the contract\n  #       rejected some transaction to signhash unexpectedly.\n  defp solidity_hash(encoded_tx, contract) when is_binary(encoded_tx) do\n    call_contract(contract, \"hashTx(address,bytes)\", [contract, encoded_tx], [{:bytes, 32}])\n  end\n\n  defp elixir_hash(%{} = tx), do: TypedDataHash.hash_struct(tx)\n  defp elixir_hash(encoded_tx), do: encoded_tx |> Transaction.decode!() |> elixir_hash()\n\n  defp assert_contract_reverted(result) do\n    {:ok, chopped_reason_binary_result} = result\n    assert <<0::size(28)-unit(8)>> = binary_part(chopped_reason_binary_result, 4, 28)\n  end\n\n  defp call_contract(contract, signature, args, return_types) do\n    data = ABI.encode(signature, args)\n\n    {:ok, return} =\n      Ethereumex.HttpClient.eth_call(%{\n        from: Encoding.to_hex(contract),\n        to: Encoding.to_hex(contract),\n        data: Encoding.to_hex(data)\n      })\n\n    decode_answer(return, return_types)\n  end\n\n  defp decode_answer(enc_return, return_types) do\n    single_return =\n      enc_return\n      |> Encoding.from_hex()\n      |> ABI.TypeDecoder.decode(return_types)\n      |> hd()\n\n    {:ok, single_return}\n  end\nend\n"
  },
  {
    "path": "apps/omg_conformance/test/support/conformance/signatures_hashes_case.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule Support.Conformance.SignaturesHashesCase do\n  @moduledoc \"\"\"\n  `ExUnit` test case for the setup required by a test of Elixir and Solidity implementation conformance\n  \"\"\"\n  alias Support.SnapshotContracts\n\n  use ExUnit.CaseTemplate\n\n  using do\n    quote do\n      @alice <<215, 32, 17, 47, 111, 72, 20, 47, 149, 226, 138, 242, 35, 254, 141, 212, 16, 22, 155, 182>>\n      @bob <<141, 246, 138, 77, 76, 3, 78, 54, 173, 40, 234, 195, 29, 170, 154, 64, 99, 14, 118, 139>>\n      @eth <<0::160>>\n      @token <<235, 169, 32, 193, 242, 237, 159, 137, 184, 46, 124, 13, 178, 171, 61, 87, 179, 179, 135, 146>>\n      @zero_address <<0::160>>\n    end\n  end\n\n  setup_all do\n    {:ok, exit_fn} = Support.DevNode.start()\n    contracts = SnapshotContracts.parse_contracts()\n    signtest_addr_hex = contracts[\"CONTRACT_ADDRESS_PAYMENT_EIP_712_LIB_MOCK\"]\n    old_config = Application.get_all_env(:omg_eth)\n    :ok = Application.put_env(:omg_eth, :contract_addr, %{plasma_framework: signtest_addr_hex})\n\n    on_exit(fn ->\n      # reverting to the original values from `omg_eth/config/test.exs`\n      :ok = Application.put_all_env(omg_eth: old_config)\n\n      exit_fn.()\n    end)\n\n    [contract: OMG.Eth.Encoding.from_hex(signtest_addr_hex)]\n  end\nend\n"
  },
  {
    "path": "apps/omg_conformance/test/test_helper.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\nExUnit.configure(exclude: [integration: true, property: true])\n{:ok, _} = Application.ensure_all_started(:propcheck)\nExUnit.start()\n"
  },
  {
    "path": "apps/omg_db/lib/db.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.DB do\n  @moduledoc \"\"\"\n  DB API module provides an interface to all needed functions that need to be implemented by the\n  underlying database layer.\n  \"\"\"\n  use Spandex.Decorators\n\n  alias OMG.DB.RocksDB\n  @type utxo_pos_db_t :: {pos_integer, non_neg_integer, non_neg_integer}\n\n  @callback start_link(term) :: GenServer.on_start()\n  @callback child_spec() :: Supervisor.child_spec()\n  @callback child_spec(term) :: Supervisor.child_spec()\n  @callback init(String.t()) :: :ok\n  @callback init() :: :ok\n  @callback initiation_multiupdate() :: :ok | {:error, any}\n\n  @callback multi_update(term()) :: :ok | {:error, any}\n  @callback blocks(block_to_fetch :: list()) :: {:ok, list(term)}\n  @callback utxos() :: {:ok, list({utxo_pos_db_t, term})}\n  @callback utxo(utxo_pos_db_t) :: {:ok, term} | :not_found\n  @callback competitors_info() :: {:ok, list(term)}\n  @callback spent_blknum(utxo_pos_db_t()) :: {:ok, pos_integer} | :not_found\n  @callback block_hashes(integer()) :: {:ok, list()}\n  @callback child_top_block_number() :: {:ok, non_neg_integer()} | :not_found\n  @callback get_single_value(atom()) :: {:ok, term} | :not_found\n  @callback batch_get(atom(), list(term)) :: {:ok, list(term)} | :not_found\n  @callback get_all_by_type(atom()) :: {:ok, list(term)} | :not_found\n\n  # callbacks useful for injecting a specific server implementation\n  @callback initiation_multiupdate(GenServer.server()) :: :ok | {:error, any}\n  @callback multi_update(term(), GenServer.server()) :: :ok | {:error, any}\n  @callback blocks(block_to_fetch :: list(), GenServer.server()) :: {:ok, list()} | {:error, any}\n  @callback utxos(GenServer.server()) :: {:ok, list({utxo_pos_db_t, term})} | {:error, any}\n  @callback utxo(utxo_pos_db_t, GenServer.server()) :: {:ok, term} | :not_found\n  @callback competitors_info(GenServer.server()) :: {:ok, list(term)} | {:error, any}\n  @callback spent_blknum(utxo_pos_db_t(), GenServer.server()) :: {:ok, pos_integer} | :not_found\n  @callback block_hashes(integer(), GenServer.server()) :: {:ok, list()}\n  @callback child_top_block_number(GenServer.server()) :: {:ok, non_neg_integer()} | :not_found\n  @callback get_single_value(atom(), GenServer.server()) :: {:ok, term} | :not_found\n  @callback batch_get(atom(), list(term), keyword()) :: {:ok, list(term)} | :not_found\n  @callback get_all_by_type(atom(), keyword()) :: {:ok, list(term)} | :not_found\n  @optional_callbacks child_spec: 1,\n                      initiation_multiupdate: 1,\n                      multi_update: 2,\n                      blocks: 2,\n                      utxos: 1,\n                      utxo: 2,\n                      spent_blknum: 2,\n                      block_hashes: 2,\n                      child_top_block_number: 1,\n                      get_single_value: 2\n\n  def start_link(args), do: RocksDB.start_link(args)\n\n  def child_spec(), do: RocksDB.child_spec()\n  def child_spec(args), do: RocksDB.child_spec(args)\n\n  def init(path) do\n    RocksDB.init(path)\n  end\n\n  def init() do\n    RocksDB.init()\n  end\n\n  @doc \"\"\"\n  Puts all zeroes and other init values to a generically initialized `OMG.DB`\n  \"\"\"\n\n  def initiation_multiupdate(), do: RocksDB.initiation_multiupdate()\n  def initiation_multiupdate(server), do: RocksDB.initiation_multiupdate(server)\n\n  @decorate span(service: :ethereum_event_listener, type: :backend, name: \"multi_update/1\")\n  def multi_update(db_updates), do: RocksDB.multi_update(db_updates)\n  def multi_update(db_updates, server), do: RocksDB.multi_update(db_updates, server)\n\n  def blocks(blocks_to_fetch), do: RocksDB.blocks(blocks_to_fetch)\n  def blocks(blocks_to_fetch, server), do: RocksDB.blocks(blocks_to_fetch, server)\n\n  def utxos(), do: RocksDB.utxos()\n  def utxos(server), do: RocksDB.utxos(server)\n\n  def utxo(utxo_pos), do: RocksDB.utxo(utxo_pos)\n  def utxo(utxo_pos, server), do: RocksDB.utxo(utxo_pos, server)\n\n  def competitors_info(), do: RocksDB.competitors_info()\n  def competitors_info(server), do: RocksDB.competitors_info(server)\n\n  def spent_blknum(utxo_pos), do: RocksDB.spent_blknum(utxo_pos)\n  def spent_blknum(utxo_pos, server), do: RocksDB.spent_blknum(utxo_pos, server)\n\n  def block_hashes(block_numbers_to_fetch), do: RocksDB.block_hashes(block_numbers_to_fetch)\n  def block_hashes(block_numbers_to_fetch, server), do: RocksDB.block_hashes(block_numbers_to_fetch, server)\n\n  def child_top_block_number(), do: RocksDB.child_top_block_number()\n\n  def get_single_value(parameter_name), do: RocksDB.get_single_value(parameter_name)\n  def get_single_value(parameter_name, server), do: RocksDB.get_single_value(parameter_name, server)\n\n  @doc \"\"\"\n  This is generic DB function that can batch get the specific data of a\n  specific type with the given specific keys of the type.\n  \"\"\"\n  def batch_get(type, specific_keys), do: RocksDB.batch_get(type, specific_keys)\n  def batch_get(type, specific_keys, opts), do: RocksDB.batch_get(type, specific_keys, opts)\n\n  @doc \"\"\"\n  This is generic DB function that can get all data of a specific type.\n  \"\"\"\n  def get_all_by_type(type), do: RocksDB.get_all_by_type(type)\n  def get_all_by_type(type, opts), do: RocksDB.get_all_by_type(type, opts)\n\n  @doc \"\"\"\n  A list of all atoms that we use as single-values stored in the database (i.e. markers/flags of all kinds)\n  \"\"\"\n  def single_value_parameter_names() do\n    [\n      # child chain - used at block forming\n      :child_top_block_number,\n      # watcher\n      :last_block_getter_eth_height,\n      :last_ife_exit_deleted_eth_height,\n      # watcher and child chain\n      :last_depositor_eth_height,\n      :last_exiter_eth_height,\n      :last_piggyback_exit_eth_height,\n      :last_in_flight_exit_eth_height,\n      :last_exit_processor_eth_height,\n      :last_exit_finalizer_eth_height,\n      :last_exit_challenger_eth_height,\n      :last_in_flight_exit_processor_eth_height,\n      :last_ife_exit_deleted_eth_height,\n      :last_piggyback_processor_eth_height,\n      :last_competitor_processor_eth_height,\n      :last_challenges_responds_processor_eth_height,\n      :last_piggyback_challenges_processor_eth_height,\n      :last_ife_exit_finalizer_eth_height,\n      :omg_eth_contracts\n    ]\n  end\nend\n"
  },
  {
    "path": "apps/omg_db/lib/omg_db/application.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.DB.Application do\n  @moduledoc false\n\n  use Application\n\n  def start(_type, _args) do\n    children = [OMG.DB.child_spec()]\n\n    opts = [strategy: :one_for_one, name: OMG.DB.Supervisor]\n\n    Supervisor.start_link(children, opts)\n  end\n\n  def start_phase(:attach_telemetry, :normal, _phase_args) do\n    handlers = [[\"measure-db\", OMG.DB.Measure.supported_events(), &OMG.DB.Measure.handle_event/4, nil]]\n\n    Enum.each(handlers, fn handler ->\n      case apply(:telemetry, :attach_many, handler) do\n        :ok -> :ok\n        {:error, :already_exists} -> :ok\n      end\n    end)\n  end\nend\n"
  },
  {
    "path": "apps/omg_db/lib/omg_db/measure.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.DB.Measure do\n  @moduledoc \"\"\"\n   A telemetry handler for DB related metrics.\n  \"\"\"\n  alias OMG.Status.Metric.Datadog\n  import OMG.Status.Metric.Event, only: [name: 1]\n\n  alias OMG.DB.RocksDB.Server\n\n  @write :write\n  @read :read\n  @multiread :multiread\n  @keys [@write, @read, @multiread]\n\n  @services [Server]\n\n  @supported_events List.foldl(@services, [], fn service, acc ->\n                      acc ++\n                        [\n                          [:process, service],\n                          [:update_write, service],\n                          [:update_read, service],\n                          [:update_multiread, service]\n                        ]\n                    end)\n  def supported_events(), do: @supported_events\n\n  def handle_event([:process, service_name], _, state, _config) when service_name in @services do\n    value =\n      self()\n      |> Process.info(:message_queue_len)\n      |> elem(1)\n\n    _ = Datadog.gauge(name(:db_message_queue_len), value, tags: [\"service_name:#{service_name}\"])\n\n    Enum.each(@keys, fn table_key ->\n      case :ets.take(state.name, table_key) do\n        [{key, value}] ->\n          _ = Datadog.gauge(name(key), value)\n\n        _ ->\n          # handling the case where the entry doesn't exist yet\n          :skip\n      end\n    end)\n  end\n\n  def handle_event([:update_write, service_name], _, state, _config) when service_name in @services do\n    :ets.update_counter(state.name, @write, {2, 1}, {@write, 0})\n  end\n\n  def handle_event([:update_read, service_name], _, state, _config) when service_name in @services do\n    :ets.update_counter(state.name, @read, {2, 1}, {@read, 0})\n  end\n\n  def handle_event([:update_multiread, service_name], _, state, _config) when service_name in @services do\n    :ets.update_counter(state.name, @multiread, {2, 1}, {@multiread, 0})\n  end\nend\n"
  },
  {
    "path": "apps/omg_db/lib/omg_db/models/payment_exit_info.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.DB.Models.PaymentExitInfo do\n  @moduledoc \"\"\"\n  DB model wrapper that is responsible for Payment (V1) Exit Info.\n  \"\"\"\n\n  alias OMG.DB\n\n  @server_name OMG.DB.RocksDB.Server\n\n  @ten_seconds 10_000\n  @one_minute 60_000\n\n  def exit_info(utxo_pos, server \\\\ @server_name) do\n    {:ok, data} = DB.batch_get(:exit_info, [utxo_pos], server: server)\n    {:ok, hd(data)}\n  end\n\n  def exit_infos(utxo_pos_list, server \\\\ @server_name)\n      when is_list(utxo_pos_list) do\n    DB.batch_get(:exit_info, utxo_pos_list, server: server, timeout: @ten_seconds)\n  end\n\n  def all_exit_infos(server \\\\ @server_name) do\n    DB.get_all_by_type(:exit_info, server: server, timeout: @one_minute)\n  end\n\n  def all_in_flight_exits_infos(server \\\\ @server_name) do\n    DB.get_all_by_type(:in_flight_exit_info, server: server, timeout: @one_minute)\n  end\nend\n"
  },
  {
    "path": "apps/omg_db/lib/omg_db/release_tasks/init_key_value_db.ex",
    "content": "# Copyright 2019-2019 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.DB.ReleaseTasks.InitKeyValueDB do\n  @moduledoc \"\"\"\n    Creates an empty instance of OMG DB storage and fills it with the required initial data.\n  \"\"\"\n\n  @start_apps [:logger, :crypto, :ssl]\n  require Logger\n\n  def run() do\n    _ = on_load()\n    path = Application.get_env(:omg_db, :path)\n    process(path)\n  end\n\n  defp process(path) do\n    _ = Logger.warn(\"Creating database at #{inspect(path)}\")\n    result = init_kv_db(path)\n    Enum.each(Enum.reverse(@start_apps), &Application.stop/1)\n    result\n  end\n\n  defp init_kv_db(path) do\n    case OMG.DB.init(path) do\n      {:error, term} ->\n        _ = Logger.error(\"Could not initialize the DB in #{path}. Reason #{inspect(term)}\")\n        {:error, term}\n\n      :ok ->\n        _ = Logger.warn(\"The database at #{inspect(path)} has been created\")\n    end\n  end\n\n  defp on_load() do\n    _ = Enum.each(@start_apps, &Application.ensure_all_started/1)\n    _ = Application.load(:omg_db)\n  end\nend\n"
  },
  {
    "path": "apps/omg_db/lib/omg_db/release_tasks/init_keys_with_values.ex",
    "content": "# Copyright 2019-2019 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.DB.ReleaseTasks.InitKeysWithValues do\n  @moduledoc \"\"\"\n  Sets values for keys stored in RocksDB, if they are not set.\n  \"\"\"\n  require Logger\n\n  @keys_to_values [last_ife_exit_deleted_eth_height: 0]\n\n  def run() do\n    {:ok, _} = Application.ensure_all_started(:logger)\n\n    path = Application.get_env(:omg_db, :path)\n    Application.put_env(:omg_db, :path, path)\n\n    case Application.ensure_all_started(:omg_db) do\n      {:ok, _} ->\n        Enum.each(@keys_to_values, &set_single_value/1)\n\n      {:error, _} ->\n        _ = Logger.info(\"DB not initialized yet, no action required\")\n        :ok\n    end\n  end\n\n  defp set_single_value({key, init_val}) do\n    case OMG.DB.RocksDB.get_single_value(key) do\n      :not_found ->\n        :ok = OMG.DB.RocksDB.multi_update([{:put, key, init_val}])\n        _ = Logger.info(\"#{key} not set. Setting it to #{inspect(init_val)}\")\n        :ok\n\n      {:ok, _} ->\n        :ok\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_db/lib/omg_db/release_tasks/set_key_value_db.ex",
    "content": "# Copyright 2019-2019 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.DB.ReleaseTasks.SetKeyValueDB do\n  @moduledoc false\n  @behaviour Config.Provider\n  require Logger\n  @app :omg_db\n\n  def init(args) do\n    args\n  end\n\n  def load(config, args) do\n    _ = on_load()\n    release = Keyword.get(args, :release)\n\n    case get_env(\"DB_PATH\") do\n      root_path when is_binary(root_path) ->\n        set_db(config, root_path, release)\n\n      _ ->\n        root_path = Path.join([System.user_home!(), \".omg/data\"])\n        set_db(config, root_path, release)\n    end\n  end\n\n  defp set_db(config, root_path, release) do\n    path = Path.join([root_path, \"#{release}\"])\n    _ = Logger.info(\"CONFIGURATION: App: #{@app} Key: DB_PATH Value: #{inspect(path)}.\")\n    # if we want to access the updated path in the same VM instance, we need to update it imidiatelly\n    Application.put_env(@app, :path, path)\n    Config.Reader.merge(config, omg_db: [path: path])\n  end\n\n  defp get_env(key), do: System.get_env(key)\n\n  defp on_load() do\n    _ = Application.ensure_all_started(:logger)\n    _ = Application.load(@app)\n  end\nend\n"
  },
  {
    "path": "apps/omg_db/lib/omg_db/rocks_db.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.DB.RocksDB do\n  @moduledoc \"\"\"\n  Our-types-aware port/adapter to a database backend.\n  Contains functions to access data stored in the database\n  \"\"\"\n  alias OMG.DB\n  @behaviour OMG.DB\n\n  require Logger\n\n  @server_name OMG.DB.RocksDB.Server\n\n  @default_genserver_timeout 5000\n  @one_minute 60_000\n  @ten_minutes 10 * @one_minute\n\n  @type utxo_pos_db_t :: {pos_integer, non_neg_integer, non_neg_integer}\n\n  def start_link(args) do\n    @server_name.start_link(args)\n  end\n\n  def child_spec() do\n    db_path = Application.fetch_env!(:omg_db, :path)\n    args = [db_path: db_path, name: OMG.DB.RocksDB.Server]\n\n    %{\n      id: OMG.DB.RocksDB.Server,\n      start: {OMG.DB.RocksDB.Server, :start_link, [args]},\n      type: :worker\n    }\n  end\n\n  def child_spec([db_path: _db_path, name: server_name] = args) do\n    %{\n      id: server_name,\n      start: {OMG.DB.RocksDB.Server, :start_link, [args]},\n      type: :worker\n    }\n  end\n\n  def multi_update(db_updates, server_name \\\\ @server_name) do\n    GenServer.call(server_name, {:multi_update, db_updates})\n  end\n\n  @spec blocks(block_to_fetch :: list(), atom) :: {:ok, list()} | {:error, any}\n  def blocks(blocks_to_fetch, server_name \\\\ @server_name)\n\n  def blocks([], _server_name), do: {:ok, []}\n\n  def blocks(blocks_to_fetch, server_name) do\n    GenServer.call(server_name, {:blocks, blocks_to_fetch})\n  end\n\n  def utxos(server_name \\\\ @server_name) do\n    _ = Logger.info(\"Reading UTXO set, this might take a while. Allowing #{inspect(@ten_minutes)} ms\")\n    GenServer.call(server_name, :utxos, @ten_minutes)\n  end\n\n  def utxo(utxo_pos, server_name \\\\ @server_name) do\n    GenServer.call(server_name, {:utxo, utxo_pos})\n  end\n\n  def competitors_info(server_name \\\\ @server_name) do\n    _ = Logger.info(\"Reading competitors' info, this might take a while. Allowing #{inspect(@one_minute)} ms\")\n    GenServer.call(server_name, :competitors_info, @one_minute)\n  end\n\n  def spent_blknum(utxo_pos, server_name \\\\ @server_name) do\n    GenServer.call(server_name, {:spent_blknum, utxo_pos})\n  end\n\n  def block_hashes(block_numbers_to_fetch, server_name \\\\ @server_name) do\n    GenServer.call(server_name, {:block_hashes, block_numbers_to_fetch})\n  end\n\n  def child_top_block_number(server_name \\\\ @server_name) do\n    GenServer.call(server_name, :child_top_block_number)\n  end\n\n  # Note: *_eth_height values below denote actual Ethereum height service has processed.\n  # It might differ from \"latest\" Ethereum block.\n\n  def get_single_value(parameter_name, server_name \\\\ @server_name) do\n    GenServer.call(server_name, {:get_single_value, parameter_name})\n  end\n\n  @doc \"\"\"\n  Batch get data of a type with the given specific keys.\n\n  optional args includes:\n  1. timeout (in ms). Defaults to 5000 which is the same default value of Genserver.\n  2. server (type in Genserver.server()). Defaults to OMG.DB.RocksDB.Server.\n  \"\"\"\n  def batch_get(type, specific_keys, opts \\\\ []) do\n    timeout = opts[:timeout] || @default_genserver_timeout\n    server = opts[:server] || @server_name\n\n    _ =\n      Logger.info(\n        \"Batch get data for type #{inspect(type)} with the following keys #{inspect(specific_keys)}.\" <>\n          \" Allowing #{inspect(timeout)} ms\"\n      )\n\n    GenServer.call(server, {:get, type, specific_keys}, timeout)\n  end\n\n  @doc \"\"\"\n  Get ALL data of a type.\n\n  optional args includes:\n  1. timeout (in ms). Defaults to 5000 which is the same default value of Genserver.\n  2. server (type in Genserver.server()). Defaults to OMG.DB.RocksDB.Server.\n  \"\"\"\n  def get_all_by_type(type, opts \\\\ []) do\n    timeout = opts[:timeout] || @default_genserver_timeout\n    server = opts[:server] || @server_name\n\n    _ =\n      Logger.info(\n        \"Reading all data for type #{inspect(type)}, this might take a while. Allowing #{inspect(timeout)} ms\"\n      )\n\n    GenServer.call(server, {:get_all_by_type, type}, timeout)\n  end\n\n  def initiation_multiupdate(server_name \\\\ @server_name) do\n    # setting a number of markers to zeroes\n    DB.single_value_parameter_names()\n    |> Enum.map(&{:put, &1, 0})\n    |> multi_update(server_name)\n  end\n\n  @doc \"\"\"\n  Does all of the initialization of `OMG.DB` based on the configured path\n  \"\"\"\n  def init(), do: do_init(@server_name, Application.fetch_env!(:omg_db, :path))\n\n  def init(path) when is_binary(path) do\n    :ok = Application.put_env(:omg_db, :path, path, persistent: true)\n    do_init(@server_name, path)\n  end\n\n  def init(server_name), do: do_init(server_name, Application.fetch_env!(:omg_db, :path))\n  def init(server_name, path), do: do_init(server_name, path)\n\n  # File.mkdir_p is called at the application start\n  # sobelow_skip [\"Traversal\"]\n  defp do_init(server_name, path) do\n    :ok = File.mkdir_p(path)\n\n    with :ok <- server_name.init_storage(path),\n         {:ok, started_apps} <- Application.ensure_all_started(:omg_db),\n         :ok <- initiation_multiupdate(server_name) do\n      started_apps |> Enum.reverse() |> Enum.each(fn app -> :ok = Application.stop(app) end)\n\n      :ok\n    else\n      error ->\n        _ = Logger.error(\"Unable to init: #{inspect(error)}\")\n        error\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_db/lib/omg_db/rocksdb/core.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.DB.RocksDB.Core do\n  @moduledoc \"\"\"\n  Responsible for converting type-aware, logic-specific queries and updates into rocksdb specific queries and updates\n  \"\"\"\n\n  # adapter - testable, if we really really want to\n  use Spandex.Decorators\n\n  @single_value_parameter_names OMG.DB.single_value_parameter_names()\n\n  # if we keep the prefix byte size consistent across all keys, we're able to use\n  # prefix extractor to reduce the number of IO scans\n  # more https://github.com/facebook/rocksdb/wiki/Prefix-Seek-API-Changes\n  @keys_prefixes %{\n    # watcher (Exit Processor) and child chain (Fresh Blocks)\n    block: \"block\",\n    # watcher (Exit Processor) and child chain (Block Queue)\n    block_hash: \"hashb\",\n    # watcher and child chain\n    utxo: \"utxoi\",\n    # watcher and child chain\n    exit_info: \"exiti\",\n    # watcher only\n    in_flight_exit_info: \"infle\",\n    # watcher only\n    competitor_info: \"compi\",\n    # watcher only\n    spend: \"spend\",\n    # watcher and child chain\n    omg_eth_contracts: \"omg_eth_contracts\"\n  }\n\n  @key_types Map.keys(@keys_prefixes)\n\n  def parse_multi_updates(db_updates), do: Enum.flat_map(db_updates, &parse_multi_update/1)\n\n  @doc \"\"\"\n  Interprets the response from rocksdb and returns a success-decorated result\n  \"\"\"\n  @spec decode_value({:ok, binary()} | :not_found) :: {:ok, term()} | :not_found\n  def decode_value(db_response) do\n    case decode_response(db_response) do\n      :not_found -> :not_found\n      other -> {:ok, other}\n    end\n  end\n\n  @doc \"\"\"\n  Interprets an enumerable of responses from rocksdb and decorates the enumerable with a `{:ok, _enumerable}`\n  if no errors occurred\n  \"\"\"\n  @spec decode_values(Enumerable.t()) :: {:ok, list}\n  def decode_values(encoded_enumerable) do\n    raw_decoded = Enum.map(encoded_enumerable, fn encoded -> decode_response(encoded) end)\n    {:ok, raw_decoded}\n  end\n\n  def filter_keys(key_stream, type) when type in @key_types,\n    do: do_filter_keys(key_stream, Map.get(@keys_prefixes, type))\n\n  @doc \"\"\"\n  Produces a type-specific LevelDB key for a combination of type and type-agnostic/LevelDB-ignorant key\n  \"\"\"\n  def key(:block, hash) when is_binary(hash), do: @keys_prefixes.block <> hash\n  def key(parameter, _) when parameter in @single_value_parameter_names, do: Atom.to_string(parameter)\n\n  def key(type, specific_key) when type in @key_types,\n    do: Map.get(@keys_prefixes, type) <> :erlang.term_to_binary(specific_key)\n\n  # `key_for_item` gets the type-specific key to persist a whole item at, as used by `:put` updates\n  defp key_for_item(:block, %{hash: hash} = _block), do: key(:block, hash)\n  defp key_for_item(:utxo, {position, _utxo}), do: key(:utxo, position)\n  defp key_for_item(:spend, {position, _blknum}), do: key(:spend, position)\n  defp key_for_item(:exit_info, {position, _exit_info}), do: key(:exit_info, position)\n  defp key_for_item(:in_flight_exit_info, {position, _info}), do: key(:in_flight_exit_info, position)\n  defp key_for_item(:competitor_info, {position, _info}), do: key(:competitor_info, position)\n  defp key_for_item(parameter, value) when parameter in @single_value_parameter_names, do: key(parameter, value)\n\n  defp parse_multi_update({:put, :block, %{number: number, hash: hash} = item}) do\n    [\n      {:put, key_for_item(:block, item), encode_value(:block, item)},\n      {:put, key(:block_hash, number), encode_value(:block_hash, hash)}\n    ]\n  end\n\n  defp parse_multi_update({:put, type, item}), do: [{:put, key_for_item(type, item), encode_value(type, item)}]\n  defp parse_multi_update({:delete, type, item}), do: [{:delete, key(type, item)}]\n\n  defp encode_value(:spend, {_position, blknum}), do: :erlang.term_to_binary(blknum)\n  defp encode_value(_type, value), do: :erlang.term_to_binary(value)\n\n  # sobelow_skip [\"Misc.BinToTerm\"]\n  defp decode_response(db_response) do\n    case db_response do\n      :not_found ->\n        :not_found\n\n      {:ok, encoded} ->\n        :erlang.binary_to_term(encoded, [:safe])\n\n      encoded ->\n        # iterator search returns raw values\n        :erlang.binary_to_term(encoded, [:safe])\n    end\n  end\n\n  defp do_filter_keys(reference, prefix) do\n    # https://github.com/facebook/rocksdb/wiki/Prefix-Seek-API-Changes#use-readoptionsprefix_seek\n    {:ok, iterator} = :rocksdb.iterator(reference, prefix_same_as_start: true)\n    move_iterator = :rocksdb.iterator_move(iterator, {:seek, prefix})\n    Enum.reverse(search(reference, iterator, move_iterator, []))\n  end\n\n  defp search(_reference, _iterator, {:error, :invalid_iterator}, acc), do: acc\n  defp search(reference, iterator, {:ok, _key, value}, acc), do: do_search(reference, iterator, [value | acc])\n\n  defp do_search(reference, iterator, acc) do\n    case :rocksdb.iterator_move(iterator, :next) do\n      {:error, :invalid_iterator} ->\n        # we've reached the end\n        :rocksdb.iterator_close(iterator)\n        acc\n\n      {:ok, _key, value} ->\n        do_search(reference, iterator, [value | acc])\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_db/lib/omg_db/rocksdb/server.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.DB.RocksDB.Server do\n  @moduledoc \"\"\"\n  Handles connection to rocksdb\n  \"\"\"\n\n  # All complex operations on data written/read should go into OMG.DB.RocksDB.Core\n\n  use GenServer\n\n  alias OMG.DB.RocksDB.Core\n  require Logger\n\n  defstruct [:db_ref, :name]\n\n  @type t() :: %__MODULE__{\n          db_ref: :rocksdb.db_handle(),\n          name: GenServer.name()\n        }\n  @doc \"\"\"\n  Initializes an empty RocksDB instance explicitly, so we can have control over it.\n  NOTE: `init` here is to init the GenServer and that assumes that `init_storage` has already been called\n  \"\"\"\n  @spec init_storage(binary) :: :ok | {:error, atom}\n  def init_storage(db_path) do\n    do_init_storage(String.to_charlist(db_path))\n  end\n\n  defp do_init_storage(db_path) do\n    with {:ok, db_ref} <- :rocksdb.open(db_path, create_if_missing: true),\n         true <- :rocksdb.is_empty(db_ref) || {:error, :rocksdb_not_empty},\n         do: :rocksdb.close(db_ref)\n  end\n\n  def start_link([db_path: _db_path, name: name] = args) do\n    GenServer.start_link(__MODULE__, args, name: name)\n  end\n\n  # https://github.com/facebook/rocksdb/wiki/RocksDB-Tuning-Guide#prefix-databases\n  def init(db_path: db_path, name: name) do\n    # needed so that terminate callback is called on normal close\n    db_path = String.to_charlist(db_path)\n    Process.flag(:trap_exit, true)\n    ^name = create_stats_table(name)\n\n    setup = [{:create_if_missing, false}, {:prefix_extractor, {:fixed_prefix_transform, 5}}]\n\n    case :rocksdb.open(db_path, setup) do\n      {:ok, db_ref} ->\n        {:ok, _} =\n          :timer.send_interval(Application.fetch_env!(:omg_db, :metrics_collection_interval), self(), :send_metrics)\n\n        _ = Logger.info(\"Started #{inspect(__MODULE__)}\")\n\n        {:ok, %__MODULE__{name: name, db_ref: db_ref}}\n\n      error ->\n        _ = Logger.error(\"It seems that database is not initialized. Check README.md\")\n        error\n    end\n  end\n\n  def handle_info(:send_metrics, state) do\n    :ok = :telemetry.execute([:process, __MODULE__], %{}, state)\n    {:noreply, state}\n  end\n\n  def handle_call({:multi_update, db_updates}, _from, state) do\n    do_multi_update(db_updates, state)\n  end\n\n  def handle_call({:blocks, blocks_to_fetch}, _from, state) do\n    do_blocks(blocks_to_fetch, state)\n  end\n\n  def handle_call(:utxos, _from, state) do\n    do_utxos(state)\n  end\n\n  def handle_call({:utxo, utxo_pos}, _from, state) do\n    do_utxo(utxo_pos, state)\n  end\n\n  def handle_call({:block_hashes, block_numbers_to_fetch}, _from, state) do\n    do_block_hashes(block_numbers_to_fetch, state)\n  end\n\n  def handle_call(:competitors_info, _from, state) do\n    do_competitors_info(state)\n  end\n\n  def handle_call({:get_single_value, parameter}, _from, state)\n      when is_atom(parameter) do\n    do_get_single_value(parameter, state)\n  end\n\n  def handle_call({:spent_blknum, utxo_pos}, _from, state) do\n    do_spent_blknum(utxo_pos, state)\n  end\n\n  def handle_call({:get, type, specific_keys}, _from, state) do\n    result =\n      Enum.map(\n        specific_keys,\n        fn key -> get_decoded_data_with_type_and_specific_key(type, key, state) end\n      )\n\n    {:reply, {:ok, result}, state}\n  end\n\n  def handle_call({:get_all_by_type, type}, _from, state) do\n    result = get_all_by_type(type, state)\n    {:reply, result, state}\n  end\n\n  # WARNING, terminate below will be called only if :trap_exit is set to true\n  def terminate(_reason, %__MODULE__{db_ref: db_ref}) do\n    :ok = :rocksdb.close(db_ref)\n  end\n\n  defp do_multi_update(db_updates, state) do\n    result =\n      db_updates\n      |> Core.parse_multi_updates()\n      |> write(state)\n\n    {:reply, result, state}\n  end\n\n  defp do_blocks(blocks_to_fetch, state) do\n    # we could avoid the number of IO random searches by using an iterator\n    # that would traverse all keys from :block prefix. Whether that's faster we don't have the data yet.\n    result =\n      blocks_to_fetch\n      |> Enum.map(fn block -> Core.key(:block, block) end)\n      |> Enum.map(fn key -> get(key, state) end)\n      |> Core.decode_values()\n\n    {:reply, result, state}\n  end\n\n  defp do_utxos(state) do\n    result = get_all_by_type(:utxo, state)\n    {:reply, result, state}\n  end\n\n  defp do_utxo(utxo_pos, state) do\n    result = Core.key(:utxo, utxo_pos) |> get(state) |> Core.decode_value()\n    {:reply, result, state}\n  end\n\n  defp do_block_hashes(block_numbers_to_fetch, state) do\n    result =\n      block_numbers_to_fetch\n      |> Enum.map(fn block_number -> Core.key(:block_hash, block_number) end)\n      |> Enum.map(fn key -> get(key, state) end)\n      |> Core.decode_values()\n\n    {:reply, result, state}\n  end\n\n  defp do_competitors_info(state) do\n    result = get_all_by_type(:competitor_info, state)\n    {:reply, result, state}\n  end\n\n  defp do_get_single_value(parameter, state) do\n    result =\n      parameter\n      |> Core.key(nil)\n      |> get(state)\n      |> Core.decode_value()\n\n    {:reply, result, state}\n  end\n\n  defp do_spent_blknum(utxo_pos, state) do\n    result =\n      :spend\n      |> Core.key(utxo_pos)\n      |> get(state)\n      |> Core.decode_value()\n\n    {:reply, result, state}\n  end\n\n  # iterator options\n  # same as read options\n  # this might be a use case for seek() https://github.com/facebook/rocksdb/wiki/Prefix-Seek-API-Changes\n  defp do_get_all_by_type(type, db_ref) do\n    Core.decode_values(Core.filter_keys(db_ref, type))\n  end\n\n  defp create_stats_table(name) do\n    case :ets.whereis(name) do\n      :undefined ->\n        true = name == :ets.new(name, table_settings())\n\n        name\n\n      _ ->\n        name\n    end\n  end\n\n  defp table_settings() do\n    [:named_table, :set, :public, write_concurrency: true]\n  end\n\n  # Argument order flipping tools :(\n  # write options\n  # write_options() = [{sync, boolean()} | {disable_wal, boolean()} | {ignore_missing_column_families, boolean()} |\n  # {no_slowdown, boolean()} | {low_pri, boolean()}]\n  # @spec write(Exleveldb.write_actions(), t) :: :ok | {:error, any}\n  defp write(operations, %__MODULE__{db_ref: db_ref} = state) do\n    :ok = :telemetry.execute([:update_write, __MODULE__], %{}, state)\n    :rocksdb.write(db_ref, operations, [])\n  end\n\n  defp get_decoded_data_with_type_and_specific_key(type, specific_key, state) do\n    {:ok, result} =\n      type\n      |> Core.key(specific_key)\n      |> get(state)\n      |> Core.decode_value()\n\n    result\n  end\n\n  # get read options\n  # read_options() = [{verify_checksums, boolean()} | {fill_cache, boolean()} | {iterate_upper_bound, binary()} |\n  # {iterate_lower_bound, binary()} | {tailing, boolean()} | {total_order_seek, boolean()} |\n  # {prefix_same_as_start, boolean()} | {snapshot, snapshot_handle()}]\n  @spec get(atom() | binary(), t) :: {:ok, binary()} | :not_found\n  defp get(key, %__MODULE__{db_ref: db_ref} = state) do\n    :ok = :telemetry.execute([:update_read, __MODULE__], %{}, state)\n    :rocksdb.get(db_ref, key, [])\n  end\n\n  defp get_all_by_type(type, %__MODULE__{db_ref: db_ref} = state) do\n    :ok = :telemetry.execute([:update_multiread, __MODULE__], %{}, state)\n    do_get_all_by_type(type, db_ref)\n  end\nend\n"
  },
  {
    "path": "apps/omg_db/mix.exs",
    "content": "defmodule OMG.DB.MixProject do\n  use Mix.Project\n\n  def project() do\n    [\n      app: :omg_db,\n      version: version(),\n      build_path: \"../../_build\",\n      config_path: \"../../config/config.exs\",\n      deps_path: \"../../deps\",\n      lockfile: \"../../mix.lock\",\n      elixir: \"~> 1.8\",\n      elixirc_paths: elixirc_paths(Mix.env()),\n      start_permanent: Mix.env() == :prod,\n      deps: deps(),\n      test_coverage: [tool: ExCoveralls]\n    ]\n  end\n\n  def application() do\n    [\n      extra_applications: [:logger, :telemetry],\n      start_phases: [{:attach_telemetry, []}],\n      mod: {OMG.DB.Application, []}\n    ]\n  end\n\n  defp version() do\n    \"git\"\n    |> System.cmd([\"describe\", \"--tags\", \"--abbrev=0\"])\n    |> elem(0)\n    |> String.replace(\"v\", \"\")\n    |> String.replace(\"\\n\", \"\")\n  end\n\n  # Specifies which paths to compile per environment.\n  defp elixirc_paths(:prod), do: [\"lib\"]\n  defp elixirc_paths(:dev), do: [\"lib\"]\n  defp elixirc_paths(:test), do: [\"lib\", \"test/support\"]\n\n  defp deps() do\n    [\n      {:rocksdb, \"~> 1.6\", system_env: [{\"ERLANG_ROCKSDB_OPTS\", \"-DWITH_SYSTEM_ROCKSDB=ON -DPORTABLE=1 PORTABLE=1\"}]},\n      {:omg_status, in_umbrella: true},\n      # NOTE: we only need in :dev and :test here, but we need in :prod too in performance\n      #       then there's some unexpected behavior of mix that won't allow to mix these, see\n      #       [here](https://elixirforum.com/t/mix-dependency-is-not-locked-error-when-building-with-edeliver/7069/3)\n      #       OMG-373 (Elixir 1.8) should fix this\n      # TEST ONLY\n      {:briefly, \"~> 0.3.0\", only: [:dev, :test]},\n      {:telemetry, \"~> 0.4.1\"},\n      {:omg_utils, in_umbrella: true}\n    ]\n  end\nend\n"
  },
  {
    "path": "apps/omg_db/test/fixtures.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.DB.Fixtures do\n  @moduledoc \"\"\"\n  Contains fixtures for tests that require db\n  \"\"\"\n  use ExUnitFixtures.FixtureModule\n\n  deffixture db_initialized do\n    db_path = Briefly.create!(directory: true)\n    Application.put_env(:omg_db, :path, db_path, persistent: true)\n\n    :ok = OMG.DB.init(db_path)\n\n    {:ok, started_apps} = Application.ensure_all_started(:omg_db)\n\n    on_exit(fn ->\n      Application.put_env(:omg_db, :path, nil)\n\n      started_apps\n      |> Enum.reverse()\n      |> Enum.map(fn app -> :ok = Application.stop(app) end)\n    end)\n\n    :ok\n  end\nend\n"
  },
  {
    "path": "apps/omg_db/test/omg_db/application_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.DB.ApplicationTest do\n  @moduledoc \"\"\"\n  Only tests if the application can start and stop and the db can init at some location\n  \"\"\"\n  use ExUnitFixtures\n  use ExUnit.Case, async: false\n\n  @moduletag :wrappers\n  @moduletag :common\n\n  @tag fixtures: [:db_initialized]\n  test \"starts and stops app, inits\", %{db_initialized: db_result} do\n    assert :ok = db_result\n  end\nend\n"
  },
  {
    "path": "apps/omg_db/test/omg_db/db_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.DBTest do\n  @moduledoc \"\"\"\n  A smoke test of the LevelDB support. The intention here is to **only** test minimally, that the pipes work.\n\n  For more detailed persistence test look for `...PersistenceTest` tests throughout the apps.\n\n  Note the excluded moduletag, this test requires an explicit `--include wrappers`\n  \"\"\"\n  use ExUnitFixtures\n  use OMG.DB.RocksDBCase, async: false\n\n  alias OMG.DB\n\n  @moduletag :wrappers\n  @moduletag :common\n  @writes 10\n\n  test \"handles object storage\", %{db_dir: dir, db_pid: pid} do\n    :ok =\n      DB.multi_update(\n        [{:put, :block, %{hash: \"xyz\"}}, {:put, :block, %{hash: \"vxyz\"}}, {:put, :block, %{hash: \"wvxyz\"}}],\n        pid\n      )\n\n    assert {:ok, [%{hash: \"wvxyz\"}, %{hash: \"xyz\"}]} == DB.blocks([\"wvxyz\", \"xyz\"], pid)\n\n    :ok = DB.multi_update([{:delete, :block, \"xyz\"}], pid)\n\n    checks = fn pid ->\n      assert {:ok, [%{hash: \"wvxyz\"}, :not_found, %{hash: \"vxyz\"}]} == DB.blocks([\"wvxyz\", \"xyz\", \"vxyz\"], pid)\n    end\n\n    checks.(pid)\n\n    # check actual persistence\n    pid = restart(dir, pid)\n    checks.(pid)\n  end\n\n  test \"handles single value storage\", %{db_dir: dir, db_pid: pid} do\n    :ok = DB.multi_update([{:put, :last_exit_finalizer_eth_height, 12}], pid)\n\n    checks = fn pid ->\n      assert {:ok, 12} == DB.get_single_value(:last_exit_finalizer_eth_height, pid)\n    end\n\n    checks.(pid)\n    # check actual persistence\n    pid = restart(dir, pid)\n    checks.(pid)\n  end\n\n  test \"block hashes return the correct range\", %{db_dir: _dir, db_pid: pid} do\n    :ok =\n      DB.multi_update(\n        [\n          {:put, :block, %{hash: \"xyz\", number: 1}},\n          {:put, :block, %{hash: \"vxyz\", number: 2}},\n          {:put, :block, %{hash: \"wvxyz\", number: 3}}\n        ],\n        pid\n      )\n\n    {:ok, [\"xyz\", \"vxyz\", \"wvxyz\"]} = OMG.DB.block_hashes([1, 2, 3], pid)\n  end\n\n  test \"utxo can be fetched by utxo position\", %{db_pid: pid} do\n    index = 123\n    item = {{index, index, index}, %{test: :crypto.strong_rand_bytes(index)}}\n    db_writes = [{:put, :utxo, item}]\n    :ok = write(db_writes, pid)\n    assert {:ok, ^item} = DB.utxo({index, index, index}, pid)\n  end\n\n  test \"utxo is not found by utxo position\", %{db_pid: pid} do\n    assert :not_found = DB.utxo({1, 0, 0}, pid)\n  end\n\n  test \"if multi reading utxos returns writen results\", %{db_dir: _dir, db_pid: pid} do\n    db_writes = create_write(:utxo, pid)\n    {:ok, utxos} = DB.utxos(pid)\n    [] = utxos -- db_writes\n  end\n\n  test \"if multi reading competitor infos returns writen results\", %{db_dir: _dir, db_pid: pid} do\n    db_writes = create_write(:competitor_info, pid)\n    {:ok, competitors_info} = DB.competitors_info(pid)\n    [] = competitors_info -- db_writes\n  end\n\n  defp create_write(:utxo = type, pid) do\n    db_writes =\n      Enum.map(1..@writes, fn index ->\n        {:put, type, {{index, index, index}, %{test: :crypto.strong_rand_bytes(index)}}}\n      end)\n\n    :ok = write(db_writes, pid)\n    get_raw_values(db_writes)\n  end\n\n  defp create_write(:competitor_info = type, pid) do\n    db_writes = Enum.map(1..@writes, fn index -> {:put, type, {:crypto.strong_rand_bytes(index), index}} end)\n\n    :ok = write(db_writes, pid)\n    get_raw_values(db_writes)\n  end\n\n  defp write(db_writes, pid), do: OMG.DB.multi_update(db_writes, pid)\n  defp get_raw_values(db_writes), do: Enum.map(db_writes, &elem(&1, 2))\n\n  defp restart(dir, pid) do\n    :ok = GenServer.stop(pid)\n    name = :\"TestDB_#{make_ref() |> inspect()}\"\n    {:ok, pid} = start_supervised(OMG.DB.child_spec(db_path: dir, name: name), restart: :temporary)\n    pid\n  end\nend\n"
  },
  {
    "path": "apps/omg_db/test/omg_db/models/payment_exit_info_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.DB.PaymentExitInfoTest do\n  @moduledoc \"\"\"\n  A smoke test of the RocksDB implementation for PaymentExitInfo.\n\n  Note the excluded moduletag, this test requires an explicit `--include wrappers`\n  \"\"\"\n  use ExUnitFixtures\n  use OMG.DB.RocksDBCase, async: false\n\n  alias OMG.DB.Models.PaymentExitInfo\n\n  @moduletag :wrappers\n  @moduletag :common\n  @writes 10\n\n  describe \"exit_info\" do\n    test \"should return single exit info when given the utxo position\", %{db_dir: _dir, db_pid: pid} do\n      {utxo_pos, _} = db_write = :exit_info |> create_write(pid) |> Enum.at(0)\n\n      {:ok, result} = PaymentExitInfo.exit_info(utxo_pos, pid)\n\n      assert result == db_write\n    end\n  end\n\n  describe \"exit_infos\" do\n    test \"should return empty list if given empty list of positions\", %{db_dir: _dir, db_pid: pid} do\n      _db_writes = create_write(:exit_info, pid)\n\n      {:ok, exits} = PaymentExitInfo.exit_infos([], pid)\n\n      assert exits == []\n    end\n\n    test \"should return all exit infos with the given utxo positions\", %{db_dir: _dir, db_pid: pid} do\n      test_range = 0..Integer.floor_div(@writes, 2)\n\n      db_writes = create_write(:exit_info, pid)\n      sliced_db_writes = Enum.slice(db_writes, test_range)\n\n      utxo_pos_list = Enum.map(sliced_db_writes, fn {utxo_pos, _} = _write -> utxo_pos end)\n\n      {:ok, exits} = PaymentExitInfo.exit_infos(utxo_pos_list, pid)\n\n      assert exits == sliced_db_writes\n    end\n  end\n\n  describe \"all_exit_infos\" do\n    test \"should return all exit infos\", %{db_dir: _dir, db_pid: pid} do\n      db_writes = create_write(:exit_info, pid)\n      {:ok, exits} = PaymentExitInfo.all_exit_infos(pid)\n      assert exits == db_writes\n    end\n  end\n\n  describe \"all_in_flight_exits_infos\" do\n    test \"should return all in-flight exits info\", %{db_dir: _dir, db_pid: pid} do\n      db_writes = create_write(:in_flight_exit_info, pid)\n      {:ok, in_flight_exits_infos} = PaymentExitInfo.all_in_flight_exits_infos(pid)\n      assert in_flight_exits_infos == db_writes\n    end\n  end\n\n  defp create_write(:exit_info = type, pid) do\n    db_writes =\n      Enum.map(1..@writes, fn index -> {:put, type, {{index, index, index}, :crypto.strong_rand_bytes(index)}} end)\n\n    :ok = write(db_writes, pid)\n    get_raw_values(db_writes)\n  end\n\n  defp create_write(:in_flight_exit_info = type, pid) do\n    db_writes = Enum.map(1..@writes, fn index -> {:put, type, {:crypto.strong_rand_bytes(index), index}} end)\n\n    :ok = write(db_writes, pid)\n    get_raw_values(db_writes)\n  end\n\n  defp write(db_writes, pid), do: OMG.DB.multi_update(db_writes, pid)\n  defp get_raw_values(db_writes), do: Enum.map(db_writes, &elem(&1, 2))\nend\n"
  },
  {
    "path": "apps/omg_db/test/omg_db/release_tasks/init_key_value_db_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.DB.ReleaseTasks.InitKeyValueDBTest do\n  use ExUnit.Case, async: false\n\n  alias OMG.DB.ReleaseTasks.InitKeyValueDB\n  alias OMG.DB.ReleaseTasks.SetKeyValueDB\n\n  @apps [:logger, :crypto, :ssl]\n\n  setup_all do\n    _ = Enum.each(@apps, &Application.ensure_all_started/1)\n\n    on_exit(fn ->\n      @apps |> Enum.reverse() |> Enum.each(&Application.stop/1)\n    end)\n\n    :ok\n  end\n\n  test \"init works and DB starts\" do\n    {:ok, dir} = Briefly.create(directory: true)\n    :ok = System.put_env(\"DB_PATH\", dir)\n\n    _ = SetKeyValueDB.load([], release: :child_chain)\n\n    :ok = InitKeyValueDB.run()\n\n    started_apps = Enum.map(Application.started_applications(), fn {app, _, _} -> app end)\n    [true, true, true] = Enum.map(@apps, fn app -> not Enum.member?(started_apps, app) end)\n    {:ok, _} = Application.ensure_all_started(:omg_db)\n    :ok = Application.stop(:omg_db)\n    :ok = System.delete_env(\"DB_PATH\")\n    _ = File.rm_rf!(dir)\n  end\n\n  test \"can't init non empty dir\" do\n    {:ok, dir} = Briefly.create(directory: true)\n    :ok = System.put_env(\"DB_PATH\", dir)\n\n    _ = SetKeyValueDB.load([], release: :watcher)\n    :ok = InitKeyValueDB.run()\n\n    {:error, _} = InitKeyValueDB.run()\n    :ok = System.delete_env(\"DB_PATH\")\n    _ = File.rm_rf!(dir)\n  end\n\n  test \"if init isn't called, DB doesn't start\" do\n    _ = Application.stop(:omg_db)\n    {:ok, dir} = Briefly.create(directory: true)\n    :ok = System.put_env(\"DB_PATH\", dir)\n\n    _ = SetKeyValueDB.load([], release: :child_chain)\n\n    try do\n      {:ok, _} = Application.ensure_all_started(:omg_db)\n    catch\n      _,\n      {:badmatch,\n       {:error,\n        {:omg_db,\n         {{:shutdown, {:failed_to_start_child, _, {:bad_return_value, {:error, {:db_open, _}}}}},\n          {OMG.DB.Application, :start, [:normal, []]}}}}} ->\n        :ok\n    end\n\n    :ok = System.delete_env(\"DB_PATH\")\n  end\nend\n"
  },
  {
    "path": "apps/omg_db/test/omg_db/release_tasks/init_keys_with_values_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.DB.ReleaseTasks.InitKeysWithValuesTest do\n  use ExUnit.Case, async: false\n  alias OMG.DB.ReleaseTasks.InitKeysWithValues\n  alias OMG.DB.RocksDB\n  alias OMG.DB.RocksDB.Server\n\n  setup do\n    {:ok, dir} = Briefly.create(directory: true)\n    :ok = Server.init_storage(dir)\n    :ok = Application.put_env(:omg_db, :path, dir, persistent: true)\n    {:ok, started_apps} = Application.ensure_all_started(:omg_db)\n\n    on_exit(fn ->\n      :ok = Application.put_env(:omg_db, :path, nil)\n      Enum.map(started_apps, fn app -> _ = Application.stop(app) end)\n    end)\n\n    {:ok, %{}}\n  end\n\n  test \":last_ife_exit_deleted_eth_height is set if it wasn't set previously\" do\n    :ok = RocksDB.multi_update([{:delete, :last_ife_exit_deleted_eth_height, 0}])\n\n    assert InitKeysWithValues.run() == :ok\n    assert RocksDB.get_single_value(:last_ife_exit_deleted_eth_height) == {:ok, 0}\n  end\n\n  test \"value under :last_ife_exit_deleted_eth_height is not changed if it was already set\" do\n    initial_value = 5\n    :ok = RocksDB.multi_update([{:put, :last_ife_exit_deleted_eth_height, initial_value}])\n\n    assert InitKeysWithValues.run() == :ok\n    assert RocksDB.get_single_value(:last_ife_exit_deleted_eth_height) == {:ok, initial_value}\n  end\n\n  test \"does not fail when omg db is not started\" do\n    :ok = Application.stop(:omg_db)\n    :ok = Application.put_env(:omg_db, :path, nil)\n\n    assert InitKeysWithValues.run() == :ok\n  end\nend\n"
  },
  {
    "path": "apps/omg_db/test/omg_db/release_tasks/set_key_value_db_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.DB.ReleaseTasks.SetKeyValueDBTest do\n  use ExUnit.Case, async: true\n  import ExUnit.CaptureLog, only: [capture_log: 1]\n  alias OMG.DB.ReleaseTasks.SetKeyValueDB\n\n  @app :omg_db\n\n  setup do\n    _ = Application.ensure_all_started(:logger)\n\n    on_exit(fn ->\n      :ok = System.delete_env(\"DB_PATH\")\n    end)\n\n    :ok\n  end\n\n  test \"if environment variables get applied in the configuration\" do\n    test_path = \"/tmp/YOLO/\"\n    release = :watcher_info\n    :ok = System.put_env(\"DB_PATH\", test_path)\n\n    capture_log(fn ->\n      config = SetKeyValueDB.load([], release: release)\n      path = config |> Keyword.fetch!(@app) |> Keyword.fetch!(:path)\n      assert path == test_path <> \"#{release}\"\n    end)\n  end\n\n  test \"if default configuration is used when there's no environment variables\" do\n    :ok = System.delete_env(\"DB_PATH\")\n\n    capture_log(fn ->\n      config = SetKeyValueDB.load([], release: :watcher_info)\n      path = config |> Keyword.fetch!(@app) |> Keyword.fetch!(:path)\n\n      assert path == Path.join([System.get_env(\"HOME\"), \".omg/data\"]) <> \"/watcher_info\"\n    end)\n  end\nend\n"
  },
  {
    "path": "apps/omg_db/test/omg_db/rocks_db_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.RocksDBTest do\n  @moduledoc \"\"\"\n  A smoke test of the RocksDB support. The intention here is to **only** test minimally, that the pipes work.\n\n  For more detailed persistence test look for `...PersistenceTest` tests throughout the apps.\n\n  Note the excluded moduletag, this test requires an explicit `--include wrappers`\n  \"\"\"\n  use ExUnitFixtures\n  use OMG.DB.RocksDBCase, async: false\n\n  alias OMG.DB\n\n  @moduletag :wrappers\n  @moduletag :common\n  @writes 10\n\n  test \"rocks db handles object storage\", %{db_dir: dir, db_pid: pid} do\n    :ok =\n      DB.multi_update(\n        [{:put, :block, %{hash: \"xyz\"}}, {:put, :block, %{hash: \"vxyz\"}}, {:put, :block, %{hash: \"wvxyz\"}}],\n        pid\n      )\n\n    assert {:ok, [%{hash: \"wvxyz\"}, %{hash: \"xyz\"}]} == DB.blocks([\"wvxyz\", \"xyz\"], pid)\n\n    :ok = DB.multi_update([{:delete, :block, \"xyz\"}], pid)\n\n    checks = fn pid ->\n      assert {:ok, [%{hash: \"wvxyz\"}, :not_found, %{hash: \"vxyz\"}]} == DB.blocks([\"wvxyz\", \"xyz\", \"vxyz\"], pid)\n    end\n\n    checks.(pid)\n\n    # check actual persistence\n    pid = restart(dir, pid)\n    checks.(pid)\n  end\n\n  test \"rocks db handles single value storage\", %{db_dir: dir, db_pid: pid} do\n    :ok = DB.multi_update([{:put, :last_exit_finalizer_eth_height, 12}], pid)\n\n    checks = fn pid ->\n      assert {:ok, 12} == DB.get_single_value(:last_exit_finalizer_eth_height, pid)\n    end\n\n    checks.(pid)\n    # check actual persistence\n    pid = restart(dir, pid)\n    checks.(pid)\n  end\n\n  test \"block hashes return the correct range\", %{db_dir: _dir, db_pid: pid} do\n    :ok =\n      DB.multi_update(\n        [\n          {:put, :block, %{hash: \"xyz\", number: 1}},\n          {:put, :block, %{hash: \"vxyz\", number: 2}},\n          {:put, :block, %{hash: \"wvxyz\", number: 3}}\n        ],\n        pid\n      )\n\n    {:ok, [\"xyz\", \"vxyz\", \"wvxyz\"]} = OMG.DB.block_hashes([1, 2, 3], pid)\n  end\n\n  describe \"batch_get\" do\n    test \"can get single data with the type and single specific key\", %{db_dir: _dir, db_pid: pid} do\n      type = :exit_info\n      specific_key = {1, 1, 1}\n      data = {specific_key, :crypto.strong_rand_bytes(123)}\n      :ok = DB.multi_update([{:put, type, data}], pid)\n\n      assert {:ok, [data]} == DB.batch_get(type, [specific_key], server: pid)\n    end\n\n    test \"can get multiple data with the type and multiple specific keys\", %{db_dir: _dir, db_pid: pid} do\n      type = :exit_info\n      specific_keys = [{1, 1, 1}, {2, 2, 2}]\n      data_list = Enum.map(specific_keys, fn key -> {key, :crypto.strong_rand_bytes(123)} end)\n\n      :ok =\n        data_list\n        |> Enum.map(fn data -> {:put, type, data} end)\n        |> DB.multi_update(pid)\n\n      assert {:ok, data_list} == DB.batch_get(type, specific_keys, server: pid)\n    end\n  end\n\n  test \"it can get all data with the type\", %{db_dir: _dir, db_pid: pid} do\n    db_writes = create_write(:utxo, pid)\n\n    assert {:ok, db_writes} == DB.get_all_by_type(:utxo, server: pid)\n  end\n\n  test \"if multi reading utxos returns writen results\", %{db_dir: _dir, db_pid: pid} do\n    db_writes = create_write(:utxo, pid)\n    {:ok, utxos} = DB.utxos(pid)\n    [] = utxos -- db_writes\n  end\n\n  test \"if multi reading competitor infos returns writen results\", %{db_dir: _dir, db_pid: pid} do\n    db_writes = create_write(:competitor_info, pid)\n    {:ok, competitors_info} = DB.competitors_info(pid)\n    [] = competitors_info -- db_writes\n  end\n\n  defp create_write(:utxo = type, pid) do\n    db_writes =\n      Enum.map(1..@writes, fn index ->\n        {:put, type, {{index, index, index}, %{test: :crypto.strong_rand_bytes(index)}}}\n      end)\n\n    :ok = write(db_writes, pid)\n    get_raw_values(db_writes)\n  end\n\n  defp create_write(:competitor_info = type, pid) do\n    db_writes = Enum.map(1..@writes, fn index -> {:put, type, {:crypto.strong_rand_bytes(index), index}} end)\n\n    :ok = write(db_writes, pid)\n    get_raw_values(db_writes)\n  end\n\n  defp write(db_writes, pid), do: OMG.DB.multi_update(db_writes, pid)\n  defp get_raw_values(db_writes), do: Enum.map(db_writes, &elem(&1, 2))\n\n  defp restart(dir, pid) do\n    :ok = GenServer.stop(pid)\n    name = :\"TestDB_#{make_ref() |> inspect()}\"\n    {:ok, pid} = start_supervised(OMG.DB.child_spec(db_path: dir, name: name), restart: :temporary)\n    pid\n  end\nend\n"
  },
  {
    "path": "apps/omg_db/test/support/rocks_db_case.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.DB.RocksDBCase do\n  @moduledoc \"\"\"\n  Defines the useful common setup for all `...PersistenceTests`:\n   - creates temp dir with `briefly`\n   - initializes the low-level LevelDB storage and starts the test DB server\n  \"\"\"\n\n  use ExUnit.CaseTemplate\n  alias OMG.DB.RocksDB.Server\n\n  setup %{test: test_name} do\n    {:ok, dir} = Briefly.create(directory: true)\n    :ok = Server.init_storage(dir)\n    name = :\"TestDB_#{test_name}\"\n    {:ok, pid} = start_supervised(OMG.DB.child_spec(db_path: dir, name: name), restart: :temporary)\n    {:ok, %{db_dir: dir, db_pid: pid, db_pid_name: name}}\n  end\nend\n"
  },
  {
    "path": "apps/omg_db/test/test_helper.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nExUnit.configure(exclude: [integration: true, property: true, wrappers: true])\nExUnitFixtures.start()\nExUnitFixtures.load_fixture_files()\nExUnit.start()\n\n{:ok, _} = Application.ensure_all_started(:briefly)\n"
  },
  {
    "path": "apps/omg_eth/lib/eth.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Eth do\n  @moduledoc \"\"\"\n  Library for common code of the adapter/port to contracts deployed on Ethereum.\n\n  NOTE: The library code is not intended to be used outside of `OMG.Eth`: use `OMG.Eth.RootChain` and `OMG.Eth.Token` as main\n  entrypoints to the contract-interaction functionality.\n\n  NOTE: This wrapper is intended to be as thin as possible, only offering a consistent API to the Ethereum JSONRPC client and contracts.\n\n  Handles other non-contract queries to the Ethereum client.\n\n  Notes on encoding: All APIs of `OMG.Eth` and the submodules with contract APIs always use raw, decoded binaries\n  for binaries - never use hex encoded binaries. Such binaries may be passed as is onto `ABI` related functions,\n  however they must be encoded/decoded when entering/leaving the `Ethereumex` realm\n  \"\"\"\n\n  alias OMG.Eth.Configuration\n  alias OMG.Eth.RootChain.SubmitBlock\n\n  require Logger\n  import OMG.Eth.Encoding, only: [from_hex: 1, to_hex: 1, int_from_hex: 1]\n\n  @type address :: <<_::160>>\n  @type hash :: <<_::256>>\n  @type send_transaction_opts() :: [send_transaction_option()]\n  @type send_transaction_option() :: {:passphrase, binary()}\n\n  def get_block_timestamp_by_number(height) do\n    case Ethereumex.HttpClient.eth_get_block_by_number(to_hex(height), false) do\n      {:ok, %{\"timestamp\" => timestamp_hex}} ->\n        {:ok, int_from_hex(timestamp_hex)}\n\n      other ->\n        other\n    end\n  end\n\n  @spec submit_block(binary(), pos_integer(), pos_integer()) ::\n          {:error, binary() | atom() | map()} | {:ok, <<_::256>>}\n  def submit_block(hash, nonce, gas_price) do\n    contract = from_hex(Configuration.contracts().plasma_framework)\n    from = from_hex(Configuration.authority_address())\n    backend = Configuration.eth_node()\n    SubmitBlock.submit(backend, hash, nonce, gas_price, from, contract)\n  end\nend\n"
  },
  {
    "path": "apps/omg_eth/lib/omg_eth/application.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Eth.Application do\n  @moduledoc false\n\n  alias OMG.DB\n  alias OMG.Eth.Configuration\n  alias OMG.Eth.Metric.Ethereumex\n\n  use Application\n  require Logger\n\n  def start(_type, _args) do\n    _ =\n      Logger.info(\n        \"Started #{inspect(__MODULE__)}, config used: contracts #{inspect(Configuration.contracts())} txhash_contract #{\n          inspect(Configuration.txhash_contract())\n        } authority_address #{inspect(Configuration.authority_address())}\"\n      )\n\n    valid_contracts()\n    OMG.Eth.Supervisor.start_link()\n  end\n\n  def start_phase(:attach_telemetry, :normal, _phase_args) do\n    handler = [\n      \"measure-ethereumex-rpc\",\n      Ethereumex.supported_events(),\n      &Ethereumex.handle_event/4,\n      nil\n    ]\n\n    case apply(:telemetry, :attach, handler) do\n      :ok -> :ok\n      {:error, :already_exists} -> :ok\n    end\n  end\n\n  defp valid_contracts() do\n    contracts_hash =\n      Configuration.contracts()\n      |> Map.put(:txhash_contract, Configuration.txhash_contract())\n      # authority_addr to keep backwards compatibility\n      |> Map.put(:authority_addr, Configuration.authority_address())\n      |> :erlang.phash2()\n\n    case DB.get_single_value(:omg_eth_contracts) do\n      result when result == :not_found or result == {:ok, 0} ->\n        multi_update = [{:put, :omg_eth_contracts, contracts_hash}]\n        :ok == DB.multi_update(multi_update)\n\n      {:ok, ^contracts_hash} ->\n        true\n\n      _ ->\n        _ = Logger.error(\"Contract addresses have changed since last boot!\")\n        exit(:contracts_missmatch)\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_eth/lib/omg_eth/blockchain/bit_helper.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Eth.Blockchain.BitHelper do\n  @moduledoc \"\"\"\n  Helpers for common operations on the blockchain.\n  Extracted from: https://github.com/exthereum/blockchain\n  \"\"\"\n\n  use Bitwise\n\n  alias ExPlasma.Crypto\n  @type keccak_hash :: <<_::256>>\n\n  @doc \"\"\"\n  Returns the keccak sha256 of a given input.\n\n  ## Examples\n\n      iex> OMG.Eth.Blockchain.BitHelper.kec(\"hello world\")\n      <<71, 23, 50, 133, 168, 215, 52, 30, 94, 151, 47, 198, 119, 40, 99,\n             132, 248, 2, 248, 239, 66, 165, 236, 95, 3, 187, 250, 37, 76, 176,\n             31, 173>>\n\n      iex> OMG.Eth.Blockchain.BitHelper.kec(<<0x01, 0x02, 0x03>>)\n      <<241, 136, 94, 218, 84, 183, 160, 83, 49, 140, 212, 30, 32, 147, 34,\n             13, 171, 21, 214, 83, 129, 177, 21, 122, 54, 51, 168, 59, 253, 92,\n             146, 57>>\n  \"\"\"\n  @spec kec(binary()) :: keccak_hash\n  def kec(data) do\n    Crypto.keccak_hash(data)\n  end\n\n  @doc \"\"\"\n  Similar to `:binary.encode_unsigned/1`, except we encode `0` as\n  `<<>>`, the empty string. This is because the specification says that\n  we cannot have any leading zeros, and so having <<0>> by itself is\n  leading with a zero and prohibited.\n\n  ## Examples\n\n      iex> OMG.Eth.Blockchain.BitHelper.encode_unsigned(0)\n      <<>>\n\n      iex> OMG.Eth.Blockchain.BitHelper.encode_unsigned(5)\n      <<5>>\n\n      iex> OMG.Eth.Blockchain.BitHelper.encode_unsigned(5_000_000)\n      <<76, 75, 64>>\n  \"\"\"\n  @spec encode_unsigned(non_neg_integer()) :: binary()\n  def encode_unsigned(0), do: <<>>\n  def encode_unsigned(n), do: :binary.encode_unsigned(n)\nend\n"
  },
  {
    "path": "apps/omg_eth/lib/omg_eth/blockchain/private_key.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Eth.Blockchain.PrivateKey do\n  @moduledoc \"\"\"\n  Extracts private key from environment\n  \"\"\"\n  require Integer\n\n  def get() do\n    private_key = System.get_env(\"PRIVATE_KEY\")\n    maybe_hex(private_key)\n  end\n\n  @spec maybe_hex(String.t() | nil) :: binary() | nil\n  defp maybe_hex(hex_data, type \\\\ :raw)\n  defp maybe_hex(nil, _), do: nil\n  defp maybe_hex(hex_data, :raw), do: load_raw_hex(hex_data)\n\n  @spec load_raw_hex(String.t()) :: binary()\n  defp load_raw_hex(\"0x\" <> hex_data), do: load_raw_hex(hex_data)\n\n  defp load_raw_hex(hex_data) when Integer.is_odd(byte_size(hex_data)),\n    do: load_raw_hex(\"0\" <> hex_data)\n\n  defp load_raw_hex(hex_data) do\n    Base.decode16!(hex_data, case: :mixed)\n  end\nend\n"
  },
  {
    "path": "apps/omg_eth/lib/omg_eth/blockchain/transaction/hash.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Eth.Blockchain.Transaction.Hash do\n  @moduledoc \"\"\"\n  Defines helper functions for signing and getting the signature\n  of a transaction, as defined in Appendix F of the Yellow Paper.\n\n  For any of the following functions, if chain_id is specified,\n  it's assumed that we're post-fork and we should follow the\n  specification EIP-155 from:\n\n  https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md\n  Extracted from: https://github.com/exthereum/blockchain\n  \"\"\"\n\n  alias OMG.Eth.Blockchain.BitHelper\n  alias OMG.Eth.Blockchain.Transaction\n\n  @base_recovery_id 27\n  @base_recovery_id_eip_155 35\n  @type private_key :: <<_::256>>\n  @type hash_v :: integer()\n  @type hash_r :: integer()\n  @type hash_s :: integer()\n  @doc \"\"\"\n  Returns a hash of a given transaction according to the\n  formula defined in Eq.(214) and Eq.(215) of the Yellow Paper.\n\n  Note: As per EIP-155 (https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md),\n        we will append the chain-id and nil elements to the serialized transaction.\n\n  ## Examples\n\n      iex> OMG.Eth.Blockchain.Transaction.Hash.transaction_hash(%OMG.Eth.Blockchain.Transaction{nonce: 5, gas_price: 6, gas_limit: 7, to: <<>>, value: 5, init: <<1>>})\n      <<127, 113, 209, 76, 19, 196, 2, 206, 19, 198, 240, 99, 184, 62, 8, 95, 9, 122, 135, 142, 51, 22, 61, 97, 70, 206, 206, 39, 121, 54, 83, 27>>\n\n      iex> OMG.Eth.Blockchain.Transaction.Hash.transaction_hash(%OMG.Eth.Blockchain.Transaction{nonce: 5, gas_price: 6, gas_limit: 7, to: <<1>>, value: 5, data: <<1>>})\n      <<225, 195, 128, 181, 3, 211, 32, 231, 34, 10, 166, 198, 153, 71, 210, 118, 51, 117, 22, 242, 87, 212, 229, 37, 71, 226, 150, 160, 50, 203, 127, 180>>\n\n      iex> OMG.Eth.Blockchain.Transaction.Hash.transaction_hash(%OMG.Eth.Blockchain.Transaction{nonce: 5, gas_price: 6, gas_limit: 7, to: <<1>>, value: 5, data: <<1>>}, 1)\n      <<132, 79, 28, 4, 212, 58, 235, 38, 66, 211, 167, 102, 36, 58, 229, 88, 238, 251, 153, 23, 121, 163, 212, 64, 83, 111, 200, 206, 54, 43, 112, 53>>\n  \"\"\"\n\n  @spec transaction_hash(Transaction.t(), integer() | nil) :: BitHelper.keccak_hash()\n  def transaction_hash(trx, chain_id \\\\ nil) do\n    Transaction.serialize(trx, false)\n    # See EIP-155\n    |> Kernel.++(if chain_id, do: [:binary.encode_unsigned(chain_id), <<>>, <<>>], else: [])\n    |> ExRLP.encode()\n    |> BitHelper.kec()\n  end\n\n  @doc \"\"\"\n  Returns a ECDSA signature (v,r,s) for a given hashed value.\n\n  This implementes Eq.(207) of the Yellow Paper.\n\n  ## Examples\n\n    iex> OMG.Eth.Blockchain.Transaction.Hash.sign_hash(<<2::256>>, <<1::256>>)\n    {28,\n     38938543279057362855969661240129897219713373336787331739561340553100525404231,\n     23772455091703794797226342343520955590158385983376086035257995824653222457926}\n\n    iex> OMG.Eth.Blockchain.Transaction.Hash.sign_hash(<<5::256>>, <<1::256>>)\n    {27,\n     74927840775756275467012999236208995857356645681540064312847180029125478834483,\n     56037731387691402801139111075060162264934372456622294904359821823785637523849}\n\n    iex> data = Base.decode16!(\"ec098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080018080\", case: :lower)\n    iex> hash = OMG.Eth.Blockchain.BitHelper.kec(data)\n    iex> private_key = Base.decode16!(\"4646464646464646464646464646464646464646464646464646464646464646\", case: :lower)\n    iex> OMG.Eth.Blockchain.Transaction.Hash.sign_hash(hash, private_key, 1)\n    { 37, 18515461264373351373200002665853028612451056578545711640558177340181847433846, 46948507304638947509940763649030358759909902576025900602547168820602576006531 }\n  \"\"\"\n  @spec sign_hash(BitHelper.keccak_hash(), private_key, integer() | nil) ::\n          {hash_v, hash_r, hash_s}\n  def sign_hash(hash, private_key, chain_id \\\\ nil) do\n    {:ok, {<<r::size(256), s::size(256)>>, recovery_id}} = ExSecp256k1.sign_compact(hash, private_key)\n\n    # Fork Ψ EIP-155\n    recovery_id =\n      if chain_id do\n        chain_id * 2 + @base_recovery_id_eip_155 + recovery_id\n      else\n        @base_recovery_id + recovery_id\n      end\n\n    {recovery_id, r, s}\n  end\nend\n"
  },
  {
    "path": "apps/omg_eth/lib/omg_eth/blockchain/transaction/signature.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Eth.Blockchain.Transaction.Signature do\n  @moduledoc \"\"\"\n  Defines helper functions for signing and getting the signature\n  of a transaction, as defined in Appendix F of the Yellow Paper.\n\n  For any of the following functions, if chain_id is specified,\n  it's assumed that we're post-fork and we should follow the\n  specification EIP-155 from:\n\n  https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md\n  Extracted from: https://github.com/exthereum/blockchain\n  \"\"\"\n\n  require Integer\n\n  alias OMG.Eth.Blockchain.Transaction\n  alias OMG.Eth.Blockchain.Transaction.Hash\n\n  @type private_key :: <<_::256>>\n\n  @doc \"\"\"\n  Takes a given transaction and returns a version signed\n  with the given private key. This is defined in Eq.(216) and\n  Eq.(217) of the Yellow Paper.\n\n  ## Examples\n\n      iex> OMG.Eth.Blockchain.Transaction.Signature.sign_transaction(%OMG.Eth.Blockchain.Transaction{nonce: 5, gas_price: 6, gas_limit: 7, to: <<>>, value: 5, init: <<1>>}, <<1::256>>)\n      %OMG.Eth.Blockchain.Transaction{data: <<>>, gas_limit: 7, gas_price: 6, init: <<1>>, nonce: 5, r: 97037709922803580267279977200525583527127616719646548867384185721164615918250, s: 31446571475787755537574189222065166628755695553801403547291726929250860527755, to: \"\", v: 27, value: 5}\n\n      iex> OMG.Eth.Blockchain.Transaction.Signature.sign_transaction(%OMG.Eth.Blockchain.Transaction{nonce: 5, gas_price: 6, gas_limit: 7, to: <<>>, value: 5, init: <<1>>}, <<1::256>>, 1)\n      %OMG.Eth.Blockchain.Transaction{data: <<>>, gas_limit: 7, gas_price: 6, init: <<1>>, nonce: 5, r: 25739987953128435966549144317523422635562973654702886626580606913510283002553, s: 41423569377768420285000144846773344478964141018753766296386430811329935846420, to: \"\", v: 38, value: 5}\n  \"\"\"\n  @spec sign_transaction(Transaction.t(), private_key, integer() | nil) :: Transaction.t()\n  def sign_transaction(trx, private_key, chain_id \\\\ nil) do\n    {v, r, s} =\n      trx\n      |> Hash.transaction_hash(chain_id)\n      |> Hash.sign_hash(private_key, chain_id)\n\n    %{trx | v: v, r: r, s: s}\n  end\nend\n"
  },
  {
    "path": "apps/omg_eth/lib/omg_eth/blockchain/transaction.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Eth.Blockchain.Transaction do\n  alias OMG.Eth.Blockchain.BitHelper\n\n  @moduledoc \"\"\"\n  This module encodes the transaction object, defined in Section 4.3\n  of the Yellow Paper (http://gavwood.com/Paper.pdf). We are focused\n  on implementing 𝛶, as defined in Eq.(1).\n  Extracted from: https://github.com/exthereum/blockchain\n  \"\"\"\n  defstruct nonce: 0,\n\n            # Tn\n            # Tp\n            gas_price: 0,\n            # Tg\n            gas_limit: 0,\n            # Tt\n            to: <<>>,\n            # Tv\n            value: 0,\n            # Tw\n            v: nil,\n            # Tr\n            r: nil,\n            # Ts\n            s: nil,\n            # Ti\n            init: <<>>,\n            # Td\n            data: <<>>\n\n  @type t :: %__MODULE__{\n          nonce: integer(),\n          gas_price: integer(),\n          gas_limit: integer(),\n          to: <<_::160>> | <<_::0>>,\n          value: integer(),\n          v: integer(),\n          r: integer(),\n          s: integer(),\n          init: binary(),\n          data: binary()\n        }\n\n  @doc \"\"\"\n  Encodes a transaction such that it can be RLP-encoded.\n  This is defined at L_T Eq.(14) in the Yellow Paper.\n\n  ## Examples\n\n      iex> OMG.Eth.Blockchain.Transaction.serialize(%OMG.Eth.Blockchain.Transaction{nonce: 5, gas_price: 6, gas_limit: 7, to: <<1::160>>, value: 8, v: 27, r: 9, s: 10, data: \"hi\"})\n      [<<5>>, <<6>>, <<7>>, <<1::160>>, <<8>>, \"hi\", <<27>>, <<9>>, <<10>>]\n\n      iex> OMG.Eth.Blockchain.Transaction.serialize(%OMG.Eth.Blockchain.Transaction{nonce: 5, gas_price: 6, gas_limit: 7, to: <<>>, value: 8, v: 27, r: 9, s: 10, init: <<1, 2, 3>>})\n      [<<5>>, <<6>>, <<7>>, <<>>, <<8>>, <<1, 2, 3>>, <<27>>, <<9>>, <<10>>]\n\n      iex> OMG.Eth.Blockchain.Transaction.serialize(%OMG.Eth.Blockchain.Transaction{nonce: 5, gas_price: 6, gas_limit: 7, to: <<>>, value: 8, v: 27, r: 9, s: 10, init: <<1, 2, 3>>}, false)\n      [<<5>>, <<6>>, <<7>>, <<>>, <<8>>, <<1, 2, 3>>]\n\n      iex> OMG.Eth.Blockchain.Transaction.serialize(%OMG.Eth.Blockchain.Transaction{ data: \"\", gas_limit: 21000, gas_price: 20000000000, init: \"\", nonce: 9, r: 0, s: 0, to: \"55555555555555555555\", v: 1, value: 1000000000000000000 })\n      [\"\\t\", <<4, 168, 23, 200, 0>>, \"R\\b\", \"55555555555555555555\", <<13, 224, 182, 179, 167, 100, 0, 0>>, \"\", <<1>>, \"\", \"\"]\n  \"\"\"\n  @spec serialize(t) :: ExRLP.t()\n  def serialize(trx, include_vrs \\\\ true) do\n    base = [\n      BitHelper.encode_unsigned(trx.nonce),\n      BitHelper.encode_unsigned(trx.gas_price),\n      BitHelper.encode_unsigned(trx.gas_limit),\n      trx.to,\n      BitHelper.encode_unsigned(trx.value),\n      if(trx.to == <<>>, do: trx.init, else: trx.data)\n    ]\n\n    if include_vrs do\n      base ++\n        [\n          BitHelper.encode_unsigned(trx.v),\n          BitHelper.encode_unsigned(trx.r),\n          BitHelper.encode_unsigned(trx.s)\n        ]\n    else\n      base\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_eth/lib/omg_eth/client.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\ndefmodule OMG.Eth.Client do\n  @moduledoc \"\"\"\n    Interface to Ethereum Client (not plasma contracts)\n  \"\"\"\n  alias OMG.Eth.Encoding\n\n  @spec get_ethereum_height() :: {:ok, non_neg_integer()} | Ethereumex.Client.Behaviour.error()\n  @spec get_ethereum_height(module()) :: {:ok, non_neg_integer()} | Ethereumex.Client.Behaviour.error()\n  def get_ethereum_height(client \\\\ Ethereumex.HttpClient) do\n    case client.eth_block_number() do\n      {:ok, height_hex} ->\n        {:ok, Encoding.int_from_hex(height_hex)}\n\n      other ->\n        other\n    end\n  end\n\n  @spec node_ready() :: :ok | {:error, :geth_still_syncing}\n  @spec node_ready(module()) :: :ok | {:error, :geth_still_syncing}\n  def node_ready(client \\\\ Ethereumex.HttpClient) do\n    case client.eth_syncing() do\n      {:ok, false} ->\n        :ok\n\n      {:ok, _} ->\n        {:error, :geth_still_syncing}\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_eth/lib/omg_eth/configuration.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Eth.Configuration do\n  @moduledoc \"\"\"\n  Provides access to applications configuration\n  \"\"\"\n\n  @app :omg_eth\n  def contract_semver() do\n    Application.get_env(@app, :contract_semver)\n  end\n\n  def network() do\n    Application.get_env(@app, :network)\n  end\n\n  @spec min_exit_period_seconds() :: no_return | pos_integer()\n  def min_exit_period_seconds() do\n    Application.fetch_env!(@app, :min_exit_period_seconds)\n  end\n\n  @spec ethereum_block_time_seconds() :: no_return | pos_integer()\n  def ethereum_block_time_seconds() do\n    Application.fetch_env!(@app, :ethereum_block_time_seconds)\n  end\n\n  @spec contracts() :: no_return | map()\n  def contracts() do\n    Application.fetch_env!(@app, :contract_addr)\n  end\n\n  @spec txhash_contract() :: no_return | binary()\n  def txhash_contract() do\n    Application.fetch_env!(@app, :txhash_contract)\n  end\n\n  @spec authority_address() :: no_return | binary()\n  def authority_address() do\n    Application.fetch_env!(@app, :authority_address)\n  end\n\n  @spec environment() :: :test | nil\n  def environment() do\n    Application.get_env(@app, :environment)\n  end\n\n  @spec child_block_interval() :: pos_integer | no_return\n  def child_block_interval() do\n    Application.fetch_env!(@app, :child_block_interval)\n  end\n\n  @spec eth_node() :: atom | no_return\n  def eth_node() do\n    Application.fetch_env!(@app, :eth_node)\n  end\n\n  @spec ethereum_events_check_interval_ms() :: pos_integer | no_return\n  def ethereum_events_check_interval_ms() do\n    Application.fetch_env!(@app, :ethereum_events_check_interval_ms)\n  end\n\n  @spec ethereum_stalled_sync_threshold_ms() :: pos_integer | no_return\n  def ethereum_stalled_sync_threshold_ms() do\n    Application.fetch_env!(@app, :ethereum_stalled_sync_threshold_ms)\n  end\nend\n"
  },
  {
    "path": "apps/omg_eth/lib/omg_eth/encoding/contract_constructor.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Eth.Encoding.ContractConstructor do\n  @moduledoc \"\"\"\n  Prepares data for a contract's constructor.\n  \"\"\"\n\n  @doc \"\"\"\n  Extracts a list of 2-element tuples with {type, value}, into a list of types and\n  a list of values that can be passed into `ABI.TypeEncoder.encode_raw/1`.\n\n  ## Examples\n\n      iex> OMG.Eth.Encoding.ContractConstructor.extract_params([\n      ...>   {:address, \"0x1234\"},\n      ...>   {{:uint, 256}, 1000},\n      ...>   {:bool, true}\n      ...> ])\n      {\n        [:address, {:uint, 256}, :bool],\n        [\"0x1234\", 1000, true]\n      }\n  \"\"\"\n  @spec extract_params(types_values :: [tuple()]) :: {types :: [term()], values :: [term()]}\n  def extract_params(types_values) do\n    {types, values} =\n      Enum.reduce(types_values, {[], []}, fn item, {types, values} ->\n        case item do\n          {:tuple, elements} ->\n            {tuple_types, tuple_values} = extract_params(elements)\n            {[{:tuple, tuple_types} | types], [List.to_tuple(tuple_values) | values]}\n\n          {type, arg} ->\n            {[type | types], [arg | values]}\n        end\n      end)\n\n    {Enum.reverse(types), Enum.reverse(values)}\n  end\nend\n"
  },
  {
    "path": "apps/omg_eth/lib/omg_eth/encoding.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Eth.Encoding do\n  @moduledoc \"\"\"\n  Internal encoding helpers to talk to ethereum.\n  For use in `OMG.Eth` and `OMG.Eth.DevHelper`\n  \"\"\"\n\n  alias OMG.Eth.Encoding.ContractConstructor\n\n  @doc \"\"\"\n  Ethereum JSONRPC and Ethereumex' specific encoding and decoding of binaries and ints\n\n  We are enforcing the users of Eth and Eth.<Contract> APIs to always use integers and raw decoded binaries,\n  when interacting.\n\n  Configuration entries are expected to be written in \"0xhex-style\"\n  \"\"\"\n  @spec to_hex(binary | non_neg_integer) :: binary\n  def to_hex(non_hex)\n\n  def to_hex(raw) when is_binary(raw), do: \"0x\" <> Base.encode16(raw, case: :lower)\n  def to_hex(int) when is_integer(int), do: \"0x\" <> Integer.to_string(int, 16)\n\n  @doc \"\"\"\n  Decodes to a raw binary, see `to_hex`\n  \"\"\"\n  # because https://github.com/rrrene/credo/issues/583, we need to:\n  # credo:disable-for-next-line Credo.Check.Consistency.SpaceAroundOperators\n  @spec from_hex(<<_::16, _::_*8>>, atom()) :: binary\n  def from_hex(\"0x\" <> encoded, format \\\\ :lower), do: Base.decode16!(encoded, case: format)\n\n  @doc \"\"\"\n  Decodes to an integer, see `to_hex`\n  \"\"\"\n  # because https://github.com/rrrene/credo/issues/583, we need to:\n  # credo:disable-for-next-line Credo.Check.Consistency.SpaceAroundOperators\n  @spec int_from_hex(<<_::16, _::_*8>>) :: non_neg_integer\n  def int_from_hex(\"0x\" <> encoded) do\n    {return, \"\"} = Integer.parse(encoded, 16)\n    return\n  end\n\n  @doc \"\"\"\n  Encodes a list of smart contract constructor parameters into a base16 encoded-ABI that\n  solidity expects.\n\n  ## Examples\n\n      iex> OMG.Eth.Encoding.encode_constructor_params([\n      ...>   {{:uint, 8}, 255},\n      ...> ])\n      \"00000000000000000000000000000000000000000000000000000000000000ff\"\n\n      iex> OMG.Eth.Encoding.encode_constructor_params([\n      ...>   {{:uint, 8}, 255},\n      ...>   {:string, \"hello\"},\n      ...> ])\n      \"00000000000000000000000000000000000000000000000000000000000000ff0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000568656c6c6f000000000000000000000000000000000000000000000000000000\"\n  \"\"\"\n  @spec encode_constructor_params(types_values :: [tuple()]) :: abi_base16_encoded :: binary()\n  def encode_constructor_params(types_values) do\n    {types, values} = ContractConstructor.extract_params(types_values)\n\n    values\n    |> ABI.TypeEncoder.encode_raw(types)\n    # NOTE: we're not using `to_hex` because the `0x` will be appended to the bytecode already\n    |> Base.encode16(case: :lower)\n  end\nend\n"
  },
  {
    "path": "apps/omg_eth/lib/omg_eth/ethereum_height.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Eth.EthereumHeight do\n  @moduledoc \"\"\"\n  A GenServer that subscribes to `ethereum_new_height` events coming from the internal event bus,\n  decodes and saves only the height to be consumed by other services.\n  \"\"\"\n\n  use GenServer\n  require Logger\n  alias OMG.Eth.Client\n\n  @spec get() :: {:ok, non_neg_integer()} | {:error, :error_ethereum_height}\n  def get() do\n    GenServer.call(__MODULE__, :get)\n  end\n\n  def start_link(args) do\n    GenServer.start_link(__MODULE__, args, name: __MODULE__)\n  end\n\n  def init(opts) do\n    event_bus = Keyword.fetch!(opts, :event_bus)\n    :ok = event_bus.subscribe({:root_chain, \"ethereum_new_height\"}, link: true)\n    {:ok, get_ethereum_height()}\n  end\n\n  def handle_call(:get, _from, ethereum_height) when is_atom(ethereum_height) do\n    {:reply, {:error, ethereum_height}, ethereum_height}\n  end\n\n  def handle_call(:get, _from, ethereum_height) do\n    {:reply, {:ok, ethereum_height}, ethereum_height}\n  end\n\n  def handle_info({:internal_event_bus, :ethereum_new_height, new_height}, _state) do\n    _ = Logger.debug(\"Got an internal :ethereum_new_height event with height: #{new_height}.\")\n    {:noreply, new_height}\n  end\n\n  @spec get_ethereum_height() :: non_neg_integer() | :error_ethereum_height\n  defp get_ethereum_height() do\n    {:ok, rootchain_height} = eth().get_ethereum_height()\n    rootchain_height\n  rescue\n    _check_error -> :error_ethereum_height\n  end\n\n  defp eth(), do: Application.get_env(:omg_eth, :eth_integration_module, Client)\nend\n"
  },
  {
    "path": "apps/omg_eth/lib/omg_eth/ethereum_height_monitor/alarm_handler.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Eth.EthereumHeightMonitor.AlarmHandler do\n  @moduledoc \"\"\"\n  Listens for :ethereum_connection_error and :ethereum_stalled_sync alarms and reflect\n  the alarm's state back to the monitor.\n  \"\"\"\n  require Logger\n\n  # The alarm reporter and monitor happen to be the same module here because we are just\n  # reflecting the alarm's state back to the reporter.\n  @reporter OMG.Eth.EthereumHeightMonitor\n  @monitor OMG.Eth.EthereumHeightMonitor\n\n  def init(_args) do\n    {:ok, %{}}\n  end\n\n  def handle_call(_request, state), do: {:ok, :ok, state}\n\n  def handle_event({:set_alarm, {:ethereum_connection_error, %{reporter: @reporter}}}, state) do\n    _ = Logger.warn(\":ethereum_connection_error alarm raised.\")\n    :ok = GenServer.cast(@monitor, {:set_alarm, :ethereum_connection_error})\n    {:ok, state}\n  end\n\n  def handle_event({:clear_alarm, {:ethereum_connection_error, %{reporter: @reporter}}}, state) do\n    _ = Logger.warn(\":ethereum_connection_error alarm cleared.\")\n    :ok = GenServer.cast(@monitor, {:clear_alarm, :ethereum_connection_error})\n    {:ok, state}\n  end\n\n  def handle_event({:set_alarm, {:ethereum_stalled_sync, %{reporter: @reporter}}}, state) do\n    _ = Logger.warn(\":ethereum_stalled_sync alarm raised.\")\n    :ok = GenServer.cast(@monitor, {:set_alarm, :ethereum_stalled_sync})\n    {:ok, state}\n  end\n\n  def handle_event({:clear_alarm, {:ethereum_stalled_sync, %{reporter: @reporter}}}, state) do\n    _ = Logger.warn(\":ethereum_stalled_sync alarm cleared.\")\n    :ok = GenServer.cast(@monitor, {:clear_alarm, :ethereum_stalled_sync})\n    {:ok, state}\n  end\n\n  def handle_event(event, state) do\n    _ = Logger.info(\"#{__MODULE__} got event: #{inspect(event)}. Ignoring.\")\n    {:ok, state}\n  end\nend\n"
  },
  {
    "path": "apps/omg_eth/lib/omg_eth/ethereum_height_monitor.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Eth.EthereumHeightMonitor do\n  @moduledoc \"\"\"\n  Periodically calls the Ethereum client node to check for Ethereumm's block height. Publishes\n  internal events or raises alarms accordingly.\n\n  When a new block height is received, it publishes an internal event under the topic `\"ethereum_new_height\"`\n  with the payload `{:ethereum_new_height, height}`. The event is only published when the received\n  block height is higher than the previously published height.\n\n  When the call to the Ethereum client fails or returns an invalid responnse, it raises an\n  `:ethereum_connection_error` alarm. The alarm is cleared once a valid block height is seen.\n\n  When the call to the Ethereum client returns the same block height for longer than\n  `:ethereum_stalled_sync_threshold_ms`, it raises an `:ethereum_stalled_sync` alarm.\n  The alarm is cleared once the block height starts increasing again.\n  \"\"\"\n  use GenServer\n  require Logger\n\n  @type t() :: %__MODULE__{\n          check_interval_ms: pos_integer(),\n          stall_threshold_ms: pos_integer(),\n          tref: reference() | nil,\n          eth_module: module(),\n          alarm_module: module(),\n          event_bus_module: module(),\n          ethereum_height: integer(),\n          synced_at: DateTime.t(),\n          connection_alarm_raised: boolean(),\n          stall_alarm_raised: boolean()\n        }\n\n  defstruct check_interval_ms: 10_000,\n            stall_threshold_ms: 20_000,\n            tref: nil,\n            eth_module: nil,\n            alarm_module: nil,\n            event_bus_module: nil,\n            ethereum_height: 0,\n            synced_at: nil,\n            connection_alarm_raised: false,\n            stall_alarm_raised: false\n\n  #\n  # GenServer APIs\n  #\n\n  def start_link(args) do\n    GenServer.start_link(__MODULE__, args, name: __MODULE__)\n  end\n\n  #\n  # GenServer behaviors\n  #\n\n  def init(opts) do\n    _ = Logger.info(\"Starting Ethereum height monitor.\")\n    _ = install_alarm_handler()\n\n    state = %__MODULE__{\n      check_interval_ms: Keyword.fetch!(opts, :check_interval_ms),\n      stall_threshold_ms: Keyword.fetch!(opts, :stall_threshold_ms),\n      synced_at: DateTime.utc_now(),\n      eth_module: Keyword.fetch!(opts, :eth_module),\n      alarm_module: Keyword.fetch!(opts, :alarm_module),\n      event_bus_module: Keyword.fetch!(opts, :event_bus_module)\n    }\n\n    {:ok, state, {:continue, :first_check}}\n  end\n\n  # We want the first check immediately upon start, but we cannot do it while the monitor\n  # is not fully initialized, so we need to trigger it in a :continue instruction.\n  def handle_continue(:first_check, state) do\n    _ = send(self(), :check_new_height)\n    {:noreply, state}\n  end\n\n  def handle_info({:ssl_closed, _}, state) do\n    # eat this bug https://github.com/benoitc/hackney/issues/464\n    {:noreply, state}\n  end\n\n  def handle_info(:check_new_height, state) do\n    height = fetch_height(state.eth_module)\n    stalled? = stalled?(height, state.ethereum_height, state.synced_at, state.stall_threshold_ms)\n\n    :ok = broadcast_on_new_height(state.event_bus_module, height)\n    _ = connection_alarm(state.alarm_module, state.connection_alarm_raised, height)\n    _ = stall_alarm(state.alarm_module, state.stall_alarm_raised, stalled?)\n\n    state = update_height(state, height)\n\n    {:ok, tref} = :timer.send_after(state.check_interval_ms, :check_new_height)\n    {:noreply, %{state | tref: tref}}\n  end\n\n  #\n  # Handle incoming alarms\n  #\n  # These functions are called by the AlarmHandler so that this monitor process can update\n  # its internal state according to the raised alarms.\n  #\n  def handle_cast({:set_alarm, :ethereum_connection_error}, state) do\n    {:noreply, %{state | connection_alarm_raised: true}}\n  end\n\n  def handle_cast({:clear_alarm, :ethereum_connection_error}, state) do\n    {:noreply, %{state | connection_alarm_raised: false}}\n  end\n\n  def handle_cast({:set_alarm, :ethereum_stalled_sync}, state) do\n    {:noreply, %{state | stall_alarm_raised: true}}\n  end\n\n  def handle_cast({:clear_alarm, :ethereum_stalled_sync}, state) do\n    {:noreply, %{state | stall_alarm_raised: false}}\n  end\n\n  #\n  # Private functions\n  #\n\n  @spec update_height(t(), non_neg_integer() | :error) :: t()\n  defp update_height(state, :error), do: state\n\n  defp update_height(state, height) do\n    case height > state.ethereum_height do\n      true -> %{state | ethereum_height: height, synced_at: DateTime.utc_now()}\n      false -> state\n    end\n  end\n\n  @spec stalled?(non_neg_integer() | :error, non_neg_integer(), DateTime.t(), non_neg_integer()) :: boolean()\n  defp stalled?(height, previous_height, synced_at, stall_threshold_ms) do\n    case height do\n      height when is_integer(height) and height > previous_height ->\n        false\n\n      _ ->\n        DateTime.diff(DateTime.utc_now(), synced_at, :millisecond) > stall_threshold_ms\n    end\n  end\n\n  @spec fetch_height(module()) :: non_neg_integer() | :error\n  defp fetch_height(eth_module) do\n    case eth_module.get_ethereum_height() do\n      {:ok, height} ->\n        height\n\n      error ->\n        _ = Logger.warn(\"Error retrieving Ethereum height: #{inspect(error)}\")\n        :error\n    end\n  end\n\n  @spec broadcast_on_new_height(module(), non_neg_integer() | :error) :: :ok | {:error, term()}\n  defp broadcast_on_new_height(_event_bus_module, :error), do: :ok\n\n  # we need to publish every height we fetched so that we can re-examine blocks in case of re-orgs\n  # clients subscribed to this topic need to be aware of that and if a block number repeats,\n  # it needs to re-write logs, for example\n  defp broadcast_on_new_height(event_bus_module, height) do\n    event = OMG.Bus.Event.new({:root_chain, \"ethereum_new_height\"}, :ethereum_new_height, height)\n    apply(event_bus_module, :broadcast, [event])\n  end\n\n  #\n  # Alarms management\n  #\n\n  defp install_alarm_handler() do\n    case Enum.member?(:gen_event.which_handlers(:alarm_handler), __MODULE__.AlarmHandler) do\n      true -> :ok\n      _ -> :alarm_handler.add_alarm_handler(__MODULE__.AlarmHandler)\n    end\n  end\n\n  # Raise or clear the :ethereum_client_connnection alarm\n  @spec connection_alarm(module(), boolean(), non_neg_integer() | :error) :: :ok | :duplicate\n  defp connection_alarm(alarm_module, connection_alarm_raised, raise_alarm)\n\n  defp connection_alarm(alarm_module, false, :error) do\n    alarm_module.set(alarm_module.ethereum_connection_error(__MODULE__))\n  end\n\n  defp connection_alarm(alarm_module, true, height) when is_integer(height) do\n    alarm_module.clear(alarm_module.ethereum_connection_error(__MODULE__))\n  end\n\n  defp connection_alarm(_alarm_module, _, _), do: :ok\n\n  # Raise or clear the :ethereum_stalled_sync alarm\n  @spec stall_alarm(module(), boolean(), boolean()) :: :ok | :duplicate\n  defp stall_alarm(alarm_module, stall_alarm_raised, raise_alarm)\n\n  defp stall_alarm(alarm_module, false, true) do\n    alarm_module.set(alarm_module.ethereum_stalled_sync(__MODULE__))\n  end\n\n  defp stall_alarm(alarm_module, true, false) do\n    alarm_module.clear(alarm_module.ethereum_stalled_sync(__MODULE__))\n  end\n\n  defp stall_alarm(_alarm_module, _, _), do: :ok\nend\n"
  },
  {
    "path": "apps/omg_eth/lib/omg_eth/metric/ethereumex.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Eth.Metric.Ethereumex do\n  @moduledoc \"\"\"\n  Telemetry handler for Ethereumex events\n  \"\"\"\n  alias OMG.Status.Metric.Datadog\n  def supported_events(), do: [:ethereumex]\n\n  def handle_event([:ethereumex], %{counter: counter}, %{method_name: method_name} = _metadata, _config) do\n    Datadog.increment(method_name, counter)\n  end\nend\n"
  },
  {
    "path": "apps/omg_eth/lib/omg_eth/release_tasks/set_contract.ex",
    "content": "# Copyright 2019-2019 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Eth.ReleaseTasks.SetContract do\n  @moduledoc false\n  @behaviour Config.Provider\n  require Logger\n\n  alias OMG.Eth.Encoding\n  alias OMG.Eth.RootChain.Abi\n  alias OMG.Eth.RootChain.Rpc\n\n  @networks [\"RINKEBY\", \"ROPSTEN\", \"GOERLI\", \"KOVAN\", \"MAINNET\", \"LOCALCHAIN\"]\n  @error \"Set ETHEREUM_NETWORK to #{Enum.join(@networks, \",\")} with TXHASH_CONTRACT, AUTHORITY_ADDRESS and CONTRACT_ADDRESS environment variables or CONTRACT_EXCHANGER_URL.\"\n  @ether_vault_id 1\n  @erc20_vault_id 2\n  @doc \"\"\"\n  The contract values can currently come either from ENV variables for deployments in\n  - development\n  - stagind\n  - production\n  or, they're manually deployed for local development:\n  \"\"\"\n\n  def init(args) do\n    args\n  end\n\n  def load(config, args) do\n    _ = on_load()\n    rpc_api = Keyword.get(args, :rpc_api, Rpc)\n\n    exchanger = get_env(\"CONTRACT_EXCHANGER_URL\")\n    via_env = get_env(\"ETHEREUM_NETWORK\")\n    network = get_network(via_env)\n\n    {txhash_contract, authority_address, plasma_framework} =\n      case exchanger do\n        exchanger when is_binary(exchanger) ->\n          body =\n            try do\n              {:ok, %{body: body}} = HTTPoison.get(exchanger)\n              body\n            rescue\n              reason -> exit(\"CONTRACT_EXCHANGER_URL #{exchanger} is not reachable because of #{inspect(reason)}\")\n            end\n\n          %{\n            authority_address: authority_address,\n            plasma_framework: plasma_framework,\n            plasma_framework_tx_hash: txhash_contract\n          } = Jason.decode!(body, keys: :atoms!)\n\n          {txhash_contract, authority_address, plasma_framework}\n\n        _ ->\n          txhash_contract = get_env(\"TXHASH_CONTRACT\")\n          authority_address = get_env(\"AUTHORITY_ADDRESS\")\n          plasma_framework = get_env(\"CONTRACT_ADDRESS_PLASMA_FRAMEWORK\")\n          {txhash_contract, authority_address, plasma_framework}\n      end\n\n    # get all the data from external sources\n    {payment_exit_game, eth_vault, erc20_vault, min_exit_period_seconds, contract_semver, child_block_interval} =\n      get_external_data(plasma_framework, rpc_api)\n\n    contract_addresses = %{\n      plasma_framework: plasma_framework,\n      eth_vault: eth_vault,\n      erc20_vault: erc20_vault,\n      payment_exit_game: payment_exit_game\n    }\n\n    merge_configuration(\n      config,\n      txhash_contract,\n      authority_address,\n      contract_addresses,\n      min_exit_period_seconds,\n      contract_semver,\n      network,\n      child_block_interval\n    )\n  end\n\n  defp get_external_data(plasma_framework, rpc_api) do\n    min_exit_period_seconds = get_min_exit_period(plasma_framework, rpc_api)\n\n    payment_exit_game =\n      plasma_framework |> exit_game_contract_address(ExPlasma.payment_v1(), rpc_api) |> Encoding.to_hex()\n\n    eth_vault = plasma_framework |> get_vault(@ether_vault_id, rpc_api) |> Encoding.to_hex()\n    erc20_vault = plasma_framework |> get_vault(@erc20_vault_id, rpc_api) |> Encoding.to_hex()\n    contract_semver = get_contract_semver(plasma_framework, rpc_api)\n    child_block_interval = get_child_block_interval(plasma_framework, rpc_api)\n    {payment_exit_game, eth_vault, erc20_vault, min_exit_period_seconds, contract_semver, child_block_interval}\n  end\n\n  defp merge_configuration(\n         config,\n         txhash_contract,\n         authority_address,\n         contract_addresses,\n         min_exit_period_seconds,\n         contract_semver,\n         network,\n         child_block_interval\n       )\n       when is_binary(txhash_contract) and\n              is_binary(authority_address) and is_map(contract_addresses) and is_integer(min_exit_period_seconds) and\n              is_binary(contract_semver) and is_binary(network) do\n    contract_addresses = Enum.into(contract_addresses, %{}, fn {name, addr} -> {name, String.downcase(addr)} end)\n\n    Config.Reader.merge(config,\n      omg_eth: [\n        txhash_contract: String.downcase(txhash_contract),\n        authority_address: String.downcase(authority_address),\n        contract_addr: contract_addresses,\n        min_exit_period_seconds: min_exit_period_seconds,\n        contract_semver: contract_semver,\n        network: network,\n        child_block_interval: child_block_interval\n      ]\n    )\n  end\n\n  defp merge_configuration(_, _, _, _, _, _, _, _), do: exit(@error)\n\n  defp get_min_exit_period(plasma_framework_contract, rpc_api) do\n    signature = \"minExitPeriod()\"\n    {:ok, data} = call(plasma_framework_contract, signature, [], rpc_api)\n    %{\"min_exit_period\" => min_exit_period} = Abi.decode_function(data, signature)\n    min_exit_period\n  end\n\n  defp get_contract_semver(plasma_framework_contract, rpc_api) do\n    signature = \"getVersion()\"\n    {:ok, data} = call(plasma_framework_contract, signature, [], rpc_api)\n    %{\"version\" => version} = Abi.decode_function(data, signature)\n    version\n  end\n\n  defp get_child_block_interval(plasma_framework_contract, rpc_api) do\n    signature = \"childBlockInterval()\"\n    {:ok, data} = call(plasma_framework_contract, signature, [], rpc_api)\n    %{\"child_block_interval\" => child_block_interval} = Abi.decode_function(data, signature)\n    child_block_interval\n  end\n\n  defp exit_game_contract_address(plasma_framework_contract, tx_type, rpc_api) do\n    signature = \"exitGames(uint256)\"\n    {:ok, data} = call(plasma_framework_contract, signature, [tx_type], rpc_api)\n    %{\"exit_game_address\" => exit_game_address} = Abi.decode_function(data, signature)\n    exit_game_address\n  end\n\n  defp get_vault(plasma_framework_contract, id, rpc_api) do\n    signature = \"vaults(uint256)\"\n    {:ok, data} = call(plasma_framework_contract, signature, [id], rpc_api)\n    %{\"vault_address\" => vault_address} = Abi.decode_function(data, signature)\n    vault_address\n  end\n\n  defp call(plasma_framework_contract, signature, args, rpc_api) do\n    retries_left = 3\n    call(plasma_framework_contract, signature, args, retries_left, rpc_api)\n  end\n\n  defp call(plasma_framework_contract, signature, args, 0, rpc_api) do\n    rpc_api.call_contract(plasma_framework_contract, signature, args)\n  end\n\n  defp call(plasma_framework_contract, signature, args, retries_left, rpc_api) do\n    case rpc_api.call_contract(plasma_framework_contract, signature, args) do\n      {:ok, _data} = result ->\n        result\n\n      {:error, :closed} ->\n        Process.sleep(1000)\n        call(plasma_framework_contract, signature, args, retries_left - 1, rpc_api)\n    end\n  end\n\n  defp get_env(key), do: System.get_env(key)\n\n  defp get_network(nil), do: exit(@error)\n\n  defp get_network(data) do\n    case Enum.member?(@networks, String.upcase(data)) do\n      true ->\n        String.upcase(data)\n\n      _ ->\n        exit(@error)\n    end\n  end\n\n  defp on_load() do\n    {:ok, _} = Application.ensure_all_started(:logger)\n    {:ok, _} = Application.ensure_all_started(:ethereumex)\n  end\nend\n"
  },
  {
    "path": "apps/omg_eth/lib/omg_eth/release_tasks/set_ethereum_block_time.ex",
    "content": "# Copyright 2019-2019 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Eth.ReleaseTasks.SetEthereumBlockTime do\n  @moduledoc \"\"\"\n  Configures the average ethereum block time for the network used.\n  \"\"\"\n  @behaviour Config.Provider\n  require Logger\n\n  @app :omg_eth\n  @env_key \"ETHEREUM_BLOCK_TIME_SECONDS\"\n\n  def init(args) do\n    args\n  end\n\n  def load(config, _args) do\n    _ = on_load()\n    ethereum_block_time = get_ethereum_block_time()\n    Config.Reader.merge(config, omg_eth: [ethereum_block_time_seconds: ethereum_block_time])\n  end\n\n  defp get_ethereum_block_time() do\n    ethereum_block_time_seconds = Application.get_env(@app, :ethereum_block_time_seconds)\n    ethereum_block_time_seconds = validate_integer(get_env(@env_key), ethereum_block_time_seconds)\n\n    _ =\n      Logger.info(\n        \"CONFIGURATION: App: #{@app} Key: ethereum_block_time_seconds Value: #{inspect(ethereum_block_time_seconds)}.\"\n      )\n\n    ethereum_block_time_seconds\n  end\n\n  defp get_env(key), do: System.get_env(key)\n\n  defp validate_integer(value, _default) when is_binary(value), do: String.to_integer(value)\n  defp validate_integer(_, default), do: default\n\n  defp on_load() do\n    _ = Application.ensure_all_started(:logger)\n    _ = Application.load(@app)\n  end\nend\n"
  },
  {
    "path": "apps/omg_eth/lib/omg_eth/release_tasks/set_ethereum_client.ex",
    "content": "# Copyright 2019-2019 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Eth.ReleaseTasks.SetEthereumClient do\n  @moduledoc false\n  @behaviour Config.Provider\n  require Logger\n  @app :omg_eth\n  @doc \"\"\"\n  Gets the environment setting for the ethereum client location.\n  \"\"\"\n\n  def init(args) do\n    args\n  end\n\n  def load(config, _args) do\n    _ = on_load()\n    rpc_url = get_ethereum_rpc_url()\n    rpc_client_type = get_rpc_client_type()\n    # we need to get this imidiatelly in effect because we use ethereumex in SetContract\n    Application.put_env(:ethereumex, :url, rpc_url, persistent: true)\n\n    Config.Reader.merge(config,\n      ethereumex: [url: rpc_url],\n      omg_eth: [eth_node: rpc_client_type]\n    )\n  end\n\n  defp get_ethereum_rpc_url() do\n    url = validate_string(get_env(\"ETHEREUM_RPC_URL\"), Application.get_env(:ethereumex, :url))\n    _ = Logger.info(\"CONFIGURATION: App: #{@app} Key: ETHEREUM_RPC_URL Value: #{inspect(url)}.\")\n\n    url\n  end\n\n  defp get_rpc_client_type() do\n    rpc_client_type = validate_rpc_client_type(get_env(\"ETH_NODE\"), Application.get_env(@app, :eth_node))\n    _ = Logger.info(\"CONFIGURATION: App: #{@app} Key: ETH_NODE Value: #{inspect(rpc_client_type)}.\")\n\n    rpc_client_type\n  end\n\n  defp validate_rpc_client_type(value, _default) when is_binary(value),\n    do: to_rpc_client_type(String.upcase(value))\n\n  defp validate_rpc_client_type(_value, default),\n    do: default\n\n  defp to_rpc_client_type(\"GETH\"), do: :geth\n  defp to_rpc_client_type(\"PARITY\"), do: :parity\n  defp to_rpc_client_type(\"INFURA\"), do: :infura\n  defp to_rpc_client_type(_), do: exit(\"You need to choose between geth, parity or infura.\")\n\n  defp validate_string(value, _default) when is_binary(value), do: value\n  defp validate_string(_, default), do: default\n\n  defp get_env(key), do: System.get_env(key)\n\n  defp on_load() do\n    _ = Application.ensure_all_started(:logger)\n    _ = Application.ensure_all_started(:omg_status)\n    _ = Application.load(@app)\n    _ = Application.load(:ethereumex)\n  end\nend\n"
  },
  {
    "path": "apps/omg_eth/lib/omg_eth/release_tasks/set_ethereum_events_check_interval.ex",
    "content": "# Copyright 2019-2019 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Eth.ReleaseTasks.SetEthereumEventsCheckInterval do\n  @moduledoc \"\"\"\n  Configures the interval to check for new events from Ethereum, including checking for new heights.\n\n  This is essentially the same as `OMG.Watcher.ReleaseTasks.SetEthereumEventsCheckInterval` but for a different subapp.\n  \"\"\"\n  @behaviour Config.Provider\n  require Logger\n\n  @app :omg_eth\n  @env_key \"ETHEREUM_EVENTS_CHECK_INTERVAL_MS\"\n\n  def init(args) do\n    args\n  end\n\n  def load(config, _args) do\n    _ = on_load()\n\n    interval_ms = get_interval_ms()\n\n    Config.Reader.merge(config,\n      omg_eth: [ethereum_events_check_interval_ms: interval_ms],\n      omg_watcher: [ethereum_events_check_interval_ms: interval_ms]\n    )\n  end\n\n  defp get_interval_ms() do\n    ethereum_events_check_interval_ms = Application.get_env(@app, :ethereum_events_check_interval_ms)\n    interval_ms = validate_integer(get_env(@env_key), ethereum_events_check_interval_ms)\n\n    _ =\n      Logger.info(\"CONFIGURATION: App: #{@app} Key: ethereum_events_check_interval_ms Value: #{inspect(interval_ms)}.\")\n\n    interval_ms\n  end\n\n  defp get_env(key), do: System.get_env(key)\n\n  defp validate_integer(value, _default) when is_binary(value), do: String.to_integer(value)\n  defp validate_integer(_, default), do: default\n\n  defp on_load() do\n    _ = Application.ensure_all_started(:logger)\n    _ = Application.load(@app)\n  end\nend\n"
  },
  {
    "path": "apps/omg_eth/lib/omg_eth/release_tasks/set_ethereum_stalled_sync_threshold.ex",
    "content": "# Copyright 2019-2019 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Eth.ReleaseTasks.SetEthereumStalledSyncThreshold do\n  @moduledoc false\n  @behaviour Config.Provider\n  require Logger\n\n  @app :omg_eth\n  @env_name \"ETHEREUM_STALLED_SYNC_THRESHOLD_MS\"\n\n  def init(args) do\n    args\n  end\n\n  def load(config, _args) do\n    _ = on_load()\n    threshold_ms = stalled_sync_threshold_ms()\n    Config.Reader.merge(config, omg_eth: [ethereum_stalled_sync_threshold_ms: threshold_ms])\n  end\n\n  defp stalled_sync_threshold_ms() do\n    ethereum_stalled_sync_threshold_ms = Application.get_env(@app, :ethereum_stalled_sync_threshold_ms)\n    threshold_ms = validate_integer(get_env(@env_name), ethereum_stalled_sync_threshold_ms)\n\n    _ =\n      Logger.info(\n        \"CONFIGURATION: App: #{@app} Key: ethereum_stalled_sync_threshold_ms Value: #{inspect(threshold_ms)}.\"\n      )\n\n    threshold_ms\n  end\n\n  defp get_env(key), do: System.get_env(key)\n\n  defp validate_integer(value, _default) when is_binary(value), do: String.to_integer(value)\n  defp validate_integer(_, default), do: default\n\n  defp on_load() do\n    _ = Application.ensure_all_started(:logger)\n    _ = Application.load(@app)\n  end\nend\n"
  },
  {
    "path": "apps/omg_eth/lib/omg_eth/root_chain/abi.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\ndefmodule OMG.Eth.RootChain.Abi do\n  @moduledoc \"\"\"\n  Functions that provide ethereum log decoding\n  \"\"\"\n  alias ExPlasma.Crypto\n  alias OMG.Eth.Encoding\n  alias OMG.Eth.RootChain.AbiEventSelector\n  alias OMG.Eth.RootChain.AbiFunctionSelector\n  alias OMG.Eth.RootChain.Fields\n\n  def decode_function(enriched_data, signature) do\n    \"0x\" <> data = enriched_data\n    <<method_id::binary-size(4), _::binary>> = Crypto.keccak_hash(signature)\n    method_id |> Encoding.to_hex() |> Kernel.<>(data) |> Encoding.from_hex() |> decode_function()\n  end\n\n  def decode_function(enriched_data) do\n    function_specs =\n      Enum.reduce(AbiFunctionSelector.module_info(:exports), [], fn\n        {:module_info, 0}, acc -> acc\n        {function, 0}, acc -> [apply(AbiFunctionSelector, function, []) | acc]\n        _, acc -> acc\n      end)\n\n    {function_spec, data} = ABI.find_and_decode(function_specs, enriched_data)\n    decode_function_call_result(function_spec, data)\n  end\n\n  def decode_log(log) do\n    event_specs =\n      Enum.reduce(AbiEventSelector.module_info(:exports), [], fn\n        {:module_info, 0}, acc -> acc\n        {function, 0}, acc -> [apply(AbiEventSelector, function, []) | acc]\n        _, acc -> acc\n      end)\n\n    topics =\n      Enum.map(log[\"topics\"], fn\n        nil -> nil\n        topic -> Encoding.from_hex(topic)\n      end)\n\n    data = Encoding.from_hex(log[\"data\"])\n\n    {event_spec, data} =\n      ABI.Event.find_and_decode(\n        event_specs,\n        Enum.at(topics, 0),\n        Enum.at(topics, 1),\n        Enum.at(topics, 2),\n        Enum.at(topics, 3),\n        data\n      )\n\n    data\n    |> Enum.into(%{}, fn {key, _type, _indexed, value} -> {key, value} end)\n    |> Fields.rename(event_spec)\n    |> common_parse_event(log)\n  end\n\n  def common_parse_event(\n        result,\n        %{\"blockNumber\" => eth_height, \"transactionHash\" => root_chain_txhash, \"logIndex\" => log_index} = event\n      ) do\n    # NOTE: we're using `put_new` here, because `merge` would allow us to overwrite data fields in case of conflict\n    result\n    |> Map.put_new(:eth_height, Encoding.int_from_hex(eth_height))\n    |> Map.put_new(:root_chain_txhash, Encoding.from_hex(root_chain_txhash))\n    |> Map.put_new(:log_index, Encoding.int_from_hex(log_index))\n    # just copy `event_signature` over, if it's present (could use tidying up)\n    |> Map.put_new(:event_signature, event[:event_signature])\n  end\n\n  defp decode_function_call_result(function_spec, [values]) when is_tuple(values) do\n    function_spec.input_names\n    |> Enum.zip(Tuple.to_list(values))\n    |> Enum.into(%{})\n    |> Fields.rename(function_spec)\n  end\n\n  # workaround for https://github.com/omgnetwork/elixir-omg/issues/1632\n  defp decode_function_call_result(%{function: \"startExit\"} = function_spec, values) do\n    function_spec.input_names\n    |> Enum.zip(values)\n    |> Enum.into(%{})\n    |> Fields.rename(function_spec)\n  end\n\n  defp decode_function_call_result(function_spec, values) do\n    function_spec.input_names\n    |> Enum.zip(values)\n    |> Enum.into(%{})\n  end\nend\n"
  },
  {
    "path": "apps/omg_eth/lib/omg_eth/root_chain/abi_event_selector.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Eth.RootChain.AbiEventSelector do\n  @moduledoc \"\"\"\n  We define Solidity Event selectors that help us decode returned values from function calls.\n  Function names are to be used as inputs to Event Fetcher.\n  Function names describe the type of the event Event Fetcher will retrieve.\n  \"\"\"\n\n  @spec exit_started() :: ABI.FunctionSelector.t()\n  def exit_started() do\n    %ABI.FunctionSelector{\n      function: \"ExitStarted\",\n      input_names: [\"owner\", \"exitId\"],\n      inputs_indexed: [true, false],\n      method_id: <<221, 111, 117, 92>>,\n      returns: [],\n      type: :event,\n      types: [:address, {:uint, 160}]\n    }\n  end\n\n  @spec in_flight_exit_started() :: ABI.FunctionSelector.t()\n  def in_flight_exit_started() do\n    %ABI.FunctionSelector{\n      function: \"InFlightExitStarted\",\n      input_names: [\"initiator\", \"txHash\"],\n      inputs_indexed: [true, true],\n      method_id: <<213, 241, 254, 157>>,\n      returns: [],\n      type: :event,\n      types: [:address, {:bytes, 32}]\n    }\n  end\n\n  @spec in_flight_exit_deleted() :: ABI.FunctionSelector.t()\n  def in_flight_exit_deleted() do\n    %ABI.FunctionSelector{\n      function: \"InFlightExitDeleted\",\n      input_names: [\"exitId\"],\n      inputs_indexed: [true],\n      method_id: <<25, 145, 196, 195>>,\n      returns: [],\n      type: :event,\n      types: [uint: 160]\n    }\n  end\n\n  @spec in_flight_exit_challenged() :: ABI.FunctionSelector.t()\n  def in_flight_exit_challenged() do\n    %ABI.FunctionSelector{\n      function: \"InFlightExitChallenged\",\n      input_names: [\"challenger\", \"txHash\", \"challengeTxPosition\"],\n      inputs_indexed: [true, true, false],\n      method_id: <<104, 116, 1, 150>>,\n      returns: [],\n      type: :event,\n      types: [:address, {:bytes, 32}, {:uint, 256}]\n    }\n  end\n\n  @spec deposit_created() :: ABI.FunctionSelector.t()\n  def deposit_created() do\n    %ABI.FunctionSelector{\n      function: \"DepositCreated\",\n      input_names: [\"depositor\", \"blknum\", \"token\", \"amount\"],\n      inputs_indexed: [true, true, true, false],\n      method_id: <<24, 86, 145, 34>>,\n      returns: [],\n      type: :event,\n      types: [:address, {:uint, 256}, :address, {:uint, 256}]\n    }\n  end\n\n  @spec in_flight_exit_input_piggybacked() :: ABI.FunctionSelector.t()\n  def in_flight_exit_input_piggybacked() do\n    %ABI.FunctionSelector{\n      function: \"InFlightExitInputPiggybacked\",\n      input_names: [\"exitTarget\", \"txHash\", \"inputIndex\"],\n      inputs_indexed: [true, true, false],\n      method_id: <<169, 60, 14, 155>>,\n      returns: [],\n      type: :event,\n      types: [:address, {:bytes, 32}, {:uint, 16}]\n    }\n  end\n\n  @spec in_flight_exit_output_piggybacked() :: ABI.FunctionSelector.t()\n  def in_flight_exit_output_piggybacked() do\n    %ABI.FunctionSelector{\n      function: \"InFlightExitOutputPiggybacked\",\n      input_names: [\"exitTarget\", \"txHash\", \"outputIndex\"],\n      inputs_indexed: [true, true, false],\n      method_id: <<110, 205, 142, 121>>,\n      returns: [],\n      type: :event,\n      types: [:address, {:bytes, 32}, {:uint, 16}]\n    }\n  end\n\n  @spec block_submitted() :: ABI.FunctionSelector.t()\n  def block_submitted() do\n    %ABI.FunctionSelector{\n      function: \"BlockSubmitted\",\n      input_names: [\"blockNumber\"],\n      inputs_indexed: [false],\n      method_id: <<90, 151, 143, 71>>,\n      returns: [],\n      type: :event,\n      types: [uint: 256]\n    }\n  end\n\n  @spec exit_finalized() :: ABI.FunctionSelector.t()\n  def exit_finalized() do\n    %ABI.FunctionSelector{\n      function: \"ExitFinalized\",\n      input_names: [\"exitId\"],\n      inputs_indexed: [true],\n      method_id: <<10, 219, 41, 176>>,\n      returns: [],\n      type: :event,\n      types: [uint: 160]\n    }\n  end\n\n  @spec in_flight_exit_challenge_responded() :: ABI.FunctionSelector.t()\n  def in_flight_exit_challenge_responded() do\n    # <<99, 124, 196, 167>> == \"c|ħ\"\n    %ABI.FunctionSelector{\n      function: \"InFlightExitChallengeResponded\",\n      input_names: [\"challenger\", \"txHash\", \"challengeTxPosition\"],\n      inputs_indexed: [true, true, false],\n      # method_id: \"c|ħ\",\n      method_id: <<99, 124, 196, 167>>,\n      returns: [],\n      type: :event,\n      types: [:address, {:bytes, 32}, {:uint, 256}]\n    }\n  end\n\n  @spec exit_challenged() :: ABI.FunctionSelector.t()\n  def exit_challenged() do\n    %ABI.FunctionSelector{\n      function: \"ExitChallenged\",\n      input_names: [\"utxoPos\"],\n      inputs_indexed: [true],\n      method_id: <<93, 251, 165, 38>>,\n      returns: [],\n      type: :event,\n      types: [uint: 256]\n    }\n  end\n\n  @spec in_flight_exit_input_blocked() :: ABI.FunctionSelector.t()\n  def in_flight_exit_input_blocked() do\n    %ABI.FunctionSelector{\n      function: \"InFlightExitInputBlocked\",\n      input_names: [\"challenger\", \"txHash\", \"inputIndex\"],\n      inputs_indexed: [true, true, false],\n      method_id: <<71, 148, 4, 88>>,\n      returns: [],\n      type: :event,\n      types: [:address, {:bytes, 32}, {:uint, 16}]\n    }\n  end\n\n  @spec in_flight_exit_output_blocked() :: ABI.FunctionSelector.t()\n  def in_flight_exit_output_blocked() do\n    %ABI.FunctionSelector{\n      function: \"InFlightExitOutputBlocked\",\n      input_names: [\"challenger\", \"txHash\", \"outputIndex\"],\n      inputs_indexed: [true, true, false],\n      method_id: <<203, 232, 218, 210>>,\n      returns: [],\n      type: :event,\n      types: [:address, {:bytes, 32}, {:uint, 16}]\n    }\n  end\n\n  @spec in_flight_exit_input_withdrawn() :: ABI.FunctionSelector.t()\n  def in_flight_exit_input_withdrawn() do\n    %ABI.FunctionSelector{\n      function: \"InFlightExitInputWithdrawn\",\n      input_names: [\"exitId\", \"inputIndex\"],\n      inputs_indexed: [true, false],\n      method_id: <<68, 70, 236, 17>>,\n      returns: [],\n      type: :event,\n      types: [uint: 160, uint: 16]\n    }\n  end\n\n  @spec in_flight_exit_output_withdrawn() :: ABI.FunctionSelector.t()\n  def in_flight_exit_output_withdrawn() do\n    %ABI.FunctionSelector{\n      function: \"InFlightExitOutputWithdrawn\",\n      input_names: [\"exitId\", \"outputIndex\"],\n      inputs_indexed: [true, false],\n      method_id: <<162, 65, 198, 222>>,\n      returns: [],\n      type: :event,\n      types: [uint: 160, uint: 16]\n    }\n  end\nend\n"
  },
  {
    "path": "apps/omg_eth/lib/omg_eth/root_chain/abi_function_selector.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Eth.RootChain.AbiFunctionSelector do\n  @moduledoc \"\"\"\n\n  We define Solidity Function selectors that help us decode returned values from function calls\n  \"\"\"\n  # workaround for https://github.com/omgnetwork/elixir-omg/issues/1632\n  def start_exit() do\n    %ABI.FunctionSelector{\n      function: \"startExit\",\n      input_names: [\n        \"utxoPosToExit\",\n        \"rlpOutputTxToContract\",\n        \"outputTxToContractInclusionProof\",\n        \"rlpInputCreationTx\",\n        \"inputCreationTxInclusionProof\",\n        \"utxoPosInput\"\n      ],\n      inputs_indexed: nil,\n      method_id: <<191, 31, 49, 109>>,\n      returns: [],\n      type: :function,\n      types: [{:uint, 256}, :bytes, :bytes, :bytes, :bytes, {:uint, 256}]\n    }\n  end\n\n  def start_standard_exit() do\n    %ABI.FunctionSelector{\n      function: \"startStandardExit\",\n      input_names: [\"utxoPos\", \"rlpOutputTx\", \"outputTxInclusionProof\"],\n      inputs_indexed: nil,\n      method_id: <<112, 224, 20, 98>>,\n      returns: [],\n      type: :function,\n      types: [tuple: [{:uint, 256}, :bytes, :bytes]]\n    }\n  end\n\n  def challenge_in_flight_exit_not_canonical() do\n    %ABI.FunctionSelector{\n      function: \"challengeInFlightExitNotCanonical\",\n      input_names: [\n        \"inputTx\",\n        \"inputUtxoPos\",\n        \"inFlightTx\",\n        \"inFlightTxInputIndex\",\n        \"competingTx\",\n        \"competingTxInputIndex\",\n        \"competingTxPos\",\n        \"competingTxInclusionProof\",\n        \"competingTxWitness\"\n      ],\n      inputs_indexed: [true, true, true, true, true, true, true, true, true],\n      method_id: <<232, 54, 34, 152>>,\n      returns: [],\n      type: :function,\n      types: [\n        tuple: [\n          :bytes,\n          {:uint, 256},\n          :bytes,\n          {:uint, 16},\n          :bytes,\n          {:uint, 16},\n          {:uint, 256},\n          :bytes,\n          :bytes\n        ]\n      ]\n    }\n  end\n\n  def start_in_flight_exit() do\n    %ABI.FunctionSelector{\n      function: \"startInFlightExit\",\n      input_names: [\"inFlightTx\", \"inputTxs\", \"inputUtxosPos\", \"inputTxsInclusionProofs\", \"inFlightTxWitnesses\"],\n      inputs_indexed: nil,\n      method_id: <<90, 82, 133, 20>>,\n      returns: [],\n      type: :function,\n      types: [\n        tuple: [\n          :bytes,\n          {:array, :bytes},\n          {:array, {:uint, 256}},\n          {:array, :bytes},\n          {:array, :bytes}\n        ]\n      ]\n    }\n  end\n\n  # min_exit_period/0, get_version/0, exit_games/0, vaults/0 are\n  # victims of unfortinate bug: https://github.com/poanetwork/ex_abi/issues/25\n  # All these selectors were intially pulled in with\n  # `ABI.parse_specification(contract_abi_json_decoded,include_events?: true)`\n  # and later modified so that `types` hold what `returns` should have because of\n  # issue 25.\n  # the commented properties of the struct is what it was generated,\n  # the new types were added to mitigate the bug.\n  def min_exit_period() do\n    %ABI.FunctionSelector{\n      function: \"minExitPeriod\",\n      input_names: [\"min_exit_period\"],\n      inputs_indexed: nil,\n      method_id: <<212, 162, 180, 239>>,\n      # returns: [uint: 256],\n      type: :function,\n      # types: []\n      types: [uint: 256]\n    }\n  end\n\n  def get_version() do\n    %ABI.FunctionSelector{\n      function: \"getVersion\",\n      input_names: [\"version\"],\n      inputs_indexed: nil,\n      method_id: <<13, 142, 110, 44>>,\n      # returns: [:string],\n      type: :function,\n      # types: []\n      types: [:string]\n    }\n  end\n\n  def exit_games() do\n    %ABI.FunctionSelector{\n      function: \"exitGames\",\n      input_names: [\"exit_game_address\"],\n      inputs_indexed: nil,\n      method_id: <<175, 7, 151, 100>>,\n      # returns: [:address],\n      type: :function,\n      # types: [uint: 256]\n      types: [:address]\n    }\n  end\n\n  def vaults() do\n    %ABI.FunctionSelector{\n      function: \"vaults\",\n      input_names: [\"vault_address\"],\n      inputs_indexed: nil,\n      method_id: <<140, 100, 234, 74>>,\n      # returns: [:address],\n      type: :function,\n      # types: [uint: 256]\n      types: [:address]\n    }\n  end\n\n  def child_block_interval() do\n    %ABI.FunctionSelector{\n      function: \"childBlockInterval\",\n      input_names: [\"child_block_interval\"],\n      inputs_indexed: nil,\n      method_id: <<56, 169, 224, 188>>,\n      # returns: [uint: 256],\n      type: :function,\n      # types: []\n      types: [uint: 256]\n    }\n  end\n\n  def next_child_block() do\n    %ABI.FunctionSelector{\n      function: \"nextChildBlock\",\n      input_names: [\"block_number\"],\n      inputs_indexed: nil,\n      method_id: <<76, 168, 113, 79>>,\n      # returns: [uint: 256],\n      type: :function,\n      # types: []\n      types: [uint: 256]\n    }\n  end\n\n  def blocks() do\n    %ABI.FunctionSelector{\n      function: \"blocks\",\n      input_names: [\"block_hash\", \"block_timestamp\"],\n      inputs_indexed: nil,\n      method_id: <<242, 91, 63, 153>>,\n      # returns: [bytes: 32, uint: 256],\n      type: :function,\n      # types: [uint: 256]\n      types: [bytes: 32, uint: 256]\n    }\n  end\n\n  def standard_exits() do\n    %ABI.FunctionSelector{\n      function: \"standardExits\",\n      input_names: [\"standard_exit_structs\"],\n      inputs_indexed: nil,\n      method_id: <<12, 165, 182, 118>>,\n      # returns: [\n      #   array: {:tuple, [:bool, {:uint, 256}, {:bytes, 32}, :address, {:uint, 256}, {:uint, 256}]}\n      # ],\n      type: :function,\n      # types: [array: {:uint, 160}]\n      types: [\n        array: {:tuple, [:bool, {:uint, 256}, {:bytes, 32}, :address, {:uint, 256}, {:uint, 256}]}\n      ]\n    }\n  end\n\n  def in_flight_exits() do\n    %ABI.FunctionSelector{\n      function: \"inFlightExits\",\n      input_names: [\"in_flight_exit_structs\"],\n      inputs_indexed: nil,\n      method_id: <<206, 201, 225, 167>>,\n      # returns: [\n      #   array: {:tuple,\n      #           [\n      #             :bool,\n      #             {:uint, 64},\n      #             {:uint, 256},\n      #             {:uint, 256},\n      #             {:array, :tuple, 4},\n      #             {:array, :tuple, 4},\n      #             :address,\n      #             {:uint, 256},\n      #             {:uint, 256}\n      #           ]}\n      # ],\n      type: :function,\n      # types: [array: {:uint, 160}]\n      types: [\n        {:array, {:tuple, [:bool, {:uint, 64}, {:uint, 256}, {:uint, 256}, :address, {:uint, 256}, {:uint, 256}]}}\n      ]\n    }\n  end\nend\n"
  },
  {
    "path": "apps/omg_eth/lib/omg_eth/root_chain/event.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\ndefmodule OMG.Eth.RootChain.Event do\n  @moduledoc \"\"\"\n  Parse signatures from Event definitions so that we're able to create eth_getLogs topics\n  \"\"\"\n  alias OMG.Eth.RootChain.AbiEventSelector\n\n  @spec get_events(list(atom())) :: list(binary())\n  def get_events(wanted_events) do\n    events = events()\n\n    wanted_events\n    |> Enum.reduce([], fn wanted_event_name, acc ->\n      get_event(events, wanted_event_name, acc)\n    end)\n    |> Enum.reverse()\n  end\n\n  # pull all exported functions out the AbiEventSelector module\n  # and create an event signature\n  # function_name(arguments)\n  @spec events() :: list({atom(), binary()})\n  defp events() do\n    Enum.reduce(AbiEventSelector.module_info(:exports), [], fn\n      {:module_info, 0}, acc -> acc\n      {function, 0}, acc -> [{function, describe_event(apply(AbiEventSelector, function, []))} | acc]\n      _, acc -> acc\n    end)\n  end\n\n  defp describe_event(selector) do\n    \"#{selector.function}(\" <> build_types_string(selector.types) <> \")\"\n  end\n\n  defp build_types_string(types), do: build_types_string(types, \"\")\n  defp build_types_string([], string), do: string\n\n  defp build_types_string([{type, size} | [] = types], string) do\n    build_types_string(types, string <> \"#{type}\" <> \"#{size}\")\n  end\n\n  defp build_types_string([{type, size} | types], string) do\n    build_types_string(types, string <> \"#{type}\" <> \"#{size}\" <> \",\")\n  end\n\n  defp build_types_string([type | [] = types], string) do\n    build_types_string(types, string <> \"#{type}\")\n  end\n\n  defp build_types_string([type | types], string) do\n    build_types_string(types, string <> \"#{type}\" <> \",\")\n  end\n\n  def get_event(events, wanted_event_name, acc) do\n    case Enum.find(events, fn {function_name, _signature} -> function_name == wanted_event_name end) do\n      nil -> acc\n      {_, signature} -> [signature | acc]\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_eth/lib/omg_eth/root_chain/fields.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Eth.RootChain.Fields do\n  @moduledoc \"\"\"\n  Adapt to naming from contracts to elixir-omg.\n\n  I need to do this even though I'm bleeding out of my eyes.\n  \"\"\"\n  def rename(data, %ABI.FunctionSelector{function: \"DepositCreated\"}) do\n    # key is naming coming from plasma contracts\n    # value is what we use\n    contracts_naming = [{\"token\", :currency}, {\"depositor\", :owner}, {\"blknum\", :blknum}, {\"amount\", :amount}]\n\n    reduce_naming(data, contracts_naming)\n  end\n\n  # we always call it output_index, which is kinda weird?\n  def rename(data, %ABI.FunctionSelector{function: \"InFlightExitInputPiggybacked\"}) do\n    # key is naming coming from plasma contracts\n    # value is what we use\n    # in_flight_exit_input_piggybacked -> has \"inputIndex\" that needs to be converted to :output_index\n    # in_flight_exit_output_piggybacked -> has \"outputIndex\" that needs to be converted to :output_index\n    # not a typo, both are output_index.\n\n    # InFlightExitInput\n    contracts_naming = [\n      {\"inputIndex\", :output_index},\n      {\"exitTarget\", :owner},\n      {\"txHash\", :tx_hash}\n    ]\n\n    key = :piggyback_type\n    value = :input\n    Map.update(reduce_naming(data, contracts_naming), :omg_data, %{key => value}, &Map.put(&1, key, value))\n  end\n\n  # we always call it output_index, which is kinda weird?\n  def rename(data, %ABI.FunctionSelector{function: \"InFlightExitOutputPiggybacked\"}) do\n    # key is naming coming from plasma contracts\n    # value is what we use\n    # in_flight_exit_input_piggybacked -> has \"inputIndex\" that needs to be converted to :output_index\n    # in_flight_exit_output_piggybacked -> has \"outputIndex\" that needs to be converted to :output_index\n    # not a typo, both are output_index.\n\n    contracts_naming = [\n      {\"outputIndex\", :output_index},\n      {\"exitTarget\", :owner},\n      {\"txHash\", :tx_hash}\n    ]\n\n    key = :piggyback_type\n    value = :output\n    Map.update(reduce_naming(data, contracts_naming), :omg_data, %{key => value}, &Map.put(&1, key, value))\n  end\n\n  def rename(data, %ABI.FunctionSelector{function: \"BlockSubmitted\"}) do\n    contracts_naming = [{\"blockNumber\", :blknum}]\n    reduce_naming(data, contracts_naming)\n  end\n\n  def rename(data, %ABI.FunctionSelector{function: \"ExitFinalized\"}) do\n    contracts_naming = [{\"exitId\", :exit_id}]\n    reduce_naming(data, contracts_naming)\n  end\n\n  def rename(data, %ABI.FunctionSelector{function: \"InFlightExitDeleted\"}) do\n    contracts_naming = [{\"exitId\", :exit_id}]\n    reduce_naming(data, contracts_naming)\n  end\n\n  def rename(data, %ABI.FunctionSelector{function: \"InFlightExitChallenged\"}) do\n    contracts_naming = [\n      {\"challenger\", :challenger},\n      {\"challengeTxPosition\", :competitor_position},\n      {\"txHash\", :tx_hash}\n    ]\n\n    reduce_naming(data, contracts_naming)\n  end\n\n  def rename(data, %ABI.FunctionSelector{function: \"ExitChallenged\"}) do\n    contracts_naming = [\n      {\"utxoPos\", :utxo_pos}\n    ]\n\n    reduce_naming(data, contracts_naming)\n  end\n\n  def rename(data, %ABI.FunctionSelector{function: \"InFlightExitChallengeResponded\"}) do\n    contracts_naming = [\n      {\"challengeTxPosition\", :challenge_position},\n      {\"challenger\", :challenger},\n      {\"txHash\", :tx_hash}\n    ]\n\n    reduce_naming(data, contracts_naming)\n  end\n\n  def rename(data, %ABI.FunctionSelector{function: \"InFlightExitOutputBlocked\"}) do\n    # InFlightExitOutputBlocked has outputIndex that's renamed into output_index\n    # InFlightExitInputBlocked has inputIndex that's renamed into output_index as well\n\n    contracts_naming = [\n      {\"challenger\", :challenger},\n      {\"outputIndex\", :output_index},\n      {\"txHash\", :tx_hash}\n    ]\n\n    key = :piggyback_type\n    value = :output\n    Map.update(reduce_naming(data, contracts_naming), :omg_data, %{key => value}, &Map.put(&1, key, value))\n  end\n\n  def rename(data, %ABI.FunctionSelector{function: \"InFlightExitInputBlocked\"}) do\n    # InFlightExitOutputBlocked has outputIndex that's renamed into output_index\n    # InFlightExitInputBlocked has inputIndex that's renamed into output_index as well\n\n    contracts_naming = [\n      {\"challenger\", :challenger},\n      {\"inputIndex\", :output_index},\n      {\"txHash\", :tx_hash}\n    ]\n\n    key = :piggyback_type\n    value = :input\n    Map.update(reduce_naming(data, contracts_naming), :omg_data, %{key => value}, &Map.put(&1, key, value))\n  end\n\n  def rename(data, %ABI.FunctionSelector{function: \"InFlightExitStarted\"}) do\n    contracts_naming = [\n      {\"initiator\", :initiator},\n      {\"txHash\", :tx_hash}\n    ]\n\n    reduce_naming(data, contracts_naming)\n  end\n\n  def rename(data, %ABI.FunctionSelector{function: \"ExitStarted\"}) do\n    contracts_naming = [\n      {\"owner\", :owner},\n      {\"exitId\", :exit_id}\n    ]\n\n    reduce_naming(data, contracts_naming)\n  end\n\n  def rename(data, %ABI.FunctionSelector{function: \"InFlightExitInputWithdrawn\"}) do\n    # InFlightExitInputWithdrawn\n    contracts_naming = [{\"exitId\", :in_flight_exit_id}, {\"inputIndex\", :output_index}]\n    key = :piggyback_type\n    value = :input\n    Map.update(reduce_naming(data, contracts_naming), :omg_data, %{key => value}, &Map.put(&1, key, value))\n  end\n\n  def rename(data, %ABI.FunctionSelector{function: \"InFlightExitOutputWithdrawn\"}) do\n    # InFlightExitOutputWithdrawn\n    contracts_naming = [{\"exitId\", :in_flight_exit_id}, {\"outputIndex\", :output_index}]\n    key = :piggyback_type\n    value = :output\n    Map.update(reduce_naming(data, contracts_naming), :omg_data, %{key => value}, &Map.put(&1, key, value))\n  end\n\n  def rename(data, %ABI.FunctionSelector{function: \"startInFlightExit\"}) do\n    contracts_naming = [\n      {\"inFlightTx\", :in_flight_tx},\n      {\"inputTxs\", :input_txs},\n      {\"inputUtxosPos\", :input_utxos_pos},\n      {\"inputTxsInclusionProofs\", :input_inclusion_proofs},\n      {\"inFlightTxWitnesses\", :in_flight_tx_sigs}\n    ]\n\n    reduce_naming(data, contracts_naming)\n  end\n\n  def rename(data, %ABI.FunctionSelector{function: \"startStandardExit\"}) do\n    contracts_naming = [\n      {\"outputTxInclusionProof\", :output_tx_inclusion_proof},\n      {\"rlpOutputTx\", :output_tx},\n      {\"utxoPos\", :utxo_pos}\n    ]\n\n    # not used and discarded\n    Map.delete(reduce_naming(data, contracts_naming), :output_tx_inclusion_proof)\n  end\n\n  # workaround for https://github.com/omgnetwork/elixir-omg/issues/1632\n  def rename(data, %ABI.FunctionSelector{function: \"startExit\"}) do\n    contracts_naming = [\n      {\"utxoPosToExit\", :utxo_pos},\n      {\"rlpOutputTxToContract\", :output_tx},\n      {\"outputTxToContractInclusionProof\", :output_tx_inclusion_proof},\n      {\"rlpInputCreationTx\", :rlp_input_creation_tx},\n      {\"inputCreationTxInclusionProof\", :input_creation_tx_inclusion_proof},\n      {\"utxoPosInput\", :utxo_pos_input}\n    ]\n\n    # not used and discarded\n    Map.drop(reduce_naming(data, contracts_naming), [\n      :output_tx_inclusion_proof,\n      :rlp_input_creation_tx,\n      :input_creation_tx_inclusion_proof,\n      :utxo_pos_input\n    ])\n  end\n\n  def rename(data, %ABI.FunctionSelector{function: \"challengeInFlightExitNotCanonical\"}) do\n    contracts_naming = [\n      {\"competingTx\", :competing_tx},\n      {\"competingTxInclusionProof\", :competing_tx_inclusion_proof},\n      {\"competingTxInputIndex\", :competing_tx_input_index},\n      {\"competingTxPos\", :competing_tx_pos},\n      {\"competingTxWitness\", :competing_tx_sig},\n      {\"inFlightTx\", :in_flight_tx},\n      {\"inFlightTxInputIndex\", :in_flight_input_index},\n      {\"inputTx\", :input_tx_bytes},\n      {\"inputUtxoPos\", :input_utxo_pos}\n    ]\n\n    # not used and discarded\n    Map.delete(reduce_naming(data, contracts_naming), :competing_tx_inclusion_proof)\n  end\n\n  defp reduce_naming(data, contracts_naming) do\n    Enum.reduce(contracts_naming, %{}, fn\n      {old_name, new_name}, acc ->\n        value = Map.get(data, old_name)\n\n        acc\n        |> Map.put_new(new_name, value)\n        |> Map.delete(old_name)\n    end)\n  end\nend\n"
  },
  {
    "path": "apps/omg_eth/lib/omg_eth/root_chain/rpc.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\ndefmodule OMG.Eth.RootChain.Rpc do\n  @moduledoc \"\"\"\n   Does RPC calls for enriching event functions or bare events polling to plasma contracts.\n  \"\"\"\n  require Logger\n  alias ExPlasma.Crypto\n  alias OMG.Eth.Encoding\n\n  def call_contract(client \\\\ Ethereumex.HttpClient, contract, signature, args) do\n    data = signature |> ABI.encode(args) |> Encoding.to_hex()\n    client.eth_call(%{from: contract, to: contract, data: data})\n  end\n\n  def get_ethereum_events(block_from, block_to, [_ | _] = signatures, [_ | _] = contracts) do\n    topics = Enum.map(signatures, fn signature -> event_topic_for_signature(signature) end)\n\n    topics_and_signatures =\n      Enum.reduce(Enum.zip(topics, signatures), %{}, fn {topic, signature}, acc -> Map.put(acc, topic, signature) end)\n\n    contracts = Enum.map(contracts, &Encoding.to_hex(&1))\n    block_from = Encoding.to_hex(block_from)\n    block_to = Encoding.to_hex(block_to)\n\n    params = %{\n      fromBlock: block_from,\n      toBlock: block_to,\n      address: contracts,\n      topics: [topics]\n    }\n\n    {:ok, logs} = Ethereumex.HttpClient.eth_get_logs(params)\n    filtered_and_enriched_logs = handle_result(logs, topics, topics_and_signatures)\n    {:ok, filtered_and_enriched_logs}\n  end\n\n  def get_ethereum_events(block_from, block_to, [_ | _] = signatures, contract) do\n    get_ethereum_events(block_from, block_to, signatures, [contract])\n  end\n\n  def get_ethereum_events(block_from, block_to, signature, [_ | _] = contracts) do\n    get_ethereum_events(block_from, block_to, [signature], contracts)\n  end\n\n  def get_ethereum_events(block_from, block_to, signature, contract) do\n    get_ethereum_events(block_from, block_to, [signature], [contract])\n  end\n\n  def get_call_data(root_chain_txhash) do\n    {:ok, %{\"input\" => input}} =\n      root_chain_txhash\n      |> Encoding.to_hex()\n      |> Ethereumex.HttpClient.eth_get_transaction_by_hash()\n\n    {:ok, input}\n  end\n\n  defp event_topic_for_signature(signature) do\n    signature\n    |> Crypto.keccak_hash()\n    |> Encoding.to_hex()\n  end\n\n  defp handle_result(logs, topics, topics_and_signatures) do\n    acc = []\n    handle_result(logs, topics, topics_and_signatures, acc)\n  end\n\n  defp handle_result([], _topics, _topics_and_signatures, acc), do: acc\n\n  defp handle_result([%{\"removed\" => true} | _logs], _topics, _topics_and_signatures, acc) do\n    acc\n  end\n\n  defp handle_result([log | logs], topics, topics_and_signatures, acc) do\n    topic = Enum.find(topics, fn topic -> Enum.at(log[\"topics\"], 0) == topic end)\n    enriched_log = put_signature(log, Map.get(topics_and_signatures, topic))\n    handle_result(logs, topics, topics_and_signatures, [enriched_log | acc])\n  end\n\n  defp put_signature(log, signature), do: Map.put(log, :event_signature, signature)\nend\n"
  },
  {
    "path": "apps/omg_eth/lib/omg_eth/root_chain/submit_block.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Eth.RootChain.SubmitBlock do\n  @moduledoc \"\"\"\n  Interface to contract block submission.\n  \"\"\"\n  alias OMG.Eth.Blockchain.PrivateKey\n  alias OMG.Eth.Encoding\n  alias OMG.Eth.Transaction\n\n  @type address :: <<_::160>>\n  @type hash :: <<_::256>>\n\n  @spec submit(\n          atom(),\n          binary(),\n          pos_integer(),\n          pos_integer(),\n          OMG.Eth.address(),\n          OMG.Eth.address()\n        ) ::\n          {:error, binary() | atom() | map()}\n          | {:ok, <<_::256>>}\n  def submit(backend, hash, nonce, gas_price, from, contract) do\n    # NOTE: we're not using any defaults for opts here!\n    contract_transact(\n      backend,\n      from,\n      contract,\n      \"submitBlock(bytes32)\",\n      [hash],\n      nonce: nonce,\n      gasPrice: gas_price,\n      value: 0,\n      gas: 100_000\n    )\n  end\n\n  @spec contract_transact(atom(), address, address, binary, [any], keyword) :: {:ok, hash()} | {:error, any}\n  defp contract_transact(:infura = backend, _from, to, signature, args, opts) do\n    abi_encoded_data = ABI.encode(signature, args)\n    [nonce: nonce, gasPrice: gas_price, value: value, gas: gas_limit] = opts\n    private_key = PrivateKey.get()\n\n    transaction_data =\n      %OMG.Eth.Blockchain.Transaction{\n        data: abi_encoded_data,\n        gas_limit: gas_limit,\n        gas_price: gas_price,\n        init: <<>>,\n        nonce: nonce,\n        to: to,\n        value: value\n      }\n      |> OMG.Eth.Blockchain.Transaction.Signature.sign_transaction(private_key)\n      |> OMG.Eth.Blockchain.Transaction.serialize()\n      |> ExRLP.encode()\n      |> Base.encode16(case: :lower)\n\n    Transaction.send(backend, \"0x\" <> transaction_data)\n  end\n\n  defp contract_transact(backend, from, to, signature, args, opts) do\n    data = encode_tx_data(signature, args)\n\n    txmap =\n      %{from: Encoding.to_hex(from), to: Encoding.to_hex(to), data: data}\n      |> Map.merge(Map.new(opts))\n      |> encode_all_integer_opts()\n\n    Transaction.send(backend, txmap)\n  end\n\n  defp encode_tx_data(signature, args) do\n    signature\n    |> ABI.encode(args)\n    |> Encoding.to_hex()\n  end\n\n  defp encode_all_integer_opts(opts) do\n    opts\n    |> Enum.filter(fn {_k, v} -> is_integer(v) end)\n    |> Enum.into(opts, fn {k, v} -> {k, Encoding.to_hex(v)} end)\n  end\nend\n"
  },
  {
    "path": "apps/omg_eth/lib/omg_eth/root_chain.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Eth.RootChain do\n  @moduledoc \"\"\"\n  Adapter/port to RootChain contract\n\n  Handles sending transactions and fetching events.\n\n  Should remain simple and not contain any business logic, except being aware of the RootChain contract(s) APIs.\n  \"\"\"\n\n  require Logger\n  import OMG.Eth.Encoding, only: [from_hex: 1, int_from_hex: 1]\n\n  alias OMG.Eth\n  alias OMG.Eth.Configuration\n  alias OMG.Eth.RootChain.Abi\n  alias OMG.Eth.RootChain.Rpc\n\n  @type optional_address_t() :: %{atom => Eth.address()} | %{atom => nil}\n\n  def get_mined_child_block() do\n    child_block_interval = Configuration.child_block_interval()\n    mined_num = next_child_block()\n    mined_num - child_block_interval\n  end\n\n  def next_child_block() do\n    contract_address = Configuration.contracts().plasma_framework\n    %{\"block_number\" => mined_num} = get_external_data(contract_address, \"nextChildBlock()\", [])\n    mined_num\n  end\n\n  def blocks(mined_num) do\n    contract_address = Configuration.contracts().plasma_framework\n\n    %{\"block_hash\" => block_hash, \"block_timestamp\" => block_timestamp} =\n      get_external_data(contract_address, \"blocks(uint256)\", [mined_num])\n\n    {block_hash, block_timestamp}\n  end\n\n  @doc \"\"\"\n  Returns lists of block submissions from Ethereum logs\n  \"\"\"\n  def get_block_submitted_events(from_height, to_height) do\n    contract = from_hex(Configuration.contracts().plasma_framework)\n    signature = \"BlockSubmitted(uint256)\"\n    {:ok, logs} = Rpc.get_ethereum_events(from_height, to_height, signature, contract)\n\n    {:ok, Enum.map(logs, &Abi.decode_log(&1))}\n  end\n\n  ##\n  ## these two cannot be parsed with ABI decoder!\n  ##\n\n  @doc \"\"\"\n  Returns standard exits data from the contract for a list of `exit_id`s. Calls contract method.\n  \"\"\"\n  def get_standard_exit_structs(exit_ids) do\n    contract = Configuration.contracts().payment_exit_game\n\n    %{\"standard_exit_structs\" => standard_exit_structs} =\n      get_external_data(contract, \"standardExits(uint160[])\", [exit_ids])\n\n    {:ok, standard_exit_structs}\n  end\n\n  @doc \"\"\"\n  Returns in flight exits of the specified ids. Calls a contract method.\n  \"\"\"\n  def get_in_flight_exit_structs(in_flight_exit_ids) do\n    contract = Configuration.contracts().payment_exit_game\n\n    %{\"in_flight_exit_structs\" => in_flight_exit_structs} =\n      get_external_data(contract, \"inFlightExits(uint160[])\", [in_flight_exit_ids])\n\n    {:ok, in_flight_exit_structs}\n  end\n\n  ########################\n  # MISC #\n  ########################\n\n  @spec get_root_deployment_height() ::\n          {:ok, integer()} | Ethereumex.HttpClient.error()\n  def get_root_deployment_height() do\n    plasma_framework = Configuration.contracts().plasma_framework\n    txhash = Configuration.txhash_contract()\n\n    case Ethereumex.HttpClient.eth_get_transaction_receipt(txhash) do\n      {:ok, %{\"contractAddress\" => ^plasma_framework, \"blockNumber\" => height}} ->\n        {:ok, int_from_hex(height)}\n\n      {:ok, _} ->\n        {:error, :wrong_contract_address}\n\n      other ->\n        other\n    end\n  end\n\n  defp get_external_data(contract_address, signature, args) do\n    {:ok, data} = Rpc.call_contract(contract_address, signature, args)\n    Abi.decode_function(data, signature)\n  end\nend\n"
  },
  {
    "path": "apps/omg_eth/lib/omg_eth/supervisor.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Eth.Supervisor do\n  @moduledoc \"\"\"\n   OMG Eth top level supervisor is supervising connection monitor towards Eth clients and\n   a gen server that serves as a unified view of reported block height (`OMG.Eth.EthereumHeight`).\n  \"\"\"\n  use Supervisor\n  require Logger\n\n  alias OMG.Eth.Configuration\n  alias OMG.Status.Alert.Alarm\n\n  def start_link() do\n    Supervisor.start_link(__MODULE__, :ok, name: __MODULE__)\n  end\n\n  def init(:ok) do\n    check_interval_ms = Configuration.ethereum_events_check_interval_ms()\n    stall_threshold_ms = Configuration.ethereum_stalled_sync_threshold_ms()\n\n    children = [\n      {OMG.Eth.EthereumHeightMonitor,\n       [\n         check_interval_ms: check_interval_ms,\n         stall_threshold_ms: stall_threshold_ms,\n         eth_module: OMG.Eth.Client,\n         alarm_module: Alarm,\n         event_bus_module: OMG.Bus\n       ]},\n      {OMG.Eth.EthereumHeight, [event_bus: OMG.Bus]}\n    ]\n\n    opts = [strategy: :one_for_one]\n\n    _ = Logger.info(\"Starting #{inspect(__MODULE__)}\")\n    Supervisor.init(children, opts)\n  end\nend\n"
  },
  {
    "path": "apps/omg_eth/lib/omg_eth/transaction.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Eth.Transaction do\n  @moduledoc \"\"\"\n  An interface to Ethereum client transact function.\n  \"\"\"\n  require Logger\n  alias OMG.Eth.Encoding\n\n  @doc \"\"\"\n  Send transaction to be singed by a key managed by Ethereum node, geth or parity.\n  For geth, account must be unlocked externally.\n  If using parity, account passphrase must be provided directly or via config.\n  \"\"\"\n  @spec send(:infura, binary()) :: {:ok, OMG.Eth.hash()} | {:error, any()}\n  @spec send(atom(), map()) :: {:ok, OMG.Eth.hash()} | {:error, any()}\n  def send(backend, txmap) do\n    transact(backend, txmap)\n  end\n\n  defp transact(:geth, txmap) do\n    eth_send_transaction = Ethereumex.HttpClient.eth_send_transaction(txmap)\n\n    case eth_send_transaction do\n      {:ok, receipt_enc} -> {:ok, Encoding.from_hex(receipt_enc)}\n      other -> other\n    end\n  end\n\n  defp transact(:infura, transaction_data) do\n    case Ethereumex.HttpClient.eth_send_raw_transaction(transaction_data) do\n      {:ok, receipt_enc} -> {:ok, Encoding.from_hex(receipt_enc)}\n      other -> other\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_eth/mix.exs",
    "content": "defmodule OMG.Eth.MixProject do\n  use Mix.Project\n\n  require Logger\n\n  def project() do\n    [\n      app: :omg_eth,\n      version: version(),\n      build_path: \"../../_build\",\n      config_path: \"../../config/config.exs\",\n      deps_path: \"../../deps\",\n      lockfile: \"../../mix.lock\",\n      elixir: \"~> 1.8\",\n      elixirc_paths: elixirc_paths(Mix.env()),\n      start_permanent: Mix.env() == :prod,\n      deps: deps(),\n      test_coverage: [tool: ExCoveralls]\n    ]\n  end\n\n  def application() do\n    [\n      mod: {OMG.Eth.Application, []},\n      start_phases: [{:attach_telemetry, []}],\n      extra_applications: [:sasl, :logger, :ex_plasma, :ex_rlp]\n    ]\n  end\n\n  defp version() do\n    \"git\"\n    |> System.cmd([\"describe\", \"--tags\", \"--abbrev=0\"])\n    |> elem(0)\n    |> String.replace(\"v\", \"\")\n    |> String.replace(\"\\n\", \"\")\n  end\n\n  # Specifies which paths to compile per environment.\n  # :dev compiles `test/support` to gain access to various `Support.*` helpers\n  defp elixirc_paths(:prod), do: [\"lib\"]\n  defp elixirc_paths(:dev), do: [\"lib\", \"test/support\"]\n  defp elixirc_paths(:test), do: [\"lib\", \"test/support\"]\n\n  defp deps() do\n    [\n      {:ex_abi, \"~> 0.5.1\"},\n      {:ethereumex, \"~> 0.6.0\"},\n      {:ex_secp256k1, \"~> 0.1.2\"},\n      # Umbrella\n      {:omg_bus, in_umbrella: true},\n      {:omg_status, in_umbrella: true},\n      {:omg_utils, in_umbrella: true},\n      {:omg_db, in_umbrella: true},\n      # TEST ONLY\n      {:exexec, \"~> 0.2.0\", only: [:dev, :test]},\n      {:briefly, \"~> 0.3.0\", only: [:dev, :test]}\n    ]\n  end\nend\n"
  },
  {
    "path": "apps/omg_eth/test/fixtures.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Eth.Fixtures do\n  @moduledoc \"\"\"\n  Contains fixtures for tests that require geth and contract\n  \"\"\"\n  use ExUnitFixtures.FixtureModule\n\n  alias OMG.Eth.Configuration\n  alias OMG.Eth.Encoding\n  alias Support.DevHelper\n  alias Support.DevNode\n  alias Support.RootChainHelper\n  alias Support.SnapshotContracts\n\n  @test_eth_vault_id 1\n  @test_erc20_vault_id 2\n\n  deffixture eth_node do\n    case System.get_env(\"DOCKER_GETH\") do\n      nil ->\n        if Application.get_env(:omg_eth, :run_test_eth_dev_node, true) do\n          {:ok, exit_fn} = DevNode.start()\n\n          on_exit(exit_fn)\n        end\n\n        :ok\n\n      _ ->\n        :ok\n    end\n  end\n\n  deffixture contract(eth_node) do\n    :ok = eth_node\n\n    {:ok, true} =\n      Ethereumex.HttpClient.request(\"personal_unlockAccount\", [\"0x6de4b3b9c28e9c3e84c2b2d3a875c947a84de68d\", \"\", 0], [])\n\n    add_exit_queue = RootChainHelper.add_exit_queue(@test_eth_vault_id, \"0x0000000000000000000000000000000000000000\")\n\n    {:ok, %{\"status\" => _}} = Support.DevHelper.transact_sync!(add_exit_queue)\n\n    :ok\n  end\n\n  deffixture token(contract) do\n    :ok = contract\n    contracts = SnapshotContracts.parse_contracts()\n    token_addr = contracts[\"CONTRACT_ERC20_MINTABLE\"]\n\n    # ensuring that the root chain contract handles token_addr\n    {:ok, _} = has_exit_queue(@test_erc20_vault_id, token_addr)\n    {:ok, _} = DevHelper.transact_sync!(RootChainHelper.add_exit_queue(@test_erc20_vault_id, token_addr))\n    {:ok, true} = has_exit_queue(@test_erc20_vault_id, token_addr)\n\n    token_addr\n  end\n\n  defp has_exit_queue(vault_id, token) do\n    plasma_framework = Configuration.contracts().plasma_framework\n    token = Encoding.from_hex(token, :mixed)\n    call_contract(plasma_framework, \"hasExitQueue(uint256,address)\", [vault_id, token], [:bool])\n  end\n\n  defp call_contract(contract, signature, args, return_types) do\n    data = ABI.encode(signature, args)\n    {:ok, return} = Ethereumex.HttpClient.eth_call(%{from: contract, to: contract, data: Encoding.to_hex(data)})\n    decode_answer(return, return_types)\n  end\n\n  defp decode_answer(enc_return, return_types) do\n    single_return =\n      enc_return\n      |> Encoding.from_hex()\n      |> ABI.TypeDecoder.decode(return_types)\n      |> hd()\n\n    {:ok, single_return}\n  end\nend\n"
  },
  {
    "path": "apps/omg_eth/test/omg_eth/application_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Eth.ApplicationTest do\n  use ExUnit.Case, async: false\n  import ExUnit.CaptureLog, only: [capture_log: 1]\n  alias OMG.DB\n  alias OMG.Eth.Configuration\n\n  setup do\n    db_path = Briefly.create!(directory: true)\n    Application.put_env(:omg_db, :path, db_path, persistent: true)\n    :ok = DB.init()\n    {:ok, apps} = Application.ensure_all_started(:omg_eth)\n\n    on_exit(fn ->\n      contracts_hash = DB.get_single_value(:omg_eth_contracts)\n      :ok = DB.multi_update([{:delete, :omg_eth_contracts, contracts_hash}])\n      apps |> Enum.reverse() |> Enum.each(&Application.stop/1)\n    end)\n\n    {:ok, %{apps: apps}}\n  end\n\n  describe \"valid_contracts/0\" do\n    test \"if contracts hash is persisted when application starts\" do\n      contracts_hash =\n        Configuration.contracts()\n        |> Map.put(:txhash_contract, Configuration.txhash_contract())\n        # authority_addr to keep backwards compatibility\n        |> Map.put(:authority_addr, Configuration.authority_address())\n        |> :erlang.phash2()\n\n      assert DB.get_single_value(:omg_eth_contracts) == {:ok, contracts_hash}\n    end\n\n    test \"that if contracts change boot is not permitted\", %{apps: apps} do\n      contracts_hash =\n        Configuration.contracts()\n        |> Map.put(:txhash_contract, Configuration.txhash_contract())\n        # authority_addr to keep backwards compatibility\n        |> Map.put(:authority_addr, Configuration.authority_address())\n        |> :erlang.phash2()\n\n      assert DB.get_single_value(:omg_eth_contracts) == {:ok, contracts_hash}\n\n      apps |> Enum.reverse() |> Enum.each(&Application.stop/1)\n      contracts = Configuration.contracts()\n      Application.put_env(:omg_eth, :contract_addr, %{\"test\" => \"test\"})\n\n      assert capture_log(fn ->\n               assert Application.ensure_all_started(:omg_eth) ==\n                        {:error,\n                         {:omg_eth,\n                          {:bad_return, {{OMG.Eth.Application, :start, [:normal, []]}, {:EXIT, :contracts_missmatch}}}}}\n             end) =~ \"[error]\"\n\n      Application.put_env(:omg_eth, :contract_addr, contracts)\n      {:ok, _} = Application.ensure_all_started(:omg_eth)\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_eth/test/omg_eth/blockchain/bit_helper_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Eth.Blockchain.BitHelperTest do\n  use ExUnit.Case, async: true\n  doctest OMG.Eth.Blockchain.BitHelper\nend\n"
  },
  {
    "path": "apps/omg_eth/test/omg_eth/blockchain/transaction/hash_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Eth.Blockchain.Transaction.HashTest do\n  use ExUnit.Case, async: true\n  doctest OMG.Eth.Blockchain.Transaction.Hash\nend\n"
  },
  {
    "path": "apps/omg_eth/test/omg_eth/blockchain/transaction/signature_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Eth.Blockchain.Transaction.SignatureTest do\n  use ExUnit.Case, async: true\n  doctest OMG.Eth.Blockchain.Transaction.Signature\nend\n"
  },
  {
    "path": "apps/omg_eth/test/omg_eth/blockchain/transaction_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Eth.Blockchain.TransactionTest do\n  use ExUnit.Case, async: true\n  doctest OMG.Eth.Blockchain.Transaction\nend\n"
  },
  {
    "path": "apps/omg_eth/test/omg_eth/client_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Eth.ClientTest do\n  use ExUnit.Case, async: true\n  alias OMG.Eth.Client\n\n  test \"get_ethereum_height/0 returns the block number\", %{test: test_name} do\n    defmodule test_name do\n      def eth_block_number() do\n        {:ok, \"0xfc\"}\n      end\n    end\n\n    {:ok, number} = Client.get_ethereum_height(test_name)\n    assert is_integer(number)\n  end\n\n  test \"node_ready/0 returns not ready\", %{test: test_name} do\n    defmodule test_name do\n      def eth_syncing() do\n        {:ok, true}\n      end\n    end\n\n    assert Client.node_ready(test_name) == {:error, :geth_still_syncing}\n  end\n\n  test \"node_ready/0 returns ready\", %{test: test_name} do\n    defmodule test_name do\n      def eth_syncing() do\n        {:ok, false}\n      end\n    end\n\n    assert Client.node_ready(test_name) == :ok\n  end\nend\n"
  },
  {
    "path": "apps/omg_eth/test/omg_eth/encoding/contract_constructor_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Eth.Encoding.ContractConstructorTest do\n  use ExUnit.Case, async: true\n\n  alias OMG.Eth.Encoding.ContractConstructor\n\n  @moduletag :common\n\n  doctest ContractConstructor\n\n  describe \"extract_params/1\" do\n    test \"returns a tuple with empty lists when given an empty list\" do\n      encoded = ContractConstructor.extract_params([])\n      assert encoded == {[], []}\n    end\n\n    test \"returns the correct list of types and values when given a list of elementary types\" do\n      params = [\n        {:address, \"0x1234\"},\n        {{:uint, 256}, 1000},\n        {{:uint, 256}, 2000},\n        {:bool, true}\n      ]\n\n      encoded = ContractConstructor.extract_params(params)\n\n      assert encoded == {\n               [:address, {:uint, 256}, {:uint, 256}, :bool],\n               [\"0x1234\", 1000, 2000, true]\n             }\n    end\n\n    test \"returns the correct list of types and values when given a list with one tuple\" do\n      params = [\n        {:tuple,\n         [\n           {:address, \"0x1234\"},\n           {{:uint, 256}, 1000},\n           {:bool, true}\n         ]}\n      ]\n\n      encoded = ContractConstructor.extract_params(params)\n\n      assert encoded == {\n               [{:tuple, [:address, {:uint, 256}, :bool]}],\n               [{\"0x1234\", 1000, true}]\n             }\n    end\n\n    test \"returns the correct list of types and values when given a list of tuples\" do\n      params = [\n        {:tuple,\n         [\n           {:address, \"0x1234\"},\n           {{:uint, 256}, 1000},\n           {:bool, true}\n         ]},\n        {:tuple,\n         [\n           {{:uint, 128}, 2000},\n           {:bool, false}\n         ]}\n      ]\n\n      encoded = ContractConstructor.extract_params(params)\n\n      assert encoded == {\n               [{:tuple, [:address, {:uint, 256}, :bool]}, {:tuple, [{:uint, 128}, :bool]}],\n               [{\"0x1234\", 1000, true}, {2000, false}]\n             }\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_eth/test/omg_eth/encoding_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Eth.EncodingTest do\n  use ExUnit.Case, async: true\n\n  alias OMG.Eth.Encoding\n\n  @moduletag :common\n\n  doctest Encoding\n\n  describe \"encode_constructor_params/1\" do\n    test \"encoding an empty list of params returns an empty string\" do\n      assert Encoding.encode_constructor_params([]) == \"\"\n    end\n\n    test \"returns a valid base16 string when given a list of elementary types\" do\n      params = [\n        {:address, \"0x1234\"},\n        {{:uint, 256}, 1000},\n        {{:uint, 256}, 2000},\n        {:bool, true}\n      ]\n\n      encoded = Encoding.encode_constructor_params(params)\n\n      # This function mainly does encoding via `Elixir.Base` and `ABI.TypeEncoder`,\n      # so we'll assert just the expected format, but not the content.\n      assert {:ok, _} = Base.decode16(encoded, case: :lower)\n    end\n\n    test \"returns the correct list of types and values when given a list with one tuple\" do\n      params = [\n        {:tuple,\n         [\n           {:address, \"0x1234\"},\n           {{:uint, 256}, 1000},\n           {:bool, true}\n         ]}\n      ]\n\n      encoded = Encoding.encode_constructor_params(params)\n\n      # This function mainly does encoding via `Elixir.Base` and `ABI.TypeEncoder`,\n      # so we'll assert just the expected format, but not the content.\n      assert {:ok, _} = Base.decode16(encoded, case: :lower)\n    end\n\n    test \"returns the correct list of types and values when given a list of tuples\" do\n      params = [\n        {:tuple,\n         [\n           {:address, \"0x1234\"},\n           {{:uint, 256}, 1000},\n           {:bool, true}\n         ]},\n        {:tuple,\n         [\n           {{:uint, 128}, 2000},\n           {:bool, false}\n         ]}\n      ]\n\n      encoded = Encoding.encode_constructor_params(params)\n\n      # This function mainly does encoding via `Elixir.Base` and `ABI.TypeEncoder`,\n      # so we'll assert just the expected format, but not the content.\n      assert {:ok, _} = Base.decode16(encoded, case: :lower)\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_eth/test/omg_eth/eth_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.EthTest do\n  @moduledoc \"\"\"\n  Thin smoke test of the Ethereum port/adapter.\n  The purpose of this test to only prod the marshalling and calling functionalities of the `Eth` wrapper.\n  This shouldn't test the contract and should rely as little as possible on the contract logic.\n  `OMG.Eth` is intended to be as thin and deprived of own logic as possible, to not require extensive testing.\n\n  \"\"\"\n  use ExUnit.Case, async: false\n\n  alias OMG.Eth\n  alias OMG.Eth.Configuration\n  alias Support.DevHelper\n\n  @moduletag :common\n\n  setup_all do\n    {:ok, exit_fn} = Support.DevNode.start()\n    authority_address = Configuration.authority_address()\n    {:ok, true} = Ethereumex.HttpClient.request(\"personal_unlockAccount\", [authority_address, \"\", 0], [])\n\n    on_exit(exit_fn)\n    :ok\n  end\n\n  test \"get_block_timestamp_by_number/1 the block timestamp by block number\" do\n    {:ok, timestamp} = Eth.get_block_timestamp_by_number(2)\n    assert is_integer(timestamp)\n  end\n\n  test \"submit_block/1 submits a block to the contract\" do\n    response = Eth.submit_block(<<234::256>>, 1, 20_000_000_000)\n\n    assert {:ok, _} = DevHelper.transact_sync!(response)\n  end\nend\n"
  },
  {
    "path": "apps/omg_eth/test/omg_eth/ethereum_height_monitor_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Eth.EthereumHeightMonitorTest do\n  # async:false since `eth_integration_module` is being overridden\n  use ExUnit.Case, async: false\n  alias __MODULE__.EthereumClientMock\n  alias OMG.Eth.EthereumHeightMonitor\n  alias OMG.Status.Alert.Alarm\n\n  @moduletag :capture_log\n\n  setup_all do\n    _ = Agent.start_link(fn -> 55_555 end, name: :port_holder)\n    {:ok, status_apps} = Application.ensure_all_started(:omg_status)\n    {:ok, bus_apps} = Application.ensure_all_started(:omg_bus)\n    apps = status_apps ++ bus_apps\n\n    {:ok, _} = EthereumClientMock.start_link()\n\n    on_exit(fn ->\n      _ = apps |> Enum.reverse() |> Enum.each(&Application.stop/1)\n    end)\n  end\n\n  setup do\n    check_interval_ms = 10\n    stall_threshold_ms = 100\n\n    {:ok, monitor} =\n      EthereumHeightMonitor.start_link(\n        check_interval_ms: check_interval_ms,\n        stall_threshold_ms: stall_threshold_ms,\n        eth_module: EthereumClientMock,\n        alarm_module: Alarm,\n        event_bus_module: OMG.Bus\n      )\n\n    _ = Alarm.clear_all()\n\n    _ =\n      on_exit(fn ->\n        _ = EthereumClientMock.reset_state()\n        _ = Process.sleep(10)\n        true = Process.exit(monitor, :kill)\n      end)\n\n    {:ok,\n     %{\n       monitor: monitor,\n       check_interval_ms: check_interval_ms,\n       stall_threshold_ms: stall_threshold_ms\n     }}\n  end\n\n  #\n  # Internal event publishing\n  #\n\n  test \"that an ethereum_new_height event is published when the height increases\", context do\n    _ = EthereumClientMock.set_stalled(false)\n\n    {:ok, listener} = __MODULE__.EventBusListener.start(self())\n    on_exit(fn -> GenServer.stop(listener) end)\n\n    assert_receive(:got_ethereum_new_height, Kernel.trunc(context.check_interval_ms * 10))\n  end\n\n  #\n  # Connection error\n  #\n\n  test \"that the connection alarm gets raised when connection becomes unhealthy\" do\n    # Initialize as healthy and alarm not present\n    _ = EthereumClientMock.set_faulty_response(false)\n    :ok = pull_client_alarm([], 100)\n\n    # Toggle faulty response\n    _ = EthereumClientMock.set_faulty_response(true)\n\n    # Assert the alarm and event are present\n    assert pull_client_alarm(\n             [ethereum_connection_error: %{node: :nonode@nohost, reporter: OMG.Eth.EthereumHeightMonitor}],\n             100\n           ) == :ok\n  end\n\n  test \"that the connection alarm gets cleared when connection becomes healthy\" do\n    # Initialize as unhealthy\n    _ = EthereumClientMock.set_faulty_response(true)\n\n    :ok =\n      pull_client_alarm(\n        [ethereum_connection_error: %{node: :nonode@nohost, reporter: OMG.Eth.EthereumHeightMonitor}],\n        100\n      )\n\n    # Toggle healthy response\n    _ = EthereumClientMock.set_faulty_response(false)\n\n    # Assert the alarm and event are no longer present\n    assert pull_client_alarm([], 100) == :ok\n  end\n\n  #\n  # Stalling sync\n  #\n\n  test \"that the stall alarm gets raised when block height stalls\" do\n    # Initialize as healthy and alarm not present\n    _ = EthereumClientMock.set_stalled(false)\n    :ok = pull_client_alarm([], 200)\n\n    # Toggle stalled height\n    _ = EthereumClientMock.set_stalled(true)\n\n    # Assert alarm now present\n    assert pull_client_alarm(\n             [ethereum_stalled_sync: %{node: :nonode@nohost, reporter: OMG.Eth.EthereumHeightMonitor}],\n             200\n           ) == :ok\n  end\n\n  test \"that the stall alarm gets cleared when block height unstalls\" do\n    # Initialize as unhealthy\n    _ = EthereumClientMock.set_stalled(true)\n\n    :ok =\n      pull_client_alarm([ethereum_stalled_sync: %{node: :nonode@nohost, reporter: OMG.Eth.EthereumHeightMonitor}], 300)\n\n    # Toggle unstalled height\n    _ = EthereumClientMock.set_stalled(false)\n\n    # Assert alarm no longer present\n    assert pull_client_alarm([], 300) == :ok\n  end\n\n  defp pull_client_alarm(_, 0), do: {:cant_match, Alarm.all()}\n\n  defp pull_client_alarm(match, n) do\n    case Alarm.all() do\n      ^match ->\n        :ok\n\n      _ ->\n        Process.sleep(50)\n        pull_client_alarm(match, n - 1)\n    end\n  end\n\n  #\n  # Test submodules\n  #\n\n  defmodule EthereumClientMock do\n    @moduledoc \"\"\"\n    Mocking the ETH module integration point.\n    \"\"\"\n    use GenServer\n\n    @initial_state %{height: 0, faulty: false, stalled: false}\n\n    def start_link(), do: GenServer.start_link(__MODULE__, [], name: __MODULE__)\n\n    def get_ethereum_height(), do: GenServer.call(__MODULE__, :get_ethereum_height)\n\n    def set_faulty_response(faulty), do: GenServer.call(__MODULE__, {:set_faulty_response, faulty})\n\n    def set_long_response(milliseconds), do: GenServer.call(__MODULE__, {:set_long_response, milliseconds})\n\n    def set_stalled(stalled), do: GenServer.call(__MODULE__, {:set_stalled, stalled})\n\n    def reset_state(), do: GenServer.call(__MODULE__, :reset_state)\n\n    def stop(), do: GenServer.stop(__MODULE__, :normal)\n\n    def init(_), do: {:ok, @initial_state}\n\n    def handle_call(:reset_state, _, _state), do: {:reply, :ok, @initial_state}\n\n    def handle_call({:set_faulty_response, true}, _, state), do: {:reply, :ok, %{state | faulty: true}}\n    def handle_call({:set_faulty_response, false}, _, state), do: {:reply, :ok, %{state | faulty: false}}\n\n    def handle_call({:set_long_response, milliseconds}, _, state) do\n      {:reply, :ok, Map.merge(%{long_response: milliseconds}, state)}\n    end\n\n    def handle_call({:set_stalled, true}, _, state), do: {:reply, :ok, %{state | stalled: true}}\n    def handle_call({:set_stalled, false}, _, state), do: {:reply, :ok, %{state | stalled: false}}\n\n    # Heights management\n\n    def handle_call(:get_ethereum_height, _, %{faulty: true} = state) do\n      {:reply, :error, state}\n    end\n\n    def handle_call(:get_ethereum_height, _, %{long_response: milliseconds} = state) when not is_nil(milliseconds) do\n      _ = Process.sleep(milliseconds)\n      {:reply, {:ok, state.height}, %{state | height: next_height(state.height, state.stalled)}}\n    end\n\n    def handle_call(:get_ethereum_height, _, state) do\n      {:reply, {:ok, state.height}, %{state | height: next_height(state.height, state.stalled)}}\n    end\n\n    defp next_height(height, false), do: height + 1\n    defp next_height(height, true), do: height\n  end\n\n  defmodule EventBusListener do\n    use GenServer\n\n    def start(parent), do: GenServer.start(__MODULE__, parent)\n\n    def init(parent) do\n      :ok = OMG.Bus.subscribe({:root_chain, \"ethereum_new_height\"}, link: true)\n      {:ok, parent}\n    end\n\n    def handle_info({:internal_event_bus, :ethereum_new_height, _height}, parent) do\n      _ = send(parent, :got_ethereum_new_height)\n      {:noreply, parent}\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_eth/test/omg_eth/release_tasks/set_contract_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Eth.ReleaseTasks.SetContractTest do\n  use ExUnit.Case, async: true\n\n  alias OMG.Eth.ReleaseTasks.SetContract\n\n  setup_all do\n    plasma_framework = Support.SnapshotContracts.parse_contracts()[\"CONTRACT_ADDRESS_PLASMA_FRAMEWORK\"]\n\n    contract_addresses_value = %{\n      plasma_framework: plasma_framework\n    }\n\n    %{\n      contract_addresses_value: contract_addresses_value,\n      plasma_framework: plasma_framework\n    }\n  end\n\n  setup %{} do\n    on_exit(fn ->\n      :ok = System.delete_env(\"ETHEREUM_NETWORK\")\n      :ok = System.delete_env(\"CONTRACT_EXCHANGER_URL\")\n      :ok = System.delete_env(\"ETHEREUM_NETWORK\")\n      :ok = System.delete_env(\"TXHASH_CONTRACT\")\n      :ok = System.delete_env(\"AUTHORITY_ADDRESS\")\n      :ok = System.delete_env(\"CONTRACT_ADDRESS_PLASMA_FRAMEWORK\")\n    end)\n\n    :ok\n  end\n\n  test \"fetching from contract exchanger\", %{\n    contract_addresses_value: contract_addresses_value\n  } do\n    port = 9009\n    pid = spawn(fn -> start(port) end)\n    :ok = System.put_env(\"CONTRACT_EXCHANGER_URL\", \"http://localhost:#{port}\")\n    :ok = System.put_env(\"ETHEREUM_NETWORK\", \"RINKEBY\")\n    config = SetContract.load([], rpc_api: __MODULE__.Rpc)\n    authority_address = config |> Keyword.fetch!(:omg_eth) |> Keyword.fetch!(:authority_address)\n    assert authority_address == \"authority_address_value\"\n\n    plasma_framework = config |> Keyword.get(:omg_eth) |> Keyword.fetch!(:contract_addr) |> Map.get(:plasma_framework)\n    assert plasma_framework == contract_addresses_value.plasma_framework\n\n    txhash_contract_value = config |> Keyword.get(:omg_eth) |> Keyword.fetch!(:txhash_contract)\n    assert txhash_contract_value == \"txhash_contract_value\"\n\n    :ok = Process.send(pid, :stop, [])\n  end\n\n  test \"fetching from contract exchanger sets default exit period seconds\" do\n    port = 9010\n    _pid = spawn(fn -> start(port) end)\n    :ok = System.put_env(\"CONTRACT_EXCHANGER_URL\", \"http://localhost:#{port}\")\n    :ok = System.put_env(\"ETHEREUM_NETWORK\", \"RINKEBY\")\n    config = SetContract.load([], rpc_api: __MODULE__.Rpc)\n    min_exit_period_seconds = config |> Keyword.get(:omg_eth) |> Keyword.fetch!(:min_exit_period_seconds)\n    assert min_exit_period_seconds == 20\n  end\n\n  test \"unsuported network throws exception for contract exchanger\" do\n    port = 9011\n    pid = spawn(fn -> start(port) end)\n    :ok = System.put_env(\"CONTRACT_EXCHANGER_URL\", \"http://localhost:#{port}\")\n    :ok = System.put_env(\"ETHEREUM_NETWORK\", \"RINKEBY-GORLI\")\n    assert catch_exit(SetContract.load([], rpc_api: __MODULE__.Rpc))\n    :ok = Process.send(pid, :stop, [])\n  end\n\n  test \"contract details from env\", %{\n    plasma_framework: plasma_framework,\n    contract_addresses_value: contract_addresses_value\n  } do\n    :ok = System.put_env(\"ETHEREUM_NETWORK\", \"rinkeby\")\n    :ok = System.put_env(\"TXHASH_CONTRACT\", \"txhash_contract_value\")\n    :ok = System.put_env(\"AUTHORITY_ADDRESS\", \"authority_address_value\")\n    :ok = System.put_env(\"CONTRACT_ADDRESS_PLASMA_FRAMEWORK\", plasma_framework)\n    config = SetContract.load([], rpc_api: __MODULE__.Rpc)\n    authority_address = config |> Keyword.fetch!(:omg_eth) |> Keyword.fetch!(:authority_address)\n    assert authority_address == \"authority_address_value\"\n\n    plasma_framework = config |> Keyword.get(:omg_eth) |> Keyword.fetch!(:contract_addr) |> Map.get(:plasma_framework)\n    assert plasma_framework == contract_addresses_value.plasma_framework\n\n    txhash_contract_value = config |> Keyword.get(:omg_eth) |> Keyword.fetch!(:txhash_contract)\n    assert txhash_contract_value == \"txhash_contract_value\"\n  end\n\n  test \"contract details from env, mixed case\", %{\n    plasma_framework: plasma_framework,\n    contract_addresses_value: contract_addresses_value\n  } do\n    :ok = System.put_env(\"ETHEREUM_NETWORK\", \"rinkeby\")\n    :ok = System.put_env(\"TXHASH_CONTRACT\", \"Txhash_contract_value\")\n    :ok = System.put_env(\"AUTHORITY_ADDRESS\", \"Authority_address_value\")\n    :ok = System.put_env(\"CONTRACT_ADDRESS_PLASMA_FRAMEWORK\", plasma_framework)\n\n    config = SetContract.load([], rpc_api: __MODULE__.Rpc)\n    authority_address = config |> Keyword.fetch!(:omg_eth) |> Keyword.fetch!(:authority_address)\n    assert authority_address == \"authority_address_value\"\n\n    plasma_framework = config |> Keyword.get(:omg_eth) |> Keyword.fetch!(:contract_addr) |> Map.get(:plasma_framework)\n    assert plasma_framework == contract_addresses_value.plasma_framework\n\n    txhash_contract_value = config |> Keyword.get(:omg_eth) |> Keyword.fetch!(:txhash_contract)\n    assert txhash_contract_value == \"txhash_contract_value\"\n  end\n\n  test \"contract details from env for localchain\", %{\n    plasma_framework: plasma_framework,\n    contract_addresses_value: contract_addresses_value\n  } do\n    :ok = System.put_env(\"ETHEREUM_NETWORK\", \"localchain\")\n    :ok = System.put_env(\"TXHASH_CONTRACT\", \"txhash_contract_value\")\n    :ok = System.put_env(\"AUTHORITY_ADDRESS\", \"authority_address_value\")\n    :ok = System.put_env(\"CONTRACT_ADDRESS_PLASMA_FRAMEWORK\", plasma_framework)\n\n    config = SetContract.load([], rpc_api: __MODULE__.Rpc)\n    authority_address = config |> Keyword.fetch!(:omg_eth) |> Keyword.fetch!(:authority_address)\n    assert authority_address == \"authority_address_value\"\n\n    plasma_framework = config |> Keyword.get(:omg_eth) |> Keyword.fetch!(:contract_addr) |> Map.get(:plasma_framework)\n    assert plasma_framework == contract_addresses_value.plasma_framework\n\n    txhash_contract_value = config |> Keyword.get(:omg_eth) |> Keyword.fetch!(:txhash_contract)\n    assert txhash_contract_value == \"txhash_contract_value\"\n  end\n\n  test \"contract details from env sets default exit period seconds\", %{\n    plasma_framework: plasma_framework\n  } do\n    :ok = System.put_env(\"ETHEREUM_NETWORK\", \"rinkeby\")\n    :ok = System.put_env(\"TXHASH_CONTRACT\", \"txhash_contract_value\")\n    :ok = System.put_env(\"AUTHORITY_ADDRESS\", \"authority_address_value\")\n    :ok = System.put_env(\"CONTRACT_ADDRESS_PLASMA_FRAMEWORK\", plasma_framework)\n\n    config = SetContract.load([], rpc_api: __MODULE__.Rpc)\n    min_exit_period_seconds = config |> Keyword.get(:omg_eth) |> Keyword.fetch!(:min_exit_period_seconds)\n    assert min_exit_period_seconds == 20\n  end\n\n  test \"contract details and exit period seconds from env\", %{\n    plasma_framework: plasma_framework,\n    contract_addresses_value: contract_addresses_value\n  } do\n    :ok = System.put_env(\"ETHEREUM_NETWORK\", \"rinkeby\")\n    :ok = System.put_env(\"TXHASH_CONTRACT\", \"txhash_contract_value\")\n    :ok = System.put_env(\"AUTHORITY_ADDRESS\", \"authority_address_value\")\n    :ok = System.put_env(\"CONTRACT_ADDRESS_PLASMA_FRAMEWORK\", plasma_framework)\n\n    config = SetContract.load([], rpc_api: __MODULE__.Rpc)\n    min_exit_period_seconds = config |> Keyword.get(:omg_eth) |> Keyword.fetch!(:min_exit_period_seconds)\n    assert min_exit_period_seconds == 20\n\n    authority_address = config |> Keyword.fetch!(:omg_eth) |> Keyword.fetch!(:authority_address)\n    assert authority_address == \"authority_address_value\"\n\n    plasma_framework = config |> Keyword.get(:omg_eth) |> Keyword.fetch!(:contract_addr) |> Map.get(:plasma_framework)\n    assert plasma_framework == contract_addresses_value.plasma_framework\n\n    txhash_contract_value = config |> Keyword.get(:omg_eth) |> Keyword.fetch!(:txhash_contract)\n    assert txhash_contract_value == \"txhash_contract_value\"\n  end\n\n  test \"that exit is thrown when env configuration is faulty for network name\", %{\n    plasma_framework: plasma_framework\n  } do\n    :ok = System.put_env(\"ETHEREUM_NETWORK\", \"rinkeby is what we are, rinkeby is what we know\")\n    :ok = System.put_env(\"TXHASH_CONTRACT\", \"txhash_contract_value\")\n    :ok = System.put_env(\"AUTHORITY_ADDRESS\", \"authority_address_value\")\n    :ok = System.put_env(\"CONTRACT_ADDRESS_PLASMA_FRAMEWORK\", plasma_framework)\n    assert catch_exit(SetContract.load([], rpc_api: __MODULE__.Rpc))\n  end\n\n  test \"that exit is thrown when there's no mandatory configuration\" do\n    :ok = System.delete_env(\"ETHEREUM_NETWORK\")\n    :ok = System.delete_env(\"TXHASH_CONTRACT\")\n    :ok = System.delete_env(\"AUTHORITY_ADDRESS\")\n    :ok = System.delete_env(\"CONTRACT_ADDRESS_PLASMA_FRAMEWORK\")\n    :ok = System.delete_env(\"CONTRACT_EXCHANGER_URL\")\n    assert catch_exit(SetContract.load([], rpc_api: __MODULE__.Rpc))\n  end\n\n  # a very simple web server that serves conctract exchanger requests\n  defp start(port) do\n    {:ok, sock} = :gen_tcp.listen(port, [{:active, false}])\n    spawn(fn -> loop(sock) end)\n\n    receive do\n      :stop ->\n        :gen_tcp.close(sock)\n    end\n  end\n\n  defp loop(sock) do\n    case :gen_tcp.accept(sock) do\n      {:ok, conn} ->\n        handler = spawn(fn -> handle(conn) end)\n        :gen_tcp.controlling_process(conn, handler)\n        loop(sock)\n\n      _ ->\n        :ok\n    end\n  end\n\n  defp handle(conn) do\n    plasma_framework = Support.SnapshotContracts.parse_contracts()[\"CONTRACT_ADDRESS_PLASMA_FRAMEWORK\"]\n\n    exchanger_body = %{\n      plasma_framework_tx_hash: \"txhash_contract_value\",\n      plasma_framework: nil,\n      authority_address: \"authority_address_value\"\n    }\n\n    body = exchanger_body |> Map.put(:plasma_framework, plasma_framework) |> Jason.encode!()\n\n    :ok = :gen_tcp.send(conn, [\"HTTP/1.0 \", Integer.to_charlist(200), \"\\r\\n\", [], \"\\r\\n\", body])\n\n    :gen_tcp.close(conn)\n  end\n\n  defmodule Rpc do\n    def call_contract(_, \"vaults(uint256)\", _) do\n      {:ok, \"0x0000000000000000000000004e3aeff70f022a6d4cc5947423887e7152826cf7\"}\n    end\n\n    def call_contract(_, \"exitGames(uint256)\", _) do\n      {:ok,\n       \"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"}\n    end\n\n    def call_contract(_, \"childBlockInterval()\", _) do\n      {:ok, \"0x00000000000000000000000000000000000000000000000000000000000003e8\"}\n    end\n\n    def call_contract(_, \"getVersion()\", _) do\n      {:ok,\n       \"0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d312e302e342b6136396337363300000000000000000000000000000000000000\"}\n    end\n\n    def call_contract(_, \"minExitPeriod()\", _) do\n      {:ok, \"0x0000000000000000000000000000000000000000000000000000000000000014\"}\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_eth/test/omg_eth/release_tasks/set_ethereum_block_time_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Eth.ReleaseTasks.SetEthereumBlockTimeTest do\n  use ExUnit.Case, async: true\n  alias OMG.Eth.ReleaseTasks.SetEthereumBlockTime\n\n  @app :omg_eth\n  @env_key \"ETHEREUM_BLOCK_TIME_SECONDS\"\n  @config_key :ethereum_block_time_seconds\n\n  test \"that block time is set when the env var is present\" do\n    :ok = System.put_env(@env_key, \"1234\")\n    config = SetEthereumBlockTime.load([], [])\n    ethereum_block_time_seconds = config |> Keyword.fetch!(@app) |> Keyword.fetch!(@config_key)\n    assert ethereum_block_time_seconds == 1234\n    :ok = System.delete_env(@env_key)\n  end\n\n  test \"that the default config is used when the env var is not set\" do\n    old_config = Application.get_env(@app, @config_key)\n    :ok = System.delete_env(@env_key)\n    config = SetEthereumBlockTime.load([], [])\n    ethereum_block_time_seconds = config |> Keyword.fetch!(@app) |> Keyword.fetch!(@config_key)\n    assert ethereum_block_time_seconds == old_config\n  end\nend\n"
  },
  {
    "path": "apps/omg_eth/test/omg_eth/release_tasks/set_ethereum_client_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Eth.ReleaseTasks.SetEthereumClientTest do\n  use ExUnit.Case, async: true\n  alias OMG.Eth.ReleaseTasks.SetEthereumClient\n\n  @app :omg_eth\n\n  test \"if defaults are used when env vars are not set\" do\n    default_url = Application.get_env(:ethereumex, :url)\n    default_eth_node = Application.get_env(@app, :eth_node)\n    config = SetEthereumClient.load([], [])\n    eth_node = config |> Keyword.fetch!(@app) |> Keyword.fetch!(:eth_node)\n    url = config |> Keyword.fetch!(:ethereumex) |> Keyword.fetch!(:url)\n    assert url == default_url\n    assert eth_node == default_eth_node\n  end\n\n  test \"if values are used when env vars set\" do\n    :ok = System.put_env(\"ETHEREUM_RPC_URL\", \"url\")\n    :ok = System.put_env(\"ETH_NODE\", \"geth\")\n    config = SetEthereumClient.load([], [])\n    eth_node = config |> Keyword.fetch!(@app) |> Keyword.fetch!(:eth_node)\n    url = config |> Keyword.fetch!(:ethereumex) |> Keyword.fetch!(:url)\n    assert url == \"url\"\n    assert eth_node == :geth\n\n    :ok = System.put_env(\"ETH_NODE\", \"parity\")\n    config = SetEthereumClient.load([], [])\n    eth_node = config |> Keyword.fetch!(@app) |> Keyword.fetch!(:eth_node)\n    url = config |> Keyword.fetch!(:ethereumex) |> Keyword.fetch!(:url)\n    assert url == \"url\"\n    assert eth_node == :parity\n\n    :ok = System.put_env(\"ETH_NODE\", \"infura\")\n    config = SetEthereumClient.load([], [])\n    eth_node = config |> Keyword.fetch!(@app) |> Keyword.fetch!(:eth_node)\n    url = config |> Keyword.fetch!(:ethereumex) |> Keyword.fetch!(:url)\n    assert url == \"url\"\n    assert eth_node == :infura\n    # cleanup\n    :ok = System.delete_env(\"ETHEREUM_RPC_URL\")\n    :ok = System.delete_env(\"ETH_NODE\")\n  end\n\n  test \"if faulty eth node exits\" do\n    :ok = System.put_env(\"ETH_NODE\", \"random random random\")\n\n    assert catch_exit(SetEthereumClient.load([], []))\n    :ok = System.delete_env(\"ETH_NODE\")\n  end\nend\n"
  },
  {
    "path": "apps/omg_eth/test/omg_eth/release_tasks/set_ethereum_events_check_interval_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Eth.ReleaseTasks.SetEthereumEventsCheckIntervalTest do\n  use ExUnit.Case, async: true\n  alias OMG.Eth.ReleaseTasks.SetEthereumEventsCheckInterval\n\n  @app :omg_eth\n  @env_key \"ETHEREUM_EVENTS_CHECK_INTERVAL_MS\"\n  @config_key :ethereum_events_check_interval_ms\n\n  test \"that interval is set when the env var is present\" do\n    :ok = System.put_env(@env_key, \"1234\")\n    config = SetEthereumEventsCheckInterval.load([], [])\n    ethereum_events_check_interval_ms = config |> Keyword.fetch!(@app) |> Keyword.fetch!(@config_key)\n    assert ethereum_events_check_interval_ms == 1234\n    :ok = System.delete_env(@env_key)\n  end\n\n  test \"that the default config is used when the env var is not set\" do\n    old_config = Application.get_env(@app, @config_key)\n    :ok = System.delete_env(@env_key)\n    config = SetEthereumEventsCheckInterval.load([], [])\n    ethereum_events_check_interval_ms = config |> Keyword.fetch!(@app) |> Keyword.fetch!(@config_key)\n    assert ethereum_events_check_interval_ms == old_config\n  end\nend\n"
  },
  {
    "path": "apps/omg_eth/test/omg_eth/release_tasks/set_ethereum_stalled_sync_threshold_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Eth.ReleaseTasks.SetEthereumStalledSyncThresholdTest do\n  use ExUnit.Case, async: true\n  alias OMG.Eth.ReleaseTasks.SetEthereumStalledSyncThreshold\n\n  @app :omg_eth\n  @env_key \"ETHEREUM_STALLED_SYNC_THRESHOLD_MS\"\n  @config_key :ethereum_stalled_sync_threshold_ms\n\n  test \"that interval is set when the env var is present\" do\n    :ok = System.put_env(@env_key, \"9999\")\n    config = SetEthereumStalledSyncThreshold.load([], [])\n    ethereum_stalled_sync_threshold_ms = config |> Keyword.fetch!(@app) |> Keyword.fetch!(@config_key)\n    assert ethereum_stalled_sync_threshold_ms == 9999\n    :ok = System.delete_env(@env_key)\n  end\n\n  test \"that the default config is used when the env var is not set\" do\n    old_config = Application.get_env(@app, @config_key)\n    :ok = System.delete_env(@env_key)\n    config = SetEthereumStalledSyncThreshold.load([], [])\n    ethereum_stalled_sync_threshold_ms = config |> Keyword.fetch!(@app) |> Keyword.fetch!(@config_key)\n    assert ethereum_stalled_sync_threshold_ms == old_config\n  end\nend\n"
  },
  {
    "path": "apps/omg_eth/test/omg_eth/root_chain/abi_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Eth.RootChain.AbiTest do\n  @moduledoc false\n\n  use ExUnit.Case, async: true\n  alias OMG.Eth.RootChain.Abi\n\n  test \"if deposit created event can be decoded from log\" do\n    deposit_created_log = %{\n      :event_signature => \"DepositCreated(address,uint256,address,uint256)\",\n      \"address\" => \"0x4e3aeff70f022a6d4cc5947423887e7152826cf7\",\n      \"blockHash\" => \"0xe5b0487de36b161f2d3e8c228ad4e1e84ab1ae25ca4d5ef53f9f03298ab3545f\",\n      \"blockNumber\" => \"0x186\",\n      \"data\" => \"0x000000000000000000000000000000000000000000000000000000000000000a\",\n      \"logIndex\" => \"0x0\",\n      \"removed\" => false,\n      \"topics\" => [\n        \"0x18569122d84f30025bb8dffb33563f1bdbfb9637f21552b11b8305686e9cb307\",\n        \"0x0000000000000000000000003b9f4c1dd26e0be593373b1d36cee2008cbeb837\",\n        \"0x0000000000000000000000000000000000000000000000000000000000000001\",\n        \"0x0000000000000000000000000000000000000000000000000000000000000000\"\n      ],\n      \"transactionHash\" => \"0x4d72a63ff42f1db50af2c36e8b314101d2fea3e0003575f30298e9153fe3d8ee\",\n      \"transactionIndex\" => \"0x0\"\n    }\n\n    expected_event_parsed = %{\n      amount: 10,\n      blknum: 1,\n      currency: <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>>,\n      eth_height: 390,\n      event_signature: \"DepositCreated(address,uint256,address,uint256)\",\n      log_index: 0,\n      owner: <<59, 159, 76, 29, 210, 110, 11, 229, 147, 55, 59, 29, 54, 206, 226, 0, 140, 190, 184, 55>>,\n      root_chain_txhash:\n        <<77, 114, 166, 63, 244, 47, 29, 181, 10, 242, 195, 110, 139, 49, 65, 1, 210, 254, 163, 224, 0, 53, 117, 243, 2,\n          152, 233, 21, 63, 227, 216, 238>>\n    }\n\n    assert Abi.decode_log(deposit_created_log) == expected_event_parsed\n  end\n\n  test \"if input piggybacked event log can be decoded\" do\n    input_piggybacked_log = %{\n      :event_signature => \"InFlightExitInputPiggybacked(address,bytes32,uint16)\",\n      \"address\" => \"0x92ce4d7773c57d96210c46a07b89acf725057f21\",\n      \"blockHash\" => \"0x6d95b14290cc2ac112f1560f2cd7aa0d747b91ec9cb1d47e11c205270d83c88c\",\n      \"blockNumber\" => \"0x19a\",\n      \"data\" => \"0x0000000000000000000000000000000000000000000000000000000000000001\",\n      \"logIndex\" => \"0x0\",\n      \"removed\" => false,\n      \"topics\" => [\n        \"0xa93c0e9b202feaf554acf6ef1185b898c9f214da16e51740b06b5f7487b018e5\",\n        \"0x0000000000000000000000001513abcd3590a25e0bed840652d957391dde9955\",\n        \"0xff90b77303e56bd230a9adf4a6553a95f5ffb563486205d6fba25d3e46594940\"\n      ],\n      \"transactionHash\" => \"0x0cc9e5556bbd6eeaf4302f44adca215786ff08cfa44a34be1760eca60f97364f\",\n      \"transactionIndex\" => \"0x0\"\n    }\n\n    expected_event_parsed = %{\n      eth_height: 410,\n      event_signature: \"InFlightExitInputPiggybacked(address,bytes32,uint16)\",\n      log_index: 0,\n      output_index: 1,\n      owner: <<21, 19, 171, 205, 53, 144, 162, 94, 11, 237, 132, 6, 82, 217, 87, 57, 29, 222, 153, 85>>,\n      root_chain_txhash:\n        <<12, 201, 229, 85, 107, 189, 110, 234, 244, 48, 47, 68, 173, 202, 33, 87, 134, 255, 8, 207, 164, 74, 52, 190,\n          23, 96, 236, 166, 15, 151, 54, 79>>,\n      tx_hash:\n        <<255, 144, 183, 115, 3, 229, 107, 210, 48, 169, 173, 244, 166, 85, 58, 149, 245, 255, 181, 99, 72, 98, 5, 214,\n          251, 162, 93, 62, 70, 89, 73, 64>>,\n      omg_data: %{piggyback_type: :input}\n    }\n\n    assert Abi.decode_log(input_piggybacked_log) == expected_event_parsed\n  end\n\n  test \"if output piggybacked event log can be decoded\" do\n    output_piggybacked_log = %{\n      :event_signature => \"InFlightExitOutputPiggybacked(address,bytes32,uint16)\",\n      \"address\" => \"0x92ce4d7773c57d96210c46a07b89acf725057f21\",\n      \"blockHash\" => \"0x3e34475a29dafb28cd6deb65bc1782ccf6d73d6673d462a6d404ac0993d1e7eb\",\n      \"blockNumber\" => \"0x198\",\n      \"data\" => \"0x0000000000000000000000000000000000000000000000000000000000000001\",\n      \"logIndex\" => \"0x1\",\n      \"removed\" => false,\n      \"topics\" => [\n        \"0x6ecd8e79a5f67f6c12b54371ada2ffb41bc128c61d9ac1e969f0aa2aca46cd78\",\n        \"0x0000000000000000000000001513abcd3590a25e0bed840652d957391dde9955\",\n        \"0xff90b77303e56bd230a9adf4a6553a95f5ffb563486205d6fba25d3e46594940\"\n      ],\n      \"transactionHash\" => \"0x7cf43a6080e99677dee0b26c23e469b1df9cfb56a5c3f2a0123df6edae7b5b5e\",\n      \"transactionIndex\" => \"0x0\"\n    }\n\n    expected_event_parsed = %{\n      eth_height: 408,\n      event_signature: \"InFlightExitOutputPiggybacked(address,bytes32,uint16)\",\n      log_index: 1,\n      output_index: 1,\n      owner: <<21, 19, 171, 205, 53, 144, 162, 94, 11, 237, 132, 6, 82, 217, 87, 57, 29, 222, 153, 85>>,\n      root_chain_txhash:\n        <<124, 244, 58, 96, 128, 233, 150, 119, 222, 224, 178, 108, 35, 228, 105, 177, 223, 156, 251, 86, 165, 195, 242,\n          160, 18, 61, 246, 237, 174, 123, 91, 94>>,\n      tx_hash:\n        <<255, 144, 183, 115, 3, 229, 107, 210, 48, 169, 173, 244, 166, 85, 58, 149, 245, 255, 181, 99, 72, 98, 5, 214,\n          251, 162, 93, 62, 70, 89, 73, 64>>,\n      omg_data: %{piggyback_type: :output}\n    }\n\n    assert Abi.decode_log(output_piggybacked_log) == expected_event_parsed\n  end\n\n  test \"if block emitted event log can be decoded\" do\n    block_submitted_log = %{\n      :event_signature => \"BlockSubmitted(uint256)\",\n      \"address\" => \"0xc673e4ffcb8464faff908a6804fe0e635af0ea2f\",\n      \"blockHash\" => \"0x31285f2f55e9334ae24a2bab8d5211b6f85177820f5ddf42cba652e0a88488c1\",\n      \"blockNumber\" => \"0x18e\",\n      \"data\" => \"0x00000000000000000000000000000000000000000000000000000000000003e8\",\n      \"logIndex\" => \"0x0\",\n      \"removed\" => false,\n      \"topics\" => [\"0x5a978f4723b249ccf79cd7a658a8601ce1ff8b89fc770251a6be35216351ce32\"],\n      \"transactionHash\" => \"0x297559979b5efa854ad29e216c76a64c3f43621bbf3dc16e4b31fb0cb6dcebf4\",\n      \"transactionIndex\" => \"0x0\"\n    }\n\n    expected_event_parsed = %{\n      blknum: 1000,\n      eth_height: 398,\n      event_signature: \"BlockSubmitted(uint256)\",\n      log_index: 0,\n      root_chain_txhash:\n        <<41, 117, 89, 151, 155, 94, 250, 133, 74, 210, 158, 33, 108, 118, 166, 76, 63, 67, 98, 27, 191, 61, 193, 110,\n          75, 49, 251, 12, 182, 220, 235, 244>>\n    }\n\n    assert Abi.decode_log(block_submitted_log) == expected_event_parsed\n  end\n\n  test \"if exit finalized event log can be decoded\" do\n    exit_finalized_log = %{\n      :event_signature => \"ExitFinalized(uint160)\",\n      \"address\" => \"0x92ce4d7773c57d96210c46a07b89acf725057f21\",\n      \"blockHash\" => \"0xcafbc4b710c5fab8f3d719f65053637407231ecde31a859f1709e3478a2eda54\",\n      \"blockNumber\" => \"0x14a\",\n      \"data\" => \"0x\",\n      \"logIndex\" => \"0x2\",\n      \"removed\" => false,\n      \"topics\" => [\n        \"0x0adb29b0831e081044cefe31155c1f2b2b85ad3613a480a5f901ee287addef55\",\n        \"0x000000000000000000000000003fd275046f2823936fd97c1e3c8b225464d7f1\"\n      ],\n      \"transactionHash\" => \"0xbe310ade41278c5607620311b79363aa520ac46c7ba754bf3027d501c5a95f40\",\n      \"transactionIndex\" => \"0x0\"\n    }\n\n    assert Abi.decode_log(exit_finalized_log) == %{\n             eth_height: 330,\n             event_signature: \"ExitFinalized(uint160)\",\n             exit_id: 1_423_280_346_484_099_708_949_144_162_169_101_241_792_387_057,\n             log_index: 2,\n             root_chain_txhash:\n               <<190, 49, 10, 222, 65, 39, 140, 86, 7, 98, 3, 17, 183, 147, 99, 170, 82, 10, 196, 108, 123, 167, 84,\n                 191, 48, 39, 213, 1, 197, 169, 95, 64>>\n           }\n  end\n\n  test \"if in flight exit challanged can be decoded\" do\n    in_flight_exit_challanged_log = %{\n      :event_signature => \"InFlightExitChallenged(address,bytes32,uint256)\",\n      \"address\" => \"0x92ce4d7773c57d96210c46a07b89acf725057f21\",\n      \"blockHash\" => \"0xcfffb9645dc8d73acc4c825b67ba62924c62402cc125564b655f469e0adeef32\",\n      \"blockNumber\" => \"0x196\",\n      \"data\" => \"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\",\n      \"logIndex\" => \"0x0\",\n      \"removed\" => false,\n      \"topics\" => [\n        \"0x687401968e501bda2d2d6f880dd1a0a56ff50b1787185ee0b6f4c3fb9fc417ab\",\n        \"0x0000000000000000000000007ae8190d9968cbb3b52e56a56b2cd4cd5e15a44f\",\n        \"0x7532528ec22439a9a1ed5f4fce6cd66d71625add6202cefb970c10d04f2d5091\"\n      ],\n      \"transactionHash\" => \"0xd9e3b3aaff8156dab8b004882d3bce834ba842c95deff7ec97da8f942f870ab4\",\n      \"transactionIndex\" => \"0x0\"\n    }\n\n    assert Abi.decode_log(in_flight_exit_challanged_log) == %{\n             challenger: <<122, 232, 25, 13, 153, 104, 203, 179, 181, 46, 86, 165, 107, 44, 212, 205, 94, 21, 164, 79>>,\n             competitor_position:\n               115_792_089_237_316_195_423_570_985_008_687_907_853_269_984_665_640_564_039_457_584_007_913_129_639_935,\n             eth_height: 406,\n             event_signature: \"InFlightExitChallenged(address,bytes32,uint256)\",\n             log_index: 0,\n             root_chain_txhash:\n               <<217, 227, 179, 170, 255, 129, 86, 218, 184, 176, 4, 136, 45, 59, 206, 131, 75, 168, 66, 201, 93, 239,\n                 247, 236, 151, 218, 143, 148, 47, 135, 10, 180>>,\n             tx_hash:\n               <<117, 50, 82, 142, 194, 36, 57, 169, 161, 237, 95, 79, 206, 108, 214, 109, 113, 98, 90, 221, 98, 2, 206,\n                 251, 151, 12, 16, 208, 79, 45, 80, 145>>\n           }\n  end\n\n  test \"if exit challenged can be decoded \" do\n    exit_challenged_log = %{\n      :event_signature => \"ExitChallenged(uint256)\",\n      \"address\" => \"0x92ce4d7773c57d96210c46a07b89acf725057f21\",\n      \"blockHash\" => \"0x95948e75cb18f299ba10e528401a9a2debf19e26425190582f7e01d888cbb7d0\",\n      \"blockNumber\" => \"0x11f\",\n      \"data\" => \"0x\",\n      \"logIndex\" => \"0x0\",\n      \"removed\" => false,\n      \"topics\" => [\n        \"0x5dfba526c59b25f899f935c5b0d5b8739e97e4d89c38c158eca3192ea34b87d8\",\n        \"0x000000000000000000000000000000000000000000000000000000e8d4a51000\"\n      ],\n      \"transactionHash\" => \"0x4252551c98e590863df08fd6389c616aab511038306ab8f78224a82d15070325\",\n      \"transactionIndex\" => \"0x0\"\n    }\n\n    assert Abi.decode_log(exit_challenged_log) == %{\n             eth_height: 287,\n             event_signature: \"ExitChallenged(uint256)\",\n             log_index: 0,\n             root_chain_txhash:\n               <<66, 82, 85, 28, 152, 229, 144, 134, 61, 240, 143, 214, 56, 156, 97, 106, 171, 81, 16, 56, 48, 106, 184,\n                 247, 130, 36, 168, 45, 21, 7, 3, 37>>,\n             utxo_pos: 1_000_000_000_000\n           }\n  end\n\n  test \"if in flight exit challenge responded can be decoded\" do\n    in_flight_exit_challenge_responded_log = %{\n      :event_signature => \"InFlightExitChallengeResponded(address,bytes32,uint256)\",\n      \"address\" => \"0x92ce4d7773c57d96210c46a07b89acf725057f21\",\n      \"blockHash\" => \"0x4f3960b70634b34d69fa4a05c5d3561809cb66f2890539a01187f040a44988d1\",\n      \"blockNumber\" => \"0x125\",\n      \"data\" => \"0x000000000000000000000000000000000000000000000000000000e8d4a51000\",\n      \"logIndex\" => \"0x0\",\n      \"removed\" => false,\n      \"topics\" => [\n        \"0x637cc4a7148767df19331a5c7dfb6d31f0a7e159a3dbb28a716be18c8c74f768\",\n        \"0x00000000000000000000000018e688329ff9d6197108a66619912cda5d9ea163\",\n        \"0xe60f426cbc3714ba7235df24027bf296d4d52a1a0cb36d46d6c88a3940f98d6b\"\n      ],\n      \"transactionHash\" => \"0x3fb63662a52fdc05d471fed92b65c9c53a9b0d990b7baefce318a6e4fa6cd517\",\n      \"transactionIndex\" => \"0x0\"\n    }\n\n    assert Abi.decode_log(in_flight_exit_challenge_responded_log) == %{\n             challenge_position: 1_000_000_000_000,\n             challenger: <<24, 230, 136, 50, 159, 249, 214, 25, 113, 8, 166, 102, 25, 145, 44, 218, 93, 158, 161, 99>>,\n             eth_height: 293,\n             event_signature: \"InFlightExitChallengeResponded(address,bytes32,uint256)\",\n             log_index: 0,\n             root_chain_txhash:\n               <<63, 182, 54, 98, 165, 47, 220, 5, 212, 113, 254, 217, 43, 101, 201, 197, 58, 155, 13, 153, 11, 123,\n                 174, 252, 227, 24, 166, 228, 250, 108, 213, 23>>,\n             tx_hash:\n               <<230, 15, 66, 108, 188, 55, 20, 186, 114, 53, 223, 36, 2, 123, 242, 150, 212, 213, 42, 26, 12, 179, 109,\n                 70, 214, 200, 138, 57, 64, 249, 141, 107>>\n           }\n  end\n\n  test \"if challenge in flight exit not cannonical can be decoded\" do\n    eth_tx_input =\n      <<232, 54, 34, 152, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n        32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 32, 0, 0, 0, 0,\n        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 59, 154, 202, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 32, 0,\n        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 64, 0, 0, 0, 0, 0, 0,\n        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85, 248, 83, 1, 192, 238, 237, 1,\n        235, 148, 140, 7, 214, 39, 36, 232, 102, 145, 82, 184, 199, 23, 67, 29, 135, 188, 216, 208, 23, 89, 148, 0, 0,\n        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 128, 160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 165, 248, 163, 1, 225, 160, 0, 0, 0, 0, 0, 0,\n        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 59, 154, 202, 0, 248, 92, 237, 1, 235, 148,\n        140, 7, 214, 39, 36, 232, 102, 145, 82, 184, 199, 23, 67, 29, 135, 188, 216, 208, 23, 89, 148, 0, 0, 0, 0, 0, 0,\n        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 237, 1, 235, 148, 140, 7, 214, 39, 36, 232, 102, 145, 82, 184, 199,\n        23, 67, 29, 135, 188, 216, 208, 23, 89, 148, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 128,\n        160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 118, 248, 116, 1, 225, 160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 59, 154, 202, 0, 238, 237, 1, 235, 148, 130, 28, 224, 68,\n        235, 159, 239, 63, 140, 241, 0, 192, 44, 230, 131, 216, 224, 52, 2, 224, 148, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n        0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 128, 160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n        0, 0, 0, 0, 0, 0, 0, 65, 213, 14, 110, 137, 144, 125, 5, 4, 94, 64, 55, 85, 66, 96, 210, 166, 41, 110, 42, 187,\n        199, 54, 83, 228, 31, 85, 4, 44, 153, 33, 56, 182, 104, 35, 67, 129, 11, 98, 78, 229, 81, 4, 199, 65, 155, 47,\n        3, 187, 179, 69, 65, 239, 135, 219, 72, 233, 93, 232, 14, 157, 74, 187, 190, 63, 28, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>>\n\n    assert Abi.decode_function(eth_tx_input) ==\n             %{\n               competing_tx:\n                 <<248, 116, 1, 225, 160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n                   0, 0, 59, 154, 202, 0, 238, 237, 1, 235, 148, 130, 28, 224, 68, 235, 159, 239, 63, 140, 241, 0, 192,\n                   44, 230, 131, 216, 224, 52, 2, 224, 148, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n                   9, 128, 160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n                   0, 0, 0>>,\n               competing_tx_input_index: 0,\n               competing_tx_pos: 0,\n               competing_tx_sig:\n                 <<213, 14, 110, 137, 144, 125, 5, 4, 94, 64, 55, 85, 66, 96, 210, 166, 41, 110, 42, 187, 199, 54, 83,\n                   228, 31, 85, 4, 44, 153, 33, 56, 182, 104, 35, 67, 129, 11, 98, 78, 229, 81, 4, 199, 65, 155, 47, 3,\n                   187, 179, 69, 65, 239, 135, 219, 72, 233, 93, 232, 14, 157, 74, 187, 190, 63, 28>>,\n               in_flight_input_index: 0,\n               in_flight_tx:\n                 <<248, 163, 1, 225, 160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n                   0, 0, 59, 154, 202, 0, 248, 92, 237, 1, 235, 148, 140, 7, 214, 39, 36, 232, 102, 145, 82, 184, 199,\n                   23, 67, 29, 135, 188, 216, 208, 23, 89, 148, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n                   0, 5, 237, 1, 235, 148, 140, 7, 214, 39, 36, 232, 102, 145, 82, 184, 199, 23, 67, 29, 135, 188, 216,\n                   208, 23, 89, 148, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 128, 160, 0, 0, 0,\n                   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>>,\n               input_tx_bytes:\n                 <<248, 83, 1, 192, 238, 237, 1, 235, 148, 140, 7, 214, 39, 36, 232, 102, 145, 82, 184, 199, 23, 67, 29,\n                   135, 188, 216, 208, 23, 89, 148, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 128,\n                   160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n                   0>>,\n               input_utxo_pos: 1_000_000_000\n             }\n  end\n\n  test \"if in flight exit input/output blocked can be decoded \" do\n    in_flight_exit_output_blocked_log = %{\n      :event_signature => \"InFlightExitOutputBlocked(address,bytes32,uint16)\",\n      \"address\" => \"0x92ce4d7773c57d96210c46a07b89acf725057f21\",\n      \"blockHash\" => \"0x1be26da1ab54eaf962157ae6c2079179a3024eeaa993d4f326e659e99cf8215e\",\n      \"blockNumber\" => \"0x1b6\",\n      \"data\" => \"0x0000000000000000000000000000000000000000000000000000000000000001\",\n      \"logIndex\" => \"0x0\",\n      \"removed\" => false,\n      \"topics\" => [\n        \"0xcbe8dad2e7fcbfe0dcba2f9b2e44f122c66cd26dc0808a0f7e9ec41e4fe285bf\",\n        \"0x000000000000000000000000d5089cfa403a6031a1f383bd467e980ed0bd5cba\",\n        \"0x2a3f2ef50884e123a32a2c40d86758e8fe5b82a9a2b82e2c0849be6f13c95702\"\n      ],\n      \"transactionHash\" => \"0x984796ba697b532be624029990fc6d4f72e4e1434cf68dcf3b05b34b7987c468\",\n      \"transactionIndex\" => \"0x0\"\n    }\n\n    assert Abi.decode_log(in_flight_exit_output_blocked_log) == %{\n             challenger: <<213, 8, 156, 250, 64, 58, 96, 49, 161, 243, 131, 189, 70, 126, 152, 14, 208, 189, 92, 186>>,\n             eth_height: 438,\n             event_signature: \"InFlightExitOutputBlocked(address,bytes32,uint16)\",\n             log_index: 0,\n             output_index: 1,\n             root_chain_txhash:\n               <<152, 71, 150, 186, 105, 123, 83, 43, 230, 36, 2, 153, 144, 252, 109, 79, 114, 228, 225, 67, 76, 246,\n                 141, 207, 59, 5, 179, 75, 121, 135, 196, 104>>,\n             tx_hash:\n               <<42, 63, 46, 245, 8, 132, 225, 35, 163, 42, 44, 64, 216, 103, 88, 232, 254, 91, 130, 169, 162, 184, 46,\n                 44, 8, 73, 190, 111, 19, 201, 87, 2>>,\n             omg_data: %{piggyback_type: :output}\n           }\n  end\n\n  test \"if in flight exit started can be decoded\" do\n    in_flight_exit_started_log = %{\n      :event_signature => \"InFlightExitStarted(address,bytes32)\",\n      \"address\" => \"0x92ce4d7773c57d96210c46a07b89acf725057f21\",\n      \"blockHash\" => \"0xc8d61620144825f38394feb2c9c1d721a161ed67c123c3cb1af787fb366866c1\",\n      \"blockNumber\" => \"0x2d6\",\n      \"data\" => \"0x\",\n      \"logIndex\" => \"0x0\",\n      \"removed\" => false,\n      \"topics\" => [\n        \"0xd5f1fe9d48880b57daa227004b16d320c0eb885d6c49d472d54c16a05fa3179e\",\n        \"0x0000000000000000000000002c6a9f42318025cd6627baf21c468201622020df\",\n        \"0x4f46053b5df585094cc652ddd8c365962a3889c2053592f18331b95a7dff620e\"\n      ],\n      \"transactionHash\" => \"0xf0e44af0d26443b9e5133c64f5a71f06a4d4d0d40c5e7412b5ea0dfcb2f1a133\",\n      \"transactionIndex\" => \"0x0\"\n    }\n\n    assert Abi.decode_log(in_flight_exit_started_log) == %{\n             eth_height: 726,\n             event_signature: \"InFlightExitStarted(address,bytes32)\",\n             initiator: <<44, 106, 159, 66, 49, 128, 37, 205, 102, 39, 186, 242, 28, 70, 130, 1, 98, 32, 32, 223>>,\n             log_index: 0,\n             root_chain_txhash:\n               <<240, 228, 74, 240, 210, 100, 67, 185, 229, 19, 60, 100, 245, 167, 31, 6, 164, 212, 208, 212, 12, 94,\n                 116, 18, 181, 234, 13, 252, 178, 241, 161, 51>>,\n             tx_hash:\n               <<79, 70, 5, 59, 93, 245, 133, 9, 76, 198, 82, 221, 216, 195, 101, 150, 42, 56, 137, 194, 5, 53, 146,\n                 241, 131, 49, 185, 90, 125, 255, 98, 14>>\n           }\n  end\n\n  test \"if start in flight exit can be decoded \" do\n    in_flight_exit_start_log =\n      <<90, 82, 133, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n        32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 160, 0, 0, 0,\n        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 64, 0, 0, 0, 0, 0, 0, 0, 0,\n        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n        0, 0, 0, 0, 0, 0, 0, 0, 126, 248, 124, 1, 225, 160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n        0, 0, 0, 0, 0, 0, 1, 210, 32, 127, 180, 0, 246, 245, 1, 243, 148, 118, 78, 248, 3, 28, 17, 248, 220, 42, 92, 18,\n        141, 145, 248, 79, 186, 190, 47, 160, 172, 148, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 136,\n        69, 99, 145, 130, 68, 244, 0, 0, 128, 160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n        0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n        32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 93, 248, 91, 1,\n        192, 246, 245, 1, 243, 148, 118, 78, 248, 3, 28, 17, 248, 220, 42, 92, 18, 141, 145, 248, 79, 186, 190, 47, 160,\n        172, 148, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 136, 138, 199, 35, 4, 137, 232, 0, 0, 128,\n        160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,\n        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 210, 32, 127, 180, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 243, 154, 134, 159, 98, 231, 92, 245, 240, 191, 145, 70, 136, 166, 178,\n        137, 202, 242, 4, 148, 53, 216, 230, 140, 92, 94, 109, 5, 228, 73, 19, 243, 78, 213, 192, 45, 109, 72, 200, 147,\n        36, 134, 201, 157, 58, 217, 153, 229, 216, 148, 157, 195, 190, 59, 48, 88, 204, 41, 121, 105, 12, 62, 58, 98,\n        28, 121, 43, 20, 191, 102, 248, 42, 243, 111, 0, 245, 251, 167, 1, 79, 160, 193, 226, 255, 60, 124, 39, 59, 254,\n        82, 60, 26, 207, 103, 220, 63, 95, 160, 128, 166, 134, 165, 160, 208, 92, 61, 72, 34, 253, 84, 214, 50, 220,\n        156, 192, 75, 22, 22, 4, 110, 186, 44, 228, 153, 235, 154, 247, 159, 94, 185, 73, 105, 10, 4, 4, 171, 244, 206,\n        186, 252, 124, 255, 250, 56, 33, 145, 183, 221, 158, 125, 247, 120, 88, 30, 111, 183, 142, 250, 179, 95, 211,\n        100, 201, 213, 218, 218, 212, 86, 155, 109, 212, 127, 127, 234, 186, 250, 53, 113, 248, 66, 67, 68, 37, 84, 131,\n        53, 172, 110, 105, 13, 208, 113, 104, 216, 188, 91, 119, 151, 156, 26, 103, 2, 51, 79, 82, 159, 87, 131, 247,\n        158, 148, 47, 210, 205, 3, 246, 229, 90, 194, 207, 73, 110, 132, 159, 222, 156, 68, 111, 171, 70, 168, 210, 125,\n        177, 227, 16, 15, 39, 90, 119, 125, 56, 91, 68, 227, 203, 192, 69, 202, 186, 201, 218, 54, 202, 224, 64, 173,\n        81, 96, 130, 50, 76, 150, 18, 124, 242, 159, 69, 53, 235, 91, 126, 186, 207, 226, 161, 214, 211, 170, 184, 236,\n        4, 131, 211, 32, 121, 168, 89, 255, 112, 249, 33, 89, 112, 168, 190, 235, 177, 193, 100, 196, 116, 232, 36, 56,\n        23, 76, 142, 235, 111, 188, 140, 180, 89, 75, 136, 201, 68, 143, 29, 64, 176, 155, 234, 236, 172, 91, 69, 219,\n        110, 65, 67, 74, 18, 43, 105, 92, 90, 133, 134, 45, 142, 174, 64, 179, 38, 143, 111, 55, 228, 20, 51, 123, 227,\n        142, 186, 122, 181, 187, 243, 3, 208, 31, 75, 122, 224, 127, 215, 62, 220, 47, 59, 224, 94, 67, 148, 138, 52,\n        65, 138, 50, 114, 80, 156, 67, 194, 129, 26, 130, 30, 92, 152, 43, 165, 24, 116, 172, 125, 201, 221, 121, 168,\n        12, 194, 240, 95, 111, 102, 76, 157, 187, 46, 69, 68, 53, 19, 125, 160, 108, 228, 77, 228, 85, 50, 165, 106, 58,\n        112, 7, 162, 208, 198, 180, 53, 247, 38, 249, 81, 4, 191, 166, 231, 7, 4, 111, 193, 84, 186, 233, 24, 152, 208,\n        58, 26, 10, 198, 249, 180, 94, 71, 22, 70, 226, 85, 90, 199, 158, 63, 232, 126, 177, 120, 30, 38, 242, 5, 0, 36,\n        12, 55, 146, 116, 254, 145, 9, 110, 96, 209, 84, 90, 128, 69, 87, 31, 218, 185, 181, 48, 208, 214, 231, 232,\n        116, 110, 120, 191, 159, 32, 244, 232, 111, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n        0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n        0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65,\n        52, 191, 197, 222, 130, 0, 246, 100, 25, 133, 115, 123, 250, 19, 77, 122, 226, 50, 133, 34, 71, 195, 27, 188,\n        147, 104, 200, 235, 121, 231, 64, 251, 107, 58, 88, 55, 118, 117, 53, 9, 224, 81, 93, 0, 167, 62, 195, 202, 233,\n        207, 237, 254, 185, 95, 207, 246, 144, 69, 242, 160, 58, 161, 96, 70, 28, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>>\n\n    assert Abi.decode_function(in_flight_exit_start_log) == %{\n             in_flight_tx:\n               <<248, 124, 1, 225, 160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,\n                 210, 32, 127, 180, 0, 246, 245, 1, 243, 148, 118, 78, 248, 3, 28, 17, 248, 220, 42, 92, 18, 141, 145,\n                 248, 79, 186, 190, 47, 160, 172, 148, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 136,\n                 69, 99, 145, 130, 68, 244, 0, 0, 128, 160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>>,\n             in_flight_tx_sigs: [\n               <<52, 191, 197, 222, 130, 0, 246, 100, 25, 133, 115, 123, 250, 19, 77, 122, 226, 50, 133, 34, 71, 195,\n                 27, 188, 147, 104, 200, 235, 121, 231, 64, 251, 107, 58, 88, 55, 118, 117, 53, 9, 224, 81, 93, 0, 167,\n                 62, 195, 202, 233, 207, 237, 254, 185, 95, 207, 246, 144, 69, 242, 160, 58, 161, 96, 70, 28>>\n             ],\n             input_inclusion_proofs: [\n               <<243, 154, 134, 159, 98, 231, 92, 245, 240, 191, 145, 70, 136, 166, 178, 137, 202, 242, 4, 148, 53, 216,\n                 230, 140, 92, 94, 109, 5, 228, 73, 19, 243, 78, 213, 192, 45, 109, 72, 200, 147, 36, 134, 201, 157, 58,\n                 217, 153, 229, 216, 148, 157, 195, 190, 59, 48, 88, 204, 41, 121, 105, 12, 62, 58, 98, 28, 121, 43, 20,\n                 191, 102, 248, 42, 243, 111, 0, 245, 251, 167, 1, 79, 160, 193, 226, 255, 60, 124, 39, 59, 254, 82, 60,\n                 26, 207, 103, 220, 63, 95, 160, 128, 166, 134, 165, 160, 208, 92, 61, 72, 34, 253, 84, 214, 50, 220,\n                 156, 192, 75, 22, 22, 4, 110, 186, 44, 228, 153, 235, 154, 247, 159, 94, 185, 73, 105, 10, 4, 4, 171,\n                 244, 206, 186, 252, 124, 255, 250, 56, 33, 145, 183, 221, 158, 125, 247, 120, 88, 30, 111, 183, 142,\n                 250, 179, 95, 211, 100, 201, 213, 218, 218, 212, 86, 155, 109, 212, 127, 127, 234, 186, 250, 53, 113,\n                 248, 66, 67, 68, 37, 84, 131, 53, 172, 110, 105, 13, 208, 113, 104, 216, 188, 91, 119, 151, 156, 26,\n                 103, 2, 51, 79, 82, 159, 87, 131, 247, 158, 148, 47, 210, 205, 3, 246, 229, 90, 194, 207, 73, 110, 132,\n                 159, 222, 156, 68, 111, 171, 70, 168, 210, 125, 177, 227, 16, 15, 39, 90, 119, 125, 56, 91, 68, 227,\n                 203, 192, 69, 202, 186, 201, 218, 54, 202, 224, 64, 173, 81, 96, 130, 50, 76, 150, 18, 124, 242, 159,\n                 69, 53, 235, 91, 126, 186, 207, 226, 161, 214, 211, 170, 184, 236, 4, 131, 211, 32, 121, 168, 89, 255,\n                 112, 249, 33, 89, 112, 168, 190, 235, 177, 193, 100, 196, 116, 232, 36, 56, 23, 76, 142, 235, 111, 188,\n                 140, 180, 89, 75, 136, 201, 68, 143, 29, 64, 176, 155, 234, 236, 172, 91, 69, 219, 110, 65, 67, 74, 18,\n                 43, 105, 92, 90, 133, 134, 45, 142, 174, 64, 179, 38, 143, 111, 55, 228, 20, 51, 123, 227, 142, 186,\n                 122, 181, 187, 243, 3, 208, 31, 75, 122, 224, 127, 215, 62, 220, 47, 59, 224, 94, 67, 148, 138, 52, 65,\n                 138, 50, 114, 80, 156, 67, 194, 129, 26, 130, 30, 92, 152, 43, 165, 24, 116, 172, 125, 201, 221, 121,\n                 168, 12, 194, 240, 95, 111, 102, 76, 157, 187, 46, 69, 68, 53, 19, 125, 160, 108, 228, 77, 228, 85, 50,\n                 165, 106, 58, 112, 7, 162, 208, 198, 180, 53, 247, 38, 249, 81, 4, 191, 166, 231, 7, 4, 111, 193, 84,\n                 186, 233, 24, 152, 208, 58, 26, 10, 198, 249, 180, 94, 71, 22, 70, 226, 85, 90, 199, 158, 63, 232, 126,\n                 177, 120, 30, 38, 242, 5, 0, 36, 12, 55, 146, 116, 254, 145, 9, 110, 96, 209, 84, 90, 128, 69, 87, 31,\n                 218, 185, 181, 48, 208, 214, 231, 232, 116, 110, 120, 191, 159, 32, 244, 232, 111, 6>>\n             ],\n             input_txs: [\n               <<248, 91, 1, 192, 246, 245, 1, 243, 148, 118, 78, 248, 3, 28, 17, 248, 220, 42, 92, 18, 141, 145, 248,\n                 79, 186, 190, 47, 160, 172, 148, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 136, 138,\n                 199, 35, 4, 137, 232, 0, 0, 128, 160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>>\n             ],\n             input_utxos_pos: [2_002_000_000_000]\n           }\n  end\n\n  test \"if in flight exit deleted can be decoded\" do\n    in_flight_exit_deleted_log = %{\n      :event_signature => \"InFlightExitDeleted(uint160)\",\n      \"address\" => \"0x89afce326e7da55647d22e24336c6a2816c99f6b\",\n      \"blockHash\" => \"0xa27ed6299f3d74954e2c32629a5d807743627f8e57f83c8cbeaa4351da73f597\",\n      \"blockNumber\" => \"0x3e8\",\n      \"data\" => \"0x\",\n      \"logIndex\" => \"0x0\",\n      \"removed\" => false,\n      \"topics\" => [\n        \"0x1991c4c350498b0cc937c6a08bc5bdecf2e4fdd9d918052a880f102e43dbe45c\",\n        \"0x00000000000000000000000000d1d291fd21f1899f4c9d621f65dd1e0aa2355d\"\n      ],\n      \"transactionHash\" => \"0xbe310ade41278c5607620311b79363aa520ac46c7ba754bf3027d501c5a95f40\",\n      \"transactionIndex\" => \"0x0\"\n    }\n\n    assert Abi.decode_log(in_flight_exit_deleted_log) == %{\n             eth_height: 1000,\n             event_signature: \"InFlightExitDeleted(uint160)\",\n             exit_id: 4_679_199_003_952_701_118_642_806_135_853_996_264_334_177_629,\n             log_index: 0,\n             root_chain_txhash:\n               <<190, 49, 10, 222, 65, 39, 140, 86, 7, 98, 3, 17, 183, 147, 99, 170, 82, 10, 196, 108, 123, 167, 84,\n                 191, 48, 39, 213, 1, 197, 169, 95, 64>>\n           }\n  end\n\n  test \"if in flight exit output withdrawn can be decoded\" do\n    in_flight_exit_output_withdrawn_log = %{\n      :event_signature => \"InFlightExitOutputWithdrawn(uint160,uint16)\",\n      \"address\" => \"0x92ce4d7773c57d96210c46a07b89acf725057f21\",\n      \"blockHash\" => \"0x2218cd9358fd6ed3b720b512b645a88a9a3ed9f472e6192fae202f60e40ac7a2\",\n      \"blockNumber\" => \"0x14f\",\n      \"data\" => \"0x0000000000000000000000000000000000000000000000000000000000000001\",\n      \"logIndex\" => \"0x1\",\n      \"removed\" => false,\n      \"topics\" => [\n        \"0xa241c6deaf193e53a1b002d779e4f247bf5d57ba0be5a753e628dfcee645a4f7\",\n        \"0x00000000000000000000000000acccc8410b2139de37be92bb345c4fa10644a4\"\n      ],\n      \"transactionHash\" => \"0x50f80a28c7b45e5700d6e756a49d4c6ceebd5c4a5285b28abeb97058c941b966\",\n      \"transactionIndex\" => \"0x0\"\n    }\n\n    assert Abi.decode_log(in_flight_exit_output_withdrawn_log) == %{\n             eth_height: 335,\n             event_signature: \"InFlightExitOutputWithdrawn(uint160,uint16)\",\n             in_flight_exit_id: 3_853_567_223_408_339_354_111_409_210_931_346_801_537_991_844,\n             log_index: 1,\n             output_index: 1,\n             root_chain_txhash:\n               <<80, 248, 10, 40, 199, 180, 94, 87, 0, 214, 231, 86, 164, 157, 76, 108, 238, 189, 92, 74, 82, 133, 178,\n                 138, 190, 185, 112, 88, 201, 65, 185, 102>>,\n             omg_data: %{piggyback_type: :output}\n           }\n  end\n\n  test \"if exit started can be decoded\" do\n    exit_started_log = %{\n      :event_signature => \"ExitStarted(address,uint160)\",\n      \"address\" => \"0x92ce4d7773c57d96210c46a07b89acf725057f21\",\n      \"blockHash\" => \"0x1bee6f75c74ceeb4817dc160e2fb56dd1337a9fc2980a2b013252cf1e620f246\",\n      \"blockNumber\" => \"0x2f7\",\n      \"data\" => \"0x000000000000000000000000002b191e750d8d4d3dcad14a9c8e5a5cf0c81761\",\n      \"logIndex\" => \"0x1\",\n      \"removed\" => false,\n      \"topics\" => [\n        \"0xdd6f755cba05d0a420007aef6afc05e4889ab424505e2e440ecd1c434ba7082e\",\n        \"0x00000000000000000000000008858124b3b880c68b360fd319cc61da27545e9a\"\n      ],\n      \"transactionHash\" => \"0x4a8248b88a17b2be4c6086a1984622de1a60dda3c9dd9ece1ef97ed18efa028c\",\n      \"transactionIndex\" => \"0x0\"\n    }\n\n    assert Abi.decode_log(exit_started_log) == %{\n             eth_height: 759,\n             event_signature: \"ExitStarted(address,uint160)\",\n             exit_id: 961_120_214_746_159_734_848_620_722_848_998_552_444_082_017,\n             log_index: 1,\n             owner: <<8, 133, 129, 36, 179, 184, 128, 198, 139, 54, 15, 211, 25, 204, 97, 218, 39, 84, 94, 154>>,\n             root_chain_txhash:\n               <<74, 130, 72, 184, 138, 23, 178, 190, 76, 96, 134, 161, 152, 70, 34, 222, 26, 96, 221, 163, 201, 221,\n                 158, 206, 30, 249, 126, 209, 142, 250, 2, 140>>\n           }\n  end\n\n  test \"if start standard exit can be decoded\" do\n    start_standard_exit_log =\n      <<112, 224, 20, 98, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n        32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 209, 228, 228, 234, 0, 0,\n        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0,\n        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 93, 248, 91, 1, 192, 246, 245, 1, 243, 148, 8, 133,\n        129, 36, 179, 184, 128, 198, 139, 54, 15, 211, 25, 204, 97, 218, 39, 84, 94, 154, 148, 0, 0, 0, 0, 0, 0, 0, 0,\n        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 136, 13, 224, 182, 179, 167, 100, 0, 0, 128, 160, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 243, 154, 134, 159, 98, 231, 92, 245, 240, 191,\n        145, 70, 136, 166, 178, 137, 202, 242, 4, 148, 53, 216, 230, 140, 92, 94, 109, 5, 228, 73, 19, 243, 78, 213,\n        192, 45, 109, 72, 200, 147, 36, 134, 201, 157, 58, 217, 153, 229, 216, 148, 157, 195, 190, 59, 48, 88, 204, 41,\n        121, 105, 12, 62, 58, 98, 28, 121, 43, 20, 191, 102, 248, 42, 243, 111, 0, 245, 251, 167, 1, 79, 160, 193, 226,\n        255, 60, 124, 39, 59, 254, 82, 60, 26, 207, 103, 220, 63, 95, 160, 128, 166, 134, 165, 160, 208, 92, 61, 72, 34,\n        253, 84, 214, 50, 220, 156, 192, 75, 22, 22, 4, 110, 186, 44, 228, 153, 235, 154, 247, 159, 94, 185, 73, 105,\n        10, 4, 4, 171, 244, 206, 186, 252, 124, 255, 250, 56, 33, 145, 183, 221, 158, 125, 247, 120, 88, 30, 111, 183,\n        142, 250, 179, 95, 211, 100, 201, 213, 218, 218, 212, 86, 155, 109, 212, 127, 127, 234, 186, 250, 53, 113, 248,\n        66, 67, 68, 37, 84, 131, 53, 172, 110, 105, 13, 208, 113, 104, 216, 188, 91, 119, 151, 156, 26, 103, 2, 51, 79,\n        82, 159, 87, 131, 247, 158, 148, 47, 210, 205, 3, 246, 229, 90, 194, 207, 73, 110, 132, 159, 222, 156, 68, 111,\n        171, 70, 168, 210, 125, 177, 227, 16, 15, 39, 90, 119, 125, 56, 91, 68, 227, 203, 192, 69, 202, 186, 201, 218,\n        54, 202, 224, 64, 173, 81, 96, 130, 50, 76, 150, 18, 124, 242, 159, 69, 53, 235, 91, 126, 186, 207, 226, 161,\n        214, 211, 170, 184, 236, 4, 131, 211, 32, 121, 168, 89, 255, 112, 249, 33, 89, 112, 168, 190, 235, 177, 193,\n        100, 196, 116, 232, 36, 56, 23, 76, 142, 235, 111, 188, 140, 180, 89, 75, 136, 201, 68, 143, 29, 64, 176, 155,\n        234, 236, 172, 91, 69, 219, 110, 65, 67, 74, 18, 43, 105, 92, 90, 133, 134, 45, 142, 174, 64, 179, 38, 143, 111,\n        55, 228, 20, 51, 123, 227, 142, 186, 122, 181, 187, 243, 3, 208, 31, 75, 122, 224, 127, 215, 62, 220, 47, 59,\n        224, 94, 67, 148, 138, 52, 65, 138, 50, 114, 80, 156, 67, 194, 129, 26, 130, 30, 92, 152, 43, 165, 24, 116, 172,\n        125, 201, 221, 121, 168, 12, 194, 240, 95, 111, 102, 76, 157, 187, 46, 69, 68, 53, 19, 125, 160, 108, 228, 77,\n        228, 85, 50, 165, 106, 58, 112, 7, 162, 208, 198, 180, 53, 247, 38, 249, 81, 4, 191, 166, 231, 7, 4, 111, 193,\n        84, 186, 233, 24, 152, 208, 58, 26, 10, 198, 249, 180, 94, 71, 22, 70, 226, 85, 90, 199, 158, 63, 232, 126, 177,\n        120, 30, 38, 242, 5, 0, 36, 12, 55, 146, 116, 254, 145, 9, 110, 96, 209, 84, 90, 128, 69, 87, 31, 218, 185, 181,\n        48, 208, 214, 231, 232, 116, 110, 120, 191, 159, 32, 244, 232, 111, 6>>\n\n    assert Abi.decode_function(start_standard_exit_log) == %{\n             output_tx:\n               <<248, 91, 1, 192, 246, 245, 1, 243, 148, 8, 133, 129, 36, 179, 184, 128, 198, 139, 54, 15, 211, 25, 204,\n                 97, 218, 39, 84, 94, 154, 148, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 136, 13,\n                 224, 182, 179, 167, 100, 0, 0, 128, 160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>>,\n             utxo_pos: 2_001_000_000_000\n           }\n  end\n\n  test \"blocks(uint256) function call gets decoded properly\" do\n    data =\n      \"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"\n\n    %{\n      \"block_hash\" =>\n        <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>>,\n      \"block_timestamp\" => 0\n    } = Abi.decode_function(data, \"blocks(uint256)\")\n  end\n\n  test \"nextChildBlock() function call gets decoded properly\" do\n    data =\n      \"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"\n\n    %{\n      \"block_number\" => next_child_block\n    } = Abi.decode_function(data, \"nextChildBlock()\")\n\n    assert is_integer(next_child_block)\n  end\n\n  test \"minExitPeriod() function call gets decoded properly\" do\n    data = \"0x0000000000000000000000000000000000000000000000000000000000000014\"\n\n    %{\"min_exit_period\" => 20} = Abi.decode_function(data, \"minExitPeriod()\")\n  end\n\n  test \"exitGames(uint256) function call gets decoded properly\" do\n    data =\n      \"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"\n\n    %{\n      \"block_hash\" =>\n        <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>>,\n      \"block_timestamp\" => 0\n    } = Abi.decode_function(data, \"blocks(uint256)\")\n  end\n\n  test \"vaults(uint256) function call gets decoded properly\" do\n    data = \"0x0000000000000000000000004e3aeff70f022a6d4cc5947423887e7152826cf7\"\n\n    %{\"vault_address\" => vault_address} = Abi.decode_function(data, \"vaults(uint256)\")\n\n    assert is_binary(vault_address)\n  end\n\n  test \"getVersion() function call gets decoded properly\" do\n    data =\n      \"0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d312e302e342b6136396337363300000000000000000000000000000000000000\"\n\n    %{\"version\" => version} = Abi.decode_function(data, \"getVersion()\")\n\n    assert is_binary(version)\n  end\n\n  test \"childBlockInterval() function call gets decoded properly\" do\n    data = \"0x00000000000000000000000000000000000000000000000000000000000003e8\"\n\n    %{\"child_block_interval\" => 1000} = Abi.decode_function(data, \"childBlockInterval()\")\n  end\n\n  # workaround for https://github.com/omgnetwork/elixir-omg/issues/1632\n  test \"decode liquidity standard exit\" do\n    \"0x\" <> encoded =\n      \"0xbf1f316d000000000000000000000000000000000000000000000000000647fc93f6800000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000380000000000000000000000000000000000000000000000000000000000000046000000000000000000000000000000000000000000000000000064713bf517001000000000000000000000000000000000000000000000000000000000000007ef87c01e1a000000000000000000000000000000000000000000000000000064713bf517001f6f501f394584128cad14df97a6eb38c83293a23bd3297d3429400000000000000000000000000000000000000008801632a2f7232200080a0000000000000000000000000000000000000466173742045786974205465737400000000000000000000000000000000000000000000000000000000000000000200c79632b66c36683bee7872870431dbe49ddd78158135be7929f0897aabf6fdf74ed5c02d6d48c8932486c99d3ad999e5d8949dc3be3b3058cc2979690c3e3a621c792b14bf66f82af36f00f5fba7014fa0c1e2ff3c7c273bfe523c1acf67dc3f5fa080a686a5a0d05c3d4822fd54d632dc9cc04b1616046eba2ce499eb9af79f5eb949690a0404abf4cebafc7cfffa382191b7dd9e7df778581e6fb78efab35fd364c9d5dadad4569b6dd47f7feabafa3571f842434425548335ac6e690dd07168d8bc5b77979c1a6702334f529f5783f79e942fd2cd03f6e55ac2cf496e849fde9c446fab46a8d27db1e3100f275a777d385b44e3cbc045cabac9da36cae040ad516082324c96127cf29f4535eb5b7ebacfe2a1d6d3aab8ec0483d32079a859ff70f9215970a8beebb1c164c474e82438174c8eeb6fbc8cb4594b88c9448f1d40b09beaecac5b45db6e41434a122b695c5a85862d8eae40b3268f6f37e414337be38eba7ab5bbf303d01f4b7ae07fd73edc2f3be05e43948a34418a3272509c43c2811a821e5c982ba51874ac7dc9dd79a80cc2f05f6f664c9dbb2e454435137da06ce44de45532a56a3a7007a2d0c6b435f726f95104bfa6e707046fc154bae91898d03a1a0ac6f9b45e471646e2555ac79e3fe87eb1781e26f20500240c379274fe91096e60d1545a8045571fdab9b530d0d6e7e8746e78bf9f20f4e86f0600000000000000000000000000000000000000000000000000000000000000b5f8b301e1a00000000000000000000000000000000000000000000000000006445941624000f86cf501f3944a6848f78cefad797d025241cc8557d71a2e294394000000000000000000000000000000000000000088042963456b4006a0f501f3946878616891f0e320f0b52906d4cba11677acd77294000000000000000000000000000000000000000088016345785d8a000080a00000000000000000000000000000000000004661737420457869742054657374000000000000000000000000000000000000000000000000000000000000000000000000000000000002000bfbbf0e382245c5cf76853509353a24f83cefca7eefc6109173e873950b46224ed5c02d6d48c8932486c99d3ad999e5d8949dc3be3b3058cc2979690c3e3a621c792b14bf66f82af36f00f5fba7014fa0c1e2ff3c7c273bfe523c1acf67dc3f5fa080a686a5a0d05c3d4822fd54d632dc9cc04b1616046eba2ce499eb9af79f5eb949690a0404abf4cebafc7cfffa382191b7dd9e7df778581e6fb78efab35fd364c9d5dadad4569b6dd47f7feabafa3571f842434425548335ac6e690dd07168d8bc5b77979c1a6702334f529f5783f79e942fd2cd03f6e55ac2cf496e849fde9c446fab46a8d27db1e3100f275a777d385b44e3cbc045cabac9da36cae040ad516082324c96127cf29f4535eb5b7ebacfe2a1d6d3aab8ec0483d32079a859ff70f9215970a8beebb1c164c474e82438174c8eeb6fbc8cb4594b88c9448f1d40b09beaecac5b45db6e41434a122b695c5a85862d8eae40b3268f6f37e414337be38eba7ab5bbf303d01f4b7ae07fd73edc2f3be05e43948a34418a3272509c43c2811a821e5c982ba51874ac7dc9dd79a80cc2f05f6f664c9dbb2e454435137da06ce44de45532a56a3a7007a2d0c6b435f726f95104bfa6e707046fc154bae91898d03a1a0ac6f9b45e471646e2555ac79e3fe87eb1781e26f20500240c379274fe91096e60d1545a8045571fdab9b530d0d6e7e8746e78bf9f20f4e86f06\"\n\n    decoded =\n      encoded\n      |> Base.decode16!(case: :lower)\n      |> Abi.decode_function()\n\n    assert decoded == %{\n             output_tx:\n               \"\\xF8|\\x01\\xE1\\xA0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x06G\\x13\\xBFQp\\x01\\xF6\\xF5\\x01\\xF3\\x94XA(\\xCA\\xD1M\\xF9zn\\xB3\\x8C\\x83):#\\xBD2\\x97\\xD3B\\x94\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\x88\\x01c*/r2 \\0\\x80\\xA0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0Fast Exit Test\",\n             utxo_pos: 1_768_000_000_000_000\n           }\n  end\nend\n"
  },
  {
    "path": "apps/omg_eth/test/omg_eth/root_chain/event_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\ndefmodule OMG.RootChain.EventTest do\n  use ExUnit.Case, async: true\n  alias OMG.Eth.RootChain.Event\n\n  test \"that filter and building an event definition works as expected\" do\n    assert Event.get_events([:deposit_created]) == [\"DepositCreated(address,uint256,address,uint256)\"]\n  end\n\n  test \"that order of returned events is preserved\" do\n    assert Event.get_events([:deposit_created, :in_flight_exit_challenged, :in_flight_exit_started]) == [\n             \"DepositCreated(address,uint256,address,uint256)\",\n             \"InFlightExitChallenged(address,bytes32,uint256)\",\n             \"InFlightExitStarted(address,bytes32)\"\n           ]\n  end\nend\n"
  },
  {
    "path": "apps/omg_eth/test/omg_eth/root_chain_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Eth.RootChainTest do\n  use ExUnit.Case, async: false\n\n  alias ExPlasma.Builder\n  alias ExPlasma.Crypto\n  alias ExPlasma.Transaction.Type.PaymentV1\n  alias OMG.Eth.Configuration\n  alias OMG.Eth.Encoding\n  alias OMG.Eth.RootChain\n  alias OMG.Eth.RootChain.Abi\n  alias Support.DevHelper\n  alias Support.RootChainHelper\n  @eth \"0x0000000000000000000000000000000000000000\"\n  @moduletag :common\n\n  setup_all do\n    {:ok, exit_fn} = Support.DevNode.start()\n\n    on_exit(exit_fn)\n    :ok\n  end\n\n  test \"get_root_deployment_height/2 returns current block number\" do\n    {:ok, number} = RootChain.get_root_deployment_height()\n    assert is_integer(number)\n  end\n\n  describe \"get_standard_exit_structs/2\" do\n    test \"returns a list of standard exits by the given exit ids\" do\n      authority_address = Configuration.authority_address()\n      {:ok, true} = Ethereumex.HttpClient.request(\"personal_unlockAccount\", [authority_address, \"\", 0], [])\n\n      # Make 3 deposits so we can do 3 exits. 1 exit will not be queried, so we can check for false positives\n      _ = add_queue(authority_address)\n      {utxo_pos_1, exit_1} = deposit_then_start_exit(authority_address, 1, @eth)\n      {utxo_pos_2, _exit_2} = deposit_then_start_exit(authority_address, 2, @eth)\n      {utxo_pos_3, exit_3} = deposit_then_start_exit(authority_address, 3, @eth)\n\n      # Now get the exits by their ids and asserts the result\n      exit_id_1 = exit_id_from_receipt(exit_1)\n      exit_id_3 = exit_id_from_receipt(exit_3)\n\n      {:ok, exits} = RootChain.get_standard_exit_structs([exit_id_1, exit_id_3])\n\n      assert length(exits) == 2\n      assert Enum.any?(exits, fn e -> elem(e, 1) == utxo_pos_1 end)\n      refute Enum.any?(exits, fn e -> elem(e, 1) == utxo_pos_2 end)\n      assert Enum.any?(exits, fn e -> elem(e, 1) == utxo_pos_3 end)\n    end\n  end\n\n  defp deposit_then_start_exit(owner, amount, currency) do\n    owner = Encoding.from_hex(owner)\n    currency = Encoding.from_hex(currency)\n    rlp = deposit_transaction(amount, owner, currency)\n\n    {:ok, deposit_tx} =\n      rlp\n      |> RootChainHelper.deposit(amount, owner)\n      |> DevHelper.transact_sync!()\n\n    deposit_txlog = hd(deposit_tx[\"logs\"])\n    deposit_blknum = RootChainHelper.deposit_blknum_from_receipt(deposit_tx)\n    deposit_txindex = OMG.Eth.Encoding.int_from_hex(deposit_txlog[\"transactionIndex\"])\n\n    utxo_pos = ExPlasma.Output.Position.pos(%{blknum: deposit_blknum, txindex: deposit_txindex, oindex: 0})\n    proof = ExPlasma.Merkle.proof([rlp], 0)\n\n    {:ok, start_exit_tx} =\n      utxo_pos\n      |> RootChainHelper.start_exit(rlp, proof, owner)\n      |> DevHelper.transact_sync!()\n\n    {utxo_pos, start_exit_tx}\n  end\n\n  defp exit_id_from_receipt(%{\"logs\" => logs}) do\n    topic =\n      \"ExitStarted(address,uint160)\"\n      |> Crypto.keccak_hash()\n      |> Encoding.to_hex()\n\n    [%{exit_id: exit_id}] =\n      logs\n      |> Enum.filter(&(topic in &1[\"topics\"]))\n      |> Enum.map(fn log ->\n        Abi.decode_log(log)\n      end)\n\n    exit_id\n  end\n\n  defp add_queue(authority_address) do\n    {:ok, true} = Ethereumex.HttpClient.request(\"personal_unlockAccount\", [authority_address, \"\", 0], [])\n\n    add_exit_queue = RootChainHelper.add_exit_queue(1, \"0x0000000000000000000000000000000000000000\")\n\n    {:ok, %{\"status\" => \"0x1\"}} = Support.DevHelper.transact_sync!(add_exit_queue)\n  end\n\n  defp deposit_transaction(amount_in_wei, address, currency) do\n    address\n    |> deposit(currency, amount_in_wei)\n    |> ExPlasma.encode!(signed: false)\n  end\n\n  defp deposit(owner, token, amount) do\n    output = PaymentV1.new_output(owner, token, amount)\n    Builder.new(ExPlasma.payment_v1(), outputs: [output])\n  end\nend\n"
  },
  {
    "path": "apps/omg_eth/test/support/defaults.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Eth.Defaults do\n  @moduledoc \"\"\"\n  Internal defaults of non-production critical calls to `OMG.Eth.RootChain` and `OMG.Eth.Token`.\n\n  Don't ever use this for `OMG.Eth.RootChain.submit_block/5` or any other production related code.\n  Don't ever use this for `OMG.Eth.submit_block/5` or any other production related code.\n  \"\"\"\n\n  alias OMG.Eth.Encoding\n\n  # safe, reasonable amount, equal to the testnet block gas limit\n  @lots_of_gas 5_712_388\n  @gas_price 1_000_000_000\n\n  def tx_defaults() do\n    Enum.map([value: 0, gasPrice: @gas_price, gas: @lots_of_gas], fn {k, v} -> {k, Encoding.to_hex(v)} end)\n  end\nend\n"
  },
  {
    "path": "apps/omg_eth/test/support/dev_geth.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Eth.DevGeth do\n  use GenServer\n\n  @moduledoc \"\"\"\n  Helper module for deployment of contracts to dev geth.\n  \"\"\"\n\n  @doc \"\"\"\n  Run geth in temp dir, kill it with SIGKILL when done.\n  \"\"\"\n\n  require Logger\n\n  alias Support.WaitFor\n\n  def start() do\n    {:ok, homedir} = Briefly.create(directory: true)\n    snapshot_dir = Path.expand(Path.join([Mix.Project.build_path(), \"../../\", \"data/geth/\"]))\n    {\"\", 0} = System.cmd(\"cp\", [\"-rf\", snapshot_dir, homedir])\n\n    keystore = Path.join([homedir, \"/geth/keystore\"])\n    datadir = Path.join([homedir, \"/geth\"])\n    :ok = File.write!(\"/tmp/geth-blank-password\", \"\")\n    geth = ~s(geth --miner.gastarget 7500000 \\\n            --nodiscover \\\n            --maxpeers 0 \\\n            --miner.gasprice \"10\" \\\n            --syncmode 'full' \\\n            --networkid 1337 \\\n            --gasprice '1' \\\n            --keystore #{keystore} \\\n            --password /tmp/geth-blank-password \\\n            --unlock \"0,1\" \\\n            --rpc --rpcapi personal,web3,eth,net --rpcaddr 0.0.0.0 --rpcvhosts='*' --rpcport=8545 \\\n            --ws --wsaddr 0.0.0.0 --wsorigins='*' \\\n            --allow-insecure-unlock \\\n            --mine --datadir #{datadir} 2>&1)\n    _pid = launch(geth)\n\n    {:ok, :ready} = WaitFor.eth_rpc(20_000)\n\n    on_exit = fn ->\n      Exexec.run(\"pkill -9 geth\")\n    end\n\n    {:ok, on_exit}\n  end\n\n  @impl true\n  def init(cmd) do\n    _ = Logger.debug(\"Starting geth\")\n\n    {:ok, geth_proc, os_proc} = Exexec.run(cmd, stdout: true)\n\n    {:ok, %{geth_proc: geth_proc, os_proc: os_proc, ready?: false}}\n  end\n\n  @impl true\n  def handle_info({:stdout, pid, stdout}, %{os_proc: pid} = state) do\n    new_state =\n      if String.contains?(stdout, \"IPC endpoint opened\") do\n        Map.put(state, :ready?, true)\n      else\n        state\n      end\n\n    _ =\n      case Application.get_env(:omg_eth, :node_logging_in_debug) do\n        true -> Logger.debug(\"eth node: \" <> stdout)\n        _ -> :ok\n      end\n\n    {:noreply, new_state}\n  end\n\n  @impl true\n  def handle_call(:ready?, _from, state) do\n    {:reply, state.ready?, state}\n  end\n\n  # PRIVATE\n\n  defp launch(cmd) do\n    {:ok, pid} = start_link(cmd)\n\n    waiting_task = fn ->\n      wait_for_rpc(pid)\n    end\n\n    waiting_task\n    |> Task.async()\n    |> Task.await(90_000)\n\n    pid\n  end\n\n  defp wait_for_rpc(pid) do\n    if ready?(pid) do\n      :ok\n    else\n      Process.sleep(2_000)\n      wait_for_rpc(pid)\n    end\n  end\n\n  defp start_link(cmd) do\n    GenServer.start_link(__MODULE__, cmd)\n  end\n\n  defp ready?(pid) do\n    GenServer.call(pid, :ready?)\n  end\nend\n"
  },
  {
    "path": "apps/omg_eth/test/support/dev_helper.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule Support.DevHelper do\n  @moduledoc \"\"\"\n  Helpers used when setting up development environment and test fixtures, related to contracts and ethereum.\n  Run against `geth --dev` and similar.\n  \"\"\"\n  import OMG.Eth.Encoding, only: [to_hex: 1, from_hex: 1, int_from_hex: 1]\n\n  require Logger\n\n  alias OMG.Eth\n  alias OMG.Eth.Client\n  alias OMG.Eth.Configuration\n  alias OMG.Eth.RootChain\n  alias OMG.Eth.Transaction\n  alias Support.WaitFor\n\n  @one_hundred_eth trunc(:math.pow(10, 18) * 100)\n\n  # about 4 Ethereum blocks on \"realistic\" networks, use to timeout synchronous operations in demos on testnets\n  # NOTE: such timeout works only in dev setting; on mainnet one must track its transactions carefully\n  @about_4_blocks_time 60_000\n\n  @passphrase \"ThisIsATestnetPassphrase\"\n\n  @doc \"\"\"\n  Will take a map with eth-account information (from &generate_entity/0) and then\n  import priv key->unlock->fund with test ETH on that account\n\n  Options:\n    - :faucet - the address to send the test ETH from, assumed to be unlocked and have the necessary funds\n    - :initial_funds_wei - the amount of test ETH that will be granted to every generated user\n  \"\"\"\n  def import_unlock_fund(account, opts \\\\ []) do\n    {:ok, account_enc} = create_account_from_secret(account, @passphrase)\n    {:ok, _} = fund_address_from_faucet(account_enc, opts)\n\n    {:ok, account_enc}\n  end\n\n  @doc \"\"\"\n  Use with contract-transacting functions that return {:ok, txhash}, e.g. `Eth.Token.mint`, for synchronous waiting\n  for mining of a successful result\n  \"\"\"\n  @spec transact_sync!({:ok, Eth.hash()}, keyword()) :: {:ok, map}\n  def transact_sync!({:ok, txhash} = _transaction_submission_result, opts \\\\ []) when byte_size(txhash) == 32 do\n    timeout = Keyword.get(opts, :timeout, @about_4_blocks_time)\n\n    {:ok, _} =\n      txhash\n      |> WaitFor.eth_receipt(timeout)\n      |> case do\n        {:ok, %{\"status\" => \"0x1\"} = receipt} ->\n          {:ok, Map.update!(receipt, \"blockNumber\", &int_from_hex(&1))}\n\n        {:ok, %{\"status\" => \"0x0\"} = receipt} ->\n          case get_reason(txhash) do\n            \"Exit queue exists\" -> {:ok, Map.update!(receipt, \"blockNumber\", &int_from_hex(&1))}\n            reason -> {:error, Map.put(receipt, \"reason\", reason)}\n          end\n\n        other ->\n          other\n      end\n  end\n\n  @doc \"\"\"\n  Uses `transact_sync!` for synchronous deploy-transaction sending and extracts important data from the receipt\n  \"\"\"\n  @spec deploy_sync!({:ok, Eth.hash()}) :: {:ok, Eth.hash(), Eth.address()}\n  def deploy_sync!({:ok, txhash} = transaction_submission_result) do\n    {:ok, %{\"contractAddress\" => contract, \"status\" => \"0x1\", \"gasUsed\" => _gas_used}} =\n      transact_sync!(transaction_submission_result)\n\n    {:ok, txhash, from_hex(contract)}\n  end\n\n  def wait_for_root_chain_block(awaited_eth_height, timeout \\\\ 600_000) do\n    f = fn ->\n      {:ok, eth_height} = Client.get_ethereum_height()\n\n      if eth_height < awaited_eth_height, do: :repeat, else: {:ok, eth_height}\n    end\n\n    WaitFor.ok(f, timeout)\n  end\n\n  def wait_for_next_child_block(blknum) do\n    timeout = 10_000\n\n    f = fn ->\n      next_num = RootChain.next_child_block()\n\n      if next_num < blknum, do: :repeat, else: {:ok, next_num}\n    end\n\n    WaitFor.ok(f, timeout)\n  end\n\n  def create_account_from_secret(account, passphrase) do\n    method_name = \"personal_importRawKey\"\n    secret = Base.encode16(account.priv)\n\n    case Ethereumex.HttpClient.request(method_name, [secret, passphrase], []) do\n      {:ok, response} ->\n        {:ok, response}\n\n      {:error, %{\"code\" => -32_000, \"message\" => \"account already exists\"}} ->\n        {:ok, \"0x\" <> Base.encode16(account.addr)}\n    end\n  end\n\n  defp fund_address_from_faucet(account_enc, opts) do\n    {:ok, [default_faucet | _]} = Ethereumex.HttpClient.eth_accounts()\n    defaults = [faucet: default_faucet, initial_funds_wei: @one_hundred_eth]\n\n    %{faucet: faucet, initial_funds_wei: initial_funds_wei} =\n      defaults\n      |> Keyword.merge(opts)\n      |> Enum.into(%{})\n\n    unlock_if_possible(account_enc)\n\n    params = %{from: faucet, to: account_enc, value: to_hex(initial_funds_wei)}\n\n    {:ok, tx_fund} = Transaction.send(Configuration.eth_node(), params)\n\n    case Keyword.get(opts, :timeout) do\n      nil -> WaitFor.eth_receipt(tx_fund, @about_4_blocks_time)\n      timeout -> WaitFor.eth_receipt(tx_fund, timeout)\n    end\n  end\n\n  defp unlock_if_possible(account_enc) do\n    Ethereumex.HttpClient.request(\"personal_unlockAccount\", [account_enc, @passphrase, 0], [])\n  end\n\n  # gets the `revert` reason for a failed transaction by txhash\n  # based on https://gist.github.com/gluk64/fdea559472d957f1138ed93bcbc6f78a\n  defp get_reason(txhash) do\n    # we get the exact transaction details\n    {:ok, tx} = Ethereumex.HttpClient.eth_get_transaction_by_hash(to_hex(txhash))\n    # we use them (with minor tweak) to be called on the Ethereum client at the exact block of the original call\n    {:ok, call_result} = tx |> Map.put(\"data\", tx[\"input\"]) |> Ethereumex.HttpClient.eth_call(tx[\"blockNumber\"])\n    # this call result is hex decoded and then additionally decoded with ABI, should yield a readable ascii-string\n    if call_result == \"0x\", do: \"out of gas, reason is 0x\", else: call_result |> from_hex() |> abi_decode_reason()\n  end\n\n  defp abi_decode_reason(result) do\n    bytes_to_throw_away = 2 * 32 + 4\n    # trimming the 4-byte function selector, 32 byte size of size and 32 byte size\n    result |> binary_part(bytes_to_throw_away, byte_size(result) - bytes_to_throw_away) |> String.trim(<<0>>)\n  end\nend\n"
  },
  {
    "path": "apps/omg_eth/test/support/dev_node.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule Support.DevNode do\n  @moduledoc \"\"\"\n  Common library for running geth and parity in dev mode.\n  \"\"\"\n  require Logger\n\n  def start() do\n    OMG.Eth.DevGeth.start()\n  end\nend\n"
  },
  {
    "path": "apps/omg_eth/test/support/root_chain_helper.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule Support.RootChainHelper do\n  @moduledoc \"\"\"\n    Helper functions for RootChain.\n  \"\"\"\n  import OMG.Eth.Encoding, only: [to_hex: 1, from_hex: 1]\n\n  alias ExPlasma.Crypto\n  alias OMG.Eth.Blockchain.BitHelper\n  alias OMG.Eth.Configuration\n  alias OMG.Eth.RootChain.Abi\n  alias OMG.Eth.TransactionHelper\n  @tx_defaults OMG.Eth.Defaults.tx_defaults()\n\n  @type optional_addr_t() :: <<_::160>> | nil\n\n  @gas_add_exit_queue 800_000\n  @gas_start_exit 400_000\n  @gas_challenge_exit 300_000\n  @gas_deposit 180_000\n  @gas_deposit_from 250_000\n  @gas_init 1_000_000\n  @gas_start_in_flight_exit 1_500_000\n  @gas_respond_to_non_canonical_challenge 1_000_000\n  @gas_challenge_in_flight_exit_not_canonical 1_000_000\n  @gas_piggyback 1_000_000\n\n  @standard_exit_bond 14_000_000_000_000_000\n  @ife_bond 37_000_000_000_000_000\n  @piggyback_bond 28_000_000_000_000_000\n\n  @type in_flight_exit_piggybacked_event() :: %{owner: <<_::160>>, tx_hash: <<_::256>>, output_index: non_neg_integer}\n\n  def start_exit(utxo_pos, tx_bytes, proof, from) do\n    opts =\n      @tx_defaults\n      |> Keyword.put(:gas, @gas_start_exit)\n      |> Keyword.put(:value, @standard_exit_bond)\n\n    contract = from_hex(Configuration.contracts().payment_exit_game)\n    backend = :geth\n\n    TransactionHelper.contract_transact(\n      backend,\n      from,\n      contract,\n      \"startStandardExit((uint256,bytes,bytes))\",\n      [{utxo_pos, tx_bytes, proof}],\n      opts\n    )\n  end\n\n  def piggyback_in_flight_exit_on_input(in_flight_tx, input_index, from) do\n    opts =\n      @tx_defaults\n      |> Keyword.put(:gas, @gas_piggyback)\n      |> Keyword.put(:value, @piggyback_bond)\n\n    contract = from_hex(Configuration.contracts().payment_exit_game)\n    signature = \"piggybackInFlightExitOnInput((bytes,uint16))\"\n    args = [{in_flight_tx, input_index}]\n    backend = Configuration.eth_node()\n\n    TransactionHelper.contract_transact(backend, from, contract, signature, args, opts)\n  end\n\n  def piggyback_in_flight_exit_on_output(in_flight_tx, output_index, from) do\n    opts =\n      @tx_defaults\n      |> Keyword.put(:gas, @gas_piggyback)\n      |> Keyword.put(:value, @piggyback_bond)\n\n    contract = from_hex(Configuration.contracts().payment_exit_game)\n\n    signature = \"piggybackInFlightExitOnOutput((bytes,uint16))\"\n    args = [{in_flight_tx, output_index}]\n    backend = Configuration.eth_node()\n    TransactionHelper.contract_transact(backend, from, contract, signature, args, opts)\n  end\n\n  def deposit(tx_bytes, value, from) do\n    opts = []\n    defaults = Keyword.put(@tx_defaults, :gas, @gas_deposit)\n\n    opts =\n      defaults\n      |> Keyword.merge(opts)\n      |> Keyword.put(:value, value)\n\n    contract = from_hex(Configuration.contracts().eth_vault)\n    backend = Configuration.eth_node()\n    TransactionHelper.contract_transact(backend, from, contract, \"deposit(bytes)\", [tx_bytes], opts)\n  end\n\n  def deposit_from(tx, from) do\n    opts = Keyword.put(@tx_defaults, :gas, @gas_deposit_from)\n    contract = from_hex(Configuration.contracts().erc20_vault)\n    backend = Configuration.eth_node()\n    TransactionHelper.contract_transact(backend, from, contract, \"deposit(bytes)\", [tx], opts)\n  end\n\n  def add_exit_queue(vault_id, token) do\n    opts = Keyword.put(@tx_defaults, :gas, @gas_add_exit_queue)\n\n    contract = from_hex(Configuration.contracts().plasma_framework)\n    token = from_hex(token)\n    {:ok, [from | _]} = Ethereumex.HttpClient.eth_accounts()\n    backend = Configuration.eth_node()\n\n    TransactionHelper.contract_transact(\n      backend,\n      from_hex(from),\n      contract,\n      \"addExitQueue(uint256, address)\",\n      [vault_id, token],\n      opts\n    )\n  end\n\n  def challenge_exit(exit_id, exiting_tx, challenge_tx, input_index, challenge_tx_sig, from) do\n    opts = Keyword.put(@tx_defaults, :gas, @gas_challenge_exit)\n    sender_data = BitHelper.kec(from)\n\n    contract = from_hex(Configuration.contracts().payment_exit_game)\n    signature = \"challengeStandardExit((uint160,bytes,bytes,uint16,bytes,bytes32))\"\n    args = [{exit_id, exiting_tx, challenge_tx, input_index, challenge_tx_sig, sender_data}]\n\n    backend = Configuration.eth_node()\n    TransactionHelper.contract_transact(backend, from, contract, signature, args, opts)\n  end\n\n  def activate_child_chain(from \\\\ nil) do\n    opts = Keyword.put(@tx_defaults, :gas, @gas_init)\n    contract = Configuration.contracts().plasma_framework\n    from = from || from_hex(Configuration.authority_address())\n    backend = Configuration.eth_node()\n\n    TransactionHelper.contract_transact(backend, from, contract, \"activateChildChain()\", [], opts)\n  end\n\n  def in_flight_exit(\n        in_flight_tx,\n        input_txs,\n        input_utxos_pos,\n        input_txs_inclusion_proofs,\n        in_flight_tx_sigs,\n        from\n      ) do\n    opts =\n      @tx_defaults\n      |> Keyword.put(:value, @ife_bond)\n      |> Keyword.put(:gas, @gas_start_in_flight_exit)\n\n    contract = from_hex(Configuration.contracts().payment_exit_game)\n    signature = \"startInFlightExit((bytes,bytes[],uint256[],bytes[],bytes[]))\"\n\n    args = [{in_flight_tx, input_txs, input_utxos_pos, input_txs_inclusion_proofs, in_flight_tx_sigs}]\n\n    backend = Configuration.eth_node()\n\n    TransactionHelper.contract_transact(backend, from, contract, signature, args, opts)\n  end\n\n  def process_exits(vault_id, token, top_exit_id, exits_to_process, from) do\n    opts = @tx_defaults\n    token = from_hex(token)\n    contract = from_hex(Configuration.contracts().plasma_framework)\n    signature = \"processExits(uint256,address,uint160,uint256)\"\n    args = [vault_id, token, top_exit_id, exits_to_process]\n    backend = Configuration.eth_node()\n\n    TransactionHelper.contract_transact(backend, from, contract, signature, args, opts)\n  end\n\n  # credo:disable-for-next-line Credo.Check.Refactor.FunctionArity\n  def challenge_in_flight_exit_not_canonical(\n        input_tx_bytes,\n        input_utxo_pos,\n        in_flight_txbytes,\n        in_flight_input_index,\n        competing_txbytes,\n        competing_input_index,\n        competing_tx_pos,\n        competing_proof,\n        competing_sig,\n        from\n      ) do\n    opts = Keyword.put(@tx_defaults, :gas, @gas_challenge_in_flight_exit_not_canonical)\n\n    contract = from_hex(Configuration.contracts().payment_exit_game)\n\n    signature = \"challengeInFlightExitNotCanonical((bytes,uint256,bytes,uint16,bytes,uint16,uint256,bytes,bytes))\"\n\n    args = [\n      {input_tx_bytes, input_utxo_pos, in_flight_txbytes, in_flight_input_index, competing_txbytes,\n       competing_input_index, competing_tx_pos, competing_proof, competing_sig}\n    ]\n\n    backend = Configuration.eth_node()\n\n    TransactionHelper.contract_transact(backend, from, contract, signature, args, opts)\n  end\n\n  def respond_to_non_canonical_challenge(\n        in_flight_tx,\n        in_flight_tx_pos,\n        in_flight_tx_inclusion_proof,\n        from\n      ) do\n    opts = Keyword.put(@tx_defaults, :gas, @gas_respond_to_non_canonical_challenge)\n\n    contract = from_hex(Configuration.contracts().payment_exit_game)\n    signature = \"respondToNonCanonicalChallenge(bytes,uint256,bytes)\"\n\n    args = [in_flight_tx, in_flight_tx_pos, in_flight_tx_inclusion_proof]\n    backend = Configuration.eth_node()\n\n    TransactionHelper.contract_transact(backend, from, contract, signature, args, opts)\n  end\n\n  # credo:disable-for-next-line Credo.Check.Refactor.FunctionArity\n  def challenge_in_flight_exit_input_spent(\n        in_flight_txbytes,\n        in_flight_input_index,\n        spending_txbytes,\n        spending_tx_input_index,\n        spending_tx_sig,\n        input_txbytes,\n        input_utxo_pos,\n        from\n      ) do\n    opts = @tx_defaults\n\n    contract = from_hex(Configuration.contracts().payment_exit_game)\n    signature = \"challengeInFlightExitInputSpent((bytes,uint16,bytes,uint16,bytes,bytes,uint256))\"\n\n    args = [\n      {in_flight_txbytes, in_flight_input_index, spending_txbytes, spending_tx_input_index, spending_tx_sig,\n       input_txbytes, input_utxo_pos}\n    ]\n\n    backend = Application.fetch_env!(:omg_eth, :eth_node)\n\n    TransactionHelper.contract_transact(backend, from, contract, signature, args, opts)\n  end\n\n  # credo:disable-for-next-line Credo.Check.Refactor.FunctionArity\n  def challenge_in_flight_exit_output_spent(\n        in_flight_txbytes,\n        in_flight_output_pos,\n        in_flight_tx_inclusion_proof,\n        spending_txbytes,\n        spending_tx_input_index,\n        spending_tx_sig,\n        from\n      ) do\n    opts = @tx_defaults\n    contract = from_hex(Configuration.contracts().payment_exit_game)\n    signature = \"challengeInFlightExitOutputSpent((bytes,bytes,uint256,bytes,uint16,bytes))\"\n\n    args = [\n      {in_flight_txbytes, in_flight_tx_inclusion_proof, in_flight_output_pos, spending_txbytes, spending_tx_input_index,\n       spending_tx_sig}\n    ]\n\n    backend = Application.fetch_env!(:omg_eth, :eth_node)\n\n    TransactionHelper.contract_transact(backend, from, contract, signature, args, opts)\n  end\n\n  def deposit_blknum_from_receipt(%{\"logs\" => logs}) do\n    topic =\n      \"DepositCreated(address,uint256,address,uint256)\"\n      |> Crypto.keccak_hash()\n      |> to_hex()\n\n    [%{blknum: deposit_blknum}] =\n      logs\n      |> Enum.filter(&(topic in &1[\"topics\"]))\n      |> Enum.map(&Abi.decode_log/1)\n\n    deposit_blknum\n  end\nend\n"
  },
  {
    "path": "apps/omg_eth/test/support/snapshot_contracts.ex",
    "content": "# Copyright 2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule Support.SnapshotContracts do\n  @moduledoc \"\"\"\n  Provides facilities to use contracts from the `geth` snapshot including the `plasma-contracts` dev deployment\n  \"\"\"\n\n  def parse_contracts() do\n    local_umbrella_path = Path.join([File.cwd!(), \"../../\", \"localchain_contract_addresses.env\"])\n\n    contract_addreses_path =\n      case File.exists?(local_umbrella_path) do\n        true ->\n          local_umbrella_path\n\n        _ ->\n          # CI/CD\n          Path.join([File.cwd!(), \"localchain_contract_addresses.env\"])\n      end\n\n    contract_addreses_path\n    |> File.read!()\n    |> String.split(\"\\n\", trim: true)\n    |> List.flatten()\n    |> Enum.reduce(%{}, fn line, acc ->\n      [key, value] = String.split(line, \"=\")\n      Map.put(acc, key, value)\n    end)\n  end\nend\n"
  },
  {
    "path": "apps/omg_eth/test/support/token.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Eth.Token do\n  @moduledoc \"\"\"\n  Adapter/port to tokens that implement ERC20 interface\n  \"\"\"\n\n  alias OMG.Eth.Encoding\n  alias OMG.Eth.TransactionHelper\n\n  @tx_defaults OMG.Eth.Defaults.tx_defaults()\n\n  @gas_token_ops 80_000\n\n  ##########\n  # writes #\n  ##########\n\n  def mint(owner, amount, token, opts \\\\ []) do\n    opts = @tx_defaults |> Keyword.put(:gas, @gas_token_ops) |> Keyword.merge(opts)\n\n    {:ok, [from | _]} = Ethereumex.HttpClient.eth_accounts()\n    backend = Application.fetch_env!(:omg_eth, :eth_node)\n\n    TransactionHelper.contract_transact(\n      backend,\n      Encoding.from_hex(from),\n      token,\n      \"mint(address,uint256)\",\n      [owner, amount],\n      opts\n    )\n  end\n\n  def approve(from, spender, amount, token, opts \\\\ []) do\n    opts = @tx_defaults |> Keyword.put(:gas, @gas_token_ops) |> Keyword.merge(opts)\n    backend = Application.fetch_env!(:omg_eth, :eth_node)\n    TransactionHelper.contract_transact(backend, from, token, \"approve(address,uint256)\", [spender, amount], opts)\n  end\nend\n"
  },
  {
    "path": "apps/omg_eth/test/support/transaction_helper.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Eth.TransactionHelper do\n  @moduledoc \"\"\"\n  Standard interface for transacting with Ethereum\n  \"\"\"\n\n  alias OMG.Eth.Encoding\n  alias OMG.Eth.Transaction\n\n  @spec contract_transact(atom(), <<_::160>>, <<_::160>>, binary, [any]) :: {:ok, <<_::256>>} | {:error, any}\n  def contract_transact(backend, from, to, signature, args, opts \\\\ []) do\n    data = encode_tx_data(signature, args)\n\n    txmap =\n      %{from: Encoding.to_hex(from), to: Encoding.to_hex(to), data: data}\n      |> Map.merge(Map.new(opts))\n      |> encode_all_integer_opts()\n\n    Transaction.send(backend, txmap)\n  end\n\n  defp encode_tx_data(signature, args) do\n    signature\n    |> ABI.encode(args)\n    |> Encoding.to_hex()\n  end\n\n  defp encode_all_integer_opts(opts) do\n    opts\n    |> Enum.filter(fn {_k, v} -> is_integer(v) end)\n    |> Enum.into(opts, fn {k, v} -> {k, Encoding.to_hex(v)} end)\n  end\nend\n"
  },
  {
    "path": "apps/omg_eth/test/support/wait_for.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule Support.WaitFor do\n  @moduledoc \"\"\"\n  Generic wait_for_* utils, styled after web3 counterparts\n  \"\"\"\n\n  alias OMG.Eth.Encoding\n  alias __MODULE__\n\n  def eth_rpc(timeout \\\\ 10_000) do\n    f = fn ->\n      case Ethereumex.HttpClient.eth_syncing() do\n        {:ok, false} ->\n          {:ok, :ready}\n\n        _ ->\n          :repeat\n      end\n    end\n\n    WaitFor.ok(f, timeout)\n  end\n\n  @doc \"\"\"\n  NOTE: `eth_receipt` takes txhash as raw decoded binary, like the rest of Eth APIs, but binaries in the receipt\n  returned are in `0xhex-style`\n\n  This is low-level, consider using `|> Support.DevHelper.transact_sync!()` for eth-transactions' syncronicity in tests\n  \"\"\"\n  def eth_receipt(txhash, timeout \\\\ 15_000) do\n    f = fn ->\n      txhash\n      |> Encoding.to_hex()\n      |> Ethereumex.HttpClient.eth_get_transaction_receipt()\n      |> case do\n        {:ok, receipt} when receipt != nil -> {:ok, receipt}\n        _ -> :repeat\n      end\n    end\n\n    WaitFor.ok(f, timeout)\n  end\n\n  # Repeats f until f returns {:ok, ...}, :ok OR exception is raised (see :erlang.exit, :erlang.error) OR timeout\n  # after `timeout` milliseconds specified\n  #\n  # Simple throws and :badmatch are treated as signals to repeat\n  def ok(f, timeout \\\\ 5_000) do\n    fn -> repeat_until_ok(f) end\n    |> Task.async()\n    |> Task.await(timeout)\n  end\n\n  defp repeat_until_ok(f) do\n    Process.sleep(100)\n\n    try do\n      case f.() do\n        :ok = return -> return\n        {:ok, _} = return -> return\n        _ -> repeat_until_ok(f)\n      end\n    catch\n      _something -> repeat_until_ok(f)\n      :error, {:badmatch, _} = _error -> repeat_until_ok(f)\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_eth/test/test_helper.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nExUnit.configure(exclude: [integration: true, property: true, wrappers: true, common: true])\nExUnit.start()\n{:ok, _} = Application.ensure_all_started(:ethereumex)\n{:ok, _} = Application.ensure_all_started(:briefly)\n{:ok, _} = Application.ensure_all_started(:erlexec)\n"
  },
  {
    "path": "apps/omg_status/.gitignore",
    "content": "# The directory Mix will write compiled artifacts to.\n/_build\n\n# If you run \"mix test --cover\", coverage assets end up here.\n/cover\n\n# The directory Mix downloads your dependencies sources to.\n/deps\n\n# Where 3rd-party dependencies like ExDoc output generated docs.\n/doc\n\n# If the VM crashes, it generates a dump, let's ignore it too.\nerl_crash.dump\n\n# Also ignore archive artifacts (built via \"mix archive.build\").\n*.ez\n"
  },
  {
    "path": "apps/omg_status/README.md",
    "content": "# Status\nStatus is a umbrella application. Its purpose is to gather and send metrics.\n"
  },
  {
    "path": "apps/omg_status/lib/omg_status/alert/alarm.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Status.Alert.Alarm do\n  @moduledoc \"\"\"\n  Interface for raising and clearing alarms related to OMG Status.\n  \"\"\"\n  alias OMG.Status.Alert.AlarmHandler\n\n  @typedoc \"\"\"\n  The raw alarm being used to `set` the Alarm\n  \"\"\"\n  @type alarm_detail :: %{\n          node: Node.t(),\n          reporter: module()\n        }\n\n  @type alarms ::\n          {:boot_in_progress\n           | :ethereum_connection_error\n           | :ethereum_stalled_sync\n           | :invalid_fee_source\n           | :statsd_client_connection\n           | :main_supervisor_halted\n           | :system_memory_too_high\n           | :block_submit_stalled, alarm_detail}\n\n  def alarm_types(),\n    do: [\n      :boot_in_progress,\n      :ethereum_connection_error,\n      :ethereum_stalled_sync,\n      :invalid_fee_source,\n      :statsd_client_connection,\n      :main_supervisor_halted,\n      :system_memory_too_high,\n      :block_submit_stalled\n    ]\n\n  @spec statsd_client_connection(module()) :: {:statsd_client_connection, alarm_detail}\n  def statsd_client_connection(reporter),\n    do: {:statsd_client_connection, %{node: Node.self(), reporter: reporter}}\n\n  @spec ethereum_connection_error(module()) :: {:ethereum_connection_error, alarm_detail}\n  def ethereum_connection_error(reporter),\n    do: {:ethereum_connection_error, %{node: Node.self(), reporter: reporter}}\n\n  @spec ethereum_stalled_sync(module()) :: {:ethereum_stalled_sync, alarm_detail}\n  def ethereum_stalled_sync(reporter),\n    do: {:ethereum_stalled_sync, %{node: Node.self(), reporter: reporter}}\n\n  @spec boot_in_progress(module()) :: {:boot_in_progress, alarm_detail}\n  def boot_in_progress(reporter),\n    do: {:boot_in_progress, %{node: Node.self(), reporter: reporter}}\n\n  @spec invalid_fee_source(module()) :: {:invalid_fee_source, alarm_detail}\n  def invalid_fee_source(reporter),\n    do: {:invalid_fee_source, %{node: Node.self(), reporter: reporter}}\n\n  @spec main_supervisor_halted(module()) :: {:main_supervisor_halted, alarm_detail}\n  def main_supervisor_halted(reporter),\n    do: {:main_supervisor_halted, %{node: Node.self(), reporter: reporter}}\n\n  @spec system_memory_too_high(module()) :: {:system_memory_too_high, alarm_detail}\n  def system_memory_too_high(reporter),\n    do: {:system_memory_too_high, %{node: Node.self(), reporter: reporter}}\n\n  @spec block_submit_stalled(module()) :: {:block_submit_stalled, alarm_detail}\n  def block_submit_stalled(reporter),\n    do: {:block_submit_stalled, %{node: Node.self(), reporter: reporter}}\n\n  @spec set(alarms()) :: :ok | :duplicate\n  def set(alarm), do: do_raise(alarm)\n\n  @spec clear(alarms()) :: :ok | :not_raised\n  def clear(alarm), do: do_clear(alarm)\n\n  def clear_all() do\n    Enum.each(all(), &:alarm_handler.clear_alarm(&1))\n  end\n\n  def all() do\n    :gen_event.call(:alarm_handler, AlarmHandler, :get_alarms)\n  end\n\n  defp do_raise(alarm) do\n    if Enum.member?(all(), alarm) do\n      :duplicate\n    else\n      :alarm_handler.set_alarm(alarm)\n    end\n  end\n\n  defp do_clear(alarm) do\n    if Enum.member?(all(), alarm) do\n      :alarm_handler.clear_alarm(alarm)\n    else\n      :not_raised\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_status/lib/omg_status/alert/alarm_handler.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Status.Alert.AlarmHandler do\n  @moduledoc \"\"\"\n    This is the SASL alarm handler process.\n  \"\"\"\n  alias OMG.Status.Alert.Alarm\n  @table_name :alarms\n\n  def install() do\n    case Enum.member?(:gen_event.which_handlers(:alarm_handler), __MODULE__) do\n      true ->\n        :ok\n\n      false ->\n        previous_alarms = :alarm_handler.get_alarms()\n        :ok = :gen_event.swap_handler(:alarm_handler, {:alarm_handler, :swap}, {__MODULE__, :ok})\n        # migrates old alarms\n        Enum.each(previous_alarms, &:alarm_handler.set_alarm(&1))\n    end\n  end\n\n  def table_name(), do: @table_name\n\n  # -----------------------------------------------------------------\n  # :gen_event handlers\n  # -----------------------------------------------------------------\n  def init(_args) do\n    table_setup()\n    :ok = Enum.each(Alarm.alarm_types(), &write_clear/1)\n    {:ok, %{alarms: []}}\n  end\n\n  def handle_call(:get_alarms, %{alarms: alarms} = state), do: {:ok, alarms, state}\n\n  def handle_event({:set_alarm, new_alarm}, %{alarms: alarms} = state) do\n    # was the alarm raised already and is this our type of alarm?\n\n    case Enum.any?(alarms, &(&1 == new_alarm)) do\n      true ->\n        {:ok, state}\n\n      false ->\n        # the alarm has not been raised before and we're subscribed\n        _ = write_raise(new_alarm)\n        {:ok, %{alarms: [new_alarm | alarms]}}\n    end\n  end\n\n  def handle_event({:clear_alarm, alarm_id}, %{alarms: alarms}) do\n    new_alarms =\n      alarms\n      |> Enum.filter(&(elem(&1, 0) != alarm_id))\n      |> Enum.filter(&(&1 != alarm_id))\n\n    _ = write_clear(alarm_id)\n    {:ok, %{alarms: new_alarms}}\n  end\n\n  def handle_event(_event, state) do\n    {:ok, state}\n  end\n\n  def terminate(:swap, state), do: {__MODULE__, state}\n  def terminate(_, _), do: :ok\n\n  defp table_setup() do\n    _ = if :undefined == :ets.info(@table_name), do: @table_name = :ets.new(@table_name, table_settings())\n  end\n\n  defp table_settings(), do: [:named_table, :set, :protected, read_concurrency: true]\n\n  defp write_raise(alarm) when is_tuple(alarm), do: write_raise(elem(alarm, 0))\n  defp write_raise(key) when is_atom(key), do: :ets.update_counter(@table_name, key, {2, 1, 1, 1}, {key, 0})\n\n  defp write_clear(alarm) when is_tuple(alarm), do: write_clear(elem(alarm, 0))\n  defp write_clear(key) when is_atom(key), do: :ets.update_counter(@table_name, key, {2, -1, 0, 0}, {key, 1})\nend\n"
  },
  {
    "path": "apps/omg_status/lib/omg_status/alert/alarm_printer.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Status.AlarmPrinter do\n  @moduledoc \"\"\"\n    A loud reminder of raised events\n  \"\"\"\n  use GenServer\n  require Logger\n  @interval 5_000\n  # 5 minutes\n  @max_interval 300_000\n  def start_link(args) do\n    GenServer.start_link(__MODULE__, args, name: Keyword.get(args, :name, __MODULE__))\n  end\n\n  def init(args) do\n    alarm_module = Keyword.fetch!(args, :alarm_module)\n    _ = :timer.send_after(@interval, :print_alarms)\n    {:ok, %{previous_backoff: @interval, alarm_module: alarm_module}}\n  end\n\n  def handle_info(:print_alarms, state) do\n    :ok = Enum.each(state.alarm_module.all(), fn alarm -> Logger.warn(\"An alarm was raised #{inspect(alarm)}\") end)\n\n    previous_backoff =\n      case @max_interval < state.previous_backoff do\n        true ->\n          @interval\n\n        false ->\n          state.previous_backoff\n      end\n\n    next_backoff = round(previous_backoff * 2) + Enum.random(-1000..1000)\n\n    _ = :timer.send_after(next_backoff, :print_alarms)\n    {:noreply, Map.put(state, :previous_backoff, next_backoff)}\n  end\nend\n"
  },
  {
    "path": "apps/omg_status/lib/omg_status/application.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Status.Application do\n  @moduledoc \"\"\"\n  Top level application module.\n  \"\"\"\n  use Application\n\n  alias OMG.Status.AlarmPrinter\n  alias OMG.Status.Alert.Alarm\n  alias OMG.Status.Alert.AlarmHandler\n  alias OMG.Status.Configuration\n  alias OMG.Status.DatadogEvent.AlarmConsumer\n  alias OMG.Status.Metric.Datadog\n  alias OMG.Status.Metric.Telemetry\n  alias OMG.Status.Metric.VmstatsSink\n\n  def start(_type, _args) do\n    import Supervisor.Spec, warn: false\n\n    system_memory_check_interval_ms = Configuration.system_memory_check_interval_ms()\n    system_memory_high_threshold = Configuration.system_memory_high_threshold()\n    release = Configuration.release()\n    current_version = Configuration.current_version()\n\n    children =\n      if Configuration.datadog_disabled?() do\n        # spandex datadog api server is able to flush when disabled?: true\n        [{SpandexDatadog.ApiServer, spandex_datadog_options()}]\n      else\n        [\n          {OMG.Status.Monitor.StatsdMonitor, [alarm_module: Alarm, child_module: Datadog]},\n          {\n            OMG.Status.Monitor.MemoryMonitor,\n            [\n              alarm_module: Alarm,\n              memsup_module: :memsup,\n              threshold: system_memory_high_threshold,\n              interval_ms: system_memory_check_interval_ms\n            ]\n          },\n          {\n            Telemetry,\n            [\n              release: release,\n              current_version: current_version\n            ]\n          },\n          VmstatsSink.prepare_child(),\n          {SpandexDatadog.ApiServer, spandex_datadog_options()},\n          {\n            AlarmConsumer,\n            [\n              dd_alarm_handler: AlarmHandler,\n              release: release,\n              current_version: current_version,\n              publisher: Datadog\n            ]\n          }\n        ]\n      end\n\n    child = [{AlarmPrinter, [alarm_module: Alarm]}]\n    Supervisor.start_link(children ++ child, strategy: :one_for_one, name: Status.Supervisor)\n  end\n\n  def start_phase(:install_alarm_handler, _start_type, _phase_args) do\n    :ok = AlarmHandler.install()\n  end\n\n  defp spandex_datadog_options() do\n    config = Application.get_all_env(:spandex_datadog)\n    config_host = config[:host]\n    config_port = config[:port]\n    config_batch_size = config[:batch_size]\n    config_sync_threshold = config[:sync_threshold]\n    config_http = config[:http]\n    spandex_datadog_options(config_host, config_port, config_batch_size, config_sync_threshold, config_http)\n  end\n\n  defp spandex_datadog_options(config_host, config_port, config_batch_size, config_sync_threshold, config_http) do\n    [\n      host: config_host || \"localhost\",\n      port: config_port || 8126,\n      batch_size: config_batch_size || 10,\n      sync_threshold: config_sync_threshold || 100,\n      http: config_http || HTTPoison\n    ]\n  end\nend\n"
  },
  {
    "path": "apps/omg_status/lib/omg_status/configuration.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Status.Configuration do\n  @moduledoc \"\"\"\n  Provides access to applications configuration\n  \"\"\"\n  alias OMG.Status.Metric.Tracer\n\n  @app :omg_status\n\n  @spec system_memory_check_interval_ms() :: integer() | no_return()\n  def system_memory_check_interval_ms() do\n    Application.fetch_env!(@app, :system_memory_check_interval_ms)\n  end\n\n  @spec system_memory_high_threshold() :: float() | no_return()\n  def system_memory_high_threshold() do\n    Application.fetch_env!(@app, :system_memory_high_threshold)\n  end\n\n  @spec datadog_disabled?() :: boolean()\n  def datadog_disabled?() do\n    Application.fetch_env!(@app, Tracer)[:disabled?]\n  end\n\n  @spec release() :: atom() | nil\n  def release() do\n    Application.get_env(@app, :release)\n  end\n\n  @spec current_version() :: String.t() | nil\n  def current_version() do\n    Application.get_env(@app, :current_version)\n  end\nend\n"
  },
  {
    "path": "apps/omg_status/lib/omg_status/datadog_event/alarm_consumer.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Status.DatadogEvent.AlarmConsumer do\n  @moduledoc \"\"\"\n  Installs a alarm handler and publishes the alarms as events\n  \"\"\"\n\n  require Logger\n  use GenServer\n\n  @doc \"\"\"\n  Returns child_specs for the given `AlarmConsumer` setup, to be included e.g. in Supervisor's children.\n  Mandatory params are in Keyword form:\n  - :publisher is Module that implements a function `event/3` (title, message, option). It's purpose is to forward\n  alarms to a collector (for example, Datadog)\n  - :alarm_handler (http://erlang.org/doc/man/alarm_handler.html) is a gen_event process that allows us to install our alarm handler ontu. Our installed handler will than receive\n  system alarms (set and cleared) and cast us the alarms.\n  - :dd_alarm_handler is the module that we install as alarm_handler and will get notified of set and cleared alarms and forward them to THIS AlarmConsumer process.\n  - :release is the mode this current process is runing under (for example, currently we support watcher, child chain or watcher info)\n  - :current_version is semver of the current code\n  \"\"\"\n  @spec prepare_child(keyword()) :: %{id: atom(), start: tuple()}\n  def prepare_child(opts) do\n    %{id: :alarm_consumer, start: {__MODULE__, :start_link, [opts]}, shutdown: :brutal_kill, type: :worker}\n  end\n\n  @doc \"\"\"\n  args explained above in prepare_child/1\n  \"\"\"\n  def start_link(args) do\n    GenServer.start_link(__MODULE__, args, name: __MODULE__)\n  end\n\n  @doc \"\"\"\n  args explained above in prepare_child/1\n  \"\"\"\n  def init(args) do\n    publisher = Keyword.fetch!(args, :publisher)\n    alarm_handler_process = Keyword.get(args, :alarm_handler, :alarm_handler)\n    dd_alarm_handler = Keyword.fetch!(args, :dd_alarm_handler)\n    release = Keyword.fetch!(args, :release)\n    current_version = Keyword.fetch!(args, :current_version)\n\n    :ok = install_alarm_handler(alarm_handler_process, dd_alarm_handler)\n    _ = Logger.info(\"Started #{inspect(__MODULE__)}\")\n    {:ok, %{publisher: publisher, release: release, current_version: current_version}}\n  end\n\n  # Gets events from the alarm consumer and send them off\n  def handle_cast(alarm, state) do\n    {alarm_type, data} = elem(alarm, 1)\n    action = elem(alarm, 0)\n\n    level =\n      case action do\n        :clear_alarm -> [alert_type: :info]\n        _ -> [alert_type: :warning]\n      end\n\n    aggregation_key = :alarm\n    timestamp = DateTime.to_unix(DateTime.utc_now(), :millisecond)\n\n    options = tags(aggregation_key, state.release, state.current_version, timestamp)\n    title = \"#{action} - #{inspect(alarm_type)}\"\n    message = \"#{inspect(data)} - Timestamp: #{timestamp}\"\n\n    :ok = apply(state.publisher, :event, create_event_data(title, message, level ++ options))\n\n    {:noreply, state}\n  end\n\n  defp create_event_data(title, message, options) do\n    [title, message, options]\n  end\n\n  # https://docs.datadoghq.com/api/?lang=bash#api-reference\n  defp tags(aggregation_key, release, current_version, _timestamp) do\n    [\n      {:aggregation_key, aggregation_key},\n      {:tags, [\"#{aggregation_key}\", \"#{release}\", \"vsn-#{current_version}\"]}\n    ]\n  end\n\n  defp install_alarm_handler(alarm_handler, dd_alarm_handler) do\n    case Enum.member?(:gen_event.which_handlers(alarm_handler), dd_alarm_handler) do\n      true -> :ok\n      _ -> alarm_handler.add_alarm_handler(dd_alarm_handler, [self()])\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_status/lib/omg_status/datadog_event/alarm_handler.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Status.DatadogEvent.AlarmHandler do\n  @moduledoc \"\"\"\n     Is notified of raised and cleared alarms and casts them to AlarmConsumer process.\n  \"\"\"\n\n  require Logger\n\n  def init([reporter]) do\n    {:ok, reporter}\n  end\n\n  def handle_call(_request, reporter), do: {:ok, :ok, reporter}\n\n  def handle_event({:set_alarm, _alarm_details} = alarm, reporter) do\n    :ok = GenServer.cast(reporter, alarm)\n    {:ok, reporter}\n  end\n\n  def handle_event({:clear_alarm, _alarm_details} = alarm, reporter) do\n    :ok = GenServer.cast(reporter, alarm)\n    {:ok, reporter}\n  end\n\n  def handle_event(event, reporter) do\n    _ = Logger.info(\"#{__MODULE__} got event: #{inspect(event)}. Ignoring.\")\n    {:ok, reporter}\n  end\nend\n"
  },
  {
    "path": "apps/omg_status/lib/omg_status/metric/datadog.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Status.Metric.Datadog do\n  @moduledoc \"\"\"\n  Datadog connection wrapper\n  \"\"\"\n\n  # we want to override Statix in :test\n  # because we don't want to send metrics in unittests\n  case Application.get_env(:omg_status, :environment) do\n    :test -> use OMG.Status.Metric.Statix\n    _ -> use Statix, runtime_config: true\n  end\n\n  use GenServer\n  require Logger\n\n  def start_link(), do: GenServer.start_link(__MODULE__, [], [])\n\n  def init(_opts) do\n    _ = Process.flag(:trap_exit, true)\n    _ = Logger.info(\"Starting #{inspect(__MODULE__)} and connecting to Datadog.\")\n    __MODULE__.connect()\n    _ = Logger.info(\"Connection opened #{inspect(current_conn())}\")\n    {:ok, current_conn()}\n  end\n\n  def handle_info({:EXIT, port, reason}, %Statix.Conn{sock: __MODULE__} = state) do\n    _ = Logger.error(\"Port in #{inspect(__MODULE__)} #{inspect(port)} exited with reason #{reason}\")\n    {:stop, :normal, state}\n  end\nend\n"
  },
  {
    "path": "apps/omg_status/lib/omg_status/metric/event.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Status.Metric.Event do\n  @moduledoc \"\"\"\n  A centralised repository of all emitted event types with description.\n  \"\"\"\n\n  @services [\n    :challenges_responds_processor,\n    :competitor_processor,\n    :depositor,\n    :exit_challenger,\n    :exit_finalizer,\n    :exit_processor,\n    :exiter,\n    :ife_exit_finalizer,\n    :in_flight_exit,\n    :in_flight_exit_processor,\n    :in_flight_exit_deleted_processor,\n    :piggyback,\n    :piggyback_challenges_processor,\n    :piggyback_processor,\n    :block_queue\n  ]\n\n  @doc \"\"\"\n  :transaction_submission - Child Chain API's received transaction submission.\n  :transaction_submission_success - Child Chain API's successful processing of transaction submission.\n  :transaction_submission_failed - Child Chain API's failed processing of transaction submission.\n  :transaction_submission_failed - Childchain OMG.Watcher.State mempool transactions\n  :pending_transactions - Childchain OMG.Watcher.State mempool transactions\n  :block_transactions - Childchain OMG.Watcher.State transactions in formed block\n  :block_submission_attempt - Childchain Block submission attempted\n  :block_submission_success Childchain Block successfully submitted\n  :block_submission_gas Child Chain Block queue gas usage metric\n  :block_queue_blknum_submitting - Child Chain BlockQueue's blknum of the block being submitted\n  :block_queue_blknum_submitted - Child Chain BlockQueue's blknum of the block submitted\n  :block_queue_num_blocks_stalled - Child Chain BlockQueue's number of blocks currently being submitted and stalled\n  :authority_balance - Child Chain authority address balance\n  :balance - OMG.Watcher.State balance per currency\n  :unique_users - OMG.Watcher.State number of unique_users in the system\n  :block_getter_message_queue_len - OMG.Watcher.BlockGetter message queue length\n  :watcher_exit_processor_message_queue_len - OMG.Watcher.ExitProcessor message queue length\n  :eventer_message_queue_len - OMG.Watcher.Eventer message queue length\n  :db_message_queue_len - OMG.DB server implementation (OMG.DB.LevelDB.Server, or OMG.DB.RocksDB.Server,)  message queue length\n  :write - OMG.DB KV layer has three types of actions: write, read,  multiread\n  :read - OMG.DB KV layer has three types of actions: write, read,  multiread\n  :multiread - OMG.DB KV layer has three types of actions: write, read,  multiread\n  @services - We're interested in the events queue length that particular OMG.Watcher.EthereumEventListener service process\n  \"\"\"\n  def name(:transaction_submission), do: \"transaction_submission\"\n  def name(:transaction_submission_success), do: \"transaction_submission_success\"\n  def name(:transaction_submission_failed), do: \"transaction_submission_failed\"\n  def name(:pending_transactions), do: \"pending_transactions\"\n  def name(:block_transactions), do: \"block_transactions\"\n  def name(:block_submission_attempt), do: \"block_submission_attempt\"\n  def name(:block_submission_success), do: \"block_submission_success\"\n  def name(:block_submission_gas), do: \"block_submission_gas\"\n  def name(:block_queue_blknum_submitting), do: \"block_queue_blknum_submitting\"\n  def name(:block_queue_blknum_submitted), do: \"block_queue_blknum_submitted\"\n  def name(:block_queue_num_blocks_stalled), do: \"block_queue_num_blocks_stalled\"\n  def name(:authority_balance), do: \"authority_balance\"\n  def name(:balance), do: \"balance\"\n  def name(:unique_users), do: \"unique_users\"\n  def name(:block_getter_message_queue_len), do: \"block_getter_message_queue_len\"\n  def name(:watcher_exit_processor_message_queue_len), do: \"watcher_exit_processor_message_queue_len\"\n  def name(:eventer_message_queue_len), do: \"eventer_message_queue_len\"\n  def name(:db_message_queue_len), do: \"db_message_queue_len\"\n  def name(:write), do: \"db_write\"\n  def name(:read), do: \"db_read\"\n  def name(:multiread), do: \"db_multiread\"\n\n  @doc \"\"\"\n    :events - We're interested in the events queue length that particular OMG.Watcher.EthereumEventListener service process\n    is handling.\n\n    message_queue_len -  We're interested in the message queue length of particular OMG.Watcher.EthereumEventListener service process\n  \"\"\"\n  def name(service, :events) when service in @services, do: events_name(service)\n  def name(service, :message_queue_len) when service in @services, do: message_queue_len_name(service)\n\n  defp events_name(:depositor), do: \"depositor_ethereum_events\"\n  defp events_name(:in_flight_exit), do: \"in_flight_exit_ethereum_events\"\n  defp events_name(:piggyback), do: \"piggyback_ethereum_events\"\n  defp events_name(:exiter), do: \"exiter_ethereum_events\"\n  defp events_name(:exit_processor), do: \"exit_processor_ethereum_events\"\n  defp events_name(:exit_finalizer), do: \"exit_finalizer_ethereum_events\"\n  defp events_name(:exit_challenger), do: \"exit_challenger_ethereum_events\"\n  defp events_name(:in_flight_exit_processor), do: \"in_flight_exit_processor_ethereum_events\"\n  defp events_name(:in_flight_exit_deleted_processor), do: \"in_flight_exit_deleted_processor_ethereum_events\"\n  defp events_name(:piggyback_processor), do: \"piggyback_processor_ethereum_events\"\n  defp events_name(:competitor_processor), do: \"competitor_processor_ethereum_events\"\n  defp events_name(:challenges_responds_processor), do: \"challenges_responds_processor_ethereum_events\"\n  defp events_name(:piggyback_challenges_processor), do: \"piggyback_challenges_processor_ethereum_events\"\n  defp events_name(:ife_exit_finalizer), do: \"ife_exit_finalizer_ethereum_events\"\n\n  defp message_queue_len_name(:block_queue), do: \"block_queue_message_queue_len\"\n  defp message_queue_len_name(:depositor), do: \"depositor_message_queue_len\"\n  defp message_queue_len_name(:in_flight_exit), do: \"in_flight_exit_message_queue_len\"\n  defp message_queue_len_name(:piggyback), do: \"piggyback_message_queue_len\"\n  defp message_queue_len_name(:exiter), do: \"exiter_message_queue_len\"\n  defp message_queue_len_name(:exit_processor), do: \"exit_processor_message_queue_len\"\n  defp message_queue_len_name(:exit_finalizer), do: \"exit_finalizer_message_queue_len\"\n  defp message_queue_len_name(:exit_challenger), do: \"exit_challenger_message_queue_len\"\n  defp message_queue_len_name(:in_flight_exit_processor), do: \"in_flight_exit_processor_message_queue_len\"\n  defp message_queue_len_name(:piggyback_processor), do: \"piggyback_processor_message_queue_len\"\n  defp message_queue_len_name(:competitor_processor), do: \"competitor_processor_message_queue_len\"\n  defp message_queue_len_name(:challenges_responds_processor), do: \"challenges_responds_processor_message_queue_len\"\n  defp message_queue_len_name(:piggyback_challenges_processor), do: \"piggyback_challenges_processor_message_queue_len\"\n  defp message_queue_len_name(:ife_exit_finalizer), do: \"ife_exit_finalizer_message_queue_len\"\nend\n"
  },
  {
    "path": "apps/omg_status/lib/omg_status/metric/statix.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\ndefmodule OMG.Status.Metric.Statix do\n  @moduledoc \"\"\"\n  Useful for overwritting Statix behaviour.\n  \"\"\"\n  defmacro __using__(_opts) do\n    quote location: :keep do\n      @behaviour Statix\n      def connect(), do: :ok\n\n      def increment(_), do: :ok\n      def increment(_, _, options \\\\ []), do: :ok\n\n      def decrement(_, val \\\\ 1, options \\\\ []), do: :ok\n\n      def gauge(_, val, options \\\\ []), do: :ok\n\n      def histogram(_, val, options \\\\ []), do: :ok\n\n      def timing(_, val, options \\\\ []), do: :ok\n\n      def measure(key, options \\\\ [], fun), do: :ok\n\n      def set(key, val, options \\\\ []), do: :ok\n\n      def event(key, val, options), do: :ok\n\n      def service_check(key, val, options), do: :ok\n\n      def current_conn(), do: %Statix.Conn{sock: __MODULE__}\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_status/lib/omg_status/metric/telemetry.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Status.Metric.Telemetry do\n  @moduledoc \"\"\"\n  Metrics handler to send telemetry events to Datadog\n  \"\"\"\n\n  use Supervisor\n  import Telemetry.Metrics\n\n  def start_link(arg) do\n    Supervisor.start_link(__MODULE__, arg, name: __MODULE__)\n  end\n\n  def init(arg) do\n    service = Keyword.fetch!(arg, :release)\n    version = Keyword.fetch!(arg, :current_version)\n    dd_host = Application.get_env(:statix, :host)\n    dd_port = Application.get_env(:statix, :port)\n\n    children = [\n      {\n        TelemetryMetricsStatsd,\n        metrics: metrics(),\n        global_tags: [\n          service: service,\n          version: version\n        ],\n        host: dd_host,\n        port: dd_port,\n        prefix: \"elixir\",\n        formatter: :datadog\n      }\n    ]\n\n    Supervisor.init(children, strategy: :one_for_one)\n  end\n\n  defp metrics() do\n    [\n      # Phoenix Metrics\n      summary(\n        \"phoenix.endpoint.stop.duration\",\n        tags: [:service, :version],\n        unit: {:native, :millisecond}\n      ),\n      summary(\n        \"phoenix.router_dispatch.stop.duration\",\n        tags: [:service, :version, :route],\n        unit: {:native, :millisecond}\n      ),\n      summary(\n        \"phoenix.router_dispatch.exception.duration\",\n        tags: [:service, :version, :kind],\n        unit: {:native, :millisecond}\n      ),\n      summary(\n        \"phoenix.error_rendered.duration\",\n        tags: [:service, :version, :kind],\n        unit: {:native, :millisecond}\n      ),\n\n      # Custom web metrics\n      counter(\n        \"web.fallback.error\",\n        tags: [:service, :version, :route, :error_code]\n      )\n    ]\n  end\nend\n"
  },
  {
    "path": "apps/omg_status/lib/omg_status/metric/tracer.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Status.Metric.Tracer do\n  @moduledoc \"\"\"\n  Trace requests and reports information to Datadog via Spandex\n  \"\"\"\n\n  use Spandex.Tracer, otp_app: :omg_status\nend\n"
  },
  {
    "path": "apps/omg_status/lib/omg_status/metric/vmstats_sink.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\ndefmodule OMG.Status.Metric.VmstatsSink do\n  @moduledoc \"\"\"\n  Interface implementation.\n  \"\"\"\n  alias OMG.Status.Metric.Datadog\n  @type vm_stat :: {:vmstats_sup, :start_link, [any(), ...]}\n  @behaviour :vmstats_sink\n\n  @doc \"\"\"\n  Returns child_specs for the given metric setup, to be included e.g. in Supervisor's children.\n  \"\"\"\n  @spec prepare_child() :: %{id: :vmstats_sup, start: vm_stat()}\n  def prepare_child() do\n    %{id: :vmstats_sup, start: {:vmstats_sup, :start_link, [__MODULE__, base_key()]}}\n  end\n\n  defp base_key(), do: Application.get_env(:vmstats, :base_key)\n  # statix currently does not support `count` or `monotonic_count`, only increment and decrement\n  # because of that, we're sending counters as gauges\n  def collect(:counter, key, value), do: _ = Datadog.gauge(key, value)\n\n  def collect(:gauge, key, value), do: _ = Datadog.gauge(key, value)\n\n  def collect(:timing, key, value), do: _ = Datadog.timing(key, value)\nend\n"
  },
  {
    "path": "apps/omg_status/lib/omg_status/monitor/memory_monitor.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Status.Monitor.MemoryMonitor do\n  @moduledoc \"\"\"\n  Monitors and raises the :system_memory_too_high alarm when the system memory reaches\n  the specified threshold.\n\n  Intentionally raising a different alarm name from :memsup (which uses :system_memory_high_watermark)\n  so there is no ambiguity to which module is responsible for which alarm.\n\n  See http://erlang.org/pipermail/erlang-questions/2006-September/023144.html\n  \"\"\"\n  use GenServer\n  require Logger\n\n  @type t :: %__MODULE__{\n          alarm_module: module(),\n          memsup_module: module(),\n          interval_ms: pos_integer(),\n          threshold: float(),\n          raised: boolean(),\n          timer_ref: reference() | nil\n        }\n\n  defstruct alarm_module: nil,\n            memsup_module: nil,\n            interval_ms: nil,\n            threshold: 1.0,\n            raised: false,\n            timer_ref: nil\n\n  def start_link(args) do\n    GenServer.start_link(__MODULE__, args, name: __MODULE__)\n  end\n\n  # monitor init\n  def init([_ | _] = opts) do\n    _ = Logger.info(\"Starting #{inspect(__MODULE__)}.\")\n    install_alarm_handler()\n\n    alarm_module = Keyword.fetch!(opts, :alarm_module)\n    memsup_module = Keyword.fetch!(opts, :memsup_module)\n    interval_ms = Keyword.fetch!(opts, :interval_ms)\n    threshold = Keyword.fetch!(opts, :threshold)\n\n    state = %__MODULE__{\n      alarm_module: alarm_module,\n      memsup_module: memsup_module,\n      interval_ms: interval_ms,\n      threshold: threshold\n    }\n\n    {:ok, state, {:continue, :first_check}}\n  end\n\n  # gen_event init\n  def init(_args) do\n    {:ok, %{}}\n  end\n\n  # We want the first check immediately upon start, but we cannot do it while the monitor\n  # is not fully initialized, so we need to trigger it in a :continue instruction.\n  def handle_continue(:first_check, state) do\n    _ = send(self(), :check)\n    {:noreply, state}\n  end\n\n  def handle_info(:check, state) do\n    exceed_threshold? = system_memory_exceed_threshold?(state.memsup_module, state.threshold)\n    _ = raise_clear(state.alarm_module, state.raised, exceed_threshold?)\n\n    {:ok, timer_ref} = :timer.send_after(state.interval_ms, :check)\n    {:noreply, %{state | timer_ref: timer_ref}}\n  end\n\n  def handle_cast(:set_alarm, state) do\n    {:noreply, %{state | raised: true}}\n  end\n\n  def handle_cast(:clear_alarm, state) do\n    {:noreply, %{state | raised: false}}\n  end\n\n  #\n  # gen_event handlers\n  #\n  def handle_call(_request, state), do: {:ok, :ok, state}\n\n  def handle_event({:set_alarm, {:system_memory_too_high, %{reporter: __MODULE__}}}, state) do\n    _ = Logger.warn(\"System memory usage is too high. :system_memory_too_high alarm raised.\")\n    :ok = GenServer.cast(__MODULE__, :set_alarm)\n    {:ok, state}\n  end\n\n  def handle_event({:clear_alarm, {:system_memory_too_high, %{reporter: __MODULE__}}}, state) do\n    _ = Logger.warn(\"System memory usage went below threshold. :system_memory_too_high alarm cleared.\")\n    :ok = GenServer.cast(__MODULE__, :clear_alarm)\n    {:ok, state}\n  end\n\n  def handle_event(event, state) do\n    _ = Logger.info(\"#{__MODULE__} got event: #{inspect(event)}. Ignoring.\")\n    {:ok, state}\n  end\n\n  #\n  # Memory-checking logic\n  #\n\n  defp system_memory_exceed_threshold?(memsup_module, threshold) do\n    memory = get_memory(memsup_module)\n    used = memory.total - (memory.free + memory.buffered + memory.cached)\n    used_ratio = used / memory.total\n\n    used_ratio > threshold\n  end\n\n  defp get_memory(memsup_module) do\n    data = memsup_module.get_system_memory_data()\n\n    %{\n      total: Keyword.fetch!(data, :total_memory),\n      free: Keyword.fetch!(data, :free_memory),\n      buffered: Keyword.get(data, :buffered_memory, 0),\n      cached: Keyword.get(data, :cached_memory, 0)\n    }\n  end\n\n  #\n  # Alarm management\n  #\n\n  # if an alarm is raised, we don't have to raise it again.\n  # if an alarm is cleared, we don't need to clear it again\n  # we want to avoid pushing events again\n  @spec raise_clear(module(), boolean(), boolean()) :: :ok | :duplicate\n  defp raise_clear(alarm_module, false, true) do\n    alarm_module.set(alarm_module.system_memory_too_high(__MODULE__))\n  end\n\n  defp raise_clear(alarm_module, true, false) do\n    alarm_module.clear(alarm_module.system_memory_too_high(__MODULE__))\n  end\n\n  defp raise_clear(_alarm_module, true, true), do: :ok\n\n  defp raise_clear(_alarm_module, false, false), do: :ok\n\n  defp install_alarm_handler() do\n    case Enum.member?(:gen_event.which_handlers(:alarm_handler), __MODULE__) do\n      true -> :ok\n      _ -> :alarm_handler.add_alarm_handler(__MODULE__)\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_status/lib/omg_status/monitor/statsd_monitor.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Status.Monitor.StatsdMonitor do\n  @moduledoc \"\"\"\n  This module is a custom implemented supervisor that monitors all it's chilldren.\n  \"\"\"\n  use GenServer\n\n  require Logger\n\n  @type t :: %__MODULE__{\n          alarm_module: module(),\n          child_module: module(),\n          interval: pos_integer(),\n          pid: pid(),\n          raised: boolean(),\n          tref: reference() | nil\n        }\n\n  defstruct alarm_module: nil,\n            child_module: nil,\n            interval: Application.get_env(:omg_status, :statsd_reconnect_backoff_ms),\n            pid: nil,\n            raised: false,\n            tref: nil\n\n  def start_link(args) do\n    GenServer.start_link(__MODULE__, args, name: __MODULE__)\n  end\n\n  def init([_ | _] = opts) do\n    _ = Logger.info(\"Starting #{inspect(__MODULE__)}.\")\n    install_alarm_handler()\n\n    alarm_module = Keyword.fetch!(opts, :alarm_module)\n    child_module = Keyword.fetch!(opts, :child_module)\n    false = Process.flag(:trap_exit, true)\n    {:ok, pid} = apply(child_module, :start_link, [])\n\n    state = %__MODULE__{\n      alarm_module: alarm_module,\n      child_module: child_module,\n      pid: pid\n    }\n\n    _ = raise_clear(alarm_module, state.raised, Process.alive?(pid))\n    {:ok, state}\n  end\n\n  # gen_event init\n  def init(_args) do\n    {:ok, %{}}\n  end\n\n  def handle_info({:EXIT, _, reason}, state) do\n    _ = Logger.error(\"Monitored datadog connection process from statix died of reason #{inspect(reason)} \")\n    _ = state.alarm_module.set(state.alarm_module.statsd_client_connection(__MODULE__))\n    _ = :timer.cancel(state.tref)\n    {:ok, tref} = :timer.send_after(state.interval, :connect)\n    {:noreply, %{state | raised: true, tref: tref}}\n  end\n\n  def handle_info(:connect, state) do\n    {:ok, pid} = apply(state.child_module, :start_link, [])\n    alive = Process.alive?(pid)\n    _ = raise_clear(state.alarm_module, state.raised, alive)\n    {:noreply, %{state | pid: pid}}\n  end\n\n  def handle_cast(:clear_alarm, state) do\n    {:noreply, %{state | raised: false}}\n  end\n\n  def handle_cast(:set_alarm, state) do\n    {:noreply, %{state | raised: true}}\n  end\n\n  def terminate(_, _), do: :ok\n\n  #\n  # gen_event\n  #\n  def handle_call(_request, state), do: {:ok, :ok, state}\n\n  def handle_event({:clear_alarm, {:statsd_client_connection, %{reporter: __MODULE__}}}, state) do\n    _ = Logger.warn(\"Established connection to the client. :statsd_client_connection alarm clearead.\")\n    :ok = GenServer.cast(__MODULE__, :clear_alarm)\n    {:ok, state}\n  end\n\n  def handle_event({:set_alarm, {:statsd_client_connection, %{reporter: __MODULE__}}}, state) do\n    _ = Logger.warn(\"Connection dropped raising :statsd_client_connection alarm.\")\n    :ok = GenServer.cast(__MODULE__, :set_alarm)\n    {:ok, state}\n  end\n\n  # flush\n  def handle_event(event, state) do\n    _ = Logger.info(\"#{__MODULE__} got event: #{inspect(event)}. Ignoring.\")\n    {:ok, state}\n  end\n\n  # if an alarm is raised, we don't have to raise it again.\n  # if an alarm is cleared, we don't need to clear it again\n  # we want to avoid pushing events again\n  @spec raise_clear(module(), boolean(), boolean()) :: :ok | :duplicate\n  defp raise_clear(_alarm_module, true, false), do: :ok\n\n  defp raise_clear(alarm_module, false, false),\n    do: alarm_module.set(alarm_module.statsd_client_connection(__MODULE__))\n\n  defp raise_clear(alarm_module, true, _),\n    do: alarm_module.clear(alarm_module.statsd_client_connection(__MODULE__))\n\n  defp raise_clear(_alarm_module, false, _), do: :ok\n\n  defp install_alarm_handler() do\n    case Enum.member?(:gen_event.which_handlers(:alarm_handler), __MODULE__) do\n      true -> :ok\n      _ -> :alarm_handler.add_alarm_handler(__MODULE__)\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_status/lib/omg_status/release_tasks/set_application.ex",
    "content": "# Copyright 2019-2019 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Status.ReleaseTasks.SetApplication do\n  @moduledoc false\n  @behaviour Config.Provider\n\n  def init(args) do\n    args\n  end\n\n  def load(config, release: release, current_version: current_version) do\n    Config.Reader.merge(config, omg_status: [release: release, current_version: current_version])\n  end\nend\n"
  },
  {
    "path": "apps/omg_status/lib/omg_status/release_tasks/set_logger.ex",
    "content": "# Copyright 2019-2019 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Status.ReleaseTasks.SetLogger do\n  @moduledoc false\n  @behaviour Config.Provider\n  require Logger\n\n  @app :logger\n  @default_backend Ink\n\n  def init(args) do\n    args\n  end\n\n  def load(config, _args) do\n    _ = on_load()\n    logger_backends = Application.get_env(@app, :backends, persistent: true)\n    logger_backend = get_logger_backend()\n\n    remove =\n      case logger_backend do\n        :console -> Ink\n        _ -> :console\n      end\n\n    backends = logger_backends |> Kernel.--([remove]) |> Enum.concat([logger_backend]) |> Enum.uniq()\n    Config.Reader.merge(config, logger: [backends: backends])\n  end\n\n  defp get_logger_backend() do\n    logger =\n      \"LOGGER_BACKEND\"\n      |> get_env()\n      |> validate_string(@default_backend)\n\n    _ = Logger.info(\"CONFIGURATION: App: #{@app} Key: LOGGER_BACKEND Value: #{inspect(logger)}.\")\n    logger\n  end\n\n  defp get_env(key), do: System.get_env(key)\n\n  defp validate_string(nil, default), do: default\n  defp validate_string(value, default), do: do_validate_string(String.upcase(value), default)\n  defp do_validate_string(\"CONSOLE\", _default), do: :console\n  defp do_validate_string(\"INK\", _default), do: Ink\n  defp do_validate_string(_, default), do: default\n\n  defp on_load() do\n    _ = Application.load(:logger)\n  end\nend\n"
  },
  {
    "path": "apps/omg_status/lib/omg_status/release_tasks/set_sentry.ex",
    "content": "# Copyright 2019-2019 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Status.ReleaseTasks.SetSentry do\n  @moduledoc false\n\n  @behaviour Config.Provider\n  require Logger\n\n  @app :sentry\n\n  def init(args) do\n    args\n  end\n\n  def load(config, release: release, current_version: current_version) do\n    _ = Application.ensure_all_started(:logger)\n    app_env = get_app_env()\n    sentry_dsn = System.get_env(\"SENTRY_DSN\")\n\n    case is_binary(sentry_dsn) do\n      true ->\n        hostname = get_hostname()\n\n        _ =\n          Logger.warn(\n            \"Sentry configuration provided. Enabling Sentry with APP ENV #{inspect(app_env)}, with SENTRY_DSN #{\n              inspect(sentry_dsn)\n            }, with HOSTNAME (server_name) #{inspect(hostname)}\"\n          )\n\n        Config.Reader.merge(\n          config,\n          sentry: [\n            dsn: sentry_dsn,\n            environment_name: app_env,\n            included_environments: [app_env],\n            server_name: hostname,\n            tags: %{\n              application: release,\n              eth_network: get_env(\"ETHEREUM_NETWORK\"),\n              eth_node: get_rpc_client_type(),\n              current_version: \"vsn-#{current_version}\",\n              app_env: \"#{app_env}\",\n              hostname: \"#{hostname}\"\n            }\n          ]\n        )\n\n      _ ->\n        _ =\n          Logger.warn(\n            \"Sentry configuration not provided. Disabling Sentry. If you want it enabled provide APP_ENV and SENTRY_DSN.\"\n          )\n\n        Config.Reader.merge(config, sentry: [included_environments: []])\n    end\n  end\n\n  defp get_app_env() do\n    env = validate_string(get_env(\"APP_ENV\"), Application.get_env(@app, :environment_name))\n    _ = Logger.info(\"CONFIGURATION: App: #{@app} Key: APP_ENV Value: #{inspect(env)}.\")\n    env\n  end\n\n  defp get_hostname() do\n    hostname = validate_string(get_env(\"HOSTNAME\"), Application.get_env(@app, :server_name))\n\n    _ = Logger.info(\"CONFIGURATION: App: #{@app} Key: HOSTNAME, server_name Value: #{inspect(hostname)}.\")\n    hostname\n  end\n\n  defp get_rpc_client_type() do\n    rpc_client_type = validate_rpc_client_type(get_env(\"ETH_NODE\"), Application.get_env(@app, :tags)[:eth_node])\n    _ = Logger.info(\"CONFIGURATION: App: #{@app} Key: ETH_NODE Value: #{inspect(rpc_client_type)}.\")\n\n    rpc_client_type\n  end\n\n  defp get_env(key), do: System.get_env(key)\n\n  defp validate_rpc_client_type(value, _default) when is_binary(value),\n    do: to_rpc_client_type(String.upcase(value))\n\n  defp validate_rpc_client_type(_value, default),\n    do: default\n\n  defp to_rpc_client_type(\"GETH\"), do: :geth\n  defp to_rpc_client_type(\"PARITY\"), do: :parity\n  defp to_rpc_client_type(\"INFURA\"), do: :infura\n  defp to_rpc_client_type(_), do: exit(\"ETH_NODE must be either GETH, PARITY or INFURA.\")\n\n  defp validate_string(value, _default) when is_binary(value), do: value\n  defp validate_string(_, default), do: default\nend\n"
  },
  {
    "path": "apps/omg_status/lib/omg_status/release_tasks/set_tracer.ex",
    "content": "# Copyright 2019-2019 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Status.ReleaseTasks.SetTracer do\n  @moduledoc false\n  @behaviour Config.Provider\n  alias OMG.Status.Metric.Tracer\n  require Logger\n  @app :omg_status\n\n  def init(args) do\n    args\n  end\n\n  def load(config, args) do\n    _ = on_load()\n    adapter = Keyword.get(args, :system_adapter, System)\n    _ = Process.put(:system_adapter, adapter)\n    dd_disabled = get_dd_disabled()\n\n    tracer_config =\n      @app\n      |> Application.get_env(Tracer)\n      |> Keyword.put(:disabled?, dd_disabled)\n\n    {app_env, tracer_config} =\n      case dd_disabled do\n        false ->\n          app_env = get_app_env()\n          {app_env, Keyword.put(tracer_config, :env, app_env)}\n\n        true ->\n          app_env = \"\"\n          {app_env, Keyword.put(tracer_config, :env, app_env)}\n      end\n\n    release = Keyword.get(args, :release)\n    tags = [\"application:#{release}\", \"app_env:#{app_env}\", \"hostname:#{get_hostname()}\"]\n    spandex_datadog_host = Application.get_env(:spandex_datadog, :host)\n    spandex_datadog_port = Application.get_env(:spandex_datadog, :port)\n    statix_default_port = Application.get_env(:statix, :port)\n    statix_default_hostname = Application.get_env(:statix, :host)\n    batch_size = get_batch_size()\n    sync_threshold = get_sync_threshold()\n\n    Config.Reader.merge(config,\n      spandex_datadog: [\n        host: get_dd_hostname(spandex_datadog_host),\n        port: get_dd_spandex_port(spandex_datadog_port),\n        batch_size: batch_size,\n        sync_threshold: sync_threshold\n      ],\n      statix: [\n        port: get_dd_port(statix_default_port),\n        host: get_dd_hostname(statix_default_hostname),\n        tags: tags\n      ],\n      omg_status: [{Tracer, tracer_config}]\n    )\n  end\n\n  defp get_hostname() do\n    hostname = validate_hostname(get_env(\"HOSTNAME\"))\n\n    _ = Logger.info(\"CONFIGURATION: App: #{@app} Key: HOSTNAME Value: #{inspect(hostname)}.\")\n    hostname\n  end\n\n  defp get_dd_disabled() do\n    dd_disabled? = validate_bool(get_env(\"DD_DISABLED\"), Application.get_env(@app, Tracer)[:disabled?])\n\n    _ = Logger.info(\"CONFIGURATION: App: #{@app} Key: DD_DISABLED Value: #{inspect(dd_disabled?)}.\")\n    dd_disabled?\n  end\n\n  defp get_app_env() do\n    env = validate_string(get_env(\"APP_ENV\"), Application.get_env(@app, Tracer)[:env])\n    _ = Logger.info(\"CONFIGURATION: App: #{@app} Key: APP_ENV Value: #{inspect(env)}.\")\n    env\n  end\n\n  defp get_dd_hostname(default) do\n    dd_hostname = validate_string(get_env(\"DD_HOSTNAME\"), default)\n    _ = Logger.info(\"CONFIGURATION: App: #{@app} Key: DD_HOSTNAME Value: #{inspect(dd_hostname)}.\")\n    dd_hostname\n  end\n\n  defp get_dd_port(default) do\n    dd_port = validate_integer(get_env(\"DD_PORT\"), default)\n    _ = Logger.info(\"CONFIGURATION: App: #{@app} Key: DD_PORT Value: #{inspect(dd_port)}.\")\n    dd_port\n  end\n\n  defp get_dd_spandex_port(default) do\n    dd_spandex_port = validate_integer(get_env(\"DD_APM_PORT\"), default)\n    _ = Logger.info(\"CONFIGURATION: App: #{@app} Key: DD_APM_PORT Value: #{inspect(dd_spandex_port)}.\")\n    dd_spandex_port\n  end\n\n  def get_batch_size() do\n    batch_size = validate_integer(get_env(\"BATCH_SIZE\"), Application.get_env(:spandex_datadog, :batch_size))\n\n    _ = Logger.info(\"CONFIGURATION: App: #{@app} Key: BATCH_SIZE Value: #{inspect(batch_size)}.\")\n    batch_size\n  end\n\n  defp validate_hostname(value) when is_binary(value), do: value\n  defp validate_hostname(_), do: exit(\"HOSTNAME is not set correctly.\")\n\n  def get_sync_threshold() do\n    sync_threshold = Application.get_env(:spandex_datadog, :sync_threshold)\n    sync_threshold = validate_integer(get_env(\"SYNC_THRESHOLD\"), sync_threshold)\n\n    _ = Logger.info(\"CONFIGURATION: App: #{@app} Key: SYNC_THRESHOLD Value: #{inspect(sync_threshold)}.\")\n    sync_threshold\n  end\n\n  defp get_env(key) do\n    Process.get(:system_adapter).get_env(key)\n  end\n\n  defp validate_bool(value, _default) when is_binary(value), do: to_bool(String.upcase(value))\n  defp validate_bool(_, default), do: default\n\n  defp to_bool(\"TRUE\"), do: true\n  defp to_bool(\"FALSE\"), do: false\n  defp to_bool(_), do: exit(\"DD_DISABLED either true or false.\")\n\n  defp validate_string(value, _default) when is_binary(value), do: value\n  defp validate_string(_, default), do: default\n\n  defp validate_integer(value, _default) when is_binary(value), do: String.to_integer(value)\n  defp validate_integer(_, default), do: default\n\n  defp on_load() do\n    _ = Application.ensure_all_started(:logger)\n    _ = Application.load(@app)\n    _ = Application.load(:spandex_datadog)\n    _ = Application.load(:statix)\n  end\nend\n"
  },
  {
    "path": "apps/omg_status/lib/omg_status/sentry_filter.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Status.SentryFilter do\n  @moduledoc \"\"\"\n  Sentry callback for filtering events.\n  \"\"\"\n  @behaviour Sentry.EventFilter\n\n  # when the development environment restarts it lacks network access\n  # something to do with Cloud DNS\n  def exclude_exception?(%MatchError{term: {:error, :nxdomain}}, _), do: true\n\n  # Ignoring 406 status code invalid headers exception\n  def exclude_exception?(%Phoenix.NotAcceptableError{plug_status: 406}, _), do: true\n\n  def exclude_exception?(%Plug.Parsers.RequestTooLargeError{}, _), do: true\n\n  def exclude_exception?(%CaseClauseError{term: {:error, :econnrefused}}, _), do: true\n\n  def exclude_exception?(_, _), do: false\nend\n"
  },
  {
    "path": "apps/omg_status/lib/status.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Status do\n  @moduledoc \"\"\"\n  An interface towards the node health for RPC requests.\n  For the RPC to work we need Ethereum client connectivity and booting should not be in progress.\n  \"\"\"\n  alias OMG.Status.Alert.AlarmHandler\n\n  # this can be read as\n  # if ETS table has a tuple entry in form of {:boot_in_progress, 0}, return false\n  # if ETS table has a tuple entry in form of {:boot_in_progress, 1}, return true\n  @health_match List.flatten(\n                  for n <- [\n                        :boot_in_progress,\n                        :ethereum_connection_error,\n                        :ethereum_stalled_sync,\n                        :main_supervisor_halted\n                      ],\n                      do: [{{n, 0}, [], [false]}, {{n, 1}, [], [true]}]\n                )\n\n  @spec is_healthy() :: boolean()\n  def is_healthy() do\n    # the selector returns true when an alarm is raised\n    # the selector returns false when an alarm is not raised\n    # one alarm is enough to say we're not healthy\n    not Enum.member?(:ets.select(AlarmHandler.table_name(), @health_match), true)\n  end\nend\n"
  },
  {
    "path": "apps/omg_status/mix.exs",
    "content": "defmodule OMG.Status.Mixfile do\n  use Mix.Project\n\n  def project() do\n    [\n      app: :omg_status,\n      version: version(),\n      build_path: \"../../_build\",\n      config_path: \"../../config/config.exs\",\n      deps_path: \"../../deps\",\n      lockfile: \"../../mix.lock\",\n      elixir: \"~> 1.8\",\n      elixirc_paths: elixirc_paths(Mix.env()),\n      start_permanent: Mix.env() == :prod,\n      deps: deps(),\n      test_coverage: [tool: ExCoveralls]\n    ]\n  end\n\n  # Specifies which paths to compile per environment.\n  defp elixirc_paths(:prod), do: [\"lib\"]\n  defp elixirc_paths(:dev), do: [\"lib\"]\n  defp elixirc_paths(:test), do: [\"lib\", \"test/support\"]\n\n  def application() do\n    [\n      mod: {OMG.Status.Application, []},\n      start_phases: [{:install_alarm_handler, []}],\n      extra_applications: [:logger, :sasl, :os_mon, :statix, :telemetry],\n      included_applications: [:vmstats]\n    ]\n  end\n\n  defp version() do\n    \"git\"\n    |> System.cmd([\"describe\", \"--tags\", \"--abbrev=0\"])\n    |> elem(0)\n    |> String.replace(\"v\", \"\")\n    |> String.replace(\"\\n\", \"\")\n  end\n\n  defp deps(),\n    do: [\n      {:telemetry, \"~> 0.4.1\"},\n      {:telemetry_metrics, \"~> 0.4\"},\n      {:telemetry_metrics_statsd, \"~> 0.3.0\"},\n      {:sentry, \"~> 8.0\"},\n      {:statix, git: \"https://github.com/omgnetwork/statix\", branch: \"otp-21.3.8.4-support-global-tag-patch\"},\n      {:spandex_datadog, \"~> 1.0\"},\n      {:decorator, \"~> 1.2\"},\n      {:vmstats, \"~> 2.3\", runtime: false},\n      {:ink, \"~> 1.1\"},\n      # umbrella apps\n      {:omg_bus, in_umbrella: true}\n    ]\nend\n"
  },
  {
    "path": "apps/omg_status/test/omg_status/alert/alarm_printer_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Status.Alert.AlarmPrinterTest do\n  use ExUnit.Case, async: true\n  import ExUnit.CaptureLog, only: [capture_log: 1]\n\n  alias OMG.Status.AlarmPrinter\n\n  @moduletag :common\n\n  setup do\n    {:ok, alarm_printer} =\n      AlarmPrinter.start_link(alarm_module: __MODULE__.Alarm, name: String.to_atom(\"test-#{:rand.uniform(1000)}\"))\n\n    %{alarm_printer: alarm_printer}\n  end\n\n  test \"if the process has a previous backoff set\", %{alarm_printer: alarm_printer} do\n    assert capture_log(fn ->\n             :erlang.trace(alarm_printer, true, [:receive])\n             %{previous_backoff: previous_backoff} = :sys.get_state(alarm_printer)\n             assert is_number(previous_backoff)\n           end)\n  end\n\n  test \"that the process sends itself a message after startup\", %{alarm_printer: alarm_printer} do\n    assert capture_log(fn ->\n             %{previous_backoff: previous_backoff} = :sys.get_state(alarm_printer)\n             :erlang.trace(alarm_printer, true, [:send])\n             :ok = Process.sleep(previous_backoff)\n\n             assert_receive {:trace, _, :send, {:notify, {:warn, _, {Logger, \"An alarm was raised 1\", {_, _}, _}}},\n                             Logger}\n\n             assert_receive {:trace, _, :send, {:notify, {:warn, _, {Logger, \"An alarm was raised 2\", {_, _}, _}}},\n                             Logger}\n\n             assert_receive {:trace, _, :send, {:notify, {:warn, _, {Logger, \"An alarm was raised 3\", {_, _}, _}}},\n                             Logger}\n           end)\n  end\n\n  test \"that the process increases the backoff\", %{alarm_printer: alarm_printer} do\n    assert capture_log(fn ->\n             %{previous_backoff: previous_backoff} = :sys.get_state(alarm_printer)\n             :erlang.trace(alarm_printer, true, [:send])\n             :ok = Process.sleep(previous_backoff)\n\n             assert_receive {:trace, _, :send, {:notify, {:warn, _, {Logger, \"An alarm was raised 1\", {_, _}, _}}},\n                             Logger}\n\n             assert_receive {:trace, _, :send, {:notify, {:warn, _, {Logger, \"An alarm was raised 2\", {_, _}, _}}},\n                             Logger}\n\n             assert_receive {:trace, _, :send, {:notify, {:warn, _, {Logger, \"An alarm was raised 3\", {_, _}, _}}},\n                             Logger}\n\n             %{previous_backoff: previous_backoff_1} = :sys.get_state(alarm_printer)\n             assert previous_backoff_1 > previous_backoff\n           end)\n  end\n\n  defmodule Alarm do\n    def all(), do: [1, 2, 3]\n  end\nend\n"
  },
  {
    "path": "apps/omg_status/test/omg_status/datadog_event/alarm_consumer_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Status.DatadogEvent.AlarmConsumerTest do\n  @moduledoc false\n\n  use ExUnit.Case, async: true\n  alias OMG.Status.DatadogEvent.AlarmConsumer\n  @alarm_details %{test_pid: :test_case_1}\n  setup_all do\n    {:ok, _pid} = :gen_event.start_link({:local, __MODULE__.DatadogAlarmMock})\n    :ok\n  end\n\n  setup do\n    start_supervised(\n      AlarmConsumer.prepare_child(\n        alarm_handler: __MODULE__.DatadogAlarmMock,\n        dd_alarm_handler: __MODULE__.DatadogAlarmHandlerMock,\n        release: \"child_chain_or_watcher\",\n        current_version: \"test-123\",\n        publisher: __MODULE__.DatadogEventMock\n      )\n    )\n\n    :ok\n  end\n\n  test \"if a event message put on omg bus is consumed by the event consumer and published on the publisher interface\" do\n    %{test_pid: test_pid_name} = @alarm_details\n    true = Process.register(self(), test_pid_name)\n    alarm = {:ethereum_connection_error, @alarm_details}\n    __MODULE__.DatadogAlarmMock.set_alarm(alarm)\n    assert_receive :event\n  end\n\n  defmodule DatadogEventMock do\n    # we've put the this test process identifieer into the alarm details\n    # message is a binary string \"%{test_pid: :test_case_1}\"\n    def event(_title, \"%{test_pid: :\" <> rest = _message, _options) do\n      <<test_pid_name::binary-size(11), _::binary>> = rest\n      # test_pid_name should now be \":test_case_1\"\n      Kernel.send(String.to_existing_atom(test_pid_name), :event)\n    end\n  end\n\n  defmodule DatadogAlarmHandlerMock do\n    def init([alarm_consumer_process]) do\n      {:ok, alarm_consumer_process}\n    end\n\n    def handle_event({:set_alarm, {:ethereum_connection_error, _details}} = alarm, alarm_consumer_process) do\n      :ok = GenServer.cast(alarm_consumer_process, alarm)\n      {:ok, alarm_consumer_process}\n    end\n\n    def handle_event({:clear_alarm, {:ethereum_connection_error, _details}} = alarm, alarm_consumer_process) do\n      :ok = GenServer.cast(alarm_consumer_process, alarm)\n      {:ok, alarm_consumer_process}\n    end\n  end\n\n  defmodule DatadogAlarmMock do\n    def init(_) do\n      {:ok, []}\n    end\n\n    def add_alarm_handler(module, args) do\n      :gen_event.add_handler(__MODULE__, module, args)\n    end\n\n    def set_alarm(alarm) do\n      :gen_event.notify(__MODULE__, {:set_alarm, alarm})\n    end\n\n    def clear_alarm(alarm_id) do\n      :gen_event.notify(__MODULE__, {:clear_alarm, alarm_id})\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_status/test/omg_status/integration/alarms_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Status.Alert.AlarmTest do\n  use ExUnit.Case, async: false\n  alias OMG.Status.Alert.Alarm\n\n  @moduletag :integration\n  @moduletag :common\n  @moduletag timeout: 240_000\n\n  setup_all do\n    {:ok, apps} = Application.ensure_all_started(:omg_status)\n\n    on_exit(fn ->\n      apps |> Enum.reverse() |> Enum.each(&Application.stop/1)\n    end)\n\n    :ok\n  end\n\n  setup do\n    Alarm.clear_all()\n  end\n\n  test \"raise and clear alarm based only on id\" do\n    alarm = {:id, \"details\"}\n    :alarm_handler.set_alarm(alarm)\n    assert get_alarms([:id]) == [alarm]\n    :alarm_handler.clear_alarm(alarm)\n    assert get_alarms([:id]) == []\n  end\n\n  test \"raise and clear alarm based on full alarm\" do\n    alarm = {:id5, %{a: 12, b: 34}}\n    :alarm_handler.set_alarm(alarm)\n    assert get_alarms([:id5]) == [alarm]\n    :alarm_handler.clear_alarm({:id5, %{a: 12, b: 666}})\n    assert get_alarms([:id5]) == [alarm]\n    :alarm_handler.clear_alarm(alarm)\n    assert get_alarms([:id5]) == []\n  end\n\n  test \"adds and removes alarms\" do\n    # we *do* (unifying them under one app) want system alarms (like CPU, memory...)\n    :alarm_handler.set_alarm({:some_system_alarm, \"description_1\"})\n    assert not Enum.empty?(get_alarms([:some_system_alarm]))\n    Alarm.clear_all()\n    Alarm.set(Alarm.ethereum_connection_error(__MODULE__))\n    assert Enum.count(get_alarms([:some_system_alarm, :ethereum_connection_error])) == 1\n\n    Alarm.set(Alarm.ethereum_connection_error(__MODULE__.SecondProcess))\n    assert Enum.count(get_alarms([:some_system_alarm, :ethereum_connection_error])) == 2\n\n    Alarm.clear(Alarm.ethereum_connection_error(__MODULE__))\n    assert Enum.count(get_alarms([:some_system_alarm, :ethereum_connection_error])) == 1\n\n    Alarm.clear_all()\n    assert Enum.empty?(get_alarms([:some_system_alarm, :ethereum_connection_error])) == true\n  end\n\n  test \"an alarm raise twice is reported once\" do\n    Alarm.set(Alarm.ethereum_connection_error(__MODULE__))\n    first_count = Enum.count(get_alarms([:ethereum_connection_error]))\n    Alarm.set(Alarm.ethereum_connection_error(__MODULE__))\n    ^first_count = Enum.count(get_alarms([:ethereum_connection_error]))\n  end\n\n  test \"memsup alarms\" do\n    # memsup set alarm\n    :alarm_handler.set_alarm({:system_memory_high_watermark, []})\n\n    assert Enum.any?(Alarm.all(), &(elem(&1, 0) == :system_memory_high_watermark))\n  end\n\n  # we need to filter them because of unwanted system alarms, like high memory threshold\n  # so we send the alarms we want to find in the args\n  defp get_alarms(ids), do: Enum.filter(Alarm.all(), fn {id, _desc} -> id in ids end)\nend\n"
  },
  {
    "path": "apps/omg_status/test/omg_status/metric/datadog_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Status.Metric.DatadogTest do\n  use ExUnit.Case, async: true\n  alias OMG.Status.Metric.Datadog\n\n  test \"if exiting process/port sends an exit signal to the parent process\" do\n    parent = self()\n\n    {:ok, _} =\n      Task.start(fn ->\n        {:ok, datadog_pid} = Datadog.start_link()\n        port = Port.open({:spawn, \"cat\"}, [:binary])\n        true = Process.link(datadog_pid)\n        send(parent, {:data, port, datadog_pid})\n\n        # we want to exit because the port forcefully closes\n        # so this sleep shouldn't happen\n        Process.sleep(10_000)\n      end)\n\n    receive do\n      {:data, port, datadog_pid} ->\n        :erlang.trace(datadog_pid, true, [:receive])\n        true = Process.exit(port, :portkill)\n        assert_receive {:trace, ^datadog_pid, :receive, {:EXIT, _port, :portkill}}\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_status/test/omg_status/monitor/memory_monitor_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Status.Monitor.MemoryMonitorTest do\n  use ExUnit.Case, async: true\n  alias OMG.Status.Monitor.MemoryMonitor\n\n  setup do\n    {:ok, apps} = Application.ensure_all_started(:omg_status)\n    {:ok, _} = __MODULE__.Alarm.start_link(self())\n    {:ok, _} = __MODULE__.Memsup.start_link()\n\n    {:ok, monitor_pid} =\n      MemoryMonitor.start_link(\n        alarm_module: __MODULE__.Alarm,\n        memsup_module: __MODULE__.Memsup,\n        interval_ms: 10,\n        threshold: 0.8\n      )\n\n    on_exit(fn ->\n      apps |> Enum.reverse() |> Enum.each(&Application.stop/1)\n    end)\n\n    {:ok, %{monitor_pid: monitor_pid}}\n  end\n\n  test \"raises an alarm if used memory is above the threshold\" do\n    set_memsup(total_memory: 1000, free_memory: 100, buffered_memory: 0, cached_memory: 0)\n    assert_receive :got_raise_alarm\n  end\n\n  test \"clears the alarm if used memory is below threshold\", context do\n    :sys.replace_state(context.monitor_pid, fn state -> %{state | raised: true} end)\n    set_memsup(total_memory: 1000, free_memory: 201, buffered_memory: 0, cached_memory: 0)\n    assert_receive :got_clear_alarm\n  end\n\n  test \"raises an alarm if combined used memory is above the threshold\" do\n    set_memsup(total_memory: 1000, free_memory: 60, buffered_memory: 60, cached_memory: 60)\n    assert_receive :got_raise_alarm\n  end\n\n  test \"clears the alarm if combined used memory is below threshold\", context do\n    :sys.replace_state(context.monitor_pid, fn state -> %{state | raised: true} end)\n    set_memsup(total_memory: 1000, free_memory: 70, buffered_memory: 70, cached_memory: 70)\n    assert_receive :got_clear_alarm\n  end\n\n  test \"works with :buffered_memory and :cached_memory values are not provided\" do\n    set_memsup(total_memory: 1000, free_memory: 100)\n    assert_receive :got_raise_alarm\n  end\n\n  defp set_memsup(memory_data) do\n    :sys.replace_state(__MODULE__.Memsup, fn _ -> memory_data end)\n  end\n\n  defmodule Alarm do\n    use GenServer\n\n    def start_link(parent) do\n      GenServer.start_link(__MODULE__, [parent], name: __MODULE__)\n    end\n\n    def init([parent]) do\n      {:ok, %{parent: parent}}\n    end\n\n    def system_memory_too_high(reporter) do\n      {:system_memory_too_high, %{node: Node.self(), reporter: reporter}}\n    end\n\n    def set({:system_memory_too_high, _}) do\n      GenServer.call(__MODULE__, :got_raise_alarm)\n    end\n\n    def clear({:system_memory_too_high, _}) do\n      GenServer.call(__MODULE__, :got_clear_alarm)\n    end\n\n    def handle_call(:got_raise_alarm, _, state) do\n      {:reply, send(state.parent, :got_raise_alarm), state}\n    end\n\n    def handle_call(:got_clear_alarm, _, state) do\n      {:reply, send(state.parent, :got_clear_alarm), state}\n    end\n  end\n\n  defmodule Memsup do\n    use GenServer\n\n    def start_link() do\n      GenServer.start_link(__MODULE__, [], name: __MODULE__)\n    end\n\n    def init(_) do\n      memory_data = [\n        total_memory: 1000,\n        free_memory: 1000,\n        buffered_memory: 0,\n        cached_memory: 0\n      ]\n\n      {:ok, memory_data}\n    end\n\n    def get_system_memory_data() do\n      GenServer.call(__MODULE__, :get_system_memory_data)\n    end\n\n    def handle_call(:get_system_memory_data, _, state) do\n      {:reply, state, state}\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_status/test/omg_status/monitor/statsd_monitor_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Status.Monitor.StatsdMonitorTest do\n  use ExUnit.Case, async: true\n  alias OMG.Status.Monitor.StatsdMonitor\n\n  setup do\n    {:ok, apps} = Application.ensure_all_started(:omg_status)\n    {:ok, alarm_process} = __MODULE__.Alarm.start(self())\n\n    {:ok, statsd_monitor} =\n      StatsdMonitor.start_link(alarm_module: __MODULE__.Alarm, child_module: __MODULE__.StasdWrapper)\n\n    on_exit(fn ->\n      apps |> Enum.reverse() |> Enum.each(&Application.stop/1)\n      Process.exit(alarm_process, :cleanup)\n      Process.exit(statsd_monitor, :cleanup)\n      Process.sleep(10)\n    end)\n\n    %{\n      alarm_process: alarm_process,\n      statsd_monitor: statsd_monitor\n    }\n  end\n\n  test \"if exiting process/port sends an exit signal to the parent process\", %{alarm_process: alarm_process} do\n    :erlang.trace(alarm_process, true, [:receive])\n    %{pid: pid} = :sys.get_state(StatsdMonitor)\n    true = Process.exit(pid, :testkill)\n    assert_receive :got_raise_alarm\n  end\n\n  test \"if exiting process/port sends an exit signal to the parent process 2\", %{\n    alarm_process: alarm_process,\n    statsd_monitor: _statsd_monitor\n  } do\n    %{pid: pid} = :sys.get_state(StatsdMonitor)\n    :erlang.trace(alarm_process, true, [:receive])\n    true = Process.exit(pid, :testkill)\n    assert_receive :got_raise_alarm\n    assert_receive :got_clear_alarm\n  end\n\n  defmodule Alarm do\n    use GenServer\n\n    def start(parent) do\n      GenServer.start(__MODULE__, [parent], name: __MODULE__)\n    end\n\n    def init([parent]) do\n      {:ok, %{parent: parent}}\n    end\n\n    def statsd_client_connection(reporter),\n      do: {:statsd_client_connection, %{node: Node.self(), reporter: reporter}}\n\n    def set({:statsd_client_connection, _details}) do\n      GenServer.call(__MODULE__, :got_raise_alarm)\n    end\n\n    def clear({:statsd_client_connection, _details}) do\n      GenServer.call(__MODULE__, :got_clear_alarm)\n    end\n\n    def handle_call(:got_raise_alarm, _, state) do\n      {:reply, send(state.parent, :got_raise_alarm), state}\n    end\n\n    def handle_call(:got_clear_alarm, _, state) do\n      {:reply, send(state.parent, :got_clear_alarm), state}\n    end\n  end\n\n  defmodule StasdWrapper do\n    use GenServer\n\n    def start_link() do\n      GenServer.start_link(__MODULE__, [], [])\n    end\n\n    def init(_) do\n      {:ok, %{}}\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_status/test/omg_status/release_tasks/set_logger_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Status.ReleaseTasks.SetLoggerTest do\n  use ExUnit.Case, async: true\n  alias OMG.Status.ReleaseTasks.SetLogger\n  @app :logger\n\n  setup do\n    :ok = System.delete_env(\"LOGGER_BACKEND\")\n\n    on_exit(fn ->\n      :ok = System.delete_env(\"LOGGER_BACKEND\")\n    end)\n  end\n\n  test \"if environment variables (INK) get applied in the configuration\" do\n    :ok = System.put_env(\"LOGGER_BACKEND\", \"INK\")\n    config = SetLogger.load([], [])\n    backends = config |> Keyword.fetch!(:logger) |> Keyword.fetch!(:backends)\n    assert Enum.member?(backends, Ink) == true\n  end\n\n  test \"if environment variables (CONSOLE) get applied in the configuration\" do\n    # env var to console and asserting that Ink gets removed\n    :ok = System.put_env(\"LOGGER_BACKEND\", \"conSole\")\n    config = SetLogger.load([], [])\n    backends = config |> Keyword.fetch!(:logger) |> Keyword.fetch!(:backends)\n    assert Enum.member?(backends, :console) == true\n  end\n\n  test \"if environment variables are not present the default configuration gets used (INK)\" do\n    config = SetLogger.load([], [])\n    backends = config |> Keyword.fetch!(@app) |> Keyword.fetch!(:backends)\n    assert Enum.member?(backends, :console) == false\n    assert Enum.member?(backends, Ink) == true\n  end\nend\n"
  },
  {
    "path": "apps/omg_status/test/omg_status/release_tasks/set_sentry_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Status.ReleaseTasks.SetSentryTest do\n  use ExUnit.Case, async: true\n  import ExUnit.CaptureLog, only: [capture_log: 1]\n\n  alias OMG.Status.ReleaseTasks.SetSentry\n\n  setup do\n    on_exit(fn ->\n      :ok = System.delete_env(\"SENTRY_DSN\")\n      :ok = System.delete_env(\"APP_ENV\")\n      :ok = System.delete_env(\"HOSTNAME\")\n      :ok = System.delete_env(\"ETHEREUM_NETWORK\")\n      :ok = System.delete_env(\"ETH_NODE\")\n    end)\n\n    :ok\n  end\n\n  test \"if environment variables get applied in the configuration\" do\n    dsn = \"/dsn/dsn/dsn\"\n    yolo = \"YOLO\"\n    server_name = \"server name\"\n    network = \"RINKEBY\"\n    current_version = \"current_version\"\n    :ok = System.put_env(\"SENTRY_DSN\", dsn)\n    :ok = System.put_env(\"APP_ENV\", yolo)\n    :ok = System.put_env(\"HOSTNAME\", server_name)\n    :ok = System.put_env(\"ETHEREUM_NETWORK\", network)\n\n    capture_log(fn ->\n      config = SetSentry.load([], release: :watcher, current_version: current_version)\n\n      config_expect = [\n        sentry: [\n          dsn: dsn,\n          environment_name: yolo,\n          included_environments: [yolo],\n          server_name: server_name,\n          tags: %{\n            application: :watcher,\n            eth_network: network,\n            eth_node: :geth,\n            current_version: \"vsn-\" <> current_version,\n            app_env: yolo,\n            hostname: server_name\n          }\n        ]\n      ]\n\n      assert config == config_expect\n    end)\n  end\n\n  test \"if sentry is disabled if there's no SENTRY DSN env var set\" do\n    capture_log(fn ->\n      config = SetSentry.load([], release: :child_chain, current_version: \"current_version\")\n      config_expect = [sentry: [included_environments: []]]\n      assert config == config_expect\n    end)\n  end\n\n  test \"if faulty eth node exits\" do\n    :ok = System.put_env(\"ETH_NODE\", \"random random random\")\n    :ok = System.put_env(\"SENTRY_DSN\", \"/dsn/dsn/dsn\")\n\n    capture_log(fn ->\n      assert catch_exit(SetSentry.load([], release: :child_chain, current_version: \"current_version\"))\n    end)\n  end\nend\n"
  },
  {
    "path": "apps/omg_status/test/omg_status/release_tasks/set_tracer_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Status.ReleaseTasks.SetTracerTest do\n  use ExUnit.Case, async: true\n  import ExUnit.CaptureLog, only: [capture_log: 1]\n\n  alias OMG.Status.Metric.Tracer\n  alias OMG.Status.ReleaseTasks.SetTracer\n\n  @app :omg_status\n  setup do\n    {:ok, pid} = __MODULE__.System.start_link([])\n    nil = Process.put(__MODULE__.System, pid)\n    :ok\n  end\n\n  test \"if environment variables get applied in the configuration\" do\n    :ok = __MODULE__.System.put_env(\"DD_DISABLED\", \"TRUE\")\n    :ok = __MODULE__.System.put_env(\"APP_ENV\", \"YOLO\")\n    :ok = __MODULE__.System.put_env(\"HOSTNAME\", \"this is my tracer test 3\")\n\n    assert capture_log(fn ->\n             config = SetTracer.load([], system_adapter: __MODULE__.System)\n             disabled = config |> Keyword.fetch!(@app) |> Keyword.fetch!(Tracer) |> Keyword.fetch!(:disabled?)\n             env = config |> Keyword.fetch!(@app) |> Keyword.fetch!(Tracer) |> Keyword.fetch!(:env)\n\n             assert disabled == true\n             # if it's disabled, env doesn't matter, so we set it to an empty string\n             assert env == \"\"\n           end)\n  end\n\n  test \"if default configuration is used when there's no environment variables\" do\n    :ok = __MODULE__.System.put_env(\"HOSTNAME\", \"this is my tracer test 3\")\n\n    assert capture_log(fn ->\n             config = SetTracer.load([], system_adapter: __MODULE__.System)\n             # we set env to an empty string because disabled? is set to true!\n             configuration = @app |> Application.get_env(Tracer) |> Keyword.put(:env, \"\") |> Enum.sort()\n             tracer_config = config |> Keyword.get(@app) |> Keyword.get(Tracer) |> Enum.sort()\n             assert configuration == tracer_config\n           end)\n  end\n\n  test \"if environment variables get applied in the statix configuration\" do\n    set_cluster = \"cluster\"\n    set_port = \"1919\"\n    set_hostname = \"this is my tracer test 1\"\n    set_app_env = \"test 1\"\n    set_disabled = \"false\"\n    :ok = __MODULE__.System.put_env(\"DD_HOSTNAME\", set_cluster)\n    :ok = __MODULE__.System.put_env(\"DD_PORT\", set_port)\n    :ok = __MODULE__.System.put_env(\"HOSTNAME\", set_hostname)\n    :ok = __MODULE__.System.put_env(\"APP_ENV\", set_app_env)\n    :ok = __MODULE__.System.put_env(\"DD_DISABLED\", set_disabled)\n\n    assert capture_log(fn ->\n             config = SetTracer.load([], release: :test_case_1, system_adapter: __MODULE__.System)\n             port = config |> Keyword.fetch!(:statix) |> Keyword.fetch!(:port)\n             host = config |> Keyword.fetch!(:statix) |> Keyword.fetch!(:host)\n             tags = config |> Keyword.fetch!(:statix) |> Keyword.fetch!(:tags)\n             assert host == set_cluster\n             assert port == String.to_integer(set_port)\n             assert Enum.member?(tags, \"app_env:test 1\") == true\n           end)\n  end\n\n  test \"if default statix configuration is used when there's no environment variables\" do\n    app_env = \"test 2\"\n    hostname = \"this is my tracer test 2\"\n    disabled = \"false\"\n    :ok = __MODULE__.System.put_env(\"HOSTNAME\", hostname)\n    :ok = __MODULE__.System.put_env(\"APP_ENV\", app_env)\n    :ok = __MODULE__.System.put_env(\"DD_DISABLED\", disabled)\n    configuration = SetTracer.load([], release: :test_case_2, system_adapter: __MODULE__.System)\n    tags = configuration |> Keyword.fetch!(:statix) |> Keyword.fetch!(:tags)\n    assert tags == [\"application:test_case_2\", \"app_env:#{app_env}\", \"hostname:#{hostname}\"]\n  end\n\n  test \"if environment variables get applied in the spandex_datadog configuration\" do\n    :ok = __MODULE__.System.put_env(\"DD_HOSTNAME\", \"cluster\")\n    :ok = __MODULE__.System.put_env(\"DD_APM_PORT\", \"1919\")\n    :ok = __MODULE__.System.put_env(\"BATCH_SIZE\", \"7000\")\n    :ok = __MODULE__.System.put_env(\"SYNC_THRESHOLD\", \"900\")\n    :ok = __MODULE__.System.put_env(\"HOSTNAME\", \"this is my tracer test 4\")\n\n    capture_log(fn ->\n      config = SetTracer.load([], system_adapter: __MODULE__.System)\n      port = config |> Keyword.fetch!(:spandex_datadog) |> Keyword.fetch!(:port)\n      host = config |> Keyword.fetch!(:spandex_datadog) |> Keyword.fetch!(:host)\n      batch_size = config |> Keyword.fetch!(:spandex_datadog) |> Keyword.fetch!(:batch_size)\n      sync_threshold = config |> Keyword.fetch!(:spandex_datadog) |> Keyword.fetch!(:sync_threshold)\n      assert port == 1919\n      assert host == \"cluster\"\n      assert batch_size == 7000\n      assert sync_threshold == 900\n    end)\n  end\n\n  test \"if default spandex_datadog configuration is used when there's no environment variables\" do\n    :ok = __MODULE__.System.put_env(\"HOSTNAME\", \"this is my tracer test 5\")\n    config = SetTracer.load([], system_adapter: __MODULE__.System)\n    configuration = Application.get_all_env(:spandex_datadog)\n    sorted_configuration = configuration |> Enum.sort() |> Keyword.drop([:http])\n    spandex_datadog_config = Keyword.fetch!(config, :spandex_datadog)\n    assert sorted_configuration == Enum.sort(spandex_datadog_config)\n  end\n\n  test \"if exit is thrown when faulty configuration is used\" do\n    :ok = __MODULE__.System.put_env(\"DD_DISABLED\", \"TRUEeee\")\n    catch_exit(SetTracer.load([], system_adapter: __MODULE__.System))\n  end\n\n  test \"if exit is thrown when faulty configuration for hostname is used\" do\n    :ok = __MODULE__.System.put_env(\"DD_DISABLED\", \"TRUE\")\n    :ok = __MODULE__.System.put_env(\"APP_ENV\", \"YOLO\")\n    assert catch_exit(SetTracer.load([], system_adapter: __MODULE__.System)) == \"HOSTNAME is not set correctly.\"\n  end\n\n  defmodule System do\n    def start_link(args), do: GenServer.start_link(__MODULE__, args, [])\n    def get_env(key), do: __MODULE__ |> Process.get() |> GenServer.call({:get_env, key})\n    def put_env(key, value), do: __MODULE__ |> Process.get() |> GenServer.call({:put_env, key, value})\n    def init(_), do: {:ok, %{}}\n\n    def handle_call({:get_env, key}, _, state) do\n      {:reply, state[key], state}\n    end\n\n    def handle_call({:put_env, key, value}, _, state) do\n      {:reply, :ok, Map.put(state, key, value)}\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_status/test/sentry_filter_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Status.SentryFilterTest do\n  use ExUnit.Case, async: true\n\n  test \"excludes exception properly\" do\n    # ignore excluded exception\n    assert_raise(\n      Phoenix.NotAcceptableError,\n      fn ->\n        raise Phoenix.NotAcceptableError,\n              \"Could not render \\\"406.json\\\" for OMG.WatcherRPC.Web.Views.Error, please define a matching clause for render/2 or define a template at \\\"lib/omg_watcher_rpc_web/templates/views/error/*\\\". No templates were compiled for this module.\"\n      end\n    )\n\n    assert Sentry.capture_exception(\n             %Phoenix.NotAcceptableError{plug_status: 406},\n             event_source: :plug,\n             result: :sync\n           ) == :excluded\n  end\nend\n"
  },
  {
    "path": "apps/omg_status/test/test_helper.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nExUnit.configure(exclude: [integration: true, property: true, wrappers: true, common: true])\nExUnit.start()\n"
  },
  {
    "path": "apps/omg_utils/lib/omg_utils/app_version.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Utils.AppVersion do\n  @moduledoc false\n\n  @sha String.replace(elem(System.cmd(\"git\", [\"rev-parse\", \"--short=7\", \"HEAD\"]), 0), \"\\n\", \"\")\n\n  @doc \"\"\"\n  Derive the running service's version for adding to a response.\n  \"\"\"\n  @spec version(Application.app()) :: String.t()\n  def version(app) do\n    {:ok, vsn} = :application.get_key(app, :vsn)\n    List.to_string(vsn) <> \"+\" <> @sha\n  end\nend\n"
  },
  {
    "path": "apps/omg_utils/lib/omg_utils/http_rpc/encoding.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Utils.HttpRPC.Encoding do\n  @moduledoc \"\"\"\n  Provides binary to HEX and reverse encodings.\n  NOTE: Intentionally wraps see: `OMG.Eth.Encoding` to keep flexibility for change.\n  \"\"\"\n\n  @doc \"\"\"\n  Encodes raw binary to '0x'-preceded, lowercase HEX string\n  \"\"\"\n  # because https://github.com/rrrene/credo/issues/583, we need to:\n  # credo:disable-for-next-line Credo.Check.Consistency.SpaceAroundOperators\n  @spec to_hex(binary()) :: <<_::16, _::_*8>>\n  def to_hex(binary), do: \"0x\" <> Base.encode16(binary, case: :lower)\n\n  @doc \"\"\"\n  Decodes '0x'-preceded, lowercase HEX string to raw binary, see `to_hex`\n  \"\"\"\n  # because https://github.com/rrrene/credo/issues/583, we need to:\n  # credo:disable-for-next-line Credo.Check.Consistency.SpaceAroundOperators\n  @spec from_hex(<<_::16, _::_*8>>) :: {:ok, binary()} | {:error, :invalid_hex}\n  def from_hex(\"0x\" <> hexstr), do: Base.decode16(hexstr, case: :mixed)\n  def from_hex(_), do: {:error, :invalid_hex}\n\n  # credo:disable-for-next-line Credo.Check.Consistency.SpaceAroundOperators\n  @spec from_hex!(<<_::16, _::_*8>>) :: binary\n  def from_hex!(\"0x\" <> encoded), do: Base.decode16!(encoded, case: :lower)\nend\n"
  },
  {
    "path": "apps/omg_utils/lib/omg_utils/http_rpc/error.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Utils.HttpRPC.Error do\n  @moduledoc \"\"\"\n  Provides standard data structure for API Error response\n  \"\"\"\n  alias OMG.Utils.HttpRPC.Response\n\n  @doc \"\"\"\n  Serializes error's code and description provided in response's data field.\n  \"\"\"\n  @spec serialize(atom() | String.t(), String.t() | nil, map() | nil) :: map()\n  def serialize(code, description, messages \\\\ nil) do\n    %{\n      object: :error,\n      code: code,\n      description: description\n    }\n    |> add_messages(messages)\n    |> Response.serialize()\n  end\n\n  defp add_messages(data, nil), do: data\n  defp add_messages(data, messages), do: Map.put_new(data, :messages, messages)\nend\n"
  },
  {
    "path": "apps/omg_utils/lib/omg_utils/http_rpc/response.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Utils.HttpRPC.Response do\n  @moduledoc \"\"\"\n  Serializes the response into expected result/data format.\n  \"\"\"\n  alias OMG.Utils.HttpRPC.Encoding\n\n  @type response_t :: %{version: binary(), success: boolean(), data: map()}\n\n  def serialize_page(data, data_paging) do\n    data\n    |> serialize()\n    |> Map.put(:data_paging, data_paging)\n  end\n\n  @doc \"\"\"\n  Append result of operation to the response data forming standard api response structure\n  \"\"\"\n  @spec serialize(any()) :: response_t()\n  def serialize(%{object: :error} = error) do\n    to_response(error, :error)\n  end\n\n  def serialize(data) do\n    data\n    |> sanitize()\n    |> to_response(:success)\n  end\n\n  @doc \"\"\"\n  Removes or encodes fields in response that cannot be serialized to api response.\n  By default, it:\n   * encodes to hex all binary values\n   * removes metadata fields\n  Provides standard data structure for API response\n  \"\"\"\n  @spec sanitize(any()) :: any()\n  def sanitize(response)\n\n  # serialize all DateTimes to ISO8601 formatted strings\n  def sanitize(%DateTime{} = datetime) do\n    datetime |> DateTime.truncate(:second) |> DateTime.to_iso8601()\n  end\n\n  def sanitize(list) when is_list(list) do\n    Enum.map(list, &sanitize/1)\n  end\n\n  def sanitize(map_or_struct) when is_map(map_or_struct) do\n    map_or_struct\n    |> to_map()\n    |> do_filter()\n    |> sanitize_map()\n  end\n\n  def sanitize(bin) when is_binary(bin), do: Encoding.to_hex(bin)\n  def sanitize({:skip_hex_encode, bin}), do: bin\n  def sanitize({{key, value}, _}), do: Map.put_new(%{}, key, value)\n  def sanitize({key, value}), do: Map.put_new(%{}, key, value)\n  def sanitize(value), do: value\n\n  defp do_filter(map_or_struct) do\n    if :code.is_loaded(Ecto) do\n      Enum.filter(map_or_struct, fn\n        {_, %{__struct__: Ecto.Association.NotLoaded}} -> false\n        _ -> true\n      end)\n      |> Map.new()\n    else\n      map_or_struct\n    end\n  end\n\n  # Allows to skip sanitize on specifies keys provided in list in key :skip_hex_encode\n  defp sanitize_map(map) do\n    {skip_keys, map} = Map.pop(map, :skip_hex_encode, [])\n    skip_keys = MapSet.new(skip_keys)\n\n    map\n    |> Enum.map(fn {k, v} ->\n      case MapSet.member?(skip_keys, k) do\n        true -> {k, v}\n        false -> {k, sanitize(v)}\n      end\n    end)\n    |> Map.new()\n  end\n\n  defp to_map(struct), do: Map.drop(struct, [:__struct__, :__meta__])\n\n  defp to_response(data, result) do\n    %{\n      success: result == :success,\n      data: data\n    }\n  end\nend\n"
  },
  {
    "path": "apps/omg_utils/lib/omg_utils/http_rpc/validators/base.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Utils.HttpRPC.Validator.Base do\n  @moduledoc \"\"\"\n  Implements simple validation engine with basic validators provided and allows to chain them\n  to make more comprehensive one.\n  \"\"\"\n\n  alias OMG.Utils.HttpRPC.Encoding\n\n  @type validation_error_t() :: {:error, {:validation_error, binary(), any()}}\n\n  # Creates a named chain of basic validators aka alias, for easier to use.\n  # IMPORTANT: Alias can use already defined validators, not other aliases (no backtracking)\n  @aliases %{\n    address: [:hex, length: 20],\n    currency: [:hex, length: 20],\n    hash: [:hex, length: 32],\n    signature: [:hex, length: 65],\n    pos_integer: [:integer, greater: 0],\n    non_neg_integer: [:integer, greater: -1]\n  }\n\n  @doc \"\"\"\n  Validates value of given key in the map with provided list of validators.\n  First validator list is preprocessed which replaces aliases with its definitions.\n  Then value is fetched from the map and each validator is run passing a tuple\n  where first element is a value and second validation error from previous validator.\n  If all validators succeed on the value the second element is empty list (no validation errors).\n  Last result of the validation is translated to {:ok, value} or error.\n\n  ## Examples\n  * `expect(args, \"arg_name\", [:integer, greater: 1000])`\n    Validate and positive integer greater than 1000\n\n  * `expect(args, \"arg_name\", [:integer, :optional])`\n  Validate integer value or when `arg_name` key is missing {:ok, `nil`} is returned\n\n  * `expect(args, \"arg_name\", [:optional, :integer])`\n    NOTE: **invalid order** it's the same as just `:integer`\n    To validate optional integer values it should be `:integer, :optional`\n  \"\"\"\n  @spec expect(map(), atom() | binary(), atom() | list()) :: {:ok, any()} | validation_error_t()\n  def expect(map, key, atom) when is_atom(atom), do: expect(map, key, [atom])\n\n  def expect(map, key, opts) do\n    opts\n    |> replace_aliases()\n    |> Enum.reduce(\n      map |> get(key),\n      &validate/2\n    )\n    |> case do\n      {val, []} -> {:ok, val}\n      {_, [err | _]} -> error(key, err)\n    end\n  end\n\n  @doc \"\"\"\n  Creates custom validation error\n  \"\"\"\n  @spec error(binary(), any()) :: validation_error_t()\n  def error(parent_name, {:validation_error, child_name, reason}),\n    do: error(parent_name <> \".\" <> child_name, reason)\n\n  def error(param_name, reason) when is_binary(param_name),\n    do: {:error, {:validation_error, param_name, reason}}\n\n  @doc \"\"\"\n  Unwraps elements from the results list: `[{:ok, elt} | {:error, any()}]` or returns the first error\n  \"\"\"\n  @spec all_success_or_error([{:ok, any()} | {:error, any()}]) :: list() | {:error, any()}\n  def all_success_or_error(result_list) do\n    with nil <- result_list |> Enum.find(&(:error == Kernel.elem(&1, 0))),\n         do:\n           result_list\n           |> Enum.map(fn {:ok, elt} -> elt end)\n  end\n\n  @doc \"\"\"\n  `integer` function is an example of basic validator used by the engine.\n  Validators are passed to the `expect` function in `opts` parameter as a keyword list.\n  Each validator expects a tuple, where first element is value of specified `key` in `map`\n  possibly processed by previous validators in `opts` list. Second element is a validator list\n  which fails on the value.\n  It depends on validator but usually if some previous validator returns error on value, others\n  just pass the error through and do not add themselves to the list.\n  \"\"\"\n  @spec integer({any(), list()}) :: {any(), list()}\n  def integer({_, [_ | _]} = err), do: err\n  def integer({val, []} = acc) when is_integer(val), do: acc\n  def integer({val, []}), do: {val, [:integer]}\n\n  @spec optional({any(), list()}) :: {any(), list()}\n  def optional({val, _}) when val in [:missing, nil], do: {nil, []}\n  def optional(acc), do: acc\n\n  @spec optional({any(), list()}, atom()) :: {any(), list()}\n  def optional({val, _}, true) when val in [:missing, nil], do: {nil, []}\n  def optional(acc, _), do: acc\n\n  @spec hex({any(), list()}) :: {any(), list()}\n  def hex({_, [_ | _]} = err), do: err\n\n  def hex({str, []}) do\n    case Encoding.from_hex(str) do\n      {:ok, bin} ->\n        {bin, []}\n\n      _ ->\n        {str, [:hex]}\n    end\n  end\n\n  @spec length({any(), list()}, non_neg_integer()) :: {any(), list()}\n  def length({_, [_ | _]} = err, _len), do: err\n\n  def length({str, []}, len) when is_binary(str) do\n    if Kernel.byte_size(str) == len,\n      do: {str, []},\n      else: {str, length: len}\n  end\n\n  def length({val, []}, len), do: {val, length: len}\n\n  @spec max_length({any(), list()}, non_neg_integer()) :: {any(), list()}\n  def max_length({_, [_ | _]} = err, _len), do: err\n  def max_length({list, []}, len) when is_list(list) and length(list) <= len, do: {list, []}\n  def max_length({val, []}, len), do: {val, max_length: len}\n\n  @spec min_length({any(), list()}, non_neg_integer()) :: {any(), list()}\n  def min_length({_, [_ | _]} = err, _len), do: err\n  def min_length({list, []}, len) when is_list(list) and length(list) >= len, do: {list, []}\n  def min_length({val, []}, len), do: {val, min_length: len}\n\n  @spec greater({any(), list()}, integer()) :: {any(), list()}\n  def greater({_, [_ | _]} = err, _b), do: err\n  def greater({val, []}, bound) when is_integer(val) and val > bound, do: {val, []}\n  def greater({val, []}, _b) when not is_integer(val), do: {val, [:integer]}\n  def greater({val, []}, bound), do: {val, greater: bound}\n\n  @spec lesser({any(), list()}, integer()) :: {any(), list()}\n  def lesser({_, [_ | _]} = err, _b), do: err\n  def lesser({val, []}, bound) when is_integer(val) and val < bound, do: {val, []}\n  def lesser({val, []}, _b) when not is_integer(val), do: {val, [:integer]}\n  def lesser({val, []}, bound), do: {val, lesser: bound}\n\n  @spec list({any(), list()}, function() | nil) :: {any(), list()}\n  def list(tuple, mapper \\\\ nil)\n  def list({_, [_ | _]} = err, _), do: err\n  def list({val, []}, nil) when is_list(val), do: {val, []}\n  def list({val, []}, mapper) when is_list(val), do: list_processor(val, mapper)\n  def list({val, _}, _), do: {val, [:list]}\n\n  @spec map({any(), list()}, function() | nil) :: {any(), list()}\n  def map(tuple, parser \\\\ nil)\n  def map({_, [_ | _]} = err, _), do: err\n  def map({val, []}, nil) when is_map(val), do: {val, []}\n\n  def map({val, []}, parser) when is_map(val),\n    do:\n      (case parser.(val) do\n         {:error, err} -> {val, [err]}\n         {:ok, map} -> {map, []}\n       end)\n\n  def map({val, _}, _), do: {val, [:map]}\n\n  defp list_processor(val, mapper) do\n    list_reducer = fn\n      {:error, map_err}, _acc -> {:halt, map_err}\n      {:ok, elt}, acc -> {:cont, [elt | acc]}\n      elt, acc -> {:cont, [elt | acc]}\n    end\n\n    val\n    |> Enum.reduce_while([], fn elt, acc -> mapper.(elt) |> list_reducer.(acc) end)\n    |> case do\n      list when is_list(list) ->\n        {Enum.reverse(list), []}\n\n      err ->\n        {val, [err]}\n    end\n  end\n\n  # provides initial value to the validators reducer, see: `expect`\n  defp get(map, key), do: {Map.get(map, key, :missing), []}\n\n  defp validate(validator, acc) when is_atom(validator), do: Kernel.apply(__MODULE__, validator, [acc])\n  defp validate({validator, args}, acc), do: Kernel.apply(__MODULE__, validator, [acc, args])\n\n  defp replace_aliases(validators) do\n    validators\n    |> Enum.reduce(\n      [],\n      fn v, acc ->\n        key = validator_name(v)\n        pre = Map.get(@aliases, key, [v])\n\n        [_ | _] = acc ++ pre\n      end\n    )\n  end\n\n  defp validator_name(v) when is_atom(v), do: v\n  defp validator_name({v, _}), do: v\nend\n"
  },
  {
    "path": "apps/omg_utils/lib/omg_utils/paginator.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\ndefmodule OMG.Utils.Paginator do\n  @moduledoc \"\"\"\n  Wraps resulted query data along with pagination information used.\n  \"\"\"\n\n  @default_limit 200\n  @first_page 1\n\n  @type t(data_type) :: %__MODULE__{\n          data: list(data_type),\n          data_paging: %{limit: pos_integer(), page: pos_integer()}\n        }\n\n  defstruct data: [],\n            data_paging: %{\n              limit: @default_limit,\n              page: @first_page\n            }\n\n  @doc \"\"\"\n  Creates new paginator from query constraints like [limit: 200, page: 1], none of keys is required.\n  \"\"\"\n  @spec from_constraints(Keyword.t(), integer()) :: %__MODULE__{:data => [], :data_paging => map()}\n  def from_constraints(constraints, max_limit) when is_integer(max_limit) do\n    data_paging =\n      constraints\n      |> Keyword.take([:page, :limit])\n      |> Keyword.put_new(:page, @first_page)\n      |> Keyword.update(:limit, max_limit, &min(&1, max_limit))\n      |> Map.new()\n\n    %__MODULE__{data: [], data_paging: data_paging}\n  end\n\n  @spec set_data(list(), t(%__MODULE__{})) :: t(%__MODULE__{})\n  def set_data(data, paginator) when is_list(data), do: %__MODULE__{paginator | data: data}\nend\n"
  },
  {
    "path": "apps/omg_utils/lib/omg_utils/remote_ip.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\ndefmodule OMG.Utils.RemoteIP do\n  @moduledoc \"\"\"\n  This plug sets remote_ip from CF-Connecting-IP header.\n  \"\"\"\n  import Plug.Conn\n\n  @header_name \"cf-connecting-ip\"\n\n  def init(options), do: options\n\n  def call(conn, _opts) do\n    ips = get_req_header(conn, @header_name)\n\n    parse_and_set_ip(conn, ips)\n  end\n\n  defp parse_and_set_ip(conn, [forwarded_ips]) when is_binary(forwarded_ips) do\n    left_ip =\n      forwarded_ips\n      |> String.split(\",\")\n      |> List.first()\n\n    parse_ip(conn, left_ip)\n  end\n\n  defp parse_and_set_ip(conn, _ip), do: conn\n\n  defp parse_ip(conn, ip_string) when is_binary(ip_string) do\n    parsed_ip =\n      ip_string\n      |> String.trim()\n      |> String.to_charlist()\n      |> :inet.parse_address()\n\n    case parsed_ip do\n      {:ok, ip} -> %{conn | remote_ip: ip}\n      _ -> conn\n    end\n  end\n\n  defp parse_ip(conn, _), do: conn\nend\n"
  },
  {
    "path": "apps/omg_utils/lib/utils.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Utils do\n  @moduledoc false\nend\n"
  },
  {
    "path": "apps/omg_utils/mix.exs",
    "content": "defmodule Utils.MixProject do\n  use Mix.Project\n\n  def project() do\n    [\n      app: :omg_utils,\n      version: version(),\n      build_path: \"../../_build\",\n      config_path: \"../../config/config.exs\",\n      deps_path: \"../../deps\",\n      lockfile: \"../../mix.lock\",\n      elixir: \"~> 1.8\",\n      elixirc_paths: elixirc_paths(Mix.env()),\n      start_permanent: Mix.env() == :prod,\n      deps: [],\n      test_coverage: [tool: ExCoveralls]\n    ]\n  end\n\n  def application() do\n    [extra_applications: [:plug]]\n  end\n\n  defp version() do\n    \"git\"\n    |> System.cmd([\"describe\", \"--tags\", \"--abbrev=0\"])\n    |> elem(0)\n    |> String.replace(\"v\", \"\")\n    |> String.replace(\"\\n\", \"\")\n  end\n\n  # Specifies which paths to compile per environment.\n  defp elixirc_paths(:prod), do: [\"lib\"]\n  defp elixirc_paths(:dev), do: [\"lib\"]\n  defp elixirc_paths(:test), do: [\"lib\", \"test/support\"]\nend\n"
  },
  {
    "path": "apps/omg_utils/test/omg_utils/app_version_tet.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Utils.AppVersionTest do\n  use ExUnit.Case, async: true\n\n  alias OMG.Utils.AppVersion\n\n  describe \"version/1\" do\n    test \"returns a compliant semver when given an application\" do\n      # Using :elixir as the app because it is certain to be running during the test\n      version = AppVersion.version(:elixir)\n      assert {:ok, _} = Version.parse(version)\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_utils/test/omg_utils/http_rpc/encoding_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Utils.HttpRPC.EncodingTest do\n  use ExUnit.Case, async: true\n\n  alias OMG.Utils.HttpRPC.Encoding\n\n  test \"decodes all up/down/mixed case values\" do\n    assert [{:ok, <<222, 173, 190, 239>>}, {:ok, <<222, 173, 190, 239>>}, {:ok, <<222, 173, 190, 239>>}] ==\n             Enum.map([\"0xdeadbeef\", \"0xDEADBEEF\", \"0xDeadBeeF\"], &Encoding.from_hex/1)\n  end\n\n  test \"doesn't decode hex without '0x' prefix\" do\n    assert {:error, :invalid_hex} == Encoding.from_hex(\"deadbeef\")\n  end\n\n  test \"encodes stuff\" do\n    assert \"0xdeadbeef\" == Encoding.to_hex(<<222, 173, 190, 239>>)\n  end\nend\n"
  },
  {
    "path": "apps/omg_utils/test/omg_utils/http_rpc/response_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Utils.HttpRPC.ResponseTest do\n  use ExUnit.Case, async: true\n\n  alias OMG.Utils.HttpRPC.Response\n  alias OMG.WatcherInfo.DB\n\n  @cleaned_tx %{\n    blknum: nil,\n    txbytes: nil,\n    txhash: nil,\n    txindex: nil,\n    txtype: nil,\n    metadata: nil,\n    inserted_at: nil,\n    updated_at: nil\n  }\n\n  setup %{} do\n    load_ecto()\n    :ok\n  end\n\n  describe \"test sanitization without ecto preloaded\" do\n    test \"cleaning response: simple value list works without ecto loaded\" do\n      unload_ecto()\n      value = [nil, 1, \"01234\", :atom, [], %{}, {:skip_hex_encode, \"an arbitrary string\"}]\n      expected_value = [nil, 1, \"0x3031323334\", :atom, [], %{}, \"an arbitrary string\"]\n      assert expected_value == Response.sanitize(value)\n    end\n\n    test \"cleaning response structure: list of maps when ecto unloaded\" do\n      unload_ecto()\n      refute [@cleaned_tx, @cleaned_tx] == Response.sanitize([%DB.Transaction{}, %DB.Transaction{}])\n    end\n  end\n\n  test \"cleaning response structure: map of maps\" do\n    assert %{first: @cleaned_tx, second: @cleaned_tx} ==\n             Response.sanitize(%{second: %DB.Transaction{}, first: %DB.Transaction{}})\n  end\n\n  test \"cleaning response structure: list of maps\" do\n    assert [@cleaned_tx, @cleaned_tx] == Response.sanitize([%DB.Transaction{}, %DB.Transaction{}])\n  end\n\n  test \"cleaning response: simple value list\" do\n    value = [nil, 1, \"01234\", :atom, [], %{}, {:skip_hex_encode, \"an arbitrary string\"}]\n    expected_value = [nil, 1, \"0x3031323334\", :atom, [], %{}, \"an arbitrary string\"]\n\n    assert expected_value == Response.sanitize(value)\n  end\n\n  test \"cleaning response: remove nested meta keys\" do\n    data =\n      %{\n        address: \"0xd5b6e653beec1f8131d2ea4f574b2fd58770d9e0\",\n        utxos: [\n          %{\n            __meta__: %{context: nil, source: {nil, \"txoutputs\"}, state: :loaded},\n            amount: 1,\n            creating_deposit: \"hash1\",\n            creating_transaction: nil,\n            currency: String.duplicate(\"00\", 20),\n            deposit: %{\n              __meta__: %{context: nil, source: {nil, \"txoutputs\"}, state: :loaded},\n              blknum: 1,\n              txindex: 0,\n              event_type: :deposit,\n              hash: \"hash1\"\n            },\n            id: 1\n          }\n        ]\n      }\n      |> Response.sanitize()\n\n    assert false ==\n             Enum.any?(\n               hd(data.utxos).deposit,\n               &match?({:__meta__, _}, &1)\n             )\n  end\n\n  test \"sanitize alarm types\" do\n    system_alarm = {:system_memory_high_watermark, []}\n    system_disk_alarm = {{:disk_almost_full, \"/dev/null\"}, []}\n    app_alarm = {:ethereum_connection_error, %{node: Node.self(), reporter: __MODULE__}}\n\n    assert %{disk_almost_full: \"/dev/null\"} == Response.sanitize(system_disk_alarm)\n\n    assert %{ethereum_connection_error: %{node: Node.self(), reporter: __MODULE__}} ==\n             Response.sanitize(app_alarm)\n\n    assert %{system_memory_high_watermark: []} == Response.sanitize(system_alarm)\n  end\n\n  test \"skiping sanitize for specified keys\" do\n    # simplified EIP-712 structures serialization where\n    # `types` should be skip entirely\n    # `domain` sanitized partially\n    # `message` fully sanitized\n\n    address = <<124, 39, 109, 202, 171, 153, 189, 22, 22, 60, 27, 204, 230, 113, 202, 214, 161, 236, 9, 69>>\n    address_hex = \"0x7c276dcaab99bd16163c1bcce671cad6a1ec0945\"\n    zero20_hex = \"0x\" <> String.duplicate(\"00\", 20)\n\n    domain_spec = [\n      %{name: \"name\", type: \"string\"},\n      %{name: \"verifyingContract\", type: \"address\"},\n      %{name: \"chainId\", type: \"uint256\"}\n    ]\n\n    domain_data = %{\n      name: {:skip_hex_encode, \"OMG Network\"},\n      verifyingContract: address,\n      chainId: 32\n    }\n\n    message = %{\n      input0: %{owner: address, currency: <<0::160>>, amount: 111}\n    }\n\n    typed_data = %{\n      types: %{Eip712Domain: domain_spec},\n      primaryType: \"Transaction\",\n      domain: domain_data,\n      message: message,\n\n      # spicifies key to skip during sanitize\n      skip_hex_encode: [:types, :primaryType, :non_existing]\n    }\n\n    response = Response.sanitize(typed_data)\n\n    assert %{\n             domain: %{name: \"OMG Network\", verifyingContract: ^address_hex, chainId: 32},\n             message: %{\n               input0: %{owner: ^address_hex, currency: ^zero20_hex, amount: 111}\n             },\n             primaryType: \"Transaction\",\n             types: %{Eip712Domain: ^domain_spec}\n           } = response\n\n    # meta-key is removed from sanitized response\n    assert response |> Map.get(:skip_hex_encode) |> is_nil()\n  end\n\n  defp unload_ecto() do\n    :code.purge(Ecto)\n    :code.delete(Ecto)\n    false = :code.is_loaded(Ecto)\n  end\n\n  defp load_ecto(), do: true = Code.ensure_loaded?(Ecto)\nend\n"
  },
  {
    "path": "apps/omg_utils/test/omg_utils/http_rpc/validators/base_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Utils.HttpRPC.Validator.BaseTest do\n  alias OMG.Utils.HttpRPC.Encoding\n\n  use ExUnitFixtures\n  use ExUnit.Case, async: true\n\n  import OMG.Utils.HttpRPC.Validator.Base\n\n  @params %{\n    \"int_1\" => -1_234_567_890,\n    \"int_2\" => 0,\n    \"int_3\" => 1_234_567_890,\n    \"nint_1\" => \"1234567890\",\n    \"nil\" => nil,\n    \"opt_1\" => true,\n    \"hex_1\" => \"0x1234567890abcdef\",\n    \"hex_2\" => \"0x1234567890AbCdEf\",\n    \"hex_3\" => \"0x1234567890ABCDEF\",\n    \"non_hex_1\" => \"!@#$%^&*().?\",\n    \"non_hex_2\" => \"1234567890ABCDE\",\n    \"non_hex_3\" => \"1234567890ABCDEZ\",\n    \"valid_address\" => \"0x\" <> String.duplicate(\"00\", 20),\n    \"non_hex_address\" => \"0x\" <> String.duplicate(\"ZZ\", 20),\n    \"too_long_address\" => \"0x\" <> String.duplicate(\"00\", 21),\n    \"too_short_address\" => \"0x\" <> String.duplicate(\"00\", 19),\n    \"valid_signature\" => \"0x\" <> String.duplicate(\"00\", 65),\n    \"non_hex_signature\" => \"0x\" <> String.duplicate(\"ZZ\", 65),\n    \"too_long_signature\" => \"0x\" <> String.duplicate(\"00\", 66),\n    \"too_short_signature\" => \"0x\" <> String.duplicate(\"00\", 64),\n    \"valid_hash\" => \"0x\" <> String.duplicate(\"00\", 32),\n    \"non_hex_hash\" => \"0x\" <> String.duplicate(\"ZZ\", 32),\n    \"too_long_hash\" => \"0x\" <> String.duplicate(\"00\", 33),\n    \"too_short_hash\" => \"0x\" <> String.duplicate(\"00\", 31),\n    \"len_1\" => \"1\",\n    \"len_2\" => <<1, 2, 3, 4, 5>>,\n    \"max_len_1\" => [1, 2, 3, 4, 5],\n    \"min_len_1\" => [1, 2, 3]\n  }\n\n  describe \"Basic validation:\" do\n    test \"integer: positive cases\" do\n      assert {:ok, -1_234_567_890} == expect(@params, \"int_1\", :integer)\n      assert {:ok, 0} == expect(@params, \"int_2\", :integer)\n      assert {:ok, 1_234_567_890} == expect(@params, \"int_3\", [:integer])\n    end\n\n    test \"integer: negative cases\" do\n      assert {:error, {:validation_error, \"nint_1\", :integer}} == expect(@params, \"nint_1\", :integer)\n      assert {:error, {:validation_error, \"nil\", :integer}} == expect(@params, \"nil\", :integer)\n    end\n\n    test \"optional: positive cases\" do\n      assert {:ok, 0} == expect(@params, \"int_2\", :optional)\n      assert {:ok, true} == expect(@params, \"opt_1\", :optional)\n      assert {:ok, nil} == expect(@params, \"no_such_key\", [:optional])\n\n      assert {:ok, nil} == expect(@params, \"nil\", [:integer, :optional])\n      assert {:ok, 1_234_567_890} == expect(@params, \"int_3\", [:integer, :optional])\n    end\n\n    test \"optional, list\" do\n      list = [1, 2, 3]\n      assert {:ok, list} == expect(%{\"list\" => list}, \"list\", [:list, :optional])\n      assert {:ok, nil} == expect(%{}, \"list\", [:list, :optional])\n      assert {:ok, nil} == expect(%{}, \"list\", list: &(&1 * 2), optional: true)\n      assert {:ok, [2, 4, 6]} == expect(%{\"list\" => list}, \"list\", list: &(&1 * 2), optional: true)\n      assert {:error, {:validation_error, \"list\", :list}} == expect(%{}, \"list\", list: &(&1 * 2), optional: false)\n    end\n\n    test \"optional: negative cases\" do\n      assert {:error, {:validation_error, \"nil\", :integer}} == expect(@params, \"nil\", [:optional, :integer])\n    end\n\n    test \"hex: positive cases\" do\n      {:ok, hex_1_value} = @params |> Map.get(\"hex_1\") |> Encoding.from_hex()\n      assert {:ok, hex_1_value} == expect(@params, \"hex_1\", :hex)\n\n      {:ok, hex_2_value} = @params |> Map.get(\"hex_2\") |> Encoding.from_hex()\n      assert {:ok, hex_2_value} == expect(@params, \"hex_2\", :hex)\n\n      {:ok, hex_3_value} = @params |> Map.get(\"hex_3\") |> Encoding.from_hex()\n      assert {:ok, hex_3_value} == expect(@params, \"hex_3\", :hex)\n    end\n\n    test \"hex: negative cases\" do\n      assert {:error, {:validation_error, \"non_hex_1\", :hex}} == expect(@params, \"non_hex_1\", :hex)\n    end\n\n    test \"length: positive cases\" do\n      assert {:ok, \"1\"} == expect(@params, \"len_1\", length: 1)\n      assert {:ok, <<1, 2, 3, 4, 5>>} == expect(@params, \"len_2\", length: 5)\n      assert {:ok, [1, 2, 3, 4, 5]} == expect(@params, \"max_len_1\", max_length: 10)\n      assert {:ok, [1, 2, 3, 4, 5]} == expect(@params, \"max_len_1\", max_length: 5)\n    end\n\n    test \"length: negative cases\" do\n      assert {:error, {:validation_error, \"len_1\", {:length, 5}}} == expect(@params, \"len_1\", length: 5)\n      assert {:error, {:validation_error, \"len_2\", {:length, 1}}} == expect(@params, \"len_2\", length: 1)\n      assert {:error, {:validation_error, \"max_len_1\", {:max_length, 3}}} == expect(@params, \"max_len_1\", max_length: 3)\n    end\n\n    test \"max_length: positive cases\" do\n      assert {:ok, [1, 2, 3, 4, 5]} == expect(@params, \"max_len_1\", max_length: 10)\n      assert {:ok, [1, 2, 3, 4, 5]} == expect(@params, \"max_len_1\", max_length: 5)\n    end\n\n    test \"max_length: negative cases\" do\n      assert {:error, {:validation_error, \"max_len_1\", {:max_length, 3}}} == expect(@params, \"max_len_1\", max_length: 3)\n    end\n\n    test \"min_length: positive cases\" do\n      assert {:ok, [1, 2, 3]} == expect(@params, \"min_len_1\", min_length: 0)\n      assert {:ok, [1, 2, 3]} == expect(@params, \"min_len_1\", min_length: 2)\n      assert {:ok, [1, 2, 3]} == expect(@params, \"min_len_1\", min_length: 3)\n    end\n\n    test \"min_length: negative cases\" do\n      assert {:error, {:validation_error, \"min_len_1\", {:min_length, 4}}} == expect(@params, \"min_len_1\", min_length: 4)\n    end\n\n    test \"list: positive cases\" do\n      list = [1, \"a\", :b]\n      assert {:ok, list} == expect(%{\"list\" => list}, \"list\", :list)\n    end\n\n    test \"list: negative cases\" do\n      assert {:error, {:validation_error, \"list\", :list}} == expect(%{\"list\" => \"[42]\"}, \"list\", :list)\n    end\n\n    test \"map: positive cases\" do\n      map = %{\"a\" => 0, \"b\" => 1}\n      assert {:ok, map} == expect(%{\"map\" => map}, \"map\", :map)\n    end\n\n    test \"map: negative cases\" do\n      assert {:error, {:validation_error, \"map\", :map}} == expect(%{\"map\" => [42]}, \"map\", :map)\n    end\n\n    test \"map, missing\" do\n      assert {:error, {:validation_error, \"map\", :map}} == expect(%{}, \"map\", :map)\n    end\n  end\n\n  describe \"list and map preprocessing:\" do\n    test \"mapping list elements\" do\n      assert {:ok, [2, 4, 6]} == expect(%{\"list\" => [1, 2, 3]}, \"list\", list: &(&1 * 2))\n    end\n\n    test \"validating list elements\" do\n      is_even = fn\n        elt when rem(elt, 2) == 0 -> {:ok, elt}\n        _ -> {:error, :odd_number}\n      end\n\n      assert {:ok, [2, 4, 6]} ==\n               expect(\n                 %{\"all_even\" => [2, 4, 6]},\n                 \"all_even\",\n                 list: is_even\n               )\n\n      assert {:error, {:validation_error, \"all_even\", :odd_number}} ==\n               expect(\n                 %{\"all_even\" => [2, 3, 6]},\n                 \"all_even\",\n                 list: is_even\n               )\n    end\n\n    test \"parsing map\" do\n      parser = fn map ->\n        with {:ok, currency} <- expect(map, \"currency\", :address),\n             {:ok, amount} <- expect(map, \"amount\", :non_neg_integer),\n             do: {:ok, %{currency: currency, amount: amount}}\n      end\n\n      {:ok, address_value} = @params |> Map.get(\"valid_address\") |> Encoding.from_hex()\n\n      assert {:ok, %{currency: address_value, amount: 100}} ==\n               expect(\n                 %{\"fee\" => %{\"currency\" => @params[\"valid_address\"], \"amount\" => 100}},\n                 \"fee\",\n                 map: parser\n               )\n\n      assert {:error, {:validation_error, \"fee.currency\", :hex}} =\n               expect(\n                 %{\"fee\" => %{\"currency\" => @params[\"non_hex_address\"], \"amount\" => 100}},\n                 \"fee\",\n                 map: parser\n               )\n    end\n\n    test \"unwrapping results list\" do\n      list = Enum.to_list(0..9)\n\n      ok_list = Enum.map(list, &{:ok, &1})\n      assert list == all_success_or_error(ok_list)\n\n      error = {:error, \"bad news\"}\n      list_with_err = Enum.shuffle([error | ok_list])\n      assert error == all_success_or_error(list_with_err)\n    end\n  end\n\n  describe \"Preprocessors:\" do\n    test \"greater: positive cases\" do\n      assert {:ok, 0} == expect(@params, \"int_2\", greater: -1)\n\n      assert {:ok, 1_234_567_890} == expect(@params, \"int_3\", greater: 1_000_000_000)\n    end\n\n    test \"greater: negative cases\" do\n      assert {:error, {:validation_error, \"int_2\", {:greater, 0}}} == expect(@params, \"int_2\", greater: 0)\n\n      assert {:error, {:validation_error, \"nint_1\", :integer}} == expect(@params, \"nint_1\", greater: 0)\n    end\n\n    test \"lesser: positive cases\" do\n      upper_bound = 1000\n      limit_param = 999\n\n      assert {:ok, 999} = expect(%{\"limit\" => limit_param}, \"limit\", lesser: upper_bound)\n    end\n\n    test \"lesser: negative cases\" do\n      upper_bound = 1000\n      limit_param = 1001\n\n      assert {:error, {:validation_error, \"limit\", {:lesser, 1000}}} =\n               expect(%{\"limit\" => limit_param}, \"limit\", lesser: upper_bound)\n    end\n\n    test \"address should validate both hex value and length\" do\n      {:ok, address_value} = @params |> Map.get(\"valid_address\") |> Encoding.from_hex()\n      assert {:ok, address_value} == expect(@params, \"valid_address\", :address)\n\n      assert {:error, {:validation_error, \"non_hex_address\", :hex}} == expect(@params, \"non_hex_address\", :address)\n\n      assert {:error, {:validation_error, \"too_short_address\", {:length, 20}}} ==\n               expect(@params, \"too_short_address\", :address)\n\n      assert {:error, {:validation_error, \"too_long_address\", {:length, 20}}} ==\n               expect(@params, \"too_long_address\", :address)\n    end\n\n    test \"signature should validate both hex value and length\" do\n      {:ok, signature_value} = @params |> Map.get(\"valid_signature\") |> Encoding.from_hex()\n      assert {:ok, signature_value} == expect(@params, \"valid_signature\", :signature)\n\n      assert {:error, {:validation_error, \"non_hex_signature\", :hex}} ==\n               expect(@params, \"non_hex_signature\", :signature)\n\n      assert {:error, {:validation_error, \"too_short_signature\", {:length, 65}}} ==\n               expect(@params, \"too_short_signature\", :signature)\n\n      assert {:error, {:validation_error, \"too_long_signature\", {:length, 65}}} ==\n               expect(@params, \"too_long_signature\", :signature)\n    end\n\n    test \"hash should validate both hex value and length\" do\n      {:ok, hash_value} = @params |> Map.get(\"valid_hash\") |> Encoding.from_hex()\n      assert {:ok, hash_value} == expect(@params, \"valid_hash\", :hash)\n\n      assert {:error, {:validation_error, \"non_hex_hash\", :hex}} ==\n               expect(@params, \"non_hex_hash\", :hash)\n\n      assert {:error, {:validation_error, \"too_short_hash\", {:length, 32}}} ==\n               expect(@params, \"too_short_hash\", :hash)\n\n      assert {:error, {:validation_error, \"too_long_hash\", {:length, 32}}} ==\n               expect(@params, \"too_long_hash\", :hash)\n    end\n  end\n\n  test \"positive and non negative integers\" do\n    args = %{\n      \"neg\" => -1,\n      \"zero\" => 0,\n      \"pos\" => 1,\n      \"NaN\" => true\n    }\n\n    assert {:ok, 0} == expect(args, \"zero\", :non_neg_integer)\n    assert {:error, {:validation_error, \"neg\", {:greater, -1}}} == expect(args, \"neg\", :non_neg_integer)\n\n    assert {:ok, 1} == expect(args, \"pos\", :pos_integer)\n\n    assert {:error, {:validation_error, \"zero\", {:greater, 0}}} == expect(args, \"zero\", :pos_integer)\n\n    assert {:error, {:validation_error, \"NaN\", :integer}} == expect(args, \"NaN\", :pos_integer)\n  end\nend\n"
  },
  {
    "path": "apps/omg_utils/test/omg_utils/remote_ip_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License\ndefmodule OMG.Utils.RemoteIPTest do\n  use ExUnit.Case, async: true\n\n  alias OMG.Utils.RemoteIP\n\n  describe \"call/2\" do\n    test \"sets remote_ip field\" do\n      conn = %Plug.Conn{\n        req_headers: [\n          {\"cf-connecting-ip\", \"99.99.99.99\"}\n        ]\n      }\n\n      conn_with_remote_ip = RemoteIP.call(conn, %{})\n\n      assert conn_with_remote_ip.remote_ip == {99, 99, 99, 99}\n    end\n\n    test \"does not set remote_ip if cf-connecting-ip header is not set\" do\n      conn = %Plug.Conn{}\n\n      conn_with_remote_ip = RemoteIP.call(conn, %{})\n\n      assert is_nil(conn_with_remote_ip.remote_ip)\n    end\n\n    test \"does not set remote_ip if cf-connecting-ip header is invalid\" do\n      conn = %Plug.Conn{\n        req_headers: [\n          {\"cf-connecting-ip\", \"myip\"}\n        ]\n      }\n\n      conn_with_remote_ip = RemoteIP.call(conn, %{})\n\n      assert is_nil(conn_with_remote_ip.remote_ip)\n    end\n\n    test \"sets the left-most ip address\" do\n      conn = %Plug.Conn{\n        req_headers: [\n          {\"cf-connecting-ip\", \"77.77.77.77, 99.99.99.99\"}\n        ]\n      }\n\n      conn_with_remote_ip = RemoteIP.call(conn, %{})\n\n      assert conn_with_remote_ip.remote_ip == {77, 77, 77, 77}\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_utils/test/test_helper.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nExUnit.start()\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/api/account.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.API.Account do\n  @moduledoc \"\"\"\n  Module provides operations related to plasma accounts.\n  \"\"\"\n\n  alias OMG.DB.Models.PaymentExitInfo\n  alias OMG.Watcher.Crypto\n  alias OMG.Watcher.Utxo\n  require OMG.Watcher.Utxo\n\n  @doc \"\"\"\n  Gets all utxos belonging to the given address. Slow operation.\n  \"\"\"\n  @spec get_exitable_utxos(Crypto.address_t()) :: list(OMG.Watcher.State.Core.exitable_utxos())\n  def get_exitable_utxos(address) do\n    # OMG.DB.utxos() takes a while.\n    {:ok, utxos} = OMG.DB.utxos()\n    standard_exitable_utxos = OMG.Watcher.State.Core.standard_exitable_utxos(utxos, address)\n\n    # PaymentExitInfo.all_exit_infos() takes a while.\n    {:ok, standard_exits} = PaymentExitInfo.all_exit_infos()\n    {:ok, in_flight_exits} = PaymentExitInfo.all_in_flight_exits_infos()\n\n    # See issue for more details: https://github.com/omgnetwork/private-issues/issues/41\n    active_exiting_utxos =\n      MapSet.union(\n        OMG.Watcher.ExitProcessor.Core.active_standard_exiting_utxos(standard_exits),\n        OMG.Watcher.ExitProcessor.Core.active_in_flight_exiting_inputs(in_flight_exits)\n      )\n\n    # active standard exiting utxos are excluded\n    filter_standard_exiting_utxos(standard_exitable_utxos, active_exiting_utxos)\n  end\n\n  defp filter_standard_exiting_utxos(standard_exitable_utxos, active_exiting_utxos) do\n    Enum.filter(\n      standard_exitable_utxos,\n      fn %{blknum: blknum, txindex: txindex, oindex: oindex} ->\n        not MapSet.member?(active_exiting_utxos, Utxo.position(blknum, txindex, oindex))\n      end\n    )\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/api/alarm.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.API.Alarm do\n  @moduledoc \"\"\"\n  Watcher alarm API\n  \"\"\"\n\n  alias OMG.Status.Alert.Alarm\n\n  @spec get_alarms() :: {:ok, Alarm.alarms()}\n  def get_alarms(), do: {:ok, Alarm.all()}\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/api/configuration.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.API.Configuration do\n  @moduledoc \"\"\"\n  Watcher API for retrieving configuration\n  \"\"\"\n\n  alias OMG.Watcher.Configuration\n\n  @spec get_configuration() :: {:ok, map()}\n  def get_configuration() do\n    configuration = %{\n      exit_processor_sla_margin: Configuration.exit_processor_sla_margin(),\n      deposit_finality_margin: Configuration.deposit_finality_margin(),\n      contract_semver: OMG.Eth.Configuration.contract_semver(),\n      network: OMG.Eth.Configuration.network()\n    }\n\n    {:ok, configuration}\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/api/in_flight_exit.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.API.InFlightExit do\n  @moduledoc \"\"\"\n  Module provides API for starting, validating and challenging in-flight exits\n  \"\"\"\n\n  alias OMG.Watcher.API\n  alias OMG.Watcher.ExitProcessor\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.Utxo\n\n  require Utxo\n\n  @type in_flight_exit() :: %{\n          in_flight_tx: binary(),\n          input_txs: list(binary()),\n          input_txs_inclusion_proofs: list(binary()),\n          in_flight_tx_sigs: list(binary())\n        }\n\n  @doc \"\"\"\n  Returns arguments for plasma contract function that starts in-flight exit for a given transaction.\n  \"\"\"\n  @spec get_in_flight_exit(binary) :: {:ok, in_flight_exit()} | {:error, atom}\n  def get_in_flight_exit(txbytes) do\n    with {:ok, tx} <- Transaction.Signed.decode(txbytes),\n         {:ok, {proofs, input_txs, input_utxos_pos}} <- find_input_data(tx) do\n      %Transaction.Signed{sigs: sigs} = tx\n\n      {:ok,\n       %{\n         in_flight_tx: Transaction.raw_txbytes(tx),\n         input_txs: input_txs,\n         input_utxos_pos: input_utxos_pos,\n         input_txs_inclusion_proofs: proofs,\n         in_flight_tx_sigs: sigs\n       }}\n    end\n  end\n\n  @doc \"\"\"\n  Returns arguments for plasma contract function that challenges a non-canonical IFE with a competitor for a given\n  in-flight-exiting transaction.\n\n  This delegates directly to `OMG.Watcher.ExitProcessor` see there for details\n  \"\"\"\n  def get_competitor(txbytes) do\n    ExitProcessor.get_competitor_for_ife(txbytes)\n  end\n\n  @doc \"\"\"\n  Returns arguments for plasma contract function that responds to a challeng to an IFE with an inclusion proof\n\n  This delegates directly to `OMG.Watcher.ExitProcessor` see there for details\n  \"\"\"\n  def prove_canonical(txbytes) do\n    ExitProcessor.prove_canonical_for_ife(txbytes)\n  end\n\n  @doc \"\"\"\n  Returns arguments for plasma contract function proving that input was double-signed in some other IFE.\n\n  This delegates directly to `OMG.Watcher.ExitProcessor` see there for details\n  \"\"\"\n  def get_input_challenge_data(txbytes, input_index) do\n    ExitProcessor.get_input_challenge_data(txbytes, input_index)\n  end\n\n  @doc \"\"\"\n  Returns arguments for plasma contract function proving that output was double-spent in other IFE or block.\n\n  This delegates directly to `OMG.Watcher.ExitProcessor` see there for details\n  \"\"\"\n  def get_output_challenge_data(txbytes, output_index) do\n    ExitProcessor.get_output_challenge_data(txbytes, output_index)\n  end\n\n  defp find_input_data(tx) do\n    tx\n    |> Transaction.get_inputs()\n    # reversing to preserve the order of inputs, the `reduce_while` builds 3 lists by prepending\n    |> Enum.reverse()\n    |> Enum.reduce_while({:ok, {[], [], []}}, &find_single_input_data/2)\n  end\n\n  defp find_single_input_data(input_utxo_pos, {:ok, {proofs, txbyteses, utxo_positions}}) do\n    input_utxo_pos\n    |> API.Utxo.compose_utxo_exit()\n    |> case do\n      {:ok, %{proof: proof, txbytes: txbytes}} ->\n        utxo_pos = Utxo.Position.encode(input_utxo_pos)\n        {:cont, {:ok, {[proof | proofs], [txbytes | txbyteses], [utxo_pos | utxo_positions]}}}\n\n      {:error, :utxo_not_found} ->\n        {:halt, {:error, :tx_for_input_not_found}}\n\n      {:error, :no_deposit_for_given_blknum} ->\n        {:halt, {:error, :deposit_input_spent_ife_unsupported}}\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/api/status.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.API.Status do\n  @moduledoc \"\"\"\n  Watcher status API\n  \"\"\"\n\n  alias OMG.Watcher.API.StatusCache\n\n  @doc \"\"\"\n  Returns status of the watcher from the ETS cache.\n  \"\"\"\n  @spec get_status() :: {:ok, StatusCache.status()}\n  def get_status() do\n    {:ok, StatusCache.get()}\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/api/status_cache/external.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.API.StatusCache.External do\n  @moduledoc \"\"\"\n  The integration point of the caching process\n  \"\"\"\n  alias OMG.Eth\n  alias OMG.Eth.Client\n  alias OMG.Eth.Configuration\n  alias OMG.Eth.EthereumHeight\n  alias OMG.Eth.RootChain\n  alias OMG.Utils.HttpRPC.Encoding\n  alias OMG.Watcher.BlockGetter\n  alias OMG.Watcher.Event\n  alias OMG.Watcher.ExitProcessor\n  alias OMG.Watcher.RootChainCoordinator\n  alias OMG.Watcher.State\n\n  @type t() :: %{\n          last_validated_child_block_number: non_neg_integer(),\n          last_validated_child_block_timestamp: non_neg_integer(),\n          last_mined_child_block_number: non_neg_integer(),\n          last_mined_child_block_timestamp: non_neg_integer(),\n          last_seen_eth_block_number: non_neg_integer(),\n          last_seen_eth_block_timestamp: non_neg_integer(),\n          eth_syncing: boolean(),\n          byzantine_events: list(Event.t()),\n          in_flight_exits: ExitProcessor.Core.in_flight_exits_response_t(),\n          contract_addr: binary,\n          services_synced_heights: RootChainCoordinator.Core.ethereum_heights_result_t()\n        }\n\n  def get_ethereum_height() do\n    EthereumHeight.get()\n  end\n\n  # Returns status of the watcher. Status consists of last validated child block number,\n  # last mined child block number and it's timestamp, and a flag indicating if watcher is syncing with Ethereum.\n\n  # This function calls into a number of services (internal and external), collects the results.\n  # If any of the underlying services are unavailable, it will crash\n  @spec get_status(non_neg_integer()) :: {:ok, t()}\n  def get_status(eth_block_number) do\n    {:ok, eth_block_timestamp} = Eth.get_block_timestamp_by_number(eth_block_number)\n    eth_syncing = syncing?()\n\n    validated_child_block_number = get_validated_child_block_number()\n    contracts = Configuration.contracts()\n    contract_addr = contract_map_from_hex(contracts)\n    mined_child_block_number = RootChain.get_mined_child_block()\n    {_, mined_child_block_timestamp} = RootChain.blocks(mined_child_block_number)\n    {_, validated_child_block_timestamp} = RootChain.blocks(validated_child_block_number)\n    {:ok, services_synced_heights} = RootChainCoordinator.get_ethereum_heights()\n    {_, events_processor} = ExitProcessor.check_validity()\n    {:ok, in_flight_exits} = ExitProcessor.get_active_in_flight_exits()\n    {:ok, {_, events_block_getter}} = BlockGetter.get_events()\n\n    status = %{\n      last_validated_child_block_number: validated_child_block_number,\n      last_validated_child_block_timestamp: validated_child_block_timestamp,\n      last_mined_child_block_number: mined_child_block_number,\n      last_mined_child_block_timestamp: mined_child_block_timestamp,\n      last_seen_eth_block_number: eth_block_number,\n      last_seen_eth_block_timestamp: eth_block_timestamp,\n      eth_syncing: eth_syncing,\n      byzantine_events: events_processor ++ events_block_getter,\n      in_flight_exits: in_flight_exits,\n      contract_addr: contract_addr,\n      services_synced_heights: services_synced_heights\n    }\n\n    {:ok, status}\n  end\n\n  # Checks geth syncing status, errors are treated as not synced.\n  # Returns:\n  # * false - geth is synced\n  # * true  - geth is still syncing.\n  defp syncing?() do\n    Client.node_ready() != :ok\n  end\n\n  defp get_validated_child_block_number() do\n    child_block_interval = Configuration.child_block_interval()\n    {state_current_block, _} = State.get_status()\n    state_current_block - child_block_interval\n  end\n\n  defp contract_map_from_hex(contract_map) do\n    Enum.into(contract_map, %{}, fn {name, addr} -> {name, Encoding.from_hex!(addr)} end)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/api/status_cache/storage.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.API.StatusCache.Storage do\n  @moduledoc \"\"\"\n  Watcher status API storage\n  \"\"\"\n\n  @doc \"\"\"\n  This gets periodically called (defined by Ethereum height change).\n  \"\"\"\n  def update_status(ets, key, eth_block_number, integration_module) do\n    {:ok, status} = integration_module.get_status(eth_block_number)\n    :ets.insert(ets, {key, status})\n  end\n\n  def ensure_ets_init(status_cache) do\n    case :ets.info(status_cache) do\n      :undefined ->\n        ^status_cache = :ets.new(status_cache, [:set, :public, :named_table, read_concurrency: true])\n        :ok\n\n      _ ->\n        :ok\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/api/status_cache.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.API.StatusCache do\n  @moduledoc \"\"\"\n  Watcher status API cache\n  \"\"\"\n\n  alias OMG.Watcher.API.StatusCache.External\n  alias OMG.Watcher.API.StatusCache.Storage\n  alias OMG.Watcher.SyncSupervisor\n\n  use GenServer\n  require Logger\n\n  @type status() :: External.t()\n\n  defstruct [:ets, :integration_module]\n\n  @type t :: %__MODULE__{\n          ets: atom(),\n          integration_module: module()\n        }\n  @spec get() :: status()\n  def get() do\n    :ets.lookup_element(SyncSupervisor.status_cache(), key(), 2)\n  end\n\n  def start_link(args) do\n    GenServer.start_link(__MODULE__, args, name: __MODULE__)\n  end\n\n  @spec init(Keyword.t()) :: {:ok, t()}\n  def init(opts) do\n    event_bus = Keyword.fetch!(opts, :event_bus)\n    ets = Keyword.fetch!(opts, :ets)\n    integration_module = Keyword.get(opts, :integration_module, External)\n    :ok = event_bus.subscribe({:root_chain, \"ethereum_new_height\"}, link: true)\n    {:ok, eth_block_number} = integration_module.get_ethereum_height()\n    Storage.update_status(ets, key(), eth_block_number, integration_module)\n    _ = Logger.info(\"Started #{inspect(__MODULE__)}.\")\n    {:ok, %__MODULE__{ets: ets, integration_module: integration_module}}\n  end\n\n  @doc \"\"\"\n  This gets periodically called (defined by Ethereum height change).\n  \"\"\"\n\n  def handle_info({:internal_event_bus, :ethereum_new_height, eth_block_number}, state) do\n    _ = Storage.update_status(state.ets, key(), eth_block_number, state.integration_module)\n    {:noreply, state}\n  end\n\n  def handle_info({:ssl_closed, _}, state) do\n    # eat this bug https://github.com/benoitc/hackney/issues/464\n    {:noreply, state}\n  end\n\n  defp key() do\n    :status\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/api/transaction.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.API.Transaction do\n  @moduledoc \"\"\"\n  Module provides API for transactions\n  \"\"\"\n\n  alias OMG.Watcher.HttpRPC.Client\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.Utxo\n  require Utxo\n\n  @doc \"\"\"\n  Passes the signed transaction to the child chain.\n\n  Caution: This function is unaware of the child chain's security status, e.g.:\n\n  * Watcher is fully synced,\n  * all operator blocks have been verified,\n  * transaction doesn't spend funds not yet mined\n  * etc...\n  \"\"\"\n  @spec submit(list(Transaction.Signed.t())) :: Client.response_t() | {:error, atom()}\n  def batch_submit(signed_txs) do\n    url = Application.get_env(:omg_watcher, :child_chain_url)\n    Client.batch_submit(signed_txs, url)\n  end\n\n  @spec submit(Transaction.Signed.t()) :: Client.response_t() | {:error, atom()}\n  def submit(%Transaction.Signed{} = signed_tx) do\n    url = Application.get_env(:omg_watcher, :child_chain_url)\n\n    signed_tx\n    |> Transaction.Signed.encode()\n    |> Client.submit(url)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/api/utxo.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.API.Utxo do\n  @moduledoc \"\"\"\n  Module provides API for utxos\n  \"\"\"\n\n  alias OMG.Eth.Configuration\n  alias OMG.Watcher.ExitProcessor\n  alias OMG.Watcher.Utxo\n  alias OMG.Watcher.UtxoExit.Core\n\n  require Utxo\n\n  @type exit_t() :: %{\n          utxo_pos: pos_integer(),\n          txbytes: binary(),\n          proof: binary(),\n          sigs: binary()\n        }\n\n  @interval Configuration.child_block_interval()\n\n  # Based on the contract parameters determines whether UTXO position provided was created by a deposit\n  defguardp is_deposit(blknum) when rem(blknum, @interval) != 0\n\n  @doc \"\"\"\n  Returns a proof that utxo was spent\n  \"\"\"\n  @spec create_challenge(Utxo.Position.t()) ::\n          {:ok, ExitProcessor.StandardExit.Challenge.t()} | {:error, :utxo_not_spent} | {:error, :exit_not_found}\n  def create_challenge(utxo) do\n    ExitProcessor.create_challenge(utxo)\n  end\n\n  @spec compose_utxo_exit(Utxo.Position.t()) ::\n          {:ok, exit_t()} | {:error, :utxo_not_found} | {:error, :no_deposit_for_given_blknum}\n  def compose_utxo_exit(Utxo.position(blknum, _, _) = utxo_pos) when is_deposit(blknum) do\n    utxo_pos |> Utxo.Position.to_input_db_key() |> OMG.DB.utxo() |> Core.compose_deposit_standard_exit()\n  end\n\n  def compose_utxo_exit(Utxo.position(blknum, _, _) = utxo_pos) do\n    with {:ok, [blk_hash]} <- OMG.DB.block_hashes([blknum]),\n         {:ok, [db_block]} <- OMG.DB.blocks([blk_hash]) do\n      Core.compose_block_standard_exit(db_block, utxo_pos)\n    else\n      _error -> {:error, :utxo_not_found}\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/application.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.Application do\n  # See https://hexdocs.pm/elixir/Application.html\n  # for more information on OTP Applications\n  @moduledoc false\n\n  use Application\n\n  require Logger\n\n  def start(_type, _args) do\n    _ = Logger.info(\"Starting #{inspect(__MODULE__)}\")\n    start_root_supervisor()\n  end\n\n  def start_root_supervisor() do\n    # root supervisor must stop whenever any of its children supervisors goes down (children carry the load of restarts)\n    children = [\n      %{\n        id: OMG.Watcher.Supervisor,\n        start: {OMG.Watcher.Supervisor, :start_link, []},\n        restart: :permanent,\n        type: :supervisor\n      }\n    ]\n\n    opts = [\n      strategy: :one_for_one,\n      # whenever any of supervisor's children goes down, so it does\n      max_restarts: 0,\n      name: OMG.Watcher.RootSupervisor\n    ]\n\n    Supervisor.start_link(children, opts)\n  end\n\n  def start_phase(:attach_telemetry, :normal, _phase_args) do\n    handlers = [\n      [\"measure-state\", OMG.Watcher.State.Measure.supported_events(), &OMG.Watcher.State.Measure.handle_event/4, nil],\n      [\n        \"measure-blockgetter\",\n        OMG.Watcher.BlockGetter.Measure.supported_events(),\n        &OMG.Watcher.BlockGetter.Measure.handle_event/4,\n        nil\n      ],\n      [\n        \"measure-ethereum-event-listener\",\n        OMG.Watcher.EthereumEventListener.Measure.supported_events(),\n        &OMG.Watcher.EthereumEventListener.Measure.handle_event/4,\n        nil\n      ]\n    ]\n\n    Enum.each(handlers, fn handler ->\n      case apply(:telemetry, :attach_many, handler) do\n        :ok -> :ok\n        {:error, :already_exists} -> :ok\n      end\n    end)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/block.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.Block do\n  @moduledoc \"\"\"\n  Representation of an OMG network child chain block.\n  \"\"\"\n\n  alias OMG.Watcher.Merkle\n  alias OMG.Watcher.State.Transaction\n\n  @type block_hash_t() :: <<_::256>>\n\n  defstruct [:transactions, :hash, :number]\n\n  @type t() :: %__MODULE__{\n          transactions: list(Transaction.Signed.tx_bytes()),\n          hash: block_hash_t(),\n          number: pos_integer()\n        }\n\n  @type db_t() :: %{\n          transactions: list(binary),\n          hash: block_hash_t(),\n          number: pos_integer()\n        }\n\n  @doc \"\"\"\n  Returns a Block from enumberable of transactions, at a certain child block number, along with a calculated merkle\n  root hash\n  \"\"\"\n  @spec hashed_txs_at(list(Transaction.Recovered.t()), non_neg_integer()) :: t()\n  def hashed_txs_at(txs, blknum) do\n    signed_txs_bytes = Enum.map(txs, & &1.signed_tx_bytes)\n    txs_bytes = Enum.map(txs, &Transaction.raw_txbytes/1)\n\n    %__MODULE__{hash: Merkle.hash(txs_bytes), transactions: signed_txs_bytes, number: blknum}\n  end\n\n  @doc \"\"\"\n  Coerces the block struct to a format more in-line with the external API format\n  \"\"\"\n  def to_api_format(%__MODULE__{number: blknum} = struct_block) do\n    struct_block\n    |> Map.from_struct()\n    |> Map.delete(:number)\n    |> Map.put(:blknum, blknum)\n  end\n\n  # NOTE: we have no migrations, so we handle data compatibility here (make_db_update/1 and from_db_kv/1), OMG-421\n  def to_db_value(%__MODULE__{transactions: transactions, hash: hash, number: number})\n      when is_list(transactions) and is_binary(hash) and is_integer(number) do\n    %{transactions: transactions, hash: hash, number: number}\n  end\n\n  def from_db_value(%{transactions: transactions, hash: hash, number: number})\n      when is_list(transactions) and is_binary(hash) and is_integer(number) do\n    value = %{transactions: transactions, hash: hash, number: number}\n    struct!(__MODULE__, value)\n  end\n\n  @doc \"\"\"\n  Calculates inclusion proof for the transaction in the block\n  \"\"\"\n  @spec inclusion_proof(t() | list(Transaction.Signed.tx_bytes()), non_neg_integer()) :: binary()\n  def inclusion_proof(transactions, txindex) when is_list(transactions) do\n    txs_bytes =\n      transactions\n      |> Enum.map(&Transaction.Signed.decode!/1)\n      |> Enum.map(&Transaction.raw_txbytes/1)\n\n    Merkle.create_tx_proof(txs_bytes, txindex)\n  end\n\n  def inclusion_proof(%__MODULE__{transactions: transactions}, txindex), do: inclusion_proof(transactions, txindex)\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/block_getter/block_application.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.BlockGetter.BlockApplication do\n  @moduledoc \"\"\"\n  Contains all the information that `apply_block` and `handle_cast(:apply_block)` would need to apply a statelessly\n  valid, downloaded block\n  \"\"\"\n\n  alias OMG.Watcher.BlockGetter.BlockApplication\n\n  @type t :: %__MODULE__{\n          number: pos_integer(),\n          eth_height: non_neg_integer(),\n          eth_height_done: boolean(),\n          hash: binary(),\n          timestamp: pos_integer(),\n          transactions: list()\n        }\n\n  defstruct [\n    :number,\n    :eth_height,\n    :eth_height_done,\n    :hash,\n    :timestamp,\n    transactions: []\n  ]\n\n  def new(block, recovered_txs, block_timestamp) do\n    struct!(\n      BlockApplication,\n      block\n      |> Map.from_struct()\n      |> Map.put(:transactions, recovered_txs)\n      |> Map.put(:timestamp, block_timestamp)\n    )\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/block_getter/core.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.BlockGetter.Core do\n  @moduledoc \"\"\"\n  Logic module for the `OMG.Watcher.BlockGetter`.\n\n  Responsible for:\n    - figuring out the range of child chain blocks needed to be downloaded\n    - tracking the block downloading process and signaling withholding if need be\n    - doing the stateless validation of blocks (and transactions within those blocks)\n    - tracking the progress of stateful validation of blocks\n    - matching up `BlockSubmitted` root chain events with the downloaded blocks, to discover submission `eth_height`.\n  \"\"\"\n\n  alias OMG.Watcher.Block\n  alias OMG.Watcher.BlockGetter.BlockApplication\n  alias OMG.Watcher.Event\n  alias OMG.Watcher.ExitProcessor\n  alias OMG.Watcher.State.Transaction\n\n  require Logger\n\n  defmodule Config do\n    @moduledoc false\n    defstruct [\n      :block_interval,\n      :block_getter_reorg_margin,\n      :block_getter_loops_interval_ms,\n      :child_chain_url,\n      maximum_number_of_pending_blocks: 10,\n      maximum_block_withholding_time_ms: 0,\n      maximum_number_of_unapplied_blocks: 50\n    ]\n\n    @type t :: %__MODULE__{\n            maximum_number_of_pending_blocks: pos_integer,\n            maximum_block_withholding_time_ms: pos_integer,\n            maximum_number_of_unapplied_blocks: pos_integer,\n            block_interval: pos_integer,\n            block_getter_loops_interval_ms: pos_integer,\n            child_chain_url: String.t()\n          }\n  end\n\n  defmodule PotentialWithholdingReport do\n    @moduledoc \"\"\"\n    Represents a downloading error interpreted as a potential block withholding event.\n    \"\"\"\n\n    defstruct [:blknum, :hash, :time]\n\n    @type t :: %__MODULE__{\n            blknum: pos_integer,\n            hash: binary,\n            time: pos_integer\n          }\n  end\n\n  defmodule PotentialWithholding do\n    @moduledoc \"\"\"\n    Used to track a recognized potential withholding and track work towards resolving it.\n    \"\"\"\n    defstruct time: nil, downloading: false\n\n    @type t :: %__MODULE__{\n            time: pos_integer,\n            downloading: boolean\n          }\n  end\n\n  defstruct [\n    :synced_height,\n    :last_applied_block,\n    :num_of_highest_block_being_downloaded,\n    :number_of_blocks_being_downloaded,\n    :unapplied_blocks,\n    :potential_block_withholdings,\n    :config,\n    :events,\n    :chain_status\n  ]\n\n  @type t() :: %__MODULE__{\n          synced_height: pos_integer(),\n          last_applied_block: non_neg_integer,\n          num_of_highest_block_being_downloaded: non_neg_integer,\n          number_of_blocks_being_downloaded: non_neg_integer,\n          unapplied_blocks: %{non_neg_integer => BlockApplication.t()},\n          potential_block_withholdings: %{\n            non_neg_integer => PotentialWithholding.t()\n          },\n          config: Config.t(),\n          events: block_getter_events_t(),\n          chain_status: chain_status_t()\n        }\n\n  @type block_getter_event_t() :: Event.InvalidBlock.t() | Event.BlockWithholding.t()\n  @type block_getter_events_t() :: list(block_getter_event_t())\n  @type chain_status_t() :: :ok | :error\n\n  @type chain_ok_response_t() :: {chain_status_t(), block_getter_events_t()}\n\n  @type block_error() ::\n          :incorrect_hash\n          | :bad_returned_hash\n          | :withholding\n          | Transaction.Recovered.recover_tx_error()\n\n  @type init_error() :: :not_at_block_beginning\n\n  @type validate_download_response_result_t() ::\n          {:ok, BlockApplication.t() | PotentialWithholdingReport.t()}\n          | {:error, {block_error(), binary(), pos_integer()}}\n\n  @doc \"\"\"\n  Initializes a fresh instance of BlockGetter's state, having `block_number` as last consumed child block,\n  using `child_block_interval` when progressing from one child block to another,\n  `synced_height` as the root chain height up to witch all published blocked were processed\n  and `block_getter_reorg_margin` as number of root chain blocks that may change during an reorg.\n\n  Opts can be:\n    - `:maximum_number_of_pending_blocks` - how many block should be pulled from the child chain at once (10)\n    - `:maximum_block_withholding_time_ms` - how much time should we wait after the first failed pull until we call it\n      a block withholding byzantine condition of the child chain (0 ms).\n  \"\"\"\n  @spec init(\n          non_neg_integer,\n          pos_integer,\n          non_neg_integer,\n          non_neg_integer,\n          boolean,\n          ExitProcessor.Core.check_validity_result_t(),\n          Keyword.t()\n        ) :: {:ok, %__MODULE__{}} | {:error, init_error()}\n  def init(\n        block_number,\n        child_block_interval,\n        synced_height,\n        block_getter_reorg_margin,\n        state_at_block_beginning,\n        exit_processor_results,\n        opts \\\\ []\n      ) do\n    with true <- state_at_block_beginning || {:error, :not_at_block_beginning},\n         true <- init_opts_valid?(opts) do\n      state = %__MODULE__{\n        synced_height: synced_height,\n        last_applied_block: block_number,\n        num_of_highest_block_being_downloaded: block_number,\n        number_of_blocks_being_downloaded: 0,\n        unapplied_blocks: %{},\n        potential_block_withholdings: %{},\n        config:\n          struct(\n            Config,\n            Keyword.merge(opts,\n              block_interval: child_block_interval,\n              block_getter_reorg_margin: block_getter_reorg_margin\n            )\n          ),\n        events: [],\n        chain_status: :ok\n      }\n\n      state = consider_exits(state, exit_processor_results)\n\n      {:ok, state}\n    end\n  end\n\n  @doc \"\"\"\n    Returns:\n      1. `chain_status` which is based on BlockGetter events and ExitProcessor events\n      2. BlockGetter events.\n  \"\"\"\n  @spec chain_ok(t()) :: chain_ok_response_t()\n  def chain_ok(%__MODULE__{chain_status: chain_status, events: events}), do: {chain_status, events}\n\n  @doc \"\"\"\n  Marks that child chain block published on `eth_height` was processed\n  \"\"\"\n  @spec apply_block(t(), BlockApplication.t()) :: {t(), non_neg_integer(), list()}\n  def apply_block(%__MODULE__{} = state, %BlockApplication{\n        number: blknum,\n        eth_height: eth_height,\n        eth_height_done: eth_height_done\n      }) do\n    _ = Logger.debug(\"\\##{inspect(blknum)}, from: #{inspect(eth_height)}, eth height done: #{inspect(eth_height_done)}\")\n\n    if eth_height_done do\n      # final - we need to mark this eth height as processed\n      state = %{state | synced_height: eth_height}\n      {state, eth_height, [{:put, :last_block_getter_eth_height, eth_height}]}\n    else\n      # not final - this applied child block doesn't wrap up any eth height\n      {state, state.synced_height, []}\n    end\n  end\n\n  @doc \"\"\"\n  Produces root chain block height range to search for events of block submission.\n  If the range is not empty it spans from current synced root chain height to `coordinator_height`.\n  Empty range case is solved naturally with {a, b}, a > b.\n  \"\"\"\n  @spec get_eth_range_for_block_submitted_events(t(), non_neg_integer()) :: {pos_integer(), pos_integer()}\n  def get_eth_range_for_block_submitted_events(\n        %__MODULE__{synced_height: synced_height, config: config},\n        coordinator_height\n      ) do\n    {max(0, synced_height - config.block_getter_reorg_margin), coordinator_height}\n  end\n\n  @doc \"\"\"\n  Returns blocks that can be pushed to state or updates the `synced_height` if no new blocks` submissions are found in\n  a range.\n\n  That is the longest continuous range of blocks downloaded from child chain, contained in `block_submitted_events`,\n  published on ethereum height not exceeding `coordinator_height` and not pushed to state before.\n  \"\"\"\n  @spec get_blocks_to_apply(t(), list(), non_neg_integer()) ::\n          {list(BlockApplication.t()), non_neg_integer(), list(), t()}\n  def get_blocks_to_apply(\n        %__MODULE__{last_applied_block: last_applied} = state,\n        block_submitted_events,\n        coordinator_height\n      ) do\n    # this ensures that we don't take submissions of already applied blocks into account **at all**\n    filtered_submissions = block_submitted_events |> Enum.filter(fn %{blknum: blknum} -> blknum > last_applied end)\n\n    do_get_blocks_to_apply(state, filtered_submissions, coordinator_height)\n  end\n\n  @doc \"\"\"\n   Returns additional blocks number on which the Core will be waiting.\n   The number of expected block is limited by maximum_number_of_pending_blocks.\n  \"\"\"\n  @spec get_numbers_of_blocks_to_download(%__MODULE__{}, non_neg_integer) :: {%__MODULE__{}, list(non_neg_integer)}\n  def get_numbers_of_blocks_to_download(\n        %__MODULE__{\n          unapplied_blocks: unapplied_blocks,\n          num_of_highest_block_being_downloaded: num_of_highest_block_being_downloaded,\n          number_of_blocks_being_downloaded: number_of_blocks_being_downloaded,\n          potential_block_withholdings: potential_block_withholdings,\n          config: config,\n          chain_status: :ok\n        } = state,\n        next_child\n      ) do\n    first_block_number = num_of_highest_block_being_downloaded + config.block_interval\n\n    number_of_empty_slots = config.maximum_number_of_pending_blocks - number_of_blocks_being_downloaded\n\n    potential_block_withholding_numbers =\n      potential_block_withholdings\n      |> Enum.filter(fn {_, %PotentialWithholding{downloading: downloading}} -> !downloading end)\n      |> Enum.map(fn {key, _} -> key end)\n\n    potential_next_block_numbers =\n      first_block_number\n      |> Stream.iterate(&(&1 + config.block_interval))\n      |> Stream.take_while(&(&1 < next_child))\n      |> Enum.to_list()\n\n    number_of_blocks_to_download =\n      min(\n        number_of_empty_slots,\n        max(\n          0,\n          config.maximum_number_of_unapplied_blocks - number_of_blocks_being_downloaded -\n            Kernel.map_size(unapplied_blocks)\n        )\n      )\n\n    blocks_numbers =\n      (potential_block_withholding_numbers ++ potential_next_block_numbers)\n      |> Enum.take(number_of_blocks_to_download)\n\n    [num_of_highest_block_being_downloaded | _] =\n      ([num_of_highest_block_being_downloaded] ++ blocks_numbers) |> Enum.sort(&(&1 > &2))\n\n    _ = log_downloading_blocks(next_child, blocks_numbers)\n\n    update_for_witholding =\n      potential_block_withholdings\n      |> Map.take(blocks_numbers)\n      |> Enum.map(fn {key, value} -> {key, Map.put(value, :downloading, true)} end)\n      |> Map.new()\n\n    {%{\n       state\n       | number_of_blocks_being_downloaded: length(blocks_numbers) + number_of_blocks_being_downloaded,\n         num_of_highest_block_being_downloaded: num_of_highest_block_being_downloaded,\n         potential_block_withholdings: Map.merge(potential_block_withholdings, update_for_witholding)\n     }, blocks_numbers}\n  end\n\n  def get_numbers_of_blocks_to_download(state, _next_child) do\n    {state, []}\n  end\n\n  @doc \"\"\"\n  Statelessly decodes and validates a downloaded block, does all the checks before handing off to State.exec-checking.\n  Requested_hash is given to compare to always have a consistent data structure coming out.\n  Requested_number is given to _override_ since we're getting by hash, we can have empty blocks with same hashes!\n  \"\"\"\n  @spec validate_download_response(\n          {:ok, map()} | {:error, block_error()},\n          binary(),\n          pos_integer(),\n          pos_integer(),\n          pos_integer()\n        ) :: validate_download_response_result_t()\n  def validate_download_response(\n        {:ok, %{hash: returned_hash, transactions: transactions, number: number}},\n        requested_hash,\n        requested_number,\n        block_timestamp,\n        _time\n      ) do\n    with true <- returned_hash == requested_hash || {:error, :bad_returned_hash},\n         true <- number == requested_number || {:error, :bad_returned_number},\n         {:ok, recovered_txs} <- recover_all_txs(transactions),\n         # hash the block yourself and compare\n         %Block{hash: calculated_hash} = block = Block.hashed_txs_at(recovered_txs, number),\n         true <- calculated_hash == requested_hash || {:error, :incorrect_hash} do\n      {:ok, BlockApplication.new(block, recovered_txs, block_timestamp)}\n    else\n      {:error, reason} -> {:error, {reason, requested_hash, requested_number}}\n    end\n  end\n\n  def validate_download_response({:error, _} = error, requested_hash, requested_number, _block_timestamp, time) do\n    _ = Logger.info(\"Potential block withholding #{inspect(error)}, number: \\##{inspect(requested_number)}\")\n    {:ok, %PotentialWithholdingReport{blknum: requested_number, hash: requested_hash, time: time}}\n  end\n\n  @doc \"\"\"\n  First scenario:\n    Add block to \\\"block to consume\\\" tick off the block from pending blocks.\n    Returns the consumable, contiguous list of ordered blocks\n  Second scenario:\n    In case of invalid block detection\n    Returns InvalidBlock event.\n  Third scenario:\n    In case of potential withholding block detection\n    Returns same state, state with new  potential_block_withholding or BlockWithHolding event\n  \"\"\"\n  @spec handle_downloaded_block(\n          %__MODULE__{},\n          {:ok, BlockApplication.t() | PotentialWithholdingReport.t()}\n          | {:error, {block_error(), binary(), pos_integer()}}\n        ) ::\n          {:ok | {:error, block_error()}, %__MODULE__{}}\n          | {:error, :duplicate | :unexpected_block}\n  def handle_downloaded_block(\n        %__MODULE__{\n          number_of_blocks_being_downloaded: number_of_blocks_being_downloaded,\n          potential_block_withholdings: potential_block_withholdings\n        } = state,\n        response\n      ) do\n    blknum = get_blknum(response)\n\n    # if there was a potential withholding registered - mark it as non-downloading. Otherwise noop\n    potential_block_withholdings =\n      case potential_block_withholdings[blknum] do\n        nil ->\n          potential_block_withholdings\n\n        potential_block_withholding ->\n          Map.put(potential_block_withholdings, blknum, %PotentialWithholding{\n            potential_block_withholding\n            | downloading: false\n          })\n      end\n\n    state = %{\n      state\n      | number_of_blocks_being_downloaded: number_of_blocks_being_downloaded - 1,\n        potential_block_withholdings: potential_block_withholdings\n    }\n\n    validate_downloaded_block(state, response)\n  end\n\n  @spec validate_executions(\n          list({Transaction.tx_hash(), pos_integer, pos_integer}),\n          map,\n          t()\n        ) :: {:ok, t()} | {{:error, {:tx_execution, any()}}, t()}\n  def validate_executions(tx_execution_results, %{hash: hash, number: blknum}, state) do\n    case all_tx_executions_ok?(tx_execution_results) do\n      true ->\n        {:ok, state}\n\n      {:error, reason} ->\n        event = %Event.InvalidBlock{error_type: :tx_execution, hash: hash, blknum: blknum}\n        state = state |> set_chain_status(:error) |> add_distinct_event(event)\n        {{:error, {:tx_execution, reason}}, state}\n    end\n  end\n\n  @doc \"\"\"\n  Takes results from `ExitProcessor.check_validity` into account, to potentially stop getting blocks\n  \"\"\"\n  @spec consider_exits(t(), ExitProcessor.Core.check_validity_result_t()) :: t()\n  def consider_exits(%__MODULE__{} = state, {:ok, _}), do: state\n\n  def consider_exits(%__MODULE__{} = state, {{:error, :unchallenged_exit} = error, _}) do\n    _ = Logger.warn(\"Chain invalid when taking exits into account, because of #{inspect(error)}\")\n    set_chain_status(state, :error)\n  end\n\n  #\n  # Private functions\n  #\n\n  defp init_opts_valid?(opts) do\n    maximum_number_of_pending_blocks = Keyword.get(opts, :maximum_number_of_pending_blocks, 1)\n    maximum_number_of_pending_blocks >= 1 || {:error, :maximum_number_of_pending_blocks_too_low}\n  end\n\n  # height served as syncable from the `OMG.Watcher.RootChainCoordinator` is older, nothing we can do about it, so noop\n  defp do_get_blocks_to_apply(\n         %__MODULE__{synced_height: synced_height} = state,\n         _block_submitted_events,\n         coordinator_height\n       )\n       when coordinator_height <= synced_height do\n    {[], synced_height, [], state}\n  end\n\n  # there are no **non-applied** submissions in the prescribed range of eth-blocks, so let's as much as we can\n  defp do_get_blocks_to_apply(%__MODULE__{} = state, [], coordinator_height) do\n    db_updates = [{:put, :last_block_getter_eth_height, coordinator_height}]\n    {[], coordinator_height, db_updates, %{state | synced_height: coordinator_height}}\n  end\n\n  # there are blocks to apply, so let's schedule that. This clause defers advancing the synced_height until apply_block\n  defp do_get_blocks_to_apply(\n         %__MODULE__{unapplied_blocks: blocks, config: config} = state,\n         block_submissions,\n         _coordinator_height\n       ) do\n    eth_height_done_by_blknum = final_blknums(block_submissions)\n\n    block_submissions =\n      Enum.into(block_submissions, %{}, fn %{blknum: blknum, eth_height: eth_height} -> {blknum, eth_height} end)\n\n    first_blknum_to_apply = state.last_applied_block + config.block_interval\n\n    blknums_to_apply =\n      first_blknum_to_apply\n      |> Stream.iterate(&(&1 + config.block_interval))\n      |> Enum.take_while(fn blknum -> Map.has_key?(block_submissions, blknum) and Map.has_key?(blocks, blknum) end)\n\n    blocks_to_keep = Map.drop(blocks, blknums_to_apply)\n    last_applied_block = List.last([state.last_applied_block] ++ blknums_to_apply)\n\n    blocks_to_apply =\n      blknums_to_apply\n      |> Enum.map(fn blknum ->\n        Map.get(blocks, blknum)\n        |> Map.put(:eth_height, Map.get(block_submissions, blknum))\n        |> Map.put(:eth_height_done, Map.has_key?(eth_height_done_by_blknum, blknum))\n        |> struct!()\n      end)\n\n    {blocks_to_apply, state.synced_height, [],\n     %{\n       state\n       | unapplied_blocks: blocks_to_keep,\n         last_applied_block: last_applied_block\n     }}\n  end\n\n  # goes through new submissions and figures out a mapping from blknum to eth_height, where blknum\n  # is the **last** child block number submitted at the root chain height it maps to\n  # this is later used to sign eth heights off as synced (`apply_block`)\n  defp final_blknums(new_submissions) do\n    new_submissions\n    |> Enum.group_by(fn %{eth_height: eth_height} -> eth_height end, fn %{blknum: blknum} -> blknum end)\n    |> Enum.into(%{}, fn {eth_height, blknums} ->\n      last_blknum = Enum.max(blknums)\n      {last_blknum, eth_height}\n    end)\n  end\n\n  defp log_downloading_blocks(_next_child, []), do: :ok\n\n  defp log_downloading_blocks(next_child, blocks_numbers) do\n    Logger.info(\"Child chain seen at block \\##{inspect(next_child)}. Downloading blocks #{inspect(blocks_numbers)}\")\n  end\n\n  defp get_blknum({:ok, %{number: number}}), do: number\n  defp get_blknum({:ok, %PotentialWithholdingReport{blknum: blknum}}), do: blknum\n  defp get_blknum({:error, {_error_type, _hash, number}}), do: number\n\n  defp validate_downloaded_block(\n         %__MODULE__{\n           unapplied_blocks: unapplied_blocks,\n           potential_block_withholdings: potential_block_withholdings\n         } = state,\n         {:ok, %BlockApplication{number: number} = to_apply}\n       ) do\n    with true <- not_queued_up_yet?(number, unapplied_blocks) || {{:error, :duplicate}, state},\n         true <- expected_to_queue_up?(number, state) || {{:error, :unexpected_block}, state} do\n      state = %{\n        state\n        | unapplied_blocks: Map.put(unapplied_blocks, number, to_apply),\n          potential_block_withholdings: Map.delete(potential_block_withholdings, number)\n      }\n\n      {:ok, state}\n    end\n  end\n\n  defp validate_downloaded_block(\n         %__MODULE__{} = state,\n         {:error, {error_type, hash, blknum}}\n       ) do\n    event = %Event.InvalidBlock{error_type: error_type, hash: hash, blknum: blknum}\n    state = state |> set_chain_status(:error) |> add_distinct_event(event)\n    {{:error, error_type}, state}\n  end\n\n  defp validate_downloaded_block(\n         %__MODULE__{\n           potential_block_withholdings: potential_block_withholdings,\n           config: config\n         } = state,\n         {:ok, %PotentialWithholdingReport{blknum: blknum, hash: hash, time: time}}\n       ) do\n    %{time: blknum_time} = Map.get(potential_block_withholdings, blknum, %PotentialWithholding{})\n\n    cond do\n      blknum_time == nil ->\n        potential_block_withholdings = Map.put(potential_block_withholdings, blknum, %PotentialWithholding{time: time})\n        state = %{state | potential_block_withholdings: potential_block_withholdings}\n        {:ok, state}\n\n      time - blknum_time >= config.maximum_block_withholding_time_ms ->\n        event = %Event.BlockWithholding{blknum: blknum, hash: hash}\n        state = state |> set_chain_status(:error) |> add_distinct_event(event)\n        {{:error, :withholding}, state}\n\n      true ->\n        {:ok, state}\n    end\n  end\n\n  defp not_queued_up_yet?(number, unapplied_blocks), do: not Map.has_key?(unapplied_blocks, number)\n\n  defp expected_to_queue_up?(number, %{num_of_highest_block_being_downloaded: highest, last_applied_block: last}),\n    do: last < number and number <= highest\n\n  defp recover_all_txs(transactions) do\n    transactions\n    |> Enum.reverse()\n    |> Enum.reduce_while({:ok, []}, fn tx, {:ok, recovered_so_far} ->\n      case Transaction.Recovered.recover_from(tx) do\n        {:ok, recovered} -> {:cont, {:ok, [recovered | recovered_so_far]}}\n        other -> {:halt, other}\n      end\n    end)\n  end\n\n  defp all_tx_executions_ok?(tx_execution_results) do\n    Enum.find(tx_execution_results, &(!match?({:ok, {_, _, _}}, &1)))\n    |> case do\n      nil -> true\n      other -> other\n    end\n  end\n\n  defp add_distinct_event(%__MODULE__{events: events} = state, event) do\n    if Enum.member?(events, event),\n      do: state,\n      else: %{state | events: [event | events]}\n  end\n\n  defp set_chain_status(state, status), do: %{state | chain_status: status}\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/block_getter/measure.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.BlockGetter.Measure do\n  @moduledoc \"\"\"\n  Counting business metrics sent to Datadog\n  \"\"\"\n\n  import OMG.Status.Metric.Event, only: [name: 1]\n\n  alias OMG.Status.Metric.Datadog\n  alias OMG.Watcher.BlockGetter\n\n  @supported_events [[:process, BlockGetter]]\n  def supported_events(), do: @supported_events\n\n  def handle_event([:process, BlockGetter], _, _state, _config) do\n    value =\n      self()\n      |> Process.info(:message_queue_len)\n      |> elem(1)\n\n    _ = Datadog.gauge(name(:block_getter_message_queue_len), value)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/block_getter/status.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.BlockGetter.Status do\n  @moduledoc \"\"\"\n  Keeps track and exposes the current status of `OMG.Watcher.BlockGetter`.\n\n  The reason to have this is that the parent `OMG.Watcher.BlockGetter` is doing a lot of synchronous heavy lifting and\n  can easily become unresponsive when syncing (especially when catching up a lot of blocks).\n\n  Expects current status to be eagerly pushed to it.\n  \"\"\"\n\n  alias OMG.Watcher.BlockGetter.Core\n\n  def start_link() do\n    Agent.start_link(fn -> nil end, name: __MODULE__)\n  end\n\n  @doc \"\"\"\n  Overwrites the currently stored status with the provided one\n  \"\"\"\n  @spec update(Core.chain_ok_response_t()) :: :ok\n  def update(status), do: Agent.update(__MODULE__, fn _old_status -> status end)\n\n  @doc \"\"\"\n  Retrieves the freshest information about `OMG.Watcher.BlockGetter`'s status.\n  Prefer `OMG.Watcher.BlockGetter.get_events/0` to this\n  \"\"\"\n  @spec get_events() :: {:ok, Core.chain_ok_response_t()}\n  def get_events(), do: Agent.get(__MODULE__, fn status -> {:ok, status} end)\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/block_getter/supervisor.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.BlockGetter.Supervisor do\n  @moduledoc \"\"\"\n  This supervisor takes care of BlockGetter and State processes.\n  In case one process fails, this supervisor's role is to restore consistent state\n  \"\"\"\n  use Supervisor\n  require Logger\n\n  alias OMG.Watcher.BlockGetter\n  alias OMG.Watcher.Configuration\n\n  def start_link(args) do\n    Supervisor.start_link(__MODULE__, args, name: __MODULE__)\n  end\n\n  def init(args) do\n    contract_deployment_height = Keyword.fetch!(args, :contract_deployment_height)\n    block_getter_reorg_margin = Configuration.block_getter_reorg_margin()\n    maximum_block_withholding_time_ms = Configuration.maximum_block_withholding_time_ms()\n    maximum_number_of_unapplied_blocks = Configuration.maximum_number_of_unapplied_blocks()\n    metrics_collection_interval = Configuration.metrics_collection_interval()\n    child_chain_url = Configuration.child_chain_url()\n    child_block_interval = OMG.Eth.Configuration.child_block_interval()\n    contracts = OMG.Eth.Configuration.contracts()\n    block_getter_loops_interval_ms = Configuration.ethereum_events_check_interval_ms()\n\n    fee_claimer_address = Base.decode16!(\"DEAD000000000000000000000000000000000000\")\n\n    # State and Block Getter are linked, because they must restore their state to the last stored state\n    # If Block Getter fails, it starts from the last checkpoint while State might have had executed some transactions\n    # such a situation will cause error when trying to execute already executed transaction\n\n    children = [\n      # NOTE: Watcher doesn't need the actual fee claimer address\n      {OMG.Watcher.State,\n       [\n         fee_claimer_address: fee_claimer_address,\n         child_block_interval: child_block_interval,\n         metrics_collection_interval: metrics_collection_interval\n       ]},\n      %{\n        id: BlockGetter,\n        start:\n          {BlockGetter, :start_link,\n           [\n             [\n               child_block_interval: child_block_interval,\n               block_getter_reorg_margin: block_getter_reorg_margin,\n               maximum_block_withholding_time_ms: maximum_block_withholding_time_ms,\n               maximum_number_of_unapplied_blocks: maximum_number_of_unapplied_blocks,\n               metrics_collection_interval: metrics_collection_interval,\n               block_getter_loops_interval_ms: block_getter_loops_interval_ms,\n               child_chain_url: child_chain_url,\n               contract_deployment_height: contract_deployment_height,\n               contracts: contracts\n             ]\n           ]},\n        restart: :transient\n      }\n    ]\n\n    opts = [strategy: :one_for_all]\n\n    _ = Logger.info(\"Starting #{inspect(__MODULE__)}\")\n    Supervisor.init(children, opts)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/block_getter.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.BlockGetter do\n  @moduledoc \"\"\"\n  Downloads blocks from child chain, validates them and updates watcher state.\n  Manages concurrent downloading and stateless-validation of blocks.\n  Detects byzantine behaviors like invalid blocks and block withholding and exposes those events.\n\n  Responsible for processing all block submissions and processing them once, regardless of the reorg situation.\n  Note that `BlockGetter` shouldn't have any finality margin configured, i.e. it should be prepared to be served events\n  from zero-confirmation Ethereum blocks from the `OMG.Watcher.RootChainCoordinator`.\n\n  The flow of getting blocks is as follows:\n    - `BlockGetter` tracks the top child block number mined in the root chain contract (by doing `eth_call` on the\n      ethereum node)\n    - if this is newer than local state, it gets the hash of the block from the contract (another `eth_call`)\n    - with the hash it calls `block.get` on the child chain server\n      - if this succeeds it continues to statelessly validate the block (recover transactions, calculate Merkle root)\n      - if this fails (e.g. timeout) it goes into a `PotentialWithholding` state and tries to see if the problem\n        resolves. If not it ends up reporting a `block_withholding` byzantine event\n    - it holds such downloaded block until `OMG.Watcher.RootChainCoordinator` allows the blocks submitted at given Ethereum\n      heights to be applied\n    - Applies the block by statefully validating and executing the txs on `OMG.Watcher.State`\n    - after the block is fully validated it gathers all the updates to `OMG.DB` and executes them. This includes marking\n      a respective Ethereum height (that contained the `BlockSubmitted` event) as processed\n    - checks in to `OMG.Watcher.RootChainCoordinator` to let other services know about progress\n\n  The process of downloading and stateless validation of blocks is done in `Task`s for concurrency.\n\n  See `OMG.Watcher.BlockGetter.Core` for the implementation of the business logic for the getter.\n  \"\"\"\n  use GenServer\n  require Logger\n  use Spandex.Decorators\n\n  alias OMG.Eth.RootChain\n  alias OMG.Watcher.BlockGetter.BlockApplication\n  alias OMG.Watcher.BlockGetter.Core\n  alias OMG.Watcher.BlockGetter.Status\n  alias OMG.Watcher.ExitProcessor\n  alias OMG.Watcher.HttpRPC.Client\n  alias OMG.Watcher.RootChainCoordinator\n  alias OMG.Watcher.RootChainCoordinator.SyncGuide\n  alias OMG.Watcher.State\n\n  @doc \"\"\"\n  Retrieves the freshest information about `OMG.Watcher.BlockGetter`'s status, as stored by the slave process `Status`.\n  \"\"\"\n  @spec get_events() :: {:ok, Core.chain_ok_response_t()}\n  def get_events(), do: __MODULE__.Status.get_events()\n\n  def start_link(args) do\n    GenServer.start_link(__MODULE__, args, name: __MODULE__)\n  end\n\n  @doc \"\"\"\n  Reads the status of block getting and application from `OMG.DB`, reads the current state of the contract and root\n  chain and starts the pollers that will take care of getting blocks.\n  \"\"\"\n  def init(args) do\n    child_block_interval = Keyword.fetch!(args, :child_block_interval)\n    # how many eth blocks backward can change during an reorg\n    block_getter_reorg_margin = Keyword.fetch!(args, :block_getter_reorg_margin)\n    maximum_block_withholding_time_ms = Keyword.fetch!(args, :maximum_block_withholding_time_ms)\n    maximum_number_of_unapplied_blocks = Keyword.fetch!(args, :maximum_number_of_unapplied_blocks)\n    block_getter_loops_interval_ms = Keyword.fetch!(args, :block_getter_loops_interval_ms)\n    child_chain_url = Keyword.fetch!(args, :child_chain_url)\n    contract_deployment_height = Keyword.fetch!(args, :contract_deployment_height)\n    # TODO rethink posible solutions see issue #724\n    # if we do not wait here, `ExitProcessor.check_validity()` may timeouts,\n    # which causes State and BlockGetter to reboot, fetches entire UTXO set again, and then timeout...\n    exit_processor_initial_results = ExitProcessor.check_validity(10 * 60_000)\n    # State treats current as the next block to be executed or a block that is being executed\n    # while top block number is a block that has been formed (they differ by the interval)\n    {current_block_height, state_at_block_beginning} = State.get_status()\n    child_top_block_number = current_block_height - child_block_interval\n\n    {:ok, last_synced_height} = OMG.DB.get_single_value(:last_block_getter_eth_height)\n    synced_height = max(contract_deployment_height, last_synced_height)\n\n    {:ok, state} =\n      Core.init(\n        child_top_block_number,\n        child_block_interval,\n        synced_height,\n        block_getter_reorg_margin,\n        state_at_block_beginning,\n        exit_processor_initial_results,\n        maximum_block_withholding_time_ms: maximum_block_withholding_time_ms,\n        maximum_number_of_unapplied_blocks: maximum_number_of_unapplied_blocks,\n        # NOTE: not elegant, but this should limit the number of heavy-lifting workers and chance to starve the rest\n        maximum_number_of_pending_blocks: System.schedulers(),\n        block_getter_loops_interval_ms: block_getter_loops_interval_ms,\n        child_chain_url: child_chain_url\n      )\n\n    :ok = check_in_to_coordinator(synced_height)\n    {:ok, _} = schedule_sync_height(block_getter_loops_interval_ms)\n    {:ok, _} = schedule_producer(block_getter_loops_interval_ms)\n\n    {:ok, _} = __MODULE__.Status.start_link()\n    :ok = update_status(state)\n    metrics_collection_interval = Keyword.fetch!(args, :metrics_collection_interval)\n\n    {:ok, _} = :timer.send_interval(metrics_collection_interval, self(), :send_metrics)\n\n    _ =\n      Logger.info(\n        \"Started #{inspect(__MODULE__)}, synced_height: #{inspect(synced_height)} maximum_block_withholding_time_ms: #{\n          maximum_block_withholding_time_ms\n        }\"\n      )\n\n    {:ok, state}\n  end\n\n  # :apply_block pipeline of steps\n\n  @doc \"\"\"\n  Read top down:\n   - (execute_transactions) Stateful validation and execution of transactions on `OMG.Watcher.State`. Reacts in case that returns any failed transactions.\n  - (run_block_download_task) Schedules more blocks to download in case some work downloading is finished and we want to progress.\n  - (close_and_apply_block) Marks a block as applied and updates `OMG.DB` values. Also commits the updates to `OMG.DB` that `OMG.Watcher.State` handed off\n  containing the data coming from the newly applied block.\n  - (check_validity) Updates its view of validity of the chain.\n  \"\"\"\n  def handle_continue({:apply_block_step, :execute_transactions, block_application}, state) do\n    tx_exec_results = for(tx <- block_application.transactions, do: OMG.Watcher.State.exec(tx, :ignore_fees))\n\n    case Core.validate_executions(tx_exec_results, block_application, state) do\n      {:ok, state} ->\n        if Code.ensure_loaded?(OMG.WatcherInfo.DB.Block),\n          do: Kernel.apply(OMG.WatcherInfo.BlockApplicator, :insert_block!, [block_application])\n\n        {:noreply, state, {:continue, {:apply_block_step, :run_block_download_task, block_application}}}\n\n      {{:error, _} = error, new_state} ->\n        :ok = update_status(new_state)\n        _ = Logger.error(\"Invalid block #{inspect(block_application.number)}, because of #{inspect(error)}\")\n        {:noreply, new_state}\n    end\n  end\n\n  def handle_continue({:apply_block_step, :run_block_download_task, block_application}, state) do\n    {:noreply, run_block_download_task(state),\n     {:continue, {:apply_block_step, :close_and_apply_block, block_application}}}\n  end\n\n  def handle_continue({:apply_block_step, :close_and_apply_block, block_application}, state) do\n    {:ok, db_updates_from_state} = OMG.Watcher.State.close_block()\n\n    {state, synced_height, db_updates} = Core.apply_block(state, block_application)\n\n    _ = Logger.debug(\"Synced height update: #{inspect(db_updates)}\")\n\n    :ok = OMG.DB.multi_update(db_updates ++ db_updates_from_state)\n    :ok = check_in_to_coordinator(synced_height)\n\n    _ =\n      Logger.info(\n        \"Applied block: \\##{inspect(block_application.number)}, from eth height: #{\n          inspect(block_application.eth_height)\n        } \" <>\n          \"with #{inspect(length(block_application.transactions))} txs\"\n      )\n\n    {:noreply, state, {:continue, {:apply_block_step, :check_validity}}}\n  end\n\n  def handle_continue({:apply_block_step, :check_validity}, state) do\n    exit_processor_results = ExitProcessor.check_validity()\n    state = Core.consider_exits(state, exit_processor_results)\n    :ok = update_status(state)\n    {:noreply, state}\n  end\n\n  @doc \"\"\"\n  Statefully apply a statelessly validated block, coming in as a `BlockApplication` structure.\n  \"\"\"\n  def handle_cast({:apply_block, %BlockApplication{} = block_application}, state) do\n    case Core.chain_ok(state) do\n      {:ok, _} ->\n        {:noreply, state, {:continue, {:apply_block_step, :execute_transactions, block_application}}}\n\n      error ->\n        :ok = update_status(state)\n\n        _ =\n          Logger.warn(\n            \"Chain already invalid before applying block #{inspect(block_application.number)} because of #{\n              inspect(error)\n            }\"\n          )\n\n        {:noreply, state}\n    end\n  end\n\n  @spec handle_info(\n          :producer\n          | {reference(), {:downloaded_block, {:ok, map}}}\n          | {reference(), {:downloaded_block, {:error, Core.block_error()}}}\n          | {:DOWN, reference(), :process, pid, :normal},\n          Core.t()\n        ) :: {:noreply, Core.t()} | {:stop, :normal, Core.t()}\n  def handle_info(msg, state)\n\n  def handle_info(:producer, state), do: do_producer(state)\n  def handle_info({_ref, {:downloaded_block, response}}, state), do: do_downloaded_block(response, state)\n  def handle_info({:DOWN, _ref, :process, _pid, :normal} = _process, state), do: {:noreply, state}\n  def handle_info(:sync, state), do: do_sync(state)\n\n  def handle_info(:send_metrics, state) do\n    :ok = :telemetry.execute([:process, __MODULE__], %{}, state)\n    {:noreply, state}\n  end\n\n  def handle_info({:ssl_closed, _}, state) do\n    # eat this bug https://github.com/benoitc/hackney/issues/464\n    {:noreply, state}\n  end\n\n  #\n  # Private functions\n  #\n\n  defp do_producer(state) do\n    case Core.chain_ok(state) do\n      {:ok, _} ->\n        new_state = run_block_download_task(state)\n        {:ok, _} = schedule_producer(state.config.block_getter_loops_interval_ms)\n        :ok = update_status(new_state)\n        {:noreply, new_state}\n\n      {:error, _} = error ->\n        :ok = update_status(state)\n        _ = Logger.warn(\"Chain invalid when trying to download blocks, because of #{inspect(error)}, won't try again\")\n        {:noreply, state}\n    end\n  end\n\n  defp do_downloaded_block(response, state) do\n    # 1/ process the block that arrived and consume\n\n    case Core.handle_downloaded_block(state, response) do\n      {:ok, state} ->\n        state = run_block_download_task(state)\n        :ok = update_status(state)\n        {:noreply, state}\n\n      {{:error, _} = error, state} ->\n        :ok = update_status(state)\n        _ = Logger.error(\"Error while handling downloaded block because of #{inspect(error)}\")\n        {:noreply, state}\n    end\n  end\n\n  defp do_sync(state) do\n    with {:ok, _} <- Core.chain_ok(state),\n         %SyncGuide{sync_height: next_synced_height} <- RootChainCoordinator.get_sync_info() do\n      {block_from, block_to} = Core.get_eth_range_for_block_submitted_events(state, next_synced_height)\n\n      {:ok, submissions} = get_block_submitted_events(block_from, block_to)\n\n      {blocks_to_apply, synced_height, db_updates, state} =\n        Core.get_blocks_to_apply(state, submissions, next_synced_height)\n\n      _ = Logger.debug(\"Synced height is #{inspect(synced_height)}, got #{length(blocks_to_apply)} blocks to apply\")\n\n      Enum.each(blocks_to_apply, &GenServer.cast(__MODULE__, {:apply_block, &1}))\n\n      :ok = OMG.DB.multi_update(db_updates)\n      :ok = check_in_to_coordinator(synced_height)\n      {:ok, _} = schedule_sync_height(state.config.block_getter_loops_interval_ms)\n      :ok = update_status(state)\n      :ok = publish_events(submissions)\n      {:noreply, state}\n    else\n      :nosync ->\n        :ok = check_in_to_coordinator(state.synced_height)\n        :ok = update_status(state)\n        {:ok, _} = schedule_sync_height(state.config.block_getter_loops_interval_ms)\n        {:noreply, state}\n\n      {:error, _} = error ->\n        :ok = update_status(state)\n        _ = Logger.warn(\"Chain invalid when trying to sync, because of #{inspect(error)}, won't try again\")\n        {:noreply, state}\n    end\n  end\n\n  @decorate trace(tracer: OMG.Watcher.Tracer, type: :backend, service: :block_getter)\n  defp get_block_submitted_events(block_from, block_to) do\n    RootChain.get_block_submitted_events(block_from, block_to)\n  end\n\n  defp run_block_download_task(state) do\n    next_child = RootChain.next_child_block()\n    {new_state, blocks_numbers} = Core.get_numbers_of_blocks_to_download(state, next_child)\n\n    Enum.each(\n      blocks_numbers,\n      # captures the result in handle_info/2 with the atom: downloaded_block\n      &Task.async(fn ->\n        {:downloaded_block, download_block(&1, state.config.child_chain_url)}\n      end)\n    )\n\n    new_state\n  end\n\n  defp schedule_sync_height(block_getter_loops_interval_ms) do\n    :timer.send_after(block_getter_loops_interval_ms, self(), :sync)\n  end\n\n  defp schedule_producer(block_getter_loops_interval_ms) do\n    :timer.send_after(block_getter_loops_interval_ms, self(), :producer)\n  end\n\n  @spec download_block(pos_integer(), String.t()) :: Core.validate_download_response_result_t()\n  defp download_block(requested_number, child_chain_url) do\n    {requested_hash, block_timestamp} = RootChain.blocks(requested_number)\n\n    response = Client.get_block(requested_hash, child_chain_url)\n\n    Core.validate_download_response(\n      response,\n      requested_hash,\n      requested_number,\n      block_timestamp,\n      :os.system_time(:millisecond)\n    )\n  end\n\n  defp check_in_to_coordinator(synced_height), do: RootChainCoordinator.check_in(synced_height, :block_getter)\n\n  defp update_status(%Core{} = state), do: Status.update(Core.chain_ok(state))\n\n  defp publish_events([%{event_signature: event_signature} | _] = data) do\n    # event signature is string with a method name with arguments,\n    # for example: BlockSubmitted(uint256)\n    [event_signature, _] = String.split(event_signature, \"(\")\n\n    {:root_chain, event_signature}\n    |> OMG.Bus.Event.new(:data, data)\n    |> OMG.Bus.direct_local_broadcast()\n  end\n\n  defp publish_events([]), do: :ok\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/block_validator.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.BlockValidator do\n  @moduledoc \"\"\"\n  Operations related to block validation.\n  \"\"\"\n\n  alias OMG.Watcher.Block\n  alias OMG.Watcher.Merkle\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.Utxo.Position\n\n  @transaction_upper_limit 2 |> :math.pow(16) |> Kernel.trunc()\n\n  @doc \"\"\"\n  Executes stateless validation of a submitted block:\n  - Verifies that the number of transactions falls within the accepted range.\n  - Verifies that (payment and fee) transactions  are correctly formed.\n  - Verifies that fee transactions are correctly placed and unique per currency.\n  - Verifies that there are no duplicate inputs at the block level.\n  - Verifies that given Merkle root matches reconstructed Merkle root.\n\n  \"\"\"\n  @spec stateless_validate(Block.t()) :: {:ok, boolean()} | {:error, atom()}\n  def stateless_validate(submitted_block) do\n    with :ok <- number_of_transactions_within_limit(submitted_block.transactions),\n         {:ok, recovered_transactions} <- verify_transactions(submitted_block.transactions),\n         {:ok, _fee_transactions} <- verify_fee_transactions(recovered_transactions),\n         {:ok, _inputs} <- verify_no_duplicate_inputs(recovered_transactions),\n         {:ok, _block} <- verify_merkle_root(submitted_block, recovered_transactions) do\n      {:ok, true}\n    end\n  end\n\n  @spec verify_merkle_root(Block.t(), list(Transaction.Recovered.t())) ::\n          {:ok, Block.t()} | {:error, :mismatched_merkle_root}\n  defp verify_merkle_root(block, transactions) do\n    reconstructed_merkle_hash =\n      transactions\n      |> Enum.map(&Transaction.raw_txbytes/1)\n      |> Merkle.hash()\n\n    case block.hash do\n      ^reconstructed_merkle_hash -> {:ok, block}\n      _ -> {:error, :invalid_merkle_root}\n    end\n  end\n\n  @spec verify_transactions(transactions :: list(Transaction.Signed.tx_bytes())) ::\n          {:ok, list(Transaction.Recovered.t())}\n          | {:error, Transaction.Recovered.recover_tx_error()}\n  defp verify_transactions(transactions) do\n    transactions\n    |> Enum.reverse()\n    |> Enum.reduce_while({:ok, []}, fn tx, {:ok, already_recovered} ->\n      case Transaction.Recovered.recover_from(tx) do\n        {:ok, recovered} ->\n          {:cont, {:ok, [recovered | already_recovered]}}\n\n        error ->\n          {:halt, error}\n      end\n    end)\n  end\n\n  @spec number_of_transactions_within_limit([Transaction.Signed.tx_bytes()]) :: :ok | {:error, atom()}\n  defp number_of_transactions_within_limit([]), do: {:error, :empty_block}\n\n  defp number_of_transactions_within_limit(transactions) when length(transactions) > @transaction_upper_limit do\n    {:error, :transactions_exceed_block_limit}\n  end\n\n  defp number_of_transactions_within_limit(_transactions), do: :ok\n\n  @spec verify_no_duplicate_inputs([Transaction.Recovered.t()]) :: {:ok, [map()]} | {:error, :block_duplicate_inputs}\n  defp verify_no_duplicate_inputs(transactions) do\n    all_inputs = Enum.flat_map(transactions, &Transaction.get_inputs/1)\n    uniq_inputs = Enum.uniq_by(all_inputs, &Position.encode/1)\n\n    case length(all_inputs) == length(uniq_inputs) do\n      true -> {:ok, all_inputs}\n      false -> {:error, :block_duplicate_inputs}\n    end\n  end\n\n  @spec verify_fee_transactions([Transaction.Recovered.t()]) :: {:ok, [Transaction.Recovered.t()]} | {:error, atom()}\n  defp verify_fee_transactions(transactions) do\n    identified_fee_transactions = Enum.filter(transactions, &is_fee/1)\n\n    with :ok <- expected_index(transactions, identified_fee_transactions),\n         :ok <- unique_fee_transaction_per_currency(identified_fee_transactions) do\n      {:ok, identified_fee_transactions}\n    end\n  end\n\n  @spec expected_index([Transaction.Recovered.t()], [Transaction.Recovered.t()]) :: :ok | {:error, atom()}\n  defp expected_index(transactions, identified_fee_transactions) do\n    number_of_fee_txs = length(identified_fee_transactions)\n    tail = Enum.slice(transactions, -number_of_fee_txs, number_of_fee_txs)\n\n    case identified_fee_transactions do\n      ^tail -> :ok\n      _ -> {:error, :unexpected_transaction_type_at_fee_index}\n    end\n  end\n\n  @spec unique_fee_transaction_per_currency([Transaction.Recovered.t()]) :: :ok | {:error, atom()}\n  defp unique_fee_transaction_per_currency(identified_fee_transactions) do\n    identified_fee_transactions\n    |> Enum.uniq_by(fn fee_transaction -> fee_transaction |> get_fee_output() |> Map.get(:currency) end)\n    |> case do\n      ^identified_fee_transactions -> :ok\n      _ -> {:error, :duplicate_fee_transaction_for_ccy}\n    end\n  end\n\n  defp is_fee(%Transaction.Recovered{signed_tx: %Transaction.Signed{raw_tx: %Transaction.Fee{}}}) do\n    true\n  end\n\n  defp is_fee(_), do: false\n\n  defp get_fee_output(fee_transaction) do\n    fee_transaction |> Transaction.get_outputs() |> Enum.at(0)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/child_manager.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.ChildManager do\n  @moduledoc \"\"\"\n    Reports it's health to the Monitor after start or restart and shutsdown.\n  \"\"\"\n  use GenServer, restart: :transient\n\n  require Logger\n  @timer 100\n  def start_link(args) do\n    GenServer.start_link(__MODULE__, args, name: __MODULE__)\n  end\n\n  def init(args) do\n    monitor = Keyword.fetch!(args, :monitor)\n    {:ok, _tref} = :timer.send_after(@timer, :health_checkin)\n    {:ok, %{timer: @timer, monitor: monitor}}\n  end\n\n  def handle_info(:health_checkin, state) do\n    :ok = state.monitor.health_checkin()\n\n    {:stop, :normal, state}\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/configuration.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.Configuration do\n  @moduledoc \"\"\"\n  Provides access to applications configuration\n  \"\"\"\n  @app :omg_watcher\n  def exit_processor_sla_margin() do\n    Application.fetch_env!(@app, :exit_processor_sla_margin)\n  end\n\n  def exit_processor_sla_margin_forced() do\n    Application.fetch_env!(@app, :exit_processor_sla_margin_forced)\n  end\n\n  def metrics_collection_interval() do\n    Application.fetch_env!(@app, :metrics_collection_interval)\n  end\n\n  def block_getter_reorg_margin() do\n    Application.fetch_env!(@app, :block_getter_reorg_margin)\n  end\n\n  def maximum_block_withholding_time_ms() do\n    Application.fetch_env!(@app, :maximum_block_withholding_time_ms)\n  end\n\n  def maximum_number_of_unapplied_blocks() do\n    Application.fetch_env!(@app, :maximum_number_of_unapplied_blocks)\n  end\n\n  def child_chain_url() do\n    Application.get_env(@app, :child_chain_url)\n  end\n\n  def exit_finality_margin() do\n    Application.get_env(@app, :exit_finality_margin)\n  end\n\n  @spec deposit_finality_margin() :: pos_integer() | no_return\n  def deposit_finality_margin() do\n    Application.get_env(@app, :deposit_finality_margin)\n  end\n\n  @spec fee_claimer_address() :: binary() | no_return\n  def fee_claimer_address() do\n    Application.fetch_env!(@app, :fee_claimer_address)\n  end\n\n  @spec ethereum_events_check_interval_ms() :: pos_integer() | no_return\n  def ethereum_events_check_interval_ms() do\n    Application.fetch_env!(@app, :ethereum_events_check_interval_ms)\n  end\n\n  @spec coordinator_eth_height_check_interval_ms() :: pos_integer() | no_return\n  def coordinator_eth_height_check_interval_ms() do\n    Application.fetch_env!(@app, :coordinator_eth_height_check_interval_ms)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/coordinator_setup.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.CoordinatorSetup do\n  @moduledoc \"\"\"\n  The setup of `OMG.Watcher.RootChainCoordinator` for the Watcher - configures the relations between different event listeners\n  \"\"\"\n\n  @doc \"\"\"\n  The `OMG.Watcher.RootChainCoordinator` setup for the `OMG.Watcher` app. Summary of the configuration:\n\n    - deposits are recognized after `deposit_finality_margin`. Should take child chain server's setting into account\n    - exit-related events are recognized after `exit_finality_margin`\n    - exit-related events wait for deposits and themselves respectively, in case of the inter-dependent IFE events\n    - exit finalization-related events wait for deposits and blocks to never finalize not-yet created UTXOs\n    - blocks wait for deposits _BUT_ they advance by the finality margin of the `depositor`. In practice this means that\n      blocks wait for deposits when syncing, but don't when processing fresh events. This allows for 0-confirmation\n      finality of child chain transaction (the user is responsible for deciding on finality and confirmations)\n  \"\"\"\n\n  def coordinator_setup(\n        metrics_collection_interval,\n        coordinator_eth_height_check_interval_ms,\n        finality_margin,\n        deposit_finality_margin\n      ) do\n    {[\n       metrics_collection_interval: metrics_collection_interval,\n       coordinator_eth_height_check_interval_ms: coordinator_eth_height_check_interval_ms\n     ],\n     %{\n       depositor: [finality_margin: deposit_finality_margin],\n       block_getter: [\n         waits_for: [depositor: :no_margin],\n         finality_margin: 0\n       ],\n       exit_processor: [waits_for: :depositor, finality_margin: finality_margin],\n       exit_finalizer: [\n         waits_for: [:depositor, :block_getter, :exit_processor],\n         finality_margin: finality_margin\n       ],\n       exit_challenger: [waits_for: :exit_processor, finality_margin: finality_margin],\n       in_flight_exit_processor: [waits_for: :depositor, finality_margin: finality_margin],\n       in_flight_exit_deleted_processor: [waits_for: :in_flight_exit_processor, finality_margin: finality_margin],\n       piggyback_processor: [waits_for: :in_flight_exit_processor, finality_margin: finality_margin],\n       competitor_processor: [waits_for: :in_flight_exit_processor, finality_margin: finality_margin],\n       challenges_responds_processor: [waits_for: :competitor_processor, finality_margin: finality_margin],\n       piggyback_challenges_processor: [waits_for: :piggyback_processor, finality_margin: finality_margin],\n       ife_exit_finalizer: [\n         waits_for: [:depositor, :block_getter, :in_flight_exit_processor, :piggyback_processor],\n         finality_margin: finality_margin\n       ]\n     }}\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/crypto.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.Crypto do\n  @moduledoc \"\"\"\n  Signs and validates signatures. Constructed signatures can be used directly\n  in Ethereum with `ecrecover` call.\n\n  For unsafe code, limited to `:test` and `:dev` environments and related to private key handling refer to:\n  `OMG.Watcher.DevCrypto` in `test/support`\n  \"\"\"\n  alias ExPlasma.Crypto\n  alias OMG.Watcher.Signature\n\n  @type sig_t() :: <<_::520>>\n  @type pub_key_t() :: <<_::512>>\n  @type priv_key_t() :: <<_::256>> | <<>>\n  @type address_t() :: <<_::160>>\n  @type hash_t() :: <<_::256>>\n  @type domain_separator_t() :: <<_::256>> | nil\n\n  @doc \"\"\"\n  Produces a KECCAK digest for the message.\n\n  see https://hexdocs.pm/exth_crypto/ExthCrypto.Hash.html#kec/0\n\n  ## Example\n\n    iex> OMG.Watcher.Crypto.hash(\"omg!\")\n    <<241, 85, 204, 147, 187, 239, 139, 133, 69, 248, 239, 233, 219, 51, 189, 54,\n      171, 76, 106, 229, 69, 102, 203, 7, 21, 134, 230, 92, 23, 209, 187, 12>>\n  \"\"\"\n  @spec hash(binary) :: hash_t()\n  def hash(message), do: Crypto.keccak_hash(message)\n\n  @doc \"\"\"\n  Recovers the address of the signer from a binary-encoded signature.\n  \"\"\"\n  @spec recover_address(hash_t(), sig_t()) :: {:ok, address_t()} | {:error, atom()}\n  def recover_address(<<digest::binary-size(32)>>, <<packed_signature::binary-size(65)>>) do\n    case Signature.recover_public(digest, packed_signature) do\n      {:ok, pub} ->\n        generate_address(pub)\n\n      {:error, :recovery_id_not_u8} ->\n        {:error, :signature_corrupt}\n\n      {:error, _} = error ->\n        error\n    end\n  end\n\n  @doc \"\"\"\n  Given public key, returns an address.\n  \"\"\"\n  @spec generate_address(pub_key_t()) :: {:ok, address_t()}\n  def generate_address(<<pub::binary-size(64)>>) do\n    <<_::binary-size(12), address::binary-size(20)>> = hash(pub)\n    {:ok, address}\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/datadog_event/contract_event_consumer.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.DatadogEvent.ContractEventConsumer do\n  @moduledoc \"\"\"\n  Subscribes to new events from EthereumEventListeners and pushes them to Datadog\n  Integrated with the help of: https://docs.datadoghq.com/api/?lang=bash#post-an-event\n  Most things from the doc doesn't work. Either because Statix doesn't work or Datadog.\n  Date_happened, aggregation_key, source_type_name doesn't seem to appear in Events list.\n  Hence we transform everything into a tag.\n  \"\"\"\n\n  require Logger\n  alias OMG.Watcher.DatadogEvent.Encode\n\n  @doc \"\"\"\n  Returns child_specs for the given `EventConsumer`, to be included e.g. in Supervisor's children.\n  \"\"\"\n  # sobelow_skip [\"DOS.StringToAtom\"]\n  @spec prepare_child(keyword()) :: %{id: atom(), start: tuple()}\n  def prepare_child(opts \\\\ []) do\n    {:root_chain, topic_name} = Keyword.fetch!(opts, :topic)\n\n    %{\n      id: String.to_atom(\"root_chain:#{topic_name}_worker\"),\n      start: {__MODULE__, :start_link, [opts]},\n      shutdown: :brutal_kill,\n      type: :worker\n    }\n  end\n\n  def start_link(args) do\n    GenServer.start_link(__MODULE__, args)\n  end\n\n  ### Server\n\n  use GenServer\n\n  def init(args) do\n    publisher = Keyword.fetch!(args, :publisher)\n    {:root_chain, event_name} = topic = Keyword.fetch!(args, :topic)\n    release = Keyword.fetch!(args, :release)\n    current_version = Keyword.fetch!(args, :current_version)\n    :ok = OMG.Bus.subscribe(topic, link: true)\n\n    _ = Logger.info(\"Started #{inspect(__MODULE__)} for event root_chain:#{event_name}\")\n    {:ok, %{publisher: publisher, release: release, current_version: current_version}}\n  end\n\n  def handle_info({:internal_event_bus, :enqueue_block, _omg_block}, state) do\n    # ignore for now\n    {:noreply, state}\n  end\n\n  @doc \"\"\"\n    Listens to events via OMG BUS and send them off\n    the assumption is all events are of the same type\n  \"\"\"\n  def handle_info({:internal_event_bus, :data, data}, state) do\n    aggregation_key = :root_chain\n    timestamp = DateTime.to_unix(DateTime.utc_now(), :millisecond)\n\n    options = tags(aggregation_key, state.release, state.current_version, timestamp)\n\n    Enum.each(data, fn ev ->\n      %{event_signature: event_signature} = ev\n      [event_name, _] = String.split(event_signature, \"(\")\n      title = \"#{event_name}\"\n      message = \"[#{inspect(Encode.make_it_readable!(ev))}] - Timestamp: #{timestamp}\"\n      :ok = apply(state.publisher, :event, create_event_data(title, message, options))\n    end)\n\n    {:noreply, state}\n  end\n\n  defp create_event_data(title, message, options) do\n    [title, message, options]\n  end\n\n  # https://docs.datadoghq.com/api/?lang=bash#api-reference\n  defp tags(aggregation_key, release, current_version, _timestamp) do\n    [\n      {:aggregation_key, aggregation_key},\n      {:tags, [\"#{aggregation_key}\", \"#{release}\", \"vsn-#{current_version}\"]},\n      {:alert_type, \"success\"}\n      # {:timestamp, timestamp}\n    ]\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/datadog_event/encode.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.DatadogEvent.Encode do\n  @moduledoc \"\"\"\n  Iterates the input and hex encodes binaries\n  \"\"\"\n\n  def make_it_readable!(event) when is_map(event) do\n    compactor = fn {k, v}, acc ->\n      cond do\n        is_map(v) and Enum.empty?(v) -> acc\n        is_list(v) -> Map.put_new(acc, k, make_it_readable!(v))\n        is_map(v) -> Map.put_new(acc, k, make_it_readable!(v))\n        k == :event_signature -> Map.put_new(acc, k, v)\n        is_binary(v) -> Map.put_new(acc, k, \"0x\" <> Base.encode16(v, case: :lower))\n        true -> Map.put_new(acc, k, v)\n      end\n    end\n\n    Enum.reduce(event, %{}, compactor)\n  end\n\n  def make_it_readable!(event) when is_list(event) do\n    compactor = fn v, acc ->\n      cond do\n        is_map(v) and Enum.empty?(v) -> acc\n        is_integer(v) -> [v | acc]\n        is_map(v) -> [make_it_readable!(v) | acc]\n        is_binary(v) -> [\"0x\" <> Base.encode16(v, case: :lower) | acc]\n        true -> [v | acc]\n      end\n    end\n\n    Enum.reduce(event, [], compactor)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/ethereum_event_aggregator.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\ndefmodule OMG.Watcher.EthereumEventAggregator do\n  @moduledoc \"\"\"\n  This process combines all plasma contract events we're interested in and does eth_getLogs + enriches them if needed\n  for all Ethereum Event Listener processes.\n  \"\"\"\n  use GenServer\n  require Logger\n  use Spandex.Decorators\n\n  alias OMG.Eth.RootChain.Abi\n  alias OMG.Eth.RootChain.Event\n  alias OMG.Eth.RootChain.Rpc\n\n  @timeout 55_000\n  @type result() :: {:ok, list(map())} | {:error, :check_range}\n\n  @spec deposit_created(GenServer.server(), pos_integer(), pos_integer()) :: result()\n  def deposit_created(server \\\\ __MODULE__, from_block, to_block) do\n    forward_call(server, :deposit_created, from_block, to_block, @timeout)\n  end\n\n  @spec exit_started(GenServer.server(), pos_integer(), pos_integer()) :: result()\n  def exit_started(server \\\\ __MODULE__, from_block, to_block) do\n    forward_call(server, :exit_started, from_block, to_block, @timeout)\n  end\n\n  @spec exit_finalized(GenServer.server(), pos_integer(), pos_integer()) :: result()\n  def exit_finalized(server \\\\ __MODULE__, from_block, to_block) do\n    forward_call(server, :exit_finalized, from_block, to_block, @timeout)\n  end\n\n  @spec exit_challenged(GenServer.server(), pos_integer(), pos_integer()) :: result()\n  def exit_challenged(server \\\\ __MODULE__, from_block, to_block) do\n    forward_call(server, :exit_challenged, from_block, to_block, @timeout)\n  end\n\n  @spec in_flight_exit_started(GenServer.server(), pos_integer(), pos_integer()) :: result()\n  def in_flight_exit_started(server \\\\ __MODULE__, from_block, to_block) do\n    forward_call(server, :in_flight_exit_started, from_block, to_block, @timeout)\n  end\n\n  @spec in_flight_exit_deleted(GenServer.server(), pos_integer(), pos_integer()) :: result()\n  def in_flight_exit_deleted(server \\\\ __MODULE__, from_block, to_block) do\n    forward_call(server, :in_flight_exit_deleted, from_block, to_block, @timeout)\n  end\n\n  @spec in_flight_exit_piggybacked(GenServer.server(), pos_integer(), pos_integer()) :: result()\n  def in_flight_exit_piggybacked(server \\\\ __MODULE__, from_block, to_block) do\n    # input and output\n    forward_call(server, :in_flight_exit_piggybacked, from_block, to_block, @timeout)\n  end\n\n  @spec in_flight_exit_challenged(GenServer.server(), pos_integer(), pos_integer()) :: result()\n  def in_flight_exit_challenged(server \\\\ __MODULE__, from_block, to_block) do\n    forward_call(server, :in_flight_exit_challenged, from_block, to_block, @timeout)\n  end\n\n  @spec in_flight_exit_challenge_responded(GenServer.server(), pos_integer(), pos_integer()) :: result()\n  def in_flight_exit_challenge_responded(server \\\\ __MODULE__, from_block, to_block) do\n    forward_call(server, :in_flight_exit_challenge_responded, from_block, to_block, @timeout)\n  end\n\n  @spec in_flight_exit_blocked(GenServer.server(), pos_integer(), pos_integer()) :: result()\n  def in_flight_exit_blocked(server \\\\ __MODULE__, from_block, to_block) do\n    forward_call(server, :in_flight_exit_blocked, from_block, to_block, @timeout)\n  end\n\n  @spec in_flight_exit_withdrawn(GenServer.server(), pos_integer(), pos_integer()) :: result()\n  def in_flight_exit_withdrawn(server \\\\ __MODULE__, from_block, to_block) do\n    forward_call(server, :in_flight_exit_withdrawn, from_block, to_block, @timeout)\n  end\n\n  defstruct delete_events_threshold_ethereum_block_height: 1000,\n            ets_bucket: nil,\n            event_signatures: [],\n            events: [],\n            contracts: [],\n            rpc: nil\n\n  def start_link(opts) do\n    GenServer.start_link(__MODULE__, opts, name: Keyword.get(opts, :name, __MODULE__))\n  end\n\n  def init(opts) do\n    contracts = opts |> Keyword.fetch!(:contracts) |> Map.values() |> Enum.map(&from_hex(&1))\n    # events = [[signature: \"ExitStarted(address,uint160)\", name: :exit_started, enrich: true],..]\n    events =\n      opts\n      |> Keyword.fetch!(:events)\n      |> Enum.map(&Keyword.fetch!(&1, :name))\n      |> Event.get_events()\n      |> Enum.zip(Keyword.fetch!(opts, :events))\n      |> Enum.reduce([], fn {signature, event}, acc -> [Keyword.put(event, :signature, signature) | acc] end)\n\n    events_signatures =\n      opts\n      |> Keyword.fetch!(:events)\n      |> Enum.map(&Keyword.fetch!(&1, :name))\n      |> Event.get_events()\n\n    ets_bucket = Keyword.fetch!(opts, :ets_bucket)\n    rpc = Keyword.get(opts, :rpc, Rpc)\n\n    {:ok,\n     %__MODULE__{\n       # 1000 blocks of events will be kept in memory\n       delete_events_threshold_ethereum_block_height: 1000,\n       ets_bucket: ets_bucket,\n       event_signatures: events_signatures,\n       events: events,\n       contracts: contracts,\n       rpc: rpc\n     }}\n  end\n\n  @decorate trace(tracer: OMG.Watcher.Tracer, type: :backend, service: __MODULE__, name: \"handle_call/3\")\n  def handle_call({:in_flight_exit_withdrawn, from_block, to_block}, _, state) do\n    names = [:in_flight_exit_input_withdrawn, :in_flight_exit_output_withdrawn]\n\n    logs =\n      Enum.reduce(names, [], fn name, acc ->\n        signature =\n          state.events\n          |> Enum.find(fn event -> Keyword.fetch!(event, :name) == name end)\n          |> Keyword.fetch!(:signature)\n\n        logs = retrieve_log(signature, from_block, to_block, state)\n        logs ++ acc\n      end)\n\n    {:reply, {:ok, logs}, state, {:continue, from_block}}\n  end\n\n  @decorate trace(tracer: OMG.Watcher.Tracer, type: :backend, service: __MODULE__, name: \"handle_call/3\")\n  def handle_call({:in_flight_exit_blocked, from_block, to_block}, _, state) do\n    names = [:in_flight_exit_input_blocked, :in_flight_exit_output_blocked]\n\n    logs =\n      names\n      |> Enum.reduce([], fn name, acc ->\n        signature =\n          state.events\n          |> Enum.find(fn event -> Keyword.fetch!(event, :name) == name end)\n          |> Keyword.fetch!(:signature)\n\n        logs = retrieve_log(signature, from_block, to_block, state)\n        logs ++ acc\n      end)\n\n    {:reply, {:ok, logs}, state, {:continue, from_block}}\n  end\n\n  @decorate trace(tracer: OMG.Watcher.Tracer, type: :backend, service: __MODULE__, name: \"handle_call/3\")\n  def handle_call({:in_flight_exit_piggybacked, from_block, to_block}, _, state) do\n    names = [:in_flight_exit_output_piggybacked, :in_flight_exit_input_piggybacked]\n\n    logs =\n      names\n      |> Enum.reduce([], fn name, acc ->\n        signature =\n          state.events\n          |> Enum.find(fn event -> Keyword.fetch!(event, :name) == name end)\n          |> Keyword.fetch!(:signature)\n\n        logs = retrieve_log(signature, from_block, to_block, state)\n        logs ++ acc\n      end)\n\n    {:reply, {:ok, logs}, state, {:continue, from_block}}\n  end\n\n  @decorate trace(tracer: OMG.Watcher.Tracer, type: :backend, service: __MODULE__, name: \"handle_call/3\")\n  def handle_call({name, from_block, to_block}, _, state) do\n    signature =\n      state.events\n      |> Enum.find(fn event -> Keyword.fetch!(event, :name) == name end)\n      |> Keyword.fetch!(:signature)\n\n    logs = retrieve_log(signature, from_block, to_block, state)\n    {:reply, {:ok, logs}, state, {:continue, from_block}}\n  end\n\n  defp forward_call(server, event, from_block, to_block, timeout) when from_block <= to_block do\n    GenServer.call(server, {event, from_block, to_block}, timeout)\n  end\n\n  defp forward_call(_, _, from_block, to_block, _) when from_block > to_block do\n    _ = Logger.error(\"From block #{from_block} was bigger than to_block #{to_block}\")\n    {:error, :check_range}\n  end\n\n  def handle_continue(new_height_blknum, state) do\n    _num_deleted = delete_old_logs(new_height_blknum, state)\n\n    {:noreply, state}\n  end\n\n  defp retrieve_and_store_logs(from_block, to_block, state) do\n    from_block\n    |> get_logs(to_block, state)\n    |> enrich_logs_with_call_data(state)\n    |> store_logs(from_block, to_block, state)\n  end\n\n  defp get_logs(from_height, to_height, state) do\n    {:ok, logs} = state.rpc.get_ethereum_events(from_height, to_height, state.event_signatures, state.contracts)\n    Enum.map(logs, &Abi.decode_log(&1))\n  end\n\n  # we get the logs from RPC and we cross check with the event definition if we need to enrich them\n  defp enrich_logs_with_call_data(decoded_logs, state) do\n    events = state.events\n    rpc = state.rpc\n\n    Enum.map(decoded_logs, fn decoded_log ->\n      decoded_log_signature = decoded_log.event_signature\n\n      event = Enum.find(events, fn event -> Keyword.fetch!(event, :signature) == decoded_log_signature end)\n\n      case Keyword.fetch!(event, :enrich) do\n        true ->\n          {:ok, enriched_data} = rpc.get_call_data(decoded_log.root_chain_txhash)\n          enriched_data_decoded = enriched_data |> from_hex |> Abi.decode_function()\n          Map.put(decoded_log, :call_data, enriched_data_decoded)\n\n        _ ->\n          decoded_log\n      end\n    end)\n  end\n\n  defp store_logs(decoded_logs, from_block, to_block, state) do\n    event_signatures = state.event_signatures\n\n    # all logs come in a list of maps\n    # we want to group them by blknum and signature:\n    # [{286, \"InFlightExitChallengeResponded(address,bytes32,uint256)\", [event]},\n    # {287, \"ExitChallenged(uint256)\",[event, event]]\n    decoded_logs_in_keypair =\n      decoded_logs\n      |> Enum.group_by(\n        fn decoded_log ->\n          {decoded_log.eth_height, decoded_log.event_signature}\n        end,\n        fn decoded_log ->\n          decoded_log\n        end\n      )\n      |> Enum.map(fn {{blknum, signature}, logs} ->\n        {blknum, signature, logs}\n      end)\n\n    # if we visited a particular range of blknum (from, to) we want to\n    # insert empty data in the DB, so that clients know we've been there and that blocks are\n    # empty of logs.\n    # for the whole from, to range and signatures we create group pairs like so:\n    # from = 286, to = 287 signatures = [\"Exit\", \"Deposit\"]\n    # [{286, \"Exit\", []},{286, \"Deposit\", []},{287, \"Exit\", []},{287, \"Deposit\", []}]\n    empty_blknum_signature_events =\n      from_block..to_block\n      |> Enum.to_list()\n      |> Enum.map(fn blknum -> Enum.map(event_signatures, fn signature -> {blknum, signature, []} end) end)\n      |> List.flatten()\n\n    # we now merge the two lists\n    # it is important that logs we got from RPC are first\n    # because uniq_by takes the first occurance of {blknum, signature}\n    # so that we don't overwrite retrieved logs\n    data =\n      decoded_logs_in_keypair\n      |> Enum.concat(empty_blknum_signature_events)\n      |> Enum.uniq_by(fn {blknum, signature, _data} ->\n        {blknum, signature}\n      end)\n\n    true = :ets.insert(state.ets_bucket, data)\n    :ok\n  end\n\n  # delete everything older then (current block - delete_events_threshold)\n  defp delete_old_logs(new_height_blknum, state) do\n    # :ets.fun2ms(fn {block_number, _event_signature, _event} when\n    # block_number <= new_height - delete_events_threshold -> true end)\n    match_spec = [\n      {{:\"$1\", :\"$2\", :\"$3\"},\n       [\n         {:\"=<\", :\"$1\",\n          {:-, {:const, new_height_blknum}, {:const, state.delete_events_threshold_ethereum_block_height}}}\n       ], [true]}\n    ]\n\n    :ets.select_delete(state.ets_bucket, match_spec)\n  end\n\n  # allow ethereum event listeners to retrieve logs from ETS in bulk\n  defp retrieve_log(signature, from_block, to_block, state) do\n    # :ets.fun2ms(fn {block_number, event_signature, event} when\n    # block_number >= from_block and block_number <= to_block\n    # and event_signature == signature -> event\n    # end)\n    event_match_spec = [\n      {{:\"$1\", :\"$2\", :\"$3\"},\n       [\n         {:andalso, {:andalso, {:>=, :\"$1\", {:const, from_block}}, {:\"=<\", :\"$1\", {:const, to_block}}},\n          {:==, :\"$2\", {:const, signature}}}\n       ], [:\"$3\"]}\n    ]\n\n    block_range = [\n      {{:\"$1\", :\"$2\", :\"$3\"},\n       [\n         {:andalso, {:andalso, {:>=, :\"$1\", {:const, from_block}}, {:\"=<\", :\"$1\", {:const, to_block}}},\n          {:==, :\"$2\", {:const, signature}}}\n       ], [:\"$1\"]}\n    ]\n\n    events = state.ets_bucket |> :ets.select(event_match_spec) |> List.flatten()\n\n    blknum_list = :ets.select(state.ets_bucket, block_range)\n\n    # we may not have all the block information the ethereum event listener wants\n    # so we check for that and find all logs for missing blocks\n    # in one RPC call for all signatures\n\n    case Enum.to_list(from_block..to_block) -- blknum_list do\n      [] ->\n        events\n\n      missing_blocks ->\n        missing_blocks = Enum.sort(missing_blocks)\n        missing_from_block = List.first(missing_blocks)\n        missing_to_block = List.last(missing_blocks)\n\n        _ =\n          Logger.debug(\n            \"Missing block information (#{missing_from_block}, #{missing_to_block}) in event fetcher. Retrieving from RPC.\"\n          )\n\n        :ok = retrieve_and_store_logs(missing_from_block, missing_to_block, state)\n        retrieve_log(signature, from_block, to_block, state)\n    end\n  end\n\n  defp from_hex(\"0x\" <> encoded), do: Base.decode16!(encoded, case: :lower)\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/ethereum_event_listener/core.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.EthereumEventListener.Core do\n  @moduledoc \"\"\"\n  Logic module for the `OMG.Watcher.EthereumEventListener`\n\n  Responsible for:\n    - deciding what ranges of Ethereum events should be fetched from the Ethereum node\n    - deciding the right size of event batches to read (too little means many RPC requests, too big can timeout)\n    - deciding what to check in into the `OMG.Watcher.RootChainCoordinator`\n    - deciding what to put into the `OMG.DB` in terms of Ethereum height till which the events are already processed\n\n  Leverages a rudimentary in-memory cache for events, to be able to ask for right-sized batches of events\n  \"\"\"\n  alias OMG.Watcher.RootChainCoordinator.SyncGuide\n\n  use Spandex.Decorators\n\n  # synced_height is what's being exchanged with `RootChainCoordinator`.\n  # The point in root chain until where it processed\n  defstruct synced_height_update_key: nil,\n            service_name: nil,\n            synced_height: 0,\n            ethereum_events_check_interval_ms: nil,\n            request_max_size: 1000\n\n  @type event :: %{eth_height: non_neg_integer()}\n\n  @type t() :: %__MODULE__{\n          synced_height_update_key: atom(),\n          service_name: atom(),\n          synced_height: integer(),\n          ethereum_events_check_interval_ms: non_neg_integer(),\n          request_max_size: pos_integer()\n        }\n\n  @doc \"\"\"\n  Initializes the listener logic based on its configuration and the last persisted Ethereum height, till which events\n  were processed\n  \"\"\"\n  @spec init(atom(), atom(), non_neg_integer(), non_neg_integer(), non_neg_integer()) :: {t(), non_neg_integer()}\n\n  def init(\n        update_key,\n        service_name,\n        last_synced_ethereum_height,\n        ethereum_events_check_interval_ms,\n        request_max_size \\\\ 1000\n      ) do\n    initial_state = %__MODULE__{\n      synced_height_update_key: update_key,\n      synced_height: last_synced_ethereum_height,\n      service_name: service_name,\n      request_max_size: request_max_size,\n      ethereum_events_check_interval_ms: ethereum_events_check_interval_ms\n    }\n\n    {initial_state, last_synced_ethereum_height}\n  end\n\n  @doc \"\"\"\n  Returns the events range -\n  - from (inclusive!),\n  - to (inclusive!)\n  that needs to be scraped and sets synced_height in the state.\n\n  \"\"\"\n  @decorate span(service: :ethereum_event_listener, type: :backend, name: \"calc_events_range_set_height/2\")\n  @spec calc_events_range_set_height(t(), SyncGuide.t()) ::\n          {:dont_fetch_events, t()} | {{non_neg_integer, non_neg_integer}, t()}\n  def calc_events_range_set_height(state, sync_guide) do\n    case sync_guide.sync_height <= state.synced_height do\n      true ->\n        {:dont_fetch_events, state}\n\n      _ ->\n        # if sync_guide.sync_height has applied margin (reorg protection)\n        # the only thing we need to be aware of is that we don't go pass that!\n        # but we want to move as fast as possible so we try to fetch as much as we can (request_max_size)\n        first_not_visited = state.synced_height + 1\n        # if first not visited = 1, and request max size is 10\n        # it means we can scrape AT MOST request_max_size events\n        max_height = state.request_max_size - 1\n        upper_bound = min(sync_guide.sync_height, first_not_visited + max_height)\n\n        {{first_not_visited, upper_bound}, %{state | synced_height: upper_bound}}\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/ethereum_event_listener/measure.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.EthereumEventListener.Measure do\n  @moduledoc \"\"\"\n  Counting business metrics sent to Datadog.\n  We don't want to pattern match on :ok to Datadog because the connection\n  towards the statsd client can be intermittent and sending would be unsuccessful and that\n  would trigger the removal of telemetry handler. But because we have monitors in place,\n  that eventually recover the connection to Statsd handlers wouldn't exist anymore and metrics\n  wouldn't be published.\n  \"\"\"\n\n  import OMG.Status.Metric.Event, only: [name: 2]\n\n  alias OMG.Status.Metric.Datadog\n  alias OMG.Status.Metric.Tracer\n\n  @supported_events [\n    [:process, OMG.Watcher.EthereumEventListener],\n    [:trace, OMG.Watcher.EthereumEventListener],\n    [:trace, OMG.Watcher.EthereumEventListener.Core]\n  ]\n  def supported_events(), do: @supported_events\n\n  def handle_event([:process, OMG.Watcher.EthereumEventListener], %{events: events}, state, _config) do\n    _ = Datadog.gauge(name(state.service_name, :events), length(events))\n  end\n\n  def handle_event([:process, OMG.Watcher.EthereumEventListener], %{}, state, _config) do\n    value =\n      self()\n      |> Process.info(:message_queue_len)\n      |> elem(1)\n\n    _ = Datadog.gauge(name(state.service_name, :message_queue_len), value)\n  end\n\n  def handle_event([:trace, _], %{}, state, _config) do\n    Tracer.update_top_span(service: state.service_name, tags: [])\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/ethereum_event_listener.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.EthereumEventListener do\n  @moduledoc \"\"\"\n  GenServer running the listener.\n\n  Periodically fetches events made on dynamically changing block range\n  from the root chain contract and feeds them to a callback.\n\n  It is **not** responsible for figuring out which ranges of Ethereum blocks are eligible to scan and when, see\n  `OMG.Watcher.RootChainCoordinator` for that.\n  The `OMG.Watcher.RootChainCoordinator` provides the `SyncGuide` that indicates what's eligible to scan, taking into account:\n   - finality margin\n   - mutual ordering and dependencies of various types of Ethereum events to be respected.\n\n  It **is** responsible for processing all events from all blocks and processing them only once.\n\n  It accomplishes that by keeping a persisted value in `OMG.DB` and its state that reflects till which Ethereum height\n  the events were processed (`synced_height`).\n  This `synced_height` is updated after every batch of Ethereum events get successfully consumed by\n  `callbacks.process_events_callback`, as called in `sync_height/2`, together with all the `OMG.DB` updates this\n  callback returns, atomically.\n  The key in `OMG.DB` used to persist `synced_height` is defined by the value of `synced_height_update_key`.\n\n  What specific Ethereum events it fetches, and what it does with them is up to predefined `callbacks`.\n\n  See `OMG.Watcher.EthereumEventListener.Core` for the implementation of the business logic for the listener.\n  \"\"\"\n  use GenServer\n  use Spandex.Decorators\n  require Logger\n\n  alias OMG.Watcher.EthereumEventListener.Core\n  alias OMG.Watcher.RootChainCoordinator\n\n  @type config() :: %{\n          block_finality_margin: non_neg_integer,\n          synced_height_update_key: atom,\n          service_name: atom,\n          # maps a pair denoting eth height range to a list of ethereum events\n          get_events_callback: (non_neg_integer, non_neg_integer -> {:ok, [map]}),\n          # maps a list of ethereum events to a list of `db_updates` to send to `OMG.DB`\n          process_events_callback: ([any] -> {:ok, [tuple]})\n        }\n\n  ### Client\n\n  @spec start_link(config()) :: GenServer.on_start()\n  def start_link(config) do\n    %{service_name: name} = config\n    GenServer.start_link(__MODULE__, config, name: name)\n  end\n\n  @doc \"\"\"\n  Returns child_specs for the given `EthereumEventListener` setup, to be included e.g. in Supervisor's children.\n  See `handle_continue/2` for the required keyword arguments.\n  \"\"\"\n  @spec prepare_child(keyword()) :: %{id: atom(), start: tuple()}\n  def prepare_child(opts \\\\ []) do\n    name = Keyword.fetch!(opts, :service_name)\n\n    %{\n      id: name,\n      start: {OMG.Watcher.EthereumEventListener, :start_link, [Map.new(opts)]},\n      shutdown: :brutal_kill,\n      type: :worker\n    }\n  end\n\n  ### Server\n\n  @doc \"\"\"\n  Initializes the GenServer state, most work done in `handle_continue/2`.\n  \"\"\"\n  def init(init) do\n    {:ok, init, {:continue, :setup}}\n  end\n\n  @doc \"\"\"\n  Reads the status of listening (till which Ethereum height were the events processed) from the `OMG.DB` and initializes\n  the logic `OMG.Watcher.EthereumEventListener.Core` with it. Does an initial `OMG.Watcher.RootChainCoordinator.check_in` with the\n  Ethereum height it last stopped on. Next, it continues to monitor and fetch the events as usual.\n  \"\"\"\n  def handle_continue(\n        :setup,\n        %{\n          contract_deployment_height: contract_deployment_height,\n          synced_height_update_key: update_key,\n          service_name: service_name,\n          get_events_callback: get_events_callback,\n          process_events_callback: process_events_callback,\n          metrics_collection_interval: metrics_collection_interval,\n          ethereum_events_check_interval_ms: ethereum_events_check_interval_ms\n        }\n      ) do\n    _ = Logger.info(\"Starting #{inspect(__MODULE__)} for #{service_name}.\")\n\n    {:ok, last_event_block_height} = OMG.DB.get_single_value(update_key)\n\n    # we don't need to ever look at earlier than contract deployment\n    last_event_block_height = max(last_event_block_height, contract_deployment_height)\n\n    {initial_state, height_to_check_in} =\n      Core.init(update_key, service_name, last_event_block_height, ethereum_events_check_interval_ms)\n\n    callbacks = %{\n      get_ethereum_events_callback: get_events_callback,\n      process_events_callback: process_events_callback\n    }\n\n    {:ok, _} = schedule_get_events(ethereum_events_check_interval_ms)\n    :ok = RootChainCoordinator.check_in(height_to_check_in, service_name)\n    {:ok, _} = :timer.send_interval(metrics_collection_interval, self(), :send_metrics)\n\n    _ = Logger.info(\"Started #{inspect(__MODULE__)} for #{service_name}, synced_height: #{inspect(height_to_check_in)}\")\n\n    {:noreply, {initial_state, callbacks}}\n  end\n\n  def handle_info(:send_metrics, {state, callbacks}) do\n    :ok = :telemetry.execute([:process, __MODULE__], %{}, state)\n    {:noreply, {state, callbacks}}\n  end\n\n  @doc \"\"\"\n  Main worker function, called on a cadence as initialized in `handle_continue/2`.\n\n  Does the following:\n   - asks `OMG.Watcher.RootChainCoordinator` about how to sync, with respect to other services listening to Ethereum\n   - (`sync_height/2`) figures out what is the suitable range of Ethereum blocks to download events for\n   - (`sync_height/2`) if necessary fetches those events to the in-memory cache in `OMG.Watcher.EthereumEventListener.Core`\n   - (`sync_height/2`) executes the related event-consuming callback with events as arguments\n   - (`sync_height/2`) does `OMG.DB` updates that persist the processes Ethereum height as well as whatever the\n      callbacks returned to persist\n   - (`sync_height/2`) `OMG.Watcher.RootChainCoordinator.check_in` to tell the rest what Ethereum height was processed.\n  \"\"\"\n  @decorate trace(service: :ethereum_event_listener, type: :backend)\n  def handle_info(:sync, {state, callbacks}) do\n    :ok = :telemetry.execute([:trace, __MODULE__], %{}, state)\n\n    case RootChainCoordinator.get_sync_info() do\n      :nosync ->\n        :ok = RootChainCoordinator.check_in(state.synced_height, state.service_name)\n        {:ok, _} = schedule_get_events(state.ethereum_events_check_interval_ms)\n        {:noreply, {state, callbacks}}\n\n      sync_info ->\n        new_state = sync_height(state, callbacks, sync_info)\n        {:ok, _} = schedule_get_events(state.ethereum_events_check_interval_ms)\n        {:noreply, {new_state, callbacks}}\n    end\n  end\n\n  # see `handle_info/2`, clause for `:sync`\n  @decorate span(service: :ethereum_event_listener, type: :backend, name: \"sync_height/3\")\n  defp sync_height(state, callbacks, sync_guide) do\n    {events, new_state} =\n      state\n      |> Core.calc_events_range_set_height(sync_guide)\n      |> get_events(callbacks.get_ethereum_events_callback)\n\n    db_update = [{:put, new_state.synced_height_update_key, new_state.synced_height}]\n    :ok = :telemetry.execute([:process, __MODULE__], %{events: events}, new_state)\n\n    {:ok, db_updates_from_callback} = callbacks.process_events_callback.(events)\n    :ok = publish_events(events)\n    :ok = OMG.DB.multi_update(db_update ++ db_updates_from_callback)\n    :ok = RootChainCoordinator.check_in(new_state.synced_height, new_state.service_name)\n\n    new_state\n  end\n\n  defp get_events({{from, to}, state}, get_events_callback) do\n    {:ok, new_events} = get_events_callback.(from, to)\n    {new_events, state}\n  end\n\n  defp get_events({:dont_fetch_events, state}, _callback) do\n    {[], state}\n  end\n\n  defp schedule_get_events(ethereum_events_check_interval_ms) do\n    :timer.send_after(ethereum_events_check_interval_ms, self(), :sync)\n  end\n\n  defp publish_events([%{event_signature: event_signature} | _] = data) do\n    [event_signature, _] = String.split(event_signature, \"(\")\n\n    {:root_chain, event_signature}\n    |> OMG.Bus.Event.new(:data, data)\n    |> OMG.Bus.direct_local_broadcast()\n  end\n\n  defp publish_events([]), do: :ok\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/event.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.Event do\n  @moduledoc \"\"\"\n  Definitions of structures representing various events delivered by the Watcher\n\n  This module is agnostic of mode of delivery of events - both push and poll events go here\n  \"\"\"\n  alias OMG.Watcher.Block\n  alias OMG.Watcher.Crypto\n  alias OMG.Watcher.State.Transaction\n\n  @type byzantine_t ::\n          OMG.Watcher.Event.InvalidBlock.t()\n          | OMG.Watcher.Event.BlockWithholding.t()\n          | OMG.Watcher.Event.InvalidExit.t()\n          | OMG.Watcher.Event.UnchallengedExit.t()\n          | OMG.Watcher.Event.NonCanonicalIFE.t()\n          | OMG.Watcher.Event.UnchallengedNonCanonicalIFE.t()\n          | OMG.Watcher.Event.InvalidIFEChallenge.t()\n          | OMG.Watcher.Event.PiggybackAvailable.t()\n          | OMG.Watcher.Event.InvalidPiggyback.t()\n          | OMG.Watcher.Event.UnchallengedPiggyback.t()\n\n  @type t ::\n          OMG.Watcher.Event.AddressReceived.t()\n          | OMG.Watcher.Event.ExitFinalized.t()\n          | byzantine_t()\n\n  @type module_t ::\n          OMG.Watcher.Event.InvalidBlock\n          | OMG.Watcher.Event.BlockWithholding\n          | OMG.Watcher.Event.InvalidExit\n          | OMG.Watcher.Event.UnchallengedExit\n          | OMG.Watcher.Event.NonCanonicalIFE\n          | OMG.Watcher.Event.UnchallengedNonCanonicalIFE.t()\n          | OMG.Watcher.Event.InvalidIFEChallenge\n          | OMG.Watcher.Event.PiggybackAvailable\n          | OMG.Watcher.Event.InvalidPiggyback\n          | OMG.Watcher.Event.UnchallengedPiggyback\n          | OMG.Watcher.Event.AddressReceived\n          | OMG.Watcher.Event.ExitFinalized\n  #  TODO The reason why events have name as String and byzantine events as atom is that\n  #  Phoniex websockets requires topics as strings + currently we treat Strings and binaries in\n  #  the same way in `OMG.Watcher.Web.Serializers.Response`\n  defmodule AddressReceived do\n    @moduledoc \"\"\"\n    Notifies about received funds by particular address\n    \"\"\"\n\n    defstruct [:tx, :child_blknum, :child_txindex, :child_block_hash, :submited_at_ethheight]\n\n    @type t :: %__MODULE__{\n            tx: Transaction.Recovered.t(),\n            child_blknum: pos_integer(),\n            child_txindex: non_neg_integer(),\n            child_block_hash: Block.block_hash_t(),\n            submited_at_ethheight: pos_integer()\n          }\n  end\n\n  defmodule AddressSpent do\n    @moduledoc \"\"\"\n    Notifies about spent funds by particular address\n    \"\"\"\n\n    defstruct [:tx, :child_blknum, :child_txindex, :child_block_hash, :submited_at_ethheight]\n\n    @type t :: %__MODULE__{\n            tx: Transaction.Recovered.t(),\n            child_blknum: pos_integer(),\n            child_txindex: non_neg_integer(),\n            child_block_hash: Block.block_hash_t(),\n            submited_at_ethheight: pos_integer()\n          }\n  end\n\n  defmodule ExitFinalized do\n    @moduledoc \"\"\"\n    Notifies about finalized exit\n    \"\"\"\n\n    defstruct [:currency, :amount, :child_blknum, :child_txindex, :child_oindex]\n\n    @type t :: %__MODULE__{\n            currency: Crypto.address_t(),\n            amount: non_neg_integer(),\n            child_blknum: non_neg_integer(),\n            child_txindex: non_neg_integer(),\n            child_oindex: non_neg_integer()\n          }\n  end\n\n  defmodule InvalidBlock do\n    @moduledoc \"\"\"\n    Notifies about invalid block\n    \"\"\"\n\n    defstruct [:hash, :blknum, :error_type, name: :invalid_block]\n\n    @type t :: %__MODULE__{\n            hash: Block.block_hash_t(),\n            blknum: integer(),\n            error_type: atom(),\n            name: atom()\n          }\n  end\n\n  defmodule BlockWithholding do\n    @moduledoc \"\"\"\n    Notifies about block-withholding\n    \"\"\"\n\n    defstruct [:blknum, :hash, name: :block_withholding]\n\n    @type t :: %__MODULE__{\n            blknum: pos_integer(),\n            hash: Block.block_hash_t(),\n            name: atom()\n          }\n  end\n\n  defmodule InvalidExit do\n    @moduledoc \"\"\"\n    Notifies about invalid exit\n    \"\"\"\n\n    defstruct [\n      :amount,\n      :currency,\n      :eth_height,\n      :owner,\n      :root_chain_txhash,\n      :scheduled_finalization_time,\n      :utxo_pos,\n      :spending_txhash,\n      name: :invalid_exit\n    ]\n\n    @type t :: %__MODULE__{\n            amount: pos_integer(),\n            currency: binary(),\n            owner: binary(),\n            utxo_pos: pos_integer(),\n            eth_height: pos_integer(),\n            name: atom(),\n            root_chain_txhash: Transaction.tx_hash() | nil,\n            scheduled_finalization_time: pos_integer() | nil,\n            spending_txhash: Transaction.tx_hash() | nil,\n            root_chain_txhash: Transaction.tx_hash() | nil,\n            name: atom()\n          }\n  end\n\n  defmodule UnchallengedExit do\n    @moduledoc \"\"\"\n    Notifies about an invalid exit, that is dangerously approaching finalization, without being challenged\n\n    It is a prompt to exit\n    \"\"\"\n\n    defstruct [\n      :amount,\n      :currency,\n      :eth_height,\n      :owner,\n      :root_chain_txhash,\n      :scheduled_finalization_time,\n      :utxo_pos,\n      :spending_txhash,\n      name: :unchallenged_exit\n    ]\n\n    @type t :: %__MODULE__{\n            amount: pos_integer(),\n            currency: binary(),\n            owner: binary(),\n            utxo_pos: pos_integer(),\n            eth_height: pos_integer(),\n            name: atom(),\n            root_chain_txhash: Transaction.tx_hash() | nil,\n            scheduled_finalization_time: pos_integer() | nil,\n            root_chain_txhash: Transaction.tx_hash() | nil,\n            spending_txhash: Transaction.tx_hash() | nil,\n            name: atom()\n          }\n  end\n\n  defmodule NonCanonicalIFE do\n    @moduledoc \"\"\"\n    Notifies about an in-flight exit which has a competitor\n    \"\"\"\n\n    defstruct [:txbytes, name: :non_canonical_ife]\n\n    @type t :: %__MODULE__{\n            txbytes: binary(),\n            name: atom()\n          }\n  end\n\n  defmodule UnchallengedNonCanonicalIFE do\n    @moduledoc \"\"\"\n    Notifies about an in-flight exit which has a competitor but is dangerously close to finalization.\n\n    It is a prompt to exit\n    \"\"\"\n\n    defstruct [:txbytes, name: :unchallenged_non_canonical_ife]\n\n    @type t :: %__MODULE__{\n            txbytes: binary(),\n            name: atom()\n          }\n  end\n\n  defmodule InvalidIFEChallenge do\n    @moduledoc \"\"\"\n    Notifies that a canonical in-flight exit has been challenged. The challenge should be responded to.\n    \"\"\"\n\n    defstruct [:txbytes, name: :invalid_ife_challenge]\n\n    @type t :: %__MODULE__{\n            txbytes: binary(),\n            name: atom()\n          }\n  end\n\n  defmodule PiggybackAvailable do\n    @moduledoc \"\"\"\n    Notifies about an available piggyback.\n    It is only fired, when the transaction hasn't been seen included.\n    \"\"\"\n\n    defstruct [:txbytes, :available_outputs, :available_inputs, name: :piggyback_available]\n\n    @type available_output :: %{index: pos_integer(), address: binary()}\n\n    @type t :: %__MODULE__{\n            txbytes: binary(),\n            available_outputs: list(available_output()),\n            available_inputs: list(available_output()),\n            name: atom()\n          }\n  end\n\n  defmodule InvalidPiggyback do\n    @moduledoc \"\"\"\n    Notifies about invalid piggyback. Piggyback is invalid if it is on input and that particular\n    input was double-spend in other transaction (or other in-flight exit) or if it is on output\n    that was spent on plasma chain.\n    \"\"\"\n\n    defstruct [:txbytes, :inputs, :outputs, name: :invalid_piggyback]\n\n    @type t :: %__MODULE__{\n            txbytes: binary(),\n            inputs: [non_neg_integer()],\n            outputs: [non_neg_integer()],\n            name: atom()\n          }\n  end\n\n  defmodule UnchallengedPiggyback do\n    @moduledoc \"\"\"\n    Notifies about invalid piggyback, that is dangerously approaching finalization, without being challenged\n\n    It is a prompt to exit\n    \"\"\"\n\n    defstruct [:txbytes, :inputs, :outputs, name: :unchallenged_piggyback]\n\n    @type t :: %__MODULE__{\n            txbytes: binary(),\n            inputs: [non_neg_integer()],\n            outputs: [non_neg_integer()],\n            name: atom()\n          }\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/exit_processor/canonicity.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.ExitProcessor.Canonicity do\n  @moduledoc \"\"\"\n  Encapsulates managing and executing the behaviors related to treating exits by the child chain and watchers\n  Keeps a state of exits that are in progress, updates it with news from the root chain, compares to the\n  state of the ledger (`OMG.Watcher.State`), issues notifications as it finds suitable.\n\n  Should manage all kinds of exits allowed in the protocol and handle the interactions between them.\n\n  This is the functional, zero-side-effect part of the exit processor. Logic should go here:\n    - orchestrating the persistence of the state\n    - finding invalid exits, disseminating them as events according to rules\n    - enabling to challenge invalid exits\n    - figuring out critical failure of invalid exit challenging (aka `:unchallenged_exit` event)\n    - MoreVP protocol managing in general\n\n  For the imperative shell, see `OMG.Watcher.ExitProcessor`\n  \"\"\"\n\n  alias OMG.Watcher.Block\n  alias OMG.Watcher.Crypto\n  alias OMG.Watcher.Event\n  alias OMG.Watcher.ExitProcessor\n  alias OMG.Watcher.ExitProcessor.Core\n  alias OMG.Watcher.ExitProcessor.DoubleSpend\n  alias OMG.Watcher.ExitProcessor.InFlightExitInfo\n  alias OMG.Watcher.ExitProcessor.KnownTx\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.Utxo\n\n  import OMG.Watcher.ExitProcessor.Tools\n\n  require Utxo\n  require Logger\n\n  @type competitor_data_t :: %{\n          input_txbytes: binary(),\n          input_utxo_pos: Utxo.Position.t(),\n          in_flight_txbytes: binary(),\n          in_flight_input_index: non_neg_integer(),\n          competing_txbytes: binary(),\n          competing_input_index: non_neg_integer(),\n          competing_sig: Crypto.sig_t(),\n          competing_tx_pos: nil | Utxo.Position.t(),\n          competing_proof: binary()\n        }\n\n  @type prove_canonical_data_t :: %{\n          in_flight_txbytes: binary(),\n          in_flight_tx_pos: Utxo.Position.t(),\n          in_flight_proof: binary()\n        }\n\n  @doc \"\"\"\n  Returns a tuple with byzantine events: first element is a list of events for ifes with competitor\n  and the second is the same list filtered for late ifes past sla margin\n  \"\"\"\n  @spec get_ife_txs_with_competitors(Core.t(), KnownTx.known_txs_by_input_t(), pos_integer()) ::\n          {list(Event.NonCanonicalIFE.t()), list(Event.UnchallengedNonCanonicalIFE.t())}\n  def get_ife_txs_with_competitors(state, known_txs_by_input, eth_height_now) do\n    non_canonical_ifes =\n      state.in_flight_exits\n      |> Map.values()\n      |> Stream.map(fn ife -> {ife, DoubleSpend.find_competitor(known_txs_by_input, ife.tx)} end)\n      |> Stream.filter(fn {_ife, maybe_competitor} -> !is_nil(maybe_competitor) end)\n      |> Stream.filter(fn {ife, %DoubleSpend{known_tx: %KnownTx{utxo_pos: utxo_pos}}} ->\n        InFlightExitInfo.is_viable_competitor?(ife, utxo_pos)\n      end)\n\n    non_canonical_ife_events =\n      non_canonical_ifes\n      |> Stream.map(fn {ife, _double_spend} -> Transaction.raw_txbytes(ife.tx) end)\n      |> Enum.uniq()\n      |> Enum.map(fn txbytes -> %Event.NonCanonicalIFE{txbytes: txbytes} end)\n\n    past_sla_margin = fn {ife, _double_spend} ->\n      ife.eth_height + state.sla_margin <= eth_height_now\n    end\n\n    late_non_canonical_ife_events =\n      non_canonical_ifes\n      |> Stream.filter(past_sla_margin)\n      |> Stream.map(fn {ife, _double_spend} -> Transaction.raw_txbytes(ife.tx) end)\n      |> Enum.uniq()\n      |> Enum.map(fn txbytes -> %Event.UnchallengedNonCanonicalIFE{txbytes: txbytes} end)\n\n    {non_canonical_ife_events, late_non_canonical_ife_events}\n  end\n\n  @doc \"\"\"\n  Returns byzantine events for open IFEs that were challenged with an invalid challenge\n  \"\"\"\n  @spec get_invalid_ife_challenges(Core.t()) :: list(Event.InvalidIFEChallenge.t())\n  def get_invalid_ife_challenges(%Core{in_flight_exits: ifes}) do\n    ifes\n    |> Map.values()\n    |> Stream.filter(&InFlightExitInfo.is_invalidly_challenged?/1)\n    |> Stream.map(&Transaction.raw_txbytes(&1.tx))\n    |> Enum.uniq()\n    |> Enum.map(fn txbytes -> %Event.InvalidIFEChallenge{txbytes: txbytes} end)\n  end\n\n  @doc \"\"\"\n  Gets the root chain contract-required set of data to challenge a non-canonical ife\n  \"\"\"\n  @spec get_competitor_for_ife(ExitProcessor.Request.t(), Core.t(), binary()) ::\n          {:ok, competitor_data_t()}\n          | {:error, :competitor_not_found}\n          | {:error, :ife_not_known_for_tx}\n          | {:error, :no_viable_competitor_found}\n          | {:error, Transaction.decode_error()}\n  def get_competitor_for_ife(\n        %ExitProcessor.Request{blocks_result: blocks},\n        %Core{} = state,\n        ife_txbytes\n      ) do\n    known_txs_by_input = KnownTx.get_all_from_blocks_appendix(blocks, state)\n    # find its competitor and use it to prepare the requested data\n    with {:ok, ife_tx} <- Transaction.decode(ife_txbytes),\n         {:ok, ife} <- get_ife(ife_tx, state.in_flight_exits),\n         {:ok, double_spend} <- get_competitor(known_txs_by_input, ife.tx),\n         %DoubleSpend{known_tx: %KnownTx{utxo_pos: utxo_pos}} = double_spend,\n         true <- check_viable_competitor(ife, utxo_pos),\n         do: {:ok, prepare_competitor_response(double_spend, ife, blocks)}\n  end\n\n  @doc \"\"\"\n  Gets the root chain contract-required set of data to challenge an ife appearing as non-canonical in the root chain\n  contract but which is known to be canonical locally because included in one of the blocks\n  \"\"\"\n  @spec prove_canonical_for_ife(Core.t(), binary()) ::\n          {:ok, prove_canonical_data_t()} | {:error, :no_viable_canonical_proof_found}\n  def prove_canonical_for_ife(%Core{} = state, ife_txbytes) do\n    with {:ok, raw_ife_tx} <- Transaction.decode(ife_txbytes),\n         {:ok, ife} <- get_ife(raw_ife_tx, state.in_flight_exits),\n         true <- check_is_invalidly_challenged(ife),\n         do: {:ok, prepare_canonical_response(ife)}\n  end\n\n  defp prepare_competitor_response(\n         %DoubleSpend{\n           index: in_flight_input_index,\n           known_spent_index: competing_input_index,\n           known_tx: %KnownTx{signed_tx: known_signed_tx, utxo_pos: known_tx_utxo_pos}\n         },\n         %InFlightExitInfo{tx: signed_ife_tx} = ife,\n         blocks\n       ) do\n    {:ok, input_witnesses} = Transaction.Signed.get_witnesses(signed_ife_tx)\n    owner = input_witnesses[in_flight_input_index]\n\n    %{\n      input_tx: Enum.at(ife.input_txs, in_flight_input_index),\n      input_utxo_pos: Enum.at(ife.input_utxos_pos, in_flight_input_index),\n      in_flight_txbytes: signed_ife_tx |> Transaction.raw_txbytes(),\n      in_flight_input_index: in_flight_input_index,\n      competing_txbytes: known_signed_tx |> Transaction.raw_txbytes(),\n      competing_input_index: competing_input_index,\n      competing_sig: find_sig!(known_signed_tx, owner),\n      competing_tx_pos: known_tx_utxo_pos || Utxo.position(0, 0, 0),\n      competing_proof: maybe_calculate_proof(known_tx_utxo_pos, blocks)\n    }\n  end\n\n  defp prepare_canonical_response(%InFlightExitInfo{tx: tx, tx_seen_in_blocks_at: {pos, proof}}),\n    do: %{in_flight_txbytes: Transaction.raw_txbytes(tx), in_flight_tx_pos: pos, in_flight_proof: proof}\n\n  defp maybe_calculate_proof(nil, _), do: <<>>\n\n  defp maybe_calculate_proof(Utxo.position(blknum, txindex, _), blocks) do\n    blocks\n    |> Enum.find(fn %Block{number: number} -> blknum == number end)\n    |> Block.inclusion_proof(txindex)\n  end\n\n  defp get_competitor(known_txs_by_input, signed_ife_tx) do\n    known_txs_by_input\n    |> DoubleSpend.find_competitor(signed_ife_tx)\n    |> case do\n      nil -> {:error, :competitor_not_found}\n      value -> {:ok, value}\n    end\n  end\n\n  defp check_viable_competitor(ife, utxo_pos),\n    do: if(InFlightExitInfo.is_viable_competitor?(ife, utxo_pos), do: true, else: {:error, :no_viable_competitor_found})\n\n  defp check_is_invalidly_challenged(ife),\n    do: if(InFlightExitInfo.is_invalidly_challenged?(ife), do: true, else: {:error, :no_viable_canonical_proof_found})\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/exit_processor/competitor_info.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.ExitProcessor.CompetitorInfo do\n  @moduledoc \"\"\"\n  Represents the bulk of information about a competitor to an IFE.\n\n  Internal stuff of `OMG.Watcher.ExitProcessor`\n  \"\"\"\n\n  alias OMG.Watcher.Crypto\n  alias OMG.Watcher.ExitProcessor.InFlightExitInfo\n  alias OMG.Watcher.State.Transaction\n\n  # mapped by tx_hash\n  defstruct [\n    :tx,\n    # TODO: what if someone does challenges once more but with another input?\n    :competing_input_index,\n    :competing_input_signature\n  ]\n\n  # NOTE: Although `Transaction.Signed` is used here, not all inputs will have signatures in this construct\n  #       Still, we do use it, because it is formally correct - it is just not a valid transaction from the POV of\n  #       the ledger\n  @type t :: %__MODULE__{\n          tx: Transaction.Signed.t(),\n          competing_input_index: Transaction.input_index_t(),\n          competing_input_signature: Crypto.sig_t()\n        }\n\n  # NOTE: we have no migrations, so we handle data compatibility here (make_db_update/1 and from_db_kv/1), OMG-421\n  def make_db_update(\n        {tx_hash,\n         %__MODULE__{\n           tx: tx = %Transaction.Signed{},\n           competing_input_index: input_index,\n           competing_input_signature: signature\n         }}\n      )\n      when is_integer(input_index) and is_binary(signature) do\n    value = %{\n      tx: InFlightExitInfo.to_db_value(tx),\n      competing_input_index: input_index,\n      competing_input_signature: signature\n    }\n\n    {:put, :competitor_info, {tx_hash, value}}\n  end\n\n  def from_db_kv({tx_hash, %{tx: signed_tx_map, competing_input_index: index, competing_input_signature: signature}})\n      when is_map(signed_tx_map) and is_integer(index) and is_binary(signature) do\n    tx = InFlightExitInfo.from_db_signed_tx(signed_tx_map)\n\n    competitor_map = %{\n      tx: tx,\n      competing_input_index: index,\n      competing_input_signature: signature\n    }\n\n    {tx_hash, struct!(__MODULE__, competitor_map)}\n  end\n\n  def new(%{call_data: %{competing_tx: tx_bytes, competing_tx_input_index: index, competing_tx_sig: sig}}),\n    do: do_new(tx_bytes, index, sig)\n\n  defp do_new(tx_bytes, competing_input_index, competing_input_signature) do\n    with {:ok, %Transaction.Payment{} = raw_tx} <- Transaction.decode(tx_bytes) do\n      {Transaction.raw_txhash(raw_tx),\n       %__MODULE__{\n         tx: %Transaction.Signed{\n           raw_tx: raw_tx,\n           sigs: [competing_input_signature]\n         },\n         competing_input_index: competing_input_index,\n         competing_input_signature: competing_input_signature\n       }}\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/exit_processor/core.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.ExitProcessor.Core do\n  @moduledoc \"\"\"\n  Logic related to treating exits by the Watcher.\n\n  This is the functional, zero-side-effect part of the exit processor. Logic should go here:\n    - orchestrating the persistence of the state\n    - finding invalid exits, disseminating them as events according to rules\n    - enabling to challenge invalid exits\n    - figuring out critical failure of invalid exit challenging (aka `:unchallenged_exit` event)\n    - MoreVP protocol managing in general\n\n  This is the functional logic driving the `GenServer` in `OMG.Watcher.ExitProcessor`\n  \"\"\"\n\n  alias OMG.Watcher.Block\n  alias OMG.Watcher.Event\n  alias OMG.Watcher.ExitProcessor\n  alias OMG.Watcher.ExitProcessor.CompetitorInfo\n  alias OMG.Watcher.ExitProcessor.ExitInfo\n  alias OMG.Watcher.ExitProcessor.InFlightExitInfo\n  alias OMG.Watcher.ExitProcessor.KnownTx\n  alias OMG.Watcher.ExitProcessor.StandardExit\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.Utxo\n\n  import OMG.Watcher.ExitProcessor.Tools\n\n  require Utxo\n  require Transaction.Payment\n\n  require Logger\n\n  @default_sla_margin 10\n\n  @zero_address <<0::160>>\n\n  @max_inputs Transaction.Payment.max_inputs()\n  @max_outputs Transaction.Payment.max_outputs()\n\n  @type new_in_flight_exit_status_t() :: {tuple(), pos_integer()}\n\n  @type piggyback_input_index_t() :: 0..unquote(@max_inputs - 1)\n  @type piggyback_output_index_t() :: 0..unquote(@max_outputs - 1)\n\n  @type new_piggyback_input_event_t() :: %{\n          tx_hash: Transaction.tx_hash(),\n          output_index: piggyback_input_index_t(),\n          omg_data: %{piggyback_type: :input}\n        }\n  @type new_piggyback_output_event_t() :: %{\n          tx_hash: Transaction.tx_hash(),\n          output_index: piggyback_output_index_t(),\n          omg_data: %{piggyback_type: :output}\n        }\n\n  @type new_piggyback_event_t() :: new_piggyback_input_event_t() | new_piggyback_output_event_t()\n\n  defstruct [\n    :sla_margin,\n    :min_exit_period_seconds,\n    :child_block_interval,\n    exits: %{},\n    in_flight_exits: %{},\n    exit_ids: %{},\n    competitors: %{}\n  ]\n\n  @type t :: %__MODULE__{\n          sla_margin: non_neg_integer(),\n          exits: %{Utxo.Position.t() => ExitInfo.t()},\n          in_flight_exits: %{Transaction.tx_hash() => InFlightExitInfo.t()},\n          # NOTE: maps only standard exit_ids to the natural keys of standard exits (input pointers/utxo_pos)\n          #       rethink the approach to the keys in the data structures - how to manage exit_ids? should the contract\n          #       serve more data (e.g. input pointers/tx hashes) where it would normally only serve exit_ids?\n          exit_ids: %{non_neg_integer() => Utxo.Position.t()},\n          competitors: %{Transaction.tx_hash() => CompetitorInfo.t()},\n          min_exit_period_seconds: non_neg_integer(),\n          child_block_interval: non_neg_integer()\n        }\n\n  @type check_validity_result_t :: {:ok | {:error, :unchallenged_exit}, list(Event.byzantine_t())}\n\n  @type spent_blknum_result_t() :: {:ok, pos_integer} | :not_found\n\n  @type in_flight_exit_response_t() :: %{\n          txhash: binary(),\n          txbytes: binary(),\n          eth_height: non_neg_integer(),\n          piggybacked_inputs: list(non_neg_integer()),\n          piggybacked_outputs: list(non_neg_integer())\n        }\n  @type in_flight_exits_response_t() :: %{binary() => in_flight_exit_response_t()}\n\n  @doc \"\"\"\n  Reads database-specific list of exits and turns them into current state\n  \"\"\"\n  @spec init(\n          db_exits :: [{{pos_integer, non_neg_integer, non_neg_integer}, map}],\n          db_in_flight_exits :: [{Transaction.tx_hash(), InFlightExitInfo.t()}],\n          db_competitors :: [{Transaction.tx_hash(), CompetitorInfo.t()}],\n          min_exit_period_seconds :: non_neg_integer(),\n          child_block_interval :: non_neg_integer,\n          sla_margin :: non_neg_integer\n        ) :: {:ok, t()}\n  def init(\n        db_exits,\n        db_in_flight_exits,\n        db_competitors,\n        min_exit_period_seconds,\n        child_block_interval,\n        sla_margin \\\\ @default_sla_margin\n      ) do\n    exits = db_exits |> Enum.map(&ExitInfo.from_db_kv/1) |> Map.new()\n\n    exit_ids = Enum.into(exits, %{}, fn {utxo_pos, %ExitInfo{exit_id: exit_id}} -> {exit_id, utxo_pos} end)\n\n    {:ok,\n     %__MODULE__{\n       exits: exits,\n       in_flight_exits: db_in_flight_exits |> Enum.map(&InFlightExitInfo.from_db_kv/1) |> Map.new(),\n       exit_ids: exit_ids,\n       competitors: db_competitors |> Enum.map(&CompetitorInfo.from_db_kv/1) |> Map.new(),\n       sla_margin: sla_margin,\n       min_exit_period_seconds: min_exit_period_seconds,\n       child_block_interval: child_block_interval\n     }}\n  end\n\n  @doc \"\"\"\n  Use to check if the settings regarding the `:exit_processor_sla_margin` config of `:omg_watcher` are OK.\n\n  Since there are combinations of our configuration that may lead to a dangerous setup of the Watcher\n  (in particular - muting the reports of unchallenged_exits), we're enforcing that the `exit_processor_sla_margin`\n  be not larger than `min_exit_period`.\n  \"\"\"\n  @spec check_sla_margin(pos_integer(), boolean(), pos_integer(), pos_integer()) :: :ok | {:error, :sla_margin_too_big}\n  def check_sla_margin(sla_margin, sla_margin_forced, min_exit_period_seconds, ethereum_block_time_seconds)\n\n  def check_sla_margin(sla_margin, true, min_exit_period_seconds, ethereum_block_time_seconds) do\n    _ =\n      if !sla_margin_safe?(sla_margin, min_exit_period_seconds, ethereum_block_time_seconds),\n        do: Logger.warn(\"Allowing unsafe sla margin of #{sla_margin} blocks\")\n\n    :ok\n  end\n\n  def check_sla_margin(sla_margin, false, min_exit_period_seconds, ethereum_block_time_seconds) do\n    if sla_margin_safe?(sla_margin, min_exit_period_seconds, ethereum_block_time_seconds),\n      do: :ok,\n      else: {:error, :sla_margin_too_big}\n  end\n\n  def exit_key_by_exit_id(%__MODULE__{exit_ids: exit_ids}, exit_id), do: exit_ids[exit_id]\n\n  @doc \"\"\"\n  Add new exits from Ethereum events into tracked state.\n\n  The list of `exit_contract_statuses` is used to track current (as in wall-clock \"now\", not syncing \"now\") status.\n  This is to prevent spurious invalid exit events being fired during syncing for exits that were challenged/finalized\n  Still we do want to track these exits when syncing, to have them spend from `OMG.Watcher.State` on their finalization\n  \"\"\"\n  @spec new_exits(t(), list(map()), list(map)) :: {t(), list()} | {:error, :unexpected_events}\n  def new_exits(state, new_exits, exit_contract_statuses)\n\n  def new_exits(_, new_exits, exit_contract_statuses) when length(new_exits) != length(exit_contract_statuses) do\n    {:error, :unexpected_events}\n  end\n\n  def new_exits(%__MODULE__{exits: exits, exit_ids: exit_ids} = state, new_exits, exit_contract_statuses) do\n    new_exits_kv_pairs =\n      new_exits\n      |> Enum.zip(exit_contract_statuses)\n      |> Enum.map(fn {event, contract_status} ->\n        {ExitInfo.new_key(contract_status, event), ExitInfo.new(contract_status, event)}\n      end)\n\n    db_updates = new_exits_kv_pairs |> Enum.map(&ExitInfo.make_db_update/1)\n    new_exits_map = Map.new(new_exits_kv_pairs)\n\n    new_exit_ids_map =\n      new_exits_map |> Enum.into(%{}, fn {utxo_pos, %ExitInfo{exit_id: exit_id}} -> {exit_id, utxo_pos} end)\n\n    {%{state | exits: Map.merge(exits, new_exits_map), exit_ids: Map.merge(exit_ids, new_exit_ids_map)}, db_updates}\n  end\n\n  defdelegate finalize_exits(state, validities), to: ExitProcessor.Finalizations\n  defdelegate prepare_utxo_exits_for_in_flight_exit_finalizations(state, finalizations), to: ExitProcessor.Finalizations\n  defdelegate finalize_in_flight_exits(state, finalizations, validities), to: ExitProcessor.Finalizations\n\n  @spec challenge_exits(t(), list(map)) :: {t(), list}\n  def challenge_exits(%__MODULE__{exits: exits} = state, challenges) do\n    challenged_positions = get_positions_from_events(challenges)\n\n    new_exits_kv_pairs =\n      exits\n      |> Map.take(challenged_positions)\n      |> Enum.into(%{}, fn {utxo_pos, exit_info} -> {utxo_pos, %ExitInfo{exit_info | is_active: false}} end)\n\n    new_state = %{state | exits: Map.merge(exits, new_exits_kv_pairs)}\n    db_updates = new_exits_kv_pairs |> Enum.map(&ExitInfo.make_db_update/1)\n    {new_state, db_updates}\n  end\n\n  defp get_positions_from_events(exits) do\n    exits\n    |> Enum.map(fn %{utxo_pos: utxo_pos} = _finalization_info -> Utxo.Position.decode!(utxo_pos) end)\n  end\n\n  @doc \"\"\"\n  Add new in flight exits from Ethereum events into tracked state.\n  \"\"\"\n  @spec new_in_flight_exits(t(), list(map()), list(new_in_flight_exit_status_t())) ::\n          {t(), list()} | {:error, :unexpected_events}\n  def new_in_flight_exits(state, new_ifes_events, contract_statuses)\n\n  def new_in_flight_exits(_state, new_ifes_events, contract_statuses)\n      when length(new_ifes_events) != length(contract_statuses),\n      do: {:error, :unexpected_events}\n\n  def new_in_flight_exits(%__MODULE__{in_flight_exits: ifes} = state, new_ifes_events, contract_statuses) do\n    new_ifes =\n      new_ifes_events\n      |> Enum.zip(contract_statuses)\n      |> Enum.map(fn {event, contract_status} -> InFlightExitInfo.new_kv(event, contract_status) end)\n      |> Map.new()\n\n    updated_state = %{state | in_flight_exits: Map.merge(ifes, new_ifes)}\n    updated_ife_keys = new_ifes |> Enum.unzip() |> elem(0)\n\n    db_updates = ife_db_updates(updated_state, updated_ife_keys)\n\n    {updated_state, db_updates}\n  end\n\n  defp ife_db_updates(%__MODULE__{in_flight_exits: ifes}, updated_ife_keys) do\n    ifes\n    |> Map.take(Enum.to_list(updated_ife_keys))\n    |> Enum.map(&InFlightExitInfo.make_db_update/1)\n  end\n\n  @doc \"\"\"\n    Add piggybacks from Ethereum events into tracked state.\n  \"\"\"\n  @spec new_piggybacks(t(), list(new_piggyback_event_t())) :: {t(), list()}\n  def new_piggybacks(%__MODULE__{} = state, piggyback_events) when is_list(piggyback_events) do\n    event_field_f = fn event -> {event[:omg_data][:piggyback_type], event[:output_index]} end\n    consume_events(state, piggyback_events, event_field_f, &InFlightExitInfo.piggyback/2)\n  end\n\n  @spec new_ife_challenges(t(), [map()]) :: {t(), list()}\n  def new_ife_challenges(%__MODULE__{} = state, challenges_events) do\n    {updated_state, ife_db_updates} =\n      consume_events(state, challenges_events, & &1[:competitor_position], &InFlightExitInfo.challenge/2)\n\n    {updated_state2, competitors_db_updates} = append_new_competitors(updated_state, challenges_events)\n    {updated_state2, competitors_db_updates ++ ife_db_updates}\n  end\n\n  defp append_new_competitors(%__MODULE__{competitors: competitors} = state, challenges_events) do\n    new_competitors = challenges_events |> Enum.map(&CompetitorInfo.new/1)\n    db_updates = new_competitors |> Enum.map(&CompetitorInfo.make_db_update/1)\n\n    {%{state | competitors: Map.merge(competitors, Map.new(new_competitors))}, db_updates}\n  end\n\n  @spec respond_to_in_flight_exits_challenges(t(), [map()]) :: {t(), list()}\n  def respond_to_in_flight_exits_challenges(%__MODULE__{} = state, responds_events) do\n    consume_events(state, responds_events, & &1[:challenge_position], &InFlightExitInfo.respond_to_challenge/2)\n  end\n\n  @spec challenge_piggybacks(t(), [map()]) :: {t(), list()}\n  def challenge_piggybacks(%__MODULE__{} = state, challenges) do\n    event_field_f = fn event -> {event[:omg_data][:piggyback_type], event[:output_index]} end\n    consume_events(state, challenges, event_field_f, &InFlightExitInfo.challenge_piggyback/2)\n  end\n\n  # produces new state and some db_updates based on\n  #   - an enumerable of Ethereum events with tx_hash and some field\n  #   - name of that other field\n  #   - a function operating on a single IFE structure and that fields value\n  # Leverages the fact, that operating on various IFE-related events follows the same pattern\n  defp consume_events(%__MODULE__{} = state, events, event_field_f, ife_f) do\n    processing_f = process_reducing_events_f(event_field_f, ife_f)\n    {updated_state, updated_ife_keys} = Enum.reduce(events, {state, MapSet.new()}, processing_f)\n    db_updates = ife_db_updates(updated_state, updated_ife_keys)\n    {updated_state, db_updates}\n  end\n\n  # produces an `Enum.reduce`-able function that: grabs an IFE by tx_hash from the event, invokes a function on that\n  # using the value of a different field from the event, returning updated state and mapset of modified keys.\n  # Pseudocode:\n  # `event |> get IFE by tx_hash |> ife_f.(event[event_field])`\n  defp process_reducing_events_f(event_field_f, ife_f) do\n    fn event, {%__MODULE__{in_flight_exits: ifes} = state, updated_ife_keys} ->\n      tx_hash = event.tx_hash\n      event_field_value = event_field_f.(event)\n      updated_ife = ifes |> Map.fetch!(tx_hash) |> ife_f.(event_field_value)\n\n      updated_state = %{state | in_flight_exits: Map.put(ifes, tx_hash, updated_ife)}\n      {updated_state, MapSet.put(updated_ife_keys, tx_hash)}\n    end\n  end\n\n  @doc \"\"\"\n  Only for the active in-flight exits, based on the current tracked state.\n  Only for IFEs which transactions where included into the chain and whose outputs were potentially spent.\n\n  Compare with determine_utxo_existence_to_get/2.\n  \"\"\"\n  @spec determine_ife_input_utxos_existence_to_get(ExitProcessor.Request.t(), t()) :: ExitProcessor.Request.t()\n  def determine_ife_input_utxos_existence_to_get(\n        %ExitProcessor.Request{blknum_now: blknum_now} = request,\n        %__MODULE__{in_flight_exits: ifes}\n      )\n      when not is_nil(blknum_now) do\n    ife_input_positions =\n      ifes\n      |> Map.values()\n      |> Enum.filter(&InFlightExitInfo.should_be_seeked_in_blocks?/1)\n      |> Enum.filter(&InFlightExitInfo.is_relevant?(&1, blknum_now))\n      |> Enum.flat_map(&Transaction.get_inputs(&1.tx))\n      |> :lists.usort()\n\n    %{request | ife_input_utxos_to_check: ife_input_positions}\n  end\n\n  @doc \"\"\"\n  All the active exits, in-flight exits, exiting output piggybacks etc., based on the current tracked state\n  \"\"\"\n  @spec determine_utxo_existence_to_get(ExitProcessor.Request.t(), t()) :: ExitProcessor.Request.t()\n  def determine_utxo_existence_to_get(\n        %ExitProcessor.Request{blknum_now: blknum_now} = request,\n        %__MODULE__{} = state\n      )\n      when not is_nil(blknum_now) do\n    %{request | utxos_to_check: do_determine_utxo_existence_to_get(state, blknum_now)}\n  end\n\n  defp do_determine_utxo_existence_to_get(%__MODULE__{in_flight_exits: ifes} = state, blknum_now) do\n    standard_exits_pos =\n      StandardExit.exiting_positions(state)\n      |> Enum.filter(fn Utxo.position(blknum, _, _) -> blknum < blknum_now end)\n\n    active_relevant_ifes =\n      ifes\n      |> Map.values()\n      |> Enum.filter(& &1.is_active)\n      |> Enum.filter(&InFlightExitInfo.is_relevant?(&1, blknum_now))\n\n    ife_inputs_pos = active_relevant_ifes |> Enum.flat_map(&Transaction.get_inputs(&1.tx))\n    ife_outputs_pos = active_relevant_ifes |> Enum.flat_map(&InFlightExitInfo.get_active_output_piggybacks_positions/1)\n\n    (ife_outputs_pos ++ ife_inputs_pos ++ standard_exits_pos)\n    |> :lists.usort()\n  end\n\n  @doc \"\"\"\n  Figures out which numbers of \"spending transaction blocks\" to get for the utxos, based on the existence reported by\n  `OMG.Watcher.State` and possibly other factors, eg. only take the non-existent UTXOs spends (naturally) and ones that\n  pertain to IFE transaction inputs.\n\n  Assumes that UTXOs that haven't been checked (i.e. not a key in `utxo_exists?` map) **exist**\n\n  To proceed with validation/proof building, this function must ask for blocks that satisfy following criteria:\n    1/ blocks where any input to any IFE was spent\n    2/ blocks where any output to any IFE was spent\n  \"\"\"\n  @spec determine_spends_to_get(ExitProcessor.Request.t(), __MODULE__.t()) :: ExitProcessor.Request.t()\n  def determine_spends_to_get(\n        %ExitProcessor.Request{\n          utxos_to_check: utxos_to_check,\n          utxo_exists_result: utxo_exists_result\n        } = request,\n        %__MODULE__{in_flight_exits: ifes}\n      ) do\n    utxo_exists? = Enum.zip(utxos_to_check, utxo_exists_result) |> Map.new()\n\n    spends_to_get =\n      ifes\n      |> Map.values()\n      |> Enum.flat_map(fn %{tx: tx} = ife ->\n        InFlightExitInfo.get_active_output_piggybacks_positions(ife) ++ Transaction.get_inputs(tx)\n      end)\n      |> only_utxos_checked_and_missing(utxo_exists?)\n      |> :lists.usort()\n\n    %{request | spends_to_get: spends_to_get}\n  end\n\n  @doc \"\"\"\n  Figures out which numbers of \"spending transaction blocks\" to get for the outputs on IFEs utxos.\n\n  To proceed with validation/proof building, this function must ask for blocks that satisfy following criteria:\n    1/ blocks, where any output from an IFE tx might have been created, by including such IFE tx\n\n  Similar to `determine_spends_to_get`, otherwise.\n  \"\"\"\n  @spec determine_ife_spends_to_get(ExitProcessor.Request.t(), __MODULE__.t()) :: ExitProcessor.Request.t()\n  def determine_ife_spends_to_get(\n        %ExitProcessor.Request{\n          ife_input_utxos_to_check: utxos_to_check,\n          ife_input_utxo_exists_result: utxo_exists_result\n        } = request,\n        %__MODULE__{in_flight_exits: ifes}\n      ) do\n    utxo_exists? = Enum.zip(utxos_to_check, utxo_exists_result) |> Map.new()\n\n    spends_to_get =\n      ifes\n      |> Map.values()\n      |> Enum.flat_map(&Transaction.get_inputs(&1.tx))\n      |> only_utxos_checked_and_missing(utxo_exists?)\n      |> :lists.usort()\n\n    %{request | ife_input_spends_to_get: spends_to_get}\n  end\n\n  @doc \"\"\"\n  Filters out all the spends that have not been found (`:not_found` instead of a block)\n  This might occur if a UTXO is exited by exit finalization. A block spending such UTXO will not exist.\n  \"\"\"\n  @spec handle_spent_blknum_result(list(spent_blknum_result_t()), list(Utxo.Position.t())) :: list(pos_integer())\n  def handle_spent_blknum_result(spent_blknum_result, spent_positions_to_get) do\n    {not_founds, founds} =\n      Stream.zip(spent_positions_to_get, spent_blknum_result)\n      |> Enum.split_with(fn {_utxo_pos, result} -> result == :not_found end)\n\n    blknums_to_get = founds |> Enum.unzip() |> elem(1) |> Enum.map(fn {:ok, blknum} -> blknum end)\n\n    warn? = !Enum.empty?(not_founds)\n    _ = if warn?, do: Logger.warn(\"UTXO doesn't exists but no spend registered (spent in exit?) #{inspect(not_founds)}\")\n\n    Enum.uniq(blknums_to_get)\n  end\n\n  @doc \"\"\"\n  Based on the result of exit validity (utxo existence), return invalid exits or appropriate notifications\n\n  NOTE: We're using `ExitStarted`-height with `sla_exit_margin` added on top, to determine old, unchallenged invalid\n        exits. This is different than documented, according to what we ought to be using\n        `exitable_at - sla_exit_margin_s` to determine such exits.\n\n  NOTE: If there were any exits unchallenged for some time in chain history, this might detect breach of SLA,\n        even if the exits were eventually challenged (e.g. during syncing)\n  \"\"\"\n  @spec check_validity(ExitProcessor.Request.t(), t()) :: check_validity_result_t()\n  def check_validity(\n        %ExitProcessor.Request{\n          eth_height_now: eth_height_now,\n          utxos_to_check: utxos_to_check,\n          utxo_exists_result: utxo_exists_result,\n          blocks_result: blocks\n        },\n        %__MODULE__{} = state\n      )\n      when not is_nil(eth_height_now) do\n    utxo_exists? = Enum.zip(utxos_to_check, utxo_exists_result) |> Map.new()\n\n    {invalid_exits, late_invalid_exits} = StandardExit.get_invalid(state, utxo_exists?, eth_height_now)\n\n    invalid_exit_events =\n      invalid_exits\n      |> Enum.map(fn {position, exit_info} -> ExitInfo.make_event_data(Event.InvalidExit, position, exit_info) end)\n\n    late_invalid_exits_events =\n      late_invalid_exits\n      |> Enum.map(fn {position, late_exit} -> ExitInfo.make_event_data(Event.UnchallengedExit, position, late_exit) end)\n\n    known_txs_by_input = KnownTx.get_all_from_blocks_appendix(blocks, state)\n\n    {non_canonical_ife_events, late_non_canonical_ife_events} =\n      ExitProcessor.Canonicity.get_ife_txs_with_competitors(state, known_txs_by_input, eth_height_now)\n\n    invalid_ife_challenges_events = ExitProcessor.Canonicity.get_invalid_ife_challenges(state)\n\n    {invalid_piggybacks_events, late_invalid_piggybacks_events} =\n      ExitProcessor.Piggyback.get_invalid_piggybacks_events(state, known_txs_by_input, eth_height_now)\n\n    available_piggybacks_events =\n      state\n      |> get_ifes_to_piggyback()\n      |> Enum.flat_map(&prepare_available_piggyback/1)\n\n    unchallenged_exit_events =\n      late_non_canonical_ife_events ++ late_invalid_exits_events ++ late_invalid_piggybacks_events\n\n    chain_validity = if Enum.empty?(unchallenged_exit_events), do: :ok, else: {:error, :unchallenged_exit}\n\n    events =\n      Enum.concat([\n        unchallenged_exit_events,\n        invalid_exit_events,\n        invalid_piggybacks_events,\n        non_canonical_ife_events,\n        invalid_ife_challenges_events,\n        available_piggybacks_events\n      ])\n\n    {chain_validity, events}\n  end\n\n  defdelegate get_competitor_for_ife(request, state, ife_txbytes), to: ExitProcessor.Canonicity\n  defdelegate prove_canonical_for_ife(state, ife_txbytes), to: ExitProcessor.Canonicity\n\n  defdelegate get_input_challenge_data(request, state, txbytes, input_index), to: ExitProcessor.Piggyback\n  defdelegate get_output_challenge_data(request, state, txbytes, output_index), to: ExitProcessor.Piggyback\n\n  defdelegate determine_standard_challenge_queries(request, state, exiting_utxo_exists), to: ExitProcessor.StandardExit\n  defdelegate create_challenge(request, state), to: ExitProcessor.StandardExit\n\n  @spec get_ifes_to_piggyback(t()) :: list(InFlightExitInfo.t())\n  defp get_ifes_to_piggyback(%__MODULE__{in_flight_exits: ifes}) do\n    ifes\n    |> Map.values()\n    |> Stream.filter(fn %InFlightExitInfo{is_active: is_active, tx_seen_in_blocks_at: seen} -> is_active && !seen end)\n    |> Enum.uniq_by(fn %InFlightExitInfo{tx: signed_tx} -> signed_tx end)\n  end\n\n  @spec prepare_available_piggyback(InFlightExitInfo.t()) :: list(Event.PiggybackAvailable.t())\n  defp prepare_available_piggyback(%InFlightExitInfo{tx: signed_tx} = ife) do\n    outputs = Transaction.get_outputs(signed_tx)\n    {:ok, input_witnesses} = Transaction.Signed.get_witnesses(signed_tx)\n\n    available_inputs =\n      input_witnesses\n      |> Enum.filter(fn {index, _} -> not InFlightExitInfo.is_piggybacked?(ife, {:input, index}) end)\n      |> Enum.map(fn {index, owner} -> %{index: index, address: owner} end)\n\n    available_outputs =\n      outputs\n      |> Enum.filter(fn %{owner: owner} -> zero_address?(owner) end)\n      |> Enum.with_index()\n      |> Enum.filter(fn {_, index} -> not InFlightExitInfo.is_piggybacked?(ife, {:output, index}) end)\n      |> Enum.map(fn {%{owner: owner}, index} -> %{index: index, address: owner} end)\n\n    if Enum.empty?(available_inputs) and Enum.empty?(available_outputs) do\n      []\n    else\n      [\n        %Event.PiggybackAvailable{\n          txbytes: Transaction.raw_txbytes(signed_tx),\n          available_outputs: available_outputs,\n          available_inputs: available_inputs\n        }\n      ]\n    end\n  end\n\n  @doc \"\"\"\n  Returns a map of active in flight exits, where keys are IFE hashes and values are IFES\n  \"\"\"\n  @spec get_active_in_flight_exits(__MODULE__.t()) :: list(map)\n  def get_active_in_flight_exits(%__MODULE__{in_flight_exits: ifes}) do\n    ifes\n    |> Enum.filter(fn {_, %InFlightExitInfo{is_active: is_active}} -> is_active end)\n    |> Enum.map(&prepare_in_flight_exit/1)\n  end\n\n  @doc \"\"\"\n  Returns a set of utxo positions for standard exiting utxos\n  \"\"\"\n  @spec active_standard_exiting_utxos(list(map)) :: MapSet.t(Utxo.Position.t())\n  def active_standard_exiting_utxos(db_exits) do\n    db_exits\n    |> Stream.map(&ExitInfo.from_db_kv/1)\n    |> Stream.filter(fn {_, exit_info} -> exit_info.is_active end)\n    |> Enum.map(&Kernel.elem(&1, 0))\n    |> MapSet.new()\n  end\n\n  @doc \"\"\"\n  Returns a set of input's utxo positions for in-flight exiting transactions\n  \"\"\"\n  @spec active_in_flight_exiting_inputs(list(map)) :: MapSet.t(Utxo.Position.t())\n  def active_in_flight_exiting_inputs(db_exits) do\n    db_exits\n    |> Stream.map(&InFlightExitInfo.from_db_kv/1)\n    |> Stream.filter(fn {_, exit_info} -> exit_info.is_active end)\n    |> Enum.flat_map(fn {_, exit_info} -> exit_info.input_utxos_pos end)\n    |> MapSet.new()\n  end\n\n  defp prepare_in_flight_exit({txhash, ife_info}) do\n    %{tx: tx, eth_height: eth_height} = ife_info\n\n    %{\n      txhash: txhash,\n      txbytes: Transaction.raw_txbytes(tx),\n      eth_height: eth_height,\n      piggybacked_inputs: InFlightExitInfo.actively_piggybacked_inputs(ife_info),\n      piggybacked_outputs: InFlightExitInfo.actively_piggybacked_outputs(ife_info)\n    }\n  end\n\n  @doc \"\"\"\n  If IFE's spend is in blocks, find its txpos and update the IFE.\n  Note: this change is not persisted later!\n  \"\"\"\n  def find_ifes_in_blocks(\n        %__MODULE__{in_flight_exits: ifes} = state,\n        %ExitProcessor.Request{ife_input_spending_blocks_result: blocks}\n      ) do\n    # precompute some useful maps first\n    blocks = Enum.filter(blocks, &(&1 != :not_found))\n    positions_by_tx_hash = KnownTx.get_positions_by_txhash(blocks)\n    blocks_by_blknum = KnownTx.get_blocks_by_blknum(blocks)\n\n    new_ifes =\n      ifes\n      |> Enum.filter(fn {_, ife} -> InFlightExitInfo.should_be_seeked_in_blocks?(ife) end)\n      |> Enum.map(fn {hash, ife} ->\n        {hash, ife, KnownTx.find_tx_in_blocks(hash, positions_by_tx_hash, blocks_by_blknum)}\n      end)\n      |> Enum.filter(fn {_hash, _ife, maybepos} -> maybepos != nil end)\n      |> Enum.into(ifes, fn {hash, ife, {block, position}} ->\n        Utxo.position(_, txindex, _) = position\n        proof = Block.inclusion_proof(block, txindex)\n        {hash, %InFlightExitInfo{ife | tx_seen_in_blocks_at: {position, proof}}}\n      end)\n\n    %{state | in_flight_exits: new_ifes}\n  end\n\n  defp zero_address?(address) do\n    address != @zero_address\n  end\n\n  defp sla_margin_safe?(exit_processor_sla_margin, min_exit_period_seconds, ethereum_block_time_seconds),\n    do: exit_processor_sla_margin * ethereum_block_time_seconds < min_exit_period_seconds\n\n  @doc \"\"\"\n  Deletes in-flight exits from state and returns deleted exits\n  \"\"\"\n  @spec delete_in_flight_exits(__MODULE__.t(), list(map)) :: {__MODULE__.t(), list(InFlightExitInfo.t()), list(any())}\n  def delete_in_flight_exits(state, deletions) do\n    exit_ids =\n      deletions\n      |> Enum.map(fn %{exit_id: exit_id} -> InFlightExitInfo.to_contract_id(exit_id) end)\n      |> MapSet.new()\n\n    deleted_ifes_by_key =\n      state.in_flight_exits\n      |> Enum.filter(fn {_, ife} -> MapSet.member?(exit_ids, ife.contract_id) end)\n      |> Map.new()\n\n    deleted_keys = Map.keys(deleted_ifes_by_key)\n    updated_ifes = Map.drop(state.in_flight_exits, deleted_keys)\n\n    deleted_utxos =\n      deleted_ifes_by_key\n      |> Map.values()\n      |> InFlightExitInfo.get_input_utxos()\n\n    db_updates = Enum.map(deleted_keys, fn key -> {:delete, :in_flight_exit_info, key} end)\n\n    {%{state | in_flight_exits: updated_ifes}, deleted_utxos, db_updates}\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/exit_processor/double_spend.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.ExitProcessor.DoubleSpend do\n  @moduledoc \"\"\"\n  Wraps information about a single double spend occuring between a verified transaction and a known transaction\n  \"\"\"\n\n  defstruct [:index, :utxo_pos, :known_spent_index, :known_tx]\n\n  alias OMG.Watcher.ExitProcessor.KnownTx\n  alias OMG.Watcher.ExitProcessor.Tools\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.Utxo\n\n  @type t() :: %__MODULE__{\n          index: non_neg_integer(),\n          utxo_pos: Utxo.Position.t(),\n          known_spent_index: non_neg_integer,\n          known_tx: KnownTx.t()\n        }\n\n  @doc \"\"\"\n  Finds the single, oldest competitor from a set of known transactions grouped by input. `nil` if there's none\n\n  `known_txs_by_input` are assumed to hold _the oldest_ transaction spending given input for every input\n  \"\"\"\n  @spec find_competitor(KnownTx.known_txs_by_input_t(), Transaction.any_flavor_t()) :: nil | t()\n  def find_competitor(known_txs_by_input, tx) do\n    inputs = Transaction.get_inputs(tx)\n\n    known_txs_by_input\n    |> all_distinct_spends_of_inputs(inputs, tx)\n    # need to sort, to get the oldest transaction (double-) spending for _all the_ inputs of `tx`\n    |> Enum.sort(&KnownTx.is_older?/2)\n    |> Enum.at(0)\n    |> case do\n      nil -> nil\n      known_tx -> inputs |> Enum.with_index() |> Tools.double_spends_from_known_tx(known_tx) |> hd()\n    end\n  end\n\n  @doc \"\"\"\n  Gets all the double spends found in an `known_txs_by_input`, following an indexed breakdown of particular\n  utxo_positions of `tx`.\n\n  This is useful if the interesting utxo positions aren't just inputs of `tx` (e.g. piggybacking, tx's outputs, etc.)\n  \"\"\"\n  @spec all_double_spends_by_index(\n          list({Utxo.Position.t(), non_neg_integer}),\n          map(),\n          Transaction.any_flavor_t()\n        ) :: %{non_neg_integer => t()}\n  def all_double_spends_by_index(indexed_utxo_positions, known_txs_by_input, tx) do\n    {inputs, _indices} = Enum.unzip(indexed_utxo_positions)\n\n    # Will find all spenders of provided indexed inputs.\n    known_txs_by_input\n    |> all_distinct_spends_of_inputs(inputs, tx)\n    |> Stream.flat_map(&Tools.double_spends_from_known_tx(indexed_utxo_positions, &1))\n    |> Enum.group_by(& &1.index)\n  end\n\n  # filters all the transactions, spending any of the inputs, distinct from `tx` - to find all the double-spending txs\n  defp all_distinct_spends_of_inputs(known_txs_by_input, inputs, tx) do\n    known_txs_by_input\n    |> Map.take(inputs)\n    |> Stream.flat_map(fn {_input, spending_txs} -> spending_txs end)\n    |> Stream.filter(&Tools.txs_different(tx, &1.signed_tx))\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/exit_processor/exit_info.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.ExitProcessor.ExitInfo do\n  @moduledoc \"\"\"\n  Represents the bulk of information about a tracked exit.\n\n  Internal stuff of `OMG.Watcher.ExitProcessor`\n  \"\"\"\n\n  alias OMG.Watcher.Crypto\n  alias OMG.Watcher.Event\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.Utxo\n\n  require Utxo\n\n  @enforce_keys [\n    :amount,\n    :currency,\n    :owner,\n    :exit_id,\n    :exiting_txbytes,\n    :is_active,\n    :eth_height,\n    :root_chain_txhash,\n    :scheduled_finalization_time,\n    :block_timestamp,\n    :spending_txhash\n  ]\n\n  defstruct @enforce_keys\n\n  @type t :: %__MODULE__{\n          amount: non_neg_integer(),\n          currency: Crypto.address_t(),\n          owner: Crypto.address_t(),\n          exit_id: non_neg_integer(),\n          # the transaction creating the exiting output\n          exiting_txbytes: Transaction.tx_bytes(),\n          # this means the exit has been first seen active. If false, it won't be considered harmful\n          is_active: boolean(),\n          eth_height: pos_integer(),\n          root_chain_txhash: Transaction.tx_hash() | nil,\n          scheduled_finalization_time: pos_integer() | nil,\n          block_timestamp: pos_integer() | nil,\n          spending_txhash: Transaction.tx_hash() | nil\n        }\n\n  @spec new(map(), map()) :: t()\n  def new(\n        contract_status,\n        %{\n          eth_height: eth_height,\n          call_data: %{output_tx: txbytes},\n          exit_id: exit_id,\n          root_chain_txhash: root_chain_txhash,\n          scheduled_finalization_time: scheduled_finalization_time,\n          block_timestamp: block_timestamp\n        } = exit_event\n      ) do\n    Utxo.position(_, _, oindex) = utxo_pos_for(exit_event)\n    {:ok, raw_tx} = Transaction.decode(txbytes)\n    %{amount: amount, currency: currency, owner: owner} = raw_tx |> Transaction.get_outputs() |> Enum.at(oindex)\n\n    do_new(contract_status,\n      amount: amount,\n      currency: currency,\n      owner: owner,\n      exit_id: exit_id,\n      exiting_txbytes: txbytes,\n      eth_height: eth_height,\n      root_chain_txhash: root_chain_txhash,\n      scheduled_finalization_time: scheduled_finalization_time,\n      block_timestamp: block_timestamp,\n      spending_txhash: nil\n    )\n  end\n\n  def new_key(_contract_status, exit_info),\n    do: utxo_pos_for(exit_info)\n\n  defp utxo_pos_for(%{call_data: %{utxo_pos: utxo_pos_enc}} = _exit_info),\n    do: Utxo.Position.decode!(utxo_pos_enc)\n\n  @spec do_new(map(), list(keyword())) :: t()\n  defp do_new(contract_status, fields) do\n    fields = Keyword.put_new(fields, :is_active, parse_contract_exit_status(contract_status))\n    struct!(__MODULE__, fields)\n  end\n\n  @spec make_event_data(Event.module_t(), Utxo.Position.t(), t()) :: struct()\n  def make_event_data(type, position, exit_info) do\n    struct(\n      type,\n      exit_info |> Map.from_struct() |> Map.put(:utxo_pos, Utxo.Position.encode(position))\n    )\n  end\n\n  # NOTE: we have no migrations, so we handle data compatibility here (make_db_update/1 and from_db_kv/1), OMG-421\n  @spec make_db_update({Utxo.Position.t(), t()}) :: {:put, :exit_info, {Utxo.Position.db_t(), map()}}\n  def make_db_update({position, exit_info}) do\n    value = %{\n      amount: exit_info.amount,\n      currency: exit_info.currency,\n      owner: exit_info.owner,\n      exit_id: exit_info.exit_id,\n      exiting_txbytes: exit_info.exiting_txbytes,\n      is_active: exit_info.is_active,\n      eth_height: exit_info.eth_height,\n      root_chain_txhash: exit_info.root_chain_txhash,\n      scheduled_finalization_time: exit_info.scheduled_finalization_time,\n      block_timestamp: exit_info.block_timestamp\n    }\n\n    {:put, :exit_info, {Utxo.Position.to_db_key(position), value}}\n  end\n\n  @spec from_db_kv({Utxo.Position.db_t(), map()}) :: {Utxo.Position.t(), t()}\n  def from_db_kv({db_utxo_pos, exit_info}) do\n    # mapping is used in case of changes in data structure\n    value = %{\n      amount: exit_info.amount,\n      currency: exit_info.currency,\n      owner: exit_info.owner,\n      exit_id: exit_info.exit_id,\n      exiting_txbytes: exit_info.exiting_txbytes,\n      is_active: exit_info.is_active,\n      eth_height: exit_info.eth_height,\n      spending_txhash: nil,\n      # defaults value to nil if non-existent in the DB.\n      root_chain_txhash: Map.get(exit_info, :root_chain_txhash),\n      scheduled_finalization_time: Map.get(exit_info, :scheduled_finalization_time),\n      block_timestamp: Map.get(exit_info, :block_timestamp)\n    }\n\n    {Utxo.Position.from_db_key(db_utxo_pos), struct!(__MODULE__, value)}\n  end\n\n  # processes the return value of `Eth.get_standard_exit_structs(exit_ids)`\n  # `exitable` will be `false` if the exit was challenged\n  # `exitable` will be `false` ALONG WITH the whole tuple holding zeroees, if the exit was processed successfully\n  # **NOTE** one can only rely on the zero-nonzero of this data, since for processed exits this data will be all zeros\n  defp parse_contract_exit_status({exitable, _, _, _, _, _}), do: exitable\n\n  # Based on the block number determines whether UTXO was created by a deposit.\n  defguardp is_deposit(blknum, child_block_interval) when rem(blknum, child_block_interval) != 0\n\n  @doc \"\"\"\n  Calculates the time at which an exit can be processed and released if not challenged successfully.\n  See https://docs.omg.network/challenge-period for calculation logic.\n  \"\"\"\n  @spec calculate_sft(\n          blknum :: pos_integer(),\n          exit_block_timestamp :: pos_integer(),\n          utxo_creation_timestamp :: pos_integer(),\n          min_exit_period :: pos_integer(),\n          child_block_interval :: pos_integer()\n        ) ::\n          {:ok, pos_integer()}\n  def calculate_sft(blknum, exit_block_timestamp, utxo_creation_timestamp, min_exit_period, child_block_interval) do\n    case is_deposit(blknum, child_block_interval) do\n      true ->\n        {:ok, max(exit_block_timestamp + min_exit_period, utxo_creation_timestamp + min_exit_period)}\n\n      false ->\n        {:ok, max(exit_block_timestamp + min_exit_period, utxo_creation_timestamp + 2 * min_exit_period)}\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/exit_processor/finalizations.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.ExitProcessor.Finalizations do\n  @moduledoc \"\"\"\n  Encapsulates managing and executing the behaviors related to treating exits by the child chain and watchers\n  Keeps a state of exits that are in progress, updates it with news from the root chain, compares to the\n  state of the ledger (`OMG.Watcher.State`), issues notifications as it finds suitable.\n\n  Should manage all kinds of exits allowed in the protocol and handle the interactions between them.\n\n  This is the functional, zero-side-effect part of the exit processor. Logic should go here:\n    - orchestrating the persistence of the state\n    - finding invalid exits, disseminating them as events according to rules\n    - enabling to challenge invalid exits\n    - figuring out critical failure of invalid exit challenging (aka `:unchallenged_exit` event)\n    - MoreVP protocol managing in general\n\n  For the imperative shell, see `OMG.Watcher.ExitProcessor`\n  \"\"\"\n\n  alias OMG.Watcher.ExitProcessor.Core\n  alias OMG.Watcher.ExitProcessor.ExitInfo\n  alias OMG.Watcher.ExitProcessor.InFlightExitInfo\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.Utxo\n\n  require Logger\n\n  require Utxo\n\n  @doc \"\"\"\n  Finalize exits based on Ethereum events, removing from tracked state if valid.\n\n  Invalid finalizing exits should continue being tracked as `is_active`, to continue emitting events.\n  This includes non-`is_active` exits that finalize invalid, which are turned to be `is_active` now.\n  \"\"\"\n  @spec finalize_exits(Core.t(), validities :: {list(Utxo.Position.t()), list(Utxo.Position.t())}) ::\n          {Core.t(), list(), list()}\n  def finalize_exits(%Core{exits: exits} = state, {valid_finalizations, invalid}) do\n    # handling valid finalizations\n\n    new_exits_kv_pairs =\n      exits\n      |> Map.take(valid_finalizations)\n      |> Enum.into(%{}, fn {utxo_pos, exit_info} -> {utxo_pos, %ExitInfo{exit_info | is_active: false}} end)\n\n    new_state1 = %{state | exits: Map.merge(exits, new_exits_kv_pairs)}\n    db_updates = new_exits_kv_pairs |> Enum.map(&ExitInfo.make_db_update/1)\n\n    # invalid ones - activating, in case they were inactive, to keep being invalid forever\n    {new_state2, activating_db_updates} = activate_on_invalid_finalization(new_state1, invalid)\n\n    {new_state2, db_updates ++ activating_db_updates}\n  end\n\n  defp activate_on_invalid_finalization(%Core{exits: exits} = state, invalid_finalizations) do\n    exits_to_activate =\n      exits\n      |> Map.take(invalid_finalizations)\n      |> Enum.map(fn {k, v} -> {k, Map.update!(v, :is_active, fn _ -> true end)} end)\n      |> Map.new()\n\n    activating_db_updates =\n      exits_to_activate\n      |> Enum.map(&ExitInfo.make_db_update/1)\n\n    state = %{state | exits: Map.merge(exits, exits_to_activate)}\n    {state, activating_db_updates}\n  end\n\n  @doc \"\"\"\n  Returns a tuple of `{:ok, %{ife_exit_id => {finalized_input_exits | finalized_output_exits}}, list(events_exits)}`.\n  Finalized input exits and finalized output exits structures both fit into `OMG.Watcher.State.exit_utxos/1`.\n  Events exits list contains Ethereum's finalization events paired with utxos they exits. This data is needed to\n  broadcast information to the consumers about utxos that needs to marked as spend as the result of finalization.\n\n  When there are invalid finalizations, returns one of the following:\n    - {:inactive_piggybacks_finalizing, list of piggybacks that exit processor state is not aware of}\n    - {:unknown_in_flight_exit, set of in-flight exit ids that exit processor is not aware of}\n  \"\"\"\n  @spec prepare_utxo_exits_for_in_flight_exit_finalizations(Core.t(), [map()]) ::\n          {:ok, map(), list()}\n          | {:inactive_piggybacks_finalizing, list()}\n          | {:unknown_in_flight_exit, MapSet.t(non_neg_integer())}\n  def prepare_utxo_exits_for_in_flight_exit_finalizations(%Core{in_flight_exits: ifes}, finalizations) do\n    finalizations = finalizations |> Enum.map(&ife_id_to_binary/1)\n\n    with {:ok, ifes_by_id} <- get_all_finalized_ifes_by_ife_contract_id(finalizations, ifes),\n         {:ok, []} <- known_piggybacks?(finalizations, ifes_by_id) do\n      {exiting_positions_by_ife_id, events_with_positions} =\n        finalizations\n        |> Enum.reverse()\n        |> Enum.reduce({%{}, []}, &combine_utxo_exits_with_finalization(&1, &2, ifes_by_id))\n\n      {\n        :ok,\n        exiting_positions_by_ife_id,\n        Enum.reject(events_with_positions, &Kernel.match?({_, []}, &1))\n      }\n    end\n  end\n\n  # converts from int, which is how the contract serves it\n  defp ife_id_to_binary(finalization),\n    do: Map.update!(finalization, :in_flight_exit_id, fn id -> <<id::192>> end)\n\n  defp get_all_finalized_ifes_by_ife_contract_id(finalizations, ifes) do\n    finalizations_ids =\n      finalizations\n      |> Enum.map(fn %{in_flight_exit_id: id} -> id end)\n      |> MapSet.new()\n\n    by_contract_id =\n      ifes\n      |> Enum.map(fn {_tx_hash, %InFlightExitInfo{contract_id: id} = ife} -> {id, ife} end)\n      |> Map.new()\n\n    known_ifes =\n      by_contract_id\n      |> Map.keys()\n      |> MapSet.new()\n\n    unknown_ifes = MapSet.difference(finalizations_ids, known_ifes)\n\n    if Enum.empty?(unknown_ifes) do\n      {:ok, by_contract_id}\n    else\n      {:unknown_in_flight_exit, unknown_ifes}\n    end\n  end\n\n  defp known_piggybacks?(finalizations, ifes_by_id) do\n    finalizations\n    |> Enum.filter(&finalization_not_piggybacked?(&1, ifes_by_id))\n    |> case do\n      [] -> {:ok, []}\n      not_piggybacked -> {:inactive_piggybacks_finalizing, not_piggybacked}\n    end\n  end\n\n  defp finalization_not_piggybacked?(\n         %{in_flight_exit_id: ife_id, output_index: output_index, omg_data: %{piggyback_type: piggyback_type}},\n         ifes_by_id\n       ),\n       do: not InFlightExitInfo.is_active?(ifes_by_id[ife_id], {piggyback_type, output_index})\n\n  defp combine_utxo_exits_with_finalization(\n         %{in_flight_exit_id: ife_id, output_index: output_index, omg_data: %{piggyback_type: piggyback_type}} = event,\n         {exiting_positions, events_with_positions},\n         ifes_by_id\n       ) do\n    ife = ifes_by_id[ife_id]\n    # a runtime sanity check - if this were false it would mean all piggybacks finalized so contract wouldn't allow that\n    true = InFlightExitInfo.is_active?(ife, {piggyback_type, output_index})\n\n    # figure out if there's any UTXOs really exiting from the `OMG.Watcher.State`\n    # from this IFE's piggybacked input/output\n    exiting_positions_for_piggyback = get_exiting_positions(ife, output_index, piggyback_type)\n\n    {\n      Map.update(exiting_positions, ife_id, exiting_positions_for_piggyback, &(exiting_positions_for_piggyback ++ &1)),\n      [{event, exiting_positions_for_piggyback} | events_with_positions]\n    }\n  end\n\n  defp get_exiting_positions(ife, output_index, :input) do\n    %InFlightExitInfo{tx: %Transaction.Signed{raw_tx: tx}} = ife\n    input_position = tx |> Transaction.get_inputs() |> Enum.at(output_index)\n    [input_position]\n  end\n\n  defp get_exiting_positions(ife, output_index, :output) do\n    case ife.tx_seen_in_blocks_at do\n      nil -> []\n      {Utxo.position(blknum, txindex, _), _proof} -> [Utxo.position(blknum, txindex, output_index)]\n    end\n  end\n\n  @doc \"\"\"\n  Finalizes in-flight exits.\n\n  Returns a tuple of {:ok, updated state, database updates}.\n  When there are invalid finalizations, returns one of the following:\n    - {:inactive_piggybacks_finalizing, list of piggybacks that exit processor state is not aware of}\n    - {:unknown_in_flight_exit, set of in-flight exit ids that exit processor is not aware of}\n  \"\"\"\n  @spec finalize_in_flight_exits(Core.t(), [map()], map()) ::\n          {:ok, Core.t(), list()}\n          | {:inactive_piggybacks_finalizing, list()}\n          | {:unknown_in_flight_exit, MapSet.t(non_neg_integer())}\n  def finalize_in_flight_exits(%Core{in_flight_exits: ifes} = state, finalizations, invalidities_by_ife_id) do\n    # convert ife_id from int (given by contract) to a binary\n    finalizations = Enum.map(finalizations, &ife_id_to_binary/1)\n\n    with {:ok, ifes_by_id} <- get_all_finalized_ifes_by_ife_contract_id(finalizations, ifes),\n         {:ok, []} <- known_piggybacks?(finalizations, ifes_by_id) do\n      {ifes_by_id, updated_ifes} =\n        finalizations\n        |> Enum.reduce({ifes_by_id, MapSet.new()}, &finalize_single_exit/2)\n        |> activate_on_invalid_utxo_exits(invalidities_by_ife_id)\n\n      db_updates =\n        ifes_by_id\n        |> Map.take(Enum.to_list(updated_ifes))\n        |> Map.values()\n        # re-key those IFEs by tx_hash as how they are originally stored\n        |> Enum.map(&{Transaction.raw_txhash(&1.tx), &1})\n        |> Enum.map(&InFlightExitInfo.make_db_update/1)\n\n      ifes =\n        ifes_by_id\n        # re-key those IFEs by tx_hash as how they are originally stored\n        |> Map.values()\n        |> Enum.into(%{}, &{Transaction.raw_txhash(&1.tx), &1})\n\n      {:ok, %{state | in_flight_exits: ifes}, db_updates}\n    end\n  end\n\n  defp finalize_single_exit(\n         %{in_flight_exit_id: ife_id, output_index: output_index, omg_data: %{piggyback_type: piggyback_type}},\n         {ifes_by_id, updated_ifes}\n       ) do\n    combined_index = {piggyback_type, output_index}\n    ife = ifes_by_id[ife_id]\n\n    if InFlightExitInfo.is_active?(ife, combined_index) do\n      {:ok, finalized_ife} = InFlightExitInfo.finalize(ife, combined_index)\n      ifes_by_id = Map.put(ifes_by_id, ife_id, finalized_ife)\n      updated_ifes = MapSet.put(updated_ifes, ife_id)\n\n      {ifes_by_id, updated_ifes}\n    else\n      {ifes_by_id, updated_ifes}\n    end\n  end\n\n  defp activate_on_invalid_utxo_exits({ifes_by_id, updated_ifes}, invalidities_by_ife_id) do\n    ids_to_activate =\n      invalidities_by_ife_id\n      |> Enum.filter(fn {_ife_id, invalidities} -> not Enum.empty?(invalidities) end)\n      |> Enum.map(fn {ife_id, _invalidities} -> ife_id end)\n      |> MapSet.new()\n\n    # iterates over the ifes that are spotted with invalid finalizing (their `ife_ids`) and activates the ifes\n    new_ifes_by_id =\n      Enum.reduce(ids_to_activate, ifes_by_id, fn id, ifes -> Map.update!(ifes, id, &InFlightExitInfo.activate/1) end)\n\n    {new_ifes_by_id, MapSet.union(ids_to_activate, updated_ifes)}\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/exit_processor/in_flight_exit_info.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.ExitProcessor.InFlightExitInfo do\n  @moduledoc \"\"\"\n  Represents the bulk of information about a tracked in-flight exit.\n\n  Internal stuff of `OMG.Watcher.ExitProcessor`\n  \"\"\"\n\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.Utxo\n\n  require OMG.Watcher.Utxo\n  require Transaction\n  require Transaction.Payment\n\n  @max_inputs Transaction.Payment.max_inputs()\n  @max_outputs Transaction.Payment.max_outputs()\n  @inputs_index_range 0..(@max_inputs - 1)\n  @outputs_index_range 0..(@max_outputs - 1)\n\n  @type combined_index_t() :: {:input, 0..unquote(@max_inputs - 1)} | {:output, 0..unquote(@max_outputs - 1)}\n\n  # TODO: divide into inputs and outputs: prevent contract's implementation from leaking into watcher\n  # https://github.com/omgnetwork/elixir-omg/pull/361#discussion_r247926222\n\n  @enforce_keys [\n    :tx,\n    :timestamp,\n    :contract_id,\n    :eth_height,\n    :is_active\n  ]\n\n  defstruct [\n    :tx,\n    :contract_tx_pos,\n    :tx_seen_in_blocks_at,\n    :timestamp,\n    :contract_id,\n    :oldest_competitor,\n    :eth_height,\n    :input_txs,\n    :input_utxos_pos,\n    :relevant_from_blknum,\n    # piggybacking & finalization\n    exit_map: Map.new(),\n    is_canonical: true,\n    is_active: true\n  ]\n\n  @type blknum() :: pos_integer()\n  @type tx_index() :: non_neg_integer()\n\n  @type ife_contract_id() :: <<_::192>>\n\n  @type exit_map_t() :: %{\n          {:input | :output, non_neg_integer()} => %{\n            is_piggybacked: boolean(),\n            is_finalized: boolean(),\n            is_challenged: boolean()\n          }\n        }\n\n  @type t :: %__MODULE__{\n          tx: Transaction.Signed.t(),\n          # if not nil, position was proven in contract\n          contract_tx_pos: Utxo.Position.t() | nil,\n          # nil value means that it was not included\n          # OR we haven't processed it yet\n          # OR we have found and filled this data, but haven't persisted it later\n          tx_seen_in_blocks_at: {Utxo.Position.t(), inclusion_proof :: binary()} | nil,\n          timestamp: non_neg_integer(),\n          contract_id: ife_contract_id(),\n          # includes a special value denoting \"age\" of a non-included transaction being a competitor\n          oldest_competitor: Utxo.Position.t() | :no_position | nil,\n          eth_height: pos_integer(),\n          input_txs: list(Transaction.Protocol.t()),\n          input_utxos_pos: list(Utxo.Position.t()),\n          relevant_from_blknum: pos_integer(),\n          exit_map: exit_map_t(),\n          is_canonical: boolean(),\n          is_active: boolean()\n        }\n\n  @doc \"\"\"\n  Creates a new instance of the key-value pair for the respective `InFlightExitInfo`, from the Ethereum event map\n  \"\"\"\n  @spec new_kv(map(), {tuple(), non_neg_integer()}) :: t()\n  def new_kv(\n        %{\n          eth_height: eth_height,\n          call_data: %{\n            in_flight_tx: tx_bytes,\n            in_flight_tx_sigs: signatures,\n            input_txs: input_txs,\n            input_utxos_pos: input_utxos_pos\n          }\n        },\n        {contract_status, contract_ife_id}\n      ) do\n    do_new(tx_bytes, signatures, contract_status,\n      contract_id: <<contract_ife_id::192>>,\n      eth_height: eth_height,\n      input_txs: input_txs,\n      input_utxos_pos: Enum.map(input_utxos_pos, &Utxo.Position.decode!/1)\n    )\n  end\n\n  defp do_new(tx_bytes, tx_signatures, contract_status, fields) do\n    {timestamp, is_active} = parse_contract_in_flight_exit_status(contract_status)\n\n    with {:ok, tx} <- prepare_tx(tx_bytes, tx_signatures) do\n      # NOTE: in case of using output_id as the input pointer, getting the youngest will be entirely different\n      Utxo.position(youngest_input_blknum, _, _) =\n        tx\n        |> Transaction.get_inputs()\n        |> Enum.sort_by(&Utxo.Position.encode/1, &>=/2)\n        |> hd()\n\n      fields =\n        fields\n        |> Keyword.put_new(:tx, tx)\n        |> Keyword.put_new(:is_active, is_active)\n        |> Keyword.put_new(:relevant_from_blknum, youngest_input_blknum)\n        |> Keyword.put_new(:timestamp, timestamp)\n\n      {Transaction.raw_txhash(tx), struct!(__MODULE__, fields)}\n    end\n  end\n\n  defp parse_contract_in_flight_exit_status({_, timestamp, _, _, _, _, _}), do: {timestamp, timestamp != 0}\n\n  defp prepare_tx(tx_bytes, tx_signatures) do\n    with {:ok, raw_tx} <- Transaction.decode(tx_bytes) do\n      tx = %Transaction.Signed{raw_tx: raw_tx, sigs: tx_signatures}\n      {:ok, tx}\n    end\n  end\n\n  # NOTE: we have no migrations, so we handle data compatibility here (make_db_update/1 and from_db_kv/1), OMG-421\n  def make_db_update(\n        {ife_hash,\n         %__MODULE__{\n           tx: %Transaction.Signed{} = tx,\n           contract_tx_pos: tx_pos,\n           timestamp: timestamp,\n           contract_id: contract_id,\n           oldest_competitor: oldest_competitor,\n           eth_height: eth_height,\n           input_txs: input_txs,\n           input_utxos_pos: input_utxos_pos,\n           relevant_from_blknum: relevant_from_blknum,\n           exit_map: exit_map,\n           is_canonical: is_canonical,\n           is_active: is_active\n         }}\n      )\n      when is_binary(contract_id) and\n             is_integer(timestamp) and is_integer(eth_height) and is_list(input_txs) and is_list(input_utxos_pos) and\n             is_integer(relevant_from_blknum) and\n             is_map(exit_map) and is_boolean(is_canonical) and is_boolean(is_active) do\n    :ok = assert_utxo_pos_type(tx_pos)\n    :ok = assert_utxo_pos_type(oldest_competitor)\n    # mapping is used in case of changes in data structure\n    value = %{\n      tx: to_db_value(tx),\n      tx_pos: tx_pos,\n      timestamp: timestamp,\n      contract_id: contract_id,\n      oldest_competitor: oldest_competitor,\n      eth_height: eth_height,\n      input_txs: input_txs,\n      input_utxos_pos: input_utxos_pos,\n      relevant_from_blknum: relevant_from_blknum,\n      exit_map: exit_map,\n      is_canonical: is_canonical,\n      is_active: is_active\n    }\n\n    {:put, :in_flight_exit_info, {ife_hash, value}}\n  end\n\n  @doc \"\"\"\n  Returns all input utxos for given in-flight exits\n  \"\"\"\n  @spec get_input_utxos(list(t())) :: list(Utxo.Position.t())\n  def get_input_utxos(in_flight_exits) do\n    in_flight_exits\n    |> Enum.map(& &1.input_utxos_pos)\n    |> List.flatten()\n  end\n\n  defp assert_utxo_pos_type(Utxo.position(blknum, txindex, oindex))\n       when is_integer(blknum) and is_integer(txindex) and is_integer(oindex),\n       do: :ok\n\n  defp assert_utxo_pos_type(nil), do: :ok\n\n  # a special value denoting position (\"age\") of a non-included transaction is ok too\n  defp assert_utxo_pos_type(:no_position), do: :ok\n\n  def from_db_kv({ife_hash, fields}) do\n    # TODO: this got really horrible. Instead of tidying up/maintaining maybe go `Ecto` and use `Ecto.x` facilities\n    #       on this here and elsewhere\n    assert_types(fields, [:tx_pos, :oldest_competitor], fn value -> :ok = assert_utxo_pos_type(value) end)\n    assert_types(fields, [:tx, :exit_map], fn value -> true = is_map(value) end)\n    assert_types(fields, [:contract_id], fn value -> true = is_binary(value) end)\n    assert_types(fields, [:timestamp, :eth_height, :relevant_from_blknum], fn value -> true = is_integer(value) end)\n    assert_types(fields, [:input_txs, :input_utxos_pos], fn value -> true = is_list(value) end)\n    assert_types(fields, [:is_canonical, :is_active], fn value -> true = is_boolean(value) end)\n\n    # mapping is used in case of changes in data structure\n    ife_map = %{\n      tx: from_db_signed_tx(fields[:tx]),\n      contract_tx_pos: fields[:tx_pos],\n      timestamp: fields[:timestamp],\n      contract_id: fields[:contract_id],\n      oldest_competitor: fields[:oldest_competitor],\n      eth_height: fields[:eth_height],\n      input_txs: fields[:input_txs],\n      input_utxos_pos: fields[:input_utxos_pos],\n      relevant_from_blknum: fields[:relevant_from_blknum],\n      exit_map: fields[:exit_map],\n      is_canonical: fields[:is_canonical],\n      is_active: fields[:is_active]\n    }\n\n    {ife_hash, struct!(__MODULE__, ife_map)}\n  end\n\n  defp assert_types(fields, keys, assertion) do\n    fields\n    |> Map.take(keys)\n    |> Map.values()\n    |> Enum.each(assertion)\n  end\n\n  # NOTE: non-private because `CompetitorInfo` holds `Transaction.Signed` objects too\n  def from_db_signed_tx(%{raw_tx: raw_tx_map, sigs: sigs}) when is_map(raw_tx_map) and is_list(sigs) do\n    value = %{raw_tx: from_db_raw_tx(raw_tx_map), sigs: sigs}\n    struct!(Transaction.Signed, value)\n  end\n\n  def from_db_raw_tx(%{tx_type: tx_type, inputs: inputs, outputs: outputs, metadata: metadata})\n      when is_list(inputs) and is_list(outputs) and Transaction.is_metadata(metadata) do\n    value = %{tx_type: tx_type, inputs: inputs, outputs: outputs, metadata: metadata}\n    struct!(Transaction.Payment, value)\n  end\n\n  def to_db_value(%Transaction.Signed{raw_tx: raw_tx, sigs: sigs}) when is_list(sigs) do\n    %{raw_tx: to_db_value(raw_tx), sigs: sigs}\n  end\n\n  def to_db_value(%Transaction.Payment{tx_type: tx_type, inputs: inputs, outputs: outputs, metadata: metadata})\n      when is_list(inputs) and is_list(outputs) and Transaction.is_metadata(metadata) do\n    %{tx_type: tx_type, inputs: inputs, outputs: outputs, metadata: metadata}\n  end\n\n  @spec piggyback(t(), combined_index_t()) :: t() | {:error, :non_existent_exit | :cannot_piggyback}\n  def piggyback(ife, index)\n\n  def piggyback(%__MODULE__{exit_map: exit_map} = ife, combined_index) when is_tuple(combined_index) do\n    with exit <- exit_map_get(exit_map, combined_index),\n         {:ok, updated_exit} <- piggyback_exit(exit) do\n      %{ife | exit_map: Map.put(exit_map, combined_index, updated_exit)}\n    end\n  end\n\n  def piggyback(%__MODULE__{}, _), do: {:error, :non_existent_exit}\n\n  defp piggyback_exit(%{is_piggybacked: false, is_finalized: false, is_challenged: false} = exit_map_entry),\n    do: {:ok, %{exit_map_entry | is_piggybacked: true}}\n\n  defp piggyback_exit(_), do: {:error, :cannot_piggyback}\n\n  @spec challenge(t(), non_neg_integer()) :: t() | {:error, :competitor_too_young}\n  def challenge(ife, competitor_position)\n\n  def challenge(%__MODULE__{oldest_competitor: nil} = ife, competitor_position),\n    do: %{ife | is_canonical: false, oldest_competitor: decode_position_possibly_exceeding(competitor_position)}\n\n  def challenge(%__MODULE__{oldest_competitor: current_oldest} = ife, competitor_position) do\n    with decoded_competitor_pos <- Utxo.Position.decode!(competitor_position),\n         true <- is_older?(decoded_competitor_pos, current_oldest) do\n      %{ife | is_canonical: false, oldest_competitor: decoded_competitor_pos}\n    else\n      _ -> {:error, :competitor_too_young}\n    end\n  end\n\n  @spec challenge_piggyback(t(), combined_index_t()) :: t()\n  def challenge_piggyback(%__MODULE__{exit_map: exit_map} = ife, combined_index) when is_tuple(combined_index) do\n    %{is_piggybacked: true, is_finalized: false, is_challenged: false} =\n      exit_map_entry = exit_map_get(exit_map, combined_index)\n\n    %{ife | exit_map: Map.replace!(exit_map, combined_index, %{exit_map_entry | is_challenged: true})}\n  end\n\n  @spec respond_to_challenge(t(), Utxo.Position.t()) ::\n          t() | {:error, :responded_with_too_young_tx | :cannot_respond}\n  def respond_to_challenge(ife, tx_position)\n\n  def respond_to_challenge(%__MODULE__{oldest_competitor: current_oldest} = ife, tx_position) do\n    decoded = Utxo.Position.decode!(tx_position)\n\n    if is_nil(current_oldest) or is_older?(decoded, current_oldest) do\n      %{ife | oldest_competitor: decoded, is_canonical: true, contract_tx_pos: decoded}\n    else\n      {:error, :responded_with_too_young_tx}\n    end\n  end\n\n  def respond_to_challenge(%__MODULE__{}, _), do: {:error, :cannot_respond}\n\n  @spec finalize(t(), combined_index_t()) :: {:ok, t()} | :unknown_output_index\n  def finalize(%__MODULE__{exit_map: exit_map} = ife, combined_index) when is_tuple(combined_index) do\n    case exit_map_get(exit_map, combined_index) do\n      nil ->\n        :unknown_output_index\n\n      output_exit ->\n        output_exit = %{output_exit | is_finalized: true}\n        exit_map = Map.put(exit_map, combined_index, output_exit)\n        ife = %{ife | exit_map: exit_map}\n\n        is_active =\n          exit_map\n          |> Map.keys()\n          |> Enum.any?(&is_active?(ife, &1))\n\n        ife = %{ife | is_active: is_active}\n        {:ok, ife}\n    end\n  end\n\n  @spec get_active_output_piggybacks_positions(t()) :: [Utxo.Position.t()]\n  def get_active_output_piggybacks_positions(%__MODULE__{tx_seen_in_blocks_at: nil}), do: []\n\n  def get_active_output_piggybacks_positions(\n        %__MODULE__{tx_seen_in_blocks_at: {Utxo.position(blknum, txindex, _), _}} = ife\n      ) do\n    @outputs_index_range\n    |> Enum.filter(&is_unchallenged?(ife, {:output, &1}))\n    |> Enum.map(&Utxo.position(blknum, txindex, &1))\n  end\n\n  def unchallenged_piggybacks_by_ife(%__MODULE__{tx: tx} = ife, :input) do\n    indexed_piggybacked_inputs =\n      tx\n      |> Transaction.get_inputs()\n      |> Enum.with_index()\n      |> Enum.filter(fn {_input, index} -> is_unchallenged?(ife, {:input, index}) end)\n\n    {ife, indexed_piggybacked_inputs}\n  end\n\n  def unchallenged_piggybacks_by_ife(%__MODULE__{} = ife, :output) do\n    indexed_piggybacked_outputs =\n      ife\n      |> get_active_output_piggybacks_positions()\n      |> Enum.map(&index_output_position/1)\n\n    {ife, indexed_piggybacked_outputs}\n  end\n\n  defp index_output_position(position) do\n    Utxo.position(_, _, oindex) = position\n    {position, oindex}\n  end\n\n  def actively_piggybacked_inputs(ife) do\n    @inputs_index_range\n    |> Enum.filter(&is_active?(ife, {:input, &1}))\n  end\n\n  def actively_piggybacked_outputs(ife) do\n    @outputs_index_range\n    |> Enum.filter(&is_active?(ife, {:output, &1}))\n  end\n\n  @spec is_active?(t(), combined_index_t()) :: boolean()\n  def is_active?(%__MODULE__{} = ife, combined_index) do\n    is_piggybacked?(ife, combined_index) and\n      !is_finalized?(ife, combined_index) and\n      !is_challenged?(ife, combined_index)\n  end\n\n  @spec is_unchallenged?(t(), combined_index_t()) :: boolean()\n  def is_unchallenged?(%__MODULE__{} = ife, combined_index) do\n    is_piggybacked?(ife, combined_index) and\n      !is_challenged?(ife, combined_index)\n  end\n\n  def activate(%__MODULE__{} = ife) do\n    %{ife | is_active: true}\n  end\n\n  def should_be_seeked_in_blocks?(%__MODULE__{} = ife),\n    do: ife.is_active && ife.tx_seen_in_blocks_at == nil\n\n  @doc \"\"\"\n  First, it determines if it is challenged at all - if it isn't returns false.\n  Second, If the tx hasn't been seen at all then it will be false\n  If it is challenged (hence non-canonical) and seen it will figure out if the IFE tx has been seen in an older than\n  oldest competitor's position.\n  \"\"\"\n  @spec is_invalidly_challenged?(t()) :: boolean()\n  def is_invalidly_challenged?(%__MODULE__{is_canonical: true}), do: false\n  def is_invalidly_challenged?(%__MODULE__{tx_seen_in_blocks_at: nil}), do: false\n\n  def is_invalidly_challenged?(%__MODULE__{\n        tx_seen_in_blocks_at: {Utxo.position(_, _, _) = seen_in_pos, _proof},\n        oldest_competitor: oldest_competitor_pos\n      }),\n      do: is_older?(seen_in_pos, oldest_competitor_pos)\n\n  @doc \"\"\"\n  Converts integer to contract's in-flight exit id\n  \"\"\"\n  @spec to_contract_id(non_neg_integer) :: <<_::192>>\n  def to_contract_id(id), do: <<id::192>>\n\n  @doc \"\"\"\n  Checks if the competitor being seen at `competitor_pos` (`nil` if unseen) is viable to challenge with, considering the\n  current state of the IFE - that is, only if it is older than IFE tx's inclusion and other competitors\n  \"\"\"\n  @spec is_viable_competitor?(t(), Utxo.Position.t() | nil) :: boolean()\n  def is_viable_competitor?(\n        %__MODULE__{tx_seen_in_blocks_at: nil, oldest_competitor: oldest_competitor_pos},\n        competitor_pos\n      ),\n      do: do_is_viable_competitor?(nil, oldest_competitor_pos, competitor_pos)\n\n  def is_viable_competitor?(\n        %__MODULE__{tx_seen_in_blocks_at: {seen_at_pos, _proof}, oldest_competitor: oldest_competitor_pos},\n        competitor_pos\n      ),\n      do: do_is_viable_competitor?(seen_at_pos, oldest_competitor_pos, competitor_pos)\n\n  def is_relevant?(%__MODULE__{relevant_from_blknum: relevant_from_blknum}, blknum_now),\n    do: relevant_from_blknum < blknum_now\n\n  @spec is_piggybacked?(t(), combined_index_t()) :: boolean()\n  def is_piggybacked?(%__MODULE__{exit_map: map}, combined_index) when is_tuple(combined_index) do\n    if exit = exit_map_get(map, combined_index) do\n      Map.get(exit, :is_piggybacked, false)\n    else\n      false\n    end\n  end\n\n  @spec is_finalized?(t(), combined_index_t()) :: boolean()\n  defp is_finalized?(%__MODULE__{exit_map: map}, combined_index) do\n    if exit = exit_map_get(map, combined_index) do\n      Map.get(exit, :is_finalized, false)\n    else\n      false\n    end\n  end\n\n  @spec is_challenged?(t(), combined_index_t()) :: boolean()\n  defp is_challenged?(%__MODULE__{exit_map: map}, combined_index) do\n    if exit = exit_map_get(map, combined_index) do\n      Map.get(exit, :is_challenged, false)\n    else\n      false\n    end\n  end\n\n  # there's nothing with any position, so there's nothing older than competitor, so it's good to challenge with\n  defp do_is_viable_competitor?(nil, nil, _competitor_pos), do: true\n  # there's something with position and the competitor doesn't have any - not good to challenge with\n  defp do_is_viable_competitor?(_seen_at_pos, _oldest_pos, nil), do: false\n  # there already is a competitor reported in the contract, if the competitor is older then good to challenge with\n  defp do_is_viable_competitor?(nil, oldest_pos, competitor_pos), do: is_older?(competitor_pos, oldest_pos)\n  # this IFE tx has been already seen at some position, if the competitor is older then good to challenge with\n  defp do_is_viable_competitor?(seen_at_pos, nil, competitor_pos), do: is_older?(competitor_pos, seen_at_pos)\n  # the competitor must be older than anything else to be good to challenge with\n  defp do_is_viable_competitor?(seen_at_pos, oldest_pos, competitor_pos),\n    do: is_older?(competitor_pos, seen_at_pos) and is_older?(competitor_pos, oldest_pos)\n\n  # no position is older than any real position\n  defp is_older?(Utxo.position(_, _, _), :no_position), do: true\n  # no position is younger than any real position\n  defp is_older?(:no_position, Utxo.position(_, _, _)), do: false\n  # for real positions, the smaller it is the older it is\n  defp is_older?(Utxo.position(tx1_blknum, tx1_index, _), Utxo.position(tx2_blknum, tx2_index, _)),\n    do: tx1_blknum < tx2_blknum or (tx1_blknum == tx2_blknum and tx1_index < tx2_index)\n\n  # to cater for utxo positions coming from the contract, that represent non-included transactions\n  defp decode_position_possibly_exceeding(encoded_position) do\n    case Utxo.Position.decode(encoded_position) do\n      {:ok, Utxo.position(_, _, _) = decoded} -> decoded\n      # The position was huge so it denoted a non-included transaction.\n      # Use a special value denoting \"age\" of a non-included transaction\n      {:error, :encoded_utxo_position_too_low} -> :no_position\n    end\n  end\n\n  @spec exit_map_get(exit_map_t(), combined_index_t()) :: %{\n          is_piggybacked: boolean(),\n          is_finalized: boolean(),\n          is_challenged: boolean()\n        }\n  defp exit_map_get(exit_map, {type, index} = combined_index)\n       when (type == :input and index < @max_inputs) or (type == :output and index < @max_outputs),\n       do: Map.get(exit_map, combined_index, %{is_piggybacked: false, is_finalized: false, is_challenged: false})\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/exit_processor/known_tx.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.ExitProcessor.KnownTx do\n  @moduledoc \"\"\"\n  Wrapps information about a particular signed transaction known from somewhere, optionally with its UTXO position\n\n  Private\n  \"\"\"\n  defstruct [:signed_tx, :utxo_pos]\n\n  alias OMG.Watcher.Block\n  alias OMG.Watcher.ExitProcessor.Core\n  alias OMG.Watcher.ExitProcessor.TxAppendix\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.Utxo\n\n  require Utxo\n\n  @type t() :: %__MODULE__{\n          signed_tx: Transaction.Signed.t(),\n          utxo_pos: Utxo.Position.t() | nil\n        }\n\n  @type known_txs_by_input_t() :: %{Utxo.Position.t() => list(__MODULE__.t())}\n\n  def new(%Transaction.Signed{} = signed_tx, Utxo.position(_, _, _) = utxo_pos),\n    do: %__MODULE__{signed_tx: signed_tx, utxo_pos: utxo_pos}\n\n  def new(%Transaction.Signed{} = signed_tx),\n    do: %__MODULE__{signed_tx: signed_tx}\n\n  def get_positions_by_txhash(blocks) do\n    blocks\n    |> get_all_from()\n    # cannot simply `Enum.into` here, because for every position the tx might have been included, we need the oldest\n    |> Enum.group_by(&Transaction.raw_txhash(&1.signed_tx), & &1.utxo_pos)\n    |> Enum.into(%{}, fn {txhash, positions} -> {txhash, hd(positions)} end)\n  end\n\n  def get_blocks_by_blknum(blocks),\n    do: blocks |> Enum.into(%{}, fn %Block{number: blknum} = block -> {blknum, block} end)\n\n  def find_tx_in_blocks(txhash, positions_by_tx_hash, blocks_by_blknum) do\n    txhash\n    |> (&Map.get(positions_by_tx_hash, &1)).()\n    |> case do\n      nil -> nil\n      Utxo.position(blknum, _, _) = position -> {blocks_by_blknum[blknum], position}\n    end\n  end\n\n  @doc \"\"\"\n  Groups the spending transactions by the input spent, preserves the sorting for every input.\n\n  Expects an `Enumberable` of `KnownTx`s\n  Duplicates are possible.\n  \"\"\"\n  @spec group_txs_by_input(Enumerable.t()) :: known_txs_by_input_t\n  def group_txs_by_input(all_known_txs) do\n    all_known_txs\n    |> Stream.map(&{&1, Transaction.get_inputs(&1.signed_tx)})\n    |> Stream.flat_map(fn {known_tx, inputs} -> for input <- inputs, do: {input, known_tx} end)\n    |> Enum.group_by(&elem(&1, 0), &elem(&1, 1))\n  end\n\n  # returns the known transactions in the proper order - first ones from blocks sorted from oldest then ones\n  # from outside of blocks (TxAppendix)\n  def get_all_from_blocks_appendix(blocks, %Core{} = processor) do\n    [get_all_from(blocks), get_all_from(processor)]\n    |> Stream.concat()\n    |> group_txs_by_input()\n  end\n\n  defp get_all_from(%Core{} = processor) do\n    TxAppendix.get_all(processor)\n    |> Stream.map(&new/1)\n  end\n\n  defp get_all_from(%Block{transactions: txs, number: blknum}) do\n    txs\n    |> Stream.map(&Transaction.Signed.decode!/1)\n    |> Stream.with_index()\n    |> Stream.map(fn {signed, txindex} -> new(signed, Utxo.position(blknum, txindex, 0)) end)\n  end\n\n  defp get_all_from(blocks) when is_list(blocks), do: blocks |> sort_blocks() |> Stream.flat_map(&get_all_from/1)\n\n  # we're sorting the blocks by their blknum here, because we wan't oldest (best) competitors first always\n  defp sort_blocks(blocks), do: blocks |> Enum.sort_by(fn %Block{number: number} -> number end)\n\n  def is_older?(%__MODULE__{utxo_pos: utxo_pos1}, %__MODULE__{utxo_pos: utxo_pos2}) do\n    cond do\n      is_nil(utxo_pos1) -> false\n      is_nil(utxo_pos2) -> true\n      true -> Utxo.Position.encode(utxo_pos1) < Utxo.Position.encode(utxo_pos2)\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/exit_processor/measure.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.ExitProcessor.Measure do\n  @moduledoc \"\"\"\n  Counting business metrics sent to Datadog\n  \"\"\"\n\n  import OMG.Status.Metric.Event, only: [name: 1]\n\n  alias OMG.Status.Metric.Datadog\n  alias OMG.Watcher.ExitProcessor\n\n  def handle_event([:process, ExitProcessor], _, _state, _config) do\n    value =\n      self()\n      |> Process.info(:message_queue_len)\n      |> elem(1)\n\n    _ = Datadog.gauge(name(:watcher_exit_processor_message_queue_len), value)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/exit_processor/piggyback.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.ExitProcessor.Piggyback do\n  @moduledoc \"\"\"\n  Encapsulates managing and executing the behaviors related to treating exits by the child chain and watchers\n  Keeps a state of exits that are in progress, updates it with news from the root chain, compares to the\n  state of the ledger (`OMG.Watcher.State`), issues notifications as it finds suitable.\n\n  Should manage all kinds of exits allowed in the protocol and handle the interactions between them.\n\n  This is the functional, zero-side-effect part of the exit processor. Logic should go here:\n    - orchestrating the persistence of the state\n    - finding invalid exits, disseminating them as events according to rules\n    - enabling to challenge invalid exits\n    - figuring out critical failure of invalid exit challenging (aka `:unchallenged_exit` event)\n    - MoreVP protocol managing in general\n\n  For the imperative shell, see `OMG.Watcher.ExitProcessor`\n  \"\"\"\n\n  alias OMG.Watcher.Event\n  alias OMG.Watcher.ExitProcessor\n  alias OMG.Watcher.ExitProcessor.Core\n  alias OMG.Watcher.ExitProcessor.DoubleSpend\n  alias OMG.Watcher.ExitProcessor.InFlightExitInfo\n  alias OMG.Watcher.ExitProcessor.KnownTx\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.Utxo\n\n  import OMG.Watcher.ExitProcessor.Tools\n\n  require Transaction.Payment\n\n  require Logger\n\n  @type piggyback_type_t() :: :input | :output\n  @type piggyback_t() :: {piggyback_type_t(), non_neg_integer()}\n\n  @type input_challenge_data :: %{\n          in_flight_txbytes: Transaction.tx_bytes(),\n          in_flight_input_index: 0..3,\n          spending_txbytes: Transaction.tx_bytes(),\n          spending_input_index: 0..3,\n          spending_sig: <<_::520>>,\n          input_tx: Transaction.tx_bytes(),\n          input_utxo_pos: Utxo.Position.t()\n        }\n\n  @type output_challenge_data :: %{\n          in_flight_txbytes: Transaction.tx_bytes(),\n          in_flight_output_pos: pos_integer(),\n          in_flight_input_index: 4..7,\n          spending_txbytes: Transaction.tx_bytes(),\n          spending_input_index: 0..3,\n          spending_sig: <<_::520>>\n        }\n\n  @type piggyback_challenge_data_error() ::\n          :ife_not_known_for_tx\n          | Transaction.decode_error()\n          | :no_double_spend_on_particular_piggyback\n\n  def get_input_challenge_data(request, state, txbytes, input_index) do\n    case input_index in 0..(Transaction.Payment.max_inputs() - 1) do\n      true -> get_piggyback_challenge_data(request, state, txbytes, {:input, input_index})\n      false -> {:error, :piggybacked_index_out_of_range}\n    end\n  end\n\n  def get_output_challenge_data(request, state, txbytes, output_index) do\n    case output_index in 0..(Transaction.Payment.max_outputs() - 1) do\n      true -> get_piggyback_challenge_data(request, state, txbytes, {:output, output_index})\n      false -> {:error, :piggybacked_index_out_of_range}\n    end\n  end\n\n  @doc \"\"\"\n  Returns a tuple of ivalid piggybacks and invalid piggybacks that are past SLA margin.\n  This is inclusive, invalid piggybacks past SLA margin are included in the invalid piggybacks list.\n  \"\"\"\n  @spec get_invalid_piggybacks_events(Core.t(), KnownTx.known_txs_by_input_t(), pos_integer()) ::\n          {list(Event.InvalidPiggyback.t()), list(Event.UnchallengedPiggyback.t())}\n  def get_invalid_piggybacks_events(\n        %Core{sla_margin: sla_margin, in_flight_exits: ifes},\n        known_txs_by_input,\n        eth_height_now\n      ) do\n    invalid_piggybacks_by_ife =\n      ifes\n      |> Map.values()\n      |> all_invalid_piggybacks_by_ife(known_txs_by_input)\n\n    invalid_piggybacks_events = to_events(invalid_piggybacks_by_ife, &to_invalid_piggyback_event/1)\n\n    past_sla_margin = fn {ife, _type, _materials} ->\n      ife.eth_height + sla_margin <= eth_height_now\n    end\n\n    unchallenged_piggybacks_events =\n      invalid_piggybacks_by_ife\n      |> Enum.filter(past_sla_margin)\n      |> to_events(&to_unchallenged_piggyback_event/1)\n\n    {invalid_piggybacks_events, unchallenged_piggybacks_events}\n  end\n\n  defp all_invalid_piggybacks_by_ife(ifes_values, known_txs_by_input) do\n    [:input, :output]\n    |> Enum.flat_map(fn pb_type -> invalid_piggybacks_by_ife(known_txs_by_input, pb_type, ifes_values) end)\n  end\n\n  defp to_events(piggybacks_by_ife, to_event) do\n    piggybacks_by_ife\n    |> group_by_txbytes()\n    |> Enum.map(to_event)\n  end\n\n  defp to_invalid_piggyback_event({txbytes, type_materials_pairs}) do\n    %Event.InvalidPiggyback{\n      txbytes: txbytes,\n      inputs: invalid_piggyback_indices(type_materials_pairs, :input),\n      outputs: invalid_piggyback_indices(type_materials_pairs, :output)\n    }\n  end\n\n  defp to_unchallenged_piggyback_event({txbytes, type_materials_pairs}) do\n    %Event.UnchallengedPiggyback{\n      txbytes: txbytes,\n      inputs: invalid_piggyback_indices(type_materials_pairs, :input),\n      outputs: invalid_piggyback_indices(type_materials_pairs, :output)\n    }\n  end\n\n  # we need to produce only one event per IFE, with both piggybacks on inputs and outputs\n  defp group_by_txbytes(invalid_piggybacks) do\n    invalid_piggybacks\n    |> Enum.map(fn {ife, type, materials} -> {Transaction.raw_txbytes(ife.tx), type, materials} end)\n    |> Enum.group_by(&elem(&1, 0), fn {_, type, materials} -> {type, materials} end)\n  end\n\n  defp invalid_piggyback_indices(type_materials_pairs, pb_type) do\n    # here we need to additionally group the materials found by type input/output\n    # then we gut just the list of indices to present to the user in the event\n    type_materials_pairs\n    |> Enum.filter(fn {type, _materials} -> type == pb_type end)\n    |> Enum.flat_map(fn {_type, materials} -> Map.keys(materials) end)\n  end\n\n  @spec invalid_piggybacks_by_ife(KnownTx.known_txs_by_input_t(), piggyback_type_t(), list(InFlightExitInfo.t())) ::\n          list({InFlightExitInfo.t(), piggyback_type_t(), %{non_neg_integer => DoubleSpend.t()}})\n  defp invalid_piggybacks_by_ife(known_txs_by_input, pb_type, ifes) do\n    ifes\n    |> Enum.map(&InFlightExitInfo.unchallenged_piggybacks_by_ife(&1, pb_type))\n    |> Enum.filter(&ife_has_something?/1)\n    |> Enum.map(fn {ife, indexed_piggybacked_utxo_positions} ->\n      proof_materials =\n        DoubleSpend.all_double_spends_by_index(indexed_piggybacked_utxo_positions, known_txs_by_input, ife.tx)\n\n      {ife, pb_type, proof_materials}\n    end)\n    |> Enum.filter(&ife_has_something?/1)\n  end\n\n  defp ife_has_something?({_ife, finds_for_ife}), do: !Enum.empty?(finds_for_ife)\n  defp ife_has_something?({_ife, _, finds_for_ife}), do: !Enum.empty?(finds_for_ife)\n\n  @spec get_piggyback_challenge_data(ExitProcessor.Request.t(), Core.t(), binary(), piggyback_t()) ::\n          {:ok, input_challenge_data() | output_challenge_data()} | {:error, piggyback_challenge_data_error()}\n  defp get_piggyback_challenge_data(%ExitProcessor.Request{blocks_result: blocks}, state, txbytes, piggyback) do\n    with {:ok, tx} <- Transaction.decode(txbytes),\n         {:ok, ife} <- get_ife(tx, state.in_flight_exits) do\n      known_txs_by_input = KnownTx.get_all_from_blocks_appendix(blocks, state)\n      produce_invalid_piggyback_proof(ife, known_txs_by_input, piggyback)\n    end\n  end\n\n  @spec produce_invalid_piggyback_proof(InFlightExitInfo.t(), KnownTx.known_txs_by_input_t(), piggyback_t()) ::\n          {:ok, input_challenge_data() | output_challenge_data()} | {:error, :no_double_spend_on_particular_piggyback}\n  defp produce_invalid_piggyback_proof(ife, known_txs_by_input, {pb_type, pb_index} = piggyback) do\n    with {:ok, proof_materials} <- get_proofs_for_particular_ife(ife, pb_type, known_txs_by_input),\n         {:ok, proof} <- get_proof_for_particular_piggyback(pb_index, proof_materials) do\n      {:ok, prepare_piggyback_challenge_response(ife, piggyback, proof)}\n    end\n  end\n\n  # gets all proof materials for all possibly invalid piggybacks for a single ife, for a determined type (input/output)\n  defp get_proofs_for_particular_ife(ife, pb_type, known_txs_by_input) do\n    invalid_piggybacks_by_ife(known_txs_by_input, pb_type, [ife])\n    |> case do\n      [] -> {:error, :no_double_spend_on_particular_piggyback}\n      # ife and pb_type are pinned here for a runtime sanity check - we got what we explicitly asked for\n      [{^ife, ^pb_type, proof_materials}] -> {:ok, proof_materials}\n    end\n  end\n\n  # gets any proof of a particular invalid piggyback, after we have figured the exact piggyback index affected\n  defp get_proof_for_particular_piggyback(pb_index, proof_materials) do\n    proof_materials\n    |> Map.get(pb_index)\n    |> case do\n      nil -> {:error, :no_double_spend_on_particular_piggyback}\n      # any challenging tx will do, taking the very first\n      [proof | _] -> {:ok, proof}\n    end\n  end\n\n  @spec prepare_piggyback_challenge_response(InFlightExitInfo.t(), piggyback_t(), DoubleSpend.t()) ::\n          input_challenge_data() | output_challenge_data()\n  defp prepare_piggyback_challenge_response(ife, {:input, input_index}, proof) do\n    %{\n      in_flight_txbytes: Transaction.raw_txbytes(ife.tx),\n      in_flight_input_index: input_index,\n      spending_txbytes: Transaction.raw_txbytes(proof.known_tx.signed_tx),\n      spending_input_index: proof.known_spent_index,\n      spending_sig: Enum.at(proof.known_tx.signed_tx.sigs, proof.known_spent_index),\n      input_tx: Enum.at(ife.input_txs, input_index),\n      input_utxo_pos: Enum.at(ife.input_utxos_pos, input_index)\n    }\n  end\n\n  defp prepare_piggyback_challenge_response(ife, {:output, _output_index}, proof) do\n    {_, inclusion_proof} = ife.tx_seen_in_blocks_at\n\n    %{\n      in_flight_txbytes: Transaction.raw_txbytes(ife.tx),\n      in_flight_output_pos: proof.utxo_pos,\n      in_flight_proof: inclusion_proof,\n      spending_txbytes: Transaction.raw_txbytes(proof.known_tx.signed_tx),\n      spending_input_index: proof.known_spent_index,\n      spending_sig: Enum.at(proof.known_tx.signed_tx.sigs, proof.known_spent_index)\n    }\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/exit_processor/request.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.ExitProcessor.Request do\n  @moduledoc \"\"\"\n  Encapsulates the state of processing of `OMG.Watcher.ExitProcessor` pipelines\n\n  Holds all the necessary query date and the respective response\n  \"\"\"\n\n  alias OMG.Watcher.Block\n  alias OMG.Watcher.Utxo\n\n  defstruct [\n    :eth_height_now,\n    :blknum_now,\n    utxos_to_check: [],\n    spends_to_get: [],\n    blknums_to_get: [],\n    ife_input_utxos_to_check: [],\n    ife_input_spends_to_get: [],\n    piggybacked_blknums_to_get: [],\n    utxo_exists_result: [],\n    blocks_result: [],\n    ife_input_utxo_exists_result: [],\n    ife_input_spending_blocks_result: [],\n    se_exiting_pos: nil,\n    se_spending_blocks_to_get: [],\n    se_spending_blocks_result: []\n  ]\n\n  @type t :: %__MODULE__{\n          eth_height_now: nil | pos_integer,\n          blknum_now: nil | pos_integer,\n          utxos_to_check: list(Utxo.Position.t()),\n          spends_to_get: list(Utxo.Position.t()),\n          blknums_to_get: list(pos_integer),\n          ife_input_utxos_to_check: list(Utxo.Position.t()),\n          ife_input_spends_to_get: list(Utxo.Position.t()),\n          piggybacked_blknums_to_get: list(pos_integer),\n          utxo_exists_result: list(boolean),\n          blocks_result: list(Block.t()),\n          ife_input_utxo_exists_result: list(boolean),\n          ife_input_spending_blocks_result: list(Block.t()),\n          se_exiting_pos: nil | Utxo.Position.t(),\n          se_spending_blocks_to_get: list(Utxo.Position.t()),\n          se_spending_blocks_result: list(Block.t())\n        }\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/exit_processor/standard_exit.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.ExitProcessor.StandardExit do\n  @moduledoc \"\"\"\n  Part of Core to handle SE challenges & invalid exit detection.\n\n  Treat as private helper submodule of `OMG.Watcher.ExitProcessor.Core`, test and call via that\n  \"\"\"\n\n  defmodule Challenge do\n    @moduledoc \"\"\"\n    Represents a challenge to a standard exit as returned by the `ExitProcessor`\n    \"\"\"\n    @enforce_keys [:exit_id, :exiting_tx, :txbytes, :input_index, :sig]\n    defstruct @enforce_keys\n\n    alias OMG.Watcher.Crypto\n    alias OMG.Watcher.State.Transaction\n\n    @type t() :: %__MODULE__{\n            exit_id: pos_integer(),\n            exiting_tx: Transaction.tx_bytes(),\n            txbytes: Transaction.tx_bytes(),\n            input_index: non_neg_integer(),\n            sig: Crypto.sig_t()\n          }\n  end\n\n  alias OMG.Watcher.Block\n  alias OMG.Watcher.ExitProcessor\n  alias OMG.Watcher.ExitProcessor.Core\n  alias OMG.Watcher.ExitProcessor.DoubleSpend\n  alias OMG.Watcher.ExitProcessor.ExitInfo\n  alias OMG.Watcher.ExitProcessor.KnownTx\n  alias OMG.Watcher.ExitProcessor.TxAppendix\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.Utxo\n  import OMG.Watcher.ExitProcessor.Tools\n\n  require Utxo\n\n  @doc \"\"\"\n  Gets all utxo positions exiting via active standard exits\n  \"\"\"\n  @spec exiting_positions(Core.t()) :: list(Utxo.Position.t())\n  def exiting_positions(%Core{} = state) do\n    state\n    |> active_exits()\n    |> Enum.map(fn {utxo_pos, _value} -> utxo_pos end)\n  end\n\n  @doc \"\"\"\n  Gets all standard exits that are invalid, all and late ones separately, also adds their :spending_txhash\n  \"\"\"\n  @spec get_invalid(Core.t(), %{Utxo.Position.t() => boolean}, pos_integer()) ::\n          {%{Utxo.Position.t() => ExitInfo.t()}, %{Utxo.Position.t() => ExitInfo.t()}}\n  def get_invalid(%Core{sla_margin: sla_margin} = state, utxo_exists?, eth_height_now) do\n    active_exits = active_exits(state)\n\n    exits_invalid_by_ife =\n      state\n      |> TxAppendix.get_all()\n      |> get_invalid_exits_based_on_ifes(active_exits)\n\n    invalid_exit_positions =\n      active_exits\n      |> Enum.map(fn {utxo_pos, _value} -> utxo_pos end)\n      |> only_utxos_checked_and_missing(utxo_exists?)\n\n    standard_invalid_exits =\n      active_exits\n      |> Map.take(invalid_exit_positions)\n      |> Enum.map(fn {utxo_pos, invalid_exit} ->\n        spending_txhash = spending_txhash_for_exit_at(utxo_pos)\n\n        {utxo_pos, %{invalid_exit | spending_txhash: spending_txhash}}\n      end)\n\n    invalid_exits = standard_invalid_exits |> Enum.concat(exits_invalid_by_ife) |> Enum.uniq()\n\n    # get exits which are still invalid and after the SLA margin\n    late_invalid_exits =\n      Enum.filter(invalid_exits, fn {_, %ExitInfo{eth_height: eth_height}} ->\n        eth_height + sla_margin <= eth_height_now\n      end)\n\n    {Map.new(invalid_exits), Map.new(late_invalid_exits)}\n  end\n\n  defp spending_txhash_for_exit_at(utxo_pos) do\n    utxo_pos\n    |> Utxo.Position.to_input_db_key()\n    |> OMG.DB.spent_blknum()\n    |> List.wrap()\n    |> Core.handle_spent_blknum_result([utxo_pos])\n    |> do_get_blocks()\n    |> case do\n      [block] ->\n        %DoubleSpend{known_tx: %KnownTx{signed_tx: spending_tx}} = get_double_spend_for_standard_exit(block, utxo_pos)\n        Transaction.raw_txhash(spending_tx)\n\n      _ ->\n        nil\n    end\n  end\n\n  defp do_get_blocks(blknums) do\n    {:ok, hashes} = OMG.DB.block_hashes(blknums)\n    {:ok, blocks} = OMG.DB.blocks(hashes)\n\n    Enum.map(blocks, &Block.from_db_value/1)\n  end\n\n  @doc \"\"\"\n  Determines the utxo-creating and utxo-spending blocks to get from `OMG.DB`\n  `se_spending_blocks_to_get` are requested by the UTXO position they spend\n  \"\"\"\n  @spec determine_standard_challenge_queries(ExitProcessor.Request.t(), Core.t(), boolean()) ::\n          {:ok, ExitProcessor.Request.t()} | {:error, :exit_not_found | :utxo_not_spent}\n  def determine_standard_challenge_queries(\n        %ExitProcessor.Request{se_exiting_pos: Utxo.position(_, _, _) = exiting_pos} = request,\n        %Core{exits: exits} = state,\n        exiting_utxo_exists\n      ) do\n    with {:ok, _exit_info} <- get_exit(exits, exiting_pos),\n         # once figured out the exit exists, check if it is spent in an IFE?\n         ife_based_on_utxo = get_ife_based_on_utxo(exiting_pos, state),\n         # To be challengable, the exit utxo must be spent in either an IFE or missing from the `OMG.Watcher.State`.\n         # In the latter case we'll go on looking for the spending tx in the `OMG.DB`\n         true <- !is_nil(ife_based_on_utxo) || !exiting_utxo_exists || {:error, :utxo_not_spent} do\n      # if the exit utxo is spent in an IFE no need to bother with looking for the spending tx in the blocks\n      spending_blocks_to_get = if ife_based_on_utxo, do: [], else: [exiting_pos]\n\n      {:ok, %ExitProcessor.Request{request | se_spending_blocks_to_get: spending_blocks_to_get}}\n    end\n  end\n\n  @doc \"\"\"\n  Creates the final challenge response, if possible\n  \"\"\"\n  @spec create_challenge(ExitProcessor.Request.t(), Core.t()) ::\n          {:ok, Challenge.t()} | {:error, :utxo_not_spent}\n  def create_challenge(\n        %ExitProcessor.Request{se_exiting_pos: exiting_pos, se_spending_blocks_result: spending_blocks_result},\n        %Core{exits: exits} = state\n      )\n      when not is_nil(exiting_pos) do\n    %ExitInfo{owner: owner, exit_id: exit_id, exiting_txbytes: exiting_txbytes} = exits[exiting_pos]\n    ife_result = get_ife_based_on_utxo(exiting_pos, state)\n\n    with {:ok, spending_tx_or_block} <- ensure_challengeable(spending_blocks_result, ife_result) do\n      %DoubleSpend{known_spent_index: input_index, known_tx: %KnownTx{signed_tx: challenging_signed}} =\n        get_double_spend_for_standard_exit(spending_tx_or_block, exiting_pos)\n\n      {:ok,\n       %Challenge{\n         exit_id: exit_id,\n         input_index: input_index,\n         exiting_tx: exiting_txbytes,\n         txbytes: challenging_signed |> Transaction.raw_txbytes(),\n         sig: find_sig!(challenging_signed, owner)\n       }}\n    end\n  end\n\n  defp ensure_challengeable(spending_blknum_response, ife_response)\n\n  defp ensure_challengeable([%Block{} = block], _), do: {:ok, block}\n  defp ensure_challengeable(_, ife_response) when not is_nil(ife_response), do: {:ok, ife_response}\n  defp ensure_challengeable(_, _), do: {:error, :utxo_not_spent}\n\n  @spec get_ife_based_on_utxo(Utxo.Position.t(), Core.t()) :: KnownTx.t() | nil\n  defp get_ife_based_on_utxo(Utxo.position(_, _, _) = utxo_pos, %Core{} = state) do\n    state\n    |> TxAppendix.get_all()\n    |> get_ife_txs_by_spent_input()\n    |> Map.get(utxo_pos)\n    |> case do\n      nil -> nil\n      some -> Enum.at(some, 0)\n    end\n  end\n\n  # finds transaction in given block and input index spending given utxo\n  @spec get_double_spend_for_standard_exit(Block.t() | KnownTx.t(), Utxo.Position.t()) :: DoubleSpend.t() | nil\n  defp get_double_spend_for_standard_exit(%Block{transactions: txs}, utxo_pos) do\n    txs\n    |> Enum.map(&Transaction.Signed.decode!/1)\n    |> Enum.find_value(fn tx -> get_double_spend_for_standard_exit(%KnownTx{signed_tx: tx}, utxo_pos) end)\n  end\n\n  defp get_double_spend_for_standard_exit(%KnownTx{} = known_tx, utxo_pos) do\n    Enum.at(get_double_spends_by_utxo_pos(utxo_pos, known_tx), 0)\n  end\n\n  # Gets all standard exits invalidated by IFEs exiting their utxo positions and append the spending_txhash\n  @spec get_invalid_exits_based_on_ifes(TxAppendix.t(), %{Utxo.Position.t() => ExitInfo.t()}) ::\n          list({Utxo.Position.t(), ExitInfo.t()})\n  defp get_invalid_exits_based_on_ifes(tx_appendix, active_exits) do\n    known_txs_by_input = get_ife_txs_by_spent_input(tx_appendix)\n\n    active_exits\n    |> Enum.filter(fn {utxo_pos, _exit_info} -> Map.has_key?(known_txs_by_input, utxo_pos) end)\n    |> Enum.map(fn {utxo_pos, exit_info} ->\n      spending_txhash =\n        known_txs_by_input\n        |> Map.get(utxo_pos)\n        |> Enum.at(0)\n        |> Map.get(:signed_tx)\n        |> Transaction.raw_txhash()\n\n      {utxo_pos, %{exit_info | spending_txhash: spending_txhash}}\n    end)\n  end\n\n  @spec get_double_spends_by_utxo_pos(Utxo.Position.t(), KnownTx.t()) :: list(DoubleSpend.t())\n  defp get_double_spends_by_utxo_pos(Utxo.position(_, _, oindex) = utxo_pos, known_tx),\n    # the function used expects positions with an index (either input index or oindex), hence the oindex added\n    do: [{utxo_pos, oindex}] |> double_spends_from_known_tx(known_tx)\n\n  defp get_ife_txs_by_spent_input(tx_appendix) do\n    tx_appendix\n    |> Enum.map(fn signed -> %KnownTx{signed_tx: signed} end)\n    |> KnownTx.group_txs_by_input()\n  end\n\n  defp get_exit(exits, exiting_pos) do\n    case Map.get(exits, exiting_pos) do\n      nil -> {:error, :exit_not_found}\n      other -> {:ok, other}\n    end\n  end\n\n  defp active_exits(%Core{exits: exits}),\n    do:\n      exits\n      |> Enum.filter(fn {_key, %ExitInfo{is_active: is_active}} -> is_active end)\n      |> Map.new()\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/exit_processor/tools.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.ExitProcessor.Tools do\n  @moduledoc \"\"\"\n  Private tools that various components of the `ExitProcessor` share\n  \"\"\"\n\n  alias OMG.Watcher.Crypto\n  alias OMG.Watcher.ExitProcessor.DoubleSpend\n  alias OMG.Watcher.ExitProcessor.KnownTx\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.TypedDataHash\n  alias OMG.Watcher.Utxo\n\n  require OMG.Watcher.Utxo\n\n  @typep eth_event_t() :: %{root_chain_txhash: Crypto.hash_t(), log_index: non_neg_integer()}\n  @typep eth_event_with_exiting_positions_t() :: {eth_event_t(), list(Utxo.Position.t())} | eth_event_t()\n\n  # Intersects utxos, looking for duplicates. Gives full list of double-spends with indexes for\n  # a pair of transactions.\n  @spec double_spends_from_known_tx(list({Utxo.Position.t(), non_neg_integer()}), KnownTx.t()) ::\n          list(DoubleSpend.t())\n  def double_spends_from_known_tx(inputs, %KnownTx{signed_tx: signed} = known_tx) when is_list(inputs) do\n    known_spent_inputs = signed |> Transaction.get_inputs() |> Enum.with_index()\n\n    # NOTE: possibly ineffective if Transaction.Payment.max_inputs >> 4, BUT we're calling it seldom so no biggie\n    for {left, left_index} <- inputs,\n        {right, right_index} <- known_spent_inputs,\n        left == right,\n        do: %DoubleSpend{index: left_index, utxo_pos: left, known_spent_index: right_index, known_tx: known_tx}\n  end\n\n  # based on an enumberable of `Utxo.Position` and a mapping that tells whether one exists it will pick\n  # only those that **were checked** and were missing\n  # (i.e. those not checked are assumed to be present)\n  def only_utxos_checked_and_missing(utxo_positions, utxo_exists?) do\n    # the default value below is true, so that the assumption is that utxo not checked is **present**\n    Enum.filter(utxo_positions, fn utxo_pos -> !Map.get(utxo_exists?, utxo_pos, true) end)\n  end\n\n  @doc \"\"\"\n  Finds the exact signature which signed the particular transaction for the given owner address\n  \"\"\"\n  @spec find_sig(Transaction.Signed.t(), Crypto.address_t()) :: {:ok, Crypto.sig_t()} | nil\n  def find_sig(%Transaction.Signed{sigs: sigs, raw_tx: raw_tx}, owner) do\n    tx_hash = TypedDataHash.hash_struct(raw_tx)\n\n    Enum.find(sigs, fn sig ->\n      {:ok, owner} == Crypto.recover_address(tx_hash, sig)\n    end)\n    |> case do\n      nil -> nil\n      other -> {:ok, other}\n    end\n  end\n\n  @doc \"\"\"\n  Throwing version of `find_sig/2`\n\n  At some point having a tx that wasn't actually signed is an error, hence pattern match\n  if `find_sig/2` returns nil it means somethings very wrong - the owner taken (effectively) from the contract\n  doesn't appear to have signed the potential competitor, which means that some prior signature checking was skipped\n  \"\"\"\n  def find_sig!(tx, owner) do\n    {:ok, sig} = find_sig(tx, owner)\n    sig\n  end\n\n  def txs_different(tx1, tx2), do: Transaction.raw_txhash(tx1) != Transaction.raw_txhash(tx2)\n\n  def get_ife(ife_tx, ifes) do\n    case ifes[Transaction.raw_txhash(ife_tx)] do\n      nil -> {:error, :ife_not_known_for_tx}\n      value -> {:ok, value}\n    end\n  end\n\n  @doc \"\"\"\n  Transforms Ethereum events like InFlightExitStarted or InFlightExitOutputWithdrawn\n  to form that can be consumed by subscribers\n  \"\"\"\n  @spec to_bus_events_data(list(eth_event_with_exiting_positions_t())) ::\n          list(%{\n            call_data: map(),\n            root_chain_txhash: charlist(),\n            log_index: non_neg_integer(),\n            eth_height: pos_integer()\n          })\n  def to_bus_events_data(eth_events_with_exiting_utxos) do\n    Enum.reduce(eth_events_with_exiting_utxos, [], &to_bus_events_reducer/2)\n  end\n\n  defp to_bus_events_reducer(\n         {%{root_chain_txhash: root_chain_txhash, log_index: log_index, eth_height: eth_height}, utxo_positions},\n         bus_events\n       ) do\n    utxo_pos_transform = fn\n      Utxo.position(_, _, _) = u -> Utxo.Position.encode(u)\n      encoded when is_integer(encoded) -> encoded\n    end\n\n    utxo_positions\n    |> Enum.map(\n      &%{\n        call_data: %{utxo_pos: utxo_pos_transform.(&1)},\n        root_chain_txhash: root_chain_txhash,\n        eth_height: eth_height,\n        log_index: log_index\n      }\n    )\n    |> Enum.concat(bus_events)\n  end\n\n  defp to_bus_events_reducer(%{omg_data: %{piggyback_type: :input}}, bus_events) do\n    # In-flight transaction's inputs are spend when IFE is started we are not interested with input piggybacks\n    bus_events\n  end\n\n  defp to_bus_events_reducer(\n         %{\n           root_chain_txhash: root_chain_txhash,\n           log_index: log_index,\n           eth_height: eth_height,\n           omg_data: %{piggyback_type: :output},\n           tx_hash: txhash,\n           output_index: oindex\n         },\n         bus_events\n       ) do\n    # Note: It cannot be deposit as it is piggyback to output, so output is created by in-flight transaction\n    # If transaction was included in plasma block, output is created and could be spend by this event\n    [\n      %{\n        call_data: %{txhash: txhash, oindex: oindex},\n        root_chain_txhash: root_chain_txhash,\n        log_index: log_index,\n        eth_height: eth_height\n      }\n      | bus_events\n    ]\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/exit_processor/tx_appendix.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.ExitProcessor.TxAppendix do\n  @moduledoc \"\"\"\n  Part of the exit processor serving as the API to the transaction appendix\n\n  Transaction appendix (TxAppendix) serves the transactions that were witnessed, but aren't included in the blocks\n  \"\"\"\n\n  alias OMG.Watcher.ExitProcessor\n\n  @doc \"\"\"\n  Enumerable of `Transaction.Signed.t()`\n  \"\"\"\n  @type t() :: Enumerable.t()\n\n  @spec get_all(ExitProcessor.Core.t()) :: t()\n  def get_all(%ExitProcessor.Core{in_flight_exits: ifes, competitors: competitors}) do\n    ifes\n    |> Map.values()\n    |> Stream.concat(Map.values(competitors))\n    |> Stream.map(&Map.get(&1, :tx))\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/exit_processor.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.ExitProcessor do\n  @moduledoc \"\"\"\n  Tracks and handles the exits from the child chain, their validity and challenges.\n\n  Keeps a state of exits that are in progress, updates it with news from the root chain contract, compares to the\n  state of the ledger (`OMG.Watcher.State`), issues notifications as it finds suitable.\n\n  Should manage all kinds of exits allowed in the protocol and handle the interactions between them.\n\n  For functional logic and more info see `OMG.Watcher.ExitProcessor.Core`\n\n  NOTE: Note that all calls return `db_updates` and relay on the caller to do persistence.\n  \"\"\"\n\n  alias OMG.DB\n  alias OMG.DB.Models.PaymentExitInfo\n  alias OMG.Eth\n  alias OMG.Eth.EthereumHeight\n  alias OMG.Eth.RootChain\n  alias OMG.Watcher.Block\n  alias OMG.Watcher.ExitProcessor\n  alias OMG.Watcher.ExitProcessor.Core\n  alias OMG.Watcher.ExitProcessor.ExitInfo\n  alias OMG.Watcher.ExitProcessor.StandardExit\n  alias OMG.Watcher.ExitProcessor.Tools\n  alias OMG.Watcher.State\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.Utxo\n\n  require Logger\n  require Utxo\n\n  @timeout 60_000\n\n  ### Client\n\n  @doc \"\"\"\n  Starts the `GenServer` process with options. For documentation of the options see `init/1`\n  \"\"\"\n  def start_link(args) do\n    GenServer.start_link(__MODULE__, args, name: __MODULE__)\n  end\n\n  @doc \"\"\"\n  Accepts events and processes them in the state - new exits are tracked.\n\n  Returns `db_updates` to be sent to `OMG.DB` by the caller\n\n  - takes a list of standard exit start events from the contract\n  - fetches the currently observed exit status in the contract (to decide if exits are \"inactive on recognition\", which\n    helps cover the case when the Watcher is syncing up)\n  - updates the `ExitProcessor`'s state\n  - returns `db_updates`\n  \"\"\"\n\n  def new_exits([]), do: {:ok, []}\n\n  def new_exits(exits) do\n    GenServer.call(__MODULE__, {:new_exits, exits}, @timeout)\n  end\n\n  @doc \"\"\"\n  Accepts events and processes them in the state - new in flight exits are tracked.\n\n  Returns `db_updates` to be sent to `OMG.DB` by the caller\n\n  - takes a list of IFE exit start events from the contract\n  - fetches the currently observed exit status in the contract (to decide if exits are \"inactive on recognition\", which\n    helps cover the case when the Watcher is syncing up)\n  - updates the `ExitProcessor`'s state\n  - returns `db_updates`\n  \"\"\"\n  # empty list clause to not block the server for no-ops\n  def new_in_flight_exits([]), do: {:ok, []}\n\n  def new_in_flight_exits(in_flight_exit_started_events) do\n    GenServer.call(__MODULE__, {:new_in_flight_exits, in_flight_exit_started_events}, @timeout)\n  end\n\n  @doc \"\"\"\n  Accepts events and processes them in the state - new in flight exits are tracked.\n\n  Returns `db_updates` to be sent to `OMG.DB` by the caller\n  - spends input utxos\n  - deletes in-flight exits from state\n  \"\"\"\n\n  def delete_in_flight_exits([]), do: {:ok, []}\n\n  def delete_in_flight_exits(in_flight_exit_deleted_events) do\n    GenServer.call(__MODULE__, {:delete_in_flight_exits, in_flight_exit_deleted_events}, @timeout)\n  end\n\n  @doc \"\"\"\n  Accepts events and processes them in the state - finalized exits are untracked _if valid_ otherwise raises alert\n\n  Returns `db_updates` to be sent to `OMG.DB` by the caller\n  - takes a list of standard exit finalization events from the contract\n  - discovers the `OMG.Watcher.State`'s native key for the finalizing exits (`utxo_pos`) (`Core.exit_key_by_exit_id/2`)\n  - marks as spent these UTXOs in `OMG.Watcher.State` expecting it to tell which of those were valid finalizations (UTXOs exist)\n  - reflects this result in the `ExitProcessor`'s state\n  - returns `db_updates`, concatenated with those related to the call to `OMG.Watcher.State`\n\n  \"\"\"\n\n  def finalize_exits([]), do: {:ok, []}\n\n  def finalize_exits(finalizations) do\n    GenServer.call(__MODULE__, {:finalize_exits, finalizations}, @timeout)\n  end\n\n  @doc \"\"\"\n  Accepts events and processes them in the state - new piggybacks are tracked, if invalid raises an alert\n\n  Returns `db_updates` to be sent to `OMG.DB` by the caller\n  - takes a list of IFE piggybacking events from the contract\n  - updates the `ExitProcessor`'s state\n  - returns `db_updates`\n  \"\"\"\n\n  def piggyback_exits([]), do: {:ok, []}\n\n  def piggyback_exits(piggybacks) do\n    GenServer.call(__MODULE__, {:piggyback_exits, piggybacks}, @timeout)\n  end\n\n  @doc \"\"\"\n  Accepts events and processes them in the state - challenged exits are untracked\n\n  Returns `db_updates` to be sent to `OMG.DB` by the caller\n  - takes a list of standard exit challenge events from the contract\n  - updates the `ExitProcessor`'s state\n  - returns `db_updates`\n  \"\"\"\n\n  def challenge_exits([]), do: {:ok, []}\n\n  def challenge_exits(challenges) do\n    GenServer.call(__MODULE__, {:challenge_exits, challenges}, @timeout)\n  end\n\n  @doc \"\"\"\n  Accepts events and processes them in the state.\n\n  Marks the challenged IFE as non-canonical and persists information about the competitor and its age.\n\n  Competitors are stored for future use (i.e. to challenge an in flight exit).\n\n  Returns `db_updates` to be sent to `OMG.DB` by the caller\n    - takes a list of IFE exit canonicity challenge events from the contract\n  - updates the `ExitProcessor`'s state\n  - returns `db_updates`\n  \"\"\"\n\n  def new_ife_challenges([]), do: {:ok, []}\n\n  def new_ife_challenges(challenges) do\n    GenServer.call(__MODULE__, {:new_ife_challenges, challenges}, @timeout)\n  end\n\n  @doc \"\"\"\n  Accepts events and processes them in state.\n\n  Marks the IFE as canonical and perists information about the inclusion age as responded with in the contract.\n\n  Returns `db_updates` to be sent to `OMG.DB` by the caller\n  - takes a list of IFE exit canonicity challenge response events from the contract\n  - updates the `ExitProcessor`'s state\n  - returns `db_updates`\n  \"\"\"\n\n  def respond_to_in_flight_exits_challenges([]), do: {:ok, []}\n\n  def respond_to_in_flight_exits_challenges(responds) do\n    GenServer.call(__MODULE__, {:respond_to_in_flight_exits_challenges, responds}, @timeout)\n  end\n\n  @doc \"\"\"\n  Accepts events and processes them in state.\n\n  Returns `db_updates` to be sent to `OMG.DB` by the caller\n    - takes a list of IFE piggyback challenge events from the contract\n  - updates the `ExitProcessor`'s state\n  - returns `db_updates`\n  \"\"\"\n\n  def challenge_piggybacks([]), do: {:ok, []}\n\n  def challenge_piggybacks(challenges) do\n    GenServer.call(__MODULE__, {:challenge_piggybacks, challenges}, @timeout)\n  end\n\n  @doc \"\"\"\n  Accepts events and processes them in state - finalized outputs are applied to the state.\n\n  Returns `db_updates` to be sent to `OMG.DB` by the caller\n  - takes a list of IFE exit finalization events from the contract\n  - pulls current information on IFE transaction inclusion\n  - discovers the `OMG.Watcher.State`'s native key for the finalizing exits (`utxo_pos`)\n    (`Core.prepare_utxo_exits_for_in_flight_exit_finalizations/2`)\n  - marks as spent these UTXOs in `OMG.Watcher.State` expecting it to tell which of those were valid finalizations (UTXOs exist)\n  - reflects this result in the `ExitProcessor`'s state\n  - returns `db_updates`, concatenated with those related to the call to `OMG.Watcher.State`\n\n  \"\"\"\n\n  def finalize_in_flight_exits([]), do: {:ok, []}\n\n  def finalize_in_flight_exits(finalizations) do\n    GenServer.call(__MODULE__, {:finalize_in_flight_exits, finalizations}, @timeout)\n  end\n\n  @doc \"\"\"\n  Checks validity of all exit-related events and returns the list of actionable items.\n  Works with `OMG.Watcher.State` to discern validity.\n\n  This function may also update some internal caches to make subsequent calls not redo the work,\n  but under unchanged conditions, it should have unchanged behavior from POV of an outside caller.\n\n  - pulls current information on IFE transaction inclusion\n  - gets a list of interesting UTXOs to check for existence in `OMG.Watcher.State`\n  - combines this information to discover the state of all the exits to report (mainly byzantine events)\n\n  \"\"\"\n  def check_validity() do\n    GenServer.call(__MODULE__, :check_validity, @timeout)\n  end\n\n  def check_validity(timeout) do\n    GenServer.call(__MODULE__, :check_validity, timeout)\n  end\n\n  @doc \"\"\"\n  Returns a map of requested in flight exits, keyed by transaction hash\n  \"\"\"\n  @spec get_active_in_flight_exits() :: {:ok, Core.in_flight_exits_response_t()}\n  def get_active_in_flight_exits() do\n    GenServer.call(__MODULE__, :get_active_in_flight_exits, @timeout)\n  end\n\n  @doc \"\"\"\n  Returns all information required to produce a transaction to the root chain contract to present a competitor for\n  a non-canonical in-flight exit\n\n  - pulls current information on IFE transaction inclusion\n  - gets a list of interesting UTXOs to check for existence in `OMG.Watcher.State`\n  - combines this information to compose the challenge data\n  \"\"\"\n  @spec get_competitor_for_ife(binary()) ::\n          {:ok, ExitProcessor.Canonicity.competitor_data_t()}\n          | {:error, :competitor_not_found}\n          | {:error, :no_viable_competitor_found}\n  def get_competitor_for_ife(txbytes) do\n    GenServer.call(__MODULE__, {:get_competitor_for_ife, txbytes}, @timeout)\n  end\n\n  @doc \"\"\"\n  Returns all information required to produce a transaction to the root chain contract to present a proof of canonicity\n  for a challenged in-flight exit\n\n  - pulls current information on IFE transaction inclusion\n  - gets a list of interesting UTXOs to check for existence in `OMG.Watcher.State`\n  - combines this information to compose the challenge data\n  \"\"\"\n  @spec prove_canonical_for_ife(binary()) ::\n          {:ok, ExitProcessor.Canonicity.prove_canonical_data_t()} | {:error, :no_viable_canonical_proof_found}\n  def prove_canonical_for_ife(txbytes) do\n    GenServer.call(__MODULE__, {:prove_canonical_for_ife, txbytes}, @timeout)\n  end\n\n  @doc \"\"\"\n  Returns all information required to challenge an invalid input piggyback\n  - gets a list of interesting UTXOs to check for existence in `OMG.Watcher.State`\n  - combines this information to compose the challenge data\n  \"\"\"\n  @spec get_input_challenge_data(Transaction.Signed.tx_bytes(), Transaction.input_index_t()) ::\n          {:ok, ExitProcessor.Piggyback.input_challenge_data()}\n          | {:error, ExitProcessor.Piggyback.piggyback_challenge_data_error()}\n  def get_input_challenge_data(txbytes, input_index) do\n    GenServer.call(__MODULE__, {:get_input_challenge_data, txbytes, input_index}, @timeout)\n  end\n\n  @doc \"\"\"\n  Returns all information required to challenge an invalid output piggyback\n  - pulls current information on IFE transaction inclusion\n  - gets a list of interesting UTXOs to check for existence in `OMG.Watcher.State`\n  - combines this information to compose the challenge data\n\n  \"\"\"\n  @spec get_output_challenge_data(Transaction.Signed.tx_bytes(), Transaction.input_index_t()) ::\n          {:ok, ExitProcessor.Piggyback.output_challenge_data()}\n          | {:error, ExitProcessor.Piggyback.piggyback_challenge_data_error()}\n  def get_output_challenge_data(txbytes, output_index) do\n    GenServer.call(__MODULE__, {:get_output_challenge_data, txbytes, output_index}, @timeout)\n  end\n\n  @doc \"\"\"\n  Returns challenge for an invalid standard exit\n  - leverages `OMG.Watcher.State` to quickly learn if the exiting UTXO exists or was spent\n  - pulls some additional data from `OMG.DB`, if needed\n  - combines this information to compose the challenge data\n  \"\"\"\n  @spec create_challenge(Utxo.Position.t()) ::\n          {:ok, StandardExit.Challenge.t()} | {:error, :utxo_not_spent | :exit_not_found}\n  def create_challenge(exiting_utxo_pos) do\n    GenServer.call(__MODULE__, {:create_challenge, exiting_utxo_pos}, @timeout)\n  end\n\n  ### Server\n\n  use GenServer\n\n  @doc \"\"\"\n  Initializes the state of the `ExitProcessor`'s `GenServer`.\n\n  Reads the exit data from `OMG.DB`.\n\n  Options:\n    - `exit_processor_sla_margin`: number of blocks after exit start before it's considered late (and potentially:\n      unchallenged)\n    - `exit_processor_sla_margin_forced`: if `true` will override the check of `exit_processor_sla_margin` against\n      `min_exit_period_seconds`\n    - `min_exit_period_seconds`: should reflect the value of this parameter for the specific child chain watched,\n    - `ethereum_block_time_seconds`: just to relate blocks to seconds for the `exit_processor_sla_margin` check\n    - `metrics_collection_interval`: how often are the metrics sent to `telemetry` (in milliseconds)\n  \"\"\"\n  def init(\n        exit_processor_sla_margin: exit_processor_sla_margin,\n        exit_processor_sla_margin_forced: exit_processor_sla_margin_forced,\n        metrics_collection_interval: metrics_collection_interval,\n        min_exit_period_seconds: min_exit_period_seconds,\n        ethereum_block_time_seconds: ethereum_block_time_seconds,\n        child_block_interval: child_block_interval\n      ) do\n    {:ok, db_exits} = PaymentExitInfo.all_exit_infos()\n    {:ok, db_ifes} = PaymentExitInfo.all_in_flight_exits_infos()\n    {:ok, db_competitors} = DB.competitors_info()\n\n    :ok =\n      Core.check_sla_margin(\n        exit_processor_sla_margin,\n        exit_processor_sla_margin_forced,\n        min_exit_period_seconds,\n        ethereum_block_time_seconds\n      )\n\n    {:ok, processor} =\n      Core.init(\n        db_exits,\n        db_ifes,\n        db_competitors,\n        min_exit_period_seconds,\n        child_block_interval,\n        exit_processor_sla_margin\n      )\n\n    {:ok, _} = :timer.send_interval(metrics_collection_interval, self(), :send_metrics)\n\n    _ = Logger.info(\"Initializing with: #{inspect(processor)}\")\n    {:ok, processor}\n  end\n\n  def handle_call({:new_exits, exits}, _from, state) do\n    _ = if not Enum.empty?(exits), do: Logger.info(\"Recognized #{Enum.count(exits)} exits: #{inspect(exits)}\")\n\n    {:ok, exit_contract_statuses} = Eth.RootChain.get_standard_exit_structs(get_in(exits, [Access.all(), :exit_id]))\n\n    exit_maps =\n      exits\n      |> Task.async_stream(\n        fn exit_event ->\n          put_timestamp_and_sft(exit_event, state.min_exit_period_seconds, state.child_block_interval)\n        end,\n        timeout: 50_000,\n        on_timeout: :exit,\n        max_concurrency: System.schedulers_online() * 2\n      )\n      |> Enum.map(fn {:ok, result} -> result end)\n\n    if Code.ensure_loaded?(OMG.WatcherInfo.DB.EthEvent),\n      do: Kernel.apply(OMG.WatcherInfo.DB.EthEvent, :insert_exits!, [exits, :standard_exit, nil])\n\n    {new_state, db_updates} = Core.new_exits(state, exit_maps, exit_contract_statuses)\n    {:reply, {:ok, db_updates}, new_state}\n  end\n\n  def handle_call({:new_in_flight_exits, exits}, _from, state) do\n    _ = if not Enum.empty?(exits), do: Logger.info(\"Recognized #{Enum.count(exits)} in-flight exits: #{inspect(exits)}\")\n\n    contract_ife_ids =\n      Enum.map(exits, fn %{call_data: %{in_flight_tx: txbytes}} ->\n        ExPlasma.InFlightExit.tx_bytes_to_id(txbytes)\n      end)\n\n    # Prepare events data for internal bus\n    events =\n      exits\n      |> Enum.map(fn %{call_data: %{input_utxos_pos: inputs}} = event ->\n        {event, inputs}\n      end)\n      |> Tools.to_bus_events_data()\n\n    :ok = publish_internal_bus_events(events, :InFlightExitStarted)\n\n    if Code.ensure_loaded?(OMG.WatcherInfo.DB.EthEvent),\n      do: Kernel.apply(OMG.WatcherInfo.DB.EthEvent, :insert_exits!, [events, :in_flight_exit, :InFlightExitStarted])\n\n    {:ok, statuses} = Eth.RootChain.get_in_flight_exit_structs(contract_ife_ids)\n    ife_contract_statuses = Enum.zip(statuses, contract_ife_ids)\n    {new_state, db_updates} = Core.new_in_flight_exits(state, exits, ife_contract_statuses)\n    {:reply, {:ok, db_updates}, new_state}\n  end\n\n  def handle_call({:delete_in_flight_exits, deletions}, _from, state) do\n    _ =\n      if not Enum.empty?(deletions),\n        do: Logger.info(\"Recognized #{Enum.count(deletions)} deletions: #{inspect(deletions)}\")\n\n    {new_state, deleted_utxos, db_updates} = Core.delete_in_flight_exits(state, deletions)\n    {:ok, db_updates_from_state, _validities} = State.exit_utxos(deleted_utxos)\n\n    {:reply, {:ok, db_updates ++ db_updates_from_state}, new_state}\n  end\n\n  def handle_call({:finalize_exits, exits}, _from, state) do\n    _ = if not Enum.empty?(exits), do: Logger.info(\"Recognized #{Enum.count(exits)} finalizations: #{inspect(exits)}\")\n\n    {:ok, db_updates_from_state, validities} =\n      exits |> Enum.map(&Core.exit_key_by_exit_id(state, &1.exit_id)) |> State.exit_utxos()\n\n    {new_state, db_updates} = Core.finalize_exits(state, validities)\n\n    {:reply, {:ok, db_updates ++ db_updates_from_state}, new_state}\n  end\n\n  def handle_call({:piggyback_exits, exits}, _from, state) do\n    _ = if not Enum.empty?(exits), do: Logger.info(\"Recognized #{Enum.count(exits)} piggybacks: #{inspect(exits)}\")\n    {new_state, db_updates} = Core.new_piggybacks(state, exits)\n\n    events = Tools.to_bus_events_data(exits)\n    :ok = publish_internal_bus_events(events, :InFlightTxOutputPiggybacked)\n\n    if Code.ensure_loaded?(OMG.WatcherInfo.DB.EthEvent),\n      do:\n        Kernel.apply(\n          OMG.WatcherInfo.DB.EthEvent,\n          :insert_exits!,\n          [events, :in_flight_exit, :InFlightTxOutputPiggybacked]\n        )\n\n    {:reply, {:ok, db_updates}, new_state}\n  end\n\n  def handle_call({:challenge_exits, exits}, _from, state) do\n    _ = if not Enum.empty?(exits), do: Logger.info(\"Recognized #{Enum.count(exits)} challenges: #{inspect(exits)}\")\n    {new_state, db_updates} = Core.challenge_exits(state, exits)\n    {:reply, {:ok, db_updates}, new_state}\n  end\n\n  def handle_call({:new_ife_challenges, challenges}, _from, state) do\n    _ =\n      if not Enum.empty?(challenges),\n        do: Logger.info(\"Recognized #{Enum.count(challenges)} ife challenges: #{inspect(challenges)}\")\n\n    {new_state, db_updates} = Core.new_ife_challenges(state, challenges)\n    {:reply, {:ok, db_updates}, new_state}\n  end\n\n  def handle_call({:challenge_piggybacks, challenges}, _from, state) do\n    _ =\n      if not Enum.empty?(challenges),\n        do: Logger.info(\"Recognized #{Enum.count(challenges)} piggyback challenges: #{inspect(challenges)}\")\n\n    {new_state, db_updates} = Core.challenge_piggybacks(state, challenges)\n    {:reply, {:ok, db_updates}, new_state}\n  end\n\n  def handle_call({:respond_to_in_flight_exits_challenges, responds}, _from, state) do\n    _ =\n      if not Enum.empty?(responds),\n        do: Logger.info(\"Recognized #{Enum.count(responds)} response to IFE challenge: #{inspect(responds)}\")\n\n    {new_state, db_updates} = Core.respond_to_in_flight_exits_challenges(state, responds)\n    {:reply, {:ok, db_updates}, new_state}\n  end\n\n  def handle_call({:finalize_in_flight_exits, finalizations}, _from, state) do\n    _ = Logger.info(\"Recognized #{Enum.count(finalizations)} ife finalizations: #{inspect(finalizations)}\")\n\n    # necessary, so that the processor knows the current state of inclusion of exiting IFE txs\n    state2 = update_with_ife_txs_from_blocks(state)\n\n    {:ok, exiting_positions, events_with_utxos} =\n      Core.prepare_utxo_exits_for_in_flight_exit_finalizations(state2, finalizations)\n\n    # NOTE: it's not straightforward to track from utxo position returned when exiting utxo in State to ife id\n    # See issue #671 https://github.com/omgnetwork/elixir-omg/issues/671\n    {invalidities, state_db_updates} =\n      Enum.reduce(exiting_positions, {%{}, []}, &collect_invalidities_and_state_db_updates/2)\n\n    {:ok, state3, db_updates} = Core.finalize_in_flight_exits(state2, finalizations, invalidities)\n\n    events = Tools.to_bus_events_data(events_with_utxos)\n    :ok = publish_internal_bus_events(events, :InFlightExitOutputWithdrawn)\n\n    if Code.ensure_loaded?(OMG.WatcherInfo.DB.EthEvent),\n      do:\n        Kernel.apply(\n          OMG.WatcherInfo.DB.EthEvent,\n          :insert_exits!,\n          [events, :in_flight_exit, :InFlightExitOutputWithdrawn]\n        )\n\n    {:reply, {:ok, state_db_updates ++ db_updates}, state3}\n  end\n\n  def handle_call(:check_validity, _from, state) do\n    new_state = update_with_ife_txs_from_blocks(state)\n\n    response =\n      %ExitProcessor.Request{}\n      |> fill_request_with_spending_data(new_state)\n      |> Core.check_validity(new_state)\n\n    {:reply, response, new_state}\n  end\n\n  def handle_call(:get_active_in_flight_exits, _from, state) do\n    {:reply, {:ok, Core.get_active_in_flight_exits(state)}, state}\n  end\n\n  def handle_call({:get_competitor_for_ife, txbytes}, _from, state) do\n    # TODO: run_status_gets and getting all non-existent UTXO positions imaginable can be optimized out heavily\n    #       only the UTXO positions being inputs to `txbytes` must be looked at, but it becomes problematic as\n    #       txbytes can be invalid so we'd need a with here...\n    new_state = update_with_ife_txs_from_blocks(state)\n\n    competitor_result =\n      %ExitProcessor.Request{}\n      |> fill_request_with_spending_data(new_state)\n      |> Core.get_competitor_for_ife(new_state, txbytes)\n\n    {:reply, competitor_result, new_state}\n  end\n\n  def handle_call({:prove_canonical_for_ife, txbytes}, _from, state) do\n    new_state = update_with_ife_txs_from_blocks(state)\n    canonicity_result = Core.prove_canonical_for_ife(new_state, txbytes)\n    {:reply, canonicity_result, new_state}\n  end\n\n  def handle_call({:get_input_challenge_data, txbytes, input_index}, _from, state) do\n    response =\n      %ExitProcessor.Request{}\n      |> fill_request_with_spending_data(state)\n      |> Core.get_input_challenge_data(state, txbytes, input_index)\n\n    {:reply, response, state}\n  end\n\n  def handle_call({:get_output_challenge_data, txbytes, output_index}, _from, state) do\n    new_state = update_with_ife_txs_from_blocks(state)\n\n    response =\n      %ExitProcessor.Request{}\n      |> fill_request_with_spending_data(new_state)\n      |> Core.get_output_challenge_data(new_state, txbytes, output_index)\n\n    {:reply, response, new_state}\n  end\n\n  def handle_call({:create_challenge, exiting_utxo_pos}, _from, state) do\n    request = %ExitProcessor.Request{se_exiting_pos: exiting_utxo_pos}\n    exiting_utxo_exists = State.utxo_exists?(exiting_utxo_pos)\n\n    response =\n      with {:ok, request} <- Core.determine_standard_challenge_queries(request, state, exiting_utxo_exists),\n           do:\n             request\n             |> fill_request_with_standard_challenge_data()\n             |> Core.create_challenge(state)\n\n    {:reply, response, state}\n  end\n\n  def handle_info(:send_metrics, state) do\n    :ok = :telemetry.execute([:process, __MODULE__], %{}, state)\n    {:noreply, state}\n  end\n\n  defp fill_request_with_standard_challenge_data(%ExitProcessor.Request{se_spending_blocks_to_get: positions} = request) do\n    %ExitProcessor.Request{request | se_spending_blocks_result: do_get_spending_blocks(positions)}\n  end\n\n  # based on the exits being processed, fills the request structure with data required to process queries\n  @spec fill_request_with_spending_data(ExitProcessor.Request.t(), Core.t()) :: ExitProcessor.Request.t()\n  defp fill_request_with_spending_data(request, state) do\n    request\n    |> run_status_gets()\n    |> Core.determine_utxo_existence_to_get(state)\n    |> get_utxo_existence()\n    |> Core.determine_spends_to_get(state)\n    |> get_spending_blocks()\n  end\n\n  # based on in-flight exiting transactions, updates the state with witnesses of those transactions' inclusions in block\n  @spec update_with_ife_txs_from_blocks(Core.t()) :: Core.t()\n  defp update_with_ife_txs_from_blocks(state) do\n    prepared_request =\n      %ExitProcessor.Request{}\n      |> run_status_gets()\n      # To find if IFE was included, see first if its inputs were spent.\n      |> Core.determine_ife_input_utxos_existence_to_get(state)\n      |> get_ife_input_utxo_existence()\n      # Next, check by what transactions they were spent.\n      |> Core.determine_ife_spends_to_get(state)\n      |> get_ife_input_spending_blocks()\n\n    # Compare found txes with ife.tx.\n    # If equal, persist information about position.\n    Core.find_ifes_in_blocks(state, prepared_request)\n  end\n\n  defp run_status_gets(%ExitProcessor.Request{eth_height_now: nil, blknum_now: nil} = request) do\n    {:ok, eth_height_now} = EthereumHeight.get()\n    {blknum_now, _} = State.get_status()\n\n    _ = Logger.debug(\"eth_height_now: #{inspect(eth_height_now)}, blknum_now: #{inspect(blknum_now)}\")\n    %{request | eth_height_now: eth_height_now, blknum_now: blknum_now}\n  end\n\n  defp get_utxo_existence(%ExitProcessor.Request{utxos_to_check: positions} = request),\n    do: %{request | utxo_exists_result: do_utxo_exists?(positions)}\n\n  defp get_ife_input_utxo_existence(%ExitProcessor.Request{ife_input_utxos_to_check: positions} = request),\n    do: %{request | ife_input_utxo_exists_result: do_utxo_exists?(positions)}\n\n  defp do_utxo_exists?(positions) do\n    result = Enum.map(positions, &State.utxo_exists?/1)\n    _ = Logger.debug(\"utxos_to_check: #{inspect(positions)}, utxo_exists_result: #{inspect(result)}\")\n    result\n  end\n\n  defp get_spending_blocks(%ExitProcessor.Request{spends_to_get: positions} = request) do\n    %{request | blocks_result: do_get_spending_blocks(positions)}\n  end\n\n  defp get_ife_input_spending_blocks(%ExitProcessor.Request{ife_input_spends_to_get: positions} = request) do\n    %{request | ife_input_spending_blocks_result: do_get_spending_blocks(positions)}\n  end\n\n  defp do_get_spending_blocks(spent_positions_to_get) do\n    blknums = Enum.map(spent_positions_to_get, &do_get_spent_blknum/1)\n    _ = Logger.debug(\"spends_to_get: #{inspect(spent_positions_to_get)}, spent_blknum_result: #{inspect(blknums)}\")\n\n    blknums\n    |> Core.handle_spent_blknum_result(spent_positions_to_get)\n    |> do_get_blocks()\n  end\n\n  defp do_get_blocks(blknums) do\n    {:ok, hashes} = OMG.DB.block_hashes(blknums)\n    _ = Logger.debug(\"blknums: #{inspect(blknums)}, hashes: #{inspect(hashes)}\")\n    {:ok, blocks} = OMG.DB.blocks(hashes)\n    _ = Logger.debug(\"blocks_result: #{inspect(blocks)}\")\n\n    Enum.map(blocks, &Block.from_db_value/1)\n  end\n\n  defp do_get_spent_blknum(position) do\n    position |> Utxo.Position.to_input_db_key() |> OMG.DB.spent_blknum()\n  end\n\n  defp collect_invalidities_and_state_db_updates(\n         {ife_id, exiting_positions},\n         {invalidities_by_ife_id, state_db_updates}\n       ) do\n    {:ok, exits_state_updates, {_, invalidities}} = State.exit_utxos(exiting_positions)\n\n    _ =\n      if not Enum.empty?(invalidities), do: Logger.warn(\"Invalid in-flight exit finalization: #{inspect(invalidities)}\")\n\n    invalidities_by_ife_id = Map.put(invalidities_by_ife_id, ife_id, invalidities)\n    state_db_updates = exits_state_updates ++ state_db_updates\n\n    {invalidities_by_ife_id, state_db_updates}\n  end\n\n  @spec put_timestamp_and_sft(map(), pos_integer(), pos_integer()) :: map()\n  defp put_timestamp_and_sft(\n         %{eth_height: eth_height, call_data: %{utxo_pos: utxo_pos_enc}} = exit_event,\n         min_exit_period_seconds,\n         child_block_interval\n       ) do\n    {:utxo_position, blknum, _, _} = Utxo.Position.decode!(utxo_pos_enc)\n    {_block_hash, utxo_creation_block_timestamp} = RootChain.blocks(blknum)\n    {:ok, exit_block_timestamp} = Eth.get_block_timestamp_by_number(eth_height)\n\n    {:ok, scheduled_finalization_time} =\n      ExitInfo.calculate_sft(\n        blknum,\n        exit_block_timestamp,\n        utxo_creation_block_timestamp,\n        min_exit_period_seconds,\n        child_block_interval\n      )\n\n    exit_event\n    |> Map.put(:scheduled_finalization_time, scheduled_finalization_time)\n    |> Map.put(:block_timestamp, exit_block_timestamp)\n  end\n\n  defp publish_internal_bus_events([], _), do: :ok\n\n  defp publish_internal_bus_events(events_data, topic) when is_list(events_data) and is_atom(topic) do\n    {:watcher, topic}\n    |> OMG.Bus.Event.new(:data, events_data)\n    |> OMG.Bus.direct_local_broadcast()\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/fees/fee_filter.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.Fees.FeeFilter do\n  @moduledoc \"\"\"\n  Filtering of fees.\n  \"\"\"\n\n  alias OMG.Watcher.Fees\n\n  @doc ~S\"\"\"\n  Returns a filtered map of fees given a list of transaction types and currencies.\n  Passing a nil value or an empty array skip the filtering.\n\n  ## Examples\n\n      iex> OMG.Watcher.Fees.FeeFilter.filter(\n      ...>   %{\n      ...>     1 => %{\n      ...>       \"eth\" => %{\n      ...>         amount: 1,\n      ...>         subunit_to_unit: 1_000_000_000_000_000_000,\n      ...>         pegged_amount: 4,\n      ...>         pegged_currency: \"USD\",\n      ...>         pegged_subunit_to_unit: 100,\n      ...>         updated_at: DateTime.from_iso8601(\"2019-01-01T10:10:00+00:00\")\n      ...>       },\n      ...>       \"omg\" => %{\n      ...>         amount: 3,\n      ...>         subunit_to_unit: 1_000_000_000_000_000_000,\n      ...>         pegged_amount: 4,\n      ...>         pegged_currency: \"USD\",\n      ...>         pegged_subunit_to_unit: 100,\n      ...>         updated_at: DateTime.from_iso8601(\"2019-01-01T10:10:00+00:00\")\n      ...>       }\n      ...>     },\n      ...>     2 => %{\n      ...>       \"omg\" => %{\n      ...>         amount: 3,\n      ...>         subunit_to_unit: 1_000_000_000_000_000_000,\n      ...>         pegged_amount: 4,\n      ...>         pegged_currency: \"USD\",\n      ...>         pegged_subunit_to_unit: 100,\n      ...>         updated_at: DateTime.from_iso8601(\"2019-01-01T10:10:00+00:00\")\n      ...>       }\n      ...>     },\n      ...>     3 => %{\n      ...>       \"omg\" => %{\n      ...>         amount: 3,\n      ...>         subunit_to_unit: 1_000_000_000_000_000_000,\n      ...>         pegged_amount: 4,\n      ...>         pegged_currency: \"USD\",\n      ...>         pegged_subunit_to_unit: 100,\n      ...>         updated_at: DateTime.from_iso8601(\"2019-01-01T10:10:00+00:00\")\n      ...>       }\n      ...>     }\n      ...>   },\n      ...>   [1,2],\n      ...>   [\"eth\"]\n      ...> )\n      {:ok,\n        %{\n          1 => %{\n            \"eth\" => %{\n              amount: 1,\n              subunit_to_unit: 1_000_000_000_000_000_000,\n              pegged_amount: 4,\n              pegged_currency: \"USD\",\n              pegged_subunit_to_unit: 100,\n              updated_at: DateTime.from_iso8601(\"2019-01-01T10:10:00+00:00\")\n            }\n          },\n          2 => %{}\n        }\n      }\n\n  \"\"\"\n  @spec filter(Fees.full_fee_t(), list(non_neg_integer()), list(String.t()) | nil) ::\n          {:ok, Fees.full_fee_t()} | {:error, :currency_fee_not_supported} | {:error, :tx_type_not_supported}\n  # empty list = no filter\n  def filter(fees, []), do: {:ok, fees}\n  def filter(fees, nil), do: {:ok, fees}\n\n  def filter(fees, tx_types, currencies) do\n    with {:ok, fees} <- filter_tx_type(fees, tx_types) do\n      filter_currency(fees, currencies)\n    end\n  end\n\n  defp filter_tx_type(fees, []), do: {:ok, fees}\n  defp filter_tx_type(fees, nil), do: {:ok, fees}\n\n  defp filter_tx_type(fees, tx_types) do\n    with :ok <- validate_tx_types(tx_types, fees), do: {:ok, Map.take(fees, tx_types)}\n  end\n\n  defp validate_tx_types(tx_types, fees) do\n    tx_types\n    |> Enum.all?(&Map.has_key?(fees, &1))\n    |> case do\n      true -> :ok\n      false -> {:error, :tx_type_not_supported}\n    end\n  end\n\n  defp filter_currency(fees, []), do: {:ok, fees}\n  defp filter_currency(fees, nil), do: {:ok, fees}\n\n  defp filter_currency(fees, currencies) do\n    with :ok <- validate_currencies(currencies, fees) do\n      {:ok, do_filter_currencies(currencies, fees)}\n    end\n  end\n\n  defp validate_currencies(currencies, fees) do\n    currencies\n    |> Enum.all?(fn currency -> Enum.any?(fees, &Map.has_key?(elem(&1, 1), currency)) end)\n    |> case do\n      true -> :ok\n      false -> {:error, :currency_fee_not_supported}\n    end\n  end\n\n  defp do_filter_currencies(currencies, fees) do\n    fees\n    |> Enum.map(fn {tx_type, fees_for_tx_type} ->\n      {tx_type, Map.take(fees_for_tx_type, currencies)}\n    end)\n    |> Enum.into(%{})\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/fees.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.Fees do\n  @moduledoc \"\"\"\n  Transaction's fee validation functions.\n  \"\"\"\n\n  alias OMG.Watcher.Crypto\n  alias OMG.Watcher.MergeTransactionValidator\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.Utxo\n\n  require Utxo\n\n  require Logger\n\n  @typedoc \"A map of token addresses to a single fee spec\"\n  @type fee_t() :: %{Crypto.address_t() => fee_spec_t()}\n  @typedoc \"\"\"\n  A map of transaction types to fees\n  where fees is itself a map of token to fee spec\n  \"\"\"\n  @type full_fee_t() :: %{non_neg_integer() => fee_t()}\n  @type optional_fee_t() :: merged_fee_t() | :ignore_fees | :no_fees_required\n  @typedoc \"A map representing a single fee\"\n  @type fee_spec_t() :: %{\n          amount: pos_integer(),\n          subunit_to_unit: pos_integer(),\n          pegged_amount: pos_integer(),\n          pegged_currency: String.t(),\n          pegged_subunit_to_unit: pos_integer(),\n          updated_at: DateTime.t()\n        }\n\n  @typedoc \"\"\"\n  A map of currency to amounts used internally where amounts is a list of supported fee amounts.\n  \"\"\"\n  @type typed_merged_fee_t() :: %{non_neg_integer() => merged_fee_t()}\n  @type merged_fee_t() :: %{Crypto.address_t() => list(pos_integer())}\n\n  @doc ~S\"\"\"\n  Checks whether the surplus of tokens sent in a transaction (inputs - outputs) covers the fees\n  depending on the fee model.\n\n  ## Examples\n\n      iex> Fees.check_if_covered(%{\"eth\" => 1, \"omg\" => 0}, %{\"eth\" => [1], \"omg\" => [3]})\n      :ok\n      iex> Fees.check_if_covered(%{\"eth\" => 1, \"omg\" => 0}, %{\"eth\" => [2, 1], \"omg\" => [1, 3]})\n      :ok\n      iex> Fees.check_if_covered(%{\"eth\" => 1}, %{\"eth\" => [2]})\n      {:error, :fees_not_covered}\n      iex> Fees.check_if_covered(%{\"eth\" => 2}, %{\"eth\" => [3, 1]})\n      {:error, :fees_not_covered}\n      iex> Fees.check_if_covered(%{\"eth\" => 1, \"omg\" => 1}, %{\"eth\" => [1]})\n      {:error, :multiple_potential_currency_fees}\n      iex> Fees.check_if_covered(%{\"eth\" => 2}, %{\"eth\" => [1]})\n      {:error, :overpaying_fees}\n      iex> Fees.check_if_covered(%{\"eth\" => 2}, %{\"eth\" => [1, 3]})\n      {:error, :overpaying_fees}\n      iex> Fees.check_if_covered(%{\"eth\" => 1}, :no_fees_required)\n      {:error, :overpaying_fees}\n      iex> Fees.check_if_covered(%{\"eth\" => 1}, :ignore_fees)\n      :ok\n\n  \"\"\"\n  @spec check_if_covered(implicit_paid_fee_by_currency :: map(), accepted_fees :: optional_fee_t()) ::\n          :ok | {:error, :fees_not_covered} | {:error, :overpaying_fees} | {:error, :multiple_potential_currency_fees}\n  # If :ignore_fees is given, we don't require any surplus of tokens. If surplus exists, it will be collected.\n  def check_if_covered(_, :ignore_fees), do: :ok\n  def check_if_covered(_, accepted_fees) when map_size(accepted_fees) == 0, do: :ok\n\n  # Otherwise we remove all non positive tokens from the map and process it\n  def check_if_covered(implicit_paid_fee_by_currency, accepted_fees) do\n    implicit_paid_fee_by_currency\n    |> remove_zero_fees()\n    |> check_positive_amounts(accepted_fees)\n  end\n\n  # With :no_fees_required, we ensure that no surplus of token is given\n  # meaning that input amount == output amount. This is used for merge transactions.\n  defp check_positive_amounts([], :no_fees_required), do: :ok\n  defp check_positive_amounts(_, :no_fees_required), do: {:error, :overpaying_fees}\n\n  # When accepting fees, we ensure that only one fee token is given\n  defp check_positive_amounts([], _), do: {:error, :fees_not_covered}\n\n  # When accepting fees, we ensure that the paid amount matches exactly the required amount and that\n  # the given surplus token is accepted as a fee token\n  defp check_positive_amounts([{currency, paid_fee}], accepted_fees) do\n    case Map.get(accepted_fees, currency) do\n      nil ->\n        {:error, :fees_not_covered}\n\n      amounts ->\n        check_if_exact_match(amounts, paid_fee)\n    end\n  end\n\n  defp check_positive_amounts(_, _), do: {:error, :multiple_potential_currency_fees}\n\n  defp remove_zero_fees(implicit_paid_fee_by_currency) do\n    Enum.filter(implicit_paid_fee_by_currency, fn {_currency, paid_fee} ->\n      paid_fee > 0\n    end)\n  end\n\n  defp check_if_exact_match([current_amount | _] = amounts, paid_fee) do\n    cond do\n      paid_fee in amounts ->\n        :ok\n\n      current_amount > paid_fee ->\n        {:error, :fees_not_covered}\n\n      current_amount < paid_fee ->\n        {:error, :overpaying_fees}\n    end\n  end\n\n  @doc ~S\"\"\"\n  Returns the fees to pay for a particular transaction,\n  and under particular fee specs listed in `fee_map`.\n\n  ## Examples\n  iex> OMG.Watcher.Fees.for_transaction(\n  ...> %OMG.Watcher.State.Transaction.Recovered{\n  ...>   signed_tx: %OMG.Watcher.State.Transaction.Signed{raw_tx: OMG.Watcher.State.Transaction.Payment.new([], [], <<0::256>>)}\n  ...> },\n  ...> %{\n  ...>   1 => %{\n  ...>     \"eth\" => [1],\n  ...>     \"omg\" => [3]\n  ...>   },\n  ...>   2 => %{\n  ...>     \"eth\" => [4],\n  ...>     \"omg\" => [5]\n  ...>   }\n  ...> }\n  ...> )\n  %{\n    \"eth\" => [1],\n    \"omg\" => [3]\n  }\n  \"\"\"\n  @spec for_transaction(Transaction.Recovered.t(), merged_fee_t()) :: optional_fee_t()\n  def for_transaction(transaction, fee_map) do\n    case MergeTransactionValidator.is_merge_transaction?(transaction) do\n      true -> :no_fees_required\n      false -> get_fee_for_type(transaction, fee_map)\n    end\n  end\n\n  defp get_fee_for_type(%Transaction.Recovered{signed_tx: %Transaction.Signed{raw_tx: %{tx_type: type}}}, fee_map) do\n    case type do\n      nil -> %{}\n      type -> Map.get(fee_map, type, %{})\n    end\n  end\n\n  defp get_fee_for_type(_, _fee_map), do: %{}\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/http_rpc/adapter.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.HttpRPC.Adapter do\n  @moduledoc \"\"\"\n  Provides functions to communicate with Child Chain API\n  \"\"\"\n\n  alias OMG.Utils.AppVersion\n\n  require Logger\n\n  @doc \"\"\"\n    Makes HTTP POST request to the API\n  \"\"\"\n  def rpc_post(body, path, url) do\n    addr = \"#{url}/#{path}\"\n    headers = [{\"content-type\", \"application/json\"}, {\"X-Watcher-Version\", AppVersion.version(:omg_watcher)}]\n\n    with {:ok, body} <- Jason.encode(body),\n         {:ok, %HTTPoison.Response{} = response} <- HTTPoison.post(addr, body, headers) do\n      _ = Logger.debug(\"rpc post #{inspect(addr)} completed successfully\")\n      response\n    else\n      err ->\n        _ = Logger.warn(\"rpc post #{inspect(addr)} failed with #{inspect(err)}\")\n        err\n    end\n  end\n\n  @doc \"\"\"\n  Retrieves body from response structure but don't deserialize it.\n  \"\"\"\n  def get_unparsed_response_body({:ok, %HTTPoison.Response{} = response}),\n    do: get_unparsed_response_body(response)\n\n  def get_unparsed_response_body(%HTTPoison.Response{status_code: 200, body: body}),\n    do: {:ok, body}\n\n  def get_unparsed_response_body(%HTTPoison.Response{body: error}),\n    do: {:error, {:client_error, error}}\n\n  def get_unparsed_response_body({:error, %HTTPoison.Error{reason: :econnrefused}}) do\n    {:error, :host_unreachable}\n  end\n\n  def get_unparsed_response_body({:error, %HTTPoison.Error{reason: reason}}) do\n    {:error, {:server_error, reason}}\n  end\n\n  def get_unparsed_response_body(error), do: error\n\n  @doc \"\"\"\n  Retrieves body from response structure. When response is successful\n  the structure in body is known, so we can try to deserialize it.\n  \"\"\"\n  @spec get_response_body(HTTPoison.Response.t() | {:error, HTTPoison.Error.t()}) ::\n          {:ok, map()} | {:ok, list(map())} | {:error, atom() | tuple() | HTTPoison.Error.t()}\n  def get_response_body(http_response) do\n    with {:ok, body} <- get_unparsed_response_body(http_response),\n         {:ok, response} <- Jason.decode(body),\n         %{\"success\" => true, \"data\" => data} <- response do\n      {:ok, convert_keys_to_atoms(data)}\n    else\n      %{\"success\" => false, \"data\" => data} -> {:error, {:client_error, data}}\n      error -> error\n    end\n  end\n\n  defp convert_keys_to_atoms(data) when is_list(data) do\n    Enum.map(data, &convert_keys_to_atoms/1)\n  end\n\n  defp convert_keys_to_atoms(data) when is_map(data) do\n    data\n    |> Stream.map(fn {k, v} -> {String.to_existing_atom(k), v} end)\n    |> Map.new()\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/http_rpc/client.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.HttpRPC.Client do\n  @moduledoc \"\"\"\n  Provides functions to communicate with Child Chain API\n  \"\"\"\n\n  alias OMG.Utils.HttpRPC.Encoding\n  alias OMG.Watcher.HttpRPC.Adapter\n\n  require Logger\n\n  @type response_t() ::\n          list()\n          | {:ok, %{required(atom()) => any()}}\n          | {:error,\n             {:client_error | :server_error, any()}\n             | {:malformed_response, any() | {:error, :invalid}}}\n\n  @doc \"\"\"\n  Gets Block of given hash\n  \"\"\"\n  @spec get_block(binary(), binary()) :: response_t()\n  def get_block(hash, url), do: call(%{hash: Encoding.to_hex(hash)}, \"block.get\", url)\n\n  @doc \"\"\"\n  Submits transaction\n  \"\"\"\n  @spec submit(binary(), binary()) :: response_t()\n  def submit(tx, url), do: call(%{transaction: Encoding.to_hex(tx)}, \"transaction.submit\", url)\n\n  @doc \"\"\"\n  Submits a batch of transactions\n  \"\"\"\n  @spec batch_submit(list(binary()), binary()) :: response_t()\n  def batch_submit(txs, url) do\n    call(%{transactions: Enum.map(txs, &Encoding.to_hex(&1))}, \"transaction.batch_submit\", url)\n  end\n\n  defp call(params, path, url) do\n    params |> Adapter.rpc_post(path, url) |> Adapter.get_response_body() |> decode_response()\n  end\n\n  # Translates response's body to known elixir structure, either block or tx submission response or error.\n  defp decode_response({:ok, %{transactions: transactions, blknum: number, hash: hash}}) do\n    {:ok,\n     %{\n       number: number,\n       hash: decode16!(hash),\n       transactions: Enum.map(transactions, &decode16!/1)\n     }}\n  end\n\n  defp decode_response({:ok, %{txhash: _hash} = response}) do\n    {:ok, Map.update!(response, :txhash, &decode16!/1)}\n  end\n\n  defp decode_response({:ok, response}) when is_list(response) do\n    decode_response(response, [])\n  end\n\n  defp decode_response(error), do: error\n\n  defp decode_response([], acc) do\n    Enum.reverse(acc)\n  end\n\n  defp decode_response([%{txhash: _hash} = transaction_response | response], acc) do\n    decode_response(response, [Map.update!(transaction_response, :txhash, &decode16!/1) | acc])\n  end\n\n  # all error tuples\n  defp decode_response([%{error: error} | response], acc) do\n    decode_response(response, [%{error: {:skip_hex_encode, error}} | acc])\n  end\n\n  defp decode16!(hexstr) do\n    {:ok, bin} = Encoding.from_hex(hexstr)\n    bin\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/merge_transaction_validator.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.MergeTransactionValidator do\n  @moduledoc \"\"\"\n  Decides whether transactions qualify as \"merge\" transactions that use a single currency,\n  single recipient address and have fewer outputs than inputs. This decision is necessary\n  to know by the child chain to not require the transaction fees.\n  \"\"\"\n\n  alias OMG.Output\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.Utxo\n\n  require Utxo\n\n  require Logger\n\n  @spec is_merge_transaction?(%Transaction.Recovered{}) :: boolean()\n  def is_merge_transaction?(recovered_transaction) do\n    [\n      &is_payment?/1,\n      &only_fungible_tokens?/1,\n      &has_less_outputs_than_inputs?/1,\n      &has_single_currency?/1,\n      &has_same_account?/1\n    ]\n    |> Enum.all?(fn predicate -> predicate.(recovered_transaction) end)\n  end\n\n  defp is_payment?(%Transaction.Recovered{signed_tx: %{raw_tx: %Transaction.Payment{}}}), do: true\n  defp is_payment?(_), do: false\n\n  defp only_fungible_tokens?(tx),\n    do: tx |> Transaction.get_outputs() |> Enum.all?(&match?(%Output{}, &1))\n\n  defp has_same_account?(%Transaction.Recovered{witnesses: witnesses} = tx) do\n    spenders = Map.values(witnesses)\n\n    tx\n    |> Transaction.get_outputs()\n    |> Enum.map(& &1.owner)\n    |> Enum.concat(spenders)\n    |> single?()\n  end\n\n  defp has_single_currency?(tx) do\n    tx\n    |> Transaction.get_outputs()\n    |> Enum.map(& &1.currency)\n    |> single?()\n  end\n\n  defp has_less_outputs_than_inputs?(tx) do\n    has_less_outputs_than_inputs?(\n      Transaction.get_inputs(tx),\n      Transaction.get_outputs(tx)\n    )\n  end\n\n  defp has_less_outputs_than_inputs?(inputs, outputs), do: length(inputs) >= 1 and length(inputs) > length(outputs)\n\n  defp single?(list), do: 1 == list |> Enum.uniq() |> length()\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/merkle.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.Merkle do\n  @moduledoc \"\"\"\n  Encapsulates all the interactions with the MerkleTree library.\n  \"\"\"\n\n  alias OMG.Watcher.Crypto\n\n  @transaction_merkle_tree_height 16\n  @default_leaf <<0::256>>\n\n  # Creates a Merkle proof that transaction under a given transaction index\n  # is included in block consisting of hashed transactions\n  @spec create_tx_proof(list(String.t()), non_neg_integer()) :: binary()\n  def create_tx_proof(txs_bytes, txindex) do\n    build(txs_bytes)\n    |> prove(txindex)\n    |> Enum.reverse()\n    |> Enum.join()\n  end\n\n  @spec hash(list(String.t())) :: binary()\n  def hash(hashed_txs) do\n    MerkleTree.fast_root(hashed_txs,\n      hash_function: &Crypto.hash/1,\n      height: @transaction_merkle_tree_height,\n      default_data_block: @default_leaf\n    )\n  end\n\n  defp build(txs_bytes) do\n    MerkleTree.build(txs_bytes,\n      hash_function: &Crypto.hash/1,\n      height: @transaction_merkle_tree_height,\n      default_data_block: @default_leaf\n    )\n  end\n\n  defp prove(tx_bytes, txindex) do\n    MerkleTree.Proof.prove(tx_bytes, txindex)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/monitor.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.Monitor do\n  @moduledoc \"\"\"\n  This module restarts it's children if the Ethereum client\n  connectivity is dropped.\n  It subscribes to alarms and when an alarm is cleared it restarts it\n  children if they're dead.\n  \"\"\"\n  defmodule Child do\n    @moduledoc false\n    @type t :: %__MODULE__{\n            pid: pid(),\n            spec: {module(), term()} | map()\n          }\n    defstruct pid: nil, spec: nil\n  end\n\n  use GenServer\n\n  require Logger\n\n  @type t :: %__MODULE__{\n          alarm_module: module(),\n          child: Child.t()\n        }\n  defstruct alarm_module: nil, child: nil\n\n  def health_checkin() do\n    GenServer.cast(__MODULE__, :health_checkin)\n  end\n\n  def start_link(args) do\n    GenServer.start_link(__MODULE__, args, name: __MODULE__)\n  end\n\n  def init([alarm_module, child_spec]) do\n    subscribe_to_alarms()\n    Process.flag(:trap_exit, true)\n    # we raise the alarms first, because we get a health checkin when all\n    # sub processes of the supervisor are ready to go\n    _ = alarm_module.set(alarm_module.main_supervisor_halted(__MODULE__))\n    {:ok, %__MODULE__{alarm_module: alarm_module, child: start_child(child_spec)}}\n  end\n\n  # gen_event boot\n  def init(_args) do\n    {:ok, %{}}\n  end\n\n  #\n  # gen_event\n  #\n  def handle_call(_request, state), do: {:ok, :ok, state}\n\n  def handle_event({:clear_alarm, {:ethereum_connection_error, _}}, state) do\n    _ = Logger.warn(\":ethereum_connection_error alarm was cleared. Beginning to restart processes.\")\n    :ok = GenServer.cast(__MODULE__, :start_child)\n    {:ok, state}\n  end\n\n  # flush\n  def handle_event(event, state) do\n    _ = Logger.info(\"#{__MODULE__} got event: #{inspect(event)}. Ignoring.\")\n    {:ok, state}\n  end\n\n  # There's a supervisor below us that did the needed restarts for it's children\n  # so we do not attempt to restart the exit from the supervisor, if the alarm clears, we restart it then.\n  # We declare the sytem unhealthy\n  def handle_info({:EXIT, _from, reason}, state) do\n    _ = Logger.warn(\"Watcher supervisor crashed. Raising alarm. Reason #{inspect(reason)}\")\n    state.alarm_module.set(state.alarm_module.main_supervisor_halted(__MODULE__))\n\n    {:noreply, state}\n  end\n\n  # alarm has cleared, we can now begin restarting supervisor child\n  def handle_cast(:health_checkin, state) do\n    _ = Logger.info(\"Got a health checkin... clearing alarm main_supervisor_halted.\")\n    _ = state.alarm_module.clear(state.alarm_module.main_supervisor_halted(__MODULE__))\n    {:noreply, state}\n  end\n\n  # alarm has cleared, we can now begin restarting supervisor child\n  def handle_cast(:start_child, state) do\n    child = state.child\n    _ = Logger.info(\"Monitor is restarting child #{inspect(child)}.\")\n\n    {:noreply, %{state | child: start_child(child)}}\n  end\n\n  defp start_child(%{id: _name, start: {child_module, function, args}} = spec) do\n    {:ok, pid} = apply(child_module, function, args)\n    %Child{pid: pid, spec: spec}\n  end\n\n  defp start_child(%Child{pid: pid, spec: spec} = child) do\n    case Process.alive?(pid) do\n      true ->\n        child\n\n      false ->\n        %{id: _name, start: {child_module, function, args}} = spec\n        {:ok, pid} = apply(child_module, function, args)\n        %Child{pid: pid, spec: spec}\n    end\n  end\n\n  defp subscribe_to_alarms() do\n    case Enum.member?(:gen_event.which_handlers(:alarm_handler), __MODULE__) do\n      true -> :ok\n      _ -> :alarm_handler.add_alarm_handler(__MODULE__)\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/output.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Output do\n  @moduledoc \"\"\"\n   `OMG.Output` and `OMG.Output.Protocol` represent the outputs of transactions, i.e. the valuables or other pieces of\n  data spendable via transactions on the child chain, and/or exitable to the root chain.\n\n  This module specificially dispatches generic calls to the various specific types\n\n\n\n\n\n  THIS IS WHAT HAPPENS IF YOU STORE ELIXIR STRUCTS IN A DATABASE\n\n  This struct is binary encoded into RocksDB, so when we call `:erlang.binary_to_term(encoded, [:safe])`\n  for old outputs stored, it expects this struct to exist.\n  Just make sure they do the same thing!!!\n\n  \"\"\"\n  alias OMG.Watcher.Crypto\n  alias OMG.Watcher.RawData\n\n  @output_types Map.keys(OMG.Watcher.WireFormatTypes.output_type_modules())\n\n  @type t :: %__MODULE__{\n          output_type: binary(),\n          owner: Crypto.address_t(),\n          currency: Crypto.address_t(),\n          amount: non_neg_integer()\n        }\n  @type error_t() :: {:error, atom()}\n  defstruct [:output_type, :owner, :currency, :amount]\n\n  @doc \"\"\"\n  Reconstructs the structure from a list of RLP items\n  \"\"\"\n  @spec reconstruct(any()) :: t() | error_t()\n  def reconstruct(_rlp_data)\n\n  def reconstruct([raw_type, [_owner, _currency, _amount]] = rlp_data) when is_binary(raw_type) do\n    with {:ok, type, owner, currency, amont} <- clean_and_validate_data(rlp_data),\n         do: %__MODULE__{\n           output_type: type,\n           owner: owner,\n           currency: currency,\n           amount: amont\n         }\n  end\n\n  def reconstruct([_raw_type, [_owner, _currency, _amount]]), do: {:error, :unrecognized_output_type}\n  def reconstruct(_), do: {:error, :malformed_outputs}\n\n  def from_db_value(%{owner: owner, currency: currency, amount: amount, output_type: output_type})\n      when is_binary(owner) and is_binary(currency) and is_integer(amount) and is_integer(output_type) do\n    %__MODULE__{owner: owner, currency: currency, amount: amount, output_type: output_type}\n  end\n\n  def to_db_value(%__MODULE__{owner: owner, currency: currency, amount: amount, output_type: output_type})\n      when is_binary(owner) and is_binary(currency) and is_integer(amount) and is_integer(output_type) do\n    %{owner: owner, currency: currency, amount: amount, output_type: output_type}\n  end\n\n  def get_data_for_rlp(%__MODULE__{owner: owner, currency: currency, amount: amount, output_type: output_type}),\n    do: [output_type, [owner, currency, amount]]\n\n  # TODO(achiurizo)\n  # remove the validation here and port the error tuple response handling into ex_plasma.\n  defp clean_and_validate_data([raw_type, [owner, currency, amount]]) do\n    with {:ok, parsed_type} <- RawData.parse_uint256(raw_type),\n         {:ok, _} <- valid_output_type?(parsed_type),\n         {:ok, parsed_owner} <- RawData.parse_address(owner),\n         {:ok, _} <- non_zero_owner?(owner),\n         {:ok, parsed_currency} <- RawData.parse_address(currency),\n         {:ok, parsed_amount} <- RawData.parse_amount(amount),\n         do: {:ok, parsed_type, parsed_owner, parsed_currency, parsed_amount}\n  end\n\n  defp non_zero_owner?(<<0::160>>), do: {:error, :output_guard_cant_be_zero}\n  defp non_zero_owner?(_), do: {:ok, :valid}\n\n  defp valid_output_type?(type) when type in @output_types, do: {:ok, :valid}\n  defp valid_output_type?(_), do: {:error, :unrecognized_output_type}\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/raw_data.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.RawData do\n  @moduledoc \"\"\"\n  Provides functions to decode various data types from RLP raw format\n  \"\"\"\n\n  alias OMG.Watcher.Crypto\n\n  @doc \"\"\"\n  Parses amount, where 0 < amount < 2^256\n  \"\"\"\n  @spec parse_amount(binary()) ::\n          {:ok, pos_integer()} | {:error, :amount_cant_be_zero | :leading_zeros_in_encoded_uint | :encoded_uint_too_big}\n  def parse_amount(binary) when is_binary(binary) do\n    case parse_uint256(binary) do\n      {:ok, 0} ->\n        {:error, :amount_cant_be_zero}\n\n      {:ok, amount} ->\n        {:ok, amount}\n\n      error ->\n        error\n    end\n  end\n\n  @doc \"\"\"\n  Parses 20-bytes address\n  Case `<<>>` is necessary, because RLP handles empty string equally to integer 0\n  \"\"\"\n  @spec parse_address(<<>> | Crypto.address_t()) :: {:ok, Crypto.address_t()} | {:error, :malformed_address}\n  def parse_address(binary)\n  def parse_address(<<_::160>> = address_bytes), do: {:ok, address_bytes}\n  def parse_address(_), do: {:error, :malformed_address}\n\n  @doc \"\"\"\n  Parses unsigned at-most 32-bytes integer. Leading zeros are disallowed\n  \"\"\"\n  @spec parse_uint256(binary()) ::\n          {:ok, non_neg_integer()} | {:error, :encoded_uint_too_big | :leading_zeros_in_encoded_uint}\n  def parse_uint256(<<0>> <> _binary), do: {:error, :leading_zeros_in_encoded_uint}\n  def parse_uint256(binary) when byte_size(binary) <= 32, do: {:ok, :binary.decode_unsigned(binary, :big)}\n  def parse_uint256(binary) when byte_size(binary) > 32, do: {:error, :encoded_uint_too_big}\n  def parse_uint256(_), do: {:error, :malformed_uint256}\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/release_tasks/set_application.ex",
    "content": "# Copyright 2019-2019 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.ReleaseTasks.SetApplication do\n  @moduledoc false\n  @behaviour Config.Provider\n\n  def init(args) do\n    args\n  end\n\n  def load(config, release: release, current_version: current_version) do\n    Config.Reader.merge(config, omg_watcher: [release: release, current_version: current_version])\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/release_tasks/set_ethereum_events_check_interval.ex",
    "content": "# Copyright 2019-2019 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.ReleaseTasks.SetEthereumEventsCheckInterval do\n  @moduledoc \"\"\"\n  Configures the interval to check for new events from Ethereum.\n\n  This is essentially the same as `OMG.Watcher.Eth.ReleaseTasks.SetEthereumEventsCheckInterval` but for a different subapp.\n  \"\"\"\n  @behaviour Config.Provider\n  require Logger\n\n  @app :omg_watcher\n  @env_key \"ETHEREUM_EVENTS_CHECK_INTERVAL_MS\"\n\n  def init(args) do\n    args\n  end\n\n  def load(config, _args) do\n    _ = on_load()\n\n    interval_ms = get_interval_ms()\n\n    Config.Reader.merge(config, omg_watcher: [ethereum_events_check_interval_ms: interval_ms])\n  end\n\n  defp get_interval_ms() do\n    ethereum_events_check_interval_ms = Application.get_env(@app, :ethereum_events_check_interval_ms)\n    interval_ms = validate_integer(get_env(@env_key), ethereum_events_check_interval_ms)\n\n    _ =\n      Logger.info(\"CONFIGURATION: App: #{@app} Key: ethereum_events_check_interval_ms Value: #{inspect(interval_ms)}.\")\n\n    interval_ms\n  end\n\n  defp get_env(key), do: System.get_env(key)\n\n  defp validate_integer(value, _default) when is_binary(value), do: String.to_integer(value)\n  defp validate_integer(_, default), do: default\n\n  def on_load() do\n    _ = Application.ensure_all_started(:logger)\n    _ = Application.load(:omg_watcher)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/release_tasks/set_exit_processor_sla_margin.ex",
    "content": "# Copyright 2019-2019 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.ReleaseTasks.SetExitProcessorSLAMargin do\n  @moduledoc false\n  @behaviour Config.Provider\n  require Logger\n  @app :omg_watcher\n\n  @system_env_name_margin \"EXIT_PROCESSOR_SLA_MARGIN\"\n  @app_env_name_margin :exit_processor_sla_margin\n\n  @system_env_name_force \"EXIT_PROCESSOR_SLA_MARGIN_FORCED\"\n  @app_env_name_force :exit_processor_sla_margin_forced\n\n  def init(args) do\n    args\n  end\n\n  def load(config, _args) do\n    _ = Application.ensure_all_started(:logger)\n\n    Config.Reader.merge(config,\n      omg_watcher: [\n        exit_processor_sla_margin: get_exit_processor_sla_margin(),\n        exit_processor_sla_margin_forced: get_exit_processor_sla_forced()\n      ]\n    )\n  end\n\n  defp get_exit_processor_sla_margin() do\n    config_value = validate_int(get_env(@system_env_name_margin), Application.get_env(@app, @app_env_name_margin))\n    _ = Logger.info(\"CONFIGURATION: App: #{@app} Key: #{@system_env_name_margin} Value: #{inspect(config_value)}.\")\n    config_value\n  end\n\n  defp get_exit_processor_sla_forced() do\n    config_value = validate_bool(get_env(@system_env_name_force), Application.get_env(@app, @app_env_name_force))\n    _ = Logger.info(\"CONFIGURATION: App: #{@app} Key: #{@system_env_name_force} Value: #{inspect(config_value)}.\")\n    config_value\n  end\n\n  defp get_env(key), do: System.get_env(key)\n\n  defp validate_int(value, _default) when is_binary(value), do: to_int(value)\n  defp validate_int(_, default), do: default\n\n  defp validate_bool(value, _default) when is_binary(value), do: to_bool(String.upcase(value))\n  defp validate_bool(_, default), do: default\n\n  defp to_bool(\"TRUE\"), do: true\n  defp to_bool(\"FALSE\"), do: false\n  defp to_bool(_), do: exit(\"#{@system_env_name_force} either true or false.\")\n\n  defp to_int(value) do\n    case Integer.parse(value) do\n      {result, \"\"} -> result\n      _ -> exit(\"#{@system_env_name_margin} must be an integer.\")\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/release_tasks/set_tracer.ex",
    "content": "# Copyright 2019-2019 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.ReleaseTasks.SetTracer do\n  @moduledoc false\n  @behaviour Config.Provider\n  require Logger\n  @app :omg_watcher\n\n  def init(args) do\n    args\n  end\n\n  def load(config, args) do\n    _ = on_load()\n    adapter = Keyword.get(args, :system_adapter, System)\n    _ = Process.put(:system_adapter, adapter)\n    dd_disabled = get_dd_disabled()\n\n    tracer_config =\n      @app\n      |> Application.get_env(OMG.Watcher.Tracer)\n      |> Keyword.put(:disabled?, dd_disabled)\n\n    tracer_config =\n      case dd_disabled do\n        false ->\n          app_env = get_app_env()\n          Keyword.put(tracer_config, :env, app_env)\n\n        true ->\n          Keyword.put(tracer_config, :env, \"\")\n      end\n\n    Config.Reader.merge(config, omg_watcher: [{OMG.Watcher.Tracer, tracer_config}])\n  end\n\n  defp get_dd_disabled() do\n    disabled = Application.get_env(@app, OMG.Watcher.Tracer)[:disabled?]\n    dd_disabled? = validate_bool(get_env(\"DD_DISABLED\"), disabled)\n\n    _ = Logger.info(\"CONFIGURATION: App: #{@app} Key: DD_DISABLED Value: #{inspect(dd_disabled?)}.\")\n    dd_disabled?\n  end\n\n  defp get_app_env() do\n    env = validate_app_env(get_env(\"APP_ENV\"))\n    _ = Logger.info(\"CONFIGURATION: App: #{@app} Key: APP_ENV Value: #{inspect(env)}.\")\n    env\n  end\n\n  defp get_env(key) do\n    Process.get(:system_adapter).get_env(key)\n  end\n\n  defp validate_bool(value, _default) when is_binary(value), do: to_bool(String.upcase(value))\n  defp validate_bool(_, default), do: default\n\n  defp to_bool(\"TRUE\"), do: true\n  defp to_bool(\"FALSE\"), do: false\n  defp to_bool(_), do: exit(\"DD_DISABLED either true or false.\")\n\n  defp validate_app_env(value) when is_binary(value), do: value\n  defp validate_app_env(nil), do: exit(\"APP_ENV must be set.\")\n\n  defp on_load() do\n    _ = Application.ensure_all_started(:logger)\n    _ = Application.load(@app)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/root_chain_coordinator/core.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\ndefmodule OMG.Watcher.RootChainCoordinator.Core do\n  @moduledoc \"\"\"\n  Synchronizes multiple log-reading services on root chain height.\n  Each synchronized service must have a unique name.\n  Service reports its height by calling 'check_in'.\n  After all the services are checked in, coordinator returns currently synchronizable height,\n  for every service which asks by calling `RootChainCoordinator.get_height()`\n\n  In case a service fails, it is checked out and coordinator does not resume until the missing service checks_in again.\n  Coordinator periodically updates root chain height, looks after finality margins and ensures geth-queries aren't huge.\n\n  Coordinator is forgiving in terms of height backoffs:\n    - if the root chain's height backs off, it will treat it as an interim state and ignore the back off (noop)\n    - if any of the coordinated services backs off, it will register the backed off height and coordinate acordingly.\n      All services must accept a `SyncGuide` that tells them they should back off. All services must ensure this doesn't\n      cause them to process any events twice! All services must ensure they process everything!\n  \"\"\"\n\n  alias OMG.Watcher.RootChainCoordinator.Service\n  alias OMG.Watcher.RootChainCoordinator.SyncGuide\n\n  require Logger\n\n  defstruct configs_services: %{}, root_chain_height: 0, services: %{}\n\n  @type config_t :: keyword()\n  @type configs_services :: %{required(atom()) => config_t()}\n\n  @type t() :: %__MODULE__{\n          configs_services: configs_services,\n          root_chain_height: non_neg_integer(),\n          services: %{required(atom()) => Service.t()}\n        }\n\n  @type check_in_error_t :: {:error, :service_not_allowed}\n\n  @type ethereum_heights_result_t() :: %{atom() => non_neg_integer()}\n\n  # RootChainCoordinator is also checking if queries to Ethereum client don't get huge\n  @maximum_leap_forward 2_500\n\n  @doc \"\"\"\n  Initializes the state of the logic module.\n   - `configs_services` - configs of services that are being synchronized.\n     A map of the form `%{service_name => config}`. The `config`s are keyword lists with the following options:\n       - `:finality_margin` - number of Ethereum block confirmations to count before recognizing an event\n       - `:waits_for` - a list of other services, which should sync first. Each service in this list can be an atom,\n         being the name of the service, or a `{service_name, :no_margin}` pair, if the waiting should bypass the\n         finality margin of the awaited process.\n\n     An example config can be seen in `OMG.Watcher.Watcher.CoordinatorSetup`\n   - `root_chain_height` - current root chain height\n  \"\"\"\n  @spec init(map(), non_neg_integer()) :: t()\n  def init(configs_services, root_chain_height) do\n    %__MODULE__{configs_services: configs_services, root_chain_height: root_chain_height}\n  end\n\n  @doc \"\"\"\n  Updates Ethereum height on which a service is synchronized.\n  \"\"\"\n  @spec check_in(t(), pid(), pos_integer(), atom()) :: {:ok, t()} | check_in_error_t()\n  def check_in(state, pid, service_height, service_name) when is_integer(service_height) do\n    if allowed?(state.configs_services, service_name) do\n      update_service_synced_height(state, pid, service_height, service_name)\n    else\n      {:error, :service_not_allowed}\n    end\n  end\n\n  @doc \"\"\"\n  Sets root chain height, only allowing to progress, in case Ethereum RPC reports an earlier height\n  \"\"\"\n  @spec update_root_chain_height(t(), pos_integer()) :: {:ok, t()}\n  def update_root_chain_height(%__MODULE__{root_chain_height: old_height} = state, new_height)\n      when is_integer(new_height) do\n    {:ok, %{state | root_chain_height: max(old_height, new_height)}}\n  end\n\n  @doc \"\"\"\n  Provides synchronization guide to a service which asks\n  \"\"\"\n  @spec get_synced_info(t(), atom() | pid()) :: SyncGuide.t() | :nosync\n  def get_synced_info(state, pid) when is_pid(pid) do\n    service = Enum.find(state.services, fn service -> match?({_, %Service{pid: ^pid}}, service) end)\n\n    case service do\n      {service_name, _} -> get_synced_info(state, service_name)\n      nil -> :nosync\n    end\n  end\n\n  def get_synced_info(\n        %__MODULE__{root_chain_height: root_chain_height, configs_services: configs, services: services} = state,\n        service_name\n      )\n      when is_atom(service_name) do\n    if all_services_checked_in?(state) do\n      config = configs[service_name]\n      current_sync_height = services[service_name].synced_height\n\n      next_sync_height =\n        config\n        |> Keyword.get(:waits_for, [])\n        |> get_height_of_awaited(state)\n        |> consider_finality(configs[service_name], root_chain_height)\n        |> min(current_sync_height + @maximum_leap_forward)\n        |> min(root_chain_height)\n        |> max(0)\n\n      finality_bearing_root = max(0, root_chain_height - finality_margin_for(config))\n\n      %SyncGuide{sync_height: next_sync_height, root_chain_height: finality_bearing_root}\n    else\n      :nosync\n    end\n  end\n\n  @doc \"\"\"\n  Gets all the ethereum heights reported as synced to by the services (and the main root chain height acknowledged)\n  \"\"\"\n  @spec get_ethereum_heights(t()) :: ethereum_heights_result_t()\n  def get_ethereum_heights(%__MODULE__{root_chain_height: root_chain_height, services: services}) do\n    base_result_map = %{root_chain_height: root_chain_height}\n    Enum.into(services, base_result_map, fn {name, %Service{synced_height: height}} -> {name, height} end)\n  end\n\n  defp finality_margin_for(config), do: Keyword.get(config, :finality_margin, 0)\n  defp finality_margin_for!(config), do: Keyword.fetch!(config, :finality_margin)\n\n  # ensures we don't exceed the allowed finality margin applied to the root_chain_height\n  defp consider_finality(sync_height, config, root_chain_height),\n    do: min(sync_height, root_chain_height - finality_margin_for(config))\n\n  # get the earliest-synced of all of the services we're waiting for, if any, if none then root chain height\n  defp get_height_of_awaited([], %__MODULE__{root_chain_height: root_chain_height}),\n    # wait for nothing so root chain is the limit\n    do: root_chain_height\n\n  defp get_height_of_awaited(single_awaited, %__MODULE__{services: services}) when is_atom(single_awaited),\n    # we wait for a single service so get that\n    do: services[single_awaited].synced_height\n\n  defp get_height_of_awaited({single_awaited, :no_margin}, %__MODULE__{configs_services: configs} = state),\n    # in this clause we're waiting on a service, but skipping ahead its particular finality margin\n    do: get_height_of_awaited(single_awaited, state) + finality_margin_for!(configs[single_awaited])\n\n  defp get_height_of_awaited(awaited, state),\n    # we're waiting for multiple services, so iterate the list and get the least synced height\n    do: Enum.map(awaited, &get_height_of_awaited(&1, state)) |> Enum.min()\n\n  defp all_services_checked_in?(%__MODULE__{configs_services: configs_services, services: services}) do\n    sort = fn map -> map |> Map.keys() |> Enum.sort() end\n    sort.(configs_services) == sort.(services)\n  end\n\n  defp allowed?(configs_services, service_name), do: Map.has_key?(configs_services, service_name)\n\n  defp update_service_synced_height(state, pid, new_reported_sync_height, service_name) do\n    new_service_state = %Service{synced_height: new_reported_sync_height, pid: pid}\n    {:ok, %{state | services: Map.put(state.services, service_name, new_service_state)}}\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/root_chain_coordinator/measure.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.RootChainCoordinator.Measure do\n  @moduledoc \"\"\"\n  Counting business metrics sent to Datadog\n  \"\"\"\n\n  import OMG.Status.Metric.Event, only: [name: 2]\n\n  alias OMG.Status.Metric.Datadog\n  alias OMG.Watcher.RootChainCoordinator\n\n  def handle_event([:process, RootChainCoordinator], _, state, _config) do\n    value =\n      self()\n      |> Process.info(:message_queue_len)\n      |> elem(1)\n\n    _ = Datadog.gauge(name(state.service_name, :message_queue_len), value)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/root_chain_coordinator/service.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\ndefmodule OMG.Watcher.RootChainCoordinator.Service do\n  @moduledoc \"\"\"\n  Represents a state of a service that is coordinated by `RootChainCoordinator.Core`\n  \"\"\"\n\n  defstruct synced_height: nil, pid: nil\n\n  @type t() :: %__MODULE__{\n          synced_height: pos_integer(),\n          pid: pid()\n        }\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/root_chain_coordinator.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\ndefmodule OMG.Watcher.RootChainCoordinator do\n  @moduledoc \"\"\"\n  Synchronizes services on root chain height, see `OMG.Watcher.RootChainCoordinator.Core`\n  \"\"\"\n\n  alias OMG.Eth.EthereumHeight\n  alias OMG.Watcher.RootChainCoordinator.Core\n\n  use GenServer\n  use Spandex.Decorators\n\n  require Logger\n\n  defmodule SyncGuide do\n    @moduledoc \"\"\"\n    A guiding message to a coordinated service. Tells until which root chain height it is safe to advance syncing to.\n\n    `sync_height` - until where it is safe to process the root chain\n    `root_chain_height` - until where it is safe to pre-fetch and cache the events from the root chain\n    \"\"\"\n\n    defstruct [:root_chain_height, :sync_height]\n\n    @type t() :: %__MODULE__{\n            root_chain_height: non_neg_integer(),\n            sync_height: non_neg_integer()\n          }\n  end\n\n  @spec start_link(Core.configs_services()) :: GenServer.on_start()\n  def start_link(configs_services) do\n    GenServer.start_link(__MODULE__, configs_services, name: __MODULE__)\n  end\n\n  @doc \"\"\"\n  Notifies that calling service with name `service_name` is synced up to height `synced_height`.\n  `synced_height` is the height that the service is synced when calling this function.\n  \"\"\"\n  @decorate span(service: :ethereum_event_listener, type: :backend, name: \"check_in/2\")\n  @spec check_in(non_neg_integer(), atom()) :: :ok\n  def check_in(synced_height, service_name) do\n    GenServer.call(__MODULE__, {:check_in, synced_height, service_name})\n  end\n\n  @doc \"\"\"\n  Gets Ethereum height that services can synchronize up to.\n  \"\"\"\n  @decorate span(service: :ethereum_event_listener, type: :backend, name: \"get_sync_info/0\")\n  @spec get_sync_info() :: SyncGuide.t() | :nosync\n  def get_sync_info() do\n    GenServer.call(__MODULE__, :get_sync_info)\n  end\n\n  @doc \"\"\"\n  Gets all the current synced height for all the services checked in\n  \"\"\"\n  @spec get_ethereum_heights() :: {:ok, Core.ethereum_heights_result_t()}\n  def get_ethereum_heights() do\n    GenServer.call(__MODULE__, :get_ethereum_heights)\n  end\n\n  def init({args, configs_services}) do\n    _ = Logger.info(\"Starting #{__MODULE__} service. #{inspect({args, configs_services})}\")\n    metrics_collection_interval = Keyword.fetch!(args, :metrics_collection_interval)\n    coordinator_eth_height_check_interval_ms = Keyword.fetch!(args, :coordinator_eth_height_check_interval_ms)\n    {:ok, rootchain_height} = EthereumHeight.get()\n    {:ok, _} = schedule_get_ethereum_height(coordinator_eth_height_check_interval_ms)\n    state = Core.init(configs_services, rootchain_height)\n\n    configs_services\n    |> Map.keys()\n    |> request_sync()\n\n    {:ok, _} = :timer.send_interval(metrics_collection_interval, self(), :send_metrics)\n\n    _ = Logger.info(\"Started #{inspect(__MODULE__)}\")\n\n    {:ok, state}\n  end\n\n  def handle_info(:send_metrics, state) do\n    :ok = :telemetry.execute([:process, __MODULE__], %{}, state)\n    {:noreply, state}\n  end\n\n  def handle_info(:update_root_chain_height, state) do\n    {:ok, root_chain_height} = EthereumHeight.get()\n    {:ok, state} = Core.update_root_chain_height(state, root_chain_height)\n    {:noreply, state}\n  end\n\n  def handle_call({:check_in, synced_height, service_name}, {pid, _ref}, state) do\n    _ = Logger.debug(\"#{inspect(service_name)} checks in on height #{inspect(synced_height)}\")\n\n    {:ok, state} = Core.check_in(state, pid, synced_height, service_name)\n    {:reply, :ok, state}\n  end\n\n  def handle_call(:get_sync_info, {pid, _}, state) do\n    {:reply, Core.get_synced_info(state, pid), state}\n  end\n\n  def handle_call(:get_ethereum_heights, _from, state) do\n    {:reply, {:ok, Core.get_ethereum_heights(state)}, state}\n  end\n\n  defp schedule_get_ethereum_height(interval) do\n    :timer.send_interval(interval, self(), :update_root_chain_height)\n  end\n\n  defp request_sync(services) do\n    Enum.each(services, fn service -> safe_send(service, :sync) end)\n  end\n\n  defp safe_send(registered_name_or_pid, msg) do\n    send(registered_name_or_pid, msg)\n  rescue\n    ArgumentError ->\n      msg\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/signature.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.Signature do\n  @moduledoc \"\"\"\n  Adapted from https://github.com/exthereum/blockchain.\n  Defines helper functions for signing and getting the signature\n  of a transaction, as defined in Appendix F of the Yellow Paper.\n\n  For any of the following functions, if chain_id is specified,\n  it's assumed that we're post-fork and we should follow the\n  specification EIP-155 from:\n\n  https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md\n  \"\"\"\n  @base_recovery_id 27\n  @base_recovery_id_eip_155 35\n  @signature_len 32\n\n  @type keccak_hash :: binary()\n  @type public_key :: <<_::512>>\n  @type private_key :: <<_::256>>\n  @type hash_v :: integer()\n  @type hash_r :: integer()\n  @type hash_s :: integer()\n  @type signature_len :: unquote(@signature_len)\n\n  @doc \"\"\"\n  Recovers a public key from a signed hash.\n\n  This implements Eq.(208) of the Yellow Paper, adapted from https://stackoverflow.com/a/20000007\n\n  ## Example\n\n    iex(1)> OMG.Watcher.Signature.recover_public(<<2::256>>,\n    ...(1)>     28,\n    ...(1)>     38_938_543_279_057_362_855_969_661_240_129_897_219_713_373_336_787_331_739_561_340_553_100_525_404_231,\n    ...(1)>     23_772_455_091_703_794_797_226_342_343_520_955_590_158_385_983_376_086_035_257_995_824_653_222_457_926\n    ...(1)>     )\n    {:ok,\n     <<121, 190, 102, 126, 249, 220, 187, 172, 85, 160, 98, 149, 206, 135, 11, 7, 2, 155, 252, 219, 45, 206, 40,\n       217, 89, 242, 129, 91, 22, 248, 23, 152, 72, 58, 218, 119, 38, 163, 196, 101, 93, 164, 251, 252, 14, 17,\n       8, 168, 253, 23, 180, 72, 166, 133, 84, 25, 156, 71, 208, 143, 251, 16, 212, 184>>}\n  \"\"\"\n  @spec recover_public(keccak_hash(), hash_v, hash_r, hash_s, integer() | nil) ::\n          {:ok, public_key} | {:error, atom()}\n  def recover_public(hash, v, r, s, chain_id \\\\ nil) do\n    signature =\n      pad(:binary.encode_unsigned(r), @signature_len) <>\n        pad(:binary.encode_unsigned(s), @signature_len)\n\n    # Fork Ψ EIP-155\n    recovery_id =\n      if not is_nil(chain_id) and uses_chain_id?(v) do\n        v - chain_id * 2 - @base_recovery_id_eip_155\n      else\n        v - @base_recovery_id\n      end\n\n    case ExSecp256k1.recover_compact(hash, signature, recovery_id) do\n      {:ok, <<_byte::8, public_key::binary()>>} -> {:ok, public_key}\n      {:error, reason} -> {:error, reason}\n    end\n  end\n\n  @doc \"\"\"\n  Recovers a public key from a signed hash.\n\n  This implements Eq.(208) of the Yellow Paper, adapted from https://stackoverflow.com/a/20000007\n\n  ## Example\n\n    iex(1)> OMG.Watcher.Signature.recover_public(<<2::256>>, <<168, 39, 110, 198, 11, 113, 141, 8, 168, 151, 22, 210, 198, 150, 24, 111, 23,\n    ...(1)>         173, 42, 122, 59, 152, 143, 224, 214, 70, 96, 204, 31, 173, 154, 198, 97, 94,\n    ...(1)>         203, 172, 169, 136, 182, 131, 11, 106, 54, 190, 96, 128, 227, 222, 248, 231,\n    ...(1)>         75, 254, 141, 233, 113, 49, 74, 28, 189, 73, 249, 32, 89, 165, 27>>)\n    {:ok,\n      <<233, 102, 200, 175, 51, 251, 139, 85, 204, 181, 94, 133, 233, 88, 251, 156,\n        123, 157, 146, 192, 53, 73, 125, 213, 245, 12, 143, 102, 54, 70, 126, 35, 34,\n        167, 2, 255, 248, 68, 210, 117, 183, 156, 4, 185, 77, 27, 53, 239, 10, 57,\n        140, 63, 81, 87, 133, 241, 241, 210, 250, 35, 76, 232, 2, 153>>}\n  \"\"\"\n  def recover_public(hash, <<r::integer-size(256), s::integer-size(256), v::integer-size(8)>>, chain_id \\\\ nil) do\n    recover_public(hash, v, r, s, chain_id)\n  end\n\n  @spec uses_chain_id?(hash_v) :: boolean()\n  defp uses_chain_id?(v) do\n    v >= @base_recovery_id_eip_155\n  end\n\n  @spec pad(binary(), signature_len()) :: binary()\n  defp pad(binary, desired_length) do\n    desired_bits = desired_length * 8\n\n    case byte_size(binary) do\n      0 ->\n        <<0::size(desired_bits)>>\n\n      x when x <= desired_length ->\n        padding_bits = (desired_length - x) * 8\n        <<0::size(padding_bits)>> <> binary\n\n      _ ->\n        raise \"Binary too long for padding\"\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/state/core.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.State.Core do\n  @moduledoc \"\"\"\n  The state meant here is the state of the ledger (UTXO set), that determines spendability of coins and forms blocks.\n  All spend transactions, deposits and exits should sync on this for validity of moving funds.\n\n  ### Notes on loading of the UTXO set\n\n  We experienced long startup times on large UTXO set, which in some case caused timeouts and lethal `OMG.Watcher.State`\n  restart loop. To mitigate this issue we introduced loading UTXO set on demand (see GH#1103) instead of full load\n  on process startup.\n\n  During OMG.Watcher.State startup no UTXOs are fetched from DB, which is no longer blocking significantly.\n  Then during each of 6 utxo-related operations (see below) UTXO set is extended with UTXOs from DB to ensure operation\n  behavior hasn't been changed.\n\n  Transaction processing is populating the in-memory UTXO set and once block is formed newly created UTXO are inserted\n  to DB, but are also kept in process State. Service restart looses all UTXO created by transactions processed as well\n  as mempool transactions therefore DB content stays block-by-block consistent.\n\n  Operations that require full ledger information are:\n  - utxo_exists?\n  - exec\n  - form_block (and `close_block`)\n  - deposit\n  - exit_utxos\n\n  These operations assume that passed `OMG.Watcher.State.Core` struct instance contains sufficient UTXO information to proceed.\n  Therefore the UTXOs that in-memory state is unaware of are fetched from the `OMG.DB` and then merged into state.\n  As not every operation updates `OMG.DB` immediately additional `recently_spent` collection was added to in-memory\n  state to defend against double spends in transactions within the same block.\n\n  After block is formed `OMG.DB` contains full information up to the current block so we could waste in-memory\n  info about utxos and spends. If the process gets restarted before form_block all mempool transactions along with\n  created and spent utxos are lost and the ledger state basically resets to the previous block.\n  \"\"\"\n\n  defstruct [\n    :height,\n    :fee_claimer_address,\n    :child_block_interval,\n    utxos: %{},\n    pending_txs: [],\n    tx_index: 0,\n    utxo_db_updates: [],\n    recently_spent: MapSet.new(),\n    fees_paid: %{},\n    fee_claiming_started: false\n  ]\n\n  alias OMG.Output\n  alias OMG.Watcher.Block\n  alias OMG.Watcher.Crypto\n  alias OMG.Watcher.Fees\n  alias OMG.Watcher.State.Core\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.State.Transaction.Validator\n  alias OMG.Watcher.State.UtxoSet\n  alias OMG.Watcher.Utxo\n\n  require Logger\n  require Utxo\n\n  @type fee_summary_t() :: %{Transaction.Payment.currency() => pos_integer()}\n\n  @type t() :: %__MODULE__{\n          height: non_neg_integer(),\n          utxos: utxos,\n          pending_txs: list(Transaction.Recovered.t()),\n          tx_index: non_neg_integer(),\n          # NOTE: that this list is being build reverse, in some cases it may matter. It is reversed just before\n          #       it leaves this module in `form_block/3`\n          utxo_db_updates: list(db_update()),\n          # NOTE: because UTXO set is not loaded from DB entirely, we need to remember the UTXOs spent in already\n          # processed transaction before they get removed from DB on form_block.\n          recently_spent: MapSet.t(OMG.Watcher.Utxo.Position.t()),\n          # Summarizes fees paid by pending transactions that will be formed into current block. Fees will be claimed\n          # by appending `Transaction.Fee` txs after pending txs in current block.\n          fees_paid: fee_summary_t(),\n          # fees can be claimed at the end of the block, no other payments can be processed until next block\n          fee_claiming_started: boolean(),\n          fee_claimer_address: Crypto.address_t(),\n          child_block_interval: non_neg_integer()\n        }\n\n  @type deposit() :: %{\n          root_chain_txhash: Crypto.hash_t(),\n          log_index: non_neg_integer(),\n          blknum: non_neg_integer(),\n          currency: Crypto.address_t(),\n          owner: Crypto.address_t(),\n          amount: pos_integer(),\n          eth_height: pos_integer()\n        }\n\n  @type exit_t() :: %{utxo_pos: pos_integer()}\n\n  @type exit_finalization_t() :: %{utxo_pos: pos_integer()}\n\n  @type exiting_utxo_triggers_t() ::\n          [Utxo.Position.t()]\n          | [non_neg_integer()]\n          | [exit_t()]\n          | [exit_finalization_t()]\n          | [piggyback()]\n          | [in_flight_exit()]\n\n  @type in_flight_exit() :: %{in_flight_tx: binary()}\n  @type piggyback() :: %{tx_hash: Transaction.tx_hash(), output_index: non_neg_integer}\n\n  @type validities_t() :: {list(Utxo.Position.t()), list(Utxo.Position.t() | piggyback())}\n\n  @type utxos() :: %{Utxo.Position.t() => Utxo.t()}\n\n  @type db_update ::\n          {:put, :utxo, {Utxo.Position.db_t(), map()}}\n          | {:delete, :utxo, Utxo.Position.db_t()}\n          | {:put, :child_top_block_number, pos_integer()}\n          | {:put, :block, Block.db_t()}\n\n  @type exitable_utxos :: %{\n          owner: Crypto.address_t(),\n          currency: Crypto.address_t(),\n          amount: non_neg_integer(),\n          blknum: pos_integer(),\n          txindex: non_neg_integer(),\n          oindex: non_neg_integer()\n        }\n\n  @doc \"\"\"\n  Initializes the state from the values stored in `OMG.DB`\n  \"\"\"\n  @spec extract_initial_state(\n          height_query_result :: non_neg_integer() | :not_found,\n          child_block_interval :: pos_integer(),\n          fee_claimer_address :: Crypto.address_t()\n        ) :: {:ok, t()} | {:error, :top_block_number_not_found}\n  def extract_initial_state(height_query_result, child_block_interval, fee_claimer_address)\n      when is_integer(height_query_result) and is_integer(child_block_interval) do\n    state = %__MODULE__{\n      height: height_query_result + child_block_interval,\n      fee_claimer_address: fee_claimer_address,\n      child_block_interval: child_block_interval\n    }\n\n    {:ok, state}\n  end\n\n  def extract_initial_state(:not_found, _child_block_interval, _fee_claimer_address) do\n    {:error, :top_block_number_not_found}\n  end\n\n  @doc \"\"\"\n  Tell whether utxo position was created or spent by current state.\n  \"\"\"\n  @spec utxo_processed?(OMG.Watcher.Utxo.Position.t(), t()) :: boolean()\n  def utxo_processed?(utxo_pos, %Core{utxos: utxos, recently_spent: recently_spent}) do\n    Map.has_key?(utxos, utxo_pos) or MapSet.member?(recently_spent, utxo_pos)\n  end\n\n  @doc \"\"\"\n  Extends in-memory utxo set with needed utxos loaded from DB\n  See also: State.init_utxos_from_db/2\n  \"\"\"\n  @spec with_utxos(t(), utxos()) :: t()\n  def with_utxos(%Core{utxos: utxos} = state, db_utxos) do\n    %{state | utxos: UtxoSet.apply_effects(utxos, [], db_utxos)}\n  end\n\n  @doc \"\"\"\n  Includes the transaction into the state when valid, rejects otherwise.\n\n  NOTE that tx is assumed to have distinct inputs, that should be checked in prior state-less validation\n\n  See docs/transaction_validation.md for more information about stateful and stateless validation.\n  \"\"\"\n  @spec exec(state :: t(), tx :: Transaction.Recovered.t(), fees :: Fees.optional_fee_t()) ::\n          {:ok, {Transaction.tx_hash(), pos_integer, non_neg_integer}, t()}\n          | {{:error, Validator.can_process_tx_error()}, t()}\n  def exec(%Core{} = state, %Transaction.Recovered{} = tx, fees) do\n    tx_hash = Transaction.raw_txhash(tx)\n\n    case Validator.can_process_tx(state, tx, fees) do\n      {:ok, fees_paid} ->\n        {:ok, {tx_hash, state.height, state.tx_index},\n         state\n         |> apply_tx(tx)\n         |> add_pending_tx(tx)\n         |> handle_fees(tx, fees_paid)}\n\n      {{:error, _reason}, _state} = error ->\n        error\n    end\n  end\n\n  @doc \"\"\"\n    Filter user utxos from db response.\n    It may take a while for a large response from db\n  \"\"\"\n  @spec standard_exitable_utxos(UtxoSet.query_result_t(), Crypto.address_t()) ::\n          list(exitable_utxos)\n  def standard_exitable_utxos(utxos_query_result, address) do\n    utxos_query_result\n    |> UtxoSet.init()\n    |> UtxoSet.filter_owned_by(address)\n    |> UtxoSet.zip_with_positions()\n    |> Enum.map(fn {{_, utxo}, position} -> utxo_to_exitable_utxo_map(utxo, position) end)\n  end\n\n  @doc \"\"\"\n   - Generates block and calculates it's root hash for submission\n   - generates requests to the persistence layer for a block\n   - processes pending txs gathered, updates height etc\n   - clears `recently_spent` collection\n  \"\"\"\n  @spec form_block(state :: t()) :: {:ok, {Block.t(), [db_update]}, new_state :: t()}\n  def form_block(state) do\n    txs = Enum.reverse(state.pending_txs)\n\n    block = Block.hashed_txs_at(txs, state.height)\n\n    db_updates_block = {:put, :block, Block.to_db_value(block)}\n    db_updates_top_block_number = {:put, :child_top_block_number, state.height}\n\n    db_updates = [db_updates_top_block_number, db_updates_block | state.utxo_db_updates] |> Enum.reverse()\n\n    new_state = %Core{\n      state\n      | tx_index: 0,\n        height: state.height + state.child_block_interval,\n        pending_txs: [],\n        utxo_db_updates: [],\n        recently_spent: MapSet.new(),\n        fees_paid: %{},\n        fee_claiming_started: false\n    }\n\n    _ = :telemetry.execute([:block_transactions, __MODULE__], %{txs: txs}, %{})\n    {:ok, {block, db_updates}, new_state}\n  end\n\n  @doc \"\"\"\n  Processes a deposit event, introducing a UTXO into the ledger's state. From then on it is spendable on the child chain\n\n  **NOTE** this expects that each deposit event is fed to here exactly once, so this must be ensured elsewhere.\n           There's no double-checking of this constraint done here.\n  \"\"\"\n  @spec deposit(deposits :: [deposit()], state :: t()) :: {:ok, [db_update], new_state :: t()}\n  def deposit(deposits, %Core{utxos: utxos} = state) do\n    new_utxos_map = Enum.into(deposits, %{}, &deposit_to_utxo/1)\n    new_utxos = UtxoSet.apply_effects(utxos, [], new_utxos_map)\n    db_updates = UtxoSet.db_updates([], new_utxos_map)\n\n    _ = if deposits != [], do: Logger.info(\"Recognized deposits #{inspect(deposits)}\")\n\n    new_state = %Core{state | utxos: new_utxos}\n    {:ok, db_updates, new_state}\n  end\n\n  @doc \"\"\"\n  Retrieves exitable utxo positions from variety of exit events. Accepts either\n   - a list of utxo positions (decoded)\n   - a list of utxo positions (encoded)\n   - a list of full exit infos containing the utxo positions\n   - a list of full exit events (from ethereum listeners) containing the utxo positions\n   - a list of IFE started events\n   - a list of IFE input/output piggybacked events\n\n  NOTE: It is done like this to accommodate different clients of this function as they can either be\n  bare `EthereumEventListener` or `ExitProcessor`. Hence different forms it can get the exiting utxos delivered\n  \"\"\"\n  @spec extract_exiting_utxo_positions(exiting_utxo_triggers_t(), t()) :: list(Utxo.Position.t())\n  def extract_exiting_utxo_positions(exit_infos, state)\n\n  def extract_exiting_utxo_positions([], %Core{}), do: []\n\n  # list of full exit infos (from events) containing the utxo positions\n  def extract_exiting_utxo_positions([%{utxo_pos: _} | _] = utxo_position_events, state),\n    do: utxo_position_events |> Enum.map(& &1.utxo_pos) |> extract_exiting_utxo_positions(state)\n\n  # list of full exit events (from ethereum listeners)\n  def extract_exiting_utxo_positions([%{call_data: %{utxo_pos: _}} | _] = utxo_position_events, state),\n    do: utxo_position_events |> Enum.map(& &1.call_data) |> extract_exiting_utxo_positions(state)\n\n  # list of utxo positions (encoded)\n  def extract_exiting_utxo_positions([encoded_utxo_pos | _] = encoded_utxo_positions, %Core{})\n      when is_integer(encoded_utxo_pos),\n      do: Enum.map(encoded_utxo_positions, &Utxo.Position.decode!/1)\n\n  # list of IFE input/output piggybacked events\n  def extract_exiting_utxo_positions([%{call_data: %{in_flight_tx: _}} | _] = start_ife_events, %Core{}) do\n    _ = Logger.info(\"Recognized exits from IFE starts #{inspect(start_ife_events)}\")\n\n    Enum.flat_map(start_ife_events, fn %{call_data: %{in_flight_tx: tx_bytes}} ->\n      {:ok, tx} = Transaction.decode(tx_bytes)\n      Transaction.get_inputs(tx)\n    end)\n  end\n\n  # list of IFE input piggybacked events (they're ignored)\n  def extract_exiting_utxo_positions(\n        [%{tx_hash: _, omg_data: %{piggyback_type: :input}} | _] = piggyback_events,\n        %Core{}\n      ) do\n    _ = Logger.info(\"Ignoring input piggybacks #{inspect(piggyback_events)}\")\n    []\n  end\n\n  # list of IFE output piggybacked events. This is used by the child chain only. `OMG.Watcher.ExitProcessor` figures out\n  # the utxo positions to exit on its own\n  def extract_exiting_utxo_positions(\n        [%{tx_hash: _, omg_data: %{piggyback_type: :output}} | _] = piggyback_events,\n        %Core{} = state\n      ) do\n    _ = Logger.info(\"Recognized exits from piggybacks #{inspect(piggyback_events)}\")\n\n    piggyback_events\n    |> Enum.map(&find_utxo_matching_piggyback(&1, state))\n    |> Enum.filter(fn utxo -> utxo != nil end)\n    |> Enum.map(fn {position, _} -> position end)\n  end\n\n  # list of utxo positions (decoded)\n  def extract_exiting_utxo_positions([Utxo.position(_, _, _) | _] = utxo_positions, %Core{}), do: utxo_positions\n\n  @doc \"\"\"\n  Spends exited utxos.\n  Note: state passed here is already extended with DB.\n  \"\"\"\n  @spec exit_utxos(exiting_utxos :: list(Utxo.Position.t()), state :: t()) ::\n          {:ok, {[db_update], validities_t()}, new_state :: t()}\n  def exit_utxos([], %Core{} = state), do: {:ok, {[], {[], []}}, state}\n\n  def exit_utxos(\n        [Utxo.position(_, _, _) | _] = exiting_utxos,\n        %Core{utxos: utxos, recently_spent: recently_spent} = state\n      ) do\n    _ = Logger.info(\"Recognized exits #{inspect(exiting_utxos)}\")\n\n    {valid, _invalid} = validities = Enum.split_with(exiting_utxos, &utxo_exists?(&1, state))\n\n    new_utxos = UtxoSet.apply_effects(utxos, valid, %{})\n    new_spends = MapSet.union(recently_spent, MapSet.new(valid))\n    db_updates = UtxoSet.db_updates(valid, %{})\n    new_state = %{state | utxos: new_utxos, recently_spent: new_spends}\n\n    {:ok, {db_updates, validities}, new_state}\n  end\n\n  @doc \"\"\"\n  Checks whether utxo exists in UTXO set.\n  Note: state passed here is already extended with DB.\n  \"\"\"\n  @spec utxo_exists?(Utxo.Position.t(), t()) :: boolean()\n  def utxo_exists?(Utxo.position(_blknum, _txindex, _oindex) = utxo_pos, %Core{utxos: utxos}) do\n    UtxoSet.exists?(utxos, utxo_pos)\n  end\n\n  @doc \"\"\"\n      Gets the current block's height and whether at the beginning of the block\n  \"\"\"\n  @spec get_status(t()) :: {current_block_height :: non_neg_integer(), is_block_beginning :: boolean()}\n  def get_status(%__MODULE__{height: height, tx_index: tx_index, pending_txs: pending}) do\n    is_beginning = tx_index == 0 && Enum.empty?(pending)\n    {height, is_beginning}\n  end\n\n  defp add_pending_tx(%Core{pending_txs: pending_txs, tx_index: tx_index} = state, %Transaction.Recovered{} = new_tx) do\n    _ = :telemetry.execute([:pending_transactions, __MODULE__], %{new_tx: new_tx}, %{})\n\n    %Core{\n      state\n      | tx_index: tx_index + 1,\n        pending_txs: [new_tx | pending_txs]\n    }\n  end\n\n  defp apply_tx(\n         %Core{\n           height: blknum,\n           tx_index: tx_index,\n           utxos: utxos,\n           recently_spent: recently_spent,\n           utxo_db_updates: db_updates\n         } = state,\n         %Transaction.Recovered{signed_tx: %{raw_tx: tx}}\n       ) do\n    {spent_input_pointers, new_utxos_map} = get_effects(tx, blknum, tx_index)\n    new_utxos = UtxoSet.apply_effects(utxos, spent_input_pointers, new_utxos_map)\n    new_db_updates = UtxoSet.db_updates(spent_input_pointers, new_utxos_map)\n    # NOTE: child chain mode don't need 'spend' data for now. Consider to add only in Watcher's modes - OMG-382\n    spent_blknum_updates = Enum.map(spent_input_pointers, &{:put, :spend, {Utxo.Position.to_input_db_key(&1), blknum}})\n\n    %Core{\n      state\n      | utxos: new_utxos,\n        recently_spent: MapSet.union(recently_spent, MapSet.new(spent_input_pointers)),\n        utxo_db_updates: new_db_updates ++ spent_blknum_updates ++ db_updates\n    }\n  end\n\n  # Post-processing step of transaction execution. It either claim for Transaction.Fee and collect for the rest.\n  @spec handle_fees(state :: t(), Transaction.Recovered.t(), map()) :: t()\n  defp handle_fees(state, %Transaction.Recovered{signed_tx: %{raw_tx: %Transaction.Fee{}}} = tx, _fees_paid) do\n    [output] = Transaction.get_outputs(tx)\n\n    state\n    |> flush_collected_fees_for_token(output)\n    |> disallow_payments()\n  end\n\n  defp handle_fees(state, _tx, fees_paid) do\n    collect_fees(state, fees_paid)\n  end\n\n  # attempts to build a standard response data about a single UTXO, based on an abstract `output` structure\n  # so that the data can be useful to discover exitable UTXOs\n  defp utxo_to_exitable_utxo_map(%Utxo{output: %{output_type: otype} = output}, Utxo.position(blknum, txindex, oindex)) do\n    output\n    |> Map.from_struct()\n    |> Map.take([:owner, :currency, :amount])\n    |> Map.put(:otype, otype)\n    |> Map.put(:blknum, blknum)\n    |> Map.put(:txindex, txindex)\n    |> Map.put(:oindex, oindex)\n  end\n\n  defp collect_fees(%Core{fees_paid: fees_paid} = state, token_surpluses) do\n    fees_paid_with_new =\n      token_surpluses\n      |> Enum.reject(fn {_token, amount} -> amount == 0 end)\n      |> Map.new()\n      |> Map.merge(fees_paid, fn _token, collected, tx_surplus -> collected + tx_surplus end)\n\n    %Core{state | fees_paid: fees_paid_with_new}\n  end\n\n  defp disallow_payments(state), do: %Core{state | fee_claiming_started: true}\n\n  defp flush_collected_fees_for_token(state, %Output{currency: token}) do\n    %Core{state | fees_paid: Map.delete(state.fees_paid, token)}\n  end\n\n  # Effects of a payment transaction - spends all inputs and creates all outputs\n  # Relies on the polymorphic `get_inputs` and `get_outputs` of `Transaction`\n  defp get_effects(tx, blknum, tx_index) do\n    {Transaction.get_inputs(tx), utxos_from(tx, blknum, tx_index)}\n  end\n\n  defp utxos_from(tx, blknum, tx_index) do\n    hash = Transaction.raw_txhash(tx)\n\n    tx\n    |> Transaction.get_outputs()\n    |> Enum.with_index()\n    |> Enum.map(fn {output, oindex} ->\n      {Utxo.position(blknum, tx_index, oindex), output}\n    end)\n    |> Enum.into(%{}, fn {input_pointer, output} ->\n      {input_pointer, %Utxo{output: output, creating_txhash: hash}}\n    end)\n  end\n\n  defp deposit_to_utxo(deposit) do\n    %{blknum: blknum, currency: cur, owner: owner, amount: amount} = deposit\n\n    Transaction.Payment.new([], [{owner, cur, amount}])\n    |> utxos_from(blknum, 0)\n    |> Enum.map(& &1)\n    |> hd()\n  end\n\n  # We're looking for a UTXO that a piggyback of an in-flight IFE is referencing.\n  # This is useful when trying to do something with the outputs that are piggybacked (like exit them), without their\n  # position.\n  # Only relevant for output piggybacks\n  defp find_utxo_matching_piggyback(piggyback_events, state) do\n    %{omg_data: %{piggyback_type: :output}, tx_hash: tx_hash, output_index: oindex} = piggyback_events\n    UtxoSet.find_matching_utxo(state.utxos, tx_hash, oindex)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/state/measure.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.State.Measure do\n  @moduledoc \"\"\"\n  Counting business metrics sent to Datadog\n  \"\"\"\n\n  import OMG.Status.Metric.Event, only: [name: 1]\n  alias OMG.Status.Metric.Datadog\n  alias OMG.Watcher.State\n  alias OMG.Watcher.State.Core\n  alias OMG.Watcher.State.MeasurementCalculation\n\n  @supported_events [\n    [:process, State],\n    [:pending_transactions, Core],\n    [:block_transactions, Core]\n  ]\n  def supported_events(), do: @supported_events\n\n  def handle_event([:process, State], _, %Core{} = state, _config) do\n    execute = fn ->\n      try do\n        Enum.each(MeasurementCalculation.calculate(state), fn\n          {key, value} -> _ = Datadog.gauge(name(key), value)\n          {key, value, metadata} -> _ = Datadog.gauge(name(key), value, tags: [metadata])\n        end)\n      rescue\n        _e in ArgumentError ->\n          # This exception occurs when we run without datadog (statix).\n          # In normal scenarios, telemetry would get detached but because this is a spawned proces...\n          :ok\n      end\n    end\n\n    # TODO proper fix! this is a very hackish approach to get measurements off the back\n    # of OMG State\n    _ = Task.start(execute)\n    :ok\n  end\n\n  def handle_event([:pending_transactions, Core], %{new_tx: _new_tx}, _, _config) do\n    _ = Datadog.increment(name(:pending_transactions), 1)\n  end\n\n  def handle_event([:block_transactions, Core], %{txs: txs}, _, _config) do\n    _ = Datadog.gauge(name(:block_transactions), Enum.count(txs))\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/state/measurement_calculation.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\ndefmodule OMG.Watcher.State.MeasurementCalculation do\n  @moduledoc \"\"\"\n   Calculations based on OMG State that are sent to monitoring service.\n  \"\"\"\n  alias OMG.Eth.Encoding\n  alias OMG.Watcher.State.Core\n\n  # TODO: functions here reach uncleanly into the UtxoSet (not going through `OMG.Watcher.State.UtxoSet`) - is this bad?\n\n  def calculate(%Core{utxos: utxos}) do\n    balance =\n      Enum.map(\n        balance(utxos),\n        fn {currency, amount} ->\n          {:balance, amount, \"currency:#{Encoding.to_hex(currency)}\"}\n        end\n      )\n\n    unique_users = {:unique_users, unique_users(utxos)}\n    List.flatten([unique_users, balance])\n  end\n\n  defp unique_users(utxos) do\n    utxos\n    |> Enum.map(fn {_, %OMG.Watcher.Utxo{output: output}} -> output end)\n    # NOTE: we're counting only outputs that define an owner, so that this remains an owner-counting metric.\n    #       For anything, where the owner isn't well defined, careful rethinking would be required\n    |> Enum.map(&Map.get(&1, :owner))\n    |> Enum.filter(& &1)\n    |> Enum.uniq()\n    |> Enum.count()\n  end\n\n  defp balance(utxos) do\n    utxos\n    |> Enum.map(fn {_, %OMG.Watcher.Utxo{output: output}} -> output end)\n    # NOTE: we're counting only outputs that define a currency and amount, so that this remains a balance-counting\n    #       metric. For anything, where the balance isn't well defined, careful rethinking would be required\n    |> Enum.map(&{Map.get(&1, :currency), Map.get(&1, :amount, 0)})\n    |> Enum.filter(fn {currency, _} -> currency end)\n    |> Enum.reduce(%{}, fn {currency, amount}, acc ->\n      Map.update(acc, currency, amount, &(&1 + amount))\n    end)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/state/transaction/fee.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.State.Transaction.Fee do\n  @moduledoc \"\"\"\n  Internal representation of a fee claiming transaction in plasma chain.\n  \"\"\"\n  alias OMG.Output\n  alias OMG.Watcher.Crypto\n  alias OMG.Watcher.State.Transaction\n\n  require Transaction\n\n  @fee_token_claim_tx_type OMG.Watcher.WireFormatTypes.tx_type_for(:tx_fee_token_claim)\n  @fee_token_claim_output_type OMG.Watcher.WireFormatTypes.output_type_for(:output_fee_token_claim)\n\n  defstruct [:tx_type, :outputs, :nonce]\n\n  @type t() :: %__MODULE__{\n          tx_type: non_neg_integer(),\n          outputs: [Output.t()],\n          nonce: Crypto.hash_t()\n        }\n\n  @doc \"\"\"\n  Creates new fee claiming transaction\n  \"\"\"\n  @spec new(\n          blknum :: non_neg_integer(),\n          {Crypto.address_t(), Transaction.Payment.currency(), pos_integer}\n        ) :: t()\n  def new(blknum, {owner, currency, amount}) do\n    %__MODULE__{\n      tx_type: @fee_token_claim_tx_type,\n      outputs: [new_output(owner, currency, amount)],\n      nonce: to_nonce(blknum, currency)\n    }\n  end\n\n  @doc \"\"\"\n  Creates output for fee transaction\n  \"\"\"\n  @spec new_output(owner :: Crypto.address_t(), currency :: Transaction.Payment.currency(), amount :: pos_integer()) ::\n          Output.t()\n  def new_output(owner, currency, amount) do\n    %Output{\n      owner: owner,\n      currency: currency,\n      amount: amount,\n      output_type: @fee_token_claim_output_type\n    }\n  end\n\n  @doc \"\"\"\n  Transforms the structure of RLP items after a successful RLP decode of a raw transaction, into a structure instance\n  \"\"\"\n  def reconstruct([tx_type, outputs_rlp, nonce_rlp]) do\n    with {:ok, outputs} <- reconstruct_outputs(outputs_rlp),\n         {:ok, nonce} <- reconstruct_nonce(nonce_rlp),\n         do: {:ok, %__MODULE__{tx_type: tx_type, outputs: outputs, nonce: nonce}}\n  end\n\n  def reconstruct(_), do: {:error, :malformed_transaction}\n\n  defp reconstruct_outputs(outputs_rlp) do\n    outputs = Enum.map(outputs_rlp, &Output.reconstruct/1)\n\n    with nil <- Enum.find(outputs, &match?({:error, _}, &1)),\n         true <- only_allowed_output_types?(outputs) || {:error, :tx_cannot_create_output_type},\n         do: {:ok, outputs}\n  rescue\n    _ -> {:error, :malformed_outputs}\n  end\n\n  defp reconstruct_nonce(nonce) when is_binary(nonce) and byte_size(nonce) == 32, do: {:ok, nonce}\n  defp reconstruct_nonce(_), do: {:error, :malformed_nonce}\n\n  defp only_allowed_output_types?([%Output{}]), do: true\n  defp only_allowed_output_types?(_), do: false\n\n  @spec to_nonce(non_neg_integer(), Transaction.Payment.currency()) :: Crypto.hash_t()\n  defp to_nonce(blknum, token) do\n    blknum_bytes = ABI.TypeEncoder.encode_raw([blknum], [{:uint, 256}])\n    token_bytes = ABI.TypeEncoder.encode_raw([token], [:address])\n\n    Crypto.hash(blknum_bytes <> token_bytes)\n  end\nend\n\ndefimpl OMG.Watcher.State.Transaction.Protocol, for: OMG.Watcher.State.Transaction.Fee do\n  alias OMG.Output\n  alias OMG.Watcher.State.Transaction\n\n  @doc \"\"\"\n  Turns a structure instance into a structure of RLP items, ready to be RLP encoded, for a raw transaction\n  \"\"\"\n  @spec get_data_for_rlp(Transaction.Fee.t()) :: list(any())\n  def get_data_for_rlp(%Transaction.Fee{tx_type: tx_type, outputs: outputs, nonce: nonce}) do\n    [\n      tx_type,\n      Enum.map(outputs, &Output.get_data_for_rlp/1),\n      nonce\n    ]\n  end\n\n  @doc \"\"\"\n  Fee claiming transaction spends single pseudo-output from collected fees.\n  \"\"\"\n  @spec get_outputs(Transaction.Fee.t()) :: list(Output.t())\n  def get_outputs(%Transaction.Fee{outputs: outputs}), do: outputs\n\n  @doc \"\"\"\n  Fee claiming transaction does not contain any inputs.\n  \"\"\"\n  @spec get_inputs(Transaction.Fee.t()) :: list(OMG.Watcher.Utxo.Position.t())\n  def get_inputs(%Transaction.Fee{}), do: []\n\n  @doc \"\"\"\n  Tells whether Fee claiming transaction is valid\n  \"\"\"\n  @spec valid?(Transaction.Fee.t(), Transaction.Signed.t()) ::\n          {:error, :wrong_number_of_fee_outputs | :fee_output_amount_has_to_be_positive}\n  def valid?(%Transaction.Fee{} = fee_tx, _signed_tx) do\n    # we're able to check structure validity => single output with amount > 0\n    outputs = Transaction.get_outputs(fee_tx)\n\n    with true <- length(outputs) == 1 || {:error, :wrong_number_of_fee_outputs},\n         [output] = outputs,\n         true <- output.amount > 0 || {:error, :fee_output_amount_has_to_be_positive},\n         do: true\n  end\n\n  @doc \"\"\"\n  Fee claiming transaction is not used to transfer funds\n  \"\"\"\n  @spec can_apply?(Transaction.Fee.t(), list(Output.t())) ::\n          {:ok, map()}\n          | {:error, :surplus_in_token_not_collected | :claimed_and_collected_amounts_mismatch}\n  def can_apply?(%Transaction.Fee{outputs: [claimed]}, outputs) do\n    with %Output{} = collected <- find_output_by_currency(outputs, claimed.currency),\n         true <- amounts_equal?(collected.amount, claimed.amount),\n         do: {:ok, %{}}\n  end\n\n  defp find_output_by_currency(outputs, currency),\n    do: Enum.find(outputs, {:error, :surplus_in_token_not_collected}, fn o -> o.currency == currency end)\n\n  defp amounts_equal?(collected, claimed) when collected == claimed, do: true\n  defp amounts_equal?(_, _), do: {:error, :claimed_and_collected_amounts_mismatch}\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/state/transaction/payment.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.State.Transaction.Payment do\n  @moduledoc \"\"\"\n  Internal representation of a raw payment transaction done on Plasma chain.\n\n  This module holds the representation of a \"raw\" transaction, i.e. without signatures nor recovered input spenders\n  \"\"\"\n  alias OMG.Watcher.Crypto\n\n  alias OMG.Output\n  alias OMG.Watcher.RawData\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.Utxo\n\n  require Transaction\n  require Utxo\n\n  @zero_metadata <<0::256>>\n  @payment_tx_type OMG.Watcher.WireFormatTypes.tx_type_for(:tx_payment_v1)\n  @payment_output_type OMG.Watcher.WireFormatTypes.output_type_for(:output_payment_v1)\n\n  defstruct [:tx_type, :inputs, :outputs, metadata: @zero_metadata]\n\n  @type t() :: %__MODULE__{\n          tx_type: non_neg_integer(),\n          inputs: list(OMG.Watcher.Utxo.Position.t()),\n          outputs: list(Output.t()),\n          metadata: Transaction.metadata()\n        }\n\n  @type currency() :: Crypto.address_t()\n\n  @max_inputs 4\n  @max_outputs 4\n\n  defmacro max_inputs() do\n    quote do\n      unquote(@max_inputs)\n    end\n  end\n\n  defmacro max_outputs() do\n    quote do\n      unquote(@max_outputs)\n    end\n  end\n\n  @doc \"\"\"\n  Creates a new raw transaction structure from a list of inputs and a list of outputs, given in a succinct tuple form.\n\n  assumptions:\n  ```\n    length(inputs) <= @max_inputs\n    length(outputs) <= @max_outputs\n  ```\n  \"\"\"\n  @spec new(\n          list({pos_integer, pos_integer, 0..unquote(@max_outputs - 1)}),\n          list({Crypto.address_t(), currency(), pos_integer}),\n          Transaction.metadata()\n        ) :: t()\n  def new(inputs, outputs, metadata \\\\ @zero_metadata)\n\n  def new(inputs, outputs, metadata)\n      when Transaction.is_metadata(metadata) and length(inputs) <= @max_inputs and length(outputs) <= @max_outputs do\n    inputs = Enum.map(inputs, &new_input/1)\n    outputs = Enum.map(outputs, &new_output/1)\n    %__MODULE__{tx_type: @payment_tx_type, inputs: inputs, outputs: outputs, metadata: metadata}\n  end\n\n  @doc \"\"\"\n  Transforms the structure of RLP items after a successful RLP decode of a raw transaction, into a structure instance\n  \"\"\"\n  def reconstruct([tx_type, inputs_rlp, outputs_rlp, tx_data_rlp, metadata_rlp]) do\n    with {:ok, inputs} <- reconstruct_inputs(inputs_rlp),\n         {:ok, outputs} <- reconstruct_outputs(outputs_rlp),\n         {:ok, tx_data} <- RawData.parse_uint256(tx_data_rlp),\n         :ok <- check_tx_data(tx_data),\n         {:ok, metadata} <- reconstruct_metadata(metadata_rlp),\n         do: {:ok, %__MODULE__{tx_type: tx_type, inputs: inputs, outputs: outputs, metadata: metadata}}\n  end\n\n  def reconstruct(_), do: {:error, :malformed_transaction}\n\n  # `new_input/1` and `new_output/1` are here to just help interpret the short-hand form of inputs outputs when doing\n  # `new/3`\n  defp new_input({blknum, txindex, oindex}), do: Utxo.position(blknum, txindex, oindex)\n\n  defp new_output({owner, currency, amount}) do\n    %Output{\n      owner: owner,\n      currency: currency,\n      amount: amount,\n      output_type: @payment_output_type\n    }\n  end\n\n  defp reconstruct_inputs(inputs_rlp) do\n    with {:ok, inputs} <- parse_inputs(inputs_rlp),\n         do: {:ok, inputs}\n  end\n\n  defp reconstruct_outputs([]), do: {:error, :empty_outputs}\n\n  defp reconstruct_outputs(outputs_rlp) do\n    with {:ok, outputs} <- parse_outputs(outputs_rlp),\n         do: {:ok, outputs}\n  end\n\n  # txData is required to be zero in the contract\n  defp check_tx_data(0), do: :ok\n  defp check_tx_data(_), do: {:error, :malformed_tx_data}\n\n  defp reconstruct_metadata(metadata) when Transaction.is_metadata(metadata), do: {:ok, metadata}\n  defp reconstruct_metadata(_), do: {:error, :malformed_metadata}\n\n  defp parse_inputs(inputs_rlp) do\n    with true <- Enum.count(inputs_rlp) <= @max_inputs || {:error, :too_many_inputs},\n         # NOTE: workaround for https://github.com/omgnetwork/ex_plasma/issues/19.\n         #       remove, when this is blocked on `ex_plasma` end\n         true <- Enum.all?(inputs_rlp, &(&1 != <<0::256>>)) || {:error, :malformed_inputs},\n         do: {:ok, Enum.map(inputs_rlp, &parse_input!/1)}\n  rescue\n    _ -> {:error, :malformed_inputs}\n  end\n\n  defp parse_outputs(outputs_rlp) do\n    outputs = Enum.map(outputs_rlp, &Output.reconstruct/1)\n\n    with true <- Enum.count(outputs) <= @max_outputs || {:error, :too_many_outputs},\n         nil <- Enum.find(outputs, &match?({:error, _}, &1)),\n         true <- only_allowed_output_types?(outputs) || {:error, :tx_cannot_create_output_type},\n         do: {:ok, outputs}\n  rescue\n    _ -> {:error, :malformed_outputs}\n  end\n\n  defp only_allowed_output_types?(outputs),\n    do: Enum.all?(outputs, &match?(%Output{}, &1))\n\n  defp parse_input!(encoded), do: OMG.Watcher.Utxo.Position.decode!(encoded)\nend\n\ndefimpl OMG.Watcher.State.Transaction.Protocol, for: OMG.Watcher.State.Transaction.Payment do\n  alias OMG.Output\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.Utxo\n\n  require Transaction\n  require Utxo\n\n  @empty_signature <<0::size(520)>>\n\n  @doc \"\"\"\n  Turns a structure instance into a structure of RLP items, ready to be RLP encoded, for a raw transaction\n  \"\"\"\n  @spec get_data_for_rlp(Transaction.Payment.t()) :: list(any())\n  def get_data_for_rlp(%Transaction.Payment{tx_type: tx_type, inputs: inputs, outputs: outputs, metadata: metadata})\n      when Transaction.is_metadata(metadata),\n      do: [\n        tx_type,\n        Enum.map(inputs, &OMG.Watcher.Utxo.Position.get_data_for_rlp/1),\n        Enum.map(outputs, &Output.get_data_for_rlp/1),\n        # used to be optional and as such was `if`-appended if not null here\n        # When it is not optional, and there's the if, dialyzer complains about the if\n        0,\n        metadata\n      ]\n\n  @spec get_outputs(Transaction.Payment.t()) :: list(Output.t())\n  def get_outputs(%Transaction.Payment{outputs: outputs}), do: outputs\n\n  @spec get_inputs(Transaction.Payment.t()) :: list(OMG.Watcher.Utxo.Position.t())\n  def get_inputs(%Transaction.Payment{inputs: inputs}), do: inputs\n\n  @doc \"\"\"\n  True if the witnessses provided follow some extra custom validation.\n\n  Currently this covers the requirement for all the inputs to be signed on predetermined positions\n  \"\"\"\n  @spec valid?(Transaction.Payment.t(), Transaction.Signed.t()) :: true | {:error, atom}\n  def valid?(%Transaction.Payment{}, %Transaction.Signed{sigs: sigs} = tx) do\n    tx\n    |> Transaction.get_inputs()\n    |> all_inputs_signed?(sigs)\n  end\n\n  @doc \"\"\"\n  True if a payment can be applied, given a set of input UTXOs is present in the ledger.\n  Involves the checking of balancing of inputs and outputs for currencies\n\n  Returns the fees that this transaction is paying, mapped by currency\n  \"\"\"\n  @spec can_apply?(Transaction.Payment.t(), list(Output.t())) ::\n          {:ok, map()} | {:error, :amounts_do_not_add_up}\n  def can_apply?(%Transaction.Payment{} = tx, outputs_spent) do\n    outputs = Transaction.get_outputs(tx)\n\n    input_amounts_by_currency = get_amounts_by_currency(outputs_spent)\n    output_amounts_by_currency = get_amounts_by_currency(outputs)\n\n    with :ok <- amounts_add_up?(input_amounts_by_currency, output_amounts_by_currency),\n         do: {:ok, fees_paid(input_amounts_by_currency, output_amounts_by_currency)}\n  end\n\n  defp all_inputs_signed?(non_zero_inputs, sigs) do\n    count_non_zero_signatures = Enum.count(sigs, &(&1 != @empty_signature))\n    count_non_zero_inputs = length(non_zero_inputs)\n\n    cond do\n      count_non_zero_signatures > count_non_zero_inputs -> {:error, :superfluous_signature}\n      count_non_zero_signatures < count_non_zero_inputs -> {:error, :missing_signature}\n      true -> true\n    end\n  end\n\n  defp fees_paid(input_amounts_by_currency, output_amounts_by_currency) do\n    Enum.into(\n      input_amounts_by_currency,\n      %{},\n      fn {input_currency, input_amount} ->\n        # fee is implicit - it's the difference between funds owned and spend\n        implicit_paid_fee = input_amount - Map.get(output_amounts_by_currency, input_currency, 0)\n\n        {input_currency, implicit_paid_fee}\n      end\n    )\n  end\n\n  defp get_amounts_by_currency(outputs) do\n    outputs\n    |> Enum.group_by(fn %{currency: currency} -> currency end, fn %{amount: amount} -> amount end)\n    |> Enum.map(fn {currency, amounts} -> {currency, Enum.sum(amounts)} end)\n    |> Map.new()\n  end\n\n  defp amounts_add_up?(input_amounts, output_amounts) do\n    for {output_currency, output_amount} <- Map.to_list(output_amounts) do\n      input_amount = Map.get(input_amounts, output_currency, 0)\n      input_amount >= output_amount\n    end\n    |> Enum.all?()\n    |> if(do: :ok, else: {:error, :amounts_do_not_add_up})\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/state/transaction/recovered.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.State.Transaction.Recovered do\n  @moduledoc \"\"\"\n  Representation of a signed transaction, with addresses recovered from signatures (from `OMG.Watcher.State.Transaction.Signed`)\n  Intent is to allow concurrent processing of signatures outside of serial processing in `OMG.Watcher.State`.\n\n  `Transaction.Recovered` represents a transaction that can be sent to `OMG.Watcher.State.exec/1`\n  \"\"\"\n\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.Utxo\n\n  require Utxo\n\n  @type tx_bytes() :: binary()\n\n  @type recover_tx_error() ::\n          :duplicate_inputs\n          | :malformed_transaction\n          | :malformed_transaction_rlp\n          | :signature_corrupt\n          | :missing_signature\n\n  defstruct [:signed_tx, :tx_hash, :signed_tx_bytes, :witnesses]\n\n  @type t() :: %__MODULE__{\n          tx_hash: Transaction.tx_hash(),\n          witnesses: %{non_neg_integer => Transaction.Witness.t()},\n          signed_tx: Transaction.Signed.t(),\n          signed_tx_bytes: tx_bytes()\n        }\n\n  @doc \"\"\"\n  Transforms a RLP-encoded child chain transaction (binary) into a:\n    - decoded\n    - statelessly valid (mainly inputs logic)\n    - recovered (i.e. signatures get recovered into spenders)\n  transaction\n\n   See docs/transaction_validation.md for more information about stateful and stateless validation.\n  \"\"\"\n  @spec recover_from(binary) :: {:ok, Transaction.Recovered.t()} | {:error, recover_tx_error()}\n  def recover_from(encoded_signed_tx) do\n    with {:ok, signed_tx} <- Transaction.Signed.decode(encoded_signed_tx),\n         true <- valid?(signed_tx),\n         do: recover_from_struct(signed_tx, encoded_signed_tx)\n  end\n\n  @doc \"\"\"\n  Throwing version of `recover_from/1`\n  \"\"\"\n  @spec recover_from!(binary) :: Transaction.Recovered.t()\n  def recover_from!(encoded_signed_tx) do\n    {:ok, recovered} = Transaction.Recovered.recover_from(encoded_signed_tx)\n    recovered\n  end\n\n  @spec recover_from_struct(Transaction.Signed.t(), tx_bytes()) :: {:ok, t()} | {:error, recover_tx_error()}\n  defp recover_from_struct(%Transaction.Signed{} = signed_tx, signed_tx_bytes) do\n    with {:ok, witnesses} <- Transaction.Signed.get_witnesses(signed_tx),\n         do:\n           {:ok,\n            %__MODULE__{\n              tx_hash: Transaction.raw_txhash(signed_tx),\n              witnesses: witnesses,\n              signed_tx: signed_tx,\n              signed_tx_bytes: signed_tx_bytes\n            }}\n  end\n\n  defp valid?(%Transaction.Signed{raw_tx: raw_tx} = tx) do\n    with true <- generic_valid?(tx),\n         true <- Transaction.Protocol.valid?(raw_tx, tx),\n         do: true\n  end\n\n  defp generic_valid?(%Transaction.Signed{raw_tx: raw_tx}) do\n    inputs = Transaction.get_inputs(raw_tx)\n\n    with true <- no_duplicate_inputs?(inputs) || {:error, :duplicate_inputs},\n         do: true\n  end\n\n  defp no_duplicate_inputs?(inputs) do\n    number_of_unique_inputs =\n      inputs\n      |> Enum.uniq()\n      |> Enum.count()\n\n    inputs_length = Enum.count(inputs)\n    inputs_length == number_of_unique_inputs\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/state/transaction/signed.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.State.Transaction.Signed do\n  @moduledoc \"\"\"\n  Representation of a signed transaction.\n\n  NOTE: before you use this, make sure you shouldn't use `Transaction` or `Transaction.Recovered`\n  \"\"\"\n\n  alias OMG.Watcher.Crypto\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.State.Transaction.Witness\n  alias OMG.Watcher.TypedDataHash\n\n  @type tx_bytes() :: binary()\n\n  defstruct [:raw_tx, :sigs]\n\n  @type t() :: %__MODULE__{\n          raw_tx: Transaction.Protocol.t(),\n          sigs: [Crypto.sig_t()]\n        }\n\n  @doc \"\"\"\n  Produce a binary form of a signed transaction - coerces into RLP-encodeable structure and RLP encodes\n  \"\"\"\n  @spec encode(t()) :: tx_bytes()\n  def encode(%__MODULE__{raw_tx: %{} = raw_tx, sigs: sigs}) do\n    ExRLP.encode([sigs | Transaction.Protocol.get_data_for_rlp(raw_tx)])\n  end\n\n  @doc \"\"\"\n  Produces a struct from the binary encoded form of a signed transactions - RLP decodes to structure of RLP-items\n  and then produces an Elixir struct\n  \"\"\"\n  @spec decode(tx_bytes()) :: {:ok, t()} | {:error, atom}\n  def decode(signed_tx_bytes) do\n    with {:ok, tx_rlp_decoded_chunks} <- generic_decode(signed_tx_bytes),\n         do: reconstruct(tx_rlp_decoded_chunks)\n  end\n\n  @doc \"\"\"\n  See `decode/1`\n  \"\"\"\n  @spec decode!(tx_bytes()) :: t()\n  def decode!(signed_tx_bytes) do\n    {:ok, decoded} = decode(signed_tx_bytes)\n    decoded\n  end\n\n  @doc \"\"\"\n  Recovers the witnesses for non-empty signatures, in the order they appear in transaction's signatures\n  \"\"\"\n  @spec get_witnesses(Transaction.Signed.t()) ::\n          {:ok, %{(index :: non_neg_integer) => Crypto.address_t()}} | {:error, atom}\n  def get_witnesses(%Transaction.Signed{sigs: []}), do: {:ok, %{}}\n\n  def get_witnesses(%Transaction.Signed{raw_tx: raw_tx, sigs: raw_witnesses}) do\n    raw_txhash = TypedDataHash.hash_struct(raw_tx)\n\n    with {:ok, reversed_witnesses} <- get_reversed_witnesses(raw_txhash, raw_tx, raw_witnesses),\n         do:\n           {:ok,\n            reversed_witnesses\n            |> Enum.reverse()\n            |> Enum.with_index()\n            |> Enum.into(%{}, fn {witness, idx} -> {idx, witness} end)}\n  end\n\n  defp get_reversed_witnesses(raw_txhash, raw_tx, raw_witnesses) do\n    Enum.reduce_while(raw_witnesses, {:ok, []}, fn raw_witness, acc ->\n      get_witness(raw_txhash, raw_tx, raw_witness, acc)\n    end)\n  end\n\n  defp get_witness(raw_txhash, raw_tx, raw_witness, {:ok, witnesses}) do\n    raw_witness\n    |> Witness.recover(raw_txhash, raw_tx)\n    |> case do\n      {:ok, witness} -> {:cont, {:ok, [witness | witnesses]}}\n      error -> {:halt, error}\n    end\n  end\n\n  defp generic_decode(signed_tx_bytes) do\n    {:ok, ExRLP.decode(signed_tx_bytes)}\n  rescue\n    _ -> {:error, :malformed_transaction_rlp}\n  end\n\n  def reconstruct([raw_witnesses | typed_tx_rlp_decoded_chunks]) do\n    with true <- is_list(raw_witnesses) || {:error, :malformed_witnesses},\n         true <- Enum.all?(raw_witnesses, &Witness.valid?/1) || {:error, :malformed_witnesses},\n         {:ok, raw_tx} <- Transaction.dispatching_reconstruct(typed_tx_rlp_decoded_chunks),\n         do: {:ok, %Transaction.Signed{raw_tx: raw_tx, sigs: raw_witnesses}}\n  end\n\n  def reconstruct(_), do: {:error, :malformed_transaction}\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/state/transaction/validator/fee_claim.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.State.Transaction.Validator.FeeClaim do\n  @moduledoc \"\"\"\n  Contains generic validation rules for `Transaction.Fee` transactions. Specific transaction type's validation\n  is passed to `Transaction.Protocol.can_apply?`\n  \"\"\"\n\n  alias OMG.Watcher.State.Core\n  alias OMG.Watcher.State.Transaction\n\n  @type fee_claim_error :: :surplus_in_token_not_collected | :claimed_and_collected_amounts_mismatch\n\n  @spec can_claim_fees(Core.t(), Transaction.Recovered.t()) ::\n          {:ok, %{}} | {{:error, fee_claim_error()}, Core.t()}\n  def can_claim_fees(\n        %Core{fee_claimer_address: owner, fees_paid: fees_paid} = state,\n        %Transaction.Recovered{signed_tx: %{raw_tx: fee_tx}}\n      ) do\n    # NOTE: Fee claiming transaction does not transfer funds. It spends pseudo-output resultant of fees collection\n    outputs = make_outputs(owner, fees_paid)\n\n    case Transaction.Protocol.can_apply?(fee_tx, outputs) do\n      {:ok, _} -> {:ok, %{}}\n      {:error, _reason} = error -> {error, state}\n    end\n  end\n\n  defp make_outputs(owner, fees_paid) do\n    Enum.map(fees_paid, fn {currency, amount} ->\n      Transaction.Fee.new_output(owner, currency, amount)\n    end)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/state/transaction/validator/payment.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.State.Transaction.Validator.Payment do\n  @moduledoc \"\"\"\n  Provides functions for stateful transaction validation for transaction processing in OMG.Watcher.State.Core.\n  Specific transaction type's validation is passed to `Transaction.Protocol.can_apply?`\n  \"\"\"\n\n  alias OMG.Output\n  alias OMG.Watcher.Fees\n  alias OMG.Watcher.State.Core\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.State.UtxoSet\n  alias OMG.Watcher.Utxo\n\n  require Utxo\n\n  @type can_apply_error ::\n          :amounts_do_not_add_up\n          | :fees_not_covered\n          | :input_utxo_ahead_of_state\n          | :unauthorized_spend\n          | :utxo_not_found\n          | :overpaying_fees\n          | :multiple_potential_currency_fees\n\n  @spec can_apply_tx(state :: Core.t(), tx :: Transaction.Recovered.t(), fees :: Fees.optional_fee_t()) ::\n          {:ok, map()} | {{:error, can_apply_error()}, Core.t()}\n  def can_apply_tx(state, tx, fees) do\n    %Transaction.Recovered{signed_tx: %{raw_tx: raw_tx}, witnesses: witnesses} = tx\n    inputs = Transaction.get_inputs(tx)\n\n    with true <- not state.fee_claiming_started || {:error, :payments_rejected_during_fee_claiming},\n         :ok <- inputs_not_from_future_block?(state, inputs),\n         {:ok, outputs_spent} <- UtxoSet.get_by_inputs(state.utxos, inputs),\n         :ok <- authorized?(outputs_spent, witnesses),\n         {:ok, implicit_paid_fee_by_currency} <- Transaction.Protocol.can_apply?(raw_tx, outputs_spent),\n         :ok <- Fees.check_if_covered(implicit_paid_fee_by_currency, fees) do\n      {:ok, implicit_paid_fee_by_currency}\n    else\n      {:error, _reason} = error -> {error, state}\n    end\n  end\n\n  defp inputs_not_from_future_block?(%Core{height: blknum}, inputs) do\n    no_utxo_from_future_block = Enum.all?(inputs, fn Utxo.position(input_blknum, _, _) -> blknum >= input_blknum end)\n\n    if no_utxo_from_future_block, do: :ok, else: {:error, :input_utxo_ahead_of_state}\n  end\n\n  # Checks the outputs spent by this transaction have been authorized by correct witnesses\n  @spec authorized?(list(Output.t()), list(Transaction.Witness.t())) ::\n          :ok | {:error, :unauthorized_spend}\n  defp authorized?(outputs_spent, witnesses) do\n    outputs_spent\n    |> Enum.with_index()\n    |> Enum.map(fn {output_spent, index} -> can_spend?(output_spent, witnesses[index]) end)\n    |> Enum.all?()\n    |> if(do: :ok, else: {:error, :unauthorized_spend})\n  end\n\n  defp can_spend?(%Output{owner: owner}, witness), do: owner == witness\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/state/transaction/validator.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.State.Transaction.Validator do\n  @moduledoc \"\"\"\n  Dispatches validation flow to payments application or fee claiming modules\n  \"\"\"\n\n  require OMG.Watcher.State.Transaction.Payment\n\n  @maximum_block_size 65_536\n  # NOTE: Last processed transaction could potentially take his room but also generate `max_inputs` fee transactions\n  @safety_margin 1 + OMG.Watcher.State.Transaction.Payment.max_inputs()\n  @available_block_size @maximum_block_size - @safety_margin\n\n  alias OMG.Watcher.Fees\n  alias OMG.Watcher.State.Core\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.State.Transaction.Validator\n\n  @type can_process_tx_error ::\n          :too_many_transactions_in_block | Validator.Payment.can_apply_error() | Validator.FeeClaim.fee_claim_error()\n\n  @doc \"\"\"\n  Checks, whether at a given state of the ledger, a particular transaction can be applied (!) to it,\n  subject to particular fee requirements\n  \"\"\"\n  @spec can_process_tx(state :: Core.t(), tx :: Transaction.Recovered.t(), fees :: Fees.optional_fee_t()) ::\n          {:ok, map()} | {:ok, map()} | {{:error, can_process_tx_error()}, Core.t()}\n  def can_process_tx(%Core{} = state, %Transaction.Recovered{} = tx, fees) do\n    with :ok <- validate_block_size(state), do: dispatch_validation(state, tx, fees)\n  end\n\n  defp validate_block_size(%Core{tx_index: number_of_transactions_in_block, fees_paid: fees_paid} = state) do\n    fee_transactions_count = Enum.count(fees_paid)\n\n    case number_of_transactions_in_block + fee_transactions_count > @available_block_size do\n      true -> {{:error, :too_many_transactions_in_block}, state}\n      false -> :ok\n    end\n  end\n\n  defp dispatch_validation(state, %Transaction.Recovered{signed_tx: %{raw_tx: %Transaction.Payment{}}} = tx, fees) do\n    Validator.Payment.can_apply_tx(state, tx, fees)\n  end\n\n  defp dispatch_validation(\n         state,\n         %Transaction.Recovered{signed_tx: %{raw_tx: %Transaction.Fee{}}} = tx,\n         _fees\n       ) do\n    Validator.FeeClaim.can_claim_fees(state, tx)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/state/transaction/witness.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.State.Transaction.Witness do\n  @moduledoc \"\"\"\n  Code required to validate and recover raw witnesses (e.g. signatures) goes here.\n\n  These should be called by the stateless validation, in order to put load off stateful validation (i.e. sig recovery)\n  \"\"\"\n  alias OMG.Watcher.Crypto\n  @signature_length 65\n\n  @type t :: Crypto.address_t()\n\n  @doc \"\"\"\n  Pre-check done after decoding to quickly assert whether the witness has one of valid forms\n  \"\"\"\n  def valid?(witness) when is_binary(witness), do: signature_length?(witness)\n  def valid?(_), do: false\n\n  @doc \"\"\"\n  Prepares the witness to be quickly used in stateful validation\n  \"\"\"\n  def recover(raw_witness, raw_txhash, _raw_tx) when is_binary(raw_witness),\n    do: Crypto.recover_address(raw_txhash, raw_witness)\n\n  defp signature_length?(sig) when byte_size(sig) == @signature_length, do: true\n  defp signature_length?(_sig), do: false\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/state/transaction.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.State.Transaction do\n  @moduledoc \"\"\"\n  This module contains the public Transaction API to be prefered to access data of different transaction \"flavors\",\n  like `Transaction.Signed` or `Transaction.Recovered`\n  \"\"\"\n\n  alias OMG.Watcher.Crypto\n  alias OMG.Watcher.RawData\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.Utxo\n\n  require Utxo\n\n  @tx_types_modules OMG.Watcher.WireFormatTypes.tx_type_modules()\n  @tx_types Map.keys(@tx_types_modules)\n\n  @type any_flavor_t() :: __MODULE__.Signed.t() | __MODULE__.Recovered.t() | __MODULE__.Protocol.t()\n\n  @type tx_bytes() :: binary()\n  @type tx_hash() :: Crypto.hash_t()\n  @type metadata() :: binary() | nil\n\n  @type decode_error() ::\n          :malformed_transaction_rlp\n          | :malformed_inputs\n          | :malformed_outputs\n          | :malformed_address\n          | :malformed_metadata\n          | :unrecognized_transaction_type\n          | :malformed_transaction\n\n  defmacro is_metadata(metadata) do\n    quote do\n      is_binary(unquote(metadata)) and byte_size(unquote(metadata)) == 32\n    end\n  end\n\n  @type input_index_t() :: 0..3\n\n  def dispatching_reconstruct([raw_type | raw_tx_rlp_decoded_chunks]) when is_binary(raw_type) do\n    case RawData.parse_uint256(raw_type) do\n      {:ok, tx_type} when tx_type in @tx_types ->\n        protocol_module = @tx_types_modules[tx_type]\n        protocol_module.reconstruct([tx_type | raw_tx_rlp_decoded_chunks])\n\n      _ ->\n        {:error, :unrecognized_transaction_type}\n    end\n  end\n\n  def dispatching_reconstruct([_raw_type | _raw_tx_rlp_decoded_chunks]), do: {:error, :unrecognized_transaction_type}\n  def dispatching_reconstruct(_), do: {:error, :malformed_transaction}\n\n  @spec decode(tx_bytes()) :: {:ok, Transaction.Protocol.t()} | {:error, decode_error()}\n  def decode(tx_bytes) do\n    with {:ok, raw_tx_rlp_decoded_chunks} <- try_exrlp_decode(tx_bytes),\n         do: dispatching_reconstruct(raw_tx_rlp_decoded_chunks)\n  end\n\n  def decode!(tx_bytes) do\n    {:ok, tx} = decode(tx_bytes)\n    tx\n  end\n\n  defp try_exrlp_decode(tx_bytes) do\n    {:ok, ExRLP.decode(tx_bytes)}\n  rescue\n    _ -> {:error, :malformed_transaction_rlp}\n  end\n\n  defp encode(transaction) do\n    Transaction.Protocol.get_data_for_rlp(transaction)\n    |> ExRLP.encode()\n  end\n\n  defp hash(tx) do\n    tx\n    |> encode()\n    |> Crypto.hash()\n  end\n\n  @doc \"\"\"\n  Returns all inputs, never returns zero inputs\n  \"\"\"\n  @spec get_inputs(any_flavor_t()) :: [{:utxo_position, non_neg_integer(), non_neg_integer(), non_neg_integer()}]\n  def get_inputs(%__MODULE__.Recovered{signed_tx: signed_tx}), do: get_inputs(signed_tx)\n  def get_inputs(%__MODULE__.Signed{raw_tx: raw_tx}), do: get_inputs(raw_tx)\n  def get_inputs(tx), do: Transaction.Protocol.get_inputs(tx)\n\n  @doc \"\"\"\n  Returns all outputs, never returns zero outputs\n  \"\"\"\n  @spec get_outputs(any_flavor_t()) :: list()\n  def get_outputs(%__MODULE__.Recovered{signed_tx: signed_tx}), do: get_outputs(signed_tx)\n  def get_outputs(%__MODULE__.Signed{raw_tx: raw_tx}), do: get_outputs(raw_tx)\n  def get_outputs(tx), do: Transaction.Protocol.get_outputs(tx)\n\n  @doc \"\"\"\n  Returns the encoded bytes of the raw transaction involved, i.e. without the signatures\n  \"\"\"\n  @spec raw_txbytes(any_flavor_t()) :: binary\n  def raw_txbytes(%__MODULE__.Recovered{signed_tx: signed_tx}), do: raw_txbytes(signed_tx)\n  def raw_txbytes(%__MODULE__.Signed{raw_tx: raw_tx}), do: raw_txbytes(raw_tx)\n  def raw_txbytes(raw_tx), do: encode(raw_tx)\n\n  @doc \"\"\"\n  Returns the hash of the raw transaction involved, i.e. without the signatures\n  \"\"\"\n  @spec raw_txhash(any_flavor_t()) :: tx_hash()\n  def raw_txhash(%__MODULE__.Recovered{tx_hash: hash}), do: hash\n  def raw_txhash(%__MODULE__.Signed{raw_tx: raw_tx}), do: raw_txhash(raw_tx)\n  def raw_txhash(raw_tx), do: hash(raw_tx)\nend\n\ndefprotocol OMG.Watcher.State.Transaction.Protocol do\n  @moduledoc \"\"\"\n  Should be implemented for any type of transaction processed in the system\n  \"\"\"\n\n  alias OMG.Output\n  alias OMG.Watcher.State.Transaction\n\n  @doc \"\"\"\n  Transforms structured data into RLP-structured (encodable) list of fields\n  \"\"\"\n  @spec get_data_for_rlp(t()) :: list(any())\n  def get_data_for_rlp(tx)\n\n  @doc \"\"\"\n  List of input pointers (e.g. of which one implementation is `utxo_pos`) this transaction is intending to spend\n  \"\"\"\n  @spec get_inputs(t()) :: list(OMG.Watcher.Utxo.Position.t())\n  def get_inputs(tx)\n\n  @doc \"\"\"\n  List of outputs this transaction intends to create\n  \"\"\"\n  @spec get_outputs(t()) :: list(Output.t())\n  def get_outputs(tx)\n\n  @doc \"\"\"\n  Custom validation of the transaction with respect to its witnesses. Part of stateless validation routine\n  \"\"\"\n  @spec valid?(t(), Transaction.Signed.t()) :: true | {:error, atom}\n  def valid?(tx, signed_tx)\n\n  @doc \"\"\"\n  Custom stateful validity, based on pre-fetched subset of input UTXOs. Check's if the `tx` can be applied given the\n  `input_utxos` presented, presumably UTXOs in the current state of `OMG.Watcher.State`\n\n  Should also return the fees that this transaction is paying, mapped by currency; for fee validation\n  \"\"\"\n  @spec can_apply?(t(), list(Output.t())) :: {:ok, map()} | {:error, atom}\n  def can_apply?(tx, input_utxos)\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/state/utxo_set.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.State.UtxoSet do\n  @moduledoc \"\"\"\n  Handles all the operations done on the UTXOs held in the ledger.\n  Provides the requested UTXOs by a collection of input pointers.\n  Trades in transaction effects (new utxos, utxos to delete).\n\n  Translates the modifications to itself into DB updates, and is able to interpret the UTXO query result from DB.\n\n  Intended to handle any kind UTXO _subsets_ of the entire UTXO set, relying on that the subset of UTXOs is selected\n  correctly.\n  \"\"\"\n\n  alias OMG.Watcher.Crypto\n\n  alias OMG.Output\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.Utxo\n\n  require Utxo\n\n  @type t() :: %{OMG.Watcher.Utxo.Position.t() => Utxo.t()}\n  @type query_result_t() :: list({OMG.DB.utxo_pos_db_t(), OMG.Watcher.Utxo.t()})\n\n  @spec init(query_result_t()) :: t()\n  def init(utxos_query_result) do\n    utxos_query_result\n    |> Enum.reject(&(&1 == :not_found))\n    |> Enum.into(%{}, fn {db_input_pointer, db_utxo} ->\n      {OMG.Watcher.Utxo.Position.from_db_key(db_input_pointer), Utxo.from_db_value(db_utxo)}\n    end)\n  end\n\n  @doc \"\"\"\n  Provides the outputs that are pointed by `inputs` provided\n  \"\"\"\n  @spec get_by_inputs(t(), list(OMG.Watcher.Utxo.Position.t())) ::\n          {:ok, list(Output.t())} | {:error, :utxo_not_found}\n  def get_by_inputs(utxos, inputs) do\n    with {:ok, utxos_for_inputs} <- get_utxos_by_inputs(utxos, inputs),\n         do: {:ok, utxos_for_inputs |> Enum.reverse() |> Enum.map(fn %Utxo{output: output} -> output end)}\n  end\n\n  @doc \"\"\"\n  Updates itself given a list of spent input pointers and a map of UTXOs created upon a transaction\n  \"\"\"\n  @spec apply_effects(t(), list(OMG.Watcher.Utxo.Position.t()), t()) :: t()\n  def apply_effects(utxos, spent_input_pointers, new_utxos_map) do\n    utxos |> Map.merge(new_utxos_map) |> Map.drop(spent_input_pointers)\n  end\n\n  @doc \"\"\"\n  Returns the DB updates required given a list of spent input pointers and a map of UTXOs created upon a transaction\n  \"\"\"\n  @spec db_updates(list(OMG.Watcher.Utxo.Position.t()), t()) ::\n          list({:put, :utxo, {Utxo.Position.db_t(), Utxo.t()}} | {:delete, :utxo, Utxo.Position.db_t()})\n  def db_updates(spent_input_pointers, new_utxos_map) do\n    db_updates_new_utxos = new_utxos_map |> Enum.map(&utxo_to_db_put/1)\n    db_updates_spent_utxos = spent_input_pointers |> Enum.map(&utxo_to_db_delete/1)\n    Enum.concat(db_updates_new_utxos, db_updates_spent_utxos)\n  end\n\n  @spec exists?(t(), OMG.Watcher.Utxo.Position.t()) :: boolean()\n  def exists?(utxos, input_pointer),\n    do: Map.has_key?(utxos, input_pointer)\n\n  @doc \"\"\"\n  Searches the UTXO set for a particular UTXO created with a `txhash` on `oindex` position.\n\n  Current implementation is **expensive**\n  \"\"\"\n  @spec find_matching_utxo(t(), Transaction.tx_hash(), non_neg_integer()) :: {OMG.Watcher.Utxo.Position.t(), Utxo.t()}\n  def find_matching_utxo(utxos, requested_txhash, oindex) do\n    utxos\n    |> Stream.filter(&utxo_kv_created_by?(&1, requested_txhash))\n    |> Enum.find(&utxo_kv_has_oindex_equal?(&1, oindex))\n  end\n\n  @doc \"\"\"\n  Streams the UTXO key-value pairs found to be owner by a particular address\n  \"\"\"\n  @spec filter_owned_by(t(), Crypto.address_t()) :: Enumerable.t()\n  def filter_owned_by(utxos, address) do\n    Stream.filter(utxos, fn utxo_kv -> utxo_kv_get_owner(utxo_kv) == address end)\n  end\n\n  @doc \"\"\"\n  Turns any enumerable of UTXOs (for example an instance of `OMG.Watcher.State.UtxoSet.t` here) and produces a new enumerable\n  where the UTXO k-v pairs got zipped with UTXO positions coming from the data confined in the UTXO set\n  \"\"\"\n  @spec zip_with_positions(t() | Enumerable.t()) :: Enumerable.t()\n  def zip_with_positions(utxos) do\n    Stream.map(utxos, fn utxo_kv -> {utxo_kv, utxo_kv_get_position(utxo_kv)} end)\n  end\n\n  defp get_utxos_by_inputs(utxos, inputs) do\n    Enum.reduce_while(inputs, {:ok, []}, fn input, acc -> get_utxo(utxos, input, acc) end)\n  end\n\n  defp get_utxo(utxos, position, {:ok, acc}) do\n    case Map.get(utxos, position) do\n      nil -> {:halt, {:error, :utxo_not_found}}\n      found -> {:cont, {:ok, [found | acc]}}\n    end\n  end\n\n  defp utxo_to_db_put({input_pointer, utxo}),\n    do: {:put, :utxo, {Utxo.Position.to_input_db_key(input_pointer), Utxo.to_db_value(utxo)}}\n\n  defp utxo_to_db_delete(input_pointer),\n    do: {:delete, :utxo, Utxo.Position.to_input_db_key(input_pointer)}\n\n  # based on some key-value pair representing {input_pointer, utxo}, get its position from somewhere\n  defp utxo_kv_get_position(utxo_kv)\n  defp utxo_kv_get_position({Utxo.position(_, _, _) = utxo_pos, _utxo}), do: utxo_pos\n  defp utxo_kv_get_position({_non_utxo_pos_input_pointer, %{utxo_pos: Utxo.position(_, _, _) = utxo_pos}}), do: utxo_pos\n\n  # based on some key-value pair representing {input_pointer, utxo}, get its owner\n  defp utxo_kv_get_owner(utxo_kv)\n  defp utxo_kv_get_owner({_input_pointer, %Utxo{output: %{owner: owner}}}), do: owner\n  defp utxo_kv_get_owner({%{owner: owner}, _output_without_owner_specified}), do: owner\n\n  defp utxo_kv_created_by?({_input_pointer, %Utxo{creating_txhash: requested_txhash}}, requested_txhash), do: true\n  defp utxo_kv_created_by?({_input_pointer, %Utxo{}}, _), do: false\n\n  defp utxo_kv_has_oindex_equal?(utxo_kv, oindex) do\n    Utxo.position(_, _, utxo_kv_oindex) = utxo_kv_get_position(utxo_kv)\n    utxo_kv_oindex == oindex\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/state.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.State do\n  @moduledoc \"\"\"\n  A GenServer serving the ledger, for functional core and more info see `OMG.Watcher.State.Core`.\n\n  Keeps the state of the ledger, mainly the spendable UTXO set that can be employed in both `OMG.Watcher.ChildChain` and\n  `OMG.Watcher.Watcher`.\n\n  Maintains the state of the UTXO set by:\n    - recognizing deposits\n    - executing child chain transactions\n    - recognizing exits\n\n  Assumes that all stateless transaction validation is done outside of `exec/2`, so it accepts `OMG.Watcher.State.Transaction.Recovered`\n  \"\"\"\n\n  alias OMG.DB\n\n  alias OMG.Watcher.Fees\n\n  alias OMG.Watcher.State.Core\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.State.Transaction.Validator\n  alias OMG.Watcher.State.UtxoSet\n  alias OMG.Watcher.Utxo\n\n  use GenServer\n  require Logger\n\n  require Utxo\n\n  @type exec_error :: Validator.can_process_tx_error()\n\n  @timeout 10_000\n\n  ### Client\n\n  @doc \"\"\"\n  Starts the `GenServer` maintaining the ledger\n  \"\"\"\n  def start_link(opts) do\n    GenServer.start_link(__MODULE__, opts, name: __MODULE__)\n  end\n\n  @doc \"\"\"\n  Executes a single, statelessly validated child chain transaction. May take information on the fees required, in case\n  fees are charged.\n\n  Checks statefull validity and executes a transaction on `OMG.Watcher.State` when successful. Otherwise, returns an error and has no effect on\n  `OMG.Watcher.State` and the ledger\n  \"\"\"\n  @spec exec(tx :: Transaction.Recovered.t(), fees :: Fees.optional_fee_t()) ::\n          {:ok, {Transaction.tx_hash(), pos_integer, non_neg_integer}}\n          | {:error, exec_error()}\n  def exec(tx, input_fees) do\n    GenServer.call(__MODULE__, {:exec, tx, input_fees}, @timeout)\n  end\n\n  @doc \"\"\"\n  Intended for the `OMG.Watcher.Watcher`. \"Closes\" a block, acknowledging that all transactions have been executed, and the next\n  `exec/2` will belong to the next block.\n\n  Depends on the caller to do persistence.\n\n  Synchronous\n  \"\"\"\n  @spec close_block() :: {:ok, list(Core.db_update())}\n  def close_block() do\n    GenServer.call(__MODULE__, :close_block, @timeout)\n  end\n\n  @doc \"\"\"\n  Recognizes a list of deposits based on Ethereum events.\n\n  Depends on the caller to do persistence.\n  \"\"\"\n  @spec deposit(deposits :: [Core.deposit()]) :: {:ok, list(Core.db_update())}\n  # empty list clause to not block state for a no-op\n  def deposit([]), do: {:ok, []}\n\n  def deposit(deposits) do\n    GenServer.call(__MODULE__, {:deposit, deposits}, @timeout)\n  end\n\n  @doc \"\"\"\n  Recognizes a list of exits based on various triggers. Returns exit validities which indicate which of the UTXO positions\n  actually pointed to UTXOs in the UTXO set of the ledger.\n\n  For a list of things that can be triggers see `OMG.Watcher.State.Core.extract_exiting_utxo_positions/2`.\n\n  Depends on the caller to do persistence.\n  \"\"\"\n  @spec exit_utxos(exiting_utxo_triggers :: Core.exiting_utxo_triggers_t()) ::\n          {:ok, list(Core.db_update()), Core.validities_t()}\n  # empty list clause to not block state for a no-op\n  def exit_utxos([]), do: {:ok, [], {[], []}}\n\n  def exit_utxos(exiting_utxo_triggers) do\n    GenServer.call(__MODULE__, {:exit_utxos, exiting_utxo_triggers}, @timeout)\n  end\n\n  @doc \"\"\"\n  Provides a peek into the UTXO set to check if particular output exist (have not been spent)\n  \"\"\"\n  @spec utxo_exists?(Utxo.Position.t()) :: boolean()\n  def utxo_exists?(utxo) do\n    GenServer.call(__MODULE__, {:utxo_exists, utxo}, @timeout)\n  end\n\n  @doc \"\"\"\n  Returns the current `blknum` and whether at the beginning of a block.\n\n  The beginning of the block is `true/false` depending on whether there have been no transactions executed yet for\n  the current child chain block\n  \"\"\"\n  @spec get_status() :: {non_neg_integer(), boolean()}\n  def get_status() do\n    GenServer.call(__MODULE__, :get_status, @timeout)\n  end\n\n  ### Server\n\n  @doc \"\"\"\n  Initializes the state. UTXO set is not loaded now.\n  \"\"\"\n  def init(opts) do\n    {:ok, child_top_block_number} = DB.get_single_value(:child_top_block_number)\n    child_block_interval = Keyword.fetch!(opts, :child_block_interval)\n    fee_claimer_address = Keyword.fetch!(opts, :fee_claimer_address)\n    metrics_collection_interval = Keyword.fetch!(opts, :metrics_collection_interval)\n\n    {:ok, _data} =\n      result = Core.extract_initial_state(child_top_block_number, child_block_interval, fee_claimer_address)\n\n    _ = Logger.info(\"Started #{inspect(__MODULE__)}, height: #{child_top_block_number}}\")\n\n    {:ok, _} = :timer.send_interval(metrics_collection_interval, self(), :send_metrics)\n\n    result\n  end\n\n  def handle_info(:send_metrics, state) do\n    :ok = :telemetry.execute([:process, __MODULE__], %{}, state)\n    {:noreply, state}\n  end\n\n  @doc \"\"\"\n  see `exec/2`\n  see `deposit/1`\n  see `exit_utxos/1`\n    Flow:\n      - translates the triggers to UTXO positions digestible by the UTXO set\n      - exits the UTXOs from the ledger if they exists, reports invalidity wherever they don't\n      - returns the `db_updates` to be applied by the caller\n  see `utxo_exists/1`\n  see `get_status/0`\n  see `close_block/0`\n    Works exactly like `handle_cast(:form_block)` but:\n    - is synchronous\n    - relies on the caller to handle persistence, instead of handling itself\n\n    Someday, one might want to skip some of computations done (like calculating the root hash, which is scrapped)\n  \"\"\"\n  def handle_call({:exec, tx, fees}, _from, state) do\n    db_utxos =\n      tx\n      |> Transaction.get_inputs()\n      |> fetch_utxos_from_db(state)\n\n    state\n    |> Core.with_utxos(db_utxos)\n    |> Core.exec(tx, fees)\n    |> case do\n      {:ok, tx_result, new_state} ->\n        {:reply, {:ok, tx_result}, new_state}\n\n      {tx_result, new_state} ->\n        {:reply, tx_result, new_state}\n    end\n  end\n\n  def handle_call({:deposit, deposits}, _from, state) do\n    if Code.ensure_loaded?(OMG.WatcherInfo.DB.EthEvent) do\n      :ok = Kernel.apply(OMG.WatcherInfo.DB.EthEvent, :insert_deposits!, [deposits])\n    end\n\n    {:ok, db_updates, new_state} = Core.deposit(deposits, state)\n\n    {:reply, {:ok, db_updates}, new_state}\n  end\n\n  def handle_call({:exit_utxos, exiting_utxo_triggers}, _from, state) do\n    exiting_utxos = Core.extract_exiting_utxo_positions(exiting_utxo_triggers, state)\n\n    db_utxos = fetch_utxos_from_db(exiting_utxos, state)\n    state = Core.with_utxos(state, db_utxos)\n\n    {:ok, {db_updates, validities}, new_state} = Core.exit_utxos(exiting_utxos, state)\n\n    {:reply, {:ok, db_updates, validities}, new_state}\n  end\n\n  def handle_call({:utxo_exists, utxo_pos}, _from, state) do\n    db_utxos = fetch_utxos_from_db([utxo_pos], state)\n    new_state = Core.with_utxos(state, db_utxos)\n\n    {:reply, Core.utxo_exists?(utxo_pos, new_state), new_state}\n  end\n\n  def handle_call(:get_status, _from, state) do\n    {:reply, Core.get_status(state), state}\n  end\n\n  def handle_call(:close_block, _from, state) do\n    {:ok, {block, db_updates}, new_state} = Core.form_block(state)\n\n    :ok = publish_block_to_event_bus(block)\n    {:reply, {:ok, db_updates}, new_state}\n  end\n\n  defp publish_block_to_event_bus(block) do\n    {:child_chain, \"blocks\"}\n    |> OMG.Bus.Event.new(:enqueue_block, block)\n    |> OMG.Bus.direct_local_broadcast()\n  end\n\n  @spec fetch_utxos_from_db(list(OMG.Watcher.Utxo.Position.t()), Core.t()) :: UtxoSet.t()\n  defp fetch_utxos_from_db(utxo_pos_list, state) do\n    utxo_pos_list\n    |> Stream.reject(&Core.utxo_processed?(&1, state))\n    |> Enum.map(&utxo_from_db/1)\n    |> UtxoSet.init()\n  end\n\n  defp utxo_from_db(input_pointer) do\n    # `DB` query can return `:not_found` which is filtered out by following `is_input_pointer?`\n    with {:ok, utxo_kv} <- DB.utxo(Utxo.Position.to_input_db_key(input_pointer)),\n         do: utxo_kv\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/supervisor.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.Supervisor do\n  @moduledoc \"\"\"\n  Starts and supervises child processes for the security-critical watcher. It may start its own child processes\n  or start other supervisors.\n  \"\"\"\n  use Supervisor\n  require Logger\n\n  alias OMG.Eth.RootChain\n  alias OMG.Status.Alert.Alarm\n  alias OMG.Watcher.DatadogEvent.ContractEventConsumer\n  alias OMG.Watcher.Monitor\n  alias OMG.Watcher.SyncSupervisor\n  alias OMG.Watcher.Tracer\n\n  def start_link() do\n    Supervisor.start_link(__MODULE__, :ok, name: __MODULE__)\n  end\n\n  def init(:ok) do\n    {:ok, contract_deployment_height} = RootChain.get_root_deployment_height()\n\n    children = [\n      {Monitor,\n       [\n         Alarm,\n         %{\n           id: SyncSupervisor,\n           start: {SyncSupervisor, :start_link, [[contract_deployment_height: contract_deployment_height]]},\n           restart: :permanent,\n           type: :supervisor\n         }\n       ]}\n    ]\n\n    is_datadog_disabled = is_disabled?()\n\n    rest_children =\n      if is_datadog_disabled do\n        children\n      else\n        create_event_consumer_children() ++ children\n      end\n\n    opts = [strategy: :one_for_one]\n\n    _ = Logger.info(\"Starting #{inspect(__MODULE__)}\")\n    Supervisor.init(rest_children, opts)\n  end\n\n  defp create_event_consumer_children() do\n    topics =\n      Enum.map(\n        [\n          \"blocks\",\n          \"DepositCreated\",\n          \"InFlightExitInputPiggybacked\",\n          \"InFlightExitOutputPiggybacked\",\n          \"BlockSubmitted\",\n          \"ExitFinalized\",\n          \"ExitChallenged\",\n          \"InFlightExitChallenged\",\n          \"InFlightExitChallengeResponded\",\n          \"InFlightExitInputBlocked\",\n          \"InFlightExitOutputBlocked\",\n          \"InFlightExitInputWithdrawn\",\n          \"InFlightExitOutputWithdrawn\",\n          \"InFlightExitStarted\",\n          \"ExitStarted\"\n        ],\n        &{:root_chain, &1}\n      )\n\n    Enum.map(\n      topics,\n      fn topic ->\n        ContractEventConsumer.prepare_child(\n          topic: topic,\n          release: Application.get_env(:omg_watcher, :release),\n          current_version: Application.get_env(:omg_watcher, :current_version),\n          publisher: OMG.Status.Metric.Datadog\n        )\n      end\n    )\n  end\n\n  @spec is_disabled?() :: boolean()\n  defp is_disabled?(), do: Application.get_env(:omg_watcher, Tracer)[:disabled?]\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/sync_supervisor.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.SyncSupervisor do\n  @moduledoc \"\"\"\n  Starts and supervises security-critical watcher's child processes and supervisors related to\n  rootchain synchronisations.\n  \"\"\"\n  use Supervisor\n  require Logger\n\n  alias OMG.Watcher\n  alias OMG.Watcher.API.StatusCache\n  alias OMG.Watcher.API.StatusCache.Storage\n  alias OMG.Watcher.ChildManager\n  alias OMG.Watcher.Configuration\n  alias OMG.Watcher.CoordinatorSetup\n  alias OMG.Watcher.EthereumEventAggregator\n  alias OMG.Watcher.EthereumEventListener\n  alias OMG.Watcher.ExitProcessor\n  alias OMG.Watcher.Monitor\n\n  @events_bucket :events_bucket\n  @status_cache :status_cache\n\n  def status_cache() do\n    @status_cache\n  end\n\n  def events_bucket() do\n    @events_bucket\n  end\n\n  def start_link(args) do\n    Supervisor.start_link(__MODULE__, args, name: __MODULE__)\n  end\n\n  def init(args) do\n    # Assuming the values max_restarts and max_seconds,\n    # then, if more than max_restarts restarts occur within max_seconds seconds,\n    # the supervisor terminates all child processes and then itself.\n    # The termination reason for the supervisor itself in that case will be shutdown.\n    # max_restarts defaults to 3 and max_seconds defaults to 5.\n\n    # We have 16 children, roughly 14 of them have a dependency to the internetz.\n    # The internetz is flaky. We account for that and allow the flakyness to pass\n    # by increasing the restart strategy. But not too much, because if the internetz is\n    # really off, HeightMonitor should catch that in max 8 seconds and raise an alarm.\n    max_restarts = 3\n    max_seconds = 5\n    opts = [strategy: :one_for_one, max_restarts: max_restarts * 10, max_seconds: max_seconds * 2]\n\n    _ = Logger.info(\"Starting #{inspect(__MODULE__)}\")\n    :ok = Storage.ensure_ets_init(status_cache())\n    :ok = ensure_ets_init(events_bucket())\n    Supervisor.init(children(args), opts)\n  end\n\n  defp children(args) do\n    contract_deployment_height = Keyword.fetch!(args, :contract_deployment_height)\n    exit_processor_sla_margin = Configuration.exit_processor_sla_margin()\n    exit_processor_sla_margin_forced = Configuration.exit_processor_sla_margin_forced()\n    metrics_collection_interval = Configuration.metrics_collection_interval()\n    finality_margin = Configuration.exit_finality_margin()\n    deposit_finality_margin = Configuration.deposit_finality_margin()\n    ethereum_events_check_interval_ms = Configuration.ethereum_events_check_interval_ms()\n    coordinator_eth_height_check_interval_ms = Configuration.coordinator_eth_height_check_interval_ms()\n    min_exit_period_seconds = OMG.Eth.Configuration.min_exit_period_seconds()\n    ethereum_block_time_seconds = OMG.Eth.Configuration.ethereum_block_time_seconds()\n    child_block_interval = OMG.Eth.Configuration.child_block_interval()\n    contracts = OMG.Eth.Configuration.contracts()\n\n    [\n      {OMG.Watcher.RootChainCoordinator,\n       CoordinatorSetup.coordinator_setup(\n         metrics_collection_interval,\n         coordinator_eth_height_check_interval_ms,\n         finality_margin,\n         deposit_finality_margin\n       )},\n      {ExitProcessor,\n       [\n         exit_processor_sla_margin: exit_processor_sla_margin,\n         exit_processor_sla_margin_forced: exit_processor_sla_margin_forced,\n         metrics_collection_interval: metrics_collection_interval,\n         min_exit_period_seconds: min_exit_period_seconds,\n         ethereum_block_time_seconds: ethereum_block_time_seconds,\n         child_block_interval: child_block_interval\n       ]},\n      %{\n        id: OMG.Watcher.BlockGetter.Supervisor,\n        start:\n          {OMG.Watcher.BlockGetter.Supervisor, :start_link, [[contract_deployment_height: contract_deployment_height]]},\n        restart: :permanent,\n        type: :supervisor\n      },\n      {EthereumEventAggregator,\n       contracts: contracts,\n       ets_bucket: events_bucket(),\n       events: [\n         [name: :deposit_created, enrich: false],\n         [name: :exit_started, enrich: true],\n         [name: :exit_finalized, enrich: false],\n         [name: :exit_challenged, enrich: false],\n         [name: :in_flight_exit_started, enrich: true],\n         [name: :in_flight_exit_deleted, enrich: false],\n         [name: :in_flight_exit_input_piggybacked, enrich: false],\n         [name: :in_flight_exit_output_piggybacked, enrich: false],\n         [name: :in_flight_exit_challenged, enrich: true],\n         [name: :in_flight_exit_challenge_responded, enrich: false],\n         [name: :in_flight_exit_input_blocked, enrich: false],\n         [name: :in_flight_exit_output_blocked, enrich: false],\n         [name: :in_flight_exit_input_withdrawn, enrich: false],\n         [name: :in_flight_exit_output_withdrawn, enrich: false]\n       ]},\n      EthereumEventListener.prepare_child(\n        metrics_collection_interval: metrics_collection_interval,\n        ethereum_events_check_interval_ms: ethereum_events_check_interval_ms,\n        contract_deployment_height: contract_deployment_height,\n        service_name: :depositor,\n        synced_height_update_key: :last_depositor_eth_height,\n        get_events_callback: &EthereumEventAggregator.deposit_created/2,\n        process_events_callback: &OMG.Watcher.State.deposit/1\n      ),\n      EthereumEventListener.prepare_child(\n        metrics_collection_interval: metrics_collection_interval,\n        ethereum_events_check_interval_ms: ethereum_events_check_interval_ms,\n        contract_deployment_height: contract_deployment_height,\n        service_name: :exit_processor,\n        synced_height_update_key: :last_exit_processor_eth_height,\n        get_events_callback: &EthereumEventAggregator.exit_started/2,\n        process_events_callback: &Watcher.ExitProcessor.new_exits/1\n      ),\n      EthereumEventListener.prepare_child(\n        metrics_collection_interval: metrics_collection_interval,\n        ethereum_events_check_interval_ms: ethereum_events_check_interval_ms,\n        contract_deployment_height: contract_deployment_height,\n        service_name: :exit_finalizer,\n        synced_height_update_key: :last_exit_finalizer_eth_height,\n        get_events_callback: &EthereumEventAggregator.exit_finalized/2,\n        process_events_callback: &Watcher.ExitProcessor.finalize_exits/1\n      ),\n      EthereumEventListener.prepare_child(\n        metrics_collection_interval: metrics_collection_interval,\n        ethereum_events_check_interval_ms: ethereum_events_check_interval_ms,\n        contract_deployment_height: contract_deployment_height,\n        service_name: :exit_challenger,\n        synced_height_update_key: :last_exit_challenger_eth_height,\n        get_events_callback: &EthereumEventAggregator.exit_challenged/2,\n        process_events_callback: &Watcher.ExitProcessor.challenge_exits/1\n      ),\n      EthereumEventListener.prepare_child(\n        metrics_collection_interval: metrics_collection_interval,\n        ethereum_events_check_interval_ms: ethereum_events_check_interval_ms,\n        contract_deployment_height: contract_deployment_height,\n        service_name: :in_flight_exit_processor,\n        synced_height_update_key: :last_in_flight_exit_processor_eth_height,\n        get_events_callback: &EthereumEventAggregator.in_flight_exit_started/2,\n        process_events_callback: &Watcher.ExitProcessor.new_in_flight_exits/1\n      ),\n      EthereumEventListener.prepare_child(\n        metrics_collection_interval: metrics_collection_interval,\n        ethereum_events_check_interval_ms: ethereum_events_check_interval_ms,\n        contract_deployment_height: contract_deployment_height,\n        service_name: :piggyback_processor,\n        synced_height_update_key: :last_piggyback_processor_eth_height,\n        get_events_callback: &EthereumEventAggregator.in_flight_exit_piggybacked/2,\n        process_events_callback: &Watcher.ExitProcessor.piggyback_exits/1\n      ),\n      EthereumEventListener.prepare_child(\n        metrics_collection_interval: metrics_collection_interval,\n        ethereum_events_check_interval_ms: ethereum_events_check_interval_ms,\n        contract_deployment_height: contract_deployment_height,\n        service_name: :competitor_processor,\n        synced_height_update_key: :last_competitor_processor_eth_height,\n        get_events_callback: &EthereumEventAggregator.in_flight_exit_challenged/2,\n        process_events_callback: &Watcher.ExitProcessor.new_ife_challenges/1\n      ),\n      EthereumEventListener.prepare_child(\n        metrics_collection_interval: metrics_collection_interval,\n        ethereum_events_check_interval_ms: ethereum_events_check_interval_ms,\n        contract_deployment_height: contract_deployment_height,\n        service_name: :challenges_responds_processor,\n        synced_height_update_key: :last_challenges_responds_processor_eth_height,\n        get_events_callback: &EthereumEventAggregator.in_flight_exit_challenge_responded/2,\n        process_events_callback: &Watcher.ExitProcessor.respond_to_in_flight_exits_challenges/1\n      ),\n      EthereumEventListener.prepare_child(\n        metrics_collection_interval: metrics_collection_interval,\n        ethereum_events_check_interval_ms: ethereum_events_check_interval_ms,\n        contract_deployment_height: contract_deployment_height,\n        service_name: :piggyback_challenges_processor,\n        synced_height_update_key: :last_piggyback_challenges_processor_eth_height,\n        get_events_callback: &EthereumEventAggregator.in_flight_exit_blocked/2,\n        process_events_callback: &Watcher.ExitProcessor.challenge_piggybacks/1\n      ),\n      EthereumEventListener.prepare_child(\n        metrics_collection_interval: metrics_collection_interval,\n        ethereum_events_check_interval_ms: ethereum_events_check_interval_ms,\n        contract_deployment_height: contract_deployment_height,\n        service_name: :ife_exit_finalizer,\n        synced_height_update_key: :last_ife_exit_finalizer_eth_height,\n        get_events_callback: &EthereumEventAggregator.in_flight_exit_withdrawn/2,\n        process_events_callback: &Watcher.ExitProcessor.finalize_in_flight_exits/1\n      ),\n      EthereumEventListener.prepare_child(\n        metrics_collection_interval: metrics_collection_interval,\n        ethereum_events_check_interval_ms: ethereum_events_check_interval_ms,\n        contract_deployment_height: contract_deployment_height,\n        service_name: :in_flight_exit_deleted_processor,\n        synced_height_update_key: :last_ife_exit_deleted_eth_height,\n        get_events_callback: &EthereumEventAggregator.in_flight_exit_deleted/2,\n        process_events_callback: &Watcher.ExitProcessor.delete_in_flight_exits/1\n      ),\n      {StatusCache, [event_bus: OMG.Bus, ets: status_cache()]},\n      {ChildManager, [monitor: Monitor]}\n    ]\n  end\n\n  defp ensure_ets_init(events_bucket) do\n    case :ets.info(events_bucket) do\n      :undefined ->\n        ^events_bucket = :ets.new(events_bucket, [:bag, :public, :named_table])\n        :ok\n\n      _ ->\n        :ok\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/tracer.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.Tracer do\n  @moduledoc \"\"\"\n  Trace Ecto requests and reports information to Datadog via Spandex\n  \"\"\"\n\n  use Spandex.Tracer, otp_app: :omg_watcher\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/typed_data_hash/config.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.TypedDataHash.Config do\n  @moduledoc \"\"\"\n  Separates computation of EIP-172 domain separator to allow precompute domain separator in TypedDataHash module's\n  attribute, so it doesn't need to compute every time structure data is hashed.\n  \"\"\"\n\n  alias OMG.Eth.Encoding\n  alias OMG.Watcher.TypedDataHash.Tools\n\n  require Logger\n\n  @doc \"\"\"\n  Returns EIP-712 domain based on values from configuration in a format `signTypedData` expects.\n  \"\"\"\n  @spec domain_data_from_config() :: Tools.eip712_domain_t()\n  def domain_data_from_config() do\n    verifying_contract_addr =\n      Application.get_env(:omg_eth, :contract_addr) |> Access.get(:plasma_framework) |> Encoding.from_hex()\n\n    Application.fetch_env!(:omg_watcher, :eip_712_domain)\n    |> Map.new()\n    |> Map.put_new(:verifyingContract, verifying_contract_addr)\n    |> Map.update!(:salt, &Encoding.from_hex/1)\n  end\n\n  @doc \"\"\"\n  Computes default domain separator based on values from configuration.\n  This value is taken to structured hash computation when no domain separator is passed.\n  \"\"\"\n  @spec domain_separator_from_config() :: OMG.Watcher.Crypto.hash_t()\n  def domain_separator_from_config() do\n    Tools.domain_separator(domain_data_from_config())\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/typed_data_hash/tools.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.TypedDataHash.Tools do\n  @moduledoc \"\"\"\n  Implements EIP-712 structural hashing primitives for Transaction type.\n  See also: http://eips.ethereum.org/EIPS/eip-712\n  \"\"\"\n\n  alias OMG.Output\n  alias OMG.Watcher.Crypto\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.TypedDataHash.Types\n  alias OMG.Watcher.Utxo\n\n  require Utxo\n\n  @type eip712_domain_t() :: %{\n          name: binary(),\n          version: binary(),\n          salt: OMG.Watcher.Crypto.hash_t(),\n          verifyingContract: OMG.Watcher.Crypto.address_t()\n        }\n\n  @domain_encoded_type Types.encode_type(:EIP712Domain)\n  @domain_type_hash Crypto.hash(@domain_encoded_type)\n\n  @transaction_encoded_type Types.encode_type(:Transaction)\n  @input_encoded_type Types.encode_type(:Input)\n  @output_encoded_type Types.encode_type(:Output)\n\n  @transaction_type_hash Crypto.hash(@transaction_encoded_type <> @input_encoded_type <> @output_encoded_type)\n  @input_type_hash Crypto.hash(@input_encoded_type)\n  @output_type_hash Crypto.hash(@output_encoded_type)\n\n  @doc \"\"\"\n  Computes Domain Separator `hashStruct(eip712Domain)`,\n  @see: http://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator\n  \"\"\"\n  @spec domain_separator(eip712_domain_t(), Crypto.hash_t()) :: Crypto.hash_t()\n  def domain_separator(\n        %{\n          name: name,\n          version: version,\n          verifyingContract: verifying_contract,\n          salt: salt\n        },\n        domain_type_hash \\\\ @domain_type_hash\n      ) do\n    [\n      domain_type_hash,\n      Crypto.hash(name),\n      Crypto.hash(version),\n      ABI.TypeEncoder.encode_raw([verifying_contract], [:address]),\n      ABI.TypeEncoder.encode_raw([salt], [{:bytes, 32}])\n    ]\n    |> Enum.join()\n    |> Crypto.hash()\n  end\n\n  @spec hash_transaction(\n          non_neg_integer(),\n          list(Utxo.Position.t()),\n          list(Output.t()),\n          Transaction.metadata(),\n          Crypto.hash_t(),\n          Crypto.hash_t()\n        ) :: Crypto.hash_t()\n  def hash_transaction(plasma_framework_tx_type, inputs, outputs, metadata, empty_input_hash, empty_output_hash) do\n    require Transaction.Payment\n\n    raw_encoded_tx_type = ABI.TypeEncoder.encode_raw([plasma_framework_tx_type], [{:uint, 256}])\n\n    input_hashes =\n      inputs\n      |> Stream.map(&hash_input/1)\n      |> Stream.concat(Stream.cycle([empty_input_hash]))\n      |> Enum.take(Transaction.Payment.max_inputs())\n\n    output_hashes =\n      outputs\n      |> Stream.map(&hash_output/1)\n      |> Stream.concat(Stream.cycle([empty_output_hash]))\n      |> Enum.take(Transaction.Payment.max_outputs())\n\n    tx_data = ABI.TypeEncoder.encode_raw([0], [{:uint, 256}])\n    metadata = metadata || <<0::256>>\n\n    [\n      @transaction_type_hash,\n      raw_encoded_tx_type,\n      input_hashes,\n      output_hashes,\n      tx_data,\n      metadata\n    ]\n    |> List.flatten()\n    |> Enum.join()\n    |> Crypto.hash()\n  end\n\n  @spec hash_input(Utxo.Position.t()) :: Crypto.hash_t()\n  def hash_input(Utxo.position(blknum, txindex, oindex)) do\n    [\n      @input_type_hash,\n      ABI.TypeEncoder.encode_raw([blknum], [{:uint, 256}]),\n      ABI.TypeEncoder.encode_raw([txindex], [{:uint, 256}]),\n      ABI.TypeEncoder.encode_raw([oindex], [{:uint, 256}])\n    ]\n    |> Enum.join()\n    |> Crypto.hash()\n  end\n\n  @spec hash_output(Output.t()) :: Crypto.hash_t()\n  def hash_output(%Output{\n        owner: owner,\n        currency: currency,\n        amount: amount,\n        output_type: output_type\n      }) do\n    [\n      @output_type_hash,\n      ABI.TypeEncoder.encode_raw([output_type], [{:uint, 256}]),\n      ABI.TypeEncoder.encode_raw([owner], [{:bytes, 20}]),\n      ABI.TypeEncoder.encode_raw([currency], [:address]),\n      ABI.TypeEncoder.encode_raw([amount], [{:uint, 256}])\n    ]\n    |> Enum.join()\n    |> Crypto.hash()\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/typed_data_hash/types.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.TypedDataHash.Types do\n  @moduledoc \"\"\"\n  Specifies all types needed to produce `eth_signTypedData` request.\n  See: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md#specification-of-the-eth_signtypeddata-json-rpc\n  \"\"\"\n\n  @type typedDataSignRequest_t() :: %{\n          types: map(),\n          primaryType: binary(),\n          domain: map(),\n          message: map()\n        }\n\n  @make_spec &%{name: &1, type: &2}\n\n  @eip_712_domain_spec [\n    @make_spec.(\"name\", \"string\"),\n    @make_spec.(\"version\", \"string\"),\n    @make_spec.(\"verifyingContract\", \"address\"),\n    @make_spec.(\"salt\", \"bytes32\")\n  ]\n\n  @tx_spec Enum.concat([\n             [@make_spec.(\"txType\", \"uint256\")],\n             Enum.map(0..3, fn i -> @make_spec.(\"input\" <> Integer.to_string(i), \"Input\") end),\n             Enum.map(0..3, fn i -> @make_spec.(\"output\" <> Integer.to_string(i), \"Output\") end),\n             [@make_spec.(\"txData\", \"uint256\")],\n             [@make_spec.(\"metadata\", \"bytes32\")]\n           ])\n\n  @input_spec [\n    @make_spec.(\"blknum\", \"uint256\"),\n    @make_spec.(\"txindex\", \"uint256\"),\n    @make_spec.(\"oindex\", \"uint256\")\n  ]\n\n  @output_spec [\n    @make_spec.(\"outputType\", \"uint256\"),\n    @make_spec.(\"outputGuard\", \"bytes20\"),\n    @make_spec.(\"currency\", \"address\"),\n    @make_spec.(\"amount\", \"uint256\")\n  ]\n\n  @types %{\n    EIP712Domain: @eip_712_domain_spec,\n    Transaction: @tx_spec,\n    Input: @input_spec,\n    Output: @output_spec\n  }\n\n  def eip712_types_specification(),\n    do: %{\n      types: @types,\n      primaryType: \"Transaction\"\n    }\n\n  def encode_type(type_name) when is_atom(type_name) do\n    \"#{type_name}(#{\n      @types[type_name]\n      |> Enum.map(fn %{name: name, type: type} -> \"#{type} #{name}\" end)\n      |> Enum.join(\",\")\n    })\"\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/typed_data_hash.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.TypedDataHash do\n  @moduledoc \"\"\"\n  Facilitates veryfing typed structured data (see: http://eips.ethereum.org/EIPS/eip-712) by producing a `hash_struct`\n  for structured transaction data. These `struct_txhash`es are later used as digest to sign and recover signatures.\n  \"\"\"\n\n  alias OMG.Output\n  alias OMG.Watcher.Crypto\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.Utxo\n\n  require Utxo\n\n  @zero_address <<0::160>>\n\n  # Precomputed hash of empty input for performance\n  @empty_input_hash __MODULE__.Tools.hash_input(Utxo.position(0, 0, 0))\n\n  # Precomputed hash of empty output for performance\n  @empty_output_hash __MODULE__.Tools.hash_output(%Output{\n                       owner: @zero_address,\n                       currency: @zero_address,\n                       amount: 0,\n                       output_type: 0\n                     })\n\n  # Prefix and version byte motivated by http://eips.ethereum.org/EIPS/eip-191\n  @eip_191_prefix <<0x19, 0x01>>\n\n  @doc \"\"\"\n  Computes a hash of encoded transaction as defined in EIP-712\n  \"\"\"\n  @spec hash_struct(Transaction.Payment.t(), Crypto.domain_separator_t()) :: Crypto.hash_t()\n  def hash_struct(%Transaction.Payment{} = raw_tx, domain_separator \\\\ nil) do\n    domain_separator = domain_separator || __MODULE__.Config.domain_separator_from_config()\n    Crypto.hash(@eip_191_prefix <> domain_separator <> hash_transaction(raw_tx))\n  end\n\n  @spec hash_transaction(Transaction.Payment.t()) :: Crypto.hash_t()\n  def hash_transaction(%Transaction.Payment{} = raw_tx) do\n    __MODULE__.Tools.hash_transaction(\n      raw_tx.tx_type,\n      Transaction.get_inputs(raw_tx),\n      Transaction.get_outputs(raw_tx),\n      raw_tx.metadata,\n      @empty_input_hash,\n      @empty_output_hash\n    )\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/utxo/position.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.Utxo.Position do\n  @moduledoc \"\"\"\n  Representation of a UTXO position in the child chain, providing encoding/decoding to/from formats digestible in `Eth`\n  and in the `OMG.DB`\n  \"\"\"\n\n  # these two offset constants are driven by the constants from the RootChain.sol contract\n  @input_pointer_output_type 1\n  alias ExPlasma.Output, as: ExPlasmaOutput\n  alias ExPlasma.Output.Position, as: ExPlasmaPosition\n  alias OMG.Watcher.Utxo\n  require Utxo\n\n  @type t() :: {\n          :utxo_position,\n          # blknum\n          non_neg_integer(),\n          # txindex\n          non_neg_integer(),\n          # oindex\n          non_neg_integer()\n        }\n\n  @type db_t() :: {non_neg_integer(), non_neg_integer(), non_neg_integer()}\n\n  @type input_db_key_t() :: {:input_pointer, pos_integer(), db_t()}\n\n  defguardp is_position(blknum, txindex, oindex)\n            when is_integer(blknum) and blknum >= 0 and\n                   is_integer(txindex) and txindex >= 0 and\n                   is_integer(oindex) and oindex >= 0\n\n  @doc \"\"\"\n  Encode an input utxo position into an integer value.\n\n  ## Examples\n\n      iex> utxo_pos = {:utxo_position, 4, 5, 1}\n      iex> OMG.Watcher.Utxo.Position.encode(utxo_pos)\n      4_000_050_001\n  \"\"\"\n  @spec encode(t()) :: pos_integer()\n  def encode(Utxo.position(blknum, txindex, oindex)) when is_position(blknum, txindex, oindex) do\n    ExPlasmaPosition.pos(%{blknum: blknum, txindex: txindex, oindex: oindex})\n  end\n\n  @doc \"\"\"\n  Decode an integer or binary into a utxo position tuple.\n\n  ## Examples\n\n      # Decodes an integer encoded utxo position.\n      iex> OMG.Watcher.Utxo.Position.decode!(4_000_050_001)\n      {:utxo_position, 4, 5, 1}\n\n      # Decode a binary encoded utxo position.\n      iex> encoded_pos = <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 238, 107, 235, 81>>\n      iex> OMG.Watcher.Utxo.Position.decode!(encoded_pos)\n      {:utxo_position, 4, 5, 1}\n  \"\"\"\n  @spec decode!(binary()) :: t()\n  def decode!(encoded) do\n    {:ok, decoded} = decode(encoded)\n    decoded\n  end\n\n  @doc \"\"\"\n  Decode an integer or binary into a utxo position tuple.\n\n  ## Examples\n\n      # Decode an integer encoded utxo position.\n      iex> OMG.Watcher.Utxo.Position.decode(4_000_050_001)\n      {:ok, {:utxo_position, 4, 5, 1}}\n\n      # Returns an error if the value is too low.\n      iex> OMG.Watcher.Utxo.Position.decode(0)\n      {:error, :encoded_utxo_position_too_low}\n\n      iex> OMG.Watcher.Utxo.Position.decode(-1)\n      {:error, :encoded_utxo_position_too_low}\n\n      # Decode a binary encoded utxo position.\n      iex> encoded_pos = <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 238, 107, 235, 81>>\n      iex> OMG.Watcher.Utxo.Position.decode(encoded_pos)\n      {:ok, {:utxo_position, 4, 5, 1}}\n  \"\"\"\n  @spec decode(binary()) :: {:ok, t()} | {:error, :encoded_utxo_position_too_low | {:blknum, :exceeds_maximum}}\n  def decode(encoded) when is_number(encoded) and encoded <= 0, do: {:error, :encoded_utxo_position_too_low}\n  def decode(encoded) when is_integer(encoded) and encoded > 0, do: do_decode(encoded)\n  def decode(encoded) when is_binary(encoded) and byte_size(encoded) == 32, do: do_decode(encoded)\n\n  # TODO(achiurizo)\n  # Refactor to_input_db_key/1 and to_db_key/1. Doing this because\n  # this was merged from a previous module where one code path still wants the 3 item tuple.\n  @doc \"\"\"\n  Convert a utxo position into the input db key tuple.\n\n  ## Examples\n\n      iex> utxo_pos = {:utxo_position, 1, 2, 3}\n      iex> OMG.Watcher.Utxo.Position.to_input_db_key(utxo_pos)\n      {:input_pointer, 1, {1, 2, 3}}\n  \"\"\"\n  @spec to_input_db_key(t()) :: {:input_pointer, unquote(@input_pointer_output_type), db_t()}\n  def to_input_db_key(Utxo.position(blknum, txindex, oindex)) when is_position(blknum, txindex, oindex),\n    do: {:input_pointer, @input_pointer_output_type, {blknum, txindex, oindex}}\n\n  @doc \"\"\"\n  Convert a utxo position into the db key tuple. (legacy?)\n\n  ## Examples\n\n      iex> utxo_pos = {:utxo_position, 1, 2, 3}\n      iex> OMG.Watcher.Utxo.Position.to_db_key(utxo_pos)\n      {1, 2, 3}\n  \"\"\"\n  @spec to_db_key(t()) :: db_t()\n  def to_db_key(Utxo.position(blknum, txindex, oindex)), do: {blknum, txindex, oindex}\n\n  # TODO(achiurizo)\n  # Refactor so we only have one db key type.\n  @doc \"\"\"\n  Convert an input db key tuple into a utxo position.\n\n  ## Examples\n\n      # Convert an input db key tuple into a utxo position.\n      iex> input_db_key = {:input_pointer, 1, {1, 2, 3}}\n      iex> OMG.Watcher.Utxo.Position.from_db_key(input_db_key)\n      {:utxo_position, 1, 2, 3}\n\n      # Convert a 'legacy' db key tuple into a utxo position\n      iex> legacy_input_db_key = {1, 2, 3}\n      iex> OMG.Watcher.Utxo.Position.from_db_key(legacy_input_db_key)\n      {:utxo_position, 1, 2, 3}\n  \"\"\"\n  @spec from_db_key(db_t() | input_db_key_t()) :: t()\n  def from_db_key({:input_pointer, _output_type, db_value}), do: from_db_key(db_value)\n\n  def from_db_key({blknum, txindex, oindex}) when is_position(blknum, txindex, oindex),\n    do: Utxo.position(blknum, txindex, oindex)\n\n  # TODO(achiurizo)\n  # better name for this function, like to_rlp/1.\n  @doc \"\"\"\n  Returns the rlp-encodable data for the given utxo position.\n\n  ## Examples\n\n      iex> utxo_pos = {:utxo_position, 1, 2, 3}\n      iex> OMG.Watcher.Utxo.Position.get_data_for_rlp(utxo_pos)\n      <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n        0, 0, 0, 0, 0, 0, 59, 155, 24, 35>>\n  \"\"\"\n  @spec get_data_for_rlp(t()) :: binary()\n  def get_data_for_rlp(Utxo.position(blknum, txindex, oindex)) do\n    utxo = ExPlasmaPosition.new(blknum, txindex, oindex)\n    ExPlasmaPosition.to_rlp(utxo)\n  end\n\n  defp do_decode(encoded) when is_binary(encoded) do\n    {:ok, %ExPlasmaOutput{output_id: %{blknum: blknum, txindex: txindex, oindex: oindex}}} =\n      ExPlasmaOutput.decode_id(encoded)\n\n    {:ok, Utxo.position(blknum, txindex, oindex)}\n  end\n\n  defp do_decode(encoded) when is_integer(encoded) do\n    {:ok, utxo} = ExPlasmaPosition.to_map(encoded)\n    {:ok, Utxo.position(utxo.blknum, utxo.txindex, utxo.oindex)}\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/utxo.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.Utxo do\n  @moduledoc \"\"\"\n  Manipulates a single unspent transaction output (UTXO) held be the child chain state.\n  \"\"\"\n\n  alias OMG.Output\n  alias OMG.Watcher.State.Transaction\n\n  defstruct [:output, :creating_txhash]\n\n  @type t() :: %__MODULE__{\n          output: Output.t(),\n          creating_txhash: Transaction.tx_hash()\n        }\n\n  @doc \"\"\"\n  Inserts a representation of an UTXO position, usable in guards. See Utxo.Position for handling of these entities\n  \"\"\"\n  defmacro position(blknum, txindex, oindex) do\n    quote do\n      {:utxo_position, unquote(blknum), unquote(txindex), unquote(oindex)}\n    end\n  end\n\n  defguardp is_nil_or_binary(creating_tx_hash) when is_nil(creating_tx_hash) or is_binary(creating_tx_hash)\n\n  # NOTE: we have no migrations, so we handle data compatibility here (make_db_update/1 and from_db_kv/1), OMG-421\n  def to_db_value(%__MODULE__{output: output, creating_txhash: creating_txhash})\n      when is_nil_or_binary(creating_txhash) do\n    %{creating_txhash: creating_txhash}\n    |> Map.put(:output, Output.to_db_value(output))\n  end\n\n  def from_db_value(%{output: output, creating_txhash: creating_txhash})\n      when is_nil_or_binary(creating_txhash) do\n    value = %{\n      output: Output.from_db_value(output),\n      creating_txhash: creating_txhash\n    }\n\n    struct!(__MODULE__, value)\n  end\n\n  # Reading from old db format, only `OMG.Output.FungibleMoreVPToken`\n  def from_db_value(%{owner: owner, currency: currency, amount: amount, creating_txhash: creating_txhash})\n      when is_nil_or_binary(creating_txhash) do\n    output = %{owner: owner, currency: currency, amount: amount}\n\n    value = %{\n      output: Output.from_db_value(output),\n      creating_txhash: creating_txhash\n    }\n\n    struct!(__MODULE__, value)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/utxo_exit/core.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.UtxoExit.Core do\n  @moduledoc \"\"\"\n  Module provides API for compose exit\n  \"\"\"\n\n  alias OMG.Output\n  alias OMG.Watcher.Block\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.Utxo\n  alias OMG.Watcher.Utxo.Position\n\n  require Utxo\n\n  def compose_block_standard_exit(:not_found, _), do: {:error, :utxo_not_found}\n\n  def compose_block_standard_exit(db_block, Utxo.position(blknum, txindex, _) = utxo_pos) do\n    %Block{transactions: sorted_txs_bytes, number: ^blknum} = block = Block.from_db_value(db_block)\n\n    with {:ok, signed_tx_bytes} <- get_tx_by_index(sorted_txs_bytes, txindex),\n         signed_tx = Transaction.Signed.decode!(signed_tx_bytes),\n         :ok <- get_output_by_index(signed_tx, utxo_pos) do\n      {:ok,\n       %{\n         utxo_pos: Position.encode(utxo_pos),\n         txbytes: Transaction.raw_txbytes(signed_tx),\n         proof: Block.inclusion_proof(block, txindex)\n       }}\n    end\n  end\n\n  @spec compose_deposit_standard_exit({:ok, {tuple, map}} | :not_found) ::\n          {:error, :no_deposit_for_given_blknum}\n          | {:ok, %{utxo_pos: non_neg_integer, txbytes: binary, proof: binary}}\n  def compose_deposit_standard_exit({:ok, {db_utxo_pos, db_utxo_value}}) do\n    utxo_pos = Position.from_db_key(db_utxo_pos)\n\n    %Utxo{output: %Output{amount: amount, currency: currency, owner: owner}} = Utxo.from_db_value(db_utxo_value)\n\n    tx = Transaction.Payment.new([], [{owner, currency, amount}])\n    txs = [Transaction.Signed.encode(%Transaction.Signed{raw_tx: tx, sigs: []})]\n\n    {:ok,\n     %{\n       utxo_pos: Position.encode(utxo_pos),\n       txbytes: Transaction.raw_txbytes(tx),\n       proof: Block.inclusion_proof(txs, 0)\n     }}\n  end\n\n  def compose_deposit_standard_exit(:not_found), do: {:error, :no_deposit_for_given_blknum}\n\n  defp get_tx_by_index(sorted_txs, txindex) do\n    sorted_txs\n    |> Enum.at(txindex)\n    |> case do\n      nil -> {:error, :utxo_not_found}\n      found -> {:ok, found}\n    end\n  end\n\n  defp get_output_by_index(tx, Utxo.position(_, _, oindex)) do\n    tx\n    |> Transaction.get_outputs()\n    |> Enum.at(oindex)\n    |> case do\n      nil -> {:error, :utxo_not_found}\n      _found -> :ok\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher/wire_format_types.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.WireFormatTypes do\n  @moduledoc \"\"\"\n  Provides wire format's tx/output type values and mapping to modules which decodes them.\n  \"\"\"\n\n  @type tx_type_to_module_map() :: %{non_neg_integer() => atom()}\n\n  @tx_type_values %{\n    tx_payment_v1: 1,\n    tx_fee_token_claim: 3\n  }\n\n  @tx_type_modules %{\n    1 => OMG.Watcher.State.Transaction.Payment,\n    3 => OMG.Watcher.State.Transaction.Fee\n  }\n\n  @module_tx_types %{\n    OMG.Watcher.State.Transaction.Payment => 1,\n    OMG.Watcher.State.Transaction.Fee => 3\n  }\n\n  @input_pointer_type_values %{\n    input_pointer_utxo_position: 1\n  }\n\n  @output_type_values %{\n    output_payment_v1: 1,\n    output_fee_token_claim: 2\n  }\n\n  @output_type_modules %{\n    1 => OMG.Output,\n    2 => OMG.Output\n  }\n\n  @known_tx_types Map.keys(@tx_type_values)\n  @known_input_pointer_types Map.keys(@input_pointer_type_values)\n  @known_output_types Map.keys(@output_type_values)\n\n  @doc \"\"\"\n  Returns wire format type value of known transaction type\n  \"\"\"\n  @spec tx_type_for(tx_type :: atom()) :: non_neg_integer()\n  def tx_type_for(tx_type) when tx_type in @known_tx_types, do: @tx_type_values[tx_type]\n\n  @doc \"\"\"\n  Returns module atom that is able to decode transaction of given type\n  \"\"\"\n  @spec tx_type_modules() :: tx_type_to_module_map()\n  def tx_type_modules(), do: @tx_type_modules\n\n  @doc \"\"\"\n  Returns the tx type that is associated with the given module\n  \"\"\"\n  @spec module_tx_types() :: %{atom() => non_neg_integer()}\n  def module_tx_types(), do: @module_tx_types\n\n  @doc \"\"\"\n  Returns wire format type value of known input pointer type\n  \"\"\"\n  @spec input_pointer_type_for(input_pointer_type :: atom()) :: non_neg_integer()\n  def input_pointer_type_for(input_pointer_type) when input_pointer_type in @known_input_pointer_types,\n    do: @input_pointer_type_values[input_pointer_type]\n\n  @doc \"\"\"\n  Returns wire format type value of known output type\n  \"\"\"\n  @spec output_type_for(output_type :: atom()) :: non_neg_integer()\n  def output_type_for(output_type) when output_type in @known_output_types, do: @output_type_values[output_type]\n\n  @doc \"\"\"\n  Returns module atom that is able to decode output of given type\n  \"\"\"\n  @spec output_type_modules() :: tx_type_to_module_map()\n  def output_type_modules(), do: @output_type_modules\nend\n"
  },
  {
    "path": "apps/omg_watcher/lib/omg_watcher.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher do\n  @moduledoc \"\"\"\n  Watcher is responsible for syncing and validating the child chain, and providing a secure interface to it.\n\n  For details see [here](README.md) and [here](docs/tesuji_blockchain_design.md)\n  \"\"\"\nend\n"
  },
  {
    "path": "apps/omg_watcher/mix.exs",
    "content": "defmodule OMG.Watcher.MixProject do\n  use Mix.Project\n\n  def project() do\n    [\n      app: :omg_watcher,\n      version: version(),\n      build_path: \"../../_build\",\n      config_path: \"../../config/config.exs\",\n      deps_path: \"../../deps\",\n      lockfile: \"../../mix.lock\",\n      elixir: \"~> 1.8\",\n      elixirc_paths: elixirc_paths(Mix.env()),\n      start_permanent: Mix.env() == :prod,\n      deps: deps(),\n      test_coverage: [tool: ExCoveralls]\n    ]\n  end\n\n  # Run \"mix help compile.app\" to learn about applications.\n  def application() do\n    [\n      mod: {OMG.Watcher.Application, []},\n      start_phases: [{:attach_telemetry, []}],\n      extra_applications: [:logger, :runtime_tools, :telemetry, :phoenix, :poison]\n    ]\n  end\n\n  defp version() do\n    \"git\"\n    |> System.cmd([\"describe\", \"--tags\", \"--abbrev=0\"])\n    |> elem(0)\n    |> String.replace(\"v\", \"\")\n    |> String.replace(\"\\n\", \"\")\n  end\n\n  # Specifies which paths to compile per environment.\n  defp elixirc_paths(:prod), do: [\"lib\"]\n  defp elixirc_paths(:dev), do: [\"lib\"]\n  defp elixirc_paths(:test), do: [\"lib\", \"test/support\"]\n\n  defp deps() do\n    [\n      {:ex_plasma, \"~> 0.2.0\"},\n      {:ex_rlp, \"~> 0.5.3\"},\n      {:merkle_tree, \"~> 2.0.0\"},\n      {:telemetry, \"~> 0.4.1\"},\n      # there's no apparent reason why libsecp256k1, spandex need to be included as dependencies\n      # to this umbrella application apart from mix ecto.gen.migration not working, so here they are, copied from\n      # the parent (main) mix.exs\n      {:spandex, \"~> 3.0.2\"},\n\n      # UMBRELLA\n      {:omg_bus, in_umbrella: true},\n      {:omg_status, in_umbrella: true},\n      {:omg_db, in_umbrella: true},\n      {:omg_eth, in_umbrella: true},\n      {:omg_utils, in_umbrella: true},\n\n      # TEST ONLY\n      # here only to leverage common test helpers and code\n      {:fake_server, \"~> 2.1\", only: [:dev, :test], runtime: false},\n      {:briefly, \"~> 0.3.0\", only: [:dev, :test]}\n    ]\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/fixtures.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.Fixtures do\n  use ExUnitFixtures.FixtureModule\n\n  use OMG.DB.Fixtures\n  use OMG.Eth.Fixtures\n\n  alias OMG.Eth.Configuration\n  alias OMG.Status.Alert.Alarm\n  alias OMG.Watcher.State.Core\n\n  import OMG.Watcher.TestHelper\n\n  @eth <<0::160>>\n  @fee_claimer_address \"NO FEE CLAIMER ADDR!\"\n\n  deffixture(entities, do: entities())\n\n  deffixture(alice(entities), do: entities.alice)\n  deffixture(bob(entities), do: entities.bob)\n  deffixture(carol(entities), do: entities.carol)\n\n  deffixture(stable_alice(entities), do: entities.stable_alice)\n  deffixture(stable_bob(entities), do: entities.stable_bob)\n  deffixture(stable_mallory(entities), do: entities.stable_mallory)\n\n  deffixture state_empty() do\n    child_block_interval = Configuration.child_block_interval()\n    {:ok, state} = Core.extract_initial_state(0, child_block_interval, @fee_claimer_address)\n    state\n  end\n\n  deffixture state_alice_deposit(state_empty, alice) do\n    do_deposit(state_empty, alice, %{amount: 10, currency: @eth, blknum: 1})\n  end\n\n  deffixture state_stable_alice_deposit(state_empty, stable_alice) do\n    do_deposit(state_empty, stable_alice, %{amount: 10, currency: @eth, blknum: 1})\n  end\n\n  deffixture in_beam_watcher(db_initialized, contract) do\n    :ok = db_initialized\n    _ = contract\n\n    case System.get_env(\"DOCKER_GETH\") do\n      nil ->\n        :ok\n\n      _ ->\n        # have to hack my way out of this so that we can migrate the watcher integration tests out\n        Application.put_env(:omg_watcher, :exit_processor_sla_margin, 40)\n    end\n\n    {:ok, started_apps} = Application.ensure_all_started(:omg_db)\n    {:ok, started_security_watcher} = Application.ensure_all_started(:omg_watcher)\n    {:ok, started_watcher_api} = Application.ensure_all_started(:omg_watcher_rpc)\n    wait_for_web()\n\n    on_exit(fn ->\n      Application.put_env(:omg_db, :path, nil)\n\n      (started_apps ++ started_security_watcher ++ started_watcher_api)\n      |> Enum.reverse()\n      |> Enum.map(fn app ->\n        :ok = Application.stop(app)\n      end)\n\n      Process.sleep(5_000)\n    end)\n  end\n\n  deffixture test_server do\n    server_id = :watcher_test_server\n    {:ok, pid} = FakeServer.start(server_id)\n\n    real_addr = Application.fetch_env!(:omg_watcher, :child_chain_url)\n    old_client_env = Application.fetch_env!(:omg_watcher, :child_chain_url)\n    {:ok, port} = FakeServer.port(server_id)\n    fake_addr = \"http://localhost:#{port}\"\n\n    on_exit(fn ->\n      Application.put_env(:omg_watcher, :child_chain_url, old_client_env)\n\n      FakeServer.stop(server_id)\n    end)\n\n    %{\n      real_addr: real_addr,\n      fake_addr: fake_addr,\n      server_id: server_id,\n      server_pid: pid\n    }\n  end\n\n  defp wait_for_web(), do: wait_for_web(100)\n\n  defp wait_for_web(counter) do\n    case Keyword.has_key?(Alarm.all(), elem(Alarm.main_supervisor_halted(__MODULE__), 0)) do\n      true ->\n        Process.sleep(100)\n        wait_for_web(counter - 1)\n\n      false ->\n        :ok\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/api/account_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.API.AccountTest do\n  use ExUnit.Case, async: false\n\n  alias OMG.Watcher.API.Account\n  alias OMG.Watcher.TestHelper\n  alias OMG.Watcher.Utxo\n\n  require Utxo\n\n  @eth <<0::160>>\n  @payment_output_type OMG.Watcher.WireFormatTypes.output_type_for(:output_payment_v1)\n\n  setup do\n    db_path = Briefly.create!(directory: true)\n    Application.put_env(:omg_db, :path, db_path, persistent: true)\n\n    :ok = OMG.DB.init(db_path)\n\n    {:ok, started_apps} = Application.ensure_all_started(:omg_db)\n\n    on_exit(fn ->\n      Application.put_env(:omg_db, :path, nil)\n\n      started_apps\n      |> Enum.reverse()\n      |> Enum.map(fn app -> :ok = Application.stop(app) end)\n    end)\n\n    :ok\n  end\n\n  describe \"get_exitable_utxos/1\" do\n    test \"returns an empty list if the address does not have any utxo\" do\n      alice = TestHelper.generate_entity()\n      assert Account.get_exitable_utxos(alice.addr) == []\n    end\n\n    test \"returns utxos for the given address\" do\n      alice = TestHelper.generate_entity()\n      bob = TestHelper.generate_entity()\n\n      blknum = 1927\n      txindex = 78\n      oindex = 1\n\n      _ =\n        OMG.DB.multi_update([\n          {:put, :utxo,\n           {\n             {blknum, txindex, oindex},\n             %{\n               output: %{amount: 333, currency: @eth, owner: alice.addr, output_type: @payment_output_type},\n               creating_txhash: nil\n             }\n           }},\n          {:put, :utxo,\n           {\n             {blknum, txindex, oindex + 1},\n             %{\n               output: %{amount: 999, currency: @eth, owner: bob.addr, output_type: @payment_output_type},\n               creating_txhash: nil\n             }\n           }}\n        ])\n\n      [utxo] = Account.get_exitable_utxos(alice.addr)\n\n      assert %{blknum: ^blknum, txindex: ^txindex, oindex: ^oindex} = utxo\n    end\n\n    test \"does not return exiting utxos\" do\n      alice = TestHelper.generate_entity()\n\n      amount = 333\n      blknum = 1927\n      txindex = 78\n      oindex = 1\n      utxo_position = Utxo.position(blknum, txindex, oindex)\n\n      _ =\n        OMG.DB.multi_update([\n          {:put, :utxo,\n           {\n             {blknum, txindex, oindex},\n             %{\n               output: %{amount: amount, currency: @eth, owner: alice.addr, output_type: @payment_output_type},\n               creating_txhash: nil\n             }\n           }},\n          {:put, :exit_info,\n           {\n             Utxo.Position.to_db_key(utxo_position),\n             %{\n               amount: amount,\n               currency: @eth,\n               owner: alice.addr,\n               is_active: true,\n               exit_id: 1,\n               exiting_txbytes: <<0>>,\n               eth_height: 1,\n               root_chain_txhash: nil\n             }\n           }}\n        ])\n\n      assert [] == Account.get_exitable_utxos(alice.addr)\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/api/alarm_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.API.AlarmTest do\n  use ExUnit.Case, async: false\n\n  alias OMG.Watcher.API.Alarm\n\n  setup %{} do\n    {:ok, apps} = Application.ensure_all_started(:omg_status)\n    system_alarm = {:system_memory_high_watermark, []}\n    system_disk_alarm = {{:disk_almost_full, \"/dev/null\"}, []}\n    app_alarm = {:ethereum_connection_error, %{node: Node.self(), reporter: Reporter}}\n\n    on_exit(fn ->\n      apps |> Enum.reverse() |> Enum.each(&Application.stop/1)\n    end)\n\n    %{system_alarm: system_alarm, system_disk_alarm: system_disk_alarm, app_alarm: app_alarm}\n  end\n\n  test \"if alarms are returned when there are no alarms raised\", _ do\n    _ = OMG.Status.Alert.Alarm.clear_all()\n    {:ok, []} = Alarm.get_alarms()\n  end\n\n  test \"if alarms are returned when there are alarms raised\", %{\n    system_alarm: system_alarm,\n    system_disk_alarm: system_disk_alarm,\n    app_alarm: app_alarm\n  } do\n    :ok = :alarm_handler.set_alarm(system_alarm)\n    :ok = :alarm_handler.set_alarm(app_alarm)\n    :ok = :alarm_handler.set_alarm(system_disk_alarm)\n\n    find_alarms = [\n      {{:disk_almost_full, \"/dev/null\"}, []},\n      {:ethereum_connection_error, %{node: Node.self(), reporter: Reporter}},\n      {:system_memory_high_watermark, []}\n    ]\n\n    {:ok, alarms} = Alarm.get_alarms()\n\n    ^find_alarms =\n      Enum.filter(\n        alarms,\n        fn alarm ->\n          Enum.member?(find_alarms, alarm)\n        end\n      )\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/api/status_cache_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.API.StatusCacheTest do\n  use ExUnit.Case, async: true\n\n  alias __MODULE__.BusMock\n  alias __MODULE__.IntegrationModuleMock\n  alias OMG.Watcher.API.StatusCache\n  alias OMG.Watcher.API.StatusCache.Storage\n  alias OMG.Watcher.SyncSupervisor\n\n  setup do\n    :ok = Storage.ensure_ets_init(SyncSupervisor.status_cache())\n    :ok\n  end\n\n  describe \"get/0\" do\n    test \"read from set ets\" do\n      :ets.insert(SyncSupervisor.status_cache(), {:status, :yolo})\n      :yolo = StatusCache.get()\n      :ets.delete(SyncSupervisor.status_cache(), :status)\n    end\n  end\n\n  describe \"start_link/1\" do\n    test \"process stands up and inserts data on block message\" do\n      {:ok, pid} =\n        StatusCache.start_link(\n          ets: SyncSupervisor.status_cache(),\n          event_bus: BusMock,\n          integration_module: IntegrationModuleMock\n        )\n\n      assert StatusCache.get() == %{41 => 42}\n      :erlang.trace(pid, true, [:receive])\n      Kernel.send(pid, {:internal_event_bus, :ethereum_new_height, 43})\n      assert_receive {:trace, _, :receive, {:internal_event_bus, :ethereum_new_height, 43}}\n      :erlang.trace(pid, false, [:receive])\n      assert StatusCache.get() == %{43 => 42}\n    end\n  end\n\n  defmodule IntegrationModuleMock do\n    def get_status(eth_block_number) do\n      {:ok, %{eth_block_number => 42}}\n    end\n\n    def get_ethereum_height() do\n      {:ok, 42 - 1}\n    end\n  end\n\n  defmodule BusMock do\n    def subscribe(_, _) do\n      :ok\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/block_getter/core_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.BlockGetter.CoreTest do\n  use ExUnitFixtures\n  use ExUnit.Case, async: true\n  use OMG.Watcher.Fixtures\n  use Plug.Test\n\n  alias OMG.Watcher.Block\n  alias OMG.Watcher.BlockGetter.BlockApplication\n  alias OMG.Watcher.BlockGetter.Core\n  alias OMG.Watcher.Event\n\n  @eth <<0::160>>\n\n  def assert_check(result, status, value) do\n    assert {^status, new_state, ^value} = result\n    new_state\n  end\n\n  def assert_check(result, value) do\n    assert {new_state, ^value} = result\n    new_state\n  end\n\n  defp handle_downloaded_block(state, {:ok, block}, error, events) do\n    assert {^error, %{events: ^events}} = new_state = Core.handle_downloaded_block(state, {:ok, block})\n    new_state\n  end\n\n  defp handle_downloaded_block(state, {:ok, block}) do\n    assert {:ok, new_state} = Core.handle_downloaded_block(state, {:ok, block})\n    new_state\n  end\n\n  defp handle_downloaded_block(state, block) do\n    assert {:ok, new_state} = Core.handle_downloaded_block(state, {:ok, block})\n    new_state\n  end\n\n  test \"get numbers of blocks to download\" do\n    init_state(init_opts: [maximum_number_of_pending_blocks: 4])\n    |> Core.get_numbers_of_blocks_to_download(20_000)\n    |> assert_check([1_000, 2_000, 3_000, 4_000])\n    |> handle_downloaded_block(%BlockApplication{number: 4_000})\n    |> handle_downloaded_block(%BlockApplication{number: 2_000})\n    |> Core.get_numbers_of_blocks_to_download(20_000)\n    |> assert_check([5_000, 6_000])\n  end\n\n  test \"first block to download number is not zero\" do\n    init_state(start_block_number: 7_000, interval: 100, init_opts: [maximum_number_of_pending_blocks: 4])\n    |> Core.get_numbers_of_blocks_to_download(20_000)\n    |> assert_check([7_100, 7_200, 7_300, 7_400])\n    |> handle_downloaded_block(%BlockApplication{number: 7_200})\n    |> handle_downloaded_block({:ok, %BlockApplication{number: 7_100}})\n  end\n\n  test \"does not download same blocks twice and respects increasing next block number\" do\n    init_state(init_opts: [maximum_number_of_pending_blocks: 5])\n    |> Core.get_numbers_of_blocks_to_download(4_000)\n    |> assert_check([1_000, 2_000, 3_000])\n    |> Core.get_numbers_of_blocks_to_download(2_000)\n    |> assert_check([])\n    |> Core.get_numbers_of_blocks_to_download(8_000)\n    |> assert_check([4_000, 5_000])\n  end\n\n  test \"downloaded duplicated and unexpected block\" do\n    state =\n      init_state(init_opts: [maximum_number_of_pending_blocks: 5])\n      |> Core.get_numbers_of_blocks_to_download(3_000)\n      |> assert_check([1_000, 2_000])\n\n    assert {{:error, :duplicate}, state} =\n             state\n             |> handle_downloaded_block(%BlockApplication{number: 2_000})\n             |> Core.handle_downloaded_block({:ok, %BlockApplication{number: 2_000}})\n\n    assert {{:error, :unexpected_block}, _} =\n             state |> Core.handle_downloaded_block({:ok, %BlockApplication{number: 3_000}})\n  end\n\n  @tag fixtures: [:alice, :bob, :state_alice_deposit]\n  test \"decodes block and validates transaction execution\", %{\n    alice: alice,\n    bob: bob,\n    state_alice_deposit: state_alice_deposit\n  } do\n    block =\n      [OMG.Watcher.TestHelper.create_recovered([{1, 0, 0, alice}], @eth, [{bob, 7}, {alice, 3}])]\n      |> Block.hashed_txs_at(26_000)\n\n    state = process_single_block(block)\n    synced_height = 2\n\n    assert {[\n              %BlockApplication{\n                transactions: [tx],\n                eth_height: ^synced_height,\n                eth_height_done: true\n              }\n            ], _, _,\n            _} = Core.get_blocks_to_apply(state, [%{blknum: block.number, eth_height: synced_height}], synced_height)\n\n    # check feasibility of transactions from block to consume at the OMG.State\n    assert {:ok, tx_result, _} = OMG.Watcher.State.Core.exec(state_alice_deposit, tx, :ignore_fees)\n\n    assert {:ok, ^state} = Core.validate_executions([{:ok, tx_result}], block, state)\n\n    assert {:ok, []} = Core.chain_ok(state)\n  end\n\n  @tag fixtures: [:alice, :bob]\n  test \"decodes and executes tx with different currencies, always with no fee required\", %{alice: alice, bob: bob} do\n    other_currency = <<1::160>>\n\n    block =\n      [\n        OMG.Watcher.TestHelper.create_recovered([{1, 0, 0, alice}], other_currency, [{bob, 7}, {alice, 3}]),\n        OMG.Watcher.TestHelper.create_recovered([{2, 0, 0, alice}], @eth, [{bob, 7}, {alice, 3}])\n      ]\n      |> Block.hashed_txs_at(26_000)\n\n    state = process_single_block(block)\n\n    synced_height = 2\n\n    assert {[%BlockApplication{transactions: [_tx1, _tx2]}], _, _, _} =\n             Core.get_blocks_to_apply(state, [%{blknum: block.number, eth_height: synced_height}], synced_height)\n  end\n\n  defp process_single_block(%Block{hash: requested_hash} = block) do\n    block_height = 25_000\n    interval = 1_000\n\n    {state, _} =\n      init_state(start_block_number: block_height, interval: interval)\n      |> Core.get_numbers_of_blocks_to_download(block_height + 2 * interval)\n\n    assert {:ok, decoded_block} =\n             Core.validate_download_response({:ok, block}, requested_hash, block_height + interval, 0, 0)\n\n    handle_downloaded_block(state, decoded_block)\n  end\n\n  @tag fixtures: [:alice]\n  test \"does not validate block with invalid hash\", %{alice: alice} do\n    matching_bad_returned_hash = <<12::256>>\n    state = init_state()\n\n    block = %Block{\n      Block.hashed_txs_at([OMG.Watcher.TestHelper.create_recovered([{1_000, 20, 0, alice}], @eth, [{alice, 100}])], 1)\n      | hash: matching_bad_returned_hash\n    }\n\n    assert {:error, {:incorrect_hash, matching_bad_returned_hash, 1}} ==\n             Core.validate_download_response({:ok, block}, matching_bad_returned_hash, 1, 0, 0)\n\n    events = [%Event.InvalidBlock{error_type: :incorrect_hash, hash: matching_bad_returned_hash, blknum: 1}]\n\n    assert {{:error, :incorrect_hash}, %{events: ^events}} =\n             Core.handle_downloaded_block(state, {:error, {:incorrect_hash, matching_bad_returned_hash, 1}})\n  end\n\n  @tag fixtures: [:alice]\n  test \"check error returned by decoding, one of Transaction.Recovered.recover_from checks\", %{alice: alice} do\n    # NOTE: this test only test if Transaction.Recovered.recover_from-specific checks are run and errors returned\n    #       the more extensive testing of such checks is done in API.CoreTest where it belongs\n\n    %Block{hash: hash} =\n      block =\n      [OMG.Watcher.TestHelper.create_recovered([{1_000, 20, 0, alice}], @eth, [{alice, 100}])]\n      |> Block.hashed_txs_at(1)\n\n    block = %{block | transactions: block.transactions ++ [<<34>>]}\n\n    # a particular Transaction.Recovered.recover_from error instance\n    assert {:error, {:malformed_transaction, hash, 1}} == Core.validate_download_response({:ok, block}, hash, 1, 0, 0)\n  end\n\n  test \"check error returned by decode_block, hash mismatch checks\" do\n    hash = <<12::256>>\n    block = Block.hashed_txs_at([], 1)\n\n    assert {:error, {:bad_returned_hash, hash, 1}} == Core.validate_download_response({:ok, block}, hash, 1, 0, 0)\n  end\n\n  test \"the blknum is checked against the requested one\" do\n    %Block{hash: hash} = block = Block.hashed_txs_at([], 1)\n    assert {:error, {:bad_returned_number, ^hash, 2}} = Core.validate_download_response({:ok, block}, hash, 2, 0, 0)\n  end\n\n  test \"handle_downloaded_block function called once with PotentialWithholdingReport doesn't return BlockWithholding event, and get_numbers_of_blocks_to_download function returns this block\" do\n    {:ok, %Core.PotentialWithholdingReport{}} =\n      potential_withholding = Core.validate_download_response({:error, :error_reason}, <<>>, 2_000, 0, 0)\n\n    init_state()\n    |> Core.get_numbers_of_blocks_to_download(3_000)\n    |> assert_check([1_000, 2_000])\n    |> handle_downloaded_block(potential_withholding)\n    |> Core.get_numbers_of_blocks_to_download(3_000)\n    |> assert_check([2_000])\n  end\n\n  test \"handle_downloaded_block function called twice with PotentialWithholdingReport returns BlockWithholding event\" do\n    requested_hash = <<1>>\n\n    init_state(init_opts: [maximum_number_of_pending_blocks: 5, maximum_block_withholding_time_ms: 0])\n    |> Core.get_numbers_of_blocks_to_download(3_000)\n    |> assert_check([1_000, 2_000])\n    |> handle_downloaded_block(Core.validate_download_response({:error, :error_reason}, requested_hash, 2_000, 0, 0))\n    |> handle_downloaded_block(\n      Core.validate_download_response({:error, :error_reason}, requested_hash, 2_000, 0, 1),\n      {:error, :withholding},\n      [%Event.BlockWithholding{blknum: 2000, hash: requested_hash}]\n    )\n  end\n\n  test \"get_numbers_of_blocks_to_download function returns number of potential withholding block which then is canceled\" do\n    init_state(init_opts: [maximum_number_of_pending_blocks: 4, maximum_block_withholding_time_ms: 0])\n    |> Core.get_numbers_of_blocks_to_download(20_000)\n    |> assert_check([1_000, 2_000, 3_000, 4_000])\n    |> handle_downloaded_block(%BlockApplication{number: 1_000})\n    |> handle_downloaded_block(%BlockApplication{number: 2_000})\n    |> handle_downloaded_block(Core.validate_download_response({:error, :error_reason}, <<>>, 3_000, 0, 0))\n    |> Core.get_numbers_of_blocks_to_download(5_000)\n    |> assert_check([3_000])\n    |> handle_downloaded_block(%BlockApplication{number: 3_000})\n    |> Core.get_numbers_of_blocks_to_download(20_000)\n    |> assert_check([5_000, 6_000, 7_000])\n  end\n\n  test \"get_numbers_of_blocks_to_download does not return blocks that are being downloaded\" do\n    init_state(init_opts: [maximum_number_of_pending_blocks: 4, maximum_block_withholding_time_ms: 0])\n    |> Core.get_numbers_of_blocks_to_download(20_000)\n    |> assert_check([1_000, 2_000, 3_000, 4_000])\n    |> handle_downloaded_block(%BlockApplication{number: 1_000})\n    |> handle_downloaded_block(%BlockApplication{number: 2_000})\n    |> handle_downloaded_block(Core.validate_download_response({:error, :error_reason}, <<>>, 3_000, 0, 0))\n    |> Core.get_numbers_of_blocks_to_download(20_000)\n    |> assert_check([3_000, 5_000, 6_000])\n    |> handle_downloaded_block(%BlockApplication{number: 5_000})\n    |> Core.get_numbers_of_blocks_to_download(20_000)\n    |> assert_check([7_000])\n  end\n\n  test \"get_numbers_of_blocks_to_download function doesn't return next blocks if state doesn't have empty slots left\" do\n    init_state(init_opts: [maximum_number_of_pending_blocks: 3])\n    |> Core.get_numbers_of_blocks_to_download(20_000)\n    |> assert_check([1_000, 2_000, 3_000])\n    |> handle_downloaded_block(Core.validate_download_response({:error, :error_reason}, <<>>, 1_000, 0, 0))\n    |> handle_downloaded_block(Core.validate_download_response({:error, :error_reason}, <<>>, 2_000, 0, 0))\n    |> handle_downloaded_block(Core.validate_download_response({:error, :error_reason}, <<>>, 3_000, 0, 0))\n    |> Core.get_numbers_of_blocks_to_download(20_000)\n    |> assert_check([1_000, 2_000, 3_000])\n  end\n\n  test \"handle_downloaded_block function after maximum_block_withholding_time_ms returns BlockWithholding event\" do\n    requested_hash = <<1>>\n\n    init_state(init_opts: [maximum_number_of_pending_blocks: 4, maximum_block_withholding_time_ms: 1000])\n    |> handle_downloaded_block(Core.validate_download_response({:error, :error_reason}, requested_hash, 3_000, 0, 0))\n    |> handle_downloaded_block(Core.validate_download_response({:error, :error_reason}, requested_hash, 3_000, 0, 500))\n    |> handle_downloaded_block(\n      Core.validate_download_response({:error, :error_reason}, requested_hash, 3_000, 0, 1000),\n      {:error, :withholding},\n      [%Event.BlockWithholding{blknum: 3_000, hash: requested_hash}]\n    )\n  end\n\n  test \"allows progressing when no unchallenged exits are detected\" do\n    assert {:ok, []} = init_state() |> Core.consider_exits({:ok, []}) |> Core.chain_ok()\n    assert {:ok, []} = init_state() |> Core.consider_exits({:ok, [%Event.InvalidExit{}]}) |> Core.chain_ok()\n  end\n\n  @tag :capture_log\n  test \"prevents progressing when unchallenged_exit is detected\" do\n    assert {:error, []} = init_state() |> Core.consider_exits({{:error, :unchallenged_exit}, []}) |> Core.chain_ok()\n  end\n\n  @tag :capture_log\n  test \"prevents applying when started with an unchallenged_exit\" do\n    state = init_state(exit_processor_results: {{:error, :unchallenged_exit}, []})\n    assert {:error, []} = Core.chain_ok(state)\n  end\n\n  test \"validate_executions function prevent getter from progressing when invalid block is detected\" do\n    state = init_state()\n    block = %Block{number: 1, hash: <<>>}\n\n    assert {{:error, {:tx_execution, :some_exec_error_reason}}, state} =\n             Core.validate_executions([{:error, :some_exec_error_reason}], block, state)\n\n    assert {:error, [%Event.InvalidBlock{error_type: :tx_execution, hash: \"\", blknum: 1}]} = Core.chain_ok(state)\n  end\n\n  test \"after detecting twice same maximum possible potential withholdings get_numbers_of_blocks_to_download don't return this block\" do\n    potential_withholding_1_000 = Core.validate_download_response({:error, :error_reson}, <<>>, 1_000, 0, 0)\n    potential_withholding_2_000 = Core.validate_download_response({:error, :error_reson}, <<>>, 2_000, 0, 0)\n\n    init_state(init_opts: [maximum_number_of_pending_blocks: 2, maximum_block_withholding_time_ms: 10_000])\n    |> Core.get_numbers_of_blocks_to_download(20_000)\n    |> assert_check([1_000, 2_000])\n    |> handle_downloaded_block(potential_withholding_1_000)\n    |> handle_downloaded_block(potential_withholding_2_000)\n    |> Core.get_numbers_of_blocks_to_download(20_000)\n    |> assert_check([1_000, 2_000])\n    |> handle_downloaded_block(potential_withholding_2_000)\n    |> Core.get_numbers_of_blocks_to_download(20_000)\n    |> assert_check([2_000])\n    |> handle_downloaded_block(potential_withholding_1_000)\n    |> Core.get_numbers_of_blocks_to_download(20_000)\n    |> assert_check([1_000])\n  end\n\n  test \"applying block updates height\" do\n    state =\n      init_state(synced_height: 0, init_opts: [maximum_number_of_pending_blocks: 5])\n      |> Core.get_numbers_of_blocks_to_download(4_000)\n      |> assert_check([1_000, 2_000, 3_000])\n      |> handle_downloaded_block(%BlockApplication{number: 1_000})\n      |> handle_downloaded_block(%BlockApplication{number: 2_000})\n      |> handle_downloaded_block(%BlockApplication{number: 3_000})\n\n    synced_height = 2\n    next_synced_height = synced_height + 1\n\n    assert {[application1, application2], 0, [], state} =\n             Core.get_blocks_to_apply(\n               state,\n               [%{blknum: 1_000, eth_height: synced_height}, %{blknum: 2_000, eth_height: synced_height}],\n               synced_height\n             )\n\n    assert {state, 0, []} = Core.apply_block(state, application1)\n\n    assert {state, ^synced_height, [{:put, :last_block_getter_eth_height, ^synced_height}]} =\n             Core.apply_block(state, application2)\n\n    assert {[application3], ^synced_height, [], state} =\n             Core.get_blocks_to_apply(state, [%{blknum: 3_000, eth_height: next_synced_height}], next_synced_height)\n\n    assert {state, ^next_synced_height, [{:put, :last_block_getter_eth_height, ^next_synced_height}]} =\n             Core.apply_block(state, application3)\n\n    # weird case when submissions for next_synced_height are now empty\n    assert {[], ^next_synced_height, [], ^state} = Core.get_blocks_to_apply(state, [], next_synced_height)\n\n    # moving forward\n    next_synced_height2 = next_synced_height + 1\n\n    assert {[], ^next_synced_height2, [{:put, :last_block_getter_eth_height, ^next_synced_height2}], _} =\n             Core.get_blocks_to_apply(state, [], next_synced_height2)\n  end\n\n  test \"long running applying block scenario\" do\n    # this test replicates a long running scenario, with various inputs from the root chain coordinator\n    # We're testing if we're applying blocks and height updates correctly\n\n    # child block submissions on the root chain, by eth_height\n    submissions = %{\n      57 => [%{blknum: 0, eth_height: 57}],\n      58 => [%{blknum: 1000, eth_height: 58}],\n      59 => [%{blknum: 2000, eth_height: 59}],\n      60 => [%{blknum: 3000, eth_height: 60}],\n      61 => [%{blknum: 4000, eth_height: 61}, %{blknum: 5000, eth_height: 61}],\n      62 => [],\n      63 => [%{blknum: 6000, eth_height: 63}, %{blknum: 7000, eth_height: 63}],\n      64 => []\n    }\n\n    # take a flattened list of submissions between two heights (inclusive, just like Eth events API works)\n    take_submissions = fn {first, last} ->\n      range = Enum.to_list(Range.new(first, last))\n\n      submissions\n      |> Map.take(range)\n      |> Enum.flat_map(fn {_k, v} -> v end)\n    end\n\n    state =\n      init_state(synced_height: 58, start_block_number: 1_000, init_opts: [maximum_number_of_pending_blocks: 3])\n      |> Core.get_numbers_of_blocks_to_download(16_000_000)\n      |> assert_check([2_000, 3_000, 4_000])\n      |> handle_downloaded_block(%BlockApplication{number: 2_000})\n      |> handle_downloaded_block(%BlockApplication{number: 3_000})\n      |> handle_downloaded_block(%BlockApplication{number: 4_000})\n\n    # coordinator dwells in the past\n    assert {[], 58, [], _} = Core.get_blocks_to_apply(state, take_submissions.({58, 58}), 58)\n\n    # coordinator allows into the future\n    assert {[application0, application1000], 58, [], state_alt} =\n             Core.get_blocks_to_apply(\n               state,\n               take_submissions.(Core.get_eth_range_for_block_submitted_events(state, 60)),\n               60\n             )\n\n    assert {_, 59, [{:put, :last_block_getter_eth_height, 59}]} = Core.apply_block(state_alt, application0)\n    assert {_, 60, [{:put, :last_block_getter_eth_height, 60}]} = Core.apply_block(state_alt, application1000)\n\n    # coordinator on time\n    assert {[^application0], 58, [], state} =\n             Core.get_blocks_to_apply(\n               state,\n               take_submissions.(Core.get_eth_range_for_block_submitted_events(state, 59)),\n               59\n             )\n\n    assert {state, 59, [{:put, :last_block_getter_eth_height, 59}]} = Core.apply_block(state, application0)\n\n    state =\n      state\n      |> Core.get_numbers_of_blocks_to_download(16_000_000)\n      |> assert_check([5_000, 6_000, 7_000])\n      |> handle_downloaded_block(%BlockApplication{number: 5_000})\n      |> handle_downloaded_block(%BlockApplication{number: 6_000})\n\n    # coordinator dwells in the past\n    assert {[], 59, [], ^state} = Core.get_blocks_to_apply(state, take_submissions.({59, 59}), 59)\n\n    # coordinator allows into the future\n    assert {[^application1000, application4000, application5000], 59, [], state_alt} =\n             Core.get_blocks_to_apply(\n               state,\n               take_submissions.(Core.get_eth_range_for_block_submitted_events(state, 61)),\n               61\n             )\n\n    assert {state_alt, 60, [{:put, :last_block_getter_eth_height, 60}]} = Core.apply_block(state_alt, application1000)\n    assert {state_alt, 60, []} = Core.apply_block(state_alt, application4000)\n    assert {_, 61, [{:put, :last_block_getter_eth_height, 61}]} = Core.apply_block(state_alt, application5000)\n\n    # coordinator on time\n    assert {[^application1000], 59, [], state} =\n             Core.get_blocks_to_apply(\n               state,\n               take_submissions.(Core.get_eth_range_for_block_submitted_events(state, 60)),\n               60\n             )\n\n    assert {state, 60, [{:put, :last_block_getter_eth_height, 60}]} = Core.apply_block(state, application1000)\n\n    # coordinator dwells in the past\n    assert {[], 60, [], ^state} = Core.get_blocks_to_apply(state, take_submissions.({60, 60}), 60)\n\n    # coordinator allows into the future\n    assert {[^application4000, ^application5000], 60, [], state_alt} =\n             Core.get_blocks_to_apply(\n               state,\n               take_submissions.(Core.get_eth_range_for_block_submitted_events(state, 62)),\n               62\n             )\n\n    assert {state_alt, 60, []} = Core.apply_block(state_alt, application4000)\n    assert {_, 61, [{:put, :last_block_getter_eth_height, 61}]} = Core.apply_block(state_alt, application5000)\n\n    # coordinator on time\n    assert {[^application4000, ^application5000], 60, [], state} =\n             Core.get_blocks_to_apply(\n               state,\n               take_submissions.(Core.get_eth_range_for_block_submitted_events(state, 61)),\n               61\n             )\n\n    assert {state, 60, []} = Core.apply_block(state, application4000)\n    assert {state, 61, [{:put, :last_block_getter_eth_height, 61}]} = Core.apply_block(state, application5000)\n\n    # coordinator dwells in the past\n    assert {[], 61, [], ^state} = Core.get_blocks_to_apply(state, take_submissions.({61, 61}), 61)\n\n    # coordinator allows into the future\n    assert {[application6000], 61, [], state_alt} =\n             Core.get_blocks_to_apply(\n               state,\n               take_submissions.(Core.get_eth_range_for_block_submitted_events(state, 63)),\n               63\n             )\n\n    application7000 = %BlockApplication{number: 7_000, eth_height: 63, eth_height_done: true}\n\n    assert {state_alt, 61, []} = Core.apply_block(state_alt, application6000)\n    assert {_, 63, [{:put, :last_block_getter_eth_height, 63}]} = Core.apply_block(state_alt, application7000)\n\n    # coordinator on time\n    assert {[], 62, [{:put, :last_block_getter_eth_height, 62}], state} =\n             Core.get_blocks_to_apply(\n               state,\n               take_submissions.(Core.get_eth_range_for_block_submitted_events(state, 62)),\n               62\n             )\n\n    # coordinator dwells in the past\n    assert {[], 62, [], ^state} =\n             Core.get_blocks_to_apply(\n               state,\n               take_submissions.(Core.get_eth_range_for_block_submitted_events(state, 62)),\n               62\n             )\n\n    # coordinator allows into the future\n    assert {[^application6000], 62, [], state_alt} =\n             Core.get_blocks_to_apply(\n               state,\n               take_submissions.(Core.get_eth_range_for_block_submitted_events(state, 64)),\n               64\n             )\n\n    assert {_, 62, []} = Core.apply_block(state_alt, application6000)\n\n    # coordinator on time\n    assert {[^application6000], 62, [], state} =\n             Core.get_blocks_to_apply(\n               state,\n               take_submissions.(Core.get_eth_range_for_block_submitted_events(state, 63)),\n               63\n             )\n\n    assert {state, 62, []} = Core.apply_block(state, application6000)\n    assert {_, 63, [{:put, :last_block_getter_eth_height, 63}]} = Core.apply_block(state, application7000)\n  end\n\n  test \"gets continous ranges of blocks to apply\" do\n    state =\n      init_state(synced_height: 0, init_opts: [maximum_number_of_pending_blocks: 5])\n      |> Core.get_numbers_of_blocks_to_download(5_000)\n      |> assert_check([1_000, 2_000, 3_000, 4_000])\n      |> handle_downloaded_block(%BlockApplication{number: 1_000})\n      |> handle_downloaded_block(%BlockApplication{number: 3_000})\n      |> handle_downloaded_block(%BlockApplication{number: 4_000})\n\n    {[%BlockApplication{eth_height: 1, eth_height_done: true}], _, _, state} =\n      Core.get_blocks_to_apply(state, [%{blknum: 1_000, eth_height: 1}, %{blknum: 2_000, eth_height: 2}], 2)\n\n    state = state |> handle_downloaded_block(%BlockApplication{number: 2_000})\n\n    {[%BlockApplication{eth_height: 2, eth_height_done: true}], _, _, _} =\n      Core.get_blocks_to_apply(state, [%{blknum: 1_000, eth_height: 1}, %{blknum: 2_000, eth_height: 2}], 2)\n  end\n\n  test \"do not download blocks when there are too many downloaded blocks not yet applied\" do\n    state =\n      init_state(\n        synced_height: 0,\n        init_opts: [maximum_number_of_pending_blocks: 5, maximum_number_of_unapplied_blocks: 3]\n      )\n      |> Core.get_numbers_of_blocks_to_download(5_000)\n      |> assert_check([1_000, 2_000, 3_000])\n      |> Core.get_numbers_of_blocks_to_download(5_000)\n      |> assert_check([])\n      |> handle_downloaded_block(%BlockApplication{number: 1_000})\n      |> Core.get_numbers_of_blocks_to_download(5_000)\n      |> assert_check([])\n\n    synced_height = 1\n    {_, _, _, state} = Core.get_blocks_to_apply(state, [%{blknum: 1_000, eth_height: synced_height}], synced_height)\n\n    {_, [4_000]} = Core.get_numbers_of_blocks_to_download(state, 5_000)\n  end\n\n  test \"when State is not at the beginning should not init state properly\" do\n    assert init_state(state_at_beginning: false) == {:error, :not_at_block_beginning}\n  end\n\n  test \"maximum_number_of_pending_blocks can't be too low\" do\n    assert init_state(init_opts: [maximum_number_of_pending_blocks: 0]) ==\n             {:error, :maximum_number_of_pending_blocks_too_low}\n  end\n\n  test \"BlockGetter omits submissions of already applied blocks\" do\n    state =\n      init_state(synced_height: 1, start_block_number: 1000)\n      |> Core.get_numbers_of_blocks_to_download(5_000)\n      |> assert_check([2_000, 3_000, 4_000])\n      |> handle_downloaded_block(%BlockApplication{number: 2_000})\n\n    submissions = [%{blknum: 1_000, eth_height: 1}, %{blknum: 2_000, eth_height: 2}]\n    {[application], 1, [], state} = Core.get_blocks_to_apply(state, submissions, 2)\n\n    # apply that and see if we won't get the same thing again\n    {state, 2, _} = Core.apply_block(state, application)\n    {[], 2, _, _} = Core.get_blocks_to_apply(state, submissions, 2)\n  end\n\n  test \"an unapplied block appears in an already synced eth block (due to reorg)\" do\n    state =\n      init_state(synced_height: 2, start_block_number: 1000)\n      |> Core.get_numbers_of_blocks_to_download(5_000)\n      |> assert_check([2_000, 3_000, 4_000])\n      |> handle_downloaded_block(%BlockApplication{number: 2_000})\n      |> handle_downloaded_block(%BlockApplication{number: 3_000})\n\n    {[\n       %BlockApplication{number: 2_000, eth_height: 1, eth_height_done: true},\n       %BlockApplication{number: 3_000, eth_height: 3, eth_height_done: true}\n     ], 2, _,\n     _} = Core.get_blocks_to_apply(state, [%{blknum: 2_000, eth_height: 1}, %{blknum: 3_000, eth_height: 3}], 3)\n  end\n\n  test \"an already applied child chain block appears in a block above synced_height (due to a reorg)\" do\n    state =\n      init_state(start_block_number: 1_000)\n      |> Core.get_numbers_of_blocks_to_download(5_000)\n      |> assert_check([2_000, 3_000, 4_000])\n      |> handle_downloaded_block(%BlockApplication{number: 2_000})\n\n    {[%BlockApplication{number: 2_000, eth_height: 3, eth_height_done: true}], 1, [], _} =\n      Core.get_blocks_to_apply(state, [%{blknum: 1_000, eth_height: 3}, %{blknum: 2_000, eth_height: 3}], 3)\n  end\n\n  test \"apply block with eth_height lower than synced_height\" do\n    state =\n      init_state(synced_height: 2)\n      |> Core.get_numbers_of_blocks_to_download(2_000)\n      |> assert_check([1_000])\n      |> handle_downloaded_block(%BlockApplication{number: 1_000})\n\n    {[application], 2, [], state} = Core.get_blocks_to_apply(state, [%{blknum: 1_000, eth_height: 1}], 3)\n    {_, 1, _} = Core.apply_block(state, application)\n  end\n\n  test \"apply a block that moved forward\" do\n    state =\n      init_state(synced_height: 1, start_block_number: 1000)\n      |> Core.get_numbers_of_blocks_to_download(5_000)\n      |> assert_check([2_000, 3_000, 4_000])\n\n    # block 2_000 first appears at height 3\n    {[], 1, [], state} =\n      Core.get_blocks_to_apply(state, [%{blknum: 2_000, eth_height: 3}, %{blknum: 3_000, eth_height: 4}], 4)\n\n    # download blocks\n    state =\n      state\n      |> handle_downloaded_block(%BlockApplication{number: 2_000})\n      |> handle_downloaded_block(%BlockApplication{number: 3_000})\n\n    # block then moves forward\n    {[application1, application2], 1, [], state} =\n      Core.get_blocks_to_apply(state, [%{blknum: 2_000, eth_height: 4}, %{blknum: 3_000, eth_height: 4}], 4)\n\n    # the block is applied at height it was first seen\n    {state, 1, _} = Core.apply_block(state, application1)\n    {_, 4, _} = Core.apply_block(state, application2)\n  end\n\n  test \"apply a block that moved backward\" do\n    state =\n      init_state(synced_height: 1, start_block_number: 1000)\n      |> Core.get_numbers_of_blocks_to_download(5_000)\n      |> assert_check([2_000, 3_000, 4_000])\n\n    # block 2_000 first appears at height 3\n    {[], 1, [], state} =\n      Core.get_blocks_to_apply(state, [%{blknum: 2_000, eth_height: 3}, %{blknum: 3_000, eth_height: 4}], 4)\n\n    # download blocks\n    state =\n      state\n      |> handle_downloaded_block(%BlockApplication{number: 2_000})\n      |> handle_downloaded_block(%BlockApplication{number: 3_000})\n\n    # block then moves backward\n    {[application1, application2], 1, [], state} =\n      Core.get_blocks_to_apply(state, [%{blknum: 2_000, eth_height: 2}, %{blknum: 3_000, eth_height: 4}], 4)\n\n    # the block is applied at updated height\n    {state, 2, _} = Core.apply_block(state, application1)\n    {_, 4, _} = Core.apply_block(state, application2)\n  end\n\n  test \"move forward even though an applied block appears in submissions\" do\n    state =\n      init_state(start_block_number: 1_000, synced_height: 2)\n      |> Core.get_numbers_of_blocks_to_download(3_000)\n      |> assert_check([2_000])\n\n    {[], 3, [_], _} = Core.get_blocks_to_apply(state, [%{blknum: 1_000, eth_height: 1}], 3)\n  end\n\n  test \"returns valid eth range\" do\n    # properly looks `block_getter_reorg_margin` number of blocks backward\n    state = init_state(synced_height: 100, block_getter_reorg_margin: 10)\n    assert {100 - 10, 101} == Core.get_eth_range_for_block_submitted_events(state, 101)\n\n    # beginning of the range is no less than 0\n    state = init_state(synced_height: 0, block_getter_reorg_margin: 10)\n    assert {0, 101} == Core.get_eth_range_for_block_submitted_events(state, 101)\n  end\n\n  defp init_state(opts \\\\ []) do\n    init_params =\n      [\n        start_block_number: 0,\n        interval: 1_000,\n        synced_height: 1,\n        block_getter_reorg_margin: 5,\n        state_at_beginning: true,\n        exit_processor_results: {:ok, []},\n        init_opts: []\n      ]\n      |> Keyword.merge(opts)\n      |> Map.new()\n\n    with {:ok, state} <-\n           Core.init(\n             init_params.start_block_number,\n             init_params.interval,\n             init_params.synced_height,\n             init_params.block_getter_reorg_margin,\n             init_params.state_at_beginning,\n             init_params.exit_processor_results,\n             init_params.init_opts\n           ),\n         do: state\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/block_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.BlockTest do\n  @moduledoc \"\"\"\n  Simple unit test of part of `OMG.Watcher.Block`.\n  \"\"\"\n\n  use ExUnitFixtures\n  use ExUnit.Case, async: true\n\n  alias OMG.Watcher.Block\n  alias OMG.Watcher.TestHelper\n\n  defp eth(), do: <<0::160>>\n\n  describe \"hashed_txs_at/2\" do\n    @tag fixtures: [:stable_alice, :stable_bob]\n    test \"returns a block with the list of transactions and a computed merkle root hash\", %{\n      stable_alice: alice,\n      stable_bob: bob\n    } do\n      tx_1 = TestHelper.create_recovered([{1, 0, 0, alice}], eth(), [{bob, 100}])\n      tx_2 = TestHelper.create_recovered([{1, 1, 1, alice}], eth(), [{bob, 100}])\n\n      transactions = [tx_1, tx_2]\n\n      assert Block.hashed_txs_at(transactions, 10) == %Block{\n               hash:\n                 <<189, 245, 69, 5, 94, 45, 148, 210, 5, 89, 98, 245, 201, 111, 222, 48, 61, 114, 145, 55, 122, 84, 196,\n                   156, 254, 80, 85, 184, 3, 205, 163, 233>>,\n               number: 10,\n               transactions: [\n                 tx_1.signed_tx_bytes,\n                 tx_2.signed_tx_bytes\n               ]\n             }\n    end\n\n    @tag timeout: 60_000 * 3\n    @tag fixtures: [:stable_alice, :stable_bob]\n    test \"correctly calculates hash for a lot of transactions\", %{\n      stable_alice: alice,\n      stable_bob: bob\n    } do\n      transactions =\n        Enum.map(1..64_000, fn index ->\n          TestHelper.create_recovered([{1, index, index, alice}], eth(), [{bob, 100}])\n        end)\n\n      block = Block.hashed_txs_at(transactions, 10)\n\n      assert block.hash ==\n               <<12, 40, 202, 7, 16, 175, 119, 138, 7, 95, 8, 3, 148, 93, 162, 168, 136, 226, 196, 236, 83, 62, 220, 75,\n                 59, 52, 6, 18, 249, 52, 124, 228>>\n    end\n\n    test \"handles an empty list of transactions\" do\n      assert Block.hashed_txs_at([], 10) == %Block{\n               hash:\n                 <<246, 9, 190, 253, 254, 144, 102, 254, 20, 231, 67, 179, 98, 62, 174, 135, 143, 188, 70, 128, 5, 96,\n                   136, 22, 131, 44, 157, 70, 15, 42, 149, 210>>,\n               number: 10,\n               transactions: []\n             }\n    end\n  end\n\n  describe \"to_api_format/1\" do\n    test \"formats to map for API\" do\n      block = %Block{\n        hash: \"hash\",\n        number: 10,\n        transactions: [\"tx_1_bytes\", \"tx_2_bytes\"]\n      }\n\n      assert Block.to_api_format(block) == %{\n               blknum: 10,\n               hash: \"hash\",\n               transactions: [\"tx_1_bytes\", \"tx_2_bytes\"]\n             }\n    end\n  end\n\n  describe \"to_db_value/1\" do\n    test \"formats to DB format with valid inputs\" do\n      block = %Block{\n        hash: \"hash\",\n        number: 10,\n        transactions: [\"tx_1_bytes\", \"tx_2_bytes\"]\n      }\n\n      assert Block.to_db_value(block) == %{\n               hash: \"hash\",\n               number: 10,\n               transactions: [\"tx_1_bytes\", \"tx_2_bytes\"]\n             }\n    end\n\n    test \"fails to format when the list of transactions is not a list\" do\n      # Not sure how we want to handle this, there is currently no\n      # fallback function in the Block module\n      assert_raise FunctionClauseError, fn ->\n        Block.to_db_value(%Block{\n          hash: \"hash\",\n          number: 10,\n          transactions: %{}\n        })\n      end\n    end\n\n    test \"fails to format when the hash is not a binary\" do\n      assert_raise FunctionClauseError, fn ->\n        Block.to_db_value(%Block{\n          hash: 1,\n          number: 10,\n          transactions: []\n        })\n      end\n    end\n\n    test \"fails to format when the number is not an integer\" do\n      assert_raise FunctionClauseError, fn ->\n        Block.to_db_value(%Block{\n          hash: 1,\n          number: \"10\",\n          transactions: []\n        })\n      end\n    end\n  end\n\n  describe \"from_db_value/1\" do\n    test \"formats from DB format with valid inputs\" do\n      block = %{\n        hash: \"hash\",\n        number: 10,\n        transactions: [\"tx_1_bytes\", \"tx_2_bytes\"]\n      }\n\n      assert Block.from_db_value(block) == %Block{\n               hash: \"hash\",\n               number: 10,\n               transactions: [\"tx_1_bytes\", \"tx_2_bytes\"]\n             }\n    end\n\n    test \"fails to format when the list of transactions is not a list\" do\n      # Not sure how we want to handle this, there is currently no\n      # fallback function in the Block module\n      assert_raise FunctionClauseError, fn ->\n        Block.from_db_value(%{\n          hash: \"hash\",\n          number: 10,\n          transactions: %{}\n        })\n      end\n    end\n\n    test \"fails to format when the hash is not a binary\" do\n      assert_raise FunctionClauseError, fn ->\n        Block.from_db_value(%{\n          hash: 1,\n          number: 10,\n          transactions: []\n        })\n      end\n    end\n\n    test \"fails to format when the number is not an integer\" do\n      assert_raise FunctionClauseError, fn ->\n        Block.from_db_value(%{\n          hash: 1,\n          number: \"10\",\n          transactions: []\n        })\n      end\n    end\n  end\n\n  describe \"inclusion_proof/2\" do\n    # The tests below checks merkle proof normally tested via speaking to the contract\n    # (integration tests) against a fixed binary. The motivation for having such\n    # test is a quick test of whether the merkle proving didn't change.\n    @tag fixtures: [:stable_alice]\n    test \"calculates the inclusion proof when a list of transactions is given\", %{\n      stable_alice: alice\n    } do\n      # odd number of transactions, just in case\n      tx_1 = TestHelper.create_encoded([{1, 0, 0, alice}], eth(), [{alice, 7}])\n      tx_2 = TestHelper.create_encoded([{1, 1, 0, alice}], eth(), [{alice, 2}])\n      tx_3 = TestHelper.create_encoded([{1, 0, 1, alice}], eth(), [{alice, 2}])\n\n      proof = Block.inclusion_proof([tx_1, tx_2, tx_3], 1)\n\n      assert <<141, 42, 165, 123, 233, 242, 135, 178>> <> _ = proof\n      assert is_binary(proof)\n      assert byte_size(proof) == 32 * 16\n    end\n\n    @tag fixtures: [:stable_alice]\n    test \"calculates the inclusion proof when a block is given\", %{\n      stable_alice: alice\n    } do\n      tx_1 = TestHelper.create_encoded([{1, 0, 0, alice}], eth(), [{alice, 7}])\n      tx_2 = TestHelper.create_encoded([{1, 1, 0, alice}], eth(), [{alice, 2}])\n      proof = Block.inclusion_proof(%Block{transactions: [tx_1, tx_2]}, 1)\n\n      assert is_binary(proof)\n      assert byte_size(proof) == 32 * 16\n    end\n\n    @tag fixtures: [:stable_alice]\n    test \"calculating a proof via a block or a list of transactions return the same result\", %{\n      stable_alice: alice\n    } do\n      tx_1 = TestHelper.create_encoded([{1, 0, 0, alice}], eth(), [{alice, 7}])\n      tx_2 = TestHelper.create_encoded([{1, 1, 0, alice}], eth(), [{alice, 2}])\n\n      block_proof = Block.inclusion_proof(%Block{transactions: [tx_1, tx_2]}, 1)\n      transactions_proof = Block.inclusion_proof([tx_1, tx_2], 1)\n\n      assert block_proof == transactions_proof\n    end\n\n    test \"calculates the inclusion proof when an empty list is given\" do\n      proof = Block.inclusion_proof([], 1)\n\n      assert is_binary(proof)\n      assert byte_size(proof) == 32 * 16\n    end\n\n    test \"raises an error when an invalid input is given (map)\" do\n      assert_raise FunctionClauseError, fn ->\n        Block.inclusion_proof(%{}, 1)\n      end\n    end\n\n    @tag fixtures: [:alice]\n    test \"calculates a block merkle proof for deposit transactions\",\n         %{alice: alice} do\n      tx = TestHelper.create_encoded([], eth(), [{alice, 7}])\n      proof = Block.inclusion_proof(%Block{transactions: [tx]}, 0)\n\n      assert is_binary(proof)\n      assert byte_size(proof) == 32 * 16\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/block_validator_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.Validator.BlockValidatorTest do\n  use ExUnit.Case, async: true\n  use OMG.WatcherRPC.Web, :controller\n\n  alias OMG.Watcher.BlockValidator\n  alias OMG.Watcher.Merkle\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.TestHelper\n\n  @alice TestHelper.generate_entity()\n  @bob TestHelper.generate_entity()\n\n  @eth <<0::160>>\n\n  @payment_tx_type OMG.Watcher.WireFormatTypes.tx_type_for(:tx_payment_v1)\n\n  @fee_claimer <<27::160>>\n  @transaction_upper_limit 2 |> :math.pow(16) |> Kernel.trunc()\n\n  describe \"stateless_validate/1\" do\n    test \"returns an error if a transaction within the block is not correctly formed (e.g. duplicate inputs in this test)\" do\n      input_1 = {1, 0, 0, @alice}\n      input_2 = {2, 0, 0, @alice}\n      input_3 = {3, 0, 0, @alice}\n\n      signed_valid_tx = TestHelper.create_signed([input_1, input_2], @eth, [{@bob, 10}])\n      signed_invalid_tx = TestHelper.create_signed([input_3, input_3], @eth, [{@bob, 10}])\n\n      %{sigs: sigs_valid} = signed_valid_tx\n      %{sigs: sigs_invalid} = signed_invalid_tx\n\n      txbytes_valid = Transaction.raw_txbytes(signed_valid_tx)\n      txbytes_invalid = Transaction.raw_txbytes(signed_invalid_tx)\n\n      [_, inputs_valid, outputs_valid, _, _] = ExRLP.decode(txbytes_valid)\n      [_, inputs_invalid, outputs_invalid, _, _] = ExRLP.decode(txbytes_invalid)\n\n      hash_valid = ExRLP.encode([sigs_valid, @payment_tx_type, inputs_valid, outputs_valid, 0, <<0::256>>])\n\n      hash_invalid =\n        ExRLP.encode([\n          sigs_invalid,\n          @payment_tx_type,\n          inputs_invalid,\n          outputs_invalid,\n          0,\n          <<0::256>>\n        ])\n\n      block = %{\n        hash: Merkle.hash([txbytes_valid, txbytes_invalid]),\n        number: 1000,\n        transactions: [hash_invalid, hash_valid]\n      }\n\n      assert {:error, :duplicate_inputs} == BlockValidator.stateless_validate(block)\n    end\n\n    test \"accepts correctly formed transactions\" do\n      recovered_tx_1 = TestHelper.create_recovered([{1, 0, 0, @alice}, {2, 0, 0, @alice}], @eth, [{@bob, 10}])\n\n      recovered_tx_2 = TestHelper.create_recovered([{3, 0, 0, @alice}, {4, 0, 0, @alice}], @eth, [{@bob, 10}])\n\n      signed_txbytes_1 = recovered_tx_1.signed_tx_bytes\n      signed_txbytes_2 = recovered_tx_2.signed_tx_bytes\n\n      block = %{\n        hash: derive_merkle_root([recovered_tx_1, recovered_tx_2]),\n        number: 1000,\n        transactions: [signed_txbytes_1, signed_txbytes_2]\n      }\n\n      assert {:ok, true} == BlockValidator.stateless_validate(block)\n    end\n\n    test \"returns an error if the given hash does not match the reconstructed Merkle root hash\" do\n      recovered_tx_1 = TestHelper.create_recovered([{1, 0, 0, @alice}], @eth, [{@bob, 100}])\n      recovered_tx_2 = TestHelper.create_recovered([{2, 0, 0, @alice}], @eth, [{@bob, 100}])\n\n      signed_txbytes = Enum.map([recovered_tx_1, recovered_tx_2], fn tx -> tx.signed_tx_bytes end)\n\n      block = %{\n        hash: \"0x0\",\n        number: 1000,\n        transactions: signed_txbytes\n      }\n\n      assert {:error, :invalid_merkle_root} == BlockValidator.stateless_validate(block)\n    end\n\n    test \"accepts a matching Merkle root hash\" do\n      recovered_tx_1 = TestHelper.create_recovered([{1, 0, 0, @alice}], @eth, [{@bob, 100}])\n      recovered_tx_2 = TestHelper.create_recovered([{2, 0, 0, @alice}], @eth, [{@bob, 100}])\n\n      signed_txbytes = Enum.map([recovered_tx_1, recovered_tx_2], fn tx -> tx.signed_tx_bytes end)\n\n      valid_merkle_root = derive_merkle_root([recovered_tx_1, recovered_tx_2])\n\n      block = %{\n        hash: valid_merkle_root,\n        number: 1000,\n        transactions: signed_txbytes\n      }\n\n      assert {:ok, true} = BlockValidator.stateless_validate(block)\n    end\n\n    test \"rejects a block with no transactions or more transactions than the defined limit\" do\n      oversize_block = %{\n        hash: \"0x0\",\n        number: 1000,\n        transactions: List.duplicate(\"0x0\", @transaction_upper_limit + 1)\n      }\n\n      undersize_block = %{\n        hash: \"0x0\",\n        number: 1000,\n        transactions: []\n      }\n\n      assert {:error, :transactions_exceed_block_limit} = BlockValidator.stateless_validate(oversize_block)\n\n      assert {:error, :empty_block} = BlockValidator.stateless_validate(undersize_block)\n    end\n\n    test \"rejects a block that uses the same input in different transactions\" do\n      duplicate_input = {1, 0, 0, @alice}\n\n      recovered_tx_1 = TestHelper.create_recovered([duplicate_input], @eth, [{@bob, 10}])\n      recovered_tx_2 = TestHelper.create_recovered([duplicate_input], @eth, [{@bob, 10}])\n\n      signed_txbytes_1 = recovered_tx_1.signed_tx_bytes\n      signed_txbytes_2 = recovered_tx_2.signed_tx_bytes\n\n      block = %{\n        hash: derive_merkle_root([recovered_tx_1, recovered_tx_2]),\n        number: 1000,\n        transactions: [signed_txbytes_1, signed_txbytes_2]\n      }\n\n      assert {:error, :block_duplicate_inputs} == BlockValidator.stateless_validate(block)\n    end\n  end\n\n  describe \"stateless_validate/1 (fee validation)\" do\n    test \"rejects a block if there are multiple fee transactions of the same currency\" do\n      input_1 = {1, 0, 0, @alice}\n      input_2 = {2, 0, 0, @alice}\n\n      payment_tx_1 = TestHelper.create_recovered([input_1], @eth, [{@bob, 10}])\n      payment_tx_2 = TestHelper.create_recovered([input_2], @eth, [{@bob, 10}])\n      fee_tx_1 = TestHelper.create_recovered_fee_tx(1, @fee_claimer, @eth, 1)\n      fee_tx_2 = TestHelper.create_recovered_fee_tx(1, @fee_claimer, @eth, 1)\n\n      signed_txbytes = Enum.map([payment_tx_1, payment_tx_2, fee_tx_1, fee_tx_2], fn tx -> tx.signed_tx_bytes end)\n\n      block = %{\n        hash: derive_merkle_root([payment_tx_1, payment_tx_2, fee_tx_1, fee_tx_2]),\n        number: 1000,\n        transactions: signed_txbytes\n      }\n\n      assert {:error, :duplicate_fee_transaction_for_ccy} = BlockValidator.stateless_validate(block)\n    end\n\n    test \"rejects a block if fee transactions are not at the tail of the transactions' list (one fee currency)\" do\n      input_1 = {1, 0, 0, @alice}\n      input_2 = {2, 0, 0, @alice}\n\n      payment_tx_1 = TestHelper.create_recovered([input_1], @eth, [{@bob, 10}])\n      payment_tx_2 = TestHelper.create_recovered([input_2], @eth, [{@bob, 10}])\n      fee_tx = TestHelper.create_recovered_fee_tx(1, @fee_claimer, @eth, 5)\n\n      invalid_ordered_transactions = [payment_tx_1, fee_tx, payment_tx_2]\n      signed_txbytes = Enum.map(invalid_ordered_transactions, fn tx -> tx.signed_tx_bytes end)\n\n      block = %{\n        hash: derive_merkle_root(invalid_ordered_transactions),\n        number: 1000,\n        transactions: signed_txbytes\n      }\n\n      assert {:error, :unexpected_transaction_type_at_fee_index} = BlockValidator.stateless_validate(block)\n    end\n\n    test \"rejects a block if fee transactions are not at the tail of the transactions' list (two fee currencies)\" do\n      ccy_1 = @eth\n      ccy_2 = <<1::160>>\n\n      ccy_1_fee = 1\n      ccy_2_fee = 1\n\n      input_1 = {1, 0, 0, @alice}\n      input_2 = {2, 0, 0, @alice}\n\n      payment_tx_1 = TestHelper.create_recovered([input_1], ccy_1, [{@bob, 10}])\n      payment_tx_2 = TestHelper.create_recovered([input_2], ccy_2, [{@bob, 10}])\n\n      fee_tx_1 = TestHelper.create_recovered_fee_tx(1, @fee_claimer, ccy_1, ccy_1_fee)\n      fee_tx_2 = TestHelper.create_recovered_fee_tx(1, @fee_claimer, ccy_2, ccy_2_fee)\n\n      invalid_ordered_transactions = [payment_tx_1, fee_tx_1, payment_tx_2, fee_tx_2]\n      signed_txbytes = Enum.map(invalid_ordered_transactions, fn tx -> tx.signed_tx_bytes end)\n\n      block = %{\n        hash: derive_merkle_root(invalid_ordered_transactions),\n        number: 1000,\n        transactions: signed_txbytes\n      }\n\n      assert {:error, :unexpected_transaction_type_at_fee_index} = BlockValidator.stateless_validate(block)\n    end\n  end\n\n  @spec derive_merkle_root([Transaction.Recovered.t()]) :: binary()\n  defp(derive_merkle_root(transactions)) do\n    transactions |> Enum.map(&Transaction.raw_txbytes/1) |> Merkle.hash()\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/child_manager_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.ChildManagerTest do\n  use ExUnit.Case, async: true\n  alias OMG.Watcher.ChildManager\n\n  test \"that the process starts, sends a checkin to us and shuts down\" do\n    # start a mock module so that the child manager has someone to report to\n    {:ok, _} = __MODULE__.Monitor.start(self())\n    {:ok, pid} = ChildManager.start_link(monitor: __MODULE__.Monitor)\n    # when does the mananger send a health check?\n    %{timer: timer} = :sys.get_state(pid)\n    # we wait for that long\n    assert_receive(:got_health_checkin, Kernel.trunc(timer * 1.5))\n    # make sure child manager has shutdown\n    _ = Process.sleep(100)\n    assert Process.alive?(pid) == false\n  end\n\n  defmodule Monitor do\n    use GenServer\n\n    def start(parent) do\n      GenServer.start(__MODULE__, [parent], name: __MODULE__)\n    end\n\n    def init([parent]) do\n      {:ok, parent}\n    end\n\n    def health_checkin() do\n      GenServer.cast(__MODULE__, :health_checkin)\n    end\n\n    def handle_cast(:health_checkin, state) do\n      _ = send(state, :got_health_checkin)\n      {:stop, :normal, state}\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/crypto_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.CryptoTest do\n  use ExUnit.Case, async: true\n  doctest OMG.Watcher.Crypto\n\n  @moduledoc \"\"\"\n  A sanity and compatibility check of the crypto implementation.\n  \"\"\"\n\n  alias OMG.Watcher.Crypto\n  alias OMG.Watcher.DevCrypto\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.TypedDataHash\n\n  describe \"recover_address/2\" do\n    # Tests that we can digest, sign, and recover.\n    test \"recovers address of the signer from a binary-encoded signature\" do\n      {:ok, priv} = DevCrypto.generate_private_key()\n      {:ok, pub} = DevCrypto.generate_public_key(priv)\n      {:ok, address} = Crypto.generate_address(pub)\n\n      msg = :crypto.strong_rand_bytes(32)\n      sig = DevCrypto.signature_digest(msg, priv)\n\n      assert {:ok, ^address} = Crypto.recover_address(msg, sig)\n    end\n\n    # Test that we can sign and verify\n    test \"recovers address from an encoded transaction\" do\n      {:ok, priv} = DevCrypto.generate_private_key()\n      {:ok, pub} = DevCrypto.generate_public_key(priv)\n      {:ok, address} = Crypto.generate_address(pub)\n\n      raw_tx = Transaction.Payment.new([{1000, 1, 0}], [])\n      signature = DevCrypto.signature(raw_tx, priv)\n      assert byte_size(signature) == 65\n\n      assert raw_tx\n             |> TypedDataHash.hash_struct()\n             |> Crypto.recover_address(signature)\n             |> (&match?({:ok, ^address}, &1)).()\n\n      refute Transaction.Payment.new([{1000, 0, 1}], [])\n             |> TypedDataHash.hash_struct()\n             |> Crypto.recover_address(signature)\n             |> (&match?({:ok, ^address}, &1)).()\n    end\n  end\n\n  describe \"generate_address/1\" do\n    test \"generates an address with SHA3\" do\n      # test vectors below were generated using pyethereum's sha3 and privtoaddr\n      py_priv = \"7880aec93413f117ef14bd4e6d130875ab2c7d7d55a064fac3c2f7bd51516380\"\n      py_pub = \"c4d178249d840f548b09ad8269e8a3165ce2c170\"\n      priv = Crypto.hash(<<\"11\">>)\n\n      {:ok, pub} = DevCrypto.generate_public_key(priv)\n      {:ok, address} = Crypto.generate_address(pub)\n      {:ok, decoded_private} = Base.decode16(py_priv, case: :lower)\n      {:ok, decoded_address} = Base.decode16(py_pub, case: :lower)\n\n      assert ^decoded_private = priv\n      assert ^address = decoded_address\n    end\n\n    test \"generates an address with a public signature\" do\n      # test vector was generated using plasma.utils.utils.sign/2 from plasma-mvp\n      py_signature =\n        \"b8670d619701733e1b4d10149bc90eb4eb276760d2f77a08a5428d4cbf2eadbd656f374c187b1ac80ce31d8c62076af26150e52ef1f33bfc07c6d244da7ca38c1c\"\n\n      msg = Crypto.hash(\"1234\")\n      priv = Crypto.hash(\"11\")\n\n      {:ok, pub} = DevCrypto.generate_public_key(priv)\n      {:ok, _} = Crypto.generate_address(pub)\n\n      sig = DevCrypto.signature_digest(msg, priv)\n      assert ^sig = Base.decode16!(py_signature, case: :lower)\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/datadog_event/contract_event_consumer_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.DatadogEvent.ContractEventConsumerTest do\n  @moduledoc false\n\n  use ExUnit.Case, async: true\n  alias OMG.Watcher.DatadogEvent.ContractEventConsumer\n\n  setup_all do\n    Application.ensure_all_started(:omg_bus)\n\n    on_exit(fn ->\n      :ok = Application.stop(:omg_bus)\n    end)\n  end\n\n  setup do\n    pid = :erlang.pid_to_list(self())\n\n    start_supervised(\n      ContractEventConsumer.prepare_child(\n        topic: {:root_chain, \"#{pid}\"},\n        release: \"child_chain\",\n        current_version: \"test-123\",\n        publisher: __MODULE__.DatadogEventMock\n      )\n    )\n\n    :ok\n  end\n\n  test \"if an event put on omg bus is consumed by the event consumer and published on the publisher interface\" do\n    topic_name = self() |> :erlang.pid_to_list() |> to_string()\n    sig = \"#{topic_name}(bytes32)\"\n    data = [%{event_signature: sig}]\n    {:root_chain, topic_name} |> OMG.Bus.Event.new(:data, data) |> OMG.Bus.direct_local_broadcast()\n    assert_receive {:event, _, _}\n  end\n\n  test \"if a list of events put on omg bus is consumed by the event consumer, broken down and published individually on the publisher interface\" do\n    topic_name = self() |> :erlang.pid_to_list() |> to_string()\n    sig = \"#{topic_name}(bytes32)\"\n    data = [%{event_signature: sig, pos: 1}, %{event_signature: sig, pos: 2}]\n    {:root_chain, topic_name} |> OMG.Bus.Event.new(:data, data) |> OMG.Bus.direct_local_broadcast()\n\n    Enum.each(data, fn ev ->\n      %{pos: pos} = ev\n\n      assert_receive {:event, message, _}\n\n      [[_, pos_from_message]] = Regex.scan(~r{pos:.*?(\\d).*?}, message)\n      assert pos == String.to_integer(pos_from_message)\n    end)\n  end\n\n  defmodule DatadogEventMock do\n    def event(title, message, options) do\n      pid =\n        title\n        |> String.to_charlist()\n        |> :erlang.list_to_pid()\n\n      Kernel.send(pid, {:event, message, options})\n      :ok\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/datadog_event/encode_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.DatadogEvent.EncodeTest do\n  @moduledoc false\n\n  use ExUnit.Case, async: true\n  alias OMG.Watcher.DatadogEvent.Encode\n\n  test \"if deposit created event can be decoded from log\" do\n    deposit_created_event = %{\n      amount: 10,\n      blknum: 1,\n      currency: <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>>,\n      eth_height: 390,\n      event_signature: \"DepositCreated(address,uint256,address,uint256)\",\n      log_index: 0,\n      owner: <<59, 159, 76, 29, 210, 110, 11, 229, 147, 55, 59, 29, 54, 206, 226, 0, 140, 190, 184, 55>>,\n      root_chain_txhash:\n        <<77, 114, 166, 63, 244, 47, 29, 181, 10, 242, 195, 110, 139, 49, 65, 1, 210, 254, 163, 224, 0, 53, 117, 243, 2,\n          152, 233, 21, 63, 227, 216, 238>>\n    }\n\n    assert Encode.make_it_readable!(deposit_created_event) == %{\n             amount: 10,\n             blknum: 1,\n             currency: \"0x0000000000000000000000000000000000000000\",\n             eth_height: 390,\n             event_signature: \"DepositCreated(address,uint256,address,uint256)\",\n             log_index: 0,\n             owner: \"0x3b9f4c1dd26e0be593373b1d36cee2008cbeb837\",\n             root_chain_txhash: \"0x4d72a63ff42f1db50af2c36e8b314101d2fea3e0003575f30298e9153fe3d8ee\"\n           }\n  end\n\n  test \"if input piggybacked event log can be decoded\" do\n    input_piggybacked_event = %{\n      eth_height: 410,\n      event_signature: \"InFlightExitInputPiggybacked(address,bytes32,uint16)\",\n      log_index: 0,\n      output_index: 1,\n      owner: <<21, 19, 171, 205, 53, 144, 162, 94, 11, 237, 132, 6, 82, 217, 87, 57, 29, 222, 153, 85>>,\n      root_chain_txhash:\n        <<12, 201, 229, 85, 107, 189, 110, 234, 244, 48, 47, 68, 173, 202, 33, 87, 134, 255, 8, 207, 164, 74, 52, 190,\n          23, 96, 236, 166, 15, 151, 54, 79>>,\n      tx_hash:\n        <<255, 144, 183, 115, 3, 229, 107, 210, 48, 169, 173, 244, 166, 85, 58, 149, 245, 255, 181, 99, 72, 98, 5, 214,\n          251, 162, 93, 62, 70, 89, 73, 64>>,\n      omg_data: %{piggyback_type: :input}\n    }\n\n    assert Encode.make_it_readable!(input_piggybacked_event) == %{\n             output_index: 1,\n             owner: \"0x1513abcd3590a25e0bed840652d957391dde9955\",\n             tx_hash: \"0xff90b77303e56bd230a9adf4a6553a95f5ffb563486205d6fba25d3e46594940\",\n             eth_height: 410,\n             event_signature: \"InFlightExitInputPiggybacked(address,bytes32,uint16)\",\n             log_index: 0,\n             omg_data: %{piggyback_type: :input},\n             root_chain_txhash: \"0x0cc9e5556bbd6eeaf4302f44adca215786ff08cfa44a34be1760eca60f97364f\"\n           }\n  end\n\n  test \"if output piggybacked event log can be decoded\" do\n    output_piggybacked_event = %{\n      eth_height: 408,\n      event_signature: \"InFlightExitOutputPiggybacked(address,bytes32,uint16)\",\n      log_index: 1,\n      output_index: 1,\n      owner: <<21, 19, 171, 205, 53, 144, 162, 94, 11, 237, 132, 6, 82, 217, 87, 57, 29, 222, 153, 85>>,\n      root_chain_txhash:\n        <<124, 244, 58, 96, 128, 233, 150, 119, 222, 224, 178, 108, 35, 228, 105, 177, 223, 156, 251, 86, 165, 195, 242,\n          160, 18, 61, 246, 237, 174, 123, 91, 94>>,\n      tx_hash:\n        <<255, 144, 183, 115, 3, 229, 107, 210, 48, 169, 173, 244, 166, 85, 58, 149, 245, 255, 181, 99, 72, 98, 5, 214,\n          251, 162, 93, 62, 70, 89, 73, 64>>,\n      omg_data: %{piggyback_type: :output}\n    }\n\n    assert Encode.make_it_readable!(output_piggybacked_event) == %{\n             log_index: 1,\n             omg_data: %{piggyback_type: :output},\n             output_index: 1,\n             eth_height: 408,\n             event_signature: \"InFlightExitOutputPiggybacked(address,bytes32,uint16)\",\n             root_chain_txhash: \"0x7cf43a6080e99677dee0b26c23e469b1df9cfb56a5c3f2a0123df6edae7b5b5e\",\n             owner: \"0x1513abcd3590a25e0bed840652d957391dde9955\",\n             tx_hash: \"0xff90b77303e56bd230a9adf4a6553a95f5ffb563486205d6fba25d3e46594940\"\n           }\n  end\n\n  test \"if block emitted event log can be decoded\" do\n    block_submitted_event = %{\n      blknum: 1000,\n      eth_height: 398,\n      event_signature: \"BlockSubmitted(uint256)\",\n      log_index: 0,\n      root_chain_txhash:\n        <<41, 117, 89, 151, 155, 94, 250, 133, 74, 210, 158, 33, 108, 118, 166, 76, 63, 67, 98, 27, 191, 61, 193, 110,\n          75, 49, 251, 12, 182, 220, 235, 244>>\n    }\n\n    assert Encode.make_it_readable!(block_submitted_event) == %{\n             blknum: 1000,\n             eth_height: 398,\n             event_signature: \"BlockSubmitted(uint256)\",\n             log_index: 0,\n             root_chain_txhash: \"0x297559979b5efa854ad29e216c76a64c3f43621bbf3dc16e4b31fb0cb6dcebf4\"\n           }\n  end\n\n  test \"if exit finalized event log can be decoded\" do\n    exit_finalized_event = %{\n      eth_height: 330,\n      event_signature: \"ExitFinalized(uint160)\",\n      exit_id: 1_423_280_346_484_099_708_949_144_162_169_101_241_792_387_057,\n      log_index: 2,\n      root_chain_txhash:\n        <<190, 49, 10, 222, 65, 39, 140, 86, 7, 98, 3, 17, 183, 147, 99, 170, 82, 10, 196, 108, 123, 167, 84, 191, 48,\n          39, 213, 1, 197, 169, 95, 64>>\n    }\n\n    assert Encode.make_it_readable!(exit_finalized_event) == %{\n             eth_height: 330,\n             event_signature: \"ExitFinalized(uint160)\",\n             exit_id: 1_423_280_346_484_099_708_949_144_162_169_101_241_792_387_057,\n             log_index: 2,\n             root_chain_txhash: \"0xbe310ade41278c5607620311b79363aa520ac46c7ba754bf3027d501c5a95f40\"\n           }\n  end\n\n  test \"if in flight exit challanged can be decoded\" do\n    in_flight_exit_challanged_event = %{\n      challenger: <<122, 232, 25, 13, 153, 104, 203, 179, 181, 46, 86, 165, 107, 44, 212, 205, 94, 21, 164, 79>>,\n      competitor_position:\n        115_792_089_237_316_195_423_570_985_008_687_907_853_269_984_665_640_564_039_457_584_007_913_129_639_935,\n      eth_height: 406,\n      event_signature: \"InFlightExitChallenged(address,bytes32,uint256)\",\n      log_index: 0,\n      root_chain_txhash:\n        <<217, 227, 179, 170, 255, 129, 86, 218, 184, 176, 4, 136, 45, 59, 206, 131, 75, 168, 66, 201, 93, 239, 247,\n          236, 151, 218, 143, 148, 47, 135, 10, 180>>,\n      tx_hash:\n        <<117, 50, 82, 142, 194, 36, 57, 169, 161, 237, 95, 79, 206, 108, 214, 109, 113, 98, 90, 221, 98, 2, 206, 251,\n          151, 12, 16, 208, 79, 45, 80, 145>>\n    }\n\n    assert Encode.make_it_readable!(in_flight_exit_challanged_event) == %{\n             challenger: \"0x7ae8190d9968cbb3b52e56a56b2cd4cd5e15a44f\",\n             competitor_position:\n               115_792_089_237_316_195_423_570_985_008_687_907_853_269_984_665_640_564_039_457_584_007_913_129_639_935,\n             eth_height: 406,\n             event_signature: \"InFlightExitChallenged(address,bytes32,uint256)\",\n             log_index: 0,\n             root_chain_txhash: \"0xd9e3b3aaff8156dab8b004882d3bce834ba842c95deff7ec97da8f942f870ab4\",\n             tx_hash: \"0x7532528ec22439a9a1ed5f4fce6cd66d71625add6202cefb970c10d04f2d5091\"\n           }\n  end\n\n  test \"if exit challenged can be decoded \" do\n    exit_challenged_event = %{\n      eth_height: 287,\n      event_signature: \"ExitChallenged(uint256)\",\n      log_index: 0,\n      root_chain_txhash:\n        <<66, 82, 85, 28, 152, 229, 144, 134, 61, 240, 143, 214, 56, 156, 97, 106, 171, 81, 16, 56, 48, 106, 184, 247,\n          130, 36, 168, 45, 21, 7, 3, 37>>,\n      utxo_pos: 1_000_000_000_000\n    }\n\n    assert Encode.make_it_readable!(exit_challenged_event) == %{\n             eth_height: 287,\n             event_signature: \"ExitChallenged(uint256)\",\n             log_index: 0,\n             root_chain_txhash: \"0x4252551c98e590863df08fd6389c616aab511038306ab8f78224a82d15070325\",\n             utxo_pos: 1_000_000_000_000\n           }\n  end\n\n  test \"if in flight exit challenge responded can be decoded\" do\n    in_flight_exit_challenge_responded_event = %{\n      challenge_position: 1_000_000_000_000,\n      challenger: <<24, 230, 136, 50, 159, 249, 214, 25, 113, 8, 166, 102, 25, 145, 44, 218, 93, 158, 161, 99>>,\n      eth_height: 293,\n      event_signature: \"InFlightExitChallengeResponded(address,bytes32,uint256)\",\n      log_index: 0,\n      root_chain_txhash:\n        <<63, 182, 54, 98, 165, 47, 220, 5, 212, 113, 254, 217, 43, 101, 201, 197, 58, 155, 13, 153, 11, 123, 174, 252,\n          227, 24, 166, 228, 250, 108, 213, 23>>,\n      tx_hash:\n        <<230, 15, 66, 108, 188, 55, 20, 186, 114, 53, 223, 36, 2, 123, 242, 150, 212, 213, 42, 26, 12, 179, 109, 70,\n          214, 200, 138, 57, 64, 249, 141, 107>>\n    }\n\n    assert Encode.make_it_readable!(in_flight_exit_challenge_responded_event) == %{\n             challenge_position: 1_000_000_000_000,\n             challenger: \"0x18e688329ff9d6197108a66619912cda5d9ea163\",\n             eth_height: 293,\n             event_signature: \"InFlightExitChallengeResponded(address,bytes32,uint256)\",\n             log_index: 0,\n             root_chain_txhash: \"0x3fb63662a52fdc05d471fed92b65c9c53a9b0d990b7baefce318a6e4fa6cd517\",\n             tx_hash: \"0xe60f426cbc3714ba7235df24027bf296d4d52a1a0cb36d46d6c88a3940f98d6b\"\n           }\n  end\n\n  test \"if challenge in flight exit not cannonical can be decoded\" do\n    eth_tx_input_event = %{\n      competing_tx:\n        <<248, 116, 1, 225, 160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 59,\n          154, 202, 0, 238, 237, 1, 235, 148, 130, 28, 224, 68, 235, 159, 239, 63, 140, 241, 0, 192, 44, 230, 131, 216,\n          224, 52, 2, 224, 148, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 128, 160, 0, 0, 0, 0, 0,\n          0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>>,\n      competing_tx_input_index: 0,\n      competing_tx_pos: 0,\n      competing_tx_sig:\n        <<213, 14, 110, 137, 144, 125, 5, 4, 94, 64, 55, 85, 66, 96, 210, 166, 41, 110, 42, 187, 199, 54, 83, 228, 31,\n          85, 4, 44, 153, 33, 56, 182, 104, 35, 67, 129, 11, 98, 78, 229, 81, 4, 199, 65, 155, 47, 3, 187, 179, 69, 65,\n          239, 135, 219, 72, 233, 93, 232, 14, 157, 74, 187, 190, 63, 28>>,\n      in_flight_input_index: 0,\n      in_flight_tx:\n        <<248, 163, 1, 225, 160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 59,\n          154, 202, 0, 248, 92, 237, 1, 235, 148, 140, 7, 214, 39, 36, 232, 102, 145, 82, 184, 199, 23, 67, 29, 135,\n          188, 216, 208, 23, 89, 148, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 237, 1, 235, 148,\n          140, 7, 214, 39, 36, 232, 102, 145, 82, 184, 199, 23, 67, 29, 135, 188, 216, 208, 23, 89, 148, 0, 0, 0, 0, 0,\n          0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 128, 160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n          0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>>,\n      input_tx_bytes:\n        <<248, 83, 1, 192, 238, 237, 1, 235, 148, 140, 7, 214, 39, 36, 232, 102, 145, 82, 184, 199, 23, 67, 29, 135,\n          188, 216, 208, 23, 89, 148, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 128, 160, 0, 0, 0,\n          0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>>,\n      input_utxo_pos: 1_000_000_000\n    }\n\n    assert Encode.make_it_readable!(eth_tx_input_event) == %{\n             competing_tx:\n               \"0xf87401e1a0000000000000000000000000000000000000000000000000000000003b9aca00eeed01eb94821ce044eb9fef3f8cf100c02ce683d8e03402e09400000000000000000000000000000000000000000980a00000000000000000000000000000000000000000000000000000000000000000\",\n             competing_tx_input_index: 0,\n             competing_tx_pos: 0,\n             competing_tx_sig:\n               \"0xd50e6e89907d05045e4037554260d2a6296e2abbc73653e41f55042c992138b6682343810b624ee55104c7419b2f03bbb34541ef87db48e95de80e9d4abbbe3f1c\",\n             in_flight_input_index: 0,\n             in_flight_tx:\n               \"0xf8a301e1a0000000000000000000000000000000000000000000000000000000003b9aca00f85ced01eb948c07d62724e8669152b8c717431d87bcd8d0175994000000000000000000000000000000000000000005ed01eb948c07d62724e8669152b8c717431d87bcd8d017599400000000000000000000000000000000000000000480a00000000000000000000000000000000000000000000000000000000000000000\",\n             input_tx_bytes:\n               \"0xf85301c0eeed01eb948c07d62724e8669152b8c717431d87bcd8d017599400000000000000000000000000000000000000000a80a00000000000000000000000000000000000000000000000000000000000000000\",\n             input_utxo_pos: 1_000_000_000\n           }\n  end\n\n  test \"if in flight exit input/output blocked can be decoded \" do\n    in_flight_exit_output_blocked_event = %{\n      challenger: <<213, 8, 156, 250, 64, 58, 96, 49, 161, 243, 131, 189, 70, 126, 152, 14, 208, 189, 92, 186>>,\n      eth_height: 438,\n      event_signature: \"InFlightExitOutputBlocked(address,bytes32,uint16)\",\n      log_index: 0,\n      output_index: 1,\n      root_chain_txhash:\n        <<152, 71, 150, 186, 105, 123, 83, 43, 230, 36, 2, 153, 144, 252, 109, 79, 114, 228, 225, 67, 76, 246, 141, 207,\n          59, 5, 179, 75, 121, 135, 196, 104>>,\n      tx_hash:\n        <<42, 63, 46, 245, 8, 132, 225, 35, 163, 42, 44, 64, 216, 103, 88, 232, 254, 91, 130, 169, 162, 184, 46, 44, 8,\n          73, 190, 111, 19, 201, 87, 2>>,\n      omg_data: %{piggyback_type: :output}\n    }\n\n    assert Encode.make_it_readable!(in_flight_exit_output_blocked_event) == %{\n             challenger: \"0xd5089cfa403a6031a1f383bd467e980ed0bd5cba\",\n             eth_height: 438,\n             event_signature: \"InFlightExitOutputBlocked(address,bytes32,uint16)\",\n             log_index: 0,\n             omg_data: %{piggyback_type: :output},\n             output_index: 1,\n             root_chain_txhash: \"0x984796ba697b532be624029990fc6d4f72e4e1434cf68dcf3b05b34b7987c468\",\n             tx_hash: \"0x2a3f2ef50884e123a32a2c40d86758e8fe5b82a9a2b82e2c0849be6f13c95702\"\n           }\n  end\n\n  test \"if in flight exit started can be decoded\" do\n    in_flight_exit_started_event = %{\n      eth_height: 726,\n      event_signature: \"InFlightExitStarted(address,bytes32)\",\n      initiator: <<44, 106, 159, 66, 49, 128, 37, 205, 102, 39, 186, 242, 28, 70, 130, 1, 98, 32, 32, 223>>,\n      log_index: 0,\n      root_chain_txhash:\n        <<240, 228, 74, 240, 210, 100, 67, 185, 229, 19, 60, 100, 245, 167, 31, 6, 164, 212, 208, 212, 12, 94, 116, 18,\n          181, 234, 13, 252, 178, 241, 161, 51>>,\n      tx_hash:\n        <<79, 70, 5, 59, 93, 245, 133, 9, 76, 198, 82, 221, 216, 195, 101, 150, 42, 56, 137, 194, 5, 53, 146, 241, 131,\n          49, 185, 90, 125, 255, 98, 14>>\n    }\n\n    assert Encode.make_it_readable!(in_flight_exit_started_event) == %{\n             eth_height: 726,\n             event_signature: \"InFlightExitStarted(address,bytes32)\",\n             initiator: \"0x2c6a9f42318025cd6627baf21c468201622020df\",\n             log_index: 0,\n             root_chain_txhash: \"0xf0e44af0d26443b9e5133c64f5a71f06a4d4d0d40c5e7412b5ea0dfcb2f1a133\",\n             tx_hash: \"0x4f46053b5df585094cc652ddd8c365962a3889c2053592f18331b95a7dff620e\"\n           }\n  end\n\n  test \"if start in flight exit can be decoded \" do\n    in_flight_exit_start_event = %{\n      in_flight_tx:\n        <<248, 124, 1, 225, 160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 210,\n          32, 127, 180, 0, 246, 245, 1, 243, 148, 118, 78, 248, 3, 28, 17, 248, 220, 42, 92, 18, 141, 145, 248, 79, 186,\n          190, 47, 160, 172, 148, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 136, 69, 99, 145, 130, 68,\n          244, 0, 0, 128, 160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n          0, 0>>,\n      in_flight_tx_sigs: [\n        <<52, 191, 197, 222, 130, 0, 246, 100, 25, 133, 115, 123, 250, 19, 77, 122, 226, 50, 133, 34, 71, 195, 27, 188,\n          147, 104, 200, 235, 121, 231, 64, 251, 107, 58, 88, 55, 118, 117, 53, 9, 224, 81, 93, 0, 167, 62, 195, 202,\n          233, 207, 237, 254, 185, 95, 207, 246, 144, 69, 242, 160, 58, 161, 96, 70, 28>>\n      ],\n      input_inclusion_proofs: [\n        <<243, 154, 134, 159, 98, 231, 92, 245, 240, 191, 145, 70, 136, 166, 178, 137, 202, 242, 4, 148, 53, 216, 230,\n          140, 92, 94, 109, 5, 228, 73, 19, 243, 78, 213, 192, 45, 109, 72, 200, 147, 36, 134, 201, 157, 58, 217, 153,\n          229, 216, 148, 157, 195, 190, 59, 48, 88, 204, 41, 121, 105, 12, 62, 58, 98, 28, 121, 43, 20, 191, 102, 248,\n          42, 243, 111, 0, 245, 251, 167, 1, 79, 160, 193, 226, 255, 60, 124, 39, 59, 254, 82, 60, 26, 207, 103, 220,\n          63, 95, 160, 128, 166, 134, 165, 160, 208, 92, 61, 72, 34, 253, 84, 214, 50, 220, 156, 192, 75, 22, 22, 4,\n          110, 186, 44, 228, 153, 235, 154, 247, 159, 94, 185, 73, 105, 10, 4, 4, 171, 244, 206, 186, 252, 124, 255,\n          250, 56, 33, 145, 183, 221, 158, 125, 247, 120, 88, 30, 111, 183, 142, 250, 179, 95, 211, 100, 201, 213, 218,\n          218, 212, 86, 155, 109, 212, 127, 127, 234, 186, 250, 53, 113, 248, 66, 67, 68, 37, 84, 131, 53, 172, 110,\n          105, 13, 208, 113, 104, 216, 188, 91, 119, 151, 156, 26, 103, 2, 51, 79, 82, 159, 87, 131, 247, 158, 148, 47,\n          210, 205, 3, 246, 229, 90, 194, 207, 73, 110, 132, 159, 222, 156, 68, 111, 171, 70, 168, 210, 125, 177, 227,\n          16, 15, 39, 90, 119, 125, 56, 91, 68, 227, 203, 192, 69, 202, 186, 201, 218, 54, 202, 224, 64, 173, 81, 96,\n          130, 50, 76, 150, 18, 124, 242, 159, 69, 53, 235, 91, 126, 186, 207, 226, 161, 214, 211, 170, 184, 236, 4,\n          131, 211, 32, 121, 168, 89, 255, 112, 249, 33, 89, 112, 168, 190, 235, 177, 193, 100, 196, 116, 232, 36, 56,\n          23, 76, 142, 235, 111, 188, 140, 180, 89, 75, 136, 201, 68, 143, 29, 64, 176, 155, 234, 236, 172, 91, 69, 219,\n          110, 65, 67, 74, 18, 43, 105, 92, 90, 133, 134, 45, 142, 174, 64, 179, 38, 143, 111, 55, 228, 20, 51, 123,\n          227, 142, 186, 122, 181, 187, 243, 3, 208, 31, 75, 122, 224, 127, 215, 62, 220, 47, 59, 224, 94, 67, 148, 138,\n          52, 65, 138, 50, 114, 80, 156, 67, 194, 129, 26, 130, 30, 92, 152, 43, 165, 24, 116, 172, 125, 201, 221, 121,\n          168, 12, 194, 240, 95, 111, 102, 76, 157, 187, 46, 69, 68, 53, 19, 125, 160, 108, 228, 77, 228, 85, 50, 165,\n          106, 58, 112, 7, 162, 208, 198, 180, 53, 247, 38, 249, 81, 4, 191, 166, 231, 7, 4, 111, 193, 84, 186, 233, 24,\n          152, 208, 58, 26, 10, 198, 249, 180, 94, 71, 22, 70, 226, 85, 90, 199, 158, 63, 232, 126, 177, 120, 30, 38,\n          242, 5, 0, 36, 12, 55, 146, 116, 254, 145, 9, 110, 96, 209, 84, 90, 128, 69, 87, 31, 218, 185, 181, 48, 208,\n          214, 231, 232, 116, 110, 120, 191, 159, 32, 244, 232, 111, 6>>\n      ],\n      input_txs: [\n        <<248, 91, 1, 192, 246, 245, 1, 243, 148, 118, 78, 248, 3, 28, 17, 248, 220, 42, 92, 18, 141, 145, 248, 79, 186,\n          190, 47, 160, 172, 148, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 136, 138, 199, 35, 4, 137,\n          232, 0, 0, 128, 160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n          0, 0>>\n      ],\n      input_utxos_pos: [2_002_000_000_000]\n    }\n\n    assert Encode.make_it_readable!(in_flight_exit_start_event) == %{\n             in_flight_tx:\n               \"0xf87c01e1a0000000000000000000000000000000000000000000000000000001d2207fb400f6f501f394764ef8031c11f8dc2a5c128d91f84fbabe2fa0ac940000000000000000000000000000000000000000884563918244f4000080a00000000000000000000000000000000000000000000000000000000000000000\",\n             in_flight_tx_sigs: [\n               \"0x34bfc5de8200f6641985737bfa134d7ae232852247c31bbc9368c8eb79e740fb6b3a583776753509e0515d00a73ec3cae9cfedfeb95fcff69045f2a03aa160461c\"\n             ],\n             input_inclusion_proofs: [\n               \"0xf39a869f62e75cf5f0bf914688a6b289caf2049435d8e68c5c5e6d05e44913f34ed5c02d6d48c8932486c99d3ad999e5d8949dc3be3b3058cc2979690c3e3a621c792b14bf66f82af36f00f5fba7014fa0c1e2ff3c7c273bfe523c1acf67dc3f5fa080a686a5a0d05c3d4822fd54d632dc9cc04b1616046eba2ce499eb9af79f5eb949690a0404abf4cebafc7cfffa382191b7dd9e7df778581e6fb78efab35fd364c9d5dadad4569b6dd47f7feabafa3571f842434425548335ac6e690dd07168d8bc5b77979c1a6702334f529f5783f79e942fd2cd03f6e55ac2cf496e849fde9c446fab46a8d27db1e3100f275a777d385b44e3cbc045cabac9da36cae040ad516082324c96127cf29f4535eb5b7ebacfe2a1d6d3aab8ec0483d32079a859ff70f9215970a8beebb1c164c474e82438174c8eeb6fbc8cb4594b88c9448f1d40b09beaecac5b45db6e41434a122b695c5a85862d8eae40b3268f6f37e414337be38eba7ab5bbf303d01f4b7ae07fd73edc2f3be05e43948a34418a3272509c43c2811a821e5c982ba51874ac7dc9dd79a80cc2f05f6f664c9dbb2e454435137da06ce44de45532a56a3a7007a2d0c6b435f726f95104bfa6e707046fc154bae91898d03a1a0ac6f9b45e471646e2555ac79e3fe87eb1781e26f20500240c379274fe91096e60d1545a8045571fdab9b530d0d6e7e8746e78bf9f20f4e86f06\"\n             ],\n             input_txs: [\n               \"0xf85b01c0f6f501f394764ef8031c11f8dc2a5c128d91f84fbabe2fa0ac940000000000000000000000000000000000000000888ac7230489e8000080a00000000000000000000000000000000000000000000000000000000000000000\"\n             ],\n             input_utxos_pos: [2_002_000_000_000]\n           }\n  end\n\n  test \"if in flight exit finalized can be decoded\" do\n    in_flight_exit_finalized_event = %{\n      eth_height: 335,\n      event_signature: \"InFlightExitOutputWithdrawn(uint160,uint16)\",\n      in_flight_exit_id: 3_853_567_223_408_339_354_111_409_210_931_346_801_537_991_844,\n      log_index: 1,\n      output_index: 1,\n      root_chain_txhash:\n        <<80, 248, 10, 40, 199, 180, 94, 87, 0, 214, 231, 86, 164, 157, 76, 108, 238, 189, 92, 74, 82, 133, 178, 138,\n          190, 185, 112, 88, 201, 65, 185, 102>>,\n      omg_data: %{piggyback_type: :output}\n    }\n\n    assert Encode.make_it_readable!(in_flight_exit_finalized_event) == %{\n             eth_height: 335,\n             event_signature: \"InFlightExitOutputWithdrawn(uint160,uint16)\",\n             in_flight_exit_id: 3_853_567_223_408_339_354_111_409_210_931_346_801_537_991_844,\n             log_index: 1,\n             omg_data: %{piggyback_type: :output},\n             output_index: 1,\n             root_chain_txhash: \"0x50f80a28c7b45e5700d6e756a49d4c6ceebd5c4a5285b28abeb97058c941b966\"\n           }\n  end\n\n  test \"if exit started can be decoded\" do\n    exit_started_event = %{\n      eth_height: 759,\n      event_signature: \"ExitStarted(address,uint160)\",\n      exit_id: 961_120_214_746_159_734_848_620_722_848_998_552_444_082_017,\n      log_index: 1,\n      owner: <<8, 133, 129, 36, 179, 184, 128, 198, 139, 54, 15, 211, 25, 204, 97, 218, 39, 84, 94, 154>>,\n      root_chain_txhash:\n        <<74, 130, 72, 184, 138, 23, 178, 190, 76, 96, 134, 161, 152, 70, 34, 222, 26, 96, 221, 163, 201, 221, 158, 206,\n          30, 249, 126, 209, 142, 250, 2, 140>>\n    }\n\n    assert Encode.make_it_readable!(exit_started_event) == %{\n             eth_height: 759,\n             event_signature: \"ExitStarted(address,uint160)\",\n             exit_id: 961_120_214_746_159_734_848_620_722_848_998_552_444_082_017,\n             log_index: 1,\n             owner: \"0x08858124b3b880c68b360fd319cc61da27545e9a\",\n             root_chain_txhash: \"0x4a8248b88a17b2be4c6086a1984622de1a60dda3c9dd9ece1ef97ed18efa028c\"\n           }\n  end\n\n  test \"if start standard exit can be decoded\" do\n    start_standard_exit_event = %{\n      output_tx:\n        <<248, 91, 1, 192, 246, 245, 1, 243, 148, 8, 133, 129, 36, 179, 184, 128, 198, 139, 54, 15, 211, 25, 204, 97,\n          218, 39, 84, 94, 154, 148, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 136, 13, 224, 182, 179,\n          167, 100, 0, 0, 128, 160, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n          0, 0, 0, 0>>,\n      utxo_pos: 2_001_000_000_000\n    }\n\n    assert Encode.make_it_readable!(start_standard_exit_event) == %{\n             output_tx:\n               \"0xf85b01c0f6f501f39408858124b3b880c68b360fd319cc61da27545e9a940000000000000000000000000000000000000000880de0b6b3a764000080a00000000000000000000000000000000000000000000000000000000000000000\",\n             utxo_pos: 2_001_000_000_000\n           }\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/ethereum_event_aggregator_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\ndefmodule OMG.Watcher.EthereumEventAggregatorTest do\n  use ExUnit.Case, async: true\n  import ExUnit.CaptureLog, only: [capture_log: 1]\n  alias OMG.Eth.RootChain.Abi\n  alias OMG.Watcher.EthereumEventAggregator\n\n  setup do\n    table = :ets.new(String.to_atom(\"test-#{:rand.uniform(1000)}\"), [:bag, :public, :named_table])\n    event_fetcher_name = String.to_atom(\"test-#{:rand.uniform(1000)}\")\n\n    start_supervised(\n      {EthereumEventAggregator,\n       name: event_fetcher_name,\n       contracts: %{},\n       ets_bucket: table,\n       events: [\n         [name: :deposit_created, enrich: false],\n         [name: :exit_started, enrich: true],\n         [name: :in_flight_exit_input_piggybacked, enrich: false],\n         [name: :in_flight_exit_output_piggybacked, enrich: false],\n         [name: :in_flight_exit_started, enrich: true],\n         [name: :in_flight_exit_deleted, enrich: false]\n       ]}\n    )\n\n    {:ok, %{event_fetcher_name: event_fetcher_name, table: table}}\n  end\n\n  @tag common: true\n  test \"(watcher) the performance of event retrieving\", %{\n    table: table,\n    event_fetcher_name: event_fetcher_name,\n    test: test_name\n  } do\n    defmodule test_name do\n      alias OMG.Watcher.EthereumEventAggregatorTest\n\n      def get_ethereum_events(_from_block, to_block, _signatures, _contracts) do\n        deposits = for n <- 1..10_000, do: EthereumEventAggregatorTest.deposit_created_log(n)\n        {:ok, [EthereumEventAggregatorTest.in_flight_exit_input_piggybacked_log(to_block) | deposits]}\n      end\n\n      def get_call_data(_tx_hash) do\n        {:ok, EthereumEventAggregatorTest.start_standard_exit_log()}\n      end\n    end\n\n    from_block = 1\n    to_block = 80_000\n    :sys.replace_state(event_fetcher_name, fn state -> Map.put(state, :rpc, test_name) end)\n    events = event_fetcher_name |> :sys.get_state() |> Map.get(:events)\n    _ = EthereumEventAggregator.deposit_created(event_fetcher_name, from_block, to_block)\n    assert Enum.count(:ets.tab2list(table)) == Enum.count(events) * 80_000\n  end\n\n  describe \"start_link/1 and init/1\" do\n    test \"(watcher) that events are correctly initialized \", %{\n      event_fetcher_name: event_fetcher_name\n    } do\n      assert event_fetcher_name |> :sys.get_state() |> Map.get(:events) == [\n               [\n                 signature: \"InFlightExitDeleted(uint160)\",\n                 name: :in_flight_exit_deleted,\n                 enrich: false\n               ],\n               [\n                 signature: \"InFlightExitStarted(address,bytes32)\",\n                 name: :in_flight_exit_started,\n                 enrich: true\n               ],\n               [\n                 signature: \"InFlightExitOutputPiggybacked(address,bytes32,uint16)\",\n                 name: :in_flight_exit_output_piggybacked,\n                 enrich: false\n               ],\n               [\n                 signature: \"InFlightExitInputPiggybacked(address,bytes32,uint16)\",\n                 name: :in_flight_exit_input_piggybacked,\n                 enrich: false\n               ],\n               [\n                 signature: \"ExitStarted(address,uint160)\",\n                 name: :exit_started,\n                 enrich: true\n               ],\n               [\n                 signature: \"DepositCreated(address,uint256,address,uint256)\",\n                 name: :deposit_created,\n                 enrich: false\n               ]\n             ]\n    end\n\n    test \"(watcher) that signatures are correctly initialized \", %{\n      event_fetcher_name: event_fetcher_name\n    } do\n      assert event_fetcher_name |> :sys.get_state() |> Map.get(:event_signatures) |> Enum.sort() ==\n               Enum.sort([\n                 \"InFlightExitStarted(address,bytes32)\",\n                 \"InFlightExitOutputPiggybacked(address,bytes32,uint16)\",\n                 \"InFlightExitInputPiggybacked(address,bytes32,uint16)\",\n                 \"ExitStarted(address,uint160)\",\n                 \"InFlightExitDeleted(uint160)\",\n                 \"DepositCreated(address,uint256,address,uint256)\"\n               ])\n    end\n  end\n\n  describe \"delete_old_logs/2\" do\n    # we start the test with a completely empty ETS table, meaning to events were retrieved yet\n    # so the first call from a ETH event listener would actually retrieve values from Infura\n    test \"(watcher) that :delete_events_threshold_ethereum_block_height is respected and that events get deleted from ETS\",\n         %{\n           event_fetcher_name: event_fetcher_name,\n           table: table,\n           test: test_name\n         } do\n      defmodule test_name do\n        alias OMG.Watcher.EthereumEventAggregatorTest\n\n        def get_ethereum_events(from_block, to_block, _signatures, _contracts) do\n          {:ok,\n           [\n             EthereumEventAggregatorTest.deposit_created_log(from_block),\n             EthereumEventAggregatorTest.exit_started_log(to_block),\n             EthereumEventAggregatorTest.in_flight_exit_output_piggybacked_log(from_block),\n             EthereumEventAggregatorTest.in_flight_exit_input_piggybacked_log(to_block)\n           ]}\n        end\n\n        def get_call_data(_tx_hash) do\n          {:ok, EthereumEventAggregatorTest.start_standard_exit_log()}\n        end\n      end\n\n      from_block = 1\n      to_block = 3\n      :sys.replace_state(event_fetcher_name, fn state -> Map.put(state, :rpc, test_name) end)\n\n      :sys.replace_state(event_fetcher_name, fn state ->\n        Map.put(state, :delete_events_threshold_ethereum_block_height, 1)\n      end)\n\n      events = event_fetcher_name |> :sys.get_state() |> Map.get(:events)\n\n      # create data that we need\n      deposit_created = from_block |> deposit_created_log() |> Abi.decode_log()\n      deposit_created_2 = from_block |> Kernel.+(1) |> deposit_created_log() |> Abi.decode_log()\n\n      exit_started_log =\n        to_block\n        |> exit_started_log()\n        |> Abi.decode_log()\n        |> Map.put(:call_data, start_standard_exit_log() |> from_hex |> Abi.decode_function())\n\n      in_flight_exit_output_piggybacked_log = from_block |> in_flight_exit_output_piggybacked_log() |> Abi.decode_log()\n      in_flight_exit_input_piggybacked_log = to_block |> in_flight_exit_input_piggybacked_log() |> Abi.decode_log()\n\n      data = [\n        {from_block, get_signature_from_event(events, :deposit_created), [deposit_created]},\n        {from_block, get_signature_from_event(events, :in_flight_exit_output_piggybacked),\n         [in_flight_exit_output_piggybacked_log]},\n        {from_block, get_signature_from_event(events, :in_flight_exit_started), []},\n        {from_block, get_signature_from_event(events, :in_flight_exit_input_piggybacked),\n         [in_flight_exit_input_piggybacked_log]},\n        {from_block, get_signature_from_event(events, :exit_started), [exit_started_log]},\n        # this deposit will get called out below\n        {from_block + 1, get_signature_from_event(events, :deposit_created), [deposit_created_2]},\n        {from_block + 1, get_signature_from_event(events, :in_flight_exit_output_piggybacked), []},\n        {from_block + 1, get_signature_from_event(events, :in_flight_exit_started), []},\n        {from_block + 1, get_signature_from_event(events, :in_flight_exit_input_piggybacked), []},\n        {from_block + 1, get_signature_from_event(events, :exit_started), []},\n        {to_block, get_signature_from_event(events, :deposit_created), []},\n        {to_block, get_signature_from_event(events, :in_flight_exit_output_piggybacked), []},\n        {to_block, get_signature_from_event(events, :exit_started), [exit_started_log]},\n        {to_block, get_signature_from_event(events, :in_flight_exit_input_piggybacked),\n         [in_flight_exit_input_piggybacked_log]},\n        {to_block, get_signature_from_event(events, :in_flight_exit_started), []}\n      ]\n\n      _ = :ets.insert(table, data)\n\n      from_block_2 = 2\n      to_block_2 = 3\n      # this should induce a ETS delete call\n      assert EthereumEventAggregator.deposit_created(event_fetcher_name, from_block_2, to_block_2) ==\n               {:ok, [deposit_created_2]}\n\n      what_should_be_left_in_db = [\n        {from_block + 1, get_signature_from_event(events, :deposit_created), [deposit_created_2]},\n        {from_block + 1, get_signature_from_event(events, :in_flight_exit_output_piggybacked), []},\n        {from_block + 1, get_signature_from_event(events, :in_flight_exit_started), []},\n        {from_block + 1, get_signature_from_event(events, :in_flight_exit_input_piggybacked), []},\n        {from_block + 1, get_signature_from_event(events, :exit_started), []},\n        {to_block, get_signature_from_event(events, :deposit_created), []},\n        {to_block, get_signature_from_event(events, :in_flight_exit_output_piggybacked), []},\n        {to_block, get_signature_from_event(events, :exit_started), [exit_started_log]},\n        {to_block, get_signature_from_event(events, :in_flight_exit_input_piggybacked),\n         [in_flight_exit_input_piggybacked_log]},\n        {to_block, get_signature_from_event(events, :in_flight_exit_started), []}\n      ]\n\n      # we're just making sure that handle continue gets called after handle_call\n      :ok = Process.sleep(100)\n      assert Enum.sort(:ets.tab2list(table)) == Enum.sort(what_should_be_left_in_db)\n    end\n  end\n\n  describe \"api calls/2 calls/3 and store_logs/4\" do\n    # We also assert if blocks that did NOT have any events get commited to ETS as empty.\n    # This is important because we do not want to re-scan blocks for which we know contain nothing.\n    test \"(watcher) if we get response for range and all events are commited to ETS\", %{\n      event_fetcher_name: event_fetcher_name,\n      table: table,\n      test: test_name\n    } do\n      defmodule test_name do\n        alias OMG.Watcher.EthereumEventAggregatorTest\n\n        def get_ethereum_events(from_block, to_block, _signatures, _contracts) do\n          {:ok,\n           [\n             EthereumEventAggregatorTest.deposit_created_log(from_block),\n             EthereumEventAggregatorTest.deposit_created_log(from_block + 1),\n             EthereumEventAggregatorTest.exit_started_log(to_block),\n             EthereumEventAggregatorTest.in_flight_exit_output_piggybacked_log(from_block),\n             EthereumEventAggregatorTest.in_flight_exit_input_piggybacked_log(to_block),\n             EthereumEventAggregatorTest.in_flight_exit_deleted_log(from_block)\n           ]}\n        end\n\n        def get_call_data(_tx_hash) do\n          {:ok, EthereumEventAggregatorTest.start_standard_exit_log()}\n        end\n      end\n\n      # we need to set the RPC module with our mocked implementation\n      :sys.replace_state(event_fetcher_name, fn state -> Map.put(state, :rpc, test_name) end)\n      # we read the events from the aggregators state so that we're able to build the\n      # event data later\n      events = event_fetcher_name |> :sys.get_state() |> Map.get(:events)\n\n      from_block = 1\n      to_block = 3\n      # we need to create events that we later expect when we call the aggregator APIs\n      # for example, deposit_created and deposit_created_2 are expected if the range is from 1 to 3\n      deposit_created = from_block |> deposit_created_log() |> Abi.decode_log()\n      deposit_created_2 = from_block |> Kernel.+(1) |> deposit_created_log() |> Abi.decode_log()\n\n      exit_started_log =\n        to_block\n        |> exit_started_log()\n        |> Abi.decode_log()\n        |> Map.put(:call_data, start_standard_exit_log() |> from_hex |> Abi.decode_function())\n\n      in_flight_exit_output_piggybacked_log = from_block |> in_flight_exit_output_piggybacked_log() |> Abi.decode_log()\n      in_flight_exit_input_piggybacked_log = to_block |> in_flight_exit_input_piggybacked_log() |> Abi.decode_log()\n\n      exit_deleted = from_block |> in_flight_exit_deleted_log() |> Abi.decode_log()\n      # now we're asserting that the API returns the correct events based on the range\n      assert EthereumEventAggregator.exit_started(event_fetcher_name, from_block, to_block) == {:ok, [exit_started_log]}\n\n      assert EthereumEventAggregator.in_flight_exit_piggybacked(event_fetcher_name, from_block, to_block) ==\n               {:ok, [in_flight_exit_input_piggybacked_log, in_flight_exit_output_piggybacked_log]}\n\n      assert EthereumEventAggregator.deposit_created(event_fetcher_name, from_block, to_block) ==\n               {:ok, [deposit_created, deposit_created_2]}\n\n      assert EthereumEventAggregator.in_flight_exit_deleted(event_fetcher_name, from_block, to_block) ==\n               {:ok, [exit_deleted]}\n\n      # and now we're asserting that the API calls actually stored the events above\n      # also that the events were stored at the right blknum key\n      assert Enum.sort(:ets.tab2list(table)) ==\n               Enum.sort([\n                 {from_block, get_signature_from_event(events, :in_flight_exit_deleted), [exit_deleted]},\n                 {from_block, get_signature_from_event(events, :deposit_created), [deposit_created]},\n                 {from_block, get_signature_from_event(events, :in_flight_exit_output_piggybacked),\n                  [in_flight_exit_output_piggybacked_log]},\n                 {from_block, get_signature_from_event(events, :in_flight_exit_started), []},\n                 {from_block, get_signature_from_event(events, :in_flight_exit_input_piggybacked), []},\n                 {from_block, get_signature_from_event(events, :exit_started), []},\n                 {from_block + 1, get_signature_from_event(events, :deposit_created), [deposit_created_2]},\n                 {from_block + 1, get_signature_from_event(events, :in_flight_exit_output_piggybacked), []},\n                 {from_block + 1, get_signature_from_event(events, :in_flight_exit_started), []},\n                 {from_block + 1, get_signature_from_event(events, :in_flight_exit_input_piggybacked), []},\n                 {from_block + 1, get_signature_from_event(events, :exit_started), []},\n                 {from_block + 1, get_signature_from_event(events, :in_flight_exit_deleted), []},\n                 {to_block, get_signature_from_event(events, :deposit_created), []},\n                 {to_block, get_signature_from_event(events, :in_flight_exit_output_piggybacked), []},\n                 {to_block, get_signature_from_event(events, :exit_started), [exit_started_log]},\n                 {to_block, get_signature_from_event(events, :in_flight_exit_input_piggybacked),\n                  [in_flight_exit_input_piggybacked_log]},\n                 {to_block, get_signature_from_event(events, :in_flight_exit_started), []},\n                 {to_block, get_signature_from_event(events, :in_flight_exit_deleted), []}\n               ])\n    end\n\n    test \"(watcher) if we get response for range where from equals to and that all events are commited to ETS\", %{\n      event_fetcher_name: event_fetcher_name,\n      table: table,\n      test: test_name\n    } do\n      defmodule test_name do\n        alias OMG.Watcher.EthereumEventAggregatorTest\n\n        def get_ethereum_events(from_block, to_block, _signatures, _contracts) do\n          {:ok,\n           [\n             EthereumEventAggregatorTest.deposit_created_log(from_block),\n             EthereumEventAggregatorTest.exit_started_log(to_block),\n             EthereumEventAggregatorTest.in_flight_exit_output_piggybacked_log(from_block),\n             EthereumEventAggregatorTest.in_flight_exit_input_piggybacked_log(to_block)\n           ]}\n        end\n\n        def get_call_data(_tx_hash) do\n          {:ok, EthereumEventAggregatorTest.start_standard_exit_log()}\n        end\n      end\n\n      :sys.replace_state(event_fetcher_name, fn state -> Map.put(state, :rpc, test_name) end)\n      from_block = 1\n      to_block = 1\n      deposit_created = from_block |> deposit_created_log() |> Abi.decode_log()\n\n      assert EthereumEventAggregator.deposit_created(event_fetcher_name, from_block, to_block) ==\n               {:ok, [deposit_created]}\n\n      exit_started_log =\n        to_block\n        |> exit_started_log()\n        |> Abi.decode_log()\n        |> Map.put(:call_data, start_standard_exit_log() |> from_hex |> Abi.decode_function())\n\n      assert EthereumEventAggregator.exit_started(event_fetcher_name, from_block, to_block) == {:ok, [exit_started_log]}\n\n      in_flight_exit_output_piggybacked_log = from_block |> in_flight_exit_output_piggybacked_log() |> Abi.decode_log()\n      in_flight_exit_input_piggybacked_log = to_block |> in_flight_exit_input_piggybacked_log() |> Abi.decode_log()\n\n      assert EthereumEventAggregator.in_flight_exit_piggybacked(event_fetcher_name, from_block, to_block) ==\n               {:ok, [in_flight_exit_input_piggybacked_log, in_flight_exit_output_piggybacked_log]}\n\n      events = event_fetcher_name |> :sys.get_state() |> Map.get(:events)\n\n      assert Enum.sort(:ets.tab2list(table)) ==\n               Enum.sort([\n                 {from_block, get_signature_from_event(events, :in_flight_exit_deleted), []},\n                 {from_block, get_signature_from_event(events, :deposit_created), [deposit_created]},\n                 {to_block, get_signature_from_event(events, :exit_started), [exit_started_log]},\n                 {from_block, get_signature_from_event(events, :in_flight_exit_output_piggybacked),\n                  [in_flight_exit_output_piggybacked_log]},\n                 {to_block, get_signature_from_event(events, :in_flight_exit_input_piggybacked),\n                  [in_flight_exit_input_piggybacked_log]},\n                 {to_block, get_signature_from_event(events, :in_flight_exit_started), []}\n               ])\n    end\n  end\n\n  describe \"get_logs/3\" do\n    test \"(watcher) that data and order (blknum) is preserved in returned data when we fetch deposits\", %{\n      event_fetcher_name: event_fetcher_name,\n      table: table,\n      test: test_name\n    } do\n      defmodule test_name do\n        alias OMG.Watcher.EthereumEventAggregatorTest\n\n        def get_ethereum_events(_from_block, _to_block, _signatures, _contracts) do\n          {:ok,\n           [\n             EthereumEventAggregatorTest.deposit_created_log(1),\n             EthereumEventAggregatorTest.deposit_created_log(2)\n           ]}\n        end\n\n        def get_call_data(_tx_hash) do\n          {:ok, EthereumEventAggregatorTest.start_standard_exit_log()}\n        end\n      end\n\n      from_block = 1\n      to_block = 3\n      # we get these events so that we're able to extract signatures\n      # where we construct custom data\n      events = event_fetcher_name |> :sys.get_state() |> Map.get(:events)\n\n      # create data that we need\n      # two deposits, one exit started and one in flight exit output piggybacked\n      # and one in flight exit input piggynacked\n      deposit_created = from_block |> deposit_created_log() |> Abi.decode_log()\n      deposit_created_2 = from_block |> Kernel.+(1) |> deposit_created_log() |> Abi.decode_log()\n\n      exit_started_log =\n        to_block\n        |> exit_started_log()\n        |> Abi.decode_log()\n        |> Map.put(:call_data, start_standard_exit_log() |> from_hex |> Abi.decode_function())\n\n      in_flight_exit_output_piggybacked_log = from_block |> in_flight_exit_output_piggybacked_log() |> Abi.decode_log()\n      in_flight_exit_input_piggybacked_log = to_block |> in_flight_exit_input_piggybacked_log() |> Abi.decode_log()\n      # we put the events into a list of events below\n      # some are empty, others get filled by the data we created above\n      # we just need to make sure, that the block number (from_block, from_block + 1, to_block)\n      # coincides with the event data\n      data = [\n        {from_block, get_signature_from_event(events, :deposit_created), [deposit_created]},\n        {from_block, get_signature_from_event(events, :in_flight_exit_output_piggybacked),\n         [in_flight_exit_output_piggybacked_log]},\n        {from_block, get_signature_from_event(events, :in_flight_exit_started), []},\n        {from_block, get_signature_from_event(events, :in_flight_exit_input_piggybacked),\n         [in_flight_exit_input_piggybacked_log]},\n        {from_block, get_signature_from_event(events, :exit_started), [exit_started_log]},\n        # this deposit will get called out below\n        {from_block + 1, get_signature_from_event(events, :deposit_created), [deposit_created_2]},\n        {from_block + 1, get_signature_from_event(events, :in_flight_exit_output_piggybacked), []},\n        {from_block + 1, get_signature_from_event(events, :in_flight_exit_started), []},\n        {from_block + 1, get_signature_from_event(events, :in_flight_exit_input_piggybacked), []},\n        {from_block + 1, get_signature_from_event(events, :exit_started), []},\n        {to_block, get_signature_from_event(events, :deposit_created), []},\n        {to_block, get_signature_from_event(events, :in_flight_exit_output_piggybacked), []},\n        {to_block, get_signature_from_event(events, :exit_started), [exit_started_log]},\n        {to_block, get_signature_from_event(events, :in_flight_exit_input_piggybacked),\n         [in_flight_exit_input_piggybacked_log]},\n        {to_block, get_signature_from_event(events, :in_flight_exit_started), []}\n      ]\n\n      # data gets inserted into the ETS table that the event aggregator us using\n      true = :ets.insert(table, data)\n      # we want the event aggregator to use our mocked RPC module\n      :sys.replace_state(event_fetcher_name, fn state -> Map.put(state, :rpc, test_name) end)\n      # we assert that if we pull deposits in the from_block and to_block range\n      # the deposits that we created above are returned in the correct order\n      # and that there's no more non *empty* deposits, only those that we defined\n      {:ok, data} = EthereumEventAggregator.deposit_created(event_fetcher_name, from_block, to_block)\n      assert Enum.at(data, 0) == deposit_created\n      assert Enum.at(data, 1) == deposit_created_2\n      # we defined two, so there shouldn't be any more!\n      assert Enum.at(data, 2) == nil\n    end\n  end\n\n  describe \"handle_call/3, forward_call/5\" do\n    test \"that APIs dont allow weird range (where from_block is bigger then to_block)\", %{\n      event_fetcher_name: event_fetcher_name\n    } do\n      from = 3\n      to = 1\n\n      assert capture_log(fn ->\n               assert EthereumEventAggregator.deposit_created(event_fetcher_name, from, to) == {:error, :check_range}\n             end) =~ \"[error]\"\n\n      assert capture_log(fn ->\n               assert EthereumEventAggregator.exit_started(event_fetcher_name, from, to) == {:error, :check_range}\n             end) =~ \"[error]\"\n\n      assert capture_log(fn ->\n               assert EthereumEventAggregator.exit_finalized(event_fetcher_name, from, to) == {:error, :check_range}\n             end) =~ \"[error]\"\n    end\n  end\n\n  # data that we extracted into helper functions\n  def deposit_created_log(block_number) do\n    %{\n      :event_signature => \"DepositCreated(address,uint256,address,uint256)\",\n      \"address\" => \"0x4e3aeff70f022a6d4cc5947423887e7152826cf7\",\n      \"blockHash\" => \"0xe5b0487de36b161f2d3e8c228ad4e1e84ab1ae25ca4d5ef53f9f03298ab3545f\",\n      \"blockNumber\" => \"0x\" <> Integer.to_string(block_number, 16),\n      \"data\" => \"0x000000000000000000000000000000000000000000000000000000000000000a\",\n      \"logIndex\" => \"0x0\",\n      \"removed\" => false,\n      \"topics\" => [\n        \"0x18569122d84f30025bb8dffb33563f1bdbfb9637f21552b11b8305686e9cb307\",\n        \"0x0000000000000000000000003b9f4c1dd26e0be593373b1d36cee2008cbeb837\",\n        \"0x0000000000000000000000000000000000000000000000000000000000000001\",\n        \"0x0000000000000000000000000000000000000000000000000000000000000000\"\n      ],\n      \"transactionHash\" => \"0x4d72a63ff42f1db50af2c36e8b314101d2fea3e0003575f30298e9153fe3d8ee\",\n      \"transactionIndex\" => \"0x0\"\n    }\n  end\n\n  def exit_started_log(block_number) do\n    %{\n      :event_signature => \"ExitStarted(address,uint160)\",\n      \"address\" => \"0x92ce4d7773c57d96210c46a07b89acf725057f21\",\n      \"blockHash\" => \"0x1bee6f75c74ceeb4817dc160e2fb56dd1337a9fc2980a2b013252cf1e620f246\",\n      \"blockNumber\" => \"0x\" <> Integer.to_string(block_number, 16),\n      \"data\" => \"0x000000000000000000000000002b191e750d8d4d3dcad14a9c8e5a5cf0c81761\",\n      \"logIndex\" => \"0x1\",\n      \"removed\" => false,\n      \"topics\" => [\n        \"0xdd6f755cba05d0a420007aef6afc05e4889ab424505e2e440ecd1c434ba7082e\",\n        \"0x00000000000000000000000008858124b3b880c68b360fd319cc61da27545e9a\"\n      ],\n      \"transactionHash\" => \"0x4a8248b88a17b2be4c6086a1984622de1a60dda3c9dd9ece1ef97ed18efa028c\",\n      \"transactionIndex\" => \"0x0\"\n    }\n  end\n\n  def in_flight_exit_output_piggybacked_log(block_number) do\n    %{\n      :event_signature => \"InFlightExitOutputPiggybacked(address,bytes32,uint16)\",\n      \"address\" => \"0x92ce4d7773c57d96210c46a07b89acf725057f21\",\n      \"blockHash\" => \"0x3e34475a29dafb28cd6deb65bc1782ccf6d73d6673d462a6d404ac0993d1e7eb\",\n      \"blockNumber\" => \"0x\" <> Integer.to_string(block_number, 16),\n      \"data\" => \"0x0000000000000000000000000000000000000000000000000000000000000001\",\n      \"logIndex\" => \"0x1\",\n      \"removed\" => false,\n      \"topics\" => [\n        \"0x6ecd8e79a5f67f6c12b54371ada2ffb41bc128c61d9ac1e969f0aa2aca46cd78\",\n        \"0x0000000000000000000000001513abcd3590a25e0bed840652d957391dde9955\",\n        \"0xff90b77303e56bd230a9adf4a6553a95f5ffb563486205d6fba25d3e46594940\"\n      ],\n      \"transactionHash\" => \"0x7cf43a6080e99677dee0b26c23e469b1df9cfb56a5c3f2a0123df6edae7b5b5e\",\n      \"transactionIndex\" => \"0x0\"\n    }\n  end\n\n  def in_flight_exit_input_piggybacked_log(block_number) do\n    %{\n      :event_signature => \"InFlightExitInputPiggybacked(address,bytes32,uint16)\",\n      \"address\" => \"0x92ce4d7773c57d96210c46a07b89acf725057f21\",\n      \"blockHash\" => \"0x6d95b14290cc2ac112f1560f2cd7aa0d747b91ec9cb1d47e11c205270d83c88c\",\n      \"blockNumber\" => \"0x\" <> Integer.to_string(block_number, 16),\n      \"data\" => \"0x0000000000000000000000000000000000000000000000000000000000000001\",\n      \"logIndex\" => \"0x0\",\n      \"removed\" => false,\n      \"topics\" => [\n        \"0xa93c0e9b202feaf554acf6ef1185b898c9f214da16e51740b06b5f7487b018e5\",\n        \"0x0000000000000000000000001513abcd3590a25e0bed840652d957391dde9955\",\n        \"0xff90b77303e56bd230a9adf4a6553a95f5ffb563486205d6fba25d3e46594940\"\n      ],\n      \"transactionHash\" => \"0x0cc9e5556bbd6eeaf4302f44adca215786ff08cfa44a34be1760eca60f97364f\",\n      \"transactionIndex\" => \"0x0\"\n    }\n  end\n\n  def in_flight_exit_deleted_log(block_number) do\n    %{\n      :event_signature => \"InFlightExitDeleted(uint160)\",\n      \"address\" => \"0x92ce4d7773c57d96210c46a07b89acf725057f21\",\n      \"blockHash\" => \"0xcafbc4b710c5fab8f3d719f65053637407231ecde31a859f1709e3478a2eda54\",\n      \"blockNumber\" => \"0x\" <> Integer.to_string(block_number, 16),\n      \"data\" => \"0x\",\n      \"logIndex\" => \"0x2\",\n      \"removed\" => false,\n      \"topics\" => [\n        \"0x1991c4c350498b0cc937c6a08bc5bdecf2e4fdd9d918052a880f102e43dbe45c\",\n        \"0x000000000000000000000000003fd275046f2823936fd97c1e3c8b225464d7f1\"\n      ],\n      \"transactionHash\" => \"0xbe310ade41278c5607620311b79363aa520ac46c7ba754bf3027d501c5a95f40\",\n      \"transactionIndex\" => \"0x0\"\n    }\n  end\n\n  def start_standard_exit_log() do\n    \"0x70e014620000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000001d1e4e4ea00000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000005df85b01c0f6f501f39408858124b3b880c68b360fd319cc61da27545e9a940000000000000000000000000000000000000000880de0b6b3a764000080a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200f39a869f62e75cf5f0bf914688a6b289caf2049435d8e68c5c5e6d05e44913f34ed5c02d6d48c8932486c99d3ad999e5d8949dc3be3b3058cc2979690c3e3a621c792b14bf66f82af36f00f5fba7014fa0c1e2ff3c7c273bfe523c1acf67dc3f5fa080a686a5a0d05c3d4822fd54d632dc9cc04b1616046eba2ce499eb9af79f5eb949690a0404abf4cebafc7cfffa382191b7dd9e7df778581e6fb78efab35fd364c9d5dadad4569b6dd47f7feabafa3571f842434425548335ac6e690dd07168d8bc5b77979c1a6702334f529f5783f79e942fd2cd03f6e55ac2cf496e849fde9c446fab46a8d27db1e3100f275a777d385b44e3cbc045cabac9da36cae040ad516082324c96127cf29f4535eb5b7ebacfe2a1d6d3aab8ec0483d32079a859ff70f9215970a8beebb1c164c474e82438174c8eeb6fbc8cb4594b88c9448f1d40b09beaecac5b45db6e41434a122b695c5a85862d8eae40b3268f6f37e414337be38eba7ab5bbf303d01f4b7ae07fd73edc2f3be05e43948a34418a3272509c43c2811a821e5c982ba51874ac7dc9dd79a80cc2f05f6f664c9dbb2e454435137da06ce44de45532a56a3a7007a2d0c6b435f726f95104bfa6e707046fc154bae91898d03a1a0ac6f9b45e471646e2555ac79e3fe87eb1781e26f20500240c379274fe91096e60d1545a8045571fdab9b530d0d6e7e8746e78bf9f20f4e86f06\"\n  end\n\n  defp from_hex(\"0x\" <> encoded), do: Base.decode16!(encoded, case: :lower)\n\n  defp get_signature_from_event(events, name) do\n    events\n    |> Enum.find(fn event -> Keyword.get(event, :name) == name end)\n    |> Keyword.get(:signature)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/ethereum_event_listener/core_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.Watcher.EthereumEventListener.CoreTest do\n  use ExUnitFixtures\n  use ExUnit.Case, async: true\n\n  alias OMG.Watcher.Configuration\n  alias OMG.Watcher.EthereumEventListener.Core\n  alias OMG.Watcher.RootChainCoordinator.SyncGuide\n\n  @db_key :db_key\n  @service_name :name\n  @request_max_size 5\n\n  test \"respects request_max_size argument\" do\n    create_state(0, request_max_size: 10)\n    |> Core.calc_events_range_set_height(%SyncGuide{sync_height: 20, root_chain_height: 10})\n    |> assert_range({1, 10})\n\n    create_state(0, request_max_size: 10)\n    |> Core.calc_events_range_set_height(%SyncGuide{sync_height: 11, root_chain_height: 10})\n    |> assert_range({1, 10})\n\n    create_state(0, request_max_size: 10)\n    |> Core.calc_events_range_set_height(%SyncGuide{sync_height: 10, root_chain_height: 10})\n    |> assert_range({1, 10})\n  end\n\n  test \"event range is capped at the SyncGuide sync_height\" do\n    # if request_max_size is taken into account it would\n    # push the event range above the threshold  and would remove the reorg protection\n    create_state(0, request_max_size: 2)\n    |> Core.calc_events_range_set_height(%SyncGuide{sync_height: 1, root_chain_height: 10})\n    |> assert_range({1, 1})\n  end\n\n  test \"get events range is capped at request_max_size and the events range returned is less then SyncGuide sync_height\" do\n    create_state(0, request_max_size: 2)\n    |> Core.calc_events_range_set_height(%SyncGuide{sync_height: 4, root_chain_height: 10})\n    |> assert_range({1, 2})\n  end\n\n  test \"works well close to zero\" do\n    0\n    |> create_state()\n    |> Core.calc_events_range_set_height(%SyncGuide{sync_height: 1, root_chain_height: 10})\n    |> assert_range({1, 1})\n    |> Core.calc_events_range_set_height(%SyncGuide{sync_height: 8, root_chain_height: 10})\n    |> assert_range({2, 6})\n\n    0\n    |> create_state()\n    |> Core.calc_events_range_set_height(%SyncGuide{sync_height: 9, root_chain_height: 10})\n    |> assert_range({1, 5})\n  end\n\n  test \"always returns correct height to check in\" do\n    state =\n      0\n      |> create_state()\n      |> Core.calc_events_range_set_height(%SyncGuide{sync_height: 1, root_chain_height: 10})\n      |> assert_range({1, 1})\n\n    assert state.synced_height == 1\n  end\n\n  test \"produces next ethereum height range to get events from\" do\n    0\n    |> create_state()\n    |> Core.calc_events_range_set_height(%SyncGuide{sync_height: 5, root_chain_height: 10})\n    |> assert_range({1, 5})\n    |> Core.calc_events_range_set_height(%SyncGuide{sync_height: 5, root_chain_height: 10})\n    |> assert_range(:dont_fetch_events)\n    |> Core.calc_events_range_set_height(%SyncGuide{sync_height: 7, root_chain_height: 10})\n    |> assert_range({6, 7})\n    |> Core.calc_events_range_set_height(%SyncGuide{sync_height: 7, root_chain_height: 10})\n    |> assert_range(:dont_fetch_events)\n  end\n\n  test \"if synced requested higher than root chain height\" do\n    # doesn't make too much sense, but still should work well\n    0\n    |> create_state()\n    |> Core.calc_events_range_set_height(%SyncGuide{sync_height: 5, root_chain_height: 5})\n    |> assert_range({1, 5})\n    |> Core.calc_events_range_set_height(%SyncGuide{sync_height: 7, root_chain_height: 5})\n    |> assert_range({6, 7})\n  end\n\n  test \"will be eager to get more events, even if none are pulled at first. All will be returned\" do\n    0\n    |> create_state()\n    |> Core.calc_events_range_set_height(%SyncGuide{sync_height: 2, root_chain_height: 2})\n    |> assert_range({1, 2})\n    |> Core.calc_events_range_set_height(%SyncGuide{sync_height: 4, root_chain_height: 4})\n    |> assert_range({3, 4})\n  end\n\n  test \"restart allows to continue with proper bounds\" do\n    1\n    |> create_state()\n    |> Core.calc_events_range_set_height(%SyncGuide{sync_height: 4, root_chain_height: 10})\n    |> assert_range({2, 4})\n    |> Core.calc_events_range_set_height(%SyncGuide{sync_height: 4, root_chain_height: 10})\n    |> assert_range(:dont_fetch_events)\n    |> Core.calc_events_range_set_height(%SyncGuide{sync_height: 5, root_chain_height: 10})\n    |> assert_range({5, 5})\n\n    3\n    |> create_state()\n    |> Core.calc_events_range_set_height(%SyncGuide{sync_height: 3, root_chain_height: 10})\n    |> assert_range(:dont_fetch_events)\n    |> Core.calc_events_range_set_height(%SyncGuide{sync_height: 5, root_chain_height: 10})\n    |> assert_range({4, 5})\n\n    3\n    |> create_state()\n    |> Core.calc_events_range_set_height(%SyncGuide{sync_height: 7, root_chain_height: 10})\n    |> assert_range({4, 7})\n  end\n\n  test \"wont move over if not allowed by sync_height\" do\n    5\n    |> create_state()\n    |> Core.calc_events_range_set_height(%SyncGuide{sync_height: 6, root_chain_height: 10})\n    |> assert_range({6, 6})\n  end\n\n  test \"can get an empty events list when events too fresh\" do\n    4\n    |> create_state()\n    |> Core.calc_events_range_set_height(%SyncGuide{sync_height: 6, root_chain_height: 10})\n    |> assert_range({5, 6})\n  end\n\n  test \"persists/checks in eth_height without margins substracted, and never goes negative\" do\n    create_state(0, request_max_size: 10)\n    |> Core.calc_events_range_set_height(%SyncGuide{sync_height: 6, root_chain_height: 10})\n    |> assert_range({1, 6})\n  end\n\n  test \"tolerates being asked to sync on height already synced\" do\n    create_state(5)\n    |> Core.calc_events_range_set_height(%SyncGuide{sync_height: 1, root_chain_height: 10})\n    |> assert_range(:dont_fetch_events)\n  end\n\n  defp create_state(height, opts \\\\ []) do\n    request_max_size = Keyword.get(opts, :request_max_size, @request_max_size)\n    # this assert is meaningful - currently we want to explicitly check_in the height read from DB\n    assert {state, ^height} =\n             Core.init(\n               @db_key,\n               @service_name,\n               height,\n               Configuration.ethereum_events_check_interval_ms(),\n               request_max_size\n             )\n\n    state\n  end\n\n  defp assert_range({range, state}, expect) do\n    assert range == expect\n    state\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/exit_processor/canonicity_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.ExitProcessor.CanonicityTest do\n  @moduledoc \"\"\"\n  Test of the logic of exit processor - detecting conditions related to canonicity game and challenging them:\n    - competitors\n    - invalid competitors\n  \"\"\"\n  use OMG.Watcher.ExitProcessor.Case, async: true\n\n  alias OMG.Watcher.Block\n  alias OMG.Watcher.Event\n  alias OMG.Watcher.ExitProcessor\n  alias OMG.Watcher.ExitProcessor.Core\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.TestHelper\n  alias OMG.Watcher.Utxo\n\n  require Utxo\n\n  import OMG.Watcher.ExitProcessor.TestHelper\n\n  @eth <<0::160>>\n  @late_blknum 10_000\n\n  describe \"sanity checks\" do\n    test \"can process empty challenges and responses\", %{processor_empty: empty, processor_filled: filled} do\n      {^empty, []} = Core.new_ife_challenges(empty, [])\n      {^filled, []} = Core.new_ife_challenges(filled, [])\n      {^empty, []} = Core.respond_to_in_flight_exits_challenges(empty, [])\n      {^filled, []} = Core.respond_to_in_flight_exits_challenges(filled, [])\n    end\n  end\n\n  describe \"finds competitors and allows canonicity challenges\" do\n    test \"none if input never spent elsewhere\",\n         %{processor_filled: processor} do\n      assert {:ok, []} =\n               %ExitProcessor.Request{blknum_now: 1000, eth_height_now: 5}\n               |> check_validity_filtered(processor, exclude: [Event.PiggybackAvailable])\n    end\n\n    test \"none if different input spent in some tx from appendix\",\n         %{processor_filled: processor, transactions: [tx1 | _], unrelated_tx: comp} do\n      txbytes = txbytes(tx1)\n      processor = processor |> start_ife_from(comp)\n\n      assert {:ok, []} =\n               %ExitProcessor.Request{blknum_now: 1000, eth_height_now: 5}\n               |> check_validity_filtered(processor, exclude: [Event.PiggybackAvailable])\n\n      assert {:error, :competitor_not_found} =\n               %ExitProcessor.Request{blknum_now: 5000, eth_height_now: 5}\n               |> Core.get_competitor_for_ife(processor, txbytes)\n    end\n\n    test \"none if different input spent in some tx from block\",\n         %{processor_filled: processor, transactions: [tx1 | _], unrelated_tx: comp} do\n      txbytes = txbytes(tx1)\n\n      exit_processor_request = %ExitProcessor.Request{\n        blknum_now: 5000,\n        eth_height_now: 5,\n        blocks_result: [Block.hashed_txs_at([comp], 3000)]\n      }\n\n      assert {:ok, []} =\n               exit_processor_request |> check_validity_filtered(processor, exclude: [Event.PiggybackAvailable])\n\n      assert {:error, :competitor_not_found} = exit_processor_request |> Core.get_competitor_for_ife(processor, txbytes)\n    end\n\n    test \"none if input spent in _same_ tx in block\",\n         %{processor_filled: processor, transactions: [tx1 | _]} do\n      txbytes = txbytes(tx1)\n\n      exit_processor_request = %ExitProcessor.Request{\n        blknum_now: 5000,\n        eth_height_now: 5,\n        blocks_result: [Block.hashed_txs_at([tx1], 3000)]\n      }\n\n      assert {:ok, []} =\n               exit_processor_request |> check_validity_filtered(processor, exclude: [Event.PiggybackAvailable])\n\n      assert {:error, :competitor_not_found} =\n               exit_processor_request\n               |> Core.get_competitor_for_ife(processor, txbytes)\n    end\n\n    test \"none if input spent in _same_ tx in tx appendix\",\n         %{processor_filled: processor, transactions: [tx | _]} do\n      txbytes = txbytes(tx)\n      processor = processor |> start_ife_from(tx)\n\n      assert {:ok, []} =\n               %ExitProcessor.Request{blknum_now: 5000, eth_height_now: 5}\n               |> check_validity_filtered(processor, exclude: [Event.PiggybackAvailable])\n\n      assert {:error, :competitor_not_found} =\n               %ExitProcessor.Request{blknum_now: 5000, eth_height_now: 5}\n               |> Core.get_competitor_for_ife(processor, txbytes)\n    end\n\n    test \"each other, if input spent in different ife\",\n         %{processor_filled: processor, transactions: [tx1 | _], competing_tx: comp} do\n      txbytes = txbytes(tx1)\n      {comp_txbytes, comp_signature} = {txbytes(comp), sig(comp)}\n      processor = processor |> start_ife_from(comp)\n\n      assert {:ok, events} =\n               %ExitProcessor.Request{blknum_now: 5000, eth_height_now: 5}\n               |> check_validity_filtered(processor, only: [Event.NonCanonicalIFE])\n\n      assert_events(events, [%Event.NonCanonicalIFE{txbytes: txbytes}, %Event.NonCanonicalIFE{txbytes: comp_txbytes}])\n\n      assert {:ok,\n              %{\n                in_flight_txbytes: ^txbytes,\n                in_flight_input_index: 0,\n                competing_txbytes: ^comp_txbytes,\n                competing_input_index: 1,\n                competing_sig: ^comp_signature,\n                competing_tx_pos: Utxo.position(0, 0, 0),\n                competing_proof: \"\"\n              }} =\n               %ExitProcessor.Request{blknum_now: 5000, eth_height_now: 5}\n               |> Core.get_competitor_for_ife(processor, txbytes)\n    end\n\n    test \"a competitor that's submitted as challenge to other IFE\",\n         %{alice: alice, processor_filled: processor, transactions: [tx1, tx2 | _]} do\n      # ifes in processor here aren't competitors to each other, but the challenge filed for tx2 is a competitor\n      # for tx1, which is what we want to detect:\n      comp = TestHelper.create_recovered([{1, 0, 0, alice}, {2, 1, 0, alice}], [{alice, @eth, 1}])\n      {comp_txbytes, comp_signature} = {txbytes(comp), sig(comp)}\n      txbytes = Transaction.raw_txbytes(tx1)\n      challenge_event = ife_challenge(tx2, comp)\n      {processor, _} = Core.new_ife_challenges(processor, [challenge_event])\n\n      exit_processor_request = %ExitProcessor.Request{blknum_now: 5000, eth_height_now: 5}\n\n      assert {:ok, [%Event.NonCanonicalIFE{txbytes: ^txbytes}]} =\n               exit_processor_request |> check_validity_filtered(processor, only: [Event.NonCanonicalIFE])\n\n      assert {:ok,\n              %{\n                in_flight_txbytes: ^txbytes,\n                competing_txbytes: ^comp_txbytes,\n                competing_input_index: 0,\n                competing_sig: ^comp_signature\n              }} = exit_processor_request |> Core.get_competitor_for_ife(processor, txbytes)\n    end\n\n    test \"a single competitor included in a block, with proof\",\n         %{processor_filled: processor, transactions: [tx1 | _], competing_tx: comp} do\n      txbytes = txbytes(tx1)\n      {comp_txbytes, comp_signature} = {txbytes(comp), sig(comp)}\n\n      other_blknum = 3000\n\n      exit_processor_request = %ExitProcessor.Request{\n        blknum_now: 5000,\n        eth_height_now: 5,\n        blocks_result: [Block.hashed_txs_at([comp], other_blknum)]\n      }\n\n      assert {:ok, [%Event.NonCanonicalIFE{txbytes: ^txbytes}]} =\n               exit_processor_request\n               |> check_validity_filtered(processor, only: [Event.NonCanonicalIFE])\n\n      assert {:ok,\n              %{\n                in_flight_txbytes: ^txbytes,\n                in_flight_input_index: 0,\n                competing_txbytes: ^comp_txbytes,\n                competing_input_index: 1,\n                competing_sig: ^comp_signature,\n                competing_tx_pos: Utxo.position(^other_blknum, 0, 0),\n                competing_proof: proof_bytes\n              }} =\n               exit_processor_request\n               |> Core.get_competitor_for_ife(processor, txbytes)\n\n      assert_proof_sound(proof_bytes)\n    end\n\n    test \"handle two competitors, when the younger one already challenged\",\n         %{processor_filled: processor, transactions: [tx1 | _], competing_tx: comp} do\n      txbytes = txbytes(tx1)\n      comp_txbytes = txbytes(comp)\n      other_blknum = 3000\n\n      request = %ExitProcessor.Request{\n        blknum_now: 5000,\n        eth_height_now: 5,\n        blocks_result: [Block.hashed_txs_at([comp, comp], other_blknum)],\n        ife_input_spending_blocks_result: [Block.hashed_txs_at([comp, comp], other_blknum)]\n      }\n\n      # the transaction is firstmost submitted as a competitor, plus we run the preliminary lookup\n      processor = processor |> start_ife_from(comp) |> Core.find_ifes_in_blocks(request)\n\n      # after the first, intermediate challenges, there should still be that event active\n      assert_intermediate_result = fn processor ->\n        assert {:ok, [%Event.NonCanonicalIFE{txbytes: ^txbytes}]} =\n                 request |> check_validity_filtered(processor, only: [Event.NonCanonicalIFE])\n\n        # this request always returns the oldest competitor, even if we later use a different one\n        assert {:ok, %{competing_txbytes: ^comp_txbytes, competing_tx_pos: Utxo.position(^other_blknum, 0, 0)}} =\n                 request |> Core.get_competitor_for_ife(processor, txbytes)\n      end\n\n      # sanity check - no challenges yet\n      assert_intermediate_result.(processor)\n\n      # now `comp` is used to challenge with no inclusion proof: challenge with IFE (no position, incomplete)\n      challenge = ife_challenge(tx1, comp)\n      {processor, _} = Core.new_ife_challenges(processor, [challenge])\n      assert_intermediate_result.(processor)\n\n      # challenge with the younger competitor (still incomplete challenge)\n      young_challenge = ife_challenge(tx1, comp, competitor_position: Utxo.position(other_blknum, 1, 0))\n      {processor, _} = Core.new_ife_challenges(processor, [young_challenge])\n      assert_intermediate_result.(processor)\n\n      # challenge with the older competitor (complete!)\n      older_challenge = ife_challenge(tx1, comp, competitor_position: Utxo.position(other_blknum, 0, 0))\n      {processor, _} = Core.new_ife_challenges(processor, [older_challenge])\n      # the tx1 IFE got challenged by the oldest competitor now; finally, it's over:\n      assert {:ok, []} = request |> check_validity_filtered(processor, only: [Event.NonCanonicalIFE])\n      assert {:error, :no_viable_competitor_found} = request |> Core.get_competitor_for_ife(processor, txbytes)\n    end\n\n    test \"handle two competitors, when both are non canonical and used to challenge\",\n         %{alice: alice, processor_filled: processor, transactions: [tx1 | _]} do\n      comp1 = TestHelper.create_recovered([{1, 0, 0, alice}], [{alice, @eth, 1}])\n      comp2 = TestHelper.create_recovered([{1, 0, 0, alice}], [{alice, @eth, 2}])\n      txbytes = txbytes(tx1)\n      request = %ExitProcessor.Request{blknum_now: 5000, eth_height_now: 5}\n      processor = processor |> start_ife_from(comp1) |> start_ife_from(comp2)\n\n      # before any challenge\n      assert {:ok, [_, _, _]} = request |> check_validity_filtered(processor, only: [Event.NonCanonicalIFE])\n\n      assert {:ok, %{competing_tx_pos: Utxo.position(0, 0, 0)}} =\n               request |> Core.get_competitor_for_ife(processor, txbytes)\n\n      # after challenge - one event less + no need to challenge more\n      {processor, _} = Core.new_ife_challenges(processor, [ife_challenge(tx1, comp1)])\n      assert {:ok, [_, _]} = request |> check_validity_filtered(processor, only: [Event.NonCanonicalIFE])\n      assert {:error, :no_viable_competitor_found} = request |> Core.get_competitor_for_ife(processor, txbytes)\n    end\n\n    test \"don't show competitors, if IFE tx is included\",\n         %{processor_filled: processor, transactions: [tx1 | _], competing_tx: comp} do\n      txbytes = txbytes(tx1)\n      comp_txbytes = txbytes(comp)\n      other_blknum = 3000\n\n      request = %ExitProcessor.Request{\n        blknum_now: 5000,\n        eth_height_now: 5,\n        ife_input_spending_blocks_result: [Block.hashed_txs_at([tx1], other_blknum)]\n      }\n\n      processor = processor |> start_ife_from(comp) |> Core.find_ifes_in_blocks(request)\n\n      # notice this is `comp` having a competitor reported, not `tx1`\n      assert {:ok, [%Event.NonCanonicalIFE{txbytes: ^comp_txbytes}]} =\n               request |> check_validity_filtered(processor, only: [Event.NonCanonicalIFE])\n\n      assert {:error, :no_viable_competitor_found} = request |> Core.get_competitor_for_ife(processor, txbytes)\n    end\n\n    test \"don't show competitors, if IFE tx is included and is the oldest\",\n         %{processor_filled: processor, transactions: [tx1 | _], competing_tx: comp} do\n      txbytes = txbytes(tx1)\n      other_blknum = 3000\n\n      request = %ExitProcessor.Request{\n        blknum_now: 5000,\n        eth_height_now: 5,\n        blocks_result: [Block.hashed_txs_at([tx1, comp], other_blknum)],\n        ife_input_spending_blocks_result: [Block.hashed_txs_at([tx1, comp], other_blknum)]\n      }\n\n      processor = processor |> Core.find_ifes_in_blocks(request)\n      # notice this is `comp` having a competitor reported, not `tx1`\n      assert {:ok, []} = request |> check_validity_filtered(processor, only: [Event.NonCanonicalIFE])\n      assert {:error, :no_viable_competitor_found} = request |> Core.get_competitor_for_ife(processor, txbytes)\n    end\n\n    test \"show competitors, if IFE tx is included but not the oldest\",\n         %{processor_filled: processor, transactions: [tx1 | _], competing_tx: comp} do\n      txbytes = txbytes(tx1)\n      other_blknum = 3000\n\n      request = %ExitProcessor.Request{\n        blknum_now: 5000,\n        eth_height_now: 5,\n        blocks_result: [Block.hashed_txs_at([comp, tx1], other_blknum)],\n        ife_input_spending_blocks_result: [Block.hashed_txs_at([comp, tx1], other_blknum)]\n      }\n\n      processor = processor |> Core.find_ifes_in_blocks(request)\n      # notice this is `comp` having a competitor reported, not `tx1`\n      assert {:ok, [%Event.NonCanonicalIFE{txbytes: ^txbytes}]} =\n               request |> check_validity_filtered(processor, only: [Event.NonCanonicalIFE])\n\n      assert {:ok, %{}} = request |> Core.get_competitor_for_ife(processor, txbytes)\n    end\n\n    test \"detects that non-canonical ife becomes unchallenged exit when sla period passes\",\n         %{processor_filled: processor, transactions: [tx1 | _], competing_tx: comp} do\n      txbytes = txbytes(tx1)\n      other_blknum = 3000\n\n      request = %ExitProcessor.Request{\n        blknum_now: 5000,\n        eth_height_now: 5 + processor.sla_margin,\n        blocks_result: [Block.hashed_txs_at([comp, tx1], other_blknum)],\n        ife_input_spending_blocks_result: [Block.hashed_txs_at([comp, tx1], other_blknum)]\n      }\n\n      processor = processor |> Core.find_ifes_in_blocks(request)\n\n      assert {{:error, :unchallenged_exit}, [%Event.UnchallengedNonCanonicalIFE{txbytes: ^txbytes}]} =\n               request |> check_validity_filtered(processor, only: [Event.UnchallengedNonCanonicalIFE])\n    end\n\n    test \"show competitors, if IFE tx is included but not the oldest - distinct blocks\",\n         %{processor_filled: processor, transactions: [tx1 | _], competing_tx: comp} do\n      txbytes = txbytes(tx1)\n      block1 = Block.hashed_txs_at([comp], 3000)\n      block2 = Block.hashed_txs_at([tx1], 4000)\n\n      request = %ExitProcessor.Request{\n        blknum_now: 5000,\n        eth_height_now: 5,\n        blocks_result: [block1, block2],\n        # note the flipped order here, all still works as the blocks should be processed starting from oldest\n        ife_input_spending_blocks_result: [block2, block1]\n      }\n\n      processor = processor |> Core.find_ifes_in_blocks(request)\n      # notice this is `comp` having a competitor reported, not `tx1`\n      assert {:ok, [%Event.NonCanonicalIFE{txbytes: ^txbytes}]} =\n               request |> check_validity_filtered(processor, only: [Event.NonCanonicalIFE])\n\n      assert {:ok, %{}} = request |> Core.get_competitor_for_ife(processor, txbytes)\n    end\n\n    test \"none if IFE is challenged enough already\",\n         %{processor_filled: processor, transactions: [tx1 | _], competing_tx: comp} do\n      txbytes = txbytes(tx1)\n      other_blknum = 3000\n\n      request = %ExitProcessor.Request{\n        blknum_now: 5000,\n        eth_height_now: 5,\n        blocks_result: [Block.hashed_txs_at([comp], other_blknum)]\n      }\n\n      challenge =\n        ife_challenge(tx1, comp, competitor_position: Utxo.position(other_blknum, 0, 0), competing_tx_input_index: 1)\n\n      {processor, _} = Core.new_ife_challenges(processor, [challenge])\n\n      assert {:ok, []} = request |> check_validity_filtered(processor, only: [Event.NonCanonicalIFE])\n      assert {:error, :no_viable_competitor_found} = request |> Core.get_competitor_for_ife(processor, txbytes)\n    end\n\n    test \"a competitor having the double-spend on various input indices\",\n         %{alice: alice, processor_empty: processor} do\n      tx = TestHelper.create_recovered([{1, 0, 0, alice}, {1, 2, 1, alice}], [{alice, @eth, 1}])\n      txbytes = txbytes(tx)\n      processor = processor |> start_ife_from(tx)\n\n      input_spent_in_idx0 = {1, 0, 0}\n      input_spent_in_idx1 = {1, 2, 1}\n      other_input1 = {110, 2, 1}\n      other_input2 = {111, 2, 1}\n      other_input3 = {112, 2, 1}\n\n      comps = [\n        Transaction.Payment.new([input_spent_in_idx0], [{alice.addr, @eth, 1}]),\n        Transaction.Payment.new([other_input1, input_spent_in_idx0], [{alice.addr, @eth, 1}]),\n        Transaction.Payment.new([other_input1, other_input2, input_spent_in_idx0], [{alice.addr, @eth, 1}]),\n        Transaction.Payment.new([other_input1, other_input2, other_input3, input_spent_in_idx0], [{alice.addr, @eth, 1}]),\n        Transaction.Payment.new([input_spent_in_idx1], [{alice.addr, @eth, 1}]),\n        Transaction.Payment.new([other_input1, input_spent_in_idx1], [{alice.addr, @eth, 1}]),\n        Transaction.Payment.new([other_input1, other_input2, input_spent_in_idx1], [{alice.addr, @eth, 1}]),\n        Transaction.Payment.new([other_input1, other_input2, other_input3, input_spent_in_idx1], [{alice.addr, @eth, 1}])\n      ]\n\n      expected_input_ids = [{0, 0}, {1, 0}, {2, 0}, {3, 0}, {0, 1}, {1, 1}, {2, 1}, {3, 1}]\n\n      check = fn {comp, {competing_input_index, in_flight_input_index}} ->\n        # unfortunately, transaction validity requires us to duplicate a signature for every non-zero input\n        required_priv_key_list =\n          comp\n          |> Transaction.get_inputs()\n          |> Enum.count()\n          |> (&List.duplicate(alice.priv, &1)).()\n\n        other_recovered = TestHelper.sign_recover!(comp, required_priv_key_list)\n\n        exit_processor_request = %ExitProcessor.Request{\n          blknum_now: 5000,\n          eth_height_now: 5,\n          blocks_result: [Block.hashed_txs_at([other_recovered], 3000)]\n        }\n\n        assert {:ok, [%Event.NonCanonicalIFE{txbytes: ^txbytes}]} =\n                 exit_processor_request |> check_validity_filtered(processor, only: [Event.NonCanonicalIFE])\n\n        assert {:ok,\n                %{\n                  in_flight_input_index: ^in_flight_input_index,\n                  competing_input_index: ^competing_input_index\n                }} =\n                 exit_processor_request\n                 |> Core.get_competitor_for_ife(processor, txbytes)\n      end\n\n      comps\n      |> Enum.zip(expected_input_ids)\n      |> Enum.each(check)\n    end\n\n    test \"a competitor being signed on various positions\",\n         %{processor_filled: processor, transactions: [tx1 | _], alice: alice, bob: bob} do\n      comp = TestHelper.create_recovered([{10, 2, 1, bob}, {1, 0, 0, alice}], [{alice, @eth, 1}])\n      comp_signature = sig(comp, 1)\n\n      exit_processor_request = %ExitProcessor.Request{\n        blknum_now: 5000,\n        eth_height_now: 5,\n        blocks_result: [Block.hashed_txs_at([comp], 3000)]\n      }\n\n      assert {:ok, %{competing_sig: ^comp_signature}} =\n               exit_processor_request |> Core.get_competitor_for_ife(processor, txbytes(tx1))\n    end\n\n    test \"a best competitor, included earliest in a block, regardless of conflicting utxo position\",\n         %{alice: alice, processor_filled: processor, transactions: [tx1 | _], competing_tx: comp} do\n      # NOTE that the recent competitor spends an __older__ input. Also note the reversing of block results done below\n      #      Regardless of these, the best competitor (from blknum 2000) must always be returned\n      # NOTE also that non-included competitors always are considered last, and hence worst and never are returned\n\n      # first the included competitors\n      recovered_recent = TestHelper.create_recovered([{1, 0, 0, alice}], [{alice, @eth, 1}])\n      recovered_oldest = TestHelper.create_recovered([{1, 0, 0, alice}, {2, 2, 1, alice}], [{alice, @eth, 1}])\n\n      # ife-related competitor\n      processor = processor |> start_ife_from(comp)\n\n      exit_processor_request = %ExitProcessor.Request{\n        blknum_now: 5000,\n        eth_height_now: 5,\n        blocks_result: [Block.hashed_txs_at([recovered_oldest], 2000), Block.hashed_txs_at([recovered_recent], 3000)]\n      }\n\n      txbytes = txbytes(tx1)\n\n      assert {:ok, %{competing_tx_pos: Utxo.position(2000, 0, 0)}} =\n               exit_processor_request\n               |> Core.get_competitor_for_ife(processor, txbytes)\n\n      assert {:ok, %{competing_tx_pos: Utxo.position(2000, 0, 0)}} =\n               exit_processor_request\n               |> Map.update!(:blocks_result, &Enum.reverse/1)\n               |> struct!()\n               |> Core.get_competitor_for_ife(processor, txbytes)\n\n      # check also that the rule applies to order of txs within a block\n      assert {:ok, %{competing_tx_pos: Utxo.position(2000, 0, 0)}} =\n               exit_processor_request\n               |> Map.put(:blocks_result, [Block.hashed_txs_at([recovered_oldest, recovered_recent], 2000)])\n               |> struct!()\n               |> Core.get_competitor_for_ife(processor, txbytes)\n    end\n\n    test \"by asking for utxo existence concerning active ifes and standard exits\",\n         %{processor_empty: processor, alice: alice} do\n      standard_exit_tx = TestHelper.create_recovered([{1000, 0, 0, alice}], @eth, [{alice, 10}, {alice, 10}])\n      ife_tx = TestHelper.create_recovered([{1, 0, 0, alice}], @eth, [{alice, 10}])\n      standard_exiting_pos = Utxo.position(2_000, 0, 1)\n      processor = processor |> start_se_from(standard_exit_tx, standard_exiting_pos) |> start_ife_from(ife_tx)\n\n      assert %{utxos_to_check: [Utxo.position(1, 0, 0), _standard_exiting_pos]} =\n               %ExitProcessor.Request{blknum_now: @late_blknum}\n               |> Core.determine_utxo_existence_to_get(processor)\n    end\n\n    test \"by asking for utxo spends concerning active ifes\",\n         %{processor_filled: processor} do\n      assert %{spends_to_get: [Utxo.position(1, 2, 1)]} =\n               %ExitProcessor.Request{\n                 utxos_to_check: [Utxo.position(1, 2, 1), Utxo.position(112, 2, 1)],\n                 utxo_exists_result: [false, false]\n               }\n               |> Core.determine_spends_to_get(processor)\n    end\n\n    test \"by not asking for utxo spends concerning non-active ifes\",\n         %{processor_empty: processor, transactions: [tx | _]} do\n      processor = processor |> start_ife_from(tx, status: :inactive)\n\n      assert %{utxos_to_check: []} =\n               %ExitProcessor.Request{blknum_now: @late_blknum} |> Core.determine_utxo_existence_to_get(processor)\n    end\n\n    test \"by not asking for utxo existence concerning finalized ifes\",\n         %{processor_empty: processor, transactions: [tx | _]} do\n      tx_hash = Transaction.raw_txhash(tx)\n      ife_id = 123\n\n      processor =\n        processor\n        |> start_ife_from(tx, exit_id: ife_id)\n        |> piggyback_ife_from(tx_hash, 1, :input)\n        |> piggyback_ife_from(tx_hash, 2, :input)\n\n      finalizations = [\n        %{in_flight_exit_id: ife_id, output_index: 1, omg_data: %{piggyback_type: :input}},\n        %{in_flight_exit_id: ife_id, output_index: 2, omg_data: %{piggyback_type: :input}}\n      ]\n\n      {:ok, processor, _} = Core.finalize_in_flight_exits(processor, finalizations, %{})\n\n      assert %{utxos_to_check: []} =\n               %ExitProcessor.Request{blknum_now: @late_blknum} |> Core.determine_utxo_existence_to_get(processor)\n    end\n\n    test \"returns input txs and input utxo positions for canonicity challenges\",\n         %{processor_filled: processor, transactions: [tx | _], competing_tx: comp} do\n      txbytes = txbytes(tx)\n      processor = processor |> start_ife_from(comp)\n\n      request = %ExitProcessor.Request{blknum_now: 1000, eth_height_now: 5}\n\n      assert {:ok, %{input_tx: \"input_tx\", input_utxo_pos: Utxo.position(1, 0, 0)}} =\n               Core.get_competitor_for_ife(request, processor, txbytes)\n    end\n\n    test \"by not asking for spends on no ifes\",\n         %{processor_empty: processor} do\n      assert %{spends_to_get: []} =\n               %ExitProcessor.Request{utxos_to_check: [Utxo.position(1, 0, 0)], utxo_exists_result: [false]}\n               |> Core.determine_spends_to_get(processor)\n    end\n\n    test \"none if input not yet created during sync\",\n         %{processor_filled: processor} do\n      assert %{utxos_to_check: to_check} =\n               %ExitProcessor.Request{blknum_now: 1000, eth_height_now: 13}\n               |> Core.determine_utxo_existence_to_get(processor)\n\n      assert Utxo.position(9000, 0, 1) not in to_check\n    end\n\n    test \"for nonexistent tx doesn't crash\",\n         %{transactions: [tx | _], processor_empty: processor} do\n      txbytes = Transaction.raw_txbytes(tx)\n\n      assert {:error, :ife_not_known_for_tx} =\n               %ExitProcessor.Request{blknum_now: 5000, eth_height_now: 5}\n               |> Core.get_competitor_for_ife(processor, txbytes)\n    end\n\n    test \"for malformed input txbytes doesn't crash\",\n         %{processor_empty: processor} do\n      assert {:error, :malformed_transaction} =\n               %ExitProcessor.Request{blknum_now: 5000, eth_height_now: 5}\n               |> Core.get_competitor_for_ife(processor, <<0>>)\n    end\n  end\n\n  describe \"detects the need and allows to respond to canonicity challenges\" do\n    test \"against a competitor\",\n         %{processor_filled: processor, transactions: [tx1 | _] = txs, competing_tx: comp} do\n      {challenged_processor, _} = Core.new_ife_challenges(processor, [ife_challenge(tx1, comp)])\n      txbytes = Transaction.raw_txbytes(tx1)\n      other_blknum = 3000\n\n      request = %ExitProcessor.Request{\n        blknum_now: 5000,\n        eth_height_now: 5,\n        ife_input_spending_blocks_result: [Block.hashed_txs_at(txs, other_blknum)]\n      }\n\n      challenged_processor = challenged_processor |> Core.find_ifes_in_blocks(request)\n\n      assert {:ok, [%Event.InvalidIFEChallenge{txbytes: ^txbytes}]} =\n               request |> check_validity_filtered(challenged_processor, only: [Event.InvalidIFEChallenge])\n\n      assert {:ok,\n              %{\n                in_flight_txbytes: ^txbytes,\n                in_flight_tx_pos: Utxo.position(^other_blknum, 0, 0),\n                in_flight_proof: proof_bytes\n              }} = Core.prove_canonical_for_ife(challenged_processor, txbytes)\n\n      assert_proof_sound(proof_bytes)\n    end\n\n    test \"proving canonical for nonexistent tx doesn't crash\", %{processor_empty: processor, transactions: [tx | _]} do\n      txbytes = Transaction.raw_txbytes(tx)\n      request = %ExitProcessor.Request{blknum_now: 5000, eth_height_now: 5}\n      processor = processor |> Core.find_ifes_in_blocks(request)\n      assert {:error, :ife_not_known_for_tx} = Core.prove_canonical_for_ife(processor, txbytes)\n    end\n\n    test \"for malformed input txbytes doesn't crash\", %{processor_empty: processor} do\n      request = %ExitProcessor.Request{blknum_now: 5000, eth_height_now: 5}\n      processor = processor |> Core.find_ifes_in_blocks(request)\n      assert {:error, :malformed_transaction} = Core.prove_canonical_for_ife(processor, <<0>>)\n    end\n\n    test \"none if ifes are fresh and canonical by default\", %{processor_filled: processor} do\n      assert {:ok, []} =\n               %ExitProcessor.Request{blknum_now: 5000, eth_height_now: 5}\n               |> check_validity_filtered(processor, exclude: [Event.PiggybackAvailable])\n    end\n\n    test \"none if challenge gets responded and ife canonical\",\n         %{processor_filled: processor, transactions: [tx | _] = txs, competing_tx: comp} do\n      txbytes = Transaction.raw_txbytes(tx)\n      other_blknum = 3000\n      {processor, _} = Core.new_ife_challenges(processor, [ife_challenge(tx, comp)])\n\n      {processor, _} =\n        processor\n        |> Core.respond_to_in_flight_exits_challenges([ife_response(tx, Utxo.position(other_blknum, 0, 0))])\n\n      request = %ExitProcessor.Request{\n        blknum_now: 5000,\n        eth_height_now: 5,\n        ife_input_spending_blocks_result: [Block.hashed_txs_at(txs, other_blknum)]\n      }\n\n      processor = processor |> Core.find_ifes_in_blocks(request)\n      assert {:ok, []} = request |> check_validity_filtered(processor, only: [Event.InvalidIFEChallenge])\n      assert {:error, :no_viable_canonical_proof_found} = Core.prove_canonical_for_ife(processor, txbytes)\n    end\n\n    test \"when there are two transaction inclusions to respond with\",\n         %{processor_filled: processor, transactions: [tx | _], competing_tx: comp} do\n      txbytes = Transaction.raw_txbytes(tx)\n      other_blknum = 3000\n      {processor, _} = Core.new_ife_challenges(processor, [ife_challenge(tx, comp)])\n\n      request = %ExitProcessor.Request{\n        blknum_now: 5000,\n        eth_height_now: 5,\n        # NOTE: `tx` is included twice\n        ife_input_spending_blocks_result: [Block.hashed_txs_at([tx, tx], other_blknum)]\n      }\n\n      processor = processor |> Core.find_ifes_in_blocks(request)\n\n      assert {:ok, [%Event.InvalidIFEChallenge{txbytes: ^txbytes}]} =\n               request |> check_validity_filtered(processor, only: [Event.InvalidIFEChallenge])\n\n      # older is returned but we'll respond with the younger first and then older\n      assert {:ok, %{in_flight_tx_pos: Utxo.position(^other_blknum, 0, 0)}} =\n               Core.prove_canonical_for_ife(processor, txbytes)\n\n      {processor, _} =\n        processor\n        |> Core.respond_to_in_flight_exits_challenges([ife_response(tx, Utxo.position(other_blknum, 1, 0))])\n\n      assert {:ok, []} = request |> check_validity_filtered(processor, only: [Event.InvalidIFEChallenge])\n      assert {:error, :no_viable_canonical_proof_found} = Core.prove_canonical_for_ife(processor, txbytes)\n\n      {processor, _} =\n        processor\n        |> Core.respond_to_in_flight_exits_challenges([ife_response(tx, Utxo.position(other_blknum, 0, 0))])\n\n      assert {:ok, []} = request |> check_validity_filtered(processor, only: [Event.InvalidIFEChallenge])\n      assert {:error, :no_viable_canonical_proof_found} = Core.prove_canonical_for_ife(processor, txbytes)\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/exit_processor/core/state_interaction_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.ExitProcessor.Core.StateInteractionTest do\n  @moduledoc \"\"\"\n  Test talking to OMG.State.Core\n  \"\"\"\n  use ExUnit.Case, async: false\n\n  alias OMG.Eth.Configuration\n  alias OMG.Watcher.Event\n  alias OMG.Watcher.ExitProcessor\n  alias OMG.Watcher.ExitProcessor.Core\n  alias OMG.Watcher.State\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.TestHelper\n  alias OMG.Watcher.Utxo\n  require Utxo\n\n  import OMG.Watcher.ExitProcessor.TestHelper,\n    only: [start_se_from: 3, start_se_from: 4, start_ife_from: 2, start_ife_from: 3, piggyback_ife_from: 4]\n\n  @default_min_exit_period_seconds 120\n  @default_child_block_interval 1000\n\n  @eth <<0::160>>\n  @fee_claimer_address \"NO FEE CLAIMER ADDR!\"\n\n  @early_blknum 1_000\n  @late_blknum 10_000\n  @utxo_pos1 Utxo.position(2, 0, 0)\n  @utxo_pos2 Utxo.position(@late_blknum - 1_000, 0, 1)\n\n  @fee %{@eth => [1]}\n\n  # needs to match up with the default from `ExitProcessor.Case` :(\n  @exit_id 9876\n\n  setup do\n    db_path = Briefly.create!(directory: true)\n    Application.put_env(:omg_db, :path, db_path, persistent: true)\n    :ok = OMG.DB.init()\n    {:ok, started_apps} = Application.ensure_all_started(:omg_db)\n\n    {:ok, processor_empty} = Core.init([], [], [], @default_min_exit_period_seconds, @default_child_block_interval)\n    child_block_interval = Configuration.child_block_interval()\n    {:ok, state_empty} = State.Core.extract_initial_state(0, child_block_interval, @fee_claimer_address)\n\n    on_exit(fn ->\n      Application.put_env(:omg_db, :path, nil)\n\n      Enum.map(started_apps, fn app -> :ok = Application.stop(app) end)\n    end)\n\n    {:ok, %{alice: TestHelper.generate_entity(), processor_empty: processor_empty, state_empty: state_empty}}\n  end\n\n  test \"can work with State to determine and notify invalid exits\",\n       %{processor_empty: processor, state_empty: state, alice: alice} do\n    exiting_position = Utxo.Position.encode(@utxo_pos1)\n\n    standard_exit_tx = TestHelper.create_recovered([{1, 0, 0, alice}], @eth, [{alice, 10}])\n    processor = start_se_from(processor, standard_exit_tx, @utxo_pos1)\n\n    assert {:ok, [%Event.InvalidExit{utxo_pos: ^exiting_position}]} =\n             %ExitProcessor.Request{eth_height_now: 5, blknum_now: @late_blknum}\n             |> Core.determine_utxo_existence_to_get(processor)\n             |> mock_utxo_exists(state)\n             |> Core.check_validity(processor)\n  end\n\n  test \"exits of utxos that couldn't have been seen created yet never excite events\",\n       %{processor_empty: processor, state_empty: state, alice: alice} do\n    standard_exit_tx = TestHelper.create_recovered([{1, 0, 0, alice}], @eth, [{alice, 10}])\n    processor = start_se_from(processor, standard_exit_tx, Utxo.position(@late_blknum, 0, 0))\n\n    assert {:ok, []} =\n             %ExitProcessor.Request{eth_height_now: 13, blknum_now: @early_blknum}\n             |> Core.determine_utxo_existence_to_get(processor)\n             |> mock_utxo_exists(state)\n             |> Core.check_validity(processor)\n  end\n\n  test \"handles invalid exit finalization - doesn't forget and causes a byzantine chain report\",\n       %{processor_empty: processor, state_empty: state, alice: alice} do\n    standard_exit_tx1 = TestHelper.create_recovered([{1, 0, 0, alice}], @eth, [{alice, 10}])\n    standard_exit_tx2 = TestHelper.create_recovered([{1000, 0, 0, alice}], @eth, [{alice, 10}, {alice, 10}])\n\n    processor =\n      processor\n      |> start_se_from(standard_exit_tx1, @utxo_pos1, exit_id: 1)\n      |> start_se_from(standard_exit_tx2, @utxo_pos2, eth_height: 4, exit_id: 2)\n\n    # exits invalidly finalize and continue/start emitting events and complain\n    {:ok, {_, two_spend}, state_after_spend} =\n      [1, 2] |> Enum.map(&Core.exit_key_by_exit_id(processor, &1)) |> State.Core.exit_utxos(state)\n\n    # finalizing here - note that without `finalize_exits`, we would just get a single invalid exit event\n    # with - we get 3, because we include the invalidly finalized on which will hurt forever\n    # (see persistence tests for the \"forever\" part)\n    assert {processor, _} = Core.finalize_exits(processor, two_spend)\n\n    assert {{:error, :unchallenged_exit}, [_event1, _event2, _event3]} =\n             %ExitProcessor.Request{eth_height_now: 12, blknum_now: @late_blknum}\n             |> Core.determine_utxo_existence_to_get(processor)\n             |> mock_utxo_exists(state_after_spend)\n             |> Core.check_validity(processor)\n  end\n\n  test \"can work with State to determine valid exits and finalize them\",\n       %{processor_empty: processor, state_empty: state_empty, alice: alice} do\n    state = state_empty |> TestHelper.do_deposit(alice, %{amount: 10, currency: @eth, blknum: 2})\n\n    standard_exit_tx = TestHelper.create_recovered([{1, 0, 0, alice}], @eth, [{alice, 10}])\n    processor = start_se_from(processor, standard_exit_tx, @utxo_pos1)\n\n    assert {:ok, []} =\n             %ExitProcessor.Request{eth_height_now: 5, blknum_now: @late_blknum}\n             |> Core.determine_utxo_existence_to_get(processor)\n             |> mock_utxo_exists(state)\n             |> Core.check_validity(processor)\n\n    # go into the future - old exits work the same\n    assert {:ok, []} =\n             %ExitProcessor.Request{eth_height_now: 105, blknum_now: @late_blknum}\n             |> Core.determine_utxo_existence_to_get(processor)\n             |> mock_utxo_exists(state)\n             |> Core.check_validity(processor)\n\n    # exit validly finalizes and continues to not emit any events\n    {:ok, {_, spends}, _} =\n      [@exit_id] |> Enum.map(&Core.exit_key_by_exit_id(processor, &1)) |> State.Core.exit_utxos(state)\n\n    assert {processor, [{:put, :exit_info, {{2, 0, 0}, _}}]} = Core.finalize_exits(processor, spends)\n\n    assert %ExitProcessor.Request{utxos_to_check: []} =\n             Core.determine_utxo_existence_to_get(%ExitProcessor.Request{blknum_now: @late_blknum}, processor)\n  end\n\n  test \"only asking for spends concerning ifes\",\n       %{alice: alice, processor_empty: processor, state_empty: state_empty} do\n    processor = start_ife_from(processor, TestHelper.create_recovered([{1, 0, 0, alice}], [{alice, @eth, 1}]))\n\n    state = state_empty |> TestHelper.do_deposit(alice, %{amount: 10, currency: @eth, blknum: 1})\n    comp = TestHelper.create_recovered([{1, 0, 0, alice}], @eth, [{alice, 9}])\n\n    # first sanity-check as if the utxo was not spent yet\n    assert %{utxos_to_check: utxos_to_check, utxo_exists_result: utxo_exists_result, spends_to_get: spends_to_get} =\n             %ExitProcessor.Request{blknum_now: @late_blknum}\n             |> Core.determine_utxo_existence_to_get(processor)\n             |> mock_utxo_exists(state)\n             |> Core.determine_spends_to_get(processor)\n\n    assert {Utxo.position(1, 0, 0), false} not in Enum.zip(utxos_to_check, utxo_exists_result)\n    assert Utxo.position(1, 0, 0) not in spends_to_get\n\n    # spend and see that Core now requests the relevant utxo checks and spends to get\n    {:ok, _, state} = OMG.Watcher.State.Core.exec(state, comp, @fee)\n    {:ok, {block, _}, state} = OMG.Watcher.State.Core.form_block(state)\n\n    assert %{utxos_to_check: utxos_to_check, utxo_exists_result: utxo_exists_result, spends_to_get: spends_to_get} =\n             %ExitProcessor.Request{blknum_now: @late_blknum, blocks_result: [block]}\n             |> Core.determine_utxo_existence_to_get(processor)\n             |> mock_utxo_exists(state)\n             |> Core.determine_spends_to_get(processor)\n\n    assert {Utxo.position(1, 0, 0), false} in Enum.zip(utxos_to_check, utxo_exists_result)\n    assert Utxo.position(1, 0, 0) in spends_to_get\n  end\n\n  test \"can work with State to exit utxos from in-flight transactions\",\n       %{processor_empty: processor, state_empty: state, alice: alice} do\n    # canonical & included\n    ife_exit_tx1 = TestHelper.create_recovered([{1, 0, 0, alice}], @eth, [{alice, 9}])\n    ife_id1 = 1\n    # non-canonical\n    ife_exit_tx2 = TestHelper.create_recovered([{2, 0, 0, alice}], @eth, [{alice, 19}])\n    tx_hash2 = State.Transaction.raw_txhash(ife_exit_tx2)\n    ife_id2 = 2\n\n    {:ok, {tx_hash1, _, _}, state} =\n      state\n      |> TestHelper.do_deposit(alice, %{amount: 10, currency: @eth, blknum: 1})\n      |> TestHelper.do_deposit(alice, %{amount: 10, currency: @eth, blknum: 2})\n      |> OMG.Watcher.State.Core.exec(ife_exit_tx1, @fee)\n\n    {:ok, {block, _}, state} = State.Core.form_block(state)\n\n    request = %ExitProcessor.Request{blknum_now: 5000, eth_height_now: 5, ife_input_spending_blocks_result: [block]}\n\n    processor =\n      processor\n      |> start_ife_from(ife_exit_tx1, exit_id: ife_id1)\n      |> start_ife_from(ife_exit_tx2, exit_id: ife_id2)\n      |> piggyback_ife_from(tx_hash1, 0, :output)\n      |> piggyback_ife_from(tx_hash2, 0, :input)\n      |> Core.find_ifes_in_blocks(request)\n\n    finalizations = [\n      %{in_flight_exit_id: ife_id1, output_index: 0, omg_data: %{piggyback_type: :output}},\n      %{in_flight_exit_id: ife_id2, output_index: 0, omg_data: %{piggyback_type: :input}}\n    ]\n\n    ife_id1 = <<ife_id1::192>>\n    ife_id2 = <<ife_id2::192>>\n    ife_tx1_output_pos = Utxo.position(1000, 0, 0)\n    ife_tx2_input_pos = Utxo.position(2, 0, 0)\n\n    {:ok, %{^ife_id1 => exiting_positions1, ^ife_id2 => exiting_positions2},\n     [\n       {%{in_flight_exit_id: ^ife_id1}, [^ife_tx1_output_pos]},\n       {%{in_flight_exit_id: ^ife_id2}, [^ife_tx2_input_pos]}\n     ]} = Core.prepare_utxo_exits_for_in_flight_exit_finalizations(processor, finalizations)\n\n    assert {:ok,\n            {[{:delete, :utxo, _}, {:delete, :utxo, _}], {[Utxo.position(1000, 0, 0), Utxo.position(2, 0, 0)], []}},\n            _} = State.Core.exit_utxos(exiting_positions1 ++ exiting_positions2, state)\n  end\n\n  test \"tolerates piggybacked outputs exiting if they're concerning non-included IFE txs\",\n       %{processor_empty: processor, state_empty: state, alice: alice} do\n    ife_exit_tx = TestHelper.create_recovered([{1, 0, 0, alice}], @eth, [{alice, 10}])\n    tx_hash = State.Transaction.raw_txhash(ife_exit_tx)\n    ife_id = 1\n\n    # ife tx cannot be found in blocks, hence `ife_input_spending_blocks_result: []`\n    request = %ExitProcessor.Request{blknum_now: 5000, eth_height_now: 5, ife_input_spending_blocks_result: []}\n\n    processor =\n      processor\n      |> start_ife_from(ife_exit_tx, exit_id: ife_id)\n      |> piggyback_ife_from(tx_hash, 0, :output)\n      |> Core.find_ifes_in_blocks(request)\n\n    finalizations = [%{in_flight_exit_id: ife_id, output_index: 0, omg_data: %{piggyback_type: :output}}]\n    ife_id = <<ife_id::192>>\n\n    {:ok, %{^ife_id => exiting_positions}, []} =\n      Core.prepare_utxo_exits_for_in_flight_exit_finalizations(processor, finalizations)\n\n    {:ok, {_, {[], [] = invalidities}}, _} = State.Core.exit_utxos(exiting_positions, state)\n\n    assert {:ok, processor, [_]} = Core.finalize_in_flight_exits(processor, finalizations, %{ife_id => invalidities})\n    assert [] = Core.get_active_in_flight_exits(processor)\n  end\n\n  test \"acts on invalidities reported when exiting utxos in State\",\n       %{processor_empty: processor, state_empty: state, alice: alice} do\n    # canonical & included\n    ife_exit_tx = TestHelper.create_recovered([{1, 0, 0, alice}], @eth, [{alice, 9}])\n    # double-spending the piggybacked ouptut\n    spending_tx = TestHelper.create_recovered([{1000, 0, 0, alice}], @eth, [{alice, 8}])\n    ife_id = 1\n\n    state = TestHelper.do_deposit(state, alice, %{amount: 10, currency: @eth, blknum: 1})\n    {:ok, {tx_hash, _, _}, state} = OMG.Watcher.State.Core.exec(state, ife_exit_tx, @fee)\n    {:ok, {_, _, _}, state} = OMG.Watcher.State.Core.exec(state, spending_tx, @fee)\n    {:ok, {block, _}, state} = OMG.Watcher.State.Core.form_block(state)\n\n    request = %ExitProcessor.Request{blknum_now: 5000, eth_height_now: 5, ife_input_spending_blocks_result: [block]}\n\n    processor =\n      processor\n      |> start_ife_from(ife_exit_tx, exit_id: ife_id)\n      |> piggyback_ife_from(tx_hash, 0, :output)\n      |> Core.find_ifes_in_blocks(request)\n\n    finalizations = [%{in_flight_exit_id: ife_id, output_index: 0, omg_data: %{piggyback_type: :output}}]\n    ife_id = <<ife_id::192>>\n    [exiting_utxo] = Transaction.get_inputs(spending_tx)\n\n    {:ok, %{^ife_id => exiting_positions}, [{%{in_flight_exit_id: ^ife_id}, [^exiting_utxo]}]} =\n      Core.prepare_utxo_exits_for_in_flight_exit_finalizations(processor, finalizations)\n\n    {:ok, {_, {[], [_] = invalidities}}, _} = State.Core.exit_utxos(exiting_positions, state)\n\n    assert {:ok, processor, [_]} = Core.finalize_in_flight_exits(processor, finalizations, %{ife_id => invalidities})\n    assert [_] = Core.get_active_in_flight_exits(processor)\n  end\n\n  test \"deleting in-flight exits works with State\",\n       %{processor_empty: processor, state_empty: state, alice: alice} do\n    ife_exit_tx = TestHelper.create_recovered([{1, 0, 0, alice}], @eth, [{alice, 9}])\n    ife_id = 1\n\n    state = TestHelper.do_deposit(state, alice, %{amount: 10, currency: @eth, blknum: 1})\n    {:ok, _, state} = OMG.Watcher.State.Core.form_block(state)\n\n    {_processor, deleted_utxos, _db_updates} =\n      processor\n      |> start_ife_from(ife_exit_tx, exit_id: ife_id)\n      |> Core.delete_in_flight_exits([%{exit_id: ife_id}])\n\n    assert {:ok, {[{:delete, :utxo, _}], {[{:utxo_position, 1, 0, 0}], []}}, _} =\n             State.Core.exit_utxos(deleted_utxos, state)\n  end\n\n  defp mock_utxo_exists(%ExitProcessor.Request{utxos_to_check: positions} = request, state) do\n    %{request | utxo_exists_result: positions |> Enum.map(&OMG.Watcher.State.Core.utxo_exists?(&1, state))}\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/exit_processor/core_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.ExitProcessor.CoreTest do\n  @moduledoc \"\"\"\n  Test of the logic of exit processor - various generic tests: starting events, some sanity checks, ife listing\n  \"\"\"\n  use OMG.Watcher.ExitProcessor.Case, async: true\n\n  alias OMG.Watcher.Block\n  alias OMG.Watcher.ExitProcessor\n  alias OMG.Watcher.ExitProcessor.Core\n  alias OMG.Watcher.ExitProcessor.InFlightExitInfo\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.TestHelper\n  alias OMG.Watcher.Utxo\n\n  require Utxo\n\n  import OMG.Watcher.ExitProcessor.TestHelper\n  import ExUnit.CaptureLog, only: [capture_log: 1]\n\n  @eth <<0::160>>\n\n  @late_blknum 10_000\n\n  @utxo_pos1 Utxo.position(2, 0, 0)\n  @utxo_pos2 Utxo.position(@late_blknum - 1_000, 0, 1)\n\n  describe \"generic sanity checks\" do\n    test \"can start new standard exits one by one or batched\", %{processor_empty: empty, alice: alice, bob: bob} do\n      standard_exit_tx1 = TestHelper.create_recovered([{1, 0, 0, alice}], @eth, [{alice, 10}])\n      standard_exit_tx2 = TestHelper.create_recovered([{1, 0, 0, alice}], @eth, [{bob, 10}, {bob, 10}])\n      {event1, status1} = se_event_status(standard_exit_tx1, @utxo_pos1)\n      {event2, status2} = se_event_status(standard_exit_tx2, @utxo_pos2)\n      events = [event1, event2]\n      statuses = [status1, status2]\n\n      {state2, _} = Core.new_exits(empty, Enum.slice(events, 0, 1), Enum.slice(statuses, 0, 1))\n      {final_state, _} = Core.new_exits(empty, events, statuses)\n      assert {^final_state, _} = Core.new_exits(state2, Enum.slice(events, 1, 1), Enum.slice(statuses, 1, 1))\n    end\n\n    test \"new_exits sanity checks\", %{processor_empty: processor} do\n      {:error, :unexpected_events} = processor |> Core.new_exits([:anything], [])\n      {:error, :unexpected_events} = processor |> Core.new_exits([], [:anything])\n    end\n\n    test \"can process empty new exits, empty in flight exits\",\n         %{processor_empty: empty, processor_filled: filled} do\n      assert {^empty, []} = Core.new_exits(empty, [], [])\n      assert {^empty, []} = Core.new_in_flight_exits(empty, [], [])\n      assert {^filled, []} = Core.new_exits(filled, [], [])\n      assert {^filled, []} = Core.new_in_flight_exits(filled, [], [])\n    end\n\n    test \"empty processor returns no exiting utxo positions\", %{processor_empty: empty} do\n      assert %ExitProcessor.Request{utxos_to_check: []} =\n               Core.determine_utxo_existence_to_get(%ExitProcessor.Request{blknum_now: @late_blknum}, empty)\n    end\n\n    test \"in flight exits sanity checks\",\n         %{processor_empty: state, in_flight_exit_events: events} do\n      assert {state, []} == Core.new_in_flight_exits(state, [], [])\n      assert {:error, :unexpected_events} == Core.new_in_flight_exits(state, Enum.slice(events, 0, 1), [])\n      assert {:error, :unexpected_events} == Core.new_in_flight_exits(state, [], [{:anything, 1}])\n    end\n\n    test \"knows exits by exit_id the moment they start\",\n         %{processor_empty: processor, alice: alice} do\n      standard_exit_tx = TestHelper.create_recovered([{1, 0, 0, alice}], @eth, [{alice, 10}])\n      assert nil == Core.exit_key_by_exit_id(processor, 314)\n\n      processor =\n        processor\n        |> start_se_from(standard_exit_tx, @utxo_pos1, exit_id: 314)\n\n      assert @utxo_pos1 == Core.exit_key_by_exit_id(processor, 314)\n      assert nil == Core.exit_key_by_exit_id(processor, 315)\n    end\n\n    test \"knows exits by exit_id after challenging\",\n         %{processor_empty: processor, alice: alice} do\n      standard_exit_tx = TestHelper.create_recovered([{1, 0, 0, alice}], @eth, [{alice, 10}])\n\n      {processor, _} =\n        processor\n        |> start_se_from(standard_exit_tx, @utxo_pos1, exit_id: 314)\n        |> Core.challenge_exits([%{utxo_pos: Utxo.Position.encode(@utxo_pos1)}])\n\n      assert @utxo_pos1 == Core.exit_key_by_exit_id(processor, 314)\n    end\n\n    test \"doesn't know ife by exit_id because NOT IMPLEMENTED, remove when it's implemented\",\n         %{processor_empty: processor, alice: alice} do\n      standard_exit_tx = TestHelper.create_recovered([{1, 0, 0, alice}], @eth, [{alice, 10}])\n\n      {processor, _} =\n        processor\n        |> start_ife_from(standard_exit_tx, exit_id: 314)\n        |> Core.challenge_exits([%{utxo_pos: Utxo.Position.encode(@utxo_pos1)}])\n\n      # because not implemented yet\n      # TODO fix when implemented\n      assert nil == Core.exit_key_by_exit_id(processor, 314)\n    end\n  end\n\n  describe \"check_sla_margin/4\" do\n    test \"allows only safe margins if not forcing\" do\n      assert {:error, :sla_margin_too_big} = Core.check_sla_margin(10, false, 100, 15)\n      assert :ok = Core.check_sla_margin(10, false, 300, 15)\n    end\n\n    test \"allows anything if forcing\" do\n      capture_log(fn -> assert :ok = Core.check_sla_margin(10, true, 100, 15) end)\n      assert :ok = Core.check_sla_margin(10, true, 300, 15)\n    end\n  end\n\n  describe \"active SE/IFE listing (only IFEs for now)\" do\n    test \"properly processes new in flight exits, returns all of them on request\",\n         %{processor_empty: processor, in_flight_exit_events: events} do\n      assert [] == Core.get_active_in_flight_exits(processor)\n      # some statuses as received from the contract\n      statuses = [{active_ife_status(), 1}, {active_ife_status(), 2}]\n\n      {processor, _} = Core.new_in_flight_exits(processor, events, statuses)\n      ifes_response = Core.get_active_in_flight_exits(processor)\n\n      assert ifes_response |> Enum.count() == 2\n    end\n\n    test \"correct format of getting all ifes\",\n         %{processor_filled: processor, transactions: [tx1, tx2 | _]} do\n      assert [\n               %{\n                 txbytes: Transaction.raw_txbytes(tx1),\n                 txhash: Transaction.raw_txhash(tx1),\n                 eth_height: 1,\n                 piggybacked_inputs: [],\n                 piggybacked_outputs: []\n               },\n               %{\n                 txbytes: Transaction.raw_txbytes(tx2),\n                 txhash: Transaction.raw_txhash(tx2),\n                 eth_height: 4,\n                 piggybacked_inputs: [],\n                 piggybacked_outputs: []\n               }\n             ] == Core.get_active_in_flight_exits(processor) |> Enum.sort_by(& &1.eth_height)\n    end\n\n    test \"reports piggybacked inputs/outputs when getting ifes\",\n         %{processor_empty: processor, transactions: [tx | _]} do\n      txhash = Transaction.raw_txhash(tx)\n      processor = start_ife_from(processor, tx)\n      assert [%{piggybacked_inputs: [], piggybacked_outputs: []}] = Core.get_active_in_flight_exits(processor)\n\n      processor = piggyback_ife_from(processor, txhash, 0, :input)\n      assert [%{piggybacked_inputs: [0], piggybacked_outputs: []}] = Core.get_active_in_flight_exits(processor)\n\n      processor = processor |> piggyback_ife_from(txhash, 0, :output) |> piggyback_ife_from(txhash, 1, :output)\n      assert [%{piggybacked_inputs: [0], piggybacked_outputs: [0, 1]}] = Core.get_active_in_flight_exits(processor)\n    end\n\n    test \"challenges don't affect the list of IFEs returned\",\n         %{processor_filled: processor, transactions: [tx | _], competing_tx: comp} do\n      assert Core.get_active_in_flight_exits(processor) |> Enum.count() == 2\n      {processor2, _} = Core.new_ife_challenges(processor, [ife_challenge(tx, comp)])\n      assert Core.get_active_in_flight_exits(processor2) |> Enum.count() == 2\n      # sanity\n      assert processor2 != processor\n    end\n  end\n\n  describe \"handling of spent blknums result\" do\n    test \"asks for the right blocks when all are spent correctly\" do\n      assert [1000] = Core.handle_spent_blknum_result([{:ok, 1000}], [@utxo_pos1])\n      assert [] = Core.handle_spent_blknum_result([], [])\n      assert [2000, 1000] = Core.handle_spent_blknum_result([{:ok, 2000}, {:ok, 1000}], [@utxo_pos2, @utxo_pos1])\n    end\n\n    test \"asks for blocks just once\" do\n      assert [1000] = Core.handle_spent_blknum_result([{:ok, 1000}, {:ok, 1000}], [@utxo_pos2, @utxo_pos1])\n    end\n\n    @tag :capture_log\n    test \"asks for the right blocks if some spends are missing\" do\n      assert [1000] = Core.handle_spent_blknum_result([:not_found, {:ok, 1000}], [@utxo_pos2, @utxo_pos1])\n    end\n  end\n\n  describe \"finding IFE txs in blocks\" do\n    test \"handles well situation when syncing is in progress\", %{processor_filled: state} do\n      assert %ExitProcessor.Request{utxos_to_check: [], ife_input_utxos_to_check: []} =\n               %ExitProcessor.Request{eth_height_now: 13, blknum_now: 0}\n               |> Core.determine_ife_input_utxos_existence_to_get(state)\n               |> Core.determine_utxo_existence_to_get(state)\n    end\n\n    test \"seeks all IFE txs' inputs spends in blocks\", %{processor_filled: processor, transactions: txs} do\n      request = %ExitProcessor.Request{\n        blknum_now: 5000,\n        eth_height_now: 5\n      }\n\n      # for one piggybacked output, we're asking for its inputs positions to check utxo existence\n      request = Core.determine_ife_input_utxos_existence_to_get(request, processor)\n      expected_inputs = txs |> Enum.flat_map(&Transaction.get_inputs/1)\n      assert Enum.sort(expected_inputs) == Enum.sort(request.ife_input_utxos_to_check)\n\n      # if it turns out to not exists, we're fetching the spending block\n      request =\n        request\n        |> struct!(%{ife_input_utxo_exists_result: [false, true, true, true]})\n        |> Core.determine_ife_spends_to_get(processor)\n\n      assert length(request.ife_input_spends_to_get) == 1\n      assert hd(request.ife_input_spends_to_get) in expected_inputs\n    end\n\n    test \"seeks IFE txs in blocks, correctly if IFE inputs duplicate\",\n         %{processor_filled: processor, alice: alice, transactions: txs} do\n      other_tx = TestHelper.create_recovered([{1, 0, 0, alice}], [{alice, @eth, 1}])\n      processor = start_ife_from(processor, other_tx)\n\n      request = %ExitProcessor.Request{\n        blknum_now: 5000,\n        eth_height_now: 5\n      }\n\n      # for one piggybacked output, we're asking for its inputs positions to check utxo existence\n      request = Core.determine_ife_input_utxos_existence_to_get(request, processor)\n      expected_inputs = txs |> Enum.flat_map(&Transaction.get_inputs/1)\n\n      assert Enum.sort(expected_inputs) == Enum.sort(request.ife_input_utxos_to_check)\n    end\n\n    test \"seeks IFE txs in blocks only if not already found\",\n         %{processor_filled: processor, transactions: [tx1, tx2]} do\n      request = %ExitProcessor.Request{\n        blknum_now: 5000,\n        eth_height_now: 5,\n        ife_input_spending_blocks_result: [Block.hashed_txs_at([tx1], 3000)]\n      }\n\n      processor = processor |> Core.find_ifes_in_blocks(request)\n      # for one piggybacked output, we're asking for its inputs positions to check utxo existence\n      request = Core.determine_ife_input_utxos_existence_to_get(request, processor)\n\n      expected_inputs = Transaction.get_inputs(tx2)\n      assert Enum.sort(expected_inputs) == Enum.sort(request.ife_input_utxos_to_check)\n    end\n  end\n\n  describe \"active_standard_exiting_utxos\" do\n    test \"returns a set of exiting utxo positions\" do\n      utxo_pos_active = {active_blknum, active_txindex, active_txoutput} = {1000, 0, 0}\n\n      active_exit = %{\n        amount: 1,\n        block_timestamp: 1,\n        currency: <<1::160>>,\n        eth_height: 1,\n        exit_id: 1,\n        exiting_txbytes: \"txbytes\",\n        is_active: true,\n        owner: <<1::160>>,\n        root_chain_txhash: <<1::256>>,\n        scheduled_finalization_time: 2,\n        spending_txhash: nil\n      }\n\n      utxo_pos_inactive = {1000, 0, 1}\n      inactive_exit = Map.replace!(active_exit, :is_active, false)\n\n      db_exits = [{utxo_pos_active, active_exit}, {utxo_pos_inactive, inactive_exit}]\n\n      expected = MapSet.new([Utxo.position(active_blknum, active_txindex, active_txoutput)])\n\n      assert expected == Core.active_standard_exiting_utxos(db_exits)\n    end\n  end\n\n  describe \"active_in_flight_exiting_inputs\" do\n    test \"returns a set of exiting utxo positions\" do\n      expected_utxos = [Utxo.position(2001, 0, 0), Utxo.position(2002, 0, 0)]\n\n      db_exits = [\n        {<<1>>, prepare_fake_ife_db_kv(false, [Utxo.position(1001, 0, 0)])},\n        {<<2>>, prepare_fake_ife_db_kv(true, expected_utxos)}\n      ]\n\n      assert MapSet.new(expected_utxos) == Core.active_in_flight_exiting_inputs(db_exits)\n    end\n\n    defp prepare_fake_ife_db_kv(is_active, utxos_pos) do\n      raw_tx_map = %{tx_type: 1, inputs: [], outputs: [], metadata: <<0::256>>}\n      signed_tx_map = %{raw_tx: raw_tx_map, sigs: []}\n      utxo_pos = Utxo.position(0, 0, 0)\n\n      db_value_map =\n        %{\n          tx: signed_tx_map,\n          exit_map: %{},\n          tx_pos: utxo_pos,\n          oldest_competitor: utxo_pos,\n          contract_id: <<1>>,\n          timestamp: 0,\n          eth_height: 100,\n          relevant_from_blknum: 0,\n          input_txs: [],\n          input_utxos_pos: [],\n          is_canonical: true,\n          is_active: true\n        }\n        |> Map.update!(:is_active, fn _ -> is_active end)\n        |> Map.update!(:input_utxos_pos, fn _ -> utxos_pos end)\n\n      # sanity check - we need above date to be parsed correctly\n      assert {<<1>>, %InFlightExitInfo{}} = InFlightExitInfo.from_db_kv({<<1>>, db_value_map})\n\n      db_value_map\n    end\n  end\n\n  describe \"delete_in_flight_exits/2\" do\n    test \"returns deleted utxos and database updates\", %{processor_empty: processor, alice: alice} do\n      ife_exit_tx1 = TestHelper.create_recovered([{1, 0, 0, alice}], @eth, [{alice, 9}])\n      ife_id1 = 1\n      tx_hash1 = Transaction.raw_txhash(ife_exit_tx1)\n      ife_exit_tx2 = TestHelper.create_recovered([{2, 0, 1, alice}, {2, 0, 2, alice}], @eth, [{alice, 9}])\n      ife_id2 = 2\n      ife_exit_tx3 = TestHelper.create_recovered([{3, 0, 1, alice}, {3, 0, 2, alice}], @eth, [{alice, 9}])\n      ife_id3 = 3\n      tx_hash3 = Transaction.raw_txhash(ife_exit_tx3)\n\n      {_processor, deleted_utxos, db_updates} =\n        processor\n        |> start_ife_from(ife_exit_tx1, exit_id: ife_id1)\n        |> start_ife_from(ife_exit_tx2, exit_id: ife_id2)\n        |> start_ife_from(ife_exit_tx3, exit_id: ife_id3)\n        |> Core.delete_in_flight_exits([%{exit_id: ife_id1}, %{exit_id: ife_id3}])\n\n      assert Enum.sort(deleted_utxos) ==\n               Enum.sort([{:utxo_position, 3, 0, 1}, {:utxo_position, 3, 0, 2}, {:utxo_position, 1, 0, 0}])\n\n      assert Enum.sort(db_updates) ==\n               Enum.sort([{:delete, :in_flight_exit_info, tx_hash1}, {:delete, :in_flight_exit_info, tx_hash3}])\n    end\n\n    test \"deletes in-flight exits from processor\", %{processor_empty: processor, alice: alice} do\n      ife_exit_tx = TestHelper.create_recovered([{1, 0, 0, alice}], @eth, [{alice, 9}])\n      ife_id = 1\n\n      {processor, _deleted_utxos, _db_updates} =\n        processor\n        |> start_ife_from(ife_exit_tx, exit_id: ife_id)\n        |> Core.delete_in_flight_exits([%{exit_id: ife_id}])\n\n      assert Enum.empty?(processor.in_flight_exits)\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/exit_processor/exit_info_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.ExitProcessor.ExitInfoTest do\n  @moduledoc \"\"\"\n  Test of the logic of the exit_info module\n  \"\"\"\n  use OMG.Watcher.ExitProcessor.Case, async: true\n\n  alias OMG.Watcher.ExitProcessor.ExitInfo\n\n  @recently_added_keys [:root_chain_txhash, :scheduled_finalization_time, :block_timestamp]\n  @utxo_pos_1 {1000, 0, 0}\n  @exit_1 [\n    amount: 1,\n    block_timestamp: 1,\n    currency: <<1::160>>,\n    eth_height: 1,\n    exit_id: 1,\n    exiting_txbytes: \"txbytes\",\n    is_active: false,\n    owner: <<1::160>>,\n    root_chain_txhash: <<1::256>>,\n    scheduled_finalization_time: 2,\n    spending_txhash: nil\n  ]\n\n  @exit_info_1 struct!(ExitInfo, @exit_1)\n\n  @min_exit_period 20\n  @child_block_interval 1000\n\n  describe \"from_db_kv/1\" do\n    test \"default recently added keys to nil for existing entries without said key\" do\n      exit_info = Map.drop(@exit_info_1, @recently_added_keys)\n\n      {_, exit_info_struct} = ExitInfo.from_db_kv({@utxo_pos_1, exit_info})\n\n      Enum.each(@recently_added_keys, fn recently_added_key ->\n        value = Map.get(exit_info_struct, recently_added_key)\n        assert value == nil\n      end)\n    end\n\n    test \"accepts an exit argument with recently added keys and includes them in the struct\" do\n      {_, exit_info_struct} = ExitInfo.from_db_kv({@utxo_pos_1, @exit_info_1})\n\n      Enum.each(@recently_added_keys, fn recently_added_key ->\n        assert Map.get(exit_info_struct, recently_added_key) == Map.get(@exit_info_1, recently_added_key)\n      end)\n    end\n  end\n\n  describe \"calculate_sft/4\" do\n    test \"calculates scheduled finalisation time correctly if UTXO was created by a deposit\" do\n      deposit_blknum = 2001\n      utxo_creation_ts = 50\n      # By setting the exit timestamp at within @min_exit_period from the creation of the UTXO,\n      # the fact that the UTXO was a deposit changes the resulting scheduled finalisation time,\n      # thereby testing as intended.\n      exit_ts = utxo_creation_ts + @min_exit_period - 10\n\n      expected_sft = max(exit_ts + @min_exit_period, utxo_creation_ts + @min_exit_period)\n\n      assert {:ok, expected_sft} ==\n               ExitInfo.calculate_sft(\n                 deposit_blknum,\n                 exit_ts,\n                 utxo_creation_ts,\n                 @min_exit_period,\n                 @child_block_interval\n               )\n    end\n\n    test \"calculates scheduled finalisation time correctly if UTXO was created by a child chain transaction\" do\n      blknum = 2000\n      utxo_creation_ts = 50\n      # By setting the exit timestamp at within @min_exit_period from the creation of the UTXO,\n      # the fact that the UTXO was not created by a deposit changes the resulting scheduled finalisation time,\n      # thereby testing as intended.\n      exit_ts = utxo_creation_ts + @min_exit_period - 10\n\n      expected_sft = max(exit_ts + @min_exit_period, utxo_creation_ts + 2 * @min_exit_period)\n\n      assert {:ok, expected_sft} ==\n               ExitInfo.calculate_sft(\n                 blknum,\n                 exit_ts,\n                 utxo_creation_ts,\n                 @min_exit_period,\n                 @child_block_interval\n               )\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/exit_processor/finalizations_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.ExitProcessor.FinalizationsTest do\n  @moduledoc \"\"\"\n  Test of the logic of exit processor - finalizing various flavors of exits and handling finalization validity\n  \"\"\"\n  use OMG.Watcher.ExitProcessor.Case, async: true\n\n  alias OMG.Watcher.Block\n  alias OMG.Watcher.ExitProcessor\n  alias OMG.Watcher.ExitProcessor.Core\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.Utxo\n\n  require Utxo\n\n  import OMG.Watcher.ExitProcessor.TestHelper\n\n  @exit_id 1\n\n  describe \"sanity checks\" do\n    test \"can process empty finalizations\", %{processor_empty: empty, processor_filled: filled} do\n      assert {^empty, []} = Core.finalize_exits(empty, {[], []})\n      assert {^filled, []} = Core.finalize_exits(filled, {[], []})\n      assert {:ok, %{}, []} = Core.prepare_utxo_exits_for_in_flight_exit_finalizations(empty, [])\n      assert {:ok, %{}, []} = Core.prepare_utxo_exits_for_in_flight_exit_finalizations(filled, [])\n      assert {:ok, ^empty, []} = Core.finalize_in_flight_exits(empty, [], %{})\n      assert {:ok, ^filled, []} = Core.finalize_in_flight_exits(filled, [], %{})\n    end\n  end\n\n  describe \"determining utxos that are exited by finalization\" do\n    test \"signals all included txs' outputs as exiting when piggybacked output exits\",\n         %{processor_empty: processor, transactions: [tx1 | _]} do\n      ife_id1 = 1\n      tx_hash1 = Transaction.raw_txhash(tx1)\n      tx1_blknum = 3000\n\n      # both IFE txs are inlcuded in one of the blocks and picked up by the `ExitProcessor`\n      request = %ExitProcessor.Request{\n        blknum_now: 5000,\n        eth_height_now: 5,\n        ife_input_spending_blocks_result: [Block.hashed_txs_at([tx1], tx1_blknum)]\n      }\n\n      processor =\n        processor\n        |> start_ife_from(tx1, exit_id: ife_id1)\n        |> piggyback_ife_from(tx_hash1, 0, :input)\n        |> piggyback_ife_from(tx_hash1, 1, :input)\n        |> piggyback_ife_from(tx_hash1, 0, :output)\n        |> piggyback_ife_from(tx_hash1, 1, :output)\n        |> Core.find_ifes_in_blocks(request)\n\n      finalizations = [\n        %{in_flight_exit_id: ife_id1, output_index: 0, omg_data: %{piggyback_type: :output}},\n        %{in_flight_exit_id: ife_id1, output_index: 1, omg_data: %{piggyback_type: :output}}\n      ]\n\n      ife_id1 = <<ife_id1::192>>\n\n      tx1_first_output = Utxo.position(tx1_blknum, 0, 0)\n      tx1_second_output = Utxo.position(tx1_blknum, 0, 1)\n\n      assert {\n               :ok,\n               %{^ife_id1 => [^tx1_first_output, ^tx1_second_output]},\n               [{%{output_index: 0}, [^tx1_first_output]}, {%{output_index: 1}, [^tx1_second_output]}]\n             } = Core.prepare_utxo_exits_for_in_flight_exit_finalizations(processor, finalizations)\n    end\n\n    test \"doesn't signal non-included txs' outputs as exiting when piggybacked output exits\",\n         %{processor_empty: processor, transactions: [tx1 | _]} do\n      ife_id1 = 2\n      tx_hash1 = Transaction.raw_txhash(tx1)\n\n      request = %ExitProcessor.Request{\n        blknum_now: 5000,\n        eth_height_now: 5,\n        ife_input_spending_blocks_result: []\n      }\n\n      processor =\n        processor\n        |> start_ife_from(tx1, exit_id: ife_id1)\n        |> piggyback_ife_from(tx_hash1, 0, :output)\n        |> piggyback_ife_from(tx_hash1, 1, :output)\n        |> Core.find_ifes_in_blocks(request)\n\n      finalizations = [\n        %{in_flight_exit_id: ife_id1, output_index: 0, omg_data: %{piggyback_type: :output}},\n        %{in_flight_exit_id: ife_id1, output_index: 1, omg_data: %{piggyback_type: :output}}\n      ]\n\n      ife_id1 = <<ife_id1::192>>\n\n      assert {:ok, %{^ife_id1 => []}, []} =\n               Core.prepare_utxo_exits_for_in_flight_exit_finalizations(processor, finalizations)\n    end\n\n    test \"returns utxos that should be spent when exit finalizes, two ifes combined\",\n         %{processor_empty: processor, transactions: [tx1, tx2 | _]} do\n      ife_id1 = 1\n      ife_id2 = 2\n      tx_hash1 = Transaction.raw_txhash(tx1)\n      tx_hash2 = Transaction.raw_txhash(tx2)\n      tx2_blknum = 3000\n\n      # both IFE txs are inlcuded in one of the blocks and picked up by the `ExitProcessor`\n      request = %ExitProcessor.Request{\n        blknum_now: 5000,\n        eth_height_now: 5,\n        ife_input_spending_blocks_result: [Block.hashed_txs_at([tx1, tx2], tx2_blknum)]\n      }\n\n      processor =\n        processor\n        |> start_ife_from(tx1, exit_id: ife_id1)\n        |> start_ife_from(tx2, exit_id: ife_id2)\n        |> Core.find_ifes_in_blocks(request)\n        |> piggyback_ife_from(tx_hash1, 0, :input)\n        |> piggyback_ife_from(tx_hash1, 1, :input)\n        |> piggyback_ife_from(tx_hash2, 0, :output)\n        |> piggyback_ife_from(tx_hash2, 1, :output)\n\n      finalizations = [\n        %{in_flight_exit_id: ife_id1, output_index: 0, omg_data: %{piggyback_type: :input}},\n        %{in_flight_exit_id: ife_id2, output_index: 0, omg_data: %{piggyback_type: :output}}\n      ]\n\n      assert {:ok, %{}, []} = Core.prepare_utxo_exits_for_in_flight_exit_finalizations(processor, [])\n\n      ife_id1 = <<ife_id1::192>>\n      ife_id2 = <<ife_id2::192>>\n\n      tx1_first_input = tx1 |> Transaction.get_inputs() |> hd()\n      tx2_first_output = Utxo.position(tx2_blknum, 1, 0)\n\n      assert {\n               :ok,\n               %{^ife_id1 => [^tx1_first_input], ^ife_id2 => [^tx2_first_output]},\n               [\n                 {%{in_flight_exit_id: ^ife_id1}, [^tx1_first_input]},\n                 {%{in_flight_exit_id: ^ife_id2}, [^tx2_first_output]}\n               ]\n             } = Core.prepare_utxo_exits_for_in_flight_exit_finalizations(processor, finalizations)\n    end\n\n    test \"fails when unknown in-flight exit is being finalized\", %{processor_empty: processor} do\n      finalization = %{in_flight_exit_id: @exit_id, output_index: 1, omg_data: %{piggyback_type: :input}}\n\n      {:unknown_in_flight_exit, unknown_exits} =\n        Core.prepare_utxo_exits_for_in_flight_exit_finalizations(processor, [finalization])\n\n      assert unknown_exits == MapSet.new([<<@exit_id::192>>])\n    end\n\n    test \"fails when exiting an output that is not piggybacked\",\n         %{processor_empty: processor, transactions: [tx | _]} do\n      tx_hash = Transaction.raw_txhash(tx)\n      ife_id = 123\n\n      processor =\n        processor\n        |> start_ife_from(tx, exit_id: ife_id)\n        |> piggyback_ife_from(tx_hash, 1, :input)\n\n      finalization1 = %{in_flight_exit_id: ife_id, output_index: 1, omg_data: %{piggyback_type: :input}}\n      finalization2 = %{in_flight_exit_id: ife_id, output_index: 2, omg_data: %{piggyback_type: :input}}\n\n      expected_unknown_piggybacks = [\n        %{in_flight_exit_id: <<ife_id::192>>, output_index: 2, omg_data: %{piggyback_type: :input}}\n      ]\n\n      {:inactive_piggybacks_finalizing, ^expected_unknown_piggybacks} =\n        Core.prepare_utxo_exits_for_in_flight_exit_finalizations(processor, [finalization1, finalization2])\n    end\n  end\n\n  describe \"in-flight exit finalization\" do\n    test \"exits piggybacked transaction inputs\",\n         %{processor_empty: processor, transactions: [tx | _]} do\n      ife_id = 123\n      tx_hash = Transaction.raw_txhash(tx)\n\n      processor =\n        processor\n        |> start_ife_from(tx, exit_id: ife_id)\n        |> piggyback_ife_from(tx_hash, 0, :input)\n        |> piggyback_ife_from(tx_hash, 1, :input)\n\n      assert {:ok, processor, [{:put, :in_flight_exit_info, _}]} =\n               Core.finalize_in_flight_exits(\n                 processor,\n                 [%{in_flight_exit_id: ife_id, output_index: 0, omg_data: %{piggyback_type: :input}}],\n                 %{}\n               )\n\n      assert {:ok, _, [{:put, :in_flight_exit_info, _}]} =\n               Core.finalize_in_flight_exits(\n                 processor,\n                 [%{in_flight_exit_id: ife_id, output_index: 1, omg_data: %{piggyback_type: :input}}],\n                 %{}\n               )\n    end\n\n    test \"exits piggybacked transaction outputs\",\n         %{processor_empty: processor, transactions: [tx | _]} do\n      ife_id = 123\n      tx_hash = Transaction.raw_txhash(tx)\n\n      processor =\n        processor\n        |> start_ife_from(tx, exit_id: ife_id)\n        |> piggyback_ife_from(tx_hash, 0, :output)\n        |> piggyback_ife_from(tx_hash, 1, :output)\n\n      assert {:ok, _, [{:put, :in_flight_exit_info, _}]} =\n               Core.finalize_in_flight_exits(\n                 processor,\n                 [\n                   %{in_flight_exit_id: ife_id, output_index: 1, omg_data: %{piggyback_type: :output}},\n                   %{in_flight_exit_id: ife_id, output_index: 0, omg_data: %{piggyback_type: :output}}\n                 ],\n                 %{}\n               )\n    end\n\n    test \"deactivates in-flight exit after all piggybacked outputs are finalized\",\n         %{processor_empty: processor, transactions: [tx | _]} do\n      ife_id = 123\n      tx_hash = Transaction.raw_txhash(tx)\n\n      processor =\n        processor\n        |> start_ife_from(tx, exit_id: ife_id)\n        |> piggyback_ife_from(tx_hash, 1, :input)\n        |> piggyback_ife_from(tx_hash, 2, :input)\n\n      {:ok, processor, _} =\n        Core.finalize_in_flight_exits(\n          processor,\n          [%{in_flight_exit_id: ife_id, output_index: 1, omg_data: %{piggyback_type: :input}}],\n          %{}\n        )\n\n      [_] = Core.get_active_in_flight_exits(processor)\n\n      {:ok, processor, _} =\n        Core.finalize_in_flight_exits(\n          processor,\n          [%{in_flight_exit_id: ife_id, output_index: 2, omg_data: %{piggyback_type: :input}}],\n          %{}\n        )\n\n      assert [] == Core.get_active_in_flight_exits(processor)\n    end\n\n    test \"finalizing multiple times returns an error since it is not possible\",\n         %{processor_empty: processor, transactions: [tx | _]} do\n      ife_id = 123\n      tx_hash = Transaction.raw_txhash(tx)\n\n      processor =\n        processor\n        |> start_ife_from(tx, exit_id: ife_id)\n        |> piggyback_ife_from(tx_hash, 1, :input)\n\n      finalization = %{in_flight_exit_id: ife_id, output_index: 1, omg_data: %{piggyback_type: :input}}\n      {:ok, processor, _} = Core.finalize_in_flight_exits(processor, [finalization], %{})\n      {:inactive_piggybacks_finalizing, _} = Core.finalize_in_flight_exits(processor, [finalization], %{})\n    end\n\n    test \"finalizing perserve in flights exits that are not being finalized\",\n         %{processor_empty: processor, transactions: [tx1, tx2]} do\n      ife_id1 = 123\n      tx_hash1 = Transaction.raw_txhash(tx1)\n      ife_id2 = 124\n      tx_hash2 = Transaction.raw_txhash(tx2)\n\n      processor =\n        processor\n        |> start_ife_from(tx1, exit_id: ife_id1)\n        |> start_ife_from(tx2, exit_id: ife_id2)\n        |> piggyback_ife_from(tx_hash1, 1, :input)\n\n      finalization = %{in_flight_exit_id: ife_id1, output_index: 1, omg_data: %{piggyback_type: :input}}\n      {:ok, processor, _} = Core.finalize_in_flight_exits(processor, [finalization], %{})\n      [%{txhash: ^tx_hash2}] = Core.get_active_in_flight_exits(processor)\n    end\n\n    test \"fails when unknown in-flight exit is being finalized\", %{processor_empty: processor} do\n      finalization = %{in_flight_exit_id: @exit_id, output_index: 1, omg_data: %{piggyback_type: :input}}\n\n      {:unknown_in_flight_exit, unknown_exits} = Core.finalize_in_flight_exits(processor, [finalization], %{})\n      assert unknown_exits == MapSet.new([<<@exit_id::192>>])\n    end\n\n    test \"fails when exiting an output that is not piggybacked\",\n         %{processor_empty: processor, transactions: [tx | _]} do\n      tx_hash = Transaction.raw_txhash(tx)\n      ife_id = 123\n\n      processor =\n        processor\n        |> start_ife_from(tx, exit_id: ife_id)\n        |> piggyback_ife_from(tx_hash, 1, :input)\n\n      finalization1 = %{in_flight_exit_id: ife_id, output_index: 1, omg_data: %{piggyback_type: :input}}\n      finalization2 = %{in_flight_exit_id: ife_id, output_index: 2, omg_data: %{piggyback_type: :input}}\n\n      expected_unknown_piggybacks = [\n        %{in_flight_exit_id: <<ife_id::192>>, output_index: 2, omg_data: %{piggyback_type: :input}}\n      ]\n\n      {:inactive_piggybacks_finalizing, ^expected_unknown_piggybacks} =\n        Core.finalize_in_flight_exits(processor, [finalization1, finalization2], %{})\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/exit_processor/in_flight_exit_info_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.ExitProcessor.InFlightExitInfoTest do\n  @moduledoc false\n\n  use OMG.Watcher.ExitProcessor.Case, async: true\n\n  alias OMG.Watcher.ExitProcessor.InFlightExitInfo\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.Utxo.Position\n\n  @eth <<0::160>>\n\n  describe \"get_input_utxos/1\" do\n    test \"returns a list of input utxos\" do\n      inputs1 = [\n        Position.encode({:utxo_position, 1, 0, 0}),\n        Position.encode({:utxo_position, 1, 0, 1})\n      ]\n\n      inputs2 = [Position.encode({:utxo_position, 1, 0, 2})]\n\n      ife_infos = [\n        ife_info_with_inputs(inputs1),\n        ife_info_with_inputs(inputs2)\n      ]\n\n      expected = inputs1 ++ inputs2\n\n      assert InFlightExitInfo.get_input_utxos(ife_infos) == expected\n    end\n  end\n\n  defp ife_info_with_inputs(inputs) do\n    tx =\n      Transaction.Payment.new(\n        [{1, 0, 0}],\n        [{\"alice\", @eth, 1}, {\"alice\", @eth, 2}],\n        <<0::256>>\n      )\n\n    %InFlightExitInfo{\n      tx: %Transaction.Signed{raw_tx: tx, sigs: <<1::520>>},\n      timestamp: 1,\n      contract_id: <<1::160>>,\n      eth_height: 1,\n      is_active: true,\n      input_utxos_pos: inputs\n    }\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/exit_processor/persistence_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.ExitProcessor.PersistenceTest do\n  @moduledoc \"\"\"\n  Test focused on the persistence bits of `OMG.Watcher.ExitProcessor.Core`.\n\n  The aim of this test is to ensure, that whatever state the processor ends up being in will be revived from the DB\n  \"\"\"\n\n  use ExUnitFixtures\n  use OMG.DB.RocksDBCase, async: true\n\n  alias OMG.DB.Models.PaymentExitInfo\n  alias OMG.Watcher.DevCrypto\n  alias OMG.Watcher.ExitProcessor.Core\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.Utxo\n\n  require Utxo\n\n  import OMG.Watcher.ExitProcessor.TestHelper\n\n  @default_min_exit_period_seconds 120\n  @default_child_block_interval 1000\n  @eth <<0::160>>\n\n  @utxo_pos1 Utxo.position(1, 0, 0)\n  @utxo_pos2 Utxo.position(1_000, 0, 1)\n\n  @zero_exit_id 0\n  @non_zero_exit_id 1\n  @zero_sig <<0::520>>\n\n  setup %{db_pid: db_pid} do\n    :ok = OMG.DB.initiation_multiupdate(db_pid)\n\n    alice = OMG.Watcher.TestHelper.generate_entity()\n    carol = OMG.Watcher.TestHelper.generate_entity()\n    {:ok, processor_empty} = Core.init([], [], [], @default_min_exit_period_seconds, @default_child_block_interval)\n\n    transactions = [\n      Transaction.Payment.new([{1, 0, 0}, {1, 2, 1}], [{alice.addr, @eth, 1}, {carol.addr, @eth, 2}]),\n      Transaction.Payment.new([{2, 1, 0}, {2, 2, 1}], [{alice.addr, @eth, 1}, {carol.addr, @eth, 2}])\n    ]\n\n    [txbytes1, txbytes2] = transactions |> Enum.map(&Transaction.raw_txbytes/1)\n\n    exits =\n      {[\n         %{\n           owner: alice.addr,\n           eth_height: 2,\n           exit_id: 1,\n           call_data: %{utxo_pos: Utxo.Position.encode(@utxo_pos1), output_tx: txbytes1},\n           root_chain_txhash: <<1::256>>,\n           block_timestamp: 1,\n           scheduled_finalization_time: 2\n         },\n         %{\n           owner: alice.addr,\n           eth_height: 4,\n           exit_id: 2,\n           call_data: %{utxo_pos: Utxo.Position.encode(@utxo_pos2), output_tx: txbytes2},\n           root_chain_txhash: <<2::256>>,\n           block_timestamp: 3,\n           scheduled_finalization_time: 4\n         }\n       ],\n       [\n         {true, Utxo.Position.encode(@utxo_pos1), Utxo.Position.encode(@utxo_pos1), alice.addr, 10, 0},\n         {false, Utxo.Position.encode(@utxo_pos2), Utxo.Position.encode(@utxo_pos2), alice.addr, 10, 0}\n       ]}\n\n    {:ok, %{alice: alice, carol: carol, processor_empty: processor_empty, transactions: transactions, exits: exits}}\n  end\n\n  test \"persist finalizations with mixed validities\",\n       %{processor_empty: processor, db_pid: db_pid, exits: {exit_events, statuses}} do\n    processor\n    |> persist_new_exits(exit_events, statuses, db_pid)\n    |> persist_finalize_exits({[@utxo_pos1], [@utxo_pos2]}, db_pid)\n  end\n\n  test \"persist finalizations with all valid\",\n       %{processor_empty: processor, db_pid: db_pid, exits: {exit_events, statuses}} do\n    processor\n    |> persist_new_exits(exit_events, statuses, db_pid)\n    |> persist_finalize_exits({[@utxo_pos1, @utxo_pos2], []}, db_pid)\n  end\n\n  test \"persist finalizations with all invalid\",\n       %{processor_empty: processor, db_pid: db_pid, exits: {exit_events, statuses}} do\n    processor\n    |> persist_new_exits(exit_events, statuses, db_pid)\n    |> persist_finalize_exits({[], [@utxo_pos1, @utxo_pos2]}, db_pid)\n  end\n\n  test \"persist challenges\",\n       %{processor_empty: processor, db_pid: db_pid, exits: {exit_events, statuses}} do\n    processor\n    |> persist_new_exits(exit_events, statuses, db_pid)\n    |> persist_challenge_exits([@utxo_pos1], db_pid)\n  end\n\n  test \"persist multiple challenges\",\n       %{processor_empty: processor, db_pid: db_pid, exits: {exit_events, statuses}} do\n    processor\n    |> persist_new_exits(exit_events, statuses, db_pid)\n    |> persist_challenge_exits([@utxo_pos2, @utxo_pos1], db_pid)\n  end\n\n  test \"persist started ifes regardless of status\",\n       %{processor_empty: processor, alice: alice, carol: carol, db_pid: db_pid} do\n    txs = [\n      Transaction.Payment.new([{1, 0, 0}, {1, 2, 1}], [{alice.addr, @eth, 1}]),\n      Transaction.Payment.new([{2, 1, 0}, {2, 2, 1}], [{alice.addr, @eth, 1}, {carol.addr, @eth, 2}])\n    ]\n\n    contract_statuses = [{active_ife_status(), @non_zero_exit_id}, {inactive_ife_status(), @zero_exit_id}]\n\n    processor\n    |> persist_new_ifes(txs, [[alice.priv], [alice.priv, carol.priv]], contract_statuses, db_pid)\n  end\n\n  test \"persist new challenges, responses and piggybacks\",\n       %{processor_empty: processor, alice: alice, db_pid: db_pid} do\n    tx = Transaction.Payment.new([{2, 1, 0}], [{alice.addr, @eth, 1}, {alice.addr, @eth, 2}])\n    hash = Transaction.raw_txhash(tx)\n    competing_tx = Transaction.Payment.new([{2, 1, 0}, {1, 0, 0}], [{alice.addr, @eth, 2}, {alice.addr, @eth, 1}])\n\n    challenge = %{\n      tx_hash: hash,\n      competitor_position: Utxo.Position.encode(@utxo_pos2),\n      call_data: %{\n        competing_tx: Transaction.raw_txbytes(competing_tx),\n        competing_tx_input_index: 0,\n        competing_tx_sig: @zero_sig\n      }\n    }\n\n    piggybacks1 = [\n      %{tx_hash: hash, output_index: 0, omg_data: %{piggyback_type: :input}},\n      %{tx_hash: hash, output_index: 0, omg_data: %{piggyback_type: :output}}\n    ]\n\n    piggybacks2 = [%{tx_hash: hash, output_index: 1, omg_data: %{piggyback_type: :output}}]\n\n    processor\n    |> persist_new_ifes([tx], [[alice.priv]], db_pid)\n    |> persist_new_piggybacks(piggybacks1, db_pid)\n    |> persist_new_piggybacks(piggybacks2, db_pid)\n    |> persist_new_ife_challenges([challenge], db_pid)\n    |> persist_challenge_piggybacks(piggybacks2, db_pid)\n    |> persist_challenge_piggybacks(piggybacks1, db_pid)\n    |> persist_respond_to_in_flight_exits_challenges([ife_response(tx, @utxo_pos1)], db_pid)\n  end\n\n  test \"persist ife finalizations\",\n       %{processor_empty: processor, alice: alice, db_pid: db_pid} do\n    tx = Transaction.Payment.new([{2, 1, 0}], [{alice.addr, @eth, 1}, {alice.addr, @eth, 2}])\n    hash = Transaction.raw_txhash(tx)\n\n    piggybacks1 = [\n      %{tx_hash: hash, output_index: 0, omg_data: %{piggyback_type: :input}},\n      %{tx_hash: hash, output_index: 0, omg_data: %{piggyback_type: :output}}\n    ]\n\n    piggybacks2 = [%{tx_hash: hash, output_index: 1, omg_data: %{piggyback_type: :output}}]\n\n    processor\n    |> persist_new_ifes([tx], [[alice.priv]], db_pid)\n    |> persist_new_piggybacks(piggybacks1, db_pid)\n    |> persist_new_piggybacks(piggybacks2, db_pid)\n    |> persist_finalize_ifes(\n      [%{in_flight_exit_id: @non_zero_exit_id, output_index: 0, omg_data: %{piggyback_type: :input}}],\n      db_pid\n    )\n    |> persist_finalize_ifes(\n      [\n        %{in_flight_exit_id: @non_zero_exit_id, output_index: 0, omg_data: %{piggyback_type: :output}},\n        %{in_flight_exit_id: @non_zero_exit_id, output_index: 1, omg_data: %{piggyback_type: :output}}\n      ],\n      db_pid\n    )\n  end\n\n  # mimics `&OMG.Watcher.ExitProcessor.init/1`\n  defp state_from(db_pid) do\n    {:ok, db_exits} = PaymentExitInfo.all_exit_infos(db_pid)\n    {:ok, db_ifes} = PaymentExitInfo.all_in_flight_exits_infos(db_pid)\n    {:ok, db_competitors} = OMG.DB.competitors_info(db_pid)\n\n    {:ok, state} =\n      Core.init(db_exits, db_ifes, db_competitors, @default_min_exit_period_seconds, @default_child_block_interval)\n\n    state\n  end\n\n  defp persist_common(processor, db_updates, db_pid) do\n    assert :ok = OMG.DB.multi_update(db_updates, db_pid)\n    assert processor == state_from(db_pid)\n    processor\n  end\n\n  defp persist_new_exits(processor, exit_events, contract_statuses, db_pid) do\n    {processor, db_updates} = Core.new_exits(processor, exit_events, contract_statuses)\n    persist_common(processor, db_updates, db_pid)\n  end\n\n  defp persist_finalize_exits(processor, validities, db_pid) do\n    {processor, db_updates} = Core.finalize_exits(processor, validities)\n    persist_common(processor, db_updates, db_pid)\n  end\n\n  defp persist_challenge_exits(processor, utxo_positions, db_pid) do\n    {processor, db_updates} =\n      Core.challenge_exits(processor, utxo_positions |> Enum.map(&%{utxo_pos: Utxo.Position.encode(&1)}))\n\n    persist_common(processor, db_updates, db_pid)\n  end\n\n  defp persist_new_ifes(processor, txs, priv_keys, statuses \\\\ nil, db_pid) do\n    in_flight_exit_events =\n      txs\n      |> Enum.zip(priv_keys)\n      |> Enum.map(fn {tx, keys} -> {tx, DevCrypto.sign(tx, keys)} end)\n      |> Enum.map(fn {tx, signed_tx} -> ife_event(tx, sigs: signed_tx.sigs) end)\n\n    statuses = statuses || List.duplicate({active_ife_status(), @non_zero_exit_id}, length(in_flight_exit_events))\n    {processor, db_updates} = Core.new_in_flight_exits(processor, in_flight_exit_events, statuses)\n    persist_common(processor, db_updates, db_pid)\n  end\n\n  defp persist_new_piggybacks(processor, piggybacks, db_pid) do\n    {processor, db_updates} = Core.new_piggybacks(processor, piggybacks)\n    persist_common(processor, db_updates, db_pid)\n  end\n\n  defp persist_new_ife_challenges(processor, challenges, db_pid) do\n    {processor, db_updates} = Core.new_ife_challenges(processor, challenges)\n    persist_common(processor, db_updates, db_pid)\n  end\n\n  defp persist_respond_to_in_flight_exits_challenges(processor, challenges, db_pid) do\n    {processor, db_updates} = Core.respond_to_in_flight_exits_challenges(processor, challenges)\n    persist_common(processor, db_updates, db_pid)\n  end\n\n  defp persist_challenge_piggybacks(processor, piggybacks, db_pid) do\n    {processor, db_updates} = Core.challenge_piggybacks(processor, piggybacks)\n    persist_common(processor, db_updates, db_pid)\n  end\n\n  defp persist_finalize_ifes(processor, finalizations, db_pid) do\n    {:ok, processor, db_updates} = Core.finalize_in_flight_exits(processor, finalizations, %{})\n    persist_common(processor, db_updates, db_pid)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/exit_processor/piggyback_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.ExitProcessor.PiggybackTest do\n  @moduledoc \"\"\"\n  Test of the logic of exit processor - detecting conditions related to piggybacks\n  \"\"\"\n  # this is where the setup comes from!!!\n  use OMG.Watcher.ExitProcessor.Case, async: true\n\n  alias OMG.Watcher.Block\n  alias OMG.Watcher.Event\n  alias OMG.Watcher.ExitProcessor\n  alias OMG.Watcher.ExitProcessor.Core\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.TestHelper\n  alias OMG.Watcher.Utxo\n\n  require Utxo\n\n  import OMG.Watcher.ExitProcessor.TestHelper\n\n  # needs to match up with the default from `ExitProcessor.Case` :(\n  @exit_id 9876\n\n  @eth <<0::160>>\n\n  describe \"sanity checks\" do\n    test \"throwing when unknown piggyback events arrive\", %{processor_filled: processor, ife_tx_hashes: [ife_id | _]} do\n      non_existent_exit_id = <<0>>\n      index_beyond_bound = 4\n      catch_error(piggyback_ife_from(processor, non_existent_exit_id, 0, :input))\n      catch_error(piggyback_ife_from(processor, ife_id, index_beyond_bound, :output))\n      # cannot piggyback twice the same output\n      updated_processor = piggyback_ife_from(processor, ife_id, 0, :input)\n      catch_error(piggyback_ife_from(updated_processor, ife_id, 0, :input))\n    end\n\n    test \"can process empty piggybacks and challenges\", %{processor_empty: empty, processor_filled: filled} do\n      {^empty, []} = Core.new_piggybacks(empty, [])\n      {^filled, []} = Core.new_piggybacks(filled, [])\n      {^empty, []} = Core.challenge_piggybacks(empty, [])\n      {^filled, []} = Core.challenge_piggybacks(filled, [])\n    end\n\n    test \"can process new piggybacks in batch\", %{processor_filled: processor, ife_tx_hashes: [tx_hash1, tx_hash2]} do\n      updated_processor =\n        processor\n        |> piggyback_ife_from(tx_hash1, 0, :input)\n        |> piggyback_ife_from(tx_hash2, 0, :input)\n\n      assert {^updated_processor, _} =\n               Core.new_piggybacks(processor, [\n                 %{tx_hash: tx_hash1, output_index: 0, omg_data: %{piggyback_type: :input}},\n                 %{tx_hash: tx_hash2, output_index: 0, omg_data: %{piggyback_type: :input}}\n               ])\n    end\n  end\n\n  test \"forgets challenged piggybacks\",\n       %{processor_filled: processor, ife_tx_hashes: [tx_hash1, tx_hash2]} do\n    processor =\n      processor\n      |> piggyback_ife_from(tx_hash1, 0, :input)\n      |> piggyback_ife_from(tx_hash2, 0, :input)\n\n    # sanity: there are some piggybacks after piggybacking, to be removed later\n    assert [%{piggybacked_inputs: [_]}, %{piggybacked_inputs: [_]}] = Core.get_active_in_flight_exits(processor)\n\n    {processor, _} =\n      Core.challenge_piggybacks(processor, [%{tx_hash: tx_hash1, output_index: 0, omg_data: %{piggyback_type: :input}}])\n\n    assert [%{txhash: ^tx_hash1, piggybacked_inputs: []}, %{piggybacked_inputs: [0]}] =\n             Core.get_active_in_flight_exits(processor)\n             |> Enum.sort_by(&length(&1.piggybacked_inputs))\n  end\n\n  test \"can open and challenge two piggybacks at one call\",\n       %{processor_filled: processor, ife_tx_hashes: [tx_hash1, tx_hash2]} do\n    events = [\n      %{tx_hash: tx_hash1, output_index: 0, omg_data: %{piggyback_type: :input}},\n      %{tx_hash: tx_hash2, output_index: 0, omg_data: %{piggyback_type: :input}}\n    ]\n\n    {processor, _} = Core.new_piggybacks(processor, events)\n    # sanity: there are some piggybacks after piggybacking, to be removed later\n    assert [%{piggybacked_inputs: [_]}, %{piggybacked_inputs: [_]}] = Core.get_active_in_flight_exits(processor)\n    {processor, _} = Core.challenge_piggybacks(processor, events)\n\n    assert [%{piggybacked_inputs: []}, %{piggybacked_inputs: []}] = Core.get_active_in_flight_exits(processor)\n  end\n\n  describe \"available piggybacks\" do\n    test \"detects multiple available piggybacks, with all the fields\",\n         %{processor_filled: processor, transactions: [tx1, tx2], alice: alice, carol: carol} do\n      txbytes_1 = Transaction.raw_txbytes(tx1)\n      txbytes_2 = Transaction.raw_txbytes(tx2)\n\n      assert {:ok, events} =\n               %ExitProcessor.Request{blknum_now: 5000, eth_height_now: 5}\n               |> Core.check_validity(processor)\n\n      assert_events(events, [\n        %Event.PiggybackAvailable{\n          available_inputs: [%{address: alice.addr, index: 0}, %{address: carol.addr, index: 1}],\n          available_outputs: [%{address: alice.addr, index: 0}, %{address: carol.addr, index: 1}],\n          txbytes: txbytes_1\n        },\n        %Event.PiggybackAvailable{\n          available_inputs: [%{address: alice.addr, index: 0}, %{address: carol.addr, index: 1}],\n          available_outputs: [%{address: alice.addr, index: 0}, %{address: carol.addr, index: 1}],\n          txbytes: txbytes_2\n        }\n      ])\n    end\n\n    test \"detects available piggyback because tx not seen in valid block, regardless of competitors\",\n         %{processor_empty: processor, alice: alice} do\n      # testing this because everywhere else, the test fixtures always imply competitors\n      tx = TestHelper.create_recovered([{1, 0, 0, alice}], [{alice, @eth, 1}])\n      txbytes = txbytes(tx)\n      processor = processor |> start_ife_from(tx)\n\n      assert {:ok, [%Event.PiggybackAvailable{txbytes: ^txbytes}]} =\n               %ExitProcessor.Request{blknum_now: 5000, eth_height_now: 5}\n               |> Core.check_validity(processor)\n    end\n\n    test \"detects available piggyback correctly, even if signed multiple times\",\n         %{processor_empty: processor, alice: alice} do\n      # there is leeway in the contract, that allows IFE transactions to hold non-zero signatures for zero-inputs\n      # we want to be sure that this doesn't crash the `ExitProcessor`\n      tx = Transaction.Payment.new([{1, 0, 0}], [{alice.addr, @eth, 1}])\n      txbytes = txbytes(tx)\n      # superfluous signatures\n      %{sigs: sigs} = signed_tx = OMG.Watcher.DevCrypto.sign(tx, [alice.priv])\n      processor = processor |> start_ife_from(signed_tx, sigs: sigs)\n\n      assert {:ok, [%Event.PiggybackAvailable{txbytes: ^txbytes}]} =\n               %ExitProcessor.Request{blknum_now: 5000, eth_height_now: 5}\n               |> Core.check_validity(processor)\n    end\n\n    test \"doesn't detect available piggybacks because txs seen in valid block\",\n         %{processor_filled: processor, transactions: [tx1, tx2]} do\n      txbytes2 = txbytes(tx2)\n\n      request = %ExitProcessor.Request{\n        blknum_now: 5000,\n        eth_height_now: 5,\n        ife_input_spending_blocks_result: [Block.hashed_txs_at([tx1], 3000)]\n      }\n\n      processor = processor |> Core.find_ifes_in_blocks(request)\n      assert {:ok, [%Event.PiggybackAvailable{txbytes: ^txbytes2}]} = request |> Core.check_validity(processor)\n    end\n\n    test \"transaction with different input/output owners\",\n         %{alice: alice, bob: bob, carol: carol, processor_empty: processor} do\n      tx = TestHelper.create_recovered([{1, 0, 0, alice}, {1, 2, 1, bob}], [{carol, @eth, 1}])\n      alice_addr = alice.addr\n      bob_addr = bob.addr\n      carol_addr = carol.addr\n      txbytes = txbytes(tx)\n      processor = processor |> start_ife_from(tx)\n\n      assert {:ok,\n              [\n                %Event.PiggybackAvailable{\n                  available_inputs: [%{address: ^alice_addr, index: 0}, %{address: ^bob_addr, index: 1}],\n                  available_outputs: [%{address: ^carol_addr, index: 0}],\n                  txbytes: ^txbytes\n                }\n              ]} =\n               check_validity_filtered(%ExitProcessor.Request{blknum_now: 1000, eth_height_now: 5}, processor,\n                 only: [Event.PiggybackAvailable]\n               )\n    end\n\n    test \"when input is already piggybacked, it is not reported in piggyback available event\",\n         %{alice: alice, processor_empty: processor} do\n      tx = TestHelper.create_recovered([{1, 0, 0, alice}, {1, 2, 1, alice}], [{alice, @eth, 1}])\n      tx_hash = Transaction.raw_txhash(tx)\n      processor = processor |> start_ife_from(tx) |> piggyback_ife_from(tx_hash, 0, :input)\n\n      assert {:ok,\n              [\n                %Event.PiggybackAvailable{\n                  available_inputs: [%{index: 1}],\n                  available_outputs: [%{index: 0}]\n                }\n              ]} =\n               check_validity_filtered(%ExitProcessor.Request{blknum_now: 1000, eth_height_now: 5}, processor,\n                 only: [Event.PiggybackAvailable]\n               )\n    end\n\n    test \"when output is already piggybacked, it is not reported in piggyback available event\",\n         %{alice: alice, processor_empty: processor} do\n      tx = TestHelper.create_recovered([{1, 0, 0, alice}, {1, 2, 1, alice}], [{alice, @eth, 1}])\n      tx_hash = Transaction.raw_txhash(tx)\n      processor = processor |> start_ife_from(tx) |> piggyback_ife_from(tx_hash, 0, :output)\n\n      assert {:ok,\n              [\n                %Event.PiggybackAvailable{\n                  available_inputs: [%{index: 0}, %{index: 1}],\n                  available_outputs: []\n                }\n              ]} =\n               check_validity_filtered(%ExitProcessor.Request{blknum_now: 1000, eth_height_now: 5}, processor,\n                 only: [Event.PiggybackAvailable]\n               )\n    end\n\n    test \"when output is already piggybacked, it is not reported in piggyback available event, even if challenged\",\n         %{alice: alice, processor_empty: processor} do\n      tx = TestHelper.create_recovered([{1, 0, 0, alice}, {1, 2, 1, alice}], [{alice, @eth, 1}])\n      tx_hash = Transaction.raw_txhash(tx)\n\n      {processor, _} =\n        processor\n        |> start_ife_from(tx)\n        |> piggyback_ife_from(tx_hash, 0, :output)\n        |> Core.challenge_piggybacks([%{tx_hash: tx_hash, output_index: 0, omg_data: %{piggyback_type: :output}}])\n\n      assert {:ok,\n              [\n                %Event.PiggybackAvailable{\n                  available_inputs: [%{index: 0}, %{index: 1}],\n                  available_outputs: []\n                }\n              ]} =\n               check_validity_filtered(%ExitProcessor.Request{blknum_now: 1000, eth_height_now: 5}, processor,\n                 only: [Event.PiggybackAvailable]\n               )\n    end\n\n    test \"when ife is finalized, it's outputs are not reported as available for piggyback\",\n         %{alice: alice, processor_empty: processor} do\n      tx = TestHelper.create_recovered([{1, 0, 0, alice}, {1, 2, 1, alice}], [{alice, @eth, 1}])\n      tx_hash = Transaction.raw_txhash(tx)\n      processor = processor |> start_ife_from(tx) |> piggyback_ife_from(tx_hash, 0, :input)\n      finalization = %{in_flight_exit_id: @exit_id, output_index: 0, omg_data: %{piggyback_type: :input}}\n      {:ok, processor, _} = Core.finalize_in_flight_exits(processor, [finalization], %{})\n\n      assert {:ok, []} =\n               check_validity_filtered(%ExitProcessor.Request{blknum_now: 1000, eth_height_now: 5}, processor,\n                 only: [Event.PiggybackAvailable]\n               )\n    end\n\n    test \"challenged IFEs emit the same piggybacks as canonical ones\",\n         %{processor_filled: processor, transactions: [tx | _], competing_tx: comp} do\n      assert {:ok, events_canonical} =\n               Core.check_validity(%ExitProcessor.Request{blknum_now: 1000, eth_height_now: 5}, processor)\n\n      {challenged_processor, _} = Core.new_ife_challenges(processor, [ife_challenge(tx, comp)])\n\n      assert {:ok, events_challenged} =\n               Core.check_validity(%ExitProcessor.Request{blknum_now: 5000, eth_height_now: 5}, challenged_processor)\n\n      assert_events(events_canonical, events_challenged)\n    end\n  end\n\n  describe \"evaluates correctness of new piggybacks\" do\n    test \"no event if input double-spent but not piggybacked\",\n         %{processor_filled: processor, competing_tx: comp} do\n      processor = processor |> start_ife_from(comp)\n\n      assert {:ok, []} =\n               %ExitProcessor.Request{blknum_now: 5000, eth_height_now: 5}\n               |> check_validity_filtered(processor, only: [Event.InvalidPiggyback])\n    end\n\n    test \"no event if output spent but not piggybacked\",\n         %{alice: alice, processor_filled: processor, transactions: [tx | _]} do\n      tx_blknum = 3000\n\n      # 2. transaction which spends that piggybacked output\n      comp = TestHelper.create_recovered([{tx_blknum, 0, 0, alice}], [{alice, @eth, 1}])\n\n      request = %ExitProcessor.Request{\n        blknum_now: 5000,\n        eth_height_now: 5,\n        ife_input_spending_blocks_result: [Block.hashed_txs_at([tx], tx_blknum)]\n      }\n\n      # 3. stuff happens in the contract, but NO PIGGYBACK!\n      processor = processor |> start_ife_from(comp) |> Core.find_ifes_in_blocks(request)\n\n      assert {:ok, []} =\n               %ExitProcessor.Request{blknum_now: 5000, eth_height_now: 5}\n               |> check_validity_filtered(processor, only: [Event.InvalidPiggyback])\n    end\n\n    test \"detects double-spend of an input, found in IFE\",\n         %{processor_filled: state, transactions: [tx | _], competing_tx: comp, ife_tx_hashes: [ife_id | _]} do\n      txbytes = txbytes(tx)\n      {comp_txbytes, other_sig} = {txbytes(comp), sig(comp, 1)}\n      state = state |> start_ife_from(comp) |> piggyback_ife_from(ife_id, 0, :input)\n      request = %ExitProcessor.Request{blknum_now: 1000, eth_height_now: 5}\n\n      assert {:ok, [%Event.InvalidPiggyback{txbytes: ^txbytes, inputs: [0], outputs: []}]} =\n               check_validity_filtered(request, state, only: [Event.InvalidPiggyback])\n\n      assert {:ok,\n              %{\n                in_flight_input_index: 0,\n                in_flight_txbytes: ^txbytes,\n                spending_txbytes: ^comp_txbytes,\n                spending_input_index: 1,\n                spending_sig: ^other_sig\n              }} = Core.get_input_challenge_data(request, state, txbytes, 0)\n    end\n\n    test \"detects double-spend of an input, found in IFE, even if finalized\",\n         %{processor_filled: state, transactions: [tx | _], competing_tx: comp, ife_tx_hashes: [tx_hash | _]} do\n      txbytes = txbytes(tx)\n      # this comes from `ExitProcessor.Case` and could use some improvement to not be so dispersed\n      exit_id = 1\n\n      {:ok, state, _} =\n        state\n        |> start_ife_from(comp)\n        |> piggyback_ife_from(tx_hash, 0, :input)\n        |> Core.finalize_in_flight_exits(\n          [%{in_flight_exit_id: exit_id, output_index: 0, omg_data: %{piggyback_type: :input}}],\n          %{}\n        )\n\n      request = %ExitProcessor.Request{blknum_now: 1000, eth_height_now: 5}\n\n      assert {:ok, [%Event.InvalidPiggyback{txbytes: ^txbytes, inputs: [0], outputs: []}]} =\n               check_validity_filtered(request, state, only: [Event.InvalidPiggyback])\n    end\n\n    test \"doesn't detect double-spend of an input, found in IFE, if challenged\",\n         %{processor_filled: state, transactions: [tx | _], competing_tx: comp, ife_tx_hashes: [tx_hash | _]} do\n      txbytes = txbytes(tx)\n\n      {state, _} =\n        state\n        |> start_ife_from(comp)\n        |> piggyback_ife_from(tx_hash, 0, :input)\n        |> Core.challenge_piggybacks([%{tx_hash: tx_hash, output_index: 0, omg_data: %{piggyback_type: :input}}])\n\n      request = %ExitProcessor.Request{blknum_now: 1000, eth_height_now: 5}\n\n      assert {:ok, []} = check_validity_filtered(request, state, only: [Event.InvalidPiggyback])\n\n      assert {:error, :no_double_spend_on_particular_piggyback} =\n               Core.get_input_challenge_data(request, state, txbytes, 0)\n    end\n\n    test \"detects double-spend of an input, found in a block\",\n         %{processor_filled: state, transactions: [tx | _], competing_tx: comp, ife_tx_hashes: [ife_id | _]} do\n      txbytes = txbytes(tx)\n      {comp_txbytes, comp_sig} = {txbytes(comp), sig(comp, 1)}\n\n      comp_blknum = 4000\n\n      request = %ExitProcessor.Request{\n        blknum_now: 5000,\n        eth_height_now: 5,\n        blocks_result: [Block.hashed_txs_at([comp], comp_blknum)]\n      }\n\n      state = state |> piggyback_ife_from(ife_id, 0, :input) |> Core.find_ifes_in_blocks(request)\n\n      assert {:ok, [%Event.InvalidPiggyback{txbytes: ^txbytes, inputs: [0], outputs: []}]} =\n               check_validity_filtered(request, state, only: [Event.InvalidPiggyback])\n\n      assert {:ok,\n              %{\n                in_flight_input_index: 0,\n                in_flight_txbytes: ^txbytes,\n                spending_txbytes: ^comp_txbytes,\n                spending_input_index: 1,\n                spending_sig: ^comp_sig\n              }} = Core.get_input_challenge_data(request, state, txbytes, 0)\n    end\n\n    test \"detects double-spend of an output, found in a IFE\",\n         %{alice: alice, processor_filled: state, transactions: [tx | _], ife_tx_hashes: [ife_id | _]} do\n      # 1. transaction which is, ife'd, output piggybacked, and included in a block\n      txbytes = txbytes(tx)\n      tx_blknum = 3000\n\n      # 2. transaction which spends that piggybacked output\n      comp = TestHelper.create_recovered([{tx_blknum, 0, 0, alice}], [{alice, @eth, 1}])\n      {comp_txbytes, comp_signature} = {txbytes(comp), sig(comp)}\n\n      request = %ExitProcessor.Request{\n        blknum_now: 5000,\n        eth_height_now: 5,\n        ife_input_spending_blocks_result: [Block.hashed_txs_at([tx], tx_blknum)]\n      }\n\n      # 3. stuff happens in the contract\n      state =\n        state |> start_ife_from(comp) |> piggyback_ife_from(ife_id, 0, :output) |> Core.find_ifes_in_blocks(request)\n\n      assert {:ok, [%Event.InvalidPiggyback{txbytes: ^txbytes, inputs: [], outputs: [0]}]} =\n               check_validity_filtered(request, state, only: [Event.InvalidPiggyback])\n\n      assert {:ok,\n              %{\n                in_flight_output_pos: Utxo.position(^tx_blknum, 0, 0),\n                in_flight_proof: proof_bytes,\n                in_flight_txbytes: ^txbytes,\n                spending_txbytes: ^comp_txbytes,\n                spending_input_index: 0,\n                spending_sig: ^comp_signature\n              }} = Core.get_output_challenge_data(request, state, txbytes, 0)\n\n      assert_proof_sound(proof_bytes)\n    end\n\n    test \"detects that invalid piggyback becomes unchalleneged exit when sla period passes\",\n         %{alice: alice, processor_filled: state, transactions: [tx | _], ife_tx_hashes: [ife_id | _]} do\n      # 1. transaction which is, ife'd, output piggybacked, and included in a block\n      txbytes = txbytes(tx)\n      tx_blknum = 3000\n\n      # 2. transaction which spends that piggybacked output\n      comp = TestHelper.create_recovered([{tx_blknum, 0, 0, alice}], [{alice, @eth, 1}])\n\n      request = %ExitProcessor.Request{\n        blknum_now: 5000,\n        eth_height_now: 5 + state.sla_margin,\n        ife_input_spending_blocks_result: [Block.hashed_txs_at([tx], tx_blknum)]\n      }\n\n      # 3. stuff happens in the contract\n      state =\n        state |> start_ife_from(comp) |> piggyback_ife_from(ife_id, 0, :output) |> Core.find_ifes_in_blocks(request)\n\n      assert {{:error, :unchallenged_exit},\n              [\n                %Event.UnchallengedPiggyback{txbytes: ^txbytes, inputs: [], outputs: [0]}\n              ]} = check_validity_filtered(request, state, only: [Event.UnchallengedPiggyback])\n    end\n\n    test \"detects double-spend of an output, found in a IFE, even if finalized\",\n         %{alice: alice, processor_filled: state, transactions: [tx | _], ife_tx_hashes: [tx_hash | _]} do\n      txbytes = txbytes(tx)\n      tx_blknum = 3000\n      # this comes from `ExitProcessor.Case` and could use some improvement to not be so dispersed\n      exit_id = 1\n\n      comp = TestHelper.create_recovered([{tx_blknum, 0, 0, alice}], [{alice, @eth, 1}])\n\n      request = %ExitProcessor.Request{\n        blknum_now: 5000,\n        eth_height_now: 5,\n        ife_input_spending_blocks_result: [Block.hashed_txs_at([tx], tx_blknum)]\n      }\n\n      {:ok, state, _} =\n        state\n        |> start_ife_from(comp)\n        |> piggyback_ife_from(tx_hash, 0, :output)\n        |> Core.find_ifes_in_blocks(request)\n        |> Core.finalize_in_flight_exits(\n          [%{in_flight_exit_id: exit_id, output_index: 0, omg_data: %{piggyback_type: :output}}],\n          %{}\n        )\n\n      assert {:ok, [%Event.InvalidPiggyback{txbytes: ^txbytes, inputs: [], outputs: [0]}]} =\n               check_validity_filtered(request, state, only: [Event.InvalidPiggyback])\n    end\n\n    test \"doesn't detect double-spend of an output, found in a IFE, if challenged\",\n         %{alice: alice, processor_filled: state, transactions: [tx | _], ife_tx_hashes: [tx_hash | _]} do\n      txbytes = txbytes(tx)\n      tx_blknum = 3000\n\n      comp = TestHelper.create_recovered([{tx_blknum, 0, 0, alice}], [{alice, @eth, 1}])\n\n      request = %ExitProcessor.Request{\n        blknum_now: 5000,\n        eth_height_now: 5,\n        ife_input_spending_blocks_result: [Block.hashed_txs_at([tx], tx_blknum)]\n      }\n\n      {state, _} =\n        state\n        |> start_ife_from(comp)\n        |> piggyback_ife_from(tx_hash, 0, :output)\n        |> Core.find_ifes_in_blocks(request)\n        |> Core.challenge_piggybacks([%{tx_hash: tx_hash, output_index: 0, omg_data: %{piggyback_type: :output}}])\n\n      assert {:ok, []} = check_validity_filtered(request, state, only: [Event.InvalidPiggyback])\n\n      assert {:error, :no_double_spend_on_particular_piggyback} =\n               Core.get_output_challenge_data(request, state, txbytes, 0)\n    end\n\n    test \"detects and proves double-spend of an output, found in a block\",\n         %{alice: alice, processor_filled: state, transactions: [tx | _], ife_tx_hashes: [ife_id | _]} do\n      # this time, the piggybacked-output-spending tx is going to be included in a block, which requires more back&forth\n      # 1. transaction which is, ife'd, output piggybacked, and included in a block\n      txbytes = txbytes(tx)\n      tx_blknum = 3000\n\n      # 2. transaction which spends that piggybacked output\n      comp = TestHelper.create_recovered([{tx_blknum, 0, 0, alice}], [{alice, @eth, 1}])\n      {comp_txbytes, comp_signature} = {txbytes(comp), sig(comp)}\n\n      comp_blknum = 4000\n\n      request = %ExitProcessor.Request{\n        blknum_now: 5000,\n        eth_height_now: 5,\n        ife_input_spending_blocks_result: [Block.hashed_txs_at([tx], tx_blknum)],\n        blocks_result: [Block.hashed_txs_at([comp], comp_blknum)]\n      }\n\n      # 3. stuff happens in the contract\n      state = state |> piggyback_ife_from(ife_id, 0, :output) |> Core.find_ifes_in_blocks(request)\n\n      assert {:ok, [%Event.InvalidPiggyback{txbytes: ^txbytes, inputs: [], outputs: [0]}]} =\n               check_validity_filtered(request, state, only: [Event.InvalidPiggyback])\n\n      assert {:ok,\n              %{\n                in_flight_output_pos: Utxo.position(^tx_blknum, 0, 0),\n                in_flight_proof: proof_bytes,\n                in_flight_txbytes: ^txbytes,\n                spending_txbytes: ^comp_txbytes,\n                spending_input_index: 0,\n                spending_sig: ^comp_signature\n              }} = Core.get_output_challenge_data(request, state, txbytes, 0)\n\n      assert_proof_sound(proof_bytes)\n    end\n\n    test \"detects and proves double-spend of an output, found in a block, various output indices\",\n         %{carol: carol, processor_filled: state, transactions: [tx | _], ife_tx_hashes: [ife_id | _]} do\n      txbytes = txbytes(tx)\n      tx_blknum = 3000\n\n      comp = TestHelper.create_recovered([{tx_blknum, 0, 1, carol}], [{carol, @eth, 1}])\n      {comp_txbytes, comp_signature} = {txbytes(comp), sig(comp)}\n\n      comp_blknum = 4000\n\n      request = %ExitProcessor.Request{\n        blknum_now: 5000,\n        eth_height_now: 5,\n        ife_input_spending_blocks_result: [Block.hashed_txs_at([tx], tx_blknum)],\n        blocks_result: [Block.hashed_txs_at([comp], comp_blknum)]\n      }\n\n      state = state |> piggyback_ife_from(ife_id, 1, :output) |> Core.find_ifes_in_blocks(request)\n\n      assert {:ok, [%Event.InvalidPiggyback{txbytes: ^txbytes, inputs: [], outputs: [1]}]} =\n               check_validity_filtered(request, state, only: [Event.InvalidPiggyback])\n\n      assert {:ok,\n              %{\n                in_flight_output_pos: Utxo.position(^tx_blknum, 0, 1),\n                in_flight_proof: proof_bytes,\n                in_flight_txbytes: ^txbytes,\n                spending_txbytes: ^comp_txbytes,\n                spending_input_index: 0,\n                spending_sig: ^comp_signature\n              }} = Core.get_output_challenge_data(request, state, txbytes, 1)\n\n      assert_proof_sound(proof_bytes)\n    end\n\n    test \"detects and proves double-spend of an output, found in a block, various spending input indices\",\n         %{alice: alice, carol: carol, processor_filled: state, transactions: [tx | _], ife_tx_hashes: [ife_id | _]} do\n      txbytes = txbytes(tx)\n      tx_blknum = 3000\n\n      comp = TestHelper.create_recovered([{tx_blknum, 0, 0, alice}, {tx_blknum, 0, 1, carol}], [{alice, @eth, 1}])\n      {comp_txbytes, comp_signature} = {txbytes(comp), sig(comp, 1)}\n\n      comp_blknum = 4000\n\n      request = %ExitProcessor.Request{\n        blknum_now: 5000,\n        eth_height_now: 5,\n        ife_input_spending_blocks_result: [Block.hashed_txs_at([tx], tx_blknum)],\n        blocks_result: [Block.hashed_txs_at([comp], comp_blknum)]\n      }\n\n      state = state |> piggyback_ife_from(ife_id, 1, :output) |> Core.find_ifes_in_blocks(request)\n\n      assert {:ok, [%Event.InvalidPiggyback{txbytes: ^txbytes, inputs: [], outputs: [1]}]} =\n               check_validity_filtered(request, state, only: [Event.InvalidPiggyback])\n\n      assert {:ok,\n              %{\n                in_flight_output_pos: Utxo.position(^tx_blknum, 0, 1),\n                in_flight_proof: proof_bytes,\n                in_flight_txbytes: ^txbytes,\n                spending_txbytes: ^comp_txbytes,\n                spending_input_index: 1,\n                spending_sig: ^comp_signature\n              }} = Core.get_output_challenge_data(request, state, txbytes, 1)\n\n      assert_proof_sound(proof_bytes)\n    end\n\n    test \"proves and proves double-spend of an output, found in a block, for various inclusion positions\",\n         %{alice: alice, bob: bob, processor_filled: state, transactions: [tx | _], ife_tx_hashes: [ife_id | _]} do\n      other_tx = TestHelper.create_recovered([{10_000, 0, 0, bob}], [{alice, @eth, 1}])\n      txbytes = txbytes(tx)\n      tx_blknum = 3000\n\n      comp = TestHelper.create_recovered([{tx_blknum, 1, 0, alice}], [{alice, @eth, 1}])\n      {comp_txbytes, comp_signature} = {txbytes(comp), sig(comp)}\n\n      comp_blknum = 4000\n\n      request = %ExitProcessor.Request{\n        blknum_now: 5000,\n        eth_height_now: 5,\n        ife_input_spending_blocks_result: [Block.hashed_txs_at([other_tx, tx], tx_blknum)],\n        blocks_result: [Block.hashed_txs_at([comp], comp_blknum)]\n      }\n\n      state = state |> piggyback_ife_from(ife_id, 0, :output) |> Core.find_ifes_in_blocks(request)\n\n      assert {:ok, [%Event.InvalidPiggyback{txbytes: ^txbytes, inputs: [], outputs: [0]}]} =\n               check_validity_filtered(request, state, only: [Event.InvalidPiggyback])\n\n      assert {:ok,\n              %{\n                in_flight_output_pos: Utxo.position(^tx_blknum, 1, 0),\n                in_flight_proof: proof_bytes,\n                in_flight_txbytes: ^txbytes,\n                spending_txbytes: ^comp_txbytes,\n                spending_input_index: 0,\n                spending_sig: ^comp_signature\n              }} = Core.get_output_challenge_data(request, state, txbytes, 0)\n\n      assert_proof_sound(proof_bytes)\n    end\n\n    test \"detects no double-spend of an input, if a different input is being spent in block\",\n         %{processor_filled: state, competing_tx: comp, ife_tx_hashes: [ife_id | _]} do\n      # NOTE: the piggybacked index is the second one, compared to the invalid piggyback situation\n      request = %ExitProcessor.Request{\n        blknum_now: 5000,\n        eth_height_now: 5,\n        blocks_result: [Block.hashed_txs_at([comp], 4000)]\n      }\n\n      state = state |> piggyback_ife_from(ife_id, 1, :input) |> Core.find_ifes_in_blocks(request)\n\n      assert {:ok, []} = check_validity_filtered(request, state, only: [Event.InvalidPiggyback])\n    end\n\n    test \"detects no double-spend of an output, if a different output is being spent in block\",\n         %{alice: alice, processor_filled: state, transactions: [tx | _], ife_tx_hashes: [ife_id | _]} do\n      # NOTE: the piggybacked index is the second one, compared to the invalid piggyback situation\n      tx_blknum = 3000\n\n      # 2. transaction which _doesn't_ spend that piggybacked output\n      comp = TestHelper.create_recovered([{tx_blknum, 0, 0, alice}], [{alice, @eth, 1}])\n\n      request = %ExitProcessor.Request{\n        blknum_now: 5000,\n        eth_height_now: 5,\n        ife_input_spending_blocks_result: [Block.hashed_txs_at([tx], tx_blknum)],\n        blocks_result: [Block.hashed_txs_at([comp], 4000)]\n      }\n\n      state = state |> piggyback_ife_from(ife_id, 1, :output) |> Core.find_ifes_in_blocks(request)\n\n      assert {:ok, []} = check_validity_filtered(request, state, only: [Event.InvalidPiggyback])\n    end\n\n    test \"does not look into ife_input_spending_blocks_result when it should not\",\n         %{processor_filled: state, transactions: [tx | _], ife_tx_hashes: [ife_id | _]} do\n      request = %ExitProcessor.Request{\n        blknum_now: 5000,\n        eth_height_now: 5,\n        ife_input_spending_blocks_result: [Block.hashed_txs_at([tx], 3000)]\n      }\n\n      state = state |> piggyback_ife_from(ife_id, 0, :output) |> Core.find_ifes_in_blocks(request)\n\n      # now zero out the prior result to make a sanity check of well-behaving wrt. to the database results\n      request = %{request | blocks_result: [], ife_input_spending_blocks_result: nil}\n      assert {:ok, []} = check_validity_filtered(request, state, only: [Event.InvalidPiggyback])\n      assert {:error, _} = Core.get_output_challenge_data(request, state, txbytes(tx), 0)\n    end\n\n    test \"detects multiple double-spends in single IFE, correctly as more piggybacks appear\",\n         %{alice: alice, processor_filled: state, transactions: [tx | _], ife_tx_hashes: [ife_id | _]} do\n      tx_blknum = 3000\n      txbytes = txbytes(tx)\n\n      comp =\n        TestHelper.create_recovered(\n          [{1, 0, 0, alice}, {1, 2, 1, alice}, {tx_blknum, 0, 0, alice}, {tx_blknum, 0, 1, alice}],\n          [{alice, @eth, 1}]\n        )\n\n      {comp_txbytes, alice_sig} = {txbytes(comp), sig(comp)}\n\n      request = %ExitProcessor.Request{\n        blknum_now: 4000,\n        eth_height_now: 5,\n        ife_input_spending_blocks_result: [Block.hashed_txs_at([tx], tx_blknum)]\n      }\n\n      state =\n        state |> start_ife_from(comp) |> piggyback_ife_from(ife_id, 0, :input) |> Core.find_ifes_in_blocks(request)\n\n      assert {:ok, [%Event.InvalidPiggyback{txbytes: ^txbytes, inputs: [0], outputs: []}]} =\n               check_validity_filtered(request, state, only: [Event.InvalidPiggyback])\n\n      state = state |> piggyback_ife_from(ife_id, 1, :input)\n\n      assert {:ok, [%Event.InvalidPiggyback{txbytes: ^txbytes, inputs: [0, 1], outputs: []}]} =\n               check_validity_filtered(request, state, only: [Event.InvalidPiggyback])\n\n      state = state |> piggyback_ife_from(ife_id, 0, :output)\n\n      assert {:ok, [%Event.InvalidPiggyback{txbytes: ^txbytes, inputs: [0, 1], outputs: [0]}]} =\n               check_validity_filtered(request, state, only: [Event.InvalidPiggyback])\n\n      state = state |> piggyback_ife_from(ife_id, 1, :output)\n\n      assert {:ok, [%Event.InvalidPiggyback{txbytes: ^txbytes, inputs: [0, 1], outputs: [0, 1]}]} =\n               check_validity_filtered(request, state, only: [Event.InvalidPiggyback])\n\n      assert {:ok,\n              %{\n                in_flight_input_index: 1,\n                in_flight_txbytes: ^txbytes,\n                spending_txbytes: ^comp_txbytes,\n                spending_input_index: 1,\n                spending_sig: ^alice_sig\n              }} = Core.get_input_challenge_data(request, state, txbytes, 1)\n\n      assert {:ok,\n              %{\n                in_flight_txbytes: ^txbytes,\n                in_flight_output_pos: Utxo.position(^tx_blknum, 0, 0),\n                in_flight_proof: inclusion_proof,\n                spending_txbytes: ^comp_txbytes,\n                spending_input_index: 2,\n                spending_sig: ^alice_sig\n              }} = Core.get_output_challenge_data(request, state, txbytes, 0)\n\n      assert_proof_sound(inclusion_proof)\n    end\n\n    test \"returns input txs and input utxo positions for invalid input piggyback challenges\",\n         %{processor_filled: state, transactions: [tx | _], competing_tx: comp, ife_tx_hashes: [ife_id | _]} do\n      txbytes = txbytes(tx)\n      state = state |> start_ife_from(comp) |> piggyback_ife_from(ife_id, 0, :input)\n\n      request = %ExitProcessor.Request{blknum_now: 1000, eth_height_now: 5}\n\n      assert {:ok, %{input_tx: \"input_tx\", input_utxo_pos: Utxo.position(1, 0, 0)}} =\n               Core.get_input_challenge_data(request, state, txbytes, 0)\n    end\n  end\n\n  describe \"produces challenges for bad piggybacks\" do\n    test \"produces single challenge proof on double-spent piggyback input\",\n         %{\n           invalid_piggyback_on_input: %{\n             state: state,\n             request: request,\n             ife_input_index: ife_input_index,\n             ife_txbytes: ife_txbytes,\n             spending_txbytes: spending_txbytes,\n             spending_input_index: spending_input_index,\n             spending_sig: spending_sig\n           }\n         } do\n      assert {:ok,\n              %{\n                in_flight_input_index: ^ife_input_index,\n                in_flight_txbytes: ^ife_txbytes,\n                spending_txbytes: ^spending_txbytes,\n                spending_input_index: ^spending_input_index,\n                spending_sig: ^spending_sig\n              }} = Core.get_input_challenge_data(request, state, ife_txbytes, ife_input_index)\n    end\n\n    test \"fail when asked to produce proof for wrong oindex\",\n         %{\n           invalid_piggyback_on_input: %{\n             state: state,\n             request: request,\n             ife_input_index: bad_pb_output,\n             ife_txbytes: txbytes\n           }\n         } do\n      assert bad_pb_output != 1\n\n      assert {:error, :no_double_spend_on_particular_piggyback} =\n               Core.get_input_challenge_data(request, state, txbytes, 1)\n    end\n\n    test \"fail when asked to produce proof for wrong txhash\",\n         %{invalid_piggyback_on_input: %{state: state, request: request}, unrelated_tx: comp} do\n      comp_txbytes = Transaction.raw_txbytes(comp)\n      assert {:error, :ife_not_known_for_tx} = Core.get_input_challenge_data(request, state, comp_txbytes, 0)\n      assert {:error, :ife_not_known_for_tx} = Core.get_output_challenge_data(request, state, comp_txbytes, 0)\n    end\n\n    test \"fail when asked to produce proof for wrong badly encoded tx\",\n         %{invalid_piggyback_on_input: %{state: state, request: request}} do\n      assert {:error, :malformed_transaction} = Core.get_input_challenge_data(request, state, <<0>>, 0)\n      assert {:error, :malformed_transaction} = Core.get_output_challenge_data(request, state, <<0>>, 0)\n    end\n\n    test \"fail when asked to produce proof for illegal oindex\",\n         %{invalid_piggyback_on_input: %{state: state, request: request, ife_txbytes: txbytes}} do\n      assert {:error, :piggybacked_index_out_of_range} = Core.get_input_challenge_data(request, state, txbytes, -1)\n      assert {:error, :piggybacked_index_out_of_range} = Core.get_output_challenge_data(request, state, txbytes, -1)\n    end\n\n    test \"will fail if asked to produce proof for wrong output\",\n         %{\n           invalid_piggyback_on_output: %{\n             state: state,\n             request: request,\n             ife_input_index: bad_pb_output,\n             ife_txbytes: txbytes\n           }\n         } do\n      assert 2 != bad_pb_output - 4\n\n      assert {:error, :no_double_spend_on_particular_piggyback} =\n               Core.get_output_challenge_data(request, state, txbytes, 2)\n    end\n\n    test \"will fail if asked to produce proof for correct piggyback on output\",\n         %{\n           invalid_piggyback_on_output: %{\n             state: state,\n             request: request,\n             ife_good_pb_index: good_pb_output,\n             ife_txbytes: txbytes\n           }\n         } do\n      assert {:error, :no_double_spend_on_particular_piggyback} =\n               Core.get_output_challenge_data(request, state, txbytes, good_pb_output - 4)\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/exit_processor/standard_exit_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.ExitProcessor.StandardExitTest do\n  @moduledoc \"\"\"\n  Test of the logic of exit processor, in the area of standard exits\n  \"\"\"\n\n  use OMG.Watcher.ExitProcessor.Case, async: false\n\n  alias OMG.Watcher.Block\n  alias OMG.Watcher.Event\n  alias OMG.Watcher.ExitProcessor\n  alias OMG.Watcher.ExitProcessor.Core\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.TestHelper\n  alias OMG.Watcher.Utxo\n\n  require Utxo\n\n  import OMG.Watcher.ExitProcessor.TestHelper,\n    only: [start_ife_from: 2, start_se_from: 3, start_se_from: 4, check_validity_filtered: 3]\n\n  @eth <<0::160>>\n\n  @deposit_blknum 1\n  @deposit_blknum2 2\n  @early_blknum 1_000\n  @blknum @early_blknum\n  @late_blknum 10_000\n  @blknum2 @late_blknum - 1_000\n\n  @utxo_pos_tx Utxo.position(@blknum, 0, 0)\n  @utxo_pos_tx2 Utxo.position(@blknum2, 0, 1)\n  @utxo_pos_deposit Utxo.position(@deposit_blknum, 0, 0)\n  @utxo_pos_deposit2 Utxo.position(@deposit_blknum2, 0, 0)\n\n  @deposit_input2 {@deposit_blknum2, 0, 0}\n\n  # needs to match up with the default from `ExitProcessor.Case` :(\n  @exit_id 9876\n\n  @default_min_exit_period_seconds 120\n  @default_child_block_interval 1000\n\n  setup do\n    {:ok, empty} = Core.init([], [], [], @default_min_exit_period_seconds, @default_child_block_interval)\n    db_path = Briefly.create!(directory: true)\n    Application.put_env(:omg_db, :path, db_path, persistent: true)\n    :ok = OMG.DB.init()\n    {:ok, started_apps} = Application.ensure_all_started(:omg_db)\n\n    on_exit(fn ->\n      Application.put_env(:omg_db, :path, nil)\n\n      Enum.map(started_apps, fn app -> :ok = Application.stop(app) end)\n    end)\n\n    %{processor_empty: empty, alice: TestHelper.generate_entity(), bob: TestHelper.generate_entity()}\n  end\n\n  describe \"Core.determine_standard_challenge_queries\" do\n    test \"doesn't ask for anything and stops if deposit utxo not spent at all\",\n         %{alice: alice, processor_empty: processor} do\n      processor = processor |> start_se_from_deposit(@utxo_pos_deposit, alice)\n\n      assert {:error, :utxo_not_spent} =\n               %ExitProcessor.Request{se_exiting_pos: @utxo_pos_deposit}\n               |> Core.determine_standard_challenge_queries(processor, true)\n    end\n\n    test \"doesn't ask for anything and stops if tx utxo not spent at all\",\n         %{alice: alice, processor_empty: processor} do\n      processor = processor |> start_se_from_block_tx(@utxo_pos_tx, alice)\n\n      assert {:error, :utxo_not_spent} =\n               %ExitProcessor.Request{se_exiting_pos: @utxo_pos_tx}\n               |> Core.determine_standard_challenge_queries(processor, true)\n    end\n\n    test \"asks for correct data: deposit utxo double spent in IFE\",\n         %{alice: alice, processor_empty: processor} do\n      ife_tx = TestHelper.create_recovered([{@deposit_blknum, 0, 0, alice}], @eth, [{alice, 1}])\n      processor = processor |> start_se_from_deposit(@utxo_pos_deposit, alice) |> start_ife_from(ife_tx)\n\n      assert {:ok, %ExitProcessor.Request{se_spending_blocks_to_get: []}} =\n               %ExitProcessor.Request{se_exiting_pos: @utxo_pos_deposit}\n               |> Core.determine_standard_challenge_queries(processor, true)\n    end\n\n    test \"asks for correct data: deposit utxo double spent outside an IFE\",\n         %{alice: alice, processor_empty: processor} do\n      processor = processor |> start_se_from_deposit(@utxo_pos_deposit, alice)\n\n      assert {:ok, %ExitProcessor.Request{se_spending_blocks_to_get: [@utxo_pos_deposit]}} =\n               %ExitProcessor.Request{se_exiting_pos: @utxo_pos_deposit}\n               |> Core.determine_standard_challenge_queries(processor, false)\n    end\n\n    test \"asks for correct data: tx utxo double spent in an IFE\",\n         %{alice: alice, processor_empty: processor} do\n      ife_tx = TestHelper.create_recovered([{@blknum, 0, 0, alice}], @eth, [{alice, 1}])\n      processor = processor |> start_se_from_block_tx(@utxo_pos_tx, alice) |> start_ife_from(ife_tx)\n\n      assert {:ok, %ExitProcessor.Request{se_spending_blocks_to_get: []}} =\n               %ExitProcessor.Request{se_exiting_pos: @utxo_pos_tx}\n               |> Core.determine_standard_challenge_queries(processor, true)\n    end\n\n    test \"asks for correct data: tx utxo double spent outside an IFE\",\n         %{alice: alice, processor_empty: processor} do\n      processor = processor |> start_se_from_block_tx(@utxo_pos_tx, alice)\n\n      assert {:ok, %ExitProcessor.Request{se_spending_blocks_to_get: [@utxo_pos_tx]}} =\n               %ExitProcessor.Request{se_exiting_pos: @utxo_pos_tx}\n               |> Core.determine_standard_challenge_queries(processor, false)\n    end\n\n    test \"stops immediately, if exit not found, utxo exists\",\n         %{processor_empty: processor} do\n      assert {:error, :exit_not_found} =\n               %ExitProcessor.Request{se_exiting_pos: @utxo_pos_tx}\n               |> Core.determine_standard_challenge_queries(processor, true)\n    end\n\n    test \"stops immediately, if exit not found, utxo doesn't exist\",\n         %{processor_empty: processor} do\n      assert {:error, :exit_not_found} =\n               %ExitProcessor.Request{se_exiting_pos: @utxo_pos_tx}\n               |> Core.determine_standard_challenge_queries(processor, false)\n    end\n  end\n\n  describe \"Core.create_challenge\" do\n    test \"returns a deposit exiting_tx as part of the challenge response\",\n         %{alice: alice, processor_empty: processor} do\n      exiting_tx = TestHelper.create_recovered([], [{alice, @eth, 10}])\n      processor = processor |> start_se_from(exiting_tx, @utxo_pos_deposit)\n\n      recovered_spend = TestHelper.create_recovered([{@deposit_blknum, 0, 0, alice}], @eth, [{alice, 10}])\n      {txbytes, _alice_sig} = get_bytes_sig(recovered_spend)\n      {exiting_txbytes, _} = get_bytes_sig(exiting_tx)\n\n      assert {:ok, %{exiting_tx: ^exiting_txbytes, txbytes: ^txbytes}} =\n               %ExitProcessor.Request{\n                 se_exiting_pos: @utxo_pos_deposit,\n                 se_spending_blocks_result: [Block.hashed_txs_at([recovered_spend], @blknum)]\n               }\n               |> Core.create_challenge(processor)\n    end\n\n    test \"returns a block exiting_tx as part of the challenge response\",\n         %{alice: alice, processor_empty: processor} do\n      exiting_tx = TestHelper.create_recovered([Tuple.append(@deposit_input2, alice)], [{alice, @eth, 10}])\n      processor = processor |> start_se_from(exiting_tx, @utxo_pos_tx)\n\n      recovered_spend = TestHelper.create_recovered([{@blknum, 0, 0, alice}], @eth, [{alice, 10}])\n      {txbytes, _alice_sig} = get_bytes_sig(recovered_spend)\n      {exiting_txbytes, _} = get_bytes_sig(exiting_tx)\n\n      assert {:ok, %{exiting_tx: ^exiting_txbytes, txbytes: ^txbytes}} =\n               %ExitProcessor.Request{\n                 se_exiting_pos: @utxo_pos_tx,\n                 se_spending_blocks_result: [Block.hashed_txs_at([recovered_spend], @late_blknum)]\n               }\n               |> Core.create_challenge(processor)\n    end\n\n    test \"creates challenge: deposit utxo double spent in IFE\",\n         %{alice: alice, processor_empty: processor} do\n      ife_tx = TestHelper.create_recovered([{@deposit_blknum, 0, 0, alice}], @eth, [{alice, 1}])\n      {txbytes, alice_sig} = get_bytes_sig(ife_tx)\n      processor = processor |> start_se_from_deposit(@utxo_pos_deposit, alice) |> start_ife_from(ife_tx)\n\n      assert {:ok, %{exit_id: @exit_id, input_index: 0, txbytes: ^txbytes, sig: ^alice_sig}} =\n               %ExitProcessor.Request{se_exiting_pos: @utxo_pos_deposit}\n               |> Core.create_challenge(processor)\n    end\n\n    test \"creates challenge: deposit utxo double spent outside an IFE\",\n         %{alice: alice, processor_empty: processor} do\n      processor = processor |> start_se_from_deposit(@utxo_pos_deposit, alice)\n\n      recovered_spend = TestHelper.create_recovered([{1, 0, 0, alice}], @eth, [{alice, 10}])\n      {txbytes, alice_sig} = get_bytes_sig(recovered_spend)\n\n      assert {:ok, %{exit_id: @exit_id, input_index: 0, txbytes: ^txbytes, sig: ^alice_sig}} =\n               %ExitProcessor.Request{\n                 se_exiting_pos: @utxo_pos_deposit,\n                 se_spending_blocks_result: [Block.hashed_txs_at([recovered_spend], @blknum)]\n               }\n               |> Core.create_challenge(processor)\n    end\n\n    test \"creates challenge: tx utxo double spent in an IFE\",\n         %{alice: alice, processor_empty: processor} do\n      # quite similar to the deposit utxo case, but leaving the test in for completeness\n      ife_tx = TestHelper.create_recovered([{@blknum, 0, 0, alice}], @eth, [{alice, 1}])\n      {txbytes, alice_sig} = get_bytes_sig(ife_tx)\n      processor = processor |> start_se_from_block_tx(@utxo_pos_tx, alice) |> start_ife_from(ife_tx)\n\n      assert {:ok, %{exit_id: @exit_id, input_index: 0, txbytes: ^txbytes, sig: ^alice_sig}} =\n               %ExitProcessor.Request{se_exiting_pos: @utxo_pos_tx}\n               |> Core.create_challenge(processor)\n    end\n\n    test \"creates challenge: tx utxo double spent outside an IFE\",\n         %{alice: alice, processor_empty: processor} do\n      processor = processor |> start_se_from_block_tx(@utxo_pos_tx, alice)\n\n      recovered_spend = TestHelper.create_recovered([{@blknum, 0, 0, alice}], @eth, [{alice, 10}])\n      {txbytes, alice_sig} = get_bytes_sig(recovered_spend)\n\n      assert {:ok, %{exit_id: @exit_id, input_index: 0, txbytes: ^txbytes, sig: ^alice_sig}} =\n               %ExitProcessor.Request{\n                 se_exiting_pos: @utxo_pos_tx,\n                 se_spending_blocks_result: [Block.hashed_txs_at([recovered_spend], @blknum)]\n               }\n               |> Core.create_challenge(processor)\n    end\n\n    test \"creates challenge: tx utxo double spent outside an IFE, but there is an unrelated IFE open\",\n         %{alice: alice, processor_empty: processor} do\n      unrelated = TestHelper.create_recovered([{@blknum, 10, 0, alice}], @eth, [{alice, 1}])\n      processor = processor |> start_se_from_block_tx(@utxo_pos_tx, alice) |> start_ife_from(unrelated)\n\n      recovered_spend = TestHelper.create_recovered([{@blknum, 0, 0, alice}], @eth, [{alice, 10}])\n      {txbytes, alice_sig} = get_bytes_sig(recovered_spend)\n\n      assert {:ok, %{exit_id: @exit_id, input_index: 0, txbytes: ^txbytes, sig: ^alice_sig}} =\n               %ExitProcessor.Request{\n                 se_exiting_pos: @utxo_pos_tx,\n                 se_spending_blocks_result: [Block.hashed_txs_at([recovered_spend], @blknum)]\n               }\n               |> Core.create_challenge(processor)\n    end\n\n    test \"creates challenge: tx utxo double spent on input various positions\",\n         %{alice: alice, processor_empty: processor} do\n      processor = processor |> start_se_from_block_tx(@utxo_pos_tx, alice)\n\n      input = {@blknum, 0, 0, alice}\n\n      recovered_spends = [\n        TestHelper.create_recovered([input], @eth, [{alice, 10}]),\n        TestHelper.create_recovered([{1, 0, 0, alice}, input], @eth, [{alice, 10}]),\n        TestHelper.create_recovered([{1, 0, 0, alice}, {2, 0, 0, alice}, input], @eth, [{alice, 10}]),\n        TestHelper.create_recovered([{1, 0, 0, alice}, {2, 0, 0, alice}, {3, 0, 0, alice}, input], @eth, [{alice, 10}])\n      ]\n\n      recovered_spends\n      |> Enum.with_index()\n      |> Enum.map(fn {recovered_spend, expected_index} ->\n        {txbytes, alice_sig} = get_bytes_sig(recovered_spend)\n\n        assert {:ok, %{exit_id: @exit_id, input_index: ^expected_index, txbytes: ^txbytes, sig: ^alice_sig}} =\n                 %ExitProcessor.Request{\n                   se_exiting_pos: @utxo_pos_tx,\n                   se_spending_blocks_result: [Block.hashed_txs_at([recovered_spend], @blknum)]\n                 }\n                 |> Core.create_challenge(processor)\n      end)\n    end\n\n    test \"creates challenge: tx utxo double spent signed_by different signers\",\n         %{alice: alice, bob: bob, processor_empty: processor} do\n      tx1 = Transaction.Payment.new([@deposit_input2], [{alice.addr, @eth, 10}])\n      tx2 = Transaction.Payment.new([@deposit_input2], [{bob.addr, @eth, 10}])\n      processor1 = processor |> start_se_from(tx1, @utxo_pos_tx)\n      processor2 = processor |> start_se_from(tx2, @utxo_pos_tx)\n\n      recovered_spends = [\n        TestHelper.create_recovered([{1, 0, 0, bob}, {@blknum, 0, 0, alice}], @eth, [{alice, 10}]),\n        TestHelper.create_recovered([{1, 0, 0, alice}, {@blknum, 0, 0, bob}], @eth, [{alice, 10}])\n      ]\n\n      recovered_spends\n      |> Enum.zip([processor1, processor2])\n      |> Enum.map(fn {recovered_spend, processor} ->\n        {txbytes, second_sig} = get_bytes_sig(recovered_spend, 1)\n\n        assert {:ok, %{exit_id: @exit_id, input_index: 1, txbytes: ^txbytes, sig: ^second_sig}} =\n                 %ExitProcessor.Request{\n                   se_exiting_pos: @utxo_pos_tx,\n                   se_spending_blocks_result: [Block.hashed_txs_at([recovered_spend], @blknum)]\n                 }\n                 |> Core.create_challenge(processor)\n      end)\n    end\n\n    test \"creates challenge: both utxos spent don't interfere\",\n         %{alice: alice, processor_empty: processor} do\n      tx = Transaction.Payment.new([@deposit_input2], [{alice.addr, @eth, 10}, {alice.addr, @eth, 10}])\n      processor = processor |> start_se_from(tx, @utxo_pos_tx)\n\n      recovered_spend = TestHelper.create_recovered([{@blknum, 0, 0, alice}], @eth, [{alice, 10}])\n      recovered_spend2 = TestHelper.create_recovered([{@blknum, 0, 1, alice}], @eth, [{alice, 10}])\n      {txbytes, alice_sig} = get_bytes_sig(recovered_spend)\n\n      assert {:ok, %{exit_id: @exit_id, input_index: 0, txbytes: ^txbytes, sig: ^alice_sig}} =\n               %ExitProcessor.Request{\n                 se_exiting_pos: @utxo_pos_tx,\n                 se_spending_blocks_result: [Block.hashed_txs_at([recovered_spend, recovered_spend2], @blknum)]\n               }\n               |> Core.create_challenge(processor)\n    end\n\n    test \"creates challenge: tx utxo double spent in both block and IFE don't interfere\",\n         %{alice: alice, processor_empty: processor} do\n      ife_tx = TestHelper.create_recovered([{@blknum, 0, 0, alice}], @eth, [{alice, 1}])\n      {txbytes, alice_sig} = get_bytes_sig(ife_tx)\n      processor = processor |> start_se_from_block_tx(@utxo_pos_tx, alice) |> start_ife_from(ife_tx)\n\n      # same tx spends in both\n      assert {:ok, %{exit_id: @exit_id, input_index: 0, txbytes: ^txbytes, sig: ^alice_sig}} =\n               %ExitProcessor.Request{\n                 se_exiting_pos: @utxo_pos_tx,\n                 se_spending_blocks_result: [Block.hashed_txs_at([ife_tx], @blknum)]\n               }\n               |> Core.create_challenge(processor)\n\n      # different txs spend, block tx takes preference\n      recovered_spend2 = TestHelper.create_recovered([{@blknum, 0, 0, alice}], @eth, [{alice, 10}])\n\n      {block_txbytes, alice_sig2} = get_bytes_sig(recovered_spend2)\n\n      assert {:ok, %{exit_id: @exit_id, input_index: 0, txbytes: ^block_txbytes, sig: ^alice_sig2}} =\n               %ExitProcessor.Request{\n                 se_exiting_pos: @utxo_pos_tx,\n                 se_spending_blocks_result: [Block.hashed_txs_at([recovered_spend2], @blknum)]\n               }\n               |> Core.create_challenge(processor)\n    end\n\n    test \"doesn't create challenge: tx utxo not double spent\",\n         %{alice: alice, processor_empty: processor} do\n      processor = processor |> start_se_from_block_tx(@utxo_pos_tx, alice)\n\n      assert {:error, :utxo_not_spent} =\n               %ExitProcessor.Request{se_exiting_pos: @utxo_pos_tx, se_spending_blocks_result: []}\n               |> Core.create_challenge(processor)\n\n      assert {:error, :utxo_not_spent} =\n               %ExitProcessor.Request{se_exiting_pos: @utxo_pos_tx, se_spending_blocks_result: [:not_found]}\n               |> Core.create_challenge(processor)\n    end\n  end\n\n  describe \"Core.check_validity\" do\n    test \"detect invalid standard exit based on utxo missing in main ledger\",\n         %{processor_empty: processor, alice: alice} do\n      exiting_pos = @utxo_pos_tx\n      exiting_pos_enc = Utxo.Position.encode(exiting_pos)\n      # standard_exit_tx = TestHelper.create_recovered([{@deposit_blknum, 0, 0, alice}], @eth, [{alice, 10}])\n      %{signed_tx_bytes: signed_tx_bytes, tx_hash: tx_hash} =\n        standard_exit_tx = TestHelper.create_recovered([{@blknum, 0, 0, alice}], @eth, [{alice, 10}])\n\n      request = %ExitProcessor.Request{\n        eth_height_now: 5,\n        blknum_now: @late_blknum,\n        utxos_to_check: [exiting_pos],\n        utxo_exists_result: [false]\n      }\n\n      # before the exit starts\n      assert {:ok, []} = Core.check_validity(request, processor)\n      # after\n      processor = start_se_from(processor, standard_exit_tx, exiting_pos)\n\n      block_updates = [{:put, :block, %{number: @blknum, hash: <<0::160>>, transactions: [signed_tx_bytes]}}]\n      spent_blknum_updates = [{:put, :spend, {Utxo.Position.to_input_db_key(@utxo_pos_tx), @blknum}}]\n      :ok = OMG.DB.multi_update(block_updates ++ spent_blknum_updates)\n\n      assert {:ok, [%Event.InvalidExit{utxo_pos: ^exiting_pos_enc, spending_txhash: ^tx_hash}]} =\n               Core.check_validity(request, processor)\n    end\n\n    test \"detect old invalid standard exit\", %{processor_empty: processor, alice: alice} do\n      exiting_pos = @utxo_pos_tx\n      exiting_pos_enc = Utxo.Position.encode(exiting_pos)\n\n      %{signed_tx_bytes: signed_tx_bytes, tx_hash: tx_hash} =\n        standard_exit_tx = TestHelper.create_recovered([{@blknum, 0, 0, alice}], @eth, [{alice, 10}])\n\n      request = %ExitProcessor.Request{\n        eth_height_now: 50,\n        blknum_now: @late_blknum,\n        utxos_to_check: [exiting_pos],\n        utxo_exists_result: [false]\n      }\n\n      processor = start_se_from(processor, standard_exit_tx, exiting_pos)\n\n      block_updates = [{:put, :block, %{number: @blknum, hash: <<0::160>>, transactions: [signed_tx_bytes]}}]\n      spent_blknum_updates = [{:put, :spend, {Utxo.Position.to_input_db_key(@utxo_pos_tx), @blknum}}]\n      :ok = OMG.DB.multi_update(block_updates ++ spent_blknum_updates)\n\n      assert {{:error, :unchallenged_exit},\n              [\n                %Event.UnchallengedExit{utxo_pos: ^exiting_pos_enc, spending_txhash: ^tx_hash},\n                %Event.InvalidExit{utxo_pos: ^exiting_pos_enc, spending_txhash: ^tx_hash}\n              ]} = Core.check_validity(request, processor)\n    end\n\n    test \"invalid exits that have been witnessed already inactive don't excite events\",\n         %{processor_empty: processor, alice: alice} do\n      exiting_pos = @utxo_pos_tx\n      standard_exit_tx = TestHelper.create_recovered([{@deposit_blknum, 0, 0, alice}], @eth, [{alice, 10}])\n\n      request = %ExitProcessor.Request{\n        eth_height_now: 13,\n        blknum_now: @late_blknum,\n        utxos_to_check: [exiting_pos],\n        utxo_exists_result: [false]\n      }\n\n      processor = processor |> start_se_from(standard_exit_tx, exiting_pos, inactive: true)\n      assert {:ok, []} = request |> Core.check_validity(processor)\n    end\n\n    test \"exits of utxos that couldn't have been seen created yet never excite querying the ledger\",\n         %{processor_empty: processor, alice: alice} do\n      exiting_pos = @utxo_pos_tx2\n      standard_exit_tx = TestHelper.create_recovered([{@deposit_blknum, 0, 0, alice}], @eth, [{alice, 1}, {alice, 1}])\n\n      processor = processor |> start_se_from(standard_exit_tx, exiting_pos)\n\n      assert %ExitProcessor.Request{utxos_to_check: []} =\n               %ExitProcessor.Request{eth_height_now: 13, blknum_now: @early_blknum}\n               |> Core.determine_utxo_existence_to_get(processor)\n    end\n\n    test \"detect invalid standard exit based on ife tx which spends same input\",\n         %{processor_empty: processor, alice: alice} do\n      standard_exit_tx = TestHelper.create_recovered([{@deposit_blknum, 0, 0, alice}], @eth, [{alice, 10}])\n      %{tx_hash: tx_hash} = tx = TestHelper.create_recovered([{@blknum, 0, 0, alice}], [{alice, @eth, 1}])\n      exiting_pos = @utxo_pos_tx\n      exiting_pos_enc = Utxo.Position.encode(exiting_pos)\n      processor = processor |> start_se_from(standard_exit_tx, exiting_pos) |> start_ife_from(tx)\n\n      assert {:ok, [%Event.InvalidExit{utxo_pos: ^exiting_pos_enc, spending_txhash: ^tx_hash}]} =\n               check_validity_filtered(%ExitProcessor.Request{eth_height_now: 5, blknum_now: @late_blknum}, processor,\n                 only: [Event.InvalidExit]\n               )\n    end\n\n    test \"ifes and standard exits don't interfere\",\n         %{alice: alice, processor_empty: processor, transactions: [tx | _]} do\n      %{signed_tx_bytes: signed_tx_bytes, tx_hash: tx_hash} =\n        standard_exit_tx = TestHelper.create_recovered([{@blknum, 0, 0, alice}], @eth, [{alice, 10}])\n\n      processor = processor |> start_se_from(standard_exit_tx, @utxo_pos_tx) |> start_ife_from(tx)\n\n      assert %{utxos_to_check: [_, Utxo.position(1, 2, 1), @utxo_pos_tx]} =\n               exit_processor_request =\n               %ExitProcessor.Request{eth_height_now: 5, blknum_now: @late_blknum}\n               |> Core.determine_utxo_existence_to_get(processor)\n\n      block_updates = [{:put, :block, %{number: @blknum, hash: <<0::160>>, transactions: [signed_tx_bytes]}}]\n      spent_blknum_updates = [{:put, :spend, {Utxo.Position.to_input_db_key(@utxo_pos_tx), @blknum}}]\n      :ok = OMG.DB.multi_update(block_updates ++ spent_blknum_updates)\n\n      # here it's crucial that the missing utxo related to the ife isn't interpeted as a standard invalid exit\n      # that missing utxo isn't enough for any IFE-related event too\n      assert {:ok, [%Event.InvalidExit{spending_txhash: ^tx_hash}]} =\n               exit_processor_request\n               |> struct!(utxo_exists_result: [false, false, false])\n               |> check_validity_filtered(processor, exclude: [Event.PiggybackAvailable])\n    end\n\n    test \"ifes and standard exits don't interfere, when standard exit is challenged\",\n         %{alice: alice, processor_empty: processor, transactions: [tx | _]} do\n      standard_exit_tx = TestHelper.create_recovered([], @eth, [{alice, 10}])\n\n      {processor, _} =\n        processor\n        |> start_se_from(standard_exit_tx, @utxo_pos_deposit)\n        |> start_ife_from(tx)\n        |> Core.challenge_exits([%{utxo_pos: Utxo.Position.encode(@utxo_pos_deposit)}])\n\n      # doesn't check the challenged SE utxo\n      assert %{utxos_to_check: [_, Utxo.position(1, 2, 1)]} =\n               exit_processor_request =\n               %ExitProcessor.Request{eth_height_now: 5, blknum_now: @late_blknum}\n               |> Core.determine_utxo_existence_to_get(processor)\n\n      # doesn't alert on the challenged SE, despite it being a double-spend wrt the IFE\n      assert {:ok, []} =\n               exit_processor_request\n               |> struct!(utxo_exists_result: [false, false])\n               |> check_validity_filtered(processor, exclude: [Event.PiggybackAvailable])\n    end\n\n    test \"ifes and standard exits don't interfere if all valid\",\n         %{alice: alice, processor_empty: processor, transactions: [tx | _]} do\n      standard_exit_tx = TestHelper.create_recovered([{@deposit_blknum, 0, 0, alice}], @eth, [{alice, 10}])\n      processor = processor |> start_se_from(standard_exit_tx, @utxo_pos_tx) |> start_ife_from(tx)\n\n      assert %{utxos_to_check: [_, Utxo.position(1, 2, 1), @utxo_pos_tx]} =\n               exit_processor_request =\n               %ExitProcessor.Request{eth_height_now: 5, blknum_now: @late_blknum}\n               |> Core.determine_utxo_existence_to_get(processor)\n\n      assert {:ok, []} =\n               exit_processor_request\n               |> struct!(utxo_exists_result: [true, true, true])\n               |> check_validity_filtered(processor, exclude: [Event.PiggybackAvailable])\n    end\n  end\n\n  describe \"challenge events\" do\n    test \"can challenge exits, which are then forgotten completely\",\n         %{processor_empty: processor, alice: alice} do\n      standard_exit_tx1 = TestHelper.create_recovered([{1, 0, 0, alice}], @eth, [{alice, 10}])\n      standard_exit_tx2 = TestHelper.create_recovered([{@blknum2, 0, 1, alice}], @eth, [{alice, 10}, {alice, 10}])\n\n      processor =\n        processor\n        |> start_se_from(standard_exit_tx1, @utxo_pos_deposit2)\n        |> start_se_from(standard_exit_tx2, @utxo_pos_tx2)\n\n      # sanity\n      assert %ExitProcessor.Request{utxos_to_check: [_, _]} =\n               Core.determine_utxo_existence_to_get(%ExitProcessor.Request{blknum_now: @late_blknum}, processor)\n\n      {processor, _} =\n        processor\n        |> Core.challenge_exits([@utxo_pos_deposit2, @utxo_pos_tx2] |> Enum.map(&%{utxo_pos: Utxo.Position.encode(&1)}))\n\n      assert %ExitProcessor.Request{utxos_to_check: []} =\n               Core.determine_utxo_existence_to_get(%ExitProcessor.Request{blknum_now: @late_blknum}, processor)\n    end\n\n    test \"can process challenged exits\", %{processor_empty: processor, alice: alice} do\n      # see the contract and `Eth.RootChain.get_standard_exit_structs/1` for some explanation why like this\n      # this is what an exit looks like after a challenge\n      zero_status = {false, 0, 0, 0, 0, 0}\n      standard_exit_tx = TestHelper.create_recovered([{1, 0, 0, alice}], @eth, [{alice, 10}])\n      processor = processor |> start_se_from(standard_exit_tx, @utxo_pos_deposit2, status: zero_status)\n\n      # sanity\n      assert %ExitProcessor.Request{utxos_to_check: []} =\n               Core.determine_utxo_existence_to_get(%ExitProcessor.Request{blknum_now: @late_blknum}, processor)\n\n      # pinning because challenge shouldn't change the already challenged exit in the processor\n      {^processor, _} = Core.challenge_exits(processor, [%{utxo_pos: Utxo.Position.encode(@utxo_pos_deposit2)}])\n    end\n  end\n\n  defp start_se_from_deposit(processor, exiting_pos, alice) do\n    tx = TestHelper.create_recovered([], [{alice, @eth, 10}])\n    start_se_from(processor, tx, exiting_pos)\n  end\n\n  defp start_se_from_block_tx(processor, exiting_pos, alice) do\n    tx = TestHelper.create_recovered([Tuple.append(@deposit_input2, alice)], [{alice, @eth, 10}])\n    start_se_from(processor, tx, exiting_pos)\n  end\n\n  defp get_bytes_sig(tx, sig_idx \\\\ 0), do: {Transaction.raw_txbytes(tx), Enum.at(tx.signed_tx.sigs, sig_idx)}\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/exit_processor/tools_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.ExitProcessor.ToolsTest do\n  @moduledoc \"\"\"\n  Test of the logic of exit processor - various generic tests: starting events, some sanity checks, ife listing\n  \"\"\"\n  use OMG.Watcher.ExitProcessor.Case, async: true\n\n  alias OMG.Watcher.ExitProcessor.Tools\n  alias OMG.Watcher.Utxo\n\n  require Utxo\n\n  describe \"to_bus_events/1\" do\n    setup _ do\n      {:ok,\n       %{\n         finalizations: [\n           %{in_flight_exit_id: <<1::192>>, log_index: 1, root_chain_txhash: <<1::256>>, eth_height: 1},\n           %{in_flight_exit_id: <<2::192>>, log_index: 2, root_chain_txhash: <<2::256>>, eth_height: 2}\n         ],\n         start_ife_events: [\n           %{log_index: 1, root_chain_txhash: <<11::256>>, tx_hash: <<255::256>>, eth_height: 110},\n           %{log_index: 2, root_chain_txhash: <<12::256>>, tx_hash: <<255::256>>, eth_height: 111}\n         ],\n         utxos: [\n           Utxo.position(1, 0, 0),\n           Utxo.position(1000, 0, 0),\n           Utxo.position(2000, 0, 0)\n         ]\n       }}\n    end\n\n    test \"mapping single finalization\", %{finalizations: [f1 | _], utxos: [utxo_1 | _]} do\n      utxo_pos = Utxo.Position.encode(utxo_1)\n\n      assert [\n               %{log_index: 1, root_chain_txhash: <<1::256>>, call_data: %{utxo_pos: ^utxo_pos}}\n             ] = Tools.to_bus_events_data([{f1, [utxo_1]}])\n    end\n\n    test \"mapping multiple finalizations\", %{finalizations: [f1, f2 | _], utxos: utxos} do\n      [utxo_1, utxo_2, utxo_3 | _] = utxos\n      [utxo_pos_1, utxo_pos_2, utxo_pos_3 | _] = Enum.map(utxos, &Utxo.Position.encode/1)\n\n      assert [\n               %{log_index: 2, root_chain_txhash: <<2::256>>, eth_height: 2, call_data: %{utxo_pos: ^utxo_pos_2}},\n               %{log_index: 2, root_chain_txhash: <<2::256>>, eth_height: 2, call_data: %{utxo_pos: ^utxo_pos_3}},\n               %{log_index: 1, root_chain_txhash: <<1::256>>, eth_height: 1, call_data: %{utxo_pos: ^utxo_pos_1}}\n             ] = Tools.to_bus_events_data([{f1, [utxo_1]}, {f2, [utxo_2, utxo_3]}])\n    end\n\n    test \"finalization without exiting utxos does not produce events\",\n         %{finalizations: [f1, f2 | _], utxos: [utxo_1 | _]} do\n      utxo_pos = Utxo.Position.encode(utxo_1)\n\n      assert [\n               %{log_index: 2, root_chain_txhash: <<2::256>>, call_data: %{utxo_pos: ^utxo_pos}}\n             ] = Tools.to_bus_events_data([{f1, []}, {f2, [utxo_1]}])\n    end\n\n    test \"empty finalization list does not produce events\" do\n      assert [] = Tools.to_bus_events_data([])\n    end\n\n    test \"mapping new_in_flight_exits events\", %{start_ife_events: [s1, s2 | _], utxos: utxos} do\n      [utxo_pos_1, utxo_pos_2, utxo_pos_3] =\n        encoded_utxos =\n        utxos\n        |> Enum.map(&Utxo.Position.encode/1)\n        |> Enum.take(3)\n\n      events_with_utxos = [\n        {s1, Enum.take(encoded_utxos, 2)},\n        {s2, Enum.drop(encoded_utxos, 2)}\n      ]\n\n      assert [\n               %{log_index: 2, root_chain_txhash: <<12::256>>, eth_height: 111, call_data: %{utxo_pos: ^utxo_pos_3}},\n               %{log_index: 1, root_chain_txhash: <<11::256>>, eth_height: 110, call_data: %{utxo_pos: ^utxo_pos_1}},\n               %{log_index: 1, root_chain_txhash: <<11::256>>, eth_height: 110, call_data: %{utxo_pos: ^utxo_pos_2}}\n             ] = Tools.to_bus_events_data(events_with_utxos)\n    end\n\n    test \"mapping piggyback_exits events\" do\n      txhash = <<255::256>>\n\n      piggyback_events = [\n        %{\n          log_index: 1,\n          root_chain_txhash: <<11::256>>,\n          tx_hash: txhash,\n          eth_height: 210,\n          output_index: 1,\n          omg_data: %{piggyback_type: :output}\n        },\n        %{\n          log_index: 2,\n          root_chain_txhash: <<12::256>>,\n          tx_hash: txhash,\n          eth_height: 210,\n          output_index: 0,\n          omg_data: %{piggyback_type: :input}\n        },\n        %{\n          log_index: 3,\n          root_chain_txhash: <<13::256>>,\n          tx_hash: txhash,\n          eth_height: 210,\n          output_index: 3,\n          omg_data: %{piggyback_type: :output}\n        }\n      ]\n\n      # Note: Piggyback to input in log_index: 2 is ignored\n      assert [\n               %{log_index: 3, root_chain_txhash: <<13::256>>, call_data: %{txhash: ^txhash, oindex: 3}},\n               %{log_index: 1, root_chain_txhash: <<11::256>>, call_data: %{txhash: ^txhash, oindex: 1}}\n             ] = Tools.to_bus_events_data(piggyback_events)\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/fees/fee_filter_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.Fees.FeeFilterTest do\n  @moduledoc false\n\n  use ExUnit.Case, async: true\n\n  alias OMG.Watcher.Fees.FeeFilter\n\n  doctest OMG.Watcher.Fees.FeeFilter\n\n  @eth <<0::160>>\n  @not_eth_1 <<1::size(160)>>\n  @not_eth_2 <<2::size(160)>>\n  @payment_tx_type OMG.Watcher.WireFormatTypes.tx_type_for(:tx_payment_v1)\n\n  @payment_fees %{\n    @eth => %{\n      amount: 1,\n      subunit_to_unit: 1_000_000_000_000_000_000,\n      pegged_amount: 4,\n      pegged_currency: \"USD\",\n      pegged_subunit_to_unit: 100,\n      updated_at: DateTime.from_iso8601(\"2019-01-01T10:10:00+00:00\")\n    },\n    @not_eth_1 => %{\n      amount: 3,\n      subunit_to_unit: 1000,\n      pegged_amount: 4,\n      pegged_currency: \"USD\",\n      pegged_subunit_to_unit: 100,\n      updated_at: DateTime.from_iso8601(\"2019-01-01T10:10:00+00:00\")\n    }\n  }\n\n  @fees %{\n    @payment_tx_type => @payment_fees,\n    2 => @payment_fees,\n    3 => %{\n      @not_eth_2 => %{\n        amount: 3,\n        subunit_to_unit: 1000,\n        pegged_amount: 4,\n        pegged_currency: \"USD\",\n        pegged_subunit_to_unit: 100,\n        updated_at: DateTime.from_iso8601(\"2019-01-01T10:10:00+00:00\")\n      }\n    }\n  }\n\n  describe \"filter/2\" do\n    test \"does not filter tx_type when given an empty list\" do\n      assert FeeFilter.filter(@fees, [], []) == {:ok, @fees}\n    end\n\n    test \"does not filter tx_type when given a nil value\" do\n      assert FeeFilter.filter(@fees, nil, []) == {:ok, @fees}\n    end\n\n    test \"does not filter currencies when given an empty list\" do\n      assert FeeFilter.filter(@fees, [], []) == {:ok, @fees}\n    end\n\n    test \"does not filter currencies when given a nil value\" do\n      assert FeeFilter.filter(@fees, [], nil) == {:ok, @fees}\n    end\n\n    test \"filter fees by currency given a list of currencies\" do\n      assert FeeFilter.filter(@fees, [], [@eth]) ==\n               {:ok,\n                %{\n                  @payment_tx_type => Map.take(@payment_fees, [@eth]),\n                  2 => Map.take(@payment_fees, [@eth]),\n                  3 => %{}\n                }}\n\n      assert FeeFilter.filter(@fees, [], [@not_eth_2]) == {:ok, %{@payment_tx_type => %{}, 2 => %{}, 3 => @fees[3]}}\n    end\n\n    test \"filter fees by tx_type when given a list of tx_types\" do\n      assert FeeFilter.filter(@fees, [1, 2], []) == {:ok, Map.drop(@fees, [3])}\n    end\n\n    test \"filter fees by both tx_type and currencies\" do\n      assert FeeFilter.filter(@fees, [1, 2], [@eth]) ==\n               {:ok,\n                %{\n                  @payment_tx_type => Map.take(@payment_fees, [@eth]),\n                  2 => Map.take(@payment_fees, [@eth])\n                }}\n    end\n\n    test \"returns an error when given an unsupported currency\" do\n      other_token = <<9::160>>\n      assert FeeFilter.filter(@fees, [], [other_token]) == {:error, :currency_fee_not_supported}\n    end\n\n    test \"returns an error when given an unsupported tx_type\" do\n      tx_type = 99_999\n      assert FeeFilter.filter(@fees, [tx_type], []) == {:error, :tx_type_not_supported}\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/fees_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.FeesTest do\n  @moduledoc false\n\n  use ExUnitFixtures\n  use OMG.Watcher.Fixtures\n  use ExUnit.Case, async: true\n\n  import OMG.Watcher.TestHelper\n\n  alias __MODULE__.DummyTransaction\n  alias OMG.Watcher.Fees\n\n  doctest OMG.Watcher.Fees\n\n  @eth <<0::160>>\n  @not_eth_1 <<1::size(160)>>\n\n  @payment_tx_type OMG.Watcher.WireFormatTypes.tx_type_for(:tx_payment_v1)\n\n  @payment_fees %{\n    @eth => [1],\n    @not_eth_1 => [3]\n  }\n\n  @fees %{\n    @payment_tx_type => @payment_fees\n  }\n\n  describe \"check_if_covered/2\" do\n    test \"returns :ok when given fees are 0 and :ignore_fees is passed\" do\n      assert Fees.check_if_covered(%{@eth => 0}, :ignore_fees) == :ok\n    end\n\n    test \"returns :ok when given positive fees and :ignore_fees is passed\" do\n      assert Fees.check_if_covered(%{@eth => 1, @not_eth_1 => 2}, :ignore_fees) == :ok\n    end\n\n    test \"returns :overpaying_fees when given positive fees and :no_fees_required is passed\" do\n      assert Fees.check_if_covered(%{@not_eth_1 => 0, @eth => 1}, :no_fees_required) == {:error, :overpaying_fees}\n    end\n\n    test \"returns :ok when given fees are 0 and :no_fees_required is passed\" do\n      assert Fees.check_if_covered(%{@eth => 0}, :no_fees_required) == :ok\n    end\n\n    test \"returns :ok when fees are exactly covered by one currency\" do\n      assert Fees.check_if_covered(%{@not_eth_1 => 3, @eth => 0}, @payment_fees) == :ok\n    end\n\n    test \"returns :ok when fees are exactly covered by one currency with previous fees\" do\n      fees =\n        @payment_fees\n        |> Map.put(@eth, [1, 2])\n        |> Map.put(@not_eth_1, [1, 3])\n\n      assert Fees.check_if_covered(%{@not_eth_1 => 3, @eth => 0}, fees) == :ok\n    end\n\n    test \"returns :multiple_potential_currency_fees when multiple implicit fees are given\" do\n      assert Fees.check_if_covered(%{@eth => 2, @not_eth_1 => 2}, @payment_fees) ==\n               {:error, :multiple_potential_currency_fees}\n    end\n\n    test \"returns :fees_not_covered when no positive implicit fees given\" do\n      other_currency = <<2::160>>\n      assert Fees.check_if_covered(%{other_currency => 0}, @payment_fees) == {:error, :fees_not_covered}\n    end\n\n    test \"returns :fees_not_covered when the implicit fees currency does not match any of the supported fee currencies\" do\n      other_currency = <<2::160>>\n      assert Fees.check_if_covered(%{other_currency => 100}, @payment_fees) == {:error, :fees_not_covered}\n    end\n\n    test \"returns :fees_not_covered when fees do not cover the fee price\" do\n      assert Fees.check_if_covered(%{@not_eth_1 => 1}, @payment_fees) == {:error, :fees_not_covered}\n    end\n\n    test \"returns :fees_not_covered when fees do not cover the fee price with previous fees\" do\n      fees =\n        @payment_fees\n        |> Map.put(@eth, [1, 2])\n        |> Map.put(@not_eth_1, [3, 1])\n\n      assert Fees.check_if_covered(%{@not_eth_1 => 2}, fees) == {:error, :fees_not_covered}\n    end\n\n    test \"returns :overpaying_fees when fees cover more than the fee price\" do\n      assert Fees.check_if_covered(%{@not_eth_1 => 4}, @payment_fees) == {:error, :overpaying_fees}\n    end\n\n    test \"returns :overpaying_fees when fees cover more than the fee price with previous fees\" do\n      fees =\n        @payment_fees\n        |> Map.put(@eth, [1, 2])\n        |> Map.put(@not_eth_1, [1, 3])\n\n      assert Fees.check_if_covered(%{@not_eth_1 => 2}, fees) == {:error, :overpaying_fees}\n    end\n\n    @tag fixtures: [:alice, :bob]\n    test \"returns :ok when one input is dedicated for fee payment, and outputs are other tokens\",\n         %{alice: alice, bob: bob} do\n      # a token that we don't allow to pay the fees in\n      other_token = <<2::160>>\n\n      # it is presumed that one input is `other_token` (to cover outputs) and the other input is `@not_eth_1` to cover\n      # the fee only. Note that `@not_eth_1` doesn't appear in the outputs\n      transaction =\n        create_recovered([{1, 0, 0, alice}, {2, 0, 0, alice}], [{bob, other_token, 5}, {alice, other_token, 5}])\n\n      fees = Fees.for_transaction(transaction, @fees)\n      # here we tell `Fees` that 3 `@not_eth_1` was sent to cover the fee\n      assert Fees.check_if_covered(%{@not_eth_1 => 3, other_token => 0}, fees) == :ok\n    end\n  end\n\n  describe \"for_transaction/2\" do\n    @tag fixtures: [:alice, :bob]\n    test \"returns the fee map when not a merge transaction\", %{alice: alice, bob: bob} do\n      transaction = create_recovered([{1, 0, 0, alice}], @eth, [{bob, 6}, {alice, 3}])\n      assert Fees.for_transaction(transaction, @fees) == @payment_fees\n    end\n\n    @tag fixtures: [:alice]\n    test \"returns :no_fees_required for merge transactions\",\n         %{alice: alice} do\n      transaction = create_recovered([{1, 0, 0, alice}, {2, 0, 0, alice}], @eth, [{alice, 10}])\n      assert Fees.for_transaction(transaction, @fees) == :no_fees_required\n    end\n\n    @tag fixtures: [:alice]\n    test \"returns :no_fees_required for valid merge transactions with multiple inputs/ouputs\",\n         %{alice: alice} do\n      transaction =\n        create_recovered(\n          [{1, 0, 0, alice}, {1, 0, 1, alice}, {2, 0, 0, alice}, {2, 1, 0, alice}],\n          [{alice, @eth, 10}, {alice, @eth, 10}]\n        )\n\n      assert Fees.for_transaction(transaction, @fees) == :no_fees_required\n    end\n\n    test \"returns an empty hash when given an unsuported tx type\" do\n      transaction = %OMG.Watcher.State.Transaction.Recovered{\n        signed_tx: %OMG.Watcher.State.Transaction.Signed{raw_tx: DummyTransaction.new(), sigs: []},\n        tx_hash: \"\",\n        witnesses: [],\n        signed_tx_bytes: \"\"\n      }\n\n      assert Fees.for_transaction(transaction, @fees) == %{}\n    end\n\n    @tag fixtures: [:alice, :bob]\n    test \"returns an empty hash when given invalid tx type\", %{alice: alice, bob: bob} do\n      fees = %{\n        999 => %{\n          @eth => %{\n            amount: 1,\n            subunit_to_unit: 1_000_000_000_000_000_000,\n            pegged_amount: 4,\n            pegged_currency: \"USD\",\n            pegged_subunit_to_unit: 100,\n            updated_at: DateTime.from_iso8601(\"2019-01-01T10:10:00+00:00\")\n          }\n        }\n      }\n\n      transaction = create_recovered([{1, 0, 0, alice}], @eth, [{bob, 6}, {alice, 3}])\n      assert Fees.for_transaction(transaction, fees) == %{}\n    end\n  end\n\n  defmodule DummyTransaction do\n    defstruct []\n\n    def new(), do: %__MODULE__{}\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/http_rpc/adapter_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.HttpRPC.AdapterTest do\n  use ExUnit.Case, async: true\n\n  import FakeServer\n\n  alias OMG.Utils.AppVersion\n  alias OMG.Watcher.HttpRPC.Adapter\n\n  describe \"rpc_post/3\" do\n    test_with_server \"includes X-Watcher-Version header\" do\n      route(\"/path\", FakeServer.Response.ok())\n      _ = Adapter.rpc_post(%{}, \"path\", FakeServer.address())\n\n      expected_watcher_version = AppVersion.version(:omg_watcher_info)\n\n      assert request_received(\n               \"/path\",\n               method: \"POST\",\n               headers: %{\"content-type\" => \"application/json\", \"x-watcher-version\" => expected_watcher_version}\n             )\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/integration/block_getter_1_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.Integration.BlockGetter1Test do\n  @moduledoc \"\"\"\n  This test is intended to be the major smoke/integration test of the Watcher\n\n  It tests whether valid/invalid blocks, deposits and exits are tracked correctly within the Watcher\n  \"\"\"\n\n  use ExUnitFixtures\n  use ExUnit.Case, async: false\n  use OMG.Watcher.Fixtures\n  use OMG.Watcher.Integration.Fixtures\n  use Plug.Test\n\n  require OMG.Watcher.Utxo\n\n  alias OMG.Eth\n  alias OMG.Watcher.BlockGetter\n  alias OMG.Watcher.Event\n  alias OMG.Watcher.Integration.BadChildChainServer\n  alias OMG.Watcher.Integration.TestHelper, as: IntegrationTest\n  alias OMG.Watcher.TestHelper\n  alias Support.DevHelper\n  alias Support.RootChainHelper\n  alias Support.WatcherHelper\n\n  @timeout 40_000\n  @eth <<0::160>>\n\n  @moduletag :mix_based_child_chain\n\n  @moduletag timeout: 150_000\n\n  @tag fixtures: [:in_beam_watcher, :stable_alice, :token, :stable_alice_deposits, :test_server]\n  test \"transaction which is spending an exiting output before the `sla_margin` causes an invalid_exit event only\",\n       %{stable_alice: alice, stable_alice_deposits: {deposit_blknum, _}, test_server: context} do\n    Process.sleep(12_000)\n    tx = TestHelper.create_encoded([{deposit_blknum, 0, 0, alice}], @eth, [{alice, 9}])\n    %{\"blknum\" => exit_blknum} = WatcherHelper.submit(tx)\n\n    # Here we're preparing invalid block\n    bad_block_number = 2_000\n    bad_tx = TestHelper.create_recovered([{exit_blknum, 0, 0, alice}], @eth, [{alice, 9}])\n\n    %{hash: bad_block_hash, number: _, transactions: _} =\n      bad_block = OMG.Watcher.Block.hashed_txs_at([bad_tx], bad_block_number)\n\n    # from now on the child chain server is broken until end of test\n    route = BadChildChainServer.prepare_route_to_inject_bad_block(context, bad_block)\n\n    :sys.replace_state(BlockGetter, fn state ->\n      config = state.config\n      new_config = %{config | child_chain_url: \"http://localhost:#{route.port}\"}\n      %{state | config: new_config}\n    end)\n\n    IntegrationTest.wait_for_block_fetch(exit_blknum, @timeout)\n    Process.sleep(12_000)\n\n    %{\n      \"txbytes\" => txbytes,\n      \"proof\" => proof,\n      \"utxo_pos\" => utxo_pos\n    } = WatcherHelper.get_exit_data(exit_blknum, 0, 0)\n\n    {:ok, %{\"status\" => \"0x1\", \"blockNumber\" => _eth_height}} =\n      utxo_pos\n      |> RootChainHelper.start_exit(\n        txbytes,\n        proof,\n        alice.addr\n      )\n      |> DevHelper.transact_sync!()\n\n    # THIS IS CHILDCHAIN CODE\n\n    # Here we're manually submitting invalid block to the root chain\n    # NOTE: this **must** come after `start_exit` is mined (see just above) but still not later than\n    #       `sla_margin` after exit start, hence the `config/test.exs` entry for the margin is rather high\n    {:ok, _} = Eth.submit_block(bad_block_hash, 2, 1)\n\n    IntegrationTest.wait_for_byzantine_events([%Event.InvalidExit{}.name], @timeout)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/integration/block_getter_2_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.Integration.BlockGetter2Test do\n  @moduledoc \"\"\"\n  This test is intended to be the major smoke/integration test of the Watcher\n\n  It tests whether valid/invalid blocks, deposits and exits are tracked correctly within the Watcher\n  \"\"\"\n\n  use ExUnitFixtures\n  use ExUnit.Case, async: false\n  use OMG.Watcher.Fixtures\n  use OMG.Watcher.Integration.Fixtures\n  use Plug.Test\n\n  require OMG.Watcher.Utxo\n\n  alias OMG.Eth\n  alias OMG.Watcher.BlockGetter\n  alias OMG.Watcher.Event\n  alias OMG.Watcher.Integration.BadChildChainServer\n  alias OMG.Watcher.Integration.TestHelper, as: IntegrationTest\n  alias OMG.Watcher.TestHelper\n  alias Support.DevHelper\n  alias Support.RootChainHelper\n  alias Support.WatcherHelper\n\n  @timeout 60_000\n  @eth <<0::160>>\n\n  @moduletag :mix_based_child_chain\n\n  @moduletag timeout: 180_000\n\n  @tag fixtures: [:in_beam_watcher, :stable_alice, :token, :stable_alice_deposits, :test_server]\n  test \"transaction which is spending an exiting output after the `sla_margin` causes an unchallenged_exit event\",\n       %{stable_alice: alice, stable_alice_deposits: {deposit_blknum, _}, test_server: context} do\n    Process.sleep(12_000)\n\n    tx = TestHelper.create_encoded([{deposit_blknum, 0, 0, alice}], @eth, [{alice, 9}])\n    %{\"blknum\" => exit_blknum} = WatcherHelper.submit(tx)\n\n    # Here we're preparing invalid block\n    bad_tx = OMG.Watcher.TestHelper.create_recovered([{exit_blknum, 0, 0, alice}], @eth, [{alice, 9}])\n\n    bad_block_number = 2_000\n\n    %{hash: bad_block_hash, number: _, transactions: _} =\n      bad_block = OMG.Watcher.Block.hashed_txs_at([bad_tx], bad_block_number)\n\n    # from now on the child chain server is broken until end of test\n    route = BadChildChainServer.prepare_route_to_inject_bad_block(context, bad_block)\n\n    :sys.replace_state(BlockGetter, fn state ->\n      config = state.config\n      new_config = %{config | child_chain_url: \"http://localhost:#{route.port}\"}\n      %{state | config: new_config}\n    end)\n\n    IntegrationTest.wait_for_block_fetch(exit_blknum, @timeout)\n    Process.sleep(10_000)\n\n    %{\n      \"txbytes\" => txbytes,\n      \"proof\" => proof,\n      \"utxo_pos\" => utxo_pos\n    } = WatcherHelper.get_exit_data(exit_blknum, 0, 0)\n\n    {:ok, %{\"status\" => \"0x1\", \"blockNumber\" => eth_height}} =\n      utxo_pos\n      |> RootChainHelper.start_exit(\n        txbytes,\n        proof,\n        alice.addr\n      )\n      |> DevHelper.transact_sync!()\n\n    exit_processor_sla_margin = Application.fetch_env!(:omg_watcher, :exit_processor_sla_margin)\n    DevHelper.wait_for_root_chain_block(eth_height + exit_processor_sla_margin, @timeout)\n\n    # checking if both machines and humans learn about the byzantine condition\n    assert WatcherHelper.capture_log(fn ->\n             # Here we're manually submitting invalid block to the root chain\n             # THIS IS CHILDCHAIN CODE\n             {:ok, _} = Eth.submit_block(bad_block_hash, 2, 1)\n\n             IntegrationTest.wait_for_byzantine_events(\n               [%Event.InvalidExit{}.name, %Event.UnchallengedExit{}.name],\n               @timeout\n             )\n           end) =~ inspect(:unchallenged_exit)\n\n    # we should still be able to challenge this \"unchallenged exit\" - just smoke testing the endpoint, details elsewhere\n    WatcherHelper.get_exit_challenge(exit_blknum, 0, 0)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/integration/block_getter_3_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.Integration.BlockGetter3Test do\n  @moduledoc \"\"\"\n  This test is intended to be the major smoke/integration test of the Watcher\n\n  It tests whether valid/invalid blocks, deposits and exits are tracked correctly within the Watcher\n  \"\"\"\n\n  use ExUnitFixtures\n  use ExUnit.Case, async: false\n  use OMG.Watcher.Fixtures\n  use OMG.Watcher.Integration.Fixtures\n  use Plug.Test\n\n  require OMG.Watcher.Utxo\n\n  import ExUnit.CaptureLog, only: [capture_log: 1]\n\n  alias OMG.Eth\n  alias OMG.Watcher.Event\n  alias OMG.Watcher.Integration.TestHelper, as: IntegrationTest\n  alias OMG.Watcher.TestHelper\n  alias Support.DevHelper\n  alias Support.RootChainHelper\n  alias Support.WatcherHelper\n\n  @timeout 40_000\n  @eth <<0::160>>\n\n  @moduletag :mix_based_child_chain\n\n  @moduletag timeout: 100_000\n\n  @tag fixtures: [:in_beam_watcher, :stable_alice, :token, :stable_alice_deposits]\n  test \"block getting halted by block withholding doesn't halt detection of new invalid exits\", %{\n    stable_alice: alice,\n    stable_alice_deposits: {deposit_blknum, _}\n  } do\n    Process.sleep(11_000)\n    tx = TestHelper.create_encoded([{deposit_blknum, 0, 0, alice}], @eth, [{alice, 9}])\n    %{\"blknum\" => deposit_blknum} = WatcherHelper.submit(tx)\n\n    tx = TestHelper.create_encoded([{deposit_blknum, 0, 0, alice}], @eth, [{alice, 8}])\n    %{\"blknum\" => tx_blknum, \"txhash\" => _tx_hash} = WatcherHelper.submit(tx)\n\n    IntegrationTest.wait_for_block_fetch(tx_blknum, @timeout)\n\n    {_, nonce} = get_next_blknum_nonce(tx_blknum)\n\n    {:ok, _txhash} = Eth.submit_block(<<0::256>>, nonce, 20_000_000_000)\n\n    # checking if both machines and humans learn about the byzantine condition\n    assert capture_log(fn ->\n             IntegrationTest.wait_for_byzantine_events([%Event.BlockWithholding{}.name], @timeout)\n           end) =~ inspect(:withholding)\n\n    %{\n      \"txbytes\" => txbytes,\n      \"proof\" => proof,\n      \"utxo_pos\" => utxo_pos\n    } = WatcherHelper.get_exit_data(deposit_blknum, 0, 0)\n\n    {:ok, %{\"status\" => \"0x1\", \"blockNumber\" => _eth_height}} =\n      utxo_pos\n      |> RootChainHelper.start_exit(\n        txbytes,\n        proof,\n        alice.addr\n      )\n      |> DevHelper.transact_sync!()\n\n    IntegrationTest.wait_for_byzantine_events([%Event.BlockWithholding{}.name, %Event.InvalidExit{}.name], @timeout)\n  end\n\n  defp get_next_blknum_nonce(blknum) do\n    child_block_interval = Application.fetch_env!(:omg_eth, :child_block_interval)\n    next_blknum = blknum + child_block_interval\n    {next_blknum, trunc(next_blknum / child_block_interval)}\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/integration/block_getter_4_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.Integration.BlockGetter4Test do\n  @moduledoc \"\"\"\n  This test is intended to be the major smoke/integration test of the Watcher\n\n  It tests whether valid/invalid blocks, deposits and exits are tracked correctly within the Watcher\n  \"\"\"\n\n  use ExUnitFixtures\n  use ExUnit.Case, async: false\n  use OMG.Watcher.Fixtures\n  use OMG.Watcher.Integration.Fixtures\n  use Plug.Test\n\n  require OMG.Watcher.Utxo\n\n  alias OMG.Eth\n  alias OMG.Watcher.BlockGetter\n  alias OMG.Watcher.Configuration\n  alias OMG.Watcher.Event\n  alias OMG.Watcher.Integration.BadChildChainServer\n  alias OMG.Watcher.Integration.TestHelper, as: IntegrationTest\n  alias OMG.Watcher.TestHelper\n  alias Support.WatcherHelper\n\n  @timeout 40_000\n  @eth <<0::160>>\n\n  @moduletag :mix_based_child_chain\n\n  @moduletag timeout: 100_000\n\n  @tag fixtures: [:in_beam_watcher, :test_server, :stable_alice, :stable_alice_deposits]\n  test \"operator claimed fees incorrectly (too much | little amount, not collected token)\", %{\n    stable_alice: alice,\n    test_server: context,\n    stable_alice_deposits: {deposit_blknum, _}\n  } do\n    Process.sleep(11_000)\n    fee_claimer = Configuration.fee_claimer_address()\n\n    # preparing transactions for a fake block that overclaim fees\n    txs = [\n      TestHelper.create_recovered([{deposit_blknum, 0, 0, alice}], @eth, [{alice, 9}]),\n      TestHelper.create_recovered([{1000, 0, 0, alice}], @eth, [{alice, 8}]),\n      TestHelper.create_recovered_fee_tx(1000, fee_claimer, @eth, 3)\n    ]\n\n    block_overclaiming_fees = OMG.Watcher.Block.hashed_txs_at(txs, 1000)\n\n    # from now on the child chain server is broken until end of test\n    route =\n      BadChildChainServer.prepare_route_to_inject_bad_block(\n        context,\n        block_overclaiming_fees\n      )\n\n    :sys.replace_state(BlockGetter, fn state ->\n      config = state.config\n      new_config = %{config | child_chain_url: \"http://localhost:#{route.port}\"}\n      %{state | config: new_config}\n    end)\n\n    # checking if both machines and humans learn about the byzantine condition\n    assert WatcherHelper.capture_log(fn ->\n             {:ok, _txhash} = Eth.submit_block(block_overclaiming_fees.hash, 1, 20_000_000_000)\n             IntegrationTest.wait_for_byzantine_events([%Event.InvalidBlock{}.name], @timeout)\n           end) =~ inspect({:tx_execution, :claimed_and_collected_amounts_mismatch})\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/integration/block_getter_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.Integration.BlockGetterTest do\n  @moduledoc \"\"\"\n  This test is intended to be the major smoke/integration test of the Watcher\n\n  It tests whether valid/invalid blocks, deposits and exits are tracked correctly within the Watcher\n  \"\"\"\n\n  use ExUnitFixtures\n  use ExUnit.Case, async: false\n  use OMG.Watcher.Fixtures\n  use OMG.Watcher.Integration.Fixtures\n  use Plug.Test\n\n  require OMG.Watcher.Utxo\n\n  alias OMG.Eth\n  alias OMG.Watcher.BlockGetter\n  alias OMG.Watcher.Event\n  alias OMG.Watcher.Integration.BadChildChainServer\n  alias OMG.Watcher.Integration.TestHelper, as: IntegrationTest\n  alias Support.WatcherHelper\n\n  @timeout 40_000\n  @eth <<0::160>>\n\n  @moduletag :integration\n  @moduletag :watcher\n\n  @moduletag timeout: 100_000\n\n  @tag fixtures: [:in_beam_watcher, :test_server]\n  test \"hash of returned block does not match hash submitted to the root chain\", %{test_server: context} do\n    different_hash = <<0::256>>\n    block_with_incorrect_hash = %{OMG.Watcher.Block.hashed_txs_at([], 1000) | hash: different_hash}\n\n    # from now on the child chain server is broken until end of test\n    route =\n      BadChildChainServer.prepare_route_to_inject_bad_block(\n        context,\n        block_with_incorrect_hash,\n        different_hash\n      )\n\n    :sys.replace_state(BlockGetter, fn state ->\n      config = state.config\n      new_config = %{config | child_chain_url: \"http://localhost:#{route.port}\"}\n      %{state | config: new_config}\n    end)\n\n    # checking if both machines and humans learn about the byzantine condition\n    assert WatcherHelper.capture_log(fn ->\n             {:ok, _txhash} = Eth.submit_block(different_hash, 1, 20_000_000_000)\n             IntegrationTest.wait_for_byzantine_events([%Event.InvalidBlock{}.name], @timeout)\n           end) =~ inspect({:error, :incorrect_hash})\n  end\n\n  @tag fixtures: [:in_beam_watcher, :alice, :test_server]\n  test \"bad transaction with not existing utxo, detected by interactions with State\", %{\n    alice: alice,\n    test_server: context\n  } do\n    # preparing block with invalid transaction\n    recovered = OMG.Watcher.TestHelper.create_recovered([{1, 0, 0, alice}], @eth, [{alice, 10}])\n    block_with_incorrect_transaction = OMG.Watcher.Block.hashed_txs_at([recovered], 1000)\n\n    # from now on the child chain server is broken until end of test\n    route =\n      BadChildChainServer.prepare_route_to_inject_bad_block(\n        context,\n        block_with_incorrect_transaction\n      )\n\n    :sys.replace_state(BlockGetter, fn state ->\n      config = state.config\n      new_config = %{config | child_chain_url: \"http://localhost:#{route.port}\"}\n      %{state | config: new_config}\n    end)\n\n    invalid_block_hash = block_with_incorrect_transaction.hash\n\n    # checking if both machines and humans learn about the byzantine condition\n    assert WatcherHelper.capture_log(fn ->\n             {:ok, _txhash} = Eth.submit_block(invalid_block_hash, 1, 20_000_000_000)\n             IntegrationTest.wait_for_byzantine_events([%Event.InvalidBlock{}.name], @timeout)\n           end) =~ inspect(:tx_execution)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/integration/in_flight_exit_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.Integration.InFlightExitTest do\n  @moduledoc \"\"\"\n  This needs to go away real soon.\n  \"\"\"\n  use ExUnitFixtures\n  use ExUnit.Case, async: false\n  use OMG.Watcher.Fixtures\n  use Plug.Test\n  use OMG.Watcher.Integration.Fixtures\n\n  alias OMG.Watcher.Integration.TestHelper, as: IntegrationTest\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.TestHelper\n  alias OMG.Watcher.Utxo\n  alias Support.DevHelper\n  alias Support.RootChainHelper\n  alias Support.WatcherHelper\n  require Utxo\n\n  @timeout 40_000\n  @eth <<0::160>>\n  @hex_eth \"0x0000000000000000000000000000000000000000\"\n\n  @moduletag :mix_based_child_chain\n  # bumping the timeout to three minutes for the tests here, as they do a lot of transactions to Ethereum to test\n  @moduletag timeout: 240_000\n\n  @tag fixtures: [:in_beam_watcher, :alice, :bob, :token, :alice_deposits]\n  test \"finalization of utxo double-spent in state leaves in-flight exit active and invalid; warns\",\n       %{alice: alice, bob: bob, alice_deposits: {deposit_blknum, _}} do\n    Process.sleep(12_000)\n    DevHelper.import_unlock_fund(bob)\n\n    tx = TestHelper.create_signed([{deposit_blknum, 0, 0, alice}], @eth, [{alice, 5}, {bob, 4}])\n    ife1 = tx |> Transaction.Signed.encode() |> WatcherHelper.get_in_flight_exit()\n\n    %{\"blknum\" => blknum} = tx |> Transaction.Signed.encode() |> WatcherHelper.submit()\n    invalidating_tx = TestHelper.create_encoded([{blknum, 0, 0, alice}], @eth, [{alice, 4}])\n    %{\"blknum\" => invalidating_blknum} = WatcherHelper.submit(invalidating_tx)\n    IntegrationTest.wait_for_block_fetch(invalidating_blknum, @timeout)\n\n    _ = exit_in_flight_and_wait_for_ife(ife1, alice)\n\n    # checking if both machines and humans learn about the byzantine condition\n    assert WatcherHelper.capture_log(fn ->\n             # :output type\n             _ = piggyback_and_process_exits(tx, 0, alice)\n           end) =~ \"Invalid in-flight exit finalization\"\n\n    assert %{\"in_flight_exits\" => [_], \"byzantine_events\" => byzantine_events} = WatcherHelper.success?(\"/status.get\")\n    # invalid piggyback is past sla margin, unchallenged_piggyback event is emitted\n    assert [%{\"event\" => \"unchallenged_piggyback\"}, %{\"event\" => \"invalid_piggyback\"}] =\n             Enum.filter(byzantine_events, &(&1[\"event\"] != \"piggyback_available\"))\n  end\n\n  defp exit_in_flight(%Transaction.Signed{} = tx, exiting_user) do\n    get_in_flight_exit_response = tx |> Transaction.Signed.encode() |> WatcherHelper.get_in_flight_exit()\n    exit_in_flight(get_in_flight_exit_response, exiting_user)\n  end\n\n  defp exit_in_flight(get_in_flight_exit_response, exiting_user) do\n    RootChainHelper.in_flight_exit(\n      get_in_flight_exit_response[\"in_flight_tx\"],\n      get_in_flight_exit_response[\"input_txs\"],\n      get_in_flight_exit_response[\"input_utxos_pos\"],\n      get_in_flight_exit_response[\"input_txs_inclusion_proofs\"],\n      get_in_flight_exit_response[\"in_flight_tx_sigs\"],\n      exiting_user.addr\n    )\n    |> DevHelper.transact_sync!()\n  end\n\n  defp exit_in_flight_and_wait_for_ife(tx, exiting_user) do\n    {:ok, %{\"status\" => \"0x1\", \"blockNumber\" => eth_height}} = exit_in_flight(tx, exiting_user)\n    exit_finality_margin = Application.fetch_env!(:omg_watcher, :exit_finality_margin)\n    DevHelper.wait_for_root_chain_block(eth_height + exit_finality_margin + 1)\n  end\n\n  defp piggyback_and_process_exits(%Transaction.Signed{raw_tx: raw_tx}, index, output_owner) do\n    raw_tx_bytes = Transaction.raw_txbytes(raw_tx)\n\n    {:ok, %{\"status\" => \"0x1\"}} =\n      raw_tx_bytes\n      |> RootChainHelper.piggyback_in_flight_exit_on_output(index, output_owner.addr)\n      |> DevHelper.transact_sync!()\n\n    :ok = IntegrationTest.process_exits(1, @hex_eth, output_owner)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/integration/in_flight_exit_test_1_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.Integration.InFlightExit1Test do\n  @moduledoc \"\"\"\n  This needs to go away real soon.\n  \"\"\"\n  use ExUnitFixtures\n  use ExUnit.Case, async: false\n  use OMG.Watcher.Fixtures\n  use Plug.Test\n  use OMG.Watcher.Integration.Fixtures\n\n  alias OMG.Watcher.EthereumEventAggregator\n  alias OMG.Watcher.Integration.TestHelper, as: IntegrationTest\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.TestHelper\n  alias OMG.Watcher.Utxo\n  alias Support.DevHelper\n  alias Support.RootChainHelper\n  alias Support.WatcherHelper\n  require Utxo\n\n  @timeout 40_000\n  @eth <<0::160>>\n\n  @moduletag :mix_based_child_chain\n  # bumping the timeout to three minutes for the tests here, as they do a lot of transactions to Ethereum to test\n  @moduletag timeout: 180_000\n\n  @tag fixtures: [:in_beam_watcher, :alice, :bob, :token, :alice_deposits]\n  test \"invalid in-flight exit challenge is detected by watcher, because it contains no position\",\n       %{alice: alice, bob: bob, alice_deposits: {deposit_blknum, _}} do\n    # we need to recognized the deposit on the childchain first\n    Process.sleep(12_000)\n    # tx1 is submitted then in-flight-exited\n    # tx2 is in-flight-exited, it will be _invalidly_ used to challenge tx1!\n    tx1 = TestHelper.create_signed([{deposit_blknum, 0, 0, alice}], @eth, [{alice, 5}, {alice, 4}])\n    tx2 = TestHelper.create_signed([{deposit_blknum, 0, 0, alice}], @eth, [{bob, 9}])\n\n    ife1 = tx1 |> Transaction.Signed.encode() |> WatcherHelper.get_in_flight_exit()\n    ife2 = tx2 |> Transaction.Signed.encode() |> WatcherHelper.get_in_flight_exit()\n\n    assert %{\n             \"blknum\" => blknum,\n             \"txindex\" => 0,\n             \"txhash\" => <<_::256>>\n           } = tx1 |> Transaction.Signed.encode() |> WatcherHelper.submit()\n\n    IntegrationTest.wait_for_block_fetch(blknum, @timeout)\n\n    raw_tx1_bytes = Transaction.raw_txbytes(tx1)\n    raw_tx2_bytes = Transaction.raw_txbytes(tx2)\n\n    {:ok, %{\"status\" => \"0x1\", \"blockNumber\" => _}} = exit_in_flight(ife1, alice)\n    {:ok, %{\"status\" => \"0x1\", \"blockNumber\" => ife_eth_height}} = exit_in_flight(ife2, alice)\n    # sanity check in-flight exit has started on root chain, wait for finality\n    assert {:ok, [_, _]} = EthereumEventAggregator.in_flight_exit_started(0, ife_eth_height)\n    exit_finality_margin = Application.fetch_env!(:omg_watcher, :exit_finality_margin)\n    DevHelper.wait_for_root_chain_block(ife_eth_height + exit_finality_margin)\n\n    ###\n    # CANONICITY GAME\n    ###\n\n    # we're unable to get the invalid challenge using `in_flight_exit.get_competitor`!\n    # ...so we need to stich it together from some pieces we have:\n    %{sigs: [competing_sig | _]} = tx2\n    competing_tx_input_txbytes = [] |> Transaction.Payment.new([{alice.addr, @eth, 10}]) |> Transaction.raw_txbytes()\n    competing_tx_input_utxo_pos = Utxo.Position.encode(Utxo.position(deposit_blknum, 0, 0))\n\n    {:ok, %{\"status\" => \"0x1\", \"blockNumber\" => _challenge_eth_height}} =\n      competing_tx_input_txbytes\n      |> RootChainHelper.challenge_in_flight_exit_not_canonical(\n        competing_tx_input_utxo_pos,\n        raw_tx1_bytes,\n        0,\n        raw_tx2_bytes,\n        0,\n        0,\n        \"\",\n        competing_sig,\n        alice.addr\n      )\n      |> DevHelper.transact_sync!()\n\n    # existence of `invalid_ife_challenge` event\n    # vanishing of `non_canonical_ife` event\n    expected_events = [\n      # this is the tx2's non-canonical-ife which we leave as is\n      %{\"event\" => \"non_canonical_ife\"},\n      %{\"event\" => \"invalid_ife_challenge\"},\n      %{\"event\" => \"piggyback_available\"}\n    ]\n\n    :ok = wait_for(expected_events)\n\n    # now included IFE transaction tx1 is challenged and non-canonical, let's respond\n    get_prove_canonical_response = WatcherHelper.get_prove_canonical(raw_tx1_bytes)\n\n    {:ok, %{\"status\" => \"0x1\", \"blockNumber\" => _response_eth_height}} =\n      get_prove_canonical_response[\"in_flight_txbytes\"]\n      |> RootChainHelper.respond_to_non_canonical_challenge(\n        get_prove_canonical_response[\"in_flight_tx_pos\"],\n        get_prove_canonical_response[\"in_flight_proof\"],\n        alice.addr\n      )\n      |> DevHelper.transact_sync!()\n\n    expected_events = [\n      # this is the tx2's non-canonical-ife which we leave as is\n      %{\"event\" => \"non_canonical_ife\"},\n      %{\"event\" => \"piggyback_available\"}\n    ]\n\n    :ok = wait_for(expected_events)\n  end\n\n  defp exit_in_flight(%Transaction.Signed{} = tx, exiting_user) do\n    get_in_flight_exit_response = tx |> Transaction.Signed.encode() |> WatcherHelper.get_in_flight_exit()\n    exit_in_flight(get_in_flight_exit_response, exiting_user)\n  end\n\n  defp exit_in_flight(get_in_flight_exit_response, exiting_user) do\n    get_in_flight_exit_response[\"in_flight_tx\"]\n    |> RootChainHelper.in_flight_exit(\n      get_in_flight_exit_response[\"input_txs\"],\n      get_in_flight_exit_response[\"input_utxos_pos\"],\n      get_in_flight_exit_response[\"input_txs_inclusion_proofs\"],\n      get_in_flight_exit_response[\"in_flight_tx_sigs\"],\n      exiting_user.addr\n    )\n    |> DevHelper.transact_sync!()\n  end\n\n  defp wait_for(expected_events) do\n    Enum.reduce_while(1..1000, 0, fn x, acc ->\n      events =\n        \"/status.get\" |> WatcherHelper.success?() |> Map.get(\"byzantine_events\") |> Enum.map(&Map.take(&1, [\"event\"]))\n\n      case events do\n        ^expected_events ->\n          {:halt, :ok}\n\n        _ ->\n          Process.sleep(10)\n\n          {:cont, acc + x}\n      end\n    end)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/integration/in_flight_exit_test_2_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.Integration.InFlightExit2Test do\n  @moduledoc \"\"\"\n  This needs to go away real soon.\n  \"\"\"\n  use ExUnitFixtures\n  use ExUnit.Case, async: false\n  use OMG.Watcher.Fixtures\n  use Plug.Test\n  use OMG.Watcher.Integration.Fixtures\n\n  alias OMG.Watcher.EthereumEventAggregator\n  alias OMG.Watcher.Integration.TestHelper, as: IntegrationTest\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.TestHelper\n  alias OMG.Watcher.Utxo\n  alias Support.DevHelper\n  alias Support.RootChainHelper\n  alias Support.WatcherHelper\n\n  require Utxo\n\n  @timeout 40_000\n  @eth <<0::160>>\n\n  @moduletag :mix_based_child_chain\n  # bumping the timeout to three minutes for the tests here, as they do a lot of transactions to Ethereum to test\n  @moduletag timeout: 180_000\n\n  @tag fixtures: [:in_beam_watcher, :alice, :bob, :token, :alice_deposits]\n  test \"in-flight exit competitor is detected by watcher and proven with position immediately\",\n       %{alice: alice, bob: bob, alice_deposits: {deposit_blknum, _}} do\n    # we need to recognized the deposit on the childchain first\n    Process.sleep(12_000)\n    # tx1 is submitted then in-flight-exited\n    # tx2 is in-flight-exited\n    tx1 = TestHelper.create_signed([{deposit_blknum, 0, 0, alice}], @eth, [{alice, 5}, {alice, 4}])\n    tx2 = TestHelper.create_signed([{deposit_blknum, 0, 0, alice}], @eth, [{bob, 9}])\n\n    ife1 = tx1 |> Transaction.Signed.encode() |> WatcherHelper.get_in_flight_exit()\n    ife2 = tx2 |> Transaction.Signed.encode() |> WatcherHelper.get_in_flight_exit()\n\n    assert %{\"blknum\" => blknum} = tx1 |> Transaction.Signed.encode() |> WatcherHelper.submit()\n\n    IntegrationTest.wait_for_block_fetch(blknum, @timeout)\n\n    raw_tx2_bytes = Transaction.raw_txbytes(tx2)\n\n    {:ok, %{\"status\" => \"0x1\", \"blockNumber\" => _}} = exit_in_flight(ife1, alice)\n    {:ok, %{\"status\" => \"0x1\", \"blockNumber\" => ife_eth_height}} = exit_in_flight(ife2, alice)\n    # sanity check in-flight exit has started on root chain, wait for finality\n    assert {:ok, [_, _]} = EthereumEventAggregator.in_flight_exit_started(0, ife_eth_height)\n\n    ###\n    # EVENTS DETECTION\n    ###\n\n    # existence of competitors detected by checking if `non_canonical_ife` events exists\n    # Also, there should be piggybacks on input/output available\n\n    expected_events = [\n      # only a single non_canonical event, since on of the IFE tx is included!\n      %{\"event\" => \"non_canonical_ife\"},\n      %{\"event\" => \"piggyback_available\"}\n    ]\n\n    :ok = wait_for(expected_events)\n\n    # Check if IFE is recognized as IFE by watcher (kept separate from the above for readability)\n    assert %{\"in_flight_exits\" => [%{}, %{}]} = WatcherHelper.success?(\"/status.get\")\n\n    ###\n    # CANONICITY GAME\n    ###\n\n    assert %{\"competing_tx_pos\" => id, \"competing_proof\" => proof} =\n             get_competitor_response = WatcherHelper.get_in_flight_exit_competitors(raw_tx2_bytes)\n\n    assert id > 0\n    assert proof != \"\"\n\n    {:ok, %{\"status\" => \"0x1\", \"blockNumber\" => _challenge_eth_height}} =\n      RootChainHelper.challenge_in_flight_exit_not_canonical(\n        get_competitor_response[\"input_tx\"],\n        get_competitor_response[\"input_utxo_pos\"],\n        get_competitor_response[\"in_flight_txbytes\"],\n        get_competitor_response[\"in_flight_input_index\"],\n        get_competitor_response[\"competing_txbytes\"],\n        get_competitor_response[\"competing_input_index\"],\n        get_competitor_response[\"competing_tx_pos\"],\n        get_competitor_response[\"competing_proof\"],\n        get_competitor_response[\"competing_sig\"],\n        alice.addr\n      )\n      |> DevHelper.transact_sync!()\n\n    # vanishing of `non_canonical_ife` event\n    expected_events = [%{\"event\" => \"piggyback_available\"}]\n\n    :ok = wait_for(expected_events)\n  end\n\n  defp exit_in_flight(%Transaction.Signed{} = tx, exiting_user) do\n    get_in_flight_exit_response = tx |> Transaction.Signed.encode() |> WatcherHelper.get_in_flight_exit()\n    exit_in_flight(get_in_flight_exit_response, exiting_user)\n  end\n\n  defp exit_in_flight(get_in_flight_exit_response, exiting_user) do\n    RootChainHelper.in_flight_exit(\n      get_in_flight_exit_response[\"in_flight_tx\"],\n      get_in_flight_exit_response[\"input_txs\"],\n      get_in_flight_exit_response[\"input_utxos_pos\"],\n      get_in_flight_exit_response[\"input_txs_inclusion_proofs\"],\n      get_in_flight_exit_response[\"in_flight_tx_sigs\"],\n      exiting_user.addr\n    )\n    |> DevHelper.transact_sync!()\n  end\n\n  defp wait_for(expected_events) do\n    Enum.reduce_while(1..1000, 0, fn x, acc ->\n      events =\n        \"/status.get\" |> WatcherHelper.success?() |> Map.get(\"byzantine_events\") |> Enum.map(&Map.take(&1, [\"event\"]))\n\n      case events do\n        ^expected_events ->\n          {:halt, :ok}\n\n        _ ->\n          Process.sleep(10)\n\n          {:cont, acc + x}\n      end\n    end)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/integration/in_flight_exit_test_3_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.Integration.InFlightExit3Test do\n  @moduledoc \"\"\"\n  This needs to go away real soon.\n  \"\"\"\n  use ExUnitFixtures\n  use ExUnit.Case, async: false\n  use OMG.Watcher.Fixtures\n  use Plug.Test\n  use OMG.Watcher.Integration.Fixtures\n\n  alias OMG.Watcher.Integration.TestHelper, as: IntegrationTest\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.TestHelper\n  alias OMG.Watcher.Utxo\n  alias Support.DevHelper\n  alias Support.RootChainHelper\n  alias Support.WatcherHelper\n  require Utxo\n\n  @timeout 40_000\n  @eth <<0::160>>\n  @hex_eth \"0x0000000000000000000000000000000000000000\"\n\n  @moduletag :mix_based_child_chain\n  # bumping the timeout to three minutes for the tests here, as they do a lot of transactions to Ethereum to test\n  @moduletag timeout: 180_000\n\n  @tag fixtures: [:in_beam_watcher, :alice, :bob, :token, :alice_deposits]\n  test \"honest and cooperating users exit in-flight transaction\",\n       %{alice: alice, bob: bob, alice_deposits: {deposit_blknum, _}} do\n    # we need to recognized the deposit on the childchain first\n    Process.sleep(12_000)\n    DevHelper.import_unlock_fund(bob)\n\n    tx = TestHelper.create_signed([{deposit_blknum, 0, 0, alice}], @eth, [{alice, 4}, {bob, 5}])\n    ife1 = tx |> Transaction.Signed.encode() |> WatcherHelper.get_in_flight_exit()\n\n    %{\"blknum\" => blknum} = tx |> Transaction.Signed.encode() |> WatcherHelper.submit()\n    IntegrationTest.wait_for_block_fetch(blknum, @timeout)\n\n    {:ok, %{\"status\" => \"0x1\", \"blockNumber\" => _eth_height}} = exit_in_flight(ife1, alice)\n\n    [ife] = wait_for_not_empty_in_flight_exits()\n    assert is_map(ife)\n    _ = piggyback_and_process_exits(tx, 1, :output, bob)\n    expected_events = []\n    :ok = wait_for(expected_events)\n    :ok = wait_for_empty_in_flight_exits()\n  end\n\n  defp piggyback_and_process_exits(%Transaction.Signed{raw_tx: raw_tx}, index, piggyback_type, output_owner) do\n    raw_tx_bytes = Transaction.raw_txbytes(raw_tx)\n\n    {:ok, %{\"status\" => \"0x1\"}} =\n      case piggyback_type do\n        :input ->\n          RootChainHelper.piggyback_in_flight_exit_on_input(raw_tx_bytes, index, output_owner.addr)\n\n        :output ->\n          RootChainHelper.piggyback_in_flight_exit_on_output(raw_tx_bytes, index, output_owner.addr)\n      end\n      |> DevHelper.transact_sync!()\n\n    :ok = IntegrationTest.process_exits(1, @hex_eth, output_owner)\n  end\n\n  defp exit_in_flight(%Transaction.Signed{} = tx, exiting_user) do\n    get_in_flight_exit_response = tx |> Transaction.Signed.encode() |> WatcherHelper.get_in_flight_exit()\n    exit_in_flight(get_in_flight_exit_response, exiting_user)\n  end\n\n  defp exit_in_flight(get_in_flight_exit_response, exiting_user) do\n    get_in_flight_exit_response[\"in_flight_tx\"]\n    |> RootChainHelper.in_flight_exit(\n      get_in_flight_exit_response[\"input_txs\"],\n      get_in_flight_exit_response[\"input_utxos_pos\"],\n      get_in_flight_exit_response[\"input_txs_inclusion_proofs\"],\n      get_in_flight_exit_response[\"in_flight_tx_sigs\"],\n      exiting_user.addr\n    )\n    |> DevHelper.transact_sync!()\n  end\n\n  defp wait_for(expected_events) do\n    Enum.reduce_while(1..1000, 0, fn x, acc ->\n      events =\n        \"/status.get\" |> WatcherHelper.success?() |> Map.get(\"byzantine_events\") |> Enum.map(&Map.take(&1, [\"event\"]))\n\n      case events do\n        ^expected_events ->\n          {:halt, :ok}\n\n        _ ->\n          Process.sleep(10)\n\n          {:cont, acc + x}\n      end\n    end)\n  end\n\n  defp wait_for_not_empty_in_flight_exits() do\n    Enum.reduce_while(1..1000, 0, fn x, acc ->\n      ife = \"/status.get\" |> WatcherHelper.success?() |> Map.get(\"in_flight_exits\")\n\n      case ife do\n        [] ->\n          Process.sleep(10)\n\n          {:cont, acc + x}\n\n        ife ->\n          {:halt, ife}\n      end\n    end)\n  end\n\n  defp wait_for_empty_in_flight_exits() do\n    Enum.reduce_while(1..1000, 0, fn x, acc ->\n      ife = \"/status.get\" |> WatcherHelper.success?() |> Map.get(\"in_flight_exits\")\n\n      case ife do\n        [] ->\n          {:halt, :ok}\n\n        _ ->\n          Process.sleep(10)\n\n          {:cont, acc + x}\n      end\n    end)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/integration/in_flight_exit_test_4_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.Integration.InFlightExit4Test do\n  @moduledoc \"\"\"\n  This needs to go away real soon.\n  \"\"\"\n  use ExUnitFixtures\n  use ExUnit.Case, async: false\n  use OMG.Watcher.Fixtures\n  use Plug.Test\n  use OMG.Watcher.Integration.Fixtures\n\n  alias OMG.Watcher.Integration.TestHelper, as: IntegrationTest\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.TestHelper\n  alias OMG.Watcher.Utxo\n  alias Support.DevHelper\n  alias Support.RootChainHelper\n  alias Support.WatcherHelper\n  require Utxo\n\n  @eth <<0::160>>\n  @hex_eth \"0x0000000000000000000000000000000000000000\"\n\n  @moduletag :mix_based_child_chain\n  # bumping the timeout to three minutes for the tests here, as they do a lot of transactions to Ethereum to test\n  @moduletag timeout: 180_000\n\n  # NOTE: if https://github.com/omgnetwork/elixir-omg/issues/994 is taken care of, this behavior will change, see comments\n  #       therein.\n  @tag fixtures: [:in_beam_watcher, :alice, :bob, :token, :alice_deposits]\n  test \"finalization of output from non-included IFE tx - all is good\",\n       %{alice: alice, bob: bob, alice_deposits: {deposit_blknum, _}} do\n    Process.sleep(12_000)\n    DevHelper.import_unlock_fund(bob)\n\n    tx = TestHelper.create_signed([{deposit_blknum, 0, 0, alice}], @eth, [{alice, 5}, {bob, 5}])\n    _ = exit_in_flight_and_wait_for_ife(tx, alice)\n    piggyback_and_process_exits(tx, 1, :output, bob)\n\n    expected_events = []\n    :ok = wait_for(expected_events)\n    :ok = wait_for_empty_in_flight_exits()\n  end\n\n  defp exit_in_flight(%Transaction.Signed{} = tx, exiting_user) do\n    get_in_flight_exit_response = tx |> Transaction.Signed.encode() |> WatcherHelper.get_in_flight_exit()\n    exit_in_flight(get_in_flight_exit_response, exiting_user)\n  end\n\n  defp exit_in_flight(get_in_flight_exit_response, exiting_user) do\n    RootChainHelper.in_flight_exit(\n      get_in_flight_exit_response[\"in_flight_tx\"],\n      get_in_flight_exit_response[\"input_txs\"],\n      get_in_flight_exit_response[\"input_utxos_pos\"],\n      get_in_flight_exit_response[\"input_txs_inclusion_proofs\"],\n      get_in_flight_exit_response[\"in_flight_tx_sigs\"],\n      exiting_user.addr\n    )\n    |> DevHelper.transact_sync!()\n  end\n\n  defp exit_in_flight_and_wait_for_ife(tx, exiting_user) do\n    {:ok, %{\"status\" => \"0x1\", \"blockNumber\" => eth_height}} = exit_in_flight(tx, exiting_user)\n    exit_finality_margin = Application.fetch_env!(:omg_watcher, :exit_finality_margin)\n    DevHelper.wait_for_root_chain_block(eth_height + exit_finality_margin + 1)\n  end\n\n  defp piggyback_and_process_exits(%Transaction.Signed{raw_tx: raw_tx}, index, piggyback_type, output_owner) do\n    raw_tx_bytes = Transaction.raw_txbytes(raw_tx)\n\n    {:ok, %{\"status\" => \"0x1\"}} =\n      case piggyback_type do\n        :input ->\n          RootChainHelper.piggyback_in_flight_exit_on_input(raw_tx_bytes, index, output_owner.addr)\n\n        :output ->\n          RootChainHelper.piggyback_in_flight_exit_on_output(raw_tx_bytes, index, output_owner.addr)\n      end\n      |> DevHelper.transact_sync!()\n\n    :ok = IntegrationTest.process_exits(1, @hex_eth, output_owner)\n  end\n\n  defp wait_for(expected_events) do\n    Enum.reduce_while(1..1000, 0, fn x, acc ->\n      events =\n        \"/status.get\" |> WatcherHelper.success?() |> Map.get(\"byzantine_events\") |> Enum.map(&Map.take(&1, [\"event\"]))\n\n      case events do\n        ^expected_events ->\n          {:halt, :ok}\n\n        _ ->\n          Process.sleep(10)\n\n          {:cont, acc + x}\n      end\n    end)\n  end\n\n  defp wait_for_empty_in_flight_exits() do\n    Enum.reduce_while(1..1000, 0, fn x, acc ->\n      ife = \"/status.get\" |> WatcherHelper.success?() |> Map.get(\"in_flight_exits\")\n\n      case ife do\n        [] ->\n          {:halt, :ok}\n\n        _ ->\n          Process.sleep(10)\n\n          {:cont, acc + x}\n      end\n    end)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/integration/invalid_exit_1_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.Integration.InvalidExit1Test do\n  use ExUnitFixtures\n  use ExUnit.Case, async: false\n  use OMG.Watcher.Fixtures\n  use Plug.Test\n  use OMG.Watcher.Integration.Fixtures\n\n  alias OMG.Watcher.Event\n  alias OMG.Watcher.Integration.TestHelper, as: IntegrationTest\n  alias OMG.Watcher.TestHelper\n  alias OMG.Watcher.Utxo\n  alias Support.DevHelper\n  alias Support.RootChainHelper\n  alias Support.WatcherHelper\n  require Utxo\n\n  @moduletag :mix_based_child_chain\n  @moduletag timeout: 180_000\n\n  @timeout 40_000\n  @eth <<0::160>>\n\n  @tag fixtures: [:in_beam_watcher, :stable_alice, :token, :stable_alice_deposits]\n  test \"invalid_exit without a challenge raises unchallenged_exit after sla_margin had passed, can be challenged\",\n       %{stable_alice: alice, stable_alice_deposits: {deposit_blknum, _}} do\n    Process.sleep(11_000)\n\n    %{\"blknum\" => first_tx_blknum} =\n      [{deposit_blknum, 0, 0, alice}] |> TestHelper.create_encoded(@eth, [{alice, 9}]) |> WatcherHelper.submit()\n\n    %{\"blknum\" => second_tx_blknum} =\n      [{first_tx_blknum, 0, 0, alice}] |> TestHelper.create_encoded(@eth, [{alice, 8}]) |> WatcherHelper.submit()\n\n    Process.sleep(11_000)\n    IntegrationTest.wait_for_block_fetch(second_tx_blknum, @timeout)\n\n    %{\"txbytes\" => txbytes, \"proof\" => proof, \"utxo_pos\" => tx_utxo_pos} =\n      WatcherHelper.get_exit_data(first_tx_blknum, 0, 0)\n\n    {:ok, %{\"status\" => \"0x1\", \"blockNumber\" => eth_height}} =\n      tx_utxo_pos\n      |> RootChainHelper.start_exit(txbytes, proof, alice.addr)\n      |> DevHelper.transact_sync!()\n\n    IntegrationTest.wait_for_byzantine_events([%Event.InvalidExit{}.name], @timeout)\n\n    exit_processor_sla_margin = Application.fetch_env!(:omg_watcher, :exit_processor_sla_margin)\n    DevHelper.wait_for_root_chain_block(eth_height + exit_processor_sla_margin, @timeout)\n    IntegrationTest.wait_for_byzantine_events([%Event.InvalidExit{}.name, %Event.UnchallengedExit{}.name], @timeout)\n\n    # after the notification has been received, a challenged is composed and sent, regardless of it being late\n    challenge = WatcherHelper.get_exit_challenge(first_tx_blknum, 0, 0)\n\n    assert {:ok, %{\"status\" => \"0x1\"}} =\n             RootChainHelper.challenge_exit(\n               challenge[\"exit_id\"],\n               challenge[\"exiting_tx\"],\n               challenge[\"txbytes\"],\n               challenge[\"input_index\"],\n               challenge[\"sig\"],\n               alice.addr\n             )\n             |> DevHelper.transact_sync!()\n\n    IntegrationTest.wait_for_byzantine_events([], @timeout)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/integration/invalid_exit_2_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.Integration.InvalidExit2Test do\n  use ExUnitFixtures\n  use ExUnit.Case, async: false\n  use OMG.Watcher.Fixtures\n  use Plug.Test\n  use OMG.Watcher.Integration.Fixtures\n\n  alias OMG.Watcher.Event\n  alias OMG.Watcher.Integration.TestHelper, as: IntegrationTest\n  alias OMG.Watcher.TestHelper\n  alias OMG.Watcher.Utxo\n  alias Support.DevHelper\n  alias Support.RootChainHelper\n  alias Support.WatcherHelper\n  require Utxo\n\n  @moduletag :mix_based_child_chain\n  @moduletag timeout: 240_000\n\n  @timeout 40_000\n  @eth <<0::160>>\n\n  @tag fixtures: [:in_beam_watcher, :stable_alice, :token, :stable_alice_deposits]\n  test \"exit which is using already spent utxo from transaction and deposit causes to emit invalid_exit event\", %{\n    stable_alice: alice,\n    stable_alice_deposits: {deposit_blknum, _}\n  } do\n    Process.sleep(12_000)\n\n    %{\"txbytes\" => deposit_txbytes, \"proof\" => deposit_proof, \"utxo_pos\" => deposit_utxo_pos} =\n      WatcherHelper.get_exit_data(deposit_blknum, 0, 0)\n\n    %{\"blknum\" => first_tx_blknum} =\n      [{deposit_blknum, 0, 0, alice}] |> TestHelper.create_encoded(@eth, [{alice, 9}]) |> WatcherHelper.submit()\n\n    Process.sleep(30_000)\n\n    %{\"blknum\" => second_tx_blknum} =\n      [{first_tx_blknum, 0, 0, alice}] |> TestHelper.create_encoded(@eth, [{alice, 8}]) |> WatcherHelper.submit()\n\n    IntegrationTest.wait_for_block_fetch(second_tx_blknum, @timeout)\n    Process.sleep(30_000)\n\n    exit_data = WatcherHelper.get_exit_data(first_tx_blknum, 0, 0)\n    %{\"txbytes\" => txbytes, \"proof\" => proof, \"utxo_pos\" => tx_utxo_pos} = exit_data\n\n    {:ok, %{\"status\" => \"0x1\"}} =\n      tx_utxo_pos\n      |> RootChainHelper.start_exit(txbytes, proof, alice.addr)\n      |> DevHelper.transact_sync!()\n\n    {:ok, %{\"status\" => \"0x1\"}} =\n      deposit_utxo_pos\n      |> RootChainHelper.start_exit(deposit_txbytes, deposit_proof, alice.addr)\n      |> DevHelper.transact_sync!()\n\n    IntegrationTest.wait_for_byzantine_events([%Event.InvalidExit{}.name, %Event.InvalidExit{}.name], @timeout)\n\n    # after the notification has been received, a challenged is composed and sent\n    challenge = WatcherHelper.get_exit_challenge(first_tx_blknum, 0, 0)\n\n    assert {:ok, %{\"status\" => \"0x1\"}} =\n             challenge[\"exit_id\"]\n             |> RootChainHelper.challenge_exit(\n               challenge[\"exiting_tx\"],\n               challenge[\"txbytes\"],\n               challenge[\"input_index\"],\n               challenge[\"sig\"],\n               alice.addr\n             )\n             |> DevHelper.transact_sync!()\n\n    # challenge standard exits from deposits\n    challenge_exit_deposit = WatcherHelper.get_exit_challenge(deposit_blknum, 0, 0)\n\n    assert {:ok, %{\"status\" => \"0x1\"}} =\n             challenge_exit_deposit[\"exit_id\"]\n             |> RootChainHelper.challenge_exit(\n               challenge_exit_deposit[\"exiting_tx\"],\n               challenge_exit_deposit[\"txbytes\"],\n               challenge_exit_deposit[\"input_index\"],\n               challenge_exit_deposit[\"sig\"],\n               alice.addr\n             )\n             |> DevHelper.transact_sync!()\n\n    IntegrationTest.wait_for_byzantine_events([], @timeout)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/integration/monitor_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.MonitorTest do\n  @moduledoc false\n\n  import ExUnit.CaptureLog, only: [capture_log: 1]\n\n  alias __MODULE__.ChildProcess\n  alias OMG.Status.Alert.Alarm\n  alias OMG.Watcher.Monitor\n\n  use ExUnit.Case, async: true\n\n  setup_all do\n    {:ok, apps} = Application.ensure_all_started(:omg_status)\n\n    on_exit(fn ->\n      apps\n      |> Enum.reverse()\n      |> Enum.each(&Application.stop/1)\n    end)\n\n    :ok\n  end\n\n  setup do\n    on_exit(fn ->\n      case Process.whereis(Monitor) do\n        nil ->\n          :ok\n\n        pid ->\n          Process.exit(pid, :kill)\n      end\n    end)\n\n    :ok\n  end\n\n  test \"that a child process gets restarted after alarm is cleared\" do\n    child = ChildProcess.prepare_child()\n    {:ok, monitor_pid} = start_and_attach_a_child([Alarm, child])\n    app_alarm = Alarm.ethereum_connection_error(__MODULE__)\n\n    # the monitor is now started, we raise an alarm and kill it's child\n    :ok = :alarm_handler.set_alarm(app_alarm)\n    _ = Process.unlink(monitor_pid)\n    {:links, [child_pid]} = Process.info(monitor_pid, :links)\n    :erlang.trace(monitor_pid, true, [:receive])\n    # the child is now killed\n    capture_log(fn ->\n      true = Process.exit(Process.whereis(ChildProcess), :kill)\n    end)\n\n    # we prove that we're linked to the child process and that when it gets killed\n    # we get the trap exit message\n    assert_receive {:trace, ^monitor_pid, :receive, {:EXIT, ^child_pid, :killed}}, 5_000\n    {:links, links} = Process.info(monitor_pid, :links)\n    assert Enum.empty?(links) == true\n    # now we can clear the alarm and let the monitor restart the child process\n    # and trace that the child process gets started\n    capture_log(fn ->\n      :ok = :alarm_handler.clear_alarm(app_alarm)\n    end)\n\n    assert_receive {:trace, ^monitor_pid, :receive, {:\"$gen_cast\", :start_child}}\n    :erlang.trace(monitor_pid, false, [:receive])\n    # we now assert that our child was re-attached to the monitor\n    Process.sleep(200)\n    {:links, children} = Process.info(monitor_pid, :links)\n    assert Enum.count(children) == 1\n  end\n\n  test \"that a child process does not get restarted if an alarm is cleared but it was not down\" do\n    child = ChildProcess.prepare_child()\n    {:ok, monitor_pid} = start_and_attach_a_child([Alarm, child])\n    app_alarm = Alarm.ethereum_connection_error(__MODULE__)\n    :ok = :alarm_handler.set_alarm(app_alarm)\n    :erlang.trace(monitor_pid, true, [:receive])\n    {:links, links} = Process.info(monitor_pid, :links)\n    # now we clear the alarm and let the monitor restart the child processes\n    # in our case the child is alive so init should NOT be called\n    capture_log(fn ->\n      :ok = :alarm_handler.clear_alarm(app_alarm)\n    end)\n\n    assert_receive {:trace, ^monitor_pid, :receive, {:\"$gen_cast\", :start_child}}, 1500\n    # at this point we're just verifying that we didn't restart or start\n    # another child\n    assert Process.info(monitor_pid, :links) == {:links, links}\n  end\n\n  defp start_and_attach_a_child(opts) do\n    case Monitor.start_link(opts) do\n      {:ok, monitor_pid} ->\n        {:ok, monitor_pid}\n\n      {:error, {{:badmatch, {:error, {:already_started, _}}}, _}} ->\n        Process.sleep(500)\n        start_and_attach_a_child(opts)\n    end\n  end\n\n  defmodule ChildProcess do\n    @moduledoc \"\"\"\n    Mocking a child process to Monitor\n    \"\"\"\n    use GenServer\n\n    @spec prepare_child() :: %{id: atom(), start: tuple()}\n    def prepare_child() do\n      %{id: __MODULE__, start: {__MODULE__, :start_link, [[]]}}\n    end\n\n    def start_link(_), do: GenServer.start_link(__MODULE__, [], name: __MODULE__)\n\n    def init(_), do: {:ok, %{}}\n\n    def terminate(_reason, _) do\n      :ok\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/integration/root_chain_coordinator_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.RootChainCoordinatorTest do\n  @moduledoc \"\"\"\n  Smoke tests the imperative shells of `OMG.Watcher.EthereumEventListener` and `OMG.Watcher.RootChainCoordinator` working together\n  \"\"\"\n  use ExUnitFixtures\n  use ExUnit.Case, async: false\n\n  use OMG.DB.Fixtures\n  use OMG.Eth.Fixtures\n\n  @moduletag :integration\n  @moduletag :common\n  setup do\n    {:ok, bus_apps} = Application.ensure_all_started(:omg_bus)\n    db_path = Briefly.create!(directory: true)\n    :ok = Application.put_env(:omg_db, :path, db_path, persistent: true)\n    :ok = OMG.DB.init()\n    {:ok, eth_apps} = Application.ensure_all_started(:omg_eth)\n    {:ok, status_apps} = Application.ensure_all_started(:omg_status)\n    apps = bus_apps ++ eth_apps ++ status_apps\n\n    on_exit(fn ->\n      apps |> Enum.reverse() |> Enum.each(&Application.stop/1)\n    end)\n\n    :ok\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/integration/test_server_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.Integration.TestServerTest do\n  use ExUnitFixtures\n  use ExUnit.Case, async: false\n\n  alias OMG.Utils.HttpRPC.Encoding\n  alias OMG.Watcher.HttpRPC.Client\n  alias OMG.Watcher.Integration.TestServer\n\n  @expected_block_hash <<0::256>>\n\n  describe \"/block.get -\" do\n    @response TestServer.make_response(%{\n                blknum: 123_000,\n                hash: Encoding.to_hex(@expected_block_hash),\n                transactions: []\n              })\n\n    @tag fixtures: [:test_server]\n    test \"successful response is parsed to expected map\", %{test_server: context} do\n      TestServer.with_route(context, \"/block.get\", @response)\n\n      assert {:ok,\n              %{\n                transactions: [],\n                number: 123_000,\n                hash: @expected_block_hash\n              }} == Client.get_block(@expected_block_hash, context.fake_addr)\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/merge_transaction_validator_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.MergeTransactionValidatorTest do\n  @moduledoc false\n\n  use ExUnitFixtures\n  use ExUnit.Case, async: true\n\n  import OMG.Watcher.TestHelper\n\n  alias OMG.Watcher.MergeTransactionValidator\n  alias OMG.Watcher.State.Transaction\n\n  @eth <<0::160>>\n  @not_eth <<1::size(160)>>\n\n  describe \"is_merge_transaction?/1\" do\n    @tag fixtures: [:alice]\n    test \"returns true when the transaction is a payment, has less outputs than inputs, has single currency, and has same account\",\n         %{alice: alice} do\n      transaction = create_recovered([{1, 0, 0, alice}, {2, 0, 0, alice}], @eth, [{alice, 10}])\n      assert MergeTransactionValidator.is_merge_transaction?(transaction)\n    end\n\n    test \"returns false when transaction is not of payment type\" do\n      refute MergeTransactionValidator.is_merge_transaction?(%Transaction.Recovered{signed_tx: %{raw_tx: \"fake\"}})\n    end\n\n    test \"returns false when transaction doesn't consist of fungible-tokens only\" do\n      refute MergeTransactionValidator.is_merge_transaction?(%Transaction.Recovered{\n               signed_tx: %Transaction.Signed{raw_tx: %Transaction.Payment{inputs: [1, 2], outputs: [%{}]}}\n             })\n    end\n\n    @tag fixtures: [:alice]\n    test \"returns false when transaction has as many outputs than inputs\", %{alice: alice} do\n      transaction = create_recovered([{1, 0, 0, alice}, {2, 0, 0, alice}], @eth, [{alice, 5}, {alice, 5}])\n      refute MergeTransactionValidator.is_merge_transaction?(transaction)\n    end\n\n    @tag fixtures: [:alice]\n    test \"returns false when transaction has more outputs than inputs\", %{alice: alice} do\n      transaction = create_recovered([{1, 0, 0, alice}, {2, 0, 0, alice}], @eth, [{alice, 3}, {alice, 3}, {alice, 4}])\n      refute MergeTransactionValidator.is_merge_transaction?(transaction)\n    end\n\n    @tag fixtures: [:alice]\n    test \"returns false when not a single currency\", %{alice: alice} do\n      transaction =\n        create_recovered(\n          [{1, 0, 0, alice}, {1, 0, 1, alice}, {2, 0, 0, alice}, {2, 1, 0, alice}],\n          [{alice, @eth, 10}, {alice, @not_eth, 10}]\n        )\n\n      refute MergeTransactionValidator.is_merge_transaction?(transaction)\n    end\n\n    @tag fixtures: [:alice, :bob]\n    test \"returns false when two different accounts in outputs\", %{alice: alice, bob: bob} do\n      transaction =\n        create_recovered([{1, 0, 0, alice}, {1, 0, 1, alice}, {2, 0, 0, alice}], @eth, [{bob, 10}, {alice, 10}])\n\n      refute MergeTransactionValidator.is_merge_transaction?(transaction)\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/merkle_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.MerkleTest do\n  @moduledoc false\n\n  use ExUnitFixtures\n  use ExUnit.Case, async: true\n\n  alias OMG.Watcher.Merkle\n\n  describe \"create_tx_proof/2\" do\n    test \"creates merkle proofs based on list of values and index\" do\n      # We don't want to be testing the underlying library here,\n      # we just want to ensure that our code calling it always\n      # returns the same result\n      values = [\"abc\", \"def\", \"ghi\"]\n\n      proof_1 = Merkle.create_tx_proof(values, 1)\n      proof_2 = Merkle.create_tx_proof(values, 2)\n\n      assert proof_1 != proof_2\n      assert \"c168262281c10d4285a4aecb\" <> _ = Base.encode16(proof_1, case: :lower)\n    end\n  end\n\n  describe \"hash/1\" do\n    test \"returns the merkle tree root for a list of transaction\" do\n      values = [\"abc\", \"def\", \"ghi\"]\n\n      proof =\n        values\n        |> Merkle.hash()\n        |> Base.encode16(case: :lower)\n\n      assert proof == \"2060aa204dd8b8cc723a8abf2ce20e982d0acbd4f95bdbfaca435495b5ad5dc6\"\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/output_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\ndefmodule OMG.OutputTest do\n  @moduledoc false\n\n  use ExUnit.Case, async: true\n  alias OMG.Output\n  doctest OMG.Output\n\n  describe \"reconstruct/1\" do\n    test \"returns an error if the output guard is invalid\" do\n      rlp_data = [\n        <<1>>,\n        [\n          <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>>,\n          <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>>,\n          <<1>>\n        ]\n      ]\n\n      assert {:error, :output_guard_cant_be_zero} = Output.reconstruct(rlp_data)\n    end\n\n    test \"returns an error if the output is malformed\" do\n      rlp_data = []\n      assert {:error, :malformed_outputs} = Output.reconstruct(rlp_data)\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/raw_data_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.RawDataTest do\n  use ExUnit.Case, async: true\n  # doctest OMG.Watcher.RawData\n  alias OMG.Watcher.RawData\n\n  describe \"parse_amount/1\" do\n    test \"rejects zero passed as amount\" do\n      [zero] = [0] |> ExRLP.encode() |> ExRLP.decode()\n\n      assert {:error, :amount_cant_be_zero} == RawData.parse_amount(zero)\n    end\n\n    test \"rejects integer greater than 32-bytes\" do\n      large = 2.0 |> :math.pow(8 * 32) |> Kernel.trunc()\n      [too_large] = [large] |> ExRLP.encode() |> ExRLP.decode()\n\n      assert {:error, :encoded_uint_too_big} == RawData.parse_amount(too_large)\n    end\n\n    test \"rejects leading zeros encoded numbers\" do\n      [one] = [1] |> ExRLP.encode() |> ExRLP.decode()\n\n      assert {:error, :leading_zeros_in_encoded_uint} == RawData.parse_amount(<<0>> <> one)\n    end\n\n    test \"accepts 32-bytes positive integers\" do\n      large = 2.0 |> :math.pow(8 * 32) |> Kernel.trunc()\n      big_just_enough = large - 1\n\n      [one, big] = [1, big_just_enough] |> ExRLP.encode() |> ExRLP.decode()\n\n      assert {:ok, 1} == RawData.parse_amount(one)\n      assert {:ok, big_just_enough} == RawData.parse_amount(big)\n    end\n  end\n\n  describe \"parse_address/1\" do\n    test \"accepts 20-bytes binaries\" do\n      zero_addr = <<0::160>>\n      non_zero_addr = <<2::160>>\n\n      [zero, addr] = [zero_addr, non_zero_addr] |> ExRLP.encode() |> ExRLP.decode()\n\n      assert {:ok, zero_addr} == RawData.parse_address(zero)\n      assert {:ok, non_zero_addr} == RawData.parse_address(addr)\n    end\n\n    test \"rejects binaries shorter or longer than address length\" do\n      too_short_addr = <<0::152>>\n      too_long_addr = <<0::168>>\n\n      [short, long] = [too_short_addr, too_long_addr] |> ExRLP.encode() |> ExRLP.decode()\n\n      assert {:error, :malformed_address} == RawData.parse_address(short)\n      assert {:error, :malformed_address} == RawData.parse_address(long)\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/release_tasks/set_ethereum_events_check_interval_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.ReleaseTasks.SetEthereumEventsCheckIntervalTest do\n  use ExUnit.Case, async: true\n  alias OMG.Watcher.ReleaseTasks.SetEthereumEventsCheckInterval\n\n  @app :omg_watcher\n  @env_key \"ETHEREUM_EVENTS_CHECK_INTERVAL_MS\"\n  @config_key :ethereum_events_check_interval_ms\n\n  test \"that interval is set when the env var is present\" do\n    :ok = System.put_env(@env_key, \"1234\")\n    config = SetEthereumEventsCheckInterval.load([], [])\n    ethereum_events_check_interval_ms = config |> Keyword.fetch!(@app) |> Keyword.fetch!(@config_key)\n    assert ethereum_events_check_interval_ms == 1234\n    :ok = System.delete_env(@env_key)\n  end\n\n  test \"that the default config is used when the env var is not set\" do\n    old_config = Application.get_env(@app, @config_key)\n    :ok = System.delete_env(@env_key)\n    config = SetEthereumEventsCheckInterval.load([], [])\n    ethereum_events_check_interval_ms = config |> Keyword.fetch!(@app) |> Keyword.fetch!(@config_key)\n    assert ethereum_events_check_interval_ms == old_config\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/release_tasks/set_exit_processor_sla_margin_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.ReleaseTasks.SetExitProcessorSLAMarginTest do\n  use ExUnit.Case, async: true\n\n  alias OMG.Watcher.ReleaseTasks.SetExitProcessorSLAMargin\n  @app :omg_watcher\n\n  test \"if environment variables get applied in the configuration\" do\n    :ok = System.put_env(\"EXIT_PROCESSOR_SLA_MARGIN\", \"15\")\n    :ok = System.put_env(\"EXIT_PROCESSOR_SLA_MARGIN_FORCED\", \"TRUE\")\n    config = SetExitProcessorSLAMargin.load([], [])\n    exit_processor_sla_margin = config |> Keyword.fetch!(@app) |> Keyword.fetch!(:exit_processor_sla_margin)\n\n    exit_processor_sla_margin_forced =\n      config |> Keyword.fetch!(@app) |> Keyword.fetch!(:exit_processor_sla_margin_forced)\n\n    assert exit_processor_sla_margin == 15\n    assert exit_processor_sla_margin_forced == true\n  end\n\n  test \"if default configuration is used when there's no environment variables\" do\n    :ok = System.delete_env(\"EXIT_PROCESSOR_SLA_MARGIN\")\n    :ok = System.delete_env(\"EXIT_PROCESSOR_SLA_MARGIN_FORCED\")\n    config = SetExitProcessorSLAMargin.load([], [])\n    exit_processor_sla_margin = config |> Keyword.fetch!(@app) |> Keyword.fetch!(:exit_processor_sla_margin)\n\n    exit_processor_sla_margin_forced =\n      config |> Keyword.fetch!(@app) |> Keyword.fetch!(:exit_processor_sla_margin_forced)\n\n    exit_processor_sla_margin_updated = Application.get_env(@app, :exit_processor_sla_margin)\n    exit_processor_sla_margin_forced_updated = Application.get_env(@app, :exit_processor_sla_margin_forced)\n    assert exit_processor_sla_margin == exit_processor_sla_margin_updated\n    assert exit_processor_sla_margin_forced == exit_processor_sla_margin_forced_updated\n  end\n\n  test \"if exit is thrown when faulty margin configuration is used\" do\n    :ok = System.put_env(\"EXIT_PROCESSOR_SLA_MARGIN\", \"15a\")\n    catch_exit(SetExitProcessorSLAMargin.load([], []))\n    :ok = System.delete_env(\"EXIT_PROCESSOR_SLA_MARGIN\")\n  end\n\n  test \"if exit is thrown when faulty margin force configuration is used\" do\n    :ok = System.put_env(\"EXIT_PROCESSOR_SLA_MARGIN_FORCED\", \"15\")\n    catch_exit(SetExitProcessorSLAMargin.load([], []))\n    :ok = System.delete_env(\"EXIT_PROCESSOR_SLA_MARGIN_FORCED\")\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/release_tasks/set_tracer_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.ReleaseTasks.SetTracerTest do\n  use ExUnit.Case, async: true\n  import ExUnit.CaptureLog, only: [capture_log: 1]\n\n  alias OMG.Watcher.ReleaseTasks.SetTracer\n  alias OMG.Watcher.Tracer\n  @app :omg_watcher\n\n  setup do\n    {:ok, pid} = __MODULE__.System.start_link([])\n    nil = Process.put(__MODULE__.System, pid)\n    :ok\n  end\n\n  test \"if environment variables get applied in the configuration\" do\n    :ok = __MODULE__.System.put_env(\"DD_DISABLED\", \"TRUE\")\n    :ok = __MODULE__.System.put_env(\"APP_ENV\", \"YOLO\")\n    :ok = __MODULE__.System.put_env(\"HOSTNAME\", \"this is my tracer test 3\")\n\n    assert capture_log(fn ->\n             config = SetTracer.load([], system_adapter: __MODULE__.System)\n             disabled = config |> Keyword.fetch!(@app) |> Keyword.fetch!(Tracer) |> Keyword.fetch!(:disabled?)\n             env = config |> Keyword.fetch!(@app) |> Keyword.fetch!(Tracer) |> Keyword.fetch!(:env)\n\n             assert disabled == true\n             # if it's disabled, env doesn't matter, so we set it to an empty string\n             assert env == \"\"\n           end)\n  end\n\n  test \"if default configuration is used when there's no environment variables\" do\n    :ok = __MODULE__.System.put_env(\"HOSTNAME\", \"this is my tracer test 3\")\n\n    assert capture_log(fn ->\n             config = SetTracer.load([], system_adapter: __MODULE__.System)\n             configuration = @app |> Application.get_env(Tracer) |> Keyword.put(:env, \"\") |> Enum.sort()\n             tracer_config = config |> Keyword.get(@app) |> Keyword.get(Tracer) |> Enum.sort()\n             assert configuration == tracer_config\n           end)\n  end\n\n  test \"if exit is thrown when faulty configuration is used\" do\n    :ok = __MODULE__.System.put_env(\"DD_DISABLED\", \"TRUEeee\")\n    catch_exit(SetTracer.load([], system_adapter: __MODULE__.System))\n  end\n\n  defmodule System do\n    def start_link(args), do: GenServer.start_link(__MODULE__, args, [])\n    def get_env(key), do: __MODULE__ |> Process.get() |> GenServer.call({:get_env, key})\n    def put_env(key, value), do: __MODULE__ |> Process.get() |> GenServer.call({:put_env, key, value})\n    def init(_), do: {:ok, %{}}\n\n    def handle_call({:get_env, key}, _, state) do\n      {:reply, state[key], state}\n    end\n\n    def handle_call({:put_env, key, value}, _, state) do\n      {:reply, :ok, Map.put(state, key, value)}\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/root_chain_coordinator/core_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\ndefmodule OMG.Watcher.RootChainCoordinator.CoreTest do\n  use ExUnitFixtures\n  use ExUnit.Case, async: true\n\n  alias OMG.Watcher.RootChainCoordinator.Core\n\n  deffixture initial_state() do\n    Core.init(%{:depositor => [], :exiter => [waits_for: :depositor]}, 10)\n  end\n\n  @tag fixtures: [:initial_state]\n  test \"does not synchronize service that is not allowed\", %{initial_state: state} do\n    {:error, :service_not_allowed} = Core.check_in(state, :c.pid(0, 1, 0), 10, :unallowed_service)\n  end\n\n  @tag fixtures: [:initial_state]\n  test \"synchronizes services\", %{initial_state: state} do\n    depositor_pid = :c.pid(0, 1, 0)\n    exiter_pid = :c.pid(0, 2, 0)\n\n    assert {:ok, state} = Core.check_in(state, exiter_pid, 1, :exiter)\n    assert :nosync = Core.get_synced_info(state, depositor_pid)\n    assert :nosync = Core.get_synced_info(state, exiter_pid)\n\n    assert {:ok, state} = Core.check_in(state, depositor_pid, 2, :depositor)\n    assert %{sync_height: 10} = Core.get_synced_info(state, depositor_pid)\n    assert %{sync_height: 2} = Core.get_synced_info(state, exiter_pid)\n\n    assert {:ok, state} = Core.check_in(state, exiter_pid, 2, :exiter)\n    assert %{sync_height: 10} = Core.get_synced_info(state, depositor_pid)\n    assert %{sync_height: 2} = Core.get_synced_info(state, exiter_pid)\n\n    assert {:ok, state} = Core.check_in(state, depositor_pid, 3, :depositor)\n    assert %{sync_height: 10} = Core.get_synced_info(state, depositor_pid)\n    assert %{sync_height: 3} = Core.get_synced_info(state, exiter_pid)\n  end\n\n  @tag fixtures: [:initial_state]\n  test \"deregisters and registers a service\", %{initial_state: state} do\n    depositor_pid = :c.pid(0, 1, 0)\n    exiter_pid = :c.pid(0, 2, 0)\n\n    assert {:ok, state} = Core.check_in(state, exiter_pid, 1, :exiter)\n    assert {:ok, state} = Core.check_in(state, depositor_pid, 1, :depositor)\n    assert %{sync_height: 10} = Core.get_synced_info(state, depositor_pid)\n    assert %{sync_height: 1} = Core.get_synced_info(state, exiter_pid)\n\n    depositor_pid2 = :c.pid(0, 3, 0)\n    assert {:ok, state} = Core.check_in(state, depositor_pid2, 2, :depositor)\n    assert %{sync_height: 10} = Core.get_synced_info(state, depositor_pid2)\n    assert %{sync_height: 2} = Core.get_synced_info(state, exiter_pid)\n  end\n\n  @tag fixtures: [:initial_state]\n  test \"updates root chain height\", %{initial_state: state} do\n    depositor_pid = :c.pid(0, 1, 0)\n    exiter_pid = :c.pid(0, 2, 0)\n\n    assert {:ok, state} = Core.check_in(state, exiter_pid, 10, :exiter)\n    assert {:ok, state} = Core.check_in(state, depositor_pid, 10, :depositor)\n    assert %{sync_height: 10} = Core.get_synced_info(state, depositor_pid)\n    assert %{sync_height: 10} = Core.get_synced_info(state, exiter_pid)\n\n    assert {:ok, state} = Core.update_root_chain_height(state, 11)\n    assert %{sync_height: 11} = Core.get_synced_info(state, depositor_pid)\n    assert %{sync_height: 10} = Core.get_synced_info(state, exiter_pid)\n\n    assert {:ok, state} = Core.update_root_chain_height(state, 14)\n    assert %{sync_height: 14} = Core.get_synced_info(state, depositor_pid)\n    assert %{sync_height: 10} = Core.get_synced_info(state, exiter_pid)\n  end\n\n  @tag fixtures: [:initial_state]\n  test \"reports synced heights\", %{initial_state: state} do\n    exiter_pid = :c.pid(0, 2, 0)\n\n    assert %{root_chain_height: 10} == Core.get_ethereum_heights(state)\n    assert {:ok, state} = Core.check_in(state, exiter_pid, 10, :exiter)\n    assert %{root_chain_height: 10, exiter: 10} == Core.get_ethereum_heights(state)\n    assert {:ok, state} = Core.update_root_chain_height(state, 11)\n    assert %{root_chain_height: 11, exiter: 10} == Core.get_ethereum_heights(state)\n  end\n\n  @tag fixtures: [:initial_state]\n  test \"prevents huge queries to Ethereum client\", %{initial_state: state} do\n    depositor_pid = :c.pid(0, 1, 0)\n    exiter_pid = :c.pid(0, 2, 0)\n\n    assert {:ok, state} = Core.check_in(state, exiter_pid, 10, :exiter)\n    assert {:ok, state} = Core.check_in(state, depositor_pid, 10, :depositor)\n    assert {:ok, state} = Core.update_root_chain_height(state, 11_000_000)\n    assert %{sync_height: new_sync_height} = Core.get_synced_info(state, depositor_pid)\n    assert new_sync_height < 100_000\n  end\n\n  @tag fixtures: [:initial_state]\n  test \"root chain back off is ignored\", %{initial_state: state} do\n    depositor_pid = :c.pid(0, 1, 0)\n    exiter_pid = :c.pid(0, 2, 0)\n\n    assert {:ok, state} = Core.check_in(state, exiter_pid, 10, :exiter)\n    assert {:ok, state} = Core.check_in(state, depositor_pid, 10, :depositor)\n    assert %{sync_height: 10} = Core.get_synced_info(state, depositor_pid)\n    assert %{sync_height: 10} = Core.get_synced_info(state, exiter_pid)\n\n    assert {:ok, state} = Core.update_root_chain_height(state, 9)\n    assert %{sync_height: 10} = Core.get_synced_info(state, depositor_pid)\n    assert %{sync_height: 10} = Core.get_synced_info(state, exiter_pid)\n\n    assert {:ok, state} = Core.update_root_chain_height(state, 11)\n    assert %{sync_height: 11} = Core.get_synced_info(state, depositor_pid)\n    assert %{sync_height: 10} = Core.get_synced_info(state, exiter_pid)\n  end\n\n  @pid %{\n    depositor: :c.pid(0, 1, 0),\n    exiter: :c.pid(0, 2, 0),\n    depositor_finality: :c.pid(0, 3, 0),\n    exiter_finality: :c.pid(0, 4, 0),\n    getter: :c.pid(0, 5, 0),\n    finalizer: :c.pid(0, 6, 0)\n  }\n\n  deffixture bigger_state() do\n    state =\n      Core.init(\n        %{\n          :depositor => [],\n          :exiter => [waits_for: :depositor],\n          :depositor_finality => [finality_margin: 2],\n          :exiter_finality => [waits_for: :depositor, finality_margin: 2],\n          :getter => [waits_for: [depositor_finality: :no_margin]],\n          :finalizer => [waits_for: [:getter, :depositor]]\n        },\n        10\n      )\n\n    {:ok, state} = Core.check_in(state, @pid[:depositor], 1, :depositor)\n    {:ok, state} = Core.check_in(state, @pid[:exiter], 1, :exiter)\n    {:ok, state} = Core.check_in(state, @pid[:depositor_finality], 1, :depositor_finality)\n    {:ok, state} = Core.check_in(state, @pid[:exiter_finality], 1, :exiter_finality)\n    {:ok, state} = Core.check_in(state, @pid[:getter], 1, :getter)\n    {:ok, state} = Core.check_in(state, @pid[:finalizer], 1, :finalizer)\n    state\n  end\n\n  @tag fixtures: [:bigger_state]\n  test \"waiting service will wait and progress accordingly\",\n       %{bigger_state: state} do\n    assert %{sync_height: 1} = Core.get_synced_info(state, @pid[:exiter])\n    {:ok, state} = Core.check_in(state, @pid[:depositor], 2, :depositor)\n    assert %{sync_height: 2} = Core.get_synced_info(state, @pid[:exiter])\n    {:ok, state} = Core.check_in(state, @pid[:depositor], 5, :depositor)\n    assert %{sync_height: 5} = Core.get_synced_info(state, @pid[:exiter])\n  end\n\n  @tag fixtures: [:bigger_state]\n  test \"waiting for multiple\",\n       %{bigger_state: state} do\n    assert %{sync_height: 1} = Core.get_synced_info(state, @pid[:finalizer])\n    {:ok, state} = Core.check_in(state, @pid[:depositor], 2, :depositor)\n    assert %{sync_height: 1} = Core.get_synced_info(state, @pid[:finalizer])\n    {:ok, state} = Core.check_in(state, @pid[:getter], 2, :getter)\n    assert %{sync_height: 2} = Core.get_synced_info(state, @pid[:finalizer])\n    {:ok, state} = Core.check_in(state, @pid[:depositor], 5, :depositor)\n    {:ok, state} = Core.check_in(state, @pid[:getter], 5, :getter)\n    assert %{sync_height: 5} = Core.get_synced_info(state, @pid[:finalizer])\n  end\n\n  @tag fixtures: [:bigger_state]\n  test \"waiting when margin of the awaited process should be skipped ahead\",\n       %{bigger_state: state} do\n    assert %{sync_height: 3} = Core.get_synced_info(state, @pid[:getter])\n    {:ok, state} = Core.check_in(state, @pid[:depositor_finality], 5, :depositor_finality)\n    assert %{sync_height: 7} = Core.get_synced_info(state, @pid[:getter])\n    {:ok, state} = Core.check_in(state, @pid[:depositor_finality], 8, :depositor_finality)\n    assert %{sync_height: 10} = Core.get_synced_info(state, @pid[:getter])\n\n    assert {:ok, state} = Core.update_root_chain_height(state, 11)\n\n    assert %{sync_height: 10} = Core.get_synced_info(state, @pid[:getter])\n    {:ok, state} = Core.check_in(state, @pid[:depositor_finality], 9, :depositor_finality)\n    assert %{sync_height: 11} = Core.get_synced_info(state, @pid[:getter])\n\n    # sanity check - will not accidently spill over root chain height (but depositor wouldn't likely check in at 11)\n    {:ok, state} = Core.check_in(state, @pid[:depositor_finality], 11, :depositor_finality)\n    assert %{sync_height: 11} = Core.get_synced_info(state, @pid[:getter])\n  end\n\n  @tag fixtures: [:bigger_state]\n  test \"waiting only for the finality margin\",\n       %{bigger_state: state} do\n    assert %{sync_height: 8} = Core.get_synced_info(state, @pid[:depositor_finality])\n    {:ok, state} = Core.check_in(state, @pid[:depositor_finality], 5, :depositor_finality)\n    assert %{sync_height: 8} = Core.get_synced_info(state, @pid[:depositor_finality])\n    assert {:ok, state} = Core.update_root_chain_height(state, 11)\n    assert %{sync_height: 9} = Core.get_synced_info(state, @pid[:depositor_finality])\n  end\n\n  @tag fixtures: [:bigger_state]\n  test \"waiting only for the finality margin and some service\",\n       %{bigger_state: state} do\n    assert %{sync_height: 1} = Core.get_synced_info(state, @pid[:exiter_finality])\n    {:ok, state} = Core.check_in(state, @pid[:depositor], 5, :depositor)\n    assert %{sync_height: 5} = Core.get_synced_info(state, @pid[:exiter_finality])\n    assert {:ok, state} = Core.update_root_chain_height(state, 11)\n    assert %{sync_height: 5} = Core.get_synced_info(state, @pid[:exiter_finality])\n    {:ok, state} = Core.check_in(state, @pid[:depositor], 9, :depositor)\n    assert %{sync_height: 9} = Core.get_synced_info(state, @pid[:exiter_finality])\n\n    # is reorg safe - root chain height going backwards is ignored\n    assert {:ok, state} = Core.update_root_chain_height(state, 10)\n    assert %{sync_height: 9} = Core.get_synced_info(state, @pid[:exiter_finality])\n  end\n\n  test \"behaves well close to zero\",\n       %{} do\n    state = Core.init(%{:depositor => [finality_margin: 2], :exiter => [waits_for: :depositor, finality_margin: 2]}, 0)\n\n    {:ok, state} = Core.check_in(state, @pid[:depositor], 0, :depositor)\n    {:ok, state} = Core.check_in(state, @pid[:exiter], 0, :exiter)\n    assert %{sync_height: 0} = Core.get_synced_info(state, @pid[:depositor])\n    assert %{sync_height: 0} = Core.get_synced_info(state, @pid[:exiter])\n    assert {:ok, state} = Core.update_root_chain_height(state, 1)\n    assert %{sync_height: 0} = Core.get_synced_info(state, @pid[:depositor])\n    assert %{sync_height: 0} = Core.get_synced_info(state, @pid[:exiter])\n    assert {:ok, state} = Core.update_root_chain_height(state, 3)\n    assert %{sync_height: 1} = Core.get_synced_info(state, @pid[:depositor])\n    assert %{sync_height: 0} = Core.get_synced_info(state, @pid[:exiter])\n    {:ok, state} = Core.check_in(state, @pid[:depositor], 1, :depositor)\n    assert %{sync_height: 1} = Core.get_synced_info(state, @pid[:exiter])\n  end\n\n  @tag fixtures: [:bigger_state]\n  test \"root chain heights reported observe the finality margin, if present\",\n       %{bigger_state: state} do\n    assert %{root_chain_height: 10} = Core.get_synced_info(state, @pid[:depositor])\n    assert %{root_chain_height: 8} = Core.get_synced_info(state, @pid[:depositor_finality])\n    assert %{root_chain_height: 10} = Core.get_synced_info(state, @pid[:exiter])\n    assert %{root_chain_height: 8} = Core.get_synced_info(state, @pid[:exiter_finality])\n    assert %{root_chain_height: 10} = Core.get_synced_info(state, @pid[:getter])\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/signature_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.SignatureTest do\n  use ExUnit.Case, async: true\n  doctest OMG.Watcher.Signature\n  alias OMG.Watcher.Crypto\n  alias OMG.Watcher.Signature\n\n  describe \"recover_public/3\" do\n    test \"returns an error from an invalid hash\" do\n      {:error, :invalid_recovery_id} =\n        Signature.recover_public(\n          <<2::256>>,\n          55,\n          38_938_543_279_057_362_855_969_661_240_129_897_219_713_373_336_787_331_739_561_340_553_100_525_404_231,\n          23_772_455_091_703_794_797_226_342_343_520_955_590_158_385_983_376_086_035_257_995_824_653_222_457_926\n        )\n    end\n\n    test \"recovers from generating a signed hash 1\" do\n      data =\n        Base.decode16!(\"ec098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080018080\",\n          case: :lower\n        )\n\n      hash = Crypto.hash(data)\n      v = 27\n      r = 18_515_461_264_373_351_373_200_002_665_853_028_612_451_056_578_545_711_640_558_177_340_181_847_433_846\n      s = 46_948_507_304_638_947_509_940_763_649_030_358_759_909_902_576_025_900_602_547_168_820_602_576_006_531\n      {:ok, public_key} = Signature.recover_public(hash, v, r, s)\n\n      assert public_key ==\n               <<75, 194, 163, 18, 101, 21, 63, 7, 231, 14, 11, 171, 8, 114, 78, 107, 133, 226, 23, 248, 205, 98, 140,\n                 235, 98, 151, 66, 71, 187, 73, 51, 130, 206, 40, 202, 183, 154, 215, 17, 158, 225, 173, 62, 188, 219,\n                 152, 161, 104, 5, 33, 21, 48, 236, 198, 207, 239, 161, 184, 142, 109, 255, 153, 35, 42>>\n    end\n\n    test \"recovers from generating a signed hash 2\" do\n      {v, r, s} =\n        {37, 18_515_461_264_373_351_373_200_002_665_853_028_612_451_056_578_545_711_640_558_177_340_181_847_433_846,\n         46_948_507_304_638_947_509_940_763_649_030_358_759_909_902_576_025_900_602_547_168_820_602_576_006_531}\n\n      data =\n        Base.decode16!(\"ec098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080018080\",\n          case: :lower\n        )\n\n      hash = Crypto.hash(data)\n      {:ok, public_key} = Signature.recover_public(hash, v, r, s, 1)\n\n      assert public_key ==\n               <<75, 194, 163, 18, 101, 21, 63, 7, 231, 14, 11, 171, 8, 114, 78, 107, 133, 226, 23, 248, 205, 98, 140,\n                 235, 98, 151, 66, 71, 187, 73, 51, 130, 206, 40, 202, 183, 154, 215, 17, 158, 225, 173, 62, 188, 219,\n                 152, 161, 104, 5, 33, 21, 48, 236, 198, 207, 239, 161, 184, 142, 109, 255, 153, 35, 42>>\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/state/core_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.State.CoreTest do\n  @moduledoc \"\"\"\n  Tests functional behaviors of our high-throughput ledger being `OMG.Watcher.State.Core`. For test related to state\n  persistence of this see `OMG.Watcher.State.PersistenceTest`\n  \"\"\"\n  use ExUnitFixtures\n  use ExUnit.Case, async: true\n  import OMG.Watcher.TestHelper\n\n  require Logger\n  require OMG.Watcher.Utxo\n\n  alias OMG.Eth.Configuration\n  alias OMG.Output\n  alias OMG.Watcher.Block\n  alias OMG.Watcher.Fees\n  alias OMG.Watcher.State.Core\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.Utxo\n\n  @eth <<0::160>>\n  @not_eth <<1::size(160)>>\n  @interval Configuration.child_block_interval()\n  @blknum1 @interval\n  @blknum2 @interval * 2\n\n  @empty_block_hash <<246, 9, 190, 253, 254, 144, 102, 254, 20, 231, 67, 179, 98, 62, 174, 135, 143, 188, 70, 128, 5,\n                      96, 136, 22, 131, 44, 157, 70, 15, 42, 149, 210>>\n\n  @fee %{@eth => [1], @not_eth => [1]}\n\n  @tag fixtures: [:alice, :bob, :state_empty]\n  test \"can spend deposits\", %{alice: alice, bob: bob, state_empty: state} do\n    state\n    |> do_deposit(alice, %{amount: 10, currency: @eth, blknum: 1})\n    |> Core.exec(create_recovered([{1, 0, 0, alice}], @eth, [{bob, 6}, {alice, 3}]), @fee)\n    |> success?\n    |> Core.exec(create_recovered([{@blknum1, 0, 1, alice}], @eth, [{bob, 2}]), @fee)\n    |> success?\n  end\n\n  describe \"Lazy loaded utxo set\" do\n    @tag fixtures: [:alice, :bob, :state_alice_deposit]\n    test \"applies utxos with recent spends to check whether utxo should be fetched from db\",\n         %{alice: alice, bob: bob, state_alice_deposit: state} do\n      # make some utxos\n      state =\n        state\n        |> Core.exec(create_recovered([{1, 0, 0, alice}], @eth, [{alice, 5}, {bob, 2}, {alice, 2}]), @fee)\n        |> success?()\n        |> Core.exec(create_recovered([{1000, 0, 0, alice}], @eth, [{bob, 3}, {alice, 1}]), @fee)\n        |> success?()\n\n      deposit_pos = Utxo.position(1, 0, 0)\n      assert Core.utxo_processed?(deposit_pos, state) == true\n\n      spend_pos = Utxo.position(1000, 0, 0)\n      assert Core.utxo_processed?(spend_pos, state) == true\n\n      known_pos = [Utxo.position(1000, 0, 2), Utxo.position(1000, 1, 1)]\n      assert Enum.map(known_pos, &Core.utxo_processed?(&1, state)) == [true, true]\n\n      unknown_pos = [Utxo.position(1000, 2, 0), Utxo.position(1000, 1, 2)]\n      assert Enum.map(unknown_pos, &Core.utxo_processed?(&1, state)) == [false, false]\n    end\n\n    @tag fixtures: [:alice, :state_empty]\n    test \"transaction input is missing in state\", %{alice: alice, state_empty: state} do\n      tx = create_recovered([{1, 0, 0, alice}], @eth, [{alice, 10}])\n\n      state\n      |> Core.with_utxos(%{})\n      |> Core.exec(tx, @fee)\n      |> fail?(:utxo_not_found)\n    end\n\n    @tag fixtures: [:alice, :bob, :state_empty]\n    test \"all transaction inputs are merged from db\", %{alice: alice, bob: bob, state_empty: state} do\n      tx = create_recovered([{1000, 0, 0, alice}, {1000, 1, 0, alice}], @eth, [{bob, 7}, {alice, 2}])\n\n      db_utxos = make_utxos([{1000, 0, 0, alice, @eth, 5}, {1000, 1, 0, alice, @eth, 5}])\n\n      state\n      |> Core.with_utxos(db_utxos)\n      |> Core.exec(tx, @fee)\n      |> success?()\n    end\n\n    @tag fixtures: [:alice, :bob, :state_empty]\n    test \"transaction utxos are mixed in memory and db\", %{alice: alice, bob: bob, state_empty: state} do\n      tx = create_recovered([{1000, 0, 0, alice}, {1, 0, 0, alice}], @eth, [{bob, 7}, {alice, 2}])\n\n      db_utxos = make_utxos([{1000, 0, 0, alice, @eth, 8}])\n\n      state\n      |> do_deposit(alice, %{amount: 2, currency: @eth, blknum: 1})\n      |> Core.with_utxos(db_utxos)\n      |> Core.exec(tx, @fee)\n      |> success?()\n    end\n\n    @tag fixtures: [:alice, :bob, :state_alice_deposit]\n    test \"spending utxo that resides in memory - double spend impossible\",\n         %{alice: alice, bob: bob, state_alice_deposit: state} do\n      tx = create_recovered([{1, 0, 0, alice}], @eth, [{bob, 7}, {alice, 2}])\n\n      state\n      |> Core.exec(tx, @fee)\n      |> success?()\n      |> Core.exec(tx, @fee)\n      |> fail?(:utxo_not_found)\n    end\n\n    @tag fixtures: [:alice, :bob, :state_empty]\n    test \"extending state with same utxos does not change it\", %{alice: alice, bob: bob, state_empty: state} do\n      db_utxos = make_utxos([{1000, 0, 0, alice, @eth, 8}, {1000, 0, 1, bob, @eth, 2}])\n      state = Core.with_utxos(state, db_utxos)\n\n      state\n      |> Core.with_utxos(db_utxos)\n      |> same?(state)\n    end\n\n    @tag fixtures: [:alice, :bob, :state_empty]\n    test \"extending state partially\", %{alice: alice, bob: bob, state_empty: state} do\n      db_utxos1 = make_utxos([{1000, 0, 0, alice, @eth, 6}])\n      db_utxos2 = make_utxos([{1000, 5, 0, alice, @eth, 6}])\n\n      tx = create_recovered([{1000, 0, 0, alice}, {1000, 5, 0, alice}], @eth, [{bob, 11}])\n\n      state\n      |> Core.with_utxos(db_utxos1)\n      |> Core.exec(tx, @fee)\n      |> fail?(:utxo_not_found)\n      |> Core.with_utxos(db_utxos2)\n      |> Core.exec(tx, @fee)\n      |> success?()\n    end\n  end\n\n  describe \"Transaction amounts and fees\" do\n    @tag fixtures: [:alice, :bob, :state_empty]\n    test \"fees are not needed when given :ignore_fees\", %{alice: alice, bob: bob, state_empty: state} do\n      state\n      |> do_deposit(alice, %{amount: 10, currency: @eth, blknum: 1})\n      |> Core.exec(create_recovered([{1, 0, 0, alice}], @eth, [{bob, 5}, {alice, 5}]), :ignore_fees)\n      |> success?\n    end\n\n    @tag fixtures: [:alice, :bob, :state_empty]\n    test \"fees can be overpaid when given :ignore_fees\", %{alice: alice, bob: bob, state_empty: state} do\n      state\n      |> do_deposit(alice, %{amount: 10, currency: @eth, blknum: 1})\n      |> Core.exec(create_recovered([{1, 0, 0, alice}], @eth, [{bob, 1}, {alice, 1}]), :ignore_fees)\n      |> success?\n    end\n\n    @tag fixtures: [:alice, :bob, :state_empty]\n    test \":ignore_fees does not allow output amounts > input amounts\", %{alice: alice, bob: bob, state_empty: state} do\n      state\n      |> do_deposit(alice, %{amount: 10, currency: @eth, blknum: 1})\n      |> Core.exec(create_recovered([{1, 0, 0, alice}], @eth, [{bob, 10}, {alice, 1}]), :ignore_fees)\n      |> fail?(:amounts_do_not_add_up)\n    end\n\n    @tag fixtures: [:alice, :state_empty]\n    test \"output currencies must be included in input currencies\", %{alice: alice, state_empty: state} do\n      state1 =\n        state\n        |> do_deposit(alice, %{amount: 10, currency: @not_eth, blknum: 1})\n        |> Core.exec(create_recovered([{1, 0, 0, alice}], @not_eth, [{alice, 7}, {alice, 2}]), @fee)\n        |> success?\n\n      state1\n      |> Core.exec(create_recovered([{1000, 0, 0, alice}], @eth, [{alice, 8}]), @fee)\n      |> fail?(:amounts_do_not_add_up)\n\n      state1\n      |> Core.exec(\n        create_recovered([{1000, 0, 0, alice}], [{alice, @eth, 9}, {alice, @not_eth, 3}]),\n        @fee\n      )\n      |> fail?(:amounts_do_not_add_up)\n\n      state1\n      |> Core.exec(create_recovered([{1000, 0, 0, alice}], [{alice, @not_eth, 6}]), @fee)\n      |> success?\n    end\n\n    @tag fixtures: [:alice, :bob, :state_empty]\n    test \"amounts from multiple inputs must add up\", %{alice: alice, bob: bob, state_empty: state} do\n      state = do_deposit(state, alice, %{amount: 10, currency: @eth, blknum: 1})\n\n      # outputs exceed inputs\n      state =\n        state\n        |> Core.exec(create_recovered([{1, 0, 0, alice}], @eth, [{alice, 7}, {bob, 4}]), @fee)\n        |> fail?(:amounts_do_not_add_up)\n        |> same?(state)\n        |> Core.exec(create_recovered([{1, 0, 0, alice}], @eth, [{bob, 2}, {alice, 7}]), @fee)\n        |> success?\n\n      state\n      |> Core.exec(\n        create_recovered([{@blknum1, 0, 0, bob}, {@blknum1, 0, 1, alice}], @eth, [{alice, 7}, {bob, 2}]),\n        @fee\n      )\n      |> fail?(:fees_not_covered)\n      |> same?(state)\n      |> Core.exec(\n        create_recovered([{@blknum1, 0, 0, bob}, {@blknum1, 0, 1, alice}], @eth, [{alice, 9}, {bob, 2}]),\n        @fee\n      )\n      |> fail?(:amounts_do_not_add_up)\n      |> same?(state)\n      |> Core.exec(\n        create_recovered([{@blknum1, 0, 0, bob}, {@blknum1, 0, 1, alice}], @eth, [{alice, 6}, {bob, 2}]),\n        @fee\n      )\n      |> success?()\n    end\n\n    @tag fixtures: [:alice, :bob, :state_empty]\n    test \"Inputs exceeds outputs plus fee\", %{alice: alice, bob: bob, state_empty: state} do\n      state\n      |> do_deposit(alice, %{amount: 10, currency: @eth, blknum: 1})\n      |> Core.exec(create_recovered([{1, 0, 0, alice}], @eth, [{bob, 4}, {alice, 3}]), @fee)\n      |> fail?(:overpaying_fees)\n    end\n\n    @tag fixtures: [:alice, :bob, :state_empty]\n    test \"Inputs sums up exactly to outputs plus fee\", %{alice: alice, bob: bob, state_empty: state} do\n      state\n      |> do_deposit(alice, %{amount: 10, currency: @eth, blknum: 1})\n      |> Core.exec(create_recovered([{1, 0, 0, alice}], @eth, [{bob, 5}, {alice, 4}]), @fee)\n      |> success?\n    end\n\n    @tag fixtures: [:alice, :bob, :state_empty]\n    test \"Inputs are not sufficient for outputs plus fee\", %{alice: alice, bob: bob, state_empty: state} do\n      state\n      |> do_deposit(alice, %{amount: 10, currency: @eth, blknum: 1})\n      |> Core.exec(create_recovered([{1, 0, 0, alice}], @eth, [{bob, 6}, {alice, 4}]), @fee)\n      |> fail?(:fees_not_covered)\n    end\n\n    @tag fixtures: [:alice, :bob, :state_empty]\n    test \"Zero fee is not allowed, transaction is not processed\", %{alice: alice, bob: bob, state_empty: state} do\n      fee = %{@eth => %{amount: 0}}\n\n      state\n      |> do_deposit(alice, %{amount: 10, currency: @eth, blknum: 1})\n      |> Core.exec(create_recovered([{1, 0, 0, alice}], @eth, [{bob, 3}, {alice, 7}]), fee)\n      |> fail?(:fees_not_covered)\n    end\n\n    @tag fixtures: [:alice, :state_empty]\n    test \"Merge transaction is fee free\", %{alice: alice, state_empty: state} do\n      fees = %{@eth => %{amount: 2}}\n      tx = create_recovered([{1, 0, 0, alice}, {2, 0, 0, alice}], @eth, [{alice, 15}])\n      fee = Fees.for_transaction(tx, fees)\n\n      state\n      |> do_deposit(alice, %{amount: 10, currency: @eth, blknum: 1})\n      |> do_deposit(alice, %{amount: 5, currency: @eth, blknum: 2})\n      |> Core.exec(tx, fee)\n      |> success?\n    end\n\n    @tag fixtures: [:alice, :state_empty]\n    test \"Merge transaction is rejected when overpaying\", %{alice: alice, state_empty: state} do\n      fees = %{@eth => %{amount: 2}}\n      tx = create_recovered([{1, 0, 0, alice}, {2, 0, 0, alice}], @eth, [{alice, 9}])\n      fee = Fees.for_transaction(tx, fees)\n\n      state\n      |> do_deposit(alice, %{amount: 10, currency: @eth, blknum: 1})\n      |> do_deposit(alice, %{amount: 5, currency: @eth, blknum: 2})\n      |> Core.exec(tx, fee)\n      |> fail?(:overpaying_fees)\n    end\n\n    @tag fixtures: [:alice, :bob, :state_empty]\n    test \"respects fees for transactions with mixed currencies\", %{\n      alice: alice,\n      bob: bob,\n      state_empty: state\n    } do\n      fees = %{@eth => [1], @not_eth => [1]}\n      not_fee_token = <<2::160>>\n\n      assert not_fee_token not in Map.keys(fees)\n\n      state =\n        state\n        |> do_deposit(alice, %{amount: 10, currency: @eth, blknum: 1})\n        |> do_deposit(alice, %{amount: 2, currency: @not_eth, blknum: 2})\n        |> do_deposit(alice, %{amount: 1, currency: @not_eth, blknum: 3})\n        |> do_deposit(alice, %{amount: 10, currency: not_fee_token, blknum: 4})\n\n      # fee is paid in the same currency as an output\n      state\n      |> Core.exec(create_recovered([{1, 0, 0, alice}, {2, 0, 0, alice}], [{bob, @eth, 10}, {bob, @not_eth, 1}]), fees)\n      |> success?\n\n      # fee is paid in different currency than outputs\n      state\n      |> Core.exec(create_recovered([{1, 0, 0, alice}, {3, 0, 0, alice}], [{bob, @eth, 9}, {bob, @eth, 1}]), fees)\n      |> success?\n\n      # fee is paid from input not transferred by transaction\n      state\n      |> Core.exec(\n        create_recovered([{1, 0, 0, alice}, {4, 0, 0, alice}], [{bob, not_fee_token, 9}, {bob, not_fee_token, 1}]),\n        %{@eth => [10]}\n      )\n      |> success?\n\n      # fee is respected but amounts don't add up\n      state\n      |> Core.exec(create_recovered([{1, 0, 0, alice}, {2, 0, 0, alice}], [{bob, @eth, 10}, {bob, @eth, 1}]), fees)\n      |> fail?(:amounts_do_not_add_up)\n      # fee is not respected\n      |> Core.exec(create_recovered([{1, 0, 0, alice}, {2, 0, 0, alice}], [{bob, @eth, 10}, {bob, @not_eth, 2}]), fees)\n      |> fail?(:fees_not_covered)\n      # transaction transferring only not fee currency still is obliged to fee\n      |> Core.exec(create_recovered([{4, 0, 0, alice}], not_fee_token, [{bob, 3}, {alice, 7}]), fees)\n      |> fail?(:fees_not_covered)\n    end\n\n    @tag fixtures: [:alice, :bob, :state_empty]\n    test \"can spend deposits with mixed currencies\", %{\n      alice: alice,\n      bob: bob,\n      state_empty: state\n    } do\n      state\n      |> do_deposit(alice, %{amount: 1, currency: @eth, blknum: 1})\n      |> do_deposit(alice, %{amount: 2, currency: @not_eth, blknum: 2})\n      |> Core.exec(create_recovered([{1, 0, 0, alice}, {2, 0, 0, alice}], [{bob, @eth, 1}, {bob, @not_eth, 1}]), @fee)\n      |> success?\n    end\n  end\n\n  @tag fixtures: [:alice, :bob, :state_empty]\n  test \"can spend a batch of deposits\", %{alice: alice, bob: bob, state_empty: state} do\n    state\n    |> do_deposit(alice, %{amount: 10, currency: @eth, blknum: 1})\n    |> do_deposit(bob, %{amount: 20, currency: @eth, blknum: 2})\n    |> Core.exec(create_recovered([{1, 0, 0, alice}], @eth, [{bob, 9}]), @fee)\n    |> success?\n    |> Core.exec(create_recovered([{2, 0, 0, bob}], @eth, [{alice, 19}]), @fee)\n    |> success?\n  end\n\n  @tag fixtures: [:alice, :bob, :state_empty]\n  test \"can't spend when signature order does not match input order (restrictive spender checks)\",\n       %{alice: alice, bob: bob, state_empty: state} do\n    state\n    |> do_deposit(alice, %{amount: 10, currency: @eth, blknum: 1})\n    |> do_deposit(bob, %{amount: 20, currency: @eth, blknum: 2})\n    |> Core.exec(create_recovered([{1, 0, 0, bob}, {2, 0, 0, alice}], @eth, [{bob, 10}]), @fee)\n    |> fail?(:unauthorized_spend)\n  end\n\n  @tag fixtures: [:alice, :bob, :state_empty]\n  test \"deposits can arrive in any order; `OMG.Watcher.State.Core` doesn't care about this\",\n       %{alice: alice, bob: bob, state_empty: state} do\n    state\n    |> do_deposit(alice, %{amount: 10, currency: @eth, blknum: 2})\n    |> do_deposit(bob, %{amount: 20, currency: @eth, blknum: 1})\n    |> Core.exec(create_recovered([{2, 0, 0, alice}], @eth, [{bob, 9}]), @fee)\n    |> success?\n    |> Core.exec(create_recovered([{1, 0, 0, bob}], @eth, [{alice, 19}]), @fee)\n    |> success?\n  end\n\n  test \"extract_initial_state function returns error when passed top block number as :not_found\" do\n    assert {:error, :top_block_number_not_found} =\n             Core.extract_initial_state(:not_found, @interval, \"NO FEE CLAIMER ADDR!\")\n  end\n\n  @tag fixtures: [:alice, :bob, :state_empty]\n  test \"can't spend nonexistent\", %{alice: alice, bob: bob, state_empty: state} do\n    state_deposit = state |> do_deposit(alice, %{amount: 10, currency: @eth, blknum: 1})\n\n    state_deposit\n    |> Core.exec(create_recovered([{1, 1, 0, alice}, {1, 0, 0, alice}], @eth, [{bob, 7}]), @fee)\n    |> fail?(:utxo_not_found)\n    |> same?(state_deposit)\n  end\n\n  @tag fixtures: [:alice, :bob, :state_alice_deposit]\n  test \"can't spend other people's funds\", %{alice: alice, bob: bob, state_alice_deposit: state} do\n    state\n    |> Core.exec(create_recovered([{1, 0, 0, bob}], @eth, [{bob, 8}, {alice, 3}]), @fee)\n    |> fail?(:unauthorized_spend)\n    |> same?(state)\n    |> Core.exec(create_recovered([{1, 0, 0, bob}], @eth, [{alice, 10}]), @fee)\n    |> fail?(:unauthorized_spend)\n    |> same?(state)\n  end\n\n  @tag fixtures: [:alice, :bob, :state_alice_deposit]\n  test \"all inputs must be authorized to be spent\", %{alice: alice, bob: bob, state_alice_deposit: state} do\n    state =\n      state\n      |> Core.exec(create_recovered([{1, 0, 0, alice}], @eth, [{bob, 6}, {alice, 3}]), @fee)\n      |> success?()\n\n    state\n    |> Core.exec(create_recovered([{@blknum1, 0, 0, bob}, {@blknum1, 0, 1, bob}], @eth, [{alice, 1}]), @fee)\n    |> fail?(:unauthorized_spend)\n    |> same?(state)\n    |> Core.exec(create_recovered([{@blknum1, 0, 0, alice}, {@blknum1, 0, 1, alice}], @eth, [{alice, 1}]), @fee)\n    |> fail?(:unauthorized_spend)\n    |> same?(state)\n\n    state\n    |> Core.exec(create_recovered([{@blknum1, 0, 0, bob}, {@blknum1, 0, 1, alice}], @eth, [{alice, 8}]), @fee)\n    |> success?()\n  end\n\n  @tag fixtures: [:alice, :bob, :state_alice_deposit]\n  test \"can't spend spent\", %{alice: alice, bob: bob, state_alice_deposit: state} do\n    transactions = [\n      create_recovered([{1, 0, 0, alice}], @eth, [{bob, 7}, {alice, 2}]),\n      create_recovered([{1, 0, 0, alice}], @eth, [{bob, 6}, {alice, 3}])\n    ]\n\n    for first <- transactions,\n        second <- transactions do\n      state\n      |> Core.exec(first, @fee)\n      |> success?\n      |> Core.exec(second, @fee)\n      |> fail?(:utxo_not_found)\n    end\n  end\n\n  @tag fixtures: [:alice, :bob, :carol, :state_alice_deposit]\n  test \"can spend change and merge coins\", %{\n    alice: alice,\n    bob: bob,\n    carol: carol,\n    state_alice_deposit: state\n  } do\n    state\n    |> Core.exec(create_recovered([{1, 0, 0, alice}], @eth, [{bob, 6}, {alice, 3}]), @fee)\n    |> success?\n    |> Core.exec(create_recovered([{@blknum1, 0, 0, bob}], @eth, [{carol, 5}]), @fee)\n    |> success?\n    |> Core.exec(create_recovered([{@blknum1, 0, 1, alice}], @eth, [{carol, 2}]), @fee)\n    |> success?\n    |> Core.exec(create_recovered([{@blknum1, 1, 0, carol}, {@blknum1, 2, 0, carol}], @eth, [{alice, 6}]), @fee)\n    |> success?\n  end\n\n  @tag fixtures: [:alice, :bob, :state_alice_deposit]\n  test \"can spend after block is formed\", %{alice: alice, bob: bob, state_alice_deposit: state} do\n    next_block_height = @blknum2\n    {:ok, {_, _}, state} = form_block_check(state)\n\n    state\n    |> Core.exec(create_recovered([{1, 0, 0, alice}], @eth, [{bob, 7}, {alice, 2}]), @fee)\n    |> success?\n    |> Core.exec(create_recovered([{next_block_height, 0, 0, bob}], @eth, [{bob, 6}]), @fee)\n    |> success?\n  end\n\n  @tag fixtures: [:alice, :bob, :state_alice_deposit]\n  test \"forming block doesn't unspend\", %{alice: alice, bob: bob, state_alice_deposit: state} do\n    recovered = create_recovered([{1, 0, 0, alice}], @eth, [{bob, 7}, {alice, 2}])\n\n    {:ok, {_, _}, state} =\n      state\n      |> Core.exec(recovered, @fee)\n      |> success?\n      |> form_block_check()\n\n    Core.exec(state, recovered, @fee) |> fail?(:utxo_not_found) |> same?(state)\n  end\n\n  @tag fixtures: [:alice, :bob, :state_alice_deposit]\n  test \"can't double spend chained txs\", %{alice: alice, bob: bob, state_alice_deposit: state} do\n    recovered = create_recovered([{1, 0, 0, alice}], @eth, [{bob, 7}, {alice, 2}])\n    recovered2 = create_recovered([{1000, 0, 0, bob}], @eth, [{bob, 6}])\n\n    state\n    |> Core.exec(recovered, @fee)\n    |> success?\n    |> Core.exec(recovered2, @fee)\n    |> success?\n    |> Core.exec(recovered2, @fee)\n    |> fail?(:utxo_not_found)\n  end\n\n  @tag fixtures: [:alice, :bob, :state_alice_deposit]\n  test \"can't spend own output\", %{bob: bob, state_alice_deposit: state} do\n    # The transaction here is designed so that it would spend its own output. Sanity checking first\n    {1000, true} = Core.get_status(state)\n    recovered2 = create_recovered([{1000, 0, 0, bob}], @eth, [{bob, 6}])\n\n    state\n    |> Core.exec(recovered2, @fee)\n    |> fail?(:utxo_not_found)\n  end\n\n  @tag fixtures: [:stable_alice, :stable_bob, :state_stable_alice_deposit]\n  test \"forming block puts all transactions in a block\", %{\n    stable_alice: alice,\n    stable_bob: bob,\n    state_stable_alice_deposit: state\n  } do\n    # odd number of transactions, just in case\n    recovered_tx_1 = create_recovered([{1, 0, 0, alice}], @eth, [{bob, 6}, {alice, 3}])\n    recovered_tx_2 = create_recovered([{@blknum1, 0, 0, bob}], @eth, [{alice, 3}, {bob, 2}])\n    recovered_tx_3 = create_recovered([{@blknum1, 0, 1, alice}], @eth, [{alice, 1}, {bob, 1}])\n\n    state =\n      state\n      |> Core.exec(recovered_tx_1, @fee)\n      |> success?\n      |> Core.exec(recovered_tx_2, @fee)\n      |> success?\n      |> Core.exec(recovered_tx_3, @fee)\n      |> success?\n\n    assert {:ok,\n            {%Block{\n               transactions: [block_tx1, block_tx2, _third_tx],\n               hash: block_hash,\n               number: @blknum1\n             }, _}, _} = form_block_check(state)\n\n    # precomputed fixed hash to check compliance with hashing algo\n    assert <<240, 92, 32, 48, 163, 193, 58, 124, 248, 71>> <> _ = block_hash\n\n    # Check that contents of the block can be recovered again to original txs\n    assert {:ok, ^recovered_tx_1} = Transaction.Recovered.recover_from(block_tx1)\n    assert {:ok, ^recovered_tx_2} = Transaction.Recovered.recover_from(block_tx2)\n  end\n\n  @tag fixtures: [:alice, :bob, :state_alice_deposit]\n  test \"forming block empty block after a non-empty block\", %{\n    alice: alice,\n    bob: bob,\n    state_alice_deposit: state\n  } do\n    state =\n      state\n      |> Core.exec(create_recovered([{1, 0, 0, alice}], @eth, [{bob, 7}, {alice, 2}]), @fee)\n      |> success?\n\n    {:ok, {_, _}, state} = form_block_check(state)\n    expected_block = empty_block(@blknum2)\n\n    assert {:ok, {^expected_block, _}, _} = form_block_check(state)\n  end\n\n  @tag fixtures: [:state_empty]\n  test \"no pending transactions at start (empty block, no db updates)\", %{state_empty: state} do\n    expected_block = empty_block()\n\n    assert {:ok, {^expected_block, [{:put, :block, _}, {:put, :child_top_block_number, @blknum1}]}, _state} =\n             form_block_check(state)\n  end\n\n  @tag fixtures: [:alice, :bob, :state_alice_deposit]\n  test \"spending produces db updates, that don't leak to next block\", %{\n    alice: alice,\n    bob: bob,\n    state_alice_deposit: state\n  } do\n    # persistence tested in-depth elsewhere\n    {:ok, {_, [_ | _]}, state} =\n      state\n      |> Core.exec(create_recovered([{1, 0, 0, alice}], @eth, [{bob, 7}, {alice, 2}]), @fee)\n      |> success?\n      |> form_block_check()\n\n    assert {:ok, {_, [{:put, :block, _}, {:put, :child_top_block_number, @blknum2}]}, _} = form_block_check(state)\n  end\n\n  @tag fixtures: [:alice, :state_empty]\n  test \"depositing produces db updates, that don't leak to next block\", %{\n    alice: alice,\n    state_empty: state\n  } do\n    # persistence tested in-depth elsewhere\n    assert {:ok, [_ | _], state} = Core.deposit([%{owner: alice.addr, currency: @eth, amount: 10, blknum: 1}], state)\n\n    assert {:ok, {_, [{:put, :block, _}, {:put, :child_top_block_number, @blknum1}]}, _} = form_block_check(state)\n  end\n\n  @tag fixtures: [:alice, :state_alice_deposit, :state_empty]\n  test \"given exit infos in various forms translates to utxo positions\",\n       %{alice: alice, state_alice_deposit: state, state_empty: state_empty} do\n    # this test checks whether all ways of calling `get_exiting_utxo_positions/2` translates\n    # to given exiting utxo positions\n    utxo_pos_exits = [Utxo.position(@blknum1, 0, 0), Utxo.position(@blknum1, 0, 1)]\n\n    assert utxo_pos_exits ==\n             utxo_pos_exits\n             |> Enum.map(&%{call_data: %{utxo_pos: Utxo.Position.encode(&1)}})\n             |> Core.extract_exiting_utxo_positions(state_empty)\n\n    assert utxo_pos_exits ==\n             utxo_pos_exits\n             |> Enum.map(&%{utxo_pos: Utxo.Position.encode(&1)})\n             |> Core.extract_exiting_utxo_positions(state_empty)\n\n    assert utxo_pos_exits ==\n             utxo_pos_exits\n             |> Enum.map(&Utxo.Position.encode/1)\n             |> Core.extract_exiting_utxo_positions(state_empty)\n\n    %Transaction.Recovered{tx_hash: tx_hash} = tx = create_recovered([{1, 0, 0, alice}], @eth, [{alice, 7}, {alice, 2}])\n\n    piggybacks = [\n      %{tx_hash: tx_hash, output_index: 0, omg_data: %{piggyback_type: :output}},\n      %{tx_hash: tx_hash, output_index: 1, omg_data: %{piggyback_type: :output}}\n    ]\n\n    state =\n      state\n      |> Core.exec(tx, @fee)\n      |> success?\n\n    assert utxo_pos_exits == Core.extract_exiting_utxo_positions(piggybacks, state)\n  end\n\n  @tag fixtures: [:alice, :state_alice_deposit]\n  test \"spends utxo validly when exiting\", %{alice: alice, state_alice_deposit: state} do\n    # persistence tested in-depth elsewhere\n    state =\n      state\n      |> Core.exec(\n        create_recovered([{1, 0, 0, alice}], @eth, [{alice, 6}, {alice, 3}]),\n        @fee\n      )\n      |> success?\n\n    utxo_pos_exit_1 = Utxo.position(@blknum1, 0, 0)\n    utxo_pos_exit_2 = Utxo.position(@blknum1, 0, 1)\n    utxo_pos_exits = [utxo_pos_exit_1, utxo_pos_exit_2]\n\n    assert {:ok, {[_ | _], {[^utxo_pos_exit_1, ^utxo_pos_exit_2], []}}, state_after_exit} =\n             Core.exit_utxos(utxo_pos_exits, state)\n\n    state_after_exit\n    |> Core.exec(create_recovered([{@blknum1, 0, 0, alice}], @eth, [{alice, 6}]), @fee)\n    |> fail?(:utxo_not_found)\n    |> same?(state_after_exit)\n    |> Core.exec(create_recovered([{@blknum1, 0, 1, alice}], @eth, [{alice, 2}]), @fee)\n    |> fail?(:utxo_not_found)\n  end\n\n  @tag fixtures: [:alice, :state_empty]\n  test \"spends utxo from db when exiting\", %{alice: alice, state_empty: state} do\n    db_utxos = make_utxos([{@blknum1, 0, 0, alice, @eth, 6}, {@blknum1, 0, 1, alice, @eth, 3}])\n    extended_state = Core.with_utxos(state, db_utxos)\n\n    utxo_pos_exit_1 = Utxo.position(@blknum1, 0, 0)\n    utxo_pos_exit_2 = Utxo.position(@blknum1, 0, 1)\n    utxo_pos_exits = [utxo_pos_exit_1, utxo_pos_exit_2]\n\n    assert {:ok, {[_ | _], {[^utxo_pos_exit_1, ^utxo_pos_exit_2], []}}, state_after_exit} =\n             Core.exit_utxos(utxo_pos_exits, extended_state)\n\n    state_after_exit\n    |> Core.exec(create_recovered([{@blknum1, 0, 0, alice}], @eth, [{alice, 6}]), @fee)\n    |> fail?(:utxo_not_found)\n    |> Core.exec(create_recovered([{@blknum1, 0, 1, alice}], @eth, [{alice, 2}]), @fee)\n    |> fail?(:utxo_not_found)\n  end\n\n  @tag fixtures: [:alice, :state_alice_deposit]\n  test \"removed utxo after piggyback from available utxo\", %{alice: alice, state_alice_deposit: state} do\n    # persistence tested in-depth elsewhere\n    tx = create_recovered([{1, 0, 0, alice}], @eth, [{alice, 7}, {alice, 2}])\n\n    state = state |> Core.exec(tx, @fee) |> success?\n\n    utxo_pos_exits_in_flight = [%{call_data: %{in_flight_tx: Transaction.raw_txbytes(tx)}}]\n\n    utxo_pos_exits_piggyback = [\n      %{tx_hash: Transaction.raw_txhash(tx), output_index: 0, omg_data: %{piggyback_type: :output}}\n    ]\n\n    expected_position = Utxo.position(@blknum1, 0, 0)\n\n    assert {:ok, {[], {[], _}}, ^state} =\n             utxo_pos_exits_in_flight\n             |> Core.extract_exiting_utxo_positions(state)\n             |> Core.exit_utxos(state)\n\n    assert {:ok, {[_ | _], {[^expected_position], []}}, state_after_exit} =\n             utxo_pos_exits_piggyback\n             |> Core.extract_exiting_utxo_positions(state)\n             |> Core.exit_utxos(state)\n\n    state_after_exit\n    |> Core.exec(create_recovered([{@blknum1, 0, 0, alice}], @eth, [{alice, 6}]), @fee)\n    |> fail?(:utxo_not_found)\n    |> same?(state_after_exit)\n    |> Core.exec(create_recovered([{@blknum1, 0, 1, alice}], @eth, [{alice, 1}]), @fee)\n    |> success?\n  end\n\n  @tag fixtures: [:alice, :state_alice_deposit]\n  test \"removed in-flight inputs from available utxo\", %{alice: alice, state_alice_deposit: state} do\n    # persistence tested in-depth elsewhere\n    state =\n      state\n      |> Core.exec(create_recovered([{1, 0, 0, alice}], @eth, [{alice, 6}, {alice, 3}]), @fee)\n      |> success?\n\n    tx = create_recovered([{@blknum1, 0, 0, alice}], @eth, [{alice, 2}, {alice, 3}])\n\n    utxo_pos_exits_in_flight = [%{call_data: %{in_flight_tx: Transaction.raw_txbytes(tx)}}]\n    expected_position = Utxo.position(@blknum1, 0, 0)\n\n    exiting_utxos = Core.extract_exiting_utxo_positions(utxo_pos_exits_in_flight, state)\n\n    assert {:ok, {[_ | _], {[^expected_position], _}}, state_after_exit} = Core.exit_utxos(exiting_utxos, state)\n\n    state_after_exit\n    |> Core.exec(create_recovered([{@blknum1, 0, 0, alice}], @eth, [{alice, 5}]), @fee)\n    |> fail?(:utxo_not_found)\n    |> same?(state_after_exit)\n    |> Core.exec(create_recovered([{@blknum1, 0, 1, alice}], @eth, [{alice, 2}]), @fee)\n    |> success?\n  end\n\n  @tag fixtures: [:state_empty]\n  test \"notifies about invalid utxo exiting\", %{state_empty: state} do\n    utxo_pos_exit_1 = Utxo.position(@blknum1, 0, 0)\n\n    assert {:ok, {[], {[], [^utxo_pos_exit_1]}}, ^state} = Core.exit_utxos([utxo_pos_exit_1], state)\n  end\n\n  @tag fixtures: [:state_alice_deposit]\n  test \"ignores a piggyback of a non-included tx's outout\", %{state_alice_deposit: state} do\n    piggyback_event = %{tx_hash: 1, output_index: 0, omg_data: %{piggyback_type: :output}}\n\n    assert {:ok, {[], {[], []}}, ^state} =\n             [piggyback_event]\n             |> Core.extract_exiting_utxo_positions(state)\n             |> Core.exit_utxos(state)\n  end\n\n  @tag fixtures: [:state_alice_deposit]\n  test \"ignores on exiting, when input piggybacks are detected\", %{state_alice_deposit: state} do\n    piggyback_event = %{tx_hash: 1, output_index: 0, omg_data: %{piggyback_type: :input}}\n\n    assert {:ok, {[], {[], []}}, ^state} =\n             [piggyback_event]\n             |> Core.extract_exiting_utxo_positions(state)\n             |> Core.exit_utxos(state)\n  end\n\n  @tag fixtures: [:alice, :state_empty]\n  test \"tells if utxo exists\", %{alice: alice, state_empty: state} do\n    assert not Core.utxo_exists?(Utxo.position(1, 0, 0), state)\n\n    state = state |> do_deposit(alice, %{amount: 10, currency: @eth, blknum: 1})\n    assert Core.utxo_exists?(Utxo.position(1, 0, 0), state)\n\n    state =\n      state\n      |> Core.exec(create_recovered([{1, 0, 0, alice}], @eth, [{alice, 9}]), @fee)\n      |> success?\n\n    assert not Core.utxo_exists?(Utxo.position(1, 0, 0), state)\n  end\n\n  @tag fixtures: [:alice, :state_empty]\n  test \"tells if utxo exists in db-extended state\", %{alice: alice, state_empty: state} do\n    state = Core.with_utxos(state, make_utxos([{1, 0, 0, alice, @eth, 10}]))\n    assert Core.utxo_exists?(Utxo.position(1, 0, 0), state)\n\n    state =\n      state\n      |> Core.exec(create_recovered([{1, 0, 0, alice}], @eth, [{alice, 9}]), @fee)\n      |> success?\n\n    assert not Core.utxo_exists?(Utxo.position(1, 0, 0), state)\n  end\n\n  @tag fixtures: [:state_empty]\n  test \"Getting current block height on empty state\", %{state_empty: state} do\n    assert {@blknum1, _} = Core.get_status(state)\n  end\n\n  @tag fixtures: [:state_empty]\n  test \"Getting current block height with one formed block\", %{state_empty: state} do\n    {:ok, {_, _}, new_state} = form_block_check(state)\n    assert {@blknum2, true} = Core.get_status(new_state)\n  end\n\n  @tag fixtures: [:alice, :state_empty]\n  test \"beginning of block changes when transactions executed and block formed\",\n       %{alice: alice, state_empty: state} do\n    # at empty state it is at the beginning of the next block\n    assert {@blknum1, true} = Core.get_status(state)\n\n    # when we execute a tx it isn't at the beginning\n    {:ok, _, state} =\n      state\n      |> do_deposit(alice, %{amount: 10, currency: @eth, blknum: 1})\n      |> Core.exec(create_recovered([{1, 0, 0, alice}], @eth, [{alice, 9}]), @fee)\n\n    assert {@blknum1, false} = Core.get_status(state)\n\n    # when a block has been newly formed it is at the beginning\n    {:ok, _, state} = form_block_check(state)\n\n    assert {@blknum2, true} = Core.get_status(state)\n  end\n\n  @tag fixtures: [:alice, :bob, :state_alice_deposit]\n  test \"Does not allow executing transactions with input utxos from the future\", %{\n    alice: alice,\n    bob: bob,\n    state_alice_deposit: state\n  } do\n    future_deposit_blknum = @blknum1 + 1\n    state = do_deposit(state, alice, %{amount: 10, currency: @eth, blknum: future_deposit_blknum})\n\n    # input utxo blknum is greater than state's blknum\n    state\n    |> Core.exec(create_recovered([{future_deposit_blknum, 0, 0, alice}], @eth, [{bob, 5}, {alice, 4}]), @fee)\n    |> fail?(:input_utxo_ahead_of_state)\n\n    state\n    |> Core.exec(\n      create_recovered([{1, 0, 0, alice}, {future_deposit_blknum, 0, 0, alice}], @eth, [{bob, 5}, {alice, 4}]),\n      @fee\n    )\n    |> fail?(:input_utxo_ahead_of_state)\n\n    # when non-existent input comes with a blknum of the current block fail with :utxo_not_found\n    state\n    |> Core.exec(create_recovered([{@blknum1, 1, 0, alice}], @eth, [{bob, 5}, {alice, 4}]), @fee)\n    |> fail?(:utxo_not_found)\n  end\n\n  @tag fixtures: [:alice]\n  test \"no utxos that belong to address within the empty query result\", %{alice: %{addr: alice}} do\n    assert [] == Core.standard_exitable_utxos([], alice)\n  end\n\n  @tag fixtures: [:alice, :bob, :carol]\n  test \"getting user utxos from utxos_query_result\",\n       %{alice: alice, bob: bob, carol: carol} do\n    output_type = OMG.Watcher.WireFormatTypes.output_type_for(:output_payment_v1)\n\n    utxos_query_result = [\n      {{1000, 0, 0},\n       %{output: %{amount: 1, currency: @eth, owner: alice.addr, output_type: output_type}, creating_txhash: \"nil\"}},\n      {{2000, 1, 1},\n       %{output: %{amount: 2, currency: @eth, owner: bob.addr, output_type: output_type}, creating_txhash: \"nil\"}},\n      {{1000, 2, 0},\n       %{\n         output: %{amount: 3, currency: @not_eth, owner: alice.addr, output_type: output_type},\n         creating_txhash: \"nil\"\n       }},\n      {{1000, 3, 1},\n       %{output: %{amount: 4, currency: @eth, owner: alice.addr, output_type: output_type}, creating_txhash: \"nil\"}},\n      {{1000, 4, 0},\n       %{output: %{amount: 5, currency: @eth, owner: bob.addr, output_type: output_type}, creating_txhash: \"nil\"}}\n    ]\n\n    assert [] == Core.standard_exitable_utxos(utxos_query_result, carol.addr)\n\n    assert MapSet.equal?(\n             MapSet.new([\n               %{blknum: 1000, txindex: 0, oindex: 0, otype: output_type, owner: alice.addr, currency: @eth, amount: 1},\n               %{\n                 blknum: 1000,\n                 txindex: 2,\n                 oindex: 0,\n                 otype: output_type,\n                 owner: alice.addr,\n                 currency: @not_eth,\n                 amount: 3\n               },\n               %{blknum: 1000, txindex: 3, oindex: 1, otype: output_type, owner: alice.addr, currency: @eth, amount: 4}\n             ]),\n             MapSet.new(Core.standard_exitable_utxos(utxos_query_result, alice.addr))\n           )\n\n    assert Map.equal?(\n             MapSet.new([\n               %{blknum: 1000, txindex: 4, oindex: 0, otype: output_type, owner: bob.addr, currency: @eth, amount: 5},\n               %{blknum: 2000, txindex: 1, oindex: 1, otype: output_type, owner: bob.addr, currency: @eth, amount: 2}\n             ]),\n             MapSet.new(Core.standard_exitable_utxos(utxos_query_result, bob.addr))\n           )\n  end\n\n  describe \"Automatic fees claiming\" do\n    setup do\n      fee_claimer = OMG.Watcher.TestHelper.generate_entity()\n\n      child_block_interval = Configuration.child_block_interval()\n      {:ok, state_empty} = Core.extract_initial_state(0, child_block_interval, fee_claimer.addr)\n\n      alice = OMG.Watcher.TestHelper.generate_entity()\n      fees = %{@eth => [2]}\n\n      state =\n        state_empty\n        |> do_deposit(alice, %{amount: 10, currency: @eth, blknum: 1})\n        |> Core.exec(create_recovered([{1, 0, 0, alice}], @eth, [{alice, 8}]), fees)\n        |> success?()\n\n      {:ok, [state: state, alice: alice, fees: fees, fee_claimer: fee_claimer, state_empty: state_empty]}\n    end\n\n    test \"fee txs cannot be intermixed with payments\", %{alice: alice, state: state, fees: fees} do\n      fee_tx = create_recovered_fee_tx(1000, state.fee_claimer_address, @eth, 2)\n\n      state\n      # fees from 1st tx are available to claim\n      |> Core.exec(fee_tx, fees)\n      |> success?()\n      # at this point no other payment can be processed\n      |> Core.exec(create_recovered([{1000, 0, 0, alice}], @eth, [{alice, 5}]), fees)\n      |> fail?(:payments_rejected_during_fee_claiming)\n    end\n\n    test \"cannot claim the same token twice\", %{state: state, fees: fees} do\n      fee_tx = create_recovered_fee_tx(1000, state.fee_claimer_address, @eth, 2)\n\n      state\n      # fees from 1st tx are available to claim\n      |> Core.exec(fee_tx, fees)\n      |> success?()\n      # at this point no other payment can be processed\n      |> Core.exec(fee_tx, fees)\n      |> fail?(:surplus_in_token_not_collected)\n    end\n\n    test \"cannot claim more than collected\", %{state: state, fees: fees} do\n      paid_fee = 2\n\n      fee_tx = create_recovered_fee_tx(1000, state.fee_claimer_address, @eth, paid_fee + 1)\n\n      state\n      |> Core.exec(fee_tx, fees)\n      |> fail?(:claimed_and_collected_amounts_mismatch)\n    end\n\n    test \"cannot claim less than collected\", %{state: state, fees: fees} do\n      paid_fee = 2\n\n      fee_tx = create_recovered_fee_tx(1000, state.fee_claimer_address, @eth, paid_fee - 1)\n\n      state\n      |> Core.exec(fee_tx, fees)\n      |> fail?(:claimed_and_collected_amounts_mismatch)\n    end\n\n    test \"no fees can be claimed after block is formed\", %{state: state, fees: fees} do\n      fee_tx = create_recovered_fee_tx(1000, state.fee_claimer_address, @eth, 2)\n\n      # now it's possible to claim Eth fee (note: no state modification)\n      state\n      |> Core.exec(fee_tx, fees)\n      |> success?()\n\n      # block is formed without claiming fees\n      {:ok, {_block, _dbupdates}, new_state} = form_block_check(state)\n\n      # it's no longer possible to claim fees\n      new_state\n      |> Core.exec(fee_tx, fees)\n      |> fail?(:surplus_in_token_not_collected)\n    end\n\n    test \"fee is paid in one token only, many surpluses prohibited\", %{alice: alice, state: state, fees: fees} do\n      state\n      |> do_deposit(alice, %{amount: 100, currency: @not_eth, blknum: 2})\n      |> Core.exec(\n        create_recovered([{1000, 0, 0, alice}, {2, 0, 0, alice}], [{alice, @eth, 5}, {alice, @not_eth, 90}]),\n        fees\n      )\n      |> fail?(:multiple_potential_currency_fees)\n    end\n\n    test \"zero surplus is not collectable\", %{alice: alice, state: state, fees: fees} do\n      state =\n        state\n        |> do_deposit(alice, %{amount: 100, currency: @not_eth, blknum: 2})\n        |> Core.exec(\n          create_recovered([{1000, 0, 0, alice}, {2, 0, 0, alice}], [{alice, @eth, 6}, {alice, @not_eth, 100}]),\n          fees\n        )\n        |> success?()\n\n      # not_eth currency is transferred in full - no surplus exists\n      state\n      |> Core.exec(create_recovered_fee_tx(1000, state.fee_claimer_address, @not_eth, 1), fees)\n      |> fail?(:surplus_in_token_not_collected)\n    end\n\n    test \"surpluses adding up for same-token-fees paid in a block\", %{alice: alice, state: state, fees: fees} do\n      state =\n        state\n        |> Core.exec(create_recovered([{1000, 0, 0, alice}], @eth, [{alice, 6}]), fees)\n        |> success?()\n\n      # we can claim sum of the surpluses from 2 txs (one in setup & one above)\n      collected = 2 + 2\n\n      state\n      |> Core.exec(create_recovered_fee_tx(1000, state.fee_claimer_address, @eth, collected), fees)\n      |> success?()\n    end\n\n    # this test takes ~26 seconds on my machine\n    @tag slow: true\n    @tag timeout: 60_000 * 3\n    test \"long running full block test\", %{alice: alice, state_empty: state, fees: fees} do\n      Logger.warn(\"slow test is running, use --exclude slow to skip\")\n\n      maximum_block_size = 65_536\n      maximum_inputs_size = 4\n      eth_fee_rate = Enum.at(fees[@eth], 0)\n      amount_for_fees = (1 + eth_fee_rate) * maximum_block_size\n      available_after_1st_tx = amount_for_fees - eth_fee_rate\n\n      # First tx is applied just to make below transactions generation easier\n      state =\n        state\n        |> do_deposit(alice, %{amount: amount_for_fees, currency: @eth, blknum: 1})\n        |> Core.exec(create_recovered([{1, 0, 0, alice}], @eth, [{alice, available_after_1st_tx}]), fees)\n        |> success?()\n\n      # we just send 1 payment and this reserves 1 spot for fee\n      already_reserved = 2\n\n      # available space is block_size\n      ntx_to_apply = maximum_block_size - (1 + maximum_inputs_size + already_reserved)\n\n      {state, _} =\n        Enum.reduce(0..ntx_to_apply, {state, available_after_1st_tx}, fn index, {curr_state, amount} ->\n          new_amount = amount - eth_fee_rate\n\n          new_state =\n            curr_state\n            |> Core.exec(create_recovered([{1000, index, 0, alice}], @eth, [{alice, new_amount}]), fees)\n            |> success?()\n\n          {new_state, new_amount}\n        end)\n\n      state\n      # NOTE: I don't care about existing utxo actual position or available amount because block size is checked first\n      |> Core.exec(create_recovered([{2, 0, 0, alice}], @eth, [{alice, 1_000_000}]), fees)\n      |> fail?(:too_many_transactions_in_block)\n    end\n  end\n\n  defp success?(result) do\n    assert {:ok, _, state} = result\n    state\n  end\n\n  defp fail?(result, expected_error) do\n    assert {{:error, ^expected_error}, state} = result\n    state\n  end\n\n  defp same?({{:error, _someerror}, state}, expected_state) do\n    assert expected_state == state\n    state\n  end\n\n  defp same?(state, expected_state) do\n    assert expected_state == state\n    state\n  end\n\n  defp empty_block(number \\\\ @blknum1) do\n    %Block{transactions: [], hash: @empty_block_hash, number: number}\n  end\n\n  # used to check the invariants in form_block\n  # use this throughout this test module instead of Core.form_block\n  defp form_block_check(state) do\n    {_, {block, db_updates}, _} = result = Core.form_block(state)\n\n    # check if block returned and sent to db_updates is the same\n    assert Enum.member?(db_updates, {:put, :block, Block.to_db_value(block)})\n    # check if that's the only db_update for block\n    is_block_put? = fn {operation, type, _} -> operation == :put && type == :block end\n    assert Enum.count(db_updates, is_block_put?) == 1\n\n    result\n  end\n\n  defp make_utxos(utxos) when is_list(utxos), do: Enum.into(utxos, %{}, &to_utxo_kv/1)\n\n  defp to_utxo_kv({blknum, txindex, oindex, owner, currency, amount}),\n    do: {\n      Utxo.position(blknum, txindex, oindex),\n      %Utxo{output: %Output{amount: amount, currency: currency, owner: owner.addr}}\n    }\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/state/measurement_calculation_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.State.MeasurementCalculationTest do\n  @moduledoc \"\"\"\n  Testing functional behaviors.\n  \"\"\"\n  use ExUnitFixtures\n  use ExUnit.Case, async: true\n\n  alias OMG.Eth.Encoding\n  alias OMG.Output\n  alias OMG.Watcher.State.Core\n  alias OMG.Watcher.Utxo\n\n  require Utxo\n\n  @eth <<0::160>>\n  @not_eth <<1::size(160)>>\n  @tag fixtures: [:alice, :bob, :carol]\n\n  test \"calculate metrics from state\", %{alice: alice, bob: bob, carol: carol} do\n    utxos = %{\n      Utxo.position(2_000, 4076, 3) => %OMG.Watcher.Utxo{\n        output: %Output{amount: 700_000_000, currency: @eth, owner: alice}\n      },\n      Utxo.position(1_000, 2559, 0) => %OMG.Watcher.Utxo{\n        output: %Output{amount: 111_111_111, currency: @not_eth, owner: alice}\n      },\n      Utxo.position(8_000, 4854, 2) => %OMG.Watcher.Utxo{\n        output: %Output{amount: 77_000_000, currency: @eth, owner: bob}\n      },\n      Utxo.position(7_000, 4057, 3) => %OMG.Watcher.Utxo{\n        output: %Output{amount: 222_222_222, currency: @not_eth, owner: carol}\n      },\n      Utxo.position(7_000, 4057, 4) => %OMG.Watcher.Utxo{output: %{}}\n    }\n\n    assert MapSet.new(OMG.Watcher.State.MeasurementCalculation.calculate(%Core{utxos: utxos})) ==\n             MapSet.new([\n               {:unique_users, 3},\n               {:balance, 777_000_000, \"currency:#{Encoding.to_hex(@eth)}\"},\n               {:balance, 333_333_333, \"currency:#{Encoding.to_hex(@not_eth)}\"}\n             ])\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/state/persistence_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.State.PersistenceTest do\n  @moduledoc \"\"\"\n  Test focused on the persistence bits of `OMG.Watcher.State.Core`\n  \"\"\"\n  use ExUnitFixtures\n  use ExUnit.Case, async: false\n\n  import OMG.Watcher.TestHelper\n\n  require OMG.Watcher.Utxo\n  require Logger\n  alias Ecto.Adapters.SQL.Sandbox\n  alias OMG.Eth.Configuration\n  alias OMG.Watcher.Block\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.Utxo\n  alias Support.WaitFor\n\n  @fee_claimer_address Base.decode16!(\"DEAD000000000000000000000000000000000000\")\n\n  @eth <<0::160>>\n  @interval Configuration.child_block_interval()\n  @blknum1 @interval\n\n  setup do\n    db_path = Briefly.create!(directory: true)\n    Application.put_env(:omg_db, :path, db_path, persistent: true)\n\n    :ok = OMG.DB.init()\n    {:ok, started_apps} = Application.ensure_all_started(:omg_db)\n    {:ok, bus_apps} = Application.ensure_all_started(:omg_bus)\n    metrics_collection_interval = 60_000\n\n    {:ok, _} =\n      Supervisor.start_link(\n        [\n          {OMG.Watcher.State,\n           [\n             fee_claimer_address: @fee_claimer_address,\n             child_block_interval: @interval,\n             metrics_collection_interval: metrics_collection_interval\n           ]}\n        ],\n        strategy: :one_for_one\n      )\n\n    Application.ensure_all_started(:postgrex)\n    Application.ensure_all_started(:spandex_ecto)\n    Application.ensure_all_started(:ecto)\n\n    {:ok, _} =\n      Supervisor.start_link(\n        [\n          %{\n            id: OMG.WatcherInfo.DB.Repo,\n            start: {OMG.WatcherInfo.DB.Repo, :start_link, []},\n            type: :supervisor\n          }\n        ],\n        strategy: :one_for_one,\n        name: WatcherInfo.Supervisor\n      )\n\n    :ok = Sandbox.checkout(OMG.WatcherInfo.DB.Repo)\n    Sandbox.mode(OMG.WatcherInfo.DB.Repo, {:shared, self()})\n\n    on_exit(fn ->\n      Application.put_env(:omg_db, :path, nil)\n\n      (started_apps ++ bus_apps)\n      |> Enum.reverse()\n      |> Enum.map(fn app -> :ok = Application.stop(app) end)\n    end)\n\n    {:ok, %{}}\n  end\n\n  @tag fixtures: [:alice, :bob]\n  test \"persists deposits and utxo is available after restart\", %{alice: alice, bob: bob} do\n    [\n      %{owner: bob, currency: @eth, amount: 10, blknum: 1},\n      %{owner: alice, currency: @eth, amount: 20, blknum: 2}\n    ]\n    |> persist_deposit()\n\n    assert OMG.Watcher.State.utxo_exists?(Utxo.position(2, 0, 0))\n\n    :ok = restart_state()\n\n    assert OMG.Watcher.State.utxo_exists?(Utxo.position(1, 0, 0))\n    assert OMG.Watcher.State.utxo_exists?(Utxo.position(2, 0, 0))\n  end\n\n  @tag fixtures: [:alice]\n  test \"utxos are persisted\", %{alice: alice} do\n    [%{owner: alice, currency: @eth, amount: 20, blknum: 1}]\n    |> persist_deposit()\n    |> exec(create_recovered([{1, 0, 0, alice}], @eth, [{alice, 19}]))\n    |> persist_form()\n\n    assert not OMG.Watcher.State.utxo_exists?(Utxo.position(1, 0, 0))\n    assert OMG.Watcher.State.utxo_exists?(Utxo.position(@blknum1, 0, 0))\n  end\n\n  @tag fixtures: [:alice, :bob]\n  test \"utxos are available after restart\", %{alice: alice, bob: bob} do\n    [%{owner: alice, currency: @eth, amount: 20, blknum: 1}]\n    |> persist_deposit()\n    |> exec(create_recovered([{1, 0, 0, alice}], @eth, [{bob, 17}, {alice, 2}]))\n    |> exec(create_recovered([{@blknum1, 0, 0, bob}, {@blknum1, 0, 1, alice}], @eth, [{bob, 18}]))\n    |> persist_form()\n\n    :ok = restart_state()\n\n    assert not OMG.Watcher.State.utxo_exists?(Utxo.position(@blknum1, 0, 0))\n    assert not OMG.Watcher.State.utxo_exists?(Utxo.position(@blknum1, 0, 1))\n    assert OMG.Watcher.State.utxo_exists?(Utxo.position(@blknum1, 1, 0))\n  end\n\n  @tag fixtures: [:alice, :bob]\n  test \"cannot double spend from the transactions within the same block\", %{alice: alice, bob: bob} do\n    :ok = persist_deposit([%{owner: alice, currency: @eth, amount: 10, blknum: 1}])\n\n    # after the restart newly up state won't have deposit's utxo in memory\n    :ok = restart_state()\n\n    assert :ok == exec(create_recovered([{1, 0, 0, alice}], @eth, [{bob, 6}, {alice, 3}]))\n    assert :utxo_not_found == exec(create_recovered([{1, 0, 0, alice}], @eth, [{alice, 10}]))\n  end\n\n  @tag fixtures: [:alice]\n  test \"blocks and spends are persisted\", %{alice: alice} do\n    tx = create_recovered([{1, 0, 0, alice}], @eth, [{alice, 19}])\n\n    [%{owner: alice, currency: @eth, amount: 20, blknum: 1}]\n    |> persist_deposit()\n    |> exec(tx)\n    |> persist_form()\n\n    assert {:ok, [hash]} = OMG.DB.block_hashes([@blknum1])\n\n    :ok = restart_state()\n\n    assert {:ok, [db_block]} = OMG.DB.blocks([hash])\n    %Block{number: @blknum1, transactions: [payment_tx], hash: ^hash} = Block.from_db_value(db_block)\n\n    assert {:ok, tx} == Transaction.Recovered.recover_from(payment_tx)\n\n    assert {:ok, 1000} ==\n             tx |> Transaction.get_inputs() |> hd() |> Utxo.Position.to_input_db_key() |> OMG.DB.spent_blknum()\n  end\n\n  @tag fixtures: [:alice]\n  test \"exiting utxo is deleted from state\", %{alice: alice} do\n    utxo_positions = [\n      Utxo.position(@blknum1, 0, 0),\n      Utxo.position(@blknum1, 0, 1)\n    ]\n\n    [%{owner: alice, currency: @eth, amount: 20, blknum: 1}]\n    |> persist_deposit()\n    |> exec(create_recovered([{1, 0, 0, alice}], @eth, [{alice, 19}]))\n    |> persist_form()\n    |> persist_exit_utxos(utxo_positions)\n\n    :ok = restart_state()\n\n    assert not OMG.Watcher.State.utxo_exists?(Utxo.position(@blknum1, 0, 0))\n    assert not OMG.Watcher.State.utxo_exists?(Utxo.position(@blknum1, 0, 1))\n  end\n\n  @tag fixtures: [:alice]\n  test \"cannot spend just exited utxo\", %{alice: alice} do\n    :ok = persist_deposit([%{owner: alice, currency: @eth, amount: 20, blknum: 1}])\n\n    {:ok, _, _} = OMG.Watcher.State.exit_utxos([Utxo.position(1, 0, 0)])\n\n    # exit db_updates won't get persisted yet, but alice tries to spent it immediately\n    assert exec(create_recovered([{1, 0, 0, alice}], @eth, [{alice, 20}])) == :utxo_not_found\n\n    # retry above with empty in-memory utxoset\n    :ok = restart_state()\n\n    {:ok, _, _} = OMG.Watcher.State.exit_utxos([Utxo.position(1, 0, 0)])\n    assert exec(create_recovered([{1, 0, 0, alice}], @eth, [{alice, 20}])) == :utxo_not_found\n  end\n\n  defp persist_deposit(deposits) do\n    {:ok, db_updates} =\n      deposits\n      |> make_deposits()\n      |> OMG.Watcher.State.deposit()\n\n    :ok = OMG.DB.multi_update(db_updates)\n  end\n\n  defp persist_form(:ok), do: persist_form()\n\n  defp persist_form() do\n    state = :sys.get_state(OMG.Watcher.State)\n\n    {:ok, {_block, db_updates}, new_state} = OMG.Watcher.State.Core.form_block(state)\n\n    :ok = OMG.DB.multi_update(db_updates)\n    :sys.replace_state(OMG.Watcher.State, fn _ -> new_state end)\n    :ok\n  end\n\n  defp exec(:ok, tx), do: exec(tx)\n\n  defp exec(tx) do\n    fee = %{@eth => [1]}\n\n    case OMG.Watcher.State.exec(tx, fee) do\n      {:ok, _} -> :ok\n      {:error, reason} -> reason\n    end\n  end\n\n  defp persist_exit_utxos(:ok, exit_infos), do: persist_exit_utxos(exit_infos)\n\n  defp persist_exit_utxos(exit_infos) do\n    {:ok, db_updates, _} = OMG.Watcher.State.exit_utxos(exit_infos)\n\n    :ok = OMG.DB.multi_update(db_updates)\n  end\n\n  defp make_deposits(list) do\n    Enum.map(list, fn %{owner: owner, currency: currency, amount: amount, blknum: blknum} ->\n      %{\n        root_chain_txhash: <<blknum::256>>,\n        log_index: 0,\n        owner: owner.addr,\n        currency: currency,\n        amount: amount,\n        blknum: blknum,\n        eth_height: 1\n      }\n    end)\n  end\n\n  defp restart_state() do\n    GenServer.stop(OMG.Watcher.State)\n\n    WaitFor.ok(fn -> if(GenServer.whereis(OMG.Watcher.State), do: :ok) end)\n    _ = Logger.info(\"OMG.Watcher.State restarted\")\n\n    :ok\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/state/transaction/fee_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.State.Transaction.FeeTest do\n  @moduledoc false\n\n  use ExUnitFixtures\n  use ExUnit.Case, async: true\n\n  alias OMG.Watcher.State.Transaction\n\n  @eth <<0::160>>\n  @other_token <<127::160>>\n\n  setup do\n    {:ok, [alice: OMG.Watcher.TestHelper.generate_entity()]}\n  end\n\n  describe \"new/2\" do\n    test \"can be encoded to binary form and back\", %{alice: owner} do\n      fee_tx = Transaction.Fee.new(1000, {owner.addr, @eth, 1551})\n\n      rlp_form = Transaction.raw_txbytes(fee_tx)\n      assert fee_tx == Transaction.decode!(rlp_form)\n    end\n\n    test \"hash can be computed with protocol implementation\", %{alice: owner} do\n      fee_tx = Transaction.Fee.new(1000, {owner.addr, @eth, 1551})\n      fee_txhash = Transaction.raw_txhash(fee_tx)\n      assert <<_::256>> = fee_txhash\n\n      assert Transaction.raw_txhash(Transaction.Fee.new(1000, {owner.addr, @eth, 1551})) == fee_txhash\n      assert Transaction.raw_txhash(Transaction.Fee.new(1001, {owner.addr, @eth, 1551})) != fee_txhash\n      assert Transaction.raw_txhash(Transaction.Fee.new(1000, {owner.addr, @other_token, 1551})) != fee_txhash\n    end\n\n    test \"fee-tx should be recoverable from binary form\", %{alice: owner} do\n      fee_tx = Transaction.Fee.new(1000, {owner.addr, @eth, 1551})\n      tx_rlp = Transaction.Signed.encode(%Transaction.Signed{raw_tx: fee_tx, sigs: []})\n\n      assert {:ok,\n              %Transaction.Recovered{\n                signed_tx: %Transaction.Signed{raw_tx: ^fee_tx}\n              }} = Transaction.Recovered.recover_from(tx_rlp)\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/state/transaction/recovered_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.State.Transaction.RecoveredTest do\n  @moduledoc \"\"\"\n  This test the public-most APIs regarging the transaction, being mainly centered around:\n    - recovery and stateless validation done in `Transaction.Recovered`\n    - usability of recovered transactions in `OMG.Watcher.State`\n    - detecting and reporting invalidly encoded, malformed, illegal transactions\n  \"\"\"\n  use ExUnitFixtures\n  use ExUnit.Case, async: true\n\n  alias OMG.Watcher.DevCrypto\n  alias OMG.Watcher.State.Core\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.TestHelper\n  alias OMG.Watcher.Utxo\n  alias OMG.Watcher.WireFormatTypes\n\n  require Utxo\n\n  @payment_tx_type WireFormatTypes.tx_type_for(:tx_payment_v1)\n  @payment_output_type WireFormatTypes.output_type_for(:output_payment_v1)\n\n  @zero_address <<0::160>>\n  @eth <<0::160>>\n  @empty_signature <<0::size(520)>>\n\n  describe \"APIs used by the `OMG.Watcher.State.exec/1`\" do\n    @tag fixtures: [:alice, :state_alice_deposit, :bob]\n    test \"using created transaction in child chain\", %{alice: alice, bob: bob, state_alice_deposit: state} do\n      state = TestHelper.do_deposit(state, alice, %{amount: 10, currency: @eth, blknum: 2})\n\n      payment = Transaction.Payment.new([{1, 0, 0}, {2, 0, 0}], [{bob.addr, @eth, 19}])\n\n      payment\n      |> DevCrypto.sign([alice.priv, alice.priv])\n      |> assert_tx_usable(state)\n    end\n\n    @tag fixtures: [:alice, :state_alice_deposit, :bob]\n    test \"using created transaction with one input in child chain\", %{\n      alice: alice,\n      bob: bob,\n      state_alice_deposit: state\n    } do\n      payment = Transaction.Payment.new([{1, 0, 0}], [{bob.addr, @eth, 9}])\n\n      payment\n      |> DevCrypto.sign([alice.priv])\n      |> assert_tx_usable(state)\n    end\n\n    @tag fixtures: [:alice, :bob]\n    test \"recovering spenders: different signers, one output\", %{alice: alice, bob: bob} do\n      {:ok, recovered} =\n        [{3000, 0, 0}, {3000, 0, 1}]\n        |> Transaction.Payment.new([{alice.addr, @eth, 10}])\n        |> DevCrypto.sign([bob.priv, alice.priv])\n        |> Transaction.Signed.encode()\n        |> Transaction.Recovered.recover_from()\n\n      assert recovered.witnesses == %{0 => bob.addr, 1 => alice.addr}\n    end\n\n    @tag fixtures: [:alice, :bob]\n    test \"signed transaction is valid in various empty input/output combinations\", %{\n      alice: alice,\n      bob: bob\n    } do\n      transaction_list = [\n        {[], [{alice, @eth, 7}]},\n        {[{1, 2, 3, alice}], [{alice, @eth, 7}]},\n        {[{1, 2, 3, alice}], [{alice, @eth, 7}, {bob, @eth, 3}]},\n        {[{1, 2, 3, alice}, {2, 3, 4, bob}], [{alice, @eth, 7}, {bob, @eth, 3}]},\n        {[{1, 2, 3, alice}, {2, 3, 4, bob}, {2, 3, 5, bob}], [{alice, @eth, 7}, {bob, @eth, 3}]},\n        {[{1, 2, 3, alice}, {2, 3, 4, bob}, {2, 3, 5, bob}], [{alice, @eth, 7}, {bob, @eth, 3}, {bob, @eth, 3}]},\n        {[{1, 2, 3, alice}, {2, 3, 1, alice}, {2, 3, 2, bob}, {3, 3, 4, bob}],\n         [{alice, @eth, 7}, {alice, @eth, 3}, {bob, @eth, 7}, {bob, @eth, 3}]}\n      ]\n\n      Enum.map(transaction_list, &parametrized_tester/1)\n    end\n  end\n\n  describe \"encoding/decoding is done properly\" do\n    @tag fixtures: [:alice]\n    test \"decoding malformed signed payment transaction\", %{alice: alice} do\n      payment = Transaction.Payment.new([{1, 0, 0}, {2, 0, 0}], [{alice.addr, @eth, 12}])\n      tx = DevCrypto.sign(payment, [alice.priv, alice.priv])\n      %Transaction.Signed{sigs: sigs} = tx\n\n      [_payment_marker, inputs, outputs, _txdata, _metadata] = tx |> Transaction.raw_txbytes() |> ExRLP.decode()\n\n      # sanity\n      assert {:ok, _} =\n               Transaction.Recovered.recover_from(\n                 ExRLP.encode([sigs, @payment_tx_type, inputs, outputs, 0, <<0::256>>])\n               )\n\n      assert {:error, :malformed_transaction} = Transaction.Recovered.recover_from(<<192>>)\n      assert {:error, :malformed_transaction} = Transaction.Recovered.recover_from(<<0x80>>)\n      assert {:error, :malformed_transaction} = Transaction.Recovered.recover_from(<<>>)\n      assert {:error, :malformed_transaction} = Transaction.Recovered.recover_from(ExRLP.encode(23))\n      assert {:error, :malformed_transaction} = Transaction.Recovered.recover_from(ExRLP.encode([sigs, 1]))\n\n      # looks like a payment transaction but type points to a `Transaction.Fee`, hence malformed not unrecognized\n      assert {:error, :malformed_transaction} =\n               Transaction.Recovered.recover_from(ExRLP.encode([sigs, 3, inputs, outputs, 0, <<0::256>>]))\n\n      assert {:error, :malformed_transaction} =\n               Transaction.Recovered.recover_from(ExRLP.encode([sigs, 1, outputs, 0, <<0::256>>]))\n\n      assert {:error, :unrecognized_transaction_type} =\n               Transaction.Recovered.recover_from(ExRLP.encode([sigs, [\"bad\"], inputs, outputs, 0, <<0::256>>]))\n\n      assert {:error, :unrecognized_transaction_type} = Transaction.Recovered.recover_from(ExRLP.encode([sigs, []]))\n\n      assert {:error, :unrecognized_transaction_type} =\n               Transaction.Recovered.recover_from(ExRLP.encode([sigs, 234_567, inputs, outputs, 0, <<0::256>>]))\n\n      assert {:error, :malformed_witnesses} ==\n               Transaction.Recovered.recover_from(\n                 ExRLP.encode([\n                   [ExPlasma.payment_v1(), ExPlasma.payment_v1()],\n                   @payment_tx_type,\n                   inputs,\n                   outputs,\n                   0,\n                   <<0::256>>\n                 ])\n               )\n\n      assert {:error, :malformed_witnesses} ==\n               Transaction.Recovered.recover_from(\n                 ExRLP.encode([ExPlasma.payment_v1(), @payment_tx_type, inputs, outputs, 0, <<0::256>>])\n               )\n\n      assert {:error, :malformed_witnesses} ==\n               Transaction.Recovered.recover_from(\n                 ExRLP.encode([[sigs], @payment_tx_type, inputs, outputs, 0, <<0::256>>])\n               )\n\n      assert {:error, :malformed_inputs} =\n               Transaction.Recovered.recover_from(ExRLP.encode([sigs, @payment_tx_type, 42, outputs, 0, <<0::256>>]))\n\n      assert {:error, :malformed_inputs} =\n               Transaction.Recovered.recover_from(\n                 ExRLP.encode([sigs, @payment_tx_type, [[1, 2]], outputs, 0, <<0::256>>])\n               )\n\n      assert {:error, :malformed_inputs} =\n               Transaction.Recovered.recover_from(\n                 ExRLP.encode([sigs, @payment_tx_type, [[1, 2, 'a']], outputs, 0, <<0::256>>])\n               )\n\n      assert {:error, :malformed_outputs} =\n               Transaction.Recovered.recover_from(ExRLP.encode([sigs, @payment_tx_type, inputs, 42, 0, <<0::256>>]))\n\n      assert {:error, :malformed_outputs} =\n               Transaction.Recovered.recover_from(\n                 ExRLP.encode([\n                   sigs,\n                   @payment_tx_type,\n                   inputs,\n                   [[@payment_output_type, alice.addr, alice.addr, 1]],\n                   0,\n                   <<0::256>>\n                 ])\n               )\n\n      assert {:error, :malformed_outputs} =\n               Transaction.Recovered.recover_from(\n                 ExRLP.encode([\n                   sigs,\n                   @payment_tx_type,\n                   inputs,\n                   [[@payment_output_type, [alice.addr, alice.addr]]],\n                   0,\n                   <<0::256>>\n                 ])\n               )\n\n      assert {:error, :malformed_outputs} =\n               Transaction.Recovered.recover_from(\n                 ExRLP.encode([\n                   sigs,\n                   @payment_tx_type,\n                   inputs,\n                   [[@payment_output_type, [alice.addr, alice.addr, 'a']]],\n                   0,\n                   <<0::256>>\n                 ])\n               )\n\n      assert {:error, :unrecognized_output_type} =\n               Transaction.Recovered.recover_from(\n                 ExRLP.encode([sigs, @payment_tx_type, inputs, [[<<232>>, [alice.addr, alice.addr, 1]]], 0, <<0::256>>])\n               )\n\n      assert {:error, :malformed_tx_data} =\n               Transaction.Recovered.recover_from(\n                 ExRLP.encode([sigs, @payment_tx_type, inputs, outputs, 1, <<0::256>>])\n               )\n\n      assert {:error, :malformed_uint256} =\n               Transaction.Recovered.recover_from(\n                 ExRLP.encode([sigs, @payment_tx_type, inputs, outputs, [<<6>>], <<0::256>>])\n               )\n\n      assert {:error, :leading_zeros_in_encoded_uint} =\n               Transaction.Recovered.recover_from(\n                 ExRLP.encode([sigs, @payment_tx_type, inputs, outputs, <<0::256>>, <<0::256>>])\n               )\n\n      assert {:error, :leading_zeros_in_encoded_uint} =\n               Transaction.Recovered.recover_from(\n                 ExRLP.encode([sigs, @payment_tx_type, inputs, outputs, <<1::256>>, <<0::256>>])\n               )\n\n      assert {:error, :malformed_metadata} =\n               Transaction.Recovered.recover_from(ExRLP.encode([sigs, @payment_tx_type, inputs, outputs, 0, \"\"]))\n\n      assert {:error, :malformed_metadata} =\n               Transaction.Recovered.recover_from(ExRLP.encode([sigs, @payment_tx_type, inputs, outputs, 0, []]))\n\n      assert {:error, :malformed_metadata} =\n               Transaction.Recovered.recover_from(\n                 ExRLP.encode([sigs, @payment_tx_type, inputs, outputs, 0, <<1::224>>])\n               )\n\n      assert {:error, :malformed_metadata} =\n               Transaction.Recovered.recover_from(\n                 ExRLP.encode([sigs, @payment_tx_type, inputs, outputs, 0, <<2::288>>])\n               )\n    end\n\n    @tag fixtures: [:alice, :bob]\n    test \"rlp encoding of a transaction is corrupt\", %{alice: alice, bob: bob} do\n      encoded_signed_tx = TestHelper.create_encoded([{1, 2, 3, alice}, {2, 3, 4, bob}], @eth, [{alice, 7}])\n\n      malformed2 = \"A\" <> encoded_signed_tx\n      assert {:error, :malformed_transaction_rlp} = Transaction.Recovered.recover_from(malformed2)\n\n      <<_, malformed3::binary>> = encoded_signed_tx\n      assert {:error, :malformed_transaction_rlp} = Transaction.Recovered.recover_from(malformed3)\n\n      cropped_size = byte_size(encoded_signed_tx) - 1\n      <<malformed4::binary-size(cropped_size), _::binary-size(1)>> = encoded_signed_tx\n      assert {:error, :malformed_transaction_rlp} = Transaction.Recovered.recover_from(malformed4)\n    end\n\n    @tag fixtures: [:alice, :bob]\n    test \"address in encoded transaction malformed\", %{alice: alice, bob: bob} do\n      malformed_alice = %{addr: \"0x00000000000000000\"}\n      malformed_eth = \"0x00000000000000000\"\n      malformed_signed1 = TestHelper.create_signed([{1, 2, 3, alice}, {2, 3, 4, bob}], @eth, [{malformed_alice, 7}])\n      malformed_signed2 = TestHelper.create_signed([{1, 2, 3, alice}, {2, 3, 4, bob}], malformed_eth, [{alice, 7}])\n\n      malformed_signed3 =\n        TestHelper.create_signed([{1, 2, 3, alice}, {2, 3, 4, bob}], @eth, [{alice, 7}, {malformed_alice, 3}])\n\n      malformed1 = Transaction.Signed.encode(malformed_signed1)\n      malformed2 = Transaction.Signed.encode(malformed_signed2)\n      malformed3 = Transaction.Signed.encode(malformed_signed3)\n\n      assert {:error, :malformed_address} = Transaction.Recovered.recover_from(malformed1)\n      assert {:error, :malformed_address} = Transaction.Recovered.recover_from(malformed2)\n      assert {:error, :malformed_address} = Transaction.Recovered.recover_from(malformed3)\n    end\n\n    @tag fixtures: [:alice]\n    test \"transactions with corrupt signatures don't do harm - one signature\", %{alice: alice} do\n      full_signed_tx = TestHelper.create_signed([{1, 2, 3, alice}], @eth, [{alice, 7}])\n\n      assert {:error, :signature_corrupt} ==\n               %Transaction.Signed{full_signed_tx | sigs: [<<1::size(520)>>]}\n               |> Transaction.Signed.encode()\n               |> Transaction.Recovered.recover_from()\n    end\n\n    @tag fixtures: [:alice]\n    test \"transactions with corrupt signatures don't do harm - one of many signatures\", %{alice: alice} do\n      full_signed_tx = TestHelper.create_signed([{1, 2, 3, alice}, {1, 2, 4, alice}], @eth, [{alice, 7}])\n      %Transaction.Signed{sigs: [sig1, sig2 | _]} = full_signed_tx\n\n      assert {:error, :signature_corrupt} ==\n               %Transaction.Signed{full_signed_tx | sigs: [sig1, <<1::size(520)>>]}\n               |> Transaction.Signed.encode()\n               |> Transaction.Recovered.recover_from()\n\n      assert {:error, :signature_corrupt} ==\n               %Transaction.Signed{full_signed_tx | sigs: [<<1::size(520)>>, sig2]}\n               |> Transaction.Signed.encode()\n               |> Transaction.Recovered.recover_from()\n    end\n  end\n\n  describe \"stateless validity critical to the ledger is checked\" do\n    @tag fixtures: [:alice]\n    test \"transaction must have distinct inputs\", %{alice: alice} do\n      duplicate_inputs = TestHelper.create_encoded([{1, 2, 3, alice}, {1, 2, 3, alice}], @eth, [{alice, 7}])\n\n      assert {:error, :duplicate_inputs} = Transaction.Recovered.recover_from(duplicate_inputs)\n    end\n  end\n\n  describe \"formal protocol rules are enforced\" do\n    test \"Decoding transaction with a zero input fails\" do\n      inputs_index_in_rlp = 2\n\n      assert {:error, :malformed_inputs} =\n               good_tx_rlp_items()\n               |> List.replace_at(inputs_index_in_rlp, [<<0::256>>, <<1::256>>])\n               |> ExRLP.encode()\n               |> Transaction.Recovered.recover_from()\n    end\n\n    @tag fixtures: [:alice]\n    test \"Decoding deposit transaction without inputs is successful\", %{alice: alice} do\n      encoded_transaction = TestHelper.create_encoded([], @eth, [{alice, 100}])\n      assert {:ok, _} = Transaction.Recovered.recover_from(encoded_transaction)\n    end\n\n    @tag fixtures: [:alice]\n    test \"Decoding transaction with zero blknum works as long as input non-zero\", %{alice: alice} do\n      encoded_transaction = TestHelper.create_encoded([{0, 0, 1, alice}], [{alice, @zero_address, 10}])\n      assert {:ok, _} = Transaction.Recovered.recover_from(encoded_transaction)\n    end\n\n    test \"Decoding transaction with list as transaction type fails\" do\n      tx_type_index_in_rlp = 1\n\n      assert {:error, :unrecognized_transaction_type} =\n               good_tx_rlp_items()\n               |> List.replace_at(tx_type_index_in_rlp, [ExPlasma.payment_v1()])\n               |> ExRLP.encode()\n               |> Transaction.Recovered.recover_from()\n    end\n\n    test \"Decoding transaction with too many inputs fails\" do\n      inputs_index_in_rlp = 2\n      [input | _] = Enum.at(good_tx_rlp_items(), inputs_index_in_rlp)\n\n      assert {:error, :too_many_inputs} =\n               good_tx_rlp_items()\n               |> List.replace_at(inputs_index_in_rlp, List.duplicate(input, 5))\n               |> ExRLP.encode()\n               |> Transaction.Recovered.recover_from()\n    end\n\n    test \"Decoding transaction with shorter input fails\" do\n      inputs_index_in_rlp = 2\n      [input | _] = Enum.at(good_tx_rlp_items(), inputs_index_in_rlp)\n\n      assert {:error, :malformed_inputs} =\n               good_tx_rlp_items()\n               |> List.replace_at(inputs_index_in_rlp, [binary_part(input, 1, 31)])\n               |> ExRLP.encode()\n               |> Transaction.Recovered.recover_from()\n    end\n\n    test \"Decoding transaction with shorter/longer/malformed address fails\" do\n      outputs_index_in_rlp = 3\n      [[type, [owner, currency, amount]]] = Enum.at(good_tx_rlp_items(), outputs_index_in_rlp)\n\n      checker = fn bad_output ->\n        assert {:error, :malformed_address} =\n                 good_tx_rlp_items()\n                 |> List.replace_at(outputs_index_in_rlp, [bad_output])\n                 |> ExRLP.encode()\n                 |> Transaction.Recovered.recover_from()\n      end\n\n      transaction_list = [\n        [type, [binary_part(owner, 1, 19), currency, amount]],\n        [type, [binary_part(owner, 0, 19), currency, amount]],\n        [type, [owner, binary_part(currency, 1, 19), amount]],\n        [type, [owner, binary_part(currency, 0, 19), amount]],\n        [type, [owner, <<1>>, amount]],\n        [type, [<<1>>, currency, amount]],\n        [type, [owner, \"\", amount]],\n        [type, [\"\", currency, amount]],\n        [type, [<<1>>, currency, amount]],\n        # address-like (21 bytes encoded) items being lists\n        [type, [owner, [<<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1>>, <<3>>, <<1>>, <<1>>], amount]],\n        [type, [[<<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1>>, <<3>>, <<1>>, <<1>>], currency, amount]]\n      ]\n\n      Enum.map(transaction_list, checker)\n    end\n\n    @tag fixtures: [:alice]\n    test \"Decoding transaction with zero amount in outputs fails \", %{alice: alice} do\n      encoded_transaction = TestHelper.create_encoded([{1000, 0, 0, alice}], @eth, [{alice, 0}, {alice, 100}])\n      assert {:error, :amount_cant_be_zero} = Transaction.Recovered.recover_from(encoded_transaction)\n    end\n\n    @tag fixtures: [:alice]\n    test \"Decoding transaction with zero output guard in outputs fails \", %{alice: alice} do\n      no_account = %{addr: @zero_address}\n\n      assert {:error, :output_guard_cant_be_zero} =\n               Transaction.Recovered.recover_from(\n                 TestHelper.create_encoded([{1000, 0, 0, alice}], @eth, [{no_account, 10}, {alice, 100}])\n               )\n    end\n\n    @tag fixtures: [:alice]\n    test \"Decoding transaction with zero output fails\", %{alice: alice} do\n      no_account = %{addr: @zero_address}\n\n      assert {:error, :output_guard_cant_be_zero} =\n               Transaction.Recovered.recover_from(\n                 TestHelper.create_encoded([{1000, 0, 0, alice}], [{no_account, @zero_address, 0}])\n               )\n    end\n\n    test \"Decoding transaction with zero output type fails\" do\n      outputs_index_in_rlp = 3\n      [[_type, output_fields]] = Enum.at(good_tx_rlp_items(), outputs_index_in_rlp)\n      bad_output = [0, output_fields]\n\n      assert {:error, :unrecognized_output_type} =\n               good_tx_rlp_items()\n               |> List.replace_at(outputs_index_in_rlp, [bad_output])\n               |> ExRLP.encode()\n               |> Transaction.Recovered.recover_from()\n    end\n\n    test \"Decoding transaction with list as output type fails\" do\n      outputs_index_in_rlp = 3\n      [[_type, output_fields]] = Enum.at(good_tx_rlp_items(), outputs_index_in_rlp)\n      bad_output = [[<<1>>], output_fields]\n\n      assert {:error, :unrecognized_output_type} =\n               good_tx_rlp_items()\n               |> List.replace_at(outputs_index_in_rlp, [bad_output])\n               |> ExRLP.encode()\n               |> Transaction.Recovered.recover_from()\n    end\n\n    test \"Decoding transaction with malformed output fails\" do\n      outputs_index_in_rlp = 3\n      [output] = Enum.at(good_tx_rlp_items(), outputs_index_in_rlp)\n\n      assert {:error, :malformed_outputs} =\n               good_tx_rlp_items()\n               |> List.replace_at(outputs_index_in_rlp, output)\n               |> ExRLP.encode()\n               |> Transaction.Recovered.recover_from()\n    end\n\n    test \"Decoding transaction with leading-zeros in output amount fails\" do\n      outputs_index_in_rlp = 3\n      [[type, [owner, currency, _amount]]] = Enum.at(good_tx_rlp_items(), outputs_index_in_rlp)\n\n      checker = fn bad_amount ->\n        assert {:error, :leading_zeros_in_encoded_uint} =\n                 good_tx_rlp_items()\n                 |> List.replace_at(outputs_index_in_rlp, [[type, [owner, currency, bad_amount]]])\n                 |> ExRLP.encode()\n                 |> Transaction.Recovered.recover_from()\n      end\n\n      Enum.map([<<1::288>>, <<1::224>>, <<1::64>>, <<0, 1>>], checker)\n    end\n\n    test \"Decoding transaction with not-a-uint256 in output amount fails\" do\n      outputs_index_in_rlp = 3\n      [[type, [owner, currency, _amount]]] = Enum.at(good_tx_rlp_items(), outputs_index_in_rlp)\n\n      assert {:error, :malformed_outputs} =\n               good_tx_rlp_items()\n               |> List.replace_at(outputs_index_in_rlp, [[type, [owner, currency, [<<6>>]]]])\n               |> ExRLP.encode()\n               |> Transaction.Recovered.recover_from()\n    end\n\n    test \"Decoding transaction with a bad RLP (non-optimal encoding) fails\" do\n      # NOTE: it's hard to build a bad RLP encoding of a full transaction, so just check if invalidity of RLP is a\n      # specific error. This is a regression test for the underlying RLP implementation for\n      # https://github.com/mana-ethereum/ex_rlp/issues/26\n\n      # sanity check - correct RLP but nonsense\n      assert {:error, :malformed_transaction} = Transaction.Recovered.recover_from(<<10>>)\n      # non-optimally encoded `<<10>>` in RLP, a specific error is returned\n      assert {:error, :malformed_transaction_rlp} = Transaction.Recovered.recover_from(<<129, 10>>)\n    end\n\n    test \"Decoding transaction with >32 bytes in output amount fails\" do\n      outputs_index_in_rlp = 3\n      [[type, [owner, currency, _amount]]] = Enum.at(good_tx_rlp_items(), outputs_index_in_rlp)\n      bad_amount = :binary.copy(<<1>>, 33)\n\n      assert {:error, :encoded_uint_too_big} =\n               good_tx_rlp_items()\n               |> List.replace_at(outputs_index_in_rlp, [[type, [owner, currency, bad_amount]]])\n               |> ExRLP.encode()\n               |> Transaction.Recovered.recover_from()\n    end\n\n    test \"Decoding transaction a list in output amount fails\" do\n      outputs_index_in_rlp = 3\n      [[type, [owner, currency, _amount]]] = Enum.at(good_tx_rlp_items(), outputs_index_in_rlp)\n      bad_amount = [<<1>>]\n\n      assert {:error, :malformed_outputs} =\n               good_tx_rlp_items()\n               |> List.replace_at(outputs_index_in_rlp, [[type, [owner, currency, bad_amount]]])\n               |> ExRLP.encode()\n               |> Transaction.Recovered.recover_from()\n    end\n\n    test \"Decoding transaction with too many outputs fails\" do\n      outputs_index_in_rlp = 3\n      [output] = Enum.at(good_tx_rlp_items(), outputs_index_in_rlp)\n\n      assert {:error, :too_many_outputs} =\n               good_tx_rlp_items()\n               |> List.replace_at(outputs_index_in_rlp, List.duplicate(output, 5))\n               |> ExRLP.encode()\n               |> Transaction.Recovered.recover_from()\n    end\n\n    @tag fixtures: [:alice]\n    test \"Decoding transaction without outputs fails\", %{alice: alice} do\n      assert {:error, :empty_outputs} =\n               Transaction.Recovered.recover_from(TestHelper.create_encoded([{1000, 0, 0, alice}], @eth, []))\n    end\n\n    @tag fixtures: [:alice, :bob]\n    test \"transaction is not allowed to have input and empty sigs\", %{alice: alice} do\n      tx = TestHelper.create_signed([{1, 2, 3, alice}, {2, 3, 4, alice}], @eth, [{alice, 7}])\n      tx_no_sigs = %{tx | sigs: [@empty_signature, @empty_signature]}\n      tx_hash = Transaction.Signed.encode(tx_no_sigs)\n      assert {:error, :missing_signature} == Transaction.Recovered.recover_from(tx_hash)\n    end\n\n    @tag fixtures: [:alice]\n    test \"transactions with superfluous signatures don't do harm\", %{alice: alice} do\n      full_signed_tx = TestHelper.create_signed([{1, 2, 3, alice}], @eth, [{alice, 7}])\n      %Transaction.Signed{sigs: [sig1 | _]} = full_signed_tx\n\n      assert {:error, :superfluous_signature} ==\n               %Transaction.Signed{full_signed_tx | sigs: [sig1, sig1]}\n               |> Transaction.Signed.encode()\n               |> Transaction.Recovered.recover_from()\n    end\n  end\n\n  defp assert_tx_usable(signed, state_core) do\n    fee = %{@eth => [1]}\n\n    {:ok, transaction} = signed |> Transaction.Signed.encode() |> Transaction.Recovered.recover_from()\n    assert {:ok, {_, _, _}, _state} = Core.exec(state_core, transaction, fee)\n  end\n\n  defp parametrized_tester({inputs, outputs}) do\n    tx = TestHelper.create_signed(inputs, outputs)\n\n    encoded_signed_tx = Transaction.Signed.encode(tx)\n\n    witnesses =\n      inputs\n      |> Enum.filter(fn {_, _, _, %{addr: addr}} -> addr != nil end)\n      |> Enum.map(fn {_, _, _, spender} -> spender.addr end)\n      |> Enum.with_index()\n      |> Enum.into(%{}, fn {witness, index} -> {index, witness} end)\n\n    assert {:ok,\n            %Transaction.Recovered{\n              signed_tx: ^tx,\n              witnesses: ^witnesses\n            }} = Transaction.Recovered.recover_from(encoded_signed_tx)\n  end\n\n  # provides one with RLP items (ready for `ExRLP.encode/1`) representing a valid transaction\n  defp good_tx_rlp_items() do\n    alice = TestHelper.generate_entity()\n\n    good_tx_rlp_items =\n      TestHelper.create_encoded([{1000, 0, 0, alice}, {1000, 0, 1, alice}], [{alice, @eth, 10}])\n      |> ExRLP.decode()\n\n    # sanity check just in case\n    assert {:ok, _} =\n             good_tx_rlp_items\n             |> ExRLP.encode()\n             |> Transaction.Recovered.recover_from()\n\n    good_tx_rlp_items\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/state/transaction/witness_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.State.Transaction.WitnessTest do\n  @moduledoc false\n\n  use ExUnit.Case, async: true\n\n  alias OMG.Watcher.State.Transaction.Witness\n\n  describe \"valid?/1\" do\n    test \"returns true when is binary and 65 bytes long\" do\n      assert Witness.valid?(<<0::520>>)\n    end\n\n    test \"returns false when not a binary\" do\n      refute Witness.valid?([<<0>>])\n    end\n\n    test \"returns false when not 65 bytes long\" do\n      refute Witness.valid?(<<0>>)\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/state/transaction_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.State.TransactionTest do\n  @moduledoc \"\"\"\n  This test the public-most APIs regarging the transaction, being mainly centered around:\n    - creation and encoding of raw transactions\n    - some basic checks of internal APIs used elsewhere - getting inputs/outputs, spend authorization, hashing, encoding\n  \"\"\"\n  use ExUnitFixtures\n  use ExUnit.Case, async: true\n\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.Utxo\n\n  require Utxo\n\n  @eth <<0::160>>\n  @payment_output_type OMG.Watcher.WireFormatTypes.output_type_for(:output_payment_v1)\n  @utxo_positions [{20, 42, 1}, {2, 21, 0}, {1000, 0, 0}, {10_001, 0, 0}]\n  @transaction Transaction.Payment.new(\n                 [{1, 1, 0}, {1, 2, 1}],\n                 [{\"alicealicealicealice\", @eth, 1}, {\"carolcarolcarolcarol\", @eth, 2}],\n                 <<0::256>>\n               )\n\n  test \"create transaction with metadata\" do\n    tx_with_metadata = Transaction.Payment.new(@utxo_positions, [{\"Joe Black\", @eth, 53}], <<0::256>>)\n    tx_without_metadata = Transaction.Payment.new(@utxo_positions, [{\"Joe Black\", @eth, 53}])\n\n    assert Transaction.raw_txhash(tx_with_metadata) == Transaction.raw_txhash(tx_without_metadata)\n\n    assert byte_size(Transaction.raw_txbytes(tx_with_metadata)) ==\n             byte_size(Transaction.raw_txbytes(tx_without_metadata))\n  end\n\n  test \"raw transaction hash is invariant\" do\n    assert <<21, 94, 181, 22, 125, 2, 47, 124, 113>> <> _ = Transaction.raw_txhash(@transaction)\n  end\n\n  test \"create transaction with different number inputs and outputs\" do\n    check_input1 = Utxo.position(20, 42, 1)\n    output1 = {\"Joe Black\", @eth, 99}\n    check_output2 = %{amount: 99, currency: @eth, owner: \"Joe Black\", output_type: @payment_output_type}\n    # 1 - input, 1 - output\n    tx1_1 = Transaction.Payment.new([hd(@utxo_positions)], [output1])\n    assert 1 == tx1_1 |> Transaction.get_inputs() |> length()\n    assert 1 == tx1_1 |> Transaction.get_outputs() |> length()\n    assert [^check_input1 | _] = Transaction.get_inputs(tx1_1)\n    assert ^check_output2 = Transaction.get_outputs(tx1_1) |> hd() |> Map.from_struct()\n    # 4 - input, 4 - outputs\n    tx4_4 = Transaction.Payment.new(@utxo_positions, [output1, {\"J\", @eth, 929}, {\"J\", @eth, 929}, {\"J\", @eth, 199}])\n    assert 4 == tx4_4 |> Transaction.get_inputs() |> length()\n    assert 4 == tx4_4 |> Transaction.get_outputs() |> length()\n    assert [^check_input1 | _] = Transaction.get_inputs(tx4_4)\n    assert ^check_output2 = Transaction.get_outputs(tx4_4) |> hd() |> Map.from_struct()\n  end\n\n  test \"Decode raw transaction, a low level encode/decode parity check\" do\n    {:ok, decoded} = @transaction |> Transaction.raw_txbytes() |> Transaction.decode()\n    assert decoded == @transaction\n    assert decoded == @transaction |> Transaction.raw_txbytes() |> Transaction.decode!()\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/state/utxo_set_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.State.UtxoSetTest do\n  @moduledoc \"\"\"\n  Low-level unit test of `OMG.Watcher.State.UtxoSet`\n  \"\"\"\n  use ExUnitFixtures\n  use ExUnit.Case, async: true\n\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.State.UtxoSet\n  alias OMG.Watcher.Utxo\n\n  import OMG.Watcher.TestHelper, only: [generate_entity: 0, create_recovered: 2]\n\n  require Utxo\n\n  @eth <<0::160>>\n\n  setup do\n    [alice, bob] = 1..2 |> Enum.map(fn _ -> generate_entity() end)\n\n    transaction = create_recovered([{1, 0, 0, alice}, {2, 0, 0, bob}], [{bob, @eth, 1}, {bob, @eth, 2}])\n    inputs = Transaction.get_inputs(transaction)\n    outputs = Transaction.get_outputs(transaction)\n\n    db_query_result =\n      inputs\n      |> Enum.zip(outputs)\n      |> Enum.map(fn {input, output} -> {input, %Utxo{output: output, creating_txhash: <<1>>}} end)\n      |> Enum.map(fn {input, utxo} -> {Utxo.Position.to_input_db_key(input), Utxo.to_db_value(utxo)} end)\n\n    utxo_set = UtxoSet.init(db_query_result)\n\n    {:ok,\n     %{alice: alice, bob: bob, inputs: inputs, outputs: outputs, db_query_result: db_query_result, utxo_set: utxo_set}}\n  end\n\n  describe \"init/1\" do\n    test \"can initialize empty\", %{inputs: inputs} do\n      assert {:error, :utxo_not_found} =\n               []\n               |> UtxoSet.init()\n               |> UtxoSet.get_by_inputs(inputs)\n    end\n\n    test \"can initialize with db query result\", %{inputs: inputs, outputs: outputs, db_query_result: db_query_result} do\n      assert {:ok, ^outputs} =\n               db_query_result\n               |> UtxoSet.init()\n               |> UtxoSet.get_by_inputs(inputs)\n    end\n\n    test \"ignores not_founds from db query results\",\n         %{inputs: inputs, outputs: outputs, db_query_result: db_query_result} do\n      db_results_with_missings = Enum.intersperse(db_query_result, :not_found)\n\n      assert {:ok, ^outputs} =\n               db_results_with_missings\n               |> UtxoSet.init()\n               |> UtxoSet.get_by_inputs(inputs)\n    end\n  end\n\n  describe \"get_by_inputs/2\" do\n    test \"will get all by inputs in input order\", %{inputs: inputs, utxo_set: utxo_set} do\n      {:ok, result1} = UtxoSet.get_by_inputs(utxo_set, inputs)\n      assert {:ok, Enum.reverse(result1)} == UtxoSet.get_by_inputs(utxo_set, Enum.reverse(inputs))\n      assert {:ok, result1 ++ result1} == UtxoSet.get_by_inputs(utxo_set, inputs ++ inputs)\n    end\n\n    test \"will get for empty inputs\", %{utxo_set: utxo_set} do\n      assert {:ok, []} = UtxoSet.get_by_inputs(utxo_set, [])\n    end\n\n    test \"will get for subset of inputs\", %{inputs: [input | _], outputs: [output | _], utxo_set: utxo_set} do\n      assert {:ok, [^output]} = UtxoSet.get_by_inputs(utxo_set, [input])\n    end\n  end\n\n  describe \"apply_effects/3\" do\n    test \"will apply effects of spends\", %{inputs: [input1, input2 | _], outputs: [output1 | _], utxo_set: utxo_set} do\n      assert {:ok, [^output1]} =\n               utxo_set\n               |> UtxoSet.apply_effects([input2], %{})\n               |> UtxoSet.get_by_inputs([input1])\n\n      assert {:error, :utxo_not_found} =\n               utxo_set\n               |> UtxoSet.apply_effects([input2], %{})\n               |> UtxoSet.get_by_inputs([input2])\n    end\n\n    test \"will apply effects of new utxos being created\", %{inputs: [input | _], outputs: [output | _]} do\n      utxo_map = %{input => %Utxo{output: output, creating_txhash: <<1>>}}\n\n      assert {:ok, [^output]} =\n               [] |> UtxoSet.init() |> UtxoSet.apply_effects([], utxo_map) |> UtxoSet.get_by_inputs([input])\n    end\n\n    test \"will create first, spend second\", %{inputs: [input | _], outputs: [output | _]} do\n      # this would not happen now, since `apply_effects/3` is called per tx, which cannot spend it's own input\n      # nevertheless, let's make sure this is catered for on this level too\n      utxo_map = %{input => %Utxo{output: output, creating_txhash: <<1>>}}\n\n      assert {:error, :utxo_not_found} =\n               [] |> UtxoSet.init() |> UtxoSet.apply_effects([input], utxo_map) |> UtxoSet.get_by_inputs([input])\n    end\n  end\n\n  describe \"db_updates/2\" do\n    test \"will write to db, creating first, spending second\", %{inputs: [input | _], outputs: [output | _]} do\n      # this would not happen now, since `apply_effects/3` is called per tx, which cannot spend it's own input\n      # nevertheless, let's make sure this is catered for on this level too\n      utxo_map = %{input => %Utxo{output: output, creating_txhash: <<1>>}}\n\n      assert [{:put, :utxo, {key, _}}, {:delete, :utxo, key}] = UtxoSet.db_updates([input], utxo_map)\n    end\n  end\n\n  describe \"exists?/2\" do\n    test \"false if input absent\", %{inputs: [input | _]} do\n      refute [] |> UtxoSet.init() |> UtxoSet.exists?(input)\n    end\n\n    test \"true if present\", %{inputs: [input | _], utxo_set: utxo_set} do\n      assert UtxoSet.exists?(utxo_set, input)\n    end\n  end\n\n  describe \"find_matching_utxo/3\" do\n    test \"will find pair if matches\", %{inputs: [input | _], outputs: [output | _]} do\n      utxo_map = %{input => %Utxo{output: output, creating_txhash: <<1>>}}\n\n      assert hd(Map.to_list(utxo_map)) ==\n               [] |> UtxoSet.init() |> UtxoSet.apply_effects([], utxo_map) |> UtxoSet.find_matching_utxo(<<1>>, 0)\n    end\n\n    test \"won't find if none matches\", %{inputs: [input | _], outputs: [output | _]} do\n      utxo_map = %{input => %Utxo{output: output, creating_txhash: <<1>>}}\n      refute [] |> UtxoSet.init() |> UtxoSet.apply_effects([], utxo_map) |> UtxoSet.find_matching_utxo(<<1>>, 1)\n      refute [] |> UtxoSet.init() |> UtxoSet.find_matching_utxo(<<1>>, 0)\n    end\n  end\n\n  describe \"filter_owned_by/2\" do\n    test \"will find Bob's utxos\", %{bob: bob, utxo_set: utxo_set} do\n      assert [_, _] = UtxoSet.filter_owned_by(utxo_set, bob.addr) |> Enum.to_list()\n    end\n\n    test \"will NOT find Alice's utxos, b/c she doesn't have any\", %{alice: alice, utxo_set: utxo_set} do\n      assert [] = UtxoSet.filter_owned_by(utxo_set, alice.addr) |> Enum.to_list()\n    end\n  end\n\n  describe \"zip_with_positions/1\" do\n    test \"will zip all utxos with their positions\", %{utxo_set: utxo_set} do\n      # for now a trivial test case. When input pointers other than `utxo_pos` are used this becomes relevant\n      assert [{_, Utxo.position(1, 0, 0)}, {_, Utxo.position(2, 0, 0)}] =\n               UtxoSet.zip_with_positions(utxo_set) |> Enum.to_list()\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/state_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.StateTest do\n  @moduledoc \"\"\"\n  Smoke tests the imperative shell - runs a happy path on `OMG.Watcher.State`. Logic tested elsewhere\n  \"\"\"\n  use ExUnitFixtures\n  use ExUnit.Case, async: false\n\n  use OMG.DB.Fixtures\n\n  alias Ecto.Adapters.SQL.Sandbox\n  alias OMG.Eth.Configuration\n  alias OMG.Watcher.State\n  alias OMG.Watcher.TestHelper\n  alias OMG.Watcher.Utxo\n\n  require Utxo\n\n  @eth <<0::160>>\n  @fee_claimer_address Base.decode16!(\"DEAD000000000000000000000000000000000000\")\n\n  deffixture standalone_state_server(db_initialized) do\n    # match variables to hide \"unused var\" warnings (can't be fixed by underscoring in line above, breaks macro):\n    _ = db_initialized\n    # need to override that to very often, so that many checks fall in between a single child chain block submission\n    {:ok, started_apps} = Application.ensure_all_started(:omg_db)\n    # the pubsub is required, because `OMG.Watcher.State` is broadcasting to the `OMG.Bus`\n    {:ok, bus_apps} = Application.ensure_all_started(:omg_bus)\n\n    Application.ensure_all_started(:postgrex)\n    Application.ensure_all_started(:spandex_ecto)\n    Application.ensure_all_started(:ecto)\n\n    {:ok, _} =\n      Supervisor.start_link(\n        [\n          %{\n            id: OMG.WatcherInfo.DB.Repo,\n            start: {OMG.WatcherInfo.DB.Repo, :start_link, []},\n            type: :supervisor\n          }\n        ],\n        strategy: :one_for_one,\n        name: WatcherInfo.Supervisor\n      )\n\n    :ok = Sandbox.checkout(OMG.WatcherInfo.DB.Repo)\n    Sandbox.mode(OMG.WatcherInfo.DB.Repo, {:shared, self()})\n\n    on_exit(fn ->\n      (started_apps ++ bus_apps)\n      |> Enum.reverse()\n      |> Enum.map(fn app -> :ok = Application.stop(app) end)\n    end)\n\n    child_block_interval = Configuration.child_block_interval()\n    metrics_collection_interval = 60_000\n\n    {:ok, _} =\n      Supervisor.start_link(\n        [\n          {OMG.Watcher.State,\n           [\n             fee_claimer_address: @fee_claimer_address,\n             child_block_interval: child_block_interval,\n             metrics_collection_interval: metrics_collection_interval\n           ]}\n        ],\n        strategy: :one_for_one\n      )\n\n    :ok\n  end\n\n  @tag fixtures: [:alice, :standalone_state_server]\n  test \"can execute various calls on OMG.Watcher.State, one happy path only\", %{alice: alice} do\n    fee = %{@eth => [1]}\n\n    # deposits, transactions, utxo existence\n    assert {:ok, _} =\n             State.deposit([\n               %{\n                 owner: alice.addr,\n                 currency: @eth,\n                 amount: 10,\n                 blknum: 1,\n                 root_chain_txhash: <<1::256>>,\n                 eth_height: 1,\n                 log_index: 0\n               }\n             ])\n\n    assert true == State.utxo_exists?(Utxo.position(1, 0, 0))\n\n    assert {:ok, _} = State.exec(TestHelper.create_recovered([{1, 0, 0, alice}], @eth, [{alice, 9}]), fee)\n\n    # block forming & status\n    assert {blknum, _} = State.get_status()\n    # exits, with invalid ones\n    assert {:ok, _db, _} = State.exit_utxos([Utxo.position(blknum, 0, 0)])\n    # close block\n    assert {:ok, _db} = State.close_block()\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/supervisor_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\ndefmodule OMG.Watcher.SupervisorTest do\n  @moduledoc \"\"\"\n  This test is here mainly to test the logic-rich part of the supervisor setup, namely the config of\n  `OMG.Watcher.RootChainCoordinator.Core` supplied therein\n  \"\"\"\n  use ExUnit.Case, async: true\n\n  alias OMG.Watcher.RootChainCoordinator.Core\n\n  setup do\n    {_args, config_services} = OMG.Watcher.CoordinatorSetup.coordinator_setup(1, 1, 1, 1)\n    init = Core.init(config_services, 10)\n\n    pid =\n      config_services\n      |> Map.keys()\n      |> Enum.with_index(1)\n      |> Enum.into(%{}, fn {key, idx} -> {key, :c.pid(0, idx, 0)} end)\n\n    {:ok, %{state: initial_check_in(init, Map.keys(config_services), pid), pid: pid}}\n  end\n\n  test \"syncs services correctly\", %{state: state, pid: pid} do\n    # NOTE: this assumes some finality margins embedded in `config/test.exs`. Consider refactoring if these\n    #       needs to change and break this test, instead of modifying this test!\n\n    # start - only depositor and getter allowed to move\n    assert %{sync_height: 9, root_chain_height: 9} = Core.get_synced_info(state, pid[:depositor])\n    assert %{sync_height: 0, root_chain_height: 9} = Core.get_synced_info(state, pid[:exit_processor])\n    assert %{sync_height: 0, root_chain_height: 9} = Core.get_synced_info(state, pid[:in_flight_exit_processor])\n    assert %{sync_height: 1, root_chain_height: 10} = Core.get_synced_info(state, pid[:block_getter])\n\n    # depositor advances\n    assert {:ok, state} = Core.check_in(state, pid[:depositor], 9, :depositor)\n    assert %{sync_height: 9, root_chain_height: 9} = Core.get_synced_info(state, pid[:exit_processor])\n    assert %{sync_height: 9, root_chain_height: 9} = Core.get_synced_info(state, pid[:in_flight_exit_processor])\n\n    # exit_processor advances\n    assert %{sync_height: 0, root_chain_height: 9} = Core.get_synced_info(state, pid[:exit_challenger])\n    assert {:ok, state} = Core.check_in(state, pid[:exit_processor], 9, :exit_processor)\n    assert %{sync_height: 9, root_chain_height: 9} = Core.get_synced_info(state, pid[:exit_challenger])\n\n    # in flights advance\n    assert %{sync_height: 0, root_chain_height: 9} = Core.get_synced_info(state, pid[:piggyback_processor])\n    assert %{sync_height: 0, root_chain_height: 9} = Core.get_synced_info(state, pid[:competitor_processor])\n    assert {:ok, state} = Core.check_in(state, pid[:in_flight_exit_processor], 9, :in_flight_exit_processor)\n    assert %{sync_height: 9, root_chain_height: 9} = Core.get_synced_info(state, pid[:piggyback_processor])\n    assert %{sync_height: 9, root_chain_height: 9} = Core.get_synced_info(state, pid[:competitor_processor])\n\n    assert %{sync_height: 0, root_chain_height: 9} = Core.get_synced_info(state, pid[:piggyback_challenges_processor])\n    assert {:ok, state} = Core.check_in(state, pid[:piggyback_processor], 9, :piggyback_processor)\n    assert %{sync_height: 9, root_chain_height: 9} = Core.get_synced_info(state, pid[:piggyback_challenges_processor])\n\n    assert %{sync_height: 0, root_chain_height: 9} = Core.get_synced_info(state, pid[:challenges_responds_processor])\n    assert {:ok, state} = Core.check_in(state, pid[:competitor_processor], 9, :competitor_processor)\n    assert %{sync_height: 9, root_chain_height: 9} = Core.get_synced_info(state, pid[:challenges_responds_processor])\n\n    # BlockGetter advances\n    assert %{sync_height: 0, root_chain_height: 9} = Core.get_synced_info(state, pid[:exit_finalizer])\n    assert %{sync_height: 0, root_chain_height: 9} = Core.get_synced_info(state, pid[:ife_exit_finalizer])\n    assert {:ok, state} = Core.check_in(state, pid[:block_getter], 10, :block_getter)\n    assert %{sync_height: 9, root_chain_height: 9} = Core.get_synced_info(state, pid[:exit_finalizer])\n    assert %{sync_height: 9, root_chain_height: 9} = Core.get_synced_info(state, pid[:ife_exit_finalizer])\n\n    # root chain advances\n    assert {:ok, state} = Core.update_root_chain_height(state, 100)\n    assert %{sync_height: 9, root_chain_height: 99} = Core.get_synced_info(state, pid[:exit_finalizer])\n    assert %{sync_height: 9, root_chain_height: 99} = Core.get_synced_info(state, pid[:ife_exit_finalizer])\n    assert %{sync_height: 99, root_chain_height: 99} = Core.get_synced_info(state, pid[:depositor])\n    assert %{sync_height: 10, root_chain_height: 100} = Core.get_synced_info(state, pid[:block_getter])\n  end\n\n  defp initial_check_in(state, services, pid) do\n    {:ok, state} =\n      Enum.reduce(services, {:ok, state}, fn service, {:ok, state} -> Core.check_in(state, pid[service], 0, service) end)\n\n    state\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/typed_data_hash_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.TypedDataHashTest do\n  @moduledoc \"\"\"\n  Idea behind testing functionality like this (which produces random byte-strings) is 4-tiered test suite.\n  * tier 1: acknowledged third party (Metamask) signatures we can verify (recover address from)\n  * tier 2: final structural hash on prepared transaction that gives the same signatures as above\n  * tier 3: intermediate results of hashing (domain separator, structural hashes of inputs & outputs)\n  * tier 4: end-to-end test of generating signatures in elixir code and verifying them in solidity library (\n    done in `OMG.Watcher.DependencyConformance.SignatureTest`)\n  \"\"\"\n\n  use ExUnitFixtures\n  use ExUnit.Case, async: true\n\n  alias OMG.Watcher.Crypto\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.TypedDataHash\n  alias OMG.Watcher.TypedDataHash.Tools\n  alias OMG.Watcher.Utxo\n\n  require Utxo\n  require OMG.Watcher.TypedDataHash.Tools\n\n  @test_domain_separator Tools.domain_separator(%{\n                           name: \"OMG Network\",\n                           version: \"1\",\n                           verifyingContract: Base.decode16!(\"44de0ec539b8c4a4b530c78620fe8320167f2f74\", case: :mixed),\n                           salt:\n                             Base.decode16!(\"fad5c7f626d80f9256ef01929f3beb96e058b8b4b0e3fe52d84f054c0e2a7a83\",\n                               case: :mixed\n                             )\n                         })\n\n  setup_all do\n    null_addr = <<0::160>>\n    owner = Base.decode16!(\"2258a5279850f6fb78888a7e45ea2a5eb1b3c436\", case: :mixed)\n    token = Base.decode16!(\"0123456789abcdef000000000000000000000000\", case: :mixed)\n\n    {:ok,\n     %{\n       inputs: [\n         {1, 0, 0},\n         {1000, 2, 3},\n         {101_000, 1337, 3}\n       ],\n       outputs: [\n         {owner, null_addr, 100},\n         {token, null_addr, 111},\n         {owner, token, 1337},\n         {null_addr, null_addr, 0}\n       ],\n       metadata: Base.decode16!(\"853a8d8af99c93405a791b97d57e819e538b06ffaa32ad70da2582500bc18d43\", case: :mixed)\n     }}\n  end\n\n  describe \"Compliance with contract code\" do\n    test \"EIP domain type is encoded correctly\" do\n      eip_domain = \"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract,bytes32 salt)\"\n      expected_hash = \"d87cd6ef79d4e2b95e15ce8abf732db51ec771f1ca2edccf22a46c729ac56472\"\n\n      assert expected_hash == eip_domain |> Crypto.hash() |> Base.encode16(case: :lower)\n    end\n\n    test \"Input type hash is computed correctly\" do\n      expected = \"5f0e06e50b513a68a090818949172483acfec769d9b7756cad7c00b26b52178c\"\n\n      assert expected ==\n               \"Input(uint256 blknum,uint256 txindex,uint256 oindex)\" |> Crypto.hash() |> Base.encode16(case: :lower)\n    end\n\n    test \"Output type hash is computed correctly\" do\n      expected = \"9fd642c2bbaa2f3431add55df5d3932807048fb41b6b07d65c59e0f9ad3a8eb7\"\n\n      assert expected ==\n               \"Output(uint256 outputType,bytes20 outputGuard,address currency,uint256 amount)\"\n               |> Crypto.hash()\n               |> Base.encode16(case: :lower)\n    end\n\n    test \"Transaction type hash is computed correctly\" do\n      expected = \"186aebaa7ec9e4abef44830c07670c034d8efb44e91542dc63df2f65205e61cc\"\n\n      full_type =\n        \"Transaction(\" <>\n          \"Input input0,Input input1,Input input2,Input input3,\" <>\n          \"Output output0,Output output1,Output output2,Output output3,\" <>\n          \"uint256 txdata,bytes32 metadata)\" <>\n          \"Input(uint256 blknum,uint256 txindex,uint256 oindex)\" <>\n          \"Output(uint256 outputType,bytes20 outputGuard,address currency,uint256 amount)\"\n\n      assert expected == full_type |> Crypto.hash() |> Base.encode16(case: :lower)\n    end\n\n    test \"domain separator is computed correctly\" do\n      expected = \"b542beb7bafc6796b8439716a4e460a2634ac432216cebc524e54f8789e2924c\"\n\n      assert expected == Base.encode16(@test_domain_separator, case: :lower)\n    end\n\n    test \"Input is hashed properly\" do\n      assert \"1a5933eb0b3223b0500fbbe7039cab9badc006adda6cf3d337751412fd7a4b61\" ==\n               Utxo.position(0, 0, 0) |> Tools.hash_input() |> Base.encode16(case: :lower)\n\n      assert \"7377afcd24fdc685fd8c6ea2b5d15a74f2c898c3d5bcce3499f448a4d68db290\" ==\n               Utxo.position(1, 0, 0) |> Tools.hash_input() |> Base.encode16(case: :lower)\n\n      assert \"c198a0ab9b12c3f225195cf0f7870c7ab12c316b33eb99771dfd0f3f7da455a5\" ==\n               Utxo.position(101_000, 1337, 3) |> Tools.hash_input() |> Base.encode16(case: :lower)\n    end\n\n    test \"Output is hashed properly\", %{outputs: [output1, output2, output3, output4]} do\n      to_output = fn {owner, currency, amount} ->\n        [output] = Transaction.get_outputs(Transaction.Payment.new([], [{owner, currency, amount}]))\n\n        output\n      end\n\n      assert \"4b85fe2caac41f533c3d3b56ec75ca3363d0205e4dde63ca16b0d377fa79364d\" ==\n               to_output.(output1) |> Tools.hash_output() |> Base.encode16(case: :lower)\n\n      assert \"27962e5f1453285204261a3b2fe420be5ee504f3606d857e5c3120e1fc7aac3f\" ==\n               to_output.(output2) |> Tools.hash_output() |> Base.encode16(case: :lower)\n\n      assert \"257ce332ccd9571fb364f8abd0b22ca53cd3d7e4ba9a14fd208cdf25caf8854f\" ==\n               to_output.(output3) |> Tools.hash_output() |> Base.encode16(case: :lower)\n\n      assert \"168031cd8ed05efce595276a59045cabf7a33d14a4dcad1ea16fdd0c98ad7598\" ==\n               to_output.(output4) |> Tools.hash_output() |> Base.encode16(case: :lower)\n    end\n\n    test \"Metadata is hashed properly\", %{metadata: metadata} do\n      empty = <<0::256>>\n\n      assert \"290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563\" ==\n               empty |> Crypto.hash() |> Base.encode16(case: :lower)\n\n      assert \"f32aecc93539658c0e9f102ad05b1f37ec4692366142955451b7e432f59a513a\" ==\n               metadata |> Crypto.hash() |> Base.encode16(case: :lower)\n    end\n\n    test \"Transaction is hashed correctly\", %{inputs: inputs, outputs: outputs, metadata: metadata} do\n      assert \"3f5b24d7cf1db32c34ae2921a3537b7af40ad7e787fb7e8e03f88715a861dfe7\" ==\n               Transaction.Payment.new([], []) |> TypedDataHash.hash_transaction() |> Base.encode16(case: :lower)\n\n      assert \"d5fb24437003566da84b8948fde09c367bbf93da39cdd23390ecaa98e3054f2d\" ==\n               Transaction.Payment.new(inputs, outputs)\n               |> TypedDataHash.hash_transaction()\n               |> Base.encode16(case: :lower)\n\n      assert \"7c3f89120b00c4b1ca433811b544e8177f109c5a4ca27ff434e08b02d66e77f4\" ==\n               Transaction.Payment.new(inputs, outputs, metadata)\n               |> TypedDataHash.hash_transaction()\n               |> Base.encode16(case: :lower)\n    end\n\n    test \"Structured hash is computed correctly\", %{inputs: inputs, outputs: outputs, metadata: metadata} do\n      assert \"47f83702c496c7ebb6ec639cb11d6c8b81eb64f6d818cb087e3ed2cb92ccf1ae\" ==\n               Transaction.Payment.new([], [])\n               |> TypedDataHash.hash_struct(@test_domain_separator)\n               |> Base.encode16(case: :lower)\n\n      assert \"aeaa272f4460436415f377cb0cefe8f2646f4457f60827519a7edf86d30c0bf0\" ==\n               Transaction.Payment.new(inputs, outputs)\n               |> TypedDataHash.hash_struct(@test_domain_separator)\n               |> Base.encode16(case: :lower)\n\n      assert \"e1fcd0b07d8034ac039c15c544436a95e92879689a456604cbc0e8420e6e342a\" ==\n               Transaction.Payment.new(inputs, outputs, metadata)\n               |> TypedDataHash.hash_struct(@test_domain_separator)\n               |> Base.encode16(case: :lower)\n    end\n  end\n\n  describe \"Eip-712 types\" do\n    test \"align with encodeType format\" do\n      assert \"EIP712Domain(string name,string version,address verifyingContract,bytes32 salt)\" ==\n               TypedDataHash.Types.encode_type(:EIP712Domain)\n\n      assert \"Transaction(\" <>\n               \"uint256 txType,\" <>\n               \"Input input0,Input input1,Input input2,Input input3,\" <>\n               \"Output output0,Output output1,Output output2,Output output3,\" <>\n               \"uint256 txData,bytes32 metadata)\" ==\n               TypedDataHash.Types.encode_type(:Transaction)\n\n      assert \"Input(uint256 blknum,uint256 txindex,uint256 oindex)\" ==\n               TypedDataHash.Types.encode_type(:Input)\n\n      assert \"Output(uint256 outputType,bytes20 outputGuard,address currency,uint256 amount)\" ==\n               TypedDataHash.Types.encode_type(:Output)\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/utxo/position_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.Utxo.PositionTest do\n  @moduledoc false\n\n  use ExUnit.Case, async: true\n  doctest OMG.Watcher.Utxo.Position\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/utxo_exit/core_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.UtxoExit.CoreTest do\n  use ExUnitFixtures\n  use ExUnit.Case, async: true\n\n  alias OMG.Watcher.Block\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.TestHelper\n  alias OMG.Watcher.Utxo\n  alias OMG.Watcher.Utxo.Position\n  alias OMG.Watcher.UtxoExit.Core\n  require OMG.Watcher.Utxo\n\n  @eth <<0::160>>\n\n  setup do\n    alice = TestHelper.generate_entity()\n    %{alice: alice}\n  end\n\n  describe \"compose_deposit_standard_exit/1\" do\n    test \"creates deposit exit\", %{alice: alice} do\n      position = Utxo.position(1003, 0, 0)\n      encode_utxo = position |> Utxo.Position.encode()\n\n      [output] = Transaction.get_outputs(Transaction.Payment.new([], [{alice.addr, @eth, 10}]))\n      fake_utxo_db_kv = {Position.to_input_db_key(position), Utxo.to_db_value(%Utxo{output: output})}\n\n      assert {:ok,\n              %{\n                utxo_pos: ^encode_utxo,\n                txbytes: txbytes,\n                proof: proof\n              }} = Core.compose_deposit_standard_exit({:ok, fake_utxo_db_kv})\n\n      assert [%{amount: 10}] = txbytes |> Transaction.decode!() |> Transaction.get_outputs()\n      assert byte_size(proof) == 32 * 16\n    end\n\n    test \"fails to create deposit exit when UTXO missing in DB\" do\n      assert {:error, :no_deposit_for_given_blknum} = Core.compose_deposit_standard_exit(:not_found)\n    end\n  end\n\n  describe \"compose_utxo_exit/2\" do\n    test \"composes output exit from tx inside a block\", %{alice: alice} do\n      blknum = 4000\n      tx_exit = TestHelper.create_recovered([{1_000, 1, 0, alice}], @eth, [{alice, 10}])\n      tx_exit_raw_tx_bytes = Transaction.raw_txbytes(tx_exit)\n      position = Utxo.position(blknum, 1, 0)\n      encode_utxo = position |> Utxo.Position.encode()\n\n      block =\n        [\n          TestHelper.create_recovered([{1_000, 2, 0, alice}], @eth, [{alice, 10}]),\n          tx_exit,\n          TestHelper.create_recovered([{1_000, 3, 0, alice}], @eth, [{alice, 10}])\n        ]\n        |> Block.hashed_txs_at(blknum)\n        |> Block.to_db_value()\n\n      assert {:ok,\n              %{\n                proof: proof,\n                txbytes: ^tx_exit_raw_tx_bytes,\n                utxo_pos: ^encode_utxo\n              }} = Core.compose_block_standard_exit(block, position)\n\n      # hash byte_size * merkle tree depth\n      assert byte_size(proof) == 32 * 16\n    end\n\n    test \"doesn't find utxo for the output exit, tx position exceeding the block tx count\", %{alice: alice} do\n      blknum = 4000\n      position = Utxo.position(blknum, 1, 0)\n\n      block =\n        [TestHelper.create_recovered([{1_000, 1, 0, alice}], @eth, [{alice, 10}])]\n        |> Block.hashed_txs_at(blknum)\n        |> Block.to_db_value()\n\n      assert {:error, :utxo_not_found} = Core.compose_block_standard_exit(block, position)\n    end\n\n    test \"doesn't find utxo for the output exit, output position exceeding the output count\", %{alice: alice} do\n      blknum = 4000\n      position = Utxo.position(blknum, 0, 3)\n\n      block =\n        [TestHelper.create_recovered([{1_000, 1, 0, alice}], @eth, [{alice, 10}])]\n        |> Block.hashed_txs_at(blknum)\n        |> Block.to_db_value()\n\n      assert {:error, :utxo_not_found} = Core.compose_block_standard_exit(block, position)\n    end\n\n    test \"throws when composing output exit, mismatch blknum and utxo pos (should never occur)\", %{alice: alice} do\n      position = Utxo.position(3000, 0, 0)\n\n      block =\n        [TestHelper.create_recovered([{1_000, 1, 0, alice}], @eth, [{alice, 10}])]\n        |> Block.hashed_txs_at(4000)\n        |> Block.to_db_value()\n\n      assert_raise MatchError, fn -> Core.compose_block_standard_exit(block, position) end\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/utxo_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\ndefmodule OMG.Watcher.UtxoTest do\n  @moduledoc false\n  use ExUnit.Case, async: true\n  doctest OMG.Watcher.Utxo\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/omg_watcher/wire_format_types_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.WireFormatTypesTest do\n  use ExUnitFixtures\n  use ExUnit.Case, async: true\n\n  alias OMG.Watcher.WireFormatTypes\n\n  describe \"tx_type_for/1\" do\n    test \"returns the tx type for the given atom\" do\n      assert WireFormatTypes.tx_type_for(:tx_payment_v1) == 1\n    end\n  end\n\n  describe \"input_pointer_type_for/1\" do\n    test \"returns the input type for the given input\" do\n      assert WireFormatTypes.input_pointer_type_for(:input_pointer_utxo_position) == 1\n    end\n  end\n\n  describe \"output_type_for/1\" do\n    test \"returns the output type for the given output\" do\n      assert WireFormatTypes.output_type_for(:output_payment_v1) == 1\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/support/dev_crypto.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.DevCrypto do\n  @moduledoc \"\"\"\n  Non-production crypto code like:\n    - anything that touches private keys\n  \"\"\"\n\n  alias OMG.Watcher.Crypto\n  alias OMG.Watcher.SignatureHelper\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.TypedDataHash\n\n  @doc \"\"\"\n  Generates private key. Internally uses OpenSSL RAND_bytes. May throw if there is not enough entropy.\n  \"\"\"\n  @spec generate_private_key() :: {:ok, Crypto.priv_key_t()}\n  def generate_private_key(), do: {:ok, :crypto.strong_rand_bytes(32)}\n\n  @doc \"\"\"\n  Given a private key, returns public key.\n  \"\"\"\n  @spec generate_public_key(Crypto.priv_key_t()) :: {:ok, Crypto.pub_key_t()}\n  def generate_public_key(<<priv::binary-size(32)>>) do\n    {:ok, der_pub} = get_public_key(priv)\n    {:ok, der_to_raw(der_pub)}\n  end\n\n  @doc \"\"\"\n    Signs transaction using private keys\n\n    private keys are in the  binary form, e.g.:\n    ```<<54, 43, 207, 67, 140, 160, 190, 135, 18, 162, 70, 120, 36, 245, 106, 165, 5, 101, 183,\n      55, 11, 117, 126, 135, 49, 50, 12, 228, 173, 219, 183, 175>>```\n  \"\"\"\n  @spec sign(Transaction.Protocol.t(), list(Crypto.priv_key_t())) :: Transaction.Signed.t()\n  def sign(%{} = tx, private_keys) do\n    sigs = Enum.map(private_keys, fn pk -> signature(tx, pk) end)\n    %Transaction.Signed{raw_tx: tx, sigs: sigs}\n  end\n\n  @doc \"\"\"\n  Produces a stand-alone, 65 bytes long, signature for message hash.\n  \"\"\"\n  @spec signature_digest(<<_::256>>, <<_::256>>) :: <<_::520>>\n  def signature_digest(digest, priv) when is_binary(digest) and byte_size(digest) == 32 do\n    {v, r, s} = SignatureHelper.sign_hash(digest, priv)\n    pack_signature(v, r, s)\n  end\n\n  @doc \"\"\"\n  Produces a stand-alone, 65 bytes long, signature for a given transaction.\n  \"\"\"\n  @spec signature(Transaction.Protocol.t(), Crypto.priv_key_t()) :: Crypto.sig_t()\n  def signature(tx, priv), do: do_signature(tx, priv)\n\n  defp do_signature(%{} = tx, priv) do\n    tx\n    |> TypedDataHash.hash_struct()\n    |> signature_digest(priv)\n  end\n\n  # Pack a {v,r,s} signature as 65-bytes binary.\n  defp pack_signature(v, r, s) do\n    <<r::integer-size(256), s::integer-size(256), v::integer-size(8)>>\n  end\n\n  defp der_to_raw(<<4::integer-size(8), data::binary>>), do: data\n\n  defp get_public_key(private_key) do\n    case ExSecp256k1.create_public_key(private_key) do\n      {:ok, public_key} -> {:ok, public_key}\n      {:error, reason} -> {:error, to_string(reason)}\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/support/exit_processor/case.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.ExitProcessor.Case do\n  @moduledoc \"\"\"\n  `ExUnit` test case for a shared setup used in `ExitProcessor.Core` logic tests\n  \"\"\"\n  use ExUnit.CaseTemplate\n\n  alias OMG.Watcher.Block\n  alias OMG.Watcher.ExitProcessor\n  alias OMG.Watcher.ExitProcessor.Core\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.TestHelper\n  alias OMG.Watcher.Utxo\n\n  require Utxo\n\n  import OMG.Watcher.ExitProcessor.TestHelper\n\n  @default_min_exit_period_seconds 120\n  @default_child_block_interval 1000\n\n  @eth <<0::160>>\n  @not_eth <<1::size(160)>>\n\n  setup do\n    [alice, bob, carol] = 1..3 |> Enum.map(fn _ -> TestHelper.generate_entity() end)\n\n    transactions = [\n      TestHelper.create_recovered([{1, 0, 0, alice}, {1, 2, 1, carol}], [{alice, @eth, 1}, {carol, @eth, 2}]),\n      TestHelper.create_recovered([{2, 1, 0, alice}, {2, 2, 1, carol}], [{alice, @not_eth, 1}, {carol, @not_eth, 2}])\n    ]\n\n    competing_tx =\n      TestHelper.create_recovered([{10, 2, 1, alice}, {1, 0, 0, alice}], [{bob, @eth, 2}, {carol, @eth, 1}])\n\n    unrelated_tx =\n      TestHelper.create_recovered([{20, 1, 0, alice}, {20, 20, 1, alice}], [{bob, @eth, 2}, {carol, @eth, 1}])\n\n    {:ok, processor_empty} = Core.init([], [], [], @default_min_exit_period_seconds, @default_child_block_interval)\n\n    in_flight_exit_events =\n      transactions |> Enum.zip([2, 4]) |> Enum.map(fn {tx, eth_height} -> ife_event(tx, eth_height: eth_height) end)\n\n    ife_tx_hashes = transactions |> Enum.map(&Transaction.raw_txhash/1)\n\n    processor_filled =\n      transactions\n      |> Enum.zip([1, 4])\n      |> Enum.reduce(processor_empty, fn {tx, idx}, processor ->\n        # use the idx as both two distinct ethereum heights and two distinct exit_ids arriving from the root chain\n        start_ife_from(processor, tx, eth_height: idx, exit_id: idx)\n      end)\n\n    {:ok,\n     %{\n       alice: alice,\n       bob: bob,\n       carol: carol,\n       transactions: transactions,\n       competing_tx: competing_tx,\n       unrelated_tx: unrelated_tx,\n       processor_empty: processor_empty,\n       in_flight_exit_events: in_flight_exit_events,\n       ife_tx_hashes: ife_tx_hashes,\n       processor_filled: processor_filled,\n       invalid_piggyback_on_input:\n         invalid_piggyback_on_input(processor_filled, transactions, ife_tx_hashes, competing_tx),\n       invalid_piggyback_on_output: invalid_piggyback_on_output(alice, processor_filled, transactions, ife_tx_hashes)\n     }}\n  end\n\n  defp invalid_piggyback_on_input(state, [tx | _], [ife_id | _], competing_tx) do\n    request = %ExitProcessor.Request{\n      blknum_now: 4000,\n      eth_height_now: 5,\n      ife_input_spending_blocks_result: [Block.hashed_txs_at([tx], 3000)]\n    }\n\n    state =\n      state\n      |> start_ife_from(competing_tx)\n      |> piggyback_ife_from(ife_id, 0, :input)\n      |> Core.find_ifes_in_blocks(request)\n\n    %{\n      state: state,\n      request: request,\n      ife_input_index: 0,\n      ife_txbytes: txbytes(tx),\n      spending_txbytes: txbytes(competing_tx),\n      spending_input_index: 1,\n      spending_sig: sig(competing_tx)\n    }\n  end\n\n  defp invalid_piggyback_on_output(alice, state, [tx | _], [ife_id | _]) do\n    # the piggybacked-output-spending tx is going to be included in a block, which requires more back&forth\n    # 1. transaction which is, ife'd, output piggybacked, and included in a block\n    # 2. transaction which spends that piggybacked output\n    comp = TestHelper.create_recovered([{3000, 0, 0, alice}], [{alice, @eth, 1}])\n\n    tx_blknum = 3000\n    comp_blknum = 4000\n    block = Block.hashed_txs_at([tx], tx_blknum)\n\n    request = %ExitProcessor.Request{\n      blknum_now: 5000,\n      eth_height_now: 5,\n      blocks_result: [block],\n      ife_input_spending_blocks_result: [block, Block.hashed_txs_at([comp], comp_blknum)]\n    }\n\n    # 3. stuff happens in the contract; output #4 is a double-spend; #5 is OK\n    state =\n      state\n      |> piggyback_ife_from(ife_id, 0, :output)\n      |> piggyback_ife_from(ife_id, 1, :output)\n      |> Core.find_ifes_in_blocks(request)\n\n    %{\n      state: state,\n      request: request,\n      ife_good_pb_index: 5,\n      ife_txbytes: txbytes(tx),\n      ife_output_pos: Utxo.position(tx_blknum, 0, 0),\n      ife_proof: Block.inclusion_proof(block, 0),\n      spending_txbytes: txbytes(comp),\n      spending_input_index: 0,\n      spending_sig: sig(comp),\n      ife_input_index: 4\n    }\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/support/exit_processor/test_helper.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.ExitProcessor.TestHelper do\n  @moduledoc \"\"\"\n  Common utilities to manipulate the `ExitProcessor`\n  \"\"\"\n\n  import ExUnit.Assertions\n\n  alias OMG.Watcher.ExitProcessor.Core\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.Utxo\n\n  require Utxo\n\n  # default exit_id used when starting exits using `start_se_from` and `start_ife_from`\n  @exit_id 9876\n\n  def start_se_from(%Core{} = processor, tx, exiting_pos, opts \\\\ []) do\n    {event, status} = se_event_status(tx, exiting_pos, opts)\n    {processor, _} = Core.new_exits(processor, [event], [status])\n    processor\n  end\n\n  def se_event_status(tx, exiting_pos, opts \\\\ []) do\n    Utxo.position(_, _, oindex) = exiting_pos\n    txbytes = Transaction.raw_txbytes(tx)\n    enc_pos = Utxo.Position.encode(exiting_pos)\n    owner = tx |> Transaction.get_outputs() |> Enum.at(oindex) |> Map.get(:owner)\n    eth_height = Keyword.get(opts, :eth_height, 2)\n    exit_id = Keyword.get(opts, :exit_id, @exit_id)\n    call_data = %{utxo_pos: enc_pos, output_tx: txbytes}\n    root_chain_txhash = <<1::256>>\n    block_timestamp = :os.system_time(:second)\n    scheduled_finalization_time = block_timestamp + 100\n\n    event = %{\n      owner: owner,\n      eth_height: eth_height,\n      exit_id: exit_id,\n      call_data: call_data,\n      root_chain_txhash: root_chain_txhash,\n      block_timestamp: block_timestamp,\n      scheduled_finalization_time: scheduled_finalization_time\n    }\n\n    exitable = not Keyword.get(opts, :inactive, false)\n    # those should be unused so setting to `nil`\n    fake_output_id = enc_pos\n    amount = nil\n    bond_size = nil\n\n    status = Keyword.get(opts, :status) || {exitable, enc_pos, fake_output_id, owner, amount, bond_size}\n\n    {event, status}\n  end\n\n  def start_ife_from(%Core{} = processor, tx, opts \\\\ []) do\n    exit_id = Keyword.get(opts, :exit_id, @exit_id)\n    status = Keyword.get(opts, :status, active_ife_status())\n    status = if status == :inactive, do: inactive_ife_status(), else: status\n\n    {processor, _} = Core.new_in_flight_exits(processor, [ife_event(tx, opts)], [{status, exit_id}])\n\n    processor\n  end\n\n  # See `OMG.Eth.RootChain.get_in_flight_exits_structs/2` for reference of where this comes from\n  # `nil`s are unused portions of the returns data from the contract\n  def active_ife_status(), do: {nil, 1, nil, nil, nil, nil, nil}\n  def inactive_ife_status(), do: {nil, 0, nil, nil, nil, nil, nil}\n\n  def piggyback_ife_from(%Core{} = processor, tx_hash, output_index, piggyback_type) do\n    {processor, _} =\n      Core.new_piggybacks(processor, [\n        %{\n          tx_hash: tx_hash,\n          output_index: output_index,\n          omg_data: %{piggyback_type: piggyback_type}\n        }\n      ])\n\n    processor\n  end\n\n  def ife_event(tx, opts \\\\ []) do\n    sigs = Keyword.get(opts, :sigs) || sigs(tx)\n    input_utxos_pos = Transaction.get_inputs(tx) |> Enum.map(&Utxo.Position.encode/1)\n\n    input_txs = Keyword.get(opts, :input_txs) || List.duplicate(\"input_tx\", length(input_utxos_pos))\n\n    eth_height = Keyword.get(opts, :eth_height, 2)\n\n    %{\n      call_data: %{\n        in_flight_tx: Transaction.raw_txbytes(tx),\n        input_txs: input_txs,\n        input_utxos_pos: input_utxos_pos,\n        in_flight_tx_sigs: sigs\n      },\n      eth_height: eth_height\n    }\n  end\n\n  def ife_response(tx, position),\n    do: %{tx_hash: Transaction.raw_txhash(tx), challenge_position: Utxo.Position.encode(position)}\n\n  def ife_challenge(tx, comp, opts \\\\ []) do\n    competitor_position = Keyword.get(opts, :competitor_position)\n\n    competitor_position =\n      if competitor_position,\n        do: Utxo.Position.encode(competitor_position),\n        else: not_included_competitor_pos()\n\n    %{\n      tx_hash: Transaction.raw_txhash(tx),\n      competitor_position: competitor_position,\n      call_data: %{\n        competing_tx: txbytes(comp),\n        competing_tx_input_index: Keyword.get(opts, :competing_tx_input_index, 0),\n        competing_tx_sig: Keyword.get(opts, :competing_tx_sig, sig(comp))\n      }\n    }\n  end\n\n  def txbytes(tx), do: Transaction.raw_txbytes(tx)\n  def sigs(tx), do: tx.signed_tx.sigs\n  def sig(tx, idx \\\\ 0), do: tx |> sigs() |> Enum.at(idx)\n\n  def assert_proof_sound(proof_bytes) do\n    # NOTE: checking of actual proof working up to the contract integration test\n    assert is_binary(proof_bytes)\n    # hash size * merkle tree depth\n    assert byte_size(proof_bytes) == 32 * 16\n  end\n\n  def assert_events(events, expected_events) do\n    assert MapSet.new(events) == MapSet.new(expected_events)\n  end\n\n  def check_validity_filtered(request, processor, opts) do\n    exclude_events = Keyword.get(opts, :exclude, [])\n    only_events = Keyword.get(opts, :only, [])\n\n    {result, events} = Core.check_validity(request, processor)\n\n    any? = fn filtering_events, event ->\n      Enum.any?(filtering_events, fn filtering_event -> event.__struct__ == filtering_event end)\n    end\n\n    filtered_events =\n      events\n      |> Enum.filter(fn event ->\n        Enum.empty?(exclude_events) or not any?.(exclude_events, event)\n      end)\n      |> Enum.filter(fn event ->\n        Enum.empty?(only_events) or any?.(only_events, event)\n      end)\n\n    {result, filtered_events}\n  end\n\n  defp not_included_competitor_pos() do\n    <<long::256>> =\n      List.duplicate(<<255::8>>, 32)\n      |> Enum.reduce(fn val, acc -> val <> acc end)\n\n    long\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/support/integration/bad_child_chain_server.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.Integration.BadChildChainServer do\n  @moduledoc \"\"\"\n    Module useful for creating integration tests where we want to simulate byzantine child chain server\n    which is returning a bad block for a particular block hash.\n  \"\"\"\n\n  alias OMG.Utils.HttpRPC.Encoding\n  alias OMG.Utils.HttpRPC.Response\n  alias OMG.Watcher.Block\n  alias OMG.Watcher.HttpRPC.Adapter\n  alias OMG.Watcher.Integration.TestServer\n\n  @doc \"\"\"\n  Adds a route to TestServer which responded with prepared bad block when asked for known hash\n  all other requests are redirected to `real` Child Chain API\n  \"\"\"\n  def prepare_route_to_inject_bad_block(context, bad_block, bad_block_hash) do\n    TestServer.with_route(\n      context,\n      \"/block.get\",\n      fn %{body: %{\"hash\" => req_hash}} ->\n        if {:ok, bad_block_hash} == Encoding.from_hex(req_hash) do\n          bad_block\n          |> Block.to_api_format()\n          |> Response.sanitize()\n          |> TestServer.make_response()\n        else\n          {:ok, block} =\n            %{hash: req_hash}\n            |> Adapter.rpc_post(\"block.get\", context.real_addr)\n            |> Adapter.get_response_body()\n\n          TestServer.make_response(block)\n        end\n      end\n    )\n  end\n\n  @doc \"\"\"\n  Version of `prepare_route_to_inject_bad_block/3` when we want to serve the block under it's real hash\n  \"\"\"\n  def prepare_route_to_inject_bad_block(context, %{hash: bad_block_hash} = bad_block) do\n    prepare_route_to_inject_bad_block(context, bad_block, bad_block_hash)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/support/integration/deposit_helper.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule Support.Integration.DepositHelper do\n  @moduledoc \"\"\"\n  Common helper functions that are useful when integration-testing the child chain and watcher requiring deposits\n  \"\"\"\n\n  alias OMG.Eth.Encoding\n  alias OMG.Eth.Token\n  alias OMG.Watcher.Configuration\n  alias OMG.Watcher.State.Transaction\n  alias Support.DevHelper\n  alias Support.RootChainHelper\n\n  @eth <<0::160>>\n\n  def deposit_to_child_chain(to, value, token \\\\ @eth)\n\n  def deposit_to_child_chain(to, value, @eth) do\n    {:ok, receipt} =\n      Transaction.Payment.new([], [{to, @eth, value}])\n      |> Transaction.raw_txbytes()\n      |> RootChainHelper.deposit(value, to)\n      |> DevHelper.transact_sync!()\n\n    process_deposit(receipt)\n  end\n\n  def deposit_to_child_chain(to, value, token_addr) when is_binary(token_addr) and byte_size(token_addr) == 20 do\n    contract_addr = Encoding.from_hex(OMG.Eth.Configuration.contracts().erc20_vault)\n\n    {:ok, _} = DevHelper.transact_sync!(Token.approve(to, contract_addr, value, token_addr))\n\n    {:ok, receipt} =\n      Transaction.Payment.new([], [{to, token_addr, value}])\n      |> Transaction.raw_txbytes()\n      |> RootChainHelper.deposit_from(to)\n      |> DevHelper.transact_sync!()\n\n    process_deposit(receipt)\n  end\n\n  defp process_deposit(%{\"blockNumber\" => deposit_eth_height} = receipt) do\n    _ = wait_deposit_recognized(deposit_eth_height)\n    RootChainHelper.deposit_blknum_from_receipt(receipt)\n  end\n\n  defp wait_deposit_recognized(deposit_eth_height) do\n    post_event_block_finality = deposit_eth_height + Configuration.deposit_finality_margin()\n    {:ok, _} = DevHelper.wait_for_root_chain_block(post_event_block_finality + 1)\n    # sleeping until the deposit is spendable\n    Process.sleep(Application.fetch_env!(:omg_watcher, :ethereum_events_check_interval_ms) * 2)\n    :ok\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/support/integration/fixtures.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.Integration.Fixtures do\n  use ExUnitFixtures.FixtureModule\n  use OMG.Watcher.Fixtures\n\n  alias OMG.Eth\n  alias OMG.Eth.Encoding\n  alias Support.DevHelper\n  alias Support.Integration.DepositHelper\n\n  deffixture alice_deposits(alice, token) do\n    prepare_deposits(alice, token)\n  end\n\n  deffixture stable_alice_deposits(stable_alice, token) do\n    prepare_deposits(stable_alice, token)\n  end\n\n  defp prepare_deposits(alice, token_addr) do\n    some_value = 10\n\n    {:ok, _} = DevHelper.import_unlock_fund(alice)\n\n    deposit_blknum = DepositHelper.deposit_to_child_chain(alice.addr, some_value)\n    token_addr = Encoding.from_hex(token_addr)\n    {:ok, _} = Eth.Token.mint(alice.addr, some_value, token_addr) |> DevHelper.transact_sync!()\n    token_deposit_blknum = DepositHelper.deposit_to_child_chain(alice.addr, some_value, token_addr)\n\n    {deposit_blknum, token_deposit_blknum}\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/support/integration/test_helper.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.Integration.TestHelper do\n  @moduledoc \"\"\"\n  Common helper functions that are useful when integration-testing the watcher\n  \"\"\"\n  require OMG.Watcher.Utxo\n\n  alias OMG.Watcher.Configuration\n  alias OMG.Watcher.State\n  alias Support.DevHelper\n  alias Support.RootChainHelper\n  alias Support.WaitFor\n  alias Support.WatcherHelper\n\n  def wait_for_byzantine_events(event_names, timeout) do\n    fn ->\n      %{\"byzantine_events\" => emitted_events} = WatcherHelper.success?(\"/status.get\")\n      emitted_event_names = Enum.map(emitted_events, &String.to_atom(&1[\"event\"]))\n\n      if Enum.sort(emitted_event_names) == Enum.sort(event_names),\n        do: {:ok, emitted_event_names},\n        else: :repeat\n    end\n    |> WaitFor.ok(timeout)\n  end\n\n  def wait_for_block_fetch(block_number, timeout) do\n    # TODO query to State used in tests instead of an event system, remove when event system is here\n    fn ->\n      case elem(State.get_status(), 0) do\n        blknum when blknum < block_number -> :repeat\n        _ -> {:ok, block_number}\n      end\n    end\n    |> WaitFor.ok(timeout)\n\n    # write to db seems to be async and wait_for_block_fetch would return too early, so sleep\n    # leverage `block` events if they get implemented\n    Process.sleep(100)\n  end\n\n  @doc \"\"\"\n  The above wait_for_block_fetch/2 function only waits for a block to appear in the\n  state and add some \"random\" sleep to give the database time to process and write the block.\n  This function will instead poll for block.get until found (or timeout).\n  \"\"\"\n  def wait_for_block_inserted_in_db(block_number, timeout) do\n    func = fn ->\n      case WatcherHelper.get_block(block_number) do\n        {:error, _} -> :repeat\n        {:ok, %{\"blknum\" => ^block_number}} -> {:ok, block_number}\n      end\n    end\n\n    WaitFor.ok(func, timeout)\n  end\n\n  @doc \"\"\"\n  We need to wait on both a margin of eth blocks and exit processing\n  \"\"\"\n  def wait_for_exit_processing(exit_eth_height, timeout \\\\ 5_000) do\n    exit_finality = Configuration.exit_finality_margin() + 1\n    DevHelper.wait_for_root_chain_block(exit_eth_height + exit_finality, timeout)\n    # wait some more to ensure exit is processed\n    Process.sleep(Configuration.ethereum_events_check_interval_ms() * 2)\n  end\n\n  def process_exits(vault_id, token, user) do\n    min_exit_period_ms = OMG.Eth.Configuration.min_exit_period_seconds() * 1000\n    # enough to wait out the exit period on the contract\n    Process.sleep(2 * min_exit_period_ms)\n\n    {:ok, %{\"status\" => \"0x1\", \"blockNumber\" => process_eth_height, \"logs\" => logs}} =\n      RootChainHelper.process_exits(vault_id, token, 0, 1, user.addr) |> Support.DevHelper.transact_sync!()\n\n    # status 0x1 doesn't yet mean much. To smoke test the success of the processing (exits actually processed) we\n    # take a look at the logs. Single entry means no logs were processed (it is the `ProcessedExitsNum`, that always\n    # gets emmitted)\n    true = length(logs) > 1 || {:error, :looks_like_no_exits_were_processed}\n\n    # to have the new event fully acknowledged by the services, wait the finality margin\n    exit_finality_margin = Configuration.exit_finality_margin()\n    DevHelper.wait_for_root_chain_block(process_eth_height + exit_finality_margin + 1)\n    # just a little more to ensure events are recognized by services\n    check_interval_ms = Configuration.ethereum_events_check_interval_ms()\n    Process.sleep(3 * check_interval_ms)\n    :ok\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/support/integration/test_server.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.Integration.TestServer do\n  @moduledoc \"\"\"\n  Helper functions to provide behavior to FakeServer without using FakeServer defined macros.\n  Use with :test_server fixture which provides context variables\n  For now it's strictly tied with child chain api and handles env variable changes\n  \"\"\"\n\n  @doc \"\"\"\n  Configures route for fake server to respond for given path with given response\n  **Please note: **\n  When the route is configured with a list of FakeServer.HTTP.Responses, the server will respond with the first element\n  in the list and then remove it. This will be repeated for each request made for this route.\n  Use `fn req -> response end` when you need to return always the same or modified response on every request\n\n  Also first use of `with_route` changes configuration variable to child chain api to fake server, so invoke this\n  function when fake response is needed.\n  \"\"\"\n  def with_route(%{fake_addr: fake_addr, server_pid: server_pid, server_id: server_id} = _context, path, response_block) do\n    Application.put_env(:omg_watcher, :child_chain_url, fake_addr)\n\n    FakeServer.put_route(server_pid, path, response_block)\n\n    {:ok, port} = FakeServer.port(server_id)\n\n    %{port: port}\n  end\n\n  def make_response(data) when is_map(data) do\n    WatcherTestServerResponseFactory.build(:json_rpc, data: data, success: not Map.has_key?(data, :code))\n  end\nend\n\ndefmodule WatcherTestServerResponseFactory do\n  @moduledoc false\n  use FakeServer.ResponseFactory\n\n  def json_rpc_response() do\n    ok(\n      %{\n        version: \"1.0\",\n        success: true,\n        data: %{}\n      },\n      %{\"Content-Type\" => \"application/json\"}\n    )\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/support/signature_helper.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\ndefmodule OMG.Watcher.SignatureHelper do\n  @moduledoc false\n  @doc \"\"\"\n  Returns a ECDSA signature (v,r,s) for a given hashed value.\n\n  This implementes Eq.(207) of the Yellow Paper.\n\n  \"\"\"\n  @base_recovery_id 27\n  @base_recovery_id_eip_155 35\n  @type keccak_hash :: binary()\n\n  @type private_key :: <<_::256>>\n  @type hash_v :: integer()\n  @type hash_r :: integer()\n  @type hash_s :: integer()\n  @spec sign_hash(keccak_hash(), private_key, integer() | nil) ::\n          {hash_v, hash_r, hash_s}\n  def sign_hash(hash, private_key, chain_id \\\\ nil) do\n    {:ok, {<<r::size(256), s::size(256)>>, recovery_id}} = ExSecp256k1.sign_compact(hash, private_key)\n\n    # Fork Ψ EIP-155\n    recovery_id =\n      if chain_id do\n        chain_id * 2 + @base_recovery_id_eip_155 + recovery_id\n      else\n        @base_recovery_id + recovery_id\n      end\n\n    {recovery_id, r, s}\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/support/test_helper.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.Watcher.TestHelper do\n  @moduledoc false\n\n  alias OMG.Watcher.Crypto\n  alias OMG.Watcher.DevCrypto\n  alias OMG.Watcher.State.Core\n  alias OMG.Watcher.State.Transaction\n\n  @type entity :: %{priv: Crypto.priv_key_t(), addr: Crypto.pub_key_t()}\n  @empty_metadata <<0::256>>\n\n  # Deterministic entities. Use only when truly needed.\n  def entities_stable(),\n    do: %{\n      stable_alice: %{\n        priv:\n          <<54, 43, 207, 67, 140, 160, 190, 135, 18, 162, 70, 120, 36, 245, 106, 165, 5, 101, 183, 55, 11, 117, 126,\n            135, 49, 50, 12, 228, 173, 219, 183, 175>>,\n        addr: <<59, 159, 76, 29, 210, 110, 11, 229, 147, 55, 59, 29, 54, 206, 226, 0, 140, 190, 184, 55>>\n      },\n      stable_bob: %{\n        priv:\n          <<208, 253, 134, 150, 198, 155, 175, 125, 158, 156, 21, 108, 208, 7, 103, 242, 9, 139, 26, 140, 118, 50, 144,\n            21, 226, 19, 156, 2, 210, 97, 84, 128>>,\n        addr: <<207, 194, 79, 222, 88, 128, 171, 217, 153, 41, 195, 239, 138, 178, 227, 16, 72, 173, 118, 35>>\n      },\n      stable_mallory: %{\n        priv:\n          <<89, 253, 200, 245, 173, 195, 234, 62, 168, 206, 213, 19, 136, 51, 147, 209, 1, 14, 180, 107, 106, 8, 133,\n            131, 75, 157, 81, 109, 102, 19, 91, 130>>,\n        addr: <<48, 120, 88, 246, 235, 202, 79, 121, 216, 73, 40, 199, 165, 186, 120, 113, 36, 119, 87, 207>>\n      }\n    }\n\n  def entities(),\n    do:\n      Map.merge(\n        %{\n          alice: generate_entity(),\n          bob: generate_entity(),\n          carol: generate_entity()\n        },\n        entities_stable()\n      )\n\n  @spec generate_entity :: entity()\n  def generate_entity() do\n    {:ok, priv} = DevCrypto.generate_private_key()\n    {:ok, pub} = DevCrypto.generate_public_key(priv)\n    {:ok, address} = Crypto.generate_address(pub)\n    %{priv: priv, addr: address}\n  end\n\n  def do_deposit(state, owner, %{amount: amount, currency: cur, blknum: blknum}) do\n    {:ok, _, new_state} = Core.deposit([%{owner: owner.addr, currency: cur, amount: amount, blknum: blknum}], state)\n\n    new_state\n  end\n\n  @doc \"\"\"\n  convenience function around Transaction.new to create recovered transactions,\n  by allowing to provider private keys of utxo owners along with the inputs\n  \"\"\"\n  @spec create_recovered(\n          list({pos_integer, non_neg_integer, 0 | 1, map}),\n          Transaction.Payment.currency(),\n          list({map, pos_integer}),\n          Transaction.metadata()\n        ) :: Transaction.Recovered.t()\n  def create_recovered(inputs, currency, outputs, metadata \\\\ @empty_metadata) do\n    create_encoded(inputs, currency, outputs, metadata) |> Transaction.Recovered.recover_from!()\n  end\n\n  @spec create_recovered(\n          list({pos_integer, non_neg_integer, 0 | 1, map}),\n          list({map, Transaction.Payment.currency(), pos_integer})\n        ) :: Transaction.Recovered.t()\n  def create_recovered(inputs, outputs), do: create_encoded(inputs, outputs) |> Transaction.Recovered.recover_from!()\n\n  def create_encoded(inputs, currency, outputs, metadata \\\\ @empty_metadata) do\n    create_signed(inputs, currency, outputs, metadata) |> Transaction.Signed.encode()\n  end\n\n  def create_encoded(inputs, outputs) do\n    create_signed(inputs, outputs) |> Transaction.Signed.encode()\n  end\n\n  def create_encoded_fee_tx(blknum, owner, currency, amount) do\n    %Transaction.Signed{\n      raw_tx: Transaction.Fee.new(blknum, {owner, currency, amount}),\n      sigs: []\n    }\n    |> Transaction.Signed.encode()\n  end\n\n  def create_recovered_fee_tx(blknum, owner, currency, amount),\n    do: create_encoded_fee_tx(blknum, owner, currency, amount) |> Transaction.Recovered.recover_from!()\n\n  @doc \"\"\"\n  convenience function around Transaction.new to create signed transactions (see create_recovered)\n  \"\"\"\n  @spec create_signed(\n          list({pos_integer, non_neg_integer, 0 | 1, map}),\n          Transaction.Payment.currency(),\n          list({map, pos_integer}),\n          Transaction.metadata()\n        ) :: Transaction.Signed.t()\n  def create_signed(inputs, currency, outputs, metadata \\\\ @empty_metadata) do\n    raw_tx =\n      Transaction.Payment.new(\n        inputs |> Enum.map(fn {blknum, txindex, oindex, _} -> {blknum, txindex, oindex} end),\n        outputs |> Enum.map(fn {owner, amount} -> {owner.addr, currency, amount} end),\n        metadata\n      )\n\n    privs = get_private_keys(inputs)\n    DevCrypto.sign(raw_tx, privs)\n  end\n\n  @spec create_signed(\n          list({pos_integer, non_neg_integer, 0 | 1, map}),\n          list({map, Transaction.Payment.currency(), pos_integer})\n        ) :: Transaction.Signed.t()\n  def create_signed(inputs, outputs) do\n    raw_tx =\n      Transaction.Payment.new(\n        inputs |> Enum.map(fn {blknum, txindex, oindex, _} -> {blknum, txindex, oindex} end),\n        outputs |> Enum.map(fn {owner, currency, amount} -> {owner.addr, currency, amount} end)\n      )\n\n    privs = get_private_keys(inputs)\n    DevCrypto.sign(raw_tx, privs)\n  end\n\n  def sign_encode(%{} = tx, priv_keys), do: tx |> DevCrypto.sign(priv_keys) |> Transaction.Signed.encode()\n\n  def sign_recover!(%{} = tx, priv_keys) do\n    tx |> sign_encode(priv_keys) |> Transaction.Recovered.recover_from!()\n  end\n\n  defp get_private_keys(inputs),\n    do: Enum.map(inputs, fn {_, _, _, owner} -> owner.priv end)\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/support/watcher_helper.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule Support.WatcherHelper do\n  @moduledoc \"\"\"\n  Module provides common testing functions used by App's tests.\n  \"\"\"\n  alias ExUnit.CaptureLog\n  alias OMG.Utils.HttpRPC.Encoding\n  alias OMG.Watcher.Utxo\n\n  require Utxo\n\n  import ExUnit.Assertions\n  import Plug.Conn\n  import Phoenix.ConnTest\n  @endpoint OMG.WatcherRPC.Web.Endpoint\n\n  def wait_for_process(pid, timeout \\\\ :infinity) when is_pid(pid) do\n    ref = Process.monitor(pid)\n\n    receive do\n      {:DOWN, ^ref, :process, _, _} ->\n        :ok\n    after\n      timeout ->\n        throw({:timeouted_waiting_for, pid})\n    end\n  end\n\n  def success?(path, body \\\\ nil) do\n    response_body = rpc_call(path, body, 200)\n    version = Map.get(response_body, \"version\")\n\n    %{\"version\" => ^version, \"success\" => true, \"data\" => data} = response_body\n    data\n  end\n\n  def no_success?(path, body \\\\ nil) do\n    response_body = rpc_call(path, body, 200)\n    version = Map.get(response_body, \"version\")\n    %{\"version\" => ^version, \"success\" => false, \"data\" => data} = response_body\n    data\n  end\n\n  def server_error?(path, body \\\\ nil) do\n    response_body = rpc_call(path, body, 500)\n    version = Map.get(response_body, \"version\")\n    %{\"version\" => ^version, \"success\" => false, \"data\" => data} = response_body\n    data\n  end\n\n  def rpc_call(path, body \\\\ nil, expected_resp_status \\\\ 200) do\n    response =\n      build_conn()\n      |> put_req_header(\"content-type\", \"application/json\")\n      |> post(path, body)\n\n    # CORS check\n    assert [\"*\"] == get_resp_header(response, \"access-control-allow-origin\")\n\n    required_headers = [\n      \"access-control-allow-origin\",\n      \"access-control-expose-headers\",\n      \"access-control-allow-credentials\"\n    ]\n\n    for header <- required_headers do\n      assert header in Enum.map(response.resp_headers, &elem(&1, 0))\n    end\n\n    # CORS check\n    assert response.status == expected_resp_status\n    Jason.decode!(response.resp_body)\n  end\n\n  def create_topic(main_topic, subtopic), do: main_topic <> \":\" <> subtopic\n\n  @doc \"\"\"\n  Decodes specified keys in map from hex to binary\n  \"\"\"\n  @spec decode16(map(), list()) :: map()\n  def decode16(data, keys) do\n    keys\n    |> Enum.into(%{}, &decode16_for_key(data, &1))\n    |> (&Map.merge(data, &1)).()\n  end\n\n  defp decode16_for_key(data, key) do\n    case data[key] do\n      value when is_binary(value) ->\n        {key, decode_binary!(value)}\n\n      value when is_list(value) ->\n        bin_list =\n          value\n          |> Enum.map(&Encoding.from_hex/1)\n          |> Enum.map(fn {:ok, bin} -> bin end)\n\n        {key, bin_list}\n    end\n  end\n\n  defp decode_binary!(value) do\n    {:ok, bin} = Encoding.from_hex(value)\n    bin\n  end\n\n  def get_balance(address, token) do\n    encoded_token = Encoding.to_hex(token)\n\n    address\n    |> get_balance()\n    |> Enum.find(%{\"amount\" => 0}, fn %{\"currency\" => currency} -> encoded_token == currency end)\n    |> Map.get(\"amount\")\n  end\n\n  def get_utxos(params) when is_map(params) do\n    hex_string_address = Encoding.to_hex(params.address)\n    success?(\"/account.get_utxos\", %{params | address: hex_string_address})\n  end\n\n  @doc \"\"\"\n  shortcut helper for get_utxos that inject pagination data for you\n  \"\"\"\n  def get_utxos(address, page \\\\ 1, limit \\\\ 100) do\n    success?(\"/account.get_utxos\", %{\"address\" => Encoding.to_hex(address), \"page\" => page, \"limit\" => limit})\n  end\n\n  def get_exitable_utxos(address) do\n    success?(\"/account.get_exitable_utxos\", %{\"address\" => Encoding.to_hex(address)})\n  end\n\n  def get_balance(address) do\n    success?(\"/account.get_balance\", %{\"address\" => Encoding.to_hex(address)})\n  end\n\n  def get_block(blknum) do\n    response_body = rpc_call(\"block.get\", %{blknum: blknum}, 200)\n\n    case response_body do\n      %{\"success\" => false, \"data\" => error} -> {:error, error}\n      %{\"success\" => true, \"data\" => block} -> {:ok, block}\n    end\n  end\n\n  def get_exit_data(blknum, txindex, oindex) do\n    get_exit_data(Utxo.Position.encode(Utxo.position(blknum, txindex, oindex)))\n  end\n\n  def get_exit_challenge(blknum, txindex, oindex) do\n    utxo_pos = Utxo.position(blknum, txindex, oindex) |> Utxo.Position.encode()\n\n    data = success?(\"utxo.get_challenge_data\", %{utxo_pos: utxo_pos})\n\n    decode16(data, [\"exiting_tx\", \"txbytes\", \"sig\"])\n  end\n\n  def get_in_flight_exit(transaction) do\n    exit_data = success?(\"in_flight_exit.get_data\", %{txbytes: Encoding.to_hex(transaction)})\n\n    decode16(exit_data, [\"in_flight_tx\", \"input_txs\", \"input_txs_inclusion_proofs\", \"in_flight_tx_sigs\"])\n  end\n\n  def get_in_flight_exit_competitors(transaction) do\n    competitor_data = success?(\"in_flight_exit.get_competitor\", %{txbytes: Encoding.to_hex(transaction)})\n\n    decode16(competitor_data, [\"in_flight_txbytes\", \"competing_txbytes\", \"competing_sig\", \"competing_proof\", \"input_tx\"])\n  end\n\n  def get_prove_canonical(transaction) do\n    competitor_data = success?(\"in_flight_exit.prove_canonical\", %{txbytes: Encoding.to_hex(transaction)})\n\n    decode16(competitor_data, [\"in_flight_txbytes\", \"in_flight_proof\"])\n  end\n\n  def submit(transaction) do\n    submission_info = success?(\"transaction.submit\", %{transaction: Encoding.to_hex(transaction)})\n\n    decode16(submission_info, [\"txhash\"])\n  end\n\n  def get_input_challenge_data(transaction, input_index) do\n    proof_data =\n      success?(\"in_flight_exit.get_input_challenge_data\", %{\n        txbytes: Encoding.to_hex(transaction),\n        input_index: input_index\n      })\n\n    decode16(proof_data, [\n      \"in_flight_txbytes\",\n      \"spending_txbytes\",\n      \"spending_sig\",\n      \"input_tx\"\n    ])\n  end\n\n  def get_output_challenge_data(transaction, output_index) do\n    proof_data =\n      success?(\"in_flight_exit.get_output_challenge_data\", %{\n        txbytes: Encoding.to_hex(transaction),\n        output_index: output_index\n      })\n\n    decode16(proof_data, [\n      \"in_flight_txbytes\",\n      \"in_flight_proof\",\n      \"spending_txbytes\",\n      \"spending_sig\"\n    ])\n  end\n\n  def capture_log(function, max_waiting_ms \\\\ 2_000) do\n    CaptureLog.capture_log(fn ->\n      logs = CaptureLog.capture_log(fn -> function.() end)\n\n      case logs do\n        \"\" -> wait_for_log(max_waiting_ms)\n        logs -> logs\n      end\n    end)\n  end\n\n  defp wait_for_log(max_waiting_ms, sleep_time_ms \\\\ 20) do\n    steps = :erlang.ceil(max_waiting_ms / sleep_time_ms)\n\n    Enum.reduce_while(1..steps, nil, fn _, _ ->\n      logs = CaptureLog.capture_log(fn -> Process.sleep(sleep_time_ms) end)\n\n      case logs do\n        \"\" -> {:cont, \"\"}\n        logs -> {:halt, logs}\n      end\n    end)\n  end\n\n  defp get_exit_data(encoded_position) do\n    data = success?(\"utxo.get_exit_data\", %{utxo_pos: encoded_position})\n    decode16(data, [\"txbytes\", \"proof\"])\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher/test/test_helper.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nExUnit.configure(\n  exclude: [mix_based_child_chain: true, watcher: true, common: true, integration: true, property: true, wrappers: true]\n)\n\nExUnitFixtures.load_fixture_files(Path.join([Mix.Project.build_path(), \"../../\", \"apps/*/test/**/fixtures.exs\"]))\nExUnitFixtures.start()\nExUnit.start()\n\n{:ok, _} = Application.ensure_all_started(:httpoison)\n{:ok, _} = Application.ensure_all_started(:fake_server)\n\n# TODO: even though watcher does not have postgres, this needs to be here\n# because tests breach scope\nMix.Task.run(\"ecto.create\", ~w(--quiet))\nMix.Task.run(\"ecto.migrate\", ~w(--quiet))\n\n{:ok, _} = Application.ensure_all_started(:briefly)\n{:ok, _} = Application.ensure_all_started(:erlexec)\n"
  },
  {
    "path": "apps/omg_watcher_info/lib/omg_watcher_info/api/account.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherInfo.API.Account do\n  @moduledoc \"\"\"\n  Module provides operations related to plasma accounts.\n  \"\"\"\n  alias OMG.Utils.Paginator\n  alias OMG.Watcher.Crypto\n  alias OMG.WatcherInfo.DB\n\n  @doc \"\"\"\n  Returns a list of amounts of currencies that a given address owns\n  \"\"\"\n  @spec get_balance(Crypto.address_t()) :: list(DB.TxOutput.balance())\n  def get_balance(address) do\n    DB.TxOutput.get_balance(address)\n  end\n\n  @doc \"\"\"\n  Gets all utxos belonging to the given address.\n  \"\"\"\n  @spec get_utxos(Keyword.t()) :: Paginator.t(%DB.TxOutput{})\n  def get_utxos(params) do\n    DB.TxOutput.get_utxos(params)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/lib/omg_watcher_info/api/block.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherInfo.API.Block do\n  @moduledoc \"\"\"\n  Module provides operations related to plasma blocks.\n  \"\"\"\n\n  alias OMG.Utils.Paginator\n  alias OMG.WatcherInfo.DB\n\n  @default_blocks_limit 100\n\n  @doc \"\"\"\n  Retrieves a specific block by block number\n  \"\"\"\n  @spec get(pos_integer()) :: {:ok, %DB.Block{}} | {:error, :block_not_found}\n  def get(blknum) do\n    case DB.Block.get(blknum) do\n      nil -> {:error, :block_not_found}\n      block -> {:ok, block}\n    end\n  end\n\n  @doc \"\"\"\n  Retrieves a list of blocks.\n  Length of the list is limited by `limit` and `page` arguments.\n  \"\"\"\n  @spec get_blocks(Keyword.t()) :: Paginator.t(%DB.Block{})\n  def get_blocks(constraints) do\n    paginator = Paginator.from_constraints(constraints, @default_blocks_limit)\n\n    DB.Block.get_blocks(paginator)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/lib/omg_watcher_info/api/deposit.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherInfo.API.Deposit do\n  @moduledoc \"\"\"\n  Module provides operations related to deposits.\n  \"\"\"\n\n  alias OMG.Utils.Paginator\n  alias OMG.WatcherInfo.DB\n\n  @default_events_limit 100\n\n  @doc \"\"\"\n  Retrieves a list of deposits.\n  Length of the list is limited by `limit` and `page` arguments.\n  \"\"\"\n  @spec get_deposits(Keyword.t()) :: Paginator.t(%DB.EthEvent{})\n  def get_deposits(constraints) do\n    {:ok, address} = Keyword.fetch(constraints, :address)\n\n    constraints\n    |> Paginator.from_constraints(@default_events_limit)\n    |> DB.EthEvent.get_deposits(address)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/lib/omg_watcher_info/api/stats.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherInfo.API.Stats do\n  @moduledoc \"\"\"\n  Module provides operations related to network statistics.\n  \"\"\"\n\n  alias OMG.WatcherInfo.DB.Block\n  alias OMG.WatcherInfo.DB.Transaction\n\n  @seconds_in_twenty_four_hours 86_400\n\n  @doc \"\"\"\n  Retrieves network statistics.\n  \"\"\"\n  def get() do\n    end_datetime = DateTime.to_unix(DateTime.utc_now())\n    start_datetime_24_hours = end_datetime - @seconds_in_twenty_four_hours\n\n    response = %{\n      transaction_count: %{\n        all_time: Transaction.count_all(),\n        last_24_hours: Transaction.count_all_between_timestamps(start_datetime_24_hours, end_datetime)\n      },\n      block_count: %{\n        all_time: Block.count_all(),\n        last_24_hours: Block.count_all_between_timestamps(start_datetime_24_hours, end_datetime)\n      },\n      average_block_interval_seconds: %{\n        all_time: get_average_block_interval_all_time(),\n        last_24_hours: get_average_block_interval_between(start_datetime_24_hours, end_datetime)\n      }\n    }\n\n    {:ok, response}\n  end\n\n  @doc \"\"\"\n  Calculates the all-time average block interval.\n  \"\"\"\n  @spec get_average_block_interval_all_time() :: float() | nil\n  def get_average_block_interval_all_time() do\n    block_count = Block.count_all()\n\n    case block_count do\n      n when n < 2 ->\n        nil\n\n      _ ->\n        %{:max => max, :min => min} = Block.get_timestamp_range_all()\n\n        # Formula for average of difference\n\n        max\n        |> Kernel.-(min)\n        |> Kernel./(block_count - 1)\n    end\n  end\n\n  @doc \"\"\"\n  Calculates the average block interval between two given timestamps.\n  \"\"\"\n  @spec get_average_block_interval_between(non_neg_integer(), non_neg_integer()) ::\n          float() | nil\n  def get_average_block_interval_between(start_datetime, end_datetime) do\n    block_count = Block.count_all_between_timestamps(start_datetime, end_datetime)\n\n    case block_count do\n      n when n < 2 ->\n        nil\n\n      _ ->\n        %{:max => max, :min => min} = Block.get_timestamp_range_between(start_datetime, end_datetime)\n\n        # Formula for average of difference\n\n        max\n        |> Kernel.-(min)\n        |> Kernel./(block_count - 1)\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/lib/omg_watcher_info/api/transaction.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherInfo.API.Transaction do\n  @moduledoc \"\"\"\n  Module provides API for transactions\n  \"\"\"\n\n  alias OMG.Utils.Paginator\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.Utxo\n  alias OMG.WatcherInfo.DB\n  alias OMG.WatcherInfo.HttpRPC.Client\n  alias OMG.WatcherInfo.Transaction, as: TransactionCreator\n\n  require Utxo\n  require Transaction.Payment\n\n  @default_transactions_limit 200\n\n  @type create_t() ::\n          {:ok,\n           %{\n             result: :complete | :intermediate,\n             transactions: nonempty_list(TransactionCreator.transaction_with_typed_data_t())\n           }}\n          | {:error, :too_many_outputs}\n          | {:error, :empty_transaction}\n          | {:error, {:insufficient_funds, list(map())}}\n\n  @doc \"\"\"\n  Retrieves a specific transaction by id\n  \"\"\"\n  @spec get(binary()) :: {:ok, %DB.Transaction{}} | {:error, :transaction_not_found}\n  def get(transaction_id) do\n    if transaction = DB.Transaction.get(transaction_id),\n      do: {:ok, transaction},\n      else: {:error, :transaction_not_found}\n  end\n\n  @doc \"\"\"\n  Retrieves a list of transactions that:\n   - (optionally) a given address is involved as input or output owner.\n   - (optionally) belong to a given child block number\n\n  Length of the list is limited by `limit` argument\n  \"\"\"\n  @spec get_transactions(Keyword.t()) :: Paginator.t(%DB.Transaction{})\n  def get_transactions(constraints) do\n    paginator = Paginator.from_constraints(constraints, @default_transactions_limit)\n\n    constraints\n    |> Keyword.drop([:limit, :page])\n    |> DB.Transaction.get_by_filters(paginator)\n  end\n\n  @doc \"\"\"\n  Passes the signed transaction to the child chain.\n\n  Caution: This function is unaware of the child chain's security status, e.g.:\n\n  * Watcher is fully synced,\n  * all operator blocks have been verified,\n  * transaction doesn't spend funds not yet mined\n  * etc...\n  \"\"\"\n  @spec submit(Transaction.Signed.t()) :: Client.response_t() | {:error, atom()}\n  def submit(%Transaction.Signed{} = signed_tx) do\n    url = Application.get_env(:omg_watcher_info, :child_chain_url)\n\n    signed_tx\n    |> Transaction.Signed.encode()\n    |> Client.submit(url)\n  end\n\n  @doc \"\"\"\n  Given order finds spender's inputs sufficient to perform a payment.\n  If also provided with receiver's address, creates and encodes a transaction.\n  \"\"\"\n  @spec create(TransactionCreator.order_t()) :: create_t()\n  def create(order) do\n    owner_inputs =\n      order.owner\n      |> DB.TxOutput.get_sorted_grouped_utxos(:desc)\n      |> TransactionCreator.select_inputs(order)\n\n    case owner_inputs do\n      {:ok, inputs} ->\n        inputs\n        |> get_utxos_count()\n        |> create_transaction(inputs, order)\n\n      err ->\n        err\n    end\n  end\n\n  @doc \"\"\"\n  Converts parameter keyword list to a map before passing it to multi-clause \"handle_merge/`1\"\n  \"\"\"\n  @spec merge(Keyword.t()) :: create_t()\n  def merge(parameters) do\n    parameters |> Map.new() |> handle_merge()\n  end\n\n  @spec handle_merge(map()) :: create_t()\n  defp handle_merge(%{address: address, currency: currency}) do\n    merge_inputs =\n      address\n      |> DB.TxOutput.get_sorted_grouped_utxos(:asc)\n      |> Map.get(currency, [])\n\n    case merge_inputs do\n      [] ->\n        {:error, :no_inputs_found}\n\n      [_single_input] ->\n        {:error, :single_input}\n\n      inputs ->\n        {:ok, TransactionCreator.generate_merge_transactions(inputs)}\n    end\n  end\n\n  defp handle_merge(%{utxo_positions: utxo_positions}) do\n    with {:ok, inputs} <- get_merge_inputs(utxo_positions),\n         :ok <- no_duplicates(inputs),\n         :ok <- single_owner(inputs),\n         :ok <- single_currency(inputs) do\n      {:ok, TransactionCreator.generate_merge_transactions(inputs)}\n    end\n  end\n\n  defp get_utxos_count(currencies) do\n    Enum.reduce(currencies, 0, fn {_, currency_inputs}, acc -> acc + length(currency_inputs) end)\n  end\n\n  defp create_transaction(utxos_count, inputs, _order) when utxos_count > Transaction.Payment.max_inputs() do\n    transactions =\n      inputs\n      |> Enum.reduce([], fn {_, token_inputs}, acc ->\n        merged_transactions =\n          token_inputs\n          |> TransactionCreator.generate_merge_transactions()\n          |> Enum.reverse()\n\n        merged_transactions ++ acc\n      end)\n      |> Enum.reverse()\n\n    respond({:ok, transactions}, :intermediate)\n  end\n\n  defp create_transaction(_utxos_count, inputs, order) do\n    inputs\n    |> TransactionCreator.create(order)\n    |> respond(:complete)\n  end\n\n  @spec get_merge_inputs(list()) :: {:ok, list()} | {:error, atom()}\n  defp get_merge_inputs(utxo_positions) do\n    Enum.reduce_while(utxo_positions, {:ok, []}, fn encoded_position, {:ok, acc} ->\n      case encoded_position |> Utxo.Position.decode!() |> DB.TxOutput.get_by_position() do\n        nil -> {:halt, {:error, :position_not_found}}\n        input -> {:cont, {:ok, [input | acc]}}\n      end\n    end)\n  end\n\n  @spec no_duplicates(list()) :: :ok | {:error, :duplicate_input_positions}\n  defp no_duplicates(inputs) do\n    inputs\n    |> Enum.uniq()\n    |> length()\n    |> case do\n      n when n == length(inputs) -> :ok\n      _ -> {:error, :duplicate_input_positions}\n    end\n  end\n\n  @spec single_owner(list()) :: :ok | {:error, :multiple_input_owners}\n  defp single_owner(inputs) do\n    case inputs |> Enum.uniq_by(fn input -> input.owner end) |> length() do\n      1 -> :ok\n      _ -> {:error, :multiple_input_owners}\n    end\n  end\n\n  @spec single_currency(list()) :: :ok | {:error, :multiple_currencies}\n  defp single_currency(inputs) do\n    case inputs |> Enum.uniq_by(fn input -> input.currency end) |> length() do\n      1 -> :ok\n      _ -> {:error, :multiple_currencies}\n    end\n  end\n\n  defp respond({:ok, transactions}, result), do: {:ok, %{result: result, transactions: transactions}}\n  defp respond(error, _), do: error\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/lib/omg_watcher_info/application.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherInfo.Application do\n  @moduledoc false\n  use Application\n  require Logger\n\n  def start(_type, _args) do\n    _ = Logger.info(\"Starting #{inspect(__MODULE__)}\")\n\n    _ = attach_ecto_telemetry()\n\n    start_root_supervisor()\n  end\n\n  def start_root_supervisor() do\n    # root supervisor must stop whenever any of its children supervisors goes down (children carry the load of restarts)\n    children = [\n      %{\n        id: OMG.WatcherInfo.Supervisor,\n        start: {OMG.WatcherInfo.Supervisor, :start_link, []},\n        restart: :permanent,\n        type: :supervisor\n      }\n    ]\n\n    opts = [\n      strategy: :one_for_one,\n      # whenever any of supervisor's children goes down, so it does\n      max_restarts: 0,\n      name: OMG.WatcherInfo.RootSupervisor\n    ]\n\n    Supervisor.start_link(children, opts)\n  end\n\n  def start_phase(:attach_telemetry, :normal, _phase_args) do\n    handlers = [\n      [\"measure-watcher-info\", OMG.WatcherInfo.Measure.supported_events(), &OMG.WatcherInfo.Measure.handle_event/4, nil]\n    ]\n\n    Enum.each(handlers, fn handler ->\n      case apply(:telemetry, :attach_many, handler) do\n        :ok -> :ok\n        {:error, :already_exists} -> :ok\n      end\n    end)\n  end\n\n  defp attach_ecto_telemetry() do\n    event = [:omg_watcher, :watcher_info, :db, :repo, :query]\n\n    :telemetry.attach(\"spandex-query-tracer\", event, &SpandexEcto.TelemetryAdapter.handle_event/4, nil)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/lib/omg_watcher_info/block_applicator.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherInfo.BlockApplicator do\n  @moduledoc \"\"\"\n  Handles new block applications from Watcher's `BlockGetter` and persists them for further processing\n  \"\"\"\n\n  alias OMG.WatcherInfo.DB\n\n  @type block_application_t :: %{\n          eth_height: pos_integer(),\n          hash: binary(),\n          number: pos_integer(),\n          timestamp: pos_integer(),\n          transactions: [OMG.Watcher.State.Transaction.Recovered.t()]\n        }\n\n  @doc \"\"\"\n  Inserts a block along with transactions and outputs, does not break when block already exists.\n  \"\"\"\n  @spec insert_block!(block_application_t()) :: :ok\n  def insert_block!(block) do\n    block\n    |> DB.Block.insert_from_block_application()\n    |> case do\n      {:ok, _} ->\n        :ok\n\n      # Ensures insert idempotency. Trying to add block with the same `blknum` that already exists takes no effect.\n      # See also [comment](https://github.com/omgnetwork/elixir-omg/pull/1769#discussion_r528700434)\n      {:error, \"current_block\", changeset, _explain} ->\n        [{:blknum, {_msg, [constraint: :unique, constraint_name: _name]}}] = changeset.errors()\n        :ok\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/lib/omg_watcher_info/db/block.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherInfo.DB.Block do\n  @moduledoc \"\"\"\n  Ecto schema for Plasma Chain block\n  \"\"\"\n  use Ecto.Schema\n  require Logger\n  use Spandex.Decorators\n  import Ecto.Changeset\n\n  alias OMG.Utils.Paginator\n  alias OMG.Watcher.State\n  alias OMG.WatcherInfo.DB\n  alias OMG.WatcherInfo.DB.Block.Chunk\n\n  import Ecto.Query, only: [from: 2]\n\n  @type mined_block() :: %{\n          transactions: [State.Transaction.Recovered.t()],\n          blknum: pos_integer(),\n          blkhash: <<_::256>>,\n          timestamp: pos_integer(),\n          eth_height: pos_integer()\n        }\n\n  @primary_key {:blknum, :integer, []}\n  @derive {Phoenix.Param, key: :blknum}\n  schema \"blocks\" do\n    field(:hash, :binary)\n    field(:eth_height, :integer)\n    field(:timestamp, :integer)\n    field(:tx_count, :integer, virtual: true, default: nil)\n\n    has_many(:transactions, DB.Transaction, foreign_key: :blknum)\n\n    timestamps(type: :utc_datetime_usec)\n  end\n\n  @spec get_max_blknum() :: non_neg_integer()\n  @decorate trace(service: :ecto, type: :db, tracer: OMG.WatcherInfo.Tracer)\n  def get_max_blknum() do\n    DB.Repo.aggregate(__MODULE__, :max, :blknum)\n  end\n\n  @doc \"\"\"\n    Gets a block specified by a block number.\n  \"\"\"\n  @decorate trace(service: :ecto, type: :db, tracer: OMG.WatcherInfo.Tracer)\n  def get(blknum) do\n    query =\n      from(\n        block in base_query(),\n        where: [blknum: ^blknum]\n      )\n\n    DB.Repo.one(query)\n  end\n\n  def base_query() do\n    from(\n      block in __MODULE__,\n      left_join: tx in assoc(block, :transactions),\n      group_by: block.blknum,\n      select: %{block | tx_count: count(tx.txhash)}\n    )\n  end\n\n  @doc \"\"\"\n  Returns a list of blocks\n  \"\"\"\n  @spec get_blocks(Paginator.t(%DB.Block{})) :: Paginator.t(%DB.Block{})\n  @decorate trace(service: :ecto, type: :db, tracer: OMG.WatcherInfo.Tracer)\n  def get_blocks(paginator) do\n    query_get_last(paginator.data_paging)\n    |> DB.Repo.all()\n    |> Paginator.set_data(paginator)\n  end\n\n  @spec query_timestamp_between(Ecto.Query.t(), non_neg_integer(), non_neg_integer()) ::\n          Ecto.Query.t()\n  def query_timestamp_between(query, start_datetime, end_datetime) do\n    from(block in query,\n      where:\n        block.timestamp >= ^start_datetime and\n          block.timestamp <= ^end_datetime\n    )\n  end\n\n  @doc \"\"\"\n  Returns the total number of blocks in between given timestamps\n  \"\"\"\n  @spec count_all_between_timestamps(non_neg_integer(), non_neg_integer()) :: non_neg_integer()\n  @decorate trace(service: :ecto, type: :db, tracer: OMG.WatcherInfo.Tracer)\n  def count_all_between_timestamps(start_datetime, end_datetime) do\n    query_count()\n    |> query_timestamp_between(start_datetime, end_datetime)\n    |> DB.Repo.one!()\n  end\n\n  @doc \"\"\"\n  Returns the total number of blocks\n  \"\"\"\n  @spec count_all() :: non_neg_integer()\n  @decorate trace(service: :ecto, type: :db, tracer: OMG.WatcherInfo.Tracer)\n  def count_all() do\n    DB.Repo.one!(query_count())\n  end\n\n  @doc \"\"\"\n  Returns a map with the timestamps of the earliest and latest blocks of all time.\n  \"\"\"\n  @spec get_timestamp_range_all :: %{min: non_neg_integer(), max: non_neg_integer()}\n  @decorate trace(service: :ecto, type: :db, tracer: OMG.WatcherInfo.Tracer)\n  def get_timestamp_range_all() do\n    DB.Repo.one!(query_timestamp_range())\n  end\n\n  @doc \"\"\"\n  Returns a map with the timestamps of the earliest and latest blocks within a given time range.\n  \"\"\"\n  @spec get_timestamp_range_between(non_neg_integer(), non_neg_integer()) :: %{\n          min: non_neg_integer(),\n          max: non_neg_integer()\n        }\n  @decorate trace(service: :ecto, type: :db, tracer: OMG.WatcherInfo.Tracer)\n  def get_timestamp_range_between(start_datetime, end_datetime) do\n    query_timestamp_range()\n    |> query_timestamp_between(start_datetime, end_datetime)\n    |> DB.Repo.one!()\n  end\n\n  @spec insert(map()) :: {:ok, %__MODULE__{}} | {:error, Ecto.Changeset.t()}\n  @decorate trace(service: :ecto, type: :db, tracer: OMG.WatcherInfo.Tracer)\n  def insert(params) do\n    %__MODULE__{}\n    |> changeset(params)\n    |> DB.Repo.insert()\n  end\n\n  @doc \"\"\"\n  Takes a block application and inserts block into the database.\n  \"\"\"\n  # sobelow_skip [\"Misc.BinToTerm\"]\n  @spec insert_from_block_application(map()) :: {:ok, %__MODULE__{}} | {:error, binary(), Ecto.Changeset.t(), any()}\n  @decorate trace(service: :ecto, type: :db, tracer: OMG.WatcherInfo.Tracer)\n  def insert_from_block_application(block_application) do\n    %{number: block_number, transactions: transactions} = block_application\n\n    {db_txs, db_outputs, db_inputs} = prepare_db_transactions(transactions, block_number)\n\n    current_block = %{\n      blknum: block_number,\n      hash: block_application.hash,\n      timestamp: block_application.timestamp,\n      eth_height: block_application.eth_height\n    }\n\n    db_txs_stream = Chunk.chunk(db_txs)\n    db_outputs_stream = Chunk.chunk(db_outputs)\n\n    multi =\n      Ecto.Multi.new()\n      |> Ecto.Multi.insert(\"current_block\", changeset(%__MODULE__{}, current_block), [])\n      |> prepare_inserts(db_txs_stream, \"db_txs_\", DB.Transaction)\n      |> prepare_inserts(db_outputs_stream, \"db_outputs_\", DB.TxOutput)\n      |> DB.TxOutput.spend_utxos(db_inputs)\n\n    {insert_duration, result} = :timer.tc(DB.Repo, :transaction, [multi])\n\n    case result do\n      {:ok, _} ->\n        _ = Logger.info(\"Block \\##{block_number} persisted in WatcherDB, done in #{insert_duration / 1000}ms\")\n\n        result\n\n      {:error, name, changeset, explain} ->\n        _ = Logger.info(\"Block \\##{block_number} not persisted in WatcherDB, done in #{insert_duration / 1000}ms\")\n\n        _ = Logger.info(\"Error in transaction #{name}: #{inspect(changeset.errors)} #{inspect(explain)}\")\n        result\n    end\n  end\n\n  defp prepare_inserts(multi, stream, name, schema) do\n    {ecto_multi, _} =\n      Enum.reduce(stream, {multi, 0}, fn action, {multi, index} ->\n        {Ecto.Multi.insert_all(multi, name <> \"#{index}\", schema, action), index + 1}\n      end)\n\n    ecto_multi\n  end\n\n  @spec prepare_db_transactions(State.Transaction.Recovered.t(), pos_integer()) ::\n          {[map()], [%DB.TxOutput{}], [%DB.TxOutput{}]}\n  defp prepare_db_transactions(mined_transactions, block_number) do\n    mined_transactions\n    |> Stream.with_index()\n    |> Enum.reduce({[], [], []}, fn {tx, txindex}, {tx_list, output_list, input_list} ->\n      {tx, outputs, inputs} = prepare_db_transaction(tx, block_number, txindex)\n      {[tx | tx_list], outputs ++ output_list, inputs ++ input_list}\n    end)\n  end\n\n  @spec prepare_db_transaction(State.Transaction.Recovered.t(), pos_integer(), integer()) :: [\n          {map(), [%DB.TxOutput{}], [%DB.TxOutput{}]}\n        ]\n  defp prepare_db_transaction(recovered_tx, block_number, txindex) do\n    tx = Map.fetch!(recovered_tx, :signed_tx)\n    raw_tx = Map.fetch!(tx, :raw_tx)\n    tx_type = Map.fetch!(raw_tx, :tx_type)\n    metadata = Map.get(raw_tx, :metadata)\n    signed_tx_bytes = Map.fetch!(recovered_tx, :signed_tx_bytes)\n    tx_hash = State.Transaction.raw_txhash(tx)\n\n    transaction = create(block_number, txindex, tx_hash, tx_type, signed_tx_bytes, metadata)\n    outputs = DB.TxOutput.create_outputs(block_number, txindex, tx_hash, tx)\n    inputs = DB.TxOutput.create_inputs(tx, tx_hash)\n    {transaction, outputs, inputs}\n  end\n\n  @spec create(pos_integer(), integer(), binary(), pos_integer(), binary(), State.Transaction.metadata()) ::\n          map()\n  defp create(block_number, txindex, txhash, txtype, txbytes, metadata) do\n    %{\n      txhash: txhash,\n      txtype: txtype,\n      txbytes: txbytes,\n      blknum: block_number,\n      txindex: txindex,\n      metadata: metadata\n    }\n  end\n\n  @spec query_timestamp_range() :: Ecto.Query.t()\n  defp query_timestamp_range() do\n    from(block in __MODULE__,\n      select: %{\n        max: max(block.timestamp),\n        min: min(block.timestamp)\n      }\n    )\n  end\n\n  @spec query_count() :: Ecto.Query.t()\n  defp query_count() do\n    from(block in __MODULE__, select: count())\n  end\n\n  defp changeset(block, params) do\n    block\n    |> cast(params, [:blknum, :hash, :timestamp, :eth_height])\n    |> unique_constraint(:blknum, name: :blocks_pkey)\n    |> validate_required([:blknum, :hash, :timestamp, :eth_height])\n    |> validate_number(:blknum, greater_than: 0)\n    |> validate_number(:timestamp, greater_than: 0)\n    |> validate_number(:eth_height, greater_than: 0)\n  end\n\n  defp query_get_last(%{limit: limit, page: page}) do\n    offset = (page - 1) * limit\n\n    from(\n      block in base_query(),\n      order_by: [desc: :blknum],\n      limit: ^limit,\n      offset: ^offset\n    )\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/lib/omg_watcher_info/db/eth_event.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherInfo.DB.EthEvent do\n  @moduledoc \"\"\"\n  Ecto schema for events logged by Ethereum\n  \"\"\"\n  import Ecto.Query, only: [from: 2]\n  import Ecto.Changeset\n\n  use Ecto.Schema\n  use Spandex.Decorators\n\n  alias OMG.Eth.Encoding\n  alias OMG.Utils.Paginator\n  alias OMG.Watcher.Crypto\n  alias OMG.Watcher.Utxo\n  alias OMG.Watcher.Utxo.Position\n  alias OMG.Watcher.WireFormatTypes\n  alias OMG.WatcherInfo.DB\n\n  require Utxo\n\n  @typep available_event_type_t() :: :standard_exit | :in_flight_exit\n  @typep output_pointer_t() :: %{utxo_pos: pos_integer()} | %{txhash: Crypto.hash_t(), oindex: non_neg_integer()}\n  @typep event_data_t() :: %{\n           call_data: output_pointer_t(),\n           root_chain_txhash: charlist(),\n           log_index: non_neg_integer(),\n           eth_height: pos_integer()\n         }\n\n  @primary_key false\n  schema \"ethevents\" do\n    field(:root_chain_txhash, :binary, primary_key: true)\n    field(:log_index, :integer, primary_key: true)\n    field(:event_type, OMG.WatcherInfo.DB.Types.AtomType)\n    field(:eth_height, :integer)\n\n    field(:root_chain_txhash_event, :binary)\n\n    many_to_many(\n      :txoutputs,\n      DB.TxOutput,\n      join_through: DB.EthEventTxOutput,\n      join_keys: [root_chain_txhash_event: :root_chain_txhash_event, child_chain_utxohash: :child_chain_utxohash]\n    )\n\n    timestamps(type: :utc_datetime_usec)\n  end\n\n  @doc \"\"\"\n  Inserts deposits based on a list of event maps (if not already inserted before)\n  \"\"\"\n  @spec insert_deposits!([OMG.Watcher.State.Core.deposit()]) :: :ok\n  def insert_deposits!(deposits) do\n    Enum.each(deposits, &insert_deposit!/1)\n  end\n\n  @spec insert_deposit!(OMG.Watcher.State.Core.deposit()) :: {:ok, %__MODULE__{}} | {:error, Ecto.Changeset.t()}\n  @decorate trace(service: :ecto, type: :db, tracer: OMG.WatcherInfo.Tracer)\n  defp insert_deposit!(params) do\n    %{\n      root_chain_txhash: root_chain_txhash,\n      log_index: log_index,\n      blknum: blknum,\n      owner: owner,\n      eth_height: eth_height,\n      currency: currency,\n      amount: amount\n    } = params\n\n    event_type = :deposit\n    position = Utxo.position(blknum, 0, 0)\n    root_chain_txhash_event = generate_root_chain_txhash_event(root_chain_txhash, log_index)\n\n    case get(root_chain_txhash_event) do\n      nil ->\n        deposit = %__MODULE__{\n          root_chain_txhash_event: root_chain_txhash_event,\n          log_index: log_index,\n          root_chain_txhash: root_chain_txhash,\n          event_type: event_type,\n          eth_height: eth_height,\n\n          # a deposit from the root chain will only ever have 1 childchain txoutput object\n          txoutputs: [\n            %DB.TxOutput{\n              child_chain_utxohash: generate_child_chain_utxohash(position),\n              blknum: blknum,\n              txindex: 0,\n              oindex: 0,\n              otype: WireFormatTypes.output_type_for(:output_payment_v1),\n              owner: owner,\n              currency: currency,\n              amount: amount\n            }\n          ]\n        }\n\n        {:ok, _} = DB.Repo.insert(deposit)\n\n        # an ethevents row just got inserted, now return the ethevent with all populated fields including\n        # those populated by the DB (eg: inserted_at, updated_at, ...)\n        {:ok, get(root_chain_txhash_event)}\n\n      existing_deposit ->\n        {:ok, existing_deposit}\n    end\n  end\n\n  @doc \"\"\"\n  Uses a list of encoded `Utxo.Position`s to insert the exits (if not already inserted before)\n  \"\"\"\n  @spec insert_exits!([event_data_t()], available_event_type_t(), atom()) :: :ok\n  def insert_exits!(exits, event_type, event_type_detailed) do\n    ensure_output = expect_output_existence?(event_type, event_type_detailed)\n\n    exits\n    |> Stream.map(&utxo_exit_from_exit_event/1)\n    |> Enum.each(&insert_exit!(&1, event_type, ensure_output))\n  end\n\n  def txoutput_changeset(txoutput, params, ethevent) do\n    fields = [:blknum, :txindex, :oindex, :owner, :amount, :currency, :child_chain_utxohash]\n\n    txoutput\n    |> cast(params, fields)\n    |> put_assoc(:ethevents, txoutput.ethevents ++ [ethevent])\n    |> validate_required(fields)\n  end\n\n  @doc \"\"\"\n  Generate a unique child_chain_utxohash from the Utxo.position\n  \"\"\"\n  @spec generate_child_chain_utxohash(Utxo.Position.t()) :: OMG.Watcher.Crypto.hash_t()\n  def generate_child_chain_utxohash(position) do\n    Crypto.hash(\"<#{Position.encode(position)}>\")\n  end\n\n  def generate_root_chain_txhash_event(root_chain_txhash, log_index) do\n    (Encoding.to_hex(root_chain_txhash) <> Integer.to_string(log_index)) |> Crypto.hash()\n  end\n\n  @doc \"\"\"\n  Retrieves event by `root_chain_txhash_event` (unique identifier). Preload txoutputs in a single query as there will not be a large number of them.\n  \"\"\"\n  @spec get(binary()) :: %__MODULE__{}\n  def get(root_chain_txhash_event) do\n    DB.Repo.one(\n      from(ethevent in base_query(),\n        where: ethevent.root_chain_txhash_event == ^root_chain_txhash_event\n      )\n    )\n  end\n\n  @doc \"\"\"\n  Gets a paginated list of deposits filtered by address.\n  \"\"\"\n  @spec get_deposits(\n          paginator :: Paginator.t(%DB.EthEvent{}),\n          address :: Crypto.address_t()\n        ) :: Paginator.t(%DB.EthEvent{})\n  @decorate trace(service: :ecto, type: :db, tracer: OMG.WatcherInfo.Tracer)\n  def get_deposits(paginator, address) do\n    base_query()\n    |> query_deposits()\n    |> query_by_address(address)\n    |> query_paginated(paginator.data_paging)\n    |> DB.Repo.all()\n    |> Paginator.set_data(paginator)\n  end\n\n  @spec utxo_exit_from_exit_event(event_data_t()) ::\n          %{\n            root_chain_txhash: binary(),\n            log_index: non_neg_integer(),\n            eth_height: pos_integer(),\n            output_pointer: tuple()\n          }\n  defp utxo_exit_from_exit_event(%{\n         call_data: output_pointer,\n         root_chain_txhash: root_chain_txhash,\n         log_index: log_index,\n         eth_height: eth_height\n       }) do\n    %{\n      root_chain_txhash: root_chain_txhash,\n      log_index: log_index,\n      output_pointer: transform_output_pointer(output_pointer),\n      eth_height: eth_height\n    }\n  end\n\n  defp transform_output_pointer(%{utxo_pos: utxo_pos}),\n    do: {:utxo_position, Position.decode!(utxo_pos)}\n\n  defp transform_output_pointer(%{txhash: txhash, oindex: oindex}),\n    do: {:output_id, {txhash, oindex}}\n\n  @spec insert_exit!(\n          %{\n            root_chain_txhash: binary(),\n            log_index: non_neg_integer(),\n            output_pointer: {:utxo_position, Position.t()} | {:output_id, tuple()},\n            eth_height: pos_integer()\n          },\n          available_event_type_t(),\n          boolean()\n        ) :: :ok | :noop\n  defp insert_exit!(event, event_type, ensure_output) do\n    %{\n      root_chain_txhash: root_chain_txhash,\n      log_index: log_index,\n      eth_height: eth_height,\n      output_pointer: output_pointer\n    } = event\n\n    root_chain_txhash_event = generate_root_chain_txhash_event(root_chain_txhash, log_index)\n\n    ethevent =\n      case get(root_chain_txhash_event) do\n        nil ->\n          %__MODULE__{\n            root_chain_txhash_event: root_chain_txhash_event,\n            log_index: log_index,\n            root_chain_txhash: root_chain_txhash,\n            eth_height: eth_height,\n            event_type: event_type\n          }\n\n        event ->\n          event\n      end\n\n    case resolve_tx_output(output_pointer) do\n      %DB.TxOutput{} = tx_output ->\n        :ok = insert_exit_if_not_exist(ethevent, tx_output)\n\n      # The transaction's output is expected to be found in the DB unless explicitly allowed by `ensure_output = false`\n      # More explanation can be found in [the issue discussion](https://github.com/omgnetwork/elixir-omg/issues/1760#issuecomment-722313713).\n      nil when not ensure_output ->\n        :noop\n    end\n  end\n\n  @spec resolve_tx_output(tuple()) :: %DB.TxOutput{} | nil\n  defp resolve_tx_output({:utxo_position, utxo_pos}), do: DB.TxOutput.get_by_position(utxo_pos)\n  defp resolve_tx_output({:output_id, {txhash, oindex}}), do: DB.TxOutput.get_by_output_id(txhash, oindex)\n\n  @spec insert_exit_if_not_exist(%__MODULE__{}, %DB.TxOutput{} | nil) :: :ok\n  defp insert_exit_if_not_exist(ethevent, tx_output) do\n    # if TxOutput is already assiociated with this (or any other) spending event no action is needed\n    if output_spent?(tx_output),\n      do: :ok,\n      else: do_insert_exit(ethevent, tx_output)\n  end\n\n  @spec do_insert_exit(%__MODULE__{}, %DB.TxOutput{}) :: {:ok, %__MODULE__{}} | {:error, Ecto.Changeset.t()}\n  @decorate trace(service: :ecto, type: :db, tracer: OMG.WatcherInfo.Tracer)\n  defp do_insert_exit(ethevent, tx_output) when ethevent != nil and tx_output != nil do\n    # sanity check\n    false = output_spent?(tx_output)\n\n    decoded_utxo_position = Utxo.position(tx_output.blknum, tx_output.txindex, tx_output.oindex)\n\n    tx_output\n    |> txoutput_changeset(%{child_chain_utxohash: generate_child_chain_utxohash(decoded_utxo_position)}, ethevent)\n    |> DB.Repo.update!()\n\n    :ok\n  end\n\n  defp base_query() do\n    from(\n      ethevent in __MODULE__,\n      order_by: [desc: :eth_height],\n      left_join: txoutputs in assoc(ethevent, :txoutputs),\n      preload: [txoutputs: txoutputs]\n    )\n  end\n\n  defp query_by_address(query, address) do\n    from(\n      [ethevent, txoutputs] in query,\n      where: txoutputs.owner == ^address\n    )\n  end\n\n  defp query_deposits(query) do\n    from(\n      ethevent in query,\n      where: ethevent.event_type == ^:deposit\n    )\n  end\n\n  defp query_paginated(query, paginator) do\n    offset = (paginator.page - 1) * paginator.limit\n\n    from(\n      event in query,\n      limit: ^paginator.limit,\n      offset: ^offset\n    )\n  end\n\n  # Tells whether `TxOutput` was already spent\n  # NOTE: it looks a little too deep into DB.TxOutput module, but I don't want to extent its API\n  @spec output_spent?(%DB.TxOutput{}) :: boolean()\n  defp output_spent?(%DB.TxOutput{spending_txhash: nil} = tx_output) do\n    Enum.any?(tx_output.ethevents, &(&1.event_type != :deposit))\n  end\n\n  defp output_spent?(%DB.TxOutput{}), do: true\n\n  # Tells whether processing exit event, exited output has to be present in the database\n  # For more see: https://github.com/omgnetwork/elixir-omg/issues/1760#issuecomment-722313713\n  defp expect_output_existence?(:standard_exit, _), do: true\n  defp expect_output_existence?(:in_flight_exit, :InFlightTxOutputPiggybacked), do: false\n  defp expect_output_existence?(:in_flight_exit, _any), do: true\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/lib/omg_watcher_info/db/eth_event_txoutput.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherInfo.DB.EthEventTxOutput do\n  @moduledoc \"\"\"\n  Ecto Schema representing a many-to-many ethevents <-> txoutputs association\n  \"\"\"\n  use Ecto.Schema\n\n  alias OMG.WatcherInfo.DB\n\n  @primary_key false\n  schema \"ethevents_txoutputs\" do\n    belongs_to(:ethevents, DB.EthEvent,\n      references: :root_chain_txhash_event,\n      foreign_key: :root_chain_txhash_event,\n      type: :binary\n    )\n\n    belongs_to(:txoutputs, DB.TxOutput,\n      references: :child_chain_utxohash,\n      foreign_key: :child_chain_utxohash,\n      type: :binary\n    )\n\n    timestamps(type: :utc_datetime_usec)\n  end\n\n  def changeset(params \\\\ %{}) do\n    %__MODULE__{}\n    |> Ecto.Changeset.cast(params, [:root_chain_txhash_event, :child_chain_utxohash])\n    |> Ecto.Changeset.validate_required([:root_chain_txhash_event, :child_chain_utxohash])\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/lib/omg_watcher_info/db/repo.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherInfo.DB.Repo do\n  use Ecto.Repo,\n    otp_app: :omg_watcher_info,\n    adapter: Ecto.Adapters.Postgres\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/lib/omg_watcher_info/db/transaction.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherInfo.DB.Transaction do\n  @moduledoc \"\"\"\n  Ecto Schema representing a transaction\n  \"\"\"\n  use Ecto.Schema\n  use Spandex.Decorators\n  require Logger\n\n  alias OMG.Utils.Paginator\n  alias OMG.Watcher.Utxo\n  alias OMG.WatcherInfo.DB\n\n  require Utxo\n\n  import Ecto.Query, only: [from: 2, where: 2, where: 3, select: 3, join: 5, distinct: 2]\n\n  @primary_key {:txhash, :binary, []}\n  @derive {Phoenix.Param, key: :txhash}\n  @derive {Jason.Encoder, except: [:__meta__]}\n  schema \"transactions\" do\n    field(:txindex, :integer)\n    field(:txtype, :integer)\n    field(:txbytes, :binary)\n    field(:metadata, :binary)\n\n    has_many(:inputs, DB.TxOutput, foreign_key: :spending_txhash)\n    has_many(:outputs, DB.TxOutput, foreign_key: :creating_txhash)\n    belongs_to(:block, DB.Block, foreign_key: :blknum, references: :blknum, type: :integer)\n\n    timestamps(type: :utc_datetime_usec)\n  end\n\n  @doc \"\"\"\n    Gets a transaction specified by a hash.\n    Optionally, fetches block which the transaction was included in.\n  \"\"\"\n  @decorate trace(service: :ecto, type: :db, tracer: OMG.WatcherInfo.Tracer)\n  def get(hash) do\n    query =\n      from(\n        __MODULE__,\n        where: [txhash: ^hash],\n        preload: [\n          block: ^DB.Block.base_query(),\n          inputs: ^from(txo in DB.TxOutput, order_by: :spending_tx_oindex),\n          outputs: ^from(txo in DB.TxOutput, order_by: :oindex)\n        ]\n      )\n\n    DB.Repo.one(query)\n  end\n\n  @doc \"\"\"\n  Returns transactions possibly filtered by constraints\n  * constraints - accepts keyword in the form of [schema_field: value]\n  \"\"\"\n  @spec get_by_filters(Keyword.t(), Paginator.t(%__MODULE__{})) :: Paginator.t(%__MODULE__{})\n  @decorate trace(service: :ecto, type: :db, tracer: OMG.WatcherInfo.Tracer)\n  def get_by_filters(constraints, paginator) do\n    allowed_constraints = [:address, :blknum, :txindex, :txtypes, :metadata, :end_datetime]\n\n    constraints = filter_constraints(constraints, allowed_constraints)\n\n    # we need to handle complex constraints with dedicated modifier function\n    {address, constraints} = Keyword.pop(constraints, :address)\n    {txtypes, constraints} = Keyword.pop(constraints, :txtypes)\n    {end_datetime, constraints} = Keyword.pop(constraints, :end_datetime)\n\n    query_get_last(paginator.data_paging)\n    |> query_get_by_address(address)\n    |> query_get_by_txtypes(txtypes)\n    |> query_get_by(constraints)\n    |> query_get_by_end_datetime(end_datetime)\n    |> DB.Repo.all()\n    |> Paginator.set_data(paginator)\n  end\n\n  defp query_get_last(%{limit: limit, page: page}) do\n    offset = (page - 1) * limit\n\n    from(\n      __MODULE__,\n      order_by: [desc: :blknum, desc: :txindex],\n      limit: ^limit,\n      offset: ^offset,\n      preload: [\n        :block,\n        inputs: ^from(txo in DB.TxOutput, order_by: :spending_tx_oindex),\n        outputs: ^from(txo in DB.TxOutput, order_by: :oindex)\n      ]\n    )\n  end\n\n  @spec query_count() :: Ecto.Query.t()\n  defp query_count() do\n    from(transaction in __MODULE__, select: count())\n  end\n\n  @spec query_timestamp_between(Ecto.Query.t(), non_neg_integer(), non_neg_integer()) :: Ecto.Query.t()\n  def query_timestamp_between(query, start_datetime, end_datetime) do\n    from(transaction in query,\n      join: block in assoc(transaction, :block),\n      where:\n        block.timestamp >= ^start_datetime and\n          block.timestamp <= ^end_datetime\n    )\n  end\n\n  @doc \"\"\"\n  Returns the total number of transactions between the given timestamps.\n  \"\"\"\n  @spec count_all_between_timestamps(non_neg_integer(), non_neg_integer()) :: non_neg_integer()\n  @decorate trace(service: :ecto, type: :db, tracer: OMG.WatcherInfo.Tracer)\n  def count_all_between_timestamps(start_datetime, end_datetime) do\n    query_count()\n    |> query_timestamp_between(start_datetime, end_datetime)\n    |> DB.Repo.one!()\n  end\n\n  @doc \"\"\"\n  Returns the total number of transactions\n  \"\"\"\n  @spec count_all() :: non_neg_integer()\n  @decorate trace(service: :ecto, type: :db, tracer: OMG.WatcherInfo.Tracer)\n  def count_all() do\n    DB.Repo.one!(query_count())\n  end\n\n  defp query_get_by_address(query, nil), do: query\n\n  defp query_get_by_address(query, address) do\n    query\n    |> join(:inner, [t], o in DB.TxOutput, on: t.txhash == o.creating_txhash or t.txhash == o.spending_txhash)\n    |> where([t, o], o.owner == ^address)\n    |> select([t, o], t)\n    |> distinct(true)\n  end\n\n  defp query_get_by_txtypes(query, nil), do: query\n  defp query_get_by_txtypes(query, []), do: query\n\n  defp query_get_by_txtypes(query, txtypes) do\n    where(query, [t], t.txtype in ^txtypes)\n  end\n\n  defp query_get_by(query, constraints) when is_list(constraints), do: query |> where(^constraints)\n\n  @spec get_by_blknum(pos_integer) :: list(%__MODULE__{})\n  @decorate trace(service: :ecto, type: :db, tracer: OMG.WatcherInfo.Tracer)\n  def get_by_blknum(blknum) do\n    __MODULE__\n    |> query_get_by(blknum: blknum)\n    |> from(order_by: [asc: :txindex])\n    |> DB.Repo.all()\n  end\n\n  defp query_get_by_end_datetime(query, nil), do: query\n\n  defp query_get_by_end_datetime(query, end_datetime) do\n    from(transaction in query,\n      join: block in assoc(transaction, :block),\n      where: block.timestamp <= ^end_datetime\n    )\n  end\n\n  @decorate trace(service: :ecto, type: :db, tracer: OMG.WatcherInfo.Tracer)\n  def get_by_position(blknum, txindex) do\n    DB.Repo.one(from(__MODULE__, where: [blknum: ^blknum, txindex: ^txindex]))\n  end\n\n  defp filter_constraints(constraints, allowed_constraints) do\n    case Keyword.drop(constraints, allowed_constraints) do\n      [{out_of_schema, _} | _] ->\n        _ =\n          Logger.warn(\"Constraint on #{inspect(out_of_schema)} does not exist in schema and was dropped from the query\")\n\n        constraints |> Keyword.take(allowed_constraints)\n\n      [] ->\n        constraints\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/lib/omg_watcher_info/db/txoutput.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherInfo.DB.TxOutput do\n  @moduledoc \"\"\"\n  Ecto schema for transaction's output or input\n  \"\"\"\n  use Ecto.Schema\n  use Spandex.Decorators\n\n  alias OMG.Utils.Paginator\n  alias OMG.Watcher.Crypto\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.Utxo\n  alias OMG.WatcherInfo.DB\n  alias OMG.WatcherInfo.DB.Repo\n\n  require Utxo\n\n  import Ecto.Query, only: [from: 2]\n\n  @default_get_utxos_limit 200\n\n  @type balance() :: %{\n          currency: binary(),\n          amount: non_neg_integer()\n        }\n\n  @type exit_t() :: %{\n          utxo_pos: pos_integer(),\n          txbytes: binary(),\n          proof: binary(),\n          sigs: binary()\n        }\n\n  @type order_t() :: :asc | :desc\n\n  @primary_key false\n  schema \"txoutputs\" do\n    field(:blknum, :integer, primary_key: true)\n    field(:txindex, :integer, primary_key: true)\n    field(:oindex, :integer, primary_key: true)\n    field(:owner, :binary)\n    field(:otype, :integer)\n    field(:amount, OMG.WatcherInfo.DB.Types.IntegerType)\n    field(:currency, :binary)\n    field(:proof, :binary)\n    field(:spending_tx_oindex, :integer)\n    field(:child_chain_utxohash, :binary)\n\n    belongs_to(:creating_transaction, DB.Transaction, foreign_key: :creating_txhash, references: :txhash, type: :binary)\n    belongs_to(:spending_transaction, DB.Transaction, foreign_key: :spending_txhash, references: :txhash, type: :binary)\n\n    many_to_many(\n      :ethevents,\n      DB.EthEvent,\n      join_through: DB.EthEventTxOutput,\n      join_keys: [child_chain_utxohash: :child_chain_utxohash, root_chain_txhash_event: :root_chain_txhash_event]\n    )\n\n    timestamps(type: :utc_datetime_usec)\n  end\n\n  # preload ethevents in a single query as there will not be a large number of them\n  @spec get_by_position(Utxo.Position.t()) :: map() | nil\n  @decorate trace(service: :ecto, type: :db, tracer: OMG.WatcherInfo.Tracer)\n  def get_by_position(Utxo.position(blknum, txindex, oindex)) do\n    Repo.one(\n      from(txoutput in __MODULE__,\n        preload: [:ethevents],\n        where: txoutput.blknum == ^blknum and txoutput.txindex == ^txindex and txoutput.oindex == ^oindex\n      )\n    )\n  end\n\n  @spec get_by_output_id(txhash :: OMG.Watcher.Crypto.hash_t(), oindex :: non_neg_integer()) :: map() | nil\n  @decorate trace(service: :ecto, type: :db, tracer: OMG.WatcherInfo.Tracer)\n  def get_by_output_id(txhash, oindex) do\n    Repo.one(\n      from(txoutput in __MODULE__,\n        preload: [:ethevents],\n        where: txoutput.creating_txhash == ^txhash and txoutput.oindex == ^oindex\n      )\n    )\n  end\n\n  @spec get_utxos(keyword) :: OMG.Utils.Paginator.t(%__MODULE__{})\n  @decorate trace(service: :ecto, type: :db, tracer: OMG.WatcherInfo.Tracer)\n  def get_utxos(params) do\n    address = Keyword.fetch!(params, :address)\n    paginator = Paginator.from_constraints(params, @default_get_utxos_limit)\n    %{limit: limit, page: page} = paginator.data_paging\n    offset = (page - 1) * limit\n\n    address\n    |> query_get_utxos()\n    |> from(limit: ^limit, offset: ^offset)\n    |> Repo.all()\n    |> Paginator.set_data(paginator)\n  end\n\n  @spec get_balance(Crypto.address_t()) :: list(balance())\n  @decorate trace(service: :ecto, type: :db, tracer: OMG.WatcherInfo.Tracer)\n  def get_balance(owner) do\n    query =\n      from(\n        txoutput in __MODULE__,\n        left_join: ethevent in assoc(txoutput, :ethevents),\n        # select txoutputs by owner that have neither been spent nor have a corresponding ethevents exit events\n        where:\n          txoutput.owner == ^owner and is_nil(txoutput.spending_txhash) and\n            (is_nil(ethevent) or\n               fragment(\n                 \"\n NOT EXISTS (SELECT 1\n             FROM ethevents_txoutputs AS etfrag\n             JOIN ethevents AS efrag ON\n                      etfrag.root_chain_txhash_event=efrag.root_chain_txhash_event\n                      AND efrag.event_type = ANY(?)\n                      AND etfrag.child_chain_utxohash = ?)\",\n                 [\"standard_exit\", \"in_flight_exit\"],\n                 txoutput.child_chain_utxohash\n               )),\n        group_by: txoutput.currency,\n        select: {txoutput.currency, sum(txoutput.amount)}\n      )\n\n    Repo.all(query)\n    |> Enum.map(fn {currency, amount} ->\n      # defends against sqlite that returns integer here\n      amount = amount |> Decimal.new() |> Decimal.to_integer()\n      %{currency: currency, amount: amount}\n    end)\n  end\n\n  @spec spend_utxos(Ecto.Multi.t(), [map()]) :: Ecto.Multi.t()\n  def spend_utxos(multi, db_inputs) do\n    utc_now = DateTime.utc_now()\n\n    {multi0, _} =\n      Enum.reduce(db_inputs, {multi, 0}, fn data, {multi, index} ->\n        {Utxo.position(blknum, txindex, oindex), spending_oindex, spending_txhash} = data\n\n        {Ecto.Multi.update_all(\n           multi,\n           \"spend_utxos_#{index}\",\n           from(p in DB.TxOutput,\n             where: p.blknum == ^blknum and p.txindex == ^txindex and p.oindex == ^oindex\n           ),\n           set: [\n             spending_tx_oindex: spending_oindex,\n             spending_txhash: spending_txhash,\n             updated_at: utc_now\n           ]\n         ), index + 1}\n      end)\n\n    multi0\n  end\n\n  @spec create_outputs(pos_integer(), integer(), binary(), Transaction.any_flavor_t()) :: [map()]\n  def create_outputs(blknum, txindex, txhash, tx) do\n    # zero-value outputs are not inserted, tx can have no outputs at all\n    outputs =\n      tx\n      |> Transaction.get_outputs()\n      |> Enum.with_index()\n      |> Enum.flat_map(fn {%{currency: currency, owner: owner, amount: amount, output_type: otype}, oindex} ->\n        create_output(otype, blknum, txindex, oindex, txhash, owner, currency, amount)\n      end)\n\n    outputs\n  end\n\n  defp create_output(_otype, _blknum, _txindex, _txhash, _oindex, _owner, _currency, 0), do: []\n\n  defp create_output(otype, blknum, txindex, oindex, txhash, owner, currency, amount) when amount > 0 do\n    [\n      %{\n        otype: otype,\n        blknum: blknum,\n        txindex: txindex,\n        oindex: oindex,\n        owner: owner,\n        amount: amount,\n        currency: currency,\n        creating_txhash: txhash\n      }\n    ]\n  end\n\n  @spec create_inputs(Transaction.any_flavor_t(), binary()) :: [tuple()]\n  def create_inputs(tx, spending_txhash) do\n    tx\n    |> Transaction.get_inputs()\n    |> Enum.with_index()\n    |> Enum.map(fn {Utxo.position(_, _, _) = input_utxo_pos, index} ->\n      {input_utxo_pos, index, spending_txhash}\n    end)\n  end\n\n  @spec get_sorted_grouped_utxos(Crypto.address_t(), order_t()) :: %{Crypto.address_t() => list(%__MODULE__{})}\n  def get_sorted_grouped_utxos(owner, order) do\n    # TODO: use clever DB query to get following out of DB\n    owner\n    |> get_all_utxos()\n    |> Enum.group_by(fn utxo -> utxo.currency end)\n    |> Enum.map(fn {currency, utxos} ->\n      {currency, Enum.sort_by(utxos, fn utxo -> utxo.amount end, order)}\n    end)\n    |> Map.new()\n  end\n\n  defp query_get_utxos(address) do\n    from(\n      txoutput in __MODULE__,\n      preload: [:ethevents],\n      left_join: ethevent in assoc(txoutput, :ethevents),\n      # select txoutputs by owner that have neither been spent nor have a corresponding ethevents exit events\n      where:\n        txoutput.owner == ^address and is_nil(txoutput.spending_txhash) and\n          (is_nil(ethevent) or\n             fragment(\n               \"\nNOT EXISTS (SELECT 1\n           FROM ethevents_txoutputs AS etfrag\n           JOIN ethevents AS efrag ON\n                    etfrag.root_chain_txhash_event=efrag.root_chain_txhash_event\n                    AND efrag.event_type = ANY(?)\n                    AND etfrag.child_chain_utxohash = ?)\",\n               [\"standard_exit\", \"in_flight_exit\"],\n               txoutput.child_chain_utxohash\n             )),\n      order_by: [asc: :blknum, asc: :txindex, asc: :oindex]\n    )\n  end\n\n  @spec get_all_utxos(Crypto.address_t()) :: list()\n  @decorate trace(service: :ecto, type: :db, tracer: OMG.WatcherInfo.Tracer)\n  defp get_all_utxos(address) do\n    query = query_get_utxos(address)\n    Repo.all(query)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/lib/omg_watcher_info/db/types/atom_type.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherInfo.DB.Types.AtomType do\n  @moduledoc \"\"\"\n  Custom Ecto type that converts DB's string value into atom.\n  \"\"\"\n  @behaviour Ecto.Type\n  def type(), do: :string\n\n  def cast(value), do: {:ok, value}\n\n  def load(value), do: {:ok, String.to_existing_atom(value)}\n\n  def dump(value) when is_atom(value), do: {:ok, Atom.to_string(value)}\n\n  def dump(_), do: :error\n  # https://hexdocs.pm/ecto/Ecto.Type.html#c:embed_as/1\n  def embed_as(_), do: :self\n\n  def equal?(value1, value2), do: value1 == value2\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/lib/omg_watcher_info/db/types/block/chunk.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherInfo.DB.Block.Chunk do\n  @moduledoc \"\"\"\n  La chunk.\n  \"\"\"\n  @max_params_count 0xFFFF\n  # Prepares entries to the database in chunks to avoid `too many parameters` error.\n  # Accepts the same parameters that `Repo.insert_all/3`.\n  @spec chunk(Enumerable.t()) :: Enumerable.t()\n  def chunk(entries) do\n    utc_now = DateTime.utc_now()\n    entries = Enum.map(entries, fn entry -> Map.merge(entry, %{inserted_at: utc_now, updated_at: utc_now}) end)\n\n    chunk_size = entries |> hd() |> chunk_size()\n\n    Stream.chunk_every(entries, chunk_size)\n  end\n\n  # Note: an entry with 0 fields will cause a divide-by-zero error.\n  #\n  # DB.Repo.chunk_size(%{}) ==> (ArithmeticError) bad argument in arithmetic expression\n  #\n  # But we could not think of a case where this code happen, so no defensive\n  # checks here.\n  defp chunk_size(entry), do: div(@max_params_count, fields_count(entry))\n\n  defp fields_count(map), do: Kernel.map_size(map)\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/lib/omg_watcher_info/db/types/integer_type.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherInfo.DB.Types.IntegerType do\n  @moduledoc \"\"\"\n  Custom Ecto type that converts DB's decimal value into integer.\n  Ecto supports `:decimal` type out of the box (via `decimal` package). However,\n  since this `:decimal` type requires its own functions to operate, e.g. `Decimal.add/2`,\n  and we only work with whole numbers, we can safely convert to Elixir's primitive integer for easier operations.\n  \"\"\"\n  @behaviour Ecto.Type\n  def type(), do: :integer\n\n  def cast(value) do\n    {:ok, value}\n  end\n\n  def load(value) do\n    {:ok, Decimal.to_integer(value)}\n  end\n\n  def load!(nil), do: 0\n\n  def load!(value), do: Decimal.to_integer(value)\n\n  def dump(value) do\n    {:ok, value}\n  end\n\n  # https://hexdocs.pm/ecto/Ecto.Type.html#c:embed_as/1\n  def embed_as(_), do: :self\n\n  def equal?(value1, value2), do: value1 == value2\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/lib/omg_watcher_info/http_rpc/adapter.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherInfo.HttpRPC.Adapter do\n  @moduledoc \"\"\"\n  Provides functions to communicate with Child Chain API\n  \"\"\"\n\n  alias OMG.Utils.AppVersion\n\n  require Logger\n\n  @doc \"\"\"\n    Makes HTTP POST request to the API\n  \"\"\"\n  def rpc_post(body, path, url) do\n    addr = \"#{url}/#{path}\"\n    headers = [{\"content-type\", \"application/json\"}, {\"X-Watcher-Version\", AppVersion.version(:omg_watcher_info)}]\n\n    with {:ok, body} <- Jason.encode(body),\n         {:ok, %HTTPoison.Response{} = response} <- HTTPoison.post(addr, body, headers) do\n      _ = Logger.debug(\"rpc post #{inspect(addr)} completed successfully\")\n      response\n    else\n      err ->\n        _ = Logger.warn(\"rpc post #{inspect(addr)} failed with #{inspect(err)}\")\n        err\n    end\n  end\n\n  @doc \"\"\"\n  Retrieves body from response structure but don't deserialize it.\n  \"\"\"\n  def get_unparsed_response_body(%HTTPoison.Response{status_code: 200, body: body}) do\n    with {:ok, response} <- Jason.decode(body),\n         %{\"success\" => true, \"data\" => data} <- response do\n      {:ok, data}\n    else\n      %{\"success\" => false, \"data\" => data} -> {:error, {:client_error, data}}\n      match_err -> {:error, {:malformed_response, match_err}}\n    end\n  end\n\n  def get_unparsed_response_body(%HTTPoison.Response{body: error}),\n    do: {:error, {:server_error, error}}\n\n  def get_unparsed_response_body({:error, %HTTPoison.Error{reason: :econnrefused}}) do\n    {:error, :childchain_unreachable}\n  end\n\n  def get_unparsed_response_body({:error, %HTTPoison.Error{reason: reason}}) do\n    {:error, reason}\n  end\n\n  def get_unparsed_response_body(error), do: error\n\n  @doc \"\"\"\n  Retrieves body from response structure. When response is successful\n  the structure in body is known, so we can try to deserialize it.\n  \"\"\"\n  def get_response_body(response) do\n    case get_unparsed_response_body(response) do\n      {:ok, data} -> {:ok, convert_keys_to_atoms(data)}\n      error -> error\n    end\n  end\n\n  defp convert_keys_to_atoms(data) when is_list(data),\n    do: Enum.map(data, &convert_keys_to_atoms/1)\n\n  defp convert_keys_to_atoms(data) when is_map(data) do\n    data\n    |> Stream.map(fn {k, v} -> {String.to_existing_atom(k), v} end)\n    |> Map.new()\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/lib/omg_watcher_info/http_rpc/client.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherInfo.HttpRPC.Client do\n  @moduledoc \"\"\"\n  Provides functions to communicate with Child Chain API\n  \"\"\"\n\n  alias OMG.Utils.HttpRPC.Encoding\n  alias OMG.WatcherInfo.HttpRPC.Adapter\n\n  require Logger\n\n  @type response_t() ::\n          {:ok, %{required(atom()) => any()}}\n          | {:error,\n             {:client_error | :server_error, any()}\n             | {:malformed_response, any() | {:error, :invalid}}}\n\n  @doc \"\"\"\n  Gets Block of given hash\n  \"\"\"\n  @spec get_block(binary(), binary()) :: response_t()\n  def get_block(hash, url), do: call(%{hash: Encoding.to_hex(hash)}, \"block.get\", url)\n\n  @doc \"\"\"\n  Gets supported fees\n  \"\"\"\n  @spec get_fees(map(), binary()) ::\n          {:ok, map()}\n          | {:error,\n             {:client_error | :server_error, any()}\n             | {:malformed_response, any() | {:error, :invalid}}}\n  def get_fees(params, url) do\n    params\n    |> Adapter.rpc_post(\"fees.all\", url)\n    |> Adapter.get_unparsed_response_body()\n  end\n\n  @doc \"\"\"\n  Submits transaction\n  \"\"\"\n  @spec submit(binary(), binary()) :: response_t()\n  def submit(tx, url), do: call(%{transaction: Encoding.to_hex(tx)}, \"transaction.submit\", url)\n\n  defp call(params, path, url),\n    do: params |> Adapter.rpc_post(path, url) |> Adapter.get_response_body() |> decode_response()\n\n  # Translates response's body to known elixir structure, either block or tx submission response or error.\n  defp decode_response({:ok, %{transactions: transactions, blknum: number, hash: hash}}) do\n    {:ok,\n     %{\n       number: number,\n       hash: decode16!(hash),\n       transactions: Enum.map(transactions, &decode16!/1)\n     }}\n  end\n\n  defp decode_response({:ok, %{txhash: _hash} = response}) do\n    {:ok, Map.update!(response, :txhash, &decode16!/1)}\n  end\n\n  defp decode_response(error), do: error\n\n  defp decode16!(hexstr) do\n    {:ok, bin} = Encoding.from_hex(hexstr)\n    bin\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/lib/omg_watcher_info/measure.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherInfo.Measure do\n  @moduledoc \"\"\"\n  Counting business metrics sent to Datadog.\n  \"\"\"\n  import OMG.Status.Metric.Event, only: [name: 1]\n\n  alias OMG.Status.Metric.Datadog\n  alias OMG.WatcherInfo.PendingBlockQueueLengthChecker\n\n  @supported_events [\n    [:pending_block_queue_length, PendingBlockQueueLengthChecker]\n  ]\n\n  def supported_events(), do: @supported_events\n\n  def handle_event([:pending_block_queue_length, PendingBlockQueueLengthChecker], %{length: length}, _state, _config) do\n    _ = Datadog.gauge(name(:pending_block_queue_length), length)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/lib/omg_watcher_info/order_fee_fetcher.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherInfo.OrderFeeFetcher do\n  @moduledoc \"\"\"\n  Handle fetching and formatting of fees for an order\n  \"\"\"\n\n  alias OMG.Utils.HttpRPC.Encoding\n  alias OMG.Watcher.Crypto\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.WireFormatTypes\n  alias OMG.WatcherInfo.HttpRPC.Client\n  alias OMG.WatcherInfo.Transaction, as: TransactionCreator\n\n  # Note: Hardcoding the tx_type for now, until we need to support more types of transactions\n  # that require fetching fees from the child chain.\n  # For now, only transaction.create uses this module and it's used only for payment type transactions.\n  @tx_type WireFormatTypes.tx_type_for(:tx_payment_v1)\n  @str_tx_type Integer.to_string(@tx_type)\n\n  @type order_without_fee_amount_t() :: %{\n          owner: Crypto.address_t(),\n          payments: nonempty_list(TransactionCreator.payment_t()),\n          fee: %{currency: Transaction.Payment.currency()},\n          metadata: binary() | nil\n        }\n\n  @doc \"\"\"\n  Fetch the correct fee amount for the given fee currency from the childchain\n  and adds it to the order map.\n  \"\"\"\n  @spec add_fee_to_order(order_without_fee_amount_t(), String.t() | nil) ::\n          {:ok, TransactionCreator.order_t()} | {:error, atom()}\n  def add_fee_to_order(%{fee: %{currency: currency}} = order, url \\\\ nil) do\n    child_chain_url = url || Application.get_env(:omg_watcher_info, :child_chain_url)\n    encoded_currency = Encoding.to_hex(currency)\n    params = %{\"currencies\" => [encoded_currency], \"tx_types\" => [@tx_type]}\n\n    with {:ok, fees} <- Client.get_fees(params, child_chain_url),\n         {:ok, amount} <- validate_child_chain_fees(fees, encoded_currency) do\n      {:ok, Kernel.put_in(order, [:fee, :amount], amount)}\n    end\n  end\n\n  defp validate_child_chain_fees(fees, currency) do\n    case fees do\n      # Ensuring that the child chain response is correct, we should only have\n      # 1 currency for the given tx type as we filter it in the params of the request.\n      # We also take this opportunity to pattern match the amount.\n      %{@str_tx_type => [%{\"amount\" => amount, \"currency\" => ^currency}]} ->\n        {:ok, amount}\n\n      _ ->\n        {:error, :unexpected_fee_currency}\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/lib/omg_watcher_info/release_tasks/init_postgresql_db.ex",
    "content": "# Copyright 2019-2019 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherInfo.ReleaseTasks.InitPostgresqlDB do\n  @moduledoc false\n  @app :omg_watcher_info\n\n  def migrate() do\n    for repo <- repos() do\n      {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true))\n    end\n  end\n\n  def rollback(repo, version) do\n    {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, to: version))\n  end\n\n  defp repos() do\n    _ = Application.load(@app)\n    Application.fetch_env!(@app, :ecto_repos)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/lib/omg_watcher_info/release_tasks/set_tracer.ex",
    "content": "# Copyright 2019-2019 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherInfo.ReleaseTasks.SetTracer do\n  @moduledoc false\n  @behaviour Config.Provider\n  require Logger\n  alias OMG.WatcherInfo.Tracer\n  @app :omg_watcher_info\n\n  def init(args) do\n    args\n  end\n\n  def load(config, args) do\n    _ = on_load()\n    adapter = Keyword.get(args, :system_adapter, System)\n    _ = Process.put(:system_adapter, adapter)\n    dd_disabled = get_dd_disabled()\n\n    tracer_config =\n      @app\n      |> Application.get_env(Tracer)\n      |> Keyword.put(:disabled?, dd_disabled)\n\n    tracer_config =\n      case dd_disabled do\n        false ->\n          app_env = get_app_env()\n          Keyword.put(tracer_config, :env, app_env)\n\n        true ->\n          Keyword.put(tracer_config, :env, \"\")\n      end\n\n    Config.Reader.merge(config, omg_watcher_info: [{Tracer, tracer_config}])\n  end\n\n  defp get_dd_disabled() do\n    dd_disabled = Application.get_env(@app, Tracer)[:disabled?]\n    dd_disabled? = validate_bool(get_env(\"DD_DISABLED\"), dd_disabled)\n\n    _ = Logger.info(\"CONFIGURATION: App: #{@app} Key: DD_DISABLED Value: #{inspect(dd_disabled?)}.\")\n    dd_disabled?\n  end\n\n  defp get_app_env() do\n    env = validate_app_env(get_env(\"APP_ENV\"))\n    _ = Logger.info(\"CONFIGURATION: App: #{@app} Key: APP_ENV Value: #{inspect(env)}.\")\n    env\n  end\n\n  defp get_env(key) do\n    Process.get(:system_adapter).get_env(key)\n  end\n\n  defp validate_bool(value, _default) when is_binary(value), do: to_bool(String.upcase(value))\n  defp validate_bool(_, default), do: default\n\n  defp to_bool(\"TRUE\"), do: true\n  defp to_bool(\"FALSE\"), do: false\n  defp to_bool(_), do: exit(\"DD_DISABLED either true or false.\")\n\n  defp validate_app_env(value) when is_binary(value), do: value\n  defp validate_app_env(nil), do: exit(\"APP_ENV must be set.\")\n\n  defp on_load() do\n    _ = Application.ensure_all_started(:logger)\n    Application.load(@app)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/lib/omg_watcher_info/supervisor.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherInfo.Supervisor do\n  @moduledoc \"\"\"\n  Starts and supervises Watcher Informational services such as the watcher informational database,\n  block consumer, deposit consumer, exit consumer, etc.\n  \"\"\"\n  use Supervisor\n  require Logger\n\n  alias OMG.WatcherInfo\n\n  if Mix.env() == :test do\n    defmodule Sandbox do\n      @moduledoc \"\"\"\n      Must be start after WatcherInfo.DB.Repo,\n      that no data will be downloaded/inserted before setting the sandbox option.\n      \"\"\"\n      use GenServer\n      alias Ecto.Adapters.SQL\n\n      def start_link(_args) do\n        GenServer.start_link(__MODULE__, :ok, name: __MODULE__)\n      end\n\n      def init(stack) do\n        :ok = SQL.Sandbox.checkout(WatcherInfo.DB.Repo)\n        SQL.Sandbox.mode(WatcherInfo.DB.Repo, {:shared, self()})\n        {:ok, stack}\n      end\n    end\n  end\n\n  @children_run_after_repo if(Mix.env() == :test, do: [{__MODULE__.Sandbox, []}], else: [])\n\n  def start_link() do\n    Supervisor.start_link(__MODULE__, :ok, name: __MODULE__)\n  end\n\n  def init(:ok) do\n    # why sandbox is in this code\n    # https://github.com/omgnetwork/elixir-omg/pull/562\n    top_children =\n      [\n        %{\n          id: WatcherInfo.DB.Repo,\n          start: {WatcherInfo.DB.Repo, :start_link, []},\n          type: :supervisor\n        }\n      ] ++ @children_run_after_repo\n\n    opts = [strategy: :one_for_one]\n    _ = Logger.info(\"Starting #{inspect(__MODULE__)}\")\n    Supervisor.init(top_children, opts)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/lib/omg_watcher_info/tracer.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherInfo.Tracer do\n  @moduledoc \"\"\"\n  Trace Ecto requests and reports information to Datadog via Spandex\n  \"\"\"\n\n  use Spandex.Tracer, otp_app: :omg_watcher_info\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/lib/omg_watcher_info/transaction.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherInfo.Transaction do\n  @moduledoc \"\"\"\n  Module creates transaction from selected utxos and order.\n  \"\"\"\n\n  alias OMG.Watcher.Crypto\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.TypedDataHash\n  alias OMG.WatcherInfo.DB\n  alias OMG.WatcherInfo.UtxoSelection\n\n  require Transaction.Payment\n\n  @empty_metadata <<0::256>>\n  @max_outputs Transaction.Payment.max_outputs()\n  @merge_fee 0\n\n  @type create_t() ::\n          {:ok, nonempty_list(transaction_t())}\n          | {:error, :too_many_outputs}\n          | {:error, :empty_transaction}\n\n  @type create_typed_data_t() ::\n          {:ok, nonempty_list(transaction_with_typed_data_t())}\n          | {:error, :too_many_outputs}\n          | {:error, :empty_transaction}\n\n  @type fee_t() :: %{\n          currency: UtxoSelection.currency_t(),\n          amount: non_neg_integer()\n        }\n  @type payment_t() :: %{\n          owner: Crypto.address_t() | nil,\n          currency: UtxoSelection.currency_t(),\n          amount: pos_integer()\n        }\n\n  @type transaction_t() :: %{\n          inputs: nonempty_list(%DB.TxOutput{}),\n          outputs: nonempty_list(payment_t()),\n          fee: fee_t(),\n          txbytes: Transaction.tx_bytes() | nil,\n          metadata: Transaction.metadata(),\n          sign_hash: Crypto.hash_t() | nil\n        }\n\n  @type transaction_with_typed_data_t() :: %{\n          inputs: nonempty_list(%DB.TxOutput{}),\n          outputs: nonempty_list(payment_t()),\n          fee: fee_t(),\n          txbytes: Transaction.tx_bytes() | nil,\n          metadata: Transaction.metadata(),\n          sign_hash: Crypto.hash_t() | nil,\n          typed_data: TypedDataHash.Types.typedDataSignRequest_t()\n        }\n\n  @type order_t() :: %{\n          owner: Crypto.address_t(),\n          payments: list(payment_t()),\n          metadata: binary() | nil,\n          fee: fee_t()\n        }\n\n  @type utxos_map_t() :: %{UtxoSelection.currency_t() => UtxoSelection.utxo_list_t()}\n\n  @type inputs_t() :: {:ok, utxos_map_t()} | {:error, {:insufficient_funds, list(map())}}\n\n  @doc \"\"\"\n  Given an `order`, finds spender's inputs sufficient to perform a payment.\n  If also provided with receiver's address, creates and encodes a transaction.\n  \"\"\"\n  @spec select_inputs(utxos_map_t(), order_t()) :: inputs_t()\n  def select_inputs(utxos, %{payments: payments, fee: fee}) do\n    reviewed_selected_utxos =\n      payments\n      # calculates net amount to satisfy payments and fee.\n      |> UtxoSelection.calculate_net_amount(fee)\n      # tries to select utxos that satisfy net amount.\n      |> UtxoSelection.select_utxos(utxos)\n      # reviews if selected utxos satisfy net amount.\n      |> UtxoSelection.review_selected_utxos()\n\n    case reviewed_selected_utxos do\n      {:ok, funds} ->\n        stealth_merge_utxos =\n          utxos\n          |> UtxoSelection.prioritize_merge_utxos(funds)\n          |> UtxoSelection.add_utxos_for_stealth_merge(funds)\n\n        {:ok, stealth_merge_utxos}\n\n      err ->\n        err\n    end\n  end\n\n  @doc \"\"\"\n  Given selected utxos and order, create inputs and outputs, then returns either {:error, reason} or transactions.\n\n  - Returns transactions when the inputs look good.\n  - Returns an error when any of the following conditions is met:\n    1. A number of outputs overs maximum.\n    2. An inputs are empty.\n  \"\"\"\n  @spec create(utxos_map_t(), order_t()) :: create_t()\n  def create(utxos_per_token, order) do\n    inputs = build_inputs(utxos_per_token)\n    outputs = build_outputs(utxos_per_token, order)\n\n    cond do\n      Enum.count(outputs) > @max_outputs ->\n        {:error, :too_many_outputs}\n\n      Enum.empty?(inputs) ->\n        {:error, :empty_transaction}\n\n      true ->\n        raw_tx = create_raw_transaction(inputs, outputs, order.metadata)\n\n        {:ok,\n         [\n           %{\n             inputs: inputs,\n             outputs: outputs,\n             fee: order.fee,\n             metadata: order.metadata,\n             txbytes: Transaction.raw_txbytes(raw_tx),\n             sign_hash: TypedDataHash.hash_struct(raw_tx)\n           }\n         ]}\n    end\n  end\n\n  @spec include_typed_data(create_t()) :: create_typed_data_t()\n  def include_typed_data({:error, _} = err), do: err\n\n  def include_typed_data({:ok, %{result: result, transactions: txs}}) do\n    {\n      :ok,\n      %{result: result, transactions: Enum.map(txs, fn tx -> Map.put_new(tx, :typed_data, add_type_specs(tx)) end)}\n    }\n  end\n\n  def include_typed_data({:ok, txs}) do\n    {\n      :ok,\n      %{transactions: Enum.map(txs, fn tx -> Map.put_new(tx, :typed_data, add_type_specs(tx)) end)}\n    }\n  end\n\n  @spec generate_merge_transactions(UtxoSelection.utxo_list_t()) :: list(transaction_t())\n  def generate_merge_transactions(merge_inputs) do\n    merge_inputs\n    |> Stream.chunk_every(@max_outputs)\n    |> Enum.flat_map(fn input_set ->\n      case input_set do\n        [_single_input] ->\n          []\n\n        inputs ->\n          create_merge(inputs)\n      end\n    end)\n  end\n\n  @spec create_merge(UtxoSelection.utxo_list_t()) :: list(transaction_t())\n  defp create_merge(inputs) do\n    %{currency: currency, owner: owner} = List.first(inputs)\n\n    case create(%{currency => inputs}, %{\n           fee: %{amount: @merge_fee, currency: currency},\n           metadata: @empty_metadata,\n           owner: owner,\n           payments: []\n         }) do\n      {:error, :empty_transaction} ->\n        []\n\n      {:error, :too_many_outputs} ->\n        []\n\n      {:ok, transactions} ->\n        transactions\n    end\n  end\n\n  defp build_inputs(utxos_per_token) do\n    utxos_per_token\n    |> Enum.reduce([], fn {_, utxos}, acc -> Enum.reverse(utxos) ++ acc end)\n    |> Enum.reverse()\n  end\n\n  defp build_outputs(utxos_per_token, order) do\n    rests =\n      utxos_per_token\n      |> Stream.map(fn {token, utxos} ->\n        outputs =\n          [order.fee | order.payments]\n          |> Stream.filter(fn %{currency: currency} -> currency == token end)\n          |> Stream.map(fn %{amount: amount} -> amount end)\n          |> Enum.sum()\n\n        inputs = utxos |> Stream.map(fn %{amount: amount} -> amount end) |> Enum.sum()\n        %{amount: inputs - outputs, owner: order.owner, currency: token}\n      end)\n      |> Enum.filter(fn %{amount: amount} -> amount > 0 end)\n\n    order.payments ++ rests\n  end\n\n  defp create_raw_transaction(inputs, outputs, metadata) do\n    Transaction.Payment.new(\n      Enum.map(inputs, fn input -> {input.blknum, input.txindex, input.oindex} end),\n      Enum.map(outputs, fn output -> {output.owner, output.currency, output.amount} end),\n      metadata || @empty_metadata\n    )\n  end\n\n  defp add_type_specs(%{inputs: inputs, outputs: outputs, metadata: metadata}) do\n    message =\n      [\n        create_inputs(inputs),\n        create_outputs(outputs),\n        [metadata: metadata || @empty_metadata]\n      ]\n      |> Enum.concat()\n      |> Map.new()\n\n    Map.merge(\n      %{\n        domain: TypedDataHash.Config.domain_data_from_config(),\n        message: message\n      },\n      TypedDataHash.Types.eip712_types_specification()\n    )\n  end\n\n  defp create_inputs(inputs) do\n    inputs\n    |> Stream.map(fn input -> %{blknum: input.blknum, txindex: input.txindex, oindex: input.oindex} end)\n    |> Stream.concat(Stream.repeatedly(fn -> %{blknum: 0, txindex: 0, oindex: 0} end))\n    |> (fn input -> Enum.zip([:input0, :input1, :input2, :input3], input) end).()\n  end\n\n  defp create_outputs(outputs) do\n    zero_addr = <<0::160>>\n    empty_gen = fn -> %{owner: zero_addr, currency: zero_addr, amount: 0} end\n\n    outputs\n    |> Stream.concat(Stream.repeatedly(empty_gen))\n    |> (fn output -> Enum.zip([:output0, :output1, :output2, :output3], output) end).()\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/lib/omg_watcher_info/utxo_selection.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherInfo.UtxoSelection do\n  @moduledoc \"\"\"\n  Provides Utxos selection and merging algorithms.\n  \"\"\"\n\n  alias OMG.Utils.HttpRPC.Encoding\n  alias OMG.Watcher.State.Transaction\n  alias OMG.WatcherInfo.DB\n  alias OMG.WatcherInfo.Transaction, as: TransactionCreator\n\n  require Transaction\n  require Transaction.Payment\n\n  @type currency_t() :: Transaction.Payment.currency()\n  @type utxos_map_t() :: %{currency_t() => utxo_list_t()}\n  @type utxo_list_t() :: list(%DB.TxOutput{})\n\n  @doc \"\"\"\n  Defines and prioritises available UTXOs for stealth merge based on the available and selected sets.\n  - Excludes currencies not already used in the transaction and UTXOs in the selected set.\n  - Prioritises currencies that have the largest number of UTXOs\n  - Sorts by ascending order of UTXO value within the currency groupings (\"dust first\").\n  \"\"\"\n  @spec prioritize_merge_utxos(utxos_map_t(), utxos_map_t()) :: utxo_list_t()\n  def prioritize_merge_utxos(utxos, selected_utxos) do\n    utxos_hash =\n      selected_utxos\n      |> Enum.flat_map(fn {_ccy, utxos} -> utxos end)\n      |> Enum.reduce(%{}, fn utxo, acc -> Map.put(acc, utxo.child_chain_utxohash, true) end)\n\n    case utxos_hash do\n      hashes_map when map_size(hashes_map) == 0 ->\n        []\n\n      hashes_map ->\n        selected_utxos\n        |> Enum.map(&prioritize_utxos_by_currency(&1, utxos, hashes_map))\n        |> Enum.sort_by(&length/1, :desc)\n        |> Enum.map(fn currency_utxos -> currency_utxos |> Enum.slice(0, 3) |> Enum.reverse() end)\n        |> Enum.reduce(fn utxos, acc -> utxos ++ acc end)\n        |> Enum.reverse()\n    end\n  end\n\n  @doc \"\"\"\n  Given a map of UTXOs sufficient for the transaction and a set of available UTXOs,\n  adds UTXOs to the transaction for \"stealth merge\" until the limit is reached or\n  no UTXOs are available. Agnostic to the priority ordering of available UTXOs.\n  Returns an updated map of UTXOs for the transaction.\n  \"\"\"\n  @spec add_utxos_for_stealth_merge(utxo_list_t(), utxos_map_t()) :: utxos_map_t()\n  def add_utxos_for_stealth_merge([], selected_utxos), do: selected_utxos\n\n  def add_utxos_for_stealth_merge(available_utxos, selected_utxos) do\n    case get_number_of_utxos(selected_utxos) do\n      Transaction.Payment.max_inputs() ->\n        selected_utxos\n\n      _ ->\n        [priority_utxo | remaining_available_utxos] = available_utxos\n\n        stealth_merge_utxos =\n          Map.update!(selected_utxos, priority_utxo.currency, fn current_utxos ->\n            [priority_utxo | current_utxos]\n          end)\n\n        add_utxos_for_stealth_merge(remaining_available_utxos, stealth_merge_utxos)\n    end\n  end\n\n  @doc \"\"\"\n  Given the available set of UTXOs and the net amount by currency, tries to find a UTXO that satisfies the payment with no change.\n  If this fails, starts to collect UTXOs (starting from the largest amount) until the payment is covered.\n  Returns {currency, { variance, [utxos] }}. A `variance` greater than zero means insufficient funds.\n  The ordering of UTXOs in descending order of amount is implicitly assumed for this algorithm to work deterministically.\n  \"\"\"\n  @spec select_utxos(%{currency_t() => pos_integer()}, utxos_map_t()) ::\n          list({currency_t(), {integer, utxo_list_t()}})\n  def select_utxos(net_amount, utxos) do\n    Enum.map(net_amount, fn {token, need} ->\n      selected_utxos =\n        utxos\n        |> Map.get(token, [])\n        |> find_utxos_by_token(need)\n\n      {token, selected_utxos}\n    end)\n  end\n\n  @doc \"\"\"\n  Sums up payable amount by token, including the fee.\n  \"\"\"\n  @spec calculate_net_amount(list(TransactionCreator.payment_t()), %{amount: pos_integer(), currency: currency_t()}) ::\n          %{currency_t() => pos_integer()}\n  def calculate_net_amount(payments, %{currency: fee_currency, amount: fee_amount}) do\n    net_amount_map =\n      payments\n      |> Enum.group_by(fn payment -> payment.currency end)\n      |> Stream.map(fn {token, payment} ->\n        {token, payment |> Stream.map(fn payment -> payment.amount end) |> Enum.sum()}\n      end)\n      |> Map.new()\n\n    Map.update(net_amount_map, fee_currency, fee_amount, fn amount -> amount + fee_amount end)\n  end\n\n  @doc \"\"\"\n  Checks if the result of `select_utxos/2` covers the amount(s) of the transaction order.\n  \"\"\"\n  @spec review_selected_utxos([\n          {currency :: currency_t(), {variance :: integer(), selected_utxos :: utxo_list_t()}}\n        ]) ::\n          {:ok, utxos_map_t()}\n          | {:error, {:insufficient_funds, [%{token: String.t(), missing: pos_integer()}]}}\n  def review_selected_utxos(utxo_selection) do\n    missing_funds =\n      utxo_selection\n      |> Stream.filter(fn {_currency, {variance, _selected_utxos}} -> variance > 0 end)\n      |> Enum.map(fn {currency, {missing, _selected_utxos}} ->\n        %{token: Encoding.to_hex(currency), missing: missing}\n      end)\n\n    case Enum.empty?(missing_funds) do\n      true ->\n        {:ok,\n         Enum.reduce(utxo_selection, %{}, fn {token, {_missing_amount, utxos}}, acc ->\n           Map.put(acc, token, utxos)\n         end)}\n\n      _ ->\n        {:error, {:insufficient_funds, missing_funds}}\n    end\n  end\n\n  defp recursively_find_utxos(_, need, selected_utxos) when need <= 0, do: {need, selected_utxos}\n  defp recursively_find_utxos([], need, _), do: {need, []}\n\n  defp recursively_find_utxos([utxo | utxos], need, selected_utxos),\n    do: recursively_find_utxos(utxos, need - utxo.amount, [utxo | selected_utxos])\n\n  defp find_utxos_by_token(token_utxos, need) do\n    case Enum.find(token_utxos, fn %DB.TxOutput{amount: amount} -> amount == need end) do\n      nil ->\n        recursively_find_utxos(token_utxos, need, [])\n\n      utxo ->\n        {0, [utxo]}\n    end\n  end\n\n  defp prioritize_utxos_by_currency({currency, _utxos}, utxos, selected_utxo_hashes) do\n    utxos[currency]\n    |> filter_unselected(selected_utxo_hashes)\n    |> Enum.sort_by(fn utxo -> utxo.amount end, :asc)\n  end\n\n  @spec filter_unselected(utxo_list_t(), %{currency_t() => boolean()}) :: utxo_list_t()\n  defp filter_unselected(available_utxos, selected_utxo_hashes) do\n    Enum.filter(available_utxos, fn utxo ->\n      !Map.has_key?(selected_utxo_hashes, utxo.child_chain_utxohash)\n    end)\n  end\n\n  @spec get_number_of_utxos(utxos_map_t()) :: integer()\n  defp get_number_of_utxos(utxos_by_currency) do\n    Enum.reduce(utxos_by_currency, 0, fn {_currency, utxos}, acc -> length(utxos) + acc end)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/lib/watcher_info.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherInfo do\n  @moduledoc \"\"\"\n  WatcherInfo is responsible for the non-security-critical part of the Watcher.\n  \"\"\"\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/mix.exs",
    "content": "defmodule OMG.WatcherInfo.MixProject do\n  use Mix.Project\n\n  def project() do\n    [\n      app: :omg_watcher_info,\n      version: version(),\n      build_path: \"../../_build\",\n      config_path: \"../../config/config.exs\",\n      deps_path: \"../../deps\",\n      lockfile: \"../../mix.lock\",\n      elixir: \"~> 1.8\",\n      elixirc_paths: elixirc_paths(Mix.env()),\n      compilers: [:phoenix] ++ Mix.compilers(),\n      start_permanent: Mix.env() == :prod,\n      deps: deps(),\n      test_coverage: [tool: ExCoveralls]\n    ]\n  end\n\n  def application() do\n    [\n      mod: {OMG.WatcherInfo.Application, []},\n      start_phases: [{:attach_telemetry, []}],\n      extra_applications: [:logger, :runtime_tools, :telemetry, :omg_watcher]\n    ]\n  end\n\n  defp version() do\n    \"git\"\n    |> System.cmd([\"describe\", \"--tags\", \"--abbrev=0\"])\n    |> elem(0)\n    |> String.replace(\"v\", \"\")\n    |> String.replace(\"\\n\", \"\")\n  end\n\n  # Specifies which paths to compile per environment.\n  defp elixirc_paths(:prod), do: [\"lib\"]\n  defp elixirc_paths(:dev), do: [\"lib\"]\n  defp elixirc_paths(:test), do: [\"lib\", \"test/support\"]\n\n  defp deps() do\n    [\n      {:postgrex, \"~> 0.15\"},\n      {:ecto_sql, \"~> 3.4\"},\n      {:telemetry, \"~> 0.4.1\"},\n      {:spandex_ecto, \"~> 0.6.0\"},\n      # there's no apparent reason why libsecp256k1, spandex need to be included as dependencies\n      # to this umbrella application apart from mix ecto.gen.migration not working, so here they are, copied from\n      # the parent (main) mix.exs\n\n      {:spandex, \"~> 3.0.2\"},\n      {:jason, \"~> 1.0\"},\n\n      # UMBRELLA\n      {:omg_status, in_umbrella: true},\n      {:omg_utils, in_umbrella: true},\n\n      # TEST ONLY\n      # here only to leverage common test helpers and code\n      {:fake_server, \"~> 2.1\", only: [:dev, :test], runtime: false},\n      {:briefly, \"~> 0.3.0\", only: [:dev, :test]},\n      {:phoenix, \"~> 1.5\", runtime: false},\n      {:poison, \"~> 4.0\"},\n      {:ex_machina, \"~> 2.3\", only: [:test], runtime: false}\n    ]\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/priv/repo/migrations/20180813131000_create_block_table.exs",
    "content": "defmodule OMG.WatcherInfo.Repo.Migrations.CreateBlockTable do\n  use Ecto.Migration\n\n  def change() do\n    create table(:blocks, primary_key: false) do\n      add :blknum, :bigint, null: false, primary_key: true\n      add :hash, :binary, null: false\n      add :timestamp, :integer, null: false\n      add :eth_height, :bigint, null: false\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/priv/repo/migrations/20180813131706_create_transaction_table.exs",
    "content": "defmodule OMG.WatcherInfo.Repo.Migrations.CreateTransactionTable do\n  use Ecto.Migration\n\n  def change() do\n    create table(:transactions, primary_key: false) do\n      add :txhash, :binary, primary_key: true\n      add :txindex, :integer, null: false\n      add :txbytes, :binary, null: false\n      add :sent_at, :timestamp\n      add :blknum, references(:blocks, column: :blknum, type: :bigint)\n    end\n\n    # TODO: this will work as long as there will be not nulls here\n    create unique_index(:transactions, [:blknum, :txindex], name: :unq_transaction_blknum_txindex)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/priv/repo/migrations/20180813133000_create_ethevent_table.exs",
    "content": "defmodule OMG.WatcherInfo.Repo.Migrations.CreateEtheventTable do\n  use Ecto.Migration\n\n  def change() do\n    create table(:ethevents, primary_key: false) do\n      add :hash, :binary, primary_key: true\n      add :blknum, :bigint\n      add :txindex, :integer\n      add :event_type, :string, size: 124, null: false\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/priv/repo/migrations/20180813143343_create_txoutput_table.exs",
    "content": "defmodule OMG.WatcherInfo.Repo.Migrations.CreateTxoutputTable do\n  use Ecto.Migration\n\n  def change() do\n    create table(:txoutputs, primary_key: false) do\n      add :blknum, :bigint, null: false, primary_key: true\n      add :txindex, :integer, null: false, primary_key: true\n      add :oindex, :integer, null: false, primary_key: true\n      add :creating_txhash, references(:transactions, column: :txhash, type: :binary)\n      add :creating_deposit, references(:ethevents, column: :hash, type: :binary)\n      add :spending_txhash, references(:transactions, column: :txhash, type: :binary)\n      add :spending_exit, references(:ethevents, column: :hash, type: :binary)\n      add :spending_tx_oindex, :integer\n      add :owner, :binary, null: false\n      add :amount, :decimal, precision: 81, scale: 0, null: false\n      add :currency, :binary, null: false\n      add :proof, :binary\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/priv/repo/migrations/20190314105410_alter_transactions_table_add_metadata_field.exs",
    "content": "defmodule OMG.WatcherInfo.DB.Repo.Migrations.AlterTransactionsTableAddMetadataField do\n  use Ecto.Migration\n\n  def change() do\n    alter table(:transactions) do\n      add(:metadata, :binary)\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/priv/repo/migrations/20190315095855_alter_transactions_table_add_partitial_index.exs",
    "content": "defmodule OMG.WatcherInfo.DB.Repo.Migrations.AlterTransactionsTableAddPartialIndex do\n  use Ecto.Migration\n\n  def up() do\n    execute(\"CREATE INDEX transactions_metadata_index ON transactions(metadata) WHERE metadata IS NOT NULL\")\n  end\n\n  def down() do\n    execute(\"DROP INDEX transactions_metadata_index\")\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/priv/repo/migrations/20190408131000_add_missing_indices_to_txoutputs.exs",
    "content": "defmodule OMG.WatcherInfo.Repo.Migrations.AddMissingIndicesToTxOuputs do\n  use Ecto.Migration\n\n  def change() do\n    create index(:txoutputs, [:creating_txhash, :spending_txhash])\n    create index(:txoutputs, [:creating_deposit])\n    create index(:txoutputs, [:spending_txhash])\n    create index(:txoutputs, [:spending_exit], where: \"spending_exit IS NOT NULL\")\n    create index(:txoutputs, [:owner])\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/priv/repo/migrations/20190806111817_alter_txoutputs_ethevents_make_many_to_many_relation.exs",
    "content": "\ndefmodule OMG.WatcherInfo.DB.Repo.Migrations.AlterTxOutputsTableAddRootchainTxnHashDepositAndExitColumns do\n  use Ecto.Migration\n\n  # non-backward compatible migration, thus cannot use `change/0`\n\n  def up() do\n    drop constraint(:txoutputs, \"txoutputs_creating_deposit_fkey\")\n    drop constraint(:txoutputs, \"txoutputs_spending_exit_fkey\")\n    drop constraint(:ethevents, \"ethevents_pkey\")\n\n    flush()\n\n    # drop ethevents table and rebuild it as this table is currently unused for all practical purposes.\n    # when getting utxos we filter on txoutputs.creating_deposit is nil and txoutputs.spending is nil and\n    # never query/join with the ethevents table.\n    # dropping is easiest here because we are altering what the primary key is\n\n    drop table(:ethevents)\n\n    create table(:ethevents, primary_key: false) do\n      add(:root_chain_txhash, :binary, primary_key: true)\n      add(:log_index, :int, primary_key: true)\n\n      add(:event_type, :string, size: 124)\n\n      add(:root_chain_txhash_event, :binary)\n\n      timestamps([type: :utc_datetime])\n    end\n\n    create index(:ethevents, :root_chain_txhash)\n    create index(:ethevents, :log_index)\n    create unique_index(:ethevents, :root_chain_txhash_event)\n\n    # how to do this in ecto correctly? do it manually\n    execute(\"ALTER TABLE ethevents ALTER COLUMN inserted_at SET DEFAULT (now() at time zone 'utc');\")\n    execute(\"ALTER TABLE ethevents ALTER COLUMN updated_at SET DEFAULT (now() at time zone 'utc');\")\n\n    alter table(:txoutputs) do\n      add(:child_chain_utxohash, :binary)\n    end\n\n    create unique_index(:txoutputs, :child_chain_utxohash)\n\n    flush()\n\n    # backfill child_chain_utxohash with values from either creating_deposit or spending_exit\n    execute \"\"\"\n      UPDATE txoutputs as t\n        SET child_chain_utxohash =\n          (SELECT\n             CASE WHEN creating_deposit IS NULL THEN spending_exit\n                  WHEN spending_exit IS NULL THEN creating_deposit\n                  ELSE creating_deposit || spending_exit\n             END AS txhash\n           FROM txoutputs as t_inner\n           WHERE t.creating_deposit = t_inner.creating_deposit OR t.spending_exit = t_inner.spending_exit);\n    \"\"\"\n\n    alter table(:txoutputs) do\n      remove(:creating_deposit)\n      remove(:spending_exit)\n\n      timestamps(type: :utc_datetime, default: fragment(\"(now() at time zone 'utc')\"), null: true)\n    end\n\n    create table(:ethevents_txoutputs, primary_key: false) do\n      add(:root_chain_txhash_event,\n        references(:ethevents, column: :root_chain_txhash_event, type: :binary, on_delete: :restrict),\n                   primary_key: true)\n      add(:child_chain_utxohash, references(:txoutputs, column: :child_chain_utxohash, type: :binary,\n          on_delete: :restrict), primary_key: true)\n\n      timestamps([type: :utc_datetime])\n    end\n\n    # how to do this in ecto correctly? do it manually\n    execute(\"ALTER TABLE ethevents_txoutputs ALTER COLUMN inserted_at SET DEFAULT (now() at time zone 'utc');\")\n    execute(\"ALTER TABLE ethevents_txoutputs ALTER COLUMN updated_at SET DEFAULT (now() at time zone 'utc');\")\n\n    create index(:ethevents_txoutputs, :root_chain_txhash_event)\n    create index(:ethevents_txoutputs, :child_chain_utxohash)\n  end\n\n  def down() do\n    # non-backward compatible migration, thus cannot use `change/0`\n    # no-op\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/priv/repo/migrations/20190917165912_set_inserted_at_updated_at_to_epoc.exs",
    "content": "defmodule OMG.WatcherInfo.Repo.Migrations.SetInsertedAtUpdatedAtToEpoch do\n  use Ecto.Migration\n\n  def change() do\n    execute(\"UPDATE txoutputs SET inserted_at = 'epoch' at time zone 'utc';\")\n    execute(\"UPDATE txoutputs SET updated_at = 'epoch' at time zone 'utc';\")\n    execute(\"ALTER TABLE txoutputs ALTER COLUMN inserted_at SET NOT NULL;\")\n    execute(\"ALTER TABLE txoutputs ALTER COLUMN updated_at SET NOT NULL;\")\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/priv/repo/migrations/20200129051756_index_block_timestamp.exs",
    "content": "defmodule OMG.WatcherInfo.DB.Repo.Migrations.IndexBlockTimestamp do\n  use Ecto.Migration\n\n  def change() do\n    create(index(:blocks, [:timestamp]))\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/priv/repo/migrations/20200211064454_add_txtype_to_transaction_and_output.exs",
    "content": "defmodule OMG.WatcherInfo.DB.Repo.Migrations.AddTxtypeToTransactionAndOutput do\n  use Ecto.Migration\n\n  import Ecto.Query, only: [from: 2]\n\n  alias Ecto.Adapters.SQL\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.WireFormatTypes\n  alias OMG.WatcherInfo.DB.Repo\n\n  def up() do\n    alter table(:transactions) do\n      add :txtype, :integer\n    end\n    alter table(:txoutputs) do\n      add :otype, :integer\n    end\n    create index(:transactions, :txtype)\n    create index(:txoutputs, :otype)\n    flush()\n\n    set_txtypes()\n\n    alter table(:transactions) do\n      modify(:txtype, :integer, null: false)\n    end\n    alter table(:txoutputs) do\n      modify(:otype, :integer, null: false)\n    end\n  end\n\n  def down() do\n    # This migration only supports outputs of type 1 and 2, we prevent rollback so we\n    # don't have problems if new types are introduced in the future.\n    raise \"can't rollback this migration\"\n  end\n\n  # Update existing transactions and output that don't have a type\n  defp set_txtypes() do\n    :ok = update_transaction_types()\n    {_, nil} = update_fee_outputs()\n    {_, nil} = update_payment_outputs()\n  end\n\n  defp update_transaction_types() do\n    Repo\n    |> SQL.query!(\"SELECT txhash, txbytes FROM transactions\")\n    |> Map.get(:rows)\n    |> Enum.reduce(%{}, &reduce_txhash_txbytes/2)\n    |> Enum.each(&update_txtype_for_txhashes/1)\n  end\n\n  defp reduce_txhash_txbytes([txhash, txbytes], txtype_to_txhashes) do\n    %{raw_tx: %{tx_type: txtype}} = Transaction.Signed.decode!(txbytes)\n\n    hashes = case txtype_to_txhashes[txtype] do\n      nil -> [txhash]\n      hashes -> [txhash | hashes]\n    end\n    Map.put(txtype_to_txhashes, txtype, hashes)\n  end\n\n  defp update_txtype_for_txhashes({txtype, txhashes}) do\n    count = length(txhashes)\n\n    {^count, nil} = Repo.update_all(\n      from(t in \"transactions\", where: t.txhash in ^txhashes),\n      set: [txtype: txtype]\n    )\n  end\n\n  defp update_fee_outputs() do\n    fee_tx_type = WireFormatTypes.tx_type_for(:tx_fee_token_claim)\n    Repo.update_all(\n      from(\n        o in \"txoutputs\",\n        join: t in \"transactions\",\n        on: o.creating_txhash == t.txhash,\n        where: t.txtype == ^fee_tx_type\n      ),\n      set: [otype: WireFormatTypes.output_type_for(:output_fee_token_claim)]\n    )\n  end\n\n  defp update_payment_outputs() do\n     Repo.update_all(\n      from(o in \"txoutputs\", where: is_nil(o.otype)),\n      set: [otype: WireFormatTypes.output_type_for(:output_payment_v1)]\n    )\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/priv/repo/migrations/20200214132000_add_and_fix_timestamps.exs",
    "content": "defmodule OMG.Watcher.Repo.Migrations.AddAndFixTimestamps do\n  use Ecto.Migration\n\n  # this is a non-backward compatible change as the the `transactions.sent_at` column\n  # is being removed, so only an `up()` function is defined\n\n  def up() do\n    Enum.each([\"ethevents\", \"txoutputs\", \"ethevents_txoutputs\"], fn table_name ->\n      # backfill tables that may have null values for `inserted_at` and `updated_at` before adding NOT NULL constraint\n      backfill_null_timestamps(table_name)\n\n      update_timestamps_ddl(table_name)\n    end)\n\n    alter table(:transactions) do\n      remove(:sent_at)\n\n      timestamps([type: :timestamptz, default: fragment(\"('epoch'::TIMESTAMPTZ AT TIME ZONE 'UTC')\")])\n    end\n\n    alter table(:blocks) do\n      timestamps([type: :timestamptz, default: fragment(\"('epoch'::TIMESTAMPTZ AT TIME ZONE 'UTC')\")])\n    end\n  end\n\n  defp backfill_null_timestamps(table_name) do\n    execute(\"UPDATE #{table_name} SET inserted_at=('epoch'::TIMESTAMPTZ AT TIME ZONE 'UTC'), updated_at=('epoch'::TIMESTAMPTZ AT TIME ZONE 'UTC') WHERE inserted_at IS NULL AND updated_at IS NULL\")\n  end\n\n  defp update_timestamps_ddl(table_name) do\n    # change column type. this must be done before column default can be set\n    execute(\"ALTER TABLE #{table_name} ALTER COLUMN inserted_at TYPE TIMESTAMPTZ USING inserted_at AT TIME ZONE 'UTC'\")\n    execute(\"ALTER TABLE #{table_name} ALTER COLUMN updated_at TYPE TIMESTAMPTZ USING updated_at AT TIME ZONE 'UTC'\")\n\n    # add column default value and not null constraint\n    execute(\"ALTER TABLE #{table_name} ALTER COLUMN inserted_at SET DEFAULT ('epoch'::TIMESTAMPTZ AT TIME ZONE 'UTC'), ALTER COLUMN inserted_at SET NOT NULL\")\n    execute(\"ALTER TABLE #{table_name} ALTER COLUMN updated_at SET DEFAULT ('epoch'::TIMESTAMPTZ AT TIME ZONE 'UTC'), ALTER COLUMN updated_at SET NOT NULL\")\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/priv/repo/migrations/20200514115919_add_eth_height_to_eth_events.exs",
    "content": "defmodule OMG.WatcherInfo.DB.Repo.Migrations.AddEthHeightToEthEvents do\n  use Ecto.Migration\n\n  def up() do\n    alter table(:ethevents) do\n      add(:eth_height, :integer)\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/priv/repo/migrations/20200529085008_create_pending_block_table.exs",
    "content": "defmodule OMG.WatcherInfo.DB.Repo.Migrations.CreatePendingBlockTable do\n  use Ecto.Migration\n\n  def change() do\n    create table(:pending_blocks, primary_key: false) do\n      add :blknum, :bigint, null: false, primary_key: true\n      add :data, :binary, null: false\n\n      timestamps([type: :timestamptz, default: fragment(\"('epoch'::TIMESTAMPTZ AT TIME ZONE 'UTC')\")])\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/test/fixtures.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherInfo.Fixtures do\n  use ExUnitFixtures.FixtureModule\n\n  use OMG.Eth.Fixtures\n  use OMG.DB.Fixtures\n  use OMG.Watcher.Fixtures\n\n  alias Ecto.Adapters.SQL\n  alias OMG.Watcher.Crypto\n  alias OMG.WatcherInfo\n  alias OMG.WatcherInfo.DB\n\n  @eth <<0::160>>\n\n  deffixture in_beam_watcher(db_initialized, contract) do\n    :ok = db_initialized\n    _ = contract\n\n    {:ok, started_apps} = Application.ensure_all_started(:omg_db)\n    {:ok, started_watcher} = Application.ensure_all_started(:omg_watcher_info)\n    {:ok, started_watcher_api} = Application.ensure_all_started(:omg_watcher_rpc)\n\n    [] = DB.Repo.all(DB.Block)\n\n    on_exit(fn ->\n      Application.put_env(:omg_db, :path, nil)\n\n      (started_apps ++ started_watcher ++ started_watcher_api)\n      |> Enum.reverse()\n      |> Enum.map(fn app -> :ok = Application.stop(app) end)\n    end)\n  end\n\n  deffixture web_endpoint do\n    Application.ensure_all_started(:spandex_ecto)\n    Application.ensure_all_started(:telemetry)\n\n    :telemetry.attach(\n      \"spandex-query-tracer\",\n      [:omg_watcher, :watcher, :db, :repo, :query],\n      &SpandexEcto.TelemetryAdapter.handle_event/4,\n      nil\n    )\n\n    {:ok, pid} = ensure_web_started(OMG.WatcherRPC.Web.Endpoint, :start_link, [], 100)\n\n    _ = Application.load(:omg_watcher_rpc)\n\n    on_exit(fn ->\n      wait_for_process(pid)\n      :ok\n    end)\n  end\n\n  @doc \"run only database in sandbox and endpoint to make request\"\n  deffixture phoenix_ecto_sandbox(web_endpoint) do\n    :ok = web_endpoint\n\n    Supervisor.start_link(\n      [%{id: DB.Repo, start: {DB.Repo, :start_link, []}, type: :supervisor}],\n      strategy: :one_for_one,\n      name: WatcherInfo.Supervisor\n    )\n\n    :ok = SQL.Sandbox.checkout(DB.Repo)\n    SQL.Sandbox.mode(DB.Repo, {:shared, self()})\n  end\n\n  deffixture initial_blocks(alice, bob, blocks_inserter, initial_deposits) do\n    :ok = initial_deposits\n\n    blocks = [\n      {1000,\n       [\n         OMG.Watcher.TestHelper.create_recovered([{1, 0, 0, alice}], @eth, [{bob, 300}]),\n         OMG.Watcher.TestHelper.create_recovered([{1000, 0, 0, bob}], @eth, [{alice, 100}, {bob, 200}])\n       ]},\n      {2000,\n       [\n         OMG.Watcher.TestHelper.create_recovered(\n           [{1000, 1, 0, alice}],\n           @eth,\n           [{bob, 99}, {alice, 1}],\n           <<1337::256>>\n         )\n       ]},\n      {3000,\n       [\n         OMG.Watcher.TestHelper.create_recovered([], @eth, [{alice, 150}]),\n         OMG.Watcher.TestHelper.create_recovered([{1000, 1, 1, bob}], @eth, [{bob, 150}, {alice, 50}])\n       ]}\n    ]\n\n    blocks_inserter.(blocks)\n  end\n\n  deffixture initial_deposits(alice, bob, phoenix_ecto_sandbox) do\n    :ok = phoenix_ecto_sandbox\n\n    deposits = [\n      %{\n        root_chain_txhash: Crypto.hash(<<1000::256>>),\n        log_index: 0,\n        owner: alice.addr,\n        currency: @eth,\n        amount: 333,\n        eth_height: 1,\n        otype: 1,\n        blknum: 1\n      },\n      %{\n        root_chain_txhash: Crypto.hash(<<2000::256>>),\n        log_index: 0,\n        owner: bob.addr,\n        currency: @eth,\n        amount: 100,\n        otype: 1,\n        eth_height: 2,\n        blknum: 2\n      }\n    ]\n\n    # Initial data depending tests can reuse\n    DB.EthEvent.insert_deposits!(deposits)\n    :ok\n  end\n\n  deffixture blocks_inserter(phoenix_ecto_sandbox) do\n    :ok = phoenix_ecto_sandbox\n\n    fn blocks -> Enum.flat_map(blocks, &prepare_one_block/1) end\n  end\n\n  defp prepare_one_block({blknum, recovered_txs}) do\n    block_application = %{\n      transactions: recovered_txs,\n      number: blknum,\n      hash: \"##{blknum}\",\n      timestamp: 1_540_465_606,\n      eth_height: 1\n    }\n\n    {:ok, _} = DB.Block.insert_from_block_application(block_application)\n\n    recovered_txs\n    |> Enum.with_index()\n    |> Enum.map(fn {recovered_tx, txindex} ->\n      {blknum, txindex, recovered_tx.tx_hash, recovered_tx}\n    end)\n  end\n\n  defp ensure_web_started(module, function, args, counter) do\n    _ = Process.flag(:trap_exit, true)\n    do_ensure_web_started(module, function, args, counter)\n  end\n\n  defp do_ensure_web_started(module, function, args, 0), do: apply(module, function, args)\n\n  defp do_ensure_web_started(module, function, args, counter) do\n    {:ok, _pid} = result = apply(module, function, args)\n    result\n  rescue\n    e in MatchError ->\n      case e do\n        %MatchError{\n          term:\n            {:error, {:shutdown, {:failed_to_start_child, {:ranch_listener_sup, OMG.WatcherRPC.Web.Endpoint.HTTP}, _}}}\n        } ->\n          :ok = Process.sleep(5)\n          ensure_web_started(module, function, args, counter - 1)\n\n        %MatchError{term: {:error, {:already_started, pid}}} ->\n          {:ok, pid}\n      end\n  end\n\n  defp wait_for_process(pid, timeout \\\\ :infinity) when is_pid(pid) do\n    ref = Process.monitor(pid)\n\n    receive do\n      {:DOWN, ^ref, :process, _, _} ->\n        :ok\n    after\n      timeout ->\n        throw({:timeouted_waiting_for, pid})\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/test/omg_watcher_info/api/block_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherInfo.API.BlockTest do\n  use ExUnitFixtures\n  use ExUnit.Case, async: false\n  use OMG.WatcherInfo.Fixtures\n\n  import OMG.WatcherInfo.Factory\n\n  alias OMG.Utils.Paginator\n  alias OMG.WatcherInfo.API\n  alias OMG.WatcherInfo.DB\n\n  describe \"get_block/1\" do\n    @tag fixtures: [:initial_blocks]\n    test \"returns block by block number\" do\n      blknum = 1000\n      block = DB.Block.get(blknum)\n      assert {:ok, block} == API.Block.get(blknum)\n    end\n\n    @tag fixtures: [:initial_blocks]\n    test \"returns expected error if block not found\" do\n      non_existent_block = 5000\n      assert {:error, :block_not_found} == API.Block.get(non_existent_block)\n    end\n  end\n\n  describe \"get_blocks/1\" do\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns a paginator with a list of blocks\" do\n      _ = insert(:block, blknum: 1000, hash: \"0x1000\", eth_height: 1, timestamp: 100)\n      _ = insert(:block, blknum: 2000, hash: \"0x2000\", eth_height: 2, timestamp: 200)\n      _ = insert(:block, blknum: 3000, hash: \"0x3000\", eth_height: 3, timestamp: 300)\n\n      constraints = []\n      results = API.Block.get_blocks(constraints)\n\n      assert %Paginator{} = results\n      assert length(results.data) == 3\n      assert Enum.all?(results.data, fn block -> %DB.Block{} = block end)\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns a paginator according to the provided paginator constraints\" do\n      _inserted_1 = insert(:block, blknum: 1000, hash: \"0x1000\", eth_height: 1, timestamp: 100)\n      inserted_2 = insert(:block, blknum: 2000, hash: \"0x2000\", eth_height: 2, timestamp: 200)\n      inserted_3 = insert(:block, blknum: 3000, hash: \"0x3000\", eth_height: 3, timestamp: 300)\n\n      constraints = [limit: 2, page: 1]\n      results = API.Block.get_blocks(constraints)\n\n      assert %Paginator{} = results\n      assert length(results.data) == 2\n      assert results.data |> Enum.at(0) |> Map.get(:blknum) == inserted_3.blknum\n      assert results.data |> Enum.at(1) |> Map.get(:blknum) == inserted_2.blknum\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/test/omg_watcher_info/api/deposit_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherInfo.API.DepositTest do\n  use ExUnitFixtures\n  use ExUnit.Case, async: false\n  use OMG.WatcherInfo.Fixtures\n\n  import OMG.WatcherInfo.Factory\n\n  alias OMG.Utils.Paginator\n  alias OMG.WatcherInfo.API\n  alias OMG.WatcherInfo.DB\n\n  describe \"get_deposits/1\" do\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns a paginator with a list of deposits\" do\n      owner = <<1::160>>\n\n      _ = insert(:ethevent, event_type: :deposit, txoutputs: [build(:txoutput, %{owner: owner})])\n      _ = insert(:ethevent, event_type: :deposit, txoutputs: [build(:txoutput, %{owner: owner})])\n      _ = insert(:ethevent, event_type: :standard_exit, txoutputs: [build(:txoutput, %{owner: owner})])\n\n      constraints = [address: owner]\n      results = API.Deposit.get_deposits(constraints)\n\n      assert %Paginator{} = results\n      assert length(results.data) == 2\n      assert Enum.all?(results.data, fn ethevent -> %DB.EthEvent{event_type: :deposit} = ethevent end)\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns a paginator according to the provided paginator constraints\" do\n      owner = <<1::160>>\n\n      _ = insert(:ethevent, event_type: :deposit, txoutputs: [build(:txoutput, %{owner: owner})])\n      _ = insert(:ethevent, event_type: :deposit, txoutputs: [build(:txoutput, %{owner: owner})])\n      _ = insert(:ethevent, event_type: :deposit, txoutputs: [build(:txoutput, %{owner: owner})])\n\n      assert [page: 1, limit: 2, address: owner] |> API.Deposit.get_deposits() |> Map.get(:data) |> length() == 2\n      assert [page: 2, limit: 2, address: owner] |> API.Deposit.get_deposits() |> Map.get(:data) |> length() == 1\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns results filtered by address\" do\n      owner_1 = <<1::160>>\n      owner_2 = <<2::160>>\n\n      deposit_output_1 = build(:txoutput, %{owner: owner_1})\n      deposit_output_2 = build(:txoutput, %{owner: owner_2})\n\n      _ = insert(:ethevent, event_type: :deposit, txoutputs: [deposit_output_1])\n      _ = insert(:ethevent, event_type: :deposit, txoutputs: [deposit_output_2])\n\n      constraint = [address: owner_1]\n      result = API.Deposit.get_deposits(constraint)\n\n      assert %Paginator{data: [%DB.EthEvent{} = deposit]} = result\n      assert deposit |> Map.get(:txoutputs) |> Enum.at(0) |> Map.get(:owner) == owner_1\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/test/omg_watcher_info/api/stats_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherInfo.API.StatsTest do\n  use ExUnitFixtures\n  use ExUnit.Case, async: false\n  use OMG.WatcherInfo.Fixtures\n\n  alias OMG.WatcherInfo.API.Stats\n\n  import OMG.WatcherInfo.Factory\n\n  @seconds_in_twenty_four_hours 86_400\n\n  describe \"get/0\" do\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"retrieves expected statistics\" do\n      now = DateTime.to_unix(DateTime.utc_now())\n\n      within_today = now - @seconds_in_twenty_four_hours + 100\n      before_today = now - @seconds_in_twenty_four_hours - 100\n\n      block_1 = insert(:block, blknum: 1000, timestamp: within_today)\n      _ = insert(:transaction, block: block_1, txindex: 0)\n      _ = insert(:transaction, block: block_1, txindex: 1)\n\n      block_2 = insert(:block, blknum: 2000, timestamp: before_today)\n      _ = insert(:transaction, block: block_2, txindex: 0)\n      _ = insert(:transaction, block: block_2, txindex: 1)\n\n      result = Stats.get()\n\n      expected =\n        {:ok,\n         %{\n           block_count: %{all_time: 2, last_24_hours: 1},\n           transaction_count: %{all_time: 4, last_24_hours: 2},\n           average_block_interval_seconds: %{all_time: 200.0, last_24_hours: nil}\n         }}\n\n      assert result == expected\n    end\n  end\n\n  describe \"get_average_block_interval_all_time/0\" do\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"correctly returns the average difference of block timestamps for all time\" do\n      base = 100\n      [diff_1, diff_2, diff_3] = [10, 10, 30]\n\n      _ = insert(:block, blknum: 1000, hash: \"0x1000\", eth_height: 1, timestamp: base)\n      _ = insert(:block, blknum: 2000, hash: \"0x2000\", eth_height: 2, timestamp: base + diff_1)\n      _ = insert(:block, blknum: 3000, hash: \"0x3000\", eth_height: 3, timestamp: base + diff_1 + diff_2)\n      _ = insert(:block, blknum: 4000, hash: \"0x4000\", eth_height: 4, timestamp: base + diff_1 + diff_2 + diff_3)\n\n      expected = (diff_1 + diff_2 + diff_3) / 3\n\n      actual = Stats.get_average_block_interval_all_time()\n\n      assert expected == actual\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns nil if the number of blocks is less than 2\" do\n      result_1 = Stats.get_average_block_interval_all_time()\n      assert result_1 == nil\n\n      _ = insert(:block, blknum: 1000, hash: \"0x1000\", eth_height: 1, timestamp: 100)\n      result_2 = Stats.get_average_block_interval_all_time()\n      assert result_2 == nil\n\n      _ = insert(:block, blknum: 2000, hash: \"0x2000\", eth_height: 2, timestamp: 200)\n      result_3 = Stats.get_average_block_interval_all_time()\n      assert result_3 == 100\n    end\n  end\n\n  describe \"get_average_block_interval_between/2\" do\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"correctly returns the average difference of block timestamps in the given time range\" do\n      end_datetime = DateTime.to_unix(DateTime.utc_now())\n      start_datetime = end_datetime - @seconds_in_twenty_four_hours\n      [diff_1, diff_2] = [80, 90]\n\n      _ = insert(:block, blknum: 1000, hash: \"0x1000\", eth_height: 1, timestamp: start_datetime - 100)\n      _ = insert(:block, blknum: 2000, hash: \"0x2000\", eth_height: 2, timestamp: start_datetime)\n      _ = insert(:block, blknum: 3000, hash: \"0x3000\", eth_height: 3, timestamp: start_datetime + diff_1)\n      _ = insert(:block, blknum: 4000, hash: \"0x4000\", eth_height: 4, timestamp: start_datetime + diff_1 + diff_2)\n\n      expected = (diff_1 + diff_2) / 2\n\n      actual = Stats.get_average_block_interval_between(start_datetime, end_datetime)\n\n      assert expected == actual\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns nil if the number of blocks in the given time range is less than 2\" do\n      end_datetime = DateTime.to_unix(DateTime.utc_now())\n      start_datetime = end_datetime - @seconds_in_twenty_four_hours\n\n      _ = insert(:block, blknum: 1000, hash: \"0x1000\", eth_height: 1, timestamp: start_datetime - 100)\n      _ = insert(:block, blknum: 2000, hash: \"0x2000\", eth_height: 2, timestamp: start_datetime - 50)\n\n      result_1 = Stats.get_average_block_interval_between(start_datetime, end_datetime)\n      assert result_1 == nil\n\n      _ = insert(:block, blknum: 3000, hash: \"0x3000\", eth_height: 3, timestamp: start_datetime)\n      result_2 = Stats.get_average_block_interval_between(start_datetime, end_datetime)\n      assert result_2 == nil\n\n      _ = insert(:block, blknum: 4000, hash: \"0x4000\", eth_height: 4, timestamp: start_datetime + 100)\n      result_2 = Stats.get_average_block_interval_between(start_datetime, end_datetime)\n      assert result_2 == 100\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/test/omg_watcher_info/api/transaction_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherInfo.API.TransactionTest do\n  use ExUnitFixtures\n  use ExUnit.Case, async: false\n  use OMG.WatcherInfo.Fixtures\n\n  alias OMG.Watcher.Utxo.Position\n  alias OMG.WatcherInfo.API.Transaction\n\n  import OMG.WatcherInfo.Factory\n\n  @alice <<1::160>>\n  @bob <<2::160>>\n  @currency_1 <<3::160>>\n  @currency_2 <<4::160>>\n\n  describe \"merge/1 with address and currency parameters\" do\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"merge with address and currency forms multiple merge transactions if possible\" do\n      insert_initial_utxo()\n\n      _ = insert(:txoutput, currency: @currency_1, owner: @alice, amount: 1)\n      _ = insert(:txoutput, currency: @currency_1, owner: @alice, amount: 1)\n      _ = insert(:txoutput, currency: @currency_1, owner: @alice, amount: 1)\n      _ = insert(:txoutput, currency: @currency_1, owner: @alice, amount: 1)\n      _ = insert(:txoutput, currency: @currency_1, owner: @alice, amount: 1)\n      _ = insert(:txoutput, currency: @currency_1, owner: @alice, amount: 1)\n      _ = insert(:txoutput, currency: @currency_1, owner: @alice, amount: 1)\n\n      assert {:ok, [%{inputs: [_, _, _, _], outputs: [output_1]}, %{inputs: [_, _, _], outputs: [output_2]}]} =\n               Transaction.merge(%{address: @alice, currency: @currency_1})\n\n      assert output_1 === %{amount: 4, currency: @currency_1, owner: @alice}\n      assert output_2 === %{amount: 3, currency: @currency_1, owner: @alice}\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"fetches inputs for the given addresss only\" do\n      insert_initial_utxo()\n\n      _ = insert(:txoutput, currency: @currency_1, owner: @bob, amount: 1)\n      _ = insert(:txoutput, currency: @currency_1, owner: @bob, amount: 1)\n      _ = insert(:txoutput, currency: @currency_1, owner: @alice, amount: 1)\n      _ = insert(:txoutput, currency: @currency_1, owner: @alice, amount: 1)\n      _ = insert(:txoutput, currency: @currency_1, owner: @alice, amount: 1)\n      _ = insert(:txoutput, currency: @currency_1, owner: @alice, amount: 1)\n\n      assert {:ok, [alice_merge]} = Transaction.merge(%{address: @alice, currency: @currency_1})\n      assert %{inputs: [_, _, _, _], outputs: [%{amount: 4, currency: @currency_1, owner: @alice}]} = alice_merge\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"fetches inputs for the given currency only\" do\n      insert_initial_utxo()\n\n      _ = insert(:txoutput, currency: @currency_1, owner: @alice, amount: 1)\n      _ = insert(:txoutput, currency: @currency_1, owner: @alice, amount: 1)\n      _ = insert(:txoutput, currency: @currency_2, owner: @alice, amount: 1)\n      _ = insert(:txoutput, currency: @currency_2, owner: @alice, amount: 1)\n\n      assert {:ok, [alice_merge]} = Transaction.merge(%{address: @alice, currency: @currency_2})\n      assert %{inputs: [_, _], outputs: [%{amount: 2, currency: @currency_2, owner: @alice}]} = alice_merge\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns one merge transaction if five UTXOs are available – prioritising the lowest value inputs\" do\n      insert_initial_utxo()\n\n      _ = insert(:txoutput, currency: @currency_1, owner: @alice, amount: 1)\n      _ = insert(:txoutput, currency: @currency_1, owner: @alice, amount: 2)\n      _ = insert(:txoutput, currency: @currency_1, owner: @alice, amount: 3)\n      _ = insert(:txoutput, currency: @currency_1, owner: @alice, amount: 4)\n      _ = insert(:txoutput, currency: @currency_1, owner: @alice, amount: 5)\n\n      assert {:ok, [merge_tx]} = Transaction.merge(%{address: @alice, currency: @currency_1})\n\n      assert %{\n               inputs: [%{amount: 1}, %{amount: 2}, %{amount: 3}, %{amount: 4}],\n               outputs: [%{amount: 10, currency: @currency_1, owner: @alice}]\n             } = merge_tx\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"merge with address and currency fails on single input\" do\n      insert_initial_utxo()\n\n      _ = insert(:txoutput, currency: @currency_1, owner: @alice, amount: 1)\n      _ = insert(:txoutput, currency: @currency_2, owner: @alice, amount: 1)\n\n      assert Transaction.merge(%{address: @alice, currency: @currency_1}) ==\n               {:error, :single_input}\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns expected error when no inputs are found\" do\n      assert Transaction.merge(%{address: @alice, currency: @currency_1}) == {:error, :no_inputs_found}\n    end\n  end\n\n  describe \"merge/1 with utxo_positions parameter\" do\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"given  valid `utxo_positions` parameters, forms a merge transaction correctly\" do\n      insert_initial_utxo()\n\n      position_1 =\n        :txoutput |> insert(owner: @alice, currency: @currency_1, amount: 1) |> encoded_position_from_insert()\n\n      position_2 =\n        :txoutput |> insert(owner: @alice, currency: @currency_1, amount: 1) |> encoded_position_from_insert()\n\n      position_3 =\n        :txoutput |> insert(owner: @alice, currency: @currency_1, amount: 1) |> encoded_position_from_insert()\n\n      {:ok, [%{inputs: inputs, outputs: outputs}]} =\n        Transaction.merge([{:utxo_positions, [position_1, position_2, position_3]}])\n\n      assert length(inputs) == 3\n      assert [%{amount: 3, currency: @currency_1, owner: @alice}] = outputs\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns an error if any duplicate positions are in the list\" do\n      insert_initial_utxo()\n\n      position_1 = :txoutput |> insert() |> encoded_position_from_insert()\n\n      assert Transaction.merge([{:utxo_positions, [position_1, position_1]}]) ==\n               {:error, :duplicate_input_positions}\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns expected error if any position is not found\" do\n      insert_initial_utxo()\n\n      position_1 = :txoutput |> insert() |> encoded_position_from_insert()\n      position_2 = :txoutput |> insert() |> encoded_position_from_insert()\n      position_3 = :txoutput |> insert() |> encoded_position_from_insert()\n\n      empty_position = insert(:txoutput) |> Map.update!(:blknum, fn n -> n + 1 end) |> encoded_position_from_insert()\n\n      assert Transaction.merge([{:utxo_positions, [position_1, position_2, position_3, empty_position]}]) ==\n               {:error, :position_not_found}\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns an error if there is more than one owner for the given set of UTXO positions\" do\n      insert_initial_utxo()\n\n      position_1 = :txoutput |> insert(owner: @alice, currency: @currency_1) |> encoded_position_from_insert()\n      position_2 = :txoutput |> insert(owner: @alice, currency: @currency_1) |> encoded_position_from_insert()\n      position_3 = :txoutput |> insert(owner: @bob, currency: @currency_1) |> encoded_position_from_insert()\n\n      assert Transaction.merge([{:utxo_positions, [position_1, position_2, position_3]}]) ==\n               {:error, :multiple_input_owners}\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns an error if there is more than one currency for the given set of UTXO positions\" do\n      insert_initial_utxo()\n\n      position_1 = :txoutput |> insert(owner: @alice, currency: @currency_1) |> encoded_position_from_insert()\n      position_2 = :txoutput |> insert(owner: @alice, currency: @currency_1) |> encoded_position_from_insert()\n      position_3 = :txoutput |> insert(owner: @alice, currency: @currency_2) |> encoded_position_from_insert()\n\n      assert Transaction.merge([{:utxo_positions, [position_1, position_2, position_3]}]) ==\n               {:error, :multiple_currencies}\n    end\n  end\n\n  defp encoded_position_from_insert(%{oindex: oindex, txindex: txindex, blknum: blknum}) do\n    Position.encode({:utxo_position, blknum, txindex, oindex})\n  end\n\n  # This is needed so that UTXOs inserted subsequently can have a proper (non-zero) position\n  defp insert_initial_utxo() do\n    insert(:txoutput)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/test/omg_watcher_info/block_applicator_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherInfo.BlockApplicatorTest do\n  use ExUnitFixtures\n  use ExUnit.Case, async: false\n\n  alias OMG.Watcher.BlockGetter.BlockApplication\n  alias OMG.WatcherInfo.BlockApplicator\n  alias OMG.WatcherInfo.DB\n\n  import Ecto.Query, only: [where: 2]\n\n  setup do\n    eth = <<0::160>>\n    alice = OMG.Watcher.TestHelper.generate_entity()\n    tx = OMG.Watcher.TestHelper.create_recovered([{1, 0, 0, alice}], eth, [{alice, 100}])\n\n    block_application = %BlockApplication{\n      number: 1_000,\n      eth_height: 1,\n      eth_height_done: true,\n      hash: \"0x1000\",\n      transactions: [tx],\n      timestamp: 1_576_500_000\n    }\n\n    {:ok, block_application: block_application}\n  end\n\n  describe \"insert_block!\" do\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"inserts the given block application into pending block\", %{block_application: block_application} do\n      assert :ok = BlockApplicator.insert_block!(block_application)\n\n      assert [%DB.Block{blknum: 1_000}] = DB.Repo.all(DB.Block)\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"insert block operation is idempotent\", %{block_application: block_application} do\n      blknum = block_application.number\n      :ok = BlockApplicator.insert_block!(block_application)\n\n      assert :ok = BlockApplicator.insert_block!(block_application)\n      assert %DB.Block{blknum: ^blknum} = DB.Block |> where(blknum: ^blknum) |> DB.Repo.one()\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"breaks when block application is invalid\", %{block_application: block_application} do\n      block_application = %BlockApplication{block_application | number: \"not an integer\"}\n\n      assert_raise MatchError, fn -> BlockApplicator.insert_block!(block_application) end\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/test/omg_watcher_info/db/block/chunk_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherInfo.DB.Block.ChunkTest do\n  use ExUnitFixtures\n  use ExUnit.Case, async: false\n  use OMG.Watcher.Fixtures\n\n  import OMG.WatcherInfo.Factory\n\n  alias OMG.Watcher.Utxo\n  alias OMG.WatcherInfo.DB\n  alias OMG.WatcherInfo.DB.Block.Chunk\n\n  require Utxo\n\n  describe \"Chunk.chunk/3\" do\n    # The current number of columns on the transaction table allow up to 8191\n    # transactions to be inserted using `DB.Repo.insert_all/3` before chunking must\n    # be done to avoid hitting postgres limits. The test `DB.Repo.insert_all for\n    # transactions (via postgres INSERT)...` below shows how this number is derived\n    # and asserts the number is correct\n    #\n    # This is the chunk_size for the transactions table.\n    @max_txns_before_chunking 8191\n\n    # A special test for insert_all_chunked/3 is here because under the hood it calls insert_all/2. Using\n    # insert_all/3 with a queryable means that certain autogenerated columns, such as inserted_at and\n    # updated_at, will not be inserted as they would be if you used a plain insert. More info\n    # is here: https://hexdocs.pm/ecto/Ecto.Repo.html#c:insert_all/3\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"insert_all_chunked adds inserted_at and updated_at timestamps correctly\" do\n      txoutput =\n        :txoutput\n        |> params_for()\n        |> Map.drop([:ethevents])\n\n      Enum.each(Chunk.chunk([txoutput]), &DB.Repo.insert_all(DB.TxOutput, &1))\n\n      txoutput_with_dates =\n        DB.TxOutput.get_by_position(Utxo.position(txoutput.blknum, txoutput.txindex, txoutput.oindex))\n\n      assert txoutput_with_dates.inserted_at != nil\n      assert DateTime.compare(txoutput_with_dates.inserted_at, txoutput_with_dates.updated_at) == :eq\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"insert_all_chunked/3 does not exceed postgres' max of 65535 parameters\" do\n      block = insert(:block)\n\n      # Create an array of transactions beyond postgres limits where chunking\n      # is required.\n      transactions = new_transactions(block.blknum, @max_txns_before_chunking + 1)\n      assert Enum.map(Chunk.chunk(transactions), &DB.Repo.insert_all(DB.Transaction, &1)) == [{8191, nil}, {1, nil}]\n    end\n  end\n\n  @tag fixtures: [:phoenix_ecto_sandbox]\n  test \"DB.Repo.insert_all for transactions (via postgres INSERT) is limited to #{@max_txns_before_chunking} transactions\" do\n    utc_now = DateTime.utc_now()\n\n    # test that transaction inseration at the max limit succeeds\n    block = insert(:block)\n    transactions = new_transactions(block.blknum, @max_txns_before_chunking, utc_now)\n\n    {transactions_inserted, _} = DB.Repo.insert_all(OMG.WatcherInfo.DB.Transaction, transactions)\n\n    assert transactions_inserted == @max_txns_before_chunking\n\n    # test that transaction inseration above the max limit raises an exception\n    block = insert(:block)\n    transactions = new_transactions(block.blknum, @max_txns_before_chunking + 1, utc_now)\n\n    assert_raise(\n      Postgrex.QueryError,\n      \"postgresql protocol can not handle 65536 parameters, the maximum is 65535\",\n      fn -> DB.Repo.insert_all(OMG.WatcherInfo.DB.Transaction, transactions) end\n    )\n  end\n\n  describe \"DB.Repo timestamps\" do\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"all tables have inserted_at and updated_at timestamps set correctly on inserts and udpates\" do\n      Enum.each([:block, :transaction, :txoutput, :ethevent], fn row ->\n        row = insert(row)\n\n        assert row.inserted_at != nil\n        assert DateTime.compare(row.inserted_at, row.updated_at) == :eq\n\n        {:ok, row} = DB.Repo.update(Ecto.Changeset.change(row), [{:force, true}])\n\n        assert DateTime.compare(row.inserted_at, row.updated_at) == :lt\n      end)\n    end\n  end\n\n  # Prefer using `ExMachina.build_list/3 which uses `OMG.WatcherInfo.Factory.Transaction`\n  # over this function. This function is built to be fast and simple.\n  defp new_transactions(blknum, count, utc_now \\\\ nil) do\n    Enum.reduce(1..count, [], fn index, acc ->\n      [new_transaction(blknum, index, utc_now) | acc]\n    end)\n  end\n\n  # Prefer using `ExMachina.build/2 which uses `OMG.WatcherInfo.Factory.Transaction`\n  # over this function. This function is built to be fast and simple.\n  #\n  # `ExMachina.params_for/2` could be used here to make use of `OMG.WatcherInfo.Factory.Transaction`.\n  # But the transaction factory does a lot of extra stuff unnecessary for this test. This stripped\n  # down version is about 15x faster. Also using `ExMachina.params_for/2` here also requires some\n  # tweaking of the map it returns because `OMG.WatcherInfo.DB.Block.chunk/1` is the code\n  # being tested rather than `Ecto.Repo.insert_all/3`. The 2 functions differ in the inputs they\n  # expect.\n  defp new_transaction(blknum, index, utc_now) do\n    transaction = %{\n      txhash: to_string(index),\n      txindex: index,\n      txbytes: to_string(index),\n      metadata: to_string(index),\n      txtype: 1,\n      blknum: blknum\n    }\n\n    if utc_now != nil do\n      Map.merge(transaction, %{inserted_at: utc_now, updated_at: utc_now})\n    else\n      transaction\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/test/omg_watcher_info/db/block_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherInfo.DB.BlockTest do\n  use ExUnitFixtures\n  use ExUnit.Case, async: false\n  use OMG.Watcher.Fixtures\n\n  import OMG.WatcherInfo.Factory\n  import Ecto.Query, only: [from: 2]\n\n  alias OMG.Utils.Paginator\n  alias OMG.WatcherInfo.DB\n\n  @eth <<0::160>>\n  @seconds_in_twenty_four_hours 86_400\n\n  describe \"base_query\" do\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"can be used to retrieve all blocks\" do\n      _ = insert(:block, blknum: 1000, hash: <<1000>>, eth_height: 1, timestamp: 100)\n      _ = insert(:block, blknum: 2000, hash: <<2000>>, eth_height: 2, timestamp: 200)\n      _ = insert(:block, blknum: 3000, hash: <<3000>>, eth_height: 3, timestamp: 300)\n\n      result = DB.Repo.all(DB.Block.base_query())\n\n      assert length(result) == 3\n      assert Enum.all?(result, fn block -> %DB.Block{} = block end)\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"can be used with a 'where' query expression to retrieve a specific block\" do\n      _ = insert(:block, blknum: 1000, hash: <<1000>>, eth_height: 1, timestamp: 100)\n      _ = insert(:block, blknum: 2000, hash: <<2000>>, eth_height: 2, timestamp: 200)\n      _ = insert(:block, blknum: 3000, hash: <<3000>>, eth_height: 3, timestamp: 300)\n\n      target_blknum = 1000\n\n      query =\n        from(\n          block in DB.Block.base_query(),\n          where: [blknum: ^target_blknum]\n        )\n\n      result = DB.Repo.one(query)\n\n      assert %DB.Block{} = result\n      assert result.blknum == target_blknum\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"includes the transaction count corresponding to a block\" do\n      block = insert(:block)\n      _ = insert(:transaction, block: block, txindex: 0)\n      _ = insert(:transaction, block: block, txindex: 1)\n\n      tx_count =\n        DB.Block.base_query()\n        |> DB.Repo.all()\n        |> Enum.at(0)\n        |> Map.get(:tx_count)\n\n      assert tx_count == 2\n    end\n  end\n\n  describe \"get/1\" do\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"retrieves a block by block number\" do\n      blknum = 1000\n      _ = insert(:block, blknum: blknum, hash: \"0x#{blknum}\", eth_height: 1, timestamp: 100)\n      block = DB.Block.get(blknum)\n      assert %DB.Block{} = block\n      assert block.blknum == blknum\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns a correct transaction count if block contains transactions\" do\n      blknum = 1000\n\n      block = insert(:block, blknum: 1000)\n      _ = insert(:transaction, block: block, txindex: 0)\n      _ = insert(:transaction, block: block, txindex: 1)\n\n      result = DB.Block.get(blknum)\n\n      assert result.tx_count == 2\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns a tx_count of zero if block has no transactions\" do\n      blknum = 1000\n      _ = insert(:block, blknum: blknum, hash: \"0x#{blknum}\", eth_height: 1, timestamp: 100)\n\n      result = DB.Block.get(blknum)\n\n      assert result.tx_count == 0\n    end\n  end\n\n  describe \"get_max_blknum/0\" do\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"last consumed block is not set in empty database\" do\n      assert nil == DB.Block.get_max_blknum()\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"last consumed block returns correct block number\" do\n      _ = insert(:block, blknum: 1000, hash: <<1000>>, eth_height: 1, timestamp: 100)\n      _ = insert(:block, blknum: 2000, hash: <<2000>>, eth_height: 2, timestamp: 200)\n      _ = insert(:block, blknum: 3000, hash: <<3000>>, eth_height: 3, timestamp: 300)\n\n      assert 3000 == DB.Block.get_max_blknum()\n    end\n  end\n\n  describe \"get_blocks/1\" do\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns a list of blocks\" do\n      _ = insert(:block, blknum: 1000, hash: \"0x1000\", eth_height: 1, timestamp: 100)\n      _ = insert(:block, blknum: 2000, hash: \"0x2000\", eth_height: 2, timestamp: 200)\n      _ = insert(:block, blknum: 3000, hash: \"0x3000\", eth_height: 3, timestamp: 300)\n\n      paginator = %Paginator{\n        data: [],\n        data_paging: %{\n          limit: 10,\n          page: 1\n        }\n      }\n\n      results = DB.Block.get_blocks(paginator)\n\n      assert length(results.data) == 3\n      assert Enum.all?(results.data, fn block -> %DB.Block{} = block end)\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns a list of blocks sorted by descending blknum\" do\n      _ = insert(:block, blknum: 1000, hash: \"0x1000\", eth_height: 1, timestamp: 100)\n      _ = insert(:block, blknum: 2000, hash: \"0x2000\", eth_height: 2, timestamp: 200)\n      _ = insert(:block, blknum: 3000, hash: \"0x3000\", eth_height: 3, timestamp: 300)\n\n      paginator = %Paginator{\n        data: [],\n        data_paging: %{\n          limit: 10,\n          page: 1\n        }\n      }\n\n      results = DB.Block.get_blocks(paginator)\n\n      assert length(results.data) == 3\n      assert results.data |> Enum.at(0) |> Map.get(:blknum) == 3000\n      assert results.data |> Enum.at(1) |> Map.get(:blknum) == 2000\n      assert results.data |> Enum.at(2) |> Map.get(:blknum) == 1000\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns an empty list when given limit: 0\" do\n      paginator = %Paginator{\n        data: [],\n        data_paging: %{\n          limit: 0,\n          page: 1\n        }\n      }\n\n      results = DB.Block.get_blocks(paginator)\n\n      assert results.data == []\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns a correct transaction count if block contains transactions\" do\n      block = insert(:block, blknum: 1000)\n      _ = insert(:transaction, block: block, txindex: 0)\n      _ = insert(:transaction, block: block, txindex: 1)\n\n      paginator = %Paginator{\n        data: [],\n        data_paging: %{\n          limit: 10,\n          page: 1\n        }\n      }\n\n      tx_count =\n        DB.Block.get_blocks(paginator)\n        |> Map.get(:data)\n        |> Enum.at(0)\n        |> Map.get(:tx_count)\n\n      assert tx_count == 2\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns a tx_count of zero if block has no transactions\" do\n      _ = insert(:block, blknum: 1000, hash: \"0x1000\", eth_height: 1, timestamp: 100)\n\n      paginator = %Paginator{\n        data: [],\n        data_paging: %{\n          limit: 10,\n          page: 1\n        }\n      }\n\n      tx_count =\n        DB.Block.get_blocks(paginator)\n        |> Map.get(:data)\n        |> Enum.at(0)\n        |> Map.get(:tx_count)\n\n      assert tx_count == 0\n    end\n  end\n\n  describe \"count_all/0\" do\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns correct number of blocks\" do\n      _ = insert(:block, blknum: 1000, hash: \"0x1000\", eth_height: 1, timestamp: 100)\n      _ = insert(:block, blknum: 2000, hash: \"0x2000\", eth_height: 2, timestamp: 200)\n      _ = insert(:block, blknum: 3000, hash: \"0x3000\", eth_height: 3, timestamp: 300)\n\n      block_count = DB.Block.count_all()\n\n      assert block_count == 3\n    end\n  end\n\n  describe \"count_all_between_timestamps/2\" do\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns correct count if blocks have been produced between the two given timestamps\" do\n      end_datetime = DateTime.to_unix(DateTime.utc_now())\n      start_datetime = end_datetime - @seconds_in_twenty_four_hours\n\n      _ = insert(:block, blknum: 1000, hash: \"0x1000\", eth_height: 1, timestamp: start_datetime + 100)\n      _ = insert(:block, blknum: 2000, hash: \"0x2000\", eth_height: 2, timestamp: start_datetime)\n      _ = insert(:block, blknum: 3000, hash: \"0x3000\", eth_height: 3, timestamp: start_datetime - 100)\n\n      block_count = DB.Block.count_all_between_timestamps(start_datetime, end_datetime)\n\n      assert block_count == 2\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns correct count if no blocks have been produced between the two given timestamps\" do\n      end_datetime = DateTime.to_unix(DateTime.utc_now())\n      start_datetime = end_datetime - @seconds_in_twenty_four_hours\n\n      _ = insert(:block, blknum: 1000, hash: \"0x1000\", eth_height: 1, timestamp: start_datetime - 100)\n      _ = insert(:block, blknum: 2000, hash: \"0x2000\", eth_height: 2, timestamp: start_datetime - 100)\n      _ = insert(:block, blknum: 3000, hash: \"0x3000\", eth_height: 3, timestamp: start_datetime - 100)\n\n      block_count = DB.Block.count_all_between_timestamps(start_datetime, end_datetime)\n\n      assert block_count == 0\n    end\n  end\n\n  describe \"get_timestamp_range_all/0\" do\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"retrieves the timestamps of the earliest and latest block of all time correctly\" do\n      earliest_datetime = 100\n\n      _ = insert(:block, blknum: 1000, hash: \"0x1000\", eth_height: 1, timestamp: earliest_datetime)\n      _ = insert(:block, blknum: 2000, hash: \"0x2000\", eth_height: 2, timestamp: earliest_datetime + 100)\n      _ = insert(:block, blknum: 3000, hash: \"0x3000\", eth_height: 3, timestamp: earliest_datetime + 200)\n      _ = insert(:block, blknum: 4000, hash: \"0x4000\", eth_height: 3, timestamp: earliest_datetime + 300)\n\n      expected = %{\n        max: earliest_datetime + 300,\n        min: earliest_datetime\n      }\n\n      actual = DB.Block.get_timestamp_range_all()\n\n      assert expected == actual\n    end\n  end\n\n  describe \"get_timestamp_range_between/2\" do\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"retrieves the timestamps of the earliest and latest block within a given time range correctly\" do\n      end_datetime = DateTime.to_unix(DateTime.utc_now())\n      start_datetime = end_datetime - @seconds_in_twenty_four_hours\n\n      _ = insert(:block, blknum: 1000, hash: \"0x1000\", eth_height: 1, timestamp: start_datetime - 100)\n      _ = insert(:block, blknum: 2000, hash: \"0x2000\", eth_height: 2, timestamp: start_datetime)\n      _ = insert(:block, blknum: 3000, hash: \"0x3000\", eth_height: 3, timestamp: start_datetime + 100)\n      _ = insert(:block, blknum: 4000, hash: \"0x4000\", eth_height: 4, timestamp: start_datetime + 200)\n\n      expected = %{\n        max: start_datetime + 200,\n        min: start_datetime\n      }\n\n      actual = DB.Block.get_timestamp_range_between(start_datetime, end_datetime)\n\n      assert expected == actual\n    end\n  end\n\n  describe \"insert_from_block_application/1\" do\n    @tag fixtures: [:phoenix_ecto_sandbox, :alice, :bob]\n    test \"inserts the block, its transactions and transaction outputs\", %{alice: alice, bob: bob} do\n      tx_1 = OMG.Watcher.TestHelper.create_recovered([{1, 0, 0, alice}], @eth, [{bob, 300}])\n      tx_2 = OMG.Watcher.TestHelper.create_recovered([{1, 0, 0, alice}], @eth, [{bob, 500}])\n\n      block_application = %{\n        transactions: [tx_1, tx_2],\n        number: 1000,\n        hash: \"0x1000\",\n        timestamp: 1_576_500_000,\n        eth_height: 1\n      }\n\n      {:ok, block} = DB.Block.insert_from_block_application(block_application)\n\n      assert %DB.Block{} = block[\"current_block\"]\n      current_block_hash = block[\"current_block\"].hash\n      assert block_application.hash == current_block_hash\n\n      assert DB.Repo.get(DB.Transaction, tx_1.tx_hash)\n      assert DB.Repo.get(DB.Transaction, tx_2.tx_hash)\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox, :alice, :bob]\n    test \"returns an error when inserting with an existing blknum\", %{alice: alice, bob: bob} do\n      tx_1 = OMG.Watcher.TestHelper.create_recovered([{1, 0, 0, alice}], @eth, [{bob, 300}])\n      tx_2 = OMG.Watcher.TestHelper.create_recovered([{1, 0, 0, alice}], @eth, [{bob, 500}])\n\n      block_application = %{\n        transactions: [tx_1, tx_2],\n        number: 1000,\n        hash: \"0x1000\",\n        timestamp: 1_576_500_000,\n        eth_height: 1\n      }\n\n      {:ok, _block} = DB.Block.insert_from_block_application(block_application)\n\n      assert {:error, \"current_block\", changeset, %{}} = DB.Block.insert_from_block_application(block_application)\n\n      assert changeset.errors == [\n               blknum: {\"has already been taken\", [constraint: :unique, constraint_name: \"blocks_pkey\"]}\n             ]\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox, :alice, :bob]\n    @tag :watcher_info\n    @tag timeout: :infinity\n    test \"full block test\", %{alice: alice, bob: bob} do\n      tx_1 = OMG.Watcher.TestHelper.create_recovered([{1, 0, 0, alice}], @eth, [{bob, 1}])\n      tx_2 = OMG.Watcher.TestHelper.create_recovered([{1, 0, 0, alice}], @eth, [{bob, 2}])\n\n      transactions =\n        Enum.map(3..64_000, fn _index ->\n          a = OMG.Watcher.TestHelper.generate_entity()\n          b = OMG.Watcher.TestHelper.generate_entity()\n          amount = 5\n          OMG.Watcher.TestHelper.create_recovered([{1, 0, 0, a}], @eth, [{b, amount}])\n        end)\n\n      block_application = %{\n        transactions: [tx_1, tx_2] ++ transactions,\n        number: 1000,\n        hash: \"0x1000\",\n        timestamp: 1_576_500_000,\n        eth_height: 1\n      }\n\n      {:ok, block} = DB.Block.insert_from_block_application(block_application)\n\n      assert %DB.Block{} = block[\"current_block\"]\n      current_block_hash = block[\"current_block\"].hash\n      assert block_application.hash == current_block_hash\n\n      assert DB.Repo.get(DB.Transaction, tx_1.tx_hash)\n      assert DB.Repo.get(DB.Transaction, tx_2.tx_hash)\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/test/omg_watcher_info/db/eth_event_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherInfo.DB.EthEventTest do\n  use ExUnitFixtures\n  use ExUnit.Case, async: false\n  use OMG.Watcher.Fixtures\n\n  alias OMG.Utils.Paginator\n  alias OMG.Watcher.Crypto\n  alias OMG.Watcher.Utxo\n  alias OMG.Watcher.Utxo.Position\n  alias OMG.WatcherInfo.DB\n\n  import OMG.WatcherInfo.Factory\n\n  require Utxo\n\n  @eth <<0::160>>\n  @default_paginator %Paginator{\n    data: [],\n    data_paging: %{\n      limit: 10,\n      page: 1\n    }\n  }\n\n  @tag fixtures: [:phoenix_ecto_sandbox]\n  test \"insert deposits: creates deposit event and utxo\" do\n    expected_root_chain_txnhash = Crypto.hash(<<1::256>>)\n    expected_log_index = 0\n    expected_event_type = :deposit\n    expected_eth_height = 1\n\n    expected_blknum = 10_000\n    expected_txindex = 0\n    expected_oindex = 0\n    expected_owner = <<1::160>>\n    expected_currency = @eth\n    expected_amount = 1\n\n    root_chain_txhash_event =\n      DB.EthEvent.generate_root_chain_txhash_event(expected_root_chain_txnhash, expected_log_index)\n\n    expected_child_chain_utxohash =\n      DB.EthEvent.generate_child_chain_utxohash(Utxo.position(expected_blknum, expected_txindex, expected_oindex))\n\n    assert :ok =\n             DB.EthEvent.insert_deposits!([\n               %{\n                 root_chain_txhash: expected_root_chain_txnhash,\n                 log_index: expected_log_index,\n                 blknum: expected_blknum,\n                 owner: expected_owner,\n                 eth_height: expected_eth_height,\n                 currency: expected_currency,\n                 amount: expected_amount\n               }\n             ])\n\n    event = DB.EthEvent.get(root_chain_txhash_event)\n\n    assert %DB.EthEvent{\n             root_chain_txhash: ^expected_root_chain_txnhash,\n             log_index: ^expected_log_index,\n             event_type: ^expected_event_type,\n             eth_height: ^expected_eth_height\n           } = event\n\n    # check ethevent side of relationship\n    assert length(event.txoutputs) == 1\n\n    assert [\n             %DB.TxOutput{\n               blknum: ^expected_blknum,\n               txindex: ^expected_txindex,\n               oindex: ^expected_oindex,\n               owner: ^expected_owner,\n               amount: ^expected_amount,\n               currency: ^expected_currency,\n               creating_txhash: nil,\n               spending_txhash: nil,\n               spending_tx_oindex: nil,\n               proof: nil,\n               child_chain_utxohash: ^expected_child_chain_utxohash\n             }\n             | _tail\n           ] = event.txoutputs\n\n    # check txoutput side of relationship\n    txoutput = DB.TxOutput.get_by_position(Utxo.position(expected_blknum, expected_txindex, expected_oindex))\n\n    assert %DB.TxOutput{\n             blknum: ^expected_blknum,\n             txindex: ^expected_txindex,\n             oindex: ^expected_oindex,\n             owner: ^expected_owner,\n             amount: ^expected_amount,\n             currency: ^expected_currency,\n             creating_txhash: nil,\n             spending_txhash: nil,\n             spending_tx_oindex: nil,\n             proof: nil,\n             child_chain_utxohash: ^expected_child_chain_utxohash\n           } = txoutput\n\n    assert length(txoutput.ethevents) == 1\n\n    assert [\n             %DB.EthEvent{\n               root_chain_txhash: ^expected_root_chain_txnhash,\n               log_index: ^expected_log_index,\n               event_type: ^expected_event_type,\n               eth_height: ^expected_eth_height\n             }\n             | _tail\n           ] = txoutput.ethevents\n  end\n\n  @tag fixtures: [:phoenix_ecto_sandbox, :alice]\n  test \"insert deposits: creates deposits and retrieves them by hash\", %{alice: alice} do\n    expected_event_type = :deposit\n    expected_owner = alice.addr\n    expected_currency = @eth\n    expected_log_index = 0\n    expected_amount = 1\n    expected_eth_height = 1\n\n    expected_root_chain_txhash_1 = Crypto.hash(<<2::256>>)\n\n    expected_root_chain_txhash_event_1 =\n      DB.EthEvent.generate_root_chain_txhash_event(expected_root_chain_txhash_1, expected_log_index)\n\n    expected_blknum_1 = 20_000\n\n    expected_root_chain_txhash_2 = Crypto.hash(<<3::256>>)\n\n    expected_root_chain_txhash_event_2 =\n      DB.EthEvent.generate_root_chain_txhash_event(expected_root_chain_txhash_2, expected_log_index)\n\n    expected_blknum_2 = 30_000\n\n    expected_root_chain_txhash_3 = Crypto.hash(<<4::256>>)\n\n    expected_root_chain_txhash_event_3 =\n      DB.EthEvent.generate_root_chain_txhash_event(expected_root_chain_txhash_3, expected_log_index)\n\n    expected_blknum_3 = 40_000\n\n    assert :ok =\n             DB.EthEvent.insert_deposits!([\n               %{\n                 root_chain_txhash: expected_root_chain_txhash_1,\n                 log_index: expected_log_index,\n                 blknum: expected_blknum_1,\n                 owner: expected_owner,\n                 currency: expected_currency,\n                 amount: expected_amount,\n                 eth_height: expected_eth_height\n               },\n               %{\n                 root_chain_txhash: expected_root_chain_txhash_2,\n                 log_index: expected_log_index,\n                 blknum: expected_blknum_2,\n                 owner: expected_owner,\n                 currency: expected_currency,\n                 amount: expected_amount,\n                 eth_height: expected_eth_height\n               },\n               %{\n                 root_chain_txhash: expected_root_chain_txhash_3,\n                 log_index: expected_log_index,\n                 blknum: expected_blknum_3,\n                 owner: expected_owner,\n                 currency: expected_currency,\n                 amount: expected_amount,\n                 eth_height: expected_eth_height\n               }\n             ])\n\n    assert %DB.EthEvent{\n             root_chain_txhash: ^expected_root_chain_txhash_1,\n             event_type: ^expected_event_type,\n             root_chain_txhash_event: ^expected_root_chain_txhash_event_1,\n             eth_height: ^expected_eth_height\n           } = DB.EthEvent.get(expected_root_chain_txhash_event_1)\n\n    assert %DB.EthEvent{\n             root_chain_txhash: ^expected_root_chain_txhash_2,\n             event_type: ^expected_event_type,\n             root_chain_txhash_event: ^expected_root_chain_txhash_event_2,\n             eth_height: ^expected_eth_height\n           } = DB.EthEvent.get(expected_root_chain_txhash_event_2)\n\n    assert %DB.EthEvent{\n             root_chain_txhash: ^expected_root_chain_txhash_3,\n             event_type: ^expected_event_type,\n             root_chain_txhash_event: ^expected_root_chain_txhash_event_3,\n             eth_height: ^expected_eth_height\n           } = DB.EthEvent.get(expected_root_chain_txhash_event_3)\n\n    %{data: alice_utxos} = DB.TxOutput.get_utxos(address: alice.addr)\n\n    assert [^expected_root_chain_txhash_1, ^expected_root_chain_txhash_2, ^expected_root_chain_txhash_3] =\n             Enum.map(alice_utxos, fn txoutput ->\n               [head | _tail] = txoutput.ethevents\n               head.root_chain_txhash\n             end)\n  end\n\n  @tag fixtures: [:phoenix_ecto_sandbox]\n  test \"insert exits: creates exit event and marks utxo as spent\" do\n    expected_owner = <<1::160>>\n    expected_log_index = 0\n    expected_amount = 1\n    expected_currency = @eth\n\n    expected_blknum = 50_000\n    expected_txindex = 0\n    expected_oindex = 0\n\n    expected_utxo_encoded_position = Position.encode(Utxo.position(expected_blknum, expected_txindex, expected_oindex))\n\n    expected_deposit_root_chain_txhash = Crypto.hash(<<5::256>>)\n    expected_exit_root_chain_txhash = Crypto.hash(<<6::256>>)\n\n    expected_deposit_eth_height = 1\n    expected_exit_eth_height = 2\n\n    assert :ok =\n             DB.EthEvent.insert_deposits!([\n               %{\n                 root_chain_txhash: expected_deposit_root_chain_txhash,\n                 log_index: expected_log_index,\n                 blknum: expected_blknum,\n                 owner: expected_owner,\n                 currency: expected_currency,\n                 amount: expected_amount,\n                 eth_height: expected_deposit_eth_height\n               }\n             ])\n\n    %{data: utxos} = DB.TxOutput.get_utxos(address: expected_owner)\n    assert length(utxos) == 1\n\n    assert :ok =\n             DB.EthEvent.insert_exits!(\n               [\n                 %{\n                   call_data: %{utxo_pos: expected_utxo_encoded_position},\n                   root_chain_txhash: expected_exit_root_chain_txhash,\n                   log_index: expected_log_index,\n                   eth_height: expected_exit_eth_height\n                 }\n               ],\n               :standard_exit,\n               nil\n             )\n\n    %{data: utxos_after_exit} = DB.TxOutput.get_utxos(address: expected_owner)\n    assert Enum.empty?(utxos_after_exit)\n  end\n\n  @tag fixtures: [:alice, :initial_blocks]\n  test \"Writes of deposits and exits are idempotent\", %{alice: alice} do\n    # try to insert again existing deposit (from initial_blocks)\n    assert :ok =\n             DB.EthEvent.insert_deposits!([\n               %{\n                 root_chain_txhash: Crypto.hash(<<1000::256>>),\n                 eth_height: 1,\n                 log_index: 0,\n                 owner: alice.addr,\n                 currency: @eth,\n                 amount: 333,\n                 blknum: 1\n               }\n             ])\n\n    exits = [\n      %{\n        root_chain_txhash: Crypto.hash(<<1000::256>>),\n        log_index: 1,\n        call_data: %{utxo_pos: Utxo.Position.encode(Utxo.position(1, 0, 0))},\n        eth_height: 2\n      },\n      %{\n        root_chain_txhash: Crypto.hash(<<1000::256>>),\n        log_index: 1,\n        call_data: %{utxo_pos: Utxo.Position.encode(Utxo.position(1, 0, 0))},\n        eth_height: 2\n      }\n    ]\n\n    assert :ok = DB.EthEvent.insert_exits!(exits, :in_flight_exit, :InFlightExitStarted)\n  end\n\n  @tag fixtures: [:alice, :initial_blocks]\n  test \"Can spend multiple outputs with single start_ife event\", %{alice: alice} do\n    expected_log_index = 0\n    expected_eth_height = 0\n    expected_eth_txhash = Crypto.hash(<<6::256>>)\n    expected_event_type = :in_flight_exit\n\n    %{data: utxos} = DB.TxOutput.get_utxos(address: alice.addr)\n\n    [\n      %DB.TxOutput{blknum: blknum1, txindex: txindex1, oindex: oindex1},\n      %DB.TxOutput{blknum: blknum2, txindex: txindex2, oindex: oindex2} | _\n    ] = utxos\n\n    utxo_pos1 = Utxo.position(blknum1, txindex1, oindex1)\n    utxo_pos2 = Utxo.position(blknum2, txindex2, oindex2)\n\n    exits = [\n      %{\n        root_chain_txhash: expected_eth_txhash,\n        log_index: expected_log_index,\n        eth_height: expected_eth_height,\n        call_data: %{utxo_pos: Utxo.Position.encode(utxo_pos1)}\n      },\n      %{\n        root_chain_txhash: expected_eth_txhash,\n        log_index: expected_log_index,\n        eth_height: expected_eth_height,\n        call_data: %{utxo_pos: Utxo.Position.encode(utxo_pos2)}\n      }\n    ]\n\n    assert :ok = DB.EthEvent.insert_exits!(exits, expected_event_type, :InFlightExitStarted)\n\n    txo1 = DB.TxOutput.get_by_position(utxo_pos1)\n    assert txo1 != nil\n\n    assert [\n             %DB.EthEvent{\n               log_index: ^expected_log_index,\n               root_chain_txhash: ^expected_eth_txhash,\n               event_type: ^expected_event_type\n             }\n           ] = txo1.ethevents\n\n    txo2 = DB.TxOutput.get_by_position(utxo_pos2)\n    assert txo2 != nil\n\n    assert [\n             %DB.EthEvent{\n               log_index: ^expected_log_index,\n               root_chain_txhash: ^expected_eth_txhash,\n               event_type: ^expected_event_type\n             }\n           ] = txo2.ethevents\n  end\n\n  @tag fixtures: [:alice, :initial_blocks]\n  test \"Can spend ife piggybacked output\", %{alice: alice} do\n    expected_log_index1 = 0\n    expected_log_index2 = 1\n    expected_eth_height1 = 0\n    expected_eth_height2 = 1\n    expected_eth_txhash1 = Crypto.hash(<<6::256>>)\n    expected_eth_txhash2 = Crypto.hash(<<7::256>>)\n    expected_event_type = :in_flight_exit\n\n    %{data: utxos} = DB.TxOutput.get_utxos(address: alice.addr)\n\n    [\n      %DB.TxOutput{creating_txhash: txhash1, oindex: oindex1},\n      %DB.TxOutput{creating_txhash: txhash2, oindex: oindex2}\n    ] =\n      utxos\n      |> Enum.drop(1)\n      |> Enum.take(2)\n\n    exits = [\n      %{\n        root_chain_txhash: expected_eth_txhash1,\n        log_index: expected_log_index1,\n        eth_height: expected_eth_height1,\n        call_data: %{txhash: txhash1, oindex: oindex1}\n      },\n      %{\n        root_chain_txhash: expected_eth_txhash2,\n        log_index: expected_log_index2,\n        eth_height: expected_eth_height2,\n        call_data: %{txhash: txhash2, oindex: oindex2}\n      }\n    ]\n\n    assert :ok = DB.EthEvent.insert_exits!(exits, expected_event_type, :InFlightExitOutputWithdrawn)\n\n    assert_txoutput_spent_by_event(\n      txhash1,\n      oindex1,\n      expected_log_index1,\n      expected_eth_txhash1,\n      expected_eth_height1,\n      expected_event_type\n    )\n\n    assert_txoutput_spent_by_event(\n      txhash2,\n      oindex2,\n      expected_log_index2,\n      expected_eth_txhash2,\n      expected_eth_height2,\n      expected_event_type\n    )\n  end\n\n  @tag fixtures: [:initial_blocks]\n  test \"Allows for missing output when the event explicitly allows it\" do\n    max_blknum = DB.Repo.aggregate(DB.TxOutput, :max, :blknum)\n    pos_from_future = Utxo.position(max_blknum + 1, 0, 0)\n\n    exits = [\n      %{\n        root_chain_txhash: Crypto.hash(<<6::256>>),\n        log_index: 0,\n        eth_height: 0,\n        call_data: %{utxo_pos: Utxo.Position.encode(pos_from_future)}\n      }\n    ]\n\n    assert :ok = DB.EthEvent.insert_exits!(exits, :in_flight_exit, :InFlightTxOutputPiggybacked)\n  end\n\n  @tag fixtures: [:initial_blocks]\n  test \"Fails when missing output disallowed\" do\n    max_blknum = DB.Repo.aggregate(DB.TxOutput, :max, :blknum)\n    pos_from_future = Utxo.position(max_blknum + 1, 0, 0)\n\n    exits = [\n      %{\n        root_chain_txhash: Crypto.hash(<<6::256>>),\n        log_index: 0,\n        eth_height: 0,\n        call_data: %{utxo_pos: Utxo.Position.encode(pos_from_future)}\n      }\n    ]\n\n    assert_raise CaseClauseError, fn ->\n      DB.EthEvent.insert_exits!(exits, :in_flight_exit, :InFlightExitOutputWithdrawn)\n    end\n  end\n\n  @tag fixtures: [:phoenix_ecto_sandbox, :alice]\n  test \"deposited and exited utxo is retrievable by position\", %{alice: alice} do\n    assert :ok =\n             DB.EthEvent.insert_deposits!([\n               %{\n                 root_chain_txhash: Crypto.hash(<<1000::256>>),\n                 eth_height: 1,\n                 log_index: 0,\n                 owner: alice.addr,\n                 currency: @eth,\n                 amount: 333,\n                 blknum: 1\n               }\n             ])\n\n    assert :ok =\n             DB.EthEvent.insert_exits!(\n               [\n                 %{\n                   root_chain_txhash: Crypto.hash(<<1001::256>>),\n                   eth_height: 2,\n                   log_index: 1,\n                   call_data: %{utxo_pos: Utxo.Position.encode(Utxo.position(1, 0, 0))}\n                 }\n               ],\n               :in_flight_exit,\n               :InFlightExitStarted\n             )\n\n    assert %DB.TxOutput{ethevents: events} = DB.TxOutput.get_by_position(Utxo.position(1, 0, 0))\n\n    assert [\n             %DB.EthEvent{event_type: :deposit},\n             %DB.EthEvent{event_type: :in_flight_exit}\n           ] = Enum.sort(events, &(&1.eth_height < &2.eth_height))\n  end\n\n  @tag fixtures: [:initial_blocks]\n  test \"only one exit type can be associated to output which is retrievable by output_id\",\n       %{initial_blocks: blocks} do\n    # Get the transaction with _unspent_ output\n    {blknum, txindex, txhash, _tx} = Enum.find(blocks, fn {blknum, _, _, _} -> blknum == 2000 end)\n    utxo_pos = Utxo.Position.encode(Utxo.position(blknum, txindex, 0))\n\n    assert :ok =\n             DB.EthEvent.insert_exits!(\n               [\n                 %{\n                   root_chain_txhash: Crypto.hash(<<1001::256>>),\n                   eth_height: 1,\n                   log_index: 0,\n                   call_data: %{utxo_pos: utxo_pos}\n                 }\n               ],\n               :standard_exit,\n               nil\n             )\n\n    assert :ok =\n             DB.EthEvent.insert_exits!(\n               [\n                 %{\n                   root_chain_txhash: Crypto.hash(<<1002::256>>),\n                   eth_height: 2,\n                   log_index: 1,\n                   call_data: %{utxo_pos: utxo_pos}\n                 }\n               ],\n               :in_flight_exit,\n               :InFlightExitStarted\n             )\n\n    assert %DB.TxOutput{ethevents: events} = DB.TxOutput.get_by_output_id(txhash, 0)\n\n    assert [%DB.EthEvent{event_type: :standard_exit}] = events\n  end\n\n  defp assert_txoutput_spent_by_event(txhash, oindex, log_index, eth_txhash, eth_height, event_type) do\n    txo = DB.TxOutput.get_by_output_id(txhash, oindex)\n\n    assert txo != nil\n\n    assert [\n             %DB.EthEvent{\n               log_index: ^log_index,\n               root_chain_txhash: ^eth_txhash,\n               eth_height: ^eth_height,\n               event_type: ^event_type\n             }\n           ] = txo.ethevents\n  end\n\n  describe \"get_deposits\" do\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"filters deposits by address\" do\n      owner_1 = <<1::160>>\n      owner_2 = <<2::160>>\n\n      deposit_output_1 = build(:txoutput, %{owner: owner_1})\n      deposit_output_2 = build(:txoutput, %{owner: owner_2})\n\n      _ = insert(:ethevent, event_type: :deposit, txoutputs: [deposit_output_1])\n      _ = insert(:ethevent, event_type: :deposit, txoutputs: [deposit_output_2])\n\n      %{data: [deposit_1]} = DB.EthEvent.get_deposits(@default_paginator, owner_1)\n      %{data: [deposit_2]} = DB.EthEvent.get_deposits(@default_paginator, owner_2)\n\n      assert deposit_1 |> Map.get(:txoutputs) |> Enum.at(0) |> Map.get(:owner) == owner_1\n      assert deposit_2 |> Map.get(:txoutputs) |> Enum.at(0) |> Map.get(:owner) == owner_2\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns deposits sorted by descending eth_height\" do\n      owner = <<1::160>>\n\n      _ = insert(:ethevent, eth_height: 1, txoutputs: [build(:txoutput, %{owner: owner})])\n      _ = insert(:ethevent, eth_height: 3, txoutputs: [build(:txoutput, %{owner: owner})])\n      _ = insert(:ethevent, eth_height: 2, txoutputs: [build(:txoutput, %{owner: owner})])\n\n      results = DB.EthEvent.get_deposits(@default_paginator, owner)\n\n      assert results.data |> Enum.at(0) |> Map.get(:eth_height) == 3\n      assert results.data |> Enum.at(1) |> Map.get(:eth_height) == 2\n      assert results.data |> Enum.at(2) |> Map.get(:eth_height) == 1\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"pagination - correctly paginates responses\" do\n      owner = <<1::160>>\n\n      _ = insert(:ethevent, txoutputs: [build(:txoutput, %{owner: owner})])\n      _ = insert(:ethevent, txoutputs: [build(:txoutput, %{owner: owner})])\n      _ = insert(:ethevent, txoutputs: [build(:txoutput, %{owner: owner})])\n\n      paginator_1 = %Paginator{\n        data: [],\n        data_paging: %{\n          limit: 2,\n          page: 1\n        }\n      }\n\n      paginator_2 = %Paginator{\n        data: [],\n        data_paging: %{\n          limit: 2,\n          page: 2\n        }\n      }\n\n      %{data: data_page_1} = DB.EthEvent.get_deposits(paginator_1, owner)\n      %{data: data_page_2} = DB.EthEvent.get_deposits(paginator_2, owner)\n\n      assert length(data_page_1) == 2\n      assert length(data_page_2) == 1\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"pagination - returns empty array if given limit 0\" do\n      owner = <<1::160>>\n\n      _ = insert(:ethevent, txoutputs: [build(:txoutput, %{owner: owner})])\n      _ = insert(:ethevent, txoutputs: [build(:txoutput, %{owner: owner})])\n      _ = insert(:ethevent, txoutputs: [build(:txoutput, %{owner: owner})])\n\n      paginator = %Paginator{\n        data: [],\n        data_paging: %{\n          limit: 0,\n          page: 1\n        }\n      }\n\n      %{data: data} = DB.EthEvent.get_deposits(paginator, owner)\n\n      assert Enum.empty?(data)\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/test/omg_watcher_info/db/transaction_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherInfo.DB.TransactionTest do\n  @moduledoc \"\"\"\n  Currently, this test focuses on testing behaviors not testable via Controllers.TransactionTest.\n\n  The reason is that we are treating the DB schema etc. as implementation detail. In case testing through controllers\n  becomes hard/slow or otherwise unreasnable, refactor these two kinds of tests appropriately\n  \"\"\"\n\n  use ExUnitFixtures\n  use ExUnit.Case, async: false\n  use OMG.Watcher.Fixtures\n  use Plug.Test\n\n  alias OMG.Utils.Paginator\n  alias OMG.Watcher.Utxo\n  alias OMG.WatcherInfo.DB\n\n  require Utxo\n  import ExUnit.CaptureLog\n  import OMG.WatcherInfo.Factory\n\n  @seconds_in_twenty_four_hours 86_400\n\n  @tag fixtures: [:initial_blocks]\n  test \"the associated block can be preloaded\" do\n    preloaded =\n      DB.Transaction.get_by_position(3000, 1)\n      |> DB.Repo.preload(:block)\n\n    assert %DB.Transaction{\n             blknum: 3000,\n             txindex: 1,\n             block: %DB.Block{blknum: 3000}\n           } = preloaded\n  end\n\n  @tag fixtures: [:initial_blocks]\n  test \"gets all transactions from a block\", %{initial_blocks: initial_blocks} do\n    # this test is here to ensure that calls coming from places other than `transaction` controllers are covered\n    [tx0, tx1] = DB.Transaction.get_by_blknum(3000)\n\n    tx_hashes =\n      initial_blocks\n      |> Enum.filter(&(elem(&1, 0) == 3000))\n      |> Enum.map(&elem(&1, 2))\n\n    assert tx_hashes == [tx0, tx1] |> Enum.map(& &1.txhash)\n\n    assert [] == DB.Transaction.get_by_blknum(5000)\n  end\n\n  @tag fixtures: [:initial_blocks]\n  test \"passing constrains out of allowed takes no effect and print a warning\" do\n    assert capture_log([level: :warn], fn ->\n             DB.Transaction.get_by_filters(\n               [blknum: 2000, nothing: \"there's no such thing\"],\n               %Paginator{}\n             )\n           end) =~\n             \"Constraint on :nothing does not exist in schema and was dropped from the query\"\n  end\n\n  describe \"count_all/0\" do\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns a correct transaction count\" do\n      block = insert(:block, blknum: 1000)\n      _ = insert(:transaction, block: block, txindex: 0)\n      _ = insert(:transaction, block: block, txindex: 1)\n\n      tx_count = DB.Transaction.count_all()\n\n      assert tx_count == 2\n    end\n  end\n\n  describe \"get/1\" do\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns the transaction from its hash with all data\" do\n      block = insert(:block, blknum: 1000)\n      %{txhash: txhash} = insert(:transaction, block: block, txindex: 0, txtype: 1)\n      _ = insert(:transaction, block: block, txindex: 1, txtype: 3)\n\n      tx = DB.Transaction.get(txhash)\n\n      assert tx.txindex == 0\n      assert tx.txtype == 1\n      assert tx.txhash == txhash\n    end\n  end\n\n  describe \"count_all_between_timestamp/2\" do\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns correct count if transactions have been made between the given timestamps\" do\n      end_datetime = DateTime.to_unix(DateTime.utc_now())\n      start_datetime = end_datetime - @seconds_in_twenty_four_hours\n\n      block = insert(:block, blknum: 1000, timestamp: start_datetime + 100)\n      _ = insert(:transaction, block: block, txindex: 0)\n      _ = insert(:transaction, block: block, txindex: 1)\n\n      tx_count = DB.Transaction.count_all_between_timestamps(start_datetime, end_datetime)\n\n      assert tx_count == 2\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns correct count if no transactions have been made between the given timestamps\" do\n      end_datetime = DateTime.to_unix(DateTime.utc_now())\n      start_datetime = end_datetime - @seconds_in_twenty_four_hours\n\n      block = insert(:block, blknum: 1000, timestamp: start_datetime - 100)\n      _ = insert(:transaction, block: block, txindex: 0)\n      _ = insert(:transaction, block: block, txindex: 1)\n\n      tx_count = DB.Transaction.count_all_between_timestamps(start_datetime, end_datetime)\n\n      assert tx_count == 0\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/test/omg_watcher_info/db/txoutput_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherInfo.DB.TxOutputTest do\n  use ExUnitFixtures\n  use ExUnit.Case, async: false\n  use OMG.Watcher.Fixtures\n\n  import OMG.WatcherInfo.Factory\n\n  alias OMG.Watcher.TestHelper\n  alias OMG.Watcher.Utxo\n  alias OMG.WatcherInfo.DB\n\n  require Utxo\n\n  @eth <<0::160>>\n\n  @tag fixtures: [:phoenix_ecto_sandbox, :alice]\n  test \"transaction output schema handles big numbers properly\", %{alice: alice} do\n    power_of_2 = fn n -> :lists.duplicate(n, 2) |> Enum.reduce(&(&1 * &2)) end\n    assert 16 == power_of_2.(4)\n\n    big_amount = power_of_2.(256) - 1\n\n    block_application = %{\n      transactions: [OMG.Watcher.TestHelper.create_recovered([], @eth, [{alice, big_amount}])],\n      number: 11_000,\n      hash: <<?#::256>>,\n      timestamp: :os.system_time(:second),\n      eth_height: 10\n    }\n\n    {:ok, _} = DB.Block.insert_from_block_application(block_application)\n\n    utxo = DB.TxOutput.get_by_position(Utxo.position(11_000, 0, 0))\n    assert not is_nil(utxo)\n    assert utxo.amount == big_amount\n  end\n\n  describe \"create_outputs/4\" do\n    @tag fixtures: [:phoenix_ecto_sandbox, :alice]\n    test \"create outputs according to params\", %{alice: alice} do\n      blknum = 11_000\n      amount_1 = 1000\n      amount_2 = 2000\n      tx = OMG.Watcher.TestHelper.create_recovered([], @eth, [{alice, amount_1}, {alice, amount_2}])\n\n      assert [\n               %{\n                 amount: amount_1,\n                 blknum: blknum,\n                 creating_txhash: tx.tx_hash,\n                 currency: @eth,\n                 oindex: 0,\n                 otype: 1,\n                 owner: alice.addr,\n                 txindex: 0\n               },\n               %{\n                 amount: amount_2,\n                 blknum: blknum,\n                 creating_txhash: tx.tx_hash,\n                 currency: @eth,\n                 oindex: 1,\n                 otype: 1,\n                 owner: alice.addr,\n                 txindex: 0\n               }\n             ] == DB.TxOutput.create_outputs(blknum, 0, tx.tx_hash, tx)\n    end\n  end\n\n  describe \"OMG.WatcherInfo.DB.TxOutput.spend_utxos/3\" do\n    # a special test for spend_utxos/3 is here because under the hood it calls update_all/3. using\n    # update_all/3 with a queryable means that certain autogenerated columns, such as inserted_at and\n    # updated_at, will not be updated as they would be if you used a plain update. More info\n    # is here: https://hexdocs.pm/ecto/Ecto.Repo.html#c:update_all/3\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"spend_utxos updates the updated_at timestamp correctly\" do\n      deposit = :txoutput |> build() |> with_deposit()\n\n      :transaction |> insert() |> with_inputs([deposit])\n\n      txinput = DB.TxOutput.get_by_position(Utxo.position(deposit.blknum, deposit.txindex, deposit.oindex))\n\n      spend_utxo_params = spend_uxto_params_from_txoutput(txinput)\n\n      _ = DB.Repo.transaction(DB.TxOutput.spend_utxos(Ecto.Multi.new(), [spend_utxo_params]))\n\n      spent_txoutput = DB.TxOutput.get_by_position(Utxo.position(txinput.blknum, txinput.txindex, txinput.oindex))\n\n      assert :eq == DateTime.compare(txinput.inserted_at, spent_txoutput.inserted_at)\n      assert :lt == DateTime.compare(txinput.updated_at, spent_txoutput.updated_at)\n    end\n  end\n\n  describe \"get_utxos_grouped_by_currency/1\" do\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns outputs grouped by currency\" do\n      alice = TestHelper.generate_entity()\n      currency_1 = <<1::160>>\n      currency_2 = <<2::160>>\n\n      _ = insert(:txoutput, currency: currency_1, owner: alice.addr)\n      _ = insert(:txoutput, currency: currency_2, owner: alice.addr)\n      _ = insert(:txoutput, currency: currency_1, owner: alice.addr)\n      _ = insert(:txoutput, currency: currency_2, owner: alice.addr)\n\n      result = DB.TxOutput.get_sorted_grouped_utxos(alice.addr, :desc)\n\n      assert Map.keys(result) == [currency_1, currency_2]\n\n      Enum.each(result, fn {currency, outputs} ->\n        assert length(outputs) == 2\n        Enum.each(outputs, fn output -> assert output.currency == currency end)\n      end)\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns outputs for the given address only\" do\n      alice = <<1::160>>\n      bob = <<2::160>>\n\n      _ = insert(:txoutput, owner: alice)\n      _ = insert(:txoutput, owner: alice)\n      _ = insert(:txoutput, owner: bob)\n\n      result = DB.TxOutput.get_sorted_grouped_utxos(alice, :desc)\n\n      Enum.each(result, fn {_currency, outputs} ->\n        Enum.each(outputs, fn output -> assert output.owner == alice end)\n      end)\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns outputs for a given currency in descending amount order if specified\" do\n      alice = <<1::160>>\n\n      _ = insert(:txoutput, amount: 100, currency: @eth, owner: alice)\n      _ = insert(:txoutput, amount: 200, currency: @eth, owner: alice)\n      _ = insert(:txoutput, amount: 300, currency: @eth, owner: alice)\n\n      result = alice |> DB.TxOutput.get_sorted_grouped_utxos(:desc) |> Map.get(@eth)\n\n      assert result |> Enum.at(0) |> Map.get(:amount) == 300\n      assert result |> Enum.at(1) |> Map.get(:amount) == 200\n      assert result |> Enum.at(2) |> Map.get(:amount) == 100\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns outputs for a given currency in ascending amount order if specified\" do\n      alice = <<1::160>>\n\n      _ = insert(:txoutput, amount: 100, currency: @eth, owner: alice)\n      _ = insert(:txoutput, amount: 200, currency: @eth, owner: alice)\n      _ = insert(:txoutput, amount: 300, currency: @eth, owner: alice)\n\n      result = alice |> DB.TxOutput.get_sorted_grouped_utxos(:asc) |> Map.get(@eth)\n\n      assert result |> Enum.at(0) |> Map.get(:amount) == 100\n      assert result |> Enum.at(1) |> Map.get(:amount) == 200\n      assert result |> Enum.at(2) |> Map.get(:amount) == 300\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/test/omg_watcher_info/http_rpc/adapter_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherInfo.HttpRPC.AdapterTest do\n  use ExUnit.Case, async: true\n\n  import FakeServer\n\n  alias OMG.Utils.AppVersion\n  alias OMG.WatcherInfo.HttpRPC.Adapter\n\n  describe \"get_unparsed_response_body/1\" do\n    test \"returns an unparsed body when successful\" do\n      body = \"{\\r\\n  \\\"success\\\": true,\\r\\n  \\\"data\\\": {\\r\\n    \\\"test\\\": \\\"something\\\"\\r\\n  }\\r\\n}\"\n      assert {:ok, response} = Adapter.get_unparsed_response_body(%HTTPoison.Response{status_code: 200, body: body})\n      assert response == %{\"test\" => \"something\"}\n    end\n\n    test \"returns a `client_error` error with the data when failed\" do\n      body = \"{\\r\\n  \\\"success\\\": false,\\r\\n  \\\"data\\\": {\\r\\n    \\\"test\\\": \\\"something\\\"\\r\\n  }\\r\\n}\"\n      assert {:error, response} = Adapter.get_unparsed_response_body(%HTTPoison.Response{status_code: 200, body: body})\n      assert response == {:client_error, %{\"test\" => \"something\"}}\n    end\n\n    test \"returns a `malformed_response` error with the data when the body is not recognized\" do\n      body = \"{\\r\\n  \\\"malformed\\\": \\\"body\\\"\\r\\n}\"\n      assert {:error, response} = Adapter.get_unparsed_response_body(%HTTPoison.Response{status_code: 200, body: body})\n      assert response == {:malformed_response, %{\"malformed\" => \"body\"}}\n    end\n\n    test \"returns a `childchain_unreachable` error when `econnrefused` is returned\" do\n      assert {:error, :childchain_unreachable} =\n               Adapter.get_unparsed_response_body({:error, %HTTPoison.Error{id: nil, reason: :econnrefused}})\n    end\n\n    test \"returns the HTTPoison error reason when present\" do\n      assert {:error, :a_reason} =\n               Adapter.get_unparsed_response_body({:error, %HTTPoison.Error{id: nil, reason: :a_reason}})\n    end\n  end\n\n  describe \"get_response_body/1\" do\n    test \"returns a body with the key parsed when successful\" do\n      body = \"{\\r\\n  \\\"success\\\": true,\\r\\n  \\\"data\\\": {\\r\\n    \\\"test\\\": \\\"something\\\"\\r\\n  }\\r\\n}\"\n      assert {:ok, response} = Adapter.get_response_body(%HTTPoison.Response{status_code: 200, body: body})\n      assert response == %{test: \"something\"}\n    end\n\n    test \"returns a `client_error` error with the data when failed\" do\n      body = \"{\\r\\n  \\\"success\\\": false,\\r\\n  \\\"data\\\": {\\r\\n    \\\"test\\\": \\\"something\\\"\\r\\n  }\\r\\n}\"\n      assert {:error, response} = Adapter.get_response_body(%HTTPoison.Response{status_code: 200, body: body})\n      assert response == {:client_error, %{\"test\" => \"something\"}}\n    end\n\n    test \"returns a `malformed_response` error with the data when the body is not recognized\" do\n      body = \"{\\r\\n  \\\"malformed\\\": \\\"body\\\"\\r\\n}\"\n      assert {:error, response} = Adapter.get_response_body(%HTTPoison.Response{status_code: 200, body: body})\n      assert response == {:malformed_response, %{\"malformed\" => \"body\"}}\n    end\n  end\n\n  describe \"rpc_post/3\" do\n    test_with_server \"includes X-Watcher-Version header\" do\n      route(\"/path\", FakeServer.Response.ok())\n      _ = Adapter.rpc_post(%{}, \"path\", FakeServer.address())\n\n      expected_watcher_version = AppVersion.version(:omg_watcher_info)\n\n      assert request_received(\n               \"/path\",\n               method: \"POST\",\n               headers: %{\"content-type\" => \"application/json\", \"x-watcher-version\" => expected_watcher_version}\n             )\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/test/omg_watcher_info/order_fee_fetcher_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherInfo.OrderFeeFetcherTest do\n  use ExUnitFixtures\n  use ExUnit.Case, async: true\n  use OMG.WatcherInfo.Fixtures\n\n  alias OMG.Utils.HttpRPC.Encoding\n  alias OMG.Watcher.WireFormatTypes\n  alias OMG.WatcherInfo.OrderFeeFetcher\n  alias OMG.WatcherInfo.TestServer\n\n  @eth <<0::160>>\n  @not_eth <<1::160>>\n  @tx_type WireFormatTypes.tx_type_for(:tx_payment_v1)\n  @str_tx_type Integer.to_string(@tx_type)\n\n  setup do\n    context = TestServer.start()\n    on_exit(fn -> TestServer.stop(context) end)\n    context\n  end\n\n  describe \"add_fee_to_order/2\" do\n    test \"adds the correct amount to the order\", context do\n      prepare_test_server(context, %{\n        @str_tx_type => [\n          %{\n            \"currency\" => Encoding.to_hex(@eth),\n            \"amount\" => 2,\n            \"subunit_to_unit\" => 1_000_000_000_000_000_000,\n            \"pegged_amount\" => 4,\n            \"pegged_currency\" => \"USD\",\n            \"pegged_subunit_to_unit\" => 100,\n            \"updated_at\" => \"2019-01-01T10:10:00+00:00\"\n          }\n        ]\n      })\n\n      order = %{\n        fee: %{currency: @eth}\n      }\n\n      assert OrderFeeFetcher.add_fee_to_order(order, context.fake_addr) ==\n               {:ok, Kernel.put_in(order, [:fee, :amount], 2)}\n    end\n\n    test \"returns an `unexpected_fee_currency` error when the child chain returns an unexpected fee value\", context do\n      prepare_test_server(context, %{\n        @str_tx_type => [\n          %{\n            \"currency\" => Encoding.to_hex(@not_eth),\n            \"amount\" => 2,\n            \"subunit_to_unit\" => 1_000_000_000_000_000_000,\n            \"pegged_amount\" => 4,\n            \"pegged_currency\" => \"USD\",\n            \"pegged_subunit_to_unit\" => 100,\n            \"updated_at\" => \"2019-01-01T10:10:00+00:00\"\n          }\n        ]\n      })\n\n      assert OrderFeeFetcher.add_fee_to_order(%{fee: %{currency: @eth}}, context.fake_addr) ==\n               {:error, :unexpected_fee_currency}\n    end\n\n    test \"forwards the childchain error\", context do\n      prepare_test_server(context, %{\n        code: \"fees.all:some_error\",\n        description: \"Some errors\"\n      })\n\n      assert OrderFeeFetcher.add_fee_to_order(%{fee: %{currency: @eth}}, context.fake_addr) ==\n               {:error, {:client_error, %{\"code\" => \"fees.all:some_error\", \"description\" => \"Some errors\"}}}\n    end\n  end\n\n  defp prepare_test_server(context, response) do\n    response\n    |> TestServer.make_response()\n    |> TestServer.with_response(context, \"/fees.all\")\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/test/omg_watcher_info/release_tasks/set_tracer_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherInfo.ReleaseTasks.SetTracerTest do\n  use ExUnit.Case, async: true\n  import ExUnit.CaptureLog, only: [capture_log: 1]\n\n  alias OMG.WatcherInfo.ReleaseTasks.SetTracer\n  alias OMG.WatcherInfo.Tracer\n  @app :omg_watcher_info\n\n  setup do\n    {:ok, pid} = __MODULE__.System.start_link([])\n    nil = Process.put(__MODULE__.System, pid)\n    :ok\n  end\n\n  test \"if environment variables get applied in the configuration\" do\n    :ok = __MODULE__.System.put_env(\"DD_DISABLED\", \"TRUE\")\n    :ok = __MODULE__.System.put_env(\"APP_ENV\", \"YOLO\")\n\n    assert capture_log(fn ->\n             config = SetTracer.load([], system_adapter: __MODULE__.System)\n             disabled = config |> Keyword.fetch!(@app) |> Keyword.fetch!(Tracer) |> Keyword.fetch!(:disabled?)\n             env = config |> Keyword.fetch!(@app) |> Keyword.fetch!(Tracer) |> Keyword.fetch!(:env)\n\n             assert disabled == true\n             # if it's disabled, env doesn't matter, so we set it to an empty string\n             assert env == \"\"\n           end)\n  end\n\n  test \"if default configuration is used when there's no environment variables\" do\n    :ok = __MODULE__.System.put_env(\"HOSTNAME\", \"this is my tracer test 3\")\n\n    assert capture_log(fn ->\n             config = SetTracer.load([], system_adapter: __MODULE__.System)\n             # we set env to an empty string because disabled? is set to true!\n             configuration = @app |> Application.get_env(Tracer) |> Keyword.put(:env, \"\") |> Enum.sort()\n             tracer_config = config |> Keyword.get(@app) |> Keyword.get(Tracer) |> Enum.sort()\n             assert configuration == tracer_config\n           end)\n  end\n\n  test \"if exit is thrown when faulty configuration is used\" do\n    :ok = __MODULE__.System.put_env(\"DD_DISABLED\", \"TRUEeee\")\n    catch_exit(SetTracer.load([], system_adapter: __MODULE__.System))\n  end\n\n  defmodule System do\n    def start_link(args), do: GenServer.start_link(__MODULE__, args, [])\n    def get_env(key), do: __MODULE__ |> Process.get() |> GenServer.call({:get_env, key})\n    def put_env(key, value), do: __MODULE__ |> Process.get() |> GenServer.call({:put_env, key, value})\n    def init(_), do: {:ok, %{}}\n\n    def handle_call({:get_env, key}, _, state) do\n      {:reply, state[key], state}\n    end\n\n    def handle_call({:put_env, key, value}, _, state) do\n      {:reply, :ok, Map.put(state, key, value)}\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/test/omg_watcher_info/transaction_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherInfo.TransactionTest do\n  use ExUnitFixtures\n  use ExUnit.Case, async: false\n  use OMG.Watcher.Fixtures\n\n  alias OMG.Watcher.Utxo\n  alias OMG.WatcherInfo.DB\n  alias OMG.WatcherInfo.Transaction\n\n  import OMG.WatcherInfo.Factory\n\n  require Utxo\n\n  @eth <<0::160>>\n  @alice <<27::160>>\n  @bob <<28::160>>\n\n  describe \"select_inputs/2\" do\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns {:ok, transctions} when able to select utxos to satisfy payments and fee\" do\n      _ = insert(:txoutput, amount: 10, currency: @eth, owner: @alice)\n      _ = insert(:txoutput, amount: 10, currency: @eth, owner: @alice)\n\n      utxos_per_token = DB.TxOutput.get_sorted_grouped_utxos(@alice, :desc)\n\n      order = %{\n        owner: @alice,\n        payments: [\n          %{amount: 10, currency: @eth, owner: @bob}\n        ],\n        fee: %{currency: @eth, amount: 5},\n        metadata: nil\n      }\n\n      assert {:ok,\n              %{\n                @eth => [\n                  %{amount: 10, currency: @eth},\n                  %{amount: 10, currency: @eth}\n                ]\n              }} = Transaction.select_inputs(utxos_per_token, order)\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns error when the funds are not sufficient to satisfy payments and fee\" do\n      _ = insert(:txoutput, amount: 10, currency: @eth, owner: @alice)\n      _ = insert(:txoutput, amount: 10, currency: @eth, owner: @alice)\n      _ = insert(:txoutput, amount: 10, currency: @eth, owner: @alice)\n\n      utxos_per_token = DB.TxOutput.get_sorted_grouped_utxos(@alice, :desc)\n\n      order = %{\n        owner: @alice,\n        payments: [\n          %{amount: 30, currency: @eth, owner: @bob}\n        ],\n        fee: %{currency: @eth, amount: 10},\n        metadata: nil\n      }\n\n      assert {:error, {:insufficient_funds, [%{missing: 10}]}} = Transaction.select_inputs(utxos_per_token, order)\n    end\n  end\n\n  describe \"create/2\" do\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns {:error, :too_many_outputs} when a number of outputs > maximum\" do\n      _ = insert(:txoutput, amount: 10, currency: @eth, owner: @alice)\n      _ = insert(:txoutput, amount: 10, currency: @eth, owner: @alice)\n      _ = insert(:txoutput, amount: 10, currency: @eth, owner: @alice)\n      _ = insert(:txoutput, amount: 10, currency: @eth, owner: @alice)\n\n      utxos_per_token = DB.TxOutput.get_sorted_grouped_utxos(@alice, :desc)\n\n      order = %{\n        owner: @alice,\n        payments: [\n          %{amount: 9, currency: @eth, owner: @bob},\n          %{amount: 9, currency: @eth, owner: <<29::160>>},\n          %{amount: 9, currency: @eth, owner: <<30::160>>},\n          %{amount: 9, currency: @eth, owner: <<31::160>>},\n          %{amount: 9, currency: @eth, owner: <<32::160>>}\n        ],\n        fee: %{currency: @eth, amount: 9},\n        metadata: nil\n      }\n\n      assert {:error, :too_many_outputs} == Transaction.create(utxos_per_token, order)\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns {:error, :empty_inputs} when a inputs of inputs = 0\" do\n      order = %{\n        owner: @alice,\n        payments: [\n          %{amount: 45, currency: @eth, owner: @bob}\n        ],\n        fee: %{currency: @eth, amount: 5},\n        metadata: nil\n      }\n\n      assert {:error, :empty_transaction} == Transaction.create([], order)\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns {:ok, transactions} when 0 < inputs <= 4 and 0 < outputs <= 4\" do\n      _ = insert(:txoutput, amount: 10, currency: @eth, owner: @alice)\n      _ = insert(:txoutput, amount: 10, currency: @eth, owner: @alice)\n      _ = insert(:txoutput, amount: 10, currency: @eth, owner: @alice)\n      _ = insert(:txoutput, amount: 10, currency: @eth, owner: @alice)\n\n      utxos_per_token = DB.TxOutput.get_sorted_grouped_utxos(@alice, :desc)\n\n      order = %{\n        owner: @alice,\n        payments: [\n          %{amount: 35, currency: @eth, owner: @bob}\n        ],\n        fee: %{currency: @eth, amount: 5},\n        metadata: nil\n      }\n\n      assert {:ok,\n              [\n                %{\n                  fee: %{currency: @eth, amount: 5},\n                  inputs: [\n                    %{amount: 10, currency: @eth},\n                    %{amount: 10, currency: @eth},\n                    %{amount: 10, currency: @eth},\n                    %{amount: 10, currency: @eth}\n                  ],\n                  outputs: [\n                    %{amount: 35, currency: @eth, owner: @bob}\n                  ]\n                }\n              ]} = Transaction.create(utxos_per_token, order)\n    end\n  end\n\n  describe \"include_typed_data/1\" do\n    test \"returns an original error when the param is matched with {:error, _}\" do\n      assert {:error, :any} == Transaction.include_typed_data({:error, :any})\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns transactions with :typed_data\" do\n      _ = insert(:txoutput, amount: 10, currency: @eth, owner: @alice)\n\n      utxos_per_token = DB.TxOutput.get_sorted_grouped_utxos(@alice, :desc)\n\n      order = %{\n        owner: @alice,\n        payments: [\n          %{amount: 5, currency: @eth, owner: @bob}\n        ],\n        fee: %{currency: @eth, amount: 5},\n        metadata: nil\n      }\n\n      {:ok, transactions} = Transaction.create(utxos_per_token, order)\n\n      assert {:ok, %{transactions: transactions}} =\n               Transaction.include_typed_data({:ok, %{transactions: transactions, result: :complete}})\n\n      assert Enum.all?(transactions, &Map.has_key?(&1, :typed_data))\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/test/omg_watcher_info/utxo_selection_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n\ndefmodule OMG.WatcherInfo.UtxoSelectionTest do\n  use ExUnitFixtures\n  use ExUnit.Case, async: false\n  use OMG.Watcher.Fixtures\n\n  alias OMG.Eth.Encoding\n  alias OMG.Watcher.Utxo\n  alias OMG.WatcherInfo.DB\n  alias OMG.WatcherInfo.UtxoSelection\n\n  import OMG.WatcherInfo.Factory\n\n  require Utxo\n\n  @alice <<27::160>>\n  @eth <<0::160>>\n  @other_token <<127::160>>\n\n  describe \"calculate_net_amount/2\" do\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns a correct map when payment_currency != fee_currency\" do\n      payment_currency = @eth\n      fee_currency = @other_token\n\n      payments = [\n        %{\n          owner: @alice,\n          currency: payment_currency,\n          amount: 1_000\n        }\n      ]\n\n      fee = %{\n        currency: fee_currency,\n        amount: 2_000\n      }\n\n      assert %{\n               payment_currency => 1_000,\n               fee_currency => 2_000\n             } == UtxoSelection.calculate_net_amount(payments, fee)\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns a correct map when payment_currency == fee_currency\" do\n      payment_currency = @eth\n\n      payments = [\n        %{\n          owner: @alice,\n          currency: payment_currency,\n          amount: 1_000\n        }\n      ]\n\n      fee = %{\n        currency: payment_currency,\n        amount: 2_000\n      }\n\n      assert %{\n               payment_currency => 3_000\n             } == UtxoSelection.calculate_net_amount(payments, fee)\n    end\n  end\n\n  describe \"review_selected_utxos/1\" do\n    test \"should return the expected error if selected UTXOs do not cover the amount of the transaction order\" do\n      variances = %{@eth => 5, @other_token => 10}\n\n      # UTXO list is empty for simplicty as the error response does not need it.\n      utxo_list = []\n\n      constructed_argument = Enum.map([@eth, @other_token], fn ccy -> {ccy, {variances[ccy], utxo_list}} end)\n\n      assert UtxoSelection.review_selected_utxos(constructed_argument) ==\n               {:error,\n                {:insufficient_funds,\n                 [\n                   %{missing: variances[@eth], token: Encoding.to_hex(@eth)},\n                   %{missing: variances[@other_token], token: Encoding.to_hex(@other_token)}\n                 ]}}\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"should return the expected response if UTXOs cover the amount of the transaction order\" do\n      variances = %{@eth => -5, @other_token => 0}\n\n      _ = insert(:txoutput, amount: 100, currency: @eth, owner: @alice)\n      _ = insert(:txoutput, amount: 100, currency: @other_token, owner: @alice)\n\n      %{@eth => [eth_utxo], @other_token => [other_token_utxo]} = DB.TxOutput.get_sorted_grouped_utxos(@alice, :desc)\n\n      constructed_argument = [\n        {@eth, {variances[@eth], [eth_utxo]}},\n        {@other_token, {variances[@other_token], [other_token_utxo]}}\n      ]\n\n      assert {:ok,\n              %{\n                @eth => [_eth_utxo],\n                @other_token => [^other_token_utxo]\n              }} = UtxoSelection.review_selected_utxos(constructed_argument)\n    end\n  end\n\n  describe \"select_utxos/2\" do\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns the expected utxos if UTXOs cover `net_amount`\" do\n      net_amount = %{\n        @eth => 2_000\n      }\n\n      _ = insert(:txoutput, amount: 1_200, currency: @eth, owner: @alice)\n      _ = insert(:txoutput, amount: 1_000, currency: @eth, owner: @alice)\n\n      utxos = DB.TxOutput.get_sorted_grouped_utxos(@alice, :desc)\n\n      assert [{@eth, {-200, _utxos}}] = UtxoSelection.select_utxos(net_amount, utxos)\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns the expected utxos if any of UTXOs exactly matched `net_amount`\" do\n      net_amount = %{\n        @eth => 2_000\n      }\n\n      _ = insert(:txoutput, amount: 2_000, currency: @eth, owner: @alice)\n\n      utxos = DB.TxOutput.get_sorted_grouped_utxos(@alice, :desc)\n\n      assert [{@eth, {0, _utxos}}] = UtxoSelection.select_utxos(net_amount, utxos)\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns positive variance if UTXOs don't cover `net_amount`\" do\n      net_amount = %{\n        @eth => 2_000\n      }\n\n      _ = insert(:txoutput, amount: 500, currency: @eth, owner: @alice)\n      _ = insert(:txoutput, amount: 500, currency: @eth, owner: @alice)\n\n      utxos = DB.TxOutput.get_sorted_grouped_utxos(@alice, :desc)\n\n      assert [{@eth, {1_000, _utxos}}] = UtxoSelection.select_utxos(net_amount, utxos)\n    end\n  end\n\n  describe \"add_utxos_for_stealth_merge/2\" do\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns selected UTXOs with no additions if the maximum has already been selected\" do\n      for _i <- 1..5 do\n        _ = insert(:txoutput, owner: @alice)\n      end\n\n      [not_included | included] =\n        @alice\n        |> DB.TxOutput.get_sorted_grouped_utxos(:desc)\n        |> Map.get(@eth)\n\n      inputs = %{\n        @eth => included\n      }\n\n      assert UtxoSelection.add_utxos_for_stealth_merge([not_included], inputs) == inputs\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns selected UTXOs with no additions if no other UTXOs are available\" do\n      for _i <- 1..4 do\n        _ = insert(:txoutput, owner: @alice)\n      end\n\n      inputs = DB.TxOutput.get_sorted_grouped_utxos(@alice, :desc)\n\n      other_available = []\n\n      assert UtxoSelection.add_utxos_for_stealth_merge(other_available, inputs) == inputs\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"adds UTXOs until the limit is reached in the case of one currency\" do\n      for _i <- 1..5 do\n        _ = insert(:txoutput, owner: @alice)\n      end\n\n      [included | available] =\n        @alice\n        |> DB.TxOutput.get_sorted_grouped_utxos(:desc)\n        |> Map.get(@eth)\n\n      [available_1, available_2, available_3 | _not_for_inclusion] = available\n\n      inputs = %{\n        @eth => [included]\n      }\n\n      expected = %{\n        @eth => [available_3, available_2, available_1, included]\n      }\n\n      assert UtxoSelection.add_utxos_for_stealth_merge(available, inputs) == expected\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"adds UTXOs until the limit is reached in the case of multiple currencies\" do\n      _ = insert(:txoutput, currency: @eth, owner: @alice)\n      _ = insert(:txoutput, currency: @eth, owner: @alice)\n      _ = insert(:txoutput, currency: @other_token, owner: @alice)\n      _ = insert(:txoutput, currency: @other_token, owner: @alice)\n\n      utxos = DB.TxOutput.get_sorted_grouped_utxos(@alice, :desc)\n\n      [input_1, merge_1] = Map.get(utxos, @eth)\n      [input_2, merge_2] = Map.get(utxos, @other_token)\n\n      inputs = %{\n        @eth => [input_1],\n        @other_token => [input_2]\n      }\n\n      assert %{\n               @eth => [^merge_1, ^input_1],\n               @other_token => [^merge_2, ^input_2]\n             } = UtxoSelection.add_utxos_for_stealth_merge([merge_1, merge_2], inputs)\n    end\n  end\n\n  describe \"prioritize_merge_utxos/2\" do\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns only UTXOs that have not already been selected for the transaction\" do\n      _ = insert(:txoutput, currency: @eth, owner: @alice)\n      _ = insert(:txoutput, currency: @eth, owner: @alice)\n      _ = insert(:txoutput, currency: @eth, owner: @alice)\n      _ = insert(:txoutput, currency: @eth, owner: @alice)\n\n      %{\n        @eth => [selected_eth | available_eth]\n      } = utxos = DB.TxOutput.get_sorted_grouped_utxos(@alice, :desc)\n\n      inputs = %{\n        @eth => [selected_eth]\n      }\n\n      result = UtxoSelection.prioritize_merge_utxos(utxos, inputs)\n\n      assert result == available_eth\n      assert Enum.member?(result, selected_eth) == false\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns UTXOs only for currencies already in the transaction\" do\n      token_a = <<65::160>>\n      token_b = <<66::160>>\n      token_c = <<67::160>>\n\n      _ = insert(:txoutput, currency: token_a, owner: @alice)\n      _ = insert(:txoutput, currency: token_a, owner: @alice)\n      _ = insert(:txoutput, currency: token_b, owner: @alice)\n      _ = insert(:txoutput, currency: token_b, owner: @alice)\n      _ = insert(:txoutput, currency: token_c, owner: @alice)\n      _ = insert(:txoutput, currency: token_c, owner: @alice)\n\n      %{\n        ^token_a => [utxo_a_1, utxo_a_2],\n        ^token_b => [utxo_b_1, utxo_b_2],\n        ^token_c => [_, _]\n      } = utxos = DB.TxOutput.get_sorted_grouped_utxos(@alice, :desc)\n\n      inputs = %{\n        token_a => [utxo_a_1],\n        token_b => [utxo_b_1]\n      }\n\n      result = UtxoSelection.prioritize_merge_utxos(utxos, inputs)\n\n      assert result == [utxo_a_2, utxo_b_2]\n      assert Enum.find(result, fn utxo -> utxo.currency == token_c end) == nil\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"orders UTXOs by currency in descending order of set size\" do\n      token_a = <<65::160>>\n      token_b = <<66::160>>\n      token_c = <<67::160>>\n\n      for _i <- 1..4 do\n        _ = insert(:txoutput, currency: token_a, owner: @alice)\n      end\n\n      for _i <- 1..3 do\n        _ = insert(:txoutput, currency: token_b, owner: @alice)\n      end\n\n      for _i <- 1..2 do\n        _ = insert(:txoutput, currency: token_c, owner: @alice)\n      end\n\n      %{\n        ^token_a => [selected_a | available_a],\n        ^token_b => [selected_b | available_b],\n        ^token_c => [selected_c | available_c]\n      } = utxos = DB.TxOutput.get_sorted_grouped_utxos(@alice, :desc)\n\n      inputs = %{\n        token_a => [selected_a],\n        token_b => [selected_b],\n        token_c => [selected_c]\n      }\n\n      result = UtxoSelection.prioritize_merge_utxos(utxos, inputs)\n\n      assert result == available_a ++ available_b ++ available_c\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"orders UTXOs by currency in descending order of set size, excluding already selected UTXOs\" do\n      token_a = <<65::160>>\n      token_b = <<66::160>>\n\n      for _i <- 1..3 do\n        _ = insert(:txoutput, currency: token_a, owner: @alice)\n        _ = insert(:txoutput, currency: token_b, owner: @alice)\n      end\n\n      %{\n        ^token_a => [a_1, a_2, a_3],\n        ^token_b => [b_1, b_2, b_3]\n      } = utxos = DB.TxOutput.get_sorted_grouped_utxos(@alice, :desc)\n\n      inputs = %{\n        token_a => [a_1, a_2],\n        token_b => [b_1]\n      }\n\n      assert UtxoSelection.prioritize_merge_utxos(utxos, inputs) == [b_2, b_3, a_3]\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"within the currency grouping, orders UTXOs in ascending order of value ('dust first')\" do\n      token_a = <<65::160>>\n      token_b = <<66::160>>\n\n      _ = insert(:txoutput, amount: 10, currency: token_a, owner: @alice)\n      _ = insert(:txoutput, amount: 30, currency: token_b, owner: @alice)\n      _ = insert(:txoutput, amount: 20, currency: token_a, owner: @alice)\n      _ = insert(:txoutput, amount: 40, currency: token_b, owner: @alice)\n      _ = insert(:txoutput, amount: 40, currency: token_a, owner: @alice)\n      _ = insert(:txoutput, amount: 20, currency: token_b, owner: @alice)\n      _ = insert(:txoutput, amount: 30, currency: token_a, owner: @alice)\n      _ = insert(:txoutput, amount: 10, currency: token_b, owner: @alice)\n\n      %{\n        ^token_a => [a_1 | available_a],\n        ^token_b => [b_1, b_2 | available_b]\n      } = utxos = DB.TxOutput.get_sorted_grouped_utxos(@alice, :desc)\n\n      inputs = %{\n        token_a => [a_1],\n        token_b => [b_1, b_2]\n      }\n\n      sorted_available_a = Enum.sort_by(available_a, fn utxo -> utxo.amount end, :asc)\n      sorted_available_b = Enum.sort_by(available_b, fn utxo -> utxo.amount end, :asc)\n\n      assert UtxoSelection.prioritize_merge_utxos(utxos, inputs) == Enum.concat(sorted_available_a, sorted_available_b)\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns no more than 3 UTXOs per currency grouping\" do\n      for _i <- 1..5 do\n        _ = insert(:txoutput, currency: @eth, owner: @alice)\n      end\n\n      %{\n        @eth => [eth_1 | available_eth]\n      } = utxos = DB.TxOutput.get_sorted_grouped_utxos(@alice, :desc)\n\n      assert length(available_eth) > 3\n\n      inputs = %{\n        @eth => [eth_1]\n      }\n\n      assert UtxoSelection.prioritize_merge_utxos(utxos, inputs)\n             |> Enum.filter(fn utxo -> utxo.currency == @eth end)\n             |> length() == 3\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns empty list when original utxos are empty\" do\n      _ = insert(:txoutput, currency: @eth, owner: @alice)\n      _ = insert(:txoutput, currency: @eth, owner: @alice)\n\n      utxos = DB.TxOutput.get_sorted_grouped_utxos(@alice, :desc)\n\n      assert [] == UtxoSelection.prioritize_merge_utxos(utxos, %{})\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/test/support/factories/block_factory.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherInfo.Factory.Block do\n  @moduledoc \"\"\"\n    Block factory.\n\n    Generates an empty test block with no transactions.\n\n    Two ways to generate a block with transactions are:\n      1. Build a transaction first:\n            ```\n              build(:transaction)\n            ```\n         which will automatically create a block that the transaction belongs to.\n\n      2. Build an empty block and then build a transaction passing in the empty\n         block to the transaction factory:\n            ```\n              block = build(:block)\n              transaction = build(:transaction, block: block)\n\n            ```\n\n    Note that `tx_count` is an aggregate sum(block.transactions) field and does\n    not automatically get setup in the tests. In most cases `tx_count` will\n    need to be managed manually.\n  \"\"\"\n  defmacro __using__(_opts) do\n    quote do\n      alias OMG.WatcherInfo.DB\n\n      def block_factory() do\n        block = %DB.Block{\n          blknum: sequence(:block_blknum, fn seq -> seq * 1000 end),\n          hash: insecure_random_bytes(32),\n          eth_height: sequence(:block_eth_height, fn seq -> seq end),\n          timestamp: sequence(:block_timestamp, fn seq -> seq end),\n          transactions: [],\n          tx_count: 0\n        }\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/test/support/factories/data_helper.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherInfo.Factory.DataHelper do\n  @moduledoc \"\"\"\n    A data helper module with functions to generate useful data for testing. Unlike the factories,\n    the data generated in this module is not constrained to the sructures defined in the DB models.\n  \"\"\"\n  defmacro __using__(_opts) do\n    quote do\n      alias OMG.Eth.Encoding\n      alias OMG.Watcher.Utxo\n\n      require Utxo\n\n      # Generates a certain length of random bytes. Uniqueness not guaranteed so it's not recommended for identifiers.\n      def insecure_random_bytes(num_bytes) when num_bytes >= 0 and num_bytes <= 255 do\n        0..255 |> Enum.shuffle() |> Enum.take(num_bytes) |> :erlang.list_to_binary()\n      end\n\n      # creates event data specifically for the TxOutput.spend_utxos/3function\n      def spend_uxto_params_from_txoutput(txoutput) do\n        {Utxo.position(txoutput.blknum, txoutput.txindex, txoutput.oindex), txoutput.spending_tx_oindex,\n         txoutput.spending_txhash}\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/test/support/factories/eth_event_factory.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherInfo.Factory.EthEvent do\n  @moduledoc \"\"\"\n    EthEvent factory.\n\n    Generates an ethevent. For testing flexibility, an ethevent can be created with 0 txoutputs. Although this does not\n    conform to the business logic, this violates no database constraints.\n\n    To associate an ethevent with one or more txoutputs, an array of txoutputs can be passed in via by overriding\n    `txoutputs`.\n\n    Most scenarios will have a only a 1-1 relationship between ethevents an txoutputs or a one-to-many\n    (txoutput -> ethevents) relationship. However, with an ExitFinalized ethevent (processExits()) scenario, an ethevent\n    may have many txoutputs. A txoutput for every utxo in the exit queue when processExits() was called.\n\n    The default event type is `:deposit`, but can be overridden by setting `event_type`.\n  \"\"\"\n  defmacro __using__(_opts) do\n    quote do\n      alias OMG.WatcherInfo.DB\n\n      def ethevent_factory() do\n        ethevent = %DB.EthEvent{\n          root_chain_txhash: insecure_random_bytes(32),\n          # within a log there may be 0 or more ethereum events, this is the index of the\n          # event within the log\n          log_index: 0,\n          eth_height: 1,\n          event_type: :deposit,\n          txoutputs: []\n        }\n\n        root_chain_txhash_event =\n          DB.EthEvent.generate_root_chain_txhash_event(ethevent.root_chain_txhash, ethevent.log_index)\n\n        Map.put(ethevent, :root_chain_txhash_event, root_chain_txhash_event)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/test/support/factories/transaction_factory.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherInfo.Factory.Transaction do\n  @moduledoc \"\"\"\n    Transaction factory.\n\n    Generates a transaction without any transaction inputs or outputs.\n\n    To generate a transaction with closest data to production, consider generating transaction inputs and/or outputs\n    and associate them with this transaction.\n  \"\"\"\n  defmacro __using__(_opts) do\n    quote do\n      alias OMG.WatcherInfo.DB\n\n      alias OMG.Watcher.Utxo\n      require Utxo\n\n      def transaction_factory(attrs \\\\ %{}) do\n        {block, attrs} =\n          case attrs[:block] do\n            nil ->\n              {build(:block, tx_count: 1), attrs}\n\n            block ->\n              block = Map.put(block, :tx_count, Map.get(block, :tx_count) + 1)\n\n              {block, Map.delete(attrs, :block)}\n          end\n\n        transaction = %DB.Transaction{\n          txhash: insecure_random_bytes(32),\n          txindex: block.tx_count - 1,\n          txbytes: insecure_random_bytes(32),\n          metadata: insecure_random_bytes(32),\n          txtype: 1,\n          block: block,\n          inputs: [],\n          outputs: []\n        }\n\n        # not returning `merge_attributes(transaction, attrs)` directly to avoid dialyzer errors\n        transaction = merge_attributes(transaction, attrs)\n        transaction\n      end\n\n      def with_inputs(transaction, txoutputs) do\n        {_, transaction} =\n          txoutputs\n          |> Enum.with_index()\n          |> Enum.map_reduce(transaction, fn {txoutput, index}, transaction ->\n            input_fields = %{\n              spending_transaction: transaction,\n              spending_tx_oindex: index\n            }\n\n            utxo_pos = Utxo.position(transaction.block.blknum, txoutput.txindex, txoutput.oindex)\n\n            txoutput = DB.TxOutput.get_by_position(utxo_pos) || txoutput\n\n            {:ok, txoutput} =\n              txoutput\n              |> Ecto.Changeset.change(input_fields)\n              |> DB.Repo.insert_or_update()\n\n            {{txoutput, index}, Map.put(transaction, :inputs, transaction.inputs ++ [txoutput])}\n          end)\n\n        transaction\n      end\n\n      def with_outputs(transaction, txoutputs) do\n        {_, transaction} =\n          txoutputs\n          |> Enum.with_index()\n          |> Enum.map_reduce(transaction, fn {txoutput, index}, transaction ->\n            output_fields = %{\n              creating_transaction: transaction,\n              blknum: transaction.block.blknum,\n              txindex: transaction.txindex,\n              oindex: index\n            }\n\n            child_chain_utxohash =\n              DB.EthEvent.generate_child_chain_utxohash(\n                Utxo.position(txoutput.blknum, txoutput.txindex, txoutput.oindex)\n              )\n\n            output_fields = Map.put(output_fields, :child_chain_utxohash, child_chain_utxohash)\n\n            txoutput = insert(struct(txoutput, output_fields))\n\n            {{txoutput, index}, Map.put(transaction, :outputs, transaction.outputs ++ [txoutput])}\n          end)\n\n        transaction\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/test/support/factories/txoutput_factory.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherInfo.Factory.TxOutput do\n  @moduledoc \"\"\"\n    TxOutput factory.\n\n    Generates a txoutput with a `blknum` using blknum sequence from the block factory.the 1, 1001,\n    2001, etc... In most test use cases `blknum` should be overridden.\n\n    If you are overriding some values, also consider its relation to other values. E.g:\n\n      - To override `blknum`, also consider overriding `txindex`.\n      - To override `creating_transaction`, also consider overriding `txindex` and `oindex`.\n      - To override `spending_transaction`, also consider overriding `spending_tx_oindex`\n  \"\"\"\n  defmacro __using__(_opts) do\n    quote do\n      alias OMG.Watcher.Utxo\n      alias OMG.WatcherInfo.DB\n\n      require Utxo\n\n      @eth <<0::160>>\n\n      def txoutput_factory(attrs \\\\ %{}) do\n        attrs = Map.put_new_lazy(attrs, :blknum, fn -> build(:block).blknum end)\n\n        txoutput = %DB.TxOutput{\n          blknum: 0,\n          txindex: 0,\n          oindex: 0,\n          otype: 1,\n          owner: insecure_random_bytes(20),\n          amount: 100,\n          currency: @eth,\n          creating_transaction: nil,\n          spending_transaction: nil,\n          spending_tx_oindex: nil,\n          proof: insecure_random_bytes(32),\n          ethevents: []\n        }\n\n        txoutput = merge_attributes(txoutput, attrs)\n\n        child_chain_utxohash =\n          DB.EthEvent.generate_child_chain_utxohash(Utxo.position(txoutput.blknum, txoutput.txindex, txoutput.oindex))\n\n        Map.put(txoutput, :child_chain_utxohash, child_chain_utxohash)\n      end\n\n      def with_deposit(txoutput) do\n        ethevent = build(:ethevent)\n        Map.put(txoutput, :ethevents, [ethevent] ++ txoutput.ethevents)\n      end\n\n      def with_standard_exit(txoutput) do\n        ethevent = build(:ethevent, event_type: :standard_exit)\n        Map.put(txoutput, :ethevents, [ethevent] ++ txoutput.ethevents)\n      end\n\n      # if testing with a transaction containing multiple txoutput outputs then consider using the transaction\n      # factory's `with_outputs()` function instead\n      def with_creating_transaction(txoutput, transaction \\\\ nil) do\n        transaction =\n          case transaction do\n            nil -> build(:transaction)\n            transaction -> transaction\n          end\n\n        txoutput =\n          struct(txoutput, %{\n            blknum: transaction.block.blknum,\n            creating_txhash: transaction.txhash,\n            txindex: length(transaction.outputs)\n          })\n\n        Map.put(\n          txoutput,\n          :child_chain_utxohash,\n          DB.EthEvent.generate_child_chain_utxohash(Utxo.position(txoutput.blknum, txoutput.txindex, txoutput.oindex))\n        )\n      end\n\n      # if testing with a transaction containing multiple txoutput inputs then consider using the transaction\n      # factory's `with_inputs()` function instead\n      def with_spending_transaction(txoutput, transaction \\\\ nil) do\n        transaction =\n          case transaction do\n            nil -> build(:transaction)\n            transaction -> transaction\n          end\n\n        txoutput\n        |> Map.put(:proof, insecure_random_bytes(32))\n        |> Map.put(:spending_transaction, transaction)\n        |> Map.put(:spending_tx_oindex, length(transaction.inputs))\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/test/support/factory.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherInfo.Factory do\n  @moduledoc \"\"\"\n  Test data factory for OMG.WatcherInfo.\n\n  Use this module to build structs or insert data into WatcherInfo's database.\n\n  ## Usage\n\n  Import this factory into your test module with `import OMG.WatcherInfo.Factory`. Importing only this factory and\n  you will be able to use all the factories in the factories directory. To specify which factory ExMachina should\n  use pass an atom of the factory name to the `build()` and `insert()` functions.\n\n  For example, to build a block using the block factory use: `block = build(:block)`\n  To build a transaction using the transaction factory use: `transaction = build(:transaction)`\n\n  Use [`build/1`](https://hexdocs.pm/ex_machina/ExMachina.html#c:build/1)\n  to build a struct without inserting them to the database, or\n  [`build/2`](https://hexdocs.pm/ex_machina/ExMachina.html#c:build/2) to override default data.\n\n  Or use [`insert/1`](https://hexdocs.pm/ex_machina/ExMachina.html#c:insert/1) to build and\n  insert the struct to database or [`build/2`](https://hexdocs.pm/ex_machina/ExMachina.html#c:build/2)\n  to insert with overrides.\n\n  See all available APIs at https://hexdocs.pm/ex_machina/ExMachina.html.\n\n  ## Example\n\n      defmodule MyTest do\n        use ExUnit.Case\n\n        import OMG.WatcherInfo.Factory\n\n        test ... do\n          # Returns %OMG.WatcherInfo.DB.Block{blknum: ..., hash: ...}\n          build(:block)\n\n          # Returns %OMG.WatcherInfo.DB.Block{blknum: 1234, hash: ...}\n          build(:block, blknum: 1234)\n\n          # Inserts and returns %OMG.WatcherInfo.DB.Block{blknum: ..., hash: ...}\n          insert(:block)\n\n          # Inserts and returns %OMG.WatcherInfo.DB.Block{blknum: 1234, hash: ...}\n          insert(:block, blknum: 1234)\n        end\n      end\n  \"\"\"\n  use ExMachina.Ecto, repo: OMG.WatcherInfo.DB.Repo\n\n  use OMG.WatcherInfo.Factory.Block\n  use OMG.WatcherInfo.Factory.DataHelper\n  use OMG.WatcherInfo.Factory.EthEvent\n  use OMG.WatcherInfo.Factory.Transaction\n  use OMG.WatcherInfo.Factory.TxOutput\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/test/support/test_server.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherInfo.TestServer do\n  @moduledoc \"\"\"\n  Helper functions to provide behavior to FakeServer without using FakeServer defined macros.\n  For now it's strictly tied with child chain api and handles env variable changes\n  \"\"\"\n\n  @doc \"\"\"\n  Starts a mock server that will handle child chain requests\n  \"\"\"\n  def start() do\n    server_id = :watcher_info_test_server\n    {:ok, pid} = FakeServer.start(server_id)\n\n    real_addr = Application.fetch_env!(:omg_watcher_info, :child_chain_url)\n    {:ok, port} = FakeServer.port(server_id)\n    fake_addr = \"http://localhost:#{port}\"\n\n    %{\n      real_addr: real_addr,\n      fake_addr: fake_addr,\n      server_id: server_id,\n      server_pid: pid\n    }\n  end\n\n  @doc \"\"\"\n  Stops a server and put back the original child chain address to the env.\n  \"\"\"\n  def stop(%{real_addr: real_addr, server_id: server_id}) do\n    Application.put_env(:omg_watcher_info, :child_chain_url, real_addr)\n    FakeServer.stop(server_id)\n  end\n\n  @doc \"\"\"\n  Configures route for fake server to respond for given path with given response\n  **Please note: **\n  When the route is configured with a list of FakeServer.HTTP.Responses, the server will respond with the first element\n  in the list and then remove it. This will be repeated for each request made for this route.\n  Use `fn req -> response end` when you need to return always the same or modified response on every request\n\n  Also first use of `with_response` changes configuration variable to child chain api to fake server, so invoke this\n  function when fake response is needed.\n  \"\"\"\n  def with_response(response_block, %{fake_addr: fake_addr, server_pid: server_pid} = _context, path) do\n    Application.put_env(:omg_watcher_info, :child_chain_url, fake_addr)\n\n    FakeServer.put_route(server_pid, path, fn _ ->\n      response_block\n    end)\n  end\n\n  def make_response(data) when is_map(data) do\n    TestServerResponseFactory.build(:json_rpc, data: data, success: not Map.has_key?(data, :code))\n  end\nend\n\ndefmodule TestServerResponseFactory do\n  @moduledoc false\n  use FakeServer.ResponseFactory\n\n  def json_rpc_response() do\n    ok(\n      %{\n        version: \"1.0\",\n        success: true,\n        data: %{}\n      },\n      %{\"Content-Type\" => \"application/json\"}\n    )\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_info/test/test_helper.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nExUnit.configure(exclude: [mix_based_child_chain: true, integration: true, property: true, wrappers: true])\nExUnitFixtures.start()\nExUnit.start()\n\n{:ok, _} = Application.ensure_all_started(:httpoison)\n{:ok, _} = Application.ensure_all_started(:fake_server)\n{:ok, _} = Application.ensure_all_started(:briefly)\n{:ok, _} = Application.ensure_all_started(:erlexec)\n{:ok, _} = Application.ensure_all_started(:ex_machina)\n\nMix.Task.run(\"ecto.create\", ~w(--quiet))\nMix.Task.run(\"ecto.migrate\", ~w(--quiet))\n"
  },
  {
    "path": "apps/omg_watcher_rpc/lib/application.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Application do\n  @moduledoc false\n  use Application\n  require Logger\n\n  def start(_type, _args) do\n    _ = Logger.info(\"Starting #{inspect(__MODULE__)}\")\n\n    start_root_supervisor()\n  end\n\n  def start_root_supervisor() do\n    # root supervisor must stop whenever any of its children supervisors goes down (children carry the load of restarts)\n\n    _ =\n      SpandexPhoenix.Telemetry.install(\n        endpoint_telemetry_prefix: [:watcher_rpc, :endpoint],\n        tracer: OMG.WatcherRPC.Tracer,\n        customize_metadata: &OMG.WatcherRPC.Tracer.add_trace_metadata/1\n      )\n\n    children = [\n      %{\n        id: OMG.WatcherRPC.Web.Endpoint,\n        start: {OMG.WatcherRPC.Web.Endpoint, :start_link, []},\n        type: :supervisor\n      }\n    ]\n\n    opts = [\n      strategy: :one_for_one,\n      # whenever any of supervisor's children goes down, so it does\n      name: OMG.WatcherRPC.RootSupervisor\n    ]\n\n    Supervisor.start_link(children, opts)\n  end\n\n  # Tell Phoenix to update the endpoint configuration\n  # whenever the application is updated.\n  def config_change(changed, _new, removed) do\n    OMG.WatcherRPC.Web.Endpoint.config_change(changed, removed)\n    :ok\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/lib/configuration.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Configuration do\n  @moduledoc \"\"\"\n  Provides access to applications configuration\n  \"\"\"\n  @app :omg_watcher_rpc\n\n  @spec version() :: String.t()\n  def version() do\n    OMG.Utils.AppVersion.version(@app)\n  end\n\n  @spec service_name() :: atom()\n  def service_name() do\n    Application.get_env(@app, :api_mode)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/lib/release_tasks/set_api_mode.ex",
    "content": "# Copyright 2019-2019 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.ReleaseTasks.SetApiMode do\n  @moduledoc false\n  @behaviour Config.Provider\n  require Logger\n\n  def init(nil) do\n    exit(\"WatcherRPC's API mode is not provided.\")\n  end\n\n  def init(args) do\n    args\n  end\n\n  def load(config, api_mode) do\n    Config.Reader.merge(config, omg_watcher_rpc: [api_mode: api_mode])\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/lib/release_tasks/set_endpoint.ex",
    "content": "# Copyright 2019-2019 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.ReleaseTasks.SetEndpoint do\n  @moduledoc false\n  @behaviour Config.Provider\n  require Logger\n  @app :omg_watcher_rpc\n\n  def init(args) do\n    args\n  end\n\n  def load(config, _args) do\n    _ = on_load()\n    endpoint_config = Application.get_env(@app, OMG.WatcherRPC.Web.Endpoint)\n\n    endpoint_config =\n      Keyword.put(\n        endpoint_config,\n        :http,\n        List.foldl(endpoint_config[:http], [], fn\n          {:port, _num}, acc -> [get_port() | acc]\n          other, acc -> [other | acc]\n        end)\n      )\n\n    endpoint_config =\n      Keyword.put(\n        endpoint_config,\n        :url,\n        List.foldl(endpoint_config[:url], [], fn\n          {:host, _num}, acc -> [get_hostname() | acc]\n          other, acc -> [other | acc]\n        end)\n      )\n\n    Config.Reader.merge(config, omg_watcher_rpc: [{OMG.WatcherRPC.Web.Endpoint, Enum.sort(endpoint_config)}])\n  end\n\n  defp get_port() do\n    port =\n      validate_integer(\n        get_env(\"PORT\"),\n        Keyword.get(Application.get_env(@app, OMG.WatcherRPC.Web.Endpoint)[:http], :port)\n      )\n\n    _ = Logger.info(\"CONFIGURATION: App: #{@app} Key: PORT Value: #{inspect(port)}.\")\n    {:port, port}\n  end\n\n  defp get_hostname() do\n    hostname =\n      validate_string(\n        get_env(\"HOSTNAME\"),\n        Keyword.get(Application.get_env(@app, OMG.WatcherRPC.Web.Endpoint)[:url], :host)\n      )\n\n    _ = Logger.info(\"CONFIGURATION: App: #{@app} Key: HOSTNAME Value: #{inspect(hostname)}.\")\n    {:host, hostname}\n  end\n\n  defp get_env(key), do: System.get_env(key)\n\n  defp validate_integer(value, _default) when is_binary(value), do: String.to_integer(value)\n  defp validate_integer(_, default), do: default\n\n  defp validate_string(value, _default) when is_binary(value), do: value\n  defp validate_string(_, default), do: default\n\n  defp on_load() do\n    _ = Application.ensure_all_started(:logger)\n    _ = Application.load(@app)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/lib/release_tasks/set_tracer.ex",
    "content": "# Copyright 2019-2019 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.ReleaseTasks.SetTracer do\n  @moduledoc false\n  @behaviour Config.Provider\n  alias OMG.WatcherRPC.Tracer\n  require Logger\n\n  @app :omg_watcher_rpc\n\n  def init(args) do\n    args\n  end\n\n  def load(config, args) do\n    _ = on_load()\n    adapter = Keyword.get(args, :system_adapter, System)\n    _ = Process.put(:system_adapter, adapter)\n    dd_disabled = get_dd_disabled()\n\n    tracer_config =\n      @app\n      |> Application.get_env(Tracer)\n      |> Keyword.put(:disabled?, dd_disabled)\n\n    tracer_config =\n      case dd_disabled do\n        false ->\n          app_env = get_app_env()\n          Keyword.put(tracer_config, :env, app_env)\n\n        true ->\n          Keyword.put(tracer_config, :env, \"\")\n      end\n\n    Config.Reader.merge(config,\n      omg_watcher_rpc: [{Tracer, tracer_config}],\n      spandex_phoenix: [tracer: Tracer]\n    )\n  end\n\n  defp get_dd_disabled() do\n    dd_disabled = Application.get_env(@app, OMG.WatcherRPC.Tracer)[:disabled?]\n    dd_disabled? = validate_bool(get_env(\"DD_DISABLED\"), dd_disabled)\n\n    _ = Logger.info(\"CONFIGURATION: App: #{@app} Key: DD_DISABLED Value: #{inspect(dd_disabled?)}.\")\n    dd_disabled?\n  end\n\n  defp get_app_env() do\n    env = validate_string(get_env(\"APP_ENV\"), Application.get_env(@app, OMG.WatcherRPC.Tracer)[:env])\n    _ = Logger.info(\"CONFIGURATION: App: #{@app} Key: APP_ENV Value: #{inspect(env)}.\")\n    env\n  end\n\n  defp get_env(key) do\n    Process.get(:system_adapter).get_env(key)\n  end\n\n  defp validate_bool(value, _default) when is_binary(value), do: to_bool(String.upcase(value))\n  defp validate_bool(_, default), do: default\n\n  defp to_bool(\"TRUE\"), do: true\n  defp to_bool(\"FALSE\"), do: false\n  defp to_bool(_), do: exit(\"DD_DISABLED either true or false.\")\n\n  defp validate_string(value, _default) when is_binary(value), do: value\n  defp validate_string(_, default), do: default\n\n  defp on_load() do\n    _ = Application.ensure_all_started(:logger)\n    Application.load(@app)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/lib/tracer.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Tracer do\n  @moduledoc \"\"\"\n  Trace Phoenix requests and reports information to Datadog via Spandex\n  \"\"\"\n\n  use Spandex.Tracer, otp_app: :omg_watcher_rpc\n  alias OMG.WatcherRPC.Configuration\n\n  def add_trace_metadata(%{assigns: %{error_type: error_type, error_msg: error_msg}} = conn) do\n    service_name = Configuration.service_name()\n    version = Configuration.version()\n\n    conn\n    |> SpandexPhoenix.default_metadata()\n    |> Keyword.put(:service, service_name)\n    |> Keyword.put(:error, [{:error, true}])\n    |> Keyword.put(:tags, [{:version, version}, {:\"error.type\", error_type}, {:\"error.msg\", error_msg}])\n  end\n\n  def add_trace_metadata(conn) do\n    service_name = Configuration.service_name()\n    version = Configuration.version()\n\n    conn\n    |> SpandexPhoenix.default_metadata()\n    |> Keyword.put(:service, service_name)\n    |> Keyword.put(:tags, [{:version, version}])\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/lib/web/controllers/account.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.Controller.Account do\n  @moduledoc \"\"\"\n  Module provides operation related to plasma accounts.\n  \"\"\"\n\n  use OMG.WatcherRPC.Web, :controller\n\n  alias OMG.Watcher.API, as: SecurityAPI\n  alias OMG.WatcherInfo.API, as: InfoAPI\n  alias OMG.WatcherRPC.Web.Validator.AccountConstraints\n\n  @doc \"\"\"\n  Gets plasma account balance\n  \"\"\"\n  def get_balance(conn, params) do\n    with {:ok, address} <- expect(params, \"address\", :address) do\n      address\n      |> InfoAPI.Account.get_balance()\n      |> api_response(conn, :balance)\n    end\n  end\n\n  def get_utxos(conn, params) do\n    with {:ok, constraints} <- AccountConstraints.parse(params) do\n      constraints\n      |> InfoAPI.Account.get_utxos()\n      |> api_response(conn, :utxos)\n    end\n  end\n\n  def get_exitable_utxos(conn, params) do\n    with {:ok, address} <- expect(params, \"address\", :address) do\n      address\n      |> SecurityAPI.Account.get_exitable_utxos()\n      |> api_response(conn, :exitable_utxos)\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/lib/web/controllers/alarm.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.Controller.Alarm do\n  @moduledoc \"\"\"\n  Module provides operation related to the watcher raised alarms that might point to\n  faulty watcher node.\n  \"\"\"\n\n  use OMG.WatcherRPC.Web, :controller\n\n  alias OMG.Watcher.API.Alarm\n\n  def get_alarms(conn, _params) do\n    {:ok, alarms} = Alarm.get_alarms()\n    api_response(alarms, conn, :alarm)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/lib/web/controllers/block.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.Controller.Block do\n  @moduledoc \"\"\"\n  Operations related to block.\n  \"\"\"\n  require Logger\n\n  use OMG.WatcherRPC.Web, :controller\n\n  alias OMG.Watcher\n  alias OMG.WatcherInfo.API.Block, as: InfoApiBlock\n  alias OMG.WatcherRPC.Web.Validator\n\n  @doc \"\"\"\n  Retrieves a specific block by block number.\n  \"\"\"\n  def get_block(conn, params) do\n    with {:ok, blknum} <- expect(params, \"blknum\", :pos_integer) do\n      blknum\n      |> InfoApiBlock.get()\n      |> api_response(conn, :block)\n    end\n  end\n\n  @doc \"\"\"\n  Retrieves a list of most recent blocks\n  \"\"\"\n  def get_blocks(conn, params) do\n    with {:ok, constraints} <- Validator.BlockConstraints.parse(params) do\n      constraints\n      |> InfoApiBlock.get_blocks()\n      |> api_response(conn, :blocks)\n    end\n  end\n\n  @doc \"\"\"\n  Executes stateful and stateless validation of a block.\n  \"\"\"\n  def validate_block(conn, params) do\n    with {:ok, block} <- Validator.BlockConstraints.parse_to_validate(params) do\n      case Watcher.BlockValidator.stateless_validate(block) do\n        {:ok, true} ->\n          api_response(%{valid: true}, conn, :validate_block)\n\n        {:error, reason} ->\n          Logger.info(\"Block #{block.number} is invalid due to #{reason}\")\n          api_response(%{valid: false}, conn, :validate_block)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/lib/web/controllers/challenge.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.Controller.Challenge do\n  @moduledoc \"\"\"\n  Handles exit challenges\n  \"\"\"\n\n  use OMG.WatcherRPC.Web, :controller\n\n  alias OMG.Watcher.API\n  alias OMG.Watcher.Utxo\n\n  @doc \"\"\"\n  Challenges exits\n  \"\"\"\n  def get_utxo_challenge(conn, params) do\n    with {:ok, utxo_pos} <- expect(params, \"utxo_pos\", :pos_integer),\n         {:ok, utxo} <- Utxo.Position.decode(utxo_pos) do\n      utxo\n      |> API.Utxo.create_challenge()\n      |> api_response(conn, :challenge)\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/lib/web/controllers/configuration.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.Controller.Configuration do\n  @moduledoc \"\"\"\n  Module provides operation related to the watcher raised alarms that might point to\n  faulty watcher node.\n  \"\"\"\n\n  use OMG.WatcherRPC.Web, :controller\n\n  alias OMG.Watcher.API.Configuration\n\n  def get_configuration(conn, _params) do\n    {:ok, configuration} = Configuration.get_configuration()\n    api_response(configuration, conn, :configuration)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/lib/web/controllers/deposit.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.Controller.Deposit do\n  @moduledoc \"\"\"\n  Operations related to deposits.\n  \"\"\"\n\n  use OMG.WatcherRPC.Web, :controller\n\n  alias OMG.WatcherInfo.API.Deposit, as: InfoApiDeposit\n  alias OMG.WatcherRPC.Web.Validator\n\n  @doc \"\"\"\n  Retrieves a list of deposits.\n  \"\"\"\n  def get_deposits(conn, params) do\n    case Validator.DepositConstraints.parse(params) do\n      {:ok, constraints} ->\n        constraints\n        |> InfoApiDeposit.get_deposits()\n        |> api_response(conn, :deposits)\n\n      error ->\n        error\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/lib/web/controllers/fallback.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.Controller.Fallback do\n  @moduledoc \"\"\"\n  The fallback handler.\n  \"\"\"\n\n  use Phoenix.Controller\n  import Plug.Conn\n  alias OMG.WatcherRPC.Web.Views\n\n  @errors %{\n    exit_not_found: %{\n      code: \"challenge:exit_not_found\",\n      description: \"The challenge of particular exit is impossible because exit is inactive or missing\"\n    },\n    utxo_not_spent: %{\n      code: \"challenge:utxo_not_spent\",\n      description: \"The challenge of particular exit is impossible because provided utxo is not spent\"\n    },\n    transaction_not_found: %{\n      code: \"transaction:not_found\",\n      description: \"Transaction doesn't exist for provided search criteria\"\n    },\n    utxo_not_found: %{\n      code: \"exit:invalid\",\n      description: \"Utxo was spent or does not exist.\"\n    },\n    tx_for_input_not_found: %{\n      code: \"in_flight_exit:tx_for_input_not_found\",\n      description: \"No transaction that created input.\"\n    },\n    deposit_input_spent_ife_unsupported: %{\n      code: \"in_flight_exit:deposit_input_spent_ife_unsupported\",\n      description: \"Retrieving IFE data of a transaction with a spent deposit is unsupported.\"\n    },\n    econnrefused: %{\n      code: \"connection:econnrefused\",\n      description: \"Cannot connect to the Ethereum node.\"\n    },\n    childchain_unreachable: %{\n      code: \"connection:childchain_unreachable\",\n      description: \"Cannot communicate with the childchain.\"\n    },\n    insufficient_funds: %{\n      code: \"transaction.create:insufficient_funds\",\n      description: \"Account balance is too low to satisfy the payment.\"\n    },\n    too_many_inputs: %{\n      code: \"transaction.create:too_many_inputs\",\n      description: \"The number of inputs required to cover the payment and fee exceeds the maximum allowed.\"\n    },\n    too_many_outputs: %{\n      code: \"transaction.create:too_many_outputs\",\n      description: \"Total number of payments + change + fees exceed maximum allowed outputs.\"\n    },\n    single_input: %{\n      code: \"merge:single_input\",\n      description: \"Only one input found for the given address and currency.\"\n    },\n    no_inputs_found: %{\n      code: \"merge:no_inputs_found\",\n      description: \"No inputs found for the given address and currency.\"\n    },\n    multiple_currencies: %{\n      code: \"merge:multiple_currencies\",\n      description: \"All inputs must have the same currency.\"\n    },\n    multiple_input_owners: %{\n      code: \"merge:multiple_input_owners\",\n      description: \"All inputs must have the same owner.\"\n    },\n    duplicate_input_positions: %{\n      code: \"merge:duplicate_input_positions\",\n      description: \"Duplicate input positions provided.\"\n    },\n    empty_transaction: %{\n      code: \"transaction.create:empty_transaction\",\n      description: \"Requested payment transfers no funds.\"\n    },\n    self_transaction_not_supported: %{\n      code: \"transaction.create:self_transaction_not_supported\",\n      description: \"This endpoint cannot be used to create merge or split transactions.\"\n    },\n    invalid_merkle_root: %{\n      code: \"block.validate:invalid_merkle_root\",\n      description: \"Block hash does not match reconstructed Merkle root.\"\n    },\n    missing_signature: %{\n      code: \"submit_typed:missing_signature\",\n      description:\n        \"Signatures should correspond to inputs owner. When all non-empty inputs has the same owner, \" <>\n          \"signatures should be duplicated.\"\n    },\n    superfluous_signature: %{\n      code: \"submit_typed:superfluous_signature\",\n      description: \"Number of non-empty inputs should match signatures count. Remove redundant signatures.\"\n    },\n    no_deposit_for_given_blknum: %{\n      code: \"exit:invalid\",\n      description: \"Utxo was spent or does not exist.\"\n    },\n    operation_not_found: %{\n      code: \"operation:not_found\",\n      description: \"Operation cannot be found. Check request URL.\"\n    },\n    operation_bad_request: %{\n      code: \"operation:bad_request\",\n      description: \"Parameters required by this operation are missing or incorrect.\"\n    }\n  }\n\n  def call(conn, {:error, {:validation_error, param_name, validator}}) do\n    error = error_info(conn, :operation_bad_request)\n    :telemetry.execute([:web, :fallback], %{error: 1}, %{error_code: error.code, route: current_route(conn)})\n\n    conn\n    |> assign_error_metadata_to_conn(error)\n    |> put_view(Views.Error)\n    |> render(\n      :error,\n      %{\n        code: error.code,\n        description: error.description,\n        messages: %{\n          validation_error: %{\n            parameter: param_name,\n            validator: inspect(validator)\n          }\n        }\n      }\n    )\n  end\n\n  def call(conn, {:error, {reason, data}}) do\n    error = error_info(conn, reason)\n    :telemetry.execute([:web, :fallback], %{error: 1}, %{error_code: error.code, route: current_route(conn)})\n\n    conn\n    |> assign_error_metadata_to_conn(error)\n    |> put_view(Views.Error)\n    |> render(:error, %{code: error.code, description: error.description, messages: data})\n  end\n\n  def call(conn, {:error, reason}) do\n    error = error_info(conn, reason)\n    :telemetry.execute([:web, :fallback], %{error: 1}, %{error_code: error.code, route: current_route(conn)})\n\n    conn\n    |> assign_error_metadata_to_conn(error)\n    |> put_view(Views.Error)\n    |> render(:error, %{code: error.code, description: error.description})\n  end\n\n  def call(conn, :error), do: call(conn, {:error, :unknown_error})\n\n  # Controller's action with expression has no match, e.g. on guard\n  def call(conn, _), do: call(conn, {:error, :unknown_error})\n\n  defp error_info(conn, reason) do\n    case Map.get(@errors, reason) do\n      nil -> %{code: \"#{action_name(conn)}#{inspect(reason)}\", description: nil}\n      error -> error\n    end\n  end\n\n  defp assign_error_metadata_to_conn(conn, error) do\n    conn\n    |> assign(:error_type, error.code)\n    |> assign(:error_msg, error.description)\n  end\n\n  # There isn't a way to get the route directly from a conn, so we need this roundabout way.\n  # We want the route instead of the request path because it's of limited cardinality for the metric tags.\n  defp current_route(%{private: %{phoenix_router: phoenix_router}} = conn) do\n    case Phoenix.Router.route_info(phoenix_router, conn.method, conn.request_path, conn.host) do\n      %{:route => route} -> route\n      :error -> nil\n    end\n  end\n\n  defp current_route(_) do\n    nil\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/lib/web/controllers/fee.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.Controller.Fee do\n  @moduledoc \"\"\"\n  Operations related to fees.\n  \"\"\"\n\n  use OMG.WatcherRPC.Web, :controller\n\n  alias OMG.WatcherInfo.HttpRPC.Client\n\n  def fees_all(conn, params) do\n    with {:ok, _} <- expect(params, \"currencies\", list: &to_currency/1, optional: true),\n         {:ok, _} <- expect(params, \"tx_types\", list: &to_tx_type/1, optional: true),\n         child_chain_url <- Application.get_env(:omg_watcher_info, :child_chain_url),\n         {:ok, fees} <- Client.get_fees(params, child_chain_url) do\n      api_response(fees, conn, :fees_all)\n    end\n  end\n\n  defp to_currency(currency_str) do\n    expect(%{\"currency\" => currency_str}, \"currency\", :address)\n  end\n\n  defp to_tx_type(tx_type_str) do\n    expect(%{\"tx_type\" => tx_type_str}, \"tx_type\", :non_neg_integer)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/lib/web/controllers/in_flight_exit.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.Controller.InFlightExit do\n  @moduledoc \"\"\"\n  Operations related to in flight exits starting and handling.\n  \"\"\"\n\n  use OMG.WatcherRPC.Web, :controller\n\n  alias OMG.Watcher.API\n\n  @doc \"\"\"\n  For a given transaction provided in params,\n  responds with arguments for plasma contract function that starts in-flight exit.\n  \"\"\"\n  def get_in_flight_exit(conn, params) do\n    handle_txbytes_based_request(conn, params, &API.InFlightExit.get_in_flight_exit/1, :in_flight_exit)\n  end\n\n  def get_competitor(conn, params) do\n    handle_txbytes_based_request(conn, params, &API.InFlightExit.get_competitor/1, :competitor)\n  end\n\n  def prove_canonical(conn, params) do\n    handle_txbytes_based_request(conn, params, &API.InFlightExit.prove_canonical/1, :prove_canonical)\n  end\n\n  def get_input_challenge_data(conn, params) do\n    with {:ok, txbytes} <- expect(params, \"txbytes\", :hex),\n         {:ok, input_index} <- expect(params, \"input_index\", :non_neg_integer) do\n      API.InFlightExit.get_input_challenge_data(txbytes, input_index)\n      |> api_response(conn, :get_input_challenge_data)\n    end\n  end\n\n  def get_output_challenge_data(conn, params) do\n    with {:ok, txbytes} <- expect(params, \"txbytes\", :hex),\n         {:ok, output_index} <- expect(params, \"output_index\", :non_neg_integer) do\n      API.InFlightExit.get_output_challenge_data(txbytes, output_index)\n      |> api_response(conn, :get_output_challenge_data)\n    end\n  end\n\n  # NOTE: don't overdo this DRYing here - if the above controller functions evolve and diverge, it might be better to\n  #       un-DRY\n  defp handle_txbytes_based_request(conn, params, api_function, template) do\n    with {:ok, txbytes} <- expect(params, \"txbytes\", :hex) do\n      txbytes\n      |> api_function.()\n      |> api_response(conn, template)\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/lib/web/controllers/stats.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.Controller.Stats do\n  @moduledoc \"\"\"\n  Operations related to network statistics.\n  \"\"\"\n\n  use OMG.WatcherRPC.Web, :controller\n\n  alias OMG.WatcherInfo.API.Stats, as: InfoApiStats\n\n  @doc \"\"\"\n  Retrieves network statistics\n  \"\"\"\n  def get_statistics(conn, _params) do\n    response = InfoApiStats.get()\n    api_response(response, conn, :stats)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/lib/web/controllers/status.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.Controller.Status do\n  @moduledoc \"\"\"\n  Module provides operation related to the child chain health status, like: geth syncing status, last minned block\n  number and time and last block verified by watcher.\n  \"\"\"\n\n  use OMG.WatcherRPC.Web, :controller\n  # check for health before calling action\n  plug(OMG.WatcherRPC.Web.Plugs.Health)\n  alias OMG.Watcher.API.Status\n\n  @doc \"\"\"\n  Gets plasma network and Watcher status\n  \"\"\"\n  def get_status(conn, _params) do\n    api_response(Status.get_status(), conn, :status)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/lib/web/controllers/transaction.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.Controller.Transaction do\n  @moduledoc \"\"\"\n  Operations related to transaction.\n  \"\"\"\n\n  use OMG.WatcherRPC.Web, :controller\n\n  alias OMG.Watcher.API.Transaction, as: SecurityApiTransaction\n  alias OMG.Watcher.State.Transaction\n  alias OMG.WatcherInfo.API.Transaction, as: InfoApiTransaction\n  alias OMG.WatcherInfo.OrderFeeFetcher\n  alias OMG.WatcherInfo.Transaction, as: TransactionCreator\n  alias OMG.WatcherRPC.Web.Validator\n\n  @doc \"\"\"\n  Retrieves a specific transaction by id.\n  \"\"\"\n  def get_transaction(conn, params) do\n    with {:ok, id} <- expect(params, \"id\", :hash) do\n      id\n      |> InfoApiTransaction.get()\n      |> api_response(conn, :transaction)\n    end\n  end\n\n  @doc \"\"\"\n  Retrieves a list of transactions\n  \"\"\"\n  def get_transactions(conn, params) do\n    with {:ok, constraints} <- Validator.TransactionConstraints.parse(params) do\n      constraints\n      |> InfoApiTransaction.get_transactions()\n      |> api_response(conn, :transactions)\n    end\n  end\n\n  @doc \"\"\"\n  Submits transaction to child chain\n  \"\"\"\n  def submit(conn, params) do\n    with {:ok, txbytes} <- expect(params, \"transaction\", :hex) do\n      submit_tx_sec(txbytes, conn)\n    end\n  end\n\n  @doc \"\"\"\n  Submits transaction to child chain\n  \"\"\"\n  def batch_submit(conn, params) do\n    with {:ok, txbytes} <- expect(params, \"transactions\", list: &to_transaction/1, optional: false) do\n      submit_tx_sec(txbytes, conn)\n    end\n  end\n\n  @doc \"\"\"\n  Thin-client version of `/transaction.submit` that accepts json encoded transaction\n  \"\"\"\n  def submit_typed(conn, params) do\n    with {:ok, signed_tx} <- Validator.TypedDataSigned.parse(params) do\n      # it's tempting to skip the unnecessary encoding-decoding part, but it gain broader\n      # validation and communicates with API layer with known structures than bytes\n      signed_tx\n      |> Transaction.Signed.encode()\n      |> submit_tx_inf(conn)\n    end\n  end\n\n  @doc \"\"\"\n  Given token, amount and spender, finds spender's inputs sufficient to perform a payment.\n  If also provided with receiver's address, creates and encodes a transaction.\n  \"\"\"\n  def create(conn, params) do\n    with {:ok, order} <- Validator.Order.parse(params),\n         {:ok, order} <- OrderFeeFetcher.add_fee_to_order(order) do\n      order\n      |> InfoApiTransaction.create()\n      |> TransactionCreator.include_typed_data()\n      |> api_response(conn, :create)\n    end\n  end\n\n  @doc \"\"\"\n  Creates and encodes a merge transaction.\n  Can be called with either an array of utxo positions or an address currency pair.\n  \"\"\"\n  def merge(conn, params) do\n    with {:ok, constraints} <- Validator.MergeConstraints.parse(params) do\n      constraints\n      |> InfoApiTransaction.merge()\n      |> TransactionCreator.include_typed_data()\n      |> api_response(conn, :merge)\n    end\n  end\n\n  # Provides extra validation (recover_from) and passes transaction to API layer\n  defp submit_tx_inf(txbytes, conn) do\n    with {:ok, recovered_tx} <- Transaction.Recovered.recover_from(txbytes),\n         :ok <- is_supported(recovered_tx) do\n      recovered_tx\n      |> Map.get(:signed_tx)\n      |> InfoApiTransaction.submit()\n      |> api_response(conn, :submission)\n    end\n  end\n\n  # Provides extra validation (recover_from) and passes transaction to API layer\n  defp submit_tx_sec(txbytes, conn) when is_list(txbytes) do\n    txbytes\n    |> SecurityApiTransaction.batch_submit()\n    |> api_response(conn, :batch_submission)\n  end\n\n  defp submit_tx_sec(txbytes, conn) do\n    with {:ok, recovered_tx} <- Transaction.Recovered.recover_from(txbytes),\n         :ok <- is_supported(recovered_tx) do\n      recovered_tx\n      |> Map.get(:signed_tx)\n      |> SecurityApiTransaction.submit()\n      |> api_response(conn, :submission)\n    end\n  end\n\n  defp is_supported(%Transaction.Recovered{signed_tx: %Transaction.Signed{raw_tx: %Transaction.Fee{}}}) do\n    {:error, :transaction_not_supported}\n  end\n\n  defp is_supported(%Transaction.Recovered{}), do: :ok\n\n  defp to_transaction(transaction) do\n    expect(%{\"transaction\" => transaction}, \"transaction\", :hex)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/lib/web/controllers/utxo.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.Controller.Utxo do\n  @moduledoc \"\"\"\n  Operations related to utxo.\n  Modify the state in the database.\n  \"\"\"\n\n  use OMG.WatcherRPC.Web, :controller\n\n  alias OMG.Watcher.API\n  alias OMG.Watcher.Utxo\n\n  def get_utxo_exit(conn, params) do\n    with {:ok, utxo_pos} <- expect(params, \"utxo_pos\", :pos_integer),\n         {:ok, utxo} <- Utxo.Position.decode(utxo_pos) do\n      utxo\n      |> API.Utxo.compose_utxo_exit()\n      |> api_response(conn, :utxo_exit)\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/lib/web/endpoint.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.Endpoint do\n  use Sentry.PlugCapture\n  use Phoenix.Endpoint, otp_app: :omg_watcher_rpc\n\n  plug(OMG.Utils.RemoteIP)\n  plug(Plug.RequestId)\n  plug(Plug.Logger, log: :debug)\n  plug(Plug.Telemetry, event_prefix: [:watcher_rpc, :endpoint])\n\n  if code_reloading? do\n    plug(Phoenix.CodeReloader)\n  end\n\n  plug(\n    Plug.Parsers,\n    parsers: [:json],\n    pass: [],\n    json_decoder: Jason\n  )\n\n  plug(Sentry.PlugContext)\n  plug(Plug.MethodOverride)\n  plug(Plug.Head)\n\n  if Application.get_env(:omg_watcher_rpc, OMG.WatcherRPC.Web.Endpoint)[:enable_cors],\n    do: plug(CORSPlug)\n\n  plug(OMG.WatcherRPC.Web.Plugs.MethodParamFilter)\n  plug(OMG.WatcherRPC.Web.Router)\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/lib/web/plugs/health.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.Plugs.Health do\n  @moduledoc \"\"\"\n  Observes the systems alarms and prevents calls towards an unhealthy one.\n  \"\"\"\n\n  alias OMG.Status\n  alias OMG.Utils.HttpRPC.Error\n  alias Phoenix.Controller\n\n  import Plug.Conn\n  require Logger\n  use GenServer\n\n  ###\n  ### PLUG\n  ###\n  def init(options), do: options\n\n  def call(conn, _params) do\n    # is anything raised?\n    if Status.is_healthy() do\n      conn\n    else\n      data =\n        Error.serialize(\n          \"operation:service_unavailable\",\n          \"The server is not ready to handle the request. Check the alarms for more info.\"\n        )\n\n      conn\n      |> Controller.json(data)\n      |> halt()\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/lib/web/plugs/method_param_filter.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.Plugs.MethodParamFilter do\n  @moduledoc \"\"\"\n  Filters the `query_params`, `body_params` and `params` of the conn\n  depending on the HTTP method used.\n\n  For a POST: `query_params` will be ignored and `body_params` will be\n  set to `params`.\n\n  For a GET: `body_params` will be ignored and `query_params` will be\n  set to `params`.\n\n  For other http method, the original `conn` is returned.\n  \"\"\"\n\n  def init(args), do: args\n\n  def call(%Plug.Conn{method: \"POST\", body_params: params} = conn, _) do\n    conn\n    |> Map.put(:query_params, %{})\n    |> Map.put(:params, params)\n  end\n\n  def call(%Plug.Conn{method: \"GET\", query_params: params} = conn, _) do\n    conn\n    |> Map.put(:body_params, %{})\n    |> Map.put(:params, params)\n  end\n\n  def call(conn, _), do: conn\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/lib/web/plugs/supported_watcher_modes.ex",
    "content": "# Copyright 2019 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.Plugs.SupportedWatcherModes do\n  @moduledoc \"\"\"\n  Ensures that unsupported endpoints return an appropriate error, depending on watcher's mode.\n  \"\"\"\n  alias OMG.WatcherRPC.Web.Controller\n\n  @behaviour Plug\n  @app :omg_watcher_rpc\n\n  @spec init([atom()]) :: [atom()]\n  def init(supported_modes), do: supported_modes\n\n  @spec call(Plug.Conn.t(), [atom()]) :: Plug.Conn.t()\n  def call(conn, supported_modes) do\n    case Application.get_env(@app, :api_mode) in supported_modes do\n      true ->\n        conn\n\n      false ->\n        conn\n        |> Controller.Fallback.call({:error, :operation_not_found})\n        |> Plug.Conn.halt()\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/lib/web/response.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.Response do\n  @moduledoc \"\"\"\n  Prepares the response into the expected result/data format.\n\n  Contains only the behaviours specific to the watcher.\n  For the generic response, see `OMG.Utils.HttpRPC.Response`.\n  \"\"\"\n\n  alias OMG.WatcherRPC.Configuration\n\n  @doc \"\"\"\n  Adds \"version\" and \"service_name\" to the response map.\n  \"\"\"\n  @spec add_app_infos(map()) :: %{version: String.t(), service_name: String.t()}\n  def add_app_infos(response) do\n    response\n    |> Map.put(:version, Configuration.version())\n    |> Map.put(:service_name, \"#{Configuration.service_name()}\")\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/lib/web/router.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.Router do\n  use OMG.WatcherRPC.Web, :router\n  alias OMG.WatcherRPC.Web.Plugs.SupportedWatcherModes\n\n  pipeline :api do\n    plug(:accepts, [\"json\"])\n  end\n\n  pipeline :security_api do\n    plug(:accepts, [\"json\"])\n    plug(SupportedWatcherModes, [:watcher, :watcher_info])\n  end\n\n  pipeline :info_api do\n    plug(:accepts, [\"json\"])\n    plug(SupportedWatcherModes, [:watcher_info])\n  end\n\n  # A note on scope ordering.\n  #\n  # The scopes are order-sensitive. Due to the way that plug works sequentially,\n  # once a plug halts, the rest of the router does not get evaluated, even if it is\n  # outside the scope of the used plug.\n  #\n  # Therefore, always put the more permissive scope first, e.g. put the scope with\n  # `plug(SupportedWatcherModes, [:watcher, :watcher_info])` before the scope with\n  # plug(SupportedWatcherModes, [:watcher_info])\n\n  #\n  # Endpoints allowed on both Watcher Security-Critical and Info API\n  #\n  scope \"/\", OMG.WatcherRPC.Web do\n    pipe_through([:security_api])\n\n    post(\"/status.get\", Controller.Status, :get_status)\n    get(\"/alarm.get\", Controller.Alarm, :get_alarms)\n    get(\"/configuration.get\", Controller.Configuration, :get_configuration)\n\n    post(\"/account.get_exitable_utxos\", Controller.Account, :get_exitable_utxos)\n\n    post(\"/block.validate\", Controller.Block, :validate_block)\n\n    post(\"/utxo.get_exit_data\", Controller.Utxo, :get_utxo_exit)\n    post(\"/utxo.get_challenge_data\", Controller.Challenge, :get_utxo_challenge)\n\n    post(\"/transaction.submit\", Controller.Transaction, :submit)\n    post(\"/transaction.batch_submit\", Controller.Transaction, :batch_submit)\n\n    post(\"/in_flight_exit.get_data\", Controller.InFlightExit, :get_in_flight_exit)\n    post(\"/in_flight_exit.get_competitor\", Controller.InFlightExit, :get_competitor)\n    post(\"/in_flight_exit.prove_canonical\", Controller.InFlightExit, :prove_canonical)\n    post(\"/in_flight_exit.get_input_challenge_data\", Controller.InFlightExit, :get_input_challenge_data)\n    post(\"/in_flight_exit.get_output_challenge_data\", Controller.InFlightExit, :get_output_challenge_data)\n  end\n\n  #\n  # Extra endpoints allowed only on Watcher Info API\n  #\n  scope \"/\", OMG.WatcherRPC.Web do\n    pipe_through([:info_api])\n\n    post(\"/account.get_balance\", Controller.Account, :get_balance)\n    post(\"/account.get_utxos\", Controller.Account, :get_utxos)\n    post(\"/account.get_transactions\", Controller.Transaction, :get_transactions)\n\n    post(\"/block.all\", Controller.Block, :get_blocks)\n\n    post(\"/deposit.all\", Controller.Deposit, :get_deposits)\n\n    post(\"/transaction.all\", Controller.Transaction, :get_transactions)\n    post(\"/transaction.get\", Controller.Transaction, :get_transaction)\n    post(\"/transaction.create\", Controller.Transaction, :create)\n\n    post(\"/transaction.submit_typed\", Controller.Transaction, :submit_typed)\n    post(\"/transaction.merge\", Controller.Transaction, :merge)\n\n    post(\"/block.get\", Controller.Block, :get_block)\n\n    post(\"/fees.all\", Controller.Fee, :fees_all)\n\n    post(\"/stats.get\", Controller.Stats, :get_statistics)\n  end\n\n  # Fallbacks\n  # NOTE: This *has to* be the last route, catching all unhandled paths\n  scope \"/\", OMG.WatcherRPC.Web do\n    pipe_through([:api])\n    match(:*, \"/*path\", Controller.Fallback, {:error, :operation_not_found})\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/lib/web/serializers/base.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.Serializer.Base do\n  @moduledoc \"\"\"\n  Common structure formatters module.\n  \"\"\"\n\n  def to_utxo(%{blknum: blknum, txindex: txindex, oindex: oindex} = db_entry) do\n    alias OMG.Watcher.Utxo\n    require Utxo\n\n    db_entry\n    |> Map.take([\n      :amount,\n      :currency,\n      :blknum,\n      :txindex,\n      :oindex,\n      :otype,\n      :owner,\n      :creating_txhash,\n      :spending_txhash,\n      :inserted_at,\n      :updated_at\n    ])\n    |> Map.put(:utxo_pos, Utxo.position(blknum, txindex, oindex) |> Utxo.Position.encode())\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/lib/web/sockets/socket.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.Socket do\n  @moduledoc \"\"\"\n  This module is the entry points for websocket connections to the watcher API. It contains the\n  channels to which providers/clients can connect to listen and receive events.\n  \"\"\"\n\n  use Phoenix.Socket, log: :debug\n\n  # Socket params are passed from the client and can\n  # be used to verify and authenticate a user. After\n  # verification, you can put default assigns into\n  # the socket that will be set for all channels, ie\n  #\n  #     {:ok, assign(socket, :user_id, verified_user_id)}\n  #\n  # To deny connection, return `:error`.\n  #\n  # See `Phoenix.Token` documentation for examples in\n  # performing token verification on connect.\n  def connect(_params, socket) do\n    {:ok, socket}\n  end\n\n  # Socket id's are topics that allow you to identify all sockets for a given user:\n  #\n  #     def id(socket), do: \"user_socket:#{socket.assigns.user_id}\"\n  #\n  # Would allow you to broadcast a \"disconnect\" event and terminate\n  # all active sockets and channels for a given user:\n  #\n  #     OMG.WatcherRPC.Web.Endpoint.broadcast(\"user_socket:#{user.id}\", \"disconnect\", %{})\n  #\n  # Returning `nil` makes this socket anonymous.\n  def id(_socket), do: nil\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/lib/web/validators/account_constraints.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.Validator.AccountConstraints do\n  @moduledoc \"\"\"\n  Validates `/account.get_utxos` query parameters\n  \"\"\"\n\n  alias OMG.WatcherRPC.Web.Validator.Helpers\n\n  @doc \"\"\"\n  Validates possible query constraints, stops on first error.\n  \"\"\"\n  @spec parse(%{binary() => any()}) :: {:ok, Keyword.t()} | {:error, any()}\n  def parse(params) do\n    constraints = [\n      {\"limit\", [pos_integer: true, lesser: 1000, optional: true], :limit},\n      {\"page\", [:pos_integer, :optional], :page},\n      {\"address\", [:address], :address}\n    ]\n\n    Helpers.validate_constraints(params, constraints)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/lib/web/validators/block_constraints.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.Validator.BlockConstraints do\n  @moduledoc \"\"\"\n  Validates `/block.all` query parameters\n  \"\"\"\n\n  use OMG.WatcherRPC.Web, :controller\n\n  alias OMG.Watcher.Block\n  alias OMG.WatcherRPC.Web.Validator.Helpers\n\n  @doc \"\"\"\n  Validates possible query constraints, stops on first error.\n  \"\"\"\n  @spec parse(%{binary() => any()}) :: {:ok, Keyword.t()} | {:error, any()}\n  def parse(params) do\n    constraints = [\n      {\"limit\", [pos_integer: true, lesser: 1000, optional: true], :limit},\n      {\"page\", [:pos_integer, :optional], :page}\n    ]\n\n    Helpers.validate_constraints(params, constraints)\n  end\n\n  @doc \"\"\"\n  Validates that a block submitted for validation is correctly formed.\n  \"\"\"\n  @spec parse_to_validate(Block.t()) ::\n          {:error, {:validation_error, binary, any}} | {:ok, Block.t()}\n  def parse_to_validate(block) do\n    with {:ok, hash} <- expect(block, \"hash\", :hash),\n         {:ok, transactions} <- expect(block, \"transactions\", list: &is_hex/1),\n         {:ok, number} <- expect(block, \"number\", :pos_integer),\n         do: {:ok, %Block{hash: hash, transactions: transactions, number: number}}\n  end\n\n  defp is_hex(original) do\n    expect(%{\"hash\" => original}, \"hash\", :hex)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/lib/web/validators/deposit_constraints.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.Validator.DepositConstraints do\n  @moduledoc \"\"\"\n  Validates query parameters for `deposit.all`\n  \"\"\"\n\n  alias OMG.WatcherRPC.Web.Validator.Helpers\n\n  @doc \"\"\"\n  Validates possible query constraints, stops on first error.\n  \"\"\"\n  @spec parse(%{binary() => any()}) :: {:ok, Keyword.t()} | {:error, any()}\n  def parse(params) do\n    constraints = [\n      {\"limit\", [pos_integer: true, lesser: 1000, optional: true], :limit},\n      {\"page\", [:pos_integer, :optional], :page},\n      {\"address\", :address, :address}\n    ]\n\n    Helpers.validate_constraints(params, constraints)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/lib/web/validators/helpers.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.Validator.Helpers do\n  @moduledoc \"\"\"\n  helper for validators\n  \"\"\"\n  import OMG.Utils.HttpRPC.Validator.Base, only: [expect: 3]\n\n  @doc \"\"\"\n  Validates possible params with query constraints, stops on first error.\n  \"\"\"\n  @spec validate_constraints(%{binary() => any()}, list()) :: {:ok, Keyword.t()} | {:error, any()}\n  def validate_constraints(params, constraints) do\n    Enum.reduce_while(constraints, {:ok, []}, fn {key, validators, atom}, {:ok, list} ->\n      case expect(params, key, validators) do\n        {:ok, nil} ->\n          {:cont, {:ok, list}}\n\n        {:ok, value} ->\n          {:cont, {:ok, [{atom, value} | list]}}\n\n        error ->\n          {:halt, error}\n      end\n    end)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/lib/web/validators/merge_constraints.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.Validator.MergeConstraints do\n  @moduledoc \"\"\"\n  Validates `/transaction.merge` parameters\n  \"\"\"\n\n  alias OMG.Utils.HttpRPC.Validator.Base\n  alias OMG.WatcherRPC.Web.Validator.Helpers\n\n  import OMG.Utils.HttpRPC.Validator.Base\n\n  require OMG.Watcher.State.Transaction.Payment\n\n  @doc \"\"\"\n  Parses and validates request body for `/transaction.merge`\n  \"\"\"\n  @spec parse(map()) :: {:ok, Keyword.t()} | Base.validation_error_t()\n  def parse(params) do\n    with {:ok, constraints} <- get_constraints(params),\n         {:ok, result} <- Helpers.validate_constraints(params, constraints) do\n      {:ok, result}\n    end\n  end\n\n  defp get_constraints(params) do\n    case params do\n      %{\"address\" => _, \"currency\" => _} ->\n        {:ok, [{\"address\", [:address], :address}, {\"currency\", [:currency], :currency}]}\n\n      %{\"utxo_positions\" => _} ->\n        {:ok, [{\"utxo_positions\", [min_length: 2, max_length: 4, list: &to_utxo_pos/1], :utxo_positions}]}\n\n      _ ->\n        {:error, :operation_bad_request}\n    end\n  end\n\n  defp to_utxo_pos(utxo_pos_string) do\n    expect(%{\"utxo_pos\" => utxo_pos_string}, \"utxo_pos\", :non_neg_integer)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/lib/web/validators/order.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.Validator.Order do\n  @moduledoc \"\"\"\n  Validates `/transaction.create` request body.\n  \"\"\"\n\n  require OMG.Watcher.State.Transaction.Payment\n\n  import OMG.Utils.HttpRPC.Validator.Base\n\n  alias OMG.Utils.HttpRPC.Validator.Base\n  alias OMG.Watcher.State.Transaction\n  alias OMG.WatcherInfo.Transaction, as: TransactionCreator\n\n  @doc \"\"\"\n  Parses and validates request body\n  \"\"\"\n  @spec parse(map()) :: {:ok, TransactionCreator.order_t()} | Base.validation_error_t()\n  def parse(params) do\n    with {:ok, owner} <- expect(params, \"owner\", :address),\n         {:ok, metadata} <- expect(params, \"metadata\", [:hash, :optional]),\n         {:ok, fee} <- expect(params, \"fee\", map: &parse_fee/1),\n         {:ok, payments} <- expect(params, \"payments\", list: &parse_payment/1),\n         {:ok, payments} <- fills_in_outputs?(payments),\n         :ok <- ensure_not_self_transaction(owner, payments) do\n      {:ok,\n       %{\n         owner: owner,\n         payments: payments,\n         fee: fee,\n         metadata: metadata\n       }}\n    end\n  end\n\n  defp ensure_not_self_transaction(owner, payments) when length(payments) > 0 do\n    payments\n    |> Enum.any?(fn payment ->\n      owner != payment[:owner]\n    end)\n    |> handle_self_tx_result()\n  end\n\n  defp ensure_not_self_transaction(_, _), do: :ok\n\n  defp handle_self_tx_result(true), do: :ok\n  defp handle_self_tx_result(false), do: {:error, :self_transaction_not_supported}\n\n  defp fills_in_outputs?(payments) do\n    if length(payments) <= Transaction.Payment.max_outputs(),\n      do: {:ok, payments},\n      else: error(\"payments\", {:too_many_payments, Transaction.Payment.max_outputs()})\n  end\n\n  defp parse_payment(raw_payment) do\n    with {:ok, owner} <- expect(raw_payment, \"owner\", [:address, :optional]),\n         {:ok, amount} <- expect(raw_payment, \"amount\", :pos_integer),\n         {:ok, currency} <- expect(raw_payment, \"currency\", :address),\n         do: {:ok, %{owner: owner, currency: currency, amount: amount}}\n  end\n\n  defp parse_fee(map) when is_map(map) do\n    with {:ok, currency} <- expect(map, \"currency\", :address) do\n      {:ok, %{currency: currency}}\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/lib/web/validators/transaction_constraints.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.Validator.TransactionConstraints do\n  @moduledoc \"\"\"\n  Validates `/transaction.all` query parameters\n  \"\"\"\n  import OMG.Utils.HttpRPC.Validator.Base, only: [expect: 3]\n  alias OMG.WatcherRPC.Web.Validator.Helpers\n  @max_tx_types 16\n\n  @doc \"\"\"\n  Validates possible query constraints, stops on first error.\n  \"\"\"\n  @spec parse(%{binary() => any()}) :: {:ok, Keyword.t()} | {:error, any()}\n  def parse(params) do\n    constraints = [\n      {\"address\", [:address, :optional], :address},\n      {\"blknum\", [:pos_integer, :optional], :blknum},\n      {\"metadata\", [:hash, :optional], :metadata},\n      {\"txtypes\", [list: &to_tx_type/1, max_length: @max_tx_types, optional: true], :txtypes},\n      {\"limit\", [pos_integer: true, lesser: 1000, optional: true], :limit},\n      {\"page\", [:pos_integer, :optional], :page},\n      {\"end_datetime\", [:pos_integer, :optional], :end_datetime}\n    ]\n\n    Helpers.validate_constraints(params, constraints)\n  end\n\n  defp to_tx_type(tx_type_str) do\n    expect(%{\"txtype\" => tx_type_str}, \"txtype\", :non_neg_integer)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/lib/web/validators/typed_data_signed.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.Validator.TypedDataSigned do\n  @moduledoc \"\"\"\n  Validates `/transaction.submit_typed` request body.\n  \"\"\"\n\n  alias OMG.Utils.HttpRPC.Validator.Base\n  alias OMG.Watcher.Crypto\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.TypedDataHash.Config\n  alias OMG.Watcher.TypedDataHash.Tools\n  import OMG.Utils.HttpRPC.Validator.Base\n\n  @doc \"\"\"\n  Parses and validates request body for /transaction.submit_typed`\n  \"\"\"\n  @spec parse(map()) :: {:ok, Transaction.Signed.t()} | {:error, any()}\n  def parse(params) do\n    with {:ok, domain} <- expect(params, \"domain\", map: &parse_domain/1),\n         :ok <- ensure_network_match(domain),\n         {:ok, sigs} <- expect(params, \"signatures\", list: &to_signature/1),\n         {:ok, raw_tx} <- parse_transaction(params) do\n      {:ok, %Transaction.Signed{raw_tx: raw_tx, sigs: sigs}}\n    end\n  end\n\n  @spec parse_transaction(map()) :: {:ok, Transaction.Payment.t()} | {:error, any}\n  def parse_transaction(params) do\n    with {:ok, msg} <- expect(params, \"message\", :map),\n         inputs when is_list(inputs) <- parse_inputs(msg),\n         outputs when is_list(outputs) <- parse_outputs(msg),\n         {:ok, metadata} <- expect(msg, \"metadata\", :hash) do\n      {:ok, Transaction.Payment.new(inputs, outputs, metadata)}\n    end\n  end\n\n  @spec parse_domain(map()) :: {:ok, Tools.eip712_domain_t()} | Base.validation_error_t()\n  def parse_domain(map) when is_map(map) do\n    name = Map.get(map, \"name\")\n    version = Map.get(map, \"version\")\n\n    with {:ok, salt} <- expect(map, \"salt\", :hash),\n         {:ok, contract} <- expect(map, \"verifyingContract\", :address),\n         do: {:ok, %{name: name, version: version, salt: salt, verifyingContract: contract}}\n  end\n\n  @spec ensure_network_match(Tools.eip712_domain_t(), Tools.eip712_domain_t() | nil) :: :ok | Base.validation_error_t()\n  def ensure_network_match(domain_from_params, network_domain \\\\ nil) do\n    network_domain =\n      case network_domain do\n        nil -> Config.domain_separator_from_config()\n        params when is_map(params) -> Tools.domain_separator(params)\n      end\n\n    if network_domain == Tools.domain_separator(domain_from_params),\n      do: :ok,\n      else: error(\"domain\", :domain_separator_mismatch)\n  end\n\n  @spec to_signature(binary()) :: {:ok, <<_::520>>} | Base.validation_error_t()\n  defp to_signature(sig_str), do: expect(%{\"signature\" => sig_str}, \"signature\", :signature)\n\n  @spec parse_input(map()) :: {:ok, {integer(), integer(), integer()}} | Base.validation_error_t()\n  defp parse_input(input) do\n    with {:ok, blknum} <- expect(input, \"blknum\", :non_neg_integer),\n         {:ok, txindex} <- expect(input, \"txindex\", :non_neg_integer),\n         {:ok, oindex} <- expect(input, \"oindex\", :non_neg_integer),\n         do: {:ok, {blknum, txindex, oindex}}\n  end\n\n  @spec parse_inputs(map()) :: [{integer(), integer(), integer()}] | {:error, any()}\n  defp parse_inputs(message) do\n    require Transaction.Payment\n\n    0..(Transaction.Payment.max_inputs() - 1)\n    |> Enum.map(fn i -> expect(message, \"input#{i}\", map: &parse_input/1) end)\n    |> Enum.reject(&empty_input?/1)\n    |> all_success_or_error()\n  end\n\n  @spec parse_output(map()) ::\n          {:ok, {Crypto.address_t(), Crypto.address_t(), integer()}} | Base.validation_error_t()\n  defp parse_output(output) do\n    with {:ok, owner} <- expect(output, \"owner\", :address),\n         {:ok, currency} <- expect(output, \"currency\", :address),\n         {:ok, amount} <- expect(output, \"amount\", :non_neg_integer),\n         do: {:ok, {owner, currency, amount}}\n  end\n\n  @spec parse_outputs(map()) :: [{Crypto.address_t(), Crypto.address_t(), integer()}] | {:error, any()}\n  defp parse_outputs(message) do\n    require Transaction.Payment\n\n    0..(Transaction.Payment.max_outputs() - 1)\n    |> Enum.map(fn i -> expect(message, \"output#{i}\", map: &parse_output/1) end)\n    |> Enum.reject(&empty_output?/1)\n    |> all_success_or_error()\n  end\n\n  # we do not longer pad with empty so we need to filter here, because typed data still require exact 4 in/outputs\n  defp empty_input?({:ok, {0, 0, 0}}), do: true\n  defp empty_input?(_input), do: false\n  defp empty_output?({:ok, {_owner, _currency, 0}}), do: true\n  defp empty_output?(_output), do: false\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/lib/web/views/account.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.View.Account do\n  @moduledoc \"\"\"\n  The account view for rendering json\n  \"\"\"\n\n  use OMG.WatcherRPC.Web, :view\n  alias OMG.Utils.HttpRPC.Response\n  alias OMG.Utils.Paginator\n  alias OMG.Watcher.Utxo\n  alias OMG.WatcherRPC.Web.Response, as: WatcherRPCResponse\n\n  require Utxo\n\n  def render(\"balance.json\", %{response: balance}) do\n    balance\n    |> Response.serialize()\n    |> WatcherRPCResponse.add_app_infos()\n  end\n\n  def render(\"utxos.json\", %{response: %Paginator{data: utxos, data_paging: data_paging}}) do\n    utxos\n    |> Enum.map(&to_utxo/1)\n    |> Response.serialize_page(data_paging)\n    |> WatcherRPCResponse.add_app_infos()\n  end\n\n  def render(\"exitable_utxos.json\", %{response: utxos}) do\n    utxos\n    |> Enum.map(&to_utxo/1)\n    |> Response.serialize()\n    |> WatcherRPCResponse.add_app_infos()\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/lib/web/views/alarm.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.View.Alarm do\n  @moduledoc \"\"\"\n  The alarm view for rendering json\n  \"\"\"\n\n  use OMG.WatcherRPC.Web, :view\n  alias OMG.Utils.HttpRPC.Response\n  alias OMG.WatcherRPC.Web.Response, as: WatcherRPCResponse\n\n  def render(\"alarm.json\", %{response: alarms}) do\n    alarms\n    |> Response.serialize()\n    |> WatcherRPCResponse.add_app_infos()\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/lib/web/views/block.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.View.Block do\n  @moduledoc \"\"\"\n  The block view for rendering json\n  \"\"\"\n\n  use OMG.WatcherRPC.Web, :view\n\n  alias OMG.Utils.HttpRPC.Response\n  alias OMG.Utils.Paginator\n  alias OMG.WatcherRPC.Web.Response, as: WatcherRPCResponse\n\n  def render(\"block.json\", %{response: block}) do\n    block\n    |> Response.serialize()\n    |> WatcherRPCResponse.add_app_infos()\n  end\n\n  def render(\"blocks.json\", %{response: %Paginator{data: blocks, data_paging: data_paging}}) do\n    blocks\n    |> Response.serialize_page(data_paging)\n    |> WatcherRPCResponse.add_app_infos()\n  end\n\n  def render(\"validate_block.json\", %{response: block}) do\n    block\n    |> Response.serialize()\n    |> WatcherRPCResponse.add_app_infos()\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/lib/web/views/challenge.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.View.Challenge do\n  @moduledoc \"\"\"\n  The challenge view for rendering json\n  \"\"\"\n\n  use OMG.WatcherRPC.Web, :view\n  alias OMG.Utils.HttpRPC.Response\n  alias OMG.WatcherRPC.Web.Response, as: WatcherRPCResponse\n\n  def render(\"challenge.json\", %{response: challenge}) do\n    challenge\n    |> Response.serialize()\n    |> WatcherRPCResponse.add_app_infos()\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/lib/web/views/configuration.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.View.Configuration do\n  @moduledoc \"\"\"\n  The Configuration view for rendering json\n  \"\"\"\n\n  use OMG.WatcherRPC.Web, :view\n  alias OMG.Utils.HttpRPC.Response\n  alias OMG.WatcherRPC.Web.Response, as: WatcherRPCResponse\n\n  def render(\"configuration.json\", %{response: configuration}) do\n    configuration\n    |> to_api_format()\n    |> Response.serialize()\n    |> WatcherRPCResponse.add_app_infos()\n  end\n\n  defp to_api_format(%{contract_semver: contract_semver, network: network} = response) do\n    response\n    |> Map.put(:contract_semver, {:skip_hex_encode, contract_semver})\n    |> Map.put(:network, {:skip_hex_encode, network})\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/lib/web/views/deposit.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.View.Deposit do\n  @moduledoc \"\"\"\n  The deposit view for rendering JSON.\n  \"\"\"\n\n  alias OMG.Utils.HttpRPC.Response\n  alias OMG.Utils.Paginator\n  alias OMG.WatcherRPC.Web.Response, as: WatcherRPCResponse\n\n  use OMG.WatcherRPC.Web, :view\n\n  def render(\"deposits.json\", %{response: %Paginator{data: ethevents, data_paging: data_paging}}) do\n    ethevents\n    |> Enum.map(&render_ethevent/1)\n    |> Response.serialize_page(data_paging)\n    |> WatcherRPCResponse.add_app_infos()\n  end\n\n  defp render_ethevent(event) do\n    event\n    |> Map.update!(:txoutputs, &render_txoutputs/1)\n    |> Map.take([\n      :eth_height,\n      :event_type,\n      :log_index,\n      :root_chain_txhash,\n      :txoutputs,\n      :inserted_at,\n      :updated_at\n    ])\n  end\n\n  defp render_txoutputs(outputs) do\n    Enum.map(outputs, &to_utxo/1)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/lib/web/views/error.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.Views.Error do\n  @moduledoc \"\"\"\n  The error view for rendering json\n  \"\"\"\n\n  use OMG.WatcherRPC.Web, :view\n  require Logger\n\n  alias OMG.Utils.HttpRPC.Error\n  alias OMG.WatcherRPC.Web.Response, as: WatcherRPCResponse\n\n  @doc \"\"\"\n  Handles client errors, e.g. malformed json in request body\n  \"\"\"\n  def render(\"400.json\", _) do\n    \"operation:bad_request\"\n    |> Error.serialize(\"Server has failed to parse the request.\")\n    |> WatcherRPCResponse.add_app_infos()\n  end\n\n  @doc \"\"\"\n  Handles invalid input parsing errors, e.g. Content-Type not application/json\n  \"\"\"\n  def render(\"415.json\", _) do\n    \"operation:bad_request\"\n    |> Error.serialize(\"Invalid Content-Type header, use application/json.\")\n    |> WatcherRPCResponse.add_app_infos()\n  end\n\n  @doc \"\"\"\n  Supports internal server error thrown by Phoenix.\n  \"\"\"\n  def render(\"500.json\", %{reason: %{message: message}} = _conn) do\n    \"server:internal_server_error\"\n    |> Error.serialize(message)\n    |> WatcherRPCResponse.add_app_infos()\n  end\n\n  @doc \"\"\"\n  Renders the given error code, description and messages.\n  \"\"\"\n  def render(\"error.json\", %{code: code, description: description, messages: messages}) do\n    code\n    |> Error.serialize(description, messages)\n    |> WatcherRPCResponse.add_app_infos()\n  end\n\n  @doc \"\"\"\n  Renders the given error code and description.\n  \"\"\"\n  def render(\"error.json\", %{code: code, description: description}) do\n    code\n    |> Error.serialize(description)\n    |> WatcherRPCResponse.add_app_infos()\n  end\n\n  # In case no render clause matches or no\n  # template is found, let's render it as 500\n  def template_not_found(_template, _assigns) do\n    \"server:internal_server_error\"\n    |> Error.serialize(\"Server has failed to render the error.\")\n    |> WatcherRPCResponse.add_app_infos()\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/lib/web/views/fee.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.View.Fee do\n  @moduledoc \"\"\"\n  The challenge view for rendering json\n  \"\"\"\n\n  use OMG.WatcherRPC.Web, :view\n  alias OMG.Utils.HttpRPC.Response\n  alias OMG.WatcherRPC.Web.Response, as: WatcherRPCResponse\n\n  def render(\"fees_all.json\", %{response: fees}) do\n    fees\n    |> Enum.map(&parse_for_type/1)\n    |> Enum.into(%{})\n    |> Response.serialize()\n    |> WatcherRPCResponse.add_app_infos()\n  end\n\n  defp parse_for_type({tx_type, fees}) do\n    {tx_type, Enum.map(fees, &parse_for_token/1)}\n  end\n\n  defp parse_for_token(fee) do\n    fee\n    |> Map.put(\"currency\", {:skip_hex_encode, fee[\"currency\"]})\n    |> Map.put(\"pegged_currency\", {:skip_hex_encode, fee[\"pegged_currency\"]})\n    |> Map.put(\"updated_at\", {:skip_hex_encode, fee[\"updated_at\"]})\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/lib/web/views/in_flight_exit.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.View.InFlightExit do\n  @moduledoc \"\"\"\n  The transaction view for rendering json\n  \"\"\"\n\n  use OMG.WatcherRPC.Web, :view\n\n  alias OMG.Utils.HttpRPC.Response\n  alias OMG.Watcher.Utxo\n  alias OMG.WatcherRPC.Web.Response, as: WatcherRPCResponse\n\n  def render(\"in_flight_exit.json\", %{response: in_flight_exit}) do\n    in_flight_exit\n    |> Response.serialize()\n    |> WatcherRPCResponse.add_app_infos()\n  end\n\n  def render(\"competitor.json\", %{response: competitor}) do\n    competitor\n    |> Map.update!(:competing_tx_pos, &Utxo.Position.encode/1)\n    |> Map.update!(:input_utxo_pos, &Utxo.Position.encode/1)\n    |> Response.serialize()\n    |> WatcherRPCResponse.add_app_infos()\n  end\n\n  def render(\"prove_canonical.json\", %{response: prove_canonical}) do\n    prove_canonical\n    |> Map.update!(:in_flight_tx_pos, &Utxo.Position.encode/1)\n    |> Response.serialize()\n    |> WatcherRPCResponse.add_app_infos()\n  end\n\n  def render(\"get_input_challenge_data.json\", %{response: challenge_data}) do\n    challenge_data\n    |> Map.update!(:input_utxo_pos, &Utxo.Position.encode/1)\n    |> Response.serialize()\n    |> WatcherRPCResponse.add_app_infos()\n  end\n\n  def render(\"get_output_challenge_data.json\", %{response: challenge_data}) do\n    challenge_data\n    |> Map.update!(:in_flight_output_pos, &Utxo.Position.encode/1)\n    |> Response.serialize()\n    |> WatcherRPCResponse.add_app_infos()\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/lib/web/views/stats.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.View.Stats do\n  @moduledoc \"\"\"\n  The block view for rendering json\n  \"\"\"\n\n  use OMG.WatcherRPC.Web, :view\n\n  alias OMG.Utils.HttpRPC.Response\n  alias OMG.WatcherRPC.Web.Response, as: WatcherRPCResponse\n\n  def render(\"stats.json\", %{response: stats}) do\n    stats\n    |> Response.serialize()\n    |> WatcherRPCResponse.add_app_infos()\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/lib/web/views/status.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.View.Status do\n  @moduledoc \"\"\"\n  The status view for rendering json\n  \"\"\"\n\n  use OMG.WatcherRPC.Web, :view\n  alias OMG.Utils.HttpRPC.Response\n  alias OMG.WatcherRPC.Web.Response, as: WatcherRPCResponse\n\n  def render(\"status.json\", %{response: status}) do\n    status\n    |> format_byzantine_events()\n    |> Response.serialize()\n    |> WatcherRPCResponse.add_app_infos()\n  end\n\n  defp format_byzantine_events(%{byzantine_events: byzantine_events, services_synced_heights: heights} = status) do\n    prepared_events = Enum.map(byzantine_events, &format_byzantine_event/1)\n    prepared_heights = Enum.map(heights, &format_synced_height/1)\n\n    %{status | byzantine_events: prepared_events, services_synced_heights: prepared_heights}\n  end\n\n  defp format_byzantine_event(%{name: name} = event) do\n    %{event: name, details: event}\n  end\n\n  defp format_synced_height({name, height}) do\n    %{service: name, height: height}\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/lib/web/views/transaction.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.View.Transaction do\n  @moduledoc \"\"\"\n  The transaction view for rendering json\n  \"\"\"\n\n  alias OMG.Utils.HttpRPC.Response\n  alias OMG.Utils.Paginator\n  alias OMG.WatcherRPC.Web.Response, as: WatcherRPCResponse\n\n  use OMG.WatcherRPC.Web, :view\n\n  def render(\"transaction.json\", %{response: transaction}) do\n    transaction\n    |> render_transaction()\n    |> Response.serialize()\n    |> WatcherRPCResponse.add_app_infos()\n  end\n\n  def render(\"transactions.json\", %{response: %Paginator{data: transactions, data_paging: data_paging}}) do\n    transactions\n    |> Enum.map(&render_transaction/1)\n    |> Response.serialize_page(data_paging)\n    |> WatcherRPCResponse.add_app_infos()\n  end\n\n  def render(\"submission.json\", %{response: transaction}) do\n    transaction\n    |> Response.serialize()\n    |> WatcherRPCResponse.add_app_infos()\n  end\n\n  def render(\"batch_submission.json\", %{response: transactions}) do\n    transactions\n    |> Response.serialize()\n    |> WatcherRPCResponse.add_app_infos()\n  end\n\n  def render(\"merge.json\", %{response: advice}) do\n    render_transactions(advice)\n  end\n\n  def render(\"create.json\", %{response: advice}) do\n    render_transactions(advice)\n  end\n\n  defp render_transactions(advice) do\n    transactions =\n      advice.transactions\n      |> Enum.map(fn tx -> Map.update!(tx, :inputs, &render_txoutputs/1) end)\n      |> Enum.map(&skip_hex_encoding/1)\n\n    advice\n    |> Map.put(:transactions, transactions)\n    |> Response.serialize()\n    |> WatcherRPCResponse.add_app_infos()\n  end\n\n  defp render_transaction(transaction) do\n    transaction\n    |> Map.take([:txindex, :txhash, :txtype, :block, :inputs, :outputs, :txbytes, :metadata, :inserted_at, :updated_at])\n    |> Map.update!(:inputs, &render_txoutputs/1)\n    |> Map.update!(:outputs, &render_txoutputs/1)\n  end\n\n  defp render_txoutputs(inputs) do\n    inputs\n    |> Enum.map(&to_utxo/1)\n  end\n\n  defp skip_hex_encoding(%{typed_data: typed_data} = tx) do\n    typed_data_esc =\n      typed_data\n      |> Kernel.put_in([:skip_hex_encode], [:types, :primaryType])\n      |> Kernel.put_in([:domain, :skip_hex_encode], [:name, :version])\n\n    %{tx | typed_data: typed_data_esc}\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/lib/web/views/utxo.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.View.Utxo do\n  @moduledoc \"\"\"\n  The utxo view for rendering json\n  \"\"\"\n\n  use OMG.WatcherRPC.Web, :view\n  alias OMG.Utils.HttpRPC.Response\n  alias OMG.WatcherRPC.Web.Response, as: WatcherRPCResponse\n\n  def render(\"utxo_exit.json\", %{response: utxo_exit}) do\n    utxo_exit\n    |> Response.serialize()\n    |> WatcherRPCResponse.add_app_infos()\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/lib/web.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web do\n  @moduledoc \"\"\"\n  The entrypoint for defining your web interface, such\n  as controllers, views, channels and so on.\n\n  This can be used in your application as:\n\n      use OMG.WatcherRPC.Web, :controller\n      use OMG.WatcherRPC.Web, :view\n\n  The definitions below will be executed for every view,\n  controller, etc, so keep them short and clean, focused\n  on imports, uses and aliases.\n\n  Do NOT define functions inside the quoted expressions\n  below. Instead, define any helper function in modules\n  and import those modules here.\n  \"\"\"\n\n  def controller() do\n    quote do\n      use Phoenix.Controller, namespace: OMG.WatcherRPC.Web, log: :debug\n      import Plug.Conn\n      import OMG.WatcherRPC.Web.Router.Helpers\n      import OMG.Utils.HttpRPC.Validator.Base\n\n      action_fallback(OMG.WatcherRPC.Web.Controller.Fallback)\n\n      @doc \"\"\"\n      Passes result to the render process when successful or returns error result unchanged.\n      Error tuple will be passed to the see: `OMG.WatcherRPC.Web.Controller.Fallback`\n      \"\"\"\n      def api_response(api_result, conn, template) when is_tuple(api_result),\n        do: with({:ok, data} <- api_result, do: api_response(data, conn, template))\n\n      @doc \"\"\"\n      Takes advantage of preset api response structure and module names conventions to discover parameters\n      to Phoenix Controller's [render/3](https://hexdocs.pm/phoenix/Phoenix.Controller.html#render/3)\n      \"\"\"\n      def api_response(data, conn, template) do\n        view_module =\n          conn\n          |> controller_module()\n          |> Atom.to_string()\n          |> String.replace(\"Controller\", \"View\")\n          |> String.to_existing_atom()\n\n        conn\n        |> put_view(view_module)\n        |> render(template, response: data)\n      end\n    end\n  end\n\n  def view() do\n    quote do\n      use Phoenix.View,\n        root: \"lib/omg_watcher_rpc_web/templates\",\n        namespace: OMG.WatcherRPC.Web\n\n      # Import convenience functions from controllers\n      import Phoenix.Controller, only: [get_flash: 2, view_module: 1]\n\n      import OMG.WatcherRPC.Web.Router.Helpers\n      import OMG.WatcherRPC.Web.Serializer.Base\n    end\n  end\n\n  def router() do\n    quote do\n      use Phoenix.Router\n      import Plug.Conn\n      import Phoenix.Controller\n    end\n  end\n\n  def channel() do\n    quote do\n      use Phoenix.Channel\n    end\n  end\n\n  @doc \"\"\"\n  When used, dispatch to the appropriate controller/view/etc.\n  \"\"\"\n  defmacro __using__(which) when is_atom(which) do\n    apply(__MODULE__, which, [])\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/mix.exs",
    "content": "defmodule OMG.WatcherRPC.Mixfile do\n  use Mix.Project\n\n  def project() do\n    [\n      app: :omg_watcher_rpc,\n      version: version(),\n      build_path: \"../../_build\",\n      config_path: \"../../config/config.exs\",\n      deps_path: \"../../deps\",\n      lockfile: \"../../mix.lock\",\n      elixir: \"~> 1.8\",\n      elixirc_paths: elixirc_paths(Mix.env()),\n      compilers: [:phoenix] ++ Mix.compilers(),\n      start_permanent: Mix.env() == :prod,\n      deps: deps(),\n      test_coverage: [tool: ExCoveralls]\n    ]\n  end\n\n  def application() do\n    [\n      mod: {OMG.WatcherRPC.Application, []},\n      extra_applications: [:logger, :runtime_tools, :telemetry, :omg_watcher]\n    ]\n  end\n\n  defp version() do\n    \"git\"\n    |> System.cmd([\"describe\", \"--tags\", \"--abbrev=0\"])\n    |> elem(0)\n    |> String.replace(\"v\", \"\")\n    |> String.replace(\"\\n\", \"\")\n  end\n\n  # Specifies which paths to compile per environment.\n  defp elixirc_paths(:prod), do: [\"lib\"]\n  defp elixirc_paths(:dev), do: [\"lib\"]\n  defp elixirc_paths(:test), do: [\"lib\", \"test/support\"]\n\n  defp deps() do\n    [\n      {:phoenix, \"~> 1.5\"},\n      {:poison, \"~> 4.0\"},\n      {:plug_cowboy, \"~> 2.3\"},\n      {:cors_plug, \"~> 2.0\"},\n      {:spandex_phoenix, \"~> 1.0\"},\n      {:spandex_datadog, \"~> 1.0\"},\n      {:telemetry, \"~> 0.4.1\"},\n      # UMBRELLA\n      {:omg_bus, in_umbrella: true},\n      {:omg_utils, in_umbrella: true},\n      {:omg_watcher, in_umbrella: true},\n      # UMBRELLA but test only\n      {:omg_watcher_info, in_umbrella: true, only: [:test]},\n      # TEST ONLY\n      {:ex_machina, \"~> 2.3\", only: [:test], runtime: false}\n    ]\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/info_api_specs/account/paths.yaml",
    "content": "account.get_balance:\n  post:\n    tags:\n      - Account\n    summary: Returns the balance of each currency for the given account address.\n    operationId: account_get_balance\n    requestBody:\n      $ref: 'request_bodies.yaml#/AddressBodySchema'\n    responses:\n      200:\n        $ref: 'responses.yaml#/AccountBalanceResponse'\n      500:\n        $ref: '../responses.yaml#/InternalServerError'\n\naccount.get_utxos:\n  post:\n    tags:\n      - Account\n    summary: Gets all utxos belonging to the given address.\n    operationId: account_get_utxos\n    requestBody:\n      $ref: 'request_bodies.yaml#/AddressBodySchema'\n    responses:\n      200:\n        $ref: 'responses.yaml#/AccountUtxoResponse'\n      500:\n        $ref: '../responses.yaml#/InternalServerError'\n\n# This is served by transaction's controller, referencing there for definitions\naccount.get_transactions:\n  post:\n    tags:\n      - Account\n    summary: Gets a list of transactions for given account address.\n    operationId: account_get_transactions\n    requestBody:\n      $ref: '../transaction/request_bodies.yaml#/GetAllTransactionsBodySchema'\n    responses:\n      200:\n        $ref: '../transaction/responses.yaml#/GetAllTransactionsResponse'\n      500:\n        $ref: '../responses.yaml#/InternalServerError'\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/info_api_specs/account/request_bodies.yaml",
    "content": "AddressBodySchema:\n  description: HEX-encoded address of the account and pagination fields\n  required: true\n  content:\n    application/json:\n      schema:\n        title: 'AddressBodySchema'\n        type: object\n        properties:\n          address:\n            type: string\n          page:\n            type: integer\n            format: int32\n            default: 1            \n          limit:\n            type: integer\n            format: int32\n            default: 200\n             \n        required:\n          - address\n        example:\n          address: '0xb3256026863eb6ae5b06fa396ab09069784ea8ea'\n          limit: 100\n          page: 2\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/info_api_specs/account/response_schemas.yaml",
    "content": "AccountBalanceResponseSchema:\n  allOf:\n  - $ref: '../response_schemas.yaml#/WatcherInfoBaseResponseSchema'\n  - type: object\n    properties:\n      data:\n        type: array\n        items:\n          $ref: 'schemas.yaml#/AccountBalanceSchema'\n    example:\n      data:\n        -\n          currency: '0xbfdf85743ef16cfb1f8d4dd1dfc74c51dc496434'\n          amount: 20\n        -\n          currency: '0x0000000000000000000000000000000000000000'\n          amount: 1000000000\n\nAccountUtxoResponseSchema:\n  allOf:\n  - $ref: '../response_schemas.yaml#/WatcherInfoBaseResponseSchema'\n  - type: object\n    properties:\n      data:\n        type: array\n        items:\n          $ref: 'schemas.yaml#/AccountUtxoSchema'\n      data_paging:\n        type: object\n        properties:\n          page:\n            type: integer\n            format: int32\n            default: 1\n          limit:\n            type: integer\n            format: int32\n            default: 200\n    example:\n      data:\n      -\n        amount: 10\n        blknum: 123000\n        creating_txhash: '0x2c499b95ccb6bf7b923049b32b03a613d30882a448102136e544b302119eb722'\n        currency: '0x0000000000000000000000000000000000000000'\n        inserted_at: '2020-02-10T12:07:32Z'\n        oindex: 0\n        otype: 1\n        owner: '0xb3256026863eb6ae5b06fa396ab09069784ea8ea'\n        spending_txhash: null\n        txindex: 111\n        updated_at: '2020-02-15T04:07:57Z'\n        utxo_pos: 123000001110000\n      data_paging:\n        page: 1\n        limit: 200"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/info_api_specs/account/responses.yaml",
    "content": "AccountBalanceResponse:\n  description: Account balance successful response\n  content:\n    application/json:\n      schema:\n        $ref: 'response_schemas.yaml#/AccountBalanceResponseSchema'\nAccountUtxoResponse:\n  description: Account utxos succcessful response\n  content:\n    application/json:\n      schema:\n        $ref: 'response_schemas.yaml#/AccountUtxoResponseSchema'\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/info_api_specs/account/schemas.yaml",
    "content": "AccountBalanceSchema:\n  type: object\n  properties:\n    currency:\n      type: string\n\n    amount:\n      type: integer\n      format: int256\n\nAccountUtxoSchema:\n  type: object\n  properties:\n    blknum:\n      type: integer\n      format: int64\n    txindex:\n      type: integer\n      format: int16\n    otype:\n      type: integer\n      format: int16\n    oindex:\n      type: integer\n      format: int8\n    utxo_pos:\n      type: integer\n      format: int256\n    creating_txhash:\n      type: string\n    spending_txhash:\n      type: string\n    owner:\n      type: string\n    currency:\n      type: string\n    amount:\n      type: integer\n      format: int256\n    inserted_at:\n      type: string\n    updated_at:\n      type: string\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/info_api_specs/alarm/alarms_schema.yml",
    "content": "components:\n  schemas:\n    ethereum_connection_error:\n      type: object\n      properties:\n        ethereum_connection_error:\n          type: object\n          properties:\n            node:\n              type: string\n            reporter:\n              type: string\n    ethereum_stalled_sync:\n      type: object\n      properties:\n        ethereum_stalled_sync:\n          type: object\n          properties:\n            ethereum_height:\n              type: integer\n              minimum: 0\n            synced_at:\n              type: string\n              format: date-time\n    invalid_fee_source:\n      type: object\n      properties:\n        invalid_fee_source:\n          type: object\n          properties:\n            node:\n              type: string\n            reporter:\n              type: string\n    statsd_client_connection:\n      type: object\n      properties:\n        statsd_client_connection:\n          type: object\n          properties:\n            node:\n              type: string\n            reporter:\n              type: string\n    system_memory_high_watermark:\n      type: object\n      properties:\n        statsd_client_connection:\n          type: array\n          items:\n            type: string\n          default: []\n    disk_almost_full:\n      type: object\n      properties:\n        disk_almost_full:\n          type: string\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/info_api_specs/alarm/paths.yaml",
    "content": "alarm.get:\n  get:\n    tags:\n      - Alarm\n    summary: Provides alarms related to system memory, cpu and storage and application specific alarms.\n    description: >\n      **Note:** Service operator alarms.\n    operationId: alarm_get\n    responses:\n      200:\n        $ref: 'responses.yaml#/AlarmResponse'\n      500:\n        $ref: '../responses.yaml#/InternalServerError'\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/info_api_specs/alarm/response_schemas.yaml",
    "content": "AlarmResponseSchema:\n  allOf:\n  - $ref: '../response_schemas.yaml#/WatcherInfoBaseResponseSchema'\n  - type: object\n    properties:\n      data:\n        type: array\n        items:\n          $ref: 'schemas.yaml#/AlarmSchema'\n    example:\n      data:\n      -\n        disk_almost_full: \"/dev/null\"\n        ethereum_connection_error: {}\n        ethereum_stalled_sync: {}\n        system_memory_high_watermark: []\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/info_api_specs/alarm/responses.yaml",
    "content": "AlarmResponse:\n  description: System alarms\n  content:\n    application/json:\n      schema:\n        $ref: 'response_schemas.yaml#/AlarmResponseSchema'"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/info_api_specs/alarm/schemas.yaml",
    "content": "AlarmSchema:\n  type: array\n  items:\n    anyOf:\n      - $ref: 'alarms_schema.yml#/components/schemas/ethereum_connection_error'\n      - $ref: 'alarms_schema.yml#/components/schemas/ethereum_stalled_sync'\n      - $ref: 'alarms_schema.yml#/components/schemas/invalid_fee_source'\n      - $ref: 'alarms_schema.yml#/components/schemas/statsd_client_connection'\n      - $ref: 'alarms_schema.yml#/components/schemas/system_memory_high_watermark'\n      - $ref: 'alarms_schema.yml#/components/schemas/disk_almost_full'\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/info_api_specs/batch_transaction/paths.yaml",
    "content": "transaction.batch_submit:\n  post:\n    tags:\n      - Transaction\n    summary: This endpoint submits an array of signed transaction to the child chain.\n    description: >\n      Normally you should call the Watcher's Transaction - Submit instead of this.\n      The Watcher's version performs various security and validation checks (TO DO) before submitting the transaction,\n      so is much safer. However, if the Watcher is not available this version exists.\n    operationId: batch_submit\n    requestBody:\n      $ref: 'request_bodies.yaml#/TransactionBatchSubmitBodySchema'\n    responses:\n      200:\n        $ref: 'responses.yaml#/TransactionBatchSubmitResponse'\n      500:\n        $ref: '../responses.yaml#/InternalServerError'\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/info_api_specs/batch_transaction/request_bodies.yaml",
    "content": "TransactionBatchSubmitBodySchema:\n  description: Array of signed transactions, RLP-encoded to bytes, and HEX-encoded to string\n  required: true\n  content:\n    application/json:\n      schema:\n        title: 'TransactionBatchSubmitBodySchema'\n        type: object\n        properties:\n          transactions:\n            type: array\n            items:\n              type: string\n        required:\n          - transactions\n        example:\n          transactions: ['0xf8d083015ba98080808080940000...', '0xf8d083a15ba98080808080920000...']\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/info_api_specs/batch_transaction/response_schemas.yaml",
    "content": "TransactionBatchSubmitResponseSchema:\n  allOf:\n  - $ref: '../response_schemas.yaml#/WatcherBaseResponseSchema'\n  - type: object\n    properties:\n      data:\n        type: array\n        $ref: 'schemas.yaml#/TransactionBatchSubmitSchema '\n    example:\n      data:\n      -\n        blknum: 123000\n        txindex: 111\n        txhash: '0xbdf562c24ace032176e27621073df58ce1c6f65de3b5932343b70ba03c72132d'\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/info_api_specs/batch_transaction/responses.yaml",
    "content": "TransactionBatchSubmitResponse:\n  description: Transaction batch submission successful response\n  content:\n    application/json:\n      schema:\n        $ref: 'response_schemas.yaml#/TransactionBatchSubmitResponseSchema'\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/info_api_specs/batch_transaction/schemas.yaml",
    "content": "TransactionBatchSubmitSchema:\n  type: array\n  items:\n    type: object\n    properties:\n      blknum:\n        type: integer\n        format: int64\n      txindex:\n        type: integer\n        format: int16\n      txhash:\n        type: string\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/info_api_specs/block/paths.yaml",
    "content": "block.all:\n  post:\n    tags:\n      - Block\n    summary: Gets all blocks (can be limited with various filters).\n    description: >\n      Returns a list of blocks ordered by the highest block number first.\n      Intended to be used for presenting an overview of most recent blocks.\n\n      Note: Due to eventual consistency nature of the informational API,\n      blocks may later than deposits, exits, etc.\n    operationId: block_all\n    requestBody:\n      $ref: 'request_bodies.yaml#/AllBlocksBodySchema'\n    responses:\n      200:\n        $ref: 'responses.yaml#/BlocksAllResponse'\n      500:\n        $ref: '../responses.yaml#/InternalServerError'\nblock.get:\n  post:\n    tags:\n      - Block\n    summary: Retrieves a single block for the given block number.\n    description: >\n      Intended for operations requiring info for a specific block only.\n\n      Returns a single block object for the given block number. The returned object includes transaction count but not associated transactions - for which you can use transaction.all with blknum in the request body.\n    operationId: block_get\n    requestBody:\n      $ref: 'request_bodies.yaml#/GetBlockBodySchema'\n    responses:\n      200:\n        $ref: 'responses.yaml#/BlockResponse'\n      500:\n        $ref: '../responses.yaml#/InternalServerError'\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/info_api_specs/block/request_bodies.yaml",
    "content": "AllBlocksBodySchema:\n  description: The supported request parameters for /block.all\n  content:\n    application/json:\n      schema:\n        title: 'AllBlocksBodySchema'\n        type: object\n        properties:\n          page:\n            type: integer\n            format: int32\n            default: 1\n          limit:\n            type: integer\n            format: int32\n            default: 100\n        example:\n          page: 2\n          limit: 100\n\nGetBlockBodySchema:\n  description: Block number\n  required: true\n  content:\n    application/json:\n      schema:\n        title: 'GetBlockBodySchema'\n        type: object\n        properties:\n          blknum:\n            type: integer\n            format: int64\n        example:\n          blknum: 68290000\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/info_api_specs/block/response_schemas.yaml",
    "content": "BlocksAllResponseSchema:\n  allOf:\n  - $ref: '../response_schemas.yaml#/WatcherInfoBaseListResponseSchema'\n  - type: object\n    properties:\n      data:\n        type: array\n        items:\n          $ref: 'schemas.yaml#/BlockSchema'\n      data_paging:\n        type: object\n        properties:\n          page:\n            type: integer\n            format: int32\n            default: 1\n          limit:\n            type: integer\n            format: int32\n            default: 100\n    example:\n      data:\n      -\n        blknum: 68290000\n        hash: '0x0017372421f9a92bedb7163310918e623557ab5310befc14e67212b660c33bec'\n        eth_height: 97424\n        timestamp: 1540365586\n        tx_count: 2\n        inserted_at: '2020-02-10T12:07:32Z'\n        updated_at: '2020-02-15T04:07:57Z'      \n      data_paging:\n        page: 1\n        limit: 100\nBlockResponseSchema:\n  allOf:\n  - $ref: '../response_schemas.yaml#/WatcherInfoBaseResponseSchema'\n  - type: object\n    properties:\n      data:\n        type: object\n        $ref: 'schemas.yaml#/BlockSchema'\n    example:\n      data:\n        timestamp: 1540365586\n        hash: '0x0017372421f9a92bedb7163310918e623557ab5310befc14e67212b660c33bec'\n        eth_height: 97424\n        blknum: 68290000\n        tx_count: 2\n        inserted_at: '2020-02-10T12:07:32Z'\n        updated_at: '2020-02-15T04:07:57Z' \n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/info_api_specs/block/responses.yaml",
    "content": "BlocksAllResponse:\n  description: Blocks succcessful response\n  content:\n    application/json:\n      schema:\n        $ref: 'response_schemas.yaml#/BlocksAllResponseSchema'\nBlockResponse:\n  description: Block succcessful response\n  content:\n    application/json:\n      schema:\n        $ref: 'response_schemas.yaml#/BlockResponseSchema'"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/info_api_specs/block/schemas.yaml",
    "content": "BlockSchema:\n  type: object\n  properties:\n    blknum:\n      type: integer\n      format: int64\n    hash:\n      type: string\n       \n    eth_height:\n      type: integer\n      format: int64\n    timestamp:\n      type: integer\n      format: int64\n    tx_count:\n      type: integer\n      format: int64\n\n    inserted_at:\n      type: string\n    updated_at:\n      type: string\n  "
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/info_api_specs/configuration/configuration_schema.yml",
    "content": "components:\n  schemas:\n    deposit_finality_margin:\n      type: integer\n      format: int256\n    contract_semver:\n      type: string\n    network:\n      type: string\n    exit_processor_sla_margin:\n      type: integer\n      format: int256"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/info_api_specs/configuration/paths.yaml",
    "content": "configuration.get:\n  get:\n    tags:\n      - Configuration\n    summary: Provides configuration values\n    description: >\n      **Note:** Configuration values.\n    operationId: configuration_get\n    responses:\n      200:\n        $ref: 'responses.yaml#/ConfigurationResponse'\n      500:\n        $ref: '../responses.yaml#/InternalServerError'\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/info_api_specs/configuration/response_schemas.yaml",
    "content": "ConfigurationResponseSchema:\n  allOf:\n  - $ref: '../response_schemas.yaml#/WatcherInfoBaseResponseSchema'\n  - type: object\n    properties:\n      data:\n        type: array\n        items:\n          $ref: 'schemas.yaml#/ConfigurationSchema'\n    example:\n      data:\n      -\n        deposit_finality_margin: 10\n        contract_semver: \"1.0.0.1+a1s29s8\"\n        exit_processor_sla_margin: 5520\n        network: \"RINKEBY\"\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/info_api_specs/configuration/responses.yaml",
    "content": "ConfigurationResponse:\n  description: Configuration response\n  content:\n    application/json:\n      schema:\n        $ref: 'response_schemas.yaml#/ConfigurationResponseSchema'"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/info_api_specs/configuration/schemas.yaml",
    "content": "ConfigurationSchema:\n  type: object\n  properties:\n    deposit_finality_margin:\n      type: integer\n      format: int256\n    contract_semver:\n      type: string\n       \n    exit_processor_sla_margin:\n      type: integer\n      format: int256\n    network:\n      type: string\n       "
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/info_api_specs/deposit/paths.yaml",
    "content": "deposit.all:\n  post:\n    tags:\n      - Deposit\n    summary: Gets a paginated list of deposit for the given address.\n    description: >\n      Returns a list of deposits ordered by Ethereum height in descending order for the given address.\n\n\n    operationId: deposit_all\n    requestBody:\n      $ref: 'request_bodies.yaml#/AllDepositsBodySchema'\n    responses:\n      200:\n        $ref: 'responses.yaml#/DepositsAllResponse'\n      500:\n        $ref: '../responses.yaml#/InternalServerError'\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/info_api_specs/deposit/request_bodies.yaml",
    "content": "AllDepositsBodySchema:\n  description: The supported request parameters for /deposit.all. The \"limit\" and \"page\" parameters are optional.\n  required: true\n  content:\n    application/json:\n      schema:\n        title: 'AllDepositsBodySchema'\n        type: object\n        properties:\n          address: \n            type: string\n          page:\n            type: integer\n            format: int32\n            default: 1\n          limit:\n            type: integer\n            format: int32\n            default: 100\n\n\n        example:\n          page: 2\n          limit: 100\n          address: \"0xb01cb6f56d798a62d1e0bace406c73a122c39c9d\""
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/info_api_specs/deposit/response_schemas.yaml",
    "content": "DepositsAllResponseSchema:\n  allOf:\n  - $ref: '../response_schemas.yaml#/WatcherInfoBaseListResponseSchema'\n  - type: object\n    properties:\n      data:\n        type: array\n        items:\n          $ref: 'schemas.yaml#/DepositSchema'\n      data_paging:\n        type: object\n        properties:\n          page:\n            type: integer\n            format: int32\n            default: 1\n          limit:\n            type: integer\n            format: int32\n            default: 100\n    example:\n      data:\n      -\n        event_type: \"deposit\"\n        inserted_at: \"2020-05-15T12:37:40Z\"\n        eth_height: 168637\n        log_index: 0\n        root_chain_txhash: \"0x63c056f122f5bf30bf8119ec0a2184b73f975951229995a427ea58d904eaab85\"\n        txoutputs: \n          -\n            blknum: 1\n            txindex: 0\n            otype: 1\n            oindex: 0\n            utxo_pos: 1000000000\n            owner: '0xb3256026863eb6ae5b06fa396ab09069784ea8ea'\n            currency: '0x0000000000000000000000000000000000000000'\n            creating_txhash: null\n            spending_txhash: '0x5df13a6bf96dbcf6e66d8babd6b55bd40d64d4320c3b115364c6588fc18c2a21'\n            amount: 20000000\n            inserted_at: '2020-02-10T12:07:32Z'\n            updated_at: '2020-02-10T12:07:32Z'\n      data_paging:\n        page: 1\n        limit: 100\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/info_api_specs/deposit/responses.yaml",
    "content": "DepositsAllResponse:\n  description: /deposit.all succcessful response\n  content:\n    application/json:\n      schema:\n        $ref: 'response_schemas.yaml#/DepositsAllResponseSchema'\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/info_api_specs/deposit/schemas.yaml",
    "content": "DepositSchema:\n  type: object\n  properties:\n    event_type:\n      type: string\n    root_chain_txhash:\n      type: string \n    log_index: \n      type: integer\n    eth_height:\n      type: integer\n      format: int64\n    inserted_at:\n      type: string\n    updated_at:\n      type: string\n    txoutputs: \n      type: array\n      items:\n        $ref: '#/TransactionOutputSchema'\n\nTransactionOutputSchema:\n  type: object\n  properties:\n    blknum:\n      type: integer\n      format: int64\n    txindex:\n      type: integer\n      format: int16\n    oindex:\n      type: integer\n      format: int8\n    otype:\n      type: integer\n      format: int8\n    utxo_pos:\n      type: integer\n      format: int256\n    owner:\n      type: string \n    currency:\n      type: string    \n    amount:\n      type: integer\n      format: int256\n    creating_txhash:\n      type: string\n    spending_txhash:\n      type: string\n    inserted_at:\n      type: string\n    updated_at:\n      type: string"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/info_api_specs/fees/paths.yaml",
    "content": "fees.all:\n  post:\n    tags:\n      - Fees\n    summary: This endpoint retrieves the list of fee tokens currently supported by the childchain and the current amount needed to perform a transaction.\n    operationId: fees_all\n    requestBody:\n      $ref: 'request_bodies.yaml#/FeesAllBodySchema'\n    responses:\n      200:\n        $ref: 'responses.yaml#/AllFeesResponse'\n      500:\n        $ref: '../responses.yaml#/InternalServerError'\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/info_api_specs/fees/request_bodies.yaml",
    "content": "FeesAllBodySchema:\n  description: An optional array of currencies to filter, raises an error if one of the currencies is not supported.\n  required: false\n  content:\n    application/json:\n      schema:\n        title: 'FeesAllBodySchema'\n        type: object\n        properties:\n          currencies:\n            type: array\n            items: \n              type: string\n               \n          tx_types:\n            type: array\n            items: \n              type: integer\n        example:\n          currencies: ['0x0000000000000000000000000000000000000000']\n          tx_types: [1]\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/info_api_specs/fees/response_schemas.yaml",
    "content": "AllFeesResponseSchema:\n  allOf:\n  - $ref: '../response_schemas.yaml#/WatcherInfoBaseResponseSchema'\n  - type: object\n    properties:\n      data:\n        type: object\n        $ref: 'schemas.yaml#/FeeAllSchema'\n    example:\n      data:\n        '1':\n          -\n            currency: '0x0000000000000000000000000000000000000000'\n            amount: 220000000000000\n            subunit_to_unit: 1000000000000000000\n            pegged_currency: 'USD'\n            pegged_amount: 4\n            pegged_subunit_to_unit: 100\n            updated_at: '2019-01-01T10:10:10+00:00'\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/info_api_specs/fees/responses.yaml",
    "content": "AllFeesResponse:\n  description: List of all supported fees response\n  content:\n    application/json:\n      schema:\n        $ref: 'response_schemas.yaml#/AllFeesResponseSchema'\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/info_api_specs/fees/schemas.yaml",
    "content": "FeeAllSchema:\n  type: object\n  additionalProperties:\n    type: array\n    items:\n      $ref: '#/FeeItemSchema'\n\nFeeItemSchema:\n  type: object\n  properties:\n    currency:\n      type: string\n       \n    amount:\n      type: integer\n      format: int256\n    subunit_to_unit:\n      type: integer\n      format: int256\n    pegged_currency:\n      type: string\n       \n    pegged_amount:\n      type: integer\n      format: int256\n    pegged_subunit_to_unit:\n      type: integer\n      format: int256\n    updated_at:\n      type: string\n      format: date-time"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/info_api_specs/response_schemas.yaml",
    "content": "WatcherInfoBaseResponseSchema:\n  description: The response schema for a successful operation\n  type: object\n  properties:\n    version:\n      type: string\n    success:\n      type: boolean\n    data:\n      type: object\n    service_name:\n      type: string\n  required:\n    - service_name\n    - version\n    - success\n    - data\n  example:\n    service_name: watcher_info\n    version: '1.0.0+abcdefa'\n    success: true\n    data: {}\n\nWatcherInfoBaseListResponseSchema:\n  description: The response schema for a successful list operation\n  type: object\n  properties:\n    version:\n      type: string\n    success:\n      type: boolean\n    data:\n      type: array\n      items:\n        type: object\n    service_name:\n      type: string\n  required:\n    - service_name\n    - version\n    - success\n    - data\n  example:\n    service_name: watcher_info\n    version: '1.0+abcdefa'\n    success: true\n    data: []\n\nWatcherInfoErrorResponseSchema:\n  description: The response schema for an error\n  allOf:\n    - $ref: 'response_schemas.yaml#/WatcherInfoBaseResponseSchema'\n    - type: object\n      properties:\n        data:\n          $ref: '../shared/schemas.yaml#/ErrorSchema'\n      required:\n        - data\n      example:\n        success: false\n        data:\n          object: error\n          code: server:internal_server_error\n          description: Something went wrong on the server\n          messages: {error_key: error_reason}\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/info_api_specs/responses.yaml",
    "content": "InternalServerError:\n  description: Returns an internal server error\n  content:\n    application/json:\n      schema:\n        $ref: 'response_schemas.yaml#/WatcherInfoErrorResponseSchema'\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/info_api_specs/stats/paths.yaml",
    "content": "stats.get:\n    post:\n      tags:\n        - Stats\n      summary: Retrieves network statistics\n      description: >\n        Retrieves transaction count, block count and average block interval, both for all time and the last 24 hours.\n  \n      operationId: stats_get\n      responses:\n        200:\n          $ref: 'responses.yaml#/StatsGetResponse'\n        500:\n          $ref: '../responses.yaml#/InternalServerError'\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/info_api_specs/stats/response_schemas.yaml",
    "content": "StatsGetResponseSchema:\n    allOf:\n    - $ref: '../response_schemas.yaml#/WatcherInfoBaseListResponseSchema'\n    - type: object\n      properties:\n        data:\n          type: object\n          items:\n            $ref: 'schemas.yaml#/StatsSchema'\n      example:\n        data:\n          transaction_count:\n            all_time: 4\n            last_24_hours: 2\n          block_count:\n            all_time: 2\n            last_24_hours: 1\n          average_block_interval:\n            all_time: 100\n            last_24_hours: null"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/info_api_specs/stats/responses.yaml",
    "content": "StatsGetResponse:\n    description: Stats Successful Response\n    content:\n      application/json:\n        schema:\n          $ref: 'response_schemas.yaml#/StatsGetResponseSchema'\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/info_api_specs/stats/schemas.yaml",
    "content": "StatsSchema:\n    type: object\n    properties:\n        transaction_count:\n            type: object\n            properties: \n                all_time:\n                    type: integer\n                    format: int64\n                last_24_hours:\n                    type: integer\n                    format: int64\n        block_count:\n            type: object\n            properties: \n                all_time:\n                    type: integer\n                    format: int64\n                last_24_hours:\n                    type: integer\n                    format: int64\n        average_block_interval:\n            type: object\n            properties: \n                all_time:\n                    type: number\n                    format: int64\n                last_24_hours:\n                    type: number\n                    format: int64        \n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/info_api_specs/swagger.yaml",
    "content": "openapi: 3.0.0\ninfo:\n  version: '1.0.0'\n  title: Watcher Info API\n  description: >\n    API specification of the Watcher's Informational Service\n\n    Error codes are available in [html](https://github.com/omgnetwork/elixir-omg/blob/master/docs/api_specs/errors.md#error-codes-description) format.\n  contact:\n    name: OMG Network\n    email: engineering@omg.network\n  license:\n    name: 'Apache 2.0: https://www.apache.org/licenses/LICENSE-2.0'\n    url: 'https://omg.network/'\n\nservers:\n  - url: https://watcher-info.ropsten.v1.omg.network/\n  - url: http://localhost:7534/\n\ntags:\n  - name: Account\n    description: Account related API.\n  - name: Block\n    description: Block related API.\n  - name: Deposit\n    description: Deposit related API.\n  - name: Transaction\n    description: Transaction related API.\n  - name: Fees\n    description: Fees related API.\n  - name: Stats\n    description: Stats related API.\n  - name: Configuration\n    description: Configuration related API.\n\npaths:\n  /alarm.get:\n    $ref: 'alarm/paths.yaml#/alarm.get'\n  /configuration.get:\n    $ref: 'configuration/paths.yaml#/configuration.get'\n  /account.get_balance:\n    $ref: 'account/paths.yaml#/account.get_balance'\n  /account.get_utxos:\n    $ref: 'account/paths.yaml#/account.get_utxos'\n  /account.get_transactions:\n    $ref: 'account/paths.yaml#/account.get_transactions'\n  /block.get:\n    $ref: 'block/paths.yaml#/block.get'\n  /block.all:\n    $ref: 'block/paths.yaml#/block.all'\n  /deposit.all:\n    $ref: 'deposit/paths.yaml#/deposit.all'\n  /transaction.all:\n    $ref: 'transaction/paths.yaml#/transaction.all'\n  /transaction.create:\n    $ref: 'transaction/paths.yaml#/transaction.create'\n  /transaction.merge:\n    $ref: 'transaction/paths.yaml#/transaction.merge'\n  /transaction.get:\n    $ref: 'transaction/paths.yaml#/transaction.get'\n  /transaction.submit_typed:\n    $ref: 'transaction/paths.yaml#/transaction.submit_typed'\n  /fees.all:\n    $ref: 'fees/paths.yaml#/fees.all'\n  /stats.get: \n    $ref: 'stats/paths.yaml#/stats.get'\n\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/info_api_specs/transaction/paths.yaml",
    "content": "transaction.all:\n  post:\n    tags:\n      - Transaction\n    summary: Gets all transactions (can be limited with various filters).\n    description: >\n      Digests the details of the transaction, by listing the value of outputs, aggregated by currency.\n      Intended to be used when presenting the little details about multiple transactions.\n      For all details queries to `/transaction.get` should be made using the transaction's hash provided.\n    operationId: transactions_all\n    requestBody:\n      $ref: 'request_bodies.yaml#/GetAllTransactionsBodySchema'\n    responses:\n      200:\n        $ref: 'responses.yaml#/GetAllTransactionsResponse'\n      500:\n        $ref: '../responses.yaml#/InternalServerError'\n\ntransaction.create:\n    post:\n      tags:\n        - Transaction\n      summary: Finds an optimal way to construct a transaction spending particular amount.\n      description: >\n        Given token, amount and spender, finds spender's inputs sufficient to perform a payment.\n        If also provided with receiver's address, creates and encodes a transaction.\n      operationId: createTransaction\n      requestBody:\n        $ref: 'request_bodies.yaml#/CreateTransactionsBodySchema'\n      responses:\n        200:\n          $ref: 'responses.yaml#/CreateTransactionResponse'\n        500:\n          $ref: '../responses.yaml#/InternalServerError'\n\ntransaction.merge:\n    post:\n      tags:\n        - Transaction\n      summary: Constructs merge transactions.\n      description: |\n        - Given address and currency parameters, returns a list of merge transactions for correspondng \n        UTXOs – grouped in ascending order of value. \n        - Given between two and four UTXO positions, returns a merge transaction for the corresponding\n        UTXOs. (These UTXOs must have the same owner and currency)\n      operationId: mergeTransaction\n      requestBody:\n        $ref: 'request_bodies.yaml#/MergeTransactionsBodySchema'\n      responses:\n        200:\n          $ref: 'responses.yaml#/MergeTransactionResponse'\n        500:\n          $ref: '../responses.yaml#/InternalServerError'\n\ntransaction.get:\n  post:\n    tags:\n      - Transaction\n    summary: Gets a transaction with the given id.\n    operationId: transaction_get\n    requestBody:\n      $ref: 'request_bodies.yaml#/GetTransactionBodySchema'\n    responses:\n      200:\n        $ref: 'responses.yaml#/GetTransactionResponse'\n      500:\n        $ref: '../responses.yaml#/InternalServerError'\n\ntransaction.submit_typed:\n  post:\n    tags:\n      - Transaction\n    summary: Sends EIP-712 formatted transaction to Child chain.\n    description: >\n        Request to this method is the same as to Web3 `eth_signTypedData` with additional `signatures` array.\n        The `/transaction.create` `typed_data` field can be used to prepare transaction. The same conditions\n        are met as with security-critical `/transaction.submit`\n    operationId: submit_typed\n    requestBody:\n      $ref: 'request_bodies.yaml#/TransactionSubmitTypedBodySchema'\n    responses:\n      200:\n        $ref: '../../security_critical_api_specs/transaction/responses.yaml#/TransactionSubmitResponse'\n      500:\n        $ref: '../responses.yaml#/InternalServerError'\n\ntransaction.get_by_position:\n  post:\n    tags:\n      - Transaction\n    summary: Gets a transaction with the given position (block number, transaction index).\n    description: __Not implemented yet, proposed in OMG-364__\n    operationId: get_transaction_by_pos\n    requestBody:\n      $ref: 'request_bodies.yaml#/GetTransactionByPosBodySchema'\n    responses:\n      200:\n        $ref: 'responses.yaml#/GetTransactionResponse'\n      500:\n        $ref: '../responses.yaml#/InternalServerError'\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/info_api_specs/transaction/request_bodies.yaml",
    "content": "GetAllTransactionsBodySchema:\n  description: Account address, block number and other criteria\n  required: true\n  content:\n    application/json:\n      schema:\n        title: 'GetAllTransactionsBodySchema'\n        type: object\n        properties:\n          address:\n            type: string\n          blknum:\n            type: integer\n            format: int64\n          txtypes:\n            type: array\n            items: \n              type: integer\n          metadata:\n            type: string    \n          page:\n            type: integer\n            format: int32\n            default: 1            \n          limit:\n            type: integer\n            format: int32\n            default: 200\n          end_datetime:\n            type: integer\n            format: int32\n        required:\n          - limit\n        example:\n          address: '0xb3256026863eb6ae5b06fa396ab09069784ea8ea'\n          metadata: '0x5df13a6bf96dbcf6e66d8babd6b55bd40d64d4320c3b115364c6588fc18c2a21'\n          txtypes: [1]\n          blknum: 68290000\n          limit: 100\n          page: 2\n          end_datetime: 1592476174\n\nCreateTransactionsBodySchema:\n  description: The description of transaction to be crafted.\n  required: true\n  content:\n    application/json:\n      schema:\n        title: 'CreateTransactionsBodySchema'\n        type: object\n        properties:\n          owner:\n            type: string    \n          payments:\n            type: array\n            items: \n              type: object\n              properties:\n                amount:\n                  type: integer\n                  format: int256\n                currency:\n                  type: string\n                owner:\n                  type: string                \n              required:\n                - amount\n                - currency\n          fee:\n            type: object\n            properties:\n              currency:\n                type: string\n                 \n            required:\n              - currency\n          metadata:\n            type: string\n             \n        required:\n          - owner\n          - payments\n          - fee\n        example:\n          owner: '0xb3256026863eb6ae5b06fa396ab09069784ea8ea'\n          payments:\n            -\n              amount: 100\n              currency: '0x0000000000000000000000000000000000000000'\n              owner: '0xae8ae48796090ba693af60b5ea6be3686206523b'\n          fee:\n            currency: '0x0000000000000000000000000000000000000000'\n          metadata: '0x5df13a6bf96dbcf6e66d8babd6b55bd40d64d4320c3b115364c6588fc18c2a21'\n\nMergeTransactionsBodySchema:\n  description: The description of merge transaction to be crafted.\n  content:\n    application/json:\n      schema:\n        title: 'MergeTransactionsBodySchema'\n        type: object\n        properties:\n          address:\n            type: string    \n          currency:\n            type: string\n          utxo_positions:\n            type: array\n            items:\n              type: string           \n      examples:\n        UTXO positions:  \n          value:\n            utxo_positions: [\"811000000000001\", \"811000000000002\"]\n        address and currency:      \n          value:\n            address: '0xb3256026863eb6ae5b06fa396ab09069784ea8ea'\n            currency: '0x0000000000000000000000000000000000000000'\n\nGetTransactionBodySchema:\n  description: Id (hash) of the transaction\n  required: true\n  content:\n    application/json:\n      schema:\n        title: 'GetTransactionBodySchema'\n        type: object\n        properties:\n          id:\n            type: string\n             \n        required:\n          - id\n        example:\n          id: '0x5df13a6bf96dbcf6e66d8babd6b55bd40d64d4320c3b115364c6588fc18c2a21'\n\nTransactionSubmitTypedBodySchema:\n  description: Transaction as for `eth_signTypedData` along with signatures\n  required: true\n  content:\n    application/json:\n      schema:\n        title: 'TransactionSubmitTypedBodySchema'\n        allOf:\n        - $ref: 'schemas.yaml#/Eip712SignRequestSchema'\n        - type: object\n          properties:\n            signatures:\n              type: array\n              items:\n                type: string\n                 \n          required:\n            - domain\n            - message\n            - signatures\n          example:\n            domain:\n              name: 'OMG Network'\n              salt: '0xfad5c7f626d80f9256ef01929f3beb96e058b8b4b0e3fe52d84f054c0e2a7a83'\n              verifyingContract: '0x44de0ec539b8c4a4b530c78620fe8320167f2f74'\n              version: '1'\n            message:\n              input0: \n                blknum: 1\n                txindex: 0\n                oindex: 0\n              input1: \n                blknum: 1000\n                txindex: 1\n                oindex: 1\n              input2: \n                blknum: 0\n                txindex: 0\n                oindex: 0\n              input3: \n                blknum: 0\n                txindex: 0\n                oindex: 0\n              output0:\n                owner: '0x0527a37aa7081efcf405bd7c8fe36b01e91df27d'\n                currency: '0x0000000000000000000000000000000000000000'\n                amount: 100\n              output1:\n                owner: '0x3b9f4c1dd26e0be593373b1d36cee2008cbeb837'\n                currency: '0x0000000000000000000000000000000000000000'\n                amount: 10\n              output2:\n                owner: '0x0000000000000000000000000000000000000000'\n                currency: '0x0000000000000000000000000000000000000000'\n                amount: 0\n              output3:\n                owner: '0x0000000000000000000000000000000000000000'\n                currency: '0x0000000000000000000000000000000000000000'\n                amount: 0\n              metadata: '0x0000000000000000000000000000000000000000000000000000000000000000'\n            signatures: \n              - '0x6bfb9b2dbe32...'\n\nGetTransactionByPosBodySchema:\n  description: Position of the transaction\n  required: true\n  content:\n    application/json:\n      schema:\n        title: 'GetTransactionByPosBodySchema'\n        type: object\n        properties:\n          blknum:\n            type: string\n             \n          txindex:\n            type: integer\n            format: int16\n        required:\n          - blknum\n          - txindex\n        example:\n          blknum: 68290000\n          txindex: 100\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/info_api_specs/transaction/response_schemas.yaml",
    "content": "GetAllTransactionsResponseSchema:\n  allOf:\n  - $ref: '../response_schemas.yaml#/WatcherInfoBaseListResponseSchema'\n  - type: object\n    properties:\n      data:\n        type: array\n        items:\n          $ref: 'schemas.yaml#/TransactionSchema'\n      data_paging:\n        type: object\n        properties:\n          page:\n            type: integer\n            format: int32\n            default: 1\n          limit:\n            type: integer\n            format: int32\n            default: 200\n    example:\n      data:\n      -\n        block:\n          timestamp: 1540365586\n          hash: '0x0017372421f9a92bedb7163310918e623557ab5310befc14e67212b660c33bec'\n          eth_height: 97424\n          blknum: 68290000\n          inserted_at: '2020-02-10T12:07:32Z'\n          updated_at: '2020-02-15T04:07:57Z'\n        txindex: 0\n        txtype: 1\n        txhash: '0x5df13a6bf96dbcf6e66d8babd6b55bd40d64d4320c3b115364c6588fc18c2a21'\n        metadata: '0x00000000000000000000000000000000000000000000000000000048656c6c6f'\n        txbytes: '0x5df13a6bee20000...'\n        inserted_at: '2020-02-10T12:07:32Z'\n        updated_at: '2020-02-15T04:07:57Z'\n        inputs:\n        -\n          blknum: 1000\n          txindex: 111\n          otype: 1\n          oindex: 0\n          utxo_pos: 1000001110000\n          owner: '0xb3256026863eb6ae5b06fa396ab09069784ea8ea'\n          currency: '0x0000000000000000000000000000000000000000'\n          creating_txhash: '0x40d65df1c3b1156d813d6bf96d5bd3b5bcf6e6588fc18c2a2ba564c6a64d4320'\n          spending_txhash: '0x5df13a6bf96dbcf6e66d8babd6b55bd40d64d4320c3b115364c6588fc18c2a21'\n          amount: 20000000\n          inserted_at: '2020-02-10T12:07:32Z'\n          updated_at: '2020-02-15T04:07:57Z'\n        outputs:\n        -\n          blknum: 68290000\n          txindex: 5113\n          otype: 1\n          oindex: 0\n          utxo_pos: 68290000051130000\n          owner: '0xae8ae48796090ba693af60b5ea6be3686206523b'\n          currency: '0x0000000000000000000000000000000000000000'\n          amount: 15000000\n          creating_txhash: '0x5df13a6bf96dbcf6e66d8babd6b55bd40d64d4320c3b115364c6588fc18c2a21'\n          spending_txhash: null\n          inserted_at: '2020-02-10T12:07:32Z'\n          updated_at: '2020-02-15T04:07:57Z'\n        -\n          blknum: 68290000\n          txindex: 5113\n          otype: 1\n          oindex: 1\n          utxo_pos: 68290000051130001\n          owner: '0xb3256026863eb6ae5b06fa396ab09069784ea8ea'\n          currency: '0x0000000000000000000000000000000000000000'\n          amount: 5000000\n          creating_txhash: '0x5df13a6bf96dbcf6e66d8babd6b55bd40d64d4320c3b115364c6588fc18c2a21'\n          spending_txhash: null\n          inserted_at: '2020-02-10T12:07:32Z'\n          updated_at: '2020-02-15T04:07:57Z'\n      data_paging:\n        page: 1\n        limit: 200\n\nCreateTransactionResponseSchema:\n  allOf:\n  - $ref: '../response_schemas.yaml#/WatcherInfoBaseResponseSchema'\n  - type: object\n    properties:\n      data:\n        type: object\n        $ref: 'schemas.yaml#/CreateTransactionSchema'\n    example:\n      data:\n        result: 'complete'\n        transactions:\n          -\n            inputs:\n              -\n                blknum: 123000\n                txindex: 111\n                oindex: 0\n                utxo_pos: 123000001110000\n                otype: 1\n                owner: '0xb3256026863eb6ae5b06fa396ab09069784ea8ea'\n                currency: '0x0000000000000000000000000000000000000000'\n                amount: 50\n                creating_txhash: '0x40d65df1c3b1156d813d6bf96d5bd3b5bcf6e6588fc18c2a2ba564c6a64d4320'\n                spending_txhash: null\n              -\n                blknum: 277000\n                txindex: 2340\n                oindex: 3\n                utxo_pos: 277000023400003\n                otype: 1\n                owner: '0xb3256026863eb6ae5b06fa396ab09069784ea8ea'\n                currency: '0x0000000000000000000000000000000000000000'\n                amount: 75\n                creating_txhash: '0x40d65df1c3b1156d813d6bf96d5bd3b5bcf6e6588fc18c2a2ba564c6a64d4320'\n                spending_txhash: null\n            outputs:\n              -\n                amount: 100\n                currency: '0x0000000000000000000000000000000000000000'\n                owner: '0xae8ae48796090ba693af60b5ea6be3686206523b'\n              -\n                amount: 20\n                currency: '0x0000000000000000000000000000000000000000'\n                owner: '0xb3256026863eb6ae5b06fa396ab09069784ea8ea'\n            fee:\n              amount: 5\n              currency: '0x0000000000000000000000000000000000000000'\n            metadata: '0x5df13a6bf96dbcf6e66d8babd6b55bd40d64d4320c3b115364c6588fc18c2a21'\n            txbytes: '0x5df13a6bee20000...'\n            sign_hash: '0x7851b951edb0b9e88f0fc80e83461f71d0f4b1d4e44fae7d25a5d4ab6adc5d3d'\n            typed_data:\n              types:\n                EIP712Domain:\n                - name: name\n                  type: string\n                - name: version\n                  type: string\n                - name: verifyingContract\n                  type: address\n                - name: salt\n                  type: bytes32\n                Transaction:\n                - name: input0\n                  type: Input\n                - name: input1\n                  type: Input\n                - name: input2\n                  type: Input\n                - name: input3\n                  type: Input\n                - name: output0\n                  type: Output\n                - name: output1\n                  type: Output\n                - name: output2\n                  type: Output\n                - name: output3\n                  type: Output\n                - name: metadata\n                  type: bytes32\n                Input:\n                - name: blknum\n                  type: uint256\n                - name: txindex\n                  type: uint256\n                - name: oindex\n                  type: uint256\n                Output:\n                - name: owner\n                  type: address\n                - name: currency\n                  type: address\n                - name: amount\n                  type: uint256\n              primaryType: 'Transaction'\n              domain:\n                name: 'OMG Network'\n                salt: '0xfad5c7f626d80f9256ef01929f3beb96e058b8b4b0e3fe52d84f054c0e2a7a83'\n                verifyingContract: '0x44de0ec539b8c4a4b530c78620fe8320167f2f74'\n                version: '1'\n              message:\n                input0:\n                  blknum: 123000\n                  txindex: 111\n                  oindex: 0\n                input1:\n                  blknum: 277000\n                  txindex: 2340\n                  oindex: 3\n                input2:\n                  blknum: 0\n                  txindex: 0\n                  oindex: 0\n                input3:\n                  blknum: 0\n                  txindex: 0\n                  oindex: 0\n                output0:\n                  owner: '0xae8ae48796090ba693af60b5ea6be3686206523b'\n                  currency: '0x0000000000000000000000000000000000000000'\n                  amount: 100\n                output1:\n                  owner: '0xb3256026863eb6ae5b06fa396ab09069784ea8ea'\n                  currency: '0x0000000000000000000000000000000000000000'\n                  amount: 20\n                output2:\n                  owner: '0x0000000000000000000000000000000000000000'\n                  currency: '0x0000000000000000000000000000000000000000'\n                  amount: 0\n                output3:\n                  owner: '0x0000000000000000000000000000000000000000'\n                  currency: '0x0000000000000000000000000000000000000000'\n                  amount: 0\n                metadata: '0x5df13a6bf96dbcf6e66d8babd6b55bd40d64d4320c3b115364c6588fc18c2a21'\n\nMergeTransactionResponseSchema:\n  allOf:\n  - $ref: '../response_schemas.yaml#/WatcherInfoBaseResponseSchema'\n  - type: object\n    properties:\n      data:\n        type: object\n        $ref: 'schemas.yaml#/MergeTransactionSchema'\n    example:\n      data:\n        result: 'complete'\n        transactions:\n          -\n            inputs:\n              -\n                blknum: 123000\n                txindex: 111\n                oindex: 0\n                utxo_pos: 123000001110000\n                otype: 1\n                owner: '0xb3256026863eb6ae5b06fa396ab09069784ea8ea'\n                currency: '0x0000000000000000000000000000000000000000'\n                amount: 50\n                creating_txhash: '0x40d65df1c3b1156d813d6bf96d5bd3b5bcf6e6588fc18c2a2ba564c6a64d4320'\n                spending_txhash: null\n              -\n                blknum: 277000\n                txindex: 2340\n                oindex: 3\n                utxo_pos: 277000023400003\n                otype: 1\n                owner: '0xb3256026863eb6ae5b06fa396ab09069784ea8ea'\n                currency: '0x0000000000000000000000000000000000000000'\n                amount: 50\n                creating_txhash: '0x40d65df1c3b1156d813d6bf96d5bd3b5bcf6e6588fc18c2a2ba564c6a64d4320'\n                spending_txhash: null\n            outputs:\n              -\n                amount: 100\n                currency: '0x0000000000000000000000000000000000000000'\n                owner: '0xb3256026863eb6ae5b06fa396ab09069784ea8ea'\n            fee:\n              amount: 0\n              currency: '0x0000000000000000000000000000000000000000'\n            metadata: '0x5df13a6bf96dbcf6e66d8babd6b55bd40d64d4320c3b115364c6588fc18c2a21'\n            txbytes: '0x5df13a6bee20000...'\n            sign_hash: '0x7851b951edb0b9e88f0fc80e83461f71d0f4b1d4e44fae7d25a5d4ab6adc5d3d'\n            typed_data:\n              types:\n                EIP712Domain:\n                - name: name\n                  type: string\n                - name: version\n                  type: string\n                - name: verifyingContract\n                  type: address\n                - name: salt\n                  type: bytes32\n                Transaction:\n                - name: input0\n                  type: Input\n                - name: input1\n                  type: Input\n                - name: input2\n                  type: Input\n                - name: input3\n                  type: Input\n                - name: output0\n                  type: Output\n                - name: output1\n                  type: Output\n                - name: output2\n                  type: Output\n                - name: output3\n                  type: Output\n                - name: metadata\n                  type: bytes32\n                Input:\n                - name: blknum\n                  type: uint256\n                - name: txindex\n                  type: uint256\n                - name: oindex\n                  type: uint256\n                Output:\n                - name: owner\n                  type: address\n                - name: currency\n                  type: address\n                - name: amount\n                  type: uint256\n              primaryType: 'Transaction'\n              domain:\n                name: 'OMG Network'\n                salt: '0xfad5c7f626d80f9256ef01929f3beb96e058b8b4b0e3fe52d84f054c0e2a7a83'\n                verifyingContract: '0x44de0ec539b8c4a4b530c78620fe8320167f2f74'\n                version: '1'\n              message:\n                input0:\n                  blknum: 123000\n                  txindex: 111\n                  oindex: 0\n                input1:\n                  blknum: 277000\n                  txindex: 2340\n                  oindex: 3\n                input2:\n                  blknum: 0\n                  txindex: 0\n                  oindex: 0\n                input3:\n                  blknum: 0\n                  txindex: 0\n                  oindex: 0\n                output0:\n                  owner: '0xb3256026863eb6ae5b06fa396ab09069784ea8ea'\n                  currency: '0x0000000000000000000000000000000000000000'\n                  amount: 100\n                output1:\n                  owner: '0x0000000000000000000000000000000000000000'\n                  currency: '0x0000000000000000000000000000000000000000'\n                  amount: 0\n                output2:\n                  owner: '0x0000000000000000000000000000000000000000'\n                  currency: '0x0000000000000000000000000000000000000000'\n                  amount: 0\n                output3:\n                  owner: '0x0000000000000000000000000000000000000000'\n                  currency: '0x0000000000000000000000000000000000000000'\n                  amount: 0\n                metadata: '0x0000000000000000000000000000000000000000000000000000000000000000'\n\n\nGetTransactionResponseSchema:\n  allOf:\n  - $ref: '../response_schemas.yaml#/WatcherInfoBaseResponseSchema'\n  - type: object\n    properties:\n      data:\n        type: object\n        $ref: 'schemas.yaml#/TransactionSchema'\n    example:\n      data:\n        txindex: 5113\n        txtype: 1\n        txhash: '0x5df13a6bf96dbcf6e66d8babd6b55bd40d64d4320c3b115364c6588fc18c2a21'\n        metadata: '0x00000000000000000000000000000000000000000000000000000048656c6c6f'\n        txbytes: '0x5df13a6bee20000...'\n        inserted_at: '2020-02-10T12:07:32Z'\n        updated_at: '2020-02-15T04:07:57Z'\n        block:\n          timestamp: 1540365586\n          hash: '0x0017372421f9a92bedb7163310918e623557ab5310befc14e67212b660c33bec'\n          eth_height: 97424\n          blknum: 68290000\n          inserted_at: '2020-02-10T12:07:32Z'\n          updated_at: '2020-02-15T04:07:57Z'\n        inputs:\n        -\n          blknum: 1000\n          txindex: 111\n          oindex: 0\n          otype: 1\n          utxo_pos: 1000001110000\n          owner: '0xb3256026863eb6ae5b06fa396ab09069784ea8ea'\n          currency: '0x0000000000000000000000000000000000000000'\n          amount: 10\n          creating_txhash: '0x40d65df1c3b1156d813d6bf96d5bd3b5bcf6e6588fc18c2a2ba564c6a64d4320'\n          spending_txhash: '0x5df13a6bf96dbcf6e66d8babd6b55bd40d64d4320c3b115364c6588fc18c2a21'\n          inserted_at: '2020-02-10T12:07:32Z'\n          updated_at: '2020-02-15T04:07:57Z'\n        outputs:\n        -\n          blknum: 68290000\n          txindex: 5113\n          oindex: 0\n          otype: 1\n          utxo_pos: 68290000051130000\n          owner: '0xae8ae48796090ba693af60b5ea6be3686206523b'\n          currency: '0x0000000000000000000000000000000000000000'\n          amount: 2\n          creating_txhash: '0x5df13a6bf96dbcf6e66d8babd6b55bd40d64d4320c3b115364c6588fc18c2a21'\n          spending_txhash: null\n          inserted_at: '2020-02-10T12:07:32Z'\n          updated_at: '2020-02-15T04:07:57Z'\n        -\n          blknum: 68290000\n          txindex: 5113\n          oindex: 1\n          otype: 1\n          utxo_pos: 68290000051130001\n          owner: '0xb3256026863eb6ae5b06fa396ab09069784ea8ea'\n          currency: '0x0000000000000000000000000000000000000000'\n          amount: 7\n          creating_txhash: '0x5df13a6bf96dbcf6e66d8babd6b55bd40d64d4320c3b115364c6588fc18c2a21'\n          spending_txhash: null\n          inserted_at: '2020-02-10T12:07:32Z'\n          updated_at: '2020-02-15T04:07:57Z'\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/info_api_specs/transaction/responses.yaml",
    "content": "GetAllTransactionsResponse:\n  description: Transactions succcessful response\n  content:\n    application/json:\n      schema:\n        $ref: 'response_schemas.yaml#/GetAllTransactionsResponseSchema'\n\nCreateTransactionResponse:\n  description: Transaction create successful response\n  content:\n    application/json:\n      schema:\n        $ref: 'response_schemas.yaml#/CreateTransactionResponseSchema'\n\nMergeTransactionResponse:\n  description: Transaction merge successful response\n  content:\n    application/json:\n      schema:\n        $ref: 'response_schemas.yaml#/MergeTransactionResponseSchema'\n\nGetTransactionResponse:\n  description: Transaction details succcessful response\n  content:\n    application/json:\n      schema:\n        $ref: 'response_schemas.yaml#/GetTransactionResponseSchema'\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/info_api_specs/transaction/schemas.yaml",
    "content": "TransactionOutputSchema:\n  type: object\n  properties:\n    blknum:\n      type: integer\n      format: int64\n    txindex:\n      type: integer\n      format: int16\n    oindex:\n      type: integer\n      format: int8\n    otype:\n      type: integer\n      format: int8\n    utxo_pos:\n      type: integer\n      format: int256\n    owner:\n      type: string\n       \n    currency:\n      type: string\n       \n    amount:\n      type: integer\n      format: int256\n    creating_txhash:\n      type: string\n\n    spending_txhash:\n      type: string\n\n    inserted_at:\n      type: string\n    updated_at:\n      type: string\n  \n       \n\nTransactionSchema:\n  type: object\n  properties:\n    txindex:\n      type: integer\n      format: int16\n    txtype:\n      type: integer\n      format: int16\n    txhash:\n      type: string\n       \n    metadata:\n      type: string\n       \n    txbytes:\n      type: string\n\n    inserted_at:\n      type: string\n    updated_at:\n      type: string\n   \n    block:\n      $ref: '../block/schemas.yaml#/BlockSchema'\n    inputs:\n      type: array\n      items:\n        $ref: '#/TransactionOutputSchema'\n    outputs:\n      type: array\n      items:\n        $ref: '#/TransactionOutputSchema'\n\nEip712DomainSchema:\n  type: object\n  properties:\n    name:\n      type: string\n    salt:\n      type: string\n       \n    verifyingContract:\n      type: string\n       \n    version:\n      type: string\n  required:\n    - name\n    - salt\n    - verifyingContract\n    - version\n\nEip712MsgInputSchema:\n  type: object\n  properties:\n    blknum:\n      type: integer\n      format: int64\n    txindex:\n      type: integer\n      format: int16\n    oindex:\n      type: integer\n      format: int8\n\nEip712MsgOutputSchema:\n  type: object\n  properties:\n    owner:\n      type: string\n       \n    currency:\n      type: string\n       \n    amount:\n      type: integer\n      format: int256\n\nEip712MsgTransactionSchema:\n  type: object\n  properties:\n    input0:\n      $ref: '#/Eip712MsgInputSchema'\n    input1:\n      $ref: '#/Eip712MsgInputSchema'\n    input2:\n      $ref: '#/Eip712MsgInputSchema'\n    input3:\n      $ref: '#/Eip712MsgInputSchema'\n    output0:\n      $ref: '#/Eip712MsgOutputSchema'\n    output1:\n      $ref: '#/Eip712MsgOutputSchema'\n    output2:\n      $ref: '#/Eip712MsgOutputSchema'\n    output3:\n      $ref: '#/Eip712MsgOutputSchema'\n    metadata:\n      type: string\n       \n  required:\n    - input0\n    - input1\n    - input2\n    - input3\n    - output0\n    - output1\n    - output2\n    - output3\n    - metadata\n\nEip712SignRequestSchema:\n  type: object\n  properties:\n    types:\n      type: object\n      properties:\n        EIP712Domain:\n          type: array\n          items:\n            type: object\n            properties:\n              name:\n                type: string\n              type:\n                type: string\n        additionalProperties:\n          type: array\n          items:\n            type: object\n            properties:\n              name:\n                type: string\n              type:\n                type: string\n            required:\n              - name\n              - type\n    primaryType:\n      type: string\n    domain:\n      $ref: '#/Eip712DomainSchema'\n    message:\n      $ref: '#/Eip712MsgTransactionSchema'\n\nCreateTransactionSchema:\n  type: object\n  properties:\n    result:\n      type: string\n      enum: [complete, intermediate]\n    transactions:\n      type: array\n      items:\n        type: object\n        properties:\n          inputs:\n            type: array\n            items:\n              $ref: '#/TransactionOutputSchema'\n          outputs:\n            type: array\n            items:\n              type: object\n              properties:\n                amount:\n                  type: integer\n                  format: int256\n                currency:\n                  type: string\n                   \n                owner:\n                  type: string\n                   \n          fee:\n            type: object\n            properties:\n              amount:\n                type: integer\n                format: int256\n              currency:\n                type: string\n                 \n          metadata:\n            type: string\n             \n          txbytes:\n            type: string\n             \n          sign_hash:\n            type: string\n             \n          typed_data:\n            $ref: '#/Eip712SignRequestSchema'\n\nMergeTransactionSchema:\n  type: object\n  properties:\n    transactions:\n      type: array\n      items:\n        type: object\n        properties:\n          inputs:\n            type: array\n            items:\n              $ref: '#/TransactionOutputSchema'\n          outputs:\n            type: array\n            items:\n              type: object\n              properties:\n                amount:\n                  type: integer\n                  format: int256\n                currency:\n                  type: string\n                   \n                owner:\n                  type: string\n                   \n          fee:\n            type: object\n            properties:\n              amount:\n                type: integer\n                format: int256\n              currency:\n                type: string\n                 \n          metadata:\n            type: string\n             \n          txbytes:\n            type: string\n             \n          sign_hash:\n            type: string\n             \n          typed_data:\n            $ref: '#/Eip712SignRequestSchema'\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/info_api_specs.yaml",
    "content": "openapi: 3.0.0\ninfo:\n  version: 1.0.0\n  title: Watcher Info API\n  description: |\n    API specification of the Watcher's Informational Service\n    Error codes are available in [html](https://github.com/omgnetwork/elixir-omg/blob/master/docs/api_specs/errors.md#error-codes-description) format.\n  contact:\n    name: OMG Network\n    email: engineering@omg.network\n  license:\n    name: 'Apache 2.0: https://www.apache.org/licenses/LICENSE-2.0'\n    url: 'https://omg.network/'\nservers:\n  - url: 'https://watcher-info.ropsten.v1.omg.network/'\n  - url: 'http://localhost:7534/'\ntags:\n  - name: Account\n    description: Account related API.\n  - name: Block\n    description: Block related API.\n  - name: Deposit\n    description: Deposit related API.\n  - name: Transaction\n    description: Transaction related API.\n  - name: Fees\n    description: Fees related API.\n  - name: Stats\n    description: Stats related API.\n  - name: Configuration\n    description: Configuration related API.\npaths:\n  /alarm.get:\n    get:\n      tags:\n        - Alarm\n      summary: 'Provides alarms related to system memory, cpu and storage and application specific alarms.'\n      description: |\n        **Note:** Service operator alarms.\n      operationId: alarm_get\n      responses:\n        '200':\n          description: System alarms\n          content:\n            application/json:\n              schema:\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher_info\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        type: array\n                        items:\n                          type: array\n                          items:\n                            anyOf:\n                              - type: object\n                                properties:\n                                  ethereum_connection_error:\n                                    type: object\n                                    properties:\n                                      node:\n                                        type: string\n                                      reporter:\n                                        type: string\n                              - type: object\n                                properties:\n                                  ethereum_stalled_sync:\n                                    type: object\n                                    properties:\n                                      ethereum_height:\n                                        type: integer\n                                        minimum: 0\n                                      synced_at:\n                                        type: string\n                                        format: date-time\n                              - type: object\n                                properties:\n                                  invalid_fee_source:\n                                    type: object\n                                    properties:\n                                      node:\n                                        type: string\n                                      reporter:\n                                        type: string\n                              - type: object\n                                properties:\n                                  statsd_client_connection:\n                                    type: object\n                                    properties:\n                                      node:\n                                        type: string\n                                      reporter:\n                                        type: string\n                              - type: object\n                                properties:\n                                  statsd_client_connection:\n                                    type: array\n                                    items:\n                                      type: string\n                                    default: []\n                              - type: object\n                                properties:\n                                  disk_almost_full:\n                                    type: string\n                    example:\n                      data:\n                        - disk_almost_full: /dev/null\n                          ethereum_connection_error: {}\n                          ethereum_stalled_sync: {}\n                          system_memory_high_watermark: []\n        '500':\n          description: Returns an internal server error\n          content:\n            application/json:\n              schema:\n                description: The response schema for an error\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher_info\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        description: The object schema for an error\n                        type: object\n                        properties:\n                          object:\n                            type: string\n                          code:\n                            type: string\n                          description:\n                            type: string\n                          messages:\n                            type: object\n                        required:\n                          - object\n                          - code\n                          - description\n                          - messages\n                    required:\n                      - data\n                    example:\n                      success: false\n                      data:\n                        object: error\n                        code: 'server:internal_server_error'\n                        description: Something went wrong on the server\n                        messages:\n                          error_key: error_reason\n  /configuration.get:\n    get:\n      tags:\n        - Configuration\n      summary: Provides configuration values\n      description: |\n        **Note:** Configuration values.\n      operationId: configuration_get\n      responses:\n        '200':\n          description: Configuration response\n          content:\n            application/json:\n              schema:\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher_info\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        type: array\n                        items:\n                          type: object\n                          properties:\n                            deposit_finality_margin:\n                              type: integer\n                              format: int256\n                            contract_semver:\n                              type: string\n                            exit_processor_sla_margin:\n                              type: integer\n                              format: int256\n                            network:\n                              type: string\n                    example:\n                      data:\n                        - deposit_finality_margin: 10\n                          contract_semver: 1.0.0.1+a1s29s8\n                          exit_processor_sla_margin: 5520\n                          network: RINKEBY\n        '500':\n          description: Returns an internal server error\n          content:\n            application/json:\n              schema:\n                description: The response schema for an error\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher_info\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        description: The object schema for an error\n                        type: object\n                        properties:\n                          object:\n                            type: string\n                          code:\n                            type: string\n                          description:\n                            type: string\n                          messages:\n                            type: object\n                        required:\n                          - object\n                          - code\n                          - description\n                          - messages\n                    required:\n                      - data\n                    example:\n                      success: false\n                      data:\n                        object: error\n                        code: 'server:internal_server_error'\n                        description: Something went wrong on the server\n                        messages:\n                          error_key: error_reason\n  /account.get_balance:\n    post:\n      tags:\n        - Account\n      summary: Returns the balance of each currency for the given account address.\n      operationId: account_get_balance\n      requestBody:\n        description: HEX-encoded address of the account and pagination fields\n        required: true\n        content:\n          application/json:\n            schema:\n              title: AddressBodySchema\n              type: object\n              properties:\n                address:\n                  type: string\n                page:\n                  type: integer\n                  format: int32\n                  default: 1\n                limit:\n                  type: integer\n                  format: int32\n                  default: 200\n              required:\n                - address\n              example:\n                address: '0xb3256026863eb6ae5b06fa396ab09069784ea8ea'\n                limit: 100\n                page: 2\n      responses:\n        '200':\n          description: Account balance successful response\n          content:\n            application/json:\n              schema:\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher_info\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        type: array\n                        items:\n                          type: object\n                          properties:\n                            currency:\n                              type: string\n                            amount:\n                              type: integer\n                              format: int256\n                    example:\n                      data:\n                        - currency: '0xbfdf85743ef16cfb1f8d4dd1dfc74c51dc496434'\n                          amount: 20\n                        - currency: '0x0000000000000000000000000000000000000000'\n                          amount: 1000000000\n        '500':\n          description: Returns an internal server error\n          content:\n            application/json:\n              schema:\n                description: The response schema for an error\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher_info\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        description: The object schema for an error\n                        type: object\n                        properties:\n                          object:\n                            type: string\n                          code:\n                            type: string\n                          description:\n                            type: string\n                          messages:\n                            type: object\n                        required:\n                          - object\n                          - code\n                          - description\n                          - messages\n                    required:\n                      - data\n                    example:\n                      success: false\n                      data:\n                        object: error\n                        code: 'server:internal_server_error'\n                        description: Something went wrong on the server\n                        messages:\n                          error_key: error_reason\n  /account.get_utxos:\n    post:\n      tags:\n        - Account\n      summary: Gets all utxos belonging to the given address.\n      operationId: account_get_utxos\n      requestBody:\n        description: HEX-encoded address of the account and pagination fields\n        required: true\n        content:\n          application/json:\n            schema:\n              title: AddressBodySchema\n              type: object\n              properties:\n                address:\n                  type: string\n                page:\n                  type: integer\n                  format: int32\n                  default: 1\n                limit:\n                  type: integer\n                  format: int32\n                  default: 200\n              required:\n                - address\n              example:\n                address: '0xb3256026863eb6ae5b06fa396ab09069784ea8ea'\n                limit: 100\n                page: 2\n      responses:\n        '200':\n          description: Account utxos succcessful response\n          content:\n            application/json:\n              schema:\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher_info\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        type: array\n                        items:\n                          type: object\n                          properties:\n                            blknum:\n                              type: integer\n                              format: int64\n                            txindex:\n                              type: integer\n                              format: int16\n                            otype:\n                              type: integer\n                              format: int16\n                            oindex:\n                              type: integer\n                              format: int8\n                            utxo_pos:\n                              type: integer\n                              format: int256\n                            creating_txhash:\n                              type: string\n                            spending_txhash:\n                              type: string\n                            owner:\n                              type: string\n                            currency:\n                              type: string\n                            amount:\n                              type: integer\n                              format: int256\n                            inserted_at:\n                              type: string\n                            updated_at:\n                              type: string\n                      data_paging:\n                        type: object\n                        properties:\n                          page:\n                            type: integer\n                            format: int32\n                            default: 1\n                          limit:\n                            type: integer\n                            format: int32\n                            default: 200\n                    example:\n                      data:\n                        - amount: 10\n                          blknum: 123000\n                          creating_txhash: '0x2c499b95ccb6bf7b923049b32b03a613d30882a448102136e544b302119eb722'\n                          currency: '0x0000000000000000000000000000000000000000'\n                          inserted_at: '2020-02-10T12:07:32Z'\n                          oindex: 0\n                          otype: 1\n                          owner: '0xb3256026863eb6ae5b06fa396ab09069784ea8ea'\n                          spending_txhash: null\n                          txindex: 111\n                          updated_at: '2020-02-15T04:07:57Z'\n                          utxo_pos: 123000001110000\n                      data_paging:\n                        page: 1\n                        limit: 200\n        '500':\n          description: Returns an internal server error\n          content:\n            application/json:\n              schema:\n                description: The response schema for an error\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher_info\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        description: The object schema for an error\n                        type: object\n                        properties:\n                          object:\n                            type: string\n                          code:\n                            type: string\n                          description:\n                            type: string\n                          messages:\n                            type: object\n                        required:\n                          - object\n                          - code\n                          - description\n                          - messages\n                    required:\n                      - data\n                    example:\n                      success: false\n                      data:\n                        object: error\n                        code: 'server:internal_server_error'\n                        description: Something went wrong on the server\n                        messages:\n                          error_key: error_reason\n  /account.get_transactions:\n    post:\n      tags:\n        - Account\n      summary: Gets a list of transactions for given account address.\n      operationId: account_get_transactions\n      requestBody:\n        description: 'Account address, block number and other criteria'\n        required: true\n        content:\n          application/json:\n            schema:\n              title: GetAllTransactionsBodySchema\n              type: object\n              properties:\n                address:\n                  type: string\n                blknum:\n                  type: integer\n                  format: int64\n                txtypes:\n                  type: array\n                  items:\n                    type: integer\n                metadata:\n                  type: string\n                page:\n                  type: integer\n                  format: int32\n                  default: 1\n                limit:\n                  type: integer\n                  format: int32\n                  default: 200\n                end_datetime:\n                  type: integer\n                  format: int32\n              required:\n                - limit\n              example:\n                address: '0xb3256026863eb6ae5b06fa396ab09069784ea8ea'\n                metadata: '0x5df13a6bf96dbcf6e66d8babd6b55bd40d64d4320c3b115364c6588fc18c2a21'\n                txtypes:\n                  - 1\n                blknum: 68290000\n                limit: 100\n                page: 2\n                end_datetime: 1592476174\n      responses:\n        '200':\n          description: Transactions succcessful response\n          content:\n            application/json:\n              schema:\n                allOf:\n                  - description: The response schema for a successful list operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: array\n                        items:\n                          type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher_info\n                      version: 1.0+abcdefa\n                      success: true\n                      data: []\n                  - type: object\n                    properties:\n                      data:\n                        type: array\n                        items:\n                          type: object\n                          properties:\n                            txindex:\n                              type: integer\n                              format: int16\n                            txtype:\n                              type: integer\n                              format: int16\n                            txhash:\n                              type: string\n                            metadata:\n                              type: string\n                            txbytes:\n                              type: string\n                            inserted_at:\n                              type: string\n                            updated_at:\n                              type: string\n                            block:\n                              type: object\n                              properties:\n                                blknum:\n                                  type: integer\n                                  format: int64\n                                hash:\n                                  type: string\n                                eth_height:\n                                  type: integer\n                                  format: int64\n                                timestamp:\n                                  type: integer\n                                  format: int64\n                                tx_count:\n                                  type: integer\n                                  format: int64\n                                inserted_at:\n                                  type: string\n                                updated_at:\n                                  type: string\n                            inputs:\n                              type: array\n                              items:\n                                type: object\n                                properties:\n                                  blknum:\n                                    type: integer\n                                    format: int64\n                                  txindex:\n                                    type: integer\n                                    format: int16\n                                  oindex:\n                                    type: integer\n                                    format: int8\n                                  otype:\n                                    type: integer\n                                    format: int8\n                                  utxo_pos:\n                                    type: integer\n                                    format: int256\n                                  owner:\n                                    type: string\n                                  currency:\n                                    type: string\n                                  amount:\n                                    type: integer\n                                    format: int256\n                                  creating_txhash:\n                                    type: string\n                                  spending_txhash:\n                                    type: string\n                                  inserted_at:\n                                    type: string\n                                  updated_at:\n                                    type: string\n                            outputs:\n                              type: array\n                              items:\n                                type: object\n                                properties:\n                                  blknum:\n                                    type: integer\n                                    format: int64\n                                  txindex:\n                                    type: integer\n                                    format: int16\n                                  oindex:\n                                    type: integer\n                                    format: int8\n                                  otype:\n                                    type: integer\n                                    format: int8\n                                  utxo_pos:\n                                    type: integer\n                                    format: int256\n                                  owner:\n                                    type: string\n                                  currency:\n                                    type: string\n                                  amount:\n                                    type: integer\n                                    format: int256\n                                  creating_txhash:\n                                    type: string\n                                  spending_txhash:\n                                    type: string\n                                  inserted_at:\n                                    type: string\n                                  updated_at:\n                                    type: string\n                      data_paging:\n                        type: object\n                        properties:\n                          page:\n                            type: integer\n                            format: int32\n                            default: 1\n                          limit:\n                            type: integer\n                            format: int32\n                            default: 200\n                    example:\n                      data:\n                        - block:\n                            timestamp: 1540365586\n                            hash: '0x0017372421f9a92bedb7163310918e623557ab5310befc14e67212b660c33bec'\n                            eth_height: 97424\n                            blknum: 68290000\n                            inserted_at: '2020-02-10T12:07:32Z'\n                            updated_at: '2020-02-15T04:07:57Z'\n                          txindex: 0\n                          txtype: 1\n                          txhash: '0x5df13a6bf96dbcf6e66d8babd6b55bd40d64d4320c3b115364c6588fc18c2a21'\n                          metadata: '0x00000000000000000000000000000000000000000000000000000048656c6c6f'\n                          txbytes: 0x5df13a6bee20000...\n                          inserted_at: '2020-02-10T12:07:32Z'\n                          updated_at: '2020-02-15T04:07:57Z'\n                          inputs:\n                            - blknum: 1000\n                              txindex: 111\n                              otype: 1\n                              oindex: 0\n                              utxo_pos: 1000001110000\n                              owner: '0xb3256026863eb6ae5b06fa396ab09069784ea8ea'\n                              currency: '0x0000000000000000000000000000000000000000'\n                              creating_txhash: '0x40d65df1c3b1156d813d6bf96d5bd3b5bcf6e6588fc18c2a2ba564c6a64d4320'\n                              spending_txhash: '0x5df13a6bf96dbcf6e66d8babd6b55bd40d64d4320c3b115364c6588fc18c2a21'\n                              amount: 20000000\n                              inserted_at: '2020-02-10T12:07:32Z'\n                              updated_at: '2020-02-15T04:07:57Z'\n                          outputs:\n                            - blknum: 68290000\n                              txindex: 5113\n                              otype: 1\n                              oindex: 0\n                              utxo_pos: 68290000051130000\n                              owner: '0xae8ae48796090ba693af60b5ea6be3686206523b'\n                              currency: '0x0000000000000000000000000000000000000000'\n                              amount: 15000000\n                              creating_txhash: '0x5df13a6bf96dbcf6e66d8babd6b55bd40d64d4320c3b115364c6588fc18c2a21'\n                              spending_txhash: null\n                              inserted_at: '2020-02-10T12:07:32Z'\n                              updated_at: '2020-02-15T04:07:57Z'\n                            - blknum: 68290000\n                              txindex: 5113\n                              otype: 1\n                              oindex: 1\n                              utxo_pos: 68290000051130000\n                              owner: '0xb3256026863eb6ae5b06fa396ab09069784ea8ea'\n                              currency: '0x0000000000000000000000000000000000000000'\n                              amount: 5000000\n                              creating_txhash: '0x5df13a6bf96dbcf6e66d8babd6b55bd40d64d4320c3b115364c6588fc18c2a21'\n                              spending_txhash: null\n                              inserted_at: '2020-02-10T12:07:32Z'\n                              updated_at: '2020-02-15T04:07:57Z'\n                      data_paging:\n                        page: 1\n                        limit: 200\n        '500':\n          description: Returns an internal server error\n          content:\n            application/json:\n              schema:\n                description: The response schema for an error\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher_info\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        description: The object schema for an error\n                        type: object\n                        properties:\n                          object:\n                            type: string\n                          code:\n                            type: string\n                          description:\n                            type: string\n                          messages:\n                            type: object\n                        required:\n                          - object\n                          - code\n                          - description\n                          - messages\n                    required:\n                      - data\n                    example:\n                      success: false\n                      data:\n                        object: error\n                        code: 'server:internal_server_error'\n                        description: Something went wrong on the server\n                        messages:\n                          error_key: error_reason\n  /block.get:\n    post:\n      tags:\n        - Block\n      summary: Retrieves a single block for the given block number.\n      description: |\n        Intended for operations requiring info for a specific block only.\n        Returns a single block object for the given block number. The returned object includes transaction count but not associated transactions - for which you can use transaction.all with blknum in the request body.\n      operationId: block_get\n      requestBody:\n        description: Block number\n        required: true\n        content:\n          application/json:\n            schema:\n              title: GetBlockBodySchema\n              type: object\n              properties:\n                blknum:\n                  type: integer\n                  format: int64\n              example:\n                blknum: 68290000\n      responses:\n        '200':\n          description: Block succcessful response\n          content:\n            application/json:\n              schema:\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher_info\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        type: object\n                        properties:\n                          blknum:\n                            type: integer\n                            format: int64\n                          hash:\n                            type: string\n                          eth_height:\n                            type: integer\n                            format: int64\n                          timestamp:\n                            type: integer\n                            format: int64\n                          tx_count:\n                            type: integer\n                            format: int64\n                          inserted_at:\n                            type: string\n                          updated_at:\n                            type: string\n                    example:\n                      data:\n                        timestamp: 1540365586\n                        hash: '0x0017372421f9a92bedb7163310918e623557ab5310befc14e67212b660c33bec'\n                        eth_height: 97424\n                        blknum: 68290000\n                        tx_count: 2\n                        inserted_at: '2020-02-10T12:07:32Z'\n                        updated_at: '2020-02-15T04:07:57Z'\n        '500':\n          description: Returns an internal server error\n          content:\n            application/json:\n              schema:\n                description: The response schema for an error\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher_info\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        description: The object schema for an error\n                        type: object\n                        properties:\n                          object:\n                            type: string\n                          code:\n                            type: string\n                          description:\n                            type: string\n                          messages:\n                            type: object\n                        required:\n                          - object\n                          - code\n                          - description\n                          - messages\n                    required:\n                      - data\n                    example:\n                      success: false\n                      data:\n                        object: error\n                        code: 'server:internal_server_error'\n                        description: Something went wrong on the server\n                        messages:\n                          error_key: error_reason\n  /block.all:\n    post:\n      tags:\n        - Block\n      summary: Gets all blocks (can be limited with various filters).\n      description: |\n        Returns a list of blocks ordered by the highest block number first. Intended to be used for presenting an overview of most recent blocks.\n        Note: Due to eventual consistency nature of the informational API, blocks may later than deposits, exits, etc.\n      operationId: block_all\n      requestBody:\n        description: The supported request parameters for /block.all\n        content:\n          application/json:\n            schema:\n              title: AllBlocksBodySchema\n              type: object\n              properties:\n                page:\n                  type: integer\n                  format: int32\n                  default: 1\n                limit:\n                  type: integer\n                  format: int32\n                  default: 100\n              example:\n                page: 2\n                limit: 100\n      responses:\n        '200':\n          description: Blocks succcessful response\n          content:\n            application/json:\n              schema:\n                allOf:\n                  - description: The response schema for a successful list operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: array\n                        items:\n                          type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher_info\n                      version: 1.0+abcdefa\n                      success: true\n                      data: []\n                  - type: object\n                    properties:\n                      data:\n                        type: array\n                        items:\n                          type: object\n                          properties:\n                            blknum:\n                              type: integer\n                              format: int64\n                            hash:\n                              type: string\n                            eth_height:\n                              type: integer\n                              format: int64\n                            timestamp:\n                              type: integer\n                              format: int64\n                            tx_count:\n                              type: integer\n                              format: int64\n                            inserted_at:\n                              type: string\n                            updated_at:\n                              type: string\n                      data_paging:\n                        type: object\n                        properties:\n                          page:\n                            type: integer\n                            format: int32\n                            default: 1\n                          limit:\n                            type: integer\n                            format: int32\n                            default: 100\n                    example:\n                      data:\n                        - blknum: 68290000\n                          hash: '0x0017372421f9a92bedb7163310918e623557ab5310befc14e67212b660c33bec'\n                          eth_height: 97424\n                          timestamp: 1540365586\n                          tx_count: 2\n                          inserted_at: '2020-02-10T12:07:32Z'\n                          updated_at: '2020-02-15T04:07:57Z'\n                      data_paging:\n                        page: 1\n                        limit: 100\n        '500':\n          description: Returns an internal server error\n          content:\n            application/json:\n              schema:\n                description: The response schema for an error\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher_info\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        description: The object schema for an error\n                        type: object\n                        properties:\n                          object:\n                            type: string\n                          code:\n                            type: string\n                          description:\n                            type: string\n                          messages:\n                            type: object\n                        required:\n                          - object\n                          - code\n                          - description\n                          - messages\n                    required:\n                      - data\n                    example:\n                      success: false\n                      data:\n                        object: error\n                        code: 'server:internal_server_error'\n                        description: Something went wrong on the server\n                        messages:\n                          error_key: error_reason\n  /deposit.all:\n    post:\n      tags:\n        - Deposit\n      summary: Gets a paginated list of deposit for the given address.\n      description: |\n        Returns a list of deposits ordered by Ethereum height in descending order for the given address.\n      operationId: deposit_all\n      requestBody:\n        description: The supported request parameters for /deposit.all. The \"limit\" and \"page\" parameters are optional.\n        required: true\n        content:\n          application/json:\n            schema:\n              title: AllDepositsBodySchema\n              type: object\n              properties:\n                address:\n                  type: string\n                page:\n                  type: integer\n                  format: int32\n                  default: 1\n                limit:\n                  type: integer\n                  format: int32\n                  default: 100\n              example:\n                page: 2\n                limit: 100\n                address: '0xb01cb6f56d798a62d1e0bace406c73a122c39c9d'\n      responses:\n        '200':\n          description: /deposit.all succcessful response\n          content:\n            application/json:\n              schema:\n                allOf:\n                  - description: The response schema for a successful list operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: array\n                        items:\n                          type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher_info\n                      version: 1.0+abcdefa\n                      success: true\n                      data: []\n                  - type: object\n                    properties:\n                      data:\n                        type: array\n                        items:\n                          type: object\n                          properties:\n                            event_type:\n                              type: string\n                            root_chain_txhash:\n                              type: string\n                            log_index:\n                              type: integer\n                            eth_height:\n                              type: integer\n                              format: int64\n                            inserted_at:\n                              type: string\n                            updated_at:\n                              type: string\n                            txoutputs:\n                              type: array\n                              items:\n                                type: object\n                                properties:\n                                  blknum:\n                                    type: integer\n                                    format: int64\n                                  txindex:\n                                    type: integer\n                                    format: int16\n                                  oindex:\n                                    type: integer\n                                    format: int8\n                                  otype:\n                                    type: integer\n                                    format: int8\n                                  utxo_pos:\n                                    type: integer\n                                    format: int256\n                                  owner:\n                                    type: string\n                                  currency:\n                                    type: string\n                                  amount:\n                                    type: integer\n                                    format: int256\n                                  creating_txhash:\n                                    type: string\n                                  spending_txhash:\n                                    type: string\n                                  inserted_at:\n                                    type: string\n                                  updated_at:\n                                    type: string\n                      data_paging:\n                        type: object\n                        properties:\n                          page:\n                            type: integer\n                            format: int32\n                            default: 1\n                          limit:\n                            type: integer\n                            format: int32\n                            default: 100\n                    example:\n                      data:\n                        - event_type: deposit\n                          inserted_at: '2020-05-15T12:37:40Z'\n                          eth_height: 168637\n                          log_index: 0\n                          root_chain_txhash: '0x63c056f122f5bf30bf8119ec0a2184b73f975951229995a427ea58d904eaab85'\n                          txoutputs:\n                            - blknum: 1\n                              txindex: 0\n                              otype: 1\n                              oindex: 0\n                              utxo_pos: 1000000000\n                              owner: '0xb3256026863eb6ae5b06fa396ab09069784ea8ea'\n                              currency: '0x0000000000000000000000000000000000000000'\n                              creating_txhash: null\n                              spending_txhash: '0x5df13a6bf96dbcf6e66d8babd6b55bd40d64d4320c3b115364c6588fc18c2a21'\n                              amount: 20000000\n                              inserted_at: '2020-02-10T12:07:32Z'\n                              updated_at: '2020-02-10T12:07:32Z'\n                      data_paging:\n                        page: 1\n                        limit: 100\n        '500':\n          description: Returns an internal server error\n          content:\n            application/json:\n              schema:\n                description: The response schema for an error\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher_info\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        description: The object schema for an error\n                        type: object\n                        properties:\n                          object:\n                            type: string\n                          code:\n                            type: string\n                          description:\n                            type: string\n                          messages:\n                            type: object\n                        required:\n                          - object\n                          - code\n                          - description\n                          - messages\n                    required:\n                      - data\n                    example:\n                      success: false\n                      data:\n                        object: error\n                        code: 'server:internal_server_error'\n                        description: Something went wrong on the server\n                        messages:\n                          error_key: error_reason\n  /transaction.all:\n    post:\n      tags:\n        - Transaction\n      summary: Gets all transactions (can be limited with various filters).\n      description: |\n        Digests the details of the transaction, by listing the value of outputs, aggregated by currency. Intended to be used when presenting the little details about multiple transactions. For all details queries to `/transaction.get` should be made using the transaction's hash provided.\n      operationId: transactions_all\n      requestBody:\n        description: 'Account address, block number and other criteria'\n        required: true\n        content:\n          application/json:\n            schema:\n              title: GetAllTransactionsBodySchema\n              type: object\n              properties:\n                address:\n                  type: string\n                blknum:\n                  type: integer\n                  format: int64\n                txtypes:\n                  type: array\n                  items:\n                    type: integer\n                metadata:\n                  type: string\n                page:\n                  type: integer\n                  format: int32\n                  default: 1\n                limit:\n                  type: integer\n                  format: int32\n                  default: 200\n                end_datetime:\n                  type: integer\n                  format: int32\n              required:\n                - limit\n              example:\n                address: '0xb3256026863eb6ae5b06fa396ab09069784ea8ea'\n                metadata: '0x5df13a6bf96dbcf6e66d8babd6b55bd40d64d4320c3b115364c6588fc18c2a21'\n                txtypes:\n                  - 1\n                blknum: 68290000\n                limit: 100\n                page: 2\n                end_datetime: 1592476174\n      responses:\n        '200':\n          description: Transactions succcessful response\n          content:\n            application/json:\n              schema:\n                allOf:\n                  - description: The response schema for a successful list operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: array\n                        items:\n                          type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher_info\n                      version: 1.0+abcdefa\n                      success: true\n                      data: []\n                  - type: object\n                    properties:\n                      data:\n                        type: array\n                        items:\n                          type: object\n                          properties:\n                            txindex:\n                              type: integer\n                              format: int16\n                            txtype:\n                              type: integer\n                              format: int16\n                            txhash:\n                              type: string\n                            metadata:\n                              type: string\n                            txbytes:\n                              type: string\n                            inserted_at:\n                              type: string\n                            updated_at:\n                              type: string\n                            block:\n                              type: object\n                              properties:\n                                blknum:\n                                  type: integer\n                                  format: int64\n                                hash:\n                                  type: string\n                                eth_height:\n                                  type: integer\n                                  format: int64\n                                timestamp:\n                                  type: integer\n                                  format: int64\n                                tx_count:\n                                  type: integer\n                                  format: int64\n                                inserted_at:\n                                  type: string\n                                updated_at:\n                                  type: string\n                            inputs:\n                              type: array\n                              items:\n                                type: object\n                                properties:\n                                  blknum:\n                                    type: integer\n                                    format: int64\n                                  txindex:\n                                    type: integer\n                                    format: int16\n                                  oindex:\n                                    type: integer\n                                    format: int8\n                                  otype:\n                                    type: integer\n                                    format: int8\n                                  utxo_pos:\n                                    type: integer\n                                    format: int256\n                                  owner:\n                                    type: string\n                                  currency:\n                                    type: string\n                                  amount:\n                                    type: integer\n                                    format: int256\n                                  creating_txhash:\n                                    type: string\n                                  spending_txhash:\n                                    type: string\n                                  inserted_at:\n                                    type: string\n                                  updated_at:\n                                    type: string\n                            outputs:\n                              type: array\n                              items:\n                                type: object\n                                properties:\n                                  blknum:\n                                    type: integer\n                                    format: int64\n                                  txindex:\n                                    type: integer\n                                    format: int16\n                                  oindex:\n                                    type: integer\n                                    format: int8\n                                  otype:\n                                    type: integer\n                                    format: int8\n                                  utxo_pos:\n                                    type: integer\n                                    format: int256\n                                  owner:\n                                    type: string\n                                  currency:\n                                    type: string\n                                  amount:\n                                    type: integer\n                                    format: int256\n                                  creating_txhash:\n                                    type: string\n                                  spending_txhash:\n                                    type: string\n                                  inserted_at:\n                                    type: string\n                                  updated_at:\n                                    type: string\n                      data_paging:\n                        type: object\n                        properties:\n                          page:\n                            type: integer\n                            format: int32\n                            default: 1\n                          limit:\n                            type: integer\n                            format: int32\n                            default: 200\n                    example:\n                      data:\n                        - block:\n                            timestamp: 1540365586\n                            hash: '0x0017372421f9a92bedb7163310918e623557ab5310befc14e67212b660c33bec'\n                            eth_height: 97424\n                            blknum: 68290000\n                            inserted_at: '2020-02-10T12:07:32Z'\n                            updated_at: '2020-02-15T04:07:57Z'\n                          txindex: 0\n                          txtype: 1\n                          txhash: '0x5df13a6bf96dbcf6e66d8babd6b55bd40d64d4320c3b115364c6588fc18c2a21'\n                          metadata: '0x00000000000000000000000000000000000000000000000000000048656c6c6f'\n                          txbytes: 0x5df13a6bee20000...\n                          inserted_at: '2020-02-10T12:07:32Z'\n                          updated_at: '2020-02-15T04:07:57Z'\n                          inputs:\n                            - blknum: 1000\n                              txindex: 111\n                              otype: 1\n                              oindex: 0\n                              utxo_pos: 1000001110000\n                              owner: '0xb3256026863eb6ae5b06fa396ab09069784ea8ea'\n                              currency: '0x0000000000000000000000000000000000000000'\n                              creating_txhash: '0x40d65df1c3b1156d813d6bf96d5bd3b5bcf6e6588fc18c2a2ba564c6a64d4320'\n                              spending_txhash: '0x5df13a6bf96dbcf6e66d8babd6b55bd40d64d4320c3b115364c6588fc18c2a21'\n                              amount: 20000000\n                              inserted_at: '2020-02-10T12:07:32Z'\n                              updated_at: '2020-02-15T04:07:57Z'\n                          outputs:\n                            - blknum: 68290000\n                              txindex: 5113\n                              otype: 1\n                              oindex: 0\n                              utxo_pos: 68290000051130000\n                              owner: '0xae8ae48796090ba693af60b5ea6be3686206523b'\n                              currency: '0x0000000000000000000000000000000000000000'\n                              amount: 15000000\n                              creating_txhash: '0x5df13a6bf96dbcf6e66d8babd6b55bd40d64d4320c3b115364c6588fc18c2a21'\n                              spending_txhash: null\n                              inserted_at: '2020-02-10T12:07:32Z'\n                              updated_at: '2020-02-15T04:07:57Z'\n                            - blknum: 68290000\n                              txindex: 5113\n                              otype: 1\n                              oindex: 1\n                              utxo_pos: 68290000051130000\n                              owner: '0xb3256026863eb6ae5b06fa396ab09069784ea8ea'\n                              currency: '0x0000000000000000000000000000000000000000'\n                              amount: 5000000\n                              creating_txhash: '0x5df13a6bf96dbcf6e66d8babd6b55bd40d64d4320c3b115364c6588fc18c2a21'\n                              spending_txhash: null\n                              inserted_at: '2020-02-10T12:07:32Z'\n                              updated_at: '2020-02-15T04:07:57Z'\n                      data_paging:\n                        page: 1\n                        limit: 200\n        '500':\n          description: Returns an internal server error\n          content:\n            application/json:\n              schema:\n                description: The response schema for an error\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher_info\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        description: The object schema for an error\n                        type: object\n                        properties:\n                          object:\n                            type: string\n                          code:\n                            type: string\n                          description:\n                            type: string\n                          messages:\n                            type: object\n                        required:\n                          - object\n                          - code\n                          - description\n                          - messages\n                    required:\n                      - data\n                    example:\n                      success: false\n                      data:\n                        object: error\n                        code: 'server:internal_server_error'\n                        description: Something went wrong on the server\n                        messages:\n                          error_key: error_reason\n  /transaction.create:\n    post:\n      tags:\n        - Transaction\n      summary: Finds an optimal way to construct a transaction spending particular amount.\n      description: |\n        Given token, amount and spender, finds spender's inputs sufficient to perform a payment. If also provided with receiver's address, creates and encodes a transaction.\n      operationId: createTransaction\n      requestBody:\n        description: The description of transaction to be crafted.\n        required: true\n        content:\n          application/json:\n            schema:\n              title: CreateTransactionsBodySchema\n              type: object\n              properties:\n                owner:\n                  type: string\n                payments:\n                  type: array\n                  items:\n                    type: object\n                    properties:\n                      amount:\n                        type: integer\n                        format: int256\n                      currency:\n                        type: string\n                      owner:\n                        type: string\n                    required:\n                      - amount\n                      - currency\n                fee:\n                  type: object\n                  properties:\n                    currency:\n                      type: string\n                  required:\n                    - currency\n                metadata:\n                  type: string\n              required:\n                - owner\n                - payments\n                - fee\n              example:\n                owner: '0xb3256026863eb6ae5b06fa396ab09069784ea8ea'\n                payments:\n                  - amount: 100\n                    currency: '0x0000000000000000000000000000000000000000'\n                    owner: '0xae8ae48796090ba693af60b5ea6be3686206523b'\n                fee:\n                  currency: '0x0000000000000000000000000000000000000000'\n                metadata: '0x5df13a6bf96dbcf6e66d8babd6b55bd40d64d4320c3b115364c6588fc18c2a21'\n      responses:\n        '200':\n          description: Transaction create successful response\n          content:\n            application/json:\n              schema:\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher_info\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        type: object\n                        properties:\n                          result:\n                            type: string\n                            enum:\n                              - complete\n                              - intermediate\n                          transactions:\n                            type: array\n                            items:\n                              type: object\n                              properties:\n                                inputs:\n                                  type: array\n                                  items:\n                                    type: object\n                                    properties:\n                                      blknum:\n                                        type: integer\n                                        format: int64\n                                      txindex:\n                                        type: integer\n                                        format: int16\n                                      oindex:\n                                        type: integer\n                                        format: int8\n                                      otype:\n                                        type: integer\n                                        format: int8\n                                      utxo_pos:\n                                        type: integer\n                                        format: int256\n                                      owner:\n                                        type: string\n                                      currency:\n                                        type: string\n                                      amount:\n                                        type: integer\n                                        format: int256\n                                      creating_txhash:\n                                        type: string\n                                      spending_txhash:\n                                        type: string\n                                      inserted_at:\n                                        type: string\n                                      updated_at:\n                                        type: string\n                                outputs:\n                                  type: array\n                                  items:\n                                    type: object\n                                    properties:\n                                      amount:\n                                        type: integer\n                                        format: int256\n                                      currency:\n                                        type: string\n                                      owner:\n                                        type: string\n                                fee:\n                                  type: object\n                                  properties:\n                                    amount:\n                                      type: integer\n                                      format: int256\n                                    currency:\n                                      type: string\n                                metadata:\n                                  type: string\n                                txbytes:\n                                  type: string\n                                sign_hash:\n                                  type: string\n                                typed_data:\n                                  type: object\n                                  properties:\n                                    types:\n                                      type: object\n                                      properties:\n                                        EIP712Domain:\n                                          type: array\n                                          items:\n                                            type: object\n                                            properties:\n                                              name:\n                                                type: string\n                                              type:\n                                                type: string\n                                        additionalProperties:\n                                          type: array\n                                          items:\n                                            type: object\n                                            properties:\n                                              name:\n                                                type: string\n                                              type:\n                                                type: string\n                                            required:\n                                              - name\n                                              - type\n                                    primaryType:\n                                      type: string\n                                    domain:\n                                      type: object\n                                      properties:\n                                        name:\n                                          type: string\n                                        salt:\n                                          type: string\n                                        verifyingContract:\n                                          type: string\n                                        version:\n                                          type: string\n                                      required:\n                                        - name\n                                        - salt\n                                        - verifyingContract\n                                        - version\n                                    message:\n                                      type: object\n                                      properties:\n                                        input0:\n                                          type: object\n                                          properties:\n                                            blknum:\n                                              type: integer\n                                              format: int64\n                                            txindex:\n                                              type: integer\n                                              format: int16\n                                            oindex:\n                                              type: integer\n                                              format: int8\n                                        input1:\n                                          type: object\n                                          properties:\n                                            blknum:\n                                              type: integer\n                                              format: int64\n                                            txindex:\n                                              type: integer\n                                              format: int16\n                                            oindex:\n                                              type: integer\n                                              format: int8\n                                        input2:\n                                          type: object\n                                          properties:\n                                            blknum:\n                                              type: integer\n                                              format: int64\n                                            txindex:\n                                              type: integer\n                                              format: int16\n                                            oindex:\n                                              type: integer\n                                              format: int8\n                                        input3:\n                                          type: object\n                                          properties:\n                                            blknum:\n                                              type: integer\n                                              format: int64\n                                            txindex:\n                                              type: integer\n                                              format: int16\n                                            oindex:\n                                              type: integer\n                                              format: int8\n                                        output0:\n                                          type: object\n                                          properties:\n                                            owner:\n                                              type: string\n                                            currency:\n                                              type: string\n                                            amount:\n                                              type: integer\n                                              format: int256\n                                        output1:\n                                          type: object\n                                          properties:\n                                            owner:\n                                              type: string\n                                            currency:\n                                              type: string\n                                            amount:\n                                              type: integer\n                                              format: int256\n                                        output2:\n                                          type: object\n                                          properties:\n                                            owner:\n                                              type: string\n                                            currency:\n                                              type: string\n                                            amount:\n                                              type: integer\n                                              format: int256\n                                        output3:\n                                          type: object\n                                          properties:\n                                            owner:\n                                              type: string\n                                            currency:\n                                              type: string\n                                            amount:\n                                              type: integer\n                                              format: int256\n                                        metadata:\n                                          type: string\n                                      required:\n                                        - input0\n                                        - input1\n                                        - input2\n                                        - input3\n                                        - output0\n                                        - output1\n                                        - output2\n                                        - output3\n                                        - metadata\n                    example:\n                      data:\n                        result: complete\n                        transactions:\n                          - inputs:\n                              - blknum: 123000\n                                txindex: 111\n                                oindex: 0\n                                utxo_pos: 123000001110000\n                                otype: 1\n                                owner: '0xb3256026863eb6ae5b06fa396ab09069784ea8ea'\n                                currency: '0x0000000000000000000000000000000000000000'\n                                amount: 50\n                                creating_txhash: '0x40d65df1c3b1156d813d6bf96d5bd3b5bcf6e6588fc18c2a2ba564c6a64d4320'\n                                spending_txhash: null\n                              - blknum: 277000\n                                txindex: 2340\n                                oindex: 3\n                                utxo_pos: 277000023400003\n                                otype: 1\n                                owner: '0xb3256026863eb6ae5b06fa396ab09069784ea8ea'\n                                currency: '0x0000000000000000000000000000000000000000'\n                                amount: 75\n                                creating_txhash: '0x40d65df1c3b1156d813d6bf96d5bd3b5bcf6e6588fc18c2a2ba564c6a64d4320'\n                                spending_txhash: null\n                            outputs:\n                              - amount: 100\n                                currency: '0x0000000000000000000000000000000000000000'\n                                owner: '0xae8ae48796090ba693af60b5ea6be3686206523b'\n                              - amount: 20\n                                currency: '0x0000000000000000000000000000000000000000'\n                                owner: '0xb3256026863eb6ae5b06fa396ab09069784ea8ea'\n                            fee:\n                              amount: 5\n                              currency: '0x0000000000000000000000000000000000000000'\n                            metadata: '0x5df13a6bf96dbcf6e66d8babd6b55bd40d64d4320c3b115364c6588fc18c2a21'\n                            txbytes: 0x5df13a6bee20000...\n                            sign_hash: '0x7851b951edb0b9e88f0fc80e83461f71d0f4b1d4e44fae7d25a5d4ab6adc5d3d'\n                            typed_data:\n                              types:\n                                EIP712Domain:\n                                  - name: name\n                                    type: string\n                                  - name: version\n                                    type: string\n                                  - name: verifyingContract\n                                    type: address\n                                  - name: salt\n                                    type: bytes32\n                                Transaction:\n                                  - name: input0\n                                    type: Input\n                                  - name: input1\n                                    type: Input\n                                  - name: input2\n                                    type: Input\n                                  - name: input3\n                                    type: Input\n                                  - name: output0\n                                    type: Output\n                                  - name: output1\n                                    type: Output\n                                  - name: output2\n                                    type: Output\n                                  - name: output3\n                                    type: Output\n                                  - name: metadata\n                                    type: bytes32\n                                Input:\n                                  - name: blknum\n                                    type: uint256\n                                  - name: txindex\n                                    type: uint256\n                                  - name: oindex\n                                    type: uint256\n                                Output:\n                                  - name: owner\n                                    type: address\n                                  - name: currency\n                                    type: address\n                                  - name: amount\n                                    type: uint256\n                              primaryType: Transaction\n                              domain:\n                                name: OMG Network\n                                salt: '0xfad5c7f626d80f9256ef01929f3beb96e058b8b4b0e3fe52d84f054c0e2a7a83'\n                                verifyingContract: '0x44de0ec539b8c4a4b530c78620fe8320167f2f74'\n                                version: '1'\n                              message:\n                                input0:\n                                  blknum: 123000\n                                  txindex: 111\n                                  oindex: 0\n                                input1:\n                                  blknum: 277000\n                                  txindex: 2340\n                                  oindex: 3\n                                input2:\n                                  blknum: 0\n                                  txindex: 0\n                                  oindex: 0\n                                input3:\n                                  blknum: 0\n                                  txindex: 0\n                                  oindex: 0\n                                output0:\n                                  owner: '0xae8ae48796090ba693af60b5ea6be3686206523b'\n                                  currency: '0x0000000000000000000000000000000000000000'\n                                  amount: 100\n                                output1:\n                                  owner: '0xb3256026863eb6ae5b06fa396ab09069784ea8ea'\n                                  currency: '0x0000000000000000000000000000000000000000'\n                                  amount: 20\n                                output2:\n                                  owner: '0x0000000000000000000000000000000000000000'\n                                  currency: '0x0000000000000000000000000000000000000000'\n                                  amount: 0\n                                output3:\n                                  owner: '0x0000000000000000000000000000000000000000'\n                                  currency: '0x0000000000000000000000000000000000000000'\n                                  amount: 0\n                                metadata: '0x5df13a6bf96dbcf6e66d8babd6b55bd40d64d4320c3b115364c6588fc18c2a21'\n        '500':\n          description: Returns an internal server error\n          content:\n            application/json:\n              schema:\n                description: The response schema for an error\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher_info\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        description: The object schema for an error\n                        type: object\n                        properties:\n                          object:\n                            type: string\n                          code:\n                            type: string\n                          description:\n                            type: string\n                          messages:\n                            type: object\n                        required:\n                          - object\n                          - code\n                          - description\n                          - messages\n                    required:\n                      - data\n                    example:\n                      success: false\n                      data:\n                        object: error\n                        code: 'server:internal_server_error'\n                        description: Something went wrong on the server\n                        messages:\n                          error_key: error_reason\n  /transaction.merge:\n    post:\n      tags:\n        - Transaction\n      summary: Constructs merge transactions.\n      description: \"- Given address and currency parameters, returns a list of merge transactions for correspondng \\nUTXOs –\\_grouped in ascending order of value. \\n- Given between two and four UTXO positions, returns a merge transaction for the corresponding\\nUTXOs. (These UTXOs must have the same owner and currency)\\n\"\n      operationId: mergeTransaction\n      requestBody:\n        description: The description of merge transaction to be crafted.\n        content:\n          application/json:\n            schema:\n              title: MergeTransactionsBodySchema\n              type: object\n              properties:\n                address:\n                  type: string\n                currency:\n                  type: string\n                utxo_positions:\n                  type: array\n                  items:\n                    type: string\n            examples:\n              UTXO positions:\n                value:\n                  utxo_positions:\n                    - '811000000000001'\n                    - '811000000000002'\n              address and currency:\n                value:\n                  address: '0xb3256026863eb6ae5b06fa396ab09069784ea8ea'\n                  currency: '0x0000000000000000000000000000000000000000'\n      responses:\n        '200':\n          description: Transaction merge successful response\n          content:\n            application/json:\n              schema:\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher_info\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        type: object\n                        properties:\n                          transactions:\n                            type: array\n                            items:\n                              type: object\n                              properties:\n                                inputs:\n                                  type: array\n                                  items:\n                                    type: object\n                                    properties:\n                                      blknum:\n                                        type: integer\n                                        format: int64\n                                      txindex:\n                                        type: integer\n                                        format: int16\n                                      oindex:\n                                        type: integer\n                                        format: int8\n                                      otype:\n                                        type: integer\n                                        format: int8\n                                      utxo_pos:\n                                        type: integer\n                                        format: int256\n                                      owner:\n                                        type: string\n                                      currency:\n                                        type: string\n                                      amount:\n                                        type: integer\n                                        format: int256\n                                      creating_txhash:\n                                        type: string\n                                      spending_txhash:\n                                        type: string\n                                      inserted_at:\n                                        type: string\n                                      updated_at:\n                                        type: string\n                                outputs:\n                                  type: array\n                                  items:\n                                    type: object\n                                    properties:\n                                      amount:\n                                        type: integer\n                                        format: int256\n                                      currency:\n                                        type: string\n                                      owner:\n                                        type: string\n                                fee:\n                                  type: object\n                                  properties:\n                                    amount:\n                                      type: integer\n                                      format: int256\n                                    currency:\n                                      type: string\n                                metadata:\n                                  type: string\n                                txbytes:\n                                  type: string\n                                sign_hash:\n                                  type: string\n                                typed_data:\n                                  type: object\n                                  properties:\n                                    types:\n                                      type: object\n                                      properties:\n                                        EIP712Domain:\n                                          type: array\n                                          items:\n                                            type: object\n                                            properties:\n                                              name:\n                                                type: string\n                                              type:\n                                                type: string\n                                        additionalProperties:\n                                          type: array\n                                          items:\n                                            type: object\n                                            properties:\n                                              name:\n                                                type: string\n                                              type:\n                                                type: string\n                                            required:\n                                              - name\n                                              - type\n                                    primaryType:\n                                      type: string\n                                    domain:\n                                      type: object\n                                      properties:\n                                        name:\n                                          type: string\n                                        salt:\n                                          type: string\n                                        verifyingContract:\n                                          type: string\n                                        version:\n                                          type: string\n                                      required:\n                                        - name\n                                        - salt\n                                        - verifyingContract\n                                        - version\n                                    message:\n                                      type: object\n                                      properties:\n                                        input0:\n                                          type: object\n                                          properties:\n                                            blknum:\n                                              type: integer\n                                              format: int64\n                                            txindex:\n                                              type: integer\n                                              format: int16\n                                            oindex:\n                                              type: integer\n                                              format: int8\n                                        input1:\n                                          type: object\n                                          properties:\n                                            blknum:\n                                              type: integer\n                                              format: int64\n                                            txindex:\n                                              type: integer\n                                              format: int16\n                                            oindex:\n                                              type: integer\n                                              format: int8\n                                        input2:\n                                          type: object\n                                          properties:\n                                            blknum:\n                                              type: integer\n                                              format: int64\n                                            txindex:\n                                              type: integer\n                                              format: int16\n                                            oindex:\n                                              type: integer\n                                              format: int8\n                                        input3:\n                                          type: object\n                                          properties:\n                                            blknum:\n                                              type: integer\n                                              format: int64\n                                            txindex:\n                                              type: integer\n                                              format: int16\n                                            oindex:\n                                              type: integer\n                                              format: int8\n                                        output0:\n                                          type: object\n                                          properties:\n                                            owner:\n                                              type: string\n                                            currency:\n                                              type: string\n                                            amount:\n                                              type: integer\n                                              format: int256\n                                        output1:\n                                          type: object\n                                          properties:\n                                            owner:\n                                              type: string\n                                            currency:\n                                              type: string\n                                            amount:\n                                              type: integer\n                                              format: int256\n                                        output2:\n                                          type: object\n                                          properties:\n                                            owner:\n                                              type: string\n                                            currency:\n                                              type: string\n                                            amount:\n                                              type: integer\n                                              format: int256\n                                        output3:\n                                          type: object\n                                          properties:\n                                            owner:\n                                              type: string\n                                            currency:\n                                              type: string\n                                            amount:\n                                              type: integer\n                                              format: int256\n                                        metadata:\n                                          type: string\n                                      required:\n                                        - input0\n                                        - input1\n                                        - input2\n                                        - input3\n                                        - output0\n                                        - output1\n                                        - output2\n                                        - output3\n                                        - metadata\n                    example:\n                      data:\n                        result: complete\n                        transactions:\n                          - inputs:\n                              - blknum: 123000\n                                txindex: 111\n                                oindex: 0\n                                utxo_pos: 123000001110000\n                                otype: 1\n                                owner: '0xb3256026863eb6ae5b06fa396ab09069784ea8ea'\n                                currency: '0x0000000000000000000000000000000000000000'\n                                amount: 50\n                                creating_txhash: '0x40d65df1c3b1156d813d6bf96d5bd3b5bcf6e6588fc18c2a2ba564c6a64d4320'\n                                spending_txhash: null\n                              - blknum: 277000\n                                txindex: 2340\n                                oindex: 3\n                                utxo_pos: 277000023400003\n                                otype: 1\n                                owner: '0xb3256026863eb6ae5b06fa396ab09069784ea8ea'\n                                currency: '0x0000000000000000000000000000000000000000'\n                                amount: 50\n                                creating_txhash: '0x40d65df1c3b1156d813d6bf96d5bd3b5bcf6e6588fc18c2a2ba564c6a64d4320'\n                                spending_txhash: null\n                            outputs:\n                              - amount: 100\n                                currency: '0x0000000000000000000000000000000000000000'\n                                owner: '0xb3256026863eb6ae5b06fa396ab09069784ea8ea'\n                            fee:\n                              amount: 0\n                              currency: '0x0000000000000000000000000000000000000000'\n                            metadata: '0x5df13a6bf96dbcf6e66d8babd6b55bd40d64d4320c3b115364c6588fc18c2a21'\n                            txbytes: 0x5df13a6bee20000...\n                            sign_hash: '0x7851b951edb0b9e88f0fc80e83461f71d0f4b1d4e44fae7d25a5d4ab6adc5d3d'\n                            typed_data:\n                              types:\n                                EIP712Domain:\n                                  - name: name\n                                    type: string\n                                  - name: version\n                                    type: string\n                                  - name: verifyingContract\n                                    type: address\n                                  - name: salt\n                                    type: bytes32\n                                Transaction:\n                                  - name: input0\n                                    type: Input\n                                  - name: input1\n                                    type: Input\n                                  - name: input2\n                                    type: Input\n                                  - name: input3\n                                    type: Input\n                                  - name: output0\n                                    type: Output\n                                  - name: output1\n                                    type: Output\n                                  - name: output2\n                                    type: Output\n                                  - name: output3\n                                    type: Output\n                                  - name: metadata\n                                    type: bytes32\n                                Input:\n                                  - name: blknum\n                                    type: uint256\n                                  - name: txindex\n                                    type: uint256\n                                  - name: oindex\n                                    type: uint256\n                                Output:\n                                  - name: owner\n                                    type: address\n                                  - name: currency\n                                    type: address\n                                  - name: amount\n                                    type: uint256\n                              primaryType: Transaction\n                              domain:\n                                name: OMG Network\n                                salt: '0xfad5c7f626d80f9256ef01929f3beb96e058b8b4b0e3fe52d84f054c0e2a7a83'\n                                verifyingContract: '0x44de0ec539b8c4a4b530c78620fe8320167f2f74'\n                                version: '1'\n                              message:\n                                input0:\n                                  blknum: 123000\n                                  txindex: 111\n                                  oindex: 0\n                                input1:\n                                  blknum: 277000\n                                  txindex: 2340\n                                  oindex: 3\n                                input2:\n                                  blknum: 0\n                                  txindex: 0\n                                  oindex: 0\n                                input3:\n                                  blknum: 0\n                                  txindex: 0\n                                  oindex: 0\n                                output0:\n                                  owner: '0xb3256026863eb6ae5b06fa396ab09069784ea8ea'\n                                  currency: '0x0000000000000000000000000000000000000000'\n                                  amount: 100\n                                output1:\n                                  owner: '0x0000000000000000000000000000000000000000'\n                                  currency: '0x0000000000000000000000000000000000000000'\n                                  amount: 0\n                                output2:\n                                  owner: '0x0000000000000000000000000000000000000000'\n                                  currency: '0x0000000000000000000000000000000000000000'\n                                  amount: 0\n                                output3:\n                                  owner: '0x0000000000000000000000000000000000000000'\n                                  currency: '0x0000000000000000000000000000000000000000'\n                                  amount: 0\n                                metadata: '0x0000000000000000000000000000000000000000000000000000000000000000'\n        '500':\n          description: Returns an internal server error\n          content:\n            application/json:\n              schema:\n                description: The response schema for an error\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher_info\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        description: The object schema for an error\n                        type: object\n                        properties:\n                          object:\n                            type: string\n                          code:\n                            type: string\n                          description:\n                            type: string\n                          messages:\n                            type: object\n                        required:\n                          - object\n                          - code\n                          - description\n                          - messages\n                    required:\n                      - data\n                    example:\n                      success: false\n                      data:\n                        object: error\n                        code: 'server:internal_server_error'\n                        description: Something went wrong on the server\n                        messages:\n                          error_key: error_reason\n  /transaction.get:\n    post:\n      tags:\n        - Transaction\n      summary: Gets a transaction with the given id.\n      operationId: transaction_get\n      requestBody:\n        description: Id (hash) of the transaction\n        required: true\n        content:\n          application/json:\n            schema:\n              title: GetTransactionBodySchema\n              type: object\n              properties:\n                id:\n                  type: string\n              required:\n                - id\n              example:\n                id: '0x5df13a6bf96dbcf6e66d8babd6b55bd40d64d4320c3b115364c6588fc18c2a21'\n      responses:\n        '200':\n          description: Transaction details succcessful response\n          content:\n            application/json:\n              schema:\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher_info\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        type: object\n                        properties:\n                          txindex:\n                            type: integer\n                            format: int16\n                          txtype:\n                            type: integer\n                            format: int16\n                          txhash:\n                            type: string\n                          metadata:\n                            type: string\n                          txbytes:\n                            type: string\n                          inserted_at:\n                            type: string\n                          updated_at:\n                            type: string\n                          block:\n                            type: object\n                            properties:\n                              blknum:\n                                type: integer\n                                format: int64\n                              hash:\n                                type: string\n                              eth_height:\n                                type: integer\n                                format: int64\n                              timestamp:\n                                type: integer\n                                format: int64\n                              tx_count:\n                                type: integer\n                                format: int64\n                              inserted_at:\n                                type: string\n                              updated_at:\n                                type: string\n                          inputs:\n                            type: array\n                            items:\n                              type: object\n                              properties:\n                                blknum:\n                                  type: integer\n                                  format: int64\n                                txindex:\n                                  type: integer\n                                  format: int16\n                                oindex:\n                                  type: integer\n                                  format: int8\n                                otype:\n                                  type: integer\n                                  format: int8\n                                utxo_pos:\n                                  type: integer\n                                  format: int256\n                                owner:\n                                  type: string\n                                currency:\n                                  type: string\n                                amount:\n                                  type: integer\n                                  format: int256\n                                creating_txhash:\n                                  type: string\n                                spending_txhash:\n                                  type: string\n                                inserted_at:\n                                  type: string\n                                updated_at:\n                                  type: string\n                          outputs:\n                            type: array\n                            items:\n                              type: object\n                              properties:\n                                blknum:\n                                  type: integer\n                                  format: int64\n                                txindex:\n                                  type: integer\n                                  format: int16\n                                oindex:\n                                  type: integer\n                                  format: int8\n                                otype:\n                                  type: integer\n                                  format: int8\n                                utxo_pos:\n                                  type: integer\n                                  format: int256\n                                owner:\n                                  type: string\n                                currency:\n                                  type: string\n                                amount:\n                                  type: integer\n                                  format: int256\n                                creating_txhash:\n                                  type: string\n                                spending_txhash:\n                                  type: string\n                                inserted_at:\n                                  type: string\n                                updated_at:\n                                  type: string\n                    example:\n                      data:\n                        txindex: 5113\n                        txtype: 1\n                        txhash: '0x5df13a6bf96dbcf6e66d8babd6b55bd40d64d4320c3b115364c6588fc18c2a21'\n                        metadata: '0x00000000000000000000000000000000000000000000000000000048656c6c6f'\n                        txbytes: 0x5df13a6bee20000...\n                        inserted_at: '2020-02-10T12:07:32Z'\n                        updated_at: '2020-02-15T04:07:57Z'\n                        block:\n                          timestamp: 1540365586\n                          hash: '0x0017372421f9a92bedb7163310918e623557ab5310befc14e67212b660c33bec'\n                          eth_height: 97424\n                          blknum: 68290000\n                          inserted_at: '2020-02-10T12:07:32Z'\n                          updated_at: '2020-02-15T04:07:57Z'\n                        inputs:\n                          - blknum: 1000\n                            txindex: 111\n                            oindex: 0\n                            otype: 1\n                            utxo_pos: 1000001110000\n                            owner: '0xb3256026863eb6ae5b06fa396ab09069784ea8ea'\n                            currency: '0x0000000000000000000000000000000000000000'\n                            amount: 10\n                            creating_txhash: '0x40d65df1c3b1156d813d6bf96d5bd3b5bcf6e6588fc18c2a2ba564c6a64d4320'\n                            spending_txhash: '0x5df13a6bf96dbcf6e66d8babd6b55bd40d64d4320c3b115364c6588fc18c2a21'\n                            inserted_at: '2020-02-10T12:07:32Z'\n                            updated_at: '2020-02-15T04:07:57Z'\n                        outputs:\n                          - blknum: 68290000\n                            txindex: 5113\n                            oindex: 0\n                            otype: 1\n                            utxo_pos: 68290000051130000\n                            owner: '0xae8ae48796090ba693af60b5ea6be3686206523b'\n                            currency: '0x0000000000000000000000000000000000000000'\n                            amount: 2\n                            creating_txhash: '0x5df13a6bf96dbcf6e66d8babd6b55bd40d64d4320c3b115364c6588fc18c2a21'\n                            spending_txhash: null\n                            inserted_at: '2020-02-10T12:07:32Z'\n                            updated_at: '2020-02-15T04:07:57Z'\n                          - blknum: 68290000\n                            txindex: 5113\n                            oindex: 1\n                            otype: 1\n                            utxo_pos: 68290000051130000\n                            owner: '0xb3256026863eb6ae5b06fa396ab09069784ea8ea'\n                            currency: '0x0000000000000000000000000000000000000000'\n                            amount: 7\n                            creating_txhash: '0x5df13a6bf96dbcf6e66d8babd6b55bd40d64d4320c3b115364c6588fc18c2a21'\n                            spending_txhash: null\n                            inserted_at: '2020-02-10T12:07:32Z'\n                            updated_at: '2020-02-15T04:07:57Z'\n        '500':\n          description: Returns an internal server error\n          content:\n            application/json:\n              schema:\n                description: The response schema for an error\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher_info\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        description: The object schema for an error\n                        type: object\n                        properties:\n                          object:\n                            type: string\n                          code:\n                            type: string\n                          description:\n                            type: string\n                          messages:\n                            type: object\n                        required:\n                          - object\n                          - code\n                          - description\n                          - messages\n                    required:\n                      - data\n                    example:\n                      success: false\n                      data:\n                        object: error\n                        code: 'server:internal_server_error'\n                        description: Something went wrong on the server\n                        messages:\n                          error_key: error_reason\n  /transaction.submit_typed:\n    post:\n      tags:\n        - Transaction\n      summary: Sends EIP-712 formatted transaction to Child chain.\n      description: |\n        Request to this method is the same as to Web3 `eth_signTypedData` with additional `signatures` array. The `/transaction.create` `typed_data` field can be used to prepare transaction. The same conditions are met as with security-critical `/transaction.submit`\n      operationId: submit_typed\n      requestBody:\n        description: Transaction as for `eth_signTypedData` along with signatures\n        required: true\n        content:\n          application/json:\n            schema:\n              title: TransactionSubmitTypedBodySchema\n              allOf:\n                - type: object\n                  properties:\n                    types:\n                      type: object\n                      properties:\n                        EIP712Domain:\n                          type: array\n                          items:\n                            type: object\n                            properties:\n                              name:\n                                type: string\n                              type:\n                                type: string\n                        additionalProperties:\n                          type: array\n                          items:\n                            type: object\n                            properties:\n                              name:\n                                type: string\n                              type:\n                                type: string\n                            required:\n                              - name\n                              - type\n                    primaryType:\n                      type: string\n                    domain:\n                      type: object\n                      properties:\n                        name:\n                          type: string\n                        salt:\n                          type: string\n                        verifyingContract:\n                          type: string\n                        version:\n                          type: string\n                      required:\n                        - name\n                        - salt\n                        - verifyingContract\n                        - version\n                    message:\n                      type: object\n                      properties:\n                        input0:\n                          type: object\n                          properties:\n                            blknum:\n                              type: integer\n                              format: int64\n                            txindex:\n                              type: integer\n                              format: int16\n                            oindex:\n                              type: integer\n                              format: int8\n                        input1:\n                          type: object\n                          properties:\n                            blknum:\n                              type: integer\n                              format: int64\n                            txindex:\n                              type: integer\n                              format: int16\n                            oindex:\n                              type: integer\n                              format: int8\n                        input2:\n                          type: object\n                          properties:\n                            blknum:\n                              type: integer\n                              format: int64\n                            txindex:\n                              type: integer\n                              format: int16\n                            oindex:\n                              type: integer\n                              format: int8\n                        input3:\n                          type: object\n                          properties:\n                            blknum:\n                              type: integer\n                              format: int64\n                            txindex:\n                              type: integer\n                              format: int16\n                            oindex:\n                              type: integer\n                              format: int8\n                        output0:\n                          type: object\n                          properties:\n                            owner:\n                              type: string\n                            currency:\n                              type: string\n                            amount:\n                              type: integer\n                              format: int256\n                        output1:\n                          type: object\n                          properties:\n                            owner:\n                              type: string\n                            currency:\n                              type: string\n                            amount:\n                              type: integer\n                              format: int256\n                        output2:\n                          type: object\n                          properties:\n                            owner:\n                              type: string\n                            currency:\n                              type: string\n                            amount:\n                              type: integer\n                              format: int256\n                        output3:\n                          type: object\n                          properties:\n                            owner:\n                              type: string\n                            currency:\n                              type: string\n                            amount:\n                              type: integer\n                              format: int256\n                        metadata:\n                          type: string\n                      required:\n                        - input0\n                        - input1\n                        - input2\n                        - input3\n                        - output0\n                        - output1\n                        - output2\n                        - output3\n                        - metadata\n                - type: object\n                  properties:\n                    signatures:\n                      type: array\n                      items:\n                        type: string\n                  required:\n                    - domain\n                    - message\n                    - signatures\n                  example:\n                    domain:\n                      name: OMG Network\n                      salt: '0xfad5c7f626d80f9256ef01929f3beb96e058b8b4b0e3fe52d84f054c0e2a7a83'\n                      verifyingContract: '0x44de0ec539b8c4a4b530c78620fe8320167f2f74'\n                      version: '1'\n                    message:\n                      input0:\n                        blknum: 1\n                        txindex: 0\n                        oindex: 0\n                      input1:\n                        blknum: 1000\n                        txindex: 1\n                        oindex: 1\n                      input2:\n                        blknum: 0\n                        txindex: 0\n                        oindex: 0\n                      input3:\n                        blknum: 0\n                        txindex: 0\n                        oindex: 0\n                      output0:\n                        owner: '0x0527a37aa7081efcf405bd7c8fe36b01e91df27d'\n                        currency: '0x0000000000000000000000000000000000000000'\n                        amount: 100\n                      output1:\n                        owner: '0x3b9f4c1dd26e0be593373b1d36cee2008cbeb837'\n                        currency: '0x0000000000000000000000000000000000000000'\n                        amount: 10\n                      output2:\n                        owner: '0x0000000000000000000000000000000000000000'\n                        currency: '0x0000000000000000000000000000000000000000'\n                        amount: 0\n                      output3:\n                        owner: '0x0000000000000000000000000000000000000000'\n                        currency: '0x0000000000000000000000000000000000000000'\n                        amount: 0\n                      metadata: '0x0000000000000000000000000000000000000000000000000000000000000000'\n                    signatures:\n                      - 0x6bfb9b2dbe32...\n      responses:\n        '200':\n          description: Transaction submission successful response\n          content:\n            application/json:\n              schema:\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        type: object\n                        properties:\n                          blknum:\n                            type: integer\n                            format: int64\n                          txindex:\n                            type: integer\n                            format: int16\n                          txhash:\n                            type: string\n                    example:\n                      data:\n                        blknum: 123000\n                        txindex: 111\n                        txhash: '0xbdf562c24ace032176e27621073df58ce1c6f65de3b5932343b70ba03c72132d'\n        '500':\n          description: Returns an internal server error\n          content:\n            application/json:\n              schema:\n                description: The response schema for an error\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher_info\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        description: The object schema for an error\n                        type: object\n                        properties:\n                          object:\n                            type: string\n                          code:\n                            type: string\n                          description:\n                            type: string\n                          messages:\n                            type: object\n                        required:\n                          - object\n                          - code\n                          - description\n                          - messages\n                    required:\n                      - data\n                    example:\n                      success: false\n                      data:\n                        object: error\n                        code: 'server:internal_server_error'\n                        description: Something went wrong on the server\n                        messages:\n                          error_key: error_reason\n  /fees.all:\n    post:\n      tags:\n        - Fees\n      summary: This endpoint retrieves the list of fee tokens currently supported by the childchain and the current amount needed to perform a transaction.\n      operationId: fees_all\n      requestBody:\n        description: 'An optional array of currencies to filter, raises an error if one of the currencies is not supported.'\n        required: false\n        content:\n          application/json:\n            schema:\n              title: FeesAllBodySchema\n              type: object\n              properties:\n                currencies:\n                  type: array\n                  items:\n                    type: string\n                tx_types:\n                  type: array\n                  items:\n                    type: integer\n              example:\n                currencies:\n                  - '0x0000000000000000000000000000000000000000'\n                tx_types:\n                  - 1\n      responses:\n        '200':\n          description: List of all supported fees response\n          content:\n            application/json:\n              schema:\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher_info\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        type: object\n                        additionalProperties:\n                          type: array\n                          items:\n                            type: object\n                            properties:\n                              currency:\n                                type: string\n                              amount:\n                                type: integer\n                                format: int256\n                              subunit_to_unit:\n                                type: integer\n                                format: int256\n                              pegged_currency:\n                                type: string\n                              pegged_amount:\n                                type: integer\n                                format: int256\n                              pegged_subunit_to_unit:\n                                type: integer\n                                format: int256\n                              updated_at:\n                                type: string\n                                format: date-time\n                    example:\n                      data:\n                        '1':\n                          - currency: '0x0000000000000000000000000000000000000000'\n                            amount: 220000000000000\n                            subunit_to_unit: 1000000000000000000\n                            pegged_currency: USD\n                            pegged_amount: 4\n                            pegged_subunit_to_unit: 100\n                            updated_at: '2019-01-01T10:10:10+00:00'\n        '500':\n          description: Returns an internal server error\n          content:\n            application/json:\n              schema:\n                description: The response schema for an error\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher_info\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        description: The object schema for an error\n                        type: object\n                        properties:\n                          object:\n                            type: string\n                          code:\n                            type: string\n                          description:\n                            type: string\n                          messages:\n                            type: object\n                        required:\n                          - object\n                          - code\n                          - description\n                          - messages\n                    required:\n                      - data\n                    example:\n                      success: false\n                      data:\n                        object: error\n                        code: 'server:internal_server_error'\n                        description: Something went wrong on the server\n                        messages:\n                          error_key: error_reason\n  /stats.get:\n    post:\n      tags:\n        - Stats\n      summary: Retrieves network statistics\n      description: |\n        Retrieves transaction count, block count and average block interval, both for all time and the last 24 hours.\n      operationId: stats_get\n      responses:\n        '200':\n          description: Stats Successful Response\n          content:\n            application/json:\n              schema:\n                allOf:\n                  - description: The response schema for a successful list operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: array\n                        items:\n                          type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher_info\n                      version: 1.0+abcdefa\n                      success: true\n                      data: []\n                  - type: object\n                    properties:\n                      data:\n                        type: object\n                        items:\n                          type: object\n                          properties:\n                            transaction_count:\n                              type: object\n                              properties:\n                                all_time:\n                                  type: integer\n                                  format: int64\n                                last_24_hours:\n                                  type: integer\n                                  format: int64\n                            block_count:\n                              type: object\n                              properties:\n                                all_time:\n                                  type: integer\n                                  format: int64\n                                last_24_hours:\n                                  type: integer\n                                  format: int64\n                            average_block_interval:\n                              type: object\n                              properties:\n                                all_time:\n                                  type: number\n                                  format: int64\n                                last_24_hours:\n                                  type: number\n                                  format: int64\n                    example:\n                      data:\n                        transaction_count:\n                          all_time: 4\n                          last_24_hours: 2\n                        block_count:\n                          all_time: 2\n                          last_24_hours: 1\n                        average_block_interval:\n                          all_time: 100\n                          last_24_hours: null\n        '500':\n          description: Returns an internal server error\n          content:\n            application/json:\n              schema:\n                description: The response schema for an error\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher_info\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        description: The object schema for an error\n                        type: object\n                        properties:\n                          object:\n                            type: string\n                          code:\n                            type: string\n                          description:\n                            type: string\n                          messages:\n                            type: object\n                        required:\n                          - object\n                          - code\n                          - description\n                          - messages\n                    required:\n                      - data\n                    example:\n                      success: false\n                      data:\n                        object: error\n                        code: 'server:internal_server_error'\n                        description: Something went wrong on the server\n                        messages:\n                          error_key: error_reason\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs/account/paths.yaml",
    "content": "account.get_exitable_utxos:\n  post:\n    tags:\n      - Account\n    summary: Gets all utxos belonging to the given address.\n    description: >\n      **Note:** this is a performance intensive call and should only be used if the chain is byzantine and the user needs to retrieve utxo information to be able to exit.\n      Normally an application should use the Informational API's [Account - Get Utxos](http://TODO) instead.\n      This version is provided in case the Informational API is not available.\n    operationId: account_get_exitable_utxos\n    requestBody:\n      $ref: 'request_bodies.yaml#/AddressBodySchema'\n    responses:\n      200:\n        $ref: 'responses.yaml#/AccountUtxoResponse'\n      500:\n        $ref: '../responses.yaml#/InternalServerError'\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs/account/request_bodies.yaml",
    "content": "AddressBodySchema:\n  description: HEX-encoded address of the account\n  required: true\n  content:\n    application/json:\n      schema:\n        title: 'AddressBodySchema'\n        type: object\n        properties:\n          address:\n            type: string\n             \n        required:\n          - address\n        example:\n          address: '0xb3256026863eb6ae5b06fa396ab09069784ea8ea'\n          "
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs/account/response_schemas.yaml",
    "content": "AccountUtxoResponseSchema:\n  allOf:\n  - $ref: '../response_schemas.yaml#/WatcherBaseResponseSchema'\n  - type: object\n    properties:\n      data:\n        type: array\n        items:\n          $ref: 'schemas.yaml#/AccountUtxoSchema'\n    example:\n      data:\n      -\n        blknum: 123000\n        txindex: 111\n        oindex: 0\n        otype: 1\n        utxo_pos: 123000001110000\n        owner: '0xb3256026863eb6ae5b06fa396ab09069784ea8ea'\n        currency: '0x0000000000000000000000000000000000000000'\n        amount: 10\n\n        \n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs/account/responses.yaml",
    "content": "AccountUtxoResponse:\n  description: Account utxos succcessful response\n  content:\n    application/json:\n      schema:\n        $ref: 'response_schemas.yaml#/AccountUtxoResponseSchema'"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs/account/schemas.yaml",
    "content": "AccountUtxoSchema:\n  type: object\n  properties:\n    blknum:\n      type: integer\n      format: int64\n    txindex:\n      type: integer\n      format: int16\n    otype:\n      type: integer\n      format: int16\n    oindex:\n      type: integer\n      format: int8\n    utxo_pos:\n      type: integer\n      format: int256\n    owner:\n      type: string\n    currency:\n      type: string\n    amount:\n      type: integer\n      format: int256\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs/alarm/alarms_schema.yml",
    "content": "components:\n  schemas:\n    ethereum_connection_error:\n      type: object\n      properties:\n        ethereum_connection_error:\n          type: object\n          properties:\n            node:\n              type: string\n            reporter:\n              type: string\n    ethereum_stalled_sync:\n      type: object\n      properties:\n        ethereum_stalled_sync:\n          type: object\n          properties:\n            ethereum_height:\n              type: integer\n              minimum: 0\n            synced_at:\n              type: string\n              format: date-time\n    invalid_fee_source:\n      type: object\n      properties:\n        invalid_fee_source:\n          type: object\n          properties:\n            node:\n              type: string\n            reporter:\n              type: string\n    statsd_client_connection:\n      type: object\n      properties:\n        statsd_client_connection:\n          type: object\n          properties:\n            node:\n              type: string\n            reporter:\n              type: string\n    system_memory_high_watermark:\n      type: object\n      properties:\n        statsd_client_connection:\n          type: array\n          items:\n            type: string\n          default: []\n    disk_almost_full:\n      type: object\n      properties:\n        disk_almost_full:\n          type: string\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs/alarm/paths.yaml",
    "content": "alarm.get:\n  get:\n    tags:\n      - Alarm\n    summary: Provides alarms related to system memory, cpu and storage and application specific alarms.\n    description: >\n      **Note:** Service operator alarms.\n    operationId: alarm_get\n    responses:\n      200:\n        $ref: 'responses.yaml#/AlarmResponse'\n      500:\n        $ref: '../responses.yaml#/InternalServerError'\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs/alarm/response_schemas.yaml",
    "content": "AlarmResponseSchema:\n  allOf:\n  - $ref: '../response_schemas.yaml#/WatcherBaseResponseSchema'\n  - type: object\n    properties:\n      data:\n        type: array\n        items:\n          $ref: 'schemas.yaml#/AlarmSchema'\n    example:\n      data:\n      -\n        disk_almost_full: \"/dev/null\"\n        ethereum_connection_error: {}\n        ethereum_stalled_sync: {}\n        system_memory_high_watermark: []\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs/alarm/responses.yaml",
    "content": "AlarmResponse:\n  description: System alarms\n  content:\n    application/json:\n      schema:\n        $ref: 'response_schemas.yaml#/AlarmResponseSchema'"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs/alarm/schemas.yaml",
    "content": "AlarmSchema:\n  type: array\n  items:\n    anyOf:\n      - $ref: 'alarms_schema.yml#/components/schemas/ethereum_connection_error'\n      - $ref: 'alarms_schema.yml#/components/schemas/ethereum_stalled_sync'\n      - $ref: 'alarms_schema.yml#/components/schemas/invalid_fee_source'\n      - $ref: 'alarms_schema.yml#/components/schemas/statsd_client_connection'\n      - $ref: 'alarms_schema.yml#/components/schemas/system_memory_high_watermark'\n      - $ref: 'alarms_schema.yml#/components/schemas/disk_almost_full'\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs/batch_transaction/paths.yaml",
    "content": "transaction.batch_submit:\n  post:\n    tags:\n      - Transaction\n    summary: This endpoint submits an array of signed transaction to the child chain.\n    description: >\n      Normally you should call the Watcher's Transaction - Submit instead of this.\n      The Watcher's version performs various security and validation checks (TO DO) before submitting the transaction,\n      so is much safer. However, if the Watcher is not available this version exists.\n    operationId: batch_submit\n    requestBody:\n      $ref: 'request_bodies.yaml#/TransactionBatchSubmitBodySchema'\n    responses:\n      200:\n        $ref: 'responses.yaml#/TransactionBatchSubmitResponse'\n      500:\n        $ref: '../responses.yaml#/InternalServerError'\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs/batch_transaction/request_bodies.yaml",
    "content": "TransactionBatchSubmitBodySchema:\n  description: Array of signed transactions, RLP-encoded to bytes, and HEX-encoded to string\n  required: true\n  content:\n    application/json:\n      schema:\n        title: 'TransactionBatchSubmitBodySchema'\n        type: object\n        properties:\n          transactions:\n            type: array\n            items:\n              type: string\n        required:\n          - transactions\n        example:\n          transactions: ['0xf8d083015ba98080808080940000...', '0xf8d083a15ba98080808080920000...']\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs/batch_transaction/response_schemas.yaml",
    "content": "TransactionBatchSubmitResponseSchema:\n  allOf:\n  - $ref: '../response_schemas.yaml#/WatcherBaseResponseSchema'\n  - type: object\n    properties:\n      data:\n        type: array\n        $ref: 'schemas.yaml#/TransactionBatchSubmitSchema '\n    example:\n      data:\n      -\n        blknum: 123000\n        txindex: 111\n        txhash: '0xbdf562c24ace032176e27621073df58ce1c6f65de3b5932343b70ba03c72132d'\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs/batch_transaction/responses.yaml",
    "content": "TransactionBatchSubmitResponse:\n  description: Transaction batch submission successful response\n  content:\n    application/json:\n      schema:\n        $ref: 'response_schemas.yaml#/TransactionBatchSubmitResponseSchema'\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs/batch_transaction/schemas.yaml",
    "content": "TransactionBatchSubmitSchema:\n  type: array\n  items:\n    type: object\n    properties:\n      blknum:\n        type: integer\n        format: int64\n      txindex:\n        type: integer\n        format: int16\n      txhash:\n        type: string\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs/block/paths.yaml",
    "content": "block.validate:\n  post:\n    tags:\n      - Block\n    summary: Verifies the stateless validity of a block. \n    description: |\n      - Verifies that given Merkle root matches reconstructed Merkle root.\n      - Verifies that (payment and fee) transactions  are correctly formed.\n      - Verifies that there are no duplicate inputs at the block level.\n      - Verifies that the number of transactions falls within the accepted range.\n      - Verifies that fee transactions are correctly placed and unique per currency.\n    operationId: validate\n    requestBody:\n      $ref: 'request_bodies.yaml#/BlockValidateBodySchema'\n    responses:\n      200:\n        $ref: 'responses.yaml#/BlockValidateResponse'\n      500:\n        $ref: '../responses.yaml#/InternalServerError'\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs/block/request_bodies.yaml",
    "content": "BlockValidateBodySchema:\n  description: Block object with a hash, number and array of hexadecimal transaction bytes.\n  required: true\n  content:\n    application/json:\n      schema:\n        title: 'BlockValidateBodySchema'\n        type: object\n        properties:\n          hash:\n            type: string\n          transactions: \n            type: array\n            items: \n              type: string\n          number: \n            type: integer\n        required:\n          - hash\n          - transactions\n          - number\n        example:\n          number: 1000\n          hash: '0xf8d083015ba98080808080940000...'\n          transactions: [\"0xf8c0f843b841fc6dbf49a4baa783ec576291f6083be5ea...\", \"0xf852c003eeed02eb94916f3753bd53e124d6d565ef1701...\" ]\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs/block/response_schemas.yaml",
    "content": "BlockValidateResponseSchema:\n  allOf:\n  - $ref: '../response_schemas.yaml#/WatcherBaseResponseSchema'\n  - type: object\n    properties:\n      data:\n        type: object\n        $ref: 'schemas.yaml#/BlockValidateSchema'\n    example:\n      data:\n        valid: false\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs/block/responses.yaml",
    "content": "BlockValidateResponse:\n  description: Successful response to calling /block.validate\n  content:\n    application/json:\n      schema:\n        $ref: 'response_schemas.yaml#/BlockValidateResponseSchema'\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs/block/schemas.yaml",
    "content": "BlockValidateSchema:\n  type: object\n  properties:\n    valid:\n      type: boolean\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs/configuration/configuration_schema.yml",
    "content": "components:\n  schemas:\n    deposit_finality_margin:\n      type: integer\n    contract_semver:\n      type: string\n    exit_processor_sla_margin:\n      type: integer\n    network:\n      type: string\n      "
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs/configuration/paths.yaml",
    "content": "configuration.get:\n  get:\n    tags:\n      - Configuration\n    summary: Provides configuration values\n    description: >\n      **Note:** Configuration values.\n    operationId: configuration_get\n    responses:\n      200:\n        $ref: 'responses.yaml#/ConfigurationResponse'\n      500:\n        $ref: '../responses.yaml#/InternalServerError'\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs/configuration/response_schemas.yaml",
    "content": "ConfigurationResponseSchema:\n  allOf:\n  - $ref: '../response_schemas.yaml#/WatcherBaseResponseSchema'\n  - type: object\n    properties:\n      data:\n        type: array\n        items:\n          $ref: 'schemas.yaml#/ConfigurationSchema'\n    example:\n      data:\n      -\n        deposit_finality_margin: 10\n        contract_semver: \"1.0.0.1+a1s29s8\"\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs/configuration/responses.yaml",
    "content": "ConfigurationResponse:\n  description: Configuration response\n  content:\n    application/json:\n      schema:\n        $ref: 'response_schemas.yaml#/ConfigurationResponseSchema'"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs/configuration/schemas.yaml",
    "content": "ConfigurationSchema:\n  type: object\n  properties:\n    deposit_finality_margin:\n      type: integer\n      format: int256\n    contract_semver:\n      type: string\n       \n    exit_processor_sla_margin:\n      type: integer\n      format: int256\n    network:\n      type: string\n       "
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs/in_flight_exit/paths.yaml",
    "content": "in_flight_exit.get_data:\n  post:\n    tags:\n      - InFlightExit\n    summary: Gets exit data for an in-flight exit.\n    description: >\n      Exit data are arguments to `startInFlightExit` root chain contract function.\n    operationId: in_flight_exit_get_data\n    requestBody:\n      $ref: 'request_bodies.yaml#/InFlightExitTxBytesBodySchema'\n    responses:\n      200:\n        $ref: 'responses.yaml#/GetInFlightExitDataResponse'\n      500:\n        $ref: '../responses.yaml#/InternalServerError'\n\nin_flight_exit.get_competitor:\n  post:\n    tags:\n      - InFlightExit\n    summary: Returns a competitor to an in-flight exit.\n    description: Note that if the competing transaction has not been put into a block `competing_tx_pos` and `competing_proof` will not be returned.\n    operationId: in_flight_exit_get_competitor\n    requestBody:\n      $ref: 'request_bodies.yaml#/InFlightExitTxBytesBodySchema'\n    responses:\n      200:\n        $ref: 'responses.yaml#/GetCompetitorResponse'\n      500:\n        $ref: '../responses.yaml#/InternalServerError'\n\nin_flight_exit.prove_canonical:\n  post:\n    tags:\n      - InFlightExit\n    summary: Proves transaction is canonical.\n    description: To respond to a challenge to an in-flight exit, this proves that the transaction has been put into a block (and therefore is canonical).\n    operationId: in_flight_exit_prove_canonical\n    requestBody:\n      $ref: 'request_bodies.yaml#/InFlightExitTxBytesBodySchema'\n    responses:\n      200:\n        $ref: 'responses.yaml#/ProveCanonicalResponse'\n      500:\n        $ref: '../responses.yaml#/InternalServerError'\n\nin_flight_exit.get_input_challenge_data:\n  post:\n    tags:\n      - InFlightExit\n    summary: Gets the data to challenge an invalid input piggybacked on an in-flight exit.\n    description: To respond to invalid piggybacked input in non-canonical in-flight transaction provides data needed to challenge it, e.g. transaction that spent this input and signature.\n    operationId: in_flight_exit_get_input_challenge_data\n    requestBody:\n      $ref: 'request_bodies.yaml#/InFlightExitInputChallengeDataBodySchema'\n    responses:\n      200:\n        $ref: 'responses.yaml#/InputChallengeDataResponse'\n      500:\n        $ref: '../responses.yaml#/InternalServerError'\n\nin_flight_exit.get_output_challenge_data:\n  post:\n    tags:\n      - InFlightExit\n    summary: Gets the data to challenge an invalid output piggybacked on an in-flight exit.\n    description: To respond to invalid piggybacked output in canonical in-flight transaction provides data needed to challenge it, e.g. in-flight transaction inclusion proof, transaction that spent this output and signature.\n    operationId: in_flight_exit_get_output_challenge_data\n    requestBody:\n      $ref: 'request_bodies.yaml#/InFlightExitOutputChallengeDataBodySchema'\n    responses:\n      200:\n        $ref: 'responses.yaml#/OutputChallengeDataResponse'\n      500:\n        $ref: '../responses.yaml#/InternalServerError'\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs/in_flight_exit/request_bodies.yaml",
    "content": "InFlightExitTxBytesBodySchema:\n  description: In-flight transaction bytes body\n  required: true\n  content:\n    application/json:\n      schema:\n        title: 'InFlightExitTxBytesBodySchema'\n        type: object\n        properties:\n          txbytes:\n            type: string\n             \n        required:\n          - txbytes\n        example:\n          txbytes: '0xf3170101c0940000...'\n\nInFlightExitInputChallengeDataBodySchema:\n  description: In-flight transaction bytes and invalid input index\n  required: true\n  content:\n    application/json:\n      schema:\n        title: 'InFlightExitInputChallengeDataBodySchema'\n        type: object\n        properties:\n          txbytes:\n            type: string\n             \n          input_index:\n            type: integer\n            format: int8\n        required:\n          - txbytes\n          - input_index\n        example:\n          txbytes: '0xf3170101c0940000...'\n          input_index: 1\n\nInFlightExitOutputChallengeDataBodySchema:\n  description: In-flight transaction bytes and invalid output index\n  required: true\n  content:\n    application/json:\n      schema:\n        title: 'InFlightExitOutputChallengeDataBodySchema'\n        type: object\n        properties:\n          txbytes:\n            type: string\n             \n          output_index:\n            type: integer\n            format: int8\n        required:\n          - txbytes\n          - output_index\n        example:\n          txbytes: '0xf3170101c0940000...'\n          output_index: 0\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs/in_flight_exit/response_schemas.yaml",
    "content": "GetInFlightExitDataResponseSchema:\n  allOf:\n  - $ref: '../response_schemas.yaml#/WatcherBaseResponseSchema'\n  - type: object\n    properties:\n      data:\n        type: object\n        $ref: 'schemas.yaml#/InFlightExitDataSchema'\n    example:\n      data:\n        in_flight_tx: '0xf3170101c0940000...'\n        input_txs:\n        - '0xa3470101c0940000...'\n        input_txs_inclusion_proofs:\n        - '0xcedb8b31d1e4...'\n        in_flight_tx_sigs:\n        - '0x6bfb9b2dbe32...'\n        input_utxos_pos:\n        - 300010002001\n\nGetCompetitorResponseSchema:\n  allOf:\n  - $ref: '../response_schemas.yaml#/WatcherBaseResponseSchema'\n  - type: object\n    properties:\n      data:\n        type: object\n        $ref: 'schemas.yaml#/CompetitorSchema'\n    example:\n      data:\n        in_flight_txbytes: '0xf3170101c0940000...'\n        in_flight_input_index: 1\n        competing_txbytes: '0x5df13a6bee20000...'\n        competing_input_index: 1\n        competing_sig: '0xa3470101c0940000...'\n        competing_tx_pos: 26000003920000\n        competing_proof: '0xcedb8b31d1e4...'\n        input_tx: '0xaaa70101c0940000...'\n        input_utxo_pos: 300010002001\n\nProveCanonicalResponseSchema:\n  allOf:\n  - $ref: '../response_schemas.yaml#/WatcherBaseResponseSchema'\n  - type: object\n    properties:\n      data:\n        type: object\n        $ref: 'schemas.yaml#/ProveCanonicalSchema'\n    example:\n      data:\n        in_flight_txbytes: '0xf3170101c0940000...'\n        in_flight_tx_pos: 26000003920000\n        in_flight_proof: '0xcedb8b31d1e4...'\n\nInputChallengeDataResponseSchema:\n  allOf:\n  - $ref: '../response_schemas.yaml#/WatcherBaseResponseSchema'\n  - type: object\n    properties:\n      data:\n        type: object\n        $ref: 'schemas.yaml#/InputChallengeDataSchema'\n    example:\n      data:\n        in_flight_txbytes: '0xf3170101c0940000...'\n        in_flight_input_index: 1\n        spending_txbytes: '0x5df13a6bee20000...'\n        spending_input_index: 1\n        spending_sig: '0xa3470101c0940000...'\n        input_tx: '0xaaa70101c0940000...'\n        input_utxo_pos: 300010002001\n\nOutputChallengeDataResponseSchema:\n  allOf:\n  - $ref: '../response_schemas.yaml#/WatcherBaseResponseSchema'\n  - type: object\n    properties:\n      data:\n        type: object\n        $ref: 'schemas.yaml#/OutputChallengeDataSchema'\n    example:\n      data:\n        in_flight_txbytes: '0xf3170101c0940000...'\n        in_flight_output_pos: 21000634002\n        in_flight_proof: '0xcedb8b31d1e4...'\n        spending_txbytes: '0x5df13a6bee20000...'\n        spending_input_index: 1\n        spending_sig: '0xa3470101c0940000...'\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs/in_flight_exit/responses.yaml",
    "content": "GetInFlightExitDataResponse:\n  description: Get in-flight exit successful response\n  content:\n    application/json:\n      schema:\n        $ref: 'response_schemas.yaml#/GetInFlightExitDataResponseSchema'\n\nGetCompetitorResponse:\n  description: Get competitor successful response\n  content:\n    application/json:\n      schema:\n        $ref: 'response_schemas.yaml#/GetCompetitorResponseSchema'\n\nProveCanonicalResponse:\n  description: Prove canonical successful response\n  content:\n    application/json:\n      schema:\n        $ref: 'response_schemas.yaml#/ProveCanonicalResponseSchema'\n\nInputChallengeDataResponse:\n  description: Get input challenge successful response\n  content:\n    application/json:\n      schema:\n        $ref: 'response_schemas.yaml#/InputChallengeDataResponseSchema'\n\nOutputChallengeDataResponse:\n  description: Get output challenge successful response\n  content:\n    application/json:\n      schema:\n        $ref: 'response_schemas.yaml#/OutputChallengeDataResponseSchema'\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs/in_flight_exit/schemas.yaml",
    "content": "InFlightExitDataSchema:\n  description: The object schema for an in flight exit\n  properties:\n    in_flight_tx:\n      type: string\n       \n    input_txs:\n      type: array\n      items:\n        type: string\n         \n    input_txs_inclusion_proofs:\n      type: array\n      items:\n        type: string\n         \n    in_flight_tx_sigs:\n      type: array\n      items:\n        type: string\n         \n    input_utxos_pos:\n      type: array\n      items:\n        type: integer\n        format: int256\n\nCompetitorSchema:\n  description: The object schema for a competitor\n  properties:\n    in_flight_txbytes:\n      type: string\n       \n    in_flight_input_index:\n      type: integer\n      format: int8\n    competing_txbytes:\n      type: string\n       \n    competing_input_index:\n      type: integer\n      format: int8\n    competing_sig:\n      type: string\n       \n    competing_tx_pos:\n      type: integer\n      format: int256\n    competing_proof:\n      type: string\n       \n    input_tx:\n      type: string\n       \n    input_utxo_pos:\n      type: integer\n      format: int256\n\nProveCanonicalSchema:\n  description: The object schema for a canonical proof\n  properties:\n    in_flight_txbytes:\n      type: string\n       \n    in_flight_tx_pos:\n      type: integer\n      format: int256\n    in_flight_proof:\n      type: string\n       \n\n\nInputChallengeDataSchema:\n  description: The object schema for an input challenge data\n  properties:\n    in_flight_txbytes:\n      type: string\n       \n    in_flight_input_index:\n      type: integer\n      format: int8\n    spending_txbytes:\n      type: string\n       \n    spending_input_index:\n      type: integer\n      format: int8\n    spending_sig:\n      type: string\n       \n    input_tx:\n      type: string\n       \n    input_utxo_pos:\n      type: integer\n      format: int256\n\nOutputChallengeDataSchema:\n  description: The object schema for an output challenge data\n  properties:\n    in_flight_txbytes:\n      type: string\n       \n    in_flight_output_pos:\n      type: integer\n      format: int256\n    in_flight_proof:\n      type: string\n       \n    spending_txbytes:\n      type: string\n       \n    spending_input_index:\n      type: integer\n      format: int8\n    spending_sig:\n      type: string\n       \n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs/response_schemas.yaml",
    "content": "WatcherBaseResponseSchema:\n  description: The response schema for a successful operation\n  type: object\n  properties:\n    version:\n      type: string\n    success:\n      type: boolean\n    data:\n      type: object\n    service_name:\n      type: string\n  required:\n    - service_name\n    - version\n    - success\n    - data\n  example:\n    service_name: watcher\n    version: '1.0.0+abcdefa'\n    success: true\n    data: {}\n\nWatcherBaseListResponseSchema:\n  description: The response schema for a successful list operation\n  type: object\n  properties:\n    version:\n      type: string\n    success:\n      type: boolean\n    data:\n      type: array\n      items:\n        type: object\n    service_name:\n      type: string\n  required:\n    - service_name\n    - version\n    - success\n    - data\n  example:\n    service_name: watcher\n    version: '1.0+abcdefa'\n    success: true\n    data: []\n\nWatcherErrorResponseSchema:\n  description: The response schema for an error\n  allOf:\n    - $ref: 'response_schemas.yaml#/WatcherBaseResponseSchema'\n    - type: object\n      properties:\n        data:\n          $ref: '../shared/schemas.yaml#/ErrorSchema'\n      required:\n        - data\n      example:\n        success: false\n        data:\n          object: error\n          code: server:internal_server_error\n          description: Something went wrong on the server\n          messages: {error_key: error_reason}\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs/responses.yaml",
    "content": "InternalServerError:\n  description: Returns an internal server error\n  content:\n    application/json:\n      schema:\n        $ref: 'response_schemas.yaml#/WatcherErrorResponseSchema'\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs/status/byzantine_events_schema.yml",
    "content": "components:\n  schemas:\n    invalid_exit:\n      type: object\n      properties:\n        event:\n          type: string\n          enum: [invalid_exit]\n        details:\n          type: object\n          properties:\n            eth_height:\n              type: integer\n            utxo_pos:\n              type: integer\n            owner:\n              type: string\n            currency:\n              type: string\n            amount:\n              type: integer\n            root_chain_txhash:\n              type: string\n            spending_txhash: \n              type: string\n            scheduled_finalization_time:\n              type: integer\n    unchallenged_exit:\n      type: object\n      properties:\n        event:\n          type: string\n          enum: [unchallenged_exit]\n        details:\n          type: object\n          properties:\n            eth_height:\n              type: integer\n            utxo_pos:\n              type: integer\n            owner:\n              type: string\n            currency:\n              type: string\n            amount:\n              type: integer\n            root_chain_txhash:\n              type: string\n            spending_txhash: \n              type: string\n            scheduled_finalization_time:\n              type: integer\n    invalid_block:\n      type: object\n      properties:\n        event:\n          type: string\n          enum: [invalid_block]\n        details:\n          type: object\n          properties:\n            blknum:\n              type: integer\n            blockhash:\n              type: string\n            error_type:\n              type: string\n              enum: [tx_execution, incorrect_hash]\n    block_withholding:\n      type: object\n      properties:\n        event:\n          type: string\n          enum: [block_withholding]\n        details:\n          type: object\n          properties:\n            hash:\n              type: string\n            blknum:\n              type: string\n    noncanonical_ife:\n      type: object\n      properties:\n        event:\n          type: string\n          enum: [noncanonical_ife]\n        details:\n          type: object\n          properties:\n            txbytes:\n              type: string\n    invalid_ife_challenge:\n      type: object\n      properties:\n        event:\n          type: string\n          enum: [invalid_ife_challenge]\n        details:\n          type: object\n          properties:\n            txbytes:\n              type: string\n    piggyback_available:\n      type: object\n      properties:\n        event:\n          type: string\n          enum: [piggyback_available]\n        details:\n          type: object\n          properties:\n            txbytes:\n              type: string\n            available_outputs:\n              type: array\n              items:\n                type: object\n                properties:\n                  index:\n                    type: integer\n                  address:\n                    type: string\n            available_inputs:\n              type: array\n              items:\n                type: object\n                properties:\n                  index:\n                    type: integer\n                  address:\n                    type: string\n    invalid_piggyback:\n      type: object\n      properties:\n        event:\n          type: string\n          enum: [invalid_piggyback]\n        details:\n          type: object\n          properties:\n            txbytes:\n              type: string\n            inputs:\n              type: array\n              items:\n                type: integer\n            outputs:\n              type: array\n              items:\n                type: integer\n    ethereum_stalled_sync:\n      type: object\n      properties:\n        ethereum_stalled_sync:\n          type: object\n          properties:\n            ethereum_height:\n              type: integer\n              minimum: 0\n            synced_at:\n              type: string\n              format: date-time\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs/status/paths.yaml",
    "content": "status.get:\n  post:\n    tags:\n      - Status\n    summary: Returns information about the current state of the child chain and the watcher.\n    description: >\n      The most critical function of the Watcher is to monitor the ChildChain and report dishonest activity.\n      The user must call the `/status.get` endpoint periodically to check. Any situation that requires the user\n      to either exit or challenge an invalid exit will be included in the `byzantine_events` field.\n    operationId: status_get\n    responses:\n      200:\n        $ref: 'responses.yaml#/StatusResponse'\n      500:\n        $ref: '../responses.yaml#/InternalServerError'\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs/status/request_bodies.yaml",
    "content": "\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs/status/response_schemas.yaml",
    "content": "StatusResponseSchema:\n  description: The response schema for a status\n  allOf:\n  - $ref: '../response_schemas.yaml#/WatcherBaseResponseSchema'\n  - type: object\n    properties:\n      data:\n        type: object\n        $ref: 'schemas.yaml#/StatusSchema'\n    example:\n      data:\n        last_validated_child_block_timestamp: 1558535130\n        last_validated_child_block_number: 10000\n        last_mined_child_block_timestamp: 1558535190\n        last_mined_child_block_number: 11000\n        last_seen_eth_block_timestamp: 1558535190\n        last_seen_eth_block_number: 4427041\n        contract_addr:\n          plasma_framework: '0x44de0ec539b8c4a4b530c78620fe8320167f2f74'\n        eth_syncing: true\n        byzantine_events:\n        -\n          event: \"invalid_exit\"\n          details:\n            eth_height: 615440\n            utxo_pos: 10000000010000000\n            owner: \"0xb3256026863eb6ae5b06fa396ab09069784ea8ea\"\n            currency: \"0x0000000000000000000000000000000000000000\"\n            amount: 100\n            root_chain_txhash: \"0xde8210dd179e4a067c5649ebeee8871e0f258fecbd1eb02e11db88121bb8de01\"\n            spending_txhash: \"0x21aee8dcc74d6b309f6e98a967a6aa6002432f98a5bc13c75529dbe228e04451\"\n            scheduled_finalization_time: 1588144725\n        -\n          event: \"unchallenged_exit\"\n          details:\n            eth_height: 615440\n            utxo_pos: 10000000010000000\n            owner: \"0xb3256026863eb6ae5b06fa396ab09069784ea8ea\"\n            currency: \"0x0000000000000000000000000000000000000000\"\n            amount: 100\n            root_chain_txhash: \"0xde8210dd179e4a067c5649ebeee8871e0f258fecbd1eb02e11db88121bb8de01\"\n            spending_txhash: \"0x21aee8dcc74d6b309f6e98a967a6aa6002432f98a5bc13c75529dbe228e04451\"\n            scheduled_finalization_time: 1588144725\n        -\n          event: \"invalid_block\"\n          details:\n            blockhash: \"0x0017372421f9a92bedb7163310918e623557ab5310befc14e67212b660c33bec\"\n            blknum: 10000\n            error_type: \"tx_execution\"\n        -\n          event: \"block_withholding\"\n          details:\n            hash: \"0x0017372421f9a92bedb7163310918e623557ab5310befc14e67212b660c33bec\"\n            blknum: 10000\n        -\n          event: \"noncanonical_ife\"\n          details:\n            txbytes: \"0x0017372421f9a92bedb7163310918e623557ab5310befc14e67212b660c33bec\"\n        -\n          event: \"invalid_ife_challenge\"\n          details:\n            txbytes: \"0x0017372421f9a92bedb7163310918e623557ab5310befc14e67212b660c33bec\"\n        -\n          event: \"piggyback_available\"\n          details:\n            txbytes: \"0x0017372421f9a92bedb7163310918e623557ab5310befc14e67212b660c33bec\"\n            available_outputs:\n            -\n              index: 0\n              address: \"0xb3256026863eb6ae5b06fa396ab09069784ea8ea\"\n            -\n              index: 1,\n              address: \"0x488f85743ef16cfb1f8d4dd1dfc74c51dc496434\"\n            available_inputs:\n            -\n              index: 0\n              address: \"0xb3256026863eb6ae5b06fa396ab09069784ea8ea\"\n        -\n          event: \"invalid_piggyback\"\n          details:\n            txbytes: \"0x0017372421f9a92bedb7163310918e623557ab5310befc14e67212b660c33bec\"\n            inputs: [1]\n            outputs: [0]\n        -\n          event: \"ethereum_stalled_sync\"\n          details:\n            eth_height: 615440\n            synced_at: \"2020-02-07T10:10:10+00:00\"\n        in_flight_txs:\n        -\n          txhash: '0xbdf562c24ace032176e27621073df58ce1c6f65de3b5932343b70ba03c72132d'\n          txbytes: '0x3eb6ae5b06f3...'\n          input_addresses:\n          - '0x1234...'\n          ouput_addresses:\n          - '0x1234...'\n          - '0x7890...'\n        in_flight_exits:\n        -\n          txhash: '0x5df13a6bf96dbcf6e66d8babd6b55bd40d64d4320c3b115364c6588fc18c2a21'\n          txbytes: '0xf3170101c094...'\n          eth_height: 615441\n          piggybacked_inputs:\n          - 1\n          piggybacked_outputs:\n          - 0\n          - 1\n        services_synced_heights:\n        -\n          service: \"block_getter\"\n          height: 4427041\n        -\n          service: \"challenges_responds_processor\"\n          height: 4427029\n        -\n          service: \"competitor_processor\"\n          height: 4427029\n        -\n          service: \"depositor\"\n          height: 4427031\n        -\n          service: \"exit_challenger\"\n          height: 4427029\n        -\n          service: \"exit_finalizer\"\n          height: 4427029\n        -\n          service: \"exit_processor\"\n          height: 4427029\n        -\n          service: \"ife_exit_finalizer\"\n          height: 4427029\n        -\n          service: \"in_flight_exit_processor\"\n          height: 4427029\n        -\n          service: \"piggyback_challenges_processor\"\n          height: 4427029\n        -\n          service: \"piggyback_processor\"\n          height: 4427029\n        -\n          service: \"root_chain_height\"\n          height: 4427041\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs/status/responses.yaml",
    "content": "StatusResponse:\n  description: Returns the status of the watcher\n  content:\n    application/json:\n      schema:\n        $ref: 'response_schemas.yaml#/StatusResponseSchema'\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs/status/schemas.yaml",
    "content": "StatusSchema:\n  description: The object schema for a status\n  type: object\n  properties:\n    last_validated_child_block_timestamp:\n      type: integer\n      format: int64\n    last_validated_child_block_number:\n      type: integer\n      format: int64\n    last_mined_child_block_timestamp:\n      type: integer\n      format: int64\n    last_mined_child_block_number:\n      type: integer\n      format: int64\n    last_seen_eth_block_timestamp:\n      type: integer\n      format: int64\n    last_seen_eth_block_number:\n      type: integer\n      format: int64\n    contract_addr:\n      type: object\n      additionalProperties: true\n      properties:\n        plasma_framework:\n          type: string\n    eth_syncing:\n      type: boolean\n    byzantine_events:\n      type: array\n      items:\n        anyOf:\n          - $ref: 'byzantine_events_schema.yml#/components/schemas/invalid_exit'\n          - $ref: 'byzantine_events_schema.yml#/components/schemas/unchallenged_exit'\n          - $ref: 'byzantine_events_schema.yml#/components/schemas/invalid_block'\n          - $ref: 'byzantine_events_schema.yml#/components/schemas/block_withholding'\n          - $ref: 'byzantine_events_schema.yml#/components/schemas/noncanonical_ife'\n          - $ref: 'byzantine_events_schema.yml#/components/schemas/invalid_ife_challenge'\n          - $ref: 'byzantine_events_schema.yml#/components/schemas/piggyback_available'\n          - $ref: 'byzantine_events_schema.yml#/components/schemas/invalid_piggyback'\n          - $ref: 'byzantine_events_schema.yml#/components/schemas/ethereum_stalled_sync'\n    in_flight_txs:\n      type: array\n      items:\n        type: object\n        properties:\n          txhash:\n            type: string\n          txbytes:\n            type: string\n          input_addresses:\n            type: array\n            items:\n              type: string\n          ouput_addresses:\n            type: array\n            items:\n              type: string\n    in_flight_exits:\n      type: array\n      items:\n        type: object\n        properties:\n          txhash:\n            type: string\n          txbytes:\n            type: string\n          eth_height:\n            type: integer\n          piggybacked_inputs:\n            type: array\n            items:\n              type: integer\n          piggybacked_outputs:\n            type: array\n            items:\n              type: integer\n    services_synced_heights:\n      type: array\n      items:\n        type: object\n        properties:\n          service:\n            type: string\n          height:\n            type: integer\n            format: int256\n  required:\n    - last_validated_child_block_timestamp\n    - last_validated_child_block_number\n    - last_mined_child_block_timestamp\n    - last_mined_child_block_number\n    - last_seen_eth_block_timestamp\n    - last_seen_eth_block_number\n    - contract_addr\n    - eth_syncing\n    - byzantine_events\n    - in_flight_txs\n    - in_flight_exits\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs/swagger.yaml",
    "content": "openapi: 3.0.0\ninfo:\n  version: '1.0.0'\n  title: Watcher security-critical API\n  description: >\n    API specification of the Watcher's security-critical Service\n\n    Error codes are available in [html](https://github.com/omgnetwork/elixir-omg/blob/master/docs/api_specs/errors.md#error-codes-description) format.\n  contact:\n    name: OMG Network\n    email: engineering@omg.network\n  license:\n    name: 'Apache 2.0: https://www.apache.org/licenses/LICENSE-2.0'\n    url: 'https://omg.network/'\n\nservers:\n  - url: https://watcher.ropsten.v1.omg.network/\n  - url: http://localhost:7434/\n\ntags:\n  - name: Status\n    description: Status of the child chain.\n    externalDocs:\n      description: \"Byzantine events description\"\n      url: \"https://github.com/omgnetwork/elixir-omg/blob/master/docs/api_specs/status_events_specs.md#byzantine-events\"\n  - name: Account\n    description: Account related API.\n  - name: Block\n    description: Block-related API\n  - name: UTXO\n    description: UTXO related API.\n  - name: Transaction\n    description: Transaction related API.\n  - name: InFlightExit\n    description: InFlightExit related API.\n\npaths:\n  /alarm.get:\n    $ref: 'alarm/paths.yaml#/alarm.get'\n  /configuration.get:\n    $ref: 'configuration/paths.yaml#/configuration.get'\n  /status.get:\n    $ref: 'status/paths.yaml#/status.get'\n  /account.get_exitable_utxos:\n    $ref: 'account/paths.yaml#/account.get_exitable_utxos'\n  /block.validate:\n    $ref: 'block/paths.yaml#/block.validate'\n  /utxo.get_challenge_data:\n    $ref: 'utxo/paths.yaml#/utxo.get_challenge_data'\n  /utxo.get_exit_data:\n    $ref: 'utxo/paths.yaml#/utxo.get_exit_data'\n  /transaction.submit:\n    $ref: 'transaction/paths.yaml#/transaction.submit'\n  /transaction.batch_submit:\n    $ref: 'batch_transaction/paths.yaml#/transaction.batch_submit'\n  /in_flight_exit.get_data:\n    $ref: 'in_flight_exit/paths.yaml#/in_flight_exit.get_data'\n  /in_flight_exit.get_competitor:\n    $ref: 'in_flight_exit/paths.yaml#/in_flight_exit.get_competitor'\n  /in_flight_exit.prove_canonical:\n    $ref: 'in_flight_exit/paths.yaml#/in_flight_exit.prove_canonical'\n  /in_flight_exit.get_input_challenge_data:\n    $ref: 'in_flight_exit/paths.yaml#/in_flight_exit.get_input_challenge_data'\n  /in_flight_exit.get_output_challenge_data:\n    $ref: 'in_flight_exit/paths.yaml#/in_flight_exit.get_output_challenge_data'\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs/transaction/paths.yaml",
    "content": "transaction.submit:\n  post:\n    tags:\n      - Transaction\n    summary: Sends transaction to Child chain.\n    description: Watcher passes signed transaction to the child chain only if it's secure, e.g. Watcher is fully synced, all operator blocks have been verified, transaction doesn't spend funds not yet mined...\n    operationId: submit\n    requestBody:\n      $ref: 'request_bodies.yaml#/TransactionSubmitBodySchema'\n    responses:\n      200:\n        $ref: 'responses.yaml#/TransactionSubmitResponse'\n      500:\n        $ref: '../responses.yaml#/InternalServerError'\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs/transaction/request_bodies.yaml",
    "content": "TransactionSubmitBodySchema:\n  description: Signed transaction RLP-encoded to bytes and HEX-encoded to string\n  required: true\n  content:\n    application/json:\n      schema:\n        title: 'TransactionSubmitBodySchema'\n        type: object\n        properties:\n          transaction:\n            type: string\n             \n        required:\n          - transaction\n        example:\n          transaction: '0xf8d083015ba98080808080940000...'\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs/transaction/response_schemas.yaml",
    "content": "TransactionSubmitResponseSchema:\n  allOf:\n  - $ref: '../response_schemas.yaml#/WatcherBaseResponseSchema'\n  - type: object\n    properties:\n      data:\n        type: object\n        $ref: 'schemas.yaml#/TransactionSubmitSchema'\n    example:\n      data:\n        blknum: 123000\n        txindex: 111\n        txhash: '0xbdf562c24ace032176e27621073df58ce1c6f65de3b5932343b70ba03c72132d'\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs/transaction/responses.yaml",
    "content": "TransactionSubmitResponse:\n  description: Transaction submission successful response\n  content:\n    application/json:\n      schema:\n        $ref: 'response_schemas.yaml#/TransactionSubmitResponseSchema'\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs/transaction/schemas.yaml",
    "content": "TransactionSubmitSchema:\n  type: object\n  properties:\n    blknum:\n      type: integer\n      format: int64\n    txindex:\n      type: integer\n      format: int16\n    txhash:\n      type: string\n       \n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs/utxo/paths.yaml",
    "content": "utxo.get_challenge_data:\n  post:\n    tags:\n      - UTXO\n    summary: Gets challenge data for a given utxo exit.\n    description: Gets challenge data for a given utxo exit.\n    operationId: utxo_get_challenge_data\n    requestBody:\n      $ref: 'request_bodies.yaml#/UtxoPositionBodySchema'\n    responses:\n      200:\n        $ref: 'responses.yaml#/GetUtxoChallengeResponse'\n      500:\n        $ref: '../responses.yaml#/InternalServerError'\n\nutxo.get_exit_data:\n  post:\n    tags:\n      - UTXO\n    summary: Gets exit data for a given utxo.\n    description: Gets exit data for a given utxo.\n    operationId: utxo_get_exit_data\n    requestBody:\n      $ref: 'request_bodies.yaml#/UtxoPositionBodySchema'\n    responses:\n      200:\n        $ref: 'responses.yaml#/GetUtxoExitResponse'\n      500:\n        $ref: '../responses.yaml#/InternalServerError'\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs/utxo/request_bodies.yaml",
    "content": "UtxoPositionBodySchema:\n  description: Utxo position (encoded as single integer, the way contract represents them)\n  required: true\n  content:\n    application/json:\n      schema:\n        title: 'UtxoPositionBodySchema'\n        type: object\n        properties:\n          utxo_pos:\n            type: integer\n            format: int256\n        required:\n          - utxo_pos\n        example:\n          utxo_pos: 10000000010000000\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs/utxo/response_schemas.yaml",
    "content": "GetUtxoChallengeResponseSchema:\n  description: The response schema for utxo challenge data\n  allOf:\n  - $ref: '../response_schemas.yaml#/WatcherBaseResponseSchema'\n  - type: object\n    properties:\n      data:\n        type: object\n        $ref: 'schemas.yaml#/GetUtxoChallengeSchema'\n    example:\n      data:\n        exit_id: 1717611893014159315373779059565546411346446754\n        input_index: 0\n        sig: '0x6bfb9b2dbe32...'\n        txbytes: '0x3eb6ae5b06f3...'\n        exiting_tx: '0x6d6bda6bd6d6...'\n\nGetUtxoExitResponseSchema:\n  description: The response schema for utxo exit data\n  allOf:\n  - $ref: '../response_schemas.yaml#/WatcherBaseResponseSchema'\n  - type: object\n    properties:\n      data:\n        type: object\n        $ref: 'schemas.yaml#/GetUtxoExitSchema'\n    example:\n      data:\n        proof: '0xcedb8b31d1e4...'\n        txbytes: '0x3eb6ae5b06f3...'\n        utxo_pos: 10000000010000000\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs/utxo/responses.yaml",
    "content": "GetUtxoChallengeResponse:\n  description: Utxo challenge successful response\n  content:\n    application/json:\n      schema:\n        $ref: 'response_schemas.yaml#/GetUtxoChallengeResponseSchema'\n\nGetUtxoExitResponse:\n  description: Utxo exit successful response\n  content:\n    application/json:\n      schema:\n        $ref: 'response_schemas.yaml#/GetUtxoExitResponseSchema'\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs/utxo/schemas.yaml",
    "content": "GetUtxoChallengeSchema:\n  type: object\n  properties:\n    exit_id:\n      type: integer\n      format: int192\n    input_index:\n      type: integer\n      format: int8\n    sig:\n      type: string\n       \n    txbytes:\n      type: string\n       \n    exiting_tx:\n      type: string\n       \n\nGetUtxoExitSchema:\n  type: object\n  properties:\n    utxo_pos:\n      type: integer\n      format: int256\n    txbytes:\n      type: string\n       \n    proof:\n      type: string\n       \n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs.yaml",
    "content": "openapi: 3.0.0\ninfo:\n  version: 1.0.0\n  title: Watcher security-critical API\n  description: |\n    API specification of the Watcher's security-critical Service\n    Error codes are available in [html](https://github.com/omgnetwork/elixir-omg/blob/master/docs/api_specs/errors.md#error-codes-description) format.\n  contact:\n    name: OMG Network\n    email: engineering@omg.network\n  license:\n    name: 'Apache 2.0: https://www.apache.org/licenses/LICENSE-2.0'\n    url: 'https://omg.network/'\nservers:\n  - url: 'https://watcher.ropsten.v1.omg.network/'\n  - url: 'http://localhost:7434/'\ntags:\n  - name: Status\n    description: Status of the child chain.\n    externalDocs:\n      description: Byzantine events description\n      url: 'https://github.com/omgnetwork/elixir-omg/blob/master/docs/api_specs/status_events_specs.md#byzantine-events'\n  - name: Account\n    description: Account related API.\n  - name: Block\n    description: Block-related API\n  - name: UTXO\n    description: UTXO related API.\n  - name: Transaction\n    description: Transaction related API.\n  - name: InFlightExit\n    description: InFlightExit related API.\npaths:\n  /alarm.get:\n    get:\n      tags:\n        - Alarm\n      summary: 'Provides alarms related to system memory, cpu and storage and application specific alarms.'\n      description: |\n        **Note:** Service operator alarms.\n      operationId: alarm_get\n      responses:\n        '200':\n          description: System alarms\n          content:\n            application/json:\n              schema:\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        type: array\n                        items:\n                          type: array\n                          items:\n                            anyOf:\n                              - type: object\n                                properties:\n                                  ethereum_connection_error:\n                                    type: object\n                                    properties:\n                                      node:\n                                        type: string\n                                      reporter:\n                                        type: string\n                              - type: object\n                                properties:\n                                  ethereum_stalled_sync:\n                                    type: object\n                                    properties:\n                                      ethereum_height:\n                                        type: integer\n                                        minimum: 0\n                                      synced_at:\n                                        type: string\n                                        format: date-time\n                              - type: object\n                                properties:\n                                  invalid_fee_source:\n                                    type: object\n                                    properties:\n                                      node:\n                                        type: string\n                                      reporter:\n                                        type: string\n                              - type: object\n                                properties:\n                                  statsd_client_connection:\n                                    type: object\n                                    properties:\n                                      node:\n                                        type: string\n                                      reporter:\n                                        type: string\n                              - type: object\n                                properties:\n                                  statsd_client_connection:\n                                    type: array\n                                    items:\n                                      type: string\n                                    default: []\n                              - type: object\n                                properties:\n                                  disk_almost_full:\n                                    type: string\n                    example:\n                      data:\n                        - disk_almost_full: /dev/null\n                          ethereum_connection_error: {}\n                          ethereum_stalled_sync: {}\n                          system_memory_high_watermark: []\n        '500':\n          description: Returns an internal server error\n          content:\n            application/json:\n              schema:\n                description: The response schema for an error\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        description: The object schema for an error\n                        type: object\n                        properties:\n                          object:\n                            type: string\n                          code:\n                            type: string\n                          description:\n                            type: string\n                          messages:\n                            type: object\n                        required:\n                          - object\n                          - code\n                          - description\n                          - messages\n                    required:\n                      - data\n                    example:\n                      success: false\n                      data:\n                        object: error\n                        code: 'server:internal_server_error'\n                        description: Something went wrong on the server\n                        messages:\n                          error_key: error_reason\n  /configuration.get:\n    get:\n      tags:\n        - Configuration\n      summary: Provides configuration values\n      description: |\n        **Note:** Configuration values.\n      operationId: configuration_get\n      responses:\n        '200':\n          description: Configuration response\n          content:\n            application/json:\n              schema:\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        type: array\n                        items:\n                          type: object\n                          properties:\n                            deposit_finality_margin:\n                              type: integer\n                              format: int256\n                            contract_semver:\n                              type: string\n                            exit_processor_sla_margin:\n                              type: integer\n                              format: int256\n                            network:\n                              type: string\n                    example:\n                      data:\n                        - deposit_finality_margin: 10\n                          contract_semver: 1.0.0.1+a1s29s8\n        '500':\n          description: Returns an internal server error\n          content:\n            application/json:\n              schema:\n                description: The response schema for an error\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        description: The object schema for an error\n                        type: object\n                        properties:\n                          object:\n                            type: string\n                          code:\n                            type: string\n                          description:\n                            type: string\n                          messages:\n                            type: object\n                        required:\n                          - object\n                          - code\n                          - description\n                          - messages\n                    required:\n                      - data\n                    example:\n                      success: false\n                      data:\n                        object: error\n                        code: 'server:internal_server_error'\n                        description: Something went wrong on the server\n                        messages:\n                          error_key: error_reason\n  /status.get:\n    post:\n      tags:\n        - Status\n      summary: Returns information about the current state of the child chain and the watcher.\n      description: |\n        The most critical function of the Watcher is to monitor the ChildChain and report dishonest activity. The user must call the `/status.get` endpoint periodically to check. Any situation that requires the user to either exit or challenge an invalid exit will be included in the `byzantine_events` field.\n      operationId: status_get\n      responses:\n        '200':\n          description: Returns the status of the watcher\n          content:\n            application/json:\n              schema:\n                description: The response schema for a status\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        type: object\n                        description: The object schema for a status\n                        properties:\n                          last_validated_child_block_timestamp:\n                            type: integer\n                            format: int64\n                          last_validated_child_block_number:\n                            type: integer\n                            format: int64\n                          last_mined_child_block_timestamp:\n                            type: integer\n                            format: int64\n                          last_mined_child_block_number:\n                            type: integer\n                            format: int64\n                          last_seen_eth_block_timestamp:\n                            type: integer\n                            format: int64\n                          last_seen_eth_block_number:\n                            type: integer\n                            format: int64\n                          contract_addr:\n                            type: object\n                            additionalProperties: true\n                            properties:\n                              plasma_framework:\n                                type: string\n                          eth_syncing:\n                            type: boolean\n                          byzantine_events:\n                            type: array\n                            items:\n                              anyOf:\n                                - type: object\n                                  properties:\n                                    event:\n                                      type: string\n                                      enum:\n                                        - invalid_exit\n                                    details:\n                                      type: object\n                                      properties:\n                                        eth_height:\n                                          type: integer\n                                        utxo_pos:\n                                          type: integer\n                                        owner:\n                                          type: string\n                                        currency:\n                                          type: string\n                                        amount:\n                                          type: integer\n                                        root_chain_txhash:\n                                          type: string\n                                        spending_txhash:\n                                          type: string\n                                        scheduled_finalization_time:\n                                          type: integer\n                                - type: object\n                                  properties:\n                                    event:\n                                      type: string\n                                      enum:\n                                        - unchallenged_exit\n                                    details:\n                                      type: object\n                                      properties:\n                                        eth_height:\n                                          type: integer\n                                        utxo_pos:\n                                          type: integer\n                                        owner:\n                                          type: string\n                                        currency:\n                                          type: string\n                                        amount:\n                                          type: integer\n                                        root_chain_txhash:\n                                          type: string\n                                        spending_txhash:\n                                          type: string\n                                        scheduled_finalization_time:\n                                          type: integer\n                                - type: object\n                                  properties:\n                                    event:\n                                      type: string\n                                      enum:\n                                        - invalid_block\n                                    details:\n                                      type: object\n                                      properties:\n                                        blknum:\n                                          type: integer\n                                        blockhash:\n                                          type: string\n                                        error_type:\n                                          type: string\n                                          enum:\n                                            - tx_execution\n                                            - incorrect_hash\n                                - type: object\n                                  properties:\n                                    event:\n                                      type: string\n                                      enum:\n                                        - block_withholding\n                                    details:\n                                      type: object\n                                      properties:\n                                        hash:\n                                          type: string\n                                        blknum:\n                                          type: string\n                                - type: object\n                                  properties:\n                                    event:\n                                      type: string\n                                      enum:\n                                        - noncanonical_ife\n                                    details:\n                                      type: object\n                                      properties:\n                                        txbytes:\n                                          type: string\n                                - type: object\n                                  properties:\n                                    event:\n                                      type: string\n                                      enum:\n                                        - invalid_ife_challenge\n                                    details:\n                                      type: object\n                                      properties:\n                                        txbytes:\n                                          type: string\n                                - type: object\n                                  properties:\n                                    event:\n                                      type: string\n                                      enum:\n                                        - piggyback_available\n                                    details:\n                                      type: object\n                                      properties:\n                                        txbytes:\n                                          type: string\n                                        available_outputs:\n                                          type: array\n                                          items:\n                                            type: object\n                                            properties:\n                                              index:\n                                                type: integer\n                                              address:\n                                                type: string\n                                        available_inputs:\n                                          type: array\n                                          items:\n                                            type: object\n                                            properties:\n                                              index:\n                                                type: integer\n                                              address:\n                                                type: string\n                                - type: object\n                                  properties:\n                                    event:\n                                      type: string\n                                      enum:\n                                        - invalid_piggyback\n                                    details:\n                                      type: object\n                                      properties:\n                                        txbytes:\n                                          type: string\n                                        inputs:\n                                          type: array\n                                          items:\n                                            type: integer\n                                        outputs:\n                                          type: array\n                                          items:\n                                            type: integer\n                                - type: object\n                                  properties:\n                                    ethereum_stalled_sync:\n                                      type: object\n                                      properties:\n                                        ethereum_height:\n                                          type: integer\n                                          minimum: 0\n                                        synced_at:\n                                          type: string\n                                          format: date-time\n                          in_flight_txs:\n                            type: array\n                            items:\n                              type: object\n                              properties:\n                                txhash:\n                                  type: string\n                                txbytes:\n                                  type: string\n                                input_addresses:\n                                  type: array\n                                  items:\n                                    type: string\n                                ouput_addresses:\n                                  type: array\n                                  items:\n                                    type: string\n                          in_flight_exits:\n                            type: array\n                            items:\n                              type: object\n                              properties:\n                                txhash:\n                                  type: string\n                                txbytes:\n                                  type: string\n                                eth_height:\n                                  type: integer\n                                piggybacked_inputs:\n                                  type: array\n                                  items:\n                                    type: integer\n                                piggybacked_outputs:\n                                  type: array\n                                  items:\n                                    type: integer\n                          services_synced_heights:\n                            type: array\n                            items:\n                              type: object\n                              properties:\n                                service:\n                                  type: string\n                                height:\n                                  type: integer\n                                  format: int256\n                        required:\n                          - last_validated_child_block_timestamp\n                          - last_validated_child_block_number\n                          - last_mined_child_block_timestamp\n                          - last_mined_child_block_number\n                          - last_seen_eth_block_timestamp\n                          - last_seen_eth_block_number\n                          - contract_addr\n                          - eth_syncing\n                          - byzantine_events\n                          - in_flight_txs\n                          - in_flight_exits\n                    example:\n                      data:\n                        last_validated_child_block_timestamp: 1558535130\n                        last_validated_child_block_number: 10000\n                        last_mined_child_block_timestamp: 1558535190\n                        last_mined_child_block_number: 11000\n                        last_seen_eth_block_timestamp: 1558535190\n                        last_seen_eth_block_number: 4427041\n                        contract_addr:\n                          plasma_framework: '0x44de0ec539b8c4a4b530c78620fe8320167f2f74'\n                        eth_syncing: true\n                        byzantine_events:\n                          - event: invalid_exit\n                            details:\n                              eth_height: 615440\n                              utxo_pos: 10000000010000000\n                              owner: '0xb3256026863eb6ae5b06fa396ab09069784ea8ea'\n                              currency: '0x0000000000000000000000000000000000000000'\n                              amount: 100\n                              root_chain_txhash: '0xde8210dd179e4a067c5649ebeee8871e0f258fecbd1eb02e11db88121bb8de01'\n                              spending_txhash: '0x21aee8dcc74d6b309f6e98a967a6aa6002432f98a5bc13c75529dbe228e04451'\n                              scheduled_finalization_time: 1588144725\n                          - event: unchallenged_exit\n                            details:\n                              eth_height: 615440\n                              utxo_pos: 10000000010000000\n                              owner: '0xb3256026863eb6ae5b06fa396ab09069784ea8ea'\n                              currency: '0x0000000000000000000000000000000000000000'\n                              amount: 100\n                              root_chain_txhash: '0xde8210dd179e4a067c5649ebeee8871e0f258fecbd1eb02e11db88121bb8de01'\n                              spending_txhash: '0x21aee8dcc74d6b309f6e98a967a6aa6002432f98a5bc13c75529dbe228e04451'\n                              scheduled_finalization_time: 1588144725\n                          - event: invalid_block\n                            details:\n                              blockhash: '0x0017372421f9a92bedb7163310918e623557ab5310befc14e67212b660c33bec'\n                              blknum: 10000\n                              error_type: tx_execution\n                          - event: block_withholding\n                            details:\n                              hash: '0x0017372421f9a92bedb7163310918e623557ab5310befc14e67212b660c33bec'\n                              blknum: 10000\n                          - event: noncanonical_ife\n                            details:\n                              txbytes: '0x0017372421f9a92bedb7163310918e623557ab5310befc14e67212b660c33bec'\n                          - event: invalid_ife_challenge\n                            details:\n                              txbytes: '0x0017372421f9a92bedb7163310918e623557ab5310befc14e67212b660c33bec'\n                          - event: piggyback_available\n                            details:\n                              txbytes: '0x0017372421f9a92bedb7163310918e623557ab5310befc14e67212b660c33bec'\n                              available_outputs:\n                                - index: 0\n                                  address: '0xb3256026863eb6ae5b06fa396ab09069784ea8ea'\n                                - index: '1,'\n                                  address: '0x488f85743ef16cfb1f8d4dd1dfc74c51dc496434'\n                              available_inputs:\n                                - index: 0\n                                  address: '0xb3256026863eb6ae5b06fa396ab09069784ea8ea'\n                          - event: invalid_piggyback\n                            details:\n                              txbytes: '0x0017372421f9a92bedb7163310918e623557ab5310befc14e67212b660c33bec'\n                              inputs:\n                                - 1\n                              outputs:\n                                - 0\n                          - event: ethereum_stalled_sync\n                            details:\n                              eth_height: 615440\n                              synced_at: '2020-02-07T10:10:10+00:00'\n                        in_flight_txs:\n                          - txhash: '0xbdf562c24ace032176e27621073df58ce1c6f65de3b5932343b70ba03c72132d'\n                            txbytes: 0x3eb6ae5b06f3...\n                            input_addresses:\n                              - 0x1234...\n                            ouput_addresses:\n                              - 0x1234...\n                              - 0x7890...\n                        in_flight_exits:\n                          - txhash: '0x5df13a6bf96dbcf6e66d8babd6b55bd40d64d4320c3b115364c6588fc18c2a21'\n                            txbytes: 0xf3170101c094...\n                            eth_height: 615441\n                            piggybacked_inputs:\n                              - 1\n                            piggybacked_outputs:\n                              - 0\n                              - 1\n                        services_synced_heights:\n                          - service: block_getter\n                            height: 4427041\n                          - service: challenges_responds_processor\n                            height: 4427029\n                          - service: competitor_processor\n                            height: 4427029\n                          - service: depositor\n                            height: 4427031\n                          - service: exit_challenger\n                            height: 4427029\n                          - service: exit_finalizer\n                            height: 4427029\n                          - service: exit_processor\n                            height: 4427029\n                          - service: ife_exit_finalizer\n                            height: 4427029\n                          - service: in_flight_exit_processor\n                            height: 4427029\n                          - service: piggyback_challenges_processor\n                            height: 4427029\n                          - service: piggyback_processor\n                            height: 4427029\n                          - service: root_chain_height\n                            height: 4427041\n        '500':\n          description: Returns an internal server error\n          content:\n            application/json:\n              schema:\n                description: The response schema for an error\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        description: The object schema for an error\n                        type: object\n                        properties:\n                          object:\n                            type: string\n                          code:\n                            type: string\n                          description:\n                            type: string\n                          messages:\n                            type: object\n                        required:\n                          - object\n                          - code\n                          - description\n                          - messages\n                    required:\n                      - data\n                    example:\n                      success: false\n                      data:\n                        object: error\n                        code: 'server:internal_server_error'\n                        description: Something went wrong on the server\n                        messages:\n                          error_key: error_reason\n  /account.get_exitable_utxos:\n    post:\n      tags:\n        - Account\n      summary: Gets all utxos belonging to the given address.\n      description: |\n        **Note:** this is a performance intensive call and should only be used if the chain is byzantine and the user needs to retrieve utxo information to be able to exit. Normally an application should use the Informational API's [Account - Get Utxos](http://TODO) instead. This version is provided in case the Informational API is not available.\n      operationId: account_get_exitable_utxos\n      requestBody:\n        description: HEX-encoded address of the account\n        required: true\n        content:\n          application/json:\n            schema:\n              title: AddressBodySchema\n              type: object\n              properties:\n                address:\n                  type: string\n              required:\n                - address\n              example:\n                address: '0xb3256026863eb6ae5b06fa396ab09069784ea8ea'\n      responses:\n        '200':\n          description: Account utxos succcessful response\n          content:\n            application/json:\n              schema:\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        type: array\n                        items:\n                          type: object\n                          properties:\n                            blknum:\n                              type: integer\n                              format: int64\n                            txindex:\n                              type: integer\n                              format: int16\n                            otype:\n                              type: integer\n                              format: int16\n                            oindex:\n                              type: integer\n                              format: int8\n                            utxo_pos:\n                              type: integer\n                              format: int256\n                            owner:\n                              type: string\n                            currency:\n                              type: string\n                            amount:\n                              type: integer\n                              format: int256\n                    example:\n                      data:\n                        - blknum: 123000\n                          txindex: 111\n                          oindex: 0\n                          otype: 1\n                          utxo_pos: 123000001110000\n                          owner: '0xb3256026863eb6ae5b06fa396ab09069784ea8ea'\n                          currency: '0x0000000000000000000000000000000000000000'\n                          amount: 10\n        '500':\n          description: Returns an internal server error\n          content:\n            application/json:\n              schema:\n                description: The response schema for an error\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        description: The object schema for an error\n                        type: object\n                        properties:\n                          object:\n                            type: string\n                          code:\n                            type: string\n                          description:\n                            type: string\n                          messages:\n                            type: object\n                        required:\n                          - object\n                          - code\n                          - description\n                          - messages\n                    required:\n                      - data\n                    example:\n                      success: false\n                      data:\n                        object: error\n                        code: 'server:internal_server_error'\n                        description: Something went wrong on the server\n                        messages:\n                          error_key: error_reason\n  /block.validate:\n    post:\n      tags:\n        - Block\n      summary: Verifies the stateless validity of a block.\n      description: |\n        - Verifies that given Merkle root matches reconstructed Merkle root.\n        - Verifies that (payment and fee) transactions  are correctly formed.\n        - Verifies that there are no duplicate inputs at the block level.\n        - Verifies that the number of transactions falls within the accepted range.\n        - Verifies that fee transactions are correctly placed and unique per currency.\n      operationId: validate\n      requestBody:\n        description: 'Block object with a hash, number and array of hexadecimal transaction bytes.'\n        required: true\n        content:\n          application/json:\n            schema:\n              title: BlockValidateBodySchema\n              type: object\n              properties:\n                hash:\n                  type: string\n                transactions:\n                  type: array\n                  items:\n                    type: string\n                number:\n                  type: integer\n              required:\n                - hash\n                - transactions\n                - number\n              example:\n                number: 1000\n                hash: 0xf8d083015ba98080808080940000...\n                transactions:\n                  - 0xf8c0f843b841fc6dbf49a4baa783ec576291f6083be5ea...\n                  - 0xf852c003eeed02eb94916f3753bd53e124d6d565ef1701...\n      responses:\n        '200':\n          description: Successful response to calling /block.validate\n          content:\n            application/json:\n              schema:\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        type: object\n                        properties:\n                          valid:\n                            type: boolean\n                    example:\n                      data:\n                        valid: false\n        '500':\n          description: Returns an internal server error\n          content:\n            application/json:\n              schema:\n                description: The response schema for an error\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        description: The object schema for an error\n                        type: object\n                        properties:\n                          object:\n                            type: string\n                          code:\n                            type: string\n                          description:\n                            type: string\n                          messages:\n                            type: object\n                        required:\n                          - object\n                          - code\n                          - description\n                          - messages\n                    required:\n                      - data\n                    example:\n                      success: false\n                      data:\n                        object: error\n                        code: 'server:internal_server_error'\n                        description: Something went wrong on the server\n                        messages:\n                          error_key: error_reason\n  /utxo.get_challenge_data:\n    post:\n      tags:\n        - UTXO\n      summary: Gets challenge data for a given utxo exit.\n      description: Gets challenge data for a given utxo exit.\n      operationId: utxo_get_challenge_data\n      requestBody:\n        description: 'Utxo position (encoded as single integer, the way contract represents them)'\n        required: true\n        content:\n          application/json:\n            schema:\n              title: UtxoPositionBodySchema\n              type: object\n              properties:\n                utxo_pos:\n                  type: integer\n                  format: int256\n              required:\n                - utxo_pos\n              example:\n                utxo_pos: 10000000010000000\n      responses:\n        '200':\n          description: Utxo challenge successful response\n          content:\n            application/json:\n              schema:\n                description: The response schema for utxo challenge data\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        type: object\n                        properties:\n                          exit_id:\n                            type: integer\n                            format: int192\n                          input_index:\n                            type: integer\n                            format: int8\n                          sig:\n                            type: string\n                          txbytes:\n                            type: string\n                          exiting_tx:\n                            type: string\n                    example:\n                      data:\n                        exit_id: 1.7176118930141594e+45\n                        input_index: 0\n                        sig: 0x6bfb9b2dbe32...\n                        txbytes: 0x3eb6ae5b06f3...\n                        exiting_tx: 0x6d6bda6bd6d6...\n        '500':\n          description: Returns an internal server error\n          content:\n            application/json:\n              schema:\n                description: The response schema for an error\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        description: The object schema for an error\n                        type: object\n                        properties:\n                          object:\n                            type: string\n                          code:\n                            type: string\n                          description:\n                            type: string\n                          messages:\n                            type: object\n                        required:\n                          - object\n                          - code\n                          - description\n                          - messages\n                    required:\n                      - data\n                    example:\n                      success: false\n                      data:\n                        object: error\n                        code: 'server:internal_server_error'\n                        description: Something went wrong on the server\n                        messages:\n                          error_key: error_reason\n  /utxo.get_exit_data:\n    post:\n      tags:\n        - UTXO\n      summary: Gets exit data for a given utxo.\n      description: Gets exit data for a given utxo.\n      operationId: utxo_get_exit_data\n      requestBody:\n        description: 'Utxo position (encoded as single integer, the way contract represents them)'\n        required: true\n        content:\n          application/json:\n            schema:\n              title: UtxoPositionBodySchema\n              type: object\n              properties:\n                utxo_pos:\n                  type: integer\n                  format: int256\n              required:\n                - utxo_pos\n              example:\n                utxo_pos: 10000000010000000\n      responses:\n        '200':\n          description: Utxo exit successful response\n          content:\n            application/json:\n              schema:\n                description: The response schema for utxo exit data\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        type: object\n                        properties:\n                          utxo_pos:\n                            type: integer\n                            format: int256\n                          txbytes:\n                            type: string\n                          proof:\n                            type: string\n                    example:\n                      data:\n                        proof: 0xcedb8b31d1e4...\n                        txbytes: 0x3eb6ae5b06f3...\n                        utxo_pos: 10000000010000000\n        '500':\n          description: Returns an internal server error\n          content:\n            application/json:\n              schema:\n                description: The response schema for an error\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        description: The object schema for an error\n                        type: object\n                        properties:\n                          object:\n                            type: string\n                          code:\n                            type: string\n                          description:\n                            type: string\n                          messages:\n                            type: object\n                        required:\n                          - object\n                          - code\n                          - description\n                          - messages\n                    required:\n                      - data\n                    example:\n                      success: false\n                      data:\n                        object: error\n                        code: 'server:internal_server_error'\n                        description: Something went wrong on the server\n                        messages:\n                          error_key: error_reason\n  /transaction.submit:\n    post:\n      tags:\n        - Transaction\n      summary: Sends transaction to Child chain.\n      description: 'Watcher passes signed transaction to the child chain only if it''s secure, e.g. Watcher is fully synced, all operator blocks have been verified, transaction doesn''t spend funds not yet mined...'\n      operationId: submit\n      requestBody:\n        description: Signed transaction RLP-encoded to bytes and HEX-encoded to string\n        required: true\n        content:\n          application/json:\n            schema:\n              title: TransactionSubmitBodySchema\n              type: object\n              properties:\n                transaction:\n                  type: string\n              required:\n                - transaction\n              example:\n                transaction: 0xf8d083015ba98080808080940000...\n      responses:\n        '200':\n          description: Transaction submission successful response\n          content:\n            application/json:\n              schema:\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        type: object\n                        properties:\n                          blknum:\n                            type: integer\n                            format: int64\n                          txindex:\n                            type: integer\n                            format: int16\n                          txhash:\n                            type: string\n                    example:\n                      data:\n                        blknum: 123000\n                        txindex: 111\n                        txhash: '0xbdf562c24ace032176e27621073df58ce1c6f65de3b5932343b70ba03c72132d'\n        '500':\n          description: Returns an internal server error\n          content:\n            application/json:\n              schema:\n                description: The response schema for an error\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        description: The object schema for an error\n                        type: object\n                        properties:\n                          object:\n                            type: string\n                          code:\n                            type: string\n                          description:\n                            type: string\n                          messages:\n                            type: object\n                        required:\n                          - object\n                          - code\n                          - description\n                          - messages\n                    required:\n                      - data\n                    example:\n                      success: false\n                      data:\n                        object: error\n                        code: 'server:internal_server_error'\n                        description: Something went wrong on the server\n                        messages:\n                          error_key: error_reason\n  /transaction.batch_submit:\n    post:\n      tags:\n        - Transaction\n      summary: This endpoint submits an array of signed transaction to the child chain.\n      description: |\n        Normally you should call the Watcher's Transaction - Submit instead of this. The Watcher's version performs various security and validation checks (TO DO) before submitting the transaction, so is much safer. However, if the Watcher is not available this version exists.\n      operationId: batch_submit\n      requestBody:\n        description: 'Array of signed transactions, RLP-encoded to bytes, and HEX-encoded to string'\n        required: true\n        content:\n          application/json:\n            schema:\n              title: TransactionBatchSubmitBodySchema\n              type: object\n              properties:\n                transactions:\n                  type: array\n                  items:\n                    type: string\n              required:\n                - transactions\n              example:\n                transactions:\n                  - 0xf8d083015ba98080808080940000...\n                  - 0xf8d083a15ba98080808080920000...\n      responses:\n        '200':\n          description: Transaction batch submission successful response\n          content:\n            application/json:\n              schema:\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        type: array\n                        items:\n                          type: object\n                          properties:\n                            blknum:\n                              type: integer\n                              format: int64\n                            txindex:\n                              type: integer\n                              format: int16\n                            txhash:\n                              type: string\n                    example:\n                      data:\n                        - blknum: 123000\n                          txindex: 111\n                          txhash: '0xbdf562c24ace032176e27621073df58ce1c6f65de3b5932343b70ba03c72132d'\n        '500':\n          description: Returns an internal server error\n          content:\n            application/json:\n              schema:\n                description: The response schema for an error\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        description: The object schema for an error\n                        type: object\n                        properties:\n                          object:\n                            type: string\n                          code:\n                            type: string\n                          description:\n                            type: string\n                          messages:\n                            type: object\n                        required:\n                          - object\n                          - code\n                          - description\n                          - messages\n                    required:\n                      - data\n                    example:\n                      success: false\n                      data:\n                        object: error\n                        code: 'server:internal_server_error'\n                        description: Something went wrong on the server\n                        messages:\n                          error_key: error_reason\n  /in_flight_exit.get_data:\n    post:\n      tags:\n        - InFlightExit\n      summary: Gets exit data for an in-flight exit.\n      description: |\n        Exit data are arguments to `startInFlightExit` root chain contract function.\n      operationId: in_flight_exit_get_data\n      requestBody:\n        description: In-flight transaction bytes body\n        required: true\n        content:\n          application/json:\n            schema:\n              title: InFlightExitTxBytesBodySchema\n              type: object\n              properties:\n                txbytes:\n                  type: string\n              required:\n                - txbytes\n              example:\n                txbytes: 0xf3170101c0940000...\n      responses:\n        '200':\n          description: Get in-flight exit successful response\n          content:\n            application/json:\n              schema:\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        type: object\n                        description: The object schema for an in flight exit\n                        properties:\n                          in_flight_tx:\n                            type: string\n                          input_txs:\n                            type: array\n                            items:\n                              type: string\n                          input_txs_inclusion_proofs:\n                            type: array\n                            items:\n                              type: string\n                          in_flight_tx_sigs:\n                            type: array\n                            items:\n                              type: string\n                          input_utxos_pos:\n                            type: array\n                            items:\n                              type: integer\n                              format: int256\n                    example:\n                      data:\n                        in_flight_tx: 0xf3170101c0940000...\n                        input_txs:\n                          - 0xa3470101c0940000...\n                        input_txs_inclusion_proofs:\n                          - 0xcedb8b31d1e4...\n                        in_flight_tx_sigs:\n                          - 0x6bfb9b2dbe32...\n                        input_utxos_pos:\n                          - 300010002001\n        '500':\n          description: Returns an internal server error\n          content:\n            application/json:\n              schema:\n                description: The response schema for an error\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        description: The object schema for an error\n                        type: object\n                        properties:\n                          object:\n                            type: string\n                          code:\n                            type: string\n                          description:\n                            type: string\n                          messages:\n                            type: object\n                        required:\n                          - object\n                          - code\n                          - description\n                          - messages\n                    required:\n                      - data\n                    example:\n                      success: false\n                      data:\n                        object: error\n                        code: 'server:internal_server_error'\n                        description: Something went wrong on the server\n                        messages:\n                          error_key: error_reason\n  /in_flight_exit.get_competitor:\n    post:\n      tags:\n        - InFlightExit\n      summary: Returns a competitor to an in-flight exit.\n      description: Note that if the competing transaction has not been put into a block `competing_tx_pos` and `competing_proof` will not be returned.\n      operationId: in_flight_exit_get_competitor\n      requestBody:\n        description: In-flight transaction bytes body\n        required: true\n        content:\n          application/json:\n            schema:\n              title: InFlightExitTxBytesBodySchema\n              type: object\n              properties:\n                txbytes:\n                  type: string\n              required:\n                - txbytes\n              example:\n                txbytes: 0xf3170101c0940000...\n      responses:\n        '200':\n          description: Get competitor successful response\n          content:\n            application/json:\n              schema:\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        type: object\n                        description: The object schema for a competitor\n                        properties:\n                          in_flight_txbytes:\n                            type: string\n                          in_flight_input_index:\n                            type: integer\n                            format: int8\n                          competing_txbytes:\n                            type: string\n                          competing_input_index:\n                            type: integer\n                            format: int8\n                          competing_sig:\n                            type: string\n                          competing_tx_pos:\n                            type: integer\n                            format: int256\n                          competing_proof:\n                            type: string\n                          input_tx:\n                            type: string\n                          input_utxo_pos:\n                            type: integer\n                            format: int256\n                    example:\n                      data:\n                        in_flight_txbytes: 0xf3170101c0940000...\n                        in_flight_input_index: 1\n                        competing_txbytes: 0x5df13a6bee20000...\n                        competing_input_index: 1\n                        competing_sig: 0xa3470101c0940000...\n                        competing_tx_pos: 26000003920000\n                        competing_proof: 0xcedb8b31d1e4...\n                        input_tx: 0xaaa70101c0940000...\n                        input_utxo_pos: 300010002001\n        '500':\n          description: Returns an internal server error\n          content:\n            application/json:\n              schema:\n                description: The response schema for an error\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        description: The object schema for an error\n                        type: object\n                        properties:\n                          object:\n                            type: string\n                          code:\n                            type: string\n                          description:\n                            type: string\n                          messages:\n                            type: object\n                        required:\n                          - object\n                          - code\n                          - description\n                          - messages\n                    required:\n                      - data\n                    example:\n                      success: false\n                      data:\n                        object: error\n                        code: 'server:internal_server_error'\n                        description: Something went wrong on the server\n                        messages:\n                          error_key: error_reason\n  /in_flight_exit.prove_canonical:\n    post:\n      tags:\n        - InFlightExit\n      summary: Proves transaction is canonical.\n      description: 'To respond to a challenge to an in-flight exit, this proves that the transaction has been put into a block (and therefore is canonical).'\n      operationId: in_flight_exit_prove_canonical\n      requestBody:\n        description: In-flight transaction bytes body\n        required: true\n        content:\n          application/json:\n            schema:\n              title: InFlightExitTxBytesBodySchema\n              type: object\n              properties:\n                txbytes:\n                  type: string\n              required:\n                - txbytes\n              example:\n                txbytes: 0xf3170101c0940000...\n      responses:\n        '200':\n          description: Prove canonical successful response\n          content:\n            application/json:\n              schema:\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        type: object\n                        description: The object schema for a canonical proof\n                        properties:\n                          in_flight_txbytes:\n                            type: string\n                          in_flight_tx_pos:\n                            type: integer\n                            format: int256\n                          in_flight_proof:\n                            type: string\n                    example:\n                      data:\n                        in_flight_txbytes: 0xf3170101c0940000...\n                        in_flight_tx_pos: 26000003920000\n                        in_flight_proof: 0xcedb8b31d1e4...\n        '500':\n          description: Returns an internal server error\n          content:\n            application/json:\n              schema:\n                description: The response schema for an error\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        description: The object schema for an error\n                        type: object\n                        properties:\n                          object:\n                            type: string\n                          code:\n                            type: string\n                          description:\n                            type: string\n                          messages:\n                            type: object\n                        required:\n                          - object\n                          - code\n                          - description\n                          - messages\n                    required:\n                      - data\n                    example:\n                      success: false\n                      data:\n                        object: error\n                        code: 'server:internal_server_error'\n                        description: Something went wrong on the server\n                        messages:\n                          error_key: error_reason\n  /in_flight_exit.get_input_challenge_data:\n    post:\n      tags:\n        - InFlightExit\n      summary: Gets the data to challenge an invalid input piggybacked on an in-flight exit.\n      description: 'To respond to invalid piggybacked input in non-canonical in-flight transaction provides data needed to challenge it, e.g. transaction that spent this input and signature.'\n      operationId: in_flight_exit_get_input_challenge_data\n      requestBody:\n        description: In-flight transaction bytes and invalid input index\n        required: true\n        content:\n          application/json:\n            schema:\n              title: InFlightExitInputChallengeDataBodySchema\n              type: object\n              properties:\n                txbytes:\n                  type: string\n                input_index:\n                  type: integer\n                  format: int8\n              required:\n                - txbytes\n                - input_index\n              example:\n                txbytes: 0xf3170101c0940000...\n                input_index: 1\n      responses:\n        '200':\n          description: Get input challenge successful response\n          content:\n            application/json:\n              schema:\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        type: object\n                        description: The object schema for an input challenge data\n                        properties:\n                          in_flight_txbytes:\n                            type: string\n                          in_flight_input_index:\n                            type: integer\n                            format: int8\n                          spending_txbytes:\n                            type: string\n                          spending_input_index:\n                            type: integer\n                            format: int8\n                          spending_sig:\n                            type: string\n                          input_tx:\n                            type: string\n                          input_utxo_pos:\n                            type: integer\n                            format: int256\n                    example:\n                      data:\n                        in_flight_txbytes: 0xf3170101c0940000...\n                        in_flight_input_index: 1\n                        spending_txbytes: 0x5df13a6bee20000...\n                        spending_input_index: 1\n                        spending_sig: 0xa3470101c0940000...\n                        input_tx: 0xaaa70101c0940000...\n                        input_utxo_pos: 300010002001\n        '500':\n          description: Returns an internal server error\n          content:\n            application/json:\n              schema:\n                description: The response schema for an error\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        description: The object schema for an error\n                        type: object\n                        properties:\n                          object:\n                            type: string\n                          code:\n                            type: string\n                          description:\n                            type: string\n                          messages:\n                            type: object\n                        required:\n                          - object\n                          - code\n                          - description\n                          - messages\n                    required:\n                      - data\n                    example:\n                      success: false\n                      data:\n                        object: error\n                        code: 'server:internal_server_error'\n                        description: Something went wrong on the server\n                        messages:\n                          error_key: error_reason\n  /in_flight_exit.get_output_challenge_data:\n    post:\n      tags:\n        - InFlightExit\n      summary: Gets the data to challenge an invalid output piggybacked on an in-flight exit.\n      description: 'To respond to invalid piggybacked output in canonical in-flight transaction provides data needed to challenge it, e.g. in-flight transaction inclusion proof, transaction that spent this output and signature.'\n      operationId: in_flight_exit_get_output_challenge_data\n      requestBody:\n        description: In-flight transaction bytes and invalid output index\n        required: true\n        content:\n          application/json:\n            schema:\n              title: InFlightExitOutputChallengeDataBodySchema\n              type: object\n              properties:\n                txbytes:\n                  type: string\n                output_index:\n                  type: integer\n                  format: int8\n              required:\n                - txbytes\n                - output_index\n              example:\n                txbytes: 0xf3170101c0940000...\n                output_index: 0\n      responses:\n        '200':\n          description: Get output challenge successful response\n          content:\n            application/json:\n              schema:\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        type: object\n                        description: The object schema for an output challenge data\n                        properties:\n                          in_flight_txbytes:\n                            type: string\n                          in_flight_output_pos:\n                            type: integer\n                            format: int256\n                          in_flight_proof:\n                            type: string\n                          spending_txbytes:\n                            type: string\n                          spending_input_index:\n                            type: integer\n                            format: int8\n                          spending_sig:\n                            type: string\n                    example:\n                      data:\n                        in_flight_txbytes: 0xf3170101c0940000...\n                        in_flight_output_pos: 21000634002\n                        in_flight_proof: 0xcedb8b31d1e4...\n                        spending_txbytes: 0x5df13a6bee20000...\n                        spending_input_index: 1\n                        spending_sig: 0xa3470101c0940000...\n        '500':\n          description: Returns an internal server error\n          content:\n            application/json:\n              schema:\n                description: The response schema for an error\n                allOf:\n                  - description: The response schema for a successful operation\n                    type: object\n                    properties:\n                      version:\n                        type: string\n                      success:\n                        type: boolean\n                      data:\n                        type: object\n                      service_name:\n                        type: string\n                    required:\n                      - service_name\n                      - version\n                      - success\n                      - data\n                    example:\n                      service_name: watcher\n                      version: 1.0.0+abcdefa\n                      success: true\n                      data: {}\n                  - type: object\n                    properties:\n                      data:\n                        description: The object schema for an error\n                        type: object\n                        properties:\n                          object:\n                            type: string\n                          code:\n                            type: string\n                          description:\n                            type: string\n                          messages:\n                            type: object\n                        required:\n                          - object\n                          - code\n                          - description\n                          - messages\n                    required:\n                      - data\n                    example:\n                      success: false\n                      data:\n                        object: error\n                        code: 'server:internal_server_error'\n                        description: Something went wrong on the server\n                        messages:\n                          error_key: error_reason\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/shared/paths.yaml",
    "content": ""
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/shared/request_bodies.yaml",
    "content": ""
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/shared/schemas.yaml",
    "content": "ErrorSchema:\n  description: The object schema for an error\n  type: object\n  properties:\n    object:\n      type: string\n    code:\n      type: string\n    description:\n      type: string\n    messages:\n      type: object\n  required:\n    - object\n    - code\n    - description\n    - messages\n"
  },
  {
    "path": "apps/omg_watcher_rpc/priv/swagger/swagger.md",
    "content": "# Designing API specifications\n\nOpenAPI definitions, allow devs to specify the operations and metadata of their APIs in machine-readable form. This enables them to automate various processes around the API lifecycle.\n\n### Development specs\n\nIn order to facilitate the development and maintenance of the API documentation, the open api spec is splat into multiple files.\n\nThese files are grouped under a resource and each resource has 5 spec files. The basic structure is as follow:\n```\n/info_api_specs\n  /resource1 (account for example)\n    paths.yaml\n    request_bodies.yaml\n    response_schemas.yaml\n    responses.yaml\n    schemas.yaml\n  /resource2\n    paths.yaml\n    request_bodies.yaml\n    ...\n  ...\n```\n\nEach of these file contain different part of the API definition.\n\nWhen developing you should modify these files, under the `info_api_specs/` and `security_critical_api_specs/` folders and NOT directly the `info_api_specs.yaml` or `security_critical_api_specs.yaml` which are automatically generated.\n\n### Generating the final spec file\n\nWhen you are done editing the different spec files, you need to generate the final file which group all specifications together into one `\"big\"` file.\n\nIn order to do this you need to have the following installed and available:\n  - [node.js](https://nodejs.org/en/download/package-manager/)\n  - [swagger-cli](https://www.npmjs.com/package/swagger-cli). Install using: `npm install -g swagger-cli`\n  - [openapi-generator](https://github.com/OpenAPITools/openapi-generator). Install using: `https://github.com/OpenAPITools/openapi-generator`\n\nThen you need to run the following commands to generate the final spec.\n\n**Watcher Security-Critical API:**\n\n```\nswagger-cli bundle -r -t yaml -o apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs.yaml apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs/swagger.yaml\nopenapi-generator-cli validate -i apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs.yaml\n```\n\n**Watcher Info API:**\n\n```\nswagger-cli bundle -r -t yaml -o apps/omg_watcher_rpc/priv/swagger/info_api_specs.yaml apps/omg_watcher_rpc/priv/swagger/info_api_specs/swagger.yaml\nopenapi-generator-cli validate -i apps/omg_watcher_rpc/priv/swagger/info_api_specs.yaml\n```\n"
  },
  {
    "path": "apps/omg_watcher_rpc/test/omg_watcher_rpc/release_tasks/set_endpoint_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.ReleaseTasks.SetEndpointTest do\n  use ExUnit.Case, async: true\n  alias OMG.WatcherRPC.ReleaseTasks.SetEndpoint\n  alias OMG.WatcherRPC.Web.Endpoint\n\n  @app :omg_watcher_rpc\n\n  test \"if environment variables get applied in the configuration\" do\n    :ok = System.put_env(\"PORT\", \"1\")\n    :ok = System.put_env(\"HOSTNAME\", \"host\")\n    config = SetEndpoint.load([], [])\n    port = config |> Keyword.fetch!(@app) |> Keyword.fetch!(Endpoint) |> Keyword.fetch!(:http) |> Keyword.fetch!(:port)\n    host = config |> Keyword.fetch!(@app) |> Keyword.fetch!(Endpoint) |> Keyword.fetch!(:url) |> Keyword.fetch!(:host)\n    assert port == 1\n    assert host == \"host\"\n  end\n\n  test \"if default configuration is used when there's no environment variables\" do\n    :ok = System.delete_env(\"PORT\")\n    :ok = System.delete_env(\"HOSTNAME\")\n    config = SetEndpoint.load([], [])\n    port = config |> Keyword.fetch!(@app) |> Keyword.fetch!(Endpoint) |> Keyword.fetch!(:http) |> Keyword.fetch!(:port)\n    host = config |> Keyword.fetch!(@app) |> Keyword.fetch!(Endpoint) |> Keyword.fetch!(:url) |> Keyword.fetch!(:host)\n    config_port = Application.get_env(@app, Endpoint)[:http][:port]\n    config_host = Application.get_env(@app, Endpoint)[:url][:host]\n    assert port == config_port\n    assert host == config_host\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/test/omg_watcher_rpc/release_tasks/set_tracer_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.ReleaseTasks.SetTracerTest do\n  use ExUnit.Case, async: true\n  import ExUnit.CaptureLog, only: [capture_log: 1]\n\n  alias OMG.WatcherRPC.ReleaseTasks.SetTracer\n  alias OMG.WatcherRPC.Tracer\n\n  @app :omg_watcher_rpc\n  setup do\n    {:ok, pid} = __MODULE__.System.start_link([])\n    nil = Process.put(__MODULE__.System, pid)\n    :ok\n  end\n\n  test \"if environment variables get applied in the configuration\" do\n    :ok = __MODULE__.System.put_env(\"DD_DISABLED\", \"TRUE\")\n    :ok = __MODULE__.System.put_env(\"APP_ENV\", \"YOLO\")\n\n    assert capture_log(fn ->\n             config = SetTracer.load([], system_adapter: __MODULE__.System)\n             disabled = config |> Keyword.fetch!(@app) |> Keyword.fetch!(Tracer) |> Keyword.fetch!(:disabled?)\n             env = config |> Keyword.fetch!(@app) |> Keyword.fetch!(Tracer) |> Keyword.fetch!(:env)\n\n             assert disabled == true\n             # if it's disabled, env doesn't matter, so we set it to an empty string\n             assert env == \"\"\n           end)\n  end\n\n  test \"if default configuration is used when there's no environment variables\" do\n    :ok = __MODULE__.System.put_env(\"HOSTNAME\", \"this is my tracer test 3\")\n\n    assert capture_log(fn ->\n             config = SetTracer.load([], system_adapter: __MODULE__.System)\n             # we set env to an empty string because disabled? is set to true!\n             configuration = @app |> Application.get_env(Tracer) |> Keyword.put(:env, \"\") |> Enum.sort()\n             tracer_config = config |> Keyword.get(@app) |> Keyword.get(Tracer) |> Enum.sort()\n             assert configuration == tracer_config\n           end)\n  end\n\n  test \"if exit is thrown when faulty configuration is used\" do\n    :ok = __MODULE__.System.put_env(\"DD_DISABLED\", \"TRUEeee\")\n    catch_exit(SetTracer.load([], system_adapter: __MODULE__.System))\n  end\n\n  defmodule System do\n    def start_link(args), do: GenServer.start_link(__MODULE__, args, [])\n    def get_env(key), do: __MODULE__ |> Process.get() |> GenServer.call({:get_env, key})\n    def put_env(key, value), do: __MODULE__ |> Process.get() |> GenServer.call({:put_env, key, value})\n    def init(_), do: {:ok, %{}}\n\n    def handle_call({:get_env, key}, _, state) do\n      {:reply, state[key], state}\n    end\n\n    def handle_call({:put_env, key, value}, _, state) do\n      {:reply, :ok, Map.put(state, key, value)}\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/test/omg_watcher_rpc/tracer_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.TracerTest do\n  @app :omg_watcher_rpc\n\n  use ExUnit.Case\n  import Plug.Conn\n  alias OMG.WatcherRPC.Configuration\n  alias OMG.WatcherRPC.Tracer\n\n  setup do\n    original_mode = Application.get_env(:omg_watcher_rpc, :api_mode)\n    _ = on_exit(fn -> Application.put_env(:omg_watcher_rpc, :api_mode, original_mode) end)\n\n    :ok\n  end\n\n  test \"api responses without errors get traced with metadata\" do\n    :ok = Application.put_env(@app, :api_mode, :watcher)\n    version = Configuration.version()\n\n    resp_body = \"\"\"\n    {\n      \"data\": [],\n      \"service_name\": \"watcher\",\n      \"success\": true,\n      \"version\": \"#{version}\"\n    }\n    \"\"\"\n\n    conn =\n      :get\n      |> Phoenix.ConnTest.build_conn(\"/alerts.get\")\n      |> Plug.Conn.resp(200, resp_body)\n\n    trace_metadata = Tracer.add_trace_metadata(conn)\n\n    expected =\n      Keyword.new([\n        {:tags, [version: version]},\n        {:service, :watcher},\n        {:http, [method: \"GET\", query_string: \"\", status_code: 200, url: \"/alerts.get\", user_agent: nil]},\n        {:resource, \"GET /alerts.get\"},\n        {:type, :web}\n      ])\n\n    assert trace_metadata == expected\n  end\n\n  test \"if api responses with errors get traced with metadata\" do\n    :ok = Application.put_env(@app, :api_mode, :watcher_info)\n    version = Configuration.version()\n\n    resp_body = \"\"\"\n    {\n      \"data\": {\n      \"code\": \"operation:not_found\",\n      \"description\": \"Operation cannot be found. Check request URL.\",\n      \"object\": \"error\"\n      },\n      \"service_name\": \"watcher_info\",\n      \"success\": false,\n      \"version\": \"#{version}\"\n    }\n    \"\"\"\n\n    conn =\n      :post\n      |> Phoenix.ConnTest.build_conn(\"/\")\n      |> Plug.Conn.resp(200, resp_body)\n      |> assign(:error_type, \"operation:not_found\")\n      |> assign(:error_msg, \"Operation cannot be found. Check request URL.\")\n\n    trace_metadata = Tracer.add_trace_metadata(conn)\n\n    expected =\n      Keyword.new([\n        {\n          :tags,\n          [\n            {:version, version},\n            {:\"error.type\", \"operation:not_found\"},\n            {:\"error.msg\", \"Operation cannot be found. Check request URL.\"}\n          ]\n        },\n        {:error, [error: true]},\n        {:service, :watcher_info},\n        {:http, [method: \"POST\", query_string: \"\", status_code: 200, url: \"/\", user_agent: nil]},\n        {:resource, \"POST /\"},\n        {:type, :web}\n      ])\n\n    assert trace_metadata == expected\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/test/omg_watcher_rpc/web/conn_case.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.ConnCase do\n  @moduledoc \"\"\"\n  This module defines the test case to be used by\n  tests that require setting up a connection.\n\n  Such tests rely on `Phoenix.ConnTest` and also\n  import other functionality to make it easier\n  to build common datastructures and query the data layer.\n\n  Finally, if the test case interacts with the database,\n  it cannot be async. For this reason, every test runs\n  inside a transaction which is reset at the beginning\n  of the test unless the test case is marked as async.\n  \"\"\"\n\n  alias Ecto.Adapters.SQL\n  alias OMG.WatcherInfo\n  use ExUnit.CaseTemplate\n\n  using do\n    quote do\n      # Import conveniences for testing with connections\n      import Plug.Conn\n      import Phoenix.ConnTest\n      import OMG.WatcherRPC.Web.Router.Helpers\n\n      # The default endpoint for testing\n      @endpoint OMG.WatcherRPC.Web.Endpoint\n    end\n  end\n\n  setup tags do\n    :ok = SQL.Sandbox.checkout(OMG.WatcherInfo.DB.Repo)\n\n    unless tags[:async] do\n      SQL.Sandbox.mode(WatcherInfo.DB.Repo, {:shared, self()})\n    end\n\n    {:ok, conn: Phoenix.ConnTest.build_conn()}\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/test/omg_watcher_rpc/web/controllers/account_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.Controller.AccountTest do\n  use ExUnitFixtures\n  use ExUnit.Case, async: false\n  use OMG.Watcher.Fixtures\n  use OMG.WatcherInfo.Fixtures\n\n  alias OMG.Utils.HttpRPC.Encoding\n  alias OMG.Watcher.Crypto\n  alias OMG.Watcher.Utxo\n  alias OMG.WatcherInfo.DB\n  alias Support.WatcherHelper\n\n  require Utxo\n\n  @eth <<0::160>>\n  @payment_output_type OMG.Watcher.WireFormatTypes.output_type_for(:output_payment_v1)\n  @eth_hex @eth |> Encoding.to_hex()\n  @other_token <<127::160>>\n  @other_token_hex @other_token |> Encoding.to_hex()\n\n  @tag fixtures: [:alice, :bob, :blocks_inserter, :initial_blocks]\n  test \"Account balance groups account tokens and provide sum of available funds\", %{\n    blocks_inserter: blocks_inserter,\n    alice: alice,\n    bob: bob\n  } do\n    assert [%{\"currency\" => @eth_hex, \"amount\" => 349}] == WatcherHelper.success?(\"account.get_balance\", body_for(bob))\n\n    # adds other token funds for alice to make more interesting\n    blocks_inserter.([\n      {11_000, [OMG.Watcher.TestHelper.create_recovered([], @other_token, [{alice, 121}, {alice, 256}])]}\n    ])\n\n    data = WatcherHelper.success?(\"account.get_balance\", body_for(alice))\n\n    assert [\n             %{\"currency\" => @eth_hex, \"amount\" => 201},\n             %{\"currency\" => @other_token_hex, \"amount\" => 377}\n           ] == data |> Enum.sort(&(Map.get(&1, \"currency\") <= Map.get(&2, \"currency\")))\n  end\n\n  @tag fixtures: [:phoenix_ecto_sandbox]\n  test \"Account balance for non-existing account responds with empty array\" do\n    no_account = %{addr: <<0::160>>}\n\n    assert [] == WatcherHelper.success?(\"account.get_balance\", body_for(no_account))\n  end\n\n  defp body_for(%{addr: address}) do\n    %{\"address\" => Encoding.to_hex(address)}\n  end\n\n  @tag fixtures: [:initial_blocks, :alice]\n  test \"returns last transactions that involve given address\", %{\n    alice: alice\n  } do\n    # refer to `/transaction.all` tests for more thorough cases, this is the same\n    alice_addr = Encoding.to_hex(alice.addr)\n\n    assert [_] = WatcherHelper.success?(\"account.get_transactions\", %{\"address\" => alice_addr, \"limit\" => 1})\n  end\n\n  @tag fixtures: [:phoenix_ecto_sandbox]\n  test \"account.get_balance handles improper type of parameter\" do\n    assert %{\n             \"object\" => \"error\",\n             \"code\" => \"operation:bad_request\",\n             \"description\" => \"Parameters required by this operation are missing or incorrect.\",\n             \"messages\" => %{\n               \"validation_error\" => %{\n                 \"parameter\" => \"address\",\n                 \"validator\" => \":hex\"\n               }\n             }\n           } == WatcherHelper.no_success?(\"account.get_balance\", %{\"address\" => 1_234_567_890})\n  end\n\n  @tag fixtures: [:alice, :phoenix_ecto_sandbox]\n  test \"account.get_balance returns bad request error if address is passed as a query parameter\", %{\n    alice: alice\n  } do\n    %{\"address\" => address} = body_for(alice)\n\n    assert %{\n             \"object\" => \"error\",\n             \"code\" => \"operation:bad_request\",\n             \"description\" => \"Parameters required by this operation are missing or incorrect.\",\n             \"messages\" => %{\n               \"validation_error\" => %{\n                 \"parameter\" => \"address\",\n                 \"validator\" => \":hex\"\n               }\n             }\n           } == WatcherHelper.no_success?(\"account.get_balance?address=#{address}\")\n  end\n\n  describe \"standard_exitable\" do\n    @tag fixtures: [:phoenix_ecto_sandbox, :db_initialized, :carol]\n    test \"no utxos are returned for non-existing addresses\", %{carol: carol} do\n      assert [] == WatcherHelper.get_exitable_utxos(carol.addr)\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox, :db_initialized, :alice, :bob]\n    test \"get_utxos and get_exitable_utxos have the same return values\", %{alice: alice, bob: bob} do\n      DB.EthEvent.insert_deposits!([\n        %{\n          root_chain_txhash: Crypto.hash(<<1000::256>>),\n          log_index: 0,\n          eth_height: 1,\n          owner: alice.addr,\n          currency: @eth,\n          amount: 333,\n          blknum: 1\n        }\n      ])\n\n      # TODO: this test is brittle because of the way the DB entries are hardcoded\n      OMG.DB.multi_update([\n        {:put, :utxo,\n         {\n           {1, 0, 0},\n           %{\n             output: %{amount: 333, currency: @eth, owner: alice.addr, output_type: @payment_output_type},\n             creating_txhash: nil\n           }\n         }},\n        {:put, :utxo,\n         {\n           {2, 0, 0},\n           %{\n             output: %{amount: 100, currency: @eth, owner: bob.addr, output_type: @payment_output_type},\n             creating_txhash: nil\n           }\n         }}\n      ])\n\n      # utxos contain extra fields such as `spending_txhash` so we compare only the fields we expect from both.\n      fields = [\"blknum\", \"txindex\", \"oindex\", \"utxo_pos\", \"amount\", \"currency\", \"owner\"]\n\n      exitable_utxos =\n        alice.addr\n        |> WatcherHelper.get_exitable_utxos()\n        |> Enum.map(fn utxo -> Map.take(utxo, fields) end)\n\n      utxos =\n        alice.addr\n        |> WatcherHelper.get_utxos()\n        |> Enum.map(fn utxo -> Map.take(utxo, fields) end)\n\n      assert utxos == exitable_utxos\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"account.get_exitable_utxos handles improper type of parameter\" do\n      assert %{\n               \"object\" => \"error\",\n               \"code\" => \"operation:bad_request\",\n               \"description\" => \"Parameters required by this operation are missing or incorrect.\",\n               \"messages\" => %{\n                 \"validation_error\" => %{\n                   \"parameter\" => \"address\",\n                   \"validator\" => \":hex\"\n                 }\n               }\n             } == WatcherHelper.no_success?(\"account.get_exitable_utxos\", %{\"address\" => 1_234_567_890})\n    end\n  end\n\n  @tag fixtures: [:initial_blocks, :carol]\n  test \"no utxos are returned for non-existing addresses\", %{carol: carol} do\n    assert [] == WatcherHelper.get_utxos(carol.addr)\n  end\n\n  @tag fixtures: [:initial_blocks, :alice]\n  test \"utxo from initial blocks are available\", %{alice: alice} do\n    alice_enc = alice.addr |> Encoding.to_hex()\n\n    assert [\n             %{\n               \"amount\" => 1,\n               \"currency\" => @eth_hex,\n               \"blknum\" => 2000,\n               \"txindex\" => 0,\n               \"oindex\" => 1,\n               \"owner\" => ^alice_enc\n             },\n             %{\n               \"amount\" => 150,\n               \"currency\" => @eth_hex,\n               \"blknum\" => 3000,\n               \"txindex\" => 0,\n               \"oindex\" => 0,\n               \"owner\" => ^alice_enc\n             },\n             %{\n               \"amount\" => 50,\n               \"currency\" => @eth_hex,\n               \"blknum\" => 3000,\n               \"txindex\" => 1,\n               \"oindex\" => 1,\n               \"owner\" => ^alice_enc\n             }\n           ] = WatcherHelper.get_utxos(alice.addr)\n  end\n\n  @tag fixtures: [:initial_blocks, :alice]\n  test \"encoded utxo positions are delivered\", %{alice: alice} do\n    [%{\"utxo_pos\" => utxo_pos, \"blknum\" => blknum, \"txindex\" => txindex, \"oindex\" => oindex} | _] =\n      WatcherHelper.get_utxos(alice.addr)\n\n    assert Utxo.position(^blknum, ^txindex, ^oindex) = utxo_pos |> Utxo.Position.decode!()\n  end\n\n  @tag fixtures: [:initial_blocks, :bob, :carol]\n  test \"spent utxos are moved to new owner\", %{bob: bob, carol: carol} do\n    [] = WatcherHelper.get_utxos(carol.addr)\n\n    # bob spends his utxo to carol\n    block_application = %{\n      transactions: [OMG.Watcher.TestHelper.create_recovered([{2000, 0, 0, bob}], @eth, [{bob, 49}, {carol, 50}])],\n      number: 11_000,\n      hash: <<?#::256>>,\n      timestamp: :os.system_time(:second),\n      eth_height: 10\n    }\n\n    {:ok, _} = DB.Block.insert_from_block_application(block_application)\n\n    assert [\n             %{\n               \"amount\" => 50,\n               \"blknum\" => 11_000,\n               \"txindex\" => 0,\n               \"oindex\" => 1,\n               \"currency\" => @eth_hex\n             }\n           ] = WatcherHelper.get_utxos(carol.addr)\n  end\n\n  @tag fixtures: [:initial_blocks, :bob]\n  test \"unspent deposits are a part of utxo set\", %{bob: bob} do\n    bob_enc = bob.addr |> Encoding.to_hex()\n    deposited_utxo = bob.addr |> WatcherHelper.get_utxos() |> Enum.find(&(&1[\"blknum\"] < 1000))\n\n    assert %{\n             \"amount\" => 100,\n             \"currency\" => @eth_hex,\n             \"blknum\" => 2,\n             \"txindex\" => 0,\n             \"oindex\" => 0,\n             \"owner\" => ^bob_enc\n           } = deposited_utxo\n  end\n\n  @tag fixtures: [:initial_blocks, :alice]\n  test \"spent deposits are not a part of utxo set\", %{alice: alice} do\n    assert utxos = WatcherHelper.get_utxos(alice.addr)\n\n    assert [] = utxos |> Enum.filter(&(&1[\"blknum\"] < 1000))\n  end\n\n  @tag fixtures: [:initial_blocks, :carol, :bob]\n  test \"deposits are spent\", %{carol: carol, bob: bob} do\n    assert [] = WatcherHelper.get_utxos(carol.addr)\n\n    assert utxos = WatcherHelper.get_utxos(bob.addr)\n\n    # bob has 1 unspent deposit\n    assert %{\n             \"amount\" => 100,\n             \"currency\" => @eth_hex,\n             \"blknum\" => blknum,\n             \"txindex\" => 0,\n             \"oindex\" => 0\n           } = utxos |> Enum.find(&(&1[\"blknum\"] < 1000))\n\n    block_application = %{\n      transactions: [OMG.Watcher.TestHelper.create_recovered([{blknum, 0, 0, bob}], @eth, [{carol, 100}])],\n      number: 11_000,\n      hash: <<?#::256>>,\n      timestamp: :os.system_time(:second),\n      eth_height: 10\n    }\n\n    {:ok, _} = DB.Block.insert_from_block_application(block_application)\n\n    utxos = WatcherHelper.get_utxos(bob.addr)\n\n    # bob has spent his deposit\n    assert [] == utxos |> Enum.filter(&(&1[\"blknum\"] < 1000))\n\n    carol_enc = carol.addr |> Encoding.to_hex()\n\n    # carol has new utxo from above tx\n    assert [\n             %{\n               \"amount\" => 100,\n               \"currency\" => @eth_hex,\n               \"blknum\" => 11_000,\n               \"txindex\" => 0,\n               \"oindex\" => 0,\n               \"owner\" => ^carol_enc\n             }\n           ] = WatcherHelper.get_utxos(carol.addr)\n  end\n\n  @tag fixtures: [:phoenix_ecto_sandbox]\n  test \"account.get_utxos handles improper type of parameter\" do\n    assert %{\n             \"object\" => \"error\",\n             \"code\" => \"operation:bad_request\",\n             \"description\" => \"Parameters required by this operation are missing or incorrect.\",\n             \"messages\" => %{\n               \"validation_error\" => %{\n                 \"parameter\" => \"address\",\n                 \"validator\" => \":hex\"\n               }\n             }\n           } == WatcherHelper.no_success?(\"account.get_utxos\", %{\"address\" => 1_234_567_890})\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/test/omg_watcher_rpc/web/controllers/alarm_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.Controller.AlarmTest do\n  use ExUnitFixtures\n  use ExUnit.Case, async: true\n\n  use OMG.Watcher.Fixtures\n  use OMG.WatcherInfo.Fixtures\n  import Plug.Conn\n  import Phoenix.ConnTest\n\n  @endpoint OMG.WatcherRPC.Web.Endpoint\n  setup do\n    {:ok, apps} = Application.ensure_all_started(:omg_status)\n\n    Enum.each(\n      :gen_event.call(:alarm_handler, OMG.Status.Alert.AlarmHandler, :get_alarms),\n      fn alarm -> :alarm_handler.clear_alarm(alarm) end\n    )\n\n    on_exit(fn ->\n      Enum.each(Enum.reverse(apps), fn app -> :ok = Application.stop(app) end)\n    end)\n  end\n\n  ### a very basic test of empty alarms should be sufficient, alarms encoding is\n  ### covered in OMG.Utils.HttpRPC.ResponseTest\n  @tag fixtures: [:phoenix_ecto_sandbox, :db_initialized]\n  test \"if the controller returns the correct result when there's no alarms raised\", _ do\n    assert [] == get(\"alarm.get\")\n  end\n\n  @tag fixtures: [:phoenix_ecto_sandbox, :db_initialized]\n  test \"sets remote ip from cf-connecting-ip header\", _ do\n    response =\n      build_conn()\n      |> put_req_header(\"content-type\", \"application/json\")\n      |> put_req_header(\"cf-connecting-ip\", \"99.99.99.99\")\n      |> get(\"alarm.get\")\n\n    assert response.remote_ip == {99, 99, 99, 99}\n  end\n\n  defp get(path) do\n    response_body = rpc_call_get(path, 200)\n    version = Map.get(response_body, \"version\")\n\n    %{\"version\" => ^version, \"success\" => true, \"data\" => data} = response_body\n    data\n  end\n\n  defp rpc_call_get(path, expected_resp_status) do\n    response = get(put_req_header(build_conn(), \"content-type\", \"application/json\"), path)\n    # CORS check\n    assert [\"*\"] == get_resp_header(response, \"access-control-allow-origin\")\n\n    required_headers = [\n      \"access-control-allow-origin\",\n      \"access-control-expose-headers\",\n      \"access-control-allow-credentials\"\n    ]\n\n    for header <- required_headers do\n      assert header in Enum.map(response.resp_headers, &elem(&1, 0))\n    end\n\n    # CORS check\n    assert response.status == expected_resp_status\n    Jason.decode!(response.resp_body)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/test/omg_watcher_rpc/web/controllers/block_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.Controller.BlockTest do\n  use ExUnitFixtures\n  use ExUnit.Case, async: false\n  use OMG.WatcherInfo.Fixtures\n  use OMG.WatcherRPC.Web, :controller\n\n  import OMG.WatcherInfo.Factory\n\n  alias OMG.Eth.Encoding\n  alias OMG.Watcher.Merkle\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.TestHelper\n  alias OMG.Watcher.WireFormatTypes\n\n  alias Support.WatcherHelper\n\n  @valid_block %{\n    hash: \"0x\" <> String.duplicate(\"00\", 32),\n    number: 1000,\n    transactions: [\"0x00\"]\n  }\n  @eth <<0::160>>\n  @alice OMG.Watcher.TestHelper.generate_entity()\n  @bob OMG.Watcher.TestHelper.generate_entity()\n  @payment_tx_type WireFormatTypes.tx_type_for(:tx_payment_v1)\n\n  describe \"get_block/2\" do\n    @tag fixtures: [:initial_blocks]\n    test \"/block.get returns correct block if existent\" do\n      existent_blknum = 1000\n\n      %{\"success\" => success, \"data\" => data} = WatcherHelper.rpc_call(\"block.get\", %{blknum: existent_blknum}, 200)\n\n      assert data[\"blknum\"] == existent_blknum\n      assert success == true\n    end\n\n    @tag fixtures: [:initial_blocks]\n    test \"/block.get rejects parameter of wrong type\" do\n      string_blknum = \"1000\"\n      %{\"data\" => data} = WatcherHelper.rpc_call(\"block.get\", %{blknum: string_blknum}, 200)\n\n      expected = %{\n        \"code\" => \"operation:bad_request\",\n        \"description\" => \"Parameters required by this operation are missing or incorrect.\",\n        \"messages\" => %{\n          \"validation_error\" => %{\"parameter\" => \"blknum\", \"validator\" => \":integer\"}\n        },\n        \"object\" => \"error\"\n      }\n\n      assert data == expected\n    end\n\n    @tag fixtures: [:initial_blocks]\n    test \"/block.get endpoint rejects request without parameters\" do\n      missing_param = %{}\n      %{\"data\" => data} = WatcherHelper.rpc_call(\"block.get\", missing_param, 200)\n\n      expected = %{\n        \"code\" => \"operation:bad_request\",\n        \"description\" => \"Parameters required by this operation are missing or incorrect.\",\n        \"messages\" => %{\n          \"validation_error\" => %{\"parameter\" => \"blknum\", \"validator\" => \":integer\"}\n        },\n        \"object\" => \"error\"\n      }\n\n      assert data == expected\n    end\n\n    @tag fixtures: [:initial_blocks]\n    test \"/block.get returns expected error if block not found\" do\n      non_existent_block = 5000\n      %{\"data\" => data} = WatcherHelper.rpc_call(\"block.get\", %{blknum: non_existent_block}, 200)\n\n      expected = %{\n        \"code\" => \"get_block:block_not_found\",\n        \"description\" => nil,\n        \"object\" => \"error\"\n      }\n\n      assert data == expected\n    end\n  end\n\n  describe \"get_blocks/2\" do\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns the API response with the blocks\" do\n      _ = insert(:block, blknum: 1000, hash: <<1>>, eth_height: 1, timestamp: 100)\n      _ = insert(:block, blknum: 2000, hash: <<2>>, eth_height: 2, timestamp: 200)\n\n      request_data = %{\"limit\" => 200, \"page\" => 1}\n      response = WatcherHelper.rpc_call(\"block.all\", request_data, 200)\n\n      assert %{\n               \"success\" => true,\n               \"data\" => [\n                 %{\n                   \"blknum\" => 2000,\n                   \"eth_height\" => 2,\n                   \"hash\" => \"0x02\",\n                   \"timestamp\" => 200,\n                   \"tx_count\" => 0\n                 },\n                 %{\n                   \"blknum\" => 1000,\n                   \"eth_height\" => 1,\n                   \"hash\" => \"0x01\",\n                   \"timestamp\" => 100,\n                   \"tx_count\" => 0\n                 }\n               ],\n               \"data_paging\" => %{\n                 \"limit\" => 100,\n                 \"page\" => 1\n               },\n               \"service_name\" => _,\n               \"version\" => _\n             } = response\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns the error API response when an error occurs\" do\n      request_data = %{\"limit\" => \"this should error\", \"page\" => 1}\n      response = WatcherHelper.rpc_call(\"block.all\", request_data, 200)\n\n      assert %{\n               \"success\" => false,\n               \"data\" => %{\n                 \"object\" => \"error\",\n                 \"code\" => \"operation:bad_request\",\n                 \"description\" => \"Parameters required by this operation are missing or incorrect.\",\n                 \"messages\" => _\n               },\n               \"service_name\" => _,\n               \"version\" => _\n             } = response\n    end\n  end\n\n  describe \"validate_block/2\" do\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns the error API response if a parameter is incorectly formed\" do\n      invalid_hash = \"0x1234\"\n      invalid_params = Map.replace!(@valid_block, :hash, invalid_hash)\n\n      %{\"data\" => data} = WatcherHelper.rpc_call(\"block.validate\", invalid_params, 200)\n\n      expected = %{\n        \"code\" => \"operation:bad_request\",\n        \"description\" => \"Parameters required by this operation are missing or incorrect.\",\n        \"messages\" => %{\n          \"validation_error\" => %{\n            \"parameter\" => \"hash\",\n            \"validator\" => \"{:length, 32}\"\n          }\n        },\n        \"object\" => \"error\"\n      }\n\n      assert expected == data\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns the expected error if the block hash does not match the reconstructed Merkle root\" do\n      recovered_tx_1 = TestHelper.create_recovered([{1, 0, 0, @alice}], @eth, [{@bob, 100}])\n      recovered_tx_2 = TestHelper.create_recovered([{2, 0, 0, @alice}], @eth, [{@bob, 100}])\n\n      signed_txbytes =\n        [recovered_tx_1, recovered_tx_2]\n        |> Enum.map(fn tx -> tx.signed_tx_bytes end)\n        |> Enum.map(&Encoding.to_hex/1)\n\n      invalid_merkle_root = \"0x\" <> String.duplicate(\"00\", 32)\n\n      params = %{\n        hash: invalid_merkle_root,\n        number: 1000,\n        transactions: signed_txbytes\n      }\n\n      %{\"data\" => data} = WatcherHelper.rpc_call(\"block.validate\", params, 200)\n\n      assert data == %{\"valid\" => false}\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns the expected error if the transactions are incorrectly formed\" do\n      input_1 = {1, 0, 0, @alice}\n      input_2 = {2, 0, 0, @alice}\n      input_3 = {3, 0, 0, @alice}\n\n      signed_valid_tx = TestHelper.create_signed([input_1, input_2], @eth, [{@bob, 10}])\n      signed_invalid_tx = TestHelper.create_signed([input_3, input_3], @eth, [{@bob, 10}])\n\n      %{sigs: sigs_valid} = signed_valid_tx\n      %{sigs: sigs_invalid} = signed_invalid_tx\n\n      txbytes_valid = Transaction.raw_txbytes(signed_valid_tx)\n      txbytes_invalid = Transaction.raw_txbytes(signed_invalid_tx)\n\n      [_, inputs_valid, outputs_valid, _, _] = ExRLP.decode(txbytes_valid)\n      [_, inputs_invalid, outputs_invalid, _, _] = ExRLP.decode(txbytes_invalid)\n\n      hash_valid =\n        [sigs_valid, @payment_tx_type, inputs_valid, outputs_valid, 0, <<0::256>>]\n        |> ExRLP.encode()\n        |> Encoding.to_hex()\n\n      hash_invalid =\n        [sigs_invalid, @payment_tx_type, inputs_invalid, outputs_invalid, 0, <<0::256>>]\n        |> ExRLP.encode()\n        |> Encoding.to_hex()\n\n      merkle_root = [txbytes_invalid, txbytes_valid] |> Merkle.hash() |> Encoding.to_hex()\n\n      params = %{\n        hash: merkle_root,\n        transactions: [hash_invalid, hash_valid],\n        number: 1000\n      }\n\n      # Sanity check\n      assert {:ok, Encoding.from_hex(merkle_root)} == expect(%{hash: merkle_root}, :hash, :hash)\n\n      %{\"data\" => data} = WatcherHelper.rpc_call(\"block.validate\", params, 200)\n\n      assert data == %{\"valid\" => false}\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns the block if it is valid\" do\n      recovered_tx_1 = TestHelper.create_recovered([{1, 0, 0, @alice}], @eth, [{@bob, 100}])\n      recovered_tx_2 = TestHelper.create_recovered([{2, 0, 0, @alice}], @eth, [{@bob, 100}])\n\n      signed_txbytes =\n        [recovered_tx_1, recovered_tx_2]\n        |> Enum.map(fn tx -> tx.signed_tx_bytes end)\n        |> Enum.map(&Encoding.to_hex/1)\n\n      valid_merkle_root =\n        [recovered_tx_1, recovered_tx_2]\n        |> Enum.map(&Transaction.raw_txbytes/1)\n        |> Merkle.hash()\n        |> Encoding.to_hex()\n\n      # Sanity check\n      assert {:ok, Encoding.from_hex(valid_merkle_root)} == expect(%{hash: valid_merkle_root}, :hash, :hash)\n\n      params = %{\n        hash: valid_merkle_root,\n        number: 1000,\n        transactions: signed_txbytes\n      }\n\n      %{\"data\" => data} = WatcherHelper.rpc_call(\"block.validate\", params, 200)\n\n      assert data == %{\"valid\" => true}\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/test/omg_watcher_rpc/web/controllers/challenge_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.Controller.ChallengeTest do\n  use ExUnitFixtures\n  use ExUnit.Case, async: false\n  use OMG.Watcher.Fixtures\n  use OMG.WatcherInfo.Fixtures\n\n  alias OMG.Watcher.Utxo\n  alias OMG.WatcherInfo.DB\n  alias Support.WatcherHelper\n\n  require Utxo\n\n  @eth <<0::160>>\n\n  @tag skip: true\n  @tag fixtures: [:phoenix_ecto_sandbox, :alice]\n  test \"challenge data is properly formatted\", %{alice: alice} do\n    DB.EthEvent.insert_deposits!([%{owner: alice.addr, currency: @eth, amount: 100, blknum: 1, eth_height: 1}])\n\n    block_application = %{\n      transactions: [OMG.Watcher.TestHelper.create_recovered([{1, 0, 0, alice}], @eth, [{alice, 100}])],\n      number: 1000,\n      hash: <<?#::256>>,\n      timestamp: :os.system_time(:second),\n      eth_height: 1\n    }\n\n    {:ok, _} = DB.Block.insert_from_block_application(block_application)\n\n    utxo_pos = Utxo.position(1, 0, 0) |> Utxo.Position.encode()\n\n    %{\n      \"input_index\" => _input_index,\n      \"utxo_pos\" => _utxo_pos,\n      \"sig\" => _sig,\n      \"txbytes\" => _txbytes\n    } = WatcherHelper.success?(\"utxo.get_challenge_data\", %{\"utxo_pos\" => utxo_pos})\n  end\n\n  @tag skip: true\n  @tag fixtures: [:phoenix_ecto_sandbox]\n  test \"challenging non-existent utxo returns error\" do\n    utxo_pos = Utxo.position(1, 1, 0) |> Utxo.Position.encode()\n\n    %{\n      \"code\" => \"challenge:invalid\",\n      \"description\" => \"The challenge of particular exit is invalid because provided utxo is not spent\"\n    } = WatcherHelper.no_success?(\"utxo.get_challenge_data\", %{\"utxo_pos\" => utxo_pos})\n  end\n\n  @tag fixtures: [:phoenix_ecto_sandbox]\n  test \"utxo.get_challenge_data handles improper type of parameter\" do\n    assert %{\n             \"object\" => \"error\",\n             \"code\" => \"operation:bad_request\",\n             \"description\" => \"Parameters required by this operation are missing or incorrect.\",\n             \"messages\" => %{\n               \"validation_error\" => %{\n                 \"parameter\" => \"utxo_pos\",\n                 \"validator\" => \":integer\"\n               }\n             }\n           } == WatcherHelper.no_success?(\"utxo.get_challenge_data\", %{\"utxo_pos\" => \"1200000120000\"})\n  end\n\n  @tag fixtures: [:phoenix_ecto_sandbox]\n  test \"utxo.get_exit_data handles too low utxo position inputs\" do\n    assert %{\n             \"object\" => \"error\",\n             \"code\" => \"operation:bad_request\",\n             \"description\" => \"Parameters required by this operation are missing or incorrect.\",\n             \"messages\" => %{\n               \"validation_error\" => %{\n                 \"parameter\" => \"utxo_pos\",\n                 \"validator\" => \"{:greater, 0}\"\n               }\n             }\n           } = WatcherHelper.no_success?(\"utxo.get_challenge_data\", %{\"utxo_pos\" => 0})\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/test/omg_watcher_rpc/web/controllers/deposit_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.Controller.DepositTest do\n  use ExUnitFixtures\n  use ExUnit.Case, async: false\n  use OMG.WatcherInfo.Fixtures\n\n  import OMG.WatcherInfo.Factory\n\n  alias OMG.Utils.HttpRPC.Encoding\n  alias Support.WatcherHelper\n\n  describe \"get_deposits/2\" do\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns expected API response format with listed deposits\" do\n      owner_1 = <<1::160>>\n\n      _ = insert(:ethevent, txoutputs: [build(:txoutput, %{owner: owner_1})])\n\n      address = Encoding.to_hex(owner_1)\n      request_body = %{\"limit\" => 10, \"page\" => 1, \"address\" => address}\n\n      WatcherHelper.rpc_call(\"deposit.all\", request_body, 200)\n\n      assert %{\n               \"success\" => true,\n               \"data\" => [\n                 %{\n                   \"event_type\" => \"deposit\",\n                   \"eth_height\" => _,\n                   \"log_index\" => _,\n                   \"root_chain_txhash\" => _,\n                   \"inserted_at\" => _,\n                   \"updated_at\" => _,\n                   \"txoutputs\" => [\n                     %{\n                       \"amount\" => _,\n                       \"blknum\" => _,\n                       \"creating_txhash\" => _,\n                       \"oindex\" => _,\n                       \"otype\" => _,\n                       \"owner\" => ^address,\n                       \"spending_txhash\" => _,\n                       \"txindex\" => _\n                     }\n                   ]\n                 }\n               ],\n               \"data_paging\" => %{\n                 \"limit\" => 10,\n                 \"page\" => 1\n               },\n               \"service_name\" => \"watcher_info\",\n               \"version\" => _\n             } = WatcherHelper.rpc_call(\"deposit.all\", request_body, 200)\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"filters events by address\" do\n      owner_1 = <<1::160>>\n      owner_2 = <<2::160>>\n\n      txo_1 = build(:txoutput, %{owner: owner_1})\n      txo_2 = build(:txoutput, %{owner: owner_2})\n\n      _ = insert(:ethevent, event_type: :deposit, txoutputs: [txo_1])\n      _ = insert(:ethevent, event_type: :deposit, txoutputs: [txo_2])\n\n      address = Encoding.to_hex(owner_1)\n      request_body = %{\"address\" => address}\n\n      %{\n        \"data\" => [\n          %{\"txoutputs\" => [deposit_txo]}\n        ]\n      } = WatcherHelper.rpc_call(\"deposit.all\", request_body, 200)\n\n      assert deposit_txo[\"owner\"] == address\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns expected error if invalid parameters are given\" do\n      incorrect_address = 0\n      request_body = %{\"address\" => incorrect_address}\n\n      assert %{\n               \"data\" => %{\n                 \"code\" => \"operation:bad_request\",\n                 \"description\" => \"Parameters required by this operation are missing or incorrect.\",\n                 \"messages\" => %{\n                   \"validation_error\" => %{\"parameter\" => \"address\", \"validator\" => \":hex\"}\n                 },\n                 \"object\" => \"error\"\n               },\n               \"service_name\" => \"watcher_info\",\n               \"success\" => false,\n               \"version\" => _\n             } = WatcherHelper.rpc_call(\"deposit.all\", request_body, 200)\n    end\n  end\n\n  @tag fixtures: [:phoenix_ecto_sandbox]\n  test \"returns expected error if no address parameter is given\" do\n    assert %{\n             \"data\" => %{\n               \"code\" => \"operation:bad_request\",\n               \"description\" => \"Parameters required by this operation are missing or incorrect.\",\n               \"messages\" => %{\n                 \"validation_error\" => %{\"parameter\" => \"address\", \"validator\" => \":hex\"}\n               },\n               \"object\" => \"error\"\n             },\n             \"service_name\" => \"watcher_info\",\n             \"success\" => false,\n             \"version\" => _\n           } = WatcherHelper.rpc_call(\"deposit.all\", %{}, 200)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/test/omg_watcher_rpc/web/controllers/enforce_content_plug_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.Controller.EnforceContentPlugTest do\n  @moduledoc \"\"\"\n  This test module tested header enforcing which we decided to remove in #759. Instead of removing it, it was reversed\n  to show no header is required. We can remove this test as it basically shows HTTP protocol behavior.\n  \"\"\"\n\n  use ExUnitFixtures\n  use ExUnit.Case, async: false\n  use OMG.Watcher.Fixtures\n  use OMG.WatcherInfo.Fixtures\n  use Plug.Test\n  alias OMG.Utils.HttpRPC.Encoding\n\n  @tag fixtures: [:phoenix_ecto_sandbox]\n  test \"Content type header is no longer required\" do\n    no_account = Encoding.to_hex(<<0::160>>)\n    post = conn(:post, \"account.get_balance\", %{\"address\" => no_account})\n    response = OMG.WatcherRPC.Web.Endpoint.call(post, [])\n\n    assert response.status == 200\n    assert %{\"success\" => true} = Jason.decode!(response.resp_body)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/test/omg_watcher_rpc/web/controllers/fallback_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.Controller.FallbackTest do\n  use ExUnitFixtures\n  use ExUnit.Case, async: false\n  use OMG.WatcherInfo.Fixtures\n\n  alias Support.WatcherHelper\n\n  @tag fixtures: [:phoenix_ecto_sandbox]\n  test \"returns error for non existing method\" do\n    assert %{\n             \"object\" => \"error\",\n             \"code\" => \"operation:not_found\",\n             \"description\" => \"Operation cannot be found. Check request URL.\"\n           } == WatcherHelper.no_success?(\"no_such.endpoint\", %{})\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/test/omg_watcher_rpc/web/controllers/fee_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.Controller.FeeTest do\n  use ExUnitFixtures\n  use ExUnit.Case, async: false\n  use OMG.Watcher.Fixtures\n  use OMG.WatcherInfo.Fixtures\n\n  alias OMG.Utils.HttpRPC.Encoding\n  alias OMG.Watcher.WireFormatTypes\n  alias OMG.WatcherInfo.TestServer\n  alias Support.WatcherHelper\n\n  @eth <<0::160>>\n  @tx_type WireFormatTypes.tx_type_for(:tx_payment_v1)\n  @str_tx_type Integer.to_string(@tx_type)\n\n  setup do\n    context = TestServer.start()\n    on_exit(fn -> TestServer.stop(context) end)\n    context\n  end\n\n  describe \"fees_all/2\" do\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"forward a successful childchain response\", context do\n      childchain_response = %{\n        @str_tx_type => [\n          %{\n            \"currency\" => Encoding.to_hex(@eth),\n            \"amount\" => 2,\n            \"subunit_to_unit\" => 1_000_000_000_000_000_000,\n            \"pegged_amount\" => 4,\n            \"pegged_currency\" => \"USD\",\n            \"pegged_subunit_to_unit\" => 100,\n            \"updated_at\" => \"2019-01-01T10:10:00+00:00\"\n          }\n        ]\n      }\n\n      prepare_test_server(context, childchain_response)\n\n      ^childchain_response = WatcherHelper.success?(\"/fees.all\")\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"raises an error gracefully when childchain is unreachable\" do\n      assert %{\n               \"code\" => \"connection:childchain_unreachable\",\n               \"description\" => \"Cannot communicate with the childchain.\",\n               \"object\" => \"error\"\n             } = WatcherHelper.no_success?(\"/fees.all\")\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"fees.all endpoint rejects request with non list currencies\" do\n      assert %{\n               \"object\" => \"error\",\n               \"code\" => \"operation:bad_request\",\n               \"messages\" => %{\n                 \"validation_error\" => %{\n                   \"parameter\" => \"currencies\",\n                   \"validator\" => \":list\"\n                 }\n               }\n             } = WatcherHelper.no_success?(\"/fees.all\", %{currencies: \"0x0000000000000000000000000000000000000000\"})\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"fees.all endpoint rejects request with non hex currencies\" do\n      assert %{\n               \"object\" => \"error\",\n               \"code\" => \"operation:bad_request\",\n               \"messages\" => %{\n                 \"validation_error\" => %{\n                   \"parameter\" => \"currencies.currency\",\n                   \"validator\" => \":hex\"\n                 }\n               }\n             } = WatcherHelper.no_success?(\"/fees.all\", %{currencies: [\"invalid\"]})\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"fees.all endpoint rejects request with non list tx_types\" do\n      assert %{\n               \"object\" => \"error\",\n               \"code\" => \"operation:bad_request\",\n               \"messages\" => %{\n                 \"validation_error\" => %{\n                   \"parameter\" => \"tx_types\",\n                   \"validator\" => \":list\"\n                 }\n               }\n             } = WatcherHelper.no_success?(\"/fees.all\", %{tx_types: 1})\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"fees.all endpoint rejects request with negative tx_types\" do\n      assert %{\n               \"object\" => \"error\",\n               \"code\" => \"operation:bad_request\",\n               \"messages\" => %{\n                 \"validation_error\" => %{\n                   \"parameter\" => \"tx_types.tx_type\",\n                   \"validator\" => \"{:greater, -1}\"\n                 }\n               }\n             } = WatcherHelper.no_success?(\"/fees.all\", %{tx_types: [-5]})\n    end\n  end\n\n  defp prepare_test_server(context, response) do\n    response\n    |> TestServer.make_response()\n    |> TestServer.with_response(context, \"/fees.all\")\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/test/omg_watcher_rpc/web/controllers/in_flight_exit_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.Controller.InFlightExitTest do\n  use ExUnitFixtures\n  use ExUnit.Case, async: false\n  use OMG.Watcher.Fixtures\n  use OMG.WatcherInfo.Fixtures\n\n  alias OMG.Utils.HttpRPC.Encoding\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.TestHelper\n  alias OMG.Watcher.Utxo\n  alias Support.WatcherHelper\n  require Utxo\n\n  @eth <<0::160>>\n\n  describe \"getting in-flight exits\" do\n    @tag fixtures: [:web_endpoint, :db_initialized, :bob, :alice]\n    test \"returns properly formatted in-flight exit data\", %{bob: bob, alice: alice} do\n      test_in_flight_exit_data = fn inputs, expected_input_txs ->\n        in_flight_signed_txbytes = TestHelper.create_encoded(inputs, @eth, [{bob, 100}])\n        # `2 + ` for prepending `0x` in HEX encoded binaries\n        in_flight_raw_txbytes = in_flight_signed_txbytes |> Transaction.Signed.decode!() |> Transaction.raw_txbytes()\n\n        # checking just lengths in majority as we prepare verify correctness in the contract in integration tests\n        assert %{\n                 \"in_flight_tx\" => ^in_flight_raw_txbytes,\n                 \"input_txs\" => input_txs,\n                 \"input_utxos_pos\" => input_utxos_pos,\n                 \"input_txs_inclusion_proofs\" => proofs,\n                 \"in_flight_tx_sigs\" => sigs\n               } = WatcherHelper.get_in_flight_exit(in_flight_signed_txbytes)\n\n        input_txs = Enum.map(input_txs, &Transaction.decode!/1)\n\n        assert Enum.count(input_txs) == Enum.count(inputs)\n        assert Enum.count(input_utxos_pos) == Enum.count(inputs)\n        assert Enum.count(proofs) == Enum.count(inputs)\n        assert Enum.count(sigs) == Enum.count(inputs)\n\n        input_utxos_pos\n        |> Enum.map(&Utxo.Position.decode!/1)\n        |> Enum.zip(inputs)\n        # assert true because we just want to pattern match both positions against each other\n        |> Enum.each(fn {Utxo.position(blknum, txindex, oindex), {blknum, txindex, oindex, _}} -> assert true end)\n\n        Enum.each(proofs, fn proof -> assert byte_size(proof) == 16 * 32 end)\n        Enum.each(sigs, fn sig -> assert byte_size(sig) == 65 end)\n        assert input_txs == expected_input_txs\n      end\n\n      OMG.DB.multi_update(\n        [\n          [\n            TestHelper.create_encoded([{1, 0, 0, alice}], @eth, [{bob, 300}]),\n            TestHelper.create_encoded([{1000, 0, 0, bob}], @eth, [{alice, 100}, {bob, 200}])\n          ],\n          [TestHelper.create_encoded([{1000, 1, 0, alice}], @eth, [{bob, 99}, {alice, 1}], <<1322::256>>)],\n          [\n            TestHelper.create_encoded([], @eth, [{alice, 150}]),\n            TestHelper.create_encoded([{1000, 1, 1, bob}], @eth, [{bob, 150}, {alice, 50}])\n          ]\n        ]\n        |> Enum.with_index(1)\n        |> Enum.map(fn {transactions, index} ->\n          {:put, :block, %{hash: <<index>>, number: index * 1000, transactions: transactions}}\n        end)\n      )\n\n      test_in_flight_exit_data.([{3000, 1, 0, alice}], [\n        Transaction.Payment.new([{1000, 1, 1}], [{bob.addr, @eth, 150}, {alice.addr, @eth, 50}])\n      ])\n\n      test_in_flight_exit_data.([{3000, 1, 0, alice}, {2000, 0, 1, alice}], [\n        Transaction.Payment.new([{1000, 1, 1}], [{bob.addr, @eth, 150}, {alice.addr, @eth, 50}]),\n        Transaction.Payment.new([{1000, 1, 0}], [{bob.addr, @eth, 99}, {alice.addr, @eth, 1}], <<1322::256>>)\n      ])\n    end\n\n    @tag fixtures: [:web_endpoint, :db_initialized, :bob]\n    test \"behaves well if input is not found\", %{bob: bob} do\n      in_flight_txbytes =\n        [{3000, 1, 0, bob}]\n        |> TestHelper.create_encoded(@eth, [{bob, 150}])\n        |> Encoding.to_hex()\n\n      assert %{\n               \"code\" => \"in_flight_exit:tx_for_input_not_found\",\n               \"description\" => \"No transaction that created input.\"\n             } = WatcherHelper.no_success?(\"/in_flight_exit.get_data\", %{\"txbytes\" => in_flight_txbytes})\n    end\n\n    @tag fixtures: [:web_endpoint, :db_initialized, :bob]\n    test \"Provides a report on unsupported start IFE case if input is a spent deposit\", %{bob: bob} do\n      in_flight_txbytes =\n        [{1, 0, 0, bob}]\n        |> TestHelper.create_encoded(@eth, [{bob, 150}])\n        |> Encoding.to_hex()\n\n      assert %{\n               \"code\" => \"in_flight_exit:deposit_input_spent_ife_unsupported\",\n               \"description\" => \"Retrieving IFE data of a transaction with a spent deposit is unsupported.\"\n             } = WatcherHelper.no_success?(\"/in_flight_exit.get_data\", %{\"txbytes\" => in_flight_txbytes})\n    end\n\n    @tag fixtures: [:web_endpoint]\n    test \"behaves well if input malformed\" do\n      assert %{\"code\" => \"get_in_flight_exit:malformed_transaction\"} =\n               WatcherHelper.no_success?(\"/in_flight_exit.get_data\", %{\"txbytes\" => \"0x00\"})\n    end\n\n    @tag fixtures: [:web_endpoint]\n    test \"responds with error for malformed in-flight transaction bytes\" do\n      assert %{\n               \"code\" => \"operation:bad_request\",\n               \"description\" => \"Parameters required by this operation are missing or incorrect.\",\n               \"messages\" => %{\n                 \"validation_error\" => %{\n                   \"parameter\" => \"txbytes\",\n                   \"validator\" => \":hex\"\n                 }\n               }\n             } = WatcherHelper.no_success?(\"/in_flight_exit.get_data\", %{\"txbytes\" => \"tx\"})\n\n      assert %{\n               \"code\" => \"get_in_flight_exit:malformed_transaction_rlp\",\n               \"object\" => \"error\"\n             } = WatcherHelper.no_success?(\"/in_flight_exit.get_data\", %{\"txbytes\" => \"0x1234\"})\n    end\n  end\n\n  describe \"get_competitor/1\" do\n    @tag fixtures: [:web_endpoint]\n    test \"responds with validation error if given non-hex parameter\" do\n      non_hex_parameter = \"tx\"\n\n      assert %{\n               \"code\" => \"operation:bad_request\",\n               \"description\" => \"Parameters required by this operation are missing or incorrect.\",\n               \"messages\" => %{\n                 \"validation_error\" => %{\n                   \"parameter\" => \"txbytes\",\n                   \"validator\" => \":hex\"\n                 }\n               }\n             } = WatcherHelper.no_success?(\"/in_flight_exit.get_competitor\", %{\"txbytes\" => non_hex_parameter})\n    end\n  end\n\n  describe \"prove_canonical/1\" do\n    @tag fixtures: [:web_endpoint]\n    test \"responds with validation error if given non-hex parameter\" do\n      non_hex_parameter = \"tx\"\n\n      assert %{\n               \"code\" => \"operation:bad_request\",\n               \"description\" => \"Parameters required by this operation are missing or incorrect.\",\n               \"messages\" => %{\n                 \"validation_error\" => %{\n                   \"parameter\" => \"txbytes\",\n                   \"validator\" => \":hex\"\n                 }\n               }\n             } = WatcherHelper.no_success?(\"/in_flight_exit.prove_canonical\", %{\"txbytes\" => non_hex_parameter})\n    end\n  end\n\n  describe \"get_input_challenge_data/1\" do\n    @tag fixtures: [:web_endpoint]\n    test \"responds with validation error if given non-hex txbytes parameter\" do\n      non_hex_parameter = \"tx\"\n\n      assert %{\n               \"code\" => \"operation:bad_request\",\n               \"description\" => \"Parameters required by this operation are missing or incorrect.\",\n               \"messages\" => %{\n                 \"validation_error\" => %{\n                   \"parameter\" => \"txbytes\",\n                   \"validator\" => \":hex\"\n                 }\n               }\n             } =\n               WatcherHelper.no_success?(\"/in_flight_exit.get_input_challenge_data\", %{\n                 \"txbytes\" => non_hex_parameter,\n                 \"input_index\" => 1\n               })\n    end\n\n    @tag fixtures: [:web_endpoint]\n    test \"responds with validation error if given invalid input_index parameter\" do\n      invalid_input_index = \"0\"\n\n      assert %{\n               \"code\" => \"operation:bad_request\",\n               \"description\" => \"Parameters required by this operation are missing or incorrect.\",\n               \"messages\" => %{\n                 \"validation_error\" => %{\n                   \"parameter\" => \"input_index\",\n                   \"validator\" => \":integer\"\n                 }\n               }\n             } =\n               WatcherHelper.no_success?(\"/in_flight_exit.get_input_challenge_data\", %{\n                 \"txbytes\" => \"0x1234\",\n                 \"input_index\" => invalid_input_index\n               })\n    end\n  end\n\n  describe \"get_output_challenge_data/1\" do\n    @tag fixtures: [:web_endpoint]\n    test \"responds with validation error if given non-hex txbytes parameter\" do\n      non_hex_parameter = \"tx\"\n\n      assert %{\n               \"code\" => \"operation:bad_request\",\n               \"description\" => \"Parameters required by this operation are missing or incorrect.\",\n               \"messages\" => %{\n                 \"validation_error\" => %{\n                   \"parameter\" => \"txbytes\",\n                   \"validator\" => \":hex\"\n                 }\n               }\n             } =\n               WatcherHelper.no_success?(\"/in_flight_exit.get_output_challenge_data\", %{\n                 \"txbytes\" => non_hex_parameter,\n                 \"output_index\" => 1\n               })\n    end\n\n    @tag fixtures: [:web_endpoint]\n    test \"responds with validation error if given invalid output_index parameter\" do\n      invalid_output_index = \"0\"\n\n      assert %{\n               \"code\" => \"operation:bad_request\",\n               \"description\" => \"Parameters required by this operation are missing or incorrect.\",\n               \"messages\" => %{\n                 \"validation_error\" => %{\n                   \"parameter\" => \"output_index\",\n                   \"validator\" => \":integer\"\n                 }\n               }\n             } =\n               WatcherHelper.no_success?(\"/in_flight_exit.get_output_challenge_data\", %{\n                 \"txbytes\" => \"0x1234\",\n                 \"output_index\" => invalid_output_index\n               })\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/test/omg_watcher_rpc/web/controllers/stats_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.Controller.StatsTet do\n  use ExUnitFixtures\n  use ExUnit.Case, async: false\n  use OMG.WatcherInfo.Fixtures\n\n  alias Support.WatcherHelper\n\n  import OMG.WatcherInfo.Factory\n\n  @seconds_in_twenty_four_hours 86_400\n\n  describe \"get/0\" do\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"retrieves expected statistics\" do\n      now = DateTime.to_unix(DateTime.utc_now())\n      within_today = now - @seconds_in_twenty_four_hours + 100\n      before_today = now - @seconds_in_twenty_four_hours - 100\n\n      block_1 = insert(:block, blknum: 1000, timestamp: within_today)\n      _ = insert(:transaction, block: block_1, txindex: 0)\n      _ = insert(:transaction, block: block_1, txindex: 1)\n\n      block_2 = insert(:block, blknum: 2000, timestamp: before_today)\n      _ = insert(:transaction, block: block_2, txindex: 0)\n      _ = insert(:transaction, block: block_2, txindex: 1)\n\n      %{\"data\" => data} = WatcherHelper.rpc_call(\"stats.get\", %{}, 200)\n\n      expected = %{\n        \"block_count\" => %{\"all_time\" => 2, \"last_24_hours\" => 1},\n        \"transaction_count\" => %{\"all_time\" => 4, \"last_24_hours\" => 2},\n        \"average_block_interval_seconds\" => %{\"all_time\" => 200.0, \"last_24_hours\" => nil}\n      }\n\n      assert data == expected\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/test/omg_watcher_rpc/web/controllers/status_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.Controller.StatusTest do\n  use ExUnitFixtures\n  use ExUnit.Case, async: false\n\n  @moduletag :integration\n  @moduletag :watcher\n  # a test in OMG.WatcherInfo.Integration.StatusTest fully tests the controller,\n  # but it needs whole system setup so it's declared as integration test\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/test/omg_watcher_rpc/web/controllers/transaction_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.Controller.TransactionTest do\n  use ExUnitFixtures\n  use ExUnit.Case, async: true\n  use OMG.Watcher.Fixtures\n  use OMG.WatcherInfo.Fixtures\n  use OMG.Watcher.Fixtures\n\n  import OMG.WatcherInfo.Factory,\n    only: [build: 1, with_deposit: 1, insert: 1, insert: 2, with_inputs: 2, with_outputs: 2]\n\n  alias OMG.Utils.HttpRPC.Encoding\n  alias OMG.Utils.HttpRPC.Response\n  alias OMG.Watcher.DevCrypto\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.TestHelper, as: Test\n  alias OMG.Watcher.TypedDataHash\n  alias OMG.Watcher.Utxo\n  alias OMG.Watcher.Utxo.Position\n  alias OMG.Watcher.WireFormatTypes\n  alias OMG.WatcherInfo.DB\n  alias OMG.WatcherInfo.TestServer\n  alias Support.WatcherHelper\n\n  require OMG.Watcher.State.Transaction.Payment\n  require Utxo\n\n  @eth <<0::160>>\n  @other_token <<127::160>>\n  @eth_hex Encoding.to_hex(@eth)\n  @other_token_hex Encoding.to_hex(@other_token)\n  @default_data_paging %{\"limit\" => 200, \"page\" => 1}\n  @tx_type WireFormatTypes.tx_type_for(:tx_payment_v1)\n  @str_tx_type Integer.to_string(@tx_type)\n\n  describe \"/transaction.get\" do\n    @tag fixtures: [:initial_blocks]\n    test \"verifies all inserted transactions available to get\", %{initial_blocks: initial_blocks} do\n      Enum.each(initial_blocks, fn {blknum, txindex, txhash, _recovered_tx} ->\n        txhash_enc = Encoding.to_hex(txhash)\n\n        assert %{\"block\" => %{\"blknum\" => ^blknum}, \"txhash\" => ^txhash_enc, \"txindex\" => ^txindex} =\n                 WatcherHelper.success?(\"transaction.get\", %{id: txhash_enc})\n      end)\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns transaction in expected format\" do\n      deposit_1 = with_deposit(build(:txoutput))\n      deposit_2 = with_deposit(build(:txoutput))\n\n      input_1 = build(:txoutput)\n      input_2 = build(:txoutput)\n\n      output_1 = build(:txoutput)\n      output_2 = build(:txoutput)\n\n      creating_transaction =\n        insert(:transaction)\n        |> with_inputs([deposit_1, deposit_2])\n        |> with_outputs([input_1, input_2])\n\n      spending_transaction =\n        insert(:transaction)\n        |> with_inputs(creating_transaction.outputs)\n        |> with_outputs([output_1, output_2])\n\n      expected_response = %{\n        \"block\" => %{\n          \"blknum\" => spending_transaction.block.blknum,\n          \"eth_height\" => spending_transaction.block.eth_height,\n          \"hash\" => Encoding.to_hex(spending_transaction.block.hash),\n          \"timestamp\" => spending_transaction.block.timestamp,\n          \"tx_count\" => spending_transaction.block.tx_count,\n          \"inserted_at\" => Response.serialize(spending_transaction.block.inserted_at).data,\n          \"updated_at\" => Response.serialize(spending_transaction.block.updated_at).data\n        },\n        \"inputs\" =>\n          Enum.map(spending_transaction.inputs, fn input ->\n            %{\n              \"amount\" => input.amount,\n              \"blknum\" => input.blknum,\n              \"currency\" => Encoding.to_hex(input.currency),\n              \"oindex\" => input.oindex,\n              \"owner\" => Encoding.to_hex(input.owner),\n              \"txindex\" => input.txindex,\n              \"otype\" => input.otype,\n              \"utxo_pos\" => Utxo.Position.encode({:utxo_position, input.blknum, input.txindex, input.oindex}),\n              \"creating_txhash\" => to_hex_or_nil(input.creating_txhash),\n              \"spending_txhash\" => to_hex_or_nil(input.spending_txhash),\n              \"inserted_at\" => Response.serialize(input.inserted_at).data,\n              \"updated_at\" => Response.serialize(input.updated_at).data\n            }\n          end),\n        \"outputs\" =>\n          Enum.map(spending_transaction.outputs, fn output ->\n            %{\n              \"amount\" => output.amount,\n              \"blknum\" => output.blknum,\n              \"currency\" => Encoding.to_hex(output.currency),\n              \"oindex\" => output.oindex,\n              \"owner\" => Encoding.to_hex(output.owner),\n              \"txindex\" => output.txindex,\n              \"otype\" => output.otype,\n              \"utxo_pos\" => Utxo.Position.encode({:utxo_position, output.blknum, output.txindex, output.oindex}),\n              \"creating_txhash\" => to_hex_or_nil(output.creating_txhash),\n              \"spending_txhash\" => to_hex_or_nil(output.spending_txhash),\n              \"inserted_at\" => Response.serialize(output.inserted_at).data,\n              \"updated_at\" => Response.serialize(output.updated_at).data\n            }\n          end),\n        \"txhash\" => Encoding.to_hex(spending_transaction.txhash),\n        \"txbytes\" => Encoding.to_hex(spending_transaction.txbytes),\n        \"txindex\" => spending_transaction.txindex,\n        \"txtype\" => spending_transaction.txtype,\n        \"metadata\" => Encoding.to_hex(spending_transaction.metadata),\n        \"inserted_at\" => Response.serialize(spending_transaction.inserted_at).data,\n        \"updated_at\" => Response.serialize(spending_transaction.updated_at).data\n      }\n\n      response = WatcherHelper.success?(\"transaction.get\", %{\"id\" => Encoding.to_hex(spending_transaction.txhash)})\n\n      assert response == expected_response\n    end\n\n    @tag fixtures: [:blocks_inserter, :initial_deposits, :alice, :bob]\n    test \"returns up to 4 inputs / 4 outputs\", %{\n      blocks_inserter: blocks_inserter,\n      alice: alice\n    } do\n      [_, {_, _, txhash, _recovered_tx}] =\n        blocks_inserter.([\n          {1000,\n           [\n             Test.create_recovered(\n               [{1, 0, 0, alice}],\n               @eth,\n               [{alice, 10}, {alice, 20}, {alice, 30}, {alice, 40}]\n             ),\n             Test.create_recovered(\n               [{1000, 0, 0, alice}, {1000, 0, 1, alice}, {1000, 0, 2, alice}, {1000, 0, 3, alice}],\n               @eth,\n               [{alice, 1}, {alice, 2}, {alice, 3}, {alice, 4}]\n             )\n           ]}\n        ])\n\n      txhash = Encoding.to_hex(txhash)\n\n      assert %{\n               \"inputs\" => [%{\"amount\" => 10}, %{\"amount\" => 20}, %{\"amount\" => 30}, %{\"amount\" => 40}],\n               \"outputs\" => [%{\"amount\" => 1}, %{\"amount\" => 2}, %{\"amount\" => 3}, %{\"amount\" => 4}],\n               \"txhash\" => ^txhash,\n               \"txindex\" => 1\n             } = WatcherHelper.success?(\"transaction.get\", %{\"id\" => txhash})\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns error for non existing transaction\" do\n      txhash = Encoding.to_hex(<<0::256>>)\n\n      assert %{\n               \"object\" => \"error\",\n               \"code\" => \"transaction:not_found\",\n               \"description\" => \"Transaction doesn't exist for provided search criteria\"\n             } == WatcherHelper.no_success?(\"transaction.get\", %{\"id\" => txhash})\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"handles improper length of id parameter\" do\n      assert %{\n               \"object\" => \"error\",\n               \"code\" => \"operation:bad_request\",\n               \"description\" => \"Parameters required by this operation are missing or incorrect.\",\n               \"messages\" => %{\n                 \"validation_error\" => %{\n                   \"parameter\" => \"id\",\n                   \"validator\" => \"{:length, 32}\"\n                 }\n               }\n             } == WatcherHelper.no_success?(\"transaction.get\", %{\"id\" => \"0x50e901b98fe3389e32d56166a13a88208b03ea75\"})\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns bad request error if transaction hash is passed as query parameter\" do\n      txhash = insert(:transaction) |> Map.get(:txhash) |> Encoding.to_hex()\n\n      assert %{\n               \"object\" => \"error\",\n               \"code\" => \"operation:bad_request\",\n               \"description\" => \"Parameters required by this operation are missing or incorrect.\",\n               \"messages\" => %{\n                 \"validation_error\" => %{\n                   \"parameter\" => \"id\",\n                   \"validator\" => \":hex\"\n                 }\n               }\n             } == WatcherHelper.no_success?(\"transaction.get?id=#{txhash}\")\n    end\n  end\n\n  describe \"/transaction.all\" do\n    @tag fixtures: [:initial_blocks]\n    test \"returns multiple transactions in expected format\", %{initial_blocks: initial_blocks} do\n      {blknum, txindex, txhash, _recovered_tx} = initial_blocks |> Enum.reverse() |> hd()\n\n      %DB.Block{timestamp: timestamp, eth_height: eth_height, hash: block_hash} = get_block(blknum)\n      txhash = Encoding.to_hex(txhash)\n      block_hash = Encoding.to_hex(block_hash)\n\n      assert [\n               %{\n                 \"block\" => %{\n                   \"blknum\" => ^blknum,\n                   \"eth_height\" => ^eth_height,\n                   \"hash\" => ^block_hash,\n                   \"timestamp\" => ^timestamp\n                 },\n                 \"inputs\" => [\n                   %{\n                     \"amount\" => _,\n                     \"blknum\" => _,\n                     \"currency\" => _,\n                     \"oindex\" => _,\n                     \"owner\" => _,\n                     \"txindex\" => _,\n                     \"utxo_pos\" => _,\n                     \"creating_txhash\" => _,\n                     \"spending_txhash\" => _\n                   }\n                   | _\n                 ],\n                 \"outputs\" => [\n                   %{\n                     \"amount\" => _,\n                     \"blknum\" => _,\n                     \"currency\" => _,\n                     \"oindex\" => _,\n                     \"owner\" => _,\n                     \"txindex\" => _,\n                     \"utxo_pos\" => _,\n                     \"creating_txhash\" => _,\n                     \"spending_txhash\" => _\n                   }\n                   | _\n                 ],\n                 \"txhash\" => ^txhash,\n                 \"txindex\" => ^txindex\n               }\n               | _\n             ] = transaction_all_result()\n    end\n\n    @tag fixtures: [:blocks_inserter, :alice]\n    test \"returns tx from a particular block\", %{\n      blocks_inserter: blocks_inserter,\n      alice: alice\n    } do\n      blocks_inserter.([\n        {1000, [Test.create_recovered([{1, 0, 0, alice}], @eth, [{alice, 300}])]},\n        {2000,\n         [\n           Test.create_recovered([{1000, 0, 0, alice}], @eth, [{alice, 300}]),\n           Test.create_recovered([{2000, 1, 0, alice}], @eth, [{alice, 300}])\n         ]}\n      ])\n\n      assert [%{\"block\" => %{\"blknum\" => 2000}, \"txindex\" => 1}, %{\"block\" => %{\"blknum\" => 2000}, \"txindex\" => 0}] =\n               transaction_all_result(%{\"blknum\" => 2000})\n\n      assert [] = transaction_all_result(%{\"blknum\" => 3000})\n    end\n\n    @tag fixtures: [:blocks_inserter, :alice, :bob]\n    test \"returns tx from a particular block that contains requested address as the sender\", %{\n      blocks_inserter: blocks_inserter,\n      alice: alice,\n      bob: bob\n    } do\n      blocks_inserter.([\n        {1000, [Test.create_recovered([{1, 0, 0, alice}], @eth, [{alice, 300}])]},\n        {2000,\n         [\n           Test.create_recovered([{1000, 0, 0, alice}], @eth, [{alice, 300}]),\n           Test.create_recovered([{2, 0, 0, bob}], @eth, [{bob, 300}])\n         ]}\n      ])\n\n      address = Encoding.to_hex(bob.addr)\n\n      assert [%{\"block\" => %{\"blknum\" => 2000}, \"txindex\" => 1}] =\n               transaction_all_result(%{\"address\" => address, \"blknum\" => 2000})\n    end\n\n    @tag fixtures: [:blocks_inserter, :initial_deposits, :alice, :bob]\n    test \"returns tx that contains requested address as the sender and not recipient\", %{\n      blocks_inserter: blocks_inserter,\n      alice: alice,\n      bob: bob\n    } do\n      blocks_inserter.([\n        {1000,\n         [\n           Test.create_recovered([{1, 0, 0, alice}], @eth, [{bob, 300}])\n         ]}\n      ])\n\n      address = Encoding.to_hex(alice.addr)\n\n      assert [%{\"block\" => %{\"blknum\" => 1000}, \"txindex\" => 0}] = transaction_all_result(%{\"address\" => address})\n    end\n\n    @tag fixtures: [:blocks_inserter, :initial_deposits, :alice, :bob, :carol]\n    test \"returns only and all txs that match the address filtered\", %{\n      blocks_inserter: blocks_inserter,\n      alice: alice,\n      bob: bob,\n      carol: carol\n    } do\n      blocks_inserter.([\n        {1000,\n         [\n           Test.create_recovered([{1, 0, 0, alice}], @eth, [{bob, 300}]),\n           Test.create_recovered([{2, 0, 0, bob}], @eth, [{bob, 300}]),\n           Test.create_recovered([{1000, 1, 0, bob}], @eth, [{alice, 300}])\n         ]}\n      ])\n\n      alice_addr = Encoding.to_hex(alice.addr)\n      carol_addr = Encoding.to_hex(carol.addr)\n\n      assert [%{\"block\" => %{\"blknum\" => 1000}, \"txindex\" => 2}, %{\"block\" => %{\"blknum\" => 1000}, \"txindex\" => 0}] =\n               transaction_all_result(%{\"address\" => alice_addr})\n\n      assert [] = transaction_all_result(%{\"address\" => carol_addr})\n    end\n\n    @tag fixtures: [:blocks_inserter, :alice, :bob]\n    test \"returns tx that contains requested address as the recipient and not sender\", %{\n      blocks_inserter: blocks_inserter,\n      alice: alice,\n      bob: bob\n    } do\n      blocks_inserter.([\n        {1000,\n         [\n           Test.create_recovered([{2, 0, 0, bob}], @eth, [{alice, 100}])\n         ]}\n      ])\n\n      address = Encoding.to_hex(alice.addr)\n\n      assert [%{\"block\" => %{\"blknum\" => 1000}, \"txindex\" => 0}] = transaction_all_result(%{\"address\" => address})\n    end\n\n    @tag fixtures: [:blocks_inserter, :alice]\n    test \"returns tx that contains requested address as both sender & recipient is listed once\", %{\n      blocks_inserter: blocks_inserter,\n      alice: alice\n    } do\n      blocks_inserter.([\n        {1000,\n         [\n           Test.create_recovered([{1, 0, 0, alice}], @eth, [{alice, 100}])\n         ]}\n      ])\n\n      address = Encoding.to_hex(alice.addr)\n\n      assert [%{\"block\" => %{\"blknum\" => 1000}, \"txindex\" => 0}] = transaction_all_result(%{\"address\" => address})\n    end\n\n    @tag fixtures: [:blocks_inserter, :alice]\n    test \"returns tx without inputs and contains requested address as recipient\", %{\n      blocks_inserter: blocks_inserter,\n      alice: alice\n    } do\n      blocks_inserter.([\n        {1000,\n         [\n           Test.create_recovered([], @eth, [{alice, 10}])\n         ]}\n      ])\n\n      address = Encoding.to_hex(alice.addr)\n\n      assert [%{\"block\" => %{\"blknum\" => 1000}, \"txindex\" => 0}] = transaction_all_result(%{\"address\" => address})\n    end\n\n    @tag fixtures: [:initial_blocks]\n    test \"returns transactions containing metadata\", %{initial_blocks: initial_blocks} do\n      {blknum, txindex, txhash, recovered_tx} = Enum.find(initial_blocks, &match?({2000, 0, _, _}, &1))\n\n      expected_metadata = Encoding.to_hex(recovered_tx.signed_tx.raw_tx.metadata)\n      expected_txhash = Encoding.to_hex(txhash)\n\n      assert [\n               %{\n                 \"block\" => %{\"blknum\" => ^blknum},\n                 \"metadata\" => ^expected_metadata,\n                 \"txhash\" => ^expected_txhash,\n                 \"txindex\" => ^txindex\n               }\n             ] = transaction_all_result(%{\"metadata\" => expected_metadata})\n    end\n\n    @tag fixtures: [:blocks_inserter, :initial_deposits, :alice]\n    test \"returns transactions with matching txtype\", %{\n      blocks_inserter: blocks_inserter,\n      alice: alice\n    } do\n      blocks_inserter.([\n        {1000,\n         [\n           Test.create_recovered([{1, 0, 0, alice}], @eth, [{alice, 300}]),\n           Test.create_recovered([{2, 0, 0, alice}], @eth, [{alice, 300}]),\n           Test.create_recovered([{1000, 1, 0, alice}], @eth, [{alice, 300}]),\n           Test.create_recovered_fee_tx(1000, alice.addr, @eth, 5)\n         ]}\n      ])\n\n      assert [%{\"txindex\" => 2}, %{\"txindex\" => 1}, %{\"txindex\" => 0}] = transaction_all_result(%{\"txtypes\" => [1]})\n      assert [%{\"txindex\" => 3}] = transaction_all_result(%{\"txtypes\" => [3]})\n\n      assert [%{\"txindex\" => 3}, %{\"txindex\" => 2}, %{\"txindex\" => 1}, %{\"txindex\" => 0}] =\n               transaction_all_result(%{\"txtypes\" => [1, 3]})\n\n      assert [%{\"txindex\" => 3}, %{\"txindex\" => 2}, %{\"txindex\" => 1}, %{\"txindex\" => 0}] =\n               transaction_all_result(%{\"txtypes\" => []})\n    end\n  end\n\n  describe \"/transaction.all pagination\" do\n    @tag fixtures: [:alice, :bob, :initial_deposits, :blocks_inserter]\n    test \"returns list of transactions limited by address\", %{\n      blocks_inserter: blocks_inserter,\n      alice: alice,\n      bob: bob\n    } do\n      blocks_inserter.([\n        {1000,\n         [\n           Test.create_recovered([{1, 0, 0, alice}], @eth, [{bob, 3}]),\n           Test.create_recovered([{1_000, 0, 0, bob}], @eth, [{bob, 2}])\n         ]},\n        {2000,\n         [\n           Test.create_recovered([{1_000, 1, 0, bob}], @eth, [{alice, 1}])\n         ]}\n      ])\n\n      alice_addr = Encoding.to_hex(alice.addr)\n\n      assert {\n               [%{\"block\" => %{\"blknum\" => 2000}, \"txindex\" => 0}, %{\"block\" => %{\"blknum\" => 1000}, \"txindex\" => 1}],\n               %{\"limit\" => 2, \"page\" => 1}\n             } = transaction_all_with_paging(%{limit: 2})\n\n      assert {[%{\"block\" => %{\"blknum\" => 2000}, \"txindex\" => 0}, %{\"block\" => %{\"blknum\" => 1000}, \"txindex\" => 0}],\n              %{\"limit\" => 2, \"page\" => 1}} = transaction_all_with_paging(%{address: alice_addr, limit: 2})\n\n      bob_addr = Encoding.to_hex(bob.addr)\n\n      assert {[%{\"block\" => %{\"blknum\" => 1000}, \"txindex\" => 0}], %{\"limit\" => 2, \"page\" => 2}} =\n               transaction_all_with_paging(%{address: bob_addr, limit: 2, page: 2})\n    end\n\n    @tag fixtures: [:initial_blocks]\n    test \"returns list of transactions limited by block number\" do\n      assert {[%{\"block\" => %{\"blknum\" => 1000}, \"txindex\" => 1}], %{\"limit\" => 1, \"page\" => 1}} =\n               transaction_all_with_paging(%{blknum: 1000, limit: 1, page: 1})\n\n      assert {[%{\"block\" => %{\"blknum\" => 1000}, \"txindex\" => 0}], %{\"limit\" => 1, \"page\" => 2}} =\n               transaction_all_with_paging(%{blknum: 1000, limit: 1, page: 2})\n\n      assert {[], %{\"limit\" => 1, \"page\" => 3}} = transaction_all_with_paging(%{blknum: 1000, limit: 1, page: 3})\n    end\n\n    @tag fixtures: [:initial_blocks]\n    test \"limiting all transactions without address filter\" do\n      assert {[\n                %{\"block\" => %{\"blknum\" => 3000}, \"txindex\" => 1} = tx1,\n                %{\"block\" => %{\"blknum\" => 3000}, \"txindex\" => 0} = tx2\n              ], %{\"limit\" => 2, \"page\" => 1}} = transaction_all_with_paging(%{limit: 2})\n\n      assert {[^tx1, ^tx2], %{\"limit\" => 2, \"page\" => 1}} = transaction_all_with_paging(%{limit: 2, page: 1})\n\n      assert {[%{\"block\" => %{\"blknum\" => 2000}, \"txindex\" => 0}, %{\"block\" => %{\"blknum\" => 1000}, \"txindex\" => 1}],\n              %{\"limit\" => 2, \"page\" => 2}} = transaction_all_with_paging(%{limit: 2, page: 2})\n\n      assert {[%{\"block\" => %{\"blknum\" => 1000}, \"txindex\" => 0}], %{\"limit\" => 2, \"page\" => 3}} =\n               transaction_all_with_paging(%{limit: 2, page: 3})\n    end\n\n    @tag fixtures: [:alice, :bob, :initial_deposits, :blocks_inserter]\n    test \"pagination is unstable - client libs needs to remove duplicates\", %{\n      blocks_inserter: blocks_inserter,\n      alice: alice,\n      bob: bob\n    } do\n      blocks_inserter.([\n        {1000,\n         [\n           Test.create_recovered([{1, 0, 0, alice}], @eth, [{bob, 3}]),\n           Test.create_recovered([{1_000, 0, 0, bob}], @eth, [{bob, 2}])\n         ]}\n      ])\n\n      assert {[\n                %{\"block\" => %{\"blknum\" => 1000}, \"txindex\" => 1} = tx1,\n                %{\"block\" => %{\"blknum\" => 1000}, \"txindex\" => 0} = tx2\n              ], %{\"limit\" => 2, \"page\" => 1}} = transaction_all_with_paging(%{limit: 2})\n\n      # After 2 txs were requested 2 more was added, so then asking for the next page, the same\n      # already seen transaction will be returned. This test shows the limitation of current implementation.\n      blocks_inserter.([\n        {2000,\n         [\n           Test.create_recovered([{5, 0, 0, alice}], @eth, [{bob, 10}]),\n           Test.create_recovered([{1_002, 0, 0, bob}], @eth, [{alice, 5}])\n         ]}\n      ])\n\n      assert {[^tx1, ^tx2], %{\"limit\" => 2, \"page\" => 2}} = transaction_all_with_paging(%{limit: 2, page: 2})\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"handles improper limit parameter\" do\n      invalid_limit = \"50\"\n\n      assert %{\n               \"object\" => \"error\",\n               \"code\" => \"operation:bad_request\",\n               \"description\" => \"Parameters required by this operation are missing or incorrect.\",\n               \"messages\" => %{\n                 \"validation_error\" => %{\n                   \"parameter\" => \"limit\",\n                   \"validator\" => \":integer\"\n                 }\n               }\n             } ==\n               WatcherHelper.no_success?(\"transaction.all\", %{\"limit\" => invalid_limit})\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"handles improper address parameter\" do\n      too_short_address = \"0x\" <> String.duplicate(\"00\", 19)\n\n      assert %{\n               \"object\" => \"error\",\n               \"code\" => \"operation:bad_request\",\n               \"description\" => \"Parameters required by this operation are missing or incorrect.\",\n               \"messages\" => %{\n                 \"validation_error\" => %{\n                   \"parameter\" => \"address\",\n                   \"validator\" => \"{:length, 20}\"\n                 }\n               }\n             } ==\n               WatcherHelper.no_success?(\"transaction.all\", %{\"address\" => too_short_address})\n    end\n  end\n\n  describe \"/transaction.submit with binary-encoded transaction\" do\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"handles incorrectly encoded parameter\" do\n      hex_without_0x = \"5df13a6bf96dbcf6e66d8babd6b55bd40d64d4320c3b115364c6588fc18c2a21\"\n\n      assert %{\n               \"object\" => \"error\",\n               \"code\" => \"operation:bad_request\",\n               \"description\" => \"Parameters required by this operation are missing or incorrect.\",\n               \"messages\" => %{\n                 \"validation_error\" => %{\n                   \"parameter\" => \"transaction\",\n                   \"validator\" => \":hex\"\n                 }\n               }\n             } == WatcherHelper.no_success?(\"transaction.submit\", %{\"transaction\" => hex_without_0x})\n    end\n\n    @tag fixtures: [:alice, :phoenix_ecto_sandbox]\n    test \"provides stateless validation\", %{alice: alice} do\n      signed_bytes = Test.create_encoded([{1, 0, 0, alice}, {1, 0, 0, alice}], @eth, [{alice, 100}])\n\n      assert %{\n               \"code\" => \"submit:duplicate_inputs\",\n               \"description\" => nil,\n               \"object\" => \"error\"\n             } == WatcherHelper.no_success?(\"transaction.submit\", %{\"transaction\" => Encoding.to_hex(signed_bytes)})\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox, :alice]\n    test \"does not accept fee transactions\", %{alice: alice} do\n      fee_tx =\n        Transaction.Fee.new(1000, {alice.addr, @eth, 1551})\n        |> Test.sign_encode([])\n        |> Encoding.to_hex()\n\n      assert %{\n               \"code\" => \"submit:transaction_not_supported\",\n               \"description\" => _,\n               \"object\" => \"error\"\n             } =\n               WatcherHelper.no_success?(\"transaction.submit\", %{\n                 \"transaction\" => fee_tx\n               })\n    end\n  end\n\n  describe \"/transaction.submit with structural transaction\" do\n    deffixture typed_data_request(alice, bob) do\n      contract_addr = Application.fetch_env!(:omg_eth, :contract_addr)\n      alice_addr = Encoding.to_hex(alice.addr)\n      bob_addr = Encoding.to_hex(bob.addr)\n\n      %{\n        # these values should match configuration :omg_watcher, :eip_712_domain\n        \"domain\" => %{\n          \"name\" => \"OMG Network\",\n          \"version\" => \"1\",\n          \"salt\" => \"0xfad5c7f626d80f9256ef01929f3beb96e058b8b4b0e3fe52d84f054c0e2a7a83\",\n          \"verifyingContract\" => contract_addr.plasma_framework\n        },\n        \"message\" => %{\n          \"input0\" => %{\"blknum\" => 1000, \"txindex\" => 0, \"oindex\" => 1},\n          \"input1\" => %{\"blknum\" => 3001, \"txindex\" => 0, \"oindex\" => 0},\n          \"input2\" => %{\"blknum\" => 0, \"txindex\" => 0, \"oindex\" => 0},\n          \"input3\" => %{\"blknum\" => 0, \"txindex\" => 0, \"oindex\" => 0},\n          \"output0\" => %{\"owner\" => alice_addr, \"currency\" => @eth_hex, \"amount\" => 10},\n          \"output1\" => %{\"owner\" => alice_addr, \"currency\" => @other_token_hex, \"amount\" => 300},\n          \"output2\" => %{\"owner\" => bob_addr, \"currency\" => @other_token_hex, \"amount\" => 100},\n          \"output3\" => %{\"owner\" => @eth_hex, \"currency\" => @eth_hex, \"amount\" => 0},\n          \"metadata\" => Encoding.to_hex(<<0::256>>)\n        },\n        \"signatures\" => <<127::520>> |> List.duplicate(2) |> Enum.map(&Encoding.to_hex/1)\n      }\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox, :typed_data_request]\n    test \"ensures all required fields are passed\", %{typed_data_request: typed_data_request} do\n      req_without_domain = Map.drop(typed_data_request, [\"domain\"])\n\n      assert %{\n               \"code\" => \"operation:bad_request\",\n               \"description\" => \"Parameters required by this operation are missing or incorrect.\",\n               \"object\" => \"error\",\n               \"messages\" => %{\n                 \"validation_error\" => %{\n                   \"parameter\" => \"domain\",\n                   \"validator\" => \":map\"\n                 }\n               }\n             } == WatcherHelper.no_success?(\"transaction.submit_typed\", req_without_domain)\n\n      req_without_message = Map.drop(typed_data_request, [\"message\"])\n\n      assert %{\n               \"code\" => \"operation:bad_request\",\n               \"description\" => \"Parameters required by this operation are missing or incorrect.\",\n               \"object\" => \"error\",\n               \"messages\" => %{\n                 \"validation_error\" => %{\n                   \"parameter\" => \"message\",\n                   \"validator\" => \":map\"\n                 }\n               }\n             } == WatcherHelper.no_success?(\"transaction.submit_typed\", req_without_message)\n\n      req_without_sigs = Map.drop(typed_data_request, [\"signatures\"])\n\n      assert %{\n               \"code\" => \"operation:bad_request\",\n               \"description\" => \"Parameters required by this operation are missing or incorrect.\",\n               \"object\" => \"error\",\n               \"messages\" => %{\n                 \"validation_error\" => %{\n                   \"parameter\" => \"signatures\",\n                   \"validator\" => \":list\"\n                 }\n               }\n             } == WatcherHelper.no_success?(\"transaction.submit_typed\", req_without_sigs)\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox, :typed_data_request]\n    test \"input & sigs count should match\", %{typed_data_request: typed_data_request} do\n      # Providing 2 non-zero inputs & 1 signature\n      too_little_sigs = Map.update!(typed_data_request, \"signatures\", fn sigs -> Enum.take(sigs, 1) end)\n\n      assert %{\n               \"code\" => \"submit_typed:missing_signature\",\n               \"description\" =>\n                 \"Signatures should correspond to inputs owner. When all non-empty inputs has the same owner, \" <>\n                   \"signatures should be duplicated.\",\n               \"object\" => \"error\"\n             } == WatcherHelper.no_success?(\"transaction.submit_typed\", too_little_sigs)\n\n      # Providing 2 non-zero inputs & 4 signatures\n      too_many_sigs = Map.update!(typed_data_request, \"signatures\", fn sigs -> sigs ++ sigs end)\n\n      assert %{\n               \"code\" => \"submit_typed:superfluous_signature\",\n               \"description\" =>\n                 \"Number of non-empty inputs should match signatures count. Remove redundant signatures.\",\n               \"object\" => \"error\"\n             } == WatcherHelper.no_success?(\"transaction.submit_typed\", too_many_sigs)\n    end\n  end\n\n  describe \"/transaction.create\" do\n    setup tags do\n      context = TestServer.start()\n      on_exit(fn -> TestServer.stop(context) end)\n      Map.put(tags, :test_server, context)\n    end\n\n    @default_fee_amount 5\n    @default_fee_currency @eth_hex\n    @fee_response %{\n      @str_tx_type => [\n        %{\n          \"currency\" => @default_fee_currency,\n          \"amount\" => @default_fee_amount,\n          \"subunit_to_unit\" => 1_000_000_000_000_000_000,\n          \"pegged_amount\" => 4,\n          \"pegged_currency\" => \"USD\",\n          \"pegged_subunit_to_unit\" => 100,\n          \"updated_at\" => \"2019-01-01T10:10:00+00:00\"\n        }\n      ]\n    }\n    deffixture more_utxos(alice, blocks_inserter) do\n      blocks_inserter.([\n        {5000,\n         [\n           Test.create_recovered([], @eth, [{alice, 40}, {alice, 42}, {alice, 43}, {alice, 44}]),\n           Test.create_recovered([], @eth, [{alice, 41}, {alice, 45}]),\n           Test.create_recovered([], @other_token, [{alice, 5}, {alice, 110}, {alice, 15}]),\n           Test.create_recovered([], @other_token, [{alice, 105}, {alice, 10}, {alice, 115}])\n         ]}\n      ])\n    end\n\n    @tag fixtures: [:alice, :bob, :more_utxos]\n    test \"returns appropriate schema\", %{alice: alice, bob: bob, more_utxos: inserted_txs, test_server: context} do\n      alias OMG.Watcher.Utxo\n      require Utxo\n      prepare_test_server(context, @fee_response)\n      alice_to_bob = 100\n      metadata = (alice.addr <> bob.addr) |> OMG.Watcher.Crypto.hash() |> Encoding.to_hex()\n\n      alice_addr = Encoding.to_hex(alice.addr)\n      bob_addr = Encoding.to_hex(bob.addr)\n      blknum = 5000\n      creating_txhash = inserted_txs |> Enum.at(0) |> elem(2) |> Encoding.to_hex()\n\n      assert %{\n               \"result\" => \"complete\",\n               \"transactions\" => [\n                 %{\n                   \"inputs\" => [\n                     %{\n                       \"owner\" => ^alice_addr,\n                       \"currency\" => @eth_hex,\n                       \"blknum\" => ^blknum,\n                       \"txindex\" => txindex,\n                       \"oindex\" => oindex,\n                       \"utxo_pos\" => utxo_pos,\n                       \"creating_txhash\" => ^creating_txhash,\n                       \"spending_txhash\" => nil\n                     }\n                     | _\n                   ],\n                   \"outputs\" => [\n                     %{\"amount\" => ^alice_to_bob, \"currency\" => @eth_hex, \"owner\" => ^bob_addr},\n                     %{\"currency\" => @eth_hex, \"owner\" => ^alice_addr, \"amount\" => _rest}\n                   ],\n                   \"metadata\" => ^metadata,\n                   \"fee\" => %{\"amount\" => @default_fee_amount, \"currency\" => @default_fee_currency},\n                   \"txbytes\" => \"0x\" <> _txbytes,\n                   \"sign_hash\" => \"0x\" <> _hash\n                 }\n               ]\n             } =\n               WatcherHelper.success?(\n                 \"transaction.create\",\n                 %{\n                   \"owner\" => alice_addr,\n                   \"payments\" => [\n                     %{\"amount\" => alice_to_bob, \"currency\" => @eth_hex, \"owner\" => bob_addr}\n                   ],\n                   \"fee\" => %{\"currency\" => @default_fee_currency},\n                   \"metadata\" => metadata\n                 }\n               )\n\n      assert Utxo.Position.encode(Utxo.position(blknum, txindex, oindex)) == utxo_pos\n    end\n\n    @tag fixtures: [:alice, :bob, :more_utxos]\n    test \"returns correctly formed transaction, identical with the verbose form\", %{\n      alice: alice,\n      bob: bob,\n      test_server: context\n    } do\n      alias OMG.Watcher.State.Transaction\n\n      prepare_test_server(context, @fee_response)\n\n      assert %{\n               \"result\" => \"complete\",\n               \"transactions\" => [\n                 %{\n                   \"inputs\" => verbose_inputs,\n                   \"outputs\" => verbose_outputs,\n                   \"metadata\" => verbose_metadata,\n                   \"txbytes\" => tx_hex,\n                   \"sign_hash\" => sign_hash_hex\n                 }\n               ]\n             } =\n               WatcherHelper.success?(\n                 \"transaction.create\",\n                 %{\n                   \"owner\" => Encoding.to_hex(alice.addr),\n                   \"payments\" => [%{\"amount\" => 100, \"currency\" => @eth_hex, \"owner\" => Encoding.to_hex(bob.addr)}],\n                   \"fee\" => %{\"currency\" => @default_fee_currency},\n                   \"metadata\" => Encoding.to_hex(<<123::256>>)\n                 }\n               )\n\n      verbose_tx =\n        Transaction.Payment.new(\n          Enum.map(verbose_inputs, &{&1[\"blknum\"], &1[\"txindex\"], &1[\"oindex\"]}),\n          Enum.map(verbose_outputs, &{from_hex!(&1[\"owner\"]), from_hex!(&1[\"currency\"]), &1[\"amount\"]}),\n          from_hex!(verbose_metadata)\n        )\n\n      assert tx_hex == verbose_tx |> Transaction.raw_txbytes() |> Encoding.to_hex()\n      assert sign_hash_hex == verbose_tx |> TypedDataHash.hash_struct() |> Encoding.to_hex()\n    end\n\n    @tag fixtures: [:alice, :bob, :more_utxos]\n    test \"returns typed data in the form of request of typedDataSign\", %{alice: alice, bob: bob, test_server: context} do\n      metadata_hex = Encoding.to_hex(<<123::256>>)\n      prepare_test_server(context, @fee_response)\n\n      assert %{\n               \"result\" => \"complete\",\n               \"transactions\" => [\n                 %{\n                   \"typed_data\" => %{\n                     \"primaryType\" => \"Transaction\",\n                     \"types\" => %{\n                       \"EIP712Domain\" => [%{\"name\" => \"name\"} | _],\n                       \"Transaction\" => [_ | _],\n                       \"Input\" => [_ | _],\n                       \"Output\" => [_ | _]\n                     },\n                     \"domain\" => %{\n                       \"name\" => \"OMG Network\",\n                       \"verifyingContract\" => \"0x\" <> _contract\n                     },\n                     \"message\" => %{\n                       \"input0\" => %{\"blknum\" => _, \"txindex\" => _, \"oindex\" => _},\n                       \"output0\" => %{\"owner\" => \"0x\" <> _, \"currency\" => @eth_hex, \"amount\" => _},\n                       \"metadata\" => ^metadata_hex\n                     }\n                   }\n                 }\n               ]\n             } =\n               WatcherHelper.success?(\n                 \"transaction.create\",\n                 %{\n                   \"owner\" => Encoding.to_hex(alice.addr),\n                   \"payments\" => [%{\"amount\" => 100, \"currency\" => @eth_hex, \"owner\" => Encoding.to_hex(bob.addr)}],\n                   \"fee\" => %{\"currency\" => @default_fee_currency},\n                   \"metadata\" => metadata_hex\n                 }\n               )\n    end\n\n    @tag fixtures: [:alice, :bob, :more_utxos, :blocks_inserter]\n    test \"allows to pay single token tx\", %{\n      alice: alice,\n      bob: bob,\n      blocks_inserter: blocks_inserter,\n      test_server: context\n    } do\n      alice_balance = balance_in_token(alice.addr, @eth)\n      bob_balance = balance_in_token(bob.addr, @eth)\n\n      payment = 100\n\n      prepare_test_server(context, @fee_response)\n\n      assert %{\n               \"result\" => \"complete\",\n               \"transactions\" => [%{\"txbytes\" => tx_hex}]\n             } =\n               WatcherHelper.success?(\n                 \"transaction.create\",\n                 %{\n                   \"owner\" => Encoding.to_hex(alice.addr),\n                   \"payments\" => [\n                     %{\"amount\" => payment, \"currency\" => @eth_hex, \"owner\" => Encoding.to_hex(bob.addr)}\n                   ],\n                   \"fee\" => %{\"currency\" => @default_fee_currency}\n                 }\n               )\n\n      make_payments(7000, alice, [tx_hex], blocks_inserter)\n\n      assert alice_balance - (payment + @default_fee_amount) == balance_in_token(alice.addr, @eth)\n      assert bob_balance + payment == balance_in_token(bob.addr, @eth)\n    end\n\n    @tag fixtures: [:alice, :bob, :more_utxos, :blocks_inserter]\n    test \"allows to pay multi token tx\", %{\n      alice: alice,\n      bob: bob,\n      blocks_inserter: blocks_inserter,\n      test_server: context\n    } do\n      alice_eth = balance_in_token(alice.addr, @eth)\n      alice_token = balance_in_token(alice.addr, @other_token)\n      bob_eth = balance_in_token(bob.addr, @eth)\n      bob_token = balance_in_token(bob.addr, @other_token)\n\n      payment_eth = 100\n      payment_token = 110\n\n      prepare_test_server(context, @fee_response)\n\n      assert %{\n               \"result\" => \"complete\",\n               \"transactions\" => [%{\"txbytes\" => tx_hex}]\n             } =\n               WatcherHelper.success?(\n                 \"transaction.create\",\n                 %{\n                   \"owner\" => Encoding.to_hex(alice.addr),\n                   \"payments\" => [\n                     %{\"amount\" => payment_eth, \"currency\" => @eth_hex, \"owner\" => Encoding.to_hex(bob.addr)},\n                     %{\"amount\" => payment_token, \"currency\" => @other_token_hex, \"owner\" => Encoding.to_hex(bob.addr)}\n                   ],\n                   \"fee\" => %{\"currency\" => @default_fee_currency}\n                 }\n               )\n\n      make_payments(7000, alice, [tx_hex], blocks_inserter)\n\n      assert alice_eth - (payment_eth + @default_fee_amount) == balance_in_token(alice.addr, @eth)\n      assert alice_token - payment_token == balance_in_token(alice.addr, @other_token)\n      assert bob_eth + payment_eth == balance_in_token(bob.addr, @eth)\n      assert bob_token + payment_token == balance_in_token(bob.addr, @other_token)\n    end\n\n    @tag fixtures: [:alice, :bob, :more_utxos, :blocks_inserter]\n    test \"advice on merge single token tx\", %{\n      alice: alice,\n      bob: bob,\n      blocks_inserter: blocks_inserter,\n      test_server: context\n    } do\n      alice_balance = balance_in_token(alice.addr, @eth)\n      max_spendable = max_amount_spendable_in_single_tx(alice.addr, @eth)\n\n      payment = max_spendable + 10\n\n      prepare_test_server(context, @fee_response)\n\n      assert %{\n               \"result\" => \"intermediate\",\n               \"transactions\" => transactions\n             } =\n               WatcherHelper.success?(\n                 \"transaction.create\",\n                 %{\n                   \"owner\" => Encoding.to_hex(alice.addr),\n                   \"payments\" => [\n                     %{\"amount\" => payment, \"currency\" => @eth_hex, \"owner\" => Encoding.to_hex(bob.addr)}\n                   ],\n                   \"fee\" => %{\"currency\" => @default_fee_currency}\n                 }\n               )\n\n      make_payments(7000, alice, Enum.map(transactions, & &1[\"txbytes\"]), blocks_inserter)\n\n      assert alice_balance == balance_in_token(alice.addr, @eth)\n      assert max_amount_spendable_in_single_tx(alice.addr, @eth) >= payment\n    end\n\n    @tag fixtures: [:alice, :bob, :more_utxos]\n    test \"advice on merge does not merge single utxo\", %{alice: alice, bob: bob, test_server: context} do\n      max_spendable = max_amount_spendable_in_single_tx(alice.addr, @eth)\n\n      payment = max_spendable + 1\n\n      prepare_test_server(context, @fee_response)\n\n      assert %{\n               \"result\" => \"intermediate\",\n               \"transactions\" => [transaction]\n             } =\n               WatcherHelper.success?(\n                 \"transaction.create\",\n                 %{\n                   \"owner\" => Encoding.to_hex(alice.addr),\n                   \"payments\" => [\n                     %{\"amount\" => payment, \"currency\" => @eth_hex, \"owner\" => Encoding.to_hex(bob.addr)}\n                   ],\n                   \"fee\" => %{\"currency\" => @default_fee_currency}\n                 }\n               )\n\n      assert OMG.Watcher.State.Transaction.Payment.max_inputs() == length(transaction[\"inputs\"])\n    end\n\n    @tag fixtures: [:alice, :bob, :more_utxos, :blocks_inserter]\n    test \"allows to pay other token tx with fee in different currency\",\n         %{alice: alice, bob: bob, blocks_inserter: blocks_inserter, test_server: context} do\n      alice_eth = balance_in_token(alice.addr, @eth)\n      alice_token = balance_in_token(alice.addr, @other_token)\n      bob_token = balance_in_token(bob.addr, @other_token)\n\n      payment_token = 110\n\n      prepare_test_server(context, @fee_response)\n\n      assert %{\n               \"transactions\" => [%{\"txbytes\" => tx_hex}]\n             } =\n               WatcherHelper.success?(\n                 \"transaction.create\",\n                 %{\n                   \"owner\" => Encoding.to_hex(alice.addr),\n                   \"payments\" => [\n                     %{\"amount\" => payment_token, \"currency\" => @other_token_hex, \"owner\" => Encoding.to_hex(bob.addr)}\n                   ],\n                   \"fee\" => %{\"currency\" => @default_fee_currency}\n                 }\n               )\n\n      make_payments(7000, alice, [tx_hex], blocks_inserter)\n\n      assert alice_eth - @default_fee_amount == balance_in_token(alice.addr, @eth)\n      assert alice_token - payment_token == balance_in_token(alice.addr, @other_token)\n      assert bob_token + payment_token == balance_in_token(bob.addr, @other_token)\n    end\n\n    @tag fixtures: [:alice, :bob, :more_utxos]\n    test \"insufficient funds returns custom error\", %{alice: alice, bob: bob, test_server: context} do\n      balance = balance_in_token(alice.addr, @eth)\n      payment = balance + 10\n\n      prepare_test_server(context, @fee_response)\n\n      assert %{\n               \"object\" => \"error\",\n               \"code\" => \"transaction.create:insufficient_funds\",\n               \"description\" => \"Account balance is too low to satisfy the payment.\",\n               \"messages\" => [%{\"token\" => @eth_hex, \"missing\" => payment + @default_fee_amount - balance}]\n             } ==\n               WatcherHelper.no_success?(\n                 \"transaction.create\",\n                 %{\n                   \"owner\" => Encoding.to_hex(alice.addr),\n                   \"payments\" => [\n                     %{\"amount\" => payment, \"currency\" => @eth_hex, \"owner\" => Encoding.to_hex(bob.addr)}\n                   ],\n                   \"fee\" => %{\"currency\" => @default_fee_currency}\n                 }\n               )\n    end\n\n    @tag fixtures: [:alice, :bob, :more_utxos]\n    test \"unknown owner returns insufficient funds error\", %{alice: alice, bob: bob, test_server: context} do\n      assert 0 == balance_in_token(bob.addr, @eth)\n      payment = 25\n\n      prepare_test_server(context, @fee_response)\n\n      assert %{\n               \"object\" => \"error\",\n               \"code\" => \"transaction.create:insufficient_funds\",\n               \"description\" => \"Account balance is too low to satisfy the payment.\",\n               \"messages\" => [%{\"token\" => @eth_hex, \"missing\" => payment + @default_fee_amount}]\n             } ==\n               WatcherHelper.no_success?(\n                 \"transaction.create\",\n                 %{\n                   \"owner\" => Encoding.to_hex(bob.addr),\n                   \"payments\" => [\n                     %{\"amount\" => payment, \"currency\" => @eth_hex, \"owner\" => Encoding.to_hex(alice.addr)}\n                   ],\n                   \"fee\" => %{\"currency\" => @default_fee_currency}\n                 }\n               )\n    end\n\n    @tag fixtures: [:alice, :bob, :more_utxos]\n    test \"total number of outputs exceeds allowed outputs returns custom error\", %{\n      alice: alice,\n      bob: bob,\n      test_server: context\n    } do\n      bob_addr = Encoding.to_hex(bob.addr)\n\n      prepare_test_server(context, @fee_response)\n\n      assert %{\n               \"object\" => \"error\",\n               \"code\" => \"transaction.create:too_many_outputs\",\n               \"description\" => \"Total number of payments + change + fees exceed maximum allowed outputs.\"\n             } ==\n               WatcherHelper.no_success?(\n                 \"transaction.create\",\n                 %{\n                   \"owner\" => Encoding.to_hex(alice.addr),\n                   \"payments\" => [\n                     %{\"amount\" => 1, \"currency\" => @other_token_hex, \"owner\" => bob_addr},\n                     %{\"amount\" => 2, \"currency\" => @other_token_hex, \"owner\" => bob_addr},\n                     %{\"amount\" => 3, \"currency\" => @other_token_hex, \"owner\" => bob_addr}\n                   ],\n                   \"fee\" => %{\"currency\" => @default_fee_currency}\n                 }\n               )\n    end\n\n    @tag fixtures: [:alice, :more_utxos]\n    test \"transaction without payments that burns funds in fees is created correctly and incorrect on decoding\",\n         %{alice: alice, test_server: context} do\n      prepare_test_server(context, %{\n        @str_tx_type => [\n          %{\n            \"currency\" => @other_token_hex,\n            \"amount\" => @default_fee_amount,\n            \"subunit_to_unit\" => 1_000_000_000_000_000_000,\n            \"pegged_amount\" => 4,\n            \"pegged_currency\" => \"USD\",\n            \"pegged_subunit_to_unit\" => 100,\n            \"updated_at\" => \"2019-01-01T10:10:00+00:00\"\n          }\n        ]\n      })\n\n      assert %{\n               \"transactions\" => [%{\"txbytes\" => tx_hex}]\n             } =\n               WatcherHelper.success?(\n                 \"transaction.create\",\n                 %{\n                   \"owner\" => Encoding.to_hex(alice.addr),\n                   \"payments\" => [],\n                   \"fee\" => %{\"currency\" => @other_token_hex}\n                 }\n               )\n\n      assert {:error, :empty_outputs} = tx_hex |> from_hex!() |> Transaction.decode()\n    end\n\n    @tag fixtures: [:alice, :more_utxos]\n    test \"empty transaction without payments list is not allowed\", %{alice: alice, test_server: context} do\n      alice_addr = Encoding.to_hex(alice.addr)\n\n      prepare_test_server(context, %{\n        @str_tx_type => [\n          %{\n            \"currency\" => @default_fee_currency,\n            \"amount\" => 0,\n            \"subunit_to_unit\" => 1_000_000_000_000_000_000,\n            \"pegged_amount\" => 4,\n            \"pegged_currency\" => \"USD\",\n            \"pegged_subunit_to_unit\" => 100,\n            \"updated_at\" => \"2019-01-01T10:10:00+00:00\"\n          }\n        ]\n      })\n\n      assert %{\n               \"object\" => \"error\",\n               \"code\" => \"transaction.create:empty_transaction\",\n               \"description\" => \"Requested payment transfers no funds.\"\n             } ==\n               WatcherHelper.no_success?(\n                 \"transaction.create\",\n                 %{\"owner\" => alice_addr, \"payments\" => [], \"fee\" => %{\"currency\" => @default_fee_currency}}\n               )\n    end\n\n    @tag fixtures: [:alice, :more_utxos]\n    test \"returns an error when requester is equal to all the outputs owner\", %{alice: alice} do\n      params = %{\n        \"owner\" => Encoding.to_hex(alice.addr),\n        \"payments\" => [\n          %{\"amount\" => 1, \"currency\" => @eth_hex, \"owner\" => Encoding.to_hex(alice.addr)},\n          %{\"amount\" => 1, \"currency\" => @eth_hex, \"owner\" => Encoding.to_hex(alice.addr)}\n        ],\n        \"fee\" => %{\"currency\" => @default_fee_currency}\n      }\n\n      assert %{\n               \"object\" => \"error\",\n               \"code\" => \"transaction.create:self_transaction_not_supported\",\n               \"description\" => \"This endpoint cannot be used to create merge or split transactions.\"\n             } == WatcherHelper.no_success?(\"transaction.create\", params)\n    end\n  end\n\n  describe \"/transaction.create stealth add inputs\" do\n    setup tags do\n      context = TestServer.start()\n      on_exit(fn -> TestServer.stop(context) end)\n      Map.put(tags, :test_server, context)\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"does not stealth add inputs where the number of inputs reached maximum\", %{\n      test_server: context\n    } do\n      alice = OMG.Watcher.TestHelper.generate_entity()\n      bob = OMG.Watcher.TestHelper.generate_entity()\n      prepare_test_server(context, @fee_response)\n\n      _payment_1 = insert(:txoutput, amount: 10, currency: @eth, owner: alice.addr)\n      _payment_2 = insert(:txoutput, amount: 10, currency: @eth, owner: alice.addr)\n      _payment_3 = insert(:txoutput, amount: 10, currency: @eth, owner: alice.addr)\n      _payment_and_fee = insert(:txoutput, amount: 15, currency: @eth, owner: alice.addr)\n\n      alice_addr_hex = Encoding.to_hex(alice.addr)\n      bob_addr_hex = Encoding.to_hex(bob.addr)\n\n      params = %{\n        \"owner\" => alice_addr_hex,\n        \"payments\" => [\n          %{\"amount\" => 40, \"currency\" => @eth_hex, \"owner\" => bob_addr_hex}\n        ],\n        \"fee\" => %{\"currency\" => @eth_hex}\n      }\n\n      assert %{\n               \"transactions\" => [\n                 %{\n                   \"fee\" => %{\n                     \"amount\" => @default_fee_amount,\n                     \"currency\" => @eth_hex\n                   },\n                   \"inputs\" => [\n                     %{\"amount\" => 10, \"currency\" => @eth_hex},\n                     %{\"amount\" => 10, \"currency\" => @eth_hex},\n                     %{\"amount\" => 10, \"currency\" => @eth_hex},\n                     %{\"amount\" => 15, \"currency\" => @eth_hex}\n                   ],\n                   \"outputs\" => [\n                     %{\"amount\" => 40, \"currency\" => @eth_hex, \"owner\" => ^bob_addr_hex}\n                   ]\n                 }\n               ]\n             } = WatcherHelper.success?(\"transaction.create\", params)\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"stealth add 1 more input when 3 inputs with single currency covers payment and fee\", %{test_server: context} do\n      alice = OMG.Watcher.TestHelper.generate_entity()\n      bob = OMG.Watcher.TestHelper.generate_entity()\n      prepare_test_server(context, @fee_response)\n\n      _payment_1 = insert(:txoutput, amount: 10, currency: @eth, owner: alice.addr)\n      _payment_2 = insert(:txoutput, amount: 10, currency: @eth, owner: alice.addr)\n      _fee_1 = insert(:txoutput, amount: @default_fee_amount, currency: @eth, owner: alice.addr)\n      _stealth_merge_1 = insert(:txoutput, amount: 10, currency: @eth, owner: alice.addr)\n\n      alice_addr_hex = Encoding.to_hex(alice.addr)\n      bob_addr_hex = Encoding.to_hex(bob.addr)\n\n      # Assert when payment amount exactly matched with input amounts\n      params = %{\n        \"owner\" => alice_addr_hex,\n        \"payments\" => [\n          %{\"amount\" => 20, \"currency\" => @eth_hex, \"owner\" => bob_addr_hex}\n        ],\n        \"fee\" => %{\"currency\" => @eth_hex}\n      }\n\n      assert %{\n               \"transactions\" => [\n                 %{\n                   \"fee\" => %{\n                     \"amount\" => @default_fee_amount,\n                     \"currency\" => @eth_hex\n                   },\n                   \"inputs\" => [\n                     %{\"amount\" => @default_fee_amount, \"currency\" => @eth_hex},\n                     %{\"amount\" => 10, \"currency\" => @eth_hex},\n                     %{\"amount\" => 10, \"currency\" => @eth_hex},\n                     %{\"amount\" => 10, \"currency\" => @eth_hex}\n                   ],\n                   \"outputs\" => [\n                     %{\"amount\" => 20, \"currency\" => @eth_hex, \"owner\" => ^bob_addr_hex},\n                     %{\"amount\" => 10, \"currency\" => @eth_hex, \"owner\" => ^alice_addr_hex}\n                   ]\n                 }\n               ]\n             } = WatcherHelper.success?(\"transaction.create\", params)\n\n      # Assert when payment amount doesn't exactly matched with input amounts\n      params = %{\n        \"owner\" => alice_addr_hex,\n        \"payments\" => [\n          %{\"amount\" => 18, \"currency\" => @eth_hex, \"owner\" => bob_addr_hex}\n        ],\n        \"fee\" => %{\"currency\" => @eth_hex}\n      }\n\n      assert %{\n               \"transactions\" => [\n                 %{\n                   \"fee\" => %{\n                     \"amount\" => @default_fee_amount,\n                     \"currency\" => @eth_hex\n                   },\n                   \"inputs\" => [\n                     %{\"amount\" => @default_fee_amount, \"currency\" => @eth_hex},\n                     %{\"amount\" => 10, \"currency\" => @eth_hex},\n                     %{\"amount\" => 10, \"currency\" => @eth_hex},\n                     %{\"amount\" => 10, \"currency\" => @eth_hex}\n                   ],\n                   \"outputs\" => [\n                     %{\"amount\" => 18, \"currency\" => @eth_hex, \"owner\" => ^bob_addr_hex},\n                     %{\"amount\" => 12, \"currency\" => @eth_hex, \"owner\" => ^alice_addr_hex}\n                   ]\n                 }\n               ]\n             } = WatcherHelper.success?(\"transaction.create\", params)\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"stealth add 1 more input when 3 inputs with multiple currency covers payment and fee\", %{\n      test_server: context\n    } do\n      alice = OMG.Watcher.TestHelper.generate_entity()\n      bob = OMG.Watcher.TestHelper.generate_entity()\n      prepare_test_server(context, @fee_response)\n\n      _payment_1 = insert(:txoutput, amount: 5, currency: @other_token, owner: alice.addr)\n      _payment_2 = insert(:txoutput, amount: 5, currency: @other_token, owner: alice.addr)\n      _fee = insert(:txoutput, amount: @default_fee_amount, currency: @eth, owner: alice.addr)\n      _stealth_add_1 = insert(:txoutput, amount: 10, currency: @eth, owner: alice.addr)\n\n      alice_addr_hex = Encoding.to_hex(alice.addr)\n      bob_addr_hex = Encoding.to_hex(bob.addr)\n\n      # Assert when payment amount exactly matched with input amounts\n      params = %{\n        \"owner\" => alice_addr_hex,\n        \"payments\" => [\n          %{\"amount\" => 10, \"currency\" => @other_token_hex, \"owner\" => bob_addr_hex}\n        ],\n        \"fee\" => %{\"currency\" => @eth_hex}\n      }\n\n      assert %{\n               \"transactions\" => [\n                 %{\n                   \"fee\" => %{\n                     \"amount\" => @default_fee_amount,\n                     \"currency\" => @eth_hex\n                   },\n                   \"inputs\" => [\n                     %{\"amount\" => 10, \"currency\" => @eth_hex},\n                     %{\"amount\" => @default_fee_amount, \"currency\" => @eth_hex},\n                     %{\"amount\" => 5, \"currency\" => @other_token_hex},\n                     %{\"amount\" => 5, \"currency\" => @other_token_hex}\n                   ],\n                   \"outputs\" => [\n                     %{\"amount\" => 10, \"currency\" => @other_token_hex, \"owner\" => ^bob_addr_hex},\n                     %{\"amount\" => 10, \"currency\" => @eth_hex, \"owner\" => ^alice_addr_hex}\n                   ]\n                 }\n               ]\n             } = WatcherHelper.success?(\"transaction.create\", params)\n\n      # Assert when payment amount doesn't exactly matched with input amounts\n      params = %{\n        \"owner\" => alice_addr_hex,\n        \"payments\" => [\n          %{\"amount\" => 8, \"currency\" => @other_token_hex, \"owner\" => bob_addr_hex}\n        ],\n        \"fee\" => %{\"currency\" => @eth_hex}\n      }\n\n      assert %{\n               \"transactions\" => [\n                 %{\n                   \"fee\" => %{\n                     \"amount\" => @default_fee_amount,\n                     \"currency\" => @eth_hex\n                   },\n                   \"inputs\" => [\n                     %{\"amount\" => 10, \"currency\" => @eth_hex},\n                     %{\"amount\" => @default_fee_amount, \"currency\" => @eth_hex},\n                     %{\"amount\" => 5, \"currency\" => @other_token_hex},\n                     %{\"amount\" => 5, \"currency\" => @other_token_hex}\n                   ],\n                   \"outputs\" => [\n                     %{\"amount\" => 8, \"currency\" => @other_token_hex, \"owner\" => ^bob_addr_hex},\n                     %{\"amount\" => 10, \"currency\" => @eth_hex, \"owner\" => ^alice_addr_hex},\n                     %{\"amount\" => 2, \"currency\" => @other_token_hex, \"owner\" => ^alice_addr_hex}\n                   ]\n                 }\n               ]\n             } = WatcherHelper.success?(\"transaction.create\", params)\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"stealth add 2 more inputs when 2 inputs with single currency covers payments and fee\", %{test_server: context} do\n      alice = OMG.Watcher.TestHelper.generate_entity()\n      bob = OMG.Watcher.TestHelper.generate_entity()\n      prepare_test_server(context, @fee_response)\n\n      _payment_1 = insert(:txoutput, amount: 30, currency: @eth, owner: alice.addr)\n      _fee = insert(:txoutput, amount: @default_fee_amount, currency: @eth, owner: alice.addr)\n      _stealth_add_1 = insert(:txoutput, amount: 20, currency: @eth, owner: alice.addr)\n      _stealth_add_2 = insert(:txoutput, amount: 10, currency: @eth, owner: alice.addr)\n\n      alice_addr_hex = Encoding.to_hex(alice.addr)\n      bob_addr_hex = Encoding.to_hex(bob.addr)\n\n      # Assert when payment amount exactly matched with input amounts\n      params = %{\n        \"owner\" => alice_addr_hex,\n        \"payments\" => [\n          %{\"amount\" => 30, \"currency\" => @eth_hex, \"owner\" => bob_addr_hex}\n        ],\n        \"fee\" => %{\"currency\" => @eth_hex}\n      }\n\n      assert %{\n               \"transactions\" => [\n                 %{\n                   \"fee\" => %{\n                     \"amount\" => @default_fee_amount,\n                     \"currency\" => @eth_hex\n                   },\n                   \"inputs\" => [\n                     %{\"amount\" => 10, \"currency\" => @eth_hex},\n                     %{\"amount\" => @default_fee_amount, \"currency\" => @eth_hex},\n                     %{\"amount\" => 20, \"currency\" => @eth_hex},\n                     %{\"amount\" => 30, \"currency\" => @eth_hex}\n                   ],\n                   \"outputs\" => [\n                     %{\"amount\" => 30, \"currency\" => @eth_hex, \"owner\" => ^bob_addr_hex},\n                     %{\"amount\" => 30, \"currency\" => @eth_hex, \"owner\" => ^alice_addr_hex}\n                   ]\n                 }\n               ]\n             } = WatcherHelper.success?(\"transaction.create\", params)\n\n      # Assert when payment amount doesn't exactly matched with input amounts\n      params = %{\n        \"owner\" => alice_addr_hex,\n        \"payments\" => [\n          %{\"amount\" => 28, \"currency\" => @eth_hex, \"owner\" => bob_addr_hex}\n        ],\n        \"fee\" => %{\"currency\" => @eth_hex}\n      }\n\n      assert %{\n               \"transactions\" => [\n                 %{\n                   \"fee\" => %{\n                     \"amount\" => @default_fee_amount,\n                     \"currency\" => @eth_hex\n                   },\n                   \"inputs\" => [\n                     %{\"amount\" => 10, \"currency\" => @eth_hex},\n                     %{\"amount\" => @default_fee_amount, \"currency\" => @eth_hex},\n                     %{\"amount\" => 20, \"currency\" => @eth_hex},\n                     %{\"amount\" => 30, \"currency\" => @eth_hex}\n                   ],\n                   \"outputs\" => [\n                     %{\"amount\" => 28, \"currency\" => @eth_hex, \"owner\" => ^bob_addr_hex},\n                     %{\"amount\" => 32, \"currency\" => @eth_hex, \"owner\" => ^alice_addr_hex}\n                   ]\n                 }\n               ]\n             } = WatcherHelper.success?(\"transaction.create\", params)\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"stealth add 2 more inputs when 2 inputs with multiple currency covers payments and fee\", %{\n      test_server: context\n    } do\n      alice = OMG.Watcher.TestHelper.generate_entity()\n      bob = OMG.Watcher.TestHelper.generate_entity()\n      prepare_test_server(context, @fee_response)\n\n      _payment_1 = insert(:txoutput, amount: 30, currency: @other_token, owner: alice.addr)\n      _fee = insert(:txoutput, amount: @default_fee_amount, currency: @eth, owner: alice.addr)\n      _stealth_add_1 = insert(:txoutput, amount: 20, currency: @eth, owner: alice.addr)\n      _stealth_add_2 = insert(:txoutput, amount: 10, currency: @eth, owner: alice.addr)\n\n      alice_addr_hex = Encoding.to_hex(alice.addr)\n      bob_addr_hex = Encoding.to_hex(bob.addr)\n\n      # Assert when payment amount exactly matched with input amounts\n      params = %{\n        \"owner\" => alice_addr_hex,\n        \"payments\" => [\n          %{\"amount\" => 30, \"currency\" => @other_token_hex, \"owner\" => bob_addr_hex}\n        ],\n        \"fee\" => %{\"currency\" => @eth_hex}\n      }\n\n      assert %{\n               \"transactions\" => [\n                 %{\n                   \"fee\" => %{\n                     \"amount\" => @default_fee_amount,\n                     \"currency\" => @eth_hex\n                   },\n                   \"inputs\" => [\n                     %{\"amount\" => 20, \"currency\" => @eth_hex},\n                     %{\"amount\" => 10, \"currency\" => @eth_hex},\n                     %{\"amount\" => @default_fee_amount, \"currency\" => @eth_hex},\n                     %{\"amount\" => 30, \"currency\" => @other_token_hex}\n                   ],\n                   \"outputs\" => [\n                     %{\"amount\" => 30, \"currency\" => @other_token_hex, \"owner\" => ^bob_addr_hex},\n                     %{\"amount\" => 30, \"currency\" => @eth_hex, \"owner\" => ^alice_addr_hex}\n                   ]\n                 }\n               ]\n             } = WatcherHelper.success?(\"transaction.create\", params)\n\n      # Assert when payment amount doesn't exactly matched with input amounts\n      params = %{\n        \"owner\" => alice_addr_hex,\n        \"payments\" => [\n          %{\"amount\" => 28, \"currency\" => @other_token_hex, \"owner\" => bob_addr_hex}\n        ],\n        \"fee\" => %{\"currency\" => @eth_hex}\n      }\n\n      assert %{\n               \"transactions\" => [\n                 %{\n                   \"fee\" => %{\n                     \"amount\" => @default_fee_amount,\n                     \"currency\" => @eth_hex\n                   },\n                   \"inputs\" => [\n                     %{\"amount\" => 20, \"currency\" => @eth_hex},\n                     %{\"amount\" => 10, \"currency\" => @eth_hex},\n                     %{\"amount\" => @default_fee_amount, \"currency\" => @eth_hex},\n                     %{\"amount\" => 30, \"currency\" => @other_token_hex}\n                   ],\n                   \"outputs\" => [\n                     %{\"amount\" => 28, \"currency\" => @other_token_hex, \"owner\" => ^bob_addr_hex},\n                     %{\"amount\" => 30, \"currency\" => @eth_hex, \"owner\" => ^alice_addr_hex},\n                     %{\"amount\" => 2, \"currency\" => @other_token_hex, \"owner\" => ^alice_addr_hex}\n                   ]\n                 }\n               ]\n             } = WatcherHelper.success?(\"transaction.create\", params)\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"stealth add 1 more input when there're currently 2 inputs and have only 1 utxo left\", %{test_server: context} do\n      alice = OMG.Watcher.TestHelper.generate_entity()\n      bob = OMG.Watcher.TestHelper.generate_entity()\n      prepare_test_server(context, @fee_response)\n\n      _fee = insert(:txoutput, amount: @default_fee_amount, currency: @eth, owner: alice.addr)\n      _payment = insert(:txoutput, amount: 20, currency: @eth, owner: alice.addr)\n      _merge_1 = insert(:txoutput, amount: 30, currency: @eth, owner: alice.addr)\n\n      alice_addr_hex = Encoding.to_hex(alice.addr)\n      bob_addr_hex = Encoding.to_hex(bob.addr)\n\n      params = %{\n        \"owner\" => alice_addr_hex,\n        \"payments\" => [\n          %{\"amount\" => 20, \"currency\" => @eth_hex, \"owner\" => bob_addr_hex}\n        ],\n        \"fee\" => %{\"currency\" => @eth_hex}\n      }\n\n      assert %{\n               \"transactions\" => [\n                 %{\n                   \"fee\" => %{\n                     \"amount\" => @default_fee_amount,\n                     \"currency\" => @eth_hex\n                   },\n                   \"inputs\" => [\n                     %{\"amount\" => 20, \"currency\" => @eth_hex},\n                     %{\"amount\" => @default_fee_amount, \"currency\" => @eth_hex},\n                     %{\"amount\" => 30, \"currency\" => @eth_hex}\n                   ],\n                   \"outputs\" => [\n                     %{\"amount\" => 20, \"currency\" => @eth_hex, \"owner\" => ^bob_addr_hex},\n                     %{\"amount\" => 30, \"currency\" => @eth_hex, \"owner\" => ^alice_addr_hex}\n                   ]\n                 }\n               ]\n             } = WatcherHelper.success?(\"transaction.create\", params)\n    end\n  end\n\n  describe \"/transaction.create validation\" do\n    @tag fixtures: [:alice, :more_utxos]\n    test \"incorrect payment in payment list\", %{alice: alice} do\n      alice_addr = Encoding.to_hex(alice.addr)\n\n      assert %{\n               \"object\" => \"error\",\n               \"code\" => \"operation:bad_request\",\n               \"description\" => \"Parameters required by this operation are missing or incorrect.\",\n               \"messages\" => %{\n                 \"validation_error\" => %{\n                   \"parameter\" => \"payments.amount\",\n                   \"validator\" => \":integer\"\n                 }\n               }\n             } ==\n               WatcherHelper.no_success?(\n                 \"transaction.create\",\n                 %{\n                   \"owner\" => alice_addr,\n                   \"payments\" => [%{\"amount\" => \"zonk\", \"currency\" => @other_token_hex, \"owner\" => alice_addr}],\n                   \"fee\" => %{\"currency\" => @eth_hex}\n                 }\n               )\n    end\n\n    @tag fixtures: [:alice, :more_utxos]\n    test \"too many payments attempted\", %{alice: alice} do\n      alice_addr = Encoding.to_hex(alice.addr)\n      too_many_payments = List.duplicate(%{\"amount\" => 1, \"currency\" => @other_token_hex, \"owner\" => alice_addr}, 5)\n\n      assert %{\n               \"object\" => \"error\",\n               \"code\" => \"operation:bad_request\",\n               \"description\" => \"Parameters required by this operation are missing or incorrect.\",\n               \"messages\" => %{\n                 \"validation_error\" => %{\"parameter\" => \"payments\", \"validator\" => \"{:too_many_payments, 4}\"}\n               }\n             } ==\n               WatcherHelper.no_success?(\n                 \"transaction.create\",\n                 %{\n                   \"owner\" => alice_addr,\n                   \"payments\" => too_many_payments,\n                   \"fee\" => %{\"currency\" => @eth_hex}\n                 }\n               )\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"owner should be hex-encoded address\" do\n      assert %{\n               \"object\" => \"error\",\n               \"code\" => \"operation:bad_request\",\n               \"description\" => \"Parameters required by this operation are missing or incorrect.\",\n               \"messages\" => %{\n                 \"validation_error\" => %{\n                   \"parameter\" => \"owner\",\n                   \"validator\" => \":hex\"\n                 }\n               }\n             } ==\n               WatcherHelper.no_success?(\n                 \"transaction.create\",\n                 %{\"owner\" => \"not-a-hex\"}\n               )\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox, :alice]\n    test \"metadata should be hex-encoded hash\", %{alice: alice} do\n      assert %{\n               \"object\" => \"error\",\n               \"code\" => \"operation:bad_request\",\n               \"description\" => \"Parameters required by this operation are missing or incorrect.\",\n               \"messages\" => %{\n                 \"validation_error\" => %{\n                   \"parameter\" => \"metadata\",\n                   \"validator\" => \":hex\"\n                 }\n               }\n             } ==\n               WatcherHelper.no_success?(\n                 \"transaction.create\",\n                 %{\n                   \"owner\" => Encoding.to_hex(alice.addr),\n                   \"payments\" => [],\n                   \"fee\" => %{\"currency\" => @eth_hex},\n                   \"metadata\" => \"no-a-hex\"\n                 }\n               )\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox, :alice]\n    test \"payment should have valid fields\", %{alice: alice} do\n      assert %{\n               \"object\" => \"error\",\n               \"code\" => \"operation:bad_request\",\n               \"description\" => \"Parameters required by this operation are missing or incorrect.\",\n               \"messages\" => %{\n                 \"validation_error\" => %{\n                   \"parameter\" => \"payments\",\n                   \"validator\" => \":list\"\n                 }\n               }\n             } ==\n               WatcherHelper.no_success?(\n                 \"transaction.create\",\n                 %{\n                   \"owner\" => Encoding.to_hex(alice.addr),\n                   \"payments\" => \"not-a-list\",\n                   \"fee\" => %{\"currency\" => @eth_hex}\n                 }\n               )\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox, :alice]\n    test \"fee should have valid fields\", %{alice: alice} do\n      assert %{\n               \"object\" => \"error\",\n               \"code\" => \"operation:bad_request\",\n               \"description\" => \"Parameters required by this operation are missing or incorrect.\",\n               \"messages\" => %{\n                 \"validation_error\" => %{\n                   \"parameter\" => \"fee.currency\",\n                   \"validator\" => \":hex\"\n                 }\n               }\n             } ==\n               WatcherHelper.no_success?(\n                 \"transaction.create\",\n                 %{\n                   \"owner\" => Encoding.to_hex(alice.addr),\n                   \"payments\" => [],\n                   \"fee\" => %{\"currency\" => \"123\"}\n                 }\n               )\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox, :alice]\n    test \"request's fee object is mandatory\", %{alice: alice} do\n      assert %{\n               \"object\" => \"error\",\n               \"code\" => \"operation:bad_request\",\n               \"description\" => \"Parameters required by this operation are missing or incorrect.\",\n               \"messages\" => %{\n                 \"validation_error\" => %{\n                   \"parameter\" => \"fee\",\n                   \"validator\" => \":map\"\n                 }\n               }\n             } ==\n               WatcherHelper.no_success?(\n                 \"transaction.create\",\n                 %{\n                   \"owner\" => Encoding.to_hex(alice.addr),\n                   \"payments\" => []\n                 }\n               )\n    end\n  end\n\n  describe \"/transaction.merge\" do\n    setup do\n      alice = OMG.Watcher.TestHelper.generate_entity()\n      bob = OMG.Watcher.TestHelper.generate_entity()\n\n      state = %{\n        alice: Map.get(alice, :addr),\n        bob: Map.get(bob, :addr)\n      }\n\n      {:ok, state}\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns a merge transaction for the given currency\", %{alice: alice} do\n      alice_hex = Encoding.to_hex(alice)\n\n      insert(:txoutput)\n      _ = insert(:txoutput, amount: 1, currency: @eth, owner: alice)\n      _ = insert(:txoutput, amount: 1, currency: @eth, owner: alice)\n      _ = insert(:txoutput, amount: 1, currency: @eth, owner: alice)\n      _ = insert(:txoutput, amount: 1, currency: @other_token, owner: alice)\n      _ = insert(:txoutput, amount: 1, currency: @other_token, owner: alice)\n      _ = insert(:txoutput, amount: 1, currency: @other_token, owner: alice)\n\n      assert %{\n               \"transactions\" => [\n                 %{\n                   \"typed_data\" => _,\n                   \"txbytes\" => _,\n                   \"inputs\" => [\n                     %{\n                       \"amount\" => 1,\n                       \"currency\" => @eth_hex,\n                       \"owner\" => ^alice_hex\n                     },\n                     %{\n                       \"amount\" => 1,\n                       \"currency\" => @eth_hex,\n                       \"owner\" => ^alice_hex\n                     },\n                     %{\n                       \"amount\" => 1,\n                       \"currency\" => @eth_hex,\n                       \"owner\" => ^alice_hex\n                     }\n                   ],\n                   \"outputs\" => [\n                     %{\n                       \"amount\" => 3,\n                       \"currency\" => @eth_hex,\n                       \"owner\" => ^alice_hex\n                     }\n                   ]\n                 }\n               ]\n             } =\n               WatcherHelper.success?(\n                 \"transaction.merge\",\n                 %{\n                   \"address\" => alice_hex,\n                   \"currency\" => @eth_hex\n                 }\n               )\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns multiple valid merge transactions if more than four UTXO exist for the given currency\", %{\n      alice: alice\n    } do\n      alice_hex = Encoding.to_hex(alice)\n\n      insert(:txoutput)\n      _ = insert(:txoutput, amount: 1, currency: @eth, owner: alice)\n      _ = insert(:txoutput, amount: 1, currency: @eth, owner: alice)\n      _ = insert(:txoutput, amount: 1, currency: @eth, owner: alice)\n      _ = insert(:txoutput, amount: 1, currency: @eth, owner: alice)\n      _ = insert(:txoutput, amount: 1, currency: @eth, owner: alice)\n      _ = insert(:txoutput, amount: 1, currency: @eth, owner: alice)\n\n      assert %{\n               \"transactions\" => [\n                 %{\n                   \"typed_data\" => _,\n                   \"txbytes\" => _,\n                   \"inputs\" => [\n                     %{\n                       \"amount\" => 1,\n                       \"currency\" => @eth_hex,\n                       \"owner\" => ^alice_hex\n                     },\n                     %{\n                       \"amount\" => 1,\n                       \"currency\" => @eth_hex,\n                       \"owner\" => ^alice_hex\n                     },\n                     %{\n                       \"amount\" => 1,\n                       \"currency\" => @eth_hex,\n                       \"owner\" => ^alice_hex\n                     },\n                     %{\n                       \"amount\" => 1,\n                       \"currency\" => @eth_hex,\n                       \"owner\" => ^alice_hex\n                     }\n                   ],\n                   \"outputs\" => [\n                     %{\n                       \"amount\" => 4,\n                       \"currency\" => @eth_hex,\n                       \"owner\" => ^alice_hex\n                     }\n                   ]\n                 },\n                 %{\n                   \"typed_data\" => _,\n                   \"txbytes\" => _,\n                   \"inputs\" => [\n                     %{\n                       \"amount\" => 1,\n                       \"currency\" => @eth_hex,\n                       \"owner\" => ^alice_hex\n                     },\n                     %{\n                       \"amount\" => 1,\n                       \"currency\" => @eth_hex,\n                       \"owner\" => ^alice_hex\n                     }\n                   ],\n                   \"outputs\" => [\n                     %{\n                       \"amount\" => 2,\n                       \"currency\" => @eth_hex,\n                       \"owner\" => ^alice_hex\n                     }\n                   ]\n                 }\n               ]\n             } =\n               WatcherHelper.success?(\n                 \"transaction.merge\",\n                 %{\n                   \"address\" => alice_hex,\n                   \"currency\" => @eth_hex\n                 }\n               )\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns expected error if there is only one input for the given address and currency\", %{alice: alice} do\n      alice_hex = Encoding.to_hex(alice)\n\n      insert(:txoutput)\n      _ = insert(:txoutput, amount: 1, currency: @eth, owner: alice)\n\n      assert %{\n               \"code\" => \"merge:single_input\",\n               \"description\" => \"Only one input found for the given address and currency.\"\n             } =\n               WatcherHelper.no_success?(\n                 \"transaction.merge\",\n                 %{\n                   \"address\" => alice_hex,\n                   \"currency\" => @eth_hex\n                 }\n               )\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"request with valid UTXO positions returns correctly formed merge transaction\", %{alice: alice} do\n      alice_hex = Encoding.to_hex(alice)\n\n      insert(:txoutput)\n      position_1 = :txoutput |> insert(owner: alice, currency: @eth, amount: 1) |> encoded_position_from_insert()\n      position_2 = :txoutput |> insert(owner: alice, currency: @eth, amount: 1) |> encoded_position_from_insert()\n      position_3 = :txoutput |> insert(owner: alice, currency: @eth, amount: 1) |> encoded_position_from_insert()\n\n      assert %{\n               \"transactions\" => [\n                 %{\n                   \"typed_data\" => _,\n                   \"txbytes\" => _,\n                   \"inputs\" => [\n                     %{\n                       \"amount\" => 1,\n                       \"currency\" => @eth_hex,\n                       \"owner\" => ^alice_hex\n                     },\n                     %{\n                       \"amount\" => 1,\n                       \"currency\" => @eth_hex,\n                       \"owner\" => ^alice_hex\n                     },\n                     %{\n                       \"amount\" => 1,\n                       \"currency\" => @eth_hex,\n                       \"owner\" => ^alice_hex\n                     }\n                   ],\n                   \"outputs\" => [\n                     %{\n                       \"amount\" => 3,\n                       \"currency\" => @eth_hex,\n                       \"owner\" => ^alice_hex\n                     }\n                   ]\n                 }\n               ]\n             } =\n               WatcherHelper.success?(\n                 \"transaction.merge\",\n                 %{\n                   \"utxo_positions\" => [position_1, position_2, position_3]\n                 }\n               )\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"does not accept a request with more than four UTXO positions\", %{alice: alice} do\n      insert(:txoutput)\n      position_1 = :txoutput |> insert(owner: alice, currency: @eth, amount: 1) |> encoded_position_from_insert()\n      position_2 = :txoutput |> insert(owner: alice, currency: @eth, amount: 1) |> encoded_position_from_insert()\n      position_3 = :txoutput |> insert(owner: alice, currency: @eth, amount: 1) |> encoded_position_from_insert()\n      position_4 = :txoutput |> insert(owner: alice, currency: @eth, amount: 1) |> encoded_position_from_insert()\n      position_5 = :txoutput |> insert(owner: alice, currency: @eth, amount: 1) |> encoded_position_from_insert()\n\n      result =\n        WatcherHelper.no_success?(\n          \"transaction.merge\",\n          %{\n            \"utxo_positions\" => [position_1, position_2, position_3, position_4, position_5]\n          }\n        )\n\n      assert result == %{\n               \"code\" => \"operation:bad_request\",\n               \"description\" => \"Parameters required by this operation are missing or incorrect.\",\n               \"messages\" => %{\n                 \"validation_error\" => %{\n                   \"parameter\" => \"utxo_positions\",\n                   \"validator\" => \"{:max_length, 4}\"\n                 }\n               },\n               \"object\" => \"error\"\n             }\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"does not accept a request with less than two UTXO positions\", %{alice: alice} do\n      insert(:txoutput)\n      position_1 = :txoutput |> insert(owner: alice, currency: @eth, amount: 1) |> encoded_position_from_insert()\n\n      result =\n        WatcherHelper.no_success?(\n          \"transaction.merge\",\n          %{\n            \"utxo_positions\" => [position_1]\n          }\n        )\n\n      assert result == %{\n               \"code\" => \"operation:bad_request\",\n               \"description\" => \"Parameters required by this operation are missing or incorrect.\",\n               \"messages\" => %{\n                 \"validation_error\" => %{\n                   \"parameter\" => \"utxo_positions\",\n                   \"validator\" => \"{:min_length, 2}\"\n                 }\n               },\n               \"object\" => \"error\"\n             }\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"does not accept duplicate input positions\", %{alice: alice} do\n      insert(:txoutput)\n      position_1 = :txoutput |> insert(owner: alice, currency: @eth) |> encoded_position_from_insert()\n\n      assert %{\"code\" => \"merge:duplicate_input_positions\"} =\n               WatcherHelper.no_success?(\n                 \"transaction.merge\",\n                 %{\n                   \"utxo_positions\" => [position_1, position_1]\n                 }\n               )\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns expected error if there is more than one owner for a currency\", %{alice: alice, bob: bob} do\n      insert(:txoutput)\n      position_1 = :txoutput |> insert(owner: alice, currency: @eth) |> encoded_position_from_insert()\n      position_2 = :txoutput |> insert(owner: bob, currency: @eth) |> encoded_position_from_insert()\n\n      assert %{\"code\" => \"merge:multiple_input_owners\"} =\n               WatcherHelper.no_success?(\n                 \"transaction.merge\",\n                 %{\n                   \"utxo_positions\" => [position_1, position_2]\n                 }\n               )\n    end\n\n    @tag fixtures: [:phoenix_ecto_sandbox]\n    test \"returns expected error if there is more than one currency\", %{alice: alice} do\n      insert(:txoutput)\n      position_1 = :txoutput |> insert(owner: alice, currency: @eth) |> encoded_position_from_insert()\n      position_2 = :txoutput |> insert(owner: alice, currency: @other_token) |> encoded_position_from_insert()\n\n      assert %{\"code\" => \"merge:multiple_currencies\"} =\n               WatcherHelper.no_success?(\n                 \"transaction.merge\",\n                 %{\n                   \"utxo_positions\" => [position_1, position_2]\n                 }\n               )\n    end\n  end\n\n  defp encoded_position_from_insert(%{oindex: oindex, txindex: txindex, blknum: blknum}) do\n    Position.encode({:utxo_position, blknum, txindex, oindex})\n  end\n\n  defp get_block(blknum), do: DB.Repo.get(DB.Block, blknum)\n\n  defp from_hex!(hex) do\n    {:ok, result} = Encoding.from_hex(hex)\n    result\n  end\n\n  defp to_hex_or_nil(hash) do\n    case hash do\n      nil -> nil\n      hash -> Encoding.to_hex(hash)\n    end\n  end\n\n  defp transaction_all_with_paging(body) do\n    %{\n      \"success\" => true,\n      \"data\" => data,\n      \"data_paging\" => paging\n    } = WatcherHelper.rpc_call(\"transaction.all\", body, 200)\n\n    {data, paging}\n  end\n\n  defp transaction_all_result(body \\\\ nil) do\n    {result, paging} = transaction_all_with_paging(body)\n\n    assert @default_data_paging == paging\n\n    result\n  end\n\n  defp balance_in_token(address, token) do\n    currency = Encoding.to_hex(token)\n\n    Enum.find_value(WatcherHelper.get_balance(address), 0, fn\n      %{\"currency\" => ^currency, \"amount\" => amount} -> amount\n      _ -> false\n    end)\n  end\n\n  defp make_payments(blknum, spender, txs_bytes, blocks_inserter) when is_list(txs_bytes) do\n    recovered_txs =\n      Enum.map(txs_bytes, fn \"0x\" <> tx ->\n        raw_tx = tx |> Base.decode16!(case: :lower) |> Transaction.decode!()\n        n_inputs = raw_tx |> Transaction.get_inputs() |> length\n\n        raw_tx\n        |> DevCrypto.sign(List.duplicate(spender.priv, n_inputs))\n        |> Transaction.Signed.encode()\n        |> Transaction.Recovered.recover_from!()\n      end)\n\n    blocks_inserter.([{blknum, recovered_txs}])\n  end\n\n  defp prepare_test_server(context, response) do\n    response\n    |> TestServer.make_response()\n    |> TestServer.with_response(context, \"/fees.all\")\n  end\n\n  defp max_amount_spendable_in_single_tx(address, token) do\n    currency = Encoding.to_hex(token)\n\n    address\n    |> WatcherHelper.get_utxos()\n    |> Stream.filter(&(&1[\"currency\"] == currency))\n    |> Enum.sort_by(& &1[\"amount\"], &>=/2)\n    |> Stream.take(Transaction.Payment.max_inputs())\n    |> Stream.map(& &1[\"amount\"])\n    |> Enum.sum()\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/test/omg_watcher_rpc/web/controllers/utxo_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.Controller.UtxoTest do\n  use ExUnitFixtures\n  use ExUnit.Case, async: false\n  use OMG.Watcher.Fixtures\n  use OMG.WatcherInfo.Fixtures\n\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.State.Transaction.Signed\n  alias OMG.Watcher.Utxo\n  alias OMG.Watcher.Utxo.Position\n  alias Support.WatcherHelper\n\n  require Utxo\n\n  @eth <<0::160>>\n\n  @tag fixtures: [:phoenix_ecto_sandbox, :db_initialized]\n  test \"get_exit_data should return error when there is no txs in specfic block\" do\n    assert %{\n             \"code\" => \"exit:invalid\",\n             \"description\" => \"Utxo was spent or does not exist.\",\n             \"object\" => \"error\"\n           } ==\n             WatcherHelper.no_success?(\"utxo.get_exit_data\", %{\n               \"utxo_pos\" => Position.encode(Utxo.position(7001, 1, 0))\n             })\n  end\n\n  @tag fixtures: [:phoenix_ecto_sandbox, :db_initialized]\n  test \"get_exit_data should return error when there is no tx in specfic block\" do\n    assert %{\n             \"code\" => \"exit:invalid\",\n             \"description\" => \"Utxo was spent or does not exist.\",\n             \"object\" => \"error\"\n           } ==\n             WatcherHelper.no_success?(\"utxo.get_exit_data\", %{\n               \"utxo_pos\" => Position.encode(Utxo.position(7000, 1, 0))\n             })\n  end\n\n  @tag fixtures: [:phoenix_ecto_sandbox, :db_initialized, :bob]\n  test \"getting exit data returns properly formatted response\", %{bob: bob} do\n    tx = OMG.Watcher.TestHelper.create_signed([{1, 0, 0, bob}], @eth, [{bob, 100}])\n    tx_encode = Signed.encode(tx)\n\n    OMG.DB.multi_update([\n      {:put, :utxo,\n       {{1000, 0, 0}, %{amount: 100, creating_txhash: Transaction.raw_txhash(tx), currency: @eth, owner: bob.addr}}},\n      {:put, :block, %{number: 1000, hash: <<>>, transactions: [tx_encode]}}\n    ])\n\n    %{\n      \"utxo_pos\" => _utxo_pos,\n      \"txbytes\" => _txbytes,\n      \"proof\" => proof\n    } = WatcherHelper.get_exit_data(1000, 0, 0)\n\n    assert <<_proof::bytes-size(512)>> = proof\n  end\n\n  @tag fixtures: [:web_endpoint, :db_initialized]\n  test \"getting exit data returns error when there is no txs in specfic block\" do\n    utxo_pos = Position.encode(Utxo.position(7000, 1, 0))\n\n    assert %{\n             \"object\" => \"error\",\n             \"code\" => \"exit:invalid\",\n             \"description\" => \"Utxo was spent or does not exist.\"\n           } = WatcherHelper.no_success?(\"utxo.get_exit_data\", %{\"utxo_pos\" => utxo_pos})\n  end\n\n  @tag fixtures: [:phoenix_ecto_sandbox]\n  test \"utxo.get_exit_data handles improper type of parameter\" do\n    assert %{\n             \"object\" => \"error\",\n             \"code\" => \"operation:bad_request\",\n             \"description\" => \"Parameters required by this operation are missing or incorrect.\",\n             \"messages\" => %{\n               \"validation_error\" => %{\n                 \"parameter\" => \"utxo_pos\",\n                 \"validator\" => \":integer\"\n               }\n             }\n           } == WatcherHelper.no_success?(\"utxo.get_exit_data\", %{\"utxo_pos\" => \"1200000120000\"})\n  end\n\n  @tag fixtures: [:phoenix_ecto_sandbox]\n  test \"utxo.get_exit_data handles too low utxo position inputs\" do\n    assert %{\n             \"object\" => \"error\",\n             \"code\" => \"operation:bad_request\",\n             \"description\" => \"Parameters required by this operation are missing or incorrect.\",\n             \"messages\" => %{\n               \"validation_error\" => %{\n                 \"parameter\" => \"utxo_pos\",\n                 \"validator\" => \"{:greater, 0}\"\n               }\n             }\n           } = WatcherHelper.no_success?(\"utxo.get_exit_data\", %{\"utxo_pos\" => 0})\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/test/omg_watcher_rpc/web/data_case.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.DataCase do\n  @moduledoc \"\"\"\n  This module defines the setup for tests requiring\n  access to the application's data layer.\n\n  You may define functions here to be used as helpers in\n  your tests.\n\n  Finally, if the test case interacts with the database,\n  it cannot be async. For this reason, every test runs\n  inside a transaction which is reset at the beginning\n  of the test unless the test case is marked as async.\n  \"\"\"\n\n  alias Ecto.Adapters.SQL\n\n  use ExUnit.CaseTemplate\n\n  using do\n    quote do\n      alias OMG.WatcherInfo.DB\n\n      import Ecto\n      import Ecto.Changeset\n      import Ecto.Query\n      import OMG.WatcherRPC.DataCase\n    end\n  end\n\n  setup tags do\n    :ok = SQL.Sandbox.checkout(OMG.WatcherInfo.DB.Repo)\n\n    unless tags[:async] do\n      Ecto.Adapters.SQL.Sandbox.mode(OMG.WatcherInfo.DB.Repo, {:shared, self()})\n    end\n\n    :ok\n  end\n\n  @doc \"\"\"\n  A helper that transform changeset errors to a map of messages.\n\n      assert {:error, changeset} = Accounts.create_user(%{password: \"short\"})\n      assert \"password is too short\" in errors_on(changeset).password\n      assert %{password: [\"password is too short\"]} = errors_on(changeset)\n\n  \"\"\"\n  def errors_on(changeset) do\n    Ecto.Changeset.traverse_errors(changeset, fn {message, opts} ->\n      Enum.reduce(opts, message, fn {key, value}, acc ->\n        String.replace(acc, \"%{#{key}}\", to_string(value))\n      end)\n    end)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/test/omg_watcher_rpc/web/plugs/method_param_filter_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.Plugs.MethodParamFilterTest do\n  use ExUnit.Case, async: true\n  use Plug.Test\n\n  alias OMG.WatcherRPC.Web.Plugs.MethodParamFilter\n\n  # this test seems like it's reaching far to deep into internals of plugs\n  test \"filters query params for POST\" do\n    conn =\n      :post\n      |> conn(\"/some_endpoint?foo=bar\", %{\"foo_1\" => \"bar_1\"})\n      |> Plug.Parsers.call({[:json], [], nil, false})\n      |> MethodParamFilter.call([])\n\n    assert conn.body_params == %{\"foo_1\" => \"bar_1\"}\n    assert conn.query_params == %{}\n    assert conn.params == %{\"foo_1\" => \"bar_1\"}\n  end\n\n  # this test seems like it's reaching far to deep into internals of plugs\n  test \"filters body params for GET\" do\n    conn =\n      MethodParamFilter.call(\n        %Plug.Conn{body_params: %{\"foo_1\" => \"bar_1\"}, query_params: %{\"foo\" => \"bar\"}, method: \"GET\"},\n        []\n      )\n\n    assert conn.body_params == %{}\n    assert conn.query_params == %{\"foo\" => \"bar\"}\n    assert conn.params == %{\"foo\" => \"bar\"}\n  end\n\n  test \"returns original conn for other methods\" do\n    original_conn = conn(:put, \"/some_endpoint?foo=bar\", %{\"foo_1\" => \"bar_1\"})\n\n    assert MethodParamFilter.call(original_conn, []) == original_conn\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/test/omg_watcher_rpc/web/plugs/supported_watcher_modes_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.Plugs.SupportedWatcherModesTest do\n  # async: false because it needs to manipulate the global :api_mode application env.\n  use ExUnit.Case, async: false\n  use Plug.Test\n\n  alias OMG.WatcherRPC.Web.Plugs.SupportedWatcherModes\n\n  @app :omg_watcher_rpc\n\n  setup do\n    original_mode = Application.get_env(@app, :api_mode)\n\n    on_exit(fn ->\n      _ = Application.put_env(@app, :api_mode, original_mode)\n    end)\n\n    conn =\n      :post\n      |> conn(\"/some_endpoint\", %{})\n      |> Phoenix.Controller.accepts([\"json\"])\n\n    {:ok, %{conn: conn}}\n  end\n\n  test \"returns the original conn if the API mode matches a supported modes\", context do\n    :ok = Application.put_env(@app, :api_mode, :watcher_info)\n    conn = SupportedWatcherModes.call(context.conn, [:watcher, :watcher_info])\n\n    assert conn == context.conn\n  end\n\n  test \"returns operation:not_found if the API mode does not match a supported modes\", context do\n    :ok = Application.put_env(@app, :api_mode, :watcher)\n    conn = SupportedWatcherModes.call(context.conn, [:watcher_info])\n\n    assert conn.assigns[:code] == \"operation:not_found\"\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/test/omg_watcher_rpc/web/response_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.ResponseTest do\n  # async: false because it needs to manipulate the global :api_mode application env.\n  use ExUnit.Case, async: false\n  alias OMG.WatcherRPC.Web.Response\n\n  @app :omg_watcher_rpc\n\n  setup do\n    original_mode = Application.get_env(@app, :api_mode)\n\n    on_exit(fn ->\n      _ = Application.put_env(@app, :api_mode, original_mode)\n    end)\n  end\n\n  describe \"add_app_infos/1\" do\n    test \"appends the given map with a service_name and semver-compliant version\" do\n      :ok = Application.put_env(@app, :api_mode, :watcher)\n\n      assert %{foo: \"bar\", service_name: \"watcher\", version: version} = Response.add_app_infos(%{foo: \"bar\"})\n      assert {:ok, _} = Version.parse(version)\n    end\n\n    test \"appends the given map with the correct service_name\" do\n      :ok = Application.put_env(@app, :api_mode, :watcher)\n      assert %{foo: \"bar\", service_name: \"watcher\"} = Response.add_app_infos(%{foo: \"bar\"})\n\n      :ok = Application.put_env(@app, :api_mode, :watcher_info)\n      assert %{foo: \"bar\", service_name: \"watcher_info\"} = Response.add_app_infos(%{foo: \"bar\"})\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/test/omg_watcher_rpc/web/router_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.RouterTest do\n  # async: false as we need to change :api_mode application env.\n  use ExUnitFixtures\n  use ExUnit.Case, async: false\n  use OMG.WatcherInfo.Fixtures\n\n  alias Support.WatcherHelper\n\n  setup do\n    original_mode = Application.get_env(:omg_watcher_rpc, :api_mode)\n    _ = on_exit(fn -> Application.put_env(:omg_watcher_rpc, :api_mode, original_mode) end)\n\n    :ok\n  end\n\n  @tag fixtures: [:phoenix_ecto_sandbox]\n  test \"returns a successful response when calling an :info_api endpoint from :watcher_info mode\" do\n    :ok = Application.put_env(:omg_watcher_rpc, :api_mode, :watcher_info)\n    assert WatcherHelper.success?(\"transaction.all\", %{})\n  end\n\n  @tag fixtures: [:phoenix_ecto_sandbox]\n  test \"returns an error response when calling an :info_api endpoint from :watcher mode\" do\n    :ok = Application.put_env(:omg_watcher_rpc, :api_mode, :watcher)\n\n    assert %{\n             \"object\" => \"error\",\n             \"code\" => \"operation:not_found\",\n             \"description\" => \"Operation cannot be found. Check request URL.\"\n           } == WatcherHelper.no_success?(\"transaction.all\", %{})\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/test/omg_watcher_rpc/web/validators/account_contraints_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.Validator.AccountConstraintsTest do\n  @moduledoc \"\"\"\n  Account constraints validate test\n  \"\"\"\n  use ExUnit.Case, async: true\n\n  alias OMG.Eth.Encoding\n  alias OMG.WatcherRPC.Web.Validator.AccountConstraints\n\n  @fake_address_hex_string \"0x7977fe798feef376b74b6c1c5ebce8a2ccf02afd\"\n\n  describe(\"parse/1\") do\n    test \"returns page, limit and adress constraints when given page, limit and adress\" do\n      request_data = %{\n        \"page\" => 1,\n        \"limit\" => 100,\n        \"address\" => @fake_address_hex_string\n      }\n\n      {:ok, constraints} = AccountConstraints.parse(request_data)\n\n      assert constraints == [\n               address: Encoding.from_hex(@fake_address_hex_string),\n               page: 1,\n               limit: 100\n             ]\n    end\n\n    test \"return error if does not provide address\" do\n      request_data = %{\n        \"page\" => 1,\n        \"limit\" => 100\n      }\n\n      assert AccountConstraints.parse(request_data) == {:error, {:validation_error, \"address\", :hex}}\n    end\n\n    test \"return error if limit exceed 1000\" do\n      request_data = %{\n        \"address\" => @fake_address_hex_string,\n        \"page\" => 1,\n        \"limit\" => 2200\n      }\n\n      assert AccountConstraints.parse(request_data) == {:error, {:validation_error, \"limit\", {:lesser, 1000}}}\n    end\n\n    test \"return address if only address is provided\" do\n      request_data = %{\n        \"address\" => @fake_address_hex_string\n      }\n\n      {:ok, constraints} = AccountConstraints.parse(request_data)\n\n      assert constraints == [\n               address: Encoding.from_hex(@fake_address_hex_string)\n             ]\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/test/omg_watcher_rpc/web/validators/block_constraints_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.Validator.BlockConstraintsTest do\n  use ExUnit.Case, async: true\n\n  alias OMG.WatcherRPC.Web.Validator.BlockConstraints\n\n  @valid_block %{\n    \"hash\" => \"0x\" <> String.duplicate(\"00\", 32),\n    \"number\" => 1000,\n    \"transactions\" => [\"0x00\"]\n  }\n\n  describe \"parse/1\" do\n    test \"returns page and limit constraints when given page and limit params\" do\n      request_data = %{\"page\" => 1, \"limit\" => 100}\n\n      {:ok, constraints} = BlockConstraints.parse(request_data)\n      assert constraints == [page: 1, limit: 100]\n    end\n\n    test \"returns empty constraints when given no params\" do\n      request_data = %{}\n\n      {:ok, constraints} = BlockConstraints.parse(request_data)\n      assert constraints == []\n    end\n\n    test \"returns a :validation_error when the given page == 0\" do\n      assert BlockConstraints.parse(%{\"page\" => 0}) == {:error, {:validation_error, \"page\", {:greater, 0}}}\n    end\n\n    test \"returns a :validation_error when the given page < 0\" do\n      assert BlockConstraints.parse(%{\"page\" => -1}) == {:error, {:validation_error, \"page\", {:greater, 0}}}\n    end\n\n    test \"returns a :validation_error when the given page is not an integer\" do\n      assert BlockConstraints.parse(%{\"page\" => 3.14}) == {:error, {:validation_error, \"page\", :integer}}\n      assert BlockConstraints.parse(%{\"page\" => \"abcd\"}) == {:error, {:validation_error, \"page\", :integer}}\n    end\n\n    test \"returns a :validation_error when the given limit == 0\" do\n      assert BlockConstraints.parse(%{\"page\" => 0}) == {:error, {:validation_error, \"page\", {:greater, 0}}}\n    end\n\n    test \"returns a :validation_error when the given limit < 0\" do\n      assert BlockConstraints.parse(%{\"page\" => -1}) == {:error, {:validation_error, \"page\", {:greater, 0}}}\n    end\n\n    test \"returns a :validation_error when the given limit is not an integer\" do\n      assert BlockConstraints.parse(%{\"page\" => 3.14}) == {:error, {:validation_error, \"page\", :integer}}\n      assert BlockConstraints.parse(%{\"page\" => \"abcd\"}) == {:error, {:validation_error, \"page\", :integer}}\n    end\n  end\n\n  describe \"parse_to_validate/1\" do\n    test \"rejects invalid Merkle root hash\" do\n      invalid_hash = \"0x1234\"\n      invalid_block = Map.replace!(@valid_block, \"hash\", invalid_hash)\n\n      assert {:error, {:validation_error, \"hash\", {:length, 32}}} ==\n               BlockConstraints.parse_to_validate(invalid_block)\n    end\n\n    test \"rejects non-list transactions parameter\" do\n      invalid_transactions_param = \"0x1234\"\n      invalid_block = Map.replace!(@valid_block, \"transactions\", invalid_transactions_param)\n\n      assert {:error, {:validation_error, \"transactions\", :list}} ==\n               BlockConstraints.parse_to_validate(invalid_block)\n    end\n\n    test \"rejects non-hex elements in transactions list\" do\n      invalid_tx_rlp = \"0xZ\"\n      invalid_block = Map.replace!(@valid_block, \"transactions\", [invalid_tx_rlp])\n\n      assert {:error, {:validation_error, \"transactions.hash\", :hex}} ==\n               BlockConstraints.parse_to_validate(invalid_block)\n    end\n\n    test \"rejects invalid block number parameter\" do\n      invalid_blknum = \"ONE THOUSAND\"\n      invalid_block = Map.replace!(@valid_block, \"number\", invalid_blknum)\n\n      assert {:error, {:validation_error, \"number\", :integer}} ==\n               BlockConstraints.parse_to_validate(invalid_block)\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/test/omg_watcher_rpc/web/validators/merge_constraints_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.Validator.MergeConstraintsTest do\n  use ExUnit.Case, async: true\n\n  alias OMG.Eth.Encoding\n  alias OMG.WatcherRPC.Web.Validator.MergeConstraints\n\n  @eth Encoding.to_hex(<<0::160>>)\n  @alice Encoding.to_hex(<<1::160>>)\n\n  describe \"parse/1\" do\n    test \"fails if unrecognized parameters are passed in\" do\n      request_data = %{\n        \"foo\" => \"bar\"\n      }\n\n      assert MergeConstraints.parse(request_data) == {:error, :operation_bad_request}\n    end\n\n    test \"returns address and currency when given valid address and currency params\" do\n      request_data = %{\n        \"address\" => @alice,\n        \"currency\" => @eth\n      }\n\n      {:ok, constraints} = MergeConstraints.parse(request_data)\n\n      assert constraints == [{:currency, Encoding.from_hex(@eth)}, {:address, Encoding.from_hex(@alice)}]\n    end\n\n    test \"fails address/currency constraints when address is not in the right format\" do\n      request_data = %{\n        \"address\" => \"0xFake\",\n        \"currency\" => @eth\n      }\n\n      assert MergeConstraints.parse(request_data) == {:error, {:validation_error, \"address\", :hex}}\n    end\n\n    test \"fails address/currency constraints when currency is not in the right format\" do\n      request_data = %{\n        \"address\" => @alice,\n        \"currency\" => \"0xFake\"\n      }\n\n      assert MergeConstraints.parse(request_data) == {:error, {:validation_error, \"currency\", :hex}}\n    end\n\n    test \"returns `utxo_positions` when given parameter is valid\" do\n      request_data = %{\n        \"utxo_positions\" => [1, 2]\n      }\n\n      {:ok, constraints} = MergeConstraints.parse(request_data)\n\n      assert constraints == [{:utxo_positions, [1, 2]}]\n    end\n\n    test \"fails utxo_positions constraints when given less than two positions\" do\n      request_data = %{\n        \"utxo_positions\" => [1]\n      }\n\n      assert MergeConstraints.parse(request_data) == {:error, {:validation_error, \"utxo_positions\", {:min_length, 2}}}\n    end\n\n    test \"fails utxo_positions constraints when given more than four positions\" do\n      request_data = %{\n        \"utxo_positions\" => [1, 2, 3, 4, 5]\n      }\n\n      assert MergeConstraints.parse(request_data) == {:error, {:validation_error, \"utxo_positions\", {:max_length, 4}}}\n    end\n\n    test \"fails utxo_positions constraints when given a random string\" do\n      request_data = %{\n        \"utxo_positions\" => [1, 2, \"foo\"]\n      }\n\n      assert MergeConstraints.parse(request_data) == {:error, {:validation_error, \"utxo_positions.utxo_pos\", :integer}}\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/test/omg_watcher_rpc/web/validators/transaction_constraints_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.Validator.TransactionConstraintsTest do\n  use ExUnit.Case, async: true\n\n  alias OMG.Eth.Encoding\n  alias OMG.WatcherRPC.Web.Validator.TransactionConstraints\n\n  @eth <<0::160>>\n  @zero_metadata <<0::256>>\n\n  describe \"parse/1\" do\n    test \"returns page and limit constraints when given page and limit params\" do\n      request_data = %{\"page\" => 1, \"limit\" => 100}\n\n      {:ok, constraints} = TransactionConstraints.parse(request_data)\n      assert constraints == [page: 1, limit: 100]\n    end\n\n    test \"returns supported constraints when given\" do\n      request_data = %{\n        \"address\" => Encoding.to_hex(@eth),\n        \"blknum\" => 1000,\n        \"metadata\" => Encoding.to_hex(@zero_metadata),\n        \"txtypes\" => [1, 3],\n        \"end_datetime\" => 12_345_678\n      }\n\n      {:ok, constraints} = TransactionConstraints.parse(request_data)\n\n      assert constraints == [\n               end_datetime: 12_345_678,\n               txtypes: [1, 3],\n               metadata: @zero_metadata,\n               blknum: 1000,\n               address: @eth\n             ]\n    end\n\n    test \"filters unsupported constraints\" do\n      request_data = %{\n        \"something\" => \"123\"\n      }\n\n      {:ok, constraints} = TransactionConstraints.parse(request_data)\n      assert constraints == []\n    end\n\n    test \"returns empty constraints when given no params\" do\n      request_data = %{}\n\n      {:ok, constraints} = TransactionConstraints.parse(request_data)\n      assert constraints == []\n    end\n\n    test \"returns validation errors when given invalid tx_types\" do\n      assert TransactionConstraints.parse(%{\"txtypes\" => 1}) == {:error, {:validation_error, \"txtypes\", :list}}\n\n      assert TransactionConstraints.parse(%{\"txtypes\" => Enum.to_list(1..17)}) ==\n               {:error, {:validation_error, \"txtypes\", {:max_length, 16}}}\n\n      assert TransactionConstraints.parse(%{\"txtypes\" => [\"1\"]}) ==\n               {:error, {:validation_error, \"txtypes.txtype\", :integer}}\n    end\n\n    test \"returns a :validation_error when the given page == 0\" do\n      assert TransactionConstraints.parse(%{\"page\" => 0}) == {:error, {:validation_error, \"page\", {:greater, 0}}}\n    end\n\n    test \"returns a :validation_error when the given page < 0\" do\n      assert TransactionConstraints.parse(%{\"page\" => -1}) == {:error, {:validation_error, \"page\", {:greater, 0}}}\n    end\n\n    test \"returns a :validation_error when the given page is not an integer\" do\n      assert TransactionConstraints.parse(%{\"page\" => 3.14}) == {:error, {:validation_error, \"page\", :integer}}\n      assert TransactionConstraints.parse(%{\"page\" => \"abcd\"}) == {:error, {:validation_error, \"page\", :integer}}\n    end\n\n    test \"returns a :validation_error when the given limit == 0\" do\n      assert TransactionConstraints.parse(%{\"page\" => 0}) == {:error, {:validation_error, \"page\", {:greater, 0}}}\n    end\n\n    test \"returns a :validation_error when the given limit < 0\" do\n      assert TransactionConstraints.parse(%{\"page\" => -1}) == {:error, {:validation_error, \"page\", {:greater, 0}}}\n    end\n\n    test \"returns a :validation_error when the given limit is not an integer\" do\n      assert TransactionConstraints.parse(%{\"page\" => 3.14}) == {:error, {:validation_error, \"page\", :integer}}\n      assert TransactionConstraints.parse(%{\"page\" => \"abcd\"}) == {:error, {:validation_error, \"page\", :integer}}\n    end\n\n    test \"returns a :validation_error when the given end_datetime is not an integer\" do\n      assert TransactionConstraints.parse(%{\"end_datetime\" => 3.14}) ==\n               {:error, {:validation_error, \"end_datetime\", :integer}}\n\n      assert TransactionConstraints.parse(%{\"end_datetime\" => \"abcd\"}) ==\n               {:error, {:validation_error, \"end_datetime\", :integer}}\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/test/omg_watcher_rpc/web/validators/typed_data_signed_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.Validators.TypedDataSignedTest do\n  use ExUnitFixtures\n  use ExUnit.Case, async: true\n\n  alias OMG.Utils.HttpRPC.Encoding\n  alias OMG.Watcher.State.Transaction\n  alias OMG.Watcher.TestHelper\n  alias OMG.Watcher.Utxo\n  alias OMG.WatcherRPC.Web.Validator.TypedDataSigned\n\n  require Utxo\n\n  @eth <<0::160>>\n  @other_token <<127::160>>\n  @eth_hex Encoding.to_hex(@eth)\n  @token_hex Encoding.to_hex(@other_token)\n  @alice TestHelper.generate_entity()\n  @bob TestHelper.generate_entity()\n  @ari_network_address \"44DE0EC539B8C4A4B530C78620FE8320167F2F74\" |> Base.decode16!()\n  @eip_domain %{\n    name: \"OMG Network\",\n    version: \"1\",\n    salt: <<0::256>>,\n    verifyingContract: @ari_network_address\n  }\n\n  defp get_message() do\n    alice_addr = Encoding.to_hex(@alice.addr)\n    bob_addr = Encoding.to_hex(@bob.addr)\n\n    %{\n      \"input0\" => %{\"blknum\" => 1000, \"txindex\" => 0, \"oindex\" => 1},\n      \"input1\" => %{\"blknum\" => 3001, \"txindex\" => 0, \"oindex\" => 0},\n      \"input2\" => %{\"blknum\" => 0, \"txindex\" => 0, \"oindex\" => 0},\n      \"input3\" => %{\"blknum\" => 0, \"txindex\" => 0, \"oindex\" => 0},\n      \"output0\" => %{\"owner\" => alice_addr, \"currency\" => @eth_hex, \"amount\" => 10},\n      \"output1\" => %{\"owner\" => alice_addr, \"currency\" => @token_hex, \"amount\" => 300},\n      \"output2\" => %{\"owner\" => bob_addr, \"currency\" => @token_hex, \"amount\" => 100},\n      \"output3\" => %{\"owner\" => @eth_hex, \"currency\" => @eth_hex, \"amount\" => 0},\n      \"metadata\" => Encoding.to_hex(<<0::256>>)\n    }\n  end\n\n  defp get_domain(network) do\n    %{\n      \"name\" => network,\n      \"version\" => \"1\",\n      \"salt\" => Encoding.to_hex(<<0::256>>),\n      \"verifyingContract\" => @ari_network_address |> Encoding.to_hex()\n    }\n  end\n\n  test \"parses transaction from message data\" do\n    params = %{\"message\" => get_message()}\n\n    assert {:ok, tx} = TypedDataSigned.parse_transaction(params)\n\n    assert [Utxo.position(1000, 0, 1), Utxo.position(3001, 0, 0)] == Transaction.get_inputs(tx)\n    alice_addr = @alice.addr\n    bob_addr = @bob.addr\n\n    assert [\n             %{owner: ^alice_addr, currency: @eth, amount: 10},\n             %{owner: ^alice_addr, currency: @other_token, amount: 300},\n             %{owner: ^bob_addr, currency: @other_token, amount: 100}\n           ] = Transaction.get_outputs(tx)\n\n    assert <<0::256>> == tx.metadata\n  end\n\n  test \"parses transaction with metadata from message data\" do\n    metadata = (@alice.addr <> @bob.addr) |> OMG.Watcher.Crypto.hash()\n    params = %{\"message\" => %{get_message() | \"metadata\" => Encoding.to_hex(metadata)}}\n\n    assert {:ok, tx} = TypedDataSigned.parse_transaction(params)\n    assert tx.metadata == metadata\n  end\n\n  test \"validates message correctness\" do\n    invalid_input_blknum = Map.put(get_message(), \"input2\", %{\"blknum\" => -1, \"txindex\" => 0, \"oindex\" => 1})\n\n    assert {:error, {:validation_error, \"input2.blknum\", {:greater, -1}}} ==\n             TypedDataSigned.parse_transaction(%{\"message\" => invalid_input_blknum})\n\n    invalid_owner_addr = Map.put(get_message(), \"output1\", %{\"owner\" => \"0x\", \"currency\" => @eth_hex, \"amount\" => 10})\n\n    assert {:error, {:validation_error, \"output1.owner\", {:length, 20}}} ==\n             TypedDataSigned.parse_transaction(%{\"message\" => invalid_owner_addr})\n  end\n\n  test \"parses eip712 domain\" do\n    assert {:ok, @eip_domain} == \"OMG Network\" |> get_domain() |> TypedDataSigned.parse_domain()\n  end\n\n  test \"ensures network domains match\" do\n    correct_domain = @eip_domain\n    incorrect_domain = %{@eip_domain | name: \"Z0nk\"}\n\n    assert TypedDataSigned.ensure_network_match(correct_domain, @eip_domain)\n\n    assert {:error, {:validation_error, \"domain\", :domain_separator_mismatch}} ==\n             TypedDataSigned.ensure_network_match(incorrect_domain, @eip_domain)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/test/omg_watcher_rpc/web/view_case.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.ViewCase do\n  @moduledoc \"\"\"\n  This module defines common behaviors shared between view tests.\n  \"\"\"\n  use ExUnit.CaseTemplate\n\n  using do\n    quote do\n      use ExUnit.Case\n      import Phoenix.View\n    end\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/test/omg_watcher_rpc/web/views/transaction_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule OMG.WatcherRPC.Web.View.TransactionTest do\n  use ExUnitFixtures\n  use ExUnit.Case, async: false\n  use OMG.WatcherInfo.Fixtures\n\n  alias OMG.Utils.Paginator\n  alias OMG.Watcher.Utxo\n  alias OMG.WatcherInfo.DB\n  alias OMG.WatcherRPC.Web.View\n\n  require Utxo\n\n  describe \"render/2 with transaction.json\" do\n    @tag fixtures: [:initial_blocks]\n    test \"renders the transaction's inputs and outputs\" do\n      transaction =\n        1000\n        |> DB.Transaction.get_by_position(1)\n        |> DB.Repo.preload([:inputs, :outputs])\n\n      rendered = View.Transaction.render(\"transaction.json\", %{response: transaction})\n\n      # Asserts all transaction inputs get rendered\n      assert Map.has_key?(rendered.data, :inputs)\n      assert utxos_match_all?(rendered.data.inputs, transaction.inputs)\n\n      # Asserts all transaction outputs get rendered\n      assert Map.has_key?(rendered.data, :outputs)\n      assert utxos_match_all?(rendered.data.outputs, transaction.outputs)\n    end\n  end\n\n  describe \"render/2 with transactions.json\" do\n    @tag fixtures: [:initial_blocks]\n    test \"renders the transactions' inputs and outputs\" do\n      tx_1 = DB.Transaction.get_by_position(1000, 0) |> DB.Repo.preload([:inputs, :outputs])\n      tx_2 = DB.Transaction.get_by_position(1000, 1) |> DB.Repo.preload([:inputs, :outputs])\n\n      paginator = %Paginator{\n        data: [tx_1, tx_2],\n        data_paging: %{\n          limit: 10,\n          page: 1\n        }\n      }\n\n      rendered = View.Transaction.render(\"transactions.json\", %{response: paginator})\n      [rendered_1, rendered_2] = rendered.data\n\n      assert utxos_match_all?(rendered_1.inputs, tx_1.inputs)\n      assert utxos_match_all?(rendered_1.outputs, tx_1.outputs)\n      assert utxos_match_all?(rendered_2.inputs, tx_2.inputs)\n      assert utxos_match_all?(rendered_2.outputs, tx_2.outputs)\n    end\n  end\n\n  defp utxos_match_all?(renders, originals) when length(renders) != length(originals), do: false\n\n  defp utxos_match_all?(renders, originals) do\n    original_utxo_positions =\n      Enum.map(originals, fn utxo ->\n        Utxo.position(utxo.blknum, utxo.txindex, utxo.oindex) |> Utxo.Position.encode()\n      end)\n\n    Enum.all?(renders, fn rendered -> rendered.utxo_pos in original_utxo_positions end)\n  end\nend\n"
  },
  {
    "path": "apps/omg_watcher_rpc/test/test_helper.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nExUnit.configure(exclude: [mix_based_child_chain: true, integration: true, property: true, wrappers: true])\nExUnitFixtures.start()\nExUnit.start()\n\n{:ok, _} = Application.ensure_all_started(:httpoison)\n{:ok, _} = Application.ensure_all_started(:fake_server)\n{:ok, _} = Application.ensure_all_started(:ex_machina)\n\nMix.Task.run(\"ecto.create\", ~w(--quiet))\nMix.Task.run(\"ecto.migrate\", ~w(--quiet))\n"
  },
  {
    "path": "apps/xomg_tasks/lib/mix/tasks/watcher.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule Mix.Tasks.Xomg.Watcher.Start do\n  @moduledoc \"\"\"\n  Contains mix.task to run the watcher in security-critical only modes.\n\n  See the README.md file.\n  \"\"\"\n  use Mix.Task\n\n  import XomgTasks.Utils\n\n  @shortdoc \"Starts the security-critical watcher. See Mix.Tasks.Watcher.\"\n\n  def run(args) do\n    args\n    |> config_db(\"--db\")\n    |> generic_prepare_args()\n    |> generic_run([:omg_watcher, :omg_watcher_rpc])\n  end\nend\n"
  },
  {
    "path": "apps/xomg_tasks/lib/mix/tasks/watcher_info.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule Mix.Tasks.Xomg.WatcherInfo.Start do\n  @moduledoc \"\"\"\n  Contains mix.task to run the watcher in security-critical + informational mode.\n\n  See the README.md file.\n  \"\"\"\n  use Mix.Task\n\n  import XomgTasks.Utils\n\n  @shortdoc \"Starts the security-critical + informational watcher. See Mix.Tasks.Xomg.WatcherInfo.Start.\"\n\n  def run(args) do\n    args\n    |> generic_prepare_args()\n    |> generic_run([:omg_watcher_info, :omg_watcher_rpc])\n  end\nend\n"
  },
  {
    "path": "apps/xomg_tasks/lib/utils.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule XomgTasks.Utils do\n  @moduledoc \"\"\"\n  Common convenience code used to run Mix.Tasks goes here\n  \"\"\"\n\n  @doc \"\"\"\n  Runs a specific app for some arguments. Will handle IEx, if one's running\n  \"\"\"\n  def generic_run(_args, apps) when is_list(apps) do\n    _ =\n      Enum.each(apps, fn app ->\n        {:ok, _} = Application.ensure_all_started(app)\n      end)\n\n    iex_running?() || Process.sleep(:infinity)\n  end\n\n  @doc \"\"\"\n  Will do all the generic preparations on the arguments required\n  \"\"\"\n  def generic_prepare_args(args) do\n    args\n    |> ensure_contains(\"--no-start\")\n    |> ensure_doesnt_contain(\"--no-halt\")\n  end\n\n  def config_db(args, arg) do\n    index = Enum.find_index(args, fn x -> x == arg end)\n    Application.put_env(:omg_db, :path, Enum.at(args, index + 1), persistent: true)\n    args\n  end\n\n  def config_logger_level(args, arg) do\n    index = Enum.find_index(args, fn x -> x == arg end)\n    :ok = Logger.configure(level: String.to_atom(Enum.at(args, index + 1)))\n    args\n  end\n\n  defp iex_running?() do\n    Code.ensure_loaded?(IEx) and IEx.started?()\n  end\n\n  defp ensure_contains(args, arg) do\n    if Enum.member?(args, arg) do\n      args\n    else\n      [arg | args]\n    end\n  end\n\n  defp ensure_doesnt_contain(args, arg) do\n    List.delete(args, arg)\n  end\nend\n"
  },
  {
    "path": "apps/xomg_tasks/mix.exs",
    "content": "defmodule OMG.XomgTasks.MixProject do\n  @moduledoc \"\"\"\n  This is just a proxy app to hold and use all the code related to running `xomg` Mix.Tasks.\n\n  NOTE: this is not a proper mix app, just some Mix.Tasks which call into other mix apps\n  \"\"\"\n  use Mix.Project\n\n  def project() do\n    [\n      app: :xomg_tasks,\n      version: version(),\n      build_path: \"../../_build\",\n      deps_path: \"../../deps\",\n      lockfile: \"../../mix.lock\",\n      elixir: \"~> 1.8\",\n      elixirc_paths: [\"lib\"],\n      start_permanent: false,\n      deps: []\n    ]\n  end\n\n  def application() do\n    [extra_applications: [:iex, :logger]]\n  end\n\n  defp version() do\n    \"git\"\n    |> System.cmd([\"describe\", \"--tags\", \"--abbrev=0\"])\n    |> elem(0)\n    |> String.replace(\"v\", \"\")\n    |> String.replace(\"\\n\", \"\")\n  end\nend\n"
  },
  {
    "path": "apps/xomg_tasks/test/test_helper.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n"
  },
  {
    "path": "bin/generate-localchain-env",
    "content": "#!/bin/bash\n\n# this script takes in arguments (in key value pairs) and template file (localchain_contract_addresses.env)\n# looks for keys and replaces them with valuesß\nfor ARGUMENT in \"$@\"\ndo\n\n    KEY=$(echo $ARGUMENT | cut -f1 -d=)\n    VALUE=$(echo $ARGUMENT | cut -f2 -d=)\n\n    case \"$KEY\" in\n            AUTHORITY_ADDRESS)              AUTHORITY_ADDRESS=${VALUE} ;;\n            ETH_VAULT)    ETH_VAULT=${VALUE} ;;\n            ERC20_VAULT)              ERC20_VAULT=${VALUE} ;;\n            PAYMENT_EXIT_GAME)    PAYMENT_EXIT_GAME=${VALUE} ;;\n            CONTRACT_ADDRESS_PAYMENT_EXIT_GAME)              CONTRACT_ADDRESS_PAYMENT_EXIT_GAME=${VALUE} ;;\n            PLASMA_FRAMEWORK_TX_HASH)    PLASMA_FRAMEWORK_TX_HASH=${VALUE} ;;\n            PLASMA_FRAMEWORK)              PLASMA_FRAMEWORK=${VALUE} ;;\n            PAYMENT_EIP712_LIBMOCK)    PAYMENT_EIP712_LIBMOCK=${VALUE} ;;\n            MERKLE_WRAPPER)    MERKLE_WRAPPER=${VALUE} ;;\n            ERC20_MINTABLE)    ERC20_MINTABLE=${VALUE} ;;\n            *)\n    esac\n\ndone\n\nsed 's/{AUTHORITY_ADDRESS}/'$AUTHORITY_ADDRESS'/g' ../contract_addresses_template.env | \\\nsed 's/{CONTRACT_ADDRESS_ETH_VAULT}/'$ETH_VAULT'/g' | \\\nsed 's/{CONTRACT_ADDRESS_ERC20_VAULT}/'$ERC20_VAULT'/g' | \\\nsed 's/{CONTRACT_ADDRESS_PAYMENT_EXIT_GAME}/'$PAYMENT_EXIT_GAME'/g' | \\\nsed 's/{TXHASH_CONTRACT}/'$PLASMA_FRAMEWORK_TX_HASH'/g' | \\\nsed 's/{CONTRACT_ADDRESS_PLASMA_FRAMEWORK}/'$PLASMA_FRAMEWORK'/g' | \\\nsed 's/{CONTRACT_ADDRESS_PAYMENT_EIP_712_LIB_MOCK}/'$PAYMENT_EIP712_LIBMOCK'/g' | \\\nsed 's/{CONTRACT_ADDRESS_MERKLE_WRAPPER}/'$MERKLE_WRAPPER'/g' | \\\nsed 's/{CONTRACT_ERC20_MINTABLE}/'$ERC20_MINTABLE'/g' \\\n> ../localchain_contract_addresses.env\n"
  },
  {
    "path": "bin/revert",
    "content": "#!/bin/bash\n\n# This is for geth\n# https://gist.github.com/gluk64/fdea559472d957f1138ed93bcbc6f78a\n# Fetching revert reason -- https://ethereum.stackexchange.com/questions/48383/how-to-receive-revert-reason-for-past-transactions\n\nif [ -z \"$1\" ]\nthen\n    echo \"Usage: revert-reason <TX_HASH>\"\n    exit\nfi\n\nTX=$1\nSCRIPT=\" tx = eth.getTransaction( \\\"$TX\\\" ); tx.data = tx.input; eth.call(tx, tx.blockNumber)\"\n\ngeth --exec \"$SCRIPT\" attach http://localhost:8545 | cut -d '\"' -f 2 | cut -c139- | xxd -r -p\necho"
  },
  {
    "path": "bin/rocksdb",
    "content": "#!/usr/bin/env sh\n\nfancy_echo() {\n  local fmt=\"$1\"; shift\n\n  # shellcheck disable=SC2059\n  printf \"\\\\n$fmt\\\\n\" \"$@\"\n}\n\ninstall_linux_rocksdb(){\n  echo \"Installing Rocksdb v6.14.5\"\n  mkdir tmp_rocksdb \\\n  && cd tmp_rocksdb \\\n  && git clone https://github.com/facebook/rocksdb.git \\\n  && cd rocksdb  \\\n  && git checkout v6.14.5 \\\n  && make shared_lib \\\n  && sudo mkdir -p /usr/local/rocksdb/lib \\\n  && sudo mkdir -p /usr/local/rocksdb/include \\\n  && sudo cp -P librocksdb.so* /usr/local/rocksdb/lib \\\n  && sudo cp -P /usr/local/rocksdb/lib/librocksdb.so* /usr/lib/ \\\n  && sudo cp -rP include /usr/local/rocksdb/ \\\n  && sudo cp -rP include/* /usr/include/ \\\n  && cd ../../ \\\n  && rm -rf tmp_rocksdb\n}\n\ninstall_rocksdb_source(){\n  git clone https://github.com/facebook/rocksdb.git\n  cd rocksdb/\n  git checkout tags/v6.14.5\n  make clean\n  make static_lib\n  make shared_lib\n  make install INSTALL_PATH=/usr/local/Cellar/rocksdb/6.14.5\n}\n\n\ninstall_deps() {\n  local sys=`uname -s`\n  echo $sys\n  case $sys in\n    Linux*)\n      install_linux_rocksdb\n      ;;\n    Darwin*)\n      install_rocksdb_source\n      ;;\n    *)\n      fancy_echo \"Unknown system\"\n      exit 1\n      ;;\n  esac\n}\n# Exit if any subcommand fails\nset -e\n\ninstall_deps"
  },
  {
    "path": "bin/setup",
    "content": "#!/usr/bin/env sh\n\nfancy_echo() {\n  local fmt=\"$1\"; shift\n\n  # shellcheck disable=SC2059\n  printf \"\\\\n$fmt\\\\n\" \"$@\"\n}\n\ninstall_linux_deps() {\n  local LINUX_DEPS=\"automake autoconf autogen libtool gcc curl cmake gnupg liblz4-dev libsnappy-dev librocksdb-dev\"\n  local flavor=`grep \"^ID=\" /etc/os-release | cut -d\"=\" -f 2`\n  echo $flavor\n  case $flavor in\n    debian|ubuntu)\n      sudo apt-get install -y $LINUX_DEPS libgmp-dev erlang-dev build-essential libssl-dev libncurses5-dev unzip lsb-release software-properties-common\n      ;;\n    fedora|centos)\n      sudo yum install $LINUX_DEPS gmp-devel\n      ;;\n    *)\n      fancy_echo \"Unrecognized distribution $flavor\"\n      exit 1\n      ;;\n  esac\n}\n\ninstall_linux_postgres(){\n  echo \"Installing Postgres 9.6.\"\n  echo \"Adding public key 7FCC7D46ACCC4CF8 to repo.\"\n  sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 7FCC7D46ACCC4CF8\n  sudo add-apt-repository \"deb http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -sc)-pgdg main\"\n  wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -\n  sudo apt-get update\n  sudo apt-get install postgresql-9.6 -y\n  sudo apt-get install postgresql-client-9.6\n  echo \"Start PG.\"\n  sudo service postgresql start\n  echo \"Create user.\"\n  sudo -i -u postgres psql -c \"CREATE USER omisego_dev WITH PASSWORD 'omisego_dev';\"\n  echo \"Grant permission.\"\n  sudo -i -u postgres psql -c \"ALTER USER omisego_dev CREATEDB;\"\n  echo \"Done with PG.\"\n}\n\ninstall_linux_geth(){\n  echo \"Installing Geth 1.9.12\"\n  mkdir gethDownload \\\n  && wget -qO- \"https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.9.12-b6f1c8dc.tar.gz\" | tar xvz -C gethDownload --strip-components 1 \\\n  && cd gethDownload \\\n  && mkdir -p /usr/local/bin \\\n  && sudo mv geth /usr/local/bin/ \\\n  && cd .. \\\n  && rm -r gethDownload\n}\n\ninstall_darwin_deps(){\n    fancy_echo \"Installing dependencies via brew\"\n    brew bundle --file=- <<EOF\ntap \"ethereum/ethereum\"\nbrew \"automake\"\nbrew \"autoconf\"\nbrew \"gmp\"\nbrew \"libtool\"\nbrew \"cmake\"\nEOF\n}\n\ninstall_deps() {\n  local sys=`uname -s`\n  echo $sys\n  case $sys in\n    Linux*)\n      install_linux_deps\n      install_linux_geth\n      . $PWD/bin/rocksdb\n      if [ -z \"$CIRCLE_SHA1\" ]; then\n        install_linux_postgres\n      else\n        echo \"Skip Postgres setup, because CircleCI.\"\n      fi\n      ;;\n    Darwin*)\n      install_darwin_deps\n      . $PWD/bin/rocksdb\n      ;;\n    *)\n      fancy_echo \"Unknown system\"\n      exit 1\n      ;;\n  esac\n}\n\n# Exit if any subcommand fails\nset -e\n\ninstall_deps\n\n# Set up Elixir\n#git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.7.4\n#Linux\n#echo -e '\\n. $HOME/.asdf/asdf.sh' >> ~/.bashrc\n#echo -e '\\n. $HOME/.asdf/completions/asdf.bash' >> ~/.bashrc\n#source ~/.bashrc\n#MacOS\n#echo -e '\\n. $HOME/.asdf/asdf.sh' >> ~/.bash_profile\n#echo -e '\\n. $HOME/.asdf/completions/asdf.bash' >> ~/.bash_profile\n#source ~/.bash_profile\n#Linux and MacOS\n#asdf plugin-add elixir https://github.com/asdf-vm/asdf-elixir.git\n#asdf plugin-add erlang https://github.com/asdf-vm/asdf-erlang.git\n#asdf install\n\nif ! command -v mix > /dev/null; then\n  fancy_echo \"It looks like you don't have Elixir installed.\"\n  echo \"See https://asdf-vm.com/#/core-manage-asdf-vm for instructions.\"\n  echo \"See https://github.com/asdf-vm/asdf-elixir for instructions.\"\n  echo \"See https://github.com/asdf-vm/asdf-erlang for instructions.\"\n  exit 1\nfi\n\nmix local.hex --force\n\nif ! command -v mix > /dev/null; then\n  fancy_echo \"\\`mix\\`: command not found\"\n  fancy_echo \"Please add \\`~/.mix\\` to your \\$PATH environment variable\"\n  exit 1\nfi\n\nfancy_echo \"Installing elixir dependencies.\"\nmix local.hex --force\nmix local.rebar --force\n\nfancy_echo \"You're all set!\""
  },
  {
    "path": "bin/variables",
    "content": "#!/usr/bin/env sh\nexport APP_ENV=local-development\nexport HOSTNAME=http://localhost/\nexport DB_PATH=~/plasma-data/\nexport ETHEREUM_RPC_URL=http://127.0.0.1:8545\nexport ETH_NODE=geth\nexport ETHEREUM_NETWORK=LOCALCHAIN\nexport DATABASE_URL=postgresql://omisego_dev:omisego_dev@127.0.0.1:5432/omisego_dev\nexport CHILD_CHAIN_URL=http://127.0.0.1:9656\nexport ETHEREUM_HEIGHT_CHECK_INTERVAL_MS=800\nexport ETHEREUM_EVENTS_CHECK_INTERVAL_MS=800\nexport ETHEREUM_STALLED_SYNC_THRESHOLD_MS=20000\nexport EXIT_PROCESSOR_SLA_MARGIN=5520\nexport FEE_CLAIMER_ADDRESS=0x3b9f4c1dd26e0be593373b1d36cee2008cbeb837\nexport FEE_ADAPTER=feed\nexport FEE_FEED_URL=http://127.0.0.1:4000/api/v1\nexport STORED_FEE_UPDATE_INTERVAL_MINUTES=1\nexport FEE_CHANGE_TOLERANCE_PERCENT=1\nexport FEE_SPECS_FILE_PATH=./priv/dev-artifacts/fee_specs.dev.json\nexport DD_HOSTNAME=localhost\nexport DD_DISABLED=true\nexport LOGGER_BACKEND=console\nexport RELEASE_COOKIE=development\nexport NODE_HOST=127.0.0.1\n# expects it's executed from the root of the project\nFILE='./localchain_contract_addresses.env'\nwhile IFS= read -r line; do\n    DATA_TO_EXPORT='export '$line\n    eval $DATA_TO_EXPORT\ndone < ${FILE}\n"
  },
  {
    "path": "bin/variables_test_barebone",
    "content": "#!/usr/bin/env sh\n\n# this is a version of the bin/variables script which enables cabbage integration tests to run against a test-friendly\n# setup of our services\n\n# test_barebone_release taylored values. Compare also with priv/cabbage/docker-compose-cabbage.yml\nexport EXIT_PROCESSOR_SLA_MARGIN=30\n\n# the rest stays same as bin/variables\nexport APP_ENV=local-development\nexport HOSTNAME=http://localhost/\nexport DB_PATH=~/plasma-data/\nexport ETHEREUM_RPC_URL=http://127.0.0.1:8545\nexport ETH_NODE=geth\nexport ETHEREUM_NETWORK=LOCALCHAIN\nexport DATABASE_URL=postgresql://omisego_dev:omisego_dev@127.0.0.1:5432/omisego_dev\nexport CHILD_CHAIN_URL=http://127.0.0.1:9656\nexport ETHEREUM_HEIGHT_CHECK_INTERVAL_MS=800\nexport ETHEREUM_EVENTS_CHECK_INTERVAL_MS=800\nexport ETHEREUM_STALLED_SYNC_THRESHOLD_MS=20000\nexport FEE_CLAIMER_ADDRESS=0x3b9f4c1dd26e0be593373b1d36cee2008cbeb837\nexport FEE_ADAPTER=feed\nexport FEE_FEED_URL=http://127.0.0.1:4000/api/v1\nexport STORED_FEE_UPDATE_INTERVAL_MINUTES=1\nexport FEE_CHANGE_TOLERANCE_PERCENT=1\n# Fee specs file path needs to be an absolute path as the childchain will start deep in the _build subdirectory\nexport FEE_SPECS_FILE_PATH=$(pwd)/priv/dev-artifacts/fee_specs.dev.json\nexport DD_HOSTNAME=localhost\nexport DD_DISABLED=true\nexport LOGGER_BACKEND=console\nexport RELEASE_COOKIE=development\nexport NODE_HOST=127.0.0.1\n# expects it's executed from the root of the project\nFILE='./localchain_contract_addresses.env'\nwhile IFS= read -r line; do\n    DATA_TO_EXPORT='export '$line\n    eval $DATA_TO_EXPORT\ndone < ${FILE}\n"
  },
  {
    "path": "config/.credo.exs",
    "content": "# This file contains the configuration for Credo and you are probably reading\n# this after creating it with `mix credo.gen.config`.\n#\n# If you find anything wrong or unclear in this file, please report an\n# issue on GitHub: https://github.com/rrrene/credo/issues\n#\n%{\n  #\n  # You can have as many configs as you like in the `configs:` field.\n  configs: [\n    %{\n      #\n      # Run any exec using `mix credo -C <name>`. If no exec name is given\n      # \"default\" is used.\n      #\n      name: \"default\",\n      #\n      # These are the files included in the analysis:\n      files: %{\n        #\n        # You can give explicit globs or simply directories.\n        # In the latter case `**/*.{ex,exs}` will be used.\n        #\n        included: [\"lib/\", \"src/\", \"test/\", \"web/\", \"apps/\", \"config/\", \"mix.exs\"],\n        excluded: [~r\"/_build/\", ~r\"/deps/\", ~r\"/node_modules/\"]\n      },\n      #\n      # If you create your own checks, you must specify the source files for\n      # them here, so they can be loaded by Credo before running the analysis.\n      #\n      requires: [\n        \"config/credo/license_header.ex\",\n        \"config/credo/require_parentheses_on_zero_arity_defs.ex\"\n      ],\n      #\n      # If you want to enforce a style guide and need a more traditional linting\n      # experience, you can change `strict` to `true` below:\n      #\n      strict: true,\n      #\n      # If you want to use uncolored output by default, you can change `color`\n      # to `false` below:\n      #\n      color: true,\n      #\n      # You can customize the parameters of any check by adding a second element\n      # to the tuple.\n      #\n      # To disable a check put `false` as second element:\n      #\n      #     {Credo.Check.Design.DuplicatedCode, false}\n      #\n      checks: [\n        {Credo.Check.Refactor.MapInto, false},\n        # custom checks\n        {Credo.Check.Warning.LicenseHeader},\n        {Credo.Check.Readability.RequireParenthesesOnZeroArityDefs},\n        #\n        ## Consistency Checks\n        #\n        {Credo.Check.Consistency.ExceptionNames, []},\n        {Credo.Check.Consistency.LineEndings, []},\n        {Credo.Check.Consistency.ParameterPatternMatching, []},\n        {Credo.Check.Consistency.SpaceAroundOperators, []},\n        {Credo.Check.Consistency.SpaceInParentheses, []},\n        {Credo.Check.Consistency.TabsOrSpaces, []},\n\n        #\n        ## Design Checks\n        #\n        # You can customize the priority of any check\n        # Priority values are: `low, normal, high, higher`\n        #\n        {Credo.Check.Design.AliasUsage, [priority: :low, if_nested_deeper_than: 3, if_called_more_often_than: 1]},\n        # You can also customize the exit_status of each check.\n        # If you don't want TODO comments to cause `mix credo` to fail, just\n        # set this value to 0 (zero).\n        #\n        {Credo.Check.Design.TagTODO, [exit_status: 0]},\n        {Credo.Check.Design.TagFIXME, []},\n\n        #\n        ## Readability Checks\n        #\n        {Credo.Check.Readability.AliasOrder, []},\n        {Credo.Check.Readability.FunctionNames, []},\n        {Credo.Check.Readability.LargeNumbers, []},\n        {Credo.Check.Readability.MaxLineLength, [priority: :low, max_length: 120]},\n        {Credo.Check.Readability.ModuleAttributeNames, []},\n        {Credo.Check.Readability.ModuleDoc, []},\n        {Credo.Check.Readability.ModuleNames, []},\n        {Credo.Check.Readability.ParenthesesInCondition, []},\n        {Credo.Check.Readability.ParenthesesOnZeroArityDefs, false},\n        {Credo.Check.Readability.PredicateFunctionNames, []},\n        {Credo.Check.Readability.PreferImplicitTry, []},\n        {Credo.Check.Readability.RedundantBlankLines, []},\n        {Credo.Check.Readability.Semicolons, []},\n        {Credo.Check.Readability.SpaceAfterCommas, []},\n        {Credo.Check.Readability.StringSigils, []},\n        {Credo.Check.Readability.TrailingBlankLine, []},\n        {Credo.Check.Readability.TrailingWhiteSpace, []},\n        {Credo.Check.Readability.VariableNames, []},\n\n        #\n        ## Refactoring Opportunities\n        #\n        {Credo.Check.Refactor.CondStatements, []},\n        {Credo.Check.Refactor.CyclomaticComplexity, []},\n        {Credo.Check.Refactor.FunctionArity, []},\n        {Credo.Check.Refactor.LongQuoteBlocks, []},\n        {Credo.Check.Refactor.MatchInCondition, []},\n        {Credo.Check.Refactor.NegatedConditionsInUnless, []},\n        {Credo.Check.Refactor.NegatedConditionsWithElse, []},\n        {Credo.Check.Refactor.Nesting, []},\n        {Credo.Check.Refactor.PipeChainStart, false},\n        {Credo.Check.Refactor.UnlessWithElse, []},\n\n        #\n        ## Warnings\n        #\n        {Credo.Check.Warning.BoolOperationOnSameValues, []},\n        {Credo.Check.Warning.ExpensiveEmptyEnumCheck, []},\n        {Credo.Check.Warning.IExPry, []},\n        {Credo.Check.Warning.IoInspect, []},\n        {Credo.Check.Warning.LazyLogging, false},\n        {Credo.Check.Warning.OperationOnSameValues, []},\n        {Credo.Check.Warning.OperationWithConstantResult, []},\n        {Credo.Check.Warning.RaiseInsideRescue, []},\n        {Credo.Check.Warning.UnusedEnumOperation, []},\n        {Credo.Check.Warning.UnusedFileOperation, []},\n        {Credo.Check.Warning.UnusedKeywordOperation, []},\n        {Credo.Check.Warning.UnusedListOperation, []},\n        {Credo.Check.Warning.UnusedPathOperation, []},\n        {Credo.Check.Warning.UnusedRegexOperation, []},\n        {Credo.Check.Warning.UnusedStringOperation, []},\n        {Credo.Check.Warning.UnusedTupleOperation, []},\n\n        #\n        # Controversial and experimental checks (opt-in, just remove `, false`)\n        #\n        {Credo.Check.Readability.SinglePipe, []},\n        {Credo.Check.Consistency.MultiAliasImportRequireUse, false},\n        {Credo.Check.Design.DuplicatedCode, false},\n        {Credo.Check.Readability.Specs, false},\n        {Credo.Check.Refactor.ABCSize, false},\n        {Credo.Check.Refactor.AppendSingleItem, false},\n        {Credo.Check.Refactor.DoubleBooleanNegation, false},\n        {Credo.Check.Refactor.VariableRebinding, false},\n        {Credo.Check.Warning.MapGetUnsafePass, false},\n        {Credo.Check.Warning.UnsafeToAtom, false}\n\n        #\n        # Custom checks can be created using `mix credo.gen.check`.\n        #\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": "config/config.exs",
    "content": "import Config\nethereum_events_check_interval_ms = 8_000\n\nconfig :logger, level: :info\n\nconfig :logger, :console,\n  format: \"$date $time [$level] $metadata⋅$message⋅\\n\",\n  discard_threshold: 2000,\n  metadata: [:module, :function, :request_id, :trace_id, :span_id]\n\nconfig :logger,\n  backends: [Sentry.LoggerBackend, Ink]\n\nconfig :logger, Ink,\n  name: \"elixir-omg\",\n  exclude_hostname: true,\n  log_encoding_error: true\n\nconfig :logger, Sentry.LoggerBackend,\n  include_logger_metadata: true,\n  ignore_plug: true\n\nconfig :sentry,\n  filter: OMG.Status.SentryFilter,\n  dsn: nil,\n  environment_name: nil,\n  included_environments: [],\n  server_name: 'localhost',\n  tags: %{\n    application: nil,\n    eth_network: nil,\n    eth_node: :geth\n  }\n\nconfig :omg_watcher,\n  deposit_finality_margin: 10,\n  ethereum_events_check_interval_ms: ethereum_events_check_interval_ms,\n  coordinator_eth_height_check_interval_ms: 6_000\n\nconfig :omg_watcher, :eip_712_domain,\n  name: \"OMG Network\",\n  version: \"1\",\n  salt: \"0xfad5c7f626d80f9256ef01929f3beb96e058b8b4b0e3fe52d84f054c0e2a7a83\"\n\n# Configures the endpoint\n# https://ninenines.eu/docs/en/cowboy/2.4/manual/cowboy_http/\n# defaults are:\n# protocol_options:[max_header_name_length: 64,\n# max_header_value_length: 4096,\n# max_headers: 100,\n# max_request_line_length: 8096\n# ]\n\n# Use Poison for JSON parsing in Phoenix\nconfig :phoenix,\n  json_library: Jason,\n  serve_endpoints: true,\n  persistent: true\n\nconfig :omg_db,\n  metrics_collection_interval: 60_000\n\nethereum_client_timeout_ms = 20_000\n\nconfig :ethereumex,\n  url: \"http://localhost:8545\",\n  http_options: [recv_timeout: ethereum_client_timeout_ms]\n\nconfig :omg_eth,\n  contract_addr: nil,\n  authority_address: nil,\n  txhash_contract: nil,\n  eth_node: :geth,\n  child_block_interval: 1000,\n  min_exit_period_seconds: nil,\n  ethereum_block_time_seconds: 15,\n  ethereum_events_check_interval_ms: ethereum_events_check_interval_ms,\n  ethereum_stalled_sync_threshold_ms: 20_000,\n  node_logging_in_debug: false\n\nconfig :omg_status,\n  statsd_reconnect_backoff_ms: 10_000,\n  system_memory_check_interval_ms: 10_000,\n  system_memory_high_threshold: 0.8\n\nconfig :omg_status, OMG.Status.Metric.Tracer,\n  service: :omg_status,\n  adapter: SpandexDatadog.Adapter,\n  disabled?: true,\n  type: :backend\n\nconfig :spandex, :decorators, tracer: OMG.Status.Metric.Tracer\n\nconfig :statix,\n  host: \"datadog\",\n  port: 8125\n\nconfig :spandex_datadog,\n  host: \"datadog\",\n  port: 8126,\n  batch_size: 10,\n  sync_threshold: 100,\n  http: HTTPoison\n\nconfig :vmstats,\n  sink: OMG.Status.Metric.VmstatsSink,\n  interval: 15_000,\n  base_key: 'vmstats',\n  key_separator: '$.',\n  sched_time: true,\n  memory_metrics: [\n    total: :total,\n    processes_used: :procs_used,\n    atom_used: :atom_used,\n    binary: :binary,\n    ets: :ets\n  ]\n\n# Disable :os_mon's system_memory_high_watermark in favor of our own OMG.Status.Monitor.SystemMemory\n# See http://erlang.org/pipermail/erlang-questions/2006-September/023144.html\nconfig :os_mon,\n  system_memory_high_watermark: 1.00,\n  process_memory_high_watermark: 1.00\n\nconfig :omg_watcher, child_chain_url: \"http://localhost:9656\"\n\nconfig :omg_watcher,\n  # 23 hours worth of blocks - this is how long the child chain server has to block spends from exiting utxos\n  exit_processor_sla_margin: 23 * 60 * 4,\n  # this means we don't want the `sla_margin` above be larger than the `min_exit_period`\n  exit_processor_sla_margin_forced: false,\n  maximum_block_withholding_time_ms: 15 * 60 * 60 * 1000,\n  maximum_number_of_unapplied_blocks: 50,\n  exit_finality_margin: 12,\n  block_getter_reorg_margin: 200,\n  metrics_collection_interval: 60_000\n\nconfig :omg_watcher, OMG.Watcher.Tracer,\n  service: :omg_watcher,\n  adapter: SpandexDatadog.Adapter,\n  disabled?: true,\n  type: :omg_watcher\n\nconfig :omg_watcher_info,\n  child_chain_url: \"http://localhost:9656\",\n  namespace: OMG.WatcherInfo,\n  ecto_repos: [OMG.WatcherInfo.DB.Repo],\n  metrics_collection_interval: 60_000\n\n# Configures the endpoint\n\nconfig :omg_watcher_info, OMG.WatcherInfo.DB.Repo,\n  adapter: Ecto.Adapters.Postgres,\n  # NOTE: not sure if appropriate, but this allows reasonable blocks to be written to unoptimized Postgres setup\n  timeout: 180_000,\n  connect_timeout: 180_000,\n  url: \"postgres://omisego_dev:omisego_dev@localhost/omisego_dev\",\n  migration_timestamps: [type: :timestamptz],\n  telemetry_prefix: [:omg_watcher, :watcher_info, :db, :repo]\n\nconfig :omg_watcher_info, OMG.WatcherInfo.Tracer,\n  service: :ecto,\n  adapter: SpandexDatadog.Adapter,\n  disabled?: true,\n  type: :db\n\n# In mix environment, all modules are loaded, therefore it behaves like a watcher_info\nconfig :omg_watcher_rpc,\n  api_mode: :watcher_info\n\n# Configures the endpoint\n# https://ninenines.eu/docs/en/cowboy/2.4/manual/cowboy_http/\n# defaults are:\n# protocol_options:[max_header_name_length: 64,\n# max_header_value_length: 4096,\n# max_headers: 100,\n# max_request_line_length: 8096\n# ]\nconfig :omg_watcher_rpc, OMG.WatcherRPC.Web.Endpoint,\n  render_errors: [view: OMG.WatcherRPC.Web.Views.Error, accepts: ~w(json)],\n  enable_cors: true,\n  http: [:inet6, port: 7434, protocol_options: [max_request_line_length: 8192, max_header_value_length: 8192]],\n  url: [host: \"w.example.com\", port: 80],\n  code_reloader: false\n\nconfig :phoenix,\n  json_library: Jason,\n  serve_endpoints: true,\n  persistent: true\n\nconfig :spandex_ecto, SpandexEcto.EctoLogger, tracer: OMG.WatcherInfo.Tracer\n\nconfig :omg_watcher_rpc, OMG.WatcherRPC.Tracer,\n  service: :web,\n  adapter: SpandexDatadog.Adapter,\n  disabled?: true,\n  type: :web\n\nconfig :spandex_phoenix, tracer: OMG.WatcherRPC.Tracer\n\nconfig :briefly, directory: [\"/tmp/omisego\"]\n\nimport_config \"#{Mix.env()}.exs\"\n"
  },
  {
    "path": "config/credo/license_header.ex",
    "content": "defmodule Credo.Check.Warning.LicenseHeader do\n  @moduledoc \"\"\"\n  Checks whether license header has been included in every file, except those where it shouldn't be\n\n  **Doesn't** check the correctness of the header, just that it exists, so it checks first line to say `# Copyright`\n  \"\"\"\n\n  @explanation [\n    check: @moduledoc\n  ]\n\n  # you can configure the basics of your check via the `use Credo.Check` call\n  use Credo.Check, base_priority: :high, category: :custom\n\n  @doc false\n  def run(%SourceFile{filename: source_path} = source_file, params \\\\ []) do\n    # we ignore config, mix.exs and migration files, so all of these return no issues, i.e. []\n    case Path.split(source_path) do\n      [\"apps\", _, \"config\" | _] -> []\n      [\"config\" | _] -> []\n      [\"mix.exs\"] -> []\n      [\"apps\", _, \"mix.exs\" | _] -> []\n      [\"apps\", _, \"priv\" , \"repo\" | _] -> []\n      _ -> do_run(source_file, params)\n    end\n  end\n\n  defp do_run(source_file, params) do\n    lines = SourceFile.lines(source_file)\n    {1, first_line} = hd(lines)\n\n    # IssueMeta helps us pass down both the source_file and params of a check\n    # run to the lower levels where issues are created, formatted and returned\n    issue_meta = IssueMeta.for(source_file, params)\n\n    if String.starts_with?(first_line, \"# Copyright\") do\n      []\n    else\n      trigger = first_line\n      new_issue = issue_for(issue_meta, 1, trigger)\n      [new_issue]\n    end\n  end\n\n  defp issue_for(issue_meta, line_no, trigger) do\n    # format_issue/2 is a function provided by Credo.Check to help us format the\n    # found issue\n    format_issue issue_meta,\n      message: \"File is missing a license header, make sure to include the license header as other files do\",\n      line_no: line_no,\n      trigger: trigger\n  end\nend\n"
  },
  {
    "path": "config/credo/require_parentheses_on_zero_arity_defs.ex",
    "content": "defmodule Credo.Check.Readability.RequireParenthesesOnZeroArityDefs do\n  @moduledoc false\n\n  @checkdoc \"\"\"\n  Use parentheses even when defining a function which has no arguments.\n\n  The code in this example ...\n\n      def summer? do\n        # ...\n      end\n\n  ... should be refactored to look like this:\n\n      def summer?() do\n        # ...\n      end\n\n  Like all `Readability` issues, this one is not a technical concern.\n  But you can improve the odds of others reading and liking your code by making\n  it easier to follow.\n\n  This conforms with the style guide at https://github.com/lexmag/elixir-style-guide/blob/master/README.md#parentheses\n  \"\"\"\n  @explanation [check: @checkdoc]\n  @def_ops [:def, :defp, :defmacro, :defmacrop]\n\n  use Credo.Check, base_priority: :normal\n\n  @doc false\n  def run(source_file, params \\\\ []) do\n    issue_meta = IssueMeta.for(source_file, params)\n\n    Credo.Code.prewalk(source_file, &traverse(&1, &2, issue_meta))\n  end\n\n  for op <- @def_ops do\n    # catch variables named e.g. `defp`\n    defp traverse({unquote(op), _, nil} = ast, issues, _issue_meta) do\n      {ast, issues}\n    end\n\n    defp traverse({unquote(op), _, body} = ast, issues, issue_meta) do\n      function_head = Enum.at(body, 0)\n\n      {ast, issues_for_definition(function_head, issues, issue_meta)}\n    end\n  end\n\n  defp traverse(ast, issues, _issue_meta) do\n    {ast, issues}\n  end\n\n  # skip the false positive for a metaprogrammed definition\n  defp issues_for_definition({{:unquote, _, _}, _, _}, issues, _) do\n    issues\n  end\n\n  defp issues_for_definition({_, _, args}, issues, _) when length(args) > 0 do\n    issues\n  end\n\n  defp issues_for_definition({name, meta, _}, issues, issue_meta) do\n    line_no = meta[:line]\n    text = remaining_line_after(issue_meta, line_no, name)\n\n    case String.match?(text, ~r/^\\(([\\w]*)\\)(.)*/) do\n      true -> issues\n      false -> issues ++ [issue_for(issue_meta, line_no)]\n    end\n  end\n\n  defp remaining_line_after(issue_meta, line_no, text) do\n    source_file = IssueMeta.source_file(issue_meta)\n    line = SourceFile.line_at(source_file, line_no)\n    name_size = text |> to_string |> String.length()\n    skip = (column(source_file, line_no, text) || -1) + name_size - 1\n\n    String.slice(line, skip..-1)\n  end\n\n  defp issue_for(issue_meta, line_no) do\n    format_issue(\n      issue_meta,\n      message: \"Use parentheses even when defining a function which has no arguments.\",\n      line_no: line_no\n    )\n  end\n\n  # A modified version of https://github.com/rrrene/credo/blob/master/lib/credo/source_file.ex#L140-L161\n  # that removes `Regex.escape()` as it's breaking question-mark ending functions.\n  defp column(source_file, line_no, trigger)\n\n  defp column(source_file, line_no, trigger) when is_binary(trigger) or is_atom(trigger) do\n    line = SourceFile.line_at(source_file, line_no)\n    regexed = to_string(trigger)\n\n    case Regex.run(~r/\\b#{regexed}\\b/, line, return: :index) do\n      nil ->\n        nil\n\n      result ->\n        {col, _} = List.first(result)\n        col + 1\n    end\n  end\n\n  defp column(_, _, _), do: nil\nend\n"
  },
  {
    "path": "config/dev.exs",
    "content": "import Config\n\nconfig :logger,\n  backends: [:console, Sentry.LoggerBackend]\n\nconfig :omg_watcher,\n  ethereum_events_check_interval_ms: 500,\n  coordinator_eth_height_check_interval_ms: 1_000\n\nconfig :omg_db,\n  path: Path.join([System.get_env(\"HOME\"), \".omg/data\"])\n\nconfig :ethereumex,\n  http_options: [recv_timeout: 60_000]\n\nconfig :omg_eth,\n  min_exit_period_seconds: 10 * 60,\n  ethereum_block_time_seconds: 1,\n  node_logging_in_debug: true\n\nconfig :omg_watcher_rpc, environment: :dev\nconfig :phoenix, :stacktrace_depth, 20\n\nconfig :omg_watcher_rpc, OMG.WatcherRPC.Tracer,\n  disabled?: true,\n  env: \"development\"\n\nconfig :omg_watcher_info, environment: :dev\n\nconfig :omg_watcher_info, OMG.WatcherInfo.Tracer,\n  disabled?: true,\n  env: \"development\"\n\nconfig :omg_watcher, environment: :dev\n\nconfig :omg_watcher,\n  # 1 hour of Ethereum blocks\n  exit_processor_sla_margin: 60 * 4,\n  # this means we allow the `sla_margin` above be larger than the `min_exit_period`\n  exit_processor_sla_margin_forced: true\n\nconfig :omg_watcher, OMG.Watcher.Tracer,\n  disabled?: true,\n  env: \"development\"\n\nconfig :omg_status, OMG.Status.Metric.Tracer,\n  env: \"development\",\n  disabled?: true\n"
  },
  {
    "path": "config/prod.exs",
    "content": "use Mix.Config\n"
  },
  {
    "path": "config/releases.exs",
    "content": "import Config\n\n# This `releases.exs` config file gets evaluated at RUNTIME, unlike other config files that are\n# evaluated at compile-time.\n#\n# See https://hexdocs.pm/mix/1.9.0/Mix.Tasks.Release.html#module-runtime-configuration\n\n# with this helper anon. function you can\n# load and validate specific watcher or watcher info\n# configuration\n# env_var_name - gets passed into System.get_env/1\n# exception - is the string that gets thrown so that we prevent release boot\n# third argument is if this is a watcher info resolver\n# fourth argument is whether this is a watcher info specific configuration\nmandatory = fn\n  env_var_name, _exception, false, true ->\n    # this case covers a watcher info setting\n    # under watcher security application\n    # it's ok if the env var is missing\n    case System.get_env(env_var_name) do\n      nil -> \"WATCHER_INFO_SETTING\"\n      data -> data\n    end\n\n  env_var_name, exception, true, true ->\n    case System.get_env(env_var_name) do\n      nil -> throw(exception)\n      data -> data\n    end\n\n  env_var_name, exception, _, false ->\n    case System.get_env(env_var_name) do\n      nil -> throw(exception)\n      data -> data\n    end\nend\n\nwatcher_info? = fn -> Code.ensure_loaded?(OMG.WatcherInfo) end\n\nconfig :omg_watcher_info, OMG.WatcherInfo.DB.Repo,\n  url: mandatory.(\"DATABASE_URL\", \"DATABASE_URL needs to be set.\", watcher_info?.(), true),\n  # Have at most `:pool_size` DB connections on standby and serving DB queries.\n  pool_size: String.to_integer(System.get_env(\"WATCHER_INFO_DB_POOL_SIZE\") || \"10\"),\n  # Wait at most `:queue_target` for a connection. If all connections checked out during\n  # a `:queue_interval` takes more than `:queue_target`, then we double the `:queue_target`.\n  # If checking out connections take longer than the new target, a DBConnection.ConnectionError is raised.\n  # See: https://hexdocs.pm/db_connection/DBConnection.html#start_link/2-queue-config\n  queue_target: String.to_integer(System.get_env(\"WATCHER_INFO_DB_POOL_QUEUE_TARGET_MS\") || \"50\"),\n  queue_interval: String.to_integer(System.get_env(\"WATCHER_INFO_DB_POOL_QUEUE_INTERVAL_MS\") || \"1000\")\n\nconfig :omg_watcher,\n  child_chain_url: mandatory.(\"CHILD_CHAIN_URL\", \"CHILD_CHAIN_URL needs to be set.\", watcher_info?.(), false)\n\nconfig :omg_watcher_info,\n  child_chain_url: mandatory.(\"CHILD_CHAIN_URL\", \"CHILD_CHAIN_URL needs to be set.\", watcher_info?.(), true)\n"
  },
  {
    "path": "config/test.exs",
    "content": "use Mix.Config\nethereum_events_check_interval_ms = 400\n\nparse_contracts = fn ->\n  local_umbrella_path = Path.join([File.cwd!(), \"../../\", \"localchain_contract_addresses.env\"])\n\n  contract_addreses_path =\n    case File.exists?(local_umbrella_path) do\n      true ->\n        local_umbrella_path\n\n      _ ->\n        # CI/CD\n        Path.join([File.cwd!(), \"localchain_contract_addresses.env\"])\n    end\n\n  contract_addreses_path\n  |> File.read!()\n  |> String.split(\"\\n\", trim: true)\n  |> List.flatten()\n  |> Enum.reduce(%{}, fn line, acc ->\n    [key, value] = String.split(line, \"=\")\n    Map.put(acc, key, value)\n  end)\nend\n\ncontracts = parse_contracts.()\n\nconfig :logger, level: :warn\n\nconfig :logger,\n  backends: [:console, Sentry.LoggerBackend]\n\nconfig :sentry,\n  dsn: nil,\n  environment_name: nil,\n  included_environments: [],\n  server_name: nil,\n  tags: %{\n    application: nil,\n    eth_network: nil,\n    eth_node: :geth\n  }\n\nconfig :omg_utils,\n  environment: :test\n\nconfig :omg_watcher,\n  deposit_finality_margin: 1,\n  ethereum_events_check_interval_ms: ethereum_events_check_interval_ms,\n  coordinator_eth_height_check_interval_ms: 10,\n  environment: :test,\n  fee_claimer_address: Base.decode16!(\"DEAD000000000000000000000000000000000000\")\n\n# config :omg_db,\n#  path: Path.join([System.get_env(\"HOME\"), \".omg/data\"])\n\n# bumping these timeouts into infinity - let's rely on test timeouts rather than these\nconfig :ethereumex,\n  url: System.get_env(\"ETHEREUM_RPC_URL\", \"http://localhost:8545\"),\n  http_options: [recv_timeout: :infinity],\n  id_reset: true\n\nconfig :omg_eth,\n  # Needed for test only to have some value of address when `:contract_address` is not set explicitly\n  # required by the EIP-712 struct hash code\n  txhash_contract: contracts[\"TXHASH_CONTRACT\"],\n  authority_address: contracts[\"AUTHORITY_ADDRESS\"],\n  contract_addr: %{\n    erc20_vault: contracts[\"CONTRACT_ADDRESS_ERC20_VAULT\"],\n    eth_vault: contracts[\"CONTRACT_ADDRESS_ETH_VAULT\"],\n    payment_exit_game: contracts[\"CONTRACT_ADDRESS_PAYMENT_EXIT_GAME\"],\n    plasma_framework: contracts[\"CONTRACT_ADDRESS_PLASMA_FRAMEWORK\"]\n  },\n  node_logging_in_debug: true,\n  # Lower the event check interval too low and geth will die\n  ethereum_events_check_interval_ms: ethereum_events_check_interval_ms,\n  min_exit_period_seconds: 22,\n  ethereum_block_time_seconds: 1,\n  eth_node: :geth,\n  run_test_eth_dev_node: true\n\nconfig :omg_status,\n  metrics: false,\n  environment: :test,\n  statsd_reconnect_backoff_ms: 10\n\nconfig :omg_status, OMG.Status.Metric.Tracer,\n  env: \"test\",\n  disabled?: true\n\nconfig :statix,\n  host: \"datadog\",\n  port: 8125\n\nconfig :spandex_datadog,\n  host: \"datadog\",\n  port: 8126,\n  batch_size: 10,\n  sync_threshold: 10,\n  http: HTTPoison\n\nconfig :os_mon,\n  memsup_helper_timeout: 120,\n  memory_check_interval: 5,\n  system_memory_high_watermark: 0.99,\n  disk_almost_full_threshold: 0.99,\n  disk_space_check_interval: 120\n\nconfig :omg_watcher, child_chain_url: System.get_env(\"CHILD_CHAIN_URL\", \"http://localhost:9656/\")\n\nconfig :omg_watcher,\n  # NOTE `exit_processor_sla_margin` can't be made shorter. At 8 it sometimes\n  # causes unchallenged exits events because `geth --dev` is too fast\n  # Chaning this value for dockerized geth in OMG.Watcher.Fixtures!!!\n  exit_processor_sla_margin: 10,\n  # this means we allow the `sla_margin` above be larger than the `min_exit_period`\n  exit_processor_sla_margin_forced: true,\n  # NOTE: `maximum_block_withholding_time_ms` must be here - one of our integration tests\n  # actually fakes block withholding to test something\n  maximum_block_withholding_time_ms: 1_000,\n  exit_finality_margin: 1\n\nconfig :omg_watcher, OMG.Watcher.Tracer,\n  disabled?: true,\n  env: \"test\"\n\nconfig :omg_watcher_info, child_chain_url: System.get_env(\"CHILD_CHAIN_URL\", \"http://localhost:9656/\")\n\nconfig :omg_watcher_info, OMG.WatcherInfo.DB.Repo,\n  ownership_timeout: 500_000,\n  pool: Ecto.Adapters.SQL.Sandbox,\n  # DATABASE_URL format is following `postgres://{user_name}:{password}@{host:port}/{database_name}`\n  url: System.get_env(\"TEST_DATABASE_URL\", \"postgres://omisego_dev:omisego_dev@localhost:5432/omisego_test\")\n\nconfig :omg_watcher_info, OMG.WatcherInfo.Tracer,\n  disabled?: true,\n  env: \"test\"\n\nconfig :omg_watcher_info, environment: :test\n\nconfig :omg_watcher_rpc, OMG.WatcherRPC.Web.Endpoint,\n  http: [port: 7435],\n  server: true\n\nconfig :omg_watcher_rpc, OMG.WatcherRPC.Tracer,\n  service: :omg_watcher_rpc,\n  adapter: SpandexDatadog.Adapter,\n  disabled?: true,\n  env: \"test\",\n  type: :web\n"
  },
  {
    "path": "contract_addresses_template.env",
    "content": "AUTHORITY_ADDRESS={AUTHORITY_ADDRESS}\nCONTRACT_ADDRESS_ETH_VAULT={CONTRACT_ADDRESS_ETH_VAULT}\nCONTRACT_ADDRESS_ERC20_VAULT={CONTRACT_ADDRESS_ERC20_VAULT}\nCONTRACT_ADDRESS_PAYMENT_EXIT_GAME={CONTRACT_ADDRESS_PAYMENT_EXIT_GAME}\nCONTRACT_ADDRESS_PLASMA_FRAMEWORK={CONTRACT_ADDRESS_PLASMA_FRAMEWORK}\nTXHASH_CONTRACT={TXHASH_CONTRACT}\nCONTRACT_ADDRESS_PAYMENT_EIP_712_LIB_MOCK={CONTRACT_ADDRESS_PAYMENT_EIP_712_LIB_MOCK}\nCONTRACT_ADDRESS_MERKLE_WRAPPER={CONTRACT_ADDRESS_MERKLE_WRAPPER}\nCONTRACT_ERC20_MINTABLE={CONTRACT_ERC20_MINTABLE}\n"
  },
  {
    "path": "coveralls.json",
    "content": "{\n  \"skip_files\": [\n    \"apps/omg/test/support\",\n    \"apps/omg_bus/test/support\",\n    \"apps/omg_db/test/support\",\n    \"apps/omg_eth/test/support\",\n    \"apps/omg_status/test/support\",\n    \"apps/omg_utils/test/support\",\n    \"apps/omg_watcher/test/support\",\n    \"apps/omg_watcher_info/test/support\",\n    \"apps/omg_watcher_info/lib/omg_watcher_info/release_tasks/init_postgresql_db.ex\",\n    \"apps/omg_watcher_rpc/test/support\",\n    \"apps/xomg_tasks\"\n  ]\n}\n"
  },
  {
    "path": "dialyzer.ignore-warnings",
    "content": "test/support/\napps/omg/lib/omg/utxo/position.ex:46: Type specification 'Elixir.OMG.Utxo.Position':'decode!'(number()) -> t() is a supertype of the success typing: 'Elixir.OMG.Utxo.Position':'decode!'(number()) -> {'utxo_position',non_neg_integer(),non_neg_integer(),char()}\napps/omg/lib/omg/utxo/position.ex:52: Type specification 'Elixir.OMG.Utxo.Position':decode(number()) -> {'ok',t()} | {'error','encoded_utxo_position_too_low'} is a supertype of the success typing: 'Elixir.OMG.Utxo.Position':decode(number()) -> {'error','encoded_utxo_position_too_low'} | {'ok',{'utxo_position',non_neg_integer(),non_neg_integer(),char()}}\napps/omg/lib/omg/utxo/position.ex:76: Type specification 'Elixir.OMG.Utxo.Position':get_position(pos_integer()) -> {non_neg_integer(),non_neg_integer(),non_neg_integer()} is a supertype of the success typing: 'Elixir.OMG.Utxo.Position':get_position(pos_integer()) -> {non_neg_integer(),non_neg_integer(),char()}\napps/omg_watcher_rpc/lib/web/endpoint.ex:15: Expression produces a value of type 'error' | 'excluded' | 'ignored' | 'unsampled' | {'ok',binary() | pid() | #{'__struct__':='Elixir.Task', 'owner':='nil' | pid(), 'pid':='nil' | pid(), 'ref':='nil' | reference()}}, but this value is unmatched\nlib/phoenix/router.ex:316: The pattern 'error' can never match the type {#{'conn':='nil', 'log':='debug', 'path_params':=map(), 'pipe_through':=[any(),...], 'plug':='Elixir.OMG.ChildChainRPC.Web.Controller.Block' | 'Elixir.OMG.ChildChainRPC.Web.Controller.Fallback' | 'Elixir.OMG.ChildChainRPC.Web.Controller.Transaction', 'plug_opts':='Elixir.Route.NotFound' | 'get_block' | 'submit', 'route':=<<_:48,_:_*8>>},fun((_,map()) -> any()),fun((_) -> map()),{'Elixir.OMG.ChildChainRPC.Web.Controller.Block','get_block'} | {'Elixir.OMG.ChildChainRPC.Web.Controller.Fallback','Elixir.Route.NotFound'} | {'Elixir.OMG.ChildChainRPC.Web.Controller.Transaction','submit'}}\nlib/phoenix/router.ex:316: The pattern 'error' can never match the type {#{'conn':='nil', 'log':='debug', 'path_params':=map(), 'pipe_through':=[any(),...], 'plug':='Elixir.OMG.WatcherRPC.Web.Controller.Account' | 'Elixir.OMG.WatcherRPC.Web.Controller.Alarm' | 'Elixir.OMG.WatcherRPC.Web.Controller.Challenge' | 'Elixir.OMG.WatcherRPC.Web.Controller.Fallback' | 'Elixir.OMG.WatcherRPC.Web.Controller.InFlightExit' | 'Elixir.OMG.WatcherRPC.Web.Controller.Status' | 'Elixir.OMG.WatcherRPC.Web.Controller.Transaction' | 'Elixir.OMG.WatcherRPC.Web.Controller.Utxo', 'plug_opts':=atom(), 'route':=<<_:48,_:_*8>>},fun((_,map()) -> any()),fun((_) -> map()),{'Elixir.OMG.WatcherRPC.Web.Controller.Account' | 'Elixir.OMG.WatcherRPC.Web.Controller.Alarm' | 'Elixir.OMG.WatcherRPC.Web.Controller.Challenge' | 'Elixir.OMG.WatcherRPC.Web.Controller.Fallback' | 'Elixir.OMG.WatcherRPC.Web.Controller.InFlightExit' | 'Elixir.OMG.WatcherRPC.Web.Controller.Status' | 'Elixir.OMG.WatcherRPC.Web.Controller.Transaction' | 'Elixir.OMG.WatcherRPC.Web.Controller.Utxo',atom()}}\ntest/support/integration/test_server.ex:47: The pattern {'error', _} can never match the type {'ok','false' | 'nil' | 'true' | binary() | [any()] | number() | map()}\ntest/support/test_server.ex:84: The pattern {'error', _} can never match the type {'ok','false' | 'nil' | 'true' | binary() | [any()] | number() | map()}\n\n####\n#\n# Protocol-related problems, these ignores workaround the problem reported\n# here: https://github.com/elixir-lang/elixir/issues/7708 and here https://github.com/jeremyjh/dialyxir/issues/221\n# fixed in https://github.com/jeremyjh/dialyxir/commit/3d0a13f17a46649bca2413d57cef45bb278d1474, not yet released in `dialyxir`\n# undo once we use the `dialyxir` release that includes this (presumably >1.0.0-rc.6)\n:0: Unknown function 'Elixir.OMG.State.Transaction.Protocol.Atom':'__impl__'/1\n:0: Unknown function 'Elixir.OMG.State.Transaction.Protocol.BitString':'__impl__'/1\n:0: Unknown function 'Elixir.OMG.State.Transaction.Protocol.Float':'__impl__'/1\n:0: Unknown function 'Elixir.OMG.State.Transaction.Protocol.Function':'__impl__'/1\n:0: Unknown function 'Elixir.OMG.State.Transaction.Protocol.Integer':'__impl__'/1\n:0: Unknown function 'Elixir.OMG.State.Transaction.Protocol.List':'__impl__'/1\n:0: Unknown function 'Elixir.OMG.State.Transaction.Protocol.Map':'__impl__'/1\n:0: Unknown function 'Elixir.OMG.State.Transaction.Protocol.PID':'__impl__'/1\n:0: Unknown function 'Elixir.OMG.State.Transaction.Protocol.Port':'__impl__'/1\n:0: Unknown function 'Elixir.OMG.State.Transaction.Protocol.Reference':'__impl__'/1\n:0: Unknown function 'Elixir.OMG.State.Transaction.Protocol.Tuple':'__impl__'/1\n:0: Unknown function 'Elixir.OMG.Output.Atom':'__impl__'/1\n:0: Unknown function 'Elixir.OMG.Output.BitString':'__impl__'/1\n:0: Unknown function 'Elixir.OMG.Output.Float':'__impl__'/1\n:0: Unknown function 'Elixir.OMG.Output.Function':'__impl__'/1\n:0: Unknown function 'Elixir.OMG.Output.Integer':'__impl__'/1\n:0: Unknown function 'Elixir.OMG.Output.List':'__impl__'/1\n:0: Unknown function 'Elixir.OMG.Output.Map':'__impl__'/1\n:0: Unknown function 'Elixir.OMG.Output.PID':'__impl__'/1\n:0: Unknown function 'Elixir.OMG.Output.Port':'__impl__'/1\n:0: Unknown function 'Elixir.OMG.Output.Reference':'__impl__'/1\n:0: Unknown function 'Elixir.OMG.Output.Tuple':'__impl__'/1\n:0: Unknown function 'Elixir.OMG.Output.Protocol.Atom':'__impl__'/1\n:0: Unknown function 'Elixir.OMG.Output.Protocol.BitString':'__impl__'/1\n:0: Unknown function 'Elixir.OMG.Output.Protocol.Float':'__impl__'/1\n:0: Unknown function 'Elixir.OMG.Output.Protocol.Function':'__impl__'/1\n:0: Unknown function 'Elixir.OMG.Output.Protocol.Integer':'__impl__'/1\n:0: Unknown function 'Elixir.OMG.Output.Protocol.List':'__impl__'/1\n:0: Unknown function 'Elixir.OMG.Output.Protocol.Map':'__impl__'/1\n:0: Unknown function 'Elixir.OMG.Output.Protocol.PID':'__impl__'/1\n:0: Unknown function 'Elixir.OMG.Output.Protocol.Port':'__impl__'/1\n:0: Unknown function 'Elixir.OMG.Output.Protocol.Reference':'__impl__'/1\n:0: Unknown function 'Elixir.OMG.Output.Protocol.Tuple':'__impl__'/1\n#\n####\n"
  },
  {
    "path": "docker/create_databases.sql",
    "content": "CREATE USER feefeed;\nALTER USER feefeed CREATEDB;\nALTER USER feefeed WITH PASSWORD 'feefeed';\nCREATE DATABASE feefeed;\nGRANT ALL PRIVILEGES ON DATABASE feefeed TO feefeed;\n\nCREATE USER engine_repo;\nALTER USER engine_repo CREATEDB;\nALTER USER engine_repo WITH PASSWORD 'engine_repo';\nCREATE DATABASE engine_repo;\nGRANT ALL PRIVILEGES ON DATABASE engine_repo TO engine_repo;\n\nCREATE USER omisego_dev;\nALTER USER omisego_dev CREATEDB;\nALTER USER omisego_dev WITH PASSWORD 'omisego_dev';\nCREATE DATABASE omisego_dev;\nGRANT ALL PRIVILEGES ON DATABASE omisego_dev TO omisego_dev;\n\nCREATE DATABASE omisego_test;\nGRANT ALL PRIVILEGES ON DATABASE omisego_test TO omisego_dev;\n"
  },
  {
    "path": "docker/geth/command",
    "content": "# Configures geth with the deployer and authority accounts. This includes:\n#   1. Configuring the deployer's keystore\n#   2. Configuring the authority's keystore\n#   3. Configuring the keystores' password\n#   4. Unlocking the accounts by their indexes\n# CAREFUL with --allow-insecure-unlock!\n# Starts geth\n# Websocket is not used by the applications but enabled for debugging/testing convenience\ngeth \\\n--verbosity 0 \\\n--miner.gastarget 7500000 \\\n--nousb \\\n--miner.gasprice \"10\" \\\n--nodiscover \\\n--maxpeers 0 \\\n--datadir data/ \\\n--syncmode 'fast' \\\n--networkid 1337 \\\n--gasprice '1' \\\n--keystore ./data/geth/keystore/ \\\n--password /data/geth-blank-password \\\n--unlock \"0,1\" \\\n--rpc \\\n--rpcapi personal,web3,eth,net  \\\n--rpcaddr 0.0.0.0  \\\n--rpcvhosts=* \\\n--rpcport=${RPC_PORT} \\\n--ws  \\\n--wsaddr 0.0.0.0  \\\n--wsorigins='*' \\\n--mine \\\n--allow-insecure-unlock\n"
  },
  {
    "path": "docker/geth/geth-blank-password",
    "content": ""
  },
  {
    "path": "docker/nginx/geth_nginx.conf",
    "content": "server {\n    listen 80;\n    access_log  off;\n    location / {\n        proxy_pass http://172.27.0.101:8545;\n    }\n}\n\nserver {\n    listen 81;\n    access_log  off;\n    location / {\n        proxy_pass http://172.27.0.101:8546;\n        proxy_http_version 1.1;\n        proxy_set_header Upgrade $http_upgrade;\n        proxy_set_header Connection \"upgrade\";\n        proxy_connect_timeout 7d;\n        proxy_send_timeout 7d;\n        proxy_read_timeout 7d;\n    }\n}"
  },
  {
    "path": "docker/nginx/nginx.conf",
    "content": "user  nginx;\n\nevents {\n    worker_connections   1000;\n}\n\nhttp {\n        upstream childchain {\n            server 172.27.0.103:9656;\n        }\n\n        server {\n              listen 9656;\n              access_log  off;\n              location / {\n                proxy_pass http://childchain;\n\n                proxy_next_upstream non_idempotent invalid_header error timeout http_500 http_502 http_504 http_403 http_404;\n                fastcgi_read_timeout 10;\n                proxy_read_timeout 10;\n\n                error_page 504 502 =503 @empty;\n              }\n\n              location @empty {\n                internal;\n                return 200 \"\";\n              }\n        }\n\n        include \"/etc/nginx/server_config/*.conf\";\n}\n\ninclude \"/etc/nginx/main_config/*.conf\";"
  },
  {
    "path": "docker/nginx/nginx.reorg.conf",
    "content": "events {}\nhttp {\n    upstream geth {\n        server 172.27.0.201:8545;\n        server 172.27.0.202:8545;\n    }\n\n    upstream websocket {\n        server 172.27.0.201:8546;\n        server 172.27.0.202:8546;\n    }\n\n    server {\n        listen 80;\n\n        location / {\n            proxy_pass http://geth;\n            proxy_next_upstream non_idempotent invalid_header error timeout http_500 http_502 http_504 http_403 http_404;\n            proxy_next_upstream_tries 4;\n            fastcgi_read_timeout 10;\n            proxy_read_timeout 10;\n        }\n    }\n\n    server {\n        listen 81;\n\n        location / {\n            proxy_pass http://websocket;\n            proxy_http_version 1.1;\n            proxy_set_header Upgrade $http_upgrade;\n            proxy_set_header Connection \"upgrade\";\n            proxy_next_upstream non_idempotent invalid_header error timeout http_500 http_502 http_504 http_403 http_404;\n            proxy_connect_timeout 7d;\n            proxy_send_timeout 7d;\n            proxy_read_timeout 7d;\n        }\n    }\n}"
  },
  {
    "path": "docker/static_feefeed/file.json",
    "content": "{\n    \"data\":{\n       \"1\":{\n          \"0x0000000000000000000000000000000000000000\":{\n             \"amount\":1,\n             \"contract_address\":\"0x0000000000000000000000000000000000000000\",\n             \"pair\":\"eth_eth\",\n             \"pair_reversed\":false,\n             \"pegged_amount\":null,\n             \"pegged_currency\":null,\n             \"pegged_subunit_to_unit\":null,\n             \"subunit_to_unit\":1000000000000000000,\n             \"symbol\":\"ETH\",\n             \"type\":\"fixed\",\n             \"updated_at\":\"2021-01-18T18:56:59.939198Z\"\n          },\n          \"0xb1952c4e36d153a49eccc01a4d93b9ecbabb7208\":{\n             \"amount\":414023614895550,\n             \"contract_address\":\"0xd26114cd6EE289AccF82350c8d8487fedB8A0C07\",\n             \"pair\":\"omg_eth\",\n             \"pair_reversed\":false,\n             \"pegged_amount\":0.33,\n             \"pegged_currency\":\"eth_gas\",\n             \"pegged_subunit_to_unit\":1000000000000000000,\n             \"subunit_to_unit\":1000000000000000000,\n             \"symbol\":\"OMG\",\n             \"type\":\"pegged\",\n             \"updated_at\":\"2021-01-18T18:56:59.939198Z\"\n          }\n       }\n    },\n    \"success\":true,\n    \"version\":\"1\"\n }"
  },
  {
    "path": "docker-compose-infura.yml",
    "content": "version: \"2.3\"\nservices:\n  plasma-contracts:\n    environment:\n      # The private keys are likely different from the main docker-compose.yml:\n      #   1. DEPLOYER_PRIVATEKEY needs to have enough ETH on the REMOTE_URL network\n      #   2. AUTHORITY_PRIVATEKEY needs to have nonce=0 on the REMOTE_URL network\n      - REMOTE_URL=https://rinkeby.infura.io/v3/${INFURA_API_KEY}\n      - DEPLOYER_PRIVATEKEY=${DEPLOYER_PRIVATEKEY}\n      - MAINTAINER_PRIVATEKEY=${MAINTAINER_PRIVATEKEY}\n      - AUTHORITY_PRIVATEKEY=${AUTHORITY_PRIVATEKEY}\n\n  childchain:\n    environment:\n      - ETHEREUM_RPC_URL=https://rinkeby.infura.io/v3/${INFURA_API_KEY}\n      - PRIVATE_KEY=${AUTHORITY_PRIVATEKEY}\n\n  watcher:\n    environment:\n      - ETHEREUM_RPC_URL=https://rinkeby.infura.io/v3/${INFURA_API_KEY}\n\n  geth:\n    # We don't need geth but docker-compose doesn't support overrides to remove or disable a service\n    # So here we set `--dev.period 0` to minimize resource utilization.\n    entrypoint: /bin/sh -c \"apk add curl && geth --dev --dev.period 0 --rpc --rpcaddr 0.0.0.0 --rpcvhosts=* --rpcport=8545\"\n"
  },
  {
    "path": "docker-compose-watcher.yml",
    "content": "version: \"2.3\"\nservices:\n  postgres:\n    image: postgres:9.6.13-alpine\n    restart: always\n    ports:\n      - \"5432:5432\"\n    environment:\n      POSTGRES_USER: omisego_dev\n      POSTGRES_PASSWORD: omisego_dev\n      POSTGRES_DB: omisego_dev\n    healthcheck:\n      test: pg_isready -U omisego_dev\n      interval: 5s\n      timeout: 3s\n      retries: 5\n\n  watcher:\n    #last stable integration watcher\n    image: omisego/watcher:1.0.1\n    command: \"full_local\"\n    environment:\n      - ETHEREUM_RPC_URL=https://ropsten.infura.io/v3/${INFURA_API_KEY}\n      - CHILD_CHAIN_URL=https://childchain.ropsten.v1.omg.network\n      - ETHEREUM_NETWORK=ROPSTEN\n      - AUTHORITY_ADDRESS=0x3272b97b7f1b74b338cb0fdda167cf76bc4da3b6\n      - TXHASH_CONTRACT=0x25e445594f425a7a94141a20b8831580953b92ddd0d12e9c775c571e4f3da08c\n      - CONTRACT_ADDRESS_PLASMA_FRAMEWORK=0xa72c9dceeef26c9d103d55c53d411c36f5cdf7ec\n      - CONTRACT_ADDRESS_ETH_VAULT=0x2c7533f76567241341d1c27f0f239a20b6115714\n      - CONTRACT_ADDRESS_ERC20_VAULT=0x2bed2ff4ee93a208edbf4185c7813103d8c4ab7f\n      - CONTRACT_ADDRESS_PAYMENT_EXIT_GAME=0x960ca6b9faa85118ba6badbe0097b1afd8827fac\n      - DATABASE_URL=postgres://omisego_dev:omisego_dev@postgres:5432/omisego_dev\n      - PORT=7434\n      - DD_DISABLED=true\n      - DB_PATH=/app/.omg/data\n      - ETHEREUM_EVENTS_CHECK_INTERVAL_MS=8000\n      - ETHEREUM_STALLED_SYNC_THRESHOLD_MS=300000\n      - ETHEREUM_BLOCK_TIME_SECONDS=15\n      - EXIT_PROCESSOR_SLA_MARGIN=5520\n      - EXIT_PROCESSOR_SLA_MARGIN_FORCED=TRUE\n      - LOGGER_BACKEND=console\n      - DD_HOSTNAME=datadog\n      - APP_ENV=local-development\n    ports:\n      - \"7434:7434\"\n    healthcheck:\n      test: curl watcher:7434\n      interval: 5s\n      timeout: 3s\n      retries: 5\n\n  watcher_info:\n    image: omisego/watcher_info:1.0.1\n    command: \"full_local\"\n    environment:\n      - ETHEREUM_RPC_URL=https://ropsten.infura.io/v3/${INFURA_API_KEY}\n      - CHILD_CHAIN_URL=https://childchain.ropsten.v1.omg.network\n      - ETHEREUM_NETWORK=ROPSTEN\n      - AUTHORITY_ADDRESS=0x3272b97b7f1b74b338cb0fdda167cf76bc4da3b6\n      - TXHASH_CONTRACT=0x25e445594f425a7a94141a20b8831580953b92ddd0d12e9c775c571e4f3da08c\n      - CONTRACT_ADDRESS_PLASMA_FRAMEWORK=0xa72c9dceeef26c9d103d55c53d411c36f5cdf7ec\n      - CONTRACT_ADDRESS_ETH_VAULT=0x2c7533f76567241341d1c27f0f239a20b6115714\n      - CONTRACT_ADDRESS_ERC20_VAULT=0x2bed2ff4ee93a208edbf4185c7813103d8c4ab7f\n      - CONTRACT_ADDRESS_PAYMENT_EXIT_GAME=0x960ca6b9faa85118ba6badbe0097b1afd8827fac\n      - DATABASE_URL=postgres://omisego_dev:omisego_dev@postgres:5432/omisego_dev\n      - PORT=7534\n      - DD_DISABLED=true\n      - DB_PATH=/app/.omg/data\n      - ETHEREUM_EVENTS_CHECK_INTERVAL_MS=8000\n      - ETHEREUM_STALLED_SYNC_THRESHOLD_MS=300000\n      - ETHEREUM_BLOCK_TIME_SECONDS=15\n      - EXIT_PROCESSOR_SLA_MARGIN=5520\n      - EXIT_PROCESSOR_SLA_MARGIN_FORCED=TRUE\n      - LOGGER_BACKEND=console\n      - DD_HOSTNAME=datadog\n      - APP_ENV=local-development\n    restart: always\n    ports:\n      - \"7534:7534\"\n    healthcheck:\n      test: curl watcher_info:7534\n      interval: 5s\n      timeout: 3s\n      retries: 5\n    depends_on:\n      postgres:\n        condition: service_healthy\n\n"
  },
  {
    "path": "docker-compose.datadog.yml",
    "content": "version: \"2.3\"\nservices:\n  datadog:\n    image: datadog/agent:latest\n    restart: always\n    environment:\n      - DD_API_KEY=${DD_API_KEY}\n      - DD_DOGSTATSD_NON_LOCAL_TRAFFIC=true\n      - DD_LOG_LEVEL=debug\n      - DOCKER_CONTENT_TRUST=1\n      - DD_APM_ENABLED=true\n    volumes:\n      - /var/run/docker.sock:/var/run/docker.sock\n      - /proc/:/host/proc/:ro\n      - /sys/fs/cgroup:/host/sys/fs/cgroup:ro\n    ports:\n      - \"2003-2004:2003-2004\"\n      - \"2023-2024:2023-2024\"\n      - \"8125:8125/udp\"\n      - \"8126:8126/tcp\"\n"
  },
  {
    "path": "docker-compose.dev.yml",
    "content": "version: \"2.3\"\nservices:\n  elixir-omg:\n    image: omisegoimages/elixir-omg-builder:stable-20201207\n    environment:\n      DATABASE_URL: postgres://omisegodev:omisegodev@postgres:5432/omisego_dev\n      TEST_DATABASE_URL: postgres://omisegodev:omisegodev@postgres:5432/omisego_test\n      SHELL: /bin/bash\n    volumes:\n      - .:/app:rw\n    depends_on:\n      postgres:\n        condition: service_healthy\n    networks:\n      chain_net:\n        ipv4_address: 172.27.0.119\n  watcher:\n    environment:\n      - DD_DISABLED=false\n    depends_on:\n      datadog:\n        condition: service_healthy\n  watcher_info:\n    environment:\n      - DD_DISABLED=false\n    depends_on:\n      datadog:\n        condition: service_healthy\n  childchain:\n    environment:\n      - DD_DISABLED=false\n    depends_on:\n      datadog:\n        condition: service_healthy\n"
  },
  {
    "path": "docker-compose.feefeed.yml",
    "content": "version: \"2.3\"\nservices:\n  childchain:\n    environment:\n      - FEE_ADAPTER=feed\n      #- FEE_FEED_URL=http://172.27.0.110:4000/api/v1\n      - FEE_FEED_URL=http://172.27.0.110/file.json\n    depends_on:\n      feefeed:\n        condition: service_healthy\n\n  feefeed:\n    image: omisego/feefeed_mock:latest\n    volumes:\n      - ./docker/static_feefeed/:/www-data/\n    ports:\n      - \"4000:80\"\n    expose:\n      - \"4000\"\n    networks:\n      chain_net:\n        ipv4_address: 172.27.0.110\n  # feefeed:\n  #   image: \"gcr.io/omisego-development/feefeed:latest\"\n  #   command: \"start\"\n  #   container_name: feefeed\n  #   environment:\n  #     - GITHUB_TOKEN=\"\"\n  #     - GITHUB_ORGANISATION=omgnetwork\n  #     - GITHUB_REPO=fee-rules-public\n  #     - SENTRY_DSN=\"\"\n  #     - GITHUB_BRANCH=master\n  #     - RULES_FETCH_INTERVAL=20\n  #     - RATES_FETCH_INTERVAL=20\n  #     - GITHUB_FILENAME=fee_rules\n  #     - DATABASE_URL=postgresql://feefeed:feefeed@172.27.0.107:5432/feefeed\n  #     - SECRET_KEY_BASE=\"Y8naENMR8b+vbPHILjwNtEfWFrnbGi2k+UYWm75VnKHfsavmyGLtTmmeJxAGK+zJ\"\n  #     - DATADOG_DISABLED=true\n  #     - DATADOG_HOST=\"localhost\"\n  #     - ETHEREUM_NODE_URL=http://172.27.0.102:80\n  #   ports:\n  #     - \"4000:4000\"\n  #   expose:\n  #     - \"4000\"\n  #   depends_on:\n  #     postgres:\n  #       condition: service_healthy\n  #     nginx:\n  #       condition: service_healthy\n  #   restart: always\n  #   healthcheck:\n  #     test: curl -v --silent http://localhost:4000/api/v1/fees 2>&1 | grep contract_address\n  #     interval: 4s\n  #     timeout: 2s\n  #     retries: 30\n  #     start_period: 60s\n  #   networks:\n  #     chain_net:\n  #       ipv4_address: 172.27.0.110\n"
  },
  {
    "path": "docker-compose.reorg.yml",
    "content": "version: \"2.3\"\nservices:\n  geth:\n    entrypoint: [\"echo\", \"clique geth is disabled for reorgs\"]\n\n  geth-1:\n    image: ethereum/client-go:v1.9.15\n    container_name: geth-1\n    environment:\n      - ACCOUNT=0x6de4b3b9c28e9c3e84c2b2d3a875c947a84de68d\n      - BOOTNODES=enode://b655cc3e5b72ab9beb8a8536a3c3ae92fbeb79feb1ebd7f95d72be72554ca586428bd48a54eb9c2bcaae455cc674299b6dd3df3c6556a493dfd50070f1a448aa@172.27.0.202:30303\n      - INIT=false\n    entrypoint: /bin/sh -c \". data/geth/command\"\n    expose:\n      - 8545\n      - 8546\n      - 30303\n    ports:\n      - 9000:8545\n    volumes:\n      - ./data1:/data\n      - ./data/ethash:/root/.ethash\n    healthcheck:\n      test: curl geth-1:8545\n      interval: 5s\n      timeout: 3s\n      retries: 5\n    networks:\n      chain_net:\n        ipv4_address: 172.27.0.201\n\n  geth-2:\n    image: ethereum/client-go:v1.9.15\n    container_name: geth-2\n    depends_on:\n      - geth-1\n    environment:\n      - ACCOUNT=0xc0f780dfc35075979b0def588d999225b7ecc56f\n      - BOOTNODES=enode://4574f825d67bf570b9216e704a5b761d05d5015c458e2c9dd4b30abb2fe8c881400c2074a126df94690c4c9fb72ee046e6e3ac2bb73dede42fce66cb0a963b36@172.27.0.201:30303\n      - INIT=false\n    entrypoint: /bin/sh -c \". data/geth/command\"\n    expose:\n      - 8546\n      - 8545\n      - 30303\n    ports:\n      - 9001:8545\n    volumes:\n      - ./data2:/data\n      - ./data/ethash:/root/.ethash\n    healthcheck:\n      test: curl geth-2:8545\n      interval: 5s\n      timeout: 3s\n      retries: 5\n    networks:\n      chain_net:\n        ipv4_address: 172.27.0.202\n\n  nginx:\n    depends_on:\n      geth-1:\n        condition: service_healthy\n      geth-2:\n        condition: service_healthy\n    volumes:\n      - ./docker/nginx/nginx.reorg.conf:/etc/nginx/nginx.conf\n"
  },
  {
    "path": "docker-compose.specs.yml",
    "content": "# this is an override to our usual docker-compose.yml which enables cabbage integration tests to run against a\n# test-friendly setup of our services\nversion: \"2.3\"\nservices:\n  watcher:\n    environment:\n      - EXIT_PROCESSOR_SLA_MARGIN=30\n  watcher_info:\n    environment:\n      - EXIT_PROCESSOR_SLA_MARGIN=30\n"
  },
  {
    "path": "docker-compose.yml",
    "content": "version: \"2.3\"\nservices:\n  nginx:\n    image: nginx:latest\n    container_name: nginx\n    volumes:\n      - ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf:ro\n      - ./docker/nginx/geth_nginx.conf:/etc/nginx/server_config/geth.conf:ro\n    ports:\n      - 9656:9656\n      - 8545:80\n      - 8546:81\n      - 443:443\n    healthcheck:\n      test: curl geth:80\n      interval: 5s\n      timeout: 3s\n      retries: 5\n    networks:\n      chain_net:\n        ipv4_address: 172.27.0.108\n\n  postgres:\n    image: postgres:9.6.13-alpine\n    restart: always\n    ports:\n      - \"5432:5432\"\n    environment:\n      POSTGRES_USER: omisegodev\n      POSTGRES_PASSWORD: omisegodev\n    volumes:\n      - ./docker/create_databases.sql:/docker-entrypoint-initdb.d/create_databases.sql\n    healthcheck:\n      test: pg_isready -U omisego_dev\n      interval: 5s\n      timeout: 3s\n      retries: 5\n    networks:\n      chain_net:\n        ipv4_address: 172.27.0.107\n\n  feefeed:\n    image: omisego/feefeed_mock:latest\n    volumes:\n      - ./docker/static_feefeed/:/www-data/\n    ports:\n      - \"4000:80\"\n    expose:\n      - \"4000\"\n    networks:\n      chain_net:\n        ipv4_address: 172.27.0.110\n  # feefeed:\n  #   image: gcr.io/omisego-development/feefeed:latest\n  #   command: \"start\"\n  #   container_name: feefeed\n  #   environment:\n  #     - GITHUB_TOKEN=\"\"\n  #     - GITHUB_ORGANISATION=omgnetwork\n  #     - GITHUB_REPO=fee-rules-public\n  #     - SENTRY_DSN=\"\"\n  #     - GITHUB_BRANCH=master\n  #     - RULES_FETCH_INTERVAL=200\n  #     - RATES_FETCH_INTERVAL=200\n  #     - GITHUB_FILENAME=fee_rules\n  #     - DATABASE_URL=postgresql://feefeed:feefeed@172.27.0.107:5432/feefeed\n  #     - SECRET_KEY_BASE=\"Y8naENMR8b+vbPHILjwNtEfWFrnbGi2k+UYWm75VnKHfsavmyGLtTmmeJxAGK+zJ\"\n  #     - DATADOG_DISABLED=true\n  #     - DATADOG_HOST=\"localhost\"\n  #     - ETHEREUM_NODE_URL=http://172.27.0.108:80\n  #   ports:\n  #     - \"4000:4000\"\n  #   expose:\n  #     - \"4000\"\n  #   depends_on:\n  #     - postgres\n  #   restart: always\n  #   networks:\n  #     chain_net:\n  #       ipv4_address: 172.27.0.110\n\n  geth:\n    image: ethereum/client-go:v1.9.15\n    entrypoint: /bin/sh -c \". data/command\"\n    environment:\n      RPC_PORT: 8545\n    ports:\n      - \"8555:8545\"\n      - \"8556:8546\"\n    expose:\n      - \"8546\"\n      - \"8545\"\n    volumes:\n      - ./data:/data\n      - ./docker/geth/command:/data/command\n      - ./docker/geth/geth-blank-password:/data/geth-blank-password\n    healthcheck:\n      test: curl localhost:8545\n      interval: 5s\n      timeout: 3s\n      retries: 5\n    networks:\n      chain_net:\n        ipv4_address: 172.27.0.101\n\n  childchain:\n    image: omisego/child_chain:latest\n    command: \"full_local\"\n    container_name: childchain\n    env_file:\n      - ./localchain_contract_addresses.env\n      - ./fees_setup.env\n    environment:\n      - ETHEREUM_NETWORK=LOCALCHAIN\n      - ETHEREUM_RPC_URL=http://172.27.0.108:80\n      - APP_ENV=local_docker_development\n      - DD_HOSTNAME=datadog\n      - DD_DISABLED=true\n      - DB_PATH=/data\n      - ETHEREUM_EVENTS_CHECK_INTERVAL_MS=800\n      - ETHEREUM_STALLED_SYNC_THRESHOLD_MS=20000\n      - LOGGER_BACKEND=console\n      - RELEASE_COOKIE=development\n      - NODE_HOST=127.0.0.1\n      - PULSE_API_KEY=${PULSE_API_KEY}\n      - FEE_CLAIMER_ADDRESS=0x3b9f4c1dd26e0be593373b1d36cee2008cbeb837\n    restart: always\n    volumes:\n      - ./data:/data\n      - ./priv/dev-artifacts:/dev-artifacts\n    healthcheck:\n      test: curl localhost:9656\n      interval: 30s\n      timeout: 10s\n      retries: 5\n      start_period: 60s\n    depends_on:\n      - nginx\n      - geth\n    networks:\n      chain_net:\n        ipv4_address: 172.27.0.103\n\n  watcher:\n    image: omisego/watcher:latest\n    command: \"full_local\"\n    container_name: watcher\n    env_file:\n      - ./localchain_contract_addresses.env\n    environment:\n      - ETHEREUM_NETWORK=LOCALCHAIN\n      - ETHEREUM_RPC_URL=http://172.27.0.108:80\n      - CHILD_CHAIN_URL=http://172.27.0.108:9656\n      - PORT=7434\n      - APP_ENV=local_docker_development\n      - DD_HOSTNAME=datadog\n      - DD_DISABLED=true\n      - DB_PATH=/data\n      - ETHEREUM_EVENTS_CHECK_INTERVAL_MS=800\n      - ETHEREUM_STALLED_SYNC_THRESHOLD_MS=20000\n      - ETHEREUM_BLOCK_TIME_SECONDS=1\n      - EXIT_PROCESSOR_SLA_MARGIN=5520\n      - EXIT_PROCESSOR_SLA_MARGIN_FORCED=TRUE\n      - LOGGER_BACKEND=console\n      - RELEASE_COOKIE=development\n      - NODE_HOST=127.0.0.1\n    restart: always\n    ports:\n      - \"7434:7434\"\n    expose:\n      - \"7434\"\n    volumes:\n      - ./data:/data\n    healthcheck:\n      test: curl localhost:7434\n      interval: 30s\n      timeout: 1s\n      retries: 5\n      start_period: 30s\n    depends_on:\n      childchain:\n        condition: service_healthy\n    networks:\n      chain_net:\n        ipv4_address: 172.27.0.104\n\n  watcher_info:\n    image: omisego/watcher_info:latest\n    command: \"full_local\"\n    container_name: watcher_info\n    env_file:\n      - ./localchain_contract_addresses.env\n    environment:\n      - ETHEREUM_NETWORK=LOCALCHAIN\n      - ETHEREUM_RPC_URL=http://172.27.0.108:80\n      - CHILD_CHAIN_URL=http://172.27.0.108:9656\n      - DATABASE_URL=postgresql://omisego_dev:omisego_dev@172.27.0.107:5432/omisego_dev\n      - PORT=7534\n      - APP_ENV=local_docker_development\n      - DD_HOSTNAME=datadog\n      - DD_DISABLED=true\n      - DB_PATH=/data\n      - ETHEREUM_EVENTS_CHECK_INTERVAL_MS=800\n      - ETHEREUM_BLOCK_TIME_SECONDS=1\n      - EXIT_PROCESSOR_SLA_MARGIN=5520\n      - EXIT_PROCESSOR_SLA_MARGIN_FORCED=TRUE\n      - LOGGER_BACKEND=console\n      - RELEASE_COOKIE=development\n      - NODE_HOST=127.0.0.1\n    restart: always\n    ports:\n      - \"7534:7534\"\n    expose:\n      - \"7534\"\n    volumes:\n      - ./data:/data\n    healthcheck:\n      test: curl localhost:7534\n      interval: 30s\n      timeout: 1s\n      retries: 5\n      start_period: 30s\n    depends_on:\n      childchain:\n        condition: service_healthy\n      postgres:\n        condition: service_healthy\n    networks:\n      chain_net:\n        ipv4_address: 172.27.0.105\n\nnetworks:\n  chain_net:\n    name: chain_net\n    driver: bridge\n    ipam:\n      config:\n      - subnet: 172.27.0.0/24\n"
  },
  {
    "path": "docs/api_specs/errors.md",
    "content": "# Errors\n\nNote that HTTP calls will almost always return `200`, even if the result is an error. One exception to this is if an internal server error occurs - in this case it will return `500`\n\nWhen an error occurs, `success` will be set to `false` and `data` will contain more information about the error\n\n```json\n{\n  \"version\": \"1\",\n  \"success\": false,\n  \"data\": {\n    \"code\": \"account:not_found\",\n    \"description\": \"Account not found\"\n  }\n}\n```\n\n# Error codes description\n\nCode | Description\n---- | -----------  \nserver:internal_server_error | Something went wrong on the server. Try again soon.\noperation:bad_request | Parameters required by this operation are missing or incorrect. More information about error in response object `data/messages` property.\noperation:not_found | Operation cannot be found. Check request URL.\nchallenge:exit_not_found | The challenge of particular exit is impossible because exit is inactive or missing\nchallenge:utxo_not_spent | The challenge of particular exit is impossible because provided utxo is not spent\nexit:invalid | Utxo was spent or does not exist.\nget_status:econnrefused | Cannot connect to the Ethereum node.\nin_flight_exit:tx_for_input_not_found | No transaction that created input.\ntransaction:not_found | Transaction doesn't exist for provided search criteria\ntransaction.create:insufficient_funds | Account balance is too low to satisfy the payment.\ntransaction.create:too_many_outputs | Total number of payments + change + fees exceed maximum allowed outputs in transaction. We need to reserve one output per payment and one output per change for each currency used in the transaction.\ntransaction.create:empty_transaction | Requested payment resulted in empty transaction that transfers no funds.\nsubmit_typed:missing_signature | Signatures should correspond to inputs owner. When all non-empty inputs has the same owner, signatures should be duplicated.\nsubmit_typed:superfluous_signature | Number of non-empty inputs should match signatures count. Remove redundant signatures.\n\nRefer to `...web/controllers/fallback.ex` family of files for a comprehensive list of error codes and descriptions.\n"
  },
  {
    "path": "docs/api_specs/index.html.md",
    "content": "---\ntitle: OMG Network APIs Reference\n\nlanguage_tabs: # must be one of https://git.io/vQNgJ\n  - shell\n  - elixir\n  - javascript\n\ntoc_footers:\n  - <a href='https://github.com/lord/slate'>Documentation Powered by Slate</a>\n\nincludes:\n  - operator_api_specs\n  - watcher_api_specs\n  - info_api_specs\n  - errors\n\nsearch: true\n---\n\n# Introduction\n\nThis is the HTTP-RPC API for the Child Chain Server and Watcher.\n\nAll calls use HTTP POST and pass options in the request body in JSON format.\nErrors will usually return with HTTP response code 200, and the details of the error in the response body.\nSee [Errors](#errors).\n"
  },
  {
    "path": "docs/api_specs/status_events_specs.md",
    "content": "### Byzantine events\nAll of the following events indicate byzantine behaviour and that the user should either exit or challenge.\n\n#### `invalid_exit`\n> An invalid_exit event\n\n```json\n{\n    \"event\": \"invalid_exit\",\n    \"details\": {\n        \"eth_height\"  : 3521678,\n        \"utxo_pos\"  : 10000000010000000,\n        \"owner\"  : \"0xb3256026863eb6ae5b06fa396ab09069784ea8ea\",\n        \"currency\"  : \"0x0000000000000000000000000000000000000000\",\n        \"amount\" : 100\n    }\n}\n```\n\nIndicates that an invalid exit is occurring. It should be challenged.\n\n\n#### `unchallenged_exit`\n> An unchallenged_exit event\n\n```json\n{\n    \"event\": \"unchallenged_exit\",\n    \"details\": {\n        \"eth_height\"  : 3521678,\n        \"utxo_pos\"  : 10000000010000000,\n        \"owner\"  : \"0xb3256026863eb6ae5b06fa396ab09069784ea8ea\",\n        \"currency\"  : \"0x0000000000000000000000000000000000000000\",\n        \"amount\" : 100\n    }\n}\n```\n\nIndicates that an invalid exit is dangerously close to finalization and hasn't been challenged. User should exit.\nSee docs on [`unchallenged_exit` condition](../exit_validation.md#unchallenged-exit-condition) for more details.\n\n\n#### `invalid_block`\n> An invalid_block event\n\n```json\n{\n    \"event\": \"invalid_block\",\n    \"details\": {\n        \"blockhash\"  : \"0x0017372421f9a92bedb7163310918e623557ab5310befc14e67212b660c33bec\",\n        \"blknum\"  : 10000,\n        \"error_type\": \"tx_execution\"\n    }\n}\n```\n\nAn invalid block has been added to the chain. User should exit.\n\n\n#### `block_withholding`\n> A block_withholding event\n\n```json\n{\n    \"event\": \"block_withholding\",\n    \"details\": {\n        \"hash\"  : \"0x0017372421f9a92bedb7163310918e623557ab5310befc14e67212b660c33bec\",\n        \"blknum\"  : 10000\n    }\n}\n```\n\nThe ChildChain is withholding a block whose hash has been published on the root chain. User should exit.\n\n#### `non_canonical_ife`\n> A noncanonical_ife event\n\n```json\n{\n    \"event\": \"non_canonical_ife\",\n    \"details\": {\n        \"txbytes\": \"0xf3170101c0940000...\"\n    }\n}\n```\n\nAn in-flight exit of a non-canonical transaction has been started. It should be challenged.\n\nEvent details:\n\nAttribute | Type | Description\n--------- | ------- | -----------\ntxbytes | Hex encoded string | The in-flight transaction that the event relates to\n\n#### `unchallenged_non_canonical_ife`\n\n```json\n{\n    \"event\": \"unchallenged_non_canonical_ife\",\n    \"details\": {\n        \"txbytes\": \"0xf3170101c0940000...\"\n    }\n}\n```\n\nIndicates that there is unchallenged non canonical in-flight exit that is dangerously close to finalization and hasn't been challenged. User should exit.\nSee docs on [`unchallenged_exit` condition](../exit_validation.md#unchallenged-exit-condition) for more details.\n\nEvent details:\n\nAttribute | Type | Description\n--------- | ------- | -----------\ntxbytes | Hex encoded string | The in-flight transaction that the event relates to\n\n#### `invalid_ife_challenge`\n> A invalid_ife_challenge event\n\n```json\n{\n    \"event\": \"invalid_ife_challenge\",\n    \"details\": {\n        \"txbytes\": \"0xf3170101c0940000...\"\n    }\n}\n```\n\nA canonical in-flight exit has been challenged. The challenge should be responded to.\n\nEvent details:\n\nAttribute | Type | Description\n--------- | ------- | -----------\ntxbytes | Hex encoded string | The in-flight transaction that the event relates to\n\n#### `piggyback_available`\n> A piggyback_available event\n\n```json\n{\n    \"event\": \"piggyback_available\",\n    \"details\": {\n        \"txbytes\": \"0xf3170101c0940000...\",\n        \"available_outputs\" : [\n            {\"index\": 0, \"address\": \"0xb3256026863eb6ae5b06fa396ab09069784ea8ea\"},\n            {\"index\": 1, \"address\": \"0x488f85743ef16cfb1f8d4dd1dfc74c51dc496434\"},\n        ],\n        \"available_inputs\" : [\n            {\"index\": 0, \"address\": \"0xb3256026863eb6ae5b06fa396ab09069784ea8ea\"}\n        ],\n    }\n}\n```\n\nAn in-flight exit has been started and can be piggybacked. If all inputs are owned by the same address, then `available_inputs` will not be present.\nThis event is reported only for in-flight exits from transactions that have not been included in a block.\nIf input or output of exiting transaction is piggybacked it does not show up as available for piggybacking.\nWhen in-flight exit is finalized, transaction's inputs and outputs are not available for piggybacking.\n\nEvent details:\n\nAttribute | Type | Description\n--------- | ------- | -----------\ntxbytes | Hex encoded string | The in-flight transaction that the event relates to\navailable_outputs | Object array | The outputs (index and address) available to be piggybacked\navailable_inputs | Object array | The inputs (index and address) available to be piggybacked\n\n#### `invalid_piggyback`\n> A invalid_piggyback event\n\n```json\n{\n    \"event\": \"invalid_piggyback\",\n    \"details\": {\n        \"txbytes\": \"0xf3170101c0940000...\",\n        \"inputs\": [1],\n        \"outputs\": [0]\n    }\n}\n```\n\nAn invalid piggyback is in process. Should be challenged.\n\nEvent details:\n\nAttribute | Type | Description\n--------- | ------- | -----------\ntxbytes | Hex encoded string | The in-flight transaction that the event relates to\ninputs | Integer array | A list of invalid piggybacked inputs\noutputs | Integer array | A list of invalid piggybacked outputs\n\n#### `unchallenged_piggyback`\n> A invalid_piggyback event\n\n```json\n{\n    \"event\": \"unchallenged_piggyback\",\n    \"details\": {\n        \"txbytes\": \"0xf3170101c0940000...\",\n        \"inputs\": [1],\n        \"outputs\": [0]\n    }\n}\n```\n\nAn invalid piggyback is dangerously close to finalization and hasn't been challenged. User should exit.\n\nEvent details:\n\nAttribute | Type | Description\n--------- | ------- | -----------\ntxbytes | Hex encoded string | The in-flight transaction that the event relates to\ninputs | Integer array | A list of invalid piggybacked inputs\noutputs | Integer array | A list of invalid piggybacked outputs\n"
  },
  {
    "path": "docs/architecture.md",
    "content": "# Architecture\n\nThis is a high-level rundown of the architecture of the `elixir-omg` apps.\n\nThe below diagram demonstrates the various pieces and where this umbrella app fits in.\n![high level architecture overview diagram](assets/architecture_overview.jpg)\n**NOTE** only use the high-level diagram to get a vague idea, meaning of boxes/arrows may be imprecise.\n\n## Interactions\n\n**[Diagram](https://docs.google.com/drawings/d/11ugr_VQzqh0afU6NPpHW893jww182POaGE3sYhgm9Gw/edit?usp=sharing)** illustrates the interactions described below.\n\nThis lists only interactions between the different processes that build up both the Child Chain Server and Watcher.\nFor responsibilities of the processes/modules look into respective docs in `.ex` files.\n\n**NOTE** The hexagonal shape hints towards component being a wrapper (port/adapter) to something external, versus rectangular shape being an internal component.\n\n### `OMG.State`\n\n- writes blocks and UTXO set to `OMG.DB`\n\n### `OMG.ChildChain`\n\n- accepts child chain transactions, decodes, stateless-validates and executes on `OMG.State`\n\n### `OMG.Watcher.RootChainCoordinator`\n\n- reads Ethereum block height from `OMG.Eth`\n- synchronizes view of Ethereum block height of all enrolled processes (see other processes descriptions)\n\n### `:exiter`'s\n\nActually `OMG.EthereumEventListener` setup with `:exiter`.\n**NOTE** there's a multitude of exiter-related processes, which work along these lines, we're not listing them here\n\n- pushes exit-related events to `OMG.State` on child chain server's side\n- pushes exit-related events to `OMG.Watcher.ExitProcessor` on watcher's side\n- pushes exit-related events to `WatcherDB`\n\n### `:depositor`\n\nActually `OMG.EthereumEventListener` setup with `:depositor`.\n\n- pushes deposits to `OMG.State`\n- pushes deposits to `WatcherDB`\n\n### `OMG.ChildChain.BlockQueue`\n\n- requests `form_block` on `OMG.State` and takes block hashes in return\n- tracks Ethereum height and child chain block submission mining via `OMG.Eth` and `OMG.Watcher.RootChainCoordinator`\n\n### `OMG.ChildChain.FeeServer`\n- `OMG.ChildChain` calls it to get required fee amounts to validate transactions\n\n### `OMG.Watcher.BlockGetter`\n\n- tracks child chain blocks via `OMG.Watcher.RootChainCoordinator`\n- manages concurrent `Task`'s to pull blocks from child chain server API (JSON-RPC)\n- pushes decoded and statelessly valid blocks to `OMG.State`\n- pushes statefully valid blocks and transactions (acknowledged by `OMG.State` above) to `WatcherDB`\n- stops if `OMG.Watcher.ExitProcessor` reports a dangerous byzantine condition related to exits\n\n### `OMG.Watcher.ExitProcessor`\n\n- get various Ethereum events from `OMG.EthereumEventListener`\n- used only in Watcher\n- validates exits\n- spends finalizing exits in `OMG.State`\n\n### `OMG.WatcherRPC`\n\n- uses `OMG.Watcher` to server user's requests\n\n### `OMG.Performance`\n\n- executes requests to `OMG.WatcherRPC`\n- executes requests to `OMG.ChildChainRPC`\n- forces block forming by talking directly to `OMG.State`\n\n## Databases\n\n- The Child Chain talks to its local RocksDB\n- The Watcher talks to its local RocksDB\n- The Watcher Info talks to its local RocksDB and PostgreSQL\n\n### `OMG.DB`\n\nAn \"intimate\" database for `OMG.State` that holds the UTXO set and blocks.\nMay be seen and read by other processes to sync on the persisted state of `OMG.State` and UTXO set by consequence.\n\nNon-relational data, so we're having a simple KV for this.\n\nEach instance of either Child Chain Server or Watcher should have it's own instance.\n\nDatabase necessary to properly ensure validity and availability of blocks and transactions\n\n- it is read by `OMG.State` on restart to discover where it left off, whole UTXO set is not loaded.\n- it is read by many other processes to discover where they left off, on restart\n- it is used for the Watcher's security critical features to access exits info and blocks\n\n### Watcher Info DB\n\nA database running used by the Watcher in convenience API mode **only**.\n\nHolds all information necessary to conveniently manage the funds held:\n- UTXOs owned by user's particular address(es)\n- transaction history\n\nRelational data, to be able to navigate through the transactions and UTXOs.\nImplemented with PostgreSQL.\n"
  },
  {
    "path": "docs/branching.md",
    "content": "# Branching and deployments model\n\nThis document aims to discuss and document the relations between branches and deployments of `elixir-omg`, with respect to branches and deployments of `plasma-contracts`.\n\nIt is a refinement of the [OIP4 branching model](https://github.com/omgnetwork/OIP/blob/master/0004-ewallet-release-and-versioning.md), applicable to `elixir-omg` and `plasma-contracts` versioning.\n\nRationale:\n- the history and the relations between the versions are readable and simple to understand\n- we can predictably sync our respective watchers/run child chain servers against deployed contracts\n- we can move with the `master` branch quickly\n\n\n## Dependency Rules for elixir-omg\n\n### For mix.exs\n- `elixir-omg/master` will point to the `plasma-contracts/master`\n- A release branch in `elixir-omg` will point to the corresponding release branch in `plasma-contracts`. For example:\n  - `elixir-omg/v0.1` -> `plasma-contracts/v0.1`\n\n### For mix.lock\n- Always points to a specific SHA that in the history of the `plasma-contracts` branch referenced in `mix.exs`\n\n\n## Deployment Scenarios\n\n### 1 - Single production deployment, ongoing development\n\nThis is the active scenario most of the time.\n\nBranches and environments:\n- `master` is automatically deployed to **development** environment\n- `v0.1` is automatically deployed to **staging-v0-1** environment\n  - changes to the release branch will be merged into `master`, as needed\n- `v0.1` is manually deployed to **production-v0-1** environment\n\nDeploying new contracts in `master`:\n- make a PR to `elixir-omg/master` bumping the contract version in `mix.lock`\n- CI checks on the new integration\n- merge the PR\n- redeploy contracts\n- redeploy child chain and watcher\n- NB – contract deployment is currently a manual process, so we may be in a state where `mix.lock` points to a newer SHA than deployed on **development**. _We will correct this disparity as quickly as possible. This may mean rolling back `mix.lock`, if needed._\n- TODO – Automate contract deployments in **development**\n\nDeploying new contracts in the release branch:\n- :stop_sign: - _NOPE_\n- We cannot deploy any `elixir-omg` code that is incompatible with the currently deployed contracts in **staging** and **production**\n\n### 2 - Production deployment, validating a new version for network upgrade\n\nThis is a _feature freeze_ for the new version (`v0.2` branch). Try to minimize merging changes from `master` to any of the release branches.\n\nBranches and environments:\n- `master` is automatically deployed to **development** environment\n- `v0.2` is automatically deployed to **staging-v0-2** environment\n  - This assumes that during the process of validating a network upgrade, all work merged onto `master` will get deployed to for the upgrade.\n- `v0.1` is automatically deployed to **staging-v0-1** environment\n  - Keep this environment around for hotfixes\n- `v0.1` is manually deployed to **production-v0-1** environment\n\nDeploying new contracts in `master`:\n- Same as Scenario 1\n\nDeploying new contracts in `v0.1`:\n- :stop_sign: _NOPE_\n\nDeploying new contracts to `v0.2`\n- Manually deploy to **staging-v0-2**\n\n### 3 - Production deployment, ready to deploy network upgrade to production, ongoing development\n\nWhen we're confident of the stability on **staging-v0-2** and ready to go to Private Alpha, create the `v0.2` branch from `master` for both `elixir-omg` and `plasma-contracts` repos.\n\nMost importantly, we're confident about the contracts. A contract redeployment in this phase would have the most impact.\n\nBranches and environments:\n- `master` is automatically deployed to **development** environment\n- `v0.2` is automatically deployed to **staging-v0-2** environment\n  - changes to this release branch will be merged into `master`, as needed\n- `v0.2` is manually deployed to **production-v0-2** environment\n- `v0.1` is automatically deployed to **staging-v0-1** environment\n  - changes to the release branch will be merged into `master`, as needed\n- `v0.1` is manually deployed to **production-v0-1** environment\n\nDeploying new contracts to `master`:\n- Same as Scenario 1\n\nDeploying new contracts to `v0.1`\n- :stop_sign: _NOPE_\n\nDeploying new contracts to `v0.2`\n- Manually deploy to **staging-v0-2** and **production-v0-2** environments, _if absolutely necessary_\n\n### 4 - Two production deployments, ongoing development\n\nWe will have two production environments during the network upgrade, so that users have the opportunity to exit the old environment and deposit into the new environment.\n\nEverything the same as Scenario 3 except - Deploying new contracts to `v0.2`\n- :stop_sign: _NOPE_\n\nOnce this phase ends, we take down the older `production-v0-1` and `staging-v0-1` and return to Scenario 1. We may want to consider continuing to run a watcher for an old version a longer period of time.\n"
  },
  {
    "path": "docs/deployment_configuration.md",
    "content": "# Configuration via environment variables for deployment of Child Chain, Watcher and Watcher Info releases\n\n***Child Chain, Watcher and Watcher Info***\n\n- \"PORT\" - Child Chain or Watcher API port. Defaults to 9656 for Child Chain and 7434 for Watcher.\n- \"HOSTNAME\" - server domain name of Child Chain or Watcher. *mandatory*\n- \"DD_DISABLED\" - boolean that allows you to turn on or of Datadog metrics. Defaults to true.\n- \"APP_ENV\" - environment name in which the the application was deployed. *mandatory*\n- \"DB_PATH\" - directory of the KV db. *mandatory*\n- \"ETHEREUM_RPC_URL\" - address of Geth or Parity instance. *mandatory*\n- \"ETH_NODE\" - Geth, Parity or Infura. *mandatory*\n- \"SENTRY_DSN\" - if not set, Sentry is disabled.\n- \"DD_HOSTNAME\" - Datadog hostname.\n- \"DD_PORT\" - Datadog agent UDP port for metrics.\n- \"DD_APM_PORT\" - Datadog TCP port for APM.\n- \"BATCH_SIZE\" - Datadog batch size for APM.\n- \"SYNC_THRESHOLD\" - Datadog sync threshold for APM.\n- \"ETHEREUM_BLOCK_TIME_SECONDS\" - Should mirror Ethereum network's setting, defaults to 15 seconds.\n- \"ETHEREUM_EVENTS_CHECK_INTERVAL_MS\" - the frequency of HTTP requests towards the Ethereum clients and scanning for interested events. Should be less then average block time (10 to 20 seconds) on Ethereum mainnet.\n- \"ETHEREUM_STALLED_SYNC_THRESHOLD_MS\" - the threshold before considering an unchanging Ethereum block height to be considered a stalled sync. Should be slightly larger than the expected block time.\n- \"LOGGER_BACKEND\" - Ink or console. Ink will encode logs as json (useful for Datadog). Console will use the default elixir Logger backend. Default is Ink.\n\n***Child Chain only***\n\n- \"BLOCK_SUBMIT_MAX_GAS_PRICE\" - The maximum gas price to use for block submission. The first block submission after application boot will use the max price. The gas price gradually adjusts on subsequent blocks to reach the current optimum price. Defaults to `20000000000` (20 Gwei).\n- \"BLOCK_SUBMIT_STALL_THRESHOLD_BLOCKS\" - The number of root chain blocks passed until a child chain block pending submission is considered stalled. Defaults to `4` root chain blocks.\n- \"FEE_ADAPTER\" - The adapter to use to populate the fee specs. Either `file` or `feed` (case-insensitive). Defaults to `file` with an empty fee specs.\n- \"FEE_CLAIMER_ADDRESS\" - 20-bytes HEX-encoded string of Ethereum address of Fee Claimer.\n- \"FEE_BUFFER_DURATION_MS\" - Buffer period during which a fee is still valid after being updated.\n- \"FEE_SPECS_FILE_PATH\" - The path to the fee specs file including the file name.  Only applicable when `FEE_ADAPTER=file`.\n- \"FEE_FEED_URL\" - URL to fee feed service. Only applicable when `FEE_ADAPTER=feed`.\n- \"FEE_CHANGE_TOLERANCE_PERCENT\" - Positive integer describes significance of price change. When price in new reading changes above tolerance level, prices are updated immediately. Otherwise update interval is preserved. Only applicable when `FEE_ADAPTER=feed`.\n- \"STORED_FEE_UPDATE_INTERVAL_MINUTES\" - Positive integer describes time interval in minutes. The updates of token prices are carried out in update intervals as long as the changes are within tolerance. Only applicable when `FEE_ADAPTER=feed`.\n\n***Watcher and Watcher Info only***\n\n- \"CHILD_CHAIN_URL\" - Location of the Child Chain API. *mandatory*\n- \"EXIT_PROCESSOR_SLA_MARGIN\" - Number of Ethereum blocks since start of an invalid exit, before `unchallenged_exit` is reported to prompt to mass exit. Must be smaller than \"MIN_EXIT_PERIOD_SECONDS\", unless \"EXIT_PROCESSOR_SLA_MARGIN_FORCED=TRUE\".\n\n***Watcher Info only***\n\n- \"DATABASE_URL\" - Postgres address *mandatory*\n- \"WATCHER_INFO_DB_POOL_SIZE\" - The size of the database connection pool. Defaults to `10`.\n- \"WATCHER_INFO_DB_POOL_QUEUE_TARGET_MS\" - The maximum time to wait for a DB connection in milliseconds. Defaults to `50`.\n- \"WATCHER_INFO_DB_POOL_QUEUE_INTERVAL_MS\" - The interval in milliseconds to determine whether the queue target period above has been exceeded. Defaults to `1000`.\n\n***Erlang VM configuration***\n\n- \"NODE_HOST\" - The fully qualified host name of the current host.\n- \"ERLANG_COOKIE\" - Magic cookie of the node.\n- \"REPLACE_OS_VARS\" - An environment variable you export at runtime which instructed the tool to replace occurances of ${VAR} with the value from the system environment in the vm.args.\n\n***Contract address configuration***\nWe allow a static configuration or a dynamic one, served as a http endpoint (one of them is mandatory).\n\n- \"ETHEREUM_NETWORK\" - \"RINKEBY\" or \"LOCALCHAIN\".\n- \"CONTRACT_EXCHANGER_URL\" - a server that can serve JSON in form of\n```\n{\n  \"plasma_framework_tx_hash\":\"<plasma_framework_tx_hash>\",\n  \"plasma_framework\":\"<plasma_framework>\",\n  \"eth_vault\":\"<eth_vault>\",\n  \"erc20_vault\":\"<erc20_vault>\",\n  \"payment_exit_game\":\"<payment_exit_game>\",\n  \"authority_address\":\"<authority_address>\"\n}\n```\nStatic configuration\n\n- \"ETHEREUM_NETWORK\" - RINKEBY, ROPSTEN, MAINNET, or LOCALCHAIN\n- \"TXHASH_CONTRACT\"\n- \"AUTHORITY_ADDRESS\"\n- \"CONTRACT_ADDRESS_PLASMA_FRAMEWORK\"\n- \"CONTRACT_ADDRESS_ETH_VAULT\n- \"CONTRACT_ADDRESS_ERC20_VAULT\n- \"CONTRACT_ADDRESS_PAYMENT_EXIT_GAME\"\n\n***Required contract addresses***\n\nThe contract addresses that are required to be included in the `contract_addr` field (or `_CONTRACT_ADDRESS` JSON) are:\n\n```\n{\n  \"plasma_framework\": \"...\",\n  \"eth_vault\": \"...\",\n  \"erc20_vault\": \"...\",\n  \"payment_exit_game\": \"...\"\n}\n```\n"
  },
  {
    "path": "docs/details.md",
    "content": "\n**Table of Contents**\n\n<!--ts-->\n  * [elixir-omg applications](#elixir-omg-applications)\n  * [Child chain server](#child-chain-server)\n  * [Using the child chain server's API](#using-the-child-chain-servers-api)\n    * [HTTP-RPC](#http-rpc)\n  * [Ethereum private key management](#ethereum-private-key-management)\n    * [geth](#geth)\n  * [Managing the operator address](#managing-the-operator-address)\n  * [Nonces restriction](#nonces-restriction)\n  * [Funding the operator address](#funding-the-operator-address)\n  * [Watcher and Watcher Info](#watcher-and-Watcher-info)\n    * [Modes of the watcher](#modes-of-the-watcher)\n    * [Using the watcher](#using-the-watcher)\n    * [Endpoints](#endpoints)\n\n\n\n## `elixir-omg` applications\n\n`elixir-omg` is an umbrella app comprising of several Elixir applications:\n\nThe general idea of the apps responsibilities is:\n  - `omg` - common application logic used by both the child chain server and watcher\n  - `omg_bus` - an internal event bus to tie services together\n  - `omg_child_chain` - child chain server\n    - tracks Ethereum for things happening in the root chain contract (deposits/exits)\n    - gathers transactions, decides on validity, forms blocks, persists\n    - submits blocks to the root chain contract\n    - see `apps/omg_child_chain/lib/omg_child_chain/application.ex` for a rundown of children processes involved\n  - `omg_child_chain_rpc` - an HTTP-RPC server being the gateway to `omg_child_chain`\n  - `omg_db` - wrapper around the child chain server's database to store the UTXO set and blocks necessary for state persistence\n  - `omg_eth` - wrapper around the [Ethereum RPC client](https://github.com/exthereum/ethereumex)\n  - `omg_status` - application monitoring facilities\n  - `omg_utils` - various non-omg-specific shared code\n  - `omg_watcher` - the [Watcher](#watcher-and-watcher-info)\n  - `omg_watcher_info` - the [Watcher Info](#watcher-and-watcher-info)\n  - `omg_watcher_rpc` - an HTTP-RPC server being the gateway to `omg_watcher`\n\nSee [application architecture](architecture.md) for more details.\n\n## Child chain server\n\n`:omg_child_chain` is the Elixir app which runs the child chain server, whose API is exposed by `:omg_child_chain_rpc`.\n\nFor the responsibilities and design of the child chain server see [Plasma Blockchain Design document](tesuji_blockchain_design.md).\n\n## Using the child chain server's API\n\nThe child chain server is listening on port `9656` by default.\n\n### HTTP-RPC\n\nHTTP-RPC requests are served up on the port specified in `omg_child_chain_rpc`'s `config` (`:omg_child_chain_rpc, OMG.RPC.Web.Endpoint, http: [port: ...]`).\nThe available RPC calls are defined by `omg_child_chain` in `api.ex` - paths follow RPC convention e.g. `block.get`, `transaction.submit`.\nAll requests shall be POST with parameters provided in the request body in JSON object.\nObject's properties names correspond to the names of parameters. Binary values shall be hex-encoded strings.\n\nFor API documentation see: https://docs.omg.network/.\n\n## Ethereum private key management\n\n### `geth`\n\nCurrently, the child chain server assumes that the authority account is unlocked or otherwise available on the Ethereum node.\nThis might change in the future.\n\n## Managing the operator address\n\n(a.k.a `authority address`)\n\nThe Ethereum address which the operator uses to submit blocks to the root chain is a special address which must be managed accordingly to ensure liveness and security.\n\n## Nonces restriction\n\nThe [reorg protection mechanism](tesuji_blockchain_design.md#reorgs) enforces there to be a strict relation between the `submitBlock` transactions and block numbers.\nChild block number `1000` uses Ethereum nonce `1`, child block number `2000` uses Ethereum nonce `2`, **always**.\nThis provides a simple mechanism to avoid submitted blocks getting reordered in the root chain.\n\nThis restriction is respected by the child chain server, whereby the Ethereum nonce is simply derived from the child block number.\n\nAs a consequence, the operator address must never send any other transactions, if it intends to continue submitting blocks.\n(Workarounds to this limitation are available, if there's such requirement.)\n\n**NOTE** Ethereum nonce `0` is necessary to call the `RootChain.init` function, which must be called by the operator address.\nThis means that the operator address must be a fresh address for every child chain brought to life.\n\n## Funding the operator address\n\nThe address that is running the child chain server and submitting blocks needs to be funded with Ether.\nAt the current stage this is designed as a manual process, i.e. we assume that every **gas reserve checkpoint interval**, someone will ensure that **gas reserve** worth of Ether is available for transactions.\n\nGas reserve must be enough to cover the gas reserve checkpoint interval of submitting blocks, assuming the most pessimistic scenario of gas price.\n\nCalculate the gas reserve as follows:\n\n```\ngas_reserve = child_blocks_per_day * days_in_interval * gas_per_submission * highest_gas_price\n```\nwhere\n```\nchild_blocks_per_day = ethereum_blocks_per_day / submit_period\n```\n\n**Submit period** is the number of Ethereum blocks per a single child block submission) - configured in `:omg_child_chain, :child_block_submit_period`\n\n**Highest gas price** is the maximum gas price which the operator allows for when trying to have the block submission mined (operator always tries to pay less than that maximum, but has to adapt to Ethereum traffic) - configured in (**TODO** when doing OMG-47 task)\n\n**Example**\n\nAssuming:\n- submission of a child block every Ethereum block\n- 15 second block interval on Ethereum, on average\n- weekly cadence of funding, i.e. `days_in_interval == 7`\n- allowing gas price up to 40 Gwei\n- `gas_per_submission == 71505` (checked for `RootChain.sol` [at this revision](https://github.com/omgnetwork/plasma-contracts/commit/50653d52169a01a7d7d0b9e2e4e3c4a4b904f128).\nC.f. [here](https://rinkeby.etherscan.io/tx/0x1a79fdfa310f91625d93e25139e15299b4ab272ae504c56b5798a018f6f4dc7b))\n\nwe get\n```\ngas_reserve ~= (4 * 60 * 24 / 1) * 7 * 71505 * (40 / 10**9)  ~= 115 ETH\n```\n\n**NOTE** that the above calculation doesn't imply this is what is going to be used within a week, just a pessimistic scenario to calculate an adequate reserve.\nIf one assumes an _average_ gas price of 4 Gwei, the amount is immediately reduced to ~11.5 ETH weekly.\n\n## Watcher and Watcher Info\n\nThe Watcher is an observing node that connects to Ethereum and the child chain server's API.\nIt ensures that the child chain is valid and notifies otherwise.\nIt exposes the information it gathers via an HTTP-RPC interface (driven by Phoenix).\nIt provides a secure proxy to the child chain server's API and to Ethereum, ensuring that sensitive requests are only sent to a valid chain.\n\nFor more on the responsibilities and design of the Watcher see [Plasma Blockchain Design document](tesuji_blockchain_design.md).\n\n### Modes of the watcher\n\nThe watcher can be run in one of two modes:\n  - **security-critical only**\n    - intended to provide light-weight Watcher just to ensure security of funds deposited into the child chain\n    - this mode will store all of the data required for security-critical operations (exiting, challenging, etc.)\n    - it will not store data required for current and performant interacting with the child chain (spending, receiving tokens, etc.)\n    - it will not expose some endpoints related to current and performant interacting with the child chain (`account.get_utxos`, `transaction.*`, etc.)\n    - it will only require the `OMG.DB` key-value store database\n    - this mode will prune all security-related data not necessary anymore for security reasons (from `OMG.DB`)\n    - some requests to the API might be slow but must always work (called rarely in unhappy paths only, like mass exits)\n  - **security-critical and informational API**\n    - intended to provide convenient and performant API to the child chain data, on top of the security-related one\n    - this mode will provide/store everything the **security-critical** mode does\n    - this mode will store easily accessible register of all transactions _for a subset of addresses_ (currently, all addresses)\n    - this mode will leverage the PostgreSQL - based `WatcherDB` database\n\nIn releases, `watcher` refers to the security-critical mode, while `watcher_info` refers to the security-critical and informational API mode.\n\n### Using the watcher\n\nThe watcher is listening on port `7434` by default. And watcher info listens on port `7534`.\n\n### Endpoints\n\nFor API documentation see: https://docs.omg.network/\n\n### Ethereum private key management\n\nWatcher doesn't hold or manage user's keys.\nAll signatures are assumed to be done outside.\n\n# Configuration parameters\n\nFor docker deployments, and release deployments please refer to [Deployment Configuration](deployment_configuration.md).\n\n**NOTE**: all margins are denominated in Ethereum blocks\n\n## Child chain server configuration - `:omg_child_chain` app\n\n* **`submission_finality_margin`** - the margin waited before mined block submissions are purged from `BlockQueue`'s memory\n\n* **`block_queue_eth_height_check_interval_ms`** - polling interval for checking whether the root chain had progressed for the `BlockQueue` exclusively\n\n* **`fee_adapter_check_interval_ms`** - interval for checking fees updates from the fee adapter.\n*\n* **`fee_buffer_duration_ms`** - duration for which a fee is still valid after beeing updated.\n\n* **`block_submit_every_nth`** - how many new Ethereum blocks must be mined, since previous submission **attempt**, before another block is going to be formed and submitted.\n\n* **`block_submit_max_gas_price`** - the maximum gas price to use for block submission. The first block submission after application boot will use the max price,\n  and gradually adjusts to the current optimum price for subsequent blocks.\n\n* **`fee_specs_file_path`** - path to the file which defines fee requirements\n\n* **`fee_adapter`** - is a tuple, where first element is a module name implementing `FeeAdapter` behaviour, e.g. `OMG.ChildChain.Fees.FileAdapter` and the second element is a Keyword `[opts: fee_adapter_opts]`\nOptions of the fee adapter, depends on adapter\n  - **`specs_file_path`** - [FileAdaper only] path to file (including the file name) which defines fee requirements, see [fee_specs.json](fee_specs.json) for an example.\n  - **`fee_feed_url`** - [FeedAdapter only] url to the fee service, that privides actual fees prices. Response should follow the file specs format.\n  - **`fee_change_tolerance_percent`** - [FeedAdapter only!] positive integer describes significance of price change. When price in new reading changes above tolerance level, prices are updated immediately. Otherwise update interval is preserved.\n  - **`stored_fee_update_interval_minutes`** - [FeedAdapter only!] positive integer describes time interval in minutes. The updates of token prices are carried out in update intervals as long as the changes are within tolerance.\n\n## Watcher configuration - `:omg_watcher` app\n\n* **`deposit_finality_margin`** - the margin that is waited after a `DepositCreated` event in the root chain contract.\nOnly after this margin had passed:\n  - the child chain will allow spending the deposit\n  - the watcher and watcher info will consider a transaction spending this deposit a valid transaction\n\n  It is important that for a given child chain, the child chain server and watchers use the same value of this margin.\n\n  **NOTE**: This entry is defined in `omg`, despite not being accessed there, only in `omg_child_chain` and `omg_watcher`.\n  The reason here is to minimize risk of Child Chain server's and Watcher's configuration entries diverging.\n\n* **`ethereum_events_check_interval_ms`** - polling interval for pulling Ethereum events (logs) from the Ethereum client.\n\n* **`coordinator_eth_height_check_interval_ms`** - polling interval for checking whether the root chain had progressed for the `RootChainCoordinator`.\nAffects how quick the services reading Ethereum events realize there's a new block.\n\n* **`exit_processor_sla_margin`** - the margin to define the notion of a \"late\", invalid exit.\nAfter this margin passes, every invalid exit is deemed a critical failure of the child chain (`unchallenged_exit`).\nSuch event will prompt a mass exit and stop processing new blocks.\nSee [exit validation documentation](docs/exit_validation.md) for details.\nCannot be larger than `min_exit_period_seconds` because otherwise it leads to a dangerous setup of the Watcher (in particular - muting the reports of unchallenged_exits).\nOverride using the `EXIT_PROCESSOR_SLA_MARGIN` system environment variable.\n\n* **`exit_processor_sla_margin_forced`** - if set to `true`, will allow one to set a `exit_processor_sla_margin` that is larger than the `min_exit_period_seconds` of the child chain we're running for.\nSet to `true` only when you know what you are doing.\nDefaults to `false`, override using the `EXIT_PROCESSOR_SLA_MARGIN_FORCED` system environment variable.\n\n* **`maximum_block_withholding_time_ms`** - for how long the Watcher will tolerate failures to get a submitted child chain block, before reporting a block withholding attack and stopping\n\n* **`maximum_number_of_unapplied_blocks`** - the maximum number of downloaded and statelessly validated child chain blocks to hold in queue for applying\n\n* **`exit_finality_margin`** - the margin waited before an exit-related event is considered final enough to pull and process\n\n* **`block_getter_reorg_margin`** - the margin considered by `OMG.Watcher.BlockGetter` when searching for recent child chain block submission events.\nThis is driving the process of determining the height and particular event related to the submission of a particular child chain block\n\n## `OMG.DB` configuration - `:omg_db` app\n\n* **`path`** - path to the directory holding the LevelDB data store\n\n* **`server_module`** - the module to use when talking to the `OMG.DB`\n\n* **`server_name`** - the named process to refer to when talking to the `OMG.DB`\n\n## `OMG.Eth` configuration - `:omg_eth` app\n\nAll binary entries are expected in hex-encoded, `0x`-prefixed.\n\n* **`contract_addr`** - the address of the root chain contract\n\n* **`authority_address`** - the address used by the operator to submit blocks\n\n* **`txhash_contract`** - the Ethereum-transaction hash holding the deployment of the root chain contract\n\n* **`eth_node`** - the Ethereum client which is used: `\"geth\" | \"infura\"`.\n\n* **`node_logging_in_debug`** - whether the output of the Ethereum node being run in integration test should be printed to `:debug` level logs.\nIf you set this to false, remember to set the logging level to `:debug` to see the logs\n\n* **`child_block_interval`** - mirror of contract configuration `uint256 constant public CHILD_BLOCK_INTERVAL` from `RootChain.sol`\n\n* **`min_exit_period_seconds`** - mirror of contract configuration `uint256 public minExitPeriod`\n\n* **`ethereum_block_time_seconds`** - mirror the block time of the underlying root chain.\nDefaults to 15 seconds, suitable for public networks (`mainnet` and testnets).\nOverride using the `ETHEREUM_BLOCK_TIME_SECONDS` system environment variable.\n"
  },
  {
    "path": "docs/dex_design.md",
    "content": "# The OMG decentralized Exchange (ODEX)\n\nThe term decentralized is a very broad term and typically is a catch-all for a number of characteristics or dimensions. For example, we may use the term decentralized to refer to the lack of a single controlling entity, and as a result bring the benefit of censorship resistance.\n\nSimilarly the term “DEX” (decentralized exchange) also sees broad use with no widespread accepted definition. In order to provide clarity and consistency of understanding when talking about decentralization, or more specifically a DEX, we've decided to decompose our use of the term into a non exhaustive list of dimensions. We can then use these dimensions to describe the OMG DEX itself, its benefits and detail how we will prioritize its development. For the remainder of this document we will refer to the OMG DEX as \"ODEX\" for brevity.\n\n## A Taxonomy for Decentralization\n\nTo help establish a reusable taxonomy of decentralization using our identified dimensions we have grouped the them into three categories. Each category represents some aspect, or potential benefit, that may arise as a result of decentralization. These categories are:\n\n* **Intrinsic Dimensions** — these dimensions arise directly as a result of decentralization.\n* **Value Dimensions** — these dimensions are values we wish to support by leveraging the intrinsic and structural properties decentralization can bring. Specifically these are value dimensions that facilitate the delivery of fair and transparent markets.\n* **Structural Dimensions** — structural dimensions are properties that arise from the specific choices made when designing our decentralized exchange.\n\n## Intrinsic Dimensions of Decentralization\n\n|Dimension|Benefit to the End User|\n| - | - |\n|Secure / Trustless|User funds are safe from being hacked as users retain custodial control of their funds|\n|Uncensorable|No single venue can stop you from being a participant|\n\n\n## Value Dimensions Enabled by Decentralization\n|Dimension|Benefit to the End User|\n| - | - |\n|Private|Post-trade Anonymity — no adverse market reaction, by the time the market sees a trade the market reaction has already been considered in the visible market|\n|Private|Pre-trade Anonymity — no adverse market selection|\n|Transparent|Pre-trade Transparency — The ability to offer an accurate view of market liquidity|\n|Fair|Quality liquidity — the ability to access provable transparency information resulting in increased trust in the market brings better price discovery and a fairer market overall|\n|Liquid|Reduced fragmentation of liquidity by providing a single network on which all order flow can be connected|\n|Cost control|Visibility of cost structure|\n\n## Structural Dimensions Resulting from OMG Network Implementation\n|Dimension|Benefit to the End User|\n| - | - |\n|Transparent|Post-trade Transparency — The ability to offer a provably accurate view of post-trade prices|\n|Fast settlement|Trades settle quickly and you choose when your trades settle|\n|Mobility of funds|Easily move your funds between different venues on the OMG Network to achieve 'best execution'|\n|Interoperable|Trade with other blockchains that are compatible with the OMG Network: Bitcoin, Litecoin, etc|\n|Upgradeable|Transparency in the upgrade process|\n|Responsive|Trade when you want to trade (reduced probability of network congestion due to the higher throughput of the OMG Network)|\n|Private|Identifying yourself may not be required in all exchanges|\n\n\nIt isn’t possible to solve for *all* of the dimensions listed above, and each of the dimensions must be treated as design decisions on a spectrum, however using the taxonomy allows us to make informed choices around the impacts of our design choices and aids prioritization. In the following sections we will offer overviews of a number of alternative, evolving models, each driven by various tradeoffs for the dimensions above.\n\nIn each of the models our primary goal is to solve for security and specifically the minimisation of funds loss. Thereafter, there exists a number of options in the design space balancing other key dimensions such as fairness, price transparency, user privacy, and speed.\n\n# ODEX Features\n\n## Introduction\n\nThis section will review the key features of ODEX. The following diagram is a high level view of what we think the future state of the ODEX may look like. This diagram will be described in detail throughout the remaining sections. A key observation should be that the ODEX is more than just a single market. It is an infrastructure upon which any participant can engage in using, making or delivering a market. This means many market models can be supported, all with the same underlying benefits and guarantees of the ODEX, while simultaneously offering tailored trading experiences as appropriate to the target users. This means that ODEX is more than just a single market, it is a network supporting many markets with both direct users of the ODEX co-existing beside indirect users of the ODEX such as venues themselves.\n\n![ODEX Overview](assets/dex_design/01_ODEX%20Features.png)\n\n## Restricted Custody\n\nOMG Network proposes a solution whereby user funds are secured by the child chain consensus mechanism. The exchange of value occurs in a secure manner, which vastly reduces the risk exposure for both a venue *and* for the user.\n\nWe can provide this custodial safety using a model which utilizes the safety of the child chain consensus mechanism.\n\nRestricted Custody allows custody transfer to an off-chain venue to facilitate matching but only allows the venue to perform the required, fundamental actions, such as partial matching, canceling and initiation of settlement.\n\nThe constraints that are placed upon funds in custody of a venue are:\n* The venue can neither deposit nor exit from the child chain\n* Transactions to move funds must be represented as trade settlements. The venue must prove that the beneficiaries of the settlements wanted to trade (by their signed orders).\n\nIn other words, a venue cannot spend or exit user funds.\n\nTherefore, whilst users may transfer custody of their funds to a venue, the users will rely on the safety of the child chain consensus to enforce how a users' funds may be used. Also, importantly, this facilitates, in an efficient and fundamental way, firm orders. Firm orders are an essential component of any fair market.\n\nNote that work on Restricted Custody is continuing and changes can be expected to this design.\n\n## Multiple Venues\n\nAs stated in the introduction the ODEX will support multiple venues, removing single points of failure and reducing the possibility that a user will be refused exchange services. Multiple venues increases censorship resistance.\n\nThe possible addition of an on-chain exchange in the future would add much greater censorship resistance.\n\n## Off-Chain Markets\n\nOff-chain markets (typically orderbook driven) are important, not only to move computationally intensive tasks away from the operator/validators, but also to give venues the flexibility to change their market microstructure (such as minimum tick size, fee structure and so on) to ensure a competitive and differentiated market for an exchange's services, and markets themselves. It also has material implications on the speed and efficiency of the matching and trading process as we will see later.\n\n## On-Chain Markets (Parallel Phase)\n\nIn conjunction with off-chain markets, an on-chain market that is maintained by the child chain consensus system may be added. Further research is underway in this area to identify how an on-chain market, which could be offered using an orderbook structure, fits into the ODEX market model. It is also possible that multiple on-chain markets may be supported to differentiate between the specific needs of particular markets.\n\nThe research and the introduction of an on-chain market may occur in parallel to the development of off-chain market support.\n\nInitially, it is thought that an on-chain market would have the following characteristics:\n\n* Call market, rather than continuous market\n\n* Auction based mechanism built on an orderbook\n\nWhilst an on-chain market may not offer the best price to participants (for example, because of lower liquidity concentration or slowness in reacting to fundamental changes in value), the on-chain market can offer guaranteed access to an exchange mechanism for participants that cannot, or choose not to, gain access to other venues on the ODEX.\n\n## Batch Settlement\n\nWithout batch settlement, it would be expected for venues to immediately settle any order execution. In a low transaction environment that can work effectively however it doesn't scale particularly well. By supporting batch settlement we can reduce the number of transactions required for settlement, and it is therefore useful for:\n\n* Auctions\n\n* Efficiently settling in highly liquid markets\n\n* Atomic settlement where multiple orders are resolved simultaneously, such as options markets\n\n* Settlement of implied orders. Implied orders are necessary for the OMG Network core user story. Implied orders enable the exchange of two assets that do not directly trade against one another. For example,\n\n    * A user has Burger tokens and a cafe accepts Coffee tokens\n\n    * There is no market for BURGER/COFFEE tokens\n\n    * However, there is a market for BURGER/ETH and ETH/COFFEE\n\n    * Therefore an implied price and an implied order can be derived\n\n## Proveable Trade Settlement\n\nA venue will not be allowed to ‘spend’ or move funds in an unconstrained manner. Instead funds may only be ‘settled’ and a proof must be produced by the venue with the orders that constitute the resultant settlement.\n\nProveable trade settlement increases the safety of the exchange for users and provides valuable post-trade transparency (see next section).\n\n## Pre-trade and Post-trade Transparency\n\nPost-trade transparency provides trade information after a trade has been executed. Whereas pre-trade transparency indicates prices at which participants are willing to pay. High quality, and trusted transparency is an essential requirement for fair markets and good price discovery.\n\nTransparency is also important because the data can be used to predict price changes and to validate the proofs generated by venues on the ODEX.\n\nWhilst there may be multiple venues operating independently on the ODEX, information about how the market is operating may be consolidated.\n\nA ‘global’ ticker tape could be derived for:\n\n* Prices for all trade executions for a particular venue, or the network as a whole\n\n* Last price for any instrument (currency pair such as ETH/OMG)\n\n* Trusted pricing metrics and price movements for any instrument could be offered as an extension to basic ticker information\n\n## Order Privacy\n\nIn traditional markets, order details are private to a trader. This is a very important as it allows traders to take positions which reflect what they see as the fundamental value of the instrument being traded, without the knowledge that *they* are taking a position or *how* they are taking the position. If the order details were public other traders could use it to infer information about the instrument's value or other traders intent and strategies. For example that a specific trader was hedging one instrument against another. It is also a key aspect of fair markets in general, and so it is highly undesirable for this information to be made public.\n\n# ODEX Phases\n\n## Introduction\n\nIn this section we present our current phasing of development for ODEX to illustrate how we can incrementally prove out the features of ODEX that underpin the value proposition captured by the previous dimensions of decentralization. As this phasing is incremental (though not necessarily serial) any functionality added in each phase is *cumulative*, unless otherwise specified.\n\n## Phase 1 - Technology Proof of Concept\n\n![phase 1 diagram](assets/dex_design/02_Phase%201%20-%20Technology%20Proof%20of%20Concept.png)\n\nPhase 1 implements all the technical components and the basic child chain consensus changes that are required to prove out the feasibility of the ODEX.\n\nOrders are matched off-chain and trades are immediately settled on-chain with a proof.\n\n## Phase 2 - MVP\n\n![phase 2 diagram](assets/dex_design/03_Phase%202%20-%20MVP.png)\n\nBatch settlements are introduced in Phase 2. Batch settlements most importantly will enable implied orders, which are required to fulfill one of the primary OMG Network use cases (see Appendix).\n\nBatch settlement optimizes the settlement process because settlement is the net outcome of all of the trades in a batch. Matching must be performed in a deterministic way such that proofs may be independently verified.\n\nPhase 1 and phase 2 have an interesting safety property. Since the private key for a venue is only used to sign proofs for settlements, loss of the private key will not result in user funds being lost. However, if an attacker was able to gain access to a venue's private key, the attacker may be able to spam the network.\n\nCaution: Care must be taken to ensure that computation complexity of validating all of the settlement proofs can be accommodated by both a single operator (in PoA) and by the target validator sizing (in PoS).\n\n## Phase 3 - Bonded Exchanges\n\n![phase 3 diagram](assets/dex_design/04_Phase%203%20-%20Bonded%20Exchages.png)\n\nPhase 3 introduces an explicit economic disincentive for a venue to perform bad behavior. Upon proof of bad behaviour, such as an invalid settlement, a venue would lose some or all of their bond.\n\nThe size of the bond that needs to be posted is yet to be decided. However, it may be possible for the bond to be sized proportionally to the amount of risk on the order books of an exchange. Without a considered approach to bond sizing venues would be disincentivized from providing matching facilities and therefore users would ultimately suffer as the cost of transacting would become very high. This would incentivize users to seek alternative, less secure and more centralized venue offerings.\n\n## Phase 4 - Order Privacy\n\n![phase 4 diagram](assets/dex_design/05_Phase%204%20-%20Order%20Privacy.png)\n\nPhase 4 introduces order privacy, most likely utilizing zero knowledge proofs. Note that this phase is a research topic and is subject to change.\n\nZero knowledge proofs protect traders from revealing order details, but maintains provable trade settlement and post-trade transparency. In other words the basic economic details of a trade is known (the price and quantity), but not the specific details of the orders that were executed to complete that trade. This details could include information such as who traded, any limit price, original quantity and so on. Were this information available it would be very difficult for any trader to achieve a fair price with risk of adverse selection and therefore transitory volatility would increase in the market, damaging price discovery and increasing the cost of trading overall.\n\nSome flexibility may be possible with order privacy, whereby some order details are public and some order details are not public. This may be on a per venue or a per order basis. This is an active area of our research, both from a technical and markets perspective.\n\n## OMG On-Chain Markets\n\n![on chain venue diagram](assets/dex_design/06_OMG%20On-chain%20Venue.png)\n\nIn conjunction with off-chain markets, on-chain markets that are maintained by the child chain consensus system is planned. Further research will be performed in this area to identify how on-chain markets fit into the ODEX market model.\n\nThe research may occur in parallel to the development of off-chain markets, but development will initially commence with Restricted Custody (phase 1 and 2).\n\nAs was stated previously it is thought that viable and useful on-chain markets would be:\n\n* Periodic Call markets, rather than Continuous markets, and be,\n\n* Auction based, executed using an orderbook\n\nOn-chain markets have the attraction of providing guaranteed access (censorship resistance) to matching services for any participant. However, on-chain markets in and of themselves my not offer the best available execution because of a slower speed of market adaption (slower movement towards the fundamental value of an instrument), lower access to counterparty interest at the desired price (lower concentration for liquidity of an instrument) and potential a higher cost of trading (it may not be feasible to reduce the cost of trading in on-chain markets as batching may not be as efficient).\n\nHaving said that, on-chain markets can provide several key features beyond simply universal access. On-chain markets can provide uniform access to all participants on ODEX, venues and users alike and therefore on-chain markets can provide a conduit for liquidity access between all participants and a baseline view of pricing for any trading instrument.\n\n## Comparison of Phases\n\n||Phase 1|Phase 2|Phase 3|Phase 4|On-Chain|\n|--- |--- |--- |--- |--- |--- |\n|Settlement|Per-Order|Batch|Batch|Batch|Batch + Auction|\n|Partial fills|Y|Y|Y|Y|Y|\n|Implied orders, auctions|N|Y|Y|Y|Y|\n|Direct disincentive against incorrect venue behaviour?|N|N|Y|Y|Y|\n|Orders private?|N|N|N|Y|Y|\n|On-chain order book|N|N|N|N|Y|\n\n\n# Functional Market Structure\n\n## Introduction\n\nFor the ODEX to be successful, the DEX must address the basic market model principles of a well-functioning market. A well functioning market has many characteristics and the features of ODEX previously discussed has already called out many of these. The purpose of this section is to call out, in a practical sense, the requirements that need to be met to support the OMG Network vision.\n\n## Market Microstructure\n\nIn order to appreciate the detail in this section we need to introduce how we define the concept of market duration.\n\nAny market can exist for an arbitrary period of time, and in fact while someone, somewhere is making a market in a particular instrument we can say there exists a market for that instrument. In practical terms however we must assume that for any venue there will be a period over which they explicitly make a market for an instrument. In a traditional venue these market durations are usually determined by wider practical limitations.\n\nFor example most market durations are a single day, with the market being reset each day, or perhaps over a week with the market being reset, or paused weekly. These limitations can be things like people sleep at night and therefore in a localized market there will only be thin trading overnight so the markets simply close. It could be aligned to a feature of the instrument and so on.\n\nIn the crypto markets we tend to have global 24/7 markets and so concepts like a trading \"day\" are less valuable. However these concepts are still critical in operating an effective and orderly market as they facilitate things like the ability to determine orders are stale, or provide an opportunity to perform market resets, or venue maintenance.\n\n### Market Duration\n\nTo help us identify and understand the market structures that the ODEX must support we have adopted a taxonomy, really a vocabulary, for describing market duration so as to allow us to anchor the requirements we will place on ODEX to support any overlaid market model. This vocabulary is as follows:\n\n* Any market can be divided into Trading Cycles (a cycle could for example last a day)\n\n* A Trading Cycle may have one or more Trading Phases (within a cycle you might have a pre-open, open, after-hours and close phase)\n\n* Each Trading Phases may be decomposed into Trading Sessions (a session might be an auction session, or a continuous trading session)\n\nWith this macro view of market duration, as delivered by any one specific venue on the ODEX, we can now be more specific about duration requirements as they relate to the ODEX itself. Any venue connected to the ODEX can of course adopt any duration model they like but the vocabulary identified will allow those models to be mapped as needed to the features that ODEX offers.\n\n### Required Order Types\n\nThis section details the minimal characteristics of orders that must be supported. Typically these characteristics are captured as \"order types\" in a venue. Most other order types can be composed from these fundamental features.\n\n**Fill Constraints**\n\n* Partial fills of orders must be supported\n\n**Continuous Market - Immediacy**\n\n* Market order (only a quantity is specified)\n\n* Limit order (quantity, price)\n\n* Quote limit order (two-sided limit order that is required for market-makers)\n\n**Continuous Market - Time in Force**\n\n* Good for cycle (order is good until the end of the current cycle)\n\n* Immediate or cancel (order must fill immediately, otherwise the order is cancelled)\n\n**Auctions**\n\n* Market order (quantity only)\n\n* Limit order (quantity, price)\n\n**Implied orders**\n\n* Exchange of two assets that do not have directly trade against one another\n\n## Minimal Required Participants\n\nIn order for ODEX to be minimally viable we will need to have at least the following participants:\n\n* At least one exchange (aka venue)\n\n* Users\n\n* Market maker\n\n* Child Chain Operator (at PoA), Validators (at PoS)\n\n# Safety Considerations\n\nThe following diagram illustrates the security at differing layers for the OMG Network.\n\n![safety considerations diagram](assets/dex_design/07_safety_considerations.png)\n\nThe OMG Network relies on the safety of Ethereum, and applications that run on the OMG Network will rely on the consensus mechanism of the OMG Network.\n\nFurther research will be performed to consider the safety of the OMG Network and the core goal for what the OMG Network should be providing safety for.\n\nThe prior descriptions about the ODEX assume that the safety of venues are tightly coupled in the consensus of the OMG Network. That is, venues are considered as part of layer 2.\n\nIt may not be desirable to couple the consensus of the OMG Network into venues because of the potentially unbounded amount of computation that would be required for validators. In that event, venues could be moved to the ‘App’ layer, and define their own safety and security guarantees.\n\nAn alternative final state of the ODEX would then look as follows:\n\n![ODEX Alternative state](assets/dex_design/08_ODEX%20Alternative%20State.png)\n\nWhilst this model may relieve validators of computational load, we should take note the following points:\n\n1. The value proposition to venues to integrate into the ODEX is greatly reduced because the operational risk for the funds in a venue is borne by the venue.\n\n2. It is generally understood that a continuous market, not a call market (which a batch auction is) has greater liquidity. This is due to the limited pre-trade transparency and lack of immediacy of call markets.\n\nFurther research may uncover mechanisms to support more functional on-chain markets and how those markets would fit into the overall ODEX market model.\n\n# Value Proposition\n\n## Introduction\n\nThis section describes the proposed value proposition of the ODEX to each of the different users and stakeholders in the OMG ecosystem.\n\nThese value propositions still need to be validated for each of the target users and stakeholders.\n\n## End Users of Venues\n\n* A list of benefits can be found at the start of this document.\n\n## Existing Crypto and Traditional Venues\n\nExisting crypto exchanges can be hacked, leading to loss of user funds, loss of capital and loss of user confidence in the crypto ecosystem. Traditional venues (typically regulated) would like to participate in the crypto markets and offer services to their users but face all the same issues as crypto venues and therefore expose themselves to significant reputational damage and fail to offer a secure enough market to be attractive to their existing members. Both venue types could benefit significantly from:\n\n* Restricted custody of user funds\n\n* Reduced regulatory exposure (such as not taking user deposits)\n\n* Simplified operations due to lack of requiring a hot/cold wallet system\n\n* Existing business models are generally compatible with the ODEX\n\n## eWallet and Wallet Users\n\neWallet and Wallet users suffer from security risk and immediacy of fund transfers. The ODEX will offer the following key benefits:\n\n* Access to exchange functions that can support cross-currency payments\n\n* Faster access to liquid markets to trade in\n\n* The same benefits as end users of venues\n\n## OMG Network\n\nThe OMG network provides value by incentivizing a growing network of participants all of whom can benefit from the existence of the network. ODEX can help support network growth and evolution from:\n\n* Fees derived from transaction volume that is generated from settlements\n\n# Appendix\n\n## Supporting User Story\n\nDuring all discussions surrounding the ODEX design, it important to remember that the DEX must support the following fundamental user story:\n\n\n>As Alice I can cheaply use Burger tokens to Bob to make a payment to Bob (who accepts Coffee tokens)\n\nSimilarly, the DEX should support the above user story when \"Burger tokens\" and Coffee tokens” are substituted for any type of asset that may be traded on the OMG Network. This may include fiat or asset backed tokens, stablecoins and cryptocurrencies.\n\n## Prior DEX Designs\n\nWe investigated multiple types of designs, of which many have acted as inputs to the designs outlined within this document.\n\nA matrix comparing each of the current and prior designs against the dimensions that were described earlier in this document can be found below.\n\n[https://docs.google.com/spreadsheets/d/1-i304AhhiddXOezouQVCJZzyCa2RlQ-TfrKiBjyoLZY/edit?usp=sharing](https://docs.google.com/spreadsheets/d/1-i304AhhiddXOezouQVCJZzyCa2RlQ-TfrKiBjyoLZY/edit?usp=sharing)\n"
  },
  {
    "path": "docs/exit_validation.md",
    "content": "# Exit validation\n\nThis document describes the exit validation (processing) done by the Watcher in `ExitProcessor`.\n\n## Definitions\n\n* **scheduled finalization time** - a point in time when an exit will be able to process, see [this section in the blockchain design document](docs/tesuji_blockchain_design.md#finalization-of-exits).\n* **`exit_finality_margin`** - margin of the exit processor (in Ethereum blocks) - how many blocks to wait for finality of exit-related events\n* **child chain exit recognition SLA** - a form of a Service Level Agreement - how fast will the child chain recognize newly started exits and prohibit spending of exiting UTXOs\n* **unchallenged exit tolerance** - a Watcher's tolerance to an invalid exit not having a challenge for a long time since its start.\n   - **NOTE**: in practice, violation of the child chain exit recognition SLA and violation of the unchallenged exit tolerance are similar.\n  The correct reaction to both is a prompt to mass exit and a challenge of the invalid exit.\n  Because of this, only `sla_margin` is used as a configurable setting on the Watcher, covering for both conditions.\n* **`sla_margin`** - margin of the child chain exit recognition SLA (in Ethereum blocks).\nThis is a number of blocks after the start of an exit (or piggyback), during which a Child Chain Server still might include a transaction invalidating a previously valid exit, without violating the child chain exit recognition SLA.\nSimilarly, this is a number of blocks after the start of an exit (or piggyback), during which the Watcher will not report [unchallenged exits](#unchallenged_exit-condition).\n\n## Notes on the Child Chain Server\n\nThis document focuses on the Watcher, but for completeness we give a quick run-down of the rules followed by the Child Chain Server, in terms of processing exits events from the root chain contract.\n\n1. The child chain operator's objective is to pro-actively minimize the risk of chain becoming insecure or, in worst case scenario, insolvent.\nThe child chain becomes insolvent if any invalid exit gets finalized, which leads to loss of child chain funds.\n2. To satisfy this objective, the Child Chain Server:\n    - listens to every `ExitStarted` root chain event and _immediately_ marks as spent the exiting UTXO,\n    - listens to every `InFlightExitStarted` root chain event and _immediately_ marks as spent the exiting transaction's **inputs**,\n    - listens to every `InFlightExitOutputPiggybacked` root chain event and _immediately_ marks as spent the piggybacked outputs - as long as the IFEing transaction has been included in the chain and the output exists,\n\n  prohibiting spending of the exiting UTXO, preventing exit invalidation.\n\n    - **NOTE**: This immediacy is limited because the server must process deposits before exits and deposits _must_ wait for finality on the root chain.\n\nThere are scenarios, when a race condition/reorg on the root chain might make the Child Chain Server prohibit spending of a particular UTXO **late**, regardless of the immediacy mentioned above.\nThis is acceptable as long as the delay doesn't exceed the `sla_margin`.\n\n## Watcher\n\n### Choice of the `sla_margin` setting value\n\n`sla_margin` is a set on the Watcher (via [`exit_processor_sla_margin`/`EXIT_PROCESSOR_SLA_MARGIN`](./details.md#configuration-parameters)), which needs to be determined correctly for various deployments and environments.\nIt should reflect the exit period and the intended usage patterns and security requirements of the environment.\n\n`sla margin` should be large enough:\n - for the Child Chain Server (that runs the child chain the Watcher validates), to recognize exiting UTXOs, to prevent an invalidating transaction going through\n - for anyone concerned with challenging to challenge invalid exits.\n\n`sla_margin` should be tight enough:\n - to allow a successful mass exit in case of an [`unchallenged_exit` condition](#unchallenged_exit-condition).\n\n**NOTE** The `sla_margin` is usually much larger and unrelated to any margins that the Child Chain Server may wait before recognizing exits.\nSo, if everything is functioning correctly, the spending of exiting UTXOs is blocked _much_ earlier than the `sla_margin`.\nIn other words, `sla_margin` is usually picked to be ample (to avoid spurious mass exit prompts), and this doesn't impact the immediacy of the Child Chain Server's reaction to exits.\n\n### Standard Exits\n\n#### Actions that the Watcher should prompt\n\n1. If an exit is known to be invalid it should be challenged. The Watcher prompts by an `:invalid_exit` event.\n2. If an exit is invalidated with a transaction submitted *before* `start_eth_height + sla_margin` it must be challenged (`:invalid_exit` event)\n3. If an exit is invalidated with a transaction submitted *after* `start_eth_height + sla_margin` it must be challenged **AND** the Watcher prompts to exit. The Watcher prompts by both `:invalid_exit` and `:unchallenged_exit` events. Users should not deposit or spend\n4. If an exit is invalid and remains unchallenged *after* `start_eth_height + sla_margin` it must be challenged **AND** the Watcher prompts to exit. The Watcher prompts by both `:invalid_exit` and `:unchallenged_exit` events. Users should not deposit or spend.\n5. The `unchallenged_exit` event also covers the case where the invalid exit finalizes, causing an insolvent chain until [issue #1318 is solved](https://github.com/omgnetwork/elixir-omg/issues/1318).\n\nMore on the [`unchallenged_exit` condition](#unchallenged_exit-condition).\n\nThe occurrence of the `unchallenged_exit` condition is checked for on every child chain block being synced.\n\nAssumptions:\n  - the user's funds must be safe even if the user only syncs and validates the chain periodically (but not less frequently than required)\n  - the user needs to have the ability to spend their UTXOs at any time, thereby requiring more stringent validity checking of exits\n\n#### Implementation\n\n2. `ExitProcessor` pulls new start exit events from root chain contract logs, as soon as they're `exit_finality_margin` blocks old (~12 blocks)\n3. For every open exit request run `OMG.State.utxo_exists?` method\n    * if `true` -> noop,\n    * if `false` -> emit `:invalid_exit` event prompts to challenge\n    * if `false` and exit is older than `sla_margin` -> emit additionally an `:unchallenged_exit` event which promts mass exit\n4. Spend UTXOs in `OMG.State` on exit finalization\n    * if the spent UTXO is present at the moment, forget the exit from validation - this is a valid finalization\n    * if the spent UTXO is missing, keep on emitting `:unchallenged_exit` (until [issue #1318 is solved](https://github.com/omgnetwork/elixir-omg/issues/1318)) - this is an invalid finalization.\n5. `ExitProcessor` recognizes exits that are (as seen at the tip of the root chain) already gone, when pulled from old logs.\nThis prevents spurious event raising during syncing.\nThis is the current behavior (\"inactive on recognition\"), to be substituted by a more verbose one in [#1318](https://github.com/omgnetwork/elixir-omg/issues/1318)\n6. Checking the validation of exits is user's responsibility.\nThis is done by calling `/status.get` endpoint.\n\n#### `unchallenged_exit` condition\n\nThis section treats this particular condition in-depth and explains the rationale.\n\nUnchallenged exit events are reported in the `byzantine_events` in `/status.get`'s response, whenever there is _any_ exit, which is invalid and old.\n\"Old\" means that its respective challenge required _might be_ approaching scheduled finalization time, or just has been unchallenged for an unjustified amount of time.\n\nUnchallenged exits are signaled by either:\n - `unchallenged_exit` event for unchallenged invalid standard exit\n - `unchallenged_piggyback` event for unchallenged invalid piggyback on in-flight exit input or output\n - `unchallenged_non_canononical_ife` event for in-flight exit, considered canonical that has a known competitor\n\nThe action to take, when such condition is detected is to _exit all UTXOs_ held on the child chain.\nThe rationale is that we suspect that the chain is imminent to become invalid, because some funds that shouldn't be exiting are being allowed to exit.\nWe do not wait until it's \"too late\" and report _post factum_ - if we did, our mass exit could end up having too low a priority.\n\nAnother thing to explain here is that the Watcher will **stop getting new child chain blocks** whenever it finds itself in an unchallenged exit condition.\nThis stopping behavior is similar to as when an `invalid_block` condition is detected.\nThe reason for this is to:\n - protect the user from relying on a possibly corrupt or insecure state of the system (e.g. accepting funds that won't be exitable)\n - make the byzantine report loud and make the warning logged more visible\n - prevent a possible corruption of the internal state (this is more of an implementation detail, but worth to keep in mind), which could result if the exit finalized.\n\nIn short, if at any point when watcher realizes it's in the \"unsafe world\" it stops processing blocks.\n\nAn important thing to remember though, is that challenges keep on processing.\nIn particular, the root cause of the unchallenged exit condition, **might be gone** at one point, because the invalid exit got challenged.\n**In particular, it won't show up in the `byzantine_events` list, when queried from `/status.get`!**.\nHowever, by design, the Watcher won't resume getting new blocks without a manual restart; the process of \"coming back to validity\" is not supported.\nThis behavior is driven by the notion that if things go this bad, it's game over, so yanking the watcher back into \"safe world\" automatically hasn't been considered,\nfor simplicity's sake and to avoid resuming when one shouldn't by error.\n\nFrom the protocol point of view, the first moment `unchallenged_exit` is spotted, the user should have commenced their mass exit.\nThis is another reason, why resuming syncing is not currently supported.\n\n##### Notes on implementation\n\n1. We don't want to have any type of exit-related flags in `OMG.State`'s UTXOs\n2. The reason to wait `exit_finality_margin` is to not have a situation, where due to a reorg, an exit is tracked and then vanishes.\nIf we didn't handle that, it could grow old and at some point raise prompts to mass exit (`:unchallenged_exit`).\nAn alternative is to always check the current status of every exit, before taking action, but that might create excessive load on the Ethereum RPC and be quite complex\n\n### In-flight exits\n\nAll the above rules will apply analogically to in-flight exits.\nSee [MoreVP](./morevp.md) for specs and introduction to in-flight exits.\nInvalid attempts to do an in-flight related action prompt challenges.\nAbsence of challenges within the `sla_margin`, as well as invalid finalization, should result in client prompting to mass exit (to be implemented in [issue #1275](https://github.com/omgnetwork/elixir-omg/issues/1275)).\n`OMG.State` is modified on IFE finalization.\n"
  },
  {
    "path": "docs/fee_design.md",
    "content": "# Fee Exit Design\n\nThis document describes the design for fee exits. It starts with the requirements, and then describes the basic fee mechanism within our Plasma M(ore)VP design. \n\n## Requirement\n\n### Functional Requirement\n\n1. The operator can exit fees to an address the operator owns.\n2. Able to support fee exit for multiple transaction types where each transaction type can have different fee rules.\n3. Support the initial fee rule for *payment* transaction, which is a fixed amount in cents per transaction.\n4. More fee rules could be added later. Here are some examples:\n   1. as a percentage of ETH gas. \n   2. as a percentage of notional (aka transaction dollar amount).\n   3. fixed token price, floating with USD or other fiat.\n\n### Non-functional Requirement\n1. A Fee exit does not take more time than a normal exit to process.\n2. A Fee exit can batch-exit multiple fees collected in different transactions.\n\n### Out of Scope\nThe fee rules are enforced and implemented only on the child chain. While a POA Plasma network gives the operator the ability to censor transactions, users are protected by the Watcher which would report misbehaving operators that try to exit more fees than allowed in a recorded transaction.\n\n## General fee mechanism design\n\n### High level description\n\nChild chain operators implement the format and rules for fees on a transaction, not the contracts. This is the case as long as the Plasma network runs as a POA.\n\nTransaction fees are an implied behavior for a Payment Transaction. It is calculated by the difference between a transaction's sum of inputs and its sum of outputs. For instance:\n\nThe sum of inputs is 10 ETH\nThe sum of outputs is 9.9 ETH\nThe transaction fee is 0.1 ETH\n\nTo exit fees, an operator puts a special fee transaction into a plasma chain block. This allows the operator to spend the transaction fee output as a Payment transaction. By leveraging MoreVP, we only need a contract to represent the fee transaction type and can use the pre-existing exit game contract.\n\nA fee transaction is unique from a normal transaction in that:\n\n1. It does not need to consume any inputs. As the fee is implied (at least for the *payment* transactions), there is no output that consumes an input.\n2. Verification of transaction fees occur only on the Child Chain and Watcher.\n\nThe Plasma MoreVP security relies on the Watcher, not the contracts,  to verify the validity of a transaction fee. If an invalid transaction fee is mined, the Watcher will consider an operator as \"rogue\" and notify users to mass exit the network.\n\nThis decoupling of fee rule from smart contract gives the operator a more fine-tuned control on updating the fee rules.\n\n### Fee rules upgrade/change\n\nThe Child Chain sets the fee rules, while the Watcher informs users about the fee rules. The fee rules are enforced by the Child Chain during the transaction processing. Any transaction not following the fee rules is rejected. Note that the fee transaction verification part is not bound to the logic of the fee rules. Find more details on transaction verification in the design section.\n\nAs a result, fee rules updates are done by changing the logic of how the Child Chain service would accept/reject an incoming transaction and how Watcher/wallet update the logic to follow the new fee rule when generating transaction.\n\n### Fee for new transaction type\n\nIn order to allow fees for a new transaction type, one would need to define how the fee is collected in the transaction type. We would flavor the fee to be always collected in an implicit way as it has already been decided to collect fee implicitly for *payment* transactions. To collect fees explicitly, the exit game contract of such transaction type might need to make sure the fees are exited via the standard fee exit mechanism instead of directly exiting with the explicit fee transaction output.\n\nOnce the way to collect fee is designed and defined, whenever there is a new transaction coming in, the Child Chain and Watcher should add the fee amount to the storage that records the sum of fee.\n\n### Fee rule change within a transaction type\n\nThe Child chain service (the operator) needs to provide the fee rules to its clients. Clients can pass in the essential information such as address, token, transaction type, etc., and then the Child Chain can accept transactions that pay sufficient fees.\n\nThis gives the operator the ability to update fees in a traditional SaaS way. The operator can adjust fee rules at anytime with flexible control. It can be upgraded with a feature flag or via A/B testing.\n\n## Chosen Design: Generate fee transactions to summarize the fees of each block\n\nThe design proposes that the Child Chain service automatically generates fee transactions at the end of each block. The Child Chain and Watcher perform the fee transaction verifications at the block level. A block, apart from the existing transaction verification logic, is valid if:\n\n1. All fee transactions are included after all other types of transactions in that block. \n1. A fee transaction's output is the sum of the fees paid in a particular token by all transactions in that block.\n1. There must be at most one fee transaction for a token per block.\n1. There can be at most one fee transaction type per block block. (PS. as we extend the Plasma Framework, there can potentially be multiple transaction types that all count as \"fee tx type\")\n\nSince the transactions are automatically generated from the Child Chain service, there is no need to do an authentication check. Inclusion of the fee transactions are optional, however fees collected in omitted blocks are lost to claim.\n\n### Transaction design\n\nThe transaction would be of the following format:\n\n```\n        {\n                txType: xxx,\n                inputs: [],\n                outputs: [],\n                nonce: xxx,\n                metaData: xxx,\n        }\n```\n\nThe list of inputs for fee transactions is empty, and the list of outputs only contains one output.\n\nThis output follows the fungible token output format, which can be spent as an input for a *payment* transaction.\n\n```\n        {\n                outputType: xxx,\n                amount: xxx,\n                outputGuard: xxx,\n                token: xxx,\n        }\n```\nTo represent fees for multiple tokens, one would need to generate multiple fee transactions.\n\nThe last field, `nonce`, would be computed via `hash(blockNum, token)`. `blockNum` is the block number that the fee transaction is mined to. `token` is the token that is claimed in the fee transaction output. This combination promises the `nonce` to be unique, and thus promises the fee transaction to be unique.\n\nSince this fee transaction would have a specific transaction type (and also its outputs would have unique output types), we don’t need to worry about other transaction types that use the same mechanism (block number + token) to ensure uniqueness. Even if another transaction type, for instance, collides with the same outputs in the same block, they would end up with different transaction hash due to the transaction type difference. Transaction uniqueness is granted.\n\n### Fee transaction type extension\n\nThe current Plasma Framework design is immutable on the ability of spending an output type in another transaction type once the contracts have been deployed. So let's say we have *payment* v1 that can spend the fee claiming output. When we extend the framework with *payment* v2, we would need another fee output type that is able to be spent in *payment* v2. As a result, we would need fee transaction type 2 as well during the extension. (As only new transaction type can create new output type)\n\nGiven this, the block verification should limit the block to be existing with a singe fee transaction type per block for simplicity. No matter which fee transaction types (1 or 2), it should calculate the fee balance of all transaction within the block. The only difference is how the output can be spent.\n\nChild chain and Watcher could even further deprecate old fee transaction types afterward if not needed anymore. The logic change could be done by upgrading Child Chain service and Watcher together.\n\n## Adding the fee exit feature to the Plasma Framework\n\nThis section will discuss how we can add the fee exit feature to our Plasma Framework using the chosen design, assuming we are launching the network without fee exit feature at the beginning.\n\nSince the network would first be running with a *payment* transaction type which does not support having fee transaction type as input, to enable spending fee transaction into *payment* transaction, we would need a *payment* transaction v2 for that.\n\nAs a result, a high level steps of adding fee exit feature would be:\n\n1. Implement the contracts to enable spending Fee transaction in *payment* transaction V2\n2. Implement the Child Chain and Watcher logic to support new transaction types: fee transaction type and *payment* V2. Need to be able to check the correctness of the transactions, including special logic for fee transaction and the changes of block verification logics.\n3. Implement Child Chain to automatically generate fee transactions within each block.\n4. Makes sure that clients update to new Watchers\n5. Could turn on the auto fee generation feature once all clients update to the new Watcher\n6. Deploy the *paymentExitGame* for *payment* transaction v2 that has the ability to spend the fee transaction\n7. (Optional) update deposit verifier to use *payment* transaction v2 directly\n8. Wait 3 weeks for the new ExitGame contract to take effect\n9. Spend fee transaction in *payment* transaction v2 and exit.\n\n## Reference\n\n* Tetsuji blockchain fee design: https://github.com/omgnetwork/elixir-omg/blob/master/docs/tesuji_blockchain_design.md#fees\n* Scope of Five Guys\n* Fee exit high level discussion: https://github.com/omgnetwork/plasma-contracts/issues/165\n\n## FAQ\n\nQ1: Is it possible for the operator to start collecting fees now, but defer adding the new fee-exit ALD to some time in the future? \n\n> Yes, it should be possible in the abandoned design. But be aware pre-collecting fee meanings we would need to put extra effort to migrate DB for fee feature during production. For instance, we would need to calculate the sum of fees while accepting new transactions, which might impact sum of fees too.\n\n> In the chosen design, we can defer the fee-exit feature to the future but we would lose all pre-collected fees.\n\nQ2: How often does a fee exit get called?\n\n> This is for the operator to decide. We probably want to do this based upon finance requirements and risk management.\n\nQ3: Since smart contracts do not check the fee logic, how do we handle in-flight exits that do not follow the fee rules?\n\n> To be clear, it is about in-flight exit on other transaction types that are using MoreVP protocol instead of MVP, as in-flight exits are not possible for fee transactions. For other in-flight exit transactions, our current implementation would flag the inputs as spent directly. Those transactions would be overpaying on Ethereuem (assuming the gas to start the in-flight exit is larger than the fee we charge).\n\n> In the future, we are planning to include the IFE transaction into the block if not already there. We might only include the transactions that follows the fee rules to a block. If an IFE occurs that does not follow the fee rules, it can still be a valid in-flight exit and be processed.\n\n> See: https://github.com/omgnetwork/elixir-omg/issues/994\n\nQ4: Should we collect fees when spending the fee transaction's output to a *payment* transaction?\n\n> To keep the transaction processing code simple, and in order not to introduce artificial code, we can treat this *payment* transaction the same as any *payment* transaction which is spending regular inputs. Therefore, when *payment* transaction will consolidate several fee-outputs it will be fee-free as a merge-transaction. Otherwise, a fee needs to be paid, which will be collectable by another transaction.\n\nQ5: How should excessive fees paid be handled?\n\n> They should be collected/exited normally, thereby decoupling fee requirements from fee collection. We can add some sanity checks on client software to avoid excessive fees.\n\nQ6: Can we do 4 fee utxos as input (to *payment* transaction) and one *payment* utxo as output?\n\n> Yes. As each block would generate a new fee utxo, we, as the operator, would like to continuously merge all fee utxos to single *payment* UTXO.\n"
  },
  {
    "path": "docs/in_flight_exit_scenarios.md",
    "content": "# Simple In-flight Exit\n\nAlice sends tokens to Bob in transaction `tx` which has one input and 2 outputs, one to Bob and one back to herself as change. The block is withheld. \n\nAlice calls `watcher/status.get` and gets a response:\n\n```json\n{\n    \"version\": \"1\",\n    \"success\": true,\n    \"data\": {\n        \"last_validated_child_block_number\": 10000,\n        \"last_mined_child_block_timestamp\": 1535031020,\n        \"last_mined_child_block_number\": 11000,\n        \"eth_syncing\": true,\n        \"byzantine_events\": [\n            {\n                \"event\": \"block_withholding\",\n                \"details\": {\n                    \"blockhash\"  : \"DB32876CC6F...\",\n                    \"blocknum\"  : 10000,\n                }\n            }\n        ]\n    }\n}\n ```\nShe notices that the chain is byzantine and the transaction she just submitted was not included in a block (therefore it's an in-flight transaction)\nTODO - How does Alice know that her transaction hasn't been included? Does she have to store the details of every transaction she submits until it is put into a block?\n\nAlice starts an in-flight exit.\n\n#### 1. Get the exit data\n`/in_flight_exit.get_data` \n```json\n {\n    \"txbytes\": \"F3170101C0940000...\"\n }\n ```\n\n response:\n```json\n {\n    \"data\": {\n        \"txbytes\": \"F3170101C0940000...\",\n        \"sigs\": \"7C29FB8327F60BBFC62...\",\n        \"input_txs\" : [\n            \"F81891018080808...\",\n            \"2A0341808602A01...\"\n        ],\n        \"input_proofs\" : [\n             \"CEDB8B31D1E4C...\",\n             \"A67131D1E904C...\"\n        ]\n    }\n }\n ```\n\n#### 2. Start the IFE\n```\nRootChain.startInFlightExit(\n    response.data.txbytes, \n    response.data.input_txs, \n    response.data.input_proofs, \n    response.data.sigs, \n    {\"value\": inFlightExitBond}\n)\n```\n\n#### 3. Check status again \n```json\n{\n    \"version\": \"1\",\n    \"success\": true,\n    \"data\": {\n        \"last_validated_child_block_number\": 10000,\n        \"last_mined_child_block_timestamp\": 1535031020,\n        \"last_mined_child_block_number\": 11000,\n        \"eth_syncing\": true,\n        \"byzantine_events\": [\n            {\n                \"event\": \"block_withholding\",\n                \"details\": {\n                    \"blockhash\"  : \"DB32876CC6F...\",\n                    \"blocknum\"  : 10000,\n                }\n            },\n            {\n                \"event\": \"piggyback_available\",\n                \"details\": {\n                    \"txbytes\": \"F3170101C0940000...\",\n                    \"available_outputs\" : [\n                        {\"index\": 0, \"address\": \"0x7890...\"},\n                        {\"index\": 1, \"address\": \"0x1234...\"},\n                    ]\n                }\n            },\n        ],\n        \"in_flight_exits\": [\n            {\n                \"txhash\": \"230C450180808080...\",\n                \"txbytes\": \"F3170101C0940000...\",\n                \"eth_height\" : 615441,\n            }\n        ]\n    }\n}\n ```\n\n Alice sees that her in-flight exit is in progress and she can now piggyback her ouput. Note that as Alice is the sole owner of the inputs, she does not need to piggyback any input.\n\n#### 4. Piggyback the output\nThe second argument is `5` because she is piggybacking the second output.\n```\nRootChain.piggybackInFlightExit(\n    response.data.txbytes,\n    5, \n    {\"value\": piggybackBond}\n)\n```\n\nAfter finalization, if nobody challenges the exit, Alice will exit her output and get her `inFlightExitBond` and `piggybackBond` back.\n\n#### 5. Bob finds out that he can piggyback his output\nWhen Bob calls `watcher/status.get` he gets this response:\n\n```json\n{\n    \"version\": \"1\",\n    \"success\": true,\n    \"data\": {\n        \"last_validated_child_block_number\": 10000,\n        \"last_mined_child_block_timestamp\": 1535031020,\n        \"last_mined_child_block_number\": 11000,\n        \"eth_syncing\": true,\n        \"byzantine_events\": [\n            {\n                \"event\": \"block_withholding\",\n                \"details\": {\n                    \"blockhash\"  : \"DB32876CC6F...\",\n                    \"blocknum\"  : 10000,\n                }\n            },\n            {\n                \"event\": \"piggyback_available\",\n                \"details\": {\n                    \"txbytes\": \"F3170101C0940000...\",\n                    \"available_outputs\" : [\n                        {\"index\": 0, \"address\": \"0x7890...\"},\n                    ]\n                }\n            },\n        ],\n        \"in_flight_exits\": [\n            {\n                \"txhash\": \"230C450180808080...\",\n                \"txbytes\": \"F3170101C0940000...\",\n                \"eth_height\" : 615441,\n                \"piggybacked_outputs\" : [1]\n            }\n        ]\n    }\n}\n ```\n\nBecause Alice has already started an exit for the transaction there is a `piggyback_available` event indicating that `output[0]` (Bob's address) can be piggybacked.\n\n#### 6. Bob Piggybacks his output on the IFE\nThe second argument is `4` because Bob is piggybacking the first output.\n```\nRootChain.piggybackInFlightExit(\n    in_flight_exits[0].txbytes,\n    4, \n    {\"value\": piggybackBond}\n)\n```\n\nAfter finalization (if nobody challenges the exit) Bob will exit his output and get his `piggybackBond` back.\n\n\n# Challenge an IFE\nTo challenge an IFE we must attempt to prove that it is non-canonical by presenting a competing transaction that also spends one its inputs.\nIf the competing transaction has already been included in a block, then we must present its inclusion proof.\nImagine that Alice's transaction `tx1` in the previous example is a double spend - its `input0` was already spent as `input1` of another transaction `tx0`\n\nRequest `watcher/status.get`:\n\n```json\n{\n    \"version\": \"1\",\n    \"success\": true,\n    \"data\": {\n        \"last_validated_child_block_number\": 10000,\n        \"last_mined_child_block_timestamp\": 1535031020,\n        \"last_mined_child_block_number\": 11000,\n        \"eth_syncing\": true,\n        \"byzantine_events\": [\n            {\n                \"event\": \"block_withholding\",\n                \"details\": {\n                    \"blockhash\"  : \"DB32876CC6F...\",\n                    \"blocknum\"  : 10000,\n                }\n            },\n            {\n                \"event\": \"noncanonical_ife\",\n                \"details\": {\n                    \"txbytes\": \"F3170101C0940000...\"\n                }\n            },\n        ],\n        \"in_flight_exits\": [\n            {\n                \"txhash\": \"230C450180808080...\",\n                \"txbytes\": \"F3170101C0940000...\",\n                \"eth_height\" : 615441,\n                \"piggybacked_inputs\" : [0],\n                \"piggybacked_outputs\" : [0, 1],\n            }\n        ]\n    }\n}\n ```\n\n#### 1. Get the competing transaction and its inclusion proof (if available).\n`/in_flight_exit.get_competitor` \n```json\n{\n    \"txbytes\": \"F3170101C0940000...\"\n}\n```\n\nresponse:\n```json\n{\n    \"version\": \"1\",\n    \"success\": true,\n    \"data\": {\n        \"in_flight_txbytes\": \"F847010180808080940000...\",\n        \"in_flight_input_index\": 0,\n        \"competing_txbytes\": \"F317010180808080940000...\",\n        \"competing_input_index\": 1,\n        \"competing_sig\": \"9A23010180808080940000...\",\n        \"competing_tx_pos\": 26000003920000,\n        \"competing_proof\": \"004C010180808080940000...\"\n    }\n}\n```\nNote that if the competing transaction has _not_ been included in a block then its inclusion proof and tx position will not be available. In this case, you should pass \"\" and 0 to `RootChain.challengeInFlightExitNotCanonical()`\n\n#### 2. Challenge the IFE with an included competitor\n```\ntx0_data = response.data\nRootChain.challengeInFlightExitNotCanonical(\n    in_flight_txbytes, \n    in_flight_input_index, \n    competing_txbytes, \n    competing_input_index,\n    competing_tx_pos,\n    competing_proof, \n    competing_sig\n)\n```\n\n# Respond to an IFE challenge\nTo respond to a challenge to an IFE, we need to show that the transaction _is_ included. This situation can arise if the user that started the exit did not see the transaction in a block, but subsequently he or another user _did_ see the transaction being put into a block.\n\n`/watcher/status.get` response will contain:\n```\n    \"byzantine_events\": [\n        {\n            \"event\": \"invalid_ife_challenge\",\n            \"details\": {\n                \"txbytes\": \"F3170101C0940000...\"\n            }\n        }\n    ]\n```\n\n#### 1. Get the in-flight transaction's inclusion proof.\n\n`/in_flight_exit.prove_canonical` \n```json\n{\n    \"txbytes\": \"F3170101C0940000...\"\n}\n```\n\nresponse:\n```json\n{\n    \"version\": \"1\",\n    \"success\": true,\n    \"data\": {\n        \"in_flight_txbytes\": \"F847010180808080940000...\",\n        \"in_flight_tx_pos\": 26000003920000,\n        \"in_flight_proof\": \"004C010180808080940000...\"\n    }\n}\n```\n\n#### 2. Respond to an IFE challenge\n```\nRootChain.challengeInFlightExitNotCanonical(\n    in_flight_txbytes, \n    in_flight_tx_pos,\n    in_flight_proof, \n)\n```\nIf this transaction is the oldest competitor then it is canonical and the IFE succeeds - Bob exits his output.\n\n# Challenging a Piggybacked input\nTo challenge a piggybacked input we must present a different transaction that spends that input.\n\n`/watcher/status.get` response will contain:\n```\n    \"byzantine_events\": [\n        {\n            \"event\": \"invalid_piggyback\",\n            \"details\": {\n                \"txbytes\": \"F3170101C0940000...\",\n                \"inputs\": [1]\n            }\n        }\n    ]\n```\n\n#### 1. Get the transaction that challenges the input\n\n`/in_flight_exit.get_input_challenge_data` \n```json\n{\n    \"txbytes\": \"F3170101C0940000...\",\n    \"input_index\": 1\n}\n```\n\nresponse:\n```json\n{\n    \"version\": \"1\",\n    \"success\": true,\n    \"data\": {\n        \"in_flight_txbytes\": \"F3170101C0940000...\",\n        \"in_flight_input_index\": 1,\n        \"spending_txbytes\": \"F847010180808080940000...\",\n        \"spending_input_index\": 1,\n        \"spending_sig\": \"9A23010180808080940000...\"\n    }\n}\n```\n\n#### 2. Challenge the input\n```\nRootChain.challengeInFlightExitInputSpent(\n    in_flight_tx.txbytes, \n    in_flight_tx.input_index,\n    spending_tx.txbytes, \n    spending_tx.input_index,\n    spending_tx.sigs\n)\n```\n\n# Challenging a Piggybacked output\nTo challenge a piggybacked output we must present a transaction that spends that output. The in-flight transaction must have been put into a block, but the spending transaction does _not_ need to be in a block.\n\n`/watcher/status.get` response will contain:\n```\n    \"byzantine_events\": [\n        {\n            \"event\": \"invalid_piggyback\",\n            \"details\": {\n                \"txbytes\": \"F3170101C0940000...\",\n                \"outputs\": [0]\n            }\n        }\n    ]\n```\n\n#### 1. Get the output's proof of inclusion\n`/in_flight_exit.get_output_challenge_data` \n```json\n{\n    \"txbytes\": \"F3170101C0940000...\",\n    \"output_index\": 0\n}\n```\n\nresponse:\n```json\n{\n    \"version\": \"1\",\n    \"success\": true,\n    \"data\": {\n        \"in_flight_txbytes\": \"F3170101C0940000...\",\n        \"in_flight_output_pos\": 21000634002,\n        \"in_flight_proof\": \"03F451067A805540000...\",\n        \"spending_txbytes\": \"F847010180808080940000...\",\n        \"spending_input_index\": 1,\n        \"spending_sig\": \"9A23010180808080940000...\"\n    }\n}\n```\n\n#### 2. Challenge the output\n```\nRootChain.challengeInFlightExitOutputSpent(\n    in_flight_tx.txbytes, \n    in_flight_tx.output_pos,\n    in_flight_tx.proof,\n    spending_tx.txbytes, \n    spending_tx.input_index,\n    spending_tx.sigs\n)\n```\n"
  },
  {
    "path": "docs/install.md",
    "content": "# Full Installation\n\n**NOTE**: Currently the child chain server and watcher are bundled within a single umbrella app.\n\nOnly **Linux** and **OSX** platforms are supported now. These instructions have been tested on a fresh Linode 2048 instance with Ubuntu 16.04.\n\n## Prerequisites\n* **Erlang OTP** `>=22` (check with `elixir --version`)\n* **Elixir** `=1.10.*` (check with `elixir --version`)\n\n## Install prerequisite packages\nIt will install common development tools, geth and postgres.\n\n```\nsh bin/setup\n```\n\n## Install Erlang and Elixir\n\nAdd the Erlang Solutions repo and install\n```\nwget https://packages.erlang-solutions.com/erlang-solutions_2.0_all.deb \\\n&& sudo apt install ./erlang-solutions_2.0_all.deb \\\n&& rm ./erlang-solutions_2.0_all.deb\nsudo apt-get update\nsudo apt-get install esl-erlang=1:22.3.1-1 elixir=1.10.2-1\nsudo apt-get install -y erlang-os-mon erlang-parsetools erlang-tools\n```\n\n## Install hex and rebar\n```\nmix do local.hex --force, local.rebar --force\n```\n\n## Clone repo\n```\ngit clone https://github.com/omgnetwork/elixir-omg\n```\n\n## Build\n```\ncd elixir-omg\nmix deps.get\nmix compile\n```\n\n## Check this works!\nFor a quick test (with no integration tests)\n```\nmake init_test\nmix test\n```\n\nTo run integration tests (requires **not** having `geth` running in the background):\n```\nmake init_test\nmix test   --only integration\n```\n"
  },
  {
    "path": "docs/morevp.md",
    "content": "# More Viable Plasma\n\nThis document is based on the original [More Viable Plasma post](https://ethresear.ch/t/more-viable-plasma/2160/49) on ethresearch.\nThis document has been moved from the document created in [the research repo](https://github.com/omgnetwork/research/pull/44).\nSee there for the original work and discussion done on this design.\n\n## Introduction\n\n[Minimal Viable Plasma](https://ethresear.ch/t/minimal-viable-plasma/426) (“Plasma MVP”) describes a simple specification for a UTXO-based Plasma chain.\nA key component of the Plasma MVP design is a protocol for “exits,” whereby a user may withdraw back to the root chain any funds available to them on the Plasma chain.\nThe protocol presented in the MVP specification requires users sign two signatures for every transaction.\nConcerns over the poor user experience presented by this requirement motivated the search for an alternative exit protocol.\n\nIn this document, we describe More Viable Plasma (“MoreVP”), a modification to the Plasma MVP exit protocol that removes the need for a second signature and generally improves user experience.\nThe MoreVP exit protocol ensures the security of assets for clients correctly following the protocol.\nWe initially present the most basic form of the MoreVP protocol and provide intuition towards its correctness.\nWe also formalize certain requirements for Plasma MVP exit protocols and provide a proof that this protocol satisfies these requirements in the appendix.\n\nAn optimized version of the protocol is presented to account for restrictions of the Ethereum Virtual Machine.\nWe further optimize on the observation that the MoreVP exit protocol is only necessary for transactions that are in-flight when a Plasma chain becomes byzantine.\n\nWe note the existence of certain attack vectors in the protocol, but find that most of these vectors can be largely mitigated and isolated to a relatively small attack surface.\nThese attack vectors and their mitigations are described in detail.\nAlthough we conclude that the design is safe under certain reasonable assumptions about user behavior, some points are highlighted and earmarked for future consideration.\n\nOverall, we find that the MoreVP exit protocol is a significant improvement over the original Plasma MVP exit protocol.\nWe can further combine several optimizations to enhance user experience and reduce costs for users.\nFuture work will focus on decreasing implementation complexity of the design and minimizing contract gas usage.\n\n\n## Basic Mechanism\n\nIn this section, we specify the basic MoreVP exit mechanism and give an intuitive argument toward its correctness.\nA formal treatment of the protocol is presented in the appendix.\n\n\n### Definitions\n\n#### Deposit\n\nA deposit creates a new output on the Plasma chain.\nAlthough deposits are typically represented as transactions that spend some \"special\" input, we do not allow deposits to exit via the MoreVP exit protocol.\nInstead, deposits can be safely exited with the Plasma MVP exit protocol.\n\n\n#### Spend Transaction\n\nA spend transaction is any transaction that spends a UTXO already present on the Plasma chain.\n\n\n#### In-flight Transaction\n\nA transaction is considered to be “in-flight” if it has been broadcast but has not yet been included in the Plasma chain.\nA transaction may be in-flight from the perspective of an individual user if that user does not have access to the block in which the transaction is included.\n\n\n#### Competing Transaction, Competitors\n\nTwo transactions are “competing” if they share at least one input.\nThe “competitors” to a transaction is the set of all transactions that are competing with the transaction in question, including the transaction itself.\n\n\n#### Canonical Transaction\n\nA transaction is “canonical” if none of its inputs were previously spent in any other transaction, i.e. that the transaction is the oldest among all its competitors.\nThe definition of “previously spent” depends on whether or not the transaction in question is included in the Plasma chain.\n\nThe position of a transaction in the chain is determined by the tuple (block number, transaction index).\nIf the transaction was included in the chain, an input to that transaction would be considered previously spent if another transaction also spending the input was included in the chain *before* the transaction in question, decided by transaction position.\nIf the transaction was not included in the chain, an input to that transaction would be considered previously spent if another transaction also spending the input is *known to exist*.\n\nNote that in this second case it’s unimportant whether or not the other transaction is included in the chain.\nIf the other transaction is included in the chain, then the other transaction is clearly included before the transaction in question.\nIf the other transaction is not included in the chain, then we can’t tell which transaction “came first” and therefore simply say that neither is canonical.\n\n\n#### Exitable Transaction\n\nA spend transaction can be called “exitable” if the transaction is correctly formed (e.g. more input value than output value, inputs older than outputs, proper structure) and is properly signed by the owners of the transaction’s inputs.\nIf a transaction is “exitable,” then a user may attempt to start an exit that references the transaction.\n\n\n#### Valid Transaction\n\nA spend transaction is “valid” if and only if it is exitable, canonical, and only stems from valid transactions (i.e. all transactions in the history are also valid transactions).\nNote that a transaction would therefore be considered invalid if even a single invalid transaction is present in its history.\nAn exitable transaction is not necessarily a valid transaction, but all valid transactions are, by definition, exitable.\nOur exit mechanism ensures that all outputs created by valid transactions can process before any output created by an invalid transaction.\n\n\n### Desired Exit Mechanism\n\nThe MoreVP exit protocol allows the owners of both inputs and outputs to transactions to attempt an exit.\nWe want to design a mechanism that allows inputs and outputs to be withdrawn under the following conditions.\n\nThe owner of an input `in` to a transaction `tx` must prove that:\n1. `tx` is exitable.\n2. `tx` is non-canonical.\n3. `in` is not spent in any transaction other than `tx`.\n\nThe owner of an output `out` to a transaction `tx` must prove that:\n1. `tx` is exitable.\n2. `tx` is canonical.\n3. `out` is not spent.\n\nBecause a transaction either is or is not canonical, only the transaction's inputs or outputs, and not both, may exit.\n\n\n#### Priority\n\nThe above game correctly selects the inputs or outputs that are eligible to exit.\nHowever, invalid transactions can still be exitable.\nWe therefore need to enforce an ordering on exits to ensure that all outputs created by valid transactions will be paid out before any output created by an invalid transaction.\nWe do this by ordering every exit by the position of the *youngest input* to the transaction referenced in each exit, regardless of whether an input or an output is being exited.\n\n\n## Exit Protocol\n\n### Motivation\n\nThe basic exit mechanism described above guarantees that correctly behaving users will always be able to withdraw any funds they hold on the Plasma chain.\nHowever, we avoided describing how the users actually prove the statements they’re required to prove.\nThis section presents a more detailed specification for the exit protocol.\nThe MoreVP mechanism is designed to be deployed to Ethereum and, as a result, some particulars of this specification take into account limitations of the EVM.\n\nAdditionally, it's important to note that the MoreVP exit protocol is not necessary in all cases.\nWe can use the Plasma MVP exit protocol without confirmation signatures for any transaction included before an invalid (or, in the case of withheld blocks, potentially invalid) transaction.\nWe therefore only need to make use of the MoreVP protocol for the set transactions that are in-flight when a Plasma chain becomes byzantine.\n\nThe MoreVP protocol guarantees that if transaction is exitable then either the unspent inputs or unspent outputs can be withdrawn.\nWhether the inputs or outputs can be withdrawn depends on if the transaction is canonical.\nHowever, in the particular situation in which MoreVP exits are required, users may not be aware that an in-flight transaction is actually non-canonical.\nThis can occur if the owner of an input to an in-flight transaction is malicious and has signed a second transaction spending the same input.\n\nTo account for this problem, we allow exits to be ambiguous about canonicity.\nUsers can start MoreVP exits with the *assumption* that the referenced transaction is canonical.\nOther owners of inputs or outputs to the transaction can then “piggyback\" the exit.\nWe add cryptoeconomic mechanisms that determine whether the transaction is canonical and which of the inputs or outputs are unspent.\nThe end result is that we can correctly determine which inputs or outputs should be paid out.\n\n\n### MoreVP Exit Protocol Specification\n\n#### Timeline\n\nThe MoreVP exit protocol makes use of a “challenge-response” mechanism, whereby users can submit a challenge but are subject to a response that invalidates the challenge.\nTo give users enough time to respond to a challenge, the exit process is split into two “periods.” When challenges are subject to a response, we require that the challenges be submitted before the end of the first exit period and that responses be submitted before the end of the second.\nWe define each period to have a length of half the minimum finalization period (`MFP`).\nCurrently, `MFP` is set to 7 days, so each period has a length of 3.5 days.\nWatchers must validate the chain at least once every period (`MFP/2`).\n\n\n#### Starting the Exit\n\n\nAny user may initiate an exit by presenting a spend transaction and proving that the transaction is exitable.\nThe user must submit a bond, `exit bond`, for starting this action.\nThis bond is later used to cover the cost for other users to publish statements about the canonicity of the transaction in question.\n\nWe provide several possible mechanisms that allow a user to prove a transaction is exitable.\nTwo ways in which spend transactions can be proven exitable are as follows:\n1. The user may present `tx` along with each of the `input_tx1, input_tx2, ... , input_txn` that created the inputs to the transaction, a Merkle proof of inclusion for each `input_tx`, and a signature over `tx` from the `newowner` of each `input_tx`.\nThe contract can then validate that these transactions are the correct ones, that they were included in the chain, that the signatures are correct, and that the exiting transaction is correctly formed.\nThis proves the exitability of the transaction.\n2. The user may present the transaction along signatures the user claims to be valid.\nThe contract can validate that the exiting transaction is correctly formed.\nAnother user can challenge one of these signatures by presenting some transaction that created an input such that the true input owner did not sign the signature.\nIn this case, the exit would be blocked entirely and the challenging user would receive `exit bond`.\n\nOption (1) (chosen in the implementation) checks that a transaction is exitable when the exit is started.\nThis has lower communication cost and complexity but higher up-front gas cost.\nThis option also ensures that only a single exit on any given transaction can exist at any point in time.\nOption (2) allows a user to assert that a transaction is exitable, but leaves the proof to a challenge-response game.\nThis is cheaper up-front but adds complexity.\nThis option must permit multiple exits on the same transaction, as some exits may provide invalid signatures.\n\n<!-- TODO: Groom this section once we decide on (1) or (2) -->\n\nThese are not the only possible mechanisms that prove a transaction is exitable.\nThere may be further ways to optimize these two options.\n\nWe still need to provide a deterministic ordering of exits by some priority.\nMoreVP exits are given a priority based on the position in the Plasma chain of the most recently included (youngest) input to that transaction.\nUnlike the MVP protocol, we give each input and output to a transaction the same priority.\nThis should be implemented by inserting a single “exit” object into a priority queue of exits and tracking a list of inputs or outputs to be paid out once the exit is processed.\n\n\n#### Proving Canonicity\n\nWhether unspent inputs or unspent outputs are paid out in an exit depends on the canonicity of the referenced transaction, independent of any piggybacking by other users.\nUnfortunately it’s too expensive to directly prove that a transaction is or is not canonical.\nInstead, we assume that the referenced transaction is canonical by default and allow a series of challenges and responses to determine the true canonicity of the transaction.\n\nThe process of determining canonicity involves a challenge-response game.\nIn the first period of the exit, any user may reveal a competing transaction that potentially makes the exiting transaction non-canonical.\nThis competing transaction must be exitable and must share an input with the exiting transaction, but does not have to be included in the Plasma chain.\nMultiple competing transactions can be revealed during this period, but only the oldest presented transaction is considered for the purposes of a response.\n\nIf any transactions have been presented during the first period, any other user can respond to the challenge by proving that the exiting transaction is actually included in the chain before the oldest presented competing transaction.\nIf this response is given before the end of the second period, then the exiting transaction is determined to be canonical and the responder receives the `exit bond` placed by the user who started the exit.\nOtherwise, the exiting transaction is determined to be non-canonical and the challenger receives `exit bond`.\n\nNote that this challenge means it’s possible for an honest user to lose `exit bond` as they might not be aware their transaction is non-canonical.\nWe address this attack vector and several mitigations in detail later.\n\nIt might also be the case that in-flight exit is opened where some of the inputs where referenced in standard exit and those standard exits were finalized.\nIn such case in-flight exit is flagged as non-canonical and further canonicity game can't change its status.\n\n<!-- TODO: Include image of canonicity \"state machine\" -->\n\n\n#### Piggybacking an Exit\n\nAs noted earlier, it’s possible that some participants in a transaction may not be aware that the transaction is non-canonical.\nOwners of both inputs and outputs to a transaction may want to start an exit in the case that they would receive the funds from the exit.\nHowever, we want to avoid the gas cost of repeatedly publishing and proving statements about the same transaction.\nWe therefore allow owners of inputs or outputs to a transaction to piggyback an existing exit that references the transaction.\n\nUsers must piggyback an exit within the first period.\nTo piggyback an exit, an input or output owner places a bond, `piggyback bond`.\nThis bond is used to cover the cost of challenges that show the input or output is spent.\nA successful challenge blocks the specified input or output from exiting.\nThese challenges must be presented before the end of the second period.\n\nNote that it isn’t mandatory to piggyback an exit.\nUsers who choose not to piggyback an exit are choosing not to attempt a withdrawal of their funds.\nIf the chain is byzantine, not piggybacking could potentially mean loss of funds.\n\n\n#### Processing Exits\n\nAn exit can be processed after the second period.\nIf the referenced transaction was determined to be canonical, all piggybacked outputs still unchallenged are paid out.\nIf the referenced transaction was determined to be non-canonical, all piggybacked inputs still unchallenged are paid out.\nAny inputs or outputs paid out should be saved in the contract so that any future exit referencing the same inputs or outputs can be challenged.\n\n\n#### Combining with Plasma MVP Exit Protocol\n\nThe MoreVP protocol can be combined with the Plasma MVP protocol in a way that simultaneously preserves the integrity of exits and minimizes gas cost.\nAlthough the two protocols use different determinations for exit priority, total ordering on exits is still needed.\nTherefore, every exit, no matter the protocol used, must be included in the same priority queue for processing.\nHonest user which enjoys data availability should be able to ignore in-flight exits that involve their outputs.\nOwners of outputs on the Plasma chain should be able to start an exit via either mechanism, but not both.\nTo guarantee that money can't be double-spend via those two mechanisms, two approaches are possible.\n\n##### Chosen solution\nThis approach minimizes complexity of interactive games while negatively affecting gas cost of a happy path.\nContract needs to check if other type of exit exists for particular output when standard exit is being submitted and it checks if standard exit is in progress / was finalized when in-flight exit is being added.\nIn first case new exit is blocked.\nIn second case - in-flight exit is marked as one which can be exited only from inputs, and problematic inputs are marked as spent for piggybacking purposes.\nTo make such checks possible, both types of exits need to use transaction hash as an exit id.\nNo additional interactive games arise from the fact of coexistence of MVP and MoreVP protocols.\n\n##### Alternative solution, to be implemented later\nTo reduce gas costs for honest participants, new types of challenges needs to be introduced.\nPiggybacks on outputs should be challenged by standard exits and vice-versa.\nStandard exits on UTXO seen as the input of a in-flight tx exit can be challenged using tx body.\nCanonicity of in-flight exit can be removed by pointing contract to finalized standard exit from in-flight exit inputs, marking particular input as spent.\n\nFor details, [see here](./standard_vs_in_flight_exits_interaction.md).\n\n\n\n## Alice-Bob Scenarios\n\n### Alice & Bob are honest and cooperating:\n\n1. Alice spends `UTXO1` in `TX1` to Bob, creating `UTXO2`.\n2. `TX1` is in-flight.\n3. Operator begins withholding blocks while `TX1` is still in-flight.\nNeither Alice nor Bob know if the transaction has been included in a block.\n4. Someone with access to `TX1` (Alice, Bob, or otherwise) starts an exit referencing `TX1` and places `exit bond`.\n5. Bob piggybacks onto the exit and places `piggyback bond`.\n6. Alice is honest, so she hasn’t spent `UTXO1` in any transaction other than `TX1`.\n7. After period 2, Bob receives the value of `UTXO2`.\nAll bonds are refunded.\n\n\n### Mallory tries to exit a spent output:\n\n1. Alice spends `UTXO1` in `TX1` to Mallory, creating `UTXO2`.\n2. `TX1` is included in block `N`.\n3. Mallory spends `UTXO2` in `TX2`.\n4. Mallory starts an exit referencing `TX1` and places `exit bond`.\n5. Mallory piggybacks onto the exit and places `piggyback bond`.\n6. In period 2, someone reveals `TX2` spending `UTXO2`.\nThis challenger receives Mallory’s `piggyback bond`.\n7. Alice is honest, so she hasn’t spent `UTXO1` in any transaction other than `TX1`.\n8. After period 2, Mallory’s `exit bond` is refunded, no one exits any UTXOs.\n\n\n### Mallory double spends her input:\n\n1. Mallory spends `UTXO1` in `TX1` to Bob, creating `UTXO2`.\n2. `TX1` is in-flight.\n3. Operator begins withholding blocks while `TX1` is still in-flight.\nNeither Mallory nor Bob know if the transaction has been included in a block.\n4. Mallory spends `UTXO1` in `TX2`.\n5. `TX2` is included in a withheld block.\n`TX1` is not included in a block.\n6. Bob starts an exit referencing `TX1` and places `exit bond`.\n7. Bob piggybacks onto the exit and places `piggyback bond`.\n8. In period 1, someone challenges the canonicity of `TX1` by revealing `TX2`.\n9. No one is able to respond to the challenge in period 2, so `TX1` is determined to be non-canonical.\n10. After period 2, Bob’s `piggyback bond` is refunded, no one exits any UTXOs.\nThe challenger receives Bob’s `exit bond`.\n\n\n### Mallory spends her input again later:\n\n1. Mallory spends `UTXO1` in `TX1` to Bob, creating `UTXO2`.\n2. `TX1` is included in block `N`.\n3. Mallory spends `UTXO1` in `TX2`.\n4. `TX2` is included in block `N+M`.\n5. Mallory starts an exit referencing `TX1` and places `exit bond`.\n6. In period 1, someone challenges the canonicity of `TX1` by revealing `TX2`.\n7. In period 2, someone responds to the challenge by proving that `TX1` was included before `TX2`.\n8. After period 2, the user who responded to the challenge receives Mallory’s `exit bond`, no one exits any UTXOs.\n\n\n### Mallory attempts to exit a spent input:\n\n1. Mallory spends `UTXO1` and `UTXO2` in `TX1`.\n2. Mallory spends `UTXO1` in `TX2`.\n3. `TX1` and `TX2` are in-flight.\n4. Mallory starts an exit referencing `TX1` and places `exit bond`.\n5. Mallory starts an exit referencing `TX2` and places `exit bond`.\n6. In period 1 of the exit for `TX1`, someone challenges the canonicity of `TX1` by revealing `TX2`.\n7. In period 1 of the exit for `TX2`, someone challenges the canonicity of `TX2` by revealing `TX1`.\n8. After period 2 of the exit for `TX1`, the challenger receives `exit bond`, no one exits any UTXOs.\n9. After period 2 of the exit for `TX2`, the challenger receives `exit bond`, no one exits any UTXOs.\n\n\n### Operator tries to steal funds from an included transaction\n\n1. Alice spends `UTXO1` in `TX1` to Bob, creating `UTXO2`.\n2. `TX1` is included in (valid) block `N`.\n3. Operator creates invalid deposit, creating `UTXO3`.\n4. Operator spends `UTXO3` in `TX3`, creating `UTXO4`.\n5. Operator starts an exit referencing `TX3` and places `exit bond`.\n6. Operator piggybacks onto the exit and places `piggyback bond`.\n7. Bob starts a *standard* exit for `UTXO2`.\n8. Operator’s exit will have priority of position of `UTXO3`.\nBob’s exit will have priority of position of `UTXO2`.\n9. Bob receives the value of `UTXO2` first, Operator receives the value of `UTXO4` second (ideally contract is empty by this point).\nAll bonds are refunded.\n\n### Operator tries to steal funds from an in-flight transaction.\n\n1. Alice spends `UTXO1` in `TX1` to Bob, creating `UTXO2`.\n2. `TX1` is in-flight.\n3. Operator creates invalid deposit, creating `UTXO3`.\n4. Operator spends `UTXO3` in `TX3`, creating `UTXO4`.\n5. Operator starts an exit referencing `TX3` and places `exit bond`.\n6. Operator piggybacks onto the exit and places `piggyback bond`.\n7. Bob starts an exit referencing `TX1` and places `exit bond`.\n8. Bob piggybacks onto the exit and places `piggyback bond`.\n9. Alice is honest, so she hasn’t spent `UTXO1` in any transaction other than `TX1`.\n10. Operator’s exit will have priority of position of `UTXO3`.\nBob’s exit will have priority of position of `UTXO1`.\n11. Bob receives the value of `UTXO2` first, Operator receives the value of `UTXO4` second (ideally contract is empty by this point).\nAll bonds are refunded.\n\n### Operator tries to steal funds from a multi-input in-flight transaction.\n\n1. Alice spends `UTXO1a`, Malory spends `UTXO1m` in `TX1` to Bob, creating `UTXO2`.\n2. `TX1` is in-flight.\n3. Operator creates invalid deposit, creating `UTXO3`.\n4. Operator spends `UTXO3` in `TX3`, creating `UTXO4`.\n5. Operator starts an exit referencing `TX3` and places `exit bond`.\n6. Operator piggybacks onto the exit and places `piggyback bond`\n7. Malory starts an exit referencing `TX1` and places `exit bond`.\n8. Bob piggybacks onto the exit and places `piggyback bond`.\n9. Alice piggybacks onto the exit and places `piggyback bond`.\n9. Mallory double-spends `UTXO1m` in `TX2` and broadcasts.\n9. Operator includes `TX2` and submits as a competitor to `TX1` rendering it non-canonical\n10. Operator's exit of `TX3` will have priority of position of `UTXO3`.\nAlice-Mallory exit will have priority of position of `UTXO1`.\n11. Alice receives the value of `UTXO1a` first, Operator receives the value of `UTXO4` second (ideally contract is empty by this point).\nBob receives nothing.\nMallory's `exit bond` goes to the Operator.\nMallory's `TX2` is canonical and owners of outputs can attempt to exit them.\n\n### Honest receiver should not start in-flight exits\n\nAn honest user obtaining knowledge about an in-flight transaction **crediting** them **should not** start an exit, otherwise risks having their exit bond slashed.\n\nThe out-of-band process in such event should always put the burden of starting in-flight exits **on the sender**.\n\nThe following scenario demonstrates an attack that is **possible if receivers are too eager to start in-flight exits**:\n1. Mallory spends `UTXO1` in `TX1` to Bob, creating `UTXO2`.\n2. `TX1` is in-flight.\n3. Operator begins withholding blocks while `TX1` is still in-flight.\n4. Bob **eaglerly** starts an exit referencing `TX1` and places `exit bond`.\n5. Mallory spends `UTXO1` in `TX2`.\n6. In period 1 of the exit for `TX1`, Mallory challenges the canonicity of `TX1` by revealing `TX2`.\n7. No one is able to respond to the challenge in period 2, so `TX1` is determined to be non-canonical.\n8. After period 2, Mallory receives Bob’s `exit bond`, no one exits any UTXOs.\n\nMallory has therefore caused Bob to lose `exit bond`, even though Bob was acting honestly.\n\n### Attack Vectors and Mitigations\n\n#### Honest Exit Bond Slashing\n\nIt’s possible for an honest user to start an exit and have their exit bond slashed.\nThis can occur if one of the inputs to a transaction is malicious and signs a second transaction spending the same input.\n\nThe following scenario demonstrates this attack:\n1. Mallory spends `UTXO1m` and Alice spends `UTXO1a` in `TX1` to Bob, creating `UTXO2`.\n2. `TX1` is in-flight.\n3. Operator begins withholding blocks while `TX1` is still in-flight.\n4. Alice starts an exit referencing `TX1` and places `exit bond`.\n4. Alice piggybacks onto the exit and places `piggyback bond`.\n5. Mallory spends `UTXO1m` in `TX2`.\n6. In period 1 of the exit for `TX1`, Mallory challenges the canonicity of `TX1` by revealing `TX2`.\n7. No one is able to respond to the challenge in period 2, so `TX1` is determined to be non-canonical.\n8. After period 2, Mallory receives Alice's `exit bond`, Alice receives `UTXO1a` and `piggyback bond`.\n\nMallory has therefore caused Alice to lose `exit bond`, even though Alice was acting honestly.\nWe want to mitigate the impact of this attack as much as possible so that this does not prevent users from receiving funds.\n\n**NOTE** in the scenarios where Mallory double-spends her input, she doesn't get to successfully piggyback that, unless the operator includes and makes canonical her double-spending transaction.\nAs a result she might lose more than she's getting from stolen `exit bonds`.\n\n#### Honest transaction retries attack\n\nRetrying a transaction that has failed for a trivial reason is not safe under MoreVP.\n\nScenario is:\n1. Honest Alice creates/signs/submits a transaction `tx1`\n2. This fails, either loudly (error response from child chain server) or quietly (no response) - `tx1` doesn't get included in a block\n3. Alice is forced to in-flight exit, even if she just made a trivial mistake (e.g. incorrect fee)\n4. If instead Alice retries with amended `tx2`, then she opens an attack on her funds:\n    - if the child chain is nice, `tx2` will get included in a valid, non-withheld block, all is good\n    - if the child chain decides to go rogue, Alice is left defenseless, because she double-spent her input, i.e. she can't in-flight exit neither `tx1` nor `tx2` anymore\n\nSee [Timeouts section](#Timeouts) for discussion on one possible mitigation.\nHowever, due to uncertainty of timeouts in MoreVP, other mitigations for the retry problem might be necessary.\n\n##### Mitigations for Honest Exit Bond Slashing\n\n###### Bond Sharing\n\nOne way to partially mitigate this attack is for each user who piggybacks to cover some portion of `exit bond`.\nThis cuts the per-person value of `exit bond` proportionally to the number of users who have piggybacked.\nNote that this is a stronger mitigation the more users are piggybacking on the exit and would not have any impact if only a single user starts the exit/piggybacks.\n\n\n###### Small Exit Bond\n\nThe result of the above attack is that users may not exit from an in-flight transaction if the gas cost of exiting plus the value of `exit bond` is greater than the value of their input or output.\nWe can reduce the impact of this attack by minimizing the gas cost of exiting and the value of `exit bond`.\nGas cost should be highly optimized in any case, so the value of `exit bond` is of more importance.\n\n`exit bond` is necessary to incentivize challenges.\nHowever, we believe that challenges can be sufficiently incentivized if `exit bond` simply covers the gas cost of challenging.\nObservations from the Bitcoin and Ethereum ecosystems suggest that sufficiently many nodes will verify transactions without a direct in-protocol incentive to do so.\nOur system requires only a single node be properly incentivized to challenge, and it’s likely that many node operators will have strong external incentives.\nModeling the “correct” size of the exit bond is an ongoing area of research.\n\n\n##### Timeouts\n\nWe can add timeouts to each transaction (“must be included in the chain by block X”) to\n - reduce number of transactions vulnerable to [**Honest Exit Bond Slashing**](#Honest-Exit-Bond-Slashing) point in time.\n - alleviate [**Honest transaction retries attack**](#Honest-transaction-retries-attack), allowing Alice to just wait the timeout and retry\nThis will probably also be necessary from a user experience point of view, as we don’t want users to accidentally sign a double-spend simply because the first transaction hasn’t been processed yet.\n\n**TODO** At this point, it is uncertain how the timeouts scheme would modify MoreVP and whether it's feasible at all.\n\n## Appendix\n\n<!-- TODO: Clean up this entire proof section -->\n\n### Formalization of Definitions\n\n#### Transactions\n\n$TX$ is the transaction space, where each transaction has $inputs$ and $outputs.\nFor simplicity, each input and output is an integer that represents the position of that input or output in the Plasma chain.\n\n$$\nTX: ((I_1, I_2, … ,I_n), (O_1, O_2, … ,O_m))\n$$\n\nFor every transaction $t$ in $TX$ we define the “inputs” and “outputs” functions:\n\n$$\nI(t) = (I_1, I_2, …, I_n)\nO(t) = (O_1, O_2, …, O_m)\n$$\n\n\n#### Chain\n\nA Plasma chain is composed of transactions.\nFor each Plasma chain $T$, we define a mapping between each transaction position and the corresponding transaction at that position.\n\n$$\nT_n: [1, n] \\rightarrow TX\n$$\n\nWe also define a shortcut to point to a specific transaction in the chain.\n\n$$\nt_i = T_n(i)\n$$\n\n\n#### Competing Transaction, Competitors\n\nTwo transactions are competing if they have at least one input in common.\n\n$$\ncompeting(t, t’) = I(t) \\cap I(t’) \\neq \\varnothing\n$$\n\nThe set of competitors to a transaction is therefore every other transaction competing with the transaction in question.\n\n$$\ncompetitors(t) = \\{ t_{i} : i \\in (0, n], competing(t_{i}, t) \\}\n$$\n\n\n#### Canonical Transaction\n\nA transaction is called “canonical” if it’s oldest of all its competitors.\nWe define a function $first$ that determines which of a set $T$ of transactions is the oldest transaction.\n\n$$\nfirst(T) = t \\in T : \\forall t’ \\in T, t \\neq t’, min(O(t)) < min(O(t’))\n$$\n\nThe set of canonical transactions is any transaction which is the first of all its competitors.\n\n$$\ncanonical(t) = (first(competitors(t)) \\stackrel{?}{=} t)\n$$\n\nFor convenience, we define $reality$ as the set of all canonical transactions for a given chain.\n\n$$\nreality(T_{n}) = \\{ canonical(t_{i}) : i \\in [1, n] \\}\n$$\n\n\n#### Unspent, Double Spent\n\nWe define two helper functions $unspent$ and $double\\_spent$.\n$unspent$ takes a set of transactions and returns the list of transaction outputs that haven't been spent.\n$double\\_spent$ takes a list of transactions and returns any outputs that have been used as inputs to more than one transaction.\n\nFirst, we define a function $txo$ that takes a transaction and returns a list of its inputs and outputs.\n\n$$\ntxo(t) =  O(t) \\cup I(t)\n$$\n\nNext, we define a function $TXO$ that lists all inputs and outputs for an entire set of transactions:\n\n$$\nTXO(T_{n}) = \\bigcup_{i = 1}^{n} txo(t_{i})\n$$\n\nNow we can define $unspent$ and $double\\_spent$:\n\n$$\nunspent(T) = \\{ o \\in TXO(T) : \\forall t \\in T, o \\not\\in I(t) \\}\n$$\n\n$$\ndouble\\_spent(T) = \\{ o \\in TXO(T) : \\exists t,t' \\in T, t \\neq t', o \\in I(t) \\wedge o \\in I(t') \\}\n$$\n\n### Requirements\n\n#### Safety\n\nThe safety rule, in English, says \"if an output was exitable at some time and is not spent in a later transaction, then it must still be exitable\".\nIf we didn't have this condition, then it might be possible for a user to receive money but not be able to spend or exit from it later.\n\nFormally, if we say that $E(T_{n})$ represents the set of exitable outputs for some Plasma chain and $T_{n+1}$ is $T_{n}$ plus some new transaction $t_{n+1}$:\n\n$$\n\\forall o \\in E(T_{n}) : o \\not\\in I(t_{n+1}) \\implies o \\in E(T_{n+1})\n$$\n\n\n#### Liveness\n\nThe liveness rule states that \"if an output was exitable at some time and *is* spent later, then immediately after that spend, either it's still exitable or all of the spend's outputs are exitable, but not both\".\n\nThe second part ensures that something is spent, then all the resulting outputs should be exitable.\nThe first case is special - if the spend is invalid, then the outputs should not be exitable and the input should still be exitable.\n\n$$\n\\forall o \\in E(T_{n}), o \\in I(t_{n+1}) \\implies o \\in E(T_{n+1}) \\oplus O(t_{n+1}) \\subseteq E(T_{n+1})\n$$\n\n\n### Basic Exit Protocol\n\n#### Formalization\n\n$$\nE(T_{n}) = unspent(reality(T_{n})) \\setminus double\\_spent(T_{n})\n$$\n\n\n##### Priority\n\nExits are ordered by a given priority number.\nAn exit with a lower priority number will process before an exit with a higher priority number.\nWe define the priority of an exit from a transaction $t$, $p(t)$, as:\n\n$$\np(t) = \\max(I(t))\n$$\n\n\n#### Proof of Requirements\n\n#### Safety\n\nOur safety property says:\n\n$$\n\\forall o \\in E(T_{n}), o \\not\\in I(t_{n+1}) \\implies o \\in E(T_{n+1})\n$$\n\nSo to prove this for our $E(T_{n})$, let's take some $o \\in E(T_{n})$.\nFrom our definition, $o$ must be in $unspent(reality(T_{n}))$, and must not be in $double\\_spent(T_{n})$.\n\n$o \\not\\in I(t_{n+1})$ means that $o$ will still be in $reality$, because only a transaction spending $o$ can impact its inclusion in $reality$.\nAlso, $o$ can't be spent (or double spent) if it wasn't used as an input.\nSo our function is safe!\n\n\n#### Liveness\n\nOur liveness property states:\n\n$$\n\\forall o \\in E(T_{n}), o \\in I(t_{n+1}) \\implies o \\in E(T_{n+1}) \\oplus O(t_{n+1}) \\subseteq E(T_{n+1})\n$$\n\nHowever, *we do have a case for which liveness does not hold* - namely that if the second transaction is a non-canonical double spend, then both the input and all of the outputs will not be exitable.\nThis is a result of the $\\setminus double\\_spent(T_{n})$ clause.\nWe think this is fine, because it means that only double spent inputs are at risk of being \"lost\".\n\nThe updated property is therefore:\n $$\n\\forall o \\in E(T_{n}), o \\in I(t_{n+1}) \\implies o \\in E(T_{n+1}) \\oplus O(t_{n+1}) \\subseteq E(T_{n+1}) \\oplus  o \\in double\\_spent(T_{n+1})\n$$\n\nThis is more annoying to prove, because we need to show each implication holds separately, but not together.\nBasically, given $\\forall o \\in E(T_{n}), o \\in I(t_{n+1})$, we need:\n\n$$\no \\in E(T_{n+1}) \\implies O(t_{n+1}) \\cap E(T_{n+1}) = \\varnothing \\wedge  o \\not\\in double\\_spent(T_{n+1})\n$$\n\nand\n\n$$\nO(t_{n+1}) \\subseteq E(T_{n+1}) \\implies o \\not\\in E(T_{n+1}) \\wedge o \\not\\in double\\_spent(T_{n+1})\n$$\n\nand\n\n$$\no \\in double\\_spent(T_{n+1}) \\implies O(t_{n+1}) \\cap E(T_{n+1}) = \\varnothing \\wedge o \\not\\in E(T_{n+1})\n$$\n\nLet's show the first.\n$o \\in I(t_{n+1})$ means $o$ was spent in $t_{n+1}$.\nHowever, $o \\in E(T_{n+1})$ means that it's unspent in any canonical transaction.\nTherefore, $t_{n+1}$ cannot be a canonical transaction.\n$O(t_{n+1}) \\cap E(T_{n+1})$ is empty if $t_{n+1}$ is not canonical, so we've shown the half.\nOur specification states that $o \\in double\\_spent(T_{n+1}) \\implies o \\not\\in E(T_{n+1})$, so we can show the second half by looking at the contrapositive of that statement $o \\in E(T_{n+1}) \\implies o \\not\\in double\\_spent(T_{n+1})$.\n\nNext, we'll show the second statement.\n$O(t_{n+1}) \\subseteq E(T_{n+1})$ implies that $t_{n+1}$ is canonical.\nIf $t_{n+1}$ is canonical, and $o$ is an input to $t_{n+1}$, then $o$ is no longer unspent, and therefore $o \\not\\in E(T_{n+1})$.\nIf $t$ is canonical then there cannot exist another earlier spend of the input, so  $o \\not\\in double\\_spent(T_{n+1})$.\n\nNow the third statement.\n$o \\in double\\_spent(T_{n+1})$ means $t$ is necessarily not canonical, so we have $O(t_{n+1}) \\cap E(T_{n+1}) = \\varnothing$.\nIt also means that $o \\not\\in E(T_{n+1})$ because of our $\\setminus double\\_spent(T_{n})$ clause.\n\nFinally, we'll show that at least one of these must be true.\nLet's do a proof by contradiction.\nAssume the following:\n\n$$\n O(t_{n+1}) \\cap E(T_{n+1}) = \\varnothing \\wedge o \\not\\in E(T_{n+1}) \\wedge  o \\not\\in double\\_spent(T_{n+1})\n$$\n\nWe've already shown that:\n\n$$\no \\in E(T_{n+1}) \\implies O(t_{n+1}) \\cap E(T_{n+1}) = \\varnothing \\wedge  o \\not\\in double\\_spent(T_{n+1})\n$$\n\nWe can negate this statement to find:\n\n$$\no \\not\\in E(T_{n+1}) \\wedge (O(t_{n+1}) \\subseteq E(T_{n+1}) \\vee  o \\in double\\_spent(T_{n+1}))\n$$\n\nHowever, we assumed that:\n\n$$\n\nO(t_{n+1}) \\cap E(T_{n+1}) = \\varnothing \\wedge  o \\not\\in double\\_spent(T_{n+1})\n\n$$\n\nTherefore we've shown the statement by contradiction.\n\n\n#### Exit Ordering\n\nLet $t_{v}$ be some valid transaction and $t_{iv}$ be the first invalid, but still exitable and canonical, transaction in the chain.\n$t_{iv}$ must either be a deposit transaction or spend some input that didn’t exist when $t_{v}$ was created, otherwise $t_{iv}$ would be valid.\nTherefore:\n\n$$\n\\max(I(t_{v})) < \\max(I(t_{iv}))\n$$\n\nand therefore by our definition of $p(t)$:\n\n$$\np(t_{v}) < p(t_{iv})\n$$\n\nSo $p(t_{v})$ will exit before $p(t_{iv})$.\nWe now need to show that for any $t'$ that stems from $t_{iv}$, $p(t_{v}) < p(t')$ as well.\nBecause $t'$ stems from $t_{iv}$, we know that:\n\n$$\n(O(t_{iv}) \\cap I(t') \\neq \\varnothing) \\vee (\\exists t : stems\\_from(t_{iv}, t) \\wedge stems\\_from(t, t'))\n$$\n\nIf the first is true, then we can show $p(t_{iv}) < p(t')$:\n\n$$\np(t') = \\max(I(t')) \\geq \\max(I(t') \\cap O(t_{iv})) \\geq \\min(O(t_{iv})) > \\max(I(t_{iv})) = p(t_{iv})\n$$\n\nOtherwise, there's a chain of transactions from $p_{iv}$ to $p'$ for which the first is true, and therefore the inequality holds by transitivity.\nTherefore, all exiting outputs created by valid transactions will exit before any output created by an invalid transaction.\n"
  },
  {
    "path": "docs/perf_test_result_dumps.md",
    "content": "# Dumps of results of perf test run over commits\n\n## `7d219a9806cb566ede860e7a26d2b3057838ed4b`, 2018-07-05\n\nCommand:\n\n```\nmix run --no-start -e 'OMG.Performance.start_simple_perftest(8_000, 32, %{block_every_ms: 15_000})'\n```\n\nPerformance statistics:\n```\n[\n  %{\"blknum\" => 1000, \"span_ms\" => 16904, \"tps\" => 3876.95, \"txs\" => 65536},\n  %{\"blknum\" => 2000, \"span_ms\" => 14920, \"tps\" => 4050.34, \"txs\" => 60431},\n  %{\"blknum\" => 3000, \"span_ms\" => 15428, \"tps\" => 3907.25, \"txs\" => 60281},\n  %{\"blknum\" => 4000, \"span_ms\" => 14706, \"tps\" => 4035.5, \"txs\" => 59346},\n  %{\"blknum\" => 5000, \"span_ms\" => 2054, \"tps\" => 5066.21, \"txs\" => 10406}\n]\n\n```\n\ntypical block forming log:\n```\n15:30:00.267 [info] Calculations for forming block 1000 done in 1017 ms\n15:30:01.083 [info] DB.multi_update done in 816 ms\n15:30:01.148 [info] Done forming block in 1898 ms\n```\n\nrun on\n```\n4x version: Intel(R) Core(TM) i7-4790K CPU @ 4.00GHz\n```\n\n## `685b5f75b283ab64b56ae5b6ac046b99692d3fbd`, 2018-07-18\n\nCommand as above + observer:\n\n```\nmix run --no-start -e ':observer.start(); OMG.Performance.start_simple_perftest(8_000, 32, %{block_every_ms: 15_000})'\n```\n\nObserver tells us that peak memory usage (total) is ~600MB, oscillating around ~400MB most of the time.\n\n## `62249098e852d52552616364cb1ca9184be43d02`, 2018-10-16\n\nCommand as above:\n\n```\nmix run --no-start -e 'OMG.Performance.start_simple_perftest(8_000, 32, %{block_every_ms: 15_000})'\n```\n\n```\n[\n   {\"blknum\":1000, \"span_ms\":16378, \"tps\":3937, \"txs\":64484},\n   {\"blknum\":2000, \"span_ms\":15115, \"tps\":4020, \"txs\":60761},\n   {\"blknum\":3000, \"span_ms\":14915, \"tps\":4040, \"txs\":60257},\n   {\"blknum\":4000, \"span_ms\":14726, \"tps\":4110, \"txs\":60530},\n   {\"blknum\":5000, \"span_ms\":1865,  \"tps\":5345, \"txs\":9968}\n]\n```\n\ntypical block forming log:\n```\n2018-10-16 17:30:05.815 [info] ... ⋅Calculations for forming block number 2000 done in 1036 ms⋅\n2018-10-16 17:30:06.312 [info] ... ⋅Forming block done in 1533 ms⋅\n```\n\nrun on: as above.\n\n## `869c964df00c17a54b399c33c8e917d23ab05dd7`, 2018-12-07\n\nCommand as above (new syntax):\n\n```\nmix run --no-start -e 'OMG.Performance.start_simple_perftest(8_000, 32, %{block_every_ms: 15_000})'\n```\n\n```\n[\n   {\"blknum\":1000, \"span_ms\":16488, \"tps\":3157, \"txs\":52057},\n   {\"blknum\":2000, \"span_ms\":15219, \"tps\":2974, \"txs\":45254},\n   {\"blknum\":3000, \"span_ms\":14964, \"tps\":2789, \"txs\":41742},\n   {\"blknum\":4000, \"span_ms\":14740, \"tps\":2847, \"txs\":41965},\n   {\"blknum\":5000, \"span_ms\":14889, \"tps\":3005, \"txs\":44742},\n   {\"blknum\":6000, \"span_ms\":7210, \"tps\":4194, \"txs\":30240}\n]\n```\n\nand\n\n```\n2018-12-07 15:14:44.129 [info] ... ⋅Calculations for forming block number 3000 done in 1391 ms⋅\n```\n\nSome drop in throughput since last dump, but still bottlenecks lie elsewhere.\n\n## `17f73a0f90e0cec35d684da0104b97234425f787`, 2019-02-11\n\nCommand as above\n\n```\nmix run --no-start -e 'OMG.Performance.start_simple_perftest(8_000, 32, %{block_every_ms: 15_000})'\n```\n\n```\n[\n {\"txs\": 65536, \"tps\": 3976, \"span_ms\": 16482, \"blknum\": 1000},\n { \"txs\": 65536, \"tps\": 4397, \"span_ms\": 14904, \"blknum\": 2000},\n { \"txs\": 65536, \"tps\": 4264, \"span_ms\": 15370, \"blknum\": 3000},\n { \"txs\": 59392, \"tps\": 5942, \"span_ms\": 9995, \"blknum\": 4000}\n]\n```\n\nand\n```\n2019-02-11 17:16:23.392 [info] ... ⋅Calculations for forming block number 2000 done in 832 ms⋅\n```\n\n## `53dc46f80eca374c64983aacd37bd6851ec794f4`, 2019-06-28\n\n(perf drop introduced as documented in [#731](https://github.com/omgnetwork/elixir-omg/issues/731))\n\nCommand as above\n\n```\n[\n  %{blknum: 1000, span_ms: 18449, tps: 3042, txs: 56124},\n  %{blknum: 2000, span_ms: 13970, tps: 3152, txs: 44029},\n  %{blknum: 3000, span_ms: 15340, tps: 3125, txs: 47933},\n  %{blknum: 4000, span_ms: 14598, tps: 3236, txs: 47233},\n  %{blknum: 5000, span_ms: 15039, tps: 3248, txs: 48840},\n  %{blknum: 6000, span_ms: 1199, tps: 9876, txs: 11841}\n]\n```\n\n## `f3828a0f0a658b32a48a74649561ac2c452f7277`, 2019-06-28\n\n(fixed the perf drop)\n\nCommand as above\n\n```\n[\n  %{blknum: 1000, span_ms: 16699, tps: 3889, txs: 64940},\n  %{blknum: 2000, span_ms: 14753, tps: 4007, txs: 59120},\n  %{blknum: 3000, span_ms: 15225, tps: 3950, txs: 60140},\n  %{blknum: 4000, span_ms: 11507, tps: 5206, txs: 59903},\n  %{blknum: 5000, span_ms: 4059, tps: 2931, txs: 11897}\n]\n```\n\n"
  },
  {
    "path": "docs/run_local_watcher.md",
    "content": "# Running your own Watcher locally\n\nThe `docker-compose` tooling in the root of `elixir-omg` allows users to run their own instance of the Watcher to connect to the OMG Network and validate transactions.\n\n### Requirements\n\n- Docker\n- `docker-compose` - known to work with `docker-compose version 1.24.0, build 0aa59064`, version `1.17` has had problems\n- Ethereum connectivity: local Ethereum node or Infura\n\n### Startup\n\n1) Add an ENV variable `INFURA_API_KEY` to your environment or override the ETHEREUM_RPC_URL completely in the `docker-compose-watcher.yml` file with the RPC connection information.\n\n2) From the root of the `elixir-omg` execute:\n\n- `docker-compose -f docker-compose-watcher.yml up`\n\nModify the other environment variables for connecting to other networks.\n"
  },
  {
    "path": "docs/source_consumption_log.md",
    "content": "# Source Consumption Log\n\n### About:\n\nIdentifies each module of pre-existing source code used in developing source code, what license (if any) that source code was provided under, where the preexisting source code and the license can be obtained publicly (if so available), and identification of where that source is located.\n\n## Redistributed already\n\n* Elixir/Erlang/BEAM/OTP\n  * `Elixir`, Apache 2.0, https://github.com/elixir-lang/elixir\n  * `Erlang`, Apache 2.0, https://www.erlang.org\n  * `BEAM/OTP`, Apache 2.0, https://github.com/erlang/otp\n* MIX deps, as listed `mix licenses` ([licensir](https://github.com/unnawut/licensir/)), cleaned/completed manually.\n**NOTE**, unless otherwise noted, package is obtained from `hex.pm/packages/<package_name>`:\n```\nabi 0.1.12              -> MIT\nbinary 0.0.4            -> MIT\nblockchain 0.1.7        -> MIT\nbunt 0.2.0              -> MIT\nbriefly 0.3.0           -> Apache 2.0\ncertifi 2.3.1           -> BSD\ncowboy 1.1.2            -> ISC\ncowlib 1.0.2            -> ISC\ncredo 0.9.3             -> MIT\ndb_connection 1.1.3     -> Apache 2.0\ndecimal 1.5.0           -> Apache 2.0\ndialyxir 1.0.0-rc.6     -> Apache 2.0\necto 3.1.5              -> Apache 2.0\nerlang-rocksdb 1.2.0    -> Apache 2.0\nerlexec 1.7.5           -> BSD\nethereumex 0.5.4        -> MIT\nex_rlp 0.5.3            -> MIT\nex_unit_fixtures        -> MIT\nexexec 0.1.0            -> Apache 2.0\nexth_crypto 0.1.4       -> MIT\nfake_server 2.1.0       -> Apache 2.0\nhackney 1.15.1          -> Apache 2\nhttpoison 1.6.0         -> MIT\nidna 5.1.1              -> BSD\nkeccakf1600 2.0.0       -> MPL 2.0 (ok'd by legal, compliant with our Apache 2.0)\nlibsecp256k1 0.1.9      -> MIT\nlicensir 0.2.7          -> MIT\nmerkle_patricia_tree 0.2.6-> MIT\nmerkle_tree 1.5.0       -> MIT\nmetrics 1.0.1           -> BSD\nmime 1.3.0              -> Apache 2\nmimerl 1.0.2            -> MIT\nparse_trans 3.2.0       -> Apache 2.0\nphoenix 1.3.2           -> MIT\nphoenix_ecto 3.3.0      -> Apache 2.0\nphoenix_pubsub 1.0.2    -> MIT\nplug 1.5.0              -> Apache 2\npoison 3.1.0            -> CC0-1.0 (ok'd by legal, compliant with our Apache 2.0)\npostgrex 0.13.5         -> Apache 2.0\nranch 1.3.2             -> ISC\npoolboy 1.5.1           -> Apache 2.0\nsocket 0.3.13           -> WTFPL\nssl_verify_fun 1.1.1    -> MIT\nunicode_util_compat 0.3.1-> Apache 2.0\n```\n\n## Likely to be redistributed\n\n* `geth`, LGPL 3.0, https://github.com/ethereum/go-ethereum, (used via an interface, so ok)\n* `zeppelin-solidity`, MIT, https://github.com/OpenZeppelin/zeppelin-solidity\n\n## Likely to be used, but not redistributed\n\n* `web3`, MIT, https://github.com/ethereum/web3.py\n* `ethereum`, MIT, https://github.com/ethereum/pyethereum\n* `rlp`, MIT, https://github.com/ethereum/pyrlp\n* `py-solc`, MIT, https://github.com/ethereum/py-solc\n* `solc`, GPL 3.0, https://github.com/ethereum/solidity\n* `postgresql`, PostgreSQL License, https://www.postgresql.org\n\n"
  },
  {
    "path": "docs/stack_architecture.md",
    "content": "# OMG Network Client Architecture\n\nThis describes the client services stack that communicates with the child chain and root chain to secure the entire Plasma construction and ease application development. An application provider will run these client services on their own or use hosted versions.\n\n## Foundations\n\n### Root chain\n\n#### Purpose\n\nTrusted chain used by the Plasma construction to secure funds in the child chain. In our case, this is Ethereum.\n\n### Child chain\n\n#### Purpose\n\nBlockchain of transactions for our application. Continually submits block hashes to the root chain, as required by the Plasma construction. Described in the [Tesuji design](tesuji_blockchain_design.md) and the [More Viable Plasma] documentation.\n\n## Client Services\n\n### Watcher\n\n#### Purpose\n\nThe watcher first and foremost plays a critical security role in the system. The watcher monitors the child chain and root chain (Ethereum) for faulty activity.\n\n#### Design principles\n\n- Only include functionality that is critical to the operation of the Plasma security model\n- Strict focus on security role reduces complexity and attack surface area\n- Limited feature helps scalability\n  - The more the watcher does, the slower it can verify\n- 3 primary security functions:\n  - Tracking of the root chain submissions, pulling block contents (from somewhere) and validating, in order to ensure safety of funds passively in possession on the child chain. Watcher notifies in case of the funds are jeopardized.\n  - Proxy API to the child chain API (whatever it may be - PoA server or a P2P PoS network) and the root chain, that ensures that these two are never talked to if the chain is invalid or in unknown state. Only proxy calls that require the chain is operational.\n  - Storage of data critical to access of the funds - UTXO positions, `txbytes` or any other kinds of proofs\n\n#### Specifications\n\n- [Current API](https://docs.omg.network/elixir-omg/docs-ui/?url=master%2Foperator_api_specs.yaml&urls.primaryName=master%2Finfo_api_specs)\n\n- Events\n  - [Byzantine Events](https://github.com/omgnetwork/elixir-omg/blob/master/docs/api_specs/status_events_specs.md#byzantine-events)\n\n### Informational API Service\n\n#### Purpose\n\nNon-critical convenience API proxy and provide data about the chain.\n\n#### Design principles\n\n- Provide convenience APIs to proxy to the child chain/root chain/watcher to ease integration and reduce duplicate code in libraries\n- Storage of informational data about the chain\n- Support direct client requests (web browser, mobile, etc.)\n\n#### Specifications\n\n- [Current API](https://docs.omg.network/elixir-omg/docs-ui/?url=master%2Foperator_api_specs.yaml&urls.primaryName=master%2Finfo_api_specs)\n\n### Integration libraries\n\n#### Purpose\n\nNative wrappers to the Watcher and Informational API Service for supported languages and frameworks.\n\n#### Design principles\n\n- Adopt all native conventions and standards\n- Encourage open source community development\n\n#### Requirements\n\n- Support all events and API calls of the Watcher\n- Support all events and API calls of the API Service\n\n#### Current implementations\n\n- [omg-js-lib](https://github.com/omgnetwork/omg-js)\n\n\n### Application Layer\n\n#### Purpose\n\nThird party applications that use the OMG Network for value transfer and exchange.\n\n#### Design principles\n\n- Key management happens at this layer\n\n#### Requirements\n\n- Generate and secure keys\n- Sign transactions\n"
  },
  {
    "path": "docs/standard_vs_in_flight_exits_interaction.md",
    "content": "# Standard vs In-flight Exits\n\nSince both Standard exit (aka SE) and In-flight exit (aka IFE) provide an exit opportunity, it is possible to effectively double-spend. To prevent such possibility, whenever an output is finalized/withdrawn from the Plasma M(ore)VP framework, we flag the output as spent to the `PlasmaFramework`. For all exit game contract implementations, they should ignore those already-spent-outputs during `processExit`. In other words, though there can be multiple exits for the same output started concurrently, only the first that get processed can really exit the output. All the others would just not process and ignore the output if it is already flagged.\n\n#### Glossary\n\n* `IFE` - in-flight exit, active unless stated otherwise;\n* `SE` - standard exit, active unless stated otherwise;\n* `utxoPos` - output position inside Plasma chain. It is a combination of `blockNumber`, `txIndex` and `outputIndex` that shows where the output is located in the plasma chain;\n* `OutputId` - global output identifier used in Plasma Framework, see description below;\n\n#### OutputId\nTo be able to flag all finalized outputs, we would need a global schema for all outputs in our Plasma Framework. In current implementation, the global identifier for output is called `OutputId`. The schema is as followed:\n\n1. For normal transaction outputs: `OutputId = hash(txBytes, outputIndex)`\n2. For deposit transaction outputs: `OutputId = hash(txBytes, outputIndex, utxoPos)`\n\nWe add `utxoPos` as a salt for deposit transaction output because deposit transaction can potentially have same `txBytes` as another deposit transactions (see: [this issue](https://github.com/omgnetwork/plasma-contracts/issues/80)), a naive `hash(txBytes, outputIndex)` would collide when `txBytes` are not unique.\n\nAlso, there was discussion to abstract the output identifier to be more flexible, see [this note](https://github.com/omgnetwork/plasma-contracts/issues/387). But as the first version of Plasma Framework, we decided to go forward with using `OutputId` as a global schema to flag all outputs.\n\n#### Standard Exit scenario\n\nA standard exit would only impact an output. Thus, in the case of SE, only one output need to be considered. The `processExit` function for SE would check whether that output has been flagged or not before withdrawing the fund. Also, once processed, it would flag that output as spent in `PlasmaFramework`.\n\n#### In-flight Exit scenario\n\nAn in-flight exit would impact all inputs and outputs of the in-flight exit transaction. If the IFE is canonical, it would exit the unchallenged piggybacked outputs. On the other hand, if the IFE is non-canonical, the unchallenged piggybacked inputs would be exited.\n\nIf any of the in-flight exit input is flagged during `processExit`, that exit would be considered as non-canonical (for more detail on the reasons, see: [this issue](https://github.com/omgnetwork/plasma-contracts/issues/470)). Otherwise, the canonicity would be decided by the canonicity challenge game of the in-flight exit.\n\nIf the in-flight exit is considered non-canonical during processing, we flag only the exiting inputs as spent. In other words, if the input is piggybacked, unchallenged and not flagged as spent yet, it would be exited and then be flagged as spent to the `PlasmaFramework`.\n\nOn the other hand, if the in-flight exit is considered canonical, _all_ inputs plus the exiting outputs would be flagged. So if the output is piggybacked, unchallenged, and not flagged as spent yet, it would be exited and then be flagged as spent. Also all inputs would be flagged as well.\n\nWe flag all the inputs when canonical because the current interaction game would have some edge cases during data unavailability, operator can try to double spend via IFE. For more detail, see this issue: [here](https://github.com/omgnetwork/plasma-contracts/issues/102). In short, current IFE interactive game design can potentially decide the canonicity differently during data unavailability to the real canonicity when there is full data availability. We mitigate this by using Kelvin's solution of flagging all inputs (see: [this comment](https://github.com/omgnetwork/plasma-contracts/issues/102#issuecomment-495809967)). So even a mismatch canonicity happens, it cannot be double spent.\n\n\n#### Previous design on SE <> IFE interaction\n\nPreviously we had a set of rules and action on the SE <> IFE interaction. It takes some time to evolve to the current one. See the original doc on `0.2` branch used for `RootChain.sol`: https://github.com/omgnetwork/elixir-omg/blob/v0.2.0/docs/standard_vs_in_flight_exits_interaction.md.\n\nWe end up change the mechanism heavily for two reasons:\n1. Simplicity on the rule\n2. The best solution to mitigate the IFE canonicity issue is to flag all inputs. So we need to flag output anyway.\n\nQuite a lot of our current `id` schema comes from the previous doc, such as `exitId`.\n\nNote that in previous design, `startInFlightExit` would auto challenge an existing standard exit. We removed such feature from the contract. As a result, watcher should add monitoring on such event when a standard exit can be challenged by an in-flight exit tx.\n\nAlso, see the discussion of changing the SE <> IFE interaction mechanism: https://github.com/omgnetwork/plasma-contracts/issues/110\n\n#### Current Implementation Of Payment Exit Game\nFor more details on our current implementation of Payment Exit Game: https://github.com/omgnetwork/plasma-contracts/blob/master/plasma_framework/docs/design/payment-game-implementation-v1.md\n"
  },
  {
    "path": "docs/tesuji_blockchain_design.md",
    "content": "# Tesuji Plasma Blockchain Design\n\nThis document describes in detail the blockchain (consensus) design used by the first iteration of OMG Plasma-based implementation.\nThe design is heavily based on [Minimal Viable Plasma design](https://ethresear.ch/t/minimal-viable-plasma/426), but incorporates several modifications.\nThe reader is assumed to have prior knowledge of Ethereum and familiarity with general ideas behind [Plasma](http://plasma.io).\n\n## Overview\n\nTesuji Plasma's architecture allows users to take advantage of cheaper transactions with higher throughput without sacrificing security.\nThis is accomplished by allowing users to make transactions on a child chain which derives its security from a root chain.\nBy **child chain** we mean a blockchain that coalesces multiple transactions into a **child chain block** compacting them into a single, cheap transaction on a **root chain**.\nIn our case the root chain is the Ethereum blockchain.\n\n### Key Features\n\nThese are the key features of the design, which might be seen as main deviations from the big picture Plasma, as outlined by the original Plasma paper:\n\n1. Supports only transactions that transfer value between addresses (Multiple currencies: Eth + ERC20).\nSee **Transactions** section.\n(The value transfer can take the form of an atomic swap - two currencies being exchanged in a single transaction.)\n5. The network is a non-p2p, proof-of-authority network, i.e. child chain is centrally controlled by a designated, fixed Ethereum address (**child chain operator**), other participants (**users**) connect to the child chain server.\nSee **Child chain server** section\n6. The Plasma construction employed is a single-tiered one, i.e. the child chain doesn't serve as a parent of any chain\n7. There aren't facilities that allow cheap, coordinated mass exits\n\nThe essence of security and scalability features is the ability of users to perform the following scenario:\n\n1. Deposit funds into a contract on the root chain\n2. Cheaply make multiple transfers of funds deposited on the child chain\n3. Exit any funds held on the child chain to reclaim them on the root chain, securely.\nThat is, every exit of funds held on the child chain must come with an attestation that the exit is justified.\nThe nature of that attestation will be clarified in following sections.\n\nSince exits can be done regardless of the state of the PoA child chain, the funds held on the child chain and root chain might be treated _as equivalent_.\nThe condition here is that if anything goes wrong on the child chain, everyone must exit to the root chain.\n\nIt's worth noting that the Plasma architecture presumes root chain availability.\n\nThe consensus is driven by the following components:\n\n1. **root chain contract** - responsible for securing the child chain:\n    - holds funds deposited by other addresses (users)\n    - tracks child chain block hashes submitted that account for the funds being moved on the child chain\n    - manages secure exiting of funds, including exits of in-flight transactions\n2. **child chain server** - responsible for creating and submitting blocks:\n    - collects valid transactions that move funds on the child chain\n    - submits child chain block hashes to the root chain contract\n    - publishes child chain blocks' contents\n3. **watcher** - responsible for validating the child chain and making sure the child chain consensus mechanism is working properly:\n    - tracks the root chain contract, published blocks and transactions\n    - reports any breach of consensus\n    - (as additional service) collects and stores the account information required to use the child chain\n    - (as additional service) provides a convenience API to access the child chain API and Ethereum.\n    Such access is restricted only to times when the child chain is valid, in order to protect the user.\n\n**NOTE** all cryptographic primitives used (signatures, hashes) are understood to be ones compatible with whatever EVM uses.\n\n## Root chain contract\n\nNote that the child chain and the root chain contract securing it manage funds using the UTXO model (see **Transactions** section).\n\n### Deposits\n\nAny Ethereum address may deposit Eth or ERC20 token into the root chain contract.\nSuch deposit increases the pool of funds held by the root chain contract and also signals to the child chain server, that the funds should be accessible on the child chain.\nDepositing causes the deposited funds to be recognized as a single UTXO on the child chain.\nSuch UTXO can then be both spent on the child chain (provided that the child chain server follows consensus) or exited immediately on the root chain (regardless of whether child chain server follows consensus).\n\nThe mechanism of depositing consists in forming of a \"pseudo-block\" of the child chain, that contains a single transaction with the deposited funds as a new UTXO.\n\n### Exits w/ exit challenges\n\nExits are the most important part of the root chain contract facilities, as they give the equivalence of funds sitting in the child chain vs funds on the root chain.\n\nIn principle, the exits must satisfy the following conditions:\n- **E1**: only funds represented by UTXOs that were provably included in the child chain may be exited (see **Transactions** section).\nThis means that only funds that provably _existed_ may be exited.\n- **E2**: attempts to exit funds, which have been provably spent on the child chain, must be thwarted and punished.\n- **E3**: there must be a priority given to earlier UTXOs, for the event when the attacking child chain operator submits a block creating UTXOs dishonestly and attempts to exit these UTXOs.\nIt allows all UTXOs created before the dishonest UTXOs to exit first.\n- **E4**: funds that are in-flight, i.e. locked up in a transaction, that might have or might have not been included in the child chain, must be able to exit on par with funds whose inclusion is known.\n\n#### Submitting exit requests and challenging\n\n**E1** and **E2** are satisfied by the following mechanism, depending on the inclusion:\n\n**UTXOs, whose creating transaction is included in a known position in a child chain valid up to that point, use the _regular exit_:**\n\nAny Ethereum address that proves possession of funds (UTXO) on the child chain, can submit a request to exit.\nThe proof consists in showing the transaction (containing the UTXO as output) and proving inclusion of the transaction in one of the submitted child chain blocks.\n\nHowever, this isn't the full attestation required to be able to withdraw funds from the root chain contract.\nThe submitted (proven) exit request must still withstand a **challenge period** when it can be challenged by anyone who provides evidence that the exited UTXO has been spent.\nThe evidence consists in a signed transaction spending the exiting UTXO, regardless of its inclusion.\n\nExit's challenge period counts from exit request submission till that exit's scheduled finalization time (see below).\n\nA successful and timely exit challenge invalidates the exit.\n\n**Funds that are in-flight, i.e. where inclusion of a transaction manipulating them is not known or inclusion is in an invalid chain, use the _in-flight exit_:**\n\nAssuming that the in-flight transaction has inputs that had been outputs of a transaction included in a valid chain, such funds are recoverable using the [MoreVP protocol](morevp.md).\n\n#### Finalization of exits\n\nFinalizing an exit means releasing funds from the root chain contract to the exitor.\n**E3** is satisfied by exit scheduling and priorities.\n\nExits finalize at their **Scheduled finalization time (`SFT`)**, which is:\n\n```\nSFT = max(exit_request_block.timestamp + MFP, utxo_submission_block.timestamp + MFP + REP)\n```\nfor regular exits, and:\n\n```\nexitable_at = max(exit_request_block.timestamp + MFP, youngest_input_block.timestamp + MFP + REP)\n```\nfor in-flight exits, see [MoreVP protocol](morevp.md) for details.\n\nDeposits are protected against malicious operator by elevating their exit priority:\n```\nSFT = max(exit_request_block.timestamp + MFP, utxo_submission_block.timestamp + MFP)\n```\n\nIn the above formulae:\n- `exit_request_block` - root chain block, where the exit request is mined\n- `utxo_submission_block` - root chain block, where the exiting UTXO was created in a child chain block\n- `youngest_input_block` - root chain block, where the youngest input of the exiting transaction was created\n- all exits must wait at least the **Minimum finalization period (`MFP`)**, to always have the challenge period\n- fresh UTXOs exited must wait an additional **Required exit period (`REP`)**, counting from their submission to root chain contract.\n\n> Example values of `MFP` and `REP` are 1 week and 1 week respectively, as in Minimal Viable Plasma.\n\nRoot chain contract allows to finalize exits which `SFT` had passed, always processing exits in ascending order of **exit priority**.\nExit priority has two keys:\n- primary key is the `SFT`\n- secondary key is the UTXO position (see **Transactions**)\n\n#### Frequency of child chain validation\n\nThere are maximum periods of time a user can spend offline, without validating a particular aspect of the chain and exposing themselves to risk of fund loss:\n\n - must validate child chain every `REP` to have enough time to submit an exit request in case chain invalid\n - must validate exits every `MFP` to challenge invalid regular exits\n - must validate in-flight exits every `MFP/2` to challenge invalid actions in the in-flight exit protocol\n\nReassuming, to cover all the possible misbehavior of the network, the user must validate at rarest every `min(REP, MFP/2)`.\n\n#### Example exit scenarios\n\nThe relation between `MFP` and `REP` is illustrated by the following:\n\n- **Example 1**: `MFP = 1 day`, `REP = 2 day`\n    - day 1 operator creates, includes, and starts to exit an invalid UTXO\n    - day 3 user checks chain after being offline for 2 days (`REP`) and sees the invalid transaction, exits his old UTXO\n    - day 4 both operator and user can exit (after `MFP`), but user's exit takes precedence based on `utxoPos`\n\n### Block submissions\n\nOnly a designated address belonging to the child chain operator can submit blocks.\nEvery block submitted to the root chain contract compacts multiple child chain transactions.\nEffectively, the block being submitted means that during exiting, ownership of funds (inclusion of transaction) can be now proven using a new child chain block hash.\n\n### Network congestion\n\n(TODO: **Note: This is currently being researched and discussed**)\n\nThe child chain will allow a maximum of N UTXOs at given time on the child chain.\nN is bound by root chain's bandwidth limitations and is the maximum amount of UTXOs that can safely requested to exit, if the child chain becomes invalid.\n\nPlasma assumes root chain network and block gas availability to start all users' exits in time.\nIf the network becomes too congested, we'll freeze time on the root chain contract until it becomes safe to operate again.\n\n### Reorgs\n\nReorgs (block and transaction order changing) of the root chain can lead to spurious invalidity of the child chain.\nFor instance, without any protection, a deposit can be placed and then spent quickly on the child chain.\nEverything is valid, if the submit block root chain transaction gets mined after the deposit (making the honest child chain to allow the spend).\nHowever, if the order of these transactions gets reversed due to a reorg, the spend will appear before the deposit, rendering the child chain invalid.\n\nWe'll protect ourselves against reorgs by:\n1. Only allowing deposits to be used on the child chain after N Ethereum Block confirmations (should be configurable).\nThis makes invalidating of the child chain by miners as expensive as we want it to be.\nThis rule will be built into the child chain itself, i.e. the root chain contract won't enforce this in any way.\n2. Submitting blocks to the root chain contract is protected by account nonce mechanism.\nMiner attempting to mine them in wrong order would produce incorrect Ethereum block.\n3. Numbering of child chain blocks is independent of numbering of deposit blocks.\nDisappearing deposit block will not invalidate numbering of child chain blocks.\n\n## Child chain server\n\n### Collecting transactions\n\nThe child chain server will collect transactions, executing the valid ones immediately.\nThe child chain will have **transactions per block limit** - an upper limit for the number of transactions that can go in a single child chain block.\nIf a submitted transaction would exceed that limit, it's going to be held off in a queue and scheduled for inclusion in the next block.\nThat queue would be prioritized by transaction fee value.\nIf there are too many transactions in the queue the ones with the lowest fees will be lost and must be resubmitted.\n\n> Transaction per block limit is assumed to be 2^16, per Minimal Viable Plasma\n\n### Submitting and propagating blocks\n\nEvery T amount of time the child chain will submit a block (in form of blocks' transactions merkle root hash) to root chain contract.\n\nAfter the child chain has submitted a block to root chain contract it must share the block contents on watcher's request.\nThe watchers are responsible for taking in blocks and extracting whatever information they need from them (see **Watcher** section).\n\nIf the child chain operator submits an invalid block or withholds a submitted block (i.e. doesn't share the block contents) everyone must exit.\n\n### Transactions\n\nTransactions, their semantics and encoding are described in detail in the [Transactions section of the contracts integration document](https://github.com/omgnetwork/plasma-contracts/blob/master/plasma_framework/docs/integration-docs/integration-doc.md#transactions),\n\n**NOTE** To create a valid transaction, a user needs to have access to inputs pointers (UTXO positions or OutputIDs or other) of all the UTXOs that they intend to spend.\nThe Child Chain server doesn't provide this data, it is the responsibility of the Watcher (or Watcher Info) service intended to be ran by the users.\n\n### Fees\n\nThe transaction's fee is implicit (think bitcoin), i.e. surplus of the amount being inputted over the amount being outputted (`sumAmount(spent UTXOs) - sumAmount(created UTXOs) >= 0`) is the fee that the child chain operator is eligible to claim later.\n\nThis section only skims the transaction fee topic, for details see [fee design document](./fee_design.md).\n\n#### Accepting fees by the child chain server\n\nThe child chain will have a configurable fixed min fee and will not accept any transactions below the fixed min fee.\nThe fixed min fee will be derived from the average of N different apis (see [here](https://developer.makerdao.com/feeds/) for more info) pinging the central server so that it stays up to date on the current prices.\n\n#### Tracking and exiting fees\n\nChild chain operator is eligible to exit the fees accumulated from the root chain contract.\nSee **Watcher** section for Watcher's role of tracking the correctness of fee exits.\n\n## Watcher\n\nThe watcher is assumed to be run by the users, or taken differently, to be trusted by users of the child chain.\nProper functioning of the watcher is critical to the security of funds deposited.\n\nThe watcher is responsible for pinging the child chain server to ensure that everything is valid.\nThe watcher will watch the root chain contract for a `BlockSubmitted` event log (a submission of a child chain block).\nAs soon as it receives a log it will ping the child chain for the full block and then make sure the block is valid and that it's root matches the child chain root submitted.\n\nThe watcher will check for the following conditions of chain invalidity.\nAny of these make the watcher prompt for an exit of funds:\n\n1. Invalid blocks:\n    - With multiple transactions spending the same input.\n    - Transactions spending an input spent in any prior block\n    - Transactions spending exited inputs, if unchallenged or challenge failed or was too late\n    - Transactions with deposits that haven't happened.\n    - Transactions with invalid inputs.\n    - Transactions with invalid signatures.\n2. Fee exits by the child chain operator that take more fees than the operator has available to them.\nIt's the watchers job to check that the operator never exits more fees than they're due, because the funds to cover the exited fees are drawn from the same pool, where the deposited funds are.\nIn other words, if watchers overlook the child chain operator exiting too much fees, there might be not enough funds left in the root chain contract for them to exit.\n3. Inability to acquire (for a long enough period of time) enough information to validate a child chain block that's been submitted to the root chain.\n4. Any invalid claim done on the root chain contract (e.g. an invalid exit), that goes without challenge for too long and becomes a risk on the security of the funds held on the child chain.\n\nThe watcher will check for the following conditions that (optionally) prompt for an exit challenge:\n\n1. Exits during their challenge period referencing UTXOs that have already been spent on the child chain.\n2. Invalid actions taken during the in-flight exit game, see [MoreVP protocol](morevp.md).\n\nAs soon as one watcher detects the child chain to be invalid, all others will as well and everyone with assets on the child chain will be notified to exit immediately.\n\n### Storage facilities of the watcher (aka Account information)\n\nWatcher takes on an additional responsibility: collecting and storing data relevant to secure handling of user's assets on the child chain:\n\n1. UTXOs in possession of the address holding assets\n2. Full transaction history (child chain blocks)\n\n## Exchange\n\nSee [here](./dex_design.md) for a high-level discussion about exchange designs on top of Tesuji plasma.\n"
  },
  {
    "path": "docs/transaction_validation.md",
    "content": "# Transaction validation\n\nNOTE:\n* input = utxo\n\nThis document presents current way of stateless and stateful validation of\n`OMG.ChildChain.submit(encoded_signed_tx)` function.\n\n#### Stateless validation\n\n1. Decoding of encoded singed transaction using `OMG.State.Transaction.Signed.decode` method\n    * Decoding using `ExRLP.decode` method and if failed then `{:error, :malformed_transaction_rlp}`\n    * Checking the transaction type and if not allowed then `{:error, :malformed_transaction}`\n    * Decoding the raw structure of RLP items and if failed then `{:error, :malformed_transaction}`\n    * Checking output type with respect to the parent transaction type and if failed then `{:error, :unrecognized_output_type}`\n    * Checking addresses/inputs/outputs/metadata format and if failed then `{:error, :malformed_address}` / `{:error, :malformed_inputs}` / `{:error, :malformed_outputs}` / `{:error, :malformed_metadata}` respectively\n    * Checking if outputs are non-empty and if failed then `{:error, :empty_outputs}`\n    * Checking any integer values to be formatted validly and if failed then `{:error, :leading_zeros_in_encoded_uint}` or `{:error, :encoded_uint_too_big}` accordingly\n    * Checking all amount-representing values to non-zero and if failed then `{:error, :amount_cant_be_zero}`\n2. Checking and recovering (preprocessing) a decoded `Transaction.Signed` using `OMG.State.Transaction.Recovered`\n    * Checking if transaction doesn't have duplicated inputs and if failed then `{:error, :duplicate_inputs}`\n    * Checking if signatures are in correct format and lengths and if failed then `{:error, :malformed_witnesses}`\n    * Checking if transaction has no missing signature for an input supplied and if failed then `{:error, :missing_signature}`\n    * Checking if transaction has no missing input for a signature supplied and if failed then `{:error, :superfluous_signature}`\n    * Recovering addresses of spenders from signatures and if failed then `{:error, :signature_corrupt}`\n\n#### Stateful validation\n\n1. Validating block size\n    * if the number of transactions in block exceeds limit then `{:error, :too_many_transactions_in_block}`\n2. Checking correctness of input positions\n    * if the input is from the future block then `{:error, :input_utxo_ahead_of_state}`\n    * if the input does not exists then `{:error, :utxo_not_found}`\n    * if the owner of input does not match with spender then `{:error, :unauthorized_spend}`\n3. Checking if the amounts from the provided inputs adds up.\n    * if not then `{:error, :amounts_do_not_add_up}`\n4. (if in child chain server tx submission pipeline): see if the transaction pays the correct fee.\n    * if not then `{:error, :fees_not_covered}`\n"
  },
  {
    "path": "docs/unified_api.md",
    "content": "# Unified API\n\n## Problem\nCurrently the Childchain API and Watcher API behave differently e.g.\n - Childchain API:\n   - Must have jsonrpc and id in the request body\n   - **Cannot** have 'Content-Type': 'application/json' header\n   - On error, response contains response.error\n - Watcher API:\n   - **Must** have 'Content-Type': 'application/json' header\n   - On error, response contains response.result === 'error'\n\nAlso on the roadmap is the **Informational API Service** that will provide non-critical convenience APIs.\n\nAll three APIs should behave consistently.\n\n## Proposal\nThe eWallet already has a well defined API, using HTTP-RPC (rather than REST).\n - [eWallet Admin API](https://ewallet.staging.omisego.io/api/admin/docs.ui)\n - [eWallet Client API](https://ewallet.staging.omisego.io/api/client/docs.ui)\n\nWe can follow the same model and ensure consistency across all OMG Network services.\n\n#### eWallet API characteristics\nThe API is a collection of HTTP-RPC style method calls in the format\n```\nEWALLET_URL/api/METHOD_FAMILY.method\n```\nwhere `METHOD_FAMILY` is one of the functional parts of the API e.g. `account`, `transaction`, etc.\n\nResponses contain all data, metadata or errors in the body of the response. This means that HTTP calls always return `200`, even if the result is an error. One exception to this is if an internal server error occurs - in this case it will return `500`\n\nAll HTTP calls are `POST` for consistency.\n\nFollowing this HTTP-RPC style means that the service can be used via websockets as well as HTTP.\n\nExample:\n```\nPOST http://plasma-chain.network/api/account.get_balance\nBODY\n{\n    \"address\": \"0x40d6a26bd478e60f97755d62196f0d0f85c1be0d\"\n}\n\nRESPONSE 200\n{\n  \"version\": \"1\",\n  \"success\": true,\n  \"data\": [\n      {\n          \"currency\": \"0x0000000000000000000000000000000000000000\",\n          \"amount\": 100\n      },\n      {\n          \"currency\": \"0x1234560000000000000000000000000000000000\",\n          \"amount\": 300000\n      }\n  ]\n}\n\nRESPONSE 200\n{\n  \"version\": \"1\",\n  \"success\": false,\n  \"data\": {\n    \"object\": \"error\",\n    \"code\": \"account:not_found\",\n    \"description\": \"Account not found\"\n  }\n}\n\n\nRESPONSE 500\n{\n  \"version\": \"1\",\n  \"success\": false,\n  \"data\": {\n    \"object\": \"error\",\n    \"code\": \"server:internal_server_error\",\n    \"description\": \"Something went wrong on the server\",\n    \"messages\": {\n      \"error_key\": \"error_reason\"\n    }\n  }\n}\n```\n## OMG Network Plasma API\nThere are three services involved\n\n### 1. ChildChain\nNormally a user wouldn't call the ChildChain API directly, as doing so would lose the security features of the Watcher. However there may be some low-stake accounts that don't care or are fine with some amount of trust in the ChildChain operator. These users can call e.g. `submit` on the ChildChain directly.\n\n#### API endpoints\n```\n/block.get\n/transaction.submit\n```\n\n### 2. Watcher\nThe watcher first and foremost plays a critical security role in the system. The watcher monitors the child chain and root chain (Ethereum) for faulty activity.\n\n#### API endpoints\n```\n/transaction.get\n/transaction.get_in_flight_exit\n/transaction.submit\n/utxo.get_exit_data\n/utxo.get_challenge_data\n/status\n```\n\n#### Events\n```\n  new_block\n  new_transaction\n  new_deposit\n  exit_started\n  exit_challenged\n  in_flight_exit_started\n  in_flight_exit_challenged\n  exit_success\n  piggyback\n  invalid_block\n  unchallenged_exit\n  block_withholding\n  invalid_fee_exit\n```\n\n\n### 3. Informational/Convenience API\nThis service may end up being included in the Watcher as optional functionality, but conceptually it can be seen as a separate service. It stores informational data about the chain, and provides convenience APIs to proxy to the child chain/root chain/watcher to ease integration and reduce duplicate code in libraries.\n\n#### API endpoints\n```\n/account.get_balance\n/account.get_utxos\n/account.get_transactions\n/transaction.all\n/transaction.create\n/transaction.get\n/transaction.get_in_flight_exit\n/block.all\n/block.get\n... utxo management apis ...\n```\n\n#### Events\n```\n  transaction_confirmed\n  address_received\n  address_spent\n```\n\n## Architecture\nTo be decided...\n"
  },
  {
    "path": "docs/watcher_db_design.md",
    "content": "# Watcher database design\n\nWatcher benefits from two databases approach:\n* rocksdb - key-value database that child chain state uses for transaction validation\n* watcherDb - PostgreSQL database that stores transactions and contains data needed for challenges and exits. It provides user interface with the data of user's concern (e.g: did I pay the electricity bill, who sent me money last week).\n\n[PostgreSQL Schema Diagram](https://docs.google.com/drawings/d/14_0bfUGGWarndNWwpzA2Nznll4PHLbefvQy05B2LB38/edit?usp=sharing)\n\n## The blocks table\nStores data about blocks: hash, timestamp when block was mined, Ethereum height is was published on.\n\n|**blocks**|||\n|:-:|:-|:-|\n|blknum|bigint|Pk|\n|hash|bytea||\n|timestamp|integer||\n|eth_height|bigint||\n\n## The transactions table\nStores information about transactions.\n\n|**transactions**|||\n|:-:|:-|:-|\n|txhash|bytea|Pk|\n|blknum|integer|Fk(blocks, blknum), UI(blknum, txindex)|\n|txindex|integer|UI^|\n|txbytes|bytea||\n|sent_at|timestamp|UTC (w/o TZ)|\n\n## The transaction inputs and outputs table\nStores inputs and outputs of transactions. Utxo is a record in `txoutputs` table where `spending_txhash` is `NULL`. `proof` field is needed for exiting an utxo. We compute a proof from all the transactions contained in the same block as the transaction that created the utxo.\n\n|**txoutputs**|||\n|:-:|:-|:-|\n|blknum|integer|Pk(blknum, txindex, oindex)|\n|txindex|integer|Pk^|\n|oindex|integer|Pk^|\n|creating_txhash|bytea)|Fk(transactions, (txhash)), NULL|\n|creating_deposit|bytea)|Fk(eth_events, (hash)), NULL|\n|spending_txhash|bytea|Fk(transactions, (txhash)), NULL|\n|spending_exit|bytea|Fk(eth_event, (hash)), NULL|\n|spending_tx_oindex|integer||\n|owner|bytea||\n|amount|numeric(81,0)||\n|currency|bytea||\n|proof|bytea||\n|child_chain_utxohash|bytea|UI|\n|inserted_at|datetime|UTC (w/o TZ)|\n|updated_at|datetime|UTC (w/o TZ)|\n\n## The Ethereum events table\nStores events logged in root contract, such as _deposits_ or _exits_.\n\n|**ethevents**|||\n|:-:|:-|:-|\n|root_chain_txhash|bytea|Pk(root_chain_txhash, log_index)|\n|event_type|integer|Pk^|\n|event_type|varchar(124)||\n|root_chain_txhash_event|bytea|UI|\n|inserted_at|datetime|UTC (w/o TZ)|\n|updated_at|datetime|UTC (w/o TZ)|\n\n\n## The ethevents_txoutputs table\nA table for many-to-many relationships between Ethereum events and UTXOs.\n\n|**ethevents_txoutputs**|||\n|:-:|:-|:-|\n|root_chain_txhash_event|bytea|Pk(root_chain_txhash_event, child_chain_utxohash), FK(ethevents, (root_chain_txhash_event))|\n|child_chain_utxohash|bytea|Pk^, FK(txoutputs, (child_chain_utxohash))|\n|inserted_at|datetime|UTC (w/o TZ)|\n|updated_at|datetime|UTC (w/o TZ)|\n\n\n## Examples of queries against the tables\n\n### 1. get a transaction with inputs and outputs\n```\nselect t.*, i.*, o.*\n  from transactions t\n  join TxOutput i on i.spending_txhash = t.txhash\n  join TxOutput o on o.creating_txhash = t.txhash\n where t.txhash = @txhash\n```\n\n### 2. get all transactions satisfying some criteria\n... As in the previous example but with modified where clause.\n\n### 3. get utxo position by owner address for spend\n```\nselect o.blknum, o.txindex, o.oindex\n  from txoutputs o\n where o.owner = @owner_address\n```\n\n### 4. get utxo position by owner address for exit\n... see previous point\n\n### 5. add utxo when deposit detected\n```\ninsert hash, deposit_blknum, deposit_txindex, event_type\n  into ethevents\nvalues (@hash, @blknum, 0, \"deposit\")\n\ninsert creating_deposit, blknum, txindex, oindex, owner, amount, currency\n  into txoutputs\nvalues (@hash, @blknum, 0, 0, ...)\n```\n\n### 6. spend utxo when exit finalized\n```\ninsert hash, event_type\n  into ethevents\nvalues (@hash, \"exit\")\n\nupdate txoutputs\n   set spending_exit = @hash\n where ...\n```\n\n### 7. get exit data by an utxo position\nFor exit we need: proof that transaction is included in block.\n\n(**NOTE** that this is only optionally served by `WatcherDB`.\nNormally one expects this to be served by the `security-critical` Watcher mode, which doesn't run `WatcherDB`)\n\n```\nselect (t.txhash, t.txbytes)\n  into @out\nfrom transactions t\n  join txoutputs o on o.creating_txhash = t.txhash\n where (t.blknum, t.txindex, o.oindex) = @position\n\nselect t.txhash\n  from transactions t\n where t.blknum  == @blknum\n```\n\n### 8. get all ethereum events for existing utxos\n```\nSELECT CASE WHEN t.child_chain_utxohash IS NULL THEN NULL\n            WHEN t.child_chain_utxohash IS NOT NULL THEN concat('0x', encode(t.child_chain_utxohash::bytea, 'hex'))\n       END AS child_chain_utxohash,\n       CASE WHEN e.root_chain_txhash_event IS NULL THEN NULL\n            WHEN e.root_chain_txhash_event IS NOT NULL THEN concat('0x', encode(e.root_chain_txhash_event::bytea, 'hex'))\n       END AS root_chain_txhash_event,\n       CASE WHEN e.root_chain_txhash IS NULL THEN NULL\n            WHEN e.root_chain_txhash IS NOT NULL THEN concat('0x', encode(e.root_chain_txhash::bytea, 'hex'))\n       END AS root_chain_txhash,\n       blknum, txindex, oindex,\n       amount,\n       log_index,\n       event_type\nFROM txoutputs t\n    LEFT OUTER JOIN ethevents_txoutputs et ON t.child_chain_utxohash = et.child_chain_utxohash\n      LEFT OUTER JOIN ethevents e ON et.root_chain_txhash_event = e.root_chain_txhash_event;\n```\n"
  },
  {
    "path": "dummy",
    "content": ""
  },
  {
    "path": "fees_setup.env",
    "content": "FEE_CLAIMER_ADDRESS=0x3b9f4c1dd26e0be593373b1d36cee2008cbeb837\nFEE_FEED_URL=http://172.27.0.110:4000/api/v1\nSTORED_FEE_UPDATE_INTERVAL_MINUTES=1\nFEE_CHANGE_TOLERANCE_PERCENT=1\nFEE_BUFFER_DURATION_MS=30000\nFEE_ADAPTER=file\nFEE_SPECS_FILE_PATH=/dev-artifacts/fee_specs.dev.json"
  },
  {
    "path": "mix.exs",
    "content": "defmodule OMG.Umbrella.MixProject do\n  use Mix.Project\n\n  def project() do\n    [\n      # name the ap for the sake of `mix coveralls --umbrella`\n      # see https://github.com/parroty/excoveralls/issues/23#issuecomment-339379061\n      apps_path: \"apps\",\n      start_permanent: Mix.env() == :prod,\n      deps: deps(),\n      preferred_cli_env: [\n        coveralls: :test,\n        \"coveralls.detail\": :test,\n        \"coveralls.html\": :test,\n        \"coveralls.circle\": :test,\n        dialyzer: :test\n      ],\n      build_path: \"_build\" <> docker(),\n      deps_path: \"deps\" <> docker(),\n      dialyzer: dialyzer(),\n      test_coverage: [tool: ExCoveralls],\n      # gets all apps test folders for the sake of `mix coveralls --umbrella`\n      test_paths: test_paths(),\n      aliases: aliases(),\n      # Docs\n      source_url: \"https://github.com/omgnetwork/elixir-omg\",\n      version: current_version(),\n      releases: [\n        watcher: [\n          steps: steps(),\n          applications: [\n            tools: :permanent,\n            runtime_tools: :permanent,\n            omg_watcher: :permanent,\n            omg_watcher_rpc: :permanent,\n            omg_status: :permanent,\n            omg_db: :permanent,\n            omg_eth: :permanent,\n            omg_bus: :permanent\n          ],\n          config_providers: [\n            {OMG.Status.ReleaseTasks.SetSentry, [release: :watcher, current_version: current_version()]},\n            {OMG.Status.ReleaseTasks.SetTracer, [release: :watcher]},\n            {OMG.Status.ReleaseTasks.SetApplication, [release: :watcher, current_version: current_version()]},\n            {OMG.Eth.ReleaseTasks.SetEthereumEventsCheckInterval, []},\n            {OMG.Eth.ReleaseTasks.SetEthereumStalledSyncThreshold, []},\n            {OMG.Eth.ReleaseTasks.SetEthereumClient, []},\n            {OMG.Eth.ReleaseTasks.SetContract, []},\n            {OMG.DB.ReleaseTasks.SetKeyValueDB, [release: :watcher]},\n            {OMG.WatcherRPC.ReleaseTasks.SetEndpoint, []},\n            {OMG.WatcherRPC.ReleaseTasks.SetTracer, []},\n            {OMG.WatcherRPC.ReleaseTasks.SetApiMode, :watcher},\n            {OMG.Status.ReleaseTasks.SetLogger, []},\n            {OMG.Watcher.ReleaseTasks.SetEthereumEventsCheckInterval, []},\n            {OMG.Watcher.ReleaseTasks.SetExitProcessorSLAMargin, []},\n            {OMG.Watcher.ReleaseTasks.SetTracer, []},\n            {OMG.Watcher.ReleaseTasks.SetApplication, [release: :watcher, current_version: current_version()]}\n          ]\n        ],\n        watcher_info: [\n          steps: steps(),\n          version: current_version(),\n          applications: [\n            tools: :permanent,\n            runtime_tools: :permanent,\n            omg_watcher: :permanent,\n            omg_watcher_info: :permanent,\n            omg_watcher_rpc: :permanent,\n            omg_status: :permanent,\n            omg_db: :permanent,\n            omg_eth: :permanent,\n            omg_bus: :permanent\n          ],\n          config_providers: [\n            {OMG.Status.ReleaseTasks.SetSentry, [release: :watcher_info, current_version: current_version()]},\n            {OMG.Status.ReleaseTasks.SetTracer, [release: :watcher_info]},\n            {OMG.Status.ReleaseTasks.SetApplication, [release: :watcher_info, current_version: current_version()]},\n            {OMG.Status.ReleaseTasks.SetLogger, []},\n            {OMG.Eth.ReleaseTasks.SetEthereumEventsCheckInterval, []},\n            {OMG.Eth.ReleaseTasks.SetEthereumStalledSyncThreshold, []},\n            {OMG.Eth.ReleaseTasks.SetEthereumClient, []},\n            {OMG.Eth.ReleaseTasks.SetContract, []},\n            {OMG.DB.ReleaseTasks.SetKeyValueDB, [release: :watcher_info]},\n            {OMG.WatcherRPC.ReleaseTasks.SetEndpoint, []},\n            {OMG.WatcherRPC.ReleaseTasks.SetTracer, []},\n            {OMG.WatcherRPC.ReleaseTasks.SetApiMode, :watcher_info},\n            {OMG.Watcher.ReleaseTasks.SetEthereumEventsCheckInterval, []},\n            {OMG.Watcher.ReleaseTasks.SetExitProcessorSLAMargin, []},\n            {OMG.Watcher.ReleaseTasks.SetTracer, []},\n            {OMG.Watcher.ReleaseTasks.SetApplication, [release: :watcher_info, current_version: current_version()]},\n            {OMG.WatcherInfo.ReleaseTasks.SetTracer, []}\n          ]\n        ]\n      ]\n    ]\n  end\n\n  defp test_paths() do\n    \"apps/*/test\" |> Path.wildcard() |> Enum.sort()\n  end\n\n  defp deps() do\n    [\n      {:mix_audit, \"~> 0.1\", only: [:dev, :test], runtime: false},\n      {:dialyxir, \"~> 1.0\", only: [:dev, :test], runtime: false},\n      {:credo, \"~> 1.3\", only: [:dev, :test], runtime: false},\n      # https://github.com/xadhoom/excoveralls.git `52c6c8e5d7fe9abb814e5e3e546c863b9b2b41b7` rebased on `master`\n      # more or less around v0.11.1\n      {:excoveralls, \"~> 0.12.3\"},\n      {:licensir, \"~> 0.2.0\", only: :dev, runtime: false},\n      {\n        :ex_unit_fixtures,\n        git: \"https://github.com/omgnetwork/ex_unit_fixtures\", branch: \"feature/require_files_not_load\", only: [:test]\n      },\n      {:ex_doc, \"~> 0.20.2\", only: :dev, runtime: false},\n      {:spandex, \"~> 3.0.2\"}\n    ]\n  end\n\n  defp aliases() do\n    [\n      test: [\"ecto.create\", \"ecto.migrate\", \"test --no-start\"],\n      coveralls: [\"coveralls --no-start\"],\n      \"coveralls.html\": [\"coveralls.html --no-start\"],\n      \"coveralls.detail\": [\"coveralls.detail --no-start\"],\n      \"coveralls.post\": [\"coveralls.post --no-start\"],\n      \"coveralls.circle\": [\"coveralls.circle --no-start\"],\n      \"ecto.setup\": [\"ecto.create\", \"ecto.migrate\"],\n      \"ecto.reset\": [\"ecto.drop\", \"ecto.setup\"]\n    ]\n  end\n\n  defp dialyzer() do\n    [\n      flags: [:error_handling, :race_conditions, :underspecs, :unknown, :unmatched_returns],\n      ignore_warnings: \"dialyzer.ignore-warnings\",\n      list_unused_filters: true,\n      plt_add_apps: plt_apps(),\n      paths: Enum.map(File.ls!(\"apps\"), fn app -> \"_build#{docker()}/#{Mix.env()}/lib/#{app}/ebin\" end)\n    ]\n  end\n\n  defp plt_apps() do\n    [\n      :briefly,\n      :cowboy,\n      :ex_machina,\n      :ex_unit,\n      :exexec,\n      :fake_server,\n      :iex,\n      :jason,\n      :mix,\n      :plug,\n      :ranch,\n      :sentry,\n      :vmstats\n    ]\n  end\n\n  defp docker(), do: if(System.get_env(\"DOCKER\"), do: \"_docker\", else: \"\")\n\n  defp current_version() do\n    \"git\"\n    |> System.cmd([\"describe\", \"--tags\"])\n    |> elem(0)\n    |> String.replace(\"\\n\", \"\")\n  end\n\n  defp steps() do\n    case Mix.env() do\n      :prod -> [:assemble, :tar]\n      _ -> [:assemble]\n    end\n  end\nend\n"
  },
  {
    "path": "priv/dev-artifacts/README.md",
    "content": "# Dev artifacts\n\nContains various development artifacts that are useful for development environment configs but\nnot to be used in production environments.\n\nFor example:\n\n- `fee_specs.dev.json`: The fee specs used by docker-compose.yml which is only intended for development setup.\n"
  },
  {
    "path": "priv/dev-artifacts/fee_specs.dev.json",
    "content": "{\n    \"1\": {\n        \"0x0000000000000000000000000000000000000000\": {\n            \"amount\": 1,\n            \"pegged_amount\": null,\n            \"pegged_currency\": null,\n            \"pegged_subunit_to_unit\": null,\n            \"subunit_to_unit\": 1000000000000000000,\n            \"updated_at\": \"2019-01-01T10:10:00+00:00\",\n            \"symbol\": \"ETH\",\n            \"type\": \"fixed\"\n        }\n    }\n}\n"
  },
  {
    "path": "priv/dev-artifacts/fee_specs.test.json",
    "content": "{\n}\n"
  },
  {
    "path": "priv/perf/.formatter.exs",
    "content": "# Used by \"mix format\"\n[\n  inputs: [\"mix.exs\", \"config/*.exs\"],\n  line_length: 120,\n  subdirectories: [\"apps/*\"]\n]\n"
  },
  {
    "path": "priv/perf/.gitignore",
    "content": "# The directory Mix will write compiled artifacts to.\n/_build/\n\n# If you run \"mix test --cover\", coverage assets end up here.\n/cover/\n\n# The directory Mix downloads your dependencies sources to.\n/deps/\n\n# Where third-party dependencies like ExDoc output generated docs.\n/doc/\n\n# Ignore .fetch files in case you like to edit your project deps locally.\n/.fetch\n\n# If the VM crashes, it generates a dump, let's ignore it too.\nerl_crash.dump\n\n# Also ignore archive artifacts (built via \"mix archive.build\").\n*.ez\n\n# Open api auto generated elixir client directories\n/apps/child_chain_api/\n/apps/watcher_info_api/\n/apps/watcher_security_critical_api/\n\n# Load test results\n/results/\n"
  },
  {
    "path": "priv/perf/Dockerfile",
    "content": "FROM elixir:1.11.2-alpine\n\nRUN apk add --no-cache rust \\\n        cargo \\\n        git \\\n        curl \\\n        bash \\\n        maven jq \\\n        autoconf \\\n        automake \\\n        gmp \\\n        gmp-dev \\\n        libtool \\\n        gcc \\\n        cmake \\\n        gnupg \\\n        alpine-sdk\n\nCOPY ./ ./elixir-omg\n\nWORKDIR ./elixir-omg\n\nRUN mkdir -p priv/openapitools \\\n        && curl https://raw.githubusercontent.com/OpenAPITools/openapi-generator/v4.3.1/bin/utils/openapi-generator-cli.sh > priv/openapitools/openapi-generator-cli \\\n        && chmod u+x priv/openapitools/openapi-generator-cli\n\nRUN priv/openapitools/openapi-generator-cli generate \\\n        -i https://raw.githubusercontent.com/omgnetwork/omg-childchain-v1/master/apps/omg_child_chain_rpc/priv/swagger/operator_api_specs.yaml \\\n        -g elixir \\\n        -o priv/perf/apps/child_chain_api/\n\nRUN priv/openapitools/openapi-generator-cli generate \\\n        -i apps/omg_watcher_rpc/priv/swagger/security_critical_api_specs.yaml \\\n        -g elixir \\\n        -o priv/perf/apps/watcher_security_critical_api/\n\nRUN priv/openapitools/openapi-generator-cli generate \\\n        -i apps/omg_watcher_rpc/priv/swagger/info_api_specs.yaml \\\n        -g elixir \\\n        -o priv/perf/apps/watcher_info_api/\n\nRUN mix local.hex --force && mix local.rebar --force\n\nWORKDIR ./priv/perf\n\nRUN mix deps.get && mix compile\n"
  },
  {
    "path": "priv/perf/Makefile",
    "content": ".PHONY: list\n\nCOMPOSE_FULL_SERVICES=-f ../../docker-compose.yml -f ../../docker-compose.datadog.yml\n\nlist:\n\t@$(MAKE) -pRrq -f $(lastword $(MAKEFILE_LIST)) : 2>/dev/null | awk -v RS= -F: '/^# File/,/^# Finished Make data base/ {if ($$1 !~ \"^[#.]\") {print $$1}}' | sort | egrep -v -e '^[^[:alnum:]]' -e '^$@$$'\n\nclean:\n\tdocker-compose $(COMPOSE_FULL_SERVICES) down && docker volume prune --force\n\nstart-services:\n\tcd ../../ && \\\n\tSNAPSHOT=SNAPSHOT_MIX_EXIT_PERIOD_SECONDS_120 make init-contracts && \\\n\tcd priv/perf/ && \\\n\tdocker-compose $(COMPOSE_FULL_SERVICES) up -d\n\nstop-services:\n\tdocker-compose $(COMPOSE_FULL_SERVICES) down\n\nlog-services:\n\tdocker-compose $(COMPOSE_FULL_SERVICES) logs -f childchain feefeed watcher watcher_info geth\n\ninit:\n\t. scripts/generate_api_client.sh\n\tmix deps.get\n\ntest: # runs test against child chain and geth provided in test docker containers\n\tLOAD_TEST_FAUCET_PRIVATE_KEY=0xd885a307e35738f773d8c9c63c7a3f3977819274638d04aaf934a1e1158513ce mix test\n\nformat-code-check-warnings:\n\tLOAD_TEST_FAUCET_PRIVATE_KEY=0xd885a307e35738f773d8c9c63c7a3f3977819274638d04aaf934a1e1158513ce MIX_ENV=test mix do compile --warnings-as-errors --ignore-module-conflict --force, test --exclude test\n"
  },
  {
    "path": "priv/perf/README.md",
    "content": "# Perf\n\nUmbrella app for performance/load/stress tests\n\n\n## How to run the tests\n\n### 1. Set up the environment vars\n\n```\nexport CHILD_CHAIN_URL=<childchain api url>\nexport WATCHER_INFO_URL=<watcher-info api url>\nexport ETHEREUM_RPC_URL=<ethereum node url>\nexport CONTRACT_ADDRESS_PLASMA_FRAMEWORK=<address of the plasma framework contract>\nexport CONTRACT_ADDRESS_ETH_VAULT=<address of the eth vault contract>\nexport CONTRACT_ADDRESS_ERC20_VAULT=<address of the erc20 vault contract>\nexport LOAD_TEST_FAUCET_PRIVATE_KEY=<faucet private key>\n```\n\n\n### 2. Generate the open-api client\n ```\nmake init\n```\n\n## Tests with assertions\n\nThese tests check the integrity of the system during their run. They meant to be run with the given rate (tests/second) over the given period (seconds).\n\nDuring test runs, the perf project sends metrics to datadog. After a test finishes its executions, datadog monitor events are checked. If some metrics exceed values set in monitors, the test is marked as failed.\n\nCurrently there two tests are implemented: `deposits` and `transactions` tests.\n\n### Deposits tests\n\nA single iteration of this test consists of the following steps:\n\n1. It creates two accounts: the depositor and the receiver.\n2. It funds depositor with the specified amount (`initial_amount`) on the rootchain.\n3. It creates deposit (`deposited_amount`) with gas price `gas_price` for the depositor account on the childchain and\n   checks balances on the rootchain and the childchain after this deposit.\n4. The depositor account sends the specifed amount (`transferred_amount`) on the childchain to the receiver\n  and checks its balance on the childchain.\n\n### Transactions tests\n\nA single iteration of this test consists of the following steps:\n\n1.1 Two accounts are created - the sender and the receiver\n\n2.1 The sender account is funded with `initial_amount` `token`\n2.2 The balance on the childchain of the sender is validated using WatcherInfoAPI.Api.Account.account_get_balance API.\n2.3 Utxos of the sender are validated using WatcherInfoAPI.Api.Account.account_get_utxos API\n\n3.1 The sender sends all his tokens to the receiver with fee `fee`\n3.2 The balance on the childchain of the sender is validated\n3.3 The balance on the childchain of the receiver is validated\n3.4 Utxos of the sender are validated\n3.5 Utxos of the receiver are validated\n\n### Basic usage\n\nIf you want to run `deposits` with 5 tests / second rate over 20 seconds, you\nshould run the following command:\n\n```bash\n mix run -e \"LoadTest.TestRunner.run()\" -- deposits 5 20\n```\n\n### Help and documentation\n\nTo see up-to-date docs, run:\n\n```bash\nmix run -e \"LoadTest.TestRunner.run()\" -- help\n```\n\nTo see info about a specific test, run:\n\n```bash\n mix run -e \"LoadTest.TestRunner.run()\" -- help transactions\n```\n\n### Docker\n\nThe perf project is packaged into a docker image. So instead of using the project directly, you can run all commands with docker container.\n\nFor example, help command looks like this:\n\n```bash\ndocker run -it omisego/perf:latest mix run -e \"LoadTest.TestRunner.run()\" -- help\n```\n\nTo run deposits tests, use:\n\n```bash\ndocker run -it --env-file ./localchain_contract_addresses.env --network host omisego/perf:latest mix run -e \"LoadTest.TestRunner.run()\" -- \"deposits\" 10 1\n```\n\n## Tests without assertions\n\n\n### 1. Configure the tests\nEdit the config file (e.g. `config/dev.exs`) set the test parameters e.g.\n```\n  childchain_transactions_test_config: %{\n    concurrent_sessions: 100,\n    transactions_per_session: 600,\n    transaction_delay: 1000\n  }\n```\n\nNote that by default the tests use ETH both as the currency spent and as the fee.\nThis makes the code simpler as it doesn't have to manage separate fee utxos.\nHowever, if necessary you can configure the tests to use a different currency. e.g.\n```\nconfig :load_test,\n  test_currency: \"0x942f123b3587EDe66193aa52CF2bF9264C564F87\",\n  fee_amount: 6_000_000_000_000_000,\n```\n\n### 2. Run the tests\n```\nMIX_ENV=<your_env> mix test\n```\n\nOr just `mix test` if you want to run against local services.\n\nYou can specify a particular test on the command line e.g.\n\n```\nMIX_ENV=dev mix test apps/load_test/test/load_tests/runner/childchain_test.exs\n```\n\n**Important** After each test run, you need to wait ~15 seconds before running it again.\nThis is necessary to wait for the faucet account's utxos to be spendable.\nDepending on the watcher-info load, it can take longer than this.\n\nIf you get an error like this\n```\nmodule=LoadTest.Service.Faucet Funding user 0x76f0a3aade31c19d306bc91b46817b95072a8cbd with 2 from utxo: 10800070000⋅\nmodule=LoadTest.ChildChain.Transaction Transaction submission has failed, reason: \"submit:utxo_not_found\"⋅\n```\n\nthen you haven't waited long enough.\nKill it, wait some more, try again.\n\n### Increase connection pool size and connection\nOne can override the setup in config to increase the `pool_size` and `max_connection`.\nIf you found the latency on the api calls are high but the data dog latency shows way smaller,\nit might be latency from setting up the connection instead of real api latency.\n\n### Retrying on errors\nThe Tesla HTTP middleware can be configured to retry on error.\nBy default this is disabled, but it can be enabled by modifying the `retry?` function in `connection_defaults.ex`.\n\nFor example, to retry any 500 response:\n```\n  defp retry?() do\n    fn\n      {:ok, %{status: status}} when status in 500..599 -> true\n      {:ok, _} -> false\n      {:error, _} -> false\n    end\n  end\n```\n\nSee [Tesla.Middleware.Retry](https://hexdocs.pm/tesla/Tesla.Middleware.Retry.html) for more details.\n"
  },
  {
    "path": "priv/perf/apps/load_test/.formatter.exs",
    "content": "# Used by \"mix format\"\n[\n  inputs: [\"{mix,.formatter}.exs\", \"{config,lib,test}/**/*.{ex,exs}\"],\n  line_length: 120\n]\n"
  },
  {
    "path": "priv/perf/apps/load_test/.gitignore",
    "content": "# The directory Mix will write compiled artifacts to.\n/_build/\n\n# If you run \"mix test --cover\", coverage assets end up here.\n/cover/\n\n# The directory Mix downloads your dependencies sources to.\n/deps/\n\n# Where third-party dependencies like ExDoc output generated docs.\n/doc/\n\n# Ignore .fetch files in case you like to edit your project deps locally.\n/.fetch\n\n# If the VM crashes, it generates a dump, let's ignore it too.\nerl_crash.dump\n\n# Also ignore archive artifacts (built via \"mix archive.build\").\n*.ez\n\n# Ignore package tarball (built via \"mix hex.build\").\nload_test-*.tar\n\n# Ignore Chaperon report outputs\n/results/\n"
  },
  {
    "path": "priv/perf/apps/load_test/README.md",
    "content": "# LoadTest\n\nLoad test is load test!\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/application.ex",
    "content": "defmodule LoadTest.Application do\n  @moduledoc \"\"\"\n  Application for the load test\n  \"\"\"\n  use Application\n\n  alias LoadTest.Connection.ConnectionDefaults\n  alias LoadTest.Ethereum.NonceTracker\n  alias LoadTest.Service.Datadog\n  alias LoadTest.Service.Faucet\n\n  def start(_type, _args) do\n    :ok = start_hackney_pool()\n\n    NonceTracker.init()\n\n    children = [{Faucet, fetch_faucet_config()}, {Datadog, []}]\n\n    Supervisor.start_link(children, strategy: :one_for_one, restart: :temporary)\n  end\n\n  defp start_hackney_pool() do\n    pool_size = Application.fetch_env!(:load_test, :pool_size)\n    max_connections = Application.fetch_env!(:load_test, :max_connection)\n\n    :hackney_pool.start_pool(\n      ConnectionDefaults.pool_name(),\n      timeout: 180_000,\n      connect_timeout: 30_000,\n      pool_size: pool_size,\n      max_connections: max_connections\n    )\n  end\n\n  def stop(_app) do\n    :hackney_pool.stop_pool(:perf_pool)\n  end\n\n  defp fetch_faucet_config() do\n    faucet_config_keys = [\n      :faucet_private_key,\n      :fee_amount,\n      :faucet_deposit_amount,\n      :deposit_finality_margin,\n      :gas_price\n    ]\n\n    Enum.map(faucet_config_keys, fn key -> {key, Application.fetch_env!(:load_test, key)} end)\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/child_chain/abi/abi_event_selector.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.ChildChain.Abi.AbiEventSelector do\n  @moduledoc \"\"\"\n  We define Solidity Event selectors that help us decode returned values from function calls.\n  Function names are to be used as inputs to Event Fetcher.\n  Function names describe the type of the event Event Fetcher will retrieve.\n  \"\"\"\n\n  @spec deposit_created() :: ABI.FunctionSelector.t()\n  def deposit_created() do\n    %ABI.FunctionSelector{\n      function: \"DepositCreated\",\n      input_names: [\"depositor\", \"blknum\", \"token\", \"amount\"],\n      inputs_indexed: [true, true, true, false],\n      method_id: <<24, 86, 145, 34>>,\n      returns: [],\n      type: :event,\n      types: [:address, {:uint, 256}, :address, {:uint, 256}]\n    }\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/child_chain/abi/abi_function_selector.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.ChildChain.Abi.AbiFunctionSelector do\n  @moduledoc \"\"\"\n\n  We define Solidity Function selectors that help us decode returned values from function calls\n  \"\"\"\n  # workaround for https://github.com/omgnetwork/elixir-omg/issues/1632\n  def blocks() do\n    %ABI.FunctionSelector{\n      function: \"blocks\",\n      input_names: [\"block_hash\", \"block_timestamp\"],\n      inputs_indexed: nil,\n      method_id: <<242, 91, 63, 153>>,\n      # returns: [bytes: 32, uint: 256],\n      type: :function,\n      # types: [uint: 256]\n      types: [bytes: 32, uint: 256]\n    }\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/child_chain/abi/fields.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.ChildChain.Abi.Fields do\n  @moduledoc \"\"\"\n  Adapt to naming from contracts to elixir-omg.\n\n  I need to do this even though I'm bleeding out of my eyes.\n  \"\"\"\n  def rename(data, %ABI.FunctionSelector{function: \"DepositCreated\"}) do\n    # key is naming coming from plasma contracts\n    # value is what we use\n    contracts_naming = [{\"token\", :currency}, {\"depositor\", :owner}, {\"blknum\", :blknum}, {\"amount\", :amount}]\n\n    reduce_naming(data, contracts_naming)\n  end\n\n  defp reduce_naming(data, contracts_naming) do\n    Enum.reduce(contracts_naming, %{}, fn\n      {old_name, new_name}, acc ->\n        value = Map.get(data, old_name)\n\n        acc\n        |> Map.put_new(new_name, value)\n        |> Map.delete(old_name)\n    end)\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/child_chain/abi.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.ChildChain.Abi do\n  @moduledoc \"\"\"\n  Functions that provide ethereum log decoding\n  \"\"\"\n  alias ExPlasma.Encoding\n  alias LoadTest.ChildChain.Abi.AbiEventSelector\n  alias LoadTest.ChildChain.Abi.AbiFunctionSelector\n  alias LoadTest.ChildChain.Abi.Fields\n\n  def decode_function(enriched_data, signature) do\n    \"0x\" <> data = enriched_data\n    <<method_id::binary-size(4), _::binary>> = elem(ExKeccak.hash_256(signature), 1)\n    method_id |> Encoding.to_hex() |> Kernel.<>(data) |> Encoding.to_binary() |> decode_function()\n  end\n\n  def decode_function(enriched_data) do\n    function_specs =\n      Enum.reduce(AbiFunctionSelector.module_info(:exports), [], fn\n        {:module_info, 0}, acc -> acc\n        {function, 0}, acc -> [apply(AbiFunctionSelector, function, []) | acc]\n        _, acc -> acc\n      end)\n\n    {function_spec, data} = ABI.find_and_decode(function_specs, enriched_data)\n    decode_function_call_result(function_spec, data)\n  end\n\n  def decode_log(log) do\n    event_specs =\n      Enum.reduce(AbiEventSelector.module_info(:exports), [], fn\n        {:module_info, 0}, acc -> acc\n        {function, 0}, acc -> [apply(AbiEventSelector, function, []) | acc]\n        _, acc -> acc\n      end)\n\n    topics =\n      Enum.map(log[\"topics\"], fn\n        nil -> nil\n        topic -> Encoding.to_binary(topic)\n      end)\n\n    data = Encoding.to_binary(log[\"data\"])\n\n    {event_spec, data} =\n      ABI.Event.find_and_decode(\n        event_specs,\n        Enum.at(topics, 0),\n        Enum.at(topics, 1),\n        Enum.at(topics, 2),\n        Enum.at(topics, 3),\n        data\n      )\n\n    data\n    |> Enum.into(%{}, fn {key, _type, _indexed, value} -> {key, value} end)\n    |> Fields.rename(event_spec)\n    |> common_parse_event(log)\n  end\n\n  def common_parse_event(\n        result,\n        %{\"blockNumber\" => eth_height, \"transactionHash\" => root_chain_txhash, \"logIndex\" => log_index} = event\n      ) do\n    # NOTE: we're using `put_new` here, because `merge` would allow us to overwrite data fields in case of conflict\n    result\n    |> Map.put_new(:eth_height, Encoding.to_int(eth_height))\n    |> Map.put_new(:root_chain_txhash, Encoding.to_binary(root_chain_txhash))\n    |> Map.put_new(:log_index, Encoding.to_int(log_index))\n    # just copy `event_signature` over, if it's present (could use tidying up)\n    |> Map.put_new(:event_signature, event[:event_signature])\n  end\n\n  defp decode_function_call_result(function_spec, [values]) when is_tuple(values) do\n    function_spec.input_names\n    |> Enum.zip(Tuple.to_list(values))\n    |> Enum.into(%{})\n    |> Fields.rename(function_spec)\n  end\n\n  # workaround for https://github.com/omgnetwork/elixir-omg/issues/1632\n  defp decode_function_call_result(%{function: \"startExit\"} = function_spec, values) do\n    function_spec.input_names\n    |> Enum.zip(values)\n    |> Enum.into(%{})\n    |> Fields.rename(function_spec)\n  end\n\n  defp decode_function_call_result(function_spec, values) do\n    function_spec.input_names\n    |> Enum.zip(values)\n    |> Enum.into(%{})\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/child_chain/deposit.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.ChildChain.Deposit do\n  @moduledoc \"\"\"\n  Utility functions for deposits on a child chain\n  \"\"\"\n\n  require Logger\n\n  alias ExPlasma.Encoding\n  alias ExPlasma.Transaction.Deposit\n  alias ExPlasma.Utxo\n  alias LoadTest.Ethereum\n  alias LoadTest.Ethereum.Account\n  alias LoadTest.Service.Sync\n\n  @eth <<0::160>>\n\n  @doc \"\"\"\n  Deposits funds into the childchain.\n\n  If currency is ETH, funds will be deposited into the EthVault.\n  If currency is ERC20, 'approve()' will be called before depositing funds into the Erc20Vault.\n\n  This function accepts three required parameters:\n  1. depositor account\n  2. the amount to be deposited\n  3. currency\n  4. the number of verifications\n  5. gas price of the transaction\n  6. return - it can be :utxo or :txhash\n\n  Returns the utxo created by the deposit or the hash of the the deposit transaction.\n  \"\"\"\n  @spec deposit_from(Account.t(), pos_integer(), Account.t(), non_neg_integer(), non_neg_integer, atom()) ::\n          Utxo.t() | binary()\n  def deposit_from(depositor, amount, currency, deposit_finality_margin, gas_price, return) do\n    deposit_utxo = %Utxo{amount: amount, owner: depositor.addr, currency: currency}\n\n    {:ok, deposit} = Deposit.new(deposit_utxo)\n    {:ok, {deposit_blknum, eth_blknum, eth_txhash}} = send_deposit(deposit, depositor, amount, currency, gas_price)\n\n    :ok = wait_deposit_finality(eth_blknum, deposit_finality_margin)\n\n    case return do\n      :utxo -> Utxo.new(%{blknum: deposit_blknum, txindex: 0, oindex: 0, amount: amount})\n      _ -> eth_txhash\n    end\n  end\n\n  defp send_deposit(deposit, account, value, @eth, gas_price) do\n    vault_address = Application.fetch_env!(:load_test, :eth_vault_address)\n    do_deposit(vault_address, deposit, account, value, gas_price)\n  end\n\n  defp send_deposit(deposit, account, value, erc20_contract, gas_price) do\n    vault_address = Application.fetch_env!(:load_test, :erc20_vault_address)\n\n    # First have to approve the token\n    {:ok, tx_hash} = approve(erc20_contract, vault_address, account, value, gas_price)\n    {:ok, _} = Ethereum.transact_sync(tx_hash)\n\n    # Note that when depositing erc20 tokens, then tx value must be 0\n    do_deposit(vault_address, deposit, account, 0, gas_price)\n  end\n\n  defp do_deposit(vault_address, deposit, account, value, gas_price) do\n    %{data: deposit_data} = LoadTest.Utils.Encoding.encode_deposit(deposit)\n\n    tx = %LoadTest.Ethereum.Transaction{\n      to: Encoding.to_binary(vault_address),\n      value: value,\n      gas_price: gas_price,\n      gas_limit: 200_000,\n      data: Encoding.to_binary(deposit_data)\n    }\n\n    {:ok, tx_hash} = Ethereum.send_raw_transaction(tx, account)\n    {:ok, %{\"blockNumber\" => eth_blknum}} = Ethereum.transact_sync(tx_hash)\n\n    {:ok, %{\"logs\" => logs}} = Ethereumex.HttpClient.eth_get_transaction_receipt(tx_hash)\n\n    %{\"topics\" => [_topic, _addr, blknum | _]} =\n      Enum.find(logs, fn %{\"address\" => address} -> address == vault_address end)\n\n    {:ok, {Encoding.to_int(blknum), eth_blknum, tx_hash}}\n  end\n\n  defp wait_deposit_finality(deposit_eth_blknum, finality_margin) do\n    func = fn ->\n      {:ok, current_blknum} = Ethereumex.HttpClient.eth_block_number()\n      current_blknum = Encoding.to_int(current_blknum)\n\n      if current_blknum >= deposit_eth_blknum + finality_margin do\n        :ok\n      end\n    end\n\n    Sync.repeat_until_success(func, :infinity, \"Waiting for deposit finality\")\n  end\n\n  defp approve(contract, vault_address, account, value, gas_price) do\n    data = ABI.encode(\"approve(address,uint256)\", [Encoding.to_binary(vault_address), value])\n\n    tx = %LoadTest.Ethereum.Transaction{\n      to: contract,\n      gas_price: gas_price,\n      gas_limit: 200_000,\n      data: data\n    }\n\n    Ethereum.send_raw_transaction(tx, account)\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/child_chain/exit.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.ChildChain.Exit do\n  @moduledoc \"\"\"\n  Utility functions for exits on a child chain.\n  \"\"\"\n\n  require Logger\n\n  alias ExPlasma.Encoding\n  alias ExPlasma.Utxo\n  alias LoadTest.ChildChain.Transaction\n  alias LoadTest.Ethereum\n  alias LoadTest.Ethereum.Account\n  alias LoadTest.Ethereum.Crypto\n  alias LoadTest.Service.Sync\n\n  @gas_start_exit 500_000\n  @gas_challenge_exit 300_000\n  @gas_add_exit_queue 800_000\n  @standard_exit_bond 14_000_000_000_000_000\n\n  @doc \"\"\"\n  Returns the exit data of a utxo.\n  \"\"\"\n  @spec get_exit_data(Utxo.t()) :: any()\n  def get_exit_data(%Utxo{} = utxo), do: get_exit_data(Utxo.pos(utxo))\n\n  @spec get_exit_data(non_neg_integer()) :: any()\n  def get_exit_data(utxo_pos) do\n    body = %WatcherSecurityCriticalAPI.Model.UtxoPositionBodySchema{\n      utxo_pos: utxo_pos\n    }\n\n    {:ok, response} =\n      WatcherSecurityCriticalAPI.Api.UTXO.utxo_get_exit_data(\n        LoadTest.Connection.WatcherSecurity.client(),\n        body\n      )\n\n    data = Jason.decode!(response.body)[\"data\"]\n\n    %{\n      proof: data[\"proof\"],\n      txbytes: data[\"txbytes\"],\n      utxo_pos: data[\"utxo_pos\"]\n    }\n  end\n\n  @doc \"\"\"\n  Retries until the exit data of a utxo is found.\n  \"\"\"\n  @spec wait_for_exit_data(Utxo.t(), pos_integer()) :: any()\n  def wait_for_exit_data(utxo_pos, timeout \\\\ 100_000) do\n    func = fn ->\n      data = get_exit_data(utxo_pos)\n\n      if not is_nil(data.proof) do\n        {:ok, data}\n      end\n    end\n\n    {:ok, result} = Sync.repeat_until_success(func, timeout, \"waiting for exit data\")\n\n    result\n  end\n\n  @doc \"\"\"\n  Starts an exit.\n  \"\"\"\n  @spec start_exit(any(), Account.t(), pos_integer()) :: any()\n  def start_exit(exit_data, from, gas_price) do\n    data =\n      ABI.encode(\n        \"startStandardExit((uint256,bytes,bytes))\",\n        [\n          {\n            exit_data.utxo_pos,\n            Encoding.to_binary(exit_data.txbytes),\n            Encoding.to_binary(exit_data.proof)\n          }\n        ]\n      )\n\n    tx = %Ethereum.Transaction{\n      to: contract_address_payment_exit_game(),\n      value: @standard_exit_bond,\n      gas_price: gas_price,\n      gas_limit: @gas_start_exit,\n      data: data\n    }\n\n    {:ok, tx_hash} = Ethereum.send_raw_transaction(tx, from)\n    tx_hash\n  end\n\n  def add_exit_queue(vault_id, token, from, gas_price) do\n    if has_exit_queue?(vault_id, token) do\n      _ = Logger.info(\"Exit queue was already added.\")\n    else\n      _ = Logger.info(\"Exit queue missing. Adding...\")\n\n      data =\n        ABI.encode(\n          \"addExitQueue(uint256,address)\",\n          [vault_id, token]\n        )\n\n      tx = %Ethereum.Transaction{\n        to: Encoding.to_binary(Application.fetch_env!(:load_test, :contract_address_plasma_framework)),\n        gas_price: gas_price,\n        gas_limit: @gas_add_exit_queue,\n        data: data\n      }\n\n      {:ok, receipt_hash} = Ethereum.send_raw_transaction(tx, from)\n      Ethereum.transact_sync(receipt_hash)\n      wait_for_exit_queue(vault_id, token)\n      receipt_hash\n    end\n  end\n\n  defp wait_for_exit_queue(vault_id, token, timeout \\\\ 100_000) do\n    func = fn ->\n      if has_exit_queue?(vault_id, token) do\n        :ok\n      end\n    end\n\n    Sync.repeat_until_success(func, timeout, \"waiting for exit queue\")\n  end\n\n  defp has_exit_queue?(vault_id, token) do\n    data =\n      ABI.encode(\n        \"hasExitQueue(uint256,address)\",\n        [vault_id, token]\n      )\n\n    {:ok, receipt_enc} =\n      Ethereumex.HttpClient.eth_call(%{\n        from: Application.fetch_env!(:load_test, :contract_address_plasma_framework),\n        to: Application.fetch_env!(:load_test, :contract_address_plasma_framework),\n        data: Encoding.to_hex(data)\n      })\n\n    receipt_enc\n    |> Encoding.to_binary()\n    |> ABI.TypeDecoder.decode([:bool])\n    |> hd()\n  end\n\n  def challenge_exit(exit_id, exiting_tx, challenge_tx, input_index, challenge_tx_sig, from) do\n    opts = Keyword.put(tx_defaults(), :gas, @gas_challenge_exit)\n    sender_data = Crypto.hash(from)\n\n    contract = contract_address_payment_exit_game()\n    signature = \"challengeStandardExit((uint160,bytes,bytes,uint16,bytes,bytes32))\"\n    args = [{exit_id, exiting_tx, challenge_tx, input_index, challenge_tx_sig, sender_data}]\n\n    {:ok, transaction_hash} = Ethereum.contract_transact(from, contract, signature, args, opts)\n\n    Encoding.to_hex(transaction_hash)\n  end\n\n  def tx_defaults() do\n    Transaction.tx_defaults()\n  end\n\n  defp contract_address_payment_exit_game() do\n    :load_test\n    |> Application.fetch_env!(:contract_address_payment_exit_game)\n    |> Encoding.to_binary()\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/child_chain/transaction.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\ndefmodule LoadTest.ChildChain.Transaction do\n  @moduledoc \"\"\"\n  Utility functions for sending transaction to child chain\n  \"\"\"\n  require Logger\n\n  alias ChildChainAPI.Api\n  alias ChildChainAPI.Model\n  alias ExPlasma.Encoding\n  alias ExPlasma.Transaction\n  alias ExPlasma.Utxo\n  alias LoadTest.Connection.ChildChain, as: Connection\n  alias LoadTest.Service.Metrics\n  alias LoadTest.Service.Sync\n\n  # safe, reasonable amount, equal to the testnet block gas limit\n  @lots_of_gas 5_712_388\n  @gas_price 1_000_000_000\n\n  @doc \"\"\"\n  Spends a utxo.\n\n  Creates, signs and submits a transaction using the utxo as the input,\n  one output with the amount and receiver address and another output if there is any change.\n\n  Returns the utxos created by the transaction. If a change utxo was created, it will be the first in the list.\n\n  Note that input must cover fees, so the currency must be a fee paying currency.\n  \"\"\"\n  @spec spend_utxo(\n          Utxo.t(),\n          pos_integer(),\n          pos_integer(),\n          LoadTest.Ethereum.Account.t(),\n          LoadTest.Ethereum.Account.t(),\n          Utxo.address_binary(),\n          pos_integer()\n        ) :: list(Utxo.t())\n  def spend_utxo(utxo, amount, fee, signer, receiver, currency, retries \\\\ 120_000)\n\n  def spend_utxo(utxo, amount, fee, signer, receiver, currency, timeout) when byte_size(currency) == 20 do\n    change_amount = utxo.amount - amount - fee\n    receiver_output = %Utxo{owner: receiver.addr, currency: currency, amount: amount}\n    do_spend(utxo, receiver_output, change_amount, currency, signer, timeout)\n  end\n\n  def spend_utxo(utxo, amount, fee, signer, receiver, currency, timeout) do\n    spend_utxo(utxo, amount, fee, signer, receiver, Encoding.to_binary(currency), timeout)\n  end\n\n  def tx_defaults() do\n    Enum.map([value: 0, gasPrice: @gas_price, gas: @lots_of_gas], fn {k, v} -> {k, Encoding.to_hex(v)} end)\n  end\n\n  @doc \"\"\"\n  Submits a transaction\n\n  Creates a transaction from the given inputs and outputs, signs it and submits it to the childchain.\n\n  Returns the utxos created by the transaction.\n  \"\"\"\n  @spec submit_tx(\n          list(Utxo.output_map()),\n          list(Utxo.input_map()),\n          list(LoadTest.Ethereum.Account.t()),\n          pos_integer()\n        ) :: list(Utxo.t())\n  def submit_tx(inputs, outputs, signers, retries \\\\ 120_000) do\n    {:ok, tx} = Transaction.Payment.new(%{inputs: inputs, outputs: outputs})\n\n    keys =\n      signers\n      |> Enum.map(&Map.get(&1, :priv))\n      |> Enum.map(&Encoding.to_hex/1)\n\n    {:ok, blknum, txindex} =\n      tx\n      |> Transaction.sign(keys: keys)\n      |> try_submit_tx(retries)\n\n    outputs\n    |> Enum.with_index()\n    |> Enum.map(fn {output, i} ->\n      %Utxo{blknum: blknum, txindex: txindex, oindex: i, amount: output.amount, currency: output.currency}\n    end)\n  end\n\n  def recover(encoded_signed_tx) do\n    {:ok, trx} =\n      encoded_signed_tx\n      |> Encoding.to_binary()\n      |> ExRLP.decode()\n      |> reconstruct()\n\n    trx\n  end\n\n  defp reconstruct([raw_witnesses | typed_tx_rlp_decoded_chunks]) do\n    with true <- is_list(raw_witnesses) || {:error, :malformed_witnesses},\n         true <- Enum.all?(raw_witnesses, &valid_witness?/1) || {:error, :malformed_witnesses},\n         {:ok, raw_tx} <- reconstruct_transaction(typed_tx_rlp_decoded_chunks) do\n      {:ok, %{raw_tx: raw_tx, sigs: raw_witnesses}}\n    end\n  end\n\n  defp do_spend(_input, _output, change_amount, _currency, _signer, _retries) when change_amount < 0 do\n    :error_insufficient_funds\n  end\n\n  defp do_spend(input, output, 0, _currency, signer, retries) do\n    submit_tx([input], [output], [signer], retries)\n  end\n\n  defp do_spend(input, output, change_amount, currency, signer, retries) do\n    change_output = %Utxo{owner: signer.addr, currency: currency, amount: change_amount}\n    submit_tx([input], [change_output, output], [signer], retries)\n  end\n\n  defp valid_witness?(witness) when is_binary(witness), do: byte_size(witness) == 65\n  defp valid_witness?(_), do: false\n\n  defp reconstruct_transaction([raw_type, inputs_rlp, outputs_rlp, tx_data_rlp, metadata_rlp])\n       when is_binary(raw_type) do\n    with {:ok, 1} <- parse_uint256(raw_type),\n         {:ok, inputs} <- parse_inputs(inputs_rlp),\n         {:ok, outputs} <- parse_outputs(outputs_rlp),\n         {:ok, tx_data} <- parse_uint256(tx_data_rlp),\n         0 <- tx_data,\n         {:ok, metadata} <- validate_metadata(metadata_rlp) do\n      {:ok, %{tx_type: 1, inputs: inputs, outputs: outputs, metadata: metadata}}\n    else\n      _ -> {:error, :unrecognized_transaction_type}\n    end\n  end\n\n  defp reconstruct_transaction([tx_type, outputs_rlp, nonce_rlp]) do\n    with {:ok, 3} <- parse_uint256(tx_type),\n         {:ok, outputs} <- parse_outputs(outputs_rlp),\n         {:ok, nonce} <- reconstruct_nonce(nonce_rlp) do\n      {:ok, %{tx_type: 3, outputs: outputs, nonce: nonce}}\n    end\n  end\n\n  defp reconstruct_nonce(nonce) when is_binary(nonce) and byte_size(nonce) == 32, do: {:ok, nonce}\n  defp reconstruct_nonce(_), do: {:error, :malformed_nonce}\n\n  defp validate_metadata(metadata) when is_binary(metadata) and byte_size(metadata) == 32, do: {:ok, metadata}\n  defp validate_metadata(_), do: {:error, :malformed_metadata}\n\n  defp parse_inputs(inputs_rlp) do\n    with true <- Enum.count(inputs_rlp) <= 4 || {:error, :too_many_inputs},\n         # NOTE: workaround for https://github.com/omgnetwork/ex_plasma/issues/19.\n         #       remove, when this is blocked on `ex_plasma` end\n         true <- Enum.all?(inputs_rlp, &(&1 != <<0::256>>)) || {:error, :malformed_inputs},\n         do: {:ok, Enum.map(inputs_rlp, &parse_input!/1)}\n  rescue\n    _ -> {:error, :malformed_inputs}\n  end\n\n  defp parse_input!(encoded) do\n    {:ok, result} = decode_position(encoded)\n\n    result\n  end\n\n  defp decode_position(encoded) when is_number(encoded) and encoded <= 0, do: {:error, :encoded_utxo_position_too_low}\n  defp decode_position(encoded) when is_integer(encoded) and encoded > 0, do: do_decode_position(encoded)\n  defp decode_position(encoded) when is_binary(encoded) and byte_size(encoded) == 32, do: do_decode_position(encoded)\n\n  defp do_decode_position(encoded) do\n    ExPlasma.Utxo.new(encoded)\n  end\n\n  defp parse_outputs(outputs_rlp) do\n    outputs = Enum.map(outputs_rlp, &parse_output!/1)\n\n    with true <- Enum.count(outputs) <= 4 || {:error, :too_many_outputs},\n         nil <- Enum.find(outputs, &match?({:error, _}, &1)),\n         do: {:ok, outputs}\n  rescue\n    _ -> {:error, :malformed_outputs}\n  end\n\n  defp parse_output!(rlp_data) do\n    {:ok, result} = ExPlasma.Utxo.new(rlp_data)\n\n    result\n  end\n\n  defp parse_uint256(<<0>> <> _binary), do: {:error, :leading_zeros_in_encoded_uint}\n  defp parse_uint256(binary) when byte_size(binary) <= 32, do: {:ok, :binary.decode_unsigned(binary, :big)}\n  defp parse_uint256(binary) when byte_size(binary) > 32, do: {:error, :encoded_uint_too_big}\n  defp parse_uint256(_), do: {:error, :malformed_uint256}\n\n  defp try_submit_tx(tx, timeout) do\n    {:ok, {blknum, txindex}} =\n      Sync.repeat_until_success(fn -> do_submit_tx(tx) end, timeout, \"Failed to submit transaction\")\n\n    {:ok, blknum, txindex}\n  end\n\n  defp do_submit_tx(tx) do\n    Metrics.run_with_metrics(\n      fn ->\n        submit_request(tx)\n      end,\n      \"Childchain.submit\"\n    )\n  end\n\n  defp submit_request(tx) do\n    {:ok, response} =\n      tx\n      |> Transaction.encode()\n      |> do_submit_tx_rpc()\n\n    response\n    |> Map.fetch!(:body)\n    |> Jason.decode!()\n    |> Map.fetch!(\"data\")\n    |> case do\n      %{\"blknum\" => blknum, \"txindex\" => txindex} ->\n        _ = Logger.debug(\"[Transaction submitted successfully {#{inspect(blknum)}, #{inspect(txindex)}}\")\n        {:ok, {blknum, txindex}}\n\n      %{\"code\" => reason} ->\n        _ =\n          Logger.warn(\"Transaction submission has failed, reason: #{inspect(reason)}, tx inputs: #{inspect(tx.inputs)}\")\n\n        {:error, reason}\n    end\n  end\n\n  @spec do_submit_tx_rpc(binary) :: {:ok, map} | {:error, any}\n  defp do_submit_tx_rpc(encoded_tx) do\n    body = %Model.TransactionSubmitBodySchema{\n      transaction: Encoding.to_hex(encoded_tx)\n    }\n\n    Api.Transaction.submit(Connection.client(), body)\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/child_chain/utxos.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.ChildChain.Utxos do\n  @moduledoc \"\"\"\n  Utility functions for utxos\n  \"\"\"\n  require Logger\n\n  alias ExPlasma.Encoding\n  alias ExPlasma.Utxo\n  alias LoadTest.ChildChain.Transaction\n  alias LoadTest.Service.Sync\n\n  @doc \"\"\"\n  Returns an addresses utxos.\n  \"\"\"\n  @spec get_utxos(Utxo.address_binary()) :: list(Utxo.t())\n  def get_utxos(address) do\n    body = %WatcherInfoAPI.Model.AddressBodySchema1{\n      address: Encoding.to_hex(address)\n    }\n\n    {:ok, response} =\n      WatcherInfoAPI.Api.Account.account_get_utxos(\n        LoadTest.Connection.WatcherInfo.client(),\n        body\n      )\n\n    utxos = Jason.decode!(response.body)[\"data\"]\n\n    Enum.map(\n      utxos,\n      fn x ->\n        %Utxo{\n          blknum: x[\"blknum\"],\n          txindex: x[\"txindex\"],\n          oindex: x[\"oindex\"],\n          currency: x[\"currency\"],\n          amount: x[\"amount\"]\n        }\n      end\n    )\n  end\n\n  @doc \"\"\"\n  Returns the highest value utxo of a given currency\n  \"\"\"\n  @spec get_largest_utxo(list(Utxo.t()), Utxo.address_binary()) :: Utxo.t()\n  def get_largest_utxo([], _currency), do: nil\n\n  def get_largest_utxo(utxos, currency) do\n    utxos\n    |> Enum.filter(fn utxo -> currency == LoadTest.Utils.Encoding.from_hex(utxo.currency) end)\n    |> Enum.max_by(fn x -> x.amount end, fn -> nil end)\n  end\n\n  @doc \"\"\"\n  Merges all the given utxos into one.\n  Note that this can take several iterations to complete.\n  \"\"\"\n  @spec merge(list(Utxo.t()), Utxo.address_binary(), Account.t()) :: Utxo.t()\n  def merge(utxos, currency, faucet_account) do\n    utxos\n    |> Enum.filter(fn utxo -> LoadTest.Utils.Encoding.from_hex(utxo.currency) == currency end)\n    |> merge(faucet_account)\n  end\n\n  @spec merge(list(Utxo.t()), Account.t()) :: Utxo.t()\n  defp merge([], _faucet_account), do: :error_empty_utxo_list\n  defp merge([single_utxo], _faucet_account), do: single_utxo\n\n  defp merge(utxos, faucet_account) when length(utxos) > 4 do\n    utxos\n    |> Enum.chunk_every(4)\n    |> Enum.map(fn inputs -> merge(inputs, faucet_account) end)\n    |> merge(faucet_account)\n  end\n\n  defp merge([%{currency: currency} | _] = inputs, faucet_account) do\n    tx_amount = Enum.reduce(inputs, 0, fn x, acc -> x.amount + acc end)\n    output = %Utxo{amount: tx_amount, currency: currency, owner: faucet_account.addr}\n\n    [utxo] =\n      Transaction.submit_tx(\n        inputs,\n        [output],\n        List.duplicate(faucet_account, length(inputs))\n      )\n\n    utxo\n  end\n\n  @doc \"\"\"\n  Retries until the utxo is found.\n  \"\"\"\n  @spec wait_for_utxo(Utxo.address_binary(), Utxo.t(), pos_integer()) :: :ok\n  def wait_for_utxo(address, utxo, timeout \\\\ 100_000) do\n    func = fn ->\n      find_utxo(address, utxo)\n    end\n\n    Sync.repeat_until_success(func, timeout, \"waiting for utxo\")\n  end\n\n  defp find_utxo(address, utxo) do\n    utxos = get_utxos(address)\n\n    if Enum.find(utxos, fn x -> Utxo.pos(x) == Utxo.pos(utxo) end) do\n      :ok\n    end\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/child_chain/watcher_sync.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.ChildChain.WatcherSync do\n  @moduledoc \"\"\"\n  Wait for the watcher to sync to a certain root chain block height\n  \"\"\"\n\n  require Logger\n\n  alias LoadTest.Service.Sync\n\n  @doc \"\"\"\n  Blocks the caller until the watcher configured reports to be fully synced up (both child chain blocks and eth events)\n\n  Options:\n    - :root_chain_height - if not `nil`, in addition to synchronizing to current top mined child chain block, it will\n      sync up till all the Watcher's services report at at least this Ethereum height\n  \"\"\"\n  @spec watcher_synchronize(keyword()) :: :ok\n  def watcher_synchronize(opts \\\\ []) do\n    root_chain_height = Keyword.get(opts, :root_chain_height, nil)\n    service = Keyword.get(opts, :service, nil)\n\n    _ = Logger.info(\"Waiting for the watcher to synchronize\")\n\n    :ok =\n      Sync.repeat_until_success(\n        fn -> watcher_synchronized?(root_chain_height, service) end,\n        500_000,\n        \"Failed to sync watcher\"\n      )\n\n    # NOTE: allowing some more time for the dust to settle on the synced Watcher\n    # otherwise some of the freshest UTXOs to exit will appear as missing on the Watcher\n    # related issue to remove this `sleep` and fix properly is https://github.com/omgnetwork/elixir-omg/issues/1031\n    Process.sleep(2000)\n    _ = Logger.info(\"Watcher synchronized\")\n  end\n\n  # This function is prepared to be called in `Sync`.\n  # It repeatedly ask for Watcher's `/status.get` until Watcher consume mined block\n  defp watcher_synchronized?(root_chain_height, service) do\n    {:ok, status_response} =\n      WatcherSecurityCriticalAPI.Api.Status.status_get(LoadTest.Connection.WatcherSecurity.client())\n\n    status = Jason.decode!(status_response.body)[\"data\"]\n\n    with true <- watcher_synchronized_to_mined_block?(status),\n         true <- root_chain_synced?(root_chain_height, status, service) do\n      :ok\n    else\n      _ -> :repeat\n    end\n  end\n\n  defp root_chain_synced?(nil, _, _), do: true\n\n  defp root_chain_synced?(root_chain_height, status, nil) do\n    status\n    |> Map.get(\"services_synced_heights\")\n    |> Enum.reject(fn height ->\n      service = height[\"service\"]\n      # these service heights are stuck on circle ci, but they work fine locally\n      # I think ci machine is not powerful enough\n      service == \"block_getter\" || service == \"exit_finalizer\" || service == \"ife_exit_finalizer\"\n    end)\n    |> Enum.all?(&(&1[\"height\"] >= root_chain_height))\n  end\n\n  defp root_chain_synced?(root_chain_height, status, service) do\n    heights = Map.get(status, \"services_synced_heights\")\n\n    found_root_chain_height = Enum.find(heights, fn height -> height[\"service\"] == service end)\n\n    found_root_chain_height && found_root_chain_height[\"height\"] >= root_chain_height\n  end\n\n  defp watcher_synchronized_to_mined_block?(%{\n         \"last_mined_child_block_number\" => last_mined_child_block_number,\n         \"last_validated_child_block_number\" => last_validated_child_block_number\n       })\n       when last_mined_child_block_number == last_validated_child_block_number and\n              last_mined_child_block_number > 0 do\n    _ = Logger.debug(\"Synced to blknum: #{last_validated_child_block_number}\")\n    true\n  end\n\n  defp watcher_synchronized_to_mined_block?(_params) do\n    :not_synchronized\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/connection/child_chain.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\ndefmodule LoadTest.Connection.ChildChain do\n  @moduledoc \"\"\"\n  Module that overrides the Tesla middleware with the url from config.\n  \"\"\"\n\n  alias LoadTest.Connection.ConnectionDefaults\n\n  def client() do\n    base_url = Application.fetch_env!(:load_test, :child_chain_url)\n    middleware = [{Tesla.Middleware.BaseUrl, base_url} | ConnectionDefaults.middleware()]\n\n    Tesla.client(middleware)\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/connection/connection_defaults.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\ndefmodule LoadTest.Connection.ConnectionDefaults do\n  @moduledoc \"\"\"\n  Utility functions shared between all connection modules\n  \"\"\"\n\n  @doc \"\"\"\n  Returns Tesla middleware common for all connections.\n  \"\"\"\n  def middleware() do\n    [\n      {Tesla.Middleware.EncodeJson, engine: Poison},\n      {Tesla.Middleware.Headers, [{\"user-agent\", \"Load-Test\"}, {\"Content-Type\", \"application/json\"}]},\n      {Tesla.Middleware.Retry, delay: 500, max_retries: 10, max_delay: 45_000, should_retry: retry?()},\n      {Tesla.Middleware.Timeout, timeout: 30_000},\n      {Tesla.Middleware.Opts, [adapter: [recv_timeout: 30_000, pool: pool_name()]]}\n    ]\n  end\n\n  @doc \"\"\"\n  Returns connection pool name\n  \"\"\"\n  def pool_name(), do: :perf_pool\n\n  # Don't automatically retry on error\n  # It _can_ sometimes be useful to retry though, so if you need it return true here\n  # See README.md for more info\n  defp retry?() do\n    fn\n      _ -> false\n    end\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/connection/watcher_info.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\ndefmodule LoadTest.Connection.WatcherInfo do\n  @moduledoc \"\"\"\n  Module that overrides the Tesla middleware with the url from config.\n  \"\"\"\n\n  alias LoadTest.Connection.ConnectionDefaults\n\n  def client() do\n    base_url = Application.fetch_env!(:load_test, :watcher_info_url)\n    middleware = [{Tesla.Middleware.BaseUrl, base_url} | ConnectionDefaults.middleware()]\n\n    Tesla.client(middleware)\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/connection/watcher_security.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\ndefmodule LoadTest.Connection.WatcherSecurity do\n  @moduledoc \"\"\"\n  Module that overrides the Tesla middleware with the url from config.\n  \"\"\"\n\n  alias LoadTest.Connection.ConnectionDefaults\n\n  def client() do\n    base_url = Application.fetch_env!(:load_test, :watcher_security_url)\n    middleware = [{Tesla.Middleware.BaseUrl, base_url} | ConnectionDefaults.middleware()]\n\n    Tesla.client(middleware)\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/ethereum/account.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.Ethereum.Account do\n  @moduledoc \"\"\"\n  Functions for creating Ethereum accounts\n  \"\"\"\n\n  require Logger\n\n  alias ExPlasma.Encoding\n  alias LoadTest.Ethereum.Crypto\n\n  @type private_key_t :: <<_::256>>\n  @type private_key_hex_t :: <<_::512>> | <<_::528>>\n  @type public_key_t :: <<_::512>>\n  @type addr_t :: <<_::160>>\n  @type t :: %__MODULE__{\n          priv: private_key_t(),\n          pub: public_key_t(),\n          addr: addr_t()\n        }\n\n  defstruct [:priv, :pub, :addr]\n\n  @spec new(private_key_t()) :: {:ok, t()} | {:error, atom()}\n  def new(private_key) when byte_size(private_key) == 32 do\n    {:ok, der_public_key} = compute_public_key(private_key)\n    public_key = der_to_raw(der_public_key)\n    {:ok, address} = compute_address(public_key)\n\n    {:ok, struct!(__MODULE__, priv: private_key, pub: public_key, addr: address)}\n  end\n\n  @spec new(private_key_hex_t()) :: {:ok, t()} | {:error, atom()}\n  def new(private_key_hex) do\n    private_key_hex\n    |> Encoding.to_binary()\n    |> new()\n  end\n\n  @spec new() :: {:ok, t()} | {:error, atom()}\n  def new() do\n    {:ok, priv} = Crypto.generate_private_key()\n    new(priv)\n  end\n\n  defp compute_public_key(private_key) do\n    ExSecp256k1.create_public_key(private_key)\n  end\n\n  defp compute_address(<<pub::binary-size(64)>>) do\n    <<_::binary-size(12), address::binary-size(20)>> = Crypto.hash(pub)\n    {:ok, address}\n  end\n\n  defp der_to_raw(<<4::integer-size(8), data::binary>>), do: data\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/ethereum/bit_helper.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.Ethereum.BitHelper do\n  @moduledoc \"\"\"\n  Helpers for common operations on the blockchain.\n  Extracted from: https://github.com/exthereum/blockchain\n  \"\"\"\n\n  use Bitwise\n\n  @type keccak_hash :: binary()\n\n  @doc \"\"\"\n  Returns the keccak sha256 of a given input.\n  \"\"\"\n  @spec kec(binary()) :: keccak_hash\n  def kec(data) do\n    elem(ExKeccak.hash_256(data), 1)\n  end\n\n  @doc \"\"\"\n  Similar to `:binary.encode_unsigned/1`, except we encode `0` as\n  `<<>>`, the empty string. This is because the specification says that\n  we cannot have any leading zeros, and so having <<0>> by itself is\n  leading with a zero and prohibited.\n  \"\"\"\n  @spec encode_unsigned(non_neg_integer()) :: binary()\n  def encode_unsigned(0), do: <<>>\n  def encode_unsigned(n), do: :binary.encode_unsigned(n)\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/ethereum/crypto.ex",
    "content": "defmodule LoadTest.Ethereum.Crypto do\n  @moduledoc \"\"\"\n  Cryptography related utility functions\n  \"\"\"\n  @type hash_t() :: <<_::256>>\n  @type priv_key_t :: <<_::256>>\n\n  @doc \"\"\"\n  Produces a KECCAK digest for the message.\n\n  see https://hexdocs.pm/exth_crypto/ExthCrypto.Hash.html#kec/0\n  \"\"\"\n  @spec hash(binary) :: hash_t()\n  def hash(message), do: elem(ExKeccak.hash_256(message), 1)\n\n  @doc \"\"\"\n  Generates private key. Internally uses OpenSSL RAND_bytes. May throw if there is not enough entropy.\n  \"\"\"\n  @spec generate_private_key() :: {:ok, priv_key_t()}\n  def generate_private_key, do: {:ok, :crypto.strong_rand_bytes(32)}\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/ethereum/ethereum.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.Ethereum do\n  @moduledoc \"\"\"\n  Support for synchronous transactions.\n  \"\"\"\n  require Logger\n\n  alias ExPlasma.Encoding\n  alias LoadTest.ChildChain.Abi\n  alias LoadTest.Ethereum.Account\n  alias LoadTest.Ethereum.NonceTracker\n  alias LoadTest.Ethereum.Transaction\n  alias LoadTest.Ethereum.Transaction.Signature\n  alias LoadTest.Service.Sync\n\n  @about_4_blocks_time 120_000\n  @poll_timeout 60_000\n\n  @type hash_t() :: <<_::256>>\n\n  @doc \"\"\"\n  Send transaction to be singed by a key managed by Ethereum node, geth or parity.\n  For geth, account must be unlocked externally.\n  If using parity, account passphrase must be provided directly or via config.\n  \"\"\"\n  @spec contract_transact(<<_::160>>, <<_::160>>, binary, [any]) :: {:ok, <<_::256>>} | {:error, any}\n  def contract_transact(from, to, signature, args, opts \\\\ []) do\n    data = encode_tx_data(signature, args)\n\n    txmap =\n      %{from: Encoding.to_hex(from), to: Encoding.to_hex(to), data: data}\n      |> Map.merge(Map.new(opts))\n      |> encode_all_integer_opts()\n\n    case Ethereumex.HttpClient.eth_send_transaction(txmap) do\n      {:ok, receipt_enc} -> {:ok, Encoding.to_binary(receipt_enc)}\n      other -> other\n    end\n  end\n\n  @spec get_gas_used(String.t()) :: non_neg_integer()\n  def get_gas_used(receipt_hash) do\n    {{:ok, %{\"gasUsed\" => gas_used}}, {:ok, %{\"gasPrice\" => gas_price}}} =\n      {Ethereumex.HttpClient.eth_get_transaction_receipt(receipt_hash),\n       Ethereumex.HttpClient.eth_get_transaction_by_hash(receipt_hash)}\n\n    {gas_price_value, \"\"} = gas_price |> String.replace_prefix(\"0x\", \"\") |> Integer.parse(16)\n    {gas_used_value, \"\"} = gas_used |> String.replace_prefix(\"0x\", \"\") |> Integer.parse(16)\n\n    gas_price_value * gas_used_value\n  end\n\n  @doc \"\"\"\n  Waits until transaction is mined\n  Returns transaction receipt updated with Ethereum block number in which the transaction was mined\n  \"\"\"\n  @spec transact_sync(hash_t(), pos_integer()) :: {:ok, map()}\n  def transact_sync(txhash, timeout \\\\ @about_4_blocks_time) do\n    {:ok, %{\"status\" => \"0x1\"} = receipt} = eth_receipt(txhash, timeout)\n    {:ok, Map.update!(receipt, \"blockNumber\", &Encoding.to_int(&1))}\n  end\n\n  def block_hash(mined_num) do\n    contract_address = Application.fetch_env!(:load_test, :contract_address_plasma_framework)\n\n    %{\"block_hash\" => block_hash, \"block_timestamp\" => block_timestamp} =\n      get_external_data(contract_address, \"blocks(uint256)\", [mined_num])\n\n    {block_hash, block_timestamp}\n  end\n\n  def send_raw_transaction(txmap, sender) do\n    nonce = NonceTracker.get_next_nonce(sender.addr)\n\n    txmap\n    |> Map.merge(%{nonce: nonce})\n    |> Signature.sign_transaction(sender.priv)\n    |> Transaction.serialize()\n    |> ExRLP.encode()\n    |> Encoding.to_hex()\n    |> Ethereumex.HttpClient.eth_send_raw_transaction()\n  end\n\n  def get_next_nonce_for_account(address) when byte_size(address) == 20 do\n    address\n    |> ExPlasma.Encoding.to_hex()\n    |> get_next_nonce_for_account()\n  end\n\n  def get_next_nonce_for_account(\"0x\" <> _ = address) do\n    {:ok, nonce} = Ethereumex.HttpClient.eth_get_transaction_count(address)\n    Encoding.to_int(nonce)\n  end\n\n  def wait_for_root_chain_block(awaited_eth_height, timeout \\\\ 600_000) do\n    f = fn ->\n      {:ok, eth_height} =\n        case Ethereumex.HttpClient.eth_block_number() do\n          {:ok, height_hex} ->\n            {:ok, Encoding.to_int(height_hex)}\n\n          other ->\n            other\n        end\n\n      if eth_height < awaited_eth_height, do: :repeat, else: {:ok, eth_height}\n    end\n\n    Sync.repeat_until_success(f, timeout, \"Failed to fetch eth block number\")\n  end\n\n  @spec fetch_balance(Account.addr_t(), Account.addr_t()) :: non_neg_integer() | no_return()\n  def fetch_balance(address, <<0::160>>) do\n    {:ok, initial_balance} =\n      Sync.repeat_until_success(\n        fn ->\n          address\n          |> Encoding.to_hex()\n          |> eth_account_get_balance()\n        end,\n        @poll_timeout,\n        \"Failed to fetch eth balance from rootchain\"\n      )\n\n    {initial_balance, \"\"} = initial_balance |> String.replace_prefix(\"0x\", \"\") |> Integer.parse(16)\n    initial_balance\n  end\n\n  def fetch_balance(address, currency) do\n    Sync.repeat_until_success(\n      fn ->\n        do_root_chain_get_erc20_balance(address, currency)\n      end,\n      @poll_timeout,\n      \"Failed to fetch erc20 balance from rootchain\"\n    )\n  end\n\n  defp eth_account_get_balance(address) do\n    Ethereumex.HttpClient.eth_get_balance(address)\n  end\n\n  defp do_root_chain_get_erc20_balance(address, currency) do\n    data = ABI.encode(\"balanceOf(address)\", [Encoding.to_binary(address)])\n\n    case Ethereumex.HttpClient.eth_call(%{\n           from: Encoding.to_hex(currency),\n           to: Encoding.to_hex(currency),\n           data: Encoding.to_hex(data)\n         }) do\n      {:ok, result} ->\n        balance =\n          result\n          |> Encoding.to_binary()\n          |> ABI.TypeDecoder.decode([{:uint, 256}])\n          |> hd()\n\n        {:ok, balance}\n\n      error ->\n        error\n    end\n  end\n\n  defp get_external_data(address, signature, params) do\n    data = signature |> ABI.encode(params) |> Encoding.to_hex()\n\n    {:ok, data} = Ethereumex.HttpClient.eth_call(%{from: address, to: address, data: data})\n\n    Abi.decode_function(data, signature)\n  end\n\n  defp eth_receipt(txhash, timeout) do\n    f = fn ->\n      txhash\n      |> Ethereumex.HttpClient.eth_get_transaction_receipt()\n      |> case do\n        {:ok, receipt} when receipt != nil -> {:ok, receipt}\n        _ -> :repeat\n      end\n    end\n\n    Sync.repeat_until_success(f, timeout, \"Failed to fetch eth receipt\")\n  end\n\n  defp encode_tx_data(signature, args) do\n    signature\n    |> ABI.encode(args)\n    |> Encoding.to_hex()\n  end\n\n  defp encode_all_integer_opts(opts) do\n    opts\n    |> Enum.filter(fn {_k, v} -> is_integer(v) end)\n    |> Enum.into(opts, fn {k, v} -> {k, Encoding.to_hex(v)} end)\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/ethereum/hash.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.Ethereum.Hash do\n  @moduledoc \"\"\"\n  Defines helper functions for signing and getting the signature\n  of a transaction, as defined in Appendix F of the Yellow Paper.\n\n  For any of the following functions, if chain_id is specified,\n  it's assumed that we're post-fork and we should follow the\n  specification EIP-155 from:\n\n  https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md\n  Extracted from: https://github.com/exthereum/blockchain\n  \"\"\"\n\n  alias LoadTest.Ethereum.BitHelper\n  alias LoadTest.Ethereum.Transaction\n\n  @base_recovery_id 27\n  @base_recovery_id_eip_155 35\n  @type private_key :: <<_::256>>\n  @type hash_v :: integer()\n  @type hash_r :: integer()\n  @type hash_s :: integer()\n\n  @doc \"\"\"\n  Returns a hash of a given transaction according to the\n  formula defined in Eq.(214) and Eq.(215) of the Yellow Paper.\n\n  Note: As per EIP-155 (https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md),\n        we will append the chain-id and nil elements to the serialized transaction.\n\n  ## Examples\n\n      iex> LoadTest.Ethereum.Hash.transaction_hash(%LoadTest.Ethereum.Transaction{nonce: 5, gas_price: 6, gas_limit: 7, to: <<>>, value: 5, init: <<1>>})\n      <<127, 113, 209, 76, 19, 196, 2, 206, 19, 198, 240, 99, 184, 62, 8, 95, 9, 122, 135, 142, 51, 22, 61, 97, 70, 206, 206, 39, 121, 54, 83, 27>>\n\n      iex> LoadTest.Ethereum.Hash.transaction_hash(%LoadTest.Ethereum.Transaction{nonce: 5, gas_price: 6, gas_limit: 7, to: <<1>>, value: 5, data: <<1>>})\n      <<225, 195, 128, 181, 3, 211, 32, 231, 34, 10, 166, 198, 153, 71, 210, 118, 51, 117, 22, 242, 87, 212, 229, 37, 71, 226, 150, 160, 50, 203, 127, 180>>\n\n      iex> LoadTest.Ethereum.Hash.transaction_hash(%LoadTest.Ethereum.Transaction{nonce: 5, gas_price: 6, gas_limit: 7, to: <<1>>, value: 5, data: <<1>>}, 1)\n      <<132, 79, 28, 4, 212, 58, 235, 38, 66, 211, 167, 102, 36, 58, 229, 88, 238, 251, 153, 23, 121, 163, 212, 64, 83, 111, 200, 206, 54, 43, 112, 53>>\n  \"\"\"\n\n  @spec transaction_hash(Transaction.t(), integer() | nil) :: BitHelper.keccak_hash()\n  def transaction_hash(trx, chain_id \\\\ nil) do\n    trx\n    |> Transaction.serialize(false)\n    # See EIP-155\n    |> Kernel.++(if chain_id, do: [:binary.encode_unsigned(chain_id), <<>>, <<>>], else: [])\n    |> ExRLP.encode()\n    |> BitHelper.kec()\n  end\n\n  @doc \"\"\"\n  Returns a ECDSA signature (v,r,s) for a given hashed value.\n\n  This implementes Eq.(207) of the Yellow Paper.\n\n  ## Examples\n\n    iex> LoadTest.Ethereum.Hash.sign_hash(<<2::256>>, <<1::256>>)\n    {28,\n     38938543279057362855969661240129897219713373336787331739561340553100525404231,\n     23772455091703794797226342343520955590158385983376086035257995824653222457926}\n\n    iex> LoadTest.Ethereum.Hash.sign_hash(<<5::256>>, <<1::256>>)\n    {27,\n     74927840775756275467012999236208995857356645681540064312847180029125478834483,\n     56037731387691402801139111075060162264934372456622294904359821823785637523849}\n\n    iex> data = Base.decode16!(\"ec098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080018080\", case: :lower)\n    iex> hash = LoadTest.Ethereum.BitHelper.kec(data)\n    iex> private_key = Base.decode16!(\"4646464646464646464646464646464646464646464646464646464646464646\", case: :lower)\n    iex> LoadTest.Ethereum.Hash.sign_hash(hash, private_key, 1)\n    { 37, 18515461264373351373200002665853028612451056578545711640558177340181847433846, 46948507304638947509940763649030358759909902576025900602547168820602576006531 }\n  \"\"\"\n  @spec sign_hash(BitHelper.keccak_hash(), private_key, integer() | nil) ::\n          {hash_v, hash_r, hash_s}\n  def sign_hash(hash, private_key, chain_id \\\\ nil) do\n    {:ok, {<<r::size(256), s::size(256)>>, recovery_id}} = ExSecp256k1.sign_compact(hash, private_key)\n\n    # Fork Ψ EIP-155\n    recovery_id =\n      if chain_id do\n        chain_id * 2 + @base_recovery_id_eip_155 + recovery_id\n      else\n        @base_recovery_id + recovery_id\n      end\n\n    {recovery_id, r, s}\n  end\n\n  @doc \"\"\"\n    Packs a {v,r,s} signature as 65-bytes binary.\n  \"\"\"\n  @spec pack_signature({hash_v, hash_r, hash_s}) :: binary\n  def pack_signature({v, r, s}) do\n    <<r::integer-size(256), s::integer-size(256), v::integer-size(8)>>\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/ethereum/nonce_tracker.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.Ethereum.NonceTracker do\n  @moduledoc \"\"\"\n  Nonce tracker for sending ethereum transactions\n  \"\"\"\n\n  alias ExPlasma.Encoding\n\n  def init() do\n    :ets.new(:nonce_tracker, [:set, :public, :named_table])\n  end\n\n  def get_next_nonce(address) do\n    if Enum.empty?(:ets.lookup(:nonce_tracker, address)) do\n      current_nonce =\n        address\n        |> Encoding.to_hex()\n        |> Ethereumex.HttpClient.eth_get_transaction_count(\"pending\")\n        |> elem(1)\n        |> Encoding.to_int()\n\n      # it might happen that this is called more than once, but\n      # we rely on :ets.update_counter being atomic, so starting value is not changed\n      :ets.update_counter(:nonce_tracker, address, 1, {0, current_nonce - 1})\n    else\n      :ets.update_counter(:nonce_tracker, address, 1)\n    end\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/ethereum/transaction/signature.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.Ethereum.Transaction.Signature do\n  @moduledoc \"\"\"\n  Defines helper functions for signing and getting the signature\n  of a transaction, as defined in Appendix F of the Yellow Paper.\n\n  For any of the following functions, if chain_id is specified,\n  it's assumed that we're post-fork and we should follow the\n  specification EIP-155 from:\n\n  https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md\n  Extracted from: https://github.com/exthereum/blockchain\n  \"\"\"\n\n  require Integer\n\n  alias LoadTest.Ethereum.Hash\n  alias LoadTest.Ethereum.Transaction\n\n  @type private_key :: <<_::256>>\n\n  @doc \"\"\"\n  Takes a given transaction and returns a version signed\n  with the given private key. This is defined in Eq.(216) and\n  Eq.(217) of the Yellow Paper.\n  \"\"\"\n  @spec sign_transaction(Transaction.t(), private_key, integer() | nil) :: Transaction.t()\n  def sign_transaction(trx, private_key, chain_id \\\\ nil) do\n    {v, r, s} =\n      trx\n      |> Hash.transaction_hash(chain_id)\n      |> Hash.sign_hash(private_key, chain_id)\n\n    %{trx | v: v, r: r, s: s}\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/ethereum/transaction/transaction.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.Ethereum.Transaction do\n  alias LoadTest.Ethereum.BitHelper\n\n  @moduledoc \"\"\"\n  This module encodes the transaction object, defined in Section 4.3\n  of the Yellow Paper (http://gavwood.com/Paper.pdf). We are focused\n  on implementing 𝛶, as defined in Eq.(1).\n  Extracted from: https://github.com/exthereum/blockchain\n  \"\"\"\n  defstruct nonce: 0,\n\n            # Tn\n            # Tp\n            gas_price: 0,\n            # Tg\n            gas_limit: 0,\n            # Tt\n            to: <<>>,\n            # Tv\n            value: 0,\n            # Tw\n            v: nil,\n            # Tr\n            r: nil,\n            # Ts\n            s: nil,\n            # Ti\n            init: <<>>,\n            # Td\n            data: <<>>\n\n  @type t :: %__MODULE__{\n          nonce: integer(),\n          gas_price: integer(),\n          gas_limit: integer(),\n          to: <<_::160>> | <<_::0>>,\n          value: integer(),\n          v: integer(),\n          r: integer(),\n          s: integer(),\n          init: binary(),\n          data: binary()\n        }\n\n  @doc \"\"\"\n  Encodes a transaction such that it can be RLP-encoded.\n  This is defined at L_T Eq.(14) in the Yellow Paper.\n\n  ## Examples\n\n      iex> LoadTest.Ethereum.Transaction.serialize(%LoadTest.Ethereum.Transaction{nonce: 5, gas_price: 6, gas_limit: 7, to: <<1::160>>, value: 8, v: 27, r: 9, s: 10, data: \"hi\"})\n      [<<5>>, <<6>>, <<7>>, <<1::160>>, <<8>>, \"hi\", <<27>>, <<9>>, <<10>>]\n\n      iex> LoadTest.Ethereum.Transaction.serialize(%LoadTest.Ethereum.Transaction{nonce: 5, gas_price: 6, gas_limit: 7, to: <<>>, value: 8, v: 27, r: 9, s: 10, init: <<1, 2, 3>>})\n      [<<5>>, <<6>>, <<7>>, <<>>, <<8>>, <<1, 2, 3>>, <<27>>, <<9>>, <<10>>]\n\n      iex> LoadTest.Ethereum.Transaction.serialize(%LoadTest.Ethereum.Transaction{nonce: 5, gas_price: 6, gas_limit: 7, to: <<>>, value: 8, v: 27, r: 9, s: 10, init: <<1, 2, 3>>}, false)\n      [<<5>>, <<6>>, <<7>>, <<>>, <<8>>, <<1, 2, 3>>]\n\n      iex> LoadTest.Ethereum.Transaction.serialize(%LoadTest.Ethereum.Transaction{ data: \"\", gas_limit: 21000, gas_price: 20000000000, init: \"\", nonce: 9, r: 0, s: 0, to: \"55555555555555555555\", v: 1, value: 1000000000000000000 })\n      [\"\\t\", <<4, 168, 23, 200, 0>>, \"R\\b\", \"55555555555555555555\", <<13, 224, 182, 179, 167, 100, 0, 0>>, \"\", <<1>>, \"\", \"\"]\n  \"\"\"\n  @spec serialize(t) :: ExRLP.t()\n  def serialize(trx, include_vrs \\\\ true) do\n    base = [\n      BitHelper.encode_unsigned(trx.nonce),\n      BitHelper.encode_unsigned(trx.gas_price),\n      BitHelper.encode_unsigned(trx.gas_limit),\n      trx.to,\n      BitHelper.encode_unsigned(trx.value),\n      if(trx.to == <<>>, do: trx.init, else: trx.data)\n    ]\n\n    if include_vrs do\n      base ++\n        [\n          BitHelper.encode_unsigned(trx.v),\n          BitHelper.encode_unsigned(trx.r),\n          BitHelper.encode_unsigned(trx.s)\n        ]\n    else\n      base\n    end\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/performance.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.Performance do\n  @moduledoc \"\"\"\n  OMG network performance tests. Provides general setup and utilities to do the perf tests.\n  \"\"\"\n\n  defmacro __using__(_opt) do\n    quote do\n      alias LoadTest.Common.ByzantineEvents\n      alias LoadTest.Common.ExtendedPerftest\n      alias LoadTest.Common.Generators\n\n      alias LoadTest.Performance\n\n      import Performance, only: [timeit: 1]\n      require Performance\n      require Logger\n\n      :ok\n    end\n  end\n\n  @doc \"\"\"\n  Utility macro which causes the expression given to be timed, the timing logged (`info`) and the original result of the\n  call to be returned\n\n  ## Examples\n\n    iex> use LoadTest.Performance\n    iex> timeit 1+2\n    3\n  \"\"\"\n  defmacro timeit(call) do\n    quote do\n      {duration, result} = :timer.tc(fn -> unquote(call) end)\n      duration_s = duration / 1_000_000\n      _ = Logger.info(\"Lasted #{inspect(duration_s)} seconds\")\n      result\n    end\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/runner/childchain.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.Runner.ChildChainTransactions do\n  @moduledoc \"\"\"\n  Creates load on the child chain by submitting transactions as fast as possible.\n\n  Run with `mix test apps/load_test/test/load_tests/runner/childchain_test.exs`\n\n  In each session, the test creates a new address and funds it from the faucet.\n  It then creates transactions from this address to another temporary address.\n  Transactions are chained i.e. using the returned blocknum and tx_pos from `transaction.submit`\n  we can calculate the next utxo to be spent without waiting for the block to finalize.\n  This allows us to submit transactions as fast as possible, limited only by latency of `transaction.submit`\n\n  Note that the latency of `transaction.submit` can be high enough to mean that one account sending\n  transactions in this way is not enough to stress the childchain. It is necessary to run many concurrent\n  sessions to provide meaningful load.\n  \"\"\"\n  use Chaperon.LoadTest\n\n  alias LoadTest.Ethereum.Account\n\n  @default_config %{\n    concurrent_sessions: 1,\n    transactions_per_session: 1,\n    transaction_delay: 0\n  }\n\n  def default_config() do\n    Application.get_env(:load_test, :childchain_transactions_test_config, @default_config)\n  end\n\n  def scenarios() do\n    test_currency = Application.fetch_env!(:load_test, :test_currency)\n    fee_amount = Application.fetch_env!(:load_test, :fee_amount)\n    config = default_config()\n\n    {:ok, sender} = Account.new()\n    {:ok, receiver} = Account.new()\n\n    amount = 1\n\n    ntx_to_send = config.transactions_per_session\n    initial_funds = (amount + fee_amount) * ntx_to_send\n\n    [\n      {{config.concurrent_sessions, [LoadTest.Scenario.FundAccount, LoadTest.Scenario.SpendEthUtxo]},\n       %{\n         account: sender,\n         initial_funds: initial_funds,\n         sender: sender,\n         receiver: receiver,\n         amount: amount,\n         test_currency: test_currency\n       }}\n    ]\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/runner/deposits.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.Runner.Deposits do\n  @moduledoc \"\"\"\n  Deposits tests runner.\n  \"\"\"\n  use Chaperon.LoadTest\n\n  def scenarios do\n    [\n      {{1, LoadTest.Scenario.Deposits}, %{}}\n    ]\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/runner/smoke.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.Runner.Smoke do\n  @moduledoc \"\"\"\n  Smoke test to verify that the childchain, watcher and watcher-info are up and running\n\n  Run with `mix test apps/load_test/test/load_tests/runner/smoke_test.exs`\n  \"\"\"\n\n  use Chaperon.LoadTest\n\n  def scenarios do\n    [\n      {{1, LoadTest.Scenario.Smoke}, %{}}\n    ]\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/runner/standard_exits.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.Runner.StandardExits do\n  @moduledoc \"\"\"\n  Runs the Standard Exit load test scenarios.\n\n  Run with `mix test apps/load_test/test/load_tests/runner/standard_exit_test.exs`\n\n  The ManyStandardExits scenarios first creates and funds a new address, then\n  creates many utxos and then starts an exits on each one. It then waits for the\n  Watcher to sync with the root chain.\n\n  Finally, it calls Watcher status.get to measure the timing.\n\n  \"\"\"\n  use Chaperon.LoadTest\n\n  alias ExPlasma.Encoding\n  alias LoadTest.ChildChain.Exit\n  alias LoadTest.Service.Faucet\n\n  @default_config %{\n    concurrent_sessions: 1,\n    exits_per_session: 1\n  }\n\n  def default_config() do\n    Application.get_env(:load_test, :standard_exit_test_config, @default_config)\n  end\n\n  def scenarios() do\n    test_currency = Application.fetch_env!(:load_test, :test_currency)\n    gas_price = Application.fetch_env!(:load_test, :gas_price)\n    config = default_config()\n\n    # Use the faucet account to add the token's exit queue if necessary\n    {:ok, faucet} = Faucet.get_faucet()\n    _ = Exit.add_exit_queue(1, Encoding.to_binary(test_currency), faucet, gas_price)\n\n    [\n      {{config.concurrent_sessions, [LoadTest.Scenario.ManyStandardExits, LoadTest.Scenario.WatcherStatus]},\n       %{\n         gas_price: gas_price,\n         test_currency: test_currency\n       }}\n    ]\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/runner/transactions.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.Runner.Transactions do\n  @moduledoc \"\"\"\n  Transactions tests runner.\n  \"\"\"\n  use Chaperon.LoadTest\n\n  def scenarios do\n    [\n      {{1, LoadTest.Scenario.Transactions}, %{}}\n    ]\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/runner/utxos_load.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.Runner.UtxosLoad do\n  @moduledoc \"\"\"\n  Creates utxos and submits transactions to test how child chain performs when\n  there are many utxos in its state.\n\n  Run with `mix test apps/load_test/test/load_tests/runner/utxos_load_test.exs`\n\n  This test first creates a number of utxos by funding a new address from the faucet and then\n  successively splitting its utxos into 4 until the desired number of utxos is reached.\n\n  It then creates a number of transactions from the address, measuring the time taken.\n  \"\"\"\n  use Chaperon.LoadTest\n\n  alias LoadTest.Ethereum.Account\n\n  @default_config %{\n    concurrent_sessions: 1,\n    utxos_to_create_per_session: 30,\n    transactions_per_session: 10\n  }\n\n  def default_config() do\n    utxo_load_test_config = Application.get_env(:load_test, :utxo_load_test_config, @default_config)\n\n    %{\n      concurrent_sessions: utxo_load_test_config[:concurrent_sessions],\n      utxos_to_create_per_session: utxo_load_test_config[:utxos_to_create_per_session],\n      transactions_per_session: utxo_load_test_config[:transactions_per_session]\n    }\n  end\n\n  def scenarios() do\n    {:ok, sender} = Account.new()\n\n    %{concurrent_sessions: concurrent_sessions} = default_config()\n\n    [\n      {{concurrent_sessions, [LoadTest.Scenario.CreateUtxos, LoadTest.Scenario.SpendEthUtxo]},\n       %{\n         sender: sender,\n         receiver: sender\n       }}\n    ]\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/runner/watcher_info.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.Runner.WatcherInfoAccountApi do\n  @moduledoc \"\"\"\n  Tests all the `account.*` apis on the watcher-info\n\n  Run with `mix test apps/load_test/test/load_tests/runner/watcher_info_test.exs`\n\n  This test first creates a new address and funds from the faucet.\n  Next it calls the watcher-info apis:\n    - `account.get_balance`\n    - `account.get_utxos`\n    - `account.get_transactions`\n  It then creates a transaction from the address, measuring the time taken.\n  \"\"\"\n  use Chaperon.LoadTest\n\n  @default_config %{\n    concurrent_sessions: 1,\n    iterations: 1,\n    merge_scenario_sessions: true\n  }\n\n  def default_config() do\n    Application.get_env(:load_test, :watcher_info_test_config, @default_config)\n  end\n\n  def scenarios() do\n    %{concurrent_sessions: concurrent_sessions} = default_config()\n\n    [\n      {{concurrent_sessions, LoadTest.Scenario.AccountTransactions}, %{}}\n    ]\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/scenario/account_transactions.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.Scenario.AccountTransactions do\n  @moduledoc \"\"\"\n  This scenario tests watcher info apis when number of transaction increases for an account.\n  \"\"\"\n\n  use Chaperon.Scenario\n\n  alias Chaperon.Timing\n  alias LoadTest.Connection.WatcherInfo, as: Connection\n  alias LoadTest.Ethereum.Account\n  alias LoadTest.Ethereum.Hash\n  alias LoadTest.Service.Faucet\n  alias LoadTest.Utils.Encoding\n  alias WatcherInfoAPI.Api\n  alias WatcherInfoAPI.Model\n\n  @poll_interval 15_000\n  @default_retry_attempts 15\n  @retry_delay 30\n\n  @eth <<0::160>>\n  @test_output_amount 1\n\n  @spec init(Chaperon.Session.t()) :: Chaperon.Session.t()\n  def init(session) do\n    session\n    |> log_info(\"start init with random delay...\")\n    |> random_delay(Timing.seconds(5))\n  end\n\n  def run(session) do\n    iterations = config(session, [:iterations])\n    fee_amount = Application.fetch_env!(:load_test, :fee_amount)\n\n    amount = iterations * (@test_output_amount + fee_amount)\n    {:ok, sender} = Account.new()\n    {:ok, _} = Faucet.fund_child_chain_account(sender, amount, @eth)\n\n    {:ok, faucet} = Faucet.get_faucet()\n\n    session\n    |> assign(faucet: faucet, iteration: 1)\n    |> wait_for_balance_update(sender)\n    |> log_info(\"user created: \" <> Encoding.to_hex(sender.addr))\n    |> repeat(:repeat_task, [sender], iterations)\n    |> log_info(\"end...\")\n  end\n\n  def repeat_task(session, sender) do\n    session\n    |> log_info(\"running iteration #{session.assigned.iteration}\")\n    |> retry_on_error(\n      :test_apis,\n      [sender],\n      retries: @default_retry_attempts,\n      random_delay: seconds(@retry_delay)\n    )\n    |> update_assign(iteration: &(&1 + 1))\n  end\n\n  def test_apis(session, sender) do\n    session\n    |> measure_get_balance(sender)\n    |> measure_get_utxos(sender)\n    |> measure_get_transactions(sender)\n    |> measure_create_and_submit_transactions(sender)\n  end\n\n  defp wait_for_balance_update(session, sender, retry \\\\ @default_retry_attempts) do\n    {:ok, session} = do_wait_for_balance_update(session, sender, retry)\n    session\n  end\n\n  defp measure(session, sender, api_call, metric_name) do\n    start = Timing.timestamp()\n    {:ok, _} = api_call.(sender)\n\n    add_metric(\n      session,\n      {:call, {LoadTest.Scenario.AccountTransactions, metric_name}},\n      Timing.timestamp() - start\n    )\n  end\n\n  defp measure_get_balance(session, sender) do\n    measure(session, sender, &get_balance/1, \"/account.get_balance\")\n  end\n\n  defp measure_get_utxos(session, sender) do\n    measure(session, sender, &get_utxos/1, \"/account.get_utxos\")\n  end\n\n  defp measure_get_transactions(session, sender) do\n    measure(session, sender, &get_transactions/1, \"/account.get_transactions\")\n  end\n\n  defp measure_create_and_submit_transactions(session, sender) do\n    start = Timing.timestamp()\n    {:ok, [inputs, sign_hash, typed_data, _txbytes]} = create_transaction(session, sender)\n\n    session =\n      add_metric(\n        session,\n        {:call, {LoadTest.Scenario.AccountTransactions, '/transaction.create'}},\n        Timing.timestamp() - start\n      )\n\n    typed_data_signed = sign_tx(inputs, sign_hash, typed_data, sender)\n\n    start = Timing.timestamp()\n    {:ok, response} = Api.Transaction.submit_typed(Connection.client(), typed_data_signed)\n\n    session =\n      add_metric(\n        session,\n        {:call, {LoadTest.Scenario.AccountTransactions, '/transaction.submit_typed'}},\n        Timing.timestamp() - start\n      )\n\n    %{\n      \"txhash\" => tx_id\n    } = Jason.decode!(response.body)[\"data\"]\n\n    wait_until_tx_sync_to_watcher(session, tx_id)\n  end\n\n  defp create_transaction(session, sender) do\n    {:ok, response} =\n      Api.Transaction.create_transaction(\n        Connection.client(),\n        %Model.CreateTransactionsBodySchema{\n          owner: Encoding.to_hex(sender.addr),\n          fee: %Model.TransactionCreateFee{\n            currency: Encoding.to_hex(@eth)\n          },\n          payments: [\n            %Model.TransactionCreatePayments{\n              amount: @test_output_amount,\n              currency: Encoding.to_hex(@eth),\n              owner: Encoding.to_hex(session.assigned.faucet.addr)\n            }\n          ]\n        }\n      )\n\n    %{\n      \"result\" => \"complete\",\n      \"transactions\" => [\n        %{\n          \"inputs\" => inputs,\n          \"sign_hash\" => sign_hash,\n          \"typed_data\" => typed_data,\n          \"txbytes\" => txbytes\n        }\n      ]\n    } = Jason.decode!(response.body)[\"data\"]\n\n    {:ok, [inputs, sign_hash, typed_data, txbytes]}\n  end\n\n  defp get_balance(sender) do\n    {:ok, response} =\n      Api.Account.account_get_balance(\n        Connection.client(),\n        %Model.AddressBodySchema{\n          address: Encoding.to_hex(sender.addr)\n        }\n      )\n\n    {:ok, response.body}\n  end\n\n  defp get_utxos(sender) do\n    {:ok, response} =\n      Api.Account.account_get_utxos(\n        Connection.client(),\n        %Model.AddressBodySchema1{\n          address: Encoding.to_hex(sender.addr)\n        }\n      )\n\n    {:ok, response.body}\n  end\n\n  defp get_transactions(_sender) do\n    # There is an issue openapi generated client does not work well with optional request body\n    # https://github.com/OpenAPITools/openapi-generator/issues/5234\n    # So we are not filtering by sender now\n\n    {:ok, response} =\n      Api.Account.account_get_transactions(\n        Connection.client(),\n        %Model.GetAllTransactionsBodySchema{}\n      )\n\n    {:ok, response.body}\n  end\n\n  defp sign_tx(inputs, sign_hash, typed_data, sender) do\n    signature =\n      sign_hash\n      |> Encoding.to_binary()\n      |> Hash.sign_hash(sender.priv)\n      |> Hash.pack_signature()\n      |> Encoding.to_hex()\n\n    signatures = Enum.map(inputs, fn _ -> signature end)\n    Map.put_new(typed_data, \"signatures\", signatures)\n  end\n\n  defp do_wait_for_balance_update(_session, _sender, 0), do: :wait_for_balance_failed\n\n  defp do_wait_for_balance_update(session, sender, retry) do\n    {:ok, response_body} = get_balance(sender)\n    utxos = Jason.decode!(response_body)[\"data\"]\n\n    if Enum.empty?(utxos) do\n      Process.sleep(@poll_interval)\n\n      session\n      |> log_debug(\"retry for the balance update for sender: #{Encoding.to_hex(sender.addr)}\")\n      |> do_wait_for_balance_update(sender, retry - 1)\n    else\n      {:ok, session}\n    end\n  end\n\n  defp wait_until_tx_sync_to_watcher(session, tx_id) do\n    {:ok, session} = do_wait_until_tx_sync_to_watcher(session, tx_id, @default_retry_attempts)\n    session\n  end\n\n  defp do_wait_until_tx_sync_to_watcher(_session, _tx_id, 0), do: :wait_until_tx_sync_failed\n\n  defp do_wait_until_tx_sync_to_watcher(session, tx_id, retry) do\n    {:ok, response} =\n      Api.Transaction.transaction_get(\n        Connection.client(),\n        %Model.GetTransactionBodySchema{\n          id: tx_id\n        }\n      )\n\n    case Jason.decode!(response.body) do\n      %{\"success\" => true} ->\n        {:ok, session}\n\n      _ ->\n        Process.sleep(@poll_interval)\n\n        session\n        |> log_debug(\"retry for watcher info to sync the submitted tx_id: #{tx_id}\")\n        |> do_wait_until_tx_sync_to_watcher(tx_id, retry - 1)\n    end\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/scenario/create_utxos.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.Scenario.CreateUtxos do\n  @moduledoc \"\"\"\n  Funds an account and then splits the resulting utxo into many more utxos.\n\n  ## configuration values\n  - `sender` the owner of the utxos\n  - `utxos_to_create_per_session` the amount of utxos to create\n  \"\"\"\n\n  use Chaperon.Scenario\n\n  alias Chaperon.Session\n  alias ExPlasma.Utxo\n\n  @spawned_outputs_per_transaction 3\n\n  @spec run(Session.t()) :: Session.t()\n  def run(session) do\n    fee_amount = Application.fetch_env!(:load_test, :fee_amount)\n    session = Session.assign(session, fee_amount: fee_amount)\n    test_currency = Application.fetch_env!(:load_test, :test_currency)\n    session = Session.assign(session, test_currency: test_currency)\n\n    sender = config(session, [:sender])\n    utxos_to_create_per_session = config(session, [:utxos_to_create_per_session])\n    number_of_transactions = div(utxos_to_create_per_session, 3)\n\n    transactions_per_session = config(session, [:transactions_per_session])\n    min_final_change = transactions_per_session * fee_amount + 1\n\n    amount_per_utxo = get_amount_per_created_utxo(fee_amount)\n\n    initial_funds =\n      number_of_transactions * fee_amount + utxos_to_create_per_session * amount_per_utxo + min_final_change\n\n    session\n    |> run_scenario(LoadTest.Scenario.FundAccount, %{\n      account: sender,\n      initial_funds: initial_funds,\n      test_currency: test_currency\n    })\n    |> repeat(:submit_transaction, [sender], number_of_transactions)\n  end\n\n  def submit_transaction(session, sender) do\n    {inputs, outputs} =\n      create_transaction(\n        sender,\n        session.assigned.utxo,\n        session.assigned.test_currency,\n        session.assigned.fee_amount\n      )\n\n    new_outputs = LoadTest.ChildChain.Transaction.submit_tx(inputs, outputs, [sender])\n\n    Session.assign(session, utxo: List.last(new_outputs))\n  end\n\n  defp create_transaction(sender, input, currency, fee_amount) do\n    amount_per_utxo = get_amount_per_created_utxo(fee_amount)\n    change = input.amount - @spawned_outputs_per_transaction * amount_per_utxo - fee_amount\n\n    created_output = %Utxo{owner: sender.addr, currency: currency, amount: amount_per_utxo}\n    change_output = %Utxo{owner: sender.addr, currency: currency, amount: change}\n\n    {[input], List.duplicate(created_output, @spawned_outputs_per_transaction) ++ [change_output]}\n  end\n\n  defp get_amount_per_created_utxo(fee_amount), do: fee_amount + 2\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/scenario/deposits.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.Scenario.Deposits do\n  @moduledoc \"\"\"\n  The scenario for deposits tests:\n\n  1. It creates two accounts: the depositor and the receiver.\n  2. It funds depositor with the specified amount on the rootchain.\n  3. It creates deposit for the depositor account on the childchain and\n     checks balances on the rootchain and the childchain after this deposit.\n  4. The depositor account sends the specifed amount on the childchain to the receiver\n     and checks its balance on the childchain.\n\n  \"\"\"\n\n  use Chaperon.Scenario\n\n  alias Chaperon.Session\n  alias LoadTest.ChildChain.Deposit\n  alias LoadTest.Ethereum\n  alias LoadTest.Ethereum.Account\n  alias LoadTest.Service.Faucet\n  alias LoadTest.Service.Metrics\n  alias LoadTest.WatcherInfo.Balance\n  alias LoadTest.WatcherInfo.Transaction\n\n  @spec run(Session.t()) :: Session.t()\n  def run(session) do\n    tps = config(session, [:run_config, :tps])\n    period_in_seconds = config(session, [:run_config, :period_in_seconds])\n\n    total_number_of_transactions = tps * period_in_seconds\n    period_in_mseconds = period_in_seconds * 1_000\n\n    session\n    |> cc_spread(\n      :create_deposit_and_make_assertions,\n      total_number_of_transactions,\n      period_in_mseconds\n    )\n    |> await_all(:create_deposit_and_make_assertions)\n  end\n\n  def create_deposit_and_make_assertions(session) do\n    {_, session} =\n      Metrics.run_with_metrics(\n        fn ->\n          do_create_deposit_and_make_assertions(session)\n        end,\n        \"deposits_test\"\n      )\n\n    session\n  end\n\n  defp do_create_deposit_and_make_assertions(session) do\n    with {:ok, from, to} <- create_accounts(session),\n         :ok <- create_deposit(from, session),\n         :ok <- send_value_to_receiver(from, to, session) do\n      {:ok, session}\n    else\n      _error -> {:error, session}\n    end\n  end\n\n  defp create_accounts(session) do\n    initial_amount = config(session, [:chain_config, :initial_amount])\n    {:ok, from_address} = Account.new()\n    {:ok, to_address} = Account.new()\n\n    {:ok, _} = Faucet.fund_root_chain_account(from_address.addr, initial_amount)\n\n    {:ok, from_address, to_address}\n  end\n\n  defp create_deposit(from_address, session) do\n    token = config(session, [:chain_config, :token])\n    deposited_amount = config(session, [:chain_config, :deposited_amount])\n    initial_amount = config(session, [:chain_config, :initial_amount])\n    gas_price = config(session, [:chain_config, :gas_price])\n\n    txhash = Deposit.deposit_from(from_address, deposited_amount, token, 10, gas_price, :txhash)\n\n    gas_used = Ethereum.get_gas_used(txhash)\n\n    with :ok <-\n           fetch_childchain_balance(from_address,\n             amount: deposited_amount,\n             token: token,\n             error: :wrong_childchain_from_balance_after_deposit\n           ),\n         :ok <-\n           fetch_rootchain_balance(\n             from_address,\n             amount: initial_amount - deposited_amount - gas_used,\n             token: token,\n             error: :wrong_rootchain_balance_after_deposit\n           ) do\n      :ok\n    end\n  end\n\n  defp send_value_to_receiver(from_address, to_address, session) do\n    token = config(session, [:chain_config, :token])\n    transferred_amount = config(session, [:chain_config, :transferred_amount])\n\n    with _ <- send_amount_on_childchain(from_address, to_address, token, transferred_amount),\n         :ok <-\n           fetch_childchain_balance(\n             to_address,\n             amount: transferred_amount,\n             token: token,\n             error: :wrong_childchain_to_balance_after_sending_deposit\n           ) do\n      :ok\n    end\n  end\n\n  defp send_amount_on_childchain(from, to, token, amount) do\n    {:ok, [sign_hash, typed_data, _txbytes]} =\n      Transaction.create_transaction(\n        amount,\n        from.addr,\n        to.addr,\n        token\n      )\n\n    Transaction.submit_transaction(typed_data, sign_hash, [from.priv])\n  end\n\n  defp fetch_childchain_balance(account, amount: amount, token: token, error: error) do\n    childchain_balance = Balance.fetch_balance(account.addr, amount, token)\n\n    case childchain_balance[\"amount\"] do\n      ^amount -> :ok\n      _ -> error\n    end\n  end\n\n  defp fetch_rootchain_balance(account, amount: amount, token: token, error: error) do\n    rootchain_balance = Ethereum.fetch_balance(account.addr, token)\n\n    case rootchain_balance do\n      ^amount -> :ok\n      _ -> error\n    end\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/scenario/fund_account.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.Scenario.FundAccount do\n  @moduledoc \"\"\"\n  Funds an account with some ether from the faucet.\n  Returns the new utxo in the session.\n\n  ## configuration values\n  - `account` the account to fund\n  - `initial_funds` the amount to fund (in wei)\n  \"\"\"\n\n  use Chaperon.Scenario\n\n  alias Chaperon.Session\n  alias LoadTest.Service.Faucet\n\n  @spec run(Session.t()) :: Session.t()\n  def run(session) do\n    account = config(session, [:account])\n    initial_funds = config(session, [:initial_funds])\n    test_currency = config(session, [:test_currency])\n    {:ok, utxo} = Faucet.fund_child_chain_account(account, initial_funds, test_currency)\n\n    Session.assign(session, utxo: utxo)\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/scenario/many_standard_exits.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.Scenario.ManyStandardExits do\n  @moduledoc \"\"\"\n  Creates and funds an account, creates many utxos and starts a standard exit on each utxo\n\n  ## configuration values\n  - `exits_per_session` the number od utxos to create and then exit\n  \"\"\"\n\n  use Chaperon.Scenario\n\n  alias LoadTest.ChildChain.WatcherSync\n  alias LoadTest.Ethereum\n  alias LoadTest.Ethereum.Account\n  alias LoadTest.Service.Faucet\n\n  @gas_start_exit 500_000\n  @standard_exit_bond 14_000_000_000_000_000\n\n  def run(session) do\n    exits_per_session = config(session, [:exits_per_session])\n    gas_price = config(session, [:gas_price])\n\n    # Create a new exiter account\n    {:ok, exiter} = Account.new()\n    amount = (@gas_start_exit * gas_price + @standard_exit_bond) * exits_per_session\n\n    # Fund the exiter with some root chain eth\n    {:ok, _} = Faucet.fund_root_chain_account(exiter.addr, amount)\n\n    # Create many utxos on the child chain\n    session =\n      run_scenario(session, LoadTest.Scenario.CreateUtxos, %{\n        sender: exiter,\n        transactions_per_session: 1,\n        utxos_to_create_per_session: exits_per_session\n      })\n\n    # Wait for the last utxo to seen by the watcher\n    :ok = LoadTest.ChildChain.Utxos.wait_for_utxo(exiter.addr, session.assigned.utxo)\n\n    # Start a standard exit on each of the exiter's utxos\n    session =\n      exiter.addr\n      |> LoadTest.ChildChain.Utxos.get_utxos()\n      |> Enum.map(&exit_utxo(session, &1, exiter))\n      |> List.last()\n\n    last_tx_hash = session.assigned.tx_hash\n    {:ok, %{\"status\" => \"0x1\", \"blockNumber\" => last_exit_height}} = Ethereum.transact_sync(last_tx_hash)\n\n    :ok = WatcherSync.watcher_synchronize(root_chain_height: last_exit_height)\n\n    log_info(session, \"Many Standard Exits Test done.\")\n  end\n\n  def exit_utxo(session, utxo, exiter) do\n    run_scenario(\n      session,\n      LoadTest.Scenario.StartStandardExit,\n      %{\n        exiter: exiter,\n        utxo: utxo\n      }\n    )\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/scenario/smoke.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.Scenario.Smoke do\n  @moduledoc \"\"\"\n  Smoke test scenario to ensure services are up\n  \"\"\"\n  use Chaperon.Scenario\n\n  def run(session) do\n    log_info(session, \"run smoke test to make sure services are up...\")\n\n    check_child_chain_up()\n    check_watcher_security_up()\n    check_watcher_info_up()\n\n    log_info(session, \"smoke test done...\")\n  end\n\n  defp check_child_chain_up() do\n    {:ok, response} = ChildChainAPI.Api.Configuration.configuration_get(LoadTest.Connection.ChildChain.client())\n\n    # some sanity check\n    %{\n      \"data\" => %{\n        \"contract_semver\" => _contract_semver,\n        \"deposit_finality_margin\" => _deposit_finality_margin,\n        \"network\" => _network\n      },\n      \"service_name\" => \"child_chain\"\n    } = Jason.decode!(response.body)\n  end\n\n  defp check_watcher_security_up() do\n    {:ok, response} = WatcherSecurityCriticalAPI.Api.Status.status_get(LoadTest.Connection.WatcherSecurity.client())\n\n    # some sanity check\n    %{\n      \"data\" => %{\n        \"byzantine_events\" => _byzantine_events,\n        \"contract_addr\" => _contract_addr\n      },\n      \"service_name\" => \"watcher\"\n    } = Jason.decode!(response.body)\n  end\n\n  defp check_watcher_info_up() do\n    {:ok, response} = WatcherInfoAPI.Api.Stats.stats_get(LoadTest.Connection.WatcherInfo.client())\n\n    # some sanity check\n    %{\n      \"data\" => %{\n        \"average_block_interval_seconds\" => _average_block_interval,\n        \"block_count\" => _block_count\n      },\n      \"service_name\" => \"watcher_info\"\n    } = Jason.decode!(response.body)\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/scenario/spend_eth_utxo.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.Scenario.SpendEthUtxo do\n  @moduledoc \"\"\"\n  Spends a utxo in a transaction.\n\n  Can be done repeatedly by setting `transactions_per_session`\n  Returns the first output of the spent transaction in the session. Normally this will be the change output.\n\n  ## configuration values\n  - `sender` the owner of the utxo\n  - `receiver` the receiver's account\n  - `amount` the amount to spend. If amount + fee is less than the value of the utxo then the change\n     will be sent back to the sender\n  - `transactions_per_session` the number of transactions to send. Each transaction after the first\n     will spend the change output of the previous transaction\n  - `transaction_delay` delay in milliseconds before sending the transaction. Used to control the tx rate.\n  \"\"\"\n\n  use Chaperon.Scenario\n\n  alias Chaperon.Session\n  alias Chaperon.Timing\n\n  def run(session) do\n    fee_amount = Application.fetch_env!(:load_test, :fee_amount)\n\n    sender = config(session, [:sender])\n    receiver = config(session, [:receiver])\n    amount = config(session, [:amount], nil)\n    test_currency = config(session, [:test_currency], nil)\n    delay = config(session, [:transaction_delay], 0)\n    transactions_per_session = config(session, [:transactions_per_session])\n\n    repeat(\n      session,\n      :submit_transaction,\n      [amount, fee_amount, sender, receiver, test_currency, delay],\n      transactions_per_session\n    )\n  end\n\n  def submit_transaction(session, nil, fee_amount, sender, receiver, currency, delay) do\n    utxo = session.assigned.utxo\n    amount = utxo.amount - fee_amount\n    submit_transaction(session, amount, fee_amount, sender, receiver, currency, delay)\n  end\n\n  def submit_transaction(session, amount, fee_amount, sender, receiver, currency, delay) do\n    Process.sleep(delay)\n    utxo = session.assigned.utxo\n    start = Timing.timestamp()\n\n    [next_utxo | _] = LoadTest.ChildChain.Transaction.spend_utxo(utxo, amount, fee_amount, sender, receiver, currency)\n\n    session\n    |> Session.assign(utxo: next_utxo)\n    |> Session.add_metric(\n      {:call, {LoadTest.Scenario.SpendEthUtxo, \"submit_transaction\"}},\n      Timing.timestamp() - start\n    )\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/scenario/start_standard_exit.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.Scenario.StartStandardExit do\n  @moduledoc \"\"\"\n  Starts a standard exit.\n\n  ## configuration values\n  - `exiter` the account that's starting the exit\n  - `utxo` the utxo to exit\n  \"\"\"\n\n  use Chaperon.Scenario\n\n  alias Chaperon.Session\n  alias LoadTest.ChildChain.Exit\n\n  def run(session) do\n    exiter = config(session, [:exiter])\n    utxo = config(session, [:utxo])\n    gas_price = config(session, [:gas_price])\n\n    tx_hash =\n      utxo\n      |> Exit.wait_for_exit_data()\n      |> Exit.start_exit(exiter, gas_price)\n\n    Session.assign(session, tx_hash: tx_hash)\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/scenario/transactions.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.Scenario.Transactions do\n  @moduledoc \"\"\"\n  The scenario for transactions tests:\n\n  1. It creates two accounts: the sender and the receiver.\n  2. It funds sender with the specified amount on the childchain, checks utxos and balance.\n  3. The sender account sends the specifed amount on the childchain to the receiver,\n      checks its balance on the childchain and utxos for both accounts.\n\n  \"\"\"\n\n  use Chaperon.Scenario\n\n  alias Chaperon.Session\n  alias ExPlasma.Encoding\n  alias LoadTest.ChildChain.Transaction\n  alias LoadTest.Ethereum.Account\n  alias LoadTest.Service.Faucet\n  alias LoadTest.Service.Metrics\n  alias LoadTest.WatcherInfo.Balance\n  alias LoadTest.WatcherInfo.Utxo\n\n  @spec run(Session.t()) :: Session.t()\n  def run(session) do\n    tps = config(session, [:run_config, :tps])\n    period_in_seconds = config(session, [:run_config, :period_in_seconds])\n\n    total_number_of_transactions = tps * period_in_seconds\n    period_in_mseconds = period_in_seconds * 1_000\n\n    session\n    |> cc_spread(\n      :create_utxos_and_make_assertions,\n      total_number_of_transactions,\n      period_in_mseconds\n    )\n    |> await_all(:create_utxos_and_make_assertions)\n  end\n\n  def create_utxos_and_make_assertions(session) do\n    {_, session} =\n      Metrics.run_with_metrics(\n        fn ->\n          do_create_utxos_and_make_assertions(session)\n        end,\n        \"transactions_test\"\n      )\n\n    session\n  end\n\n  defp do_create_utxos_and_make_assertions(session) do\n    with {:ok, sender, receiver} <- create_accounts(),\n         {:ok, utxo} <- fund_account(session, sender),\n         :ok <- spend_utxo(session, utxo, sender, receiver) do\n      {:ok, session}\n    else\n      _ -> {:error, session}\n    end\n  end\n\n  defp create_accounts() do\n    {:ok, sender_address} = Account.new()\n    {:ok, receiver_address} = Account.new()\n\n    {:ok, sender_address, receiver_address}\n  end\n\n  defp fund_account(session, account) do\n    initial_amount = config(session, [:chain_config, :initial_amount])\n    token = config(session, [:chain_config, :token])\n\n    with {:ok, utxo} <- fund_childchain_account(account, initial_amount, token),\n         :ok <-\n           fetch_childchain_balance(account,\n             amount: initial_amount,\n             token: Encoding.to_binary(token),\n             error: :wrong_childchain_after_funding\n           ),\n         :ok <- validate_utxos(account, %{utxo | owner: account.addr}) do\n      {:ok, utxo}\n    end\n  end\n\n  defp validate_utxos(account, utxo) do\n    utxo_with_owner =\n      case utxo do\n        :empty -> :empty\n        _ -> %{utxo | owner: account.addr}\n      end\n\n    case Utxo.get_utxos(account, utxo_with_owner) do\n      {:ok, _} -> :ok\n      _other -> :invalid_utxos\n    end\n  end\n\n  defp fund_childchain_account(address, amount, token) do\n    case Faucet.fund_child_chain_account(address, amount, token) do\n      {:ok, utxo} -> {:ok, utxo}\n      _ -> :failed_to_fund_childchain_account\n    end\n  end\n\n  defp spend_utxo(session, utxo, sender, receiver) do\n    amount = config(session, [:chain_config, :initial_amount])\n    token = config(session, [:chain_config, :token])\n    fee = config(session, [:chain_config, :fee])\n    amount_to_transfer = amount - fee\n\n    with [new_utxo] <- Transaction.spend_utxo(utxo, amount_to_transfer, fee, sender, receiver, token),\n         :ok <- validate_utxos(sender, :empty),\n         :ok <- validate_utxos(receiver, %{new_utxo | owner: receiver.addr}),\n         :ok <-\n           fetch_childchain_balance(sender, amount: 0, token: Encoding.to_binary(token), error: :wrong_sender_balance),\n         :ok <-\n           fetch_childchain_balance(receiver,\n             amount: amount_to_transfer,\n             token: Encoding.to_binary(token),\n             error: :wrong_sender_balance\n           ) do\n      :ok\n    end\n  end\n\n  defp fetch_childchain_balance(account, amount: amount, token: token, error: error) do\n    childchain_balance = Balance.fetch_balance(account.addr, amount, token)\n\n    case childchain_balance do\n      nil when amount == 0 -> :ok\n      %{\"amount\" => ^amount} -> :ok\n      _ -> error\n    end\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/scenario/watcher_status.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.Scenario.WatcherStatus do\n  @moduledoc \"\"\"\n  Calls Watcher status.get\n  \"\"\"\n  use Chaperon.Scenario\n\n  alias Chaperon.Session\n\n  def run(session) do\n    {:ok, response} = WatcherSecurityCriticalAPI.Api.Status.status_get(LoadTest.Connection.WatcherSecurity.client())\n    watcher_status = Jason.decode!(response.body)\n    Session.assign(session, watcher_status: watcher_status)\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/service/datadog/api.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.Service.Datadog.API do\n  @moduledoc \"\"\"\n    Funcions for fetching monitor events from datadog.\n  \"\"\"\n\n  require Logger\n\n  @datadog_events_api_path \"api/v1/events\"\n  @datadog_monitor_resolve_path \"monitor/bulk_resolve\"\n  @datadog_app_url \"https://app.datadoghq.com\"\n\n  def assert_metrics(environment, start_unix, end_unix)\n      when is_integer(start_unix) and is_integer(end_unix) do\n    # events aren't pulished instantly, so we will poll them\n    do_assert_metrics(environment, start_unix, end_unix)\n  end\n\n  def assert_metrics(environment, start_datetime, end_datetime) do\n    start_unix = DateTime.to_unix(start_datetime)\n    end_unix = DateTime.to_unix(end_datetime)\n\n    assert_metrics(environment, start_unix, end_unix)\n  end\n\n  defp do_assert_metrics(environment, start_unix, end_unix, poll_count \\\\ 60)\n\n  defp do_assert_metrics(_environment, _start_unix, _end_unix, 0), do: :ok\n\n  defp do_assert_metrics(environment, start_unix, end_unix, poll_count) do\n    case fetch_events(start_unix, end_unix, environment) do\n      {:ok, []} ->\n        Process.sleep(1_000)\n        do_assert_metrics(environment, start_unix, end_unix, poll_count - 1)\n\n      {:ok, events} ->\n        # failed monitors with the same tags do not emit events, so we're resolving\n        # failed monitors for future runs\n        resolve_monitors(events)\n\n        {:error, events}\n\n      other ->\n        other\n    end\n  end\n\n  defp fetch_events(start_time, end_time, environment, retries \\\\ 5)\n\n  defp fetch_events(start_time, end_time, environment, 0) do\n    Logger.error(\"failed to fetch events #{inspect({start_time, end_time, environment})}\")\n\n    {:error, :failed_to_fetch_event}\n  end\n\n  defp fetch_events(start_time, end_time, environment, retries) do\n    params = %{\n      start: start_time,\n      end: end_time,\n      tags: environment,\n      unaggregated: true\n    }\n\n    url = api_url() <> @datadog_events_api_path <> \"?\" <> URI.encode_query(params)\n\n    case HTTPoison.get(url, headers()) do\n      {:ok, %{status_code: 200, body: body}} ->\n        events =\n          body\n          |> Jason.decode!()\n          |> parse_events(environment)\n\n        {:ok, events}\n\n      {:ok, %{body: body}} ->\n        Logger.warn(\"failed to fetch events #{inspect(body)}. retrying\")\n        fetch_events(start_time, end_time, environment, retries - 1)\n\n      {:error, error} ->\n        Logger.warn(\"failed to fetch events #{inspect(error)}. retrying\")\n        fetch_events(start_time, end_time, environment, retries - 1)\n    end\n  end\n\n  defp parse_events(events_response, environment) do\n    events_response[\"events\"]\n    |> Enum.filter(fn event ->\n      event[\"alert_type\"] == \"error\" and String.contains?(event[\"text\"], environment)\n    end)\n    |> Enum.map(fn event ->\n      {:ok, date} = DateTime.from_unix(event[\"date_happened\"])\n\n      %{\n        \"title\" => event[\"title\"],\n        \"url\" => @datadog_app_url <> event[\"url\"],\n        \"date\" => date,\n        \"monitor_id\" => find_monitor_id(event[\"text\"])\n      }\n    end)\n  end\n\n  defp resolve_monitors(events) do\n    params =\n      events\n      |> Enum.map(fn event -> event[\"monitor_id\"] end)\n      |> Enum.filter(fn id -> !(is_nil(id) or id == \"\") end)\n      |> Enum.uniq()\n      |> Enum.map(fn id -> %{id => \"ALL_GROUPS\"} end)\n\n    do_resolve_monitors(params)\n  end\n\n  defp do_resolve_monitors(params, retries \\\\ 5)\n\n  defp do_resolve_monitors([], _), do: :ok\n\n  defp do_resolve_monitors(params, 0) do\n    Logger.error(\"failed to resolve monitors #{params}\")\n\n    {:error, :failed_to_resolve_monitors}\n  end\n\n  defp do_resolve_monitors(params, retries) do\n    payload = Jason.encode!(%{\"resolve\" => params})\n\n    url = api_url() <> @datadog_monitor_resolve_path\n\n    case HTTPoison.post(url, payload, headers()) do\n      {:ok, %{status_code: 200}} ->\n        :ok\n\n      {:ok, %{body: body}} ->\n        Logger.warn(\"failed to resolve monitors #{inspect(body)}. retrying\")\n\n        Process.sleep(1_000)\n\n        do_resolve_monitors(params, retries - 1)\n\n      {:error, error} ->\n        Logger.warn(\"failed to resolve monitors #{inspect(error)}. retrying\")\n\n        Process.sleep(1_000)\n\n        do_resolve_monitors(params, retries - 1)\n    end\n  end\n\n  defp find_monitor_id(text) do\n    case String.split(text, [\"monitors#\", \"?to_ts\"], parts: 3) do\n      [_, id, _] -> String.to_integer(id)\n      _ -> nil\n    end\n  end\n\n  defp headers() do\n    %{\n      \"Content-Type\" => \"application/json\",\n      \"DD-API-KEY\" => api_key(),\n      \"DD-APPLICATION-KEY\" => app_key()\n    }\n  end\n\n  defp api_key() do\n    Keyword.fetch!(datadog_config(), :api_key)\n  end\n\n  defp app_key() do\n    Keyword.fetch!(datadog_config(), :app_key)\n  end\n\n  defp api_url() do\n    Keyword.fetch!(datadog_config(), :api_url)\n  end\n\n  defp datadog_config() do\n    Application.fetch_env!(:load_test, :datadog)\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/service/datadog/dummy_statix.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.Service.Datadog.DummyStatix do\n  @moduledoc \"\"\"\n  Useful for overwritting Statix behaviour.\n  \"\"\"\n  defmacro __using__(_opts) do\n    quote location: :keep do\n      @behaviour Statix\n      def connect(options \\\\ []), do: :ok\n\n      def increment(_), do: :ok\n      def increment(_, _, options \\\\ []), do: :ok\n\n      def decrement(_, val \\\\ 1, options \\\\ []), do: :ok\n\n      def gauge(_, val, options \\\\ []), do: :ok\n\n      def histogram(_, val, options \\\\ []), do: :ok\n\n      def timing(_, val, options \\\\ []), do: :ok\n\n      def measure(key, options \\\\ [], fun), do: :ok\n\n      def set(key, val, options \\\\ []), do: :ok\n\n      def event(key, val, options), do: :ok\n\n      def service_check(key, val, options), do: :ok\n\n      def current_conn(), do: %Statix.Conn{sock: __MODULE__}\n    end\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/service/datadog.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.Service.Datadog do\n  @moduledoc \"\"\"\n  Datadog connection wrapper\n  \"\"\"\n\n  # we want to override Statix\n  # because we don't want to send metrics in unittests\n  case Application.get_env(:load_test, :record_metrics) do\n    true -> use Statix, runtime_config: true\n    _ -> use LoadTest.Service.Datadog.DummyStatix\n  end\n\n  use GenServer\n  require Logger\n\n  def start_link(_params), do: GenServer.start_link(__MODULE__, [], [])\n\n  def init(_opts) do\n    _ = Process.flag(:trap_exit, true)\n    _ = Logger.info(\"Starting #{inspect(__MODULE__)} and connecting to Datadog.\")\n\n    :ok = __MODULE__.connect()\n\n    _ = Logger.info(\"Datadog Connection for Statix was opened\")\n\n    {:ok, []}\n  end\n\n  def handle_info({:EXIT, port, reason}, %Statix.Conn{sock: __MODULE__} = state) do\n    _ = Logger.error(\"Port in #{inspect(__MODULE__)} #{inspect(port)} exited with reason #{reason}\")\n    {:stop, :normal, state}\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/service/faucet.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.Service.Faucet do\n  @moduledoc \"\"\"\n  Handles funding accounts on child chain.\n\n  For simplicity, the faucet will always use its largest value utxo to fund other accounts.\n  If its largest value utxo is insufficient (or if it has no utxos) it will do a deposit,\n  wait for it to finalize and then use that deposit utxo for funding accounts.\n\n  This means that faucet account must have sufficient funds on the root chain.\n\n  After a few test runs, the faucet can end up with a large amount of utxos. This is not a\n  problem per se, but keeping the number of utxos down can speed things up.\n  You can merge utxos periodically by running merge_utxos e.g.\n\n  MIX_ENV=test mix run -e \"LoadTest.Service.Faucet.merge_utxos(<<0::160>>)\"\n  \"\"\"\n\n  require Logger\n\n  use GenServer\n\n  alias ExPlasma.Encoding\n  alias ExPlasma.Utxo\n  alias LoadTest.ChildChain.Deposit\n  alias LoadTest.ChildChain.Transaction\n  alias LoadTest.ChildChain.Utxos\n  alias LoadTest.Ethereum\n  alias LoadTest.Ethereum.Account\n\n  # Submitting a transaction to the childchain can fail if it is under heavy load,\n  # allow the faucet to retry to avoid failing the test prematurely.\n  @fund_child_chain_account_timeout 100_000\n\n  @type state :: %__MODULE__{\n          faucet_account: Account.t(),\n          fee: pos_integer(),\n          faucet_deposit_amount: pos_integer(),\n          deposit_finality_margin: pos_integer(),\n          gas_price: pos_integer(),\n          utxos: map()\n        }\n  defstruct [:faucet_account, :fee, :faucet_deposit_amount, :deposit_finality_margin, :gas_price, utxos: %{}]\n\n  @doc \"\"\"\n  Sends funds to an account on the rootchain.\n  \"\"\"\n  @spec fund_root_chain_account(<<_::160>>, pos_integer()) :: Utxo.t()\n  def fund_root_chain_account(receiver, amount) do\n    GenServer.call(__MODULE__, {:fund_root_chain, receiver, amount}, :infinity)\n  end\n\n  @doc \"\"\"\n  Sends funds to an account on the childchain.\n  If the faucet doesn't have enough funds it will deposit more. Note that this can take some time to finalize.\n  \"\"\"\n  @spec fund_child_chain_account(Account.t(), pos_integer(), Utxo.address_binary()) :: Utxo.t()\n  def fund_child_chain_account(receiver, amount, currency) when byte_size(currency) == 20 do\n    GenServer.call(__MODULE__, {:fund_child_chain, receiver, amount, currency}, :infinity)\n  end\n\n  def fund_child_chain_account(receiver, amount, currency) do\n    fund_child_chain_account(receiver, amount, Encoding.to_binary(currency))\n  end\n\n  @doc \"\"\"\n  Returns the faucet account.\n  \"\"\"\n  @spec get_faucet() :: Account.t()\n  def get_faucet() do\n    GenServer.call(__MODULE__, :get_faucet)\n  end\n\n  @doc \"\"\"\n  Merges all the utxos of the given currency into one.\n  Note that this can take some time.\n  \"\"\"\n  @spec merge_utxos(Utxo.address_binary()) :: Utxo.t()\n  def merge_utxos(currency) when byte_size(currency) == 20 do\n    GenServer.call(__MODULE__, {:merge_utxos, currency}, :infinity)\n  end\n\n  @spec merge_utxos(Utxo.address_hex()) :: Utxo.t()\n  def merge_utxos(currency), do: merge_utxos(Encoding.to_binary(currency))\n\n  def start_link(config) do\n    GenServer.start_link(__MODULE__, config, name: __MODULE__)\n  end\n\n  def init(config) do\n    {:ok, faucet_account} = Account.new(Keyword.fetch!(config, :faucet_private_key))\n    Logger.debug(\"Using faucet: #{Encoding.to_hex(faucet_account.addr)}\")\n\n    state =\n      struct!(\n        __MODULE__,\n        faucet_account: faucet_account,\n        fee: Keyword.fetch!(config, :fee_amount),\n        faucet_deposit_amount: Keyword.fetch!(config, :faucet_deposit_amount),\n        deposit_finality_margin: Keyword.fetch!(config, :deposit_finality_margin),\n        gas_price: Keyword.fetch!(config, :gas_price)\n      )\n\n    {:ok, state}\n  end\n\n  def handle_call(:get_faucet, _from, state) do\n    {:reply, {:ok, state.faucet_account}, state}\n  end\n\n  def handle_call({:fund_child_chain, receiver, amount, currency}, _from, state) do\n    utxo = get_funding_utxo(state, currency, amount)\n\n    Logger.debug(\"Funding user #{Encoding.to_hex(receiver.addr)} with #{amount} from utxo: #{Utxo.pos(utxo)}\")\n\n    outputs =\n      Transaction.spend_utxo(\n        utxo,\n        amount,\n        state.fee,\n        state.faucet_account,\n        receiver,\n        currency,\n        @fund_child_chain_account_timeout\n      )\n\n    [next_faucet_utxo, user_utxo] =\n      case outputs do\n        [single_output] -> [nil, single_output]\n        [change_output, user_output] -> [change_output, user_output]\n      end\n\n    updated_state = Map.put(state, :utxos, Map.put(state.utxos, currency, next_faucet_utxo))\n\n    {:reply, {:ok, user_utxo}, updated_state}\n  end\n\n  def handle_call({:fund_root_chain, receiver, amount}, _from, state) do\n    Logger.debug(\"Funding user #{Encoding.to_hex(receiver)} with #{amount} on root chain}\")\n\n    tx = %LoadTest.Ethereum.Transaction{\n      to: receiver,\n      value: amount,\n      gas_price: state.gas_price,\n      gas_limit: 21_000\n    }\n\n    {:ok, tx_hash} = Ethereum.send_raw_transaction(tx, state.faucet_account)\n    {:ok, _} = Ethereum.transact_sync(tx_hash)\n\n    {:reply, {:ok, tx_hash}, state}\n  end\n\n  def handle_call({:merge_utxos, currency}, _from, state) do\n    utxos = Utxos.get_utxos(state.faucet_account.addr)\n    Logger.debug(\"Merging #{length(utxos)} utxos of #{Encoding.to_hex(currency)}\")\n    utxo = Utxos.merge(utxos, currency, state.faucet_account)\n    {:reply, {:ok, utxo}, state}\n  end\n\n  @spec get_funding_utxo(state(), Utxo.address_binary(), pos_integer()) :: Utxo.t()\n  defp get_funding_utxo(state, currency, amount) do\n    utxo = choose_largest_utxo(state.utxos[currency], state.faucet_account, currency)\n\n    if utxo == nil or utxo.amount - amount - state.fee < 0 do\n      deposit(\n        state.faucet_account,\n        max(state.faucet_deposit_amount, amount + state.fee),\n        currency,\n        state.deposit_finality_margin,\n        state.gas_price\n      )\n    else\n      utxo\n    end\n  end\n\n  defp choose_largest_utxo(nil, account, currency) do\n    account.addr\n    |> Utxos.get_utxos()\n    |> Utxos.get_largest_utxo(currency)\n  end\n\n  defp choose_largest_utxo(utxo, _account, _currency), do: utxo\n\n  @spec deposit(Account.t(), pos_integer(), Utxo.address_binary(), pos_integer(), pos_integer()) :: Utxo.t()\n  defp deposit(faucet_account, amount, currency, deposit_finality_margin, gas_price) do\n    Logger.debug(\"Not enough funds in the faucet, depositing #{amount} from the root chain\")\n\n    {:ok, utxo} = Deposit.deposit_from(faucet_account, amount, currency, deposit_finality_margin, gas_price, :utxo)\n    utxo\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/service/metrics.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.Service.Metrics do\n  @moduledoc \"\"\"\n  Functions for aggregating metrics.\n  \"\"\"\n\n  alias LoadTest.Service.Datadog\n  alias LoadTest.Service.Datadog.API\n\n  def run_with_metrics(func, property) do\n    case Application.get_env(:load_test, :record_metrics) do\n      true -> do_run_with_metrics(func, property)\n      false -> func.()\n    end\n  end\n\n  def assert_metrics(start_datetime, end_datetime) do\n    env = :statix |> Application.get_env(:tags) |> List.first()\n\n    API.assert_metrics(env, start_datetime, end_datetime)\n  end\n\n  defp do_run_with_metrics(func, property) do\n    {time, result} = :timer.tc(func)\n\n    time_ms = time / 1_000\n\n    case result do\n      {:ok, _} -> record_success(property, time_ms)\n      :ok -> record_success(property, time_ms)\n      {:error, :data_not_found} -> record_success(property, time_ms)\n      _ -> record_failure(property, time_ms)\n    end\n\n    result\n  end\n\n  defp record_success(property, time) do\n    record_datadog(property, time, \"_success\")\n  end\n\n  defp record_failure(property, time) do\n    record_datadog(property, time, \"_failure\")\n  end\n\n  defp record_datadog(property, time, postfix) do\n    property_name = property <> postfix\n    total_property_name = property <> \"_count\"\n\n    :ok = Datadog.gauge(property_name, time, tags: tags())\n\n    increment_counter(property_name <> \"_count\")\n    increment_counter(total_property_name)\n  end\n\n  defp increment_counter(property) do\n    :ok = Datadog.increment(property, 1, tags: tags())\n  end\n\n  defp tags() do\n    Application.get_env(:statix, :tags)\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/service/sleeper.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.Service.Sleeper do\n  @moduledoc \"\"\"\n    Sleeps the current process for the given timeout and print the given warning.\n  \"\"\"\n\n  require Logger\n\n  @spec sleep(String.t()) :: :ok\n  def sleep(message) do\n    _ = Logger.info(message)\n    Process.sleep(retry_sleep())\n  end\n\n  defp retry_sleep() do\n    Application.fetch_env!(:load_test, :retry_sleep)\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/service/sync.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.Service.Sync do\n  @moduledoc \"\"\"\n  Provides a function for repeating a function call until a given criteria is met.\n  \"\"\"\n\n  require Logger\n\n  alias LoadTest.Service.Sleeper\n\n  @doc \"\"\"\n  Repeats f until f returns {:ok, ...}, :ok OR exception is raised (see :erlang.exit, :erlang.error) OR timeout\n  after `timeout` milliseconds specified\n\n  Simple throws and :badmatch are treated as signals to repeat\n  \"\"\"\n  def repeat_until_success(f, timeout, message) do\n    fn -> do_repeat_until_success(f, message) end\n    |> Task.async()\n    |> Task.await(timeout)\n  end\n\n  defp do_repeat_until_success(f, message) do\n    case f.() do\n      :ok ->\n        :ok\n\n      {:ok, _} = return ->\n        return\n\n      result ->\n        repeat(f, message, result)\n    end\n  catch\n    result ->\n      repeat(f, message, result)\n  end\n\n  defp repeat(f, message, result) do\n    Sleeper.sleep(message <> \" #{inspect(result)}\")\n    do_repeat_until_success(f, message)\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/test_runner/config.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.TestRunner.Config do\n  @moduledoc \"\"\"\n  Command line args parser for TestRunner.\n  \"\"\"\n  alias ExPlasma.Encoding\n  alias LoadTest.TestRunner.Help\n\n  @tests %{\n    \"deposits\" => LoadTest.Runner.Deposits,\n    \"transactions\" => LoadTest.Runner.Transactions\n  }\n\n  @configs %{\n    \"deposits\" => %{\n      token: {:binary, \"0x0000000000000000000000000000000000000000\"},\n      initial_amount: 500_000_000_000_000_000,\n      deposited_amount: 200_000_000_000_000_000,\n      transferred_amount: 100_000_000_000_000_000,\n      gas_price: 2_000_000_000\n    },\n    \"transactions\" => %{\n      token: \"0x0000000000000000000000000000000000000000\",\n      initial_amount: 760,\n      fee: 75\n    }\n  }\n\n  def parse() do\n    case System.argv() do\n      [\"make_assertions\", start_time, end_time] ->\n        start_time_integer = String.to_integer(start_time)\n        end_time_integer = String.to_integer(end_time)\n\n        {:make_assertions, start_time_integer, end_time_integer}\n\n      [test, rate, period] ->\n        {:run_tests, config(test, rate, period, \"true\")}\n\n      [test, rate, period, make_assertions] ->\n        {:run_tests, config(test, rate, period, make_assertions)}\n\n      [\"help\"] ->\n        Help.help()\n\n      [\"help\", \"env\"] ->\n        Help.help(\"env\")\n\n      [\"help\", name] ->\n        Help.help(name)\n    end\n  end\n\n  defp config(test, rate, period, make_assertions) do\n    rate_int = String.to_integer(rate)\n    period_int = String.to_integer(period)\n\n    runner_module = Map.fetch!(@tests, test)\n\n    # Chaperon's SpreadAsyns (https://github.com/polleverywhere/chaperon/blob/13cc4a2d2a7baacddf20c46397064b5e42a48d97/lib/chaperon/action/spread_async.ex)\n    # spawns a separate process for each execution. VM may fail if too many processes are spawned\n    if rate_int * period_int > 200_000, do: raise(\"too many processes\")\n\n    run_config = %{\n      tps: rate_int,\n      period_in_seconds: period_int\n    }\n\n    chain_config = read_config!(test)\n\n    config = %{\n      run_config: run_config,\n      chain_config: chain_config,\n      make_assertions: parse_boolean(make_assertions),\n      timeout: :infinity\n    }\n\n    {runner_module, config}\n  end\n\n  defp parse_boolean(bool) do\n    case bool do\n      \"true\" -> true\n      _ -> false\n    end\n  end\n\n  defp read_config!(test) do\n    config_path = System.get_env(\"TEST_CONFIG_PATH\")\n\n    case config_path do\n      nil ->\n        @configs\n        |> Map.fetch!(test)\n        |> parse_config_values()\n\n      _ ->\n        parse_config_file!(config_path, test)\n    end\n  end\n\n  defp parse_config_file!(file_path, test) do\n    default_config = Map.fetch!(@configs, test)\n    config = file_path |> File.read!() |> Jason.decode!()\n\n    default_config\n    |> Enum.map(fn {key, default_value} ->\n      string_key = Atom.to_string(key)\n\n      value =\n        case default_value do\n          {type, value} -> {type, config[string_key] || value}\n          value -> config[string_key] || value\n        end\n\n      {key, value}\n    end)\n    |> Map.new()\n    |> parse_config_values()\n  end\n\n  defp parse_config_values(config) do\n    config\n    |> Enum.map(fn {key, value} ->\n      parsed_value =\n        case value do\n          {:binary, string} -> Encoding.to_binary(string)\n          value -> value\n        end\n\n      {key, parsed_value}\n    end)\n    |> Map.new()\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/test_runner/help.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.TestRunner.Help do\n  @moduledoc \"\"\"\n  Shows help info for TestRunner.\n  \"\"\"\n\n  require Logger\n\n  @help \"\"\"\n\n  `LoadTest.TestRunner` accepts three required parameters:\n\n  1. Test name (`transactions` or `deposits`)\n  2. Rate in tests per second\n  3. Period in seconds\n\n  For example, if you want to run `deposits` with 5 tests / second rate over 20 seconds, you\n  should run the following command:\n\n  ```\n   mix run -e \"LoadTest.TestRunner.run()\" -- deposits 5 20\n  ```\n\n  To modify tests values use `TEST_CONFIG_PATH`. It should contain a path to json file containing\n  test values:\n\n  ```\n  TEST_CONFIG_PATH=./my_file mix run -e \"LoadTest.TestRunner.run()\" -- deposits 1 5\n  ```\n\n  To see which values can be overridden, use\n\n  ```\n  mix run -e \"LoadTest.TestRunner.run()\" -- help test_name\n  ```\n\n  To see env variable, use:\n\n  ```\n  mix run -e \"LoadTest.TestRunner.run()\" -- help env\n  ```\n\n  Additonal notes.\n\n  These tests use datadog to collect metrics so you need to set:\n\n  - STATIX_TAG - env tag used by statsd/datadog-agent. different test runs are distinguished by this tag in datadog dashboard\n  - DD_API_KEY - datadog api key\n  - DD_APP_KEY - datadog app key\n\n  Available dashboards are:\n  - https://app.datadoghq.com/dashboard/rpx-xu2-b2g/deposits-perf-tests - deposits tests\n  - https://app.datadoghq.com/dashboard/7kh-xx4-9qu/transactions-perf-tests - transactions tests\n\n  Since `LoadTest.Ethereum.NonceTracker` is used to track nonces for addresses in the Ethereum,\n  it's not possible to run multiple instances of these tests using the same addresses. It may cause race conditions.\n\n\n  Creating new tests.\n\n  1. Create Chaperon runner (see `LoadTest.Runner.Transactions`)\n  2. Create Chaperon scenario (see `LoadTest.Scenario.Transactions`)\n  3. Wrap functions that you want to collect metrics for with `LoadTest.Service.Metrics.run_with_metrics/2`)\n  4. Run tests so metrics are sent to Datadog\n  5. Create dashboards and monitors in Datadog.\n\n\n  Running tests without assertions.\n\n  You can run tests without assertions by passing `false` as the last parameter:\n\n  ```\n  STATIX_TAG=\"env:perf_circleci\" mix run -e \"LoadTest.TestRunner.run()\" -- \"transactions\" 1 80 false\n  ```\n\n  To just check if there are any events in the given period of time run passing start time and end time:\n\n  ```\n  STATIX_TAG=\"env:perf_circleci\" mix run -e \"LoadTest.TestRunner.run()\" -- \"make_assertions\" 1605775276 1605785276\n  ```\n  \"\"\"\n\n  @help_test %{\n    \"deposits\" => \"\"\"\n\n    A single iteration of this test consists of the following steps:\n\n    1. It creates two accounts: the depositor and the receiver.\n    2. It funds depositor with the specified amount (`initial_amount`) on the rootchain.\n    3. It creates deposit (`deposited_amount`) with gas price `gas_price` for the depositor account on the childchain and\n       checks balances on the rootchain and the childchain after this deposit.\n    4. The depositor account sends the specifed amount (`transferred_amount`) on the childchain to the receiver\n      and checks its balance on the childchain.\n\n    Overridable parameters are:\n\n    - token. default value is \"0x0000000000000000000000000000000000000000\"\n    - initial_amount. default value is 500_000_000_000_000_000\n    - deposited_amount default value is 200_000_000_000_000_000\n    - transferred_amount. default value is 100_000_000_000_000_000\n    - gas_price. default value is 2_000_000_000\n    \"\"\",\n    \"transactions\" => \"\"\"\n\n    A single iteration of this test consists of the following steps:\n\n    1.1 Two accounts are created - the sender and the receiver\n\n    2.1 The sender account is funded with `initial_amount` `token`\n    2.2 The balance on the childchain of the sender is validated using WatcherInfoAPI.Api.Account.account_get_balance API.\n    2.3 Utxos of the sender are validated using WatcherInfoAPI.Api.Account.account_get_utxos API\n\n    3.1 The sender sends all his tokens to the receiver with fee `fee`\n    3.2 The balance on the childchain of the sender is validated\n    3.3 The balance on the childchain of the receiver is validated\n    3.4 Utxos of the sender are validated\n    3.5 Utxos of the receiver are validated\n\n    Overridable parameters are:\n\n    - initial_balance. default value is 760\n    - token. default value is \"0x0000000000000000000000000000000000000000\"\n    - fee. default value is 75\n    \"\"\"\n  }\n\n  @env \"\"\"\n\n  ETHEREUM_RPC_URL - Ethereum Json RPC url. Default value is http://localhost:8545\n  RETRY_SLEEP - Sleeping period used when polling data. Default value is 1000 (ms)\n  CHILD_CHAIN_URL - Childcahin url. Default value is http://localhost:9656\n  WATCHER_SECURITY_URL - Watcher security url. Default value is http://localhost:7434\n  WATCHER_INFO_URL - Watcehr info url. Default value is http://localhost:7534\n  LOAD_TEST_FAUCET_PRIVATE_KEY - Faucet private key. Default value is 0xd885a307e35738f773d8c9c63c7a3f3977819274638d04aaf934a1e1158513ce\n  CONTRACT_ADDRESS_ETH_VAULT - Eth vault contact address\n  CONTRACT_ADDRESS_PAYMENT_EXIT_GAME - Payment exit game contract address\n  CHILD_BLOCK_INTERVAL - Block generation interval. Default value is 1000 (ms)\n  CONTRACT_ADDRESS_PLASMA_FRAMEWORK - Plasma framework contract address\n  CONTRACT_ADDRESS_ERC20_VAULT - Erc 20 vault contract address\n  FEE_AMOUNT - Fee amount used by faucet when funding accounts. Default value is 75\n  DEPOSIT_FINALITY_MARGIN - Number of comfirmation for a deposit. Default value is 10\n  \"\"\"\n\n  def help() do\n    IO.puts(@help)\n  end\n\n  def help(\"env\") do\n    IO.puts(@env)\n  end\n\n  def help(test_name) do\n    case @help_test[test_name] do\n      nil ->\n        tests =\n          @help_test\n          |> Map.keys()\n          |> Enum.join(\", \")\n\n        IO.puts(\"\"\"\n\n        Documentation for `#{test_name}` is not found. Available tests are #{tests}.\n\n        \"\"\")\n\n      doc ->\n        IO.puts(doc)\n    end\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/test_runner.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.TestRunner do\n  @moduledoc \"\"\"\n  This module runs tests using `mix run`. For example:\n\n  mix run -e \"LoadTest.TestRunner.run()\" -- \"deposits\" \"1\" \"5\"\n\n  It accepts three arguments:\n  - test name\n  - transactions per seconds\n  - period in seconds\n\n  You can also modify values for tests by providing `TEST_CONFIG_PATH` env variable, it should contain\n  the path to json file. For example:\n\n    TEST_CONFIG_PATH=./my_file mix run -e \"LoadTest.TestRunner.run()\" -- \"deposits\" \"1\" \"5\" 90\n\n  It fetches all configuration params from env vars.\n  \"\"\"\n\n  alias LoadTest.Service.Metrics\n  alias LoadTest.TestRunner.Config\n\n  @circleci_tag \"env:perf_circleci\"\n\n  def run() do\n    case Config.parse() do\n      {:run_tests, {runner_module, config}} -> run_test(runner_module, config)\n      {:make_assertions, start_time, end_time} -> make_assertions(start_time, end_time)\n      :ok -> :ok\n    end\n  end\n\n  defp run_test(runner_module, config) do\n    case System.get_env(\"STATIX_TAG\") do\n      nil -> raise(\"STATIX_TAG is not set\")\n      _ -> :ok\n    end\n\n    start_datetime = DateTime.utc_now()\n\n    maybe_add_custom_tag(start_datetime)\n\n    Chaperon.run_load_test(runner_module, print_results: true, config: config)\n\n    end_datetime = DateTime.utc_now()\n\n    case config.make_assertions do\n      true -> make_assertions(start_datetime, end_datetime)\n      _ -> :ok\n    end\n  end\n\n  defp make_assertions(start_time, end_time) do\n    case Metrics.assert_metrics(start_time, end_time) do\n      :ok ->\n        System.halt(0)\n\n      {:error, errors} ->\n        # credo:disable-for-next-line\n        IO.inspect(\"errors: #{inspect(errors)}\")\n        System.halt(1)\n    end\n  end\n\n  defp maybe_add_custom_tag(start_date) do\n    tags = Application.get_env(:statix, :tags)\n\n    tags\n    |> Enum.find(fn value -> value == @circleci_tag end)\n    |> case do\n      nil ->\n        :ok\n\n      _ ->\n        postfix = start_date |> to_string |> String.replace(\" \", \"-\") |> String.downcase()\n        new_tag = @circleci_tag <> \":\" <> postfix\n        Application.put_env(:statix, :tags, [new_tag | tags])\n    end\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/utils/encoding.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.Utils.Encoding do\n  @moduledoc \"\"\"\n  Utility module for converting between hex strings and other types.\n  \"\"\"\n\n  def to_binary(hex) do\n    hex\n    |> String.replace_prefix(\"0x\", \"\")\n    |> String.upcase()\n    |> Base.decode16!()\n  end\n\n  @spec to_hex(binary | non_neg_integer) :: binary\n  def to_hex(non_hex)\n\n  def to_hex(raw) when is_binary(raw), do: \"0x\" <> Base.encode16(raw, case: :lower)\n  def to_hex(int) when is_integer(int), do: \"0x\" <> Integer.to_string(int, 16)\n\n  # because https://github.com/rrrene/credo/issues/583, we need to:\n  # credo:disable-for-next-line Credo.Check.Consistency.SpaceAroundOperators\n  @spec from_hex(<<_::16, _::_*8>>) :: binary\n  def from_hex(\"0x\" <> encoded), do: Base.decode16!(encoded, case: :lower)\n\n  @spec encode_deposit(ExPlasma.Transaction.t()) :: %{data: binary()}\n  def encode_deposit(transaction) do\n    tx_bytes = ExPlasma.Transaction.encode(transaction)\n    data = encode_data(\"deposit(bytes)\", [tx_bytes])\n    %{data: data}\n  end\n\n  @spec encode_data(String.t(), list()) :: binary\n  defp encode_data(function_signature, data) do\n    data = ABI.encode(function_signature, data)\n    \"0x\" <> Base.encode16(data, case: :lower)\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/watcher_info/balance.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.WatcherInfo.Balance do\n  @moduledoc \"\"\"\n  Functions related to balances on the childchain\n  \"\"\"\n  require Logger\n\n  alias ExPlasma.Encoding\n  alias LoadTest.Ethereum.Account\n  alias LoadTest.Service.Sync\n  alias LoadTest.WatcherInfo.Client\n\n  @poll_timeout 60_000\n\n  @spec fetch_balance(Account.addr_t(), non_neg_integer(), Account.addr_t()) :: non_neg_integer() | :error | nil | map()\n  def fetch_balance(address, amount, currency \\\\ <<0::160>>) do\n    {:ok, result} =\n      Sync.repeat_until_success(\n        fn ->\n          do_fetch_balance(Encoding.to_hex(address), amount, Encoding.to_hex(currency))\n        end,\n        @poll_timeout,\n        \"Failed to fetch childchain balance\"\n      )\n\n    result\n  end\n\n  defp do_fetch_balance(address, amount, currency) do\n    response =\n      case Client.get_balances(address) do\n        {:ok, decoded_response} ->\n          Enum.find(decoded_response[\"data\"], fn data -> data[\"currency\"] == currency end)\n\n        result ->\n          Logger.error(\"Failed to fetch balance from childchain #{inspect(result)}\")\n\n          :error\n      end\n\n    case response do\n      # empty response is considered no account balance!\n      nil when amount == 0 ->\n        {:ok, nil}\n\n      %{\"amount\" => ^amount} = balance ->\n        {:ok, balance}\n\n      response ->\n        {:error, response}\n    end\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/watcher_info/client.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.WatcherInfo.Client do\n  @moduledoc \"\"\"\n    Interface to Watcher info which collects metrics.\n  \"\"\"\n  alias LoadTest.Service.Metrics\n  alias WatcherInfoAPI.Api.Account\n\n  def get_balances(address) do\n    Metrics.run_with_metrics(\n      fn ->\n        request(fn ->\n          Account.account_get_balance(client(), %{address: address})\n        end)\n      end,\n      \"WatcherInfo.get_balances\"\n    )\n  end\n\n  def get_utxos(address) do\n    Metrics.run_with_metrics(\n      fn ->\n        request(fn ->\n          Account.account_get_utxos(client(), %{address: address})\n        end)\n      end,\n      \"WatcherInfo.get_utxos\"\n    )\n  end\n\n  defp request(func) do\n    case func.() do\n      {:ok, response} ->\n        case Jason.decode!(response.body) do\n          %{\"data\" => %{\"object\" => \"error\"}} = failure -> {:error, failure}\n          decoded_response -> {:ok, decoded_response}\n        end\n\n      other ->\n        other\n    end\n  end\n\n  defp client() do\n    LoadTest.Connection.WatcherInfo.client()\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/watcher_info/transaction.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.WatcherInfo.Transaction do\n  @moduledoc \"\"\"\n    Functions for working with transactions WatherInfo API\n  \"\"\"\n\n  alias ExPlasma.Encoding\n  alias LoadTest.Ethereum.Account\n  alias LoadTest.Service.Metrics\n  alias LoadTest.Service.Sync\n\n  @poll_timeout 60_000\n\n  @spec create_transaction(\n          non_neg_integer(),\n          Account.addr_t(),\n          Account.addr_t(),\n          Account.addr_t(),\n          non_neg_integer()\n        ) ::\n          {:ok, [binary()]} | {:error, map()}\n\n  def create_transaction(amount_in_wei, input_address, output_address, currency \\\\ <<0::160>>, timeout \\\\ 120_000) do\n    func = fn ->\n      Metrics.run_with_metrics(\n        fn -> do_create_transaction(amount_in_wei, input_address, output_address, currency) end,\n        \"WatcherInfo.create_transaction\"\n      )\n    end\n\n    Sync.repeat_until_success(func, timeout, \"Failed to create a transaction\")\n  end\n\n  @spec submit_transaction(binary(), binary(), [binary()]) :: map()\n  def submit_transaction(typed_data, sign_hash, private_keys) do\n    signatures =\n      Enum.map(private_keys, fn private_key ->\n        sign_hash\n        |> to_binary()\n        |> signature_digest(private_key)\n        |> Encoding.to_hex()\n      end)\n\n    typed_data_signed = Map.put_new(typed_data, \"signatures\", signatures)\n\n    Sync.repeat_until_success(\n      fn ->\n        Metrics.run_with_metrics(\n          fn -> submit_typed(typed_data_signed) end,\n          \"WatcherInfo.submit_typed\"\n        )\n      end,\n      @poll_timeout,\n      \"Failed to submit transaction\"\n    )\n  end\n\n  defp do_create_transaction(amount_in_wei, input_address, output_address, currency) do\n    transaction = %WatcherInfoAPI.Model.CreateTransactionsBodySchema{\n      owner: Encoding.to_hex(input_address),\n      payments: [\n        %WatcherInfoAPI.Model.TransactionCreatePayments{\n          amount: amount_in_wei,\n          currency: Encoding.to_hex(currency),\n          owner: Encoding.to_hex(output_address)\n        }\n      ],\n      fee: %WatcherInfoAPI.Model.TransactionCreateFee{currency: Encoding.to_hex(currency)}\n    }\n\n    {:ok, response} =\n      WatcherInfoAPI.Api.Transaction.create_transaction(LoadTest.Connection.WatcherInfo.client(), transaction)\n\n    result = Jason.decode!(response.body)[\"data\"]\n\n    process_transaction_result(result)\n  end\n\n  defp submit_typed(typed_data_signed) do\n    {:ok, response} = execute_submit_typed(typed_data_signed)\n    decoded_response = Jason.decode!(response.body)[\"data\"]\n\n    case decoded_response do\n      %{\"messages\" => %{\"code\" => \"submit:utxo_not_found\"}} ->\n        {:error, :data_not_found}\n\n      %{\"messages\" => %{\"code\" => \"operation:service_unavailable\"}} = error ->\n        {:error, error}\n\n      %{\"txhash\" => _} ->\n        {:ok, decoded_response}\n    end\n  end\n\n  defp execute_submit_typed(typed_data_signed) do\n    WatcherInfoAPI.Api.Transaction.submit_typed(LoadTest.Connection.WatcherInfo.client(), typed_data_signed)\n  end\n\n  defp process_transaction_result(result) do\n    case result do\n      %{\"code\" => \"create:client_error\"} ->\n        {:error, result}\n\n      %{\n        \"result\" => \"complete\",\n        \"transactions\" => [\n          %{\n            \"sign_hash\" => sign_hash,\n            \"typed_data\" => typed_data,\n            \"txbytes\" => txbytes\n          }\n        ]\n      } ->\n        {:ok, [sign_hash, typed_data, txbytes]}\n\n      error ->\n        {:error, error}\n    end\n  end\n\n  defp signature_digest(hash_digest, private_key) do\n    {:ok, {<<r::size(256), s::size(256)>>, recovery_id}} = ExSecp256k1.sign_compact(hash_digest, private_key)\n\n    # EIP-155\n    # See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md\n    base_recovery_id = 27\n    recovery_id = base_recovery_id + recovery_id\n\n    <<r::integer-size(256), s::integer-size(256), recovery_id::integer-size(8)>>\n  end\n\n  defp to_binary(hex) do\n    hex\n    |> String.replace_prefix(\"0x\", \"\")\n    |> String.upcase()\n    |> Base.decode16!()\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/lib/watcher_info/utxo.ex",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.w\n\ndefmodule LoadTest.WatcherInfo.Utxo do\n  @moduledoc \"\"\"\n  Functions for retrieving utxos through WatcherInfo API.\n  \"\"\"\n  require Logger\n\n  alias LoadTest.Ethereum.Account\n  alias LoadTest.Service.Sync\n  alias LoadTest.Utils.Encoding\n  alias LoadTest.WatcherInfo.Client\n\n  @poll_timeout 60_000\n\n  @spec get_utxos(Account.addr_t(), ExPlasma.Utxo.t() | nil | :empty) :: {:ok, [] | ExPlasma.Utxo.t()} | no_return\n  def get_utxos(sender, utxo \\\\ nil) do\n    Sync.repeat_until_success(\n      fn ->\n        fetch_utxos(sender, utxo)\n      end,\n      @poll_timeout,\n      \"Failed to fetch utxos\"\n    )\n  end\n\n  defp fetch_utxos(sender, utxo) do\n    address = Encoding.to_hex(sender.addr)\n\n    case Client.get_utxos(address) do\n      {:ok, result} ->\n        find_utxo(result, utxo)\n\n      other ->\n        other\n    end\n  end\n\n  defp find_utxo(decoded_response, nil) do\n    {:ok, decoded_response}\n  end\n\n  defp find_utxo(%{\"data\" => []}, :empty) do\n    {:ok, []}\n  end\n\n  defp find_utxo(decoded_response, :empty) do\n    {:error, decoded_response}\n  end\n\n  defp find_utxo(decoded_response, utxo) do\n    do_find_utxo(decoded_response, utxo)\n  end\n\n  defp do_find_utxo(response, utxo) do\n    found_utxo =\n      Enum.find(response[\"data\"], fn\n        %{\n          \"amount\" => amount,\n          \"blknum\" => blknum,\n          \"currency\" => currency,\n          \"oindex\" => oindex,\n          \"otype\" => otype,\n          \"owner\" => owner,\n          \"txindex\" => txindex\n        } ->\n          current_utxo = %ExPlasma.Utxo{\n            amount: amount,\n            blknum: blknum,\n            currency: Encoding.to_binary(currency),\n            oindex: oindex,\n            output_type: otype,\n            owner: Encoding.to_binary(owner),\n            txindex: txindex\n          }\n\n          current_utxo == utxo\n      end)\n\n    case found_utxo do\n      nil -> {:error, response}\n      _ -> {:ok, found_utxo}\n    end\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/mix.exs",
    "content": "defmodule LoadTest.MixProject do\n  use Mix.Project\n\n  def project do\n    [\n      app: :load_test,\n      version: \"0.1.0\",\n      build_path: \"../../_build\",\n      config_path: \"../../config/config.exs\",\n      deps_path: \"../../deps\",\n      lockfile: \"../../mix.lock\",\n      elixir: \"~> 1.8\",\n      elixirc_paths: elixirc_paths(Mix.env()),\n      start_permanent: Mix.env() == :prod,\n      deps: deps()\n    ]\n  end\n\n  # Run \"mix help compile.app\" to learn about applications.\n  def application do\n    [\n      extra_applications: [:logger, :ex_secp256k1],\n      mod: {LoadTest.Application, []}\n    ]\n  end\n\n  # Specifies which paths to compile per environment.\n  defp elixirc_paths(_), do: [\"lib\"]\n\n  # Run \"mix help deps\" to learn about dependencies.\n  defp deps do\n    [\n      {:ex_rlp, \"~> 0.5.3\"},\n      {:ex_keccak, \"~> 0.1.2\"},\n      {:ex_abi, \"~> 0.5.1\"},\n      {:briefly, \"~> 0.3\"},\n      {:chaperon, \"~> 0.3.1\"},\n      {:statix, \"~> 1.4\"},\n      {:histogrex, \"~> 0.0.5\"},\n      {:tesla, \"~> 1.3.0\"},\n      {:httpoison, \"~> 1.7\", override: true},\n      {:hackney,\n       git: \"https://github.com/SergeTupchiy/hackney\", ref: \"2bf38f92f647de00c4850202f37d4eaab93ed834\", override: true},\n      {:ex_plasma,\n       git: \"https://github.com/omgnetwork/ex_plasma\", ref: \"5e94c4fc82dbf26cb457b30911505ec45ec534ea\", override: true},\n      {:ex_secp256k1, \"~> 0.1.2\"},\n      {:telemetry, \"~> 0.4.1\"},\n      {:fake_server, \"~> 2.1\", only: :test},\n      {:watcher_info_api, in_umbrella: true},\n      {:watcher_security_critical_api, in_umbrella: true},\n      {:child_chain_api, in_umbrella: true}\n    ]\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/test/load_test/runner/childchain_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.Runner.ChildChainTest do\n  @moduledoc \"\"\"\n  child chain load test\n  \"\"\"\n  use ExUnit.Case\n\n  @tag timeout: 6_000_000\n  test \"childchain test\" do\n    Chaperon.run_load_test(LoadTest.Runner.ChildChainTransactions, print_results: true)\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/test/load_test/runner/smoke_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.Runner.SmokeTest do\n  @moduledoc \"\"\"\n  Runs a smoke test for the perf tests setup\n  \"\"\"\n  use ExUnit.Case\n\n  test \"smoke test\" do\n    Chaperon.run_load_test(LoadTest.Runner.Smoke, print_results: false)\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/test/load_test/runner/standard_exit_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.Runner.StandardExitTest do\n  @moduledoc \"\"\"\n  Runs a smoke test for utxos load test\n  \"\"\"\n  use ExUnit.Case\n\n  @tag timeout: 6_000_000\n  test \"should run standard exit load test\" do\n    Chaperon.run_load_test(LoadTest.Runner.StandardExits, print_results: true)\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/test/load_test/runner/utxos_load_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.Runner.UtxosLoadTest do\n  @moduledoc \"\"\"\n  Runs a smoke test for utxos load test\n  \"\"\"\n  use ExUnit.Case\n\n  @tag timeout: 6_000_000\n  test \"smoke test - should run utxos load test\" do\n    Chaperon.run_load_test(LoadTest.Runner.UtxosLoad, print_results: true)\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/test/load_test/runner/watcher_info_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.Runner.WatcherInfoTest do\n  @moduledoc \"\"\"\n  watcher info load test\n  \"\"\"\n  use ExUnit.Case\n\n  @tag timeout: 6_000_000\n  test \"watcher info test\" do\n    Chaperon.run_load_test(LoadTest.Runner.WatcherInfoAccountApi, print_results: true)\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/test/load_test/service/datadog/api_test.exs",
    "content": "# Copyright 2019-2020 OMG Network Pte Ltd\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\ndefmodule LoadTest.Service.Datadog.APITest do\n  use ExUnit.Case\n\n  require FakeServer\n\n  alias FakeServer.Response\n  alias LoadTest.Service.Datadog.API\n\n  @server_name :datadog\n  @event %{\n    \"alert_type\" => \"error\",\n    \"title\" => \"[Triggered] WatcherInfo.get_balances takes more than 40ms\",\n    \"url\" => \"/event/event?id=5718361627942929581\",\n    \"text\" => \"tag\",\n    \"date_happened\" => 1_605_191_364\n  }\n\n  setup do\n    {:ok, server} = FakeServer.start(@server_name)\n    {:ok, port} = FakeServer.port(@server_name)\n    fakeserver_address = \"http://localhost:\" <> to_string(port) <> \"/\"\n\n    datadog_params = Application.get_env(:load_test, :datadog)\n\n    new_datadog_params = Keyword.put(datadog_params, :api_url, fakeserver_address)\n\n    Application.put_env(:load_test, :datadog, new_datadog_params)\n\n    FakeServer.put_route(@server_name, \"/api/v1/events\", Response.new(200, Jason.encode!(%{\"events\" => [@event]})))\n\n    on_exit(fn ->\n      FakeServer.stop(server)\n      Application.put_env(:load_test, :datadog, datadog_params)\n    end)\n\n    :ok\n  end\n\n  describe \"assert_metrics/3\" do\n    test \"fetches events from datadog\" do\n      current_time = DateTime.utc_now()\n\n      assert {:error, [event]} = API.assert_metrics(\"tag\", current_time, current_time)\n\n      assert @event[\"title\"] == event[\"title\"]\n      assert \"https://app.datadoghq.com\" <> @event[\"url\"] == event[\"url\"]\n    end\n  end\nend\n"
  },
  {
    "path": "priv/perf/apps/load_test/test/test_helper.exs",
    "content": "{:ok, _} = Application.ensure_all_started(:fake_server)\n\nExUnit.start(exclude: [:skip])\n"
  },
  {
    "path": "priv/perf/config/.credo.exs",
    "content": "# This file contains the configuration for Credo and you are probably reading\n# this after creating it with `mix credo.gen.config`.\n#\n# If you find anything wrong or unclear in this file, please report an\n# issue on GitHub: https://github.com/rrrene/credo/issues\n#\n%{\n  #\n  # You can have as many configs as you like in the `configs:` field.\n  configs: [\n    %{\n      #\n      # Run any exec using `mix credo -C <name>`. If no exec name is given\n      # \"default\" is used.\n      #\n      name: \"default\",\n      #\n      # These are the files included in the analysis:\n      files: %{\n        #\n        # You can give explicit globs or simply directories.\n        # In the latter case `**/*.{ex,exs}` will be used.\n        #\n        included: [\"lib/\", \"src/\", \"test/\", \"web/\", \"apps/\", \"config/\", \"mix.exs\"],\n        excluded: [\n          ~r\"/_build/\",\n          ~r\"/deps/\",\n          ~r\"/node_modules/\",\n          ~r\"/results/\",\n          ~r\"/apps/child_chain_api\",\n          ~r\"/apps/watcher_security_critical_api\",\n          ~r\"/apps/watcher_info_api\"\n        ]\n      },\n      #\n      # If you create your own checks, you must specify the source files for\n      # them here, so they can be loaded by Credo before running the analysis.\n      #\n      requires: [\"config/credo/license_header.ex\"],\n      #\n      # If you want to enforce a style guide and need a more traditional linting\n      # experience, you can change `strict` to `true` below:\n      #\n      strict: true,\n      #\n      # If you want to use uncolored output by default, you can change `color`\n      # to `false` below:\n      #\n      color: true,\n      #\n      # You can customize the parameters of any check by adding a second element\n      # to the tuple.\n      #\n      # To disable a check put `false` as second element:\n      #\n      #     {Credo.Check.Design.DuplicatedCode, false}\n      #\n      checks: [\n        {Credo.Check.Refactor.MapInto, false},\n        # custom checks\n        {Credo.Check.Custom.LicenseHeader},\n        #\n        ## Consistency Checks\n        #\n        {Credo.Check.Consistency.ExceptionNames, []},\n        {Credo.Check.Consistency.LineEndings, []},\n        {Credo.Check.Consistency.ParameterPatternMatching, []},\n        {Credo.Check.Consistency.SpaceAroundOperators, []},\n        {Credo.Check.Consistency.SpaceInParentheses, []},\n        {Credo.Check.Consistency.TabsOrSpaces, []},\n\n        #\n        ## Design Checks\n        #\n        # You can customize the priority of any check\n        # Priority values are: `low, normal, high, higher`\n        #\n        {Credo.Check.Design.AliasUsage, [priority: :low, if_nested_deeper_than: 3, if_called_more_often_than: 1]},\n        # You can also customize the exit_status of each check.\n        # If you don't want TODO comments to cause `mix credo` to fail, just\n        # set this value to 0 (zero).\n        #\n        {Credo.Check.Design.TagTODO, [exit_status: 0]},\n        {Credo.Check.Design.TagFIXME, []},\n\n        #\n        ## Readability Checks\n        #\n        {Credo.Check.Readability.AliasOrder, []},\n        {Credo.Check.Readability.FunctionNames, []},\n        {Credo.Check.Readability.LargeNumbers, []},\n        {Credo.Check.Readability.MaxLineLength, [priority: :low, max_length: 120]},\n        {Credo.Check.Readability.ModuleAttributeNames, []},\n        {Credo.Check.Readability.ModuleDoc, []},\n        {Credo.Check.Readability.ModuleNames, []},\n        {Credo.Check.Readability.ParenthesesInCondition, []},\n        {Credo.Check.Readability.ParenthesesOnZeroArityDefs, false},\n        {Credo.Check.Readability.PredicateFunctionNames, []},\n        {Credo.Check.Readability.PreferImplicitTry, []},\n        {Credo.Check.Readability.RedundantBlankLines, []},\n        {Credo.Check.Readability.Semicolons, []},\n        {Credo.Check.Readability.SpaceAfterCommas, []},\n        {Credo.Check.Readability.StringSigils, []},\n        {Credo.Check.Readability.TrailingBlankLine, []},\n        {Credo.Check.Readability.TrailingWhiteSpace, []},\n        {Credo.Check.Readability.VariableNames, []},\n\n        #\n        ## Refactoring Opportunities\n        #\n        {Credo.Check.Refactor.CondStatements, []},\n        {Credo.Check.Refactor.CyclomaticComplexity, []},\n        {Credo.Check.Refactor.FunctionArity, []},\n        {Credo.Check.Refactor.LongQuoteBlocks, []},\n        {Credo.Check.Refactor.MatchInCondition, []},\n        {Credo.Check.Refactor.NegatedConditionsInUnless, []},\n        {Credo.Check.Refactor.NegatedConditionsWithElse, []},\n        {Credo.Check.Refactor.Nesting, []},\n        {Credo.Check.Refactor.PipeChainStart, false},\n        {Credo.Check.Refactor.UnlessWithElse, []},\n\n        #\n        ## Warnings\n        #\n        {Credo.Check.Warning.BoolOperationOnSameValues, []},\n        {Credo.Check.Warning.ExpensiveEmptyEnumCheck, []},\n        {Credo.Check.Warning.IExPry, []},\n        {Credo.Check.Warning.IoInspect, []},\n        {Credo.Check.Warning.LazyLogging, false},\n        {Credo.Check.Warning.OperationOnSameValues, []},\n        {Credo.Check.Warning.OperationWithConstantResult, []},\n        {Credo.Check.Warning.RaiseInsideRescue, []},\n        {Credo.Check.Warning.UnusedEnumOperation, []},\n        {Credo.Check.Warning.UnusedFileOperation, []},\n        {Credo.Check.Warning.UnusedKeywordOperation, []},\n        {Credo.Check.Warning.UnusedListOperation, []},\n        {Credo.Check.Warning.UnusedPathOperation, []},\n        {Credo.Check.Warning.UnusedRegexOperation, []},\n        {Credo.Check.Warning.UnusedStringOperation, []},\n        {Credo.Check.Warning.UnusedTupleOperation, []},\n\n        #\n        # Controversial and experimental checks (opt-in, just remove `, false`)\n        #\n        {Credo.Check.Readability.SinglePipe, []},\n        {Credo.Check.Consistency.MultiAliasImportRequireUse, false},\n        {Credo.Check.Design.DuplicatedCode, false},\n        {Credo.Check.Readability.Specs, false},\n        {Credo.Check.Refactor.ABCSize, false},\n        {Credo.Check.Refactor.AppendSingleItem, false},\n        {Credo.Check.Refactor.DoubleBooleanNegation, false},\n        {Credo.Check.Refactor.VariableRebinding, false},\n        {Credo.Check.Warning.MapGetUnsafePass, false},\n        {Credo.Check.Warning.UnsafeToAtom, false}\n\n        #\n        # Custom checks can be created using `mix credo.gen.check`.\n        #\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": "priv/perf/config/config.exs",
    "content": "use Mix.Config\n\n# Better adapter for tesla.\n# default httpc would fail when doing post request without param.\n# https://github.com/googleapis/elixir-google-api/issues/26#issuecomment-360209019\nconfig :tesla, adapter: Tesla.Adapter.Hackney\n\nethereum_client_timeout_ms = 20_000\n\nconfig :ethereumex,\n  http_options: [recv_timeout: ethereum_client_timeout_ms],\n  url: System.get_env(\"ETHEREUM_RPC_URL\") || \"http://localhost:8545\"\n\nconfig :load_test,\n  pool_size: 5000,\n  max_connection: 5000,\n  retry_sleep: \"RETRY_SLEEP\" |> System.get_env(\"1000\") |> String.to_integer(),\n  child_chain_url: System.get_env(\"CHILD_CHAIN_URL\") || \"http://localhost:9656\",\n  watcher_security_url: System.get_env(\"WATCHER_SECURITY_URL\") || \"http://localhost:7434\",\n  watcher_info_url: System.get_env(\"WATCHER_INFO_URL\") || \"http://localhost:7534\",\n  faucet_private_key:\n    System.get_env(\"LOAD_TEST_FAUCET_PRIVATE_KEY\") ||\n      \"0xd885a307e35738f773d8c9c63c7a3f3977819274638d04aaf934a1e1158513ce\",\n  eth_vault_address: System.get_env(\"CONTRACT_ADDRESS_ETH_VAULT\"),\n  contract_address_payment_exit_game: System.get_env(\"CONTRACT_ADDRESS_PAYMENT_EXIT_GAME\"),\n  child_block_interval: \"CHILD_BLOCK_INTERVAL\" |> System.get_env(\"1000\") |> String.to_integer(),\n  contract_address_plasma_framework: System.get_env(\"CONTRACT_ADDRESS_PLASMA_FRAMEWORK\"),\n  erc20_vault_address: System.get_env(\"CONTRACT_ADDRESS_ERC20_VAULT\"),\n  test_currency: \"0x0000000000000000000000000000000000000000\",\n  faucet_deposit_amount: trunc(:math.pow(10, 14)),\n  fee_amount: \"FEE_AMOUNT\" |> System.get_env(\"75\") |> String.to_integer(),\n  deposit_finality_margin: \"DEPOSIT_FINALITY_MARGIN\" |> System.get_env(\"10\") |> String.to_integer(),\n  gas_price: \"GAS_PRICE\" |> System.get_env(\"2000000000\") |> String.to_integer(),\n  record_metrics: true\n\nconfig :ex_plasma,\n  eip_712_domain: [\n    name: \"OMG Network\",\n    salt: \"0xfad5c7f626d80f9256ef01929f3beb96e058b8b4b0e3fe52d84f054c0e2a7a83\",\n    verifying_contract: System.get_env(\"CONTRACT_ADDRESS_PLASMA_FRAMEWORK\"),\n    version: \"1\"\n  ]\n\nconfig :logger, :console,\n  format: \"$date $time [$level] $metadata⋅$message⋅\\n\",\n  discard_threshold: 2000,\n  metadata: [:module, :function, :request_id, :trace_id, :span_id]\n\nconfig :statix,\n  host: \"localhost\",\n  port: 8125,\n  tags: [System.get_env(\"STATIX_TAG\")]\n\nconfig :load_test, :datadog,\n  api_key: System.get_env(\"DD_API_KEY\"),\n  app_key: System.get_env(\"DD_APP_KEY\"),\n  api_url: \"https://app.datadoghq.com/\"\n\nimport_config \"#{Mix.env()}.exs\"\n"
  },
  {
    "path": "priv/perf/config/dev.exs",
    "content": "use Mix.Config\n"
  },
  {
    "path": "priv/perf/config/stress.exs",
    "content": "use Mix.Config\n\n# Target tps.\n# Note that this is only a rough estimate and depends on the response time from the childchain.\n# If the childchain is under high load, tps will drop.\ntps = 100\n\n# Must be >= than tps, _should_ be at least 2x tps\nconcurrency = 200\n\n# Minutes that the test should run.\n# Again, a rough estimate - if the childchain is under high load the test will take longer to finish\ntest_duration = 1\n\ntx_delay = trunc(concurrency / tps) * 1000\ntx_per_session = trunc(test_duration * 60 / trunc(tx_delay / 1000))\n\nconfig :load_test,\n  utxo_load_test_config: %{\n    concurrent_sessions: 1,\n    utxos_to_create_per_session: 30,\n    transactions_per_session: 4\n  },\n  childchain_transactions_test_config: %{\n    concurrent_sessions: concurrency,\n    transactions_per_session: tx_per_session,\n    transaction_delay: tx_delay\n  },\n  watcher_info_test_config: %{\n    concurrent_sessions: 100,\n    iterations: 10,\n    merge_scenario_sessions: true\n  },\n  standard_exit_test_config: %{\n    concurrent_sessions: 1,\n    exits_per_session: 10\n  }\n"
  },
  {
    "path": "priv/perf/config/test.exs",
    "content": "use Mix.Config\n\nconfig :ethereumex,\n  url: \"http://localhost:8545\"\n\nconfig :load_test,\n  child_chain_url: \"http://localhost:9656\",\n  watcher_security_url: \"http://localhost:7434\",\n  watcher_info_url: \"http://localhost:7534\",\n  faucet_deposit_amount: trunc(:math.pow(10, 18) * 10),\n  # fee testing setup: https://github.com/omgnetwork/fee-rules-public/blob/master/fee_rules.json\n  fee_amount: 75,\n  utxo_load_test_config: %{\n    concurrent_sessions: 10,\n    utxos_to_create_per_session: 5,\n    transactions_per_session: 5\n  },\n  childchain_transactions_test_config: %{\n    concurrent_sessions: 10,\n    transactions_per_session: 10\n  },\n  watcher_info_test_config: %{\n    concurrent_sessions: 2,\n    iterations: 2,\n    merge_scenario_sessions: true\n  },\n  standard_exit_test_config: %{\n    concurrent_sessions: 1,\n    exits_per_session: 4\n  },\n  record_metrics: false\n"
  },
  {
    "path": "priv/perf/mix.exs",
    "content": "defmodule Perf.MixProject do\n  use Mix.Project\n\n  def project do\n    [\n      app: :perf,\n      apps_path: \"apps\",\n      start_permanent: Mix.env() == :prod,\n      deps: deps()\n    ]\n  end\n\n  defp deps do\n    [\n      {:credo, \"~> 1.5\", only: [:dev, :test], runtime: false}\n    ]\n  end\nend\n"
  },
  {
    "path": "priv/perf/scripts/generate_api_client.sh",
    "content": "#!/bin/bash\n\n# Script that generates the elixir client code to communicate with child chain and watcher services.\n# Auto generated elixir client would be put directly under apps/ directory.\n#\n# The client come with a default base url from the swagger spec file.\n# To connect to different environment, override the middleware in runtime: https://github.com/teamon/tesla#runtime-middleware\n\nset -e\n\necho \"Generate api client script starts...\"\n\necho \"------------------------------------------------------\"\necho \"Cleaning up .api_specs/ and generated client codes...\"\necho \"------------------------------------------------------\"\nrm -rf apps/child_chain_api\nrm -rf apps/watcher_security_critical_api\nrm -rf apps/watcher_info_api\n\n\necho \"------------------------------------------------------\"\necho \"Generating childchain clients...\"\necho \"------------------------------------------------------\"\ndocker run --rm \\\n    -v ${PWD}/apps:/apps \\\n    --user $(id -u):$(id -g) \\\n    openapitools/openapi-generator-cli generate \\\n    -i https://raw.githubusercontent.com/omgnetwork/omg-childchain-v1/master/apps/omg_child_chain_rpc/priv/swagger/operator_api_specs.yaml \\\n    -g elixir \\\n    -o /apps/child_chain_api/\n\necho \"------------------------------------------------------\"\necho \"Generating watcher security clients...\"\necho \"------------------------------------------------------\"\ndocker run --rm \\\n    -v ${PWD}/apps:/apps \\\n    -v ${PWD}/../../apps/omg_watcher_rpc/priv/swagger/:/swagger \\\n    --user $(id -u):$(id -g) \\\n    openapitools/openapi-generator-cli generate \\\n    -i /swagger/security_critical_api_specs.yaml \\\n    -g elixir \\\n    -o /apps/watcher_security_critical_api/\n\necho \"------------------------------------------------------\"\necho \"Generating watcher info clients...\"\necho \"------------------------------------------------------\"\ndocker run --rm \\\n    -v ${PWD}/apps:/apps \\\n    -v ${PWD}/../../apps/omg_watcher_rpc/priv/swagger/:/swagger \\\n    --user $(id -u):$(id -g) \\\n    openapitools/openapi-generator-cli generate \\\n    -i /swagger/info_api_specs.yaml \\\n    -g elixir \\\n    -o /apps/watcher_info_api/\n\n"
  },
  {
    "path": "rel/env.sh.eex",
    "content": "#!/bin/sh\n\n# Sets and enables heart (recommended only in daemon mode)\n# case $RELEASE_COMMAND in\n#   daemon*)\n#     HEART_COMMAND=\"$RELEASE_ROOT/bin/$RELEASE_NAME $RELEASE_COMMAND\"\n#     export HEART_COMMAND\n#     export ELIXIR_ERL_OPTIONS=\"-heart\"\n#     ;;\n#   *)\n#     ;;\n# esac\n\n# Set the release to work across nodes. If using the long name format like\n# the one below (my_app@127.0.0.1), you need to also uncomment the\n# RELEASE_DISTRIBUTION variable below. Must be \"sname\", \"name\" or \"none\".\nexport RELEASE_DISTRIBUTION=name\nexport RELEASE_NODE=<%= @release.name %>@$NODE_HOST\n"
  },
  {
    "path": "rootfs/watcher_entrypoint",
    "content": "#!/bin/execlineb -S0\n\nwith-contenv\ncd /app\ns6-setuidgid watcher\n\n## NODE_HOST\n##\n## Used in clustering; combine with an app name, this is used for identifying\n## and connecting each nodes. By default use the container hostname.\n##\nbacktick -n default_host { s6-hostname }\nimportas -iu default_host default_host\nimportas -D $default_host NODE_HOST NODE_HOST\ns6-env NODE_HOST=$NODE_HOST\n\n## ERLANG_COOKIE\n##\n## Used in clustering; all nodes in the cluster is required to have the same\n## Erlang Cookie set. Randomly generated default cookie is only useful for\n## the single instance scenario.\n##\nbacktick -n default_cookie {\n    pipeline {\n        s6-head -c 6 /dev/urandom\n    } foreground { base64 }\n}\n\nimportas -iu default_cookie default_cookie\nimportas -D $default_cookie ERLANG_COOKIE ERLANG_COOKIE\ns6-env ERLANG_COOKIE=$ERLANG_COOKIE\n\n## NODE_DNS\n##\n## Used in clustering; by default wallet is configured to discover nodes via\n## DNS. Registering nodes to the cluster is a responsibility of cluster tools\n## e.g. Kubernetes.\n##\nimportas -D localhost NODE_DNS NODE_DNS\ns6-env NODE_DNS=$NODE_DNS\n\n## HOME\n##\n## Home is required to be set since some tools use it to create e.g. cache.\n## By default Watcher will run with a limited privileges so HOME need to be\n## somewhere writable.\n##\ns6-env HOME=/app\n\n## REPLACE_OS_VARS\n##\n## Used by Distillery to replace environment variables in settings to the\n## value of environment variable.\n##\ns6-env REPLACE_OS_VARS=yes\n\n## Known commands\n##\n\nifelse { test $1 == \"full_local\" } {\n  /bin/bash -c \"/app/bin/watcher eval \\\"OMG.DB.ReleaseTasks.InitKeyValueDB.run()\\\" && /app/bin/watcher start\"\n}\n\nifelse { test $1 == \"start_iex\" } {\n  /app/bin/watcher $@\n}\n\nifelse { test $1 == \"start\" } {\n  /app/bin/watcher $@\n}\n\n## Fallback; if we're not running the known command, just run it as is.\n## This is to allow e.g. administrator attaching a shell into the container.\n##\nexec $@\n"
  },
  {
    "path": "rootfs/watcher_info_entrypoint",
    "content": "#!/bin/execlineb -S0\n\nwith-contenv\ncd /app\ns6-setuidgid watcher\n\n## NODE_HOST\n##\n## Used in clustering; combine with an app name, this is used for identifying\n## and connecting each nodes. By default use the container hostname.\n##\nbacktick -n default_host { s6-hostname }\nimportas -iu default_host default_host\nimportas -D $default_host NODE_HOST NODE_HOST\ns6-env NODE_HOST=$NODE_HOST\n\n## ERLANG_COOKIE\n##\n## Used in clustering; all nodes in the cluster is required to have the same\n## Erlang Cookie set. Randomly generated default cookie is only useful for\n## the single instance scenario.\n##\nbacktick -n default_cookie {\n    pipeline {\n        s6-head -c 6 /dev/urandom\n    } foreground { base64 }\n}\n\nimportas -iu default_cookie default_cookie\nimportas -D $default_cookie ERLANG_COOKIE ERLANG_COOKIE\ns6-env ERLANG_COOKIE=$ERLANG_COOKIE\n\n## NODE_DNS\n##\n## Used in clustering; by default wallet is configured to discover nodes via\n## DNS. Registering nodes to the cluster is a responsibility of cluster tools\n## e.g. Kubernetes.\n##\nimportas -D localhost NODE_DNS NODE_DNS\ns6-env NODE_DNS=$NODE_DNS\n\n## HOME\n##\n## Home is required to be set since some tools use it to create e.g. cache.\n## By default Watcher will run with a limited privileges so HOME need to be\n## somewhere writable.\n##\ns6-env HOME=/app\n\n## REPLACE_OS_VARS\n##\n## Used by Distillery to replace environment variables in settings to the\n## value of environment variable.\n##\ns6-env REPLACE_OS_VARS=yes\n\n## Known commands\n##\n\nifelse { test $1 == \"full_local\" } {\n  /bin/bash -c \"/app/bin/watcher_info eval \\\"OMG.WatcherInfo.ReleaseTasks.InitPostgresqlDB.migrate()\\\" && \\\n  /app/bin/watcher_info eval \\\"OMG.DB.ReleaseTasks.InitKeyValueDB.run()\\\" && \\\n  /app/bin/watcher_info start\"\n}\n\nifelse { test $1 == \"start_iex\" } {\n  /app/bin/watcher_info $@\n}\n\nifelse { test $1 == \"start\" } {\n  /app/bin/watcher_info $@\n}\n\n## Fallback; if we're not running the known command, just run it as is.\n## This is to allow e.g. administrator attaching a shell into the container.\n##\nexec $@\n"
  },
  {
    "path": "snapshot_reorg.env",
    "content": "SNAPSHOT=https://storage.googleapis.com/circleci-docker-artifacts/data-elixir-omg-tester-plasma-deployer-dev-10cafba-MIN_EXIT_PERIOD-120-PLASMA_CONTRACTS_SHA-a69c763f239b81c5eb46b0bbdef3459f764360dc-reorg.tar.gz\nCONTRACT_SHA=a69c763f239b81c5eb46b0bbdef3459f764360dc"
  },
  {
    "path": "snapshots.env",
    "content": "SNAPSHOT_MIX_EXIT_PERIOD_SECONDS_20=https://storage.googleapis.com/circleci-docker-artifacts/data-elixir-omg-tester-plasma-deployer-stable-20201207-MIN_EXIT_PERIOD-20-PLASMA_CONTRACTS_SHA-b3a5c8d5232edfab8617f6939733b08b67863c8a.tar.gz\nSNAPSHOT_MIX_EXIT_PERIOD_SECONDS_120=https://storage.googleapis.com/circleci-docker-artifacts/data-elixir-omg-tester-plasma-deployer-stable-20201207-MIN_EXIT_PERIOD-120-PLASMA_CONTRACTS_SHA-b3a5c8d5232edfab8617f6939733b08b67863c8a.tar.gz\nSNAPSHOT_MIX_EXIT_PERIOD_SECONDS_240=https://storage.googleapis.com/circleci-docker-artifacts/data-elixir-omg-tester-plasma-deployer-stable-20201207-MIN_EXIT_PERIOD-240-PLASMA_CONTRACTS_SHA-b3a5c8d5232edfab8617f6939733b08b67863c8a.tar.gz\nCONTRACT_SHA=b3a5c8d5232edfab8617f6939733b08b67863c8a\n"
  }
]