From ff8a6a2437c8f6bc39c9a7dc2f76269309a91db8 Mon Sep 17 00:00:00 2001 From: Bastien CHAMAGNE Date: Thu, 5 Jan 2023 11:21:45 +0100 Subject: [PATCH] ReferenceTransaction struct and also fetch the transaction from cache in the SNI --- .../controllers/api/web_hosting_controller.ex | 44 ++---------- .../directory_listing.ex | 14 ++-- .../api/web_hosting_controller/resources.ex | 19 +++-- lib/archethic_web/domain.ex | 18 ++--- lib/archethic_web/reference_transaction.ex | 69 +++++++++++++++++++ 5 files changed, 102 insertions(+), 62 deletions(-) create mode 100644 lib/archethic_web/reference_transaction.ex diff --git a/lib/archethic_web/controllers/api/web_hosting_controller.ex b/lib/archethic_web/controllers/api/web_hosting_controller.ex index 42bce6c14..452b4f289 100644 --- a/lib/archethic_web/controllers/api/web_hosting_controller.ex +++ b/lib/archethic_web/controllers/api/web_hosting_controller.ex @@ -3,18 +3,12 @@ defmodule ArchethicWeb.API.WebHostingController do use ArchethicWeb, :controller - alias Archethic.{ - Crypto, - TransactionChain.Transaction, - TransactionChain.Transaction.ValidationStamp, - TransactionChain.TransactionData - } - - alias ArchethicCache.LRU + alias Archethic.Crypto require Logger alias ArchethicWeb.API.WebHostingController.{Resources, DirectoryListing} + alias ArchethicWeb.ReferenceTransaction @spec web_hosting(Plug.Conn.t(), params :: map()) :: Plug.Conn.t() def web_hosting(conn, params = %{"url_path" => []}) do @@ -78,7 +72,7 @@ defmodule ArchethicWeb.API.WebHostingController do | {:error, :invalid_content} | {:error, :file_not_found} | {:error, :invalid_encoding} - | {:error, {:is_a_directory, {binary(), map(), DateTime.t()}}} + | {:error, {:is_a_directory, ReferenceTransaction.t()}} | {:error, any()} def get_website(params = %{"address" => address}, cache_headers) do @@ -86,8 +80,7 @@ defmodule ArchethicWeb.API.WebHostingController do with {:ok, address} <- Base.decode16(address, case: :mixed), true <- Crypto.valid_address?(address), - {:ok, last_address} <- Archethic.get_last_transaction_address(address), - {:ok, reference_transaction} <- get_reference_transaction(last_address), + {:ok, reference_transaction} <- ReferenceTransaction.fetch_last(address), {:ok, file_content, encoding, mime_type, cached?, etag} <- Resources.load(reference_transaction, url_path, cache_headers) do {:ok, file_content, encoding, mime_type, cached?, etag} @@ -153,33 +146,4 @@ defmodule ArchethicWeb.API.WebHostingController do else: {conn, file_content} end end - - # Fetch the reference transaction either from cache, or from the network. - # - # Instead of returning the entire transaction, - # we return a triplet with only the formatted data we need - @spec get_reference_transaction(binary()) :: - {:ok, {binary(), map(), DateTime.t()}} | {:error, term()} - defp get_reference_transaction(address) do - # started by ArchethicWeb.Supervisor - cache_server = :web_hosting_cache_ref_tx - cache_key = address - - case LRU.get(cache_server, cache_key) do - nil -> - with {:ok, - %Transaction{ - data: %TransactionData{content: content}, - validation_stamp: %ValidationStamp{timestamp: timestamp} - }} <- Archethic.search_transaction(address), - {:ok, json_content} <- Jason.decode(content) do - reference_transaction = {address, json_content, timestamp} - LRU.put(cache_server, cache_key, reference_transaction) - {:ok, reference_transaction} - end - - reference_transaction -> - {:ok, reference_transaction} - end - end end diff --git a/lib/archethic_web/controllers/api/web_hosting_controller/directory_listing.ex b/lib/archethic_web/controllers/api/web_hosting_controller/directory_listing.ex index f32ad3707..70db9aca5 100644 --- a/lib/archethic_web/controllers/api/web_hosting_controller/directory_listing.ex +++ b/lib/archethic_web/controllers/api/web_hosting_controller/directory_listing.ex @@ -1,12 +1,14 @@ defmodule ArchethicWeb.API.WebHostingController.DirectoryListing do @moduledoc false + alias ArchethicWeb.ReferenceTransaction + require Logger @spec list( request_path :: String.t(), params :: map(), - reference_transaction :: {binary(), map(), DateTime.t()}, + reference_transaction :: ReferenceTransaction.t(), cached_headers :: list() ) :: {:ok, listing_html :: binary() | nil, encoding :: nil | binary(), mime_type :: binary(), @@ -14,13 +16,17 @@ defmodule ArchethicWeb.API.WebHostingController.DirectoryListing do def list( request_path, params, - {last_address, json_content, timestamp}, + %ReferenceTransaction{ + address: address, + timestamp: timestamp, + json_content: json_content + }, cache_headers ) do url_path = Map.get(params, "url_path", []) mime_type = "text/html" - case get_cache(cache_headers, last_address, url_path) do + case get_cache(cache_headers, address, url_path) do {cached? = true, etag} -> {:ok, nil, nil, mime_type, cached?, etag} @@ -31,7 +37,7 @@ defmodule ArchethicWeb.API.WebHostingController.DirectoryListing do url_path, elem(get_metadata(json_content), 1), timestamp, - last_address + address ) {:ok, Phoenix.View.render_to_iodata(ArchethicWeb.DirListingView, "index.html", assigns), diff --git a/lib/archethic_web/controllers/api/web_hosting_controller/resources.ex b/lib/archethic_web/controllers/api/web_hosting_controller/resources.ex index ab7970ad5..f7ba2448b 100644 --- a/lib/archethic_web/controllers/api/web_hosting_controller/resources.ex +++ b/lib/archethic_web/controllers/api/web_hosting_controller/resources.ex @@ -3,25 +3,33 @@ defmodule ArchethicWeb.API.WebHostingController.Resources do alias Archethic.TransactionChain.{Transaction, TransactionData} alias ArchethicCache.LRUDisk + alias ArchethicWeb.ReferenceTransaction require Logger - @spec load(tx :: {binary(), map(), DateTime.t()}, url_path :: list(), cache_headers :: list()) :: + @spec load( + reference_transaction :: ReferenceTransaction.t(), + url_path :: list(), + cache_headers :: list() + ) :: {:ok, file_content :: binary() | nil, encoding :: binary() | nil, mime_type :: binary(), cached? :: boolean(), etag :: binary()} | {:error, :file_not_found - | {:is_a_directory, {binary(), map(), DateTime.t()}} + | {:is_a_directory, ReferenceTransaction.t()} | :invalid_encoding | any()} def load( - reference_transaction = {last_address, json_content, _timestamp}, + reference_transaction = %ReferenceTransaction{ + address: address, + json_content: json_content + }, url_path, cache_headers ) do with {:ok, metadata, _aeweb_version} <- get_metadata(json_content), {:ok, file, mime_type, resource_path} <- get_file(metadata, url_path), - {cached?, etag} <- get_cache(cache_headers, last_address, url_path), + {cached?, etag} <- get_cache(cache_headers, address, url_path), {:ok, file_content, encoding} <- get_file_content(file, cached?, resource_path) do {:ok, file_content, encoding, mime_type, cached?, etag} else @@ -32,8 +40,7 @@ defmodule ArchethicWeb.API.WebHostingController.Resources do {:error, :file_not_found} {:error, :get_metadata} -> - {:error, - "Error: Cant access metadata and aewebversion, Reftx: #{Base.encode16(last_address)}"} + {:error, "Error: Cant access metadata and aewebversion, Reftx: #{Base.encode16(address)}"} {:error, :is_a_directory} -> {:error, {:is_a_directory, reference_transaction}} diff --git a/lib/archethic_web/domain.ex b/lib/archethic_web/domain.ex index 89baa706d..5e7459836 100644 --- a/lib/archethic_web/domain.ex +++ b/lib/archethic_web/domain.ex @@ -5,9 +5,8 @@ defmodule ArchethicWeb.Domain do alias Archethic alias Archethic.Crypto - alias Archethic.TransactionChain.Transaction - alias Archethic.TransactionChain.TransactionData alias Archethic.TransactionChain.TransactionData.Ownership + alias ArchethicWeb.ReferenceTransaction require Logger @@ -51,16 +50,11 @@ defmodule ArchethicWeb.Domain do with {:ok, tx_address} <- lookup_dnslink_address(domain), {:ok, tx_address} <- Base.decode16(tx_address, case: :mixed), {:ok, - %Transaction{ - type: :hosting, - data: %TransactionData{ - content: content, - ownerships: [ownership = %Ownership{secret: secret} | _] - } - }} <- - Archethic.get_last_transaction(tx_address), - {:ok, json} <- Jason.decode(content), - {:ok, cert_pem} <- Map.fetch(json, "sslCertificate"), + %ReferenceTransaction{ + json_content: json_content, + ownerships: [ownership = %Ownership{secret: secret} | _] + }} <- ReferenceTransaction.fetch_last(tx_address), + {:ok, cert_pem} <- Map.fetch(json_content, "sslCertificate"), %{extensions: extensions} <- EasySSL.parse_pem(cert_pem), {:ok, san} <- Map.fetch(extensions, :subjectAltName), ^domain <- String.split(san, ":") |> List.last(), diff --git a/lib/archethic_web/reference_transaction.ex b/lib/archethic_web/reference_transaction.ex new file mode 100644 index 000000000..a27742f27 --- /dev/null +++ b/lib/archethic_web/reference_transaction.ex @@ -0,0 +1,69 @@ +defmodule ArchethicWeb.ReferenceTransaction do + @moduledoc """ + ReferenceTransaction is a subset of a transaction + It is meant to be cached so we use a lighter structure + """ + alias Archethic.TransactionChain.Transaction + alias Archethic.TransactionChain.Transaction.ValidationStamp + alias Archethic.TransactionChain.TransactionData + alias Archethic.TransactionChain.TransactionData.Ownership + alias ArchethicCache.LRU + + @enforce_keys [:address, :json_content, :timestamp, :ownerships] + defstruct [:address, :json_content, :timestamp, :ownerships] + + @type t() :: %__MODULE__{ + address: binary(), + json_content: map(), + timestamp: DateTime.t(), + ownerships: list(Ownership.t()) + } + + @doc """ + Fetch the reference transaction either from cache, or from the network. + """ + @spec fetch(binary()) :: {:ok, t()} | {:error, term()} + def fetch(address) do + # started by ArchethicWeb.Supervisor + cache_server = :web_hosting_cache_ref_tx + cache_key = address + + case LRU.get(cache_server, cache_key) do + nil -> + with {:ok, transaction} <- Archethic.search_transaction(address), + {:ok, reference_transaction} <- from_transaction(transaction) do + LRU.put(cache_server, cache_key, reference_transaction) + {:ok, reference_transaction} + end + + reference_transaction -> + {:ok, reference_transaction} + end + end + + @doc """ + Fetch the latest reference transaction of the chain, either from cache, or from the network. + """ + @spec fetch_last(binary()) :: {:ok, t()} | {:error, term()} + def fetch_last(address) do + with {:ok, last_address} <- Archethic.get_last_transaction_address(address) do + fetch(last_address) + end + end + + defp from_transaction(%Transaction{ + address: address, + data: %TransactionData{content: content, ownerships: ownerships}, + validation_stamp: %ValidationStamp{timestamp: timestamp} + }) do + with {:ok, json_content} <- Jason.decode(content) do + {:ok, + %__MODULE__{ + address: address, + json_content: json_content, + timestamp: timestamp, + ownerships: ownerships + }} + end + end +end