feat: Address scam badge flag (#10763)

* Address badges

* Pass badges preload in all controllers related to address

* Process review comments: redesign routes

* Changes to fit specified requirements

* Hide scam addresses from search based on the flag at the backend

* Refactoring based on review comments

* Update apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_badge_controller.ex

Co-authored-by: nikitosing <32202610+nikitosing@users.noreply.github.com>

* Update apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_badge_controller.ex

Co-authored-by: nikitosing <32202610+nikitosing@users.noreply.github.com>

* Add addresses filtering

* Hide scam tokens from the lists

---------

Co-authored-by: nikitosing <32202610+nikitosing@users.noreply.github.com>
pull/10844/head
Victor Baranov 2 months ago committed by GitHub
parent e66d345b96
commit 12517dbde5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 3
      apps/block_scout_web/.sobelow-conf
  2. 18
      apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex
  3. 90
      apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_badge_controller.ex
  4. 19
      apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex
  5. 10
      apps/block_scout_web/lib/block_scout_web/controllers/api/v2/block_controller.ex
  6. 7
      apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex
  7. 4
      apps/block_scout_web/lib/block_scout_web/controllers/api/v2/main_page_controller.ex
  8. 26
      apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex
  9. 10
      apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex
  10. 10
      apps/block_scout_web/lib/block_scout_web/models/transaction_state_helper.ex
  11. 8
      apps/block_scout_web/lib/block_scout_web/notifier.ex
  12. 58
      apps/block_scout_web/lib/block_scout_web/routers/address_badges_v2_router.ex
  13. 11
      apps/block_scout_web/lib/block_scout_web/routers/api_router.ex
  14. 33
      apps/block_scout_web/lib/block_scout_web/views/api/v2/address_badge_view.ex
  15. 7
      apps/block_scout_web/lib/block_scout_web/views/api/v2/helper.ex
  16. 4
      apps/explorer/lib/explorer/chain/address.ex
  17. 79
      apps/explorer/lib/explorer/chain/address/scam_badge_to_address.ex
  18. 12
      apps/explorer/lib/explorer/chain/advanced_filter.ex
  19. 4
      apps/explorer/lib/explorer/chain/celo/epoch_reward.ex
  20. 12
      apps/explorer/lib/explorer/chain/search.ex
  21. 2
      apps/explorer/lib/explorer/chain/smart_contract.ex
  22. 2
      apps/explorer/lib/explorer/chain/token.ex
  23. 4
      apps/explorer/lib/explorer/chain/token_transfer.ex
  24. 38
      apps/explorer/lib/explorer/helper.ex
  25. 14
      apps/explorer/priv/repo/migrations/20240910095635_add_address_badges_tables.exs
  26. 1
      config/runtime.exs
  27. 1
      cspell.json
  28. 5
      docker-compose/envs/common-blockscout.env

@ -9,6 +9,7 @@
ignore_files: [ ignore_files: [
"apps/block_scout_web/lib/block_scout_web/routers/smart_contracts_api_v2_router.ex", "apps/block_scout_web/lib/block_scout_web/routers/smart_contracts_api_v2_router.ex",
"apps/block_scout_web/lib/block_scout_web/routers/tokens_api_v2_router.ex", "apps/block_scout_web/lib/block_scout_web/routers/tokens_api_v2_router.ex",
"apps/block_scout_web/lib/block_scout_web/routers/utils_api_v2_router.ex" "apps/block_scout_web/lib/block_scout_web/routers/utils_api_v2_router.ex",
"apps/block_scout_web/lib/block_scout_web/routers/address_badges_v2_router.ex"
] ]
] ]

@ -50,8 +50,18 @@ defmodule BlockScoutWeb.AddressChannel do
@transaction_associations [ @transaction_associations [
from_address: [:names, :smart_contract, :proxy_implementations], from_address: [:names, :smart_contract, :proxy_implementations],
to_address: [:names, :smart_contract, :proxy_implementations], to_address: [
created_contract_address: [:names, :smart_contract, :proxy_implementations] :scam_badge,
:names,
:smart_contract,
:proxy_implementations
],
created_contract_address: [
:scam_badge,
:names,
:smart_contract,
:proxy_implementations
]
] ++ ] ++
@chain_type_transaction_associations @chain_type_transaction_associations
@ -404,8 +414,8 @@ defmodule BlockScoutWeb.AddressChannel do
token_transfers token_transfers
|> Repo.preload([ |> Repo.preload([
[ [
from_address: [:names, :smart_contract, :proxy_implementations], from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations],
to_address: [:names, :smart_contract, :proxy_implementations] to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]
] ]
]), ]),
conn: nil conn: nil

