fix: Better detection IPFS links in NFT metadata fetcher (#10638)

* fix: Better detection IPFS links in NFT metadata fetcher

* Fix tests

* Remove bypass from tests
pull/10641/head
Victor Baranov 3 months ago committed by GitHub
parent 012fbcc3c5
commit 1b3f84bad7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 85
      apps/explorer/lib/explorer/token/metadata_retriever.ex
  2. 27
      apps/explorer/test/explorer/token/metadata_retriever_test.exs
  3. 1
      cspell.json

@ -13,7 +13,6 @@ defmodule Explorer.Token.MetadataRetriever do
@no_uri_error "no uri"
@vm_execution_error "VM execution error"
@ipfs_protocol "ipfs://"
@invalid_base64_data "invalid data:application/json;base64"
# https://eips.ethereum.org/EIPS/eip-1155#metadata
@ -515,31 +514,11 @@ defmodule Explorer.Token.MetadataRetriever do
end
end
# CIDv0 IPFS links # https://docs.ipfs.tech/concepts/content-addressing/#version-0-v0
defp fetch_json_from_uri({:ok, ["Qm" <> _ = result]}, _, token_id, hex_token_id, from_base_uri?) do
if String.length(result) == 46 do
ipfs? = true
fetch_json_from_uri({:ok, [ipfs_link(result)]}, ipfs?, token_id, hex_token_id, from_base_uri?)
else
Logger.warning(["Unknown metadata format result #{inspect(result)}."], fetcher: :token_instances)
{:error, truncate_error(result)}
end
end
defp fetch_json_from_uri({:ok, ["'" <> token_uri]}, ipfs?, token_id, hex_token_id, from_base_uri?) do
token_uri = token_uri |> String.split("'") |> List.first()
fetch_metadata_inner(token_uri, ipfs?, token_id, hex_token_id, from_base_uri?)
end
defp fetch_json_from_uri({:ok, ["http://" <> _ = token_uri]}, ipfs?, token_id, hex_token_id, from_base_uri?) do
fetch_metadata_inner(token_uri, ipfs?, token_id, hex_token_id, from_base_uri?)
end
defp fetch_json_from_uri({:ok, ["https://" <> _ = token_uri]}, ipfs?, token_id, hex_token_id, from_base_uri?) do
fetch_metadata_inner(token_uri, ipfs?, token_id, hex_token_id, from_base_uri?)
end
defp fetch_json_from_uri(
{:ok, [type = "data:application/json;utf8," <> json]},
ipfs?,
@ -587,35 +566,63 @@ defmodule Explorer.Token.MetadataRetriever do
{:error, @invalid_base64_data}
end
defp fetch_json_from_uri({:ok, ["#{@ipfs_protocol}ipfs/" <> right]}, _ipfs?, _token_id, hex_token_id, _from_base_uri?) do
fetch_from_ipfs(right, hex_token_id)
defp fetch_json_from_uri({:ok, [token_uri_string]}, ipfs?, token_id, hex_token_id, from_base_uri?) do
fetch_from_ipfs?(token_uri_string, ipfs?, token_id, hex_token_id, from_base_uri?)
end
defp fetch_json_from_uri({:ok, ["ipfs/" <> right]}, _ipfs?, _token_id, hex_token_id, _from_base_uri?) do
fetch_from_ipfs(right, hex_token_id)
end
defp fetch_json_from_uri(uri, _ipfs?, _token_id, _hex_token_id, _from_base_uri?) do
Logger.warning(["Unknown metadata uri format #{inspect(uri)}."], fetcher: :token_instances)
defp fetch_json_from_uri({:ok, [@ipfs_protocol <> right]}, _ipfs?, _token_id, hex_token_id, _from_base_uri?) do
fetch_from_ipfs(right, hex_token_id)
{:error, "unknown metadata uri format"}
end
defp fetch_json_from_uri({:ok, [json]}, _ipfs?, _token_id, hex_token_id, _from_base_uri?) do
json = ExplorerHelper.decode_json(json, true)
# credo:disable-for-next-line Credo.Check.Refactor.CyclomaticComplexity
defp fetch_from_ipfs?(token_uri_string, ipfs?, token_id, hex_token_id, from_base_uri?) do
case URI.parse(token_uri_string) do
%URI{scheme: "ipfs", path: path} ->
resource_id =
case path do
"/ipfs/" <> resource_id ->
resource_id
"/" <> resource_id ->
resource_id
end
fetch_from_ipfs(resource_id, hex_token_id)
%URI{scheme: _, path: "/ipfs/" <> resource_id} ->
fetch_from_ipfs(resource_id, hex_token_id)
%URI{scheme: _, path: "ipfs/" <> resource_id} ->
fetch_from_ipfs(resource_id, hex_token_id)
%URI{scheme: scheme} when not is_nil(scheme) ->
fetch_metadata_inner(token_uri_string, ipfs?, token_id, hex_token_id, from_base_uri?)
%URI{path: path} ->
case path do
"Qm" <> <<_::binary-size(44)>> = resource_id ->
fetch_from_ipfs(resource_id, hex_token_id)
check_type(json, hex_token_id)
# todo: rewrite for strict CID v1 support
"bafybe" <> _ = resource_id ->
fetch_from_ipfs(resource_id, hex_token_id)
_ ->
json = ExplorerHelper.decode_json(token_uri_string, true)
check_type(json, hex_token_id)
end
end
rescue
e ->
Logger.warning(["Unknown metadata format #{inspect(json)}.", Exception.format(:error, e, __STACKTRACE__)],
Logger.warning(
["Unknown metadata format #{inspect(token_uri_string)}.", Exception.format(:error, e, __STACKTRACE__)],
fetcher: :token_instances
)
{:error, "invalid json"}
end
defp fetch_json_from_uri(uri, _ipfs?, _token_id, _hex_token_id, _from_base_uri?) do
Logger.warning(["Unknown metadata uri format #{inspect(uri)}."], fetcher: :token_instances)
{:error, "unknown metadata uri format"}
{:error, "invalid token_uri_string"}
end
defp fetch_json_from_json_string(json, ipfs?, token_id, hex_token_id, from_base_uri?, type) do

@ -885,7 +885,7 @@ defmodule Explorer.Token.MetadataRetrieverTest do
}}
end
test "fetches image from ipfs link directly", %{bypass: bypass} do
test "fetches image from ipfs link directly" do
path = "/ipfs/bafybeig6nlmyzui7llhauc52j2xo5hoy4lzp6442lkve5wysdvjkizxonu"
json = """
@ -894,14 +894,19 @@ defmodule Explorer.Token.MetadataRetrieverTest do
}
"""
Bypass.expect(bypass, "GET", path, fn conn ->
Conn.resp(conn, 200, json)
Application.put_env(:explorer, :http_adapter, Explorer.Mox.HTTPoison)
Explorer.Mox.HTTPoison
|> expect(:get, fn "https://ipfs.io/ipfs/bafybeig6nlmyzui7llhauc52j2xo5hoy4lzp6442lkve5wysdvjkizxonu",
_headers,
_options ->
{:ok, %HTTPoison.Response{status_code: 200, body: json}}
end)
data =
{:ok,
[
"http://localhost:#{bypass.port}#{path}"
path
]}
assert {:ok,
@ -912,7 +917,7 @@ defmodule Explorer.Token.MetadataRetrieverTest do
}} == MetadataRetriever.fetch_json(data)
end
test "Fetches metadata from ipfs", %{bypass: bypass} do
test "Fetches metadata from ipfs" do
path = "/ipfs/bafybeid4ed2ua7fwupv4nx2ziczr3edhygl7ws3yx6y2juon7xakgj6cfm/51.json"
json = """
@ -921,14 +926,19 @@ defmodule Explorer.Token.MetadataRetrieverTest do
}
"""
Bypass.expect(bypass, "GET", path, fn conn ->
Conn.resp(conn, 200, json)
Application.put_env(:explorer, :http_adapter, Explorer.Mox.HTTPoison)
Explorer.Mox.HTTPoison
|> expect(:get, fn "https://ipfs.io/ipfs/bafybeid4ed2ua7fwupv4nx2ziczr3edhygl7ws3yx6y2juon7xakgj6cfm/51.json",
_headers,
_options ->
{:ok, %HTTPoison.Response{status_code: 200, body: json}}
end)
data =
{:ok,
[
"http://localhost:#{bypass.port}#{path}"
path
]}
{:ok,
@ -937,6 +947,7 @@ defmodule Explorer.Token.MetadataRetrieverTest do
}} = MetadataRetriever.fetch_json(data)
assert "ipfs://bafybeihxuj3gxk7x5p36amzootyukbugmx3pw7dyntsrohg3se64efkuga/51.png" == Map.get(metadata, "image")
Application.put_env(:explorer, :http_adapter, HTTPoison)
end
test "Fetches metadata from '${url}'", %{bypass: bypass} do

@ -41,6 +41,7 @@
"badhash",
"badnumber",
"badpassword",
"bafybe",
"bafybeid",
"bafybeig",
"bafybeihxuj",

Loading…
Cancel
Save