From d563a2568adf987ccba5efb5a8742dc8e7c17071 Mon Sep 17 00:00:00 2001 From: Gustavo Santos Ferreira Date: Fri, 14 Dec 2018 15:30:24 -0200 Subject: [PATCH 1/7] add metadata column to address_names --- apps/explorer/lib/explorer/chain/address/name.ex | 6 ++++-- ...0181213111656_add_metadata_field_to_address_names.exs | 9 +++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 apps/explorer/priv/repo/migrations/20181213111656_add_metadata_field_to_address_names.exs diff --git a/apps/explorer/lib/explorer/chain/address/name.ex b/apps/explorer/lib/explorer/chain/address/name.ex index f2a6489375..926e15ef13 100644 --- a/apps/explorer/lib/explorer/chain/address/name.ex +++ b/apps/explorer/lib/explorer/chain/address/name.ex @@ -20,20 +20,22 @@ defmodule Explorer.Chain.Address.Name do address: %Ecto.Association.NotLoaded{} | Address.t(), address_hash: Hash.Address.t(), name: String.t(), - primary: boolean() + primary: boolean(), + metadata: map() } @primary_key false schema "address_names" do field(:name, :string) field(:primary, :boolean) + field(:metadata, :map) belongs_to(:address, Address, foreign_key: :address_hash, references: :hash, type: Hash.Address) timestamps() end @required_fields ~w(address_hash name)a - @optional_fields ~w(primary)a + @optional_fields ~w(primary metadata)a @allowed_fields @required_fields ++ @optional_fields def changeset(%__MODULE__{} = struct, params \\ %{}) do diff --git a/apps/explorer/priv/repo/migrations/20181213111656_add_metadata_field_to_address_names.exs b/apps/explorer/priv/repo/migrations/20181213111656_add_metadata_field_to_address_names.exs new file mode 100644 index 0000000000..593c381a29 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20181213111656_add_metadata_field_to_address_names.exs @@ -0,0 +1,9 @@ +defmodule Explorer.Repo.Migrations.AddMetadataFieldToAddressNames do + use Ecto.Migration + + def change do + alter table(:address_names) do + add(:metadata, :map) + end + end +end From 7549b8f8c80e11b4880928e81844bb718f01ed56 Mon Sep 17 00:00:00 2001 From: Gustavo Santos Ferreira Date: Fri, 14 Dec 2018 15:32:21 -0200 Subject: [PATCH 2/7] prepare configs and new dependencies for the retriever --- apps/explorer/config/config.exs | 6 + .../validator_contracts_abi/metadata.json | 579 ++++++++++++++++++ .../validator_contracts_abi/validators.json | 462 ++++++++++++++ 3 files changed, 1047 insertions(+) create mode 100644 apps/explorer/priv/validator_contracts_abi/metadata.json create mode 100644 apps/explorer/priv/validator_contracts_abi/validators.json diff --git a/apps/explorer/config/config.exs b/apps/explorer/config/config.exs index 154f6f93b4..4cabe0c008 100644 --- a/apps/explorer/config/config.exs +++ b/apps/explorer/config/config.exs @@ -32,6 +32,12 @@ config :explorer, Explorer.Tracer, adapter: SpandexDatadog.Adapter, trace_key: :blockscout +if System.get_env("METADATA_CONTRACT") && System.get_env("VALIDATORS_CONTRACT") do + config :explorer, Explorer.Validator.MetadataRetriever, + metadata_contract_address: System.get_env("METADATA_CONTRACT"), + validators_contract_address: System.get_env("VALIDATORS_CONTRACT") +end + if System.get_env("SUPPLY_MODULE") == "TransactionAndLog" do config :explorer, supply: Explorer.Chain.Supply.TransactionAndLog end diff --git a/apps/explorer/priv/validator_contracts_abi/metadata.json b/apps/explorer/priv/validator_contracts_abi/metadata.json new file mode 100644 index 0000000000..e6b619f843 --- /dev/null +++ b/apps/explorer/priv/validator_contracts_abi/metadata.json @@ -0,0 +1,579 @@ +[ + { + "constant": true, + "inputs": [ + { + "name": "_miningKey", + "type": "address" + } + ], + "name": "pendingChanges", + "outputs": [ + { + "name": "firstName", + "type": "bytes32" + }, + { + "name": "lastName", + "type": "bytes32" + }, + { + "name": "licenseId", + "type": "bytes32" + }, + { + "name": "fullAddress", + "type": "string" + }, + { + "name": "state", + "type": "bytes32" + }, + { + "name": "zipcode", + "type": "bytes32" + }, + { + "name": "expirationDate", + "type": "uint256" + }, + { + "name": "createdDate", + "type": "uint256" + }, + { + "name": "updatedDate", + "type": "uint256" + }, + { + "name": "minThreshold", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function", + "signature": "0x022c254a" + }, + { + "constant": false, + "inputs": [ + { + "name": "_firstName", + "type": "bytes32" + }, + { + "name": "_lastName", + "type": "bytes32" + }, + { + "name": "_licenseId", + "type": "bytes32" + }, + { + "name": "_fullAddress", + "type": "string" + }, + { + "name": "_state", + "type": "bytes32" + }, + { + "name": "_zipcode", + "type": "bytes32" + }, + { + "name": "_expirationDate", + "type": "uint256" + } + ], + "name": "createMetadata", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function", + "signature": "0x334460a4" + }, + { + "constant": true, + "inputs": [ + { + "name": "_miningKey", + "type": "address" + } + ], + "name": "confirmations", + "outputs": [ + { + "name": "count", + "type": "uint256" + }, + { + "name": "voters", + "type": "address[]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function", + "signature": "0x4ecb35c4" + }, + { + "constant": false, + "inputs": [ + { + "name": "_miningKey", + "type": "address" + } + ], + "name": "finalize", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function", + "signature": "0x4ef39b75" + }, + { + "constant": true, + "inputs": [], + "name": "getTime", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function", + "signature": "0x557ed1ba" + }, + { + "constant": false, + "inputs": [ + { + "name": "_miningKey", + "type": "address" + } + ], + "name": "clearMetadata", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function", + "signature": "0x885c69b5" + }, + { + "constant": false, + "inputs": [ + { + "name": "_firstName", + "type": "bytes32" + }, + { + "name": "_lastName", + "type": "bytes32" + }, + { + "name": "_licenseId", + "type": "bytes32" + }, + { + "name": "_fullAddress", + "type": "string" + }, + { + "name": "_state", + "type": "bytes32" + }, + { + "name": "_zipcode", + "type": "bytes32" + }, + { + "name": "_expirationDate", + "type": "uint256" + }, + { + "name": "_createdDate", + "type": "uint256" + }, + { + "name": "_updatedDate", + "type": "uint256" + }, + { + "name": "_minThreshold", + "type": "uint256" + }, + { + "name": "_miningKey", + "type": "address" + } + ], + "name": "initMetadata", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function", + "signature": "0x8ae881a6" + }, + { + "constant": false, + "inputs": [ + { + "name": "_miningKey", + "type": "address" + } + ], + "name": "confirmPendingChange", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function", + "signature": "0x9c715535" + }, + { + "constant": true, + "inputs": [], + "name": "initMetadataDisabled", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function", + "signature": "0xa6662a3c" + }, + { + "constant": true, + "inputs": [], + "name": "proxyStorage", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function", + "signature": "0xae4b1b5b" + }, + { + "constant": true, + "inputs": [ + { + "name": "_miningKey", + "type": "address" + } + ], + "name": "getValidatorName", + "outputs": [ + { + "name": "firstName", + "type": "bytes32" + }, + { + "name": "lastName", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function", + "signature": "0xaf2e2da9" + }, + { + "constant": false, + "inputs": [ + { + "name": "_oldMiningKey", + "type": "address" + }, + { + "name": "_newMiningKey", + "type": "address" + } + ], + "name": "moveMetadata", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function", + "signature": "0xc2d0916f" + }, + { + "constant": false, + "inputs": [ + { + "name": "_firstName", + "type": "bytes32" + }, + { + "name": "_lastName", + "type": "bytes32" + }, + { + "name": "_licenseId", + "type": "bytes32" + }, + { + "name": "_fullAddress", + "type": "string" + }, + { + "name": "_state", + "type": "bytes32" + }, + { + "name": "_zipcode", + "type": "bytes32" + }, + { + "name": "_expirationDate", + "type": "uint256" + } + ], + "name": "changeRequest", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function", + "signature": "0xc3f1b0ea" + }, + { + "constant": true, + "inputs": [], + "name": "getMinThreshold", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function", + "signature": "0xe6bbe9dd" + }, + { + "constant": false, + "inputs": [], + "name": "initMetadataDisable", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function", + "signature": "0xf0174a25" + }, + { + "constant": true, + "inputs": [ + { + "name": "_miningKey", + "type": "address" + }, + { + "name": "_voterMiningKey", + "type": "address" + } + ], + "name": "isValidatorAlreadyVoted", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function", + "signature": "0xf73294b8" + }, + { + "constant": false, + "inputs": [], + "name": "cancelPendingChange", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function", + "signature": "0xf94c12cb" + }, + { + "constant": true, + "inputs": [ + { + "name": "_miningKey", + "type": "address" + } + ], + "name": "validators", + "outputs": [ + { + "name": "firstName", + "type": "bytes32" + }, + { + "name": "lastName", + "type": "bytes32" + }, + { + "name": "licenseId", + "type": "bytes32" + }, + { + "name": "fullAddress", + "type": "string" + }, + { + "name": "state", + "type": "bytes32" + }, + { + "name": "zipcode", + "type": "bytes32" + }, + { + "name": "expirationDate", + "type": "uint256" + }, + { + "name": "createdDate", + "type": "uint256" + }, + { + "name": "updatedDate", + "type": "uint256" + }, + { + "name": "minThreshold", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function", + "signature": "0xfa52c7d8" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "miningKey", + "type": "address" + } + ], + "name": "MetadataCleared", + "type": "event", + "signature": "0x1928e25e316bab82325e01eaf5b4a29f7b2d5e3d77fc0b6ac959eb95112f3ee9" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "miningKey", + "type": "address" + } + ], + "name": "MetadataCreated", + "type": "event", + "signature": "0x76a10cc0839d166c55eec8dba39ff22f75470574ede5014b744b45cebea5fc7e" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "oldMiningKey", + "type": "address" + }, + { + "indexed": true, + "name": "newMiningKey", + "type": "address" + } + ], + "name": "MetadataMoved", + "type": "event", + "signature": "0x6a3e5f5cc14de86bb3be87bc976b74890ecd0c8fa0cfca2fe3fefe55c9b47dde" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "miningKey", + "type": "address" + } + ], + "name": "ChangeRequestInitiated", + "type": "event", + "signature": "0x6ec0e3afd4b29a1fe1c688cb1e6474d9e2c6a1032858c3add0ff32b7ba95f32f" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "miningKey", + "type": "address" + } + ], + "name": "CancelledRequest", + "type": "event", + "signature": "0x5a1dcc05b2a41ad121a798e749b9ba9584177c68a4047ee52ef37d4ca76ce08c" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "miningKey", + "type": "address" + }, + { + "indexed": false, + "name": "votingSender", + "type": "address" + }, + { + "indexed": false, + "name": "votingSenderMiningKey", + "type": "address" + } + ], + "name": "Confirmed", + "type": "event", + "signature": "0x603c57ecb4a9802537649ceb6523e5d48c939e7856768ce6f9b3128a889a5cfe" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "miningKey", + "type": "address" + } + ], + "name": "FinalizedChange", + "type": "event", + "signature": "0xccf0f685803f0fba33ec88246b35d75b758b1e77c3d65ef5658f7e630f36b85b" + } + ] \ No newline at end of file diff --git a/apps/explorer/priv/validator_contracts_abi/validators.json b/apps/explorer/priv/validator_contracts_abi/validators.json new file mode 100644 index 0000000000..5ef15d04c6 --- /dev/null +++ b/apps/explorer/priv/validator_contracts_abi/validators.json @@ -0,0 +1,462 @@ +[ + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "uint256" + } + ], + "name": "pendingList", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function", + "signature": "0x03aca792" + }, + { + "constant": true, + "inputs": [], + "name": "getCurrentValidatorsLength", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function", + "signature": "0x0eaba26a" + }, + { + "constant": false, + "inputs": [ + { + "name": "_newAddress", + "type": "address" + } + ], + "name": "setProxyStorage", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function", + "signature": "0x10855269" + }, + { + "constant": false, + "inputs": [ + { + "name": "_validator", + "type": "address" + }, + { + "name": "_shouldFireEvent", + "type": "bool" + } + ], + "name": "addValidator", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function", + "signature": "0x21a3fb85" + }, + { + "constant": true, + "inputs": [], + "name": "isMasterOfCeremonyRemovedPending", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function", + "signature": "0x273cb593" + }, + { + "constant": true, + "inputs": [], + "name": "isMasterOfCeremonyRemoved", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function", + "signature": "0x379fed9a" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "address" + } + ], + "name": "validatorsState", + "outputs": [ + { + "name": "isValidator", + "type": "bool" + }, + { + "name": "isValidatorFinalized", + "type": "bool" + }, + { + "name": "index", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function", + "signature": "0x4110a489" + }, + { + "constant": true, + "inputs": [], + "name": "getPendingList", + "outputs": [ + { + "name": "", + "type": "address[]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function", + "signature": "0x45199e0a" + }, + { + "constant": false, + "inputs": [], + "name": "finalizeChange", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function", + "signature": "0x75286211" + }, + { + "constant": false, + "inputs": [ + { + "name": "_newKey", + "type": "address" + }, + { + "name": "_oldKey", + "type": "address" + } + ], + "name": "swapValidatorKey", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function", + "signature": "0x879736b2" + }, + { + "constant": true, + "inputs": [ + { + "name": "_someone", + "type": "address" + } + ], + "name": "isValidatorFinalized", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function", + "signature": "0x8f2eabe1" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "uint256" + } + ], + "name": "currentValidators", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function", + "signature": "0x900eb5a8" + }, + { + "constant": true, + "inputs": [], + "name": "getKeysManager", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function", + "signature": "0x9a573786" + }, + { + "constant": true, + "inputs": [], + "name": "wasProxyStorageSet", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function", + "signature": "0xa5f8b874" + }, + { + "constant": true, + "inputs": [], + "name": "getCurrentValidatorsLengthWithoutMoC", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function", + "signature": "0xa8756337" + }, + { + "constant": true, + "inputs": [], + "name": "proxyStorage", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function", + "signature": "0xae4b1b5b" + }, + { + "constant": true, + "inputs": [], + "name": "finalized", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function", + "signature": "0xb3f05b97" + }, + { + "constant": true, + "inputs": [], + "name": "getValidators", + "outputs": [ + { + "name": "", + "type": "address[]" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function", + "signature": "0xb7ab4db5" + }, + { + "constant": true, + "inputs": [], + "name": "systemAddress", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function", + "signature": "0xd3e848f1" + }, + { + "constant": true, + "inputs": [], + "name": "masterOfCeremonyPending", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function", + "signature": "0xec7de1e9" + }, + { + "constant": false, + "inputs": [ + { + "name": "_validator", + "type": "address" + }, + { + "name": "_shouldFireEvent", + "type": "bool" + } + ], + "name": "removeValidator", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function", + "signature": "0xf89a77b1" + }, + { + "constant": true, + "inputs": [], + "name": "masterOfCeremony", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function", + "signature": "0xfa81b200" + }, + { + "constant": true, + "inputs": [ + { + "name": "_someone", + "type": "address" + } + ], + "name": "isValidator", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function", + "signature": "0xfacd743b" + }, + { + "inputs": [ + { + "name": "_masterOfCeremony", + "type": "address" + }, + { + "name": "validators", + "type": "address[]" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor", + "signature": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "parentHash", + "type": "bytes32" + }, + { + "indexed": false, + "name": "newSet", + "type": "address[]" + } + ], + "name": "InitiateChange", + "type": "event", + "signature": "0x55252fa6eee4741b4e24a74a70e9c11fd2c2281df8d6ea13126ff845f7825c89" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "newSet", + "type": "address[]" + } + ], + "name": "ChangeFinalized", + "type": "event", + "signature": "0x8564cd629b15f47dc310d45bcbfc9bcf5420b0d51bf0659a16c67f91d2763253" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "proxyStorage", + "type": "address" + } + ], + "name": "MoCInitializedProxyStorage", + "type": "event", + "signature": "0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22" + } + ] \ No newline at end of file From be397fbd7b24870682abcc42139d13135a680bb3 Mon Sep 17 00:00:00 2001 From: Gustavo Santos Ferreira Date: Fri, 14 Dec 2018 15:33:40 -0200 Subject: [PATCH 3/7] retriever module that queries the contracts to generate validator metadata --- .../explorer/validator/metadata_retriever.ex | 76 +++++++++++++ .../validator/metadata_retriever_test.exs | 100 ++++++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 apps/explorer/lib/explorer/validator/metadata_retriever.ex create mode 100644 apps/explorer/test/explorer/validator/metadata_retriever_test.exs diff --git a/apps/explorer/lib/explorer/validator/metadata_retriever.ex b/apps/explorer/lib/explorer/validator/metadata_retriever.ex new file mode 100644 index 0000000000..682b1e906e --- /dev/null +++ b/apps/explorer/lib/explorer/validator/metadata_retriever.ex @@ -0,0 +1,76 @@ +defmodule Explorer.Validator.MetadataRetriever do + @moduledoc """ + Consults the configured smart contracts to fetch the valivators' metadata + """ + + alias Explorer.SmartContract.Reader + + def fetch_data do + fetch_validators_list() + |> Enum.map(fn validator -> + validator + |> fetch_validator_metadata + |> translate_metadata + |> Map.merge(%{address_hash: validator, primary: true}) + end) + end + + defp fetch_validators_list do + %{"getValidators" => {:ok, [validators]}} = + Reader.query_contract(config(:validators_contract_address), contract_abi("validators.json"), %{ + "getValidators" => [] + }) + + validators + end + + defp fetch_validator_metadata(validator_address) do + %{"validators" => {:ok, fields}} = + Reader.query_contract(config(:metadata_contract_address), contract_abi("metadata.json"), %{ + "validators" => [validator_address] + }) + + fields + end + + defp translate_metadata([ + first_name, + last_name, + license_id, + full_address, + state, + zipcode, + expiration_date, + created_date, + _updated_date, + _min_treshold + ]) do + %{ + name: trim_null_bytes(first_name) <> " " <> trim_null_bytes(last_name), + metadata: %{ + license_id: trim_null_bytes(license_id), + address: full_address, + state: trim_null_bytes(state), + zipcode: trim_null_bytes(zipcode), + expiration_date: expiration_date, + created_date: created_date + } + } + end + + defp trim_null_bytes(bytes) do + String.trim_trailing(bytes, <<0>>) + end + + defp config(key) do + Application.get_env(:explorer, __MODULE__, [])[key] + end + + # sobelow_skip ["Traversal"] + defp contract_abi(file_name) do + :explorer + |> Application.app_dir("priv/validator_contracts_abi/#{file_name}") + |> File.read!() + |> Jason.decode!() + end +end diff --git a/apps/explorer/test/explorer/validator/metadata_retriever_test.exs b/apps/explorer/test/explorer/validator/metadata_retriever_test.exs new file mode 100644 index 0000000000..b2656e7137 --- /dev/null +++ b/apps/explorer/test/explorer/validator/metadata_retriever_test.exs @@ -0,0 +1,100 @@ +defmodule Explorer.Validator.MetadataRetrieverTest do + use EthereumJSONRPC.Case + + alias Explorer.Validator.MetadataRetriever + import Mox + + setup :verify_on_exit! + setup :set_mox_global + + describe "fetch_data/0" do + test "returns maps with the info on each validator" do + validators_list_mox_ok() + validator_metadata_mox_ok() + + expected = [ + %{ + address_hash: <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1>>, + name: "Testname Unitarion", + primary: true, + metadata: %{ + address: "", + created_date: 0, + expiration_date: 253_370_764_800, + license_id: "00000000", + state: "XX", + zipcode: "00000" + } + } + ] + + assert MetadataRetriever.fetch_data() == expected + end + + test "raise error when the first contract call fails" do + contract_request_with_error("getValidators") + assert_raise(MatchError, fn -> MetadataRetriever.fetch_data() end) + end + + test "raise error when a call to the metadatc contract fails" do + validators_list_mox_ok() + contract_request_with_error("validators") + assert_raise(MatchError, fn -> MetadataRetriever.fetch_data() end) + end + end + + defp contract_request_with_error(id) do + expect( + EthereumJSONRPC.Mox, + :json_rpc, + fn [%{id: id, method: _, params: _}], _options -> + {:ok, + [ + %{ + error: %{code: -32015, data: "Reverted 0x", message: "VM execution error."}, + id: id, + jsonrpc: "2.0" + } + ]} + end + ) + end + + defp validators_list_mox_ok() do + expect( + EthereumJSONRPC.Mox, + :json_rpc, + 1, + fn [%{id: "getValidators"}], _opts -> + {:ok, + [ + %{ + id: "getValidators", + jsonrpc: "2.0", + result: + "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001" + } + ]} + end + ) + end + + defp validator_metadata_mox_ok() do + expect( + EthereumJSONRPC.Mox, + :json_rpc, + 1, + fn [%{id: "validators"}], _opts -> + {:ok, + [ + %{ + id: "validators", + jsonrpc: "2.0", + result: + "0x546573746e616d65000000000000000000000000000000000000000000000000556e69746172696f6e000000000000000000000000000000000000000000000030303030303030300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000140585800000000000000000000000000000000000000000000000000000000000030303030300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003afe130e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000058585858585858207374726565742058585858585800000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + } + ]} + end + ) + end +end From ebf0aa68609e3ce6e83ce9d2eb892754677e9a8b Mon Sep 17 00:00:00 2001 From: Gustavo Santos Ferreira Date: Fri, 14 Dec 2018 15:36:15 -0200 Subject: [PATCH 4/7] module that imports validators metadata into the DataBase --- .../explorer/validator/metadata_importer.ex | 36 ++++++++++++++ .../validator/metadata_importer_test.exs | 48 +++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 apps/explorer/lib/explorer/validator/metadata_importer.ex create mode 100644 apps/explorer/test/explorer/validator/metadata_importer_test.exs diff --git a/apps/explorer/lib/explorer/validator/metadata_importer.ex b/apps/explorer/lib/explorer/validator/metadata_importer.ex new file mode 100644 index 0000000000..3bf799d51c --- /dev/null +++ b/apps/explorer/lib/explorer/validator/metadata_importer.ex @@ -0,0 +1,36 @@ +defmodule Explorer.Validator.MetadataImporter do + @moduledoc """ + module that upserts validator metadata from a list of maps + """ + alias Explorer.Chain.Address + alias Explorer.Repo + + import Ecto.Query, only: [from: 2] + + def import_metadata(metadata_maps) do + Repo.transaction(fn -> Enum.each(metadata_maps, &upsert_validator_metadata(&1)) end) + end + + defp upsert_validator_metadata(validator_changeset) do + case Repo.get_by(Address.Name, address_hash: validator_changeset.address_hash, primary: true) do + nil -> + %Address.Name{} + |> Address.Name.changeset(validator_changeset) + |> Repo.insert() + + _address_name -> + query = + from(an in Address.Name, + update: [ + set: [ + name: ^validator_changeset.name, + metadata: ^validator_changeset.metadata + ] + ], + where: an.address_hash == ^validator_changeset.address_hash and an.primary == true + ) + + Repo.update_all(query, []) + end + end +end diff --git a/apps/explorer/test/explorer/validator/metadata_importer_test.exs b/apps/explorer/test/explorer/validator/metadata_importer_test.exs new file mode 100644 index 0000000000..281c45ec2f --- /dev/null +++ b/apps/explorer/test/explorer/validator/metadata_importer_test.exs @@ -0,0 +1,48 @@ +defmodule Explorer.Validator.MetadataImporterTest do + use Explorer.DataCase + + require Ecto.Query + + import Ecto.Query + import Explorer.Factory + + alias Explorer.Chain.Address + alias Explorer.Repo + alias Explorer.Validator.MetadataImporter + + describe "import_metadata/1" do + test "inserts new address names when there's none for the validators" do + address = insert(:address) + + [%{address_hash: address.hash, name: "Testinit Unitorius", primary: true, metadata: %{"test" => "toast"}}] + |> MetadataImporter.import_metadata() + + address_names = + from(an in Address.Name, where: an.address_hash == ^address.hash and an.primary == true) + |> Repo.all() + + expected_name = %Address.Name{address_hash: address.hash, name: "Testit Unitorus", metadata: %{"test" => "toast"}} + + assert length(address_names) == 1 + assert expected_name = hd(address_names) + end + + test "updates the primary address name if the validator already has one" do + address = insert(:address) + + insert(:address_name, address: address, primary: true, name: "Nodealus Faileddi") + + [%{address_hash: address.hash, name: "Testit Unitorus", primary: true, metadata: %{"test" => "toast"}}] + |> MetadataImporter.import_metadata() + + address_names = + from(an in Address.Name, where: an.address_hash == ^address.hash and an.primary == true) + |> Repo.all() + + expected_name = %Address.Name{address_hash: address.hash, name: "Testit Unitorus", metadata: %{"test" => "toast"}} + + assert length(address_names) == 1 + assert expected_name = hd(address_names) + end + end +end From add2b2c213f3b943b7a7520b7bce4af24275b343 Mon Sep 17 00:00:00 2001 From: Gustavo Santos Ferreira Date: Mon, 17 Dec 2018 15:19:52 -0200 Subject: [PATCH 5/7] add a modal fo the validator info --- .../_validator_metadata_modal.html.eex | 41 +++++++++++++++++++ .../templates/address/overview.html.eex | 14 ++++++- .../lib/block_scout_web/views/address_view.ex | 28 +++++++++++++ 3 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 apps/block_scout_web/lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex new file mode 100644 index 0000000000..60a022f32d --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex @@ -0,0 +1,41 @@ + diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address/overview.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address/overview.html.eex index d5b61a06c8..4f665cf09f 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address/overview.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address/overview.html.eex @@ -14,6 +14,13 @@ + <%= if validator_metadata = primary_validator_metadata(@address) do %> + + + + <% end %>

<%= address_title(@address) %> <%= gettext "Details" %>

<%= @address.hash %>

@@ -65,7 +72,7 @@ - + + + +<%= if validator_metadata do %> + <%= render BlockScoutWeb.AddressView, "_validator_metadata_modal.html", address_name: address_name, validator_metadata: validator_metadata %> +<% end %> diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_view.ex index f769ceebaf..f0f3c3ab63 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/address_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/address_view.ex @@ -148,6 +148,34 @@ defmodule BlockScoutWeb.AddressView do def primary_name(%Address{names: _}), do: nil + def primary_validator_metadata(%Address{names: [_ | _] = address_names}) do + case Enum.find(address_names, &(&1.primary == true)) do + %Address.Name{ + metadata: + metadata = %{ + "license_id" => _, + "address" => _, + "state" => _, + "zipcode" => _, + "expiration_date" => _, + "created_date" => _ + } + } -> + metadata + + _ -> + nil + end + end + + def primary_validator_metadata(%Address{names: _}), do: nil + + def format_datetime_string(unix_date) do + unix_date + |> DateTime.from_unix!() + |> Timex.format!("{M}-{D}-{YYYY}") + end + def qr_code(%Address{hash: hash}) do hash |> to_string() From f2cbf372008f2ef4d99cbd4ad5944b92e9ba6071 Mon Sep 17 00:00:00 2001 From: Gustavo Santos Ferreira Date: Mon, 17 Dec 2018 15:20:35 -0200 Subject: [PATCH 6/7] genserver that executes validator data imports every 24 hours --- apps/explorer/config/config.exs | 4 +++ apps/explorer/lib/explorer/application.ex | 3 +- .../explorer/validator/metadata_processor.ex | 31 +++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 apps/explorer/lib/explorer/validator/metadata_processor.ex diff --git a/apps/explorer/config/config.exs b/apps/explorer/config/config.exs index 4cabe0c008..c58234ec9b 100644 --- a/apps/explorer/config/config.exs +++ b/apps/explorer/config/config.exs @@ -36,6 +36,10 @@ if System.get_env("METADATA_CONTRACT") && System.get_env("VALIDATORS_CONTRACT") config :explorer, Explorer.Validator.MetadataRetriever, metadata_contract_address: System.get_env("METADATA_CONTRACT"), validators_contract_address: System.get_env("VALIDATORS_CONTRACT") + + config :explorer, Explorer.Validator.MetadataProcessor, enabled: true +else + config :explorer, Explorer.Validator.MetadataProcessor, enabled: false end if System.get_env("SUPPLY_MODULE") == "TransactionAndLog" do diff --git a/apps/explorer/lib/explorer/application.ex b/apps/explorer/lib/explorer/application.ex index bf124baa35..1f088a6578 100644 --- a/apps/explorer/lib/explorer/application.ex +++ b/apps/explorer/lib/explorer/application.ex @@ -38,7 +38,8 @@ defmodule Explorer.Application do configure(Explorer.Counters.TokenHoldersCounter), configure(Explorer.Counters.TokenTransferCounter), configure(Explorer.Counters.BlockValidationCounter), - configure(Explorer.Counters.AddressesWithBalanceCounter) + configure(Explorer.Counters.AddressesWithBalanceCounter), + configure(Explorer.Validator.MetadataProcessor) ] |> List.flatten() end diff --git a/apps/explorer/lib/explorer/validator/metadata_processor.ex b/apps/explorer/lib/explorer/validator/metadata_processor.ex new file mode 100644 index 0000000000..ebc9db9d46 --- /dev/null +++ b/apps/explorer/lib/explorer/validator/metadata_processor.ex @@ -0,0 +1,31 @@ +defmodule Explorer.Validator.MetadataProcessor do + @moduledoc """ + module to periodically retrieve and update metadata belonging to validators + """ + use GenServer + alias Explorer.Validator.{MetadataImporter, MetadataRetriever} + + def start_link(_) do + GenServer.start_link(__MODULE__, :ok, name: __MODULE__) + end + + @impl true + def init(args) do + send(self(), :import_and_reschedule) + {:ok, args} + end + + @impl true + def handle_info(:import_and_reschedule, state) do + MetadataRetriever.fetch_data() + |> MetadataImporter.import_metadata() + + reschedule() + + {:noreply, state} + end + + defp reschedule do + Process.send_after(self(), :import_and_reschedule, :timer.hours(24)) + end +end From 07cb1d918bbdc2325448d46b15eed3be23537f04 Mon Sep 17 00:00:00 2001 From: Gustavo Santos Ferreira Date: Tue, 18 Dec 2018 12:54:16 -0200 Subject: [PATCH 7/7] gettext --- apps/block_scout_web/priv/gettext/default.pot | 62 ++++++++++++++----- .../priv/gettext/en/LC_MESSAGES/default.po | 62 ++++++++++++++----- 2 files changed, 94 insertions(+), 30 deletions(-) diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index 103cd20b78..2b6826c052 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -97,6 +97,7 @@ msgid "Action" msgstr "" #, elixir-format +#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:16 #: lib/block_scout_web/templates/transaction_log/index.html.eex:15 #: lib/block_scout_web/views/address_view.ex:92 msgid "Address" @@ -188,10 +189,10 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address/_tabs.html.eex:40 #: lib/block_scout_web/templates/address/_tabs.html.eex:103 -#: lib/block_scout_web/templates/address/overview.html.eex:37 +#: lib/block_scout_web/templates/address/overview.html.eex:44 #: lib/block_scout_web/templates/address_validation/index.html.eex:30 #: lib/block_scout_web/templates/address_validation/index.html.eex:57 -#: lib/block_scout_web/views/address_view.ex:225 +#: lib/block_scout_web/views/address_view.ex:253 msgid "Blocks Validated" msgstr "" @@ -207,8 +208,9 @@ msgid "Clear" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:74 -#: lib/block_scout_web/templates/address/overview.html.eex:82 +#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:37 +#: lib/block_scout_web/templates/address/overview.html.eex:81 +#: lib/block_scout_web/templates/address/overview.html.eex:89 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:84 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:92 #: lib/block_scout_web/templates/transaction_log/index.html.eex:94 @@ -222,7 +224,7 @@ msgstr "" #: lib/block_scout_web/templates/address_validation/index.html.eex:39 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:119 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:141 -#: lib/block_scout_web/views/address_view.ex:222 +#: lib/block_scout_web/views/address_view.ex:250 msgid "Code" msgstr "" @@ -340,7 +342,7 @@ msgid "Copyright %{year} POA" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:43 +#: lib/block_scout_web/templates/address/overview.html.eex:50 msgid "Created by" msgstr "" @@ -363,7 +365,7 @@ msgid "Description" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:18 +#: lib/block_scout_web/templates/address/overview.html.eex:25 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:120 msgid "Details" msgstr "" @@ -504,7 +506,7 @@ msgstr "" #: lib/block_scout_web/templates/transaction/_tabs.html.eex:14 #: lib/block_scout_web/templates/transaction/_tabs.html.eex:43 #: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:10 -#: lib/block_scout_web/views/address_view.ex:221 +#: lib/block_scout_web/views/address_view.ex:249 #: lib/block_scout_web/views/transaction_view.ex:190 msgid "Internal Transactions" msgstr "" @@ -706,7 +708,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address/overview.html.eex:13 -#: lib/block_scout_web/templates/address/overview.html.eex:73 +#: lib/block_scout_web/templates/address/overview.html.eex:80 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:13 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:13 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:83 @@ -723,7 +725,7 @@ msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:122 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:33 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:75 -#: lib/block_scout_web/views/address_view.ex:223 +#: lib/block_scout_web/views/address_view.ex:251 #: lib/block_scout_web/views/tokens/overview_view.ex:37 msgid "Read Contract" msgstr "" @@ -921,7 +923,7 @@ msgstr "" #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:12 #: lib/block_scout_web/templates/address_validation/index.html.eex:11 #: lib/block_scout_web/templates/address_validation/index.html.eex:19 -#: lib/block_scout_web/views/address_view.ex:219 +#: lib/block_scout_web/views/address_view.ex:247 msgid "Tokens" msgstr "" @@ -982,13 +984,13 @@ msgstr "" #: lib/block_scout_web/templates/block_transaction/index.html.eex:35 #: lib/block_scout_web/templates/chain/show.html.eex:69 #: lib/block_scout_web/templates/layout/_topnav.html.eex:35 -#: lib/block_scout_web/views/address_view.ex:220 +#: lib/block_scout_web/views/address_view.ex:248 msgid "Transactions" msgstr "" #, elixir-format #: lib/block_scout_web/templates/address/_tile.html.eex:19 -#: lib/block_scout_web/templates/address/overview.html.eex:33 +#: lib/block_scout_web/templates/address/overview.html.eex:40 msgid "Transactions sent" msgstr "" @@ -1115,7 +1117,7 @@ msgid "Yes" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:49 +#: lib/block_scout_web/templates/address/overview.html.eex:56 msgid "at" msgstr "" @@ -1437,7 +1439,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address/_tabs.html.eex:30 #: lib/block_scout_web/templates/address/_tabs.html.eex:96 -#: lib/block_scout_web/views/address_view.ex:224 +#: lib/block_scout_web/views/address_view.ex:252 msgid "Coin Balance History" msgstr "" @@ -1470,3 +1472,33 @@ msgstr "" #: lib/block_scout_web/templates/block/index.html.eex:21 msgid "There are no blocks." msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:24 +msgid "License Expires" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:10 +msgid "License ID" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:19 +msgid "Show Validator Info" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:30 +msgid "Validator Creation Date" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:5 +msgid "Validator Data" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:19 +msgid "Validator Info" +msgstr "" diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po index ab95f607a1..be2454f6c7 100644 --- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po +++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po @@ -97,6 +97,7 @@ msgid "Action" msgstr "" #, elixir-format +#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:16 #: lib/block_scout_web/templates/transaction_log/index.html.eex:15 #: lib/block_scout_web/views/address_view.ex:92 msgid "Address" @@ -188,10 +189,10 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address/_tabs.html.eex:40 #: lib/block_scout_web/templates/address/_tabs.html.eex:103 -#: lib/block_scout_web/templates/address/overview.html.eex:37 +#: lib/block_scout_web/templates/address/overview.html.eex:44 #: lib/block_scout_web/templates/address_validation/index.html.eex:30 #: lib/block_scout_web/templates/address_validation/index.html.eex:57 -#: lib/block_scout_web/views/address_view.ex:225 +#: lib/block_scout_web/views/address_view.ex:253 msgid "Blocks Validated" msgstr "" @@ -207,8 +208,9 @@ msgid "Clear" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:74 -#: lib/block_scout_web/templates/address/overview.html.eex:82 +#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:37 +#: lib/block_scout_web/templates/address/overview.html.eex:81 +#: lib/block_scout_web/templates/address/overview.html.eex:89 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:84 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:92 #: lib/block_scout_web/templates/transaction_log/index.html.eex:94 @@ -222,7 +224,7 @@ msgstr "" #: lib/block_scout_web/templates/address_validation/index.html.eex:39 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:119 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:141 -#: lib/block_scout_web/views/address_view.ex:222 +#: lib/block_scout_web/views/address_view.ex:250 msgid "Code" msgstr "" @@ -340,7 +342,7 @@ msgid "Copyright %{year} POA" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:43 +#: lib/block_scout_web/templates/address/overview.html.eex:50 msgid "Created by" msgstr "" @@ -363,7 +365,7 @@ msgid "Description" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:18 +#: lib/block_scout_web/templates/address/overview.html.eex:25 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:120 msgid "Details" msgstr "" @@ -504,7 +506,7 @@ msgstr "" #: lib/block_scout_web/templates/transaction/_tabs.html.eex:14 #: lib/block_scout_web/templates/transaction/_tabs.html.eex:43 #: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:10 -#: lib/block_scout_web/views/address_view.ex:221 +#: lib/block_scout_web/views/address_view.ex:249 #: lib/block_scout_web/views/transaction_view.ex:190 msgid "Internal Transactions" msgstr "" @@ -706,7 +708,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address/overview.html.eex:13 -#: lib/block_scout_web/templates/address/overview.html.eex:73 +#: lib/block_scout_web/templates/address/overview.html.eex:80 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:13 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:13 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:83 @@ -723,7 +725,7 @@ msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:122 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:33 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:75 -#: lib/block_scout_web/views/address_view.ex:223 +#: lib/block_scout_web/views/address_view.ex:251 #: lib/block_scout_web/views/tokens/overview_view.ex:37 msgid "Read Contract" msgstr "" @@ -921,7 +923,7 @@ msgstr "" #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:12 #: lib/block_scout_web/templates/address_validation/index.html.eex:11 #: lib/block_scout_web/templates/address_validation/index.html.eex:19 -#: lib/block_scout_web/views/address_view.ex:219 +#: lib/block_scout_web/views/address_view.ex:247 msgid "Tokens" msgstr "" @@ -982,13 +984,13 @@ msgstr "" #: lib/block_scout_web/templates/block_transaction/index.html.eex:35 #: lib/block_scout_web/templates/chain/show.html.eex:69 #: lib/block_scout_web/templates/layout/_topnav.html.eex:35 -#: lib/block_scout_web/views/address_view.ex:220 +#: lib/block_scout_web/views/address_view.ex:248 msgid "Transactions" msgstr "" #, elixir-format #: lib/block_scout_web/templates/address/_tile.html.eex:19 -#: lib/block_scout_web/templates/address/overview.html.eex:33 +#: lib/block_scout_web/templates/address/overview.html.eex:40 msgid "Transactions sent" msgstr "" @@ -1115,7 +1117,7 @@ msgid "Yes" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address/overview.html.eex:49 +#: lib/block_scout_web/templates/address/overview.html.eex:56 msgid "at" msgstr "" @@ -1437,7 +1439,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address/_tabs.html.eex:30 #: lib/block_scout_web/templates/address/_tabs.html.eex:96 -#: lib/block_scout_web/views/address_view.ex:224 +#: lib/block_scout_web/views/address_view.ex:252 msgid "Coin Balance History" msgstr "" @@ -1470,3 +1472,33 @@ msgstr "" #: lib/block_scout_web/templates/block/index.html.eex:21 msgid "There are no blocks." msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:24 +msgid "License Expires" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:10 +msgid "License ID" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:19 +msgid "Show Validator Info" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:30 +msgid "Validator Creation Date" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:5 +msgid "Validator Data" +msgstr "" + +#, elixir-format +#: lib/block_scout_web/templates/address/overview.html.eex:19 +msgid "Validator Info" +msgstr ""