@ -0,0 +1,90 @@
defmodule BlockScoutWeb.API.V2.AddressBadgeController do
require Logger
use BlockScoutWeb, :controller
alias Explorer.Chain
alias Explorer.Chain.Address.ScamBadgeToAddress
alias Plug.Conn
@api_true [api?: true]
action_fallback(BlockScoutWeb.API.V2.FallbackController)
def assign_badge_to_address(
conn,
%{
"address_hashes" => address_hashes
} = params
)
when is_list(address_hashes) do
with :ok <- check_sensitive_endpoint_api_key(params["api_key"]),
valid_address_hashes = filter_address_hashes(address_hashes),
{_num_of_inserted, badge_to_address_list} <- ScamBadgeToAddress.add(valid_address_hashes) do
conn
|> put_status(200)
|> render(:badge_to_address, %{
badge_to_address_list: badge_to_address_list,
status: if(Enum.empty?(badge_to_address_list), do: "update skipped", else: "added")
})
end
end
def assign_badge_to_address(_, _), do: {:error, :not_found}
def unassign_badge_from_address(
conn,
%{
"address_hashes" => address_hashes
} = params
)
when is_list(address_hashes) do
with :ok <- check_sensitive_endpoint_api_key(params["api_key"]),
valid_address_hashes = filter_address_hashes(address_hashes),
{_num_of_deleted, badge_to_address_list} <- ScamBadgeToAddress.delete(valid_address_hashes) do
conn
|> put_status(200)
|> render(:badge_to_address, %{
badge_to_address_list: badge_to_address_list,
status: if(Enum.empty?(badge_to_address_list), do: "update skipped", else: "removed")
})
end
end
def unassign_badge_from_address(_, _), do: {:error, :not_found}
def show_badge_addresses(conn, _) do
with {:ok, body, _conn} <- Conn.read_body(conn, []),
{:ok, %{"api_key" => provided_api_key}} <- Jason.decode(body),
:ok <- check_sensitive_endpoint_api_key(provided_api_key) do
badge_to_address_list = ScamBadgeToAddress.get(@api_true)
conn
|> put_status(200)
|> render(:badge_to_address, %{
badge_to_address_list: badge_to_address_list
})
else
_ ->
{:error, :not_found}
end
end
defp check_sensitive_endpoint_api_key(provided_api_key) do
with {:sensitive_endpoints_api_key, api_key} when not is_nil(api_key) <-
{:sensitive_endpoints_api_key, Application.get_env(:block_scout_web, :sensitive_endpoints_api_key)},
{:api_key, ^api_key} <- {:api_key, provided_api_key} do
:ok
end
end
defp filter_address_hashes(address_hashes) do
address_hashes
|> Enum.uniq()
|> Enum.filter(fn potential_address_hash ->
case Chain.string_to_address_hash(potential_address_hash) do
{:ok, _address_hash} -> true
_ -> false
end
end)
end
end

