├── .buildkite ├── hooks │ └── pre-command └── pipeline.yml ├── .formatter.exs ├── .gitignore ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── cmd ├── config ├── appsignal.exs ├── config.exs ├── dev.exs ├── pescadero.exs ├── prod.exs ├── sample.secret.exs └── test.exs ├── dev-cmd ├── docker-compose.yml ├── lib ├── blockchain_api.ex ├── blockchain_api │ ├── application.ex │ ├── batcher │ │ ├── pocs.ex │ │ └── txns.ex │ ├── cache │ │ └── cache_service.ex │ ├── cli.ex │ ├── committer.ex │ ├── error │ │ └── commit_error.ex │ ├── geocoder.ex │ ├── job │ │ ├── submit_coinbase.ex │ │ ├── submit_gateway.ex │ │ ├── submit_location.ex │ │ ├── submit_oui.ex │ │ ├── submit_payment.ex │ │ └── submit_sec_exchange.ex │ ├── notifier.ex │ ├── notifiers │ │ ├── hotspot_notifier.ex │ │ ├── notifier_client.ex │ │ ├── payments_notifier.ex │ │ └── rewards_notifier.ex │ ├── periodic_cleaner.ex │ ├── periodic_updater.ex │ ├── query │ │ ├── account.ex │ │ ├── account_balance.ex │ │ ├── account_transaction.ex │ │ ├── block.ex │ │ ├── coinbase_transaction.ex │ │ ├── consensus_member.ex │ │ ├── data_credit_transaction.ex │ │ ├── election_transaction.ex │ │ ├── gateway_transaction.ex │ │ ├── hotspot.ex │ │ ├── hotspot_activity.ex │ │ ├── hotspot_reward.ex │ │ ├── hotspot_status.ex │ │ ├── location_transaction.ex │ │ ├── oui_transaction.ex │ │ ├── payment_transaction.ex │ │ ├── payment_v2_txn.ex │ │ ├── pending_coinbase.ex │ │ ├── pending_gateway.ex │ │ ├── pending_location.ex │ │ ├── pending_oui.ex │ │ ├── pending_payment.ex │ │ ├── pending_sec_exchange.ex │ │ ├── poc_path_element.ex │ │ ├── poc_receipt.ex │ │ ├── poc_receipts_transaction.ex │ │ ├── poc_request_transaction.ex │ │ ├── poc_witness.ex │ │ ├── reward_txn.ex │ │ ├── rewards_transaction.ex │ │ ├── security_exchange_transaction.ex │ │ ├── security_transaction.ex │ │ ├── stats.ex │ │ └── transaction.ex │ ├── repos.ex │ ├── schema │ │ ├── account.ex │ │ ├── account_balance.ex │ │ ├── account_transaction.ex │ │ ├── block.ex │ │ ├── coinbase_transaction.ex │ │ ├── consensus_member.ex │ │ ├── data_credit_transaction.ex │ │ ├── election_transaction.ex │ │ ├── gateway_transaction.ex │ │ ├── hotspot.ex │ │ ├── hotspot_activity.ex │ │ ├── location_transaction.ex │ │ ├── oui_transaction.ex │ │ ├── payment_transaction.ex │ │ ├── payment_v2_txn.ex │ │ ├── pending_coinbase.ex │ │ ├── pending_gateway.ex │ │ ├── pending_location.ex │ │ ├── pending_oui.ex │ │ ├── pending_payment.ex │ │ ├── pending_sec_exchange.ex │ │ ├── poc_path_element.ex │ │ ├── poc_receipt.ex │ │ ├── poc_receipts_transaction.ex │ │ ├── poc_request_transaction.ex │ │ ├── poc_witness.ex │ │ ├── reward_txn.ex │ │ ├── rewards_transaction.ex │ │ ├── security_exchange_transaction.ex │ │ ├── security_transaction.ex │ │ └── transaction.ex │ ├── util.ex │ └── watcher.ex ├── blockchain_api_web.ex └── blockchain_api_web │ ├── channels │ ├── account_channel.ex │ ├── account_socket.ex │ └── block_channel.ex │ ├── controllers │ ├── account_controller.ex │ ├── account_gateway_controller.ex │ ├── account_reward_controller.ex │ ├── account_transaction_controller.ex │ ├── activity_controller.ex │ ├── block_controller.ex │ ├── challenge_controller.ex │ ├── coinbase_controller.ex │ ├── election_transaction_controller.ex │ ├── fallback_controller.ex │ ├── four_oh_four_controller.ex │ ├── gateway_controller.ex │ ├── health_check_controller.ex │ ├── hotspot_challenge_controller.ex │ ├── hotspot_controller.ex │ ├── location_controller.ex │ ├── payment_controller.ex │ ├── pending_gateway_controller.ex │ ├── pending_location_controller.ex │ ├── pending_payment_controller.ex │ ├── reward_controller.ex │ ├── rewards_controller.ex │ ├── stats_controller.ex │ ├── transaction_controller.ex │ └── witness_controller.ex │ ├── endpoint.ex │ ├── gettext.ex │ ├── router.ex │ └── views │ ├── account_gateway_view.ex │ ├── account_reward_view.ex │ ├── account_transaction_view.ex │ ├── account_view.ex │ ├── activity_view.ex │ ├── block_view.ex │ ├── challenge_view.ex │ ├── changeset_view.ex │ ├── coinbase_view.ex │ ├── data_credit_view.ex │ ├── election_transaction_view.ex │ ├── election_view.ex │ ├── error_helpers.ex │ ├── error_view.ex │ ├── gateway_view.ex │ ├── hotspot_challenge_view.ex │ ├── hotspot_reward_view.ex │ ├── hotspot_view.ex │ ├── location_view.ex │ ├── oui_view.ex │ ├── payment_v2_view.ex │ ├── payment_view.ex │ ├── poc_receipts_view.ex │ ├── poc_request_view.ex │ ├── poc_witnesses_view.ex │ ├── reward_view.ex │ ├── rewards_view.ex │ ├── sec_exchange_view.ex │ ├── security_view.ex │ ├── stats_view.ex │ ├── transaction_view.ex │ └── witness_view.ex ├── mix.exs ├── mix.lock ├── nginx ├── Dockerfile └── lb.conf ├── postgres └── Dockerfile ├── priv ├── dev │ └── genesis ├── gettext │ ├── en │ │ └── LC_MESSAGES │ │ │ └── errors.po │ └── errors.pot ├── pescadero │ └── genesis ├── prod │ └── genesis ├── repo │ ├── migrations │ │ ├── .formatter.exs │ │ ├── 20190118235355_add_blocks_table.exs │ │ ├── 20190118235356_add_transactions_table.exs │ │ ├── 20190118235357_add_payment_transactions_table.exs │ │ ├── 20190118235358_add_coinbase_transactions_table.exs │ │ ├── 20190118235359_add_gateway_transactions_table.exs │ │ ├── 20190118235400_add_location_transactions_table.exs │ │ ├── 20190122051731_add_account_table.exs │ │ ├── 20190122053407_add_account_transaction_table.exs │ │ ├── 20190222185006_add_account_balance_table.exs │ │ ├── 20190312213336_add_hotspot_table.exs │ │ ├── 20190318002450_add_poc_request_transaction_table.exs │ │ ├── 20190320211614_add_fuzzystrmatch_extension.exs │ │ ├── 20190403182210_add_poc_receipts_transaction_table.exs │ │ ├── 20190404032039_add_poc_path_elements_table.exs │ │ ├── 20190404032155_add_poc_receipt_table.exs │ │ ├── 20190404032208_add_poc_witness_table.exs │ │ ├── 20190417230217_add_pending_coinbase_table.exs │ │ ├── 20190417230218_add_pending_gateway_table.exs │ │ ├── 20190417230219_add_pending_location_table.exs │ │ ├── 20190417230220_add_pending_payment_table.exs │ │ ├── 20190421034025_add_security_transaction_table.exs │ │ ├── 20190421045052_add_election_transaction_table.exs │ │ ├── 20190421045449_add_consensus_member_table.exs │ │ ├── 20190508201122_add_hotspot_activity_table.exs │ │ ├── 20190701030444_add_rewards_transactions_table.exs │ │ ├── 20190701032610_add_reward_txns_table.exs │ │ ├── 20190725165802_add_data_credit_transaction_table.exs │ │ ├── 20190804014105_remove_pending_location_constraint.exs │ │ ├── 20190808002434_change_reward_amount_type.exs │ │ ├── 20190816231641_add_distance_to_witnesses.exs │ │ ├── 20190902083021_change_activity_table.exs │ │ ├── 20190902084108_change_poc_request_table.exs │ │ ├── 20190902084509_change_poc_receipts_transactions_table.exs │ │ ├── 20190902084944_change_transactions_table.exs │ │ ├── 20190902085239_change_poc_witnesses_table.exs │ │ ├── 20190902085252_change_poc_receipts_table.exs │ │ ├── 20190902085808_change_pending_gateways_table.exs │ │ ├── 20190902085920_change_pending_locations_table.exs │ │ ├── 20190902090020_change_pending_payments_table.exs │ │ ├── 20190902163742_change_account_transactions_table.exs │ │ ├── 20190902171545_change_account_balances_table.exs │ │ ├── 20190902174748_change_blocks_table.exs │ │ ├── 20190902211028_int8_account_balances_table.exs │ │ ├── 20190902211139_int8_poc_receipts_table.exs │ │ ├── 20190902211215_int8_poc_witnesses_table.exs │ │ ├── 20190902211258_int8_elections_transaction_table.exs │ │ ├── 20190902211338_int8_hotspot_activity_table.exs │ │ ├── 20190902211523_int8_rewards_transactions_table.exs │ │ ├── 20190902211632_int8_blocks_table.exs │ │ ├── 20190902211758_int8_reward_txns_table.exs │ │ ├── 20190906205324_change_index_pending_payments_table.exs │ │ ├── 20190911174348_add_index_poc_receipts_table.exs │ │ ├── 20190911174447_add_index_poc_witnesses_table.exs │ │ ├── 20190912181027_add_security_tokens_data_credits_to_accounts.exs │ │ ├── 20191008194531_add_security_exchange_transactions_table.exs │ │ ├── 20191008201717_add_activitiy_indices.exs │ │ ├── 20191020054507_add_hotspot_name.exs │ │ ├── 20191022224043_add_pending_oui_table.exs │ │ ├── 20191022231530_add_oui_transactions_table.exs │ │ ├── 20191029172131_add_pending_sec_exchange_table.exs │ │ ├── 20191203041036_allow_deletion.exs │ │ ├── 20191217183729_witness_quality_filter.exs │ │ ├── 20200310231635_add_payment_v2_txn_table.exs │ │ ├── 20200513214727_drop_pending_payment_constraint.exs │ │ └── 20200605195804_add_index_gateway_owner.exs │ └── seeds.exs ├── tasks │ ├── backfill_distance.ex │ ├── backfill_hotspot_name.ex │ └── release_tasks.ex └── test │ └── genesis ├── rebar3 ├── rel ├── commands │ ├── genesis │ ├── ledger │ ├── peer │ ├── snapshot │ └── status ├── config.exs ├── config │ ├── dev.exs │ ├── pescadero.exs │ └── prod.exs ├── hooks │ └── pre_start.sh ├── plugins │ └── .gitignore └── vm.args └── test ├── Makefile ├── blockchain_api ├── controllers │ ├── block_controller_test.exs │ ├── challenge_controller_test.exs │ ├── election_transaction_controller_test.exs │ ├── stats_controller_test.exs │ └── transaction_controller_test.exs ├── data │ ├── block_test.exs │ └── oui_txn_test.exs ├── geocoder_test.exs ├── notifiers │ ├── payments_notifier_test.exs │ └── rewards_notifier_test.exs └── query │ ├── block_test.exs │ ├── poc_receipts_transaction.exs │ ├── query_test.exs │ ├── reward_txn_query_test.exs │ └── stats_test.exs ├── support ├── channel_case.ex ├── conn_case.ex ├── data_case.ex ├── factory.ex ├── fake_notifier_client.ex └── test_helpers.ex └── test_helper.exs /.buildkite/hooks/pre-command: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | echo '--- :house_with_garden: Setting up the environment' 5 | 6 | . "$HOME/.asdf/asdf.sh" 7 | asdf local erlang 21.3 8 | asdf local python 3.7.3 9 | asdf local ruby 2.6.2 10 | asdf local elixir 1.8.2-otp-21 11 | -------------------------------------------------------------------------------- /.buildkite/pipeline.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | - commands: 3 | - "make ci" 4 | name: ":hammer: build" 5 | agents: 6 | queue: "erlang" 7 | -------------------------------------------------------------------------------- /.formatter.exs: -------------------------------------------------------------------------------- 1 | [ 2 | import_deps: [:ecto, :phoenix], 3 | inputs: ["*.{ex,exs}", "priv/*/seeds.exs", "{config,lib,test}/**/*.{ex,exs}"], 4 | subdirectories: ["priv/*/migrations"] 5 | ] 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where 3rd-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Ignore package tarball (built via "mix hex.build"). 23 | blockchain_node-*.tar 24 | 25 | # Since we are building assets from assets/, 26 | # we ignore priv/static. You may want to comment 27 | # this depending on your deployment strategy. 28 | /priv/static/ 29 | 30 | # Files matching config/*.secret.exs pattern contain sensitive 31 | # data and you should not commit them into version control. 32 | # 33 | # Alternatively, you may comment the line below and commit the 34 | # secrets files as long as you replace their contents by environment 35 | # variables. 36 | /config/prod.secret.exs 37 | /config/dev.secret.exs 38 | /config/docker*.env 39 | 40 | /data* 41 | /log 42 | .elixir_ls 43 | .ssh 44 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ### How to Contribute to this repository 2 | 3 | We value contributions from the community and will do everything we 4 | can go get them reviewed in a timely fashion. If you have code to send 5 | our way or a bug to report: 6 | 7 | * **Contributing Code**: If you have new code or a bug fix, fork this 8 | repo, create a logically-named branch, and [submit a PR against this 9 | repo](https://github.com/helium/blockchain-api). Include a 10 | write up of the PR with details on what it does. 11 | 12 | * **Reporting Bugs**: Open an issue [against this 13 | repo](https://github.com/helium/blockchain-api/issues) with as 14 | much detail as you can. At the very least you'll include steps to 15 | reproduce the problem. 16 | 17 | This project is intended to be a safe, welcoming space for 18 | collaboration, and contributors are expected to adhere to the 19 | [Contributor Covenant Code of 20 | Conduct](http://contributor-covenant.org/). 21 | 22 | Above all, thank you for taking the time to be a part of the Helium 23 | community. -------------------------------------------------------------------------------- /cmd: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | PROGNAME=$0 3 | 4 | usage() { 5 | cat << EOF >&2 6 | Usage: $PROGNAME [-e ] 7 | 8 | -e : ... 9 | EOF 10 | exit 1 11 | } 12 | 13 | env=prod 14 | while getopts e: o; do 15 | case $o in 16 | (e) env=$OPTARG;; 17 | (*) usage 18 | esac 19 | done 20 | shift "$((OPTIND - 1))" 21 | 22 | _build/prod/rel/blockchain_api/bin/blockchain_api $@ 23 | -------------------------------------------------------------------------------- /config/appsignal.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | config :appsignal, :config, 4 | name: "blockchain-api", 5 | push_api_key: System.get_env("APP_SIGNAL_API_KEY"), 6 | env: Mix.env 7 | -------------------------------------------------------------------------------- /config/dev.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | # For production, don't forget to configure the url host 4 | # to something meaningful, Phoenix uses this information 5 | # when generating URLs. 6 | # 7 | # Note we also include the path to a cache manifest 8 | # containing the digested version of static files. This 9 | # manifest is generated by the `mix phx.digest` task, 10 | # which you should run after static files are built and 11 | # before starting your production server. 12 | port = String.to_integer(System.get_env("PORT") || "4000") 13 | ro_mode = String.to_integer(System.get_env("RO_MODE") || "1") 14 | 15 | config :blockchain_api, BlockchainAPIWeb.Endpoint, 16 | http: [port: port], 17 | url: [host: System.get_env("HOSTNAME") || "localhost", port: port], 18 | server: true, 19 | root: ".", 20 | version: Application.spec(:blockchain_api, :vsn), 21 | check_origin: false, 22 | # force_ssl: [hsts: true, rewrite_on: [:x_forwarded_proto]], 23 | secret_key_base: System.get_env("SECRET_KEY_BASE") 24 | 25 | # cache_static_manifest: "priv/static/cache_manifest.json" 26 | 27 | config :blockchain_api, 28 | env: Mix.env(), 29 | google_maps_secret: System.get_env("GOOGLE_MAPS_API_KEY"), 30 | fastly_api_key: System.get_env("FASTLY_API_KEY"), 31 | fastly_service_id: System.get_env("FASTLY_SERVICE_ID"), 32 | notifier_client: BlockchainAPI.FakeNotifierClient, 33 | ro_mode: ro_mode, 34 | repos: [master: BlockchainAPI.Repo, replica: BlockchainAPI.Repo] # no replica in dev mode 35 | 36 | # Configure your database 37 | config :blockchain_api, BlockchainAPI.Repo, 38 | username: System.get_env("DATABASE_USER"), 39 | password: System.get_env("DATABASE_PASS"), 40 | database: System.get_env("DATABASE_NAME"), 41 | hostname: System.get_env("DATABASE_HOST"), 42 | pool_size: 20, 43 | timeout: :infinity, 44 | queue_target: 120_000, 45 | queue_interval: 5_000 46 | 47 | config :blockchain, 48 | env: Mix.env(), 49 | base_dir: String.to_charlist("/var/data/blockchain-api/dev/"), 50 | peerbook_update_interval: 60000, 51 | peerbook_allow_rfc1918: true, 52 | peer_cache_timeout: 20000 53 | 54 | config :appsignal, :config, active: true 55 | -------------------------------------------------------------------------------- /config/pescadero.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | port = String.to_integer(System.get_env("PORT") || "4002") 4 | 5 | config :blockchain_api, BlockchainAPIWeb.Endpoint, 6 | http: [port: port], 7 | url: [host: System.get_env("HOSTNAME") || "localhost", port: port], 8 | server: true, 9 | root: ".", 10 | version: Application.spec(:blockchain_api, :vsn), 11 | check_origin: false, 12 | # force_ssl: [hsts: true, rewrite_on: [:x_forwarded_proto]], 13 | secret_key_base: System.get_env("SECRET_KEY_BASE") 14 | 15 | # cache_static_manifest: "priv/static/cache_manifest.json" 16 | 17 | config :blockchain_api, 18 | env: Mix.env(), 19 | google_maps_secret: System.get_env("GOOGLE_MAPS_API_KEY"), 20 | notifier_client: BlockchainAPI.FakeNotifierClient 21 | 22 | # Configure your database 23 | config :blockchain_api, BlockchainAPI.Repo, 24 | username: System.get_env("PESCADERO_DB_USER"), 25 | password: System.get_env("PESCADERO_DB_PASS"), 26 | database: System.get_env("PESCADERO_DB"), 27 | hostname: System.get_env("PESCADERO_DB_HOST"), 28 | pool_size: 20, 29 | timeout: 120_000, 30 | log: false 31 | 32 | # Don't connect pescadero to seed nodes 33 | config :blockchain, 34 | seed_nodes: [], 35 | seed_node_dns: '', 36 | base_dir: String.to_charlist("/var/data/blockchain-api/pescadero/") 37 | -------------------------------------------------------------------------------- /config/sample.secret.exs: -------------------------------------------------------------------------------- 1 | # This is a sample secret file a dev would need to add under config/ 2 | # Remember to rename it to `prod.secret.exs` 3 | 4 | use Mix.Config 5 | 6 | # In this file, we keep production configuration that 7 | # you'll likely want to automate and keep away from 8 | # your version control system. 9 | # 10 | # You should document the content of this 11 | # file or create a script for recreating it, since it's 12 | # kept out of version control and might be hard to recover 13 | # or recreate for your teammates (or yourself later on). 14 | config :blockchain_api, BlockchainAPIWeb.Endpoint, secret_key_base: "some long encoded secret" 15 | 16 | # Configure your database 17 | config :blockchain_api, BlockchainAPI.Repo, 18 | username: "username", 19 | password: "password", 20 | database: "blockchain_api_prod", 21 | pool_size: 15 22 | -------------------------------------------------------------------------------- /config/test.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | port = String.to_integer(System.get_env("PORT") || "4002") 4 | 5 | config :blockchain_api, BlockchainAPIWeb.Endpoint, 6 | http: [port: port], 7 | url: [host: System.get_env("HOSTNAME") || "localhost", port: port], 8 | server: true, 9 | root: ".", 10 | version: Application.spec(:blockchain_api, :vsn), 11 | check_origin: false, 12 | # sample secret key for testing 13 | secret_key_base: "783ef8381ceb304d0bd6a62f2bb256751dae3969e8eadd0358fbd47797dd0bee" 14 | 15 | # Only debug statements for tests 16 | config :logger, level: :debug 17 | 18 | # Preset database configuration for tests 19 | # Configure your database 20 | config :blockchain_api, BlockchainAPI.Repo, 21 | username: "postgres", 22 | password: "postgres", 23 | database: "blockchain-api-test", 24 | hostname: "localhost", 25 | pool: Ecto.Adapters.SQL.Sandbox, 26 | pool_size: 10, 27 | timeout: 60000, 28 | log: false 29 | 30 | config :blockchain_api, 31 | env: Mix.env(), 32 | notifier_client: BlockchainAPI.FakeNotifierClient, 33 | repos: [master: BlockchainAPI.Repo, replica: BlockchainAPI.Repo] # no replica in test mode 34 | 35 | # Don't connect dev to seed nodes 36 | config :blockchain, 37 | seed_nodes: [], 38 | seed_node_dns: '', 39 | base_dir: String.to_charlist("/tmp/blockchain-api/test/"), 40 | peerbook_update_interval: 60000, 41 | peerbook_allow_rfc1918: true, 42 | peer_cache_timeout: 20000 43 | -------------------------------------------------------------------------------- /dev-cmd: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | PROGNAME=$0 3 | 4 | usage() { 5 | cat << EOF >&2 6 | Usage: $PROGNAME [-e ] 7 | 8 | -e : ... 9 | EOF 10 | exit 1 11 | } 12 | 13 | env=prod 14 | while getopts e: o; do 15 | case $o in 16 | (e) env=$OPTARG;; 17 | (*) usage 18 | esac 19 | done 20 | shift "$((OPTIND - 1))" 21 | 22 | _build/dev/rel/blockchain_api/bin/blockchain_api $@ 23 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.5' 2 | 3 | services: 4 | app: 5 | build: . 6 | image: "helium/blockchain_api:${MIX_ENV}-latest" 7 | restart: always 8 | depends_on: 9 | - postgres 10 | env_file: 11 | - config/docker-${MIX_ENV}.env 12 | depends_on: 13 | - db_local 14 | networks: 15 | - nginx_network 16 | - app_network 17 | db_local: 18 | build: ./postgres 19 | image: helium/postgres:local 20 | container_name: postgres 21 | restart: always 22 | volumes: 23 | - /var/data/dev/postgresql:/var/lib/postgresql/data 24 | env_file: 25 | - config/docker-${MIX_ENV}.env 26 | networks: 27 | - app_network 28 | nginx: 29 | build: ./nginx 30 | image: helium/nginx:local 31 | volumes: 32 | - ./nginx/lb.conf:/etc/nginx/conf.d/lb.conf 33 | ports: 34 | - "1313:8080" 35 | networks: 36 | - nginx_network 37 | depends_on: 38 | - app 39 | 40 | networks: 41 | nginx_network: 42 | external: true 43 | app_network: 44 | -------------------------------------------------------------------------------- /lib/blockchain_api.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI do 2 | @moduledoc """ 3 | BlockchainAPI keeps the contexts that define your domain 4 | and business logic. 5 | 6 | Contexts are also responsible for managing your data, regardless 7 | if it comes from the database, an external API or others. 8 | """ 9 | end 10 | -------------------------------------------------------------------------------- /lib/blockchain_api/cache/cache_service.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Cache.CacheService do 2 | import Plug.Conn 3 | 4 | def purge_key(key) do 5 | fastly_post("purge/#{key}") 6 | end 7 | 8 | # when pagination params are used, override any options provided 9 | # set a long TTL and a surrogate key of pagination 10 | def put_cache_headers(%Plug.Conn{params: %{"before" => _, "limit" => _}} = conn, _opts) do 11 | conn 12 | |> do_put_cache_headers(ttl: :long, key: "pagination") 13 | end 14 | 15 | def put_cache_headers(conn, options) do 16 | conn 17 | |> do_put_cache_headers(options) 18 | end 19 | 20 | defp do_put_cache_headers(conn, ttl: ttl, key: key) do 21 | conn 22 | |> put_resp_header("surrogate-key", key) 23 | |> do_put_cache_headers(ttl: ttl) 24 | end 25 | 26 | defp do_put_cache_headers(conn, ttl: ttl) do 27 | ttl = get_ttl_value(ttl) 28 | 29 | conn 30 | |> put_resp_header("surrogate-control", "max-age=#{ttl}") 31 | |> put_resp_header("cache-control", "max-age=#{ttl}") 32 | end 33 | 34 | defp get_ttl_value(ttl) do 35 | case ttl do 36 | :never -> 0 37 | :short -> 300 # 5 minutes 38 | :medium -> 600 # 10 minutes 39 | :long -> 86_400 # 1 day 40 | end 41 | end 42 | 43 | defp fastly_post(path, body \\ "") do 44 | api_key = Application.get_env(:blockchain_api, :fastly_api_key) 45 | service_id = Application.get_env(:blockchain_api, :fastly_service_id) 46 | 47 | if api_key && service_id do 48 | url = "https://api.fastly.com/service/#{service_id}/#{path}" 49 | HTTPoison.post(url, body, [{"Fastly-Key", api_key}]) 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/blockchain_api/cli.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.CLI do 2 | def to_chars(list) do 3 | list 4 | |> List.flatten() 5 | |> Enum.map(&String.to_charlist/1) 6 | end 7 | 8 | def clique_command(list) do 9 | list 10 | |> to_chars() 11 | |> :blockchain_console.command() 12 | end 13 | 14 | def load_genesis(genesis_file) do 15 | case File.read(genesis_file) do 16 | {:ok, genesis_block} -> 17 | :blockchain_worker.integrate_genesis_block(:blockchain_block.deserialize(genesis_block)) 18 | 19 | {:error, reason} -> 20 | IO.inspect("Error, reason: #{reason}") 21 | {:error, reason} 22 | end 23 | end 24 | 25 | def load_genesis do 26 | load_genesis(Path.join(:code.priv_dir(:blockchain_api), "genesis")) 27 | end 28 | 29 | def height() do 30 | case :blockchain_worker.blockchain() do 31 | :undefined -> 32 | "undefined" 33 | 34 | chain -> 35 | case :blockchain.height(chain) do 36 | :undefined -> "undefined" 37 | {:ok, h} -> h 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/blockchain_api/error/commit_error.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.CommitError do 2 | @moduledoc false 3 | defexception [:message] 4 | 5 | @impl true 6 | def exception(value) do 7 | msg = "Unexpected! Got: #{inspect(value)}" 8 | %BlockchainAPI.CommitError{message: msg} 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/blockchain_api/job/submit_coinbase.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Job.SubmitCoinbase do 2 | alias BlockchainAPI.Query.PendingCoinbase 3 | alias BlockchainAPI.Util 4 | require Logger 5 | 6 | def run(id) do 7 | Logger.debug("running pending_coinbase job: #{inspect(id)}") 8 | 9 | pending_coinbase = PendingCoinbase.get_by_id!(id) 10 | txn = pending_coinbase.txn |> :blockchain_txn.deserialize() 11 | 12 | txn 13 | |> :blockchain_worker.submit_txn(fn res -> 14 | case res do 15 | :ok -> 16 | Logger.info("Txn: #{Util.bin_to_string(:blockchain_txn.hash(txn))} accepted!") 17 | 18 | pending_coinbase 19 | |> PendingCoinbase.update!(%{status: "cleared"}) 20 | 21 | {:error, reason} -> 22 | Logger.error( 23 | "Txn: #{Util.bin_to_string(:blockchain_txn.hash(txn))} failed!, reason: #{ 24 | inspect(reason) 25 | }" 26 | ) 27 | 28 | pending_coinbase 29 | |> PendingCoinbase.update!(%{status: "error"}) 30 | end 31 | end) 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/blockchain_api/job/submit_gateway.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Job.SubmitGateway do 2 | alias BlockchainAPI.{ 3 | Query.Hotspot, 4 | Query.PendingGateway, 5 | HotspotNotifier, 6 | Util 7 | } 8 | require Logger 9 | 10 | @blacklisted_owner BlockchainAPI.Util.string_to_bin("14CJX5YCRf94kbhL9PPn58SL9EzqUGsrALeaEar4UikM4EB3Mx7") 11 | 12 | def run(id) do 13 | Logger.debug("running pending_gateway job: #{inspect(id)}") 14 | 15 | pending_gateway = PendingGateway.get_by_id!(id) 16 | txn = pending_gateway.txn |> :blockchain_txn.deserialize() 17 | 18 | case :blockchain_txn_add_gateway_v1.owner(txn) do 19 | @blacklisted_owner -> 20 | Logger.error("You are blacklisted! Job id: #{id}") 21 | 22 | # Mark as error right away 23 | pending_gateway 24 | |> PendingGateway.update!(%{status: "error"}) 25 | 26 | _ -> 27 | 28 | txn 29 | |> :blockchain_worker.submit_txn(fn res -> 30 | case res do 31 | :ok -> 32 | Logger.info("Txn: #{Util.bin_to_string(:blockchain_txn.hash(txn))} accepted!") 33 | notify_gateway_success(pending_gateway) 34 | 35 | pending_gateway 36 | |> PendingGateway.update!(%{status: "cleared"}) 37 | 38 | {:error, reason} -> 39 | Logger.error( 40 | "Txn: #{Util.bin_to_string(:blockchain_txn.hash(txn))} failed!, reason: #{ 41 | inspect(reason) 42 | }" 43 | ) 44 | notify_gateway_failure(pending_gateway) 45 | 46 | pending_gateway 47 | |> PendingGateway.update!(%{status: "error"}) 48 | end 49 | end) 50 | end 51 | end 52 | 53 | defp notify_gateway_success(pending_gateway) do 54 | HotspotNotifier.send_new_hotspot_notification(pending_gateway) 55 | end 56 | 57 | defp notify_gateway_failure(pending_gateway) do 58 | case Hotspot.get(pending_gateway.gateway) do 59 | nil -> 60 | HotspotNotifier.send_add_hotspot_failed(:timed_out, pending_gateway) 61 | 62 | _ -> 63 | HotspotNotifier.send_add_hotspot_failed(:already_exists, pending_gateway) 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /lib/blockchain_api/job/submit_location.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Job.SubmitLocation do 2 | alias BlockchainAPI.{ 3 | HotspotNotifier, 4 | Query.PendingLocation, 5 | Util 6 | } 7 | require Logger 8 | 9 | @blacklisted_owner BlockchainAPI.Util.string_to_bin("14CJX5YCRf94kbhL9PPn58SL9EzqUGsrALeaEar4UikM4EB3Mx7") 10 | 11 | def run(id) do 12 | Logger.debug("running pending_location job: #{inspect(id)}") 13 | 14 | pending_location = PendingLocation.get_by_id!(id) 15 | txn = pending_location.txn |> :blockchain_txn.deserialize() 16 | 17 | case :blockchain_txn_assert_location_v1.owner(txn) do 18 | @blacklisted_owner -> 19 | Logger.error("You are blacklisted! Job id: #{id}") 20 | 21 | # Mark as error right away 22 | pending_location 23 | |> PendingLocation.update!(%{status: "error"}) 24 | 25 | _ -> 26 | 27 | txn 28 | |> :blockchain_worker.submit_txn(fn res -> 29 | case res do 30 | :ok -> 31 | Logger.info("Txn: #{Util.bin_to_string(:blockchain_txn.hash(txn))} accepted!") 32 | 33 | pending_location 34 | |> PendingLocation.update!(%{status: "cleared"}) 35 | 36 | {:error, reason} -> 37 | Logger.error( 38 | "Txn: #{Util.bin_to_string(:blockchain_txn.hash(txn))} failed!, reason: #{ 39 | inspect(reason) 40 | }" 41 | ) 42 | HotspotNotifier.send_confirm_location_failed(pending_location) 43 | 44 | pending_location 45 | |> PendingLocation.update!(%{status: "error"}) 46 | end 47 | end) 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lib/blockchain_api/job/submit_oui.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Job.SubmitOUI do 2 | alias BlockchainAPI.Query.PendingOUI 3 | alias BlockchainAPI.Util 4 | require Logger 5 | 6 | def run(id) do 7 | Logger.debug("running pending_oui job: #{inspect(id)}") 8 | 9 | pending_oui = PendingOUI.get_by_id!(id) 10 | 11 | IO.inspect(pending_oui, label: :pending_oui) 12 | IO.inspect(pending_oui.txn, label: :pending_oui_txn) 13 | 14 | txn = pending_oui.txn |> :blockchain_txn.deserialize() 15 | 16 | IO.inspect(txn, label: :before_submit) 17 | 18 | txn 19 | |> :blockchain_worker.submit_txn(fn res -> 20 | case res do 21 | :ok -> 22 | Logger.info("Txn: #{Util.bin_to_string(:blockchain_txn.hash(txn))} accepted!") 23 | 24 | pending_oui 25 | |> PendingOUI.update!(%{status: "cleared"}) 26 | 27 | {:error, reason} -> 28 | Logger.error( 29 | "Txn: #{Util.bin_to_string(:blockchain_txn.hash(txn))} failed!, reason: #{ 30 | inspect(reason) 31 | }" 32 | ) 33 | 34 | pending_oui 35 | |> PendingOUI.update!(%{status: "error"}) 36 | end 37 | end) 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/blockchain_api/job/submit_payment.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Job.SubmitPayment do 2 | alias BlockchainAPI.Query.PendingPayment 3 | alias BlockchainAPI.Util 4 | require Logger 5 | 6 | @blacklisted_owner BlockchainAPI.Util.string_to_bin("14CJX5YCRf94kbhL9PPn58SL9EzqUGsrALeaEar4UikM4EB3Mx7") 7 | 8 | def run(id) do 9 | Logger.debug("running pending_payment job: #{inspect(id)}") 10 | 11 | pending_payment = PendingPayment.get_by_id!(id) 12 | txn = pending_payment.txn |> :blockchain_txn.deserialize() 13 | 14 | case :blockchain_txn_payment_v1.payer(txn) do 15 | @blacklisted_owner -> 16 | Logger.error("You are blacklisted! Job id: #{id}") 17 | 18 | # Mark as error right away 19 | pending_payment 20 | |> PendingPayment.update!(%{status: "error"}) 21 | 22 | _ -> 23 | 24 | txn 25 | |> :blockchain_worker.submit_txn(fn res -> 26 | case res do 27 | :ok -> 28 | Logger.info("Txn: #{Util.bin_to_string(:blockchain_txn.hash(txn))} accepted!") 29 | 30 | pending_payment 31 | |> PendingPayment.update!(%{status: "cleared"}) 32 | 33 | {:error, reason} -> 34 | Logger.error( 35 | "Txn: #{Util.bin_to_string(:blockchain_txn.hash(txn))} failed!, reason: #{ 36 | inspect(reason) 37 | }" 38 | ) 39 | 40 | pending_payment 41 | |> PendingPayment.update!(%{status: "error"}) 42 | end 43 | end) 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/blockchain_api/job/submit_sec_exchange.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Job.SubmitSecExchange do 2 | alias BlockchainAPI.Query.PendingSecExchange 3 | alias BlockchainAPI.Util 4 | require Logger 5 | 6 | def run(id) do 7 | Logger.debug("running pending_sec_exchange job: #{inspect(id)}") 8 | 9 | pending_sec_exchange = PendingSecExchange.get_by_id!(id) 10 | txn = pending_sec_exchange.txn |> :blockchain_txn.deserialize() 11 | 12 | txn 13 | |> :blockchain_worker.submit_txn(fn res -> 14 | case res do 15 | :ok -> 16 | Logger.info("Txn: #{Util.bin_to_string(:blockchain_txn.hash(txn))} accepted!") 17 | 18 | pending_sec_exchange 19 | |> PendingSecExchange.update!(%{status: "cleared"}) 20 | 21 | {:error, reason} -> 22 | Logger.error( 23 | "Txn: #{Util.bin_to_string(:blockchain_txn.hash(txn))} failed!, reason: #{ 24 | inspect(reason) 25 | }" 26 | ) 27 | 28 | pending_sec_exchange 29 | |> PendingSecExchange.update!(%{status: "error"}) 30 | end 31 | end) 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/blockchain_api/notifier.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Notifier do 2 | use GenServer 3 | require Logger 4 | 5 | alias BlockchainAPI.{ 6 | HotspotNotifier, 7 | PaymentsNotifier, 8 | Query.PendingGateway, 9 | Schema.Hotspot 10 | } 11 | 12 | # ================================================================== 13 | # API 14 | # ================================================================== 15 | def start_link(args) do 16 | GenServer.start_link(__MODULE__, args, name: __MODULE__) 17 | end 18 | 19 | def notify(block, ledger) do 20 | GenServer.cast(__MODULE__, {:notify, block, ledger}) 21 | end 22 | 23 | # ================================================================== 24 | # Callbacks 25 | # ================================================================== 26 | 27 | @impl true 28 | def init(_args) do 29 | chain = :blockchain_worker.blockchain() 30 | {:ok, %{chain: chain}} 31 | end 32 | 33 | @impl true 34 | def handle_cast({:notify, block, ledger}, state) do 35 | case :blockchain_block.transactions(block) do 36 | [] -> 37 | :ok 38 | 39 | txns -> 40 | Enum.map(txns, fn txn -> 41 | case :blockchain_txn.type(txn) do 42 | :blockchain_txn_payment_v1 -> 43 | Logger.info("Notifying for payments from block: #{:blockchain_block.height(block)}") 44 | PaymentsNotifier.send_notification(txn) 45 | 46 | :blockchain_txn_add_gateway_v1 -> 47 | try do 48 | Hotspot.map(:blockchain_txn_add_gateway_v1, txn, ledger) 49 | |> Map.get(:address) 50 | |> PendingGateway.get!() 51 | |> HotspotNotifier.send_new_hotspot_notification() 52 | 53 | Logger.info("Notified new hotspots for block: #{:blockchain_block.height(block)}") 54 | 55 | rescue 56 | _error in Ecto.NoResultsError -> 57 | Logger.error("Notification for new hotspots for block: #{:blockchain_block.height(block)} failed") 58 | end 59 | 60 | _ -> 61 | :ok 62 | end 63 | end) 64 | end 65 | 66 | {:noreply, state} 67 | end 68 | 69 | @impl true 70 | def handle_info(_, state) do 71 | {:noreply, state} 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /lib/blockchain_api/notifiers/hotspot_notifier.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.HotspotNotifier do 2 | alias BlockchainAPI.{Schema.Hotspot, Util} 3 | 4 | def send_new_hotspot_notification(pending_gateway) do 5 | data = %{ 6 | hotspot_address: Util.bin_to_string(pending_gateway.gateway), 7 | owner: Util.bin_to_string(pending_gateway.owner), 8 | hash: Util.bin_to_string(pending_gateway.hash), 9 | type: "addHotspotSuccess" 10 | } 11 | opts = %{external_id: UUID.uuid5(:oid, "#{pending_gateway.hash}success")} 12 | animal_name = Hotspot.animal_name(pending_gateway.gateway) 13 | message = "#{animal_name} has been added to the network!" 14 | Util.notifier_client().post(data, message, data.owner, opts) 15 | end 16 | 17 | def send_add_hotspot_failed(:timed_out, pending_gateway) do 18 | data = %{ 19 | hotspot_address: Util.bin_to_string(pending_gateway.gateway), 20 | owner: Util.bin_to_string(pending_gateway.owner), 21 | type: "addHotspotTimeOut" 22 | } 23 | opts = %{external_id: UUID.uuid5(:oid, "#{pending_gateway.hash}timed_out")} 24 | message = "Unable to Add Hotspot. Transaction Timed Out." 25 | Util.notifier_client().post(data, message, data.owner, opts) 26 | end 27 | 28 | def send_add_hotspot_failed(:already_exists, pending_gateway) do 29 | data = %{ 30 | hotspot_address: Util.bin_to_string(pending_gateway.gateway), 31 | owner: Util.bin_to_string(pending_gateway.owner), 32 | type: "addHotspotAlreadyExists" 33 | } 34 | opts = %{external_id: UUID.uuid5(:oid, "#{pending_gateway.hash}already_exists")} 35 | message = "Unable to Add Hotspot. Hotspot Already on Blockchain." 36 | Util.notifier_client().post(data, message, data.owner, opts) 37 | end 38 | 39 | def send_confirm_location_failed(pending_location) do 40 | data = %{ 41 | hotspot_address: Util.bin_to_string(pending_location.gateway), 42 | owner: Util.bin_to_string(pending_location.owner), 43 | type: "assertLocationFailure" 44 | } 45 | opts = %{external_id: UUID.uuid5(:oid, "#{pending_location.hash}failed")} 46 | animal_name = Hotspot.animal_name(pending_location.gateway) 47 | message = "#{animal_name} Added Without Location Information." 48 | Util.notifier_client().post(data, message, data.owner, opts) 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lib/blockchain_api/notifiers/notifier_client.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.NotifierClient do 2 | @url "https://onesignal.com/api/v1/notifications" 3 | 4 | def post(data, message, send_address, opts \\ %{}) do 5 | HTTPoison.post(@url, to_payload(data, message, send_address, opts), headers()) 6 | end 7 | 8 | defp headers() do 9 | [ 10 | {"Content-Type", "application/json; charset=utf-8"}, 11 | {"Authorization", 12 | "Basic #{Application.fetch_env!(:blockchain_api, :onesignal_rest_api_key)}"} 13 | ] 14 | end 15 | 16 | defp to_payload(data, message, send_address, opts) do 17 | %{ 18 | :app_id => Application.fetch_env!(:blockchain_api, :onesignal_app_id), 19 | :filters => [ 20 | %{:field => "tag", :key => "address", :relation => "=", :value => send_address} 21 | ], 22 | :contents => %{:en => message}, 23 | :data => data 24 | } 25 | |> Map.merge(opts) 26 | |> encode() 27 | end 28 | 29 | defp encode(payload) do 30 | {:ok, payload} = payload |> Jason.encode() 31 | payload 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/blockchain_api/notifiers/payments_notifier.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.PaymentsNotifier do 2 | @ticker "HNT" 3 | 4 | alias BlockchainAPI.Util 5 | 6 | def send_notification(txn) do 7 | amount = :blockchain_txn_payment_v1.amount(txn) 8 | msg = amount |> Util.units() |> message() 9 | data = payment_data(txn, amount) 10 | Util.notifier_client().post(data, msg, data.address) 11 | end 12 | 13 | defp payment_data(txn, amount) do 14 | %{ 15 | address: Util.bin_to_string(:blockchain_txn_payment_v1.payee(txn)), 16 | amount: amount, 17 | hash: Util.bin_to_string(:blockchain_txn_payment_v1.hash(txn)), 18 | type: "receivedPayment" 19 | } 20 | end 21 | 22 | defp message(units) do 23 | "You got #{units} #{@ticker}" 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/blockchain_api/notifiers/rewards_notifier.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.RewardsNotifier do 2 | use Task 3 | require Logger 4 | 5 | alias BlockchainAPI.{ 6 | Query.RewardTxn, 7 | Util 8 | } 9 | 10 | @ticker "HNT" 11 | 12 | def start_link(_) do 13 | Task.start_link(__MODULE__, :schedule_notifications, []) 14 | end 15 | 16 | # 1 week in ms 17 | @interval 1000 * 60 * 60 * 24 * 7 18 | 19 | # schedule notifications to be sent to onesignal on the next Tuesday at 00:00:00 UTC 20 | def schedule_notifications do 21 | now = Timex.now(:utc) 22 | days_to_notification = 7 - Timex.days_to_beginning_of_week(now, "Tuesday") 23 | notification_day = Timex.shift(now, days: days_to_notification) 24 | 25 | notification_time = 26 | Timex.to_datetime( 27 | {{notification_day.year, notification_day.month, notification_day.day}, {0, 0, 0}}, 28 | "Etc/UTC" 29 | ) 30 | 31 | Timex.diff(notification_time, Timex.now(), :milliseconds) 32 | |> :timer.apply_after(__MODULE__, :send_notifications, []) 33 | end 34 | 35 | # send notifications to onesignal and set timer to send again in one week 36 | def send_notifications do 37 | Logger.info("Notifying for weekly rewards") 38 | 39 | RewardTxn.get_from_last_week() 40 | |> Enum.map(fn r -> 41 | reward = reward_data(r) 42 | 43 | Util.notifier_client().post(reward, message(reward), reward.address, %{ 44 | delayed_option: "timezone", 45 | delivery_time_of_day: "10:00AM", 46 | external_id: UUID.uuid5(:oid, "#{reward.address}-#{Timex.today() |> Date.to_string()}") 47 | }) 48 | end) 49 | 50 | :timer.apply_after(@interval, __MODULE__, :send_notifications, []) 51 | end 52 | 53 | defp reward_data(reward) do 54 | %{ 55 | address: Util.bin_to_string(reward.account), 56 | amount: Util.units(reward.amount), 57 | type: "receivedRewards" 58 | } 59 | end 60 | 61 | defp message(reward) do 62 | "Your hotspots earned #{reward.amount} #{@ticker} from mining in the past week." 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /lib/blockchain_api/query/coinbase_transaction.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Query.CoinbaseTransaction do 2 | @moduledoc false 3 | import Ecto.Query, warn: false 4 | 5 | alias BlockchainAPI.{Repo, Schema.CoinbaseTransaction} 6 | 7 | def list(_params) do 8 | CoinbaseTransaction 9 | |> order_by([ct], desc: ct.id) 10 | |> Repo.replica.all() 11 | end 12 | 13 | def get!(hash) do 14 | CoinbaseTransaction 15 | |> where([ct], ct.hash == ^hash) 16 | |> Repo.replica.one!() 17 | end 18 | 19 | def create(attrs \\ %{}) do 20 | %CoinbaseTransaction{} 21 | |> CoinbaseTransaction.changeset(attrs) 22 | |> Repo.insert() 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/blockchain_api/query/consensus_member.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Query.ConsensusMember do 2 | @moduledoc false 3 | import Ecto.Query, warn: false 4 | 5 | alias BlockchainAPI.{Repo, Schema.ConsensusMember} 6 | 7 | def list(_params) do 8 | ConsensusMember 9 | |> order_by([ct], desc: ct.id) 10 | |> Repo.replica.all() 11 | end 12 | 13 | def create(attrs \\ %{}) do 14 | %ConsensusMember{} 15 | |> ConsensusMember.changeset(attrs) 16 | |> Repo.insert() 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/blockchain_api/query/data_credit_transaction.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Query.DataCreditTransaction do 2 | @moduledoc false 3 | import Ecto.Query, warn: false 4 | 5 | alias BlockchainAPI.{Repo, Schema.DataCreditTransaction} 6 | 7 | def get_balance(address) do 8 | res = 9 | DataCreditTransaction 10 | |> where([ct], ct.payee == ^address) 11 | |> order_by([ct], desc: ct.id) 12 | |> limit(1) 13 | |> Repo.replica.one() 14 | 15 | case res do 16 | nil -> 0 17 | dc -> dc.amount 18 | end 19 | end 20 | 21 | def get!(hash) do 22 | DataCreditTransaction 23 | |> where([ct], ct.hash == ^hash) 24 | |> Repo.replica.one!() 25 | end 26 | 27 | def create(attrs \\ %{}) do 28 | %DataCreditTransaction{} 29 | |> DataCreditTransaction.changeset(attrs) 30 | |> Repo.insert() 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/blockchain_api/query/hotspot_reward.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Query.HotspotReward do 2 | @moduledoc false 3 | import Ecto.Query, warn: false 4 | 5 | @default_limit 100 6 | @max_limit 500 7 | 8 | alias BlockchainAPI.{Repo, Schema.RewardTxn} 9 | 10 | def list(address, params) do 11 | address 12 | |> list_query() 13 | |> maybe_filter(params) 14 | |> Repo.replica.all() 15 | end 16 | 17 | defp list_query(address) do 18 | from( 19 | r in RewardTxn, 20 | where: r.gateway == ^address, 21 | order_by: [desc: r.id] 22 | ) 23 | end 24 | 25 | defp maybe_filter(query, %{"before" => before, "limit" => limit0} = _params) do 26 | limit = min(@max_limit, String.to_integer(limit0)) 27 | 28 | query 29 | |> where([r], r.id < ^before) 30 | |> limit(^limit) 31 | end 32 | 33 | defp maybe_filter(query, %{"before" => before} = _params) do 34 | query 35 | |> where([r], r.id < ^before) 36 | |> limit(@default_limit) 37 | end 38 | 39 | defp maybe_filter(query, %{"limit" => limit0} = _params) do 40 | limit = min(@max_limit, String.to_integer(limit0)) 41 | 42 | query 43 | |> limit(^limit) 44 | end 45 | 46 | defp maybe_filter(query, %{}) do 47 | query 48 | |> limit(@default_limit) 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lib/blockchain_api/query/hotspot_status.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Query.HotspotStatus do 2 | @moduledoc ~s(Get hotspot status using libp2p peer information) 3 | 4 | # Consider 60 minutes as stale time delta 5 | @stale_delta 3_600_000 6 | 7 | require Logger 8 | 9 | def consolidate_status(challenge_status, pubkey_bin) do 10 | case challenge_status do 11 | "online" -> 12 | "online" 13 | 14 | "offline" -> 15 | case get_staleness(pubkey_bin) do 16 | {:error, _} -> 17 | "offline" 18 | 19 | {:ok, true} -> 20 | "offline" 21 | 22 | {:ok, false} -> 23 | "online" 24 | end 25 | end 26 | end 27 | 28 | def get_staleness(pubkey_bin) do 29 | swarm = :blockchain_swarm.swarm() 30 | pb = :libp2p_swarm.peerbook(swarm) 31 | 32 | # force a pb refresh 33 | :ok = :libp2p_peerbook.refresh(pb, pubkey_bin) 34 | # sleep for 200ms, arp is fast ~80ms 35 | :timer.sleep(200) 36 | 37 | case :libp2p_peerbook.get(pb, pubkey_bin) do 38 | {:error, reason} = e -> 39 | Logger.error("Peer not found #{inspect(reason)}") 40 | e 41 | 42 | {:ok, peer} -> 43 | is_stale = :libp2p_peer.is_stale(peer, @stale_delta) 44 | {:ok, is_stale} 45 | end 46 | end 47 | 48 | def sync_percent(pubkey_bin, api_height) do 49 | swarm = :blockchain_swarm.swarm() 50 | pb = :libp2p_swarm.peerbook(swarm) 51 | 52 | case :libp2p_peerbook.get(pb, pubkey_bin) do 53 | {:error, reason} -> 54 | Logger.error("Peer not found #{inspect(reason)}") 55 | nil 56 | 57 | {:ok, peer} -> 58 | case :libp2p_peer.signed_metadata_get(peer, <<"height">>, :undefined) do 59 | height when is_integer(height) and height > 0 -> 60 | case abs(api_height - height) <= 500 do 61 | true -> 62 | 1.0 63 | 64 | false -> 65 | height / api_height 66 | end 67 | 68 | _ -> 69 | nil 70 | end 71 | end 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /lib/blockchain_api/query/location_transaction.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Query.LocationTransaction do 2 | @moduledoc false 3 | import Ecto.Query, warn: false 4 | 5 | alias BlockchainAPI.{Repo, Schema.LocationTransaction} 6 | 7 | def list(_params) do 8 | LocationTransaction 9 | |> order_by([lt], desc: [lt.id]) 10 | |> Repo.replica.all() 11 | end 12 | 13 | def get!(hash) do 14 | LocationTransaction 15 | |> where([lt], lt.hash == ^hash) 16 | |> Repo.replica.one!() 17 | end 18 | 19 | def create(attrs \\ %{}) do 20 | %LocationTransaction{} 21 | |> LocationTransaction.changeset(attrs) 22 | |> Repo.insert() 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/blockchain_api/query/oui_transaction.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Query.OUITransaction do 2 | @moduledoc false 3 | import Ecto.Query, warn: false 4 | 5 | alias BlockchainAPI.{Repo, Schema.OUITransaction} 6 | 7 | def list(_params) do 8 | OUITransaction 9 | |> order_by([oui], desc: [oui.id]) 10 | |> Repo.replica.all() 11 | end 12 | 13 | def get!(hash) do 14 | OUITransaction 15 | |> where([oui], oui.hash == ^hash) 16 | |> Repo.replica.one!() 17 | end 18 | 19 | def create(attrs \\ %{}) do 20 | %OUITransaction{} 21 | |> OUITransaction.changeset(attrs) 22 | |> Repo.insert() 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/blockchain_api/query/payment_transaction.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Query.PaymentTransaction do 2 | @moduledoc false 3 | import Ecto.Query, warn: false 4 | 5 | alias BlockchainAPI.{Repo, Schema.PaymentTransaction} 6 | 7 | def list(_params) do 8 | PaymentTransaction 9 | |> order_by([pt], desc: pt.id) 10 | |> Repo.replica.all() 11 | end 12 | 13 | def get!(hash) do 14 | PaymentTransaction 15 | |> where([pt], pt.hash == ^hash) 16 | |> Repo.replica.one!() 17 | end 18 | 19 | def create(attrs \\ %{}) do 20 | %PaymentTransaction{} 21 | |> PaymentTransaction.changeset(attrs) 22 | |> Repo.insert() 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/blockchain_api/query/payment_v2_txn.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Query.PaymentV2Txn do 2 | @moduledoc false 3 | import Ecto.Query, warn: false 4 | 5 | alias BlockchainAPI.{Repo, Schema.PaymentV2Txn} 6 | 7 | def list(_params) do 8 | PaymentV2Txn 9 | |> order_by([pt], desc: pt.id) 10 | |> Repo.replica.all() 11 | end 12 | 13 | def get!(hash) do 14 | PaymentV2Txn 15 | |> where([pt], pt.hash == ^hash) 16 | |> Repo.replica.one!() 17 | end 18 | 19 | def create(attrs \\ %{}) do 20 | %PaymentV2Txn{} 21 | |> PaymentV2Txn.changeset(attrs) 22 | |> Repo.insert() 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/blockchain_api/query/pending_coinbase.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Query.PendingCoinbase do 2 | @moduledoc false 3 | import Ecto.Query, warn: false 4 | 5 | alias BlockchainAPI.{Repo, Schema.PendingCoinbase} 6 | 7 | def create(attrs \\ %{}) do 8 | %PendingCoinbase{} 9 | |> PendingCoinbase.changeset(attrs) 10 | |> Repo.insert() 11 | end 12 | 13 | def get!(hash) do 14 | PendingCoinbase 15 | |> where([pc], pc.hash == ^hash) 16 | |> Repo.replica.one!() 17 | end 18 | 19 | def get_by_id!(id) do 20 | PendingCoinbase 21 | |> where([pc], pc.id == ^id) 22 | |> Repo.replica.one!() 23 | end 24 | 25 | def update!(pc, attrs \\ %{}) do 26 | pc 27 | |> PendingCoinbase.changeset(attrs) 28 | |> Repo.update!() 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/blockchain_api/query/pending_gateway.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Query.PendingGateway do 2 | @moduledoc false 3 | import Ecto.Query, warn: false 4 | 5 | alias BlockchainAPI.{Repo, Schema.PendingGateway} 6 | 7 | def create(attrs \\ %{}) do 8 | %PendingGateway{} 9 | |> PendingGateway.changeset(attrs) 10 | |> Repo.insert() 11 | end 12 | 13 | def get!(hash) do 14 | PendingGateway 15 | |> where([pg], pg.hash == ^hash) 16 | |> Repo.replica.one!() 17 | end 18 | 19 | def get_by_id!(id) do 20 | PendingGateway 21 | |> where([pg], pg.id == ^id) 22 | |> Repo.replica.one!() 23 | end 24 | 25 | def get(owner, gateway) do 26 | from( 27 | pg in PendingGateway, 28 | where: pg.owner == ^owner, 29 | where: pg.gateway == ^gateway, 30 | select: pg 31 | ) 32 | |> Repo.replica.one() 33 | |> format_one() 34 | end 35 | 36 | def update!(pg, attrs \\ %{}) do 37 | pg 38 | |> PendingGateway.changeset(attrs) 39 | |> Repo.update!() 40 | end 41 | 42 | def list_pending() do 43 | PendingGateway 44 | |> where([pg], pg.status == "pending") 45 | |> Repo.replica.all() 46 | end 47 | 48 | def get_by_owner(address) do 49 | from( 50 | pg in PendingGateway, 51 | where: pg.owner == ^address, 52 | where: pg.status == "pending", 53 | where: pg.status != "error", 54 | where: pg.status != "cleared", 55 | select: pg 56 | ) 57 | |> Repo.replica.all() 58 | |> format() 59 | end 60 | 61 | # ================================================================== 62 | # Helper functions 63 | # ================================================================== 64 | defp format(entries) do 65 | entries 66 | |> Enum.map(&format_one/1) 67 | end 68 | 69 | defp format_one(nil), do: %{} 70 | 71 | defp format_one(entry) do 72 | Map.merge(entry, %{type: "gateway"}) 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /lib/blockchain_api/query/pending_location.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Query.PendingLocation do 2 | @moduledoc false 3 | import Ecto.Query, warn: false 4 | 5 | alias BlockchainAPI.{Repo, Schema.PendingLocation} 6 | 7 | def create(attrs \\ %{}) do 8 | %PendingLocation{} 9 | |> PendingLocation.changeset(attrs) 10 | |> Repo.insert() 11 | end 12 | 13 | def get!(hash) do 14 | PendingLocation 15 | |> where([pl], pl.hash == ^hash) 16 | |> Repo.replica.one!() 17 | end 18 | 19 | def get_by_id!(id) do 20 | PendingLocation 21 | |> where([pl], pl.id == ^id) 22 | |> Repo.replica.one!() 23 | end 24 | 25 | def update!(pl, attrs \\ %{}) do 26 | pl 27 | |> PendingLocation.changeset(attrs) 28 | |> Repo.update!() 29 | end 30 | 31 | def list_pending() do 32 | PendingLocation 33 | |> where([pl], pl.status == "pending") 34 | |> Repo.replica.all() 35 | end 36 | 37 | def get_by_owner(address) do 38 | from( 39 | pl in PendingLocation, 40 | where: pl.owner == ^address, 41 | where: pl.status == "pending", 42 | where: pl.status != "error", 43 | where: pl.status != "cleared", 44 | select: pl 45 | ) 46 | |> Repo.replica.all() 47 | |> format() 48 | end 49 | 50 | def get(owner, gateway, nonce) do 51 | from( 52 | pl in PendingLocation, 53 | where: pl.owner == ^owner, 54 | where: pl.gateway == ^gateway, 55 | where: pl.nonce == ^nonce, 56 | select: pl 57 | ) 58 | |> Repo.replica.all() 59 | |> format() 60 | end 61 | 62 | # ================================================================== 63 | # Helper functions 64 | # ================================================================== 65 | defp format(entries) do 66 | entries 67 | |> Enum.map(&format_one/1) 68 | end 69 | 70 | defp format_one(nil), do: %{} 71 | 72 | defp format_one(entry) do 73 | Map.merge(entry, %{type: "location"}) 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /lib/blockchain_api/query/pending_oui.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Query.PendingOUI do 2 | @moduledoc false 3 | import Ecto.Query, warn: false 4 | 5 | alias BlockchainAPI.{Repo, Schema.PendingOUI} 6 | 7 | def create(attrs \\ %{}) do 8 | %PendingOUI{} 9 | |> PendingOUI.changeset(attrs) 10 | |> Repo.insert() 11 | end 12 | 13 | def get!(hash) do 14 | PendingOUI 15 | |> where([poui], poui.hash == ^hash) 16 | |> Repo.replica.one!() 17 | end 18 | 19 | def get_by_id!(id) do 20 | PendingOUI 21 | |> where([poui], poui.id == ^id) 22 | |> Repo.replica.one!() 23 | end 24 | 25 | def update!(poui, attrs \\ %{}) do 26 | poui 27 | |> PendingOUI.changeset(attrs) 28 | |> Repo.update!() 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/blockchain_api/query/pending_payment.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Query.PendingPayment do 2 | @moduledoc false 3 | import Ecto.Query, warn: false 4 | 5 | alias BlockchainAPI.{Repo, Schema.PendingPayment} 6 | 7 | def create(attrs \\ %{}) do 8 | %PendingPayment{} 9 | |> PendingPayment.changeset(attrs) 10 | |> Repo.insert() 11 | end 12 | 13 | def list_pending() do 14 | PendingPayment 15 | |> where([pp], pp.status == "pending") 16 | |> Repo.replica.all() 17 | end 18 | 19 | def get!(hash) do 20 | PendingPayment 21 | |> where([pp], pp.hash == ^hash) 22 | |> Repo.replica.one!() 23 | end 24 | 25 | def get_by_id!(id) do 26 | PendingPayment 27 | |> where([pp], pp.id == ^id) 28 | |> Repo.replica.one!() 29 | end 30 | 31 | def update!(pp, attrs \\ %{}) do 32 | pp 33 | |> PendingPayment.changeset(attrs) 34 | |> Repo.update!() 35 | end 36 | 37 | def get_by_address(address) do 38 | query = 39 | from( 40 | pp in PendingPayment, 41 | where: pp.payee == ^address or pp.payer == ^address, 42 | select: pp 43 | ) 44 | 45 | query 46 | |> Repo.replica.all() 47 | |> format() 48 | end 49 | 50 | def get_pending_by_address(address) do 51 | from( 52 | pp in PendingPayment, 53 | where: pp.payee == ^address, 54 | or_where: pp.payer == ^address, 55 | where: pp.status == "pending", 56 | where: pp.status != "error", 57 | where: pp.status != "cleared", 58 | select: pp 59 | ) 60 | |> Repo.replica.all() 61 | |> format() 62 | end 63 | 64 | def get(payer, nonce) do 65 | from( 66 | pp in PendingPayment, 67 | where: pp.payer == ^payer, 68 | where: pp.nonce == ^nonce, 69 | select: pp 70 | ) 71 | |> Repo.replica.one() 72 | |> format_one() 73 | end 74 | 75 | def delete!(pp) do 76 | pp |> Repo.delete!() 77 | end 78 | 79 | # ================================================================== 80 | # Helper functions 81 | # ================================================================== 82 | defp format(entries) do 83 | entries 84 | |> Enum.map(&format_one/1) 85 | end 86 | 87 | defp format_one(nil), do: %{} 88 | 89 | defp format_one(entry) do 90 | Map.merge(entry, %{type: "payment"}) 91 | end 92 | end 93 | -------------------------------------------------------------------------------- /lib/blockchain_api/query/pending_sec_exchange.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Query.PendingSecExchange do 2 | @moduledoc false 3 | import Ecto.Query, warn: false 4 | 5 | alias BlockchainAPI.{Repo, Schema.PendingSecExchange} 6 | 7 | def create(attrs \\ %{}) do 8 | %PendingSecExchange{} 9 | |> PendingSecExchange.changeset(attrs) 10 | |> Repo.insert() 11 | end 12 | 13 | def get!(hash) do 14 | PendingSecExchange 15 | |> where([psec], psec.hash == ^hash) 16 | |> Repo.replica.one!() 17 | end 18 | 19 | def get_by_id!(id) do 20 | PendingSecExchange 21 | |> where([psec], psec.id == ^id) 22 | |> Repo.replica.one!() 23 | end 24 | 25 | def update!(psec, attrs \\ %{}) do 26 | psec 27 | |> PendingSecExchange.changeset(attrs) 28 | |> Repo.update!() 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/blockchain_api/query/poc_path_element.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Query.POCPathElement do 2 | @moduledoc false 3 | import Ecto.Query, warn: false 4 | 5 | alias BlockchainAPI.{Repo, Schema.POCPathElement} 6 | 7 | def list(_) do 8 | POCPathElement 9 | |> Repo.replica.all() 10 | end 11 | 12 | def get!(challengee) do 13 | POCPathElement 14 | |> where([poc_path_element], poc_path_element.challengee == ^challengee) 15 | |> Repo.replica.one!() 16 | end 17 | 18 | def create(attrs \\ %{}) do 19 | %POCPathElement{} 20 | |> POCPathElement.changeset(attrs) 21 | |> Repo.insert() 22 | end 23 | 24 | def get_last_ten(challengee) do 25 | POCPathElement 26 | |> where([poc_path_element], poc_path_element.challengee == ^challengee) 27 | |> order_by([poc_path_element], desc: poc_path_element.id) 28 | |> select([poc_path_element], poc_path_element.result) 29 | |> limit(10) 30 | |> Repo.replica.all() 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/blockchain_api/query/poc_receipt.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Query.POCReceipt do 2 | @moduledoc false 3 | import Ecto.Query, warn: false 4 | 5 | alias BlockchainAPI.{Repo, Schema.POCReceipt} 6 | 7 | def list(_) do 8 | POCReceipt 9 | |> Repo.replica.all() 10 | end 11 | 12 | def create(attrs \\ %{}) do 13 | %POCReceipt{} 14 | |> POCReceipt.changeset(attrs) 15 | |> Repo.insert() 16 | end 17 | 18 | def list_for(hotspot_address) do 19 | POCReceipt 20 | |> where([p], p.gateway == ^hotspot_address) 21 | |> select([p], map(p, [:timestamp, :signal, :origin])) 22 | |> Repo.replica.all() 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/blockchain_api/query/poc_witness.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Query.POCWitness do 2 | @moduledoc false 3 | import Ecto.Query, warn: false 4 | 5 | alias BlockchainAPI.{Repo, Schema.POCWitness} 6 | 7 | def list(_) do 8 | POCWitness 9 | |> Repo.replica.all() 10 | end 11 | 12 | def create(attrs \\ %{}) do 13 | %POCWitness{} 14 | |> POCWitness.changeset(attrs) 15 | |> Repo.insert() 16 | end 17 | 18 | def list_for(hotspot_address) do 19 | POCWitness 20 | |> where([w], w.gateway == ^hotspot_address) 21 | |> select([w], map(w, [:timestamp, :signal, :distance])) 22 | |> Repo.replica.all() 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/blockchain_api/query/reward_txn.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Query.RewardTxn do 2 | @moduledoc false 3 | import Ecto.Query, warn: false 4 | 5 | alias BlockchainAPI.{ 6 | Repo, 7 | Schema.RewardTxn, 8 | Schema.Transaction, 9 | Schema.RewardsTransaction, 10 | Schema.Block} 11 | 12 | def create(attrs \\ %{}) do 13 | %RewardTxn{} 14 | |> RewardTxn.changeset(attrs) 15 | |> Repo.insert() 16 | end 17 | 18 | def get!(rewards_hash, account, type) do 19 | from( 20 | r in RewardTxn, 21 | left_join: t in Transaction, 22 | on: t.hash == ^rewards_hash, 23 | left_join: b in Block, 24 | on: t.block_height == b.height, 25 | where: r.rewards_hash == ^rewards_hash, 26 | where: r.account == ^account, 27 | where: r.type == ^type, 28 | distinct: t.block_height, 29 | select: %{ 30 | block_height: t.block_height, 31 | block_time: b.time, 32 | account: r.account, 33 | gateway: r.gateway, 34 | amount: r.amount, 35 | type: r.type, 36 | rewards_hash: t.hash 37 | } 38 | ) 39 | |> Repo.replica.one!() 40 | end 41 | 42 | def get_from_last_week do 43 | Timex.now() 44 | |> Timex.shift(days: -7) 45 | |> get_after() 46 | end 47 | 48 | def get_after(time) do 49 | from( 50 | rt in RewardTxn, 51 | where: rt.inserted_at > ^time, 52 | group_by: rt.account, 53 | select: %{ 54 | account: rt.account, 55 | amount: sum(rt.amount) 56 | } 57 | ) 58 | |> Repo.replica.all() 59 | end 60 | 61 | def total_by_epoch(hash) do 62 | from( 63 | t in RewardsTransaction, 64 | left_join: r in RewardTxn, 65 | on: r.rewards_hash == t.hash, 66 | where: t.hash == ^hash, 67 | select: sum(r.amount) 68 | ) 69 | |> Repo.replica.one() 70 | end 71 | 72 | end 73 | -------------------------------------------------------------------------------- /lib/blockchain_api/query/rewards_transaction.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Query.RewardsTransaction do 2 | @moduledoc false 3 | import Ecto.Query, warn: false 4 | 5 | alias BlockchainAPI.{ 6 | Repo, 7 | Schema.RewardsTransaction, 8 | Schema.RewardTxn, 9 | Schema.Transaction, 10 | Util 11 | } 12 | 13 | def create(attrs \\ %{}) do 14 | %RewardsTransaction{} 15 | |> RewardsTransaction.changeset(attrs) 16 | |> Repo.insert() 17 | end 18 | 19 | def get!(hash) do 20 | RewardsTransaction 21 | |> where([rewards_txn], rewards_txn.hash == ^hash) 22 | |> preload([:reward_txns]) 23 | |> Repo.replica.one!() 24 | end 25 | 26 | def list(_params) do 27 | from(r in RewardsTransaction, preload: [:reward_txns]) |> Repo.replica.all() 28 | end 29 | 30 | def list_for(account_addr) do 31 | from( 32 | r in RewardsTransaction, 33 | left_join: rt in RewardTxn, 34 | on: r.hash == rt.rewards_hash, 35 | left_join: t in Transaction, 36 | on: r.hash == t.hash, 37 | where: rt.account == ^account_addr, 38 | order_by: [desc: rt.id], 39 | select: %{ 40 | hotspot: rt.gateway, 41 | account: rt.account, 42 | amount: rt.amount, 43 | type: rt.type, 44 | hash: t.hash, 45 | height: t.block_height 46 | } 47 | ) 48 | |> Repo.replica.all() 49 | |> encode() 50 | end 51 | 52 | defp encode([]), do: [] 53 | defp encode(list) do 54 | list |> Enum.map(&encode_entry/1) 55 | end 56 | 57 | defp encode_entry(%{hotspot: g, account: a, hash: h}=map) do 58 | %{map | hotspot: Util.bin_to_string(g), account: Util.bin_to_string(a), hash: Util.bin_to_string(h)} 59 | end 60 | 61 | end 62 | -------------------------------------------------------------------------------- /lib/blockchain_api/query/security_exchange_transaction.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Query.SecurityExchangeTransaction do 2 | @moduledoc false 3 | import Ecto.Query, warn: false 4 | 5 | alias BlockchainAPI.{Repo, Schema.SecurityExchangeTransaction} 6 | 7 | def list(_params) do 8 | SecurityExchangeTransaction 9 | |> order_by([se], desc: se.id) 10 | |> Repo.replica.all() 11 | end 12 | 13 | def get!(hash) do 14 | SecurityExchangeTransaction 15 | |> where([se], se.hash == ^hash) 16 | |> Repo.replica.one!() 17 | end 18 | 19 | def create(attrs \\ %{}) do 20 | %SecurityExchangeTransaction{} 21 | |> SecurityExchangeTransaction.changeset(attrs) 22 | |> Repo.insert() 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/blockchain_api/query/security_transaction.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Query.SecurityTransaction do 2 | @moduledoc false 3 | import Ecto.Query, warn: false 4 | 5 | alias BlockchainAPI.{Repo, Schema.SecurityTransaction} 6 | 7 | def list(_params) do 8 | SecurityTransaction 9 | |> order_by([ct], desc: ct.id) 10 | |> Repo.replica.all() 11 | end 12 | 13 | def get_balance(address) do 14 | res = 15 | SecurityTransaction 16 | |> where([ct], ct.payee == ^address) 17 | |> order_by([ct], desc: ct.id) 18 | |> limit(1) 19 | |> Repo.replica.one() 20 | 21 | case res do 22 | nil -> 0 23 | s -> s.amount 24 | end 25 | end 26 | 27 | def get!(hash) do 28 | SecurityTransaction 29 | |> where([ct], ct.hash == ^hash) 30 | |> Repo.replica.one!() 31 | end 32 | 33 | def create(attrs \\ %{}) do 34 | %SecurityTransaction{} 35 | |> SecurityTransaction.changeset(attrs) 36 | |> Repo.insert() 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/blockchain_api/repos.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo do 2 | use Ecto.Repo, 3 | otp_app: :blockchain_api, 4 | adapter: Ecto.Adapters.Postgres, 5 | loggers: [{Ecto.LogEntry, :log, [:debug]}] 6 | 7 | @replica Application.get_env(:blockchain_api, :repos)[:replica] 8 | 9 | def replica() do 10 | @replica 11 | end 12 | end 13 | 14 | defmodule BlockchainAPI.RORepo do 15 | use Ecto.Repo, 16 | otp_app: :blockchain_api, 17 | adapter: Ecto.Adapters.Postgres, 18 | loggers: [{Ecto.LogEntry, :log, [:debug]}] 19 | end 20 | -------------------------------------------------------------------------------- /lib/blockchain_api/schema/account.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Schema.Account do 2 | use Ecto.Schema 3 | import Ecto.Changeset 4 | alias BlockchainAPI.{Util, Schema.Account} 5 | @attrs [:address, :name, :balance, :fee, :nonce, :security_balance, :security_nonce, :data_credit_balance] 6 | @fields [:id | @attrs] 7 | 8 | @derive {Phoenix.Param, key: :address} 9 | @derive {Jason.Encoder, only: @fields} 10 | schema "accounts" do 11 | field :name, :string, null: true 12 | field :balance, :integer, null: false 13 | field :address, :binary, null: false 14 | field :fee, :integer, null: false, default: 0 15 | field :nonce, :integer, null: false, default: 0 16 | field :security_balance, :integer, null: false, default: 0 17 | field :security_nonce, :integer, null: false, default: 0 18 | field :data_credit_balance, :integer, null: false, default: 0 19 | 20 | timestamps() 21 | end 22 | 23 | @doc false 24 | def changeset(account, attrs) do 25 | account 26 | |> cast(attrs, @attrs) 27 | |> validate_required([:address, :balance, :fee, :nonce, :security_balance, :security_nonce, :data_credit_balance]) 28 | |> unique_constraint(:address, name: :unique_account_address) 29 | end 30 | 31 | def encode_model(account) do 32 | %{ 33 | Map.take(account, @fields) 34 | | address: Util.bin_to_string(account.address) 35 | } 36 | end 37 | 38 | defimpl Jason.Encoder, for: Account do 39 | def encode(account, opts) do 40 | account 41 | |> Account.encode_model() 42 | |> Jason.Encode.map(opts) 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/blockchain_api/schema/account_balance.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Schema.AccountBalance do 2 | use Ecto.Schema 3 | import Ecto.Changeset 4 | alias BlockchainAPI.{Schema.AccountBalance, Util} 5 | @fields [:id, :account_address, :block_height, :block_time, :balance, :delta] 6 | 7 | @derive {Jason.Encoder, only: @fields} 8 | schema "account_balances" do 9 | field :account_address, :binary, null: false 10 | field :block_height, :integer, null: false 11 | field :block_time, :integer, null: false 12 | field :balance, :integer, null: false 13 | field :delta, :integer, null: false 14 | 15 | timestamps() 16 | end 17 | 18 | @doc false 19 | def changeset(account_balance, attrs) do 20 | account_balance 21 | |> cast(attrs, [:account_address, :block_height, :block_time, :balance, :delta]) 22 | |> validate_required([:account_address, :block_height, :block_time, :balance, :delta]) 23 | 24 | # |> unique_constraint(:unique_account_height_balance, name: :unique_account_height_balance) 25 | end 26 | 27 | def encode_model(account_balance) do 28 | %{ 29 | Map.take(account_balance, @fields) 30 | | account_address: Util.bin_to_string(account_balance.address) 31 | } 32 | end 33 | 34 | defimpl Jason.Encoder, for: AccountBalance do 35 | def encode(account_balance, opts) do 36 | account_balance 37 | |> AccountBalance.encode_model() 38 | |> Jason.Encode.map(opts) 39 | end 40 | end 41 | 42 | def map(address, balance, block, delta) do 43 | %{ 44 | account_address: address, 45 | block_height: :blockchain_block.height(block), 46 | block_time: :blockchain_block.time(block), 47 | balance: balance, 48 | delta: delta 49 | } 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/blockchain_api/schema/block.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Schema.Block do 2 | use Ecto.Schema 3 | import Ecto.Changeset 4 | alias BlockchainAPI.{Util, Schema.Block, Schema.Transaction} 5 | @fields [:hash, :round, :time, :height] 6 | 7 | @derive {Phoenix.Param, key: :height} 8 | @derive {Jason.Encoder, only: @fields} 9 | schema "blocks" do 10 | field :hash, :binary, null: false 11 | field :round, :integer, null: false 12 | field :time, :integer, null: false 13 | field :height, :integer, null: false 14 | 15 | has_many :transactions, Transaction, foreign_key: :block_height, references: :height 16 | 17 | timestamps() 18 | end 19 | 20 | @doc false 21 | def changeset(block, attrs) do 22 | block 23 | |> cast(attrs, [:hash, :height, :round, :time]) 24 | |> validate_required([:hash, :height, :round, :time]) 25 | |> unique_constraint(:height, name: :unique_block_height) 26 | |> unique_constraint(:time, name: :unique_block_time) 27 | end 28 | 29 | def encode_model(%{"hash" => hash, "round" => round, "time" => time, "height" => height}) do 30 | %{ 31 | hash: Util.bin_to_string(hash), 32 | round: round, 33 | time: time, 34 | height: height 35 | } 36 | end 37 | 38 | def encode_model(block) do 39 | %{ 40 | Map.take(block, @fields) 41 | | hash: Util.bin_to_string(block.hash) 42 | } 43 | end 44 | 45 | defimpl Jason.Encoder, for: Block do 46 | def encode(block, opts) do 47 | block 48 | |> Block.encode_model() 49 | |> Jason.Encode.map(opts) 50 | end 51 | end 52 | 53 | def map(block) do 54 | %{ 55 | hash: :blockchain_block.hash_block(block), 56 | height: :blockchain_block.height(block), 57 | time: :blockchain_block.time(block), 58 | round: :blockchain_block.hbbft_round(block) 59 | } 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/blockchain_api/schema/coinbase_transaction.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Schema.CoinbaseTransaction do 2 | use Ecto.Schema 3 | import Ecto.Changeset 4 | alias BlockchainAPI.{Util, Schema.CoinbaseTransaction} 5 | @fields [:id, :hash, :amount, :payee, :height, :time] 6 | 7 | @derive {Phoenix.Param, key: :hash} 8 | @derive {Jason.Encoder, only: @fields} 9 | schema "coinbase_transactions" do 10 | field :hash, :binary, null: false 11 | field :status, :string, null: false, default: "cleared" 12 | field :amount, :integer, null: false 13 | field :payee, :binary, null: false 14 | 15 | timestamps() 16 | end 17 | 18 | @doc false 19 | def changeset(coinbase, attrs) do 20 | coinbase 21 | |> cast(attrs, [:hash, :amount, :payee, :hash, :status]) 22 | |> validate_required([:hash, :amount, :payee, :hash, :status]) 23 | |> foreign_key_constraint(:hash) 24 | end 25 | 26 | def encode_model(coinbase) do 27 | coinbase 28 | |> Map.take(@fields) 29 | |> Map.merge(%{ 30 | payee: Util.bin_to_string(coinbase.payee), 31 | hash: Util.bin_to_string(coinbase.hash), 32 | type: "coinbase" 33 | }) 34 | end 35 | 36 | defimpl Jason.Encoder, for: CoinbaseTransaction do 37 | def encode(coinbase, opts) do 38 | coinbase 39 | |> CoinbaseTransaction.encode_model() 40 | |> Jason.Encode.map(opts) 41 | end 42 | end 43 | 44 | def map(coinbase) do 45 | %{ 46 | payee: :blockchain_txn_coinbase_v1.payee(coinbase), 47 | amount: :blockchain_txn_coinbase_v1.amount(coinbase), 48 | hash: :blockchain_txn_coinbase_v1.hash(coinbase) 49 | } 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/blockchain_api/schema/consensus_member.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Schema.ConsensusMember do 2 | use Ecto.Schema 3 | import Ecto.Changeset 4 | alias BlockchainAPI.{Util, Schema.ConsensusMember, Schema.ElectionTransaction} 5 | @fields [:address, :election_transactions_id] 6 | 7 | @derive {Jason.Encoder, only: @fields} 8 | schema "consensus_members" do 9 | field :address, :binary, null: false 10 | field :election_transactions_id, :integer, null: false 11 | 12 | belongs_to :election_transactions, ElectionTransaction, define_field: false, foreign_key: :id 13 | timestamps() 14 | end 15 | 16 | @doc false 17 | def changeset(member, attrs) do 18 | member 19 | |> cast(attrs, @fields) 20 | |> validate_required(@fields) 21 | |> foreign_key_constraint(:election_transactions_id) 22 | end 23 | 24 | def encode_model(member) do 25 | member 26 | |> Map.take(@fields) 27 | |> Map.merge(%{ 28 | address: Util.bin_to_string(member.address) 29 | }) 30 | end 31 | 32 | defimpl Jason.Encoder, for: ConsensusMember do 33 | def encode(member, opts) do 34 | member 35 | |> ConsensusMember.encode_model() 36 | |> Jason.Encode.map(opts) 37 | end 38 | end 39 | 40 | def map(id, address) do 41 | %{ 42 | election_transactions_id: id, 43 | address: address 44 | } 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/blockchain_api/schema/data_credit_transaction.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Schema.DataCreditTransaction do 2 | use Ecto.Schema 3 | import Ecto.Changeset 4 | alias BlockchainAPI.{Util, Schema.DataCreditTransaction} 5 | @fields [:id, :hash, :amount, :payee, :height, :time] 6 | 7 | @derive {Phoenix.Param, key: :hash} 8 | @derive {Jason.Encoder, only: @fields} 9 | schema "data_credit_transactions" do 10 | field :hash, :binary, null: false 11 | field :status, :string, null: false, default: "cleared" 12 | field :amount, :integer, null: false 13 | field :payee, :binary, null: false 14 | 15 | timestamps() 16 | end 17 | 18 | @doc false 19 | def changeset(data_credit, attrs) do 20 | data_credit 21 | |> cast(attrs, [:hash, :amount, :payee, :status]) 22 | |> validate_required([:hash, :amount, :payee, :status]) 23 | |> foreign_key_constraint(:hash) 24 | end 25 | 26 | def encode_model(data_credit) do 27 | data_credit 28 | |> Map.take(@fields) 29 | |> Map.merge(%{ 30 | payee: Util.bin_to_string(data_credit.payee), 31 | hash: Util.bin_to_string(data_credit.hash), 32 | type: "data_credit" 33 | }) 34 | end 35 | 36 | defimpl Jason.Encoder, for: DataCreditTransaction do 37 | def encode(data_credit, opts) do 38 | data_credit 39 | |> DataCreditTransaction.encode_model() 40 | |> Jason.Encode.map(opts) 41 | end 42 | end 43 | 44 | def map(data_credit) do 45 | %{ 46 | payee: :blockchain_txn_dc_coinbase_v1.payee(data_credit), 47 | amount: :blockchain_txn_dc_coinbase_v1.amount(data_credit), 48 | hash: :blockchain_txn_dc_coinbase_v1.hash(data_credit) 49 | } 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/blockchain_api/schema/election_transaction.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Schema.ElectionTransaction do 2 | use Ecto.Schema 3 | import Ecto.Changeset 4 | 5 | alias BlockchainAPI.{ 6 | Query, 7 | Schema.ConsensusMember, 8 | Schema.ElectionTransaction, 9 | Schema.Transaction, 10 | Util 11 | } 12 | 13 | @fields [:proof, :delay, :election_height, :hash] 14 | 15 | @derive {Phoenix.Param, key: :hash} 16 | @derive {Jason.Encoder, only: @fields} 17 | schema "election_transactions" do 18 | field :hash, :binary, null: false 19 | # Proof is null for genesis block 20 | field :proof, :binary, null: true 21 | field :delay, :integer, null: false 22 | field :election_height, :integer, null: false 23 | 24 | has_many :consensus_members, ConsensusMember, 25 | foreign_key: :election_transactions_id, 26 | references: :id 27 | 28 | belongs_to :transactions, Transaction, define_field: false, foreign_key: :hash 29 | timestamps() 30 | end 31 | 32 | @doc false 33 | def changeset(election, attrs) do 34 | election 35 | |> cast(attrs, @fields) 36 | |> validate_required([:hash, :delay, :election_height]) 37 | |> foreign_key_constraint(:hash) 38 | end 39 | 40 | def encode_model(election) do 41 | election 42 | |> Map.take(@fields) 43 | |> Map.merge(%{ 44 | members: Query.ElectionTransaction.get_consensus_members(election), 45 | proof: Util.bin_to_string(election.proof), 46 | hash: Util.bin_to_string(election.hash), 47 | type: "election" 48 | }) 49 | end 50 | 51 | defimpl Jason.Encoder, for: ElectionTransaction do 52 | def encode(election, opts) do 53 | election 54 | |> ElectionTransaction.encode_model() 55 | |> Jason.Encode.map(opts) 56 | end 57 | end 58 | 59 | def map(election) do 60 | %{ 61 | proof: :blockchain_txn_consensus_group_v1.proof(election), 62 | election_height: :blockchain_txn_consensus_group_v1.height(election), 63 | delay: :blockchain_txn_consensus_group_v1.delay(election), 64 | hash: :blockchain_txn_consensus_group_v1.hash(election) 65 | } 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /lib/blockchain_api/schema/gateway_transaction.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Schema.GatewayTransaction do 2 | use Ecto.Schema 3 | import Ecto.Changeset 4 | alias BlockchainAPI.{Util, Schema.GatewayTransaction} 5 | @fields [:id, :hash, :gateway, :owner, :payer, :fee, :staking_fee, :height, :time] 6 | 7 | @derive {Phoenix.Param, key: :hash} 8 | @derive {Jason.Encoder, only: @fields} 9 | schema "gateway_transactions" do 10 | field :hash, :binary, null: false 11 | field :status, :string, null: false, default: "cleared" 12 | field :gateway, :binary, null: false 13 | field :owner, :binary, null: false 14 | field :payer, :binary, null: true 15 | field :fee, :integer, null: false, default: 0 16 | field :staking_fee, :integer, null: false, default: 1 17 | 18 | timestamps() 19 | end 20 | 21 | @doc false 22 | def changeset(gateway, attrs) do 23 | gateway 24 | |> cast(attrs, [:hash, :owner, :payer, :gateway, :fee, :staking_fee, :status]) 25 | |> validate_required([:hash, :owner, :gateway, :fee, :staking_fee, :status]) 26 | |> foreign_key_constraint(:hash) 27 | |> unique_constraint(:gateway, name: :unique_gateway) 28 | end 29 | 30 | def encode_model(gateway) do 31 | gateway 32 | |> Map.take(@fields) 33 | |> Map.merge(%{ 34 | owner: Util.bin_to_string(gateway.owner), 35 | hash: Util.bin_to_string(gateway.hash), 36 | gateway: Util.bin_to_string(gateway.gateway), 37 | payer: Util.bin_to_string(gateway.payer), 38 | type: "gateway" 39 | }) 40 | end 41 | 42 | defimpl Jason.Encoder, for: GatewayTransaction do 43 | def encode(gateway, opts) do 44 | gateway 45 | |> GatewayTransaction.encode_model() 46 | |> Jason.Encode.map(opts) 47 | end 48 | end 49 | 50 | def map(txn) do 51 | %{ 52 | owner: :blockchain_txn_add_gateway_v1.owner(txn), 53 | gateway: :blockchain_txn_add_gateway_v1.gateway(txn), 54 | fee: :blockchain_txn_add_gateway_v1.fee(txn), 55 | staking_fee: :blockchain_txn_add_gateway_v1.staking_fee(txn), 56 | hash: :blockchain_txn_add_gateway_v1.hash(txn), 57 | payer: :blockchain_txn_add_gateway_v1.payer(txn) 58 | } 59 | end 60 | 61 | def map(:genesis, txn) do 62 | %{ 63 | owner: :blockchain_txn_gen_gateway_v1.owner(txn), 64 | gateway: :blockchain_txn_gen_gateway_v1.gateway(txn), 65 | fee: :blockchain_txn_gen_gateway_v1.fee(txn), 66 | hash: :blockchain_txn_gen_gateway_v1.hash(txn) 67 | } 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /lib/blockchain_api/schema/oui_transaction.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Schema.OUITransaction do 2 | use Ecto.Schema 3 | import Ecto.Changeset 4 | alias BlockchainAPI.{Util, Schema.OUITransaction} 5 | 6 | @required_fields [ 7 | :hash, 8 | :owner, 9 | :addresses, 10 | :fee, 11 | :staking_fee, 12 | :status, 13 | :oui 14 | ] 15 | 16 | @fields [:payer, :id | @required_fields] 17 | 18 | @derive {Phoenix.Param, key: :hash} 19 | @derive {Jason.Encoder, only: @fields} 20 | schema "oui_transactions" do 21 | field :hash, :binary, null: false 22 | field :owner, :binary, null: false 23 | field :addresses, {:array, :string}, null: false, default: [] 24 | field :payer, :binary, null: true # payer can be empty 25 | field :fee, :integer, null: false, default: 0 26 | field :staking_fee, :integer, null: false, default: 0 27 | field :oui, :integer, null: false, default: 1 28 | field :status, :string, null: false, default: "cleared" 29 | 30 | timestamps() 31 | end 32 | 33 | @doc false 34 | def changeset(oui, attrs) do 35 | oui 36 | |> cast(attrs, @fields) 37 | |> validate_required(@required_fields) 38 | |> foreign_key_constraint(:hash) 39 | end 40 | 41 | def encode_model(oui) do 42 | oui 43 | |> Map.take(@fields) 44 | |> Map.merge(%{ 45 | hash: Util.bin_to_string(oui.hash), 46 | payer: Util.bin_to_string(oui.payer), 47 | owner: Util.bin_to_string(oui.owner), 48 | type: "oui" 49 | }) 50 | end 51 | 52 | defimpl Jason.Encoder, for: OUITransaction do 53 | def encode(oui, opts) do 54 | oui 55 | |> OUITransaction.encode_model() 56 | |> Jason.Encode.map(opts) 57 | end 58 | end 59 | 60 | def map(txn) do 61 | %{ 62 | hash: :blockchain_txn_oui_v1.hash(txn), 63 | owner: :blockchain_txn_oui_v1.owner(txn), 64 | payer: :blockchain_txn_oui_v1.payer(txn), 65 | txn: :blockchain_txn.serialize(txn), 66 | addresses: :blockchain_txn_oui_v1.addresses(txn), 67 | staking_fee: :blockchain_txn_oui_v1.staking_fee(txn), 68 | fee: :blockchain_txn_oui_v1.fee(txn) 69 | } 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /lib/blockchain_api/schema/payment_transaction.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Schema.PaymentTransaction do 2 | use Ecto.Schema 3 | import Ecto.Changeset 4 | alias BlockchainAPI.{Util, Schema.PaymentTransaction} 5 | @fields [:id, :hash, :amount, :fee, :nonce, :payee, :payer, :height, :time] 6 | 7 | @derive {Phoenix.Param, key: :hash} 8 | @derive {Jason.Encoder, only: @fields} 9 | schema "payment_transactions" do 10 | field :amount, :integer, null: false 11 | field :fee, :integer, null: false 12 | field :nonce, :integer, null: false, default: 0 13 | field :payee, :binary, null: false 14 | field :payer, :binary, null: false 15 | field :hash, :binary, null: false 16 | field :status, :string, null: false, default: "cleared" 17 | 18 | timestamps() 19 | end 20 | 21 | @doc false 22 | def changeset(payment, attrs) do 23 | payment 24 | |> cast(attrs, [:hash, :amount, :payee, :payer, :fee, :nonce, :status]) 25 | |> validate_required([:hash, :amount, :payee, :payer, :fee, :nonce, :status]) 26 | |> foreign_key_constraint(:hash) 27 | |> unique_constraint(:unique_pending_payment, name: :unique_pending_payment) 28 | end 29 | 30 | def encode_model(payment) do 31 | payment 32 | |> Map.take(@fields) 33 | |> Map.merge(%{ 34 | payer: Util.bin_to_string(payment.payer), 35 | payee: Util.bin_to_string(payment.payee), 36 | hash: Util.bin_to_string(payment.hash), 37 | type: "payment" 38 | }) 39 | end 40 | 41 | defimpl Jason.Encoder, for: PaymentTransaction do 42 | def encode(payment, opts) do 43 | payment 44 | |> PaymentTransaction.encode_model() 45 | |> Jason.Encode.map(opts) 46 | end 47 | end 48 | 49 | def map(txn) do 50 | %{ 51 | payee: :blockchain_txn_payment_v1.payee(txn), 52 | payer: :blockchain_txn_payment_v1.payer(txn), 53 | amount: :blockchain_txn_payment_v1.amount(txn), 54 | nonce: :blockchain_txn_payment_v1.nonce(txn), 55 | fee: :blockchain_txn_payment_v1.fee(txn), 56 | hash: :blockchain_txn_payment_v1.hash(txn) 57 | } 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /lib/blockchain_api/schema/pending_coinbase.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Schema.PendingCoinbase do 2 | use Ecto.Schema 3 | import Ecto.Changeset 4 | 5 | alias BlockchainAPI.{Util, Schema.PendingCoinbase} 6 | 7 | @fields [ 8 | :payee, 9 | :hash, 10 | :status, 11 | :amount, 12 | :txn, 13 | :submit_height 14 | ] 15 | 16 | @submit_coinbase_queue :submit_coinbase_queue 17 | 18 | @derive {Jason.Encoder, only: @fields} 19 | schema "pending_coinbases" do 20 | field :hash, :binary, null: false 21 | field :status, :string, null: false, default: "pending" 22 | field :amount, :integer, null: false, default: 0 23 | field :payee, :binary, null: false 24 | field :txn, :binary, null: false 25 | field :submit_height, :integer, null: false, default: 0 26 | 27 | timestamps() 28 | end 29 | 30 | @doc false 31 | def changeset(pending_coinbase, attrs) do 32 | pending_coinbase 33 | |> cast(attrs, @fields) 34 | |> validate_required(@fields) 35 | |> unique_constraint(:unique_pending_coinbase, name: :unique_pending_coinbase) 36 | end 37 | 38 | def encode_model(pending_coinbase) do 39 | pending_coinbase 40 | |> Map.take(@fields) 41 | |> Map.drop([:txn, :submit_height]) 42 | |> Map.merge(%{ 43 | hash: Util.bin_to_string(pending_coinbase.hash), 44 | payee: Util.bin_to_string(pending_coinbase.payee), 45 | type: "coinbase" 46 | }) 47 | end 48 | 49 | defimpl Jason.Encoder, for: PendingCoinbase do 50 | def encode(pending_coinbase, opts) do 51 | pending_coinbase 52 | |> PendingCoinbase.encode_model() 53 | |> Jason.Encode.map(opts) 54 | end 55 | end 56 | 57 | def map(txn, submit_height) do 58 | %{ 59 | hash: :blockchain_txn_coinbase_v1.hash(txn), 60 | amount: :blockchain_txn_coinbase_v1.amount(txn), 61 | status: "pending", 62 | payee: :blockchain_txn_coinbase_v1.payee(txn), 63 | txn: :blockchain_txn.serialize(txn), 64 | submit_height: submit_height 65 | } 66 | end 67 | 68 | def submit_coinbase_queue, do: @submit_coinbase_queue 69 | end 70 | -------------------------------------------------------------------------------- /lib/blockchain_api/schema/pending_oui.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Schema.PendingOUI do 2 | use Ecto.Schema 3 | import Ecto.Changeset 4 | import Honeydew.EctoPollQueue.Schema 5 | 6 | alias BlockchainAPI.{Util, Schema.PendingOUI} 7 | 8 | @required [ 9 | :hash, 10 | :owner, 11 | :addresses, 12 | :staking_fee, 13 | :fee, 14 | :txn, 15 | :submit_height, 16 | :status, 17 | :oui 18 | ] 19 | 20 | @fields [:payer | @required] 21 | 22 | @submit_oui_queue :submit_oui_queue 23 | 24 | @derive {Jason.Encoder, only: @fields} 25 | schema "pending_ouis" do 26 | field :hash, :binary, null: false 27 | field :owner, :binary, null: false 28 | field :addresses, {:array, :string}, null: false, default: [] 29 | field :payer, :binary, null: true # payer can be empty 30 | field :fee, :integer, null: false, default: 0 31 | field :staking_fee, :integer, null: false, default: 0 32 | field :oui, :integer, null: false, default: 1 33 | field :txn, :binary, null: false 34 | field :submit_height, :integer, null: false, default: 0 35 | field :status, :string, null: false, default: "pending" 36 | 37 | honeydew_fields(@submit_oui_queue) 38 | 39 | timestamps() 40 | end 41 | 42 | @doc false 43 | def changeset(pending_oui, attrs) do 44 | pending_oui 45 | |> cast(attrs, @fields) 46 | |> validate_required(@required) 47 | end 48 | 49 | def encode_model(pending_oui) do 50 | pending_oui 51 | |> Map.take(@fields) 52 | |> Map.drop([:txn, :submit_height]) 53 | |> Map.merge(%{ 54 | hash: Util.bin_to_string(pending_oui.hash), 55 | owner: Util.bin_to_string(pending_oui.owner), 56 | payer: Util.bin_to_string(pending_oui.payer), 57 | type: "oui" 58 | }) 59 | end 60 | 61 | defimpl Jason.Encoder, for: PendingOUI do 62 | def encode(pending_oui, opts) do 63 | pending_oui 64 | |> PendingOUI.encode_model() 65 | |> Jason.Encode.map(opts) 66 | end 67 | end 68 | 69 | def map(txn, submit_height) do 70 | %{ 71 | hash: :blockchain_txn_oui_v1.hash(txn), 72 | owner: :blockchain_txn_oui_v1.owner(txn), 73 | payer: :blockchain_txn_oui_v1.payer(txn), 74 | txn: :blockchain_txn.serialize(txn), 75 | addresses: :blockchain_txn_oui_v1.addresses(txn), 76 | staking_fee: :blockchain_txn_oui_v1.staking_fee(txn), 77 | fee: :blockchain_txn_oui_v1.fee(txn), 78 | submit_height: submit_height, 79 | status: "pending" 80 | } 81 | end 82 | 83 | def submit_oui_queue, do: @submit_oui_queue 84 | end 85 | -------------------------------------------------------------------------------- /lib/blockchain_api/schema/reward_txn.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Schema.RewardTxn do 2 | use Ecto.Schema 3 | import Ecto.Changeset 4 | 5 | alias BlockchainAPI.{ 6 | Util, 7 | Schema.RewardsTransaction, 8 | Schema.RewardTxn 9 | } 10 | 11 | @fields [ 12 | :rewards_hash, 13 | :account, 14 | :gateway, 15 | :amount, 16 | :type 17 | ] 18 | 19 | @encoded_fields @fields ++ [:id] 20 | 21 | @derive {Jason.Encoder, only: @fields} 22 | schema "reward_txns" do 23 | field :account, :binary, null: false 24 | field :gateway, :binary, null: true 25 | field :amount, :integer, null: false 26 | field :rewards_hash, :binary, null: false 27 | field :type, :string, null: false 28 | 29 | belongs_to :rewards_transactions, RewardsTransaction, define_field: false, foreign_key: :hash 30 | 31 | timestamps() 32 | end 33 | 34 | @doc false 35 | def changeset(reward, attrs \\ %{}) do 36 | reward 37 | |> cast(attrs, @fields) 38 | |> validate_required([:account, :rewards_hash, :type, :amount]) 39 | |> foreign_key_constraint(:rewards_hash) 40 | end 41 | 42 | def encode_model(reward) do 43 | reward 44 | |> Map.take(@encoded_fields) 45 | |> Map.merge(%{ 46 | rewards_hash: Util.bin_to_string(reward.rewards_hash), 47 | account: Util.bin_to_string(reward.account), 48 | gateway: Util.bin_to_string(reward.gateway) 49 | }) 50 | end 51 | 52 | def map(rewards_hash, reward_txn) do 53 | gateway = 54 | case :blockchain_txn_reward_v1.gateway(reward_txn) do 55 | :undefined -> nil 56 | gw -> gw 57 | end 58 | 59 | %{ 60 | rewards_hash: rewards_hash, 61 | account: :blockchain_txn_reward_v1.account(reward_txn), 62 | gateway: gateway, 63 | amount: :blockchain_txn_reward_v1.amount(reward_txn), 64 | type: "#{Atom.to_string(:blockchain_txn_reward_v1.type(reward_txn))}_reward" 65 | } 66 | end 67 | 68 | defimpl Jason.Encoder, for: RewardTxn do 69 | def encode(reward, opts) do 70 | reward 71 | |> RewardTxn.encode_model() 72 | |> Jason.Encode.map(opts) 73 | end 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /lib/blockchain_api/schema/rewards_transaction.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Schema.RewardsTransaction do 2 | use Ecto.Schema 3 | import Ecto.Changeset 4 | alias BlockchainAPI.{Util, Schema.RewardsTransaction, Schema.RewardTxn} 5 | @fields [:id, :hash, :fee] 6 | 7 | @derive {Phoenix.Param, key: :hash} 8 | @derive {Jason.Encoder, only: @fields} 9 | schema "rewards_transactions" do 10 | field :hash, :binary, null: false 11 | field :fee, :integer, null: false 12 | 13 | has_many :reward_txns, RewardTxn, foreign_key: :rewards_hash, references: :hash 14 | 15 | timestamps() 16 | end 17 | 18 | @doc false 19 | def changeset(rewards, attrs) do 20 | rewards 21 | |> cast(attrs, [:hash, :fee]) 22 | |> validate_required([:hash, :fee]) 23 | |> foreign_key_constraint(:hash) 24 | end 25 | 26 | def encode_model(rewards) do 27 | rewards 28 | |> Map.take(@fields) 29 | |> Map.merge(%{ 30 | hash: Util.bin_to_string(rewards.hash), 31 | type: "rewards" 32 | }) 33 | end 34 | 35 | defimpl Jason.Encoder, for: RewardsTransaction do 36 | def encode(rewards, opts) do 37 | rewards 38 | |> RewardsTransaction.encode_model() 39 | |> Jason.Encode.map(opts) 40 | end 41 | end 42 | 43 | def map(rewards_txn) do 44 | %{ 45 | fee: :blockchain_txn_rewards_v1.fee(rewards_txn), 46 | hash: :blockchain_txn_rewards_v1.hash(rewards_txn) 47 | } 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/blockchain_api/schema/security_exchange_transaction.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Schema.SecurityExchangeTransaction do 2 | use Ecto.Schema 3 | import Ecto.Changeset 4 | alias BlockchainAPI.{Util, Schema.SecurityExchangeTransaction} 5 | 6 | @required [ 7 | :hash, 8 | :payer, 9 | :payee, 10 | :amount, 11 | :fee, 12 | :nonce, 13 | :signature, 14 | :status 15 | ] 16 | 17 | @fields [:id] ++ @required 18 | 19 | @derive {Phoenix.Param, key: :hash} 20 | @derive {Jason.Encoder, only: @fields} 21 | schema "security_exchange_transactions" do 22 | field :hash, :binary, null: false 23 | field :status, :string, null: false, default: "cleared" 24 | field :amount, :integer, null: false 25 | field :payee, :binary, null: false 26 | field :fee, :integer, null: false 27 | field :payer, :binary, null: false 28 | field :signature, :binary, null: false 29 | field :nonce, :integer, null: false 30 | 31 | timestamps() 32 | end 33 | 34 | @doc false 35 | def changeset(security, attrs) do 36 | security 37 | |> cast(attrs, @required) 38 | |> validate_required(@required) 39 | |> foreign_key_constraint(:hash) 40 | end 41 | 42 | def encode_model(security_exchange) do 43 | security_exchange 44 | |> Map.take(@fields) 45 | |> Map.merge(%{ 46 | payee: Util.bin_to_string(security_exchange.payee), 47 | hash: Util.bin_to_string(security_exchange.hash), 48 | payer: Util.bin_to_string(security_exchange.payer), 49 | signature: Util.bin_to_string(security_exchange.signature), 50 | type: "security_exchange" 51 | }) 52 | end 53 | 54 | defimpl Jason.Encoder, for: SecurityExchangeTransaction do 55 | def encode(security_exchange, opts) do 56 | security_exchange 57 | |> SecurityExchangeTransaction.encode_model() 58 | |> Jason.Encode.map(opts) 59 | end 60 | end 61 | 62 | def map(security_exchange) do 63 | %{ 64 | payee: :blockchain_txn_security_exchange_v1.payee(security_exchange), 65 | payer: :blockchain_txn_security_exchange_v1.payer(security_exchange), 66 | amount: :blockchain_txn_security_exchange_v1.amount(security_exchange), 67 | signature: :blockchain_txn_security_exchange_v1.signature(security_exchange), 68 | nonce: :blockchain_txn_security_exchange_v1.nonce(security_exchange), 69 | hash: :blockchain_txn_security_exchange_v1.hash(security_exchange), 70 | fee: :blockchain_txn_security_exchange_v1.fee(security_exchange) 71 | } 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /lib/blockchain_api/schema/security_transaction.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Schema.SecurityTransaction do 2 | use Ecto.Schema 3 | import Ecto.Changeset 4 | alias BlockchainAPI.{Util, Schema.SecurityTransaction} 5 | @fields [:id, :hash, :amount, :payee, :height, :time] 6 | 7 | @derive {Phoenix.Param, key: :hash} 8 | @derive {Jason.Encoder, only: @fields} 9 | schema "security_transactions" do 10 | field :hash, :binary, null: false 11 | field :status, :string, null: false, default: "cleared" 12 | field :amount, :integer, null: false 13 | field :payee, :binary, null: false 14 | 15 | timestamps() 16 | end 17 | 18 | @doc false 19 | def changeset(security, attrs) do 20 | security 21 | |> cast(attrs, [:hash, :amount, :payee, :hash, :status]) 22 | |> validate_required([:hash, :amount, :payee, :hash, :status]) 23 | |> foreign_key_constraint(:hash) 24 | end 25 | 26 | def encode_model(security) do 27 | security 28 | |> Map.take(@fields) 29 | |> Map.merge(%{ 30 | payee: Util.bin_to_string(security.payee), 31 | hash: Util.bin_to_string(security.hash), 32 | type: "security" 33 | }) 34 | end 35 | 36 | defimpl Jason.Encoder, for: SecurityTransaction do 37 | def encode(security, opts) do 38 | security 39 | |> SecurityTransaction.encode_model() 40 | |> Jason.Encode.map(opts) 41 | end 42 | end 43 | 44 | def map(security) do 45 | %{ 46 | payee: :blockchain_txn_security_coinbase_v1.payee(security), 47 | amount: :blockchain_txn_security_coinbase_v1.amount(security), 48 | hash: :blockchain_txn_security_coinbase_v1.hash(security) 49 | } 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/blockchain_api_web.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb do 2 | @moduledoc """ 3 | The entrypoint for defining your web interface, such 4 | as controllers, views, channels and so on. 5 | 6 | This can be used in your application as: 7 | 8 | use BlockchainAPIWeb, :controller 9 | use BlockchainAPIWeb, :view 10 | 11 | The definitions below will be executed for every view, 12 | controller, etc, so keep them short and clean, focused 13 | on imports, uses and aliases. 14 | 15 | Do NOT define functions inside the quoted expressions 16 | below. Instead, define any helper function in modules 17 | and import those modules here. 18 | """ 19 | 20 | def controller do 21 | quote do 22 | use Phoenix.Controller, namespace: BlockchainAPIWeb 23 | 24 | import Plug.Conn 25 | import BlockchainAPIWeb.Gettext 26 | alias BlockchainAPIWeb.Router.Helpers, as: Routes 27 | end 28 | end 29 | 30 | def view do 31 | quote do 32 | use Phoenix.View, 33 | root: "lib/blockchain_api_web/templates", 34 | namespace: BlockchainAPIWeb 35 | 36 | # Import convenience functions from controllers 37 | import Phoenix.Controller, only: [get_flash: 1, get_flash: 2, view_module: 1] 38 | 39 | import BlockchainAPIWeb.ErrorHelpers 40 | import BlockchainAPIWeb.Gettext 41 | alias BlockchainAPIWeb.Router.Helpers, as: Routes 42 | end 43 | end 44 | 45 | def router do 46 | quote do 47 | use Phoenix.Router 48 | import Plug.Conn 49 | import Phoenix.Controller 50 | end 51 | end 52 | 53 | def channel do 54 | quote do 55 | use Phoenix.Channel 56 | import BlockchainAPIWeb.Gettext 57 | end 58 | end 59 | 60 | @doc """ 61 | When used, dispatch to the appropriate controller/view/etc. 62 | """ 63 | defmacro __using__(which) when is_atom(which) do 64 | apply(__MODULE__, which, []) 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/channels/account_channel.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.AccountChannel do 2 | use BlockchainAPIWeb, :channel 3 | alias BlockchainAPI.Schema.Account 4 | alias BlockchainAPI.Util 5 | 6 | def join("account:" <> account_address, _params, socket) do 7 | {:ok, %{channel: "account:#{account_address}"}, 8 | assign(socket, :account_address, account_address)} 9 | end 10 | 11 | def broadcast_change(account) do 12 | payload = account |> Account.encode_model() 13 | 14 | BlockchainAPIWeb.Endpoint.broadcast( 15 | "account:#{Util.bin_to_string(account.address)}", 16 | "change", 17 | payload 18 | ) 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/channels/account_socket.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.AccountSocket do 2 | use Phoenix.Socket 3 | 4 | channel "block:*", BlockchainAPIWeb.BlockChannel 5 | channel "account:*", BlockchainAPIWeb.AccountChannel 6 | 7 | def connect(_params, socket, _connect_info) do 8 | {:ok, socket} 9 | end 10 | 11 | def id(_socket), do: nil 12 | end 13 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/channels/block_channel.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.BlockChannel do 2 | use BlockchainAPIWeb, :channel 3 | alias BlockchainAPI.Schema.Block 4 | 5 | def join("block:update", _params, socket) do 6 | {:ok, %{data: "joined block:update"}, socket} 7 | end 8 | 9 | def broadcast_change(block) do 10 | payload = block |> Block.encode_model() 11 | BlockchainAPIWeb.Endpoint.broadcast("block:update", "change", payload) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/controllers/account_gateway_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.AccountGatewayController do 2 | use BlockchainAPIWeb, :controller 3 | 4 | alias BlockchainAPI.{Util, Query} 5 | import BlockchainAPI.Cache.CacheService 6 | require Logger 7 | 8 | action_fallback BlockchainAPIWeb.FallbackController 9 | 10 | def index(conn, %{"account_address" => address} = params) do 11 | account_gateways = 12 | address 13 | |> Util.string_to_bin() 14 | |> Query.AccountTransaction.get_gateways(params) 15 | 16 | conn 17 | |> put_cache_headers(ttl: :short, key: "block") 18 | |> render("index.json", account_gateways: account_gateways) 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/controllers/account_reward_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.AccountRewardController do 2 | use BlockchainAPIWeb, :controller 3 | 4 | alias BlockchainAPI.{Util, Query} 5 | import BlockchainAPI.Cache.CacheService 6 | require Logger 7 | 8 | action_fallback BlockchainAPIWeb.FallbackController 9 | 10 | def index(conn, %{"account_address" => address} = _params) do 11 | account_rewards = 12 | address 13 | |> Util.string_to_bin() 14 | |> Query.RewardsTransaction.list_for() 15 | 16 | conn 17 | |> put_cache_headers(ttl: :short, key: "block") 18 | |> render("index.json", account_rewards: account_rewards) 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/controllers/account_transaction_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.AccountTransactionController do 2 | use BlockchainAPIWeb, :controller 3 | 4 | alias BlockchainAPI.{Util, Query} 5 | import BlockchainAPI.Cache.CacheService 6 | require Logger 7 | 8 | action_fallback BlockchainAPIWeb.FallbackController 9 | 10 | def index(conn, %{"account_address" => address} = params) do 11 | bin_address = address |> Util.string_to_bin() 12 | 13 | txns = bin_address |> Query.AccountTransaction.list(params) 14 | 15 | conn 16 | |> put_cache_headers(ttl: :never) 17 | |> render("index.json", account_transactions: txns) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/controllers/activity_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.ActivityController do 2 | use BlockchainAPIWeb, :controller 3 | 4 | alias BlockchainAPI.{Util, Query} 5 | 6 | import BlockchainAPI.Cache.CacheService 7 | 8 | action_fallback BlockchainAPIWeb.FallbackController 9 | 10 | def index(conn, %{"hotspot_address" => address} = params) do 11 | activity = 12 | address 13 | |> Util.string_to_bin() 14 | |> Query.HotspotActivity.list(params) 15 | 16 | conn 17 | |> put_cache_headers(ttl: :short, key: "block") 18 | |> render("index.json", activity: activity) 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/controllers/block_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.BlockController do 2 | use BlockchainAPIWeb, :controller 3 | 4 | alias BlockchainAPI.Query 5 | import BlockchainAPI.Cache.CacheService 6 | 7 | action_fallback BlockchainAPIWeb.FallbackController 8 | 9 | def index(conn, params) do 10 | blocks = Query.Block.list(params) 11 | 12 | conn 13 | |> put_cache_headers(ttl: :short, key: "block") 14 | |> render("index.json", blocks: blocks) 15 | end 16 | 17 | def show(conn, %{"height" => height}) do 18 | block = Query.Block.get(height) 19 | conn 20 | |> put_cache_headers(ttl: :long, key: "eternal") 21 | |> render("show.json", block: block) 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/controllers/challenge_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.ChallengeController do 2 | use BlockchainAPIWeb, :controller 3 | 4 | alias BlockchainAPI.Query 5 | import BlockchainAPI.Cache.CacheService 6 | 7 | action_fallback BlockchainAPIWeb.FallbackController 8 | 9 | def index(conn, params) do 10 | challenges = Query.POCReceiptsTransaction.list(params) 11 | total_ongoing = Query.Transaction.get_ongoing_poc_requests() 12 | issued = Query.POCReceiptsTransaction.issued() 13 | {successful, failed} = Query.POCReceiptsTransaction.aggregate_challenges(challenges) 14 | 15 | conn 16 | |> put_cache_headers(ttl: :short, key: "block") 17 | |> render( 18 | "index.json", 19 | challenges: challenges, 20 | total_ongoing: total_ongoing, 21 | issued: issued, 22 | successful: successful, 23 | failed: failed 24 | ) 25 | end 26 | 27 | def show(conn, %{"id" => id}) do 28 | challenge = Query.POCReceiptsTransaction.show(id) 29 | 30 | conn 31 | |> put_cache_headers(ttl: :long, key: "eternal") 32 | |> render("show.json", challenge: challenge) 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/controllers/coinbase_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.CoinbaseController do 2 | use BlockchainAPIWeb, :controller 3 | 4 | alias BlockchainAPI.{Util, Query} 5 | import BlockchainAPI.Cache.CacheService 6 | 7 | action_fallback BlockchainAPIWeb.FallbackController 8 | 9 | def index(conn, params) do 10 | txns = Query.CoinbaseTransaction.list(params) 11 | 12 | conn 13 | |> put_cache_headers(ttl: :long, key: "eternal") 14 | |> render("index.json", coinbase_transactions: txns) 15 | end 16 | 17 | def show(conn, %{"hash" => hash}) do 18 | coinbase = 19 | hash 20 | |> Util.string_to_bin() 21 | |> Query.CoinbaseTransaction.get!() 22 | 23 | conn 24 | |> put_cache_headers(ttl: :long, key: "eternal") 25 | |> render("show.json", coinbase: coinbase) 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/controllers/election_transaction_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.ElectionTransactionController do 2 | use BlockchainAPIWeb, :controller 3 | 4 | alias BlockchainAPI.Query 5 | import BlockchainAPI.Cache.CacheService 6 | 7 | action_fallback BlockchainAPIWeb.FallbackController 8 | 9 | def index(conn, params) do 10 | elections = Query.ElectionTransaction.list(params) 11 | conn 12 | |> put_cache_headers(ttl: :short, key: "block") 13 | |> render("index.json", elections: elections) 14 | end 15 | 16 | def show(conn, %{"hash" => hash}) do 17 | election = 18 | case Query.ElectionTransaction.get_consensus_group(hash) do 19 | nil -> %{} 20 | election -> election 21 | end 22 | 23 | conn 24 | |> put_cache_headers(ttl: :short, key: "block") 25 | |> render("show.json", election: election) 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/controllers/fallback_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.FallbackController do 2 | @moduledoc """ 3 | Translates controller action results into valid `Plug.Conn` responses. 4 | 5 | See `Phoenix.Controller.action_fallback/1` for more details. 6 | """ 7 | use BlockchainAPIWeb, :controller 8 | 9 | def call(conn, {:error, %Ecto.Changeset{} = changeset}) do 10 | conn 11 | |> put_status(:unprocessable_entity) 12 | |> put_view(BlockchainAPIWeb.ChangesetView) 13 | |> render("error.json", changeset: changeset) 14 | end 15 | 16 | def call(conn, {:error, :not_found}) do 17 | conn 18 | |> put_status(:not_found) 19 | |> put_view(BlockchainAPIWeb.ErrorView) 20 | |> render(:"404") 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/controllers/four_oh_four_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.FourOhFourController do 2 | use BlockchainAPIWeb, :controller 3 | 4 | def index(conn, _params) do 5 | conn 6 | |> put_status(:not_found) 7 | |> put_view(BlockchainAPIWeb.ErrorView) 8 | |> render("404.json") 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/controllers/gateway_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.GatewayController do 2 | use BlockchainAPIWeb, :controller 3 | 4 | alias BlockchainAPI.{Util, Query} 5 | import BlockchainAPI.Cache.CacheService 6 | 7 | action_fallback BlockchainAPIWeb.FallbackController 8 | 9 | def index(conn, params) do 10 | gateways = Query.GatewayTransaction.list(params) 11 | 12 | conn 13 | |> put_cache_headers(ttl: :short, key: "block") 14 | |> render("index.json", gateways: gateways) 15 | end 16 | 17 | def show(conn, %{"hash" => hash}) do 18 | gateway = 19 | hash 20 | |> Util.string_to_bin() 21 | |> Query.GatewayTransaction.get!() 22 | 23 | conn 24 | |> put_cache_headers(ttl: :short, key: "block") 25 | |> render("show.json", gateway: gateway) 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/controllers/health_check_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.HealthCheckController do 2 | use BlockchainAPIWeb, :controller 3 | 4 | def index(conn, _params) do 5 | send_resp(conn, 200, "ok") 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/controllers/hotspot_challenge_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.HotspotChallengeController do 2 | use BlockchainAPIWeb, :controller 3 | 4 | alias BlockchainAPI.{Util, Query} 5 | import BlockchainAPI.Cache.CacheService 6 | require Logger 7 | 8 | action_fallback BlockchainAPIWeb.FallbackController 9 | 10 | def index(conn, %{"hotspot_address" => address} = _params) do 11 | hotspot_challenges = 12 | address 13 | |> Util.string_to_bin() 14 | |> Query.POCRequestTransaction.list_for() 15 | 16 | conn 17 | |> put_cache_headers(ttl: :short, key: "block") 18 | |> render("index.json", hotspot_challenges: hotspot_challenges) 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/controllers/hotspot_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.HotspotController do 2 | use BlockchainAPIWeb, :controller 3 | 4 | alias BlockchainAPI.{Query, Util} 5 | alias BlockchainAPIWeb.{POCReceiptsView, POCWitnessesView} 6 | 7 | import BlockchainAPI.Cache.CacheService 8 | 9 | action_fallback BlockchainAPIWeb.FallbackController 10 | 11 | def index(conn, params) do 12 | hotspots = Query.Hotspot.list(params) 13 | 14 | conn 15 | |> put_cache_headers(ttl: :short, key: "block") 16 | |> render("index.json", hotspots: hotspots) 17 | end 18 | 19 | def show(conn, %{"address" => address}) do 20 | hotspot = 21 | address 22 | |> Util.string_to_bin() 23 | |> Query.Hotspot.get!() 24 | 25 | conn 26 | |> put_cache_headers(ttl: :short, key: "block") 27 | |> render("show.json", hotspot: hotspot) 28 | end 29 | 30 | def search(conn, %{"term" => term} = _params) do 31 | results = Query.Hotspot.search(term) 32 | 33 | conn 34 | |> put_cache_headers(ttl: :short, key: "block") 35 | |> render("search.json", results: results) 36 | end 37 | 38 | def timeline(conn, _params) do 39 | hotspots = Query.Hotspot.all_by_time() 40 | 41 | conn 42 | |> put_cache_headers(ttl: :short, key: "block") 43 | |> render("timeline.json", hotspots: hotspots) 44 | end 45 | 46 | def receipts(conn, %{"hotspot_address" => address}) do 47 | receipts = 48 | with address <- Util.string_to_bin(address), 49 | hotspot when not is_nil(hotspot) <- Query.Hotspot.get!(address) do 50 | Query.POCReceipt.list_for(hotspot.address) 51 | else 52 | _ -> nil 53 | end 54 | 55 | conn 56 | |> put_cache_headers(ttl: :short, key: "block") 57 | |> put_view(POCReceiptsView) 58 | |> render("index.json", poc_receipts: receipts) 59 | end 60 | 61 | def witnesses(conn, %{"hotspot_address" => address}) do 62 | witnesses = 63 | with address <- Util.string_to_bin(address), 64 | hotspot when not is_nil(hotspot) <- Query.Hotspot.get!(address) do 65 | Query.POCWitness.list_for(hotspot.address) 66 | else 67 | _ -> nil 68 | end 69 | 70 | conn 71 | |> put_cache_headers(ttl: :short, key: "block") 72 | |> put_view(POCWitnessesView) 73 | |> render("index.json", poc_witnesses: witnesses) 74 | end 75 | 76 | end 77 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/controllers/location_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.LocationController do 2 | use BlockchainAPIWeb, :controller 3 | 4 | alias BlockchainAPI.{Util, Query} 5 | import BlockchainAPI.Cache.CacheService 6 | 7 | action_fallback BlockchainAPIWeb.FallbackController 8 | 9 | def index(conn, params) do 10 | txns = Query.LocationTransaction.list(params) 11 | 12 | conn 13 | |> put_cache_headers(ttl: :short, key: "block") 14 | |> render("index.json", location_transactions: txns) 15 | end 16 | 17 | def show(conn, %{"hash" => hash}) do 18 | location = 19 | hash 20 | |> Util.string_to_bin() 21 | |> Query.LocationTransaction.get!() 22 | 23 | conn 24 | |> put_cache_headers(ttl: :long, key: "eternal") 25 | |> render("show.json", location: location) 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/controllers/payment_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.PaymentController do 2 | use BlockchainAPIWeb, :controller 3 | 4 | alias BlockchainAPI.{Util, Query} 5 | import BlockchainAPI.Cache.CacheService 6 | 7 | action_fallback BlockchainAPIWeb.FallbackController 8 | 9 | def index(conn, params) do 10 | txns = Query.PaymentTransaction.list(params) 11 | 12 | conn 13 | |> put_cache_headers(ttl: :short, key: "block") 14 | |> render("index.json", payment_transactions: txns) 15 | end 16 | 17 | def show(conn, %{"hash" => hash}) do 18 | payment = 19 | hash 20 | |> Util.string_to_bin() 21 | |> Query.PaymentTransaction.get!() 22 | 23 | conn 24 | |> put_cache_headers(ttl: :long, key: "eternal") 25 | |> render("show.json", payment: payment) 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/controllers/pending_gateway_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.PendingGatewayController do 2 | use BlockchainAPIWeb, :controller 3 | 4 | alias BlockchainAPI.{Util, Query} 5 | alias BlockchainAPIWeb.GatewayView 6 | 7 | action_fallback BlockchainAPIWeb.FallbackController 8 | 9 | def show(conn, %{"gateway" => gateway, "owner" => owner} = _params) do 10 | pending_gateway = 11 | Query.PendingGateway.get(Util.string_to_bin(owner), Util.string_to_bin(gateway)) 12 | 13 | conn 14 | |> put_view(GatewayView) 15 | |> render("show.json", gateway: pending_gateway) 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/controllers/pending_location_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.PendingLocationController do 2 | use BlockchainAPIWeb, :controller 3 | 4 | alias BlockchainAPI.{Util, Query} 5 | alias BlockchainAPIWeb.LocationView 6 | 7 | action_fallback BlockchainAPIWeb.FallbackController 8 | 9 | def show(conn, %{"gateway" => gateway, "owner" => owner, "nonce" => nonce} = _params) do 10 | pending_location = 11 | Query.PendingLocation.get(Util.string_to_bin(owner), Util.string_to_bin(gateway), nonce) 12 | 13 | conn 14 | |> put_view(LocationView) 15 | |> render("show.json", location: pending_location) 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/controllers/pending_payment_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.PendingPaymentController do 2 | use BlockchainAPIWeb, :controller 3 | 4 | alias BlockchainAPI.{Util, Query} 5 | alias BlockchainAPIWeb.PaymentView 6 | 7 | action_fallback BlockchainAPIWeb.FallbackController 8 | 9 | def show(conn, %{"payer" => payer, "nonce" => nonce} = _params) do 10 | pending_payment = Query.PendingPayment.get(Util.string_to_bin(payer), nonce) 11 | 12 | conn 13 | |> put_view(PaymentView) 14 | |> render("show.json", payment: pending_payment) 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/controllers/reward_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.HotspotRewardController do 2 | use BlockchainAPIWeb, :controller 3 | 4 | alias BlockchainAPI.{Util, Query} 5 | import BlockchainAPI.Cache.CacheService 6 | 7 | action_fallback BlockchainAPIWeb.FallbackController 8 | 9 | def index(conn, %{"hotspot_address" => address} = params) do 10 | hotspot_rewards = 11 | address 12 | |> Util.string_to_bin() 13 | |> Query.HotspotReward.list(params) 14 | 15 | conn 16 | |> put_cache_headers(ttl: :short, key: "block") 17 | |> render("index.json", hotspot_rewards: hotspot_rewards) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/controllers/rewards_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.RewardsController do 2 | use BlockchainAPIWeb, :controller 3 | 4 | alias BlockchainAPI.{Util, Query} 5 | alias BlockchainAPIWeb.RewardView 6 | 7 | action_fallback BlockchainAPIWeb.FallbackController 8 | 9 | def show(conn, %{"hash" => hash} = _params) do 10 | epoch_rewards = 11 | hash 12 | |> Util.string_to_bin() 13 | |> Query.RewardTxn.total_by_epoch() 14 | 15 | res = 16 | case epoch_rewards do 17 | nil -> 18 | 0 19 | x -> 20 | Decimal.to_integer(x) 21 | end 22 | 23 | conn 24 | |> put_view(RewardView) 25 | |> render("show.json", epoch_rewards: res) 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/controllers/stats_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.StatsController do 2 | use BlockchainAPIWeb, :controller 3 | 4 | alias BlockchainAPIWeb.StatsView 5 | alias BlockchainAPI.Query.Stats 6 | 7 | import BlockchainAPI.Cache.CacheService 8 | 9 | action_fallback BlockchainAPIWeb.FallbackController 10 | 11 | def show(conn, _params) do 12 | stats = Stats.list() 13 | 14 | conn 15 | |> put_cache_headers(ttl: :medium) 16 | |> put_view(StatsView) 17 | |> render("show.json", stats: stats) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/controllers/witness_controller.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.WitnessController do 2 | use BlockchainAPIWeb, :controller 3 | 4 | alias BlockchainAPI.Util 5 | alias BlockchainAPIWeb.WitnessView 6 | 7 | action_fallback BlockchainAPIWeb.FallbackController 8 | 9 | def show(conn, %{"address" => address} = _params) do 10 | conn 11 | |> put_view(WitnessView) 12 | |> render("show.json", witnesses: witnesses(address)) 13 | end 14 | 15 | defp witnesses(address) do 16 | address 17 | |> Util.string_to_bin() 18 | |> get_witnesses() 19 | end 20 | 21 | defp get_witnesses(addr) do 22 | :blockchain_worker.blockchain() 23 | |> :blockchain.ledger() 24 | |> :blockchain_ledger_v1.active_gateways() 25 | |> Map.get(addr) 26 | |> :blockchain_ledger_gateway_v2.witnesses() 27 | |> Map.to_list() 28 | |> Enum.reduce( 29 | [], 30 | fn({_addr, _witness}=kv, acc) -> 31 | [encode_witness(kv) | acc] 32 | end) 33 | end 34 | 35 | defp encode_witness({addr, witness}) do 36 | 37 | hist = 38 | try do 39 | :blockchain_ledger_gateway_v2.witness_hist(witness) 40 | rescue 41 | _ -> 42 | %{} 43 | end 44 | 45 | first_time = 46 | case :blockchain_ledger_gateway_v2.witness_first_time(witness) do 47 | :undefined -> nil 48 | t -> t 49 | end 50 | 51 | recent_time = 52 | case :blockchain_ledger_gateway_v2.witness_recent_time(witness) do 53 | :undefined -> nil 54 | t -> t 55 | end 56 | 57 | %{ 58 | name: BlockchainAPI.Schema.Hotspot.animal_name(addr), 59 | address: Util.bin_to_string(addr), 60 | hist: hist, 61 | first_time: first_time, 62 | recent_time: recent_time 63 | } 64 | end 65 | 66 | end 67 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/endpoint.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.Endpoint do 2 | use Phoenix.Endpoint, otp_app: :blockchain_api 3 | use Appsignal.Phoenix 4 | 5 | socket "/socket", BlockchainAPIWeb.AccountSocket, 6 | websocket: true, 7 | longpoll: false 8 | 9 | # Serve at "/" the static files from "priv/static" directory. 10 | # 11 | # You should set gzip to true if you are running phx.digest 12 | # when deploying your static files in production. 13 | plug Plug.Static, 14 | at: "/", 15 | from: :blockchain_api, 16 | gzip: false, 17 | only: ~w(css fonts images js favicon.ico robots.txt) 18 | 19 | # Code reloading can be explicitly enabled under the 20 | # :code_reloader configuration of your endpoint. 21 | if code_reloading? do 22 | plug Phoenix.CodeReloader 23 | end 24 | 25 | plug Plug.RequestId 26 | plug Plug.Logger 27 | 28 | plug Plug.Parsers, 29 | parsers: [:urlencoded, :multipart, :json], 30 | pass: ["*/*"], 31 | json_decoder: Phoenix.json_library() 32 | 33 | plug Plug.MethodOverride 34 | plug Plug.Head 35 | 36 | # The session will be stored in the cookie and signed, 37 | # this means its contents can be read but not tampered with. 38 | # Set :encryption_salt if you would also like to encrypt it. 39 | plug Plug.Session, 40 | store: :cookie, 41 | key: "_blockchain_api_key", 42 | signing_salt: "N/BFJqEf" 43 | 44 | plug BlockchainAPIWeb.Router 45 | end 46 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/gettext.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.Gettext do 2 | @moduledoc """ 3 | A module providing Internationalization with a gettext-based API. 4 | 5 | By using [Gettext](https://hexdocs.pm/gettext), 6 | your module gains a set of macros for translations, for example: 7 | 8 | import BlockchainAPIWeb.Gettext 9 | 10 | # Simple translation 11 | gettext("Here is the string to translate") 12 | 13 | # Plural translation 14 | ngettext("Here is the string to translate", 15 | "Here are the strings to translate", 16 | 3) 17 | 18 | # Domain-based translation 19 | dgettext("errors", "Here is the error message to translate") 20 | 21 | See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage. 22 | """ 23 | use Gettext, otp_app: :blockchain_api 24 | end 25 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/views/account_gateway_view.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.AccountGatewayView do 2 | use BlockchainAPIWeb, :view 3 | alias BlockchainAPIWeb.AccountGatewayView 4 | 5 | def render("index.json", data) do 6 | %{ 7 | data: render_many(data.account_gateways, AccountGatewayView, "account_gateway.json") 8 | } 9 | end 10 | 11 | def render("show.json", %{account_gateway: gateway}) do 12 | %{data: render_one(gateway, AccountGatewayView, "account_gateway.json")} 13 | end 14 | 15 | def render("account_gateway.json", %{account_gateway: gateway}) do 16 | gateway 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/views/account_reward_view.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.AccountRewardView do 2 | use BlockchainAPIWeb, :view 3 | alias BlockchainAPIWeb.AccountRewardView 4 | 5 | def render("index.json", data) do 6 | %{ 7 | data: render_many(data.account_rewards, AccountRewardView, "account_reward.json") 8 | } 9 | end 10 | 11 | def render("show.json", %{account_reward: reward}) do 12 | %{data: render_one(reward, AccountRewardView, "account_reward.json")} 13 | end 14 | 15 | def render("account_reward.json", %{account_reward: reward}) do 16 | reward 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/views/account_transaction_view.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.AccountTransactionView do 2 | use BlockchainAPIWeb, :view 3 | alias BlockchainAPIWeb.AccountTransactionView 4 | 5 | def render("index.json", data) do 6 | %{ 7 | data: 8 | render_many(data.account_transactions, AccountTransactionView, "account_transaction.json") 9 | } 10 | end 11 | 12 | def render("show.json", %{account_transaction: txn}) do 13 | %{data: render_one(txn, AccountTransactionView, "account_transaction.json")} 14 | end 15 | 16 | def render("account_transaction.json", %{account_transaction: txn}) do 17 | txn 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/views/account_view.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.AccountView do 2 | use BlockchainAPIWeb, :view 3 | alias BlockchainAPIWeb.AccountView 4 | 5 | def render("index.json", data) do 6 | %{ 7 | data: render_many(data.accounts, AccountView, "account.json") 8 | } 9 | end 10 | 11 | def render("show.json", %{account: account}) do 12 | %{data: render_one(account, AccountView, "account.json")} 13 | end 14 | 15 | def render("account.json", %{account: account}) do 16 | account 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/views/activity_view.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.ActivityView do 2 | use BlockchainAPIWeb, :view 3 | alias BlockchainAPIWeb.ActivityView 4 | 5 | def render("index.json", data) do 6 | %{ 7 | data: render_many(data.activity, ActivityView, "activity.json") 8 | } 9 | end 10 | 11 | def render("show.json", %{activity: activity}) do 12 | %{data: render_one(activity, ActivityView, "activity.json")} 13 | end 14 | 15 | def render("activity.json", %{activity: activity}) do 16 | activity 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/views/block_view.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.BlockView do 2 | use BlockchainAPIWeb, :view 3 | alias BlockchainAPIWeb.BlockView 4 | 5 | def render("index.json", data) do 6 | %{ 7 | data: render_many(data.blocks, BlockView, "block.json") 8 | } 9 | end 10 | 11 | def render("show.json", %{block: block}) do 12 | %{data: render_one(block, BlockView, "block.json")} 13 | end 14 | 15 | def render("block.json", %{block: block}) do 16 | block 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/views/challenge_view.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.ChallengeView do 2 | use BlockchainAPIWeb, :view 3 | alias BlockchainAPIWeb.ChallengeView 4 | 5 | def render("index.json", data) do 6 | %{ 7 | totalOngoing: data.total_ongoing, 8 | issued: data.issued, 9 | successful: data.successful, 10 | failed: data.failed, 11 | data: render_many(data.challenges, ChallengeView, "challenge.json") 12 | } 13 | end 14 | 15 | def render("show.json", %{challenge: challenge}) do 16 | %{data: render_one(challenge, ChallengeView, "challenge.json")} 17 | end 18 | 19 | def render("challenge.json", %{challenge: challenge}) do 20 | challenge 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/views/changeset_view.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.ChangesetView do 2 | use BlockchainAPIWeb, :view 3 | 4 | @doc """ 5 | Traverses and translates changeset errors. 6 | 7 | See `Ecto.Changeset.traverse_errors/2` and 8 | `BlockchainAPIWeb.ErrorHelpers.translate_error/1` for more details. 9 | """ 10 | def translate_errors(changeset) do 11 | Ecto.Changeset.traverse_errors(changeset, &translate_error/1) 12 | end 13 | 14 | def render("error.json", %{changeset: changeset}) do 15 | # When encoded, the changeset returns its errors 16 | # as a JSON object. So we just pass it forward. 17 | %{errors: translate_errors(changeset)} 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/views/coinbase_view.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.CoinbaseView do 2 | use BlockchainAPIWeb, :view 3 | alias BlockchainAPIWeb.CoinbaseView 4 | 5 | def render("index.json", data) do 6 | %{ 7 | data: render_many(data.coinbase_transactions, CoinbaseView, "coinbase.json") 8 | } 9 | end 10 | 11 | def render("show.json", %{coinbase: coinbase}) do 12 | %{data: render_one(coinbase, CoinbaseView, "coinbase.json")} 13 | end 14 | 15 | def render("coinbase.json", %{coinbase: coinbase}) do 16 | coinbase 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/views/data_credit_view.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.DataCreditView do 2 | use BlockchainAPIWeb, :view 3 | alias BlockchainAPIWeb.DataCreditView 4 | 5 | def render("index.json", data) do 6 | %{ 7 | data: render_many(data.data_credit_transactions, DataCreditView, "data_credit.json") 8 | } 9 | end 10 | 11 | def render("show.json", %{data_credit: data_credit}) do 12 | %{data: render_one(data_credit, DataCreditView, "data_credit.json")} 13 | end 14 | 15 | def render("data_credit.json", %{data_credit: data_credit}) do 16 | data_credit 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/views/election_transaction_view.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.ElectionTransactionView do 2 | use BlockchainAPIWeb, :view 3 | 4 | alias BlockchainAPIWeb.ElectionTransactionView 5 | 6 | def render("index.json", data) do 7 | %{data: render_many(data.elections, ElectionTransactionView, "election_transaction.json")} 8 | end 9 | 10 | def render("show.json", %{election: election}) do 11 | %{data: render_one(election, ElectionTransactionView, "election_transaction.json")} 12 | end 13 | 14 | def render("election_transaction.json", %{election_transaction: txn}) do 15 | txn 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/views/election_view.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.ElectionView do 2 | use BlockchainAPIWeb, :view 3 | alias BlockchainAPIWeb.ElectionView 4 | 5 | def render("index.json", data) do 6 | %{ 7 | data: render_many(data.election_transactions, ElectionView, "election.json") 8 | } 9 | end 10 | 11 | def render("show.json", %{election: election}) do 12 | %{data: render_one(election, ElectionView, "election.json")} 13 | end 14 | 15 | def render("election.json", %{election: election}) do 16 | election 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/views/error_helpers.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.ErrorHelpers do 2 | @moduledoc """ 3 | Conveniences for translating and building error messages. 4 | """ 5 | 6 | @doc """ 7 | Translates an error message using gettext. 8 | """ 9 | def translate_error({msg, opts}) do 10 | # When using gettext, we typically pass the strings we want 11 | # to translate as a static argument: 12 | # 13 | # # Translate "is invalid" in the "errors" domain 14 | # dgettext("errors", "is invalid") 15 | # 16 | # # Translate the number of files with plural rules 17 | # dngettext("errors", "1 file", "%{count} files", count) 18 | # 19 | # Because the error messages we show in our forms and APIs 20 | # are defined inside Ecto, we need to translate them dynamically. 21 | # This requires us to call the Gettext module passing our gettext 22 | # backend as first argument. 23 | # 24 | # Note we use the "errors" domain, which means translations 25 | # should be written to the errors.po file. The :count option is 26 | # set by Ecto and indicates we should also apply plural rules. 27 | if count = opts[:count] do 28 | Gettext.dngettext(BlockchainAPIWeb.Gettext, "errors", msg, msg, count, opts) 29 | else 30 | Gettext.dgettext(BlockchainAPIWeb.Gettext, "errors", msg, opts) 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/views/error_view.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.ErrorView do 2 | use BlockchainAPIWeb, :view 3 | 4 | # If you want to customize a particular status code 5 | # for a certain format, you may uncomment below. 6 | # def render("500.json", _assigns) do 7 | # %{errors: %{detail: "Internal Server Error"}} 8 | # end 9 | 10 | # By default, Phoenix returns the status message from 11 | # the template name. For example, "404.json" becomes 12 | # "Not Found". 13 | def template_not_found(template, _assigns) do 14 | %{errors: %{detail: Phoenix.Controller.status_message_from_template(template)}} 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/views/gateway_view.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.GatewayView do 2 | use BlockchainAPIWeb, :view 3 | alias BlockchainAPIWeb.GatewayView 4 | 5 | def render("index.json", data) do 6 | %{ 7 | data: render_many(data.gateways, GatewayView, "gateway.json") 8 | } 9 | end 10 | 11 | def render("show.json", %{gateway: gateway}) do 12 | %{data: render_one(gateway, GatewayView, "gateway.json")} 13 | end 14 | 15 | def render("gateway.json", %{gateway: gateway}) do 16 | gateway 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/views/hotspot_challenge_view.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.HotspotChallengeView do 2 | use BlockchainAPIWeb, :view 3 | alias BlockchainAPIWeb.HotspotChallengeView 4 | 5 | def render("index.json", data) do 6 | %{ 7 | data: render_many(data.hotspot_challenges, HotspotChallengeView, "hotspot_challenge.json") 8 | } 9 | end 10 | 11 | def render("show.json", %{hotspot_challenge: challenge}) do 12 | %{data: render_one(challenge, HotspotChallengeView, "hotspot_challenge.json")} 13 | end 14 | 15 | def render("hotspot_challenge.json", %{hotspot_challenge: challenge}) do 16 | challenge 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/views/hotspot_reward_view.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.HotspotRewardView do 2 | use BlockchainAPIWeb, :view 3 | alias BlockchainAPIWeb.HotspotRewardView 4 | 5 | def render("index.json", data) do 6 | %{ 7 | data: render_many(data.hotspot_rewards, HotspotRewardView, "hotspot_reward.json") 8 | } 9 | end 10 | 11 | def render("show.json", %{hotspot_reward: hotspot_reward}) do 12 | %{data: render_one(hotspot_reward, HotspotRewardView, "hotspot_reward.json")} 13 | end 14 | 15 | def render("hotspot_reward.json", %{hotspot_reward: hotspot_reward}) do 16 | hotspot_reward 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/views/hotspot_view.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.HotspotView do 2 | use BlockchainAPIWeb, :view 3 | alias BlockchainAPIWeb.HotspotView 4 | 5 | def render("index.json", data) do 6 | %{ 7 | data: render_many(data.hotspots, HotspotView, "hotspot.json") 8 | } 9 | end 10 | 11 | def render("show.json", %{hotspot: hotspot}) do 12 | %{data: render_one(hotspot, HotspotView, "hotspot.json")} 13 | end 14 | 15 | def render("search.json", data) do 16 | %{ 17 | data: render_many(data.results, HotspotView, "hotspot.json") 18 | } 19 | end 20 | 21 | def render("timeline.json", data) do 22 | %{ 23 | data: render_many(data.hotspots, HotspotView, "hotspot.json") 24 | } 25 | end 26 | 27 | def render("hotspot.json", %{hotspot: hotspot}) do 28 | hotspot 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/views/location_view.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.LocationView do 2 | use BlockchainAPIWeb, :view 3 | alias BlockchainAPIWeb.LocationView 4 | 5 | def render("index.json", data) do 6 | %{ 7 | data: render_many(data.location_transactions, LocationView, "location.json") 8 | } 9 | end 10 | 11 | def render("show.json", %{location: location}) do 12 | %{data: render_one(location, LocationView, "location.json")} 13 | end 14 | 15 | def render("location.json", %{location: location}) do 16 | location 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/views/oui_view.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.OUIView do 2 | use BlockchainAPIWeb, :view 3 | alias BlockchainAPIWeb.OUIView 4 | 5 | def render("index.json", data) do 6 | %{ 7 | data: render_many(data.ouis, OUIView, "oui.json") 8 | } 9 | end 10 | 11 | def render("show.json", %{oui: oui}) do 12 | %{data: render_one(oui, OUIView, "oui.json")} 13 | end 14 | 15 | def render("oui.json", %{oui: oui}) do 16 | oui 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/views/payment_v2_view.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.PaymentV2View do 2 | use BlockchainAPIWeb, :view 3 | alias BlockchainAPIWeb.PaymentV2View 4 | 5 | def render("index.json", data) do 6 | %{ 7 | data: render_many(data.payment_transactions, PaymentV2View, "payment_v2.json") 8 | } 9 | end 10 | 11 | def render("show.json", %{payment_v2: payment_v2}) do 12 | %{data: render_one(payment_v2, PaymentV2View, "payment_v2.json")} 13 | end 14 | 15 | def render("payment_v2.json", %{payment_v2: payment_v2}) do 16 | payment_v2 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/views/payment_view.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.PaymentView do 2 | use BlockchainAPIWeb, :view 3 | alias BlockchainAPIWeb.PaymentView 4 | 5 | def render("index.json", data) do 6 | %{ 7 | data: render_many(data.payment_transactions, PaymentView, "payment.json") 8 | } 9 | end 10 | 11 | def render("show.json", %{payment: payment}) do 12 | %{data: render_one(payment, PaymentView, "payment.json")} 13 | end 14 | 15 | def render("payment.json", %{payment: payment}) do 16 | payment 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/views/poc_receipts_view.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.POCReceiptsView do 2 | use BlockchainAPIWeb, :view 3 | alias BlockchainAPIWeb.POCReceiptsView 4 | 5 | def render("index.json", data) do 6 | %{ 7 | data: render_many(data.poc_receipts, POCReceiptsView, "poc_receipts.json") 8 | } 9 | end 10 | 11 | def render("show.json", %{poc_receipts: poc_receipts}) do 12 | %{data: render_one(poc_receipts, POCReceiptsView, "poc_receipts.json")} 13 | end 14 | 15 | def render("poc_receipts.json", %{poc_receipts: poc_receipts}) do 16 | poc_receipts 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/views/poc_request_view.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.POCRequestView do 2 | use BlockchainAPIWeb, :view 3 | alias BlockchainAPIWeb.POCRequestView 4 | 5 | def render("index.json", data) do 6 | %{ 7 | data: render_many(data.poc_request_transactions, POCRequestView, "poc_request.json") 8 | } 9 | end 10 | 11 | def render("show.json", %{poc_request: poc_request}) do 12 | %{data: render_one(poc_request, POCRequestView, "poc_request.json")} 13 | end 14 | 15 | def render("poc_request.json", %{poc_request: poc_request}) do 16 | poc_request 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/views/poc_witnesses_view.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.POCWitnessesView do 2 | use BlockchainAPIWeb, :view 3 | alias BlockchainAPIWeb.POCWitnessesView 4 | 5 | def render("index.json", data) do 6 | %{ 7 | data: render_many(data.poc_witnesses, POCWitnessesView, "poc_witnesses.json") 8 | } 9 | end 10 | 11 | def render("show.json", %{poc_witnesses: poc_witnesses}) do 12 | %{data: render_one(poc_witnesses, POCWitnessesView, "poc_witnesses.json")} 13 | end 14 | 15 | def render("poc_witnesses.json", %{poc_witnesses: poc_witnesses}) do 16 | poc_witnesses 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/views/reward_view.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.RewardView do 2 | use BlockchainAPIWeb, :view 3 | 4 | def render("show.json", %{epoch_rewards: epoch_rewards}) do 5 | %{data: epoch_rewards} 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/views/rewards_view.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.RewardsView do 2 | use BlockchainAPIWeb, :view 3 | alias BlockchainAPIWeb.RewardsView 4 | 5 | def render("index.json", data) do 6 | %{ 7 | data: render_many(data.rewards, RewardsView, "rewards.json") 8 | } 9 | end 10 | 11 | def render("show.json", %{rewards: rewards}) do 12 | %{data: render_one(rewards, RewardsView, "rewards.json")} 13 | end 14 | 15 | def render("rewards.json", %{rewards: rewards}) do 16 | rewards 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/views/sec_exchange_view.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.SecExchangeView do 2 | use BlockchainAPIWeb, :view 3 | alias BlockchainAPIWeb.SecExchangeView 4 | 5 | def render("index.json", data) do 6 | IO.inspect(data, label: :data) 7 | %{ 8 | data: render_many(data.sec_exchange_transactions, SecExchangeView, "sec_exchange.json") 9 | } 10 | end 11 | 12 | def render("show.json", %{sec_exchange: sec_exchange}) do 13 | %{data: render_one(sec_exchange, SecExchangeView, "sec_exchange.json")} 14 | end 15 | 16 | def render("sec_exchange.json", %{sec_exchange: sec_exchange}) do 17 | sec_exchange 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/views/security_view.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.SecurityView do 2 | use BlockchainAPIWeb, :view 3 | alias BlockchainAPIWeb.SecurityView 4 | 5 | def render("index.json", data) do 6 | %{ 7 | data: render_many(data.security_transactions, SecurityView, "security.json") 8 | } 9 | end 10 | 11 | def render("show.json", %{security: security}) do 12 | %{data: render_one(security, SecurityView, "security.json")} 13 | end 14 | 15 | def render("security.json", %{security: security}) do 16 | security 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/views/stats_view.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.StatsView do 2 | use BlockchainAPIWeb, :view 3 | 4 | def render("show.json", %{stats: stats}) do 5 | %{data: stats} 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/views/transaction_view.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.TransactionView do 2 | use BlockchainAPIWeb, :view 3 | alias BlockchainAPIWeb.TransactionView 4 | 5 | def render("index.json", data) do 6 | %{ 7 | data: render_many(data.transactions, TransactionView, "transaction.json") 8 | } 9 | end 10 | 11 | def render("show.json", %{transaction: transaction}) do 12 | %{data: render_one(transaction, TransactionView, "transaction.json")} 13 | end 14 | 15 | def render("transaction.json", %{transaction: txn}) do 16 | txn 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/blockchain_api_web/views/witness_view.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.WitnessView do 2 | use BlockchainAPIWeb, :view 3 | 4 | def render("show.json", %{witnesses: witnesses}) do 5 | %{data: witnesses} 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /nginx/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:latest 2 | -------------------------------------------------------------------------------- /nginx/lb.conf: -------------------------------------------------------------------------------- 1 | upstream serv { 2 | server blockchain_api_app_1:4000; 3 | server blockchain_api_app_2:4000; 4 | server blockchain_api_app_3:4000; 5 | server blockchain_api_app_4:4000; 6 | } 7 | 8 | server { 9 | listen 8080; 10 | 11 | location / { 12 | proxy_pass http://serv; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /postgres/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM postgres:latest 2 | -------------------------------------------------------------------------------- /priv/dev/genesis: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/novalabsxyz/blockchain-api/3c9fbc892e645f9bb144414f3da36749603f37bc/priv/dev/genesis -------------------------------------------------------------------------------- /priv/pescadero/genesis: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/novalabsxyz/blockchain-api/3c9fbc892e645f9bb144414f3da36749603f37bc/priv/pescadero/genesis -------------------------------------------------------------------------------- /priv/prod/genesis: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/novalabsxyz/blockchain-api/3c9fbc892e645f9bb144414f3da36749603f37bc/priv/prod/genesis -------------------------------------------------------------------------------- /priv/repo/migrations/.formatter.exs: -------------------------------------------------------------------------------- 1 | [ 2 | import_deps: [:ecto_sql], 3 | inputs: ["*.exs"] 4 | ] 5 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190118235355_add_blocks_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.AddBlocksTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:blocks) do 6 | add :height, :bigint, null: false 7 | add :hash, :binary, null: false 8 | add :round, :integer, null: false 9 | add :time, :integer, null: false 10 | 11 | timestamps() 12 | end 13 | 14 | create unique_index(:blocks, [:height], name: :unique_block_height) 15 | # NOTE: uncertain to add this, but presumably block times are ALWAYS unique 16 | # This helps in the creating the composite index for account_balances table 17 | create unique_index(:blocks, [:time], name: :unique_block_time) 18 | end 19 | 20 | end 21 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190118235356_add_transactions_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.AddTransactionsTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:transactions) do 6 | add :hash, :binary, null: false 7 | add :type, :string, null: false 8 | add :status, :string, null: false, default: "cleared" 9 | 10 | add :block_height, references(:blocks, on_delete: :nothing, column: :height), null: false 11 | 12 | timestamps() 13 | end 14 | 15 | create unique_index(:transactions, [:hash], name: :unique_txn_hash) 16 | end 17 | 18 | end 19 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190118235357_add_payment_transactions_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.AddPaymentTransactionsTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:payment_transactions) do 6 | add :amount, :bigint, null: false 7 | add :payee, :binary, null: false 8 | add :payer, :binary, null: false 9 | add :fee, :bigint, null: false 10 | add :nonce, :bigint, null: false, default: 0 11 | add :status, :string, null: false, default: "cleared" 12 | 13 | add :hash, references(:transactions, on_delete: :nothing, column: :hash, type: :binary), null: false 14 | timestamps() 15 | end 16 | 17 | create unique_index(:payment_transactions, [:hash], name: :unique_payment_hash) 18 | end 19 | 20 | end 21 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190118235358_add_coinbase_transactions_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.AddCoinbaseTransactionsTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:coinbase_transactions) do 6 | add :amount, :bigint, null: false 7 | add :payee, :binary, null: false 8 | add :status, :string, null: false, default: "cleared" 9 | 10 | add :hash, references(:transactions, on_delete: :nothing, column: :hash, type: :binary), null: false 11 | timestamps() 12 | end 13 | 14 | create unique_index(:coinbase_transactions, [:hash], name: :unique_coinbase_hash) 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190118235359_add_gateway_transactions_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.AddGatewayTransactionsTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:gateway_transactions) do 6 | add :owner, :binary, null: false 7 | add :gateway, :binary, null: false 8 | add :payer, :binary, null: true # payer can be undefined or empty binary in core 9 | add :fee, :bigint, null: false, default: 0 10 | add :staking_fee, :bigint, null: false, default: 1 11 | add :status, :string, null: false, default: "cleared" 12 | 13 | add :hash, references(:transactions, on_delete: :nothing, column: :hash, type: :binary), null: false 14 | timestamps() 15 | end 16 | 17 | create unique_index(:gateway_transactions, [:hash], name: :unique_gateway_hash) 18 | create unique_index(:gateway_transactions, [:gateway], name: :unique_gateway) 19 | end 20 | 21 | end 22 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190118235400_add_location_transactions_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.AddLocationTransactionsTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:location_transactions) do 6 | add :owner, :binary, null: false 7 | add :payer, :binary, null: true # payer can be undefined or empty binary 8 | add :location, :binary, null: false 9 | add :nonce, :bigint, null: false, default: 0 10 | add :fee, :bigint, null: false, default: 0 11 | add :status, :string, null: false, default: "cleared" 12 | add :staking_fee, :bigint, null: false, default: 1 13 | 14 | add :hash, references(:transactions, on_delete: :nothing, column: :hash, type: :binary), null: false 15 | add :gateway, references(:gateway_transactions, on_delete: :nothing, column: :gateway, type: :binary), null: false 16 | timestamps() 17 | end 18 | 19 | create unique_index(:location_transactions, [:hash], name: :unique_location_hash) 20 | end 21 | 22 | end 23 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190122051731_add_account_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.AddAccountTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:accounts) do 6 | add :address, :binary, null: false 7 | add :name, :string 8 | add :balance, :bigint, null: false 9 | add :fee, :bigint, null: false, default: 0 10 | add :nonce, :bigint, null: false, default: 0 11 | 12 | timestamps() 13 | end 14 | 15 | create unique_index(:accounts, [:address], name: :unique_account_address) 16 | end 17 | 18 | end 19 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190122053407_add_account_transaction_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.AddAccountTransactionTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:account_transactions) do 6 | add :account_address, :binary, null: false 7 | add :txn_hash, :binary, null: false 8 | add :txn_type, :string, null: false 9 | add :txn_status, :string, null: false 10 | 11 | timestamps() 12 | end 13 | 14 | # create unique_index(:account_transactions, [:account_address, :txn_hash, :txn_status, :txn_type], name: :unique_account_txn) 15 | 16 | end 17 | 18 | end 19 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190222185006_add_account_balance_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.AddAccountBalanceTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:account_balances) do 6 | add :account_address, :binary, null: false 7 | add :block_time, :integer, null: false 8 | add :block_height, :integer, null: false 9 | add :balance, :bigint, null: false 10 | add :delta, :bigint, null: false 11 | 12 | timestamps() 13 | end 14 | 15 | # composite uniqueness for account_address, block_time, balance 16 | create unique_index(:account_balances, [:account_address, :block_height, :balance], name: :unique_account_height_balance) 17 | end 18 | 19 | end 20 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190312213336_add_hotspot_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.AddHotspotTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:hotspots) do 6 | add :address, :binary, null: false 7 | add :owner, :binary, null: false 8 | add :score, :float, null: false, default: 0.25 9 | add :location, :string, null: true 10 | add :long_city, :string, null: true 11 | add :long_street, :string, null: true 12 | add :long_state, :string, null: true 13 | add :long_country, :string, null: true 14 | add :short_street, :string, null: true 15 | add :short_city, :string, null: true 16 | add :short_state, :string, null: true 17 | add :short_country, :string, null: true 18 | add :score_update_height, :bigint, null: false, default: 0 19 | 20 | timestamps() 21 | end 22 | 23 | create unique_index(:hotspots, [:address], name: :unique_hotspots) 24 | end 25 | 26 | end 27 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190318002450_add_poc_request_transaction_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.AddPocRequestTransactionTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:poc_request_transactions) do 6 | add :signature, :binary, null: false 7 | add :fee, :bigint, null: false 8 | add :onion, :binary, null: false 9 | add :location, :string, null: false 10 | add :owner, :binary, null: false 11 | 12 | add :hash, references(:transactions, on_delete: :nothing, column: :hash, type: :binary), null: false 13 | add :challenger, references(:gateway_transactions, on_delete: :nothing, column: :gateway, type: :binary), null: false 14 | timestamps() 15 | end 16 | 17 | create unique_index(:poc_request_transactions, [:hash], name: :unique_poc_hash) 18 | end 19 | 20 | end 21 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190320211614_add_fuzzystrmatch_extension.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.AddFuzzystrmatchExtension do 2 | use Ecto.Migration 3 | 4 | def up do 5 | execute "CREATE extension if not exists fuzzystrmatch;" 6 | end 7 | 8 | def down do 9 | execute "DROP extension if exists fuzzystrmatch;" 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190403182210_add_poc_receipts_transaction_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.AddPocReceiptsTransactionTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:poc_receipts_transactions) do 6 | add :poc_request_transactions_id, references(:poc_request_transactions, on_delete: :nothing, column: :id, type: :bigint), null: false 7 | add :signature, :binary, null: false 8 | add :fee, :bigint, null: false 9 | add :onion, :binary, null: false 10 | add :challenger_loc, :string, null: false 11 | add :challenger_owner, :binary, null: false 12 | 13 | add :hash, references(:transactions, on_delete: :nothing, column: :hash, type: :binary), null: false 14 | add :challenger, references(:gateway_transactions, on_delete: :nothing, column: :gateway, type: :binary), null: false 15 | 16 | timestamps() 17 | end 18 | 19 | create unique_index(:poc_receipts_transactions, [:hash]) 20 | end 21 | 22 | end 23 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190404032039_add_poc_path_elements_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.AddPocPathElementsTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:poc_path_elements) do 6 | add :poc_receipts_transactions_hash, references(:poc_receipts_transactions, on_delete: :nothing, column: :hash, type: :binary), null: false 7 | add :challengee, :binary, null: true # NOTE: last path element has no challengee by design 8 | add :challengee_loc, :string, null: true 9 | add :challengee_owner, :binary, null: false 10 | add :result, :string, null: false, default: "untested" 11 | 12 | timestamps() 13 | end 14 | 15 | create index(:poc_path_elements, [:poc_receipts_transactions_hash]) 16 | end 17 | 18 | end 19 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190404032155_add_poc_receipt_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.AddPocReceiptTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:poc_receipts) do 6 | add :poc_path_elements_id, references(:poc_path_elements, on_delete: :nothing, column: :id, type: :bigint), null: false 7 | add :gateway, :binary, null: false 8 | add :timestamp, :bigint, null: false 9 | add :signal, :integer, null: false 10 | add :data, :binary, null: false 11 | add :signature, :binary, null: false 12 | add :origin, :string, null: false 13 | add :location, :string, null: false 14 | add :owner, :binary, null: false 15 | 16 | timestamps() 17 | end 18 | end 19 | 20 | end 21 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190404032208_add_poc_witness_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.AddPocWitnessTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:poc_witnesses) do 6 | add :poc_path_elements_id, references(:poc_path_elements, on_delete: :nothing, column: :id, type: :bigint), null: false 7 | add :gateway, :binary, null: false 8 | add :timestamp, :bigint, null: false 9 | add :signal, :integer, null: false 10 | add :packet_hash, :binary, null: false 11 | add :signature, :binary, null: false 12 | add :location, :string, null: false 13 | add :owner, :binary, null: false 14 | 15 | timestamps() 16 | end 17 | end 18 | 19 | end 20 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190417230217_add_pending_coinbase_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.AddPendingCoinbaseTable do 2 | use Ecto.Migration 3 | import Honeydew.EctoPollQueue.Migration 4 | import BlockchainAPI.Schema.PendingCoinbase, only: [submit_coinbase_queue: 0] 5 | 6 | def change do 7 | create table(:pending_coinbases) do 8 | add :amount, :bigint, null: false 9 | add :payee, :binary, null: false 10 | add :status, :string, null: false, default: "pending" 11 | add :hash, :binary, null: false 12 | add :txn, :binary, null: false 13 | add :submit_height, :bigint, null: false, default: 0 14 | 15 | honeydew_fields(submit_coinbase_queue()) 16 | 17 | timestamps() 18 | end 19 | 20 | create unique_index(:pending_coinbases, [:payee, :hash, :status, :submit_height], name: :unique_pending_coinbase) 21 | honeydew_indexes(:pending_coinbases, submit_coinbase_queue()) 22 | end 23 | 24 | end 25 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190417230218_add_pending_gateway_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.AddPendingGatewayTable do 2 | use Ecto.Migration 3 | import Honeydew.EctoPollQueue.Migration 4 | import BlockchainAPI.Schema.PendingGateway, only: [submit_gateway_queue: 0] 5 | 6 | def change do 7 | create table(:pending_gateways) do 8 | add :status, :string, null: false, default: "pending" 9 | add :gateway, :binary, null: false 10 | add :fee, :bigint, null: false, default: 0 11 | add :staking_fee, :bigint, null: false, default: 1 12 | add :hash, :binary, null: false 13 | add :txn, :binary, null: false 14 | add :submit_height, :bigint, null: false, default: 0 15 | 16 | add :owner, references(:accounts, on_delete: :nothing, column: :address, type: :binary), null: false 17 | 18 | honeydew_fields(submit_gateway_queue()) 19 | 20 | timestamps() 21 | end 22 | 23 | create unique_index(:pending_gateways, [:owner, :gateway], name: :unique_pending_gateway_owner) 24 | create unique_index(:pending_gateways, [:owner, :gateway, :hash, :status, :submit_height], name: :unique_pending_gateway) 25 | honeydew_indexes(:pending_gateways, submit_gateway_queue()) 26 | end 27 | 28 | end 29 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190417230219_add_pending_location_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.AddPendingLocationTable do 2 | use Ecto.Migration 3 | import Honeydew.EctoPollQueue.Migration 4 | import BlockchainAPI.Schema.PendingLocation, only: [submit_location_queue: 0] 5 | 6 | def change do 7 | create table(:pending_locations) do 8 | add :status, :string, null: false, default: "pending" 9 | add :location, :binary, null: false 10 | add :fee, :bigint, null: false, default: 0 11 | add :staking_fee, :bigint, null: false, default: 1 12 | add :nonce, :bigint, null: false 13 | add :gateway, :binary, null: false 14 | add :hash, :binary, null: false 15 | add :txn, :binary, null: false 16 | add :submit_height, :bigint, null: false, default: 0 17 | 18 | add :owner, references(:accounts, on_delete: :nothing, column: :address, type: :binary), null: false 19 | 20 | honeydew_fields(submit_location_queue()) 21 | 22 | timestamps() 23 | end 24 | 25 | create unique_index(:pending_locations, [:owner, :gateway, :nonce], name: :unique_pending_owner_gateway_nonce) 26 | create unique_index(:pending_locations, [:owner, :gateway, :submit_height, :hash, :status], name: :unique_pending_location) 27 | honeydew_indexes(:pending_locations, submit_location_queue()) 28 | end 29 | 30 | end 31 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190417230220_add_pending_payment_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.AddPendingPaymentTable do 2 | use Ecto.Migration 3 | import Honeydew.EctoPollQueue.Migration 4 | import BlockchainAPI.Schema.PendingPayment, only: [submit_payment_queue: 0] 5 | 6 | def change do 7 | create table(:pending_payments) do 8 | add :status, :string, null: false, default: "pending" 9 | add :nonce, :bigint, null: false, default: 0 10 | add :payee, :binary, null: false 11 | add :fee, :bigint, null: false, default: 0 12 | add :amount, :bigint, null: false 13 | add :hash, :binary, null: false 14 | add :txn, :binary, null: false 15 | add :submit_height, :bigint, null: false, default: 0 16 | 17 | add :payer, references(:accounts, on_delete: :nothing, column: :address, type: :binary), null: false 18 | 19 | honeydew_fields(submit_payment_queue()) 20 | 21 | timestamps() 22 | end 23 | 24 | create unique_index(:pending_payments, [:payer, :nonce], name: :unique_pending_payment_nonce) 25 | create unique_index(:pending_payments, [:payer, :hash, :status, :submit_height], name: :unique_pending_payment) 26 | honeydew_indexes(:pending_payments, submit_payment_queue()) 27 | end 28 | 29 | end 30 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190421034025_add_security_transaction_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.AddSecurityTransactionTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:security_transactions) do 6 | add :amount, :bigint, null: false 7 | add :payee, :binary, null: false 8 | add :status, :string, null: false, default: "cleared" 9 | 10 | add :hash, references(:transactions, on_delete: :nothing, column: :hash, type: :binary), null: false 11 | timestamps() 12 | end 13 | 14 | create unique_index(:security_transactions, [:hash], name: :unique_security_hash) 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190421045052_add_election_transaction_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.AddElectionTransactionTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:election_transactions) do 6 | add :proof, :binary, null: true 7 | add :delay, :integer, null: false 8 | add :election_height, :bigint, null: false 9 | 10 | add :hash, references(:transactions, on_delete: :nothing, column: :hash, type: :binary), null: false 11 | timestamps() 12 | end 13 | 14 | create unique_index(:election_transactions, [:hash], name: :unique_election_hash) 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190421045449_add_consensus_member_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.AddConsensusMemberTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:consensus_members) do 6 | add :election_transactions_id, references(:election_transactions, on_delete: :nothing, column: :id, type: :bigint), null: false 7 | add :address, :binary, null: false 8 | 9 | timestamps() 10 | end 11 | end 12 | 13 | end 14 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190508201122_add_hotspot_activity_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.AddHotspotActivityTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:hotspot_activity) do 6 | add :gateway, references(:gateway_transactions, on_delete: :nothing, column: :gateway, type: :binary), null: false 7 | add :poc_req_txn_hash, references(:poc_request_transactions, on_delete: :nothing, column: :hash, type: :binary), null: true 8 | add :poc_req_txn_block_height, references(:blocks, on_delete: :nothing, column: :height), null: true 9 | add :poc_req_txn_block_time, references(:blocks, on_delete: :nothing, column: :time), null: true 10 | add :poc_rx_txn_hash, references(:poc_receipts_transactions, on_delete: :nothing, column: :hash, type: :binary), null: true 11 | add :poc_rx_txn_block_height, references(:blocks, on_delete: :nothing, column: :height), null: true 12 | add :poc_rx_txn_block_time, references(:blocks, on_delete: :nothing, column: :time), null: true 13 | add :poc_witness_id, references(:poc_witnesses, on_delete: :nothing, column: :id), null: true 14 | add :poc_rx_id, references(:poc_receipts, on_delete: :nothing, column: :id), null: true 15 | add :poc_witness_challenge_id, references(:poc_receipts_transactions, on_delete: :nothing, column: :id), null: true 16 | add :poc_rx_challenge_id, references(:poc_receipts_transactions, on_delete: :nothing, column: :id), null: true 17 | add :poc_score, :float, null: true 18 | add :poc_score_delta, :float, null: true 19 | add :rapid_decline, :boolean, null: true, default: :false 20 | add :in_consensus, :boolean, null: true, default: :false 21 | add :election_id, :integer, null: true 22 | add :election_block_height, :bigint, null: true 23 | add :election_txn_block_height, :bigint, null: true 24 | add :election_txn_block_time, :integer, null: true 25 | add :reward_type, :string, null: true 26 | add :reward_amount, :bigint, null: true 27 | add :reward_block_height, :bigint, null: true 28 | add :reward_block_time, :integer, null: true 29 | 30 | timestamps() 31 | end 32 | end 33 | 34 | end 35 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190701030444_add_rewards_transactions_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.AddRewardsTransactionsTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:rewards_transactions) do 6 | add :fee, :integer, null: false 7 | 8 | add :hash, references(:transactions, on_delete: :nothing, column: :hash, type: :binary), null: false 9 | timestamps() 10 | end 11 | 12 | create unique_index(:rewards_transactions, [:hash], name: :unique_rewards_hash) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190701032610_add_reward_txns_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.AddRewardTxnsTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:reward_txns) do 6 | add :rewards_hash, references(:rewards_transactions, on_delete: :nothing, column: :hash, type: :binary), null: false 7 | add :account, :binary, null: false 8 | add :gateway, :binary, null: true # NOTE: security reward has no gateway 9 | add :amount, :integer, null: false 10 | add :type, :string, null: false 11 | 12 | timestamps() 13 | end 14 | 15 | create index(:reward_txns, [:rewards_hash]) 16 | 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190725165802_add_data_credit_transaction_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.AddDataCreditTransactionTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:data_credit_transactions) do 6 | add :amount, :bigint, null: false 7 | add :payee, :binary, null: false 8 | add :status, :string, null: false, default: "cleared" 9 | 10 | add :hash, references(:transactions, on_delete: :nothing, column: :hash, type: :binary), null: false 11 | timestamps() 12 | end 13 | 14 | create unique_index(:data_credit_transactions, [:hash], name: :unique_data_credit_hash) 15 | 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190804014105_remove_pending_location_constraint.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.RemovePendingLocationConstraint do 2 | use Ecto.Migration 3 | 4 | def change do 5 | drop_if_exists(index("pending_locations", ["unique_pending_owner_gateway_nonce"], name: "unique_pending_owner_gateway_nonce")) 6 | end 7 | 8 | end 9 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190808002434_change_reward_amount_type.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.ChangeRewardAmountType do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:reward_txns) do 6 | modify :amount, :bigint, null: false 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190816231641_add_distance_to_witnesses.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.AddDistanceToWitnesses do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:poc_witnesses) do 6 | add :distance, :float, null: true 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190902083021_change_activity_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.ChangeActivityTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create_if_not_exists(index("hotspot_activity", ["gateway"], name: "gateway")) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190902084108_change_poc_request_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.ChangePocRequestTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create_if_not_exists(unique_index("poc_request_transactions", ["onion"], name: "unique_onion")) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190902084509_change_poc_receipts_transactions_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.ChangePocReceiptsTransactionsTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create_if_not_exists(unique_index("poc_receipts_transactions", ["poc_request_transactions_id"], name: "unique_poc_request")) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190902084944_change_transactions_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.ChangeTransactionsTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create_if_not_exists(index("transactions", ["block_height"], name: "block_height")) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190902085239_change_poc_witnesses_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.ChangePocWitnessesTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create_if_not_exists(index("poc_witnesses", ["poc_path_elements_id"], name: "path_witness")) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190902085252_change_poc_receipts_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.ChangePocReceiptsTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create_if_not_exists(unique_index("poc_receipts", ["poc_path_elements_id"], name: "unique_path_receipt")) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190902085808_change_pending_gateways_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.ChangePendingGatewaysTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create_if_not_exists(index("pending_gateways", ["owner"], name: "pending_gateway_owner")) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190902085920_change_pending_locations_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.ChangePendingLocationsTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create_if_not_exists(index("pending_locations", ["owner"], name: "pending_location_owner")) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190902090020_change_pending_payments_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.ChangePendingPaymentsTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create_if_not_exists(index("pending_payments", ["payee"], name: "pending_payments_payee")) 6 | create_if_not_exists(index("pending_payments", ["payer"], name: "pending_payments_payer")) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190902163742_change_account_transactions_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.ChangeAccountTransactionsTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create_if_not_exists(index("account_transactions", ["account_address"], name: "account_txn_address")) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190902171545_change_account_balances_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.ChangeAccountBalancesTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create_if_not_exists(index("account_balances", ["account_address"], name: "account_balance_address")) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190902174748_change_blocks_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.ChangeBlocksTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create_if_not_exists(unique_index("blocks", ["hash"], name: "unique_block_hash")) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190902211028_int8_account_balances_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.Int8AccountBalancesTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:account_balances) do 6 | modify :block_time, :bigint, null: false 7 | modify :block_height, :bigint, null: false 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190902211139_int8_poc_receipts_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.Int8PocReceiptsTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:poc_receipts) do 6 | modify :signal, :bigint, null: false 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190902211215_int8_poc_witnesses_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.Int8PocWitnessesTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:poc_witnesses) do 6 | modify :signal, :bigint, null: false 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190902211258_int8_elections_transaction_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.Int8ElectionsTransactionTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:election_transactions) do 6 | modify :delay, :bigint, null: false 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190902211338_int8_hotspot_activity_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.Int8HotspotActivityTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:hotspot_activity) do 6 | modify :election_id, :bigint, null: true 7 | modify :election_txn_block_time, :bigint, null: true 8 | modify :reward_block_time, :bigint, null: true 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190902211523_int8_rewards_transactions_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.Int8RewardsTransactionsTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:rewards_transactions) do 6 | modify :fee, :bigint, null: false 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190902211632_int8_blocks_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.Int8BlocksTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:blocks) do 6 | modify :round, :bigint, null: false 7 | modify :time, :bigint, null: false 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190902211758_int8_reward_txns_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.Int8RewardTxnsTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:reward_txns) do 6 | modify :amount, :bigint, null: false 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190906205324_change_index_pending_payments_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.ChangeIndexPendingPaymentsTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | drop_if_exists(unique_index("pending_payments", ["unique_pending_payment_nonce"], name: "unique_pending_payment_nonce")) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190911174348_add_index_poc_receipts_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.AddIndexPocReceiptsTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create_if_not_exists(index("poc_receipts", ["gateway"], name: "poc_receipts_gateway")) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190911174447_add_index_poc_witnesses_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.AddIndexPocWitnessesTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create_if_not_exists(index("poc_witnesses", ["gateway"], name: "poc_witnesses_gateway")) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /priv/repo/migrations/20190912181027_add_security_tokens_data_credits_to_accounts.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.AddSecurityTokensDataCreditsToAccounts do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:accounts) do 6 | add :security_balance, :bigint, null: false, default: 0 7 | add :security_nonce, :bigint, null: false, default: 0 8 | add :data_credit_balance, :bigint, null: false, default: 0 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /priv/repo/migrations/20191008194531_add_security_exchange_transactions_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.AddSecurityExchangeTransactionsTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:security_exchange_transactions) do 6 | add :amount, :bigint, null: false 7 | add :payee, :binary, null: false 8 | add :payer, :binary, null: false 9 | add :fee, :bigint, null: false 10 | add :nonce, :bigint, null: false 11 | add :signature, :binary, null: false 12 | add :status, :string, null: false, default: "cleared" 13 | 14 | add :hash, references(:transactions, on_delete: :nothing, column: :hash, type: :binary), null: false 15 | timestamps() 16 | end 17 | 18 | create unique_index(:security_exchange_transactions, [:hash], name: :unique_security_exchange_hash) 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /priv/repo/migrations/20191008201717_add_activitiy_indices.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.AddActivitiyIndices do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create_if_not_exists(index("hotspot_activity", ["poc_req_txn_block_height"], name: "poc_req_txn_block_height")) 6 | create_if_not_exists(index("hotspot_activity", ["poc_req_txn_block_time"], name: "poc_req_txn_block_time")) 7 | create_if_not_exists(index("hotspot_activity", ["poc_rx_txn_block_height"], name: "poc_rx_txn_block_height")) 8 | create_if_not_exists(index("hotspot_activity", ["poc_rx_txn_block_time"], name: "poc_rx_txn_block_time")) 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /priv/repo/migrations/20191020054507_add_hotspot_name.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.AddHotspotName do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table("hotspots") do 6 | add :name, :string, default: "", null: false 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20191022224043_add_pending_oui_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.AddPendingOUITable do 2 | use Ecto.Migration 3 | import Honeydew.EctoPollQueue.Migration 4 | import BlockchainAPI.Schema.PendingOUI, only: [submit_oui_queue: 0] 5 | 6 | def change do 7 | create table(:pending_ouis) do 8 | add :hash, :binary, null: false 9 | add :owner, :binary, null: false 10 | add :addresses, {:array, :string}, null: false, default: [] # can have empty addresses 11 | add :payer, :binary, null: true # can have an empty payer 12 | add :staking_fee, :bigint, null: false, default: 0 13 | add :oui, :bigint, null: false, default: 1 14 | add :fee, :bigint, null: false, default: 0 15 | add :txn, :binary, null: false 16 | add :submit_height, :bigint, null: false, default: 0 17 | add :status, :string, null: false, default: "pending" 18 | 19 | honeydew_fields(submit_oui_queue()) 20 | 21 | timestamps() 22 | end 23 | 24 | honeydew_indexes(:pending_ouis, submit_oui_queue()) 25 | end 26 | 27 | end 28 | -------------------------------------------------------------------------------- /priv/repo/migrations/20191022231530_add_oui_transactions_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.AddOuiTransactionsTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:oui_transactions) do 6 | add :owner, :binary, null: false 7 | add :payer, :binary, null: true # can be empty payer 8 | add :addresses, {:array, :string}, null: false, default: [] # can have empty addresses 9 | add :fee, :bigint, null: false, default: 0 10 | add :staking_fee, :bigint, null: false, default: 0 11 | add :oui, :bigint, null: false, default: 1 12 | add :status, :string, null: false, default: "cleared" 13 | 14 | add :hash, references(:transactions, on_delete: :nothing, column: :hash, type: :binary), null: false 15 | timestamps() 16 | end 17 | 18 | create unique_index(:oui_transactions, [:hash], name: :unique_oui_hash) 19 | end 20 | 21 | end 22 | -------------------------------------------------------------------------------- /priv/repo/migrations/20191029172131_add_pending_sec_exchange_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.AddPendingSecExchangeTable do 2 | use Ecto.Migration 3 | import Honeydew.EctoPollQueue.Migration 4 | import BlockchainAPI.Schema.PendingSecExchange, only: [submit_sec_exchange_queue: 0] 5 | 6 | def change do 7 | create table(:pending_sec_exchanges) do 8 | add :hash, :binary, null: false 9 | add :amount, :bigint, null: false 10 | add :payee, :binary, null: false 11 | add :payer, :binary, null: false 12 | add :fee, :bigint, null: false, default: 0 13 | add :nonce, :bigint, null: false, default: 1 14 | add :signature, :binary, null: false 15 | add :status, :string, null: false, default: "pending" 16 | add :txn, :binary, null: false 17 | add :submit_height, :bigint, null: false, default: 0 18 | 19 | honeydew_fields(submit_sec_exchange_queue()) 20 | 21 | timestamps() 22 | end 23 | 24 | honeydew_indexes(:pending_sec_exchanges, submit_sec_exchange_queue()) 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /priv/repo/migrations/20191217183729_witness_quality_filter.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.WitnessQualityFilter do 2 | use Ecto.Migration 3 | 4 | def change do 5 | alter table(:poc_witnesses) do 6 | add :is_good, :boolean, null: true, default: true 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /priv/repo/migrations/20200310231635_add_payment_v2_txn_table.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.AddPaymentV2TxnTable do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create table(:payment_v2_txns) do 6 | add :payer, :binary, null: false 7 | add :payments, :map, null: false 8 | add :fee, :bigint, null: false 9 | add :nonce, :bigint, null: false, default: 0 10 | add :status, :string, null: false, default: "cleared" 11 | 12 | add :hash, references(:transactions, on_delete: :nothing, column: :hash, type: :binary), null: false 13 | timestamps() 14 | end 15 | 16 | create unique_index(:payment_v2_txns, [:hash], name: :unique_payment_v2_hash) 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /priv/repo/migrations/20200513214727_drop_pending_payment_constraint.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.DropPendingPaymentConstraint do 2 | use Ecto.Migration 3 | 4 | def change do 5 | drop_if_exists(unique_index("pending_payments", ["unique_pending_payment"], name: "unique_pending_payment")) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /priv/repo/migrations/20200605195804_add_index_gateway_owner.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Repo.Migrations.AddIndexGatewayOwner do 2 | use Ecto.Migration 3 | 4 | def change do 5 | create_if_not_exists(index("gateway_transactions", ["owner"], name: "gateway_owner")) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /priv/repo/seeds.exs: -------------------------------------------------------------------------------- 1 | # Script for populating the database. You can run it as: 2 | # 3 | # mix run priv/repo/seeds.exs 4 | # 5 | # Inside the script, you can read and write to any of your 6 | # repositories directly: 7 | # 8 | # BlockchainAPI.Repo.insert!(%BlockchainAPI.SomeSchema{}) 9 | # 10 | # We recommend using the bang functions (`insert!`, `update!` 11 | # and so on) as they will fail if something goes wrong. 12 | -------------------------------------------------------------------------------- /priv/tasks/backfill_distance.ex: -------------------------------------------------------------------------------- 1 | defmodule Mix.Tasks.BackfillDistance do 2 | @start_apps [ 3 | :postgrex, 4 | :ecto, 5 | :ecto_sql, 6 | ] 7 | 8 | @repo BlockchainAPI.Repo 9 | 10 | use Mix.Task 11 | 12 | import Ecto.Query, warn: false 13 | 14 | alias BlockchainAPI.{ 15 | Repo, 16 | Schema.POCWitness, 17 | Schema.POCPathElement, 18 | Util 19 | } 20 | 21 | defp boot() do 22 | IO.puts "Booting pre hook..." 23 | # Ensure postgrex and ecto applications started 24 | Enum.each(@start_apps, &Application.ensure_all_started/1) 25 | end 26 | 27 | defp start_connection() do 28 | {:ok, _ } = @repo.start_link(pool_size: 10) 29 | end 30 | 31 | # Took ~20s to run locally on ~53k records 32 | @shortdoc "Backfills poc_witnesses.distance field" 33 | def run(_) do 34 | boot() 35 | start_connection() 36 | IO.puts("Backfilling distance...") 37 | 38 | query = from( 39 | wx in POCWitness, 40 | where: is_nil(wx.distance), 41 | left_join: path_element in POCPathElement, 42 | on: wx.poc_path_elements_id == path_element.id, 43 | select: %{ 44 | wx_id: wx.id, 45 | wx_loc: wx.location, 46 | challengee_loc: path_element.challengee_loc 47 | } 48 | ) 49 | 50 | witnesses_without_distance = query |> Repo.replica.all() 51 | 52 | for %{wx_id: wx_id, wx_loc: wx_loc, challengee_loc: challengee_loc} <- witnesses_without_distance do 53 | distance = Util.h3_distance_in_meters( 54 | wx_loc |> Util.h3_from_string(), 55 | challengee_loc |> Util.h3_from_string() 56 | ) 57 | %POCWitness{id: wx_id} 58 | |> POCWitness.changeset(%{distance: distance}) 59 | |> Repo.update() 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /priv/tasks/backfill_hotspot_name.ex: -------------------------------------------------------------------------------- 1 | defmodule Mix.Tasks.BackfillHotspotName do 2 | @start_apps [ 3 | :postgrex, 4 | :ecto, 5 | :ecto_sql, 6 | ] 7 | 8 | @repo BlockchainAPI.Repo 9 | 10 | use Mix.Task 11 | 12 | import Ecto.Query, warn: false 13 | 14 | alias BlockchainAPI.{ 15 | Repo, 16 | Schema.Hotspot, 17 | Query 18 | } 19 | 20 | defp boot() do 21 | IO.puts "Booting pre hook..." 22 | # Ensure postgrex and ecto applications started 23 | Enum.each(@start_apps, &Application.ensure_all_started/1) 24 | end 25 | 26 | defp start_connection() do 27 | {:ok, _ } = @repo.start_link(pool_size: 10) 28 | {:ok, _ } = @repo.replica.start_link(pool_size: 10) 29 | end 30 | 31 | @shortdoc "Backfills hotspot.name field" 32 | def run(_) do 33 | boot() 34 | start_connection() 35 | IO.puts("Backfilling hotspot names...") 36 | 37 | query = from( 38 | h in Hotspot, 39 | where: h.name == "" 40 | ) 41 | 42 | query 43 | |> IO.inspect() 44 | |> Repo.replica.all() 45 | |> IO.inspect() 46 | |> Enum.map( 47 | fn(hotspot) -> 48 | IO.inspect hotspot 49 | Query.Hotspot.update(hotspot, %{name: Hotspot.animal_name(hotspot.address)}) 50 | end) 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /priv/tasks/release_tasks.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Tasks.ReleaseTasks do 2 | @start_apps [ 3 | :postgrex, 4 | :ecto, 5 | :ecto_sql, 6 | :blockchain 7 | ] 8 | 9 | @repo BlockchainAPI.Repo 10 | 11 | @otp_app :blockchain_api 12 | 13 | def setup do 14 | boot() 15 | create_database() 16 | start_connection() 17 | run_migrations() 18 | end 19 | 20 | defp boot() do 21 | IO.puts "Booting pre hook..." 22 | # Ensure postgrex and ecto applications started 23 | Enum.each(@start_apps, &Application.ensure_all_started/1) 24 | end 25 | 26 | defp create_database() do 27 | IO.puts "Creating the database if needed..." 28 | @repo.__adapter__.storage_up(@repo.config) 29 | end 30 | 31 | defp start_connection() do 32 | IO.puts "Starting repos..." 33 | IO.puts "#{inspect(System.get_env("MIX_ENV"))}" 34 | case System.get_env("MIX_ENV") do 35 | "prod" -> 36 | {:ok, _ } = @repo.start_link(pool_size: 10) 37 | {:ok, _ } = @repo.replica.start_link(pool_size: 10) 38 | _ -> 39 | # only start repo in all other envs except prod 40 | {:ok, _ } = @repo.start_link(pool_size: 10) 41 | end 42 | end 43 | 44 | defp run_migrations() do 45 | IO.puts "Running migrations..." 46 | Ecto.Migrator.run(@repo, migrations_path(), :up, all: true) 47 | end 48 | 49 | defp migrations_path(), do: Path.join([priv_dir(), "repo", "migrations"]) 50 | 51 | defp priv_dir(), do: "#{:code.priv_dir(@otp_app)}" 52 | end 53 | -------------------------------------------------------------------------------- /priv/test/genesis: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/novalabsxyz/blockchain-api/3c9fbc892e645f9bb144414f3da36749603f37bc/priv/test/genesis -------------------------------------------------------------------------------- /rebar3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/novalabsxyz/blockchain-api/3c9fbc892e645f9bb144414f3da36749603f37bc/rebar3 -------------------------------------------------------------------------------- /rel/commands/genesis: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | case $1 in 3 | help) 4 | echo "$0 load " 5 | exit 0 6 | ;; 7 | load) 8 | release_remote_ctl rpc "BlockchainAPI.CLI.load_genesis(\"$2\")" 9 | ;; 10 | onboard) 11 | release_remote_ctl rpc "BlockchainAPI.CLI.load_genesis()" 12 | ;; 13 | esac 14 | -------------------------------------------------------------------------------- /rel/commands/ledger: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ -t 0 ] ; then 4 | export CLIQUE_COLUMNS=`stty size | cut -d ' ' -f 2` 5 | fi 6 | release_remote_ctl rpc --mfa 'BlockchainAPI.CLI.clique_command/1' --argv -- ledger $@ 7 | -------------------------------------------------------------------------------- /rel/commands/peer: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ -t 0 ] ; then 4 | export CLIQUE_COLUMNS=`stty size | cut -d ' ' -f 2` 5 | fi 6 | release_remote_ctl rpc --mfa 'BlockchainAPI.CLI.clique_command/1' --argv -- peer $@ 7 | -------------------------------------------------------------------------------- /rel/commands/snapshot: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ -t 0 ] ; then 4 | export CLIQUE_COLUMNS=`stty size | cut -d ' ' -f 2` 5 | fi 6 | release_remote_ctl rpc --mfa 'BlockchainAPI.CLI.clique_command/1' --argv -- snapshot $@ 7 | -------------------------------------------------------------------------------- /rel/commands/status: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | case $1 in 4 | height) 5 | release_remote_ctl rpc "BlockchainAPI.CLI.height()" 6 | ;; 7 | esac 8 | -------------------------------------------------------------------------------- /rel/config/dev.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | # For production, don't forget to configure the url host 4 | # to something meaningful, Phoenix uses this information 5 | # when generating URLs. 6 | # 7 | # Note we also include the path to a cache manifest 8 | # containing the digested version of static files. This 9 | # manifest is generated by the `mix phx.digest` task, 10 | # which you should run after static files are built and 11 | # before starting your production server. 12 | port = String.to_integer(System.get_env("PORT") || "4000") 13 | ro_mode = String.to_integer(System.get_env("RO_MODE") || "1") 14 | 15 | config :blockchain_api, BlockchainAPIWeb.Endpoint, 16 | http: [port: port], 17 | url: [host: System.get_env("HOSTNAME") || "localhost", port: port], 18 | server: true, 19 | root: ".", 20 | version: Application.spec(:blockchain_api, :vsn), 21 | check_origin: false, 22 | # force_ssl: [hsts: true, rewrite_on: [:x_forwarded_proto]], 23 | secret_key_base: System.get_env("SECRET_KEY_BASE") 24 | 25 | # cache_static_manifest: "priv/static/cache_manifest.json" 26 | 27 | config :blockchain_api, 28 | env: Mix.env(), 29 | google_maps_secret: System.get_env("GOOGLE_MAPS_API_KEY"), 30 | notifier_client: BlockchainAPI.FakeNotifierClient, 31 | repos: [master: BlockchainAPI.Repo, replica: BlockchainAPI.Repo] # no replica in dev mode 32 | 33 | # Configure your database 34 | config :blockchain_api, BlockchainAPI.Repo, 35 | username: System.get_env("DATABASE_USER"), 36 | password: System.get_env("DATABASE_PASS"), 37 | database: System.get_env("DATABASE_NAME"), 38 | hostname: System.get_env("DATABASE_HOST"), 39 | pool_size: 20, 40 | timeout: :infinity, 41 | queue_target: 120_000, 42 | queue_interval: 5_000 43 | 44 | config :blockchain, 45 | env: Mix.env(), 46 | base_dir: String.to_charlist("/var/data/blockchain-api/dev/") 47 | -------------------------------------------------------------------------------- /rel/config/pescadero.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | port = String.to_integer(System.get_env("PORT") || "4002") 4 | 5 | config :blockchain_api, BlockchainAPIWeb.Endpoint, 6 | http: [port: port], 7 | url: [host: System.get_env("HOSTNAME") || "localhost", port: port], 8 | server: true, 9 | root: ".", 10 | version: Application.spec(:blockchain_api, :vsn), 11 | check_origin: false, 12 | # force_ssl: [hsts: true, rewrite_on: [:x_forwarded_proto]], 13 | secret_key_base: System.get_env("SECRET_KEY_BASE") 14 | 15 | # cache_static_manifest: "priv/static/cache_manifest.json" 16 | 17 | config :blockchain_api, 18 | env: Mix.env(), 19 | google_maps_secret: System.get_env("GOOGLE_MAPS_API_KEY"), 20 | notifier_client: BlockchainAPI.FakeNotifierClient 21 | 22 | # Configure your database 23 | config :blockchain_api, BlockchainAPI.Repo, 24 | username: System.get_env("PESCADERO_DB_USER"), 25 | password: System.get_env("PESCADERO_DB_PASS"), 26 | database: System.get_env("PESCADERO_DB"), 27 | hostname: System.get_env("PESCADERO_DB_HOST"), 28 | pool_size: 20, 29 | timeout: 120_000, 30 | log: false 31 | 32 | # Don't connect pescadero to seed nodes 33 | config :blockchain, 34 | seed_nodes: [], 35 | seed_node_dns: '', 36 | base_dir: String.to_charlist("/var/data/blockchain-api/pescadero/") 37 | -------------------------------------------------------------------------------- /rel/config/prod.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | # For production, don't forget to configure the url host 4 | # to something meaningful, Phoenix uses this information 5 | # when generating URLs. 6 | # 7 | # Note we also include the path to a cache manifest 8 | # containing the digested version of static files. This 9 | # manifest is generated by the `mix phx.digest` task, 10 | # which you should run after static files are built and 11 | # before starting your production server. 12 | port = String.to_integer(System.get_env("PORT") || "4001") 13 | ro_mode = String.to_integer(System.get_env("RO_MODE") || "1") 14 | 15 | config :blockchain_api, BlockchainAPIWeb.Endpoint, 16 | http: [port: port], 17 | url: [host: System.get_env("HOSTNAME") || "localhost", port: port], 18 | server: true, 19 | root: ".", 20 | version: Application.spec(:blockchain_api, :vsn), 21 | check_origin: false, 22 | # force_ssl: [hsts: true, rewrite_on: [:x_forwarded_proto]], 23 | secret_key_base: System.get_env("SECRET_KEY_BASE") 24 | 25 | # cache_static_manifest: "priv/static/cache_manifest.json" 26 | 27 | config :blockchain_api, 28 | env: Mix.env(), 29 | google_maps_secret: System.get_env("GOOGLE_MAPS_API_KEY"), 30 | onesignal_rest_api_key: System.get_env("ONESIGNAL_API_KEY"), 31 | onesignal_app_id: System.get_env("ONESIGNAL_APP_ID"), 32 | notifier_client: BlockchainAPI.NotifierClient, 33 | repos: [master: BlockchainAPI.Repo, replica: BlockchainAPI.RORepo] # different replica in prod mode 34 | 35 | # Configure your database 36 | config :blockchain_api, BlockchainAPI.Repo, 37 | username: System.get_env("DATABASE_USER"), 38 | password: System.get_env("DATABASE_PASS"), 39 | database: System.get_env("DATABASE_NAME"), 40 | hostname: System.get_env("DATABASE_HOST"), 41 | pool_size: 10, 42 | timeout: 120_000, 43 | queue_target: 10_000, 44 | queue_interval: 5_000, 45 | log: false 46 | 47 | config :blockchain_api, BlockchainAPI.RORepo, 48 | username: System.get_env("DATABASE_USER"), 49 | password: System.get_env("DATABASE_PASS"), 50 | database: System.get_env("DATABASE_NAME"), 51 | # only the database host changes for read-only replica 52 | hostname: System.get_env("RO_DATABASE_HOST"), 53 | pool_size: 10, 54 | timeout: 120_000, 55 | queue_target: 30_000, 56 | queue_interval: 5_000, 57 | log: false 58 | 59 | config :blockchain, 60 | base_dir: String.to_charlist("/var/data/blockchain-api/prod/") 61 | -------------------------------------------------------------------------------- /rel/hooks/pre_start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | bin/blockchain_api command Elixir.BlockchainAPI.Tasks.ReleaseTasks setup 3 | -------------------------------------------------------------------------------- /rel/plugins/.gitignore: -------------------------------------------------------------------------------- 1 | *.* 2 | !*.exs 3 | !.gitignore -------------------------------------------------------------------------------- /rel/vm.args: -------------------------------------------------------------------------------- 1 | ## This file provide the arguments provided to the VM at startup 2 | ## You can find a full list of flags and their behaviours at 3 | ## http://erlang.org/doc/man/erl.html 4 | 5 | ## Name of the node 6 | -name <%= release_name %>@127.0.0.1 7 | 8 | ## Cookie for distributed erlang 9 | -setcookie <%= release.profile.cookie %> 10 | 11 | ## Heartbeat management; auto-restarts VM if it dies or becomes unresponsive 12 | ## (Disabled by default..use with caution!) 13 | ##-heart 14 | 15 | ## Enable kernel poll and a few async threads 16 | ##+K true 17 | ##+A 5 18 | ## For OTP21+, the +A flag is not used anymore, 19 | ## +SDio replace it to use dirty schedulers 20 | ##+SDio 5 21 | 22 | ## Increase number of concurrent ports/sockets 23 | ##-env ERL_MAX_PORTS 4096 24 | 25 | ## Tweak GC to run more often 26 | ##-env ERL_FULLSWEEP_AFTER 10 27 | 28 | # Enable SMP automatically based on availability 29 | # On OTP21+, this is not needed anymore. 30 | -smp auto 31 | -------------------------------------------------------------------------------- /test/Makefile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/novalabsxyz/blockchain-api/3c9fbc892e645f9bb144414f3da36749603f37bc/test/Makefile -------------------------------------------------------------------------------- /test/blockchain_api/controllers/block_controller_test.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.BlockControllerTest do 2 | use BlockchainAPIWeb.ConnCase 3 | alias BlockchainAPI.Query 4 | 5 | @num_blocks 2000 6 | @default_limit 100 7 | @max_limit 1000 8 | 9 | describe "test block controller" do 10 | setup do 11 | blocks = 12 | Range.new(1, @num_blocks) 13 | |> Enum.map(fn h -> 14 | block_map = %{hash: :crypto.strong_rand_bytes(32), height: h, round: h, time: h} 15 | {:ok, b} = Query.Block.create(block_map) 16 | b 17 | end) 18 | 19 | case length(blocks) == @num_blocks do 20 | true -> :ok 21 | false -> :error 22 | end 23 | end 24 | 25 | test "block index/2 returns #{@default_limit} blocks with no limit", %{conn: conn} do 26 | %{"data" => blocks} = 27 | conn 28 | |> get(Routes.block_path(conn, :index, %{})) 29 | |> json_response(200) 30 | 31 | assert length(blocks) == @default_limit 32 | end 33 | 34 | test "block index/2 returns #{@max_limit} blocks when limit > #{@max_limit}", %{conn: conn} do 35 | %{"data" => blocks} = 36 | conn 37 | |> get(Routes.block_path(conn, :index, %{"limit" => 5000})) 38 | |> json_response(200) 39 | 40 | assert length(blocks) == @max_limit 41 | end 42 | 43 | test "block index/2 before without limit", %{conn: conn} do 44 | %{"data" => blocks} = 45 | conn 46 | |> get(Routes.block_path(conn, :index, %{"before" => 200})) 47 | |> json_response(200) 48 | 49 | assert length(blocks) == @default_limit 50 | end 51 | 52 | test "block index/2 with valid limit", %{conn: conn} do 53 | %{"data" => blocks} = 54 | conn 55 | |> get(Routes.block_path(conn, :index, %{"limit" => 400})) 56 | |> json_response(200) 57 | 58 | assert length(blocks) == 400 59 | end 60 | 61 | test "block index/2 before with invalid limit", %{conn: conn} do 62 | %{"data" => blocks} = 63 | conn 64 | |> get(Routes.block_path(conn, :index, %{"before" => 1800, "limit" => 1500})) 65 | |> json_response(200) 66 | 67 | assert length(blocks) == @max_limit 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /test/blockchain_api/controllers/challenge_controller_test.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.ChallengeControllerTest do 2 | use BlockchainAPIWeb.ConnCase 3 | import BlockchainAPI.TestHelpers 4 | alias BlockchainAPI.Query 5 | 6 | @default_limit 100 7 | @max_limit 500 8 | 9 | describe "test challenge controller" do 10 | setup do 11 | _challenges = insert_fake_challenges() 12 | :ok 13 | end 14 | 15 | test "challenge index/2 returns #{@default_limit} challenges with no limit", %{conn: conn} do 16 | %{"data" => challenges} = 17 | conn 18 | |> get(Routes.challenge_path(conn, :index, %{})) 19 | |> json_response(200) 20 | 21 | assert length(challenges) == @default_limit 22 | end 23 | 24 | test "challenge index/2 returns #{@max_limit} challenges when limit > #{@max_limit}", %{ 25 | conn: conn 26 | } do 27 | %{"data" => challenges} = 28 | conn 29 | |> get(Routes.challenge_path(conn, :index, %{"limit" => 1000})) 30 | |> json_response(200) 31 | 32 | assert length(challenges) == @max_limit 33 | end 34 | 35 | test "challenge index/2 before without limit", %{conn: conn} do 36 | last_poc_id = Query.POCReceiptsTransaction.last_poc_id() 37 | 38 | %{"data" => challenges} = 39 | conn 40 | |> get(Routes.challenge_path(conn, :index, %{"before" => last_poc_id})) 41 | |> json_response(200) 42 | 43 | assert length(challenges) == @default_limit 44 | end 45 | 46 | test "challenge index/2 with valid limit", %{conn: conn} do 47 | valid_limit = 99 48 | 49 | %{"data" => challenges} = 50 | conn 51 | |> get(Routes.challenge_path(conn, :index, %{"limit" => valid_limit})) 52 | |> json_response(200) 53 | 54 | assert length(challenges) == valid_limit 55 | end 56 | 57 | test "challenge index/2 before with invalid limit", %{conn: conn} do 58 | last_poc_id = Query.POCReceiptsTransaction.last_poc_id() 59 | 60 | %{"data" => challenges} = 61 | conn 62 | |> get(Routes.challenge_path(conn, :index, %{"before" => last_poc_id, "limit" => 600})) 63 | |> json_response(200) 64 | 65 | assert length(challenges) == @max_limit 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /test/blockchain_api/controllers/stats_controller_test.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.StatsControllerTest do 2 | use BlockchainAPIWeb.ConnCase 3 | alias BlockchainAPI.{Query, Util} 4 | import BlockchainAPI.TestHelpers 5 | 6 | describe "StatsController" do 7 | setup do 8 | account_fixture(%{balance: 2, address: "account1"}) 9 | account_fixture(%{balance: 5, address: "account2"}) 10 | 11 | block_fixture(%{time: Util.shifted_unix_time(hours: -21), height: 6}) 12 | block_fixture(%{time: Util.shifted_unix_time(hours: -22), height: 5}) 13 | block_fixture(%{time: Util.shifted_unix_time(hours: -26), height: 4}) 14 | block_fixture(%{time: Util.shifted_unix_time(hours: -28), height: 3}) 15 | block_fixture(%{time: Util.shifted_unix_time(hours: -31), height: 2}) 16 | block_fixture(%{time: Util.shifted_unix_time(hours: -45), height: 1}) 17 | 18 | :ok 19 | end 20 | 21 | test "stats index/2 returns supply stats" do 22 | %{"data" => %{"token_supply" => %{"total" => total_supply}}} = 23 | build_conn 24 | |> get(Routes.stats_path(conn, :show, %{})) 25 | |> json_response(200) 26 | 27 | assert total_supply == 7 28 | end 29 | 30 | test "stats index/2 returns block time stats" do 31 | %{ 32 | "data" => %{ 33 | "block_time" => %{ 34 | "24h" => day_block_time, 35 | "7d" => week_block_time, 36 | "30d" => month_block_time 37 | } 38 | } 39 | } = 40 | build_conn 41 | |> get(Routes.stats_path(conn, :show, %{})) 42 | |> json_response(200) 43 | 44 | assert day_block_time == 9000.0 45 | assert week_block_time == 17280.0 46 | assert month_block_time == 17280.0 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /test/blockchain_api/controllers/transaction_controller_test.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.TransactionControllerTest do 2 | use BlockchainAPIWeb.ConnCase 3 | 4 | alias BlockchainAPI.{ 5 | Query, 6 | Util 7 | } 8 | 9 | describe "index/2" do 10 | setup [:insert_election_transactions] 11 | 12 | test "returns election transaction consensus members for a given block", %{ 13 | conn: conn, 14 | members: [member1, member2], 15 | block: b 16 | } do 17 | BlockchainAPI.Repo.replica.all(BlockchainAPI.Schema.ConsensusMember) 18 | 19 | %{"data" => [%{"members" => member_addresses}]} = 20 | conn 21 | |> get(Routes.transaction_path(conn, :index, %{"block_height" => b.height})) 22 | |> json_response(200) 23 | 24 | [address1, address2] = Enum.sort(member_addresses) 25 | 26 | assert address1 == Util.bin_to_string(member1.address) 27 | assert address2 == Util.bin_to_string(member2.address) 28 | end 29 | end 30 | 31 | defp insert_election_transactions(_) do 32 | {:ok, b} = 33 | Query.Block.create(%{ 34 | hash: "hash1", 35 | height: 1, 36 | round: 1, 37 | time: 1 38 | }) 39 | 40 | {:ok, t} = 41 | Query.Transaction.create(b.height, %{ 42 | hash: "hash2", 43 | type: "election" 44 | }) 45 | 46 | {:ok, et} = 47 | Query.ElectionTransaction.create(%{ 48 | hash: t.hash, 49 | proof: "proof1", 50 | delay: 1, 51 | election_height: b.height 52 | }) 53 | 54 | {:ok, cm1} = 55 | Query.ConsensusMember.create(%{ 56 | address: "address1", 57 | election_transactions_id: et.id 58 | }) 59 | 60 | {:ok, cm2} = 61 | Query.ConsensusMember.create(%{ 62 | address: "address2", 63 | election_transactions_id: et.id 64 | }) 65 | 66 | {:ok, members: [cm1, cm2], block: b} 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /test/blockchain_api/data/block_test.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Test.Data.Block do 2 | import BlockchainAPI.Test.Factory 3 | use BlockchainAPI.DataCase 4 | alias BlockchainAPI.Query 5 | 6 | test "block insert" do 7 | _blocks = Enum.map(1..200, fn(_) -> insert(:block) end) 8 | queried = Query.Block.list(%{"limit" => "200"}) 9 | assert length(queried) == 200 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /test/blockchain_api/data/oui_txn_test.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Test.Data.OUITxn do 2 | import BlockchainAPI.Test.Factory 3 | use BlockchainAPI.DataCase 4 | alias BlockchainAPI.Query 5 | 6 | test "oui txn insert" do 7 | 1..200 8 | |> Enum.map( 9 | fn(_) -> 10 | txn = insert(:transaction, %{type: "oui"}) 11 | _oui_txn = insert(:oui_transaction, %{txn: txn}) 12 | end) 13 | 14 | queried = Query.OUITransaction.list(%{}) 15 | assert length(queried) == 200 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/blockchain_api/notifiers/payments_notifier_test.exs: -------------------------------------------------------------------------------- 1 | defmodule PaymentsNotifierTest do 2 | use ExUnit.Case 3 | 4 | alias BlockchainAPI.Util 5 | 6 | test "whole number far greater than bones" do 7 | amount = 1_000_000_000_000_000 8 | converted = Util.units(amount) 9 | assert converted == "10,000,000" 10 | end 11 | 12 | test "whole number greater than bones" do 13 | amount = 1_000_000_000 14 | converted = Util.units(amount) 15 | assert converted == "10" 16 | end 17 | 18 | test "decimal number greater than bones" do 19 | amount = 1_000_000_000.10 20 | converted = Util.units(amount) 21 | assert converted == "10.000000001" 22 | end 23 | 24 | test "whole number less than bones" do 25 | amount = 10000 26 | converted = Util.units(amount) 27 | assert converted == "0.0001" 28 | end 29 | 30 | test "decimal number less than bones" do 31 | amount = 10000.123 32 | converted = Util.units(amount) 33 | assert converted == "0.00010000123" 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /test/blockchain_api/notifiers/rewards_notifier_test.exs: -------------------------------------------------------------------------------- 1 | defmodule RewardsNotifierTest do 2 | use BlockchainAPI.DataCase 3 | 4 | alias BlockchainAPI.{ 5 | Repo, 6 | RewardsNotifier 7 | } 8 | 9 | alias BlockchainAPI.Schema.{ 10 | Account, 11 | Block, 12 | RewardsTransaction, 13 | RewardTxn, 14 | Transaction 15 | } 16 | 17 | describe "send_notifications/0" do 18 | setup do 19 | account = 20 | Repo.insert!(%Account{ 21 | name: "account0", 22 | balance: 100, 23 | address: "address0", 24 | fee: 1, 25 | nonce: 0 26 | }) 27 | 28 | block = Repo.insert!(%Block{height: 1, hash: "hash1", round: 1, time: 1}) 29 | 30 | transaction = 31 | Repo.insert!(%Transaction{ 32 | hash: :crypto.strong_rand_bytes(32), 33 | type: "reward_txn", 34 | status: "cleared", 35 | block_height: block.height 36 | }) 37 | 38 | rewards_transaction = Repo.insert!(%RewardsTransaction{fee: 1, hash: transaction.hash}) 39 | 40 | Repo.insert!(%RewardTxn{ 41 | account: account.address, 42 | gateway: "gateway1", 43 | amount: 23, 44 | rewards_hash: rewards_transaction.hash, 45 | type: "reward_txn" 46 | }) 47 | 48 | :ok 49 | end 50 | 51 | test "notifier client succesfully sends reward txns and reschedules" do 52 | resp = RewardsNotifier.send_notifications() 53 | assert {:ok, _ref} = resp 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /test/blockchain_api/query/block_test.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.BlockTest do 2 | use BlockchainAPI.DataCase 3 | 4 | alias BlockchainAPI.Query 5 | 6 | describe "blocks" do 7 | alias BlockchainAPI.Schema.Block 8 | 9 | @valid_attrs %{hash: "some hash", height: 42, round: 42, time: 42} 10 | @invalid_attrs %{hash: nil, height: nil, round: nil, time: nil} 11 | 12 | def block_fixture(attrs \\ %{}) do 13 | {:ok, block} = 14 | attrs 15 | |> Enum.into(@valid_attrs) 16 | |> Query.Block.create() 17 | 18 | block 19 | end 20 | 21 | test "list_blocks/0 returns all blocks" do 22 | block = block_fixture() 23 | [b] = Query.Block.list(%{"limit" => "1"}) 24 | assert b.height == block.height and b.time == block.time 25 | end 26 | 27 | test "get_block!/1 returns the block with given id" do 28 | block = block_fixture() 29 | b = Query.Block.get(block.height) 30 | assert b.height == block.height and b.time == block.time 31 | end 32 | 33 | test "create_block/1 with valid data creates a block" do 34 | assert {:ok, %Block{} = block} = Query.Block.create(@valid_attrs) 35 | assert block.hash == "some hash" 36 | assert block.height == 42 37 | assert block.round == 42 38 | assert block.time == 42 39 | end 40 | 41 | test "create_block/1 with invalid data returns error changeset" do 42 | assert {:error, %Ecto.Changeset{}} = Query.Block.create(@invalid_attrs) 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /test/blockchain_api/query/poc_receipts_transaction.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Query.POCReceiptsTransactionTest do 2 | use BlockchainAPI.DataCase 3 | import BlockchainAPI.TestHelpers 4 | 5 | alias BlockchainAPI.Query.POCReiptsTransaction 6 | 7 | setup do 8 | insert_fake_challenges() 9 | :ok 10 | end 11 | 12 | test "returns challenges issued" do 13 | assert POCReceiptsTransaction.issued() == 1000 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /test/blockchain_api/query/stats_test.exs: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.Query.StatsTest do 2 | use BlockchainAPI.DataCase 3 | 4 | alias BlockchainAPI.Query.Stats 5 | alias BlockchainAPI.Util 6 | import BlockchainAPI.TestHelpers 7 | 8 | describe "supply" do 9 | test "get_supply/0 returns the total token supply" do 10 | account1 = account_fixture(%{balance: 2, address: "address1"}) 11 | account2 = account_fixture(%{balance: 5, address: "address2"}) 12 | assert Stats.get_supply() == account1.balance + account2.balance 13 | end 14 | end 15 | 16 | describe "block time" do 17 | test "get_block_time/1 given 24h shift returns avg block times" do 18 | block_fixture(%{time: Util.shifted_unix_time(seconds: -60), height: 3}) 19 | block_fixture(%{time: Util.shifted_unix_time(seconds: -125), height: 2}) 20 | block_fixture(%{time: Util.shifted_unix_time(seconds: -230), height: 1}) 21 | # intervals: 65, 105 22 | # avg: 85 23 | assert Stats.get_block_time(hours: -24) == 85.0 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /test/support/channel_case.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.ChannelCase do 2 | @moduledoc """ 3 | This module defines the test case to be used by 4 | channel tests. 5 | 6 | Such tests rely on `Phoenix.ChannelTest` and also 7 | import other functionality to make it easier 8 | to build common data structures and query the data layer. 9 | 10 | Finally, if the test case interacts with the database, 11 | it cannot be async. For this reason, every test runs 12 | inside a transaction which is reset at the beginning 13 | of the test unless the test case is marked as async. 14 | """ 15 | 16 | use ExUnit.CaseTemplate 17 | 18 | using do 19 | quote do 20 | # Import conveniences for testing with channels 21 | use Phoenix.ChannelTest 22 | 23 | # The default endpoint for testing 24 | @endpoint BlockchainAPIWeb.Endpoint 25 | end 26 | end 27 | 28 | setup tags do 29 | :ok = Ecto.Adapters.SQL.Sandbox.checkout(BlockchainAPI.Repo) 30 | 31 | unless tags[:async] do 32 | Ecto.Adapters.SQL.Sandbox.mode(BlockchainAPI.Repo, {:shared, self()}) 33 | end 34 | 35 | :ok 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /test/support/conn_case.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPIWeb.ConnCase do 2 | @moduledoc """ 3 | This module defines the test case to be used by 4 | tests that require setting up a connection. 5 | 6 | Such tests rely on `Phoenix.ConnTest` and also 7 | import other functionality to make it easier 8 | to build common data structures and query the data layer. 9 | 10 | Finally, if the test case interacts with the database, 11 | it cannot be async. For this reason, every test runs 12 | inside a transaction which is reset at the beginning 13 | of the test unless the test case is marked as async. 14 | """ 15 | 16 | use ExUnit.CaseTemplate 17 | 18 | using do 19 | quote do 20 | # Import conveniences for testing with connections 21 | use Phoenix.ConnTest 22 | alias BlockchainAPIWeb.Router.Helpers, as: Routes 23 | 24 | # The default endpoint for testing 25 | @endpoint BlockchainAPIWeb.Endpoint 26 | end 27 | end 28 | 29 | setup tags do 30 | :ok = Ecto.Adapters.SQL.Sandbox.checkout(BlockchainAPI.Repo) 31 | 32 | unless tags[:async] do 33 | Ecto.Adapters.SQL.Sandbox.mode(BlockchainAPI.Repo, {:shared, self()}) 34 | end 35 | 36 | {:ok, conn: Phoenix.ConnTest.build_conn()} 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /test/support/data_case.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.DataCase do 2 | @moduledoc """ 3 | This module defines the setup for tests requiring 4 | access to the application's data layer. 5 | 6 | You may define functions here to be used as helpers in 7 | your tests. 8 | 9 | Finally, if the test case interacts with the database, 10 | it cannot be async. For this reason, every test runs 11 | inside a transaction which is reset at the beginning 12 | of the test unless the test case is marked as async. 13 | """ 14 | 15 | use ExUnit.CaseTemplate 16 | 17 | using do 18 | quote do 19 | alias BlockchainAPI.Repo 20 | 21 | import Ecto 22 | import Ecto.Changeset 23 | import Ecto.Query 24 | import BlockchainAPI.DataCase 25 | end 26 | end 27 | 28 | setup tags do 29 | :ok = Ecto.Adapters.SQL.Sandbox.checkout(BlockchainAPI.Repo) 30 | 31 | unless tags[:async] do 32 | Ecto.Adapters.SQL.Sandbox.mode(BlockchainAPI.Repo, {:shared, self()}) 33 | end 34 | 35 | :ok 36 | end 37 | 38 | @doc """ 39 | A helper that transforms changeset errors into a map of messages. 40 | 41 | assert {:error, changeset} = Accounts.create_user(%{password: "short"}) 42 | assert "password is too short" in errors_on(changeset).password 43 | assert %{password: ["password is too short"]} = errors_on(changeset) 44 | 45 | """ 46 | def errors_on(changeset) do 47 | Ecto.Changeset.traverse_errors(changeset, fn {message, opts} -> 48 | Enum.reduce(opts, message, fn {key, value}, acc -> 49 | String.replace(acc, "%{#{key}}", to_string(value)) 50 | end) 51 | end) 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /test/support/fake_notifier_client.ex: -------------------------------------------------------------------------------- 1 | defmodule BlockchainAPI.FakeNotifierClient do 2 | def post(_data, _message, _send_address, _opts \\ %{}) do 3 | {:ok, %{}} 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | Ecto.Adapters.SQL.Sandbox.mode(BlockchainAPI.Repo, :manual) 3 | --------------------------------------------------------------------------------