@ -52,9 +52,9 @@ defmodule BlockScoutWeb.API.V2.AddressController do
@transaction_necessity_by_association [ @transaction_necessity_by_association [
necessity_by_association: necessity_by_association:
%{ %{
[created_contract_address: [:names, :smart_contract, :proxy_implementations]] => :optional, [created_contract_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional,
[from_address: [:names, :smart_contract, :proxy_implementations]] => :optional, [from_address: [:names, :smart_contract, :proxy_implementations]] => :optional,
[to_address: [:names, :smart_contract, :proxy_implementations]] => :optional, [to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional,
:block => :optional :block => :optional
} }
|> Map.merge(@chain_type_transaction_necessity_by_association), |> Map.merge(@chain_type_transaction_necessity_by_association),
@ -63,8 +63,8 @@ defmodule BlockScoutWeb.API.V2.AddressController do
@token_transfer_necessity_by_association [ @token_transfer_necessity_by_association [
necessity_by_association: %{ necessity_by_association: %{
[to_address: [:names, :smart_contract, :proxy_implementations]] => :optional, [to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional,
[from_address: [:names, :smart_contract, :proxy_implementations]] => :optional, [from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional,
:block => :optional, :block => :optional,
:transaction => :optional, :transaction => :optional,
:token => :optional :token => :optional
@ -75,6 +75,7 @@ defmodule BlockScoutWeb.API.V2.AddressController do
@address_options [ @address_options [
necessity_by_association: %{ necessity_by_association: %{
:names => :optional, :names => :optional,
:scam_badge => :optional,
:token => :optional, :token => :optional,
:proxy_implementations => :optional :proxy_implementations => :optional
}, },
@ -212,8 +213,8 @@ defmodule BlockScoutWeb.API.V2.AddressController do
options = options =
[ [
necessity_by_association: %{ necessity_by_association: %{
[to_address: [:names, :smart_contract, :proxy_implementations]] => :optional, [to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional,
[from_address: [:names, :smart_contract, :proxy_implementations]] => :optional, [from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional,
:block => :optional, :block => :optional,
:token => :optional, :token => :optional,
:transaction => :optional :transaction => :optional
@ -284,9 +285,9 @@ defmodule BlockScoutWeb.API.V2.AddressController do
full_options = full_options =
[ [
necessity_by_association: %{ necessity_by_association: %{
[created_contract_address: [:names, :smart_contract, :proxy_implementations]] => :optional, [created_contract_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional,
[from_address: [:names, :smart_contract, :proxy_implementations]] => :optional, [from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional,
[to_address: [:names, :smart_contract, :proxy_implementations]] => :optional [to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional
} }
] ]
|> Keyword.merge(paging_options(params)) |> Keyword.merge(paging_options(params))

@ -83,9 +83,9 @@ defmodule BlockScoutWeb.API.V2.BlockController do
@transaction_necessity_by_association [ @transaction_necessity_by_association [
necessity_by_association: necessity_by_association:
%{ %{
[created_contract_address: [:names, :smart_contract, :proxy_implementations]] => :optional, [created_contract_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional,
[from_address: [:names, :smart_contract, :proxy_implementations]] => :optional, [from_address: [:names, :smart_contract, :proxy_implementations]] => :optional,
[to_address: [:names, :smart_contract, :proxy_implementations]] => :optional, [to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional,
:block => :optional :block => :optional
} }
|> Map.merge(@chain_type_transaction_necessity_by_association) |> Map.merge(@chain_type_transaction_necessity_by_association)
@ -93,9 +93,9 @@ defmodule BlockScoutWeb.API.V2.BlockController do
@internal_transaction_necessity_by_association [ @internal_transaction_necessity_by_association [
necessity_by_association: %{ necessity_by_association: %{
[created_contract_address: [:names, :smart_contract, :proxy_implementations]] => :optional, [created_contract_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional,
[from_address: [:names, :smart_contract, :proxy_implementations]] => :optional, [from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional,
[to_address: [:names, :smart_contract, :proxy_implementations]] => :optional [to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional
} }
] ]

@ -133,6 +133,13 @@ defmodule BlockScoutWeb.API.V2.FallbackController do
|> render(:changeset_errors, changeset: changeset) |> render(:changeset_errors, changeset: changeset)
end end
def call(conn, {:error, :badge_creation_failed}) do
conn
|> put_status(:unprocessable_entity)
|> put_view(UserView)
|> render(:message, %{message: "Badge creation failed"})
end
def call(conn, {:restricted_access, true}) do def call(conn, {:restricted_access, true}) do
Logger.error(fn -> Logger.error(fn ->
["#{@restricted_access}"] ["#{@restricted_access}"]

@ -24,9 +24,9 @@ defmodule BlockScoutWeb.API.V2.MainPageController do
necessity_by_association: necessity_by_association:
%{ %{
:block => :required, :block => :required,
[created_contract_address: [:names, :smart_contract, :proxy_implementations]] => :optional, [created_contract_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional,
[from_address: [:names, :smart_contract, :proxy_implementations]] => :optional, [from_address: [:names, :smart_contract, :proxy_implementations]] => :optional,
[to_address: [:names, :smart_contract, :proxy_implementations]] => :optional [to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional
} }
|> Map.merge(@chain_type_transaction_necessity_by_association), |> Map.merge(@chain_type_transaction_necessity_by_association),
paging_options: %PagingOptions{page_size: 6}, paging_options: %PagingOptions{page_size: 6},

@ -70,6 +70,7 @@ defmodule BlockScoutWeb.API.V2.TransactionController do
:block => :optional, :block => :optional,
[ [
created_contract_address: [ created_contract_address: [
:scam_badge,
:names, :names,
:token, :token,
:smart_contract, :smart_contract,
@ -78,26 +79,33 @@ defmodule BlockScoutWeb.API.V2.TransactionController do
] => :optional, ] => :optional,
[from_address: [:names, :smart_contract, :proxy_implementations]] => [from_address: [:names, :smart_contract, :proxy_implementations]] =>
:optional, :optional,
[to_address: [:names, :smart_contract, :proxy_implementations]] => :optional [
to_address: [
:scam_badge,
:names,
:smart_contract,
:proxy_implementations
]
] => :optional
} }
|> Map.merge(@chain_type_transaction_necessity_by_association) |> Map.merge(@chain_type_transaction_necessity_by_association)
@token_transfers_necessity_by_association %{ @token_transfers_necessity_by_association %{
[from_address: [:names, :smart_contract, :proxy_implementations]] => :optional, [from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional,
[to_address: [:names, :smart_contract, :proxy_implementations]] => :optional [to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional
} }
@token_transfers_in_tx_necessity_by_association %{ @token_transfers_in_tx_necessity_by_association %{
[from_address: [:names, :smart_contract, :proxy_implementations]] => :optional, [from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional,
[to_address: [:names, :smart_contract, :proxy_implementations]] => :optional, [to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional,
token: :required token: :required
} }
@internal_transaction_necessity_by_association [ @internal_transaction_necessity_by_association [
necessity_by_association: %{ necessity_by_association: %{
[created_contract_address: [:names, :smart_contract, :proxy_implementations]] => :optional, [created_contract_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional,
[from_address: [:names, :smart_contract, :proxy_implementations]] => :optional, [from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional,
[to_address: [:names, :smart_contract, :proxy_implementations]] => :optional [to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional
} }
] ]
@ -515,7 +523,7 @@ defmodule BlockScoutWeb.API.V2.TransactionController do
validate_transaction(transaction_hash_string, params, validate_transaction(transaction_hash_string, params,
necessity_by_association: %{ necessity_by_association: %{
[from_address: [:names, :smart_contract, :proxy_implementations]] => :optional, [from_address: [:names, :smart_contract, :proxy_implementations]] => :optional,
[to_address: [:names, :smart_contract, :proxy_implementations]] => :optional [to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional
}, },
api?: true api?: true
) do ) do

@ -19,9 +19,9 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do
@items_limit 50 @items_limit 50
@internal_transaction_necessity_by_association [ @internal_transaction_necessity_by_association [
necessity_by_association: %{ necessity_by_association: %{
[created_contract_address: [:names, :smart_contract, :proxy_implementations]] => :optional, [created_contract_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional,
[from_address: [:names, :smart_contract, :proxy_implementations]] => :optional, [from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional,
[to_address: [:names, :smart_contract, :proxy_implementations]] => :optional [to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]] => :optional
} }
] ]
@ -108,9 +108,9 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do
Chain.select_repo(@api_true).preload(transaction, [ Chain.select_repo(@api_true).preload(transaction, [
:transaction_actions, :transaction_actions,
:block, :block,
to_address: [:names, :smart_contract], to_address: [:scam_badge, :names, :smart_contract],
from_address: [:names, :smart_contract], from_address: [:names, :smart_contract],
created_contract_address: [:names, :token, :smart_contract] created_contract_address: [:scam_badge, :names, :token, :smart_contract]
]) ])
skip_sig_provider? = false skip_sig_provider? = false

@ -69,16 +69,16 @@ defmodule BlockScoutWeb.Models.TransactionStateHelper do
|> Enum.find(&(&1.hash == transaction.hash)) |> Enum.find(&(&1.hash == transaction.hash))
|> Repo.preload( |> Repo.preload(
token_transfers: [ token_transfers: [
from_address: [:names, :smart_contract, :proxy_implementations], from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations],
to_address: [:names, :smart_contract, :proxy_implementations] to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]
], ],
internal_transactions: [ internal_transactions: [
from_address: [:names, :smart_contract, :proxy_implementations], from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations],
to_address: [:names, :smart_contract, :proxy_implementations] to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]
], ],
block: [miner: [:names, :smart_contract, :proxy_implementations]], block: [miner: [:names, :smart_contract, :proxy_implementations]],
from_address: [:names, :smart_contract, :proxy_implementations], from_address: [:names, :smart_contract, :proxy_implementations],
to_address: [:names, :smart_contract, :proxy_implementations] to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]
) )
previous_block_number = BlockNumberHelper.previous_block_number(transaction.block_number) previous_block_number = BlockNumberHelper.previous_block_number(transaction.block_number)

@ -181,8 +181,8 @@ defmodule BlockScoutWeb.Notifier do
DenormalizationHelper.extend_transaction_preload([ DenormalizationHelper.extend_transaction_preload([
:token, :token,
:transaction, :transaction,
from_address: [:names, :smart_contract, :proxy_implementations], from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations],
to_address: [:names, :smart_contract, :proxy_implementations] to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]
]) ])
) )
@ -205,9 +205,9 @@ defmodule BlockScoutWeb.Notifier do
def handle_event({:chain_event, :transactions, :realtime, transactions}) do def handle_event({:chain_event, :transactions, :realtime, transactions}) do
base_preloads = [ base_preloads = [
:block, :block,
created_contract_address: [:names, :smart_contract, :proxy_implementations], created_contract_address: [:scam_badge, :names, :smart_contract, :proxy_implementations],
from_address: [:names, :smart_contract, :proxy_implementations], from_address: [:names, :smart_contract, :proxy_implementations],
to_address: [:names, :smart_contract, :proxy_implementations] to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]
] ]
preloads = if API_V2.enabled?(), do: [:token_transfers | base_preloads], else: base_preloads preloads = if API_V2.enabled?(), do: [:token_transfers | base_preloads], else: base_preloads

@ -0,0 +1,58 @@
# This file in ignore list of `sobelow`, be careful while adding new endpoints here
defmodule BlockScoutWeb.Routers.AddressBadgesApiV2Router do
@moduledoc """
Router for /api/v2/scam-badge-addresses. This route has separate router in order to ignore sobelow's warning about missing CSRF protection
"""
use BlockScoutWeb, :router
alias BlockScoutWeb.API.V2
alias BlockScoutWeb.Plug.{CheckApiV2, RateLimit}
@max_query_string_length 5_000
pipeline :api_v2 do
plug(
Plug.Parsers,
parsers: [:urlencoded, :multipart, :json],
query_string_length: @max_query_string_length,
pass: ["*/*"],
json_decoder: Poison
)
plug(BlockScoutWeb.Plug.Logger, application: :api_v2)
plug(:accepts, ["json"])
plug(CheckApiV2)
plug(:fetch_session)
plug(:protect_from_forgery)
plug(RateLimit)
end
pipeline :api_v2_no_forgery_protect do
plug(
Plug.Parsers,
parsers: [:urlencoded, :multipart, :json],
length: 20_000_000,
query_string_length: 5_000,
pass: ["*/*"],
json_decoder: Poison
)
plug(BlockScoutWeb.Plug.Logger, application: :api_v2)
plug(:accepts, ["json"])
plug(CheckApiV2)
plug(RateLimit)
plug(:fetch_session)
end
scope "/", as: :api_v2 do
pipe_through(:api_v2_no_forgery_protect)
post("/", V2.AddressBadgeController, :assign_badge_to_address)
delete("/", V2.AddressBadgeController, :unassign_badge_from_address)
end
scope "/", as: :api_v2 do
pipe_through(:api_v2)
get("/", V2.AddressBadgeController, :show_badge_addresses)
end
end

@ -14,7 +14,15 @@ defmodule BlockScoutWeb.Routers.ApiRouter do
""" """
use BlockScoutWeb, :router use BlockScoutWeb, :router
alias BlockScoutWeb.AddressTransactionController alias BlockScoutWeb.AddressTransactionController
alias BlockScoutWeb.Routers.{APIKeyV2Router, SmartContractsApiV2Router, TokensApiV2Router, UtilsApiV2Router}
alias BlockScoutWeb.Routers.{
AddressBadgesApiV2Router,
APIKeyV2Router,
SmartContractsApiV2Router,
TokensApiV2Router,
UtilsApiV2Router
}
alias BlockScoutWeb.Plug.{CheckApiV2, RateLimit} alias BlockScoutWeb.Plug.{CheckApiV2, RateLimit}
alias BlockScoutWeb.Routers.AccountRouter alias BlockScoutWeb.Routers.AccountRouter
@ -25,6 +33,7 @@ defmodule BlockScoutWeb.Routers.ApiRouter do
forward("/v2/key", APIKeyV2Router) forward("/v2/key", APIKeyV2Router)
forward("/v2/utils", UtilsApiV2Router) forward("/v2/utils", UtilsApiV2Router)
forward("/v2/scam-badge-addresses", AddressBadgesApiV2Router)
pipeline :api do pipeline :api do
plug( plug(

@ -0,0 +1,33 @@
defmodule BlockScoutWeb.API.V2.AddressBadgeView do
use BlockScoutWeb, :view
def render("badge_to_address.json", %{badge_to_address_list: badge_to_address_list, status: status}) do
prepare_badge_to_address(badge_to_address_list, status)
end
def render("badge_to_address.json", %{badge_to_address_list: badge_to_address_list}) do
prepare_badge_to_address(badge_to_address_list)
end
defp prepare_badge_to_address(badge_to_address_list) do
%{
badge_to_address_list: format_badge_to_address_list(badge_to_address_list)
}
end
defp prepare_badge_to_address(badge_to_address_list, status) do
%{
badge_to_address_list: format_badge_to_address_list(badge_to_address_list),
status: status
}
end
defp format_badge_to_address_list(badge_to_address_list) do
badge_to_address_list
|> Enum.map(fn badge_to_address ->
%{
address_hash: "0x" <> Base.encode16(badge_to_address.address_hash.bytes)
}
end)
end
end

@ -84,6 +84,7 @@ defmodule BlockScoutWeb.API.V2.Helper do
"hash" => Address.checksum(address), "hash" => Address.checksum(address),
"is_contract" => smart_contract?, "is_contract" => smart_contract?,
"name" => address_name(address), "name" => address_name(address),
"is_scam" => address_marked_as_scam?(address),
"proxy_type" => proxy_type, "proxy_type" => proxy_type,
"implementations" => Proxy.proxy_object_info(implementation_address_hashes, implementation_names), "implementations" => Proxy.proxy_object_info(implementation_address_hashes, implementation_names),
"is_verified" => verified?(address) || verified_minimal_proxy?(proxy_implementations), "is_verified" => verified?(address) || verified_minimal_proxy?(proxy_implementations),
@ -158,6 +159,12 @@ defmodule BlockScoutWeb.API.V2.Helper do
def address_name(_), do: nil def address_name(_), do: nil
def address_marked_as_scam?(%Address{scam_badge: scam_badge}) when not is_nil(scam_badge) do
true
end
def address_marked_as_scam?(_), do: false
def verified?(%Address{smart_contract: nil}), do: false def verified?(%Address{smart_contract: nil}), do: false
def verified?(%Address{smart_contract: %{metadata_from_verified_bytecode_twin: true}}), do: false def verified?(%Address{smart_contract: %{metadata_from_verified_bytecode_twin: true}}), do: false
def verified?(%Address{smart_contract: %NotLoaded{}}), do: nil def verified?(%Address{smart_contract: %NotLoaded{}}), do: nil

@ -103,6 +103,7 @@ defmodule Explorer.Chain.Address.Schema do
) )
has_many(:names, Address.Name, foreign_key: :address_hash, references: :hash) has_many(:names, Address.Name, foreign_key: :address_hash, references: :hash)
has_one(:scam_badge, Address.ScamBadgeToAddress, foreign_key: :address_hash, references: :hash)
has_many(:decompiled_smart_contracts, DecompiledSmartContract, foreign_key: :address_hash, references: :hash) has_many(:decompiled_smart_contracts, DecompiledSmartContract, foreign_key: :address_hash, references: :hash)
has_many(:withdrawals, Withdrawal, foreign_key: :address_hash, references: :hash) has_many(:withdrawals, Withdrawal, foreign_key: :address_hash, references: :hash)
@ -126,6 +127,7 @@ defmodule Explorer.Chain.Address do
alias Ecto.Association.NotLoaded alias Ecto.Association.NotLoaded
alias Ecto.Changeset alias Ecto.Changeset
alias Explorer.Helper, as: ExplorerHelper
alias Explorer.Chain.Cache.{Accounts, NetVersion} alias Explorer.Chain.Cache.{Accounts, NetVersion}
alias Explorer.Chain.SmartContract.Proxy alias Explorer.Chain.SmartContract.Proxy
alias Explorer.Chain.SmartContract.Proxy.Models.Implementation alias Explorer.Chain.SmartContract.Proxy.Models.Implementation
@ -181,6 +183,7 @@ defmodule Explorer.Chain.Address do
Solidity source code is in `smart_contract` `t:Explorer.Chain.SmartContract.t/0` `contract_source_code` *if* the Solidity source code is in `smart_contract` `t:Explorer.Chain.SmartContract.t/0` `contract_source_code` *if* the
contract has been verified contract has been verified
* `names` - names known for the address * `names` - names known for the address
* `badges` - badges applied for the address
* `inserted_at` - when this address was inserted * `inserted_at` - when this address was inserted
* `updated_at` - when this address was last updated * `updated_at` - when this address was last updated
* `ens_domain_name` - virtual field for ENS domain name passing * `ens_domain_name` - virtual field for ENS domain name passing
@ -454,6 +457,7 @@ defmodule Explorer.Chain.Address do
) )
base_query base_query
|> ExplorerHelper.maybe_hide_scam_addresses(:hash)
|> page_addresses(paging_options) |> page_addresses(paging_options)
|> limit(^paging_options.page_size) |> limit(^paging_options.page_size)
|> Chain.select_repo(options).all() |> Chain.select_repo(options).all()

@ -0,0 +1,79 @@
defmodule Explorer.Chain.Address.ScamBadgeToAddress do
@moduledoc """
Defines Address.ScamBadgeToAddress.t() mapping with Address.t()
"""
use Explorer.Schema
import Ecto.Changeset
alias Explorer.{Chain, Repo}
alias Explorer.Chain.{Address, Hash}
import Ecto.Query, only: [from: 2]
@typedoc """
* `address` - the `t:Explorer.Chain.Address.t/0`.
* `address_hash` - foreign key for `address`.
"""
@primary_key false
typed_schema "scam_address_badge_mappings" do
belongs_to(:address, Address, foreign_key: :address_hash, references: :hash, type: Hash.Address, null: false)
timestamps()
end
@required_fields ~w(address_hash)a
@allowed_fields @required_fields
def changeset(%__MODULE__{} = struct, params \\ %{}) do
struct
|> cast(params, @allowed_fields)
|> validate_required(@required_fields)
|> foreign_key_constraint(:address_hash)
end
@doc """
Adds Address.ScamBadgeToAddress.t() by the list of Hash.Address.t()
"""
@spec add([Hash.Address.t()]) :: {non_neg_integer(), [__MODULE__.t()]}
def add(address_hashes) do
now = DateTime.utc_now()
insert_params =
address_hashes
|> Enum.map(fn address_hash_string ->
case Chain.string_to_address_hash(address_hash_string) do
{:ok, address_hash} -> %{address_hash: address_hash, inserted_at: now, updated_at: now}
:error -> nil
end
end)
|> Enum.filter(&(!is_nil(&1)))
Repo.insert_all(__MODULE__, insert_params, on_conflict: :nothing, returning: [:address_hash])
end
@doc """
Deletes Address.ScamBadgeToAddress.t() by the list of Hash.Address.t()
"""
@spec delete([Hash.Address.t()]) :: {non_neg_integer(), [__MODULE__.t()]}
def delete(address_hashes) do
query =
from(
bta in __MODULE__,
where: bta.address_hash in ^address_hashes,
select: bta
)
Repo.delete_all(query)
end
@doc """
Gets the list of Address.ScamBadgeToAddress.t()
"""
@spec get([Chain.necessity_by_association_option() | Chain.api?()]) :: [__MODULE__.t()]
def get(options) do
__MODULE__
|> Chain.select_repo(options).all()
end
end

@ -252,8 +252,8 @@ defmodule Explorer.Chain.AdvancedFilter do
preload: [ preload: [
:block, :block,
from_address: [:names, :smart_contract, :proxy_implementations], from_address: [:names, :smart_contract, :proxy_implementations],
to_address: [:names, :smart_contract, :proxy_implementations], to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations],
created_contract_address: [:names, :smart_contract, :proxy_implementations] created_contract_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]
], ],
order_by: [ order_by: [
desc: transaction.block_number, desc: transaction.block_number,
@ -289,8 +289,8 @@ defmodule Explorer.Chain.AdvancedFilter do
as: :transaction, as: :transaction,
preload: [ preload: [
from_address: [:names, :smart_contract, :proxy_implementations], from_address: [:names, :smart_contract, :proxy_implementations],
to_address: [:names, :smart_contract, :proxy_implementations], to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations],
created_contract_address: [:names, :smart_contract, :proxy_implementations], created_contract_address: [:scam_badge, :names, :smart_contract, :proxy_implementations],
transaction: transaction transaction: transaction
], ],
order_by: [ order_by: [
@ -703,8 +703,8 @@ defmodule Explorer.Chain.AdvancedFilter do
preload: [ preload: [
:transaction, :transaction,
:token, :token,
from_address: [:names, :smart_contract, :proxy_implementations], from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations],
to_address: [:names, :smart_contract, :proxy_implementations] to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]
], ],
select_merge: %{ select_merge: %{
token_ids: [token_transfer.token_id], token_ids: [token_transfer.token_id],

@ -101,8 +101,8 @@ defmodule Explorer.Chain.Celo.EpochReward do
select: {tt.log_index, tt}, select: {tt.log_index, tt},
preload: [ preload: [
:token, :token,
[from_address: [:names, :smart_contract, :proxy_implementations]], [from_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]],
[to_address: [:names, :smart_contract, :proxy_implementations]] [to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]]
] ]
) )

@ -83,10 +83,12 @@ defmodule Explorer.Chain.Search do
end end
def base_joint_query(string, term) do def base_joint_query(string, term) do
tokens_query = search_token_query(string, term) tokens_query =
contracts_query = search_contract_query(term) string |> search_token_query(term) |> ExplorerHelper.maybe_hide_scam_addresses(:contract_address_hash)
contracts_query = term |> search_contract_query() |> ExplorerHelper.maybe_hide_scam_addresses(:address_hash)
labels_query = search_label_query(term) labels_query = search_label_query(term)
address_query = search_address_query(string) address_query = string |> search_address_query() |> ExplorerHelper.maybe_hide_scam_addresses(:hash)
block_query = search_block_query(string) block_query = search_block_query(string)
basic_query = basic_query =
@ -161,6 +163,7 @@ defmodule Explorer.Chain.Search do
tokens_result = tokens_result =
search_query search_query
|> search_token_query(term) |> search_token_query(term)
|> ExplorerHelper.maybe_hide_scam_addresses(:contract_address_hash)
|> order_by([token], |> order_by([token],
desc_nulls_last: token.circulating_market_cap, desc_nulls_last: token.circulating_market_cap,
desc_nulls_last: token.fiat_value, desc_nulls_last: token.fiat_value,
@ -175,6 +178,7 @@ defmodule Explorer.Chain.Search do
contracts_result = contracts_result =
term term
|> search_contract_query() |> search_contract_query()
|> ExplorerHelper.maybe_hide_scam_addresses(:address_hash)
|> order_by([items], asc: items.name, desc: items.inserted_at) |> order_by([items], asc: items.name, desc: items.inserted_at)
|> limit(^paging_options.page_size) |> limit(^paging_options.page_size)
|> select_repo(options).all() |> select_repo(options).all()
@ -216,6 +220,7 @@ defmodule Explorer.Chain.Search do
address_result = address_result =
if query = search_address_query(search_query) do if query = search_address_query(search_query) do
query query
|> ExplorerHelper.maybe_hide_scam_addresses(:hash)
|> select_repo(options).all() |> select_repo(options).all()
else else
[] []
@ -602,6 +607,7 @@ defmodule Explorer.Chain.Search do
[ [
result[:address_hash] result[:address_hash]
|> search_address_query() |> search_address_query()
|> ExplorerHelper.maybe_hide_scam_addresses(:hash)
|> select_repo(options).all() |> select_repo(options).all()
|> merge_address_search_result_with_ens_info(result) |> merge_address_search_result_with_ens_info(result)
] ]

@ -113,6 +113,7 @@ defmodule Explorer.Chain.SmartContract do
alias Explorer.Chain.SmartContract.Proxy alias Explorer.Chain.SmartContract.Proxy
alias Explorer.Chain.SmartContract.Proxy.Models.Implementation alias Explorer.Chain.SmartContract.Proxy.Models.Implementation
alias Explorer.Helper, as: ExplorerHelper
alias Explorer.SmartContract.Helper alias Explorer.SmartContract.Helper
alias Explorer.SmartContract.Solidity.Verifier alias Explorer.SmartContract.Solidity.Verifier
@ -1273,6 +1274,7 @@ defmodule Explorer.Chain.SmartContract do
query = from(contract in __MODULE__) query = from(contract in __MODULE__)
query query
|> ExplorerHelper.maybe_hide_scam_addresses(:address_hash)
|> filter_contracts(filter) |> filter_contracts(filter)
|> search_contracts(search_string) |> search_contracts(search_string)
|> SortingHelper.apply_sorting(sorting_options, @default_sorting) |> SortingHelper.apply_sorting(sorting_options, @default_sorting)

@ -81,6 +81,7 @@ defmodule Explorer.Chain.Token do
alias Ecto.Changeset alias Ecto.Changeset
alias Explorer.{Chain, SortingHelper} alias Explorer.{Chain, SortingHelper}
alias Explorer.Chain.{BridgedToken, Hash, Search, Token} alias Explorer.Chain.{BridgedToken, Hash, Search, Token}
alias Explorer.Helper, as: ExplorerHelper
alias Explorer.Repo alias Explorer.Repo
alias Explorer.SmartContract.Helper alias Explorer.SmartContract.Helper
@ -210,6 +211,7 @@ defmodule Explorer.Chain.Token do
sorted_paginated_query = sorted_paginated_query =
query query
|> ExplorerHelper.maybe_hide_scam_addresses(:contract_address_hash)
|> apply_filter(token_type) |> apply_filter(token_type)
|> SortingHelper.apply_sorting(sorting, @default_sorting) |> SortingHelper.apply_sorting(sorting, @default_sorting)
|> SortingHelper.page_with_sorting(paging_options, sorting, @default_sorting) |> SortingHelper.page_with_sorting(paging_options, sorting, @default_sorting)

@ -240,7 +240,7 @@ defmodule Explorer.Chain.TokenTransfer do
:transaction, :transaction,
:token, :token,
[from_address: [:names, :smart_contract, :proxy_implementations]], [from_address: [:names, :smart_contract, :proxy_implementations]],
[to_address: [:names, :smart_contract, :proxy_implementations]] [to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]]
]) ])
only_consensus_transfers_query() only_consensus_transfers_query()
@ -267,7 +267,7 @@ defmodule Explorer.Chain.TokenTransfer do
:transaction, :transaction,
:token, :token,
[from_address: [:names, :smart_contract, :proxy_implementations]], [from_address: [:names, :smart_contract, :proxy_implementations]],
[to_address: [:names, :smart_contract, :proxy_implementations]] [to_address: [:scam_badge, :names, :smart_contract, :proxy_implementations]]
]) ])
only_consensus_transfers_query() only_consensus_transfers_query()

@ -7,7 +7,7 @@ defmodule Explorer.Helper do
alias Explorer.Chain alias Explorer.Chain
alias Explorer.Chain.Data alias Explorer.Chain.Data
import Ecto.Query, only: [where: 3] import Ecto.Query, only: [join: 5, where: 3]
import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0] import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0]
@max_safe_integer round(:math.pow(2, 63)) - 1 @max_safe_integer round(:math.pow(2, 63)) - 1
@ -212,4 +212,40 @@ defmodule Explorer.Helper do
true -> :eq true -> :eq
end end
end end
@doc """
Conditionally hides scam addresses in the given query.
## Parameters
- query: The Ecto query to be modified.
- address_hash_key: The key used to identify address hash field in the query to join with base query table on.
## Returns
The modified query with scam addresses hidden, if applicable.
"""
@spec maybe_hide_scam_addresses(nil | Ecto.Query.t(), atom()) :: Ecto.Query.t()
def maybe_hide_scam_addresses(nil, _address_hash_key), do: nil
def maybe_hide_scam_addresses(query, address_hash_key) do
if Application.get_env(:block_scout_web, :hide_scam_addresses) do
query
|> join(
:inner,
[q],
q2 in fragment("""
(
SELECT hash
FROM addresses a
WHERE NOT EXISTS
(SELECT 1 FROM scam_address_badge_mappings sabm WHERE sabm.address_hash=a.hash)
)
"""),
on: field(q, ^address_hash_key) == q2.hash
)
else
query
end
end
end end

@ -0,0 +1,14 @@
defmodule Explorer.Repo.Migrations.AddAddressBadgesTables do
use Ecto.Migration
def change do
create table(:scam_address_badge_mappings, primary_key: false) do
add(:address_hash, references(:addresses, column: :hash, type: :bytea, on_delete: :delete_all),
null: false,
primary_key: true
)
timestamps(null: false, type: :utc_datetime_usec)
end
end
end

@ -33,6 +33,7 @@ config :block_scout_web,
permanent_light_mode_enabled: ConfigHelper.parse_bool_env_var("PERMANENT_LIGHT_MODE_ENABLED"), permanent_light_mode_enabled: ConfigHelper.parse_bool_env_var("PERMANENT_LIGHT_MODE_ENABLED"),
display_token_icons: ConfigHelper.parse_bool_env_var("DISPLAY_TOKEN_ICONS"), display_token_icons: ConfigHelper.parse_bool_env_var("DISPLAY_TOKEN_ICONS"),
hide_block_miner: ConfigHelper.parse_bool_env_var("HIDE_BLOCK_MINER"), hide_block_miner: ConfigHelper.parse_bool_env_var("HIDE_BLOCK_MINER"),
hide_scam_addresses: ConfigHelper.parse_bool_env_var("HIDE_SCAM_ADDRESSES"),
show_tenderly_link: ConfigHelper.parse_bool_env_var("SHOW_TENDERLY_LINK"), show_tenderly_link: ConfigHelper.parse_bool_env_var("SHOW_TENDERLY_LINK"),
sensitive_endpoints_api_key: System.get_env("API_SENSITIVE_ENDPOINTS_KEY"), sensitive_endpoints_api_key: System.get_env("API_SENSITIVE_ENDPOINTS_KEY"),
disable_api?: disable_api? disable_api?: disable_api?

@ -461,6 +461,7 @@
"rollups", "rollups",
"RPC's", "RPC's",
"RPCs", "RPCs",
"sabm",
"safelow", "safelow",
"savechives", "savechives",
"Secon", "Secon",

@ -65,12 +65,12 @@ EXCHANGE_RATES_COIN=
# EXCHANGE_RATES_CRYPTORANK_COIN_ID= # EXCHANGE_RATES_CRYPTORANK_COIN_ID=
# EXCHANGE_RATES_CRYPTORANK_LIMIT= # EXCHANGE_RATES_CRYPTORANK_LIMIT=
# TOKEN_EXCHANGE_RATES_SOURCE= # TOKEN_EXCHANGE_RATES_SOURCE=
POOL_SIZE=80
# EXCHANGE_RATES_COINGECKO_PLATFORM_ID= # EXCHANGE_RATES_COINGECKO_PLATFORM_ID=
# TOKEN_EXCHANGE_RATE_INTERVAL= # TOKEN_EXCHANGE_RATE_INTERVAL=
# TOKEN_EXCHANGE_RATE_REFETCH_INTERVAL= # TOKEN_EXCHANGE_RATE_REFETCH_INTERVAL=
# TOKEN_EXCHANGE_RATE_MAX_BATCH_SIZE= # TOKEN_EXCHANGE_RATE_MAX_BATCH_SIZE=
# DISABLE_TOKEN_EXCHANGE_RATE= # DISABLE_TOKEN_EXCHANGE_RATE=
POOL_SIZE=80
POOL_SIZE_API=10 POOL_SIZE_API=10
ECTO_USE_SSL=false ECTO_USE_SSL=false
# DATADOG_HOST= # DATADOG_HOST=
@ -262,6 +262,7 @@ INDEXER_DISABLE_INTERNAL_TRANSACTIONS_FETCHER=false
# INDEXER_ARBITRUM_BRIDGE_MESSAGES_TRACKING_ENABLED= # INDEXER_ARBITRUM_BRIDGE_MESSAGES_TRACKING_ENABLED=
# INDEXER_ARBITRUM_TRACKING_MESSAGES_ON_L1_RECHECK_INTERVAL= # INDEXER_ARBITRUM_TRACKING_MESSAGES_ON_L1_RECHECK_INTERVAL=
# INDEXER_ARBITRUM_MISSED_MESSAGES_RECHECK_INTERVAL= # INDEXER_ARBITRUM_MISSED_MESSAGES_RECHECK_INTERVAL=
# INDEXER_ARBITRUM_MISSED_MESSAGES_BLOCKS_DEPTH=
# CELO_CORE_CONTRACTS= # CELO_CORE_CONTRACTS=
# INDEXER_CELO_VALIDATOR_GROUP_VOTES_BATCH_SIZE=200000 # INDEXER_CELO_VALIDATOR_GROUP_VOTES_BATCH_SIZE=200000
# INDEXER_DISABLE_CELO_EPOCH_FETCHER=false # INDEXER_DISABLE_CELO_EPOCH_FETCHER=false
@ -273,7 +274,6 @@ INDEXER_DISABLE_INTERNAL_TRANSACTIONS_FETCHER=false
# FILECOIN_PENDING_ADDRESS_OPERATIONS_MIGRATION_CONCURRENCY= # FILECOIN_PENDING_ADDRESS_OPERATIONS_MIGRATION_CONCURRENCY=
# INDEXER_DISABLE_FILECOIN_ADDRESS_INFO_FETCHER=false # INDEXER_DISABLE_FILECOIN_ADDRESS_INFO_FETCHER=false
# INDEXER_FILECOIN_ADDRESS_INFO_CONCURRENCY=1 # INDEXER_FILECOIN_ADDRESS_INFO_CONCURRENCY=1
# INDEXER_ARBITRUM_MISSED_MESSAGES_BLOCKS_DEPTH=
# INDEXER_REALTIME_FETCHER_MAX_GAP= # INDEXER_REALTIME_FETCHER_MAX_GAP=
# INDEXER_FETCHER_INIT_QUERY_LIMIT= # INDEXER_FETCHER_INIT_QUERY_LIMIT=
# INDEXER_TOKEN_BALANCES_FETCHER_INIT_QUERY_LIMIT= # INDEXER_TOKEN_BALANCES_FETCHER_INIT_QUERY_LIMIT=
@ -342,6 +342,7 @@ MAINTENANCE_ALERT_MESSAGE=
CHAIN_ID= CHAIN_ID=
MAX_SIZE_UNLESS_HIDE_ARRAY=50 MAX_SIZE_UNLESS_HIDE_ARRAY=50
HIDE_BLOCK_MINER=false HIDE_BLOCK_MINER=false
# HIDE_SCAM_ADDRESSES=
DISPLAY_TOKEN_ICONS=false DISPLAY_TOKEN_ICONS=false
RE_CAPTCHA_SECRET_KEY= RE_CAPTCHA_SECRET_KEY=
RE_CAPTCHA_CLIENT_KEY= RE_CAPTCHA_CLIENT_KEY=

Loading…
Cancel
Save