diff --git a/.agents/skills/elixir-clause-grouping/SKILL.md b/.agents/skills/elixir-clause-grouping/SKILL.md new file mode 100644 index 000000000000..3042f6d60a20 --- /dev/null +++ b/.agents/skills/elixir-clause-grouping/SKILL.md @@ -0,0 +1,66 @@ +--- +name: elixir-clause-grouping +description: Use when refactoring Elixir multi-clause functions, extracting helper functions, or fixing Credo readability warnings caused by placing `defp` helpers between clauses of the same function. Keeps function clauses contiguous and moves helpers below the full clause group. +--- + +## Overview + +In Elixir modules, all clauses of the same function should stay together. Inserting a `defp` helper between clauses of a `def` or `defp` makes the function harder to read and can trigger Credo readability warnings. When shared logic needs to be extracted, keep the original clause group contiguous and place the helper after the full group. + +## When to Use + +- When refactoring a multi-clause `def` or `defp` +- When extracting duplicated logic from multiple function clauses +- When addressing Credo warnings about clause grouping or readability +- When editing controller, view, or context modules with several clauses of the same function +- During review when a helper was added in the middle of another function's clauses + +## Core Rule + +- Keep all clauses of the same function contiguous +- Do not place `defp` helpers between clauses of another function +- Extract shared logic into a helper placed after the full clause group + +## Anti-Pattern + +```elixir +def decoded_input_data(%Transaction{to_address: nil}, _, _, _, _), do: {:error, :no_to_address} + +defp decode_input_data_with_fallback(data, abi, input, hash, skip_sig_provider?, options, methods_map, abi_map) do + ... +end + +def decoded_input_data(%Transaction{to_address: %NotLoaded{}}, _, _, _, _), do: {:error, :contract_not_verified, []} +``` + +This splits the `decoded_input_data/5` clause group and makes the function harder to scan. + +## Preferred Pattern + +```elixir +def decoded_input_data(%Transaction{to_address: nil}, _, _, _, _), do: {:error, :no_to_address} + +def decoded_input_data(%Transaction{to_address: %NotLoaded{}}, _, _, _, _), do: {:error, :contract_not_verified, []} + +def decoded_input_data(%Transaction{to_address: %{smart_contract: smart_contract}} = transaction, skip_sig_provider?, options, methods_map, abi_map) do + ... +end + +defp decode_input_data_with_fallback(data, abi, input, hash, skip_sig_provider?, options, methods_map, abi_map) do + ... +end +``` + +## Refactoring Checklist + +1. Identify every clause of the function being edited. +2. Keep those clauses adjacent to each other. +3. Extract shared logic only after the full clause group. +4. Re-check that no unrelated `def` or `defp` appears inside the group. +5. Run formatting after the refactor. + +## Notes + +- This applies to both public and private multi-clause functions. +- If a helper is only used by one clause group, place it immediately after that group. +- Preserving clause grouping is preferred even when the extracted helper is small. \ No newline at end of file diff --git a/.agents/skills/update-common-blockscout-env/SKILL.md b/.agents/skills/update-common-blockscout-env/SKILL.md new file mode 100644 index 000000000000..d1e6ba460fb4 --- /dev/null +++ b/.agents/skills/update-common-blockscout-env/SKILL.md @@ -0,0 +1,42 @@ +--- +name: update-common-blockscout-env +description: Ensure every newly introduced environment variable is also added to docker-compose/envs/common-blockscout.env so local Docker setups stay aligned with runtime configuration. +--- + +## Overview + +This skill keeps environment-variable documentation and defaults in sync for Docker users. + +When adding or changing runtime env vars (for example in config/runtime.exs), also update docker-compose/envs/common-blockscout.env in the same task. + +## Mandatory Rule + +- Every new env variable introduced in code/config must be added to docker-compose/envs/common-blockscout.env. +- Do not postpone this to a follow-up task. + +## How To Apply + +1. Identify newly added env vars in changed files (typically config/runtime.exs, config/*.exs, or modules reading System.get_env/1-2). +2. Add each variable to docker-compose/envs/common-blockscout.env. +3. Place it in the most relevant section (for example API flags near other API_* variables). +4. Prefer non-breaking defaults: + - Use a commented example line for optional flags (for example # MY_FLAG=false). + - Use an uncommented value only when the project convention requires a default to be active. +5. Keep naming and formatting consistent with existing entries. + +## Checklist + +- New env var exists in code. +- Matching entry exists in docker-compose/envs/common-blockscout.env. +- Placement is logical and discoverable. +- Default value does not change behavior unexpectedly. + +## Example + +If code adds: + +- DISABLE_TRANSACTIONS_BENS_PRELOAD + +Then docker-compose/envs/common-blockscout.env should include: + +- # DISABLE_TRANSACTIONS_BENS_PRELOAD=false diff --git a/.agents/skills/with-to-case-refactor/SKILL.md b/.agents/skills/with-to-case-refactor/SKILL.md new file mode 100644 index 000000000000..77e42fb1c52c --- /dev/null +++ b/.agents/skills/with-to-case-refactor/SKILL.md @@ -0,0 +1,131 @@ +--- +name: with-to-case-refactor +description: Replace `with` expressions that contain only a single `<-` clause and an `else` branch with a `case` expression. This addresses the Credo warning "with contains only one <- clause and an else branch, consider using case instead" and produces cleaner, more idiomatic Elixir code. +--- + +## Overview + +Elixir's `with` construct is designed for chaining multiple pattern-matching steps. When only one `<-` clause is present alongside an `else` branch, `with` adds no value over a plain `case`. Credo flags this as: + +``` +[R] → `with` contains only one <- clause and an `else` branch, consider using `case` instead +``` + +Always prefer `case` in this situation. + +## When to Use + +- When a `with` expression has exactly one `<-` clause and one or more `else` arms. +- When refactoring code to address the Credo `Credo.Check.Refactor.WithClauses` warning. + +## Anti-Pattern (Avoid) + +```elixir +# ❌ BAD: single-clause with/else — should be a case +with {:ok, response} <- json_rpc(params, opts) do + process(response) +else + {:error, reason} -> + Logger.error("RPC failed: #{inspect(reason)}") + :error +end +``` + +```elixir +# ❌ BAD: single-clause with/else wrapping a nested case +with {:ok, response} <- json_rpc(params, opts) do + case parse(response) do + {:ok, value} -> value + _ -> :error + end +else + {:error, reason} -> + Logger.error("RPC failed: #{inspect(reason)}") + :error +end +``` + +## Best Practice (Use Instead) + +```elixir +# ✅ GOOD: flat case replaces with/else +case json_rpc(params, opts) do + {:ok, response} -> + process(response) + + {:error, reason} -> + Logger.error("RPC failed: #{inspect(reason)}") + :error +end +``` + +```elixir +# ✅ GOOD: nested case is fine when the outer with is replaced +case json_rpc(params, opts) do + {:ok, response} -> + case parse(response) do + {:ok, value} -> value + _ -> :error + end + + {:error, reason} -> + Logger.error("RPC failed: #{inspect(reason)}") + :error +end +``` + +## Transformation Rules + +1. Move the expression on the right-hand side of `<-` to become the subject of `case`. +2. Turn the left-hand side of `<-` into the matching branch of `case`. +3. Move the body of the `with` block as the body of that `case` branch. +4. Move each arm of the `else` block as additional `case` branches. +5. Remove the `with`/`else`/`end` wrapper. + +## Real-World Example (from this codebase) + +### Before + +```elixir +with {:ok, response} <- + params + |> Map.merge(%{id: 0}) + |> Nonce.request() + |> json_rpc(json_rpc_named_arguments) do + case Nonce.from_response(%{id: 0, result: response}, id_to_params) do + {:ok, %{nonce: 0}} -> handle_zero_nonce(...) + {:ok, %{nonce: nonce}} when nonce > 0 -> handle_nonzero_nonce(...) + _ -> retry(...) + end +else + {:error, reason} -> + Logger.error("Error: #{inspect(reason)}") + retry(...) +end +``` + +### After + +```elixir +case params + |> Map.merge(%{id: 0}) + |> Nonce.request() + |> json_rpc(json_rpc_named_arguments) do + {:ok, response} -> + case Nonce.from_response(%{id: 0, result: response}, id_to_params) do + {:ok, %{nonce: 0}} -> handle_zero_nonce(...) + {:ok, %{nonce: nonce}} when nonce > 0 -> handle_nonzero_nonce(...) + _ -> retry(...) + end + + {:error, reason} -> + Logger.error("Error: #{inspect(reason)}") + retry(...) +end +``` + +## Notes + +- If the `with` has **two or more** `<-` clauses, keep it as `with`; this refactor only applies to the single-clause case. +- If there is no `else` branch at all, `with` is also acceptable for a single clause — but a `case` is still clearer and preferred. +- After refactoring, run `mix format` to ensure correct indentation. diff --git a/.credo.exs b/.credo.exs index 7b19b35e9690..2649b6cafc4a 100644 --- a/.credo.exs +++ b/.credo.exs @@ -30,6 +30,10 @@ ] }, # + # Load and configure plugins here: + # + plugins: [], + # # If you create your own checks, you must specify the source files for # them here, so they can be loaded by Credo before running the analysis. # @@ -40,6 +44,10 @@ # strict: true, # + # To modify the timeout for parsing files, change this value: + # + parse_timeout: 5000, + # # If you want to use uncolored output by default, you can change `color` # to `false` below: # @@ -52,101 +60,166 @@ # # {Credo.Check.Design.DuplicatedCode, false} # - checks: [ - # outdated by formatter in Elixir 1.6. See https://github.com/rrrene/credo/issues/505 - {Credo.Check.Consistency.LineEndings, false}, - {Credo.Check.Consistency.SpaceAroundOperators, false}, - {Credo.Check.Consistency.SpaceInParentheses, false}, - {Credo.Check.Consistency.TabsOrSpaces, false}, - {Credo.Check.Readability.LargeNumbers, false}, - {Credo.Check.Readability.MaxLineLength, false}, - {Credo.Check.Readability.ParenthesesInCondition, false}, - {Credo.Check.Readability.RedundantBlankLines, false}, - {Credo.Check.Readability.Semicolons, false}, - {Credo.Check.Readability.SpaceAfterCommas, false}, - {Credo.Check.Readability.TrailingBlankLine, false}, - {Credo.Check.Readability.TrailingWhiteSpace, false}, + checks: %{ + enabled: [ + # + ## Consistency Checks + # + {Credo.Check.Consistency.ExceptionNames, []}, + {Credo.Check.Consistency.ParameterPatternMatching, []}, - # outdated by lazy Logger in Elixir 1.7. See https://elixir-lang.org/blog/2018/07/25/elixir-v1-7-0-released/ - {Credo.Check.Warning.LazyLogging, false}, + # + ## Design Checks + # + # You can customize the priority of any check + # Priority values are: `low, normal, high, higher` + # + {Credo.Check.Design.AliasUsage, + [ + excluded_namespaces: ~w(Block Blocks Import Runner Socket SpandexDatadog Task Schemas), + excluded_lastnames: + ~w(Address DateTime Exporter Fetcher Full Instrumenter Logger Monitor Name Number Repo Spec Time Unit), + priority: :low, + if_nested_deeper_than: 2, + if_called_more_often_than: 0 + ]}, + {Credo.Check.Design.DuplicatedCode, excluded_macros: [], mass_threshold: 800}, + {Credo.Check.Design.TagFIXME, []}, + # You can also customize the exit_status of each check. + # If you don't want TODO comments to cause `mix credo` to fail, just + # set this value to 0 (zero). + # + {Credo.Check.Design.TagTODO, [exit_status: 0]}, - # not handled by formatter - {Credo.Check.Consistency.ExceptionNames}, - {Credo.Check.Consistency.ParameterPatternMatching}, + # + ## Readability Checks + # + {Credo.Check.Readability.AliasOrder, []}, + {Credo.Check.Readability.FunctionNames, []}, + {Credo.Check.Readability.ModuleAttributeNames, []}, + {Credo.Check.Readability.ModuleDoc, []}, + {Credo.Check.Readability.ModuleNames, []}, + {Credo.Check.Readability.ParenthesesOnZeroArityDefs, []}, + {Credo.Check.Readability.PipeIntoAnonymousFunctions, []}, + {Credo.Check.Readability.PredicateFunctionNames, []}, + {Credo.Check.Readability.PreferImplicitTry, []}, + {Credo.Check.Readability.StringSigils, []}, + {Credo.Check.Readability.UnnecessaryAliasExpansion, []}, + {Credo.Check.Readability.VariableNames, []}, + {Credo.Check.Readability.WithSingleClause, []}, - # You can customize the priority of any check - # Priority values are: `low, normal, high, higher` - # - {Credo.Check.Design.AliasUsage, - excluded_namespaces: ~w(Block Blocks Import Runner Socket SpandexDatadog Task Schemas), - excluded_lastnames: - ~w(Address DateTime Exporter Fetcher Full Instrumenter Logger Monitor Name Number Repo Spec Time Unit), - priority: :low}, + # + ## Refactoring Opportunities + # + {Credo.Check.Refactor.Apply, []}, + {Credo.Check.Refactor.CondStatements, []}, + {Credo.Check.Refactor.CyclomaticComplexity, []}, + {Credo.Check.Refactor.FilterCount, []}, + {Credo.Check.Refactor.FilterFilter, []}, + {Credo.Check.Refactor.FunctionArity, []}, + {Credo.Check.Refactor.LongQuoteBlocks, []}, + {Credo.Check.Refactor.MapJoin, []}, + {Credo.Check.Refactor.MatchInCondition, []}, + {Credo.Check.Refactor.NegatedConditionsInUnless, []}, + {Credo.Check.Refactor.NegatedConditionsWithElse, []}, + {Credo.Check.Refactor.Nesting, []}, + {Credo.Check.Refactor.RedundantWithClauseResult, []}, + {Credo.Check.Refactor.RejectReject, []}, + {Credo.Check.Refactor.UnlessWithElse, []}, + {Credo.Check.Refactor.WithClauses, []}, - # For some checks, you can also set other parameters - # - # If you don't want the `setup` and `test` macro calls in ExUnit tests - # or the `schema` macro in Ecto schemas to trigger DuplicatedCode, just - # set the `excluded_macros` parameter to `[:schema, :setup, :test]`. - # - # todo: reduce mass_threshold number - {Credo.Check.Design.DuplicatedCode, excluded_macros: [], mass_threshold: 800}, + # + ## Warnings + # + {Credo.Check.Warning.ApplicationConfigInModuleAttribute, []}, + {Credo.Check.Warning.BoolOperationOnSameValues, []}, + {Credo.Check.Warning.Dbg, []}, + {Credo.Check.Warning.ExpensiveEmptyEnumCheck, []}, + {Credo.Check.Warning.IExPry, []}, + {Credo.Check.Warning.IoInspect, []}, + {Credo.Check.Warning.MissedMetadataKeyInLoggerConfig, []}, + {Credo.Check.Warning.OperationOnSameValues, []}, + {Credo.Check.Warning.OperationWithConstantResult, []}, + {Credo.Check.Warning.RaiseInsideRescue, []}, + {Credo.Check.Warning.SpecWithStruct, []}, + {Credo.Check.Warning.StructFieldAmount, []}, + {Credo.Check.Warning.UnsafeExec, []}, + {Credo.Check.Warning.UnusedEnumOperation, []}, + {Credo.Check.Warning.UnusedFileOperation, []}, + {Credo.Check.Warning.UnusedKeywordOperation, []}, + {Credo.Check.Warning.UnusedListOperation, []}, + {Credo.Check.Warning.UnusedMapOperation, []}, + {Credo.Check.Warning.UnusedPathOperation, []}, + {Credo.Check.Warning.UnusedRegexOperation, []}, + {Credo.Check.Warning.UnusedStringOperation, []}, + {Credo.Check.Warning.UnusedTupleOperation, []}, + {Credo.Check.Warning.WrongTestFilename, []}, + {Utils.Credo.Checks.CompileEnvUsage} + ], + disabled: [ + # + # Checks scheduled for next check update (opt-in for now) + {Credo.Check.Refactor.UtcNowTruncate, []}, - # You can also customize the exit_status of each check. - # If you don't want TODO comments to cause `mix credo` to fail, just - # set this value to 0 (zero). - # - {Credo.Check.Design.TagTODO, exit_status: 0}, - {Credo.Check.Design.TagFIXME}, - {Credo.Check.Readability.FunctionNames}, - {Credo.Check.Readability.ModuleAttributeNames}, - {Credo.Check.Readability.ModuleDoc}, - {Credo.Check.Readability.ModuleNames}, - {Credo.Check.Readability.ParenthesesOnZeroArityDefs}, - {Credo.Check.Readability.PredicateFunctionNames}, - {Credo.Check.Readability.PreferImplicitTry}, - {Credo.Check.Readability.StringSigils}, - {Credo.Check.Readability.VariableNames}, - {Credo.Check.Refactor.DoubleBooleanNegation}, - {Credo.Check.Refactor.CondStatements}, - {Credo.Check.Refactor.CyclomaticComplexity}, - {Credo.Check.Refactor.FunctionArity}, - {Credo.Check.Refactor.LongQuoteBlocks}, - {Credo.Check.Refactor.MatchInCondition}, - {Credo.Check.Refactor.NegatedConditionsInUnless}, - {Credo.Check.Refactor.NegatedConditionsWithElse}, - {Credo.Check.Refactor.Nesting}, - {Credo.Check.Refactor.PipeChainStart}, - {Credo.Check.Refactor.UnlessWithElse}, - {Credo.Check.Warning.BoolOperationOnSameValues}, - {Credo.Check.Warning.ExpensiveEmptyEnumCheck}, - {Credo.Check.Warning.IExPry}, - {Credo.Check.Warning.IoInspect}, - {Credo.Check.Warning.OperationOnSameValues}, - {Credo.Check.Warning.OperationWithConstantResult}, - {Credo.Check.Warning.UnusedEnumOperation}, - {Credo.Check.Warning.UnusedFileOperation}, - {Credo.Check.Warning.UnusedKeywordOperation}, - {Credo.Check.Warning.UnusedListOperation}, - {Credo.Check.Warning.UnusedPathOperation}, - {Credo.Check.Warning.UnusedRegexOperation}, - {Credo.Check.Warning.UnusedStringOperation}, - {Credo.Check.Warning.UnusedTupleOperation}, - {Credo.Check.Warning.RaiseInsideRescue}, + # + # Controversial and experimental checks (opt-in, just move the check to `:enabled` + # and be sure to use `mix credo --strict` to see low priority checks) + # + {Credo.Check.Consistency.LineEndings, []}, + {Credo.Check.Consistency.MultiAliasImportRequireUse, []}, + {Credo.Check.Consistency.SpaceAroundOperators, []}, + {Credo.Check.Consistency.SpaceInParentheses, []}, + {Credo.Check.Consistency.TabsOrSpaces, []}, + {Credo.Check.Consistency.UnusedVariableNames, []}, + {Credo.Check.Design.SkipTestWithoutComment, []}, + {Credo.Check.Readability.AliasAs, []}, + {Credo.Check.Readability.BlockPipe, []}, + {Credo.Check.Readability.ImplTrue, []}, + {Credo.Check.Readability.LargeNumbers, []}, + {Credo.Check.Readability.MaxLineLength, [priority: :low, max_length: 120]}, + {Credo.Check.Readability.MultiAlias, []}, + {Credo.Check.Readability.NestedFunctionCalls, []}, + {Credo.Check.Readability.OneArityFunctionInPipe, []}, + {Credo.Check.Readability.OnePipePerLine, []}, + {Credo.Check.Readability.ParenthesesInCondition, []}, + {Credo.Check.Readability.RedundantBlankLines, []}, + {Credo.Check.Readability.Semicolons, []}, + {Credo.Check.Readability.SeparateAliasRequire, []}, + {Credo.Check.Readability.SingleFunctionToBlockPipe, []}, + {Credo.Check.Readability.SinglePipe, []}, + {Credo.Check.Readability.SpaceAfterCommas, []}, + {Credo.Check.Readability.Specs, []}, + {Credo.Check.Readability.StrictModuleLayout, []}, + {Credo.Check.Readability.TrailingBlankLine, []}, + {Credo.Check.Readability.TrailingWhiteSpace, []}, + {Credo.Check.Readability.WithCustomTaggedTuple, []}, + {Credo.Check.Refactor.ABCSize, []}, + {Credo.Check.Refactor.AppendSingleItem, []}, + {Credo.Check.Refactor.CondInsteadOfIfElse, []}, + {Credo.Check.Refactor.DoubleBooleanNegation, []}, + {Credo.Check.Refactor.FilterReject, []}, + {Credo.Check.Refactor.IoPuts, []}, + {Credo.Check.Refactor.MapMap, []}, + {Credo.Check.Refactor.ModuleDependencies, []}, + {Credo.Check.Refactor.NegatedIsNil, []}, + {Credo.Check.Refactor.PassAsyncInTestCases, []}, + {Credo.Check.Refactor.PipeChainStart, []}, + {Credo.Check.Refactor.RejectFilter, []}, + {Credo.Check.Refactor.VariableRebinding, []}, + {Credo.Check.Warning.LazyLogging, []}, + {Credo.Check.Warning.LeakyEnvironment, []}, + {Credo.Check.Warning.MapGetUnsafePass, []}, + {Credo.Check.Warning.MixEnv, []}, + {Credo.Check.Warning.UnsafeToAtom, []} + # {Credo.Check.Warning.UnusedOperation, [{MyMagicModule, [:fun1, :fun2]}]} - # Controversial and experimental checks (opt-in, just remove `, false`) - # - # TODO reenable before merging optimized-indexer branch - {Credo.Check.Refactor.ABCSize, false}, - {Credo.Check.Refactor.AppendSingleItem}, - {Credo.Check.Refactor.VariableRebinding}, - {Credo.Check.Warning.MapGetUnsafePass}, - {Credo.Check.Consistency.MultiAliasImportRequireUse}, + # {Credo.Check.Refactor.MapInto, []}, - # Custom checks can be created using `mix credo.gen.check`. - {Utils.Credo.Checks.CompileEnvUsage} - # - ] + # + # Custom checks can be created using `mix credo.gen.check`. + # + ] + } } ] } diff --git a/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md similarity index 100% rename from CODE_OF_CONDUCT.md rename to .github/CODE_OF_CONDUCT.md diff --git a/CONTRIBUTING.md b/.github/CONTRIBUTING.md similarity index 96% rename from CONTRIBUTING.md rename to .github/CONTRIBUTING.md index b73c86239bec..326a268c91cb 100644 --- a/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -17,6 +17,7 @@ We welcome contributions that enhance the project and improve the overall qualit ## General +* Keep `.dialyzer-ignore` as small as possible. Only add entries to suppress false positives that cannot be resolved by fixing the underlying type issue. Every new suppression should include a comment explaining why it is necessary and cannot be fixed properly. * Commits should be one logical change that still allows all tests to pass. Prefer smaller commits if there could be two levels of logic grouping. The goal is to allow contributors in the future (including your own future self) to determine your reasoning for making changes and to allow them to cherry-pick, patch or port those changes in isolation to other branches or forks. * If during your PR you reveal a pre-existing bug: 1. Try to isolate the bug and fix it on an independent branch and PR it first. @@ -180,8 +181,8 @@ runtime approaches: ```elixir scope "/v2", as: :api_v2 do - chain_scope :polygon_zkevm do - get("/zkevm-batch/:batch_number", V2.TransactionController, :polygon_zkevm_batch) + chain_scope :zksync do + get("/zksync-batch/:batch_number", V2.TransactionController, :zksync_batch) end end ``` diff --git a/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md similarity index 100% rename from PULL_REQUEST_TEMPLATE.md rename to .github/PULL_REQUEST_TEMPLATE.md diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml new file mode 100644 index 000000000000..64872574b096 --- /dev/null +++ b/.github/workflows/config.yml @@ -0,0 +1,815 @@ +name: Blockscout + +on: + push: + branches: + - master + paths-ignore: + - "CHANGELOG.md" + - "**/README.md" + - "docker/*" + - "docker-compose/*" + workflow_dispatch: + pull_request: + types: [opened, synchronize, reopened, labeled] + branches: + - master + +env: + MIX_ENV: test + OTP_VERSION: '27.3.4.6' + ELIXIR_VERSION: '1.19.4' + ACCOUNT_AUTH0_DOMAIN: "blockscoutcom.us.auth0.com" + +jobs: + matrix-builder: + name: Build matrix + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + steps: + - id: set-matrix + run: | + echo "matrix=$(node -e ' + + const defaultChainTypes = ["default"]; + + // Add/remove CI matrix chain types here + const chainTypes = [ + "default", + "arbitrum", + "arc", + "blackfort", + "ethereum", + "filecoin", + "optimism", + "optimism-celo", + "rsk", + "scroll", + "shibarium", + "stability", + "zetachain", + "zilliqa", + "zksync", + "neon" + ]; + + // Add/remove CI matrix chain types for "ci:core" label here + const coreChainTypes = [ + "default", + "ethereum", + "optimism", + "optimism-celo" + ]; + + const labels = ${{ github.event_name == 'pull_request' && toJson(github.event.pull_request.labels.*.name) || '[]' }}; + const ciLabels = labels.filter(label => label.startsWith("ci:")); + const labeledChainTypes = chainTypes.filter(chainType => + ciLabels.includes("ci:all") || + ciLabels.includes("ci:core") && coreChainTypes.includes(chainType) || + ciLabels.includes("ci:" + chainType) + ); + + // Chain type matrix we use in PRs to master branch + const ciChainTypes = labeledChainTypes.length > 0 ? labeledChainTypes : defaultChainTypes; + + // Check for bridged tokens label + const hasBridgedTokensLabel = ciLabels.includes("ci:bridged-tokens"); + + // Create matrix combinations + const targetChainTypes = ${{ github.event_name == 'pull_request' && 'ciChainTypes' || 'chainTypes' }}; + const bridgedTokensConfigs = hasBridgedTokensLabel ? [false, true] : [false]; + + const matrixIncludes = []; + for (const chainType of targetChainTypes) { + for (const bridgedTokens of bridgedTokensConfigs) { + matrixIncludes.push({ + "chain-type": chainType, + "bridged-tokens": bridgedTokens + }); + } + } + + const matrix = { "include": matrixIncludes }; + console.log(JSON.stringify(matrix)); + ')" >> $GITHUB_OUTPUT + + build-and-cache: + name: Build and Cache deps + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - uses: erlef/setup-beam@v1 + with: + otp-version: ${{ env.OTP_VERSION }} + elixir-version: ${{ env.ELIXIR_VERSION }} + hexpm-mirrors: | + https://builds.hex.pm + https://cdn.jsdelivr.net/hex + + - name: "ELIXIR_VERSION.lock" + run: echo "${ELIXIR_VERSION}" > ELIXIR_VERSION.lock + + - name: "OTP_VERSION.lock" + run: echo "${OTP_VERSION}" > OTP_VERSION.lock + + - name: Restore Mix Deps Cache + uses: actions/cache@v4 + id: deps-cache + with: + path: | + deps + _build + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash-${{ hashFiles('mix.lock') }} + restore-keys: | + ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash- + + - name: Conditionally build Mix deps cache + if: steps.deps-cache.outputs.cache-hit != 'true' + run: | + mix local.hex --force + mix local.rebar --force + mix deps.clean --all + mix deps.get + mix deps.compile --skip-umbrella-children + + - name: Restore Explorer NPM Cache + uses: actions/cache@v4 + id: explorer-npm-cache + with: + path: apps/explorer/node_modules + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-explorer-npm-${{ hashFiles('apps/explorer/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-explorer-npm- + + - name: Conditionally build Explorer NPM Cache + if: steps.explorer-npm-cache.outputs.cache-hit != 'true' + run: npm install + working-directory: apps/explorer + + - name: Restore Blockscout Web NPM Cache + uses: actions/cache@v4 + id: blockscoutweb-npm-cache + with: + path: apps/block_scout_web/assets/node_modules + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-blockscoutweb-npm-${{ hashFiles('apps/block_scout_web/assets/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-blockscoutweb-npm- + + - name: Conditionally build Blockscout Web NPM Cache + if: steps.blockscoutweb-npm-cache.outputs.cache-hit != 'true' + run: npm install + working-directory: apps/block_scout_web/assets + + credo: + name: Credo + runs-on: ubuntu-latest + needs: build-and-cache + steps: + - uses: actions/checkout@v5 + - uses: erlef/setup-beam@v1 + with: + otp-version: ${{ env.OTP_VERSION }} + elixir-version: ${{ env.ELIXIR_VERSION }} + hexpm-mirrors: | + https://builds.hex.pm + https://cdn.jsdelivr.net/hex + + - name: Restore Mix Deps Cache + uses: actions/cache/restore@v4 + id: deps-cache + with: + path: | + deps + _build + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash-${{ hashFiles('mix.lock') }} + restore-keys: | + ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash- + + - run: mix credo + + check_formatted: + name: Code formatting checks + runs-on: ubuntu-latest + needs: build-and-cache + steps: + - uses: actions/checkout@v5 + - uses: erlef/setup-beam@v1 + with: + otp-version: ${{ env.OTP_VERSION }} + elixir-version: ${{ env.ELIXIR_VERSION }} + hexpm-mirrors: | + https://builds.hex.pm + https://cdn.jsdelivr.net/hex + + - name: Restore Mix Deps Cache + uses: actions/cache/restore@v4 + id: deps-cache + with: + path: | + deps + _build + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash-${{ hashFiles('mix.lock') }} + restore-keys: | + ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash- + + - run: mix format --check-formatted + + dialyzer: + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.matrix-builder.outputs.matrix) }} + name: Dialyzer static analysis + runs-on: ubuntu-latest + needs: + - build-and-cache + - matrix-builder + steps: + - uses: actions/checkout@v5 + - uses: erlef/setup-beam@v1 + with: + otp-version: ${{ env.OTP_VERSION }} + elixir-version: ${{ env.ELIXIR_VERSION }} + hexpm-mirrors: | + https://builds.hex.pm + https://cdn.jsdelivr.net/hex + + - name: Restore Mix Deps Cache + uses: actions/cache/restore@v4 + id: deps-cache + with: + path: | + deps + _build + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash-${{ hashFiles('mix.lock') }} + restore-keys: | + ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash- + + - name: Restore Dialyzer Cache + uses: actions/cache@v4 + id: dialyzer-cache + with: + path: priv/plts + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-${{ matrix.chain-type }}-${{ matrix.bridged-tokens }}-dialyzer-mixlockhash-${{ hashFiles('mix.lock') }} + restore-keys: | + ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-${{ matrix.chain-type }}-${{ matrix.bridged-tokens }}-dialyzer-mixlockhash- + + - name: Conditionally build Dialyzer Cache + if: steps.dialyzer-cache.output.cache-hit != 'true' + run: | + mkdir -p priv/plts + mix dialyzer --plt + env: + CHAIN_TYPE: ${{ matrix.chain-type != 'default' && matrix.chain-type || '' }} + BRIDGED_TOKENS_ENABLED: ${{ matrix.bridged-tokens }} + + - name: Run Dialyzer + run: mix dialyzer --halt-exit-status + env: + CHAIN_TYPE: ${{ matrix.chain-type != 'default' && matrix.chain-type || '' }} + BRIDGED_TOKENS_ENABLED: ${{ matrix.bridged-tokens }} + + gettext: + name: Missing translation keys check + runs-on: ubuntu-latest + needs: build-and-cache + steps: + - uses: actions/checkout@v5 + - uses: erlef/setup-beam@v1 + with: + otp-version: ${{ env.OTP_VERSION }} + elixir-version: ${{ env.ELIXIR_VERSION }} + hexpm-mirrors: | + https://builds.hex.pm + https://cdn.jsdelivr.net/hex + + - name: Restore Mix Deps Cache + uses: actions/cache/restore@v4 + id: deps-cache + with: + path: | + deps + _build + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash-${{ hashFiles('mix.lock') }} + restore-keys: | + ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash- + + - run: | + mix gettext.extract --merge | tee stdout.txt + grep "Wrote priv/gettext/en/LC_MESSAGES/default.po (0 new messages, 0 removed, " stdout.txt + working-directory: "apps/block_scout_web" + + sobelow: + name: Sobelow security analysis + runs-on: ubuntu-latest + needs: build-and-cache + steps: + - uses: actions/checkout@v5 + - uses: erlef/setup-beam@v1 + with: + otp-version: ${{ env.OTP_VERSION }} + elixir-version: ${{ env.ELIXIR_VERSION }} + hexpm-mirrors: | + https://builds.hex.pm + https://cdn.jsdelivr.net/hex + + - name: Mix Deps Cache + uses: actions/cache/restore@v4 + id: deps-cache + with: + path: | + deps + _build + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash-${{ hashFiles('mix.lock') }} + restore-keys: | + ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash- + + - name: Scan explorer for vulnerabilities + run: mix sobelow --config + working-directory: "apps/explorer" + - name: Scan block_scout_web for vulnerabilities + run: mix sobelow --config + working-directory: "apps/block_scout_web" + + cspell: + name: Check spelling + runs-on: ubuntu-latest + needs: build-and-cache + steps: + - uses: actions/checkout@v5 + - uses: erlef/setup-beam@v1 + with: + otp-version: ${{ env.OTP_VERSION }} + elixir-version: ${{ env.ELIXIR_VERSION }} + hexpm-mirrors: | + https://builds.hex.pm + https://cdn.jsdelivr.net/hex + + - name: Mix Deps Cache + uses: actions/cache/restore@v4 + id: deps-cache + with: + path: | + deps + _build + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash-${{ hashFiles('mix.lock') }} + restore-keys: | + ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash- + + - name: Restore Explorer NPM Cache + uses: actions/cache@v4 + id: explorer-npm-cache + with: + path: apps/explorer/node_modules + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-explorer-npm-${{ hashFiles('apps/explorer/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-explorer-npm- + + - name: Restore Blockscout Web NPM Cache + uses: actions/cache@v4 + id: blockscoutweb-npm-cache + with: + path: apps/block_scout_web/assets/node_modules + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-blockscoutweb-npm-${{ hashFiles('apps/block_scout_web/assets/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-blockscoutweb-npm- + + - name: Run cspell + uses: streetsidesoftware/cspell-action@v6 + with: + use_cspell_files: true + incremental_files_only: false + + eslint: + name: ESLint + runs-on: ubuntu-latest + needs: build-and-cache + steps: + - uses: actions/checkout@v5 + - uses: erlef/setup-beam@v1 + with: + otp-version: ${{ env.OTP_VERSION }} + elixir-version: ${{ env.ELIXIR_VERSION }} + hexpm-mirrors: | + https://builds.hex.pm + https://cdn.jsdelivr.net/hex + + - name: Mix Deps Cache + uses: actions/cache/restore@v4 + id: deps-cache + with: + path: | + deps + _build + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash-${{ hashFiles('mix.lock') }} + restore-keys: | + ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash- + + - name: Restore Explorer NPM Cache + uses: actions/cache@v4 + id: explorer-npm-cache + with: + path: apps/explorer/node_modules + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-explorer-npm-${{ hashFiles('apps/explorer/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-explorer-npm- + + - name: Restore Blockscout Web NPM Cache + uses: actions/cache@v4 + id: blockscoutweb-npm-cache + with: + path: apps/block_scout_web/assets/node_modules + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-blockscoutweb-npm-${{ hashFiles('apps/block_scout_web/assets/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-blockscoutweb-npm- + + - name: Build assets + run: node node_modules/webpack/bin/webpack.js --mode development + working-directory: "apps/block_scout_web/assets" + + - run: ./node_modules/.bin/eslint --format=junit --output-file="test/eslint/junit.xml" js/** + working-directory: apps/block_scout_web/assets + + jest: + name: JS Tests + runs-on: ubuntu-latest + needs: build-and-cache + steps: + - uses: actions/checkout@v5 + - uses: erlef/setup-beam@v1 + with: + otp-version: ${{ env.OTP_VERSION }} + elixir-version: ${{ env.ELIXIR_VERSION }} + hexpm-mirrors: | + https://builds.hex.pm + https://cdn.jsdelivr.net/hex + + - name: Mix Deps Cache + uses: actions/cache/restore@v4 + id: deps-cache + with: + path: | + deps + _build + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash-${{ hashFiles('mix.lock') }} + restore-keys: | + ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash- + + - name: Restore Blockscout Web NPM Cache + uses: actions/cache@v4 + id: blockscoutweb-npm-cache + with: + path: apps/block_scout_web/assets/node_modules + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-blockscoutweb-npm-${{ hashFiles('apps/block_scout_web/assets/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-blockscoutweb-npm- + + - name: Build assets + run: node node_modules/webpack/bin/webpack.js --mode development + working-directory: "apps/block_scout_web/assets" + + - run: ./node_modules/.bin/jest + working-directory: apps/block_scout_web/assets + + test_utils: + name: Utils Tests + runs-on: ubuntu-latest + needs: build-and-cache + steps: + - uses: actions/checkout@v5 + - uses: erlef/setup-beam@v1 + with: + otp-version: ${{ env.OTP_VERSION }} + elixir-version: ${{ env.ELIXIR_VERSION }} + hexpm-mirrors: | + https://builds.hex.pm + https://cdn.jsdelivr.net/hex + + - name: Restore Mix Deps Cache + uses: actions/cache/restore@v4 + id: deps-cache + with: + path: | + deps + _build + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash-${{ hashFiles('mix.lock') }} + restore-keys: | + ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash- + + - working-directory: apps/utils + run: mix test + + test_nethermind_mox_ethereum_jsonrpc: + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.matrix-builder.outputs.matrix) }} + name: EthereumJSONRPC Tests + runs-on: ubuntu-latest + needs: + - build-and-cache + - matrix-builder + services: + postgres: + image: postgres:17 + env: + # Match apps/explorer/config/test.exs config :explorer, Explorer.Repo, database + POSTGRES_DB: explorer_test + # match PGPASSWORD for elixir image above + POSTGRES_PASSWORD: postgres + # match PGUSER for elixir image above + POSTGRES_USER: postgres + # Set health checks to wait until postgres has started + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + # Maps tcp port 5432 on service container to the host + - 5432:5432 + steps: + - uses: actions/checkout@v5 + - uses: erlef/setup-beam@v1 + with: + otp-version: ${{ env.OTP_VERSION }} + elixir-version: ${{ env.ELIXIR_VERSION }} + hexpm-mirrors: | + https://builds.hex.pm + https://cdn.jsdelivr.net/hex + + - name: Mix Deps Cache + uses: actions/cache/restore@v4 + id: deps-cache + with: + path: | + deps + _build + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash-${{ hashFiles('mix.lock') }} + restore-keys: | + ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash- + + - run: ./bin/install_chrome_headless.sh + - name: mix test --exclude no_nethermind + run: | + cd apps/ethereum_jsonrpc + mix compile + mix test --no-start --exclude no_nethermind + env: + # match POSTGRES_PASSWORD for postgres image below + PGPASSWORD: postgres + # match POSTGRES_USER for postgres image below + PGUSER: postgres + ETHEREUM_JSONRPC_CASE: "EthereumJSONRPC.Case.Nethermind.Mox" + ETHEREUM_JSONRPC_WEB_SOCKET_CASE: "EthereumJSONRPC.WebSocket.Case.Mox" + CHAIN_TYPE: ${{ matrix.chain-type != 'default' && matrix.chain-type || '' }} + BRIDGED_TOKENS_ENABLED: ${{ matrix.bridged-tokens }} + + test_nethermind_mox_explorer: + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.matrix-builder.outputs.matrix) }} + name: Explorer Tests + runs-on: ubuntu-latest + needs: + - build-and-cache + - matrix-builder + services: + postgres: + image: postgres:17 + env: + # Match apps/explorer/config/test.exs config :explorer, Explorer.Repo, database + POSTGRES_DB: explorer_test + # match PGPASSWORD for elixir image above + POSTGRES_PASSWORD: postgres + # match PGUSER for elixir image above + POSTGRES_USER: postgres + # Set health checks to wait until postgres has started + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + # Maps tcp port 5432 on service container to the host + - 5432:5432 + steps: + - uses: actions/checkout@v5 + - uses: erlef/setup-beam@v1 + with: + otp-version: ${{ env.OTP_VERSION }} + elixir-version: ${{ env.ELIXIR_VERSION }} + hexpm-mirrors: | + https://builds.hex.pm + https://cdn.jsdelivr.net/hex + + - name: Mix Deps Cache + uses: actions/cache/restore@v4 + id: deps-cache + with: + path: | + deps + _build + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash-${{ hashFiles('mix.lock') }} + restore-keys: | + ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash- + + - name: Restore Explorer NPM Cache + uses: actions/cache@v4 + id: explorer-npm-cache + with: + path: apps/explorer/node_modules + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-explorer-npm-${{ hashFiles('apps/explorer/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-explorer-npm + + - run: ./bin/install_chrome_headless.sh + - name: mix test --exclude no_nethermind + run: | + mix ecto.create --quiet + mix ecto.migrate + cd apps/explorer + mix compile + mix test --no-start --exclude no_nethermind + env: + # match POSTGRES_PASSWORD for postgres image below + PGPASSWORD: postgres + # match POSTGRES_USER for postgres image below + PGUSER: postgres + ETHEREUM_JSONRPC_CASE: "EthereumJSONRPC.Case.Nethermind.Mox" + ETHEREUM_JSONRPC_WEB_SOCKET_CASE: "EthereumJSONRPC.WebSocket.Case.Mox" + CHAIN_TYPE: ${{ matrix.chain-type != 'default' && matrix.chain-type || '' }} + WETH_TOKEN_TRANSFERS_FILTERING_ENABLED: "true" + BRIDGED_TOKENS_ENABLED: ${{ matrix.bridged-tokens }} + + test_nethermind_mox_indexer: + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.matrix-builder.outputs.matrix) }} + name: Indexer Tests + runs-on: ubuntu-latest + needs: + - build-and-cache + - matrix-builder + services: + postgres: + image: postgres:17 + env: + # Match apps/explorer/config/test.exs config :explorer, Explorer.Repo, database + POSTGRES_DB: explorer_test + # match PGPASSWORD for elixir image above + POSTGRES_PASSWORD: postgres + # match PGUSER for elixir image above + POSTGRES_USER: postgres + # Set health checks to wait until postgres has started + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + # Maps tcp port 5432 on service container to the host + - 5432:5432 + steps: + - uses: actions/checkout@v5 + - uses: erlef/setup-beam@v1 + with: + otp-version: ${{ env.OTP_VERSION }} + elixir-version: ${{ env.ELIXIR_VERSION }} + hexpm-mirrors: | + https://builds.hex.pm + https://cdn.jsdelivr.net/hex + + - name: Mix Deps Cache + uses: actions/cache/restore@v4 + id: deps-cache + with: + path: | + deps + _build + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash-${{ hashFiles('mix.lock') }} + restore-keys: | + ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash- + + - run: ./bin/install_chrome_headless.sh + + - name: mix test --exclude no_nethermind + run: | + mix ecto.create --quiet + mix ecto.migrate + cd apps/indexer + mix compile + mix test --no-start --exclude no_nethermind + env: + # match POSTGRES_PASSWORD for postgres image below + PGPASSWORD: postgres + # match POSTGRES_USER for postgres image below + PGUSER: postgres + ETHEREUM_JSONRPC_CASE: "EthereumJSONRPC.Case.Nethermind.Mox" + ETHEREUM_JSONRPC_WEB_SOCKET_CASE: "EthereumJSONRPC.WebSocket.Case.Mox" + CHAIN_TYPE: ${{ matrix.chain-type != 'default' && matrix.chain-type || '' }} + WETH_TOKEN_TRANSFERS_FILTERING_ENABLED: "true" + BRIDGED_TOKENS_ENABLED: ${{ matrix.bridged-tokens }} + + test_nethermind_mox_block_scout_web: + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.matrix-builder.outputs.matrix) }} + name: Blockscout Web Tests + runs-on: ubuntu-latest + needs: + - build-and-cache + - matrix-builder + services: + redis-db: + image: "redis:alpine" + ports: + - 6379:6379 + + postgres: + image: postgres:17 + env: + # Match apps/explorer/config/test.exs config :explorer, Explorer.Repo, database + POSTGRES_DB: explorer_test + # match PGPASSWORD for elixir image above + POSTGRES_PASSWORD: postgres + # match PGUSER for elixir image above + POSTGRES_USER: postgres + # Set health checks to wait until postgres has started + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + # Maps tcp port 5432 on service container to the host + - 5432:5432 + steps: + - uses: actions/checkout@v5 + - uses: erlef/setup-beam@v1 + with: + otp-version: ${{ env.OTP_VERSION }} + elixir-version: ${{ env.ELIXIR_VERSION }} + hexpm-mirrors: | + https://builds.hex.pm + https://cdn.jsdelivr.net/hex + + - name: Mix Deps Cache + uses: actions/cache/restore@v4 + id: deps-cache + with: + path: | + deps + _build + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash-${{ hashFiles('mix.lock') }} + restore-keys: | + ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash- + + - name: Restore Explorer NPM Cache + uses: actions/cache@v4 + id: explorer-npm-cache + with: + path: apps/explorer/node_modules + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-explorer-npm-${{ hashFiles('apps/explorer/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-explorer-npm- + + - name: Restore Blockscout Web NPM Cache + uses: actions/cache@v4 + id: blockscoutweb-npm-cache + with: + path: apps/block_scout_web/assets/node_modules + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-blockscoutweb-npm-${{ hashFiles('apps/block_scout_web/assets/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-blockscoutweb-npm- + + - name: Build assets + run: node node_modules/webpack/bin/webpack.js --mode development + working-directory: "apps/block_scout_web/assets" + + - run: ./bin/install_chrome_headless.sh + + - name: mix test --exclude no_nethermind + run: | + mix ecto.create --quiet + mix ecto.migrate + cd apps/block_scout_web + mix compile + mix test --no-start --exclude no_nethermind + env: + # match POSTGRES_PASSWORD for postgres image below + PGPASSWORD: postgres + # match POSTGRES_USER for postgres image below + PGUSER: postgres + ETHEREUM_JSONRPC_CASE: "EthereumJSONRPC.Case.Nethermind.Mox" + ETHEREUM_JSONRPC_WEB_SOCKET_CASE: "EthereumJSONRPC.WebSocket.Case.Mox" + CHAIN_ID: "10200" + API_RATE_LIMIT_DISABLED: "true" + API_GRAPHQL_RATE_LIMIT_DISABLED: "true" + ADMIN_PANEL_ENABLED: "true" + ACCOUNT_ENABLED: "true" + ACCOUNT_REDIS_URL: "redis://localhost:6379" + SOURCIFY_INTEGRATION_ENABLED: "true" + CHAIN_TYPE: ${{ matrix.chain-type != 'default' && matrix.chain-type || '' }} + WETH_TOKEN_TRANSFERS_FILTERING_ENABLED: "true" + BRIDGED_TOKENS_ENABLED: ${{ matrix.bridged-tokens }} + DISABLE_WEBAPP: "false" diff --git a/.github/workflows/generate-swagger.yml b/.github/workflows/generate-swagger.yml new file mode 100644 index 000000000000..050ad383457d --- /dev/null +++ b/.github/workflows/generate-swagger.yml @@ -0,0 +1,291 @@ +name: Generate OpenAPI Specs + +on: + push: + branches: + - master + - np-integrate-open-api-spex + paths-ignore: + - "CHANGELOG.md" + - "**/README.md" + - "docker/*" + - "docker-compose/*" + workflow_dispatch: + release: + types: [published] + +env: + OTP_VERSION: '27.3.4.6' + ELIXIR_VERSION: '1.19.4' + RELEASE_VERSION: 11.0.0 + +jobs: + matrix-builder: + name: Build matrix + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + steps: + - id: set-matrix + run: | + echo "matrix=$(node -e ' + + // Add/remove CI matrix chain types here + const chainTypes = [ + "default", + "arbitrum", + "arc", + "blackfort", + "ethereum", + "filecoin", + "neon", + "optimism", + "optimism-celo", + "rsk", + "scroll", + "shibarium", + "stability", + "suave", + "zetachain", + "zilliqa", + "zksync" + ]; + + const matrix = { "chain-type": ${{ 'chainTypes' }} }; + console.log(JSON.stringify(matrix)); + ')" >> $GITHUB_OUTPUT + + build-and-cache: + name: Build and Cache deps + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - uses: erlef/setup-beam@v1 + with: + otp-version: ${{ env.OTP_VERSION }} + elixir-version: ${{ env.ELIXIR_VERSION }} + hexpm-mirrors: | + https://builds.hex.pm + https://cdn.jsdelivr.net/hex + + - name: "ELIXIR_VERSION.lock" + run: echo "${ELIXIR_VERSION}" > ELIXIR_VERSION.lock + + - name: "OTP_VERSION.lock" + run: echo "${OTP_VERSION}" > OTP_VERSION.lock + + - name: Restore Mix Deps Cache + uses: actions/cache@v4 + id: deps-cache + with: + path: | + deps + _build + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash-${{ hashFiles('mix.lock') }} + restore-keys: | + ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash- + + - name: Conditionally build Mix deps cache + if: steps.deps-cache.outputs.cache-hit != 'true' + run: | + mix local.hex --force + mix local.rebar --force + mix deps.get + mix deps.compile --skip-umbrella-children + + generate-swagger: + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.matrix-builder.outputs.matrix) }} + name: Generate Open API spec + runs-on: ubuntu-latest + needs: + - build-and-cache + - matrix-builder + steps: + - uses: actions/checkout@v5 + - uses: erlef/setup-beam@v1 + with: + otp-version: ${{ env.OTP_VERSION }} + elixir-version: ${{ env.ELIXIR_VERSION }} + hexpm-mirrors: | + https://builds.hex.pm + https://cdn.jsdelivr.net/hex + + - name: Mix Deps Cache + uses: actions/cache/restore@v4 + id: deps-cache + with: + path: | + deps + _build + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash-${{ hashFiles('mix.lock') }} + restore-keys: | + ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash- + + - name: mix openapi.spec.yaml + run: | + mix openapi.spec.yaml --spec BlockScoutWeb.Specs.Public openapi.${{ matrix.chain-type }}.yaml --start-app=false + env: + CHAIN_TYPE: ${{ matrix.chain-type != 'default' && matrix.chain-type || '' }} + MUD_INDEXER_ENABLED: false + + - name: Generate MUD-enabled spec for Optimism + if: matrix.chain-type == 'optimism' + run: | + mix openapi.spec.yaml --spec BlockScoutWeb.Specs.Public openapi.mud.yaml --start-app=false + env: + CHAIN_TYPE: optimism + MUD_INDEXER_ENABLED: true + + - name: Upload OpenAPI spec + uses: actions/upload-artifact@v4 + with: + name: openapi-spec-${{ matrix.chain-type }} + path: openapi.${{ matrix.chain-type }}.yaml + retention-days: 1 + + - name: Upload MUD-enabled spec + if: matrix.chain-type == 'optimism' + uses: actions/upload-artifact@v4 + with: + name: openapi-spec-mud + path: openapi.mud.yaml + retention-days: 1 + + push-specs: + needs: + - generate-swagger + - matrix-builder + runs-on: ubuntu-latest + name: Push all OpenAPI specs + steps: + - name: Validate required secrets + run: | + if [ -z "${{ secrets.API_SPECS_PAT }}" ]; then + echo "Error: API_SPECS_PAT secret is not set" + exit 1 + fi + + - name: Checkout specs repository + uses: actions/checkout@v5 + with: + repository: ${{ vars.API_SPECS_REPOSITORY }} + token: ${{ secrets.API_SPECS_PAT }} + path: api-specs + + - name: Download all swagger specs + uses: actions/download-artifact@v5 + with: + pattern: openapi-spec-* + merge-multiple: true + path: temp-specs + + - name: Merge all OpenAPI specs into all-in-one spec + run: | + npm install js-yaml + cat > merge-specs.js << 'SCRIPT_EOF' + const fs = require('fs'); + const path = require('path'); + const yaml = require('js-yaml'); + + const specDir = './temp-specs'; + const outputFile = path.join(specDir, 'openapi.all.yaml'); + + const files = fs.readdirSync(specDir) + .filter(f => f.endsWith('.yaml')) + .sort(); + + let merged = null; + + for (const file of files) { + const content = yaml.load(fs.readFileSync(path.join(specDir, file), 'utf8')); + if (!merged) { + merged = JSON.parse(JSON.stringify(content)); + continue; + } + // Union merge paths (first definition wins for duplicates) + if (content.paths) { + merged.paths = merged.paths || {}; + for (const [p, def] of Object.entries(content.paths)) { + if (!merged.paths[p]) { + merged.paths[p] = def; + } + } + } + // Union merge components (first definition wins for duplicates) + if (content.components) { + merged.components = merged.components || {}; + for (const [section, defs] of Object.entries(content.components)) { + if (!merged.components[section]) { + merged.components[section] = defs; + } else { + for (const [name, def] of Object.entries(defs)) { + if (!merged.components[section][name]) { + merged.components[section][name] = def; + } + } + } + } + } + // Union merge tags (by name) + if (content.tags) { + merged.tags = merged.tags || []; + const existingTagNames = new Set(merged.tags.map(t => t.name)); + for (const tag of content.tags) { + if (!existingTagNames.has(tag.name)) { + merged.tags.push(tag); + existingTagNames.add(tag.name); + } + } + } + } + + fs.writeFileSync(outputFile, yaml.dump(merged, { lineWidth: -1 })); + console.log(`Merged ${files.length} specs into ${outputFile}`); + SCRIPT_EOF + node merge-specs.js + + - name: Create specs directory structure + run: | + VERSION=${{ github.event_name == 'release' && env.RELEASE_VERSION || 'master' }} + + for SPEC_FILE in temp-specs/*; do + if [ -f "$SPEC_FILE" ]; then + FILENAME=$(basename "$SPEC_FILE") + + # Handle MUD spec specially + if [ "$FILENAME" = "openapi.mud.yaml" ]; then + mkdir -p "api-specs/blockscout/${VERSION}/mud" + cp "$SPEC_FILE" "api-specs/blockscout/${VERSION}/mud/swagger.yaml" + else + # Extract chain type from filename (openapi.CHAINTYPE.yaml) + CHAIN_TYPE=$(echo "$FILENAME" | sed 's/openapi\.\(.*\)\.yaml/\1/') + mkdir -p "api-specs/blockscout/${VERSION}/${CHAIN_TYPE}" + cp "$SPEC_FILE" "api-specs/blockscout/${VERSION}/${CHAIN_TYPE}/swagger.yaml" + fi + fi + done + + + - name: Commit and push changes + working-directory: api-specs + run: | + git config --local user.email "github-actions[bot]@users.noreply.github.com" + git config --local user.name "github-actions[bot]" + + git add . + + # Only commit if there are changes + if git diff --staged --quiet; then + echo "No changes to commit" + else + git commit -m "[SKIP-GH-PAGES] create OpenAPI specs for Blockscout ${{ github.event_name == 'release' && env.RELEASE_VERSION || github.sha }}" + git push + fi + + - name: Clean up + if: always() + run: | + rm -rf temp-specs + rm -rf api-specs diff --git a/.github/workflows/pre-release-arbitrum.yml b/.github/workflows/pre-release-arbitrum.yml new file mode 100644 index 000000000000..10d639864c77 --- /dev/null +++ b/.github/workflows/pre-release-arbitrum.yml @@ -0,0 +1,67 @@ +name: Pre-release for Arbitrum + +on: + workflow_dispatch: + inputs: + number: + type: number + description: Number of pre-release alpha iteration + required: true + +permissions: + contents: read + packages: write + +env: + OTP_VERSION: '27.3.4.6' + ELIXIR_VERSION: '1.19.4' + +jobs: + push_to_registry: + name: Push Docker image to GitHub Container Registry + runs-on: build + env: + RELEASE_VERSION: 11.0.0 + steps: + - uses: actions/checkout@v5 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image for Arbitrum (indexer + API) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-arbitrum-private:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=arbitrum + + - name: Build and push Docker image for Arbitrum (indexer) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-arbitrum-private:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=arbitrum diff --git a/.github/workflows/pre-release-celo.yml b/.github/workflows/pre-release-celo.yml new file mode 100644 index 000000000000..be86acef656a --- /dev/null +++ b/.github/workflows/pre-release-celo.yml @@ -0,0 +1,68 @@ +name: Pre-release for CELO + +on: + workflow_dispatch: + inputs: + number: + type: number + description: Number of pre-release alpha iteration + required: true + +permissions: + contents: read + packages: write + +env: + OTP_VERSION: '27.3.4.6' + ELIXIR_VERSION: '1.19.4' + +jobs: + push_to_registry: + name: Push Docker image to GitHub Container Registry + runs-on: build + env: + RELEASE_VERSION: 11.0.0 + API_GRAPHQL_MAX_COMPLEXITY: 10400 + steps: + - uses: actions/checkout@v5 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image for CELO (indexer + API) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-celo-private:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=optimism-celo + + - name: Build and push Docker image for CELO (indexer) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-celo-private:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=optimism-celo diff --git a/.github/workflows/pre-release-eth.yml b/.github/workflows/pre-release-eth.yml new file mode 100644 index 000000000000..9c041cc33bba --- /dev/null +++ b/.github/workflows/pre-release-eth.yml @@ -0,0 +1,67 @@ +name: Pre-release for Ethereum + +on: + workflow_dispatch: + inputs: + number: + type: number + description: Number of pre-release alpha iteration + required: true + +permissions: + contents: read + packages: write + +env: + OTP_VERSION: '27.3.4.6' + ELIXIR_VERSION: '1.19.4' + +jobs: + push_to_registry: + name: Push Docker image to GitHub Container Registry + runs-on: build + env: + RELEASE_VERSION: 11.0.0 + steps: + - uses: actions/checkout@v5 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image for Ethereum (indexer + API) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-ethereum-private:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=ethereum + + - name: Build and push Docker image for Ethereum (indexer) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-ethereum-private:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=ethereum diff --git a/.github/workflows/pre-release-filecoin.yml b/.github/workflows/pre-release-filecoin.yml new file mode 100644 index 000000000000..26aead2f7698 --- /dev/null +++ b/.github/workflows/pre-release-filecoin.yml @@ -0,0 +1,67 @@ +name: Pre-release for Filecoin + +on: + workflow_dispatch: + inputs: + number: + type: number + description: Number of pre-release alpha iteration + required: true + +permissions: + contents: read + packages: write + +env: + OTP_VERSION: '27.3.4.6' + ELIXIR_VERSION: '1.19.4' + +jobs: + push_to_registry: + name: Push Docker image to GitHub Container Registry + runs-on: build + env: + RELEASE_VERSION: 11.0.0 + steps: + - uses: actions/checkout@v5 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image for Filecoin (indexer + API) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-filecoin-private:latest, ghcr.io/blockscout/blockscout-filecoin-private:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=filecoin + + - name: Build and push Docker image for Filecoin (indexer) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-filecoin-private:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=filecoin diff --git a/.github/workflows/pre-release-fuse.yml b/.github/workflows/pre-release-fuse.yml new file mode 100644 index 000000000000..ada4f05cb972 --- /dev/null +++ b/.github/workflows/pre-release-fuse.yml @@ -0,0 +1,67 @@ +name: Pre-release for Fuse + +on: + workflow_dispatch: + inputs: + number: + type: number + description: Number of pre-release alpha iteration + required: true + +permissions: + contents: read + packages: write + +env: + OTP_VERSION: '27.3.4.6' + ELIXIR_VERSION: '1.19.4' + +jobs: + push_to_registry: + name: Push Docker image to GitHub Container Registry + runs-on: build + env: + RELEASE_VERSION: 11.0.0 + steps: + - uses: actions/checkout@v5 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image for Fuse (indexer + API) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-fuse-private:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + BRIDGED_TOKENS_ENABLED=true + + - name: Build and push Docker image for Fuse (indexer) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-fuse-private:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + BRIDGED_TOKENS_ENABLED=true diff --git a/.github/workflows/pre-release-gnosis.yml b/.github/workflows/pre-release-gnosis.yml new file mode 100644 index 000000000000..a3db046dbe58 --- /dev/null +++ b/.github/workflows/pre-release-gnosis.yml @@ -0,0 +1,69 @@ +name: Pre-release for Gnosis Chain + +on: + workflow_dispatch: + inputs: + number: + type: number + description: Number of pre-release alpha iteration + required: true + +permissions: + contents: read + packages: write + +env: + OTP_VERSION: '27.3.4.6' + ELIXIR_VERSION: '1.19.4' + +jobs: + push_to_registry: + name: Push Docker image to GitHub Container Registry + runs-on: build + env: + RELEASE_VERSION: 11.0.0 + steps: + - uses: actions/checkout@v5 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image for Gnosis Chain (indexer + API) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-xdai-private:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + BRIDGED_TOKENS_ENABLED=true + CHAIN_TYPE=ethereum + + - name: Build and push Docker image for Gnosis Chain (indexer) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-xdai-private:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + BRIDGED_TOKENS_ENABLED=true + CHAIN_TYPE=ethereum diff --git a/.github/workflows/pre-release-optimism.yml b/.github/workflows/pre-release-optimism.yml new file mode 100644 index 000000000000..508da8ecadf3 --- /dev/null +++ b/.github/workflows/pre-release-optimism.yml @@ -0,0 +1,67 @@ +name: Pre-release for Optimism + +on: + workflow_dispatch: + inputs: + number: + type: number + description: Number of pre-release alpha iteration + required: true + +permissions: + contents: read + packages: write + +env: + OTP_VERSION: '27.3.4.6' + ELIXIR_VERSION: '1.19.4' + +jobs: + push_to_registry: + name: Push Docker image to GitHub Container Registry + runs-on: build + env: + RELEASE_VERSION: 11.0.0 + steps: + - uses: actions/checkout@v5 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image for Optimism (indexer + API) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-optimism-private:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=optimism + + - name: Build and push Docker image for Optimism (indexer) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-optimism-private:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=optimism diff --git a/.github/workflows/pre-release-rootstock.yml b/.github/workflows/pre-release-rootstock.yml new file mode 100644 index 000000000000..e82097c7c31d --- /dev/null +++ b/.github/workflows/pre-release-rootstock.yml @@ -0,0 +1,67 @@ +name: Pre-release for Rootstock + +on: + workflow_dispatch: + inputs: + number: + type: number + description: Number of pre-release alpha iteration + required: true + +permissions: + contents: read + packages: write + +env: + OTP_VERSION: '27.3.4.6' + ELIXIR_VERSION: '1.19.4' + +jobs: + push_to_registry: + name: Push Docker image to GitHub Container Registry + runs-on: build + env: + RELEASE_VERSION: 11.0.0 + steps: + - uses: actions/checkout@v5 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image for Rootstock (indexer + API) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-rsk-private:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=rsk + + - name: Build and push Docker image for Rootstock (indexer) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-rsk-private:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=rsk diff --git a/.github/workflows/pre-release-scroll.yml b/.github/workflows/pre-release-scroll.yml new file mode 100644 index 000000000000..efef56bfffd7 --- /dev/null +++ b/.github/workflows/pre-release-scroll.yml @@ -0,0 +1,67 @@ +name: Pre-release for Scroll + +on: + workflow_dispatch: + inputs: + number: + type: number + description: Number of pre-release alpha iteration + required: true + +permissions: + contents: read + packages: write + +env: + OTP_VERSION: '27.3.4.6' + ELIXIR_VERSION: '1.19.4' + +jobs: + push_to_registry: + name: Push Docker image to GitHub Container Registry + runs-on: build + env: + RELEASE_VERSION: 11.0.0 + steps: + - uses: actions/checkout@v5 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image for Scroll (indexer + API) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-scroll-private:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=scroll + + - name: Build and push Docker image for Scroll (indexer) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-scroll-private:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=scroll diff --git a/.github/workflows/pre-release-zilliqa.yml b/.github/workflows/pre-release-zilliqa.yml new file mode 100644 index 000000000000..492a5670ed2a --- /dev/null +++ b/.github/workflows/pre-release-zilliqa.yml @@ -0,0 +1,67 @@ +name: Pre-release for Zilliqa + +on: + workflow_dispatch: + inputs: + number: + type: number + description: Number of pre-release alpha iteration + required: true + +permissions: + contents: read + packages: write + +env: + OTP_VERSION: '27.3.4.6' + ELIXIR_VERSION: '1.19.4' + +jobs: + push_to_registry: + name: Push Docker image to GitHub Container Registry + runs-on: build + env: + RELEASE_VERSION: 11.0.0 + steps: + - uses: actions/checkout@v5 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image (indexer + API) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-zilliqa-private:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=zilliqa + + - name: Build and push Docker image (indexer) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-zilliqa-private:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=zilliqa diff --git a/.github/workflows/pre-release-zksync.yml b/.github/workflows/pre-release-zksync.yml new file mode 100644 index 000000000000..d0493f7d1d33 --- /dev/null +++ b/.github/workflows/pre-release-zksync.yml @@ -0,0 +1,67 @@ +name: Pre-release for ZkSync + +on: + workflow_dispatch: + inputs: + number: + type: number + description: Number of pre-release alpha iteration + required: true + +permissions: + contents: read + packages: write + +env: + OTP_VERSION: '27.3.4.6' + ELIXIR_VERSION: '1.19.4' + +jobs: + push_to_registry: + name: Push Docker image to GitHub Container Registry + runs-on: build + env: + RELEASE_VERSION: 11.0.0 + steps: + - uses: actions/checkout@v5 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image for ZkSync (indexer + API) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-zksync-private:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=zksync + + - name: Build and push Docker image for ZkSync (indexer) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-zksync-private:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=zksync diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml new file mode 100644 index 000000000000..e2203675be1c --- /dev/null +++ b/.github/workflows/pre-release.yml @@ -0,0 +1,79 @@ +name: Pre-release + +on: + workflow_dispatch: + inputs: + number: + type: number + description: Number of pre-release alpha iteration + required: true + +permissions: + contents: read + packages: write + +env: + OTP_VERSION: '27.3.4.6' + ELIXIR_VERSION: '1.19.4' + +jobs: + push_to_registry: + name: Push Docker image to GitHub Container Registry + runs-on: build + env: + RELEASE_VERSION: 11.0.0 + steps: + - uses: actions/checkout@v5 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build & Push Core Docker image (indexer + API) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + cache-from: type=registry,ref=ghcr.io/blockscout/blockscout-private:buildcache + cache-to: type=registry,ref=ghcr.io/blockscout/blockscout-private:buildcache,mode=max + tags: ghcr.io/blockscout/blockscout-private:master, ghcr.io/blockscout/blockscout-private:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DECODE_NOT_A_CONTRACT_CALLS=false + MIXPANEL_URL= + MIXPANEL_TOKEN= + AMPLITUDE_URL= + AMPLITUDE_API_KEY= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + + - name: Build & Push Core Docker image (indexer) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + cache-from: type=registry,ref=ghcr.io/blockscout/blockscout-private:buildcache + cache-to: type=registry,ref=ghcr.io/blockscout/blockscout-private:buildcache,mode=max + tags: ghcr.io/blockscout/blockscout-private:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + DECODE_NOT_A_CONTRACT_CALLS=false + MIXPANEL_URL= + MIXPANEL_TOKEN= + AMPLITUDE_URL= + AMPLITUDE_API_KEY= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} diff --git a/.github/workflows/publish-docker-image-custom-build.yml b/.github/workflows/publish-docker-image-custom-build.yml new file mode 100644 index 000000000000..022bf1a2afd5 --- /dev/null +++ b/.github/workflows/publish-docker-image-custom-build.yml @@ -0,0 +1,58 @@ +name: Publish Custom Base Docker image (master + some commit(s)) + +on: + workflow_dispatch: + push: + branches: + - custom-build +permissions: + contents: read + packages: write + +jobs: + push_to_registry: + name: Push Docker image to GitHub Container Registry + runs-on: build + env: + RELEASE_VERSION: 11.0.0 + steps: + - uses: actions/checkout@v5 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image (indexer + API) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-private:${{ env.RELEASE_VERSION }}-postrelease-custom-build-${{ env.SHORT_SHA }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + + - name: Build and push Docker image (indexer) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-private:${{ env.RELEASE_VERSION }}-postrelease-custom-build-${{ env.SHORT_SHA }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} diff --git a/.github/workflows/publish-docker-image-every-push.yml b/.github/workflows/publish-docker-image-every-push.yml new file mode 100644 index 000000000000..4356773b8afd --- /dev/null +++ b/.github/workflows/publish-docker-image-every-push.yml @@ -0,0 +1,120 @@ +name: Publish Docker image on every push to master branch + +on: + push: + branches: + - master + paths-ignore: + - 'CHANGELOG.md' + - '**/README.md' + - 'docker-compose/*' +env: + OTP_VERSION: '27.3.4.6' + ELIXIR_VERSION: '1.19.4' + RELEASE_VERSION: 11.0.0 + +permissions: + contents: read + packages: write + +jobs: + push_to_registry: + name: Push Docker image to GitHub Container Registry + runs-on: build + steps: + - uses: actions/checkout@v5 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image (indexer + API) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + cache-from: type=registry,ref=ghcr.io/blockscout/blockscout-private:buildcache + cache-to: type=registry,ref=ghcr.io/blockscout/blockscout-private:buildcache,mode=max + tags: ghcr.io/blockscout/blockscout-private:master, ghcr.io/blockscout/blockscout-private:${{ env.RELEASE_VERSION }}.commit.${{ env.SHORT_SHA }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DECODE_NOT_A_CONTRACT_CALLS=false + MIXPANEL_URL= + MIXPANEL_TOKEN= + AMPLITUDE_URL= + AMPLITUDE_API_KEY= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + + - name: Build and push Docker image (indexer) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-private:${{ env.RELEASE_VERSION }}.commit.${{ env.SHORT_SHA }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + DECODE_NOT_A_CONTRACT_CALLS=false + MIXPANEL_URL= + MIXPANEL_TOKEN= + AMPLITUDE_URL= + AMPLITUDE_API_KEY= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + + - name: Build and push Docker image for frontend + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + cache-from: type=registry,ref=ghcr.io/blockscout/blockscout-private:buildcache + tags: ghcr.io/blockscout/blockscout-private:frontend-main + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + SESSION_COOKIE_DOMAIN=k8s-dev.blockscout.com + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + deploy_e2e: + needs: push_to_registry + runs-on: ubuntu-latest + permissions: write-all + steps: + - name: Get Vault credentials + id: retrieve-vault-secrets + uses: hashicorp/vault-action@v2.4.1 + with: + url: https://vault.k8s.blockscout.com + role: ci-dev + path: github-jwt + method: jwt + tlsSkipVerify: false + exportToken: true + secrets: | + ci/data/dev/github token | WORKFLOW_TRIGGER_TOKEN ; + - name: Trigger deploy + uses: convictional/trigger-workflow-and-wait@v1.6.1 + with: + owner: blockscout + repo: deployment-values + github_token: ${{env.WORKFLOW_TRIGGER_TOKEN}} + workflow_file_name: deploy_blockscout.yaml + ref: main + wait_interval: 30 + client_payload: '{ "instance": "dev", "globalEnv": "e2e"}' diff --git a/.github/workflows/publish-docker-image-for-arbitrum.yml b/.github/workflows/publish-docker-image-for-arbitrum.yml new file mode 100644 index 000000000000..a1f65792264d --- /dev/null +++ b/.github/workflows/publish-docker-image-for-arbitrum.yml @@ -0,0 +1,61 @@ +name: Arbitrum Publish Docker image + +on: + workflow_dispatch: + push: + branches: + - production-arbitrum +permissions: + contents: read + packages: write + +jobs: + push_to_registry: + name: Push Docker image to GitHub Container Registry + runs-on: build + env: + RELEASE_VERSION: 11.0.0 + DOCKER_CHAIN_NAME: arbitrum + steps: + - uses: actions/checkout@v5 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image (indexer + API) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}-private:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=arbitrum + + - name: Build and push Docker image (indexer) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}-private:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=arbitrum diff --git a/.github/workflows/publish-docker-image-for-celo.yml b/.github/workflows/publish-docker-image-for-celo.yml new file mode 100644 index 000000000000..21b1beaf6ce4 --- /dev/null +++ b/.github/workflows/publish-docker-image-for-celo.yml @@ -0,0 +1,64 @@ +name: Celo Publish Docker image + +on: + workflow_dispatch: + push: + branches: + - production-celo +permissions: + contents: read + packages: write + +jobs: + push_to_registry: + name: Push Docker image to GitHub Container Registry + runs-on: build + env: + RELEASE_VERSION: 11.0.0 + DOCKER_CHAIN_NAME: optimism-celo + API_GRAPHQL_MAX_COMPLEXITY: 10400 + steps: + - uses: actions/checkout@v5 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image for CELO (indexer + API) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}-private:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + API_GRAPHQL_MAX_COMPLEXITY=${{ env.API_GRAPHQL_MAX_COMPLEXITY }} + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=${{ env.DOCKER_CHAIN_NAME }} + + - name: Build and push Docker image for CELO (indexer) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}-private:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + API_GRAPHQL_MAX_COMPLEXITY=${{ env.API_GRAPHQL_MAX_COMPLEXITY }} + DISABLE_API=true + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=${{ env.DOCKER_CHAIN_NAME }} diff --git a/.github/workflows/publish-docker-image-for-core.yml b/.github/workflows/publish-docker-image-for-core.yml new file mode 100644 index 000000000000..5167c7c4b15a --- /dev/null +++ b/.github/workflows/publish-docker-image-for-core.yml @@ -0,0 +1,43 @@ +name: POA Core Publish Docker image + +on: + workflow_dispatch: + push: + branches: + - production-core +permissions: + contents: read + packages: write + +jobs: + push_to_registry: + name: Push Docker image to GitHub Container Registry + runs-on: build + env: + RELEASE_VERSION: 11.0.0 + DOCKER_CHAIN_NAME: poa + steps: + - uses: actions/checkout@v5 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}-private:latest, ghcr.io/blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}-private:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} diff --git a/.github/workflows/publish-docker-image-for-eth-sepolia.yml b/.github/workflows/publish-docker-image-for-eth-sepolia.yml new file mode 100644 index 000000000000..007d2fe2731b --- /dev/null +++ b/.github/workflows/publish-docker-image-for-eth-sepolia.yml @@ -0,0 +1,61 @@ +name: ETH Sepolia Publish Docker image + +on: + workflow_dispatch: + push: + branches: + - production-eth-sepolia +permissions: + contents: read + packages: write + +jobs: + push_to_registry: + name: Push Docker image to GitHub Container Registry + runs-on: build + env: + RELEASE_VERSION: 11.0.0 + DOCKER_CHAIN_NAME: eth-sepolia + steps: + - uses: actions/checkout@v5 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image (indexer + API) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}-private:latest, ghcr.io/blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}-private:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=ethereum + + - name: Build and push Docker image (indexer) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}-private:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=ethereum diff --git a/.github/workflows/publish-docker-image-for-eth.yml b/.github/workflows/publish-docker-image-for-eth.yml new file mode 100644 index 000000000000..7bccb1bb4b6d --- /dev/null +++ b/.github/workflows/publish-docker-image-for-eth.yml @@ -0,0 +1,61 @@ +name: ETH Publish Docker image + +on: + workflow_dispatch: + push: + branches: + - production-eth +permissions: + contents: read + packages: write + +jobs: + push_to_registry: + name: Push Docker image to GitHub Container Registry + runs-on: build + env: + RELEASE_VERSION: 11.0.0 + DOCKER_CHAIN_NAME: ethereum + steps: + - uses: actions/checkout@v5 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image (indexer + API) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}-private:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=ethereum + + - name: Build and push Docker image (indexer) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}-private:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=ethereum diff --git a/.github/workflows/publish-docker-image-for-filecoin.yml b/.github/workflows/publish-docker-image-for-filecoin.yml new file mode 100644 index 000000000000..14c9983131bd --- /dev/null +++ b/.github/workflows/publish-docker-image-for-filecoin.yml @@ -0,0 +1,60 @@ +name: Publish Docker image for specific chain branches + +on: + push: + branches: + - production-filecoin +permissions: + contents: read + packages: write + +jobs: + push_to_registry: + name: Push Docker image to GitHub Container Registry + runs-on: build + env: + RELEASE_VERSION: 11.0.0 + DOCKER_CHAIN_NAME: filecoin + steps: + - uses: actions/checkout@v5 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image for Filecoin (indexer + API) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}-private:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=${{ env.DOCKER_CHAIN_NAME }} + + - name: Build and push Docker image for Filecoin (indexer) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}-private:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=${{ env.DOCKER_CHAIN_NAME }} diff --git a/.github/workflows/publish-docker-image-for-fuse.yml b/.github/workflows/publish-docker-image-for-fuse.yml new file mode 100644 index 000000000000..d155f17f2bee --- /dev/null +++ b/.github/workflows/publish-docker-image-for-fuse.yml @@ -0,0 +1,44 @@ +name: Fuse Publish Docker image + +on: + workflow_dispatch: + push: + branches: + - production-fuse +permissions: + contents: read + packages: write + +jobs: + push_to_registry: + name: Push Docker image to GitHub Container Registry + runs-on: build + env: + RELEASE_VERSION: 11.0.0 + DOCKER_CHAIN_NAME: fuse + steps: + - uses: actions/checkout@v5 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}-private:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + BRIDGED_TOKENS_ENABLED=true + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} diff --git a/.github/workflows/publish-docker-image-for-gnosis-chain.yml b/.github/workflows/publish-docker-image-for-gnosis-chain.yml new file mode 100644 index 000000000000..fafbb6a0c838 --- /dev/null +++ b/.github/workflows/publish-docker-image-for-gnosis-chain.yml @@ -0,0 +1,63 @@ +name: Gnosis Chain Publish Docker image + +on: + workflow_dispatch: + push: + branches: + - production-xdai +permissions: + contents: read + packages: write + +jobs: + push_to_registry: + name: Push Docker image to GitHub Container Registry + runs-on: build + env: + RELEASE_VERSION: 11.0.0 + DOCKER_CHAIN_NAME: xdai + steps: + - uses: actions/checkout@v5 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image (indexer + API) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}-private:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + BRIDGED_TOKENS_ENABLED=true + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=ethereum + + - name: Build and push Docker image (indexer) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}-private:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + BRIDGED_TOKENS_ENABLED=true + DISABLE_API=true + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=ethereum diff --git a/.github/workflows/publish-docker-image-for-l2-staging.yml b/.github/workflows/publish-docker-image-for-l2-staging.yml new file mode 100644 index 000000000000..1d72bca2f60d --- /dev/null +++ b/.github/workflows/publish-docker-image-for-l2-staging.yml @@ -0,0 +1,43 @@ +name: L2 staging Publish Docker image + +on: + workflow_dispatch: + push: + branches: + - staging-l2 +permissions: + contents: read + packages: write + +jobs: + push_to_registry: + name: Push Docker image to GitHub Container Registry + runs-on: build + env: + RELEASE_VERSION: 11.0.0 + DOCKER_CHAIN_NAME: optimism-l2-advanced + steps: + - uses: actions/checkout@v5 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}-private:latest, ghcr.io/blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}-private:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} diff --git a/.github/workflows/publish-docker-image-for-lukso.yml b/.github/workflows/publish-docker-image-for-lukso.yml new file mode 100644 index 000000000000..d4dcc080490a --- /dev/null +++ b/.github/workflows/publish-docker-image-for-lukso.yml @@ -0,0 +1,43 @@ +name: LUKSO Publish Docker image + +on: + workflow_dispatch: + push: + branches: + - production-lukso +permissions: + contents: read + packages: write + +jobs: + push_to_registry: + name: Push Docker image to GitHub Container Registry + runs-on: build + env: + RELEASE_VERSION: 11.0.0 + DOCKER_CHAIN_NAME: lukso + steps: + - uses: actions/checkout@v5 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}-private:latest, ghcr.io/blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}-private:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} diff --git a/.github/workflows/publish-docker-image-for-optimism-exeperimental.yml b/.github/workflows/publish-docker-image-for-optimism-exeperimental.yml new file mode 100644 index 000000000000..da282026865c --- /dev/null +++ b/.github/workflows/publish-docker-image-for-optimism-exeperimental.yml @@ -0,0 +1,61 @@ +name: Optimism Publish experimental Docker image + +on: + workflow_dispatch: + push: + branches: + - production-optimism-experimental +permissions: + contents: read + packages: write + +jobs: + push_to_registry: + name: Push Docker image to GitHub Container Registry + runs-on: build + env: + RELEASE_VERSION: 11.0.0 + DOCKER_CHAIN_NAME: optimism + steps: + - uses: actions/checkout@v5 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image (indexer + API) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}-private:${{ env.RELEASE_VERSION }}-postrelease-experimental-${{ env.SHORT_SHA }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=optimism + + - name: Build and push Docker image (indexer) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}-private:${{ env.RELEASE_VERSION }}-postrelease-experimental-${{ env.SHORT_SHA }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=optimism diff --git a/.github/workflows/publish-docker-image-for-optimism-worldchain.yml b/.github/workflows/publish-docker-image-for-optimism-worldchain.yml new file mode 100644 index 000000000000..150d04211c27 --- /dev/null +++ b/.github/workflows/publish-docker-image-for-optimism-worldchain.yml @@ -0,0 +1,61 @@ +name: Optimism Worldchain Publish Docker image + +on: + workflow_dispatch: + push: + branches: + - production-optimism-worldchain +permissions: + contents: read + packages: write + +jobs: + push_to_registry: + name: Push Docker image to GitHub Container Registry + runs-on: build + env: + RELEASE_VERSION: 11.0.0 + DOCKER_CHAIN_NAME: optimism + steps: + - uses: actions/checkout@v5 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image (indexer + API) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}-private:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }}-worldchain + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=optimism + + - name: Build and push Docker image (indexer) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}-private:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }}-worldchain-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=optimism diff --git a/.github/workflows/publish-docker-image-for-optimism.yml b/.github/workflows/publish-docker-image-for-optimism.yml new file mode 100644 index 000000000000..be2f6c0ee1de --- /dev/null +++ b/.github/workflows/publish-docker-image-for-optimism.yml @@ -0,0 +1,61 @@ +name: Optimism Publish Docker image + +on: + workflow_dispatch: + push: + branches: + - production-optimism +permissions: + contents: read + packages: write + +jobs: + push_to_registry: + name: Push Docker image to GitHub Container Registry + runs-on: build + env: + RELEASE_VERSION: 11.0.0 + DOCKER_CHAIN_NAME: optimism + steps: + - uses: actions/checkout@v5 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image (indexer + API) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}-private:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=optimism + + - name: Build and push Docker image (indexer) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}-private:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=optimism diff --git a/.github/workflows/publish-docker-image-for-rootstock.yml b/.github/workflows/publish-docker-image-for-rootstock.yml new file mode 100644 index 000000000000..73ea94b4e579 --- /dev/null +++ b/.github/workflows/publish-docker-image-for-rootstock.yml @@ -0,0 +1,44 @@ +name: Rootstock Publish Docker image + +on: + workflow_dispatch: + push: + branches: + - production-rsk +permissions: + contents: read + packages: write + +jobs: + push_to_registry: + name: Push Docker image to GitHub Container Registry + runs-on: build + env: + RELEASE_VERSION: 11.0.0 + DOCKER_CHAIN_NAME: rsk + steps: + - uses: actions/checkout@v5 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}-private:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=rsk diff --git a/.github/workflows/publish-docker-image-for-scroll.yml b/.github/workflows/publish-docker-image-for-scroll.yml new file mode 100644 index 000000000000..8764346ba00c --- /dev/null +++ b/.github/workflows/publish-docker-image-for-scroll.yml @@ -0,0 +1,61 @@ +name: Scroll Publish Docker image + +on: + workflow_dispatch: + push: + branches: + - production-scroll +permissions: + contents: read + packages: write + +jobs: + push_to_registry: + name: Push Docker image to GitHub Container Registry + runs-on: build + env: + RELEASE_VERSION: 11.0.0 + DOCKER_CHAIN_NAME: scroll + steps: + - uses: actions/checkout@v5 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image (indexer + API) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}-private:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=scroll + + - name: Build and push Docker image (indexer) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}-private:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=scroll diff --git a/.github/workflows/publish-docker-image-for-zetachain.yml b/.github/workflows/publish-docker-image-for-zetachain.yml new file mode 100644 index 000000000000..1b1c9e852ac6 --- /dev/null +++ b/.github/workflows/publish-docker-image-for-zetachain.yml @@ -0,0 +1,44 @@ +name: Zetachain publish Docker image + +on: + workflow_dispatch: + push: + branches: + - production-zetachain +permissions: + contents: read + packages: write + +jobs: + push_to_registry: + name: Push Docker image to GitHub Container Registry + runs-on: build + env: + RELEASE_VERSION: 11.0.0 + DOCKER_CHAIN_NAME: zetachain + steps: + - uses: actions/checkout@v5 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}-private:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=zetachain diff --git a/.github/workflows/publish-docker-image-for-zilliqa.yml b/.github/workflows/publish-docker-image-for-zilliqa.yml new file mode 100644 index 000000000000..8432a91354f7 --- /dev/null +++ b/.github/workflows/publish-docker-image-for-zilliqa.yml @@ -0,0 +1,61 @@ +name: Zilliqa publish Docker image + +on: + workflow_dispatch: + push: + branches: + - production-zilliqa +permissions: + contents: read + packages: write + +jobs: + push_to_registry: + name: Push Docker image to GitHub Container Registry + runs-on: build + env: + RELEASE_VERSION: 11.0.0 + DOCKER_CHAIN_NAME: zilliqa + steps: + - uses: actions/checkout@v5 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image (indexer + API) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}-private:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=${{ env.DOCKER_CHAIN_NAME }} + + - name: Build and push Docker image (indexer) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}-private:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=${{ env.DOCKER_CHAIN_NAME }} diff --git a/.github/workflows/publish-docker-image-for-zksync.yml b/.github/workflows/publish-docker-image-for-zksync.yml new file mode 100644 index 000000000000..ebfa22f1409a --- /dev/null +++ b/.github/workflows/publish-docker-image-for-zksync.yml @@ -0,0 +1,60 @@ +name: Zksync publish Docker image + +on: + push: + branches: + - production-zksync +permissions: + contents: read + packages: write + +jobs: + push_to_registry: + name: Push Docker image to GitHub Container Registry + runs-on: build + env: + RELEASE_VERSION: 11.0.0 + DOCKER_CHAIN_NAME: zksync + steps: + - uses: actions/checkout@v5 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image (indexer + API) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}-private:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=zksync + + - name: Build and push Docker image (indexer) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}-private:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=zksync diff --git a/.github/workflows/publish-docker-image-old-ui.yml b/.github/workflows/publish-docker-image-old-ui.yml new file mode 100644 index 000000000000..70c9fcb095f6 --- /dev/null +++ b/.github/workflows/publish-docker-image-old-ui.yml @@ -0,0 +1,51 @@ +name: Publish Docker image with an old UI + +on: + workflow_dispatch: + +env: + OTP_VERSION: '27.3.4.6' + ELIXIR_VERSION: '1.19.4' + +permissions: + contents: read + packages: write + +jobs: + push_to_registry: + name: Push Docker image to GitHub Container Registry + runs-on: build + env: + RELEASE_VERSION: 11.0.0 + steps: + - uses: actions/checkout@v5 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build & Push Docker image with an old UI (indexer + API) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/oldUI.Dockerfile + push: true + cache-from: type=registry,ref=ghcr.io/blockscout/blockscout:buildcache + cache-to: type=registry,ref=ghcr.io/blockscout/blockscout:buildcache,mode=max + tags: ghcr.io/blockscout/blockscout:${{ env.RELEASE_VERSION }}-with-old-ui-postrelease-${{ env.SHORT_SHA }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DECODE_NOT_A_CONTRACT_CALLS=false + MIXPANEL_URL= + MIXPANEL_TOKEN= + AMPLITUDE_URL= + AMPLITUDE_API_KEY= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} diff --git a/.github/workflows/publish-docker-image-staging-on-demand.yml b/.github/workflows/publish-docker-image-staging-on-demand.yml new file mode 100644 index 000000000000..46785176ae6f --- /dev/null +++ b/.github/workflows/publish-docker-image-staging-on-demand.yml @@ -0,0 +1,56 @@ +name: Publish Docker image to staging on demand + +on: + workflow_dispatch: + push: + branches: + - staging + paths-ignore: + - 'CHANGELOG.md' + - '**/README.md' + - 'docker-compose/*' +env: + OTP_VERSION: '27.3.4.6' + ELIXIR_VERSION: '1.19.4' + RELEASE_VERSION: 11.0.0 + +permissions: + contents: read + packages: write + +jobs: + push_to_registry: + name: Push Docker image to GitHub Container Registry + runs-on: build + steps: + - uses: actions/checkout@v5 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + cache-from: type=registry,ref=ghcr.io/blockscout/blockscout:buildcache + cache-to: type=registry,ref=ghcr.io/blockscout/blockscout:buildcache,mode=max + tags: ghcr.io/blockscout/blockscout-staging:latest, ghcr.io/blockscout/blockscout-staging:${{ env.RELEASE_VERSION }}.commit.${{ env.SHORT_SHA }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DECODE_NOT_A_CONTRACT_CALLS=false + MIXPANEL_URL= + MIXPANEL_TOKEN= + AMPLITUDE_URL= + AMPLITUDE_API_KEY= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} diff --git a/.github/workflows/publish-regular-docker-image-on-demand.yml b/.github/workflows/publish-regular-docker-image-on-demand.yml index 04d1272d06f6..34d03ad9cb39 100644 --- a/.github/workflows/publish-regular-docker-image-on-demand.yml +++ b/.github/workflows/publish-regular-docker-image-on-demand.yml @@ -2,18 +2,10 @@ name: Publish regular Docker image on demand on: workflow_dispatch: - inputs: - release_version: - description: 'Release version (e.g. 10.2.1). If empty and triggered by tag, derived from tag name.' - required: false - type: string - push: - tags: - - 'v*' - env: OTP_VERSION: '27.3.4.6' ELIXIR_VERSION: '1.19.4' + RELEASE_VERSION: 11.0.0 permissions: contents: read @@ -22,34 +14,17 @@ permissions: jobs: push_to_registry: name: Push Docker image to GitHub Container Registry - runs-on: ubuntu-latest + runs-on: build steps: - uses: actions/checkout@v5 - - - name: Determine release version - id: version - env: - INPUT_VERSION: ${{ inputs.release_version }} - REF_NAME: ${{ github.ref_name }} - REF_TYPE: ${{ github.ref_type }} - run: | - if [ -n "$INPUT_VERSION" ]; then - VERSION="$INPUT_VERSION" - elif [ "$REF_TYPE" = "tag" ]; then - VERSION="${REF_NAME#v}" - else - echo "::error::No release_version input and not running on a tag" - exit 1 - fi - echo "release_version=$VERSION" >> "$GITHUB_OUTPUT" - echo "Building RELEASE_VERSION=$VERSION" - - name: Setup repo uses: ./.github/actions/setup-repo id: setup with: github-token: ${{ secrets.GITHUB_TOKEN }} - docker-remote-multi-platform: false + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} - name: Build and push Docker image (indexer + API) uses: docker/build-push-action@v6 @@ -57,19 +32,21 @@ jobs: context: . file: ./docker/Dockerfile push: true - cache-from: type=registry,ref=ghcr.io/dos/doscan:buildcache - cache-to: type=registry,ref=ghcr.io/dos/doscan:buildcache,mode=max - tags: ghcr.io/dos/doscan:${{ steps.version.outputs.release_version }}.commit.${{ env.SHORT_SHA }} + cache-from: type=registry,ref=ghcr.io/blockscout/blockscout:buildcache + cache-to: type=registry,ref=ghcr.io/blockscout/blockscout:buildcache,mode=max + tags: ghcr.io/blockscout/blockscout:${{ env.RELEASE_VERSION }}.commit.${{ env.SHORT_SHA }} labels: ${{ steps.setup.outputs.docker-labels }} - platforms: linux/amd64 + platforms: | + linux/amd64 + linux/arm64/v8 build-args: | DECODE_NOT_A_CONTRACT_CALLS=false MIXPANEL_URL= MIXPANEL_TOKEN= AMPLITUDE_URL= AMPLITUDE_API_KEY= - BLOCKSCOUT_VERSION=v${{ steps.version.outputs.release_version }}.+commit.${{ env.SHORT_SHA }} - RELEASE_VERSION=${{ steps.version.outputs.release_version }} + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} - name: Build and push Docker image (indexer) uses: docker/build-push-action@v6 @@ -77,9 +54,11 @@ jobs: context: . file: ./docker/Dockerfile push: true - tags: ghcr.io/dos/doscan:${{ steps.version.outputs.release_version }}.commit.${{ env.SHORT_SHA }}-indexer + tags: ghcr.io/blockscout/blockscout:${{ env.RELEASE_VERSION }}.commit.${{ env.SHORT_SHA }}-indexer labels: ${{ steps.setup.outputs.docker-labels }} - platforms: linux/amd64 + platforms: | + linux/amd64 + linux/arm64/v8 build-args: | DISABLE_API=true DECODE_NOT_A_CONTRACT_CALLS=false @@ -87,5 +66,5 @@ jobs: MIXPANEL_TOKEN= AMPLITUDE_URL= AMPLITUDE_API_KEY= - BLOCKSCOUT_VERSION=v${{ steps.version.outputs.release_version }}.+commit.${{ env.SHORT_SHA }} - RELEASE_VERSION=${{ steps.version.outputs.release_version }} + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} diff --git a/.github/workflows/release-arbitrum.yml b/.github/workflows/release-arbitrum.yml new file mode 100644 index 000000000000..c022febaf4d6 --- /dev/null +++ b/.github/workflows/release-arbitrum.yml @@ -0,0 +1,64 @@ +name: Release for Arbitrum + +on: + workflow_dispatch: + release: + types: [published] + +permissions: + contents: read + packages: write + +env: + OTP_VERSION: '27.3.4.6' + ELIXIR_VERSION: '1.19.4' + +jobs: + push_to_registry: + name: Push Docker image to GitHub Container Registry + runs-on: build + env: + RELEASE_VERSION: 11.0.0 + steps: + - uses: actions/checkout@v5 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image for Arbitrum (indexer + API) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-arbitrum-private:latest, ghcr.io/blockscout/blockscout-arbitrum-private:${{ env.RELEASE_VERSION }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=arbitrum + + - name: Build and push Docker image for Arbitrum (indexer) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-arbitrum-private:${{ env.RELEASE_VERSION }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=arbitrum diff --git a/.github/workflows/release-celo.yml b/.github/workflows/release-celo.yml new file mode 100644 index 000000000000..e3fa314ba3c8 --- /dev/null +++ b/.github/workflows/release-celo.yml @@ -0,0 +1,67 @@ +name: Release for Celo + +on: + workflow_dispatch: + release: + types: [published] + +permissions: + contents: read + packages: write + +env: + OTP_VERSION: '27.3.4.6' + ELIXIR_VERSION: '1.19.4' + +jobs: + push_to_registry: + name: Push Docker image to GitHub Container Registry + runs-on: build + env: + RELEASE_VERSION: 11.0.0 + API_GRAPHQL_MAX_COMPLEXITY: 10400 + steps: + - uses: actions/checkout@v5 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image for CELO (indexer + API) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-celo-private:latest, ghcr.io/blockscout/blockscout-celo-private:${{ env.RELEASE_VERSION }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + API_GRAPHQL_MAX_COMPLEXITY=${{ env.API_GRAPHQL_MAX_COMPLEXITY }} + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=optimism-celo + + - name: Build and push Docker image for CELO (indexer) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-celo-private:${{ env.RELEASE_VERSION }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + API_GRAPHQL_MAX_COMPLEXITY=${{ env.API_GRAPHQL_MAX_COMPLEXITY }} + DISABLE_API=true + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=optimism-celo diff --git a/.github/workflows/release-default.yml b/.github/workflows/release-default.yml new file mode 100644 index 000000000000..2b8109bb4745 --- /dev/null +++ b/.github/workflows/release-default.yml @@ -0,0 +1,153 @@ +name: Release + +on: + workflow_dispatch: + release: + types: [published] + +permissions: + contents: read + packages: write + +env: + OTP_VERSION: '27.3.4.6' + ELIXIR_VERSION: '1.19.4' + +jobs: + push_to_registry: + name: Push Docker image to GitHub Container Registry + runs-on: build + env: + RELEASE_VERSION: 11.0.0 + steps: + - uses: actions/checkout@v5 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build & Push Core Docker image (indexer + API) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + cache-from: type=registry,ref=ghcr.io/blockscout/blockscout-private:buildcache + cache-to: type=registry,ref=ghcr.io/blockscout/blockscout-private:buildcache,mode=max + tags: ghcr.io/blockscout/blockscout-private:latest, ghcr.io/blockscout/blockscout-private:${{ env.RELEASE_VERSION }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DECODE_NOT_A_CONTRACT_CALLS=false + MIXPANEL_URL= + MIXPANEL_TOKEN= + AMPLITUDE_URL= + AMPLITUDE_API_KEY= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + + - name: Build & Push Core Docker image (indexer) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + cache-from: type=registry,ref=ghcr.io/blockscout/blockscout-private:buildcache + cache-to: type=registry,ref=ghcr.io/blockscout/blockscout-private:buildcache,mode=max + tags: ghcr.io/blockscout/blockscout-private:${{ env.RELEASE_VERSION }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + DECODE_NOT_A_CONTRACT_CALLS=false + MIXPANEL_URL= + MIXPANEL_TOKEN= + AMPLITUDE_URL= + AMPLITUDE_API_KEY= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + + + - name: Build & Push Docker image with an old UI (indexer + API) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/oldUI.Dockerfile + push: true + cache-from: type=registry,ref=ghcr.io/blockscout/blockscout-private:buildcache + cache-to: type=registry,ref=ghcr.io/blockscout/blockscout-private:buildcache,mode=max + tags: ghcr.io/blockscout/blockscout-private:${{ env.RELEASE_VERSION }}-with-old-ui + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DECODE_NOT_A_CONTRACT_CALLS=false + MIXPANEL_URL= + MIXPANEL_TOKEN= + AMPLITUDE_URL= + AMPLITUDE_API_KEY= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + + # - name: Send release announcement to Slack workflow + # id: slack + # uses: slackapi/slack-github-action@v1.24.0 + # with: + # payload: | + # { + # "release-version": "${{ env.RELEASE_VERSION }}", + # "release-link": "https://github.com/blockscout/blockscout/releases/tag/v${{ env.RELEASE_VERSION }}" + # } + # env: + # SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + + # merge-master-after-release: + # name: Merge 'master' to specific branch after release + # runs-on: ubuntu-latest + # env: + # BRANCHES: | + # production-core + # production-sokol + # production-eth-experimental + # production-eth-goerli + # production-lukso + # production-xdai + # production-polygon-supernets + # production-rsk + # production-immutable + # steps: + # - uses: actions/checkout@v5 + # - name: Set Git config + # run: | + # git config --local user.email "actions@github.com" + # git config --local user.name "Github Actions" + # - name: Merge master back after release + # run: | + # git fetch --unshallow + # touch errors.txt + # for branch in $BRANCHES; + # do + # git reset --merge + # git checkout master + # git fetch origin + # echo $branch + # git ls-remote --exit-code --heads origin $branch || { echo $branch >> errors.txt; continue; } + # echo "Merge 'master' to $branch" + # git checkout $branch + # git pull || { echo $branch >> errors.txt; continue; } + # git merge --no-ff master -m "Auto-merge master back to $branch" || { echo $branch >> errors.txt; continue; } + # git push || { echo $branch >> errors.txt; continue; } + # git checkout master; + # done + # [ -s errors.txt ] && echo "There are problems with merging 'master' to branches:" || echo "Errors file is empty" + # cat errors.txt + # [ ! -s errors.txt ] diff --git a/.github/workflows/release-eth.yml b/.github/workflows/release-eth.yml new file mode 100644 index 000000000000..3aa5ff3f410f --- /dev/null +++ b/.github/workflows/release-eth.yml @@ -0,0 +1,64 @@ +name: Release for Ethereum + +on: + workflow_dispatch: + release: + types: [published] + +permissions: + contents: read + packages: write + +env: + OTP_VERSION: '27.3.4.6' + ELIXIR_VERSION: '1.19.4' + +jobs: + push_to_registry: + name: Push Docker image to GitHub Container Registry + runs-on: build + env: + RELEASE_VERSION: 11.0.0 + steps: + - uses: actions/checkout@v5 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image for Ethereum (indexer + API) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-ethereum-private:latest, ghcr.io/blockscout/blockscout-ethereum-private:${{ env.RELEASE_VERSION }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=ethereum + + - name: Build and push Docker image for Ethereum (indexer) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-ethereum-private:${{ env.RELEASE_VERSION }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=ethereum diff --git a/.github/workflows/release-filecoin.yml b/.github/workflows/release-filecoin.yml new file mode 100644 index 000000000000..40fd44ddfb36 --- /dev/null +++ b/.github/workflows/release-filecoin.yml @@ -0,0 +1,64 @@ +name: Release for Filecoin + +on: + workflow_dispatch: + release: + types: [published] + +permissions: + contents: read + packages: write + +env: + OTP_VERSION: '27.3.4.6' + ELIXIR_VERSION: '1.19.4' + +jobs: + push_to_registry: + name: Push Docker image to GitHub Container Registry + runs-on: build + env: + RELEASE_VERSION: 11.0.0 + steps: + - uses: actions/checkout@v5 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image for Filecoin (indexer + API) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-filecoin-private:latest, ghcr.io/blockscout/blockscout-filecoin-private:${{ env.RELEASE_VERSION }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=filecoin + + - name: Build and push Docker image for Filecoin (indexer) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-filecoin-private:${{ env.RELEASE_VERSION }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=filecoin diff --git a/.github/workflows/release-fuse.yml b/.github/workflows/release-fuse.yml new file mode 100644 index 000000000000..7acd11e5f94e --- /dev/null +++ b/.github/workflows/release-fuse.yml @@ -0,0 +1,64 @@ +name: Release for Fuse + +on: + workflow_dispatch: + release: + types: [published] + +permissions: + contents: read + packages: write + +env: + OTP_VERSION: '27.3.4.6' + ELIXIR_VERSION: '1.19.4' + +jobs: + push_to_registry: + name: Push Docker image to GitHub Container Registry + runs-on: build + env: + RELEASE_VERSION: 11.0.0 + steps: + - uses: actions/checkout@v5 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image for Fuse (indexer + API) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-fuse-private:latest, ghcr.io/blockscout/blockscout-fuse-private:${{ env.RELEASE_VERSION }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + BRIDGED_TOKENS_ENABLED=true + + - name: Build and push Docker image for Fuse (indexer) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-fuse-private:${{ env.RELEASE_VERSION }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + BRIDGED_TOKENS_ENABLED=true diff --git a/.github/workflows/release-gnosis.yml b/.github/workflows/release-gnosis.yml new file mode 100644 index 000000000000..1d30738f0b4b --- /dev/null +++ b/.github/workflows/release-gnosis.yml @@ -0,0 +1,66 @@ +name: Release for Gnosis Chain + +on: + workflow_dispatch: + release: + types: [published] + +permissions: + contents: read + packages: write + +env: + OTP_VERSION: '27.3.4.6' + ELIXIR_VERSION: '1.19.4' + +jobs: + push_to_registry: + name: Push Docker image to GitHub Container Registry + runs-on: build + env: + RELEASE_VERSION: 11.0.0 + steps: + - uses: actions/checkout@v5 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image for Gnosis chain (indexer + API) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-xdai-private:latest, ghcr.io/blockscout/blockscout-xdai-private:${{ env.RELEASE_VERSION }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + BRIDGED_TOKENS_ENABLED=true + CHAIN_TYPE=ethereum + + - name: Build and push Docker image for Gnosis chain (indexer) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-xdai-private:${{ env.RELEASE_VERSION }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + BRIDGED_TOKENS_ENABLED=true + CHAIN_TYPE=ethereum diff --git a/.github/workflows/release-optimism.yml b/.github/workflows/release-optimism.yml new file mode 100644 index 000000000000..50b214b467cb --- /dev/null +++ b/.github/workflows/release-optimism.yml @@ -0,0 +1,64 @@ +name: Release for Optimism + +on: + workflow_dispatch: + release: + types: [published] + +permissions: + contents: read + packages: write + +env: + OTP_VERSION: '27.3.4.6' + ELIXIR_VERSION: '1.19.4' + +jobs: + push_to_registry: + name: Push Docker image to GitHub Container Registry + runs-on: build + env: + RELEASE_VERSION: 11.0.0 + steps: + - uses: actions/checkout@v5 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image for Optimism (indexer + API) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-optimism-private:latest, ghcr.io/blockscout/blockscout-optimism-private:${{ env.RELEASE_VERSION }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=optimism + + - name: Build and push Docker image for Optimism (indexer) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-optimism-private:${{ env.RELEASE_VERSION }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=optimism diff --git a/.github/workflows/release-rootstock.yml b/.github/workflows/release-rootstock.yml new file mode 100644 index 000000000000..71cddcb77ea1 --- /dev/null +++ b/.github/workflows/release-rootstock.yml @@ -0,0 +1,64 @@ +name: Release for Rootstock + +on: + workflow_dispatch: + release: + types: [published] + +permissions: + contents: read + packages: write + +env: + OTP_VERSION: '27.3.4.6' + ELIXIR_VERSION: '1.19.4' + +jobs: + push_to_registry: + name: Push Docker image to GitHub Container Registry + runs-on: build + env: + RELEASE_VERSION: 11.0.0 + steps: + - uses: actions/checkout@v5 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image for Rootstock (indexer + API) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-rsk-private:latest, ghcr.io/blockscout/blockscout-rsk-private:${{ env.RELEASE_VERSION }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=rsk + + - name: Build and push Docker image for Rootstock (indexer) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-rsk-private:${{ env.RELEASE_VERSION }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=rsk diff --git a/.github/workflows/release-scroll.yml b/.github/workflows/release-scroll.yml new file mode 100644 index 000000000000..72fbad4ac149 --- /dev/null +++ b/.github/workflows/release-scroll.yml @@ -0,0 +1,64 @@ +name: Release for Scroll + +on: + workflow_dispatch: + release: + types: [published] + +permissions: + contents: read + packages: write + +env: + OTP_VERSION: '27.3.4.6' + ELIXIR_VERSION: '1.19.4' + +jobs: + push_to_registry: + name: Push Docker image to GitHub Container Registry + runs-on: build + env: + RELEASE_VERSION: 11.0.0 + steps: + - uses: actions/checkout@v5 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image for Scroll (indexer + API) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-scroll-private:latest, ghcr.io/blockscout/blockscout-scroll-private:${{ env.RELEASE_VERSION }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=scroll + + - name: Build and push Docker image for Scroll (indexer) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-scroll-private:${{ env.RELEASE_VERSION }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=scroll diff --git a/.github/workflows/release-zetachain.yml b/.github/workflows/release-zetachain.yml new file mode 100644 index 000000000000..03f468b10a4f --- /dev/null +++ b/.github/workflows/release-zetachain.yml @@ -0,0 +1,64 @@ +name: Release for Zetachain + +on: + workflow_dispatch: + release: + types: [published] + +permissions: + contents: read + packages: write + +env: + OTP_VERSION: '27.3.4.6' + ELIXIR_VERSION: '1.19.4' + +jobs: + push_to_registry: + name: Push Docker image to GitHub Container Registry + runs-on: build + env: + RELEASE_VERSION: 11.0.0 + steps: + - uses: actions/checkout@v5 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image for Zetachain (indexer + API) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-zetachain-private:latest, ghcr.io/blockscout/blockscout-zetachain-private:${{ env.RELEASE_VERSION }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=zetachain + + - name: Build and push Docker image for Zetachain (indexer) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-zetachain-private:${{ env.RELEASE_VERSION }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=zetachain diff --git a/.github/workflows/release-zilliqa.yml b/.github/workflows/release-zilliqa.yml new file mode 100644 index 000000000000..02aed6f96e64 --- /dev/null +++ b/.github/workflows/release-zilliqa.yml @@ -0,0 +1,64 @@ +name: Release for Zilliqa + +on: + workflow_dispatch: + release: + types: [published] + +permissions: + contents: read + packages: write + +env: + OTP_VERSION: '27.3.4.6' + ELIXIR_VERSION: '1.19.4' + +jobs: + push_to_registry: + name: Push Docker image to GitHub Container Registry + runs-on: build + env: + RELEASE_VERSION: 11.0.0 + steps: + - uses: actions/checkout@v5 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image (indexer + API) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-zilliqa-private:latest, ghcr.io/blockscout/blockscout-zilliqa-private:${{ env.RELEASE_VERSION }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=zilliqa + + - name: Build and push Docker image (indexer) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-zilliqa-private:${{ env.RELEASE_VERSION }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=zilliqa diff --git a/.github/workflows/release-zksync.yml b/.github/workflows/release-zksync.yml new file mode 100644 index 000000000000..4c1b577bf91e --- /dev/null +++ b/.github/workflows/release-zksync.yml @@ -0,0 +1,64 @@ +name: Release for ZkSync + +on: + workflow_dispatch: + release: + types: [published] + +permissions: + contents: read + packages: write + +env: + OTP_VERSION: '27.3.4.6' + ELIXIR_VERSION: '1.19.4' + +jobs: + push_to_registry: + name: Push Docker image to GitHub Container Registry + runs-on: build + env: + RELEASE_VERSION: 11.0.0 + steps: + - uses: actions/checkout@v5 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image for ZkSync (indexer + API) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-zksync-private:latest, ghcr.io/blockscout/blockscout-zksync-private:${{ env.RELEASE_VERSION }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=zksync + + - name: Build and push Docker image for ZkSync (indexer) + uses: docker/build-push-action@v6 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ghcr.io/blockscout/blockscout-zksync-private:${{ env.RELEASE_VERSION }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=zksync diff --git a/.pairs b/.pairs deleted file mode 100644 index e40bd6ebc940..000000000000 --- a/.pairs +++ /dev/null @@ -1,13 +0,0 @@ -pairs: - cj: CJ Bryan; cj - dr: Doc Ritezel; doc - mo: Matt Olenick; matto - db: Derek Barnes; dgb - rdwb: Desmond Bowe; des - -email: - prefix: pair - domain: ministryofvelocity.com - no_solo_prefix: true - -global: true diff --git a/CHANGELOG.md b/CHANGELOG.md index 34e05d0bd601..d5998e3be0e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,183 @@ # Changelog +## 11.0.0 + +### 🚀 Features + +- Async CSV export ([#14028](https://github.com/blockscout/blockscout/issues/14028)) +- FHE operations and tags ([#13742](https://github.com/blockscout/blockscout/issues/13742)) +- Restore BENS preloads on the main page under toggle and add blocks BENS preload toggle ([#14262](https://github.com/blockscout/blockscout/pull/14262)) +- /api/legacy/* wrappers for three ES-compatible RPC endpoints ([#14239](https://github.com/blockscout/blockscout/pull/14239)) +- Make token balances import chunk size configurable ([#14250](https://github.com/blockscout/blockscout/pull/14250)) +- Add toggle to disable transactions / token transfers BENS preload ([#14159](https://github.com/blockscout/blockscout/issues/14159)) +- Add ENS and metadata preloading in block channel ([#12074](https://github.com/blockscout/blockscout/issues/12074)) +- Add validation for IPFS links before sending requests to gateway ([#14131](https://github.com/blockscout/blockscout/issues/14131)) +- Add search by token address hash in /api/v2/tokens ([#14102](https://github.com/blockscout/blockscout/issues/14102)) +- Add :rename heavy index db operation type and implement zero-downtime index replacement for transactions table ([#14052](https://github.com/blockscout/blockscout/issues/14052)) +- Use libraries field from eth bytecode db response ([#13948](https://github.com/blockscout/blockscout/issues/13948)) + +### 🐛 Bug Fixes + +- Fix filecoin view error ([#14255](https://github.com/blockscout/blockscout/pull/14255)) +- Internal transactions on-demand fetcher: check existence of deleted internal transactions address placeholders ([#14249](https://github.com/blockscout/blockscout/pull/14249)) +- Fix OnDemand.InternalTransaction fetcher ([#14242](https://github.com/blockscout/blockscout/pull/14242)) +- Guard missing ETS table in contract creator fetcher ([#14241](https://github.com/blockscout/blockscout/pull/14241)) +- Address ids usage improvements ([#14240](https://github.com/blockscout/blockscout/pull/14240)) +- Fix timeouts for API v1 tokentx endpoint ([#14185](https://github.com/blockscout/blockscout/issues/14185)) +- Remove internal transaction error field references ([#14213](https://github.com/blockscout/blockscout/pull/14213)) +- Handle partial errors in ContractCode fetch_codes ([#14211](https://github.com/blockscout/blockscout/pull/14211)) +- Include bridged token query params in OpenAPI spec ([#14209](https://github.com/blockscout/blockscout/pull/14209)) +- Update changed constraint name in shrink IT migration ([#14205](https://github.com/blockscout/blockscout/pull/14205)) +- Fix contract internal transactions preload ([#14203](https://github.com/blockscout/blockscout/issues/14203)) +- Handle RPC errors in ContractCreator, limit retries to 5 ([#14136](https://github.com/blockscout/blockscout/issues/14136)) +- Prevent duplicate missing block range inserts ([#14138](https://github.com/blockscout/blockscout/issues/14138)) +- Implementation address hash retrieval logic in the old UI ([#14192](https://github.com/blockscout/blockscout/issues/14192)) +- Keycloak address displaying ([#14155](https://github.com/blockscout/blockscout/issues/14155)) +- Celo election rewards csv export ([#14160](https://github.com/blockscout/blockscout/issues/14160)) +- Sync GraphQL language enum with SmartContract schema ([#14109](https://github.com/blockscout/blockscout/issues/14109)) +- Don't insert PTO for non-traceable transactions ([#14133](https://github.com/blockscout/blockscout/issues/14133)) +- Fix pending ops migration overflow by adaptive batching and chunked inserts ([#14135](https://github.com/blockscout/blockscout/issues/14135)) +- State changes use token transfer type ([#14073](https://github.com/blockscout/blockscout/issues/14073)) +- Fix 500 error when apikey provided with disabled account ([#14064](https://github.com/blockscout/blockscout/issues/14064)) +- Fix ArgumentError in BlockScoutWeb.NFTHelper.get_media_src/2 ([#14051](https://github.com/blockscout/blockscout/issues/14051)) + +### 🚜 Refactor + +- Refactor RollupReorgMonitorQueue ([#14196](https://github.com/blockscout/blockscout/issues/14196)) +- Deduplicate json_rpc_named_arguments ([#14194](https://github.com/blockscout/blockscout/issues/14194)) +- Fully migrate to `language` enum field in `smart_contracts` table ([#14049](https://github.com/blockscout/blockscout/issues/14049)) +- Migrate address_names to composite primary key on (address_hash, name) ([#14078](https://github.com/blockscout/blockscout/issues/14078)) + +### 📚 Documentation + +- Add .dialyzer-ignore hygiene guideline to CONTRIBUTING ([#14199](https://github.com/blockscout/blockscout/issues/14199)) + +### ⚡ Performance + +- Optimize token1155tx API v1 endpoint ([#14202](https://github.com/blockscout/blockscout/issues/14202)) +- Optimize optional address preloads across tx endpoints ([#14165](https://github.com/blockscout/blockscout/pull/14165)) +- Optimize on demand hot contracts performance ([#14150](https://github.com/blockscout/blockscout/issues/14150)) +- Remove join to "blocks" in api/v2/blocks/:block_number/transactions API endpoint ([#14162](https://github.com/blockscout/blockscout/issues/14162)) +- Improve performance of /api/v2/tokens API endpoint ([#14158](https://github.com/blockscout/blockscout/issues/14158)) + +### ⚙️ Miscellaneous Tasks + +- Update LICENCE ([#14201](https://github.com/blockscout/blockscout/pull/14201)) +- Remove "transaction_hash", "block_hash" and "block_index" from internal transactions, migrate Address Hashes to Address IDs ([#14099](https://github.com/blockscout/blockscout/issues/14099)) +- Remove timeout for test for FillInternalTransactionsAddressIds ([#14266](https://github.com/blockscout/blockscout/pull/14266)) +- Increase default timeout for FillInternalTransactionsAddressIds ([#14264](https://github.com/blockscout/blockscout/pull/14264)) +- Expand action of API_DISABLE_CONTRACT_CREATION_INTERNAL_TRANSACTION_ASSOCIATION flag to preload smart-contract associations ((#14257)[https://github.com/blockscout/blockscout/pull/14257]) +- Add Autoscout promo in the logs ([#14234](https://github.com/blockscout/blockscout/pull/14234)) +- Improve internal transactions migrations ([#14233](https://github.com/blockscout/blockscout/pull/14233)) +- Remove unused Explorer.Chain.Address.find_contract_addresses/2 function ([#14220](https://github.com/blockscout/blockscout/pull/14220)) +- Prevent deadlocks in IT fields removing migration ([#14215](https://github.com/blockscout/blockscout/pull/14215)) +- Filter blocks by BLOCK_RANGES in add_ranges_by_block_numbers ([#13875](https://github.com/blockscout/blockscout/pull/13875)) +- FillInternalTransactionsAddressIds improvements ([#14208](https://github.com/blockscout/blockscout/pull/14208)) +- Remove timeout between successful migrations ([#14198](https://github.com/blockscout/blockscout/issues/14198)) +- Add batch size env for FillInternalTransactionsAddressIds migration ([#14204](https://github.com/blockscout/blockscout/issues/14204)) +- Add Celo OpenAPI specs ([#14197](https://github.com/blockscout/blockscout/issues/14197), [#14229](https://github.com/blockscout/blockscout/pull/14229)) +- Cover counters to multichain export with unit tests ([#14193](https://github.com/blockscout/blockscout/issues/14193)) +- Update credo config ([#14147](https://github.com/blockscout/blockscout/issues/14147)) +- Remove Polygon zkEVM support ([#14188](https://github.com/blockscout/blockscout/issues/14188)) +- Add Block.full_refetch ([#14180](https://github.com/blockscout/blockscout/issues/14180)) +- Remove deprecated files from the root folder ([#14186](https://github.com/blockscout/blockscout/issues/14186)) +- Remove deprecated "transaction actions" indexer ([#14183](https://github.com/blockscout/blockscout/issues/14183)) +- Stabilize various flaky tests ([#14149](https://github.com/blockscout/blockscout/issues/14149)) +- Return automatic chromedriver version definition ([#14108](https://github.com/blockscout/blockscout/issues/14108)) +- Move agents skills to .agents/skills folder ([#14081](https://github.com/blockscout/blockscout/issues/14081)) +- Put in order background db migrations on the "transactions" table ([#14077](https://github.com/blockscout/blockscout/issues/14077)) +- Drop `transactions_operator_fee_constant_index` ([#14066](https://github.com/blockscout/blockscout/issues/14066)) +- Unescape ampersand in token's metadata ([#14055](https://github.com/blockscout/blockscout/issues/14055)) +- Treat blocks with huge amount of transactions as massive ([#13994](https://github.com/blockscout/blockscout/issues/13994)) +- Add OpenAPI docs for Scroll and Zilliqa endpoints ([#13972](https://github.com/blockscout/blockscout/issues/13972)) +- Add all-in-one open API spec file ([#14050](https://github.com/blockscout/blockscout/issues/14050)) + +### New ENV variables + +| Variable | Description | Parameters | +|-----------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------| +| `DISABLE_BLOCK_BROADCAST_ENRICHMENT` | If `true`, disables ENS and metadata enrichment for `new_block` WebSocket broadcasts. Implemented in [#12074](https://github.com/blockscout/blockscout/pull/12074). | Version: v11.0.0\+
Default: (empty)
Applications: API | +| `INDEXER_MASSIVE_BLOCK_THRESHOLD` | Max transactions count in a single block after which the block is treated as massive. Implemented in [#13994](https://github.com/blockscout/blockscout/pull/13994). | Version: v11.0.0\+
Default: `1000`
Applications: Indexer | +| `INDEXER_CURRENT_TOKEN_BALANCES_IMPORT_CHUNK_SIZE` | Number of CurrentTokenBalances items processed per chunk in token balances import. Default is 50; effective minimum is 1. Implemented in [#14250](https://github.com/blockscout/blockscout/pull/14250). | Version: v11.0.0\+
Default: `50`
Applications: Indexer | +| `INDEXER_FHE_OPERATIONS_ENABLED` | Flag to enable parsing of Fully Homomorphic Encryption (FHE) operations from transactions. Implemented in [#13742](https://github.com/blockscout/blockscout/pull/13742). | Version: v11.0.0\+
Default: `false`
Applications: Indexer | +| `MIGRATION_FILL_INTERNAL_TRANSACTIONS_ADDRESS_IDS_BATCH_SIZE` | Number of internal transactions to fill their address ids in the batch. Implemented in [#14204](https://github.com/blockscout/blockscout/pull/14204). | Version: v11.0.0\+
Default: `30`
Applications: Indexer | +| `MIGRATION_FILL_INTERNAL_TRANSACTIONS_ADDRESS_IDS_TIMEOUT` | Timeout between filling internal transactions address ids batches processing. Implemented in [#14208](https://github.com/blockscout/blockscout/pull/14208). | Version: v11.0.0\+
Default: `5s`
Applications: Indexer | +| `DISABLE_BLOCKS_BENS_PRELOAD` | If `true`, skips ENS name preloading in responses for block list endpoints: `/api/v2/blocks`, `/api/v2/main-page/blocks`, `/api/v2/blocks/optimism-batch/:batch_number`, `/api/v2/blocks/scroll-batch/:batch_number`. | Version: v11.0.0+
Default: `false`
Applications: API | +| `DISABLE_TRANSACTIONS_BENS_PRELOAD` | If `true`, skips ENS name preloading in responses for transaction list endpoints: `/api/v2/transactions`, `/api/v2/transactions/watchlist`, `/api/v2/main-page/transactions`, `/api/v2/main-page/transactions/watchlist`, `/api/v2/addresses/:hash/transactions`, `/api/v2/blocks/:hash/transactions`. | Version: v11.0.0+
Default: `false`
Applications: API | +| `DISABLE_TOKEN_TRANSFERS_BENS_PRELOAD` | If `true`, skips ENS name preloading in responses for token transfer list endpoints: `/api/v2/token-transfers`, `/api/v2/addresses/:hash/token-transfers`, `/api/v2/tokens/:address_hash_param/transfers`. | Version: v11.0.0+
Default: `false`
Applications: API | +| `CSV_EXPORT_ASYNC_ENABLED` | Enables async CSV export for supported endpoints. When enabled, the API returns `202 Accepted` with a `request_id` and processes exports through Oban instead of streaming them directly. Implemented in [#14028](https://github.com/blockscout/blockscout/pull/14028) | Version: v11.0.0\+ Required: No
Default: `false`
Applications: API | +| `CSV_EXPORT_ASYNC_OBAN_CONCURRENCY` | Sets Oban concurrency for the `csv_export` queue used by async CSV exports. Implemented in [#14028](https://github.com/blockscout/blockscout/pull/14028) | Version: v11.0.0\+ Required: No
Default: `10`
Applications: API | +| `CSV_EXPORT_ASYNC_GOKAPI_URL` | Base URL of the Gokapi instance used to store completed async CSV exports. Trailing slash is stripped during validation. Implemented in [#14028](https://github.com/blockscout/blockscout/pull/14028) | Version: v11.0.0\+ Required: Yes, if async export is enabled
Default: (empty)
Applications: API | +| `CSV_EXPORT_ASYNC_GOKAPI_API_KEY` | API key sent to Gokapi in the `apikey` header for async CSV export uploads. Implemented in [#14028](https://github.com/blockscout/blockscout/pull/14028) | Version: v11.0.0\+ Required: Yes, if async export is enabled
Default: (empty)
Applications: API | +| `CSV_EXPORT_ASYNC_MAX_PENDING_TASKS_PER_IP` | Maximum number of pending async CSV export requests allowed per client IP at once. Implemented in [#14028](https://github.com/blockscout/blockscout/pull/14028) | Version: v11.0.0\+ Required: No
Default: `3`
Applications: API | +| `CSV_EXPORT_ASYNC_UPLOAD_CHUNK_SIZE` | Chunk size in bytes for reading the generated CSV file and uploading it to Gokapi. Should be synchronized with Gokapi settings. Implemented in [#14028](https://github.com/blockscout/blockscout/pull/14028) | Version: v11.0.0\+ Required: No
Default: `47185920`
Applications: API | +| `CSV_EXPORT_DB_TIMEOUT` | Timeout for CSV export database work. Follows the [time format](/setup/env-variables/backend-env-variables#time-format). Implemented in [#14028](https://github.com/blockscout/blockscout/pull/14028) | Version: v11.0.0\+ Required: No
Default: `1h` if async export is enabled, otherwise `5m`
Applications: API | +| `CSV_EXPORT_ASYNC_TMP_DIR` | Directory used for in-progress async CSV export files before they are uploaded to Gokapi. Implemented in [#14028](https://github.com/blockscout/blockscout/pull/14028) | Version: v11.0.0\+ Required: No
Default: `/tmp/csv_export`
Applications: API | +| `CSV_EXPORT_ASYNC_GOKAPI_TIMEOUT` | HTTP timeout and `recv_timeout` used for Gokapi requests during async CSV export. Follows the [time format](/setup/env-variables/backend-env-variables#time-format). Implemented in [#14028](https://github.com/blockscout/blockscout/pull/14028) | Version: v11.0.0\+ Required: No
Default: `60s`
Applications: API | +| `CSV_EXPORT_ASYNC_GOKAPI_UPLOAD_EXPIRY_DAYS` | Sets Gokapi `expiryDays` for completed async CSV export uploads. Implemented in [#14028](https://github.com/blockscout/blockscout/pull/14028) | Version: v11.0.0\+ Required: No
Default: `1`
Applications: API | +| `CSV_EXPORT_ASYNC_GOKAPI_UPLOAD_ALLOWED_DOWNLOADS` | Sets Gokapi `allowedDownloads` for completed async CSV export uploads. Implemented in [#14028](https://github.com/blockscout/blockscout/pull/14028) | Version: v11.0.0\+ Required: No
Default: `1`
Applications: API | + +### Deprecated ENV variables + +| Variable | Description | Default | Version | Need recompile | Deprecated in Version | +| -------- | ----------- | ------- | ------- | -------------- | --------------------- | +| Deprecated `INDEXER_POLYGON_ZKEVM_BATCHES_ENABLED` | Enables Polygon zkEVM batches fetcher. Implemented in [#7584](https://github.com/blockscout/blockscout/pull/7584). | `false` | v5.3.1+ | | v11.0.0+ | +| Deprecated `INDEXER_POLYGON_ZKEVM_BATCHES_CHUNK_SIZE` | The number of Polygon zkEVM batches in one chunk when reading them from RPC. Implemented in [#7584](https://github.com/blockscout/blockscout/pull/7584). | `20` | v5.3.1+ | | v11.0.0+ | +| Deprecated `INDEXER_POLYGON_ZKEVM_BATCHES_RECHECK_INTERVAL` | The latest batch rechecking interval, seconds. Implemented in [#7584](https://github.com/blockscout/blockscout/pull/7584). | `60` | v5.3.1+ | | v11.0.0+ | +| Deprecated `INDEXER_POLYGON_ZKEVM_BATCHES_IGNORE` | Comma-separated list of batch numbers that should be ignored by the fetcher. Implemented in [#12387](https://github.com/blockscout/blockscout/pull/12387). | (empty) | v9.0.0+ | | v11.0.0+ | +| Deprecated `INDEXER_POLYGON_ZKEVM_L1_RPC` | The RPC endpoint for L1 used to fetch Deposit or Withdrawal bridge events. Implemented in [#9098](https://github.com/blockscout/blockscout/pull/9098). | (empty) | v6.2.0+ | | v11.0.0+ | +| Deprecated `INDEXER_POLYGON_ZKEVM_L1_BRIDGE_START_BLOCK` | The number of a start block on L1 to index L1 bridge events. If the table of bridge operations is not empty, the process will continue indexing from the last indexed L1 event. If empty or not defined, the L1 events are not handled. Implemented in [#9098](https://github.com/blockscout/blockscout/pull/9098). | (empty) | v6.2.0+ | | v11.0.0+ | +| Deprecated `INDEXER_POLYGON_ZKEVM_L1_BRIDGE_CONTRACT` | The address of PolygonZkEVMBridgeV2 contract on L1 used to fetch L1 bridge events. Required for L1 bridge events indexing. Implemented in [#9098](https://github.com/blockscout/blockscout/pull/9098). | (empty) | v6.2.0+ | | v11.0.0+ | +| Deprecated `INDEXER_POLYGON_ZKEVM_L1_BRIDGE_NETWORK_ID` | L1 Network ID in terms of Polygon zkEVM bridge (0 = Ethereum Mainnet, 1 = Polygon zkEVM, 2 = Astar zkEVM, etc.). Required if `INDEXER_POLYGON_ZKEVM_L1_BRIDGE_START_BLOCK` or `INDEXER_POLYGON_ZKEVM_L2_BRIDGE_START_BLOCK` is defined. Implemented in [#9637](https://github.com/blockscout/blockscout/pull/9637). | (empty) | v6.4.0+ | | v11.0.0+ | +| Deprecated `INDEXER_POLYGON_ZKEVM_L1_BRIDGE_ROLLUP_INDEX` | L1 Rollup index in terms of Polygon zkEVM bridge (0 = Polygon zkEVM, 1 = Astar zkEVM, etc.). Not defined if L1 is Ethereum Mainnet. Required if L1 is not Ethereum Mainnet and `INDEXER_POLYGON_ZKEVM_L1_BRIDGE_START_BLOCK` or `INDEXER_POLYGON_ZKEVM_L2_BRIDGE_START_BLOCK` is defined. Implemented in [#9637](https://github.com/blockscout/blockscout/pull/9637). | (empty) | v6.4.0+ | | v11.0.0+ | +| Deprecated `INDEXER_POLYGON_ZKEVM_L1_BRIDGE_NATIVE_SYMBOL` | The symbol of the native coin on L1 to display it in the table of the bridge Deposits and Withdrawals on UI. Implemented in [#9098](https://github.com/blockscout/blockscout/pull/9098). | `ETH` | v6.2.0+ | | v11.0.0+ | +| Deprecated `INDEXER_POLYGON_ZKEVM_L1_BRIDGE_NATIVE_DECIMALS` | The number of decimals to correctly display an amount of native coins for some Deposit or Withdrawal bridge operations on UI. Implemented in [#9098](https://github.com/blockscout/blockscout/pull/9098). | `18` | v6.2.0+ | | v11.0.0+ | +| Deprecated `INDEXER_POLYGON_ZKEVM_L2_BRIDGE_START_BLOCK` | The number of a start block on L2 to index L2 bridge events. If the table of bridge operations is not empty, the process will continue indexing from the last indexed L2 event. If empty or not defined, the L2 events are not handled. Implemented in [#9098](https://github.com/blockscout/blockscout/pull/9098). | (empty) | v6.2.0+ | | v11.0.0+ | +| Deprecated `INDEXER_POLYGON_ZKEVM_L2_BRIDGE_CONTRACT` | The address of PolygonZkEVMBridgeV2 contract on L2 used to fetch L2 bridge events. Required for L2 bridge events indexing. Implemented in [#9098](https://github.com/blockscout/blockscout/pull/9098). | (empty) | v6.2.0+ | | v11.0.0+ | +| Deprecated `INDEXER_POLYGON_ZKEVM_L2_BRIDGE_NETWORK_ID` | L2 Network ID in terms of Polygon zkEVM bridge (1 = Polygon zkEVM, 2 = Astar zkEVM, etc.). Required if `INDEXER_POLYGON_ZKEVM_L1_BRIDGE_START_BLOCK` or `INDEXER_POLYGON_ZKEVM_L2_BRIDGE_START_BLOCK` is defined. Implemented in [#9637](https://github.com/blockscout/blockscout/pull/9637). | (empty) | v6.4.0+ | | v11.0.0+ | +| Deprecated `INDEXER_POLYGON_ZKEVM_L2_BRIDGE_ROLLUP_INDEX` | L2 Rollup index in terms of Polygon zkEVM bridge (0 = Polygon zkEVM, 1 = Astar zkEVM, etc.). Required if `INDEXER_POLYGON_ZKEVM_L1_BRIDGE_START_BLOCK` or `INDEXER_POLYGON_ZKEVM_L2_BRIDGE_START_BLOCK` is defined. Implemented in [#9637](https://github.com/blockscout/blockscout/pull/9637). | (empty) | v6.4.0+ | | v11.0.0+ | +| Deprecated `INDEXER_TX_ACTIONS_AAVE_V3_POOL_CONTRACT` | Pool contract address for Aave v3 protocol. If not defined, Aave transaction actions are ignored by the indexer. Implemented in [#7185](https://github.com/blockscout/blockscout/pull/7185). | (empty) | v5.1.3+ | | v11.0.0+ | +| Deprecated `INDEXER_TX_ACTIONS_ENABLE` | If `true`, transaction action indexer is active. Implemented in [#6582](https://github.com/blockscout/blockscout/pull/6582). | `false` | v5.1.0+ | | v11.0.0+ | +| Deprecated `INDEXER_TX_ACTIONS_MAX_TOKEN_CACHE_SIZE` | Maximum number of items in an internal cache of tx actions indexing process (to limit memory consumption). Implemented in [#6582](https://github.com/blockscout/blockscout/pull/6582). | `100000` | v5.1.0+ | | v11.0.0+ | +| Deprecated `INDEXER_TX_ACTIONS_REINDEX_FIRST_BLOCK` | The first block of a block range for historical indexing or reindexing of tx actions. Implemented in [#6582](https://github.com/blockscout/blockscout/pull/6582). | (empty) | v5.1.0+ | | v11.0.0+ | +| Deprecated `INDEXER_TX_ACTIONS_REINDEX_LAST_BLOCK` | The last block of a block range for historical indexing or reindexing of tx actions. Implemented in [#6582](https://github.com/blockscout/blockscout/pull/6582). | (empty) | v5.1.0+ | | v11.0.0+ | +| Deprecated `INDEXER_TX_ACTIONS_REINDEX_PROTOCOLS` | Comma-separated names of protocols which should be indexed or reindexed on historical blocks defined by the range. Example: `uniswap_v3,zkbob` - only these protocols will be indexed or reindexed for the defined block range. If the value is empty string (or not defined), all supported protocols will be indexed/reindexed. This option is not applicable to `realtime` and `catchup` fetchers (it always indexes all supported protocols). Implemented in [#6582](https://github.com/blockscout/blockscout/pull/6582). | (empty) | v5.1.0+ | | v11.0.0+ | +| Deprecated `INDEXER_TX_ACTIONS_UNISWAP_V3_FACTORY_CONTRACT` | UniswapV3Factory contract address. Implemented in [#7312](https://github.com/blockscout/blockscout/pull/7312). | `0x1F98431c8aD98523631AE4a59f267346ea31F984` | v5.1.4+ | | v11.0.0+ | +| Deprecated `INDEXER_TX_ACTIONS_UNISWAP_V3_NFT_POSITION_MANAGER_CONTRACT` | NonfungiblePositionManager contract address for Uniswap v3. Implemented in [#7312](https://github.com/blockscout/blockscout/pull/7312). | `0xC36442b4a4522E871399CD717aBDD847Ab11FE88` | v5.1.4+ | | v11.0.0+ | +| Deprecated `MIGRATION_REINDEX_DUPLICATED_INTERNAL_TRANSACTIONS_BATCH_SIZE` | Number of internal transactions to reindex in the batch. Implemented in [#12394](https://github.com/blockscout/blockscout/pull/12394). | `100` | v8.1.0+ | | v11.0.0+ | +| Deprecated `MIGRATION_REINDEX_DUPLICATED_INTERNAL_TRANSACTIONS_CONCURRENCY` | Number of parallel reindexing internal transaction batches processing. Implemented in [#12394](https://github.com/blockscout/blockscout/pull/12394). | `1` | v8.1.0+ | | v11.0.0+ | +| Deprecated `MIGRATION_REINDEX_DUPLICATED_INTERNAL_TRANSACTIONS_TIMEOUT` | Timeout between reindexing internal transaction batches processing. Implemented in [#12394](https://github.com/blockscout/blockscout/pull/12394). | `0` | v8.1.0+ | | v11.0.0+ | + + +## 10.2.6 + +### 🐛 Bug Fixes + +- Fix PendingTransactionsSanitizer ([#14235](https://github.com/blockscout/blockscout/issues/14235)) + + +## 10.2.5 + +### 🐛 Bug Fixes + +- Update changed constraint name in shrink IT migration ([#14205](https://github.com/blockscout/blockscout/issues/14205)) + + +## 10.2.4 + +### ⚡ Performance + +- Use tuple-based comparison to utilize index ([#14178](https://github.com/blockscout/blockscout/pull/14178)) + +### 🐛 Bug Fixes + +- Update transaction from receipt in PendingTransactionsSanitizer ([#14182](https://github.com/blockscout/blockscout/issues/14182)) + +### ⚙️ Miscellaneous Tasks + +- Add swagger generation for Arc and Suave chain types ([#14181](https://github.com/blockscout/blockscout/issues/14181)) + + ## 10.2.3 ### 🐛 Bug Fixes @@ -12,7 +190,7 @@ ### 🐛 Bug Fixes - Fix token transfers block_consensus setting ([#14005](https://github.com/blockscout/blockscout/issues/14005)) -- OP Withdrawals indexer enhancement ([#14056](https://github.com/blockscout/blockscout/issues/14056)) +- OP Withdrawals indexer enhancement ([#13436](https://github.com/blockscout/blockscout/issues/13436)) ### ⚙️ Miscellaneous Tasks diff --git a/LICENSE b/LICENSE index 94a9ed024d38..dcdcab806a8c 100644 --- a/LICENSE +++ b/LICENSE @@ -1,674 +1,131 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. +SPDX-License-Identifier: LicenseRef-Blockscout + +Effective Date: 2026-04-22 +Version: 1.0 +Previous Version: N/A + +PLEASE READ THIS LICENCE CAREFULLY. BY DOWNLOADING, ACCESSING, COPYING, MODIFYING, DISTRIBUTING, DEPLOYING, OR OTHERWISE USING THE SOFTWARE, YOU CONFIRM THAT YOU HAVE READ, UNDERSTOOD, AND AGREE TO BE LEGALLY BOUND BY THE TERMS OF THIS LICENCE IN FULL. IF YOU DO NOT AGREE TO THESE TERMS, YOU MUST NOT DOWNLOAD, USE, COPY, MODIFY, OR DISTRIBUTE THE SOFTWARE. + +1. Definitions + +“Commercial Licence” means a separate written commercial licence agreement entered into between you and the Licensor, which expressly references this Licence and supplements its terms by granting additional rights, or permitting uses, that are not granted or permitted under this Licence. + +“Derivative Work” means any work, whether in source or object form, that is based on or derived from the Software and in which any editorial revisions, annotations, elaborations, additions, deletions, or other modifications, taken as a whole, constitute an original work of authorship. For the avoidance of doubt, Derivative Works do not include works that remain separable from, or merely link to, the Software. + +“Feedback” means any comments, suggestions, recommendations, ideas, proposals, or other feedback, whether oral or written, provided by you in connection with or relating to the Software. + +“Group” means, in respect of an entity, that entity together with any other entity that directly or indirectly controls, is controlled by, or is under common control with, that entity. For the purposes of this definition, “control” means the direct or indirect ownership of more than fifty per cent (50%) of the voting securities or other ownership interest of an entity, or the power to direct or cause the direction of the management and policies of that entity (whether by ownership, contract, or otherwise). + +“Licence” means this Blockscout Software Licence, as amended or updated from time to time. + +“Licensor” means Blockscout Limited, an international business company incorporated under the laws of the Republic of Seychelles. + +“Prior Software” means any prior version, release, build, or component of the Software that was made available by or on behalf of the Licensor before the Effective Date, and that is not distributed under this Licence. + +“Software” means the Blockscout blockchain explorer, a tool for inspecting and analyzing blockchain networks, as made available by the Licensor under this Licence, including the source code, object code, executable files, configuration and deployment materials, documentation, APIs/SDKs (if any), and any part or portion thereof. + +“You” or “your” means the individual who accepts this Licence. Where you act on behalf of an entity, “you” shall refer to both: (i) you as an individual exercising rights under this Licence; and (ii) the entity on whose behalf you are acting. + +2. Licence and Attribution + + a. Licence. Subject to and conditional upon your compliance with this Licence, the Licensor hereby grants you a temporary, worldwide, non-exclusive, royalty-free, revocable, non-transferable, and non-sublicensable licence to download, review, use, deploy, copy, modify, and create Derivative Works of the Software. All rights not expressly granted under this Licence are reserved by the Licensor. + + b. Branding and Attribution. You shall preserve all copyright, patent, trademark, branding, and attribution notices included in or displayed by the Software, and shall not remove, obscure, conceal, replace, alter, disable, or otherwise interfere with the display or integrity of such notices. + + c. Interface Attribution. Where the Software is used to power, enable, or provide functionality for any user interface (including any website, web application, mobile application, or other frontend), you shall ensure that such interface includes clear and reasonably prominent attribution to the Licensor at all times while you use the Software. Such attribution shall (i) prominently identify the Licensor by the brand name “Blockscout” (such as “Made with Blockscout” or “Powered by Blockscout”), and (ii) include the respective attribution text, link (or a hyperlink) to the website https://blockscout.com and any branding or notices provided by the Licensor, in each case in the same form and manner as displayed in the footer of the following website: https://eth.blockscout.com. + + d. No Endorsement or Service Provision. Except as expressly agreed in writing by the Licensor, the Licensor does not provide, and shall not be deemed to provide, any product or service that you (or any third party) offer, operate, or make available using the Software, and the Licensor is not a party to, and has no responsibility or liability for, any relationship, transaction or interaction between you and any end user or other third party. The Licensor does not endorse, sponsor, approve, or recommend you, your business, or any of your products or services. Except as expressly agreed in writing by the Licensor, you shall not (and shall not authorise or permit any third party to) state, represent, imply, or otherwise hold out that: (i) the Licensor provides any services to or for you; (ii) the Licensor acts on your behalf; (iii) you are acting as an agent, representative, partner, or affiliate of the Licensor; or (iv) the Licensor endorses, sponsors, approves, or recommends you, your business, or any of your products or services. + +3. Scope and Updates + + a. Scope. Subject to Third-Party Licences clause, this Licence applies solely to the version of the Software (and its components) with which it is distributed by or on behalf of the Licensor. This Licence does not apply to any Prior Software. + + + b. Software Changes. The Software is under active development and may be modified, updated, improved, withdrawn, suspended, or discontinued by the Licensor at any time, in whole or in part, with or without notice. The Licensor does not warrant or guarantee that any particular features, functionality, integrations, interfaces, or components of the Software will remain available, unchanged, or compatible with any prior or future versions. You acknowledge and agree that the Software may change over time and that continued use of the Software is at your sole risk. + + + c. Licence Updates. The Licensor may amend, replace, or update this Licence at any time in its sole discretion, with or without notice. Where you continue to access, use, deploy, copy, modify, or otherwise use the Software after the effective date of an updated Licence, you acknowledge and agree that your continued use constitutes acceptance of, and you shall comply with, the updated Licence. For the avoidance of doubt, an updated Licence may introduce additional restrictions or permissions, including requiring a Commercial Licence for certain uses. + +4. Restricted Uses and Commercial Licence + + a. Restricted Commercial or Monetised Use (including SaaS and RaaS). Unless and until you obtain a Commercial Licence, you shall not, and shall not authorise or permit any third party to exercise any rights granted under this Licence to (directly or indirectly) sell, license, monetise, commercialise, or otherwise make available the Software or its functionality to any third party in exchange for any fee or other consideration (including without limitation fees for hosting, access, subscriptions, support, consulting, implementation, customisation, maintenance, managed services, or any other services), where such product or service incorporates, uses, depends on, or is materially enabled by the Software (including offering the Software or its functionality on a hosted, “as-a-service”, or managed basis). + + b. Obtaining Commercial Licence. If you intend to exercise any rights or engage in any uses of the Software that are prohibited, restricted, or not granted under this Licence, you must, prior to such use, contact the Licensor at https://eaas.blockscout.com/#contact to request a Commercial Licence and applicable pricing and terms. Any such rights or uses are unauthorised unless and until a Commercial Licence has been expressly agreed in writing by the Licensor. Nothing in this Licence obliges the Licensor to grant any Commercial Licence or to enter into any agreement with you. + + c. Compliance Verification. Upon the Licensor’s reasonable request, you shall promptly provide the Licensor with such information and documentation as the Licensor may reasonably require to verify your compliance with this Licence, including (without limitation) to confirm whether your use of the Software requires a Commercial Licence. The Licensor shall use any such information solely for compliance verification purposes. + + d. Name and Branding Restrictions. You shall not distribute, market, or otherwise make available the Software or any Derivative Works under any name, designation, branding, or identifier that is identical or confusingly similar to the Licensor’s product names, trademarks, service marks, or trade names, or that is likely to cause confusion as to the origin, sponsorship, affiliation, or endorsement by the Licensor. + +5. Derivative Works + + a. Permission. Subject to and conditional upon your compliance with this Licence, you may create Derivative Works of the Software solely for your internal use of the Software. + + b. Restrictions. You shall not distribute, sublicense, sell, license, make available, or otherwise provide any Derivative Works, in whole or in part, to any third party without first obtaining a Commercial Licence. + + c. Ownership and Licence. Except as expressly provided in this Licence, ownership of any Derivative Works shall remain with you. Notwithstanding the foregoing, you hereby grant the Licensor a perpetual, irrevocable, worldwide, royalty-free, non-exclusive, transferable, and sublicensable licence to use, reproduce, modify, adapt, incorporate, and otherwise exploit any Derivative Works for any purpose, including to develop, improve, or distribute the Software. + + d. Tracking of Changes. Where you modify the Software or create any Derivative Works, you shall ensure that any modified files carry prominent notices stating that you have modified the Software and indicating the date of such modification. Any such notices shall not be construed as modifying, limiting, or otherwise affecting this Licence. + + e. Warranties. You represent and warrant that you own or otherwise have all necessary rights to create and license any Derivative Works as contemplated by this Licence, and that such Derivative Works do not infringe any third-party intellectual property rights, violate this Licence, or breach any applicable laws or regulations. + +6. Feedback + + a. Permission and Rights. You may, but are not obliged to, provide Feedback. Where you provide any Feedback, you acknowledge and agree that the Licensor may, in its sole discretion, use, reproduce, disclose, make publicly available, and otherwise exploit such Feedback for any purpose, commercial or otherwise, without restriction and without any obligation to you, including without acknowledgment or compensation. + + b. Licence. You hereby grant the Licensor a perpetual, irrevocable, worldwide, royalty-free, transferable, and sublicensable licence to use, reproduce, modify, adapt, publish, translate, distribute, publicly perform, publicly display, and otherwise exploit the Feedback, in whole or in part, in any manner and for any purpose. To the extent permitted by applicable law, you waive, and agree not to assert, any moral rights or similar rights you may have in the Feedback. + + c. Warranties. You represent and warrant that you own or otherwise have all necessary rights to grant the licence set out in this clause, and that the Feedback does not infringe any third-party rights or applicable laws. + +7. Ownership + + a. Ownership. The Licensor is and shall remain the sole owner (or, where applicable, the authorised licensor) of the Software. Nothing in this Licence shall operate to assign, transfer, or otherwise convey to you any right, title, or interest in or to the Software, save for the limited licence expressly granted under this Licence and the Commercial Licence, if applicable. All rights are licensed, not sold. You shall not take, or assist others in taking, any action that may diminish the Licensor's rights in the Software. + + b. Branding. Subject to your compliance with this Licence, the Licensor hereby grants you a temporary, worldwide, non-exclusive, royalty-free, revocable, non-transferable, and non-sublicensable licence to display the Licensor’s trademarks, trade names, and logos as provided along with the Software or as required under this Licence, solely for attribution purposes as required under this Licence. Except for the foregoing, no rights in any trademarks, trade names, or logos of the Licensor or its affiliates are granted under this Licence. + + c. Third-Party Licences. While the Software is made available in its entirety under this Licence, certain components of the Software may incorporate or be derived from third-party open-source software provided under permissive licences. Such specific third-party open-source software components are distributed under the terms of the applicable third-party licences. To the extent required by such third-party licences, applicable copyright notices, licence texts, and attribution requirements shall be preserved. Subject to the foregoing, the Software as a whole, and all parts thereof, is licensed under this Licence. + +8. Disclaimers + +To the maximum extent permitted by applicable law, the Software is provided on an “AS IS” and “AS AVAILABLE” basis and is used at your sole risk. The Licensor disclaims all warranties of any kind, whether express, implied, statutory, or otherwise, including (without limitation) any implied warranties of merchantability, satisfactory quality, fitness for a particular purpose, non-infringement, and title, and any warranties arising out of course of dealing, course of performance, or usage of trade. Without limiting the foregoing, the Licensor makes no representation or warranty that the Software will function as expected, meet your requirements, operate in combination with any other software, have any specific functionality, be uninterrupted, timely, secure, accurate, complete, or error-free, or that any defects or errors will be corrected. Nothing in this Licence excludes or limits any warranty, liability, or other term to the extent it cannot be excluded or limited under applicable law. The Software is provided for informational and technical purposes only and does not constitute legal, financial, tax, investment, or other professional advice. You are solely responsible for determining whether use of the Software is appropriate for your purposes. + +9. Limitation of Liability + +Nothing in this Licence excludes or limits liability for: (i) death or personal injury caused by negligence; (ii) fraud or fraudulent misrepresentation; or (iii) any other liability which cannot be excluded or limited under applicable law. Subject to the foregoing, to the maximum extent permitted by applicable law, the Licensor shall not be liable to you for any loss or damage whatsoever (whether direct, indirect, incidental, special, punitive or consequential), or for any loss of profits, revenue, business, business opportunity, anticipated savings, goodwill or data, or for any business interruption, arising out of or in connection with the use of, or inability to use, the Software, whether in contract, tort (including negligence), misrepresentation, restitution, breach of statutory duty, or otherwise, even if advised of the possibility of such loss or damage. To the extent that the Licensor is held liable notwithstanding the above, the total aggregate liability arising out of or in connection with this Licence or the Software shall not exceed the total amounts actually paid by you to the Licensor under this Licence in the twelve (12) months preceding the event giving rise to the claim (or, if no such amounts were paid, USD 100). + +10. Term and Termination + + a. Automatic Termination. This Licence shall automatically terminate, without any further action by the Licensor, upon any breach by you of its terms. + + b. Termination by the Licensor. The Licensor may terminate this Licence at any time in its sole discretion. Where reasonably practicable, the Licensor will use reasonable efforts to provide you with advance notice of termination. + + c. Effect of Termination. Upon termination of this Licence for any reason: (i) all rights granted to you under this Licence shall immediately cease; (ii) all Commercial Licences executed with you shall automatically terminate simultaneously with this Licence; (iii) you shall immediately cease all access to and use of the Software and any Derivative Works; (iv) you shall uninstall and delete the Software and any Derivative Works from all systems under your control and destroy all copies in your possession or control (in each case including any copies held by your contractors or service providers), except to the extent retention is required by applicable law; (v) you shall immediately cease all distribution or making available of the Software and any Derivative Works; and (vi) any provisions of this Licence which by their nature are intended to survive termination shall survive, including without limitation provisions relating to ownership, trademarks, feedback, disclaimers, limitation of liability, and governing law and jurisdiction. + +11. Governing Law and Arbitration + + a. Governing Law. This Licence and any dispute or claim (including non-contractual disputes or claims) arising out of or in connection with it or its subject matter or formation shall be governed by and construed in accordance with the law of England and Wales, excluding its conflict of law rules. For the avoidance of doubt, the provisions of the United Nations Convention on the International Sale of Goods shall not apply to this Licence. + + b. Dispute Resolution. The parties shall first attempt to resolve any dispute arising out of or in connection with this Licence informally. You may initiate such informal discussions by giving notice to the Licensor by email at info@blockscout.com. If the dispute is not resolved within thirty (30) days of such notice, the dispute shall be referred to and finally resolved by arbitration under the LCIA Rules, which Rules are deemed incorporated by reference into this clause. The seat (legal place) of arbitration shall be London, United Kingdom. The tribunal shall consist of one (1) arbitrator. The language of the arbitration shall be English. The governing law of this arbitration agreement shall be the laws of England and Wales. To the maximum extent permitted by applicable law, you may bring claims against the Licensor only in your individual capacity and not as a claimant or class member in any purported class, collective, consolidated, or representative proceeding. Any notices, requests, demands, or other communications given in connection with the arbitration may be sent in electronic form, including via email or any electronic filing system operated by the LCIA, and shall be deemed received when successfully transmitted to the recipient (as evidenced by no delivery failure notice). + +12. Miscellaneous + + a. Injunctive Relief. You acknowledge and agree that any breach of this Licence (including any breach of the restrictions on use of the Software) may cause the Licensor irreparable harm for which damages may not be an adequate remedy. Accordingly, the Licensor shall be entitled to seek injunctive relief, specific performance, and/or any other equitable relief for any such breach, in addition to any other rights or remedies available at law. + + b. Rights and Remedies. The rights and remedies provided under this Licence are cumulative and are in addition to, and not exclusive of, any rights or remedies provided by law. Any right or remedy may be exercised as often as required. + + c. Assignment. Unless otherwise permitted under this Licence, you shall not assign, transfer, charge, subcontract, declare a trust over, or deal in any other manner with any of your rights or obligations under this Licence without the prior written consent of the Licensor. The Licensor may at any time assign, transfer, charge, subcontract, or otherwise deal with any of its rights or obligations under this Licence without your consent or notice to you. + + d. Severability. If any provision (or part of a provision) of this Licence is found by any court or competent authority to be invalid, illegal, or unenforceable, that provision (or part-provision) shall be deemed modified to the minimum extent necessary to make it valid, legal, and enforceable. If such modification is not possible, the relevant provision (or part-provision) shall be deemed deleted. Any modification to or deletion of a provision (or part-provision) under this clause shall not affect the validity and enforceability of the remainder of this Licence. + + e. Entire Agreement. This Licence (together with the Commercial Licence, if any) constitutes the entire agreement between you and the Licensor in relation to its subject matter and supersedes and extinguishes all prior and contemporaneous agreements, understandings, negotiations, representations, and arrangements between the parties, whether written or oral. You acknowledge and agree that you shall have no remedies in respect of any statement, representation, assurance, or warranty (whether made innocently or negligently) that is not set out in this Licence (or the Commercial Licence). + + f. Commercial Licence. If a Commercial Licence is in place, it forms an integral part of this Licence. In the event of any conflict or inconsistency between the terms of this Licence and the Commercial Licence, the terms of the Commercial Licence shall prevail to the extent of such conflict or inconsistency. + + g. Notices. Any notice or other communication given by the Licensor under or in connection with this Licence may be given using any available means reasonably selected by the Licensor, including (without limitation) publication of notice in the Software repository, on the Licensor’s website, or through any other communication channel reasonably selected by the Licensor. Where you have a Commercial Licence in force, any notice or other communication given by the Licensor under or in connection with this Licence shall be in writing and may be delivered by email to the email address(es) specified for notices in the executed Commercial Licence. A notice sent by email shall be deemed received: (i) if sent during normal business hours, at the time of transmission; or (ii) if sent outside normal business hours, at 9:00 a.m. on the next business day. Notices sent by email shall be legally effective. + + h. Waiver. No failure or delay by the Licensor to exercise any right or remedy under this Licence or by law shall constitute a waiver of that or any other right or remedy, nor shall it prevent or restrict any further exercise of that or any other right or remedy. No single or partial exercise of any right or remedy shall prevent or restrict the further exercise of that or any other right or remedy. + + i. Third Party Rights. Except as expressly provided in this clause, a person who is not a party to this Licence shall not have any rights under the Contracts (Rights of Third Parties) Act 1999 to enforce any term of this Licence. Notwithstanding the foregoing, the Licensor’s affiliates and the Licensor’s directors, officers, employees, contractors, agents, representatives, and other personnel shall be entitled, pursuant to the Contracts (Rights of Third Parties) Act 1999, to enforce and rely on any provision of this Licence that limits or excludes the liability of the Licensor (including any limitations and exclusions of liability and any indemnities in favour of the Licensor) as if they were parties to this Licence. This Licence may be amended, varied, terminated, or rescinded (in whole or in part) without the consent of any such person. + +END OF THE LICENCE + +Copyright © Blockscout Limited 2026 diff --git a/README.md b/README.md index 54f4063679eb..c46fc2a0fb20 100644 --- a/README.md +++ b/README.md @@ -42,10 +42,12 @@ We would like to thank the EthPrize foundation for their funding support. ## Contributing -See [CONTRIBUTING.md](CONTRIBUTING.md) for contribution and pull request protocol. We expect contributors to follow our [code of conduct](CODE_OF_CONDUCT.md) when submitting code or comments. +See [CONTRIBUTING.md](.github/CONTRIBUTING.md) for contribution and pull request protocol. We expect contributors to follow our [code of conduct](.github/CODE_OF_CONDUCT.md) when submitting code or comments. ## License -[![License: GPL v3.0](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) +[![License: Blockscout Software Licence](https://img.shields.io/badge/License-Blockscout%20Software%20Licence-blue.svg)](LICENSE) -This project is licensed under the GNU General Public License v3.0. See the [LICENSE](LICENSE) file for details. +This project is licensed under the Blockscout Software Licence. See the [LICENSE](LICENSE) file for full terms. + +Third-party components included in this repository remain subject to their own licenses. See dependency manifests and bundled third-party notices for component-level license terms. diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index 1d8216ed3343..43aa08cee137 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -5,7 +5,7 @@ "packages": { "": { "name": "blockscout", - "license": "GPL-3.0", + "license": "SEE LICENSE IN ../../../LICENSE", "dependencies": { "@amplitude/analytics-browser": "^2.21.1", "@fortawesome/fontawesome-free": "^6.7.2", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index bf72d727716a..36942a27c53c 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -6,7 +6,7 @@ "private": true, "name": "blockscout", "author": "Blockscout", - "license": "GPL-3.0", + "license": "SEE LICENSE IN ../../../LICENSE", "engines": { "node": ">=16.0.0", "npm": ">=8.0.0" diff --git a/apps/block_scout_web/lib/block_scout_web/chain.ex b/apps/block_scout_web/lib/block_scout_web/chain.ex index e3739db329c1..336504693f2d 100644 --- a/apps/block_scout_web/lib/block_scout_web/chain.ex +++ b/apps/block_scout_web/lib/block_scout_web/chain.ex @@ -137,7 +137,7 @@ defmodule BlockScoutWeb.Chain do end end - @spec next_page_params(any, list(), map(), bool(), (any -> map())) :: nil | map + @spec next_page_params(any(), list(), map(), boolean(), (any() -> map())) :: nil | map() def next_page_params(next_page, list, params, increment_items_count? \\ false, paging_function \\ &paging_params/1) def next_page_params([], _list, _params, _increment_items_count?, _), do: nil @@ -775,22 +775,6 @@ defmodule BlockScoutWeb.Chain do ] end - # Clause for InternalTransaction by block (for backward compatibility): - # returned by `BlockScoutWeb.API.V2.BlockController.internal_transactions/2` (`/api/v2/blocks/:block_hash_or_number/internal-transactions`) - def paging_options(%{"block_index" => index_string}) when is_binary(index_string) do - case Integer.parse(index_string) do - {index, ""} -> - [paging_options: %{@default_paging_options | key: %{block_index: index}}] - - _ -> - [paging_options: @default_paging_options] - end - end - - def paging_options(%{"block_index" => index}) when is_integer(index) do - [paging_options: %{@default_paging_options | key: %{block_index: index}}] - end - # Clause for `Explorer.Chain.Blackfort.Validator`, # returned by `BlockScoutWeb.API.V2.ValidatorController.blackfort_validators_list/2` (`/api/v2/validators/blackfort`) def paging_options(%{ @@ -1315,14 +1299,17 @@ defmodule BlockScoutWeb.Chain do paging_options = Keyword.get(options, :paging_options, @default_paging_options) transaction_hash = Keyword.get(options, :transaction_hash) - necessity_by_association = - %{ - :block => :optional, - [from_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] => :optional, - [to_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] => :optional - } + necessity_by_association = %{block: :optional} + + address_preloads = [ + from_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()], + to_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()] + ] - options_with_necessity = Keyword.put_new(options, :necessity_by_association, necessity_by_association) + options_with_necessity = + options + |> Keyword.put_new(:necessity_by_association, necessity_by_association) + |> Keyword.put_new(:address_preloads, address_preloads) cond do match?(%PagingOptions{key: {0, 0, 0}}, paging_options) or @@ -1343,7 +1330,7 @@ defmodule BlockScoutWeb.Chain do InternalTransactionOnDemand.fetch_latest(options_with_necessity) Application.get_env(:explorer, DeleteZeroValueInternalTransactions)[:enabled] -> - from_db = InternalTransaction.fetch(options) + from_db = InternalTransaction.fetch(options_with_necessity) from_node = if InternalTransactionOnDemand.should_fetch?(from_db, paging_options.page_size) do @@ -1355,7 +1342,7 @@ defmodule BlockScoutWeb.Chain do merge_internal_transactions(from_db, from_node, paging_options.page_size) true -> - InternalTransaction.fetch(options) + InternalTransaction.fetch(options_with_necessity) end end @@ -1397,6 +1384,7 @@ defmodule BlockScoutWeb.Chain do - `block`: The block struct to fetch internal transactions for - `options`: Keyword list with optional keys: - `:necessity_by_association` - associations to preload as required or optional + - `:address_preloads` - addresses to preload with nested associations - `:paging_options` - pagination options including page_size and key - `:type` - filter by transaction type - `:call_type` - filter by call type @@ -1425,6 +1413,7 @@ defmodule BlockScoutWeb.Chain do - `options`: Keyword list with optional keys: - `:paging_options` - pagination options including page_size and key - `:necessity_by_association` - associations to preload as required or optional + - `:address_preloads` - addresses to preload with nested associations ## Returns - List of InternalTransaction structs for the given address diff --git a/apps/block_scout_web/lib/block_scout_web/channels/v2/polygon_zkevm_confirmed_batch_channel.ex b/apps/block_scout_web/lib/block_scout_web/channels/v2/polygon_zkevm_confirmed_batch_channel.ex deleted file mode 100644 index db658015ff00..000000000000 --- a/apps/block_scout_web/lib/block_scout_web/channels/v2/polygon_zkevm_confirmed_batch_channel.ex +++ /dev/null @@ -1,10 +0,0 @@ -defmodule BlockScoutWeb.V2.PolygonZkevmConfirmedBatchChannel do - @moduledoc """ - Establishes pub/sub channel for live updates of zkEVM confirmed batch events for API V2. - """ - use BlockScoutWeb, :channel - - def join("zkevm_batches:new_zkevm_confirmed_batch", _params, socket) do - {:ok, %{}, socket} - end -end diff --git a/apps/block_scout_web/lib/block_scout_web/channels/v2/user_socket.ex b/apps/block_scout_web/lib/block_scout_web/channels/v2/user_socket.ex index dec4a541b74f..49b32e981f10 100644 --- a/apps/block_scout_web/lib/block_scout_web/channels/v2/user_socket.ex +++ b/apps/block_scout_web/lib/block_scout_web/channels/v2/user_socket.ex @@ -12,7 +12,6 @@ defmodule BlockScoutWeb.V2.UserSocket do channel("transactions:*", BlockScoutWeb.V2.TransactionChannel) channel("tokens:*", BlockScoutWeb.V2.TokenChannel) channel("token_instances:*", BlockScoutWeb.TokenInstanceChannel) - channel("zkevm_batches:*", BlockScoutWeb.V2.PolygonZkevmConfirmedBatchChannel) case @chain_type do :arbitrum -> channel("arbitrum:*", BlockScoutWeb.ArbitrumChannel) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_controller.ex index 329b33b9f523..2b5b7c3c1301 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_controller.ex @@ -19,8 +19,9 @@ defmodule BlockScoutWeb.AddressContractController do :names => :optional, [smart_contract: :smart_contract_additional_sources] => :optional, :token => :optional, - Address.contract_creation_transaction_associations() => :optional + Address.contract_creation_transaction_association() => :optional }, + preload_contract_creation_internal_transaction: true, ip: ip ] diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_controller.ex index a9bf0bbbec9e..eced37610628 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_controller.ex @@ -27,14 +27,12 @@ defmodule BlockScoutWeb.AddressController do :filecoin -> @contract_address_preloads [ :smart_contract, - [contract_creation_internal_transaction: :from_address], [contract_creation_transaction: :from_address] ] _ -> @contract_address_preloads [ :smart_contract, - :contract_creation_internal_transaction, :contract_creation_transaction ] end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_internal_transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_internal_transaction_controller.ex index d3444389019d..f4392c9c257c 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_internal_transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_internal_transaction_controller.ex @@ -31,14 +31,11 @@ defmodule BlockScoutWeb.AddressInternalTransactionController do {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params) do full_options = [ - necessity_by_association: %{ - [created_contract_address: :names] => :optional, - [from_address: :names] => :optional, - [to_address: :names] => :optional, - [created_contract_address: :smart_contract] => :optional, - [from_address: :smart_contract] => :optional, - [to_address: :smart_contract] => :optional - } + address_preloads: [ + created_contract_address: [:names, :smart_contract], + from_address: [:names, :smart_contract], + to_address: [:names, :smart_contract] + ] ] |> Keyword.merge(paging_options(params)) |> Keyword.merge(current_filter(params)) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_read_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_read_contract_controller.ex index d9e80a100d76..cea736458559 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_read_contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_read_contract_controller.ex @@ -26,8 +26,9 @@ defmodule BlockScoutWeb.AddressReadContractController do :names => :optional, :smart_contract => :optional, :token => :optional, - Address.contract_creation_transaction_associations() => :optional + Address.contract_creation_transaction_association() => :optional }, + preload_contract_creation_internal_transaction: true, ip: ip ] diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_read_proxy_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_read_proxy_controller.ex index f7c7f57ad139..8a7388986053 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_read_proxy_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_read_proxy_controller.ex @@ -18,8 +18,9 @@ defmodule BlockScoutWeb.AddressReadProxyController do :names => :optional, :smart_contract => :optional, :token => :optional, - Address.contract_creation_transaction_associations() => :optional + Address.contract_creation_transaction_association() => :optional }, + preload_contract_creation_internal_transaction: true, ip: ip ] diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_write_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_write_contract_controller.ex index b0dee0db3054..eceeb79fd46a 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_write_contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_write_contract_controller.ex @@ -25,8 +25,9 @@ defmodule BlockScoutWeb.AddressWriteContractController do :names => :optional, :smart_contract => :optional, :token => :optional, - Address.contract_creation_transaction_associations() => :optional + Address.contract_creation_transaction_association() => :optional }, + preload_contract_creation_internal_transaction: true, ip: ip ] diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_write_proxy_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_write_proxy_controller.ex index 868547c46f32..81eac19671a8 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_write_proxy_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_write_proxy_controller.ex @@ -18,8 +18,9 @@ defmodule BlockScoutWeb.AddressWriteProxyController do :names => :optional, :smart_contract => :optional, :token => :optional, - Address.contract_creation_transaction_associations() => :optional + Address.contract_creation_transaction_association() => :optional }, + preload_contract_creation_internal_transaction: true, ip: ip ] diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/health_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/health_controller.ex index 448a1aeb424a..8bd6184064a6 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/health_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/health_controller.ex @@ -8,7 +8,7 @@ defmodule BlockScoutWeb.API.HealthController do @ok_message "OK" @backfill_multichain_search_db_migration_name "backfill_multichain_search_db" - @rollups [:arbitrum, :zksync, :optimism, :polygon_zkevm, :scroll] + @rollups [:arbitrum, :zksync, :optimism, :scroll] @doc """ Handles health checks for the application. diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/legacy/block_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/legacy/block_controller.ex new file mode 100644 index 000000000000..a2f84c6c0296 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/legacy/block_controller.ex @@ -0,0 +1,73 @@ +defmodule BlockScoutWeb.API.Legacy.BlockController do + use BlockScoutWeb, :controller + use OpenApiSpex.ControllerSpecs + + # aliased with as: to avoid shadowing this module's own name + # (BlockScoutWeb.API.V2.Legacy.BlockController) + alias BlockScoutWeb.API.RPC.BlockController, as: V1BlockController + alias BlockScoutWeb.Schemas.API.Legacy.{Envelope, EthBlockNumberResult, GetBlockNumberByTimeResult} + alias BlockScoutWeb.Schemas.API.V2.General + alias OpenApiSpex.{Parameter, Schema} + + tags(["legacy"]) + + operation :get_block_number_by_time, + summary: "Get block number by time stamp", + description: """ + Returns the block number created closest to a provided timestamp. + + Required: + - `timestamp` + - `closest` + """, + parameters: + [ + %Parameter{ + name: :timestamp, + in: :query, + schema: General.IntegerString, + description: "Unix timestamp in seconds." + }, + %Parameter{ + name: :closest, + in: :query, + schema: %Schema{type: :string, enum: ["before", "after"]}, + description: "Whether to return the block before or after the timestamp." + } + ] ++ General.base_params(), + responses: [ + ok: {"Block number", "application/json", Envelope.rpc_envelope(GetBlockNumberByTimeResult)} + ] + + @doc """ + Thin bridge to the v1 `getblocknobytime` action at `/api?module=block&action=getblocknobytime`. + """ + @spec get_block_number_by_time(Plug.Conn.t(), map()) :: Plug.Conn.t() + def get_block_number_by_time(conn, params), do: V1BlockController.getblocknobytime(conn, params) + + operation :eth_block_number, + summary: "Get the latest block number", + description: """ + Returns the latest block number as a hex-encoded string in a JSON-RPC 2.0 response. + """, + parameters: + [ + %Parameter{ + name: :id, + in: :query, + schema: %Schema{anyOf: [%Schema{type: :integer}, %Schema{type: :string}]}, + description: + "JSON-RPC request id echoed back in the response. " <> + "Defaults to 1 when omitted." + } + ] ++ General.base_params(), + responses: [ + ok: {"Latest block number", "application/json", Envelope.eth_rpc_envelope(EthBlockNumberResult)} + ] + + @doc """ + Thin bridge to the v1 `eth_block_number` action at `/api?module=block&action=eth_block_number`. + """ + @spec eth_block_number(Plug.Conn.t(), map()) :: Plug.Conn.t() + def eth_block_number(conn, params), do: V1BlockController.eth_block_number(conn, params) +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/legacy/logs_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/legacy/logs_controller.ex new file mode 100644 index 000000000000..559710a472c6 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/legacy/logs_controller.ex @@ -0,0 +1,62 @@ +defmodule BlockScoutWeb.API.Legacy.LogsController do + use BlockScoutWeb, :controller + use OpenApiSpex.ControllerSpecs + + alias BlockScoutWeb.API.RPC.LogsController, as: V1LogsController + alias BlockScoutWeb.Schemas.API.Legacy.{Envelope, LogItem} + alias BlockScoutWeb.Schemas.API.V2.General + alias OpenApiSpex.{Parameter, Schema} + + tags(["legacy"]) + + @topic_schema %Schema{type: :string, pattern: ~r/^0x[0-9a-fA-F]{64}$/} + @topic_opr_schema %Schema{type: :string, enum: ["and", "or"]} + + operation :get_logs, + summary: "Get Event Logs by Address and/or Topic(s)", + description: """ + Event logs for an address and topic. Use and/or with the topic operator to specify + topic retrieval options when adding multiple topics. Up to a maximum of 1,000 event logs. + + Required: + - `fromBlock` and `toBlock` + - At least one of `address`, `topic0`, `topic1`, `topic2`, `topic3` + - If any pair of topic parameters is set, the corresponding `topicA_B_opr` is required. + """, + parameters: + [ + %Parameter{ + name: :fromBlock, + in: :query, + schema: %Schema{anyOf: [General.IntegerString, %Schema{type: :string, enum: ["latest"]}]}, + description: "Start block: integer or the sentinel \"latest\"" + }, + %Parameter{ + name: :toBlock, + in: :query, + schema: %Schema{anyOf: [General.IntegerString, %Schema{type: :string, enum: ["latest"]}]}, + description: "End block: integer or the sentinel \"latest\"" + }, + %Parameter{name: :address, in: :query, schema: General.AddressHash}, + %Parameter{name: :topic0, in: :query, schema: @topic_schema}, + %Parameter{name: :topic1, in: :query, schema: @topic_schema}, + %Parameter{name: :topic2, in: :query, schema: @topic_schema}, + %Parameter{name: :topic3, in: :query, schema: @topic_schema}, + %Parameter{name: :topic0_1_opr, in: :query, schema: @topic_opr_schema}, + %Parameter{name: :topic0_2_opr, in: :query, schema: @topic_opr_schema}, + %Parameter{name: :topic0_3_opr, in: :query, schema: @topic_opr_schema}, + %Parameter{name: :topic1_2_opr, in: :query, schema: @topic_opr_schema}, + %Parameter{name: :topic1_3_opr, in: :query, schema: @topic_opr_schema}, + %Parameter{name: :topic2_3_opr, in: :query, schema: @topic_opr_schema} + ] ++ General.base_params(), + responses: [ + ok: + {"Event logs", "application/json", Envelope.rpc_envelope(%Schema{type: :array, items: LogItem, nullable: true})} + ] + + @doc """ + Thin bridge to the v1 `getlogs` action at `/api?module=logs&action=getlogs`. + """ + @spec get_logs(Plug.Conn.t(), map()) :: Plug.Conn.t() + def get_logs(conn, params), do: V1LogsController.getlogs(conn, params) +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex index f97acb3256e2..f81d78bf2bd0 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/contract_controller.ex @@ -50,18 +50,10 @@ defmodule BlockScoutWeb.API.RPC.ContractController do |> Enum.map(fn address_hash_string -> case validate_address(address_hash_string, params) do {:ok, _address_hash, address} -> - contract_creation_internal_transaction_with_transaction_association = [ - contract_creation_internal_transaction: { - Address.contract_creation_internal_transaction_preload_query(), - :transaction - } - ] - Address.maybe_preload_smart_contract_associations( address, [ - Address.contract_creation_transaction_association(), - contract_creation_internal_transaction_with_transaction_association + Address.contract_creation_transaction_association() ], @api_true ) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex index 77c2a4838eb9..11e2c7b07c6d 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex @@ -27,7 +27,14 @@ defmodule BlockScoutWeb.API.V2.AddressController do import Explorer.Helper, only: [safe_parse_non_negative_integer: 1] - import Explorer.MicroserviceInterfaces.BENS, only: [maybe_preload_ens: 1, maybe_preload_ens_to_address: 1] + import Explorer.MicroserviceInterfaces.BENS, + only: [ + maybe_preload_ens: 1, + maybe_preload_ens_for_token_transfers: 1, + maybe_preload_ens_for_transactions: 1, + maybe_preload_ens_to_address: 1 + ] + import Explorer.MicroserviceInterfaces.Metadata, only: [maybe_preload_metadata: 1] import Explorer.Chain.Address.Reputation, only: [reputation_association: 0] @@ -66,19 +73,6 @@ defmodule BlockScoutWeb.API.V2.AddressController do @chain_type_transaction_necessity_by_association %{} end - @transaction_necessity_by_association [ - necessity_by_association: - %{ - [created_contract_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] => - :optional, - [from_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] => :optional, - [to_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] => :optional, - :block => :optional - } - |> Map.merge(@chain_type_transaction_necessity_by_association), - api?: true - ] - @token_transfer_necessity_by_association [ necessity_by_association: %{ [to_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] => :optional, @@ -170,12 +164,10 @@ defmodule BlockScoutWeb.API.V2.AddressController do @spec contract_address_preloads() :: [keyword()] defp contract_address_preloads do - include_internal_tx = include_internal_transaction_association?() - chain_type_associations = case chain_type() do - :filecoin -> Address.contract_creation_transaction_with_from_address_associations(include_internal_tx) - _ -> Address.contract_creation_transaction_associations(include_internal_tx) + :filecoin -> [Address.contract_creation_transaction_with_from_address_association()] + _ -> [Address.contract_creation_transaction_association()] end [:smart_contract | chain_type_associations] @@ -422,7 +414,8 @@ defmodule BlockScoutWeb.API.V2.AddressController do case Chain.hash_to_address(address_hash, hash_to_address_options(@address_options)) do {:ok, _address} -> options = - @transaction_necessity_by_association + [necessity_by_association: address_transactions_necessity_by_association()] + |> Keyword.merge(@api_true) |> Keyword.merge(paging_options(params)) |> Keyword.merge(current_filter(params)) |> Keyword.merge(address_transactions_sorting(params)) @@ -443,7 +436,7 @@ defmodule BlockScoutWeb.API.V2.AddressController do |> put_status(200) |> put_view(TransactionView) |> render(:transactions, %{ - transactions: transactions |> maybe_preload_ens() |> maybe_preload_metadata(), + transactions: transactions |> maybe_preload_ens_for_transactions() |> maybe_preload_metadata(), next_page_params: next_page_params }) @@ -459,6 +452,34 @@ defmodule BlockScoutWeb.API.V2.AddressController do end end + defp address_transactions_necessity_by_association do + %{ + [ + created_contract_address: [ + :scam_badge, + :names, + proxy_implementations_association() + ] + ] => :optional, + [ + from_address: [ + :scam_badge, + :names, + proxy_implementations_association() + ] + ] => :optional, + [ + to_address: [ + :scam_badge, + :names, + proxy_implementations_association() + ] + ] => :optional, + :block => :optional + } + |> Map.merge(@chain_type_transaction_necessity_by_association) + end + operation :token_transfers, summary: "List token transfers involving a specific address with filtering options", description: @@ -543,7 +564,10 @@ defmodule BlockScoutWeb.API.V2.AddressController do |> put_view(TransactionView) |> render(:token_transfers, %{ token_transfers: - token_transfers |> Instance.preload_nft(@api_true) |> maybe_preload_ens() |> maybe_preload_metadata(), + token_transfers + |> Instance.preload_nft(@api_true) + |> maybe_preload_ens_for_token_transfers() + |> maybe_preload_metadata(), next_page_params: next_page_params }) @@ -604,13 +628,11 @@ defmodule BlockScoutWeb.API.V2.AddressController do {:ok, _address} -> full_options = [ - necessity_by_association: %{ - [created_contract_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] => - :optional, - [from_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] => - :optional, - [to_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] => :optional - } + address_preloads: [ + created_contract_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()], + from_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()], + to_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()] + ] ] |> Keyword.merge(paging_options(params)) |> Keyword.merge(current_filter(params)) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/advanced_filter_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/advanced_filter_controller.ex index e2dba447805a..4a8a5478c6b8 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/advanced_filter_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/advanced_filter_controller.ex @@ -5,12 +5,18 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterController do import BlockScoutWeb.Chain, only: [split_list_by_page: 1, next_page_params: 5, fetch_scam_token_toggle: 2] import Explorer.PagingOptions, only: [default_paging_options: 0] - alias BlockScoutWeb.API.V2.{AdvancedFilterView, CsvExportController} + alias BlockScoutWeb.AccessHelper + alias BlockScoutWeb.API.V2.CsvExportController alias Explorer.{Chain, PagingOptions} alias Explorer.Chain.{Address.Reputation, AdvancedFilter, ContractMethod, Data, Token, Transaction} + alias Explorer.Chain.CsvExport.AdvancedFilter, as: CsvExportAdvancedFilter + alias Explorer.Chain.CsvExport.AsyncHelper, as: AsyncCsvHelper alias Explorer.Chain.CsvExport.Helper, as: CsvHelper + alias Explorer.Chain.CsvExport.Request, as: AsyncCsvExportRequest alias Plug.Conn + require Logger + action_fallback(BlockScoutWeb.API.V2.FallbackController) @api_true [api?: true] @@ -89,27 +95,49 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterController do """ @spec list_csv(Plug.Conn.t(), map()) :: Plug.Conn.t() def list_csv(conn, params) do - full_options = - params - |> extract_filters() - |> Keyword.merge(paging_options(params)) - |> Keyword.update(:paging_options, %PagingOptions{page_size: CsvHelper.limit()}, fn %PagingOptions{} = - paging_options -> + full_options = build_csv_export_options(params) + + if CsvHelper.async_enabled?() do + handle_async_csv_export(conn, full_options) + else + stream_csv_to_conn(conn, CsvExportAdvancedFilter.export(full_options)) + end + end + + defp build_csv_export_options(params) do + [] + |> Keyword.merge(extract_filters(params)) + |> Keyword.merge(paging_options(params)) + |> Keyword.update(:paging_options, %PagingOptions{page_size: CsvHelper.limit()}, fn + %PagingOptions{} = paging_options -> %PagingOptions{paging_options | page_size: CsvHelper.limit()} - end) - |> Keyword.put(:timeout, :timer.minutes(5)) + end) + end - full_options - |> AdvancedFilter.list() - |> AdvancedFilterView.to_csv_format() - |> CsvHelper.dump_to_stream() + defp handle_async_csv_export(conn, full_options) do + case AsyncCsvExportRequest.create(AccessHelper.conn_to_ip_string(conn), %{ + advanced_filters_params: full_options |> :erlang.term_to_binary() |> Base.encode64() + }) do + {:ok, request} -> + conn |> put_status(:accepted) |> json(%{request_id: request.id}) + + {:error, :too_many_pending_requests} -> + conn + |> put_status(:conflict) + |> json(%{error: "You can only have #{AsyncCsvHelper.max_pending_tasks_per_ip()} pending requests at a time"}) + + {:error, error} -> + Logger.error("Failed to create CSV export request: #{inspect(error)}") + conn |> put_status(:internal_server_error) |> json(%{error: "Failed to create CSV export request"}) + end + end + + defp stream_csv_to_conn(conn, stream) do + stream |> Enum.reduce_while(CsvExportController.put_resp_params(conn), fn chunk, conn -> case Conn.chunk(conn, chunk) do - {:ok, conn} -> - {:cont, conn} - - {:error, :closed} -> - {:halt, conn} + {:ok, conn} -> {:cont, conn} + {:error, :closed} -> {:halt, conn} end end) end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/block_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/block_controller.ex index f7485da39035..cf413eae5c66 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/block_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/block_controller.ex @@ -27,7 +27,9 @@ defmodule BlockScoutWeb.API.V2.BlockController do internal_transaction_call_type_options: 1 ] - import Explorer.MicroserviceInterfaces.BENS, only: [maybe_preload_ens: 1] + import Explorer.MicroserviceInterfaces.BENS, + only: [maybe_preload_ens: 1, maybe_preload_ens_for_blocks: 1, maybe_preload_ens_for_transactions: 1] + import Explorer.MicroserviceInterfaces.Metadata, only: [maybe_preload_metadata: 1] import Explorer.Chain.Address.Reputation, only: [reputation_association: 0] @@ -101,25 +103,12 @@ defmodule BlockScoutWeb.API.V2.BlockController do @chain_type_block_necessity_by_association %{} end - @transaction_necessity_by_association [ - necessity_by_association: - %{ - [created_contract_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] => - :optional, - [from_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] => :optional, - [to_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] => :optional, - :block => :optional - } - |> Map.merge(@chain_type_transaction_necessity_by_association) - ] - - @internal_transaction_necessity_by_association [ - necessity_by_association: %{ - [created_contract_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] => - :optional, - [from_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] => :optional, - [to_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] => :optional - } + @internal_transaction_address_preloads [ + address_preloads: [ + created_contract_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()], + from_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()], + to_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()] + ] ] @api_true [api?: true] @@ -224,7 +213,7 @@ defmodule BlockScoutWeb.API.V2.BlockController do conn |> put_status(200) |> render(:blocks, %{ - blocks: blocks |> maybe_preload_ens() |> maybe_preload_metadata(), + blocks: blocks |> maybe_preload_ens_for_blocks() |> maybe_preload_metadata(), next_page_params: next_page_params }) end @@ -271,7 +260,7 @@ defmodule BlockScoutWeb.API.V2.BlockController do conn |> put_status(200) |> render(:blocks, %{ - blocks: blocks |> maybe_preload_ens() |> maybe_preload_metadata(), + blocks: blocks |> maybe_preload_ens_for_blocks() |> maybe_preload_metadata(), next_page_params: next_page_params }) end @@ -319,7 +308,7 @@ defmodule BlockScoutWeb.API.V2.BlockController do conn |> put_status(200) |> render(:blocks, %{ - blocks: blocks |> maybe_preload_ens() |> maybe_preload_metadata(), + blocks: blocks |> maybe_preload_ens_for_blocks() |> maybe_preload_metadata(), next_page_params: next_page_params }) end @@ -367,7 +356,7 @@ defmodule BlockScoutWeb.API.V2.BlockController do conn |> put_status(200) |> render(:blocks, %{ - blocks: blocks |> maybe_preload_ens() |> maybe_preload_metadata(), + blocks: blocks |> maybe_preload_ens_for_blocks() |> maybe_preload_metadata(), next_page_params: next_page_params }) end @@ -404,7 +393,7 @@ defmodule BlockScoutWeb.API.V2.BlockController do def transactions(conn, %{block_hash_or_number_param: block_hash_or_number} = params) do with {:ok, block} <- block_param_to_block(block_hash_or_number) do full_options = - @transaction_necessity_by_association + transaction_necessity_by_association() |> Keyword.merge(put_key_value_to_paging_options(paging_options(params), :is_index_in_asc_order, true)) |> Keyword.merge(type_filter_options(params)) |> Keyword.merge(@api_true) @@ -421,7 +410,7 @@ defmodule BlockScoutWeb.API.V2.BlockController do |> put_status(200) |> put_view(TransactionView) |> render(:transactions, %{ - transactions: transactions |> maybe_preload_ens() |> maybe_preload_metadata(), + transactions: transactions |> maybe_preload_ens_for_transactions() |> maybe_preload_metadata(), next_page_params: next_page_params }) end @@ -464,7 +453,7 @@ defmodule BlockScoutWeb.API.V2.BlockController do def internal_transactions(conn, %{block_hash_or_number_param: block_hash_or_number} = params) do with {:ok, block} <- block_param_to_block(block_hash_or_number) do full_options = - @internal_transaction_necessity_by_association + @internal_transaction_address_preloads |> Keyword.merge(paging_options(params)) |> Keyword.merge(@api_true) |> Keyword.merge(internal_transaction_type_options(params)) @@ -686,6 +675,39 @@ defmodule BlockScoutWeb.API.V2.BlockController do end end + defp transaction_necessity_by_association do + [ + necessity_by_association: + Map.merge( + %{ + [ + created_contract_address: [ + :scam_badge, + :names, + proxy_implementations_association() + ] + ] => :optional, + [ + from_address: [ + :scam_badge, + :names, + proxy_implementations_association() + ] + ] => :optional, + [ + to_address: [ + :scam_badge, + :names, + proxy_implementations_association() + ] + ] => :optional, + :block => :optional + }, + @chain_type_transaction_necessity_by_association + ) + ] + end + defp block_param_to_block(block_hash_or_number, options \\ @api_true) do with {:ok, type, value} <- parse_block_hash_or_number_param(block_hash_or_number) do fetch_block(type, value, options) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/celo_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/celo_controller.ex index 98a2724d5922..8cc418158b61 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/celo_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/celo_controller.ex @@ -1,5 +1,6 @@ defmodule BlockScoutWeb.API.V2.CeloController do use BlockScoutWeb, :controller + use OpenApiSpex.ControllerSpecs import Explorer.Helper, only: [safe_parse_non_negative_integer: 1] @@ -11,19 +12,47 @@ defmodule BlockScoutWeb.API.V2.CeloController do import Explorer.PagingOptions, only: [default_paging_options: 0] + alias BlockScoutWeb.Schemas.API.V2.ErrorResponses.NotFoundResponse alias Explorer.Chain.Celo.{AggregatedElectionReward, ElectionReward, Epoch} alias Explorer.Chain.Hash alias Explorer.PagingOptions + @celo_reward_types ElectionReward.types() + action_fallback(BlockScoutWeb.API.V2.FallbackController) + plug(OpenApiSpex.Plug.CastAndValidate, json_render_error_v2: true) + + tags(["celo"]) + + operation :epochs, + summary: "List Celo epochs.", + description: "Retrieves a paginated list of Celo epochs.", + parameters: + base_params() ++ + define_paging_params([ + "number" + ]), + responses: [ + ok: + {"List of Celo epochs.", "application/json", + paginated_response( + items: Schemas.Celo.Epoch, + next_page_params_example: %{ + "number" => 100 + }, + title_prefix: "CeloEpochs" + )}, + unprocessable_entity: JsonErrorResponse.response() + ] + @doc """ Handles GET requests to `/api/v2/celo/epochs` endpoint. """ @spec epochs(Plug.Conn.t(), map()) :: Plug.Conn.t() def epochs(conn, params) do paging_options = - with {:ok, number_string} <- Map.fetch(params, "number"), + with {:ok, number_string} <- Map.fetch(params, :number), {:ok, number} <- parse_epoch_number(number_string) do %{default_paging_options() | key: %{number: number}} else @@ -46,7 +75,7 @@ defmodule BlockScoutWeb.API.V2.CeloController do filtered_params = params - |> Map.drop(["number"]) + |> Map.drop([:number]) next_page_params = next_page_params( @@ -64,11 +93,30 @@ defmodule BlockScoutWeb.API.V2.CeloController do }) end + operation :epoch, + summary: "Get Celo epoch details.", + description: "Retrieves detailed information about a Celo epoch.", + parameters: [ + %OpenApiSpex.Parameter{ + name: :number, + in: :path, + schema: Schemas.General.IntegerString, + required: true, + description: "Epoch number in the path." + } + | base_params() + ], + responses: [ + ok: {"Celo epoch details.", "application/json", Schemas.Celo.Epoch.Detailed}, + unprocessable_entity: JsonErrorResponse.response(), + not_found: NotFoundResponse.response() + ] + @doc """ Handles GET requests to `/api/v2/celo/epochs/:number` endpoint. """ @spec epoch(Plug.Conn.t(), map()) :: Plug.Conn.t() - def epoch(conn, %{"number" => number_string}) do + def epoch(conn, %{number: number_string}) do options = [ necessity_by_association: %{ :distribution => :optional, @@ -90,12 +138,53 @@ defmodule BlockScoutWeb.API.V2.CeloController do end end + operation :election_rewards, + summary: "List Celo epoch election rewards.", + description: "Retrieves a paginated list of election rewards for a Celo epoch and reward type.", + parameters: + [ + %OpenApiSpex.Parameter{ + name: :number, + in: :path, + schema: Schemas.General.IntegerString, + required: true, + description: "Epoch number in the path." + }, + %OpenApiSpex.Parameter{ + name: :type, + in: :path, + schema: Schemas.Celo.ElectionReward.Type, + required: true, + description: "Reward type in the path." + } + | base_params() + ] ++ + define_paging_params([ + "amount", + "account_address_hash", + "associated_account_address_hash" + ]), + responses: [ + ok: + {"Election rewards for the specified Celo epoch.", "application/json", + paginated_response( + items: Schemas.Celo.ElectionReward, + next_page_params_example: %{ + "amount" => "1000000000000000000", + "account_address_hash" => "0x1234567890123456789012345678901234567890", + "associated_account_address_hash" => "0x0987654321098765432109876543210987654321" + }, + title_prefix: "CeloEpochElectionRewards" + )}, + unprocessable_entity: JsonErrorResponse.response() + ] + @doc """ Handles GET requests to `/api/v2/celo/epochs/:number/election-rewards/:type` endpoint. """ @spec election_rewards(Plug.Conn.t(), map()) :: Plug.Conn.t() - def election_rewards(conn, %{"number" => epoch_number_string, "type" => reward_type} = params) do + def election_rewards(conn, %{number: epoch_number_string, type: reward_type} = params) do with {:ok, number} <- parse_epoch_number(epoch_number_string), {:ok, reward_type_atom} <- parse_celo_reward_type(reward_type) do address_associations = [:names, :smart_contract, proxy_implementations_association()] @@ -121,11 +210,11 @@ defmodule BlockScoutWeb.API.V2.CeloController do filtered_params = params |> Map.drop([ - "number", - "type", - "amount", - "account_address_hash", - "associated_account_address_hash" + :number, + :type, + :amount, + :account_address_hash, + :associated_account_address_hash ]) next_page_params = @@ -152,9 +241,9 @@ defmodule BlockScoutWeb.API.V2.CeloController do @spec election_rewards_paging_options(map()) :: PagingOptions.t() defp election_rewards_paging_options(params) do with %{ - "amount" => amount_string, - "account_address_hash" => account_address_hash_string, - "associated_account_address_hash" => associated_account_address_hash_string + amount: amount_string, + account_address_hash: account_address_hash_string, + associated_account_address_hash: associated_account_address_hash_string } when is_binary(amount_string) and is_binary(account_address_hash_string) and @@ -186,9 +275,28 @@ defmodule BlockScoutWeb.API.V2.CeloController do end end - @spec parse_celo_reward_type(String.t()) :: + # Parses a reward type value produced by CastAndValidate. + # + # The OpenAPI schema enum (see `ElectionReward.type_enum_with_legacy/0`) + # contains both atoms (:voter, :validator, :group, :delegated_payment) + # and a legacy hyphenated string ("delegated-payment"). CastAndValidate + # returns an atom when the URL segment matches `to_string(atom)`, but + # passes "delegated-payment" through as a string because + # `to_string(:delegated_payment)` is "delegated_payment" (underscore), + # which does not match the hyphenated URL form. + # + # The atom clause handles the canonical types; the string clause handles + # the legacy "delegated-payment" form via `type_from_url_string/1`. + # + # Once the legacy form is removed from the enum, the string clause and + # catch-all can be deleted. + @spec parse_celo_reward_type(atom() | String.t()) :: {:ok, ElectionReward.type()} | {:error, {:invalid, :celo_election_reward_type}} - defp parse_celo_reward_type(reward_type_string) do + defp parse_celo_reward_type(reward_type) when reward_type in @celo_reward_types do + {:ok, reward_type} + end + + defp parse_celo_reward_type(reward_type_string) when is_binary(reward_type_string) do reward_type_string |> ElectionReward.type_from_url_string() |> case do @@ -196,4 +304,6 @@ defmodule BlockScoutWeb.API.V2.CeloController do :error -> {:error, {:invalid, :celo_election_reward_type}} end end + + defp parse_celo_reward_type(_), do: {:error, {:invalid, :celo_election_reward_type}} end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/config_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/config_controller.ex index efcf7d93ab00..238033f9acb8 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/config_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/config_controller.ex @@ -4,6 +4,7 @@ defmodule BlockScoutWeb.API.V2.ConfigController do use Utils.RuntimeEnvHelper, chain_type: [:explorer, :chain_type] + alias Explorer.Chain.CsvExport.Helper, as: CsvHelper alias Explorer.Chain.SmartContract alias Explorer.Migrator.MigrationStatus alias OpenApiSpex.Schema @@ -79,16 +80,17 @@ defmodule BlockScoutWeb.API.V2.ConfigController do responses: [ ok: {"CSV export limits.", "application/json", - %Schema{type: :object, properties: %{limit: %Schema{type: :integer}}}}, + %Schema{type: :object, properties: %{limit: %Schema{type: :integer}, async_enabled: %Schema{type: :boolean}}}}, unprocessable_entity: JsonErrorResponse.response() ] def csv_export(conn, _params) do - limit = Application.get_env(:explorer, :csv_export_limit) + limit = CsvHelper.limit() + async_enabled? = CsvHelper.async_enabled?() conn |> put_status(200) - |> json(%{limit: limit}) + |> json(%{limit: limit, async_enabled: async_enabled?}) end operation :indexer, diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/csv_export_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/csv_export_controller.ex index ca0366b22f73..5b6f6e87515c 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/csv_export_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/csv_export_controller.ex @@ -7,19 +7,20 @@ defmodule BlockScoutWeb.API.V2.CsvExportController do alias BlockScoutWeb.Schemas.API.V2.ErrorResponses.NotFoundResponse alias Explorer.Chain alias Explorer.Chain.Address - alias Explorer.Chain.Address.CurrentTokenBalance + alias Explorer.Chain.CsvExport.Address.Celo.ElectionRewards, as: AddressCeloElectionRewardsCsvExporter alias Explorer.Chain.CsvExport.Address.Logs, as: AddressLogsCsvExporter alias Explorer.Chain.CsvExport.Address.TokenTransfers, as: AddressTokenTransfersCsvExporter alias Explorer.Chain.CsvExport.Address.Transactions, as: AddressTransactionsCsvExporter - - alias Explorer.Chain.CsvExport.Address.Celo.ElectionRewards, - as: AddressCeloElectionRewardsCsvExporter - + alias Explorer.Chain.CsvExport.AsyncHelper, as: AsyncCsvHelper alias Explorer.Chain.CsvExport.Helper, as: CsvHelper + alias Explorer.Chain.CsvExport.Request, as: AsyncCsvExportRequest + alias Explorer.Chain.CsvExport.Token.Holders, as: TokenHoldersCsvExporter alias Plug.Conn import BlockScoutWeb.Chain, only: [fetch_scam_token_toggle: 2] + require Logger + action_fallback(BlockScoutWeb.API.V2.FallbackController) plug(OpenApiSpex.Plug.CastAndValidate, json_render_error_v2: true) @@ -33,8 +34,8 @@ defmodule BlockScoutWeb.API.V2.CsvExportController do base_params() ++ [ address_hash_param(), - from_period_param(), - to_period_param(), + optional_from_period_param(), + optional_to_period_param(), filter_type_param(), filter_value_param() ], @@ -53,29 +54,25 @@ defmodule BlockScoutWeb.API.V2.CsvExportController do def export_token_holders(conn, %{address_hash_param: address_hash_string} = params) do with {:format, {:ok, address_hash}} <- {:format, Chain.string_to_address_hash(address_hash_string)}, {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params), - {:not_found, {:ok, token}} <- {:not_found, Chain.token_from_address_hash(address_hash, @api_true)} do - token_holders = Chain.fetch_token_holders_from_token_hash_for_csv(address_hash, options()) - - token_holders - |> CurrentTokenBalance.to_csv_format(token) - |> CsvHelper.dump_to_stream() - |> stream_csv_chunks(conn) + {:not_found, {:ok, _token}} <- {:not_found, Chain.token_from_address_hash(address_hash, @api_true)} do + opts = %{ + address_hash: address_hash, + from_period: nil, + to_period: nil, + filter_type: nil, + filter_value: nil, + show_scam_tokens?: nil + } + + do_csv_export( + CsvHelper.async_enabled?(), + conn, + opts, + TokenHoldersCsvExporter + ) end end - defp stream_csv_chunks(csv_stream, conn) do - csv_stream - |> Enum.reduce_while(put_resp_params(conn), fn chunk, conn -> - case Conn.chunk(conn, chunk) do - {:ok, conn} -> - {:cont, conn} - - {:error, :closed} -> - {:halt, conn} - end - end) - end - @spec put_resp_params(Conn.t()) :: Conn.t() def put_resp_params(conn) do conn @@ -85,21 +82,27 @@ defmodule BlockScoutWeb.API.V2.CsvExportController do |> send_chunked(200) end - defp options, do: [paging_options: CsvHelper.paging_options(), api?: true] - defp items_csv( conn, %{ address_hash_param: address_hash_string, - from_period: from_period, - to_period: to_period + from_period: _from_period, + to_period: _to_period } = params, csv_export_module ) when is_binary(address_hash_string) do with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), {:address_exists, true} <- {:address_exists, Address.address_exists?(address_hash)} do - stream_address_export(address_hash, csv_export_module, from_period, to_period, params, conn) + do_csv_export( + CsvHelper.async_enabled?(), + conn, + Map.merge(params, %{ + address_hash: address_hash, + show_scam_tokens?: fetch_scam_token_toggle([], conn)[:show_scam_tokens?] + }), + csv_export_module + ) else :error -> unprocessable_entity(conn) @@ -111,12 +114,39 @@ defmodule BlockScoutWeb.API.V2.CsvExportController do defp items_csv(conn, _, _), do: not_found(conn) - defp stream_address_export(address_hash, csv_export_module, from_period, to_period, params, conn) do - filter_type = Map.get(params, :filter_type) - filter_value = Map.get(params, :filter_value) + @spec do_csv_export(boolean(), Conn.t(), map(), module()) :: Conn.t() + defp do_csv_export(async?, conn, params, csv_export_module) + + defp do_csv_export(true, conn, params, csv_export_module) do + params = + params + |> Map.take([:address_hash, :from_period, :to_period, :filter_type, :filter_value, :show_scam_tokens?]) + |> Map.put(:module, to_string(csv_export_module)) + + case AsyncCsvExportRequest.create(AccessHelper.conn_to_ip_string(conn), params) do + {:ok, request} -> + conn |> put_status(:accepted) |> json(%{request_id: request.id}) + + {:error, :too_many_pending_requests} -> + conn + |> put_status(:conflict) + |> json(%{error: "You can only have #{AsyncCsvHelper.max_pending_tasks_per_ip()} pending requests at a time"}) - address_hash - |> csv_export_module.export(from_period, to_period, fetch_scam_token_toggle([], conn), filter_type, filter_value) + {:error, error} -> + Logger.error("Failed to create CSV export request: #{inspect(error)}") + conn |> put_status(:internal_server_error) |> json(%{error: "Failed to create CSV export request"}) + end + end + + defp do_csv_export(false, conn, params, csv_export_module) do + params[:address_hash] + |> csv_export_module.export( + params[:from_period], + params[:to_period], + [show_scam_tokens?: params[:show_scam_tokens?]], + params[:filter_type], + params[:filter_value] + ) |> Enum.reduce_while(put_resp_params(conn), fn chunk, conn -> case Conn.chunk(conn, chunk) do {:ok, conn} -> @@ -319,4 +349,26 @@ defmodule BlockScoutWeb.API.V2.CsvExportController do def celo_election_rewards_csv(conn, params) do items_csv(conn, params, AddressCeloElectionRewardsCsvExporter) end + + operation :get_csv_export, + summary: "Get CSV export", + description: "Gets a CSV export by UUID", + parameters: [uuid_param() | base_params()], + responses: [ + ok: {"Status of CSV export.", "application/json", Schemas.CSVExport.Response}, + not_found: NotFoundResponse.response() + ], + tags: ["csv-export"] + + @doc """ + Gets a CSV export by UUID. + """ + @spec get_csv_export(Conn.t(), map()) :: Conn.t() + def get_csv_export(conn, %{uuid_param: uuid}) do + with {:not_found, request} when not is_nil(request) <- + {:not_found, + uuid |> AsyncCsvExportRequest.get_by_uuid(api?: true) |> AsyncCsvHelper.actualize_csv_export_request()} do + conn |> put_status(200) |> render(:csv_export, %{request: request}) + end + end end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/internal_transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/internal_transaction_controller.ex index c109202162b5..f571f821b67e 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/internal_transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/internal_transaction_controller.ex @@ -20,7 +20,7 @@ defmodule BlockScoutWeb.API.V2.InternalTransactionController do plug(OpenApiSpex.Plug.CastAndValidate, json_render_error_v2: true) - tags(["internal_transactions"]) + tags(["internal-transactions"]) @api_true [api?: true] diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/main_page_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/main_page_controller.ex index e4aa7618233c..f09761cd09d8 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/main_page_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/main_page_controller.ex @@ -12,6 +12,10 @@ defmodule BlockScoutWeb.API.V2.MainPageController do alias Explorer.Chain.Transaction import BlockScoutWeb.Account.AuthController, only: [current_user: 1] + + import Explorer.MicroserviceInterfaces.BENS, + only: [maybe_preload_ens_for_blocks: 1, maybe_preload_ens_for_transactions: 1] + import Explorer.MicroserviceInterfaces.Metadata, only: [maybe_preload_metadata: 1] import Explorer.Chain.Address.Reputation, only: [reputation_association: 0] @@ -43,7 +47,7 @@ defmodule BlockScoutWeb.API.V2.MainPageController do plug(OpenApiSpex.Plug.CastAndValidate, json_render_error_v2: true) - tags(["main_page"]) + tags(["main-page"]) operation :blocks, summary: "Retrieve recent blocks as displayed on Blockscout homepage", @@ -76,7 +80,7 @@ defmodule BlockScoutWeb.API.V2.MainPageController do conn |> put_status(200) |> put_view(BlockView) - |> render(:blocks, %{blocks: blocks |> maybe_preload_metadata()}) + |> render(:blocks, %{blocks: blocks |> maybe_preload_ens_for_blocks() |> maybe_preload_metadata()}) end operation :transactions, @@ -104,7 +108,9 @@ defmodule BlockScoutWeb.API.V2.MainPageController do conn |> put_status(200) |> put_view(TransactionView) - |> render(:transactions, %{transactions: recent_transactions |> maybe_preload_metadata()}) + |> render(:transactions, %{ + transactions: recent_transactions |> maybe_preload_ens_for_transactions() |> maybe_preload_metadata() + }) end operation :watchlist_transactions, @@ -135,7 +141,7 @@ defmodule BlockScoutWeb.API.V2.MainPageController do |> put_status(200) |> put_view(TransactionView) |> render(:transactions_watchlist, %{ - transactions: transactions |> maybe_preload_metadata(), + transactions: transactions |> maybe_preload_ens_for_transactions() |> maybe_preload_metadata(), watchlist_names: watchlist_names }) end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/polygon_zkevm_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/polygon_zkevm_controller.ex deleted file mode 100644 index e01b9a7caf9c..000000000000 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/polygon_zkevm_controller.ex +++ /dev/null @@ -1,180 +0,0 @@ -defmodule BlockScoutWeb.API.V2.PolygonZkevmController do - use BlockScoutWeb, :controller - - import BlockScoutWeb.Chain, - only: [ - next_page_params: 3, - paging_options: 1, - split_list_by_page: 1 - ] - - alias Explorer.Chain.PolygonZkevm.Reader - - action_fallback(BlockScoutWeb.API.V2.FallbackController) - - @batch_necessity_by_association %{ - :sequence_transaction => :optional, - :verify_transaction => :optional, - :l2_transactions => :optional - } - - @batches_necessity_by_association %{ - :sequence_transaction => :optional, - :verify_transaction => :optional - } - - @doc """ - Function to handle GET requests to `/api/v2/zkevm/batches/:batch_number` endpoint. - """ - @spec batch(Plug.Conn.t(), map()) :: Plug.Conn.t() - def batch(conn, %{"batch_number" => batch_number} = _params) do - case Reader.batch( - batch_number, - necessity_by_association: @batch_necessity_by_association, - api?: true - ) do - {:ok, batch} -> - conn - |> put_status(200) - |> render(:zkevm_batch, %{batch: batch}) - - {:error, :not_found} = res -> - res - end - end - - @doc """ - Function to handle GET requests to `/api/v2/main-page/zkevm/batches/latest-number` endpoint. - """ - @spec batch_latest_number(Plug.Conn.t(), map()) :: Plug.Conn.t() - def batch_latest_number(conn, _params) do - conn - |> put_status(200) - |> render(:zkevm_batch_latest_number, %{number: batch_latest_number()}) - end - - @doc """ - Function to handle GET requests to `/api/v2/zkevm/batches` endpoint. - """ - @spec batches(Plug.Conn.t(), map()) :: Plug.Conn.t() - def batches(conn, params) do - {batches, next_page} = - params - |> paging_options() - |> Keyword.put(:necessity_by_association, @batches_necessity_by_association) - |> Keyword.put(:api?, true) - |> Reader.batches() - |> split_list_by_page() - - next_page_params = next_page_params(next_page, batches, params) - - conn - |> put_status(200) - |> render(:zkevm_batches, %{ - batches: batches, - next_page_params: next_page_params - }) - end - - @doc """ - Function to handle GET requests to `/api/v2/zkevm/batches/count` endpoint. - """ - @spec batches_count(Plug.Conn.t(), map()) :: Plug.Conn.t() - def batches_count(conn, _params) do - conn - |> put_status(200) - |> render(:zkevm_batches_count, %{count: batch_latest_number()}) - end - - @doc """ - Function to handle GET requests to `/api/v2/main-page/zkevm/batches/confirmed` endpoint. - """ - @spec batches_confirmed(Plug.Conn.t(), map()) :: Plug.Conn.t() - def batches_confirmed(conn, _params) do - batches = - [] - |> Keyword.put(:necessity_by_association, @batches_necessity_by_association) - |> Keyword.put(:api?, true) - |> Keyword.put(:confirmed?, true) - |> Reader.batches() - - conn - |> put_status(200) - |> render(:zkevm_batches, %{batches: batches}) - end - - defp batch_latest_number do - case Reader.batch(:latest, api?: true) do - {:ok, batch} -> batch.number - {:error, :not_found} -> 0 - end - end - - @doc """ - Function to handle GET requests to `/api/v2/zkevm/deposits` endpoint. - """ - @spec deposits(Plug.Conn.t(), map()) :: Plug.Conn.t() - def deposits(conn, params) do - {deposits, next_page} = - params - |> paging_options() - |> Keyword.put(:api?, true) - |> Reader.deposits() - |> split_list_by_page() - - next_page_params = next_page_params(next_page, deposits, params) - - conn - |> put_status(200) - |> render(:polygon_zkevm_bridge_items, %{ - items: deposits, - next_page_params: next_page_params - }) - end - - @doc """ - Function to handle GET requests to `/api/v2/zkevm/deposits/count` endpoint. - """ - @spec deposits_count(Plug.Conn.t(), map()) :: Plug.Conn.t() - def deposits_count(conn, _params) do - count = Reader.deposits_count(api?: true) - - conn - |> put_status(200) - |> render(:polygon_zkevm_bridge_items_count, %{count: count}) - end - - @doc """ - Function to handle GET requests to `/api/v2/zkevm/withdrawals` endpoint. - """ - @spec withdrawals(Plug.Conn.t(), map()) :: Plug.Conn.t() - def withdrawals(conn, params) do - {withdrawals, next_page} = - params - |> paging_options() - |> Keyword.put(:api?, true) - |> Reader.withdrawals() - |> split_list_by_page() - - next_page_params = next_page_params(next_page, withdrawals, params) - - conn - |> put_status(200) - |> render(:polygon_zkevm_bridge_items, %{ - items: withdrawals, - next_page_params: next_page_params - }) - end - - @doc """ - Function to handle GET requests to `/api/v2/zkevm/withdrawals/count` endpoint. - """ - @spec withdrawals_count(Plug.Conn.t(), map()) :: Plug.Conn.t() - def withdrawals_count(conn, _params) do - count = Reader.withdrawals_count(api?: true) - - conn - |> put_status(200) - |> render(:polygon_zkevm_bridge_items_count, %{count: count}) - end -end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/account_abstraction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/account_abstraction_controller.ex index 3a559f1d8ab3..748a9c377659 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/account_abstraction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/proxy/account_abstraction_controller.ex @@ -14,7 +14,7 @@ defmodule BlockScoutWeb.API.V2.Proxy.AccountAbstractionController do plug(OpenApiSpex.Plug.CastAndValidate, json_render_error_v2: true) - tags(["account_abstraction"]) + tags(["account-abstraction"]) operation :operation, summary: "Get a user operation by hash", diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex index a561f4522200..7e21803f421c 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex @@ -42,7 +42,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do plug(OpenApiSpex.Plug.CastAndValidate, json_render_error_v2: true) - tags(["smart_contracts"]) + tags(["smart-contracts"]) operation :smart_contract, summary: "Retrieve detailed information about a verified smart contract", @@ -271,15 +271,12 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do @spec contract_creation_transaction_associations() :: [keyword()] defp contract_creation_transaction_associations do - include_internal_tx = - !Application.get_env(:explorer, :api_disable_contract_creation_internal_transaction_association, false) - case chain_type() do :filecoin -> - Address.contract_creation_transaction_with_from_address_associations(include_internal_tx) + [Address.contract_creation_transaction_with_from_address_association()] _ -> - Address.contract_creation_transaction_associations(include_internal_tx) + [Address.contract_creation_transaction_association()] end end @@ -289,7 +286,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do necessity_by_association: %{ [smart_contract: :smart_contract_additional_sources] => :optional, contract_creation_transaction_associations() => :optional - } + }, + preload_contract_creation_internal_transaction: true ] |> Keyword.merge(@api_true) end @@ -300,7 +298,8 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do necessity_by_association: %{ [:token, :names, :proxy_implementations] => :optional, contract_creation_transaction_associations() => :optional - } + }, + preload_contract_creation_internal_transaction: true ] |> Keyword.merge(@api_true) end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex index 25dcf94bc1d5..447cc0725f02 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex @@ -34,7 +34,9 @@ defmodule BlockScoutWeb.API.V2.TokenController do tokens_sorting: 1 ] - import Explorer.MicroserviceInterfaces.BENS, only: [maybe_preload_ens: 1] + import Explorer.MicroserviceInterfaces.BENS, + only: [maybe_preload_ens: 1, maybe_preload_ens_for_token_transfers: 1] + import Explorer.MicroserviceInterfaces.Metadata, only: [maybe_preload_metadata: 1] import Explorer.PagingOptions, only: [default_paging_options: 0] @@ -181,7 +183,10 @@ defmodule BlockScoutWeb.API.V2.TokenController do |> put_view(TransactionView) |> render(:token_transfers, %{ token_transfers: - token_transfers |> Instance.preload_nft(@api_true) |> maybe_preload_ens() |> maybe_preload_metadata(), + token_transfers + |> Instance.preload_nft(@api_true) + |> maybe_preload_ens_for_token_transfers() + |> maybe_preload_metadata(), next_page_params: next_page_params }) end @@ -439,7 +444,7 @@ defmodule BlockScoutWeb.API.V2.TokenController do |> put_status(200) |> put_view(TransactionView) |> render(:token_transfers, %{ - token_transfers: token_transfers |> maybe_preload_ens() |> maybe_preload_metadata(), + token_transfers: token_transfers |> maybe_preload_ens_for_token_transfers() |> maybe_preload_metadata(), next_page_params: next_page_params }) end @@ -616,7 +621,12 @@ defmodule BlockScoutWeb.API.V2.TokenController do description: "Retrieves a paginated list of bridged tokens with optional filtering and sorting.", parameters: base_params() ++ - [chain_ids_param(), q_param()] ++ + [ + chain_ids_param(), + q_param(), + sort_param(["fiat_value", "holders_count", "circulating_market_cap"]), + order_param() + ] ++ define_paging_params([ "contract_address_hash", "fiat_value", diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_transfer_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_transfer_controller.ex index 406adf2cfb8d..639d6c84a7a2 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_transfer_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_transfer_controller.ex @@ -17,7 +17,9 @@ defmodule BlockScoutWeb.API.V2.TokenTransferController do token_transfers_types_options: 1 ] - import Explorer.MicroserviceInterfaces.BENS, only: [maybe_preload_ens: 1] + import Explorer.MicroserviceInterfaces.BENS, + only: [maybe_preload_ens_for_token_transfers: 1] + import Explorer.MicroserviceInterfaces.Metadata, only: [maybe_preload_metadata: 1] import Explorer.PagingOptions, only: [default_paging_options: 0] @@ -27,7 +29,7 @@ defmodule BlockScoutWeb.API.V2.TokenTransferController do plug(OpenApiSpex.Plug.CastAndValidate, json_render_error_v2: true) - tags(["token_transfers"]) + tags(["token-transfers"]) @api_true [api?: true] @@ -105,7 +107,10 @@ defmodule BlockScoutWeb.API.V2.TokenTransferController do |> put_status(200) |> render(:token_transfers, %{ token_transfers: - token_transfers |> Instance.preload_nft(@api_true) |> maybe_preload_ens() |> maybe_preload_metadata(), + token_transfers + |> Instance.preload_nft(@api_true) + |> maybe_preload_ens_for_token_transfers() + |> maybe_preload_metadata(), decoded_transactions_map: decoded_transactions_map, next_page_params: next_page_params }) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex index 3e21278a72ea..e5906576a0b9 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex @@ -30,7 +30,13 @@ defmodule BlockScoutWeb.API.V2.TransactionController do type_filter_options: 1 ] - import Explorer.MicroserviceInterfaces.BENS, only: [maybe_preload_ens: 1, maybe_preload_ens_to_transaction: 1] + import Explorer.MicroserviceInterfaces.BENS, + only: [ + maybe_preload_ens: 1, + maybe_preload_ens_for_token_transfers: 1, + maybe_preload_ens_for_transactions: 1, + maybe_preload_ens_to_transaction: 1 + ] import Explorer.MicroserviceInterfaces.Metadata, only: [maybe_preload_metadata: 1, maybe_preload_metadata_to_transaction: 1] @@ -55,10 +61,8 @@ defmodule BlockScoutWeb.API.V2.TransactionController do alias Explorer.Chain.Beacon.Deposit, as: BeaconDeposit alias Explorer.Chain.Beacon.Reader, as: BeaconReader alias Explorer.Chain.Cache.Counters.{NewPendingTransactionsCount, Transactions24hCount} - alias Explorer.Chain.FheOperation - alias Explorer.Chain.{Hash, Transaction} + alias Explorer.Chain.{FheOperation, Hash, Transaction} alias Explorer.Chain.Optimism.TransactionBatch, as: OptimismTransactionBatch - alias Explorer.Chain.PolygonZkevm.Reader, as: PolygonZkevmReader alias Explorer.Chain.Scroll.Reader, as: ScrollReader alias Explorer.Chain.Token.Instance alias Explorer.Chain.ZkSync.Reader, as: ZkSyncReader @@ -129,13 +133,12 @@ defmodule BlockScoutWeb.API.V2.TransactionController do [token: reputation_association()] => :optional } - @internal_transaction_necessity_by_association [ - necessity_by_association: %{ - [created_contract_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] => - :optional, - [from_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] => :optional, - [to_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] => :optional - } + @internal_transaction_address_preloads [ + address_preloads: [ + created_contract_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()], + from_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()], + to_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()] + ] ] @api_true [api?: true] @@ -157,17 +160,10 @@ defmodule BlockScoutWeb.API.V2.TransactionController do def transaction(conn, %{transaction_hash_param: transaction_hash_string} = params) do necessity_by_association_with_actions = @transaction_necessity_by_association - |> Map.put(:transaction_actions, :optional) |> Map.put(:signed_authorizations, :optional) necessity_by_association = case Application.get_env(:explorer, :chain_type) do - :polygon_zkevm -> - necessity_by_association_with_actions - |> Map.put(:zkevm_batch, :optional) - |> Map.put(:zkevm_sequence_transaction, :optional) - |> Map.put(:zkevm_verify_transaction, :optional) - :zksync -> necessity_by_association_with_actions |> Map.put(:zksync_batch, :optional) @@ -246,7 +242,7 @@ defmodule BlockScoutWeb.API.V2.TransactionController do full_options = [ - necessity_by_association: @transaction_necessity_by_association + necessity_by_association: transactions_necessity_by_association() ] |> Keyword.merge(paging_options(params, filter_options)) |> Keyword.merge(method_filter_options(params)) @@ -262,51 +258,38 @@ defmodule BlockScoutWeb.API.V2.TransactionController do conn |> put_status(200) |> render(:transactions, %{ - transactions: transactions |> maybe_preload_ens() |> maybe_preload_metadata(), + transactions: transactions |> maybe_preload_ens_for_transactions() |> maybe_preload_metadata(), next_page_params: next_page_params }) end - operation :polygon_zkevm_batch, - summary: "List L2 transactions in a Polygon ZkEVM batch", - description: "Retrieves L2 transactions bound to a specific Polygon ZkEVM batch number.", - parameters: [batch_number_param() | base_params()], - responses: [ - ok: - {"Polygon ZkEVM batch transactions.", "application/json", - %Schema{ - type: :object, - properties: %{ - items: %Schema{type: :array, items: Schemas.Transaction.Response} - }, - nullable: false, - additionalProperties: false - }}, - unprocessable_entity: JsonErrorResponse.response() - ] - - @doc """ - Function to handle GET requests to `/api/v2/transactions/zkevm-batch/:batch_number` endpoint. - It renders the list of L2 transactions bound to the specified batch. - """ - @spec polygon_zkevm_batch(Plug.Conn.t(), map()) :: Plug.Conn.t() - def polygon_zkevm_batch(conn, %{batch_number_param: batch_number} = _params) do - options = - [necessity_by_association: @transaction_necessity_by_association] - |> Keyword.merge(@api_true) - - transactions = - batch_number - |> PolygonZkevmReader.batch_transactions(@api_true) - |> Enum.map(fn transaction -> transaction.hash end) - |> Chain.hashes_to_transactions(options) - - conn - |> put_status(200) - |> render(:transactions, %{ - transactions: transactions |> maybe_preload_ens() |> maybe_preload_metadata(), - items: true - }) + defp transactions_necessity_by_association do + %{ + :block => :optional, + [ + created_contract_address: [ + :scam_badge, + :names, + :token, + proxy_implementations_association() + ] + ] => :optional, + [ + from_address: [ + :scam_badge, + :names, + proxy_implementations_association() + ] + ] => :optional, + [ + to_address: [ + :scam_badge, + :names, + proxy_implementations_association() + ] + ] => :optional + } + |> Map.merge(@chain_type_transaction_necessity_by_association) end operation :zksync_batch, @@ -508,7 +491,7 @@ defmodule BlockScoutWeb.API.V2.TransactionController do end query - |> Chain.join_associations(@transaction_necessity_by_association) + |> Chain.join_associations(transactions_necessity_by_association()) |> preload([{:token_transfers, [:token, :from_address, :to_address]}]) |> Repo.replica().all() end @@ -519,7 +502,7 @@ defmodule BlockScoutWeb.API.V2.TransactionController do conn |> put_status(200) |> render(:transactions, %{ - transactions: transactions |> maybe_preload_ens() |> maybe_preload_metadata(), + transactions: transactions |> maybe_preload_ens_for_transactions() |> maybe_preload_metadata(), next_page_params: next_page_params }) end @@ -542,7 +525,7 @@ defmodule BlockScoutWeb.API.V2.TransactionController do defp handle_batch_transactions(conn, %{batch_number_param: batch_number} = params, batch_transactions_fun) do full_options = [ - necessity_by_association: @transaction_necessity_by_association + necessity_by_association: transactions_necessity_by_association() ] |> Keyword.merge(paging_options(params)) |> Keyword.merge(@api_true) @@ -563,7 +546,7 @@ defmodule BlockScoutWeb.API.V2.TransactionController do conn |> put_status(200) |> render(:transactions, %{ - transactions: transactions |> maybe_preload_ens() |> maybe_preload_metadata(), + transactions: transactions |> maybe_preload_ens_for_transactions() |> maybe_preload_metadata(), next_page_params: next_page_params }) end @@ -595,7 +578,7 @@ defmodule BlockScoutWeb.API.V2.TransactionController do def execution_node(conn, %{execution_node_hash_param: execution_node_hash_string} = params) do with {:format, {:ok, execution_node_hash}} <- {:format, Chain.string_to_address_hash(execution_node_hash_string)} do full_options = - [necessity_by_association: @transaction_necessity_by_association] + [necessity_by_association: transactions_necessity_by_association()] |> Keyword.merge(put_key_value_to_paging_options(paging_options(params), :is_index_in_asc_order, true)) |> Keyword.merge(@api_true) @@ -610,7 +593,7 @@ defmodule BlockScoutWeb.API.V2.TransactionController do conn |> put_status(200) |> render(:transactions, %{ - transactions: transactions |> maybe_preload_ens() |> maybe_preload_metadata(), + transactions: transactions |> maybe_preload_ens_for_transactions() |> maybe_preload_metadata(), next_page_params: next_page_params }) end @@ -718,7 +701,10 @@ defmodule BlockScoutWeb.API.V2.TransactionController do |> put_status(200) |> render(:token_transfers, %{ token_transfers: - token_transfers |> Instance.preload_nft(@api_true) |> maybe_preload_ens() |> maybe_preload_metadata(), + token_transfers + |> Instance.preload_nft(@api_true) + |> maybe_preload_ens_for_token_transfers() + |> maybe_preload_metadata(), next_page_params: next_page_params }) end @@ -754,7 +740,7 @@ defmodule BlockScoutWeb.API.V2.TransactionController do def internal_transactions(conn, %{transaction_hash_param: transaction_hash_string} = params) do with {:ok, transaction, _transaction_hash} <- validate_transaction(transaction_hash_string, params) do full_options = - @internal_transaction_necessity_by_association + @internal_transaction_address_preloads |> Keyword.merge(paging_options(params)) |> Keyword.merge(@api_true) @@ -952,7 +938,7 @@ defmodule BlockScoutWeb.API.V2.TransactionController do with {:auth, %{watchlist_id: watchlist_id}} <- {:auth, current_user(conn)} do full_options = [ - necessity_by_association: @transaction_necessity_by_association + necessity_by_association: transactions_necessity_by_association() ] |> Keyword.merge(paging_options(params, [:validated])) |> Keyword.merge(@api_true) @@ -967,7 +953,7 @@ defmodule BlockScoutWeb.API.V2.TransactionController do conn |> put_status(200) |> render(:transactions_watchlist, %{ - transactions: transactions |> maybe_preload_ens() |> maybe_preload_metadata(), + transactions: transactions |> maybe_preload_ens_for_transactions() |> maybe_preload_metadata(), next_page_params: next_page_params, watchlist_names: watchlist_names }) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex index 710793da1c4b..93123c38420c 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex @@ -103,7 +103,7 @@ defmodule BlockScoutWeb.SmartContractController do defp implementation_address_hash(contract_type, address) do if contract_type == "proxy" do implementation = Implementation.get_implementation(address.smart_contract) - (implementation && implementation.address_hashes |> List.first()) || burn_address_hash_string() + (implementation && (implementation.address_hashes || []) |> List.first()) || burn_address_hash_string() else burn_address_hash_string() end @@ -160,8 +160,9 @@ defmodule BlockScoutWeb.SmartContractController do :names => :optional, :smart_contract => :optional, :token => :optional, - Address.contract_creation_transaction_associations() => :optional + Address.contract_creation_transaction_association() => :optional }, + preload_contract_creation_internal_transaction: true, ip: AccessHelper.conn_to_ip_string(conn) ] diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_internal_transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_internal_transaction_controller.ex index 086ebafec71e..385ec683e1c4 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_internal_transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_internal_transaction_controller.ex @@ -11,7 +11,7 @@ defmodule BlockScoutWeb.TransactionInternalTransactionController do alias BlockScoutWeb.{AccessHelper, Controller, InternalTransactionView, TransactionController} alias Explorer.{Chain, Market} - alias Explorer.Chain.{DenormalizationHelper, Transaction} + alias Explorer.Chain.Transaction alias Phoenix.View def index(conn, %{"transaction_id" => transaction_hash_string, "type" => "JSON"} = params) do @@ -22,17 +22,12 @@ defmodule BlockScoutWeb.TransactionInternalTransactionController do {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.to_address_hash), params) do full_options = [ - necessity_by_association: %{ - [created_contract_address: :names] => :optional, - [from_address: :names] => :optional, - [to_address: :names] => :optional, - [created_contract_address: :smart_contract] => :optional, - [from_address: :smart_contract] => :optional, - [to_address: :smart_contract] => :optional, - :transaction => :optional - } + address_preloads: [ + created_contract_address: [:names, :smart_contract], + from_address: [:names, :smart_contract], + to_address: [:names, :smart_contract] + ] ] - |> DenormalizationHelper.extend_transaction_block_necessity(:optional) |> Keyword.merge(paging_options(params)) internal_transactions_plus_one = transaction_to_internal_transactions(transaction, full_options) diff --git a/apps/block_scout_web/lib/block_scout_web/csv_export/address/internal_transactions.ex b/apps/block_scout_web/lib/block_scout_web/csv_export/address/internal_transactions.ex index a6253f1a785d..880fb800e529 100644 --- a/apps/block_scout_web/lib/block_scout_web/csv_export/address/internal_transactions.ex +++ b/apps/block_scout_web/lib/block_scout_web/csv_export/address/internal_transactions.ex @@ -6,10 +6,11 @@ defmodule BlockScoutWeb.CsvExport.Address.InternalTransactions do import BlockScoutWeb.Chain, only: [address_to_internal_transactions: 2] alias Explorer.Chain.{Address, Hash, InternalTransaction, Transaction, Wei} - alias Explorer.Chain.CsvExport.Helper + alias Explorer.Chain.CsvExport.{AsyncHelper, Helper} - @spec export(Hash.Address.t(), String.t(), String.t(), String.t() | nil, String.t() | nil) :: Enumerable.t() - def export(address_hash, from_period, to_period, _options, filter_type \\ nil, filter_value \\ nil) do + @spec export(Hash.Address.t(), String.t(), String.t(), Keyword.t(), String.t() | nil, String.t() | nil) :: + Enumerable.t() + def export(address_hash, from_period, to_period, _options, filter_type, filter_value) do {from_block, to_block} = Helper.block_from_period(from_period, to_period) address_hash @@ -33,9 +34,7 @@ defmodule BlockScoutWeb.CsvExport.Address.InternalTransactions do |> Keyword.put(:paging_options, paging_options) |> Keyword.put(:from_block, from_block) |> Keyword.put(:to_block, to_block) - |> Keyword.put(:necessity_by_association, %{ - :transaction => :optional - }) + |> Keyword.put(:timeout, AsyncHelper.db_timeout()) |> (&if(Helper.valid_filter?(filter_type, filter_value, "internal_transactions"), do: &1 |> Keyword.put(:direction, String.to_atom(filter_value)), else: &1 diff --git a/apps/block_scout_web/lib/block_scout_web/endpoint.ex b/apps/block_scout_web/lib/block_scout_web/endpoint.ex index 570349c2d05e..d418f4334a7b 100644 --- a/apps/block_scout_web/lib/block_scout_web/endpoint.ex +++ b/apps/block_scout_web/lib/block_scout_web/endpoint.ex @@ -11,6 +11,32 @@ defmodule BlockScoutWeb.Endpoint do alias Explorer.ThirdPartyIntegrations.UniversalProxy + @cors_plug_base [ + headers: + [ + "x-apollo-tracing", + "updated-gas-oracle", + "recaptcha-v2-response", + "recaptcha-v3-response", + "recaptcha-bypass-token", + "scoped-recaptcha-bypass-token", + "show-scam-tokens", + @api_v2_temp_token_header_key + ] ++ CORSPlug.defaults()[:headers], + expose: [ + "bypass-429-option", + "x-ratelimit-reset", + "x-ratelimit-limit", + "x-ratelimit-remaining", + @api_v2_temp_token_header_key + ] + ] + + @cors_plug_options Keyword.merge( + @cors_plug_base, + if(Mix.env() == :dev, do: [origin: ["http://localhost:3000"]], else: []) + ) + if @sql_sandbox do plug(Phoenix.Ecto.SQL.Sandbox, repo: Explorer.Repo) end @@ -81,26 +107,7 @@ defmodule BlockScoutWeb.Endpoint do # 'x-apollo-tracing' header for https://www.graphqlbin.com to work with our GraphQL endpoint # 'updated-gas-oracle' header for /api/v2/stats endpoint, added to support cross-origin requests (e.g. multichain search explorer) - plug(CORSPlug, - headers: - [ - "x-apollo-tracing", - "updated-gas-oracle", - "recaptcha-v2-response", - "recaptcha-v3-response", - "recaptcha-bypass-token", - "scoped-recaptcha-bypass-token", - "show-scam-tokens", - @api_v2_temp_token_header_key - ] ++ CORSPlug.defaults()[:headers], - expose: [ - "bypass-429-option", - "x-ratelimit-reset", - "x-ratelimit-limit", - "x-ratelimit-remaining", - @api_v2_temp_token_header_key - ] - ) + plug(CORSPlug, @cors_plug_options) plug(BlockScoutWeb.Router) end diff --git a/apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex b/apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex index 3f9414f5dd21..70fe9a9ea8c4 100644 --- a/apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex +++ b/apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex @@ -30,13 +30,12 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do reputation_association() => :optional } ] - @internal_transaction_necessity_by_association [ - necessity_by_association: %{ - [created_contract_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] => - :optional, - [from_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] => :optional, - [to_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] => :optional - } + @internal_transaction_address_preloads [ + address_preloads: [ + created_contract_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()], + from_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()], + to_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()] + ] ] @doc """ @@ -227,9 +226,7 @@ defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do end defp fetch_internal_transactions(transaction) do - full_options = - @internal_transaction_necessity_by_association - |> Keyword.merge(@api_true) + full_options = Keyword.merge(@internal_transaction_address_preloads, @api_true) transaction |> transaction_to_internal_transactions(full_options) diff --git a/apps/block_scout_web/lib/block_scout_web/models/transaction_state_helper.ex b/apps/block_scout_web/lib/block_scout_web/models/transaction_state_helper.ex index d9f1f0ac4497..3d69917cb575 100644 --- a/apps/block_scout_web/lib/block_scout_web/models/transaction_state_helper.ex +++ b/apps/block_scout_web/lib/block_scout_web/models/transaction_state_helper.ex @@ -73,7 +73,8 @@ defmodule BlockScoutWeb.Models.TransactionStateHelper do api?: Keyword.get(options, :api?, false) ) |> Enum.filter(&(&1.index <= transaction.index)) - |> Repo.preload([:token_transfers, :internal_transactions]) + |> Repo.preload([:token_transfers]) + |> Transaction.preload_internal_transactions() transaction = block_transactions @@ -84,14 +85,14 @@ defmodule BlockScoutWeb.Models.TransactionStateHelper do from_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()], to_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()] ], - internal_transactions: [ - from_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()], - to_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()] - ], block: [miner: [:names, :smart_contract, proxy_implementations_association()]], from_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()], to_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()] ) + |> Transaction.preload_internal_transactions( + from_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()], + to_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()] + ) previous_block_number = BlockNumberHelper.previous_block_number(transaction.block_number) diff --git a/apps/block_scout_web/lib/block_scout_web/notifier.ex b/apps/block_scout_web/lib/block_scout_web/notifier.ex index ca28020d00c8..fd64bb3d8825 100644 --- a/apps/block_scout_web/lib/block_scout_web/notifier.ex +++ b/apps/block_scout_web/lib/block_scout_web/notifier.ex @@ -25,7 +25,6 @@ defmodule BlockScoutWeb.Notifier do alias BlockScoutWeb.API.V2.{ AddressView, BlockView, - PolygonZkevmView, SmartContractView, TransactionView } @@ -203,18 +202,6 @@ defmodule BlockScoutWeb.Notifier do end) end - def handle_event({:chain_event, :zkevm_confirmed_batches, :realtime, batches}) do - batches - |> Enum.sort_by(& &1.number, :asc) - |> Enum.each(fn confirmed_batch -> - rendered_batch = PolygonZkevmView.render("zkevm_batch.json", %{batch: confirmed_batch, socket: nil}) - - Endpoint.broadcast("zkevm_batches:new_zkevm_confirmed_batch", "new_zkevm_confirmed_batch", %{ - batch: rendered_batch - }) - end) - end - def handle_event({:chain_event, :exchange_rate}) do exchange_rate = Market.get_coin_exchange_rate() @@ -249,9 +236,13 @@ defmodule BlockScoutWeb.Notifier do end def handle_event( - {:chain_event, :internal_transactions, :on_demand, - [%InternalTransaction{index: 0, transaction_hash: transaction_hash}]} + {:chain_event, :internal_transactions, :on_demand, [%InternalTransaction{index: 0} = internal_transaction]} ) do + transaction_hash = + internal_transaction + |> InternalTransaction.preload_transaction() + |> Map.get(:transaction_hash) + # TODO: delete duplicated event when old UI becomes deprecated Endpoint.broadcast("transactions_old:#{transaction_hash}", "raw_trace", %{raw_trace_origin: transaction_hash}) @@ -269,8 +260,10 @@ defmodule BlockScoutWeb.Notifier do internal_transactions |> Stream.map( &(InternalTransaction.where_nonpending_operation() - |> Repo.get_by(transaction_hash: &1.transaction_hash, index: &1.index) - |> Repo.preload([:from_address, :to_address, :block])) + |> Repo.get_by(block_number: &1.block_number, transaction_index: &1.transaction_index, index: &1.index) + |> Repo.preload([:block]) + |> InternalTransaction.preload_addresses() + |> InternalTransaction.preload_transaction()) ) |> Enum.each(&broadcast_internal_transaction/1) end diff --git a/apps/block_scout_web/lib/block_scout_web/routers/api_router.ex b/apps/block_scout_web/lib/block_scout_web/routers/api_router.ex index b2e210211dc5..c7585636f3af 100644 --- a/apps/block_scout_web/lib/block_scout_web/routers/api_router.ex +++ b/apps/block_scout_web/lib/block_scout_web/routers/api_router.ex @@ -111,7 +111,7 @@ defmodule BlockScoutWeb.Routers.ApiRouter do plug(CheckFeature, feature_check: &mud_enabled?/0) end - alias BlockScoutWeb.API.V2 + alias BlockScoutWeb.API.{Legacy, V2} forward("/account", AccountRouter) @@ -153,15 +153,13 @@ defmodule BlockScoutWeb.Routers.ApiRouter do end end + get("/csv-exports/:uuid_param", V2.CsvExportController, :get_csv_export) + scope "/transactions" do get("/", V2.TransactionController, :transactions) get("/watchlist", V2.TransactionController, :watchlist_transactions) get("/stats", V2.TransactionController, :stats) - if @chain_type == :polygon_zkevm do - get("/zkevm-batch/:batch_number_param", V2.TransactionController, :polygon_zkevm_batch) - end - if @chain_type == :zksync do get("/zksync-batch/:batch_number_param", V2.TransactionController, :zksync_batch) end @@ -280,11 +278,6 @@ defmodule BlockScoutWeb.Routers.ApiRouter do get("/optimism-deposits", V2.OptimismController, :main_page_deposits) end - if @chain_type == :polygon_zkevm do - get("/zkevm/batches/confirmed", V2.PolygonZkevmController, :batches_confirmed) - get("/zkevm/batches/latest-number", V2.PolygonZkevmController, :batch_latest_number) - end - if @chain_type == :zksync do get("/zksync/batches/confirmed", V2.ZkSyncController, :batches_confirmed) get("/zksync/batches/latest-number", V2.ZkSyncController, :batch_latest_number) @@ -363,18 +356,6 @@ defmodule BlockScoutWeb.Routers.ApiRouter do get("/counters", V2.WithdrawalController, :withdrawals_counters) end - scope "/zkevm" do - if @chain_type == :polygon_zkevm do - get("/batches", V2.PolygonZkevmController, :batches) - get("/batches/count", V2.PolygonZkevmController, :batches_count) - get("/batches/:batch_number", V2.PolygonZkevmController, :batch) - get("/deposits", V2.PolygonZkevmController, :deposits) - get("/deposits/count", V2.PolygonZkevmController, :deposits_count) - get("/withdrawals", V2.PolygonZkevmController, :withdrawals) - get("/withdrawals/count", V2.PolygonZkevmController, :withdrawals_count) - end - end - scope "/proxy" do scope "/3rdparty" do get("/:platform_id", V2.Proxy.UniversalProxyController, :index) @@ -503,6 +484,19 @@ defmodule BlockScoutWeb.Routers.ApiRouter do end end + scope "/legacy" do + pipe_through(:api_v2) + + scope "/logs" do + get("/get-logs", Legacy.LogsController, :get_logs) + end + + scope "/block" do + get("/get-block-number-by-time", Legacy.BlockController, :get_block_number_by_time) + get("/eth-block-number", Legacy.BlockController, :eth_block_number) + end + end + scope "/v1/graphql" do pipe_through(:api_v1_graphql) diff --git a/apps/block_scout_web/lib/block_scout_web/routers/chain_type_scope.ex b/apps/block_scout_web/lib/block_scout_web/routers/chain_type_scope.ex index ba80577bf6da..c74b5648875a 100644 --- a/apps/block_scout_web/lib/block_scout_web/routers/chain_type_scope.ex +++ b/apps/block_scout_web/lib/block_scout_web/routers/chain_type_scope.ex @@ -15,8 +15,8 @@ defmodule BlockScoutWeb.Routers.ChainTypeScope do ## Examples - chain_scope :polygon_zkevm do - get("/zkevm-batch/:batch_number", V2.TransactionController, :polygon_zkevm_batch) + chain_scope :zksync do + get("/zksync-batch/:batch_number", V2.TransactionController, :zksync_batch) end """ defmacro chain_scope(chain_type, opts \\ [], do: block) do diff --git a/apps/block_scout_web/lib/block_scout_web/schemas/api/legacy/envelope.ex b/apps/block_scout_web/lib/block_scout_web/schemas/api/legacy/envelope.ex new file mode 100644 index 000000000000..78240c4c9bba --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/schemas/api/legacy/envelope.ex @@ -0,0 +1,66 @@ +defmodule BlockScoutWeb.Schemas.API.Legacy.Envelope do + @moduledoc false + + alias OpenApiSpex.Schema + + @doc """ + Returns an OpenAPI schema for the RPC response envelope: + `{"status": "0"|"1"|"2", "message": "...", "result": }`. + """ + @spec rpc_envelope(Schema.t()) :: Schema.t() + def rpc_envelope(result_schema) do + %Schema{ + type: :object, + properties: %{ + status: %Schema{ + type: :string, + enum: ["0", "1", "2"], + description: "`1` = OK, `0` = error, `2` = pending." + }, + message: %Schema{ + type: :string, + description: + "Human-readable status string — `OK` on success, " <> + "a descriptive error message otherwise." + }, + result: %Schema{ + description: "Endpoint-specific payload on success; `null` on error.", + nullable: true, + allOf: [result_schema] + } + }, + required: [:status, :message, :result], + additionalProperties: false + } + end + + @doc """ + Returns an OpenAPI schema for the JSON-RPC 2.0 envelope: + `{"jsonrpc": "2.0", "result": , "id": }`. + """ + @spec eth_rpc_envelope(Schema.t()) :: Schema.t() + def eth_rpc_envelope(result_schema) do + %Schema{ + type: :object, + properties: %{ + jsonrpc: %Schema{ + type: :string, + enum: ["2.0"], + description: "JSON-RPC protocol version, always `2.0`." + }, + result: %Schema{ + description: "Endpoint-specific payload.", + allOf: [result_schema] + }, + id: %Schema{ + anyOf: [%Schema{type: :integer}, %Schema{type: :string}], + description: + "Echoes the request id. When the client omits it, " <> + "the server echoes integer `1`." + } + }, + required: [:jsonrpc, :result, :id], + additionalProperties: false + } + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/schemas/api/legacy/eth_block_number_result.ex b/apps/block_scout_web/lib/block_scout_web/schemas/api/legacy/eth_block_number_result.ex new file mode 100644 index 000000000000..08c5c6e8fe9a --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/schemas/api/legacy/eth_block_number_result.ex @@ -0,0 +1,18 @@ +defmodule BlockScoutWeb.Schemas.API.Legacy.EthBlockNumberResult do + @moduledoc false + require OpenApiSpex + + # nullable: true is kept defensively. In practice, BlockNumber.get_max/0 + # delegates to Block.fetch_max_block_number/0 which returns Repo.one(query) || 0 + # (with a rescue that also returns 0), so the v1 code path never produces a null + # result. The field is marked nullable to remain accurate if the underlying + # implementation ever changes. + OpenApiSpex.schema(%{ + type: :string, + pattern: ~r/^0x[0-9a-fA-F]+$/, + nullable: true, + description: + "Hex-encoded latest block number on the chain. " <> + "Nullable in the schema for defensive reasons; always present in practice." + }) +end diff --git a/apps/block_scout_web/lib/block_scout_web/schemas/api/legacy/get_block_number_by_time_result.ex b/apps/block_scout_web/lib/block_scout_web/schemas/api/legacy/get_block_number_by_time_result.ex new file mode 100644 index 000000000000..a42fac5dcc76 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/schemas/api/legacy/get_block_number_by_time_result.ex @@ -0,0 +1,19 @@ +defmodule BlockScoutWeb.Schemas.API.Legacy.GetBlockNumberByTimeResult do + @moduledoc false + require OpenApiSpex + alias BlockScoutWeb.Schemas.API.V2.General + alias OpenApiSpex.Schema + + OpenApiSpex.schema(%{ + type: :object, + properties: %{ + blockNumber: %Schema{ + description: "Decimal-string block number.", + allOf: [General.IntegerString] + } + }, + required: [:blockNumber], + additionalProperties: false, + nullable: true + }) +end diff --git a/apps/block_scout_web/lib/block_scout_web/schemas/api/legacy/log_item.ex b/apps/block_scout_web/lib/block_scout_web/schemas/api/legacy/log_item.ex new file mode 100644 index 000000000000..14cca99b632f --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/schemas/api/legacy/log_item.ex @@ -0,0 +1,76 @@ +defmodule BlockScoutWeb.Schemas.API.Legacy.LogItem do + @moduledoc false + require OpenApiSpex + alias BlockScoutWeb.Schemas.API.V2.General + alias OpenApiSpex.Schema + + OpenApiSpex.schema(%{ + type: :object, + properties: %{ + address: General.AddressHash, + # Each topic slot is nullable: LogsView.get_topics/1 at logs_view.ex:31-38 + # always returns a 4-element list, filling unset slots with nil. A non- + # nullable items schema would fail validation on any real log with <4 topics. + topics: %Schema{ + type: :array, + minItems: 4, + maxItems: 4, + description: "32-byte indexed event topics. Always a 4-element array; unfilled slots are `null`.", + items: %Schema{ + type: :string, + pattern: ~r/^0x[0-9a-fA-F]{64}$/, + nullable: true + } + }, + data: %Schema{ + type: :string, + pattern: ~r/^0x[0-9a-fA-F]*$/, + description: "Hex-encoded event data payload (`0x`-prefixed, arbitrary length)." + }, + blockNumber: %Schema{ + type: :string, + pattern: ~r/^0x[0-9a-fA-F]+$/, + description: "Hex-encoded block number." + }, + timeStamp: %Schema{ + type: :string, + pattern: ~r/^0x[0-9a-fA-F]+$/, + description: "Hex-encoded Unix timestamp in seconds of the block." + }, + gasPrice: %Schema{ + type: :string, + pattern: ~r/^0x[0-9a-fA-F]+$/, + description: "Hex-encoded gas price in wei." + }, + gasUsed: %Schema{ + type: :string, + pattern: ~r/^0x[0-9a-fA-F]+$/, + description: "Hex-encoded gas used by the transaction." + }, + logIndex: %Schema{ + type: :string, + pattern: ~r/^0x[0-9a-fA-F]+$/, + description: "Hex-encoded position of the log within the block." + }, + transactionHash: General.FullHash, + transactionIndex: %Schema{ + type: :string, + pattern: ~r/^0x[0-9a-fA-F]+$/, + description: "Hex-encoded position of the transaction within the block." + } + }, + required: [ + :address, + :topics, + :data, + :blockNumber, + :timeStamp, + :gasPrice, + :gasUsed, + :logIndex, + :transactionHash, + :transactionIndex + ], + additionalProperties: false + }) +end diff --git a/apps/block_scout_web/lib/block_scout_web/schemas/api/v2/celo/election_reward/type.ex b/apps/block_scout_web/lib/block_scout_web/schemas/api/v2/celo/election_reward/type.ex index eb9befd2e984..69b19cb5508d 100644 --- a/apps/block_scout_web/lib/block_scout_web/schemas/api/v2/celo/election_reward/type.ex +++ b/apps/block_scout_web/lib/block_scout_web/schemas/api/v2/celo/election_reward/type.ex @@ -4,5 +4,15 @@ defmodule BlockScoutWeb.Schemas.API.V2.Celo.ElectionReward.Type do alias Explorer.Chain.Celo.ElectionReward - OpenApiSpex.schema(%{type: :string, nullable: false, enum: ElectionReward.types(), title: "CeloElectionRewardType"}) + # Uses `type_enum_with_legacy/0` instead of `types/0` so that + # CastAndValidate accepts both the canonical underscore form + # ("delegated_payment") and the legacy hyphenated URL form + # ("delegated-payment"). See `ElectionReward.type_enum_with_legacy/0` + # for details. + OpenApiSpex.schema(%{ + type: :string, + nullable: false, + enum: ElectionReward.type_enum_with_legacy(), + title: "CeloElectionRewardType" + }) end diff --git a/apps/block_scout_web/lib/block_scout_web/schemas/api/v2/celo/epoch.ex b/apps/block_scout_web/lib/block_scout_web/schemas/api/v2/celo/epoch.ex new file mode 100644 index 000000000000..ec5e4dffb1a5 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/schemas/api/v2/celo/epoch.ex @@ -0,0 +1,33 @@ +defmodule BlockScoutWeb.Schemas.API.V2.Celo.Epoch do + @moduledoc """ + This module defines the schema for a Celo epoch list item. + """ + require OpenApiSpex + + alias BlockScoutWeb.Schemas.API.V2.General + alias OpenApiSpex.Schema + + OpenApiSpex.schema(%{ + description: "Celo epoch summary.", + type: :object, + properties: %{ + number: %Schema{type: :integer, nullable: false, minimum: 0}, + type: %Schema{type: :string, enum: ["L1", "L2"], nullable: false}, + start_block_number: %Schema{type: :integer, nullable: false, minimum: 0}, + end_block_number: %Schema{type: :integer, nullable: false, minimum: 0}, + timestamp: General.TimestampNullable, + is_finalized: %Schema{type: :boolean, nullable: false}, + distribution: %Schema{type: :object, nullable: true, additionalProperties: true} + }, + required: [ + :number, + :type, + :start_block_number, + :end_block_number, + :timestamp, + :is_finalized, + :distribution + ], + additionalProperties: false + }) +end diff --git a/apps/block_scout_web/lib/block_scout_web/schemas/api/v2/celo/epoch/detailed.ex b/apps/block_scout_web/lib/block_scout_web/schemas/api/v2/celo/epoch/detailed.ex new file mode 100644 index 000000000000..a400678d80ab --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/schemas/api/v2/celo/epoch/detailed.ex @@ -0,0 +1,47 @@ +defmodule BlockScoutWeb.Schemas.API.V2.Celo.Epoch.Detailed do + @moduledoc """ + This module defines the schema for detailed Celo epoch response. + """ + require OpenApiSpex + + alias BlockScoutWeb.Schemas.API.V2.{Celo.Epoch, General} + alias BlockScoutWeb.Schemas.Helper + alias OpenApiSpex.Schema + + OpenApiSpex.schema( + Epoch.schema() + |> Helper.extend_schema( + title: "CeloEpochDetailed", + nullable: false, + properties: %{ + start_processing_block_hash: General.FullHashNullable, + start_processing_block_number: %Schema{type: :integer, nullable: true, minimum: 0}, + end_processing_block_hash: General.FullHashNullable, + end_processing_block_number: %Schema{type: :integer, nullable: true, minimum: 0}, + aggregated_election_rewards: %Schema{ + type: :object, + nullable: true, + additionalProperties: %Schema{ + type: :object, + nullable: true, + properties: %{ + total: General.IntegerString, + count: %Schema{type: :integer, nullable: false, minimum: 0}, + token: %Schema{type: :object, nullable: true, additionalProperties: true} + }, + required: [:total, :count, :token], + additionalProperties: false + } + }, + distribution: %Schema{type: :object, nullable: true, additionalProperties: true} + }, + required: [ + :start_processing_block_hash, + :start_processing_block_number, + :end_processing_block_hash, + :end_processing_block_number, + :aggregated_election_rewards + ] + ) + ) +end diff --git a/apps/block_scout_web/lib/block_scout_web/schemas/api/v2/csv_export/response.ex b/apps/block_scout_web/lib/block_scout_web/schemas/api/v2/csv_export/response.ex new file mode 100644 index 000000000000..d5c4d8cc0701 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/schemas/api/v2/csv_export/response.ex @@ -0,0 +1,15 @@ +defmodule BlockScoutWeb.Schemas.API.V2.CSVExport.Response do + @moduledoc false + require OpenApiSpex + + alias OpenApiSpex.Schema + + OpenApiSpex.schema(%{ + type: :object, + properties: %{ + status: %Schema{type: :string, nullable: false, enum: ["pending", "completed", "failed"]}, + file_id: %Schema{type: :string, nullable: true}, + expires_at: %Schema{type: :string, nullable: true, format: "date-time"} + } + }) +end diff --git a/apps/block_scout_web/lib/block_scout_web/schemas/api/v2/general.ex b/apps/block_scout_web/lib/block_scout_web/schemas/api/v2/general.ex index 5751dfad7cfc..83e1e1ed548f 100644 --- a/apps/block_scout_web/lib/block_scout_web/schemas/api/v2/general.ex +++ b/apps/block_scout_web/lib/block_scout_web/schemas/api/v2/general.ex @@ -105,6 +105,24 @@ defmodule BlockScoutWeb.Schemas.API.V2.General do } end + @doc """ + Returns an optional parameter definition for the start of the time period. + Used for endpoints like token holders CSV that don't require a time range. + """ + @spec optional_from_period_param() :: Parameter.t() + def optional_from_period_param do + %{from_period_param() | required: false} + end + + @doc """ + Returns an optional parameter definition for the end of the time period. + Used for endpoints like token holders CSV that don't require a time range. + """ + @spec optional_to_period_param() :: Parameter.t() + def optional_to_period_param do + %{to_period_param() | required: false} + end + @doc """ Returns a parameter definition for chain IDs in the query. """ @@ -950,6 +968,24 @@ defmodule BlockScoutWeb.Schemas.API.V2.General do } end + @doc """ + Returns a parameter definition for a UUID in the path. + """ + @spec uuid_param() :: Parameter.t() + def uuid_param do + %Parameter{ + name: :uuid_param, + in: :path, + schema: %Schema{ + type: :string, + format: :uuid, + pattern: ~r/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i + }, + required: true, + description: "UUID for CSV export" + } + end + @doc """ Returns a list of base parameters (api_key and key). """ @@ -1058,13 +1094,6 @@ defmodule BlockScoutWeb.Schemas.API.V2.General do required: false, description: "Transaction index for paging" }, - "block_index" => %Parameter{ - name: :block_index, - in: :query, - schema: %Schema{type: :integer}, - required: false, - description: "Block index for paging" - }, "inserted_at" => %Parameter{ name: :inserted_at, in: :query, @@ -1277,6 +1306,13 @@ defmodule BlockScoutWeb.Schemas.API.V2.General do required: false, description: "Amount for paging" }, + "account_address_hash" => %Parameter{ + name: :account_address_hash, + in: :query, + schema: AddressHash, + required: false, + description: "Account address hash for paging" + }, "associated_account_address_hash" => %Parameter{ name: :associated_account_address_hash, in: :query, diff --git a/apps/block_scout_web/lib/block_scout_web/schemas/api/v2/internal_transaction.ex b/apps/block_scout_web/lib/block_scout_web/schemas/api/v2/internal_transaction.ex index 54ab28349b63..8b6e5f16cc36 100644 --- a/apps/block_scout_web/lib/block_scout_web/schemas/api/v2/internal_transaction.ex +++ b/apps/block_scout_web/lib/block_scout_web/schemas/api/v2/internal_transaction.ex @@ -43,12 +43,7 @@ defmodule BlockScoutWeb.Schemas.API.V2.InternalTransaction do description: "The index of this internal transaction inside the transaction.", nullable: false }, - gas_limit: General.IntegerStringNullable, - block_index: %Schema{ - type: :integer, - description: "The index of this internal transaction inside the block.", - nullable: true - } + gas_limit: General.IntegerStringNullable }, required: [ :error, @@ -63,8 +58,7 @@ defmodule BlockScoutWeb.Schemas.API.V2.InternalTransaction do :block_number, :timestamp, :index, - :gas_limit, - :block_index + :gas_limit ], additionalProperties: false }) diff --git a/apps/block_scout_web/lib/block_scout_web/schemas/api/v2/transaction.ex b/apps/block_scout_web/lib/block_scout_web/schemas/api/v2/transaction.ex index f1e089835910..a0fe80410926 100644 --- a/apps/block_scout_web/lib/block_scout_web/schemas/api/v2/transaction.ex +++ b/apps/block_scout_web/lib/block_scout_web/schemas/api/v2/transaction.ex @@ -155,21 +155,6 @@ defmodule BlockScoutWeb.Schemas.API.V2.Transaction.ChainTypeCustomizations do def chain_type_fields(schema) do chain_type() |> case do - :polygon_zkevm -> - schema - |> Helper.extend_schema( - properties: %{ - zkevm_batch_number: %Schema{type: :integer, nullable: true}, - zkevm_sequence_hash: General.FullHash, - zkevm_verify_hash: General.FullHash, - zkevm_status: %Schema{ - type: :string, - enum: ["Confirmed by Sequencer", "L1 Confirmed"], - nullable: false - } - } - ) - :zksync -> schema |> Helper.extend_schema(properties: %{zksync: @zksync_schema}) @@ -329,7 +314,6 @@ defmodule BlockScoutWeb.Schemas.API.V2.Transaction do alias BlockScoutWeb.Schemas.API.V2.{Address, General, SignedAuthorization, TokenTransfer} alias BlockScoutWeb.Schemas.API.V2.Transaction.{ChainTypeCustomizations, Fee} - alias Explorer.Chain.TransactionAction alias OpenApiSpex.Schema OpenApiSpex.schema( @@ -421,32 +405,6 @@ defmodule BlockScoutWeb.Schemas.API.V2.Transaction do decoded_input: %Schema{allOf: [General.DecodedInput], nullable: true}, token_transfers: %Schema{type: :array, items: TokenTransfer, nullable: true}, token_transfers_overflow: %Schema{type: :boolean, nullable: true}, - actions: %Schema{ - type: :array, - items: %Schema{ - type: :object, - required: [:protocol, :type, :data], - properties: %{ - protocol: %Schema{ - type: :string, - enum: TransactionAction.supported_protocols(), - nullable: false - }, - type: %Schema{ - type: :string, - enum: TransactionAction.supported_types(), - nullable: false - }, - data: %Schema{ - type: :object, - description: "Transaction action details (json formatted)", - nullable: false - } - }, - additionalProperties: false - }, - nullable: true - }, exchange_rate: General.FloatStringNullable, historic_exchange_rate: General.FloatStringNullable, method: General.MethodNameNullable, @@ -511,7 +469,6 @@ defmodule BlockScoutWeb.Schemas.API.V2.Transaction do :decoded_input, :token_transfers, :token_transfers_overflow, - :actions, :exchange_rate, :historic_exchange_rate, :method, diff --git a/apps/block_scout_web/lib/block_scout_web/specs/public.ex b/apps/block_scout_web/lib/block_scout_web/specs/public.ex index 569de9b41a2d..86dbf1c16e67 100644 --- a/apps/block_scout_web/lib/block_scout_web/specs/public.ex +++ b/apps/block_scout_web/lib/block_scout_web/specs/public.ex @@ -5,11 +5,62 @@ defmodule BlockScoutWeb.Specs.Public do alias BlockScoutWeb.Routers.{ApiRouter, SmartContractsApiV2Router, TokensApiV2Router} alias BlockScoutWeb.Specs - alias OpenApiSpex.{Contact, Info, OpenApi, Paths, Server} + alias OpenApiSpex.{Contact, Info, OpenApi, Paths, Server, Tag} alias Utils.Helper + use Utils.CompileTimeEnvHelper, + chain_identity: [:explorer, :chain_identity] + + use Utils.RuntimeEnvHelper, + mud_enabled?: [:explorer, [Explorer.Chain.Mud, :enabled]] + @behaviour OpenApi + @default_api_categories [ + "blocks", + "transactions", + "addresses", + "internal-transactions", + "tokens", + "token-transfers", + "smart-contracts", + "config", + "main-page", + "search", + "stats", + "csv-export", + "account-abstraction", + "withdrawals" + ] + + # todo: if new chain type is covered with OpenAPI specs + # modify this to support proper ordering: + # 1. default endpoints + # 2. chain-type specific endpoints (e.g. optimism, celo, scroll, zilliqa) + # 3. legacy endpoints + case @chain_identity do + {:optimism, :celo} -> + @chain_type_category_tags [%Tag{name: "optimism"}, %Tag{name: "celo"}] + defp chain_type_category_tags, do: @chain_type_category_tags + + {:optimism, nil} -> + defp chain_type_category_tags do + if mud_enabled?() do + [%Tag{name: "optimism"}, %Tag{name: "mud"}] + else + [%Tag{name: "optimism"}] + end + end + + {chain_type, nil} when chain_type in [:scroll, :zilliqa] -> + @chain_type_category_tags [%Tag{name: to_string(chain_type)}] + defp chain_type_category_tags, do: @chain_type_category_tags + + _ -> + @chain_type_category_tags [] + defp chain_type_category_tags, do: @chain_type_category_tags + end + @impl OpenApi def spec do %OpenApi{ @@ -27,7 +78,10 @@ defmodule BlockScoutWeb.Specs.Public do ApiRouter |> Paths.from_router() |> Map.merge(Paths.from_routes(Specs.routes_with_prefix(TokensApiV2Router, "/v2/tokens"))) - |> Map.merge(Paths.from_routes(Specs.routes_with_prefix(SmartContractsApiV2Router, "/v2/smart-contracts"))) + |> Map.merge(Paths.from_routes(Specs.routes_with_prefix(SmartContractsApiV2Router, "/v2/smart-contracts"))), + tags: + Enum.map(@default_api_categories, fn category -> %Tag{name: category} end) ++ + chain_type_category_tags() ++ [%Tag{name: "legacy"}] } |> OpenApiSpex.resolve_schema_modules() end diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex index 2caf404faf1a..f8be8cc5893a 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex @@ -215,29 +215,6 @@ <% end %> - - <% %{transaction_actions: transaction_actions} = transaction_actions(@transaction) %> - <%= unless Enum.empty?(transaction_actions) do %> -
-
- <%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", - text: gettext("Highlighted events of the transaction.") %> - <%= gettext "Transaction Action" %> -

<%= gettext "Scroll to see more" %>

-
-
-
- <% transaction_actions_indexed = Enum.with_index(transaction_actions) %> - <% transaction_actions_length = Enum.count(transaction_actions) %> - <%= for {action, i} <- transaction_actions_indexed do %> - <% action_assigns = Map.put(assigns, :action, action) %> - <% action_assigns = Map.put(action_assigns, :isLast, (i == transaction_actions_length - 1)) %> - <%= render BlockScoutWeb.TransactionView, "_actions.html", action_assigns %> - <% end %> -
-
-
- <% 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 e3ef168a216c..247d339e07e8 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 @@ -275,7 +275,7 @@ defmodule BlockScoutWeb.AddressView do end def transaction_hash(%Address{contract_creation_internal_transaction: %InternalTransaction{}} = address) do - address.contract_creation_internal_transaction.transaction_hash + InternalTransaction.preload_transaction(address.contract_creation_internal_transaction).transaction_hash end def transaction_hash(%Address{contract_creation_transaction: %Transaction{}} = address) do diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/legacy/block_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/legacy/block_view.ex new file mode 100644 index 000000000000..91b6820fee3d --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/api/legacy/block_view.ex @@ -0,0 +1,15 @@ +defmodule BlockScoutWeb.API.Legacy.BlockView do + @moduledoc false + + alias BlockScoutWeb.API.RPC.BlockView, as: V1BlockView + + # The v1 controller calls render/2 without a template name on the success path; + # Phoenix derives it from conn.private.phoenix_action, which for this wrapper is + # :get_block_number_by_time → "get_block_number_by_time.json". Bridge that to the + # v1 template name so the delegate view resolves correctly. + def render("get_block_number_by_time.json", assigns) do + V1BlockView.render("getblocknobytime.json", assigns) + end + + defdelegate render(template, assigns), to: V1BlockView +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/legacy/logs_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/legacy/logs_view.ex new file mode 100644 index 000000000000..caa59cbd50d1 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/api/legacy/logs_view.ex @@ -0,0 +1,4 @@ +defmodule BlockScoutWeb.API.Legacy.LogsView do + @moduledoc false + defdelegate render(template, assigns), to: BlockScoutWeb.API.RPC.LogsView +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/advanced_filter_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/advanced_filter_view.ex index 0d43749c90b7..9040a3ce6c86 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/advanced_filter_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/advanced_filter_view.ex @@ -2,9 +2,7 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterView do use BlockScoutWeb, :view alias BlockScoutWeb.API.V2.{Helper, TokenTransferView, TokenView, TransactionView} - alias Explorer.Chain.{Address, AdvancedFilter, Data, MethodIdentifier, Transaction} - alias Explorer.Market - alias Explorer.Market.MarketHistory + alias Explorer.Chain.{Address, Data, Transaction} def render("advanced_filters.json", %{ advanced_filters: advanced_filters, @@ -29,137 +27,6 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterView do methods end - def to_csv_format(advanced_filters) do - exchange_rate = Market.get_coin_exchange_rate() - - date_to_prices = - Enum.reduce(advanced_filters, %{}, fn af, acc -> - date = DateTime.to_date(af.timestamp) - - if Map.has_key?(acc, date) do - acc - else - market_history = MarketHistory.price_at_date(date) - - Map.put( - acc, - date, - {market_history && market_history.opening_price, market_history && market_history.closing_price} - ) - end - end) - - row_names = [ - "TxHash", - "Type", - "MethodId", - "UtcTimestamp", - "FromAddress", - "ToAddress", - "CreatedContractAddress", - "Value", - "TokenContractAddressHash", - "TokenDecimals", - "TokenSymbol", - "TokenValue", - "TokenID", - "BlockNumber", - "Fee", - "CurrentPrice", - "TxDateOpeningPrice", - "TxDateClosingPrice" - ] - - af_lists = - advanced_filters - |> Stream.map(fn advanced_filter -> - method_id = - case advanced_filter.input do - %{bytes: <>} -> - {:ok, method_id} = MethodIdentifier.cast(method_id) - to_string(method_id) - - _ -> - nil - end - - {opening_price, closing_price} = date_to_prices[DateTime.to_date(advanced_filter.timestamp)] - - prepare_advanced_filter_csv_row(advanced_filter, exchange_rate, opening_price, closing_price, method_id) - end) - - Stream.concat([row_names], af_lists) - end - - defp prepare_advanced_filter_csv_row( - %AdvancedFilter{created_from: :token_transfer} = advanced_filter, - _exchange_rate, - _opening_price, - _closing_price, - method_id - ) do - token_transfer_total = TokenTransferView.prepare_token_transfer_total(advanced_filter.token_transfer) - - [ - to_string(advanced_filter.hash), - advanced_filter.type, - method_id, - advanced_filter.timestamp, - Address.checksum(advanced_filter.from_address_hash), - Address.checksum(advanced_filter.to_address_hash), - Address.checksum(advanced_filter.created_contract_address_hash), - decimal_to_string(advanced_filter.value, :normal), - Address.checksum(advanced_filter.token_transfer.token.contract_address_hash), - decimal_to_string(token_transfer_total["decimals"], :normal), - advanced_filter.token_transfer.token.symbol, - case token_transfer_total["decimals"] do - nil -> - decimal_to_string(token_transfer_total["value"], :xsd) - - decimals -> - token_transfer_total["value"] && - token_transfer_total["value"] - |> Decimal.div(Integer.pow(10, Decimal.to_integer(decimals))) - |> decimal_to_string(:xsd) - end, - token_transfer_total["token_id"], - advanced_filter.block_number, - decimal_to_string(advanced_filter.fee, :normal), - nil, - nil, - nil - ] - end - - defp prepare_advanced_filter_csv_row( - advanced_filter, - exchange_rate, - opening_price, - closing_price, - method_id - ) do - [ - to_string(advanced_filter.hash), - advanced_filter.type, - method_id, - advanced_filter.timestamp, - Address.checksum(advanced_filter.from_address_hash), - Address.checksum(advanced_filter.to_address_hash), - Address.checksum(advanced_filter.created_contract_address_hash), - decimal_to_string(advanced_filter.value, :normal), - nil, - nil, - nil, - nil, - nil, - advanced_filter.block_number, - decimal_to_string(advanced_filter.fee, :normal), - decimal_to_string(exchange_rate.fiat_value, :xsd), - decimal_to_string(opening_price, :xsd), - decimal_to_string(closing_price, :xsd) - ] - end - defp prepare_advanced_filter(advanced_filter, decoded_input) do %{ hash: advanced_filter.hash, @@ -234,7 +101,4 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterView do %{methods: method_ids, tokens: tokens_map} end - - defp decimal_to_string(nil, _), do: nil - defp decimal_to_string(decimal, type), do: Decimal.to_string(decimal, type) end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/csv_export_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/csv_export_view.ex new file mode 100644 index 000000000000..069a7f07a627 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/csv_export_view.ex @@ -0,0 +1,14 @@ +defmodule BlockScoutWeb.API.V2.CsvExportView do + @moduledoc """ + View for CSV export API endpoints. + """ + use BlockScoutWeb, :view + + def render("csv_export.json", %{request: %{status: status, file_id: file_id, expires_at: expires_at}}) do + %{ + status: status, + file_id: file_id, + expires_at: expires_at + } + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/filecoin_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/filecoin_view.ex index 0ebe78bdc3a7..7af8f8d25ad5 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/filecoin_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/filecoin_view.ex @@ -33,9 +33,16 @@ defmodule BlockScoutWeb.API.V2.FilecoinView do }) :: map() def preload_and_put_filecoin_robust_address(result, %{address_hash: address_hash} = params) do - address = address_hash && Address.get(address_hash, @api_true) - - put_filecoin_robust_address(result, Map.put(params, :address, address)) + address = + case address_hash do + hash when is_binary(hash) and hash != "" -> Address.get(hash, @api_true) + _ -> nil + end + + params + |> Map.put_new(:field_prefix, nil) + |> Map.put(:address, address) + |> then(&put_filecoin_robust_address(result, &1)) end def preload_and_put_filecoin_robust_address(result, _params) do diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/helper.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/helper.ex index 17fbf1488fda..8db225233e38 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/helper.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/helper.ex @@ -5,7 +5,7 @@ defmodule BlockScoutWeb.API.V2.Helper do use Utils.CompileTimeEnvHelper, chain_type: [:explorer, :chain_type] alias Ecto.Association.NotLoaded - alias Explorer.Chain.{Address, Address.Reputation, SmartContract} + alias Explorer.Chain.{Address, Address.Reputation} alias Explorer.Chain.SmartContract.Proxy alias Explorer.Chain.Transaction.History.TransactionStats @@ -187,11 +187,10 @@ defmodule BlockScoutWeb.API.V2.Helper do - `false` if the smart contract is `NotLoaded`. - `true` if the smart contract is present and does not have metadata from a verified bytecode twin. """ - @spec smart_contract_verified?(Address.t()) :: boolean() - def smart_contract_verified?(%Address{smart_contract: nil}), do: false - def smart_contract_verified?(%Address{smart_contract: %{metadata_from_verified_bytecode_twin: true}}), do: false - def smart_contract_verified?(%Address{smart_contract: %NotLoaded{}}), do: nil - def smart_contract_verified?(%Address{smart_contract: %SmartContract{}}), do: true + @spec smart_contract_verified?(Address.t()) :: boolean() | nil + def smart_contract_verified?(%Address{verified: verified}) when is_boolean(verified), do: verified + def smart_contract_verified?(%Address{verified: nil}), do: nil + def smart_contract_verified?(_), do: false def market_cap(:standard, %{ available_supply: available_supply, diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/internal_transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/internal_transaction_view.ex index 25541e0017ad..b882d96dcf31 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/internal_transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/internal_transaction_view.ex @@ -51,8 +51,7 @@ defmodule BlockScoutWeb.API.V2.InternalTransactionView do "block_number" => internal_transaction.block_number, "timestamp" => (block && block.timestamp) || (internal_transaction.block && internal_transaction.block.timestamp), "index" => internal_transaction.index, - "gas_limit" => internal_transaction.gas || Decimal.new(0), - "block_index" => internal_transaction.block_index + "gas_limit" => internal_transaction.gas || Decimal.new(0) } end end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/polygon_zkevm_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/polygon_zkevm_view.ex deleted file mode 100644 index 63b726f35663..000000000000 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/polygon_zkevm_view.ex +++ /dev/null @@ -1,190 +0,0 @@ -defmodule BlockScoutWeb.API.V2.PolygonZkevmView do - use BlockScoutWeb, :view - - alias Explorer.Chain.PolygonZkevm.Reader - alias Explorer.Chain.Transaction - - @doc """ - Function to render GET requests to `/api/v2/zkevm/batches/:batch_number` endpoint. - """ - @spec render(binary(), map()) :: map() | non_neg_integer() - def render("zkevm_batch.json", %{batch: batch}) do - sequence_transaction_hash = - if Map.has_key?(batch, :sequence_transaction) and not is_nil(batch.sequence_transaction) do - batch.sequence_transaction.hash - end - - verify_transaction_hash = - if Map.has_key?(batch, :verify_transaction) and not is_nil(batch.verify_transaction) do - batch.verify_transaction.hash - end - - l2_transactions = - if Map.has_key?(batch, :l2_transactions) do - Enum.map(batch.l2_transactions, fn transaction -> transaction.hash end) - end - - %{ - "number" => batch.number, - "status" => batch_status(batch), - "timestamp" => batch.timestamp, - "transactions" => l2_transactions, - "global_exit_root" => batch.global_exit_root, - "acc_input_hash" => batch.acc_input_hash, - "sequence_transaction_hash" => sequence_transaction_hash, - "verify_transaction_hash" => verify_transaction_hash, - "state_root" => batch.state_root - } - end - - @doc """ - Function to render GET requests to `/api/v2/zkevm/batches` endpoint. - """ - def render("zkevm_batches.json", %{ - batches: batches, - next_page_params: next_page_params - }) do - %{ - items: render_zkevm_batches(batches), - next_page_params: next_page_params - } - end - - @doc """ - Function to render GET requests to `/api/v2/main-page/zkevm/batches/confirmed` endpoint. - """ - def render("zkevm_batches.json", %{batches: batches}) do - %{items: render_zkevm_batches(batches)} - end - - @doc """ - Function to render GET requests to `/api/v2/zkevm/batches/count` endpoint. - """ - def render("zkevm_batches_count.json", %{count: count}) do - count - end - - @doc """ - Function to render GET requests to `/api/v2/main-page/zkevm/batches/latest-number` endpoint. - """ - def render("zkevm_batch_latest_number.json", %{number: number}) do - number - end - - @doc """ - Function to render GET requests to `/api/v2/zkevm/deposits` and `/api/v2/zkevm/withdrawals` endpoints. - """ - def render("polygon_zkevm_bridge_items.json", %{ - items: items, - next_page_params: next_page_params - }) do - env = Application.get_all_env(:indexer)[Indexer.Fetcher.PolygonZkevm.BridgeL1] - - %{ - items: - Enum.map(items, fn item -> - l1_token = if is_nil(Map.get(item, :l1_token)), do: %{}, else: Map.get(item, :l1_token) - l2_token = if is_nil(Map.get(item, :l2_token)), do: %{}, else: Map.get(item, :l2_token) - - decimals = - cond do - not is_nil(Map.get(l1_token, :decimals)) -> Reader.sanitize_decimals(Map.get(l1_token, :decimals)) - not is_nil(Map.get(l2_token, :decimals)) -> Reader.sanitize_decimals(Map.get(l2_token, :decimals)) - true -> env[:native_decimals] - end - - symbol = - cond do - not is_nil(Map.get(l1_token, :symbol)) -> Map.get(l1_token, :symbol) - not is_nil(Map.get(l2_token, :symbol)) -> Map.get(l2_token, :symbol) - true -> env[:native_symbol] - end - - %{ - "block_number" => item.block_number, - "index" => item.index, - "l1_transaction_hash" => item.l1_transaction_hash, - "timestamp" => item.block_timestamp, - "l2_transaction_hash" => item.l2_transaction_hash, - "value" => fractional(Decimal.new(item.amount), Decimal.new(decimals)), - "symbol" => symbol - } - end), - next_page_params: next_page_params - } - end - - @doc """ - Function to render GET requests to `/api/v2/zkevm/deposits/count` and `/api/v2/zkevm/withdrawals/count` endpoints. - """ - def render("polygon_zkevm_bridge_items_count.json", %{count: count}) do - count - end - - defp batch_status(batch) do - sequence_id = Map.get(batch, :sequence_id) - verify_id = Map.get(batch, :verify_id) - - cond do - is_nil(sequence_id) && is_nil(verify_id) -> "Unfinalized" - !is_nil(sequence_id) && is_nil(verify_id) -> "L1 Sequence Confirmed" - !is_nil(verify_id) -> "Finalized" - end - end - - defp fractional(%Decimal{} = amount, %Decimal{} = decimals) do - amount.sign - |> Decimal.new(amount.coef, amount.exp - Decimal.to_integer(decimals)) - |> Decimal.normalize() - |> Decimal.to_string(:normal) - end - - defp render_zkevm_batches(batches) do - Enum.map(batches, fn batch -> - sequence_transaction_hash = - if not is_nil(batch.sequence_transaction) do - batch.sequence_transaction.hash - end - - verify_transaction_hash = - if not is_nil(batch.verify_transaction) do - batch.verify_transaction.hash - end - - %{ - "number" => batch.number, - "status" => batch_status(batch), - "timestamp" => batch.timestamp, - "transactions_count" => batch.l2_transactions_count, - "sequence_transaction_hash" => sequence_transaction_hash, - "verify_transaction_hash" => verify_transaction_hash - } - end) - end - - def extend_transaction_json_response(out_json, %Transaction{} = transaction) do - extended_result = - out_json - |> add_optional_transaction_field(transaction, "zkevm_batch_number", :zkevm_batch, :number) - |> add_optional_transaction_field(transaction, "zkevm_sequence_hash", :zkevm_sequence_transaction, :hash) - |> add_optional_transaction_field(transaction, "zkevm_verify_hash", :zkevm_verify_transaction, :hash) - - Map.put(extended_result, "zkevm_status", zkevm_status(extended_result)) - end - - defp zkevm_status(result_map) do - if is_nil(Map.get(result_map, "zkevm_sequence_hash")) do - "Confirmed by Sequencer" - else - "L1 Confirmed" - end - end - - defp add_optional_transaction_field(out_json, transaction, out_field, association, association_field) do - case Map.get(transaction, association) do - nil -> out_json - %Ecto.Association.NotLoaded{} -> out_json - item -> Map.put(out_json, out_field, Map.get(item, association_field)) - end - end -end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/token_transfer_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/token_transfer_view.ex index 7420c4192948..50b439f595d2 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/token_transfer_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/token_transfer_view.ex @@ -2,7 +2,6 @@ defmodule BlockScoutWeb.API.V2.TokenTransferView do use BlockScoutWeb, :view alias BlockScoutWeb.API.V2.{Helper, TokenView, TransactionView} - alias BlockScoutWeb.Tokens.Helper, as: TokensHelper alias Ecto.Association.NotLoaded alias Explorer.Chain alias Explorer.Chain.{TokenTransfer, Transaction} @@ -64,13 +63,16 @@ defmodule BlockScoutWeb.API.V2.TokenTransferView do } end + # NOTE: Duplicated with `token_instance` reduction in + # Explorer.Chain.CsvExport.AdvancedFilter @doc """ - Prepares token transfer total value/id transferred to be returned in the API v2 endpoints. + Prepares token transfer total value/id transferred to be returned in the + API v2 endpoints. """ @spec prepare_token_transfer_total(TokenTransfer.t()) :: map() # credo:disable-for-next-line /Complexity/ def prepare_token_transfer_total(token_transfer) do - case TokensHelper.token_transfer_amount_for_api(token_transfer) do + case TokenTransfer.token_transfer_amount_for_api(token_transfer) do {:ok, :erc721_instance} -> %{ "token_id" => token_transfer.token_ids && List.first(token_transfer.token_ids), diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex index f086e7ee5e74..5f23271f6f2b 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex @@ -162,10 +162,6 @@ defmodule BlockScoutWeb.API.V2.TransactionView do TokenTransferView.prepare_token_transfer(token_transfer, conn, decoded_transaction) end - def render("transaction_actions.json", %{actions: actions}) do - Enum.map(actions, &prepare_transaction_action(&1)) - end - def render("internal_transactions.json", %{ internal_transactions: internal_transactions, next_page_params: next_page_params, @@ -555,7 +551,6 @@ defmodule BlockScoutWeb.API.V2.TransactionView do "decoded_input" => decoded_input_data, "token_transfers" => token_transfers(transaction.token_transfers, conn, single_transaction?), "token_transfers_overflow" => token_transfers_overflow(transaction.token_transfers, single_transaction?), - "actions" => transaction_actions(transaction.transaction_actions), "exchange_rate" => Market.get_coin_exchange_rate().fiat_value, "historic_exchange_rate" => historic_exchange_rate(block_timestamp), "method" => Transaction.method_name(transaction, decoded_input), @@ -626,15 +621,6 @@ defmodule BlockScoutWeb.API.V2.TransactionView do def token_transfers_overflow(token_transfers, _), do: Enum.count(token_transfers) > Chain.get_token_transfers_per_transaction_preview_count() - def transaction_actions(%NotLoaded{}), do: [] - - @doc """ - Renders transaction actions - """ - def transaction_actions(actions) do - render("transaction_actions.json", %{actions: actions}) - end - @doc """ Renders the authorization list for a transaction. @@ -999,18 +985,6 @@ defmodule BlockScoutWeb.API.V2.TransactionView do ) end - defp do_with_chain_type_fields( - result, - :polygon_zkevm, - transaction, - true = _single_transaction?, - _conn, - _watchlist_names - ) do - # credo:disable-for-next-line Credo.Check.Design.AliasUsage - BlockScoutWeb.API.V2.PolygonZkevmView.extend_transaction_json_response(result, transaction) - end - defp do_with_chain_type_fields(result, :zksync, transaction, true = _single_transaction?, _conn, _watchlist_names) do # credo:disable-for-next-line Credo.Check.Design.AliasUsage BlockScoutWeb.API.V2.ZkSyncView.extend_transaction_json_response(result, transaction) diff --git a/apps/block_scout_web/lib/block_scout_web/views/tokens/helper.ex b/apps/block_scout_web/lib/block_scout_web/views/tokens/helper.ex index 750b2d89b286..2e0663a3ea54 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/tokens/helper.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/tokens/helper.ex @@ -120,128 +120,6 @@ defmodule BlockScoutWeb.Tokens.Helper do nil end - def token_transfer_amount_for_api(%{ - token: token, - token_type: token_type, - amount: amount, - amounts: amounts, - token_ids: token_ids - }) do - do_token_transfer_amount_for_api(token, token_type, amount, amounts, token_ids) - end - - def token_transfer_amount_for_api(%{token: token, token_type: token_type, amount: amount, token_ids: token_ids}) do - do_token_transfer_amount_for_api(token, token_type, amount, nil, token_ids) - end - - # TODO: remove this clause along with token transfer denormalization - defp do_token_transfer_amount_for_api(%Token{type: "ERC-20"}, nil, nil, nil, _token_ids) do - {:ok, nil} - end - - defp do_token_transfer_amount_for_api(_token, "ERC-20", nil, nil, _token_ids) do - {:ok, nil} - end - - # TODO: remove this clause along with token transfer denormalization - defp do_token_transfer_amount_for_api( - %Token{type: "ERC-20", decimals: decimals}, - nil, - amount, - _amounts, - _token_ids - ) do - {:ok, amount, decimals} - end - - defp do_token_transfer_amount_for_api( - %Token{decimals: decimals}, - "ERC-20", - amount, - _amounts, - _token_ids - ) do - {:ok, amount, decimals} - end - - # TODO: remove this clause along with token transfer denormalization - defp do_token_transfer_amount_for_api(%Token{type: "ZRC-2"}, nil, nil, nil, _token_ids) do - {:ok, nil} - end - - defp do_token_transfer_amount_for_api(_token, "ZRC-2", nil, nil, _token_ids) do - {:ok, nil} - end - - # TODO: remove this clause along with token transfer denormalization - defp do_token_transfer_amount_for_api( - %Token{type: "ZRC-2", decimals: decimals}, - nil, - amount, - _amounts, - _token_ids - ) do - {:ok, amount, decimals} - end - - defp do_token_transfer_amount_for_api( - %Token{decimals: decimals}, - "ZRC-2", - amount, - _amounts, - _token_ids - ) do - {:ok, amount, decimals} - end - - # TODO: remove this clause along with token transfer denormalization - defp do_token_transfer_amount_for_api(%Token{type: "ERC-721"}, nil, _amount, _amounts, _token_ids) do - {:ok, :erc721_instance} - end - - defp do_token_transfer_amount_for_api(_token, "ERC-721", _amount, _amounts, _token_ids) do - {:ok, :erc721_instance} - end - - # TODO: remove this clause along with token transfer denormalization - defp do_token_transfer_amount_for_api( - %Token{type: type, decimals: decimals}, - nil, - amount, - amounts, - token_ids - ) - when type in ["ERC-1155", "ERC-404"] do - if amount do - {:ok, :erc1155_erc404_instance, amount, decimals} - else - {:ok, :erc1155_erc404_instance, amounts, token_ids, decimals} - end - end - - defp do_token_transfer_amount_for_api( - %Token{decimals: decimals}, - type, - amount, - amounts, - token_ids - ) - when type in ["ERC-1155", "ERC-404"] do - if amount do - {:ok, :erc1155_erc404_instance, amount, decimals} - else - {:ok, :erc1155_erc404_instance, amounts, token_ids, decimals} - end - end - - defp do_token_transfer_amount_for_api(%Token{decimals: decimals}, "ERC-7984", _amount, _amounts, _token_ids) do - {:ok, nil, decimals} - end - - defp do_token_transfer_amount_for_api(_token, _token_type, _amount, _amounts, _token_ids) do - nil - end - @doc """ Returns the token's symbol. diff --git a/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex index 09da0f1d0e2b..dc4788cfa1d0 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex @@ -64,10 +64,6 @@ defmodule BlockScoutWeb.TransactionView do if type, do: {type, transaction_with_transfers_filtered}, else: {nil, transaction_with_transfers_filtered} end - def transaction_actions(transaction) do - Repo.preload(transaction, :transaction_actions) - end - def aggregate_token_transfers(token_transfers) do %{ transfers: {ft_transfers, nft_transfers}, diff --git a/apps/block_scout_web/mix.exs b/apps/block_scout_web/mix.exs index 9cdb3b0117e4..5caf64aabfaf 100644 --- a/apps/block_scout_web/mix.exs +++ b/apps/block_scout_web/mix.exs @@ -19,10 +19,9 @@ defmodule BlockScoutWeb.Mixfile do lockfile: "../../mix.lock", package: package(), start_permanent: Mix.env() == :prod, - version: "10.2.1", + version: "11.0.0", xref: [ exclude: [ - Explorer.Chain.PolygonZkevm.Reader, Explorer.Chain.Beacon.Reader, Explorer.Chain.Cache.OptimismFinalizationPeriod, Explorer.Chain.Optimism.OutputRoot, @@ -190,7 +189,7 @@ defmodule BlockScoutWeb.Mixfile do defp package do [ maintainers: ["Blockscout"], - licenses: ["GPL 3.0"], + licenses: ["Blockscout Software Licence"], links: %{"GitHub" => "https://github.com/blockscout/blockscout"} ] end diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index a1e52c49bd8c..67fde89acea4 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -11,57 +11,47 @@ msgid "" msgstr "" -#: lib/block_scout_web/templates/common_components/_minimal_proxy_pattern.html.eex:9 #: lib/block_scout_web/templates/common_components/_minimal_proxy_pattern.html.eex:9 #, elixir-autogen, elixir-format msgid " - minimal bytecode implementation that delegates all calls to a known address" msgstr "" -#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:14 #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:14 #, elixir-autogen, elixir-format msgid " is recommended." msgstr "" -#: lib/block_scout_web/templates/address/_metatags.html.eex:3 #: lib/block_scout_web/templates/address/_metatags.html.eex:3 #, elixir-autogen, elixir-format msgid "%{address} - %{subnetwork} Explorer" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:12 #: lib/block_scout_web/templates/block/overview.html.eex:12 #, elixir-autogen, elixir-format msgid "%{block_type} Details" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:55 #: lib/block_scout_web/templates/block/overview.html.eex:55 #, elixir-autogen, elixir-format msgid "%{block_type} Height" msgstr "" -#: lib/block_scout_web/templates/block/index.html.eex:7 #: lib/block_scout_web/templates/block/index.html.eex:7 #, elixir-autogen, elixir-format msgid "%{block_type}s" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:85 #: lib/block_scout_web/templates/block/overview.html.eex:85 #, elixir-autogen, elixir-format msgid "%{count} Transaction" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:87 #: lib/block_scout_web/templates/block/overview.html.eex:87 #: lib/block_scout_web/templates/chain/_block.html.eex:11 -#: lib/block_scout_web/templates/chain/_block.html.eex:11 #, elixir-autogen, elixir-format msgid "%{count} Transactions" msgstr "" -#: lib/block_scout_web/views/address_token_balance_view.ex:10 #: lib/block_scout_web/views/address_token_balance_view.ex:10 #, elixir-autogen, elixir-format msgid "%{count} token" @@ -69,7 +59,6 @@ msgid_plural "%{count} tokens" msgstr[0] "" msgstr[1] "" -#: lib/block_scout_web/templates/block/_tile.html.eex:29 #: lib/block_scout_web/templates/block/_tile.html.eex:29 #, elixir-autogen, elixir-format msgid "%{count} transaction" @@ -77,1050 +66,809 @@ msgid_plural "%{count} transactions" msgstr[0] "" msgstr[1] "" -#: lib/block_scout_web/templates/transaction/_actions.html.eex:101 #: lib/block_scout_web/templates/transaction/_actions.html.eex:101 #, elixir-autogen, elixir-format msgid "%{qty} of Token ID [%{link_to_id}]" msgstr "" -#: lib/block_scout_web/templates/chain/_metatags.html.eex:2 #: lib/block_scout_web/templates/chain/_metatags.html.eex:2 #, elixir-autogen, elixir-format msgid "%{subnetwork} %{network} Explorer" msgstr "" -#: lib/block_scout_web/templates/layout/_default_title.html.eex:2 #: lib/block_scout_web/templates/layout/_default_title.html.eex:2 #, elixir-autogen, elixir-format msgid "%{subnetwork} Explorer - BlockScout" msgstr "" -#: lib/block_scout_web/templates/withdrawal/index.html.eex:11 #: lib/block_scout_web/templates/withdrawal/index.html.eex:11 #, elixir-autogen, elixir-format msgid "%{withdrawals_count} withdrawals processed and %{withdrawals_sum} withdrawn." msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:375 -#: lib/block_scout_web/views/transaction_view.ex:375 +#: lib/block_scout_web/views/transaction_view.ex:373 #, elixir-autogen, elixir-format msgid "(Awaiting internal transactions for status)" msgstr "" -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:59 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:59 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:70 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:70 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:82 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:82 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:104 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:104 #, elixir-autogen, elixir-format msgid "(query)" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_first.html.eex:4 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_first.html.eex:4 #, elixir-autogen, elixir-format msgid ") may be added for each contract. Click the Add Library button to add an additional one." msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:93 #: lib/block_scout_web/templates/layout/app.html.eex:93 #, elixir-autogen, elixir-format msgid "- We're indexing this chain right now. Some of the counts may be inaccurate." msgstr "" -#: lib/block_scout_web/templates/transaction/not_found.html.eex:8 #: lib/block_scout_web/templates/transaction/not_found.html.eex:8 #, elixir-autogen, elixir-format msgid "1. If you have just submitted this transaction please wait for at least 30 seconds before refreshing this page." msgstr "" -#: lib/block_scout_web/templates/transaction/not_found.html.eex:9 #: lib/block_scout_web/templates/transaction/not_found.html.eex:9 #, elixir-autogen, elixir-format msgid "2. It could still be in the TX Pool of a different node, waiting to be broadcasted." msgstr "" -#: lib/block_scout_web/templates/transaction/not_found.html.eex:10 #: lib/block_scout_web/templates/transaction/not_found.html.eex:10 #, elixir-autogen, elixir-format msgid "3. During times when the network is busy (i.e during ICOs) it can take a while for your transaction to propagate through the network and for us to index it." msgstr "" -#: lib/block_scout_web/templates/transaction/not_found.html.eex:11 #: lib/block_scout_web/templates/transaction/not_found.html.eex:11 #, elixir-autogen, elixir-format msgid "4. If it still does not show up after 1 hour, please check with your sender/exchange/wallet/transaction provider for additional information." msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:197 #: lib/block_scout_web/templates/block/overview.html.eex:197 #, elixir-autogen, elixir-format msgid "64-bit hash of value verifying proof-of-work (note: null for POA chains)." msgstr "" #: lib/block_scout_web/templates/block/overview.html.eex:97 -#: lib/block_scout_web/templates/block/overview.html.eex:97 -#: lib/block_scout_web/templates/transaction_state/_state_change.html.eex:21 #: lib/block_scout_web/templates/transaction_state/_state_change.html.eex:21 #, elixir-autogen, elixir-format msgid "A block producer who successfully included the block onto the blockchain." msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:100 #: lib/block_scout_web/templates/layout/app.html.eex:100 #, elixir-autogen, elixir-format msgid "A confirmation email was sent to" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_first.html.eex:4 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_first.html.eex:4 #, elixir-autogen, elixir-format msgid "A library name called in the .sol file. Multiple libraries (up to " msgstr "" -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:73 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:73 #, elixir-autogen, elixir-format msgid "A string with the name of the action to be invoked." msgstr "" -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:62 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:62 #, elixir-autogen, elixir-format msgid "A string with the name of the module to be invoked." msgstr "" -#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:24 #: lib/block_scout_web/templates/account/custom_abi/form.html.eex:24 #, elixir-autogen, elixir-format msgid "ABI" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_constructor_args.html.eex:3 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_constructor_args.html.eex:3 #, elixir-autogen, elixir-format msgid "ABI-encoded Constructor Arguments (if required by the contract)" msgstr "" -#: lib/block_scout_web/templates/api_docs/index.html.eex:4 #: lib/block_scout_web/templates/api_docs/index.html.eex:4 #, elixir-autogen, elixir-format msgid "API Documentation" msgstr "" -#: lib/block_scout_web/templates/api_docs/_metatags.html.eex:4 #: lib/block_scout_web/templates/api_docs/_metatags.html.eex:4 #, elixir-autogen, elixir-format msgid "API endpoints for the %{subnetwork}" msgstr "" -#: lib/block_scout_web/templates/api_docs/_metatags.html.eex:2 #: lib/block_scout_web/templates/api_docs/_metatags.html.eex:2 #, elixir-autogen, elixir-format msgid "API for the %{subnetwork} - BlockScout" msgstr "" -#: lib/block_scout_web/templates/account/api_key/form.html.eex:7 #: lib/block_scout_web/templates/account/api_key/form.html.eex:7 #: lib/block_scout_web/templates/account/api_key/form.html.eex:13 -#: lib/block_scout_web/templates/account/api_key/form.html.eex:13 -#: lib/block_scout_web/templates/account/api_key/form.html.eex:14 #: lib/block_scout_web/templates/account/api_key/form.html.eex:14 #: lib/block_scout_web/templates/account/api_key/index.html.eex:29 -#: lib/block_scout_web/templates/account/api_key/index.html.eex:29 #, elixir-autogen, elixir-format msgid "API key" msgstr "" -#: lib/block_scout_web/templates/account/api_key/index.html.eex:7 #: lib/block_scout_web/templates/account/api_key/index.html.eex:7 #: lib/block_scout_web/templates/account/common/_nav.html.eex:16 -#: lib/block_scout_web/templates/account/common/_nav.html.eex:16 -#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:18 #: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:18 #, elixir-autogen, elixir-format msgid "API keys" msgstr "" -#: lib/block_scout_web/templates/layout/_topnav.html.eex:106 #: lib/block_scout_web/templates/layout/_topnav.html.eex:106 #, elixir-autogen, elixir-format msgid "APIs" msgstr "" -#: lib/block_scout_web/templates/account/tag_address/index.html.eex:24 #: lib/block_scout_web/templates/account/tag_address/index.html.eex:24 #: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:24 -#: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:24 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:69 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:69 #, elixir-autogen, elixir-format msgid "Action" msgstr "" -#: lib/block_scout_web/templates/account/watchlist/show.html.eex:25 #: lib/block_scout_web/templates/account/watchlist/show.html.eex:25 #, elixir-autogen, elixir-format msgid "Actions" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:484 -#: lib/block_scout_web/templates/transaction/overview.html.eex:484 +#: lib/block_scout_web/templates/transaction/overview.html.eex:461 #, elixir-autogen, elixir-format msgid "Actual gas amount used by the transaction on L2." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:488 -#: lib/block_scout_web/templates/transaction/overview.html.eex:488 +#: lib/block_scout_web/templates/transaction/overview.html.eex:465 #, elixir-autogen, elixir-format msgid "Actual gas amount used by the transaction." msgstr "" -#: lib/block_scout_web/templates/account/api_key/form.html.eex:7 #: lib/block_scout_web/templates/account/api_key/form.html.eex:7 #: lib/block_scout_web/templates/account/custom_abi/form.html.eex:8 -#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:8 -#: lib/block_scout_web/templates/layout/_add_chain_to_mm.html.eex:10 #: lib/block_scout_web/templates/layout/_add_chain_to_mm.html.eex:10 #, elixir-autogen, elixir-format msgid "Add" msgstr "" -#: lib/block_scout_web/templates/account/api_key/index.html.eex:44 #: lib/block_scout_web/templates/account/api_key/index.html.eex:44 #, elixir-autogen, elixir-format msgid "Add API key" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:86 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:86 #: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:76 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:76 #, elixir-autogen, elixir-format msgid "Add Contract Libraries" msgstr "" -#: lib/block_scout_web/templates/account/custom_abi/index.html.eex:44 #: lib/block_scout_web/templates/account/custom_abi/index.html.eex:44 #, elixir-autogen, elixir-format msgid "Add Custom ABI" msgstr "" #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:97 -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:97 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:87 #: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:87 #, elixir-autogen, elixir-format msgid "Add Library" msgstr "" -#: lib/block_scout_web/templates/account/watchlist/show.html.eex:38 #: lib/block_scout_web/templates/account/watchlist/show.html.eex:38 #, elixir-autogen, elixir-format msgid "Add address" msgstr "" -#: lib/block_scout_web/templates/account/tag_address/form.html.eex:7 #: lib/block_scout_web/templates/account/tag_address/form.html.eex:7 #: lib/block_scout_web/templates/account/tag_address/index.html.eex:37 -#: lib/block_scout_web/templates/account/tag_address/index.html.eex:37 #, elixir-autogen, elixir-format msgid "Add address tag" msgstr "" -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:7 #: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:7 #, elixir-autogen, elixir-format msgid "Add address to the Watch list" msgstr "" #: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:7 -#: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:7 -#: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:37 #: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:37 #, elixir-autogen, elixir-format msgid "Add transaction tag" msgstr "" #: lib/block_scout_web/templates/account/tag_address/form.html.eex:11 -#: lib/block_scout_web/templates/account/tag_address/form.html.eex:11 -#: lib/block_scout_web/templates/account/tag_address/index.html.eex:23 #: lib/block_scout_web/templates/account/tag_address/index.html.eex:23 #: lib/block_scout_web/templates/account/watchlist/show.html.eex:23 -#: lib/block_scout_web/templates/account/watchlist/show.html.eex:23 -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:12 #: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:12 #: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:16 -#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:16 -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_address.html.eex:4 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_address.html.eex:4 #: lib/block_scout_web/templates/tokens/index.html.eex:34 -#: lib/block_scout_web/templates/tokens/index.html.eex:34 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:29 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:29 #: lib/block_scout_web/templates/transaction_state/index.html.eex:34 -#: lib/block_scout_web/templates/transaction_state/index.html.eex:34 -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:60 #: lib/block_scout_web/templates/verified_contracts/index.html.eex:60 #: lib/block_scout_web/views/address_view.ex:110 -#: lib/block_scout_web/views/address_view.ex:110 #, elixir-autogen, elixir-format msgid "Address" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:263 -#: lib/block_scout_web/templates/transaction/overview.html.eex:263 +#: lib/block_scout_web/templates/transaction/overview.html.eex:240 #, elixir-autogen, elixir-format msgid "Address (external or contract) receiving the transaction." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:245 -#: lib/block_scout_web/templates/transaction/overview.html.eex:245 +#: lib/block_scout_web/templates/transaction/overview.html.eex:222 #, elixir-autogen, elixir-format msgid "Address (external or contract) sending the transaction." msgstr "" -#: lib/block_scout_web/templates/account/common/_nav.html.eex:10 #: lib/block_scout_web/templates/account/common/_nav.html.eex:10 #: lib/block_scout_web/templates/account/tag_address/index.html.eex:7 -#: lib/block_scout_web/templates/account/tag_address/index.html.eex:7 -#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:16 #: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:16 #, elixir-autogen, elixir-format msgid "Address Tags" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:151 #: lib/block_scout_web/templates/address/overview.html.eex:151 #, elixir-autogen, elixir-format msgid "Address balance in" msgstr "" -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:51 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:51 #, elixir-autogen, elixir-format msgid "Address of the token contract" msgstr "" -#: lib/block_scout_web/templates/transaction_state/_state_change.html.eex:7 #: lib/block_scout_web/templates/transaction_state/_state_change.html.eex:7 #, elixir-autogen, elixir-format msgid "Address used in token mintings and burnings." msgstr "" -#: lib/block_scout_web/templates/address/index.html.eex:5 #: lib/block_scout_web/templates/address/index.html.eex:5 #, elixir-autogen, elixir-format msgid "Addresses" msgstr "" #: lib/block_scout_web/templates/address_withdrawal/index.html.eex:38 -#: lib/block_scout_web/templates/address_withdrawal/index.html.eex:38 -#: lib/block_scout_web/templates/withdrawal/index.html.eex:35 #: lib/block_scout_web/templates/withdrawal/index.html.eex:35 #, elixir-autogen, elixir-format msgid "Age" msgstr "" #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:26 -#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:26 -#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:28 #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:28 #: lib/block_scout_web/templates/address_transaction/index.html.eex:22 -#: lib/block_scout_web/templates/address_transaction/index.html.eex:22 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:88 #: lib/block_scout_web/templates/layout/_topnav.html.eex:88 #: lib/block_scout_web/templates/verified_contracts/index.html.eex:20 -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:20 -#: lib/block_scout_web/views/address_internal_transaction_view.ex:12 #: lib/block_scout_web/views/address_internal_transaction_view.ex:12 #: lib/block_scout_web/views/address_token_transfer_view.ex:12 -#: lib/block_scout_web/views/address_token_transfer_view.ex:12 -#: lib/block_scout_web/views/address_transaction_view.ex:12 #: lib/block_scout_web/views/address_transaction_view.ex:12 #: lib/block_scout_web/views/verified_contracts_view.ex:13 -#: lib/block_scout_web/views/verified_contracts_view.ex:13 #, elixir-autogen, elixir-format msgid "All" msgstr "" -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:13 #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:13 #, elixir-autogen, elixir-format msgid "All functions displayed below are from ABI of that contract. In order to verify current contract, proceed with" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:27 #: lib/block_scout_web/templates/address_contract/index.html.eex:27 #, elixir-autogen, elixir-format msgid "All metadata displayed below is from that contract. In order to verify current contract, click" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:176 #: lib/block_scout_web/templates/address/overview.html.eex:176 #, elixir-autogen, elixir-format msgid "All tokens in the account and total value." msgstr "" #: lib/block_scout_web/templates/address_withdrawal/index.html.eex:41 -#: lib/block_scout_web/templates/address_withdrawal/index.html.eex:41 -#: lib/block_scout_web/templates/block_withdrawal/index.html.eex:32 #: lib/block_scout_web/templates/block_withdrawal/index.html.eex:32 #: lib/block_scout_web/templates/withdrawal/index.html.eex:38 -#: lib/block_scout_web/templates/withdrawal/index.html.eex:38 #, elixir-autogen, elixir-format msgid "Amount" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:469 -#: lib/block_scout_web/templates/transaction/overview.html.eex:469 +#: lib/block_scout_web/templates/transaction/overview.html.eex:446 #, elixir-autogen, elixir-format msgid "Amount of" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:238 #: lib/block_scout_web/templates/block/overview.html.eex:238 #, elixir-autogen, elixir-format msgid "Amount of distributed reward. Miners receive a static block reward + Tx fees + uncle fees." msgstr "" -#: lib/block_scout_web/templates/internal_server_error/index.html.eex:8 #: lib/block_scout_web/templates/internal_server_error/index.html.eex:8 #, elixir-autogen, elixir-format msgid "An unexpected error has occurred. Try reloading the page, or come back soon and try again." msgstr "" -#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:15 #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:15 #, elixir-autogen, elixir-format msgid "Anything not in this list is not supported. Click on the method to be taken to the documentation for that method, and check the notes section for any potential differences." msgstr "" -#: lib/block_scout_web/templates/layout/_topnav.html.eex:134 #: lib/block_scout_web/templates/layout/_topnav.html.eex:134 #, elixir-autogen, elixir-format msgid "Apps" msgstr "" -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:21 #: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:21 #, elixir-autogen, elixir-format msgid "Average" msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:102 -#: lib/block_scout_web/templates/chain/show.html.eex:102 +#: lib/block_scout_web/templates/chain/show.html.eex:104 #, elixir-autogen, elixir-format msgid "Average block time" msgstr "" -#: lib/block_scout_web/templates/account/api_key/form.html.eex:25 #: lib/block_scout_web/templates/account/api_key/form.html.eex:25 #, elixir-autogen, elixir-format msgid "Back to API keys (Cancel)" msgstr "" -#: lib/block_scout_web/templates/account/tag_address/form.html.eex:24 #: lib/block_scout_web/templates/account/tag_address/form.html.eex:24 #, elixir-autogen, elixir-format msgid "Back to Address Tags (Cancel)" msgstr "" -#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:30 #: lib/block_scout_web/templates/account/custom_abi/form.html.eex:30 #, elixir-autogen, elixir-format msgid "Back to Custom ABI (Cancel)" msgstr "" -#: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:24 #: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:24 #, elixir-autogen, elixir-format msgid "Back to Transaction Tags (Cancel)" msgstr "" -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:81 #: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:81 #, elixir-autogen, elixir-format msgid "Back to Watch list (Cancel)" msgstr "" -#: lib/block_scout_web/templates/error422/index.html.eex:9 #: lib/block_scout_web/templates/error422/index.html.eex:9 #: lib/block_scout_web/templates/internal_server_error/index.html.eex:9 -#: lib/block_scout_web/templates/internal_server_error/index.html.eex:9 -#: lib/block_scout_web/templates/page_not_found/index.html.eex:9 #: lib/block_scout_web/templates/page_not_found/index.html.eex:9 #: lib/block_scout_web/templates/transaction/not_found.html.eex:13 -#: lib/block_scout_web/templates/transaction/not_found.html.eex:13 #, elixir-autogen, elixir-format msgid "Back to home" msgstr "" -#: lib/block_scout_web/templates/account/watchlist/show.html.eex:24 #: lib/block_scout_web/templates/account/watchlist/show.html.eex:24 #: lib/block_scout_web/templates/address/overview.html.eex:152 -#: lib/block_scout_web/templates/address/overview.html.eex:152 -#: lib/block_scout_web/templates/address_token/overview.html.eex:51 #: lib/block_scout_web/templates/address_token/overview.html.eex:51 #: lib/block_scout_web/templates/verified_contracts/index.html.eex:63 -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:63 #, elixir-autogen, elixir-format msgid "Balance" msgstr "" -#: lib/block_scout_web/templates/transaction_state/index.html.eex:40 #: lib/block_scout_web/templates/transaction_state/index.html.eex:40 #, elixir-autogen, elixir-format msgid "Balance after" msgstr "" -#: lib/block_scout_web/templates/transaction_state/index.html.eex:37 #: lib/block_scout_web/templates/transaction_state/index.html.eex:37 #, elixir-autogen, elixir-format msgid "Balance before" msgstr "" -#: lib/block_scout_web/templates/address_coin_balance/index.html.eex:14 #: lib/block_scout_web/templates/address_coin_balance/index.html.eex:14 #, elixir-autogen, elixir-format msgid "Balances" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:209 #: lib/block_scout_web/templates/block/overview.html.eex:209 #, elixir-autogen, elixir-format msgid "Base Fee per Gas" msgstr "" -#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:5 #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:5 #: lib/block_scout_web/templates/api_docs/index.html.eex:5 -#: lib/block_scout_web/templates/api_docs/index.html.eex:5 #, elixir-autogen, elixir-format msgid "Base URL:" msgstr "" -#: lib/block_scout_web/templates/withdrawal/_metatags.html.eex:2 #: lib/block_scout_web/templates/withdrawal/_metatags.html.eex:2 #, elixir-autogen, elixir-format msgid "Beacon chain withdrawals - %{subnetwork} Explorer" msgstr "" -#: lib/block_scout_web/templates/withdrawal/_metatags.html.eex:7 #: lib/block_scout_web/templates/withdrawal/_metatags.html.eex:7 #, elixir-autogen, elixir-format msgid "Beacon chain, Withdrawals, %{subnetwork}, %{coin}" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:545 -#: lib/block_scout_web/templates/transaction/overview.html.eex:545 +#: lib/block_scout_web/templates/transaction/overview.html.eex:522 #, elixir-autogen, elixir-format msgid "Binary data included with the transaction. See input / logs below for additional info." msgstr "" -#: lib/block_scout_web/templates/address_coin_balance/_coin_balances.html.eex:8 #: lib/block_scout_web/templates/address_coin_balance/_coin_balances.html.eex:8 #: lib/block_scout_web/templates/address_withdrawal/index.html.eex:35 -#: lib/block_scout_web/templates/address_withdrawal/index.html.eex:35 -#: lib/block_scout_web/templates/block/overview.html.eex:29 #: lib/block_scout_web/templates/block/overview.html.eex:29 #: lib/block_scout_web/templates/transaction/overview.html.eex:161 -#: lib/block_scout_web/templates/transaction/overview.html.eex:161 -#: lib/block_scout_web/templates/withdrawal/index.html.eex:29 #: lib/block_scout_web/templates/withdrawal/index.html.eex:29 #, elixir-autogen, elixir-format msgid "Block" msgstr "" #: lib/block_scout_web/templates/block/_link.html.eex:2 -#: lib/block_scout_web/templates/block/_link.html.eex:2 -#: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:32 #: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:32 #: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:43 -#: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:43 #, elixir-autogen, elixir-format msgid "Block #%{number}" msgstr "" -#: lib/block_scout_web/templates/block/_metatags.html.eex:3 #: lib/block_scout_web/templates/block/_metatags.html.eex:3 #, elixir-autogen, elixir-format msgid "Block %{block_number} - %{subnetwork} Explorer" msgstr "" -#: lib/block_scout_web/templates/block_transaction/404.html.eex:7 #: lib/block_scout_web/templates/block_transaction/404.html.eex:7 #, elixir-autogen, elixir-format msgid "Block Details" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:53 #: lib/block_scout_web/templates/block/overview.html.eex:53 #, elixir-autogen, elixir-format msgid "Block Height" msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:47 #: lib/block_scout_web/templates/layout/app.html.eex:47 #, elixir-autogen, elixir-format msgid "Block Mined, awaiting import..." msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:34 #: lib/block_scout_web/views/transaction_view.ex:34 #, elixir-autogen, elixir-format msgid "Block Pending" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:158 #: lib/block_scout_web/templates/block/overview.html.eex:158 #, elixir-autogen, elixir-format msgid "Block difficulty for miner, used to calibrate block generation time (Note: constant in POA based networks)." msgstr "" -#: lib/block_scout_web/views/block_transaction_view.ex:15 -#: lib/block_scout_web/views/block_transaction_view.ex:15 +#: lib/block_scout_web/views/block_transaction_view.ex:19 #, elixir-autogen, elixir-format msgid "Block not found, please try again later." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:203 #: lib/block_scout_web/templates/transaction/overview.html.eex:203 #, elixir-autogen, elixir-format msgid "Block number containing the transaction on L1." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:160 #: lib/block_scout_web/templates/transaction/overview.html.eex:160 #, elixir-autogen, elixir-format msgid "Block number containing the transaction." msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:259 #: lib/block_scout_web/templates/address/overview.html.eex:259 #, elixir-autogen, elixir-format msgid "Block number in which the address was updated." msgstr "" -#: lib/block_scout_web/templates/chain/_metatags.html.eex:4 #: lib/block_scout_web/templates/chain/_metatags.html.eex:4 #, elixir-autogen, elixir-format msgid "BlockScout provides analytics data, API, and Smart Contract tools for the %{subnetwork}" msgstr "" -#: lib/block_scout_web/templates/layout/_topnav.html.eex:29 #: lib/block_scout_web/templates/layout/_topnav.html.eex:29 #, elixir-autogen, elixir-format msgid "Blockchain" msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:156 -#: lib/block_scout_web/templates/chain/show.html.eex:156 +#: lib/block_scout_web/templates/chain/show.html.eex:158 #: lib/block_scout_web/templates/layout/_topnav.html.eex:34 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:34 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:38 #: lib/block_scout_web/templates/layout/_topnav.html.eex:38 #, elixir-autogen, elixir-format msgid "Blocks" msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:46 #: lib/block_scout_web/templates/layout/app.html.eex:46 #, elixir-autogen, elixir-format msgid "Blocks Indexed" msgstr "" -#: lib/block_scout_web/templates/address/_tabs.html.eex:56 #: lib/block_scout_web/templates/address/_tabs.html.eex:56 #: lib/block_scout_web/templates/address/overview.html.eex:277 -#: lib/block_scout_web/templates/address/overview.html.eex:277 -#: lib/block_scout_web/templates/address_validation/index.html.eex:11 #: lib/block_scout_web/templates/address_validation/index.html.eex:11 #: lib/block_scout_web/views/address_view.ex:362 -#: lib/block_scout_web/views/address_view.ex:362 #, elixir-autogen, elixir-format msgid "Blocks Validated" msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:48 #: lib/block_scout_web/templates/layout/app.html.eex:48 #, elixir-autogen, elixir-format msgid "Blocks With Internal Transactions Indexed" msgstr "" -#: lib/block_scout_web/templates/layout/_footer.html.eex:22 #: lib/block_scout_web/templates/layout/_footer.html.eex:22 #, elixir-autogen, elixir-format msgid "Blockscout is a tool for inspecting and analyzing EVM based blockchains. Blockchain explorer for Ethereum Networks." msgstr "" -#: lib/block_scout_web/templates/transaction_state/_state_change.html.eex:8 #: lib/block_scout_web/templates/transaction_state/_state_change.html.eex:8 #, elixir-autogen, elixir-format msgid "Burn address" msgstr "" #: lib/block_scout_web/templates/block/_tile.html.eex:64 -#: lib/block_scout_web/templates/block/_tile.html.eex:64 -#: lib/block_scout_web/templates/block/overview.html.eex:218 #: lib/block_scout_web/templates/block/overview.html.eex:218 #, elixir-autogen, elixir-format msgid "Burnt Fees" msgstr "" -#: lib/block_scout_web/templates/address_token/overview.html.eex:65 #: lib/block_scout_web/templates/address_token/overview.html.eex:65 #, elixir-autogen, elixir-format msgid "CRC Worth" msgstr "" -#: lib/block_scout_web/templates/common_components/_csv_export_button.html.eex:4 #: lib/block_scout_web/templates/common_components/_csv_export_button.html.eex:4 #, elixir-autogen, elixir-format msgid "CSV" msgstr "" #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:10 -#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:10 -#: lib/block_scout_web/views/internal_transaction_view.ex:21 -#: lib/block_scout_web/views/internal_transaction_view.ex:21 +#: lib/block_scout_web/views/internal_transaction_view.ex:23 #, elixir-autogen, elixir-format msgid "Call" msgstr "" -#: lib/block_scout_web/views/internal_transaction_view.ex:22 -#: lib/block_scout_web/views/internal_transaction_view.ex:22 +#: lib/block_scout_web/views/internal_transaction_view.ex:24 #, elixir-autogen, elixir-format msgid "Call Code" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:120 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:120 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:115 -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:115 -#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:41 #: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:41 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:107 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:107 +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:105 #: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:55 -#: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:55 -#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:51 #: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:51 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:47 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:47 -#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:54 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:54 #, elixir-autogen, elixir-format msgid "Cancel" msgstr "" -#: lib/block_scout_web/templates/transaction_state/index.html.eex:43 #: lib/block_scout_web/templates/transaction_state/index.html.eex:43 #, elixir-autogen, elixir-format msgid "Change" msgstr "" -#: lib/block_scout_web/templates/layout/_footer.html.eex:43 #: lib/block_scout_web/templates/layout/_footer.html.eex:43 #, elixir-autogen, elixir-format msgid "Chat (#blockscout)" msgstr "" -#: lib/block_scout_web/views/block_view.ex:65 #: lib/block_scout_web/views/block_view.ex:65 #, elixir-autogen, elixir-format msgid "Chore Reward" msgstr "" -#: lib/block_scout_web/templates/tokens/index.html.eex:38 #: lib/block_scout_web/templates/tokens/index.html.eex:38 #, elixir-autogen, elixir-format msgid "Circulating Market Cap" msgstr "" #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:137 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:137 -#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:106 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:106 #, elixir-autogen, elixir-format msgid "Clear" msgstr "" #: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:37 -#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:37 -#: lib/block_scout_web/templates/common_components/_modal_qr_code.html.eex:6 #: lib/block_scout_web/templates/common_components/_modal_qr_code.html.eex:6 #: lib/block_scout_web/templates/common_components/_modal_qr_code.html.eex:14 -#: lib/block_scout_web/templates/common_components/_modal_qr_code.html.eex:14 -#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:84 #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:84 #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:92 -#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:92 #, elixir-autogen, elixir-format msgid "Close" msgstr "" -#: lib/block_scout_web/templates/address/_tabs.html.eex:66 #: lib/block_scout_web/templates/address/_tabs.html.eex:66 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:165 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:165 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:187 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:187 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:126 -#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:126 -#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:149 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:149 #: lib/block_scout_web/views/address_view.ex:356 -#: lib/block_scout_web/views/address_view.ex:356 #, elixir-autogen, elixir-format msgid "Code" msgstr "" -#: lib/block_scout_web/templates/address/_tabs.html.eex:42 #: lib/block_scout_web/templates/address/_tabs.html.eex:42 #: lib/block_scout_web/views/address_view.ex:361 -#: lib/block_scout_web/views/address_view.ex:361 #, elixir-autogen, elixir-format msgid "Coin Balance History" msgstr "" -#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:54 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:54 #, elixir-autogen, elixir-format msgid "Collapse" msgstr "" #: lib/block_scout_web/templates/address_contract_verification_common_fields/_compiler_field.html.eex:3 -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_compiler_field.html.eex:3 -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:69 #: lib/block_scout_web/templates/verified_contracts/index.html.eex:69 #, elixir-autogen, elixir-format msgid "Compiler" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:142 #: lib/block_scout_web/templates/address_contract/index.html.eex:142 #, elixir-autogen, elixir-format msgid "Compiler Settings" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:71 #: lib/block_scout_web/templates/address_contract/index.html.eex:71 #, elixir-autogen, elixir-format msgid "Compiler version" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:368 -#: lib/block_scout_web/views/transaction_view.ex:368 +#: lib/block_scout_web/views/transaction_view.ex:366 #, elixir-autogen, elixir-format msgid "Confirmed" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:127 #: lib/block_scout_web/templates/transaction/overview.html.eex:127 #, elixir-autogen, elixir-format msgid "Confirmed by " msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:193 #: lib/block_scout_web/templates/transaction/overview.html.eex:193 #, elixir-autogen, elixir-format msgid "Confirmed within" msgstr "" #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:2 -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:2 -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:6 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:6 #: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:2 -#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:2 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:4 #: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:4 #: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:6 -#: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:6 -#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:4 #: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:4 #: lib/block_scout_web/templates/tokens/holder/index.html.eex:16 -#: lib/block_scout_web/templates/tokens/holder/index.html.eex:16 #, elixir-autogen, elixir-format msgid "Connection Lost" msgstr "" -#: lib/block_scout_web/templates/address_coin_balance/index.html.eex:12 #: lib/block_scout_web/templates/address_coin_balance/index.html.eex:12 #: lib/block_scout_web/templates/block/index.html.eex:5 -#: lib/block_scout_web/templates/block/index.html.eex:5 #, elixir-autogen, elixir-format msgid "Connection Lost, click to load newer blocks" msgstr "" -#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:15 #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:15 #, elixir-autogen, elixir-format msgid "Connection Lost, click to load newer internal transactions" msgstr "" #: lib/block_scout_web/templates/address_transaction/index.html.eex:11 -#: lib/block_scout_web/templates/address_transaction/index.html.eex:11 -#: lib/block_scout_web/templates/pending_transaction/index.html.eex:16 #: lib/block_scout_web/templates/pending_transaction/index.html.eex:16 #: lib/block_scout_web/templates/transaction/index.html.eex:22 -#: lib/block_scout_web/templates/transaction/index.html.eex:22 #, elixir-autogen, elixir-format msgid "Connection Lost, click to load newer transactions" msgstr "" -#: lib/block_scout_web/templates/address_validation/index.html.eex:10 #: lib/block_scout_web/templates/address_validation/index.html.eex:10 #, elixir-autogen, elixir-format msgid "Connection Lost, click to load newer validations" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:96 #: lib/block_scout_web/templates/address_contract/index.html.eex:96 #, elixir-autogen, elixir-format msgid "Constructor Arguments" msgstr "" -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:78 #: lib/block_scout_web/templates/verified_contracts/index.html.eex:78 #, elixir-autogen, elixir-format msgid "Constructor args" msgstr "" #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:52 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:52 -#: lib/block_scout_web/templates/transaction/overview.html.eex:273 -#: lib/block_scout_web/templates/transaction/overview.html.eex:273 +#: lib/block_scout_web/templates/transaction/overview.html.eex:250 #, elixir-autogen, elixir-format msgid "Contract" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:157 #: lib/block_scout_web/templates/address_contract/index.html.eex:157 #, elixir-autogen, elixir-format msgid "Contract ABI" msgstr "" #: lib/block_scout_web/templates/account/custom_abi/form.html.eex:18 -#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:18 -#: lib/block_scout_web/templates/account/custom_abi/index.html.eex:29 #: lib/block_scout_web/templates/account/custom_abi/index.html.eex:29 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_contract_address_field.html.eex:3 -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_contract_address_field.html.eex:3 -#: lib/block_scout_web/views/address_view.ex:108 #: lib/block_scout_web/views/address_view.ex:108 #, elixir-autogen, elixir-format msgid "Contract Address" msgstr "" #: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:16 -#: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:16 -#: lib/block_scout_web/views/address_view.ex:48 #: lib/block_scout_web/views/address_view.ex:48 #: lib/block_scout_web/views/address_view.ex:82 -#: lib/block_scout_web/views/address_view.ex:82 #, elixir-autogen, elixir-format msgid "Contract Address Pending" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:497 -#: lib/block_scout_web/views/transaction_view.ex:497 +#: lib/block_scout_web/views/transaction_view.ex:495 #, elixir-autogen, elixir-format msgid "Contract Call" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:494 -#: lib/block_scout_web/views/transaction_view.ex:494 +#: lib/block_scout_web/views/transaction_view.ex:492 #, elixir-autogen, elixir-format msgid "Contract Creation" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:174 #: lib/block_scout_web/templates/address_contract/index.html.eex:174 #: lib/block_scout_web/templates/address_contract/index.html.eex:189 -#: lib/block_scout_web/templates/address_contract/index.html.eex:189 #, elixir-autogen, elixir-format msgid "Contract Creation Code" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:90 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:90 #: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:80 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:80 #, elixir-autogen, elixir-format msgid "Contract Libraries" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:75 #: lib/block_scout_web/templates/address/overview.html.eex:75 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_contract_name_field.html.eex:3 -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_contract_name_field.html.eex:3 #, elixir-autogen, elixir-format msgid "Contract Name" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:25 #: lib/block_scout_web/templates/address_contract/index.html.eex:25 #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:11 -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:11 #, elixir-autogen, elixir-format msgid "Contract is not verified. However, we found a verified contract with the same bytecode in Blockscout DB" msgstr "" -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:47 #: lib/block_scout_web/templates/verified_contracts/index.html.eex:47 #, elixir-autogen, elixir-format msgid "Contract name or address" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:63 #: lib/block_scout_web/templates/address_contract/index.html.eex:63 #, elixir-autogen, elixir-format msgid "Contract name:" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:106 #: lib/block_scout_web/templates/address_contract/index.html.eex:106 #, elixir-autogen, elixir-format msgid "Contract source code" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:120 #: lib/block_scout_web/templates/address/overview.html.eex:120 #, elixir-autogen, elixir-format msgid "Contract was precompiled and created at genesis or contract creation transaction is missing" msgstr "" -#: lib/block_scout_web/templates/verified_contracts/_stats.html.eex:5 #: lib/block_scout_web/templates/verified_contracts/_stats.html.eex:5 #, elixir-autogen, elixir-format msgid "Contracts" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:180 #: lib/block_scout_web/templates/address_contract/index.html.eex:180 #, elixir-autogen, elixir-format msgid "Contracts that self destruct in their constructors have no contract code published and cannot be verified." msgstr "" -#: lib/block_scout_web/templates/layout/_footer.html.eex:42 #: lib/block_scout_web/templates/layout/_footer.html.eex:42 #, elixir-autogen, elixir-format msgid "Contribute" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:159 #: lib/block_scout_web/templates/address_contract/index.html.eex:159 #, elixir-autogen, elixir-format msgid "Copy ABI" msgstr "" -#: lib/block_scout_web/templates/account/api_key/row.html.eex:6 -#: lib/block_scout_web/templates/account/api_key/row.html.eex:6 #: lib/block_scout_web/templates/account/api_key/row.html.eex:6 #: lib/block_scout_web/templates/account/api_key/row.html.eex:6 #, elixir-autogen, elixir-format @@ -1129,3588 +877,2759 @@ msgstr "" #: lib/block_scout_web/templates/account/tag_address/row.html.eex:8 #: lib/block_scout_web/templates/account/tag_address/row.html.eex:8 -#: lib/block_scout_web/templates/account/tag_address/row.html.eex:8 -#: lib/block_scout_web/templates/account/tag_address/row.html.eex:8 -#: lib/block_scout_web/templates/account/tag_transaction/row.html.eex:11 #: lib/block_scout_web/templates/account/tag_transaction/row.html.eex:11 #: lib/block_scout_web/templates/account/tag_transaction/row.html.eex:11 -#: lib/block_scout_web/templates/account/tag_transaction/row.html.eex:11 -#: lib/block_scout_web/templates/account/watchlist_address/row.html.eex:7 #: lib/block_scout_web/templates/account/watchlist_address/row.html.eex:7 #: lib/block_scout_web/templates/address/overview.html.eex:38 -#: lib/block_scout_web/templates/address/overview.html.eex:38 -#: lib/block_scout_web/templates/address/overview.html.eex:39 #: lib/block_scout_web/templates/address/overview.html.eex:39 #: lib/block_scout_web/templates/block/overview.html.eex:104 -#: lib/block_scout_web/templates/block/overview.html.eex:104 -#: lib/block_scout_web/templates/block/overview.html.eex:105 #: lib/block_scout_web/templates/block/overview.html.eex:105 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:43 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:43 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:44 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:44 #, elixir-autogen, elixir-format msgid "Copy Address" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:144 #: lib/block_scout_web/templates/address_contract/index.html.eex:144 #, elixir-autogen, elixir-format msgid "Copy Compiler Settings" msgstr "" -#: lib/block_scout_web/templates/account/custom_abi/row.html.eex:6 -#: lib/block_scout_web/templates/account/custom_abi/row.html.eex:6 #: lib/block_scout_web/templates/account/custom_abi/row.html.eex:6 #: lib/block_scout_web/templates/account/custom_abi/row.html.eex:6 #, elixir-autogen, elixir-format msgid "Copy Contract Address" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:176 #: lib/block_scout_web/templates/address_contract/index.html.eex:176 #: lib/block_scout_web/templates/address_contract/index.html.eex:192 -#: lib/block_scout_web/templates/address_contract/index.html.eex:192 #, elixir-autogen, elixir-format msgid "Copy Contract Creation Code" msgstr "" #: lib/block_scout_web/templates/address_contract/index.html.eex:213 -#: lib/block_scout_web/templates/address_contract/index.html.eex:213 -#: lib/block_scout_web/templates/address_contract/index.html.eex:223 #: lib/block_scout_web/templates/address_contract/index.html.eex:223 #, elixir-autogen, elixir-format msgid "Copy Deployed ByteCode" msgstr "" #: lib/block_scout_web/templates/account/watchlist_address/row.html.eex:7 -#: lib/block_scout_web/templates/account/watchlist_address/row.html.eex:7 -#: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:17 #: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:17 #: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:18 -#: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:18 -#: lib/block_scout_web/templates/transaction/overview.html.eex:253 -#: lib/block_scout_web/templates/transaction/overview.html.eex:253 -#: lib/block_scout_web/templates/transaction/overview.html.eex:254 -#: lib/block_scout_web/templates/transaction/overview.html.eex:254 +#: lib/block_scout_web/templates/transaction/overview.html.eex:230 +#: lib/block_scout_web/templates/transaction/overview.html.eex:231 #, elixir-autogen, elixir-format msgid "Copy From Address" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:129 #: lib/block_scout_web/templates/block/overview.html.eex:129 #: lib/block_scout_web/templates/block/overview.html.eex:130 -#: lib/block_scout_web/templates/block/overview.html.eex:130 #, elixir-autogen, elixir-format msgid "Copy Hash" msgstr "" -#: lib/block_scout_web/templates/tokens/instance/metadata/index.html.eex:20 #: lib/block_scout_web/templates/tokens/instance/metadata/index.html.eex:20 #, elixir-autogen, elixir-format msgid "Copy Metadata" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:149 #: lib/block_scout_web/templates/block/overview.html.eex:149 #: lib/block_scout_web/templates/block/overview.html.eex:150 -#: lib/block_scout_web/templates/block/overview.html.eex:150 #, elixir-autogen, elixir-format msgid "Copy Parent Hash" msgstr "" -#: lib/block_scout_web/templates/transaction_raw_trace/_card_body.html.eex:9 #: lib/block_scout_web/templates/transaction_raw_trace/_card_body.html.eex:9 #, elixir-autogen, elixir-format msgid "Copy Raw Trace" msgstr "" #: lib/block_scout_web/templates/address_contract/index.html.eex:120 -#: lib/block_scout_web/templates/address_contract/index.html.eex:120 -#: lib/block_scout_web/templates/address_contract/index.html.eex:132 #: lib/block_scout_web/templates/address_contract/index.html.eex:132 #, elixir-autogen, elixir-format msgid "Copy Source Code" msgstr "" #: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:34 -#: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:34 -#: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:35 #: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:35 -#: lib/block_scout_web/templates/transaction/overview.html.eex:280 -#: lib/block_scout_web/templates/transaction/overview.html.eex:280 -#: lib/block_scout_web/templates/transaction/overview.html.eex:281 -#: lib/block_scout_web/templates/transaction/overview.html.eex:281 -#: lib/block_scout_web/templates/transaction/overview.html.eex:288 -#: lib/block_scout_web/templates/transaction/overview.html.eex:288 -#: lib/block_scout_web/templates/transaction/overview.html.eex:289 -#: lib/block_scout_web/templates/transaction/overview.html.eex:289 +#: lib/block_scout_web/templates/transaction/overview.html.eex:257 +#: lib/block_scout_web/templates/transaction/overview.html.eex:258 +#: lib/block_scout_web/templates/transaction/overview.html.eex:265 +#: lib/block_scout_web/templates/transaction/overview.html.eex:266 #, elixir-autogen, elixir-format msgid "Copy To Address" msgstr "" #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:32 -#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:32 -#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:33 #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:33 #, elixir-autogen, elixir-format msgid "Copy Token ID" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:87 #: lib/block_scout_web/templates/transaction/overview.html.eex:87 #, elixir-autogen, elixir-format msgid "Copy Transaction Hash" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:88 #: lib/block_scout_web/templates/transaction/overview.html.eex:88 #, elixir-autogen, elixir-format msgid "Copy Txn Hash" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:571 -#: lib/block_scout_web/templates/transaction/overview.html.eex:571 +#: lib/block_scout_web/templates/transaction/overview.html.eex:548 #, elixir-autogen, elixir-format msgid "Copy Txn Hex Input" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:577 -#: lib/block_scout_web/templates/transaction/overview.html.eex:577 +#: lib/block_scout_web/templates/transaction/overview.html.eex:554 #, elixir-autogen, elixir-format msgid "Copy Txn UTF-8 Input" msgstr "" #: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:20 -#: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:20 -#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:41 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:41 -#: lib/block_scout_web/templates/transaction/overview.html.eex:570 -#: lib/block_scout_web/templates/transaction/overview.html.eex:570 -#: lib/block_scout_web/templates/transaction/overview.html.eex:576 -#: lib/block_scout_web/templates/transaction/overview.html.eex:576 -#: lib/block_scout_web/templates/transaction_raw_trace/_card_body.html.eex:8 +#: lib/block_scout_web/templates/transaction/overview.html.eex:547 +#: lib/block_scout_web/templates/transaction/overview.html.eex:553 #: lib/block_scout_web/templates/transaction_raw_trace/_card_body.html.eex:8 #, elixir-autogen, elixir-format msgid "Copy Value" msgstr "" -#: lib/block_scout_web/views/internal_transaction_view.ex:26 -#: lib/block_scout_web/views/internal_transaction_view.ex:26 +#: lib/block_scout_web/views/internal_transaction_view.ex:31 #, elixir-autogen, elixir-format msgid "Create" msgstr "" -#: lib/block_scout_web/templates/account/custom_abi/index.html.eex:12 #: lib/block_scout_web/templates/account/custom_abi/index.html.eex:12 #, elixir-autogen, elixir-format msgid "Create a Custom ABI to interact with contracts." msgstr "" -#: lib/block_scout_web/templates/account/api_key/index.html.eex:12 #: lib/block_scout_web/templates/account/api_key/index.html.eex:12 #, elixir-autogen, elixir-format msgid "Create an API key to use with your RPC and EthRPC API requests." msgstr "" -#: lib/block_scout_web/views/internal_transaction_view.ex:27 -#: lib/block_scout_web/views/internal_transaction_view.ex:27 +#: lib/block_scout_web/views/internal_transaction_view.ex:32 #, elixir-autogen, elixir-format msgid "Create2" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:102 #: lib/block_scout_web/templates/address/overview.html.eex:102 #, elixir-autogen, elixir-format msgid "Creator" msgstr "" #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:146 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:146 -#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:116 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:116 #, elixir-autogen, elixir-format msgid "Curl" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:97 #: lib/block_scout_web/templates/transaction/overview.html.eex:97 #, elixir-autogen, elixir-format msgid "Current transaction state: Success, Failed (Error), or Pending (In Process)" msgstr "" #: lib/block_scout_web/templates/address_read_contract/index.html.eex:20 -#: lib/block_scout_web/templates/address_read_contract/index.html.eex:20 -#: lib/block_scout_web/templates/address_write_contract/index.html.eex:18 #: lib/block_scout_web/templates/address_write_contract/index.html.eex:18 #, elixir-autogen, elixir-format msgid "Custom" msgstr "" #: lib/block_scout_web/templates/account/common/_nav.html.eex:19 -#: lib/block_scout_web/templates/account/common/_nav.html.eex:19 -#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:8 #: lib/block_scout_web/templates/account/custom_abi/form.html.eex:8 #: lib/block_scout_web/templates/account/custom_abi/index.html.eex:7 -#: lib/block_scout_web/templates/account/custom_abi/index.html.eex:7 -#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:19 #: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:19 #, elixir-autogen, elixir-format msgid "Custom ABI" msgstr "" #: lib/block_scout_web/templates/address_read_contract/index.html.eex:25 -#: lib/block_scout_web/templates/address_read_contract/index.html.eex:25 -#: lib/block_scout_web/templates/address_write_contract/index.html.eex:23 #: lib/block_scout_web/templates/address_write_contract/index.html.eex:23 #, elixir-autogen, elixir-format msgid "Custom ABI from account" msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:70 -#: lib/block_scout_web/templates/chain/show.html.eex:70 +#: lib/block_scout_web/templates/chain/show.html.eex:72 #, elixir-autogen, elixir-format msgid "Daily Transactions" msgstr "" -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:98 #: lib/block_scout_web/templates/address_logs/_logs.html.eex:98 #: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:7 -#: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:7 -#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:23 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:23 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:130 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:130 #, elixir-autogen, elixir-format msgid "Data" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:70 #: lib/block_scout_web/templates/block/overview.html.eex:70 #, elixir-autogen, elixir-format msgid "Date & time at which block was produced." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:179 #: lib/block_scout_web/templates/transaction/overview.html.eex:179 #, elixir-autogen, elixir-format msgid "Date & time of transaction inclusion, including length of time for confirmation." msgstr "" -#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:52 #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:52 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:131 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:131 #, elixir-autogen, elixir-format msgid "Decimals" msgstr "" #: lib/block_scout_web/templates/address_logs/_logs.html.eex:32 -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:32 -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:38 #: lib/block_scout_web/templates/address_logs/_logs.html.eex:38 #: lib/block_scout_web/templates/address_logs/_logs.html.eex:53 -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:53 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:43 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:43 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:51 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:51 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:66 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:66 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:82 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:82 #, elixir-autogen, elixir-format msgid "Decoded" msgstr "" -#: lib/block_scout_web/views/internal_transaction_view.ex:23 -#: lib/block_scout_web/views/internal_transaction_view.ex:23 +#: lib/block_scout_web/views/internal_transaction_view.ex:25 #, elixir-autogen, elixir-format msgid "Delegate Call" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:211 #: lib/block_scout_web/templates/address_contract/index.html.eex:211 #: lib/block_scout_web/templates/address_contract/index.html.eex:219 -#: lib/block_scout_web/templates/address_contract/index.html.eex:219 #, elixir-autogen, elixir-format msgid "Deployed ByteCode" msgstr "" -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:53 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:53 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:188 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:188 -#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:60 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:60 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:150 -#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:150 #, elixir-autogen, elixir-format msgid "Description" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:30 #: lib/block_scout_web/templates/address/overview.html.eex:30 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:166 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:166 -#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:127 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:127 #, elixir-autogen, elixir-format msgid "Details" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:159 #: lib/block_scout_web/templates/block/overview.html.eex:159 #, elixir-autogen, elixir-format msgid "Difficulty" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:181 #: lib/block_scout_web/templates/address_contract/index.html.eex:181 #, elixir-autogen, elixir-format msgid "Displaying the init data provided of the creating transaction." msgstr "" -#: lib/block_scout_web/templates/common_components/_csv_export_button.html.eex:4 #: lib/block_scout_web/templates/common_components/_csv_export_button.html.eex:4 #: lib/block_scout_web/templates/csv_export/index.html.eex:33 -#: lib/block_scout_web/templates/csv_export/index.html.eex:33 #, elixir-autogen, elixir-format msgid "Download" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:72 #: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:72 #, elixir-autogen, elixir-format msgid "Drop all Solidity contract source files into the drop zone." msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:72 #: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:72 #, elixir-autogen, elixir-format msgid "Drop all Solidity or Yul contract source files into the drop zone." msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:18 #: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:18 #, elixir-autogen, elixir-format msgid "Drop sources and metadata JSON file or click here" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:67 #: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:67 #, elixir-autogen, elixir-format msgid "Drop sources or click here" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:28 #: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:28 #, elixir-autogen, elixir-format msgid "Drop the standard input JSON file or click here" msgstr "" -#: lib/block_scout_web/templates/common_components/_minimal_proxy_pattern.html.eex:6 #: lib/block_scout_web/templates/common_components/_minimal_proxy_pattern.html.eex:6 #, elixir-autogen, elixir-format msgid "EIP-1167" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:225 -#: lib/block_scout_web/views/transaction_view.ex:225 +#: lib/block_scout_web/views/transaction_view.ex:221 #, elixir-autogen, elixir-format msgid "ERC-1155 " msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:223 -#: lib/block_scout_web/views/transaction_view.ex:223 +#: lib/block_scout_web/views/transaction_view.ex:219 #, elixir-autogen, elixir-format msgid "ERC-20 " msgstr "" -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:40 #: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:40 #, elixir-autogen, elixir-format msgid "ERC-20 tokens (beta)" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:226 -#: lib/block_scout_web/views/transaction_view.ex:226 +#: lib/block_scout_web/views/transaction_view.ex:222 #, elixir-autogen, elixir-format msgid "ERC-404 " msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:224 -#: lib/block_scout_web/views/transaction_view.ex:224 +#: lib/block_scout_web/views/transaction_view.ex:220 #, elixir-autogen, elixir-format msgid "ERC-721 " msgstr "" -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:53 #: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:53 #, elixir-autogen, elixir-format msgid "ERC-721, ERC-1155 tokens (NFT) (beta)" msgstr "" -#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:4 #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:4 #, elixir-autogen, elixir-format msgid "ETH RPC API Documentation" msgstr "" #: lib/block_scout_web/templates/address_contract/index.html.eex:82 -#: lib/block_scout_web/templates/address_contract/index.html.eex:82 -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:30 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:30 #: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:22 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:22 #, elixir-autogen, elixir-format msgid "EVM Version" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:34 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:34 #: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:26 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:26 #, elixir-autogen, elixir-format msgid "EVM version details" msgstr "" #: lib/block_scout_web/views/block_transaction_view.ex:7 -#: lib/block_scout_web/views/block_transaction_view.ex:7 +#: lib/block_scout_web/views/block_transaction_view.ex:11 #, elixir-autogen, elixir-format msgid "Easy Cowboy! This block does not exist yet!" msgstr "" -#: lib/block_scout_web/templates/account/api_key/row.html.eex:16 #: lib/block_scout_web/templates/account/api_key/row.html.eex:16 #: lib/block_scout_web/templates/account/custom_abi/row.html.eex:16 -#: lib/block_scout_web/templates/account/custom_abi/row.html.eex:16 -#: lib/block_scout_web/templates/account/watchlist_address/row.html.eex:27 #: lib/block_scout_web/templates/account/watchlist_address/row.html.eex:27 #, elixir-autogen, elixir-format msgid "Edit" msgstr "" -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:7 #: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:7 #, elixir-autogen, elixir-format msgid "Edit Watch list address" msgstr "" -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:71 #: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:71 #, elixir-autogen, elixir-format msgid "Email notifications" msgstr "" -#: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:5 #: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:5 #, elixir-autogen, elixir-format msgid "Emission Contract" msgstr "" -#: lib/block_scout_web/views/block_view.ex:73 #: lib/block_scout_web/views/block_view.ex:73 #, elixir-autogen, elixir-format msgid "Emission Reward" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:72 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:72 #, elixir-autogen, elixir-format msgid "Enter the Solidity Contract Code" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:22 #: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:22 #, elixir-autogen, elixir-format msgid "Enter the Vyper Contract Code" msgstr "" -#: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:11 #: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:11 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:10 -#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:10 #, elixir-autogen, elixir-format msgid "Error" msgstr "" -#: lib/block_scout_web/templates/transaction/_tile.html.eex:11 #: lib/block_scout_web/templates/transaction/_tile.html.eex:11 #, elixir-autogen, elixir-format msgid "Error in internal transactions" msgstr "" -#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:33 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:33 #, elixir-autogen, elixir-format msgid "Error rendering value" msgstr "" -#: lib/block_scout_web/templates/address/_balance_dropdown.html.eex:10 #: lib/block_scout_web/templates/address/_balance_dropdown.html.eex:10 #, elixir-autogen, elixir-format msgid "Error trying to fetch balances." msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:379 -#: lib/block_scout_web/views/transaction_view.ex:379 +#: lib/block_scout_web/views/transaction_view.ex:377 #, elixir-autogen, elixir-format msgid "Error: %{reason}" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:377 -#: lib/block_scout_web/views/transaction_view.ex:377 +#: lib/block_scout_web/views/transaction_view.ex:375 #, elixir-autogen, elixir-format msgid "Error: (Awaiting internal transactions for reason)" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:120 #: lib/block_scout_web/templates/address/overview.html.eex:120 #, elixir-autogen, elixir-format msgid "Error: Could not determine contract creator." msgstr "" -#: lib/block_scout_web/templates/layout/_topnav.html.eex:120 #: lib/block_scout_web/templates/layout/_topnav.html.eex:120 #, elixir-autogen, elixir-format msgid "Eth RPC" msgstr "" #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:211 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:211 -#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:164 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:164 #, elixir-autogen, elixir-format msgid "Example Value" msgstr "" #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:128 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:128 -#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:99 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:99 #, elixir-autogen, elixir-format msgid "Execute" msgstr "" -#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:54 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:54 #, elixir-autogen, elixir-format msgid "Expand" msgstr "" -#: lib/block_scout_web/templates/csv_export/index.html.eex:14 #: lib/block_scout_web/templates/csv_export/index.html.eex:14 #, elixir-autogen, elixir-format msgid "Export" msgstr "" -#: lib/block_scout_web/templates/csv_export/index.html.eex:10 #: lib/block_scout_web/templates/csv_export/index.html.eex:10 #, elixir-autogen, elixir-format msgid "Export Data" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:248 #: lib/block_scout_web/templates/address_contract/index.html.eex:248 #, elixir-autogen, elixir-format msgid "External libraries" msgstr "" -#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:40 #: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:40 #, elixir-autogen, elixir-format msgid "Failed to decode input data." msgstr "" #: lib/block_scout_web/templates/address_logs/_logs.html.eex:35 -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:35 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:46 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:46 #, elixir-autogen, elixir-format msgid "Failed to decode log data." msgstr "" -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:22 #: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:22 #, elixir-autogen, elixir-format msgid "Fast" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:249 #: lib/block_scout_web/templates/address/overview.html.eex:249 #, elixir-autogen, elixir-format msgid "Fetching gas used..." msgstr "" -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:112 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:112 #, elixir-autogen, elixir-format msgid "Fetching holders..." msgstr "" -#: lib/block_scout_web/templates/address/_balance_dropdown.html.eex:7 #: lib/block_scout_web/templates/address/_balance_dropdown.html.eex:7 #, elixir-autogen, elixir-format msgid "Fetching tokens..." msgstr "" #: lib/block_scout_web/templates/address/overview.html.eex:196 -#: lib/block_scout_web/templates/address/overview.html.eex:196 -#: lib/block_scout_web/templates/address/overview.html.eex:204 #: lib/block_scout_web/templates/address/overview.html.eex:204 #, elixir-autogen, elixir-format msgid "Fetching transactions..." msgstr "" #: lib/block_scout_web/templates/address/overview.html.eex:223 -#: lib/block_scout_web/templates/address/overview.html.eex:223 -#: lib/block_scout_web/templates/address/overview.html.eex:231 #: lib/block_scout_web/templates/address/overview.html.eex:231 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:123 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:123 #, elixir-autogen, elixir-format msgid "Fetching transfers..." msgstr "" -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:15 #: lib/block_scout_web/templates/verified_contracts/index.html.eex:15 #, elixir-autogen, elixir-format msgid "Filter by compiler:" msgstr "" -#: lib/block_scout_web/templates/admin/dashboard/index.html.eex:16 #: lib/block_scout_web/templates/admin/dashboard/index.html.eex:16 #, elixir-autogen, elixir-format msgid "For any existing contracts in the database, insert all ABI entries into the contract_methods table. Use this in case you have verified smart contracts before early March 2019 and you want other contracts with the same functions to show those ABI's as candidate matches." msgstr "" -#: lib/block_scout_web/templates/visualize_sol2uml/index.html.eex:7 #: lib/block_scout_web/templates/visualize_sol2uml/index.html.eex:7 #, elixir-autogen, elixir-format msgid "For contract" msgstr "" -#: lib/block_scout_web/templates/layout/_topnav.html.eex:44 #: lib/block_scout_web/templates/layout/_topnav.html.eex:44 #, elixir-autogen, elixir-format msgid "Forked Blocks (Reorgs)" msgstr "" -#: lib/block_scout_web/templates/layout/_footer.html.eex:45 #: lib/block_scout_web/templates/layout/_footer.html.eex:45 #, elixir-autogen, elixir-format msgid "Forum" msgstr "" -#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:38 #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:38 #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:40 -#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:40 -#: lib/block_scout_web/templates/address_transaction/index.html.eex:34 #: lib/block_scout_web/templates/address_transaction/index.html.eex:34 -#: lib/block_scout_web/templates/transaction/overview.html.eex:246 -#: lib/block_scout_web/templates/transaction/overview.html.eex:246 -#: lib/block_scout_web/views/address_internal_transaction_view.ex:11 +#: lib/block_scout_web/templates/transaction/overview.html.eex:223 #: lib/block_scout_web/views/address_internal_transaction_view.ex:11 #: lib/block_scout_web/views/address_token_transfer_view.ex:11 -#: lib/block_scout_web/views/address_token_transfer_view.ex:11 -#: lib/block_scout_web/views/address_transaction_view.ex:11 #: lib/block_scout_web/views/address_transaction_view.ex:11 #, elixir-autogen, elixir-format msgid "From" msgstr "" -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:18 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:18 #, elixir-autogen, elixir-format msgid "GET" msgstr "" -#: lib/block_scout_web/templates/block/_tile.html.eex:67 #: lib/block_scout_web/templates/block/_tile.html.eex:67 #: lib/block_scout_web/templates/block/overview.html.eex:189 -#: lib/block_scout_web/templates/block/overview.html.eex:189 -#: lib/block_scout_web/templates/transaction/overview.html.eex:430 -#: lib/block_scout_web/templates/transaction/overview.html.eex:430 +#: lib/block_scout_web/templates/transaction/overview.html.eex:407 #, elixir-autogen, elixir-format msgid "Gas Limit" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:404 -#: lib/block_scout_web/templates/transaction/overview.html.eex:404 +#: lib/block_scout_web/templates/transaction/overview.html.eex:381 #, elixir-autogen, elixir-format msgid "Gas Price" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:242 #: lib/block_scout_web/templates/address/overview.html.eex:242 #: lib/block_scout_web/templates/block/_tile.html.eex:73 -#: lib/block_scout_web/templates/block/_tile.html.eex:73 -#: lib/block_scout_web/templates/block/overview.html.eex:180 #: lib/block_scout_web/templates/block/overview.html.eex:180 #, elixir-autogen, elixir-format msgid "Gas Used" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:489 -#: lib/block_scout_web/templates/transaction/overview.html.eex:489 +#: lib/block_scout_web/templates/transaction/overview.html.eex:466 #, elixir-autogen, elixir-format msgid "Gas Used by Transaction" msgstr "" -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:3 #: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:3 #: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:18 -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:18 #, elixir-autogen, elixir-format msgid "Gas tracker" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:241 #: lib/block_scout_web/templates/address/overview.html.eex:241 #, elixir-autogen, elixir-format msgid "Gas used by the address." msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:60 #: lib/block_scout_web/templates/block/overview.html.eex:60 #, elixir-autogen, elixir-format msgid "Genesis Block" msgstr "" -#: lib/block_scout_web/templates/layout/_footer.html.eex:24 #: lib/block_scout_web/templates/layout/_footer.html.eex:24 #, elixir-autogen, elixir-format msgid "Github" msgstr "" -#: lib/block_scout_web/templates/common_components/_rap_pagination_container.html.eex:8 #: lib/block_scout_web/templates/common_components/_rap_pagination_container.html.eex:8 #, elixir-autogen, elixir-format msgid "Go to" msgstr "" -#: lib/block_scout_web/templates/layout/_topnav.html.eex:110 #: lib/block_scout_web/templates/layout/_topnav.html.eex:110 #, elixir-autogen, elixir-format msgid "GraphQL" msgstr "" -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:11 #: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:11 #: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:20 -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:20 -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:21 #: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:21 #: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:22 -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:22 -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:38 #: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:38 #: lib/block_scout_web/views/block_view.ex:22 -#: lib/block_scout_web/views/block_view.ex:22 -#: lib/block_scout_web/views/wei_helper.ex:81 #: lib/block_scout_web/views/wei_helper.ex:81 #, elixir-autogen, elixir-format msgid "Gwei" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:123 #: lib/block_scout_web/templates/block/overview.html.eex:123 #, elixir-autogen, elixir-format msgid "Hash" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:553 -#: lib/block_scout_web/templates/transaction/overview.html.eex:553 -#: lib/block_scout_web/templates/transaction/overview.html.eex:557 -#: lib/block_scout_web/templates/transaction/overview.html.eex:557 +#: lib/block_scout_web/templates/transaction/overview.html.eex:530 +#: lib/block_scout_web/templates/transaction/overview.html.eex:534 #, elixir-autogen, elixir-format msgid "Hex (Default)" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:224 -#: lib/block_scout_web/templates/transaction/overview.html.eex:224 -#, elixir-autogen, elixir-format -msgid "Highlighted events of the transaction." -msgstr "" - -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:108 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:108 #, elixir-autogen, elixir-format msgid "Holders" msgstr "" -#: lib/block_scout_web/templates/tokens/index.html.eex:48 #: lib/block_scout_web/templates/tokens/index.html.eex:48 #, elixir-autogen, elixir-format msgid "Holders Count" msgstr "" -#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:11 #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:11 #, elixir-autogen, elixir-format msgid "However, in general, the" msgstr "" -#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:19 #: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:19 #, elixir-autogen, elixir-format msgid "IMPORTANT: This information is a best guess based on similar functions from other verified contracts." msgstr "" -#: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:42 #: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:42 #: lib/block_scout_web/templates/transaction/_tile.html.eex:92 -#: lib/block_scout_web/templates/transaction/_tile.html.eex:92 #, elixir-autogen, elixir-format msgid "IN" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:56 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:56 #: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:48 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:48 #, elixir-autogen, elixir-format msgid "If you enabled optimization during compilation, select yes." msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:135 #: lib/block_scout_web/templates/address/overview.html.eex:135 #, elixir-autogen, elixir-format msgid "Implementation" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:134 #: lib/block_scout_web/templates/address/overview.html.eex:134 #, elixir-autogen, elixir-format msgid "Implementation address of the proxy contract." msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_include_nightly_builds_field.html.eex:3 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_include_nightly_builds_field.html.eex:3 #, elixir-autogen, elixir-format msgid "Include nightly builds" msgstr "" #: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:30 -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:30 -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:43 #: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:43 #: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:56 -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:56 #, elixir-autogen, elixir-format msgid "Incoming" msgstr "" #: lib/block_scout_web/templates/address_withdrawal/index.html.eex:29 -#: lib/block_scout_web/templates/address_withdrawal/index.html.eex:29 -#: lib/block_scout_web/templates/block_withdrawal/index.html.eex:23 #: lib/block_scout_web/templates/block_withdrawal/index.html.eex:23 #: lib/block_scout_web/templates/withdrawal/index.html.eex:23 -#: lib/block_scout_web/templates/withdrawal/index.html.eex:23 #, elixir-autogen, elixir-format msgid "Index" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:537 -#: lib/block_scout_web/templates/transaction/overview.html.eex:537 +#: lib/block_scout_web/templates/transaction/overview.html.eex:514 #, elixir-autogen, elixir-format msgid "Index position of Transaction in the block." msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:251 #: lib/block_scout_web/templates/block/overview.html.eex:251 #, elixir-autogen, elixir-format msgid "Index position(s) of referenced stale blocks." msgstr "" -#: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:6 #: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:6 #, elixir-autogen, elixir-format msgid "Indexed?" msgstr "" -#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:3 #: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:3 #, elixir-autogen, elixir-format msgid "Input" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:265 -#: lib/block_scout_web/templates/transaction/overview.html.eex:265 +#: lib/block_scout_web/templates/transaction/overview.html.eex:242 #, elixir-autogen, elixir-format msgid "Interacted With (To)" msgstr "" -#: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:7 #: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:7 #, elixir-autogen, elixir-format msgid "Internal Transaction" msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:36 -#: lib/block_scout_web/templates/address/_tabs.html.eex:36 -#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:17 #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:17 #: lib/block_scout_web/templates/transaction/_tabs.html.eex:11 -#: lib/block_scout_web/templates/transaction/_tabs.html.eex:11 -#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:6 #: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:6 #: lib/block_scout_web/views/address_view.ex:353 -#: lib/block_scout_web/views/address_view.ex:353 -#: lib/block_scout_web/views/transaction_view.ex:552 -#: lib/block_scout_web/views/transaction_view.ex:552 +#: lib/block_scout_web/views/transaction_view.ex:550 #, elixir-autogen, elixir-format msgid "Internal Transactions" msgstr "" -#: lib/block_scout_web/templates/internal_server_error/index.html.eex:7 #: lib/block_scout_web/templates/internal_server_error/index.html.eex:7 #, elixir-autogen, elixir-format msgid "Internal server error" msgstr "" -#: lib/block_scout_web/views/internal_transaction_view.ex:25 -#: lib/block_scout_web/views/internal_transaction_view.ex:25 +#: lib/block_scout_web/views/internal_transaction_view.ex:27 #, elixir-autogen, elixir-format msgid "Invalid" msgstr "" #: lib/block_scout_web/templates/tokens/inventory/index.html.eex:16 -#: lib/block_scout_web/templates/tokens/inventory/index.html.eex:16 -#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:19 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:19 #: lib/block_scout_web/views/tokens/overview_view.ex:42 -#: lib/block_scout_web/views/tokens/overview_view.ex:42 #, elixir-autogen, elixir-format msgid "Inventory" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_yul_contracts_switcher.html.eex:3 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_yul_contracts_switcher.html.eex:3 #, elixir-autogen, elixir-format msgid "Is Yul contract" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:204 #: lib/block_scout_web/templates/transaction/overview.html.eex:204 #, elixir-autogen, elixir-format msgid "L1 Block" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:523 -#: lib/block_scout_web/templates/transaction/overview.html.eex:523 -#: lib/block_scout_web/templates/transaction/overview.html.eex:524 -#: lib/block_scout_web/templates/transaction/overview.html.eex:524 +#: lib/block_scout_web/templates/transaction/overview.html.eex:500 +#: lib/block_scout_web/templates/transaction/overview.html.eex:501 #, elixir-autogen, elixir-format msgid "L1 Fee Scalar" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:512 -#: lib/block_scout_web/templates/transaction/overview.html.eex:512 -#: lib/block_scout_web/templates/transaction/overview.html.eex:513 -#: lib/block_scout_web/templates/transaction/overview.html.eex:513 +#: lib/block_scout_web/templates/transaction/overview.html.eex:489 +#: lib/block_scout_web/templates/transaction/overview.html.eex:490 #, elixir-autogen, elixir-format msgid "L1 Gas Price" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:501 -#: lib/block_scout_web/templates/transaction/overview.html.eex:501 -#: lib/block_scout_web/templates/transaction/overview.html.eex:502 -#: lib/block_scout_web/templates/transaction/overview.html.eex:502 +#: lib/block_scout_web/templates/transaction/overview.html.eex:478 +#: lib/block_scout_web/templates/transaction/overview.html.eex:479 #, elixir-autogen, elixir-format msgid "L1 Gas Used by Transaction" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:426 -#: lib/block_scout_web/templates/transaction/overview.html.eex:426 +#: lib/block_scout_web/templates/transaction/overview.html.eex:403 #, elixir-autogen, elixir-format msgid "L2 Gas Limit" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:400 -#: lib/block_scout_web/templates/transaction/overview.html.eex:400 +#: lib/block_scout_web/templates/transaction/overview.html.eex:377 #, elixir-autogen, elixir-format msgid "L2 Gas Price" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:485 -#: lib/block_scout_web/templates/transaction/overview.html.eex:485 +#: lib/block_scout_web/templates/transaction/overview.html.eex:462 #, elixir-autogen, elixir-format msgid "L2 Gas Used by Transaction" msgstr "" #: lib/block_scout_web/templates/verified_contracts/_stats.html.eex:13 -#: lib/block_scout_web/templates/verified_contracts/_stats.html.eex:13 -#: lib/block_scout_web/templates/verified_contracts/_stats.html.eex:26 #: lib/block_scout_web/templates/verified_contracts/_stats.html.eex:26 #, elixir-autogen, elixir-format msgid "Last 24h" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:260 #: lib/block_scout_web/templates/address/overview.html.eex:260 #, elixir-autogen, elixir-format msgid "Last Balance Update" msgstr "" -#: lib/block_scout_web/templates/account/api_key/index.html.eex:12 #: lib/block_scout_web/templates/account/api_key/index.html.eex:12 #: lib/block_scout_web/templates/account/api_key/index.html.eex:18 -#: lib/block_scout_web/templates/account/api_key/index.html.eex:18 #, elixir-autogen, elixir-format msgid "Learn more" msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:49 #: lib/block_scout_web/templates/layout/app.html.eex:49 #, elixir-autogen, elixir-format msgid "Less than" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_address.html.eex:4 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_address.html.eex:4 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_name.html.eex:4 -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_name.html.eex:4 #, elixir-autogen, elixir-format msgid "Library" msgstr "" -#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:24 #: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:24 #, elixir-autogen, elixir-format msgid "License Expires" msgstr "" -#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:10 #: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:10 #, elixir-autogen, elixir-format msgid "License ID" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:351 -#: lib/block_scout_web/templates/transaction/overview.html.eex:351 +#: lib/block_scout_web/templates/transaction/overview.html.eex:328 #, elixir-autogen, elixir-format msgid "List of ERC-1155 tokens created in the transaction." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:335 -#: lib/block_scout_web/templates/transaction/overview.html.eex:335 +#: lib/block_scout_web/templates/transaction/overview.html.eex:312 #, elixir-autogen, elixir-format msgid "List of token burnt in the transaction." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:318 -#: lib/block_scout_web/templates/transaction/overview.html.eex:318 +#: lib/block_scout_web/templates/transaction/overview.html.eex:295 #, elixir-autogen, elixir-format msgid "List of token minted in the transaction." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:302 -#: lib/block_scout_web/templates/transaction/overview.html.eex:302 +#: lib/block_scout_web/templates/transaction/overview.html.eex:279 #, elixir-autogen, elixir-format msgid "List of token transferred in the transaction." msgstr "" -#: lib/block_scout_web/templates/address_coin_balance/index.html.eex:18 #: lib/block_scout_web/templates/address_coin_balance/index.html.eex:18 #, elixir-autogen, elixir-format msgid "Loading chart..." msgstr "" #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:77 -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:77 -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:109 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:109 #: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:35 -#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:35 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:99 #: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:99 #: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:49 -#: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:49 #: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:45 -#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:45 -#: lib/block_scout_web/templates/address_read_contract/index.html.eex:41 #: lib/block_scout_web/templates/address_read_contract/index.html.eex:41 #: lib/block_scout_web/templates/address_read_contract/index.html.eex:49 -#: lib/block_scout_web/templates/address_read_contract/index.html.eex:49 -#: lib/block_scout_web/templates/address_read_proxy/index.html.eex:12 #: lib/block_scout_web/templates/address_read_proxy/index.html.eex:12 #: lib/block_scout_web/templates/address_write_contract/index.html.eex:39 -#: lib/block_scout_web/templates/address_write_contract/index.html.eex:39 -#: lib/block_scout_web/templates/address_write_contract/index.html.eex:47 #: lib/block_scout_web/templates/address_write_contract/index.html.eex:47 #: lib/block_scout_web/templates/address_write_proxy/index.html.eex:12 -#: lib/block_scout_web/templates/address_write_proxy/index.html.eex:12 -#: lib/block_scout_web/templates/tokens/contract/index.html.eex:17 #: lib/block_scout_web/templates/tokens/contract/index.html.eex:17 #, elixir-autogen, elixir-format msgid "Loading..." msgstr "" -#: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:2 #: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:2 #, elixir-autogen, elixir-format msgid "Log Data" msgstr "" -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:140 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:140 #, elixir-autogen, elixir-format msgid "Log Index" msgstr "" -#: lib/block_scout_web/templates/address/_tabs.html.eex:49 #: lib/block_scout_web/templates/address/_tabs.html.eex:49 #: lib/block_scout_web/templates/address_logs/index.html.eex:10 -#: lib/block_scout_web/templates/address_logs/index.html.eex:10 -#: lib/block_scout_web/templates/transaction/_tabs.html.eex:17 #: lib/block_scout_web/templates/transaction/_tabs.html.eex:17 #: lib/block_scout_web/templates/transaction_log/index.html.eex:8 -#: lib/block_scout_web/templates/transaction_log/index.html.eex:8 -#: lib/block_scout_web/views/address_view.ex:363 #: lib/block_scout_web/views/address_view.ex:363 -#: lib/block_scout_web/views/transaction_view.ex:553 -#: lib/block_scout_web/views/transaction_view.ex:553 +#: lib/block_scout_web/views/transaction_view.ex:551 #, elixir-autogen, elixir-format msgid "Logs" msgstr "" -#: lib/block_scout_web/templates/layout/_footer.html.eex:54 #: lib/block_scout_web/templates/layout/_footer.html.eex:54 #, elixir-autogen, elixir-format msgid "Main Networks" msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:53 -#: lib/block_scout_web/templates/chain/show.html.eex:53 -#: lib/block_scout_web/templates/layout/app.html.eex:50 +#: lib/block_scout_web/templates/chain/show.html.eex:55 #: lib/block_scout_web/templates/layout/app.html.eex:50 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:85 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:85 -#: lib/block_scout_web/views/address_view.ex:148 #: lib/block_scout_web/views/address_view.ex:148 #, elixir-autogen, elixir-format msgid "Market Cap" msgstr "" -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:84 #: lib/block_scout_web/templates/verified_contracts/index.html.eex:84 #, elixir-autogen, elixir-format msgid "Market cap" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:440 -#: lib/block_scout_web/templates/transaction/overview.html.eex:440 +#: lib/block_scout_web/templates/transaction/overview.html.eex:417 #, elixir-autogen, elixir-format msgid "Max Fee per Gas" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:450 -#: lib/block_scout_web/templates/transaction/overview.html.eex:450 +#: lib/block_scout_web/templates/transaction/overview.html.eex:427 #, elixir-autogen, elixir-format msgid "Max Priority Fee per Gas" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:331 -#: lib/block_scout_web/views/transaction_view.ex:331 +#: lib/block_scout_web/views/transaction_view.ex:329 #, elixir-autogen, elixir-format msgid "Max of" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:425 -#: lib/block_scout_web/templates/transaction/overview.html.eex:425 +#: lib/block_scout_web/templates/transaction/overview.html.eex:402 #, elixir-autogen, elixir-format msgid "Maximum gas amount approved for the transaction on L2." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:429 -#: lib/block_scout_web/templates/transaction/overview.html.eex:429 +#: lib/block_scout_web/templates/transaction/overview.html.eex:406 #, elixir-autogen, elixir-format msgid "Maximum gas amount approved for the transaction." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:439 -#: lib/block_scout_web/templates/transaction/overview.html.eex:439 +#: lib/block_scout_web/templates/transaction/overview.html.eex:416 #, elixir-autogen, elixir-format msgid "Maximum total amount per unit of gas a user is willing to pay for a transaction, including base fee and priority fee." msgstr "" -#: lib/block_scout_web/templates/tokens/instance/metadata/index.html.eex:18 #: lib/block_scout_web/templates/tokens/instance/metadata/index.html.eex:18 #: lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:10 -#: lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:10 -#: lib/block_scout_web/views/tokens/instance/overview_view.ex:75 #: lib/block_scout_web/views/tokens/instance/overview_view.ex:75 #, elixir-autogen, elixir-format msgid "Metadata" msgstr "" -#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:5 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:5 #, elixir-autogen, elixir-format msgid "Method Id" msgstr "" #: lib/block_scout_web/templates/block/_tile.html.eex:41 -#: lib/block_scout_web/templates/block/_tile.html.eex:41 -#: lib/block_scout_web/templates/block/overview.html.eex:98 #: lib/block_scout_web/templates/block/overview.html.eex:98 #: lib/block_scout_web/templates/chain/_block.html.eex:16 -#: lib/block_scout_web/templates/chain/_block.html.eex:16 -#: lib/block_scout_web/templates/transaction_state/_state_change.html.eex:22 #: lib/block_scout_web/templates/transaction_state/_state_change.html.eex:22 #, elixir-autogen, elixir-format msgid "Miner" msgstr "" #: lib/block_scout_web/views/block_view.ex:63 -#: lib/block_scout_web/views/block_view.ex:63 -#: lib/block_scout_web/views/block_view.ex:68 #: lib/block_scout_web/views/block_view.ex:68 #, elixir-autogen, elixir-format msgid "Miner Reward" msgstr "" -#: lib/block_scout_web/templates/common_components/_minimal_proxy_pattern.html.eex:3 #: lib/block_scout_web/templates/common_components/_minimal_proxy_pattern.html.eex:3 #, elixir-autogen, elixir-format msgid "Minimal Proxy Contract for" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:208 #: lib/block_scout_web/templates/block/overview.html.eex:208 #, elixir-autogen, elixir-format msgid "Minimum fee required per unit of gas. Fee adjusts based on network congestion." msgstr "" -#: lib/block_scout_web/templates/transaction/_actions.html.eex:92 #: lib/block_scout_web/templates/transaction/_actions.html.eex:92 #, elixir-autogen, elixir-format msgid "Mint of %{address} To %{to}" msgstr "" -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:223 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:223 #, elixir-autogen, elixir-format msgid "Model" msgstr "" -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:58 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:58 #, elixir-autogen, elixir-format msgid "Module" msgstr "" -#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:12 #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:12 #, elixir-autogen, elixir-format msgid "More internal transactions have come in" msgstr "" #: lib/block_scout_web/templates/address_transaction/index.html.eex:46 -#: lib/block_scout_web/templates/address_transaction/index.html.eex:46 -#: lib/block_scout_web/templates/chain/show.html.eex:219 -#: lib/block_scout_web/templates/chain/show.html.eex:219 -#: lib/block_scout_web/templates/pending_transaction/index.html.eex:13 +#: lib/block_scout_web/templates/chain/show.html.eex:221 #: lib/block_scout_web/templates/pending_transaction/index.html.eex:13 #: lib/block_scout_web/templates/transaction/index.html.eex:19 -#: lib/block_scout_web/templates/transaction/index.html.eex:19 #, elixir-autogen, elixir-format msgid "More transactions have come in" msgstr "" -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:63 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:63 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:74 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:74 #, elixir-autogen, elixir-format msgid "Must be set to:" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:22 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:22 #, elixir-autogen, elixir-format msgid "Must match the name specified in the code. For example, in contract MyContract {..} MyContract is the contract name." msgstr "" #: lib/block_scout_web/templates/tokens/_tile.html.eex:40 -#: lib/block_scout_web/templates/tokens/_tile.html.eex:40 -#: lib/block_scout_web/templates/verified_contracts/_contract.html.eex:21 #: lib/block_scout_web/templates/verified_contracts/_contract.html.eex:21 -#: lib/block_scout_web/templates/verified_contracts/_contract.html.eex:61 -#: lib/block_scout_web/templates/verified_contracts/_contract.html.eex:61 +#: lib/block_scout_web/templates/verified_contracts/_contract.html.eex:65 #, elixir-autogen, elixir-format msgid "N/A" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:116 #: lib/block_scout_web/templates/block/overview.html.eex:116 #, elixir-autogen, elixir-format msgid "N/A bytes" msgstr "" -#: lib/block_scout_web/templates/account/api_key/form.html.eex:19 #: lib/block_scout_web/templates/account/api_key/form.html.eex:19 #: lib/block_scout_web/templates/account/api_key/index.html.eex:28 -#: lib/block_scout_web/templates/account/api_key/index.html.eex:28 -#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:13 #: lib/block_scout_web/templates/account/custom_abi/form.html.eex:13 #: lib/block_scout_web/templates/account/custom_abi/index.html.eex:28 -#: lib/block_scout_web/templates/account/custom_abi/index.html.eex:28 -#: lib/block_scout_web/templates/account/tag_address/form.html.eex:18 #: lib/block_scout_web/templates/account/tag_address/form.html.eex:18 #: lib/block_scout_web/templates/account/tag_address/index.html.eex:22 -#: lib/block_scout_web/templates/account/tag_address/index.html.eex:22 -#: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:18 #: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:18 #: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:22 -#: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:22 -#: lib/block_scout_web/templates/account/watchlist/show.html.eex:22 #: lib/block_scout_web/templates/account/watchlist/show.html.eex:22 #: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:19 -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:19 -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_name.html.eex:4 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_name.html.eex:4 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:52 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:52 -#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:59 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:59 #: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:4 -#: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:4 -#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:21 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:21 #, elixir-autogen, elixir-format msgid "Name" msgstr "" -#: lib/block_scout_web/templates/account/api_key/form.html.eex:20 #: lib/block_scout_web/templates/account/api_key/form.html.eex:20 #, elixir-autogen, elixir-format msgid "Name this API key" msgstr "" -#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:14 #: lib/block_scout_web/templates/account/custom_abi/form.html.eex:14 #, elixir-autogen, elixir-format msgid "Name this Custom ABI" msgstr "" #: lib/block_scout_web/templates/account/tag_address/form.html.eex:19 -#: lib/block_scout_web/templates/account/tag_address/form.html.eex:19 -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:20 #: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:20 #, elixir-autogen, elixir-format msgid "Name this address" msgstr "" -#: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:19 #: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:19 #, elixir-autogen, elixir-format msgid "Name this transaction" msgstr "" -#: lib/block_scout_web/templates/address_token/overview.html.eex:44 #: lib/block_scout_web/templates/address_token/overview.html.eex:44 #, elixir-autogen, elixir-format msgid "Net Worth" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:5 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:5 #, elixir-autogen, elixir-format msgid "New Smart Contract Verification" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:9 #: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:9 #, elixir-autogen, elixir-format msgid "New Smart Contract Verification via Standard input JSON" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:5 #: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:5 #, elixir-autogen, elixir-format msgid "New Smart Contract Verification via metadata JSON" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:9 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:9 #: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:7 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:7 #, elixir-autogen, elixir-format msgid "New Solidity Smart Contract Verification" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:7 #: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:7 #, elixir-autogen, elixir-format msgid "New Solidity/Yul Smart Contract Verification" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:7 #: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:7 #, elixir-autogen, elixir-format msgid "New Vyper Smart Contract Verification" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:80 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:80 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:87 -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:87 -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:95 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:95 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:103 -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:103 -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:111 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:111 #, elixir-autogen, elixir-format msgid "Next" msgstr "" #: lib/block_scout_web/templates/address_contract_verification_common_fields/_fetch_constructor_args.html.eex:9 -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_fetch_constructor_args.html.eex:9 -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_include_nightly_builds_field.html.eex:9 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_include_nightly_builds_field.html.eex:9 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_yul_contracts_switcher.html.eex:9 -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_yul_contracts_switcher.html.eex:9 -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:46 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:46 #: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:38 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:38 #, elixir-autogen, elixir-format msgid "No" msgstr "" -#: lib/block_scout_web/templates/transaction_raw_trace/_card_body.html.eex:15 #: lib/block_scout_web/templates/transaction_raw_trace/_card_body.html.eex:15 #, elixir-autogen, elixir-format msgid "No trace entries found." msgstr "" #: lib/block_scout_web/templates/block/overview.html.eex:198 -#: lib/block_scout_web/templates/block/overview.html.eex:198 -#: lib/block_scout_web/templates/transaction/overview.html.eex:535 -#: lib/block_scout_web/templates/transaction/overview.html.eex:535 +#: lib/block_scout_web/templates/transaction/overview.html.eex:512 #, elixir-autogen, elixir-format msgid "Nonce" msgstr "" -#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:11 #: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:11 #, elixir-autogen, elixir-format msgid "Not unique Token" msgstr "" -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:107 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:107 #, elixir-autogen, elixir-format msgid "Number of accounts holding the token" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:276 #: lib/block_scout_web/templates/address/overview.html.eex:276 #, elixir-autogen, elixir-format msgid "Number of blocks validated by this validator." msgstr "" -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:130 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:130 #, elixir-autogen, elixir-format msgid "Number of digits that come after the decimal place when displaying token value" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:187 #: lib/block_scout_web/templates/address/overview.html.eex:187 #, elixir-autogen, elixir-format msgid "Number of transactions related to this address." msgstr "" -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:118 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:118 #, elixir-autogen, elixir-format msgid "Number of transfers for the token" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:214 #: lib/block_scout_web/templates/address/overview.html.eex:214 #, elixir-autogen, elixir-format msgid "Number of transfers to/from this address." msgstr "" -#: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:40 #: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:40 #: lib/block_scout_web/templates/transaction/_tile.html.eex:88 -#: lib/block_scout_web/templates/transaction/_tile.html.eex:88 #, elixir-autogen, elixir-format msgid "OUT" msgstr "" -#: lib/block_scout_web/templates/common_components/_rap_pagination_container.html.eex:13 #: lib/block_scout_web/templates/common_components/_rap_pagination_container.html.eex:13 #, elixir-autogen, elixir-format msgid "Only the first" msgstr "" #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:40 -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:40 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:32 #: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:32 #: lib/block_scout_web/templates/verified_contracts/index.html.eex:75 -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:75 #, elixir-autogen, elixir-format msgid "Optimization" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:67 #: lib/block_scout_web/templates/address_contract/index.html.eex:67 #, elixir-autogen, elixir-format msgid "Optimization enabled" msgstr "" #: lib/block_scout_web/templates/address_contract/index.html.eex:76 -#: lib/block_scout_web/templates/address_contract/index.html.eex:76 -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:62 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:62 #: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:54 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:54 #, elixir-autogen, elixir-format msgid "Optimization runs" msgstr "" -#: lib/block_scout_web/templates/layout/_footer.html.eex:78 #: lib/block_scout_web/templates/layout/_footer.html.eex:78 #, elixir-autogen, elixir-format msgid "Other Explorers" msgstr "" #: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:35 -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:35 -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:48 #: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:48 #: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:61 -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:61 #, elixir-autogen, elixir-format msgid "Outgoing" msgstr "" -#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:24 #: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:24 #, elixir-autogen, elixir-format msgid "Owner Address" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:77 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:77 #, elixir-autogen, elixir-format msgid "POA solidity flattener or the" msgstr "" -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:19 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:19 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:26 -#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:26 #, elixir-autogen, elixir-format msgid "POST" msgstr "" -#: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:41 #: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:41 #, elixir-autogen, elixir-format msgid "Page" msgstr "" -#: lib/block_scout_web/templates/page_not_found/index.html.eex:7 #: lib/block_scout_web/templates/page_not_found/index.html.eex:7 #, elixir-autogen, elixir-format msgid "Page not found" msgstr "" -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:33 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:33 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:40 -#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:40 #, elixir-autogen, elixir-format msgid "Parameters" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:139 #: lib/block_scout_web/templates/block/overview.html.eex:139 #, elixir-autogen, elixir-format msgid "Parent Hash" msgstr "" #: lib/block_scout_web/templates/layout/_topnav.html.eex:63 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:63 -#: lib/block_scout_web/views/transaction_view.ex:374 -#: lib/block_scout_web/views/transaction_view.ex:374 -#: lib/block_scout_web/views/transaction_view.ex:419 -#: lib/block_scout_web/views/transaction_view.ex:419 -#: lib/block_scout_web/views/transaction_view.ex:427 -#: lib/block_scout_web/views/transaction_view.ex:427 +#: lib/block_scout_web/views/transaction_view.ex:372 +#: lib/block_scout_web/views/transaction_view.ex:417 +#: lib/block_scout_web/views/transaction_view.ex:425 #, elixir-autogen, elixir-format msgid "Pending" msgstr "" -#: lib/block_scout_web/templates/pending_transaction/index.html.eex:5 #: lib/block_scout_web/templates/pending_transaction/index.html.eex:5 #, elixir-autogen, elixir-format msgid "Pending Transactions" msgstr "" #: lib/block_scout_web/templates/address/_custom_view_df_title.html.eex:9 -#: lib/block_scout_web/templates/address/_custom_view_df_title.html.eex:9 -#: lib/block_scout_web/templates/address/_custom_view_df_title.html.eex:13 #: lib/block_scout_web/templates/address/_custom_view_df_title.html.eex:13 #, elixir-autogen, elixir-format msgid "Play" msgstr "" #: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:21 -#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:21 -#: lib/block_scout_web/templates/layout/app.html.eex:100 #: lib/block_scout_web/templates/layout/app.html.eex:100 #, elixir-autogen, elixir-format msgid "Please confirm your email address to use the My Account feature." msgstr "" -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:68 #: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:68 #, elixir-autogen, elixir-format msgid "Please select notification methods:" msgstr "" -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:24 #: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:24 #, elixir-autogen, elixir-format msgid "Please select what types of notifications you will receive:" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:537 -#: lib/block_scout_web/templates/transaction/overview.html.eex:537 +#: lib/block_scout_web/templates/transaction/overview.html.eex:514 #, elixir-autogen, elixir-format msgid "Position" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:256 #: lib/block_scout_web/templates/block/overview.html.eex:256 #, elixir-autogen, elixir-format msgid "Position %{index}" msgstr "" -#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:18 #: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:18 #, elixir-autogen, elixir-format msgid "Potential matches from contract method database:" msgstr "" -#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:32 #: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:32 #, elixir-autogen, elixir-format msgid "Potential matches from our contract method database:" msgstr "" -#: lib/block_scout_web/templates/layout/_search.html.eex:27 #: lib/block_scout_web/templates/layout/_search.html.eex:27 #, elixir-autogen, elixir-format msgid "Press / and focus will be moved to the search field" msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:42 -#: lib/block_scout_web/templates/chain/show.html.eex:42 +#: lib/block_scout_web/templates/chain/show.html.eex:44 #: lib/block_scout_web/templates/layout/app.html.eex:51 -#: lib/block_scout_web/templates/layout/app.html.eex:51 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:96 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:96 #, elixir-autogen, elixir-format msgid "Price" msgstr "" -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:95 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:95 #, elixir-autogen, elixir-format msgid "Price per token on the exchanges" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:399 -#: lib/block_scout_web/templates/transaction/overview.html.eex:399 +#: lib/block_scout_web/templates/transaction/overview.html.eex:376 #, elixir-autogen, elixir-format msgid "Price per unit of gas specified by the sender on L2. Higher gas prices can prioritize transaction inclusion during times of high usage." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:403 -#: lib/block_scout_web/templates/transaction/overview.html.eex:403 +#: lib/block_scout_web/templates/transaction/overview.html.eex:380 #, elixir-autogen, elixir-format msgid "Price per unit of gas specified by the sender. Higher gas prices can prioritize transaction inclusion during times of high usage." msgstr "" #: lib/block_scout_web/templates/block/overview.html.eex:227 -#: lib/block_scout_web/templates/block/overview.html.eex:227 -#: lib/block_scout_web/templates/transaction/overview.html.eex:460 -#: lib/block_scout_web/templates/transaction/overview.html.eex:460 +#: lib/block_scout_web/templates/transaction/overview.html.eex:437 #, elixir-autogen, elixir-format msgid "Priority Fee / Tip" msgstr "" -#: lib/block_scout_web/templates/block/_tile.html.eex:62 #: lib/block_scout_web/templates/block/_tile.html.eex:62 #, elixir-autogen, elixir-format msgid "Priority Fees" msgstr "" -#: lib/block_scout_web/templates/account/common/_nav.html.eex:4 #: lib/block_scout_web/templates/account/common/_nav.html.eex:4 #: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:14 -#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:14 #, elixir-autogen, elixir-format msgid "Profile" msgstr "" -#: lib/block_scout_web/templates/common_components/_btn_qr_code.html.eex:10 #: lib/block_scout_web/templates/common_components/_btn_qr_code.html.eex:10 #: lib/block_scout_web/templates/common_components/_modal_qr_code.html.eex:5 -#: lib/block_scout_web/templates/common_components/_modal_qr_code.html.eex:5 -#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:83 #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:83 #, elixir-autogen, elixir-format msgid "QR Code" msgstr "" -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:106 #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:106 #, elixir-autogen, elixir-format msgid "Query" msgstr "" -#: lib/block_scout_web/templates/layout/_topnav.html.eex:115 #: lib/block_scout_web/templates/layout/_topnav.html.eex:115 #, elixir-autogen, elixir-format msgid "RPC" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:546 -#: lib/block_scout_web/templates/transaction/overview.html.eex:546 +#: lib/block_scout_web/templates/transaction/overview.html.eex:523 #, elixir-autogen, elixir-format msgid "Raw Input" msgstr "" -#: lib/block_scout_web/templates/transaction/_tabs.html.eex:24 #: lib/block_scout_web/templates/transaction/_tabs.html.eex:24 #: lib/block_scout_web/templates/transaction_raw_trace/_card_body.html.eex:1 -#: lib/block_scout_web/templates/transaction_raw_trace/_card_body.html.eex:1 -#: lib/block_scout_web/views/transaction_view.ex:554 -#: lib/block_scout_web/views/transaction_view.ex:554 +#: lib/block_scout_web/views/transaction_view.ex:552 #, elixir-autogen, elixir-format msgid "Raw Trace" msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:81 -#: lib/block_scout_web/templates/address/_tabs.html.eex:81 -#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:27 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:27 #: lib/block_scout_web/views/address_view.ex:357 -#: lib/block_scout_web/views/address_view.ex:357 -#: lib/block_scout_web/views/tokens/overview_view.ex:41 #: lib/block_scout_web/views/tokens/overview_view.ex:41 #, elixir-autogen, elixir-format msgid "Read Contract" msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:88 -#: lib/block_scout_web/templates/address/_tabs.html.eex:88 -#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:41 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:41 #: lib/block_scout_web/views/address_view.ex:358 -#: lib/block_scout_web/views/address_view.ex:358 #, elixir-autogen, elixir-format msgid "Read Proxy" msgstr "" -#: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:13 #: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:13 #, elixir-autogen, elixir-format msgid "Records" msgstr "" #: lib/block_scout_web/templates/account/api_key/row.html.eex:13 -#: lib/block_scout_web/templates/account/api_key/row.html.eex:13 -#: lib/block_scout_web/templates/account/custom_abi/row.html.eex:13 #: lib/block_scout_web/templates/account/custom_abi/row.html.eex:13 #, elixir-autogen, elixir-format msgid "Remove" msgstr "" -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:77 #: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:77 #, elixir-autogen, elixir-format msgid "Remove from Watch list" msgstr "" -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:155 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:155 #, elixir-autogen, elixir-format msgid "Request URL" msgstr "" -#: lib/block_scout_web/templates/error422/index.html.eex:7 #: lib/block_scout_web/templates/error422/index.html.eex:7 #, elixir-autogen, elixir-format msgid "Request cannot be processed" msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:100 #: lib/block_scout_web/templates/layout/app.html.eex:100 #, elixir-autogen, elixir-format msgid "Resend verification email" msgstr "" #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:112 -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:112 -#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:38 #: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:38 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:104 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:104 +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:102 #: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:52 -#: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:52 -#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:48 #: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:48 #, elixir-autogen, elixir-format msgid "Reset" msgstr "" #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:173 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:173 -#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:134 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:134 #, elixir-autogen, elixir-format msgid "Response Body" msgstr "" #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:185 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:185 -#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:147 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:147 #, elixir-autogen, elixir-format msgid "Responses" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:98 #: lib/block_scout_web/templates/transaction/overview.html.eex:98 #, elixir-autogen, elixir-format msgid "Result" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:138 #: lib/block_scout_web/templates/transaction/overview.html.eex:138 #, elixir-autogen, elixir-format msgid "Revert reason" msgstr "" #: lib/block_scout_web/templates/block/_tile.html.eex:52 -#: lib/block_scout_web/templates/block/_tile.html.eex:52 -#: lib/block_scout_web/templates/chain/_block.html.eex:27 #: lib/block_scout_web/templates/chain/_block.html.eex:27 -#: lib/block_scout_web/views/internal_transaction_view.ex:29 -#: lib/block_scout_web/views/internal_transaction_view.ex:29 +#: lib/block_scout_web/views/internal_transaction_view.ex:34 #, elixir-autogen, elixir-format msgid "Reward" msgstr "" -#: lib/block_scout_web/templates/admin/dashboard/index.html.eex:21 #: lib/block_scout_web/templates/admin/dashboard/index.html.eex:21 #, elixir-autogen, elixir-format msgid "Run" msgstr "" -#: lib/block_scout_web/templates/account/api_key/form.html.eex:26 #: lib/block_scout_web/templates/account/api_key/form.html.eex:26 #: lib/block_scout_web/templates/account/custom_abi/form.html.eex:31 -#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:31 -#: lib/block_scout_web/templates/account/tag_address/form.html.eex:25 #: lib/block_scout_web/templates/account/tag_address/form.html.eex:25 #: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:25 -#: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:25 -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:83 #: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:83 #, elixir-autogen, elixir-format msgid "Save" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:226 -#: lib/block_scout_web/templates/transaction/overview.html.eex:226 -#, elixir-autogen, elixir-format -msgid "Scroll to see more" -msgstr "" - -#: lib/block_scout_web/templates/address_logs/index.html.eex:16 #: lib/block_scout_web/templates/address_logs/index.html.eex:16 #: lib/block_scout_web/templates/layout/_search.html.eex:34 -#: lib/block_scout_web/templates/layout/_search.html.eex:34 #, elixir-autogen, elixir-format msgid "Search" msgstr "" -#: lib/block_scout_web/templates/search/results.html.eex:17 #: lib/block_scout_web/templates/search/results.html.eex:17 #, elixir-autogen, elixir-format msgid "Search Results" msgstr "" -#: lib/block_scout_web/templates/layout/_search.html.eex:3 #: lib/block_scout_web/templates/layout/_search.html.eex:3 #, elixir-autogen, elixir-format msgid "Search by address, token symbol name, transaction hash, or block number" msgstr "" -#: lib/block_scout_web/templates/address_token_balance/_token_balances.html.eex:47 #: lib/block_scout_web/templates/address_token_balance/_token_balances.html.eex:47 #, elixir-autogen, elixir-format msgid "Search tokens" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_yul_contracts_switcher.html.eex:19 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_yul_contracts_switcher.html.eex:19 #, elixir-autogen, elixir-format msgid "Select Yes if you want to verify Yul contract." msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_include_nightly_builds_field.html.eex:19 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_include_nightly_builds_field.html.eex:19 #, elixir-autogen, elixir-format msgid "Select yes if you want to show nightly builds." msgstr "" -#: lib/block_scout_web/views/internal_transaction_view.ex:28 -#: lib/block_scout_web/views/internal_transaction_view.ex:28 +#: lib/block_scout_web/views/internal_transaction_view.ex:33 #, elixir-autogen, elixir-format msgid "Self-Destruct" msgstr "" #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:163 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:163 -#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:124 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:124 #, elixir-autogen, elixir-format msgid "Server Response" msgstr "" -#: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:7 #: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:7 #, elixir-autogen, elixir-format msgid "Show" msgstr "" -#: lib/block_scout_web/templates/common_components/_btn_qr_code.html.eex:11 #: lib/block_scout_web/templates/common_components/_btn_qr_code.html.eex:11 #, elixir-autogen, elixir-format msgid "Show QR Code" msgstr "" -#: lib/block_scout_web/templates/address_token/overview.html.eex:52 #: lib/block_scout_web/templates/address_token/overview.html.eex:52 #, elixir-autogen, elixir-format msgid "Shows the current" msgstr "" -#: lib/block_scout_web/templates/address_token/overview.html.eex:59 #: lib/block_scout_web/templates/address_token/overview.html.eex:59 #, elixir-autogen, elixir-format msgid "Shows the tokens held in the address (includes ERC-20, ERC-721 and ERC-1155)." msgstr "" -#: lib/block_scout_web/templates/address_token/overview.html.eex:66 #: lib/block_scout_web/templates/address_token/overview.html.eex:66 #, elixir-autogen, elixir-format msgid "Shows the total CRC balance in the address." msgstr "" -#: lib/block_scout_web/templates/address_token/overview.html.eex:45 #: lib/block_scout_web/templates/address_token/overview.html.eex:45 #, elixir-autogen, elixir-format msgid "Shows total assets held in the address" msgstr "" -#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:32 #: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:32 #, elixir-autogen, elixir-format msgid "Sign in" msgstr "" -#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:23 #: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:23 #, elixir-autogen, elixir-format msgid "Sign out" msgstr "" -#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:11 #: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:11 #, elixir-autogen, elixir-format msgid "Signed in as " msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:114 #: lib/block_scout_web/templates/block/overview.html.eex:114 #, elixir-autogen, elixir-format msgid "Size" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:113 #: lib/block_scout_web/templates/block/overview.html.eex:113 #, elixir-autogen, elixir-format msgid "Size of the block in bytes." msgstr "" -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:20 #: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:20 #, elixir-autogen, elixir-format msgid "Slow" msgstr "" -#: lib/block_scout_web/templates/verified_contracts/_contract.html.eex:28 -#: lib/block_scout_web/templates/verified_contracts/_contract.html.eex:28 +#: lib/block_scout_web/templates/verified_contracts/_contract.html.eex:31 #: lib/block_scout_web/templates/verified_contracts/index.html.eex:26 -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:26 -#: lib/block_scout_web/views/verified_contracts_view.ex:10 #: lib/block_scout_web/views/verified_contracts_view.ex:10 #, elixir-autogen, elixir-format msgid "Solidity" msgstr "" #: lib/block_scout_web/templates/address_coin_balance/index.html.eex:30 -#: lib/block_scout_web/templates/address_coin_balance/index.html.eex:30 -#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:50 #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:50 #: lib/block_scout_web/templates/address_logs/index.html.eex:23 -#: lib/block_scout_web/templates/address_logs/index.html.eex:23 -#: lib/block_scout_web/templates/address_token/index.html.eex:60 #: lib/block_scout_web/templates/address_token/index.html.eex:60 #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:58 -#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:58 -#: lib/block_scout_web/templates/address_transaction/index.html.eex:50 #: lib/block_scout_web/templates/address_transaction/index.html.eex:50 #: lib/block_scout_web/templates/address_validation/index.html.eex:20 -#: lib/block_scout_web/templates/address_validation/index.html.eex:20 -#: lib/block_scout_web/templates/address_withdrawal/index.html.eex:20 #: lib/block_scout_web/templates/address_withdrawal/index.html.eex:20 #: lib/block_scout_web/templates/block_transaction/index.html.eex:14 -#: lib/block_scout_web/templates/block_transaction/index.html.eex:14 -#: lib/block_scout_web/templates/block_withdrawal/index.html.eex:14 #: lib/block_scout_web/templates/block_withdrawal/index.html.eex:14 -#: lib/block_scout_web/templates/chain/show.html.eex:160 -#: lib/block_scout_web/templates/chain/show.html.eex:160 +#: lib/block_scout_web/templates/chain/show.html.eex:162 #: lib/block_scout_web/templates/pending_transaction/index.html.eex:18 -#: lib/block_scout_web/templates/pending_transaction/index.html.eex:18 -#: lib/block_scout_web/templates/tokens/holder/index.html.eex:24 #: lib/block_scout_web/templates/tokens/holder/index.html.eex:24 #: lib/block_scout_web/templates/tokens/instance/holder/index.html.eex:23 -#: lib/block_scout_web/templates/tokens/instance/holder/index.html.eex:23 -#: lib/block_scout_web/templates/tokens/instance/transfer/index.html.eex:23 #: lib/block_scout_web/templates/tokens/instance/transfer/index.html.eex:23 #: lib/block_scout_web/templates/tokens/inventory/index.html.eex:23 -#: lib/block_scout_web/templates/tokens/inventory/index.html.eex:23 -#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:22 #: lib/block_scout_web/templates/tokens/transfer/index.html.eex:22 #: lib/block_scout_web/templates/transaction/index.html.eex:25 -#: lib/block_scout_web/templates/transaction/index.html.eex:25 -#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:13 #: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:13 #: lib/block_scout_web/templates/transaction_log/index.html.eex:15 -#: lib/block_scout_web/templates/transaction_log/index.html.eex:15 -#: lib/block_scout_web/templates/transaction_state/index.html.eex:13 #: lib/block_scout_web/templates/transaction_state/index.html.eex:13 #: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:14 -#: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:14 -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:51 #: lib/block_scout_web/templates/verified_contracts/index.html.eex:51 #: lib/block_scout_web/templates/withdrawal/index.html.eex:14 -#: lib/block_scout_web/templates/withdrawal/index.html.eex:14 #, elixir-autogen, elixir-format msgid "Something went wrong, click to reload." msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:225 -#: lib/block_scout_web/templates/chain/show.html.eex:225 +#: lib/block_scout_web/templates/chain/show.html.eex:227 #, elixir-autogen, elixir-format msgid "Something went wrong, click to retry." msgstr "" -#: lib/block_scout_web/templates/transaction/not_found.html.eex:6 #: lib/block_scout_web/templates/transaction/not_found.html.eex:6 #, elixir-autogen, elixir-format msgid "Sorry, we are unable to locate this transaction hash" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:63 #: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:63 #, elixir-autogen, elixir-format msgid "Sources *.sol files" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:63 #: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:63 #, elixir-autogen, elixir-format msgid "Sources *.sol or *.yul files" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:14 #: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:14 #, elixir-autogen, elixir-format msgid "Sources and Metadata JSON" msgstr "" -#: lib/block_scout_web/templates/layout/_topnav.html.eex:136 #: lib/block_scout_web/templates/layout/_topnav.html.eex:136 #, elixir-autogen, elixir-format msgid "Stakes" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:24 #: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:24 #, elixir-autogen, elixir-format msgid "Standard Input JSON" msgstr "" -#: lib/block_scout_web/templates/transaction/_tabs.html.eex:29 #: lib/block_scout_web/templates/transaction/_tabs.html.eex:29 #: lib/block_scout_web/templates/transaction_state/index.html.eex:6 -#: lib/block_scout_web/templates/transaction_state/index.html.eex:6 -#: lib/block_scout_web/views/transaction_view.ex:555 -#: lib/block_scout_web/views/transaction_view.ex:555 +#: lib/block_scout_web/views/transaction_view.ex:553 #, elixir-autogen, elixir-format msgid "State changes" msgstr "" -#: lib/block_scout_web/views/internal_transaction_view.ex:24 -#: lib/block_scout_web/views/internal_transaction_view.ex:24 +#: lib/block_scout_web/views/internal_transaction_view.ex:26 #, elixir-autogen, elixir-format msgid "Static Call" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:110 #: lib/block_scout_web/templates/transaction/overview.html.eex:110 #, elixir-autogen, elixir-format msgid "Status" msgstr "" -#: lib/block_scout_web/templates/layout/_footer.html.eex:41 #: lib/block_scout_web/templates/layout/_footer.html.eex:41 #, elixir-autogen, elixir-format msgid "Submit an Issue" msgstr "" #: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:8 -#: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:8 -#: lib/block_scout_web/views/transaction_view.ex:376 -#: lib/block_scout_web/views/transaction_view.ex:376 +#: lib/block_scout_web/views/transaction_view.ex:374 #, elixir-autogen, elixir-format msgid "Success" msgstr "" -#: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:21 #: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:21 #: lib/block_scout_web/templates/transaction/_tile.html.eex:52 -#: lib/block_scout_web/templates/transaction/_tile.html.eex:52 #, elixir-autogen, elixir-format msgid "TX Fee" msgstr "" -#: lib/block_scout_web/templates/layout/_footer.html.eex:31 #: lib/block_scout_web/templates/layout/_footer.html.eex:31 #, elixir-autogen, elixir-format msgid "Telegram" msgstr "" -#: lib/block_scout_web/templates/layout/_footer.html.eex:67 #: lib/block_scout_web/templates/layout/_footer.html.eex:67 #, elixir-autogen, elixir-format msgid "Test Networks" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_first.html.eex:9 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_first.html.eex:9 #, elixir-autogen, elixir-format msgid "The 0x library address. This can be found in the generated json file or Truffle output (if using truffle)." msgstr "" #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:34 -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:34 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:26 #: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:26 #, elixir-autogen, elixir-format msgid "The EVM version the contract is written for. If the bytecode does not match the version, we try to verify using the latest EVM version." msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:122 #: lib/block_scout_web/templates/block/overview.html.eex:122 #, elixir-autogen, elixir-format msgid "The SHA256 hash of the block." msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:51 #: lib/block_scout_web/templates/block/overview.html.eex:51 #, elixir-autogen, elixir-format msgid "The block height of a particular block is defined as the number of blocks preceding it in the blockchain." msgstr "" -#: lib/block_scout_web/templates/transaction_state/index.html.eex:18 #: lib/block_scout_web/templates/transaction_state/index.html.eex:18 #, elixir-autogen, elixir-format msgid "The changes from this transaction have not yet happened since the transaction is still pending." msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:26 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:26 #: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:18 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:18 #, elixir-autogen, elixir-format msgid "The compiler version is specified in pragma solidity X.X.X. Use the compiler version rather than the nightly build. If using the Solidity compiler, run solc —version to check." msgstr "" -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:44 #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:44 #, elixir-autogen, elixir-format msgid "The fallback function is executed on a call to the contract if none of the other functions match the given function signature, or if no data was supplied at all and there is no receive Ether function. The fallback function always receives data, but in order to also receive Ether it must be marked payable." msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:138 #: lib/block_scout_web/templates/block/overview.html.eex:138 #, elixir-autogen, elixir-format msgid "The hash of the block from which this block was generated." msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:74 #: lib/block_scout_web/templates/address/overview.html.eex:74 #, elixir-autogen, elixir-format msgid "The name found in the source code of the Contract." msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:85 #: lib/block_scout_web/templates/address/overview.html.eex:85 #, elixir-autogen, elixir-format msgid "The name of the validator." msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:79 #: lib/block_scout_web/templates/block/overview.html.eex:79 #, elixir-autogen, elixir-format msgid "The number of transactions in the block." msgstr "" -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:46 #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:46 #, elixir-autogen, elixir-format msgid "The receive function is executed on a call to the contract with empty calldata. This is the function that is executed on plain Ether transfers (e.g. via .send() or .transfer()). If no such function exists, but a payable fallback function exists, the fallback function will be called on a plain Ether transfer. If neither a receive Ether nor a payable fallback function is present, the contract cannot receive Ether through regular transactions and throws an exception." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:137 #: lib/block_scout_web/templates/transaction/overview.html.eex:137 #, elixir-autogen, elixir-format msgid "The revert reason of the transaction." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:109 #: lib/block_scout_web/templates/transaction/overview.html.eex:109 #, elixir-autogen, elixir-format msgid "The status of the transaction: Confirmed or Unconfirmed." msgstr "" -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:67 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:67 #, elixir-autogen, elixir-format msgid "The total amount of tokens issued" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:179 #: lib/block_scout_web/templates/block/overview.html.eex:179 #, elixir-autogen, elixir-format msgid "The total gas amount used in the block and its percentage of gas filled in the block." msgstr "" -#: lib/block_scout_web/templates/address_validation/index.html.eex:16 #: lib/block_scout_web/templates/address_validation/index.html.eex:16 #, elixir-autogen, elixir-format msgid "There are no blocks validated by this address." msgstr "" -#: lib/block_scout_web/templates/block/index.html.eex:17 #: lib/block_scout_web/templates/block/index.html.eex:17 #, elixir-autogen, elixir-format msgid "There are no blocks." msgstr "" -#: lib/block_scout_web/templates/tokens/holder/index.html.eex:29 #: lib/block_scout_web/templates/tokens/holder/index.html.eex:29 #, elixir-autogen, elixir-format msgid "There are no holders for this Token." msgstr "" -#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:54 #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:54 #, elixir-autogen, elixir-format msgid "There are no internal transactions for this address." msgstr "" -#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:17 #: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:17 #, elixir-autogen, elixir-format msgid "There are no internal transactions for this transaction." msgstr "" -#: lib/block_scout_web/templates/address_logs/index.html.eex:28 #: lib/block_scout_web/templates/address_logs/index.html.eex:28 #, elixir-autogen, elixir-format msgid "There are no logs for this address." msgstr "" -#: lib/block_scout_web/templates/transaction_log/index.html.eex:20 #: lib/block_scout_web/templates/transaction_log/index.html.eex:20 #, elixir-autogen, elixir-format msgid "There are no logs for this transaction." msgstr "" -#: lib/block_scout_web/templates/pending_transaction/index.html.eex:22 #: lib/block_scout_web/templates/pending_transaction/index.html.eex:22 #, elixir-autogen, elixir-format msgid "There are no pending transactions." msgstr "" -#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:53 #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:53 #, elixir-autogen, elixir-format msgid "There are no token transfers for this address." msgstr "" -#: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:19 #: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:19 #, elixir-autogen, elixir-format msgid "There are no token transfers for this transaction" msgstr "" -#: lib/block_scout_web/templates/address_token/index.html.eex:65 #: lib/block_scout_web/templates/address_token/index.html.eex:65 #, elixir-autogen, elixir-format msgid "There are no tokens for this address." msgstr "" -#: lib/block_scout_web/templates/tokens/inventory/index.html.eex:28 #: lib/block_scout_web/templates/tokens/inventory/index.html.eex:28 #, elixir-autogen, elixir-format msgid "There are no tokens." msgstr "" -#: lib/block_scout_web/templates/address_transaction/index.html.eex:55 #: lib/block_scout_web/templates/address_transaction/index.html.eex:55 #, elixir-autogen, elixir-format msgid "There are no transactions for this address." msgstr "" -#: lib/block_scout_web/templates/block_transaction/index.html.eex:19 #: lib/block_scout_web/templates/block_transaction/index.html.eex:19 #, elixir-autogen, elixir-format msgid "There are no transactions for this block." msgstr "" -#: lib/block_scout_web/templates/transaction/index.html.eex:31 #: lib/block_scout_web/templates/transaction/index.html.eex:31 #, elixir-autogen, elixir-format msgid "There are no transactions." msgstr "" #: lib/block_scout_web/templates/tokens/instance/holder/index.html.eex:28 -#: lib/block_scout_web/templates/tokens/instance/holder/index.html.eex:28 -#: lib/block_scout_web/templates/tokens/instance/transfer/index.html.eex:28 #: lib/block_scout_web/templates/tokens/instance/transfer/index.html.eex:28 #: lib/block_scout_web/templates/tokens/transfer/index.html.eex:27 -#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:27 #, elixir-autogen, elixir-format msgid "There are no transfers for this Token." msgstr "" -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:99 #: lib/block_scout_web/templates/verified_contracts/index.html.eex:99 #, elixir-autogen, elixir-format msgid "There are no verified contracts." msgstr "" -#: lib/block_scout_web/templates/address_withdrawal/index.html.eex:54 #: lib/block_scout_web/templates/address_withdrawal/index.html.eex:54 #, elixir-autogen, elixir-format msgid "There are no withdrawals for this address." msgstr "" -#: lib/block_scout_web/templates/block_withdrawal/index.html.eex:45 #: lib/block_scout_web/templates/block_withdrawal/index.html.eex:45 #, elixir-autogen, elixir-format msgid "There are no withdrawals for this block." msgstr "" -#: lib/block_scout_web/templates/withdrawal/index.html.eex:53 #: lib/block_scout_web/templates/withdrawal/index.html.eex:53 #, elixir-autogen, elixir-format msgid "There are no withdrawals." msgstr "" -#: lib/block_scout_web/templates/address_coin_balance/index.html.eex:35 #: lib/block_scout_web/templates/address_coin_balance/index.html.eex:35 #, elixir-autogen, elixir-format msgid "There is no coin history for this address." msgstr "" #: lib/block_scout_web/templates/address_coin_balance/index.html.eex:21 -#: lib/block_scout_web/templates/address_coin_balance/index.html.eex:21 -#: lib/block_scout_web/templates/chain/show.html.eex:9 #: lib/block_scout_web/templates/chain/show.html.eex:9 #, elixir-autogen, elixir-format msgid "There was a problem loading the chart." msgstr "" -#: lib/block_scout_web/templates/api_docs/index.html.eex:6 #: lib/block_scout_web/templates/api_docs/index.html.eex:6 #, elixir-autogen, elixir-format msgid "This API is provided for developers transitioning their applications from Etherscan to BlockScout. It supports GET and POST requests." msgstr "" -#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:7 #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:7 #, elixir-autogen, elixir-format msgid "This API is provided to support some rpc methods in the exact format specified for ethereum nodes, which can be found " msgstr "" -#: lib/block_scout_web/views/block_transaction_view.ex:11 -#: lib/block_scout_web/views/block_transaction_view.ex:11 +#: lib/block_scout_web/views/block_transaction_view.ex:15 #, elixir-autogen, elixir-format msgid "This block has not been processed yet." msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:47 #: lib/block_scout_web/templates/address_contract/index.html.eex:47 #, elixir-autogen, elixir-format msgid "This contract has been partially verified via Sourcify." msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:51 #: lib/block_scout_web/templates/address_contract/index.html.eex:51 #, elixir-autogen, elixir-format msgid "This contract has been verified via Sourcify." msgstr "" -#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:10 #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:10 #, elixir-autogen, elixir-format msgid "This is useful to allow sending requests to blockscout without having to change anything about the request." msgstr "" -#: lib/block_scout_web/templates/page_not_found/index.html.eex:8 #: lib/block_scout_web/templates/page_not_found/index.html.eex:8 #, elixir-autogen, elixir-format msgid "This page is no longer explorable! If you are lost, use the search bar to find what you are looking for." msgstr "" -#: lib/block_scout_web/templates/transaction_state/index.html.eex:22 #: lib/block_scout_web/templates/transaction_state/index.html.eex:22 #, elixir-autogen, elixir-format msgid "This transaction hasn't changed state." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:64 #: lib/block_scout_web/templates/transaction/overview.html.eex:64 #, elixir-autogen, elixir-format msgid "This transaction is pending confirmation." msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:71 #: lib/block_scout_web/templates/block/overview.html.eex:71 #: lib/block_scout_web/templates/transaction/overview.html.eex:180 -#: lib/block_scout_web/templates/transaction/overview.html.eex:180 #, elixir-autogen, elixir-format msgid "Timestamp" msgstr "" -#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:32 #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:32 #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:34 -#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:34 -#: lib/block_scout_web/templates/address_transaction/index.html.eex:28 #: lib/block_scout_web/templates/address_transaction/index.html.eex:28 #: lib/block_scout_web/templates/block_withdrawal/index.html.eex:29 -#: lib/block_scout_web/templates/block_withdrawal/index.html.eex:29 -#: lib/block_scout_web/templates/transaction/overview.html.eex:267 -#: lib/block_scout_web/templates/transaction/overview.html.eex:267 -#: lib/block_scout_web/templates/withdrawal/index.html.eex:32 +#: lib/block_scout_web/templates/transaction/overview.html.eex:244 #: lib/block_scout_web/templates/withdrawal/index.html.eex:32 #: lib/block_scout_web/views/address_internal_transaction_view.ex:10 -#: lib/block_scout_web/views/address_internal_transaction_view.ex:10 -#: lib/block_scout_web/views/address_token_transfer_view.ex:10 #: lib/block_scout_web/views/address_token_transfer_view.ex:10 #: lib/block_scout_web/views/address_transaction_view.ex:10 -#: lib/block_scout_web/views/address_transaction_view.ex:10 #, elixir-autogen, elixir-format msgid "To" msgstr "" -#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:20 #: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:20 #, elixir-autogen, elixir-format msgid "To have guaranteed accuracy, use the link above to verify the contract's source code." msgstr "" #: lib/block_scout_web/templates/address_logs/_logs.html.eex:6 -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:6 -#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:8 #: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:8 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:6 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:6 #, elixir-autogen, elixir-format msgid "To see accurate decoded input data, the contract must be verified." msgstr "" -#: lib/block_scout_web/templates/layout/_topnav.html.eex:18 #: lib/block_scout_web/templates/layout/_topnav.html.eex:18 #, elixir-autogen, elixir-format msgid "Toggle navigation" msgstr "" #: lib/block_scout_web/templates/address/overview.html.eex:55 -#: lib/block_scout_web/templates/address/overview.html.eex:55 -#: lib/block_scout_web/templates/tokens/index.html.eex:31 #: lib/block_scout_web/templates/tokens/index.html.eex:31 #, elixir-autogen, elixir-format msgid "Token" msgstr "" #: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:3 -#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:3 -#: lib/block_scout_web/views/transaction_view.ex:488 -#: lib/block_scout_web/views/transaction_view.ex:488 +#: lib/block_scout_web/views/transaction_view.ex:486 #, elixir-autogen, elixir-format msgid "Token Burning" msgstr "" #: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:7 -#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:7 -#: lib/block_scout_web/views/transaction_view.ex:489 -#: lib/block_scout_web/views/transaction_view.ex:489 +#: lib/block_scout_web/views/transaction_view.ex:487 #, elixir-autogen, elixir-format msgid "Token Creation" msgstr "" -#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:10 #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:10 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:34 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:34 #, elixir-autogen, elixir-format msgid "Token Details" msgstr "" -#: lib/block_scout_web/templates/tokens/holder/index.html.eex:17 #: lib/block_scout_web/templates/tokens/holder/index.html.eex:17 #: lib/block_scout_web/templates/tokens/instance/holder/index.html.eex:16 -#: lib/block_scout_web/templates/tokens/instance/holder/index.html.eex:16 -#: lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:17 #: lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:17 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:11 -#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:11 -#: lib/block_scout_web/views/tokens/overview_view.ex:40 #: lib/block_scout_web/views/tokens/overview_view.ex:40 #, elixir-autogen, elixir-format msgid "Token Holders" msgstr "" #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:38 -#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:38 -#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:18 #: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:18 #: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:37 -#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:37 #, elixir-autogen, elixir-format msgid "Token ID" msgstr "" #: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:5 -#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:5 -#: lib/block_scout_web/views/transaction_view.ex:487 -#: lib/block_scout_web/views/transaction_view.ex:487 +#: lib/block_scout_web/views/transaction_view.ex:485 #, elixir-autogen, elixir-format msgid "Token Minting" msgstr "" #: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:9 -#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:9 -#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:11 #: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:11 -#: lib/block_scout_web/views/transaction_view.ex:490 -#: lib/block_scout_web/views/transaction_view.ex:490 +#: lib/block_scout_web/views/transaction_view.ex:488 #, elixir-autogen, elixir-format msgid "Token Transfer" msgstr "" -#: lib/block_scout_web/templates/address/_tabs.html.eex:13 #: lib/block_scout_web/templates/address/_tabs.html.eex:13 #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:19 -#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:19 -#: lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:3 #: lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:3 #: lib/block_scout_web/templates/tokens/instance/transfer/index.html.eex:16 -#: lib/block_scout_web/templates/tokens/instance/transfer/index.html.eex:16 -#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:5 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:5 #: lib/block_scout_web/templates/tokens/transfer/index.html.eex:15 -#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:15 -#: lib/block_scout_web/templates/transaction/_tabs.html.eex:4 #: lib/block_scout_web/templates/transaction/_tabs.html.eex:4 #: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:7 -#: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:7 -#: lib/block_scout_web/views/address_view.ex:355 #: lib/block_scout_web/views/address_view.ex:355 #: lib/block_scout_web/views/tokens/instance/overview_view.ex:74 -#: lib/block_scout_web/views/tokens/instance/overview_view.ex:74 -#: lib/block_scout_web/views/tokens/overview_view.ex:39 #: lib/block_scout_web/views/tokens/overview_view.ex:39 -#: lib/block_scout_web/views/transaction_view.ex:551 -#: lib/block_scout_web/views/transaction_view.ex:551 +#: lib/block_scout_web/views/transaction_view.ex:549 #, elixir-autogen, elixir-format msgid "Token Transfers" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:54 #: lib/block_scout_web/templates/address/overview.html.eex:54 #, elixir-autogen, elixir-format msgid "Token name and symbol." msgstr "" -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:142 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:142 #, elixir-autogen, elixir-format msgid "Token type" msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:21 -#: lib/block_scout_web/templates/address/_tabs.html.eex:21 -#: lib/block_scout_web/templates/address/overview.html.eex:177 #: lib/block_scout_web/templates/address/overview.html.eex:177 #: lib/block_scout_web/templates/address_token/overview.html.eex:58 -#: lib/block_scout_web/templates/address_token/overview.html.eex:58 -#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:13 #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:13 #: lib/block_scout_web/templates/layout/_topnav.html.eex:84 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:84 -#: lib/block_scout_web/templates/tokens/index.html.eex:10 #: lib/block_scout_web/templates/tokens/index.html.eex:10 #: lib/block_scout_web/views/address_view.ex:352 -#: lib/block_scout_web/views/address_view.ex:352 #, elixir-autogen, elixir-format msgid "Tokens" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:336 -#: lib/block_scout_web/templates/transaction/overview.html.eex:336 +#: lib/block_scout_web/templates/transaction/overview.html.eex:313 #, elixir-autogen, elixir-format msgid "Tokens Burnt" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:352 -#: lib/block_scout_web/templates/transaction/overview.html.eex:352 +#: lib/block_scout_web/templates/transaction/overview.html.eex:329 #, elixir-autogen, elixir-format msgid "Tokens Created" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:319 -#: lib/block_scout_web/templates/transaction/overview.html.eex:319 +#: lib/block_scout_web/templates/transaction/overview.html.eex:296 #, elixir-autogen, elixir-format msgid "Tokens Minted" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:303 -#: lib/block_scout_web/templates/transaction/overview.html.eex:303 +#: lib/block_scout_web/templates/transaction/overview.html.eex:280 #, elixir-autogen, elixir-format msgid "Tokens Transferred" msgstr "" -#: lib/block_scout_web/templates/address/_metatags.html.eex:13 #: lib/block_scout_web/templates/address/_metatags.html.eex:13 #, elixir-autogen, elixir-format msgid "Top Accounts - %{subnetwork} Explorer" msgstr "" -#: lib/block_scout_web/templates/address_logs/index.html.eex:14 #: lib/block_scout_web/templates/address_logs/index.html.eex:14 #, elixir-autogen, elixir-format msgid "Topic" msgstr "" #: lib/block_scout_web/templates/address_logs/_logs.html.eex:68 -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:68 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:100 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:100 #, elixir-autogen, elixir-format msgid "Topics" msgstr "" #: lib/block_scout_web/templates/verified_contracts/_stats.html.eex:9 -#: lib/block_scout_web/templates/verified_contracts/_stats.html.eex:9 -#: lib/block_scout_web/templates/verified_contracts/_stats.html.eex:22 #: lib/block_scout_web/templates/verified_contracts/_stats.html.eex:22 #, elixir-autogen, elixir-format msgid "Total" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:170 #: lib/block_scout_web/templates/block/overview.html.eex:170 #, elixir-autogen, elixir-format msgid "Total Difficulty" msgstr "" -#: lib/block_scout_web/templates/tokens/index.html.eex:43 #: lib/block_scout_web/templates/tokens/index.html.eex:43 #, elixir-autogen, elixir-format msgid "Total Supply" msgstr "" -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:84 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:84 #, elixir-autogen, elixir-format msgid "Total Supply * Price" msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:133 -#: lib/block_scout_web/templates/chain/show.html.eex:133 +#: lib/block_scout_web/templates/chain/show.html.eex:135 #, elixir-autogen, elixir-format msgid "Total blocks" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:169 #: lib/block_scout_web/templates/block/overview.html.eex:169 #, elixir-autogen, elixir-format msgid "Total difficulty of the chain until this block." msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:188 #: lib/block_scout_web/templates/block/overview.html.eex:188 #, elixir-autogen, elixir-format msgid "Total gas limit provided by all transactions in the block." msgstr "" -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:68 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:68 #, elixir-autogen, elixir-format msgid "Total supply" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:383 -#: lib/block_scout_web/templates/transaction/overview.html.eex:383 +#: lib/block_scout_web/templates/transaction/overview.html.eex:360 #, elixir-autogen, elixir-format msgid "Total transaction fee." msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:112 -#: lib/block_scout_web/templates/chain/show.html.eex:112 +#: lib/block_scout_web/templates/chain/show.html.eex:114 #, elixir-autogen, elixir-format msgid "Total transactions" msgstr "" #: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:11 -#: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:11 -#: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:23 #: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:23 #: lib/block_scout_web/templates/address_logs/_logs.html.eex:19 -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:19 -#: lib/block_scout_web/views/transaction_view.ex:500 -#: lib/block_scout_web/views/transaction_view.ex:500 +#: lib/block_scout_web/views/transaction_view.ex:498 #, elixir-autogen, elixir-format msgid "Transaction" msgstr "" -#: lib/block_scout_web/templates/transaction/_metatags.html.eex:3 #: lib/block_scout_web/templates/transaction/_metatags.html.eex:3 #, elixir-autogen, elixir-format msgid "Transaction %{transaction} - %{subnetwork} Explorer" msgstr "" -#: lib/block_scout_web/templates/transaction/_metatags.html.eex:11 #: lib/block_scout_web/templates/transaction/_metatags.html.eex:11 #, elixir-autogen, elixir-format msgid "Transaction %{transaction}, %{subnetwork} %{transaction}" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:225 -#: lib/block_scout_web/templates/transaction/overview.html.eex:225 -#, elixir-autogen, elixir-format -msgid "Transaction Action" -msgstr "" - -#: lib/block_scout_web/templates/transaction/overview.html.eex:470 -#: lib/block_scout_web/templates/transaction/overview.html.eex:470 +#: lib/block_scout_web/templates/transaction/overview.html.eex:447 #, elixir-autogen, elixir-format msgid "Transaction Burnt Fee" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:50 #: lib/block_scout_web/templates/transaction/overview.html.eex:50 #, elixir-autogen, elixir-format msgid "Transaction Details" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:384 -#: lib/block_scout_web/templates/transaction/overview.html.eex:384 +#: lib/block_scout_web/templates/transaction/overview.html.eex:361 #, elixir-autogen, elixir-format msgid "Transaction Fee" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:80 #: lib/block_scout_web/templates/transaction/overview.html.eex:80 #, elixir-autogen, elixir-format msgid "Transaction Hash" msgstr "" #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:2 -#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:2 -#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:19 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:19 #, elixir-autogen, elixir-format msgid "Transaction Inputs" msgstr "" #: lib/block_scout_web/templates/account/common/_nav.html.eex:13 -#: lib/block_scout_web/templates/account/common/_nav.html.eex:13 -#: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:7 #: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:7 #: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:17 -#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:17 #, elixir-autogen, elixir-format msgid "Transaction Tags" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:414 -#: lib/block_scout_web/templates/transaction/overview.html.eex:414 +#: lib/block_scout_web/templates/transaction/overview.html.eex:391 #, elixir-autogen, elixir-format msgid "Transaction Type" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:534 -#: lib/block_scout_web/templates/transaction/overview.html.eex:534 +#: lib/block_scout_web/templates/transaction/overview.html.eex:511 #, elixir-autogen, elixir-format msgid "Transaction number from the sending address. Each transaction sent from an address increments the nonce by 1." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:413 -#: lib/block_scout_web/templates/transaction/overview.html.eex:413 +#: lib/block_scout_web/templates/transaction/overview.html.eex:390 #, elixir-autogen, elixir-format msgid "Transaction type, introduced in EIP-2718." msgstr "" -#: lib/block_scout_web/templates/address/_tabs.html.eex:7 #: lib/block_scout_web/templates/address/_tabs.html.eex:7 #: lib/block_scout_web/templates/address/_tile.html.eex:31 -#: lib/block_scout_web/templates/address/_tile.html.eex:31 -#: lib/block_scout_web/templates/address/overview.html.eex:188 #: lib/block_scout_web/templates/address/overview.html.eex:188 #: lib/block_scout_web/templates/address/overview.html.eex:194 -#: lib/block_scout_web/templates/address/overview.html.eex:194 -#: lib/block_scout_web/templates/address/overview.html.eex:202 #: lib/block_scout_web/templates/address/overview.html.eex:202 #: lib/block_scout_web/templates/address_transaction/index.html.eex:13 -#: lib/block_scout_web/templates/address_transaction/index.html.eex:13 -#: lib/block_scout_web/templates/block/_tabs.html.eex:4 #: lib/block_scout_web/templates/block/_tabs.html.eex:4 #: lib/block_scout_web/templates/block/overview.html.eex:80 -#: lib/block_scout_web/templates/block/overview.html.eex:80 -#: lib/block_scout_web/templates/chain/show.html.eex:216 -#: lib/block_scout_web/templates/chain/show.html.eex:216 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:49 +#: lib/block_scout_web/templates/chain/show.html.eex:218 #: lib/block_scout_web/templates/layout/_topnav.html.eex:49 #: lib/block_scout_web/views/address_view.ex:354 -#: lib/block_scout_web/views/address_view.ex:354 #, elixir-autogen, elixir-format msgid "Transactions" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:101 #: lib/block_scout_web/templates/address/overview.html.eex:101 #, elixir-autogen, elixir-format msgid "Transactions and address of creation." msgstr "" #: lib/block_scout_web/templates/address/overview.html.eex:215 -#: lib/block_scout_web/templates/address/overview.html.eex:215 -#: lib/block_scout_web/templates/address/overview.html.eex:221 #: lib/block_scout_web/templates/address/overview.html.eex:221 #: lib/block_scout_web/templates/address/overview.html.eex:229 -#: lib/block_scout_web/templates/address/overview.html.eex:229 -#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:50 #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:50 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:119 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:119 #, elixir-autogen, elixir-format msgid "Transfers" msgstr "" -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:40 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:40 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:47 -#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:47 #, elixir-autogen, elixir-format msgid "Try it out" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_fetch_constructor_args.html.eex:3 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_fetch_constructor_args.html.eex:3 #, elixir-autogen, elixir-format msgid "Try to fetch constructor arguments automatically" msgstr "" -#: lib/block_scout_web/templates/layout/_footer.html.eex:27 #: lib/block_scout_web/templates/layout/_footer.html.eex:27 #, elixir-autogen, elixir-format msgid "Twitter" msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:53 #: lib/block_scout_web/templates/layout/app.html.eex:53 #, elixir-autogen, elixir-format msgid "Tx/day" msgstr "" -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:66 #: lib/block_scout_web/templates/verified_contracts/index.html.eex:66 #, elixir-autogen, elixir-format msgid "Txns" msgstr "" -#: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:5 #: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:5 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:22 -#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:22 #, elixir-autogen, elixir-format msgid "Type" msgstr "" -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:141 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:141 #, elixir-autogen, elixir-format msgid "Type of the token standard" msgstr "" -#: lib/block_scout_web/templates/visualize_sol2uml/index.html.eex:5 #: lib/block_scout_web/templates/visualize_sol2uml/index.html.eex:5 #, elixir-autogen, elixir-format msgid "UML diagram" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:560 -#: lib/block_scout_web/templates/transaction/overview.html.eex:560 +#: lib/block_scout_web/templates/transaction/overview.html.eex:537 #, elixir-autogen, elixir-format msgid "UTF-8" msgstr "" -#: lib/block_scout_web/views/block_view.ex:77 #: lib/block_scout_web/views/block_view.ex:77 #, elixir-autogen, elixir-format msgid "Uncle Reward" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:252 #: lib/block_scout_web/templates/block/overview.html.eex:252 #: lib/block_scout_web/templates/layout/_topnav.html.eex:41 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:41 #, elixir-autogen, elixir-format msgid "Uncles" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:367 -#: lib/block_scout_web/views/transaction_view.ex:367 +#: lib/block_scout_web/views/transaction_view.ex:365 #, elixir-autogen, elixir-format msgid "Unconfirmed" msgstr "" -#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:9 #: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:9 #, elixir-autogen, elixir-format msgid "Unique Token" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:79 #: lib/block_scout_web/templates/transaction/overview.html.eex:79 #, elixir-autogen, elixir-format msgid "Unique character string (TxID) assigned to every verified transaction." msgstr "" -#: lib/block_scout_web/templates/account/api_key/form.html.eex:7 #: lib/block_scout_web/templates/account/api_key/form.html.eex:7 #: lib/block_scout_web/templates/account/custom_abi/form.html.eex:8 -#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:8 #, elixir-autogen, elixir-format msgid "Update" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:449 -#: lib/block_scout_web/templates/transaction/overview.html.eex:449 +#: lib/block_scout_web/templates/transaction/overview.html.eex:426 #, elixir-autogen, elixir-format msgid "User defined maximum fee (tip) per unit of gas paid to validator for transaction prioritization." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:459 -#: lib/block_scout_web/templates/transaction/overview.html.eex:459 +#: lib/block_scout_web/templates/transaction/overview.html.eex:436 #, elixir-autogen, elixir-format msgid "User-defined tip sent to validator for transaction priority/inclusion." msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:226 #: lib/block_scout_web/templates/block/overview.html.eex:226 #, elixir-autogen, elixir-format msgid "User-defined tips sent to validator for transaction priority/inclusion." msgstr "" -#: lib/block_scout_web/templates/layout/_topnav.html.eex:56 #: lib/block_scout_web/templates/layout/_topnav.html.eex:56 #, elixir-autogen, elixir-format msgid "Validated" msgstr "" -#: lib/block_scout_web/templates/transaction/index.html.eex:12 #: lib/block_scout_web/templates/transaction/index.html.eex:12 #, elixir-autogen, elixir-format msgid "Validated Transactions" msgstr "" -#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:30 #: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:30 #, elixir-autogen, elixir-format msgid "Validator Creation Date" msgstr "" -#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:5 #: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:5 #, elixir-autogen, elixir-format msgid "Validator Data" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:86 #: lib/block_scout_web/templates/address/overview.html.eex:86 #, elixir-autogen, elixir-format msgid "Validator Name" msgstr "" #: lib/block_scout_web/templates/address_withdrawal/index.html.eex:32 -#: lib/block_scout_web/templates/address_withdrawal/index.html.eex:32 -#: lib/block_scout_web/templates/block_withdrawal/index.html.eex:26 #: lib/block_scout_web/templates/block_withdrawal/index.html.eex:26 #: lib/block_scout_web/templates/withdrawal/index.html.eex:26 -#: lib/block_scout_web/templates/withdrawal/index.html.eex:26 #, elixir-autogen, elixir-format msgid "Validator index" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:369 -#: lib/block_scout_web/templates/transaction/overview.html.eex:369 +#: lib/block_scout_web/templates/transaction/overview.html.eex:346 #, elixir-autogen, elixir-format msgid "Value" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:368 -#: lib/block_scout_web/templates/transaction/overview.html.eex:368 +#: lib/block_scout_web/templates/transaction/overview.html.eex:345 #, elixir-autogen, elixir-format msgid "Value sent in the native token (and USD) if applicable." msgstr "" -#: lib/block_scout_web/templates/address_read_contract/index.html.eex:17 #: lib/block_scout_web/templates/address_read_contract/index.html.eex:17 #: lib/block_scout_web/templates/address_write_contract/index.html.eex:15 -#: lib/block_scout_web/templates/address_write_contract/index.html.eex:15 -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:81 #: lib/block_scout_web/templates/verified_contracts/index.html.eex:81 #, elixir-autogen, elixir-format msgid "Verified" msgstr "" #: lib/block_scout_web/templates/verified_contracts/_stats.html.eex:18 -#: lib/block_scout_web/templates/verified_contracts/_stats.html.eex:18 -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:6 #: lib/block_scout_web/templates/verified_contracts/index.html.eex:6 #, elixir-autogen, elixir-format msgid "Verified Contracts" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:88 #: lib/block_scout_web/templates/address_contract/index.html.eex:88 #, elixir-autogen, elixir-format msgid "Verified at" msgstr "" -#: lib/block_scout_web/templates/layout/_topnav.html.eex:69 #: lib/block_scout_web/templates/layout/_topnav.html.eex:69 #, elixir-autogen, elixir-format msgid "Verified contracts" msgstr "" -#: lib/block_scout_web/templates/verified_contracts/_metatags.html.eex:2 #: lib/block_scout_web/templates/verified_contracts/_metatags.html.eex:2 #, elixir-autogen, elixir-format msgid "Verified contracts - %{subnetwork} Explorer" msgstr "" -#: lib/block_scout_web/templates/verified_contracts/_metatags.html.eex:7 #: lib/block_scout_web/templates/verified_contracts/_metatags.html.eex:7 #, elixir-autogen, elixir-format msgid "Verified contracts, %{subnetwork}, %{coin}" msgstr "" #: lib/block_scout_web/templates/address_contract/index.html.eex:27 -#: lib/block_scout_web/templates/address_contract/index.html.eex:27 -#: lib/block_scout_web/templates/address_contract/index.html.eex:29 #: lib/block_scout_web/templates/address_contract/index.html.eex:29 #: lib/block_scout_web/templates/address_contract/index.html.eex:197 -#: lib/block_scout_web/templates/address_contract/index.html.eex:197 -#: lib/block_scout_web/templates/address_contract/index.html.eex:228 #: lib/block_scout_web/templates/address_contract/index.html.eex:228 #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:14 -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:14 #, elixir-autogen, elixir-format msgid "Verify & Publish" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:111 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:111 #: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:37 -#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:37 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:103 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:103 -#: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:51 +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:101 #: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:51 #: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:47 -#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:47 #, elixir-autogen, elixir-format msgid "Verify & publish" msgstr "" -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:10 #: lib/block_scout_web/templates/address_logs/_logs.html.eex:10 #: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:12 -#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:12 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:10 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:10 #, elixir-autogen, elixir-format msgid "Verify the contract " msgstr "" #: lib/block_scout_web/templates/layout/_footer.html.eex:93 -#: lib/block_scout_web/templates/layout/_footer.html.eex:93 -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:72 #: lib/block_scout_web/templates/verified_contracts/index.html.eex:72 #, elixir-autogen, elixir-format msgid "Version" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:33 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:33 #, elixir-autogen, elixir-format msgid "Via Sourcify: Sources and metadata JSON file" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:27 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:27 #, elixir-autogen, elixir-format msgid "Via Standard Input JSON" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:22 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:22 #, elixir-autogen, elixir-format msgid "Via flattened source code" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:40 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:40 #, elixir-autogen, elixir-format msgid "Via multi-part files" msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:155 -#: lib/block_scout_web/templates/chain/show.html.eex:155 +#: lib/block_scout_web/templates/chain/show.html.eex:157 #, elixir-autogen, elixir-format msgid "View All Blocks" msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:215 -#: lib/block_scout_web/templates/chain/show.html.eex:215 +#: lib/block_scout_web/templates/chain/show.html.eex:217 #, elixir-autogen, elixir-format msgid "View All Transactions" msgstr "" #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:16 -#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:16 -#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:20 #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:20 #, elixir-autogen, elixir-format msgid "View Contract" msgstr "" -#: lib/block_scout_web/templates/transaction/_tile.html.eex:73 #: lib/block_scout_web/templates/transaction/_tile.html.eex:73 #, elixir-autogen, elixir-format msgid "View Less Transfers" msgstr "" -#: lib/block_scout_web/templates/transaction/_tile.html.eex:72 #: lib/block_scout_web/templates/transaction/_tile.html.eex:72 #, elixir-autogen, elixir-format msgid "View More Transfers" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:39 #: lib/block_scout_web/templates/block/overview.html.eex:39 #, elixir-autogen, elixir-format msgid "View next block" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:23 #: lib/block_scout_web/templates/block/overview.html.eex:23 #, elixir-autogen, elixir-format msgid "View previous block" msgstr "" -#: lib/block_scout_web/templates/address/_metatags.html.eex:9 #: lib/block_scout_web/templates/address/_metatags.html.eex:9 #, elixir-autogen, elixir-format msgid "View the account balance, transactions, and other data for %{address} on the %{network}" msgstr "" -#: lib/block_scout_web/templates/withdrawal/_metatags.html.eex:8 #: lib/block_scout_web/templates/withdrawal/_metatags.html.eex:8 #, elixir-autogen, elixir-format msgid "View the beacon chain withdrawals on %{subnetwork}" msgstr "" -#: lib/block_scout_web/templates/block/_metatags.html.eex:10 #: lib/block_scout_web/templates/block/_metatags.html.eex:10 #, elixir-autogen, elixir-format msgid "View the transactions, token transfers, and uncles for block number %{block_number}" msgstr "" -#: lib/block_scout_web/templates/verified_contracts/_metatags.html.eex:8 #: lib/block_scout_web/templates/verified_contracts/_metatags.html.eex:8 #, elixir-autogen, elixir-format msgid "View the verified contracts on %{subnetwork}" msgstr "" -#: lib/block_scout_web/templates/transaction/_metatags.html.eex:10 #: lib/block_scout_web/templates/transaction/_metatags.html.eex:10 #, elixir-autogen, elixir-format msgid "View transaction %{transaction} on %{subnetwork}" msgstr "" -#: lib/block_scout_web/templates/verified_contracts/_contract.html.eex:28 -#: lib/block_scout_web/templates/verified_contracts/_contract.html.eex:28 -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:32 +#: lib/block_scout_web/templates/verified_contracts/_contract.html.eex:29 #: lib/block_scout_web/templates/verified_contracts/index.html.eex:32 #: lib/block_scout_web/views/verified_contracts_view.ex:11 -#: lib/block_scout_web/views/verified_contracts_view.ex:11 #, elixir-autogen, elixir-format msgid "Vyper" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:46 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:46 #, elixir-autogen, elixir-format msgid "Vyper contract" msgstr "" -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:142 #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:142 #, elixir-autogen, elixir-format msgid "WEI" msgstr "" -#: lib/block_scout_web/templates/smart_contract/_pending_contract_write.html.eex:9 #: lib/block_scout_web/templates/smart_contract/_pending_contract_write.html.eex:9 #, elixir-autogen, elixir-format msgid "Waiting for transaction's confirmation..." msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:141 -#: lib/block_scout_web/templates/chain/show.html.eex:141 +#: lib/block_scout_web/templates/chain/show.html.eex:143 #, elixir-autogen, elixir-format msgid "Wallet addresses" msgstr "" -#: lib/block_scout_web/templates/common_components/_changed_bytecode_warning.html.eex:3 #: lib/block_scout_web/templates/common_components/_changed_bytecode_warning.html.eex:3 #, elixir-autogen, elixir-format msgid "Warning! Contract bytecode has been changed and doesn't match the verified one. Therefore, interaction with this smart contract may be risky." msgstr "" -#: lib/block_scout_web/templates/account/common/_nav.html.eex:7 #: lib/block_scout_web/templates/account/common/_nav.html.eex:7 #: lib/block_scout_web/templates/account/watchlist/show.html.eex:7 -#: lib/block_scout_web/templates/account/watchlist/show.html.eex:7 -#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:15 #: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:15 #, elixir-autogen, elixir-format msgid "Watch list" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:77 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:77 #, elixir-autogen, elixir-format msgid "We recommend using flattened code. This is necessary if your code utilizes a library or inherits dependencies. Use the" msgstr "" -#: lib/block_scout_web/views/wei_helper.ex:80 #: lib/block_scout_web/views/wei_helper.ex:80 #, elixir-autogen, elixir-format msgid "Wei" msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:29 -#: lib/block_scout_web/templates/address/_tabs.html.eex:29 -#: lib/block_scout_web/templates/address_withdrawal/index.html.eex:13 #: lib/block_scout_web/templates/address_withdrawal/index.html.eex:13 #: lib/block_scout_web/templates/block/_tabs.html.eex:13 -#: lib/block_scout_web/templates/block/_tabs.html.eex:13 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:73 #: lib/block_scout_web/templates/layout/_topnav.html.eex:73 #: lib/block_scout_web/templates/withdrawal/index.html.eex:5 -#: lib/block_scout_web/templates/withdrawal/index.html.eex:5 #, elixir-autogen, elixir-format msgid "Withdrawals" msgstr "" -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:106 #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:106 #, elixir-autogen, elixir-format msgid "Write" msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:95 -#: lib/block_scout_web/templates/address/_tabs.html.eex:95 -#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:34 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:34 #: lib/block_scout_web/views/address_view.ex:359 -#: lib/block_scout_web/views/address_view.ex:359 #, elixir-autogen, elixir-format msgid "Write Contract" msgstr "" -#: lib/block_scout_web/templates/address/_tabs.html.eex:102 #: lib/block_scout_web/templates/address/_tabs.html.eex:102 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:48 -#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:48 -#: lib/block_scout_web/views/address_view.ex:360 #: lib/block_scout_web/views/address_view.ex:360 #, elixir-autogen, elixir-format msgid "Write Proxy" msgstr "" #: lib/block_scout_web/templates/address_contract_verification_common_fields/_fetch_constructor_args.html.eex:14 -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_fetch_constructor_args.html.eex:14 -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_include_nightly_builds_field.html.eex:14 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_include_nightly_builds_field.html.eex:14 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_yul_contracts_switcher.html.eex:14 -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_yul_contracts_switcher.html.eex:14 -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:51 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:51 #: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:43 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:43 #, elixir-autogen, elixir-format msgid "Yes" msgstr "" -#: lib/block_scout_web/templates/account/api_key/index.html.eex:18 #: lib/block_scout_web/templates/account/api_key/index.html.eex:18 #, elixir-autogen, elixir-format msgid "You can create 3 API keys per account." msgstr "" -#: lib/block_scout_web/templates/account/custom_abi/index.html.eex:18 #: lib/block_scout_web/templates/account/custom_abi/index.html.eex:18 #, elixir-autogen, elixir-format msgid "You can create up to 15 Custom ABIs per account." msgstr "" -#: lib/block_scout_web/templates/account/tag_address/index.html.eex:14 #: lib/block_scout_web/templates/account/tag_address/index.html.eex:14 #, elixir-autogen, elixir-format msgid "You don't have address tags yet" msgstr "" -#: lib/block_scout_web/templates/account/watchlist/show.html.eex:14 #: lib/block_scout_web/templates/account/watchlist/show.html.eex:14 #, elixir-autogen, elixir-format msgid "You don't have addresses on you watchlist yet" msgstr "" -#: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:14 #: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:14 #, elixir-autogen, elixir-format msgid "You don't have transaction tags yet" msgstr "" -#: lib/block_scout_web/templates/error422/index.html.eex:8 #: lib/block_scout_web/templates/error422/index.html.eex:8 #, elixir-autogen, elixir-format msgid "Your request contained an error, perhaps a mistyped tx/block/address hash. Try again, and check the developer tools console for more info." msgstr "" -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:38 +#: lib/block_scout_web/templates/verified_contracts/_contract.html.eex:30 #: lib/block_scout_web/templates/verified_contracts/index.html.eex:38 #: lib/block_scout_web/views/verified_contracts_view.ex:12 -#: lib/block_scout_web/views/verified_contracts_view.ex:12 #, elixir-autogen, elixir-format msgid "Yul" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:111 #: lib/block_scout_web/templates/address/overview.html.eex:111 #, elixir-autogen, elixir-format msgid "at" msgstr "" -#: lib/block_scout_web/templates/address_token/overview.html.eex:52 #: lib/block_scout_web/templates/address_token/overview.html.eex:52 #, elixir-autogen, elixir-format msgid "balance of the address" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:469 -#: lib/block_scout_web/templates/transaction/overview.html.eex:469 +#: lib/block_scout_web/templates/transaction/overview.html.eex:446 #, elixir-autogen, elixir-format msgid "burnt for this transaction. Equals Block Base Fee per Gas * Gas Used." msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:217 #: lib/block_scout_web/templates/block/overview.html.eex:217 #, elixir-autogen, elixir-format msgid "burnt from transactions included in the block (Base fee (per unit of gas) * Gas Used)." msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:27 #: lib/block_scout_web/templates/address_contract/index.html.eex:27 #, elixir-autogen, elixir-format msgid "button" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:276 -#: lib/block_scout_web/templates/transaction/overview.html.eex:276 +#: lib/block_scout_web/templates/transaction/overview.html.eex:253 #, elixir-autogen, elixir-format msgid "created" msgstr "" -#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:12 #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:12 #, elixir-autogen, elixir-format msgid "custom RPC" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:151 #: lib/block_scout_web/templates/address/overview.html.eex:151 #, elixir-autogen, elixir-format msgid "doesn't include ERC20, ERC721, ERC1155 tokens)." msgstr "" -#: lib/block_scout_web/templates/common_components/_rap_pagination_container.html.eex:13 #: lib/block_scout_web/templates/common_components/_rap_pagination_container.html.eex:13 #, elixir-autogen, elixir-format msgid "elements are displayed" msgstr "" -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:44 #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:44 #, elixir-autogen, elixir-format msgid "fallback" msgstr "" -#: lib/block_scout_web/views/address_contract_view.ex:33 #: lib/block_scout_web/views/address_contract_view.ex:33 #, elixir-autogen, elixir-format msgid "false" msgstr "" -#: lib/block_scout_web/templates/csv_export/index.html.eex:14 #: lib/block_scout_web/templates/csv_export/index.html.eex:14 #, elixir-autogen, elixir-format msgid "for address" msgstr "" #: lib/block_scout_web/templates/address_logs/_logs.html.eex:10 -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:10 -#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:12 #: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:12 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:10 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:10 #, elixir-autogen, elixir-format msgid "here" msgstr "" -#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:9 #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:9 #, elixir-autogen, elixir-format msgid "here." msgstr "" -#: lib/block_scout_web/templates/smart_contract/_function_response.html.eex:3 #: lib/block_scout_web/templates/smart_contract/_function_response.html.eex:3 #, elixir-autogen, elixir-format msgid "method Response" msgstr "" -#: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:41 #: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:41 #, elixir-autogen, elixir-format msgid "of" msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:100 #: lib/block_scout_web/templates/layout/app.html.eex:100 #, elixir-autogen, elixir-format msgid "on sign up. Didn’t receive?" msgstr "" -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:16 #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:16 #, elixir-autogen, elixir-format msgid "page" msgstr "" -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:46 #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:46 #, elixir-autogen, elixir-format msgid "receive" msgstr "" -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:58 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:58 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:69 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:69 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:81 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:81 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:70 -#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:70 #, elixir-autogen, elixir-format msgid "required" msgstr "" -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:59 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:59 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:70 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:70 #, elixir-autogen, elixir-format msgid "string" msgstr "" -#: lib/block_scout_web/templates/csv_export/index.html.eex:17 #: lib/block_scout_web/templates/csv_export/index.html.eex:17 #, elixir-autogen, elixir-format msgid "to CSV file" msgstr "" -#: lib/block_scout_web/views/address_contract_view.ex:32 #: lib/block_scout_web/views/address_contract_view.ex:32 #, elixir-autogen, elixir-format msgid "true" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:77 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:77 #, elixir-autogen, elixir-format msgid "truffle flattener" msgstr "" + +#: lib/block_scout_web/views/transaction_view.ex:224 +#, elixir-autogen, elixir-format +msgid "ERC-7984 " +msgstr "" + +#: lib/block_scout_web/views/transaction_view.ex:223 +#, elixir-autogen, elixir-format +msgid "ZRC-2 " +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 c09d63d939b2..2221ff8ea4f5 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 @@ -11,57 +11,47 @@ msgstr "" "Language: en\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: lib/block_scout_web/templates/common_components/_minimal_proxy_pattern.html.eex:9 #: lib/block_scout_web/templates/common_components/_minimal_proxy_pattern.html.eex:9 #, elixir-autogen, elixir-format msgid " - minimal bytecode implementation that delegates all calls to a known address" msgstr "" -#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:14 #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:14 #, elixir-autogen, elixir-format msgid " is recommended." msgstr "" -#: lib/block_scout_web/templates/address/_metatags.html.eex:3 #: lib/block_scout_web/templates/address/_metatags.html.eex:3 #, elixir-autogen, elixir-format msgid "%{address} - %{subnetwork} Explorer" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:12 #: lib/block_scout_web/templates/block/overview.html.eex:12 #, elixir-autogen, elixir-format msgid "%{block_type} Details" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:55 #: lib/block_scout_web/templates/block/overview.html.eex:55 #, elixir-autogen, elixir-format msgid "%{block_type} Height" msgstr "" -#: lib/block_scout_web/templates/block/index.html.eex:7 #: lib/block_scout_web/templates/block/index.html.eex:7 #, elixir-autogen, elixir-format msgid "%{block_type}s" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:85 #: lib/block_scout_web/templates/block/overview.html.eex:85 #, elixir-autogen, elixir-format msgid "%{count} Transaction" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:87 #: lib/block_scout_web/templates/block/overview.html.eex:87 #: lib/block_scout_web/templates/chain/_block.html.eex:11 -#: lib/block_scout_web/templates/chain/_block.html.eex:11 #, elixir-autogen, elixir-format msgid "%{count} Transactions" msgstr "" -#: lib/block_scout_web/views/address_token_balance_view.ex:10 #: lib/block_scout_web/views/address_token_balance_view.ex:10 #, elixir-autogen, elixir-format msgid "%{count} token" @@ -69,7 +59,6 @@ msgid_plural "%{count} tokens" msgstr[0] "" msgstr[1] "" -#: lib/block_scout_web/templates/block/_tile.html.eex:29 #: lib/block_scout_web/templates/block/_tile.html.eex:29 #, elixir-autogen, elixir-format msgid "%{count} transaction" @@ -77,1050 +66,809 @@ msgid_plural "%{count} transactions" msgstr[0] "" msgstr[1] "" -#: lib/block_scout_web/templates/transaction/_actions.html.eex:101 #: lib/block_scout_web/templates/transaction/_actions.html.eex:101 #, elixir-autogen, elixir-format msgid "%{qty} of Token ID [%{link_to_id}]" msgstr "" -#: lib/block_scout_web/templates/chain/_metatags.html.eex:2 #: lib/block_scout_web/templates/chain/_metatags.html.eex:2 #, elixir-autogen, elixir-format msgid "%{subnetwork} %{network} Explorer" msgstr "" -#: lib/block_scout_web/templates/layout/_default_title.html.eex:2 #: lib/block_scout_web/templates/layout/_default_title.html.eex:2 #, elixir-autogen, elixir-format msgid "%{subnetwork} Explorer - BlockScout" msgstr "" -#: lib/block_scout_web/templates/withdrawal/index.html.eex:11 #: lib/block_scout_web/templates/withdrawal/index.html.eex:11 #, elixir-autogen, elixir-format msgid "%{withdrawals_count} withdrawals processed and %{withdrawals_sum} withdrawn." msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:375 -#: lib/block_scout_web/views/transaction_view.ex:375 +#: lib/block_scout_web/views/transaction_view.ex:373 #, elixir-autogen, elixir-format msgid "(Awaiting internal transactions for status)" msgstr "" -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:59 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:59 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:70 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:70 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:82 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:82 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:104 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:104 #, elixir-autogen, elixir-format msgid "(query)" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_first.html.eex:4 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_first.html.eex:4 #, elixir-autogen, elixir-format msgid ") may be added for each contract. Click the Add Library button to add an additional one." msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:93 #: lib/block_scout_web/templates/layout/app.html.eex:93 #, elixir-autogen, elixir-format msgid "- We're indexing this chain right now. Some of the counts may be inaccurate." msgstr "" -#: lib/block_scout_web/templates/transaction/not_found.html.eex:8 #: lib/block_scout_web/templates/transaction/not_found.html.eex:8 #, elixir-autogen, elixir-format msgid "1. If you have just submitted this transaction please wait for at least 30 seconds before refreshing this page." msgstr "" -#: lib/block_scout_web/templates/transaction/not_found.html.eex:9 #: lib/block_scout_web/templates/transaction/not_found.html.eex:9 #, elixir-autogen, elixir-format msgid "2. It could still be in the TX Pool of a different node, waiting to be broadcasted." msgstr "" -#: lib/block_scout_web/templates/transaction/not_found.html.eex:10 #: lib/block_scout_web/templates/transaction/not_found.html.eex:10 #, elixir-autogen, elixir-format msgid "3. During times when the network is busy (i.e during ICOs) it can take a while for your transaction to propagate through the network and for us to index it." msgstr "" -#: lib/block_scout_web/templates/transaction/not_found.html.eex:11 #: lib/block_scout_web/templates/transaction/not_found.html.eex:11 #, elixir-autogen, elixir-format msgid "4. If it still does not show up after 1 hour, please check with your sender/exchange/wallet/transaction provider for additional information." msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:197 #: lib/block_scout_web/templates/block/overview.html.eex:197 #, elixir-autogen, elixir-format msgid "64-bit hash of value verifying proof-of-work (note: null for POA chains)." msgstr "" #: lib/block_scout_web/templates/block/overview.html.eex:97 -#: lib/block_scout_web/templates/block/overview.html.eex:97 -#: lib/block_scout_web/templates/transaction_state/_state_change.html.eex:21 #: lib/block_scout_web/templates/transaction_state/_state_change.html.eex:21 #, elixir-autogen, elixir-format msgid "A block producer who successfully included the block onto the blockchain." msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:100 #: lib/block_scout_web/templates/layout/app.html.eex:100 #, elixir-autogen, elixir-format msgid "A confirmation email was sent to" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_first.html.eex:4 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_first.html.eex:4 #, elixir-autogen, elixir-format msgid "A library name called in the .sol file. Multiple libraries (up to " msgstr "" -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:73 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:73 #, elixir-autogen, elixir-format msgid "A string with the name of the action to be invoked." msgstr "" -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:62 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:62 #, elixir-autogen, elixir-format msgid "A string with the name of the module to be invoked." msgstr "" -#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:24 #: lib/block_scout_web/templates/account/custom_abi/form.html.eex:24 #, elixir-autogen, elixir-format msgid "ABI" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_constructor_args.html.eex:3 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_constructor_args.html.eex:3 #, elixir-autogen, elixir-format msgid "ABI-encoded Constructor Arguments (if required by the contract)" msgstr "" -#: lib/block_scout_web/templates/api_docs/index.html.eex:4 #: lib/block_scout_web/templates/api_docs/index.html.eex:4 #, elixir-autogen, elixir-format msgid "API Documentation" msgstr "" -#: lib/block_scout_web/templates/api_docs/_metatags.html.eex:4 #: lib/block_scout_web/templates/api_docs/_metatags.html.eex:4 #, elixir-autogen, elixir-format msgid "API endpoints for the %{subnetwork}" msgstr "" -#: lib/block_scout_web/templates/api_docs/_metatags.html.eex:2 #: lib/block_scout_web/templates/api_docs/_metatags.html.eex:2 #, elixir-autogen, elixir-format msgid "API for the %{subnetwork} - BlockScout" msgstr "" -#: lib/block_scout_web/templates/account/api_key/form.html.eex:7 #: lib/block_scout_web/templates/account/api_key/form.html.eex:7 #: lib/block_scout_web/templates/account/api_key/form.html.eex:13 -#: lib/block_scout_web/templates/account/api_key/form.html.eex:13 -#: lib/block_scout_web/templates/account/api_key/form.html.eex:14 #: lib/block_scout_web/templates/account/api_key/form.html.eex:14 #: lib/block_scout_web/templates/account/api_key/index.html.eex:29 -#: lib/block_scout_web/templates/account/api_key/index.html.eex:29 #, elixir-autogen, elixir-format msgid "API key" msgstr "" -#: lib/block_scout_web/templates/account/api_key/index.html.eex:7 #: lib/block_scout_web/templates/account/api_key/index.html.eex:7 #: lib/block_scout_web/templates/account/common/_nav.html.eex:16 -#: lib/block_scout_web/templates/account/common/_nav.html.eex:16 -#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:18 #: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:18 #, elixir-autogen, elixir-format msgid "API keys" msgstr "" -#: lib/block_scout_web/templates/layout/_topnav.html.eex:106 #: lib/block_scout_web/templates/layout/_topnav.html.eex:106 #, elixir-autogen, elixir-format msgid "APIs" msgstr "" -#: lib/block_scout_web/templates/account/tag_address/index.html.eex:24 #: lib/block_scout_web/templates/account/tag_address/index.html.eex:24 #: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:24 -#: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:24 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:69 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:69 #, elixir-autogen, elixir-format msgid "Action" msgstr "" -#: lib/block_scout_web/templates/account/watchlist/show.html.eex:25 #: lib/block_scout_web/templates/account/watchlist/show.html.eex:25 #, elixir-autogen, elixir-format msgid "Actions" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:484 -#: lib/block_scout_web/templates/transaction/overview.html.eex:484 +#: lib/block_scout_web/templates/transaction/overview.html.eex:461 #, elixir-autogen, elixir-format msgid "Actual gas amount used by the transaction on L2." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:488 -#: lib/block_scout_web/templates/transaction/overview.html.eex:488 +#: lib/block_scout_web/templates/transaction/overview.html.eex:465 #, elixir-autogen, elixir-format, fuzzy msgid "Actual gas amount used by the transaction." msgstr "" -#: lib/block_scout_web/templates/account/api_key/form.html.eex:7 #: lib/block_scout_web/templates/account/api_key/form.html.eex:7 #: lib/block_scout_web/templates/account/custom_abi/form.html.eex:8 -#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:8 -#: lib/block_scout_web/templates/layout/_add_chain_to_mm.html.eex:10 #: lib/block_scout_web/templates/layout/_add_chain_to_mm.html.eex:10 #, elixir-autogen, elixir-format msgid "Add" msgstr "" -#: lib/block_scout_web/templates/account/api_key/index.html.eex:44 #: lib/block_scout_web/templates/account/api_key/index.html.eex:44 #, elixir-autogen, elixir-format msgid "Add API key" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:86 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:86 #: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:76 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:76 #, elixir-autogen, elixir-format msgid "Add Contract Libraries" msgstr "" -#: lib/block_scout_web/templates/account/custom_abi/index.html.eex:44 #: lib/block_scout_web/templates/account/custom_abi/index.html.eex:44 #, elixir-autogen, elixir-format msgid "Add Custom ABI" msgstr "" #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:97 -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:97 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:87 #: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:87 #, elixir-autogen, elixir-format msgid "Add Library" msgstr "" -#: lib/block_scout_web/templates/account/watchlist/show.html.eex:38 #: lib/block_scout_web/templates/account/watchlist/show.html.eex:38 #, elixir-autogen, elixir-format msgid "Add address" msgstr "" -#: lib/block_scout_web/templates/account/tag_address/form.html.eex:7 #: lib/block_scout_web/templates/account/tag_address/form.html.eex:7 #: lib/block_scout_web/templates/account/tag_address/index.html.eex:37 -#: lib/block_scout_web/templates/account/tag_address/index.html.eex:37 #, elixir-autogen, elixir-format msgid "Add address tag" msgstr "" -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:7 #: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:7 #, elixir-autogen, elixir-format msgid "Add address to the Watch list" msgstr "" #: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:7 -#: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:7 -#: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:37 #: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:37 #, elixir-autogen, elixir-format msgid "Add transaction tag" msgstr "" #: lib/block_scout_web/templates/account/tag_address/form.html.eex:11 -#: lib/block_scout_web/templates/account/tag_address/form.html.eex:11 -#: lib/block_scout_web/templates/account/tag_address/index.html.eex:23 #: lib/block_scout_web/templates/account/tag_address/index.html.eex:23 #: lib/block_scout_web/templates/account/watchlist/show.html.eex:23 -#: lib/block_scout_web/templates/account/watchlist/show.html.eex:23 -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:12 #: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:12 #: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:16 -#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:16 -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_address.html.eex:4 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_address.html.eex:4 #: lib/block_scout_web/templates/tokens/index.html.eex:34 -#: lib/block_scout_web/templates/tokens/index.html.eex:34 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:29 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:29 #: lib/block_scout_web/templates/transaction_state/index.html.eex:34 -#: lib/block_scout_web/templates/transaction_state/index.html.eex:34 -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:60 #: lib/block_scout_web/templates/verified_contracts/index.html.eex:60 #: lib/block_scout_web/views/address_view.ex:110 -#: lib/block_scout_web/views/address_view.ex:110 #, elixir-autogen, elixir-format msgid "Address" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:263 -#: lib/block_scout_web/templates/transaction/overview.html.eex:263 +#: lib/block_scout_web/templates/transaction/overview.html.eex:240 #, elixir-autogen, elixir-format msgid "Address (external or contract) receiving the transaction." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:245 -#: lib/block_scout_web/templates/transaction/overview.html.eex:245 +#: lib/block_scout_web/templates/transaction/overview.html.eex:222 #, elixir-autogen, elixir-format msgid "Address (external or contract) sending the transaction." msgstr "" -#: lib/block_scout_web/templates/account/common/_nav.html.eex:10 #: lib/block_scout_web/templates/account/common/_nav.html.eex:10 #: lib/block_scout_web/templates/account/tag_address/index.html.eex:7 -#: lib/block_scout_web/templates/account/tag_address/index.html.eex:7 -#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:16 #: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:16 #, elixir-autogen, elixir-format msgid "Address Tags" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:151 #: lib/block_scout_web/templates/address/overview.html.eex:151 #, elixir-autogen, elixir-format msgid "Address balance in" msgstr "" -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:51 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:51 #, elixir-autogen, elixir-format msgid "Address of the token contract" msgstr "" -#: lib/block_scout_web/templates/transaction_state/_state_change.html.eex:7 #: lib/block_scout_web/templates/transaction_state/_state_change.html.eex:7 #, elixir-autogen, elixir-format msgid "Address used in token mintings and burnings." msgstr "" -#: lib/block_scout_web/templates/address/index.html.eex:5 #: lib/block_scout_web/templates/address/index.html.eex:5 #, elixir-autogen, elixir-format msgid "Addresses" msgstr "" #: lib/block_scout_web/templates/address_withdrawal/index.html.eex:38 -#: lib/block_scout_web/templates/address_withdrawal/index.html.eex:38 -#: lib/block_scout_web/templates/withdrawal/index.html.eex:35 #: lib/block_scout_web/templates/withdrawal/index.html.eex:35 #, elixir-autogen, elixir-format msgid "Age" msgstr "" #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:26 -#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:26 -#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:28 #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:28 #: lib/block_scout_web/templates/address_transaction/index.html.eex:22 -#: lib/block_scout_web/templates/address_transaction/index.html.eex:22 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:88 #: lib/block_scout_web/templates/layout/_topnav.html.eex:88 #: lib/block_scout_web/templates/verified_contracts/index.html.eex:20 -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:20 -#: lib/block_scout_web/views/address_internal_transaction_view.ex:12 #: lib/block_scout_web/views/address_internal_transaction_view.ex:12 #: lib/block_scout_web/views/address_token_transfer_view.ex:12 -#: lib/block_scout_web/views/address_token_transfer_view.ex:12 -#: lib/block_scout_web/views/address_transaction_view.ex:12 #: lib/block_scout_web/views/address_transaction_view.ex:12 #: lib/block_scout_web/views/verified_contracts_view.ex:13 -#: lib/block_scout_web/views/verified_contracts_view.ex:13 #, elixir-autogen, elixir-format msgid "All" msgstr "" -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:13 #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:13 #, elixir-autogen, elixir-format msgid "All functions displayed below are from ABI of that contract. In order to verify current contract, proceed with" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:27 #: lib/block_scout_web/templates/address_contract/index.html.eex:27 #, elixir-autogen, elixir-format msgid "All metadata displayed below is from that contract. In order to verify current contract, click" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:176 #: lib/block_scout_web/templates/address/overview.html.eex:176 #, elixir-autogen, elixir-format msgid "All tokens in the account and total value." msgstr "" #: lib/block_scout_web/templates/address_withdrawal/index.html.eex:41 -#: lib/block_scout_web/templates/address_withdrawal/index.html.eex:41 -#: lib/block_scout_web/templates/block_withdrawal/index.html.eex:32 #: lib/block_scout_web/templates/block_withdrawal/index.html.eex:32 #: lib/block_scout_web/templates/withdrawal/index.html.eex:38 -#: lib/block_scout_web/templates/withdrawal/index.html.eex:38 #, elixir-autogen, elixir-format, fuzzy msgid "Amount" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:469 -#: lib/block_scout_web/templates/transaction/overview.html.eex:469 +#: lib/block_scout_web/templates/transaction/overview.html.eex:446 #, elixir-autogen, elixir-format msgid "Amount of" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:238 #: lib/block_scout_web/templates/block/overview.html.eex:238 #, elixir-autogen, elixir-format msgid "Amount of distributed reward. Miners receive a static block reward + Tx fees + uncle fees." msgstr "" -#: lib/block_scout_web/templates/internal_server_error/index.html.eex:8 #: lib/block_scout_web/templates/internal_server_error/index.html.eex:8 #, elixir-autogen, elixir-format msgid "An unexpected error has occurred. Try reloading the page, or come back soon and try again." msgstr "" -#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:15 #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:15 #, elixir-autogen, elixir-format msgid "Anything not in this list is not supported. Click on the method to be taken to the documentation for that method, and check the notes section for any potential differences." msgstr "" -#: lib/block_scout_web/templates/layout/_topnav.html.eex:134 #: lib/block_scout_web/templates/layout/_topnav.html.eex:134 #, elixir-autogen, elixir-format msgid "Apps" msgstr "" -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:21 #: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:21 #, elixir-autogen, elixir-format msgid "Average" msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:102 -#: lib/block_scout_web/templates/chain/show.html.eex:102 +#: lib/block_scout_web/templates/chain/show.html.eex:104 #, elixir-autogen, elixir-format msgid "Average block time" msgstr "" -#: lib/block_scout_web/templates/account/api_key/form.html.eex:25 #: lib/block_scout_web/templates/account/api_key/form.html.eex:25 #, elixir-autogen, elixir-format msgid "Back to API keys (Cancel)" msgstr "" -#: lib/block_scout_web/templates/account/tag_address/form.html.eex:24 #: lib/block_scout_web/templates/account/tag_address/form.html.eex:24 #, elixir-autogen, elixir-format msgid "Back to Address Tags (Cancel)" msgstr "" -#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:30 #: lib/block_scout_web/templates/account/custom_abi/form.html.eex:30 #, elixir-autogen, elixir-format msgid "Back to Custom ABI (Cancel)" msgstr "" -#: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:24 #: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:24 #, elixir-autogen, elixir-format msgid "Back to Transaction Tags (Cancel)" msgstr "" -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:81 #: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:81 #, elixir-autogen, elixir-format msgid "Back to Watch list (Cancel)" msgstr "" -#: lib/block_scout_web/templates/error422/index.html.eex:9 #: lib/block_scout_web/templates/error422/index.html.eex:9 #: lib/block_scout_web/templates/internal_server_error/index.html.eex:9 -#: lib/block_scout_web/templates/internal_server_error/index.html.eex:9 -#: lib/block_scout_web/templates/page_not_found/index.html.eex:9 #: lib/block_scout_web/templates/page_not_found/index.html.eex:9 #: lib/block_scout_web/templates/transaction/not_found.html.eex:13 -#: lib/block_scout_web/templates/transaction/not_found.html.eex:13 #, elixir-autogen, elixir-format msgid "Back to home" msgstr "" -#: lib/block_scout_web/templates/account/watchlist/show.html.eex:24 #: lib/block_scout_web/templates/account/watchlist/show.html.eex:24 #: lib/block_scout_web/templates/address/overview.html.eex:152 -#: lib/block_scout_web/templates/address/overview.html.eex:152 -#: lib/block_scout_web/templates/address_token/overview.html.eex:51 #: lib/block_scout_web/templates/address_token/overview.html.eex:51 #: lib/block_scout_web/templates/verified_contracts/index.html.eex:63 -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:63 #, elixir-autogen, elixir-format msgid "Balance" msgstr "" -#: lib/block_scout_web/templates/transaction_state/index.html.eex:40 #: lib/block_scout_web/templates/transaction_state/index.html.eex:40 #, elixir-autogen, elixir-format msgid "Balance after" msgstr "" -#: lib/block_scout_web/templates/transaction_state/index.html.eex:37 #: lib/block_scout_web/templates/transaction_state/index.html.eex:37 #, elixir-autogen, elixir-format msgid "Balance before" msgstr "" -#: lib/block_scout_web/templates/address_coin_balance/index.html.eex:14 #: lib/block_scout_web/templates/address_coin_balance/index.html.eex:14 #, elixir-autogen, elixir-format msgid "Balances" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:209 #: lib/block_scout_web/templates/block/overview.html.eex:209 #, elixir-autogen, elixir-format msgid "Base Fee per Gas" msgstr "" -#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:5 #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:5 #: lib/block_scout_web/templates/api_docs/index.html.eex:5 -#: lib/block_scout_web/templates/api_docs/index.html.eex:5 #, elixir-autogen, elixir-format msgid "Base URL:" msgstr "" -#: lib/block_scout_web/templates/withdrawal/_metatags.html.eex:2 #: lib/block_scout_web/templates/withdrawal/_metatags.html.eex:2 #, elixir-autogen, elixir-format msgid "Beacon chain withdrawals - %{subnetwork} Explorer" msgstr "" -#: lib/block_scout_web/templates/withdrawal/_metatags.html.eex:7 #: lib/block_scout_web/templates/withdrawal/_metatags.html.eex:7 #, elixir-autogen, elixir-format msgid "Beacon chain, Withdrawals, %{subnetwork}, %{coin}" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:545 -#: lib/block_scout_web/templates/transaction/overview.html.eex:545 +#: lib/block_scout_web/templates/transaction/overview.html.eex:522 #, elixir-autogen, elixir-format msgid "Binary data included with the transaction. See input / logs below for additional info." msgstr "" -#: lib/block_scout_web/templates/address_coin_balance/_coin_balances.html.eex:8 #: lib/block_scout_web/templates/address_coin_balance/_coin_balances.html.eex:8 #: lib/block_scout_web/templates/address_withdrawal/index.html.eex:35 -#: lib/block_scout_web/templates/address_withdrawal/index.html.eex:35 -#: lib/block_scout_web/templates/block/overview.html.eex:29 #: lib/block_scout_web/templates/block/overview.html.eex:29 #: lib/block_scout_web/templates/transaction/overview.html.eex:161 -#: lib/block_scout_web/templates/transaction/overview.html.eex:161 -#: lib/block_scout_web/templates/withdrawal/index.html.eex:29 #: lib/block_scout_web/templates/withdrawal/index.html.eex:29 #, elixir-autogen, elixir-format msgid "Block" msgstr "" #: lib/block_scout_web/templates/block/_link.html.eex:2 -#: lib/block_scout_web/templates/block/_link.html.eex:2 -#: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:32 #: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:32 #: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:43 -#: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:43 #, elixir-autogen, elixir-format msgid "Block #%{number}" msgstr "" -#: lib/block_scout_web/templates/block/_metatags.html.eex:3 #: lib/block_scout_web/templates/block/_metatags.html.eex:3 #, elixir-autogen, elixir-format msgid "Block %{block_number} - %{subnetwork} Explorer" msgstr "" -#: lib/block_scout_web/templates/block_transaction/404.html.eex:7 #: lib/block_scout_web/templates/block_transaction/404.html.eex:7 #, elixir-autogen, elixir-format msgid "Block Details" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:53 #: lib/block_scout_web/templates/block/overview.html.eex:53 #, elixir-autogen, elixir-format msgid "Block Height" msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:47 #: lib/block_scout_web/templates/layout/app.html.eex:47 #, elixir-autogen, elixir-format msgid "Block Mined, awaiting import..." msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:34 #: lib/block_scout_web/views/transaction_view.ex:34 #, elixir-autogen, elixir-format msgid "Block Pending" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:158 #: lib/block_scout_web/templates/block/overview.html.eex:158 #, elixir-autogen, elixir-format msgid "Block difficulty for miner, used to calibrate block generation time (Note: constant in POA based networks)." msgstr "" -#: lib/block_scout_web/views/block_transaction_view.ex:15 -#: lib/block_scout_web/views/block_transaction_view.ex:15 +#: lib/block_scout_web/views/block_transaction_view.ex:19 #, elixir-autogen, elixir-format msgid "Block not found, please try again later." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:203 #: lib/block_scout_web/templates/transaction/overview.html.eex:203 #, elixir-autogen, elixir-format, fuzzy msgid "Block number containing the transaction on L1." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:160 #: lib/block_scout_web/templates/transaction/overview.html.eex:160 #, elixir-autogen, elixir-format msgid "Block number containing the transaction." msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:259 #: lib/block_scout_web/templates/address/overview.html.eex:259 #, elixir-autogen, elixir-format msgid "Block number in which the address was updated." msgstr "" -#: lib/block_scout_web/templates/chain/_metatags.html.eex:4 #: lib/block_scout_web/templates/chain/_metatags.html.eex:4 #, elixir-autogen, elixir-format msgid "BlockScout provides analytics data, API, and Smart Contract tools for the %{subnetwork}" msgstr "" -#: lib/block_scout_web/templates/layout/_topnav.html.eex:29 #: lib/block_scout_web/templates/layout/_topnav.html.eex:29 #, elixir-autogen, elixir-format msgid "Blockchain" msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:156 -#: lib/block_scout_web/templates/chain/show.html.eex:156 +#: lib/block_scout_web/templates/chain/show.html.eex:158 #: lib/block_scout_web/templates/layout/_topnav.html.eex:34 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:34 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:38 #: lib/block_scout_web/templates/layout/_topnav.html.eex:38 #, elixir-autogen, elixir-format msgid "Blocks" msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:46 #: lib/block_scout_web/templates/layout/app.html.eex:46 #, elixir-autogen, elixir-format msgid "Blocks Indexed" msgstr "" -#: lib/block_scout_web/templates/address/_tabs.html.eex:56 #: lib/block_scout_web/templates/address/_tabs.html.eex:56 #: lib/block_scout_web/templates/address/overview.html.eex:277 -#: lib/block_scout_web/templates/address/overview.html.eex:277 -#: lib/block_scout_web/templates/address_validation/index.html.eex:11 #: lib/block_scout_web/templates/address_validation/index.html.eex:11 #: lib/block_scout_web/views/address_view.ex:362 -#: lib/block_scout_web/views/address_view.ex:362 #, elixir-autogen, elixir-format msgid "Blocks Validated" msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:48 #: lib/block_scout_web/templates/layout/app.html.eex:48 #, elixir-autogen, elixir-format msgid "Blocks With Internal Transactions Indexed" msgstr "" -#: lib/block_scout_web/templates/layout/_footer.html.eex:22 #: lib/block_scout_web/templates/layout/_footer.html.eex:22 #, elixir-autogen, elixir-format msgid "Blockscout is a tool for inspecting and analyzing EVM based blockchains. Blockchain explorer for Ethereum Networks." msgstr "" -#: lib/block_scout_web/templates/transaction_state/_state_change.html.eex:8 #: lib/block_scout_web/templates/transaction_state/_state_change.html.eex:8 #, elixir-autogen, elixir-format msgid "Burn address" msgstr "" #: lib/block_scout_web/templates/block/_tile.html.eex:64 -#: lib/block_scout_web/templates/block/_tile.html.eex:64 -#: lib/block_scout_web/templates/block/overview.html.eex:218 #: lib/block_scout_web/templates/block/overview.html.eex:218 #, elixir-autogen, elixir-format msgid "Burnt Fees" msgstr "" -#: lib/block_scout_web/templates/address_token/overview.html.eex:65 #: lib/block_scout_web/templates/address_token/overview.html.eex:65 #, elixir-autogen, elixir-format msgid "CRC Worth" msgstr "" -#: lib/block_scout_web/templates/common_components/_csv_export_button.html.eex:4 #: lib/block_scout_web/templates/common_components/_csv_export_button.html.eex:4 #, elixir-autogen, elixir-format msgid "CSV" msgstr "" #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:10 -#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:10 -#: lib/block_scout_web/views/internal_transaction_view.ex:21 -#: lib/block_scout_web/views/internal_transaction_view.ex:21 +#: lib/block_scout_web/views/internal_transaction_view.ex:23 #, elixir-autogen, elixir-format msgid "Call" msgstr "" -#: lib/block_scout_web/views/internal_transaction_view.ex:22 -#: lib/block_scout_web/views/internal_transaction_view.ex:22 +#: lib/block_scout_web/views/internal_transaction_view.ex:24 #, elixir-autogen, elixir-format msgid "Call Code" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:120 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:120 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:115 -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:115 -#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:41 #: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:41 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:107 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:107 +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:105 #: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:55 -#: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:55 -#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:51 #: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:51 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:47 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:47 -#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:54 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:54 #, elixir-autogen, elixir-format msgid "Cancel" msgstr "" -#: lib/block_scout_web/templates/transaction_state/index.html.eex:43 #: lib/block_scout_web/templates/transaction_state/index.html.eex:43 #, elixir-autogen, elixir-format msgid "Change" msgstr "" -#: lib/block_scout_web/templates/layout/_footer.html.eex:43 #: lib/block_scout_web/templates/layout/_footer.html.eex:43 #, elixir-autogen, elixir-format msgid "Chat (#blockscout)" msgstr "" -#: lib/block_scout_web/views/block_view.ex:65 #: lib/block_scout_web/views/block_view.ex:65 #, elixir-autogen, elixir-format msgid "Chore Reward" msgstr "" -#: lib/block_scout_web/templates/tokens/index.html.eex:38 #: lib/block_scout_web/templates/tokens/index.html.eex:38 #, elixir-autogen, elixir-format msgid "Circulating Market Cap" msgstr "" #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:137 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:137 -#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:106 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:106 #, elixir-autogen, elixir-format msgid "Clear" msgstr "" #: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:37 -#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:37 -#: lib/block_scout_web/templates/common_components/_modal_qr_code.html.eex:6 #: lib/block_scout_web/templates/common_components/_modal_qr_code.html.eex:6 #: lib/block_scout_web/templates/common_components/_modal_qr_code.html.eex:14 -#: lib/block_scout_web/templates/common_components/_modal_qr_code.html.eex:14 -#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:84 #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:84 #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:92 -#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:92 #, elixir-autogen, elixir-format msgid "Close" msgstr "" -#: lib/block_scout_web/templates/address/_tabs.html.eex:66 #: lib/block_scout_web/templates/address/_tabs.html.eex:66 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:165 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:165 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:187 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:187 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:126 -#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:126 -#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:149 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:149 #: lib/block_scout_web/views/address_view.ex:356 -#: lib/block_scout_web/views/address_view.ex:356 #, elixir-autogen, elixir-format msgid "Code" msgstr "" -#: lib/block_scout_web/templates/address/_tabs.html.eex:42 #: lib/block_scout_web/templates/address/_tabs.html.eex:42 #: lib/block_scout_web/views/address_view.ex:361 -#: lib/block_scout_web/views/address_view.ex:361 #, elixir-autogen, elixir-format msgid "Coin Balance History" msgstr "" -#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:54 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:54 #, elixir-autogen, elixir-format msgid "Collapse" msgstr "" #: lib/block_scout_web/templates/address_contract_verification_common_fields/_compiler_field.html.eex:3 -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_compiler_field.html.eex:3 -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:69 #: lib/block_scout_web/templates/verified_contracts/index.html.eex:69 #, elixir-autogen, elixir-format msgid "Compiler" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:142 #: lib/block_scout_web/templates/address_contract/index.html.eex:142 #, elixir-autogen, elixir-format msgid "Compiler Settings" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:71 #: lib/block_scout_web/templates/address_contract/index.html.eex:71 #, elixir-autogen, elixir-format msgid "Compiler version" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:368 -#: lib/block_scout_web/views/transaction_view.ex:368 +#: lib/block_scout_web/views/transaction_view.ex:366 #, elixir-autogen, elixir-format msgid "Confirmed" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:127 #: lib/block_scout_web/templates/transaction/overview.html.eex:127 #, elixir-autogen, elixir-format msgid "Confirmed by " msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:193 #: lib/block_scout_web/templates/transaction/overview.html.eex:193 #, elixir-autogen, elixir-format msgid "Confirmed within" msgstr "" #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:2 -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:2 -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:6 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:6 #: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:2 -#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:2 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:4 #: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:4 #: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:6 -#: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:6 -#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:4 #: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:4 #: lib/block_scout_web/templates/tokens/holder/index.html.eex:16 -#: lib/block_scout_web/templates/tokens/holder/index.html.eex:16 #, elixir-autogen, elixir-format msgid "Connection Lost" msgstr "" -#: lib/block_scout_web/templates/address_coin_balance/index.html.eex:12 #: lib/block_scout_web/templates/address_coin_balance/index.html.eex:12 #: lib/block_scout_web/templates/block/index.html.eex:5 -#: lib/block_scout_web/templates/block/index.html.eex:5 #, elixir-autogen, elixir-format msgid "Connection Lost, click to load newer blocks" msgstr "" -#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:15 #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:15 #, elixir-autogen, elixir-format msgid "Connection Lost, click to load newer internal transactions" msgstr "" #: lib/block_scout_web/templates/address_transaction/index.html.eex:11 -#: lib/block_scout_web/templates/address_transaction/index.html.eex:11 -#: lib/block_scout_web/templates/pending_transaction/index.html.eex:16 #: lib/block_scout_web/templates/pending_transaction/index.html.eex:16 #: lib/block_scout_web/templates/transaction/index.html.eex:22 -#: lib/block_scout_web/templates/transaction/index.html.eex:22 #, elixir-autogen, elixir-format msgid "Connection Lost, click to load newer transactions" msgstr "" -#: lib/block_scout_web/templates/address_validation/index.html.eex:10 #: lib/block_scout_web/templates/address_validation/index.html.eex:10 #, elixir-autogen, elixir-format msgid "Connection Lost, click to load newer validations" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:96 #: lib/block_scout_web/templates/address_contract/index.html.eex:96 #, elixir-autogen, elixir-format msgid "Constructor Arguments" msgstr "" -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:78 #: lib/block_scout_web/templates/verified_contracts/index.html.eex:78 #, elixir-autogen, elixir-format msgid "Constructor args" msgstr "" #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:52 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:52 -#: lib/block_scout_web/templates/transaction/overview.html.eex:273 -#: lib/block_scout_web/templates/transaction/overview.html.eex:273 +#: lib/block_scout_web/templates/transaction/overview.html.eex:250 #, elixir-autogen, elixir-format msgid "Contract" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:157 #: lib/block_scout_web/templates/address_contract/index.html.eex:157 #, elixir-autogen, elixir-format msgid "Contract ABI" msgstr "" #: lib/block_scout_web/templates/account/custom_abi/form.html.eex:18 -#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:18 -#: lib/block_scout_web/templates/account/custom_abi/index.html.eex:29 #: lib/block_scout_web/templates/account/custom_abi/index.html.eex:29 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_contract_address_field.html.eex:3 -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_contract_address_field.html.eex:3 -#: lib/block_scout_web/views/address_view.ex:108 #: lib/block_scout_web/views/address_view.ex:108 #, elixir-autogen, elixir-format msgid "Contract Address" msgstr "" #: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:16 -#: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:16 -#: lib/block_scout_web/views/address_view.ex:48 #: lib/block_scout_web/views/address_view.ex:48 #: lib/block_scout_web/views/address_view.ex:82 -#: lib/block_scout_web/views/address_view.ex:82 #, elixir-autogen, elixir-format msgid "Contract Address Pending" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:497 -#: lib/block_scout_web/views/transaction_view.ex:497 +#: lib/block_scout_web/views/transaction_view.ex:495 #, elixir-autogen, elixir-format msgid "Contract Call" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:494 -#: lib/block_scout_web/views/transaction_view.ex:494 +#: lib/block_scout_web/views/transaction_view.ex:492 #, elixir-autogen, elixir-format msgid "Contract Creation" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:174 #: lib/block_scout_web/templates/address_contract/index.html.eex:174 #: lib/block_scout_web/templates/address_contract/index.html.eex:189 -#: lib/block_scout_web/templates/address_contract/index.html.eex:189 #, elixir-autogen, elixir-format msgid "Contract Creation Code" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:90 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:90 #: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:80 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:80 #, elixir-autogen, elixir-format msgid "Contract Libraries" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:75 #: lib/block_scout_web/templates/address/overview.html.eex:75 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_contract_name_field.html.eex:3 -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_contract_name_field.html.eex:3 #, elixir-autogen, elixir-format msgid "Contract Name" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:25 #: lib/block_scout_web/templates/address_contract/index.html.eex:25 #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:11 -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:11 #, elixir-autogen, elixir-format msgid "Contract is not verified. However, we found a verified contract with the same bytecode in Blockscout DB" msgstr "" -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:47 #: lib/block_scout_web/templates/verified_contracts/index.html.eex:47 #, elixir-autogen, elixir-format msgid "Contract name or address" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:63 #: lib/block_scout_web/templates/address_contract/index.html.eex:63 #, elixir-autogen, elixir-format msgid "Contract name:" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:106 #: lib/block_scout_web/templates/address_contract/index.html.eex:106 #, elixir-autogen, elixir-format msgid "Contract source code" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:120 #: lib/block_scout_web/templates/address/overview.html.eex:120 #, elixir-autogen, elixir-format msgid "Contract was precompiled and created at genesis or contract creation transaction is missing" msgstr "" -#: lib/block_scout_web/templates/verified_contracts/_stats.html.eex:5 #: lib/block_scout_web/templates/verified_contracts/_stats.html.eex:5 #, elixir-autogen, elixir-format msgid "Contracts" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:180 #: lib/block_scout_web/templates/address_contract/index.html.eex:180 #, elixir-autogen, elixir-format msgid "Contracts that self destruct in their constructors have no contract code published and cannot be verified." msgstr "" -#: lib/block_scout_web/templates/layout/_footer.html.eex:42 #: lib/block_scout_web/templates/layout/_footer.html.eex:42 #, elixir-autogen, elixir-format msgid "Contribute" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:159 #: lib/block_scout_web/templates/address_contract/index.html.eex:159 #, elixir-autogen, elixir-format msgid "Copy ABI" msgstr "" -#: lib/block_scout_web/templates/account/api_key/row.html.eex:6 -#: lib/block_scout_web/templates/account/api_key/row.html.eex:6 #: lib/block_scout_web/templates/account/api_key/row.html.eex:6 #: lib/block_scout_web/templates/account/api_key/row.html.eex:6 #, elixir-autogen, elixir-format @@ -1129,3588 +877,2759 @@ msgstr "" #: lib/block_scout_web/templates/account/tag_address/row.html.eex:8 #: lib/block_scout_web/templates/account/tag_address/row.html.eex:8 -#: lib/block_scout_web/templates/account/tag_address/row.html.eex:8 -#: lib/block_scout_web/templates/account/tag_address/row.html.eex:8 -#: lib/block_scout_web/templates/account/tag_transaction/row.html.eex:11 #: lib/block_scout_web/templates/account/tag_transaction/row.html.eex:11 #: lib/block_scout_web/templates/account/tag_transaction/row.html.eex:11 -#: lib/block_scout_web/templates/account/tag_transaction/row.html.eex:11 -#: lib/block_scout_web/templates/account/watchlist_address/row.html.eex:7 #: lib/block_scout_web/templates/account/watchlist_address/row.html.eex:7 #: lib/block_scout_web/templates/address/overview.html.eex:38 -#: lib/block_scout_web/templates/address/overview.html.eex:38 -#: lib/block_scout_web/templates/address/overview.html.eex:39 #: lib/block_scout_web/templates/address/overview.html.eex:39 #: lib/block_scout_web/templates/block/overview.html.eex:104 -#: lib/block_scout_web/templates/block/overview.html.eex:104 -#: lib/block_scout_web/templates/block/overview.html.eex:105 #: lib/block_scout_web/templates/block/overview.html.eex:105 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:43 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:43 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:44 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:44 #, elixir-autogen, elixir-format msgid "Copy Address" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:144 #: lib/block_scout_web/templates/address_contract/index.html.eex:144 #, elixir-autogen, elixir-format msgid "Copy Compiler Settings" msgstr "" -#: lib/block_scout_web/templates/account/custom_abi/row.html.eex:6 -#: lib/block_scout_web/templates/account/custom_abi/row.html.eex:6 #: lib/block_scout_web/templates/account/custom_abi/row.html.eex:6 #: lib/block_scout_web/templates/account/custom_abi/row.html.eex:6 #, elixir-autogen, elixir-format msgid "Copy Contract Address" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:176 #: lib/block_scout_web/templates/address_contract/index.html.eex:176 #: lib/block_scout_web/templates/address_contract/index.html.eex:192 -#: lib/block_scout_web/templates/address_contract/index.html.eex:192 #, elixir-autogen, elixir-format msgid "Copy Contract Creation Code" msgstr "" #: lib/block_scout_web/templates/address_contract/index.html.eex:213 -#: lib/block_scout_web/templates/address_contract/index.html.eex:213 -#: lib/block_scout_web/templates/address_contract/index.html.eex:223 #: lib/block_scout_web/templates/address_contract/index.html.eex:223 #, elixir-autogen, elixir-format msgid "Copy Deployed ByteCode" msgstr "" #: lib/block_scout_web/templates/account/watchlist_address/row.html.eex:7 -#: lib/block_scout_web/templates/account/watchlist_address/row.html.eex:7 -#: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:17 #: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:17 #: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:18 -#: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:18 -#: lib/block_scout_web/templates/transaction/overview.html.eex:253 -#: lib/block_scout_web/templates/transaction/overview.html.eex:253 -#: lib/block_scout_web/templates/transaction/overview.html.eex:254 -#: lib/block_scout_web/templates/transaction/overview.html.eex:254 +#: lib/block_scout_web/templates/transaction/overview.html.eex:230 +#: lib/block_scout_web/templates/transaction/overview.html.eex:231 #, elixir-autogen, elixir-format msgid "Copy From Address" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:129 #: lib/block_scout_web/templates/block/overview.html.eex:129 #: lib/block_scout_web/templates/block/overview.html.eex:130 -#: lib/block_scout_web/templates/block/overview.html.eex:130 #, elixir-autogen, elixir-format msgid "Copy Hash" msgstr "" -#: lib/block_scout_web/templates/tokens/instance/metadata/index.html.eex:20 #: lib/block_scout_web/templates/tokens/instance/metadata/index.html.eex:20 #, elixir-autogen, elixir-format msgid "Copy Metadata" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:149 #: lib/block_scout_web/templates/block/overview.html.eex:149 #: lib/block_scout_web/templates/block/overview.html.eex:150 -#: lib/block_scout_web/templates/block/overview.html.eex:150 #, elixir-autogen, elixir-format msgid "Copy Parent Hash" msgstr "" -#: lib/block_scout_web/templates/transaction_raw_trace/_card_body.html.eex:9 #: lib/block_scout_web/templates/transaction_raw_trace/_card_body.html.eex:9 #, elixir-autogen, elixir-format msgid "Copy Raw Trace" msgstr "" #: lib/block_scout_web/templates/address_contract/index.html.eex:120 -#: lib/block_scout_web/templates/address_contract/index.html.eex:120 -#: lib/block_scout_web/templates/address_contract/index.html.eex:132 #: lib/block_scout_web/templates/address_contract/index.html.eex:132 #, elixir-autogen, elixir-format msgid "Copy Source Code" msgstr "" #: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:34 -#: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:34 -#: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:35 #: lib/block_scout_web/templates/transaction/_total_transfers_from_to.html.eex:35 -#: lib/block_scout_web/templates/transaction/overview.html.eex:280 -#: lib/block_scout_web/templates/transaction/overview.html.eex:280 -#: lib/block_scout_web/templates/transaction/overview.html.eex:281 -#: lib/block_scout_web/templates/transaction/overview.html.eex:281 -#: lib/block_scout_web/templates/transaction/overview.html.eex:288 -#: lib/block_scout_web/templates/transaction/overview.html.eex:288 -#: lib/block_scout_web/templates/transaction/overview.html.eex:289 -#: lib/block_scout_web/templates/transaction/overview.html.eex:289 +#: lib/block_scout_web/templates/transaction/overview.html.eex:257 +#: lib/block_scout_web/templates/transaction/overview.html.eex:258 +#: lib/block_scout_web/templates/transaction/overview.html.eex:265 +#: lib/block_scout_web/templates/transaction/overview.html.eex:266 #, elixir-autogen, elixir-format msgid "Copy To Address" msgstr "" #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:32 -#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:32 -#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:33 #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:33 #, elixir-autogen, elixir-format msgid "Copy Token ID" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:87 #: lib/block_scout_web/templates/transaction/overview.html.eex:87 #, elixir-autogen, elixir-format msgid "Copy Transaction Hash" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:88 #: lib/block_scout_web/templates/transaction/overview.html.eex:88 #, elixir-autogen, elixir-format msgid "Copy Txn Hash" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:571 -#: lib/block_scout_web/templates/transaction/overview.html.eex:571 +#: lib/block_scout_web/templates/transaction/overview.html.eex:548 #, elixir-autogen, elixir-format msgid "Copy Txn Hex Input" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:577 -#: lib/block_scout_web/templates/transaction/overview.html.eex:577 +#: lib/block_scout_web/templates/transaction/overview.html.eex:554 #, elixir-autogen, elixir-format msgid "Copy Txn UTF-8 Input" msgstr "" #: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:20 -#: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:20 -#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:41 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:41 -#: lib/block_scout_web/templates/transaction/overview.html.eex:570 -#: lib/block_scout_web/templates/transaction/overview.html.eex:570 -#: lib/block_scout_web/templates/transaction/overview.html.eex:576 -#: lib/block_scout_web/templates/transaction/overview.html.eex:576 -#: lib/block_scout_web/templates/transaction_raw_trace/_card_body.html.eex:8 +#: lib/block_scout_web/templates/transaction/overview.html.eex:547 +#: lib/block_scout_web/templates/transaction/overview.html.eex:553 #: lib/block_scout_web/templates/transaction_raw_trace/_card_body.html.eex:8 #, elixir-autogen, elixir-format msgid "Copy Value" msgstr "" -#: lib/block_scout_web/views/internal_transaction_view.ex:26 -#: lib/block_scout_web/views/internal_transaction_view.ex:26 +#: lib/block_scout_web/views/internal_transaction_view.ex:31 #, elixir-autogen, elixir-format msgid "Create" msgstr "" -#: lib/block_scout_web/templates/account/custom_abi/index.html.eex:12 #: lib/block_scout_web/templates/account/custom_abi/index.html.eex:12 #, elixir-autogen, elixir-format msgid "Create a Custom ABI to interact with contracts." msgstr "" -#: lib/block_scout_web/templates/account/api_key/index.html.eex:12 #: lib/block_scout_web/templates/account/api_key/index.html.eex:12 #, elixir-autogen, elixir-format, fuzzy msgid "Create an API key to use with your RPC and EthRPC API requests." msgstr "" -#: lib/block_scout_web/views/internal_transaction_view.ex:27 -#: lib/block_scout_web/views/internal_transaction_view.ex:27 +#: lib/block_scout_web/views/internal_transaction_view.ex:32 #, elixir-autogen, elixir-format msgid "Create2" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:102 #: lib/block_scout_web/templates/address/overview.html.eex:102 #, elixir-autogen, elixir-format msgid "Creator" msgstr "" #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:146 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:146 -#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:116 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:116 #, elixir-autogen, elixir-format msgid "Curl" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:97 #: lib/block_scout_web/templates/transaction/overview.html.eex:97 #, elixir-autogen, elixir-format msgid "Current transaction state: Success, Failed (Error), or Pending (In Process)" msgstr "" #: lib/block_scout_web/templates/address_read_contract/index.html.eex:20 -#: lib/block_scout_web/templates/address_read_contract/index.html.eex:20 -#: lib/block_scout_web/templates/address_write_contract/index.html.eex:18 #: lib/block_scout_web/templates/address_write_contract/index.html.eex:18 #, elixir-autogen, elixir-format msgid "Custom" msgstr "" #: lib/block_scout_web/templates/account/common/_nav.html.eex:19 -#: lib/block_scout_web/templates/account/common/_nav.html.eex:19 -#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:8 #: lib/block_scout_web/templates/account/custom_abi/form.html.eex:8 #: lib/block_scout_web/templates/account/custom_abi/index.html.eex:7 -#: lib/block_scout_web/templates/account/custom_abi/index.html.eex:7 -#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:19 #: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:19 #, elixir-autogen, elixir-format msgid "Custom ABI" msgstr "" #: lib/block_scout_web/templates/address_read_contract/index.html.eex:25 -#: lib/block_scout_web/templates/address_read_contract/index.html.eex:25 -#: lib/block_scout_web/templates/address_write_contract/index.html.eex:23 #: lib/block_scout_web/templates/address_write_contract/index.html.eex:23 #, elixir-autogen, elixir-format msgid "Custom ABI from account" msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:70 -#: lib/block_scout_web/templates/chain/show.html.eex:70 +#: lib/block_scout_web/templates/chain/show.html.eex:72 #, elixir-autogen, elixir-format msgid "Daily Transactions" msgstr "" -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:98 #: lib/block_scout_web/templates/address_logs/_logs.html.eex:98 #: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:7 -#: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:7 -#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:23 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:23 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:130 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:130 #, elixir-autogen, elixir-format msgid "Data" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:70 #: lib/block_scout_web/templates/block/overview.html.eex:70 #, elixir-autogen, elixir-format msgid "Date & time at which block was produced." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:179 #: lib/block_scout_web/templates/transaction/overview.html.eex:179 #, elixir-autogen, elixir-format msgid "Date & time of transaction inclusion, including length of time for confirmation." msgstr "" -#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:52 #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:52 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:131 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:131 #, elixir-autogen, elixir-format msgid "Decimals" msgstr "" #: lib/block_scout_web/templates/address_logs/_logs.html.eex:32 -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:32 -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:38 #: lib/block_scout_web/templates/address_logs/_logs.html.eex:38 #: lib/block_scout_web/templates/address_logs/_logs.html.eex:53 -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:53 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:43 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:43 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:51 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:51 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:66 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:66 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:82 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:82 #, elixir-autogen, elixir-format msgid "Decoded" msgstr "" -#: lib/block_scout_web/views/internal_transaction_view.ex:23 -#: lib/block_scout_web/views/internal_transaction_view.ex:23 +#: lib/block_scout_web/views/internal_transaction_view.ex:25 #, elixir-autogen, elixir-format msgid "Delegate Call" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:211 #: lib/block_scout_web/templates/address_contract/index.html.eex:211 #: lib/block_scout_web/templates/address_contract/index.html.eex:219 -#: lib/block_scout_web/templates/address_contract/index.html.eex:219 #, elixir-autogen, elixir-format msgid "Deployed ByteCode" msgstr "" -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:53 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:53 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:188 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:188 -#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:60 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:60 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:150 -#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:150 #, elixir-autogen, elixir-format msgid "Description" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:30 #: lib/block_scout_web/templates/address/overview.html.eex:30 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:166 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:166 -#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:127 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:127 #, elixir-autogen, elixir-format msgid "Details" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:159 #: lib/block_scout_web/templates/block/overview.html.eex:159 #, elixir-autogen, elixir-format msgid "Difficulty" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:181 #: lib/block_scout_web/templates/address_contract/index.html.eex:181 #, elixir-autogen, elixir-format msgid "Displaying the init data provided of the creating transaction." msgstr "" -#: lib/block_scout_web/templates/common_components/_csv_export_button.html.eex:4 #: lib/block_scout_web/templates/common_components/_csv_export_button.html.eex:4 #: lib/block_scout_web/templates/csv_export/index.html.eex:33 -#: lib/block_scout_web/templates/csv_export/index.html.eex:33 #, elixir-autogen, elixir-format msgid "Download" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:72 #: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:72 #, elixir-autogen, elixir-format msgid "Drop all Solidity contract source files into the drop zone." msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:72 #: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:72 #, elixir-autogen, elixir-format msgid "Drop all Solidity or Yul contract source files into the drop zone." msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:18 #: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:18 #, elixir-autogen, elixir-format msgid "Drop sources and metadata JSON file or click here" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:67 #: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:67 #, elixir-autogen, elixir-format msgid "Drop sources or click here" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:28 #: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:28 #, elixir-autogen, elixir-format msgid "Drop the standard input JSON file or click here" msgstr "" -#: lib/block_scout_web/templates/common_components/_minimal_proxy_pattern.html.eex:6 #: lib/block_scout_web/templates/common_components/_minimal_proxy_pattern.html.eex:6 #, elixir-autogen, elixir-format msgid "EIP-1167" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:225 -#: lib/block_scout_web/views/transaction_view.ex:225 +#: lib/block_scout_web/views/transaction_view.ex:221 #, elixir-autogen, elixir-format msgid "ERC-1155 " msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:223 -#: lib/block_scout_web/views/transaction_view.ex:223 +#: lib/block_scout_web/views/transaction_view.ex:219 #, elixir-autogen, elixir-format msgid "ERC-20 " msgstr "" -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:40 #: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:40 #, elixir-autogen, elixir-format msgid "ERC-20 tokens (beta)" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:226 -#: lib/block_scout_web/views/transaction_view.ex:226 +#: lib/block_scout_web/views/transaction_view.ex:222 #, elixir-autogen, elixir-format, fuzzy msgid "ERC-404 " msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:224 -#: lib/block_scout_web/views/transaction_view.ex:224 +#: lib/block_scout_web/views/transaction_view.ex:220 #, elixir-autogen, elixir-format msgid "ERC-721 " msgstr "" -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:53 #: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:53 #, elixir-autogen, elixir-format msgid "ERC-721, ERC-1155 tokens (NFT) (beta)" msgstr "" -#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:4 #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:4 #, elixir-autogen, elixir-format msgid "ETH RPC API Documentation" msgstr "" #: lib/block_scout_web/templates/address_contract/index.html.eex:82 -#: lib/block_scout_web/templates/address_contract/index.html.eex:82 -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:30 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:30 #: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:22 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:22 #, elixir-autogen, elixir-format msgid "EVM Version" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:34 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:34 #: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:26 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:26 #, elixir-autogen, elixir-format msgid "EVM version details" msgstr "" #: lib/block_scout_web/views/block_transaction_view.ex:7 -#: lib/block_scout_web/views/block_transaction_view.ex:7 +#: lib/block_scout_web/views/block_transaction_view.ex:11 #, elixir-autogen, elixir-format msgid "Easy Cowboy! This block does not exist yet!" msgstr "" -#: lib/block_scout_web/templates/account/api_key/row.html.eex:16 #: lib/block_scout_web/templates/account/api_key/row.html.eex:16 #: lib/block_scout_web/templates/account/custom_abi/row.html.eex:16 -#: lib/block_scout_web/templates/account/custom_abi/row.html.eex:16 -#: lib/block_scout_web/templates/account/watchlist_address/row.html.eex:27 #: lib/block_scout_web/templates/account/watchlist_address/row.html.eex:27 #, elixir-autogen, elixir-format msgid "Edit" msgstr "" -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:7 #: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:7 #, elixir-autogen, elixir-format msgid "Edit Watch list address" msgstr "" -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:71 #: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:71 #, elixir-autogen, elixir-format msgid "Email notifications" msgstr "" -#: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:5 #: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:5 #, elixir-autogen, elixir-format msgid "Emission Contract" msgstr "" -#: lib/block_scout_web/views/block_view.ex:73 #: lib/block_scout_web/views/block_view.ex:73 #, elixir-autogen, elixir-format msgid "Emission Reward" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:72 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:72 #, elixir-autogen, elixir-format msgid "Enter the Solidity Contract Code" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:22 #: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:22 #, elixir-autogen, elixir-format msgid "Enter the Vyper Contract Code" msgstr "" -#: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:11 #: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:11 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:10 -#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:10 #, elixir-autogen, elixir-format msgid "Error" msgstr "" -#: lib/block_scout_web/templates/transaction/_tile.html.eex:11 #: lib/block_scout_web/templates/transaction/_tile.html.eex:11 #, elixir-autogen, elixir-format msgid "Error in internal transactions" msgstr "" -#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:33 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:33 #, elixir-autogen, elixir-format msgid "Error rendering value" msgstr "" -#: lib/block_scout_web/templates/address/_balance_dropdown.html.eex:10 #: lib/block_scout_web/templates/address/_balance_dropdown.html.eex:10 #, elixir-autogen, elixir-format msgid "Error trying to fetch balances." msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:379 -#: lib/block_scout_web/views/transaction_view.ex:379 +#: lib/block_scout_web/views/transaction_view.ex:377 #, elixir-autogen, elixir-format msgid "Error: %{reason}" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:377 -#: lib/block_scout_web/views/transaction_view.ex:377 +#: lib/block_scout_web/views/transaction_view.ex:375 #, elixir-autogen, elixir-format msgid "Error: (Awaiting internal transactions for reason)" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:120 #: lib/block_scout_web/templates/address/overview.html.eex:120 #, elixir-autogen, elixir-format msgid "Error: Could not determine contract creator." msgstr "" -#: lib/block_scout_web/templates/layout/_topnav.html.eex:120 #: lib/block_scout_web/templates/layout/_topnav.html.eex:120 #, elixir-autogen, elixir-format msgid "Eth RPC" msgstr "" #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:211 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:211 -#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:164 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:164 #, elixir-autogen, elixir-format msgid "Example Value" msgstr "" #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:128 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:128 -#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:99 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:99 #, elixir-autogen, elixir-format msgid "Execute" msgstr "" -#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:54 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:54 #, elixir-autogen, elixir-format msgid "Expand" msgstr "" -#: lib/block_scout_web/templates/csv_export/index.html.eex:14 #: lib/block_scout_web/templates/csv_export/index.html.eex:14 #, elixir-autogen, elixir-format msgid "Export" msgstr "" -#: lib/block_scout_web/templates/csv_export/index.html.eex:10 #: lib/block_scout_web/templates/csv_export/index.html.eex:10 #, elixir-autogen, elixir-format msgid "Export Data" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:248 #: lib/block_scout_web/templates/address_contract/index.html.eex:248 #, elixir-autogen, elixir-format msgid "External libraries" msgstr "" -#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:40 #: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:40 #, elixir-autogen, elixir-format msgid "Failed to decode input data." msgstr "" #: lib/block_scout_web/templates/address_logs/_logs.html.eex:35 -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:35 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:46 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:46 #, elixir-autogen, elixir-format msgid "Failed to decode log data." msgstr "" -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:22 #: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:22 #, elixir-autogen, elixir-format msgid "Fast" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:249 #: lib/block_scout_web/templates/address/overview.html.eex:249 #, elixir-autogen, elixir-format msgid "Fetching gas used..." msgstr "" -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:112 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:112 #, elixir-autogen, elixir-format msgid "Fetching holders..." msgstr "" -#: lib/block_scout_web/templates/address/_balance_dropdown.html.eex:7 #: lib/block_scout_web/templates/address/_balance_dropdown.html.eex:7 #, elixir-autogen, elixir-format msgid "Fetching tokens..." msgstr "" #: lib/block_scout_web/templates/address/overview.html.eex:196 -#: lib/block_scout_web/templates/address/overview.html.eex:196 -#: lib/block_scout_web/templates/address/overview.html.eex:204 #: lib/block_scout_web/templates/address/overview.html.eex:204 #, elixir-autogen, elixir-format msgid "Fetching transactions..." msgstr "" #: lib/block_scout_web/templates/address/overview.html.eex:223 -#: lib/block_scout_web/templates/address/overview.html.eex:223 -#: lib/block_scout_web/templates/address/overview.html.eex:231 #: lib/block_scout_web/templates/address/overview.html.eex:231 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:123 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:123 #, elixir-autogen, elixir-format msgid "Fetching transfers..." msgstr "" -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:15 #: lib/block_scout_web/templates/verified_contracts/index.html.eex:15 #, elixir-autogen, elixir-format msgid "Filter by compiler:" msgstr "" -#: lib/block_scout_web/templates/admin/dashboard/index.html.eex:16 #: lib/block_scout_web/templates/admin/dashboard/index.html.eex:16 #, elixir-autogen, elixir-format msgid "For any existing contracts in the database, insert all ABI entries into the contract_methods table. Use this in case you have verified smart contracts before early March 2019 and you want other contracts with the same functions to show those ABI's as candidate matches." msgstr "" -#: lib/block_scout_web/templates/visualize_sol2uml/index.html.eex:7 #: lib/block_scout_web/templates/visualize_sol2uml/index.html.eex:7 #, elixir-autogen, elixir-format msgid "For contract" msgstr "" -#: lib/block_scout_web/templates/layout/_topnav.html.eex:44 #: lib/block_scout_web/templates/layout/_topnav.html.eex:44 #, elixir-autogen, elixir-format msgid "Forked Blocks (Reorgs)" msgstr "" -#: lib/block_scout_web/templates/layout/_footer.html.eex:45 #: lib/block_scout_web/templates/layout/_footer.html.eex:45 #, elixir-autogen, elixir-format msgid "Forum" msgstr "" -#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:38 #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:38 #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:40 -#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:40 -#: lib/block_scout_web/templates/address_transaction/index.html.eex:34 #: lib/block_scout_web/templates/address_transaction/index.html.eex:34 -#: lib/block_scout_web/templates/transaction/overview.html.eex:246 -#: lib/block_scout_web/templates/transaction/overview.html.eex:246 -#: lib/block_scout_web/views/address_internal_transaction_view.ex:11 +#: lib/block_scout_web/templates/transaction/overview.html.eex:223 #: lib/block_scout_web/views/address_internal_transaction_view.ex:11 #: lib/block_scout_web/views/address_token_transfer_view.ex:11 -#: lib/block_scout_web/views/address_token_transfer_view.ex:11 -#: lib/block_scout_web/views/address_transaction_view.ex:11 #: lib/block_scout_web/views/address_transaction_view.ex:11 #, elixir-autogen, elixir-format msgid "From" msgstr "" -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:18 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:18 #, elixir-autogen, elixir-format msgid "GET" msgstr "" -#: lib/block_scout_web/templates/block/_tile.html.eex:67 #: lib/block_scout_web/templates/block/_tile.html.eex:67 #: lib/block_scout_web/templates/block/overview.html.eex:189 -#: lib/block_scout_web/templates/block/overview.html.eex:189 -#: lib/block_scout_web/templates/transaction/overview.html.eex:430 -#: lib/block_scout_web/templates/transaction/overview.html.eex:430 +#: lib/block_scout_web/templates/transaction/overview.html.eex:407 #, elixir-autogen, elixir-format msgid "Gas Limit" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:404 -#: lib/block_scout_web/templates/transaction/overview.html.eex:404 +#: lib/block_scout_web/templates/transaction/overview.html.eex:381 #, elixir-autogen, elixir-format, fuzzy msgid "Gas Price" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:242 #: lib/block_scout_web/templates/address/overview.html.eex:242 #: lib/block_scout_web/templates/block/_tile.html.eex:73 -#: lib/block_scout_web/templates/block/_tile.html.eex:73 -#: lib/block_scout_web/templates/block/overview.html.eex:180 #: lib/block_scout_web/templates/block/overview.html.eex:180 #, elixir-autogen, elixir-format msgid "Gas Used" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:489 -#: lib/block_scout_web/templates/transaction/overview.html.eex:489 +#: lib/block_scout_web/templates/transaction/overview.html.eex:466 #, elixir-autogen, elixir-format, fuzzy msgid "Gas Used by Transaction" msgstr "" -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:3 #: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:3 #: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:18 -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:18 #, elixir-autogen, elixir-format msgid "Gas tracker" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:241 #: lib/block_scout_web/templates/address/overview.html.eex:241 #, elixir-autogen, elixir-format msgid "Gas used by the address." msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:60 #: lib/block_scout_web/templates/block/overview.html.eex:60 #, elixir-autogen, elixir-format msgid "Genesis Block" msgstr "" -#: lib/block_scout_web/templates/layout/_footer.html.eex:24 #: lib/block_scout_web/templates/layout/_footer.html.eex:24 #, elixir-autogen, elixir-format msgid "Github" msgstr "" -#: lib/block_scout_web/templates/common_components/_rap_pagination_container.html.eex:8 #: lib/block_scout_web/templates/common_components/_rap_pagination_container.html.eex:8 #, elixir-autogen, elixir-format msgid "Go to" msgstr "" -#: lib/block_scout_web/templates/layout/_topnav.html.eex:110 #: lib/block_scout_web/templates/layout/_topnav.html.eex:110 #, elixir-autogen, elixir-format msgid "GraphQL" msgstr "" -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:11 #: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:11 #: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:20 -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:20 -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:21 #: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:21 #: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:22 -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:22 -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:38 #: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:38 #: lib/block_scout_web/views/block_view.ex:22 -#: lib/block_scout_web/views/block_view.ex:22 -#: lib/block_scout_web/views/wei_helper.ex:81 #: lib/block_scout_web/views/wei_helper.ex:81 #, elixir-autogen, elixir-format msgid "Gwei" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:123 #: lib/block_scout_web/templates/block/overview.html.eex:123 #, elixir-autogen, elixir-format msgid "Hash" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:553 -#: lib/block_scout_web/templates/transaction/overview.html.eex:553 -#: lib/block_scout_web/templates/transaction/overview.html.eex:557 -#: lib/block_scout_web/templates/transaction/overview.html.eex:557 +#: lib/block_scout_web/templates/transaction/overview.html.eex:530 +#: lib/block_scout_web/templates/transaction/overview.html.eex:534 #, elixir-autogen, elixir-format msgid "Hex (Default)" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:224 -#: lib/block_scout_web/templates/transaction/overview.html.eex:224 -#, elixir-autogen, elixir-format -msgid "Highlighted events of the transaction." -msgstr "" - -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:108 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:108 #, elixir-autogen, elixir-format msgid "Holders" msgstr "" -#: lib/block_scout_web/templates/tokens/index.html.eex:48 #: lib/block_scout_web/templates/tokens/index.html.eex:48 #, elixir-autogen, elixir-format msgid "Holders Count" msgstr "" -#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:11 #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:11 #, elixir-autogen, elixir-format msgid "However, in general, the" msgstr "" -#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:19 #: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:19 #, elixir-autogen, elixir-format msgid "IMPORTANT: This information is a best guess based on similar functions from other verified contracts." msgstr "" -#: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:42 #: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:42 #: lib/block_scout_web/templates/transaction/_tile.html.eex:92 -#: lib/block_scout_web/templates/transaction/_tile.html.eex:92 #, elixir-autogen, elixir-format msgid "IN" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:56 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:56 #: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:48 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:48 #, elixir-autogen, elixir-format msgid "If you enabled optimization during compilation, select yes." msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:135 #: lib/block_scout_web/templates/address/overview.html.eex:135 #, elixir-autogen, elixir-format msgid "Implementation" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:134 #: lib/block_scout_web/templates/address/overview.html.eex:134 #, elixir-autogen, elixir-format msgid "Implementation address of the proxy contract." msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_include_nightly_builds_field.html.eex:3 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_include_nightly_builds_field.html.eex:3 #, elixir-autogen, elixir-format msgid "Include nightly builds" msgstr "" #: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:30 -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:30 -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:43 #: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:43 #: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:56 -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:56 #, elixir-autogen, elixir-format msgid "Incoming" msgstr "" #: lib/block_scout_web/templates/address_withdrawal/index.html.eex:29 -#: lib/block_scout_web/templates/address_withdrawal/index.html.eex:29 -#: lib/block_scout_web/templates/block_withdrawal/index.html.eex:23 #: lib/block_scout_web/templates/block_withdrawal/index.html.eex:23 #: lib/block_scout_web/templates/withdrawal/index.html.eex:23 -#: lib/block_scout_web/templates/withdrawal/index.html.eex:23 #, elixir-autogen, elixir-format, fuzzy msgid "Index" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:537 -#: lib/block_scout_web/templates/transaction/overview.html.eex:537 +#: lib/block_scout_web/templates/transaction/overview.html.eex:514 #, elixir-autogen, elixir-format msgid "Index position of Transaction in the block." msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:251 #: lib/block_scout_web/templates/block/overview.html.eex:251 #, elixir-autogen, elixir-format msgid "Index position(s) of referenced stale blocks." msgstr "" -#: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:6 #: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:6 #, elixir-autogen, elixir-format msgid "Indexed?" msgstr "" -#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:3 #: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:3 #, elixir-autogen, elixir-format msgid "Input" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:265 -#: lib/block_scout_web/templates/transaction/overview.html.eex:265 +#: lib/block_scout_web/templates/transaction/overview.html.eex:242 #, elixir-autogen, elixir-format msgid "Interacted With (To)" msgstr "" -#: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:7 #: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:7 #, elixir-autogen, elixir-format msgid "Internal Transaction" msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:36 -#: lib/block_scout_web/templates/address/_tabs.html.eex:36 -#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:17 #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:17 #: lib/block_scout_web/templates/transaction/_tabs.html.eex:11 -#: lib/block_scout_web/templates/transaction/_tabs.html.eex:11 -#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:6 #: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:6 #: lib/block_scout_web/views/address_view.ex:353 -#: lib/block_scout_web/views/address_view.ex:353 -#: lib/block_scout_web/views/transaction_view.ex:552 -#: lib/block_scout_web/views/transaction_view.ex:552 +#: lib/block_scout_web/views/transaction_view.ex:550 #, elixir-autogen, elixir-format msgid "Internal Transactions" msgstr "" -#: lib/block_scout_web/templates/internal_server_error/index.html.eex:7 #: lib/block_scout_web/templates/internal_server_error/index.html.eex:7 #, elixir-autogen, elixir-format msgid "Internal server error" msgstr "" -#: lib/block_scout_web/views/internal_transaction_view.ex:25 -#: lib/block_scout_web/views/internal_transaction_view.ex:25 +#: lib/block_scout_web/views/internal_transaction_view.ex:27 #, elixir-autogen, elixir-format msgid "Invalid" msgstr "" #: lib/block_scout_web/templates/tokens/inventory/index.html.eex:16 -#: lib/block_scout_web/templates/tokens/inventory/index.html.eex:16 -#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:19 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:19 #: lib/block_scout_web/views/tokens/overview_view.ex:42 -#: lib/block_scout_web/views/tokens/overview_view.ex:42 #, elixir-autogen, elixir-format msgid "Inventory" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_yul_contracts_switcher.html.eex:3 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_yul_contracts_switcher.html.eex:3 #, elixir-autogen, elixir-format msgid "Is Yul contract" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:204 #: lib/block_scout_web/templates/transaction/overview.html.eex:204 #, elixir-autogen, elixir-format, fuzzy msgid "L1 Block" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:523 -#: lib/block_scout_web/templates/transaction/overview.html.eex:523 -#: lib/block_scout_web/templates/transaction/overview.html.eex:524 -#: lib/block_scout_web/templates/transaction/overview.html.eex:524 +#: lib/block_scout_web/templates/transaction/overview.html.eex:500 +#: lib/block_scout_web/templates/transaction/overview.html.eex:501 #, elixir-autogen, elixir-format msgid "L1 Fee Scalar" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:512 -#: lib/block_scout_web/templates/transaction/overview.html.eex:512 -#: lib/block_scout_web/templates/transaction/overview.html.eex:513 -#: lib/block_scout_web/templates/transaction/overview.html.eex:513 +#: lib/block_scout_web/templates/transaction/overview.html.eex:489 +#: lib/block_scout_web/templates/transaction/overview.html.eex:490 #, elixir-autogen, elixir-format, fuzzy msgid "L1 Gas Price" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:501 -#: lib/block_scout_web/templates/transaction/overview.html.eex:501 -#: lib/block_scout_web/templates/transaction/overview.html.eex:502 -#: lib/block_scout_web/templates/transaction/overview.html.eex:502 +#: lib/block_scout_web/templates/transaction/overview.html.eex:478 +#: lib/block_scout_web/templates/transaction/overview.html.eex:479 #, elixir-autogen, elixir-format msgid "L1 Gas Used by Transaction" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:426 -#: lib/block_scout_web/templates/transaction/overview.html.eex:426 +#: lib/block_scout_web/templates/transaction/overview.html.eex:403 #, elixir-autogen, elixir-format, fuzzy msgid "L2 Gas Limit" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:400 -#: lib/block_scout_web/templates/transaction/overview.html.eex:400 +#: lib/block_scout_web/templates/transaction/overview.html.eex:377 #, elixir-autogen, elixir-format, fuzzy msgid "L2 Gas Price" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:485 -#: lib/block_scout_web/templates/transaction/overview.html.eex:485 +#: lib/block_scout_web/templates/transaction/overview.html.eex:462 #, elixir-autogen, elixir-format msgid "L2 Gas Used by Transaction" msgstr "" #: lib/block_scout_web/templates/verified_contracts/_stats.html.eex:13 -#: lib/block_scout_web/templates/verified_contracts/_stats.html.eex:13 -#: lib/block_scout_web/templates/verified_contracts/_stats.html.eex:26 #: lib/block_scout_web/templates/verified_contracts/_stats.html.eex:26 #, elixir-autogen, elixir-format msgid "Last 24h" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:260 #: lib/block_scout_web/templates/address/overview.html.eex:260 #, elixir-autogen, elixir-format msgid "Last Balance Update" msgstr "" -#: lib/block_scout_web/templates/account/api_key/index.html.eex:12 #: lib/block_scout_web/templates/account/api_key/index.html.eex:12 #: lib/block_scout_web/templates/account/api_key/index.html.eex:18 -#: lib/block_scout_web/templates/account/api_key/index.html.eex:18 #, elixir-autogen, elixir-format msgid "Learn more" msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:49 #: lib/block_scout_web/templates/layout/app.html.eex:49 #, elixir-autogen, elixir-format msgid "Less than" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_address.html.eex:4 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_address.html.eex:4 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_name.html.eex:4 -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_name.html.eex:4 #, elixir-autogen, elixir-format msgid "Library" msgstr "" -#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:24 #: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:24 #, elixir-autogen, elixir-format msgid "License Expires" msgstr "" -#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:10 #: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:10 #, elixir-autogen, elixir-format msgid "License ID" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:351 -#: lib/block_scout_web/templates/transaction/overview.html.eex:351 +#: lib/block_scout_web/templates/transaction/overview.html.eex:328 #, elixir-autogen, elixir-format msgid "List of ERC-1155 tokens created in the transaction." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:335 -#: lib/block_scout_web/templates/transaction/overview.html.eex:335 +#: lib/block_scout_web/templates/transaction/overview.html.eex:312 #, elixir-autogen, elixir-format msgid "List of token burnt in the transaction." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:318 -#: lib/block_scout_web/templates/transaction/overview.html.eex:318 +#: lib/block_scout_web/templates/transaction/overview.html.eex:295 #, elixir-autogen, elixir-format msgid "List of token minted in the transaction." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:302 -#: lib/block_scout_web/templates/transaction/overview.html.eex:302 +#: lib/block_scout_web/templates/transaction/overview.html.eex:279 #, elixir-autogen, elixir-format msgid "List of token transferred in the transaction." msgstr "" -#: lib/block_scout_web/templates/address_coin_balance/index.html.eex:18 #: lib/block_scout_web/templates/address_coin_balance/index.html.eex:18 #, elixir-autogen, elixir-format msgid "Loading chart..." msgstr "" #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:77 -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:77 -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:109 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:109 #: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:35 -#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:35 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:99 #: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:99 #: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:49 -#: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:49 #: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:45 -#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:45 -#: lib/block_scout_web/templates/address_read_contract/index.html.eex:41 #: lib/block_scout_web/templates/address_read_contract/index.html.eex:41 #: lib/block_scout_web/templates/address_read_contract/index.html.eex:49 -#: lib/block_scout_web/templates/address_read_contract/index.html.eex:49 -#: lib/block_scout_web/templates/address_read_proxy/index.html.eex:12 #: lib/block_scout_web/templates/address_read_proxy/index.html.eex:12 #: lib/block_scout_web/templates/address_write_contract/index.html.eex:39 -#: lib/block_scout_web/templates/address_write_contract/index.html.eex:39 -#: lib/block_scout_web/templates/address_write_contract/index.html.eex:47 #: lib/block_scout_web/templates/address_write_contract/index.html.eex:47 #: lib/block_scout_web/templates/address_write_proxy/index.html.eex:12 -#: lib/block_scout_web/templates/address_write_proxy/index.html.eex:12 -#: lib/block_scout_web/templates/tokens/contract/index.html.eex:17 #: lib/block_scout_web/templates/tokens/contract/index.html.eex:17 #, elixir-autogen, elixir-format msgid "Loading..." msgstr "" -#: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:2 #: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:2 #, elixir-autogen, elixir-format msgid "Log Data" msgstr "" -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:140 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:140 #, elixir-autogen, elixir-format msgid "Log Index" msgstr "" -#: lib/block_scout_web/templates/address/_tabs.html.eex:49 #: lib/block_scout_web/templates/address/_tabs.html.eex:49 #: lib/block_scout_web/templates/address_logs/index.html.eex:10 -#: lib/block_scout_web/templates/address_logs/index.html.eex:10 -#: lib/block_scout_web/templates/transaction/_tabs.html.eex:17 #: lib/block_scout_web/templates/transaction/_tabs.html.eex:17 #: lib/block_scout_web/templates/transaction_log/index.html.eex:8 -#: lib/block_scout_web/templates/transaction_log/index.html.eex:8 -#: lib/block_scout_web/views/address_view.ex:363 #: lib/block_scout_web/views/address_view.ex:363 -#: lib/block_scout_web/views/transaction_view.ex:553 -#: lib/block_scout_web/views/transaction_view.ex:553 +#: lib/block_scout_web/views/transaction_view.ex:551 #, elixir-autogen, elixir-format msgid "Logs" msgstr "" -#: lib/block_scout_web/templates/layout/_footer.html.eex:54 #: lib/block_scout_web/templates/layout/_footer.html.eex:54 #, elixir-autogen, elixir-format msgid "Main Networks" msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:53 -#: lib/block_scout_web/templates/chain/show.html.eex:53 -#: lib/block_scout_web/templates/layout/app.html.eex:50 +#: lib/block_scout_web/templates/chain/show.html.eex:55 #: lib/block_scout_web/templates/layout/app.html.eex:50 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:85 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:85 -#: lib/block_scout_web/views/address_view.ex:148 #: lib/block_scout_web/views/address_view.ex:148 #, elixir-autogen, elixir-format msgid "Market Cap" msgstr "" -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:84 #: lib/block_scout_web/templates/verified_contracts/index.html.eex:84 #, elixir-autogen, elixir-format msgid "Market cap" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:440 -#: lib/block_scout_web/templates/transaction/overview.html.eex:440 +#: lib/block_scout_web/templates/transaction/overview.html.eex:417 #, elixir-autogen, elixir-format msgid "Max Fee per Gas" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:450 -#: lib/block_scout_web/templates/transaction/overview.html.eex:450 +#: lib/block_scout_web/templates/transaction/overview.html.eex:427 #, elixir-autogen, elixir-format msgid "Max Priority Fee per Gas" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:331 -#: lib/block_scout_web/views/transaction_view.ex:331 +#: lib/block_scout_web/views/transaction_view.ex:329 #, elixir-autogen, elixir-format msgid "Max of" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:425 -#: lib/block_scout_web/templates/transaction/overview.html.eex:425 +#: lib/block_scout_web/templates/transaction/overview.html.eex:402 #, elixir-autogen, elixir-format msgid "Maximum gas amount approved for the transaction on L2." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:429 -#: lib/block_scout_web/templates/transaction/overview.html.eex:429 +#: lib/block_scout_web/templates/transaction/overview.html.eex:406 #, elixir-autogen, elixir-format, fuzzy msgid "Maximum gas amount approved for the transaction." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:439 -#: lib/block_scout_web/templates/transaction/overview.html.eex:439 +#: lib/block_scout_web/templates/transaction/overview.html.eex:416 #, elixir-autogen, elixir-format msgid "Maximum total amount per unit of gas a user is willing to pay for a transaction, including base fee and priority fee." msgstr "" -#: lib/block_scout_web/templates/tokens/instance/metadata/index.html.eex:18 #: lib/block_scout_web/templates/tokens/instance/metadata/index.html.eex:18 #: lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:10 -#: lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:10 -#: lib/block_scout_web/views/tokens/instance/overview_view.ex:75 #: lib/block_scout_web/views/tokens/instance/overview_view.ex:75 #, elixir-autogen, elixir-format msgid "Metadata" msgstr "" -#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:5 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:5 #, elixir-autogen, elixir-format msgid "Method Id" msgstr "" #: lib/block_scout_web/templates/block/_tile.html.eex:41 -#: lib/block_scout_web/templates/block/_tile.html.eex:41 -#: lib/block_scout_web/templates/block/overview.html.eex:98 #: lib/block_scout_web/templates/block/overview.html.eex:98 #: lib/block_scout_web/templates/chain/_block.html.eex:16 -#: lib/block_scout_web/templates/chain/_block.html.eex:16 -#: lib/block_scout_web/templates/transaction_state/_state_change.html.eex:22 #: lib/block_scout_web/templates/transaction_state/_state_change.html.eex:22 #, elixir-autogen, elixir-format msgid "Miner" msgstr "" #: lib/block_scout_web/views/block_view.ex:63 -#: lib/block_scout_web/views/block_view.ex:63 -#: lib/block_scout_web/views/block_view.ex:68 #: lib/block_scout_web/views/block_view.ex:68 #, elixir-autogen, elixir-format msgid "Miner Reward" msgstr "" -#: lib/block_scout_web/templates/common_components/_minimal_proxy_pattern.html.eex:3 #: lib/block_scout_web/templates/common_components/_minimal_proxy_pattern.html.eex:3 #, elixir-autogen, elixir-format msgid "Minimal Proxy Contract for" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:208 #: lib/block_scout_web/templates/block/overview.html.eex:208 #, elixir-autogen, elixir-format msgid "Minimum fee required per unit of gas. Fee adjusts based on network congestion." msgstr "" -#: lib/block_scout_web/templates/transaction/_actions.html.eex:92 #: lib/block_scout_web/templates/transaction/_actions.html.eex:92 #, elixir-autogen, elixir-format msgid "Mint of %{address} To %{to}" msgstr "" -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:223 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:223 #, elixir-autogen, elixir-format msgid "Model" msgstr "" -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:58 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:58 #, elixir-autogen, elixir-format msgid "Module" msgstr "" -#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:12 #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:12 #, elixir-autogen, elixir-format msgid "More internal transactions have come in" msgstr "" #: lib/block_scout_web/templates/address_transaction/index.html.eex:46 -#: lib/block_scout_web/templates/address_transaction/index.html.eex:46 -#: lib/block_scout_web/templates/chain/show.html.eex:219 -#: lib/block_scout_web/templates/chain/show.html.eex:219 -#: lib/block_scout_web/templates/pending_transaction/index.html.eex:13 +#: lib/block_scout_web/templates/chain/show.html.eex:221 #: lib/block_scout_web/templates/pending_transaction/index.html.eex:13 #: lib/block_scout_web/templates/transaction/index.html.eex:19 -#: lib/block_scout_web/templates/transaction/index.html.eex:19 #, elixir-autogen, elixir-format msgid "More transactions have come in" msgstr "" -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:63 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:63 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:74 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:74 #, elixir-autogen, elixir-format msgid "Must be set to:" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:22 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:22 #, elixir-autogen, elixir-format msgid "Must match the name specified in the code. For example, in contract MyContract {..} MyContract is the contract name." msgstr "" #: lib/block_scout_web/templates/tokens/_tile.html.eex:40 -#: lib/block_scout_web/templates/tokens/_tile.html.eex:40 -#: lib/block_scout_web/templates/verified_contracts/_contract.html.eex:21 #: lib/block_scout_web/templates/verified_contracts/_contract.html.eex:21 -#: lib/block_scout_web/templates/verified_contracts/_contract.html.eex:61 -#: lib/block_scout_web/templates/verified_contracts/_contract.html.eex:61 +#: lib/block_scout_web/templates/verified_contracts/_contract.html.eex:65 #, elixir-autogen, elixir-format msgid "N/A" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:116 #: lib/block_scout_web/templates/block/overview.html.eex:116 #, elixir-autogen, elixir-format msgid "N/A bytes" msgstr "" -#: lib/block_scout_web/templates/account/api_key/form.html.eex:19 #: lib/block_scout_web/templates/account/api_key/form.html.eex:19 #: lib/block_scout_web/templates/account/api_key/index.html.eex:28 -#: lib/block_scout_web/templates/account/api_key/index.html.eex:28 -#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:13 #: lib/block_scout_web/templates/account/custom_abi/form.html.eex:13 #: lib/block_scout_web/templates/account/custom_abi/index.html.eex:28 -#: lib/block_scout_web/templates/account/custom_abi/index.html.eex:28 -#: lib/block_scout_web/templates/account/tag_address/form.html.eex:18 #: lib/block_scout_web/templates/account/tag_address/form.html.eex:18 #: lib/block_scout_web/templates/account/tag_address/index.html.eex:22 -#: lib/block_scout_web/templates/account/tag_address/index.html.eex:22 -#: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:18 #: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:18 #: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:22 -#: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:22 -#: lib/block_scout_web/templates/account/watchlist/show.html.eex:22 #: lib/block_scout_web/templates/account/watchlist/show.html.eex:22 #: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:19 -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:19 -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_name.html.eex:4 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_name.html.eex:4 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:52 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:52 -#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:59 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:59 #: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:4 -#: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:4 -#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:21 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:21 #, elixir-autogen, elixir-format msgid "Name" msgstr "" -#: lib/block_scout_web/templates/account/api_key/form.html.eex:20 #: lib/block_scout_web/templates/account/api_key/form.html.eex:20 #, elixir-autogen, elixir-format msgid "Name this API key" msgstr "" -#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:14 #: lib/block_scout_web/templates/account/custom_abi/form.html.eex:14 #, elixir-autogen, elixir-format msgid "Name this Custom ABI" msgstr "" #: lib/block_scout_web/templates/account/tag_address/form.html.eex:19 -#: lib/block_scout_web/templates/account/tag_address/form.html.eex:19 -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:20 #: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:20 #, elixir-autogen, elixir-format msgid "Name this address" msgstr "" -#: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:19 #: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:19 #, elixir-autogen, elixir-format msgid "Name this transaction" msgstr "" -#: lib/block_scout_web/templates/address_token/overview.html.eex:44 #: lib/block_scout_web/templates/address_token/overview.html.eex:44 #, elixir-autogen, elixir-format msgid "Net Worth" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:5 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:5 #, elixir-autogen, elixir-format msgid "New Smart Contract Verification" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:9 #: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:9 #, elixir-autogen, elixir-format msgid "New Smart Contract Verification via Standard input JSON" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:5 #: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:5 #, elixir-autogen, elixir-format msgid "New Smart Contract Verification via metadata JSON" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:9 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:9 #: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:7 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:7 #, elixir-autogen, elixir-format msgid "New Solidity Smart Contract Verification" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:7 #: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:7 #, elixir-autogen, elixir-format msgid "New Solidity/Yul Smart Contract Verification" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:7 #: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:7 #, elixir-autogen, elixir-format msgid "New Vyper Smart Contract Verification" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:80 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:80 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:87 -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:87 -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:95 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:95 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:103 -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:103 -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:111 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:111 #, elixir-autogen, elixir-format msgid "Next" msgstr "" #: lib/block_scout_web/templates/address_contract_verification_common_fields/_fetch_constructor_args.html.eex:9 -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_fetch_constructor_args.html.eex:9 -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_include_nightly_builds_field.html.eex:9 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_include_nightly_builds_field.html.eex:9 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_yul_contracts_switcher.html.eex:9 -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_yul_contracts_switcher.html.eex:9 -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:46 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:46 #: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:38 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:38 #, elixir-autogen, elixir-format msgid "No" msgstr "" -#: lib/block_scout_web/templates/transaction_raw_trace/_card_body.html.eex:15 #: lib/block_scout_web/templates/transaction_raw_trace/_card_body.html.eex:15 #, elixir-autogen, elixir-format msgid "No trace entries found." msgstr "" #: lib/block_scout_web/templates/block/overview.html.eex:198 -#: lib/block_scout_web/templates/block/overview.html.eex:198 -#: lib/block_scout_web/templates/transaction/overview.html.eex:535 -#: lib/block_scout_web/templates/transaction/overview.html.eex:535 +#: lib/block_scout_web/templates/transaction/overview.html.eex:512 #, elixir-autogen, elixir-format msgid "Nonce" msgstr "" -#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:11 #: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:11 #, elixir-autogen, elixir-format msgid "Not unique Token" msgstr "" -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:107 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:107 #, elixir-autogen, elixir-format msgid "Number of accounts holding the token" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:276 #: lib/block_scout_web/templates/address/overview.html.eex:276 #, elixir-autogen, elixir-format msgid "Number of blocks validated by this validator." msgstr "" -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:130 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:130 #, elixir-autogen, elixir-format msgid "Number of digits that come after the decimal place when displaying token value" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:187 #: lib/block_scout_web/templates/address/overview.html.eex:187 #, elixir-autogen, elixir-format msgid "Number of transactions related to this address." msgstr "" -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:118 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:118 #, elixir-autogen, elixir-format msgid "Number of transfers for the token" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:214 #: lib/block_scout_web/templates/address/overview.html.eex:214 #, elixir-autogen, elixir-format msgid "Number of transfers to/from this address." msgstr "" -#: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:40 #: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:40 #: lib/block_scout_web/templates/transaction/_tile.html.eex:88 -#: lib/block_scout_web/templates/transaction/_tile.html.eex:88 #, elixir-autogen, elixir-format msgid "OUT" msgstr "" -#: lib/block_scout_web/templates/common_components/_rap_pagination_container.html.eex:13 #: lib/block_scout_web/templates/common_components/_rap_pagination_container.html.eex:13 #, elixir-autogen, elixir-format msgid "Only the first" msgstr "" #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:40 -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:40 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:32 #: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:32 #: lib/block_scout_web/templates/verified_contracts/index.html.eex:75 -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:75 #, elixir-autogen, elixir-format msgid "Optimization" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:67 #: lib/block_scout_web/templates/address_contract/index.html.eex:67 #, elixir-autogen, elixir-format msgid "Optimization enabled" msgstr "" #: lib/block_scout_web/templates/address_contract/index.html.eex:76 -#: lib/block_scout_web/templates/address_contract/index.html.eex:76 -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:62 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:62 #: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:54 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:54 #, elixir-autogen, elixir-format msgid "Optimization runs" msgstr "" -#: lib/block_scout_web/templates/layout/_footer.html.eex:78 #: lib/block_scout_web/templates/layout/_footer.html.eex:78 #, elixir-autogen, elixir-format msgid "Other Explorers" msgstr "" #: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:35 -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:35 -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:48 #: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:48 #: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:61 -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:61 #, elixir-autogen, elixir-format msgid "Outgoing" msgstr "" -#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:24 #: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:24 #, elixir-autogen, elixir-format msgid "Owner Address" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:77 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:77 #, elixir-autogen, elixir-format msgid "POA solidity flattener or the" msgstr "" -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:19 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:19 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:26 -#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:26 #, elixir-autogen, elixir-format msgid "POST" msgstr "" -#: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:41 #: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:41 #, elixir-autogen, elixir-format msgid "Page" msgstr "" -#: lib/block_scout_web/templates/page_not_found/index.html.eex:7 #: lib/block_scout_web/templates/page_not_found/index.html.eex:7 #, elixir-autogen, elixir-format msgid "Page not found" msgstr "" -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:33 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:33 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:40 -#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:40 #, elixir-autogen, elixir-format msgid "Parameters" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:139 #: lib/block_scout_web/templates/block/overview.html.eex:139 #, elixir-autogen, elixir-format msgid "Parent Hash" msgstr "" #: lib/block_scout_web/templates/layout/_topnav.html.eex:63 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:63 -#: lib/block_scout_web/views/transaction_view.ex:374 -#: lib/block_scout_web/views/transaction_view.ex:374 -#: lib/block_scout_web/views/transaction_view.ex:419 -#: lib/block_scout_web/views/transaction_view.ex:419 -#: lib/block_scout_web/views/transaction_view.ex:427 -#: lib/block_scout_web/views/transaction_view.ex:427 +#: lib/block_scout_web/views/transaction_view.ex:372 +#: lib/block_scout_web/views/transaction_view.ex:417 +#: lib/block_scout_web/views/transaction_view.ex:425 #, elixir-autogen, elixir-format msgid "Pending" msgstr "" -#: lib/block_scout_web/templates/pending_transaction/index.html.eex:5 #: lib/block_scout_web/templates/pending_transaction/index.html.eex:5 #, elixir-autogen, elixir-format msgid "Pending Transactions" msgstr "" #: lib/block_scout_web/templates/address/_custom_view_df_title.html.eex:9 -#: lib/block_scout_web/templates/address/_custom_view_df_title.html.eex:9 -#: lib/block_scout_web/templates/address/_custom_view_df_title.html.eex:13 #: lib/block_scout_web/templates/address/_custom_view_df_title.html.eex:13 #, elixir-autogen, elixir-format msgid "Play" msgstr "" #: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:21 -#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:21 -#: lib/block_scout_web/templates/layout/app.html.eex:100 #: lib/block_scout_web/templates/layout/app.html.eex:100 #, elixir-autogen, elixir-format msgid "Please confirm your email address to use the My Account feature." msgstr "" -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:68 #: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:68 #, elixir-autogen, elixir-format msgid "Please select notification methods:" msgstr "" -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:24 #: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:24 #, elixir-autogen, elixir-format msgid "Please select what types of notifications you will receive:" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:537 -#: lib/block_scout_web/templates/transaction/overview.html.eex:537 +#: lib/block_scout_web/templates/transaction/overview.html.eex:514 #, elixir-autogen, elixir-format msgid "Position" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:256 #: lib/block_scout_web/templates/block/overview.html.eex:256 #, elixir-autogen, elixir-format msgid "Position %{index}" msgstr "" -#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:18 #: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:18 #, elixir-autogen, elixir-format msgid "Potential matches from contract method database:" msgstr "" -#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:32 #: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:32 #, elixir-autogen, elixir-format msgid "Potential matches from our contract method database:" msgstr "" -#: lib/block_scout_web/templates/layout/_search.html.eex:27 #: lib/block_scout_web/templates/layout/_search.html.eex:27 #, elixir-autogen, elixir-format msgid "Press / and focus will be moved to the search field" msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:42 -#: lib/block_scout_web/templates/chain/show.html.eex:42 +#: lib/block_scout_web/templates/chain/show.html.eex:44 #: lib/block_scout_web/templates/layout/app.html.eex:51 -#: lib/block_scout_web/templates/layout/app.html.eex:51 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:96 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:96 #, elixir-autogen, elixir-format msgid "Price" msgstr "" -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:95 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:95 #, elixir-autogen, elixir-format msgid "Price per token on the exchanges" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:399 -#: lib/block_scout_web/templates/transaction/overview.html.eex:399 +#: lib/block_scout_web/templates/transaction/overview.html.eex:376 #, elixir-autogen, elixir-format msgid "Price per unit of gas specified by the sender on L2. Higher gas prices can prioritize transaction inclusion during times of high usage." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:403 -#: lib/block_scout_web/templates/transaction/overview.html.eex:403 +#: lib/block_scout_web/templates/transaction/overview.html.eex:380 #, elixir-autogen, elixir-format, fuzzy msgid "Price per unit of gas specified by the sender. Higher gas prices can prioritize transaction inclusion during times of high usage." msgstr "" #: lib/block_scout_web/templates/block/overview.html.eex:227 -#: lib/block_scout_web/templates/block/overview.html.eex:227 -#: lib/block_scout_web/templates/transaction/overview.html.eex:460 -#: lib/block_scout_web/templates/transaction/overview.html.eex:460 +#: lib/block_scout_web/templates/transaction/overview.html.eex:437 #, elixir-autogen, elixir-format msgid "Priority Fee / Tip" msgstr "" -#: lib/block_scout_web/templates/block/_tile.html.eex:62 #: lib/block_scout_web/templates/block/_tile.html.eex:62 #, elixir-autogen, elixir-format msgid "Priority Fees" msgstr "" -#: lib/block_scout_web/templates/account/common/_nav.html.eex:4 #: lib/block_scout_web/templates/account/common/_nav.html.eex:4 #: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:14 -#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:14 #, elixir-autogen, elixir-format msgid "Profile" msgstr "" -#: lib/block_scout_web/templates/common_components/_btn_qr_code.html.eex:10 #: lib/block_scout_web/templates/common_components/_btn_qr_code.html.eex:10 #: lib/block_scout_web/templates/common_components/_modal_qr_code.html.eex:5 -#: lib/block_scout_web/templates/common_components/_modal_qr_code.html.eex:5 -#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:83 #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:83 #, elixir-autogen, elixir-format msgid "QR Code" msgstr "" -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:106 #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:106 #, elixir-autogen, elixir-format msgid "Query" msgstr "" -#: lib/block_scout_web/templates/layout/_topnav.html.eex:115 #: lib/block_scout_web/templates/layout/_topnav.html.eex:115 #, elixir-autogen, elixir-format msgid "RPC" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:546 -#: lib/block_scout_web/templates/transaction/overview.html.eex:546 +#: lib/block_scout_web/templates/transaction/overview.html.eex:523 #, elixir-autogen, elixir-format msgid "Raw Input" msgstr "" -#: lib/block_scout_web/templates/transaction/_tabs.html.eex:24 #: lib/block_scout_web/templates/transaction/_tabs.html.eex:24 #: lib/block_scout_web/templates/transaction_raw_trace/_card_body.html.eex:1 -#: lib/block_scout_web/templates/transaction_raw_trace/_card_body.html.eex:1 -#: lib/block_scout_web/views/transaction_view.ex:554 -#: lib/block_scout_web/views/transaction_view.ex:554 +#: lib/block_scout_web/views/transaction_view.ex:552 #, elixir-autogen, elixir-format msgid "Raw Trace" msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:81 -#: lib/block_scout_web/templates/address/_tabs.html.eex:81 -#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:27 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:27 #: lib/block_scout_web/views/address_view.ex:357 -#: lib/block_scout_web/views/address_view.ex:357 -#: lib/block_scout_web/views/tokens/overview_view.ex:41 #: lib/block_scout_web/views/tokens/overview_view.ex:41 #, elixir-autogen, elixir-format msgid "Read Contract" msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:88 -#: lib/block_scout_web/templates/address/_tabs.html.eex:88 -#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:41 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:41 #: lib/block_scout_web/views/address_view.ex:358 -#: lib/block_scout_web/views/address_view.ex:358 #, elixir-autogen, elixir-format msgid "Read Proxy" msgstr "" -#: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:13 #: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:13 #, elixir-autogen, elixir-format msgid "Records" msgstr "" #: lib/block_scout_web/templates/account/api_key/row.html.eex:13 -#: lib/block_scout_web/templates/account/api_key/row.html.eex:13 -#: lib/block_scout_web/templates/account/custom_abi/row.html.eex:13 #: lib/block_scout_web/templates/account/custom_abi/row.html.eex:13 #, elixir-autogen, elixir-format msgid "Remove" msgstr "" -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:77 #: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:77 #, elixir-autogen, elixir-format msgid "Remove from Watch list" msgstr "" -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:155 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:155 #, elixir-autogen, elixir-format msgid "Request URL" msgstr "" -#: lib/block_scout_web/templates/error422/index.html.eex:7 #: lib/block_scout_web/templates/error422/index.html.eex:7 #, elixir-autogen, elixir-format msgid "Request cannot be processed" msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:100 #: lib/block_scout_web/templates/layout/app.html.eex:100 #, elixir-autogen, elixir-format msgid "Resend verification email" msgstr "" #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:112 -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:112 -#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:38 #: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:38 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:104 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:104 +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:102 #: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:52 -#: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:52 -#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:48 #: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:48 #, elixir-autogen, elixir-format msgid "Reset" msgstr "" #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:173 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:173 -#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:134 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:134 #, elixir-autogen, elixir-format msgid "Response Body" msgstr "" #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:185 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:185 -#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:147 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:147 #, elixir-autogen, elixir-format msgid "Responses" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:98 #: lib/block_scout_web/templates/transaction/overview.html.eex:98 #, elixir-autogen, elixir-format msgid "Result" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:138 #: lib/block_scout_web/templates/transaction/overview.html.eex:138 #, elixir-autogen, elixir-format msgid "Revert reason" msgstr "" #: lib/block_scout_web/templates/block/_tile.html.eex:52 -#: lib/block_scout_web/templates/block/_tile.html.eex:52 -#: lib/block_scout_web/templates/chain/_block.html.eex:27 #: lib/block_scout_web/templates/chain/_block.html.eex:27 -#: lib/block_scout_web/views/internal_transaction_view.ex:29 -#: lib/block_scout_web/views/internal_transaction_view.ex:29 +#: lib/block_scout_web/views/internal_transaction_view.ex:34 #, elixir-autogen, elixir-format msgid "Reward" msgstr "" -#: lib/block_scout_web/templates/admin/dashboard/index.html.eex:21 #: lib/block_scout_web/templates/admin/dashboard/index.html.eex:21 #, elixir-autogen, elixir-format msgid "Run" msgstr "" -#: lib/block_scout_web/templates/account/api_key/form.html.eex:26 #: lib/block_scout_web/templates/account/api_key/form.html.eex:26 #: lib/block_scout_web/templates/account/custom_abi/form.html.eex:31 -#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:31 -#: lib/block_scout_web/templates/account/tag_address/form.html.eex:25 #: lib/block_scout_web/templates/account/tag_address/form.html.eex:25 #: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:25 -#: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:25 -#: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:83 #: lib/block_scout_web/templates/account/watchlist_address/form.html.eex:83 #, elixir-autogen, elixir-format msgid "Save" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:226 -#: lib/block_scout_web/templates/transaction/overview.html.eex:226 -#, elixir-autogen, elixir-format -msgid "Scroll to see more" -msgstr "" - -#: lib/block_scout_web/templates/address_logs/index.html.eex:16 #: lib/block_scout_web/templates/address_logs/index.html.eex:16 #: lib/block_scout_web/templates/layout/_search.html.eex:34 -#: lib/block_scout_web/templates/layout/_search.html.eex:34 #, elixir-autogen, elixir-format msgid "Search" msgstr "" -#: lib/block_scout_web/templates/search/results.html.eex:17 #: lib/block_scout_web/templates/search/results.html.eex:17 #, elixir-autogen, elixir-format msgid "Search Results" msgstr "" -#: lib/block_scout_web/templates/layout/_search.html.eex:3 #: lib/block_scout_web/templates/layout/_search.html.eex:3 #, elixir-autogen, elixir-format msgid "Search by address, token symbol name, transaction hash, or block number" msgstr "" -#: lib/block_scout_web/templates/address_token_balance/_token_balances.html.eex:47 #: lib/block_scout_web/templates/address_token_balance/_token_balances.html.eex:47 #, elixir-autogen, elixir-format msgid "Search tokens" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_yul_contracts_switcher.html.eex:19 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_yul_contracts_switcher.html.eex:19 #, elixir-autogen, elixir-format msgid "Select Yes if you want to verify Yul contract." msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_include_nightly_builds_field.html.eex:19 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_include_nightly_builds_field.html.eex:19 #, elixir-autogen, elixir-format msgid "Select yes if you want to show nightly builds." msgstr "" -#: lib/block_scout_web/views/internal_transaction_view.ex:28 -#: lib/block_scout_web/views/internal_transaction_view.ex:28 +#: lib/block_scout_web/views/internal_transaction_view.ex:33 #, elixir-autogen, elixir-format msgid "Self-Destruct" msgstr "" #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:163 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:163 -#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:124 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:124 #, elixir-autogen, elixir-format msgid "Server Response" msgstr "" -#: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:7 #: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:7 #, elixir-autogen, elixir-format msgid "Show" msgstr "" -#: lib/block_scout_web/templates/common_components/_btn_qr_code.html.eex:11 #: lib/block_scout_web/templates/common_components/_btn_qr_code.html.eex:11 #, elixir-autogen, elixir-format msgid "Show QR Code" msgstr "" -#: lib/block_scout_web/templates/address_token/overview.html.eex:52 #: lib/block_scout_web/templates/address_token/overview.html.eex:52 #, elixir-autogen, elixir-format msgid "Shows the current" msgstr "" -#: lib/block_scout_web/templates/address_token/overview.html.eex:59 #: lib/block_scout_web/templates/address_token/overview.html.eex:59 #, elixir-autogen, elixir-format msgid "Shows the tokens held in the address (includes ERC-20, ERC-721 and ERC-1155)." msgstr "" -#: lib/block_scout_web/templates/address_token/overview.html.eex:66 #: lib/block_scout_web/templates/address_token/overview.html.eex:66 #, elixir-autogen, elixir-format msgid "Shows the total CRC balance in the address." msgstr "" -#: lib/block_scout_web/templates/address_token/overview.html.eex:45 #: lib/block_scout_web/templates/address_token/overview.html.eex:45 #, elixir-autogen, elixir-format msgid "Shows total assets held in the address" msgstr "" -#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:32 #: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:32 #, elixir-autogen, elixir-format msgid "Sign in" msgstr "" -#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:23 #: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:23 #, elixir-autogen, elixir-format msgid "Sign out" msgstr "" -#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:11 #: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:11 #, elixir-autogen, elixir-format msgid "Signed in as " msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:114 #: lib/block_scout_web/templates/block/overview.html.eex:114 #, elixir-autogen, elixir-format msgid "Size" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:113 #: lib/block_scout_web/templates/block/overview.html.eex:113 #, elixir-autogen, elixir-format msgid "Size of the block in bytes." msgstr "" -#: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:20 #: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:20 #, elixir-autogen, elixir-format msgid "Slow" msgstr "" -#: lib/block_scout_web/templates/verified_contracts/_contract.html.eex:28 -#: lib/block_scout_web/templates/verified_contracts/_contract.html.eex:28 +#: lib/block_scout_web/templates/verified_contracts/_contract.html.eex:31 #: lib/block_scout_web/templates/verified_contracts/index.html.eex:26 -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:26 -#: lib/block_scout_web/views/verified_contracts_view.ex:10 #: lib/block_scout_web/views/verified_contracts_view.ex:10 #, elixir-autogen, elixir-format msgid "Solidity" msgstr "" #: lib/block_scout_web/templates/address_coin_balance/index.html.eex:30 -#: lib/block_scout_web/templates/address_coin_balance/index.html.eex:30 -#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:50 #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:50 #: lib/block_scout_web/templates/address_logs/index.html.eex:23 -#: lib/block_scout_web/templates/address_logs/index.html.eex:23 -#: lib/block_scout_web/templates/address_token/index.html.eex:60 #: lib/block_scout_web/templates/address_token/index.html.eex:60 #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:58 -#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:58 -#: lib/block_scout_web/templates/address_transaction/index.html.eex:50 #: lib/block_scout_web/templates/address_transaction/index.html.eex:50 #: lib/block_scout_web/templates/address_validation/index.html.eex:20 -#: lib/block_scout_web/templates/address_validation/index.html.eex:20 -#: lib/block_scout_web/templates/address_withdrawal/index.html.eex:20 #: lib/block_scout_web/templates/address_withdrawal/index.html.eex:20 #: lib/block_scout_web/templates/block_transaction/index.html.eex:14 -#: lib/block_scout_web/templates/block_transaction/index.html.eex:14 -#: lib/block_scout_web/templates/block_withdrawal/index.html.eex:14 #: lib/block_scout_web/templates/block_withdrawal/index.html.eex:14 -#: lib/block_scout_web/templates/chain/show.html.eex:160 -#: lib/block_scout_web/templates/chain/show.html.eex:160 +#: lib/block_scout_web/templates/chain/show.html.eex:162 #: lib/block_scout_web/templates/pending_transaction/index.html.eex:18 -#: lib/block_scout_web/templates/pending_transaction/index.html.eex:18 -#: lib/block_scout_web/templates/tokens/holder/index.html.eex:24 #: lib/block_scout_web/templates/tokens/holder/index.html.eex:24 #: lib/block_scout_web/templates/tokens/instance/holder/index.html.eex:23 -#: lib/block_scout_web/templates/tokens/instance/holder/index.html.eex:23 -#: lib/block_scout_web/templates/tokens/instance/transfer/index.html.eex:23 #: lib/block_scout_web/templates/tokens/instance/transfer/index.html.eex:23 #: lib/block_scout_web/templates/tokens/inventory/index.html.eex:23 -#: lib/block_scout_web/templates/tokens/inventory/index.html.eex:23 -#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:22 #: lib/block_scout_web/templates/tokens/transfer/index.html.eex:22 #: lib/block_scout_web/templates/transaction/index.html.eex:25 -#: lib/block_scout_web/templates/transaction/index.html.eex:25 -#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:13 #: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:13 #: lib/block_scout_web/templates/transaction_log/index.html.eex:15 -#: lib/block_scout_web/templates/transaction_log/index.html.eex:15 -#: lib/block_scout_web/templates/transaction_state/index.html.eex:13 #: lib/block_scout_web/templates/transaction_state/index.html.eex:13 #: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:14 -#: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:14 -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:51 #: lib/block_scout_web/templates/verified_contracts/index.html.eex:51 #: lib/block_scout_web/templates/withdrawal/index.html.eex:14 -#: lib/block_scout_web/templates/withdrawal/index.html.eex:14 #, elixir-autogen, elixir-format msgid "Something went wrong, click to reload." msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:225 -#: lib/block_scout_web/templates/chain/show.html.eex:225 +#: lib/block_scout_web/templates/chain/show.html.eex:227 #, elixir-autogen, elixir-format msgid "Something went wrong, click to retry." msgstr "" -#: lib/block_scout_web/templates/transaction/not_found.html.eex:6 #: lib/block_scout_web/templates/transaction/not_found.html.eex:6 #, elixir-autogen, elixir-format msgid "Sorry, we are unable to locate this transaction hash" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:63 #: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:63 #, elixir-autogen, elixir-format msgid "Sources *.sol files" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:63 #: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:63 #, elixir-autogen, elixir-format msgid "Sources *.sol or *.yul files" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:14 #: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:14 #, elixir-autogen, elixir-format msgid "Sources and Metadata JSON" msgstr "" -#: lib/block_scout_web/templates/layout/_topnav.html.eex:136 #: lib/block_scout_web/templates/layout/_topnav.html.eex:136 #, elixir-autogen, elixir-format msgid "Stakes" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:24 #: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:24 #, elixir-autogen, elixir-format msgid "Standard Input JSON" msgstr "" -#: lib/block_scout_web/templates/transaction/_tabs.html.eex:29 #: lib/block_scout_web/templates/transaction/_tabs.html.eex:29 #: lib/block_scout_web/templates/transaction_state/index.html.eex:6 -#: lib/block_scout_web/templates/transaction_state/index.html.eex:6 -#: lib/block_scout_web/views/transaction_view.ex:555 -#: lib/block_scout_web/views/transaction_view.ex:555 +#: lib/block_scout_web/views/transaction_view.ex:553 #, elixir-autogen, elixir-format msgid "State changes" msgstr "" -#: lib/block_scout_web/views/internal_transaction_view.ex:24 -#: lib/block_scout_web/views/internal_transaction_view.ex:24 +#: lib/block_scout_web/views/internal_transaction_view.ex:26 #, elixir-autogen, elixir-format msgid "Static Call" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:110 #: lib/block_scout_web/templates/transaction/overview.html.eex:110 #, elixir-autogen, elixir-format msgid "Status" msgstr "" -#: lib/block_scout_web/templates/layout/_footer.html.eex:41 #: lib/block_scout_web/templates/layout/_footer.html.eex:41 #, elixir-autogen, elixir-format msgid "Submit an Issue" msgstr "" #: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:8 -#: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:8 -#: lib/block_scout_web/views/transaction_view.ex:376 -#: lib/block_scout_web/views/transaction_view.ex:376 +#: lib/block_scout_web/views/transaction_view.ex:374 #, elixir-autogen, elixir-format msgid "Success" msgstr "" -#: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:21 #: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:21 #: lib/block_scout_web/templates/transaction/_tile.html.eex:52 -#: lib/block_scout_web/templates/transaction/_tile.html.eex:52 #, elixir-autogen, elixir-format msgid "TX Fee" msgstr "" -#: lib/block_scout_web/templates/layout/_footer.html.eex:31 #: lib/block_scout_web/templates/layout/_footer.html.eex:31 #, elixir-autogen, elixir-format msgid "Telegram" msgstr "" -#: lib/block_scout_web/templates/layout/_footer.html.eex:67 #: lib/block_scout_web/templates/layout/_footer.html.eex:67 #, elixir-autogen, elixir-format msgid "Test Networks" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_first.html.eex:9 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_library_first.html.eex:9 #, elixir-autogen, elixir-format msgid "The 0x library address. This can be found in the generated json file or Truffle output (if using truffle)." msgstr "" #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:34 -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:34 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:26 #: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:26 #, elixir-autogen, elixir-format msgid "The EVM version the contract is written for. If the bytecode does not match the version, we try to verify using the latest EVM version." msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:122 #: lib/block_scout_web/templates/block/overview.html.eex:122 #, elixir-autogen, elixir-format msgid "The SHA256 hash of the block." msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:51 #: lib/block_scout_web/templates/block/overview.html.eex:51 #, elixir-autogen, elixir-format msgid "The block height of a particular block is defined as the number of blocks preceding it in the blockchain." msgstr "" -#: lib/block_scout_web/templates/transaction_state/index.html.eex:18 #: lib/block_scout_web/templates/transaction_state/index.html.eex:18 #, elixir-autogen, elixir-format msgid "The changes from this transaction have not yet happened since the transaction is still pending." msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:26 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:26 #: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:18 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:18 #, elixir-autogen, elixir-format msgid "The compiler version is specified in pragma solidity X.X.X. Use the compiler version rather than the nightly build. If using the Solidity compiler, run solc —version to check." msgstr "" -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:44 #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:44 #, elixir-autogen, elixir-format msgid "The fallback function is executed on a call to the contract if none of the other functions match the given function signature, or if no data was supplied at all and there is no receive Ether function. The fallback function always receives data, but in order to also receive Ether it must be marked payable." msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:138 #: lib/block_scout_web/templates/block/overview.html.eex:138 #, elixir-autogen, elixir-format msgid "The hash of the block from which this block was generated." msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:74 #: lib/block_scout_web/templates/address/overview.html.eex:74 #, elixir-autogen, elixir-format msgid "The name found in the source code of the Contract." msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:85 #: lib/block_scout_web/templates/address/overview.html.eex:85 #, elixir-autogen, elixir-format msgid "The name of the validator." msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:79 #: lib/block_scout_web/templates/block/overview.html.eex:79 #, elixir-autogen, elixir-format msgid "The number of transactions in the block." msgstr "" -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:46 #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:46 #, elixir-autogen, elixir-format msgid "The receive function is executed on a call to the contract with empty calldata. This is the function that is executed on plain Ether transfers (e.g. via .send() or .transfer()). If no such function exists, but a payable fallback function exists, the fallback function will be called on a plain Ether transfer. If neither a receive Ether nor a payable fallback function is present, the contract cannot receive Ether through regular transactions and throws an exception." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:137 #: lib/block_scout_web/templates/transaction/overview.html.eex:137 #, elixir-autogen, elixir-format msgid "The revert reason of the transaction." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:109 #: lib/block_scout_web/templates/transaction/overview.html.eex:109 #, elixir-autogen, elixir-format msgid "The status of the transaction: Confirmed or Unconfirmed." msgstr "" -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:67 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:67 #, elixir-autogen, elixir-format msgid "The total amount of tokens issued" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:179 #: lib/block_scout_web/templates/block/overview.html.eex:179 #, elixir-autogen, elixir-format msgid "The total gas amount used in the block and its percentage of gas filled in the block." msgstr "" -#: lib/block_scout_web/templates/address_validation/index.html.eex:16 #: lib/block_scout_web/templates/address_validation/index.html.eex:16 #, elixir-autogen, elixir-format msgid "There are no blocks validated by this address." msgstr "" -#: lib/block_scout_web/templates/block/index.html.eex:17 #: lib/block_scout_web/templates/block/index.html.eex:17 #, elixir-autogen, elixir-format msgid "There are no blocks." msgstr "" -#: lib/block_scout_web/templates/tokens/holder/index.html.eex:29 #: lib/block_scout_web/templates/tokens/holder/index.html.eex:29 #, elixir-autogen, elixir-format msgid "There are no holders for this Token." msgstr "" -#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:54 #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:54 #, elixir-autogen, elixir-format msgid "There are no internal transactions for this address." msgstr "" -#: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:17 #: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:17 #, elixir-autogen, elixir-format msgid "There are no internal transactions for this transaction." msgstr "" -#: lib/block_scout_web/templates/address_logs/index.html.eex:28 #: lib/block_scout_web/templates/address_logs/index.html.eex:28 #, elixir-autogen, elixir-format msgid "There are no logs for this address." msgstr "" -#: lib/block_scout_web/templates/transaction_log/index.html.eex:20 #: lib/block_scout_web/templates/transaction_log/index.html.eex:20 #, elixir-autogen, elixir-format msgid "There are no logs for this transaction." msgstr "" -#: lib/block_scout_web/templates/pending_transaction/index.html.eex:22 #: lib/block_scout_web/templates/pending_transaction/index.html.eex:22 #, elixir-autogen, elixir-format msgid "There are no pending transactions." msgstr "" -#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:53 #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:53 #, elixir-autogen, elixir-format msgid "There are no token transfers for this address." msgstr "" -#: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:19 #: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:19 #, elixir-autogen, elixir-format msgid "There are no token transfers for this transaction" msgstr "" -#: lib/block_scout_web/templates/address_token/index.html.eex:65 #: lib/block_scout_web/templates/address_token/index.html.eex:65 #, elixir-autogen, elixir-format msgid "There are no tokens for this address." msgstr "" -#: lib/block_scout_web/templates/tokens/inventory/index.html.eex:28 #: lib/block_scout_web/templates/tokens/inventory/index.html.eex:28 #, elixir-autogen, elixir-format msgid "There are no tokens." msgstr "" -#: lib/block_scout_web/templates/address_transaction/index.html.eex:55 #: lib/block_scout_web/templates/address_transaction/index.html.eex:55 #, elixir-autogen, elixir-format msgid "There are no transactions for this address." msgstr "" -#: lib/block_scout_web/templates/block_transaction/index.html.eex:19 #: lib/block_scout_web/templates/block_transaction/index.html.eex:19 #, elixir-autogen, elixir-format msgid "There are no transactions for this block." msgstr "" -#: lib/block_scout_web/templates/transaction/index.html.eex:31 #: lib/block_scout_web/templates/transaction/index.html.eex:31 #, elixir-autogen, elixir-format msgid "There are no transactions." msgstr "" #: lib/block_scout_web/templates/tokens/instance/holder/index.html.eex:28 -#: lib/block_scout_web/templates/tokens/instance/holder/index.html.eex:28 -#: lib/block_scout_web/templates/tokens/instance/transfer/index.html.eex:28 #: lib/block_scout_web/templates/tokens/instance/transfer/index.html.eex:28 #: lib/block_scout_web/templates/tokens/transfer/index.html.eex:27 -#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:27 #, elixir-autogen, elixir-format msgid "There are no transfers for this Token." msgstr "" -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:99 #: lib/block_scout_web/templates/verified_contracts/index.html.eex:99 #, elixir-autogen, elixir-format msgid "There are no verified contracts." msgstr "" -#: lib/block_scout_web/templates/address_withdrawal/index.html.eex:54 #: lib/block_scout_web/templates/address_withdrawal/index.html.eex:54 #, elixir-autogen, elixir-format msgid "There are no withdrawals for this address." msgstr "" -#: lib/block_scout_web/templates/block_withdrawal/index.html.eex:45 #: lib/block_scout_web/templates/block_withdrawal/index.html.eex:45 #, elixir-autogen, elixir-format msgid "There are no withdrawals for this block." msgstr "" -#: lib/block_scout_web/templates/withdrawal/index.html.eex:53 #: lib/block_scout_web/templates/withdrawal/index.html.eex:53 #, elixir-autogen, elixir-format msgid "There are no withdrawals." msgstr "" -#: lib/block_scout_web/templates/address_coin_balance/index.html.eex:35 #: lib/block_scout_web/templates/address_coin_balance/index.html.eex:35 #, elixir-autogen, elixir-format msgid "There is no coin history for this address." msgstr "" #: lib/block_scout_web/templates/address_coin_balance/index.html.eex:21 -#: lib/block_scout_web/templates/address_coin_balance/index.html.eex:21 -#: lib/block_scout_web/templates/chain/show.html.eex:9 #: lib/block_scout_web/templates/chain/show.html.eex:9 #, elixir-autogen, elixir-format msgid "There was a problem loading the chart." msgstr "" -#: lib/block_scout_web/templates/api_docs/index.html.eex:6 #: lib/block_scout_web/templates/api_docs/index.html.eex:6 #, elixir-autogen, elixir-format msgid "This API is provided for developers transitioning their applications from Etherscan to BlockScout. It supports GET and POST requests." msgstr "" -#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:7 #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:7 #, elixir-autogen, elixir-format msgid "This API is provided to support some rpc methods in the exact format specified for ethereum nodes, which can be found " msgstr "" -#: lib/block_scout_web/views/block_transaction_view.ex:11 -#: lib/block_scout_web/views/block_transaction_view.ex:11 +#: lib/block_scout_web/views/block_transaction_view.ex:15 #, elixir-autogen, elixir-format msgid "This block has not been processed yet." msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:47 #: lib/block_scout_web/templates/address_contract/index.html.eex:47 #, elixir-autogen, elixir-format msgid "This contract has been partially verified via Sourcify." msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:51 #: lib/block_scout_web/templates/address_contract/index.html.eex:51 #, elixir-autogen, elixir-format msgid "This contract has been verified via Sourcify." msgstr "" -#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:10 #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:10 #, elixir-autogen, elixir-format msgid "This is useful to allow sending requests to blockscout without having to change anything about the request." msgstr "" -#: lib/block_scout_web/templates/page_not_found/index.html.eex:8 #: lib/block_scout_web/templates/page_not_found/index.html.eex:8 #, elixir-autogen, elixir-format msgid "This page is no longer explorable! If you are lost, use the search bar to find what you are looking for." msgstr "" -#: lib/block_scout_web/templates/transaction_state/index.html.eex:22 #: lib/block_scout_web/templates/transaction_state/index.html.eex:22 #, elixir-autogen, elixir-format msgid "This transaction hasn't changed state." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:64 #: lib/block_scout_web/templates/transaction/overview.html.eex:64 #, elixir-autogen, elixir-format msgid "This transaction is pending confirmation." msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:71 #: lib/block_scout_web/templates/block/overview.html.eex:71 #: lib/block_scout_web/templates/transaction/overview.html.eex:180 -#: lib/block_scout_web/templates/transaction/overview.html.eex:180 #, elixir-autogen, elixir-format msgid "Timestamp" msgstr "" -#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:32 #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:32 #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:34 -#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:34 -#: lib/block_scout_web/templates/address_transaction/index.html.eex:28 #: lib/block_scout_web/templates/address_transaction/index.html.eex:28 #: lib/block_scout_web/templates/block_withdrawal/index.html.eex:29 -#: lib/block_scout_web/templates/block_withdrawal/index.html.eex:29 -#: lib/block_scout_web/templates/transaction/overview.html.eex:267 -#: lib/block_scout_web/templates/transaction/overview.html.eex:267 -#: lib/block_scout_web/templates/withdrawal/index.html.eex:32 +#: lib/block_scout_web/templates/transaction/overview.html.eex:244 #: lib/block_scout_web/templates/withdrawal/index.html.eex:32 #: lib/block_scout_web/views/address_internal_transaction_view.ex:10 -#: lib/block_scout_web/views/address_internal_transaction_view.ex:10 -#: lib/block_scout_web/views/address_token_transfer_view.ex:10 #: lib/block_scout_web/views/address_token_transfer_view.ex:10 #: lib/block_scout_web/views/address_transaction_view.ex:10 -#: lib/block_scout_web/views/address_transaction_view.ex:10 #, elixir-autogen, elixir-format msgid "To" msgstr "" -#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:20 #: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:20 #, elixir-autogen, elixir-format msgid "To have guaranteed accuracy, use the link above to verify the contract's source code." msgstr "" #: lib/block_scout_web/templates/address_logs/_logs.html.eex:6 -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:6 -#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:8 #: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:8 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:6 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:6 #, elixir-autogen, elixir-format msgid "To see accurate decoded input data, the contract must be verified." msgstr "" -#: lib/block_scout_web/templates/layout/_topnav.html.eex:18 #: lib/block_scout_web/templates/layout/_topnav.html.eex:18 #, elixir-autogen, elixir-format msgid "Toggle navigation" msgstr "" #: lib/block_scout_web/templates/address/overview.html.eex:55 -#: lib/block_scout_web/templates/address/overview.html.eex:55 -#: lib/block_scout_web/templates/tokens/index.html.eex:31 #: lib/block_scout_web/templates/tokens/index.html.eex:31 #, elixir-autogen, elixir-format msgid "Token" msgstr "" #: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:3 -#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:3 -#: lib/block_scout_web/views/transaction_view.ex:488 -#: lib/block_scout_web/views/transaction_view.ex:488 +#: lib/block_scout_web/views/transaction_view.ex:486 #, elixir-autogen, elixir-format msgid "Token Burning" msgstr "" #: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:7 -#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:7 -#: lib/block_scout_web/views/transaction_view.ex:489 -#: lib/block_scout_web/views/transaction_view.ex:489 +#: lib/block_scout_web/views/transaction_view.ex:487 #, elixir-autogen, elixir-format msgid "Token Creation" msgstr "" -#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:10 #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:10 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:34 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:34 #, elixir-autogen, elixir-format msgid "Token Details" msgstr "" -#: lib/block_scout_web/templates/tokens/holder/index.html.eex:17 #: lib/block_scout_web/templates/tokens/holder/index.html.eex:17 #: lib/block_scout_web/templates/tokens/instance/holder/index.html.eex:16 -#: lib/block_scout_web/templates/tokens/instance/holder/index.html.eex:16 -#: lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:17 #: lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:17 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:11 -#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:11 -#: lib/block_scout_web/views/tokens/overview_view.ex:40 #: lib/block_scout_web/views/tokens/overview_view.ex:40 #, elixir-autogen, elixir-format msgid "Token Holders" msgstr "" #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:38 -#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:38 -#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:18 #: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:18 #: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:37 -#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:37 #, elixir-autogen, elixir-format msgid "Token ID" msgstr "" #: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:5 -#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:5 -#: lib/block_scout_web/views/transaction_view.ex:487 -#: lib/block_scout_web/views/transaction_view.ex:487 +#: lib/block_scout_web/views/transaction_view.ex:485 #, elixir-autogen, elixir-format msgid "Token Minting" msgstr "" #: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:9 -#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:9 -#: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:11 #: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:11 -#: lib/block_scout_web/views/transaction_view.ex:490 -#: lib/block_scout_web/views/transaction_view.ex:490 +#: lib/block_scout_web/views/transaction_view.ex:488 #, elixir-autogen, elixir-format msgid "Token Transfer" msgstr "" -#: lib/block_scout_web/templates/address/_tabs.html.eex:13 #: lib/block_scout_web/templates/address/_tabs.html.eex:13 #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:19 -#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:19 -#: lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:3 #: lib/block_scout_web/templates/tokens/instance/overview/_tabs.html.eex:3 #: lib/block_scout_web/templates/tokens/instance/transfer/index.html.eex:16 -#: lib/block_scout_web/templates/tokens/instance/transfer/index.html.eex:16 -#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:5 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:5 #: lib/block_scout_web/templates/tokens/transfer/index.html.eex:15 -#: lib/block_scout_web/templates/tokens/transfer/index.html.eex:15 -#: lib/block_scout_web/templates/transaction/_tabs.html.eex:4 #: lib/block_scout_web/templates/transaction/_tabs.html.eex:4 #: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:7 -#: lib/block_scout_web/templates/transaction_token_transfer/index.html.eex:7 -#: lib/block_scout_web/views/address_view.ex:355 #: lib/block_scout_web/views/address_view.ex:355 #: lib/block_scout_web/views/tokens/instance/overview_view.ex:74 -#: lib/block_scout_web/views/tokens/instance/overview_view.ex:74 -#: lib/block_scout_web/views/tokens/overview_view.ex:39 #: lib/block_scout_web/views/tokens/overview_view.ex:39 -#: lib/block_scout_web/views/transaction_view.ex:551 -#: lib/block_scout_web/views/transaction_view.ex:551 +#: lib/block_scout_web/views/transaction_view.ex:549 #, elixir-autogen, elixir-format msgid "Token Transfers" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:54 #: lib/block_scout_web/templates/address/overview.html.eex:54 #, elixir-autogen, elixir-format msgid "Token name and symbol." msgstr "" -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:142 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:142 #, elixir-autogen, elixir-format msgid "Token type" msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:21 -#: lib/block_scout_web/templates/address/_tabs.html.eex:21 -#: lib/block_scout_web/templates/address/overview.html.eex:177 #: lib/block_scout_web/templates/address/overview.html.eex:177 #: lib/block_scout_web/templates/address_token/overview.html.eex:58 -#: lib/block_scout_web/templates/address_token/overview.html.eex:58 -#: lib/block_scout_web/templates/address_token_transfer/index.html.eex:13 #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:13 #: lib/block_scout_web/templates/layout/_topnav.html.eex:84 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:84 -#: lib/block_scout_web/templates/tokens/index.html.eex:10 #: lib/block_scout_web/templates/tokens/index.html.eex:10 #: lib/block_scout_web/views/address_view.ex:352 -#: lib/block_scout_web/views/address_view.ex:352 #, elixir-autogen, elixir-format msgid "Tokens" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:336 -#: lib/block_scout_web/templates/transaction/overview.html.eex:336 +#: lib/block_scout_web/templates/transaction/overview.html.eex:313 #, elixir-autogen, elixir-format msgid "Tokens Burnt" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:352 -#: lib/block_scout_web/templates/transaction/overview.html.eex:352 +#: lib/block_scout_web/templates/transaction/overview.html.eex:329 #, elixir-autogen, elixir-format msgid "Tokens Created" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:319 -#: lib/block_scout_web/templates/transaction/overview.html.eex:319 +#: lib/block_scout_web/templates/transaction/overview.html.eex:296 #, elixir-autogen, elixir-format msgid "Tokens Minted" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:303 -#: lib/block_scout_web/templates/transaction/overview.html.eex:303 +#: lib/block_scout_web/templates/transaction/overview.html.eex:280 #, elixir-autogen, elixir-format msgid "Tokens Transferred" msgstr "" -#: lib/block_scout_web/templates/address/_metatags.html.eex:13 #: lib/block_scout_web/templates/address/_metatags.html.eex:13 #, elixir-autogen, elixir-format msgid "Top Accounts - %{subnetwork} Explorer" msgstr "" -#: lib/block_scout_web/templates/address_logs/index.html.eex:14 #: lib/block_scout_web/templates/address_logs/index.html.eex:14 #, elixir-autogen, elixir-format msgid "Topic" msgstr "" #: lib/block_scout_web/templates/address_logs/_logs.html.eex:68 -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:68 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:100 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:100 #, elixir-autogen, elixir-format msgid "Topics" msgstr "" #: lib/block_scout_web/templates/verified_contracts/_stats.html.eex:9 -#: lib/block_scout_web/templates/verified_contracts/_stats.html.eex:9 -#: lib/block_scout_web/templates/verified_contracts/_stats.html.eex:22 #: lib/block_scout_web/templates/verified_contracts/_stats.html.eex:22 #, elixir-autogen, elixir-format msgid "Total" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:170 #: lib/block_scout_web/templates/block/overview.html.eex:170 #, elixir-autogen, elixir-format msgid "Total Difficulty" msgstr "" -#: lib/block_scout_web/templates/tokens/index.html.eex:43 #: lib/block_scout_web/templates/tokens/index.html.eex:43 #, elixir-autogen, elixir-format msgid "Total Supply" msgstr "" -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:84 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:84 #, elixir-autogen, elixir-format msgid "Total Supply * Price" msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:133 -#: lib/block_scout_web/templates/chain/show.html.eex:133 +#: lib/block_scout_web/templates/chain/show.html.eex:135 #, elixir-autogen, elixir-format msgid "Total blocks" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:169 #: lib/block_scout_web/templates/block/overview.html.eex:169 #, elixir-autogen, elixir-format msgid "Total difficulty of the chain until this block." msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:188 #: lib/block_scout_web/templates/block/overview.html.eex:188 #, elixir-autogen, elixir-format msgid "Total gas limit provided by all transactions in the block." msgstr "" -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:68 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:68 #, elixir-autogen, elixir-format msgid "Total supply" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:383 -#: lib/block_scout_web/templates/transaction/overview.html.eex:383 +#: lib/block_scout_web/templates/transaction/overview.html.eex:360 #, elixir-autogen, elixir-format msgid "Total transaction fee." msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:112 -#: lib/block_scout_web/templates/chain/show.html.eex:112 +#: lib/block_scout_web/templates/chain/show.html.eex:114 #, elixir-autogen, elixir-format msgid "Total transactions" msgstr "" #: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:11 -#: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:11 -#: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:23 #: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:23 #: lib/block_scout_web/templates/address_logs/_logs.html.eex:19 -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:19 -#: lib/block_scout_web/views/transaction_view.ex:500 -#: lib/block_scout_web/views/transaction_view.ex:500 +#: lib/block_scout_web/views/transaction_view.ex:498 #, elixir-autogen, elixir-format msgid "Transaction" msgstr "" -#: lib/block_scout_web/templates/transaction/_metatags.html.eex:3 #: lib/block_scout_web/templates/transaction/_metatags.html.eex:3 #, elixir-autogen, elixir-format msgid "Transaction %{transaction} - %{subnetwork} Explorer" msgstr "" -#: lib/block_scout_web/templates/transaction/_metatags.html.eex:11 #: lib/block_scout_web/templates/transaction/_metatags.html.eex:11 #, elixir-autogen, elixir-format msgid "Transaction %{transaction}, %{subnetwork} %{transaction}" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:225 -#: lib/block_scout_web/templates/transaction/overview.html.eex:225 -#, elixir-autogen, elixir-format, fuzzy -msgid "Transaction Action" -msgstr "" - -#: lib/block_scout_web/templates/transaction/overview.html.eex:470 -#: lib/block_scout_web/templates/transaction/overview.html.eex:470 +#: lib/block_scout_web/templates/transaction/overview.html.eex:447 #, elixir-autogen, elixir-format msgid "Transaction Burnt Fee" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:50 #: lib/block_scout_web/templates/transaction/overview.html.eex:50 #, elixir-autogen, elixir-format msgid "Transaction Details" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:384 -#: lib/block_scout_web/templates/transaction/overview.html.eex:384 +#: lib/block_scout_web/templates/transaction/overview.html.eex:361 #, elixir-autogen, elixir-format msgid "Transaction Fee" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:80 #: lib/block_scout_web/templates/transaction/overview.html.eex:80 #, elixir-autogen, elixir-format msgid "Transaction Hash" msgstr "" #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:2 -#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:2 -#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:19 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:19 #, elixir-autogen, elixir-format msgid "Transaction Inputs" msgstr "" #: lib/block_scout_web/templates/account/common/_nav.html.eex:13 -#: lib/block_scout_web/templates/account/common/_nav.html.eex:13 -#: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:7 #: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:7 #: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:17 -#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:17 #, elixir-autogen, elixir-format msgid "Transaction Tags" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:414 -#: lib/block_scout_web/templates/transaction/overview.html.eex:414 +#: lib/block_scout_web/templates/transaction/overview.html.eex:391 #, elixir-autogen, elixir-format msgid "Transaction Type" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:534 -#: lib/block_scout_web/templates/transaction/overview.html.eex:534 +#: lib/block_scout_web/templates/transaction/overview.html.eex:511 #, elixir-autogen, elixir-format msgid "Transaction number from the sending address. Each transaction sent from an address increments the nonce by 1." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:413 -#: lib/block_scout_web/templates/transaction/overview.html.eex:413 +#: lib/block_scout_web/templates/transaction/overview.html.eex:390 #, elixir-autogen, elixir-format msgid "Transaction type, introduced in EIP-2718." msgstr "" -#: lib/block_scout_web/templates/address/_tabs.html.eex:7 #: lib/block_scout_web/templates/address/_tabs.html.eex:7 #: lib/block_scout_web/templates/address/_tile.html.eex:31 -#: lib/block_scout_web/templates/address/_tile.html.eex:31 -#: lib/block_scout_web/templates/address/overview.html.eex:188 #: lib/block_scout_web/templates/address/overview.html.eex:188 #: lib/block_scout_web/templates/address/overview.html.eex:194 -#: lib/block_scout_web/templates/address/overview.html.eex:194 -#: lib/block_scout_web/templates/address/overview.html.eex:202 #: lib/block_scout_web/templates/address/overview.html.eex:202 #: lib/block_scout_web/templates/address_transaction/index.html.eex:13 -#: lib/block_scout_web/templates/address_transaction/index.html.eex:13 -#: lib/block_scout_web/templates/block/_tabs.html.eex:4 #: lib/block_scout_web/templates/block/_tabs.html.eex:4 #: lib/block_scout_web/templates/block/overview.html.eex:80 -#: lib/block_scout_web/templates/block/overview.html.eex:80 -#: lib/block_scout_web/templates/chain/show.html.eex:216 -#: lib/block_scout_web/templates/chain/show.html.eex:216 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:49 +#: lib/block_scout_web/templates/chain/show.html.eex:218 #: lib/block_scout_web/templates/layout/_topnav.html.eex:49 #: lib/block_scout_web/views/address_view.ex:354 -#: lib/block_scout_web/views/address_view.ex:354 #, elixir-autogen, elixir-format msgid "Transactions" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:101 #: lib/block_scout_web/templates/address/overview.html.eex:101 #, elixir-autogen, elixir-format msgid "Transactions and address of creation." msgstr "" #: lib/block_scout_web/templates/address/overview.html.eex:215 -#: lib/block_scout_web/templates/address/overview.html.eex:215 -#: lib/block_scout_web/templates/address/overview.html.eex:221 #: lib/block_scout_web/templates/address/overview.html.eex:221 #: lib/block_scout_web/templates/address/overview.html.eex:229 -#: lib/block_scout_web/templates/address/overview.html.eex:229 -#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:50 #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:50 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:119 -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:119 #, elixir-autogen, elixir-format msgid "Transfers" msgstr "" -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:40 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:40 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:47 -#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:47 #, elixir-autogen, elixir-format msgid "Try it out" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_fetch_constructor_args.html.eex:3 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_fetch_constructor_args.html.eex:3 #, elixir-autogen, elixir-format msgid "Try to fetch constructor arguments automatically" msgstr "" -#: lib/block_scout_web/templates/layout/_footer.html.eex:27 #: lib/block_scout_web/templates/layout/_footer.html.eex:27 #, elixir-autogen, elixir-format msgid "Twitter" msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:53 #: lib/block_scout_web/templates/layout/app.html.eex:53 #, elixir-autogen, elixir-format msgid "Tx/day" msgstr "" -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:66 #: lib/block_scout_web/templates/verified_contracts/index.html.eex:66 #, elixir-autogen, elixir-format msgid "Txns" msgstr "" -#: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:5 #: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:5 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:22 -#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:22 #, elixir-autogen, elixir-format msgid "Type" msgstr "" -#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:141 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:141 #, elixir-autogen, elixir-format msgid "Type of the token standard" msgstr "" -#: lib/block_scout_web/templates/visualize_sol2uml/index.html.eex:5 #: lib/block_scout_web/templates/visualize_sol2uml/index.html.eex:5 #, elixir-autogen, elixir-format msgid "UML diagram" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:560 -#: lib/block_scout_web/templates/transaction/overview.html.eex:560 +#: lib/block_scout_web/templates/transaction/overview.html.eex:537 #, elixir-autogen, elixir-format msgid "UTF-8" msgstr "" -#: lib/block_scout_web/views/block_view.ex:77 #: lib/block_scout_web/views/block_view.ex:77 #, elixir-autogen, elixir-format msgid "Uncle Reward" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:252 #: lib/block_scout_web/templates/block/overview.html.eex:252 #: lib/block_scout_web/templates/layout/_topnav.html.eex:41 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:41 #, elixir-autogen, elixir-format msgid "Uncles" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:367 -#: lib/block_scout_web/views/transaction_view.ex:367 +#: lib/block_scout_web/views/transaction_view.ex:365 #, elixir-autogen, elixir-format msgid "Unconfirmed" msgstr "" -#: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:9 #: lib/block_scout_web/templates/tokens/inventory/_token.html.eex:9 #, elixir-autogen, elixir-format msgid "Unique Token" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:79 #: lib/block_scout_web/templates/transaction/overview.html.eex:79 #, elixir-autogen, elixir-format msgid "Unique character string (TxID) assigned to every verified transaction." msgstr "" -#: lib/block_scout_web/templates/account/api_key/form.html.eex:7 #: lib/block_scout_web/templates/account/api_key/form.html.eex:7 #: lib/block_scout_web/templates/account/custom_abi/form.html.eex:8 -#: lib/block_scout_web/templates/account/custom_abi/form.html.eex:8 #, elixir-autogen, elixir-format msgid "Update" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:449 -#: lib/block_scout_web/templates/transaction/overview.html.eex:449 +#: lib/block_scout_web/templates/transaction/overview.html.eex:426 #, elixir-autogen, elixir-format msgid "User defined maximum fee (tip) per unit of gas paid to validator for transaction prioritization." msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:459 -#: lib/block_scout_web/templates/transaction/overview.html.eex:459 +#: lib/block_scout_web/templates/transaction/overview.html.eex:436 #, elixir-autogen, elixir-format msgid "User-defined tip sent to validator for transaction priority/inclusion." msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:226 #: lib/block_scout_web/templates/block/overview.html.eex:226 #, elixir-autogen, elixir-format msgid "User-defined tips sent to validator for transaction priority/inclusion." msgstr "" -#: lib/block_scout_web/templates/layout/_topnav.html.eex:56 #: lib/block_scout_web/templates/layout/_topnav.html.eex:56 #, elixir-autogen, elixir-format msgid "Validated" msgstr "" -#: lib/block_scout_web/templates/transaction/index.html.eex:12 #: lib/block_scout_web/templates/transaction/index.html.eex:12 #, elixir-autogen, elixir-format msgid "Validated Transactions" msgstr "" -#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:30 #: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:30 #, elixir-autogen, elixir-format msgid "Validator Creation Date" msgstr "" -#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:5 #: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:5 #, elixir-autogen, elixir-format msgid "Validator Data" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:86 #: lib/block_scout_web/templates/address/overview.html.eex:86 #, elixir-autogen, elixir-format msgid "Validator Name" msgstr "" #: lib/block_scout_web/templates/address_withdrawal/index.html.eex:32 -#: lib/block_scout_web/templates/address_withdrawal/index.html.eex:32 -#: lib/block_scout_web/templates/block_withdrawal/index.html.eex:26 #: lib/block_scout_web/templates/block_withdrawal/index.html.eex:26 #: lib/block_scout_web/templates/withdrawal/index.html.eex:26 -#: lib/block_scout_web/templates/withdrawal/index.html.eex:26 #, elixir-autogen, elixir-format, fuzzy msgid "Validator index" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:369 -#: lib/block_scout_web/templates/transaction/overview.html.eex:369 +#: lib/block_scout_web/templates/transaction/overview.html.eex:346 #, elixir-autogen, elixir-format msgid "Value" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:368 -#: lib/block_scout_web/templates/transaction/overview.html.eex:368 +#: lib/block_scout_web/templates/transaction/overview.html.eex:345 #, elixir-autogen, elixir-format msgid "Value sent in the native token (and USD) if applicable." msgstr "" -#: lib/block_scout_web/templates/address_read_contract/index.html.eex:17 #: lib/block_scout_web/templates/address_read_contract/index.html.eex:17 #: lib/block_scout_web/templates/address_write_contract/index.html.eex:15 -#: lib/block_scout_web/templates/address_write_contract/index.html.eex:15 -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:81 #: lib/block_scout_web/templates/verified_contracts/index.html.eex:81 #, elixir-autogen, elixir-format msgid "Verified" msgstr "" #: lib/block_scout_web/templates/verified_contracts/_stats.html.eex:18 -#: lib/block_scout_web/templates/verified_contracts/_stats.html.eex:18 -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:6 #: lib/block_scout_web/templates/verified_contracts/index.html.eex:6 #, elixir-autogen, elixir-format msgid "Verified Contracts" msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:88 #: lib/block_scout_web/templates/address_contract/index.html.eex:88 #, elixir-autogen, elixir-format msgid "Verified at" msgstr "" -#: lib/block_scout_web/templates/layout/_topnav.html.eex:69 #: lib/block_scout_web/templates/layout/_topnav.html.eex:69 #, elixir-autogen, elixir-format msgid "Verified contracts" msgstr "" -#: lib/block_scout_web/templates/verified_contracts/_metatags.html.eex:2 #: lib/block_scout_web/templates/verified_contracts/_metatags.html.eex:2 #, elixir-autogen, elixir-format msgid "Verified contracts - %{subnetwork} Explorer" msgstr "" -#: lib/block_scout_web/templates/verified_contracts/_metatags.html.eex:7 #: lib/block_scout_web/templates/verified_contracts/_metatags.html.eex:7 #, elixir-autogen, elixir-format msgid "Verified contracts, %{subnetwork}, %{coin}" msgstr "" #: lib/block_scout_web/templates/address_contract/index.html.eex:27 -#: lib/block_scout_web/templates/address_contract/index.html.eex:27 -#: lib/block_scout_web/templates/address_contract/index.html.eex:29 #: lib/block_scout_web/templates/address_contract/index.html.eex:29 #: lib/block_scout_web/templates/address_contract/index.html.eex:197 -#: lib/block_scout_web/templates/address_contract/index.html.eex:197 -#: lib/block_scout_web/templates/address_contract/index.html.eex:228 #: lib/block_scout_web/templates/address_contract/index.html.eex:228 #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:14 -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:14 #, elixir-autogen, elixir-format msgid "Verify & Publish" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:111 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:111 #: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:37 -#: lib/block_scout_web/templates/address_contract_verification_via_json/new.html.eex:37 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:103 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:103 -#: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:51 +#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:101 #: lib/block_scout_web/templates/address_contract_verification_via_standard_json_input/new.html.eex:51 #: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:47 -#: lib/block_scout_web/templates/address_contract_verification_vyper/new.html.eex:47 #, elixir-autogen, elixir-format msgid "Verify & publish" msgstr "" -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:10 #: lib/block_scout_web/templates/address_logs/_logs.html.eex:10 #: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:12 -#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:12 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:10 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:10 #, elixir-autogen, elixir-format msgid "Verify the contract " msgstr "" #: lib/block_scout_web/templates/layout/_footer.html.eex:93 -#: lib/block_scout_web/templates/layout/_footer.html.eex:93 -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:72 #: lib/block_scout_web/templates/verified_contracts/index.html.eex:72 #, elixir-autogen, elixir-format msgid "Version" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:33 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:33 #, elixir-autogen, elixir-format msgid "Via Sourcify: Sources and metadata JSON file" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:27 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:27 #, elixir-autogen, elixir-format msgid "Via Standard Input JSON" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:22 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:22 #, elixir-autogen, elixir-format msgid "Via flattened source code" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:40 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:40 #, elixir-autogen, elixir-format msgid "Via multi-part files" msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:155 -#: lib/block_scout_web/templates/chain/show.html.eex:155 +#: lib/block_scout_web/templates/chain/show.html.eex:157 #, elixir-autogen, elixir-format msgid "View All Blocks" msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:215 -#: lib/block_scout_web/templates/chain/show.html.eex:215 +#: lib/block_scout_web/templates/chain/show.html.eex:217 #, elixir-autogen, elixir-format msgid "View All Transactions" msgstr "" #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:16 -#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:16 -#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:20 #: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:20 #, elixir-autogen, elixir-format msgid "View Contract" msgstr "" -#: lib/block_scout_web/templates/transaction/_tile.html.eex:73 #: lib/block_scout_web/templates/transaction/_tile.html.eex:73 #, elixir-autogen, elixir-format msgid "View Less Transfers" msgstr "" -#: lib/block_scout_web/templates/transaction/_tile.html.eex:72 #: lib/block_scout_web/templates/transaction/_tile.html.eex:72 #, elixir-autogen, elixir-format msgid "View More Transfers" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:39 #: lib/block_scout_web/templates/block/overview.html.eex:39 #, elixir-autogen, elixir-format msgid "View next block" msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:23 #: lib/block_scout_web/templates/block/overview.html.eex:23 #, elixir-autogen, elixir-format msgid "View previous block" msgstr "" -#: lib/block_scout_web/templates/address/_metatags.html.eex:9 #: lib/block_scout_web/templates/address/_metatags.html.eex:9 #, elixir-autogen, elixir-format msgid "View the account balance, transactions, and other data for %{address} on the %{network}" msgstr "" -#: lib/block_scout_web/templates/withdrawal/_metatags.html.eex:8 #: lib/block_scout_web/templates/withdrawal/_metatags.html.eex:8 #, elixir-autogen, elixir-format msgid "View the beacon chain withdrawals on %{subnetwork}" msgstr "" -#: lib/block_scout_web/templates/block/_metatags.html.eex:10 #: lib/block_scout_web/templates/block/_metatags.html.eex:10 #, elixir-autogen, elixir-format msgid "View the transactions, token transfers, and uncles for block number %{block_number}" msgstr "" -#: lib/block_scout_web/templates/verified_contracts/_metatags.html.eex:8 #: lib/block_scout_web/templates/verified_contracts/_metatags.html.eex:8 #, elixir-autogen, elixir-format msgid "View the verified contracts on %{subnetwork}" msgstr "" -#: lib/block_scout_web/templates/transaction/_metatags.html.eex:10 #: lib/block_scout_web/templates/transaction/_metatags.html.eex:10 #, elixir-autogen, elixir-format msgid "View transaction %{transaction} on %{subnetwork}" msgstr "" -#: lib/block_scout_web/templates/verified_contracts/_contract.html.eex:28 -#: lib/block_scout_web/templates/verified_contracts/_contract.html.eex:28 -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:32 +#: lib/block_scout_web/templates/verified_contracts/_contract.html.eex:29 #: lib/block_scout_web/templates/verified_contracts/index.html.eex:32 #: lib/block_scout_web/views/verified_contracts_view.ex:11 -#: lib/block_scout_web/views/verified_contracts_view.ex:11 #, elixir-autogen, elixir-format msgid "Vyper" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:46 #: lib/block_scout_web/templates/address_contract_verification/new.html.eex:46 #, elixir-autogen, elixir-format msgid "Vyper contract" msgstr "" -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:142 #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:142 #, elixir-autogen, elixir-format msgid "WEI" msgstr "" -#: lib/block_scout_web/templates/smart_contract/_pending_contract_write.html.eex:9 #: lib/block_scout_web/templates/smart_contract/_pending_contract_write.html.eex:9 #, elixir-autogen, elixir-format msgid "Waiting for transaction's confirmation..." msgstr "" -#: lib/block_scout_web/templates/chain/show.html.eex:141 -#: lib/block_scout_web/templates/chain/show.html.eex:141 +#: lib/block_scout_web/templates/chain/show.html.eex:143 #, elixir-autogen, elixir-format msgid "Wallet addresses" msgstr "" -#: lib/block_scout_web/templates/common_components/_changed_bytecode_warning.html.eex:3 #: lib/block_scout_web/templates/common_components/_changed_bytecode_warning.html.eex:3 #, elixir-autogen, elixir-format msgid "Warning! Contract bytecode has been changed and doesn't match the verified one. Therefore, interaction with this smart contract may be risky." msgstr "" -#: lib/block_scout_web/templates/account/common/_nav.html.eex:7 #: lib/block_scout_web/templates/account/common/_nav.html.eex:7 #: lib/block_scout_web/templates/account/watchlist/show.html.eex:7 -#: lib/block_scout_web/templates/account/watchlist/show.html.eex:7 -#: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:15 #: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:15 #, elixir-autogen, elixir-format msgid "Watch list" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:77 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:77 #, elixir-autogen, elixir-format msgid "We recommend using flattened code. This is necessary if your code utilizes a library or inherits dependencies. Use the" msgstr "" -#: lib/block_scout_web/views/wei_helper.ex:80 #: lib/block_scout_web/views/wei_helper.ex:80 #, elixir-autogen, elixir-format msgid "Wei" msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:29 -#: lib/block_scout_web/templates/address/_tabs.html.eex:29 -#: lib/block_scout_web/templates/address_withdrawal/index.html.eex:13 #: lib/block_scout_web/templates/address_withdrawal/index.html.eex:13 #: lib/block_scout_web/templates/block/_tabs.html.eex:13 -#: lib/block_scout_web/templates/block/_tabs.html.eex:13 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:73 #: lib/block_scout_web/templates/layout/_topnav.html.eex:73 #: lib/block_scout_web/templates/withdrawal/index.html.eex:5 -#: lib/block_scout_web/templates/withdrawal/index.html.eex:5 #, elixir-autogen, elixir-format msgid "Withdrawals" msgstr "" -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:106 #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:106 #, elixir-autogen, elixir-format msgid "Write" msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:95 -#: lib/block_scout_web/templates/address/_tabs.html.eex:95 -#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:34 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:34 #: lib/block_scout_web/views/address_view.ex:359 -#: lib/block_scout_web/views/address_view.ex:359 #, elixir-autogen, elixir-format msgid "Write Contract" msgstr "" -#: lib/block_scout_web/templates/address/_tabs.html.eex:102 #: lib/block_scout_web/templates/address/_tabs.html.eex:102 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:48 -#: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:48 -#: lib/block_scout_web/views/address_view.ex:360 #: lib/block_scout_web/views/address_view.ex:360 #, elixir-autogen, elixir-format msgid "Write Proxy" msgstr "" #: lib/block_scout_web/templates/address_contract_verification_common_fields/_fetch_constructor_args.html.eex:14 -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_fetch_constructor_args.html.eex:14 -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_include_nightly_builds_field.html.eex:14 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_include_nightly_builds_field.html.eex:14 #: lib/block_scout_web/templates/address_contract_verification_common_fields/_yul_contracts_switcher.html.eex:14 -#: lib/block_scout_web/templates/address_contract_verification_common_fields/_yul_contracts_switcher.html.eex:14 -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:51 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:51 #: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:43 -#: lib/block_scout_web/templates/address_contract_verification_via_multi_part_files/new.html.eex:43 #, elixir-autogen, elixir-format msgid "Yes" msgstr "" -#: lib/block_scout_web/templates/account/api_key/index.html.eex:18 #: lib/block_scout_web/templates/account/api_key/index.html.eex:18 #, elixir-autogen, elixir-format msgid "You can create 3 API keys per account." msgstr "" -#: lib/block_scout_web/templates/account/custom_abi/index.html.eex:18 #: lib/block_scout_web/templates/account/custom_abi/index.html.eex:18 #, elixir-autogen, elixir-format msgid "You can create up to 15 Custom ABIs per account." msgstr "" -#: lib/block_scout_web/templates/account/tag_address/index.html.eex:14 #: lib/block_scout_web/templates/account/tag_address/index.html.eex:14 #, elixir-autogen, elixir-format msgid "You don't have address tags yet" msgstr "" -#: lib/block_scout_web/templates/account/watchlist/show.html.eex:14 #: lib/block_scout_web/templates/account/watchlist/show.html.eex:14 #, elixir-autogen, elixir-format msgid "You don't have addresses on you watchlist yet" msgstr "" -#: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:14 #: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:14 #, elixir-autogen, elixir-format msgid "You don't have transaction tags yet" msgstr "" -#: lib/block_scout_web/templates/error422/index.html.eex:8 #: lib/block_scout_web/templates/error422/index.html.eex:8 #, elixir-autogen, elixir-format msgid "Your request contained an error, perhaps a mistyped tx/block/address hash. Try again, and check the developer tools console for more info." msgstr "" -#: lib/block_scout_web/templates/verified_contracts/index.html.eex:38 +#: lib/block_scout_web/templates/verified_contracts/_contract.html.eex:30 #: lib/block_scout_web/templates/verified_contracts/index.html.eex:38 #: lib/block_scout_web/views/verified_contracts_view.ex:12 -#: lib/block_scout_web/views/verified_contracts_view.ex:12 #, elixir-autogen, elixir-format msgid "Yul" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:111 #: lib/block_scout_web/templates/address/overview.html.eex:111 #, elixir-autogen, elixir-format msgid "at" msgstr "" -#: lib/block_scout_web/templates/address_token/overview.html.eex:52 #: lib/block_scout_web/templates/address_token/overview.html.eex:52 #, elixir-autogen, elixir-format msgid "balance of the address" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:469 -#: lib/block_scout_web/templates/transaction/overview.html.eex:469 +#: lib/block_scout_web/templates/transaction/overview.html.eex:446 #, elixir-autogen, elixir-format msgid "burnt for this transaction. Equals Block Base Fee per Gas * Gas Used." msgstr "" -#: lib/block_scout_web/templates/block/overview.html.eex:217 #: lib/block_scout_web/templates/block/overview.html.eex:217 #, elixir-autogen, elixir-format msgid "burnt from transactions included in the block (Base fee (per unit of gas) * Gas Used)." msgstr "" -#: lib/block_scout_web/templates/address_contract/index.html.eex:27 #: lib/block_scout_web/templates/address_contract/index.html.eex:27 #, elixir-autogen, elixir-format msgid "button" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:276 -#: lib/block_scout_web/templates/transaction/overview.html.eex:276 +#: lib/block_scout_web/templates/transaction/overview.html.eex:253 #, elixir-autogen, elixir-format msgid "created" msgstr "" -#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:12 #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:12 #, elixir-autogen, elixir-format msgid "custom RPC" msgstr "" -#: lib/block_scout_web/templates/address/overview.html.eex:151 #: lib/block_scout_web/templates/address/overview.html.eex:151 #, elixir-autogen, elixir-format msgid "doesn't include ERC20, ERC721, ERC1155 tokens)." msgstr "" -#: lib/block_scout_web/templates/common_components/_rap_pagination_container.html.eex:13 #: lib/block_scout_web/templates/common_components/_rap_pagination_container.html.eex:13 #, elixir-autogen, elixir-format msgid "elements are displayed" msgstr "" -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:44 #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:44 #, elixir-autogen, elixir-format msgid "fallback" msgstr "" -#: lib/block_scout_web/views/address_contract_view.ex:33 #: lib/block_scout_web/views/address_contract_view.ex:33 #, elixir-autogen, elixir-format msgid "false" msgstr "" -#: lib/block_scout_web/templates/csv_export/index.html.eex:14 #: lib/block_scout_web/templates/csv_export/index.html.eex:14 #, elixir-autogen, elixir-format msgid "for address" msgstr "" #: lib/block_scout_web/templates/address_logs/_logs.html.eex:10 -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:10 -#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:12 #: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:12 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:10 -#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:10 #, elixir-autogen, elixir-format msgid "here" msgstr "" -#: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:9 #: lib/block_scout_web/templates/api_docs/eth_rpc.html.eex:9 #, elixir-autogen, elixir-format msgid "here." msgstr "" -#: lib/block_scout_web/templates/smart_contract/_function_response.html.eex:3 #: lib/block_scout_web/templates/smart_contract/_function_response.html.eex:3 #, elixir-autogen, elixir-format msgid "method Response" msgstr "" -#: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:41 #: lib/block_scout_web/templates/common_components/_pagination_container.html.eex:41 #, elixir-autogen, elixir-format msgid "of" msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:100 #: lib/block_scout_web/templates/layout/app.html.eex:100 #, elixir-autogen, elixir-format msgid "on sign up. Didn’t receive?" msgstr "" -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:16 #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:16 #, elixir-autogen, elixir-format msgid "page" msgstr "" -#: lib/block_scout_web/templates/smart_contract/_functions.html.eex:46 #: lib/block_scout_web/templates/smart_contract/_functions.html.eex:46 #, elixir-autogen, elixir-format msgid "receive" msgstr "" -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:58 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:58 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:69 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:69 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:81 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:81 #: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:70 -#: lib/block_scout_web/templates/api_docs/_eth_rpc_item.html.eex:70 #, elixir-autogen, elixir-format msgid "required" msgstr "" -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:59 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:59 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:70 -#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:70 #, elixir-autogen, elixir-format msgid "string" msgstr "" -#: lib/block_scout_web/templates/csv_export/index.html.eex:17 #: lib/block_scout_web/templates/csv_export/index.html.eex:17 #, elixir-autogen, elixir-format msgid "to CSV file" msgstr "" -#: lib/block_scout_web/views/address_contract_view.ex:32 #: lib/block_scout_web/views/address_contract_view.ex:32 #, elixir-autogen, elixir-format msgid "true" msgstr "" -#: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:77 #: lib/block_scout_web/templates/address_contract_verification_via_flattened_code/new.html.eex:77 #, elixir-autogen, elixir-format msgid "truffle flattener" msgstr "" + +#: lib/block_scout_web/views/transaction_view.ex:224 +#, elixir-autogen, elixir-format, fuzzy +msgid "ERC-7984 " +msgstr "" + +#: lib/block_scout_web/views/transaction_view.ex:223 +#, elixir-autogen, elixir-format, fuzzy +msgid "ZRC-2 " +msgstr "" diff --git a/apps/block_scout_web/test/block_scout_web/channels/address_channel_test.exs b/apps/block_scout_web/test/block_scout_web/channels/address_channel_test.exs index 83a0113466f8..8e2add3294b8 100644 --- a/apps/block_scout_web/test/block_scout_web/channels/address_channel_test.exs +++ b/apps/block_scout_web/test/block_scout_web/channels/address_channel_test.exs @@ -167,7 +167,6 @@ defmodule BlockScoutWeb.AddressChannelTest do from_address: address, transaction_index: transaction.index, index: 0, - block_hash: transaction.block_hash, block_number: transaction.block_number ) @@ -184,7 +183,7 @@ defmodule BlockScoutWeb.AddressChannelTest do :timer.seconds(5) assert address_hash == address.hash - assert {transaction_hash, index} == {internal_transaction.transaction_hash, internal_transaction.index} + assert {transaction_hash, index} == {transaction.hash, internal_transaction.index} end test "notified of new_internal_transaction for matching to_address", %{address: address, topic: topic} do @@ -195,11 +194,9 @@ defmodule BlockScoutWeb.AddressChannelTest do internal_transaction = insert(:internal_transaction, - transaction: transaction, to_address: address, transaction_index: transaction.index, index: 0, - block_hash: transaction.block_hash, block_number: transaction.block_number ) @@ -210,13 +207,19 @@ defmodule BlockScoutWeb.AddressChannelTest do event: "internal_transaction", payload: %{ address: %{hash: address_hash}, - internal_transaction: %{transaction_hash: transaction_hash, index: index} + internal_transaction: %{ + block_number: block_number, + transaction_index: transaction_index, + index: index + } } }, :timer.seconds(5) assert address_hash == address.hash - assert {transaction_hash, index} == {internal_transaction.transaction_hash, internal_transaction.index} + + assert {block_number, transaction_index, index} == + {internal_transaction.block_number, internal_transaction.transaction_index, internal_transaction.index} end test "not notified twice of new_internal_transaction if to and from address are equal", %{ @@ -230,12 +233,10 @@ defmodule BlockScoutWeb.AddressChannelTest do internal_transaction = insert(:internal_transaction, - transaction: transaction, from_address: address, to_address: address, transaction_index: transaction.index, index: 0, - block_hash: transaction.block_hash, block_number: transaction.block_number ) @@ -246,13 +247,13 @@ defmodule BlockScoutWeb.AddressChannelTest do event: "internal_transaction", payload: %{ address: %{hash: address_hash}, - internal_transaction: %{transaction_hash: transaction_hash, index: index} + internal_transaction: %{transaction_index: transaction_index, index: index} } }, :timer.seconds(5) assert address_hash == address.hash - assert {transaction_hash, index} == {internal_transaction.transaction_hash, internal_transaction.index} + assert {transaction_index, index} == {internal_transaction.transaction_index, internal_transaction.index} refute_receive _, 100, "Received duplicate broadcast." end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_contract_controller_test.exs index 7b420c32e24e..503eb3ba8fa6 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/address_contract_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/address_contract_controller_test.exs @@ -52,7 +52,6 @@ defmodule BlockScoutWeb.AddressContractControllerTest do transaction: transaction, transaction_index: transaction.index, created_contract_address: address, - block_hash: transaction.block_hash, block_number: transaction.block_number ) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_internal_transaction_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_internal_transaction_controller_test.exs index ac137a44ef77..372bfdfefeee 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/address_internal_transaction_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/address_internal_transaction_controller_test.exs @@ -51,22 +51,18 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do from_internal_transaction = insert(:internal_transaction, - transaction: transaction, from_address: address, index: 1, block_number: transaction.block_number, - transaction_index: transaction.index, - block_hash: transaction.block_hash + transaction_index: transaction.index ) to_internal_transaction = insert(:internal_transaction, - transaction: transaction, to_address: address, index: 2, block_number: transaction.block_number, - transaction_index: transaction.index, - block_hash: transaction.block_hash + transaction_index: transaction.index ) path = address_internal_transaction_path(conn, :index, Address.checksum(address), %{"type" => "JSON"}) @@ -76,7 +72,7 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do assert Enum.all?([from_internal_transaction, to_internal_transaction], fn internal_transaction -> Enum.any?(internal_transaction_tiles, fn tile -> - String.contains?(tile, to_string(internal_transaction.transaction_hash)) && + String.contains?(tile, to_string(transaction.hash)) && String.contains?(tile, "data-internal-transaction-index=\"#{internal_transaction.index}\"") end) end) @@ -92,22 +88,18 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do from_internal_transaction = insert(:internal_transaction, - transaction: transaction, from_address: address, index: 1, block_number: transaction.block_number, - transaction_index: transaction.index, - block_hash: transaction.block_hash + transaction_index: transaction.index ) to_internal_transaction = insert(:internal_transaction, - transaction: transaction, to_address: address, index: 2, block_number: transaction.block_number, - transaction_index: transaction.index, - block_hash: transaction.block_hash + transaction_index: transaction.index ) path = @@ -121,12 +113,12 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do internal_transaction_tiles = json_response(conn, 200)["items"] assert Enum.any?(internal_transaction_tiles, fn tile -> - String.contains?(tile, to_string(from_internal_transaction.transaction_hash)) && + String.contains?(tile, to_string(transaction.hash)) && String.contains?(tile, "data-internal-transaction-index=\"#{from_internal_transaction.index}\"") end) refute Enum.any?(internal_transaction_tiles, fn tile -> - String.contains?(tile, to_string(to_internal_transaction.transaction_hash)) && + String.contains?(tile, to_string(transaction.hash)) && String.contains?(tile, "data-internal-transaction-index=\"#{to_internal_transaction.index}\"") end) end @@ -141,22 +133,18 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do from_internal_transaction = insert(:internal_transaction, - transaction: transaction, from_address: address, index: 1, block_number: transaction.block_number, - transaction_index: transaction.index, - block_hash: transaction.block_hash + transaction_index: transaction.index ) to_internal_transaction = insert(:internal_transaction, - transaction: transaction, to_address: address, index: 2, block_number: transaction.block_number, - transaction_index: transaction.index, - block_hash: transaction.block_hash + transaction_index: transaction.index ) path = @@ -167,12 +155,12 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do internal_transaction_tiles = json_response(conn, 200)["items"] assert Enum.any?(internal_transaction_tiles, fn tile -> - String.contains?(tile, to_string(to_internal_transaction.transaction_hash)) && + String.contains?(tile, to_string(transaction.hash)) && String.contains?(tile, "data-internal-transaction-index=\"#{to_internal_transaction.index}\"") end) refute Enum.any?(internal_transaction_tiles, fn tile -> - String.contains?(tile, to_string(from_internal_transaction.transaction_hash)) && + String.contains?(tile, to_string(transaction.hash)) && String.contains?(tile, "data-internal-transaction-index=\"#{from_internal_transaction.index}\"") end) end @@ -187,23 +175,19 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do from_internal_transaction = insert(:internal_transaction, - transaction: transaction, from_address: address, index: 1, block_number: transaction.block_number, - transaction_index: transaction.index, - block_hash: transaction.block_hash + transaction_index: transaction.index ) to_internal_transaction = - insert(:internal_transaction, - transaction: transaction, + insert(:internal_transaction_create, to_address: nil, created_contract_address: address, index: 2, block_number: transaction.block_number, - transaction_index: transaction.index, - block_hash: transaction.block_hash + transaction_index: transaction.index ) path = @@ -214,12 +198,12 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do internal_transaction_tiles = json_response(conn, 200)["items"] assert Enum.any?(internal_transaction_tiles, fn tile -> - String.contains?(tile, to_string(to_internal_transaction.transaction_hash)) && + String.contains?(tile, to_string(transaction.hash)) && String.contains?(tile, "data-internal-transaction-index=\"#{to_internal_transaction.index}\"") end) refute Enum.any?(internal_transaction_tiles, fn tile -> - String.contains?(tile, to_string(from_internal_transaction.transaction_hash)) && + String.contains?(tile, to_string(transaction.hash)) && String.contains?(tile, "data-internal-transaction-index=\"#{from_internal_transaction.index}\"") end) end @@ -254,8 +238,7 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do from_address: address, index: index, block_number: transaction_1.block_number, - transaction_index: transaction_1.index, - block_hash: a_block.hash + transaction_index: transaction_1.index ) end) @@ -268,8 +251,7 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do from_address: address, index: index, block_number: transaction_2.block_number, - transaction_index: transaction_2.index, - block_hash: a_block.hash + transaction_index: transaction_2.index ) end) @@ -282,8 +264,7 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do from_address: address, index: index, block_number: transaction_3.block_number, - transaction_index: transaction_3.index, - block_hash: b_block.hash + transaction_index: transaction_3.index ) end) @@ -296,8 +277,7 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do from_address: address, index: 11, block_number: transaction_3.block_number, - transaction_index: transaction_3.index, - block_hash: b_block.hash + transaction_index: transaction_3.index ) conn = @@ -312,7 +292,7 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do assert Enum.all?(second_page, fn internal_transaction -> Enum.any?(internal_transaction_tiles, fn tile -> - String.contains?(tile, to_string(internal_transaction.transaction_hash)) && + String.contains?(tile, to_string(internal_transaction.transaction.hash)) && String.contains?(tile, "data-internal-transaction-index=\"#{internal_transaction.index}\"") end) end) @@ -348,8 +328,7 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do from_address: address, index: index, block_number: transaction_1.block_number, - transaction_index: transaction_1.index, - block_hash: a_block.hash + transaction_index: transaction_1.index ) end) @@ -362,8 +341,7 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do to_address: address, index: index, block_number: transaction_2.block_number, - transaction_index: transaction_2.index, - block_hash: a_block.hash + transaction_index: transaction_2.index ) end) @@ -371,14 +349,13 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do 1..55 |> Enum.map(fn index -> insert( - :internal_transaction, + :internal_transaction_create, transaction: transaction_3, created_contract_address: address, to_address: nil, index: index, block_number: transaction_3.block_number, - transaction_index: transaction_3.index, - block_hash: b_block.hash + transaction_index: transaction_3.index ) end) @@ -447,7 +424,7 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do assert Enum.all?(first_page_items, fn internal_transaction -> Enum.any?(first_page_response, fn tile -> - String.contains?(tile, to_string(internal_transaction.transaction_hash)) && + String.contains?(tile, to_string(internal_transaction.transaction.hash)) && String.contains?(tile, "data-internal-transaction-index=\"#{internal_transaction.index}\"") end) end) @@ -456,7 +433,7 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do assert Enum.all?(second_page_items, fn internal_transaction -> Enum.any?(second_page_response, fn tile -> - String.contains?(tile, to_string(internal_transaction.transaction_hash)) && + String.contains?(tile, to_string(internal_transaction.transaction.hash)) && String.contains?(tile, "data-internal-transaction-index=\"#{internal_transaction.index}\"") end) end) @@ -465,7 +442,7 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do assert Enum.all?(third_page_items, fn internal_transaction -> Enum.any?(third_page_response, fn tile -> - String.contains?(tile, to_string(internal_transaction.transaction_hash)) && + String.contains?(tile, to_string(internal_transaction.transaction.hash)) && String.contains?(tile, "data-internal-transaction-index=\"#{internal_transaction.index}\"") end) end) @@ -474,7 +451,7 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do assert Enum.all?(fourth_page_items, fn internal_transaction -> Enum.any?(fourth_page_response, fn tile -> - String.contains?(tile, to_string(internal_transaction.transaction_hash)) && + String.contains?(tile, to_string(internal_transaction.transaction.hash)) && String.contains?(tile, "data-internal-transaction-index=\"#{internal_transaction.index}\"") end) end) @@ -498,8 +475,7 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do from_address: address, index: index, block_number: transaction.block_number, - transaction_index: transaction.index, - block_hash: transaction.block_hash + transaction_index: transaction.index ) end) @@ -538,7 +514,6 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do from_address: address, transaction_index: transaction.index, index: index, - block_hash: transaction.block_hash, block_number: transaction.block_number ) end) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_read_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_read_contract_controller_test.exs index e3f060aab597..f67e4f4ba8a2 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/address_read_contract_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/address_read_contract_controller_test.exs @@ -38,7 +38,6 @@ defmodule BlockScoutWeb.AddressReadContractControllerTest do transaction: transaction, transaction_index: transaction.index, created_contract_address: contract_address, - block_hash: transaction.block_hash, block_number: transaction.block_number ) @@ -66,7 +65,6 @@ defmodule BlockScoutWeb.AddressReadContractControllerTest do transaction: transaction, transaction_index: transaction.index, created_contract_address: contract_address, - block_hash: transaction.block_hash, block_number: transaction.block_number ) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_read_proxy_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_read_proxy_controller_test.exs index 6992df38134b..f78c728f62eb 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/address_read_proxy_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/address_read_proxy_controller_test.exs @@ -38,7 +38,6 @@ defmodule BlockScoutWeb.AddressReadProxyControllerTest do transaction: transaction, transaction_index: transaction.index, created_contract_address: contract_address, - block_hash: transaction.block_hash, block_number: transaction.block_number ) @@ -65,7 +64,6 @@ defmodule BlockScoutWeb.AddressReadProxyControllerTest do transaction: transaction, transaction_index: transaction.index, created_contract_address: contract_address, - block_hash: transaction.block_hash, block_number: transaction.block_number ) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_transaction_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_transaction_controller_test.exs index 23afdcab0b0d..43393abd1ca8 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/address_transaction_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/address_transaction_controller_test.exs @@ -138,7 +138,6 @@ defmodule BlockScoutWeb.AddressTransactionControllerTest do to_address: nil, transaction: transaction, transaction_index: transaction.index, - block_hash: block.hash, block_number: transaction.block_number ) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_write_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_write_contract_controller_test.exs index fdd456c01906..75f6c3fdb1a8 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/address_write_contract_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/address_write_contract_controller_test.exs @@ -40,7 +40,6 @@ defmodule BlockScoutWeb.AddressWriteContractControllerTest do transaction: transaction, transaction_index: transaction.index, created_contract_address: contract_address, - block_hash: transaction.block_hash, block_number: transaction.block_number ) @@ -68,7 +67,6 @@ defmodule BlockScoutWeb.AddressWriteContractControllerTest do transaction: transaction, transaction_index: transaction.index, created_contract_address: contract_address, - block_hash: transaction.block_hash, block_number: transaction.block_number ) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_write_proxy_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_write_proxy_controller_test.exs index 5f5b764c0b18..1a24ecda33fe 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/address_write_proxy_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/address_write_proxy_controller_test.exs @@ -38,7 +38,6 @@ defmodule BlockScoutWeb.AddressWriteProxyControllerTest do transaction: transaction, transaction_index: transaction.index, created_contract_address: contract_address, - block_hash: transaction.block_hash, block_number: transaction.block_number ) @@ -66,7 +65,6 @@ defmodule BlockScoutWeb.AddressWriteProxyControllerTest do transaction: transaction, transaction_index: transaction.index, created_contract_address: contract_address, - block_hash: transaction.block_hash, block_number: transaction.block_number ) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/legacy/block_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/legacy/block_controller_test.exs new file mode 100644 index 000000000000..28032d4b7474 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/legacy/block_controller_test.exs @@ -0,0 +1,249 @@ +defmodule BlockScoutWeb.API.Legacy.BlockControllerTest do + use BlockScoutWeb.ConnCase + + alias BlockScoutWeb.Chain + alias Explorer.Chain.Cache.BlockNumber + + describe "GET /api/legacy/block/get-block-number-by-time" do + test "missing timestamp param", %{conn: conn} do + response = + conn + |> get("/api/legacy/block/get-block-number-by-time", %{"closest" => "after"}) + |> json_response(200) + + assert response["status"] == "0" + assert response["message"] =~ "Query parameter 'timestamp' is required" + assert Map.has_key?(response, "result") + refute response["result"] + end + + test "missing closest param", %{conn: conn} do + response = + conn + |> get("/api/legacy/block/get-block-number-by-time", %{"timestamp" => "1617019505"}) + |> json_response(200) + + assert response["status"] == "0" + assert response["message"] =~ "Query parameter 'closest' is required" + assert Map.has_key?(response, "result") + refute response["result"] + end + + test "invalid timestamp param", %{conn: conn} do + response = + conn + |> get("/api/legacy/block/get-block-number-by-time", %{ + "timestamp" => "invalid", + "closest" => "before" + }) + |> json_response(200) + + assert response["status"] == "0" + assert response["message"] =~ "Invalid `timestamp` param" + assert Map.has_key?(response, "result") + refute response["result"] + end + + test "invalid closest param", %{conn: conn} do + response = + conn + |> get("/api/legacy/block/get-block-number-by-time", %{ + "timestamp" => "1617019505", + "closest" => "invalid" + }) + |> json_response(200) + + assert response["status"] == "0" + assert response["message"] =~ "Invalid `closest` param" + assert Map.has_key?(response, "result") + refute response["result"] + end + + test "not found — no matching block", %{conn: conn} do + response = + conn + |> get("/api/legacy/block/get-block-number-by-time", %{ + "timestamp" => "1617019505", + "closest" => "before" + }) + |> json_response(200) + + assert response["status"] == "0" + assert Map.has_key?(response, "result") + refute response["result"] + end + + test "success with closest=before", %{conn: conn} do + timestamp_string = "1617020209" + {:ok, timestamp} = Chain.param_to_block_timestamp(timestamp_string) + block = insert(:block, timestamp: timestamp) + + {timestamp_int, _} = Integer.parse(timestamp_string) + timestamp_in_the_future = to_string(timestamp_int + 1) + + response = + conn + |> get("/api/legacy/block/get-block-number-by-time", %{ + "timestamp" => timestamp_in_the_future, + "closest" => "before" + }) + |> json_response(200) + + assert response["status"] == "1" + assert response["message"] == "OK" + assert response["result"] == %{"blockNumber" => "#{block.number}"} + end + + test "success with closest=after", %{conn: conn} do + timestamp_string = "1617020209" + {:ok, timestamp} = Chain.param_to_block_timestamp(timestamp_string) + block = insert(:block, timestamp: timestamp) + + {timestamp_int, _} = Integer.parse(timestamp_string) + timestamp_in_the_past = to_string(timestamp_int - 1) + + response = + conn + |> get("/api/legacy/block/get-block-number-by-time", %{ + "timestamp" => timestamp_in_the_past, + "closest" => "after" + }) + |> json_response(200) + + assert response["status"] == "1" + assert response["message"] == "OK" + assert response["result"] == %{"blockNumber" => "#{block.number}"} + end + + # Parity invariant: response body must be byte-identical to the v1 endpoint. + test "parity with v1 /api?module=block&action=getblocknobytime — success", %{conn: conn} do + timestamp_string = "1617020209" + {:ok, timestamp} = Chain.param_to_block_timestamp(timestamp_string) + insert(:block, timestamp: timestamp) + + {timestamp_int, _} = Integer.parse(timestamp_string) + timestamp_in_the_future = to_string(timestamp_int + 1) + + params = %{"timestamp" => timestamp_in_the_future, "closest" => "before"} + + v1_response = + conn + |> get("/api", Map.merge(params, %{"module" => "block", "action" => "getblocknobytime"})) + |> json_response(200) + + v2_response = + conn + |> get("/api/legacy/block/get-block-number-by-time", params) + |> json_response(200) + + assert v1_response == v2_response + end + + test "parity with v1 /api?module=block&action=getblocknobytime — error (missing params)", %{conn: conn} do + v1_response = + conn + |> get("/api", %{"module" => "block", "action" => "getblocknobytime"}) + |> json_response(200) + + v2_response = + conn + |> get("/api/legacy/block/get-block-number-by-time") + |> json_response(200) + + assert v1_response == v2_response + end + end + + describe "GET /api/legacy/block/eth-block-number" do + setup do + Supervisor.terminate_child(Explorer.Supervisor, BlockNumber.child_id()) + Supervisor.restart_child(Explorer.Supervisor, BlockNumber.child_id()) + :ok + end + + test "default id (omitted) — returns integer id 1", %{conn: conn} do + insert(:block) + + response = + conn + |> get("/api/legacy/block/eth-block-number") + |> json_response(200) + + assert response["jsonrpc"] == "2.0" + assert is_binary(response["result"]) + assert String.starts_with?(response["result"], "0x") + # When id is omitted the v1 controller defaults to integer 1 + assert response["id"] == 1 + end + + test "integer id (?id=7) — echoed back as string (query strings are strings)", %{conn: conn} do + insert(:block) + + response = + conn + |> get("/api/legacy/block/eth-block-number", %{"id" => "7"}) + |> json_response(200) + + assert response["jsonrpc"] == "2.0" + assert is_binary(response["result"]) + # id=7 comes in as the string "7"; sanitize_id emits it quoted → "7" + assert response["id"] == "7" + end + + test "string id (?id=hello) — echoed back as string", %{conn: conn} do + insert(:block) + + response = + conn + |> get("/api/legacy/block/eth-block-number", %{"id" => "hello"}) + |> json_response(200) + + assert response["jsonrpc"] == "2.0" + assert response["id"] == "hello" + end + + test "empty database — result is \"0x0\"", %{conn: conn} do + # No blocks inserted. BlockNumber.get_max/0 delegates to + # Block.fetch_max_block_number/0 which returns Repo.one(query) || 0, so the + # result is 0, not nil. encode_quantity(0) → "0x0". + response = + conn + |> get("/api/legacy/block/eth-block-number") + |> json_response(200) + + assert response["jsonrpc"] == "2.0" + assert response["result"] == "0x0" + end + + # Parity invariant: response body must be byte-identical to the v1 endpoint. + test "parity with v1 /api?module=block&action=eth_block_number — default id", %{conn: conn} do + insert(:block) + + v1_response = + conn + |> get("/api", %{"module" => "block", "action" => "eth_block_number"}) + |> json_response(200) + + v2_response = + conn + |> get("/api/legacy/block/eth-block-number") + |> json_response(200) + + assert v1_response == v2_response + end + + test "parity with v1 /api?module=block&action=eth_block_number — empty database", %{conn: conn} do + v1_response = + conn + |> get("/api", %{"module" => "block", "action" => "eth_block_number"}) + |> json_response(200) + + v2_response = + conn + |> get("/api/legacy/block/eth-block-number") + |> json_response(200) + + assert v1_response == v2_response + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/legacy/logs_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/legacy/logs_controller_test.exs new file mode 100644 index 000000000000..f4260aea6d58 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/legacy/logs_controller_test.exs @@ -0,0 +1,262 @@ +defmodule BlockScoutWeb.API.Legacy.LogsControllerTest do + use BlockScoutWeb.ConnCase + + alias Explorer.Chain.Transaction + + describe "GET /api/legacy/logs/get-logs" do + test "missing fromBlock, toBlock, address, and topic{x}", %{conn: conn} do + response = + conn + |> get("/api/legacy/logs/get-logs") + |> json_response(200) + + assert response["status"] == "0" + assert response["message"] =~ "Required query parameters missing" + assert Map.has_key?(response, "result") + refute response["result"] + end + + test "missing fromBlock", %{conn: conn} do + response = + conn + |> get("/api/legacy/logs/get-logs", %{ + "toBlock" => "10", + "address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b" + }) + |> json_response(200) + + assert response["status"] == "0" + assert response["message"] =~ "fromBlock" + assert Map.has_key?(response, "result") + refute response["result"] + end + + test "missing toBlock", %{conn: conn} do + response = + conn + |> get("/api/legacy/logs/get-logs", %{ + "fromBlock" => "5", + "address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b" + }) + |> json_response(200) + + assert response["status"] == "0" + assert response["message"] =~ "toBlock" + assert Map.has_key?(response, "result") + refute response["result"] + end + + test "missing address and topic{x}", %{conn: conn} do + response = + conn + |> get("/api/legacy/logs/get-logs", %{"fromBlock" => "5", "toBlock" => "10"}) + |> json_response(200) + + assert response["status"] == "0" + assert response["message"] =~ "address and/or topic{x}" + assert Map.has_key?(response, "result") + refute response["result"] + end + + test "invalid fromBlock format", %{conn: conn} do + response = + conn + |> get("/api/legacy/logs/get-logs", %{ + "fromBlock" => "abc", + "toBlock" => "10", + "address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b" + }) + |> json_response(200) + + assert response["status"] == "0" + assert response["message"] =~ "Invalid fromBlock format" + assert Map.has_key?(response, "result") + refute response["result"] + end + + test "no logs found returns empty result array", %{conn: conn} do + response = + conn + |> get("/api/legacy/logs/get-logs", %{ + "fromBlock" => "5", + "toBlock" => "10", + "address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b" + }) + |> json_response(200) + + assert response["status"] == "0" + assert response["message"] == "No logs found" + assert response["result"] == [] + end + + test "fromBlock=latest and toBlock=latest", %{conn: conn} do + contract_address = insert(:contract_address) + + transaction = + %Transaction{block: block} = + :transaction + |> insert(to_address: contract_address) + |> with_block() + + insert(:log, + address: contract_address, + transaction: transaction, + block: block, + block_number: block.number + ) + + response = + conn + |> get("/api/legacy/logs/get-logs", %{ + "fromBlock" => "latest", + "toBlock" => "latest", + "address" => "#{contract_address.hash}" + }) + |> json_response(200) + + assert response["status"] == "1" + assert response["message"] == "OK" + assert is_list(response["result"]) + assert length(response["result"]) == 1 + end + + test "success with logs returned", %{conn: conn} do + contract_address = insert(:contract_address) + + transaction = + %Transaction{block: block} = + :transaction + |> insert(to_address: contract_address) + |> with_block() + + log = + insert(:log, + address: contract_address, + transaction: transaction, + block: block, + block_number: transaction.block_number + ) + + params = %{ + "fromBlock" => "#{block.number}", + "toBlock" => "#{block.number}", + "address" => "#{contract_address.hash}" + } + + response = + conn + |> get("/api/legacy/logs/get-logs", params) + |> json_response(200) + + assert response["status"] == "1" + assert response["message"] == "OK" + assert [found_log] = response["result"] + assert found_log["address"] == "#{contract_address.hash}" + assert found_log["transactionHash"] == "#{transaction.hash}" + assert found_log["blockNumber"] == integer_to_hex(log.block_number) + end + + test "two topics set, required topicA_B_opr missing", %{conn: conn} do + conditions = %{ + ["topic0", "topic1"] => "topic0_1_opr", + ["topic0", "topic2"] => "topic0_2_opr", + ["topic0", "topic3"] => "topic0_3_opr", + ["topic1", "topic2"] => "topic1_2_opr", + ["topic1", "topic3"] => "topic1_3_opr", + ["topic2", "topic3"] => "topic2_3_opr" + } + + for {[key1, key2], expectation} <- conditions do + response = + conn + |> get("/api/legacy/logs/get-logs", %{ + "fromBlock" => "5", + "toBlock" => "10", + key1 => "some topic", + key2 => "some other topic" + }) + |> json_response(200) + + assert response["status"] == "0" + assert response["message"] == "Required query parameters missing: #{expectation}" + assert Map.has_key?(response, "result") + refute response["result"] + end + end + + test "four topics set, all six topic*_opr missing", %{conn: conn} do + response = + conn + |> get("/api/legacy/logs/get-logs", %{ + "fromBlock" => "5", + "toBlock" => "10", + "topic0" => "some topic", + "topic1" => "some other topic", + "topic2" => "some extra topic", + "topic3" => "some different topic" + }) + |> json_response(200) + + assert response["status"] == "0" + + assert response["message"] =~ + "Required query parameters missing: " <> + "topic0_1_opr, topic0_2_opr, topic0_3_opr, topic1_2_opr, topic1_3_opr, topic2_3_opr" + + assert Map.has_key?(response, "result") + refute response["result"] + end + + # Parity invariant: response body must be byte-identical to the v1 endpoint. + test "parity with v1 /api?module=logs&action=getLogs — success", %{conn: conn} do + contract_address = insert(:contract_address) + + transaction = + %Transaction{block: block} = + :transaction + |> insert(to_address: contract_address) + |> with_block() + + insert(:log, + address: contract_address, + transaction: transaction, + block: block, + block_number: transaction.block_number + ) + + params = %{ + "fromBlock" => "#{block.number}", + "toBlock" => "#{block.number}", + "address" => "#{contract_address.hash}" + } + + v1_response = + conn + |> get("/api", Map.merge(params, %{"module" => "logs", "action" => "getLogs"})) + |> json_response(200) + + v2_response = + conn + |> get("/api/legacy/logs/get-logs", params) + |> json_response(200) + + assert v1_response == v2_response + end + + test "parity with v1 /api?module=logs&action=getLogs — error (missing params)", %{conn: conn} do + v1_response = + conn + |> get("/api", %{"module" => "logs", "action" => "getLogs"}) + |> json_response(200) + + v2_response = + conn + |> get("/api/legacy/logs/get-logs") + |> json_response(200) + + assert v1_response == v2_response + end + end + + defp integer_to_hex(integer), do: "0x" <> String.downcase(Integer.to_string(integer, 16)) +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs index 0288d7410cb4..6fdb311cb21a 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs @@ -6,8 +6,9 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do alias BlockScoutWeb.API.RPC.AddressController alias Explorer.{Chain, Repo, TestHelper} alias Explorer.Chain.Cache.BackgroundMigrations - alias Explorer.Chain.{Events.Subscriber, Transaction, Wei} + alias Explorer.Chain.{Events.Subscriber, InternalTransaction, Transaction, Wei} alias Explorer.Chain.Cache.Counters.{AddressesCount, AverageBlockTime} + alias Explorer.Utility.AddressIdToAddressHash alias Indexer.Fetcher.OnDemand.CoinBalance, as: CoinBalanceOnDemand setup :set_mox_global @@ -1832,7 +1833,6 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do value: 1, from_address: address, to_address: address_2, - block_hash: transaction.block_hash, block_number: block.number ) @@ -1845,7 +1845,6 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do value: 2, from_address: address, to_address: address_2, - block_hash: transaction.block_hash, block_number: block.number ) @@ -1853,8 +1852,8 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do %{ "blockNumber" => "#{transaction.block_number}", "timeStamp" => "#{DateTime.to_unix(block.timestamp)}", - "from" => "#{internal_transaction.from_address_hash}", - "to" => "#{internal_transaction.to_address_hash}", + "from" => "#{AddressIdToAddressHash.id_to_hash(internal_transaction.from_address_id)}", + "to" => "#{AddressIdToAddressHash.id_to_hash(internal_transaction.to_address_id)}", "value" => "#{internal_transaction.value.value}", "contractAddress" => "", "input" => "#{internal_transaction.input}", @@ -1900,7 +1899,6 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do value: 1, from_address: address, to_address: address_2, - block_hash: transaction.block_hash, block_number: block.number ) @@ -1913,7 +1911,6 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do value: 2, from_address: address, to_address: address_2, - block_hash: transaction.block_hash, block_number: block.number ) @@ -1921,8 +1918,8 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do %{ "blockNumber" => "#{transaction.block_number}", "timeStamp" => "#{DateTime.to_unix(block.timestamp)}", - "from" => "#{internal_transaction.from_address_hash}", - "to" => "#{internal_transaction.to_address_hash}", + "from" => "#{AddressIdToAddressHash.id_to_hash(internal_transaction.from_address_id)}", + "to" => "#{AddressIdToAddressHash.id_to_hash(internal_transaction.to_address_id)}", "value" => "#{internal_transaction.value.value}", "contractAddress" => "", "input" => "#{internal_transaction.input}", @@ -1967,7 +1964,6 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do value: 1, from_address: address, to_address: address_2, - block_hash: transaction.block_hash, block_number: block.number ) @@ -1979,7 +1975,6 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do value: 0, from_address: address, to_address: address_2, - block_hash: transaction.block_hash, block_number: block.number ) @@ -1992,7 +1987,6 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do value: 2, from_address: address, to_address: address_2, - block_hash: transaction.block_hash, block_number: block.number ) @@ -2000,8 +1994,8 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do %{ "blockNumber" => "#{transaction.block_number}", "timeStamp" => "#{DateTime.to_unix(block.timestamp)}", - "from" => "#{internal_transaction.from_address_hash}", - "to" => "#{internal_transaction.to_address_hash}", + "from" => "#{AddressIdToAddressHash.id_to_hash(internal_transaction.from_address_id)}", + "to" => "#{AddressIdToAddressHash.id_to_hash(internal_transaction.to_address_id)}", "value" => "#{internal_transaction.value.value}", "contractAddress" => "", "input" => "#{internal_transaction.input}", @@ -2049,7 +2043,6 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do value: 1, from_address: address, to_address: address_2, - block_hash: transaction.block_hash, block_number: block.number ) @@ -2062,7 +2055,6 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do value: 2, from_address: address, to_address: address_2, - block_hash: transaction.block_hash, block_number: block.number ) @@ -2075,7 +2067,6 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do value: 0, from_address: address, to_address: address_2, - block_hash: transaction.block_hash, block_number: block.number ) @@ -2083,8 +2074,8 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do %{ "blockNumber" => "#{transaction.block_number}", "timeStamp" => "#{DateTime.to_unix(block.timestamp)}", - "from" => "#{internal_transaction_b.from_address_hash}", - "to" => "#{internal_transaction_b.to_address_hash}", + "from" => "#{AddressIdToAddressHash.id_to_hash(internal_transaction_b.from_address_id)}", + "to" => "#{AddressIdToAddressHash.id_to_hash(internal_transaction_b.to_address_id)}", "value" => "#{internal_transaction_b.value.value}", "contractAddress" => "", "input" => "#{internal_transaction_b.input}", @@ -2100,8 +2091,8 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do %{ "blockNumber" => "#{internal_transaction_a.block_number}", "timeStamp" => "#{DateTime.to_unix(block.timestamp)}", - "from" => "#{internal_transaction_a.from_address_hash}", - "to" => "#{internal_transaction_a.to_address_hash}", + "from" => "#{AddressIdToAddressHash.id_to_hash(internal_transaction_a.from_address_id)}", + "to" => "#{AddressIdToAddressHash.id_to_hash(internal_transaction_a.to_address_id)}", "value" => "#{internal_transaction_a.value.value}", "contractAddress" => "", "input" => "#{internal_transaction_a.input}", @@ -2186,10 +2177,10 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do index: 0, value: 1, from_address: address, - block_hash: transaction.block_hash, + created_contract_code: contract_address.contract_code, + created_contract_address: contract_address, block_number: transaction.block_number ) - |> with_contract_creation(contract_address) params = %{ "module" => "account", @@ -2201,8 +2192,8 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do %{ "blockNumber" => "#{transaction.block_number}", "timeStamp" => "#{DateTime.to_unix(block.timestamp)}", - "from" => "#{internal_transaction.from_address_hash}", - "to" => "#{internal_transaction.to_address_hash}", + "from" => "#{AddressIdToAddressHash.id_to_hash(internal_transaction.from_address_id)}", + "to" => "#{AddressIdToAddressHash.id_to_hash(internal_transaction.to_address_id)}", "value" => "#{internal_transaction.value.value}", "contractAddress" => "#{contract_address.hash}", "input" => "", @@ -2249,10 +2240,10 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do index: 0, value: 1, from_address: address, - block_hash: transaction.block_hash, + created_contract_code: contract_address.contract_code, + created_contract_address: contract_address, block_number: transaction.block_number ) - |> with_contract_creation(contract_address) params = %{ "module" => "account", @@ -2286,7 +2277,6 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do value: 1, type: :reward, error: "some error", - block_hash: transaction.block_hash, block_number: transaction.block_number ] @@ -2321,7 +2311,6 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do transaction: transaction, transaction_index: transaction.index, index: index, - block_hash: transaction.block_hash, block_number: transaction.block_number, value: 1 ) @@ -2359,7 +2348,6 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do transaction_index: transaction.index, index: 0, value: 1, - block_hash: transaction.block_hash, block_number: block.number ) @@ -2369,7 +2357,6 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do transaction_index: transaction.index, index: 2, value: 0, - block_hash: transaction.block_hash, block_number: block.number ) @@ -2380,7 +2367,6 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do transaction_index: transaction.index, index: 1, value: 2, - block_hash: transaction.block_hash, block_number: block.number ) @@ -2388,8 +2374,8 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do %{ "blockNumber" => "#{transaction.block_number}", "timeStamp" => "#{DateTime.to_unix(block.timestamp)}", - "from" => "#{internal_transaction.from_address_hash}", - "to" => "#{internal_transaction.to_address_hash}", + "from" => "#{AddressIdToAddressHash.id_to_hash(internal_transaction.from_address_id)}", + "to" => "#{AddressIdToAddressHash.id_to_hash(internal_transaction.to_address_id)}", "value" => "#{internal_transaction.value.value}", "contractAddress" => "", "input" => "#{internal_transaction.input}", @@ -2435,7 +2421,6 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do transaction_index: transaction.index, index: 0, value: 1, - block_hash: transaction.block_hash, block_number: block.number ) @@ -2446,7 +2431,6 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do transaction_index: transaction.index, index: 1, value: 2, - block_hash: transaction.block_hash, block_number: block.number ) @@ -2457,7 +2441,6 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do transaction_index: transaction.index, index: 2, value: 0, - block_hash: transaction.block_hash, block_number: block.number ) @@ -2465,8 +2448,8 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do %{ "blockNumber" => "#{internal_transaction_b.block_number}", "timeStamp" => "#{DateTime.to_unix(block.timestamp)}", - "from" => "#{internal_transaction_b.from_address_hash}", - "to" => "#{internal_transaction_b.to_address_hash}", + "from" => "#{AddressIdToAddressHash.id_to_hash(internal_transaction_b.from_address_id)}", + "to" => "#{AddressIdToAddressHash.id_to_hash(internal_transaction_b.to_address_id)}", "value" => "#{internal_transaction_b.value.value}", "contractAddress" => "", "input" => "#{internal_transaction_b.input}", @@ -2482,8 +2465,8 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do %{ "blockNumber" => "#{transaction.block_number}", "timeStamp" => "#{DateTime.to_unix(block.timestamp)}", - "from" => "#{internal_transaction_a.from_address_hash}", - "to" => "#{internal_transaction_a.to_address_hash}", + "from" => "#{AddressIdToAddressHash.id_to_hash(internal_transaction_a.from_address_id)}", + "to" => "#{AddressIdToAddressHash.id_to_hash(internal_transaction_a.to_address_id)}", "value" => "#{internal_transaction_a.value.value}", "contractAddress" => "", "input" => "#{internal_transaction_a.input}", @@ -2590,10 +2573,10 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do transaction_index: transaction.index, index: 0, from_address: address, - block_number: block.number, - block_hash: transaction.block_hash + created_contract_code: contract_address.contract_code, + created_contract_address: contract_address, + block_number: block.number ) - |> with_contract_creation(contract_address) params = %{ "module" => "account", @@ -2605,8 +2588,8 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do %{ "blockNumber" => "#{transaction.block_number}", "timeStamp" => "#{DateTime.to_unix(block.timestamp)}", - "from" => "#{internal_transaction.from_address_hash}", - "to" => "#{internal_transaction.to_address_hash}", + "from" => "#{AddressIdToAddressHash.id_to_hash(internal_transaction.from_address_id)}", + "to" => "#{AddressIdToAddressHash.id_to_hash(internal_transaction.to_address_id)}", "value" => "#{internal_transaction.value.value}", "contractAddress" => "#{contract_address.hash}", "input" => "", @@ -2647,8 +2630,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do index: 0, type: :reward, error: "some error", - block_number: transaction.block_number, - block_hash: transaction.block_hash + block_number: transaction.block_number ] insert(:internal_transaction_create, internal_transaction_details) @@ -2686,7 +2668,6 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do transaction_index: transaction.index, index: index, block_number: transaction.block_number, - block_hash: transaction.block_hash, value: 1 } @@ -2728,7 +2709,6 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do index: 0, value: 1, from_address: address, - block_hash: transaction.block_hash, block_number: block.number ) @@ -2739,7 +2719,6 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do index: 2, value: 0, from_address: address, - block_hash: transaction.block_hash, block_number: block.number ) @@ -2751,7 +2730,6 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do index: 1, value: 2, from_address: address, - block_hash: transaction.block_hash, block_number: block.number ) @@ -2759,8 +2737,8 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do %{ "blockNumber" => "#{transaction.block_number}", "timeStamp" => "#{DateTime.to_unix(block.timestamp)}", - "from" => "#{internal_transaction.from_address_hash}", - "to" => "#{internal_transaction.to_address_hash}", + "from" => "#{AddressIdToAddressHash.id_to_hash(internal_transaction.from_address_id)}", + "to" => "#{AddressIdToAddressHash.id_to_hash(internal_transaction.to_address_id)}", "value" => "#{internal_transaction.value.value}", "contractAddress" => "", "input" => "#{internal_transaction.input}", @@ -2809,7 +2787,6 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do index: 0, value: 1, from_address: address, - block_hash: transaction.block_hash, block_number: block.number ) @@ -2821,7 +2798,6 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do index: 1, value: 2, from_address: address, - block_hash: transaction.block_hash, block_number: block.number ) @@ -2833,7 +2809,6 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do index: 2, value: 0, from_address: address, - block_hash: transaction.block_hash, block_number: block.number ) @@ -2841,8 +2816,8 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do %{ "blockNumber" => "#{transaction.block_number}", "timeStamp" => "#{DateTime.to_unix(block.timestamp)}", - "from" => "#{internal_transaction_b.from_address_hash}", - "to" => "#{internal_transaction_b.to_address_hash}", + "from" => "#{AddressIdToAddressHash.id_to_hash(internal_transaction_b.from_address_id)}", + "to" => "#{AddressIdToAddressHash.id_to_hash(internal_transaction_b.to_address_id)}", "value" => "#{internal_transaction_b.value.value}", "contractAddress" => "", "input" => "#{internal_transaction_b.input}", @@ -2858,8 +2833,8 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do %{ "blockNumber" => "#{internal_transaction_a.block_number}", "timeStamp" => "#{DateTime.to_unix(block.timestamp)}", - "from" => "#{internal_transaction_a.from_address_hash}", - "to" => "#{internal_transaction_a.to_address_hash}", + "from" => "#{AddressIdToAddressHash.id_to_hash(internal_transaction_a.from_address_id)}", + "to" => "#{AddressIdToAddressHash.id_to_hash(internal_transaction_a.to_address_id)}", "value" => "#{internal_transaction_a.value.value}", "contractAddress" => "", "input" => "#{internal_transaction_a.input}", diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs index b070d9fd2fb4..9e651e1e2ba1 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/contract_controller_test.exs @@ -6,7 +6,7 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do alias Explorer.{Repo, TestHelper} alias Explorer.Chain.SmartContract.Proxy.Models.Implementation - alias Explorer.Chain.{Address, SmartContract} + alias Explorer.Chain.{Address, InternalTransaction, SmartContract} setup :verify_on_exit! @@ -779,7 +779,6 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do created_contract_address: created_contract_address, created_contract_code: smart_contract_bytecode, block_number: transaction.block_number, - block_hash: transaction.block_hash, transaction_index: transaction.index ) @@ -1119,10 +1118,10 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do insert(:internal_transaction_create, transaction: transaction, index: 1, - block_hash: transaction.block_hash, block_number: transaction.block_number, transaction_index: transaction.index ) + |> InternalTransaction.preload_addresses() address = internal_transaction.created_contract_address @@ -1175,7 +1174,6 @@ defmodule BlockScoutWeb.API.RPC.ContractControllerTest do # index 0 should result in empty contractFactory index: 0, created_contract_address: contract_address, - block_hash: transaction.block_hash, block_number: transaction.block_number, transaction_index: transaction.index ) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs index ba6208e432d1..5a2f324b8e8d 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs @@ -269,7 +269,6 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do index: 0, type: :reward, error: error, - block_hash: transaction.block_hash, block_number: transaction.block_number ] diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs index 3631736d31ba..50b7304dad33 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs @@ -382,7 +382,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do end test "get Resolved Delegate Proxy contract info", %{conn: conn} do - proxy_address = insert(:address, contract_code: @resolved_delegate_proxy) + proxy_address = insert(:address, contract_code: @resolved_delegate_proxy, verified: true) proxy_smart_contract = insert(:smart_contract, address_hash: proxy_address.hash) transaction = @@ -2232,9 +2232,9 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do index: 1, block_number: transaction.block_number, transaction_index: transaction.index, - block_hash: transaction.block_hash, from_address: address ) + |> InternalTransaction.preload_addresses() internal_transaction_to = insert(:internal_transaction, @@ -2242,9 +2242,9 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do index: 2, block_number: transaction.block_number, transaction_index: transaction.index, - block_hash: transaction.block_hash, to_address: address ) + |> InternalTransaction.preload_addresses() request = get(conn, "/api/v2/addresses/#{address.hash}/internal-transactions") @@ -2285,7 +2285,6 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do index: 1, block_number: transaction.block_number, transaction_index: transaction.index, - block_hash: transaction.block_hash, from_address: address, to_address: insert(:address), gas: nil, @@ -2314,10 +2313,10 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do index: i, block_number: transaction.block_number, transaction_index: transaction.index, - block_hash: transaction.block_hash, from_address: address ) end + |> InternalTransaction.preload_addresses() request = get(conn, "/api/v2/addresses/#{address.hash}/internal-transactions") assert response = json_response(request, 200) @@ -2336,10 +2335,10 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do index: i, block_number: transaction.block_number, transaction_index: transaction.index, - block_hash: transaction.block_hash, to_address: address ) end + |> InternalTransaction.preload_addresses() filter = %{"filter" => "to"} request = get(conn, "/api/v2/addresses/#{address.hash}/internal-transactions", filter) @@ -3807,8 +3806,11 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do compare_item(address, address_json) end - test "check smart contract preload", %{conn: conn} do - smart_contract = insert(:smart_contract, address_hash: insert(:contract_address, fetched_coin_balance: 1).hash) + test "check smart contract properties", %{conn: conn} do + smart_contract = + insert(:smart_contract, + address_hash: insert(:contract_address, fetched_coin_balance: 1, verified: true).hash + ) request = get(conn, "/api/v2/addresses") response = json_response(request, 200) @@ -3982,7 +3984,6 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do index: x, block_number: transaction.block_number, transaction_index: transaction.index, - block_hash: transaction.block_hash, to_address: address ) end @@ -4025,7 +4026,6 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do index: x, block_number: transaction.block_number, transaction_index: transaction.index, - block_hash: transaction.block_hash, from_address: address ) end @@ -4078,7 +4078,6 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do index: x, block_number: transaction.block_number, transaction_index: transaction.index, - block_hash: transaction.block_hash, from_address: address ) end @@ -4125,7 +4124,6 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do index: x, block_number: transaction.block_number, transaction_index: transaction.index, - block_hash: transaction.block_hash, from_address: address ) end @@ -5683,7 +5681,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do assert internal_transaction.block_number == json["block_number"] assert to_string(internal_transaction.gas) == json["gas_limit"] assert internal_transaction.index == json["index"] - assert to_string(internal_transaction.transaction_hash) == json["transaction_hash"] + assert to_string(internal_transaction.transaction.hash) == json["transaction_hash"] assert Address.checksum(internal_transaction.from_address_hash) == json["from"]["hash"] assert Address.checksum(internal_transaction.to_address_hash) == json["to"]["hash"] end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/advanced_filter_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/advanced_filter_controller_test.exs index ff3714584c3f..78e5db9c41d2 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/advanced_filter_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/advanced_filter_controller_test.exs @@ -105,7 +105,6 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do for i <- 1..3 do insert(:internal_transaction, transaction: first_transaction, - block_hash: first_transaction.block_hash, block_number: first_transaction.block_number, transaction_index: first_transaction.index, index: i @@ -129,7 +128,6 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do for i <- 1..3 do insert(:internal_transaction, transaction: first_transaction, - block_hash: first_transaction.block_hash, block_number: first_transaction.block_number, transaction_index: first_transaction.index, index: i @@ -154,7 +152,6 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do for i <- 1..3 do insert(:internal_transaction, transaction: first_transaction, - block_hash: first_transaction.block_hash, block_number: first_transaction.block_number, transaction_index: first_transaction.index, index: i @@ -186,7 +183,6 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do for i <- 1..3 do insert(:internal_transaction, transaction: first_transaction, - block_hash: first_transaction.block_hash, block_number: first_transaction.block_number, transaction_index: first_transaction.index, index: i @@ -198,7 +194,6 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do for i <- 1..50 do insert(:internal_transaction, transaction: second_transaction, - block_hash: second_transaction.block_hash, block_number: second_transaction.block_number, transaction_index: second_transaction.index, index: i @@ -235,7 +230,6 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do for i <- 1..30 do insert(:internal_transaction, transaction: transaction, - block_hash: transaction.block_hash, block_number: transaction.block_number, transaction_index: transaction.index, index: i @@ -281,7 +275,6 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do insert(:internal_transaction, transaction: transaction, value: value, - block_hash: transaction.block_hash, block_number: transaction.block_number, transaction_index: transaction.index, index: i @@ -327,7 +320,6 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do insert(:internal_transaction, transaction: transaction, - block_hash: transaction.block_hash, block_number: transaction.block_number, transaction_index: transaction.index, index: i @@ -340,7 +332,6 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do transaction: transaction, to_address_hash: contract_address.hash, to_address: contract_address, - block_hash: transaction.block_hash, block_number: transaction.block_number, transaction_index: transaction.index, index: i @@ -384,13 +375,11 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do if i < 20 do transaction = insert(:transaction) |> with_block() - insert(:internal_transaction, + insert(:internal_transaction_create, transaction: transaction, - block_hash: transaction.block_hash, block_number: transaction.block_number, transaction_index: transaction.index, created_contract_address: address, - created_contract_address_hash: address.hash, to_address_hash: nil, to_address: nil, index: i @@ -407,7 +396,6 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do insert(:internal_transaction, transaction: transaction, - block_hash: transaction.block_hash, block_number: transaction.block_number, transaction_index: transaction.index, index: i @@ -493,7 +481,6 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do transaction: transaction, to_address_hash: contract_address.hash, to_address: contract_address, - block_hash: transaction.block_hash, block_number: transaction.block_number, transaction_index: transaction.index, index: i, @@ -506,7 +493,6 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do transaction: transaction, to_address_hash: contract_address.hash, to_address: contract_address, - block_hash: transaction.block_hash, block_number: transaction.block_number, transaction_index: transaction.index, index: i, @@ -564,7 +550,6 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do transaction: tx, transaction_index: tx.index, index: i + 1, - block_hash: tx.block_hash, block_number: tx.block_number, block: tx.block ) @@ -604,7 +589,6 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do transaction: transaction, from_address_hash: address.hash, from_address: address, - block_hash: transaction.block_hash, block_number: transaction.block_number, transaction_index: transaction.index, index: i + 1 @@ -620,7 +604,6 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do else insert(:internal_transaction, transaction: transaction, - block_hash: transaction.block_hash, block_number: transaction.block_number, transaction_index: transaction.index, index: i + 1 @@ -650,7 +633,6 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do transaction: transaction, from_address_hash: address.hash, from_address: address, - block_hash: transaction.block_hash, block_number: transaction.block_number, transaction_index: transaction.index, index: i + 1 @@ -666,7 +648,6 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do else insert(:internal_transaction, transaction: transaction, - block_hash: transaction.block_hash, block_number: transaction.block_number, transaction_index: transaction.index, index: i + 1 @@ -702,7 +683,6 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do transaction: transaction, from_address_hash: address_to_include.hash, from_address: address_to_include, - block_hash: transaction.block_hash, block_number: transaction.block_number, transaction_index: transaction.index, index: i + 1 @@ -718,7 +698,6 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do else insert(:internal_transaction, transaction: transaction, - block_hash: transaction.block_hash, block_number: transaction.block_number, transaction_index: transaction.index, index: i + 1 @@ -752,7 +731,6 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do transaction: transaction, to_address_hash: address.hash, to_address: address, - block_hash: transaction.block_hash, block_number: transaction.block_number, transaction_index: transaction.index, index: i + 1 @@ -768,7 +746,6 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do else insert(:internal_transaction, transaction: transaction, - block_hash: transaction.block_hash, block_number: transaction.block_number, transaction_index: transaction.index, index: i + 1 @@ -798,7 +775,6 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do transaction: transaction, to_address_hash: address.hash, to_address: address, - block_hash: transaction.block_hash, block_number: transaction.block_number, transaction_index: transaction.index, index: i + 1 @@ -814,7 +790,6 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do else insert(:internal_transaction, transaction: transaction, - block_hash: transaction.block_hash, block_number: transaction.block_number, transaction_index: transaction.index, index: i + 1 @@ -850,7 +825,6 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do transaction: transaction, to_address_hash: address_to_include.hash, to_address: address_to_include, - block_hash: transaction.block_hash, block_number: transaction.block_number, transaction_index: transaction.index, index: i + 1 @@ -866,7 +840,6 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do else insert(:internal_transaction, transaction: transaction, - block_hash: transaction.block_hash, block_number: transaction.block_number, transaction_index: transaction.index, index: i + 1 @@ -902,7 +875,6 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do transaction: transaction, from_address_hash: from_address.hash, from_address: from_address, - block_hash: transaction.block_hash, block_number: transaction.block_number, transaction_index: transaction.index, index: i + 1 @@ -923,7 +895,6 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do transaction: transaction, to_address_hash: to_address.hash, to_address: to_address, - block_hash: transaction.block_hash, block_number: transaction.block_number, transaction_index: transaction.index, index: i + 1 @@ -953,7 +924,6 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do to_address: to_address, from_address_hash: from_address.hash, from_address: from_address, - block_hash: transaction.block_hash, block_number: transaction.block_number, transaction_index: transaction.index, index: i + 1 @@ -972,7 +942,6 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do true -> insert(:internal_transaction, transaction: transaction, - block_hash: transaction.block_hash, block_number: transaction.block_number, transaction_index: transaction.index, index: i + 1 @@ -1010,7 +979,6 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do insert(:internal_transaction, transaction: transaction, - block_hash: transaction.block_hash, block_number: transaction.block_number, transaction_index: transaction.index, index: 51, @@ -1036,7 +1004,6 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do insert(:internal_transaction, transaction: transaction, - block_hash: transaction.block_hash, block_number: transaction.block_number, transaction_index: transaction.index, index: i + 1, @@ -1056,7 +1023,6 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do insert(:internal_transaction, transaction: transaction, - block_hash: transaction.block_hash, block_number: transaction.block_number, transaction_index: transaction.index, index: i + 1, @@ -1100,7 +1066,6 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do transaction: transaction, from_address_hash: from_address.hash, from_address: from_address, - block_hash: transaction.block_hash, block_number: transaction.block_number, transaction_index: transaction.index, index: i + 1 @@ -1121,7 +1086,6 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do transaction: transaction, to_address_hash: to_address.hash, to_address: to_address, - block_hash: transaction.block_hash, block_number: transaction.block_number, transaction_index: transaction.index, index: i + 1 @@ -1151,7 +1115,6 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do to_address: to_address, from_address_hash: from_address.hash, from_address: from_address, - block_hash: transaction.block_hash, block_number: transaction.block_number, transaction_index: transaction.index, index: i + 1 @@ -1170,7 +1133,6 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do true -> insert(:internal_transaction, transaction: transaction, - block_hash: transaction.block_hash, block_number: transaction.block_number, transaction_index: transaction.index, index: i + 1 @@ -1197,7 +1159,6 @@ defmodule BlockScoutWeb.API.V2.AdvancedFilterControllerTest do insert(:internal_transaction, transaction: transaction, - block_hash: transaction.block_hash, block_number: transaction.block_number, transaction_index: transaction.index, index: 1, diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/block_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/block_controller_test.exs index 927bfb581590..d13a07415500 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/block_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/block_controller_test.exs @@ -878,8 +878,7 @@ defmodule BlockScoutWeb.API.V2.BlockControllerTest do transaction: transaction, index: 0, block_number: transaction.block_number, - transaction_index: transaction.index, - block_hash: transaction.block_hash + transaction_index: transaction.index ) internal_transactions = @@ -894,10 +893,10 @@ defmodule BlockScoutWeb.API.V2.BlockControllerTest do transaction: transaction, index: index, block_number: transaction.block_number, - transaction_index: transaction.index, - block_hash: transaction.block_hash + transaction_index: transaction.index ) end) + |> InternalTransaction.preload_addresses() request = get(conn, "/api/v2/blocks/#{block.hash}/internal-transactions") assert response = json_response(request, 200) @@ -1006,7 +1005,7 @@ defmodule BlockScoutWeb.API.V2.BlockControllerTest do assert internal_transaction.block_number == json["block_number"] assert to_string(internal_transaction.gas) == json["gas_limit"] assert internal_transaction.index == json["index"] - assert to_string(internal_transaction.transaction_hash) == json["transaction_hash"] + assert to_string(internal_transaction.transaction.hash) == json["transaction_hash"] assert Address.checksum(internal_transaction.from_address_hash) == json["from"]["hash"] assert Address.checksum(internal_transaction.to_address_hash) == json["to"]["hash"] end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/celo_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/celo_controller_test.exs new file mode 100644 index 000000000000..2e7410f3b56a --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/celo_controller_test.exs @@ -0,0 +1,194 @@ +defmodule BlockScoutWeb.API.V2.CeloControllerTest do + use BlockScoutWeb.ConnCase + + use Utils.CompileTimeEnvHelper, + chain_type: [:explorer, :chain_type], + chain_identity: [:explorer, :chain_identity] + + if @chain_identity == {:optimism, :celo} do + alias Explorer.Chain.Celo.ElectionReward + + setup do + celo_token = insert(:token) + usd_token = insert(:token) + + original_core_contracts_config = + Application.get_env(:explorer, Explorer.Chain.Cache.CeloCoreContracts) + + Application.put_env(:explorer, Explorer.Chain.Cache.CeloCoreContracts, + contracts: %{ + "addresses" => %{ + "Accounts" => [], + "Election" => [], + "EpochRewards" => [], + "FeeHandler" => [], + "GasPriceMinimum" => [], + "GoldToken" => [ + %{"address" => to_string(celo_token.contract_address_hash), "updated_at_block_number" => 0} + ], + "Governance" => [], + "LockedGold" => [], + "Reserve" => [], + "StableToken" => [ + %{"address" => to_string(usd_token.contract_address_hash), "updated_at_block_number" => 0} + ], + "Validators" => [] + } + } + ) + + original_celo_config = Application.get_env(:explorer, :celo) + + on_exit(fn -> + Application.put_env( + :explorer, + Explorer.Chain.Cache.CeloCoreContracts, + original_core_contracts_config + ) + + Application.put_env(:explorer, :celo, original_celo_config) + end) + + {:ok, %{celo_token: celo_token, usd_token: usd_token}} + end + + describe "/api/v2/celo/epochs" do + test "returns empty list", %{conn: conn} do + request = get(conn, "/api/v2/celo/epochs") + assert response = json_response(request, 200) + assert response["items"] == [] + assert response["next_page_params"] == nil + end + + test "returns epochs", %{conn: conn} do + epoch = + insert(:celo_epoch, + number: 1, + fetched?: true, + start_block_number: 0, + end_block_number: 17_279 + ) + + request = get(conn, "/api/v2/celo/epochs") + assert response = json_response(request, 200) + assert [item] = response["items"] + assert item["number"] == epoch.number + assert item["start_block_number"] == epoch.start_block_number + assert item["end_block_number"] == epoch.end_block_number + assert item["is_finalized"] == true + end + end + + describe "/api/v2/celo/epochs/:number" do + test "returns 404 for non-existing epoch", %{conn: conn} do + request = get(conn, "/api/v2/celo/epochs/100") + assert %{"message" => "Not found"} = json_response(request, 404) + end + + test "returns 422 for invalid epoch number", %{conn: conn} do + request = get(conn, "/api/v2/celo/epochs/invalid") + assert %{"errors" => [_]} = json_response(request, 422) + end + + test "returns unfetched epoch with null aggregated rewards", %{conn: conn} do + insert(:celo_epoch, + number: 1, + fetched?: false, + start_block_number: 0, + end_block_number: 17_279 + ) + + request = get(conn, "/api/v2/celo/epochs/1") + assert response = json_response(request, 200) + assert response["number"] == 1 + assert response["is_finalized"] == false + assert response["aggregated_election_rewards"] == nil + end + + test "returns fetched epoch with aggregated rewards", %{conn: conn} do + insert(:celo_epoch, + number: 1, + fetched?: true, + start_block_number: 0, + end_block_number: 17_279 + ) + + for type <- ElectionReward.types() do + insert(:celo_aggregated_election_reward, + epoch_number: 1, + type: type, + sum: 1000, + count: 5 + ) + end + + request = get(conn, "/api/v2/celo/epochs/1") + assert response = json_response(request, 200) + assert response["number"] == 1 + assert response["is_finalized"] == true + + rewards = response["aggregated_election_rewards"] + assert is_map(rewards) + + for type <- ElectionReward.types() do + assert %{"total" => _, "count" => 5, "token" => _} = rewards[to_string(type)] + end + end + + test "returns L2 epoch with null delegated_payment in aggregated rewards", %{conn: conn} do + # Epoch 2 starts at block 17280. Setting l2_migration_block to 17280 + # makes epoch 2 an L2 epoch (epoch_number >= migration epoch number). + Application.put_env(:explorer, :celo, l2_migration_block: 17_280) + + insert(:celo_epoch, + number: 2, + fetched?: true, + start_block_number: 17_280, + end_block_number: 34_559 + ) + + for type <- ElectionReward.types() do + insert(:celo_aggregated_election_reward, + epoch_number: 2, + type: type, + sum: 1000, + count: 5 + ) + end + + request = get(conn, "/api/v2/celo/epochs/2") + assert response = json_response(request, 200) + assert response["type"] == "L2" + + rewards = response["aggregated_election_rewards"] + assert rewards["delegated_payment"] == nil + + for type <- ElectionReward.types() -- [:delegated_payment] do + assert %{"total" => _, "count" => 5, "token" => _} = rewards[to_string(type)] + end + end + end + + describe "/api/v2/celo/epochs/:number/election-rewards/:type" do + test "returns empty list", %{conn: conn} do + request = get(conn, "/api/v2/celo/epochs/1/election-rewards/voter") + assert response = json_response(request, 200) + assert response["items"] == [] + assert response["next_page_params"] == nil + end + + test "returns 422 for invalid epoch number", %{conn: conn} do + request = get(conn, "/api/v2/celo/epochs/invalid/election-rewards/voter") + assert %{"errors" => [_]} = json_response(request, 422) + end + + test "accepts both hyphenated and underscored delegated_payment type in URL", %{conn: conn} do + request = get(conn, "/api/v2/celo/epochs/1/election-rewards/delegated-payment") + assert %{"items" => []} = json_response(request, 200) + + request = get(conn, "/api/v2/celo/epochs/1/election-rewards/delegated_payment") + assert %{"items" => []} = json_response(request, 200) + end + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/csv_export_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/csv_export_controller_test.exs index f5de376a6d1d..49d05f3f346b 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/csv_export_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/csv_export_controller_test.exs @@ -1,8 +1,11 @@ defmodule BlockScoutWeb.Api.V2.CsvExportControllerTest do use BlockScoutWeb.ConnCase, async: true use ExUnit.Case, async: false - alias Explorer.Chain.Address + use Oban.Testing, repo: Explorer.Repo + + use Utils.CompileTimeEnvHelper, chain_identity: [:explorer, :chain_identity] + alias Explorer.Chain.Address import Mox setup :verify_on_exit! @@ -206,6 +209,33 @@ defmodule BlockScoutWeb.Api.V2.CsvExportControllerTest do csv_setup() end + test "exports transactions to csv when recaptcha is disabled", %{conn: conn} do + init_config = Application.get_env(:block_scout_web, :recaptcha) + Application.put_env(:block_scout_web, :recaptcha, is_disabled: true) + + address = insert(:address) + + :transaction + |> insert(from_address: address) + |> with_block() + + {:ok, now} = DateTime.now("Etc/UTC") + from_period = DateTime.add(now, -1, :minute) |> DateTime.to_iso8601() + to_period = now |> DateTime.to_iso8601() + + conn = + get(conn, "/api/v2/addresses/#{Address.checksum(address.hash)}/transactions/csv", %{ + "from_period" => from_period, + "to_period" => to_period + }) + + assert conn.status == 200 + assert conn.resp_body =~ "TxHash" + assert conn.resp_body |> String.split("\n") |> Enum.count() >= 2 + + Application.put_env(:block_scout_web, :recaptcha, init_config) + end + test "download csv file with transactions", %{conn: conn, v2_secret_key: recaptcha_secret_key} do expected_body = "secret=#{recaptcha_secret_key}&response=123" @@ -267,6 +297,42 @@ defmodule BlockScoutWeb.Api.V2.CsvExportControllerTest do csv_setup() end + test "exports internal transactions to csv when recaptcha is disabled", %{conn: conn} do + init_config = Application.get_env(:block_scout_web, :recaptcha) + Application.put_env(:block_scout_web, :recaptcha, is_disabled: true) + + address = insert(:address) + + transaction = + :transaction + |> insert() + |> with_block() + + insert(:internal_transaction, + index: 0, + transaction: transaction, + from_address: address, + block_number: transaction.block_number, + transaction_index: transaction.index + ) + + {:ok, now} = DateTime.now("Etc/UTC") + from_period = DateTime.add(now, -1, :day) |> DateTime.to_iso8601() + to_period = now |> DateTime.to_iso8601() + + conn = + get(conn, "/api/v2/addresses/#{Address.checksum(address.hash)}/internal-transactions/csv", %{ + "from_period" => from_period, + "to_period" => to_period + }) + + assert conn.status == 200 + assert conn.resp_body =~ "TxHash" + assert conn.resp_body |> String.split("\n") |> Enum.count() >= 2 + + Application.put_env(:block_scout_web, :recaptcha, init_config) + end + test "download csv file with internal transactions", %{conn: conn, v2_secret_key: recaptcha_secret_key} do expected_body = "secret=#{recaptcha_secret_key}&response=123" @@ -307,7 +373,6 @@ defmodule BlockScoutWeb.Api.V2.CsvExportControllerTest do transaction: transaction_1, from_address: address, block_number: transaction_1.block_number, - block_hash: transaction_1.block_hash, transaction_index: transaction_1.index ) @@ -316,17 +381,15 @@ defmodule BlockScoutWeb.Api.V2.CsvExportControllerTest do transaction: transaction_2, to_address: address, block_number: transaction_2.block_number, - block_hash: transaction_2.block_hash, transaction_index: transaction_2.index ) - insert(:internal_transaction, + insert(:internal_transaction_create, index: 2, transaction: transaction_3, created_contract_address: address, to_address: nil, block_number: transaction_3.block_number, - block_hash: transaction_3.block_hash, transaction_index: transaction_3.index ) @@ -358,11 +421,455 @@ defmodule BlockScoutWeb.Api.V2.CsvExportControllerTest do end end + describe "GET /api/v2/csv-exports/:uuid" do + setup do + bypass = Bypass.open() + original_config = Application.get_env(:explorer, Explorer.Chain.CsvExport) + original_tesla = Application.get_env(:tesla, :adapter) + + config = + (original_config || []) + |> Keyword.put(:max_pending_tasks_per_ip, 5) + |> Keyword.put(:gokapi_url, "http://localhost:#{bypass.port}") + |> Keyword.put(:gokapi_api_key, "test-api-key") + |> Keyword.put(:gokapi_upload_expiry_days, 1) + |> Keyword.put(:gokapi_upload_allowed_downloads, 1) + + Application.put_env(:explorer, Explorer.Chain.CsvExport, config) + Application.put_env(:tesla, :adapter, Tesla.Adapter.Hackney) + + on_exit(fn -> + Application.put_env(:tesla, :adapter, original_tesla) + + if original_config do + Application.put_env(:explorer, Explorer.Chain.CsvExport, original_config) + else + Application.delete_env(:explorer, Explorer.Chain.CsvExport) + end + end) + + {:ok, bypass: bypass} + end + + test "returns 200 with pending status for pending request", %{conn: conn} do + ip_hash = :crypto.hash(:sha256, "127.0.0.1") + + request = + %Explorer.Chain.CsvExport.Request{ + remote_ip_hash: ip_hash, + file_id: nil, + status: :pending + } + |> Explorer.Repo.insert!() + + conn = get(conn, "/api/v2/csv-exports/#{request.id}") + + assert %{"status" => "pending", "file_id" => nil} = json_response(conn, 200) + end + + test "returns 200 with completed status and file_id for completed request", %{ + conn: conn, + bypass: bypass + } do + ip_hash = :crypto.hash(:sha256, "127.0.0.1") + file_id = "test-file-123" + expires_at_to_expect = DateTime.utc_now() |> DateTime.truncate(:second) + + request = + %Explorer.Chain.CsvExport.Request{ + remote_ip_hash: ip_hash, + file_id: file_id, + status: :completed, + expires_at: expires_at_to_expect + } + |> Explorer.Repo.insert!() + + Bypass.expect(bypass, fn conn -> + assert conn.request_path =~ "/api/files/list/#{file_id}" + Plug.Conn.resp(conn, 200, "") + end) + + conn = get(conn, "/api/v2/csv-exports/#{request.id}") + + assert %{"status" => "completed", "file_id" => ^file_id, "expires_at" => expires_at} = json_response(conn, 200) + + assert expires_at == expires_at_to_expect |> DateTime.to_iso8601() + end + + test "returns 200 with failed status for failed request", %{conn: conn} do + ip_hash = :crypto.hash(:sha256, "127.0.0.1") + + request = + %Explorer.Chain.CsvExport.Request{ + remote_ip_hash: ip_hash, + file_id: nil, + status: :failed + } + |> Explorer.Repo.insert!() + + conn = get(conn, "/api/v2/csv-exports/#{request.id}") + + assert %{"status" => "failed", "file_id" => nil, "expires_at" => nil} = json_response(conn, 200) + end + + test "returns 404 for non-existent UUID", %{conn: conn} do + fake_uuid = "11111111-1111-1111-1111-111111111111" + + conn = get(conn, "/api/v2/csv-exports/#{fake_uuid}") + + assert %{"message" => "Not found"} = json_response(conn, 404) + end + + test "returns 422 for malformed UUID", %{conn: conn} do + conn = get(conn, "/api/v2/csv-exports/not-a-valid-uuid") + + assert %{ + "errors" => [ + %{ + "detail" => "Invalid format. Expected :uuid", + "source" => %{"pointer" => "/uuid_param"}, + "title" => "Invalid value" + } + ] + } = json_response(conn, 422) + end + + test "returns 404 when file is removed on gokapi", %{ + conn: conn, + bypass: bypass + } do + ip_hash = :crypto.hash(:sha256, "127.0.0.1") + file_id = "test-file-123" + + request = + %Explorer.Chain.CsvExport.Request{ + remote_ip_hash: ip_hash, + file_id: file_id, + status: :completed + } + |> Explorer.Repo.insert!() + + Bypass.expect(bypass, fn conn -> + assert conn.request_path =~ "/api/files/list/#{file_id}" + Plug.Conn.resp(conn, 404, "") + end) + + conn = get(conn, "/api/v2/csv-exports/#{request.id}") + + assert %{"message" => "Not found"} = json_response(conn, 404) + end + end + + describe "async mode export endpoints" do + setup do + csv_setup() + bypass = Bypass.open() + original_config = Application.get_env(:explorer, Explorer.Chain.CsvExport) + original_tesla = Application.get_env(:tesla, :adapter) + + config = + (original_config || []) + |> Keyword.put(:async?, true) + |> Keyword.put(:max_pending_tasks_per_ip, 3) + |> Keyword.put(:gokapi_url, "http://localhost:#{bypass.port}") + |> Keyword.put(:gokapi_api_key, "test-api-key") + |> Keyword.put(:gokapi_upload_expiry_days, 1) + |> Keyword.put(:gokapi_upload_allowed_downloads, 1) + + Application.put_env(:explorer, Explorer.Chain.CsvExport, config) + Application.put_env(:tesla, :adapter, Tesla.Adapter.Hackney) + + on_exit(fn -> + Application.put_env(:tesla, :adapter, original_tesla) + + if original_config do + Application.put_env(:explorer, Explorer.Chain.CsvExport, original_config) + else + Application.delete_env(:explorer, Explorer.Chain.CsvExport) + end + end) + + {:ok, bypass: bypass} + end + + test "returns 202 with request_id for token-transfers export when async enabled", %{conn: conn} do + address = insert(:address) + + :transaction + |> insert(from_address: address) + |> with_block() + + {:ok, now} = DateTime.now("Etc/UTC") + from_period = DateTime.add(now, -1, :minute) |> DateTime.to_iso8601() + to_period = now |> DateTime.to_iso8601() + + conn = + get(conn, "/api/v2/addresses/#{Address.checksum(address.hash)}/token-transfers/csv", %{ + "from_period" => from_period, + "to_period" => to_period + }) + + assert %{"request_id" => request_id} = json_response(conn, 202) + assert is_binary(request_id) + end + + test "returns 202 with request_id for transactions export when async enabled", %{conn: conn} do + address = insert(:address) + + :transaction + |> insert(from_address: address) + |> with_block() + + {:ok, now} = DateTime.now("Etc/UTC") + from_period = DateTime.add(now, -1, :minute) |> DateTime.to_iso8601() + to_period = now |> DateTime.to_iso8601() + + conn = + get(conn, "/api/v2/addresses/#{Address.checksum(address.hash)}/transactions/csv", %{ + "from_period" => from_period, + "to_period" => to_period + }) + + assert %{"request_id" => request_id} = json_response(conn, 202) + assert is_binary(request_id) + end + + test "returns 202 with request_id for internal-transactions export when async enabled", %{ + conn: conn + } do + address = insert(:address) + + transaction = + :transaction + |> insert() + |> with_block() + + insert(:internal_transaction, + index: 0, + transaction: transaction, + from_address: address, + block_number: transaction.block_number, + transaction_index: transaction.index + ) + + {:ok, now} = DateTime.now("Etc/UTC") + from_period = DateTime.add(now, -1, :day) |> DateTime.to_iso8601() + to_period = now |> DateTime.to_iso8601() + + conn = + get(conn, "/api/v2/addresses/#{Address.checksum(address.hash)}/internal-transactions/csv", %{ + "from_period" => from_period, + "to_period" => to_period + }) + + assert %{"request_id" => request_id} = json_response(conn, 202) + assert is_binary(request_id) + end + + test "returns 202 with request_id for logs export when async enabled", %{conn: conn} do + address = insert(:address) + + transaction = + :transaction + |> insert() + |> with_block() + + insert(:log, + address: address, + index: 0, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number + ) + + {:ok, now} = DateTime.now("Etc/UTC") + from_period = DateTime.add(now, -1, :minute) |> DateTime.to_iso8601() + to_period = now |> DateTime.to_iso8601() + + conn = + get(conn, "/api/v2/addresses/#{Address.checksum(address.hash)}/logs/csv", %{ + "from_period" => from_period, + "to_period" => to_period + }) + + assert conn.status == 202 + body = Jason.decode!(conn.resp_body) + assert Map.has_key?(body, "request_id") + assert is_binary(body["request_id"]) + end + + test "returns 202 with request_id for token holders export when async enabled", %{conn: conn} do + token = insert(:token, type: "ERC-20", decimals: 18) + + insert(:address_current_token_balance, + token_contract_address_hash: token.contract_address_hash, + address: insert(:address), + value: 100_000_000_000_000_000_000 + ) + + conn = get(conn, "/api/v2/tokens/#{Address.checksum(token.contract_address_hash)}/holders/csv") + + assert %{"request_id" => request_id} = json_response(conn, 202) + assert is_binary(request_id) + end + + if @chain_identity == {:optimism, :celo} do + test "returns 202 with request_id for celo election-rewards export when async enabled", %{ + conn: conn + } do + address = insert(:address) + + {:ok, now} = DateTime.now("Etc/UTC") + from_period = DateTime.add(now, -1, :day) |> DateTime.to_iso8601() + to_period = now |> DateTime.to_iso8601() + + conn = + get(conn, "/api/v2/addresses/#{Address.checksum(address.hash)}/celo/election-rewards/csv", %{ + "from_period" => from_period, + "to_period" => to_period + }) + + assert %{"request_id" => request_id} = json_response(conn, 202) + assert is_binary(request_id) + end + end + + test "returns 409 when pending request limit is reached", %{conn: conn} do + address = insert(:address) + + :transaction + |> insert(from_address: address) + |> with_block() + + {:ok, now} = DateTime.now("Etc/UTC") + from_period = DateTime.add(now, -1, :minute) |> DateTime.to_iso8601() + to_period = now |> DateTime.to_iso8601() + + args = %{ + address_hash: to_string(address.hash), + from_period: from_period, + to_period: to_period, + filter_type: nil, + filter_value: nil, + show_scam_tokens?: nil, + module: "Elixir.Explorer.Chain.CsvExport.Address.TokenTransfers" + } + + 1..3 + |> Enum.each(fn _ -> + {:ok, _} = Explorer.Chain.CsvExport.Request.create("127.0.0.1", args) + end) + + conn = + conn + |> get("/api/v2/addresses/#{Address.checksum(address.hash)}/token-transfers/csv", %{ + "from_period" => from_period, + "to_period" => to_period + }) + + assert json_response(conn, 409) == %{"error" => "You can only have 3 pending requests at a time"} + end + end + + describe "GET /api/v2/tokens/:hash/holders/csv" do + setup do + result = csv_setup() + + original_config = Application.get_env(:explorer, Explorer.Chain.CsvExport) + config = (original_config || []) |> Keyword.put(:async?, false) + Application.put_env(:explorer, Explorer.Chain.CsvExport, config) + + on_exit(fn -> + if original_config do + Application.put_env(:explorer, Explorer.Chain.CsvExport, original_config) + else + Application.delete_env(:explorer, Explorer.Chain.CsvExport) + end + end) + + result + end + + test "exports token holders to csv in sync mode", %{conn: conn} do + token = insert(:token, type: "ERC-20", decimals: 18) + + insert(:address_current_token_balance, + token_contract_address_hash: token.contract_address_hash, + address: insert(:address), + value: 100_000_000_000_000_000_000 + ) + + conn = get(conn, "/api/v2/tokens/#{Address.checksum(token.contract_address_hash)}/holders/csv") + + assert conn.status == 200 + assert conn.resp_body =~ "HolderAddress" + assert conn.resp_body =~ "Balance" + end + + test "returns 404 for non-existent token", %{conn: conn} do + fake_hash = "0x0000000000000000000000000000000000000001" + + conn = get(conn, "/api/v2/tokens/#{fake_hash}/holders/csv") + + assert %{"message" => "Not found"} = json_response(conn, 404) + end + + test "returns 422 for invalid token hash", %{conn: conn} do + conn = get(conn, "/api/v2/tokens/not-a-valid-hash/holders/csv") + + assert %{ + "errors" => [ + %{ + "detail" => "Invalid format. Expected ~r/^0x([A-Fa-f0-9]{40})$/", + "source" => %{"pointer" => "/address_hash_param"}, + "title" => "Invalid value" + } + ] + } = json_response(conn, 422) + end + end + describe "GET logs_csv/2" do setup do csv_setup() end + test "exports logs to csv when recaptcha is disabled", %{conn: conn} do + init_config = Application.get_env(:block_scout_web, :recaptcha) + Application.put_env(:block_scout_web, :recaptcha, is_disabled: true) + + address = insert(:address) + + transaction = + :transaction + |> insert() + |> with_block() + + insert(:log, + address: address, + index: 0, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number + ) + + {:ok, now} = DateTime.now("Etc/UTC") + from_period = DateTime.add(now, -1, :minute) |> DateTime.to_iso8601() + to_period = now |> DateTime.to_iso8601() + + conn = + get(conn, "/api/v2/addresses/#{Address.checksum(address.hash)}/logs/csv", %{ + "from_period" => from_period, + "to_period" => to_period + }) + + assert conn.status == 200 + assert conn.resp_body =~ "TxHash" + assert conn.resp_body |> String.split("\n") |> Enum.count() >= 2 + + Application.put_env(:block_scout_web, :recaptcha, init_config) + end + test "download csv file with logs", %{conn: conn, v2_secret_key: recaptcha_secret_key} do expected_body = "secret=#{recaptcha_secret_key}&response=123" @@ -483,6 +990,37 @@ defmodule BlockScoutWeb.Api.V2.CsvExportControllerTest do end end + if @chain_identity == {:optimism, :celo} do + describe "GET celo/election-rewards/csv" do + setup do + csv_setup() + end + + test "exports Celo election rewards to csv when recaptcha is disabled", %{conn: conn} do + init_config = Application.get_env(:block_scout_web, :recaptcha) + Application.put_env(:block_scout_web, :recaptcha, is_disabled: true) + + address = insert(:address) + + {:ok, now} = DateTime.now("Etc/UTC") + from_period = DateTime.add(now, -1, :day) |> DateTime.to_iso8601() + to_period = now |> DateTime.to_iso8601() + + conn = + get(conn, "/api/v2/addresses/#{Address.checksum(address.hash)}/celo/election-rewards/csv", %{ + "from_period" => from_period, + "to_period" => to_period + }) + + assert conn.status == 200 + assert conn.resp_body =~ "EpochNumber" + assert conn.resp_body =~ "BlockNumber" + + Application.put_env(:block_scout_web, :recaptcha, init_config) + end + end + end + defp csv_setup() do original_config = :persistent_term.get(:rate_limit_config) old_recaptcha_env = Application.get_env(:block_scout_web, :recaptcha) @@ -509,6 +1047,12 @@ defmodule BlockScoutWeb.Api.V2.CsvExportControllerTest do bucket_key_prefix: "api/v2/addresses/:param/election-rewards/csv_", isolate_rate_limit?: true }, + ["api", "v2", "addresses", ":param", "celo", "election-rewards", "csv"] => %{ + ip: %{period: 3_600_000, limit: 1}, + recaptcha_to_bypass_429: true, + bucket_key_prefix: "api/v2/addresses/:param/celo/election-rewards/csv_", + isolate_rate_limit?: true + }, ["api", "v2", "addresses", ":param", "internal-transactions", "csv"] => %{ ip: %{period: 3_600_000, limit: 1}, recaptcha_to_bypass_429: true, diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/internal_transaction_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/internal_transaction_controller_test.exs index 7b24cdae528e..e09652da7abd 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/internal_transaction_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/internal_transaction_controller_test.exs @@ -31,7 +31,6 @@ defmodule BlockScoutWeb.API.V2.InternalTransactionControllerTest do transaction: tx, transaction_index: 0, block_number: tx.block_number, - block_hash: tx.block_hash, index: 1 ) @@ -50,7 +49,6 @@ defmodule BlockScoutWeb.API.V2.InternalTransactionControllerTest do transaction: transaction, transaction_index: 0, block_number: transaction.block_number, - block_hash: transaction.block_hash, index: 1 ) @@ -62,12 +60,11 @@ defmodule BlockScoutWeb.API.V2.InternalTransactionControllerTest do transaction: transaction_2, transaction_index: 0, block_number: transaction_2.block_number, - block_hash: transaction_2.block_hash, index: i ) end - internal_transactions = [internal_transaction | internal_transactions] + internal_transactions = InternalTransaction.preload_addresses([internal_transaction | internal_transactions]) request = get(conn, "/api/v2/internal-transactions") assert response = json_response(request, 200) @@ -89,7 +86,6 @@ defmodule BlockScoutWeb.API.V2.InternalTransactionControllerTest do transaction: tx, transaction_index: 0, block_number: tx.block_number, - block_hash: tx.block_hash, index: 0, type: :call ) @@ -100,7 +96,6 @@ defmodule BlockScoutWeb.API.V2.InternalTransactionControllerTest do transaction: tx, transaction_index: 0, block_number: tx.block_number, - block_hash: tx.block_hash, index: 1, type: :call ) @@ -111,7 +106,6 @@ defmodule BlockScoutWeb.API.V2.InternalTransactionControllerTest do transaction: tx, transaction_index: 0, block_number: tx.block_number, - block_hash: tx.block_hash, index: 2, type: :call ) @@ -140,7 +134,6 @@ defmodule BlockScoutWeb.API.V2.InternalTransactionControllerTest do transaction: tx, transaction_index: 0, block_number: tx.block_number, - block_hash: tx.block_hash, index: 0, type: :call ) @@ -151,7 +144,6 @@ defmodule BlockScoutWeb.API.V2.InternalTransactionControllerTest do transaction: tx, transaction_index: 0, block_number: tx.block_number, - block_hash: tx.block_hash, index: i, type: :call ) @@ -180,7 +172,7 @@ defmodule BlockScoutWeb.API.V2.InternalTransactionControllerTest do defp compare_item(%InternalTransaction{} = internal_transaction, json) do assert Address.checksum(internal_transaction.from_address_hash) == json["from"]["hash"] assert Address.checksum(internal_transaction.to_address_hash) == json["to"]["hash"] - assert to_string(internal_transaction.transaction_hash) == json["transaction_hash"] + assert to_string(internal_transaction.transaction.hash) == json["transaction_hash"] assert internal_transaction.block_number == json["block_number"] assert internal_transaction.transaction_index == json["transaction_index"] assert internal_transaction.index == json["index"] diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/token_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/token_controller_test.exs index 98e533991791..8bc0a105c54a 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/token_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/token_controller_test.exs @@ -2645,6 +2645,54 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do assert length(items) == 1 assert next_page_params == nil end + + test "regression: sort and order params are applied for bridged tokens", %{conn: conn} do + parameter_names = + BlockScoutWeb.API.V2.TokenController.open_api_operation(:bridged_tokens_list).parameters + |> Enum.map(fn + %OpenApiSpex.Parameter{name: name} -> to_string(name) + %OpenApiSpex.Reference{} -> nil + end) + |> Enum.reject(&is_nil/1) + + assert "sort" in parameter_names + assert "order" in parameter_names + + high_fiat_token = insert(:token, %{total_supply: 20_000, name: "BridgeTokenHigh", fiat_value: 200}) + low_fiat_token = insert(:token, %{total_supply: 10_000, name: "BridgeTokenLow", fiat_value: 100}) + + for token <- [high_fiat_token, low_fiat_token] do + Explorer.Repo.update_all( + from(t in Explorer.Chain.Token, where: t.contract_address_hash == ^token.contract_address_hash), + set: [bridged: true] + ) + + {:ok, _bridged_token} = + Explorer.Repo.insert(%Explorer.Chain.BridgedToken{ + home_token_contract_address_hash: token.contract_address_hash, + foreign_chain_id: 1, + foreign_token_contract_address_hash: build(:address).hash, + type: "omni" + }) + end + + base_params = %{ + "q" => "BridgeToken", + "chain_ids" => "1", + "sort" => "fiat_value" + } + + request_asc = get(conn, "/api/v2/tokens/bridged", Map.put(base_params, "order", "asc")) + request_desc = get(conn, "/api/v2/tokens/bridged", Map.put(base_params, "order", "desc")) + + assert %{"items" => [asc_first | _] = asc_items} = json_response(request_asc, 200) + assert %{"items" => [desc_first | _] = desc_items} = json_response(request_desc, 200) + assert length(asc_items) == 2 + assert length(desc_items) == 2 + + assert asc_first["address_hash"] == Address.checksum(low_fiat_token.contract_address_hash) + assert desc_first["address_hash"] == Address.checksum(high_fiat_token.contract_address_hash) + end end end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/transaction_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/transaction_controller_test.exs index a403bcdc8161..f376ee8b2ae5 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/transaction_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/transaction_controller_test.exs @@ -605,8 +605,7 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do transaction: transaction, index: 0, block_number: transaction.block_number, - transaction_index: transaction.index, - block_hash: transaction.block_hash + transaction_index: transaction.index ) internal_transaction = @@ -614,9 +613,9 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do transaction: transaction, index: 1, block_number: transaction.block_number, - transaction_index: transaction.index, - block_hash: transaction.block_hash + transaction_index: transaction.index ) + |> InternalTransaction.preload_addresses() transaction_1 = :transaction @@ -629,8 +628,7 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do transaction: transaction_1, index: index, block_number: transaction_1.block_number, - transaction_index: transaction_1.index, - block_hash: transaction_1.block_hash + transaction_index: transaction_1.index ) end) @@ -652,8 +650,7 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do transaction: transaction, index: 0, block_number: transaction.block_number, - transaction_index: transaction.index, - block_hash: transaction.block_hash + transaction_index: transaction.index ) internal_transactions = @@ -663,10 +660,10 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do transaction: transaction, index: index, block_number: transaction.block_number, - transaction_index: transaction.index, - block_hash: transaction.block_hash + transaction_index: transaction.index ) end) + |> InternalTransaction.preload_addresses() request = get(conn, "/api/v2/transactions/#{to_string(transaction.hash)}/internal-transactions") assert response = json_response(request, 200) @@ -1629,7 +1626,6 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do index: 0, block_number: transaction.block_number, transaction_index: transaction.index, - block_hash: transaction.block_hash, value: %Wei{value: Decimal.new(7)}, from_address_hash: internal_transaction_from.hash, from_address: internal_transaction_from, @@ -1693,7 +1689,6 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do index: 0, block_number: transaction.block_number, transaction_index: transaction.index, - block_hash: transaction.block_hash, value: %Wei{value: Decimal.new(7)}, from_address_hash: internal_transaction_from.hash, from_address: internal_transaction_from, @@ -1708,7 +1703,6 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do index: 1, block_number: transaction.block_number, transaction_index: transaction.index, - block_hash: transaction.block_hash, value: %Wei{value: Decimal.new(7)}, from_address_hash: internal_transaction_from_delegatecall.hash, from_address: internal_transaction_from_delegatecall, @@ -1722,7 +1716,6 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do index: 2, block_number: transaction.block_number, transaction_index: transaction.index, - block_hash: transaction.block_hash, value: %Wei{value: Decimal.new(7)}, from_address_hash: internal_transaction_from.hash, from_address: internal_transaction_from, @@ -1970,9 +1963,9 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do index: 1, block_number: transaction.block_number, transaction_index: transaction.index, - block_hash: transaction.block_hash, value: %Wei{value: Decimal.new(1000)} ) + |> InternalTransaction.preload_addresses() insert(:internal_transaction, call_type: :call, @@ -1981,7 +1974,6 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do index: 2, block_number: transaction.block_number, transaction_index: transaction.index, - block_hash: transaction.block_hash, value: nil, from_address_hash: internal_transaction.from_address_hash, from_address: internal_transaction.from_address, @@ -2498,7 +2490,7 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do assert internal_transaction.block_number == json["block_number"] assert to_string(internal_transaction.gas) == json["gas_limit"] assert internal_transaction.index == json["index"] - assert to_string(internal_transaction.transaction_hash) == json["transaction_hash"] + assert to_string(internal_transaction.transaction.hash) == json["transaction_hash"] assert Address.checksum(internal_transaction.from_address_hash) == json["from"]["hash"] assert Address.checksum(internal_transaction.to_address_hash) == json["to"]["hash"] end @@ -2765,7 +2757,6 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do index: 1, block_number: transaction.block_number, transaction_index: transaction.index, - block_hash: transaction.block_hash, type: :reward ) @@ -2906,8 +2897,7 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do transaction: transaction, index: index, block_number: transaction.block_number, - transaction_index: transaction.index, - block_hash: transaction.block_hash + transaction_index: transaction.index ) end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs index c666292931ef..73fbec3fd835 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs @@ -143,6 +143,57 @@ defmodule BlockScoutWeb.SmartContractControllerTest do assert conn.status == 200 assert conn.assigns.read_only_functions == [] end + + test "uses first implementation from address_hashes for proxy contract" do + proxy_address = insert(:contract_address) + implementation_address = insert(:contract_address) + + insert(:smart_contract, + address_hash: proxy_address.hash, + contract_code_md5: "123" + ) + + insert(:smart_contract, + address_hash: implementation_address.hash, + abi: [ + %{ + "type" => "function", + "stateMutability" => "view", + "payable" => false, + "outputs" => [%{"type" => "uint256", "name" => ""}], + "name" => "get", + "inputs" => [], + "constant" => true + } + ], + contract_code_md5: "456" + ) + + insert(:proxy_implementation, + proxy_address_hash: proxy_address.hash, + proxy_type: "eip1967", + address_hashes: [implementation_address.hash], + names: ["implementation"] + ) + + blockchain_get_function_mock() + + path = + smart_contract_path(BlockScoutWeb.Endpoint, :index, + hash: proxy_address.hash, + type: :proxy, + action: :read + ) + + conn = + build_conn() + |> put_req_header("x-requested-with", "xmlhttprequest") + |> get(path) + + assert conn.status == 200 + assert conn.assigns.implementation_address == implementation_address.hash + refute conn.assigns.read_only_functions == [] + end end describe "GET show/3" do diff --git a/apps/block_scout_web/test/block_scout_web/controllers/transaction_internal_transaction_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/transaction_internal_transaction_controller_test.exs index 529cf301e882..e2ecf1e3e752 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/transaction_internal_transaction_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/transaction_internal_transaction_controller_test.exs @@ -44,16 +44,14 @@ defmodule BlockScoutWeb.TransactionInternalTransactionControllerTest do transaction: transaction, index: 0, block_number: transaction.block_number, - transaction_index: transaction.index, - block_hash: transaction.block_hash + transaction_index: transaction.index ) insert(:internal_transaction, transaction: transaction, index: 1, transaction_index: transaction.index, - block_number: transaction.block_number, - block_hash: transaction.block_hash + block_number: transaction.block_number ) path = transaction_internal_transaction_path(BlockScoutWeb.Endpoint, :index, transaction.hash) @@ -94,9 +92,9 @@ defmodule BlockScoutWeb.TransactionInternalTransactionControllerTest do index: 0, block_number: transaction.block_number, transaction_index: transaction.index, - block_hash: transaction.block_hash + created_contract_code: contract_address.contract_code, + created_contract_address: contract_address ) - |> with_contract_creation(contract_address) conn = get( @@ -104,7 +102,7 @@ defmodule BlockScoutWeb.TransactionInternalTransactionControllerTest do transaction_internal_transaction_path( BlockScoutWeb.Endpoint, :index, - internal_transaction.transaction_hash + internal_transaction.transaction.hash ) ) @@ -122,8 +120,7 @@ defmodule BlockScoutWeb.TransactionInternalTransactionControllerTest do transaction: transaction, index: 0, block_number: transaction.block_number, - transaction_index: transaction.index, - block_hash: transaction.block_hash + transaction_index: transaction.index ) second_page_indexes = @@ -133,8 +130,7 @@ defmodule BlockScoutWeb.TransactionInternalTransactionControllerTest do transaction: transaction, index: index, block_number: transaction.block_number, - transaction_index: transaction.index, - block_hash: transaction.block_hash + transaction_index: transaction.index ) end) |> Enum.map(& &1.index) @@ -167,8 +163,7 @@ defmodule BlockScoutWeb.TransactionInternalTransactionControllerTest do transaction: transaction, index: index, block_number: transaction.block_number, - transaction_index: transaction.index, - block_hash: transaction.block_hash + transaction_index: transaction.index ) end) @@ -197,8 +192,7 @@ defmodule BlockScoutWeb.TransactionInternalTransactionControllerTest do transaction: transaction, index: index, block_number: transaction.block_number, - transaction_index: transaction.index, - block_hash: transaction.block_hash + transaction_index: transaction.index ) end) diff --git a/apps/block_scout_web/test/block_scout_web/csv_export/address/internal_transactions_test.exs b/apps/block_scout_web/test/block_scout_web/csv_export/address/internal_transactions_test.exs index ef3d9224037a..e7abf32add25 100644 --- a/apps/block_scout_web/test/block_scout_web/csv_export/address/internal_transactions_test.exs +++ b/apps/block_scout_web/test/block_scout_web/csv_export/address/internal_transactions_test.exs @@ -2,7 +2,8 @@ defmodule BlockScoutWeb.CsvExport.Address.InternalTransactionsTest do use Explorer.DataCase alias BlockScoutWeb.CsvExport.Address.InternalTransactions, as: AddressInternalTransactionsCsvExporter - alias Explorer.Chain.{Address, Wei} + alias Explorer.Chain.{Address, InternalTransaction, Wei} + alias Explorer.Utility.AddressIdToAddressHash describe "export/3" do test "exports address internal transactions to csv" do @@ -19,7 +20,6 @@ defmodule BlockScoutWeb.CsvExport.Address.InternalTransactionsTest do transaction: transaction, from_address: address, block_number: transaction.block_number, - block_hash: transaction.block_hash, transaction_index: transaction.index ) @@ -30,7 +30,7 @@ defmodule BlockScoutWeb.CsvExport.Address.InternalTransactionsTest do res = address.hash - |> AddressInternalTransactionsCsvExporter.export(from_period, to_period, []) + |> AddressInternalTransactionsCsvExporter.export(from_period, to_period, [], nil, nil) |> Enum.to_list() |> Enum.drop(1) @@ -93,14 +93,21 @@ defmodule BlockScoutWeb.CsvExport.Address.InternalTransactionsTest do } end) - assert result.transaction_hash == to_string(internal_transaction.transaction_hash) + assert result.transaction_hash == to_string(internal_transaction.transaction.hash) assert result.index == to_string(internal_transaction.index) assert result.block_number == to_string(internal_transaction.block_number) assert result.transaction_index == to_string(internal_transaction.transaction_index) assert result.timestamp - assert result.from_address_hash == Address.checksum(internal_transaction.from_address_hash) - assert result.to_address_hash == Address.checksum(internal_transaction.to_address_hash) - assert result.created_contract_address_hash == to_string(internal_transaction.created_contract_address_hash) + + assert result.from_address_hash == + Address.checksum(AddressIdToAddressHash.id_to_hash(internal_transaction.from_address_id)) + + assert result.to_address_hash == + Address.checksum(AddressIdToAddressHash.id_to_hash(internal_transaction.to_address_id)) + + assert result.created_contract_address_hash == + to_string(AddressIdToAddressHash.id_to_hash(internal_transaction.created_contract_address_hash)) + assert result.type == to_string(internal_transaction.type) assert result.call_type == to_string(internal_transaction.call_type) assert result.gas == to_string(internal_transaction.gas) @@ -133,11 +140,9 @@ defmodule BlockScoutWeb.CsvExport.Address.InternalTransactionsTest do transaction: transaction, from_address: address, block_number: transaction.block_number, - block_hash: transaction.block_hash, transaction_index: transaction.index ) end) - |> Enum.count() 1..200 |> Enum.map(fn index -> @@ -151,11 +156,9 @@ defmodule BlockScoutWeb.CsvExport.Address.InternalTransactionsTest do transaction: transaction, to_address: address, block_number: transaction.block_number, - block_hash: transaction.block_hash, transaction_index: transaction.index ) end) - |> Enum.count() 1..200 |> Enum.map(fn index -> @@ -164,17 +167,15 @@ defmodule BlockScoutWeb.CsvExport.Address.InternalTransactionsTest do |> insert() |> with_block() - insert(:internal_transaction, + insert(:internal_transaction_create, index: index, transaction: transaction, created_contract_address: address, to_address: nil, block_number: transaction.block_number, - block_hash: transaction.block_hash, transaction_index: transaction.index ) end) - |> Enum.count() {:ok, now} = DateTime.now("Etc/UTC") @@ -183,7 +184,7 @@ defmodule BlockScoutWeb.CsvExport.Address.InternalTransactionsTest do result = address.hash - |> AddressInternalTransactionsCsvExporter.export(from_period, to_period, []) + |> AddressInternalTransactionsCsvExporter.export(from_period, to_period, [], nil, nil) |> Enum.to_list() |> Enum.drop(1) @@ -206,13 +207,13 @@ defmodule BlockScoutWeb.CsvExport.Address.InternalTransactionsTest do transaction: transaction, from_address: address, block_number: transaction.block_number, - block_hash: transaction.block_hash, transaction_index: transaction.index, error: "reverted", error_id: transaction_error.id, gas_used: nil, output: nil ) + |> InternalTransaction.preload_addresses() {:ok, now} = DateTime.now("Etc/UTC") @@ -221,7 +222,7 @@ defmodule BlockScoutWeb.CsvExport.Address.InternalTransactionsTest do res = address.hash - |> AddressInternalTransactionsCsvExporter.export(from_period, to_period, []) + |> AddressInternalTransactionsCsvExporter.export(from_period, to_period, [], nil, nil) |> Enum.to_list() |> Enum.drop(1) @@ -284,7 +285,7 @@ defmodule BlockScoutWeb.CsvExport.Address.InternalTransactionsTest do } end) - assert result.transaction_hash == to_string(internal_transaction.transaction_hash) + assert result.transaction_hash == to_string(internal_transaction.transaction.hash) assert result.index == to_string(internal_transaction.index) assert result.block_number == to_string(internal_transaction.block_number) assert result.transaction_index == to_string(internal_transaction.transaction_index) diff --git a/apps/block_scout_web/test/block_scout_web/features/pages/address_page.ex b/apps/block_scout_web/test/block_scout_web/features/pages/address_page.ex index 99b66dde1f98..e25f37b107d1 100644 --- a/apps/block_scout_web/test/block_scout_web/features/pages/address_page.ex +++ b/apps/block_scout_web/test/block_scout_web/features/pages/address_page.ex @@ -83,10 +83,10 @@ defmodule BlockScoutWeb.AddressPage do css("[data-test='address_detail_hash']", text: to_string(address)) end - def internal_transaction(%InternalTransaction{transaction_hash: transaction_hash, index: index}) do + def internal_transaction(%InternalTransaction{index: index} = internal_transaction) do css( "[data-test='internal_transaction']" <> - "[data-internal-transaction-transaction-hash='#{transaction_hash}']" <> + "[data-internal-transaction-transaction-hash='#{internal_transaction.transaction.hash}']" <> "[data-internal-transaction-index='#{index}']" ) end @@ -96,23 +96,23 @@ defmodule BlockScoutWeb.AddressPage do end def internal_transaction_address_link( - %InternalTransaction{transaction_hash: transaction_hash, index: index, from_address_hash: address_hash}, + %InternalTransaction{index: index, from_address_hash: address_hash} = internal_transaction, :from ) do checksum = Address.checksum(address_hash) css( - "[data-internal-transaction-transaction-hash='#{transaction_hash}'][data-internal-transaction-index='#{index}']" <> + "[data-internal-transaction-transaction-hash='#{internal_transaction.transaction.hash}'][data-internal-transaction-index='#{index}']" <> " [data-test='address_hash_link']" <> " [data-address-hash='#{checksum}']" ) end def internal_transaction_address_link( - %InternalTransaction{transaction_hash: transaction_hash, index: index, to_address_hash: address_hash}, + %InternalTransaction{index: index, to_address_hash: address_hash} = internal_transaction, :to ) do css( - "[data-internal-transaction-transaction-hash='#{transaction_hash}'][data-internal-transaction-index='#{index}']" <> + "[data-internal-transaction-transaction-hash='#{internal_transaction.transaction.hash}'][data-internal-transaction-index='#{index}']" <> " [data-test='address_hash_link']" <> " [data-address-hash='#{address_hash}']" ) end diff --git a/apps/block_scout_web/test/block_scout_web/features/viewing_addresses_test.exs b/apps/block_scout_web/test/block_scout_web/features/viewing_addresses_test.exs index 4def7e5f5836..82a8a2e6e2da 100644 --- a/apps/block_scout_web/test/block_scout_web/features/viewing_addresses_test.exs +++ b/apps/block_scout_web/test/block_scout_web/features/viewing_addresses_test.exs @@ -96,7 +96,6 @@ defmodule BlockScoutWeb.ViewingAddressesTest do transaction: transaction, from_address: address, created_contract_address: contract, - block_hash: transaction.block_hash, block_number: transaction.block_number ) @@ -123,7 +122,6 @@ defmodule BlockScoutWeb.ViewingAddressesTest do to_address: contract, created_contract_address: contract, type: :call, - block_hash: transaction.block_hash, block_number: transaction.block_number ) @@ -135,7 +133,6 @@ defmodule BlockScoutWeb.ViewingAddressesTest do transaction: transaction, from_address: contract, created_contract_address: another_contract, - block_hash: transaction.block_hash, block_number: transaction.block_number ) @@ -223,8 +220,7 @@ defmodule BlockScoutWeb.ViewingAddressesTest do to_address: address, transaction_index: transaction.index, index: 1, - block_number: transaction.block_number, - block_hash: transaction.block_hash + block_number: transaction.block_number ) insert(:internal_transaction, @@ -232,8 +228,7 @@ defmodule BlockScoutWeb.ViewingAddressesTest do from_address: address, transaction_index: transaction.index, index: 2, - block_number: transaction.block_number, - block_hash: transaction.block_hash + block_number: transaction.block_number ) {:ok, %{internal_transaction_lincoln_to_address: internal_transaction_lincoln_to_address}} @@ -268,8 +263,7 @@ defmodule BlockScoutWeb.ViewingAddressesTest do index: 2, from_address: addresses.lincoln, block_number: transaction.block_number, - transaction_index: transaction.index, - block_hash: transaction.block_hash + transaction_index: transaction.index ) Notifier.handle_event({:chain_event, :internal_transactions, :realtime, [internal_transaction]}) @@ -300,8 +294,7 @@ defmodule BlockScoutWeb.ViewingAddressesTest do index: 2, from_address: addresses.lincoln, block_number: from_lincoln.block_number, - transaction_index: from_lincoln.index, - block_hash: from_lincoln.block_hash + transaction_index: from_lincoln.index ) session @@ -332,8 +325,7 @@ defmodule BlockScoutWeb.ViewingAddressesTest do index: 2, from_address: addresses.lincoln, block_number: from_lincoln.block_number, - transaction_index: from_lincoln.index, - block_hash: from_lincoln.block_hash + transaction_index: from_lincoln.index ) session diff --git a/apps/block_scout_web/test/block_scout_web/features/viewing_blocks_test.exs b/apps/block_scout_web/test/block_scout_web/features/viewing_blocks_test.exs index 78cc5467c728..f6ad57c36d83 100644 --- a/apps/block_scout_web/test/block_scout_web/features/viewing_blocks_test.exs +++ b/apps/block_scout_web/test/block_scout_web/features/viewing_blocks_test.exs @@ -63,10 +63,10 @@ defmodule BlockScoutWeb.ViewingBlocksTest do transaction: transaction, transaction_index: transaction.index, index: 0, - block_hash: transaction.block_hash, + created_contract_code: contract_address.contract_code, + created_contract_address: contract_address, block_number: transaction.block_number ) - |> with_contract_creation(contract_address) session |> BlockPage.visit_page(block) diff --git a/apps/block_scout_web/test/block_scout_web/graphql/schema/query/node_test.exs b/apps/block_scout_web/test/block_scout_web/graphql/schema/query/node_test.exs index cccf4d336a92..dfac138f88e4 100644 --- a/apps/block_scout_web/test/block_scout_web/graphql/schema/query/node_test.exs +++ b/apps/block_scout_web/test/block_scout_web/graphql/schema/query/node_test.exs @@ -65,7 +65,6 @@ defmodule BlockScoutWeb.GraphQL.Schema.Query.NodeTest do transaction: transaction, transaction_index: transaction.index, index: 0, - block_hash: transaction.block_hash, block_number: transaction.block_number ) @@ -110,7 +109,6 @@ defmodule BlockScoutWeb.GraphQL.Schema.Query.NodeTest do transaction: transaction, transaction_index: transaction.index, index: 0, - block_hash: transaction.block_hash, block_number: transaction.block_number ) diff --git a/apps/block_scout_web/test/block_scout_web/graphql/schema/query/transaction_test.exs b/apps/block_scout_web/test/block_scout_web/graphql/schema/query/transaction_test.exs index 07071ca90048..73f7d2e9e8bd 100644 --- a/apps/block_scout_web/test/block_scout_web/graphql/schema/query/transaction_test.exs +++ b/apps/block_scout_web/test/block_scout_web/graphql/schema/query/transaction_test.exs @@ -1,6 +1,8 @@ defmodule BlockScoutWeb.GraphQL.Schema.Query.TransactionTest do use BlockScoutWeb.ConnCase + alias Explorer.Chain.InternalTransaction + describe "transaction field" do test "with valid argument 'hash', returns all expected fields", %{conn: conn} do block = insert(:block) @@ -135,15 +137,16 @@ defmodule BlockScoutWeb.GraphQL.Schema.Query.TransactionTest do transaction_index: transaction.index, index: 0, from_address: address, + created_contract_code: contract_address.contract_code, + created_contract_address: contract_address, call_type: :call, - block_hash: transaction.block_hash, block_number: transaction.block_number } internal_transaction = :internal_transaction_create |> insert(internal_transaction_attributes) - |> with_contract_creation(contract_address) + |> InternalTransaction.preload_addresses() query = """ query ($hash: FullHash!, $first: Int!) { @@ -208,7 +211,7 @@ defmodule BlockScoutWeb.GraphQL.Schema.Query.TransactionTest do to_string(internal_transaction.created_contract_address_hash), "from_address_hash" => to_string(internal_transaction.from_address_hash), "to_address_hash" => nil, - "transaction_hash" => to_string(internal_transaction.transaction_hash) + "transaction_hash" => to_string(internal_transaction.transaction.hash) } } ] @@ -268,7 +271,6 @@ defmodule BlockScoutWeb.GraphQL.Schema.Query.TransactionTest do transaction: transaction, transaction_index: transaction.index, index: 2, - block_hash: transaction.block_hash, block_number: transaction.block_number ) @@ -276,7 +278,6 @@ defmodule BlockScoutWeb.GraphQL.Schema.Query.TransactionTest do transaction: transaction, transaction_index: transaction.index, index: 0, - block_hash: transaction.block_hash, block_number: transaction.block_number ) @@ -284,7 +285,6 @@ defmodule BlockScoutWeb.GraphQL.Schema.Query.TransactionTest do transaction: transaction, transaction_index: transaction.index, index: 1, - block_hash: transaction.block_hash, block_number: transaction.block_number ) @@ -399,7 +399,6 @@ defmodule BlockScoutWeb.GraphQL.Schema.Query.TransactionTest do transaction: transaction, transaction_index: transaction.index, index: 2, - block_hash: transaction.block_hash, block_number: transaction.block_number ) @@ -407,7 +406,6 @@ defmodule BlockScoutWeb.GraphQL.Schema.Query.TransactionTest do transaction: transaction, transaction_index: transaction.index, index: 0, - block_hash: transaction.block_hash, block_number: transaction.block_number ) @@ -415,7 +413,6 @@ defmodule BlockScoutWeb.GraphQL.Schema.Query.TransactionTest do transaction: transaction, transaction_index: transaction.index, index: 1, - block_hash: transaction.block_hash, block_number: transaction.block_number ) @@ -457,7 +454,6 @@ defmodule BlockScoutWeb.GraphQL.Schema.Query.TransactionTest do transaction: transaction, transaction_index: transaction.index, index: index, - block_hash: transaction.block_hash, block_number: transaction.block_number ) end diff --git a/apps/block_scout_web/test/block_scout_web/specs/public_legacy_tag_test.exs b/apps/block_scout_web/test/block_scout_web/specs/public_legacy_tag_test.exs new file mode 100644 index 000000000000..70c666dcbf8e --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/specs/public_legacy_tag_test.exs @@ -0,0 +1,58 @@ +defmodule BlockScoutWeb.Specs.PublicLegacyTagTest do + use ExUnit.Case, async: true + + @legacy_paths [ + "/legacy/logs/get-logs", + "/legacy/block/get-block-number-by-time", + "/legacy/block/eth-block-number" + ] + + setup_all do + {:ok, spec: BlockScoutWeb.Specs.Public.spec()} + end + + for path <- @legacy_paths do + describe "path #{path}" do + test "exists in spec.paths", %{spec: spec} do + path = unquote(path) + assert Map.has_key?(spec.paths, path), "Expected path #{path} to be present in spec.paths" + end + + test "GET operation carries tags: [\"legacy\"]", %{spec: spec} do + path = unquote(path) + path_item = Map.fetch!(spec.paths, path) + operation = path_item.get + + assert operation != nil, "Expected a GET operation for #{path}" + assert operation.tags == ["legacy"], "Expected tags [\"legacy\"] on #{path}, got: #{inspect(operation.tags)}" + end + + test "GET operation has a declared 200 response", %{spec: spec} do + path = unquote(path) + path_item = Map.fetch!(spec.paths, path) + operation = path_item.get + + assert operation != nil, "Expected a GET operation for #{path}" + + response = Map.get(operation.responses, "200") || Map.get(operation.responses, 200) + + assert response != nil, + "Expected a 200 response on #{path}, got keys: #{inspect(Map.keys(operation.responses))}" + end + + test "GET 200 response has an application/json schema", %{spec: spec} do + path = unquote(path) + path_item = Map.fetch!(spec.paths, path) + operation = path_item.get + + assert operation != nil, "Expected a GET operation for #{path}" + + response = Map.get(operation.responses, "200") || Map.get(operation.responses, 200) + assert response != nil, "Expected a 200 response on #{path}" + + schema = get_in(response, [Access.key!(:content), "application/json", Access.key!(:schema)]) + assert schema != nil, "Expected an application/json schema in the 200 response of #{path}" + end + end + end +end diff --git a/apps/block_scout_web/test/block_scout_web/views/address_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/address_view_test.exs index f4a559f7491c..05179625bbf0 100644 --- a/apps/block_scout_web/test/block_scout_web/views/address_view_test.exs +++ b/apps/block_scout_web/test/block_scout_web/views/address_view_test.exs @@ -19,8 +19,7 @@ defmodule BlockScoutWeb.AddressViewTest do transaction: transaction, transaction_index: transaction.index, to_address: nil, - created_contract_address_hash: nil, - block_hash: transaction.block_hash, + created_contract_address: nil, block_number: transaction.block_number ) @@ -342,7 +341,7 @@ defmodule BlockScoutWeb.AddressViewTest do describe "address_page_title/1" do test "uses the Smart Contract name when the contract is verified" do smart_contract = build(:smart_contract, name: "POA") - address = build(:address, smart_contract: smart_contract) + address = build(:address, smart_contract: smart_contract, verified: true) assert AddressView.address_page_title(address) == "POA (#{address})" end diff --git a/apps/block_scout_web/test/test_helper.exs b/apps/block_scout_web/test/test_helper.exs index bba9010220aa..9f2154484c6a 100644 --- a/apps/block_scout_web/test/test_helper.exs +++ b/apps/block_scout_web/test/test_helper.exs @@ -29,7 +29,6 @@ Explorer.TestHelper.run_necessary_background_migrations() Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo, :manual) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Account, :manual) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.PolygonEdge, :manual) -Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.PolygonZkevm, :manual) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.RSK, :manual) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Shibarium, :manual) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Suave, :manual) diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/utility/ranges_helper.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/utility/ranges_helper.ex index 426096447b8a..eda487cd1a2f 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/utility/ranges_helper.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/utility/ranges_helper.ex @@ -138,6 +138,41 @@ defmodule EthereumJSONRPC.Utility.RangesHelper do end) end + @doc """ + Parses a block ranges string into a flat list of block numbers. + + This function expands each parsed range into individual block numbers and returns + all resulting numbers as a single list. The input must contain only finite ranges. + If the parsed result includes a standalone integer such as a `latest` marker, + the function raises. + + ## Parameters + + - `block_ranges_string`: A string representing block ranges. + + ## Returns + + - A list of block numbers as integers. + + ## Examples + + iex> parse_block_ranges_to_numbers("1..3,5..6") + [1, 2, 3, 5, 6] + + iex> parse_block_ranges_to_numbers("10..12") + [10, 11, 12] + + """ + @spec parse_block_ranges_to_numbers(binary()) :: [integer()] + def parse_block_ranges_to_numbers(block_ranges_string) do + block_ranges_string + |> parse_block_ranges() + |> Enum.flat_map(fn + %Range{} = range -> Range.to_list(range) + _number -> raise "Invalid ranges string" + end) + end + @doc """ Extracts the minimum block number from a given block ranges string. diff --git a/apps/ethereum_jsonrpc/mix.exs b/apps/ethereum_jsonrpc/mix.exs index d3bcc70c3869..cc19f6e906a0 100644 --- a/apps/ethereum_jsonrpc/mix.exs +++ b/apps/ethereum_jsonrpc/mix.exs @@ -19,7 +19,7 @@ defmodule EthereumJSONRPC.MixProject do elixirc_paths: elixirc_paths(Mix.env()), lockfile: "../../mix.lock", start_permanent: Mix.env() == :prod, - version: "10.2.1" + version: "11.0.0" ] end diff --git a/apps/explorer/.sobelow-conf b/apps/explorer/.sobelow-conf index c2b7ff895f31..965cebfbbdf0 100644 --- a/apps/explorer/.sobelow-conf +++ b/apps/explorer/.sobelow-conf @@ -7,6 +7,7 @@ ignore: ["Config.HTTPS"], ignore_files: [ "lib/explorer/smart_contract/solidity/code_compiler.ex", - "lib/explorer/smart_contract/vyper/code_compiler.ex" + "lib/explorer/smart_contract/vyper/code_compiler.ex", + "lib/explorer/chain/csv_export/worker.ex" ] ] diff --git a/apps/explorer/config/config.exs b/apps/explorer/config/config.exs index ed781fd11bd9..71bf3a2e4074 100644 --- a/apps/explorer/config/config.exs +++ b/apps/explorer/config/config.exs @@ -79,10 +79,6 @@ config :explorer, Explorer.Chain.Cache.Counters.Blackfort.ValidatorsCount, enable_consolidation: true, update_interval_in_milliseconds: update_interval_in_milliseconds_default -config :explorer, Explorer.Chain.Cache.TransactionActionTokensData, enabled: true - -config :explorer, Explorer.Chain.Cache.TransactionActionUniswapPools, enabled: true - config :explorer, Explorer.Market.Fetcher.Token, enabled: true config :explorer, Explorer.Chain.Cache.Counters.TokenHoldersCount, @@ -139,12 +135,12 @@ for migrator <- [ Explorer.Migrator.SanitizeEmptyContractCodeAddresses, Explorer.Migrator.BackfillMetadataURL, Explorer.Migrator.SanitizeErc1155TokenBalancesWithoutTokenIds, - Explorer.Migrator.ReindexDuplicatedInternalTransactions, Explorer.Migrator.MergeAdjacentMissingBlockRanges, Explorer.Migrator.UnescapeQuotesInTokens, Explorer.Migrator.UnescapeAmpersandsInTokens, Explorer.Migrator.SanitizeDuplicateSmartContractAdditionalSources, - Explorer.Migrator.EmptyInternalTransactionsData + Explorer.Migrator.EmptyInternalTransactionsData, + Explorer.Migrator.FillInternalTransactionsAddressIds ] do config :explorer, migrator, enabled: true end @@ -186,7 +182,17 @@ for index_operation <- [ Explorer.Migrator.HeavyDbIndexOperation.DropTransactionsCreatedContractAddressHashWithPendingIndexA, Explorer.Migrator.HeavyDbIndexOperation.UpdateInternalTransactionsPrimaryKey, Explorer.Migrator.HeavyDbIndexOperation.DropInternalTransactionsBlockHashTransactionIndexIndexIndex, - Explorer.Migrator.HeavyDbIndexOperation.DropInternalTransactionsCreatedContractAddressHashPartialIndex + Explorer.Migrator.HeavyDbIndexOperation.DropInternalTransactionsCreatedContractAddressHashPartialIndex, + Explorer.Migrator.HeavyDbIndexOperation.CreateInternalTransactionsFromAddressIdPartialIndex, + Explorer.Migrator.HeavyDbIndexOperation.CreateInternalTransactionsToAddressIdPartialIndex, + Explorer.Migrator.HeavyDbIndexOperation.CreateInternalTransactionsCreatedContractAddressIdIndex, + Explorer.Migrator.HeavyDbIndexOperation.CreateInternalTransactionsBlockNumberCreatedContractAddressIdPartialIndex, + Explorer.Migrator.HeavyDbIndexOperation.RemoveInternalTransactionsBlockHashTransactionHashBlockIndexError, + Explorer.Migrator.HeavyDbIndexOperation.CreateAddressesHashContractCodeNotNullIndex, + Explorer.Migrator.HeavyDbIndexOperation.DropInternalTransactionsBlockNumberCreatedContractAddressHashIndex, + Explorer.Migrator.HeavyDbIndexOperation.DropInternalTransactionsCreatedContractAddressHashIndex, + Explorer.Migrator.HeavyDbIndexOperation.DropInternalTransactionsFromAddressHashPartialIndex, + Explorer.Migrator.HeavyDbIndexOperation.DropInternalTransactionsToAddressHashPartialIndex ] do config :explorer, index_operation, enabled: true end diff --git a/apps/explorer/config/dev.exs b/apps/explorer/config/dev.exs index 715741583d1a..233383067a38 100644 --- a/apps/explorer/config/dev.exs +++ b/apps/explorer/config/dev.exs @@ -24,7 +24,6 @@ for repo <- [ Explorer.Repo.Mud, Explorer.Repo.Optimism, Explorer.Repo.PolygonEdge, - Explorer.Repo.PolygonZkevm, Explorer.Repo.RSK, Explorer.Repo.Scroll, Explorer.Repo.Shibarium, diff --git a/apps/explorer/config/prod.exs b/apps/explorer/config/prod.exs index 8371ee6c6d7e..04d809efa753 100644 --- a/apps/explorer/config/prod.exs +++ b/apps/explorer/config/prod.exs @@ -26,7 +26,6 @@ for repo <- [ Explorer.Repo.Mud, Explorer.Repo.Optimism, Explorer.Repo.PolygonEdge, - Explorer.Repo.PolygonZkevm, Explorer.Repo.RSK, Explorer.Repo.Scroll, Explorer.Repo.Shibarium, diff --git a/apps/explorer/config/runtime/test.exs b/apps/explorer/config/runtime/test.exs index 0a000556af44..055152ff6e28 100644 --- a/apps/explorer/config/runtime/test.exs +++ b/apps/explorer/config/runtime/test.exs @@ -70,13 +70,13 @@ for migrator <- [ Explorer.Migrator.SanitizeEmptyContractCodeAddresses, Explorer.Migrator.BackfillMetadataURL, Explorer.Migrator.SanitizeErc1155TokenBalancesWithoutTokenIds, - Explorer.Migrator.ReindexDuplicatedInternalTransactions, Explorer.Migrator.MergeAdjacentMissingBlockRanges, Explorer.Migrator.UnescapeQuotesInTokens, Explorer.Migrator.UnescapeAmpersandsInTokens, Explorer.Migrator.SanitizeDuplicateSmartContractAdditionalSources, Explorer.Migrator.DeleteZeroValueInternalTransactions, Explorer.Migrator.EmptyInternalTransactionsData, + Explorer.Migrator.FillInternalTransactionsAddressIds, # Heavy DB index operations Explorer.Migrator.HeavyDbIndexOperation.CreateLogsBlockHashIndex, @@ -114,7 +114,17 @@ for migrator <- [ Explorer.Migrator.HeavyDbIndexOperation.DropTransactionsCreatedContractAddressHashWithPendingIndexA, Explorer.Migrator.HeavyDbIndexOperation.UpdateInternalTransactionsPrimaryKey, Explorer.Migrator.HeavyDbIndexOperation.DropInternalTransactionsBlockHashTransactionIndexIndexIndex, - Explorer.Migrator.HeavyDbIndexOperation.DropInternalTransactionsCreatedContractAddressHashPartialIndex + Explorer.Migrator.HeavyDbIndexOperation.DropInternalTransactionsCreatedContractAddressHashPartialIndex, + Explorer.Migrator.HeavyDbIndexOperation.CreateInternalTransactionsFromAddressIdPartialIndex, + Explorer.Migrator.HeavyDbIndexOperation.CreateInternalTransactionsToAddressIdPartialIndex, + Explorer.Migrator.HeavyDbIndexOperation.CreateInternalTransactionsCreatedContractAddressIdIndex, + Explorer.Migrator.HeavyDbIndexOperation.CreateInternalTransactionsBlockNumberCreatedContractAddressIdPartialIndex, + Explorer.Migrator.HeavyDbIndexOperation.RemoveInternalTransactionsBlockHashTransactionHashBlockIndexError, + Explorer.Migrator.HeavyDbIndexOperation.CreateAddressesHashContractCodeNotNullIndex, + Explorer.Migrator.HeavyDbIndexOperation.DropInternalTransactionsBlockNumberCreatedContractAddressHashIndex, + Explorer.Migrator.HeavyDbIndexOperation.DropInternalTransactionsCreatedContractAddressHashIndex, + Explorer.Migrator.HeavyDbIndexOperation.DropInternalTransactionsFromAddressHashPartialIndex, + Explorer.Migrator.HeavyDbIndexOperation.DropInternalTransactionsToAddressHashPartialIndex ] do config :explorer, migrator, enabled: false end @@ -124,6 +134,12 @@ config :explorer, config :indexer, Indexer.Fetcher.TokenInstance.Helper, host_filtering_enabled?: false +# Enable Oban for async CSV export controller tests (testing: :manual from config/test.exs +# ensures jobs are not auto-executed) +config :explorer, Oban, + enabled: true, + queues: [csv_export: 1, csv_export_sanitize: 1] + variant = Variant.get() Code.require_file("#{variant}.exs", "#{__DIR__}/../../../explorer/config/test") diff --git a/apps/explorer/config/test.exs b/apps/explorer/config/test.exs index 2de39c4c3e2e..a59c5fef7824 100644 --- a/apps/explorer/config/test.exs +++ b/apps/explorer/config/test.exs @@ -75,7 +75,6 @@ for repo <- [ Explorer.Repo.Mud, Explorer.Repo.Optimism, Explorer.Repo.PolygonEdge, - Explorer.Repo.PolygonZkevm, Explorer.Repo.RSK, Explorer.Repo.Scroll, Explorer.Repo.Shibarium, @@ -99,16 +98,6 @@ for repo <- [ pool_size: 1 end -config :explorer, Explorer.Repo.PolygonZkevm, - database: database, - hostname: hostname, - url: database_url, - pool: Ecto.Adapters.SQL.Sandbox, - # Default of `5_000` was too low for `BlockFetcher` test - ownership_timeout: :timer.minutes(1), - timeout: :timer.seconds(60), - queue_target: 1000 - config :logger, :explorer, path: Path.absname("logs/test/explorer.log") config :explorer, Explorer.Chain.Fetcher.CheckBytecodeMatchingOnDemand, enabled: false diff --git a/apps/explorer/lib/explorer/application.ex b/apps/explorer/lib/explorer/application.ex index 820c66288b36..e350326ee3ca 100644 --- a/apps/explorer/lib/explorer/application.ex +++ b/apps/explorer/lib/explorer/application.ex @@ -39,6 +39,7 @@ defmodule Explorer.Application do alias Explorer.Prometheus.Instrumenter alias Explorer.Repo.PrometheusLogger alias Explorer.Utility.Hammer + alias Oban.Telemetry, as: ObanTelemetry alias Utils.ConfigHelper @impl Application @@ -46,6 +47,10 @@ defmodule Explorer.Application do PrometheusLogger.setup() Instrumenter.setup() + if Application.get_env(:explorer, Oban, [])[:enabled] && Explorer.mode() in [:api, :all] do + ObanTelemetry.attach_default_logger() + end + :telemetry.attach( "prometheus-ecto", [:explorer, :repo, :query], @@ -99,7 +104,8 @@ defmodule Explorer.Application do recv_timeout: 60_000, timeout: 60_000, max_connections: Application.get_env(:explorer, :hackney_default_pool_size) - ) + ), + Explorer.Promo.Autoscout ] children = base_children ++ configurable_children() @@ -117,6 +123,7 @@ defmodule Explorer.Application do configurable_children_set = [ configure_mode_dependent_process(Explorer.Utility.VersionConstantsUpdater, :indexer), + configure(Explorer.Utility.VersionUpgrade), configure_mode_dependent_process(Explorer.Market.Fetcher.Coin, :api), configure_mode_dependent_process(Explorer.Market.Fetcher.Token, :indexer), configure_mode_dependent_process(Explorer.Market.Fetcher.History, :indexer), @@ -126,8 +133,6 @@ defmodule Explorer.Application do configure(Explorer.Chain.Cache.Counters.NewContractsCount), configure(Explorer.Chain.Cache.Counters.VerifiedContractsCount), configure(Explorer.Chain.Cache.Counters.NewVerifiedContractsCount), - configure(Explorer.Chain.Cache.TransactionActionTokensData), - configure(Explorer.Chain.Cache.TransactionActionUniswapPools), configure(Explorer.Chain.Cache.Counters.WithdrawalsSum), configure_mode_dependent_process(Explorer.Chain.Transaction.History.Historian, :indexer), configure(Explorer.Chain.Events.Listener), @@ -179,7 +184,6 @@ defmodule Explorer.Application do configure_chain_type_dependent_process(Explorer.Chain.Cache.Counters.Stability.ValidatorsCount, :stability), configure_chain_type_dependent_process(Explorer.Chain.Cache.LatestL1BlockNumber, [ :optimism, - :polygon_zkevm, :scroll, :shibarium ]), @@ -187,7 +191,6 @@ defmodule Explorer.Application do Explorer.Migrator.SanitizeDuplicatedLogIndexLogs |> configure_mode_dependent_process(:indexer) |> configure_chain_type_dependent_process([ - :polygon_zkevm, :rsk, :filecoin ]), @@ -196,7 +199,6 @@ defmodule Explorer.Application do configure_mode_dependent_process(Explorer.Migrator.SanitizeVerifiedAddresses, :indexer), configure_mode_dependent_process(Explorer.Migrator.SanitizeEmptyContractCodeAddresses, :indexer), configure_mode_dependent_process(Explorer.Migrator.ReindexInternalTransactionsWithIncompatibleStatus, :indexer), - configure_mode_dependent_process(Explorer.Migrator.ReindexDuplicatedInternalTransactions, :indexer), configure_mode_dependent_process(Explorer.Migrator.MergeAdjacentMissingBlockRanges, :indexer), configure_mode_dependent_process(Explorer.Migrator.UnescapeQuotesInTokens, :indexer), configure_mode_dependent_process(Explorer.Migrator.UnescapeAmpersandsInTokens, :indexer), @@ -204,6 +206,7 @@ defmodule Explorer.Application do configure_mode_dependent_process(Explorer.Migrator.SanitizeDuplicateSmartContractAdditionalSources, :indexer), configure_mode_dependent_process(Explorer.Migrator.DeleteZeroValueInternalTransactions, :indexer), configure_mode_dependent_process(Explorer.Migrator.EmptyInternalTransactionsData, :indexer), + configure_mode_dependent_process(Explorer.Migrator.FillInternalTransactionsAddressIds, :indexer), configure_mode_dependent_process( Explorer.Migrator.HeavyDbIndexOperation.CreateAddressesVerifiedIndex, :indexer @@ -338,6 +341,18 @@ defmodule Explorer.Application do Explorer.Migrator.HeavyDbIndexOperation.CreateTokensNamePartialFtsIndex, :indexer ), + configure_mode_dependent_process( + Explorer.Migrator.HeavyDbIndexOperation.CreateTokensOrdMcapFiatHolderNameIndex, + :indexer + ), + configure_mode_dependent_process( + Explorer.Migrator.HeavyDbIndexOperation.CreateTokensOrdFiatHolderNameIndex, + :indexer + ), + configure_mode_dependent_process( + Explorer.Migrator.HeavyDbIndexOperation.CreateTokensOrdHolderNameIndex, + :indexer + ), configure_mode_dependent_process( Explorer.Migrator.HeavyDbIndexOperation.UpdateInternalTransactionsPrimaryKey, :indexer @@ -354,6 +369,46 @@ defmodule Explorer.Application do Explorer.Migrator.HeavyDbIndexOperation.DropTransactionsCreatedContractAddressHashWithPendingIndexA, :indexer ), + configure_mode_dependent_process( + Explorer.Migrator.HeavyDbIndexOperation.RemoveInternalTransactionsBlockHashTransactionHashBlockIndexError, + :indexer + ), + configure_mode_dependent_process( + Explorer.Migrator.HeavyDbIndexOperation.CreateInternalTransactionsFromAddressIdPartialIndex, + :indexer + ), + configure_mode_dependent_process( + Explorer.Migrator.HeavyDbIndexOperation.CreateInternalTransactionsToAddressIdPartialIndex, + :indexer + ), + configure_mode_dependent_process( + Explorer.Migrator.HeavyDbIndexOperation.CreateInternalTransactionsCreatedContractAddressIdIndex, + :indexer + ), + configure_mode_dependent_process( + Explorer.Migrator.HeavyDbIndexOperation.CreateInternalTransactionsBlockNumberCreatedContractAddressIdPartialIndex, + :indexer + ), + configure_mode_dependent_process( + Explorer.Migrator.HeavyDbIndexOperation.DropInternalTransactionsBlockNumberCreatedContractAddressHashIndex, + :indexer + ), + configure_mode_dependent_process( + Explorer.Migrator.HeavyDbIndexOperation.DropInternalTransactionsCreatedContractAddressHashIndex, + :indexer + ), + configure_mode_dependent_process( + Explorer.Migrator.HeavyDbIndexOperation.DropInternalTransactionsFromAddressHashPartialIndex, + :indexer + ), + configure_mode_dependent_process( + Explorer.Migrator.HeavyDbIndexOperation.DropInternalTransactionsToAddressHashPartialIndex, + :indexer + ), + configure_mode_dependent_process( + Explorer.Migrator.HeavyDbIndexOperation.CreateAddressesHashContractCodeNotNullIndex, + :indexer + ), Explorer.Migrator.RefetchContractCodes |> configure() |> configure_chain_type_dependent_process(:zksync), configure(Explorer.Chain.Fetcher.AddressesBlacklist), Explorer.Migrator.SwitchPendingOperations, @@ -361,7 +416,11 @@ defmodule Explorer.Application do Hammer.child_for_supervisor() |> configure_mode_dependent_process(:api), configure_mode_dependent_process(Explorer.ThirdPartyIntegrations.Dynamic.Strategy, :api), # keep at the end - configure_libcluster() + configure_libcluster(), + configure_mode_dependent_process( + {Oban, :explorer |> Application.fetch_env!(Oban) |> Keyword.delete(:enabled)}, + :api + ) ] |> List.flatten() @@ -379,7 +438,6 @@ defmodule Explorer.Application do Explorer.Repo.Filecoin, Explorer.Repo.Optimism, Explorer.Repo.PolygonEdge, - Explorer.Repo.PolygonZkevm, Explorer.Repo.RSK, Explorer.Repo.Scroll, Explorer.Repo.Shibarium, diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 4b91dbdbc57a..dd51c9069238 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -93,7 +93,7 @@ defmodule Explorer.Chain do # Geth-like node @revert_msg_prefix_5 "execution reverted: " @revert_msg_prefix_6_empty "execution reverted" - + @deduplicated_optional_address_associations [:from_address, :to_address, :created_contract_address] @typedoc """ The name of an association on the `t:Ecto.Schema.t/0` """ @@ -140,6 +140,7 @@ defmodule Explorer.Chain do {:include_internal_transaction_association?, true | false} @type ip :: {:ip, String.t()} @type show_scam_tokens? :: {:show_scam_tokens?, true | false} + @type timeout_option :: {:timeout, timeout() | nil} def wrapped_union_subquery(query) do from( @@ -196,10 +197,12 @@ defmodule Explorer.Chain do |> select_repo(options).all() end - @spec address_to_logs(Hash.Address.t(), [paging_options | necessity_by_association_option | api?]) :: [Log.t()] + @spec address_to_logs(Hash.Address.t(), [paging_options | necessity_by_association_option | api? | timeout_option]) :: + [Log.t()] def address_to_logs(address_hash, csv_export?, options \\ []) when is_list(options) do paging_options = Keyword.get(options, :paging_options) || %PagingOptions{page_size: 50} necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) + timeout = Keyword.get(options, :timeout) case paging_options do %PagingOptions{key: {0, 0}} -> @@ -238,7 +241,7 @@ defmodule Explorer.Chain do |> filter_topic(Keyword.get(options, :topic)) |> BlockReaderGeneral.where_block_number_in_period(from_block, to_block) |> join_associations(necessity_by_association) - |> select_repo(options).all() + |> select_repo(options).all(ExplorerHelper.maybe_timeout(timeout)) |> Enum.take(paging_options.page_size) end end @@ -328,20 +331,210 @@ defmodule Explorer.Chain do ] def block_to_transactions(block_hash, options \\ [], old_ui? \\ true) when is_list(options) do necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) + + {optional_address_associations, other_necessity_by_association} = + split_optional_address_associations(necessity_by_association) + type_filter = Keyword.get(options, :type) options |> Keyword.get(:paging_options, @default_paging_options) |> fetch_transactions_in_ascending_order_by_index() - |> join(:inner, [transaction], block in assoc(transaction, :block)) - |> where([_, block], block.hash == ^block_hash) + |> where([transaction], transaction.block_hash == ^block_hash) |> Transaction.apply_filter_by_type_to_transactions(type_filter) - |> join_associations(necessity_by_association) + |> join_associations(other_necessity_by_association) |> Transaction.put_has_token_transfers_to_transaction(old_ui?) |> (&if(old_ui?, do: preload(&1, [{:token_transfers, [:token, :from_address, :to_address]}]), else: &1)).() |> select_repo(options).all() + |> preload_optional_address_associations(optional_address_associations, options) + end + + defp split_optional_address_associations(necessity_by_association) when is_map(necessity_by_association) do + {optional, other} = + Enum.split_with(necessity_by_association, fn {association, necessity} -> + necessity == :optional and address_association_key?(association) + end) + + {optional, Map.new(other)} + end + + defp address_association_key?(association) when is_atom(association), + do: association in @deduplicated_optional_address_associations + + defp address_association_key?([{association, _nested_preloads}]), + do: association in @deduplicated_optional_address_associations + + defp address_association_key?(_), do: false + + defp preload_optional_address_associations([], _optional_address_associations, _options), do: [] + + defp preload_optional_address_associations(entities, [], _options) when is_list(entities), do: entities + + defp preload_optional_address_associations(entities, optional_address_associations, options) + when is_list(entities) and is_list(optional_address_associations) do + associations_and_nested_preloads = + Enum.map(optional_address_associations, &association_with_nested_preloads/1) + + associations = + associations_and_nested_preloads + |> Enum.map(&elem(&1, 0)) + |> Enum.uniq() + + address_nested_preloads = + associations_and_nested_preloads + |> Enum.flat_map(&normalize_nested_preloads(elem(&1, 1))) + |> Enum.uniq() + + hashes = + entities + |> Enum.flat_map(fn entity -> + Enum.flat_map(associations, fn association -> + association + |> address_hash_field_for_association() + |> then(&Map.get(entity, &1)) + |> hash_to_list() + end) + end) + |> Enum.uniq() + + addresses_by_hash = fetch_addresses_by_hash(hashes, address_nested_preloads, options) + + Enum.map(entities, fn entity -> + Enum.reduce(associations, entity, fn association, acc_entity -> + address = + association + |> address_hash_field_for_association() + |> then(&Map.get(acc_entity, &1)) + |> then(&Map.get(addresses_by_hash, &1)) + + Map.put(acc_entity, association, address) + end) + end) end + defp hash_to_list(nil), do: [] + defp hash_to_list(hash), do: [hash] + + defp fetch_addresses_by_hash([], _address_nested_preloads, _options), do: %{} + + defp fetch_addresses_by_hash(hashes, address_nested_preloads, options) do + # Split nested preloads into proxy and non-proxy preloads + # This allows us to load proxy implementations conditionally only for contract addresses + {proxy_preloads, non_proxy_preloads} = split_proxy_preloads(address_nested_preloads) + + addresses = + Address + |> where([address], address.hash in ^hashes) + |> maybe_preload_address_nested(non_proxy_preloads) + |> select_repo(options).all() + |> strip_smart_contract_large_fields() + + # Only fetch proxy implementations for contract addresses (where smart_contract exists) + addresses_with_proxy = + if Enum.any?(proxy_preloads) and Enum.any?(addresses, &proxy_applicable?/1) do + contract_addresses = Enum.filter(addresses, &proxy_applicable?/1) + batch_load_proxy_implementations_for_contracts(contract_addresses, proxy_preloads, options) + else + addresses + end + + Map.new(addresses_with_proxy, &{&1.hash, &1}) + end + + # Check if an address can have proxy implementations. + # Address.smart_contract?/1 relies on contract code presence, so this also covers EIP-7702 addresses. + defp proxy_applicable?(%Address{} = address), do: Address.smart_contract?(address) + defp proxy_applicable?(_), do: false + + # Split proxy-related preloads from other nested preloads + defp split_proxy_preloads(nested_preloads) when is_list(nested_preloads) do + {proxy, non_proxy} = + Enum.split_with(nested_preloads, fn + :proxy_implementations -> true + [{:proxy_implementations, _}] -> true + _ -> false + end) + + {proxy, non_proxy} + end + + # Batch load proxy implementations for contract addresses and attach to originating addresses + defp batch_load_proxy_implementations_for_contracts(addresses, proxy_preloads, options) when is_list(addresses) do + address_hashes = Enum.map(addresses, & &1.hash) + + proxy_implementations = + address_hashes + |> Implementation.get_proxy_implementations_for_multiple_proxies(options) + |> maybe_preload_proxy_implementation_nested(proxy_preloads, options) + + # Group proxy implementations by proxy_address_hash for efficient lookup + proxy_by_address = Map.new(proxy_implementations, &{&1.proxy_address_hash, &1}) + + # Attach proxy data to addresses + Enum.map(addresses, fn address -> + Map.put(address, :proxy_implementations, Map.get(proxy_by_address, address.hash)) + end) + end + + defp maybe_preload_proxy_implementation_nested(proxy_implementations, proxy_preloads, options) + when is_list(proxy_implementations) and is_list(proxy_preloads) do + case build_proxy_preload_spec(proxy_preloads) do + [] -> + proxy_implementations + + proxy_spec -> + select_repo(options).preload(proxy_implementations, proxy_spec) + end + end + + # Build the nested preload specification for implementation structs. + defp build_proxy_preload_spec(proxy_preloads) when is_list(proxy_preloads) do + Enum.find_value(proxy_preloads, fn + :proxy_implementations -> Implementation.proxy_implementations_addresses_association() + [{:proxy_implementations, nested}] -> nested + _ -> nil + end) || [] + end + + defp association_with_nested_preloads({association, _necessity}) when is_atom(association), + do: {association, []} + + defp association_with_nested_preloads({[{association, nested_preloads}], _necessity}), + do: {association, nested_preloads} + + defp normalize_nested_preloads(nil), do: [] + defp normalize_nested_preloads(nested_preload) when is_atom(nested_preload), do: [nested_preload] + defp normalize_nested_preloads(nested_preloads) when is_list(nested_preloads), do: nested_preloads + + defp maybe_preload_address_nested(query, []), do: query + defp maybe_preload_address_nested(query, nested_preloads), do: preload(query, ^nested_preloads) + + # Post-processes loaded addresses to remove large SmartContract fields to reduce memory usage + # and deserialization overhead for transaction listings. + + # Removes: + # - contract_source_code (can be hundreds of KB) + # - abi (can be hundreds of KB) + # - constructor_arguments (potentially large) + defp strip_smart_contract_large_fields(addresses) when is_list(addresses) do + Enum.map(addresses, fn address -> + if Map.has_key?(address, :smart_contract) && address.smart_contract && + !is_struct(address.smart_contract, Ecto.Association.NotLoaded) do + filtered_contract = + address.smart_contract + |> Map.drop([:contract_source_code, :abi, :constructor_arguments]) + + Map.put(address, :smart_contract, filtered_contract) + else + address + end + end) + end + + defp address_hash_field_for_association(:from_address), do: :from_address_hash + defp address_hash_field_for_association(:to_address), do: :to_address_hash + defp address_hash_field_for_association(:created_contract_address), do: :created_contract_address_hash + @spec execution_node_to_transactions(Hash.Address.t(), [paging_options | necessity_by_association_option | api?()]) :: [Transaction.t()] def execution_node_to_transactions(execution_node_hash, options \\ []) when is_list(options) do @@ -685,13 +878,17 @@ defmodule Explorer.Chain do necessity_by_association = options |> Keyword.get(:necessity_by_association, default_hash_to_address_necessity_by_association()) - |> maybe_remove_internal_transaction_association(include_internal_transaction_association?) query = Address.address_query(hash) query |> join_associations(necessity_by_association) |> select_repo(options).one() + |> then(fn address -> + if include_internal_transaction_association?, + do: Address.preload_contract_creation_internal_transaction(address, select_repo(options)), + else: address + end) |> SmartContract.compose_address_for_unverified_smart_contract(hash, options) |> case do nil -> {:error, :not_found} @@ -704,40 +901,10 @@ defmodule Explorer.Chain do :names => :optional, :smart_contract => :optional, :token => :optional, - Address.contract_creation_transaction_associations() => :optional + Address.contract_creation_transaction_association() => :optional } end - defp maybe_remove_internal_transaction_association(necessity_by_association, true), - do: necessity_by_association - - defp maybe_remove_internal_transaction_association(necessity_by_association, false) do - necessity_by_association - |> replace_association_key( - Address.contract_creation_transaction_associations(), - Address.contract_creation_transaction_associations(false) - ) - |> replace_association_key( - Address.contract_creation_transaction_with_from_address_associations(), - Address.contract_creation_transaction_with_from_address_associations(false) - ) - |> Map.delete(:contract_creation_internal_transaction) - |> Map.delete(Address.contract_creation_internal_transaction_association()) - |> Map.delete(Address.contract_creation_internal_transaction_with_from_address_association()) - end - - defp replace_association_key(necessity_by_association, old_key, new_key) do - case Map.fetch(necessity_by_association, old_key) do - {:ok, value} -> - necessity_by_association - |> Map.delete(old_key) - |> Map.put(new_key, value) - - :error -> - necessity_by_association - end - end - @doc """ Converts `t:Explorer.Chain.Address.t/0` `hash` to the `t:Explorer.Chain.Address.t/0` with that `hash`. @@ -776,7 +943,7 @@ defmodule Explorer.Chain do :names => :optional, :smart_contract => :optional, :token => :optional, - Address.contract_creation_transaction_associations() => :optional + Address.contract_creation_transaction_association() => :optional } ] ) do @@ -2042,9 +2209,20 @@ defmodule Explorer.Chain do """ @spec join_associations(atom() | Ecto.Query.t(), %{any() => :optional | :required}) :: Ecto.Query.t() def join_associations(query, necessity_by_association) when is_map(necessity_by_association) do - Enum.reduce(necessity_by_association, query, fn {association, join}, acc_query -> - join_association(acc_query, association, join) - end) + {optional_associations, required_associations} = + Enum.split_with(necessity_by_association, fn {_association, join} -> join == :optional end) + + query_with_required_joins = + Enum.reduce(required_associations, query, fn {association, _join}, acc_query -> + join_association(acc_query, association, :required) + end) + + optional_preloads = Enum.map(optional_associations, fn {association, _join} -> association end) + + case optional_preloads do + [] -> query_with_required_joins + _ -> preload(query_with_required_joins, ^optional_preloads) + end end def page_blocks(query, %PagingOptions{key: nil}), do: query @@ -2375,14 +2553,18 @@ defmodule Explorer.Chain do |> select_repo(options).all() end - @spec fetch_token_holders_from_token_hash_for_csv(Hash.Address.t(), [paging_options | api?]) :: [TokenBalance.t()] + @spec fetch_token_holders_from_token_hash_for_csv(Hash.Address.t(), [paging_options | api? | timeout_option]) :: [ + TokenBalance.t() + ] def fetch_token_holders_from_token_hash_for_csv(contract_address_hash, options \\ []) do + timeout = Keyword.get(options, :timeout) + query = contract_address_hash |> CurrentTokenBalance.token_holders_ordered_by_value_query_without_address_preload(options) query - |> select_repo(options).all() + |> select_repo(options).all(ExplorerHelper.maybe_timeout(timeout)) end def fetch_token_holders_from_token_hash_and_token_id(contract_address_hash, token_id, options \\ []) do diff --git a/apps/explorer/lib/explorer/chain/address.ex b/apps/explorer/lib/explorer/chain/address.ex index 81e9f881de5a..0191aff73556 100644 --- a/apps/explorer/lib/explorer/chain/address.ex +++ b/apps/explorer/lib/explorer/chain/address.ex @@ -137,18 +137,12 @@ defmodule Explorer.Chain.Address.Schema do field(:gas_used, :integer) field(:ens_domain_name, :string, virtual: true) field(:metadata, :any, virtual: true) + field(:contract_creation_internal_transaction, :map, virtual: true) has_one(:smart_contract, SmartContract, references: :hash) has_one(:token, Token, foreign_key: :contract_address_hash, references: :hash) has_one(:proxy_implementations, Implementation, foreign_key: :proxy_address_hash, references: :hash) - has_one( - :contract_creation_internal_transaction, - InternalTransaction, - foreign_key: :created_contract_address_hash, - references: :hash - ) - has_one( :contract_creation_transaction, Transaction, @@ -472,11 +466,27 @@ defmodule Explorer.Chain.Address do Preloads provided contracts associations if address has contract_code which is not nil """ @spec maybe_preload_smart_contract_associations(__MODULE__.t(), list, list) :: __MODULE__.t() + def maybe_preload_smart_contract_associations(address, associations, options) + def maybe_preload_smart_contract_associations(%__MODULE__{contract_code: nil} = address, _associations, _options), do: address - def maybe_preload_smart_contract_associations(%__MODULE__{contract_code: _} = address, associations, options), - do: Chain.select_repo(options).preload(address, associations) + def maybe_preload_smart_contract_associations(%__MODULE__{contract_code: _} = address, associations, options) do + repo = Chain.select_repo(options) + + address + |> repo.preload(associations) + |> maybe_preload_contract_creation_internal_transaction(repo) + end + + @spec maybe_preload_contract_creation_internal_transaction(__MODULE__.t(), module()) :: __MODULE__.t() + defp maybe_preload_contract_creation_internal_transaction(address, repo) do + if Application.get_env(:explorer, :api_disable_contract_creation_internal_transaction_association, false) do + address + else + preload_contract_creation_internal_transaction(address, repo) + end + end @doc """ Counts all the addresses where the `fetched_coin_balance` is > 0. @@ -830,6 +840,58 @@ defmodule Explorer.Chain.Address do ) end + @doc """ + Preloads the contract creation internal transaction for the given address or + list of addresses. + + For each address, this function finds the most relevant internal transaction + whose `created_contract_address_hash` resolves to the address hash, preloads + its related addresses, and assigns it to the virtual + `:contract_creation_internal_transaction` field. + + When a list of addresses is provided, the function performs a single batch + query for all address hashes, builds a map keyed by + `created_contract_address_hash`, and attaches the matched internal transaction + to each address. + + ## Parameters + + - `addresses`: An `Explorer.Chain.Address.t/0`, a list of addresses, `[]`, or `nil` + - `repo`: The repo module used to execute the query. Defaults to `Explorer.Repo` + + ## Returns + + - A list of addresses with `:contract_creation_internal_transaction` + populated when the input is a list + - A single address with `:contract_creation_internal_transaction` + populated when the input is a single struct + """ + @spec preload_contract_creation_internal_transaction([__MODULE__.t()] | __MODULE__.t() | nil, module()) :: + [__MODULE__.t()] | __MODULE__.t() | nil + def preload_contract_creation_internal_transaction(addresses, repo \\ Repo) + + def preload_contract_creation_internal_transaction([], _repo), do: [] + def preload_contract_creation_internal_transaction(nil, _repo), do: nil + + def preload_contract_creation_internal_transaction(addresses, repo) when is_list(addresses) do + address_hashes = Enum.map(addresses, & &1.hash) + + internal_transactions_map = + contract_creation_internal_transaction_preload_query() + |> InternalTransaction.where_address_match(:created_contract_address, address_hashes) + |> repo.all() + |> InternalTransaction.preload_addresses([], repo) + |> Map.new(&{&1.created_contract_address_hash, &1}) + + Enum.map(addresses, &%{&1 | contract_creation_internal_transaction: internal_transactions_map[&1.hash]}) + end + + def preload_contract_creation_internal_transaction(address, repo) do + [address] + |> preload_contract_creation_internal_transaction(repo) + |> List.first() + end + @doc """ Creates a query for preloading contract creation internal transactions. @@ -848,17 +910,17 @@ defmodule Explorer.Chain.Address do """ @spec contract_creation_internal_transaction_preload_query() :: Ecto.Query.t() def contract_creation_internal_transaction_preload_query do - from( - it in InternalTransaction, - where: it.index > 0, - order_by: [ - asc_nulls_first: coalesce(it.error, type(it.error_id, :string)), - desc: it.block_number, - desc: it.transaction_index, - desc: it.index - ], - limit: 1 + InternalTransaction + |> InternalTransaction.join_transaction_query() + |> where([it], it.index > 0) + |> order_by([it], + asc_nulls_first: it.error_id, + desc: it.block_number, + desc: it.transaction_index, + desc: it.index ) + |> limit(1) + |> select_merge([_it, t], %{transaction: t}) end @doc """ @@ -918,142 +980,6 @@ defmodule Explorer.Chain.Address do ] end - @doc """ - Returns contract creation internal transaction association specification. - - ## Note - IMPORTANT: This association function should be used ONLY for single address - operations. Using it with multiple addresses may produce unexpected results. - - As noted in [Ecto documentation](https://hexdocs.pm/ecto/Ecto.Query.html#preload/3-preload-queries), - operations like `limit` and `offset` in preload queries affect the entire - result set, not each individual association. When working with collections of - addresses, consider using window functions instead of these helpers. - - ## Returns - A keyword list with the contract creation internal transaction association. - """ - @spec contract_creation_internal_transaction_association() :: keyword() - def contract_creation_internal_transaction_association do - [ - contract_creation_internal_transaction: Address.contract_creation_internal_transaction_preload_query() - ] - end - - @doc """ - Same as `contract_creation_internal_transaction_association/0`, but - preloads a nested association for the `from_address` field. Used for Filecoin - chain type. - """ - @spec contract_creation_internal_transaction_with_from_address_association() :: keyword() - def contract_creation_internal_transaction_with_from_address_association do - [ - contract_creation_internal_transaction: { - contract_creation_internal_transaction_preload_query(), - :from_address - } - ] - end - - @doc """ - Returns contract creation transaction associations. - - By default, includes both the regular transaction association and the internal - transaction association. Can be customized via the `include_internal_transaction` - parameter. - - ## Parameters - - - `include_internal_transaction`: Whether to include the internal transaction - association. Defaults to `true`. Set to `false` to return only the regular - transaction association. - - ## Returns - - A list containing the contract creation transaction associations. - """ - @spec contract_creation_transaction_associations(boolean()) :: [keyword()] - def contract_creation_transaction_associations(include_internal_transaction \\ true) do - if include_internal_transaction do - [ - contract_creation_transaction_association(), - contract_creation_internal_transaction_association() - ] - else - [contract_creation_transaction_association()] - end - end - - @doc """ - Same as `contract_creation_transaction_associations/1`, but preloads a nested - association for the `from_address` field. Used for Filecoin chain type. - - ## Parameters - - - `include_internal_transaction`: Whether to include the internal transaction - association. Defaults to `true`. Set to `false` to return only the regular - transaction association. - - ## Returns - - A list containing the contract creation transaction associations with from_address. - """ - @spec contract_creation_transaction_with_from_address_associations(boolean()) :: [keyword()] - def contract_creation_transaction_with_from_address_associations(include_internal_transaction \\ true) do - if include_internal_transaction do - [ - contract_creation_transaction_with_from_address_association(), - contract_creation_internal_transaction_with_from_address_association() - ] - else - [contract_creation_transaction_with_from_address_association()] - end - end - - @doc """ - Finds contract addresses from a list of hashes. - - ## Parameters - - - `hashes`: A list of hashes to search for contract addresses. - - `options`: An optional keyword list of options. - - ## Options - - - `:necessity_by_association`: A map of associations with their necessity (default: `%{}`). - - ## Returns - - - `{:ok, addresses}`: A tuple with `:ok` and a list of found addresses. - - `{:error, :not_found}`: A tuple with `:error` and `:not_found` if no addresses are found. - - """ - @spec find_contract_addresses([Hash.Address.t()], [Chain.necessity_by_association_option() | Chain.api?()]) :: - {:ok, [__MODULE__.t()]} | {:error, :not_found} - def find_contract_addresses( - hashes, - options \\ [] - ) do - necessity_by_association = - options - |> Keyword.get(:necessity_by_association, %{}) - |> Map.merge(%{ - Implementation.proxy_implementations_association() => :optional - }) - - hashes - |> addresses_with_bytecode_query() - |> Chain.join_associations(necessity_by_association) - |> Chain.select_repo(options).all() - |> Enum.map(fn address_result -> - update_address_result(address_result, options, true) - end) - |> case do - [] -> {:error, :not_found} - addresses -> {:ok, addresses} - end - end - @spec update_address_result( map() | nil, [Chain.necessity_by_association_option() | Chain.api?() | Chain.ip()], @@ -1094,14 +1020,12 @@ defmodule Explorer.Chain.Address do """ @spec creation_internal_transaction_query(binary() | Hash.t()) :: Ecto.Query.t() def creation_internal_transaction_query(address_hash) do - from( - it in InternalTransaction, - inner_join: t in assoc(it, :transaction), - where: it.created_contract_address_hash == ^address_hash, - where: t.status == ^1, - order_by: [desc: it.block_number], - limit: 1 - ) + InternalTransaction + |> InternalTransaction.join_transaction_query() + |> InternalTransaction.where_address_match(:created_contract_address, address_hash) + |> where(as(:transaction).status == ^:ok) + |> order_by([it], desc: it.block_number, desc: it.transaction_index, desc: it.index) + |> limit(1) end @doc """ @@ -1132,6 +1056,13 @@ defmodule Explorer.Chain.Address do |> address_with_bytecode_query() |> Chain.join_associations(necessity_by_association) |> Chain.select_repo(options).one() + |> then(fn address -> + if Keyword.get(options, :preload_contract_creation_internal_transaction, false) do + Address.preload_contract_creation_internal_transaction(address) + else + address + end + end) |> update_address_result(options, false) |> case do nil -> {:error, :not_found} diff --git a/apps/explorer/lib/explorer/chain/address/coin_balance.ex b/apps/explorer/lib/explorer/chain/address/coin_balance.ex index 2b066aff2383..b08bce8608c9 100644 --- a/apps/explorer/lib/explorer/chain/address/coin_balance.ex +++ b/apps/explorer/lib/explorer/chain/address/coin_balance.ex @@ -311,18 +311,25 @@ defmodule Explorer.Chain.Address.CoinBalance do # credo:disable-for-next-line Credo.Check.Refactor.CyclomaticComplexity defp preload_internal_transaction_query(balance) do InternalTransaction + |> InternalTransaction.join_transaction_query() + |> InternalTransaction.join_address_mapping_query(:from_address) + |> InternalTransaction.join_address_mapping_query(:to_address) + |> InternalTransaction.join_address_mapping_query(:created_contract_address) |> where( [internal_transaction], internal_transaction.block_number == ^balance.block_number and internal_transaction.type in ~w(call create create2 selfdestruct)a and (is_nil(coalesce(type(internal_transaction.call_type_enum, :string), internal_transaction.call_type)) or coalesce(type(internal_transaction.call_type_enum, :string), internal_transaction.call_type) == ^"call") and - internal_transaction.value > ^0 and is_nil(internal_transaction.error) and is_nil(internal_transaction.error_id) and + internal_transaction.value > ^0 and is_nil(internal_transaction.error_id) and (internal_transaction.to_address_hash == ^balance.address_hash or + as(:to_address_mapping).address_hash == ^balance.address_hash or internal_transaction.from_address_hash == ^balance.address_hash or - internal_transaction.created_contract_address_hash == ^balance.address_hash) + as(:from_address_mapping).address_hash == ^balance.address_hash or + internal_transaction.created_contract_address_hash == ^balance.address_hash or + as(:created_contract_address_mapping).address_hash == ^balance.address_hash) ) - |> select([internal_transaction], internal_transaction.transaction_hash) + |> select([_internal_transaction, transaction], transaction.hash) |> limit(1) end diff --git a/apps/explorer/lib/explorer/chain/address/current_token_balance.ex b/apps/explorer/lib/explorer/chain/address/current_token_balance.ex index 2bb6413586ef..652c9fd05995 100644 --- a/apps/explorer/lib/explorer/chain/address/current_token_balance.ex +++ b/apps/explorer/lib/explorer/chain/address/current_token_balance.ex @@ -14,7 +14,7 @@ defmodule Explorer.Chain.Address.CurrentTokenBalance do import Explorer.Chain.SmartContract.Proxy.Models.Implementation, only: [proxy_implementations_association: 0] alias Explorer.{Chain, PagingOptions, Repo} - alias Explorer.Chain.{Address, Block, CurrencyHelper, Hash, Token} + alias Explorer.Chain.{Address, Block, Hash, Token} alias Explorer.Chain.Address.TokenBalance alias Explorer.Chain.Cache.BackgroundMigrations @@ -339,28 +339,6 @@ defmodule Explorer.Chain.Address.CurrentTokenBalance do TokenBalance.delete_token_balance_placeholders_below(__MODULE__, token_contract_address_hash, block_number) end - @doc """ - Converts CurrentTokenBalances to CSV format. Used in `BlockScoutWeb.API.V2.CsvExportController.export_token_holders/2` - """ - @spec to_csv_format([t()], Token.t()) :: (any(), any() -> {:halted, any()} | {:suspended, any(), (any() -> any())}) - def to_csv_format(holders, token) do - row_names = [ - "HolderAddress", - "Balance" - ] - - holders_list = - holders - |> Stream.map(fn ctb -> - [ - Address.checksum(ctb.address_hash), - ctb.value |> CurrencyHelper.divide_decimals(token.decimals) |> Decimal.to_string(:xsd) - ] - end) - - Stream.concat([row_names], holders_list) - end - @doc """ Returns a stream of all current token balances that weren't fetched values. """ diff --git a/apps/explorer/lib/explorer/chain/advanced_filter.ex b/apps/explorer/lib/explorer/chain/advanced_filter.ex index aca4aa879908..7f4a4071f1a3 100644 --- a/apps/explorer/lib/explorer/chain/advanced_filter.ex +++ b/apps/explorer/lib/explorer/chain/advanced_filter.ex @@ -23,6 +23,7 @@ defmodule Explorer.Chain.AdvancedFilter do } alias Explorer.Chain.Block.Reader.General, as: BlockGeneralReader + alias Explorer.Utility.AddressIdToAddressHash @primary_key false typed_embedded_schema null: false do @@ -391,36 +392,33 @@ defmodule Explorer.Chain.AdvancedFilter do defp internal_transactions_query_function(paging_options, options) do query = if DenormalizationHelper.transactions_denormalization_finished?() do - from(internal_transaction in InternalTransaction, - as: :internal_transaction, - join: transaction in assoc(internal_transaction, :transaction), - as: :transaction, - where: transaction.block_consensus == true, - where: - (internal_transaction.type == :call and internal_transaction.index > 0) or - internal_transaction.type != :call, - order_by: [ - desc: transaction.block_number, - desc: transaction.index, - desc: internal_transaction.index - ] + InternalTransaction + |> from(as: :internal_transaction) + |> InternalTransaction.join_transaction_query() + |> where(as(:transaction).block_consensus == true) + |> where( + (as(:internal_transaction).type == :call and as(:internal_transaction).index > 0) or + as(:internal_transaction).type != :call + ) + |> order_by( + desc: as(:transaction).block_number, + desc: as(:transaction).index, + desc: as(:internal_transaction).index ) else - from(internal_transaction in InternalTransaction, - as: :internal_transaction, - join: transaction in assoc(internal_transaction, :transaction), - as: :transaction, - join: block in assoc(internal_transaction, :block), - as: :block, - where: block.consensus == true, - where: - (internal_transaction.type == :call and internal_transaction.index > 0) or - internal_transaction.type != :call, - order_by: [ - desc: transaction.block_number, - desc: transaction.index, - desc: internal_transaction.index - ] + InternalTransaction + |> from(as: :internal_transaction) + |> InternalTransaction.join_transaction_query() + |> join(:inner, [internal_transaction], block in assoc(internal_transaction, :block), as: :block) + |> where(as(:block).consensus == true) + |> where( + (as(:internal_transaction).type == :call and as(:internal_transaction).index > 0) or + as(:internal_transaction).type != :call + ) + |> order_by( + desc: as(:transaction).block_number, + desc: as(:transaction).index, + desc: as(:internal_transaction).index ) end @@ -428,7 +426,7 @@ defmodule Explorer.Chain.AdvancedFilter do query |> page_internal_transactions(paging_options) |> limit_query(paging_options) - |> apply_transactions_filters(options, fn query -> + |> apply_internal_transactions_filters(options, fn query -> query |> order_by([internal_transaction], desc: internal_transaction.block_number, @@ -437,9 +435,13 @@ defmodule Explorer.Chain.AdvancedFilter do ) end) |> limit_query(paging_options) - |> preload([:transaction]) - fn repo, repo_options -> repo.all(filtered_and_paginated_query, repo_options) end + fn repo, repo_options -> + filtered_and_paginated_query + |> repo.all(repo_options) + |> InternalTransaction.preload_transaction(repo) + |> InternalTransaction.preload_addresses([], repo) + end end defp page_internal_transactions(query, %PagingOptions{ @@ -822,6 +824,21 @@ defmodule Explorer.Chain.AdvancedFilter do ) end + defp apply_internal_transactions_filters(query, options, order_by) do + query + |> filter_internal_transaction_by_types(options[:transaction_types]) + |> filter_transactions_by_amount(options[:amount][:from], options[:amount][:to]) + |> filter_transactions_by_methods(options[:methods]) + |> only_collated_transactions() + |> filter_by_age(:transaction, options) + |> filter_internal_transactions_by_addresses( + options[:from_address_hashes], + options[:to_address_hashes], + options[:address_relation], + order_by + ) + end + defp only_collated_transactions(query) do query |> where(not is_nil(as(:transaction).block_number) and not is_nil(as(:transaction).index)) end @@ -853,6 +870,33 @@ defmodule Explorer.Chain.AdvancedFilter do end end + defp filter_internal_transaction_by_types(query, types) when types in [nil, []], do: query + + defp filter_internal_transaction_by_types(query, types) do + if Enum.all?(@transaction_types, &Enum.member?(types, &1)) do + query + else + dynamic_condition = + types + |> Enum.reduce(nil, fn + type, dynamic_condition when type in @transaction_types -> + filter_internal_transaction_by_type(type, dynamic_condition) + + _, dynamic_condition -> + dynamic_condition + end) + + query = + if "CONTRACT_INTERACTION" in types or "CONTRACT_CREATION" in types do + InternalTransaction.join_address_query(query, :to_address) + else + query + end + + query |> where(^dynamic_condition) + end + end + defp filter_transaction_by_type("COIN_TRANSFER", nil), do: dynamic([t], t.value > ^0) defp filter_transaction_by_type("COIN_TRANSFER", dynamic_condition), @@ -869,6 +913,16 @@ defmodule Explorer.Chain.AdvancedFilter do defp filter_transaction_by_type("CONTRACT_CREATION", dynamic_condition), do: dynamic([t], is_nil(t.to_address_hash) or ^dynamic_condition) + defp filter_internal_transaction_by_type("CONTRACT_CREATION", nil), + do: dynamic([it], is_nil(as(:to_address_mapping).address_hash) and is_nil(it.to_address_hash)) + + defp filter_internal_transaction_by_type("CONTRACT_CREATION", dynamic_condition), + do: + dynamic([it], (is_nil(as(:to_address_mapping).address_hash) and is_nil(it.to_address_hash)) or ^dynamic_condition) + + defp filter_internal_transaction_by_type(type, dynamic_condition), + do: filter_transaction_by_type(type, dynamic_condition) + defp filter_token_transfer_by_types(query_function, [_ | _] = types) do types = types -- @transaction_types @@ -1166,6 +1220,17 @@ defmodule Explorer.Chain.AdvancedFilter do end end + defp filter_internal_transactions_by_addresses(query, from_addresses, to_addresses, relation, order_by) do + order_by = fn query -> query |> exclude(:order_by) |> order_by.() end + + case {process_address_inclusion(from_addresses), process_address_inclusion(to_addresses)} do + {nil, nil} -> query + {from, nil} -> do_filter_internal_transactions_by_address(query, from, :from_address, order_by) + {nil, to} -> do_filter_internal_transactions_by_address(query, to, :to_address, order_by) + {from, to} -> do_filter_internal_transactions_by_both_addresses(query, from, to, relation, order_by) + end + end + defp do_filter_transactions_by_address(query, {:include, addresses}, field, order_by) do queries = addresses @@ -1187,6 +1252,33 @@ defmodule Explorer.Chain.AdvancedFilter do |> order_by.() end + defp do_filter_internal_transactions_by_address(query, {:include, addresses}, binding, order_by) do + queries = + addresses + |> Enum.map(fn address -> + query + |> InternalTransaction.where_address_match(binding, address) + |> order_by.() + end) + |> map_first(&subquery/1) + |> Enum.reduce(fn query, acc -> union_all(acc, ^query) end) + + order_by.(from(internal_transaction in subquery(queries))) + end + + defp do_filter_internal_transactions_by_address(query, {:exclude, addresses}, binding, order_by) do + address_ids = AddressIdToAddressHash.hashes_to_ids(addresses) + address_id_field = String.to_existing_atom("#{binding}_id") + address_hash_field = String.to_existing_atom("#{binding}_hash") + + query + |> where( + [it], + field(it, ^address_id_field) not in ^address_ids and field(it, ^address_hash_field) not in ^addresses + ) + |> order_by.() + end + defp do_filter_transactions_by_both_addresses(query, {:include, from}, {:include, to}, :and, order_by) do query |> where([transaction], transaction.from_address_hash in ^from and transaction.to_address_hash in ^to) @@ -1310,6 +1402,131 @@ defmodule Explorer.Chain.AdvancedFilter do |> order_by.() end + defp do_filter_internal_transactions_by_both_addresses(query, {:include, from}, {:include, to}, :and, order_by) do + query + |> InternalTransaction.where_address_match(:from_address, from) + |> InternalTransaction.where_address_match(:to_address, to) + |> order_by.() + end + + defp do_filter_internal_transactions_by_both_addresses(query, {:include, from}, {:include, to}, _relation, order_by) do + from_queries = + from + |> Enum.map(fn from_address -> + query + |> InternalTransaction.where_address_match(:from_address, from_address) + |> order_by.() + end) + + to_queries = + to + |> Enum.map(fn to_address -> + query + |> InternalTransaction.where_address_match(:to_address, to_address) + |> order_by.() + end) + + union_query = + from_queries + |> Kernel.++(to_queries) + |> map_first(&subquery/1) + |> Enum.reduce(fn query, acc -> union(acc, ^query) end) + + order_by.(from(internal_transaction in subquery(union_query))) + end + + defp do_filter_internal_transactions_by_both_addresses(query, {:include, from}, {:exclude, to}, :and, order_by) do + to_address_ids = AddressIdToAddressHash.hashes_to_ids(to) + + from_queries = + from + |> Enum.map(fn from_address -> + query + |> InternalTransaction.where_address_match(:from_address, from_address) + |> where([it], it.to_address_id not in ^to_address_ids and it.to_address_hash not in ^to) + |> order_by.() + end) + |> map_first(&subquery/1) + |> Enum.reduce(fn query, acc -> union_all(acc, ^query) end) + + order_by.(from(internal_transaction in subquery(from_queries))) + end + + defp do_filter_internal_transactions_by_both_addresses(query, {:include, from}, {:exclude, to}, _relation, order_by) do + to_address_ids = AddressIdToAddressHash.hashes_to_ids(to) + + from_queries = + from + |> Enum.map(fn from_address -> + query + |> InternalTransaction.where_address_match(:from_address, from_address) + |> or_where([it], it.to_address_id not in ^to_address_ids and it.to_address_hash not in ^to) + |> order_by.() + end) + |> map_first(&subquery/1) + |> Enum.reduce(fn query, acc -> union_all(acc, ^query) end) + + order_by.(from(internal_transaction in subquery(from_queries))) + end + + defp do_filter_internal_transactions_by_both_addresses(query, {:exclude, from}, {:include, to}, :and, order_by) do + from_address_ids = AddressIdToAddressHash.hashes_to_ids(from) + + to_queries = + to + |> Enum.map(fn to_address -> + query + |> InternalTransaction.where_address_match(:to_address, to_address) + |> where([it], it.from_address_id not in ^from_address_ids and it.from_address_hash not in ^from) + |> order_by.() + end) + |> map_first(&subquery/1) + |> Enum.reduce(fn query, acc -> union_all(acc, ^query) end) + + order_by.(from(internal_transaction in subquery(to_queries))) + end + + defp do_filter_internal_transactions_by_both_addresses(query, {:exclude, from}, {:include, to}, _relation, order_by) do + from_address_ids = AddressIdToAddressHash.hashes_to_ids(from) + + to_queries = + to + |> Enum.map(fn to_address -> + query + |> InternalTransaction.where_address_match(:to_address, to_address) + |> or_where([it], it.from_address_id not in ^from_address_ids and it.from_address_hash not in ^from) + |> order_by.() + end) + |> map_first(&subquery/1) + |> Enum.reduce(fn query, acc -> union_all(acc, ^query) end) + + order_by.(from(internal_transaction in subquery(to_queries))) + end + + defp do_filter_internal_transactions_by_both_addresses(query, {:exclude, from}, {:exclude, to}, :and, order_by) do + from_address_ids = AddressIdToAddressHash.hashes_to_ids(from) + to_address_ids = AddressIdToAddressHash.hashes_to_ids(to) + + query + |> where([it], it.from_address_id not in ^from_address_ids and it.from_address_hash not in ^from) + |> where([it], it.to_address_id not in ^to_address_ids and it.to_address_hash not in ^to) + |> order_by.() + end + + defp do_filter_internal_transactions_by_both_addresses(query, {:exclude, from}, {:exclude, to}, _relation, order_by) do + from_address_ids = AddressIdToAddressHash.hashes_to_ids(from) + to_address_ids = AddressIdToAddressHash.hashes_to_ids(to) + + query + |> where(as(:from_address).hash not in ^from or as(:to_address).hash not in ^to) + |> where( + [it], + (it.from_address_id not in ^from_address_ids and it.from_address_hash not in ^from) or + (it.to_address_id not in ^to_address_ids and it.to_address_hash not in ^to) + ) + |> order_by.() + end + @eth_decimals 1_000_000_000_000_000_000 defp filter_transactions_by_amount(query, from, to) when not is_nil(from) and not is_nil(to) do diff --git a/apps/explorer/lib/explorer/chain/block.ex b/apps/explorer/lib/explorer/chain/block.ex index 73fcc389c086..6d95a3d23229 100644 --- a/apps/explorer/lib/explorer/chain/block.ex +++ b/apps/explorer/lib/explorer/chain/block.ex @@ -220,6 +220,8 @@ defmodule Explorer.Chain.Block do use Utils.RuntimeEnvHelper, miner_gets_burnt_fees?: [:explorer, [Explorer.Chain.Transaction, :block_miner_gets_burnt_fees?]] + alias EthereumJSONRPC.Utility.RangesHelper + alias Explorer.Chain.{ Block, DenormalizationHelper, @@ -233,6 +235,7 @@ defmodule Explorer.Chain.Block do alias Explorer.{Chain, Helper, PagingOptions, Repo} alias Explorer.Chain.Block.{EmissionReward, Reward, SecondDegreeRelation} + alias Explorer.Chain.InternalTransaction.DeleteQueue, as: InternalTransactionDeleteQueue alias Explorer.Utility.MissingBlockRange @optional_attrs ~w(size refetch_needed total_difficulty difficulty base_fee_per_gas)a @@ -619,6 +622,56 @@ defmodule Explorer.Chain.Block do |> Decimal.div_int(base_fee_max_change_denominator) end + @doc """ + Queues blocks for a full refetch and marks them as needing refetch. + + This function accepts either a block ranges string, a list of block numbers, + or a single block number. When given a ranges string, it parses the string into + individual block numbers. It then enqueues the blocks for internal transaction + cleanup and marks the corresponding blocks with `refetch_needed: true` inside a + single database transaction. + + ## Parameters + + - `block_ranges_string_or_numbers`: A block ranges string, a list of block numbers, or a single block number. + + ## Returns + + - The result of `Repo.transaction/2` for range strings and lists. + - The delegated result for a single block number. + + ## Examples + + iex> full_refetch("1..3,5..6") + {:ok, _} + + iex> full_refetch([10, 11, 12]) + {:ok, _} + + iex> full_refetch(15) + {:ok, _} + + """ + @spec full_refetch(binary() | [integer()] | integer()) :: {:ok, any()} | {:error, any()} + def full_refetch(block_ranges_string) when is_binary(block_ranges_string) do + block_ranges_string + |> RangesHelper.parse_block_ranges_to_numbers() + |> full_refetch() + end + + def full_refetch(block_numbers) when is_list(block_numbers) do + Repo.transaction( + fn -> + # TODO: delete other crucial entities as well + InternalTransactionDeleteQueue.batch_insert(block_numbers) + set_refetch_needed(block_numbers) + end, + timeout: :infinity + ) + end + + def full_refetch(block_number), do: full_refetch([block_number]) + @spec set_refetch_needed(integer | [integer]) :: :ok def set_refetch_needed(block_numbers) when is_list(block_numbers) do query = diff --git a/apps/explorer/lib/explorer/chain/bridged_token.ex b/apps/explorer/lib/explorer/chain/bridged_token.ex index ad0e47e6546c..1573f0fa5947 100644 --- a/apps/explorer/lib/explorer/chain/bridged_token.ex +++ b/apps/explorer/lib/explorer/chain/bridged_token.ex @@ -4,16 +4,9 @@ defmodule Explorer.Chain.BridgedToken do """ use Explorer.Schema - import Ecto.Changeset import EthereumJSONRPC, only: [json_rpc: 2] import Explorer.Chain.Address.Reputation, only: [reputation_association: 0] - import Ecto.Query, - only: [ - from: 2, - limit: 2 - ] - alias ABI.{TypeDecoder, TypeEncoder} alias Ecto.Changeset alias EthereumJSONRPC.Contract @@ -235,10 +228,8 @@ defmodule Explorer.Chain.BridgedToken do |> Enum.count() > 0 created_from_internal_transaction_query = - from( - it in InternalTransaction, - where: it.created_contract_address_hash == ^token_address_hash - ) + InternalTransaction + |> InternalTransaction.where_address_match(:created_contract_address, token_address_hash) created_from_internal_transaction = created_from_internal_transaction_query @@ -304,16 +295,20 @@ defmodule Explorer.Chain.BridgedToken do mediator ) do omni_bridge_mediator = Application.get_env(:explorer, __MODULE__)[mediator] - %{transaction_hash: transaction_hash} = created_from_internal_transaction_success + %{block_number: block_number, transaction_index: transaction_index} = created_from_internal_transaction_success if omni_bridge_mediator && omni_bridge_mediator !== "" do {:ok, omni_bridge_mediator_hash} = Chain.string_to_address_hash(omni_bridge_mediator) created_by_amb_mediator_query = - from( - it in InternalTransaction, - where: it.transaction_hash == ^transaction_hash, - where: it.to_address_hash == ^omni_bridge_mediator_hash + InternalTransaction + |> InternalTransaction.join_address_mapping_query(:to_address) + |> where([it], it.block_number == ^block_number) + |> where([it], it.transaction_index == ^transaction_index) + |> where( + [it], + it.to_address_hash == ^omni_bridge_mediator_hash or + as(:to_address_mapping).address_hash == ^omni_bridge_mediator_hash ) created_by_amb_mediator = diff --git a/apps/explorer/lib/explorer/chain/cache/background_migrations.ex b/apps/explorer/lib/explorer/chain/cache/background_migrations.ex index ff972d3bd350..89608d6082d5 100644 --- a/apps/explorer/lib/explorer/chain/cache/background_migrations.ex +++ b/apps/explorer/lib/explorer/chain/cache/background_migrations.ex @@ -59,10 +59,16 @@ defmodule Explorer.Chain.Cache.BackgroundMigrations do key: :heavy_indexes_drop_token_instances_token_id_index_finished, key: :heavy_indexes_drop_internal_transactions_created_contract_address_hash_partial_index_finished, key: :heavy_indexes_create_tokens_name_partial_fts_index_finished, + key: :heavy_indexes_create_idx_tokens_ord_mcap_fiat_holder_name_finished, + key: :heavy_indexes_create_idx_tokens_ord_fiat_holder_name_finished, + key: :heavy_indexes_create_idx_tokens_ord_holder_name_finished, key: :heavy_indexes_update_internal_transactions_primary_key_finished, key: :empty_internal_transactions_data_finished, key: :heavy_indexes_create_transactions_created_contract_address_hash_w_pending_index_finished, - key: :heavy_indexes_drop_transactions_created_contract_address_hash_with_pending_index_a_finished + key: :heavy_indexes_drop_transactions_created_contract_address_hash_with_pending_index_a_finished, + key: :heavy_indexes_create_addresses_hash_contract_code_not_null_index_finished, + key: :heavy_indexes_create_address_ids_internal_transactions_indexes_finished, + key: :fill_internal_transactions_address_ids_finished @dialyzer :no_match @@ -72,18 +78,21 @@ defmodule Explorer.Chain.Cache.BackgroundMigrations do ArbitrumDaRecordsNormalization, BackfillMultichainSearchDB, EmptyInternalTransactionsData, + FillInternalTransactionsAddressIds, SanitizeDuplicatedLogIndexLogs, TokenTransferTokenType, TransactionsDenormalization } alias Explorer.Migrator.HeavyDbIndexOperation.{ + CreateAddressesHashContractCodeNotNullIndex, CreateAddressesTransactionsCountAscCoinBalanceDescHashPartialIndex, CreateAddressesTransactionsCountDescPartialIndex, CreateAddressesVerifiedFetchedCoinBalanceDescHashIndex, CreateAddressesVerifiedHashIndex, CreateAddressesVerifiedTransactionsCountDescHashIndex, CreateArbitrumBatchL2BlocksUnconfirmedBlocksIndex, + CreateInternalTransactionsBlockNumberCreatedContractAddressIdPartialIndex, CreateInternalTransactionsBlockNumberDescTransactionIndexDescIndexDescIndex, CreateLogsAddressHashBlockNumberDescIndexDescIndex, CreateLogsAddressHashFirstTopicBlockNumberIndexIndex, @@ -91,6 +100,9 @@ defmodule Explorer.Chain.Cache.BackgroundMigrations do CreateLogsDepositsWithdrawalsIndex, CreateSmartContractsLanguageIndex, CreateTokensNamePartialFtsIndex, + CreateTokensOrdFiatHolderNameIndex, + CreateTokensOrdHolderNameIndex, + CreateTokensOrdMcapFiatHolderNameIndex, CreateTransactionsCreatedContractAddressHashWPendingIndex, DropInternalTransactionsCreatedContractAddressHashPartialIndex, DropInternalTransactionsFromAddressHashIndex, @@ -360,6 +372,27 @@ defmodule Explorer.Chain.Cache.BackgroundMigrations do ) end + defp handle_fallback(:heavy_indexes_create_idx_tokens_ord_mcap_fiat_holder_name_finished) do + set_and_return_migration_status( + CreateTokensOrdMcapFiatHolderNameIndex, + &set_heavy_indexes_create_idx_tokens_ord_mcap_fiat_holder_name_finished/1 + ) + end + + defp handle_fallback(:heavy_indexes_create_idx_tokens_ord_fiat_holder_name_finished) do + set_and_return_migration_status( + CreateTokensOrdFiatHolderNameIndex, + &set_heavy_indexes_create_idx_tokens_ord_fiat_holder_name_finished/1 + ) + end + + defp handle_fallback(:heavy_indexes_create_idx_tokens_ord_holder_name_finished) do + set_and_return_migration_status( + CreateTokensOrdHolderNameIndex, + &set_heavy_indexes_create_idx_tokens_ord_holder_name_finished/1 + ) + end + defp handle_fallback(:heavy_indexes_update_internal_transactions_primary_key_finished) do set_and_return_migration_status( UpdateInternalTransactionsPrimaryKey, @@ -374,6 +407,13 @@ defmodule Explorer.Chain.Cache.BackgroundMigrations do ) end + defp handle_fallback(:fill_internal_transactions_address_ids_finished) do + set_and_return_migration_status( + FillInternalTransactionsAddressIds, + &set_fill_internal_transactions_address_ids_finished/1 + ) + end + defp handle_fallback(:heavy_indexes_create_transactions_created_contract_address_hash_w_pending_index_finished) do set_and_return_migration_status( CreateTransactionsCreatedContractAddressHashWPendingIndex, @@ -388,6 +428,20 @@ defmodule Explorer.Chain.Cache.BackgroundMigrations do ) end + defp handle_fallback(:heavy_indexes_create_addresses_hash_contract_code_not_null_index_finished) do + set_and_return_migration_status( + CreateAddressesHashContractCodeNotNullIndex, + &set_heavy_indexes_create_addresses_hash_contract_code_not_null_index_finished/1 + ) + end + + defp handle_fallback(:heavy_indexes_create_address_ids_internal_transactions_indexes_finished) do + set_and_return_migration_status( + CreateInternalTransactionsBlockNumberCreatedContractAddressIdPartialIndex, + &set_heavy_indexes_create_address_ids_internal_transactions_indexes_finished/1 + ) + end + defp handle_fallback(:sanitize_verified_addresses_finished) do {:return, false} end diff --git a/apps/explorer/lib/explorer/chain/cache/optimism_finalization_period.ex b/apps/explorer/lib/explorer/chain/cache/optimism_finalization_period.ex index 4de14b059e8d..ea2766309302 100644 --- a/apps/explorer/lib/explorer/chain/cache/optimism_finalization_period.ex +++ b/apps/explorer/lib/explorer/chain/cache/optimism_finalization_period.ex @@ -22,7 +22,7 @@ defmodule Explorer.Chain.Cache.OptimismFinalizationPeriod do # call FINALIZATION_PERIOD_SECONDS() public getter of L2OutputOracle contract on L1 request = Contract.eth_call_request("0xf4daa291", output_oracle, 0, nil, nil) - case json_rpc(request, json_rpc_named_arguments(optimism_l1_rpc)) do + case json_rpc(request, Indexer.Helper.json_rpc_named_arguments(optimism_l1_rpc)) do {:ok, value} -> {:update, quantity_to_integer(value)} @@ -36,19 +36,4 @@ defmodule Explorer.Chain.Cache.OptimismFinalizationPeriod do end defp handle_fallback(_key), do: {:return, nil} - - defp json_rpc_named_arguments(optimism_l1_rpc) do - [ - transport: EthereumJSONRPC.HTTP, - transport_options: [ - http: EthereumJSONRPC.HTTP.Tesla, - urls: [optimism_l1_rpc], - http_options: [ - recv_timeout: :timer.minutes(10), - timeout: :timer.minutes(10), - pool: :ethereum_jsonrpc - ] - ] - ] - end end diff --git a/apps/explorer/lib/explorer/chain/cache/transaction_action_tokens_data.ex b/apps/explorer/lib/explorer/chain/cache/transaction_action_tokens_data.ex deleted file mode 100644 index 976bfbae4a35..000000000000 --- a/apps/explorer/lib/explorer/chain/cache/transaction_action_tokens_data.ex +++ /dev/null @@ -1,61 +0,0 @@ -defmodule Explorer.Chain.Cache.TransactionActionTokensData do - @moduledoc """ - Caches tokens data for Indexer.Transform.TransactionActions. - """ - use GenServer - - @cache_name :transaction_actions_tokens_data_cache - - @spec start_link(term()) :: GenServer.on_start() - def start_link(_) do - GenServer.start_link(__MODULE__, :ok, name: __MODULE__) - end - - @impl true - def init(_args) do - create_cache_table() - {:ok, %{}} - end - - def create_cache_table do - if :ets.whereis(@cache_name) == :undefined do - :ets.new(@cache_name, [ - :set, - :named_table, - :public, - read_concurrency: true, - write_concurrency: true - ]) - end - end - - def fetch_from_cache(address) do - with info when info != :undefined <- :ets.info(@cache_name), - [{_, value}] <- :ets.lookup(@cache_name, address) do - value - else - _ -> %{symbol: nil, decimals: nil} - end - end - - def put_to_cache(address, data) do - if not :ets.member(@cache_name, address) do - # we need to add a new item to the cache, but don't exceed the limit - cache_size = :ets.info(@cache_name, :size) - - how_many_to_remove = cache_size - get_max_token_cache_size() + 1 - - range = Range.new(1, how_many_to_remove, 1) - - for _step <- range do - :ets.delete(@cache_name, :ets.first(@cache_name)) - end - end - - :ets.insert(@cache_name, {address, data}) - end - - defp get_max_token_cache_size do - Application.get_env(:explorer, __MODULE__)[:max_cache_size] - end -end diff --git a/apps/explorer/lib/explorer/chain/cache/transaction_action_uniswap_pools.ex b/apps/explorer/lib/explorer/chain/cache/transaction_action_uniswap_pools.ex deleted file mode 100644 index 4e7719768f2f..000000000000 --- a/apps/explorer/lib/explorer/chain/cache/transaction_action_uniswap_pools.ex +++ /dev/null @@ -1,44 +0,0 @@ -defmodule Explorer.Chain.Cache.TransactionActionUniswapPools do - @moduledoc """ - Caches Uniswap pools for Indexer.Transform.TransactionActions. - """ - use GenServer - - @cache_name :transaction_actions_uniswap_pools_cache - - @spec start_link(term()) :: GenServer.on_start() - def start_link(_) do - GenServer.start_link(__MODULE__, :ok, name: __MODULE__) - end - - @impl true - def init(_args) do - create_cache_table() - {:ok, %{}} - end - - def create_cache_table do - if :ets.whereis(@cache_name) == :undefined do - :ets.new(@cache_name, [ - :set, - :named_table, - :public, - read_concurrency: true, - write_concurrency: true - ]) - end - end - - def fetch_from_cache(pool_address) do - with info when info != :undefined <- :ets.info(@cache_name), - [{_, value}] <- :ets.lookup(@cache_name, pool_address) do - value - else - _ -> nil - end - end - - def put_to_cache(address, value) do - :ets.insert(@cache_name, {address, value}) - end -end diff --git a/apps/explorer/lib/explorer/chain/celo/election_reward.ex b/apps/explorer/lib/explorer/chain/celo/election_reward.ex index 3ac72952d14b..a3f2df22c5b5 100644 --- a/apps/explorer/lib/explorer/chain/celo/election_reward.ex +++ b/apps/explorer/lib/explorer/chain/celo/election_reward.ex @@ -31,13 +31,23 @@ defmodule Explorer.Chain.Celo.ElectionReward do import Explorer.PagingOptions, only: [default_paging_options: 0] import Ecto.Query, only: [from: 2, where: 3] - alias Explorer.{Chain, SortingHelper} + alias Explorer.{Chain, Helper, SortingHelper} alias Explorer.Chain.{Address, Address.Reputation, Celo.Epoch, Hash, Token, Wei} alias Explorer.Chain.Cache.CeloCoreContracts @type type :: :voter | :validator | :group | :delegated_payment @types_enum ~w(voter validator group delegated_payment)a + # Legacy URL forms that differ from `to_string(atom)`. + # The URL path uses "delegated-payment" (hyphen), but the canonical atom + # is :delegated_payment (underscore). OpenApiSpex.Plug.CastAndValidate + # matches enum values via `to_string(atom) == binary`, so + # "delegated-payment" does not match :delegated_payment. Including the + # hyphenated string in the enum lets CastAndValidate accept both forms + # during a migration period, after which the hyphenated form can be + # removed. + @legacy_type_url_strings ["delegated-payment"] + @reward_type_url_string_to_atom %{ "voter" => :voter, "validator" => :validator, @@ -124,6 +134,21 @@ defmodule Explorer.Chain.Celo.ElectionReward do @spec types() :: [type] def types, do: @types_enum + @doc """ + Returns the list of election reward types extended with legacy hyphenated + URL strings (e.g. `"delegated-payment"`). + + Intended for use as the `enum` in OpenApiSpex schemas so that + `CastAndValidate` accepts both the canonical atom forms (`voter`, + `validator`, `group`, `delegated_payment`) and the legacy hyphenated URL + form (`delegated-payment`). + + Once the migration period ends and `"delegated-payment"` is no longer + accepted, replace usages with `types/0` and remove `@legacy_type_url_strings`. + """ + @spec type_enum_with_legacy() :: [type | String.t()] + def type_enum_with_legacy, do: @types_enum ++ @legacy_type_url_strings + @doc """ Converts a reward type url string to its corresponding atom. @@ -243,6 +268,7 @@ defmodule Explorer.Chain.Celo.ElectionReward do sorting_options = Keyword.get(options, :sorting, []) from_epoch = Keyword.get(options, :from_epoch) to_epoch = Keyword.get(options, :to_epoch) + timeout = Keyword.get(options, :timeout) address_hash |> address_hash_to_rewards_query() @@ -251,7 +277,7 @@ defmodule Explorer.Chain.Celo.ElectionReward do |> SortingHelper.apply_sorting(sorting_options, default_sorting) |> SortingHelper.page_with_sorting(paging_options, sorting_options, default_sorting) |> Chain.join_associations(necessity_by_association) - |> Chain.select_repo(options).all() + |> Chain.select_repo(options).all(Helper.maybe_timeout(timeout)) |> with_loaded_token_reputations() end diff --git a/apps/explorer/lib/explorer/chain/csv_export/address/logs.ex b/apps/explorer/lib/explorer/chain/csv_export/address/logs.ex index 1d667bde15b9..6e5737be7ecb 100644 --- a/apps/explorer/lib/explorer/chain/csv_export/address/logs.ex +++ b/apps/explorer/lib/explorer/chain/csv_export/address/logs.ex @@ -5,11 +5,11 @@ defmodule Explorer.Chain.CsvExport.Address.Logs do alias Explorer.Chain alias Explorer.Chain.{Address, Hash} - alias Explorer.Chain.CsvExport.Helper + alias Explorer.Chain.CsvExport.{AsyncHelper, Helper} @spec export(Hash.Address.t(), String.t(), String.t(), Keyword.t(), String.t() | nil, String.t() | nil) :: Enumerable.t() - def export(address_hash, from_period, to_period, _options, _filter_type \\ nil, filter_value \\ nil) do + def export(address_hash, from_period, to_period, _options, _filter_type, filter_value) do {from_block, to_block} = Helper.block_from_period(from_period, to_period) address_hash @@ -25,6 +25,7 @@ defmodule Explorer.Chain.CsvExport.Address.Logs do |> Keyword.put(:from_block, from_block) |> Keyword.put(:to_block, to_block) |> Keyword.put(:topic, filter_value) + |> Keyword.put(:timeout, AsyncHelper.db_timeout()) Chain.address_to_logs(address_hash, true, options) end diff --git a/apps/explorer/lib/explorer/chain/csv_export/address/token_transfers.ex b/apps/explorer/lib/explorer/chain/csv_export/address/token_transfers.ex index 2117431689f0..d4d1c25e8bb0 100644 --- a/apps/explorer/lib/explorer/chain/csv_export/address/token_transfers.ex +++ b/apps/explorer/lib/explorer/chain/csv_export/address/token_transfers.ex @@ -12,13 +12,13 @@ defmodule Explorer.Chain.CsvExport.Address.TokenTransfers do ] alias Explorer.Chain.{Address, DenormalizationHelper, Hash, TokenTransfer, Transaction} - alias Explorer.Chain.CsvExport.Helper + alias Explorer.Chain.CsvExport.{AsyncHelper, Helper} alias Explorer.Helper, as: ExplorerHelper alias Explorer.{PagingOptions, Repo} @spec export(Hash.Address.t(), String.t(), String.t(), Keyword.t(), String.t() | nil, String.t() | nil) :: Enumerable.t() - def export(address_hash, from_period, to_period, options, filter_type \\ nil, filter_value \\ nil) do + def export(address_hash, from_period, to_period, options, filter_type, filter_value) do {from_block, to_block} = Helper.block_from_period(from_period, to_period) paging_options = %PagingOptions{Helper.paging_options() | asc_order: true} @@ -45,6 +45,7 @@ defmodule Explorer.Chain.CsvExport.Address.TokenTransfers do |> Keyword.put(:to_block, to_block) |> Keyword.put(:filter_type, filter_type) |> Keyword.put(:filter_value, filter_value) + |> Keyword.put(:timeout, AsyncHelper.db_timeout()) address_hash_to_token_transfers_including_contract(address_hash, options) end @@ -111,6 +112,7 @@ defmodule Explorer.Chain.CsvExport.Address.TokenTransfers do @spec address_hash_to_token_transfers_including_contract(Hash.Address.t(), Keyword.t()) :: [TokenTransfer.t()] def address_hash_to_token_transfers_including_contract(address_hash, options \\ []) do paging_options = Keyword.get(options, :paging_options, Helper.default_paging_options()) + timeout = Keyword.get(options, :timeout) case paging_options do %PagingOptions{key: {0, 0}} -> @@ -132,7 +134,7 @@ defmodule Explorer.Chain.CsvExport.Address.TokenTransfers do |> handle_token_transfer_paging_options(paging_options) |> preload(^DenormalizationHelper.extend_transaction_preload([:transaction])) |> preload(:token) - |> Repo.replica().all() + |> Repo.replica().all(ExplorerHelper.maybe_timeout(timeout)) end end diff --git a/apps/explorer/lib/explorer/chain/csv_export/address/transactions.ex b/apps/explorer/lib/explorer/chain/csv_export/address/transactions.ex index 25d7507c0f6c..b8bec7e215d0 100644 --- a/apps/explorer/lib/explorer/chain/csv_export/address/transactions.ex +++ b/apps/explorer/lib/explorer/chain/csv_export/address/transactions.ex @@ -4,13 +4,13 @@ defmodule Explorer.Chain.CsvExport.Address.Transactions do """ alias Explorer.Chain.{Address, DenormalizationHelper, Hash, Transaction, Wei} - alias Explorer.Chain.CsvExport.Helper + alias Explorer.Chain.CsvExport.{AsyncHelper, Helper} alias Explorer.Market alias Explorer.Market.MarketHistory @spec export(Hash.Address.t(), String.t(), String.t(), Keyword.t(), String.t() | nil, String.t() | nil) :: Enumerable.t() - def export(address_hash, from_period, to_period, _options, filter_type \\ nil, filter_value \\ nil) do + def export(address_hash, from_period, to_period, _options, filter_type, filter_value) do {from_block, to_block} = Helper.block_from_period(from_period, to_period) exchange_rate = Market.get_coin_exchange_rate() @@ -33,6 +33,7 @@ defmodule Explorer.Chain.CsvExport.Address.Transactions do |> Keyword.put(:paging_options, paging_options) |> Keyword.put(:from_block, from_block) |> Keyword.put(:to_block, to_block) + |> Keyword.put(:timeout, AsyncHelper.db_timeout()) |> (&if(Helper.valid_filter?(filter_type, filter_value, "transactions"), do: &1 |> Keyword.put(:direction, String.to_atom(filter_value)), else: &1 diff --git a/apps/explorer/lib/explorer/chain/csv_export/advanced_filter.ex b/apps/explorer/lib/explorer/chain/csv_export/advanced_filter.ex new file mode 100644 index 000000000000..85ba83d536ba --- /dev/null +++ b/apps/explorer/lib/explorer/chain/csv_export/advanced_filter.ex @@ -0,0 +1,210 @@ +defmodule Explorer.Chain.CsvExport.AdvancedFilter do + @moduledoc """ + Module responsible for exporting advanced filters to CSV. + """ + + alias Explorer.Chain.Address + alias Explorer.Chain.{AdvancedFilter, MethodIdentifier, TokenTransfer} + alias Explorer.Chain.CsvExport.{AsyncHelper, Helper} + alias Explorer.Market + alias Explorer.Market.MarketHistory + + @spec export(Keyword.t()) :: + Enumerable.t() + def export(full_options) do + full_options + |> Keyword.put(:timeout, AsyncHelper.db_timeout()) + |> AdvancedFilter.list() + |> to_csv_format() + |> Helper.dump_to_stream() + end + + @doc """ + Converts a list or stream of advanced filter entities to an appropriate CSV-streaming format. + + Builds the CSV's header row and a stream of rows corresponding to each advanced filter, + resolving historical market data for each row's date. + + ## Parameters + + - advanced_filters: An enumerable of advanced filter structs with `timestamp` and related fields. + + ## Returns + + - A stream where the first row is the header, followed by CSV data rows for each advanced filter. + """ + @spec to_csv_format(Enumerable.t()) :: Enumerable.t() + def to_csv_format(advanced_filters) do + exchange_rate = Market.get_coin_exchange_rate() + + date_to_prices = + Enum.reduce(advanced_filters, %{}, fn af, acc -> + date = DateTime.to_date(af.timestamp) + + if Map.has_key?(acc, date) do + acc + else + market_history = MarketHistory.price_at_date(date) + + Map.put( + acc, + date, + {market_history && market_history.opening_price, market_history && market_history.closing_price} + ) + end + end) + + row_names = [ + "TxHash", + "Type", + "MethodId", + "UtcTimestamp", + "FromAddress", + "ToAddress", + "CreatedContractAddress", + "Value", + "TokenContractAddressHash", + "TokenDecimals", + "TokenSymbol", + "TokenValue", + "TokenID", + "BlockNumber", + "Fee", + "CurrentPrice", + "TxDateOpeningPrice", + "TxDateClosingPrice" + ] + + af_lists = + advanced_filters + |> Stream.map(fn advanced_filter -> + method_id = + case advanced_filter.input do + %{bytes: <>} -> + {:ok, method_id} = MethodIdentifier.cast(method_id) + to_string(method_id) + + _ -> + nil + end + + {opening_price, closing_price} = date_to_prices[DateTime.to_date(advanced_filter.timestamp)] + + prepare_advanced_filter_csv_row(advanced_filter, exchange_rate, opening_price, closing_price, method_id) + end) + + Stream.concat([row_names], af_lists) + end + + defp prepare_advanced_filter_csv_row( + %AdvancedFilter{created_from: :token_transfer} = advanced_filter, + _exchange_rate, + _opening_price, + _closing_price, + method_id + ) do + token_transfer_total = prepare_token_transfer_total(advanced_filter.token_transfer) + + [ + to_string(advanced_filter.hash), + advanced_filter.type, + method_id, + advanced_filter.timestamp, + Address.checksum(advanced_filter.from_address_hash), + Address.checksum(advanced_filter.to_address_hash), + Address.checksum(advanced_filter.created_contract_address_hash), + decimal_to_string(advanced_filter.value, :normal), + Address.checksum(advanced_filter.token_transfer.token.contract_address_hash), + decimal_to_string(token_transfer_total["decimals"], :normal), + advanced_filter.token_transfer.token.symbol, + case token_transfer_total["decimals"] do + nil -> + decimal_to_string(token_transfer_total["value"], :xsd) + + decimals -> + token_transfer_total["value"] && + token_transfer_total["value"] + |> Decimal.div(Integer.pow(10, Decimal.to_integer(decimals))) + |> decimal_to_string(:xsd) + end, + token_transfer_total["token_id"], + advanced_filter.block_number, + decimal_to_string(advanced_filter.fee, :normal), + nil, + nil, + nil + ] + end + + defp prepare_advanced_filter_csv_row( + advanced_filter, + exchange_rate, + opening_price, + closing_price, + method_id + ) do + [ + to_string(advanced_filter.hash), + advanced_filter.type, + method_id, + advanced_filter.timestamp, + Address.checksum(advanced_filter.from_address_hash), + Address.checksum(advanced_filter.to_address_hash), + Address.checksum(advanced_filter.created_contract_address_hash), + decimal_to_string(advanced_filter.value, :normal), + nil, + nil, + nil, + nil, + nil, + advanced_filter.block_number, + decimal_to_string(advanced_filter.fee, :normal), + decimal_to_string(exchange_rate.fiat_value, :xsd), + decimal_to_string(opening_price, :xsd), + decimal_to_string(closing_price, :xsd) + ] + end + + defp decimal_to_string(nil, _), do: nil + defp decimal_to_string(decimal, type), do: Decimal.to_string(decimal, type) + + # NOTE: duplicate of BlockScoutWeb.API.V2.TokenTransferView.prepare_token_transfer_total/1 but without the token_instance + defp prepare_token_transfer_total(token_transfer) do + case TokenTransfer.token_transfer_amount_for_api(token_transfer) do + {:ok, :erc721_instance} -> + token_transfer_map_erc721(token_transfer) + + {:ok, :erc1155_erc404_instance, value, decimals} -> + token_transfer_map_erc1155_single(token_transfer, value, decimals) + + {:ok, :erc1155_erc404_instance, values, token_ids, decimals} -> + token_transfer_map_erc1155_multi(values, token_ids, decimals) + + {:ok, value, decimals} -> + %{"value" => value, "decimals" => decimals} + + _ -> + nil + end + end + + defp token_transfer_map_erc721(token_transfer) do + %{"token_id" => token_transfer.token_ids && List.first(token_transfer.token_ids)} + end + + defp token_transfer_map_erc1155_single(token_transfer, value, decimals) do + %{ + "token_id" => token_transfer.token_ids && List.first(token_transfer.token_ids), + "value" => value, + "decimals" => decimals + } + end + + defp token_transfer_map_erc1155_multi(values, token_ids, decimals) do + %{ + "token_id" => token_ids && List.first(token_ids), + "value" => values && List.first(values), + "decimals" => decimals + } + end +end diff --git a/apps/explorer/lib/explorer/chain/csv_export/async_helper.ex b/apps/explorer/lib/explorer/chain/csv_export/async_helper.ex new file mode 100644 index 000000000000..c929fe9abc1f --- /dev/null +++ b/apps/explorer/lib/explorer/chain/csv_export/async_helper.ex @@ -0,0 +1,247 @@ +defmodule Explorer.Chain.CsvExport.AsyncHelper do + @moduledoc """ + Async CSV export helper functions. + """ + + alias Explorer.Chain.CsvExport.Request, as: CsvExportRequest + alias Explorer.HttpClient + alias Tesla.Multipart + + require Logger + + @doc """ + Uploads a file to Gokapi. + + Streams the file in chunks, uploads each chunk via the Gokapi chunk API, then completes the upload. The local file is removed after upload (success or failure). + + ## Parameters + + - `file_path`: Path to the local file to upload. + - `filename`: Filename to use for the uploaded file on Gokapi. + - `uuid`: Upload session identifier for the chunked upload. + + ## Returns + + - `{:ok, file_id, expires_at}` on success, where `file_id` is the Gokapi file ID and `expires_at` is the expiration timestamp of the file. + - `{:error, reason}` on failure (e.g. chunk upload or complete request error). + """ + # sobelow_skip ["Traversal.FileModule"] + @spec upload_file(String.t(), String.t(), String.t()) :: {:ok, String.t(), DateTime.t()} | {:error, any()} + def upload_file(file_path, filename, uuid) do + file_size = File.stat!(file_path).size + chunk_size = chunk_size() + + result = + file_path + |> File.stream!(chunk_size) + |> Stream.with_index() + |> Enum.reduce_while(:ok, fn {chunk, index}, _acc -> + case upload_chunk(chunk, uuid, file_size, index * chunk_size) do + :ok -> {:cont, :ok} + {:error, _} = error -> {:halt, error} + end + end) + + case result do + :ok -> complete_upload(uuid, filename, file_size) + error -> error + end + after + File.rm(file_path) + end + + @doc """ + Actualizes a CSV export request. If the file exists on the gokapi server, returns the request. If the file does not exist, deletes the request and returns nil. If there is an error, logs the error and returns the request. + + ## Parameters + + - `request`: The CSV export request to actualize. + + ## Returns + + - The actualized CSV export request or nil if the request was deleted. + """ + @spec actualize_csv_export_request(CsvExportRequest.t() | nil) :: CsvExportRequest.t() | nil + def actualize_csv_export_request(%CsvExportRequest{file_id: nil} = request), do: request + + def actualize_csv_export_request(%CsvExportRequest{} = request) do + case file_exists?(request.file_id) do + {:ok, true} -> + request + + {:ok, false} -> + CsvExportRequest.delete(request.id) + nil + + error -> + Logger.error("Failed to check if file exists: #{inspect(error)}") + request + end + end + + def actualize_csv_export_request(nil) do + nil + end + + @spec upload_chunk(binary(), String.t(), integer(), integer()) :: :ok | {:error, any()} + defp upload_chunk(chunk, uuid, filesize, offset) do + multipart = + Multipart.new() + |> Multipart.add_file_content(chunk, "chunk", name: "file") + |> Multipart.add_field("uuid", uuid) + |> Multipart.add_field("filesize", to_string(filesize)) + |> Multipart.add_field("offset", to_string(offset)) + + body = multipart |> Multipart.body() |> Enum.to_list() |> IO.iodata_to_binary() + + case HttpClient.post( + gokapi_chunk_upload_url(), + body, + [api_key_header(), {"Content-Type", "multipart/form-data; boundary=#{multipart.boundary}"}], + timeout_options() + ) do + {:ok, %{status_code: 200}} -> :ok + {:ok, resp} -> {:error, {:unexpected_status, resp.status_code}} + {:error, reason} -> {:error, reason} + end + end + + @spec complete_upload(String.t(), String.t(), integer(), String.t()) :: + {:ok, String.t(), DateTime.t()} | {:error, any()} + defp complete_upload(uuid, filename, filesize, content_type \\ "application/csv") do + result = + HttpClient.post( + gokapi_chunk_complete_url(), + nil, + [ + api_key_header(), + {"uuid", uuid}, + {"filename", filename}, + {"filesize", to_string(filesize)}, + {"contenttype", content_type}, + {"allowedDownloads", to_string(gokapi_upload_allowed_downloads())}, + {"expiryDays", to_string(gokapi_upload_expiry_days())}, + {"nonblocking", "false"} + ], + timeout_options() + ) + + with {:ok, %{status_code: 200, body: body}} <- result, + {:ok, %{"FileInfo" => %{"Id" => file_id, "ExpireAt" => expires_at_unix_timestamp}}} <- Jason.decode(body), + {:ok, expires_at} <- DateTime.from_unix(expires_at_unix_timestamp) do + {:ok, file_id, expires_at} + else + error -> {:error, error} + end + end + + @spec file_exists?(String.t()) :: {:ok, boolean()} | {:error, any()} + defp file_exists?(file_id) do + case HttpClient.get(gokapi_file_metadata_url(file_id), [api_key_header()], timeout_options()) do + {:ok, %{status_code: 200}} -> + {:ok, true} + + {:ok, %{status_code: 404}} -> + {:ok, false} + + error -> + {:error, error} + end + end + + # sobelow_skip ["Traversal.FileModule"] + @spec stream_to_temp_file(Enumerable.t(), String.t()) :: String.t() + def stream_to_temp_file(stream, uuid) do + tmp_dir = tmp_dir() + file_path = Path.join(tmp_dir, "csv_export_#{uuid}.csv") + File.mkdir_p!(tmp_dir) + + File.open!(file_path, [:write, :binary], fn file -> + Enum.each(stream, &write_chunk(file, &1)) + end) + + file_path + end + + defp write_chunk(file, chunk) do + case :file.write(file, chunk) do + :ok -> :ok + {:error, reason} -> raise "Failed to write CSV chunk: #{inspect(reason)}" + end + end + + @spec max_pending_tasks_per_ip() :: integer() + def max_pending_tasks_per_ip do + csv_export_config()[:max_pending_tasks_per_ip] + end + + @spec db_timeout() :: non_neg_integer() + def db_timeout do + csv_export_config()[:db_timeout] + end + + @spec csv_export_config() :: list() + defp csv_export_config do + Application.get_env(:explorer, Explorer.Chain.CsvExport) + end + + @spec chunk_size() :: integer() + defp chunk_size do + csv_export_config()[:chunk_size] + end + + @spec tmp_dir() :: String.t() + defp tmp_dir do + csv_export_config()[:tmp_dir] + end + + @spec gokapi_url() :: String.t() + defp gokapi_url do + "#{csv_export_config()[:gokapi_url]}/api" + end + + @spec gokapi_api_key() :: String.t() + defp gokapi_api_key do + csv_export_config()[:gokapi_api_key] + end + + @spec gokapi_upload_expiry_days() :: integer() + defp gokapi_upload_expiry_days do + csv_export_config()[:gokapi_upload_expiry_days] + end + + @spec gokapi_upload_allowed_downloads() :: integer() + defp gokapi_upload_allowed_downloads do + csv_export_config()[:gokapi_upload_allowed_downloads] + end + + @spec gokapi_chunk_upload_url() :: String.t() + defp gokapi_chunk_upload_url do + "#{gokapi_chunk_url()}/add" + end + + @spec gokapi_chunk_complete_url() :: String.t() + defp gokapi_chunk_complete_url do + "#{gokapi_chunk_url()}/complete" + end + + @spec gokapi_chunk_url() :: String.t() + defp gokapi_chunk_url do + "#{gokapi_url()}/chunk" + end + + @spec gokapi_file_metadata_url(String.t()) :: String.t() + defp gokapi_file_metadata_url(file_id) do + "#{gokapi_url()}/files/list/#{file_id}" + end + + @spec api_key_header() :: {String.t(), String.t()} + defp api_key_header do + {"apikey", gokapi_api_key()} + end + + defp timeout_options do + timeout = csv_export_config()[:gokapi_timeout] + [timeout: timeout, recv_timeout: timeout] + end +end diff --git a/apps/explorer/lib/explorer/chain/csv_export/celo/election_rewards.ex b/apps/explorer/lib/explorer/chain/csv_export/celo/election_rewards.ex index 4acf9f44bfcc..1ba4843c1009 100644 --- a/apps/explorer/lib/explorer/chain/csv_export/celo/election_rewards.ex +++ b/apps/explorer/lib/explorer/chain/csv_export/celo/election_rewards.ex @@ -3,7 +3,7 @@ defmodule Explorer.Chain.CsvExport.Address.Celo.ElectionRewards do Exports Celo election rewards to a csv file. """ alias Explorer.Chain.Celo.{ElectionReward, Epoch} - alias Explorer.Chain.CsvExport.Helper + alias Explorer.Chain.CsvExport.{AsyncHelper, Helper} alias Explorer.Chain.{Hash, Wei} @spec export(Hash.Address.t(), String.t() | nil, String.t() | nil, Keyword.t(), any(), any()) :: Enumerable.t() @@ -18,7 +18,8 @@ defmodule Explorer.Chain.CsvExport.Address.Celo.ElectionRewards do [epoch: [:end_processing_block]] => :optional }, paging_options: Helper.paging_options(), - api?: true + api?: true, + timeout: AsyncHelper.db_timeout() ] epoch_range diff --git a/apps/explorer/lib/explorer/chain/csv_export/helper.ex b/apps/explorer/lib/explorer/chain/csv_export/helper.ex index 66f59e148d90..4c8c5d60e946 100644 --- a/apps/explorer/lib/explorer/chain/csv_export/helper.ex +++ b/apps/explorer/lib/explorer/chain/csv_export/helper.ex @@ -171,4 +171,9 @@ defmodule Explorer.Chain.CsvExport.Helper do true end end + + @spec async_enabled?() :: boolean() + def async_enabled? do + Application.get_env(:explorer, Explorer.Chain.CsvExport)[:async?] + end end diff --git a/apps/explorer/lib/explorer/chain/csv_export/request.ex b/apps/explorer/lib/explorer/chain/csv_export/request.ex new file mode 100644 index 000000000000..d85b089f5c54 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/csv_export/request.ex @@ -0,0 +1,163 @@ +defmodule Explorer.Chain.CsvExport.Request do + @moduledoc """ + Represents an asynchronous CSV export request. + + When asynchronous CSV export is enabled (`CSV_EXPORT_ASYNC_ENABLED`), the API + enqueues work as an Oban job (`Explorer.Chain.CsvExport.Worker`) instead of + streaming the CSV in the request. This schema tracks the request lifecycle; + the primary key UUID is the `request_id` clients use to poll for status. + """ + + use Explorer.Schema + + alias Explorer.{Chain, Repo} + alias Explorer.Chain.CsvExport.{AsyncHelper, Worker} + + @primary_key false + typed_schema "csv_export_requests" do + field(:id, Ecto.UUID, primary_key: true, autogenerate: true) + field(:remote_ip_hash, :binary, null: false) + field(:file_id, :string) + field(:status, Ecto.Enum, values: [:pending, :completed, :failed], default: :pending) + field(:expires_at, :utc_datetime, null: true) + + timestamps() + end + + @required_attrs ~w(remote_ip_hash)a + @optional_attrs ~w(file_id status expires_at)a + + @spec changeset(t(), map()) :: Ecto.Changeset.t() + def changeset(%__MODULE__{} = request, attrs \\ %{}) do + request + |> cast(attrs, @required_attrs ++ @optional_attrs) + |> validate_required(@required_attrs) + end + + @doc """ + Creates a new async CSV export request for the given remote IP address. + + The IP is hashed with SHA-256 before storage. Returns `{:ok, request}` on success, + or `{:error, :too_many_pending_requests}` if the IP already has `max_pending_tasks` + requests with `file_id` still `nil`. + """ + @spec create(String.t(), map()) :: {:ok, t()} | {:error, any()} + def create(remote_ip, args) do + remote_ip_hash = hash_ip(remote_ip) + max_pending = AsyncHelper.max_pending_tasks_per_ip() + + Repo.transact(fn -> + pending_count = + __MODULE__ + |> where([r], r.remote_ip_hash == ^remote_ip_hash and r.status == :pending) + |> select([r], count(r.id)) + |> Repo.one() + + with {:too_many_pending_requests, false} <- {:too_many_pending_requests, pending_count >= max_pending}, + {:ok, request} <- + %__MODULE__{remote_ip_hash: remote_ip_hash} + |> changeset() + |> Repo.insert(), + {:ok, _job} <- + args + |> Map.put(:request_id, request.id) + |> Worker.new() + |> Oban.insert() do + {:ok, request} + else + {:too_many_pending_requests, true} -> Repo.rollback(:too_many_pending_requests) + {:error, error} -> Repo.rollback(error) + end + end) + end + + @doc """ + Updates the file_id and expires_at for a given request_id. + + ## Parameters + + - `request_id`: The ID of the request to update. + - `file_id`: The ID of the file to update. + + ## Returns + - The number of rows updated and the result of the update. + + ## Examples + + ```elixir + iex> update_file_id_and_expires_at("123e4567-e89b-12d3-a456-426614174000", "123e4567-e89b-12d3-a456-426614174000", DateTime.from_unix(1716393600)) + {1, nil} + ``` + """ + @spec update_file_id_and_expires_at(Ecto.UUID.t(), String.t(), DateTime.t()) :: {non_neg_integer(), nil} + def update_file_id_and_expires_at(request_id, file_id, expires_at) do + __MODULE__ + |> where([r], r.id == ^request_id) + |> Repo.update_all( + set: [file_id: file_id, expires_at: expires_at, status: :completed, updated_at: DateTime.utc_now()] + ) + end + + @doc """ + Marks a request as failed. + + Called when the Oban job exhausts all attempts without successfully completing. + """ + @spec mark_failed(Ecto.UUID.t()) :: {non_neg_integer(), nil} + def mark_failed(request_id) do + __MODULE__ + |> where([r], r.id == ^request_id) + |> Repo.update_all(set: [status: :failed, updated_at: DateTime.utc_now()]) + end + + @doc """ + Gets a request by its UUID. + + ## Parameters + + - `uuid`: The UUID of the request to get. + - `options`: The options to pass to the repository. + + ## Returns + - The request or nil if no request is found. + + ## Examples + + ```elixir + iex> get_by_uuid("123e4567-e89b-12d3-a456-426614174000") + %Explorer.Chain.CsvExport.Request{id: "123e4567-e89b-12d3-a456-426614174000"} + ``` + """ + @spec get_by_uuid(Ecto.UUID.t(), [Chain.api?()]) :: __MODULE__.t() | nil + def get_by_uuid(uuid, options \\ []) do + Chain.select_repo(options).get(__MODULE__, uuid) + end + + @doc """ + Deletes a request by its ID. + + ## Parameters + + - `request_id`: The ID of the request to delete. + + ## Returns + - The number of rows deleted and the result of the delete. + + ## Examples + + ```elixir + iex> delete("123e4567-e89b-12d3-a456-426614174000") + {1, nil} + ``` + """ + @spec delete(Ecto.UUID.t()) :: {non_neg_integer(), nil} + def delete(request_id) do + __MODULE__ + |> where([r], r.id == ^request_id) + |> Repo.delete_all() + end + + defp hash_ip(ip) do + :crypto.hash(:sha256, ip) + end +end diff --git a/apps/explorer/lib/explorer/chain/csv_export/requests_sanitizer.ex b/apps/explorer/lib/explorer/chain/csv_export/requests_sanitizer.ex new file mode 100644 index 000000000000..09592383ce2d --- /dev/null +++ b/apps/explorer/lib/explorer/chain/csv_export/requests_sanitizer.ex @@ -0,0 +1,33 @@ +defmodule Explorer.Chain.CsvExport.RequestsSanitizer do + @moduledoc """ + Oban cron worker that periodically deletes expired CSV export requests. + + Removes completed requests (file_id IS NOT NULL) whose updated_at + is older than the configured Gokapi upload expiry period. + """ + use Oban.Worker, queue: :csv_export_sanitize, max_attempts: 3 + + import Ecto.Query + + alias Explorer.Chain.CsvExport.Request + alias Explorer.Repo + + require Logger + + @impl Oban.Worker + def perform(_job) do + expiry_days = csv_export_config()[:gokapi_upload_expiry_days] + + {count, _} = + Request + |> where([r], not is_nil(r.file_id)) + |> where([r], r.updated_at < ago(^expiry_days, "day")) + |> Repo.delete_all() + + Logger.info("Deleted #{count} expired CSV export requests") + + :ok + end + + defp csv_export_config, do: Application.get_env(:explorer, Explorer.Chain.CsvExport) +end diff --git a/apps/explorer/lib/explorer/chain/csv_export/token/holders.ex b/apps/explorer/lib/explorer/chain/csv_export/token/holders.ex new file mode 100644 index 000000000000..3aac1fb5bf9a --- /dev/null +++ b/apps/explorer/lib/explorer/chain/csv_export/token/holders.ex @@ -0,0 +1,59 @@ +defmodule Explorer.Chain.CsvExport.Token.Holders do + @moduledoc """ + Exports token holders to a csv file. + """ + + alias Explorer.Chain + alias Explorer.Chain.{Address, CurrencyHelper, Hash, Token} + alias Explorer.Chain.Address.CurrentTokenBalance + alias Explorer.Chain.CsvExport.AsyncHelper + alias Explorer.Chain.CsvExport.Helper, as: CsvHelper + + @spec export(Hash.Address.t(), any(), any(), any(), any(), any()) :: + Enumerable.t() + def export( + token_address_hash, + _from_period, + _to_period, + _options, + _filter_type, + _filter_value + ) do + {:ok, token} = Chain.token_from_address_hash(token_address_hash, api?: true) + + token_address_hash + |> fetch_token_holders() + |> to_csv_format(token) + |> CsvHelper.dump_to_stream() + end + + defp fetch_token_holders(address_hash) do + Chain.fetch_token_holders_from_token_hash_for_csv(address_hash, + paging_options: CsvHelper.paging_options(), + api?: true, + timeout: AsyncHelper.db_timeout() + ) + end + + @doc """ + Converts CurrentTokenBalances to CSV format. Used in `BlockScoutWeb.API.V2.CsvExportController.export_token_holders/2` + """ + @spec to_csv_format([CurrentTokenBalance.t()], Token.t()) :: Enumerable.t() + def to_csv_format(holders, token) do + row_names = [ + "HolderAddress", + "Balance" + ] + + holders_list = + holders + |> Stream.map(fn ctb -> + [ + Address.checksum(ctb.address_hash), + ctb.value |> CurrencyHelper.divide_decimals(token.decimals) |> Decimal.to_string(:xsd) + ] + end) + + Stream.concat([row_names], holders_list) + end +end diff --git a/apps/explorer/lib/explorer/chain/csv_export/worker.ex b/apps/explorer/lib/explorer/chain/csv_export/worker.ex new file mode 100644 index 000000000000..0c92631024e1 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/csv_export/worker.ex @@ -0,0 +1,109 @@ +defmodule Explorer.Chain.CsvExport.Worker do + @moduledoc """ + Oban worker for asynchronous CSV export jobs. + + Processes export requests in the background when the requested period exceeds + the async threshold, streaming results to a temp file and uploading to storage. + """ + use Oban.Worker, queue: :csv_export, max_attempts: 1 + + alias Explorer.Chain + alias Explorer.Chain.CsvExport.{AdvancedFilter, AsyncHelper, Request} + + require Logger + + @impl Oban.Worker + def perform( + %Job{ + args: %{ + "request_id" => request_id, + "advanced_filters_params" => advanced_filters_params + } + } = job + ) do + run_with_failure_handling(job, request_id, fn -> + filename = "advanced_filters_#{request_id}.csv" + + advanced_filters_params + |> Base.decode64!() + |> :erlang.binary_to_term([:safe]) + |> AdvancedFilter.export() + |> AsyncHelper.stream_to_temp_file(request_id) + |> AsyncHelper.upload_file(filename, request_id) + |> process_upload_result(request_id) + end) + end + + @impl Oban.Worker + def perform( + %Job{ + args: + %{ + "request_id" => request_id, + "address_hash" => address_hash_string, + "from_period" => from_period, + "to_period" => to_period, + "show_scam_tokens?" => show_scam_tokens?, + "module" => module + } = args + } = job + ) do + run_with_failure_handling(job, request_id, fn -> + csv_export_module = String.to_existing_atom(module) + filename = "#{address_hash_string}_#{from_period}_#{to_period}.csv" + + {:ok, address_hash} = Chain.string_to_address_hash(address_hash_string) + + address_hash + |> csv_export_module.export( + from_period, + to_period, + [show_scam_tokens?: show_scam_tokens?], + args["filter_type"], + args["filter_value"] + ) + |> AsyncHelper.stream_to_temp_file(request_id) + |> AsyncHelper.upload_file(filename, request_id) + |> process_upload_result(request_id) + end) + end + + defp run_with_failure_handling(%Job{attempt: attempt, max_attempts: max_attempts}, request_id, fun) do + case fun.() do + :ok -> + :ok + + {:error, _reason} = error -> + if attempt >= max_attempts do + Request.mark_failed(request_id) + end + + error + end + rescue + exception -> + if attempt >= max_attempts do + Request.mark_failed(request_id) + end + + reraise exception, __STACKTRACE__ + end + + defp process_upload_result(result, request_id) do + case result do + {:ok, file_id, expires_at} -> + case Request.update_file_id_and_expires_at(request_id, file_id, expires_at) do + {0, _} -> + Logger.warning( + "CSV export request #{request_id} was deleted before file_id could be set. Uploaded file #{file_id} may be orphaned." + ) + + {_count, _} -> + :ok + end + + {:error, reason} -> + {:error, reason} + end + end +end diff --git a/apps/explorer/lib/explorer/chain/denormalization_helper.ex b/apps/explorer/lib/explorer/chain/denormalization_helper.ex index 2af7759d1c3b..5ccd37ab7680 100644 --- a/apps/explorer/lib/explorer/chain/denormalization_helper.ex +++ b/apps/explorer/lib/explorer/chain/denormalization_helper.ex @@ -14,20 +14,6 @@ defmodule Explorer.Chain.DenormalizationHelper do end end - @spec extend_transaction_block_necessity(keyword(), :optional | :required) :: keyword() - def extend_transaction_block_necessity(opts, necessity \\ :optional) do - if transactions_denormalization_finished?() do - opts - else - Keyword.update( - opts, - :necessity_by_association, - %{[transaction: :block] => necessity}, - &(&1 |> Map.delete(:transaction) |> Map.put([transaction: :block], necessity)) - ) - end - end - @spec extend_transaction_preload(list()) :: list() def extend_transaction_preload(preloads) do if transactions_denormalization_finished?() do diff --git a/apps/explorer/lib/explorer/chain/health/monitor.ex b/apps/explorer/lib/explorer/chain/health/monitor.ex index 5cdb25080989..85afbd67743c 100644 --- a/apps/explorer/lib/explorer/chain/health/monitor.ex +++ b/apps/explorer/lib/explorer/chain/health/monitor.ex @@ -11,7 +11,6 @@ defmodule Explorer.Chain.Health.Monitor do alias Explorer.Chain.Cache.Counters.LastFetchedCounter alias Explorer.Chain.Health.Helper, as: HealthHelper alias Explorer.Chain.Optimism.Reader, as: OptimismReader - alias Explorer.Chain.PolygonZkevm.Reader, as: PolygonZkevmReader alias Explorer.Chain.Scroll.Reader, as: ScrollReader alias Explorer.Chain.ZkSync.Reader, as: ZkSyncReader alias Explorer.Repo @@ -80,9 +79,6 @@ defmodule Explorer.Chain.Health.Monitor do :optimism -> get_latest_batch_info_from_module(OptimismReader) - :polygon_zkevm -> - get_latest_batch_info_from_module(PolygonZkevmReader) - :scroll -> get_latest_batch_info_from_module(ScrollReader) diff --git a/apps/explorer/lib/explorer/chain/import.ex b/apps/explorer/lib/explorer/chain/import.ex index 20027df58fad..2593989eb0a5 100644 --- a/apps/explorer/lib/explorer/chain/import.ex +++ b/apps/explorer/lib/explorer/chain/import.ex @@ -215,7 +215,15 @@ defmodule Explorer.Chain.Import do changeset_function_name = Map.get(options, :with, :changeset) struct = ecto_schema_module.__struct__() + prepare_data_function = + if Map.has_key?(Enum.into(runner.__info__(:functions), %{}), :prepare_data) do + &runner.prepare_data(&1) + else + & &1 + end + params + |> prepare_data_function.() |> Stream.map(&apply(ecto_schema_module, changeset_function_name, [struct, &1])) |> Enum.reduce({:ok, []}, fn changeset = %Changeset{valid?: false}, {:ok, _} -> diff --git a/apps/explorer/lib/explorer/chain/import/runner.ex b/apps/explorer/lib/explorer/chain/import/runner.ex index d8a8ff20f222..07b4d080eb4d 100644 --- a/apps/explorer/lib/explorer/chain/import/runner.ex +++ b/apps/explorer/lib/explorer/chain/import/runner.ex @@ -58,5 +58,10 @@ defmodule Explorer.Chain.Import.Runner do """ @callback runner_specific_options() :: [atom()] - @optional_callbacks runner_specific_options: 0 + @doc """ + The optional function to prepare data for passing it to the changeset. + """ + @callback prepare_data(changes_list) :: changes_list + + @optional_callbacks runner_specific_options: 0, prepare_data: 1 end diff --git a/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex b/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex index 9d92a32257f5..cb2143afe8ba 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex @@ -12,7 +12,6 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do alias Explorer.Chain.{ Block, - Hash, Import, InternalTransaction, PendingOperationsHelper, @@ -25,7 +24,11 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do alias Explorer.Migrator.DeleteZeroValueInternalTransactions alias Explorer.Prometheus.Instrumenter alias Explorer.Repo, as: ExplorerRepo +<<<<<<< HEAD alias Explorer.Utility.MissingRangesManipulator +======= + alias Explorer.Utility.{AddressIdToAddressHash, MissingBlockRange} +>>>>>>> v11.0.0 import Ecto.Query @@ -45,8 +48,8 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do @impl Runner def imported_table_row do %{ - value_type: "[%{index: non_neg_integer(), transaction_hash: Explorer.Chain.Hash.t()}]", - value_description: "List of maps of the `t:Explorer.Chain.InternalTransaction.t/0` `index` and `transaction_hash`" + value_type: "[InternalTransaction.t()]", + value_description: "List of maps of the `t:Explorer.Chain.InternalTransaction.t/0`" } end @@ -146,27 +149,14 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do :maybe_shrink_internal_transactions_params ) end) - |> Multi.run(:maybe_reject_zero_value, fn _, - %{ - maybe_shrink_internal_transactions_params: - maybe_shrink_internal_transactions_params - } -> - Instrumenter.block_import_stage_runner( - fn -> - maybe_reject_zero_value(maybe_shrink_internal_transactions_params) - end, - :block_pending, - :internal_transactions, - :maybe_reject_zero_value - ) - end) |> Multi.run(:internal_transactions, fn repo, %{ - maybe_reject_zero_value: internal_transactions_params + maybe_shrink_internal_transactions_params: + maybe_shrink_internal_transactions_params } -> Instrumenter.block_import_stage_runner( fn -> - insert(repo, internal_transactions_params, insert_options) + insert(repo, maybe_shrink_internal_transactions_params, insert_options) end, :block_pending, :internal_transactions, @@ -207,6 +197,13 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do end) end + @impl Runner + def prepare_data(changes_list) do + changes_list + |> maybe_reject_zero_value() + |> adjust_insert_params() + end + def run_insert_only(changes_list, %{timestamps: timestamps} = options) when is_map(options) do insert_options = options @@ -221,11 +218,11 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do # Enforce ShareLocks tables order (see docs: sharelocks.md) with {:ok, data} <- Multi.new() - |> Multi.run(:maybe_reject_zero_value, fn _, _ -> - maybe_reject_zero_value(internal_transactions_params) + |> Multi.run(:prepare_data, fn _, _ -> + {:ok, prepare_data(internal_transactions_params)} end) - |> Multi.run(:internal_transactions, fn repo, %{maybe_reject_zero_value: maybe_reject_zero_value} -> - insert(repo, maybe_reject_zero_value, insert_options) + |> Multi.run(:internal_transactions, fn repo, %{prepare_data: prepared_data} -> + insert(repo, prepared_data, insert_options) end) |> ExplorerRepo.transaction() do Publisher.broadcast(data, :on_demand) @@ -240,19 +237,32 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do required(:timeout) => timeout, required(:timestamps) => Import.timestamps() }) :: - {:ok, [%{index: non_neg_integer, transaction_hash: Hash.t()}]} + {:ok, [InternalTransaction.t()]} | {:error, [Changeset.t()]} defp insert(repo, valid_internal_transactions, %{timeout: timeout, timestamps: timestamps} = options) when is_list(valid_internal_transactions) do on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) +<<<<<<< HEAD ordered_changes_list = Enum.sort_by(valid_internal_transactions, &{&1.transaction_hash, &1.index}) +======= + ordered_changes_list = + valid_internal_transactions + |> Enum.map(fn internal_transaction -> + Map.put(internal_transaction, :trace_address, nil) + end) + |> Enum.sort_by(&{&1.transaction_index, &1.index}) +>>>>>>> v11.0.0 {:ok, internal_transactions} = Import.insert_changes_list( repo, ordered_changes_list, +<<<<<<< HEAD conflict_target: [:block_hash, :block_index], +======= + conflict_target: [:block_number, :transaction_index, :index], +>>>>>>> v11.0.0 for: InternalTransaction, on_conflict: on_conflict, returning: true, @@ -268,6 +278,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do internal_transaction in InternalTransaction, update: [ set: [ +<<<<<<< HEAD block_number: fragment("EXCLUDED.block_number"), call_type: fragment("EXCLUDED.call_type"), created_contract_address_hash: fragment("EXCLUDED.created_contract_address_hash"), @@ -284,17 +295,38 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do trace_address: fragment("EXCLUDED.trace_address"), transaction_hash: fragment("EXCLUDED.transaction_hash"), transaction_index: fragment("EXCLUDED.transaction_index"), +======= + call_type: fragment("EXCLUDED.call_type"), + call_type_enum: fragment("EXCLUDED.call_type_enum"), + created_contract_address_id: fragment("EXCLUDED.created_contract_address_id"), + created_contract_code: fragment("EXCLUDED.created_contract_code"), + error_id: fragment("EXCLUDED.error_id"), + from_address_id: fragment("EXCLUDED.from_address_id"), + gas: fragment("EXCLUDED.gas"), + gas_used: fragment("EXCLUDED.gas_used"), + init: fragment("EXCLUDED.init"), + input: fragment("EXCLUDED.input"), + output: fragment("EXCLUDED.output"), + to_address_id: fragment("EXCLUDED.to_address_id"), +>>>>>>> v11.0.0 type: fragment("EXCLUDED.type"), value: fragment("EXCLUDED.value"), inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", internal_transaction.inserted_at), updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", internal_transaction.updated_at) +<<<<<<< HEAD # Don't update `block_hash` as it is used for the conflict target # Don't update `block_index` as it is used for the conflict target +======= + # Don't update `block_number` as it is used for the conflict target + # Don't update `transaction_index` as it is used for the conflict target + # Don't update `index` as it is used for the conflict target +>>>>>>> v11.0.0 ] ], # `IS DISTINCT FROM` is used because it allows `NULL` to be equal to itself where: fragment( +<<<<<<< HEAD "(EXCLUDED.transaction_hash, EXCLUDED.index, EXCLUDED.call_type, EXCLUDED.created_contract_address_hash, EXCLUDED.created_contract_code, EXCLUDED.error, EXCLUDED.from_address_hash, EXCLUDED.gas, EXCLUDED.gas_used, EXCLUDED.init, EXCLUDED.input, EXCLUDED.output, EXCLUDED.to_address_hash, EXCLUDED.trace_address, EXCLUDED.transaction_index, EXCLUDED.type, EXCLUDED.value) IS DISTINCT FROM (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", internal_transaction.transaction_hash, internal_transaction.index, @@ -303,14 +335,27 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do internal_transaction.created_contract_code, internal_transaction.error, internal_transaction.from_address_hash, +======= + "(EXCLUDED.call_type, EXCLUDED.call_type_enum, EXCLUDED.created_contract_address_id, EXCLUDED.created_contract_code, EXCLUDED.error_id, EXCLUDED.from_address_id, EXCLUDED.gas, EXCLUDED.gas_used, EXCLUDED.init, EXCLUDED.input, EXCLUDED.output, EXCLUDED.to_address_id, EXCLUDED.type, EXCLUDED.value) IS DISTINCT FROM (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + internal_transaction.call_type, + internal_transaction.call_type_enum, + internal_transaction.created_contract_address_id, + internal_transaction.created_contract_code, + internal_transaction.error_id, + internal_transaction.from_address_id, +>>>>>>> v11.0.0 internal_transaction.gas, internal_transaction.gas_used, internal_transaction.init, internal_transaction.input, internal_transaction.output, +<<<<<<< HEAD internal_transaction.to_address_hash, internal_transaction.trace_address, internal_transaction.transaction_index, +======= + internal_transaction.to_address_id, +>>>>>>> v11.0.0 internal_transaction.type, internal_transaction.value ) @@ -349,6 +394,18 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do {:ok, {:block_hashes, repo.all(query)}} "transactions" -> +<<<<<<< HEAD +======= + transaction_hashes = + changes_list + |> Enum.reject(&is_nil(Map.get(&1, :transaction_index))) + |> Enum.map(&{&1.block_number, &1.transaction_index}) + |> Enum.uniq() + |> Transaction.by_block_number_index_query() + |> repo.all() + |> Enum.map(& &1.hash) + +>>>>>>> v11.0.0 query = from( pending_ops in PendingTransactionOperation, @@ -375,7 +432,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do from( t in Transaction, where: ^dynamic_condition, - select: map(t, [:hash, :block_hash, :block_number, :cumulative_gas_used, :status]), + select: map(t, [:hash, :block_hash, :block_number, :cumulative_gas_used, :status, :index]), # Enforce Transaction ShareLocks order (see docs: sharelocks.md) order_by: [asc: t.hash], lock: "FOR NO KEY UPDATE" @@ -388,7 +445,6 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do # Finds all mismatches between transactions and internal transactions # for a block number: # - there are no internal transactions for some transactions - # - there are internal transactions with a different block number than their transactions # Returns block numbers where any of these issues is found # Note: the case "# - there are no transactions for some internal transactions" was removed because it caused the issue https://github.com/blockscout/blockscout/issues/3367 @@ -400,23 +456,17 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do # the case "# - there are no internal transactions for some transactions" is removed since # there are may be non-traceable transactions - transactions_tuples = MapSet.new(transactions, &{&1.hash, &1.block_number}) - - internal_transactions_tuples = MapSet.new(internal_transactions_params, &{&1.transaction_hash, &1.block_number}) - - all_tuples = MapSet.union(transactions_tuples, internal_transactions_tuples) - invalid_block_numbers = if allow_non_traceable_transactions?() do - Enum.reduce(internal_transactions_tuples, [], fn {transaction_hash, block_number}, acc -> - # credo:disable-for-next-line - case Enum.find(transactions_tuples, fn {t_hash, _block_number} -> t_hash == transaction_hash end) do - nil -> acc - {_t_hash, ^block_number} -> acc - _ -> [block_number | acc] - end - end) + [] else + transactions_tuples = MapSet.new(transactions, &{&1.index, &1.block_number}) + + internal_transactions_tuples = + MapSet.new(internal_transactions_params, &{&1.transaction_index, &1.block_number}) + + all_tuples = MapSet.union(transactions_tuples, internal_transactions_tuples) + all_tuples |> MapSet.difference(internal_transactions_tuples) |> MapSet.new(fn {_hash, block_number} -> block_number end) @@ -453,13 +503,18 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do defp compose_entry_wrapper(item, blocks_map) do case item do {block_number, entries} -> +<<<<<<< HEAD compose_entry(entries, blocks_map, block_number) +======= + compose_entry(entries, block_number, blocks_map) +>>>>>>> v11.0.0 _ -> [] end end +<<<<<<< HEAD defp compose_entry(entries, blocks_map, block_number) do if Map.has_key?(blocks_map, block_number) do block_hash = Map.fetch!(blocks_map, block_number) @@ -475,11 +530,65 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do |> Map.put(:block_index, index) |> sanitize_error() end) +======= + defp compose_entry(entries, block_number, blocks_map) do + if Map.has_key?(blocks_map, block_number) do + entries +>>>>>>> v11.0.0 else [] end end +<<<<<<< HEAD +======= + defp adjust_insert_params(internal_transactions_params) do + error_to_error_id_map = + internal_transactions_params + |> Enum.map(&sanitize_error/1) + |> Enum.map(&Map.get(&1, :error)) + |> Enum.reject(&is_nil/1) + |> Enum.uniq() + |> TransactionError.find_or_create_multiple() + + address_hash_to_address_id_map = + internal_transactions_params + |> Enum.flat_map(fn params -> + params + |> Map.take([:from_address_hash, :to_address_hash, :created_contract_address_hash]) + |> Map.values() + end) + |> Enum.reject(&is_nil/1) + |> Enum.uniq() + |> AddressIdToAddressHash.find_or_create_multiple() + + Enum.map(internal_transactions_params, fn params -> + params + |> sanitize_error() + |> put_error_id(error_to_error_id_map) + |> put_address_ids(address_hash_to_address_id_map) + |> shift_created_contract_address_id() + end) + end + + defp put_error_id(entry, error_to_error_id_map) do + entry + |> Map.delete(:error) + |> Map.put(:error_id, Map.get(entry, :error_id) || error_to_error_id_map[Map.get(entry, :error)]) + end + + defp put_address_ids(entry, address_hash_to_address_id_map) do + entry + |> Map.drop([:from_address_hash, :to_address_hash, :created_contract_address_hash]) + |> Map.merge(%{ + from_address_id: address_hash_to_address_id_map[String.downcase(to_string(entry[:from_address_hash]))], + to_address_id: address_hash_to_address_id_map[String.downcase(to_string(entry[:to_address_hash]))], + created_contract_address_id: + address_hash_to_address_id_map[String.downcase(to_string(entry[:created_contract_address_hash]))] + }) + end + +>>>>>>> v11.0.0 defp valid_internal_transactions_without_first_trace(valid_internal_transactions) do json_rpc_named_arguments = Application.fetch_env!(:indexer, :json_rpc_named_arguments) variant = Keyword.fetch!(json_rpc_named_arguments, :variant) @@ -516,13 +625,21 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do defp maybe_reject_zero_value(internal_transactions) do with true <- Application.get_env(:explorer, DeleteZeroValueInternalTransactions)[:enabled], border_number when is_integer(border_number) <- DeleteZeroValueInternalTransactions.border_number() do +<<<<<<< HEAD {:ok, Enum.reject( internal_transactions, &(&1.block_number <= border_number and &1.type == :call and Decimal.eq?(&1.value.value, 0)) )} +======= + Enum.reject( + internal_transactions, + &(&1.block_number <= border_number and &1.type == :call and + (is_nil(Map.get(&1, :value)) || Decimal.eq?(&1.value.value, 0))) + ) +>>>>>>> v11.0.0 else - _ -> {:ok, internal_transactions} + _ -> internal_transactions end end @@ -541,6 +658,23 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do Map.put(entry, :error, sanitized_error) end +<<<<<<< HEAD +======= + # Shifts the `created_contract_address_id` value to `to_address_id` when applicable. + + # This function handles the migration of contract creation data by copying the + # `created_contract_address_id` to `to_address_id` field when: + # - `created_contract_address_id` is present (not nil) + # - `to_address_id` is nil + @spec shift_created_contract_address_id(map()) :: map() + defp shift_created_contract_address_id(entry) do + case {Map.get(entry, :created_contract_address_id), Map.get(entry, :to_address_id)} do + {id, nil} when not is_nil(id) -> Map.put(entry, :to_address_id, id) + _ -> entry + end + end + +>>>>>>> v11.0.0 def defer_internal_transactions_primary_key(repo) do # Allows internal_transactions primary key to not be checked during the # DB transactions and instead be checked only at the end of it. @@ -558,6 +692,8 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do if valid_internal_transactions_count == 0 do {:ok, nil} else + block_number_index_to_hash_map = Map.new(transactions, &{{&1.block_number, &1.index}, &1.hash}) + params = valid_internal_transactions |> Enum.filter(fn internal_transaction -> @@ -568,21 +704,31 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do block_hash: Map.get(trace, :block_hash), block_number: Map.get(trace, :block_number), gas_used: Map.get(trace, :gas_used), - transaction_hash: Map.get(trace, :transaction_hash), - created_contract_address_hash: Map.get(trace, :created_contract_address_hash), - error: Map.get(trace, :error), - status: if(is_nil(Map.get(trace, :error)), do: :ok, else: :error) + transaction_hash: + Map.fetch!(block_number_index_to_hash_map, {trace[:block_number], trace[:transaction_index]}), + created_contract_address_hash: + AddressIdToAddressHash.id_to_hash(Map.get(trace, :created_contract_address_id)), + error: TransactionError.id_to_error(Map.get(trace, :error_id)), + status: if(is_nil(Map.get(trace, :error_id)), do: :ok, else: :error) } end) |> Enum.filter(fn transaction_hash -> transaction_hash != nil end) transaction_hashes = valid_internal_transactions - |> MapSet.new(& &1.transaction_hash) + |> MapSet.new(&{&1.block_number, &1.transaction_index}) |> MapSet.to_list() + |> then(fn block_numbers_indexes -> Map.take(block_number_index_to_hash_map, block_numbers_indexes) end) + |> Map.values() json_rpc_named_arguments = Application.fetch_env!(:indexer, :json_rpc_named_arguments) + valid_internal_transactions_with_hashes = + Enum.map(valid_internal_transactions, fn it -> + transaction_hash = Map.fetch!(block_number_index_to_hash_map, {it[:block_number], it[:transaction_index]}) + Map.put(it, :transaction_hash, transaction_hash) + end) + result = Enum.reduce_while(params, 0, fn first_trace, transaction_hashes_iterator -> transaction_hash = Map.get(first_trace, :transaction_hash) @@ -592,7 +738,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do update_transactions_inner_wrapper( transaction_from_db, repo, - valid_internal_transactions, + valid_internal_transactions_with_hashes, transaction_hash, json_rpc_named_arguments, transaction_hashes, @@ -681,7 +827,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do defp get_trivial_transaction_hashes_with_error_in_internal_transaction(internal_transactions) do internal_transactions |> Enum.filter(fn internal_transaction -> - internal_transaction[:index] != 0 && !is_nil(internal_transaction[:error]) + internal_transaction[:index] != 0 && !is_nil(internal_transaction[:error_id]) end) |> Enum.map(fn internal_transaction -> internal_transaction[:transaction_hash] end) |> MapSet.new() @@ -891,6 +1037,58 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do end end +<<<<<<< HEAD +======= + defp empty_selfdestructed_contracts_bytecode(repo, valid_internal_transactions, timestamps) do + # Find all selfdestruct internal transactions + selfdestruct_addresses = + valid_internal_transactions + |> Enum.filter(&(&1.type == :selfdestruct)) + |> Enum.map(&{&1.block_number, &1.transaction_index, &1.from_address_id}) + |> MapSet.new() + + # Find all create/create2 internal transactions in the same transactions + created_addresses = + valid_internal_transactions + |> Enum.filter(&(&1.type in [:create, :create2])) + |> Enum.map(&{&1.block_number, &1.transaction_index, Map.get(&1, :created_contract_address_id)}) + |> Enum.reject(fn {_block_number, _tx_index, address_id} -> is_nil(address_id) end) + |> MapSet.new() + + # Filter to find addresses that were selfdestructed but NOT created in the same transaction + addresses_to_empty = + selfdestruct_addresses + |> Enum.reject(fn {block_number, tx_index, address_id} -> + MapSet.member?(created_addresses, {block_number, tx_index, address_id}) + end) + |> Enum.map(fn {_block_number, _tx_index, address_id} -> address_id end) + |> Enum.uniq() + |> AddressIdToAddressHash.ids_to_hashes() + + if Enum.empty?(addresses_to_empty) do + {:ok, []} + else + # Update the addresses to have empty contract_code + empty_contract_code = %Explorer.Chain.Data{bytes: <<>>} + + update_query = + from( + address in Address, + where: address.hash in ^addresses_to_empty, + update: [set: [contract_code: ^empty_contract_code, updated_at: ^timestamps.updated_at]] + ) + + {count, _} = repo.update_all(update_query, []) + + Logger.info( + "Emptied contract_code for #{count} selfdestructed contracts: #{inspect(addresses_to_empty, limit: :infinity)}" + ) + + {:ok, count} + end + end + +>>>>>>> v11.0.0 defp traceable_blocks_dynamic_query do if RangesHelper.trace_ranges_present?() do block_ranges = RangesHelper.get_trace_block_ranges() diff --git a/apps/explorer/lib/explorer/chain/import/runner/polygon_zkevm/batch_transactions.ex b/apps/explorer/lib/explorer/chain/import/runner/polygon_zkevm/batch_transactions.ex deleted file mode 100644 index c330da10e689..000000000000 --- a/apps/explorer/lib/explorer/chain/import/runner/polygon_zkevm/batch_transactions.ex +++ /dev/null @@ -1,79 +0,0 @@ -defmodule Explorer.Chain.Import.Runner.PolygonZkevm.BatchTransactions do - @moduledoc """ - Bulk imports `t:Explorer.Chain.PolygonZkevm.BatchTransaction.t/0`. - """ - - require Ecto.Query - - alias Ecto.{Changeset, Multi, Repo} - alias Explorer.Chain.Import - alias Explorer.Chain.PolygonZkevm.BatchTransaction - alias Explorer.Prometheus.Instrumenter - - @behaviour Import.Runner - - # milliseconds - @timeout 60_000 - - @type imported :: [BatchTransaction.t()] - - @impl Import.Runner - def ecto_schema_module, do: BatchTransaction - - @impl Import.Runner - def option_key, do: :polygon_zkevm_batch_transactions - - @impl Import.Runner - @spec imported_table_row() :: %{:value_description => binary(), :value_type => binary()} - def imported_table_row do - %{ - value_type: "[#{ecto_schema_module()}.t()]", - value_description: "List of `t:#{ecto_schema_module()}.t/0`s" - } - end - - @impl Import.Runner - @spec run(Multi.t(), list(), map()) :: Multi.t() - def run(multi, changes_list, %{timestamps: timestamps} = options) do - insert_options = - options - |> Map.get(option_key(), %{}) - |> Map.take(~w(on_conflict timeout)a) - |> Map.put_new(:timeout, @timeout) - |> Map.put(:timestamps, timestamps) - - Multi.run(multi, :insert_polygon_zkevm_batch_transactions, fn repo, _ -> - Instrumenter.block_import_stage_runner( - fn -> insert(repo, changes_list, insert_options) end, - :block_referencing, - :polygon_zkevm_batch_transactions, - :polygon_zkevm_batch_transactions - ) - end) - end - - @impl Import.Runner - def timeout, do: @timeout - - @spec insert(Repo.t(), [map()], %{required(:timeout) => timeout(), required(:timestamps) => Import.timestamps()}) :: - {:ok, [BatchTransaction.t()]} - | {:error, [Changeset.t()]} - def insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = _options) when is_list(changes_list) do - # Enforce PolygonZkevm.BatchTransaction ShareLocks order (see docs: sharelock.md) - ordered_changes_list = Enum.sort_by(changes_list, & &1.hash) - - {:ok, inserted} = - Import.insert_changes_list( - repo, - ordered_changes_list, - for: BatchTransaction, - returning: true, - timeout: timeout, - timestamps: timestamps, - conflict_target: :hash, - on_conflict: :nothing - ) - - {:ok, inserted} - end -end diff --git a/apps/explorer/lib/explorer/chain/import/runner/polygon_zkevm/bridge_l1_tokens.ex b/apps/explorer/lib/explorer/chain/import/runner/polygon_zkevm/bridge_l1_tokens.ex deleted file mode 100644 index 03ed1bd5783c..000000000000 --- a/apps/explorer/lib/explorer/chain/import/runner/polygon_zkevm/bridge_l1_tokens.ex +++ /dev/null @@ -1,101 +0,0 @@ -defmodule Explorer.Chain.Import.Runner.PolygonZkevm.BridgeL1Tokens do - @moduledoc """ - Bulk imports `t:Explorer.Chain.PolygonZkevm.BridgeL1Token.t/0`. - """ - - require Ecto.Query - - import Ecto.Query, only: [from: 2] - - alias Ecto.{Changeset, Multi, Repo} - alias Explorer.Chain.Import - alias Explorer.Chain.PolygonZkevm.BridgeL1Token - alias Explorer.Prometheus.Instrumenter - - @behaviour Import.Runner - - # milliseconds - @timeout 60_000 - - @type imported :: [BridgeL1Token.t()] - - @impl Import.Runner - def ecto_schema_module, do: BridgeL1Token - - @impl Import.Runner - def option_key, do: :polygon_zkevm_bridge_l1_tokens - - @impl Import.Runner - def imported_table_row do - %{ - value_type: "[#{ecto_schema_module()}.t()]", - value_description: "List of `t:#{ecto_schema_module()}.t/0`s" - } - end - - @impl Import.Runner - def run(multi, changes_list, %{timestamps: timestamps} = options) do - insert_options = - options - |> Map.get(option_key(), %{}) - |> Map.take(~w(on_conflict timeout)a) - |> Map.put_new(:timeout, @timeout) - |> Map.put(:timestamps, timestamps) - - Multi.run(multi, :insert_polygon_zkevm_bridge_l1_tokens, fn repo, _ -> - Instrumenter.block_import_stage_runner( - fn -> insert(repo, changes_list, insert_options) end, - :block_referencing, - :polygon_zkevm_bridge_l1_tokens, - :polygon_zkevm_bridge_l1_tokens - ) - end) - end - - @impl Import.Runner - def timeout, do: @timeout - - @spec insert(Repo.t(), [map()], %{required(:timeout) => timeout(), required(:timestamps) => Import.timestamps()}) :: - {:ok, [BridgeL1Token.t()]} - | {:error, [Changeset.t()]} - def insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do - on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) - - # Enforce BridgeL1Token ShareLocks order (see docs: sharelock.md) - ordered_changes_list = Enum.sort_by(changes_list, &{&1.address}) - - {:ok, inserted} = - Import.insert_changes_list( - repo, - ordered_changes_list, - conflict_target: :address, - on_conflict: on_conflict, - for: BridgeL1Token, - returning: true, - timeout: timeout, - timestamps: timestamps - ) - - {:ok, inserted} - end - - defp default_on_conflict do - from( - t in BridgeL1Token, - update: [ - set: [ - decimals: fragment("EXCLUDED.decimals"), - symbol: fragment("EXCLUDED.symbol"), - inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", t.inserted_at), - updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", t.updated_at) - ] - ], - where: - fragment( - "(EXCLUDED.decimals, EXCLUDED.symbol) IS DISTINCT FROM (?, ?)", - t.decimals, - t.symbol - ) - ) - end -end diff --git a/apps/explorer/lib/explorer/chain/import/runner/polygon_zkevm/bridge_operations.ex b/apps/explorer/lib/explorer/chain/import/runner/polygon_zkevm/bridge_operations.ex deleted file mode 100644 index 6cd724fe70cb..000000000000 --- a/apps/explorer/lib/explorer/chain/import/runner/polygon_zkevm/bridge_operations.ex +++ /dev/null @@ -1,115 +0,0 @@ -defmodule Explorer.Chain.Import.Runner.PolygonZkevm.BridgeOperations do - @moduledoc """ - Bulk imports `t:Explorer.Chain.PolygonZkevm.Bridge.t/0`. - """ - - require Ecto.Query - - import Ecto.Query, only: [from: 2] - - alias Ecto.{Changeset, Multi, Repo} - alias Explorer.Chain.Import - alias Explorer.Chain.PolygonZkevm.Bridge, as: PolygonZkevmBridge - alias Explorer.Prometheus.Instrumenter - - @behaviour Import.Runner - - # milliseconds - @timeout 60_000 - - @type imported :: [PolygonZkevmBridge.t()] - - @impl Import.Runner - def ecto_schema_module, do: PolygonZkevmBridge - - @impl Import.Runner - def option_key, do: :polygon_zkevm_bridge_operations - - @impl Import.Runner - def imported_table_row do - %{ - value_type: "[#{ecto_schema_module()}.t()]", - value_description: "List of `t:#{ecto_schema_module()}.t/0`s" - } - end - - @impl Import.Runner - def run(multi, changes_list, %{timestamps: timestamps} = options) do - insert_options = - options - |> Map.get(option_key(), %{}) - |> Map.take(~w(on_conflict timeout)a) - |> Map.put_new(:timeout, @timeout) - |> Map.put(:timestamps, timestamps) - - Multi.run(multi, :insert_polygon_zkevm_bridge_operations, fn repo, _ -> - Instrumenter.block_import_stage_runner( - fn -> insert(repo, changes_list, insert_options) end, - :block_referencing, - :polygon_zkevm_bridge_operations, - :polygon_zkevm_bridge_operations - ) - end) - end - - @impl Import.Runner - def timeout, do: @timeout - - @spec insert(Repo.t(), [map()], %{required(:timeout) => timeout(), required(:timestamps) => Import.timestamps()}) :: - {:ok, [PolygonZkevmBridge.t()]} - | {:error, [Changeset.t()]} - def insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do - on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) - - # Enforce PolygonZkevmBridge ShareLocks order (see docs: sharelock.md) - ordered_changes_list = Enum.sort_by(changes_list, &{&1.type, &1.index}) - - {:ok, inserted} = - Import.insert_changes_list( - repo, - ordered_changes_list, - conflict_target: [:type, :index], - on_conflict: on_conflict, - for: PolygonZkevmBridge, - returning: true, - timeout: timeout, - timestamps: timestamps - ) - - {:ok, inserted} - end - - defp default_on_conflict do - from( - op in PolygonZkevmBridge, - update: [ - set: [ - # Don't update `type` as it is part of the composite primary key and used for the conflict target - # Don't update `index` as it is part of the composite primary key and used for the conflict target - l1_transaction_hash: fragment("COALESCE(EXCLUDED.l1_transaction_hash, ?)", op.l1_transaction_hash), - l2_transaction_hash: fragment("COALESCE(EXCLUDED.l2_transaction_hash, ?)", op.l2_transaction_hash), - l1_token_id: fragment("COALESCE(EXCLUDED.l1_token_id, ?)", op.l1_token_id), - l1_token_address: fragment("COALESCE(EXCLUDED.l1_token_address, ?)", op.l1_token_address), - l2_token_address: fragment("COALESCE(EXCLUDED.l2_token_address, ?)", op.l2_token_address), - amount: fragment("EXCLUDED.amount"), - block_number: fragment("COALESCE(EXCLUDED.block_number, ?)", op.block_number), - block_timestamp: fragment("COALESCE(EXCLUDED.block_timestamp, ?)", op.block_timestamp), - inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", op.inserted_at), - updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", op.updated_at) - ] - ], - where: - fragment( - "(EXCLUDED.l1_transaction_hash, EXCLUDED.l2_transaction_hash, EXCLUDED.l1_token_id, EXCLUDED.l1_token_address, EXCLUDED.l2_token_address, EXCLUDED.amount, EXCLUDED.block_number, EXCLUDED.block_timestamp) IS DISTINCT FROM (?, ?, ?, ?, ?, ?, ?, ?)", - op.l1_transaction_hash, - op.l2_transaction_hash, - op.l1_token_id, - op.l1_token_address, - op.l2_token_address, - op.amount, - op.block_number, - op.block_timestamp - ) - ) - end -end diff --git a/apps/explorer/lib/explorer/chain/import/runner/polygon_zkevm/lifecycle_transactions.ex b/apps/explorer/lib/explorer/chain/import/runner/polygon_zkevm/lifecycle_transactions.ex deleted file mode 100644 index a7260a787c01..000000000000 --- a/apps/explorer/lib/explorer/chain/import/runner/polygon_zkevm/lifecycle_transactions.ex +++ /dev/null @@ -1,103 +0,0 @@ -defmodule Explorer.Chain.Import.Runner.PolygonZkevm.LifecycleTransactions do - @moduledoc """ - Bulk imports `t:Explorer.Chain.PolygonZkevm.LifecycleTransaction.t/0`. - """ - - require Ecto.Query - - alias Ecto.{Changeset, Multi, Repo} - alias Explorer.Chain.Import - alias Explorer.Chain.PolygonZkevm.LifecycleTransaction - alias Explorer.Prometheus.Instrumenter - - import Ecto.Query, only: [from: 2] - - @behaviour Import.Runner - - # milliseconds - @timeout 60_000 - - @type imported :: [LifecycleTransaction.t()] - - @impl Import.Runner - def ecto_schema_module, do: LifecycleTransaction - - @impl Import.Runner - def option_key, do: :polygon_zkevm_lifecycle_transactions - - @impl Import.Runner - @spec imported_table_row() :: %{:value_description => binary(), :value_type => binary()} - def imported_table_row do - %{ - value_type: "[#{ecto_schema_module()}.t()]", - value_description: "List of `t:#{ecto_schema_module()}.t/0`s" - } - end - - @impl Import.Runner - @spec run(Multi.t(), list(), map()) :: Multi.t() - def run(multi, changes_list, %{timestamps: timestamps} = options) do - insert_options = - options - |> Map.get(option_key(), %{}) - |> Map.take(~w(on_conflict timeout)a) - |> Map.put_new(:timeout, @timeout) - |> Map.put(:timestamps, timestamps) - - Multi.run(multi, :insert_polygon_zkevm_lifecycle_transactions, fn repo, _ -> - Instrumenter.block_import_stage_runner( - fn -> insert(repo, changes_list, insert_options) end, - :block_referencing, - :polygon_zkevm_lifecycle_transactions, - :polygon_zkevm_lifecycle_transactions - ) - end) - end - - @impl Import.Runner - def timeout, do: @timeout - - @spec insert(Repo.t(), [map()], %{required(:timeout) => timeout(), required(:timestamps) => Import.timestamps()}) :: - {:ok, [LifecycleTransaction.t()]} - | {:error, [Changeset.t()]} - def insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do - on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) - - # Enforce PolygonZkevm.LifecycleTransaction ShareLocks order (see docs: sharelock.md) - ordered_changes_list = Enum.sort_by(changes_list, & &1.id) - - {:ok, inserted} = - Import.insert_changes_list( - repo, - ordered_changes_list, - for: LifecycleTransaction, - returning: true, - timeout: timeout, - timestamps: timestamps, - conflict_target: :hash, - on_conflict: on_conflict - ) - - {:ok, inserted} - end - - defp default_on_conflict do - from( - transaction in LifecycleTransaction, - update: [ - set: [ - # don't update `id` as it is a primary key - # don't update `hash` as it is a unique index and used for the conflict target - is_verify: fragment("EXCLUDED.is_verify"), - inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", transaction.inserted_at), - updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", transaction.updated_at) - ] - ], - where: - fragment( - "(EXCLUDED.is_verify) IS DISTINCT FROM (?)", - transaction.is_verify - ) - ) - end -end diff --git a/apps/explorer/lib/explorer/chain/import/runner/polygon_zkevm/transaction_batches.ex b/apps/explorer/lib/explorer/chain/import/runner/polygon_zkevm/transaction_batches.ex deleted file mode 100644 index e2b8930b1828..000000000000 --- a/apps/explorer/lib/explorer/chain/import/runner/polygon_zkevm/transaction_batches.ex +++ /dev/null @@ -1,114 +0,0 @@ -defmodule Explorer.Chain.Import.Runner.PolygonZkevm.TransactionBatches do - @moduledoc """ - Bulk imports `t:Explorer.Chain.PolygonZkevm.TransactionBatch.t/0`. - """ - - require Ecto.Query - - alias Ecto.{Changeset, Multi, Repo} - alias Explorer.Chain.Import - alias Explorer.Chain.PolygonZkevm.TransactionBatch - alias Explorer.Prometheus.Instrumenter - - import Ecto.Query, only: [from: 2] - - @behaviour Import.Runner - - # milliseconds - @timeout 60_000 - - @type imported :: [TransactionBatch.t()] - - @impl Import.Runner - def ecto_schema_module, do: TransactionBatch - - @impl Import.Runner - def option_key, do: :polygon_zkevm_transaction_batches - - @impl Import.Runner - @spec imported_table_row() :: %{:value_description => binary(), :value_type => binary()} - def imported_table_row do - %{ - value_type: "[#{ecto_schema_module()}.t()]", - value_description: "List of `t:#{ecto_schema_module()}.t/0`s" - } - end - - @impl Import.Runner - @spec run(Multi.t(), list(), map()) :: Multi.t() - def run(multi, changes_list, %{timestamps: timestamps} = options) do - insert_options = - options - |> Map.get(option_key(), %{}) - |> Map.take(~w(on_conflict timeout)a) - |> Map.put_new(:timeout, @timeout) - |> Map.put(:timestamps, timestamps) - - Multi.run(multi, :insert_polygon_zkevm_transaction_batches, fn repo, _ -> - Instrumenter.block_import_stage_runner( - fn -> insert(repo, changes_list, insert_options) end, - :block_referencing, - :polygon_zkevm_transaction_batches, - :polygon_zkevm_transaction_batches - ) - end) - end - - @impl Import.Runner - def timeout, do: @timeout - - @spec insert(Repo.t(), [map()], %{required(:timeout) => timeout(), required(:timestamps) => Import.timestamps()}) :: - {:ok, [TransactionBatch.t()]} - | {:error, [Changeset.t()]} - def insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do - on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) - - # Enforce PolygonZkevm.TransactionBatch ShareLocks order (see docs: sharelock.md) - ordered_changes_list = Enum.sort_by(changes_list, & &1.number) - - {:ok, inserted} = - Import.insert_changes_list( - repo, - ordered_changes_list, - for: TransactionBatch, - returning: true, - timeout: timeout, - timestamps: timestamps, - conflict_target: :number, - on_conflict: on_conflict - ) - - {:ok, inserted} - end - - defp default_on_conflict do - from( - tb in TransactionBatch, - update: [ - set: [ - # don't update `number` as it is a primary key and used for the conflict target - timestamp: fragment("EXCLUDED.timestamp"), - l2_transactions_count: fragment("EXCLUDED.l2_transactions_count"), - global_exit_root: fragment("EXCLUDED.global_exit_root"), - acc_input_hash: fragment("EXCLUDED.acc_input_hash"), - state_root: fragment("EXCLUDED.state_root"), - sequence_id: fragment("EXCLUDED.sequence_id"), - verify_id: fragment("EXCLUDED.verify_id"), - inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", tb.inserted_at), - updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", tb.updated_at) - ] - ], - where: - fragment( - "(EXCLUDED.timestamp, EXCLUDED.l2_transactions_count, EXCLUDED.global_exit_root, EXCLUDED.acc_input_hash, EXCLUDED.state_root, EXCLUDED.sequence_id, EXCLUDED.verify_id) IS DISTINCT FROM (?, ?, ?, ?, ?, ?, ?)", - tb.timestamp, - tb.l2_transactions_count, - tb.global_exit_root, - tb.acc_input_hash, - tb.state_root, - tb.sequence_id, - tb.verify_id - ) - ) - end -end diff --git a/apps/explorer/lib/explorer/chain/import/runner/transaction_actions.ex b/apps/explorer/lib/explorer/chain/import/runner/transaction_actions.ex deleted file mode 100644 index a5e54c7763eb..000000000000 --- a/apps/explorer/lib/explorer/chain/import/runner/transaction_actions.ex +++ /dev/null @@ -1,104 +0,0 @@ -defmodule Explorer.Chain.Import.Runner.TransactionActions do - @moduledoc """ - Bulk imports `t:Explorer.Chain.TransactionAction.t/0`. - """ - - require Ecto.Query - - import Ecto.Query, only: [from: 2] - - alias Ecto.{Changeset, Multi, Repo} - alias Explorer.Chain.{Import, TransactionAction} - alias Explorer.Prometheus.Instrumenter - - @behaviour Import.Runner - - # milliseconds - @timeout 60_000 - - @type imported :: [TransactionAction.t()] - - @impl Import.Runner - def ecto_schema_module, do: TransactionAction - - @impl Import.Runner - def option_key, do: :transaction_actions - - @impl Import.Runner - def imported_table_row do - %{ - value_type: "[#{ecto_schema_module()}.t()]", - value_description: "List of `t:#{ecto_schema_module()}.t/0`s" - } - end - - @impl Import.Runner - def run(multi, changes_list, %{timestamps: timestamps} = options) do - insert_options = - options - |> Map.get(option_key(), %{}) - |> Map.take(~w(on_conflict timeout)a) - |> Map.put_new(:timeout, @timeout) - |> Map.put(:timestamps, timestamps) - - Multi.run(multi, :insert_transaction_actions, fn repo, _ -> - Instrumenter.block_import_stage_runner( - fn -> insert(repo, changes_list, insert_options) end, - :block_referencing, - :transaction_actions, - :transaction_actions - ) - end) - end - - @impl Import.Runner - def timeout, do: @timeout - - @spec insert(Repo.t(), [map()], %{required(:timeout) => timeout(), required(:timestamps) => Import.timestamps()}) :: - {:ok, [TransactionAction.t()]} - | {:error, [Changeset.t()]} - def insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do - on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) - - # Enforce TransactionAction ShareLocks order (see docs: sharelock.md) - ordered_changes_list = Enum.sort_by(changes_list, &{&1.hash, &1.log_index}) - - {:ok, inserted} = - Import.insert_changes_list( - repo, - ordered_changes_list, - conflict_target: [:hash, :log_index], - on_conflict: on_conflict, - for: TransactionAction, - returning: true, - timeout: timeout, - timestamps: timestamps - ) - - {:ok, inserted} - end - - defp default_on_conflict do - from( - action in TransactionAction, - update: [ - set: [ - # Don't update `hash` as it is part of the composite primary key and used for the conflict target - # Don't update `log_index` as it is part of the composite primary key and used for the conflict target - protocol: fragment("EXCLUDED.protocol"), - data: fragment("EXCLUDED.data"), - type: fragment("EXCLUDED.type"), - inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", action.inserted_at), - updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", action.updated_at) - ] - ], - where: - fragment( - "(EXCLUDED.protocol, EXCLUDED.data, EXCLUDED.type) IS DISTINCT FROM (?, ? ,?)", - action.protocol, - action.data, - action.type - ) - ) - end -end diff --git a/apps/explorer/lib/explorer/chain/import/stage/block_transaction_referencing.ex b/apps/explorer/lib/explorer/chain/import/stage/block_transaction_referencing.ex index e868a1d66316..1e9c50b82acf 100644 --- a/apps/explorer/lib/explorer/chain/import/stage/block_transaction_referencing.ex +++ b/apps/explorer/lib/explorer/chain/import/stage/block_transaction_referencing.ex @@ -12,7 +12,6 @@ defmodule Explorer.Chain.Import.Stage.BlockTransactionReferencing do Runner.Transaction.Forks, Runner.Block.Rewards, Runner.Block.SecondDegreeRelations, - Runner.TransactionActions, Runner.Withdrawals, Runner.SignedAuthorizations, Runner.FheOperations diff --git a/apps/explorer/lib/explorer/chain/import/stage/chain_type_specific.ex b/apps/explorer/lib/explorer/chain/import/stage/chain_type_specific.ex index ae42f2172bf7..0809c354dfc9 100644 --- a/apps/explorer/lib/explorer/chain/import/stage/chain_type_specific.ex +++ b/apps/explorer/lib/explorer/chain/import/stage/chain_type_specific.ex @@ -24,13 +24,6 @@ defmodule Explorer.Chain.Import.Stage.ChainTypeSpecific do Runner.Optimism.EIP1559ConfigUpdates, Runner.Optimism.InteropMessages ], - polygon_zkevm: [ - Runner.PolygonZkevm.LifecycleTransactions, - Runner.PolygonZkevm.TransactionBatches, - Runner.PolygonZkevm.BatchTransactions, - Runner.PolygonZkevm.BridgeL1Tokens, - Runner.PolygonZkevm.BridgeOperations - ], zksync: [ Runner.ZkSync.LifecycleTransactions, Runner.ZkSync.TransactionBatches, diff --git a/apps/explorer/lib/explorer/chain/import/stage/token_referencing.ex b/apps/explorer/lib/explorer/chain/import/stage/token_referencing.ex index de1c15b3069f..728254ec7aad 100644 --- a/apps/explorer/lib/explorer/chain/import/stage/token_referencing.ex +++ b/apps/explorer/lib/explorer/chain/import/stage/token_referencing.ex @@ -19,16 +19,21 @@ defmodule Explorer.Chain.Import.Stage.TokenReferencing do @impl Stage def all_runners, do: runners() - @ctb_chunk_size 50 + @default_ctb_chunk_size 50 @impl Stage def multis(runner_to_changes_list, options) do {ctb_multis, remaining_runner_to_changes_list} = - Stage.chunk_every(runner_to_changes_list, @ctb_runner, @ctb_chunk_size, options) + Stage.chunk_every(runner_to_changes_list, @ctb_runner, ctb_chunk_size(), options) {final_multi, final_remaining_runner_to_changes_list} = Stage.single_multi(@rest_runners, remaining_runner_to_changes_list, options) {[final_multi | ctb_multis], final_remaining_runner_to_changes_list} end + + defp ctb_chunk_size do + Application.get_env(:explorer, :token_balances_import_chunk_size, @default_ctb_chunk_size) + |> max(1) + end end diff --git a/apps/explorer/lib/explorer/chain/internal_transaction.ex b/apps/explorer/lib/explorer/chain/internal_transaction.ex index 3ea53b8cfc1c..8507d9eb1ef4 100644 --- a/apps/explorer/lib/explorer/chain/internal_transaction.ex +++ b/apps/explorer/lib/explorer/chain/internal_transaction.ex @@ -3,7 +3,7 @@ defmodule Explorer.Chain.InternalTransaction do use Explorer.Schema - alias Explorer.{Chain, PagingOptions} + alias Explorer.{Chain, Helper, PagingOptions, QueryHelper, Repo} alias Explorer.Chain.{ Address, @@ -18,10 +18,9 @@ defmodule Explorer.Chain.InternalTransaction do alias Explorer.Chain.Block.Reader.General, as: BlockReaderGeneral alias Explorer.Chain.Cache.BackgroundMigrations - alias Explorer.Chain.DenormalizationHelper alias Explorer.Chain.InternalTransaction.{CallType, Type} alias Explorer.Migrator.DeleteZeroValueInternalTransactions - alias Explorer.Utility.InternalTransactionHelper + alias Explorer.Utility.AddressIdToAddressHash import Explorer.Chain.SmartContract.Proxy.Models.Implementation, only: [proxy_implementations_association: 0] @@ -48,13 +47,10 @@ defmodule Explorer.Chain.InternalTransaction do * `to_address_hash` - hash of the sink of the `value` * `trace_address` - list of traces * `transaction` - transaction in which this internal transaction occurred - * `transaction_hash` - foreign key for `transaction` * `transaction_index` - the `t:Explorer.Chain.Transaction.t/0` `index` of `transaction` in `block_number`. * `type` - type of internal transaction * `value` - value of transferred from `from_address` to `to_address` * `block` - block in which this internal transaction occurred - * `block_hash` - foreign key for `block` - * `block_index` - the index of this internal transaction inside the `block` """ @primary_key false typed_schema "internal_transactions" do @@ -62,7 +58,7 @@ defmodule Explorer.Chain.InternalTransaction do field(:call_type, CallType) field(:call_type_enum, Ecto.Enum, values: [:call, :callcode, :delegatecall, :staticcall, :invalid]) field(:created_contract_code, Data) - field(:error, :string) + field(:error, :string, virtual: true) field(:error_id, :integer) field(:gas, :decimal) field(:gas_used, :decimal) @@ -74,46 +70,64 @@ defmodule Explorer.Chain.InternalTransaction do # todo: consider using enum field(:type, Type, null: false) field(:value, Wei) - # TODO: remove field after update PK migration is completed - field(:block_hash, Hash.Full) field(:transaction_index, :integer, primary_key: true, null: false) - # TODO: remove field after update PK migration is completed - field(:block_index, :integer) + + field(:transaction, :map, virtual: true) + field(:transaction_hash, Hash.Full, virtual: true) timestamps() + belongs_to(:created_contract_address_mapping, AddressIdToAddressHash, + foreign_key: :created_contract_address_id, + references: :address_id, + type: :integer + ) + + has_one(:created_contract_address, through: [:created_contract_address_mapping, :address]) + + # TODO: remove after migration to address ids is done belongs_to( - :created_contract_address, + :created_contract_address_by_hash, Address, foreign_key: :created_contract_address_hash, references: :hash, type: Hash.Address ) + belongs_to(:from_address_mapping, AddressIdToAddressHash, + foreign_key: :from_address_id, + references: :address_id, + type: :integer + ) + + has_one(:from_address, through: [:from_address_mapping, :address]) + + # TODO: remove after migration to address ids is done belongs_to( - :from_address, + :from_address_by_hash, Address, foreign_key: :from_address_hash, references: :hash, - type: Hash.Address, - null: false + type: Hash.Address + ) + + belongs_to(:to_address_mapping, AddressIdToAddressHash, + foreign_key: :to_address_id, + references: :address_id, + type: :integer ) + has_one(:to_address, through: [:to_address_mapping, :address]) + + # TODO: remove after migration to address ids is done belongs_to( - :to_address, + :to_address_by_hash, Address, foreign_key: :to_address_hash, references: :hash, type: Hash.Address ) - belongs_to(:transaction, Transaction, - foreign_key: :transaction_hash, - references: :hash, - type: Hash.Full, - null: false - ) - belongs_to(:block, Block, foreign_key: :block_number, references: :number, @@ -126,77 +140,51 @@ defmodule Explorer.Chain.InternalTransaction do @doc """ Validates that the `attrs` are valid. - `:create` type traces generated when a contract is created are valid. `created_contract_address_hash`, - `from_address_hash`, and `transaction_hash` are converted to `t:Explorer.Chain.Hash.t/0`, and `type` is converted to + `:create` type traces generated when a contract is created are valid. `type` is converted to `t:Explorer.Chain.InternalTransaction.Type.t/0` iex> changeset = Explorer.Chain.InternalTransaction.changeset( ...> %Explorer.Chain.InternalTransaction{}, ...> %{ - ...> created_contract_address_hash: "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb4", + ...> created_contract_address_id: 3, ...> created_contract_code: "0x606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", - ...> from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + ...> from_address_id: 1, ...> gas: 4597044, ...> gas_used: 166651, ...> index: 0, ...> init: "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", ...> trace_address: [], - ...> transaction_hash: "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", ...> transaction_index: 0, ...> type: "create", ...> value: 0, - ...> block_number: 35, - ...> block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd" + ...> block_number: 35 ...> } ...> ) iex> changeset.valid? true - iex> changeset.changes.created_contract_address_hash - %Explorer.Chain.Hash{ - byte_count: 20, - bytes: <<255, 200, 114, 57, 235, 2, 103, 188, 60, 162, 205, 81, 209, 47, 191, 39, 142, 2, 204, 180>> - } - iex> changeset.changes.from_address_hash - %Explorer.Chain.Hash{ - byte_count: 20, - bytes: <<232, 221, 197, 199, 162, 210, 240, 215, 169, 121, 132, 89, 192, 16, 79, 223, 94, 152, 122, 202>> - } - iex> changeset.changes.transaction_hash - %Explorer.Chain.Hash{ - byte_count: 32, - bytes: <<58, 62, 177, 52, 230, 121, 44, 233, 64, 62, 164, 24, 142, 94, 121, 105, 61, 233, 228, 201, 78, 73, 157, - 177, 50, 190, 8, 100, 0, 218, 121, 230>> - } iex> changeset.changes.type :create `:create` type can fail due to a Bad Instruction in the `init`, but these need to be valid, so we can display the - failures. `to_address_hash` is converted to `t:Explorer.Chain.Hash.t/0`. + failures. iex> changeset = Explorer.Chain.InternalTransaction.changeset( ...> %Explorer.Chain.InternalTransaction{}, ...> %{ - ...> error: "Bad instruction", - ...> from_address_hash: "0x78a42d3705fb3c26a4b54737a784bf064f0815fb", + ...> error_id: 1, + ...> from_address_id: 1, ...> gas: 3946728, ...> index: 0, ...> init: "0x4bb278f3", ...> trace_address: [], - ...> transaction_hash: "0x3c624bb4852fb5e35a8f45644cec7a486211f6ba89034768a2b763194f22f97d", ...> type: "create", ...> value: 0, ...> block_number: 35, - ...> block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", ...> transaction_index: 0 ...> } iex> ) iex> changeset.valid? true - iex> changeset.changes.from_address_hash - %Explorer.Chain.Hash{ - byte_count: 20, - bytes: <<120, 164, 45, 55, 5, 251, 60, 38, 164, 181, 71, 55, 167, 132, 191, 6, 79, 8, 21, 251>> - } `:call` type traces are generated when a method in a contract is call. @@ -204,15 +192,13 @@ defmodule Explorer.Chain.InternalTransaction do ...> %Explorer.Chain.InternalTransaction{}, ...> %{ ...> block_number: 35, - ...> block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", ...> transaction_index: 0, - ...> transaction_hash: "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", ...> index: 0, ...> trace_address: [], ...> call_type: "call", ...> type: "call", - ...> from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", - ...> to_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", + ...> from_address_id: 1, + ...> to_address_id: 2, ...> gas: 4677320, ...> gas_used: 27770, ...> input: "0x", @@ -229,18 +215,16 @@ defmodule Explorer.Chain.InternalTransaction do ...> %Explorer.Chain.InternalTransaction{}, ...> %{ ...> block_number: 35, - ...> block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", ...> transaction_index: 0, - ...> transaction_hash: "0xcd7c15dbbc797722bef6e1d551edfd644fc7f4fb2ccd6a7947b2d1ade9ed140b", ...> index: 0, ...> trace_address: [], ...> type: "call", ...> call_type: "call", - ...> from_address_hash: "0xc9266e6fdf5182dc47d27e0dc32bdff9e4cd2e32", - ...> to_address_hash: "0xfdca0da4158740a93693441b35809b5bb463e527", + ...> from_address_id: 1, + ...> to_address_id: 2, ...> gas: 7578728, ...> input: "0x", - ...> error: "Reverted", + ...> error_id: 1, ...> value: 10000000000000000 ...> } ...> ) @@ -255,20 +239,18 @@ defmodule Explorer.Chain.InternalTransaction do ...> %Explorer.Chain.InternalTransaction{}, ...> %{ ...> block_number: 35, - ...> block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", ...> transaction_index: 0, - ...> transaction_hash: "0xcd7c15dbbc797722bef6e1d551edfd644fc7f4fb2ccd6a7947b2d1ade9ed140b", ...> index: 0, ...> trace_address: [], ...> type: "call", ...> call_type: "call", - ...> from_address_hash: "0xc9266e6fdf5182dc47d27e0dc32bdff9e4cd2e32", - ...> to_address_hash: "0xfdca0da4158740a93693441b35809b5bb463e527", + ...> from_address_id: 1, + ...> to_address_id: 2, ...> gas: 7578728, ...> gas_used: 7578727, ...> input: "0x", ...> output: "0x", - ...> error: "Reverted", + ...> error_id: 1, ...> value: 10000000000000000 ...> } ...> ) @@ -281,15 +263,13 @@ defmodule Explorer.Chain.InternalTransaction do ...> %Explorer.Chain.InternalTransaction{}, ...> %{ ...> block_number: 35, - ...> block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", ...> transaction_index: 0, - ...> transaction_hash: "0xcd7c15dbbc797722bef6e1d551edfd644fc7f4fb2ccd6a7947b2d1ade9ed140b", ...> index: 0, ...> trace_address: [], ...> type: "call", ...> call_type: "call", - ...> from_address_hash: "0xc9266e6fdf5182dc47d27e0dc32bdff9e4cd2e32", - ...> to_address_hash: "0xfdca0da4158740a93693441b35809b5bb463e527", + ...> from_address_id: 1, + ...> to_address_id: 2, ...> input: "0x", ...> gas: 7578728, ...> value: 10000000000000000 @@ -303,48 +283,44 @@ defmodule Explorer.Chain.InternalTransaction do output: {"can't be blank for successful call", [validation: :required]} ] - For failed `:create`, `created_contract_code`, `created_contract_address_hash`, and `gas_used` are not allowed to be + For failed `:create`, `created_contract_code`, `created_contract_address_id`, and `gas_used` are not allowed to be set because they come from `result` object, which shouldn't be returned from Nethermind. The changeset will be fixed by `validate_create_error_or_result`, therefore the changeset is still valid. iex> changeset = Explorer.Chain.InternalTransaction.changeset( ...> %Explorer.Chain.InternalTransaction{}, ...> %{ - ...> created_contract_address_hash: "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb4", + ...> created_contract_address_id: 3, ...> created_contract_code: "0x606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", - ...> error: "Bad instruction", - ...> from_address_hash: "0x78a42d3705fb3c26a4b54737a784bf064f0815fb", + ...> error_id: 1, + ...> from_address_id: 1, ...> gas: 3946728, ...> gas_used: 166651, ...> index: 0, ...> init: "0x4bb278f3", ...> trace_address: [], - ...> transaction_hash: "0x3c624bb4852fb5e35a8f45644cec7a486211f6ba89034768a2b763194f22f97d", ...> type: "create", ...> value: 0, ...> block_number: 35, - ...> block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", ...> transaction_index: 0 ...> } iex> ) iex> changeset.valid? true - For successful `:create`, `created_contract_code`, `created_contract_address_hash`, and `gas_used` are required. + For successful `:create`, `created_contract_code`, `created_contract_address_id`, and `gas_used` are required. iex> changeset = Explorer.Chain.InternalTransaction.changeset( ...> %Explorer.Chain.InternalTransaction{}, ...> %{ - ...> from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + ...> from_address_id: 1, ...> gas: 4597044, ...> index: 0, ...> init: "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", ...> trace_address: [], - ...> transaction_hash: "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", ...> type: "create", ...> value: 0, ...> block_number: 35, - ...> block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", ...> transaction_index: 0 ...> } ...> ) @@ -353,7 +329,7 @@ defmodule Explorer.Chain.InternalTransaction do iex> changeset.errors [ created_contract_code: {"can't be blank for successful create", [validation: :required]}, - created_contract_address_hash: {"can't be blank for successful create", [validation: :required]}, + created_contract_address_id: {"can't be blank for successful create", [validation: :required]}, gas_used: {"can't be blank for successful create", [validation: :required]} ] @@ -362,15 +338,13 @@ defmodule Explorer.Chain.InternalTransaction do iex> changeset = Explorer.Chain.InternalTransaction.changeset( ...> %Explorer.Chain.InternalTransaction{}, ...> %{ - ...> from_address_hash: "0xa7542d78b9a0be6147536887e0065f16182d294b", + ...> from_address_id: 1, ...> index: 1, - ...> to_address_hash: "0x59e2e9ecf133649b1a7efc731162ff09d29ca5a5", + ...> to_address_id: 2, ...> trace_address: [0], - ...> transaction_hash: "0xb012b8c53498c669d87d85ed90f57385848b86d3f44ed14b2784ec685d6fda98", ...> type: "selfdestruct", ...> value: 0, ...> block_number: 35, - ...> block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", ...> transaction_index: 0 ...> } ...> ) @@ -379,24 +353,17 @@ defmodule Explorer.Chain.InternalTransaction do """ def changeset(%__MODULE__{} = internal_transaction, attrs \\ %{}) do - base_attributes = ~w(transaction_index index type)a - - all_attributes = - if InternalTransactionHelper.primary_key_updated?() do - [:block_number | base_attributes] - else - [:block_hash, :block_index | base_attributes] - end + base_attributes = ~w(block_number transaction_index index type)a internal_transaction - |> cast(attrs, all_attributes) - |> validate_required(all_attributes) + |> cast(attrs, base_attributes) + |> validate_required(base_attributes) |> type_changeset(attrs) end @doc """ Accepts changes without `:type` but with `:block_number`, if `:type` is defined - works like `changeset`, except allowing `:block_hash` to be undefined. + works like `changeset`, except allowing `:block_number` to be undefined. This is used because the `internal_transactions` runner can derive such values on its own or use empty types to know that a block has no internal transactions. @@ -417,8 +384,8 @@ defmodule Explorer.Chain.InternalTransaction do type_changeset(changeset, attrs, type) end - @call_optional_fields ~w(error error_id gas_used output block_number value)a - @call_required_fields ~w(call_type_enum from_address_hash gas input to_address_hash transaction_hash)a + @call_optional_fields ~w(error_id gas_used output value)a + @call_required_fields ~w(block_number call_type_enum from_address_id gas input to_address_id)a @call_allowed_fields @call_optional_fields ++ @call_required_fields defp type_changeset(changeset, attrs, :call) do @@ -432,11 +399,10 @@ defmodule Explorer.Chain.InternalTransaction do name: :call_has_call_type_enum ) |> check_constraint(:input, message: ~S|can't be blank when type is 'call'|, name: :call_has_input) - |> foreign_key_constraint(:transaction_hash) end - @create_optional_fields ~w(error error_id created_contract_code created_contract_address_hash gas_used block_number value)a - @create_required_fields ~w(from_address_hash gas init transaction_hash)a + @create_optional_fields ~w(error_id created_contract_code created_contract_address_id gas_used value)a + @create_required_fields ~w(block_number from_address_id gas init)a @create_allowed_fields @create_optional_fields ++ @create_required_fields defp type_changeset(changeset, attrs, type) when type in [:create, :create2] do @@ -446,11 +412,10 @@ defmodule Explorer.Chain.InternalTransaction do # TODO consider removing |> validate_create_error_or_result() |> check_constraint(:init, message: ~S|can't be blank when type is 'create'|, name: :create_has_init) - |> foreign_key_constraint(:transaction_hash) end - @selfdestruct_optional_fields ~w(block_number value)a - @selfdestruct_required_fields ~w(from_address_hash to_address_hash transaction_hash type)a + @selfdestruct_optional_fields ~w(value)a + @selfdestruct_required_fields ~w(block_number from_address_id to_address_id type)a @selfdestruct_allowed_fields @selfdestruct_optional_fields ++ @selfdestruct_required_fields defp type_changeset(changeset, attrs, :selfdestruct) do @@ -459,8 +424,8 @@ defmodule Explorer.Chain.InternalTransaction do |> validate_required(@selfdestruct_required_fields) end - @stop_optional_fields ~w(from_address_hash gas gas_used error error_id value)a - @stop_required_fields ~w(block_number transaction_hash type)a + @stop_optional_fields ~w(from_address_id gas gas_used error_id value)a + @stop_required_fields ~w(block_number type)a @stop_allowed_fields @stop_optional_fields ++ @stop_required_fields defp type_changeset(changeset, attrs, :stop) do @@ -496,8 +461,8 @@ defmodule Explorer.Chain.InternalTransaction do # Validates that :call `type` changeset either has an `error` or both `gas_used` and `output` defp validate_call_error_or_result(changeset) do - case {get_field(changeset, :error), get_field(changeset, :error_id)} do - {nil, nil} -> + case get_field(changeset, :error_id) do + nil -> validate_required(changeset, [:gas_used, :output], message: "can't be blank for successful call") _ -> @@ -508,19 +473,19 @@ defmodule Explorer.Chain.InternalTransaction do end end - @create_success_fields ~w(created_contract_code created_contract_address_hash gas_used)a + @create_success_fields ~w(created_contract_code created_contract_address_id gas_used)a - # Validates that :create `type` changeset either has an `:error` or both `:created_contract_code` and - # `:created_contract_address_hash` + # Validates that :create `type` changeset either has an `:error_id` or both `:created_contract_code` and + # `:created_contract_address_id` defp validate_create_error_or_result(changeset) do - case {get_field(changeset, :error), get_field(changeset, :error_id)} do - {nil, nil} -> + case get_field(changeset, :error_id) do + nil -> validate_required(changeset, @create_success_fields, message: "can't be blank for successful create") _ -> changeset |> delete_change(:created_contract_code) - |> delete_change(:created_contract_address_hash) + |> delete_change(:created_contract_address_id) |> delete_change(:gas_used) |> validate_disallowed(@create_success_fields, message: "can't be present for failed create") end @@ -542,52 +507,159 @@ defmodule Explorer.Chain.InternalTransaction do - returns a query considering that the given address_hash can be: to_address_hash, from_address_hash, created_contract_address_hash from internal_transactions' table. """ - def where_address_fields_match(query, address_hash, :to) do - if BackgroundMigrations.get_empty_internal_transactions_data_finished() do - where(query, [t], t.to_address_hash == ^address_hash) - else - where( - query, - [t], - t.to_address_hash == ^address_hash or - (is_nil(t.to_address_hash) and t.created_contract_address_hash == ^address_hash) - ) + def where_address_fields_match(query, address_hash, direction) do + address_id = AddressIdToAddressHash.hash_to_id(address_hash) + + case direction do + :to -> + if BackgroundMigrations.get_empty_internal_transactions_data_finished() do + where_address_match(query, :to_address, address_hash, address_id) + else + where( + query, + [it], + ^to_direction_match_dynamic(address_hash, address_id) + ) + end + + :from -> + where_address_match(query, :from_address, address_hash, address_id) + + :to_address_hash -> + if BackgroundMigrations.get_empty_internal_transactions_data_finished() do + where( + query, + [it], + ^to_address_hash_match_dynamic(address_hash, address_id) + ) + else + where_address_match(query, :to_address, address_hash, address_id) + end + + :from_address_hash -> + where_address_match(query, :from_address, address_hash, address_id) + + :created_contract_address_hash -> + where_address_match(query, :created_contract_address, address_hash, address_id) + + _ -> + where( + query, + [it], + ^all_address_fields_match_dynamic(address_hash, address_id) + ) end end - def where_address_fields_match(query, address_hash, :from) do - where(query, [t], t.from_address_hash == ^address_hash) - end + @doc """ + Adds an address filter for the given internal transaction address field. - def where_address_fields_match(query, address_hash, :to_address_hash) do - if BackgroundMigrations.get_empty_internal_transactions_data_finished() do - where(query, [it], it.to_address_hash == ^address_hash and is_nil(it.created_contract_address_hash)) - else - where(query, [it], it.to_address_hash == ^address_hash) - end + The function accepts either a single address hash or a list of address hashes. + It resolves corresponding address IDs through `Explorer.Chain.AddressIdToAddressHash` + and builds a `where` clause that matches: + + * the migrated `*_address_id` field when mapping entries exist + * the legacy `*_address_hash` field as a fallback for partially migrated rows + + This helper is intended for filtering internal transactions by one of the + logical address roles, such as `:from_address`, `:to_address`, or + `:created_contract_address`. + + ## Parameters + + * `query` - the base query to extend + * `address_field` - the logical internal transaction address field + * `address_hash_or_hashes` - a single address hash or a list of address hashes + + ## Returns + + An `Ecto.Query.t/0` with the address filter applied. + """ + @spec where_address_match( + Ecto.Query.t() | module(), + :from_address | :to_address | :created_contract_address, + Hash.Address.t() | [Hash.Address.t()] + ) :: Ecto.Query.t() + def where_address_match(query, address_field, address_hash_or_hashes) do + address_hashes = List.wrap(address_hash_or_hashes) + address_ids = AddressIdToAddressHash.hashes_to_ids(address_hashes) + + where_address_match(query, address_field, address_hashes, address_ids) end - def where_address_fields_match(query, address_hash, :from_address_hash) do - where(query, [it], it.from_address_hash == ^address_hash) + @spec where_address_match( + Ecto.Query.t() | module(), + :from_address | :to_address | :created_contract_address, + Hash.Address.t() | [Hash.Address.t()], + integer() | [integer()] | nil + ) :: Ecto.Query.t() + def where_address_match(query, address_field, address_hash_or_hashes, address_id_or_ids) do + address_hashes = List.wrap(address_hash_or_hashes) + address_ids = List.wrap(address_id_or_ids) + + where(query, [it], ^address_match_dynamic(address_field, address_hashes, address_ids)) end - def where_address_fields_match(query, address_hash, :created_contract_address_hash) do - where(query, [it], it.created_contract_address_hash == ^address_hash) + defp to_direction_match_dynamic(address_hash, address_id) do + to_match = address_match_dynamic(:to_address, address_hash, address_id) + created_contract_match = address_match_dynamic(:created_contract_address, address_hash, address_id) + + dynamic( + [it], + (^to_match and is_nil(it.created_contract_address_hash) and is_nil(it.created_contract_address_id)) or + (is_nil(it.to_address_hash) and is_nil(it.to_address_id) and ^created_contract_match) + ) end - def where_address_fields_match(query, address_hash, _) do - base_address_where(query, address_hash) + defp to_address_hash_match_dynamic(address_hash, address_id) do + to_match = address_match_dynamic(:to_address, address_hash, address_id) + + dynamic( + [it], + ^to_match and is_nil(it.created_contract_address_hash) and is_nil(it.created_contract_address_id) + ) end - defp base_address_where(query, address_hash) do - where( - query, + defp all_address_fields_match_dynamic(address_hash, address_id) do + to_match = address_match_dynamic(:to_address, address_hash, address_id) + from_match = address_match_dynamic(:from_address, address_hash, address_id) + created_contract_match = address_match_dynamic(:created_contract_address, address_hash, address_id) + + dynamic( [it], - it.to_address_hash == ^address_hash or it.from_address_hash == ^address_hash or - it.created_contract_address_hash == ^address_hash + ^to_match or ^from_match or ^created_contract_match ) end + defp address_match_dynamic(address_field, address_hash_or_hashes, address_id_or_ids) do + address_hashes = List.wrap(address_hash_or_hashes) + address_hash_field = String.to_existing_atom("#{address_field}_hash") + address_ids = List.wrap(address_id_or_ids) + address_id_field = String.to_existing_atom("#{address_field}_id") + + cond do + address_id_or_ids in [[], nil] or not address_ids_indexes_exists?() -> + dynamic([it], field(it, ^address_hash_field) in ^address_hashes) + + address_ids_filled?() -> + dynamic([it], field(it, ^address_id_field) in ^address_ids) + + true -> + dynamic( + [it], + field(it, ^address_id_field) in ^address_ids or field(it, ^address_hash_field) in ^address_hashes + ) + end + end + + defp address_ids_indexes_exists? do + BackgroundMigrations.get_heavy_indexes_create_address_ids_internal_transactions_indexes_finished() + end + + defp address_ids_filled? do + BackgroundMigrations.get_fill_internal_transactions_address_ids_finished() + end + def where_is_different_from_parent_transaction(query) do where( query, @@ -611,12 +683,13 @@ defmodule Explorer.Chain.InternalTransaction do ) "transactions" -> - where( - query, - [it], + query + |> join_transaction_query() + |> where( + [transaction: t], fragment( "(SELECT 1 FROM pending_transaction_operations WHERE transaction_hash = ? LIMIT 1) IS NULL", - it.transaction_hash + t.hash ) ) end @@ -653,6 +726,8 @@ defmodule Explorer.Chain.InternalTransaction do |> order_by([internal_transaction], asc: internal_transaction.index) |> Chain.select_repo(options).all() |> preload_error(options) + |> preload_transaction() + |> preload_addresses(options) end @spec transaction_to_internal_transactions(Hash.Full.t(), [ @@ -676,6 +751,8 @@ defmodule Explorer.Chain.InternalTransaction do |> preload(:block) |> Chain.select_repo(options).all() |> preload_error(options) + |> preload_transaction() + |> preload_addresses(options) end @spec block_to_internal_transactions(non_neg_integer(), [ @@ -702,6 +779,8 @@ defmodule Explorer.Chain.InternalTransaction do |> order_by([internal_transaction], asc: internal_transaction.transaction_index, asc: internal_transaction.index) |> Chain.select_repo(options).all() |> preload_error(options) + |> preload_transaction() + |> preload_addresses(options) end @doc """ @@ -742,7 +821,7 @@ defmodule Explorer.Chain.InternalTransaction do def deduplicate_and_trim_internal_transactions(internal_transactions, paging_options) do internal_transactions |> Enum.uniq_by(fn internal_transaction -> - {internal_transaction.transaction_hash, internal_transaction.index} + {internal_transaction.block_number, internal_transaction.transaction_index, internal_transaction.index} end) |> Enum.take(paging_options.page_size) end @@ -798,6 +877,7 @@ defmodule Explorer.Chain.InternalTransaction do def fetch_from_db_by_address(hash, options) do necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) direction = Keyword.get(options, :direction) + timeout = Keyword.get(options, :timeout) from_block = Chain.from_block(options) to_block = Chain.to_block(options) @@ -829,9 +909,11 @@ defmodule Explorer.Chain.InternalTransaction do |> common_where_and_order(paging_options) |> preload(:block) |> Chain.join_associations(necessity_by_association) - |> Chain.select_repo(options).all() + |> Chain.select_repo(options).all(Helper.maybe_timeout(timeout)) |> deduplicate_and_trim_internal_transactions(paging_options) |> preload_error(options) + |> preload_transaction() + |> preload_addresses(options) else __MODULE__ |> where_nonpending_operation() @@ -841,11 +923,135 @@ defmodule Explorer.Chain.InternalTransaction do |> common_where_limit_order(paging_options) |> preload(:block) |> Chain.join_associations(necessity_by_association) - |> Chain.select_repo(options).all() + |> Chain.select_repo(options).all(Helper.maybe_timeout(timeout)) |> preload_error(options) + |> preload_transaction() + |> preload_addresses(options) end end + @doc """ + Joins the parent transaction to an internal transaction query by + `block_number` and `transaction_index`. + + The join uses an inner join against `Explorer.Chain.Transaction` and matches + each internal transaction with its parent transaction where: + - `internal_transactions.block_number == transactions.block_number` + - `internal_transactions.transaction_index == transactions.index` + - `transactions.block_consensus == true` + + The joined binding is added under the named binding `:transaction`. If the + query already contains that named binding, it is reused and no duplicate join + is added. + + ## Parameters + - `query`: The Ecto query to join the parent transaction into + + ## Returns + - Query with named `:transaction` binding available for further filtering, + selection, or preloading + """ + @spec join_transaction_query(Ecto.Query.t() | module()) :: Ecto.Query.t() + def join_transaction_query(query) do + with_named_binding(query, :transaction, fn query, binding -> + join(query, :inner, [it], t in Transaction, + on: it.block_number == t.block_number and it.transaction_index == t.index and t.block_consensus == true, + as: ^binding + ) + end) + end + + @doc """ + Joins an internal transaction query with the address mapping and resolved + address for the given address field. + + This helper supports both migrated and partially migrated data: + + - it first left joins the `AddressIdToAddressHash` mapping using the + corresponding `*_address_id` field + - it then left joins `Explorer.Chain.Address` using the mapped hash + - if no mapping is found, it falls back to joining by the legacy + `*_address_hash` field + + This allows downstream query code to reference the joined address through + `as/1`, for example `as(:created_contract_address).hash`. + + ## Parameters + + - `query`: The base query to extend + - `address_field`: The logical address field to join. Expected values + include `:from_address`, `:to_address`, or `:created_contract_address` + + ## Returns + + An `Ecto.Query.t/0` with the mapping and address joins added. + """ + @spec join_address_query(Ecto.Query.t() | module(), :from_address | :to_address | :created_contract_address, atom()) :: + Ecto.Query.t() + def join_address_query(query, address_field, join_type \\ :left) do + mapping_binding = String.to_existing_atom("#{address_field}_mapping") + address_binding = address_field + address_id_field = String.to_existing_atom("#{address_field}_id") + address_hash_field = String.to_existing_atom("#{address_field}_hash") + + query + |> with_named_binding(mapping_binding, fn query, binding -> + join(query, join_type, [it], m in AddressIdToAddressHash, + as: ^binding, + on: field(it, ^address_id_field) == m.address_id + ) + end) + |> with_named_binding(address_binding, fn query, binding -> + join(query, join_type, [it], a in Address, + as: ^binding, + on: + a.hash == as(^mapping_binding).address_hash or + (is_nil(as(^mapping_binding).address_hash) and a.hash == field(it, ^address_hash_field)) + ) + end) + end + + @doc """ + Joins an internal transaction query with the address mapping table for the + given address field. + + The helper joins `Explorer.Chain.AddressIdToAddressHash` using the + corresponding `*_address_id` field and exposes the join under the named + binding `:"address_field_mapping"`. + + This is useful when callers need access to the resolved mapping row, while the + main address filtering logic can stay on `internal_transactions.*_address_id` + with a fallback to the legacy `*_address_hash` fields. + + ## Parameters + + - `query`: The base query to extend + - `address_field`: The logical address field to join. Expected values + include `:from_address`, `:to_address`, or `:created_contract_address` + - `join_type`: Ecto join type, defaults to `:left` + + ## Returns + + An `Ecto.Query.t/0` with the mapping join added. + """ + @spec join_address_mapping_query( + Ecto.Query.t() | module(), + :from_address | :to_address | :created_contract_address, + atom() + ) :: + Ecto.Query.t() + def join_address_mapping_query(query, address_field, join_type \\ :left) do + mapping_binding = String.to_existing_atom("#{address_field}_mapping") + address_id_field = String.to_existing_atom("#{address_field}_id") + + with_named_binding(query, mapping_binding, fn query, binding -> + join(query, join_type, [it], m in AddressIdToAddressHash, + as: ^binding, + on: field(it, ^address_id_field) == m.address_id + ) + end) + end + defp common_where_limit_order(query, paging_options) do query |> common_where_and_order(paging_options) @@ -972,11 +1178,9 @@ defmodule Explorer.Chain.InternalTransaction do ) defp for_parent_transaction(query, %Hash{byte_count: unquote(Hash.Full.byte_count())} = hash) do - from( - child in query, - inner_join: transaction in assoc(child, :transaction), - where: transaction.hash == ^hash - ) + query + |> join_transaction_query() + |> where(as(:transaction).hash == ^hash) end # filter by `type` is automatically ignored if `call_type_filter` is not empty, @@ -1018,12 +1222,13 @@ defmodule Explorer.Chain.InternalTransaction do [] _ -> - preloads = - DenormalizationHelper.extend_transaction_preload([ - :block, - [from_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]], - [to_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()]] - ]) + preload_options = + Keyword.merge(options, + address_preloads: [ + from_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()], + to_address: [:scam_badge, :names, :smart_contract, proxy_implementations_association()] + ] + ) __MODULE__ |> where_nonpending_operation() @@ -1036,17 +1241,14 @@ defmodule Explorer.Chain.InternalTransaction do desc: internal_transaction.index ) |> limit(^paging_options.page_size) - |> preload(^preloads) + |> preload(:block) |> Chain.select_repo(options).all() |> preload_error(options) + |> preload_transaction() + |> preload_addresses(preload_options) end end - defp page_block_internal_transaction(query, %PagingOptions{key: %{block_index: block_index}}) do - query - |> where([internal_transaction], internal_transaction.block_index > ^block_index) - end - defp page_block_internal_transaction(query, %PagingOptions{key: %{transaction_index: transaction_index, index: index}}) do query |> where( @@ -1058,23 +1260,16 @@ defmodule Explorer.Chain.InternalTransaction do defp page_block_internal_transaction(query, _), do: query - def internal_transaction_to_block_paging_options(%__MODULE__{ - transaction_index: transaction_index, - index: index, - block_index: block_index - }) do - if InternalTransactionHelper.primary_key_updated?() do - %{"transaction_index" => transaction_index, "index" => index} - else - %{"block_index" => block_index} - end + def internal_transaction_to_block_paging_options(%__MODULE__{transaction_index: transaction_index, index: index}) do + %{"transaction_index" => transaction_index, "index" => index} end defp where_internal_transactions_by_transaction_hash(query, nil), do: query defp where_internal_transactions_by_transaction_hash(query, transaction_hash) do query - |> where([internal_transaction], internal_transaction.transaction_hash == ^transaction_hash) + |> join_transaction_query() + |> where(as(:transaction).hash == ^transaction_hash) end defp maybe_filter_origin_transaction(query, true), do: where_is_different_from_parent_transaction(query) @@ -1106,6 +1301,17 @@ defmodule Explorer.Chain.InternalTransaction do ) end + @doc """ + Builds an `Ecto.Query` to fetch internal transactions by {block_number, transaction_index} pairs + """ + @spec by_block_number_transaction_index_query([{non_neg_integer(), non_neg_integer()}]) :: Ecto.Query.t() + def by_block_number_transaction_index_query(block_number_transaction_index_pairs) do + from( + t in __MODULE__, + where: ^QueryHelper.tuple_in([:block_number, :transaction_index], block_number_transaction_index_pairs) + ) + end + @doc """ Fetches and formats the first internal transaction trace for the given transaction parameters from the EthereumJSONRPC. @@ -1219,6 +1425,16 @@ defmodule Explorer.Chain.InternalTransaction do {:ok, [first_trace_formatted]} end + @doc """ + Finds internal transactions by {block_number, transaction_index} pairs + """ + @spec get_by_block_number_transaction_index([{non_neg_integer(), non_neg_integer()}]) :: [__MODULE__.t()] + def get_by_block_number_transaction_index(block_number_transaction_index_pairs) do + block_number_transaction_index_pairs + |> by_block_number_transaction_index_query() + |> Repo.all() + end + @spec call_type(map()) :: atom() | nil def call_type(%{call_type: call_type, call_type_enum: call_type_enum}) do if BackgroundMigrations.get_empty_internal_transactions_data_finished() do @@ -1237,23 +1453,22 @@ defmodule Explorer.Chain.InternalTransaction do def preload_error(internal_transactions, options) when is_list(internal_transactions) do error_ids = internal_transactions - |> Enum.filter(&is_nil(&1.error)) |> Enum.map(& &1.error_id) |> Enum.uniq() |> Enum.reject(&is_nil/1) - if error_ids == [] do - internal_transactions - else - error_id_to_error_map = + error_id_to_error_map = + if error_ids == [] do + %{} + else TransactionError |> where([te], te.id in ^error_ids) |> select([te], {te.id, te.message}) |> Chain.select_repo(options).all() |> Map.new() + end - Enum.map(internal_transactions, &Map.put(&1, :error, &1.error || error_id_to_error_map[&1.error_id])) - end + Enum.map(internal_transactions, &Map.put(&1, :error, Map.get(&1, :error) || error_id_to_error_map[&1.error_id])) end def preload_error(internal_transaction, options) do @@ -1261,4 +1476,190 @@ defmodule Explorer.Chain.InternalTransaction do |> preload_error(options) |> List.first() end + + @doc """ + Preloads parent transactions for internal transaction records. + + When a list of internal transactions is provided and `transactions` is `nil`, + the function fetches the corresponding parent transactions by + `{block_number, transaction_index}` and attaches each transaction to the + `:transaction` field of the matching internal transaction. + + It also ensures that `:transaction_hash` is populated from the loaded parent + transaction when available, while preserving the existing value if no parent + transaction is found. + + ## Parameters + - `internal_transactions`: A single internal transaction, a list of internal + transactions, or `nil` + - `repo`: The repo used to fetch parent transactions when they are not passed + explicitly + - `transactions`: Optional preloaded parent transactions to reuse instead of + querying the database + + ## Returns + - `nil` when `internal_transactions` is `nil` + - A list of internal transactions with the `:transaction` field populated + - A single internal transaction with the `:transaction` field populated + """ + @spec preload_transaction(__MODULE__.t() | [__MODULE__.t()] | nil, module(), [Transaction.t()] | nil) :: + __MODULE__.t() | [__MODULE__.t()] | nil + def preload_transaction(internal_transactions, repo \\ Repo, transactions \\ nil) + + def preload_transaction(nil, _repo, _transactions), do: nil + + def preload_transaction(internal_transactions, repo, existing_transactions) when is_list(internal_transactions) do + transactions = + case existing_transactions do + nil -> + internal_transactions + |> Enum.map(&{&1.block_number, &1.transaction_index}) + |> Enum.uniq() + |> Transaction.by_block_number_index_query() + |> repo.all() + + transactions -> + transactions + end + + block_number_index_to_transaction_map = Map.new(transactions, &{{&1.block_number, &1.index}, &1}) + + Enum.map(internal_transactions, fn it -> + transaction = block_number_index_to_transaction_map[{it.block_number, it.transaction_index}] + + Map.merge(it, %{ + transaction: transaction, + transaction_hash: (transaction && transaction.hash) || it.transaction_hash + }) + end) + end + + def preload_transaction(internal_transaction, repo, transactions) do + [internal_transaction] + |> preload_transaction(repo, transactions) + |> List.first() + end + + @default_address_preloads [from_address: [], to_address: [], created_contract_address: []] + + @doc """ + Preloads address associations for the given internal transaction or list of + internal transactions. + + After preloading, the function normalizes the result so that + `:from_address`, `:to_address`, and `:created_contract_address` are populated + consistently, and the corresponding `*_address_hash` fields are aligned with + the resolved addresses. + + ## Parameters + + - `internal_transactions`: An `Explorer.Chain.InternalTransaction.t/0`, a + list of internal transactions, `[]`, or `nil` + - `options`: Keyword options. Supports `:address_preloads` to specify nested + preloads for `:from_address`, `:to_address`, and + `:created_contract_address` + - `repo`: The repo module used for preloading. When omitted, it is resolved + via `Explorer.Chain.select_repo/1` + + ## Returns + + - A list of internal transactions with addresses preloaded when the input is + a list + - A single internal transaction with addresses preloaded when the input is a + single struct + """ + @spec preload_addresses([__MODULE__.t()] | __MODULE__.t() | nil, Keyword.t(), module() | nil) :: + [__MODULE__.t()] | __MODULE__.t() | nil + def preload_addresses(internal_transactions, options \\ [], repo \\ nil) + + def preload_addresses([], _options, _repo), do: [] + def preload_addresses(nil, _options, _repo), do: nil + + def preload_addresses(internal_transactions, options, repo) when is_list(internal_transactions) do + preloads = Keyword.merge(@default_address_preloads, Keyword.get(options, :address_preloads, [])) + repo = repo || Chain.select_repo(options) + + indexed_transactions = Enum.with_index(internal_transactions) + + {migrated_indexed, not_migrated_indexed} = + Enum.split_with( + indexed_transactions, + fn {it, _idx} -> + is_nil(it.from_address_hash) and is_nil(it.to_address_hash) and is_nil(it.created_contract_address_hash) + end + ) + + migrated = Enum.map(migrated_indexed, &elem(&1, 0)) + not_migrated = Enum.map(not_migrated_indexed, &elem(&1, 0)) + + not_migrated_preloaded = preload_addresses_for_not_migrated_internal_transactions(not_migrated, preloads, repo) + migrated_preloaded = preload_addresses_for_migrated_internal_transactions(migrated, preloads, repo) + + migrated_with_idx = Enum.zip(migrated_preloaded, Enum.map(migrated_indexed, &elem(&1, 1))) + not_migrated_with_idx = Enum.zip(not_migrated_preloaded, Enum.map(not_migrated_indexed, &elem(&1, 1))) + + (migrated_with_idx ++ not_migrated_with_idx) + |> Enum.sort_by(&elem(&1, 1)) + |> Enum.map(&elem(&1, 0)) + end + + def preload_addresses(internal_transaction, options, repo) do + [internal_transaction] + |> preload_addresses(options, repo) + |> List.first() + end + + defp preload_addresses_for_not_migrated_internal_transactions([], _preloads, _repo), do: [] + + defp preload_addresses_for_not_migrated_internal_transactions(internal_transactions, preloads, repo) do + unified_preloads = + preloads + |> List.wrap() + |> Enum.map(fn + preload when is_atom(preload) -> {String.to_existing_atom("#{preload}_by_hash"), []} + {preload, fields} -> {String.to_existing_atom("#{preload}_by_hash"), fields} + end) + + internal_transactions + |> repo.preload(unified_preloads) + |> Enum.map(fn internal_transaction -> + Enum.reduce( + [ + {:from_address_by_hash, :from_address_hash, :from_address}, + {:to_address_by_hash, :to_address_hash, :to_address}, + {:created_contract_address_by_hash, :created_contract_address_hash, :created_contract_address} + ], + internal_transaction, + fn {source_field, hash_field, address_field}, acc -> + corresponding_address = Map.get(acc, source_field) + + Map.merge(acc, %{ + hash_field => (corresponding_address && corresponding_address.hash) || Map.get(acc, hash_field), + address_field => corresponding_address + }) + end + ) + end) + end + + defp preload_addresses_for_migrated_internal_transactions([], _preloads, _repo), do: [] + + defp preload_addresses_for_migrated_internal_transactions(internal_transactions, preloads, repo) do + internal_transactions + |> repo.preload(preloads) + |> Enum.map(fn internal_transaction -> + Enum.reduce( + [ + {:from_address_hash, :from_address}, + {:to_address_hash, :to_address}, + {:created_contract_address_hash, :created_contract_address} + ], + internal_transaction, + fn {hash_field, address_field}, acc -> + corresponding_address = Map.get(acc, address_field) + Map.put(acc, hash_field, corresponding_address && corresponding_address.hash) + end + ) + end) + end end diff --git a/apps/explorer/lib/explorer/chain/internal_transaction/delete_queue.ex b/apps/explorer/lib/explorer/chain/internal_transaction/delete_queue.ex index 9f7ca330634d..6533e70527d7 100644 --- a/apps/explorer/lib/explorer/chain/internal_transaction/delete_queue.ex +++ b/apps/explorer/lib/explorer/chain/internal_transaction/delete_queue.ex @@ -5,6 +5,7 @@ defmodule Explorer.Chain.InternalTransaction.DeleteQueue do use Explorer.Schema import Ecto.Query + alias Explorer.Chain.{Block, Import} alias Explorer.Repo @primary_key false @@ -33,9 +34,43 @@ defmodule Explorer.Chain.InternalTransaction.DeleteQueue do when accumulator: term() def stream_data(initial, reducer, threshold \\ 600_000) when is_function(reducer, 2) do __MODULE__ + |> join(:inner, [dq], b in Block, on: dq.block_number == b.number and b.refetch_needed == false) |> where([dq], dq.updated_at < ago(^threshold, "millisecond")) |> order_by([dq], desc: :block_number) |> select([dq], dq.block_number) + |> distinct(true) |> Repo.stream_reduce(initial, reducer) end + + @doc """ + Inserts block numbers into the internal transactions delete queue. + + This function builds queue entries for the given block numbers, adds shared + insert and update timestamps, and performs a bulk insert. Existing entries are + ignored because conflicts on the primary key are handled with `:nothing`. + + ## Parameters + + - `block_numbers`: A list of block numbers to enqueue for internal transaction deletion and refetch. + + ## Returns + + - The result of `Repo.safe_insert_all/3`. + + ## Examples + + iex> batch_insert([100, 101, 102]) + {3, nil} + + iex> batch_insert([100, 100]) + {1, nil} + + """ + @spec batch_insert([integer()]) :: {non_neg_integer(), nil | [term()]} + def batch_insert(block_numbers) do + timestamps = Import.timestamps() + params = Enum.map(block_numbers, &Map.put(timestamps, :block_number, &1)) + + Repo.safe_insert_all(__MODULE__, params, timeout: :infinity, on_conflict: :nothing) + end end diff --git a/apps/explorer/lib/explorer/chain/metrics/queries/public_chain_metrics.ex b/apps/explorer/lib/explorer/chain/metrics/queries/public_chain_metrics.ex index 3b1baa59b607..40e961fe2ba0 100644 --- a/apps/explorer/lib/explorer/chain/metrics/queries/public_chain_metrics.ex +++ b/apps/explorer/lib/explorer/chain/metrics/queries/public_chain_metrics.ex @@ -196,7 +196,10 @@ defmodule Explorer.Chain.Metrics.Queries.PublicChainMetrics do internal_transactions_query = if DenormalizationHelper.transactions_denormalization_finished?() do InternalTransaction - |> join(:inner, [it], transaction in assoc(it, :transaction)) + |> InternalTransaction.join_transaction_query() + |> InternalTransaction.join_address_mapping_query(:from_address) + |> InternalTransaction.join_address_mapping_query(:to_address) + |> InternalTransaction.join_address_mapping_query(:created_contract_address) |> where([it, transaction], transaction.block_timestamp >= ago(^update_period_hours(), "hour")) |> where([it, transaction], transaction.block_consensus == true) |> where([it, transaction], transaction.status == ^1) @@ -204,16 +207,19 @@ defmodule Explorer.Chain.Metrics.Queries.PublicChainMetrics do address_hash: fragment( "UNNEST(ARRAY[?, ?, ?])", - it.from_address_hash, - it.to_address_hash, - it.created_contract_address_hash + coalesce(it.from_address_hash, as(:from_address_mapping).address_hash), + coalesce(it.to_address_hash, as(:to_address_mapping).address_hash), + coalesce(it.created_contract_address_hash, as(:created_contract_address_mapping).address_hash) ) }) |> wrapped_union_subquery() else InternalTransaction - |> join(:inner, [it], transaction in assoc(it, :transaction)) - |> join(:inner, [transaction], block in assoc(transaction, :block)) + |> InternalTransaction.join_transaction_query() + |> InternalTransaction.join_address_mapping_query(:from_address) + |> InternalTransaction.join_address_mapping_query(:to_address) + |> InternalTransaction.join_address_mapping_query(:created_contract_address) + |> join(:inner, [_it, transaction], block in assoc(transaction, :block)) |> where([it, transaction, block], transaction.block_timestamp >= ago(^update_period_hours(), "hour")) |> where([it, transaction, block], block.consensus == true) |> where([it, transaction, block], transaction.status == ^1) @@ -221,9 +227,9 @@ defmodule Explorer.Chain.Metrics.Queries.PublicChainMetrics do address_hash: fragment( "UNNEST(ARRAY[?, ?, ?])", - it.from_address_hash, - it.to_address_hash, - it.created_contract_address_hash + coalesce(it.from_address_hash, as(:from_address_mapping).address_hash), + coalesce(it.to_address_hash, as(:to_address_mapping).address_hash), + coalesce(it.created_contract_address_hash, as(:created_contract_address_mapping).address_hash) ) }) |> wrapped_union_subquery() diff --git a/apps/explorer/lib/explorer/chain/polygon_zkevm/batch_transaction.ex b/apps/explorer/lib/explorer/chain/polygon_zkevm/batch_transaction.ex deleted file mode 100644 index 18d8a775a1b3..000000000000 --- a/apps/explorer/lib/explorer/chain/polygon_zkevm/batch_transaction.ex +++ /dev/null @@ -1,37 +0,0 @@ -defmodule Explorer.Chain.PolygonZkevm.BatchTransaction do - @moduledoc "Models a list of transactions related to a batch for zkEVM." - - use Explorer.Schema - - alias Explorer.Chain.{Hash, Transaction} - alias Explorer.Chain.PolygonZkevm.TransactionBatch - - @required_attrs ~w(batch_number hash)a - - @primary_key false - typed_schema "polygon_zkevm_batch_l2_transactions" do - belongs_to(:batch, TransactionBatch, foreign_key: :batch_number, references: :number, type: :integer, null: false) - - belongs_to(:l2_transaction, Transaction, - foreign_key: :hash, - primary_key: true, - references: :hash, - type: Hash.Full, - null: false - ) - - timestamps(null: false) - end - - @doc """ - Validates that the `attrs` are valid. - """ - @spec changeset(Ecto.Schema.t(), map()) :: Ecto.Schema.t() - def changeset(%__MODULE__{} = transactions, attrs \\ %{}) do - transactions - |> cast(attrs, @required_attrs) - |> validate_required(@required_attrs) - |> foreign_key_constraint(:batch_number) - |> unique_constraint(:hash) - end -end diff --git a/apps/explorer/lib/explorer/chain/polygon_zkevm/bridge.ex b/apps/explorer/lib/explorer/chain/polygon_zkevm/bridge.ex deleted file mode 100644 index c0623f8fbf52..000000000000 --- a/apps/explorer/lib/explorer/chain/polygon_zkevm/bridge.ex +++ /dev/null @@ -1,55 +0,0 @@ -defmodule Explorer.Chain.PolygonZkevm.Bridge do - @moduledoc "Models a bridge operation for Polygon zkEVM." - - use Explorer.Schema - - alias Explorer.Chain.{Block, Hash, Token} - alias Explorer.Chain.PolygonZkevm.BridgeL1Token - - @optional_attrs ~w(l1_transaction_hash l2_transaction_hash l1_token_id l2_token_address block_number block_timestamp)a - - @required_attrs ~w(type index amount)a - - @type t :: %__MODULE__{ - type: String.t(), - index: non_neg_integer(), - l1_transaction_hash: Hash.t() | nil, - l2_transaction_hash: Hash.t() | nil, - l1_token: %Ecto.Association.NotLoaded{} | BridgeL1Token.t() | nil, - l1_token_id: non_neg_integer() | nil, - l1_token_address: Hash.Address.t() | nil, - l2_token: %Ecto.Association.NotLoaded{} | Token.t() | nil, - l2_token_address: Hash.Address.t() | nil, - amount: Decimal.t(), - block_number: Block.block_number() | nil, - block_timestamp: DateTime.t() | nil - } - - @primary_key false - schema "polygon_zkevm_bridge" do - field(:type, Ecto.Enum, values: [:deposit, :withdrawal], primary_key: true) - field(:index, :integer, primary_key: true) - field(:l1_transaction_hash, Hash.Full) - field(:l2_transaction_hash, Hash.Full) - belongs_to(:l1_token, BridgeL1Token, foreign_key: :l1_token_id, references: :id, type: :integer) - field(:l1_token_address, Hash.Address) - belongs_to(:l2_token, Token, foreign_key: :l2_token_address, references: :contract_address_hash, type: Hash.Address) - field(:amount, :decimal) - field(:block_number, :integer) - field(:block_timestamp, :utc_datetime_usec) - - timestamps() - end - - @doc """ - Checks that the `attrs` are valid. - """ - @spec changeset(Ecto.Schema.t(), map()) :: Ecto.Schema.t() - def changeset(%__MODULE__{} = operations, attrs \\ %{}) do - operations - |> cast(attrs, @required_attrs ++ @optional_attrs) - |> validate_required(@required_attrs) - |> unique_constraint([:type, :index]) - |> foreign_key_constraint(:l1_token_id) - end -end diff --git a/apps/explorer/lib/explorer/chain/polygon_zkevm/bridge_l1_token.ex b/apps/explorer/lib/explorer/chain/polygon_zkevm/bridge_l1_token.ex deleted file mode 100644 index c3187c28ea40..000000000000 --- a/apps/explorer/lib/explorer/chain/polygon_zkevm/bridge_l1_token.ex +++ /dev/null @@ -1,37 +0,0 @@ -defmodule Explorer.Chain.PolygonZkevm.BridgeL1Token do - @moduledoc "Models a bridge token on L1 for Polygon zkEVM." - - use Explorer.Schema - - alias Explorer.Chain.Hash - - @optional_attrs ~w(decimals symbol)a - - @required_attrs ~w(address)a - - @type t :: %__MODULE__{ - address: Hash.Address.t(), - decimals: non_neg_integer() | nil, - symbol: String.t() | nil - } - - @primary_key {:id, :id, autogenerate: true} - schema "polygon_zkevm_bridge_l1_tokens" do - field(:address, Hash.Address) - field(:decimals, :integer) - field(:symbol, :string) - - timestamps() - end - - @doc """ - Checks that the `attrs` are valid. - """ - @spec changeset(Ecto.Schema.t(), map()) :: Ecto.Schema.t() - def changeset(%__MODULE__{} = tokens, attrs \\ %{}) do - tokens - |> cast(attrs, @required_attrs ++ @optional_attrs) - |> validate_required(@required_attrs) - |> unique_constraint(:id) - end -end diff --git a/apps/explorer/lib/explorer/chain/polygon_zkevm/lifecycle_transaction.ex b/apps/explorer/lib/explorer/chain/polygon_zkevm/lifecycle_transaction.ex deleted file mode 100644 index e1ec0a7abc30..000000000000 --- a/apps/explorer/lib/explorer/chain/polygon_zkevm/lifecycle_transaction.ex +++ /dev/null @@ -1,33 +0,0 @@ -defmodule Explorer.Chain.PolygonZkevm.LifecycleTransaction do - @moduledoc "Models an L1 lifecycle transaction for zkEVM." - - use Explorer.Schema - - alias Explorer.Chain.Hash - alias Explorer.Chain.PolygonZkevm.TransactionBatch - - @required_attrs ~w(id hash is_verify)a - - @primary_key false - typed_schema "polygon_zkevm_lifecycle_l1_transactions" do - field(:id, :integer, primary_key: true, null: false) - field(:hash, Hash.Full, null: false) - field(:is_verify, :boolean, null: false) - - has_many(:sequenced_batches, TransactionBatch, foreign_key: :sequence_id, references: :id) - has_many(:verified_batches, TransactionBatch, foreign_key: :verify_id, references: :id) - - timestamps() - end - - @doc """ - Validates that the `attrs` are valid. - """ - @spec changeset(Ecto.Schema.t(), map()) :: Ecto.Schema.t() - def changeset(%__MODULE__{} = transaction, attrs \\ %{}) do - transaction - |> cast(attrs, @required_attrs) - |> validate_required(@required_attrs) - |> unique_constraint(:id) - end -end diff --git a/apps/explorer/lib/explorer/chain/polygon_zkevm/reader.ex b/apps/explorer/lib/explorer/chain/polygon_zkevm/reader.ex deleted file mode 100644 index f35f39f13e19..000000000000 --- a/apps/explorer/lib/explorer/chain/polygon_zkevm/reader.ex +++ /dev/null @@ -1,396 +0,0 @@ -defmodule Explorer.Chain.PolygonZkevm.Reader do - @moduledoc "Contains read functions for zkevm modules." - - import Ecto.Query, - only: [ - from: 2, - limit: 2, - order_by: 2, - where: 2, - where: 3 - ] - - import Explorer.Chain, only: [select_repo: 1] - - alias Explorer.{Chain, PagingOptions, Repo} - alias Explorer.Chain.PolygonZkevm.{BatchTransaction, Bridge, BridgeL1Token, LifecycleTransaction, TransactionBatch} - alias Explorer.Prometheus.Instrumenter - alias Indexer.Helper - - @doc """ - Reads a batch by its number from database. - If the number is :latest, gets the latest batch from `polygon_zkevm_transaction_batches` table. - Returns {:error, :not_found} in case the batch is not found. - """ - @spec batch(non_neg_integer() | :latest, list()) :: {:ok, map()} | {:error, :not_found} - def batch(number, options \\ []) - - def batch(:latest, options) when is_list(options) do - TransactionBatch - |> order_by(desc: :number) - |> limit(1) - |> select_repo(options).one() - |> case do - nil -> {:error, :not_found} - batch -> {:ok, batch} - end - end - - def batch(number, options) when is_list(options) do - necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) - - TransactionBatch - |> where(number: ^number) - |> Chain.join_associations(necessity_by_association) - |> select_repo(options).one() - |> case do - nil -> {:error, :not_found} - batch -> {:ok, batch} - end - end - - @doc """ - Reads a list of batches from `polygon_zkevm_transaction_batches` table. - """ - @spec batches(list()) :: list() - def batches(options \\ []) do - necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) - - base_query = - from(tb in TransactionBatch, - order_by: [desc: tb.number] - ) - - query = - if Keyword.get(options, :confirmed?, false) do - base_query - |> Chain.join_associations(necessity_by_association) - |> where([tb], not is_nil(tb.sequence_id) and tb.sequence_id > 0) - |> limit(10) - else - paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options()) - - case paging_options do - %PagingOptions{key: {0}} -> - [] - - _ -> - base_query - |> Chain.join_associations(necessity_by_association) - |> page_batches(paging_options) - |> limit(^paging_options.page_size) - end - end - - select_repo(options).all(query) - end - - @doc """ - Reads a list of L2 transaction hashes from `polygon_zkevm_batch_l2_transactions` table. - """ - @spec batch_transactions(non_neg_integer(), list()) :: list() - def batch_transactions(batch_number, options \\ []) do - query = from(bts in BatchTransaction, where: bts.batch_number == ^batch_number) - - select_repo(options).all(query) - end - - @doc """ - Tries to read L1 token data (address, symbol, decimals) for the given addresses - from the database. If the data for an address is not found in Explorer.Chain.PolygonZkevm.BridgeL1Token, - the address is returned in the list inside the tuple (the second item of the tuple). - The first item of the returned tuple contains `L1 token address -> L1 token data` map. - """ - @spec get_token_data_from_db(list()) :: {map(), list()} - def get_token_data_from_db(token_addresses) do - # try to read token symbols and decimals from the database - query = - from( - t in BridgeL1Token, - where: t.address in ^token_addresses, - select: {t.address, t.decimals, t.symbol} - ) - - token_data = - query - |> Repo.all() - |> Enum.reduce(%{}, fn {address, decimals, symbol}, acc -> - token_address = Helper.address_hash_to_string(address, true) - Map.put(acc, token_address, %{symbol: symbol, decimals: decimals}) - end) - - token_addresses_for_rpc = - token_addresses - |> Enum.reject(fn address -> - Map.has_key?(token_data, Helper.address_hash_to_string(address, true)) - end) - - {token_data, token_addresses_for_rpc} - end - - @doc """ - Gets last known L1 item (deposit) from polygon_zkevm_bridge table. - Returns block number and L1 transaction hash bound to that deposit. - If not found, returns zero block number and nil as the transaction hash. - """ - @spec last_l1_item() :: {non_neg_integer(), binary() | nil} - def last_l1_item do - query = - from(b in Bridge, - select: {b.block_number, b.l1_transaction_hash}, - where: b.type == :deposit and not is_nil(b.block_number), - order_by: [desc: b.index], - limit: 1 - ) - - query - |> Repo.one() - |> Kernel.||({0, nil}) - end - - @doc """ - Gets last known L2 item (withdrawal) from polygon_zkevm_bridge table. - Returns block number and L2 transaction hash bound to that withdrawal. - If not found, returns zero block number and nil as the transaction hash. - """ - @spec last_l2_item() :: {non_neg_integer(), binary() | nil} - def last_l2_item do - query = - from(b in Bridge, - select: {b.block_number, b.l2_transaction_hash}, - where: b.type == :withdrawal and not is_nil(b.block_number), - order_by: [desc: b.index], - limit: 1 - ) - - query - |> Repo.one() - |> Kernel.||({0, nil}) - end - - @doc """ - Gets the number of the latest batch with defined verify_id from `polygon_zkevm_transaction_batches` table. - Returns 0 if not found. - """ - @spec last_verified_batch_number() :: non_neg_integer() - def last_verified_batch_number do - query = - from(tb in TransactionBatch, - select: tb.number, - where: not is_nil(tb.verify_id), - order_by: [desc: tb.number], - limit: 1 - ) - - query - |> Repo.one() - |> Kernel.||(0) - end - - @doc """ - Reads a list of L1 transactions by their hashes from `polygon_zkevm_lifecycle_l1_transactions` table. - """ - @spec lifecycle_transactions(list()) :: list() - def lifecycle_transactions([]), do: [] - - def lifecycle_transactions(l1_transaction_hashes) do - query = - from( - lt in LifecycleTransaction, - select: {lt.hash, lt.id}, - where: lt.hash in ^l1_transaction_hashes - ) - - Repo.all(query, timeout: :infinity) - end - - @doc """ - Determines ID of the future lifecycle transaction by reading `polygon_zkevm_lifecycle_l1_transactions` table. - """ - @spec next_id() :: non_neg_integer() - def next_id do - query = - from(lt in LifecycleTransaction, - select: lt.id, - order_by: [desc: lt.id], - limit: 1 - ) - - last_id = - query - |> Repo.one() - |> Kernel.||(0) - - last_id + 1 - end - - @doc """ - Builds `L1 token address -> L1 token id` map for the given token addresses. - The info is taken from Explorer.Chain.PolygonZkevm.BridgeL1Token. - If an address is not in the table, it won't be in the resulting map. - """ - @spec token_addresses_to_ids_from_db(list()) :: map() - def token_addresses_to_ids_from_db(addresses) do - query = from(t in BridgeL1Token, select: {t.address, t.id}, where: t.address in ^addresses) - - query - |> Repo.all(timeout: :infinity) - |> Enum.reduce(%{}, fn {address, id}, acc -> - Map.put(acc, Helper.address_hash_to_string(address), id) - end) - end - - @doc """ - Retrieves a list of Polygon zkEVM deposits (completed and unclaimed) - sorted in descending order of the index. - """ - @spec deposits(list()) :: list() - def deposits(options \\ []) do - paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options()) - - case paging_options do - %PagingOptions{key: {0}} -> - [] - - _ -> - base_query = - from( - b in Bridge, - left_join: t1 in assoc(b, :l1_token), - left_join: t2 in assoc(b, :l2_token), - where: b.type == :deposit and not is_nil(b.l1_transaction_hash), - preload: [l1_token: t1, l2_token: t2], - order_by: [desc: b.index] - ) - - base_query - |> page_deposits_or_withdrawals(paging_options) - |> limit(^paging_options.page_size) - |> select_repo(options).all() - end - end - - @doc """ - Returns a total number of Polygon zkEVM deposits (completed and unclaimed). - """ - @spec deposits_count(list()) :: term() | nil - def deposits_count(options \\ []) do - query = - from( - b in Bridge, - where: b.type == :deposit and not is_nil(b.l1_transaction_hash) - ) - - select_repo(options).aggregate(query, :count, timeout: :infinity) - end - - @doc """ - Retrieves a list of Polygon zkEVM withdrawals (completed and unclaimed) - sorted in descending order of the index. - """ - @spec withdrawals(list()) :: list() - def withdrawals(options \\ []) do - paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options()) - - case paging_options do - %PagingOptions{key: {0}} -> - [] - - _ -> - base_query = - from( - b in Bridge, - left_join: t1 in assoc(b, :l1_token), - left_join: t2 in assoc(b, :l2_token), - where: b.type == :withdrawal and not is_nil(b.l2_transaction_hash), - preload: [l1_token: t1, l2_token: t2], - order_by: [desc: b.index] - ) - - base_query - |> page_deposits_or_withdrawals(paging_options) - |> limit(^paging_options.page_size) - |> select_repo(options).all() - end - end - - @doc """ - Returns a total number of Polygon zkEVM withdrawals (completed and unclaimed). - """ - @spec withdrawals_count(list()) :: term() | nil - def withdrawals_count(options \\ []) do - query = - from( - b in Bridge, - where: b.type == :withdrawal and not is_nil(b.l2_transaction_hash) - ) - - select_repo(options).aggregate(query, :count, timeout: :infinity) - end - - @doc """ - Filters token decimals value (cannot be greater than 0xFF). - """ - @spec sanitize_decimals(non_neg_integer()) :: non_neg_integer() - def sanitize_decimals(decimals) do - if decimals > 0xFF do - 0 - else - decimals - end - end - - @doc """ - Filters token symbol (cannot be longer than 16 characters). - """ - @spec sanitize_symbol(String.t()) :: String.t() - def sanitize_symbol(symbol) do - String.slice(symbol, 0, 16) - end - - defp page_batches(query, %PagingOptions{key: nil}), do: query - - defp page_batches(query, %PagingOptions{key: {number}}) do - from(tb in query, where: tb.number < ^number) - end - - defp page_deposits_or_withdrawals(query, %PagingOptions{key: nil}), do: query - - defp page_deposits_or_withdrawals(query, %PagingOptions{key: {index}}) do - from(b in query, where: b.index < ^index) - end - - @doc """ - Gets information about the latest finalized batch and calculates average time between finalized batches, in seconds. - - ## Parameters - - `options`: A keyword list of options that may include whether to use a replica database. - - ## Returns - - If at least two batches exist: - `{:ok, %{latest_batch_number: integer, latest_batch_timestamp: DateTime.t(), average_batch_time: integer}}` - where: - * latest_batch_number - id of the latest batch in the database. - * latest_batch_timestamp - when the latest batch was committed to L1. - * average_batch_time - average number of seconds between batches for the last 100 batches. - - - If less than two batches exist: `{:error, :not_found}`. - """ - @spec get_latest_batch_info(keyword()) :: {:ok, map()} | {:error, :not_found} - def get_latest_batch_info(options \\ []) do - query = - from(tb in TransactionBatch, - where: not is_nil(tb.timestamp), - order_by: [desc: tb.number], - limit: 100, - select: %{ - number: tb.number, - timestamp: tb.timestamp - } - ) - - items = select_repo(options).all(query) - - Instrumenter.prepare_batch_metric(items) - end -end diff --git a/apps/explorer/lib/explorer/chain/polygon_zkevm/transaction_batch.ex b/apps/explorer/lib/explorer/chain/polygon_zkevm/transaction_batch.ex deleted file mode 100644 index 92ca1dd21527..000000000000 --- a/apps/explorer/lib/explorer/chain/polygon_zkevm/transaction_batch.ex +++ /dev/null @@ -1,47 +0,0 @@ -defmodule Explorer.Chain.PolygonZkevm.TransactionBatch do - @moduledoc "Models a batch of transactions for zkEVM." - - use Explorer.Schema - - alias Explorer.Chain.Hash - alias Explorer.Chain.PolygonZkevm.{BatchTransaction, LifecycleTransaction} - - @optional_attrs ~w(timestamp sequence_id verify_id)a - - @required_attrs ~w(number l2_transactions_count global_exit_root acc_input_hash state_root)a - - @primary_key false - typed_schema "polygon_zkevm_transaction_batches" do - field(:number, :integer, primary_key: true, null: false) - field(:timestamp, :utc_datetime_usec) - field(:l2_transactions_count, :integer) - field(:global_exit_root, Hash.Full) - field(:acc_input_hash, Hash.Full) - field(:state_root, Hash.Full) - - belongs_to(:sequence_transaction, LifecycleTransaction, - foreign_key: :sequence_id, - references: :id, - type: :integer - ) - - belongs_to(:verify_transaction, LifecycleTransaction, foreign_key: :verify_id, references: :id, type: :integer) - - has_many(:l2_transactions, BatchTransaction, foreign_key: :batch_number, references: :number) - - timestamps() - end - - @doc """ - Validates that the `attrs` are valid. - """ - @spec changeset(Ecto.Schema.t(), map()) :: Ecto.Schema.t() - def changeset(%__MODULE__{} = batches, attrs \\ %{}) do - batches - |> cast(attrs, @required_attrs ++ @optional_attrs) - |> validate_required(@required_attrs) - |> foreign_key_constraint(:sequence_id) - |> foreign_key_constraint(:verify_id) - |> unique_constraint(:number) - end -end diff --git a/apps/explorer/lib/explorer/chain/rollup_reorg_monitor_queue.ex b/apps/explorer/lib/explorer/chain/rollup_reorg_monitor_queue.ex deleted file mode 100644 index 7a7365984eda..000000000000 --- a/apps/explorer/lib/explorer/chain/rollup_reorg_monitor_queue.ex +++ /dev/null @@ -1,91 +0,0 @@ -defmodule Explorer.Chain.RollupReorgMonitorQueue do - @moduledoc """ - A module containing (encapsulating) the reorg monitor queue and functions to manage it. - Mostly used by the `Indexer.Fetcher.RollupL1ReorgMonitor` module. - """ - - alias Explorer.BoundQueue - - @doc """ - Pops the number of reorg block from the front of the queue for the specified rollup module. - - ## Parameters - - `module`: The module for which the block number is popped from the queue. - - ## Returns - - The popped block number. - - `nil` if the reorg queue is empty. - """ - @spec reorg_block_pop(module()) :: non_neg_integer() | nil - def reorg_block_pop(module) do - table_name = reorg_table_name(module) - - case BoundQueue.pop_front(reorg_queue_get(table_name)) do - {:ok, {block_number, updated_queue}} -> - :ets.insert(table_name, {:queue, updated_queue}) - block_number - - {:error, :empty} -> - nil - end - end - - @doc """ - Pushes the number of reorg block to the back of the queue for the specified rollup module. - - ## Parameters - - `block_number`: The reorg block number. - - `module`: The module for which the block number is pushed to the queue. - - ## Returns - - Nothing is returned. - """ - @spec reorg_block_push(non_neg_integer(), module()) :: any() - def reorg_block_push(block_number, module) do - table_name = reorg_table_name(module) - {:ok, updated_queue} = BoundQueue.push_back(reorg_queue_get(table_name), block_number) - :ets.insert(table_name, {:queue, updated_queue}) - end - - # Reads a block number queue instance from the ETS table associated with the queue. - # The table name depends on the module name and formed by the `reorg_table_name` function. - # - # ## Parameters - # - `table_name`: The ETS table name of the queue. - # - # ## Returns - # - `BoundQueue` instance for the queue. The queue may be empty (then %BoundQueue{} is returned). - @spec reorg_queue_get(atom()) :: BoundQueue.t(any()) - defp reorg_queue_get(table_name) do - if :ets.whereis(table_name) == :undefined do - :ets.new(table_name, [ - :set, - :named_table, - :public, - read_concurrency: true, - write_concurrency: true - ]) - end - - with info when info != :undefined <- :ets.info(table_name), - [{_, value}] <- :ets.lookup(table_name, :queue) do - value - else - _ -> %BoundQueue{} - end - end - - # Forms an ETS table name for the block number queue for the given module name. - # - # ## Parameters - # - `module`: The module name (instance) for which the ETS table name should be formed. - # - # ## Returns - # - An atom defining the table name. - # - # sobelow_skip ["DOS.BinToAtom"] - @spec reorg_table_name(module()) :: atom() - defp reorg_table_name(module) do - :"#{module}#{:_reorgs}" - end -end diff --git a/apps/explorer/lib/explorer/chain/scroll/reader.ex b/apps/explorer/lib/explorer/chain/scroll/reader.ex index 62b33728f534..2d690310e991 100644 --- a/apps/explorer/lib/explorer/chain/scroll/reader.ex +++ b/apps/explorer/lib/explorer/chain/scroll/reader.ex @@ -300,8 +300,7 @@ defmodule Explorer.Chain.Scroll.Reader do base_query, [p], p.name == ^name and - (p.block_number < ^transaction.block_number or - (p.block_number == ^transaction.block_number and p.transaction_index < ^transaction.index)) + {p.block_number, p.transaction_index} < {^transaction.block_number, ^transaction.index} ) end diff --git a/apps/explorer/lib/explorer/chain/smart_contract.ex b/apps/explorer/lib/explorer/chain/smart_contract.ex index e6c8e35a9fbe..d468f0a63899 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract.ex @@ -692,7 +692,11 @@ defmodule Explorer.Chain.SmartContract do else creation_int_transaction_query = Address.creation_internal_transaction_query(address_hash) - internal_transaction = creation_int_transaction_query |> Repo.one() + internal_transaction = + creation_int_transaction_query + |> Repo.one() + |> InternalTransaction.preload_transaction() + |> InternalTransaction.preload_addresses() case internal_transaction do %{init: init} -> diff --git a/apps/explorer/lib/explorer/chain/token_transfer.ex b/apps/explorer/lib/explorer/chain/token_transfer.ex index 6ca7900a4fc5..fa56a5fdb65e 100644 --- a/apps/explorer/lib/explorer/chain/token_transfer.ex +++ b/apps/explorer/lib/explorer/chain/token_transfer.ex @@ -145,7 +145,7 @@ defmodule Explorer.Chain.TokenTransfer do import Explorer.Chain.Address.Reputation, only: [reputation_association: 0] alias Explorer.Chain - alias Explorer.Chain.{DenormalizationHelper, Hash, Log, TokenTransfer} + alias Explorer.Chain.{DenormalizationHelper, Hash, Log, Token, TokenTransfer} alias Explorer.Chain.SmartContract.Proxy.Models.Implementation alias Explorer.Helper, as: ExplorerHelper alias Explorer.{PagingOptions, QueryHelper, Repo} @@ -794,4 +794,126 @@ defmodule Explorer.Chain.TokenTransfer do true end end + + def token_transfer_amount_for_api(%{ + token: token, + token_type: token_type, + amount: amount, + amounts: amounts, + token_ids: token_ids + }) do + do_token_transfer_amount_for_api(token, token_type, amount, amounts, token_ids) + end + + def token_transfer_amount_for_api(%{token: token, token_type: token_type, amount: amount, token_ids: token_ids}) do + do_token_transfer_amount_for_api(token, token_type, amount, nil, token_ids) + end + + # TODO: remove this clause along with token transfer denormalization + defp do_token_transfer_amount_for_api(%Token{type: "ERC-20"}, nil, nil, nil, _token_ids) do + {:ok, nil} + end + + defp do_token_transfer_amount_for_api(_token, "ERC-20", nil, nil, _token_ids) do + {:ok, nil} + end + + # TODO: remove this clause along with token transfer denormalization + defp do_token_transfer_amount_for_api( + %Token{type: "ERC-20", decimals: decimals}, + nil, + amount, + _amounts, + _token_ids + ) do + {:ok, amount, decimals} + end + + defp do_token_transfer_amount_for_api( + %Token{decimals: decimals}, + "ERC-20", + amount, + _amounts, + _token_ids + ) do + {:ok, amount, decimals} + end + + # TODO: remove this clause along with token transfer denormalization + defp do_token_transfer_amount_for_api(%Token{type: "ZRC-2"}, nil, nil, nil, _token_ids) do + {:ok, nil} + end + + defp do_token_transfer_amount_for_api(_token, "ZRC-2", nil, nil, _token_ids) do + {:ok, nil} + end + + # TODO: remove this clause along with token transfer denormalization + defp do_token_transfer_amount_for_api( + %Token{type: "ZRC-2", decimals: decimals}, + nil, + amount, + _amounts, + _token_ids + ) do + {:ok, amount, decimals} + end + + defp do_token_transfer_amount_for_api( + %Token{decimals: decimals}, + "ZRC-2", + amount, + _amounts, + _token_ids + ) do + {:ok, amount, decimals} + end + + # TODO: remove this clause along with token transfer denormalization + defp do_token_transfer_amount_for_api(%Token{type: "ERC-721"}, nil, _amount, _amounts, _token_ids) do + {:ok, :erc721_instance} + end + + defp do_token_transfer_amount_for_api(_token, "ERC-721", _amount, _amounts, _token_ids) do + {:ok, :erc721_instance} + end + + # TODO: remove this clause along with token transfer denormalization + defp do_token_transfer_amount_for_api( + %Token{type: type, decimals: decimals}, + nil, + amount, + amounts, + token_ids + ) + when type in ["ERC-1155", "ERC-404"] do + if amount do + {:ok, :erc1155_erc404_instance, amount, decimals} + else + {:ok, :erc1155_erc404_instance, amounts, token_ids, decimals} + end + end + + defp do_token_transfer_amount_for_api( + %Token{decimals: decimals}, + type, + amount, + amounts, + token_ids + ) + when type in ["ERC-1155", "ERC-404"] do + if amount do + {:ok, :erc1155_erc404_instance, amount, decimals} + else + {:ok, :erc1155_erc404_instance, amounts, token_ids, decimals} + end + end + + defp do_token_transfer_amount_for_api(%Token{decimals: decimals}, "ERC-7984", _amount, _amounts, _token_ids) do + {:ok, nil, decimals} + end + + defp do_token_transfer_amount_for_api(_token, _token_type, _amount, _amounts, _token_ids) do + nil + end end diff --git a/apps/explorer/lib/explorer/chain/transaction.ex b/apps/explorer/lib/explorer/chain/transaction.ex index 0416b3b1861a..53607cf1cb7b 100644 --- a/apps/explorer/lib/explorer/chain/transaction.ex +++ b/apps/explorer/lib/explorer/chain/transaction.ex @@ -22,14 +22,12 @@ defmodule Explorer.Chain.Transaction.Schema do PendingTransactionOperation, SignedAuthorization, TokenTransfer, - TransactionAction, Wei } alias Explorer.Chain.Arbitrum.BatchBlock, as: ArbitrumBatchBlock alias Explorer.Chain.Arbitrum.BatchTransaction, as: ArbitrumBatchTransaction alias Explorer.Chain.Arbitrum.Message, as: ArbitrumMessage - alias Explorer.Chain.PolygonZkevm.BatchTransaction, as: ZkevmBatchTransaction alias Explorer.Chain.Transaction.{Fork, Status} alias Explorer.Chain.ZkSync.BatchTransaction, as: ZkSyncBatchTransaction @@ -102,29 +100,6 @@ defmodule Explorer.Chain.Transaction.Schema do 2 ) - :polygon_zkevm -> - elem( - quote do - has_one(:zkevm_batch_transaction, ZkevmBatchTransaction, - foreign_key: :hash, - references: :hash - ) - - has_one(:zkevm_batch, through: [:zkevm_batch_transaction, :batch], references: :hash) - - has_one(:zkevm_sequence_transaction, - through: [:zkevm_batch, :sequence_transaction], - references: :hash - ) - - has_one(:zkevm_verify_transaction, - through: [:zkevm_batch, :verify_transaction], - references: :hash - ) - end, - 2 - ) - :zksync -> elem( quote do @@ -253,6 +228,7 @@ defmodule Explorer.Chain.Transaction.Schema do field(:has_error_in_internal_transactions, :boolean) field(:fhe_operations_count, :integer) field(:has_token_transfers, :boolean, virtual: true) + field(:internal_transactions, {:array, :map}, virtual: true) # stability virtual fields field(:transaction_fee_log, :any, virtual: true) @@ -276,17 +252,10 @@ defmodule Explorer.Chain.Transaction.Schema do type: Hash.Address ) - has_many(:internal_transactions, InternalTransaction, foreign_key: :transaction_hash, references: :hash) has_many(:logs, Log, foreign_key: :transaction_hash, references: :hash) has_many(:token_transfers, TokenTransfer, foreign_key: :transaction_hash, references: :hash) - has_many(:transaction_actions, TransactionAction, - foreign_key: :hash, - preload_order: [asc: :log_index], - references: :hash - ) - belongs_to( :to_address, Address, @@ -340,7 +309,7 @@ defmodule Explorer.Chain.Transaction do alias Ecto.Changeset alias EthereumJSONRPC alias EthereumJSONRPC.Transaction, as: EthereumJSONRPCTransaction - alias Explorer.{Chain, Helper, PagingOptions, Repo, SortingHelper} + alias Explorer.{Chain, Helper, PagingOptions, QueryHelper, Repo, SortingHelper} alias Explorer.Chain.{ Address, @@ -883,6 +852,33 @@ defmodule Explorer.Chain.Transaction do do: {:error, :not_a_contract_call} end + # if to_address is a verified contract but smart_contract is not preloaded, + # decode using the pre-built smart_contract_full_abi_map (keyed by address hash) + def decoded_input_data( + %__MODULE__{ + input: %{bytes: data} = input, + to_address: %Address{verified: true, hash: to_address_hash, smart_contract: %NotLoaded{}}, + hash: hash + }, + skip_sig_provider?, + options, + methods_map, + smart_contract_full_abi_map + ) do + full_abi = Map.get(smart_contract_full_abi_map, to_address_hash, []) + + decode_input_data_with_fallback( + data, + full_abi, + input, + hash, + skip_sig_provider?, + options, + methods_map, + smart_contract_full_abi_map + ) + end + # if to_address's smart_contract is nil reduce to the case when to_address is not loaded def decoded_input_data( %__MODULE__{ @@ -989,6 +985,37 @@ defmodule Explorer.Chain.Transaction do ) do full_abi = check_full_abi_cache(smart_contract, smart_contract_full_abi_map, options) + decode_input_data_with_fallback( + data, + full_abi, + input, + hash, + skip_sig_provider?, + options, + methods_map, + smart_contract_full_abi_map + ) + end + + def decoded_input_data( + %__MODULE__{to_address: %{metadata: _, ens_domain_name: _}}, + _, + _, + _, + _ + ), + do: {:error, :no_to_address} + + defp decode_input_data_with_fallback( + data, + full_abi, + input, + hash, + skip_sig_provider?, + options, + methods_map, + smart_contract_full_abi_map + ) do case do_decoded_input_data(data, full_abi, hash) do # In some cases transactions use methods of some unpredictable contracts, so we can try to look up for method in a whole DB {:error, error} when error in [:could_not_decode, :no_matching_function] -> @@ -1018,15 +1045,6 @@ defmodule Explorer.Chain.Transaction do end end - def decoded_input_data( - %__MODULE__{to_address: %{metadata: _, ens_domain_name: _}}, - _, - _, - _, - _ - ), - do: {:error, :no_to_address} - defp decode_function_call_via_sig_provider_wrapper(input, hash, skip_sig_provider?) do case decode_function_call_via_sig_provider(input, hash, skip_sig_provider?) do [] -> @@ -1455,6 +1473,17 @@ defmodule Explorer.Chain.Transaction do ) end + @doc """ + Builds an `Ecto.Query` to fetch transaction by {block_number, index} pairs + """ + @spec by_block_number_index_query([{non_neg_integer(), non_neg_integer()}]) :: Ecto.Query.t() + def by_block_number_index_query(block_number_index_pairs) do + from( + t in __MODULE__, + where: ^QueryHelper.tuple_in([:block_number, :index], block_number_index_pairs) + ) + end + @doc """ Builds an `Ecto.Query` to fetch the last nonce from the given address hash. @@ -1676,6 +1705,7 @@ defmodule Explorer.Chain.Transaction do Chain.paging_options() | Chain.necessity_by_association_option() | {:sorting, SortingHelper.sorting_params()} + | Chain.timeout_option() ], boolean() ) :: [__MODULE__.t()] @@ -1695,6 +1725,7 @@ defmodule Explorer.Chain.Transaction do necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) old_ui? = old_ui? || is_tuple(Keyword.get(options, :paging_options, Chain.default_paging_options()).key) sorting_options = Keyword.get(options, :sorting, []) + timeout = Keyword.get(options, :timeout) options |> address_to_transactions_tasks_query(false, old_ui?) @@ -1702,7 +1733,11 @@ defmodule Explorer.Chain.Transaction do |> Chain.join_associations(necessity_by_association) |> put_has_token_transfers_to_transaction(old_ui?) |> matching_address_queries_list(direction, address_hash, sorting_options) - |> Enum.map(fn query -> Task.async(fn -> Chain.select_repo(options).all(query) end) end) + |> Enum.map(fn query -> + Task.async(fn -> + Chain.select_repo(options).all(query, Helper.maybe_timeout(timeout)) + end) + end) end @doc """ @@ -3004,6 +3039,72 @@ defmodule Explorer.Chain.Transaction do |> Repo.all() end + @doc """ + Finds transactions by {block_number, index} pairs + """ + @spec get_transactions_by_block_number_index([{non_neg_integer(), non_neg_integer()}]) :: [__MODULE__.t()] + def get_transactions_by_block_number_index(block_number_index_pairs) do + block_number_index_pairs + |> by_block_number_index_query() + |> Repo.all() + end + + @doc """ + Preloads internal transactions for parent transaction records. + + When a list of transactions is provided, the function fetches all matching + internal transactions by `{block_number, transaction_index}`, applies the + requested `preloads`, and attaches the resulting list to the + `:internal_transactions` field of each parent transaction. + + The loaded internal transactions are also passed through + `InternalTransaction.preload_transaction/3` so that each internal transaction + has its parent `:transaction` populated from the already available + transaction list. + + ## Parameters + - `transactions`: A single transaction or a list of transactions + - `preloads`: Optional associations to preload for each internal transaction + - `repo`: The repo used to fetch and preload internal transactions + + ## Returns + - A list of transactions with the `:internal_transactions` field populated + - A single transaction with the `:internal_transactions` field populated + """ + @spec preload_internal_transactions( + __MODULE__.t() | [__MODULE__.t()], + list(), + module() + ) :: __MODULE__.t() | [__MODULE__.t()] + def preload_internal_transactions(transactions, address_preloads \\ [], repo \\ Repo) + + def preload_internal_transactions(transactions, address_preloads, repo) when is_list(transactions) do + block_number_index_to_internal_transactions_map = + transactions + |> Enum.map(&{&1.block_number, &1.index}) + |> Enum.uniq() + |> InternalTransaction.by_block_number_transaction_index_query() + |> repo.all() + |> InternalTransaction.preload_transaction(repo, transactions) + |> InternalTransaction.preload_addresses([address_preloads: address_preloads], repo) + |> Enum.group_by(&{&1.block_number, &1.transaction_index}) + + Enum.map( + transactions, + &Map.put( + &1, + :internal_transactions, + block_number_index_to_internal_transactions_map[{&1.block_number, &1.index}] || [] + ) + ) + end + + def preload_internal_transactions(transaction, address_preloads, repo) do + [transaction] + |> preload_internal_transactions(address_preloads, repo) + |> List.first() + end + @doc """ Checks if a `t:Explorer.Chain.Transaction.t/0` with the given `hash` exists. diff --git a/apps/explorer/lib/explorer/chain/transaction_action.ex b/apps/explorer/lib/explorer/chain/transaction_action.ex deleted file mode 100644 index c1514615e76b..000000000000 --- a/apps/explorer/lib/explorer/chain/transaction_action.ex +++ /dev/null @@ -1,82 +0,0 @@ -defmodule Explorer.Chain.TransactionAction do - @moduledoc "Models transaction action." - - use Explorer.Schema - - alias Explorer.Chain.{ - Hash, - Transaction - } - - @required_attrs ~w(hash protocol data type log_index)a - @supported_protocols [:uniswap_v3, :opensea_v1_1, :wrapping, :approval, :zkbob, :aave_v3] - @supported_types [ - :mint_nft, - :mint, - :burn, - :collect, - :swap, - :sale, - :cancel, - :transfer, - :wrap, - :unwrap, - :approve, - :revoke, - :withdraw, - :deposit, - :borrow, - :supply, - :repay, - :flash_loan, - :enable_collateral, - :disable_collateral, - :liquidation_call - ] - @typedoc """ - * `hash` - transaction hash - * `protocol` - name of the action protocol (see possible values for Enum of the db table field) - * `data` - transaction action details (json formatted) - * `type` - type of the action protocol (see possible values for Enum of the db table field) - * `log_index` - index of the action for sorting (taken from log.index) - """ - @primary_key false - typed_schema "transaction_actions" do - field(:protocol, Ecto.Enum, values: @supported_protocols, null: false) - field(:data, :map, null: false) - - field(:type, Ecto.Enum, - values: @supported_types, - null: false - ) - - field(:log_index, :integer, primary_key: true, null: false) - - belongs_to(:transaction, Transaction, - foreign_key: :hash, - primary_key: true, - references: :hash, - type: Hash.Full, - null: false - ) - - timestamps() - end - - def changeset(%__MODULE__{} = transaction_actions, attrs \\ %{}) do - transaction_actions - |> cast(attrs, @required_attrs) - |> validate_required(@required_attrs) - |> foreign_key_constraint(:hash) - end - - @spec supported_protocols() :: [atom()] - def supported_protocols do - @supported_protocols - end - - @spec supported_types() :: [atom()] - def supported_types do - @supported_types - end -end diff --git a/apps/explorer/lib/explorer/chain/transaction_error.ex b/apps/explorer/lib/explorer/chain/transaction_error.ex index ea03d7bf5fa9..e8a044dac26e 100644 --- a/apps/explorer/lib/explorer/chain/transaction_error.ex +++ b/apps/explorer/lib/explorer/chain/transaction_error.ex @@ -17,9 +17,31 @@ defmodule Explorer.Chain.TransactionError do cast(transaction_error, attrs, [:message]) end + def id_to_error(nil), do: nil + + def id_to_error(id) do + __MODULE__ + |> where([te], te.id == ^id) + |> select([te], te.message) + |> Repo.one() + end + + def find_or_create(error_message) do + [error_message] + |> find_or_create_multiple() + |> Map.get(error_message) + end + def find_or_create_multiple(error_messages) do - filtered_error_messages = Enum.uniq(error_messages) + error_messages + |> Enum.uniq() + |> Enum.reject(&is_nil/1) + |> do_find_or_create_multiple() + end + + defp do_find_or_create_multiple([]), do: %{} + defp do_find_or_create_multiple(filtered_error_messages) do existing_map = __MODULE__ |> where([te], te.message in ^filtered_error_messages) diff --git a/apps/explorer/lib/explorer/etherscan.ex b/apps/explorer/lib/explorer/etherscan.ex index d1fbfbce38ea..d4ca63255743 100644 --- a/apps/explorer/lib/explorer/etherscan.ex +++ b/apps/explorer/lib/explorer/etherscan.ex @@ -83,20 +83,15 @@ defmodule Explorer.Etherscan do @internal_transaction_fields ~w( block_number - from_address_hash - to_address_hash - transaction_hash transaction_index index value - created_contract_address_hash input type call_type call_type_enum gas gas_used - error error_id )a @@ -130,28 +125,44 @@ defmodule Explorer.Etherscan do query = if DenormalizationHelper.transactions_denormalization_finished?() do - from( - it in InternalTransaction, - inner_join: transaction in assoc(it, :transaction), - where: not is_nil(transaction.block_hash), - where: it.transaction_hash == ^transaction_hash, - limit: 10_000, - select: - merge(map(it, ^@internal_transaction_fields), %{ - block_timestamp: transaction.block_timestamp - }) + InternalTransaction + |> InternalTransaction.join_transaction_query() + |> InternalTransaction.join_address_mapping_query(:from_address) + |> InternalTransaction.join_address_mapping_query(:to_address) + |> InternalTransaction.join_address_mapping_query(:created_contract_address) + |> where(not is_nil(as(:transaction).block_hash)) + |> where(as(:transaction).hash == ^transaction_hash) + |> limit(10_000) + |> select( + [it], + merge(map(it, ^@internal_transaction_fields), %{ + block_timestamp: as(:transaction).block_timestamp, + transaction_hash: as(:transaction).hash, + from_address_hash: coalesce(it.from_address_hash, as(:from_address_mapping).address_hash), + to_address_hash: coalesce(it.to_address_hash, as(:to_address_mapping).address_hash), + created_contract_address_hash: + coalesce(it.created_contract_address_hash, as(:created_contract_address_mapping).address_hash) + }) ) else - from( - it in InternalTransaction, - inner_join: t in assoc(it, :transaction), - inner_join: b in assoc(t, :block), - where: it.transaction_hash == ^transaction_hash, - limit: 10_000, - select: - merge(map(it, ^@internal_transaction_fields), %{ - block_timestamp: b.timestamp - }) + InternalTransaction + |> InternalTransaction.join_transaction_query() + |> InternalTransaction.join_address_mapping_query(:from_address) + |> InternalTransaction.join_address_mapping_query(:to_address) + |> InternalTransaction.join_address_mapping_query(:created_contract_address) + |> join(:inner, [it, t], b in assoc(t, :block), as: :block) + |> where(as(:transaction).hash == ^transaction_hash) + |> limit(10_000) + |> select( + [it], + merge(map(it, ^@internal_transaction_fields), %{ + block_timestamp: as(:block).timestamp, + transaction_hash: as(:transaction).hash, + from_address_hash: coalesce(it.from_address_hash, as(:from_address_mapping).address_hash), + to_address_hash: coalesce(it.to_address_hash, as(:to_address_mapping).address_hash), + created_contract_address_hash: + coalesce(it.created_contract_address_hash, as(:created_contract_address_mapping).address_hash) + }) ) end @@ -216,6 +227,7 @@ defmodule Explorer.Etherscan do |> limit(^options.page_size) |> Repo.replica().all() |> InternalTransaction.preload_error() + |> InternalTransaction.preload_transaction() end def list_internal_transactions( @@ -234,65 +246,99 @@ defmodule Explorer.Etherscan do |> where_end_block_match_internal_transaction(options) |> Repo.replica().all() |> InternalTransaction.preload_error() + |> InternalTransaction.preload_transaction() end defp consensus_internal_transactions_with_transactions_and_blocks_query(options) do if DenormalizationHelper.transactions_denormalization_finished?() do - from( - it in InternalTransaction, - as: :internal_transaction, - inner_join: transaction in assoc(it, :transaction), - where: not is_nil(transaction.block_hash), - where: transaction.block_consensus == true, - order_by: [ + InternalTransaction + |> from(as: :internal_transaction) + |> InternalTransaction.join_transaction_query() + |> InternalTransaction.join_address_mapping_query(:from_address) + |> InternalTransaction.join_address_mapping_query(:to_address) + |> InternalTransaction.join_address_mapping_query(:created_contract_address) + |> where(not is_nil(as(:transaction).block_hash)) + |> where(as(:transaction).block_consensus == true) + |> order_by( + [it], + [ {^options.order_by_direction, it.block_number}, {^options.order_by_direction, it.transaction_index}, {^options.order_by_direction, it.index} - ], - limit: ^options_to_limit_for_inner_query(options), - select: - merge(map(it, ^@internal_transaction_fields), %{ - block_timestamp: transaction.block_timestamp - }) + ] + ) + |> limit(^options_to_limit_for_inner_query(options)) + |> select( + [it, transaction], + merge(map(it, ^@internal_transaction_fields), %{ + block_timestamp: transaction.block_timestamp, + transaction_hash: transaction.hash, + from_address_hash: coalesce(it.from_address_hash, as(:from_address_mapping).address_hash), + to_address_hash: coalesce(it.to_address_hash, as(:to_address_mapping).address_hash), + created_contract_address_hash: + coalesce(it.created_contract_address_hash, as(:created_contract_address_mapping).address_hash) + }) ) else - from( - it in InternalTransaction, - as: :internal_transaction, - inner_join: t in assoc(it, :transaction), - inner_join: b in assoc(t, :block), - where: b.consensus == true, - order_by: [ + InternalTransaction + |> from(as: :internal_transaction) + |> InternalTransaction.join_transaction_query() + |> InternalTransaction.join_address_mapping_query(:from_address) + |> InternalTransaction.join_address_mapping_query(:to_address) + |> InternalTransaction.join_address_mapping_query(:created_contract_address) + |> join(:inner, [_it, t], b in assoc(t, :block), as: :block) + |> where(as(:block).consensus == true) + |> order_by( + [it], + [ {^options.order_by_direction, it.block_number}, {^options.order_by_direction, it.transaction_index}, {^options.order_by_direction, it.index} - ], - limit: ^options_to_limit_for_inner_query(options), - select: - merge(map(it, ^@internal_transaction_fields), %{ - block_timestamp: b.timestamp - }) + ] + ) + |> limit(^options_to_limit_for_inner_query(options)) + |> select( + [it], + merge(map(it, ^@internal_transaction_fields), %{ + block_timestamp: as(:block).timestamp, + transaction_hash: as(:transaction).hash, + from_address_hash: coalesce(it.from_address_hash, as(:from_address_mapping).address_hash), + to_address_hash: coalesce(it.to_address_hash, as(:to_address_mapping).address_hash), + created_contract_address_hash: + coalesce(it.created_contract_address_hash, as(:created_contract_address_mapping).address_hash) + }) ) end end defp internal_transactions_query(options, consensus_blocks) do - from( - it in InternalTransaction, - as: :internal_transaction, - inner_join: block in subquery(consensus_blocks), - on: it.block_number == block.number, - order_by: [ + InternalTransaction + |> from(as: :internal_transaction) + |> InternalTransaction.join_transaction_query() + |> InternalTransaction.join_address_mapping_query(:from_address) + |> InternalTransaction.join_address_mapping_query(:to_address) + |> InternalTransaction.join_address_mapping_query(:created_contract_address) + |> join(:inner, [it], block in subquery(consensus_blocks), on: it.block_number == block.number, as: :block) + |> order_by( + [it], + [ {^options.order_by_direction, it.block_number}, {^options.order_by_direction, it.transaction_index}, {^options.order_by_direction, it.index} - ], - limit: ^options.page_size, - offset: ^options_to_offset(options), - select: - merge(map(it, ^@internal_transaction_fields), %{ - block_timestamp: block.timestamp - }) + ] + ) + |> limit(^options.page_size) + |> offset(^options_to_offset(options)) + |> select( + [it], + merge(map(it, ^@internal_transaction_fields), %{ + block_timestamp: as(:block).timestamp, + transaction_hash: as(:transaction).hash, + from_address_hash: coalesce(it.from_address_hash, as(:from_address_mapping).address_hash), + to_address_hash: coalesce(it.to_address_hash, as(:to_address_mapping).address_hash), + created_contract_address_hash: + coalesce(it.created_contract_address_hash, as(:created_contract_address_mapping).address_hash) + }) ) end @@ -604,17 +650,19 @@ defmodule Explorer.Etherscan do end defp list_erc1155_token_transfers(address_hash, contract_address_hash, options) do - "ERC-1155" - |> base_token_transfers_query(address_hash, contract_address_hash, options) + base_query = build_erc1155_base_query(address_hash, contract_address_hash, options) + + from(tt in {"base", TokenTransfer}) + |> with_cte("base", as: ^base_query, materialized: true) |> join( :inner, - [token_transfer], + [tt], unnest in fragment( - "LATERAL (SELECT unnest(?) AS token_id, unnest(COALESCE(?, ARRAY[?])) AS amount, GENERATE_SERIES(0, COALESCE(ARRAY_LENGTH(?, 1), 0) - 1) as index_in_batch)", - token_transfer.token_ids, - token_transfer.amounts, - token_transfer.amount, - token_transfer.amounts + "LATERAL (SELECT unnest(?) AS token_id, unnest(COALESCE(?, ARRAY[?])) AS amount, GENERATE_SERIES(0, COALESCE(ARRAY_LENGTH(?, 1), 0) - 1) AS index_in_batch)", + tt.token_ids, + tt.amounts, + tt.amount, + tt.amounts ), as: :unnest, on: true @@ -624,13 +672,53 @@ defmodule Explorer.Etherscan do amount: fragment("?::numeric", unnest.amount), index_in_batch: fragment("?::integer", unnest.index_in_batch) }) - |> order_by( - [unnest: unnest], + |> order_by([tt, unnest: unnest], [ + {^options.order_by_direction, tt.block_number}, + {^options.order_by_direction, tt.log_index}, {^options.order_by_direction, unnest.index_in_batch} - ) + ]) + |> limit(^options.page_size) + |> offset(^options_to_offset(options)) + |> maybe_preload_entities() |> Repo.replica().all() end + defp build_erc1155_base_query(nil, contract_address_hash, options) do + TokenTransfer.only_consensus_transfers_query() + |> TokenTransfer.maybe_filter_by_token_type("ERC-1155") + |> where_contract_address_match(contract_address_hash) + |> where_start_block_match_tt(options) + |> where_end_block_match_tt(options) + |> order_by([tt], [ + {^options.order_by_direction, tt.block_number}, + {^options.order_by_direction, tt.log_index} + ]) + end + + defp build_erc1155_base_query(address_hash, contract_address_hash, options) do + inner = + TokenTransfer.only_consensus_transfers_query() + |> TokenTransfer.maybe_filter_by_token_type("ERC-1155") + |> where_contract_address_match(contract_address_hash) + |> where_start_block_match_tt(options) + |> where_end_block_match_tt(options) + |> order_by([tt], [ + {^options.order_by_direction, tt.block_number}, + {^options.order_by_direction, tt.log_index} + ]) + |> limit(^options_to_limit_for_inner_query(options)) + + from_query = inner |> where([tt], tt.from_address_hash == ^address_hash) |> Chain.wrapped_union_subquery() + to_query = inner |> where([tt], tt.to_address_hash == ^address_hash) |> Chain.wrapped_union_subquery() + + union(from_query, ^to_query) + |> Chain.wrapped_union_subquery() + |> order_by([q], [ + {^options.order_by_direction, q.block_number}, + {^options.order_by_direction, q.log_index} + ]) + end + defp list_erc404_token_transfers(address_hash, contract_address_hash, options) do "ERC-404" |> base_token_transfers_query(address_hash, contract_address_hash, options) @@ -643,17 +731,43 @@ defmodule Explorer.Etherscan do |> Repo.replica().all() end - defp base_token_transfers_query(transfers_type, address_hash, contract_address_hash, options) do + defp base_token_transfers_query(transfers_type, nil, contract_address_hash, options) do TokenTransfer.only_consensus_transfers_query() |> TokenTransfer.maybe_filter_by_token_type(transfers_type) |> where_contract_address_match(contract_address_hash) - |> where_address_match_token_transfer(address_hash) + |> where_start_block_match_tt(options) + |> where_end_block_match_tt(options) |> order_by([tt], [ {^options.order_by_direction, tt.block_number}, {^options.order_by_direction, tt.log_index} ]) - |> where_start_block_match_tt(options) - |> where_end_block_match_tt(options) + |> limit(^options.page_size) + |> offset(^options_to_offset(options)) + |> maybe_preload_entities() + end + + defp base_token_transfers_query(transfers_type, address_hash, contract_address_hash, options) do + inner_query = + TokenTransfer.only_consensus_transfers_query() + |> TokenTransfer.maybe_filter_by_token_type(transfers_type) + |> where_contract_address_match(contract_address_hash) + |> where_start_block_match_tt(options) + |> where_end_block_match_tt(options) + |> order_by([tt], [ + {^options.order_by_direction, tt.block_number}, + {^options.order_by_direction, tt.log_index} + ]) + |> limit(^options_to_limit_for_inner_query(options)) + + from_query = inner_query |> where([tt], tt.from_address_hash == ^address_hash) |> Chain.wrapped_union_subquery() + to_query = inner_query |> where([tt], tt.to_address_hash == ^address_hash) |> Chain.wrapped_union_subquery() + + union(from_query, ^to_query) + |> Chain.wrapped_union_subquery() + |> order_by([q], [ + {^options.order_by_direction, q.block_number}, + {^options.order_by_direction, q.log_index} + ]) |> limit(^options.page_size) |> offset(^options_to_offset(options)) |> maybe_preload_entities() @@ -751,12 +865,6 @@ defmodule Explorer.Etherscan do where(query, [tt], tt.token_contract_address_hash == ^contract_address_hash) end - defp where_address_match_token_transfer(query, nil), do: query - - defp where_address_match_token_transfer(query, address_hash) do - where(query, [tt], tt.from_address_hash == ^address_hash or tt.to_address_hash == ^address_hash) - end - defp options_to_offset(options), do: (options.page_number - 1) * options.page_size defp options_to_limit_for_inner_query(options), do: options.page_number * options.page_size diff --git a/apps/explorer/lib/explorer/graphql.ex b/apps/explorer/lib/explorer/graphql.ex index 93ba3b1d8416..138ea3f86f3f 100644 --- a/apps/explorer/lib/explorer/graphql.ex +++ b/apps/explorer/lib/explorer/graphql.ex @@ -43,11 +43,18 @@ defmodule Explorer.GraphQL do Returns an internal transaction for a given transaction hash and index. """ @spec get_internal_transaction(map()) :: {:ok, InternalTransaction.t()} | {:error, String.t()} - def get_internal_transaction(%{transaction_hash: _, index: _} = clauses) do - if internal_transaction = Repo.replica().get_by(InternalTransaction.where_nonpending_operation(), clauses) do - {:ok, internal_transaction} - else - {:error, "Internal transaction not found."} + def get_internal_transaction(%{transaction_hash: transaction_hash, index: index}) do + InternalTransaction + |> InternalTransaction.join_transaction_query() + |> where([it], as(:transaction).hash == ^transaction_hash) + |> where([it], it.index == ^index) + |> InternalTransaction.where_nonpending_operation() + |> Repo.replica().one() + |> InternalTransaction.preload_transaction(Repo.replica()) + |> InternalTransaction.preload_addresses([], Repo.replica()) + |> case do + nil -> {:error, "Internal transaction not found."} + internal_transaction -> {:ok, internal_transaction} end end @@ -60,15 +67,10 @@ defmodule Explorer.GraphQL do def transaction_to_internal_transactions_query(%Transaction{ hash: %Hash{byte_count: unquote(Hash.Full.byte_count())} = hash }) do - query = - from( - it in InternalTransaction, - inner_join: t in assoc(it, :transaction), - order_by: [asc: it.index], - where: it.transaction_hash == ^hash - ) - - query + InternalTransaction + |> InternalTransaction.join_transaction_query() + |> where([it], as(:transaction).hash == ^hash) + |> order_by([it], it.index) |> InternalTransaction.where_nonpending_operation() |> InternalTransaction.where_is_different_from_parent_transaction() end diff --git a/apps/explorer/lib/explorer/helper.ex b/apps/explorer/lib/explorer/helper.ex index 3ee9b893fa2b..f778619ff829 100644 --- a/apps/explorer/lib/explorer/helper.ex +++ b/apps/explorer/lib/explorer/helper.ex @@ -255,7 +255,7 @@ defmodule Explorer.Helper do """ @spec maybe_hide_scam_addresses_with_select(nil | Ecto.Query.t(), atom(), [ Chain.paging_options() | Chain.api?() | Chain.show_scam_tokens?() - ]) :: Ecto.Query.t() + ]) :: Ecto.Query.t() | nil def maybe_hide_scam_addresses_with_select(nil, _address_hash_key, _options), do: nil def maybe_hide_scam_addresses_with_select(query, address_hash_key, options) do @@ -283,13 +283,22 @@ defmodule Explorer.Helper do @doc """ Conditionally hides scam addresses in the given query, does not select the reputation field. + + Accepts two forms for the address hash locator: + - `atom()` — a field key on the query's root binding, e.g. `:to_address_hash`. + - `{binding, field}` tuple — a named binding already present in the query plus its hash + field, e.g. `{:to_address, :hash}`. Use this form when the addresses table is already + joined; it lets the query planner use the binding's join statistics for better index + selection. """ - @spec maybe_hide_scam_addresses(nil | Ecto.Query.t(), atom(), [ - Chain.paging_options() | Chain.api?() | Chain.show_scam_tokens?() - ]) :: Ecto.Query.t() + @spec maybe_hide_scam_addresses( + nil | Ecto.Query.t(), + atom() | {atom(), atom()}, + [Chain.paging_options() | Chain.api?() | Chain.show_scam_tokens?()] + ) :: Ecto.Query.t() | nil def maybe_hide_scam_addresses(nil, _address_hash_key, _options), do: nil - def maybe_hide_scam_addresses(query, address_hash_key, options) do + def maybe_hide_scam_addresses(query, address_hash_key, options) when is_atom(address_hash_key) do cond do Application.get_env(:block_scout_web, :hide_scam_addresses) && !options[:show_scam_tokens?] -> query @@ -304,6 +313,24 @@ defmodule Explorer.Helper do end end + def maybe_hide_scam_addresses(query, {named_binding, hash_field}, options) do + cond do + Application.get_env(:block_scout_web, :hide_scam_addresses) && !options[:show_scam_tokens?] -> + query + |> join(:left, [{^named_binding, address}], sabm in ScamBadgeToAddress, + as: :sabm, + on: sabm.address_hash == field(address, ^hash_field) + ) + |> where([sabm: sabm], is_nil(sabm.address_hash)) + + Application.get_env(:block_scout_web, :hide_scam_addresses) && options[:show_scam_tokens?] -> + query + + true -> + query + end + end + @doc """ Conditionally hides scam addresses in the given query for token transfers. If query already has a named binding :token, it MUST be an inner join with the token table. @@ -327,7 +354,7 @@ defmodule Explorer.Helper do """ @spec maybe_hide_scam_addresses_for_token_transfers(nil | Ecto.Query.t(), [ Chain.paging_options() | Chain.api?() | Chain.show_scam_tokens?() - ]) :: Ecto.Query.t() + ]) :: Ecto.Query.t() | nil def maybe_hide_scam_addresses_for_token_transfers(nil, _options), do: nil def maybe_hide_scam_addresses_for_token_transfers(query, options) do @@ -362,7 +389,7 @@ defmodule Explorer.Helper do """ @spec maybe_hide_scam_addresses_for_search(nil | Ecto.Query.t(), atom(), [ Chain.paging_options() | Chain.api?() | Chain.show_scam_tokens?() - ]) :: Ecto.Query.t() + ]) :: Ecto.Query.t() | nil def maybe_hide_scam_addresses_for_search(nil, _address_hash_key, _options), do: nil def maybe_hide_scam_addresses_for_search(query, address_hash_key, options) do @@ -758,4 +785,23 @@ defmodule Explorer.Helper do key end end + + @doc """ + Returns a keyword list with a timeout option if a timeout is provided. + + This helper is needed for Repo calls, since passing `timeout: nil` is not supported. + If `timeout` is `nil`, returns an empty keyword list. Otherwise, returns + a keyword list with the `:timeout` key set to the given value. + + ## Parameters + + - timeout: The timeout value to use, or `nil`. + + ## Returns + + - A keyword list with the `:timeout` key, or an empty keyword list. + """ + @spec maybe_timeout(timeout() | nil) :: keyword() + def maybe_timeout(nil), do: [] + def maybe_timeout(timeout), do: [timeout: timeout] end diff --git a/apps/explorer/lib/explorer/microservice_interfaces/bens.ex b/apps/explorer/lib/explorer/microservice_interfaces/bens.ex index 61f1d7c5f45c..b809a4214e33 100644 --- a/apps/explorer/lib/explorer/microservice_interfaces/bens.ex +++ b/apps/explorer/lib/explorer/microservice_interfaces/bens.ex @@ -325,4 +325,59 @@ defmodule Explorer.MicroserviceInterfaces.BENS do def maybe_preload_ens_to_block(block) do maybe_preload_meta(block, __MODULE__, &MetadataPreloader.preload_ens_to_block/1) end + + @doc """ + Preloads ENS data to the list of blocks unless disabled via DISABLE_BLOCKS_BENS_PRELOAD. + + Checks `Application.get_env(:explorer, __MODULE__, [])[:disable_blocks_bens_preload]`; + if the flag is set, the input is returned unchanged, otherwise `maybe_preload_ens/1` + is called to enrich the list with ENS names from the BENS microservice. + + ## Parameters + + - `blocks` (`MetadataPreloader.supported_input()`) — a list of block structs + (or any value accepted by `MetadataPreloader.supported_input()`) whose miner + and other address fields should be enriched with ENS domain names. + + ## Returns + + - `MetadataPreloader.supported_input()` — the original `blocks` value unchanged + when `DISABLE_BLOCKS_BENS_PRELOAD` is `true`; otherwise the same collection + with ENS names preloaded via `maybe_preload_ens/1`. + """ + @spec maybe_preload_ens_for_blocks(MetadataPreloader.supported_input()) :: + MetadataPreloader.supported_input() + def maybe_preload_ens_for_blocks(blocks) do + if Application.get_env(:explorer, __MODULE__, [])[:disable_blocks_bens_preload] do + blocks + else + maybe_preload_ens(blocks) + end + end + + @doc """ + Preloads ENS data to the list of token transfers unless disabled via DISABLE_TOKEN_TRANSFERS_BENS_PRELOAD + """ + @spec maybe_preload_ens_for_token_transfers(MetadataPreloader.supported_input()) :: + MetadataPreloader.supported_input() + def maybe_preload_ens_for_token_transfers(token_transfers) do + if Application.get_env(:explorer, __MODULE__, [])[:disable_token_transfers_bens_preload] do + token_transfers + else + maybe_preload_ens(token_transfers) + end + end + + @doc """ + Preloads ENS data to the list of transactions unless disabled via DISABLE_TRANSACTIONS_BENS_PRELOAD + """ + @spec maybe_preload_ens_for_transactions(MetadataPreloader.supported_input()) :: + MetadataPreloader.supported_input() + def maybe_preload_ens_for_transactions(transactions) do + if Application.get_env(:explorer, __MODULE__, [])[:disable_transactions_bens_preload] do + transactions + else + maybe_preload_ens(transactions) + end + end end diff --git a/apps/explorer/lib/explorer/migrator/delete_zero_value_internal_transactions.ex b/apps/explorer/lib/explorer/migrator/delete_zero_value_internal_transactions.ex index a2507c03b2a4..fcf338b2561c 100644 --- a/apps/explorer/lib/explorer/migrator/delete_zero_value_internal_transactions.ex +++ b/apps/explorer/lib/explorer/migrator/delete_zero_value_internal_transactions.ex @@ -92,14 +92,14 @@ defmodule Explorer.Migrator.DeleteZeroValueInternalTransactions do defp do_clear_internal_transactions(dynamic_condition) do Repo.transaction( fn -> - condition = dynamic([it], ^dynamic_condition and it.type == ^:call and it.value == ^0) + condition = dynamic([it], ^dynamic_condition and it.type == ^:call and (is_nil(it.value) or it.value == ^0)) locked_internal_transactions_to_delete_query = from( it in InternalTransaction, select: select_ctid(it), where: ^condition, - order_by: [asc: it.transaction_hash, asc: it.index], + order_by: [asc: it.block_number, asc: it.transaction_index, asc: it.index], lock: "FOR UPDATE" ) @@ -110,7 +110,9 @@ defmodule Explorer.Migrator.DeleteZeroValueInternalTransactions do on: join_on_ctid(it, locked_it), select: %{ from_address_hash: it.from_address_hash, + from_address_id: it.from_address_id, to_address_hash: it.to_address_hash, + to_address_id: it.to_address_id, block_number: it.block_number, index: it.index } @@ -145,14 +147,17 @@ defmodule Explorer.Migrator.DeleteZeroValueInternalTransactions do inner_acc internal_transaction, inner_acc -> - from_address_hash = internal_transaction.from_address_hash - to_address_hash = internal_transaction.to_address_hash + from_address_id = + internal_transaction.from_address_id || address_to_id_map[internal_transaction.from_address_hash] + + to_address_id = + internal_transaction.to_address_id || address_to_id_map[internal_transaction.to_address_hash] inner_acc |> Map.update( - from_address_hash, + from_address_id, %{ - address_id: address_to_id_map[from_address_hash], + address_id: from_address_id, block_number: block_number, count_tos: 0, count_froms: 1 @@ -162,9 +167,9 @@ defmodule Explorer.Migrator.DeleteZeroValueInternalTransactions do end ) |> Map.update( - to_address_hash, + to_address_id, %{ - address_id: address_to_id_map[to_address_hash], + address_id: to_address_id, block_number: block_number, count_tos: 1, count_froms: 0 diff --git a/apps/explorer/lib/explorer/migrator/empty_bytecode_for_selfdestructed_smart_contracts.ex b/apps/explorer/lib/explorer/migrator/empty_bytecode_for_selfdestructed_smart_contracts.ex index b9d484934373..9b3230604d5c 100644 --- a/apps/explorer/lib/explorer/migrator/empty_bytecode_for_selfdestructed_smart_contracts.ex +++ b/apps/explorer/lib/explorer/migrator/empty_bytecode_for_selfdestructed_smart_contracts.ex @@ -15,7 +15,7 @@ defmodule Explorer.Migrator.EmptyBytecodeForSelfdestructedSmartContracts do alias Explorer.Chain.{Address, Data, InternalTransaction} alias Explorer.Chain.Cache.BlockNumber alias Explorer.Migrator.{FillingMigration, MigrationStatus} - alias Explorer.Repo + alias Explorer.{QueryHelper, Repo} require Logger @@ -67,53 +67,53 @@ defmodule Explorer.Migrator.EmptyBytecodeForSelfdestructedSmartContracts do else # Find all selfdestruct internal transactions in these blocks selfdestruct_query = - from( - it in InternalTransaction, - where: it.block_number in ^block_numbers, - where: it.type == :selfdestruct, - select: %{ - transaction_hash: it.transaction_hash, - from_address_hash: it.from_address_hash, - block_number: it.block_number - } - ) + InternalTransaction + |> InternalTransaction.join_address_mapping_query(:from_address) + |> where([it], it.block_number in ^block_numbers) + |> where([it], it.type == :selfdestruct) + |> select([it], %{ + transaction_index: it.transaction_index, + from_address_hash: coalesce(it.from_address_hash, as(:from_address_mapping).address_hash), + block_number: it.block_number + }) selfdestruct_transactions = Repo.all(selfdestruct_query, timeout: :infinity) if Enum.empty?(selfdestruct_transactions) do {:ok, []} else - # Get unique transaction hashes to check for create/create2 - transaction_hashes = + # Get unique transaction block numbers and indexes to check for create/create2 + transaction_identifiers = selfdestruct_transactions - |> Enum.map(& &1.transaction_hash) + |> Enum.map(&{&1.block_number, &1.transaction_index}) |> Enum.uniq() # Find all create/create2 internal transactions in the same transactions create_query = - from( - it in InternalTransaction, - where: it.transaction_hash in ^transaction_hashes, - where: it.type in [:create, :create2], - select: %{ - transaction_hash: it.transaction_hash, - created_contract_address_hash: it.created_contract_address_hash - } - ) + InternalTransaction + |> InternalTransaction.join_address_mapping_query(:created_contract_address) + |> where(^QueryHelper.tuple_in([:block_number, :transaction_index], transaction_identifiers)) + |> where([it], it.type in [:create, :create2]) + |> select([it], %{ + block_number: it.block_number, + transaction_index: it.transaction_index, + created_contract_address_hash: + coalesce(it.created_contract_address_hash, as(:created_contract_address_mapping).address_hash) + }) created_contracts = Repo.all(create_query, timeout: :infinity) - # Build a set of {transaction_hash, address_hash} for contracts created in same tx + # Build a set of {block_number, transaction_index, address_hash} for contracts created in same tx created_in_same_tx = created_contracts - |> Enum.map(&{&1.transaction_hash, &1.created_contract_address_hash}) + |> Enum.map(&{&1.block_number, &1.transaction_index, &1.created_contract_address_hash}) |> MapSet.new() # Filter to find addresses that were selfdestructed but NOT created in the same transaction addresses_to_empty = selfdestruct_transactions |> Enum.reject(fn sd -> - MapSet.member?(created_in_same_tx, {sd.transaction_hash, sd.from_address_hash}) + MapSet.member?(created_in_same_tx, {sd.block_number, sd.transaction_index, sd.from_address_hash}) end) |> Enum.map(& &1.from_address_hash) |> Enum.uniq() diff --git a/apps/explorer/lib/explorer/migrator/empty_internal_transactions_data.ex b/apps/explorer/lib/explorer/migrator/empty_internal_transactions_data.ex index b1ced301aa4c..23e48bf79df8 100644 --- a/apps/explorer/lib/explorer/migrator/empty_internal_transactions_data.ex +++ b/apps/explorer/lib/explorer/migrator/empty_internal_transactions_data.ex @@ -66,7 +66,7 @@ defmodule Explorer.Migrator.EmptyInternalTransactionsData do (not is_nil(it.trace_address) or it.value == ^0 or (is_nil(it.call_type_enum) and not is_nil(it.call_type)) or not is_nil(it.error) or (not is_nil(it.created_contract_address_hash) and is_nil(it.to_address_hash))), - order_by: [asc: it.transaction_hash, asc: it.index], + order_by: [asc: it.block_number, asc: it.transaction_index, asc: it.index], lock: "FOR UPDATE" ) diff --git a/apps/explorer/lib/explorer/migrator/fill_internal_transactions_address_ids.ex b/apps/explorer/lib/explorer/migrator/fill_internal_transactions_address_ids.ex new file mode 100644 index 000000000000..b8c0588ef01d --- /dev/null +++ b/apps/explorer/lib/explorer/migrator/fill_internal_transactions_address_ids.ex @@ -0,0 +1,144 @@ +defmodule Explorer.Migrator.FillInternalTransactionsAddressIds do + @moduledoc """ + Clears `*_address_hash` fields in internal transactions and fills their `*_address_id` copies. + """ + + use Explorer.Migrator.FillingMigration + + import Ecto.Query + import Explorer.QueryHelper, only: [select_ctid: 1, join_on_ctid: 2] + + alias Explorer.Chain.Cache.BackgroundMigrations + alias Explorer.Chain.InternalTransaction + alias Explorer.Migrator.FillingMigration + alias Explorer.Migrator.HeavyDbIndexOperation.RemoveInternalTransactionsBlockHashTransactionHashBlockIndexError + alias Explorer.Repo + alias Explorer.Utility.AddressIdToAddressHash + + @migration_name "fill_internal_transactions_address_ids" + + @impl FillingMigration + def migration_name, do: @migration_name + + @impl FillingMigration + def dependent_from_migrations, + do: [RemoveInternalTransactionsBlockHashTransactionHashBlockIndexError.migration_name()] + + @impl FillingMigration + def last_unprocessed_identifiers(%{"max_block_number" => -1} = state), do: {[], state} + + def last_unprocessed_identifiers(%{"max_block_number" => from_block_number} = state) do + limit = batch_size() * concurrency() + to_block_number = max(from_block_number - limit + 1, 0) + + {Enum.to_list(from_block_number..to_block_number), %{state | "max_block_number" => to_block_number - 1}} + end + + def last_unprocessed_identifiers(state) do + query = + from( + it in InternalTransaction, + where: + not is_nil(it.from_address_hash) or not is_nil(it.to_address_hash) or + not is_nil(it.created_contract_address_hash), + select: max(it.block_number) + ) + + max_block_number = Repo.one(query, timeout: :infinity) + + state + |> Map.put("max_block_number", max_block_number || -1) + |> last_unprocessed_identifiers() + end + + @impl FillingMigration + def unprocessed_data_query, do: nil + + @impl FillingMigration + def update_batch(block_numbers) do + Repo.transaction( + fn -> + lock_query = + from( + it in InternalTransaction, + select: select_ctid(it), + select_merge: %{ + from_address_hash: it.from_address_hash, + to_address_hash: it.to_address_hash, + created_contract_address_hash: it.created_contract_address_hash + }, + where: + it.block_number in ^block_numbers and + (not is_nil(it.from_address_hash) or not is_nil(it.to_address_hash) or + not is_nil(it.created_contract_address_hash)), + order_by: [asc: it.block_number, asc: it.transaction_index, asc: it.index], + lock: "FOR UPDATE" + ) + + from_address_hashes_query = + from(it in InternalTransaction, + inner_join: locked_it in subquery(lock_query), + on: join_on_ctid(it, locked_it), + where: not is_nil(it.from_address_hash), + distinct: true, + select: it.from_address_hash + ) + + to_address_hashes_query = + from(it in InternalTransaction, + inner_join: locked_it in subquery(lock_query), + on: join_on_ctid(it, locked_it), + where: not is_nil(it.to_address_hash), + distinct: true, + select: it.to_address_hash + ) + + created_contract_address_hashes_query = + from(it in InternalTransaction, + inner_join: locked_it in subquery(lock_query), + on: join_on_ctid(it, locked_it), + where: not is_nil(it.created_contract_address_hash), + distinct: true, + select: it.created_contract_address_hash + ) + + from_address_hashes_query + |> union_all(^to_address_hashes_query) + |> union_all(^created_contract_address_hashes_query) + |> Repo.all() + |> Enum.uniq() + |> AddressIdToAddressHash.find_or_create_multiple() + + update_query = + from(it in InternalTransaction, + inner_join: locked_it in subquery(lock_query), + on: join_on_ctid(it, locked_it), + left_join: from_map in AddressIdToAddressHash, + on: from_map.address_hash == locked_it.from_address_hash, + left_join: to_map in AddressIdToAddressHash, + on: to_map.address_hash == locked_it.to_address_hash, + left_join: created_map in AddressIdToAddressHash, + on: created_map.address_hash == locked_it.created_contract_address_hash, + update: [ + set: [ + from_address_id: from_map.address_id, + to_address_id: coalesce(to_map.address_id, created_map.address_id), + created_contract_address_id: created_map.address_id, + from_address_hash: nil, + to_address_hash: nil, + created_contract_address_hash: nil + ] + ] + ) + + Repo.update_all(update_query, [], timeout: :infinity) + end, + timeout: :infinity + ) + end + + @impl FillingMigration + def update_cache do + BackgroundMigrations.set_fill_internal_transactions_address_ids_finished(true) + end +end diff --git a/apps/explorer/lib/explorer/migrator/filling_migration.ex b/apps/explorer/lib/explorer/migrator/filling_migration.ex index a3b5a5fadc14..30b212c9a2a3 100644 --- a/apps/explorer/lib/explorer/migrator/filling_migration.ex +++ b/apps/explorer/lib/explorer/migrator/filling_migration.ex @@ -1,3 +1,4 @@ +# credo:disable-for-this-file defmodule Explorer.Migrator.FillingMigration do @moduledoc """ Provides a behaviour and implementation for data migration tasks that fill or update @@ -154,6 +155,11 @@ defmodule Explorer.Migrator.FillingMigration do """ @callback before_start :: any() + @doc """ + Returns a list of migration names that the current migration depends on. + """ + @callback dependent_from_migrations :: list(String.t()) + @optional_callbacks unprocessed_data_query: 0, unprocessed_data_query: 1 defmacro __using__(opts) do @@ -164,6 +170,7 @@ defmodule Explorer.Migrator.FillingMigration do import Ecto.Query + alias Explorer.Migrator.HeavyDbIndexOperation.Helper, as: HeavyDbIndexOperationHelper alias Explorer.Migrator.MigrationStatus alias Explorer.Repo @@ -214,13 +221,24 @@ defmodule Explorer.Migrator.FillingMigration do {:stop, :normal, state} migration_status -> - MigrationStatus.set_status(migration_name(), "started") - before_start() - schedule_batch_migration(0) + schedule_next_migration_readiness_check(0) {:noreply, (migration_status && migration_status.meta) || %{}} end end + @impl true + def handle_info(:check_migration_readiness, state) do + if migration_is_ready_to_start?() do + MigrationStatus.set_status(migration_name(), "started") + before_start() + schedule_batch_migration(0) + else + schedule_next_migration_readiness_check() + end + + {:noreply, state} + end + # Processes a batch of unprocessed identifiers for migration. # # Retrieves the next batch of unprocessed identifiers and processes them in parallel. @@ -275,6 +293,25 @@ defmodule Explorer.Migrator.FillingMigration do @spec run_task([any()]) :: any() defp run_task(batch), do: Task.async(fn -> update_batch(batch) end) + defp migration_is_ready_to_start? do + migrations = dependent_from_migrations() + + if Enum.empty?(migrations) do + true + else + all_statuses = MigrationStatus.fetch_migration_statuses(migrations) + Enum.count(all_statuses) == Enum.count(migrations) and Enum.all?(all_statuses, &(&1 == "completed")) + end + end + + defp schedule_next_migration_readiness_check(timeout \\ nil) do + Process.send_after( + self(), + :check_migration_readiness, + timeout || HeavyDbIndexOperationHelper.get_check_interval() + ) + end + # Schedules the next batch migration by sending a delayed :migrate_batch message. # # ## Parameters @@ -305,7 +342,11 @@ defmodule Explorer.Migrator.FillingMigration do :ignore end - defoverridable on_finish: 0, before_start: 0 + def dependent_from_migrations do + [] + end + + defoverridable on_finish: 0, before_start: 0, dependent_from_migrations: 0 end end end diff --git a/apps/explorer/lib/explorer/migrator/heavy_db_index_operation.ex b/apps/explorer/lib/explorer/migrator/heavy_db_index_operation.ex index 434b0d7b6411..76bd57df6c6c 100644 --- a/apps/explorer/lib/explorer/migrator/heavy_db_index_operation.ex +++ b/apps/explorer/lib/explorer/migrator/heavy_db_index_operation.ex @@ -181,8 +181,8 @@ defmodule Explorer.Migrator.HeavyDbIndexOperation do {:db_index_operation_status, db_index_operation_status()} do if db_operation_is_ready_to_start?() do MigrationStatus.set_status(migration_name(), "started") - db_index_operation() - schedule_next_db_operation_status_check() + timeout = (db_index_operation() == :ok && 0) || nil + schedule_next_db_operation_status_check(timeout) else schedule_next_db_operation_readiness_check() end diff --git a/apps/explorer/lib/explorer/migrator/heavy_db_index_operation/create_addresses_hash_contract_code_not_null_index.ex b/apps/explorer/lib/explorer/migrator/heavy_db_index_operation/create_addresses_hash_contract_code_not_null_index.ex new file mode 100644 index 000000000000..a5d7f498af04 --- /dev/null +++ b/apps/explorer/lib/explorer/migrator/heavy_db_index_operation/create_addresses_hash_contract_code_not_null_index.ex @@ -0,0 +1,64 @@ +defmodule Explorer.Migrator.HeavyDbIndexOperation.CreateAddressesHashContractCodeNotNullIndex do + @moduledoc """ + Create partial B-tree index on `addresses` table filtering by `contract_code IS NOT NULL` + on the `hash` column. + """ + + use Explorer.Migrator.HeavyDbIndexOperation + + alias Explorer.Chain.Cache.BackgroundMigrations + alias Explorer.Migrator.{HeavyDbIndexOperation, MigrationStatus} + alias Explorer.Migrator.HeavyDbIndexOperation.Helper, as: HeavyDbIndexOperationHelper + + @table_name :addresses + @index_name "addresses_hash_contract_code_not_null" + @operation_type :create + + @impl HeavyDbIndexOperation + def table_name, do: @table_name + + @impl HeavyDbIndexOperation + def operation_type, do: @operation_type + + @impl HeavyDbIndexOperation + def index_name, do: @index_name + + @impl HeavyDbIndexOperation + def dependent_from_migrations, do: [] + + @query_string """ + CREATE INDEX #{HeavyDbIndexOperationHelper.add_concurrently_flag?()} IF NOT EXISTS "#{@index_name}" + ON #{@table_name}(hash) + WHERE contract_code IS NOT NULL; + """ + + @impl HeavyDbIndexOperation + def db_index_operation do + HeavyDbIndexOperationHelper.create_db_index(@query_string) + end + + @impl HeavyDbIndexOperation + def check_db_index_operation_progress do + HeavyDbIndexOperationHelper.check_db_index_operation_progress(@index_name, @query_string) + end + + @impl HeavyDbIndexOperation + def db_index_operation_status do + HeavyDbIndexOperationHelper.db_index_creation_status(@index_name) + end + + @impl HeavyDbIndexOperation + def restart_db_index_operation do + HeavyDbIndexOperationHelper.safely_drop_db_index(@index_name) + end + + @impl HeavyDbIndexOperation + def running_other_heavy_migration_exists?(migration_name) do + MigrationStatus.running_other_heavy_migration_for_table_exists?(@table_name, migration_name) + end + + @impl HeavyDbIndexOperation + def update_cache do + BackgroundMigrations.set_heavy_indexes_create_addresses_hash_contract_code_not_null_index_finished(true) + end +end diff --git a/apps/explorer/lib/explorer/migrator/heavy_db_index_operation/create_internal_transactions_block_number_created_contract_address_id_partial_index.ex b/apps/explorer/lib/explorer/migrator/heavy_db_index_operation/create_internal_transactions_block_number_created_contract_address_id_partial_index.ex new file mode 100644 index 000000000000..634684bd3cea --- /dev/null +++ b/apps/explorer/lib/explorer/migrator/heavy_db_index_operation/create_internal_transactions_block_number_created_contract_address_id_partial_index.ex @@ -0,0 +1,64 @@ +defmodule Explorer.Migrator.HeavyDbIndexOperation.CreateInternalTransactionsBlockNumberCreatedContractAddressIdPartialIndex do + @moduledoc """ + Create partial B-tree index `internal_transactions_block_number_created_contract_address_id_index` on `internal_transactions` table for (block_number, created_contract_address_id) columns where created_contract_address_id is not NULL. + """ + + use Explorer.Migrator.HeavyDbIndexOperation + + alias Explorer.Chain.Cache.BackgroundMigrations + alias Explorer.Migrator.{HeavyDbIndexOperation, MigrationStatus} + alias Explorer.Migrator.HeavyDbIndexOperation.CreateInternalTransactionsCreatedContractAddressIdIndex + alias Explorer.Migrator.HeavyDbIndexOperation.Helper, as: HeavyDbIndexOperationHelper + + @table_name :internal_transactions + @index_name "internal_transactions_block_number_created_contract_address_id_index" + @operation_type :create + + @impl HeavyDbIndexOperation + def table_name, do: @table_name + + @impl HeavyDbIndexOperation + def operation_type, do: @operation_type + + @impl HeavyDbIndexOperation + def index_name, do: @index_name + + @impl HeavyDbIndexOperation + def dependent_from_migrations, do: [CreateInternalTransactionsCreatedContractAddressIdIndex.migration_name()] + + @query_string """ + CREATE INDEX #{HeavyDbIndexOperationHelper.add_concurrently_flag?()} IF NOT EXISTS "#{@index_name}" + ON #{@table_name}(block_number, created_contract_address_id) + WHERE (created_contract_address_id IS NOT NULL); + """ + + @impl HeavyDbIndexOperation + def db_index_operation do + HeavyDbIndexOperationHelper.create_db_index(@query_string) + end + + @impl HeavyDbIndexOperation + def check_db_index_operation_progress do + HeavyDbIndexOperationHelper.check_db_index_operation_progress(@index_name, @query_string) + end + + @impl HeavyDbIndexOperation + def db_index_operation_status do + HeavyDbIndexOperationHelper.db_index_creation_status(@index_name) + end + + @impl HeavyDbIndexOperation + def restart_db_index_operation do + HeavyDbIndexOperationHelper.safely_drop_db_index(@index_name) + end + + @impl HeavyDbIndexOperation + def running_other_heavy_migration_exists?(migration_name) do + MigrationStatus.running_other_heavy_migration_for_table_exists?(@table_name, migration_name) + end + + @impl HeavyDbIndexOperation + def update_cache do + BackgroundMigrations.set_heavy_indexes_create_address_ids_internal_transactions_indexes_finished(true) + end +end diff --git a/apps/explorer/lib/explorer/migrator/heavy_db_index_operation/create_internal_transactions_block_number_transaction_index_index_unique_index.ex b/apps/explorer/lib/explorer/migrator/heavy_db_index_operation/create_internal_transactions_block_number_transaction_index_index_unique_index.ex index 1069de2539f7..289d73b836cd 100644 --- a/apps/explorer/lib/explorer/migrator/heavy_db_index_operation/create_internal_transactions_block_number_transaction_index_index_unique_index.ex +++ b/apps/explorer/lib/explorer/migrator/heavy_db_index_operation/create_internal_transactions_block_number_transaction_index_index_unique_index.ex @@ -10,8 +10,7 @@ defmodule Explorer.Migrator.HeavyDbIndexOperation.CreateInternalTransactionsBloc alias Explorer.Migrator.{ EmptyInternalTransactionsData, HeavyDbIndexOperation, - MigrationStatus, - ReindexDuplicatedInternalTransactions + MigrationStatus } alias Explorer.Migrator.HeavyDbIndexOperation.Helper, as: HeavyDbIndexOperationHelper @@ -33,7 +32,6 @@ defmodule Explorer.Migrator.HeavyDbIndexOperation.CreateInternalTransactionsBloc @impl HeavyDbIndexOperation def dependent_from_migrations, do: [ - ReindexDuplicatedInternalTransactions.migration_name(), EmptyInternalTransactionsData.migration_name() ] diff --git a/apps/explorer/lib/explorer/migrator/heavy_db_index_operation/create_internal_transactions_created_contract_address_id_index.ex b/apps/explorer/lib/explorer/migrator/heavy_db_index_operation/create_internal_transactions_created_contract_address_id_index.ex new file mode 100644 index 000000000000..d4e5cfc9f383 --- /dev/null +++ b/apps/explorer/lib/explorer/migrator/heavy_db_index_operation/create_internal_transactions_created_contract_address_id_index.ex @@ -0,0 +1,57 @@ +defmodule Explorer.Migrator.HeavyDbIndexOperation.CreateInternalTransactionsCreatedContractAddressIdIndex do + @moduledoc """ + Create B-tree index on `internal_transactions` table for `created_contract_address_id` column. + """ + + use Explorer.Migrator.HeavyDbIndexOperation + + alias Explorer.Migrator.{HeavyDbIndexOperation, MigrationStatus} + alias Explorer.Migrator.HeavyDbIndexOperation.CreateInternalTransactionsToAddressIdPartialIndex + alias Explorer.Migrator.HeavyDbIndexOperation.Helper, as: HeavyDbIndexOperationHelper + + @table_name :internal_transactions + @index_name "internal_transactions_created_contract_address_id_index" + @operation_type :create + @table_columns ["created_contract_address_id"] + + @impl HeavyDbIndexOperation + def table_name, do: @table_name + + @impl HeavyDbIndexOperation + def operation_type, do: @operation_type + + @impl HeavyDbIndexOperation + def index_name, do: @index_name + + @impl HeavyDbIndexOperation + def dependent_from_migrations, do: [CreateInternalTransactionsToAddressIdPartialIndex.migration_name()] + + @impl HeavyDbIndexOperation + def db_index_operation do + HeavyDbIndexOperationHelper.create_db_index(@index_name, @table_name, @table_columns) + end + + @impl HeavyDbIndexOperation + def check_db_index_operation_progress do + operation = HeavyDbIndexOperationHelper.create_index_query_string(@index_name, @table_name, @table_columns) + HeavyDbIndexOperationHelper.check_db_index_operation_progress(@index_name, operation) + end + + @impl HeavyDbIndexOperation + def db_index_operation_status do + HeavyDbIndexOperationHelper.db_index_creation_status(@index_name) + end + + @impl HeavyDbIndexOperation + def restart_db_index_operation do + HeavyDbIndexOperationHelper.safely_drop_db_index(@index_name) + end + + @impl HeavyDbIndexOperation + def running_other_heavy_migration_exists?(migration_name) do + MigrationStatus.running_other_heavy_migration_for_table_exists?(@table_name, migration_name) + end + + @impl HeavyDbIndexOperation + def update_cache, do: :ok +end diff --git a/apps/explorer/lib/explorer/migrator/heavy_db_index_operation/create_internal_transactions_from_address_id_partial_index.ex b/apps/explorer/lib/explorer/migrator/heavy_db_index_operation/create_internal_transactions_from_address_id_partial_index.ex new file mode 100644 index 000000000000..e591329a007b --- /dev/null +++ b/apps/explorer/lib/explorer/migrator/heavy_db_index_operation/create_internal_transactions_from_address_id_partial_index.ex @@ -0,0 +1,60 @@ +defmodule Explorer.Migrator.HeavyDbIndexOperation.CreateInternalTransactionsFromAddressIdPartialIndex do + @moduledoc """ + Create partial B-tree index `internal_transactions_from_address_id_partial_index` on `internal_transactions` table for (`from_address_id`, `block_number` DESC, `transaction_index` DESC, `index` DESC) columns. + """ + + use Explorer.Migrator.HeavyDbIndexOperation + + alias Explorer.Migrator.{HeavyDbIndexOperation, MigrationStatus} + alias Explorer.Migrator.HeavyDbIndexOperation.Helper, as: HeavyDbIndexOperationHelper + + @table_name :internal_transactions + @index_name "internal_transactions_from_address_id_partial_index" + @operation_type :create + + @impl HeavyDbIndexOperation + def table_name, do: @table_name + + @impl HeavyDbIndexOperation + def operation_type, do: @operation_type + + @impl HeavyDbIndexOperation + def index_name, do: @index_name + + @impl HeavyDbIndexOperation + def dependent_from_migrations, do: [] + + @query_string """ + CREATE INDEX #{HeavyDbIndexOperationHelper.add_concurrently_flag?()} IF NOT EXISTS "#{@index_name}" + ON #{@table_name}(from_address_id ASC, block_number DESC, transaction_index DESC, index DESC) + WHERE ((type = 'call' AND index > 0) OR type != 'call'); + """ + + @impl HeavyDbIndexOperation + def db_index_operation do + HeavyDbIndexOperationHelper.create_db_index(@query_string) + end + + @impl HeavyDbIndexOperation + def check_db_index_operation_progress do + HeavyDbIndexOperationHelper.check_db_index_operation_progress(@index_name, @query_string) + end + + @impl HeavyDbIndexOperation + def db_index_operation_status do + HeavyDbIndexOperationHelper.db_index_creation_status(@index_name) + end + + @impl HeavyDbIndexOperation + def restart_db_index_operation do + HeavyDbIndexOperationHelper.safely_drop_db_index(@index_name) + end + + @impl HeavyDbIndexOperation + def running_other_heavy_migration_exists?(migration_name) do + MigrationStatus.running_other_heavy_migration_for_table_exists?(@table_name, migration_name) + end + + @impl HeavyDbIndexOperation + def update_cache, do: :ok +end diff --git a/apps/explorer/lib/explorer/migrator/heavy_db_index_operation/create_internal_transactions_to_address_id_partial_index.ex b/apps/explorer/lib/explorer/migrator/heavy_db_index_operation/create_internal_transactions_to_address_id_partial_index.ex new file mode 100644 index 000000000000..1b71f540ef31 --- /dev/null +++ b/apps/explorer/lib/explorer/migrator/heavy_db_index_operation/create_internal_transactions_to_address_id_partial_index.ex @@ -0,0 +1,61 @@ +defmodule Explorer.Migrator.HeavyDbIndexOperation.CreateInternalTransactionsToAddressIdPartialIndex do + @moduledoc """ + Create partial B-tree index `internal_transactions_to_address_id_partial_index` on `internal_transactions` table for (`to_address_id`, `block_number` DESC, `transaction_index` DESC, `index` DESC) columns. + """ + + use Explorer.Migrator.HeavyDbIndexOperation + + alias Explorer.Migrator.{HeavyDbIndexOperation, MigrationStatus} + alias Explorer.Migrator.HeavyDbIndexOperation.CreateInternalTransactionsFromAddressIdPartialIndex + alias Explorer.Migrator.HeavyDbIndexOperation.Helper, as: HeavyDbIndexOperationHelper + + @table_name :internal_transactions + @index_name "internal_transactions_to_address_id_partial_index" + @operation_type :create + + @impl HeavyDbIndexOperation + def table_name, do: @table_name + + @impl HeavyDbIndexOperation + def operation_type, do: @operation_type + + @impl HeavyDbIndexOperation + def index_name, do: @index_name + + @impl HeavyDbIndexOperation + def dependent_from_migrations, do: [CreateInternalTransactionsFromAddressIdPartialIndex.migration_name()] + + @query_string """ + CREATE INDEX #{HeavyDbIndexOperationHelper.add_concurrently_flag?()} IF NOT EXISTS "#{@index_name}" + ON #{@table_name}(to_address_id ASC, block_number DESC, transaction_index DESC, index DESC) + WHERE ((type = 'call' AND index > 0) OR type != 'call'); + """ + + @impl HeavyDbIndexOperation + def db_index_operation do + HeavyDbIndexOperationHelper.create_db_index(@query_string) + end + + @impl HeavyDbIndexOperation + def check_db_index_operation_progress do + HeavyDbIndexOperationHelper.check_db_index_operation_progress(@index_name, @query_string) + end + + @impl HeavyDbIndexOperation + def db_index_operation_status do + HeavyDbIndexOperationHelper.db_index_creation_status(@index_name) + end + + @impl HeavyDbIndexOperation + def restart_db_index_operation do + HeavyDbIndexOperationHelper.safely_drop_db_index(@index_name) + end + + @impl HeavyDbIndexOperation + def running_other_heavy_migration_exists?(migration_name) do + MigrationStatus.running_other_heavy_migration_for_table_exists?(@table_name, migration_name) + end + + @impl HeavyDbIndexOperation + def update_cache, do: :ok +end diff --git a/apps/explorer/lib/explorer/migrator/heavy_db_index_operation/create_tokens_ord_fiat_holder_name_index.ex b/apps/explorer/lib/explorer/migrator/heavy_db_index_operation/create_tokens_ord_fiat_holder_name_index.ex new file mode 100644 index 000000000000..ac11a4e3ecc0 --- /dev/null +++ b/apps/explorer/lib/explorer/migrator/heavy_db_index_operation/create_tokens_ord_fiat_holder_name_index.ex @@ -0,0 +1,69 @@ +defmodule Explorer.Migrator.HeavyDbIndexOperation.CreateTokensOrdFiatHolderNameIndex do + @moduledoc """ + Create B-tree index `idx_tokens_ord_fiat_holder_name` on `tokens` table for + (`fiat_value DESC NULLS LAST`, `holder_count DESC NULLS LAST`, `name`). + """ + + use Explorer.Migrator.HeavyDbIndexOperation + + require Logger + + alias Explorer.Chain.Cache.BackgroundMigrations + alias Explorer.Migrator.{HeavyDbIndexOperation, MigrationStatus} + alias Explorer.Migrator.HeavyDbIndexOperation.CreateTokensOrdMcapFiatHolderNameIndex + alias Explorer.Migrator.HeavyDbIndexOperation.Helper, as: HeavyDbIndexOperationHelper + + @table_name :tokens + @index_name "idx_tokens_ord_fiat_holder_name" + @operation_type :create + @table_columns [ + "fiat_value DESC NULLS LAST", + "holder_count DESC NULLS LAST", + "name" + ] + + @impl HeavyDbIndexOperation + def table_name, do: @table_name + + @impl HeavyDbIndexOperation + def operation_type, do: @operation_type + + @impl HeavyDbIndexOperation + def index_name, do: @index_name + + @impl HeavyDbIndexOperation + def dependent_from_migrations do + [CreateTokensOrdMcapFiatHolderNameIndex.migration_name()] + end + + @impl HeavyDbIndexOperation + def db_index_operation do + HeavyDbIndexOperationHelper.create_db_index(@index_name, @table_name, @table_columns) + end + + @impl HeavyDbIndexOperation + def check_db_index_operation_progress do + operation = HeavyDbIndexOperationHelper.create_index_query_string(@index_name, @table_name, @table_columns) + HeavyDbIndexOperationHelper.check_db_index_operation_progress(@index_name, operation) + end + + @impl HeavyDbIndexOperation + def db_index_operation_status do + HeavyDbIndexOperationHelper.db_index_creation_status(@index_name) + end + + @impl HeavyDbIndexOperation + def restart_db_index_operation do + HeavyDbIndexOperationHelper.safely_drop_db_index(@index_name) + end + + @impl HeavyDbIndexOperation + def running_other_heavy_migration_exists?(migration_name) do + MigrationStatus.running_other_heavy_migration_for_table_exists?(@table_name, migration_name) + end + + @impl HeavyDbIndexOperation + def update_cache do + BackgroundMigrations.set_heavy_indexes_create_idx_tokens_ord_fiat_holder_name_finished(true) + end +end diff --git a/apps/explorer/lib/explorer/migrator/heavy_db_index_operation/create_tokens_ord_holder_name_index.ex b/apps/explorer/lib/explorer/migrator/heavy_db_index_operation/create_tokens_ord_holder_name_index.ex new file mode 100644 index 000000000000..4eb98e07203b --- /dev/null +++ b/apps/explorer/lib/explorer/migrator/heavy_db_index_operation/create_tokens_ord_holder_name_index.ex @@ -0,0 +1,65 @@ +defmodule Explorer.Migrator.HeavyDbIndexOperation.CreateTokensOrdHolderNameIndex do + @moduledoc """ + Create B-tree index `idx_tokens_ord_holder_name` on `tokens` table for + (`holder_count DESC NULLS LAST`, `name`). + """ + + use Explorer.Migrator.HeavyDbIndexOperation + + require Logger + + alias Explorer.Chain.Cache.BackgroundMigrations + alias Explorer.Migrator.{HeavyDbIndexOperation, MigrationStatus} + alias Explorer.Migrator.HeavyDbIndexOperation.CreateTokensOrdFiatHolderNameIndex + alias Explorer.Migrator.HeavyDbIndexOperation.Helper, as: HeavyDbIndexOperationHelper + + @table_name :tokens + @index_name "idx_tokens_ord_holder_name" + @operation_type :create + @table_columns ["holder_count DESC NULLS LAST", "name"] + + @impl HeavyDbIndexOperation + def table_name, do: @table_name + + @impl HeavyDbIndexOperation + def operation_type, do: @operation_type + + @impl HeavyDbIndexOperation + def index_name, do: @index_name + + @impl HeavyDbIndexOperation + def dependent_from_migrations do + [CreateTokensOrdFiatHolderNameIndex.migration_name()] + end + + @impl HeavyDbIndexOperation + def db_index_operation do + HeavyDbIndexOperationHelper.create_db_index(@index_name, @table_name, @table_columns) + end + + @impl HeavyDbIndexOperation + def check_db_index_operation_progress do + operation = HeavyDbIndexOperationHelper.create_index_query_string(@index_name, @table_name, @table_columns) + HeavyDbIndexOperationHelper.check_db_index_operation_progress(@index_name, operation) + end + + @impl HeavyDbIndexOperation + def db_index_operation_status do + HeavyDbIndexOperationHelper.db_index_creation_status(@index_name) + end + + @impl HeavyDbIndexOperation + def restart_db_index_operation do + HeavyDbIndexOperationHelper.safely_drop_db_index(@index_name) + end + + @impl HeavyDbIndexOperation + def running_other_heavy_migration_exists?(migration_name) do + MigrationStatus.running_other_heavy_migration_for_table_exists?(@table_name, migration_name) + end + + @impl HeavyDbIndexOperation + def update_cache do + BackgroundMigrations.set_heavy_indexes_create_idx_tokens_ord_holder_name_finished(true) + end +end diff --git a/apps/explorer/lib/explorer/migrator/heavy_db_index_operation/create_tokens_ord_mcap_fiat_holder_name_index.ex b/apps/explorer/lib/explorer/migrator/heavy_db_index_operation/create_tokens_ord_mcap_fiat_holder_name_index.ex new file mode 100644 index 000000000000..89d447430475 --- /dev/null +++ b/apps/explorer/lib/explorer/migrator/heavy_db_index_operation/create_tokens_ord_mcap_fiat_holder_name_index.ex @@ -0,0 +1,68 @@ +defmodule Explorer.Migrator.HeavyDbIndexOperation.CreateTokensOrdMcapFiatHolderNameIndex do + @moduledoc """ + Create B-tree index `idx_tokens_ord_mcap_fiat_holder_name` on `tokens` table + for (`circulating_market_cap DESC NULLS LAST`, `fiat_value DESC NULLS LAST`, + `holder_count DESC NULLS LAST`, `name`). + """ + + use Explorer.Migrator.HeavyDbIndexOperation + + require Logger + + alias Explorer.Chain.Cache.BackgroundMigrations + alias Explorer.Migrator.{HeavyDbIndexOperation, MigrationStatus} + alias Explorer.Migrator.HeavyDbIndexOperation.Helper, as: HeavyDbIndexOperationHelper + + @table_name :tokens + @index_name "idx_tokens_ord_mcap_fiat_holder_name" + @operation_type :create + @table_columns [ + "circulating_market_cap DESC NULLS LAST", + "fiat_value DESC NULLS LAST", + "holder_count DESC NULLS LAST", + "name" + ] + + @impl HeavyDbIndexOperation + def table_name, do: @table_name + + @impl HeavyDbIndexOperation + def operation_type, do: @operation_type + + @impl HeavyDbIndexOperation + def index_name, do: @index_name + + @impl HeavyDbIndexOperation + def dependent_from_migrations, do: [] + + @impl HeavyDbIndexOperation + def db_index_operation do + HeavyDbIndexOperationHelper.create_db_index(@index_name, @table_name, @table_columns) + end + + @impl HeavyDbIndexOperation + def check_db_index_operation_progress do + operation = HeavyDbIndexOperationHelper.create_index_query_string(@index_name, @table_name, @table_columns) + HeavyDbIndexOperationHelper.check_db_index_operation_progress(@index_name, operation) + end + + @impl HeavyDbIndexOperation + def db_index_operation_status do + HeavyDbIndexOperationHelper.db_index_creation_status(@index_name) + end + + @impl HeavyDbIndexOperation + def restart_db_index_operation do + HeavyDbIndexOperationHelper.safely_drop_db_index(@index_name) + end + + @impl HeavyDbIndexOperation + def running_other_heavy_migration_exists?(migration_name) do + MigrationStatus.running_other_heavy_migration_for_table_exists?(@table_name, migration_name) + end + + @impl HeavyDbIndexOperation + def update_cache do + BackgroundMigrations.set_heavy_indexes_create_idx_tokens_ord_mcap_fiat_holder_name_finished(true) + end +end diff --git a/apps/explorer/lib/explorer/migrator/heavy_db_index_operation/drop_internal_transactions_block_number_created_contract_address_hash_index.ex b/apps/explorer/lib/explorer/migrator/heavy_db_index_operation/drop_internal_transactions_block_number_created_contract_address_hash_index.ex new file mode 100644 index 000000000000..a2855562d5b2 --- /dev/null +++ b/apps/explorer/lib/explorer/migrator/heavy_db_index_operation/drop_internal_transactions_block_number_created_contract_address_hash_index.ex @@ -0,0 +1,55 @@ +defmodule Explorer.Migrator.HeavyDbIndexOperation.DropInternalTransactionsBlockNumberCreatedContractAddressHashIndex do + @moduledoc """ + Drops index "internal_transactions_block_number_created_contract_address_hash" btree (block_number, created_contract_address_hash) WHERE (created_contract_address_hash IS NOT NULL). + """ + + use Explorer.Migrator.HeavyDbIndexOperation + + alias Explorer.Migrator.{FillInternalTransactionsAddressIds, HeavyDbIndexOperation, MigrationStatus} + alias Explorer.Migrator.HeavyDbIndexOperation.Helper, as: HeavyDbIndexOperationHelper + + @table_name :internal_transactions + @index_name "internal_transactions_block_number_created_contract_address_hash" + @operation_type :drop + + @impl HeavyDbIndexOperation + def table_name, do: @table_name + + @impl HeavyDbIndexOperation + def operation_type, do: @operation_type + + @impl HeavyDbIndexOperation + def index_name, do: @index_name + + @impl HeavyDbIndexOperation + def dependent_from_migrations, do: [FillInternalTransactionsAddressIds.migration_name()] + + @impl HeavyDbIndexOperation + def db_index_operation do + HeavyDbIndexOperationHelper.safely_drop_db_index(@index_name) + end + + @impl HeavyDbIndexOperation + def check_db_index_operation_progress do + operation = HeavyDbIndexOperationHelper.drop_index_query_string(@index_name) + HeavyDbIndexOperationHelper.check_db_index_operation_progress(@index_name, operation) + end + + @impl HeavyDbIndexOperation + def db_index_operation_status do + HeavyDbIndexOperationHelper.db_index_dropping_status(@index_name) + end + + @impl HeavyDbIndexOperation + def restart_db_index_operation do + HeavyDbIndexOperationHelper.safely_drop_db_index(@index_name) + end + + @impl HeavyDbIndexOperation + def running_other_heavy_migration_exists?(migration_name) do + MigrationStatus.running_other_heavy_migration_for_table_exists?(@table_name, migration_name) + end + + @impl HeavyDbIndexOperation + def update_cache, do: :ok +end diff --git a/apps/explorer/lib/explorer/migrator/heavy_db_index_operation/drop_internal_transactions_created_contract_address_hash_index.ex b/apps/explorer/lib/explorer/migrator/heavy_db_index_operation/drop_internal_transactions_created_contract_address_hash_index.ex new file mode 100644 index 000000000000..d1d1075cac47 --- /dev/null +++ b/apps/explorer/lib/explorer/migrator/heavy_db_index_operation/drop_internal_transactions_created_contract_address_hash_index.ex @@ -0,0 +1,57 @@ +defmodule Explorer.Migrator.HeavyDbIndexOperation.DropInternalTransactionsCreatedContractAddressHashIndex do + @moduledoc """ + Drops index "internal_transactions_created_contract_address_hash_index" btree (created_contract_address_hash). + """ + + use Explorer.Migrator.HeavyDbIndexOperation + + alias Explorer.Migrator.{HeavyDbIndexOperation, MigrationStatus} + alias Explorer.Migrator.HeavyDbIndexOperation.DropInternalTransactionsBlockNumberCreatedContractAddressHashIndex + alias Explorer.Migrator.HeavyDbIndexOperation.Helper, as: HeavyDbIndexOperationHelper + + @table_name :internal_transactions + @index_name "internal_transactions_created_contract_address_hash_index" + @operation_type :drop + + @impl HeavyDbIndexOperation + def table_name, do: @table_name + + @impl HeavyDbIndexOperation + def operation_type, do: @operation_type + + @impl HeavyDbIndexOperation + def index_name, do: @index_name + + @impl HeavyDbIndexOperation + def dependent_from_migrations, + do: [DropInternalTransactionsBlockNumberCreatedContractAddressHashIndex.migration_name()] + + @impl HeavyDbIndexOperation + def db_index_operation do + HeavyDbIndexOperationHelper.safely_drop_db_index(@index_name) + end + + @impl HeavyDbIndexOperation + def check_db_index_operation_progress do + operation = HeavyDbIndexOperationHelper.drop_index_query_string(@index_name) + HeavyDbIndexOperationHelper.check_db_index_operation_progress(@index_name, operation) + end + + @impl HeavyDbIndexOperation + def db_index_operation_status do + HeavyDbIndexOperationHelper.db_index_dropping_status(@index_name) + end + + @impl HeavyDbIndexOperation + def restart_db_index_operation do + HeavyDbIndexOperationHelper.safely_drop_db_index(@index_name) + end + + @impl HeavyDbIndexOperation + def running_other_heavy_migration_exists?(migration_name) do + MigrationStatus.running_other_heavy_migration_for_table_exists?(@table_name, migration_name) + end + + @impl HeavyDbIndexOperation + def update_cache, do: :ok +end diff --git a/apps/explorer/lib/explorer/migrator/heavy_db_index_operation/drop_internal_transactions_from_address_hash_partial_index.ex b/apps/explorer/lib/explorer/migrator/heavy_db_index_operation/drop_internal_transactions_from_address_hash_partial_index.ex new file mode 100644 index 000000000000..580f58d88d3b --- /dev/null +++ b/apps/explorer/lib/explorer/migrator/heavy_db_index_operation/drop_internal_transactions_from_address_hash_partial_index.ex @@ -0,0 +1,58 @@ +defmodule Explorer.Migrator.HeavyDbIndexOperation.DropInternalTransactionsFromAddressHashPartialIndex do + @moduledoc """ + Drops index "internal_transactions_from_address_hash_partial_index" btree (from_address_hash, block_number DESC, transaction_index DESC, index DESC) WHERE ((((type)::text = 'call'::text) AND (index > 0)) OR ((type)::text <> 'call'::text)). + """ + + use Explorer.Migrator.HeavyDbIndexOperation + + alias Explorer.Migrator.{HeavyDbIndexOperation, MigrationStatus} + + alias Explorer.Migrator.HeavyDbIndexOperation.DropInternalTransactionsCreatedContractAddressHashIndex + alias Explorer.Migrator.HeavyDbIndexOperation.Helper, as: HeavyDbIndexOperationHelper + + @table_name :internal_transactions + @index_name "internal_transactions_from_address_hash_partial_index" + @operation_type :drop + + @impl HeavyDbIndexOperation + def table_name, do: @table_name + + @impl HeavyDbIndexOperation + def operation_type, do: @operation_type + + @impl HeavyDbIndexOperation + def index_name, do: @index_name + + @impl HeavyDbIndexOperation + def dependent_from_migrations, + do: [DropInternalTransactionsCreatedContractAddressHashIndex.migration_name()] + + @impl HeavyDbIndexOperation + def db_index_operation do + HeavyDbIndexOperationHelper.safely_drop_db_index(@index_name) + end + + @impl HeavyDbIndexOperation + def check_db_index_operation_progress do + operation = HeavyDbIndexOperationHelper.drop_index_query_string(@index_name) + HeavyDbIndexOperationHelper.check_db_index_operation_progress(@index_name, operation) + end + + @impl HeavyDbIndexOperation + def db_index_operation_status do + HeavyDbIndexOperationHelper.db_index_dropping_status(@index_name) + end + + @impl HeavyDbIndexOperation + def restart_db_index_operation do + HeavyDbIndexOperationHelper.safely_drop_db_index(@index_name) + end + + @impl HeavyDbIndexOperation + def running_other_heavy_migration_exists?(migration_name) do + MigrationStatus.running_other_heavy_migration_for_table_exists?(@table_name, migration_name) + end + + @impl HeavyDbIndexOperation + def update_cache, do: :ok +end diff --git a/apps/explorer/lib/explorer/migrator/heavy_db_index_operation/drop_internal_transactions_to_address_hash_partial_index.ex b/apps/explorer/lib/explorer/migrator/heavy_db_index_operation/drop_internal_transactions_to_address_hash_partial_index.ex new file mode 100644 index 000000000000..2e7184ea83e5 --- /dev/null +++ b/apps/explorer/lib/explorer/migrator/heavy_db_index_operation/drop_internal_transactions_to_address_hash_partial_index.ex @@ -0,0 +1,58 @@ +defmodule Explorer.Migrator.HeavyDbIndexOperation.DropInternalTransactionsToAddressHashPartialIndex do + @moduledoc """ + Drops index "internal_transactions_to_address_hash_partial_index" btree (to_address_hash, block_number DESC, transaction_index DESC, index DESC) WHERE ((((type)::text = 'call'::text) AND (index > 0)) OR ((type)::text <> 'call'::text)). + """ + + use Explorer.Migrator.HeavyDbIndexOperation + + alias Explorer.Migrator.{HeavyDbIndexOperation, MigrationStatus} + + alias Explorer.Migrator.HeavyDbIndexOperation.DropInternalTransactionsFromAddressHashPartialIndex + alias Explorer.Migrator.HeavyDbIndexOperation.Helper, as: HeavyDbIndexOperationHelper + + @table_name :internal_transactions + @index_name "internal_transactions_to_address_hash_partial_index" + @operation_type :drop + + @impl HeavyDbIndexOperation + def table_name, do: @table_name + + @impl HeavyDbIndexOperation + def operation_type, do: @operation_type + + @impl HeavyDbIndexOperation + def index_name, do: @index_name + + @impl HeavyDbIndexOperation + def dependent_from_migrations, + do: [DropInternalTransactionsFromAddressHashPartialIndex.migration_name()] + + @impl HeavyDbIndexOperation + def db_index_operation do + HeavyDbIndexOperationHelper.safely_drop_db_index(@index_name) + end + + @impl HeavyDbIndexOperation + def check_db_index_operation_progress do + operation = HeavyDbIndexOperationHelper.drop_index_query_string(@index_name) + HeavyDbIndexOperationHelper.check_db_index_operation_progress(@index_name, operation) + end + + @impl HeavyDbIndexOperation + def db_index_operation_status do + HeavyDbIndexOperationHelper.db_index_dropping_status(@index_name) + end + + @impl HeavyDbIndexOperation + def restart_db_index_operation do + HeavyDbIndexOperationHelper.safely_drop_db_index(@index_name) + end + + @impl HeavyDbIndexOperation + def running_other_heavy_migration_exists?(migration_name) do + MigrationStatus.running_other_heavy_migration_for_table_exists?(@table_name, migration_name) + end + + @impl HeavyDbIndexOperation + def update_cache, do: :ok +end diff --git a/apps/explorer/lib/explorer/migrator/heavy_db_index_operation/remove_internal_transactions_block_hash_transaction_hash_block_index_error.ex b/apps/explorer/lib/explorer/migrator/heavy_db_index_operation/remove_internal_transactions_block_hash_transaction_hash_block_index_error.ex new file mode 100644 index 000000000000..503d54e4281f --- /dev/null +++ b/apps/explorer/lib/explorer/migrator/heavy_db_index_operation/remove_internal_transactions_block_hash_transaction_hash_block_index_error.ex @@ -0,0 +1,180 @@ +defmodule Explorer.Migrator.HeavyDbIndexOperation.RemoveInternalTransactionsBlockHashTransactionHashBlockIndexError do + @moduledoc """ + Removes `block_hash`, `transaction_hash`, `block_index` and `error` columns from `internal_transactions` + """ + + use Explorer.Migrator.HeavyDbIndexOperation + + require Logger + + alias Explorer.Migrator.{HeavyDbIndexOperation, MigrationStatus} + + alias Explorer.Migrator.HeavyDbIndexOperation.{ + CreateInternalTransactionsBlockNumberCreatedContractAddressIdPartialIndex, + CreateInternalTransactionsCreatedContractAddressIdIndex, + CreateInternalTransactionsFromAddressIdPartialIndex, + CreateInternalTransactionsToAddressIdPartialIndex, + UpdateInternalTransactionsPrimaryKey + } + + alias Explorer.Repo + + @table_name :internal_transactions + @index_name "internal_transactions_remove_block_hash_transaction_hash_block_index_error" + @operation_type :create + + @impl HeavyDbIndexOperation + def table_name, do: @table_name + + @impl HeavyDbIndexOperation + def operation_type, do: @operation_type + + @impl HeavyDbIndexOperation + def index_name, do: @index_name + + @impl HeavyDbIndexOperation + def dependent_from_migrations, + do: [ + UpdateInternalTransactionsPrimaryKey.migration_name(), + CreateInternalTransactionsCreatedContractAddressIdIndex.migration_name(), + CreateInternalTransactionsBlockNumberCreatedContractAddressIdPartialIndex.migration_name(), + CreateInternalTransactionsFromAddressIdPartialIndex.migration_name(), + CreateInternalTransactionsToAddressIdPartialIndex.migration_name() + ] + + @impl HeavyDbIndexOperation + # sobelow_skip ["SQL"] + def db_index_operation do + Logger.info("Migration RemoveInternalTransactionsBlockHashTransactionHashBlockIndexError started") + + with {:ok, _} <- drop_blocks_foreign_key(), + {:ok, _} <- drop_transactions_foreign_key(), + {:ok, _} <- Repo.query(drop_columns_query_string(), [], timeout: :infinity) do + Logger.info("Migration RemoveInternalTransactionsBlockHashTransactionHashBlockIndexError finished") + :ok + else + {:error, error} -> + Logger.error( + "Migration RemoveInternalTransactionsBlockHashTransactionHashBlockIndexError failed: #{inspect(error)}" + ) + + :error + end + end + + @impl HeavyDbIndexOperation + def check_db_index_operation_progress do + HeavyDbIndexOperationHelper.check_db_index_operation_progress(@index_name, drop_columns_query_string()) + end + + @impl HeavyDbIndexOperation + # credo:disable-for-next-line /Complexity/ + def db_index_operation_status do + completed? = + case Repo.query(""" + SELECT COUNT(*) = 0 + FROM information_schema.columns + WHERE table_name = '#{@table_name}' AND column_name IN ('block_hash', 'transaction_hash', 'block_index', 'error'); + """) do + {:ok, %Postgrex.Result{rows: [[completed]]}} -> completed + _ -> nil + end + + started? = + case check_db_index_operation_progress() do + :in_progress -> true + :finished_or_not_started -> false + _ -> nil + end + + cond do + completed? == true -> :completed + started? == true -> :not_completed + is_nil(completed?) or is_nil(started?) -> :unknown + true -> :not_initialized + end + end + + @impl HeavyDbIndexOperation + # sobelow_skip ["SQL"] + def restart_db_index_operation, do: db_index_operation() + + @impl HeavyDbIndexOperation + def running_other_heavy_migration_exists?(migration_name) do + MigrationStatus.running_other_heavy_migration_for_table_exists?(@table_name, migration_name) + end + + @impl HeavyDbIndexOperation + def update_cache, do: :ok + + # sobelow_skip ["SQL"] + defp drop_blocks_foreign_key do + Repo.transaction( + fn -> + with {:ok, _} <- Repo.query(lock_blocks_query_string(), [], timeout: :infinity), + {:ok, _} <- Repo.query(drop_blocks_foreign_key_query_string(), [], timeout: :infinity) do + Logger.info("Migration RemoveInternalTransactionsBlockHashTransactionHashBlockIndexError finished blocks") + :ok + else + {:error, error} -> Repo.rollback(error) + end + end, + timeout: :infinity + ) + end + + # sobelow_skip ["SQL"] + defp drop_transactions_foreign_key do + Repo.transaction( + fn -> + with {:ok, _} <- Repo.query(lock_transactions_query_string(), [], timeout: :infinity), + {:ok, _} <- Repo.query(drop_transactions_foreign_key_query_string(), [], timeout: :infinity) do + Logger.info( + "Migration RemoveInternalTransactionsBlockHashTransactionHashBlockIndexError finished transactions" + ) + + :ok + else + {:error, error} -> Repo.rollback(error) + end + end, + timeout: :infinity + ) + end + + defp lock_blocks_query_string do + """ + LOCK TABLE blocks IN ACCESS EXCLUSIVE MODE; + """ + end + + defp lock_transactions_query_string do + """ + LOCK TABLE transactions IN ACCESS EXCLUSIVE MODE; + """ + end + + defp drop_blocks_foreign_key_query_string do + """ + ALTER TABLE #{@table_name} + DROP CONSTRAINT IF EXISTS internal_transactions_block_hash_fkey; + """ + end + + defp drop_transactions_foreign_key_query_string do + """ + ALTER TABLE #{@table_name} + DROP CONSTRAINT IF EXISTS internal_transactions_transaction_hash_fkey; + """ + end + + defp drop_columns_query_string do + """ + ALTER TABLE #{@table_name} + DROP COLUMN IF EXISTS block_hash, + DROP COLUMN IF EXISTS transaction_hash, + DROP COLUMN IF EXISTS block_index, + DROP COLUMN IF EXISTS error; + """ + end +end diff --git a/apps/explorer/lib/explorer/migrator/reindex_duplicated_internal_transactions.ex b/apps/explorer/lib/explorer/migrator/reindex_duplicated_internal_transactions.ex deleted file mode 100644 index b489fc469247..000000000000 --- a/apps/explorer/lib/explorer/migrator/reindex_duplicated_internal_transactions.ex +++ /dev/null @@ -1,148 +0,0 @@ -defmodule Explorer.Migrator.ReindexDuplicatedInternalTransactions do - @moduledoc """ - Searches for all blocks that contains internal transactions with duplicated block_number, transaction_index, index, - deletes all internal transactions for such blocks and adds them to pending operations. - """ - - use Explorer.Migrator.FillingMigration - - require Logger - - import Ecto.Query - import Explorer.QueryHelper, only: [select_ctid: 1, join_on_ctid: 2] - - alias Explorer.Repo - - alias Explorer.Chain.{ - Block, - Hash, - InternalTransaction, - PendingBlockOperation - } - - alias Explorer.Migrator.FillingMigration - alias Indexer.Fetcher.InternalTransaction, as: InternalTransactionFetcher - - @migration_name "reindex_duplicated_internal_transactions" - - @impl FillingMigration - def migration_name, do: @migration_name - - @impl FillingMigration - def last_unprocessed_identifiers(%{"last_processed_block_number" => _} = state) do - limit = batch_size() * concurrency() - - ids = - state - |> unprocessed_data_query() - |> distinct(true) - |> limit(^limit) - |> Repo.all(timeout: :infinity) - - case {ids, state["step"]} do - {[], step} when step != "finalize" -> - new_state = Map.put(state, "step", "finalize") - MigrationStatus.update_meta(migration_name(), new_state) - last_unprocessed_identifiers(new_state) - - {ids, step} when step != "finalize" -> - {ids, Map.put(state, "last_processed_block_number", Enum.max(ids))} - - {ids, step} when step == "finalize" -> - {ids, state} - end - end - - def last_unprocessed_identifiers(state) do - state - |> Map.put("last_processed_block_number", -1) - |> last_unprocessed_identifiers() - end - - @impl FillingMigration - def unprocessed_data_query(%{"last_processed_block_number" => last_processed_block_number} = state) do - if state["step"] == "finalize" do - from( - it in InternalTransaction, - select: it.block_hash, - where: not is_nil(it.block_hash), - group_by: [it.block_hash, it.transaction_index, it.index], - having: count("*") > 1 - ) - else - from( - it in InternalTransaction, - select: it.block_number, - where: not is_nil(it.block_number) and it.block_number >= ^last_processed_block_number, - group_by: [it.block_number, it.transaction_index, it.index], - having: count("*") > 1, - order_by: it.block_number - ) - end - end - - @impl FillingMigration - def update_batch(block_numbers_or_hashes) do - now = DateTime.utc_now() - - {it_field, block_field} = - case block_numbers_or_hashes do - [number | _] when is_integer(number) -> {:block_number, :number} - [%Hash{} | _] -> {:block_hash, :hash} - end - - result = - Repo.transaction(fn -> - locked_internal_transactions_to_delete_query = - from( - it in InternalTransaction, - select: select_ctid(it), - where: field(it, ^it_field) in ^block_numbers_or_hashes, - order_by: [asc: it.transaction_hash, asc: it.index], - lock: "FOR UPDATE" - ) - - delete_query = - from( - it in InternalTransaction, - inner_join: locked_it in subquery(locked_internal_transactions_to_delete_query), - on: join_on_ctid(it, locked_it) - ) - - Repo.delete_all(delete_query) - - pbo_params = - Block - |> where([b], field(b, ^block_field) in ^block_numbers_or_hashes) - |> where([b], b.consensus == true) - |> select([b], %{block_hash: b.hash, block_number: b.number}) - |> Repo.all() - |> Enum.map(&Map.merge(&1, %{inserted_at: now, updated_at: now})) - - ordered_pbo_params = Enum.sort_by(pbo_params, &{&1.block_hash}) - - {_total, inserted} = - Repo.insert_all(PendingBlockOperation, ordered_pbo_params, on_conflict: :nothing, returning: [:block_number]) - - inserted - end) - - case result do - {:ok, inserted_pbo} -> - if not is_nil(Process.whereis(InternalTransactionFetcher)) do - inserted_pbo - |> Enum.map(& &1.block_number) - |> InternalTransactionFetcher.async_fetch([], false) - end - - :ok - - {:error, error} -> - Logger.error("Migration #{@migration_name} failed: #{inspect(error)}") - {:error, error} - end - end - - @impl FillingMigration - def update_cache, do: :ok -end diff --git a/apps/explorer/lib/explorer/migrator/reindex_internal_transactions_with_incompatible_status.ex b/apps/explorer/lib/explorer/migrator/reindex_internal_transactions_with_incompatible_status.ex index a7a852f30b36..7c596395c7df 100644 --- a/apps/explorer/lib/explorer/migrator/reindex_internal_transactions_with_incompatible_status.ex +++ b/apps/explorer/lib/explorer/migrator/reindex_internal_transactions_with_incompatible_status.ex @@ -52,7 +52,9 @@ defmodule Explorer.Migrator.ReindexInternalTransactionsWithIncompatibleStatus do it_query = from( it in InternalTransaction, - where: parent_as(:transaction).hash == it.transaction_hash and it.index > 0, + where: + parent_as(:transaction).block_number == it.block_number and + parent_as(:transaction).index == it.transaction_index and it.index > 0, select: 1 ) @@ -60,7 +62,8 @@ defmodule Explorer.Migrator.ReindexInternalTransactionsWithIncompatibleStatus do from( it in InternalTransaction, where: - parent_as(:transaction).hash == it.transaction_hash and it.index > 0 and + parent_as(:transaction).block_number == it.block_number and + parent_as(:transaction).index == it.transaction_index and it.index > 0 and (not is_nil(it.error) or not is_nil(it.error_id)), select: 1 ) diff --git a/apps/explorer/lib/explorer/migrator/shrink_internal_transactions.ex b/apps/explorer/lib/explorer/migrator/shrink_internal_transactions.ex index 29d8f6565b83..1759fb84ec3b 100644 --- a/apps/explorer/lib/explorer/migrator/shrink_internal_transactions.ex +++ b/apps/explorer/lib/explorer/migrator/shrink_internal_transactions.ex @@ -8,7 +8,7 @@ defmodule Explorer.Migrator.ShrinkInternalTransactions do import Ecto.Query - alias Explorer.Chain.{Block, InternalTransaction} + alias Explorer.Chain.InternalTransaction alias Explorer.Migrator.FillingMigration alias Explorer.Repo @@ -47,11 +47,9 @@ defmodule Explorer.Migrator.ShrinkInternalTransactions do @impl FillingMigration def update_batch(block_numbers) do - block_hashes_query = from(b in Block, where: b.number in ^block_numbers, select: b.hash) - query = from(it in InternalTransaction, - where: it.block_hash in subquery(block_hashes_query), + where: it.block_number in ^block_numbers, update: [set: [input: fragment("substring(? FOR 4)", it.input), output: nil]] ) diff --git a/apps/explorer/lib/explorer/module_queue_registry.ex b/apps/explorer/lib/explorer/module_queue_registry.ex new file mode 100644 index 000000000000..3e03b6e5eeb1 --- /dev/null +++ b/apps/explorer/lib/explorer/module_queue_registry.ex @@ -0,0 +1,89 @@ +defmodule Explorer.ModuleQueueRegistry do + @moduledoc """ + Generic registry for module-scoped ETS-backed queues. + """ + + alias Explorer.BoundQueue + + @doc """ + Returns the ETS table name for the queue belonging to the given module. + """ + @callback table_name(module()) :: atom() + + defmacro __using__(_opts) do + quote location: :keep do + @behaviour Explorer.ModuleQueueRegistry + + alias Explorer.ModuleQueueRegistry + + @spec pop(module()) :: term() | nil + def pop(module), do: ModuleQueueRegistry.pop(__MODULE__, module) + + @spec push(term(), module()) :: true | {:error, :maximum_size} + def push(value, module), do: ModuleQueueRegistry.push(__MODULE__, value, module) + end + end + + @doc """ + Pops a value from the front of the queue for the specified module. + """ + @spec pop(module(), module()) :: term() | nil + def pop(registry_module, module) do + table_name = registry_module.table_name(module) + + :global.trans({__MODULE__, table_name}, fn -> + case BoundQueue.pop_front(queue_get(table_name)) do + {:ok, {value, updated_queue}} -> + :ets.insert(table_name, {:queue, updated_queue}) + value + + {:error, :empty} -> + nil + end + end) + end + + @doc """ + Pushes a value to the back of the queue for the specified module. + """ + @spec push(module(), term(), module()) :: true | {:error, :maximum_size} + def push(registry_module, value, module) do + table_name = registry_module.table_name(module) + + :global.trans({__MODULE__, table_name}, fn -> + case BoundQueue.push_back(queue_get(table_name), value) do + {:ok, updated_queue} -> + :ets.insert(table_name, {:queue, updated_queue}) + + {:error, :maximum_size} = error -> + error + end + end) + end + + @spec queue_get(atom()) :: BoundQueue.t(term()) + defp queue_get(table_name) do + ensure_table(table_name) + + case :ets.lookup(table_name, :queue) do + [{:queue, value}] -> value + [] -> %BoundQueue{} + end + end + + defp ensure_table(table_name) do + if :ets.whereis(table_name) == :undefined do + try do + :ets.new(table_name, [ + :set, + :named_table, + :public, + read_concurrency: true, + write_concurrency: true + ]) + rescue + ArgumentError -> :ok + end + end + end +end diff --git a/apps/explorer/lib/explorer/promo/autoscout.ex b/apps/explorer/lib/explorer/promo/autoscout.ex new file mode 100644 index 000000000000..9e295d5fa8ed --- /dev/null +++ b/apps/explorer/lib/explorer/promo/autoscout.ex @@ -0,0 +1,43 @@ +defmodule Explorer.Promo.Autoscout do + @moduledoc """ + Module responsible for logging the Autoscout promo message on application startup + and once again 10 seconds after initialization. + """ + + require Logger + + use GenServer + + @autoscout_promo """ + + + █ █ ███ █ █ █████ ███ ████ ████ ███ █ █ █████ + █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ + █ █ █ █████ █ █ █ █ █ ███ █ █ █ █ █ █ + █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ + █ █ █ █ ███ █ ███ ████ ████ ███ ███ █ + + Deploy Blockscout explorer in 5 minutes at deploy.blockscout.com + """ + + def start_link(_) do + GenServer.start_link(__MODULE__, nil, name: __MODULE__) + end + + @impl GenServer + def init(_) do + Process.send_after(self(), :promo, :timer.seconds(10)) + + {:ok, %{}} + end + + @impl GenServer + def handle_info(:promo, state) do + promo() + {:noreply, state} + end + + defp promo do + Logger.info(@autoscout_promo) + end +end diff --git a/apps/explorer/lib/explorer/repo.ex b/apps/explorer/lib/explorer/repo.ex index 17c445d7bca9..f5d30018d16b 100644 --- a/apps/explorer/lib/explorer/repo.ex +++ b/apps/explorer/lib/explorer/repo.ex @@ -145,7 +145,6 @@ defmodule Explorer.Repo do Explorer.Repo.Mud, Explorer.Repo.Optimism, Explorer.Repo.PolygonEdge, - Explorer.Repo.PolygonZkevm, Explorer.Repo.RSK, Explorer.Repo.Scroll, Explorer.Repo.Shibarium, diff --git a/apps/explorer/lib/explorer/smart_contract/helper.ex b/apps/explorer/lib/explorer/smart_contract/helper.ex index 8507825cb162..1be9a85cab1e 100644 --- a/apps/explorer/lib/explorer/smart_contract/helper.ex +++ b/apps/explorer/lib/explorer/smart_contract/helper.ex @@ -229,7 +229,7 @@ defmodule Explorer.SmartContract.Helper do defp internal_transaction_to_metadata(internal_transaction, init) do %{ "blockNumber" => to_string(internal_transaction.block_number), - "transactionHash" => to_string(internal_transaction.transaction_hash), + "transactionHash" => to_string(internal_transaction.transaction.hash), "transactionIndex" => to_string(internal_transaction.transaction_index), "deployer" => to_string(internal_transaction.from_address_hash), "creationCode" => to_string(init) diff --git a/apps/explorer/lib/explorer/smart_contract/stylus/verifier.ex b/apps/explorer/lib/explorer/smart_contract/stylus/verifier.ex index e1f6baebe816..86ddeb0c85b3 100644 --- a/apps/explorer/lib/explorer/smart_contract/stylus/verifier.ex +++ b/apps/explorer/lib/explorer/smart_contract/stylus/verifier.ex @@ -100,7 +100,7 @@ defmodule Explorer.SmartContract.Stylus.Verifier do transaction.hash %{internal_transaction: internal_transaction} -> - internal_transaction.transaction_hash + internal_transaction.transaction.hash _ -> nil diff --git a/apps/explorer/lib/explorer/stats/hot_smart_contracts.ex b/apps/explorer/lib/explorer/stats/hot_smart_contracts.ex index f11d52ae52ab..291cfc913b1f 100644 --- a/apps/explorer/lib/explorer/stats/hot_smart_contracts.ex +++ b/apps/explorer/lib/explorer/stats/hot_smart_contracts.ex @@ -137,7 +137,7 @@ defmodule Explorer.Stats.HotSmartContracts do BlockReaderGeneral.timestamp_to_block_number(now, :before, options[:api?] || false, true) do from_block |> aggregate_hot_smart_contracts_for_block_interval_query(to_block) - |> ExplorerHelper.maybe_hide_scam_addresses(:to_address_hash, options) + |> ExplorerHelper.maybe_hide_scam_addresses({:to_address, :hash}, options) |> SortingHelper.apply_sorting(sorting_options, default_sorting) |> SortingHelper.page_with_sorting(paging_options, sorting_options, default_sorting) |> Chain.select_repo(options).all() diff --git a/apps/explorer/lib/explorer/token/metadata_retriever.ex b/apps/explorer/lib/explorer/token/metadata_retriever.ex index b76ad4a91a1c..c1818634328a 100644 --- a/apps/explorer/lib/explorer/token/metadata_retriever.ex +++ b/apps/explorer/lib/explorer/token/metadata_retriever.ex @@ -14,7 +14,7 @@ defmodule Explorer.Token.MetadataRetriever do @vm_execution_error "VM execution error" @invalid_base64_data "invalid data:application/json;base64" @invalid_ipfs_path "invalid ipfs path" - @default_headers [{"User-Agent", "blockscout-10.0.6"}] + @default_headers [{"User-Agent", "blockscout-11.0.0"}] # https://eips.ethereum.org/EIPS/eip-1155#metadata @erc1155_token_id_placeholder "{id}" diff --git a/apps/explorer/lib/explorer/utility/address_id_to_address_hash.ex b/apps/explorer/lib/explorer/utility/address_id_to_address_hash.ex index 5ee9ffa2d981..d81ddda40043 100644 --- a/apps/explorer/lib/explorer/utility/address_id_to_address_hash.ex +++ b/apps/explorer/lib/explorer/utility/address_id_to_address_hash.ex @@ -24,6 +24,72 @@ defmodule Explorer.Utility.AddressIdToAddressHash do cast(address_id_to_address_hash, params, [:address_id, :address_hash]) end + @doc """ + Finds the mapping for the given address hash or creates it if it does not yet + exist. + + This function is a convenience wrapper around `find_or_create_multiple/2` + for a single address hash. + + ## Parameters + - `address_hash`: The address hash to look up or create a mapping for + + ## Returns + - An `%Explorer.Utility.AddressIdToAddressHash{}` struct for the given hash + - `nil` if `address_hash` is `nil` + """ + @spec find_or_create(Hash.Address.t() | nil) :: __MODULE__.t() | nil + def find_or_create(address_hash) do + [address_hash] + |> find_or_create_multiple(false) + |> List.first() + end + + @doc """ + Finds or creates mappings for the given address hashes in bulk. + + The input is normalized by removing `nil` values, deduplicating hashes, and + casting each hash to `Hash.Address`. Missing mappings are inserted with + `on_conflict: :nothing`, so existing mappings are preserved. + + ## Parameters + - `address_hashes`: A list of address hashes to resolve + - `to_map?`: When `true`, returns a map of `%{address_hash => address_id}`. + When `false`, returns the list of `%Explorer.Utility.AddressIdToAddressHash{}` + records + + ## Returns + - A map of address hashes to address ids when `to_map?` is `true` + - A list of `%Explorer.Utility.AddressIdToAddressHash{}` structs when + `to_map?` is `false` + """ + @spec find_or_create_multiple([Hash.Address.t() | nil], true) :: %{optional(Hash.Address.t()) => integer()} + @spec find_or_create_multiple([Hash.Address.t() | nil], false) :: [__MODULE__.t()] + def find_or_create_multiple(address_hashes, to_map? \\ true) do + filtered_address_hashes = + address_hashes + |> Enum.reject(&is_nil/1) + |> Enum.uniq() + |> Enum.map(fn address_hash -> + {:ok, casted} = Hash.Address.cast(address_hash) + casted + end) + |> Enum.sort() + + Repo.insert_all( + __MODULE__, + Enum.map(filtered_address_hashes, &%{address_hash: &1}), + on_conflict: :nothing + ) + + __MODULE__ + |> where([a], a.address_hash in ^filtered_address_hashes) + |> Repo.all() + |> then(fn records -> + if to_map?, do: Map.new(records, &{to_string(&1.address_hash), &1.address_id}), else: records + end) + end + @doc """ Retrieves the address_id for a given address_hash. @@ -34,10 +100,69 @@ defmodule Explorer.Utility.AddressIdToAddressHash do - The address_id if found, nil otherwise """ @spec hash_to_id(Hash.Address.t()) :: integer() | nil + def hash_to_id(nil), do: nil + def hash_to_id(hash) do + [hash] + |> hashes_to_ids() + |> List.first() + end + + @doc """ + Retrieves all address ids for the given address hashes. + + ## Parameters + - `hashes`: A list of address hashes to look up + + ## Returns + - A list of address ids for the matching mappings + """ + @spec hashes_to_ids([Hash.Address.t()]) :: [integer()] + def hashes_to_ids(hashes) do __MODULE__ - |> where([a], a.address_hash == ^hash) + |> where([a], a.address_hash in ^hashes) |> select([a], a.address_id) - |> Repo.one() + |> Repo.all() + end + + @doc """ + Retrieves the address hash for a given address_id. + + This function is a convenience wrapper around `ids_to_hashes/1` for a single + address id. + + ## Parameters + - `id`: The address id to look up + + ## Returns + - The address hash if found + - `nil` if the id is `nil` or no mapping exists + """ + @spec id_to_hash(integer() | nil) :: Hash.Address.t() | nil + def id_to_hash(nil), do: nil + + def id_to_hash(id) do + [id] + |> ids_to_hashes() + |> List.first() + end + + @doc """ + Retrieves all address hashes for the given address ids. + + ## Parameters + - `ids`: A list of address ids to look up + + ## Returns + - A list of address hashes for the matching mappings + """ + @spec ids_to_hashes([integer()]) :: [Hash.Address.t()] + def ids_to_hashes([]), do: [] + + def ids_to_hashes(ids) do + __MODULE__ + |> where([a], a.address_id in ^ids) + |> select([a], a.address_hash) + |> Repo.all() end end diff --git a/apps/explorer/lib/explorer/utility/internal_transactions_address_placeholder.ex b/apps/explorer/lib/explorer/utility/internal_transactions_address_placeholder.ex index 7eb78ebc3ad9..1ec7781eb2af 100644 --- a/apps/explorer/lib/explorer/utility/internal_transactions_address_placeholder.ex +++ b/apps/explorer/lib/explorer/utility/internal_transactions_address_placeholder.ex @@ -6,6 +6,8 @@ defmodule Explorer.Utility.InternalTransactionsAddressPlaceholder do use Explorer.Schema + alias Explorer.Repo + @primary_key false typed_schema "deleted_internal_transactions_address_placeholders" do field(:address_id, :integer, primary_key: true) @@ -18,4 +20,9 @@ defmodule Explorer.Utility.InternalTransactionsAddressPlaceholder do def changeset(placeholder \\ %__MODULE__{}, params) do cast(placeholder, params, [:address_id, :block_number, :count_tos, :count_froms]) end + + @spec empty?() :: boolean() + def empty? do + not Repo.exists?(__MODULE__) + end end diff --git a/apps/explorer/lib/explorer/utility/missing_block_range.ex b/apps/explorer/lib/explorer/utility/missing_block_range.ex index d66901feddc4..021a99e70b06 100644 --- a/apps/explorer/lib/explorer/utility/missing_block_range.ex +++ b/apps/explorer/lib/explorer/utility/missing_block_range.ex @@ -5,6 +5,7 @@ defmodule Explorer.Utility.MissingBlockRange do """ use Explorer.Schema + alias EthereumJSONRPC.Utility.RangesHelper alias Explorer.Chain.{Block, BlockNumberHelper} alias Explorer.Repo @@ -110,6 +111,7 @@ defmodule Explorer.Utility.MissingBlockRange do @spec add_ranges_by_block_numbers([Block.block_number()], integer() | nil) :: :ok def add_ranges_by_block_numbers(numbers, priority \\ nil) do numbers + |> RangesHelper.filter_by_block_ranges() |> numbers_to_ranges() |> save_batch(priority) @@ -385,10 +387,19 @@ defmodule Explorer.Utility.MissingBlockRange do # Inserts a new missing block range record with the provided parameters @spec insert_range(map()) :: {:ok, t()} | {:error, Ecto.Changeset.t()} - defp insert_range(params) do - params - |> changeset() - |> Repo.insert() + defp insert_range(%{from_number: from, to_number: to} = params) do + existing_record = + __MODULE__ + |> where([r], r.from_number == ^from and r.to_number == ^to) + |> Repo.one() + + if existing_record do + {:ok, existing_record} + else + params + |> changeset() + |> Repo.insert(on_conflict: :nothing, conflict_target: [:from_number, :to_number]) + end end # Updates a missing block range record with the provided parameters diff --git a/apps/explorer/lib/explorer/utility/version_upgrade.ex b/apps/explorer/lib/explorer/utility/version_upgrade.ex new file mode 100644 index 000000000000..ed2c353008f4 --- /dev/null +++ b/apps/explorer/lib/explorer/utility/version_upgrade.ex @@ -0,0 +1,118 @@ +defmodule Explorer.Utility.VersionUpgrade do + @moduledoc """ + Provides validation logic for application version upgrades. + + This module ensures that upgrading from one version of the application + to another is allowed according to a set of predefined rules. It is + designed to prevent unsafe upgrades that could lead to inconsistent + data or incomplete migrations. + + ## Upgrade Rules + + Upgrade rules are defined as a list of maps in `@upgrade_rules`. Each rule + applies to a range of target versions and has the following structure: + + * `:since` — the minimum target version (inclusive) for which the rule applies + * `:min_from` — the minimum allowed source version (inclusive) + * `:required_completed_migrations` — a list of migration names that must + have status `"completed"` before the upgrade is allowed + + Rules are evaluated based on the target version. If multiple rules match, + the most specific one (with the highest `:since` version) is applied. + + If no rule matches the target version, the upgrade is allowed by default. + """ + + alias Explorer.Application.Constants + alias Explorer.Chain.Block + alias Explorer.Migrator.HeavyDbIndexOperation.UpdateInternalTransactionsPrimaryKey + alias Explorer.Migrator.MigrationStatus + alias Explorer.Repo + + @upgrade_rules [ + %{ + since: "11.0.0", + min_from: "10.2.3", + required_completed_migrations: [UpdateInternalTransactionsPrimaryKey.migration_name()] + } + ] + + def validate_current_upgrade do + previous_version = Constants.get_previous_backend_version() + current_version = Constants.get_current_backend_version() + + validate_upgrade(previous_version, current_version) + end + + def validate_upgrade(nil, to_version) do + case find_applicable_rule(to_version) do + nil -> + :ok + + %{min_from: min_from} -> + if Repo.exists?(Block) do + raise_wrong_version(nil, to_version, min_from) + else + :ok + end + end + end + + def validate_upgrade(from_version, to_version) do + case find_applicable_rule(to_version) do + nil -> + :ok + + rule -> + validate_min_from!(from_version, to_version, rule) + validate_required_migrations!(to_version, rule) + end + end + + defp find_applicable_rule(to_version) do + @upgrade_rules + |> Enum.filter(fn %{since: since_version} -> + Version.compare(to_version, since_version) in [:eq, :gt] + end) + |> Enum.max_by(&Version.parse!(&1.since), Version) + end + + defp validate_min_from!(from_version, to_version, %{min_from: min_from}) do + if Version.compare(from_version, min_from) in [:eq, :gt] do + :ok + else + raise_wrong_version(from_version, to_version, min_from) + end + end + + defp validate_required_migrations!(_to_version, %{required_completed_migrations: []}), do: :ok + + defp validate_required_migrations!(to_version, %{required_completed_migrations: migration_names}) do + not_completed = + Enum.flat_map(migration_names, fn migration_name -> + status = MigrationStatus.get_status(migration_name) + + if status == "completed" do + [] + else + ["#{migration_name} (status: #{inspect(status)})"] + end + end) + + if not_completed == [] do + :ok + else + raise_not_completed_migrations(to_version, not_completed) + end + end + + defp validate_required_migrations!(_to_version, _rule), do: :ok + + defp raise_wrong_version(from_version, to_version, min_from) do + raise "Upgrade to #{to_version} is allowed only from version #{min_from} and higher. Current previous version: #{from_version || "(empty)"}" + end + + defp raise_not_completed_migrations(to_version, not_completed) do + raise "Upgrade to #{to_version} is not allowed because required migrations are not completed: #{Enum.join(not_completed, ", ")}" + end +end diff --git a/apps/explorer/mix.exs b/apps/explorer/mix.exs index a5fbcf6f8442..10ffed5e493a 100644 --- a/apps/explorer/mix.exs +++ b/apps/explorer/mix.exs @@ -20,7 +20,7 @@ defmodule Explorer.Mixfile do lockfile: "../../mix.lock", package: package(), start_permanent: Mix.env() == :prod, - version: "10.2.1", + version: "11.0.0", xref: [exclude: [BlockScoutWeb.Routers.WebRouter.Helpers, Indexer.Helper, Indexer.Fetcher.InternalTransaction]] ] end @@ -111,7 +111,7 @@ defmodule Explorer.Mixfile do # `:spandex` tracing of `:ecto` {:spandex_ecto, "~> 0.7.0"}, # Attach `:prometheus_ecto` to `:ecto` - {:telemetry, "~> 1.3.0"}, + {:telemetry, "~> 1.4.1"}, # `Timex.Duration` for `Explorer.Chain.Cache.Counters.AverageBlockTime.average_block_time/0` {:timex, "~> 3.7.1"}, {:con_cache, "~> 1.0"}, @@ -135,7 +135,8 @@ defmodule Explorer.Mixfile do {:inet_cidr, "~> 1.0.0"}, {:hammer, "~> 7.0"}, {:ton, "~> 0.5.0"}, - {:mint, "~> 1.0"} + {:mint, "~> 1.0"}, + {:oban, "~> 2.19"} ] end @@ -162,7 +163,7 @@ defmodule Explorer.Mixfile do defp package do [ maintainers: ["Blockscout"], - licenses: ["GPL 3.0"], + licenses: ["Blockscout Software Licence"], links: %{"GitHub" => "https://github.com/blockscout/blockscout"} ] end diff --git a/apps/explorer/package-lock.json b/apps/explorer/package-lock.json index 6fb65c57db57..a7e7328459ef 100644 --- a/apps/explorer/package-lock.json +++ b/apps/explorer/package-lock.json @@ -5,7 +5,7 @@ "packages": { "": { "name": "blockscout", - "license": "GPL-3.0", + "license": "SEE LICENSE IN ../../LICENSE", "dependencies": { "solc": "0.8.30" }, diff --git a/apps/explorer/package.json b/apps/explorer/package.json index fe64f0e1bb1b..85878348285a 100644 --- a/apps/explorer/package.json +++ b/apps/explorer/package.json @@ -6,7 +6,7 @@ "private": true, "name": "blockscout", "author": "Blockscout", - "license": "GPL-3.0", + "license": "SEE LICENSE IN ../../LICENSE", "engines": { "node": "18.x", "npm": "8.x" diff --git a/apps/explorer/priv/polygon_zkevm/migrations/20230420110800_create_zkevm_tables.exs b/apps/explorer/priv/polygon_zkevm/migrations/20230420110800_create_zkevm_tables.exs deleted file mode 100644 index 1c242e3b8ec4..000000000000 --- a/apps/explorer/priv/polygon_zkevm/migrations/20230420110800_create_zkevm_tables.exs +++ /dev/null @@ -1,55 +0,0 @@ -defmodule Explorer.Repo.PolygonZkevm.Migrations.CreateZkevmTables do - use Ecto.Migration - - def change do - create table(:zkevm_lifecycle_l1_transactions, primary_key: false) do - add(:id, :integer, null: false, primary_key: true) - add(:hash, :bytea, null: false) - add(:is_verify, :boolean, null: false) - timestamps(null: false, type: :utc_datetime_usec) - end - - create(unique_index(:zkevm_lifecycle_l1_transactions, :hash)) - - create table(:zkevm_transaction_batches, primary_key: false) do - add(:number, :integer, null: false, primary_key: true) - add(:timestamp, :"timestamp without time zone", null: false) - add(:l2_transactions_count, :integer, null: false) - add(:global_exit_root, :bytea, null: false) - add(:acc_input_hash, :bytea, null: false) - add(:state_root, :bytea, null: false) - - add( - :sequence_id, - references(:zkevm_lifecycle_l1_transactions, on_delete: :restrict, on_update: :update_all, type: :integer), - null: true - ) - - add( - :verify_id, - references(:zkevm_lifecycle_l1_transactions, on_delete: :restrict, on_update: :update_all, type: :integer), - null: true - ) - - timestamps(null: false, type: :utc_datetime_usec) - end - - create table(:zkevm_batch_l2_transactions, primary_key: false) do - add( - :batch_number, - references(:zkevm_transaction_batches, - column: :number, - on_delete: :delete_all, - on_update: :update_all, - type: :integer - ), - null: false - ) - - add(:hash, :bytea, null: false, primary_key: true) - timestamps(null: false, type: :utc_datetime_usec) - end - - create(index(:zkevm_batch_l2_transactions, :batch_number)) - end -end diff --git a/apps/explorer/priv/polygon_zkevm/migrations/20231010093238_add_bridge_tables.exs b/apps/explorer/priv/polygon_zkevm/migrations/20231010093238_add_bridge_tables.exs deleted file mode 100644 index 805acd4d6a70..000000000000 --- a/apps/explorer/priv/polygon_zkevm/migrations/20231010093238_add_bridge_tables.exs +++ /dev/null @@ -1,46 +0,0 @@ -defmodule Explorer.Repo.PolygonZkevm.Migrations.AddBridgeTables do - use Ecto.Migration - - def change do - create table(:polygon_zkevm_bridge_l1_tokens, primary_key: false) do - add(:id, :identity, primary_key: true, start_value: 0, increment: 1) - add(:address, :bytea, null: false) - add(:decimals, :smallint, null: true, default: nil) - add(:symbol, :string, size: 16, null: true, default: nil) - timestamps(null: false, type: :utc_datetime_usec) - end - - create(unique_index(:polygon_zkevm_bridge_l1_tokens, :address)) - - execute( - "CREATE TYPE polygon_zkevm_bridge_op_type AS ENUM ('deposit', 'withdrawal')", - "DROP TYPE polygon_zkevm_bridge_op_type" - ) - - create table(:polygon_zkevm_bridge, primary_key: false) do - add(:type, :polygon_zkevm_bridge_op_type, null: false, primary_key: true) - add(:index, :integer, null: false, primary_key: true) - add(:l1_transaction_hash, :bytea, null: true) - add(:l2_transaction_hash, :bytea, null: true) - - add( - :l1_token_id, - references(:polygon_zkevm_bridge_l1_tokens, on_delete: :restrict, on_update: :update_all, type: :identity), - null: true - ) - - add(:l1_token_address, :bytea, null: true) - add(:l2_token_address, :bytea, null: true) - add(:amount, :numeric, precision: 100, null: false) - add(:block_number, :bigint, null: true) - add(:block_timestamp, :"timestamp without time zone", null: true) - timestamps(null: false, type: :utc_datetime_usec) - end - - create(index(:polygon_zkevm_bridge, :l1_token_address)) - - rename(table(:zkevm_lifecycle_l1_transactions), to: table(:polygon_zkevm_lifecycle_l1_transactions)) - rename(table(:zkevm_transaction_batches), to: table(:polygon_zkevm_transaction_batches)) - rename(table(:zkevm_batch_l2_transactions), to: table(:polygon_zkevm_batch_l2_transactions)) - end -end diff --git a/apps/explorer/priv/polygon_zkevm/migrations/20240306080627_make_timestamp_optional.exs b/apps/explorer/priv/polygon_zkevm/migrations/20240306080627_make_timestamp_optional.exs deleted file mode 100644 index f813ba8ba735..000000000000 --- a/apps/explorer/priv/polygon_zkevm/migrations/20240306080627_make_timestamp_optional.exs +++ /dev/null @@ -1,9 +0,0 @@ -defmodule Explorer.Repo.PolygonZkevm.Migrations.MakeTimestampOptional do - use Ecto.Migration - - def change do - alter table("polygon_zkevm_transaction_batches") do - modify(:timestamp, :"timestamp without time zone", null: true) - end - end -end diff --git a/apps/explorer/priv/repo/migrations/20260209173317_add_csv_export_requests.exs b/apps/explorer/priv/repo/migrations/20260209173317_add_csv_export_requests.exs new file mode 100644 index 000000000000..88a279f41be6 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20260209173317_add_csv_export_requests.exs @@ -0,0 +1,22 @@ +defmodule Explorer.Repo.Migrations.AddCsvExportRequests do + use Ecto.Migration + + def change do + create table(:csv_export_requests, primary_key: false) do + add(:id, :uuid, primary_key: true, null: false) + add(:remote_ip_hash, :bytea, null: false) + add(:file_id, :string) + add(:status, :string, default: "pending", null: false) + add(:expires_at, :utc_datetime, null: true) + + timestamps(type: :utc_datetime_usec) + end + + create( + index(:csv_export_requests, [:remote_ip_hash], + where: "status = 'pending'", + name: :csv_export_requests_pending_per_ip + ) + ) + end +end diff --git a/apps/explorer/priv/repo/migrations/20260311085615_remove_internal_transactions_transaction_hash_not_null.exs b/apps/explorer/priv/repo/migrations/20260311085615_remove_internal_transactions_transaction_hash_not_null.exs new file mode 100644 index 000000000000..0fd47b72d4fe --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20260311085615_remove_internal_transactions_transaction_hash_not_null.exs @@ -0,0 +1,9 @@ +defmodule Explorer.Repo.Migrations.RemoveInternalTransactionsTransactionHashNotNull do + use Ecto.Migration + + def change do + alter table(:internal_transactions) do + modify(:transaction_hash, :bytea, null: true) + end + end +end diff --git a/apps/explorer/priv/repo/migrations/20260317081858_add_address_ids_to_internal_transactions.exs b/apps/explorer/priv/repo/migrations/20260317081858_add_address_ids_to_internal_transactions.exs new file mode 100644 index 000000000000..213dc3cf80d4 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20260317081858_add_address_ids_to_internal_transactions.exs @@ -0,0 +1,58 @@ +defmodule Explorer.Repo.Migrations.AddAddressIdsToInternalTransactions do + use Ecto.Migration + + def change do + alter table(:internal_transactions) do + add(:from_address_id, :bigint) + add(:to_address_id, :bigint) + add(:created_contract_address_id, :bigint) + end + + drop_if_exists( + constraint( + :internal_transactions, + :selfdestruct_has_from_and_to_address, + check: + "type != 'selfdestruct' OR (from_address_hash IS NOT NULL AND gas IS NULL AND to_address_hash IS NOT NULL)", + validate: false + ) + ) + + create( + constraint( + :internal_transactions, + :selfdestruct_has_from_and_to_address, + check: "type != 'selfdestruct' OR (from_address_id IS NOT NULL AND gas IS NULL AND to_address_id IS NOT NULL)", + validate: false + ) + ) + + drop_if_exists( + constraint( + :internal_transactions, + :create_has_error_id_or_result, + check: """ + type != 'create' OR + (gas IS NOT NULL AND + ((error_id IS NULL AND created_contract_address_hash IS NOT NULL AND created_contract_code IS NOT NULL AND gas_used IS NOT NULL) OR + (error_id IS NOT NULL AND created_contract_address_hash IS NULL AND created_contract_code IS NULL AND gas_used IS NULL))) + """, + validate: false + ) + ) + + create( + constraint( + :internal_transactions, + :create_has_error_id_or_result, + check: """ + type != 'create' OR + (gas IS NOT NULL AND + ((error_id IS NULL AND created_contract_address_id IS NOT NULL AND created_contract_code IS NOT NULL AND gas_used IS NOT NULL) OR + (error_id IS NOT NULL AND created_contract_address_id IS NULL AND created_contract_code IS NULL AND gas_used IS NULL))) + """, + validate: false + ) + ) + end +end diff --git a/apps/explorer/priv/repo/migrations/20260402093149_remove-tx-actions.exs b/apps/explorer/priv/repo/migrations/20260402093149_remove-tx-actions.exs new file mode 100644 index 000000000000..ad9e2e9485b6 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20260402093149_remove-tx-actions.exs @@ -0,0 +1,11 @@ +defmodule :"Elixir.Explorer.Repo.Migrations.Remove-tx-actions" do + use Ecto.Migration + + def change do + drop(table(:transaction_actions)) + + execute("DROP TYPE transaction_actions_protocol") + + execute("DROP TYPE transaction_actions_type") + end +end diff --git a/apps/explorer/priv/repo/migrations/20260406100324_add_oban_v13.exs b/apps/explorer/priv/repo/migrations/20260406100324_add_oban_v13.exs new file mode 100644 index 000000000000..c3a9e945a5b1 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20260406100324_add_oban_v13.exs @@ -0,0 +1,7 @@ +defmodule Explorer.Repo.Migrations.AddObanV13 do + use Ecto.Migration + + def up, do: Oban.Migrations.up(version: 13) + + def down, do: Oban.Migrations.down(version: 1) +end diff --git a/apps/explorer/priv/shrunk_internal_transactions/migrations/20240717080512_remove_internal_transactions_constraints.exs b/apps/explorer/priv/shrunk_internal_transactions/migrations/20240717080512_remove_internal_transactions_constraints.exs index 05a7b5323d06..a2bbdbc7cde9 100644 --- a/apps/explorer/priv/shrunk_internal_transactions/migrations/20240717080512_remove_internal_transactions_constraints.exs +++ b/apps/explorer/priv/shrunk_internal_transactions/migrations/20240717080512_remove_internal_transactions_constraints.exs @@ -4,7 +4,7 @@ defmodule Explorer.Repo.ShrunkInternalTransactions.Migrations.RemoveInternalTran def change do drop(constraint(:internal_transactions, :call_has_input, check: "type != 'call' OR input IS NOT NULL")) - drop( + drop_if_exists( constraint(:internal_transactions, :call_has_error_or_result, check: """ type != 'call' OR diff --git a/apps/explorer/priv/shrunk_internal_transactions/migrations/20260407115103_remove_call_has_error_id_or_result_constraint.exs b/apps/explorer/priv/shrunk_internal_transactions/migrations/20260407115103_remove_call_has_error_id_or_result_constraint.exs new file mode 100644 index 000000000000..4272a6dcb226 --- /dev/null +++ b/apps/explorer/priv/shrunk_internal_transactions/migrations/20260407115103_remove_call_has_error_id_or_result_constraint.exs @@ -0,0 +1,16 @@ +defmodule Explorer.Repo.ShrunkInternalTransactions.Migrations.RemoveCallHasErrorIdOrResultConstraint do + use Ecto.Migration + + def change do + drop_if_exists( + constraint(:internal_transactions, :call_has_error_id_or_result, + check: """ + type != 'call' OR + (gas IS NOT NULL AND + ((error_id IS NULL AND gas_used IS NOT NULL AND output IS NOT NULL) OR + (error_id IS NOT NULL AND output is NULL))) + """ + ) + ) + end +end diff --git a/apps/explorer/test/explorer/chain/address/coin_balance_test.exs b/apps/explorer/test/explorer/chain/address/coin_balance_test.exs index b80190ca02fe..554da911ea37 100644 --- a/apps/explorer/test/explorer/chain/address/coin_balance_test.exs +++ b/apps/explorer/test/explorer/chain/address/coin_balance_test.exs @@ -429,8 +429,7 @@ defmodule Explorer.Chain.Address.CoinBalanceTest do index: 0, transaction: transaction, transaction_index: transaction.index, - block_number: transaction.block_number, - block_hash: transaction.block_hash + block_number: transaction.block_number ) balance = insert(:unfetched_balance, address_hash: created_contract_address.hash, block_number: block.number) @@ -476,8 +475,7 @@ defmodule Explorer.Chain.Address.CoinBalanceTest do index: 0, transaction: transaction, transaction_index: transaction.index, - block_number: transaction.block_number, - block_hash: transaction.block_hash + block_number: transaction.block_number ) balance = insert(:unfetched_balance, address_hash: from_address.hash, block_number: block.number) @@ -517,8 +515,7 @@ defmodule Explorer.Chain.Address.CoinBalanceTest do index: 0, transaction: transaction, transaction_index: transaction.index, - block_number: transaction.block_number, - block_hash: transaction.block_hash + block_number: transaction.block_number ) balance = insert(:unfetched_balance, address_hash: to_address.hash, block_number: block.number) @@ -587,8 +584,7 @@ defmodule Explorer.Chain.Address.CoinBalanceTest do index: 0, transaction: from_internal_transaction_transaction, transaction_index: from_internal_transaction_transaction.index, - block_number: from_internal_transaction_transaction.block_number, - block_hash: from_internal_transaction_transaction.block_hash + block_number: from_internal_transaction_transaction.block_number ) insert(:unfetched_balance, address_hash: miner.hash, block_number: from_internal_transaction_block.number) @@ -606,8 +602,7 @@ defmodule Explorer.Chain.Address.CoinBalanceTest do to_address: miner, transaction: to_internal_transaction_transaction, transaction_index: to_internal_transaction_transaction.index, - block_number: to_internal_transaction_transaction.block_number, - block_hash: to_internal_transaction_transaction.block_hash + block_number: to_internal_transaction_transaction.block_number ) insert(:unfetched_balance, address_hash: miner.hash, block_number: to_internal_transaction_block.number) @@ -663,7 +658,6 @@ defmodule Explorer.Chain.Address.CoinBalanceTest do index: 0, transaction: from_internal_transaction_transaction, block_number: from_internal_transaction_transaction.block_number, - block_hash: from_internal_transaction_transaction.block_hash, transaction_index: from_internal_transaction_transaction.index ) @@ -678,7 +672,6 @@ defmodule Explorer.Chain.Address.CoinBalanceTest do index: 0, transaction: to_internal_transaction_transaction, block_number: to_internal_transaction_transaction.block_number, - block_hash: to_internal_transaction_transaction.block_hash, transaction_index: to_internal_transaction_transaction.index ) diff --git a/apps/explorer/test/explorer/chain/address_test.exs b/apps/explorer/test/explorer/chain/address_test.exs index b731a4b7b376..97107539c7af 100644 --- a/apps/explorer/test/explorer/chain/address_test.exs +++ b/apps/explorer/test/explorer/chain/address_test.exs @@ -155,7 +155,7 @@ defmodule Explorer.Chain.AddressTest do :token, [smart_contract: :smart_contract_additional_sources], Explorer.Chain.SmartContract.Proxy.Models.Implementation.proxy_implementations_association() - ] ++ Address.contract_creation_transaction_associations() + ] ++ [Address.contract_creation_transaction_association()] ) options = [ @@ -163,7 +163,7 @@ defmodule Explorer.Chain.AddressTest do :names => :optional, :smart_contract => :optional, :token => :optional, - Address.contract_creation_transaction_associations() => :optional + Address.contract_creation_transaction_association() => :optional } ] @@ -172,50 +172,4 @@ defmodule Explorer.Chain.AddressTest do assert response == {:ok, address} end end - - describe "contract_creation_transaction_associations/1" do - test "by default includes both transaction and internal transaction associations" do - associations = Address.contract_creation_transaction_associations() - - assert Enum.any?(associations, &Keyword.has_key?(&1, :contract_creation_transaction)) - assert Enum.any?(associations, &Keyword.has_key?(&1, :contract_creation_internal_transaction)) - end - - test "includes both associations when include_internal_transaction is true" do - associations = Address.contract_creation_transaction_associations(true) - - assert Enum.any?(associations, &Keyword.has_key?(&1, :contract_creation_transaction)) - assert Enum.any?(associations, &Keyword.has_key?(&1, :contract_creation_internal_transaction)) - end - - test "excludes internal transaction association when include_internal_transaction is false" do - associations = Address.contract_creation_transaction_associations(false) - - assert Enum.any?(associations, &Keyword.has_key?(&1, :contract_creation_transaction)) - refute Enum.any?(associations, &Keyword.has_key?(&1, :contract_creation_internal_transaction)) - end - end - - describe "contract_creation_transaction_with_from_address_associations/1" do - test "by default includes both transaction and internal transaction associations" do - associations = Address.contract_creation_transaction_with_from_address_associations() - - assert Enum.any?(associations, &Keyword.has_key?(&1, :contract_creation_transaction)) - assert Enum.any?(associations, &Keyword.has_key?(&1, :contract_creation_internal_transaction)) - end - - test "includes both associations when include_internal_transaction is true" do - associations = Address.contract_creation_transaction_with_from_address_associations(true) - - assert Enum.any?(associations, &Keyword.has_key?(&1, :contract_creation_transaction)) - assert Enum.any?(associations, &Keyword.has_key?(&1, :contract_creation_internal_transaction)) - end - - test "excludes internal transaction association when include_internal_transaction is false" do - associations = Address.contract_creation_transaction_with_from_address_associations(false) - - assert Enum.any?(associations, &Keyword.has_key?(&1, :contract_creation_transaction)) - refute Enum.any?(associations, &Keyword.has_key?(&1, :contract_creation_internal_transaction)) - end - end end diff --git a/apps/explorer/test/explorer/chain/block_test.exs b/apps/explorer/test/explorer/chain/block_test.exs index cb41e15157a1..0c6073a8aab0 100644 --- a/apps/explorer/test/explorer/chain/block_test.exs +++ b/apps/explorer/test/explorer/chain/block_test.exs @@ -3,6 +3,7 @@ defmodule Explorer.Chain.BlockTest do alias Ecto.Changeset alias Explorer.Chain.{Address, Block, PendingBlockOperation, Wei} + alias Explorer.Chain.InternalTransaction.DeleteQueue, as: InternalTransactionDeleteQueue alias Explorer.PagingOptions describe "changeset/2" do @@ -399,4 +400,64 @@ defmodule Explorer.Chain.BlockTest do } end end + + describe "full_refetch/1" do + test "with block numbers" do + Enum.each(1..5, fn _i -> + insert(:block) + end) + + blocks = Repo.all(Block) + + assert Enum.all?(blocks, &(&1.refetch_needed == false)) + assert [] = Repo.all(InternalTransactionDeleteQueue) + + block_numbers = Enum.map(blocks, & &1.number) + + Block.full_refetch(block_numbers) + + assert Enum.all?(Repo.all(Block), &(&1.refetch_needed == true)) + + delete_queue_entries = Repo.all(InternalTransactionDeleteQueue) + + assert Enum.count(delete_queue_entries) == 5 + assert Enum.map(delete_queue_entries, & &1.block_number) -- block_numbers == [] + end + + test "with block ranges" do + Enum.each(1..5, fn i -> + insert(:block, number: i) + end) + + blocks = Repo.all(Block) + + assert Enum.all?(blocks, &(&1.refetch_needed == false)) + assert [] = Repo.all(InternalTransactionDeleteQueue) + + Block.full_refetch("1..5") + + assert Enum.all?(Repo.all(Block), &(&1.refetch_needed == true)) + + delete_queue_entries = Repo.all(InternalTransactionDeleteQueue) + + assert Enum.count(delete_queue_entries) == 5 + assert Enum.map(delete_queue_entries, & &1.block_number) -- Enum.to_list(1..5) == [] + end + + test "with single block number" do + insert(:block) + + [block] = Repo.all(Block) + block_number = block.number + + assert block.refetch_needed == false + assert [] = Repo.all(InternalTransactionDeleteQueue) + + Block.full_refetch(block.number) + + assert Repo.one(Block).refetch_needed == true + + assert [%{block_number: ^block_number}] = Repo.all(InternalTransactionDeleteQueue) + end + end end diff --git a/apps/explorer/test/explorer/chain/csv_export/address/logs_test.exs b/apps/explorer/test/explorer/chain/csv_export/address/logs_test.exs index 0c148cf3e77d..cd7e6dc593d1 100644 --- a/apps/explorer/test/explorer/chain/csv_export/address/logs_test.exs +++ b/apps/explorer/test/explorer/chain/csv_export/address/logs_test.exs @@ -44,7 +44,7 @@ defmodule Explorer.Chain.Address.LogsTest do [result] = address.hash - |> AddressLogsCsvExporter.export(from_period, to_period, []) + |> AddressLogsCsvExporter.export(from_period, to_period, [], nil, nil) |> Enum.to_list() |> Enum.drop(1) |> Enum.map(fn [ @@ -122,7 +122,7 @@ defmodule Explorer.Chain.Address.LogsTest do result = address.hash - |> AddressLogsCsvExporter.export(from_period, to_period, []) + |> AddressLogsCsvExporter.export(from_period, to_period, [], nil, nil) |> Enum.to_list() |> Enum.drop(1) diff --git a/apps/explorer/test/explorer/chain/csv_export/address/token_transfers_test.exs b/apps/explorer/test/explorer/chain/csv_export/address/token_transfers_test.exs index 3e23825981c8..336ebf3d1f9d 100644 --- a/apps/explorer/test/explorer/chain/csv_export/address/token_transfers_test.exs +++ b/apps/explorer/test/explorer/chain/csv_export/address/token_transfers_test.exs @@ -24,7 +24,7 @@ defmodule Explorer.Chain.Address.TokenTransfersTest do [result] = address.hash - |> AddressTokenTransfersCsvExporter.export(from_period, to_period, []) + |> AddressTokenTransfersCsvExporter.export(from_period, to_period, [], nil, nil) |> Enum.to_list() |> Enum.drop(1) |> Enum.map(fn [ @@ -125,7 +125,7 @@ defmodule Explorer.Chain.Address.TokenTransfersTest do result = address.hash - |> AddressTokenTransfersCsvExporter.export(from_period, to_period, []) + |> AddressTokenTransfersCsvExporter.export(from_period, to_period, [], nil, nil) |> Enum.to_list() |> Enum.drop(1) diff --git a/apps/explorer/test/explorer/chain/csv_export/address/transactions_test.exs b/apps/explorer/test/explorer/chain/csv_export/address/transactions_test.exs index f1cdd8c968ba..985a9f7ed68d 100644 --- a/apps/explorer/test/explorer/chain/csv_export/address/transactions_test.exs +++ b/apps/explorer/test/explorer/chain/csv_export/address/transactions_test.exs @@ -46,7 +46,7 @@ defmodule Explorer.Chain.Address.TransactionsTest do [result] = address.hash - |> AddressTransactionsCsvExporter.export(from_period, to_period, []) + |> AddressTransactionsCsvExporter.export(from_period, to_period, [], nil, nil) |> Enum.to_list() |> Enum.drop(1) |> Enum.map(fn [ @@ -151,7 +151,7 @@ defmodule Explorer.Chain.Address.TransactionsTest do result = address.hash - |> AddressTransactionsCsvExporter.export(from_period, to_period, []) + |> AddressTransactionsCsvExporter.export(from_period, to_period, [], nil, nil) |> Enum.to_list() |> Enum.drop(1) diff --git a/apps/explorer/test/explorer/chain/csv_export/async_helper_test.exs b/apps/explorer/test/explorer/chain/csv_export/async_helper_test.exs new file mode 100644 index 000000000000..fd78b59d6c10 --- /dev/null +++ b/apps/explorer/test/explorer/chain/csv_export/async_helper_test.exs @@ -0,0 +1,61 @@ +defmodule Explorer.Chain.CsvExport.AsyncHelperTest do + use Explorer.DataCase, async: false + + alias Explorer.Chain.CsvExport.{AsyncHelper, Request} + + setup do + original_config = Application.get_env(:explorer, Explorer.Chain.CsvExport) + + config = + (original_config || []) + |> Keyword.put(:tmp_dir, System.tmp_dir!() <> "/csv_export_async_test_#{:rand.uniform(100_000)}") + + Application.put_env(:explorer, Explorer.Chain.CsvExport, config) + + on_exit(fn -> + if original_config do + Application.put_env(:explorer, Explorer.Chain.CsvExport, original_config) + else + Application.delete_env(:explorer, Explorer.Chain.CsvExport) + end + end) + + :ok + end + + describe "actualize_csv_export_request/1" do + test "returns request as-is when file_id is nil (pending)" do + {:ok, request} = + Request.create("127.0.0.1", %{ + address_hash: to_string(insert(:address).hash), + from_period: nil, + to_period: nil, + show_scam_tokens?: nil, + module: "Elixir.Explorer.Chain.CsvExport.Address.Transactions", + filter_type: nil, + filter_value: nil + }) + + assert actualized = AsyncHelper.actualize_csv_export_request(request) + assert actualized.id == request.id + assert actualized.file_id == nil + end + + test "returns nil when input is nil" do + assert nil == AsyncHelper.actualize_csv_export_request(nil) + end + end + + describe "stream_to_temp_file/2" do + test "writes stream content to temp file and returns file path" do + uuid = Ecto.UUID.generate() + stream = Stream.map(["a", "b", "c"], & &1) + + path = AsyncHelper.stream_to_temp_file(stream, uuid) + + assert path =~ "csv_export_#{uuid}.csv" + assert File.exists?(path) + assert File.read!(path) == "abc" + end + end +end diff --git a/apps/explorer/test/explorer/chain/csv_export/helper_test.exs b/apps/explorer/test/explorer/chain/csv_export/helper_test.exs new file mode 100644 index 000000000000..c3b84f1a2994 --- /dev/null +++ b/apps/explorer/test/explorer/chain/csv_export/helper_test.exs @@ -0,0 +1,80 @@ +defmodule Explorer.Chain.CsvExport.HelperTest do + use Explorer.DataCase, async: true + + alias Explorer.Chain.CsvExport.Helper + + setup do + original_config = Application.get_env(:explorer, Explorer.Chain.CsvExport) + + Application.put_env(:explorer, Explorer.Chain.CsvExport, [ + {:async?, false} + | (original_config || []) |> Keyword.drop([:async?]) + ]) + + on_exit(fn -> + if original_config do + Application.put_env(:explorer, Explorer.Chain.CsvExport, original_config) + else + Application.delete_env(:explorer, Explorer.Chain.CsvExport) + end + end) + + :ok + end + + describe "valid_filter?/3" do + test "returns true for valid address filter to" do + assert Helper.valid_filter?("address", "to", "transactions") == true + assert Helper.valid_filter?("address", "to", "token-transfers") == true + assert Helper.valid_filter?("address", "to", "internal-transactions") == true + end + + test "returns true for valid address filter from" do + assert Helper.valid_filter?("address", "from", "transactions") == true + end + + test "returns false for invalid filter type" do + assert Helper.valid_filter?("invalid", "to", "transactions") == false + end + + test "returns false for invalid filter value for address" do + assert Helper.valid_filter?("address", "invalid", "transactions") == false + end + + test "returns false for nil or empty filter value" do + refute Helper.valid_filter?("address", nil, "transactions") + refute Helper.valid_filter?("address", "", "transactions") + end + end + + describe "supported_filters/1" do + test "returns correct filters for each type" do + assert Helper.supported_filters("internal-transactions") == ["address"] + assert Helper.supported_filters("transactions") == ["address"] + assert Helper.supported_filters("token-transfers") == ["address"] + assert Helper.supported_filters("logs") == ["topic"] + assert Helper.supported_filters("unknown") == [] + end + end + + describe "async_enabled?/0" do + test "returns config value" do + Application.put_env(:explorer, Explorer.Chain.CsvExport, [async?: false] ++ []) + assert Helper.async_enabled?() == false + + Application.put_env(:explorer, Explorer.Chain.CsvExport, [async?: true] ++ []) + assert Helper.async_enabled?() == true + end + end + + describe "dump_to_stream/1" do + test "produces CSV-formatted stream" do + rows = [["a", "b", "c"], ["1", "2", "3"]] + stream = Helper.dump_to_stream(rows) + + result = stream |> Enum.to_list() |> Enum.join("") + assert result =~ "a,b,c" + assert result =~ "1,2,3" + end + end +end diff --git a/apps/explorer/test/explorer/chain/csv_export/request_test.exs b/apps/explorer/test/explorer/chain/csv_export/request_test.exs new file mode 100644 index 000000000000..761bbb1e5f1b --- /dev/null +++ b/apps/explorer/test/explorer/chain/csv_export/request_test.exs @@ -0,0 +1,143 @@ +defmodule Explorer.Chain.CsvExport.RequestTest do + use Explorer.DataCase, async: false + use Oban.Testing, repo: Explorer.Repo + + alias Explorer.Chain.CsvExport.Request + + setup do + original_config = Application.get_env(:explorer, Explorer.Chain.CsvExport) + + config = + (original_config || []) + |> Keyword.put(:max_pending_tasks_per_ip, 2) + |> Keyword.put(:async?, true) + + Application.put_env(:explorer, Explorer.Chain.CsvExport, config) + + on_exit(fn -> + if original_config do + Application.put_env(:explorer, Explorer.Chain.CsvExport, original_config) + else + Application.delete_env(:explorer, Explorer.Chain.CsvExport) + end + end) + + :ok + end + + defp address_export_args do + address = insert(:address) + + %{ + address_hash: to_string(address.hash), + from_period: nil, + to_period: nil, + filter_type: nil, + filter_value: nil, + show_scam_tokens?: nil, + module: "Elixir.Explorer.Chain.CsvExport.Address.Transactions" + } + end + + describe "create/2" do + test "creates a request with pending status and enqueues Oban job" do + args = address_export_args() + assert {:ok, request} = Request.create("127.0.0.1", args) + + assert %Request{} = request + assert request.id != nil + assert request.status == :pending + assert request.file_id == nil + assert request.remote_ip_hash != nil + assert byte_size(request.remote_ip_hash) == 32 + + assert [job] = all_enqueued(worker: Explorer.Chain.CsvExport.Worker) + assert job.args["request_id"] == request.id + assert job.args["address_hash"] == args.address_hash + end + + test "allows up to max_pending_tasks_per_ip concurrent requests from same IP" do + args = address_export_args() + + assert {:ok, _} = Request.create("127.0.0.1", args) + assert {:ok, _} = Request.create("127.0.0.1", args) + + assert {:error, :too_many_pending_requests} = Request.create("127.0.0.1", args) + end + + test "returns too_many_pending_requests when limit is reached" do + args = address_export_args() + + Request.create("192.168.1.1", args) + Request.create("192.168.1.1", args) + + assert {:error, :too_many_pending_requests} = Request.create("192.168.1.1", args) + end + + test "allows new requests from different IPs" do + args = address_export_args() + + assert {:ok, _} = Request.create("127.0.0.1", args) + assert {:ok, _} = Request.create("127.0.0.1", args) + assert {:ok, _} = Request.create("10.0.0.1", args) + end + end + + describe "update_file_id_and_expires_at/3" do + test "sets file_id and transitions status to completed" do + {:ok, request} = Request.create("127.0.0.1", address_export_args()) + file_id = "gokapi-file-123" + + assert {1, nil} = + Request.update_file_id_and_expires_at( + request.id, + file_id, + DateTime.utc_now() |> DateTime.truncate(:second) + ) + + updated = Request.get_by_uuid(request.id) + assert updated.file_id == file_id + assert updated.status == :completed + assert updated.expires_at != nil + end + end + + describe "mark_failed/1" do + test "transitions status to failed" do + {:ok, request} = Request.create("127.0.0.1", address_export_args()) + + assert {1, nil} = Request.mark_failed(request.id) + + updated = Request.get_by_uuid(request.id) + assert updated.status == :failed + end + end + + describe "get_by_uuid/2" do + test "returns the request by UUID" do + {:ok, request} = Request.create("127.0.0.1", address_export_args()) + + found = Request.get_by_uuid(request.id) + assert found.id == request.id + end + + test "returns nil for non-existent UUID" do + uuid = Ecto.UUID.generate() + assert nil == Request.get_by_uuid(uuid) + end + end + + describe "delete/1" do + test "removes the request" do + {:ok, request} = Request.create("127.0.0.1", address_export_args()) + + assert {1, nil} = Request.delete(request.id) + assert nil == Request.get_by_uuid(request.id) + end + + test "returns {0, nil} for non-existent UUID" do + uuid = Ecto.UUID.generate() + assert {0, nil} = Request.delete(uuid) + end + end +end diff --git a/apps/explorer/test/explorer/chain/csv_export/requests_sanitizer_test.exs b/apps/explorer/test/explorer/chain/csv_export/requests_sanitizer_test.exs new file mode 100644 index 000000000000..c6df33166939 --- /dev/null +++ b/apps/explorer/test/explorer/chain/csv_export/requests_sanitizer_test.exs @@ -0,0 +1,91 @@ +defmodule Explorer.Chain.CsvExport.RequestsSanitizerTest do + use Explorer.DataCase, async: false + use Oban.Testing, repo: Explorer.Repo + + alias Explorer.Chain.CsvExport.{Request, RequestsSanitizer} + + setup do + original_config = Application.get_env(:explorer, Explorer.Chain.CsvExport) + + config = + (original_config || []) + |> Keyword.put(:max_pending_tasks_per_ip, 5) + |> Keyword.put(:async?, true) + |> Keyword.put(:gokapi_upload_expiry_days, 1) + + Application.put_env(:explorer, Explorer.Chain.CsvExport, config) + + on_exit(fn -> + if original_config do + Application.put_env(:explorer, Explorer.Chain.CsvExport, original_config) + else + Application.delete_env(:explorer, Explorer.Chain.CsvExport) + end + end) + + :ok + end + + describe "perform/1" do + test "deletes completed requests older than gokapi_upload_expiry_days" do + {:ok, request1} = + Request.create("127.0.0.1", %{ + address_hash: to_string(insert(:address).hash), + from_period: nil, + to_period: nil, + show_scam_tokens?: nil, + module: "Elixir.Explorer.Chain.CsvExport.Address.Transactions", + filter_type: nil, + filter_value: nil + }) + + Request.update_file_id_and_expires_at(request1.id, "file-123", DateTime.utc_now() |> DateTime.truncate(:second)) + + past_time = DateTime.add(DateTime.utc_now(), -2, :day) + + Request + |> Ecto.Query.where([r], r.id == ^request1.id) + |> Explorer.Repo.update_all(set: [updated_at: past_time]) + + assert :ok = perform_job(RequestsSanitizer, %{}) + + assert nil == Request.get_by_uuid(request1.id) + end + + test "does not delete pending requests" do + {:ok, request} = + Request.create("127.0.0.1", %{ + address_hash: to_string(insert(:address).hash), + from_period: nil, + to_period: nil, + show_scam_tokens?: nil, + module: "Elixir.Explorer.Chain.CsvExport.Address.Transactions", + filter_type: nil, + filter_value: nil + }) + + assert :ok = perform_job(RequestsSanitizer, %{}) + + assert Request.get_by_uuid(request.id) != nil + end + + test "does not delete recently completed requests" do + {:ok, request} = + Request.create("127.0.0.1", %{ + address_hash: to_string(insert(:address).hash), + from_period: nil, + to_period: nil, + show_scam_tokens?: nil, + module: "Elixir.Explorer.Chain.CsvExport.Address.Transactions", + filter_type: nil, + filter_value: nil + }) + + Request.update_file_id_and_expires_at(request.id, "recent-file", DateTime.utc_now() |> DateTime.truncate(:second)) + + assert :ok = perform_job(RequestsSanitizer, %{}) + + assert Request.get_by_uuid(request.id) != nil + end + end +end diff --git a/apps/explorer/test/explorer/chain/csv_export/token/holders_test.exs b/apps/explorer/test/explorer/chain/csv_export/token/holders_test.exs new file mode 100644 index 000000000000..3244add51924 --- /dev/null +++ b/apps/explorer/test/explorer/chain/csv_export/token/holders_test.exs @@ -0,0 +1,112 @@ +defmodule Explorer.Chain.CsvExport.Token.HoldersTest do + use Explorer.DataCase + + alias Explorer.Chain.Address + alias Explorer.Chain.CsvExport.Token.Holders, as: TokenHoldersExporter + + setup do + original_limit = Application.get_env(:explorer, :csv_export_limit) + Application.put_env(:explorer, :csv_export_limit, 150) + + on_exit(fn -> + if original_limit do + Application.put_env(:explorer, :csv_export_limit, original_limit) + else + Application.delete_env(:explorer, :csv_export_limit) + end + end) + + :ok + end + + describe "export/6" do + test "exports token holders to csv with header and holder rows" do + token = insert(:token, type: "ERC-20", decimals: 18) + + holder1 = + insert(:address_current_token_balance, + token_contract_address_hash: token.contract_address_hash, + address: insert(:address), + value: 100_000_000_000_000_000_000 + ) + + holder2 = + insert(:address_current_token_balance, + token_contract_address_hash: token.contract_address_hash, + address: insert(:address), + value: 50_000_000_000_000_000_000 + ) + + csv_string = + token.contract_address_hash + |> TokenHoldersExporter.export("2020-01-01", "2025-12-31", [], nil, nil) + |> Enum.to_list() + |> IO.iodata_to_binary() + + [header | rows] = String.split(csv_string, "\r\n", trim: true) + assert header =~ "HolderAddress" + assert header =~ "Balance" + + assert length(rows) == 2 + + assert Enum.any?(rows, fn row -> + row =~ Address.checksum(holder1.address_hash) and row =~ "100" + end) + + assert Enum.any?(rows, fn row -> + row =~ Address.checksum(holder2.address_hash) and row =~ "50" + end) + end + + test "formats holder address as checksummed and balance with correct decimals" do + token = insert(:token, type: "ERC-20", decimals: 6) + + holder = + insert(:address_current_token_balance, + token_contract_address_hash: token.contract_address_hash, + address: insert(:address), + value: 1_234_567_890 + ) + + csv_string = + token.contract_address_hash + |> TokenHoldersExporter.export("2020-01-01", "2025-12-31", [], nil, nil) + |> Enum.to_list() + |> IO.iodata_to_binary() + + [header | rows] = String.split(csv_string, "\r\n", trim: true) + + assert header =~ "HolderAddress" + assert header =~ "Balance" + + assert length(rows) == 1 + row = hd(rows) + assert row =~ Address.checksum(holder.address_hash) + assert row =~ "1234.56789" + end + + test "respects pagination with many holders" do + token = insert(:token, type: "ERC-20", decimals: 18) + + Enum.each(1..200, fn _ -> + insert(:address_current_token_balance, + token_contract_address_hash: token.contract_address_hash, + address: insert(:address), + value: 1_000_000_000_000_000_000 + ) + end) + + csv_string = + token.contract_address_hash + |> TokenHoldersExporter.export("2020-01-01", "2025-12-31", [], nil, nil) + |> Enum.to_list() + |> IO.iodata_to_binary() + + [header | rows] = String.split(csv_string, "\r\n", trim: true) + + assert header =~ "HolderAddress" + assert header =~ "Balance" + assert length(rows) == 150 + end + end +end diff --git a/apps/explorer/test/explorer/chain/csv_export/worker_test.exs b/apps/explorer/test/explorer/chain/csv_export/worker_test.exs new file mode 100644 index 000000000000..655d622aaab9 --- /dev/null +++ b/apps/explorer/test/explorer/chain/csv_export/worker_test.exs @@ -0,0 +1,155 @@ +defmodule Explorer.Chain.CsvExport.WorkerTest do + use Explorer.DataCase, async: false + use Oban.Testing, repo: Explorer.Repo + + alias Explorer.Chain.CsvExport.{Request, Worker} + alias Plug.Conn + + setup do + bypass = Bypass.open() + original_config = Application.get_env(:explorer, Explorer.Chain.CsvExport) + original_tesla = Application.get_env(:tesla, :adapter) + + config = + (original_config || []) + |> Keyword.put(:max_pending_tasks_per_ip, 5) + |> Keyword.put(:async?, true) + |> Keyword.put(:tmp_dir, System.tmp_dir!() <> "/csv_export_test_#{:rand.uniform(100_000)}") + |> Keyword.put(:gokapi_url, "http://localhost:#{bypass.port}") + |> Keyword.put(:gokapi_api_key, "test-api-key") + |> Keyword.put(:chunk_size, 1024) + |> Keyword.put(:gokapi_upload_expiry_days, 1) + |> Keyword.put(:gokapi_upload_allowed_downloads, 1) + |> Keyword.put(:db_timeout, 60_000) + + Application.put_env(:explorer, Explorer.Chain.CsvExport, config) + Application.put_env(:tesla, :adapter, Tesla.Adapter.Hackney) + + on_exit(fn -> + Application.put_env(:tesla, :adapter, original_tesla) + + if original_config do + Application.put_env(:explorer, Explorer.Chain.CsvExport, original_config) + else + Application.delete_env(:explorer, Explorer.Chain.CsvExport) + end + end) + + {:ok, bypass: bypass} + end + + describe "perform/1 address-based export" do + test "processes address-based export and updates request on success", %{bypass: bypass} do + address = insert(:address) + + :transaction + |> insert(from_address: address) + |> with_block() + + args = %{ + address_hash: to_string(address.hash), + from_period: DateTime.utc_now() |> DateTime.to_iso8601(), + to_period: DateTime.utc_now() |> DateTime.to_iso8601(), + show_scam_tokens?: nil, + module: "Elixir.Explorer.Chain.CsvExport.Address.Transactions", + filter_type: nil, + filter_value: nil + } + + {:ok, request} = Request.create("127.0.0.1", args) + date_time_to_expect = DateTime.utc_now() |> DateTime.truncate(:second) + + Bypass.expect(bypass, fn conn -> + [path | _] = conn.request_path |> String.split("?") + + cond do + path == "/api/chunk/add" -> + Conn.resp(conn, 200, "") + + path == "/api/chunk/complete" -> + Conn.resp( + conn, + 200, + Jason.encode!(%{ + "FileInfo" => %{"Id" => "test-file-id", "ExpireAt" => date_time_to_expect |> DateTime.to_unix()} + }) + ) + + true -> + Conn.resp(conn, 404, "Not found") + end + end) + + job_args = %{ + "request_id" => request.id, + "address_hash" => args.address_hash, + "from_period" => DateTime.utc_now() |> DateTime.to_iso8601(), + "to_period" => DateTime.utc_now() |> DateTime.to_iso8601(), + "show_scam_tokens?" => nil, + "module" => args.module, + "filter_type" => nil, + "filter_value" => nil + } + + assert :ok = perform_job(Worker, job_args) + + updated = Request.get_by_uuid(request.id) + assert updated.status == :completed + assert updated.file_id == "test-file-id" + assert updated.expires_at == date_time_to_expect + end + end + + describe "perform/1 failure handling" do + test "marks request as failed when upload fails", %{bypass: bypass} do + address = insert(:address) + + :transaction + |> insert(from_address: address) + |> with_block() + + args = %{ + address_hash: to_string(address.hash), + from_period: DateTime.utc_now() |> DateTime.to_iso8601(), + to_period: DateTime.utc_now() |> DateTime.to_iso8601(), + show_scam_tokens?: nil, + module: "Elixir.Explorer.Chain.CsvExport.Address.Transactions", + filter_type: nil, + filter_value: nil + } + + {:ok, request} = Request.create("127.0.0.1", args) + + Bypass.expect(bypass, fn conn -> + [path | _] = conn.request_path |> String.split("?") + + cond do + path == "/api/chunk/add" -> + Conn.resp(conn, 200, "") + + path == "/api/chunk/complete" -> + Conn.resp(conn, 500, "Internal Server Error") + + true -> + Conn.resp(conn, 404, "") + end + end) + + job_args = %{ + "request_id" => request.id, + "address_hash" => args.address_hash, + "from_period" => DateTime.utc_now() |> DateTime.to_iso8601(), + "to_period" => DateTime.utc_now() |> DateTime.to_iso8601(), + "show_scam_tokens?" => nil, + "module" => args.module, + "filter_type" => nil, + "filter_value" => nil + } + + assert {:error, _} = perform_job(Worker, job_args) + + updated = Request.get_by_uuid(request.id) + assert updated.status == :failed + end + end +end diff --git a/apps/explorer/test/explorer/chain/import/runner/internal_transactions_test.exs b/apps/explorer/test/explorer/chain/import/runner/internal_transactions_test.exs index a6dfac8d47f7..5e28f547f221 100644 --- a/apps/explorer/test/explorer/chain/import/runner/internal_transactions_test.exs +++ b/apps/explorer/test/explorer/chain/import/runner/internal_transactions_test.exs @@ -172,11 +172,19 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do assert {:ok, _} = run_internal_transactions([transaction_changes, pending_changes]) - assert Repo.exists?(from(i in InternalTransaction, where: i.transaction_hash == ^transaction.hash)) + assert Repo.exists?( + from(i in InternalTransaction, + where: i.block_number == ^transaction.block_number and i.transaction_index == ^transaction.index + ) + ) assert PendingBlockOperation |> Repo.get(transaction.block_hash) |> is_nil() - assert from(i in InternalTransaction, where: i.transaction_hash == ^pending.hash) |> Repo.one() |> is_nil() + assert InternalTransaction + |> InternalTransaction.join_transaction_query() + |> where([_it, t], t.hash == ^pending.hash) + |> Repo.one() + |> is_nil() assert is_nil(Repo.get(Transaction, pending.hash).block_hash) end @@ -207,9 +215,17 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do assert {:ok, _} = run_internal_transactions([pending_transaction_changes, transaction_changes]) - assert from(i in InternalTransaction, where: i.transaction_hash == ^pending.hash) |> Repo.one() |> is_nil() + assert InternalTransaction + |> InternalTransaction.join_transaction_query() + |> where([_it, t], t.hash == ^pending.hash) + |> Repo.one() + |> is_nil() - assert from(i in InternalTransaction, where: i.transaction_hash == ^inserted.hash) |> Repo.one() |> is_nil() == + assert from(i in InternalTransaction, + where: i.block_number == ^inserted.block_number and i.transaction_index == ^inserted.index + ) + |> Repo.one() + |> is_nil() == false assert %{consensus: true} = Repo.get(Block, full_block.hash) @@ -228,11 +244,15 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do assert {:ok, _} = run_internal_transactions([transaction_a_changes]) - assert from(i in InternalTransaction, where: i.transaction_hash == ^transaction_a.hash) + assert from(i in InternalTransaction, + where: i.block_number == ^transaction_a.block_number and i.transaction_index == ^transaction_a.index + ) |> Repo.one() |> is_nil() - assert from(i in InternalTransaction, where: i.transaction_hash == ^transaction_b.hash) + assert from(i in InternalTransaction, + where: i.block_number == ^transaction_b.block_number and i.transaction_index == ^transaction_b.index + ) |> Repo.one() |> is_nil() @@ -255,11 +275,15 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do assert {:ok, _} = run_internal_transactions([transaction_a_changes]) - assert from(i in InternalTransaction, where: i.transaction_hash == ^transaction_a.hash) + assert from(i in InternalTransaction, + where: i.block_number == ^transaction_a.block_number and i.transaction_index == ^transaction_a.index + ) |> Repo.one() |> is_nil() - assert from(i in InternalTransaction, where: i.transaction_hash == ^transaction_b.hash) + assert from(i in InternalTransaction, + where: i.block_number == ^transaction_b.block_number and i.transaction_index == ^transaction_b.index + ) |> Repo.one() |> is_nil() @@ -287,7 +311,11 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do assert {:ok, _} = run_internal_transactions([transaction_changes]) - assert from(i in InternalTransaction, where: i.transaction_hash == ^transaction.hash) |> Repo.one() |> is_nil() + assert from(i in InternalTransaction, + where: i.block_number == ^transaction.block_number and i.transaction_index == ^transaction.index + ) + |> Repo.one() + |> is_nil() assert %{consensus: true, refetch_needed: false} = Repo.get(Block, full_block.hash) @@ -318,12 +346,18 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do assert %{consensus: true} = Repo.get(Block, empty_block.hash) assert PendingBlockOperation |> Repo.get(empty_block.hash) |> is_nil() - assert from(i in InternalTransaction, where: i.transaction_hash == ^inserted.hash, where: i.index == 1) + assert from(i in InternalTransaction, + where: i.block_number == ^inserted.block_number and i.transaction_index == ^inserted.index, + where: i.index == 1 + ) |> Repo.one() |> is_nil() == false - assert from(i in InternalTransaction, where: i.transaction_hash == ^inserted.hash, where: i.index == 2) + assert from(i in InternalTransaction, + where: i.block_number == ^inserted.block_number and i.transaction_index == ^inserted.index, + where: i.index == 2 + ) |> Repo.one() |> is_nil() == false @@ -351,7 +385,6 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do index: 0, input: input, trace_address: [], - transaction_hash: transaction.hash, transaction_index: 0, type: :stop, value: Wei.from(Decimal.new(0), :wei) @@ -375,7 +408,6 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do to_address_hash: insert(:address).hash, gas: nil, trace_address: [], - transaction_hash: transaction.hash, transaction_index: transaction.index, index: 0, type: :selfdestruct, @@ -407,7 +439,6 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do gas_used: 25000, init: %Data{bytes: <<1, 2, 3>>}, trace_address: [], - transaction_hash: transaction.hash, transaction_index: transaction.index, index: 0, type: :create, @@ -421,7 +452,6 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do to_address_hash: insert(:address).hash, gas: nil, trace_address: [], - transaction_hash: transaction.hash, transaction_index: transaction.index, index: 1, type: :selfdestruct, @@ -452,7 +482,6 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do to_address_hash: insert(:address).hash, gas: nil, trace_address: [], - transaction_hash: transaction.hash, transaction_index: transaction.index, index: 0, type: :selfdestruct, @@ -465,7 +494,6 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do to_address_hash: insert(:address).hash, gas: nil, trace_address: [], - transaction_hash: transaction.hash, transaction_index: transaction.index, index: 1, type: :selfdestruct, @@ -478,7 +506,6 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do to_address_hash: insert(:address).hash, gas: nil, trace_address: [], - transaction_hash: transaction.hash, transaction_index: transaction.index, index: 2, type: :selfdestruct, @@ -516,7 +543,6 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do gas_used: 25000, index: 0, trace_address: [], - transaction_hash: transaction.hash, transaction_index: transaction.index, init: %Data{bytes: <<1, 2, 3>>}, type: :create2, @@ -530,7 +556,6 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do to_address_hash: insert(:address).hash, gas: nil, trace_address: [], - transaction_hash: transaction.hash, transaction_index: transaction.index, index: 1, type: :selfdestruct, @@ -558,7 +583,6 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do to_address_hash: insert(:address).hash, gas: nil, trace_address: [], - transaction_hash: transaction.hash, transaction_index: transaction.index, index: 0, type: :selfdestruct, @@ -572,7 +596,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do defp run_internal_transactions(changes_list, multi \\ Multi.new()) when is_list(changes_list) do multi - |> InternalTransactions.run(changes_list, %{ + |> InternalTransactions.run(InternalTransactions.prepare_data(changes_list), %{ timeout: :infinity, timestamps: %{inserted_at: DateTime.utc_now(), updated_at: DateTime.utc_now()} }) @@ -602,7 +626,6 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do end, index: index, trace_address: [], - transaction_hash: transaction.hash, transaction_index: transaction.index, type: :call, value: Wei.from(Decimal.new(1), :wei), @@ -632,7 +655,6 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do end, index: index, trace_address: [], - transaction_hash: transaction.hash, transaction_index: transaction.index, type: :call, value: Wei.from(Decimal.new(1), :wei), diff --git a/apps/explorer/test/explorer/chain/import_test.exs b/apps/explorer/test/explorer/chain/import_test.exs index c27ef3766e38..42427f239515 100644 --- a/apps/explorer/test/explorer/chain/import_test.exs +++ b/apps/explorer/test/explorer/chain/import_test.exs @@ -77,7 +77,7 @@ defmodule Explorer.Chain.ImportTest do }, %{ block_number: 37, - transaction_index: 1, + transaction_index: 0, transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", index: 2, trace_address: [0], @@ -268,21 +268,11 @@ defmodule Explorer.Chain.ImportTest do internal_transactions: [ %{ index: 1, - transaction_hash: %Hash{ - byte_count: 32, - bytes: - <<83, 189, 136, 72, 114, 222, 62, 72, 134, 146, 136, 27, 174, 236, 38, 46, 123, 149, 35, 77, 57, - 101, 36, 140, 57, 254, 153, 47, 255, 212, 51, 229>> - } + transaction_index: 0 }, %{ index: 2, - transaction_hash: %Hash{ - byte_count: 32, - bytes: - <<83, 189, 136, 72, 114, 222, 62, 72, 134, 146, 136, 27, 174, 236, 38, 46, 123, 149, 35, 77, 57, - 101, 36, 140, 57, 254, 153, 47, 255, 212, 51, 229>> - } + transaction_index: 0 } ], logs: [ @@ -992,7 +982,7 @@ defmodule Explorer.Chain.ImportTest do type: "create", value: 0, block_number: 37, - transaction_index: 1 + transaction_index: 0 } ], timeout: 5, diff --git a/apps/explorer/test/explorer/chain/internal_transaction_test.exs b/apps/explorer/test/explorer/chain/internal_transaction_test.exs index 9492160d9322..859f50316e13 100644 --- a/apps/explorer/test/explorer/chain/internal_transaction_test.exs +++ b/apps/explorer/test/explorer/chain/internal_transaction_test.exs @@ -15,20 +15,17 @@ defmodule Explorer.Chain.InternalTransactionTest do describe "changeset/2" do test "with valid attributes" do - transaction = insert(:transaction) - changeset = InternalTransaction.changeset(%InternalTransaction{}, %{ call_type: "call", - from_address_hash: "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + from_address_id: 1, gas: 100, gas_used: 100, index: 0, input: "0x70696e746f73", output: "0x72656672696564", - to_address_hash: "0x6295ee1b4f6dd65047762f924ecd367c17eabf8f", + to_address_id: 2, trace_address: [0, 1], - transaction_hash: transaction.hash, transaction_index: 0, type: "call", value: 100, @@ -45,8 +42,6 @@ defmodule Explorer.Chain.InternalTransactionTest do end test "that a valid changeset is persistable" do - transaction = insert(:transaction) - changeset = InternalTransaction.changeset(%InternalTransaction{}, %{ call_type: "call", @@ -56,7 +51,6 @@ defmodule Explorer.Chain.InternalTransactionTest do input: "thin-mints", output: "munchos", trace_address: [0, 1], - transaction: transaction, type: "call", value: 100 }) @@ -65,17 +59,14 @@ defmodule Explorer.Chain.InternalTransactionTest do end test "with stop type" do - transaction = insert(:transaction) - changeset = InternalTransaction.changeset(%InternalTransaction{}, %{ - from_address_hash: "0x0000000000000000000000000000000000000000", + from_address_id: 1, gas: 0, gas_used: 22234, index: 0, input: "0x", trace_address: [], - transaction_hash: transaction.hash, transaction_index: 0, type: "stop", error: "execution stopped", @@ -108,7 +99,6 @@ defmodule Explorer.Chain.InternalTransactionTest do transaction: transaction, index: 0, block_number: transaction.block_number, - block_hash: transaction.block_hash, transaction_index: transaction.index ) @@ -116,7 +106,6 @@ defmodule Explorer.Chain.InternalTransactionTest do insert(:internal_transaction, transaction: transaction, index: 1, - block_hash: transaction.block_hash, block_number: transaction.block_number, transaction_index: transaction.index ) @@ -126,53 +115,43 @@ defmodule Explorer.Chain.InternalTransactionTest do # excluding of internal transactions with type=call and index=0 assert 1 == length(results) - assert Enum.all?( - results, - &({&1.transaction_hash, &1.index} in [ - {first.transaction_hash, first.index}, - {second.transaction_hash, second.index} - ]) - ) + assert [{second.block_number, second.transaction_index, second.index}] == + Enum.map(results, &{&1.block_number, &1.transaction_index, &1.index}) assert internal_transaction.block_number == block.number end - test "with transaction with internal transactions loads associations with in necessity_by_association" do + test "with transaction with internal transactions loads associations with in address_preloads" do transaction = :transaction |> insert() |> with_block() insert(:internal_transaction_create, - transaction: transaction, index: 0, block_number: transaction.block_number, - block_hash: transaction.block_hash, transaction_index: transaction.index ) assert [ %InternalTransaction{ - from_address: %Ecto.Association.NotLoaded{}, - to_address: %Ecto.Association.NotLoaded{}, - transaction: %Ecto.Association.NotLoaded{} + from_address: %{names: %Ecto.Association.NotLoaded{}}, + to_address: nil } ] = InternalTransaction.transaction_to_internal_transactions(transaction.hash) assert [ %InternalTransaction{ - from_address: %Address{}, - to_address: nil, - transaction: %Transaction{block: %Block{}} + from_address: %Address{names: []}, + to_address: nil } ] = InternalTransaction.transaction_to_internal_transactions( transaction.hash, - necessity_by_association: %{ - :from_address => :optional, - :to_address => :optional, - [transaction: :block] => :optional - } + address_preloads: [ + from_address: [:names], + to_address: [:names] + ] ) end @@ -186,7 +165,6 @@ defmodule Explorer.Chain.InternalTransactionTest do transaction: transaction, index: 0, block_number: transaction.block_number, - block_hash: transaction.block_hash, transaction_index: transaction.index ) @@ -206,13 +184,13 @@ defmodule Explorer.Chain.InternalTransactionTest do index: 0, transaction: transaction, block_number: transaction.block_number, - block_hash: transaction.block_hash, transaction_index: transaction.index ) actual = Enum.at(InternalTransaction.transaction_to_internal_transactions(transaction.hash), 0) - assert {actual.transaction_hash, actual.index} == {expected.transaction_hash, expected.index} + assert {actual.block_number, actual.transaction_index, actual.index} == + {expected.block_number, expected.transaction_index, expected.index} end test "includes internal transactions of type `reward` even when they are alone in the parent transaction" do @@ -227,13 +205,13 @@ defmodule Explorer.Chain.InternalTransactionTest do transaction: transaction, type: :reward, block_number: transaction.block_number, - block_hash: transaction.block_hash, transaction_index: transaction.index ) actual = Enum.at(InternalTransaction.transaction_to_internal_transactions(transaction.hash), 0) - assert {actual.transaction_hash, actual.index} == {expected.transaction_hash, expected.index} + assert {actual.block_number, actual.transaction_index, actual.index} == + {expected.block_number, expected.transaction_index, expected.index} end test "includes internal transactions of type `selfdestruct` even when they are alone in the parent transaction" do @@ -249,13 +227,13 @@ defmodule Explorer.Chain.InternalTransactionTest do gas: nil, type: :selfdestruct, block_number: transaction.block_number, - block_hash: transaction.block_hash, transaction_index: transaction.index ) actual = Enum.at(InternalTransaction.transaction_to_internal_transactions(transaction.hash), 0) - assert {actual.transaction_hash, actual.index} == {expected.transaction_hash, expected.index} + assert {actual.block_number, actual.transaction_index, actual.index} == + {expected.block_number, expected.transaction_index, expected.index} end test "returns the internal transactions in ascending index order" do @@ -264,40 +242,34 @@ defmodule Explorer.Chain.InternalTransactionTest do |> insert() |> with_block() - %InternalTransaction{transaction_hash: _, index: _} = + %InternalTransaction{transaction_index: _, index: _} = insert(:internal_transaction, - transaction: transaction, index: 0, block_number: transaction.block_number, - block_hash: transaction.block_hash, transaction_index: transaction.index ) - %InternalTransaction{transaction_hash: transaction_hash_1, index: index_1} = + %InternalTransaction{transaction_index: transaction_index_1, index: index_1} = insert(:internal_transaction, - transaction: transaction, index: 1, block_number: transaction.block_number, - block_hash: transaction.block_hash, transaction_index: transaction.index ) - %InternalTransaction{transaction_hash: transaction_hash_2, index: index_2} = + %InternalTransaction{transaction_index: transaction_index_2, index: index_2} = insert(:internal_transaction, - transaction: transaction, index: 2, block_number: transaction.block_number, - block_hash: transaction.block_hash, transaction_index: transaction.index ) result = transaction.hash |> InternalTransaction.transaction_to_internal_transactions() - |> Enum.map(&{&1.transaction_hash, &1.index}) + |> Enum.map(&{&1.transaction_index, &1.index}) # excluding of internal transactions with type=call and index=0 - assert [{transaction_hash_1, index_1}, {transaction_hash_2, index_2}] == result + assert [{transaction_index_1, index_1}, {transaction_index_2, index_2}] == result end test "pages by index" do @@ -306,53 +278,47 @@ defmodule Explorer.Chain.InternalTransactionTest do |> insert() |> with_block() - %InternalTransaction{transaction_hash: _, index: _} = + %InternalTransaction{transaction_index: _, index: _} = insert(:internal_transaction, - transaction: transaction, index: 0, block_number: transaction.block_number, - block_hash: transaction.block_hash, transaction_index: transaction.index ) - %InternalTransaction{transaction_hash: second_transaction_hash, index: second_index} = + %InternalTransaction{transaction_index: second_transaction_index, index: second_index} = insert(:internal_transaction, - transaction: transaction, index: 1, block_number: transaction.block_number, - block_hash: transaction.block_hash, transaction_index: transaction.index ) - %InternalTransaction{transaction_hash: third_transaction_hash, index: third_index} = + %InternalTransaction{transaction_index: third_transaction_index, index: third_index} = insert(:internal_transaction, - transaction: transaction, index: 2, block_number: transaction.block_number, - block_hash: transaction.block_hash, transaction_index: transaction.index ) - assert [{second_transaction_hash, second_index}, {third_transaction_hash, third_index}] == + assert [{second_transaction_index, second_index}, {third_transaction_index, third_index}] == transaction.hash |> InternalTransaction.transaction_to_internal_transactions( paging_options: %PagingOptions{key: {-1}, page_size: 2} ) - |> Enum.map(&{&1.transaction_hash, &1.index}) + |> Enum.map(&{&1.transaction_index, &1.index}) - assert [{second_transaction_hash, second_index}] == + assert [{second_transaction_index, second_index}] == transaction.hash |> InternalTransaction.transaction_to_internal_transactions( paging_options: %PagingOptions{key: {-1}, page_size: 1} ) - |> Enum.map(&{&1.transaction_hash, &1.index}) + |> Enum.map(&{&1.transaction_index, &1.index}) - assert [{third_transaction_hash, third_index}] == + assert [{third_transaction_index, third_index}] == transaction.hash |> InternalTransaction.transaction_to_internal_transactions( paging_options: %PagingOptions{key: {1}, page_size: 2} ) - |> Enum.map(&{&1.transaction_hash, &1.index}) + |> Enum.map(&{&1.transaction_index, &1.index}) end end @@ -376,7 +342,6 @@ defmodule Explorer.Chain.InternalTransactionTest do transaction: transaction, index: 0, block_number: transaction.block_number, - block_hash: transaction.block_hash, transaction_index: transaction.index ) @@ -384,7 +349,6 @@ defmodule Explorer.Chain.InternalTransactionTest do insert(:internal_transaction, transaction: transaction, index: 1, - block_hash: transaction.block_hash, block_number: transaction.block_number, transaction_index: transaction.index ) @@ -396,16 +360,16 @@ defmodule Explorer.Chain.InternalTransactionTest do assert Enum.all?( results, - &({&1.transaction_hash, &1.index} in [ - {first.transaction_hash, first.index}, - {second.transaction_hash, second.index} + &({&1.block_number, &1.transaction_index, &1.index} in [ + {first.block_number, first.transaction_index, first.index}, + {second.block_number, second.transaction_index, second.index} ]) ) assert internal_transaction.block_number == block.number end - test "with transaction with internal transactions loads associations with in necessity_by_association" do + test "with transaction with internal transactions loads associations with in address_preloads" do transaction = :transaction |> insert() @@ -415,32 +379,28 @@ defmodule Explorer.Chain.InternalTransactionTest do transaction: transaction, index: 0, block_number: transaction.block_number, - block_hash: transaction.block_hash, transaction_index: transaction.index ) assert [ %InternalTransaction{ - from_address: %Ecto.Association.NotLoaded{}, - to_address: %Ecto.Association.NotLoaded{}, - transaction: %Ecto.Association.NotLoaded{} + from_address: %{names: %Ecto.Association.NotLoaded{}}, + to_address: nil } ] = InternalTransaction.all_transaction_to_internal_transactions(transaction.hash) assert [ %InternalTransaction{ - from_address: %Address{}, - to_address: nil, - transaction: %Transaction{block: %Block{}} + from_address: %Address{names: []}, + to_address: nil } ] = InternalTransaction.all_transaction_to_internal_transactions( transaction.hash, - necessity_by_association: %{ - :from_address => :optional, - :to_address => :optional, - [transaction: :block] => :optional - } + address_preloads: [ + from_address: [:names], + to_address: [:names] + ] ) end @@ -454,7 +414,6 @@ defmodule Explorer.Chain.InternalTransactionTest do transaction: transaction, index: 0, block_number: transaction.block_number, - block_hash: transaction.block_hash, transaction_index: transaction.index ) @@ -474,13 +433,13 @@ defmodule Explorer.Chain.InternalTransactionTest do index: 0, transaction: transaction, block_number: transaction.block_number, - block_hash: transaction.block_hash, transaction_index: transaction.index ) actual = Enum.at(InternalTransaction.all_transaction_to_internal_transactions(transaction.hash), 0) - assert {actual.transaction_hash, actual.index} == {expected.transaction_hash, expected.index} + assert {actual.block_number, actual.transaction_index, actual.index} == + {expected.block_number, expected.transaction_index, expected.index} end test "includes internal transactions of type `reward` even when they are alone in the parent transaction" do @@ -495,13 +454,13 @@ defmodule Explorer.Chain.InternalTransactionTest do transaction: transaction, type: :reward, block_number: transaction.block_number, - block_hash: transaction.block_hash, transaction_index: transaction.index ) actual = Enum.at(InternalTransaction.all_transaction_to_internal_transactions(transaction.hash), 0) - assert {actual.transaction_hash, actual.index} == {expected.transaction_hash, expected.index} + assert {actual.block_number, actual.transaction_index, actual.index} == + {expected.block_number, expected.transaction_index, expected.index} end test "includes internal transactions of type `selfdestruct` even when they are alone in the parent transaction" do @@ -517,13 +476,13 @@ defmodule Explorer.Chain.InternalTransactionTest do gas: nil, type: :selfdestruct, block_number: transaction.block_number, - block_hash: transaction.block_hash, transaction_index: transaction.index ) actual = Enum.at(InternalTransaction.all_transaction_to_internal_transactions(transaction.hash), 0) - assert {actual.transaction_hash, actual.index} == {expected.transaction_hash, expected.index} + assert {actual.block_number, actual.transaction_index, actual.index} == + {expected.block_number, expected.transaction_index, expected.index} end test "returns the internal transactions in ascending index order" do @@ -532,30 +491,26 @@ defmodule Explorer.Chain.InternalTransactionTest do |> insert() |> with_block() - %InternalTransaction{transaction_hash: transaction_hash, index: index} = + %InternalTransaction{transaction_index: transaction_index, index: index} = insert(:internal_transaction, - transaction: transaction, index: 0, block_number: transaction.block_number, - block_hash: transaction.block_hash, transaction_index: transaction.index ) - %InternalTransaction{transaction_hash: second_transaction_hash, index: second_index} = + %InternalTransaction{transaction_index: second_transaction_index, index: second_index} = insert(:internal_transaction, - transaction: transaction, index: 1, block_number: transaction.block_number, - block_hash: transaction.block_hash, transaction_index: transaction.index ) result = transaction.hash |> InternalTransaction.all_transaction_to_internal_transactions() - |> Enum.map(&{&1.transaction_hash, &1.index}) + |> Enum.map(&{&1.transaction_index, &1.index}) - assert [{transaction_hash, index}, {second_transaction_hash, second_index}] == result + assert [{transaction_index, index}, {second_transaction_index, second_index}] == result end test "pages by index" do @@ -564,53 +519,47 @@ defmodule Explorer.Chain.InternalTransactionTest do |> insert() |> with_block() - %InternalTransaction{transaction_hash: transaction_hash, index: index} = + %InternalTransaction{transaction_index: transaction_index, index: index} = insert(:internal_transaction, - transaction: transaction, index: 0, block_number: transaction.block_number, - block_hash: transaction.block_hash, transaction_index: transaction.index ) - %InternalTransaction{transaction_hash: second_transaction_hash, index: second_index} = + %InternalTransaction{transaction_index: second_transaction_index, index: second_index} = insert(:internal_transaction, - transaction: transaction, index: 1, block_number: transaction.block_number, - block_hash: transaction.block_hash, transaction_index: transaction.index ) - %InternalTransaction{transaction_hash: third_transaction_hash, index: third_index} = + %InternalTransaction{transaction_index: third_transaction_index, index: third_index} = insert(:internal_transaction, - transaction: transaction, index: 2, block_number: transaction.block_number, - block_hash: transaction.block_hash, transaction_index: transaction.index ) - assert [{transaction_hash, index}, {second_transaction_hash, second_index}] == + assert [{transaction_index, index}, {second_transaction_index, second_index}] == transaction.hash |> InternalTransaction.all_transaction_to_internal_transactions( paging_options: %PagingOptions{key: {-1}, page_size: 2} ) - |> Enum.map(&{&1.transaction_hash, &1.index}) + |> Enum.map(&{&1.transaction_index, &1.index}) - assert [{transaction_hash, index}] == + assert [{transaction_index, index}] == transaction.hash |> InternalTransaction.all_transaction_to_internal_transactions( paging_options: %PagingOptions{key: {-1}, page_size: 1} ) - |> Enum.map(&{&1.transaction_hash, &1.index}) + |> Enum.map(&{&1.transaction_index, &1.index}) - assert [{third_transaction_hash, third_index}] == + assert [{third_transaction_index, third_index}] == transaction.hash |> InternalTransaction.all_transaction_to_internal_transactions( paging_options: %PagingOptions{key: {1}, page_size: 2} ) - |> Enum.map(&{&1.transaction_hash, &1.index}) + |> Enum.map(&{&1.transaction_index, &1.index}) end end @@ -625,36 +574,32 @@ defmodule Explorer.Chain.InternalTransactionTest do |> insert() |> with_block(block) - %InternalTransaction{transaction_hash: first_transaction_hash, index: first_index} = + %InternalTransaction{transaction_index: first_transaction_index, index: first_index} = insert(:internal_transaction, index: 1, - transaction: transaction, to_address: address, block_number: transaction.block_number, - block_hash: transaction.block_hash, transaction_index: transaction.index ) - %InternalTransaction{transaction_hash: second_transaction_hash, index: second_index} = + %InternalTransaction{transaction_index: second_transaction_index, index: second_index} = insert(:internal_transaction, index: 2, - transaction: transaction, to_address: address, block_number: transaction.block_number, - block_hash: transaction.block_hash, transaction_index: transaction.index ) result = address.hash |> InternalTransaction.address_to_internal_transactions() - |> Enum.map(&{&1.transaction_hash, &1.index}) + |> Enum.map(&{&1.transaction_index, &1.index}) - assert Enum.member?(result, {first_transaction_hash, first_index}) - assert Enum.member?(result, {second_transaction_hash, second_index}) + assert Enum.member?(result, {first_transaction_index, first_index}) + assert Enum.member?(result, {second_transaction_index, second_index}) end - test "loads associations in necessity_by_association" do + test "loads associations in address_preloads" do %Address{hash: address_hash} = address = insert(:address) block = insert(:block, number: 2000) @@ -668,7 +613,6 @@ defmodule Explorer.Chain.InternalTransactionTest do to_address: address, index: 0, block_number: transaction.block_number, - block_hash: transaction.block_hash, transaction_index: transaction.index ) @@ -677,34 +621,30 @@ defmodule Explorer.Chain.InternalTransactionTest do to_address: address, index: 1, block_number: transaction.block_number, - block_hash: transaction.block_hash, transaction_index: transaction.index ) assert [ %InternalTransaction{ - from_address: %Ecto.Association.NotLoaded{}, - to_address: %Ecto.Association.NotLoaded{}, - transaction: %Ecto.Association.NotLoaded{} + from_address: %{names: %Ecto.Association.NotLoaded{}}, + to_address: %{names: %Ecto.Association.NotLoaded{}} } | _ ] = InternalTransaction.address_to_internal_transactions(address_hash) assert [ %InternalTransaction{ - from_address: %Address{}, - to_address: %Address{}, - transaction: %Transaction{} + from_address: %Address{names: []}, + to_address: %Address{names: []} } | _ ] = InternalTransaction.address_to_internal_transactions( address_hash, - necessity_by_association: %{ - [from_address: :names] => :optional, - [to_address: :names] => :optional, - :transaction => :optional - } + address_preloads: [ + from_address: [:names], + to_address: [:names] + ] ) end @@ -718,25 +658,29 @@ defmodule Explorer.Chain.InternalTransactionTest do |> insert() |> with_block(block) - %InternalTransaction{transaction_hash: first_pending_transaction_hash, index: first_pending_index} = + %InternalTransaction{ + block_number: first_pending_block_number, + transaction_index: first_pending_transaction_index, + index: first_pending_index + } = insert( :internal_transaction, - transaction: pending_transaction, to_address: address, index: 1, block_number: pending_transaction.block_number, - block_hash: pending_transaction.block_hash, transaction_index: pending_transaction.index ) - %InternalTransaction{transaction_hash: second_pending_transaction_hash, index: second_pending_index} = + %InternalTransaction{ + block_number: second_pending_block_number, + transaction_index: second_pending_transaction_index, + index: second_pending_index + } = insert( :internal_transaction, - transaction: pending_transaction, to_address: address, index: 2, block_number: pending_transaction.block_number, - block_hash: pending_transaction.block_hash, transaction_index: pending_transaction.index ) @@ -747,25 +691,29 @@ defmodule Explorer.Chain.InternalTransactionTest do |> insert() |> with_block(a_block) - %InternalTransaction{transaction_hash: first_transaction_hash, index: first_index} = + %InternalTransaction{ + block_number: first_block_number, + transaction_index: first_transaction_index, + index: first_index + } = insert( :internal_transaction, - transaction: first_a_transaction, to_address: address, index: 1, block_number: first_a_transaction.block_number, - block_hash: a_block.hash, transaction_index: first_a_transaction.index ) - %InternalTransaction{transaction_hash: second_transaction_hash, index: second_index} = + %InternalTransaction{ + block_number: second_block_number, + transaction_index: second_transaction_index, + index: second_index + } = insert( :internal_transaction, - transaction: first_a_transaction, to_address: address, index: 2, block_number: first_a_transaction.block_number, - block_hash: a_block.hash, transaction_index: first_a_transaction.index ) @@ -774,25 +722,29 @@ defmodule Explorer.Chain.InternalTransactionTest do |> insert() |> with_block(a_block) - %InternalTransaction{transaction_hash: third_transaction_hash, index: third_index} = + %InternalTransaction{ + block_number: third_block_number, + transaction_index: third_transaction_index, + index: third_index + } = insert( :internal_transaction, - transaction: second_a_transaction, to_address: address, index: 1, block_number: second_a_transaction.block_number, - block_hash: a_block.hash, transaction_index: second_a_transaction.index ) - %InternalTransaction{transaction_hash: fourth_transaction_hash, index: fourth_index} = + %InternalTransaction{ + block_number: fourth_block_number, + transaction_index: fourth_transaction_index, + index: fourth_index + } = insert( :internal_transaction, - transaction: second_a_transaction, to_address: address, index: 2, block_number: second_a_transaction.block_number, - block_hash: a_block.hash, transaction_index: second_a_transaction.index ) @@ -803,42 +755,46 @@ defmodule Explorer.Chain.InternalTransactionTest do |> insert() |> with_block(b_block) - %InternalTransaction{transaction_hash: fifth_transaction_hash, index: fifth_index} = + %InternalTransaction{ + block_number: fifth_block_number, + transaction_index: fifth_transaction_index, + index: fifth_index + } = insert( :internal_transaction, - transaction: first_b_transaction, to_address: address, index: 1, block_number: first_b_transaction.block_number, - block_hash: b_block.hash, transaction_index: first_b_transaction.index ) - %InternalTransaction{transaction_hash: sixth_transaction_hash, index: sixth_index} = + %InternalTransaction{ + block_number: sixth_block_number, + transaction_index: sixth_transaction_index, + index: sixth_index + } = insert( :internal_transaction, - transaction: first_b_transaction, to_address: address, index: 2, block_number: first_b_transaction.block_number, - block_hash: b_block.hash, transaction_index: first_b_transaction.index ) result = address.hash |> InternalTransaction.address_to_internal_transactions() - |> Enum.map(&{&1.transaction_hash, &1.index}) + |> Enum.map(&{&1.block_number, &1.transaction_index, &1.index}) assert [ - {second_pending_transaction_hash, second_pending_index}, - {first_pending_transaction_hash, first_pending_index}, - {sixth_transaction_hash, sixth_index}, - {fifth_transaction_hash, fifth_index}, - {fourth_transaction_hash, fourth_index}, - {third_transaction_hash, third_index}, - {second_transaction_hash, second_index}, - {first_transaction_hash, first_index} + {second_pending_block_number, second_pending_transaction_index, second_pending_index}, + {first_pending_block_number, first_pending_transaction_index, first_pending_index}, + {sixth_block_number, sixth_transaction_index, sixth_index}, + {fifth_block_number, fifth_transaction_index, fifth_index}, + {fourth_block_number, fourth_transaction_index, fourth_index}, + {third_block_number, third_transaction_index, third_index}, + {second_block_number, second_transaction_index, second_index}, + {first_block_number, first_transaction_index, first_index} ] == result end @@ -854,25 +810,29 @@ defmodule Explorer.Chain.InternalTransactionTest do |> insert() |> with_block(a_block) - %InternalTransaction{transaction_hash: first_transaction_hash, index: first_index} = + %InternalTransaction{ + block_number: first_block_number, + transaction_index: first_transaction_index, + index: first_index + } = insert( :internal_transaction, - transaction: first_a_transaction, to_address: address, index: 1, block_number: first_a_transaction.block_number, - block_hash: a_block.hash, transaction_index: first_a_transaction.index ) - %InternalTransaction{transaction_hash: second_transaction_hash, index: second_index} = + %InternalTransaction{ + block_number: second_block_number, + transaction_index: second_transaction_index, + index: second_index + } = insert( :internal_transaction, - transaction: first_a_transaction, to_address: address, index: 2, block_number: first_a_transaction.block_number, - block_hash: a_block.hash, transaction_index: first_a_transaction.index ) @@ -881,25 +841,29 @@ defmodule Explorer.Chain.InternalTransactionTest do |> insert() |> with_block(a_block) - %InternalTransaction{transaction_hash: third_transaction_hash, index: third_index} = + %InternalTransaction{ + block_number: third_block_number, + transaction_index: third_transaction_index, + index: third_index + } = insert( :internal_transaction, - transaction: second_a_transaction, to_address: address, index: 1, block_number: second_a_transaction.block_number, - block_hash: a_block.hash, transaction_index: second_a_transaction.index ) - %InternalTransaction{transaction_hash: fourth_transaction_hash, index: fourth_index} = + %InternalTransaction{ + block_number: fourth_block_number, + transaction_index: fourth_transaction_index, + index: fourth_index + } = insert( :internal_transaction, - transaction: second_a_transaction, to_address: address, index: 2, block_number: second_a_transaction.block_number, - block_hash: a_block.hash, transaction_index: second_a_transaction.index ) @@ -910,69 +874,73 @@ defmodule Explorer.Chain.InternalTransactionTest do |> insert() |> with_block(b_block) - %InternalTransaction{transaction_hash: fifth_transaction_hash, index: fifth_index} = + %InternalTransaction{ + block_number: fifth_block_number, + transaction_index: fifth_transaction_index, + index: fifth_index + } = insert( :internal_transaction, - transaction: first_b_transaction, to_address: address, index: 1, block_number: first_b_transaction.block_number, - block_hash: b_block.hash, transaction_index: first_b_transaction.index ) - %InternalTransaction{transaction_hash: sixth_transaction_hash, index: sixth_index} = + %InternalTransaction{ + block_number: sixth_block_number, + transaction_index: sixth_transaction_index, + index: sixth_index + } = insert( :internal_transaction, - transaction: first_b_transaction, to_address: address, index: 2, block_number: first_b_transaction.block_number, - block_hash: b_block.hash, transaction_index: first_b_transaction.index ) # When paged, internal transactions need an associated block number, so `second_pending` and `first_pending` are # excluded. assert [ - {sixth_transaction_hash, sixth_index}, - {fifth_transaction_hash, fifth_index}, - {fourth_transaction_hash, fourth_index}, - {third_transaction_hash, third_index}, - {second_transaction_hash, second_index}, - {first_transaction_hash, first_index} + {sixth_block_number, sixth_transaction_index, sixth_index}, + {fifth_block_number, fifth_transaction_index, fifth_index}, + {fourth_block_number, fourth_transaction_index, fourth_index}, + {third_block_number, third_transaction_index, third_index}, + {second_block_number, second_transaction_index, second_index}, + {first_block_number, first_transaction_index, first_index} ] == address.hash |> InternalTransaction.address_to_internal_transactions( paging_options: %PagingOptions{key: {6001, 3, 2}, page_size: 8} ) - |> Enum.map(&{&1.transaction_hash, &1.index}) + |> Enum.map(&{&1.block_number, &1.transaction_index, &1.index}) # block number ==, transaction index ==, internal transaction index < assert [ - {fourth_transaction_hash, fourth_index}, - {third_transaction_hash, third_index}, - {second_transaction_hash, second_index}, - {first_transaction_hash, first_index} + {fourth_block_number, fourth_transaction_index, fourth_index}, + {third_block_number, third_transaction_index, third_index}, + {second_block_number, second_transaction_index, second_index}, + {first_block_number, first_transaction_index, first_index} ] == address.hash |> InternalTransaction.address_to_internal_transactions( paging_options: %PagingOptions{key: {6000, 0, 1}, page_size: 8} ) - |> Enum.map(&{&1.transaction_hash, &1.index}) + |> Enum.map(&{&1.block_number, &1.transaction_index, &1.index}) # block number ==, transaction index < assert [ - {fourth_transaction_hash, fourth_index}, - {third_transaction_hash, third_index}, - {second_transaction_hash, second_index}, - {first_transaction_hash, first_index} + {fourth_block_number, fourth_transaction_index, fourth_index}, + {third_block_number, third_transaction_index, third_index}, + {second_block_number, second_transaction_index, second_index}, + {first_block_number, first_transaction_index, first_index} ] == address.hash |> InternalTransaction.address_to_internal_transactions( paging_options: %PagingOptions{key: {6000, -1, -1}, page_size: 8} ) - |> Enum.map(&{&1.transaction_hash, &1.index}) + |> Enum.map(&{&1.block_number, &1.transaction_index, &1.index}) # block number < assert [] == @@ -980,7 +948,7 @@ defmodule Explorer.Chain.InternalTransactionTest do |> InternalTransaction.address_to_internal_transactions( paging_options: %PagingOptions{key: {2000, -1, -1}, page_size: 8} ) - |> Enum.map(&{&1.transaction_hash, &1.index}) + |> Enum.map(&{&1.block_number, &1.transaction_index, &1.index}) end test "excludes internal transactions of type `call` when they are alone in the parent transaction" do @@ -996,7 +964,6 @@ defmodule Explorer.Chain.InternalTransactionTest do to_address: address, transaction: transaction, block_number: transaction.block_number, - block_hash: transaction.block_hash, transaction_index: transaction.index ) @@ -1017,14 +984,14 @@ defmodule Explorer.Chain.InternalTransactionTest do index: 0, from_address: address, transaction: transaction, - block_hash: transaction.block_hash, block_number: transaction.block_number, transaction_index: transaction.index ) actual = Enum.at(InternalTransaction.address_to_internal_transactions(address_hash), 0) - assert {actual.transaction_hash, actual.index} == {expected.transaction_hash, expected.index} + assert {actual.block_number, actual.transaction_index, actual.index} == + {expected.block_number, expected.transaction_index, expected.index} end end diff --git a/apps/explorer/test/explorer/chain/smart_contract_test.exs b/apps/explorer/test/explorer/chain/smart_contract_test.exs index a501af868afc..35a80a44673c 100644 --- a/apps/explorer/test/explorer/chain/smart_contract_test.exs +++ b/apps/explorer/test/explorer/chain/smart_contract_test.exs @@ -33,7 +33,6 @@ defmodule Explorer.Chain.SmartContractTest do created_contract_address: created_contract_address, created_contract_code: smart_contract_bytecode, block_number: transaction.block_number, - block_hash: transaction.block_hash, transaction_index: transaction.index ) @@ -134,7 +133,6 @@ defmodule Explorer.Chain.SmartContractTest do created_contract_address: created_contract_address, created_contract_code: smart_contract_bytecode, block_number: transaction.block_number, - block_hash: transaction.block_hash, transaction_index: transaction.index ) diff --git a/apps/explorer/test/explorer/chain/transaction_test.exs b/apps/explorer/test/explorer/chain/transaction_test.exs index 0c21eb43f3da..2831969391fe 100644 --- a/apps/explorer/test/explorer/chain/transaction_test.exs +++ b/apps/explorer/test/explorer/chain/transaction_test.exs @@ -538,10 +538,8 @@ defmodule Explorer.Chain.TransactionTest do %InternalTransaction{created_contract_address: address} = insert(:internal_transaction_create, - transaction: transaction, index: 0, block_number: transaction.block_number, - block_hash: transaction.block_hash, transaction_index: transaction.index ) diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index 231f7425606d..ba3deeebda23 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -39,6 +39,8 @@ defmodule Explorer.ChainTest do alias Explorer.TestHelper + alias Explorer.Utility.AddressIdToAddressHash + @first_topic_hex_string "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" @second_topic_hex_string "0x000000000000000000000000e8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca" @third_topic_hex_string "0x000000000000000000000000515c09c5bba1ed566b02a5b0599ec5d5d0aee73d" @@ -665,7 +667,6 @@ defmodule Explorer.ChainTest do transaction: transaction, index: 0, block_number: transaction.block_number, - block_hash: transaction.block_hash, transaction_index: transaction.index ) @@ -674,7 +675,6 @@ defmodule Explorer.ChainTest do transaction: transaction, index: index, block_number: transaction.block_number, - block_hash: transaction.block_hash, transaction_index: transaction.index ) end) @@ -1121,7 +1121,7 @@ defmodule Explorer.ChainTest do gas_price: 100_000_000_000, gas_used: 50450, hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", - index: 0, + index: 1, input: "0x10855269000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef", nonce: 4, public_key: @@ -1182,6 +1182,11 @@ defmodule Explorer.ChainTest do gas_int = Decimal.new("4677320") gas_used_int = Decimal.new("27770") + %{address_id: from_address_id} = + AddressIdToAddressHash.find_or_create("0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca") + + %{address_id: to_address_id} = AddressIdToAddressHash.find_or_create("0x8bf38d4764929064f2d4d3a56520a76ab3df415b") + assert {:ok, %{ addresses: [ @@ -1283,18 +1288,8 @@ defmodule Explorer.ChainTest do block_number: 37, transaction_index: 1, created_contract_address_hash: nil, - from_address_hash: %Explorer.Chain.Hash{ - byte_count: 20, - bytes: - <<232, 221, 197, 199, 162, 210, 240, 215, 169, 121, 132, 89, 192, 16, 79, 223, 94, 152, 122, - 202>> - }, - to_address_hash: %Explorer.Chain.Hash{ - byte_count: 20, - bytes: - <<139, 243, 141, 71, 100, 146, 144, 100, 242, 212, 211, 165, 101, 32, 167, 106, 179, 223, 65, - 91>> - } + from_address_id: ^from_address_id, + to_address_id: ^to_address_id } ], logs: [ @@ -1328,7 +1323,7 @@ defmodule Explorer.ChainTest do transactions: [ %Transaction{ block_number: 37, - index: 0, + index: 1, hash: %Hash{ byte_count: 32, bytes: @@ -2125,7 +2120,6 @@ defmodule Explorer.ChainTest do created_contract_address: created_contract_address, created_contract_code: smart_contract_bytecode, block_number: transaction.block_number, - block_hash: transaction.block_hash, transaction_index: transaction.index ) diff --git a/apps/explorer/test/explorer/etherscan_test.exs b/apps/explorer/test/explorer/etherscan_test.exs index 7cc0a49f0be3..6b97051c878a 100644 --- a/apps/explorer/test/explorer/etherscan_test.exs +++ b/apps/explorer/test/explorer/etherscan_test.exs @@ -4,7 +4,8 @@ defmodule Explorer.EtherscanTest do import Explorer.Factory alias Explorer.{Etherscan, Chain} - alias Explorer.Chain.Transaction + alias Explorer.Chain.{InternalTransaction, Transaction} + alias Explorer.Utility.AddressIdToAddressHash describe "list_transactions/2" do test "with empty db" do @@ -62,16 +63,18 @@ defmodule Explorer.EtherscanTest do |> with_contract_creation(contract_address) |> with_block() - %{created_contract_address_hash: contract_address_hash} = + %{created_contract_address_id: contract_address_id} = :internal_transaction_create |> insert( transaction: transaction, index: 0, block_number: transaction.block_number, - block_hash: transaction.block_hash, + created_contract_code: contract_address.contract_code, + created_contract_address: contract_address, transaction_index: transaction.index ) - |> with_contract_creation(contract_address) + + contract_address_hash = AddressIdToAddressHash.id_to_hash(contract_address_id) [found_transaction] = Etherscan.list_transactions(contract_address_hash) @@ -139,16 +142,18 @@ defmodule Explorer.EtherscanTest do |> with_contract_creation(contract_address) |> with_block() - %{created_contract_address_hash: contract_hash} = + %{created_contract_address_id: contract_id} = :internal_transaction_create |> insert( transaction: transaction, index: 0, block_number: transaction.block_number, - block_hash: transaction.block_hash, + created_contract_code: contract_address.contract_code, + created_contract_address: contract_address, transaction_index: transaction.index ) - |> with_contract_creation(contract_address) + + contract_hash = AddressIdToAddressHash.id_to_hash(contract_id) [found_transaction] = Etherscan.list_transactions(address.hash) @@ -639,11 +644,12 @@ defmodule Explorer.EtherscanTest do index: 0, value: 1, from_address: address, + created_contract_code: contract_address.contract_code, + created_contract_address: contract_address, block_number: transaction.block_number, - block_hash: transaction.block_hash, transaction_index: transaction.index ) - |> with_contract_creation(contract_address) + |> InternalTransaction.preload_addresses() [found_internal_transaction] = Etherscan.list_internal_transactions(transaction.hash) @@ -660,7 +666,7 @@ defmodule Explorer.EtherscanTest do assert found_internal_transaction.type == internal_transaction.type assert found_internal_transaction.gas == internal_transaction.gas assert found_internal_transaction.gas_used == internal_transaction.gas_used - assert found_internal_transaction.error == internal_transaction.error + assert found_internal_transaction.error_id == internal_transaction.error_id end test "with transaction with 0 internal transactions" do @@ -684,7 +690,6 @@ defmodule Explorer.EtherscanTest do index: index, value: index + 1, block_number: transaction.block_number, - block_hash: transaction.block_hash, transaction_index: transaction.index ) end @@ -711,7 +716,6 @@ defmodule Explorer.EtherscanTest do index: 0, value: 1, block_number: transaction1.block_number, - block_hash: transaction1.block_hash, transaction_index: transaction1.index ) @@ -720,7 +724,6 @@ defmodule Explorer.EtherscanTest do index: 1, value: 2, block_number: transaction1.block_number, - block_hash: transaction1.block_hash, transaction_index: transaction1.index ) @@ -730,7 +733,6 @@ defmodule Explorer.EtherscanTest do value: 3, type: :reward, block_number: transaction2.block_number, - block_hash: transaction2.block_hash, transaction_index: transaction2.index ) @@ -756,8 +758,7 @@ defmodule Explorer.EtherscanTest do transaction_index: transaction.index, index: index, value: index + 1, - block_number: transaction.block_number, - block_hash: transaction.block_hash + block_number: transaction.block_number ) end @@ -767,8 +768,7 @@ defmodule Explorer.EtherscanTest do transaction_index: transaction.index, index: index, value: 0, - block_number: transaction.block_number, - block_hash: transaction.block_hash + block_number: transaction.block_number ) end @@ -789,8 +789,7 @@ defmodule Explorer.EtherscanTest do transaction_index: transaction.index, index: index, value: index + 1, - block_number: transaction.block_number, - block_hash: transaction.block_hash + block_number: transaction.block_number ) end @@ -800,8 +799,7 @@ defmodule Explorer.EtherscanTest do transaction_index: transaction.index, index: index, value: 0, - block_number: transaction.block_number, - block_hash: transaction.block_hash + block_number: transaction.block_number ) end @@ -836,7 +834,7 @@ defmodule Explorer.EtherscanTest do address = insert(:address) contract_address = insert(:contract_address) - block = insert(:block) + %{number: block_number, timestamp: block_timestamp} = block = insert(:block) transaction = :transaction @@ -844,41 +842,55 @@ defmodule Explorer.EtherscanTest do |> with_contract_creation(contract_address) |> with_block(block) - internal_transaction = + transaction_hash = transaction.hash + + %{ + from_address_hash: from_address_hash, + to_address_hash: to_address_hash, + value: value, + created_contract_address_hash: created_contract_address_hash, + input: input, + index: index, + transaction_index: transaction_index, + type: type, + call_type: call_type, + call_type_enum: call_type_enum, + gas: gas, + gas_used: gas_used, + error_id: error_id + } = :internal_transaction_create |> insert( transaction: transaction, index: 0, from_address: address, + created_contract_code: contract_address.contract_code, + created_contract_address: contract_address, block_number: transaction.block_number, - block_hash: block.hash, transaction_index: transaction.index ) - |> with_contract_creation(contract_address) + |> InternalTransaction.preload_addresses() [found_internal_transaction] = Etherscan.list_internal_transactions(address.hash) - expected = %{ - block_number: block.number, - block_timestamp: block.timestamp, - from_address_hash: internal_transaction.from_address_hash, - to_address_hash: internal_transaction.to_address_hash, - value: internal_transaction.value, - created_contract_address_hash: internal_transaction.created_contract_address_hash, - input: internal_transaction.input, - index: internal_transaction.index, - transaction_hash: internal_transaction.transaction_hash, - transaction_index: internal_transaction.transaction_index, - type: internal_transaction.type, - call_type: internal_transaction.call_type, - call_type_enum: internal_transaction.call_type_enum, - gas: internal_transaction.gas, - gas_used: internal_transaction.gas_used, - error: internal_transaction.error, - error_id: internal_transaction.error_id - } - - assert found_internal_transaction == expected + assert %{ + block_number: ^block_number, + block_timestamp: ^block_timestamp, + from_address_hash: ^from_address_hash, + to_address_hash: ^to_address_hash, + value: ^value, + created_contract_address_hash: ^created_contract_address_hash, + input: ^input, + index: ^index, + transaction_hash: ^transaction_hash, + transaction_index: ^transaction_index, + type: ^type, + call_type: ^call_type, + call_type_enum: ^call_type_enum, + gas: ^gas, + gas_used: ^gas_used, + error_id: ^error_id + } = found_internal_transaction end test "with address with 0 internal transactions" do @@ -904,7 +916,6 @@ defmodule Explorer.EtherscanTest do index: index, from_address: address, block_number: transaction.block_number, - block_hash: transaction.block_hash, transaction_index: transaction.index } @@ -929,7 +940,6 @@ defmodule Explorer.EtherscanTest do transaction: transaction, index: 0, block_number: transaction.block_number, - block_hash: transaction.block_hash, transaction_index: transaction.index, created_contract_address: address1 ) @@ -938,7 +948,6 @@ defmodule Explorer.EtherscanTest do transaction: transaction, index: 1, block_number: transaction.block_number, - block_hash: transaction.block_hash, transaction_index: transaction.index, from_address: address1 ) @@ -947,7 +956,6 @@ defmodule Explorer.EtherscanTest do transaction: transaction, index: 2, block_number: transaction.block_number, - block_hash: transaction.block_hash, transaction_index: transaction.index, to_address: address1 ) @@ -956,7 +964,6 @@ defmodule Explorer.EtherscanTest do transaction: transaction, index: 3, block_number: transaction.block_number, - block_hash: transaction.block_hash, transaction_index: transaction.index, from_address: address2 ) @@ -985,7 +992,6 @@ defmodule Explorer.EtherscanTest do index: index, from_address: address, block_number: transaction.block_number, - block_hash: transaction.block_hash, transaction_index: transaction.index } @@ -1027,7 +1033,6 @@ defmodule Explorer.EtherscanTest do index: index, from_address: address, block_number: block.number, - block_hash: block.hash, transaction_index: transaction.index, value: 1 } @@ -1068,8 +1073,7 @@ defmodule Explorer.EtherscanTest do index: index, value: 0, from_address: address, - block_number: transaction.block_number, - block_hash: transaction.block_hash + block_number: transaction.block_number } insert(:internal_transaction, internal_transaction_details) @@ -1082,8 +1086,7 @@ defmodule Explorer.EtherscanTest do index: index, value: index, from_address: address, - block_number: transaction.block_number, - block_hash: transaction.block_hash + block_number: transaction.block_number } insert(:internal_transaction, internal_transaction_details) @@ -1109,8 +1112,7 @@ defmodule Explorer.EtherscanTest do index: index, value: 0, from_address: address, - block_number: transaction.block_number, - block_hash: transaction.block_hash + block_number: transaction.block_number } insert(:internal_transaction, internal_transaction_details) @@ -1123,8 +1125,7 @@ defmodule Explorer.EtherscanTest do index: index, value: index, from_address: address, - block_number: transaction.block_number, - block_hash: transaction.block_hash + block_number: transaction.block_number } insert(:internal_transaction, internal_transaction_details) @@ -1166,8 +1167,7 @@ defmodule Explorer.EtherscanTest do index: index, value: 0, from_address: address, - block_number: transaction.block_number, - block_hash: transaction.block_hash + block_number: transaction.block_number } insert(:internal_transaction, internal_transaction_details) @@ -1180,8 +1180,7 @@ defmodule Explorer.EtherscanTest do index: index, value: index, from_address: address, - block_number: transaction.block_number, - block_hash: transaction.block_hash + block_number: transaction.block_number } insert(:internal_transaction, internal_transaction_details) @@ -1207,8 +1206,7 @@ defmodule Explorer.EtherscanTest do index: index, value: 0, from_address: address, - block_number: transaction.block_number, - block_hash: transaction.block_hash + block_number: transaction.block_number } insert(:internal_transaction, internal_transaction_details) @@ -1221,8 +1219,7 @@ defmodule Explorer.EtherscanTest do index: index, value: index, from_address: address, - block_number: transaction.block_number, - block_hash: transaction.block_hash + block_number: transaction.block_number } insert(:internal_transaction, internal_transaction_details) @@ -1635,6 +1632,55 @@ defmodule Explorer.EtherscanTest do assert found_token_transfer.token_contract_address_hash == contract_address.hash end + + test "does not duplicate erc20 transfers when address is both sender and receiver" do + address = insert(:address) + + transaction = + :transaction + |> insert() + |> with_block() + + insert(:token_transfer, + from_address: address, + to_address: address, + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number + ) + + result = Etherscan.list_token_transfers(:erc20, address.hash, nil, %{}) + + assert length(result) == 1 + end + + test "does not duplicate erc1155 transfers when address is both sender and receiver" do + address = insert(:address) + + token_contract_address = insert(:contract_address) + insert(:token, contract_address: token_contract_address, type: "ERC-1155") + + transaction = + :transaction + |> insert() + |> with_block() + + insert(:token_transfer, + from_address: address, + to_address: address, + token_contract_address: token_contract_address, + token_type: "ERC-1155", + token_ids: [Decimal.new(1)], + amounts: [Decimal.new(10)], + transaction: transaction, + block: transaction.block, + block_number: transaction.block_number + ) + + result = Etherscan.list_token_transfers(:erc1155, address.hash, nil, %{}) + + assert length(result) == 1 + end end describe "list_blocks/1" do diff --git a/apps/explorer/test/explorer/graphql_test.exs b/apps/explorer/test/explorer/graphql_test.exs index 7496dcb71e11..3d47d5296b6f 100644 --- a/apps/explorer/test/explorer/graphql_test.exs +++ b/apps/explorer/test/explorer/graphql_test.exs @@ -4,7 +4,7 @@ defmodule Explorer.GraphQLTest do import Explorer.Factory alias Explorer.{GraphQL, Repo} - alias Explorer.Chain.Address + alias Explorer.Chain.{Address, InternalTransaction} describe "address_to_transactions_query/1" do test "with address hash with zero transactions" do @@ -99,7 +99,6 @@ defmodule Explorer.GraphQLTest do transaction: transaction, transaction_index: transaction.index, index: 0, - block_hash: transaction.block_hash, block_number: transaction.block_number ) @@ -107,7 +106,7 @@ defmodule Explorer.GraphQLTest do {:ok, found_internal_transaction} = GraphQL.get_internal_transaction(clauses) - assert found_internal_transaction.transaction_hash == transaction.hash + assert found_internal_transaction.transaction.hash == transaction.hash assert found_internal_transaction.index == internal_transaction.index end @@ -132,7 +131,6 @@ defmodule Explorer.GraphQLTest do transaction: transaction1, transaction_index: transaction1.index, index: 0, - block_hash: transaction1.block_hash, block_number: transaction1.block_number ) @@ -140,7 +138,6 @@ defmodule Explorer.GraphQLTest do transaction: transaction2, transaction_index: transaction2.index, index: 0, - block_hash: transaction2.block_hash, block_number: transaction2.block_number ) @@ -148,8 +145,9 @@ defmodule Explorer.GraphQLTest do transaction1 |> GraphQL.transaction_to_internal_transactions_query() |> Repo.replica().all() + |> InternalTransaction.preload_transaction() - assert found_internal_transaction.transaction_hash == transaction1.hash + assert found_internal_transaction.transaction.hash == transaction1.hash assert found_internal_transaction.index == internal_transaction.index end @@ -162,7 +160,6 @@ defmodule Explorer.GraphQLTest do transaction: transaction1, transaction_index: transaction1.index, index: index, - block_hash: transaction1.block_hash, block_number: transaction1.block_number ) end @@ -171,7 +168,6 @@ defmodule Explorer.GraphQLTest do transaction: transaction2, transaction_index: transaction2.index, index: 0, - block_hash: transaction2.block_hash, block_number: transaction2.block_number ) @@ -179,11 +175,12 @@ defmodule Explorer.GraphQLTest do transaction1 |> GraphQL.transaction_to_internal_transactions_query() |> Repo.replica().all() + |> InternalTransaction.preload_transaction() assert length(found_internal_transactions) == 3 for found_internal_transaction <- found_internal_transactions do - assert found_internal_transaction.transaction_hash == transaction1.hash + assert found_internal_transaction.transaction.hash == transaction1.hash end end @@ -194,7 +191,6 @@ defmodule Explorer.GraphQLTest do transaction: transaction, transaction_index: transaction.index, index: 2, - block_hash: transaction.block_hash, block_number: transaction.block_number ) @@ -202,7 +198,6 @@ defmodule Explorer.GraphQLTest do transaction: transaction, transaction_index: transaction.index, index: 0, - block_hash: transaction.block_hash, block_number: transaction.block_number ) @@ -210,7 +205,6 @@ defmodule Explorer.GraphQLTest do transaction: transaction, transaction_index: transaction.index, index: 1, - block_hash: transaction.block_hash, block_number: transaction.block_number ) diff --git a/apps/explorer/test/explorer/microservice_interfaces/bens_test.exs b/apps/explorer/test/explorer/microservice_interfaces/bens_test.exs new file mode 100644 index 000000000000..44b3de7b7da2 --- /dev/null +++ b/apps/explorer/test/explorer/microservice_interfaces/bens_test.exs @@ -0,0 +1,57 @@ +defmodule Explorer.MicroserviceInterfaces.BENSTest do + use ExUnit.Case, async: false + + alias Explorer.MicroserviceInterfaces.BENS + + setup do + old_bens_env = Application.get_env(:explorer, BENS, []) + + on_exit(fn -> + Application.put_env(:explorer, BENS, old_bens_env) + end) + + :ok + end + + describe "maybe_preload_ens_for_blocks/1" do + test "returns input as-is when blocks BENS preload is disabled" do + Application.put_env( + :explorer, + BENS, + Keyword.put(Application.get_env(:explorer, BENS, []), :disable_blocks_bens_preload, true) + ) + + blocks = [%{number: 1}, %{number: 2}] + + assert BENS.maybe_preload_ens_for_blocks(blocks) == blocks + end + end + + describe "maybe_preload_ens_for_transactions/1" do + test "returns input as-is when transactions BENS preload is disabled" do + Application.put_env( + :explorer, + BENS, + Keyword.put(Application.get_env(:explorer, BENS, []), :disable_transactions_bens_preload, true) + ) + + transactions = [%{hash: "0x1"}, %{hash: "0x2"}] + + assert BENS.maybe_preload_ens_for_transactions(transactions) == transactions + end + end + + describe "maybe_preload_ens_for_token_transfers/1" do + test "returns input as-is when token transfers BENS preload is disabled" do + Application.put_env( + :explorer, + BENS, + Keyword.put(Application.get_env(:explorer, BENS, []), :disable_token_transfers_bens_preload, true) + ) + + token_transfers = [%{token_id: "1"}, %{token_id: "2"}] + + assert BENS.maybe_preload_ens_for_token_transfers(token_transfers) == token_transfers + end + end +end diff --git a/apps/explorer/test/explorer/migrator/delete_zero_value_internal_transactions_test.exs b/apps/explorer/test/explorer/migrator/delete_zero_value_internal_transactions_test.exs index dd5c5c80ab9f..5447e4ee0745 100644 --- a/apps/explorer/test/explorer/migrator/delete_zero_value_internal_transactions_test.exs +++ b/apps/explorer/test/explorer/migrator/delete_zero_value_internal_transactions_test.exs @@ -36,7 +36,6 @@ defmodule Explorer.Migrator.DeleteZeroValueInternalTransactionsTest do insert(:internal_transaction, index: 10, transaction: transaction, - block_hash: transaction.block_hash, block_number: transaction.block_number, from_address: address_1, to_address: address_2, @@ -55,7 +54,6 @@ defmodule Explorer.Migrator.DeleteZeroValueInternalTransactionsTest do insert(:internal_transaction, index: 10, transaction: transaction, - block_hash: transaction.block_hash, block_number: transaction.block_number, from_address: address_2, to_address: address_3, @@ -74,7 +72,6 @@ defmodule Explorer.Migrator.DeleteZeroValueInternalTransactionsTest do insert(:internal_transaction, index: 10, transaction: transaction, - block_hash: transaction.block_hash, block_number: transaction.block_number, from_address: address_3, to_address: address_1, @@ -93,7 +90,6 @@ defmodule Explorer.Migrator.DeleteZeroValueInternalTransactionsTest do insert(:internal_transaction, index: 10, transaction: transaction, - block_hash: transaction.block_hash, block_number: transaction.block_number, transaction_index: transaction.index, type: :call, @@ -110,7 +106,6 @@ defmodule Explorer.Migrator.DeleteZeroValueInternalTransactionsTest do insert(:internal_transaction_create, index: 10, transaction: transaction, - block_hash: transaction.block_hash, block_number: transaction.block_number, transaction_index: transaction.index, value: 0 @@ -126,7 +121,6 @@ defmodule Explorer.Migrator.DeleteZeroValueInternalTransactionsTest do insert(:internal_transaction, index: 10, transaction: transaction, - block_hash: transaction.block_hash, block_number: transaction.block_number, transaction_index: transaction.index, type: :call, @@ -175,16 +169,9 @@ defmodule Explorer.Migrator.DeleteZeroValueInternalTransactionsTest do id_to_hashes = Repo.all(AddressIdToAddressHash) - assert length(id_to_hashes) == 3 - - assert %{address_id: address_1_id} = - Enum.find(id_to_hashes, &(&1.address_hash == address_1.hash)) - - assert %{address_id: address_2_id} = - Enum.find(id_to_hashes, &(&1.address_hash == address_2.hash)) - - assert %{address_id: address_3_id} = - Enum.find(id_to_hashes, &(&1.address_hash == address_3.hash)) + assert %{address_id: address_1_id} = Enum.find(id_to_hashes, &(&1.address_hash == address_1.hash)) + assert %{address_id: address_2_id} = Enum.find(id_to_hashes, &(&1.address_hash == address_2.hash)) + assert %{address_id: address_3_id} = Enum.find(id_to_hashes, &(&1.address_hash == address_3.hash)) placeholders = Repo.all(InternalTransactionsAddressPlaceholder) @@ -256,7 +243,6 @@ defmodule Explorer.Migrator.DeleteZeroValueInternalTransactionsTest do insert(:internal_transaction, index: 10, transaction: transaction, - block_hash: transaction.block_hash, block_number: transaction.block_number, transaction_index: transaction.index, from_address: address_1, @@ -317,8 +303,6 @@ defmodule Explorer.Migrator.DeleteZeroValueInternalTransactionsTest do insert(:internal_transaction, index: 10, - transaction: transaction, - block_hash: transaction.block_hash, block_number: transaction.block_number, transaction_index: transaction.index, from_address: address_1, diff --git a/apps/explorer/test/explorer/migrator/empty_bytecode_for_selfdestructed_smart_contracts_test.exs b/apps/explorer/test/explorer/migrator/empty_bytecode_for_selfdestructed_smart_contracts_test.exs index 24de2c157fa0..2c050aa47b65 100644 --- a/apps/explorer/test/explorer/migrator/empty_bytecode_for_selfdestructed_smart_contracts_test.exs +++ b/apps/explorer/test/explorer/migrator/empty_bytecode_for_selfdestructed_smart_contracts_test.exs @@ -23,7 +23,6 @@ defmodule Explorer.Migrator.EmptyBytecodeForSelfdestructedSmartContractsTest do insert(:internal_transaction, transaction: transaction, - block_hash: transaction.block_hash, block_number: transaction.block_number, index: 0, transaction_index: transaction.index, @@ -46,7 +45,6 @@ defmodule Explorer.Migrator.EmptyBytecodeForSelfdestructedSmartContractsTest do insert(:internal_transaction, transaction: transaction, - block_hash: transaction.block_hash, block_number: transaction.block_number, index: 0, transaction_index: transaction.index, @@ -76,7 +74,6 @@ defmodule Explorer.Migrator.EmptyBytecodeForSelfdestructedSmartContractsTest do insert(:internal_transaction_create, transaction: transaction, - block_hash: transaction.block_hash, block_number: transaction.block_number, index: 0, transaction_index: transaction.index, @@ -86,7 +83,6 @@ defmodule Explorer.Migrator.EmptyBytecodeForSelfdestructedSmartContractsTest do insert(:internal_transaction, transaction: transaction, - block_hash: transaction.block_hash, block_number: transaction.block_number, index: 1, transaction_index: transaction.index, @@ -116,7 +112,6 @@ defmodule Explorer.Migrator.EmptyBytecodeForSelfdestructedSmartContractsTest do insert(:internal_transaction_create, transaction: transaction, - block_hash: transaction.block_hash, block_number: transaction.block_number, index: 0, transaction_index: transaction.index, @@ -127,7 +122,6 @@ defmodule Explorer.Migrator.EmptyBytecodeForSelfdestructedSmartContractsTest do insert(:internal_transaction, transaction: transaction, - block_hash: transaction.block_hash, block_number: transaction.block_number, index: 1, transaction_index: transaction.index, @@ -161,7 +155,6 @@ defmodule Explorer.Migrator.EmptyBytecodeForSelfdestructedSmartContractsTest do insert(:internal_transaction, transaction: transaction_1, - block_hash: transaction_1.block_hash, block_number: transaction_1.block_number, index: 0, transaction_index: transaction_1.index, @@ -173,7 +166,6 @@ defmodule Explorer.Migrator.EmptyBytecodeForSelfdestructedSmartContractsTest do insert(:internal_transaction, transaction: transaction_2, - block_hash: transaction_2.block_hash, block_number: transaction_2.block_number, index: 0, transaction_index: transaction_2.index, @@ -205,7 +197,6 @@ defmodule Explorer.Migrator.EmptyBytecodeForSelfdestructedSmartContractsTest do insert(:internal_transaction, transaction: transaction, - block_hash: transaction.block_hash, block_number: transaction.block_number, index: 0, transaction_index: transaction.index, @@ -234,7 +225,6 @@ defmodule Explorer.Migrator.EmptyBytecodeForSelfdestructedSmartContractsTest do insert(:internal_transaction, transaction: transaction, - block_hash: transaction.block_hash, block_number: transaction.block_number, index: 0, transaction_index: transaction.index, @@ -268,7 +258,6 @@ defmodule Explorer.Migrator.EmptyBytecodeForSelfdestructedSmartContractsTest do insert(:internal_transaction, transaction: transaction_1, - block_hash: transaction_1.block_hash, block_number: transaction_1.block_number, index: 0, transaction_index: transaction_1.index, @@ -283,7 +272,6 @@ defmodule Explorer.Migrator.EmptyBytecodeForSelfdestructedSmartContractsTest do insert(:internal_transaction, transaction: transaction_2, - block_hash: transaction_2.block_hash, block_number: transaction_2.block_number, index: 0, transaction_index: transaction_2.index, @@ -322,7 +310,6 @@ defmodule Explorer.Migrator.EmptyBytecodeForSelfdestructedSmartContractsTest do insert(:internal_transaction, transaction_hash: transaction_1.hash, - block_hash: transaction_1.block_hash, block_number: transaction_1.block_number, index: 0, transaction_index: transaction_1.index, @@ -337,7 +324,6 @@ defmodule Explorer.Migrator.EmptyBytecodeForSelfdestructedSmartContractsTest do insert(:internal_transaction_create, transaction_hash: transaction_2.hash, - block_hash: transaction_2.block_hash, block_number: transaction_2.block_number, index: 0, transaction_index: transaction_2.index, @@ -347,7 +333,6 @@ defmodule Explorer.Migrator.EmptyBytecodeForSelfdestructedSmartContractsTest do insert(:internal_transaction, transaction_hash: transaction_2.hash, - block_hash: transaction_2.block_hash, block_number: transaction_2.block_number, index: 1, transaction_index: transaction_2.index, @@ -380,7 +365,6 @@ defmodule Explorer.Migrator.EmptyBytecodeForSelfdestructedSmartContractsTest do insert(:internal_transaction, transaction_hash: transaction_1.hash, - block_hash: transaction_1.block_hash, block_number: transaction_1.block_number, index: 0, transaction_index: transaction_1.index, @@ -392,7 +376,6 @@ defmodule Explorer.Migrator.EmptyBytecodeForSelfdestructedSmartContractsTest do insert(:internal_transaction, transaction_hash: transaction_2.hash, - block_hash: transaction_2.block_hash, block_number: transaction_2.block_number, index: 0, transaction_index: transaction_2.index, diff --git a/apps/explorer/test/explorer/migrator/empty_internal_transactions_data_test.exs b/apps/explorer/test/explorer/migrator/empty_internal_transactions_data_test.exs deleted file mode 100644 index 4a27f7d5f6ab..000000000000 --- a/apps/explorer/test/explorer/migrator/empty_internal_transactions_data_test.exs +++ /dev/null @@ -1,70 +0,0 @@ -defmodule Explorer.Migrator.EmptyInternalTransactionsDataTest do - use Explorer.DataCase, async: false - - import Ecto.Query - - alias Explorer.Chain.Cache.BackgroundMigrations - alias Explorer.Chain.{InternalTransaction, Wei} - alias Explorer.Migrator.{EmptyInternalTransactionsData, MigrationStatus} - alias Explorer.Repo - - test "empties data" do - _trace_address_internal_transactions = insert_batch_of_internal_transactions(trace_address: [0]) - _value_internal_transactions = insert_batch_of_internal_transactions(value: 0) - _call_type_internal_transactions = insert_batch_of_internal_transactions(call_type: :call) - %{id: transaction_error_id} = insert(:transaction_error, message: "error") - %{id: fake_transaction_error_id} = insert(:transaction_error) - - _error_internal_transactions = - insert_batch_of_internal_transactions( - error: "error", - error_id: fake_transaction_error_id, - gas_used: nil, - output: nil - ) - - assert MigrationStatus.get_status("empty_internal_transactions_data") == nil - - EmptyInternalTransactionsData.start_link([]) - - wait_for_results(fn -> - Repo.one!( - from(ms in MigrationStatus, - where: ms.migration_name == ^"empty_internal_transactions_data" and ms.status == "completed" - ) - ) - end) - - all_internal_transactions = Repo.all(InternalTransaction) - - assert Enum.all?(all_internal_transactions, &is_nil(&1.trace_address)) - assert Enum.all?(all_internal_transactions, &(is_nil(&1.value) or Decimal.gt?(Wei.to(&1.value, :wei), 0))) - assert Enum.all?(all_internal_transactions, &is_nil(&1.call_type)) - refute Enum.all?(all_internal_transactions, &is_nil(&1.call_type_enum)) - assert Enum.all?(all_internal_transactions, &is_nil(&1.error)) - refute Enum.all?(all_internal_transactions, &is_nil(&1.error_id)) - assert Enum.all?(all_internal_transactions, &(is_nil(&1.error_id) or &1.error_id == transaction_error_id)) - - assert BackgroundMigrations.get_empty_internal_transactions_data_finished() == true - end - - defp insert_batch_of_internal_transactions(additional_fields) do - Enum.map(1..10, fn index -> - transaction = - :transaction - |> insert() - |> with_block() - - base_fields = [ - transaction: transaction, - index: index, - block_number: transaction.block_number, - transaction_index: transaction.index, - block_hash: transaction.block_hash, - block_index: index - ] - - insert(:internal_transaction, Keyword.merge(base_fields, additional_fields)) - end) - end -end diff --git a/apps/explorer/test/explorer/migrator/fill_internal_transactions_address_ids_test.exs b/apps/explorer/test/explorer/migrator/fill_internal_transactions_address_ids_test.exs new file mode 100644 index 000000000000..512fa996a78e --- /dev/null +++ b/apps/explorer/test/explorer/migrator/fill_internal_transactions_address_ids_test.exs @@ -0,0 +1,86 @@ +defmodule Explorer.Migrator.FillInternalTransactionsAddressIdsTest do + use Explorer.DataCase, async: false + + import Ecto.Query + + alias Explorer.Chain.InternalTransaction + alias Explorer.Migrator.{FillInternalTransactionsAddressIds, MigrationStatus} + alias Explorer.Migrator.HeavyDbIndexOperation.RemoveInternalTransactionsBlockHashTransactionHashBlockIndexError + alias Explorer.Repo + alias Explorer.Utility.AddressIdToAddressHash + + test "clears address hashes" do + Enum.map(1..10, fn index -> + transaction = + :transaction + |> insert() + |> with_block() + + insert(:internal_transaction, + transaction: transaction, + index: index, + block_number: transaction.block_number, + transaction_index: transaction.index + ) + end) + + Enum.map(1..10, fn index -> + transaction = + :transaction + |> insert() + |> with_block() + + insert(:internal_transaction_create, + transaction: transaction, + index: index, + block_number: transaction.block_number, + transaction_index: transaction.index + ) + end) + + MigrationStatus.set_status( + RemoveInternalTransactionsBlockHashTransactionHashBlockIndexError.migration_name(), + "completed" + ) + + assert MigrationStatus.get_status("fill_internal_transactions_address_ids") == nil + + Application.put_env(:explorer, FillInternalTransactionsAddressIds, batch_size: 100, timeout: 0) + + FillInternalTransactionsAddressIds.start_link([]) + + wait_for_results(fn -> + Repo.one!( + from(ms in MigrationStatus, + where: ms.migration_name == ^"fill_internal_transactions_address_ids" and ms.status == "completed" + ) + ) + end) + + all_internal_transactions = Repo.all(InternalTransaction) + + assert Enum.all?( + all_internal_transactions, + &(is_nil(&1.from_address_hash) and is_nil(&1.to_address_hash) and is_nil(&1.created_contract_address_hash)) + ) + + assert Enum.all?( + all_internal_transactions, + &(not is_nil(&1.from_address_id) or not is_nil(&1.to_address_id) or + not is_nil(&1.created_contract_address_id)) + ) + + all_address_ids = + all_internal_transactions + |> Enum.flat_map(&[&1.from_address_id, &1.to_address_id, &1.created_contract_address_id]) + |> Enum.uniq() + |> Enum.reject(&is_nil/1) + + existing_address_ids = + AddressIdToAddressHash + |> Repo.all() + |> Enum.map(& &1.address_id) + + assert Enum.all?(all_address_ids, &Enum.member?(existing_address_ids, &1)) + end +end diff --git a/apps/explorer/test/explorer/migrator/heavy_db_index_operation/create_logs_address_hash_block_number_desc_index_desc_index_test.exs b/apps/explorer/test/explorer/migrator/heavy_db_index_operation/create_logs_address_hash_block_number_desc_index_desc_index_test.exs index c7145eb33130..f83dbc634ba7 100644 --- a/apps/explorer/test/explorer/migrator/heavy_db_index_operation/create_logs_address_hash_block_number_desc_index_desc_index_test.exs +++ b/apps/explorer/test/explorer/migrator/heavy_db_index_operation/create_logs_address_hash_block_number_desc_index_desc_index_test.exs @@ -37,10 +37,6 @@ defmodule Explorer.Migrator.HeavyDbIndexOperation.CreateLogsAddressHashBlockNumb insert(:db_migration_status, migration_name: "heavy_indexes_create_logs_block_hash_index", status: "completed") - Process.sleep(150) - - assert MigrationStatus.get_status(migration_name) == "started" - Process.sleep(200) assert MigrationStatus.get_status(migration_name) == "completed" diff --git a/apps/explorer/test/explorer/migrator/heavy_db_index_operation/create_transactions_created_contract_address_hash_w_pending_index_test.exs b/apps/explorer/test/explorer/migrator/heavy_db_index_operation/create_transactions_created_contract_address_hash_w_pending_index_test.exs index df7e446ea96c..95b4af955989 100644 --- a/apps/explorer/test/explorer/migrator/heavy_db_index_operation/create_transactions_created_contract_address_hash_w_pending_index_test.exs +++ b/apps/explorer/test/explorer/migrator/heavy_db_index_operation/create_transactions_created_contract_address_hash_w_pending_index_test.exs @@ -37,9 +37,6 @@ defmodule Explorer.Migrator.HeavyDbIndexOperation.CreateTransactionsCreatedContr assert Helper.db_index_exists_and_valid?(index_name) == %{exists?: false, valid?: nil} CreateTransactionsCreatedContractAddressHashWPendingIndex.start_link([]) - Process.sleep(100) - - assert MigrationStatus.get_status(migration_name) == "started" Process.sleep(200) @@ -71,10 +68,6 @@ defmodule Explorer.Migrator.HeavyDbIndexOperation.CreateTransactionsCreatedContr insert(:db_migration_status, migration_name: dependent_migration_name, status: "completed") - Process.sleep(150) - - assert MigrationStatus.get_status(migration_name) == "started" - Process.sleep(200) assert MigrationStatus.get_status(migration_name) == "completed" diff --git a/apps/explorer/test/explorer/migrator/heavy_db_index_operation/drop_logs_block_number_asc_index_asc_index_test.exs b/apps/explorer/test/explorer/migrator/heavy_db_index_operation/drop_logs_block_number_asc_index_asc_index_test.exs index 7654c01900d9..8664a1c5c875 100644 --- a/apps/explorer/test/explorer/migrator/heavy_db_index_operation/drop_logs_block_number_asc_index_asc_index_test.exs +++ b/apps/explorer/test/explorer/migrator/heavy_db_index_operation/drop_logs_block_number_asc_index_asc_index_test.exs @@ -26,9 +26,6 @@ defmodule Explorer.Migrator.DropLogsBlockNumberAscIndexAscIndexTest do assert Helper.db_index_exists_and_valid?(index_name) == %{exists?: true, valid?: true} DropLogsBlockNumberAscIndexAscIndex.start_link([]) - Process.sleep(100) - - assert MigrationStatus.get_status(migration_name) == "started" Process.sleep(200) diff --git a/apps/explorer/test/explorer/migrator/heavy_db_index_operation/drop_transactions_created_contract_address_hash_with_pending_index_a_test.exs b/apps/explorer/test/explorer/migrator/heavy_db_index_operation/drop_transactions_created_contract_address_hash_with_pending_index_a_test.exs index 836c72d8b600..6819e39bc10c 100644 --- a/apps/explorer/test/explorer/migrator/heavy_db_index_operation/drop_transactions_created_contract_address_hash_with_pending_index_a_test.exs +++ b/apps/explorer/test/explorer/migrator/heavy_db_index_operation/drop_transactions_created_contract_address_hash_with_pending_index_a_test.exs @@ -36,10 +36,6 @@ defmodule Explorer.Migrator.HeavyDbIndexOperation.DropTransactionsCreatedContrac # Mark create migration as completed insert(:db_migration_status, migration_name: create_migration_name, status: "completed") - Process.sleep(150) - - assert MigrationStatus.get_status(drop_migration_name) == "started" - Process.sleep(200) assert MigrationStatus.get_status(drop_migration_name) == "completed" diff --git a/apps/explorer/test/explorer/migrator/sanitize_duplicated_log_index_logs_test.exs b/apps/explorer/test/explorer/migrator/sanitize_duplicated_log_index_logs_test.exs index 475a7808bbf1..c17b092e6be4 100644 --- a/apps/explorer/test/explorer/migrator/sanitize_duplicated_log_index_logs_test.exs +++ b/apps/explorer/test/explorer/migrator/sanitize_duplicated_log_index_logs_test.exs @@ -7,7 +7,7 @@ defmodule Explorer.Migrator.SanitizeDuplicatedLogIndexLogsTest do alias Explorer.Chain.Token.Instance alias Explorer.Migrator.{SanitizeDuplicatedLogIndexLogs, MigrationStatus} - if Application.compile_env(:explorer, :chain_type) in [:polygon_zkevm, :rsk, :filecoin] do + if Application.compile_env(:explorer, :chain_type) in [:rsk, :filecoin] do describe "Sanitize duplicated log index logs" do setup do configuration = Application.get_env(:explorer, SanitizeDuplicatedLogIndexLogs) diff --git a/apps/explorer/test/explorer/module_queue_registry_test.exs b/apps/explorer/test/explorer/module_queue_registry_test.exs new file mode 100644 index 000000000000..ff138e85ba5f --- /dev/null +++ b/apps/explorer/test/explorer/module_queue_registry_test.exs @@ -0,0 +1,63 @@ +defmodule Explorer.ModuleQueueRegistryTest do + use ExUnit.Case, async: false + + alias Explorer.BoundQueue + alias Explorer.ModuleQueueRegistry + + defmodule Registry do + use ModuleQueueRegistry + + @impl true + def table_name(module) do + :"#{module}_module_queue_registry_test" + end + end + + defmodule ModuleA do + end + + defmodule ModuleB do + end + + setup do + for table_name <- [Registry.table_name(ModuleA), Registry.table_name(ModuleB)] do + if :ets.whereis(table_name) != :undefined do + :ets.delete(table_name) + end + end + + :ok + end + + test "pop/1 returns nil for an empty queue" do + assert Registry.pop(ModuleA) == nil + end + + test "push/2 returns true and pop/1 returns values in FIFO order" do + assert Registry.push(1, ModuleA) == true + assert Registry.push(2, ModuleA) == true + + assert Registry.pop(ModuleA) == 1 + assert Registry.pop(ModuleA) == 2 + assert Registry.pop(ModuleA) == nil + end + + test "queues are isolated by module table name" do + assert Registry.push(:a, ModuleA) == true + assert Registry.push(:b, ModuleB) == true + + assert Registry.pop(ModuleA) == :a + assert Registry.pop(ModuleB) == :b + end + + test "push/2 returns {:error, :maximum_size} when queue is full" do + table_name = Registry.table_name(ModuleA) + + :ets.new(table_name, [:set, :named_table, :public]) + :ets.insert(table_name, {:queue, %BoundQueue{queue: :queue.from_list([:existing]), size: 1, maximum_size: 1}}) + + assert Registry.push(:overflow, ModuleA) == {:error, :maximum_size} + assert Registry.pop(ModuleA) == :existing + assert Registry.pop(ModuleA) == nil + end +end diff --git a/apps/explorer/test/explorer/promo/autoscout_test.exs b/apps/explorer/test/explorer/promo/autoscout_test.exs new file mode 100644 index 000000000000..53c6aca5613b --- /dev/null +++ b/apps/explorer/test/explorer/promo/autoscout_test.exs @@ -0,0 +1,42 @@ +defmodule Explorer.Promo.AutoscoutTest do + use ExUnit.Case, async: false + + import ExUnit.CaptureLog, only: [capture_log: 1] + + alias Explorer.Promo.Autoscout + + @promo_line "Deploy Blockscout explorer in 5 minutes at deploy.blockscout.com" + + @moduletag :capture_log + + describe "init/1" do + test "returns the initial state" do + parent = self() + + pid = + spawn(fn -> + send(parent, {:init_result, Autoscout.init(nil)}) + + receive do + :stop -> :ok + end + end) + + assert_receive {:init_result, {:ok, %{}}} + send(pid, :stop) + end + end + + describe "handle_info/2" do + test "logs promo and keeps state unchanged" do + state = %{example: :state} + + log = + capture_log(fn -> + assert {:noreply, ^state} = Autoscout.handle_info(:promo, state) + end) + + assert log =~ @promo_line + end + end +end diff --git a/apps/explorer/test/explorer/repo_test.exs b/apps/explorer/test/explorer/repo_test.exs index 50c68953dfdd..bff654d4a734 100644 --- a/apps/explorer/test/explorer/repo_test.exs +++ b/apps/explorer/test/explorer/repo_test.exs @@ -17,10 +17,8 @@ defmodule Explorer.RepoTest do :internal_transaction, from_address_hash: insert(:address).hash, to_address_hash: insert(:address).hash, - transaction_hash: transaction.hash, index: 0, block_number: 35, - block_hash: transaction.block_hash, transaction_index: 0 ) diff --git a/apps/explorer/test/explorer/smart_contract/solidity/verifier_test.exs b/apps/explorer/test/explorer/smart_contract/solidity/verifier_test.exs index aaedf5aaf441..5a8a78fcdd96 100644 --- a/apps/explorer/test/explorer/smart_contract/solidity/verifier_test.exs +++ b/apps/explorer/test/explorer/smart_contract/solidity/verifier_test.exs @@ -507,9 +507,9 @@ defmodule Explorer.SmartContract.Solidity.VerifierTest do |> insert() |> with_block(transaction_success_details) - :internal_transaction + :internal_transaction_create |> insert( - created_contract_address_hash: contract_address.hash, + created_contract_address: contract_address, init: init, type: "create", created_contract_code: bytecode, @@ -517,7 +517,6 @@ defmodule Explorer.SmartContract.Solidity.VerifierTest do transaction_hash: transaction.hash, transaction_index: transaction.index, index: 0, - block_hash: transaction.block_hash, block_number: transaction.block_number ) @@ -566,9 +565,9 @@ defmodule Explorer.SmartContract.Solidity.VerifierTest do |> insert() |> with_block(transaction_failure_details) - :internal_transaction + :internal_transaction_create |> insert( - created_contract_address_hash: contract_address.hash, + created_contract_address: contract_address, init: init, type: "create", created_contract_code: bytecode, @@ -576,13 +575,12 @@ defmodule Explorer.SmartContract.Solidity.VerifierTest do transaction_hash: transaction_success.hash, transaction_index: transaction_success.index, index: 0, - block_hash: transaction_success.block_hash, block_number: transaction_success.block_number ) - :internal_transaction + :internal_transaction_create |> insert( - created_contract_address_hash: contract_address.hash, + created_contract_address: contract_address, init: init, type: "create", created_contract_code: bytecode, @@ -590,7 +588,6 @@ defmodule Explorer.SmartContract.Solidity.VerifierTest do transaction_hash: transaction_failure.hash, transaction_index: transaction_failure.index, index: 0, - block_hash: transaction_failure.block_hash, block_number: transaction_failure.block_number ) diff --git a/apps/explorer/test/support/factory.ex b/apps/explorer/test/support/factory.ex index e2586b3e9c76..d3934ce7d4fe 100644 --- a/apps/explorer/test/support/factory.ex +++ b/apps/explorer/test/support/factory.ex @@ -67,6 +67,7 @@ defmodule Explorer.Factory do alias Explorer.Chain.Optimism.Deposit, as: OptimismDeposit + alias Explorer.Chain.Celo.AggregatedElectionReward, as: CeloAggregatedElectionReward alias Explorer.Chain.Celo.ElectionReward, as: CeloElectionReward alias Explorer.Chain.Celo.Epoch, as: CeloEpoch @@ -336,7 +337,7 @@ defmodule Explorer.Factory do def address_id_to_address_hash_factory do %AddressIdToAddressHash{ address_id: sequence("address_id", & &1), - address_hash: address_hash() + address: build(:address) } end @@ -781,18 +782,6 @@ defmodule Explorer.Factory do |> Repo.update!() end - def with_contract_creation(%InternalTransaction{} = internal_transaction, %Address{ - contract_code: contract_code, - hash: contract_address_hash - }) do - internal_transaction - |> InternalTransaction.changeset(%{ - contract_code: contract_code, - created_contract_address_hash: contract_address_hash - }) - |> Repo.update!() - end - def data(sequence_name) do unpadded = sequence_name @@ -846,13 +835,16 @@ defmodule Explorer.Factory do } end - def internal_transaction_factory() do + def internal_transaction_factory(attrs) do gas = Enum.random(21_000..100_000) gas_used = Enum.random(0..gas) + all_attrs = + attrs + |> adjust_internal_transaction_addresses_attrs([:from_address, :to_address]) + |> adjust_internal_transaction_error_attr() + %InternalTransaction{ - from_address: build(:address), - to_address: build(:address), call_type: :delegatecall, gas: gas, gas_used: gas_used, @@ -866,18 +858,23 @@ defmodule Explorer.Factory do type: :call, value: sequence("internal_transaction_value", &Decimal.new(&1)) } + |> merge_attributes(all_attrs) + |> evaluate_lazy_attributes() end - def internal_transaction_create_factory() do + def internal_transaction_create_factory(attrs) do gas = Enum.random(21_000..100_000) gas_used = Enum.random(0..gas) contract_code = Map.fetch!(contract_code_info(), :bytecode) + all_attrs = + attrs + |> adjust_internal_transaction_addresses_attrs([:from_address, :created_contract_address], contract_code) + |> adjust_internal_transaction_error_attr() + %InternalTransaction{ created_contract_code: contract_code, - created_contract_address: build(:address, contract_code: contract_code), - from_address: build(:address), gas: gas, gas_used: gas_used, # caller MUST supply `index` @@ -889,9 +886,16 @@ defmodule Explorer.Factory do type: :create, value: sequence("internal_transaction_value", &Decimal.new(&1)) } + |> merge_attributes(all_attrs) + |> evaluate_lazy_attributes() end - def internal_transaction_selfdestruct_factory() do + def internal_transaction_selfdestruct_factory(attrs) do + all_attrs = + attrs + |> adjust_internal_transaction_addresses_attrs([:from_address, :to_address]) + |> adjust_internal_transaction_error_attr() + %InternalTransaction{ from_address: build(:address), trace_address: nil, @@ -900,6 +904,8 @@ defmodule Explorer.Factory do type: :selfdestruct, value: sequence("internal_transaction_value", &Decimal.new(&1)) } + |> merge_attributes(all_attrs) + |> evaluate_lazy_attributes() end def transaction_error_factory do @@ -1379,6 +1385,59 @@ defmodule Explorer.Factory do } end + defp adjust_internal_transaction_addresses_attrs(attrs, addresses_fields, contract_code \\ nil) do + hash_fields = Enum.map(addresses_fields, &String.to_existing_atom("#{&1}_hash")) + {address_related_attrs, other_attrs} = Map.split(attrs, addresses_fields ++ hash_fields) + + addresses_fields + |> Enum.reduce(address_related_attrs, fn address_field, acc -> + hash_field = String.to_existing_atom("#{address_field}_hash") + + additional_fields = + case address_field do + :created_contract_address -> %{contract_code: contract_code} + _ -> %{} + end + + address = + case Map.has_key?(acc, address_field) && Map.get(acc, address_field) do + nil -> + nil + + false -> + address_params = + Map.merge(additional_fields, if(hash = Map.get(acc, hash_field), do: %{hash: hash}, else: %{})) + + case Map.get(address_params, :hash) && Repo.get_by(Address, hash: address_params.hash) do + nil -> + built_address = build(:address, address_params) + insert(built_address) + built_address + + _ -> + build(:address, address_params) + end + + address -> + address + end + + Map.merge(acc, %{ + address_field => address, + hash_field => address && address.hash, + :"#{address_field}_mapping" => address && AddressIdToAddressHash.find_or_create(address.hash) + }) + end) + |> Map.merge(other_attrs) + end + + defp adjust_internal_transaction_error_attr(attrs) do + case Map.get(attrs, :error) do + nil -> attrs + error -> Map.put(attrs, :error_id, Map.get(attrs, :error_id) || TransactionError.find_or_create(error)) + end + end + defp block_hash_to_next_transaction_index(block_hash) do import Kernel, except: [+: 2] @@ -1612,10 +1671,19 @@ defmodule Explorer.Factory do } end + def celo_aggregated_election_reward_factory do + %CeloAggregatedElectionReward{ + epoch_number: sequence("celo_aggregated_election_reward_epoch_number", & &1), + type: Enum.random(CeloElectionReward.types()), + sum: Enum.random(1..100_000), + count: Enum.random(0..100) + } + end + def celo_election_reward_factory do %CeloElectionReward{ amount: Enum.random(1..100_000), - type: Enum.random([:voter, :validator, :group, :delegated_payment]), + type: Enum.random(CeloElectionReward.types()), epoch_number: sequence("celo_election_reward_epoch_number", & &1), account_address_hash: insert(:address).hash, associated_account_address_hash: insert(:address).hash diff --git a/apps/explorer/test/test_helper.exs b/apps/explorer/test/test_helper.exs index 55deb8e9949c..b20646b462ff 100644 --- a/apps/explorer/test/test_helper.exs +++ b/apps/explorer/test/test_helper.exs @@ -16,7 +16,6 @@ Explorer.TestHelper.run_necessary_background_migrations() Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo, :auto) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Account, :auto) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.PolygonEdge, :auto) -Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.PolygonZkevm, :auto) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.RSK, :auto) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Shibarium, :auto) Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo.Suave, :auto) diff --git a/apps/indexer/README.md b/apps/indexer/README.md index 262019b28d22..8842996a0ee8 100644 --- a/apps/indexer/README.md +++ b/apps/indexer/README.md @@ -21,7 +21,6 @@ Some data has to be extracted from already fetched data, and there're several tr - `address_coin_balances`: detects coin balance-changing entities (transactions, minted blocks, etc) to create coin balance entities for further fetching - `token_transfers`: parses logs to extract token transfers - `mint_transfers`: parses logs to extract token mint transfers -- `transaction_actions`: parses logs to extract transaction actions - `address_token_balances`: creates token balance entities for further fetching, based on detected token transfers - `blocks`: extracts block signer hash from additional data for Clique chains - `optimism_withdrawals`: parses logs to extract L2 withdrawal messages @@ -45,7 +44,6 @@ Both block fetchers retrieve/extract the blocks themselves and the following add - `transactions` - `logs` - `token_transfers` -- `transaction_actions` - `addresses` - `withdrawals` diff --git a/apps/indexer/lib/indexer/block/fetcher.ex b/apps/indexer/lib/indexer/block/fetcher.ex index 4e3e01eff1df..f330de90f5f7 100644 --- a/apps/indexer/lib/indexer/block/fetcher.ex +++ b/apps/indexer/lib/indexer/block/fetcher.ex @@ -31,7 +31,6 @@ defmodule Indexer.Block.Fetcher do alias Indexer.Fetcher.CoinBalance.Catchup, as: CoinBalanceCatchup alias Indexer.Fetcher.CoinBalance.Realtime, as: CoinBalanceRealtime alias Indexer.Fetcher.Filecoin.AddressInfo, as: FilecoinAddressInfo - alias Indexer.Fetcher.PolygonZkevm.BridgeL1Tokens, as: PolygonZkevmBridgeL1Tokens alias Indexer.Fetcher.TokenBalance.Current, as: TokenBalanceCurrent alias Indexer.Fetcher.TokenBalance.Historical, as: TokenBalanceHistorical alias Indexer.Fetcher.TokenInstance.Realtime, as: TokenInstanceRealtime @@ -59,8 +58,7 @@ defmodule Indexer.Block.Fetcher do MintTransfers, SignedAuthorizations, TokenInstances, - TokenTransfers, - TransactionActions + TokenTransfers } alias Indexer.Transform.Stability.Validators, as: StabilityValidators @@ -73,7 +71,6 @@ defmodule Indexer.Block.Fetcher do alias Indexer.Transform.Shibarium.Bridge, as: ShibariumBridge alias Indexer.Transform.Blocks, as: TransformBlocks - alias Indexer.Transform.PolygonZkevm.Bridge, as: PolygonZkevmBridge alias Indexer.Transform.Celo.L1Epochs, as: CeloL1Epochs alias Indexer.Transform.Celo.L2Epochs, as: CeloL2Epochs @@ -195,7 +192,6 @@ defmodule Indexer.Block.Fetcher do celo_l2_epochs = CeloL2Epochs.parse(logs), celo_pending_account_operations = parse_celo_pending_account_operations(logs), tokens = Enum.uniq(tokens ++ celo_tokens), - %{transaction_actions: transaction_actions} = TransactionActions.parse(logs), %{fhe_operations: fhe_operations} = FheOperations.parse(logs), %{mint_transfers: mint_transfers} = MintTransfers.parse(logs), optimism_withdrawals = @@ -210,11 +206,6 @@ defmodule Indexer.Block.Fetcher do do: ShibariumBridge.parse(blocks, transactions_with_receipts, logs), else: [] ), - polygon_zkevm_bridge_operations = - if(callback_module == Indexer.Block.Realtime.Fetcher, - do: PolygonZkevmBridge.parse(blocks, logs), - else: [] - ), {arbitrum_xlevel_messages, arbitrum_transactions_for_further_handling} = ArbitrumMessaging.parse(transactions_with_receipts, logs), %FetchedBeneficiaries{params_set: beneficiary_params_set, errors: beneficiaries_errors} = @@ -228,9 +219,7 @@ defmodule Indexer.Block.Fetcher do shibarium_bridge_operations: shibarium_bridge_operations, token_transfers: token_transfers, transactions: transactions_with_receipts, - transaction_actions: transaction_actions, withdrawals: withdrawals_params, - polygon_zkevm_bridge_operations: polygon_zkevm_bridge_operations, celo_pending_account_operations: celo_pending_account_operations }), coin_balances_params_set = @@ -247,8 +236,6 @@ defmodule Indexer.Block.Fetcher do token_transfers_with_token = token_transfers_merge_token(token_transfers, tokens), address_token_balances = AddressTokenBalances.params_set(%{token_transfers_params: token_transfers_with_token}), - transaction_actions = - Enum.map(transaction_actions, fn action -> Map.put(action, :data, Map.delete(action.data, :block_number)) end), token_instances = TokenInstances.params_set(%{token_transfers_params: token_transfers}), stability_validators = StabilityValidators.parse(blocks), addresses_without_nonce = process_addresses_nonce(addresses), @@ -275,7 +262,6 @@ defmodule Indexer.Block.Fetcher do %{ transactions_with_receipts: transactions_with_receipts, optimism_withdrawals: optimism_withdrawals, - polygon_zkevm_bridge_operations: polygon_zkevm_bridge_operations, scroll_l1_fee_params: scroll_l1_fee_params, shibarium_bridge_operations: shibarium_bridge_operations, celo_gas_tokens: celo_gas_tokens, @@ -289,14 +275,7 @@ defmodule Indexer.Block.Fetcher do __MODULE__.import( state, basic_import_options |> Map.merge(additional_options) |> import_options(chain_type_import_options) - ), - {:transaction_actions, {:ok, inserted_transaction_actions}} <- - {:transaction_actions, - Chain.import(%{ - transaction_actions: %{params: transaction_actions}, - timeout: :infinity - })} do - inserted = Map.merge(inserted, inserted_transaction_actions) + ) do Prometheus.Instrumenter.set_block_batch_fetch(fetch_time, callback_module) result = {:ok, %{inserted: inserted, errors: blocks_errors}} @@ -382,13 +361,6 @@ defmodule Indexer.Block.Fetcher do ) end - defp do_import_options(:polygon_zkevm, basic_import_options, %{ - polygon_zkevm_bridge_operations: polygon_zkevm_bridge_operations - }) do - basic_import_options - |> Map.put_new(:polygon_zkevm_bridge_operations, %{params: polygon_zkevm_bridge_operations}) - end - defp do_import_options(:scroll, basic_import_options, %{scroll_l1_fee_params: scroll_l1_fee_params}) do basic_import_options |> Map.put_new(:scroll_l1_fee_params, %{params: scroll_l1_fee_params}) @@ -709,18 +681,6 @@ defmodule Indexer.Block.Fetcher do def async_import_replaced_transactions(_, _), do: :ok - @doc """ - Fills a buffer of L1 token addresses to handle it asynchronously in - the Indexer.Fetcher.PolygonZkevm.BridgeL1Tokens module. The addresses are - taken from the `operations` list. - """ - @spec async_import_polygon_zkevm_bridge_l1_tokens(map()) :: :ok - def async_import_polygon_zkevm_bridge_l1_tokens(%{polygon_zkevm_bridge_operations: operations}) do - PolygonZkevmBridgeL1Tokens.async_fetch(operations) - end - - def async_import_polygon_zkevm_bridge_l1_tokens(_), do: :ok - def async_import_celo_epoch_block_operations(%{celo_epochs: epochs}, realtime?) do CeloEpochBlockOperations.async_fetch(epochs, realtime?) end diff --git a/apps/indexer/lib/indexer/block/realtime/fetcher.ex b/apps/indexer/lib/indexer/block/realtime/fetcher.ex index 988843c0145a..24109152246f 100644 --- a/apps/indexer/lib/indexer/block/realtime/fetcher.ex +++ b/apps/indexer/lib/indexer/block/realtime/fetcher.ex @@ -20,7 +20,6 @@ defmodule Indexer.Block.Realtime.Fetcher do async_import_created_contract_codes: 2, async_import_filecoin_addresses_info: 2, async_import_internal_transactions: 2, - async_import_polygon_zkevm_bridge_l1_tokens: 1, async_import_realtime_coin_balances: 1, async_import_replaced_transactions: 2, async_import_signed_authorizations_statuses: 2, @@ -394,13 +393,6 @@ defmodule Indexer.Block.Realtime.Fetcher do Indexer.Fetcher.Optimism.Withdrawal.remove(reorg_block_number) end - # Removes all rows from `polygon_zkevm_bridge` table - # previously written starting from the reorg block number - defp do_remove_assets_by_number(:polygon_zkevm, reorg_block) do - # credo:disable-for-next-line Credo.Check.Design.AliasUsage - Indexer.Fetcher.PolygonZkevm.BridgeL2.reorg_handle(reorg_block) - end - # Removes all rows from `shibarium_bridge` table # previously written starting from the reorg block number defp do_remove_assets_by_number(:shibarium, reorg_block) do @@ -546,7 +538,6 @@ defmodule Indexer.Block.Realtime.Fetcher do async_import_uncles(imported, realtime?) async_import_replaced_transactions(imported, realtime?) async_import_blobs(imported, realtime?) - async_import_polygon_zkevm_bridge_l1_tokens(imported) async_import_celo_epoch_block_operations(imported, realtime?) async_import_celo_accounts(imported, realtime?) async_import_filecoin_addresses_info(imported, realtime?) diff --git a/apps/indexer/lib/indexer/fetcher/contract_code.ex b/apps/indexer/lib/indexer/fetcher/contract_code.ex index a673fd54b023..9c7e0853fc04 100644 --- a/apps/indexer/lib/indexer/fetcher/contract_code.ex +++ b/apps/indexer/lib/indexer/fetcher/contract_code.ex @@ -195,6 +195,23 @@ defmodule Indexer.Fetcher.ContractCode do code_addresses_params = Addresses.extract_addresses(%{codes: params}) {:ok, code_addresses_params} + {:ok, %{params_list: params, errors: errors}} -> + unique_errors = + errors + |> Enum.map(fn + %{message: message} -> + message + + error -> + inspect(error) + end) + |> Enum.uniq() + + Logger.error(fn -> ["failed to fetch some contract codes: ", inspect(unique_errors)] end) + + code_addresses_params = Addresses.extract_addresses(%{codes: params}) + {:ok, code_addresses_params} + error -> error end diff --git a/apps/indexer/lib/indexer/fetcher/internal_transaction.ex b/apps/indexer/lib/indexer/fetcher/internal_transaction.ex index b60cbcf9f056..0cffa66cb8e2 100644 --- a/apps/indexer/lib/indexer/fetcher/internal_transaction.ex +++ b/apps/indexer/lib/indexer/fetcher/internal_transaction.ex @@ -277,7 +277,10 @@ defmodule Indexer.Fetcher.InternalTransaction do import_internal_transaction(internal_transactions_params, block_numbers, data_type) rescue exception in Postgrex.Error -> - handle_foreign_key_violation(exception, internal_transactions_params, block_numbers, data_type) + Logger.error( + "Error on internal transactions import: #{inspect(exception)}, block numbers: #{inspect(block_numbers)}" + ) + {:retry, block_numbers} end @@ -364,8 +367,6 @@ defmodule Indexer.Fetcher.InternalTransaction do error_count: Enum.count(transactions_params_or_unique_numbers) ) - handle_unique_key_violation(reason, transactions_params_or_unique_numbers, data_type) - # re-queue the de-duped entries {:retry, transactions_params_or_unique_numbers} end @@ -407,43 +408,6 @@ defmodule Indexer.Fetcher.InternalTransaction do # don't count itself as a parent defp has_failed_parent?(_failed_parent_paths, [], _reverse_path_acc), do: false - defp handle_unique_key_violation( - %{exception: %{postgres: %{code: :unique_violation}}}, - transactions_params_or_unique_numbers, - data_type - ) do - block_numbers = data_to_block_numbers(transactions_params_or_unique_numbers, data_type) - - Block.set_refetch_needed(block_numbers) - - Logger.error(fn -> - [ - "unique_violation on internal transactions import, #{data_type} identifiers: ", - inspect(transactions_params_or_unique_numbers) - ] - end) - end - - defp handle_unique_key_violation(_reason, _identifiers, _data_type), do: :ok - - defp handle_foreign_key_violation(reason, internal_transactions_params, block_numbers_or_transactions, data_type) do - block_numbers = data_to_block_numbers(block_numbers_or_transactions, data_type) - - Block.set_refetch_needed(block_numbers) - - transaction_hashes = - internal_transactions_params - |> Enum.map(&to_string(&1.transaction_hash)) - |> Enum.uniq() - - Logger.error(fn -> - [ - "foreign_key_violation on internal transactions import: #{inspect(reason)}, foreign transactions hashes: ", - Enum.join(transaction_hashes, ", ") - ] - end) - end - defp handle_not_found_transaction(errors) when is_list(errors) do Enum.each(errors, &handle_not_found_transaction/1) end diff --git a/apps/indexer/lib/indexer/fetcher/on_demand/contract_creator.ex b/apps/indexer/lib/indexer/fetcher/on_demand/contract_creator.ex index 5960a0b4a789..1f139133d2e7 100644 --- a/apps/indexer/lib/indexer/fetcher/on_demand/contract_creator.ex +++ b/apps/indexer/lib/indexer/fetcher/on_demand/contract_creator.ex @@ -22,6 +22,7 @@ defmodule Indexer.Fetcher.OnDemand.ContractCreator do @table_name :contract_creator_lookup @pending_blocks_cache_key "pending_blocks" + @max_json_rpc_retries 5 def start_link(_) do :ets.new(@table_name, [ @@ -44,6 +45,7 @@ defmodule Indexer.Fetcher.OnDemand.ContractCreator do with false <- is_nil(address.contract_code), true <- is_nil(creator_hash), false <- Address.eoa_with_code?(address), + true <- table_exists?(), {:address_lookup, [{_, contract_creation_block_number}]} <- {:address_lookup, :ets.lookup(@table_name, address_cache_name(address.hash))}, {:pending_blocks_lookup, [{@pending_blocks_cache_key, blocks}]} <- @@ -62,7 +64,7 @@ defmodule Indexer.Fetcher.OnDemand.ContractCreator do end end - @spec fetch_contract_creator_address_hash(Explorer.Chain.Hash.Address.t()) :: :ok + @spec fetch_contract_creator_address_hash(Explorer.Chain.Hash.Address.t()) :: non_neg_integer() | :error defp fetch_contract_creator_address_hash(address_hash) do max_block_number = BlockNumber.get_max() @@ -72,6 +74,7 @@ defmodule Indexer.Fetcher.OnDemand.ContractCreator do previous_nonce: nil } +<<<<<<< HEAD contract_creation_block_number = find_contract_creation_block_number(initial_block_ranges, address_hash) pending_blocks = @@ -103,10 +106,12 @@ defmodule Indexer.Fetcher.OnDemand.ContractCreator do end :ok +======= + find_contract_creation_block_number(initial_block_ranges, address_hash, @max_json_rpc_retries) +>>>>>>> v11.0.0 end - defp find_contract_creation_block_number(block_ranges, address_hash) do - :ets.insert(@table_name, {address_cache_name(address_hash), :in_progress}) + defp find_contract_creation_block_number(block_ranges, address_hash, retries_left) do json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments) medium = trunc((block_ranges.right - block_ranges.left) / 2) medium_position = block_ranges.left + medium @@ -115,32 +120,49 @@ defmodule Indexer.Fetcher.OnDemand.ContractCreator do id_to_params = id_to_params([params]) - with {:ok, response} <- - params - |> Map.merge(%{id: 0}) - |> Nonce.request() - |> json_rpc(json_rpc_named_arguments) do - case Nonce.from_response(%{id: 0, result: response}, id_to_params) do - {:ok, %{nonce: 0}} -> - left_new = new_left_position(medium, medium_position) - block_ranges = Map.put(block_ranges, :left, left_new) + case params + |> Map.merge(%{id: 0}) + |> Nonce.request() + |> json_rpc(json_rpc_named_arguments) do + {:ok, response} -> + case Nonce.from_response(%{id: 0, result: response}, id_to_params) do + {:ok, %{nonce: 0}} -> + left_new = new_left_position(medium, medium_position) + block_ranges = Map.put(block_ranges, :left, left_new) - maybe_continue_binary_search(block_ranges, address_hash, 0) + maybe_continue_binary_search(block_ranges, address_hash, 0, retries_left) - {:ok, %{nonce: nonce}} when nonce > 0 -> - right_new = new_right_position(medium, medium_position) - block_ranges = Map.put(block_ranges, :right, right_new) + {:ok, %{nonce: nonce}} when nonce > 0 -> + right_new = new_right_position(medium, medium_position) + block_ranges = Map.put(block_ranges, :right, right_new) - maybe_continue_binary_search(block_ranges, address_hash, nonce) + maybe_continue_binary_search(block_ranges, address_hash, nonce, retries_left) - _ -> - Logger.error("Error while fetching 'eth_getTransactionCount' for address #{to_string(address_hash)}") - :timer.sleep(1000) - find_contract_creation_block_number(block_ranges, address_hash) - end + _ -> + Logger.error("Error while fetching 'eth_getTransactionCount' for address #{to_string(address_hash)}") + retry_find_contract_creation_block_number(block_ranges, address_hash, retries_left) + end + + {:error, reason} -> + Logger.error( + "Error while fetching 'eth_getTransactionCount' for address #{to_string(address_hash)}: #{inspect(reason)}" + ) + + retry_find_contract_creation_block_number(block_ranges, address_hash, retries_left) end end + defp retry_find_contract_creation_block_number(_block_ranges, address_hash, 0) do + Logger.error("Reached max retry attempts for 'eth_getTransactionCount' for address #{to_string(address_hash)}") + + :error + end + + defp retry_find_contract_creation_block_number(block_ranges, address_hash, retries_left) do + :timer.sleep(1000) + find_contract_creation_block_number(block_ranges, address_hash, retries_left - 1) + end + defp new_left_position(medium, medium_position) do if medium == 0, do: medium_position + 1, else: medium_position end @@ -149,7 +171,7 @@ defmodule Indexer.Fetcher.OnDemand.ContractCreator do if medium == 0, do: medium_position - 1, else: medium_position end - defp maybe_continue_binary_search(block_ranges, address_hash, nonce) do + defp maybe_continue_binary_search(block_ranges, address_hash, nonce, retries_left) do cond do block_ranges.left == block_ranges.right -> block_ranges.left @@ -159,7 +181,7 @@ defmodule Indexer.Fetcher.OnDemand.ContractCreator do true -> block_ranges = Map.put(block_ranges, :previous_nonce, nonce) - find_contract_creation_block_number(block_ranges, address_hash) + find_contract_creation_block_number(block_ranges, address_hash, retries_left) end end @@ -170,7 +192,66 @@ defmodule Indexer.Fetcher.OnDemand.ContractCreator do @impl true def handle_cast({:fetch, address}, state) do - fetch_contract_creator_address_hash(address.hash) + address_hash = address.hash + :ets.insert(@table_name, {address_cache_name(address_hash), :in_progress}) + + Task.Supervisor.start_child(Indexer.TaskSupervisor, fn -> + result = fetch_contract_creator_address_hash(address_hash) + GenServer.cast(__MODULE__, {:fetch_result, address_hash, result}) + end) + + {:noreply, state} + end + + @impl true + def handle_cast({:fetch_result, address_hash, contract_creation_block_number}, state) + when is_integer(contract_creation_block_number) do + address_hash_string = to_string(address_hash) + + pending_blocks = + case pending_blocks_cache() do + [] -> + [] + + [{_, pending_blocks}] -> + pending_blocks + end + + updated_pending_blocks = [ + %{block_number: contract_creation_block_number, address_hash_string: address_hash_string} + | Enum.reject(pending_blocks, fn %{address_hash_string: addr} -> addr == address_hash_string end) + ] + + :ets.insert(@table_name, {address_cache_name(address_hash), contract_creation_block_number}) + :ets.insert(@table_name, {@pending_blocks_cache_key, updated_pending_blocks}) + + unless Block.indexed?(contract_creation_block_number) do + # Change `1` to specific label when `priority` field becomes `Ecto.Enum`. + MissingBlockRange.add_ranges_by_block_numbers([contract_creation_block_number], 1) + end + + {:noreply, state} + end + + @impl true + def handle_cast({:fetch_result, address_hash, unexpected_value}, state) do + Logger.error( + "Unexpected contract creation block number for address #{to_string(address_hash)}: #{inspect(unexpected_value)}" + ) + + :ets.delete(@table_name, address_cache_name(address_hash)) + + {:noreply, state} + end + + @impl true + def handle_cast({:update_pending_contract_creator_cache, imported}, state) do + imported_block_numbers = + imported + |> Map.get(:blocks, []) + |> Enum.map(&Map.get(&1, :number)) + + maybe_update_pending_contract_creator_cache(imported, imported_block_numbers) {:noreply, state} end @@ -189,32 +270,38 @@ defmodule Indexer.Fetcher.OnDemand.ContractCreator do # - `[{String.t(), [map()]}]`: A list of tuples containing block identifiers and their associated data. @spec pending_blocks_cache() :: [{String.t(), [map()]}] - defp pending_blocks_cache, do: :ets.lookup(@table_name, @pending_blocks_cache_key) + defp pending_blocks_cache do + if table_exists?() do + :ets.lookup(@table_name, @pending_blocks_cache_key) + else + [] + end + end + + defp table_exists? do + :ets.whereis(@table_name) != :undefined + end @doc """ Asynchronously updates value of ETS cache :contract_creator_lookup for key "pending_blocks": removes block from the cache since the block has been imported. """ - @spec async_update_cache_of_contract_creator_on_demand(map()) :: Task.t() + @spec async_update_cache_of_contract_creator_on_demand(map()) :: :ok def async_update_cache_of_contract_creator_on_demand(imported) do - Task.async(fn -> - imported_block_numbers = - imported - |> Map.get(:blocks, []) - |> Enum.map(&Map.get(&1, :number)) - - if !Enum.empty?(imported_block_numbers) do - cache_key = @pending_blocks_cache_key - # credo:disable-for-next-line Credo.Check.Refactor.Nesting - case pending_blocks_cache() do - [{^cache_key, pending_blocks}] -> - update_pending_contract_creator_blocks(pending_blocks, imported_block_numbers, imported) - - [] -> - :ok - end - end - end) + GenServer.cast(__MODULE__, {:update_pending_contract_creator_cache, imported}) + :ok + end + + defp maybe_update_pending_contract_creator_cache(_imported, []), do: :ok + + defp maybe_update_pending_contract_creator_cache(imported, imported_block_numbers) do + case pending_blocks_cache() do + [{@pending_blocks_cache_key, pending_blocks}] -> + update_pending_contract_creator_blocks(pending_blocks, imported_block_numbers, imported) + + [] -> + :ok + end end defp update_pending_contract_creator_blocks([], _imported_block_numbers, _imported), do: [] diff --git a/apps/indexer/lib/indexer/fetcher/on_demand/internal_transaction.ex b/apps/indexer/lib/indexer/fetcher/on_demand/internal_transaction.ex index 2f13955a29da..bcc1ee5468f0 100644 --- a/apps/indexer/lib/indexer/fetcher/on_demand/internal_transaction.ex +++ b/apps/indexer/lib/indexer/fetcher/on_demand/internal_transaction.ex @@ -37,7 +37,7 @@ defmodule Indexer.Fetcher.OnDemand.InternalTransaction do true <- InternalTransaction.present_in_db?(min_block_number) do false else - _ -> true + _ -> not InternalTransactionsAddressPlaceholder.empty?() end end @@ -121,26 +121,28 @@ defmodule Indexer.Fetcher.OnDemand.InternalTransaction do |> Enum.map(&serialize/1) |> different_from_parent_transaction() |> Enum.sort_by(& &1.index) - |> add_block_hashes(transaction.block_hash) |> join_associations(necessity_by_association) |> page_internal_transaction(paging_options) |> Enum.take(paging_options.page_size) |> Repo.preload(:block) |> InternalTransaction.preload_error() + |> InternalTransaction.preload_transaction() + |> InternalTransaction.preload_addresses(options) :ignore -> [transaction.block_number] |> fetch_block_internal_transactions() |> Enum.map(&serialize/1) - |> Enum.filter(&(&1.transaction_hash == transaction.hash)) + |> Enum.filter(&(&1.block_number == transaction.block_number and &1.transaction_index == transaction.index)) |> different_from_parent_transaction() |> Enum.sort_by(& &1.index) - |> add_block_hashes(transaction.block_hash) |> join_associations(necessity_by_association) |> page_internal_transaction(paging_options) |> Enum.take(paging_options.page_size) |> Repo.preload(:block) |> InternalTransaction.preload_error() + |> InternalTransaction.preload_transaction() + |> InternalTransaction.preload_addresses(options) error -> Logger.error( @@ -191,9 +193,9 @@ defmodule Indexer.Fetcher.OnDemand.InternalTransaction do |> page_block_internal_transaction(paging_options) |> Enum.sort_by(&{&1.transaction_index, &1.index}) |> then(&if unlimited?, do: &1, else: Enum.take(&1, paging_options.page_size)) - |> add_block_hashes(block.hash) |> join_associations(necessity_by_association) |> InternalTransaction.preload_error() + |> InternalTransaction.preload_transaction() end @doc """ @@ -270,10 +272,10 @@ defmodule Indexer.Fetcher.OnDemand.InternalTransaction do }) |> Enum.sort_by(&{&1.block_number, &1.transaction_index, &1.index}, sort_func) |> Enum.take(paging_options.page_size) - |> add_block_hashes() |> join_associations(necessity_by_association) |> Repo.preload(:block) |> InternalTransaction.preload_error() + |> InternalTransaction.preload_transaction() end defp do_fetch_for_address(address_id, to_block, from_block, limit, sum_mode, sort_direction, acc \\ []) @@ -453,6 +455,21 @@ defmodule Indexer.Fetcher.OnDemand.InternalTransaction do |> windows([q], w: [order_by: [{^order, :block_number}]]) |> select([q], %{ block_number: q.block_number, + value: + fragment( + """ + CASE ? + WHEN 'froms' THEN ? + WHEN 'tos' THEN ? + ELSE ? + ? + END + """, + ^sum_mode, + q.count_froms, + q.count_tos, + q.count_froms, + q.count_tos + ), running_sum: fragment( """ @@ -476,6 +493,7 @@ defmodule Indexer.Fetcher.OnDemand.InternalTransaction do from(r in subquery(ranked_query), select: %{ block_number: r.block_number, + value: r.value, running_sum: r.running_sum, cutoff_block: fragment( @@ -489,8 +507,8 @@ defmodule Indexer.Fetcher.OnDemand.InternalTransaction do condition = case order do - :desc -> dynamic([c], is_nil(c.cutoff_block) or c.block_number >= c.cutoff_block) - :asc -> dynamic([c], is_nil(c.cutoff_block) or c.block_number <= c.cutoff_block) + :desc -> dynamic([c], c.value > 0 and (is_nil(c.cutoff_block) or c.block_number >= c.cutoff_block)) + :asc -> dynamic([c], c.value > 0 and (is_nil(c.cutoff_block) or c.block_number <= c.cutoff_block)) end final_query = @@ -698,25 +716,6 @@ defmodule Indexer.Fetcher.OnDemand.InternalTransaction do end) end - defp add_block_hashes(internal_transactions, block_hash \\ nil) do - block_number_to_hash_map = - case block_hash do - nil -> - internal_transactions - |> Enum.map(& &1.transaction_hash) - |> Enum.uniq() - |> Transaction.by_hashes_query() - |> select([t], {t.block_number, t.block_hash}) - |> Repo.all() - |> Map.new() - - _ -> - %{} - end - - Enum.map(internal_transactions, &%{&1 | block_hash: block_hash || block_number_to_hash_map[&1.block_number]}) - end - defp different_from_parent_transaction(internal_transactions) do Enum.reject(internal_transactions, &(&1.type == :call and &1.index == 0)) end @@ -725,6 +724,20 @@ defmodule Indexer.Fetcher.OnDemand.InternalTransaction do %InternalTransaction{} |> InternalTransaction.changeset(internal_transaction_params) |> Ecto.Changeset.apply_changes() + |> Map.put(:transaction_hash, internal_transaction_params[:transaction_hash]) + |> Map.put(:from_address_hash, convert_to_address_hash(internal_transaction_params[:from_address_hash])) + |> Map.put(:to_address_hash, convert_to_address_hash(internal_transaction_params[:to_address_hash])) + |> Map.put( + :created_contract_address_hash, + convert_to_address_hash(internal_transaction_params[:created_contract_address_hash]) + ) + end + + defp convert_to_address_hash(nil), do: nil + + defp convert_to_address_hash(hash_string) do + {:ok, hash} = Hash.Address.cast(hash_string) + hash end defp etherscan_serialize(internal_transaction) do diff --git a/apps/indexer/lib/indexer/fetcher/optimism.ex b/apps/indexer/lib/indexer/fetcher/optimism.ex index c3bcfec4cc56..e854d6db5632 100644 --- a/apps/indexer/lib/indexer/fetcher/optimism.ex +++ b/apps/indexer/lib/indexer/fetcher/optimism.ex @@ -22,10 +22,10 @@ defmodule Indexer.Fetcher.Optimism do alias Explorer.Chain.Cache.ChainId alias Explorer.Chain.Cache.Counters.LastFetchedCounter alias Explorer.Chain.Optimism.Withdrawal - alias Explorer.Chain.RollupReorgMonitorQueue alias Explorer.Repo alias Indexer.Fetcher.RollupL1ReorgMonitor alias Indexer.Helper + alias Indexer.RollupReorgMonitorQueue @empty_hash "0x0000000000000000000000000000000000000000000000000000000000000000" @@ -515,7 +515,7 @@ defmodule Indexer.Fetcher.Optimism do def handle_reorgs_queue(module, handle_reorg_func) do reorg_block_number = Enum.reduce_while(Stream.iterate(0, &(&1 + 1)), nil, fn _i, acc -> - number = RollupReorgMonitorQueue.reorg_block_pop(module) + number = RollupReorgMonitorQueue.pop(module) if is_nil(number) do {:halt, acc} @@ -543,7 +543,7 @@ defmodule Indexer.Fetcher.Optimism do @spec handle_realtime_l2_reorg(non_neg_integer(), module()) :: any() def handle_realtime_l2_reorg(reorg_block_number, module) do Logger.warning("L2 reorg was detected at block #{reorg_block_number}.", fetcher: module.fetcher_name()) - RollupReorgMonitorQueue.reorg_block_push(reorg_block_number, module) + RollupReorgMonitorQueue.push(reorg_block_number, module) end @doc """ diff --git a/apps/indexer/lib/indexer/fetcher/optimism/deposit.ex b/apps/indexer/lib/indexer/fetcher/optimism/deposit.ex index 83bb5c8cf34d..d5b51ad8498d 100644 --- a/apps/indexer/lib/indexer/fetcher/optimism/deposit.ex +++ b/apps/indexer/lib/indexer/fetcher/optimism/deposit.ex @@ -16,9 +16,9 @@ defmodule Indexer.Fetcher.Optimism.Deposit do alias Explorer.{Chain, Repo} alias Explorer.Chain.Events.Publisher alias Explorer.Chain.Optimism.Deposit - alias Explorer.Chain.RollupReorgMonitorQueue alias Indexer.Fetcher.Optimism alias Indexer.Helper + alias Indexer.RollupReorgMonitorQueue # 32-byte signature of the event TransactionDeposited(address indexed from, address indexed to, uint256 indexed version, bytes opaqueData) @transaction_deposited_event "0xb3813568d9991fc951961fcb4c784893574240a28925604d09fc577c55bb7c32" @@ -115,7 +115,7 @@ defmodule Indexer.Fetcher.Optimism.Deposit do ) end - reorg_block = RollupReorgMonitorQueue.reorg_block_pop(__MODULE__) + reorg_block = RollupReorgMonitorQueue.pop(__MODULE__) if !is_nil(reorg_block) && reorg_block > 0 do {deleted_count, _} = Repo.delete_all(from(d in Deposit, where: d.l1_block_number >= ^reorg_block)) diff --git a/apps/indexer/lib/indexer/fetcher/optimism/output_root.ex b/apps/indexer/lib/indexer/fetcher/optimism/output_root.ex index 49e2fed7483b..dd4b4bb2ff13 100644 --- a/apps/indexer/lib/indexer/fetcher/optimism/output_root.ex +++ b/apps/indexer/lib/indexer/fetcher/optimism/output_root.ex @@ -15,9 +15,9 @@ defmodule Indexer.Fetcher.Optimism.OutputRoot do alias Explorer.Application.Constants alias Explorer.{Chain, Helper, Repo} alias Explorer.Chain.Optimism.{DisputeGame, OutputRoot} - alias Explorer.Chain.RollupReorgMonitorQueue alias Indexer.Fetcher.Optimism alias Indexer.Helper, as: IndexerHelper + alias Indexer.RollupReorgMonitorQueue @fetcher_name :optimism_output_roots @stop_constant_key "optimism_output_roots_stopped" @@ -120,7 +120,7 @@ defmodule Indexer.Fetcher.Optimism.OutputRoot do ) end - reorg_block = RollupReorgMonitorQueue.reorg_block_pop(__MODULE__) + reorg_block = RollupReorgMonitorQueue.pop(__MODULE__) if !is_nil(reorg_block) && reorg_block > 0 do {deleted_count, _} = Repo.delete_all(from(r in OutputRoot, where: r.l1_block_number >= ^reorg_block)) diff --git a/apps/indexer/lib/indexer/fetcher/optimism/transaction_batch.ex b/apps/indexer/lib/indexer/fetcher/optimism/transaction_batch.ex index d79cd4721b73..9eb13562a270 100644 --- a/apps/indexer/lib/indexer/fetcher/optimism/transaction_batch.ex +++ b/apps/indexer/lib/indexer/fetcher/optimism/transaction_batch.ex @@ -32,13 +32,14 @@ defmodule Indexer.Fetcher.Optimism.TransactionBatch do alias EthereumJSONRPC.Block.ByHash alias EthereumJSONRPC.{Blocks, Contract} alias Explorer.{Chain, Repo} - alias Explorer.Chain.{Block, Hash, RollupReorgMonitorQueue} + alias Explorer.Chain.{Block, Hash} alias Explorer.Chain.Events.Publisher alias Explorer.Chain.Optimism.{FrameSequence, FrameSequenceBlob} alias Explorer.Chain.Optimism.TransactionBatch, as: OptimismTransactionBatch alias Indexer.Fetcher.{Optimism, RollupL1ReorgMonitor} alias Indexer.Helper alias Indexer.Prometheus.Instrumenter + alias Indexer.RollupReorgMonitorQueue alias Varint.LEB128 @fetcher_name :optimism_transaction_batches @@ -335,7 +336,7 @@ defmodule Indexer.Fetcher.Optimism.TransactionBatch do {incomplete_channels_acc, nil} end - reorg_block = RollupReorgMonitorQueue.reorg_block_pop(__MODULE__) + reorg_block = RollupReorgMonitorQueue.pop(__MODULE__) if !is_nil(reorg_block) && reorg_block > 0 do new_incomplete_channels = handle_l1_reorg(reorg_block, new_incomplete_channels) diff --git a/apps/indexer/lib/indexer/fetcher/optimism/withdrawal_event.ex b/apps/indexer/lib/indexer/fetcher/optimism/withdrawal_event.ex index 1ce8131d323e..1e6fd42a8934 100644 --- a/apps/indexer/lib/indexer/fetcher/optimism/withdrawal_event.ex +++ b/apps/indexer/lib/indexer/fetcher/optimism/withdrawal_event.ex @@ -14,9 +14,9 @@ defmodule Indexer.Fetcher.Optimism.WithdrawalEvent do alias Explorer.{Chain, Repo} alias Explorer.Chain.Optimism.WithdrawalEvent - alias Explorer.Chain.RollupReorgMonitorQueue alias Indexer.Fetcher.Optimism alias Indexer.Helper + alias Indexer.RollupReorgMonitorQueue @fetcher_name :optimism_withdrawal_events @counter_type "optimism_withdrawal_events_fetcher_last_l1_block_hash" @@ -124,7 +124,7 @@ defmodule Indexer.Fetcher.Optimism.WithdrawalEvent do ) end - reorg_block = RollupReorgMonitorQueue.reorg_block_pop(__MODULE__) + reorg_block = RollupReorgMonitorQueue.pop(__MODULE__) if !is_nil(reorg_block) && reorg_block > 0 do {deleted_count, _} = Repo.delete_all(from(we in WithdrawalEvent, where: we.l1_block_number >= ^reorg_block)) diff --git a/apps/indexer/lib/indexer/fetcher/polygon_zkevm/bridge.ex b/apps/indexer/lib/indexer/fetcher/polygon_zkevm/bridge.ex deleted file mode 100644 index a61f041bf840..000000000000 --- a/apps/indexer/lib/indexer/fetcher/polygon_zkevm/bridge.ex +++ /dev/null @@ -1,475 +0,0 @@ -defmodule Indexer.Fetcher.PolygonZkevm.Bridge do - @moduledoc """ - Contains common functions for Indexer.Fetcher.PolygonZkevm.Bridge* modules. - """ - - require Logger - - import EthereumJSONRPC, - only: [ - quantity_to_integer: 1, - timestamp_to_datetime: 1 - ] - - import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0] - - import Explorer.Helper, only: [decode_data: 2] - - alias EthereumJSONRPC.Logs - alias Explorer.Chain - alias Explorer.Chain.Hash - alias Explorer.Chain.PolygonZkevm.Reader - alias Indexer.Helper, as: IndexerHelper - alias Indexer.Transform.Addresses - - # 32-byte signature of the event BridgeEvent(uint8 leafType, uint32 originNetwork, address originAddress, uint32 destinationNetwork, address destinationAddress, uint256 amount, bytes metadata, uint32 depositCount) - @bridge_event "0x501781209a1f8899323b96b4ef08b168df93e0a90c673d1e4cce39366cb62f9b" - @bridge_event_params [{:uint, 8}, {:uint, 32}, :address, {:uint, 32}, :address, {:uint, 256}, :bytes, {:uint, 32}] - - # 32-byte signature of the event ClaimEvent(uint32 index, uint32 originNetwork, address originAddress, address destinationAddress, uint256 amount) - @claim_event_v1 "0x25308c93ceeed162da955b3f7ce3e3f93606579e40fb92029faa9efe27545983" - @claim_event_v1_params [{:uint, 32}, {:uint, 32}, :address, :address, {:uint, 256}] - - # 32-byte signature of the event ClaimEvent(uint256 globalIndex, uint32 originNetwork, address originAddress, address destinationAddress, uint256 amount) - @claim_event_v2 "0x1df3f2a973a00d6635911755c260704e95e8a5876997546798770f76396fda4d" - @claim_event_v2_params [{:uint, 256}, {:uint, 32}, :address, :address, {:uint, 256}] - - @symbol_method_selector "95d89b41" - @decimals_method_selector "313ce567" - - @erc20_abi [ - %{ - "constant" => true, - "inputs" => [], - "name" => "symbol", - "outputs" => [%{"name" => "", "type" => "string"}], - "payable" => false, - "stateMutability" => "view", - "type" => "function" - }, - %{ - "constant" => true, - "inputs" => [], - "name" => "decimals", - "outputs" => [%{"name" => "", "type" => "uint8"}], - "payable" => false, - "stateMutability" => "view", - "type" => "function" - } - ] - - @doc """ - Filters the given list of events keeping only `BridgeEvent` and `ClaimEvent` ones - emitted by the bridge contract. - """ - @spec filter_bridge_events(list(), binary()) :: list() - def filter_bridge_events(events, bridge_contract) do - Enum.filter(events, fn event -> - IndexerHelper.address_hash_to_string(event.address_hash, true) == bridge_contract and - Enum.member?( - [@bridge_event, @claim_event_v1, @claim_event_v2], - IndexerHelper.log_topic_to_string(event.first_topic) - ) - end) - end - - @doc """ - Fetches `BridgeEvent` and `ClaimEvent` events of the bridge contract from an RPC node - for the given range of blocks. - """ - @spec get_logs_all({non_neg_integer(), non_neg_integer()}, binary(), list()) :: list() - def get_logs_all({chunk_start, chunk_end}, bridge_contract, json_rpc_named_arguments) do - {:ok, result} = - IndexerHelper.get_logs( - chunk_start, - chunk_end, - bridge_contract, - [[@bridge_event, @claim_event_v1, @claim_event_v2]], - json_rpc_named_arguments, - 0, - IndexerHelper.infinite_retries_number() - ) - - Logs.elixir_to_params(result) - end - - @doc """ - Imports the given zkEVM bridge operations into database. - Used by Indexer.Fetcher.PolygonZkevm.BridgeL1 and Indexer.Fetcher.PolygonZkevm.BridgeL2 fetchers. - Doesn't return anything. - """ - @spec import_operations(list()) :: no_return() - def import_operations(operations) do - addresses = - Addresses.extract_addresses(%{ - polygon_zkevm_bridge_operations: operations - }) - - {:ok, _} = - Chain.import(%{ - addresses: %{params: addresses, on_conflict: :nothing}, - polygon_zkevm_bridge_operations: %{params: operations}, - timeout: :infinity - }) - end - - @doc """ - Converts the list of zkEVM bridge events to the list of operations - preparing them for importing to the database. - """ - @spec prepare_operations( - list(), - non_neg_integer(), - non_neg_integer(), - non_neg_integer() | nil, - non_neg_integer(), - list() | nil, - list(), - map() | nil - ) :: - list() - def prepare_operations( - events, - rollup_network_id_l1, - rollup_network_id_l2, - rollup_index_l1, - rollup_index_l2, - json_rpc_named_arguments, - json_rpc_named_arguments_l1, - block_to_timestamp \\ nil - ) do - is_l1 = json_rpc_named_arguments == json_rpc_named_arguments_l1 - - events = filter_events(events, is_l1, rollup_network_id_l1, rollup_network_id_l2, rollup_index_l1, rollup_index_l2) - - {block_to_timestamp, token_address_to_id} = - if is_nil(block_to_timestamp) do - # this function is called by the catchup indexer, - # so here we can use RPC calls as it's not so critical for delays as in realtime - bridge_events = Enum.filter(events, fn event -> event.first_topic == @bridge_event end) - l1_token_addresses = l1_token_addresses_from_bridge_events(bridge_events, rollup_network_id_l2) - - { - blocks_to_timestamps(bridge_events, json_rpc_named_arguments), - token_addresses_to_ids(l1_token_addresses, json_rpc_named_arguments_l1) - } - else - # this function is called in realtime by the transformer, - # so we don't use RPC calls to avoid delays and fetch token data - # in a separate fetcher - {block_to_timestamp, %{}} - end - - events - |> Enum.map(fn event -> - {index, l1_token_id, l1_token_address, l2_token_address, amount, block_number, block_timestamp} = - case event.first_topic do - @bridge_event -> - { - {l1_token_address, l2_token_address}, - amount, - deposit_count, - _destination_network - } = bridge_event_parse(event, rollup_network_id_l2) - - l1_token_id = Map.get(token_address_to_id, l1_token_address) - block_number = quantity_to_integer(event.block_number) - block_timestamp = Map.get(block_to_timestamp, block_number) - - # credo:disable-for-lines:2 Credo.Check.Refactor.Nesting - l1_token_address = - if is_nil(l1_token_id) do - l1_token_address - end - - {deposit_count, l1_token_id, l1_token_address, l2_token_address, amount, block_number, block_timestamp} - - @claim_event_v1 -> - {index, amount} = claim_event_v1_parse(event) - {index, nil, nil, nil, amount, nil, nil} - - @claim_event_v2 -> - {_mainnet_bit, _rollup_idx, index, _origin_network, amount} = claim_event_v2_parse(event) - {index, nil, nil, nil, amount, nil, nil} - end - - result = %{ - type: operation_type(event.first_topic, is_l1), - index: index, - amount: amount - } - - transaction_hash_field = - if is_l1 do - :l1_transaction_hash - else - :l2_transaction_hash - end - - result - |> extend_result(transaction_hash_field, event.transaction_hash) - |> extend_result(:l1_token_id, l1_token_id) - |> extend_result(:l1_token_address, l1_token_address) - |> extend_result(:l2_token_address, l2_token_address) - |> extend_result(:block_number, block_number) - |> extend_result(:block_timestamp, block_timestamp) - end) - end - - defp blocks_to_timestamps(events, json_rpc_named_arguments) do - events - |> IndexerHelper.get_blocks_by_events(json_rpc_named_arguments, IndexerHelper.infinite_retries_number()) - |> Enum.reduce(%{}, fn block, acc -> - block_number = quantity_to_integer(Map.get(block, "number")) - timestamp = timestamp_to_datetime(Map.get(block, "timestamp")) - Map.put(acc, block_number, timestamp) - end) - end - - defp bridge_event_parse(event, rollup_network_id_l2) do - [ - leaf_type, - origin_network, - origin_address_bytes, - destination_network, - _destination_address, - amount, - _metadata, - deposit_count - ] = decode_data(event.data, @bridge_event_params) - - {:ok, origin_address_hash} = Hash.Address.cast(origin_address_bytes) - - {token_address_by_origin_address(origin_address_hash, origin_network, leaf_type, rollup_network_id_l2), amount, - deposit_count, destination_network} - end - - defp claim_event_v1_parse(event) do - [index, _origin_network, _origin_address, _destination_address, amount] = - decode_data(event.data, @claim_event_v1_params) - - {index, amount} - end - - defp claim_event_v2_parse(event) do - [global_index, origin_network, _origin_address, _destination_address, amount] = - decode_data(event.data, @claim_event_v2_params) - - mainnet_bit = Bitwise.band(Bitwise.bsr(global_index, 64), 1) - - bitmask_4bytes = 0xFFFFFFFF - - rollup_index = Bitwise.band(Bitwise.bsr(global_index, 32), bitmask_4bytes) - - index = Bitwise.band(global_index, bitmask_4bytes) - - {mainnet_bit, rollup_index, index, origin_network, amount} - end - - defp filter_events(events, is_l1, rollup_network_id_l1, rollup_network_id_l2, rollup_index_l1, rollup_index_l2) do - Enum.filter(events, fn event -> - case {event.first_topic, is_l1} do - {@bridge_event, true} -> filter_bridge_event_l1(event, rollup_network_id_l2) - {@bridge_event, false} -> filter_bridge_event_l2(event, rollup_network_id_l1, rollup_network_id_l2) - {@claim_event_v2, true} -> filter_claim_event_l1(event, rollup_index_l2) - {@claim_event_v2, false} -> filter_claim_event_l2(event, rollup_network_id_l1, rollup_index_l1) - _ -> true - end - end) - end - - defp filter_bridge_event_l1(event, rollup_network_id_l2) do - {_, _, _, destination_network} = bridge_event_parse(event, rollup_network_id_l2) - # skip the Deposit event if it's for another rollup - destination_network == rollup_network_id_l2 - end - - defp filter_bridge_event_l2(event, rollup_network_id_l1, rollup_network_id_l2) do - {_, _, _, destination_network} = bridge_event_parse(event, rollup_network_id_l2) - # skip the Withdrawal event if it's for another L1 chain - destination_network == rollup_network_id_l1 - end - - defp filter_claim_event_l1(event, rollup_index_l2) do - {mainnet_bit, rollup_idx, _index, _origin_network, _amount} = claim_event_v2_parse(event) - - if mainnet_bit != 0 do - Logger.error( - "L1 ClaimEvent has non-zero mainnet bit in the transaction #{event.transaction_hash}. This event will be ignored." - ) - end - - # skip the Withdrawal event if it's for another rollup or the source network is Ethereum Mainnet - rollup_idx == rollup_index_l2 and mainnet_bit == 0 - end - - defp filter_claim_event_l2(event, rollup_network_id_l1, rollup_index_l1) do - {mainnet_bit, rollup_idx, _index, origin_network, _amount} = claim_event_v2_parse(event) - - # skip the Deposit event if it's from another L1 chain - (mainnet_bit == 1 and rollup_network_id_l1 == 0) or - (mainnet_bit == 0 and (rollup_idx == rollup_index_l1 or origin_network == rollup_network_id_l1)) - end - - defp l1_token_addresses_from_bridge_events(events, rollup_network_id_l2) do - events - |> Enum.reduce(%MapSet{}, fn event, acc -> - case bridge_event_parse(event, rollup_network_id_l2) do - {{nil, _}, _, _, _} -> acc - {{token_address, nil}, _, _, _} -> MapSet.put(acc, token_address) - end - end) - |> MapSet.to_list() - end - - defp operation_type(first_topic, is_l1) do - if first_topic == @bridge_event do - if is_l1, do: :deposit, else: :withdrawal - else - if is_l1, do: :withdrawal, else: :deposit - end - end - - @doc """ - Fetches L1 token data for the given token addresses, - builds `L1 token address -> L1 token id` map for them, - and writes the data to the database. Returns the resulting map. - """ - @spec token_addresses_to_ids(list(), list()) :: map() - def token_addresses_to_ids(l1_token_addresses, json_rpc_named_arguments) do - token_data = - l1_token_addresses - |> get_token_data(json_rpc_named_arguments) - - tokens_existing = - token_data - |> Map.keys() - |> Reader.token_addresses_to_ids_from_db() - - tokens_to_insert = - token_data - |> Enum.reject(fn {address, _} -> Map.has_key?(tokens_existing, address) end) - |> Enum.map(fn {address, data} -> Map.put(data, :address, address) end) - - {:ok, inserts} = - Chain.import(%{ - polygon_zkevm_bridge_l1_tokens: %{params: tokens_to_insert}, - timeout: :infinity - }) - - tokens_inserted = Map.get(inserts, :insert_polygon_zkevm_bridge_l1_tokens, []) - - # we need to query not inserted tokens from DB separately as they - # could be inserted by another module at the same time (a race condition). - # this is an unlikely case but we handle it here as well - tokens_not_inserted = - tokens_to_insert - |> Enum.reject(fn token -> - Enum.any?(tokens_inserted, fn inserted -> - token.address == IndexerHelper.address_hash_to_string(inserted.address) - end) - end) - |> Enum.map(& &1.address) - - tokens_inserted_outside = Reader.token_addresses_to_ids_from_db(tokens_not_inserted) - - tokens_inserted - |> Enum.reduce(%{}, fn t, acc -> Map.put(acc, IndexerHelper.address_hash_to_string(t.address), t.id) end) - |> Map.merge(tokens_existing) - |> Map.merge(tokens_inserted_outside) - end - - defp token_address_by_origin_address(origin_address, origin_network, leaf_type, rollup_network_id_l2) do - with true <- leaf_type != 1, - token_address = to_string(origin_address), - true <- token_address != burn_address_hash_string() do - if origin_network != rollup_network_id_l2 do - # this is L1 address - {token_address, nil} - else - # this is L2 address - {nil, token_address} - end - else - _ -> {nil, nil} - end - end - - defp get_token_data(token_addresses, json_rpc_named_arguments) do - # first, we're trying to read token data from the DB. - # if tokens are not in the DB, read them through RPC. - token_addresses - |> Reader.get_token_data_from_db() - |> get_token_data_from_rpc(json_rpc_named_arguments) - end - - defp get_token_data_from_rpc({token_data, token_addresses}, json_rpc_named_arguments) do - {requests, responses} = get_token_data_request_symbol_decimals(token_addresses, json_rpc_named_arguments) - - requests - |> Enum.zip(responses) - |> Enum.reduce(token_data, fn {request, {status, response} = _resp}, token_data_acc -> - if status == :ok do - response = parse_response(response) - - address = IndexerHelper.address_hash_to_string(request.contract_address, true) - - new_data = get_new_data(token_data_acc[address] || %{}, request, response) - - Map.put(token_data_acc, address, new_data) - else - token_data_acc - end - end) - end - - defp get_token_data_request_symbol_decimals(token_addresses, json_rpc_named_arguments) do - requests = - token_addresses - |> Enum.map(fn address -> - # we will call symbol() and decimals() public getters - Enum.map([@symbol_method_selector, @decimals_method_selector], fn method_id -> - %{ - contract_address: address, - method_id: method_id, - args: [] - } - end) - end) - |> List.flatten() - - {responses, error_messages} = - IndexerHelper.read_contracts_with_retries(requests, @erc20_abi, json_rpc_named_arguments, 3) - - if not Enum.empty?(error_messages) or Enum.count(requests) != Enum.count(responses) do - Logger.warning( - "Cannot read symbol and decimals of an ERC-20 token contract. Error messages: #{Enum.join(error_messages, ", ")}. Addresses: #{Enum.join(token_addresses, ", ")}" - ) - end - - {requests, responses} - end - - defp get_new_data(data, request, response) do - if atomized_key(request.method_id) == :symbol do - Map.put(data, :symbol, Reader.sanitize_symbol(response)) - else - Map.put(data, :decimals, Reader.sanitize_decimals(response)) - end - end - - defp extend_result(result, _key, value) when is_nil(value), do: result - defp extend_result(result, key, value) when is_atom(key), do: Map.put(result, key, value) - - defp atomized_key("symbol"), do: :symbol - defp atomized_key("decimals"), do: :decimals - defp atomized_key(@symbol_method_selector), do: :symbol - defp atomized_key(@decimals_method_selector), do: :decimals - - defp parse_response(response) do - case response do - [item] -> item - items -> items - end - end -end diff --git a/apps/indexer/lib/indexer/fetcher/polygon_zkevm/bridge_l1.ex b/apps/indexer/lib/indexer/fetcher/polygon_zkevm/bridge_l1.ex deleted file mode 100644 index 1e45795d35ff..000000000000 --- a/apps/indexer/lib/indexer/fetcher/polygon_zkevm/bridge_l1.ex +++ /dev/null @@ -1,275 +0,0 @@ -defmodule Indexer.Fetcher.PolygonZkevm.BridgeL1 do - @moduledoc """ - Fills polygon_zkevm_bridge DB table. - """ - - use GenServer - use Indexer.Fetcher - - require Logger - - import Ecto.Query - import Explorer.Helper, only: [parse_integer: 1] - - import Indexer.Fetcher.PolygonZkevm.Bridge, - only: [get_logs_all: 3, import_operations: 1, prepare_operations: 7] - - alias Explorer.Chain.PolygonZkevm.{Bridge, Reader} - alias Explorer.Chain.RollupReorgMonitorQueue - alias Explorer.Repo - alias Indexer.Fetcher.RollupL1ReorgMonitor - alias Indexer.Helper - - @eth_get_logs_range_size 1000 - @fetcher_name :polygon_zkevm_bridge_l1 - - def child_spec(start_link_arguments) do - spec = %{ - id: __MODULE__, - start: {__MODULE__, :start_link, start_link_arguments}, - restart: :transient, - type: :worker - } - - Supervisor.child_spec(spec, []) - end - - def start_link(args, gen_server_options \\ []) do - GenServer.start_link(__MODULE__, args, Keyword.put_new(gen_server_options, :name, __MODULE__)) - end - - @impl GenServer - def init(_args) do - {:ok, %{}, {:continue, :ok}} - end - - @impl GenServer - def handle_continue(_, state) do - Logger.metadata(fetcher: @fetcher_name) - # two seconds pause needed to avoid exceeding Supervisor restart intensity when DB issues - Process.send_after(self(), :init_with_delay, 2000) - {:noreply, state} - end - - @impl GenServer - def handle_info(:init_with_delay, _state) do - env = Application.get_all_env(:indexer)[__MODULE__] - env_l2 = Application.get_all_env(:indexer)[Indexer.Fetcher.PolygonZkevm.BridgeL2] - - with {:start_block_undefined, false} <- {:start_block_undefined, is_nil(env[:start_block])}, - _ <- RollupL1ReorgMonitor.wait_for_start(__MODULE__), - rpc = env[:rpc], - {:rpc_undefined, false} <- {:rpc_undefined, is_nil(rpc)}, - {:rollup_network_id_l1_is_valid, true} <- - {:rollup_network_id_l1_is_valid, !is_nil(env[:rollup_network_id_l1]) and env[:rollup_network_id_l1] >= 0}, - {:rollup_network_id_l2_is_valid, true} <- - {:rollup_network_id_l2_is_valid, - !is_nil(env_l2[:rollup_network_id_l2]) and env_l2[:rollup_network_id_l2] > 0}, - {:rollup_index_l2_undefined, false} <- {:rollup_index_l2_undefined, is_nil(env_l2[:rollup_index_l2])}, - {:bridge_contract_address_is_valid, true} <- - {:bridge_contract_address_is_valid, Helper.address_correct?(env[:bridge_contract])}, - start_block = parse_integer(env[:start_block]), - false <- is_nil(start_block), - true <- start_block > 0, - {last_l1_block_number, last_l1_transaction_hash} = Reader.last_l1_item(), - json_rpc_named_arguments = Helper.json_rpc_named_arguments(rpc), - {:ok, block_check_interval, safe_block} <- Helper.get_block_check_interval(json_rpc_named_arguments), - {:start_block_valid, true, _, _} <- - {:start_block_valid, - (start_block <= last_l1_block_number || last_l1_block_number == 0) && start_block <= safe_block, - last_l1_block_number, safe_block}, - {:ok, last_l1_transaction} <- - Helper.get_transaction_by_hash(last_l1_transaction_hash, json_rpc_named_arguments), - {:l1_transaction_not_found, false} <- - {:l1_transaction_not_found, !is_nil(last_l1_transaction_hash) && is_nil(last_l1_transaction)} do - Process.send(self(), :continue, []) - - {:noreply, - %{ - block_check_interval: block_check_interval, - bridge_contract: env[:bridge_contract], - json_rpc_named_arguments: json_rpc_named_arguments, - end_block: safe_block, - start_block: max(start_block, last_l1_block_number), - rollup_network_id_l1: env[:rollup_network_id_l1], - rollup_network_id_l2: env_l2[:rollup_network_id_l2], - rollup_index_l1: env[:rollup_index_l1], - rollup_index_l2: env_l2[:rollup_index_l2] - }} - else - {:start_block_undefined, true} -> - # the process shouldn't start if the start block is not defined - {:stop, :normal, %{}} - - {:rpc_undefined, true} -> - Logger.error("L1 RPC URL is not defined.") - {:stop, :normal, %{}} - - {:rollup_network_id_l1_is_valid, false} -> - Logger.error( - "Invalid network ID for L1. Please, check INDEXER_POLYGON_ZKEVM_L1_BRIDGE_NETWORK_ID env variable." - ) - - {:stop, :normal, %{}} - - {:rollup_network_id_l2_is_valid, false} -> - Logger.error( - "Invalid network ID for L2. Please, check INDEXER_POLYGON_ZKEVM_L2_BRIDGE_NETWORK_ID env variable." - ) - - {:stop, :normal, %{}} - - {:rollup_index_l2_undefined, true} -> - Logger.error( - "Rollup index is undefined for L2. Please, check INDEXER_POLYGON_ZKEVM_L2_BRIDGE_ROLLUP_INDEX env variable." - ) - - {:stop, :normal, %{}} - - {:bridge_contract_address_is_valid, false} -> - Logger.error("PolygonZkEVMBridge contract address is invalid or not defined.") - {:stop, :normal, %{}} - - {:start_block_valid, false, last_l1_block_number, safe_block} -> - Logger.error("Invalid L1 Start Block value. Please, check the value and polygon_zkevm_bridge table.") - Logger.error("last_l1_block_number = #{inspect(last_l1_block_number)}") - Logger.error("safe_block = #{inspect(safe_block)}") - {:stop, :normal, %{}} - - {:error, error_data} -> - Logger.error( - "Cannot get last L1 transaction from RPC by its hash, latest block, or block timestamp by its number due to RPC error: #{inspect(error_data)}" - ) - - {:stop, :normal, %{}} - - {:l1_transaction_not_found, true} -> - Logger.error( - "Cannot find last L1 transaction from RPC by its hash. Probably, there was a reorg on L1 chain. Please, check polygon_zkevm_bridge table." - ) - - {:stop, :normal, %{}} - - _ -> - Logger.error("L1 Start Block is invalid or zero.") - {:stop, :normal, %{}} - end - end - - @impl GenServer - def handle_info( - :continue, - %{ - bridge_contract: bridge_contract, - block_check_interval: block_check_interval, - start_block: start_block, - end_block: end_block, - json_rpc_named_arguments: json_rpc_named_arguments, - rollup_network_id_l1: rollup_network_id_l1, - rollup_network_id_l2: rollup_network_id_l2, - rollup_index_l1: rollup_index_l1, - rollup_index_l2: rollup_index_l2 - } = state - ) do - time_before = Timex.now() - - last_written_block = - start_block..end_block - |> Enum.chunk_every(@eth_get_logs_range_size) - |> Enum.reduce_while(start_block - 1, fn current_chunk, _ -> - chunk_start = List.first(current_chunk) - chunk_end = List.last(current_chunk) - - if chunk_start <= chunk_end do - Helper.log_blocks_chunk_handling(chunk_start, chunk_end, start_block, end_block, nil, :L1) - - operations = - {chunk_start, chunk_end} - |> get_logs_all(bridge_contract, json_rpc_named_arguments) - |> prepare_operations( - rollup_network_id_l1, - rollup_network_id_l2, - rollup_index_l1, - rollup_index_l2, - json_rpc_named_arguments, - json_rpc_named_arguments - ) - - import_operations(operations) - - Helper.log_blocks_chunk_handling( - chunk_start, - chunk_end, - start_block, - end_block, - "#{Enum.count(operations)} L1 operation(s)", - :L1 - ) - end - - reorg_block = RollupReorgMonitorQueue.reorg_block_pop(__MODULE__) - - if !is_nil(reorg_block) && reorg_block > 0 do - reorg_handle(reorg_block) - {:halt, if(reorg_block <= chunk_end, do: reorg_block - 1, else: chunk_end)} - else - {:cont, chunk_end} - end - end) - - new_start_block = last_written_block + 1 - - {:ok, new_end_block} = - Helper.get_block_number_by_tag("latest", json_rpc_named_arguments, Helper.infinite_retries_number()) - - delay = - if new_end_block == last_written_block do - # there is no new block, so wait for some time to let the chain issue the new block - max(block_check_interval - Timex.diff(Timex.now(), time_before, :milliseconds), 0) - else - 0 - end - - Process.send_after(self(), :continue, delay) - - {:noreply, %{state | start_block: new_start_block, end_block: new_end_block}} - end - - @impl GenServer - def handle_info({ref, _result}, state) do - Process.demonitor(ref, [:flush]) - {:noreply, state} - end - - @doc """ - Returns L1 RPC URL for this module. - """ - @spec l1_rpc_url() :: binary() - def l1_rpc_url do - Application.get_all_env(:indexer)[__MODULE__][:rpc] - end - - @doc """ - Determines if `Indexer.Fetcher.RollupL1ReorgMonitor` module must be up - for this module. - - ## Returns - - `true` if the reorg monitor must be active, `false` otherwise. - """ - @spec requires_l1_reorg_monitor?() :: boolean() - def requires_l1_reorg_monitor? do - module_config = Application.get_all_env(:indexer)[__MODULE__] - not is_nil(module_config[:start_block]) - end - - defp reorg_handle(reorg_block) do - {deleted_count, _} = - Repo.delete_all(from(b in Bridge, where: b.type == :deposit and b.block_number >= ^reorg_block)) - - if deleted_count > 0 do - Logger.warning( - "As L1 reorg was detected, some deposits with block_number >= #{reorg_block} were removed from polygon_zkevm_bridge table. Number of removed rows: #{deleted_count}." - ) - end - end -end diff --git a/apps/indexer/lib/indexer/fetcher/polygon_zkevm/bridge_l1_tokens.ex b/apps/indexer/lib/indexer/fetcher/polygon_zkevm/bridge_l1_tokens.ex deleted file mode 100644 index c208b9f6c7b0..000000000000 --- a/apps/indexer/lib/indexer/fetcher/polygon_zkevm/bridge_l1_tokens.ex +++ /dev/null @@ -1,78 +0,0 @@ -defmodule Indexer.Fetcher.PolygonZkevm.BridgeL1Tokens do - @moduledoc """ - Fetches information about L1 tokens for zkEVM bridge. - """ - - use Indexer.Fetcher, restart: :permanent - use Spandex.Decorators - - import Ecto.Query - - alias Explorer.Repo - alias Indexer.{BufferedTask, Helper} - alias Indexer.Fetcher.PolygonZkevm.{Bridge, BridgeL1} - - @behaviour BufferedTask - - @default_max_batch_size 1 - @default_max_concurrency 10 - - @doc false - def child_spec([init_options, gen_server_options]) do - rpc = Application.get_all_env(:indexer)[BridgeL1][:rpc] - json_rpc_named_arguments = Helper.json_rpc_named_arguments(rpc) - - merged_init_opts = - defaults() - |> Keyword.merge(init_options) - |> Keyword.merge(state: json_rpc_named_arguments) - - Supervisor.child_spec({BufferedTask, [{__MODULE__, merged_init_opts}, gen_server_options]}, id: __MODULE__) - end - - @impl BufferedTask - def init(_, _, _) do - {0, []} - end - - @impl BufferedTask - def run(l1_token_addresses, json_rpc_named_arguments) when is_list(l1_token_addresses) do - l1_token_addresses - |> Bridge.token_addresses_to_ids(json_rpc_named_arguments) - |> Enum.each(fn {l1_token_address, l1_token_id} -> - Repo.update_all( - from(b in Explorer.Chain.PolygonZkevm.Bridge, where: b.l1_token_address == ^l1_token_address), - set: [l1_token_id: l1_token_id, l1_token_address: nil] - ) - end) - end - - @doc """ - Fetches L1 token data asynchronously. - """ - def async_fetch(data) do - async_fetch(data, Application.get_env(:indexer, __MODULE__.Supervisor)[:enabled]) - end - - def async_fetch(_data, false), do: :ok - - def async_fetch(operations, _enabled) do - l1_token_addresses = - operations - |> Enum.reject(fn operation -> is_nil(operation.l1_token_address) end) - |> Enum.map(fn operation -> operation.l1_token_address end) - |> Enum.uniq() - - BufferedTask.buffer(__MODULE__, l1_token_addresses, true) - end - - defp defaults do - [ - flush_interval: 100, - max_concurrency: Application.get_env(:indexer, __MODULE__)[:concurrency] || @default_max_concurrency, - max_batch_size: Application.get_env(:indexer, __MODULE__)[:batch_size] || @default_max_batch_size, - poll: false, - task_supervisor: __MODULE__.TaskSupervisor - ] - end -end diff --git a/apps/indexer/lib/indexer/fetcher/polygon_zkevm/bridge_l2.ex b/apps/indexer/lib/indexer/fetcher/polygon_zkevm/bridge_l2.ex deleted file mode 100644 index 983f69a39cc1..000000000000 --- a/apps/indexer/lib/indexer/fetcher/polygon_zkevm/bridge_l2.ex +++ /dev/null @@ -1,223 +0,0 @@ -defmodule Indexer.Fetcher.PolygonZkevm.BridgeL2 do - @moduledoc """ - Fills polygon_zkevm_bridge DB table. - """ - - use GenServer - use Indexer.Fetcher - - require Logger - - import Ecto.Query - import Explorer.Helper, only: [parse_integer: 1] - - import Indexer.Fetcher.PolygonZkevm.Bridge, - only: [get_logs_all: 3, import_operations: 1, prepare_operations: 7] - - alias Explorer.Chain.PolygonZkevm.{Bridge, Reader} - alias Explorer.Repo - alias Indexer.Fetcher.PolygonZkevm.BridgeL1 - alias Indexer.Helper - - @eth_get_logs_range_size 1000 - @fetcher_name :polygon_zkevm_bridge_l2 - - def child_spec(start_link_arguments) do - spec = %{ - id: __MODULE__, - start: {__MODULE__, :start_link, start_link_arguments}, - restart: :transient, - type: :worker - } - - Supervisor.child_spec(spec, []) - end - - def start_link(args, gen_server_options \\ []) do - GenServer.start_link(__MODULE__, args, Keyword.put_new(gen_server_options, :name, __MODULE__)) - end - - @impl GenServer - def init(args) do - json_rpc_named_arguments = args[:json_rpc_named_arguments] - {:ok, %{}, {:continue, json_rpc_named_arguments}} - end - - @impl GenServer - def handle_continue(json_rpc_named_arguments, _state) do - Logger.metadata(fetcher: @fetcher_name) - # two seconds pause needed to avoid exceeding Supervisor restart intensity when DB issues - Process.send_after(self(), :init_with_delay, 2000) - {:noreply, %{json_rpc_named_arguments: json_rpc_named_arguments}} - end - - @impl GenServer - def handle_info(:init_with_delay, %{json_rpc_named_arguments: json_rpc_named_arguments} = state) do - env = Application.get_all_env(:indexer)[__MODULE__] - env_l1 = Application.get_all_env(:indexer)[BridgeL1] - - with {:start_block_undefined, false} <- {:start_block_undefined, is_nil(env[:start_block])}, - rpc_l1 = env_l1[:rpc], - {:rpc_l1_undefined, false} <- {:rpc_l1_undefined, is_nil(rpc_l1)}, - {:rollup_network_id_l1_is_valid, true} <- - {:rollup_network_id_l1_is_valid, - !is_nil(env_l1[:rollup_network_id_l1]) and env_l1[:rollup_network_id_l1] >= 0}, - {:rollup_network_id_l2_is_valid, true} <- - {:rollup_network_id_l2_is_valid, !is_nil(env[:rollup_network_id_l2]) and env[:rollup_network_id_l2] > 0}, - {:rollup_index_l2_undefined, false} <- {:rollup_index_l2_undefined, is_nil(env[:rollup_index_l2])}, - {:bridge_contract_address_is_valid, true} <- - {:bridge_contract_address_is_valid, Helper.address_correct?(env[:bridge_contract])}, - start_block = parse_integer(env[:start_block]), - false <- is_nil(start_block), - true <- start_block > 0, - {last_l2_block_number, last_l2_transaction_hash} = Reader.last_l2_item(), - {:ok, latest_block} = - Helper.get_block_number_by_tag("latest", json_rpc_named_arguments, Helper.infinite_retries_number()), - {:start_block_valid, true} <- - {:start_block_valid, - (start_block <= last_l2_block_number || last_l2_block_number == 0) && start_block <= latest_block}, - {:ok, last_l2_transaction} <- - Helper.get_transaction_by_hash(last_l2_transaction_hash, json_rpc_named_arguments), - {:l2_transaction_not_found, false} <- - {:l2_transaction_not_found, !is_nil(last_l2_transaction_hash) && is_nil(last_l2_transaction)} do - Process.send(self(), :continue, []) - - {:noreply, - %{ - bridge_contract: env[:bridge_contract], - json_rpc_named_arguments: json_rpc_named_arguments, - json_rpc_named_arguments_l1: Helper.json_rpc_named_arguments(rpc_l1), - end_block: latest_block, - start_block: max(start_block, last_l2_block_number), - rollup_network_id_l1: env_l1[:rollup_network_id_l1], - rollup_network_id_l2: env[:rollup_network_id_l2], - rollup_index_l1: env_l1[:rollup_index_l1], - rollup_index_l2: env[:rollup_index_l2] - }} - else - {:start_block_undefined, true} -> - # the process shouldn't start if the start block is not defined - {:stop, :normal, state} - - {:rpc_l1_undefined, true} -> - Logger.error("L1 RPC URL is not defined.") - {:stop, :normal, state} - - {:rollup_network_id_l1_is_valid, false} -> - Logger.error( - "Invalid network ID for L1. Please, check INDEXER_POLYGON_ZKEVM_L1_BRIDGE_NETWORK_ID env variable." - ) - - {:stop, :normal, %{}} - - {:rollup_network_id_l2_is_valid, false} -> - Logger.error( - "Invalid network ID for L2. Please, check INDEXER_POLYGON_ZKEVM_L2_BRIDGE_NETWORK_ID env variable." - ) - - {:stop, :normal, %{}} - - {:rollup_index_l2_undefined, true} -> - Logger.error( - "Rollup index is undefined for L2. Please, check INDEXER_POLYGON_ZKEVM_L2_BRIDGE_ROLLUP_INDEX env variable." - ) - - {:stop, :normal, %{}} - - {:bridge_contract_address_is_valid, false} -> - Logger.error("PolygonZkEVMBridge contract address is invalid or not defined.") - {:stop, :normal, state} - - {:start_block_valid, false} -> - Logger.error("Invalid L2 Start Block value. Please, check the value and polygon_zkevm_bridge table.") - {:stop, :normal, state} - - {:error, error_data} -> - Logger.error( - "Cannot get last L2 transaction from RPC by its hash or latest block due to RPC error: #{inspect(error_data)}" - ) - - {:stop, :normal, state} - - {:l2_transaction_not_found, true} -> - Logger.error( - "Cannot find last L2 transaction from RPC by its hash. Probably, there was a reorg on L2 chain. Please, check polygon_zkevm_bridge table." - ) - - {:stop, :normal, state} - - _ -> - Logger.error("L2 Start Block is invalid or zero.") - {:stop, :normal, state} - end - end - - @impl GenServer - def handle_info( - :continue, - %{ - bridge_contract: bridge_contract, - start_block: start_block, - end_block: end_block, - json_rpc_named_arguments: json_rpc_named_arguments, - json_rpc_named_arguments_l1: json_rpc_named_arguments_l1, - rollup_network_id_l1: rollup_network_id_l1, - rollup_network_id_l2: rollup_network_id_l2, - rollup_index_l1: rollup_index_l1, - rollup_index_l2: rollup_index_l2 - } = state - ) do - start_block..end_block - |> Enum.chunk_every(@eth_get_logs_range_size) - |> Enum.each(fn current_chunk -> - chunk_start = List.first(current_chunk) - chunk_end = List.last(current_chunk) - - if chunk_start <= chunk_end do - Helper.log_blocks_chunk_handling(chunk_start, chunk_end, start_block, end_block, nil, :L2) - - operations = - {chunk_start, chunk_end} - |> get_logs_all(bridge_contract, json_rpc_named_arguments) - |> prepare_operations( - rollup_network_id_l1, - rollup_network_id_l2, - rollup_index_l1, - rollup_index_l2, - json_rpc_named_arguments, - json_rpc_named_arguments_l1 - ) - - import_operations(operations) - - Helper.log_blocks_chunk_handling( - chunk_start, - chunk_end, - start_block, - end_block, - "#{Enum.count(operations)} L2 operation(s)", - :L2 - ) - end - end) - - {:stop, :normal, state} - end - - @impl GenServer - def handle_info({ref, _result}, state) do - Process.demonitor(ref, [:flush]) - {:noreply, state} - end - - def reorg_handle(reorg_block) do - {deleted_count, _} = - Repo.delete_all(from(b in Bridge, where: b.type == :withdrawal and b.block_number >= ^reorg_block)) - - if deleted_count > 0 do - Logger.warning( - "As L2 reorg was detected, some withdrawals with block_number >= #{reorg_block} were removed from polygon_zkevm_bridge table. Number of removed rows: #{deleted_count}." - ) - end - end -end diff --git a/apps/indexer/lib/indexer/fetcher/polygon_zkevm/transaction_batch.ex b/apps/indexer/lib/indexer/fetcher/polygon_zkevm/transaction_batch.ex deleted file mode 100644 index c1ae8972c541..000000000000 --- a/apps/indexer/lib/indexer/fetcher/polygon_zkevm/transaction_batch.ex +++ /dev/null @@ -1,375 +0,0 @@ -defmodule Indexer.Fetcher.PolygonZkevm.TransactionBatch do - @moduledoc """ - Fills polygon_zkevm_transaction_batches DB table. - """ - - use GenServer - use Indexer.Fetcher - - require Logger - - import EthereumJSONRPC, only: [integer_to_quantity: 1, json_rpc: 2, quantity_to_integer: 1] - - alias Explorer.Chain - alias Explorer.Chain.Events.Publisher - alias Explorer.Chain.PolygonZkevm.Reader - alias Indexer.Helper - alias Indexer.Prometheus.Instrumenter - - @zero_hash "0000000000000000000000000000000000000000000000000000000000000000" - - def child_spec(start_link_arguments) do - spec = %{ - id: __MODULE__, - start: {__MODULE__, :start_link, start_link_arguments}, - restart: :transient, - type: :worker - } - - Supervisor.child_spec(spec, []) - end - - def start_link(args, gen_server_options \\ []) do - GenServer.start_link(__MODULE__, args, Keyword.put_new(gen_server_options, :name, __MODULE__)) - end - - @impl GenServer - def init(args) do - Logger.metadata(fetcher: :polygon_zkevm_transaction_batches) - - config = Application.get_all_env(:indexer)[Indexer.Fetcher.PolygonZkevm.TransactionBatch] - chunk_size = config[:chunk_size] - recheck_interval = config[:recheck_interval] - - ignore_numbers = - config[:ignore_numbers] - |> String.trim() - |> String.split(",") - |> Enum.map(fn ignore_number -> - ignore_number - |> String.trim() - |> String.to_integer() - end) - - # two seconds pause needed to avoid exceeding Supervisor restart intensity when DB issues - Process.send_after(self(), :continue, 2000) - - {:ok, - %{ - chunk_size: chunk_size, - ignore_numbers: ignore_numbers, - json_rpc_named_arguments: args[:json_rpc_named_arguments], - prev_latest_batch_number: 0, - prev_virtual_batch_number: 0, - prev_verified_batch_number: 0, - recheck_interval: recheck_interval - }} - end - - @impl GenServer - def handle_info( - :continue, - %{ - chunk_size: chunk_size, - ignore_numbers: ignore_numbers, - json_rpc_named_arguments: json_rpc_named_arguments, - prev_latest_batch_number: prev_latest_batch_number, - prev_virtual_batch_number: prev_virtual_batch_number, - prev_verified_batch_number: prev_verified_batch_number, - recheck_interval: recheck_interval - } = state - ) do - {latest_batch_number, virtual_batch_number, verified_batch_number} = - fetch_latest_batch_numbers(json_rpc_named_arguments) - - {new_state, handle_duration} = - if latest_batch_number > prev_latest_batch_number or virtual_batch_number > prev_virtual_batch_number or - verified_batch_number > prev_verified_batch_number do - start_batch_number = Reader.last_verified_batch_number() + 1 - end_batch_number = latest_batch_number - - log_message = - "" - |> make_log_message(latest_batch_number, prev_latest_batch_number, "latest") - |> make_log_message(virtual_batch_number, prev_virtual_batch_number, "virtual") - |> make_log_message(verified_batch_number, prev_verified_batch_number, "verified") - - Logger.info(log_message <> "Handling the batch range #{start_batch_number}..#{end_batch_number}.") - - {handle_duration, _} = - :timer.tc(fn -> - handle_batch_range( - start_batch_number, - end_batch_number, - json_rpc_named_arguments, - chunk_size, - ignore_numbers - ) - end) - - { - %{ - state - | prev_latest_batch_number: latest_batch_number, - prev_virtual_batch_number: virtual_batch_number, - prev_verified_batch_number: verified_batch_number - }, - div(handle_duration, 1000) - } - else - {state, 0} - end - - Process.send_after(self(), :continue, max(:timer.seconds(recheck_interval) - handle_duration, 0)) - - {:noreply, new_state} - end - - @impl GenServer - def handle_info({ref, _result}, state) do - Process.demonitor(ref, [:flush]) - {:noreply, state} - end - - defp handle_batch_range(start_batch_number, end_batch_number, json_rpc_named_arguments, chunk_size, ignore_numbers) do - start_batch_number..end_batch_number - |> Enum.chunk_every(chunk_size) - |> Enum.each(fn chunk -> - chunk_start = List.first(chunk) - chunk_end = List.last(chunk) - - log_batches_chunk_handling(chunk_start, chunk_end, start_batch_number, end_batch_number) - fetch_and_save_batches(chunk_start, chunk_end, json_rpc_named_arguments, ignore_numbers) - end) - end - - defp log_batches_chunk_handling(chunk_start, chunk_end, start_block, end_block) do - target_range = - if chunk_start != start_block or chunk_end != end_block do - percentage = - (chunk_end - start_block + 1) - |> Decimal.div(end_block - start_block + 1) - |> Decimal.mult(100) - |> Decimal.round(2) - |> Decimal.to_string() - - " Target range: #{start_block}..#{end_block}. Progress: #{percentage}%" - else - "" - end - - if chunk_start == chunk_end do - Logger.info("Handling batch ##{chunk_start}.#{target_range}") - else - Logger.info("Handling batch range #{chunk_start}..#{chunk_end}.#{target_range}") - end - end - - defp make_log_message(prev_message, batch_number, prev_batch_number, type) do - if batch_number > prev_batch_number do - prev_message <> - "Found a new #{type} batch number #{batch_number}. Previous #{type} batch number is #{prev_batch_number}. " - else - prev_message - end - end - - defp fetch_and_save_batches(batch_start, batch_end, json_rpc_named_arguments, ignore_numbers) do - {:ok, responses} = perform_jsonrpc_requests(batch_start, batch_end, json_rpc_named_arguments, ignore_numbers) - - # For every batch info extract batches' L1 sequence transaction and L1 verify transaction - {sequence_hashes, verify_hashes} = - responses - |> Enum.reduce({[], []}, fn res, {sequences, verifies} = _acc -> - send_sequences_transaction_hash = get_transaction_hash(res.result, "sendSequencesTxHash") - verify_batch_transaction_hash = get_transaction_hash(res.result, "verifyBatchTxHash") - - sequences = - if send_sequences_transaction_hash != @zero_hash do - [Base.decode16!(send_sequences_transaction_hash, case: :mixed) | sequences] - else - sequences - end - - verifies = - if verify_batch_transaction_hash != @zero_hash do - [Base.decode16!(verify_batch_transaction_hash, case: :mixed) | verifies] - else - verifies - end - - {sequences, verifies} - end) - - # All L1 transactions in one list without repetition - l1_transaction_hashes = Enum.uniq(sequence_hashes ++ verify_hashes) - - # Receive all IDs for L1 transactions - hash_to_id = - l1_transaction_hashes - |> Reader.lifecycle_transactions() - |> Enum.reduce(%{}, fn {hash, id}, acc -> - Map.put(acc, hash.bytes, id) - end) - - # For every batch build batch representation, collect associated L1 and L2 transactions - {batches_to_import, l2_transactions_to_import, l1_transactions_to_import, _, _} = - responses - |> Enum.reduce({[], [], [], Reader.next_id(), hash_to_id}, fn res, - {batches, l2_transactions, l1_transactions, next_id, - hash_to_id} = _acc -> - number = quantity_to_integer(Map.get(res.result, "number")) - - # the timestamp is undefined for unfinalized batches - timestamp = - case DateTime.from_unix(quantity_to_integer(Map.get(res.result, "timestamp", 0xFFFFFFFFFFFFFFFF))) do - {:ok, ts} -> ts - _ -> nil - end - - l2_transaction_hashes = Map.get(res.result, "transactions") - global_exit_root = Map.get(res.result, "globalExitRoot") - acc_input_hash = Map.get(res.result, "accInputHash") - state_root = Map.get(res.result, "stateRoot") - - # Get ID for sequence transaction (new ID if the batch is just sequenced) - {sequence_id, l1_transactions, next_id, hash_to_id} = - res.result - |> get_transaction_hash("sendSequencesTxHash") - |> handle_transaction_hash(hash_to_id, next_id, l1_transactions, false) - - # Get ID for verify transaction (new ID if the batch is just verified) - {verify_id, l1_transactions, next_id, hash_to_id} = - res.result - |> get_transaction_hash("verifyBatchTxHash") - |> handle_transaction_hash(hash_to_id, next_id, l1_transactions, true) - - # Associate every transaction from batch with the batch number - l2_transactions_append = - l2_transaction_hashes - |> Kernel.||([]) - |> Enum.map(fn l2_transaction_hash -> - %{ - batch_number: number, - hash: l2_transaction_hash - } - end) - - batch = %{ - number: number, - timestamp: timestamp, - l2_transactions_count: Enum.count(l2_transactions_append), - global_exit_root: global_exit_root, - acc_input_hash: acc_input_hash, - state_root: state_root, - sequence_id: sequence_id, - verify_id: verify_id - } - - {[batch | batches], l2_transactions ++ l2_transactions_append, l1_transactions, next_id, hash_to_id} - end) - - # Update batches list, L1 transactions list and L2 transaction list - {:ok, _} = - Chain.import(%{ - polygon_zkevm_lifecycle_transactions: %{params: l1_transactions_to_import}, - polygon_zkevm_transaction_batches: %{params: batches_to_import}, - polygon_zkevm_batch_transactions: %{params: l2_transactions_to_import}, - timeout: :infinity - }) - - last_batch = - batches_to_import - |> Enum.max_by(& &1.number, fn -> nil end) - - if last_batch do - Instrumenter.set_latest_batch(last_batch.number, last_batch.timestamp) - end - - confirmed_batches = - Enum.filter(batches_to_import, fn batch -> not is_nil(batch.sequence_id) and batch.sequence_id > 0 end) - - # Publish update for open batches Views in BS app with the new confirmed batches - if not Enum.empty?(confirmed_batches) do - Publisher.broadcast([{:zkevm_confirmed_batches, confirmed_batches}], :realtime) - end - end - - defp perform_jsonrpc_requests(batch_start, batch_end, json_rpc_named_arguments, ignore_numbers) do - # For every batch from batch_start to batch_end request the batch info - requests = - batch_start - |> Range.new(batch_end, 1) - |> Enum.reject(fn batch_number -> - if Enum.member?(ignore_numbers, batch_number) do - Logger.warning("The batch #{batch_number} will be ignored.") - true - else - false - end - end) - |> Enum.map(fn batch_number -> - EthereumJSONRPC.request(%{ - id: batch_number, - method: "zkevm_getBatchByNumber", - params: [integer_to_quantity(batch_number), false] - }) - end) - - if requests == [] do - {:ok, []} - else - error_message = - &"Cannot call zkevm_getBatchByNumber for the batch range #{batch_start}..#{batch_end}. Error: #{inspect(&1)}" - - Helper.repeated_call(&json_rpc/2, [requests, json_rpc_named_arguments], error_message, 3) - end - end - - defp fetch_latest_batch_numbers(json_rpc_named_arguments) do - requests = [ - EthereumJSONRPC.request(%{id: 0, method: "zkevm_batchNumber", params: []}), - EthereumJSONRPC.request(%{id: 1, method: "zkevm_virtualBatchNumber", params: []}), - EthereumJSONRPC.request(%{id: 2, method: "zkevm_verifiedBatchNumber", params: []}) - ] - - error_message = &"Cannot call zkevm_batchNumber. Error: #{inspect(&1)}" - - {:ok, responses} = Helper.repeated_call(&json_rpc/2, [requests, json_rpc_named_arguments], error_message, 3) - - latest_batch_number = - Enum.find_value(responses, fn resp -> if resp.id == 0, do: quantity_to_integer(resp.result) end) - - virtual_batch_number = - Enum.find_value(responses, fn resp -> if resp.id == 1, do: quantity_to_integer(resp.result) end) - - verified_batch_number = - Enum.find_value(responses, fn resp -> if resp.id == 2, do: quantity_to_integer(resp.result) end) - - {latest_batch_number, virtual_batch_number, verified_batch_number} - end - - defp get_transaction_hash(result, type) do - case Map.get(result, type) do - "0x" <> transaction_hash -> transaction_hash - nil -> @zero_hash - end - end - - defp handle_transaction_hash(encoded_transaction_hash, hash_to_id, next_id, l1_transactions, is_verify) do - if encoded_transaction_hash != @zero_hash do - transaction_hash = Base.decode16!(encoded_transaction_hash, case: :mixed) - - id = Map.get(hash_to_id, transaction_hash) - - if is_nil(id) do - {next_id, [%{id: next_id, hash: transaction_hash, is_verify: is_verify} | l1_transactions], next_id + 1, - Map.put(hash_to_id, transaction_hash, next_id)} - else - {id, l1_transactions, next_id, hash_to_id} - end - else - {nil, l1_transactions, next_id, hash_to_id} - end - end -end diff --git a/apps/indexer/lib/indexer/fetcher/rollup_l1_reorg_monitor.ex b/apps/indexer/lib/indexer/fetcher/rollup_l1_reorg_monitor.ex index 426fb6a9a911..157ea0d75691 100644 --- a/apps/indexer/lib/indexer/fetcher/rollup_l1_reorg_monitor.ex +++ b/apps/indexer/lib/indexer/fetcher/rollup_l1_reorg_monitor.ex @@ -14,8 +14,8 @@ defmodule Indexer.Fetcher.RollupL1ReorgMonitor do require Logger alias Explorer.Chain.Cache.LatestL1BlockNumber - alias Explorer.Chain.RollupReorgMonitorQueue alias Indexer.Helper + alias Indexer.RollupReorgMonitorQueue @fetcher_name :rollup_l1_reorg_monitor @start_recheck_period_seconds 3 @@ -32,11 +32,6 @@ defmodule Indexer.Fetcher.RollupL1ReorgMonitor do Indexer.Fetcher.Optimism.WithdrawalEvent ] - :polygon_zkevm -> - [ - Indexer.Fetcher.PolygonZkevm.BridgeL1 - ] - :scroll -> [ Indexer.Fetcher.Scroll.Batch, @@ -163,7 +158,7 @@ defmodule Indexer.Fetcher.RollupL1ReorgMonitor do if latest < prev_latest do Logger.warning("Reorg detected: previous latest block ##{prev_latest}, current latest block ##{latest}.") - Enum.each(modules, &RollupReorgMonitorQueue.reorg_block_push(latest, &1)) + Enum.each(modules, &RollupReorgMonitorQueue.push(latest, &1)) end Process.send_after(self(), :reorg_monitor, block_check_interval) diff --git a/apps/indexer/lib/indexer/fetcher/scroll/batch.ex b/apps/indexer/lib/indexer/fetcher/scroll/batch.ex index ba1f02263ea6..fdbee99be7fe 100644 --- a/apps/indexer/lib/indexer/fetcher/scroll/batch.ex +++ b/apps/indexer/lib/indexer/fetcher/scroll/batch.ex @@ -26,12 +26,12 @@ defmodule Indexer.Fetcher.Scroll.Batch do alias EthereumJSONRPC.Logs alias Explorer.{Chain, Repo} alias Explorer.Chain.Block.Range, as: BlockRange - alias Explorer.Chain.RollupReorgMonitorQueue alias Explorer.Chain.Scroll.{Batch, BatchBundle, Reader} alias Indexer.Fetcher.RollupL1ReorgMonitor alias Indexer.Fetcher.Scroll.Helper, as: ScrollHelper alias Indexer.Helper alias Indexer.Prometheus.Instrumenter + alias Indexer.RollupReorgMonitorQueue # 32-byte signature of the event CommitBatch(uint256 indexed batchIndex, bytes32 indexed batchHash) @commit_batch_event "0x2c32d4ae151744d0bf0b9464a3e897a1d17ed2f1af71f7c9a75f12ce0d28238f" @@ -245,7 +245,7 @@ defmodule Indexer.Fetcher.Scroll.Batch do ) end - reorg_block = RollupReorgMonitorQueue.reorg_block_pop(__MODULE__) + reorg_block = RollupReorgMonitorQueue.pop(__MODULE__) if !is_nil(reorg_block) && reorg_block > 0 do reorg_handle(reorg_block) diff --git a/apps/indexer/lib/indexer/fetcher/scroll/bridge.ex b/apps/indexer/lib/indexer/fetcher/scroll/bridge.ex index 9b37d023e598..78e83f4ba22f 100644 --- a/apps/indexer/lib/indexer/fetcher/scroll/bridge.ex +++ b/apps/indexer/lib/indexer/fetcher/scroll/bridge.ex @@ -15,9 +15,9 @@ defmodule Indexer.Fetcher.Scroll.Bridge do alias EthereumJSONRPC.Logs alias Explorer.Chain - alias Explorer.Chain.RollupReorgMonitorQueue alias Indexer.Fetcher.Scroll.BridgeL1 alias Indexer.Helper, as: IndexerHelper + alias Indexer.RollupReorgMonitorQueue # 32-byte signature of the event SentMessage(address indexed sender, address indexed target, uint256 value, uint256 messageNonce, uint256 gasLimit, bytes message) @sent_message_event "0x104371f3b442861a2a7b82a070afbbaab748bb13757bf47769e170e37809ec1e" @@ -108,7 +108,7 @@ defmodule Indexer.Fetcher.Scroll.Bridge do ) end - reorg_block = RollupReorgMonitorQueue.reorg_block_pop(module) + reorg_block = RollupReorgMonitorQueue.pop(module) if !is_nil(reorg_block) && reorg_block > 0 do # credo:disable-for-next-line Credo.Check.Refactor.Nesting diff --git a/apps/indexer/lib/indexer/fetcher/scroll/bridge_l2.ex b/apps/indexer/lib/indexer/fetcher/scroll/bridge_l2.ex index 74504bbdeeab..8da454cba29e 100644 --- a/apps/indexer/lib/indexer/fetcher/scroll/bridge_l2.ex +++ b/apps/indexer/lib/indexer/fetcher/scroll/bridge_l2.ex @@ -21,11 +21,11 @@ defmodule Indexer.Fetcher.Scroll.BridgeL2 do import Ecto.Query - alias Explorer.Chain.RollupReorgMonitorQueue alias Explorer.Chain.Scroll.{Bridge, Reader} alias Explorer.Repo alias Indexer.Fetcher.Scroll.Bridge, as: BridgeFetcher alias Indexer.Helper + alias Indexer.RollupReorgMonitorQueue @fetcher_name :scroll_bridge_l2 @@ -150,6 +150,6 @@ defmodule Indexer.Fetcher.Scroll.BridgeL2 do ) end - RollupReorgMonitorQueue.reorg_block_push(reorg_block, __MODULE__) + RollupReorgMonitorQueue.push(reorg_block, __MODULE__) end end diff --git a/apps/indexer/lib/indexer/fetcher/shibarium/l1.ex b/apps/indexer/lib/indexer/fetcher/shibarium/l1.ex index 9fece6bc1a89..4e13d9667860 100644 --- a/apps/indexer/lib/indexer/fetcher/shibarium/l1.ex +++ b/apps/indexer/lib/indexer/fetcher/shibarium/l1.ex @@ -24,10 +24,10 @@ defmodule Indexer.Fetcher.Shibarium.L1 do only: [calc_operation_hash: 5, prepare_insert_items: 2, recalculate_cached_count: 0] alias Explorer.{Chain, Repo} - alias Explorer.Chain.RollupReorgMonitorQueue alias Explorer.Chain.Shibarium.Bridge alias Indexer.Fetcher.RollupL1ReorgMonitor alias Indexer.Helper + alias Indexer.RollupReorgMonitorQueue alias Indexer.Transform.Addresses @block_check_interval_range_size 100 @@ -135,7 +135,7 @@ defmodule Indexer.Fetcher.Shibarium.L1 do {last_l1_block_number, last_l1_transaction_hash} <- get_last_l1_item(), {:start_block_valid, true} <- {:start_block_valid, start_block <= last_l1_block_number || last_l1_block_number == 0}, - json_rpc_named_arguments = json_rpc_named_arguments(rpc), + json_rpc_named_arguments = Helper.json_rpc_named_arguments(rpc), {:ok, last_l1_transaction} <- Helper.get_transaction_by_hash(last_l1_transaction_hash, json_rpc_named_arguments), {:l1_transaction_not_found, false} <- @@ -281,7 +281,7 @@ defmodule Indexer.Fetcher.Shibarium.L1 do ) end - reorg_block = RollupReorgMonitorQueue.reorg_block_pop(__MODULE__) + reorg_block = RollupReorgMonitorQueue.pop(__MODULE__) if !is_nil(reorg_block) && reorg_block > 0 do reorg_handle(reorg_block) @@ -559,21 +559,6 @@ defmodule Indexer.Fetcher.Shibarium.L1 do ) end - defp json_rpc_named_arguments(rpc_url) do - [ - transport: EthereumJSONRPC.HTTP, - transport_options: [ - http: EthereumJSONRPC.HTTP.Tesla, - urls: [rpc_url], - http_options: [ - recv_timeout: :timer.minutes(10), - timeout: :timer.minutes(10), - pool: :ethereum_jsonrpc - ] - ] - ] - end - defp prepare_operations(events, json_rpc_named_arguments) do timestamps = events diff --git a/apps/indexer/lib/indexer/fetcher/transaction_action.ex b/apps/indexer/lib/indexer/fetcher/transaction_action.ex deleted file mode 100644 index a979b3a38a4f..000000000000 --- a/apps/indexer/lib/indexer/fetcher/transaction_action.ex +++ /dev/null @@ -1,297 +0,0 @@ -defmodule Indexer.Fetcher.TransactionAction do - @moduledoc """ - Fetches information about transaction actions. - """ - - use GenServer - use Indexer.Fetcher - - require Logger - - import Ecto.Query, - only: [ - from: 2 - ] - - import Explorer.Helper, only: [parse_integer: 1] - - alias Explorer.{Chain, Repo} - alias Explorer.Chain.{Block, BlockNumberHelper, Log, TransactionAction} - alias Explorer.Chain.Cache.Counters.LastFetchedCounter - alias Indexer.Transform.{Addresses, TransactionActions} - - @stage_first_block "transaction_action_first_block" - @stage_next_block "transaction_action_next_block" - @stage_last_block "transaction_action_last_block" - - defstruct first_block: nil, next_block: nil, last_block: nil, protocols: [], task: nil, pid: nil - - def child_spec([init_arguments]) do - child_spec([init_arguments, []]) - end - - def child_spec([_init_arguments, _gen_server_options] = start_link_arguments) do - default = %{ - id: __MODULE__, - start: {__MODULE__, :start_link, start_link_arguments} - } - - Supervisor.child_spec(default, restart: :transient) - end - - def start_link(arguments, gen_server_options \\ []) do - GenServer.start_link(__MODULE__, arguments, gen_server_options) - end - - @impl GenServer - def init(opts) when is_list(opts) do - opts = - Application.get_all_env(:indexer)[__MODULE__] - |> Keyword.merge(opts) - - first_block = Keyword.get(opts, :reindex_first_block) - last_block = Keyword.get(opts, :reindex_last_block) - - cond do - !is_nil(first_block) and !is_nil(last_block) -> - init_fetching(opts, first_block, last_block) - - is_nil(first_block) and !is_nil(last_block) -> - {:stop, "Please, specify the first block of the block range for #{__MODULE__}."} - - !is_nil(first_block) and is_nil(last_block) -> - {:stop, "Please, specify the last block of the block range for #{__MODULE__}."} - - true -> - :ignore - end - end - - @impl true - def handle_continue({opts, first_block, last_block}, _state) do - logger_metadata = Logger.metadata() - Logger.metadata(fetcher: :transaction_action) - - max_block_number = Block.fetch_max_block_number() - - if last_block > max_block_number do - Logger.warning( - "Note, that the last block number (#{last_block}) provided to #{__MODULE__} exceeds max block number available in DB (#{max_block_number})." - ) - end - - supported_protocols = - TransactionAction.supported_protocols() - |> Enum.map(&Atom.to_string(&1)) - - protocols = - opts - |> Keyword.get(:reindex_protocols, "") - |> String.trim() - |> String.split(",") - |> Enum.map(&String.trim(&1)) - |> Enum.filter(&Enum.member?(supported_protocols, &1)) - - next_block = get_next_block(first_block, last_block, protocols) - - state = - %__MODULE__{ - first_block: first_block, - next_block: next_block, - last_block: last_block, - protocols: protocols - } - |> run_fetch() - - Logger.reset_metadata(logger_metadata) - - {:noreply, state} - end - - @impl GenServer - def handle_info(:fetch, %__MODULE__{} = state) do - task = Task.Supervisor.async_nolink(Indexer.Fetcher.TransactionAction.TaskSupervisor, fn -> task(state) end) - {:noreply, %__MODULE__{state | task: task}} - end - - def handle_info(:stop_server, %__MODULE__{} = state) do - {:stop, :normal, state} - end - - def handle_info({ref, _result}, %__MODULE__{task: %Task{ref: ref}} = state) do - Process.demonitor(ref, [:flush]) - {:noreply, %__MODULE__{state | task: nil}} - end - - def handle_info( - {:DOWN, ref, :process, pid, reason}, - %__MODULE__{task: %Task{pid: pid, ref: ref}} = state - ) do - if reason === :normal do - {:noreply, %__MODULE__{state | task: nil}} - else - logger_metadata = Logger.metadata() - Logger.metadata(fetcher: :transaction_action) - Logger.error(fn -> "Transaction action fetcher task exited due to #{inspect(reason)}. Rerunning..." end) - Logger.reset_metadata(logger_metadata) - {:noreply, run_fetch(%__MODULE__{state | next_block: get_stage_block(@stage_next_block)})} - end - end - - defp run_fetch(%__MODULE__{} = state) do - pid = self() - Process.send_after(pid, :fetch, 3000, []) - %__MODULE__{state | task: nil, pid: pid} - end - - defp task( - %__MODULE__{ - first_block: first_block, - next_block: next_block, - last_block: last_block, - protocols: protocols, - pid: pid - } = _state - ) do - logger_metadata = Logger.metadata() - Logger.metadata(fetcher: :transaction_action) - - block_range = Range.new(next_block, first_block, -1) - block_range_init_length = last_block - first_block + 1 - - for block_number <- block_range do - query = - from( - log in Log, - inner_join: b in Block, - on: b.hash == log.block_hash and b.consensus == true, - where: log.block_number == ^block_number, - select: log - ) - - %{transaction_actions: transaction_actions} = - query - |> Repo.all(timeout: :infinity) - |> TransactionActions.parse(protocols) - - addresses = - Addresses.extract_addresses(%{ - transaction_actions: transaction_actions - }) - - transaction_actions_with_data = - Enum.map(transaction_actions, fn action -> - Map.put(action, :data, Map.delete(action.data, :block_number)) - end) - - {:ok, _} = - Chain.import(%{ - addresses: %{params: addresses, on_conflict: :nothing}, - transaction_actions: %{params: transaction_actions_with_data}, - timeout: :infinity - }) - - blocks_processed = last_block - block_number + 1 - - progress_percentage = - blocks_processed - |> Decimal.div(block_range_init_length) - |> Decimal.mult(100) - |> Decimal.round(2) - |> Decimal.to_string() - - next_block_new = BlockNumberHelper.previous_block_number(block_number) - - Logger.info( - "Block #{block_number} handled successfully. Progress: #{progress_percentage}%. Initial block range: #{first_block}..#{last_block}." <> - " Actions found: #{Enum.count(transaction_actions_with_data)}." <> - if(next_block_new >= first_block, do: " Remaining block range: #{first_block}..#{next_block_new}", else: "") - ) - - if block_number == next_block do - {:ok, _} = - LastFetchedCounter.upsert(%{ - counter_type: @stage_first_block, - value: first_block - }) - - {:ok, _} = - LastFetchedCounter.upsert(%{ - counter_type: @stage_last_block, - value: last_block - }) - end - - {:ok, _} = - LastFetchedCounter.upsert(%{ - counter_type: @stage_next_block, - value: next_block_new - }) - end - - Process.send(pid, :stop_server, []) - - Logger.reset_metadata(logger_metadata) - - :ok - end - - defp init_fetching(opts, first_block, last_block) do - first_block = parse_integer(first_block) - last_block = parse_integer(last_block) - - if is_nil(first_block) or is_nil(last_block) or first_block <= 0 or last_block <= 0 or first_block > last_block do - {:stop, "Correct block range must be provided to #{__MODULE__}."} - else - {:ok, %{}, {:continue, {opts, first_block, last_block}}} - end - end - - defp get_next_block(first_block, last_block, protocols) do - first_block_from_stage = get_stage_block(@stage_first_block) - last_block_from_stage = get_stage_block(@stage_last_block) - - {stage_first_block, stage_next_block, stage_last_block} = - if first_block_from_stage == 0 or last_block_from_stage == 0 do - {first_block, last_block, last_block} - else - {first_block_from_stage, get_stage_block(@stage_next_block), last_block_from_stage} - end - - next_block = - if Decimal.eq?(stage_first_block, first_block) and Decimal.eq?(stage_last_block, last_block) do - stage_next_block - else - last_block - end - - if next_block < first_block do - Logger.warning( - "It seems #{__MODULE__} already finished work for the block range #{first_block}..#{last_block} and " <> - if(Enum.empty?(protocols), - do: "all supported protocols.", - else: "the following protocols: #{Enum.join(protocols, ", ")}." - ) <> - " To run it again for a different block range, please change the range through environment variables." - ) - else - Logger.info( - "Running #{__MODULE__} for the block range #{first_block}..#{next_block} and " <> - if(Enum.empty?(protocols), - do: "all supported protocols.", - else: "the following protocols: #{Enum.join(protocols, ", ")}." - ) <> if(next_block < last_block, do: " Initial block range: #{first_block}..#{last_block}.", else: "") - ) - end - - next_block - end - - defp get_stage_block(type) do - type - |> LastFetchedCounter.get() - |> Decimal.to_integer() - rescue - _e in Ecto.NoResultsError -> 0 - end -end diff --git a/apps/indexer/lib/indexer/helper.ex b/apps/indexer/lib/indexer/helper.ex index 01e9c1e1b6f4..4270574fc88a 100644 --- a/apps/indexer/lib/indexer/helper.ex +++ b/apps/indexer/lib/indexer/helper.ex @@ -226,12 +226,17 @@ defmodule Indexer.Helper do Forms JSON RPC named arguments for the given RPC URL. """ @spec json_rpc_named_arguments(binary()) :: list() - def json_rpc_named_arguments(rpc_url) do + def json_rpc_named_arguments(rpc_url) when is_binary(rpc_url) do + normalized_rpc_url = + rpc_url + |> trim_url() + |> validate_rpc_url!() + [ transport: EthereumJSONRPC.HTTP, transport_options: [ http: EthereumJSONRPC.HTTP.Tesla, - urls: [rpc_url], + urls: [normalized_rpc_url], http_options: [ recv_timeout: :timer.minutes(10), timeout: :timer.minutes(10), @@ -241,6 +246,11 @@ defmodule Indexer.Helper do ] end + def json_rpc_named_arguments(_rpc_url), do: raise(ArgumentError, "RPC URL must be a non-empty string") + + defp validate_rpc_url!(""), do: raise(ArgumentError, "RPC URL must be a non-empty string") + defp validate_rpc_url!(rpc_url), do: rpc_url + @doc """ Splits a given range into chunks of the specified size. diff --git a/apps/indexer/lib/indexer/pending_transactions_sanitizer.ex b/apps/indexer/lib/indexer/pending_transactions_sanitizer.ex index c7c239d59b64..50f9a9b80345 100644 --- a/apps/indexer/lib/indexer/pending_transactions_sanitizer.ex +++ b/apps/indexer/lib/indexer/pending_transactions_sanitizer.ex @@ -61,7 +61,7 @@ defmodule Indexer.PendingTransactionsSanitizer do {:noreply, state} end - defp sanitize_pending_transactions(json_rpc_named_arguments) do + def sanitize_pending_transactions(json_rpc_named_arguments) do receipts_batch_size = Application.get_env(:indexer, :receipts_batch_size) pending_transactions_list_from_db = Transaction.pending_transactions_list() id_to_params = id_to_params(pending_transactions_list_from_db) @@ -169,24 +169,33 @@ defmodule Indexer.PendingTransactionsSanitizer do end defp invalidate_block(block, pending_transaction, transaction) do + transaction_info = to_elixir(transaction) + + pending_transaction + |> Transaction.changeset(%{ + gas_price: transaction_info["effectiveGasPrice"] || pending_transaction.gas_price, + created_contract_address_hash: transaction_info["contractAddress"] + }) + |> Changeset.put_change(:cumulative_gas_used, transaction_info["cumulativeGasUsed"]) + |> Changeset.put_change(:gas_used, transaction_info["gasUsed"]) + |> Changeset.put_change(:index, transaction_info["transactionIndex"]) + |> Changeset.put_change(:status, transaction_info["status"]) + |> Changeset.put_change(:block_number, block.number) + |> Changeset.put_change(:block_hash, block.hash) + |> Changeset.put_change(:block_timestamp, block.timestamp) + |> Changeset.put_change(:block_consensus, block.consensus) + |> Repo.update() + |> case do + {:ok, _result} -> + :ok + + {:error, error} -> + Logger.error("Failed to update pending transaction with hash #{pending_transaction.hash}: #{inspect(error)}") + end + if block.consensus do Block.set_refetch_needed(block.number) else - transaction_info = to_elixir(transaction) - - changeset = - pending_transaction - |> Transaction.changeset() - |> Changeset.put_change(:cumulative_gas_used, transaction_info["cumulativeGasUsed"]) - |> Changeset.put_change(:gas_used, transaction_info["gasUsed"]) - |> Changeset.put_change(:index, transaction_info["transactionIndex"]) - |> Changeset.put_change(:block_number, block.number) - |> Changeset.put_change(:block_hash, block.hash) - |> Changeset.put_change(:block_timestamp, block.timestamp) - |> Changeset.put_change(:block_consensus, false) - - Repo.update(changeset) - Logger.debug( "Pending transaction with hash #{pending_transaction.hash} assigned to block ##{block.number} with hash #{block.hash}" ) diff --git a/apps/indexer/lib/indexer/prometheus/instrumenter.ex b/apps/indexer/lib/indexer/prometheus/instrumenter.ex index 9de7269f938b..ee733654f2de 100644 --- a/apps/indexer/lib/indexer/prometheus/instrumenter.ex +++ b/apps/indexer/lib/indexer/prometheus/instrumenter.ex @@ -8,7 +8,7 @@ defmodule Indexer.Prometheus.Instrumenter do alias EthereumJSONRPC.Utility.RangesHelper - @rollups [:arbitrum, :zksync, :optimism, :polygon_zkevm, :scroll] + @rollups [:arbitrum, :zksync, :optimism, :scroll] @histogram [ name: :block_full_processing_duration_microseconds, diff --git a/apps/indexer/lib/indexer/rollup_reorg_monitor_queue.ex b/apps/indexer/lib/indexer/rollup_reorg_monitor_queue.ex new file mode 100644 index 000000000000..bba5fe79646c --- /dev/null +++ b/apps/indexer/lib/indexer/rollup_reorg_monitor_queue.ex @@ -0,0 +1,14 @@ +defmodule Indexer.RollupReorgMonitorQueue do + @moduledoc """ + Queue registry used by rollup reorg monitoring fetchers. + """ + + use Explorer.ModuleQueueRegistry + + # sobelow_skip ["DOS.BinToAtom"] + @impl true + @spec table_name(module()) :: atom() + def table_name(module) do + :"#{module}#{:_reorgs}" + end +end diff --git a/apps/indexer/lib/indexer/supervisor.ex b/apps/indexer/lib/indexer/supervisor.ex index ee3c802cb8b5..da48465bb5bb 100644 --- a/apps/indexer/lib/indexer/supervisor.ex +++ b/apps/indexer/lib/indexer/supervisor.ex @@ -54,7 +54,6 @@ defmodule Indexer.Supervisor do TokenCountersUpdater, TokenTotalSupplyUpdater, TokenUpdater, - TransactionAction, UncleBlock, Withdrawal } @@ -152,7 +151,6 @@ defmodule Indexer.Supervisor do {TokenInstanceSanitize.Supervisor, [[memory_monitor: memory_monitor]]}, configure(TokenInstanceSanitizeERC721, [[memory_monitor: memory_monitor]]), configure(TokenInstanceSanitizeERC1155, [[memory_monitor: memory_monitor]]), - configure(TransactionAction.Supervisor, [[memory_monitor: memory_monitor]]), {ContractCode.Supervisor, [[json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor]]}, {TokenBalanceHistorical.Supervisor, @@ -225,20 +223,12 @@ defmodule Indexer.Supervisor do [ [memory_monitor: memory_monitor] ]}, - configure(Indexer.Fetcher.PolygonZkevm.BridgeL1.Supervisor, [[memory_monitor: memory_monitor]]), - configure(Indexer.Fetcher.PolygonZkevm.BridgeL1Tokens.Supervisor, [[memory_monitor: memory_monitor]]), - configure(Indexer.Fetcher.PolygonZkevm.BridgeL2.Supervisor, [ - [json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor] - ]), configure(ZkSyncTransactionBatch.Supervisor, [ [json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor] ]), configure(ZkSyncBatchesStatusTracker.Supervisor, [ [json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor] ]), - configure(Indexer.Fetcher.PolygonZkevm.TransactionBatch.Supervisor, [ - [json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor] - ]), configure(ArbitrumTrackingMessagesOnL1.Supervisor, [ [json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor] ]), diff --git a/apps/indexer/lib/indexer/transform/addresses.ex b/apps/indexer/lib/indexer/transform/addresses.ex index 737418e5c29f..8fe3893f04bf 100644 --- a/apps/indexer/lib/indexer/transform/addresses.ex +++ b/apps/indexer/lib/indexer/transform/addresses.ex @@ -49,8 +49,6 @@ defmodule Indexer.Transform.Addresses do """ use Utils.CompileTimeEnvHelper, chain_type: [:explorer, :chain_type] - alias Indexer.Helper - @entity_to_address_map %{ address_coin_balances: [ [ @@ -181,11 +179,6 @@ defmodule Indexer.Transform.Addresses do %{from: :address_hash, to: :hash} ] ], - polygon_zkevm_bridge_operations: [ - [ - %{from: :l2_token_address, to: :hash} - ] - ], celo_election_rewards: [ [ %{from: :account_address_hash, to: :hash} @@ -516,11 +509,6 @@ defmodule Indexer.Transform.Addresses do required(:adapter_address_hash) => String.t() } ], - optional(:transaction_actions) => [ - %{ - required(:data) => map() - } - ], optional(:mint_transfers) => [ %{ required(:from_address_hash) => String.t(), @@ -540,11 +528,6 @@ defmodule Indexer.Transform.Addresses do required(:block_number) => non_neg_integer() } ], - optional(:polygon_zkevm_bridge_operations) => [ - %{ - optional(:l2_token_address) => String.t() - } - ], optional(:celo_election_rewards) => [ %{ required(:account_address_hash) => String.t() @@ -578,18 +561,7 @@ defmodule Indexer.Transform.Addresses do (entity_items = Map.get(fetched_data, entity_key)) != nil, do: extract_addresses_from_collection(entity_items, entity_fields, state) - transaction_actions_addresses = - fetched_data - |> Map.get(:transaction_actions, []) - |> Enum.map(fn transaction_action -> - transaction_action.data - |> Map.get(:block_number) - |> find_transaction_action_addresses(transaction_action.data) - end) - |> List.flatten() - addresses - |> Enum.concat(transaction_actions_addresses) |> List.flatten() |> merge_addresses() end @@ -599,25 +571,6 @@ defmodule Indexer.Transform.Addresses do def extract_addresses_from_item(item, fields, state), do: Enum.flat_map(fields, &extract_fields(&1, item, state)) - defp find_transaction_action_addresses(block_number, data, accumulator \\ []) - - defp find_transaction_action_addresses(block_number, data, accumulator) when is_map(data) or is_list(data) do - Enum.reduce(data, accumulator, fn - {_, value}, acc -> find_transaction_action_addresses(block_number, value, acc) - value, acc -> find_transaction_action_addresses(block_number, value, acc) - end) - end - - defp find_transaction_action_addresses(block_number, value, accumulator) when is_binary(value) do - if Helper.address_correct?(value) do - [%{:fetched_coin_balance_block_number => block_number, :hash => value} | accumulator] - else - accumulator - end - end - - defp find_transaction_action_addresses(_block_number, _value, accumulator), do: accumulator - def merge_addresses(addresses) when is_list(addresses) do addresses |> Enum.group_by(fn address -> address.hash end) diff --git a/apps/indexer/lib/indexer/transform/celo/transaction_token_transfers.ex b/apps/indexer/lib/indexer/transform/celo/transaction_token_transfers.ex index 040fb4aaf38b..5276401695aa 100644 --- a/apps/indexer/lib/indexer/transform/celo/transaction_token_transfers.ex +++ b/apps/indexer/lib/indexer/transform/celo/transaction_token_transfers.ex @@ -20,7 +20,7 @@ defmodule Indexer.Transform.Celo.TransactionTokenTransfers do ] alias Explorer.Chain.Cache.CeloCoreContracts - alias Explorer.Chain.Hash + alias Explorer.Chain.{Hash, InternalTransaction} alias Indexer.Fetcher.TokenTotalSupplyUpdater @token_type "ERC-20" @transaction_buffer_size 20_000 @@ -97,6 +97,8 @@ defmodule Indexer.Transform.Celo.TransactionTokenTransfers do not Map.has_key?(internal_transaction, :error) && (not Map.has_key?(internal_transaction, :call_type) || internal_transaction.call_type != "delegatecall") end) + |> InternalTransaction.preload_transaction() + |> InternalTransaction.preload_addresses() |> Enum.map(fn internal_transaction -> to_address_hash = Map.get(internal_transaction, :to_address_hash) || @@ -117,7 +119,7 @@ defmodule Indexer.Transform.Celo.TransactionTokenTransfers do token_contract_address_hash: celo_token_address, token_ids: nil, token_type: @token_type, - transaction_hash: internal_transaction.transaction_hash + transaction_hash: internal_transaction.transaction.hash } end) diff --git a/apps/indexer/lib/indexer/transform/polygon_zkevm/bridge.ex b/apps/indexer/lib/indexer/transform/polygon_zkevm/bridge.ex deleted file mode 100644 index ba14abac1031..000000000000 --- a/apps/indexer/lib/indexer/transform/polygon_zkevm/bridge.ex +++ /dev/null @@ -1,115 +0,0 @@ -defmodule Indexer.Transform.PolygonZkevm.Bridge do - @moduledoc """ - Helper functions for transforming data for Polygon zkEVM Bridge operations. - """ - - require Logger - - import Indexer.Fetcher.PolygonZkevm.Bridge, - only: [filter_bridge_events: 2, prepare_operations: 8] - - alias Indexer.Fetcher.PolygonZkevm.{BridgeL1, BridgeL2} - alias Indexer.Helper - - @doc """ - Returns a list of operations given a list of blocks and logs. - """ - @spec parse(list(), list()) :: list() - def parse(blocks, logs) do - prev_metadata = Logger.metadata() - Logger.metadata(fetcher: :polygon_zkevm_bridge_l2_realtime) - - items = - with false <- is_nil(Application.get_env(:indexer, BridgeL2)[:start_block]), - false <- Application.get_env(:explorer, :chain_type) != :polygon_zkevm, - rpc_l1 = Application.get_all_env(:indexer)[BridgeL1][:rpc], - {:rpc_l1_undefined, false} <- {:rpc_l1_undefined, is_nil(rpc_l1)}, - rollup_network_id_l1 = Application.get_all_env(:indexer)[BridgeL1][:rollup_network_id_l1], - rollup_network_id_l2 = Application.get_all_env(:indexer)[BridgeL2][:rollup_network_id_l2], - rollup_index_l1 = Application.get_all_env(:indexer)[BridgeL1][:rollup_index_l1], - rollup_index_l2 = Application.get_all_env(:indexer)[BridgeL2][:rollup_index_l2], - {:rollup_network_id_l1_is_valid, true} <- - {:rollup_network_id_l1_is_valid, !is_nil(rollup_network_id_l1) and rollup_network_id_l1 >= 0}, - {:rollup_network_id_l2_is_valid, true} <- - {:rollup_network_id_l2_is_valid, !is_nil(rollup_network_id_l2) and rollup_network_id_l2 > 0}, - {:rollup_index_l2_is_valid, true} <- {:rollup_index_l2_is_valid, !is_nil(rollup_index_l2)}, - bridge_contract = Application.get_env(:indexer, BridgeL2)[:bridge_contract], - {:bridge_contract_address_is_valid, true} <- - {:bridge_contract_address_is_valid, Helper.address_correct?(bridge_contract)} do - bridge_contract = String.downcase(bridge_contract) - - block_numbers = Enum.map(blocks, fn block -> block.number end) - start_block = Enum.min(block_numbers) - end_block = Enum.max(block_numbers) - - Helper.log_blocks_chunk_handling(start_block, end_block, start_block, end_block, nil, :L2) - - json_rpc_named_arguments_l1 = Helper.json_rpc_named_arguments(rpc_l1) - - block_to_timestamp = Enum.reduce(blocks, %{}, fn block, acc -> Map.put(acc, block.number, block.timestamp) end) - - items = - logs - |> filter_bridge_events(bridge_contract) - |> prepare_operations( - rollup_network_id_l1, - rollup_network_id_l2, - rollup_index_l1, - rollup_index_l2, - nil, - json_rpc_named_arguments_l1, - block_to_timestamp - ) - - Helper.log_blocks_chunk_handling( - start_block, - end_block, - start_block, - end_block, - "#{Enum.count(items)} L2 operation(s)", - :L2 - ) - - items - else - true -> - [] - - {:rpc_l1_undefined, true} -> - Logger.error("L1 RPC URL is not defined. Cannot use #{__MODULE__} for parsing logs.") - [] - - {:rollup_network_id_l1_is_valid, false} -> - Logger.error( - "Invalid network ID for L1. Please, check INDEXER_POLYGON_ZKEVM_L1_BRIDGE_NETWORK_ID env variable." - ) - - [] - - {:rollup_network_id_l2_is_valid, false} -> - Logger.error( - "Invalid network ID for L2. Please, check INDEXER_POLYGON_ZKEVM_L2_BRIDGE_NETWORK_ID env variable." - ) - - [] - - {:rollup_index_l2_is_valid, false} -> - Logger.error( - "Rollup index is undefined for L2. Please, check INDEXER_POLYGON_ZKEVM_L2_BRIDGE_ROLLUP_INDEX env variable." - ) - - [] - - {:bridge_contract_address_is_valid, false} -> - Logger.error( - "PolygonZkEVMBridge contract address is invalid or not defined. Cannot use #{__MODULE__} for parsing logs." - ) - - [] - end - - Logger.reset_metadata(prev_metadata) - - items - end -end diff --git a/apps/indexer/lib/indexer/transform/transaction_actions.ex b/apps/indexer/lib/indexer/transform/transaction_actions.ex deleted file mode 100644 index 42c0ee541e00..000000000000 --- a/apps/indexer/lib/indexer/transform/transaction_actions.ex +++ /dev/null @@ -1,992 +0,0 @@ -defmodule Indexer.Transform.TransactionActions do - @moduledoc """ - Helper functions for transforming data for transaction actions. - """ - - require Logger - - import Ecto.Query, only: [from: 2] - import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0] - import Explorer.Helper, only: [decode_data: 2] - - alias Explorer.Chain.{Address, Hash, Token, TransactionAction} - alias Explorer.Chain.Cache.{ChainId, TransactionActionTokensData, TransactionActionUniswapPools} - alias Explorer.Repo - alias Indexer.Helper, as: IndexerHelper - - @mainnet 1 - @goerli 5 - @optimism 10 - @polygon 137 - @base_mainnet 8453 - @base_goerli 84531 - # @gnosis 100 - - @uniswap_v3_factory_abi [ - %{ - "inputs" => [ - %{"internalType" => "address", "name" => "", "type" => "address"}, - %{"internalType" => "address", "name" => "", "type" => "address"}, - %{"internalType" => "uint24", "name" => "", "type" => "uint24"} - ], - "name" => "getPool", - "outputs" => [%{"internalType" => "address", "name" => "", "type" => "address"}], - "stateMutability" => "view", - "type" => "function" - } - ] - @uniswap_v3_pool_abi [ - %{ - "inputs" => [], - "name" => "fee", - "outputs" => [%{"internalType" => "uint24", "name" => "", "type" => "uint24"}], - "stateMutability" => "view", - "type" => "function" - }, - %{ - "inputs" => [], - "name" => "token0", - "outputs" => [%{"internalType" => "address", "name" => "", "type" => "address"}], - "stateMutability" => "view", - "type" => "function" - }, - %{ - "inputs" => [], - "name" => "token1", - "outputs" => [%{"internalType" => "address", "name" => "", "type" => "address"}], - "stateMutability" => "view", - "type" => "function" - } - ] - @erc20_abi [ - %{ - "constant" => true, - "inputs" => [], - "name" => "symbol", - "outputs" => [%{"name" => "", "type" => "string"}], - "payable" => false, - "stateMutability" => "view", - "type" => "function" - }, - %{ - "constant" => true, - "inputs" => [], - "name" => "decimals", - "outputs" => [%{"name" => "", "type" => "uint8"}], - "payable" => false, - "stateMutability" => "view", - "type" => "function" - } - ] - - # 32-byte signature of the event Borrow(address indexed reserve, address user, address indexed onBehalfOf, uint256 amount, uint8 interestRateMode, uint256 borrowRate, uint16 indexed referralCode) - @aave_v3_borrow_event "0xb3d084820fb1a9decffb176436bd02558d15fac9b0ddfed8c465bc7359d7dce0" - - # 32-byte signature of the event Supply(address indexed reserve, address user, address indexed onBehalfOf, uint256 amount, uint16 indexed referralCode) - @aave_v3_supply_event "0x2b627736bca15cd5381dcf80b0bf11fd197d01a037c52b927a881a10fb73ba61" - - # 32-byte signature of the event Withdraw(address indexed reserve, address indexed user, address indexed to, uint256 amount) - @aave_v3_withdraw_event "0x3115d1449a7b732c986cba18244e897a450f61e1bb8d589cd2e69e6c8924f9f7" - - # 32-byte signature of the event Repay(address indexed reserve, address indexed user, address indexed repayer, uint256 amount, bool useATokens) - @aave_v3_repay_event "0xa534c8dbe71f871f9f3530e97a74601fea17b426cae02e1c5aee42c96c784051" - - # 32-byte signature of the event FlashLoan(address indexed target, address initiator, address indexed asset, uint256 amount, uint8 interestRateMode, uint256 premium, uint16 indexed referralCode) - @aave_v3_flash_loan_event "0xefefaba5e921573100900a3ad9cf29f222d995fb3b6045797eaea7521bd8d6f0" - - # 32-byte signature of the event ReserveUsedAsCollateralEnabled(address indexed reserve, address indexed user) - @aave_v3_enable_collateral_event "0x00058a56ea94653cdf4f152d227ace22d4c00ad99e2a43f58cb7d9e3feb295f2" - - # 32-byte signature of the event ReserveUsedAsCollateralDisabled(address indexed reserve, address indexed user) - @aave_v3_disable_collateral_event "0x44c58d81365b66dd4b1a7f36c25aa97b8c71c361ee4937adc1a00000227db5dd" - - # 32-byte signature of the event LiquidationCall(address indexed collateralAsset, address indexed debtAsset, address indexed user, uint256 debtToCover, uint256 liquidatedCollateralAmount, address liquidator, bool receiveAToken) - @aave_v3_liquidation_call_event "0xe413a321e8681d831f4dbccbca790d2952b56f977908e45be37335533e005286" - - # 32-byte signature of the event Transfer(address indexed from, address indexed to, uint256 indexed tokenId) - @uniswap_v3_transfer_nft_event "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" - - # 32-byte signature of the event Mint(address sender, address indexed owner, int24 indexed tickLower, int24 indexed tickUpper, uint128 amount, uint256 amount0, uint256 amount1) - @uniswap_v3_mint_event "0x7a53080ba414158be7ec69b987b5fb7d07dee101fe85488f0853ae16239d0bde" - - # 32-byte signature of the event Burn(address indexed owner, int24 indexed tickLower, int24 indexed tickUpper, uint128 amount, uint256 amount0, uint256 amount1) - @uniswap_v3_burn_event "0x0c396cd989a39f4459b5fa1aed6a9a8dcdbc45908acfd67e028cd568da98982c" - - # 32-byte signature of the event Collect(address indexed owner, address recipient, int24 indexed tickLower, int24 indexed tickUpper, uint128 amount0, uint128 amount1) - @uniswap_v3_collect_event "0x70935338e69775456a85ddef226c395fb668b63fa0115f5f20610b388e6ca9c0" - - # 32-byte signature of the event Swap(address indexed sender, address indexed recipient, int256 amount0, int256 amount1, uint160 sqrtPriceX96, uint128 liquidity, int24 tick); - @uniswap_v3_swap_event "0xc42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67" - - # max number of token decimals - @decimals_max 0xFF - - @doc """ - Returns a list of transaction actions given a list of logs. - """ - def parse(logs, protocols_to_rewrite \\ nil) do - if Application.get_env(:indexer, Indexer.Fetcher.TransactionAction.Supervisor)[:enabled] do - actions = [] - - chain_id = ChainId.get_id() - - if not is_nil(protocols_to_rewrite) do - logs - |> logs_group_by_transactions() - |> clear_actions(protocols_to_rewrite) - end - - # create tokens cache if not exists - TransactionActionTokensData.create_cache_table() - - actions = parse_aave_v3(logs, actions, protocols_to_rewrite, chain_id) - actions = parse_uniswap_v3(logs, actions, protocols_to_rewrite, chain_id) - - %{transaction_actions: actions} - else - %{transaction_actions: []} - end - end - - defp parse_aave_v3(logs, actions, protocols_to_rewrite, chain_id) do - aave_v3_pool = Application.get_all_env(:indexer)[Indexer.Fetcher.TransactionAction][:aave_v3_pool] - - if not is_nil(aave_v3_pool) and - (is_nil(protocols_to_rewrite) or Enum.empty?(protocols_to_rewrite) or - Enum.member?(protocols_to_rewrite, "aave_v3")) do - logs - |> aave_filter_logs(String.downcase(aave_v3_pool)) - |> logs_group_by_transactions() - |> aave(actions, chain_id) - else - actions - end - end - - defp parse_uniswap_v3(logs, actions, protocols_to_rewrite, chain_id) do - if Enum.member?([@mainnet, @goerli, @optimism, @polygon, @base_mainnet, @base_goerli], chain_id) and - (is_nil(protocols_to_rewrite) or Enum.empty?(protocols_to_rewrite) or - Enum.member?(protocols_to_rewrite, "uniswap_v3")) do - uniswap_v3_positions_nft = - String.downcase( - Application.get_all_env(:indexer)[Indexer.Fetcher.TransactionAction][:uniswap_v3_nft_position_manager] - ) - - logs - |> uniswap_filter_logs(uniswap_v3_positions_nft) - |> logs_group_by_transactions() - |> uniswap(actions, chain_id, uniswap_v3_positions_nft) - else - actions - end - end - - defp aave_filter_logs(logs, pool_address) do - logs - |> Enum.filter(fn log -> - Enum.member?( - [ - @aave_v3_borrow_event, - @aave_v3_supply_event, - @aave_v3_withdraw_event, - @aave_v3_repay_event, - @aave_v3_flash_loan_event, - @aave_v3_enable_collateral_event, - @aave_v3_disable_collateral_event, - @aave_v3_liquidation_call_event - ], - sanitize_first_topic(log.first_topic) - ) && IndexerHelper.address_hash_to_string(log.address_hash, true) == pool_address - end) - end - - defp aave(logs_grouped, actions, chain_id) do - # iterate for each transaction - Enum.reduce(logs_grouped, actions, fn {_transaction_hash, transaction_logs}, actions_acc -> - # go through actions - Enum.reduce(transaction_logs, actions_acc, fn log, acc -> - acc ++ aave_handle_action(log, chain_id) - end) - end) - end - - # credo:disable-for-next-line /Complexity/ - defp aave_handle_action(log, chain_id) do - case sanitize_first_topic(log.first_topic) do - @aave_v3_borrow_event -> - # this is Borrow event - aave_handle_borrow_event(log, chain_id) - - @aave_v3_supply_event -> - # this is Supply event - aave_handle_supply_event(log, chain_id) - - @aave_v3_withdraw_event -> - # this is Withdraw event - aave_handle_withdraw_event(log, chain_id) - - @aave_v3_repay_event -> - # this is Repay event - aave_handle_repay_event(log, chain_id) - - @aave_v3_flash_loan_event -> - # this is FlashLoan event - aave_handle_flash_loan_event(log, chain_id) - - @aave_v3_enable_collateral_event -> - # this is ReserveUsedAsCollateralEnabled event - aave_handle_event("enable_collateral", log, log.second_topic, chain_id) - - @aave_v3_disable_collateral_event -> - # this is ReserveUsedAsCollateralDisabled event - aave_handle_event("disable_collateral", log, log.second_topic, chain_id) - - @aave_v3_liquidation_call_event -> - # this is LiquidationCall event - aave_handle_liquidation_call_event(log, chain_id) - - _ -> - [] - end - end - - defp aave_handle_borrow_event(log, chain_id) do - [_user, amount, _interest_rate_mode, _borrow_rate] = - decode_data(log.data, [:address, {:uint, 256}, {:uint, 8}, {:uint, 256}]) - - aave_handle_event("borrow", amount, log, log.second_topic, chain_id) - end - - defp aave_handle_supply_event(log, chain_id) do - [_user, amount] = decode_data(log.data, [:address, {:uint, 256}]) - - aave_handle_event("supply", amount, log, log.second_topic, chain_id) - end - - defp aave_handle_withdraw_event(log, chain_id) do - [amount] = decode_data(log.data, [{:uint, 256}]) - - aave_handle_event("withdraw", amount, log, log.second_topic, chain_id) - end - - defp aave_handle_repay_event(log, chain_id) do - [amount, _use_a_tokens] = decode_data(log.data, [{:uint, 256}, :bool]) - - aave_handle_event("repay", amount, log, log.second_topic, chain_id) - end - - defp aave_handle_flash_loan_event(log, chain_id) do - [_initiator, amount, _interest_rate_mode, _premium] = - decode_data(log.data, [:address, {:uint, 256}, {:uint, 8}, {:uint, 256}]) - - aave_handle_event("flash_loan", amount, log, log.third_topic, chain_id) - end - - defp aave_handle_liquidation_call_event(log, chain_id) do - [debt_amount, collateral_amount, _liquidator, _receive_a_token] = - decode_data(log.data, [{:uint, 256}, {:uint, 256}, :address, :bool]) - - debt_address = - log.third_topic - |> IndexerHelper.log_topic_to_string() - |> truncate_address_hash() - - collateral_address = - log.second_topic - |> IndexerHelper.log_topic_to_string() - |> truncate_address_hash() - - case get_token_data([debt_address, collateral_address]) do - false -> - [] - - token_data -> - debt_decimals = token_data[debt_address].decimals - collateral_decimals = token_data[collateral_address].decimals - - [ - %{ - hash: log.transaction_hash, - protocol: "aave_v3", - data: %{ - debt_amount: fractional(Decimal.new(debt_amount), Decimal.new(debt_decimals)), - debt_symbol: clarify_token_symbol(token_data[debt_address].symbol, chain_id), - debt_address: Address.checksum(debt_address), - collateral_amount: fractional(Decimal.new(collateral_amount), Decimal.new(collateral_decimals)), - collateral_symbol: clarify_token_symbol(token_data[collateral_address].symbol, chain_id), - collateral_address: Address.checksum(collateral_address), - block_number: log.block_number - }, - type: "liquidation_call", - log_index: log.index - } - ] - end - end - - defp aave_handle_event(type, amount, log, address_topic, chain_id) - when type in ["borrow", "supply", "withdraw", "repay", "flash_loan"] do - address = - address_topic - |> IndexerHelper.log_topic_to_string() - |> truncate_address_hash() - - case get_token_data([address]) do - false -> - [] - - token_data -> - decimals = token_data[address].decimals - - [ - %{ - hash: log.transaction_hash, - protocol: "aave_v3", - data: %{ - amount: fractional(Decimal.new(amount), Decimal.new(decimals)), - symbol: clarify_token_symbol(token_data[address].symbol, chain_id), - address: Address.checksum(address), - block_number: log.block_number - }, - type: type, - log_index: log.index - } - ] - end - end - - defp aave_handle_event(type, log, address_topic, chain_id) when type in ["enable_collateral", "disable_collateral"] do - address = - address_topic - |> IndexerHelper.log_topic_to_string() - |> truncate_address_hash() - - case get_token_data([address]) do - false -> - [] - - token_data -> - [ - %{ - hash: log.transaction_hash, - protocol: "aave_v3", - data: %{ - symbol: clarify_token_symbol(token_data[address].symbol, chain_id), - address: Address.checksum(address), - block_number: log.block_number - }, - type: type, - log_index: log.index - } - ] - end - end - - defp uniswap(logs_grouped, actions, chain_id, uniswap_v3_positions_nft) do - # create a list of UniswapV3Pool legitimate contracts - legitimate = uniswap_legitimate_pools(logs_grouped) - - # iterate for each transaction - Enum.reduce(logs_grouped, actions, fn {transaction_hash, transaction_logs}, actions_acc -> - # trying to find `mint_nft` actions - actions_acc = - uniswap_handle_mint_nft_actions(transaction_hash, transaction_logs, actions_acc, uniswap_v3_positions_nft) - - # go through other actions - Enum.reduce(transaction_logs, actions_acc, fn log, acc -> - acc ++ uniswap_handle_action(log, legitimate, chain_id) - end) - end) - end - - defp uniswap_filter_logs(logs, uniswap_v3_positions_nft) do - logs - |> Enum.filter(fn log -> - first_topic = sanitize_first_topic(log.first_topic) - - Enum.member?( - [ - @uniswap_v3_mint_event, - @uniswap_v3_burn_event, - @uniswap_v3_collect_event, - @uniswap_v3_swap_event - ], - first_topic - ) || - (first_topic == @uniswap_v3_transfer_nft_event && - IndexerHelper.address_hash_to_string(log.address_hash, true) == uniswap_v3_positions_nft) - end) - end - - defp uniswap_handle_action(log, legitimate, chain_id) do - first_topic = sanitize_first_topic(log.first_topic) - - with false <- first_topic == @uniswap_v3_transfer_nft_event, - # check UniswapV3Pool contract is legitimate - pool_address <- IndexerHelper.address_hash_to_string(log.address_hash, true), - false <- is_nil(legitimate[pool_address]), - false <- Enum.empty?(legitimate[pool_address]), - # this is legitimate uniswap pool, so handle this event - token_address <- legitimate[pool_address], - token_data <- get_token_data(token_address), - false <- token_data === false do - case first_topic do - @uniswap_v3_mint_event -> - # this is Mint event - uniswap_handle_mint_event(log, token_address, token_data, chain_id) - - @uniswap_v3_burn_event -> - # this is Burn event - uniswap_handle_burn_event(log, token_address, token_data, chain_id) - - @uniswap_v3_collect_event -> - # this is Collect event - uniswap_handle_collect_event(log, token_address, token_data, chain_id) - - @uniswap_v3_swap_event -> - # this is Swap event - uniswap_handle_swap_event(log, token_address, token_data, chain_id) - - _ -> - [] - end - else - _ -> [] - end - end - - defp uniswap_handle_mint_nft_actions(transaction_hash, transaction_logs, actions_acc, uniswap_v3_positions_nft) do - first_log = Enum.at(transaction_logs, 0) - - local_acc = - transaction_logs - |> Enum.reduce(%{}, fn log, acc -> - if sanitize_first_topic(log.first_topic) == @uniswap_v3_transfer_nft_event do - # This is Transfer event for NFT - from = - log.second_topic - |> IndexerHelper.log_topic_to_string() - |> truncate_address_hash() - - # credo:disable-for-next-line - if from == burn_address_hash_string() do - to = - log.third_topic - |> IndexerHelper.log_topic_to_string() - |> truncate_address_hash() - - [token_id] = - log.fourth_topic - |> IndexerHelper.log_topic_to_string() - |> decode_data([{:uint, 256}]) - - mint_nft_ids = Map.put_new(acc, to, %{ids: [], log_index: log.index}) - - Map.put(mint_nft_ids, to, %{ - ids: Enum.reverse([to_string(token_id) | Enum.reverse(mint_nft_ids[to].ids)]), - log_index: mint_nft_ids[to].log_index - }) - else - acc - end - else - acc - end - end) - |> Enum.reduce([], fn {to, %{ids: ids, log_index: log_index}}, acc -> - action = %{ - hash: transaction_hash, - protocol: "uniswap_v3", - data: %{ - name: "Uniswap V3: Positions NFT", - symbol: "UNI-V3-POS", - address: uniswap_v3_positions_nft, - to: Address.checksum(to), - ids: ids, - block_number: first_log.block_number - }, - type: "mint_nft", - log_index: log_index - } - - [action | acc] - end) - |> Enum.reverse() - - actions_acc ++ local_acc - end - - defp uniswap_handle_burn_event(log, token_address, token_data, chain_id) do - [_amount, amount0, amount1] = decode_data(log.data, [{:uint, 128}, {:uint, 256}, {:uint, 256}]) - - uniswap_handle_event("burn", amount0, amount1, log, token_address, token_data, chain_id) - end - - defp uniswap_handle_collect_event(log, token_address, token_data, chain_id) do - [_recipient, amount0, amount1] = decode_data(log.data, [:address, {:uint, 128}, {:uint, 128}]) - - uniswap_handle_event("collect", amount0, amount1, log, token_address, token_data, chain_id) - end - - defp uniswap_handle_mint_event(log, token_address, token_data, chain_id) do - [_sender, _amount, amount0, amount1] = decode_data(log.data, [:address, {:uint, 128}, {:uint, 256}, {:uint, 256}]) - - uniswap_handle_event("mint", amount0, amount1, log, token_address, token_data, chain_id) - end - - defp uniswap_handle_swap_event(log, token_address, token_data, chain_id) do - [amount0, amount1, _sqrt_price_x96, _liquidity, _tick] = - decode_data(log.data, [{:int, 256}, {:int, 256}, {:uint, 160}, {:uint, 128}, {:int, 24}]) - - uniswap_handle_event("swap", amount0, amount1, log, token_address, token_data, chain_id) - end - - defp uniswap_handle_swap_amounts(log, amount0, amount1, symbol0, symbol1, address0, address1) do - cond do - String.first(amount0) === "-" and String.first(amount1) !== "-" -> - {amount1, symbol1, address1, String.slice(amount0, 1..-1//1), symbol0, address0, false} - - String.first(amount1) === "-" and String.first(amount0) !== "-" -> - {amount0, symbol0, address0, String.slice(amount1, 1..-1//1), symbol1, address1, false} - - amount1 === "0" and String.first(amount0) !== "-" -> - {amount0, symbol0, address0, amount1, symbol1, address1, false} - - true -> - Logger.error( - "TransactionActions: Invalid Swap event in transaction #{log.transaction_hash}. Log index: #{log.index}. amount0 = #{amount0}, amount1 = #{amount1}" - ) - - {amount0, symbol0, address0, amount1, symbol1, address1, true} - end - end - - defp uniswap_handle_event(type, amount0, amount1, log, token_address, token_data, chain_id) do - address0 = Enum.at(token_address, 0) - decimals0 = token_data[address0].decimals - symbol0 = clarify_token_symbol(token_data[address0].symbol, chain_id) - address1 = Enum.at(token_address, 1) - decimals1 = token_data[address1].decimals - symbol1 = clarify_token_symbol(token_data[address1].symbol, chain_id) - - amount0 = fractional(Decimal.new(amount0), Decimal.new(decimals0)) - amount1 = fractional(Decimal.new(amount1), Decimal.new(decimals1)) - - {new_amount0, new_symbol0, new_address0, new_amount1, new_symbol1, new_address1, is_error} = - if type == "swap" do - uniswap_handle_swap_amounts(log, amount0, amount1, symbol0, symbol1, address0, address1) - else - {amount0, symbol0, address0, amount1, symbol1, address1, false} - end - - if is_error do - [] - else - [ - %{ - hash: log.transaction_hash, - protocol: "uniswap_v3", - data: %{ - amount0: new_amount0, - symbol0: new_symbol0, - address0: Address.checksum(new_address0), - amount1: new_amount1, - symbol1: new_symbol1, - address1: Address.checksum(new_address1), - block_number: log.block_number - }, - type: type, - log_index: log.index - } - ] - end - end - - defp uniswap_legitimate_pools(logs_grouped) do - TransactionActionUniswapPools.create_cache_table() - - {pools_to_request, pools_cached} = - logs_grouped - |> Enum.reduce(%{}, fn {_transaction_hash, transaction_logs}, addresses_acc -> - transaction_logs - |> Enum.filter(fn log -> - sanitize_first_topic(log.first_topic) != @uniswap_v3_transfer_nft_event - end) - |> Enum.reduce(addresses_acc, fn log, acc -> - pool_address = IndexerHelper.address_hash_to_string(log.address_hash, true) - Map.put(acc, pool_address, true) - end) - end) - |> Enum.reduce({[], %{}}, fn {pool_address, _}, {to_request, cached} -> - value_from_cache = TransactionActionUniswapPools.fetch_from_cache(pool_address) - - if is_nil(value_from_cache) do - {[pool_address | to_request], cached} - else - {to_request, Map.put(cached, pool_address, value_from_cache)} - end - end) - - req_resp = uniswap_request_tokens_and_fees(pools_to_request) - - case uniswap_request_get_pools(req_resp) do - {requests_get_pool, responses_get_pool} -> - requests_get_pool - |> Enum.zip(responses_get_pool) - |> Enum.reduce(%{}, fn {request, {_status, response} = _resp}, acc -> - value = uniswap_pool_is_legitimate(request, response) - TransactionActionUniswapPools.put_to_cache(request.pool_address, value) - Map.put(acc, request.pool_address, value) - end) - |> Map.merge(pools_cached) - - _ -> - pools_cached - end - end - - defp uniswap_pool_is_legitimate(request, response) do - response = - case response do - [item] -> item - items -> items - end - - if request.pool_address == String.downcase(response) do - [token0, token1, _] = request.args - [token0, token1] - else - [] - end - end - - defp uniswap_request_get_pools({requests_tokens_and_fees, responses_tokens_and_fees}) do - uniswap_v3_factory = Application.get_all_env(:indexer)[Indexer.Fetcher.TransactionAction][:uniswap_v3_factory] - - requests_get_pool = - requests_tokens_and_fees - |> Enum.zip(responses_tokens_and_fees) - |> Enum.reduce(%{}, fn {request, {status, response} = _resp}, acc -> - if status == :ok do - response = parse_response(response) - - acc = Map.put_new(acc, request.contract_address, %{token0: "", token1: "", fee: ""}) - item = Map.put(acc[request.contract_address], atomized_key(request.method_id), response) - Map.put(acc, request.contract_address, item) - else - acc - end - end) - |> Enum.map(fn {pool_address, pool} -> - token0 = - if IndexerHelper.address_correct?(pool.token0), - do: String.downcase(pool.token0), - else: burn_address_hash_string() - - token1 = - if IndexerHelper.address_correct?(pool.token1), - do: String.downcase(pool.token1), - else: burn_address_hash_string() - - fee = if pool.fee == "", do: 0, else: pool.fee - - # we will call getPool(token0, token1, fee) public getter - %{ - pool_address: pool_address, - contract_address: uniswap_v3_factory, - method_id: "1698ee82", - args: [token0, token1, fee] - } - end) - - {responses_get_pool, error_messages} = read_contracts(requests_get_pool, @uniswap_v3_factory_abi) - - if not Enum.empty?(error_messages) or Enum.count(requests_get_pool) != Enum.count(responses_get_pool) do - Logger.error( - "TransactionActions: Cannot read Uniswap V3 Factory contract getPool public getter. Error messages: #{Enum.join(error_messages, ", ")}. Requests: #{inspect(requests_get_pool)}" - ) - - false - else - {requests_get_pool, responses_get_pool} - end - end - - defp uniswap_request_tokens_and_fees(pools) do - requests = - pools - |> Enum.map(fn pool_address -> - # we will call token0(), token1(), fee() public getters - Enum.map(["0dfe1681", "d21220a7", "ddca3f43"], fn method_id -> - %{ - contract_address: pool_address, - method_id: method_id, - args: [] - } - end) - end) - |> List.flatten() - - {responses, error_messages} = read_contracts(requests, @uniswap_v3_pool_abi) - - if not Enum.empty?(error_messages) do - incorrect_pools = uniswap_get_incorrect_pools(requests, responses) - - Logger.warning( - "TransactionActions: Cannot read Uniswap V3 Pool contract public getters for some pools: token0(), token1(), fee(). Error messages: #{Enum.join(error_messages, ", ")}. Incorrect pools: #{Enum.join(incorrect_pools, ", ")} - they will be marked as not legitimate." - ) - end - - {requests, responses} - end - - defp uniswap_get_incorrect_pools(requests, responses) do - responses - |> Enum.with_index() - |> Enum.reduce([], fn {{status, _}, i}, acc -> - if status == :error do - pool_address = Enum.at(requests, i)[:contract_address] - TransactionActionUniswapPools.put_to_cache(pool_address, []) - [pool_address | acc] - else - acc - end - end) - |> Enum.reverse() - end - - defp atomized_key("token0"), do: :token0 - defp atomized_key("token1"), do: :token1 - defp atomized_key("fee"), do: :fee - defp atomized_key("getPool"), do: :getPool - defp atomized_key("symbol"), do: :symbol - defp atomized_key("decimals"), do: :decimals - defp atomized_key("0dfe1681"), do: :token0 - defp atomized_key("d21220a7"), do: :token1 - defp atomized_key("ddca3f43"), do: :fee - defp atomized_key("1698ee82"), do: :getPool - defp atomized_key("95d89b41"), do: :symbol - defp atomized_key("313ce567"), do: :decimals - - defp clarify_token_symbol(symbol, chain_id) do - if symbol == "WETH" && Enum.member?([@mainnet, @goerli, @optimism], chain_id) do - "Ether" - else - symbol - end - end - - defp clear_actions(logs_grouped, protocols_to_clear) do - logs_grouped - |> Enum.each(fn {transaction_hash, _} -> - query = - if Enum.empty?(protocols_to_clear) do - from(ta in TransactionAction, where: ta.hash == ^transaction_hash) - else - from(ta in TransactionAction, where: ta.hash == ^transaction_hash and ta.protocol in ^protocols_to_clear) - end - - Repo.delete_all(query) - end) - end - - defp fractional(%Decimal{} = amount, %Decimal{} = decimals) do - amount.sign - |> Decimal.new(amount.coef, amount.exp - Decimal.to_integer(decimals)) - |> Decimal.normalize() - |> Decimal.to_string(:normal) - end - - defp get_token_data(token_addresses) do - # first, we're trying to read token data from the cache. - # if the cache is empty, we read that from DB. - # if tokens are not in the cache, nor in the DB, read them through RPC. - token_data = - token_addresses - |> get_token_data_from_cache() - |> get_token_data_from_db() - |> get_token_data_from_rpc() - - if Enum.any?(token_data, fn {_, token} -> - Map.get(token, :symbol, "") == "" or Map.get(token, :decimals) > @decimals_max - end) do - false - else - token_data - end - end - - defp get_token_data_from_cache(token_addresses) do - token_addresses - |> Enum.reduce(%{}, fn address, acc -> - Map.put( - acc, - address, - TransactionActionTokensData.fetch_from_cache(address) - ) - end) - end - - defp get_token_data_from_db(token_data_from_cache) do - # a list of token addresses which we should select from the database - select_tokens_from_db = - token_data_from_cache - |> Enum.reduce([], fn {address, data}, acc -> - if is_nil(data.symbol) or is_nil(data.decimals) do - [address | acc] - else - acc - end - end) - |> Enum.reverse() - - if Enum.empty?(select_tokens_from_db) do - # we don't need to read data from db, so will use the cache - token_data_from_cache - else - # try to read token symbols and decimals from the database and then save to the cache - query = - from( - t in Token, - where: t.contract_address_hash in ^select_tokens_from_db, - select: {t.symbol, t.decimals, t.contract_address_hash} - ) - - query - |> Repo.all() - |> Enum.reduce(token_data_from_cache, fn {symbol, decimals, contract_address_hash}, token_data_acc -> - contract_address_hash = String.downcase(Hash.to_string(contract_address_hash)) - - symbol = parse_symbol(symbol, contract_address_hash, token_data_acc) - - decimals = parse_decimals(decimals, contract_address_hash, token_data_acc) - - new_data = %{symbol: symbol, decimals: decimals} - - put_to_cache(contract_address_hash, new_data) - - Map.put(token_data_acc, contract_address_hash, new_data) - end) - end - end - - defp parse_symbol(symbol, contract_address_hash, token_data_acc) do - if is_nil(symbol) or symbol == "" do - # if db field is empty, take it from the cache - token_data_acc[contract_address_hash].symbol - else - symbol - end - end - - defp parse_decimals(decimals, contract_address_hash, token_data_acc) do - if is_nil(decimals) do - # if db field is empty, take it from the cache - token_data_acc[contract_address_hash].decimals - else - decimals - end - end - - defp put_to_cache(contract_address_hash, new_data) do - if Map.get(new_data, :decimals, 0) <= @decimals_max do - TransactionActionTokensData.put_to_cache(contract_address_hash, new_data) - end - end - - defp get_token_data_from_rpc(token_data) do - token_addresses = - token_data - |> Enum.reduce([], fn {address, data}, acc -> - if is_nil(data.symbol) or data.symbol == "" or is_nil(data.decimals) do - [address | acc] - else - acc - end - end) - |> Enum.reverse() - - {requests, responses} = get_token_data_request_symbol_decimals(token_addresses) - - requests - |> Enum.zip(responses) - |> Enum.reduce(token_data, fn {request, {status, response} = _resp}, token_data_acc -> - if status == :ok do - response = parse_response(response) - - data = token_data_acc[request.contract_address] - - new_data = get_new_data(data, request, response) - - put_to_cache(request.contract_address, new_data) - - Map.put(token_data_acc, request.contract_address, new_data) - else - token_data_acc - end - end) - end - - defp parse_response(response) do - case response do - [item] -> item - items -> items - end - end - - defp get_new_data(data, request, response) do - if atomized_key(request.method_id) == :symbol do - %{data | symbol: response} - else - %{data | decimals: response} - end - end - - defp get_token_data_request_symbol_decimals(token_addresses) do - requests = - token_addresses - |> Enum.map(fn address -> - # we will call symbol() and decimals() public getters - Enum.map(["95d89b41", "313ce567"], fn method_id -> - %{ - contract_address: address, - method_id: method_id, - args: [] - } - end) - end) - |> List.flatten() - - {responses, error_messages} = read_contracts(requests, @erc20_abi) - - if not Enum.empty?(error_messages) or Enum.count(requests) != Enum.count(responses) do - Logger.warning( - "TransactionActions: Cannot read symbol and decimals of an ERC-20 token contract. Error messages: #{Enum.join(error_messages, ", ")}. Addresses: #{Enum.join(token_addresses, ", ")}" - ) - end - - {requests, responses} - end - - defp logs_group_by_transactions(logs) do - logs - |> Enum.group_by(& &1.transaction_hash) - end - - defp read_contracts(requests, abi) do - max_retries = Application.get_env(:explorer, :token_functions_reader_max_retries) - json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments) - - IndexerHelper.read_contracts_with_retries(requests, abi, json_rpc_named_arguments, max_retries) - end - - defp sanitize_first_topic(first_topic) do - if is_nil(first_topic), do: "", else: String.downcase(IndexerHelper.log_topic_to_string(first_topic)) - end - - defp truncate_address_hash(nil), do: burn_address_hash_string() - - defp truncate_address_hash("0x000000000000000000000000" <> truncated_hash) do - "0x#{truncated_hash}" - end -end diff --git a/apps/indexer/mix.exs b/apps/indexer/mix.exs index ad555a46ea68..13c199a211cd 100644 --- a/apps/indexer/mix.exs +++ b/apps/indexer/mix.exs @@ -14,7 +14,7 @@ defmodule Indexer.MixProject do elixirc_paths: elixirc_paths(Mix.env()), lockfile: "../../mix.lock", start_permanent: Mix.env() == :prod, - version: "10.2.1", + version: "11.0.0", xref: [ exclude: [ Explorer.Chain.Optimism.Deposit, diff --git a/apps/indexer/test/indexer/fetcher/internal_transaction/delete_queue_test.exs b/apps/indexer/test/indexer/fetcher/internal_transaction/delete_queue_test.exs index 94177fe185ed..68654bdfea4f 100644 --- a/apps/indexer/test/indexer/fetcher/internal_transaction/delete_queue_test.exs +++ b/apps/indexer/test/indexer/fetcher/internal_transaction/delete_queue_test.exs @@ -46,7 +46,6 @@ defmodule Indexer.Fetcher.InternalTransaction.DeleteQueueTest do insert(:internal_transaction, transaction: transaction_1, index: 0, - block_hash: transaction_1.block_hash, block_number: fresh_block_number, transaction_index: transaction_1.index ) @@ -54,7 +53,6 @@ defmodule Indexer.Fetcher.InternalTransaction.DeleteQueueTest do insert(:internal_transaction, transaction: transaction_2, index: 0, - block_hash: transaction_2.block_hash, block_number: expired_block_number, transaction_index: transaction_2.index ) diff --git a/apps/indexer/test/indexer/fetcher/internal_transaction_test.exs b/apps/indexer/test/indexer/fetcher/internal_transaction_test.exs index f007bab73172..411ce5ba4506 100644 --- a/apps/indexer/test/indexer/fetcher/internal_transaction_test.exs +++ b/apps/indexer/test/indexer/fetcher/internal_transaction_test.exs @@ -330,166 +330,6 @@ defmodule Indexer.Fetcher.InternalTransactionTest do assert %{block_hash: ^block_hash} = Repo.get(PendingBlockOperation, block_hash) end - - test "set block refetch_needed=true on foreign_key_violation", %{ - json_rpc_named_arguments: json_rpc_named_arguments - } do - block = insert(:block) - transaction = :transaction |> insert() |> with_block(block) - block_number = block.number - insert(:pending_block_operation, block_hash: block.hash, block_number: block.number) - - if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do - case Keyword.fetch!(json_rpc_named_arguments, :variant) do - EthereumJSONRPC.Nethermind -> - EthereumJSONRPC.Mox - |> expect(:json_rpc, fn [%{id: id, method: "trace_replayBlockTransactions"}], _options -> - {:ok, - [ - %{ - id: id, - result: [ - %{ - "output" => "0x", - "stateDiff" => nil, - "trace" => [ - %{ - "action" => %{ - "callType" => "call", - "from" => "0xa931c862e662134b85e4dc4baf5c70cc9ba74db4", - "gas" => "0x8600", - "input" => "0xb118e2db0000000000000000000000000000000000000000000000000000000000000008", - "to" => "0x1469b17ebf82fedf56f04109e5207bdc4554288c", - "value" => "0x174876e800" - }, - "result" => %{"gasUsed" => "0x7d37", "output" => "0x"}, - "subtraces" => 1, - "traceAddress" => [], - "type" => "call" - }, - %{ - "action" => %{ - "callType" => "call", - "from" => "0xb37b428a7ddee91f39b26d79d23dc1c89e3e12a7", - "gas" => "0x32dcf", - "input" => "0x42dad49e", - "to" => "0xee4019030fb5c2b68c42105552c6268d56c6cbfe", - "value" => "0x0" - }, - "result" => %{ - "gasUsed" => "0xb08", - "output" => "0x" - }, - "subtraces" => 0, - "traceAddress" => [0], - "type" => "call" - } - ], - "transactionHash" => transaction.hash, - "vmTrace" => nil - }, - %{ - "output" => "0x", - "stateDiff" => nil, - "trace" => [ - %{ - "action" => %{ - "callType" => "call", - "from" => "0xa931c862e662134b85e4dc4baf5c70cc9ba74db4", - "gas" => "0x8600", - "input" => "0xb118e2db0000000000000000000000000000000000000000000000000000000000000008", - "to" => "0x1469b17ebf82fedf56f04109e5207bdc4554288c", - "value" => "0x174876e800" - }, - "result" => %{"gasUsed" => "0x7d37", "output" => "0x"}, - "subtraces" => 1, - "traceAddress" => [], - "type" => "call" - }, - %{ - "action" => %{ - "callType" => "call", - "from" => "0xb37b428a7ddee91f39b26d79d23dc1c89e3e12a7", - "gas" => "0x32dcf", - "input" => "0x42dad49e", - "to" => "0xee4019030fb5c2b68c42105552c6268d56c6cbfe", - "value" => "0x0" - }, - "result" => %{ - "gasUsed" => "0xb08", - "output" => "0x" - }, - "subtraces" => 0, - "traceAddress" => [0], - "type" => "call" - } - ], - "transactionHash" => transaction_hash(), - "vmTrace" => nil - } - ] - } - ]} - end) - - EthereumJSONRPC.Geth -> - EthereumJSONRPC.Mox - |> expect(:json_rpc, fn [%{id: id, method: "debug_traceTransaction"}], _options -> - {:ok, - [ - %{ - id: id, - result: [ - %{ - "blockNumber" => block.number, - "transactionIndex" => 0, - "transactionHash" => transaction.hash, - "index" => 0, - "traceAddress" => [], - "type" => "call", - "callType" => "call", - "from" => "0xa931c862e662134b85e4dc4baf5c70cc9ba74db4", - "to" => "0x1469b17ebf82fedf56f04109e5207bdc4554288c", - "gas" => "0x8600", - "gasUsed" => "0x7d37", - "input" => "0xb118e2db0000000000000000000000000000000000000000000000000000000000000008", - "output" => "0x", - "value" => "0x174876e800" - }, - %{ - "blockNumber" => block.number, - "transactionIndex" => 0, - "transactionHash" => transaction_hash(), - "index" => 0, - "traceAddress" => [], - "type" => "call", - "callType" => "call", - "from" => "0xa931c862e662134b85e4dc4baf5c70cc9ba74db4", - "to" => "0x1469b17ebf82fedf56f04109e5207bdc4554288c", - "gas" => "0x8600", - "gasUsed" => "0x7d37", - "input" => "0xb118e2db0000000000000000000000000000000000000000000000000000000000000008", - "output" => "0x", - "value" => "0x174876e800" - } - ] - } - ]} - end) - - variant_name -> - raise ArgumentError, "Unsupported variant name (#{variant_name})" - end - end - - logs = - capture_log(fn -> - assert {:retry, [^block_number]} = InternalTransaction.run([block_number], json_rpc_named_arguments) - end) - - assert %{consensus: true, refetch_needed: true} = Repo.reload(block) - assert logs =~ "foreign_key_violation on internal transactions import" - end end test "doesn't delete pending block operations after block import if no async process was requested", %{ diff --git a/apps/indexer/test/indexer/fetcher/multichain_search_db/counters_export_queue_test.exs b/apps/indexer/test/indexer/fetcher/multichain_search_db/counters_export_queue_test.exs new file mode 100644 index 000000000000..9499a25ada69 --- /dev/null +++ b/apps/indexer/test/indexer/fetcher/multichain_search_db/counters_export_queue_test.exs @@ -0,0 +1,231 @@ +defmodule Indexer.Fetcher.MultichainSearchDb.CountersExportQueueTest do + use ExUnit.Case + use Explorer.DataCase, async: false + + import ExUnit.CaptureLog, only: [capture_log: 1] + + alias Explorer.Chain.MultichainSearchDb.CountersExportQueue + alias Explorer.MicroserviceInterfaces.MultichainSearch + alias Explorer.TestHelper + alias Explorer.Repo + alias Indexer.Fetcher.MultichainSearchDb.CountersExportQueue, as: MultichainSearchDbCountersExportQueue + alias Plug.Conn + + @moduletag :capture_log + + setup do + start_supervised!({Task.Supervisor, name: Indexer.TaskSupervisor}) + + previous_supervisor_config = + Application.get_env(:indexer, MultichainSearchDbCountersExportQueue.Supervisor) + + Application.put_env(:indexer, MultichainSearchDbCountersExportQueue.Supervisor, disabled?: false) + + on_exit(fn -> + Application.put_env( + :indexer, + MultichainSearchDbCountersExportQueue.Supervisor, + previous_supervisor_config + ) + end) + + :ok + end + + describe "init/3" do + setup do + Application.put_env(:explorer, MultichainSearch, + service_url: "http://localhost:1234", + api_key: "12345", + counters_chunk_size: 1000 + ) + + on_exit(fn -> + Application.put_env(:explorer, MultichainSearch, service_url: nil, api_key: nil, counters_chunk_size: 1000) + end) + end + + test "initializes with data from the retry queue" do + counter_item_1 = + insert(:multichain_search_db_export_counters_queue, timestamp: counter_timestamp(1), data: counter_data("10")) + + counter_item_2 = + insert(:multichain_search_db_export_counters_queue, timestamp: counter_timestamp(2), data: counter_data("20")) + + counter_item_3 = + insert(:multichain_search_db_export_counters_queue, timestamp: counter_timestamp(3), data: counter_data("30")) + + reducer = fn data, acc -> [data | acc] end + + pid = + [] + |> MultichainSearchDbCountersExportQueue.Supervisor.child_spec() + |> ExUnit.Callbacks.start_supervised!() + + results = MultichainSearchDbCountersExportQueue.init([], reducer, nil) + + assert Enum.count(results) == 3 + + assert Enum.member?(results, %{ + timestamp: counter_item_1.timestamp, + counter_type: counter_item_1.counter_type, + data: counter_item_1.data + }) + + assert Enum.member?(results, %{ + timestamp: counter_item_2.timestamp, + counter_type: counter_item_2.counter_type, + data: counter_item_2.data + }) + + assert Enum.member?(results, %{ + timestamp: counter_item_3.timestamp, + counter_type: counter_item_3.counter_type, + data: counter_item_3.data + }) + + :timer.sleep(100) + GenServer.stop(pid) + end + end + + describe "run/2" do + setup do + bypass = Bypass.open() + + Application.put_env(:explorer, MultichainSearch, + service_url: "http://localhost:#{bypass.port}", + api_key: "12345", + counters_chunk_size: 1000 + ) + + on_exit(fn -> + Application.put_env(:explorer, MultichainSearch, service_url: nil, api_key: nil, counters_chunk_size: 1000) + Application.put_env(:tesla, :adapter, Explorer.Mock.TeslaAdapter) + Bypass.down(bypass) + end) + + {:ok, bypass: bypass} + end + + test "successfully processes multichain search db export counters retry queue data", %{bypass: bypass} do + export_data = [ + insert(:multichain_search_db_export_counters_queue, timestamp: counter_timestamp(1), data: counter_data("10")), + insert(:multichain_search_db_export_counters_queue, timestamp: counter_timestamp(2), data: counter_data("20")), + insert(:multichain_search_db_export_counters_queue, timestamp: counter_timestamp(3), data: counter_data("30")) + ] + + TestHelper.get_chain_id_mock() + + Application.put_env(:tesla, :adapter, Tesla.Adapter.Mint) + + Bypass.expect_once(bypass, "POST", "/api/v1/import:batch", fn conn -> + Conn.resp( + conn, + 200, + Jason.encode!(%{"status" => "ok"}) + ) + end) + + assert :ok = MultichainSearchDbCountersExportQueue.run(export_data, nil) + assert Repo.aggregate(CountersExportQueue, :count) == 0 + end + + test "returns {:retry, failed_data} on error where failed_data is only chunks that failed to export" do + Application.put_env(:explorer, MultichainSearch, + service_url: "http://localhost:1234", + api_key: "12345", + counters_chunk_size: 1 + ) + + on_exit(fn -> + Application.put_env(:explorer, MultichainSearch, service_url: nil, api_key: nil, counters_chunk_size: 1000) + end) + + counter_item_1 = + insert(:multichain_search_db_export_counters_queue, timestamp: counter_timestamp(1), data: counter_data("10")) + + counter_item_2 = + insert(:multichain_search_db_export_counters_queue, timestamp: counter_timestamp(2), data: counter_data("20")) + + counter_item_3 = + insert(:multichain_search_db_export_counters_queue, timestamp: counter_timestamp(3), data: counter_data("30")) + + counter_item_4 = + insert(:multichain_search_db_export_counters_queue, timestamp: counter_timestamp(4), data: counter_data("40")) + + counter_item_5 = + insert(:multichain_search_db_export_counters_queue, timestamp: counter_timestamp(5), data: counter_data("50")) + + export_data = [counter_item_1, counter_item_2, counter_item_3, counter_item_4, counter_item_5] + failed_timestamp_string = Integer.to_string(DateTime.to_unix(counter_item_4.timestamp)) + + TestHelper.get_chain_id_mock() + + tesla_expectations(failed_timestamp_string, 5) + + log = + capture_log(fn -> + assert {:retry, [item_to_retry]} = MultichainSearchDbCountersExportQueue.run(export_data, nil) + + assert item_to_retry.timestamp == counter_item_4.timestamp + assert item_to_retry.counter_type == counter_item_4.counter_type + assert item_to_retry.data == counter_item_4.data + end) + + assert Repo.aggregate(CountersExportQueue, :count) == 1 + assert [remaining_item] = Repo.all(CountersExportQueue) + assert remaining_item.retries_number == 1 + assert log =~ "Batch counters export attempt to the Multichain Search DB failed" + + TestHelper.get_chain_id_mock() + + tesla_expectations(failed_timestamp_string, 1) + + MultichainSearchDbCountersExportQueue.run([counter_item_4], nil) + + assert Repo.aggregate(CountersExportQueue, :count) == 1 + assert [remaining_item] = Repo.all(CountersExportQueue) + assert remaining_item.retries_number == 2 + + TestHelper.get_chain_id_mock() + + Tesla.Test.expect_tesla_call( + times: 1, + returns: fn %{url: "http://localhost:1234/api/v1/import:batch"}, _opts -> + {:ok, %Tesla.Env{status: 200, body: Jason.encode!(%{"status" => "ok"})}} + end + ) + + assert :ok = MultichainSearchDbCountersExportQueue.run([counter_item_4], nil) + assert Repo.aggregate(CountersExportQueue, :count) == 0 + end + end + + defp tesla_expectations(failed_timestamp_string, times) do + Tesla.Test.expect_tesla_call( + times: times, + returns: fn %{url: "http://localhost:1234/api/v1/import:batch", body: body}, _opts -> + case Jason.decode(body) do + {:ok, %{"counters" => [%{"timestamp" => ^failed_timestamp_string}]}} -> + {:ok, %Tesla.Env{status: 500, body: Jason.encode!(%{"code" => 0, "message" => "Error"})}} + + _ -> + {:ok, %Tesla.Env{status: 200, body: Jason.encode!(%{"status" => "ok"})}} + end + end + ) + end + + defp counter_timestamp(offset_in_seconds) do + DateTime.from_unix!(1_700_000_000 + offset_in_seconds) + end + + defp counter_data(value) do + %{ + "daily_transactions_number" => value, + "total_transactions_number" => Integer.to_string(String.to_integer(value) * 10), + "total_addresses_number" => Integer.to_string(String.to_integer(value) * 100) + } + end +end diff --git a/apps/indexer/test/indexer/fetcher/multichain_search_db/counters_fetcher_test.exs b/apps/indexer/test/indexer/fetcher/multichain_search_db/counters_fetcher_test.exs new file mode 100644 index 000000000000..95aebef3ef9f --- /dev/null +++ b/apps/indexer/test/indexer/fetcher/multichain_search_db/counters_fetcher_test.exs @@ -0,0 +1,135 @@ +defmodule Indexer.Fetcher.MultichainSearchDb.CountersFetcherTest do + use ExUnit.Case + use Explorer.DataCase, async: false + + import ExUnit.CaptureLog, only: [capture_log: 1] + + alias Explorer.Chain.MultichainSearchDb.CountersExportQueue + alias Explorer.Chain.Cache.Counters.LastFetchedCounter + alias Explorer.Chain.Transaction.History.{Historian, TransactionStats} + alias Explorer.MicroserviceInterfaces.MultichainSearch + alias Explorer.Repo + alias Indexer.Fetcher.MultichainSearchDb.CountersFetcher + + @moduletag :capture_log + + setup do + previous_multichain_config = Application.get_env(:explorer, MultichainSearch) + + Application.put_env(:explorer, MultichainSearch, + service_url: "http://localhost:1234", + api_key: "12345", + counters_chunk_size: 1000 + ) + + on_exit(fn -> + Application.put_env(:explorer, MultichainSearch, previous_multichain_config) + end) + + :ok + end + + describe "handle_info/2" do + test "waits until transaction stats are collected for today" do + today = Date.utc_today() + yesterday = Date.add(today, -1) + + :ok = put_last_save_records_date(yesterday) + + log = + capture_log(fn -> + assert {:noreply, %{}} = CountersFetcher.handle_info(:try_to_fetch_yesterday_counters, %{}) + end) + + assert Repo.aggregate(CountersExportQueue, :count) == 0 + refute_received :fetch_yesterday_counters + assert log =~ "Waiting for transaction stats to be collected for #{yesterday}" + end + + test "queues yesterday counters once transaction stats are ready" do + today = Date.utc_today() + yesterday = Date.add(today, -1) + yesterday_dt = DateTime.new!(yesterday, Time.new!(23, 59, 59, 0)) + + :ok = put_last_save_records_date(today) + + Repo.insert_all(TransactionStats, [ + %{date: yesterday, number_of_transactions: 7} + ]) + + insert(:address, inserted_at: DateTime.add(yesterday_dt, -3600, :second)) + insert(:address, inserted_at: DateTime.add(yesterday_dt, -1800, :second)) + insert(:address, inserted_at: DateTime.add(yesterday_dt, 2, :second)) + + past_block = insert(:block, timestamp: DateTime.add(yesterday_dt, -900, :second), consensus: true) + future_block = insert(:block, timestamp: DateTime.add(yesterday_dt, 2, :second), consensus: true) + non_consensus_block = insert(:block, timestamp: DateTime.add(yesterday_dt, -600, :second), consensus: false) + + insert(:transaction) + |> with_block(past_block, block_timestamp: past_block.timestamp, block_consensus: true, status: :ok) + + insert(:transaction) + |> with_block(past_block, block_timestamp: past_block.timestamp, block_consensus: true, status: :ok) + + insert(:transaction) + |> with_block(future_block, block_timestamp: future_block.timestamp, block_consensus: true, status: :ok) + + insert(:transaction, + block_hash: non_consensus_block.hash, + block_number: non_consensus_block.number, + block_timestamp: non_consensus_block.timestamp, + block_consensus: false, + cumulative_gas_used: 21_000, + gas_used: 21_000, + index: 0, + status: :ok + ) + + log = + capture_log(fn -> + assert {:noreply, %{number_of_transactions: 7, yesterday: ^yesterday}} = + CountersFetcher.handle_info(:try_to_fetch_yesterday_counters, %{}) + + assert_receive :fetch_yesterday_counters + + assert {:noreply, %{}} = + CountersFetcher.handle_info(:fetch_yesterday_counters, %{ + number_of_transactions: 7, + yesterday: yesterday + }) + end) + + assert [queued_counter] = Repo.all(CountersExportQueue) + + assert queued_counter.timestamp == yesterday_dt + assert queued_counter.counter_type == :global + + assert queued_counter.data == %{ + "daily_transactions_number" => "7", + "total_transactions_number" => "2", + "total_addresses_number" => "2" + } + + assert log =~ "Transaction stats is now available for #{yesterday}" + assert log =~ "daily_transactions_number = 7" + assert log =~ "total_transactions_number = 2" + assert log =~ "total_addresses_number = 2" + end + end + + defp put_last_save_records_date(date) do + date + |> DateTime.new!(Time.new!(0, 0, 0, 0)) + |> DateTime.to_unix() + |> then(fn unix_timestamp -> + LastFetchedCounter.upsert(%{ + counter_type: Historian.transaction_stats_last_save_records_timestamp(), + value: unix_timestamp + }) + end) + |> case do + {:ok, _counter} -> :ok + {:error, error} -> raise inspect(error) + end + end +end diff --git a/apps/indexer/test/indexer/fetcher/on_demand/contract_creator_test.exs b/apps/indexer/test/indexer/fetcher/on_demand/contract_creator_test.exs index 7beb6b8deffd..0c92001c84ba 100644 --- a/apps/indexer/test/indexer/fetcher/on_demand/contract_creator_test.exs +++ b/apps/indexer/test/indexer/fetcher/on_demand/contract_creator_test.exs @@ -56,18 +56,17 @@ defmodule Indexer.Fetcher.OnDemand.ContractCreatorTest do insert( :internal_transaction_create, transaction: transaction, - index: 0, + index: 1, created_contract_address: contract_address, created_contract_code: "0x1234", block_number: transaction.block_number, - block_hash: transaction.block_hash, transaction_index: transaction.index ) assert :ignore = ContractCreatorOnDemand.trigger_fetch( contract_address - |> Repo.preload([:contract_creation_internal_transaction]) + |> Address.preload_contract_creation_internal_transaction() ) end @@ -80,7 +79,8 @@ defmodule Indexer.Fetcher.OnDemand.ContractCreatorTest do assert :ignore = ContractCreatorOnDemand.trigger_fetch( contract_address - |> Repo.preload([:contract_creation_transaction, :contract_creation_internal_transaction]) + |> Repo.preload([:contract_creation_transaction]) + |> Address.preload_contract_creation_internal_transaction() ) end @@ -93,7 +93,8 @@ defmodule Indexer.Fetcher.OnDemand.ContractCreatorTest do assert :ignore = ContractCreatorOnDemand.trigger_fetch( contract_address - |> Repo.preload([:contract_creation_transaction, :contract_creation_internal_transaction]) + |> Repo.preload([:contract_creation_transaction]) + |> Address.preload_contract_creation_internal_transaction() ) end @@ -111,7 +112,22 @@ defmodule Indexer.Fetcher.OnDemand.ContractCreatorTest do assert :ignore = ContractCreatorOnDemand.trigger_fetch( contract_address - |> Repo.preload([:contract_creation_transaction, :contract_creation_internal_transaction]) + |> Repo.preload([:contract_creation_transaction]) + |> Address.preload_contract_creation_internal_transaction() + ) + end + + test "does not crash when ETS table is unavailable" do + contract_address = + insert(:address, contract_code: "0x1234") + + :ets.delete(:contract_creator_lookup) + + assert :ignore = + ContractCreatorOnDemand.trigger_fetch( + contract_address + |> Repo.preload([:contract_creation_transaction]) + |> Address.preload_contract_creation_internal_transaction() ) end @@ -136,14 +152,11 @@ defmodule Indexer.Fetcher.OnDemand.ContractCreatorTest do assert :ok = ContractCreatorOnDemand.trigger_fetch( contract_address - |> Repo.preload([:contract_creation_transaction, :contract_creation_internal_transaction]) + |> Repo.preload([:contract_creation_transaction]) + |> Address.preload_contract_creation_internal_transaction() ) - :timer.sleep(100) - - assert :ets.lookup(:contract_creator_lookup, contract_address_hash) == [{contract_address_hash, :in_progress}] - - :timer.sleep(200) + :timer.sleep(300) assert [%{from_number: 3, to_number: 3, priority: 1}] = Repo.all(MissingBlockRange) @@ -174,14 +187,11 @@ defmodule Indexer.Fetcher.OnDemand.ContractCreatorTest do assert :ok = ContractCreatorOnDemand.trigger_fetch( contract_address - |> Repo.preload([:contract_creation_transaction, :contract_creation_internal_transaction]) + |> Repo.preload([:contract_creation_transaction]) + |> Address.preload_contract_creation_internal_transaction() ) - :timer.sleep(100) - - assert :ets.lookup(:contract_creator_lookup, contract_address_hash) == [{contract_address_hash, :in_progress}] - - :timer.sleep(200) + :timer.sleep(300) assert [%{from_number: 2, to_number: 2, priority: 1}] = Repo.all(MissingBlockRange) @@ -212,14 +222,11 @@ defmodule Indexer.Fetcher.OnDemand.ContractCreatorTest do assert :ok = ContractCreatorOnDemand.trigger_fetch( contract_address - |> Repo.preload([:contract_creation_transaction, :contract_creation_internal_transaction]) + |> Repo.preload([:contract_creation_transaction]) + |> Address.preload_contract_creation_internal_transaction() ) - :timer.sleep(100) - - assert :ets.lookup(:contract_creator_lookup, contract_address_hash) == [{contract_address_hash, :in_progress}] - - :timer.sleep(200) + :timer.sleep(300) assert [%{from_number: 1, to_number: 1, priority: 1}] = Repo.all(MissingBlockRange) @@ -228,6 +235,155 @@ defmodule Indexer.Fetcher.OnDemand.ContractCreatorTest do ] end + test "retries when eth_getTransactionCount returns an error tuple" do + contract_address = + insert(:address, contract_code: "0x1234") + + now = Timex.now() + + Enum.each(0..4, fn i -> + insert(:block, number: i, refetch_needed: i == 3, timestamp: Timex.shift(now, minutes: -i)) + end) + + Explorer.Chain.Cache.BlockNumber.get_max() + + contract_address_hash = to_string(contract_address.hash) + + EthereumJSONRPC.Mox + |> eth_get_transaction_count_error_mock(contract_address_hash, "0x2") + |> eth_get_transaction_count_mock(contract_address_hash, "0x2", "0x0") + |> eth_get_transaction_count_mock(contract_address_hash, "0x3", "0x1") + + pid = Process.whereis(ContractCreatorOnDemand) + + assert :ok = + ContractCreatorOnDemand.trigger_fetch( + contract_address + |> Repo.preload([:contract_creation_transaction]) + |> Address.preload_contract_creation_internal_transaction() + ) + + :timer.sleep(1200) + + assert Process.whereis(ContractCreatorOnDemand) == pid + assert [%{from_number: 3, to_number: 3, priority: 1}] = Repo.all(MissingBlockRange) + end + + test "stops retrying after 5 JSON RPC errors" do + contract_address = + insert(:address, contract_code: "0x1234") + + now = Timex.now() + + Enum.each(0..4, fn i -> + insert(:block, number: i, refetch_needed: i == 3, timestamp: Timex.shift(now, minutes: -i)) + end) + + Explorer.Chain.Cache.BlockNumber.get_max() + + contract_address_hash = to_string(contract_address.hash) + + EthereumJSONRPC.Mox + |> eth_get_transaction_count_error_mock_times(contract_address_hash, "0x2", 6) + + pid = Process.whereis(ContractCreatorOnDemand) + + assert :ok = + ContractCreatorOnDemand.trigger_fetch( + contract_address + |> Repo.preload([:contract_creation_transaction]) + |> Address.preload_contract_creation_internal_transaction() + ) + + :timer.sleep(5400) + + assert Process.whereis(ContractCreatorOnDemand) == pid + assert [] == Repo.all(MissingBlockRange) + assert [] == :ets.lookup(:contract_creator_lookup, contract_address_hash) + end + + # Regression: per-address ETS entry must be updated to the resolved integer block number + # after a successful fetch. Previously it was left as :in_progress, which caused + # trigger_fetch/1 to keep returning :ignore and never re-trigger, masking stalls, or to + # re-dispatch indefinitely when the guard was removed. + test "stamps the resolved block number in the per-address ETS entry after a successful fetch" do + contract_address = + insert(:address, contract_code: "0x1234") + + now = Timex.now() + + Enum.each(0..4, fn i -> + insert(:block, number: i, refetch_needed: i == 3, timestamp: Timex.shift(now, minutes: -i)) + end) + + Explorer.Chain.Cache.BlockNumber.get_max() + + contract_address_hash = to_string(contract_address.hash) + + EthereumJSONRPC.Mox + |> eth_get_transaction_count_mock(contract_address_hash, "0x2", "0x0") + |> eth_get_transaction_count_mock(contract_address_hash, "0x3", "0x1") + + assert :ok = + ContractCreatorOnDemand.trigger_fetch( + contract_address + |> Repo.preload([:contract_creation_transaction]) + |> Address.preload_contract_creation_internal_transaction() + ) + + :timer.sleep(300) + + # Must be the resolved integer, not the :in_progress atom that was written at fetch start + assert :ets.lookup(:contract_creator_lookup, contract_address_hash) == [ + {contract_address_hash, 3} + ] + end + + # Regression: Enum.member?(pending_blocks, block_number) compared an integer against a + # list of maps and was always false, allowing the same address to accumulate duplicate + # entries in pending_blocks. The fix uses Enum.reject/2 on address_hash_string so a + # stale entry is replaced rather than duplicated. + test "replaces a stale pending_blocks entry for the same address instead of duplicating it" do + contract_address = + insert(:address, contract_code: "0x1234") + + now = Timex.now() + + Enum.each(0..4, fn i -> + insert(:block, number: i, refetch_needed: i == 3, timestamp: Timex.shift(now, minutes: -i)) + end) + + Explorer.Chain.Cache.BlockNumber.get_max() + + contract_address_hash = to_string(contract_address.hash) + + # Pre-seed a stale pending_blocks entry for the same address at a different block (999). + # Without the fix, Enum.member?(pending_blocks, 3) would be false (integer vs map), + # the new entry would be prepended, and the list would grow to two entries. + :ets.insert( + :contract_creator_lookup, + {"pending_blocks", [%{block_number: 999, address_hash_string: contract_address_hash}]} + ) + + EthereumJSONRPC.Mox + |> eth_get_transaction_count_mock(contract_address_hash, "0x2", "0x0") + |> eth_get_transaction_count_mock(contract_address_hash, "0x3", "0x1") + + assert :ok = + ContractCreatorOnDemand.trigger_fetch( + contract_address + |> Repo.preload([:contract_creation_transaction]) + |> Address.preload_contract_creation_internal_transaction() + ) + + :timer.sleep(300) + + # Exactly one entry for this address — the stale block 999 entry was replaced + assert :ets.lookup(:contract_creator_lookup, "pending_blocks") == [ + {"pending_blocks", [%{block_number: 3, address_hash_string: contract_address_hash}]} + ] + end + defp eth_get_transaction_count_mock(mox, contract_address_hash, block_number, nonce) do mox |> expect(:json_rpc, fn %{ @@ -240,4 +396,27 @@ defmodule Indexer.Fetcher.OnDemand.ContractCreatorTest do {:ok, nonce} end) end + + defp eth_get_transaction_count_error_mock(mox, contract_address_hash, block_number) do + mox + |> expect(:json_rpc, fn %{ + id: _id, + jsonrpc: "2.0", + method: "eth_getTransactionCount", + params: [^contract_address_hash, ^block_number] + }, + _ -> + {:error, + %{ + code: -32000, + message: "missing trie node 0000000000000000000000000000000000000000000000000000000000000000 (path )" + }} + end) + end + + defp eth_get_transaction_count_error_mock_times(mox, contract_address_hash, block_number, times) do + Enum.reduce(1..times, mox, fn _, acc -> + eth_get_transaction_count_error_mock(acc, contract_address_hash, block_number) + end) + end end diff --git a/apps/indexer/test/indexer/fetcher/on_demand/internal_transaction_test.exs b/apps/indexer/test/indexer/fetcher/on_demand/internal_transaction_test.exs index 6146625ddaf1..bd32502598f9 100644 --- a/apps/indexer/test/indexer/fetcher/on_demand/internal_transaction_test.exs +++ b/apps/indexer/test/indexer/fetcher/on_demand/internal_transaction_test.exs @@ -52,19 +52,13 @@ defmodule Indexer.Fetcher.OnDemand.InternalTransactionTest do Application.put_env(:ethereum_jsonrpc, EthereumJSONRPC.Geth, tracer: "call_tracer", debug_trace_timeout: "5s") - opts = [ - necessity_by_association: %{ - [transaction: [:from_address]] => :required - } - ] - assert [ %InternalTransaction{ created_contract_address_hash: created_contract_address_hash, from_address_hash: from_address_hash, - transaction: %{from_address: %{}, block_hash: ^block_hash} + transaction: %{block_hash: ^block_hash} } - ] = InternalTransactionOnDemand.fetch_by_transaction(transaction, opts) + ] = InternalTransactionOnDemand.fetch_by_transaction(transaction) assert to_string(created_contract_address_hash) == "0x205a6b72ce16736c9d87172568a9c0cb9304de0d" assert to_string(from_address_hash) == "0x117b358218da5a4f647072ddb50ded038ed63d17" @@ -73,7 +67,7 @@ defmodule Indexer.Fetcher.OnDemand.InternalTransactionTest do test "fetch_by_block/2" do block = build(:block) block_quantity = EthereumJSONRPC.integer_to_quantity(block.number) - block_hash = block.hash + block_number = block.number expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id, params: [^block_quantity, _]}], _ -> {:ok, @@ -117,14 +111,14 @@ defmodule Indexer.Fetcher.OnDemand.InternalTransactionTest do block_traceable?: true ) - assert [%InternalTransaction{block_hash: ^block_hash, index: 1}] = + assert [%InternalTransaction{block_number: ^block_number, index: 1}] = InternalTransactionOnDemand.fetch_by_block(block, []) end test "fetch_by_block/2 (block_traceable?: false)" do transaction = :transaction |> insert() |> with_block() transaction_hash_str = to_string(transaction.hash) - block_hash = transaction.block_hash + block_number = transaction.block_number expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id, params: [^transaction_hash_str, _]}], _ -> {:ok, @@ -163,14 +157,14 @@ defmodule Indexer.Fetcher.OnDemand.InternalTransactionTest do block_traceable?: false ) - assert [%InternalTransaction{block_hash: ^block_hash, index: 1}] = + assert [%InternalTransaction{block_number: ^block_number, index: 1}] = InternalTransactionOnDemand.fetch_by_block(transaction.block, []) end test "fetch_by_address/2" do address = insert(:address) address_hash_str = to_string(address.hash) - id_to_hash = insert(:address_id_to_address_hash, address_hash: address.hash) + id_to_hash = insert(:address_id_to_address_hash, address: address) insert(:deleted_internal_transactions_address_placeholder, address_id: id_to_hash.address_id, @@ -356,4 +350,44 @@ defmodule Indexer.Fetcher.OnDemand.InternalTransactionTest do assert result |> Enum.filter(&(&1.block_number == 3)) |> Enum.count() == 3 assert result |> Enum.filter(&(&1.block_number == 2)) |> Enum.count() == 1 end + + test "fetch_by_address/2 (no suitable placeholders)" do + address = insert(:address) + address_hash_str = to_string(address.hash) + id_to_hash = insert(:address_id_to_address_hash, address: address) + + insert(:deleted_internal_transactions_address_placeholder, + address_id: id_to_hash.address_id, + block_number: 1, + count_tos: 0, + count_froms: 1 + ) + + insert(:deleted_internal_transactions_address_placeholder, + address_id: id_to_hash.address_id, + block_number: 2, + count_tos: 0, + count_froms: 2 + ) + + insert(:deleted_internal_transactions_address_placeholder, + address_id: id_to_hash.address_id, + block_number: 3, + count_tos: 0, + count_froms: 3 + ) + + Application.put_env(:ethereum_jsonrpc, EthereumJSONRPC.Geth, + tracer: "call_tracer", + debug_trace_timeout: "5s", + block_traceable?: true + ) + + opts = [ + direction: :to_address_hash, + paging_options: %PagingOptions{page_size: 4} + ] + + assert [] = InternalTransactionOnDemand.fetch_by_address(address.hash, opts) + end end diff --git a/apps/indexer/test/indexer/helper_test.exs b/apps/indexer/test/indexer/helper_test.exs new file mode 100644 index 000000000000..076fbf79bf80 --- /dev/null +++ b/apps/indexer/test/indexer/helper_test.exs @@ -0,0 +1,56 @@ +defmodule Indexer.HelperTest do + use ExUnit.Case, async: true + + alias Indexer.Helper + + describe "json_rpc_named_arguments/1" do + test "builds expected JSON-RPC named arguments from RPC URL" do + rpc_url = "https://rpc.example" + timeout = :timer.minutes(10) + + assert Helper.json_rpc_named_arguments(rpc_url) == + [ + transport: EthereumJSONRPC.HTTP, + transport_options: [ + http: EthereumJSONRPC.HTTP.Tesla, + urls: [rpc_url], + http_options: [ + recv_timeout: timeout, + timeout: timeout, + pool: :ethereum_jsonrpc + ] + ] + ] + end + + test "handles nil RPC URL" do + assert_raise ArgumentError, "RPC URL must be a non-empty string", fn -> + Helper.json_rpc_named_arguments(nil) + end + end + + test "handles empty string RPC URL" do + assert_raise ArgumentError, "RPC URL must be a non-empty string", fn -> + Helper.json_rpc_named_arguments("") + end + end + + test "normalizes trailing slash in RPC URL" do + timeout = :timer.minutes(10) + + assert Helper.json_rpc_named_arguments("https://rpc.example/") == + [ + transport: EthereumJSONRPC.HTTP, + transport_options: [ + http: EthereumJSONRPC.HTTP.Tesla, + urls: ["https://rpc.example"], + http_options: [ + recv_timeout: timeout, + timeout: timeout, + pool: :ethereum_jsonrpc + ] + ] + ] + end + end +end diff --git a/apps/indexer/test/indexer/pending_transactions_sanitizer_test.exs b/apps/indexer/test/indexer/pending_transactions_sanitizer_test.exs new file mode 100644 index 000000000000..5b3a523d1156 --- /dev/null +++ b/apps/indexer/test/indexer/pending_transactions_sanitizer_test.exs @@ -0,0 +1,164 @@ +defmodule Indexer.PendingTransactionsSanitizerTest do + use EthereumJSONRPC.Case + use Explorer.DataCase + + import Mox + + alias Explorer.Chain.{Transaction, Wei} + alias Explorer.Repo + alias Indexer.PendingTransactionsSanitizer + + describe "sanitize_pending_transactions/1" do + test "with included transaction", %{json_rpc_named_arguments: json_rpc_named_arguments} do + pending_transaction = insert(:transaction, inserted_at: Timex.shift(Timex.now(), days: -2)) + block = insert(:block, consensus: true, refetch_needed: false) + + EthereumJSONRPC.Mox + |> expect( + :json_rpc, + fn _json, _options -> + {:ok, + [ + %{ + id: 0, + jsonrpc: "2.0", + result: %{ + "transactionHash" => to_string(pending_transaction.hash), + "blockHash" => to_string(block.hash), + "cumulativeGasUsed" => "0x5208", + "gasUsed" => "0x5208", + "status" => "0x1", + "transactionIndex" => "0x0" + } + } + ]} + end + ) + + PendingTransactionsSanitizer.sanitize_pending_transactions(json_rpc_named_arguments) + + updated_block = Repo.reload(block) + assert updated_block.refetch_needed == true + + assert [transaction] = Repo.all(Transaction) + + assert transaction.cumulative_gas_used == Decimal.new("21000") + assert transaction.gas_used == Decimal.new("21000") + assert transaction.block_hash == block.hash + assert transaction.status == :ok + assert transaction.index == 0 + end + + test "with empty result", %{json_rpc_named_arguments: json_rpc_named_arguments} do + insert(:transaction, inserted_at: Timex.shift(Timex.now(), days: -2)) + block = insert(:block, consensus: true, refetch_needed: false) + + EthereumJSONRPC.Mox + |> expect( + :json_rpc, + fn _json, _options -> + {:ok, + [ + %{ + id: 0, + jsonrpc: "2.0", + result: nil + } + ]} + end + ) + + PendingTransactionsSanitizer.sanitize_pending_transactions(json_rpc_named_arguments) + + updated_block = Repo.reload(block) + assert updated_block.refetch_needed == false + + assert [] = Repo.all(Transaction) + end + + test "with non-consensus block", %{json_rpc_named_arguments: json_rpc_named_arguments} do + pending_transaction = insert(:transaction, inserted_at: Timex.shift(Timex.now(), days: -2)) + block = insert(:block, consensus: false, refetch_needed: false) + + EthereumJSONRPC.Mox + |> expect( + :json_rpc, + fn _json, _options -> + {:ok, + [ + %{ + id: 0, + jsonrpc: "2.0", + result: %{ + "transactionHash" => to_string(pending_transaction.hash), + "blockHash" => to_string(block.hash), + "cumulativeGasUsed" => "0x5208", + "gasUsed" => "0x5208", + "status" => "0x1", + "transactionIndex" => "0x0" + } + } + ]} + end + ) + + PendingTransactionsSanitizer.sanitize_pending_transactions(json_rpc_named_arguments) + + updated_block = Repo.reload(block) + assert updated_block.refetch_needed == false + + assert [transaction] = Repo.all(Transaction) + + assert transaction.cumulative_gas_used == Decimal.new("21000") + assert transaction.gas_used == Decimal.new("21000") + assert transaction.block_hash == block.hash + assert transaction.status == :ok + assert transaction.index == 0 + end + + test "with gas price and contract address", %{json_rpc_named_arguments: json_rpc_named_arguments} do + pending_transaction = insert(:transaction, inserted_at: Timex.shift(Timex.now(), days: -2), gas_price: nil) + block = insert(:block, consensus: true, refetch_needed: false) + contract_address = insert(:address) + + EthereumJSONRPC.Mox + |> expect( + :json_rpc, + fn _json, _options -> + {:ok, + [ + %{ + id: 0, + jsonrpc: "2.0", + result: %{ + "transactionHash" => to_string(pending_transaction.hash), + "blockHash" => to_string(block.hash), + "cumulativeGasUsed" => "0x5208", + "gasUsed" => "0x5208", + "effectiveGasPrice" => "0x76be5e6c00", + "contractAddress" => to_string(contract_address.hash), + "status" => "0x1", + "transactionIndex" => "0x0" + } + } + ]} + end + ) + + PendingTransactionsSanitizer.sanitize_pending_transactions(json_rpc_named_arguments) + + updated_block = Repo.reload(block) + assert updated_block.refetch_needed == true + + assert [transaction] = Repo.all(Transaction) + + assert transaction.cumulative_gas_used == Decimal.new("21000") + assert transaction.gas_used == Decimal.new("21000") + assert Wei.to(transaction.gas_price, :wei) == Decimal.new("510000000000") + assert transaction.block_hash == block.hash + assert transaction.created_contract_address_hash == contract_address.hash + assert transaction.status == :ok + assert transaction.index == 0 + end + end +end diff --git a/apps/indexer/test/indexer/transform/address_coin_balances_test.exs b/apps/indexer/test/indexer/transform/address_coin_balances_test.exs index 91985ac51621..92a84c670ce6 100644 --- a/apps/indexer/test/indexer/transform/address_coin_balances_test.exs +++ b/apps/indexer/test/indexer/transform/address_coin_balances_test.exs @@ -1,5 +1,5 @@ defmodule Indexer.Transform.AddressCoinBalancesTest do - use ExUnit.Case, async: true + use Explorer.DataCase, async: true alias Explorer.Factory alias Indexer.Transform.AddressCoinBalances @@ -77,7 +77,7 @@ defmodule Indexer.Transform.AddressCoinBalancesTest do params_set = AddressCoinBalances.params_set(%{internal_transactions_params: [internal_transaction_params]}) - assert MapSet.size(params_set) == 1 + assert MapSet.size(params_set) == 2 assert MapSet.member?(params_set, %{address_hash: created_contract_address_hash, block_number: block_number}) end diff --git a/apps/nft_media_handler/mix.exs b/apps/nft_media_handler/mix.exs index 92763a76ec41..5585fecf7a9a 100644 --- a/apps/nft_media_handler/mix.exs +++ b/apps/nft_media_handler/mix.exs @@ -4,7 +4,7 @@ defmodule NFTMediaHandler.MixProject do def project do [ app: :nft_media_handler, - version: "10.2.1", + version: "11.0.0", build_path: "../../_build", config_path: "../../config/config.exs", deps_path: "../../deps", diff --git a/apps/utils/lib/credo/checks/compile_env_usage.ex b/apps/utils/lib/credo/checks/compile_env_usage.ex index b17505011edc..13ea0441dbf4 100644 --- a/apps/utils/lib/credo/checks/compile_env_usage.ex +++ b/apps/utils/lib/credo/checks/compile_env_usage.ex @@ -46,7 +46,7 @@ defmodule Utils.Credo.Checks.CompileEnvUsage do issue_meta, message: """ Avoid using Application.compile_env, use runtime configuration instead. If you need compile-time config, use Utils.CompileTimeEnvHelper. - More details: https://github.com/blockscout/blockscout/tree/master/CONTRIBUTING.md#compile-time-environment-variables + More details: https://github.com/blockscout/blockscout/tree/master/.github/CONTRIBUTING.md#compile-time-environment-variables """, trigger: trigger ) diff --git a/apps/utils/mix.exs b/apps/utils/mix.exs index e8f2e6b6717f..1f45313ae90c 100644 --- a/apps/utils/mix.exs +++ b/apps/utils/mix.exs @@ -4,7 +4,7 @@ defmodule Utils.MixProject do def project do [ app: :utils, - version: "10.2.1", + version: "11.0.0", build_path: "../../_build", # config_path: "../../config/config.exs", deps_path: "../../deps", diff --git a/appspec.yml b/appspec.yml deleted file mode 100644 index 294c4666503c..000000000000 --- a/appspec.yml +++ /dev/null @@ -1,20 +0,0 @@ -version: 0.0 -os: linux -files: - - source: . - destination: /opt/app -hooks: - ApplicationStop: - - location: bin/deployment/stop - timeout: 300 - AfterInstall: - - location: bin/deployment/build - ApplicationStart: - - location: bin/deployment/migrate - runas: ec2-user - timeout: 300 - - location: bin/deployment/start - timeout: 3600 - ValidateService: - - location: bin/deployment/health_check - timeout: 3600 diff --git a/bin/install_chrome_headless.sh b/bin/install_chrome_headless.sh index 62d48923bc1d..97d18d4115f8 100755 --- a/bin/install_chrome_headless.sh +++ b/bin/install_chrome_headless.sh @@ -1,7 +1,8 @@ export DISPLAY=:99.0 sh -e /etc/init.d/xvfb start -export CHROMEDRIVER_VERSION=$(curl -s "https://googlechromelabs.github.io/chrome-for-testing/last-known-good-versions.json" | jq -r '.channels' | jq -r '.Stable' | jq -r '.version') +#export CHROMEDRIVER_VERSION=$(curl -s "https://googlechromelabs.github.io/chrome-for-testing/last-known-good-versions.json" | jq -r '.channels' | jq -r '.Stable' | jq -r '.version') +export CHROMEDRIVER_VERSION=146.0.7680.178 curl -L -O "https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/${CHROMEDRIVER_VERSION}/linux64/chromedriver-linux64.zip" unzip -j chromedriver-linux64.zip sudo chmod +x chromedriver diff --git a/blockscout.png b/blockscout.png deleted file mode 100644 index 5a5c2814492d..000000000000 Binary files a/blockscout.png and /dev/null differ diff --git a/config/config.exs b/config/config.exs index 9bc9a39c16d0..47fe7d5ca619 100644 --- a/config/config.exs +++ b/config/config.exs @@ -2,6 +2,18 @@ # and its dependencies with the aid of the Config module. import Config +config :explorer, Oban, + engine: Oban.Engines.Basic, + notifier: Oban.Notifiers.Postgres, + repo: Explorer.Repo, + plugins: [ + {Oban.Plugins.Pruner, max_age: 60 * 60 * 24 * 7}, + {Oban.Plugins.Cron, + crontab: [ + {"@daily", Explorer.Chain.CsvExport.RequestsSanitizer} + ]} + ] + # By default, the umbrella project as well as each child # application will require this configuration file, ensuring # they all use the same configuration. While one could diff --git a/config/config_helper.exs b/config/config_helper.exs index 2d1fbd9c3238..a9c40d516023 100644 --- a/config/config_helper.exs +++ b/config/config_helper.exs @@ -15,7 +15,6 @@ defmodule ConfigHelper do {:ethereum, nil} => [Explorer.Repo.Beacon], {:filecoin, nil} => [Explorer.Repo.Filecoin], {:optimism, nil} => [Explorer.Repo.Optimism], - {:polygon_zkevm, nil} => [Explorer.Repo.PolygonZkevm], {:rsk, nil} => [Explorer.Repo.RSK], {:scroll, nil} => [Explorer.Repo.Scroll], {:shibarium, nil} => [Explorer.Repo.Shibarium], @@ -178,7 +177,7 @@ defmodule ConfigHelper do Parses value of env var through catalogued values list. If a value is not in the list, nil is returned. Also, the application shutdown option is supported, if a value is wrong. """ - @spec parse_catalog_value(String.t(), List.t(), bool(), String.t() | nil) :: atom() | nil + @spec parse_catalog_value(String.t(), List.t(), boolean(), String.t() | nil) :: atom() | nil def parse_catalog_value(env_var, catalog, shutdown_on_wrong_value?, default_value \\ nil) do value = env_var |> safe_get_env(default_value) @@ -208,7 +207,7 @@ defmodule ConfigHelper do the map, nil is returned. Also, the application shutdown option is supported, if a value is wrong. """ - @spec parse_catalog_map_value(String.t(), %{binary() => any()}, bool(), String.t() | nil) :: any() | nil + @spec parse_catalog_map_value(String.t(), %{binary() => any()}, boolean(), String.t() | nil) :: any() | nil def parse_catalog_map_value(env_var, catalog, shutdown_on_wrong_key?, default_key \\ nil) do key = env_var |> safe_get_env(default_key) @@ -414,7 +413,6 @@ defmodule ConfigHelper do "ethereum" => :ethereum, "filecoin" => :filecoin, "optimism" => :optimism, - "polygon_zkevm" => :polygon_zkevm, "rsk" => :rsk, "scroll" => :scroll, "shibarium" => :shibarium, diff --git a/config/runtime.exs b/config/runtime.exs index 8265b461c68f..4e87dde600ef 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -320,6 +320,8 @@ config :explorer, end), addresses_blacklist: System.get_env("ADDRESSES_BLACKLIST"), addresses_blacklist_key: System.get_env("ADDRESSES_BLACKLIST_KEY"), + token_balances_import_chunk_size: + ConfigHelper.parse_integer_env_var("INDEXER_CURRENT_TOKEN_BALANCES_IMPORT_CHUNK_SIZE", 50), elasticity_multiplier: ConfigHelper.parse_integer_env_var("EIP_1559_ELASTICITY_MULTIPLIER", 2), base_fee_max_change_denominator: ConfigHelper.parse_integer_env_var("EIP_1559_BASE_FEE_MAX_CHANGE_DENOMINATOR", 8), base_fee_lower_bound: ConfigHelper.parse_integer_env_var("EIP_1559_BASE_FEE_LOWER_BOUND_WEI", 0), @@ -697,7 +699,10 @@ config :explorer, Explorer.SmartContract.SigProviderInterface, config :explorer, Explorer.MicroserviceInterfaces.BENS, service_url: ConfigHelper.parse_url_env_var("MICROSERVICE_BENS_URL"), enabled: ConfigHelper.parse_bool_env_var("MICROSERVICE_BENS_ENABLED"), - protocols: ConfigHelper.parse_list_env_var("MICROSERVICE_BENS_PROTOCOLS") + protocols: ConfigHelper.parse_list_env_var("MICROSERVICE_BENS_PROTOCOLS"), + disable_blocks_bens_preload: ConfigHelper.parse_bool_env_var("DISABLE_BLOCKS_BENS_PRELOAD", "false"), + disable_transactions_bens_preload: ConfigHelper.parse_bool_env_var("DISABLE_TRANSACTIONS_BENS_PRELOAD", "false"), + disable_token_transfers_bens_preload: ConfigHelper.parse_bool_env_var("DISABLE_TOKEN_TRANSFERS_BENS_PRELOAD", "false") config :explorer, Explorer.MicroserviceInterfaces.AccountAbstraction, service_url: ConfigHelper.parse_url_env_var("MICROSERVICE_ACCOUNT_ABSTRACTION_URL"), @@ -762,9 +767,6 @@ config :explorer, :spandex, config :explorer, :datadog, port: ConfigHelper.parse_integer_env_var("DATADOG_PORT", 8126) -config :explorer, Explorer.Chain.Cache.TransactionActionTokensData, - max_cache_size: ConfigHelper.parse_integer_env_var("INDEXER_TX_ACTIONS_MAX_TOKEN_CACHE_SIZE", 100_000) - config :explorer, Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand, fetch_interval: ConfigHelper.parse_time_env_var("MICROSERVICE_ETH_BYTECODE_DB_INTERVAL_BETWEEN_LOOKUPS", "10m"), max_concurrency: ConfigHelper.parse_integer_env_var("MICROSERVICE_ETH_BYTECODE_DB_MAX_LOOKUPS_CONCURRENCY", 10) @@ -800,11 +802,6 @@ config :explorer, Explorer.Migrator.ReindexInternalTransactionsWithIncompatibleS concurrency: ConfigHelper.parse_integer_env_var("MIGRATION_REINDEX_INTERNAL_TRANSACTIONS_STATUS_CONCURRENCY", 1), timeout: ConfigHelper.parse_time_env_var("MIGRATION_REINDEX_INTERNAL_TRANSACTIONS_STATUS_TIMEOUT", "0s") -config :explorer, Explorer.Migrator.ReindexDuplicatedInternalTransactions, - batch_size: ConfigHelper.parse_integer_env_var("MIGRATION_REINDEX_DUPLICATED_INTERNAL_TRANSACTIONS_BATCH_SIZE", 100), - concurrency: ConfigHelper.parse_integer_env_var("MIGRATION_REINDEX_DUPLICATED_INTERNAL_TRANSACTIONS_CONCURRENCY", 1), - timeout: ConfigHelper.parse_time_env_var("MIGRATION_REINDEX_DUPLICATED_INTERNAL_TRANSACTIONS_TIMEOUT", "0s") - config :explorer, Explorer.Migrator.ReindexBlocksWithMissingTransactions, batch_size: ConfigHelper.parse_integer_env_var("MIGRATION_REINDEX_BLOCKS_WITH_MISSING_TRANSACTIONS_BATCH_SIZE", 10), concurrency: ConfigHelper.parse_integer_env_var("MIGRATION_REINDEX_BLOCKS_WITH_MISSING_TRANSACTIONS_CONCURRENCY", 1), @@ -896,6 +893,10 @@ config :explorer, Explorer.Migrator.DeleteZeroValueInternalTransactions, check_interval: ConfigHelper.parse_time_env_var("MIGRATION_DELETE_ZERO_VALUE_INTERNAL_TRANSACTIONS_CHECK_INTERVAL", "1m") +config :explorer, Explorer.Migrator.FillInternalTransactionsAddressIds, + batch_size: ConfigHelper.parse_integer_env_var("MIGRATION_FILL_INTERNAL_TRANSACTIONS_ADDRESS_IDS_BATCH_SIZE", 30), + timeout: ConfigHelper.parse_time_env_var("MIGRATION_FILL_INTERNAL_TRANSACTIONS_ADDRESS_IDS_TIMEOUT", "5s") + config :explorer, Explorer.Chain.BridgedToken, eth_omni_bridge_mediator: System.get_env("BRIDGED_TOKENS_ETH_OMNI_BRIDGE_MEDIATOR"), bsc_omni_bridge_mediator: System.get_env("BRIDGED_TOKENS_BSC_OMNI_BRIDGE_MEDIATOR"), @@ -970,6 +971,46 @@ config :explorer, Explorer.Chain.Scroll.L1FeeParam, l1_base_fee_init: ConfigHelper.parse_integer_env_var("SCROLL_L1_BASE_FEE_INIT", 0), l1_blob_base_fee_init: ConfigHelper.parse_integer_env_var("SCROLL_L1_BLOB_BASE_FEE_INIT", 0) +async_csv_export_enabled? = ConfigHelper.parse_bool_env_var("CSV_EXPORT_ASYNC_ENABLED") +csv_export_oban_concurrency = ConfigHelper.parse_integer_env_var("CSV_EXPORT_ASYNC_OBAN_CONCURRENCY", 10) + +csv_export_queues = + if async_csv_export_enabled? do + [csv_export: csv_export_oban_concurrency, csv_export_sanitize: 1] + else + [] + end + +config :explorer, Oban, enabled: async_csv_export_enabled?, queues: csv_export_queues + +gokapi_url = ConfigHelper.parse_url_env_var("CSV_EXPORT_ASYNC_GOKAPI_URL") +gokapi_api_key = System.get_env("CSV_EXPORT_ASYNC_GOKAPI_API_KEY") + +default_db_timeout = if async_csv_export_enabled?, do: "1h", else: "5m" + +config :explorer, Explorer.Chain.CsvExport, + async?: async_csv_export_enabled?, + max_pending_tasks_per_ip: ConfigHelper.parse_integer_env_var("CSV_EXPORT_ASYNC_MAX_PENDING_TASKS_PER_IP", 3), + chunk_size: ConfigHelper.parse_integer_env_var("CSV_EXPORT_ASYNC_UPLOAD_CHUNK_SIZE", 47_185_920), + db_timeout: ConfigHelper.parse_time_env_var("CSV_EXPORT_DB_TIMEOUT", default_db_timeout), + tmp_dir: ConfigHelper.safe_get_env("CSV_EXPORT_ASYNC_TMP_DIR", "/tmp/csv_export"), + gokapi_url: gokapi_url, + gokapi_api_key: gokapi_api_key, + gokapi_timeout: ConfigHelper.parse_time_env_var("CSV_EXPORT_ASYNC_GOKAPI_TIMEOUT", "60s"), + gokapi_upload_expiry_days: ConfigHelper.parse_integer_env_var("CSV_EXPORT_ASYNC_GOKAPI_UPLOAD_EXPIRY_DAYS", 1), + gokapi_upload_allowed_downloads: + ConfigHelper.parse_integer_env_var("CSV_EXPORT_ASYNC_GOKAPI_UPLOAD_ALLOWED_DOWNLOADS", 1) + +if async_csv_export_enabled? do + if is_nil(gokapi_url) or gokapi_url == "" do + raise "CSV_EXPORT_ASYNC_GOKAPI_URL must be set when CSV_EXPORT_ASYNC_ENABLED=true" + end + + if is_nil(gokapi_api_key) or gokapi_api_key == "" do + raise "CSV_EXPORT_ASYNC_GOKAPI_API_KEY must be set when CSV_EXPORT_ASYNC_ENABLED=true" + end +end + ############### ### Indexer ### ############### @@ -1041,25 +1082,6 @@ config :indexer, Indexer.Supervisor, enabled: !disable_indexer? config :indexer, Indexer.Transform.FheOperations, enabled: ConfigHelper.parse_bool_env_var("INDEXER_FHE_OPERATIONS_ENABLED", "false") -config :indexer, Indexer.Fetcher.TransactionAction.Supervisor, - enabled: ConfigHelper.parse_bool_env_var("INDEXER_TX_ACTIONS_ENABLE") - -config :indexer, Indexer.Fetcher.TransactionAction, - reindex_first_block: System.get_env("INDEXER_TX_ACTIONS_REINDEX_FIRST_BLOCK"), - reindex_last_block: System.get_env("INDEXER_TX_ACTIONS_REINDEX_LAST_BLOCK"), - reindex_protocols: System.get_env("INDEXER_TX_ACTIONS_REINDEX_PROTOCOLS", ""), - aave_v3_pool: System.get_env("INDEXER_TX_ACTIONS_AAVE_V3_POOL_CONTRACT"), - uniswap_v3_factory: - ConfigHelper.safe_get_env( - "INDEXER_TX_ACTIONS_UNISWAP_V3_FACTORY_CONTRACT", - "0x1F98431c8aD98523631AE4a59f267346ea31F984" - ), - uniswap_v3_nft_position_manager: - ConfigHelper.safe_get_env( - "INDEXER_TX_ACTIONS_UNISWAP_V3_NFT_POSITION_MANAGER_CONTRACT", - "0xC36442b4a4522E871399CD717aBDD847Ab11FE88" - ) - config :indexer, Indexer.PendingTransactionsSanitizer, interval: ConfigHelper.parse_time_env_var("INDEXER_PENDING_TRANSACTIONS_SANITIZER_INTERVAL", "1h") @@ -1566,38 +1588,6 @@ config :indexer, Indexer.Fetcher.Shibarium.L1.Supervisor, enabled: ConfigHelper. config :indexer, Indexer.Fetcher.Shibarium.L2.Supervisor, enabled: ConfigHelper.chain_type() == :shibarium -config :indexer, Indexer.Fetcher.PolygonZkevm.BridgeL1, - rpc: System.get_env("INDEXER_POLYGON_ZKEVM_L1_RPC"), - start_block: System.get_env("INDEXER_POLYGON_ZKEVM_L1_BRIDGE_START_BLOCK"), - bridge_contract: System.get_env("INDEXER_POLYGON_ZKEVM_L1_BRIDGE_CONTRACT"), - native_symbol: System.get_env("INDEXER_POLYGON_ZKEVM_L1_BRIDGE_NATIVE_SYMBOL", "ETH"), - native_decimals: ConfigHelper.parse_integer_env_var("INDEXER_POLYGON_ZKEVM_L1_BRIDGE_NATIVE_DECIMALS", 18), - rollup_network_id_l1: ConfigHelper.parse_integer_or_nil_env_var("INDEXER_POLYGON_ZKEVM_L1_BRIDGE_NETWORK_ID"), - rollup_index_l1: ConfigHelper.parse_integer_or_nil_env_var("INDEXER_POLYGON_ZKEVM_L1_BRIDGE_ROLLUP_INDEX") - -config :indexer, Indexer.Fetcher.PolygonZkevm.BridgeL1.Supervisor, enabled: ConfigHelper.chain_type() == :polygon_zkevm - -config :indexer, Indexer.Fetcher.PolygonZkevm.BridgeL1Tokens.Supervisor, - enabled: ConfigHelper.chain_type() == :polygon_zkevm - -config :indexer, Indexer.Fetcher.PolygonZkevm.BridgeL2, - start_block: System.get_env("INDEXER_POLYGON_ZKEVM_L2_BRIDGE_START_BLOCK"), - bridge_contract: System.get_env("INDEXER_POLYGON_ZKEVM_L2_BRIDGE_CONTRACT"), - rollup_network_id_l2: ConfigHelper.parse_integer_or_nil_env_var("INDEXER_POLYGON_ZKEVM_L2_BRIDGE_NETWORK_ID"), - rollup_index_l2: ConfigHelper.parse_integer_or_nil_env_var("INDEXER_POLYGON_ZKEVM_L2_BRIDGE_ROLLUP_INDEX") - -config :indexer, Indexer.Fetcher.PolygonZkevm.BridgeL2.Supervisor, enabled: ConfigHelper.chain_type() == :polygon_zkevm - -config :indexer, Indexer.Fetcher.PolygonZkevm.TransactionBatch, - chunk_size: ConfigHelper.parse_integer_env_var("INDEXER_POLYGON_ZKEVM_BATCHES_CHUNK_SIZE", 20), - ignore_numbers: System.get_env("INDEXER_POLYGON_ZKEVM_BATCHES_IGNORE", "0"), - recheck_interval: ConfigHelper.parse_integer_env_var("INDEXER_POLYGON_ZKEVM_BATCHES_RECHECK_INTERVAL", 60) - -config :indexer, Indexer.Fetcher.PolygonZkevm.TransactionBatch.Supervisor, - enabled: - ConfigHelper.chain_type() == :polygon_zkevm && - ConfigHelper.parse_bool_env_var("INDEXER_POLYGON_ZKEVM_BATCHES_ENABLED") - config :indexer, Indexer.Fetcher.Celo.ValidatorGroupVotes, batch_size: ConfigHelper.parse_integer_env_var("INDEXER_CELO_VALIDATOR_GROUP_VOTES_BATCH_SIZE", 200_000) diff --git a/config/runtime/dev.exs b/config/runtime/dev.exs index f40189052ac2..f592e844bc0a 100644 --- a/config/runtime/dev.exs +++ b/config/runtime/dev.exs @@ -132,7 +132,6 @@ for repo <- [ Explorer.Repo.Filecoin, Explorer.Repo.Optimism, Explorer.Repo.PolygonEdge, - Explorer.Repo.PolygonZkevm, Explorer.Repo.RSK, Explorer.Repo.Scroll, Explorer.Repo.Shibarium, diff --git a/config/runtime/prod.exs b/config/runtime/prod.exs index d00c8187744e..0285ad2b54bf 100644 --- a/config/runtime/prod.exs +++ b/config/runtime/prod.exs @@ -97,7 +97,6 @@ for repo <- [ Explorer.Repo.Filecoin, Explorer.Repo.Optimism, Explorer.Repo.PolygonEdge, - Explorer.Repo.PolygonZkevm, Explorer.Repo.RSK, Explorer.Repo.Scroll, Explorer.Repo.Shibarium, diff --git a/config/test.exs b/config/test.exs index 2b9a7bc630d8..f45b43d14630 100644 --- a/config/test.exs +++ b/config/test.exs @@ -1,4 +1,5 @@ import Config +config :explorer, Oban, testing: :manual # Print only warnings and errors during test config :logger, level: :warn diff --git a/coveralls.json b/coveralls.json deleted file mode 100644 index 19bc74799ebc..000000000000 --- a/coveralls.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "coverage_options": { - "treat_no_relevant_lines_as_covered": true, - "minimum_coverage": 70 - }, - "terminal_options": { - "file_column_width": 120 - } -} diff --git a/cspell.json b/cspell.json index 5ddf417d0adc..0ebe6b215d4d 100644 --- a/cspell.json +++ b/cspell.json @@ -4,7 +4,7 @@ // Version of the setting file. Always 0.2 "version": "0.2", // language - current active spelling language - "language": "en", + "language": "en,en-GB", // enabled - enable code editor suggestions "enabled": true, // files - glob patterns of files to be checked @@ -56,6 +56,7 @@ "asda", "Asearch", "Asfpp", + "Astar", "atoken", "audix", "autodetectfalse", @@ -63,6 +64,7 @@ "autodetecttrue", "Autonity", "autoplay", + "Autoscout", "Averify", "awesomplete", "Backfiller", @@ -170,6 +172,7 @@ "conname", "conrelid", "Consolas", + "contenttype", "contractaddress", "contractaddresses", "contractname", @@ -311,6 +314,7 @@ "gettxreceiptstatus", "giga", "Gitter", + "GOKAPI", "goldtoken", "goqtclhifepvfnicv", "gqz", @@ -467,7 +471,9 @@ "Nodealus", "nohighlight", "nolink", + "nonblocking", "nonconsensus", + "Nonfungible", "nonpending", "noproc", "noreferrer", @@ -482,6 +488,7 @@ "Numbe", "Nunito", "nxdomain", + "Oban", "OFAC", "offchain", "ommer", @@ -735,6 +742,7 @@ "unprefixed", "unstaged", "unxswap", + "uploadrequest", "uppercased", "upsert", "upserted", diff --git a/docker-compose/envs/common-blockscout.env b/docker-compose/envs/common-blockscout.env new file mode 100644 index 000000000000..5ea1cff7d466 --- /dev/null +++ b/docker-compose/envs/common-blockscout.env @@ -0,0 +1,680 @@ +ETHEREUM_JSONRPC_VARIANT=geth +ETHEREUM_JSONRPC_HTTP_URL=http://host.docker.internal:8545/ +DISABLE_FILE_LOGGING=false +DATABASE_URL=postgresql://blockscout:ceWb1MeLBEeOIfk65gU8EjF8@db:5432/blockscout + +# DATABASE_EVENT_POOL_SIZE +# DATABASE_EVENT_URL= +# DATABASE_QUEUE_TARGET +# TEST_DATABASE_URL= +# TEST_DATABASE_READ_ONLY_API_URL= + +ETHEREUM_JSONRPC_TRANSPORT=http +ETHEREUM_JSONRPC_DISABLE_ARCHIVE_BALANCES=false +# ETHEREUM_JSONRPC_FALLBACK_HTTP_URL= +ETHEREUM_JSONRPC_TRACE_URL=http://host.docker.internal:8545/ +# ETHEREUM_JSONRPC_FALLBACK_TRACE_URL= +# ETHEREUM_JSONRPC_ETH_CALL_URL= +# ETHEREUM_JSONRPC_FALLBACK_ETH_CALL_URL= +# ETHEREUM_JSONRPC_WS_URL= +# ETHEREUM_JSONRPC_FALLBACK_WS_URL= +# ETHEREUM_JSONRPC_WS_RETRY_INTERVAL= +# ETHEREUM_JSONRPC_ARCHIVE_BALANCES_WINDOW=200 +# ETHEREUM_JSONRPC_HTTP_TIMEOUT= +# ETHEREUM_JSONRPC_HTTP_HEADERS= +# ETHEREUM_JSONRPC_HTTP_GZIP_ENABLED= +# ETHEREUM_JSONRPC_WAIT_PER_TIMEOUT= +# ETHEREUM_JSONRPC_GETH_TRACE_BY_BLOCK= +# ETHEREUM_JSONRPC_GETH_ALLOW_EMPTY_TRACES= +# ETHEREUM_JSONRPC_DEBUG_TRACE_TRANSACTION_TIMEOUT= +# ETHEREUM_JSONRPC_HTTP_URLS= +# ETHEREUM_JSONRPC_FALLBACK_HTTP_URLS= +# ETHEREUM_JSONRPC_TRACE_URLS= +# ETHEREUM_JSONRPC_FALLBACK_TRACE_URLS= +# ETHEREUM_JSONRPC_ETH_CALL_URLS= +# ETHEREUM_JSONRPC_FALLBACK_ETH_CALL_URLS= +# CHAIN_TYPE= +# IPC_PATH= +# BLOCKSCOUT_HOST= +# BLOCKSCOUT_PROTOCOL= +SECRET_KEY_BASE=56NtB48ear7+wMSf0IQuWDAAazhpb31qyc7GiyspBP2vh7t5zlCsF5QDv76chXeN +# CHECK_ORIGIN= +PORT=4000 +COIN_NAME= +# METADATA_CONTRACT= +# VALIDATORS_CONTRACT= +# KEYS_MANAGER_CONTRACT= +# REWARDS_CONTRACT= +# EMISSION_FORMAT=DEFAULT +# CHAIN_SPEC_PATH= +# CHAIN_SPEC_PROCESSING_DELAY= +# SUPPLY_MODULE= +COIN= +DISABLE_MARKET=true +# MARKET_NATIVE_COIN_SOURCE= +# MARKET_SECONDARY_COIN_SOURCE= +# MARKET_TOKENS_SOURCE= +# MARKET_NATIVE_COIN_HISTORY_SOURCE= +# MARKET_SECONDARY_COIN_HISTORY_SOURCE= +# MARKET_CAP_HISTORY_SOURCE= +# MARKET_TVL_HISTORY_SOURCE= +# MARKET_COINGECKO_COIN_ID= +# MARKET_COINGECKO_SECONDARY_COIN_ID= +# MARKET_COINGECKO_API_KEY= +# MARKET_COINGECKO_BASE_URL= +# MARKET_COINGECKO_BASE_PRO_URL= +# MARKET_COINGECKO_PLATFORM_ID= +# MARKET_COINMARKETCAP_BASE_URL= +# MARKET_COINMARKETCAP_API_KEY= +# MARKET_COINMARKETCAP_COIN_ID= +# MARKET_COINMARKETCAP_SECONDARY_COIN_ID= +# MARKET_CRYPTOCOMPARE_BASE_URL= +# MARKET_CRYPTOCOMPARE_COIN_SYMBOL= +# MARKET_CRYPTOCOMPARE_SECONDARY_COIN_SYMBOL= +# MARKET_CRYPTORANK_SECONDARY_COIN_ID= +# MARKET_CRYPTORANK_PLATFORM_ID= +# MARKET_CRYPTORANK_BASE_URL= +# MARKET_CRYPTORANK_API_KEY= +# MARKET_CRYPTORANK_COIN_ID= +# MARKET_DEFILLAMA_COIN_ID= +# MARKET_MOBULA_CHAIN_ID= +# MARKET_MOBULA_BASE_URL= +# MARKET_MOBULA_API_KEY= +# MARKET_MOBULA_COIN_ID= +# MARKET_MOBULA_SECONDARY_COIN_ID= +# MARKET_DIA_BLOCKCHAIN= +# MARKET_DIA_BASE_URL= +# MARKET_DIA_COIN_ADDRESS_HASH= +# MARKET_DIA_SECONDARY_COIN_ADDRESS_HASH= +# MARKET_COIN_FETCHER_ENABLED= +# MARKET_COIN_CACHE_PERIOD= +# MARKET_TOKENS_FETCHER_ENABLED= +# MARKET_TOKENS_INTERVAL= +# MARKET_TOKENS_REFETCH_INTERVAL= +# MARKET_TOKENS_MAX_BATCH_SIZE= +# MARKET_HISTORY_FETCHER_ENABLED= +# MARKET_HISTORY_FETCH_INTERVAL= +# MARKET_HISTORY_FIRST_FETCH_DAY_COUNT= +POOL_SIZE=80 +POOL_SIZE_API=10 +ECTO_USE_SSL=false +# DATADOG_HOST= +# DATADOG_PORT= +# SPANDEX_BATCH_SIZE= +# SPANDEX_SYNC_THRESHOLD= +HEART_BEAT_TIMEOUT=30 +# HEART_COMMAND= +# BLOCKSCOUT_VERSION= +RELEASE_LINK= +# BLOCK_TRANSFORMER=base +# BLOCK_RANGES= +# FIRST_BLOCK= +# LAST_BLOCK= +# TRACE_BLOCK_RANGES= +# TRACE_FIRST_BLOCK= +# TRACE_LAST_BLOCK= +# CACHE_BLOCK_COUNT_PERIOD=7200 +# CACHE_TXS_COUNT_PERIOD=7200 +# CACHE_ADDRESS_SUM_PERIOD=3600 +# CACHE_TOTAL_GAS_USAGE_PERIOD=7200 +# CACHE_ADDRESS_TRANSACTIONS_GAS_USAGE_COUNTER_PERIOD=1800 +# CACHE_TOKEN_HOLDERS_COUNTER_PERIOD=3600 +# CACHE_TOKEN_TRANSFERS_COUNTER_PERIOD=3600 +# CACHE_ADDRESS_COUNT_PERIOD=1800 +# CACHE_AVERAGE_BLOCK_PERIOD=1800 +# CACHE_MARKET_HISTORY_PERIOD=21600 +# CACHE_ADDRESS_TRANSACTIONS_COUNTER_PERIOD=3600 +# CACHE_ADDRESS_TOKENS_USD_SUM_PERIOD=3600 +# CACHE_ADDRESS_TOKEN_TRANSFERS_COUNTER_PERIOD=3600 +# CACHE_TRANSACTIONS_24H_STATS_PERIOD= +# CACHE_FRESH_PENDING_TRANSACTIONS_COUNTER_PERIOD= +# CACHE_PENDING_OPERATIONS_COUNT_PERIOD= +# TOKEN_BALANCE_ON_DEMAND_FETCHER_THRESHOLD= +# COIN_BALANCE_ON_DEMAND_FETCHER_THRESHOLD= +# CONTRACT_CODE_ON_DEMAND_FETCHER_THRESHOLD= +# TOKEN_INSTANCE_METADATA_REFETCH_ON_DEMAND_FETCHER_THRESHOLD= +# ADDRESSES_TABS_COUNTERS_TTL=10m +# TOKEN_METADATA_UPDATE_INTERVAL=48h +# TOKEN_COUNTERS_UPDATE_INTERVAL=3h +# CONTRACT_VERIFICATION_ALLOWED_SOLIDITY_EVM_VERSIONS=homestead,tangerineWhistle,spuriousDragon,byzantium,constantinople,petersburg,istanbul,berlin,london,paris,shanghai,cancun,prague,osaka,default +# CONTRACT_VERIFICATION_ALLOWED_VYPER_EVM_VERSIONS=byzantium,constantinople,petersburg,istanbul,berlin,paris,shanghai,cancun,osaka,default +# CONTRACT_VERIFICATION_MAX_LIBRARIES=10 +# CONTRACT_AUDIT_REPORTS_AIRTABLE_URL= +# CONTRACT_AUDIT_REPORTS_AIRTABLE_API_KEY= +# CONTRACT_CERTIFIED_LIST= +# CONTRACT_ENABLE_PARTIAL_REVERIFICATION= +# UNCLES_IN_AVERAGE_BLOCK_TIME=false +# DISABLE_BLOCK_BROADCAST_ENRICHMENT=false +# DISABLE_WEBAPP=true +ADMIN_PANEL_ENABLED=false +# API_V2_ENABLED=true +# DISABLE_BLOCKS_BENS_PRELOAD=false +# DISABLE_TRANSACTIONS_BENS_PRELOAD=false +# DISABLE_TOKEN_TRANSFERS_BENS_PRELOAD=false +API_V1_READ_METHODS_DISABLED=false +API_V1_WRITE_METHODS_DISABLED=false +# API_RATE_LIMIT_DISABLED=true +# API_SENSITIVE_ENDPOINTS_KEY= +# API_RATE_LIMIT_BY_KEY_TIME_INTERVAL=1s +# API_RATE_LIMIT_BY_WHITELISTED_IP_TIME_INTERVAL=1s +# API_RATE_LIMIT_UI_V2_WITH_TOKEN_TIME_INTERVAL=1s +# API_RATE_LIMIT_BY_ACCOUNT_API_KEY_TIME_INTERVAL=1s +# API_RATE_LIMIT_BY_IP_TIME_INTERVAL=1m +# API_RATE_LIMIT=50 +# API_RATE_LIMIT_BY_KEY=10 +# API_RATE_LIMIT_BY_WHITELISTED_IP=25 +# API_RATE_LIMIT_WHITELISTED_IPS= +# API_RATE_LIMIT_STATIC_API_KEY= +# API_RATE_LIMIT_UI_V2_WITH_TOKEN=5 +# API_RATE_LIMIT_BY_IP=300 +# API_RATE_LIMIT_CONFIG_URL= +# API_RATE_LIMIT_REMOTE_IP_HEADERS= +# API_RATE_LIMIT_REMOTE_IP_KNOWN_PROXIES= +# API_NO_RATE_LIMIT_API_KEY= +# API_GRAPHQL_ENABLED= +# API_GRAPHQL_MAX_COMPLEXITY= +# API_GRAPHQL_TOKEN_LIMIT= +# API_GRAPHQL_DEFAULT_TRANSACTION_HASH= +# API_GRAPHQL_RATE_LIMIT_DISABLED= +# API_GRAPHQL_RATE_LIMIT= +# API_GRAPHQL_RATE_LIMIT_BY_KEY= +# API_GRAPHQL_RATE_LIMIT_TIME_INTERVAL= +# API_GRAPHQL_RATE_LIMIT_BY_IP= +# API_GRAPHQL_RATE_LIMIT_BY_IP_TIME_INTERVAL= +# API_GRAPHQL_RATE_LIMIT_STATIC_API_KEY= +# API_DISABLE_CONTRACT_CREATION_INTERNAL_TRANSACTION_ASSOCIATION=false +# DISABLE_INDEXER=false +# DISABLE_REALTIME_INDEXER=false +# DISABLE_CATCHUP_INDEXER=false +# INDEXER_DISABLE_ADDRESS_COIN_BALANCE_FETCHER=false +# INDEXER_DISABLE_ARCHIVAL_TOKEN_BALANCES_FETCHER=false +# INDEXER_DISABLE_TOKEN_INSTANCE_REALTIME_FETCHER=false +# INDEXER_DISABLE_TOKEN_INSTANCE_RETRY_FETCHER=false +# INDEXER_DISABLE_TOKEN_INSTANCE_SANITIZE_FETCHER=false +# INDEXER_DISABLE_TOKEN_INSTANCE_REFETCH_FETCHER=false +# INDEXER_DISABLE_PENDING_TRANSACTIONS_FETCHER=false +# INDEXER_DISABLE_INTERNAL_TRANSACTIONS_FETCHER=false +# INDEXER_DISABLE_CATALOGED_TOKEN_UPDATER_FETCHER= +# INDEXER_DISABLE_BLOCK_REWARD_FETCHER= +# INDEXER_DISABLE_EMPTY_BLOCKS_SANITIZER= +# INDEXER_DISABLE_WITHDRAWALS_FETCHER= +# INDEXER_DISABLE_REPLACED_TRANSACTION_FETCHER= +# INDEXER_DISABLE_TOKEN_INSTANCE_ERC_1155_SANITIZE_FETCHER=false +# INDEXER_DISABLE_TOKEN_INSTANCE_ERC_721_SANITIZE_FETCHER=false +# INDEXER_DISABLE_MULTICHAIN_SEARCH_DB_EXPORT_MAIN_QUEUE_FETCHER= +# INDEXER_DISABLE_MULTICHAIN_SEARCH_DB_EXPORT_BALANCES_QUEUE_FETCHER= +# INDEXER_DISABLE_MULTICHAIN_SEARCH_DB_EXPORT_TOKEN_INFO_QUEUE_FETCHER= +# INDEXER_DISABLE_MULTICHAIN_SEARCH_DB_EXPORT_COUNTERS_QUEUE_FETCHER= +# INDEXER_CATCHUP_BLOCKS_BATCH_SIZE= +# INDEXER_CATCHUP_BLOCKS_CONCURRENCY= +# INDEXER_CATCHUP_BLOCK_INTERVAL= +# INDEXER_EMPTY_BLOCKS_SANITIZER_BATCH_SIZE=10 +# INDEXER_EMPTY_BLOCKS_SANITIZER_INTERVAL=10s +# INDEXER_EMPTY_BLOCKS_SANITIZER_HEAD_OFFSET=1000 +# INDEXER_INTERNAL_TRANSACTIONS_BATCH_SIZE= +# INDEXER_INTERNAL_TRANSACTIONS_CONCURRENCY= +# INDEXER_BLOCK_REWARD_BATCH_SIZE= +# INDEXER_BLOCK_REWARD_CONCURRENCY= +# INDEXER_PENDING_TRANSACTIONS_SANITIZER_INTERVAL= +# INDEXER_TOKEN_INSTANCE_USE_BASE_URI_RETRY= +# INDEXER_TOKEN_INSTANCE_RETRY_REFETCH_INTERVAL= +# INDEXER_TOKEN_INSTANCE_RETRY_BATCH_SIZE=10 +# INDEXER_TOKEN_INSTANCE_RETRY_CONCURRENCY= +# INDEXER_TOKEN_INSTANCE_REALTIME_BATCH_SIZE=1 +# INDEXER_TOKEN_INSTANCE_REALTIME_CONCURRENCY= +# INDEXER_TOKEN_INSTANCE_SANITIZE_BATCH_SIZE=10 +# INDEXER_TOKEN_INSTANCE_SANITIZE_CONCURRENCY= +# INDEXER_TOKEN_INSTANCE_REFETCH_BATCH_SIZE=10 +# INDEXER_TOKEN_INSTANCE_REFETCH_CONCURRENCY= +# INDEXER_TOKEN_INSTANCE_CIDR_BLACKLIST= +# INDEXER_TOKEN_INSTANCE_HOST_FILTERING_ENABLED= +# INDEXER_TOKEN_INSTANCE_ALLOWED_URI_PROTOCOLS= +# INDEXER_TOKEN_TRANSFER_BLOCK_CONSENSUS_SANITIZER_INTERVAL=20m +# INDEXER_SIGNED_AUTHORIZATION_STATUS_BATCH_SIZE= +# INDEXER_FHE_OPERATIONS_ENABLED=false +# INDEXER_MULTICHAIN_SEARCH_DB_EXPORT_MAIN_QUEUE_BATCH_SIZE= +# INDEXER_MULTICHAIN_SEARCH_DB_EXPORT_MAIN_QUEUE_CONCURRENCY= +# INDEXER_MULTICHAIN_SEARCH_DB_EXPORT_MAIN_QUEUE_ENQUEUE_BUSY_WAITING_TIMEOUT= +# INDEXER_MULTICHAIN_SEARCH_DB_EXPORT_MAIN_QUEUE_MAX_QUEUE_SIZE= +# INDEXER_MULTICHAIN_SEARCH_DB_EXPORT_MAIN_QUEUE_INIT_QUERY_LIMIT= +# INDEXER_MULTICHAIN_SEARCH_DB_EXPORT_BALANCES_QUEUE_BATCH_SIZE= +# INDEXER_MULTICHAIN_SEARCH_DB_EXPORT_BALANCES_QUEUE_CONCURRENCY= +# INDEXER_MULTICHAIN_SEARCH_DB_EXPORT_BALANCES_QUEUE_ENQUEUE_BUSY_WAITING_TIMEOUT= +# INDEXER_MULTICHAIN_SEARCH_DB_EXPORT_BALANCES_QUEUE_MAX_QUEUE_SIZE= +# INDEXER_MULTICHAIN_SEARCH_DB_EXPORT_BALANCES_QUEUE_INIT_QUERY_LIMIT= +# INDEXER_MULTICHAIN_SEARCH_DB_EXPORT_TOKEN_INFO_QUEUE_BATCH_SIZE= +# INDEXER_MULTICHAIN_SEARCH_DB_EXPORT_TOKEN_INFO_QUEUE_CONCURRENCY= +# INDEXER_MULTICHAIN_SEARCH_DB_EXPORT_TOKEN_INFO_QUEUE_ENQUEUE_BUSY_WAITING_TIMEOUT= +# INDEXER_MULTICHAIN_SEARCH_DB_EXPORT_TOKEN_INFO_QUEUE_MAX_QUEUE_SIZE= +# INDEXER_MULTICHAIN_SEARCH_DB_EXPORT_TOKEN_INFO_QUEUE_INIT_QUERY_LIMIT= +# INDEXER_MULTICHAIN_SEARCH_DB_EXPORT_COUNTERS_QUEUE_BATCH_SIZE= +# INDEXER_MULTICHAIN_SEARCH_DB_EXPORT_COUNTERS_QUEUE_CONCURRENCY= +# INDEXER_MULTICHAIN_SEARCH_DB_EXPORT_COUNTERS_QUEUE_ENQUEUE_BUSY_WAITING_TIMEOUT= +# INDEXER_MULTICHAIN_SEARCH_DB_EXPORT_COUNTERS_QUEUE_MAX_QUEUE_SIZE= +# INDEXER_MULTICHAIN_SEARCH_DB_EXPORT_COUNTERS_QUEUE_INIT_QUERY_LIMIT= +# INDEXER_INTERNAL_TRANSACTION_DELETE_QUEUE_BATCH_SIZE= +# INDEXER_INTERNAL_TRANSACTION_DELETE_QUEUE_CONCURRENCY= +# INDEXER_INTERNAL_TRANSACTION_DELETE_QUEUE_THRESHOLD= +# INDEXER_COIN_BALANCES_BATCH_SIZE= +# INDEXER_COIN_BALANCES_CONCURRENCY= +# INDEXER_RECEIPTS_BATCH_SIZE= +# INDEXER_RECEIPTS_CONCURRENCY= +# INDEXER_TOKEN_CONCURRENCY= +# INDEXER_ARCHIVAL_TOKEN_BALANCES_BATCH_SIZE= +# INDEXER_ARCHIVAL_TOKEN_BALANCES_CONCURRENCY= +# INDEXER_ARCHIVAL_TOKEN_BALANCES_MAX_REFETCH_INTERVAL= +# INDEXER_ARCHIVAL_TOKEN_BALANCES_EXPONENTIAL_TIMEOUT_COEFF= +# INDEXER_CURRENT_TOKEN_BALANCES_BATCH_SIZE= +# INDEXER_CURRENT_TOKEN_BALANCES_CONCURRENCY= +# INDEXER_TOKEN_BALANCES_IMPORT_CHUNK_SIZE=50 +# INDEXER_DB_EVENT_NOTIFICATIONS_CLEANUP_ENABLED=true +# INDEXER_DB_EVENT_NOTIFICATIONS_CLEANUP_INTERVAL= +# INDEXER_DB_EVENT_NOTIFICATIONS_CLEANUP_MAX_AGE= +# INDEXER_ZKSYNC_BATCHES_ENABLED= +# INDEXER_ZKSYNC_BATCHES_CHUNK_SIZE= +# INDEXER_ZKSYNC_NEW_BATCHES_MAX_RANGE= +# INDEXER_ZKSYNC_NEW_BATCHES_RECHECK_INTERVAL= +# INDEXER_ZKSYNC_L1_RPC= +# INDEXER_ZKSYNC_BATCHES_STATUS_RECHECK_INTERVAL= +# INDEXER_ARBITRUM_ARBSYS_CONTRACT= +# INDEXER_ARBITRUM_NODE_INTERFACE_CONTRACT= +# INDEXER_ARBITRUM_L1_RPC= +# INDEXER_ARBITRUM_L1_RPC_CHUNK_SIZE= +# INDEXER_ARBITRUM_L1_RPC_HISTORICAL_BLOCKS_RANGE= +# INDEXER_ARBITRUM_L1_ROLLUP_CONTRACT= +# INDEXER_ARBITRUM_L1_ROLLUP_INIT_BLOCK= +# INDEXER_ARBITRUM_L1_COMMON_START_BLOCK= +# INDEXER_ARBITRUM_L1_FINALIZATION_THRESHOLD= +# INDEXER_ARBITRUM_ROLLUP_CHUNK_SIZE= +# INDEXER_ARBITRUM_BATCHES_TRACKING_ENABLED= +# INDEXER_ARBITRUM_BATCHES_TRACKING_RECHECK_INTERVAL= +# INDEXER_ARBITRUM_NEW_BATCHES_LIMIT= +# INDEXER_ARBITRUM_MISSING_BATCHES_RANGE= +# INDEXER_ARBITRUM_BATCHES_TRACKING_MESSAGES_TO_BLOCKS_SHIFT= +# INDEXER_ARBITRUM_CONFIRMATIONS_TRACKING_FINALIZED= +# INDEXER_ARBITRUM_BATCHES_TRACKING_L1_FINALIZATION_CHECK_ENABLED= +# INDEXER_ARBITRUM_BATCHES_TRACKING_FAILURE_THRESHOLD= +# INDEXER_ARBITRUM_BRIDGE_MESSAGES_TRACKING_ENABLED= +# INDEXER_ARBITRUM_TRACKING_MESSAGES_ON_L1_RECHECK_INTERVAL= +# INDEXER_ARBITRUM_MESSAGES_TRACKING_FAILURE_THRESHOLD= +# INDEXER_ARBITRUM_MISSED_MESSAGE_IDS_RANGE= +# INDEXER_ARBITRUM_MISSED_MESSAGES_RECHECK_INTERVAL= +# INDEXER_ARBITRUM_MISSED_MESSAGES_BLOCKS_DEPTH= +# INDEXER_ARBITRUM_DATA_BACKFILL_ENABLED= +# INDEXER_ARBITRUM_DATA_BACKFILL_UNINDEXED_BLOCKS_RECHECK_INTERVAL= +# INDEXER_ARBITRUM_DATA_BACKFILL_BLOCKS_DEPTH= +# INDEXER_OPTIMISM_L1_RPC= +# INDEXER_OPTIMISM_L1_SYSTEM_CONFIG_CONTRACT= +# INDEXER_OPTIMISM_L1_PORTAL_CONTRACT= +# INDEXER_OPTIMISM_L1_START_BLOCK= +# INDEXER_OPTIMISM_L1_BATCH_INBOX= +# INDEXER_OPTIMISM_L1_BATCH_SUBMITTER= +# INDEXER_OPTIMISM_L1_BATCH_BLOCKS_CHUNK_SIZE= +# INDEXER_OPTIMISM_L1_BATCH_BLOCKSCOUT_BLOBS_API_URL= +# INDEXER_OPTIMISM_L1_BATCH_CELESTIA_BLOBS_API_URL= +# INDEXER_OPTIMISM_L1_BATCH_EIGENDA_BLOBS_API_URL= +# INDEXER_OPTIMISM_L1_BATCH_EIGENDA_PROXY_BASE_URL= +# INDEXER_OPTIMISM_L1_BATCH_ALT_DA_SERVER_URL= +# INDEXER_OPTIMISM_L2_BATCH_GENESIS_BLOCK_NUMBER= +# INDEXER_OPTIMISM_BLOCK_DURATION= +# INDEXER_OPTIMISM_L1_OUTPUT_ORACLE_CONTRACT= +# INDEXER_OPTIMISM_L2_WITHDRAWALS_START_BLOCK= +# INDEXER_OPTIMISM_L2_MESSAGE_PASSER_CONTRACT= +# INDEXER_OPTIMISM_L1_DEPOSITS_TRANSACTION_TYPE= +# INDEXER_OPTIMISM_L1_ETH_GET_LOGS_RANGE_SIZE= +# INDEXER_OPTIMISM_L2_ETH_GET_LOGS_RANGE_SIZE= +# INDEXER_OPTIMISM_L2_HOLOCENE_TIMESTAMP= +# INDEXER_OPTIMISM_L2_HOLOCENE_BLOCKS_CHUNK_SIZE= +# INDEXER_OPTIMISM_L2_ISTHMUS_TIMESTAMP= +# INDEXER_OPTIMISM_L2_JOVIAN_TIMESTAMP= +# INDEXER_OPTIMISM_L2_INTEROP_START_BLOCK= +# INDEXER_OPTIMISM_L2_INTEROP_BLOCKS_CHUNK_SIZE= +# INDEXER_OPTIMISM_CHAINSCOUT_API_URL= +# INDEXER_OPTIMISM_CHAINSCOUT_FALLBACK_MAP= +# INDEXER_OPTIMISM_INTEROP_PRIVATE_KEY= +# INDEXER_OPTIMISM_INTEROP_CONNECT_TIMEOUT= +# INDEXER_OPTIMISM_INTEROP_RECV_TIMEOUT= +# INDEXER_OPTIMISM_INTEROP_EXPORT_EXPIRATION_DAYS= +# INDEXER_OPTIMISM_MULTICHAIN_BATCH_SIZE= +# INDEXER_OPTIMISM_OPERATOR_FEE_QUEUE_CONCURRENCY= +# INDEXER_OPTIMISM_OPERATOR_FEE_QUEUE_BATCH_SIZE= +# INDEXER_OPTIMISM_OPERATOR_FEE_QUEUE_ENQUEUE_BUSY_WAITING_TIMEOUT= +# INDEXER_OPTIMISM_OPERATOR_FEE_QUEUE_MAX_QUEUE_SIZE= +# INDEXER_OPTIMISM_OPERATOR_FEE_QUEUE_INIT_QUERY_LIMIT= +# INDEXER_DISABLE_OPTIMISM_INTEROP_MULTICHAIN_EXPORT= +# INDEXER_SCROLL_L1_RPC= +# INDEXER_SCROLL_L1_MESSENGER_CONTRACT= +# INDEXER_SCROLL_L1_MESSENGER_START_BLOCK= +# INDEXER_SCROLL_L1_CHAIN_CONTRACT= +# INDEXER_SCROLL_L1_BATCH_START_BLOCK= +# INDEXER_SCROLL_L1_BATCH_BLOCKSCOUT_BLOBS_API_URL= +# INDEXER_SCROLL_L2_MESSENGER_CONTRACT= +# INDEXER_SCROLL_L2_MESSENGER_START_BLOCK= +# INDEXER_SCROLL_L2_GAS_ORACLE_CONTRACT= +# INDEXER_SCROLL_L1_ETH_GET_LOGS_RANGE_SIZE= +# INDEXER_SCROLL_L2_ETH_GET_LOGS_RANGE_SIZE= +# SCROLL_L2_CURIE_UPGRADE_BLOCK= +# SCROLL_L1_SCALAR_INIT= +# SCROLL_L1_OVERHEAD_INIT= +# SCROLL_L1_COMMIT_SCALAR_INIT= +# SCROLL_L1_BLOB_SCALAR_INIT= +# SCROLL_L1_BASE_FEE_INIT= +# SCROLL_L1_BLOB_BASE_FEE_INIT= +# INDEXER_ARC_NATIVE_TOKEN_DECIMALS= +# INDEXER_ARC_NATIVE_TOKEN_CONTRACT= +# INDEXER_ARC_NATIVE_TOKEN_SYSTEM_CONTRACT= +# CELO_CORE_CONTRACTS= +# CELO_L2_MIGRATION_BLOCK= +# CELO_EPOCH_MANAGER_CONTRACT= +# CELO_UNRELEASED_TREASURY_CONTRACT= +# CELO_VALIDATORS_CONTRACT= +# CELO_LOCKED_GOLD_CONTRACT= +# CELO_ACCOUNTS_CONTRACT= +# INDEXER_CELO_VALIDATOR_GROUP_VOTES_BATCH_SIZE=200000 +# INDEXER_DISABLE_CELO_EPOCH_FETCHER=false +# INDEXER_DISABLE_CELO_VALIDATOR_GROUP_VOTES_FETCHER=false +# INDEXER_CELO_ACCOUNTS_CONCURRENCY=1 +# INDEXER_CELO_ACCOUNTS_BATCH_SIZE=100 +# BERYX_API_TOKEN= +# BERYX_API_BASE_URL= +# FILECOIN_NETWORK_PREFIX=f +# INDEXER_DISABLE_FILECOIN_ADDRESS_INFO_FETCHER=false +# INDEXER_FILECOIN_ADDRESS_INFO_CONCURRENCY=1 +# INDEXER_REALTIME_FETCHER_MAX_GAP= +# INDEXER_REALTIME_FETCHER_POLLING_PERIOD= +# INDEXER_FETCHER_INIT_QUERY_LIMIT= +# INDEXER_FETCHER_INIT_DELAY= +# INDEXER_MASSIVE_BLOCK_THRESHOLD= +# INDEXER_TOKEN_BALANCES_FETCHER_INIT_QUERY_LIMIT= +# INDEXER_COIN_BALANCES_FETCHER_INIT_QUERY_LIMIT= +# INDEXER_GRACEFUL_SHUTDOWN_PERIOD= +# INDEXER_INTERNAL_TRANSACTIONS_FETCH_ORDER= +# INDEXER_SYSTEM_MEMORY_PERCENTAGE= +# WITHDRAWALS_FIRST_BLOCK= +# ROOTSTOCK_REMASC_ADDRESS= +# ROOTSTOCK_BRIDGE_ADDRESS= +# ROOTSTOCK_LOCKED_BTC_CACHE_PERIOD= +# ROOTSTOCK_LOCKING_CAP= +# INDEXER_DISABLE_ROOTSTOCK_DATA_FETCHER= +# INDEXER_ROOTSTOCK_DATA_FETCHER_INTERVAL= +# INDEXER_ROOTSTOCK_DATA_FETCHER_BATCH_SIZE= +# INDEXER_ROOTSTOCK_DATA_FETCHER_CONCURRENCY= +# INDEXER_ROOTSTOCK_DATA_FETCHER_DB_BATCH_SIZE= +# INDEXER_BEACON_RPC_URL=http://localhost:5052 +# INDEXER_DISABLE_BEACON_BLOB_FETCHER= +# INDEXER_BEACON_BLOB_FETCHER_SLOT_DURATION=12 +# INDEXER_BEACON_BLOB_FETCHER_REFERENCE_SLOT=8000000 +# INDEXER_BEACON_BLOB_FETCHER_REFERENCE_TIMESTAMP=1702824023 +# INDEXER_BEACON_BLOB_FETCHER_START_BLOCK=19200000 +# INDEXER_BEACON_BLOB_FETCHER_END_BLOCK=0 +# INDEXER_DISABLE_BEACON_DEPOSIT_FETCHER= +# INDEXER_BEACON_DEPOSIT_FETCHER_INTERVAL= +# INDEXER_BEACON_DEPOSIT_FETCHER_BATCH_SIZE= +# INDEXER_DISABLE_BEACON_DEPOSIT_STATUS_FETCHER= +# INDEXER_BEACON_DEPOSIT_STATUS_FETCHER_EPOCH_DURATION= +# INDEXER_BEACON_DEPOSIT_STATUS_FETCHER_REFERENCE_TIMESTAMP +# MISSING_BALANCE_OF_TOKENS_WINDOW_SIZE= +# INDEXER_INTERNAL_TRANSACTIONS_TRACER_TYPE= +# INDEXER_DISABLE_HOT_CONTRACTS_FETCHER= +# WEBAPP_URL= +# API_URL= +# CHECKSUM_ADDRESS_HASHES=true +# TXS_HISTORIAN_INIT_LAG=0 +TXS_STATS_DAYS_TO_COMPILE_AT_INIT=10 +COIN_BALANCE_HISTORY_DAYS=90 +# GAS_PRICE= +# GAS_PRICE_ORACLE_CACHE_PERIOD= +# GAS_PRICE_ORACLE_SIMPLE_TRANSACTION_GAS= +# GAS_PRICE_ORACLE_NUM_OF_BLOCKS= +# GAS_PRICE_ORACLE_SAFELOW_PERCENTILE= +# GAS_PRICE_ORACLE_AVERAGE_PERCENTILE= +# GAS_PRICE_ORACLE_FAST_PERCENTILE= +# GAS_PRICE_ORACLE_SAFELOW_TIME_COEFFICIENT= +# GAS_PRICE_ORACLE_AVERAGE_TIME_COEFFICIENT= +# GAS_PRICE_ORACLE_FAST_TIME_COEFFICIENT= +# ADDRESSES_BLACKLIST= +# ADDRESSES_BLACKLIST_KEY= +# ADDRESSES_BLACKLIST_URL= +# ADDRESSES_BLACKLIST_UPDATE_INTERVAL= +# ADDRESSES_BLACKLIST_RETRY_INTERVAL= +# ADDRESSES_BLACKLIST_PROVIDER= +# CHAIN_ID= +# HIDE_SCAM_ADDRESSES= +# RE_CAPTCHA_SECRET_KEY= +# RE_CAPTCHA_V3_SECRET_KEY= +RE_CAPTCHA_DISABLED=false +# RE_CAPTCHA_CHECK_HOSTNAME +# RE_CAPTCHA_SCORE_THRESHOLD +# RE_CAPTCHA_BYPASS_TOKEN +# RE_CAPTCHA_TOKEN_INSTANCE_REFETCH_METADATA_SCOPED_BYPASS_TOKEN= +# API_RATE_LIMIT_HAMMER_REDIS_URL=redis://redis-db:6379/1 +# API_RATE_LIMIT_IS_BLOCKSCOUT_BEHIND_PROXY=false +# API_RATE_LIMIT_UI_V2_TOKEN_TTL_IN_SECONDS=18000 +# FETCH_REWARDS_WAY=trace_block +# MICROSERVICE_SC_VERIFIER_ENABLED=true +# MICROSERVICE_SC_VERIFIER_URL=http://smart-contract-verifier:8050/ +# MICROSERVICE_SC_VERIFIER_TYPE=sc_verifier +# MICROSERVICE_SC_VERIFIER_API_KEY= +MICROSERVICE_SC_VERIFIER_TYPE=eth_bytecode_db +# MICROSERVICE_ETH_BYTECODE_DB_INTERVAL_BETWEEN_LOOKUPS=10m +# MICROSERVICE_ETH_BYTECODE_DB_MAX_LOOKUPS_CONCURRENCY=10 +MICROSERVICE_VISUALIZE_SOL2UML_ENABLED=true +MICROSERVICE_VISUALIZE_SOL2UML_URL=http://visualizer:8050/ +MICROSERVICE_SIG_PROVIDER_ENABLED=true +MICROSERVICE_SIG_PROVIDER_URL=http://sig-provider:8050/ +# MICROSERVICE_BENS_URL= +# MICROSERVICE_BENS_ENABLED= +# MICROSERVICE_BENS_PROTOCOLS= +# MICROSERVICE_ACCOUNT_ABSTRACTION_ENABLED=false +MICROSERVICE_ACCOUNT_ABSTRACTION_URL=http://user-ops-indexer:8050/ +# MICROSERVICE_METADATA_URL= +# MICROSERVICE_METADATA_ENABLED= +# MICROSERVICE_METADATA_PROXY_REQUESTS_TIMEOUT= +# MICROSERVICE_STYLUS_VERIFIER_URL= +# MICROSERVICE_MULTICHAIN_SEARCH_URL= +# MICROSERVICE_MULTICHAIN_SEARCH_API_KEY= +# MICROSERVICE_MULTICHAIN_SEARCH_ADDRESSES_CHUNK_SIZE= +# MICROSERVICE_MULTICHAIN_SEARCH_TOKEN_INFO_CHUNK_SIZE= +# MICROSERVICE_MULTICHAIN_SEARCH_COUNTERS_CHUNK_SIZE= +# MICROSERVICE_TAC_OPERATION_LIFECYCLE_ENABLED= +# MICROSERVICE_TAC_OPERATION_LIFECYCLE_URL= +DECODE_NOT_A_CONTRACT_CALLS=true +# DATABASE_READ_ONLY_API_URL= +# ACCOUNT_DATABASE_URL= +# ACCOUNT_POOL_SIZE= +# ACCOUNT_AUTH0_DOMAIN= +# ACCOUNT_AUTH0_CLIENT_ID= +# ACCOUNT_AUTH0_CLIENT_SECRET= +# ACCOUNT_AUTH0_APPLICATION_ID= +# ACCOUNT_SENDGRID_API_KEY= +# ACCOUNT_SENDGRID_SENDER= +# ACCOUNT_SENDGRID_TEMPLATE= +# ACCOUNT_SENDGRID_OTP_TEMPLATE= +# ACCOUNT_VERIFICATION_EMAIL_RESEND_INTERVAL= +# ACCOUNT_OTP_RESEND_INTERVAL= +# ACCOUNT_PRIVATE_TAGS_LIMIT=2000 +# ACCOUNT_WATCHLIST_ADDRESSES_LIMIT=15 +# ACCOUNT_SIWE_MESSAGE= +# ACCOUNT_DYNAMIC_ENV_ID= +# ACCOUNT_KEYCLOAK_DOMAIN= +# ACCOUNT_KEYCLOAK_REALM= +# ACCOUNT_KEYCLOAK_CLIENT_ID= +# ACCOUNT_KEYCLOAK_CLIENT_SECRET= +# ACCOUNT_KEYCLOAK_EMAIL_WEBHOOK_URL= +ACCOUNT_CLOAK_KEY= +ACCOUNT_ENABLED=false +ACCOUNT_REDIS_URL=redis://redis-db:6379 +# EIP_1559_ELASTICITY_MULTIPLIER=2 +# EIP_1559_BASE_FEE_MAX_CHANGE_DENOMINATOR=8 +# EIP_1559_BASE_FEE_LOWER_BOUND_WEI=0 +# IPFS_GATEWAY_URL= +# IPFS_GATEWAY_URL_PARAM_KEY= +# IPFS_GATEWAY_URL_PARAM_VALUE= +# IPFS_GATEWAY_URL_PARAM_LOCATION= +# IPFS_PUBLIC_GATEWAY_URL= +# MIGRATION_TOKEN_TRANSFER_TOKEN_TYPE_BATCH_SIZE= +# MIGRATION_TOKEN_TRANSFER_TOKEN_TYPE_CONCURRENCY= +# MIGRATION_SANITIZE_INCORRECT_NFT_BATCH_SIZE= +# MIGRATION_SANITIZE_INCORRECT_NFT_CONCURRENCY= +# MIGRATION_SANITIZE_INCORRECT_NFT_TIMEOUT= +# MIGRATION_TRANSACTIONS_TABLE_DENORMALIZATION_BATCH_SIZE= +# MIGRATION_TRANSACTIONS_TABLE_DENORMALIZATION_CONCURRENCY= +# MIGRATION_TOKEN_INSTANCE_OWNER_CONCURRENCY=5 +# MIGRATION_TOKEN_INSTANCE_OWNER_BATCH_SIZE=50 +# MIGRATION_RESTORE_OMITTED_WETH_TOKEN_TRANSFERS_CONCURRENCY= +# MIGRATION_RESTORE_OMITTED_WETH_TOKEN_TRANSFERS_BATCH_SIZE= +# MIGRATION_RESTORE_OMITTED_WETH_TOKEN_TRANSFERS_TIMEOUT= +# MIGRATION_SANITIZE_DUPLICATED_LOG_INDEX_LOGS_CONCURRENCY= +# MIGRATION_SANITIZE_DUPLICATED_LOG_INDEX_LOGS_BATCH_SIZE= +# MIGRATION_REFETCH_CONTRACT_CODES_BATCH_SIZE= +# MIGRATION_REFETCH_CONTRACT_CODES_CONCURRENCY= +# MIGRATION_BACKFILL_MULTICHAIN_SEARCH_BATCH_SIZE= +# MIGRATION_HEAVY_INDEX_OPERATIONS_CHECK_INTERVAL= +# MIGRATION_SANITIZE_VERIFIED_ADDRESSES_DISABLED=false +# MIGRATION_SANITIZE_VERIFIED_ADDRESSES_BATCH_SIZE=500 +# MIGRATION_SANITIZE_VERIFIED_ADDRESSES_CONCURRENCY=1 +# MIGRATION_SANITIZE_VERIFIED_ADDRESSES_TIMEOUT=0s +# MIGRATION_SANITIZE_INCORRECT_WETH_BATCH_SIZE=100 +# MIGRATION_SANITIZE_INCORRECT_WETH_CONCURRENCY=1 +# MIGRATION_SANITIZE_INCORRECT_WETH_TIMEOUT= +# MIGRATION_REINDEX_INTERNAL_TRANSACTIONS_STATUS_BATCH_SIZE= +# MIGRATION_REINDEX_INTERNAL_TRANSACTIONS_STATUS_CONCURRENCY= +# MIGRATION_REINDEX_INTERNAL_TRANSACTIONS_STATUS_TIMEOUT= +# MIGRATION_REINDEX_BLOCKS_WITH_MISSING_TRANSACTIONS_BATCH_SIZE= +# MIGRATION_REINDEX_BLOCKS_WITH_MISSING_TRANSACTIONS_CONCURRENCY= +# MIGRATION_REINDEX_BLOCKS_WITH_MISSING_TRANSACTIONS_TIMEOUT= +# MIGRATION_REINDEX_BLOCKS_WITH_MISSING_TRANSACTIONS_ENABLED= +# MIGRATION_SHRINK_INTERNAL_TRANSACTIONS_BATCH_SIZE= +# MIGRATION_SHRINK_INTERNAL_TRANSACTIONS_CONCURRENCY= +# MIGRATION_ARBITRUM_DA_RECORDS_NORMALIZATION_BATCH_SIZE= +# MIGRATION_ARBITRUM_DA_RECORDS_NORMALIZATION_CONCURRENCY= +# MIGRATION_FILECOIN_PENDING_ADDRESS_OPERATIONS_BATCH_SIZE= +# MIGRATION_FILECOIN_PENDING_ADDRESS_OPERATIONS_CONCURRENCY= +# MIGRATION_TOKEN_INSTANCE_ERC_1155_SANITIZE_CONCURRENCY=2 +# MIGRATION_TOKEN_INSTANCE_ERC_1155_SANITIZE_BATCH_SIZE=10 +# MIGRATION_TOKEN_INSTANCE_ERC_721_SANITIZE_CONCURRENCY=2 +# MIGRATION_TOKEN_INSTANCE_ERC_721_SANITIZE_BATCH_SIZE=10 +# MIGRATION_TOKEN_INSTANCE_ERC_721_SANITIZE_TOKENS_BATCH_SIZE=100 +# MIGRATION_SMART_CONTRACT_LANGUAGE_DISABLED=false +# MIGRATION_SMART_CONTRACT_LANGUAGE_BATCH_SIZE=100 +# MIGRATION_SMART_CONTRACT_LANGUAGE_CONCURRENCY=1 +# MIGRATION_SANITIZE_EMPTY_CONTRACT_CODE_ADDRESSES_BATCH_SIZE=500 +# MIGRATION_SANITIZE_EMPTY_CONTRACT_CODE_ADDRESSES_CONCURRENCY=1 +# MIGRATION_BACKFILL_METADATA_URL_DISABLED= +# MIGRATION_BACKFILL_METADATA_URL_BATCH_SIZE= +# MIGRATION_BACKFILL_METADATA_URL_CONCURRENCY= +# MIGRATION_RECOVERY_WETH_TOKEN_TRANSFERS_CONCURRENCY= +# MIGRATION_RECOVERY_WETH_TOKEN_TRANSFERS_BATCH_SIZE= +# MIGRATION_RECOVERY_WETH_TOKEN_TRANSFERS_TIMEOUT= +# MIGRATION_RECOVERY_WETH_TOKEN_TRANSFERS_BLOCKS_BATCH_SIZE= +# MIGRATION_RECOVERY_WETH_TOKEN_TRANSFERS_HIGH_VERBOSITY= +# MIGRATION_MERGE_ADJACENT_MISSING_BLOCK_RANGES_BATCH_SIZE= +# MIGRATION_DELETE_ZERO_VALUE_INTERNAL_TRANSACTIONS_ENABLED= +# MIGRATION_DELETE_ZERO_VALUE_INTERNAL_TRANSACTIONS_BATCH_SIZE= +# MIGRATION_DELETE_ZERO_VALUE_INTERNAL_TRANSACTIONS_STORAGE_PERIOD= +# MIGRATION_DELETE_ZERO_VALUE_INTERNAL_TRANSACTIONS_CHECK_INTERVAL= +# MIGRATION_FILL_INTERNAL_TRANSACTIONS_ADDRESS_IDS_BATCH_SIZE= +# MIGRATION_FILL_INTERNAL_TRANSACTIONS_ADDRESS_IDS_TIMEOUT= +# MIGRATION_EMPTY_INTERNAL_TRANSACTIONS_DATA_BATCH_SIZE= +# MIGRATION_EMPTY_INTERNAL_TRANSACTIONS_DATA_CONCURRENCY= +# MIGRATION_EMPTY_INTERNAL_TRANSACTIONS_DATA_TIMEOUT= +# HEALTH_MONITOR_CHECK_INTERVAL=1m +# HEALTH_MONITOR_BLOCKS_PERIOD=5m +# HEALTH_MONITOR_BATCHES_PERIOD=4h +# SOURCIFY_INTEGRATION_ENABLED=false +# SOURCIFY_SERVER_URL= +# SOURCIFY_REPO_URL= +# SOLIDITYSCAN_PLATFORM_ID= +# SOLIDITYSCAN_CHAIN_ID= +# SOLIDITYSCAN_API_TOKEN= +# NOVES_FI_BASE_API_URL= +# NOVES_FI_CHAIN_NAME= +# NOVES_FI_API_TOKEN= +# XNAME_BASE_API_URL= +# XNAME_API_TOKEN= +# BRIDGED_TOKENS_ENABLED= +# BRIDGED_TOKENS_ETH_OMNI_BRIDGE_MEDIATOR= +# BRIDGED_TOKENS_BSC_OMNI_BRIDGE_MEDIATOR= +# BRIDGED_TOKENS_POA_OMNI_BRIDGE_MEDIATOR= +# BRIDGED_TOKENS_AMB_BRIDGE_MEDIATORS +# BRIDGED_TOKENS_FOREIGN_JSON_RPC +# MUD_INDEXER_ENABLED= +# MUD_DATABASE_URL= +# MUD_POOL_SIZE=50 +# WETH_TOKEN_TRANSFERS_FILTERING_ENABLED=false +# WHITELISTED_WETH_CONTRACTS= +# PUBLIC_METRICS_ENABLED= +# PUBLIC_METRICS_UPDATE_PERIOD_HOURS= +# INDEXER_METRICS_ENABLED= +# INDEXER_METRICS_ENABLED_TOKEN_INSTANCES_NOT_UPLOADED_TO_CDN_COUNT= +# INDEXER_METRICS_ENABLED_FAILED_TOKEN_INSTANCES_METADATA_COUNT= +# INDEXER_METRICS_ENABLED_UNFETCHED_TOKEN_INSTANCES_COUNT= +# INDEXER_METRICS_ENABLED_MISSING_CURRENT_TOKEN_BALANCES_COUNT= +# INDEXER_METRICS_ENABLED_MISSING_ARCHIVAL_TOKEN_BALANCES_COUNT= +# CSV_EXPORT_LIMIT= +# CSV_EXPORT_ASYNC_ENABLED= +# CSV_EXPORT_ASYNC_MAX_PENDING_TASKS_PER_IP=3 +# CSV_EXPORT_ASYNC_UPLOAD_CHUNK_SIZE=47185920 +# CSV_EXPORT_DB_TIMEOUT= +# CSV_EXPORT_ASYNC_TMP_DIR=/app/tmp +# CSV_EXPORT_ASYNC_GOKAPI_URL= +# CSV_EXPORT_ASYNC_GOKAPI_API_KEY= +# CSV_EXPORT_ASYNC_GOKAPI_TIMEOUT= +# CSV_EXPORT_ASYNC_GOKAPI_UPLOAD_EXPIRY_DAYS=1 +# CSV_EXPORT_ASYNC_GOKAPI_UPLOAD_ALLOWED_DOWNLOADS=1 +# CSV_EXPORT_ASYNC_OBAN_CONCURRENCY=10 +# SHRINK_INTERNAL_TRANSACTIONS_ENABLED= +NFT_MEDIA_HANDLER_ENABLED=true +NFT_MEDIA_HANDLER_REMOTE_DISPATCHER_NODE_MODE_ENABLED=true +NFT_MEDIA_HANDLER_BUCKET_FOLDER=/folder_1 +RELEASE_NODE=producer@172.18.0.4 +RELEASE_DISTRIBUTION=name +RELEASE_COOKIE=secret_cookie +# NFT_MEDIA_HANDLER_AWS_PUBLIC_BUCKET_URL= +# NFT_MEDIA_HANDLER_BACKFILL_ENABLED= +# NFT_MEDIA_HANDLER_BACKFILL_QUEUE_SIZE= +# NFT_MEDIA_HANDLER_BACKFILL_ENQUEUE_BUSY_WAITING_TIMEOUT= +# NFT_MEDIA_HANDLER_CACHE_UNIQUENESS_MAX_SIZE= +# K8S_SERVICE=blockscout-headless + +# old UI vars + +# SHOW_TENDERLY_LINK=false +# TENDERLY_CHAIN_PATH= +# NETWORK= +# SUBNETWORK=Awesome chain +# LOGO=/images/blockscout_logo.svg +# SUPPORTED_CHAINS={} +# JSON_RPC= +# APPS_MENU=true +# EXTERNAL_APPS=[] +# SHOW_ADDRESS_MARKETCAP_PERCENTAGE=true +# TXS_STATS_ENABLED=false +# SHOW_MAINTENANCE_ALERT=false +# MAINTENANCE_ALERT_MESSAGE= +# SHOW_PRICE_CHART=false +# SHOW_PRICE_CHART_LEGEND=false +# SHOW_TXS_CHART=true +# FOOTER_CHAT_LINK= +# FOOTER_FORUM_LINK_ENABLED= +# FOOTER_FORUM_LINK= +# FOOTER_TELEGRAM_LINK_ENABLED= +# FOOTER_TELEGRAM_LINK= +# FOOTER_GITHUB_LINK= +# FOOTER_LOGO=/images/blockscout_logo.svg +# FOOTER_LINK_TO_OTHER_EXPLORERS=false +# FOOTER_OTHER_EXPLORERS={} +# CONTRACT_MAX_STRING_LENGTH_WITHOUT_TRIMMING=2040 +# CONTRACT_DISABLE_INTERACTION= +# HIDE_BLOCK_MINER=false +# BLOCK_MINER_GETS_BURNT_FEES=false +# DISPLAY_TOKEN_ICONS=false +# MIXPANEL_TOKEN= +# MIXPANEL_URL= +# AMPLITUDE_API_KEY= +# AMPLITUDE_URL= +# NETWORK_PATH=/ +# RE_CAPTCHA_CLIENT_KEY= +# RE_CAPTCHA_V3_CLIENT_KEY= +# HACKNEY_DEFAULT_POOL_SIZE=1000 +# UNIVERSAL_PROXY_CONFIG_URL= diff --git a/docker/Makefile b/docker/Makefile index f2a918ab7e4b..e82caec18210 100644 --- a/docker/Makefile +++ b/docker/Makefile @@ -10,7 +10,7 @@ STATS_CONTAINER_NAME := stats STATS_DB_CONTAINER_NAME := stats-db PROXY_CONTAINER_NAME := proxy PG_CONTAINER_NAME := postgres -RELEASE_VERSION ?= '10.2.1' +RELEASE_VERSION ?= '11.0.0' TAG := $(RELEASE_VERSION)-commit-$(shell git log -1 --pretty=format:"%h") STABLE_TAG := $(RELEASE_VERSION) diff --git a/mix.exs b/mix.exs index b151967a0e3c..21c2b8ddf104 100644 --- a/mix.exs +++ b/mix.exs @@ -7,7 +7,7 @@ defmodule BlockScout.Mixfile do [ # app: :block_scout, # aliases: aliases(config_env()), - version: "10.2.1", + version: "11.0.0", apps_path: "apps", deps: deps(), dialyzer: dialyzer(), diff --git a/mix.lock b/mix.lock index 23af433a5b84..c4b5d5931df0 100644 --- a/mix.lock +++ b/mix.lock @@ -16,8 +16,8 @@ "bypass": {:hex, :bypass, "2.1.0", "909782781bf8e20ee86a9cabde36b259d44af8b9f38756173e8f5e2e1fabb9b1", [:mix], [{:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "d9b5df8fa5b7a6efa08384e9bbecfe4ce61c77d28a4282f79e02f1ef78d96b80"}, "cachex": {:hex, :cachex, "4.1.1", "574c5cd28473db313a0a76aac8c945fe44191659538ca6a1e8946ec300b1a19f", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:ex_hash_ring, "~> 6.0", [hex: :ex_hash_ring, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm", "d6b7449ff98d6bb92dda58bd4fc3189cae9f99e7042054d669596f56dc503cd8"}, "cafezinho": {:hex, :cafezinho, "0.4.4", "36c31fc5456b1284180d8a9d968c7eaaf474782df5af023a8f8b66937c5b2785", [:mix], [{:rustler, ">= 0.0.0", [hex: :rustler, repo: "hexpm", optional: true]}, {:rustler_precompiled, "~> 0.8", [hex: :rustler_precompiled, repo: "hexpm", optional: false]}], "hexpm", "3ca334f2a2992ec081868c39ad0487eb97d213292702aae6b197fd5e6f04fd58"}, - "castore": {:hex, :castore, "1.0.17", "4f9770d2d45fbd91dcf6bd404cf64e7e58fed04fadda0923dc32acca0badffa2", [:mix], [], "hexpm", "12d24b9d80b910dd3953e165636d68f147a31db945d2dcb9365e441f8b5351e5"}, - "cbor": {:hex, :cbor, "1.0.1", "39511158e8ea5a57c1fcb9639aaa7efde67129678fee49ebbda780f6f24959b0", [:mix], [], "hexpm", "5431acbe7a7908f17f6a9cd43311002836a34a8ab01876918d8cfb709cd8b6a2"}, + "castore": {:hex, :castore, "1.0.18", "5e43ef0ec7d31195dfa5a65a86e6131db999d074179d2ba5a8de11fe14570f55", [:mix], [], "hexpm", "f393e4fe6317829b158fb74d86eb681f737d2fe326aa61ccf6293c4104957e34"}, + "cbor": {:hex, :cbor, "1.0.2", "9b0af85af291a556e10a0ffd48ba9a21a75e711828fafd3af193d56d95f0907f", [:mix], [], "hexpm", "edbc9b4a16eb93a582437b9b249c340a75af03958e338fb43d8c1be9fc65b864"}, "cc_precompiler": {:hex, :cc_precompiler, "0.1.11", "8c844d0b9fb98a3edea067f94f616b3f6b29b959b6b3bf25fee94ffe34364768", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "3427232caf0835f94680e5bcf082408a70b48ad68a5f5c0b02a3bea9f3a075b9"}, "certifi": {:hex, :certifi, "2.15.0", "0e6e882fcdaaa0a5a9f2b3db55b1394dba07e8d6d9bcad08318fb604c6839712", [:rebar3], [], "hexpm", "b147ed22ce71d72eafdad94f055165c1c182f61a2ff49df28bcc71d1d5b94a60"}, "cldr_utils": {:hex, :cldr_utils, "2.29.5", "f43161e04acb4016f5841b2320d69120d51827f5346babb2227893a2c5916dc8", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.5", [hex: :certifi, repo: "hexpm", optional: true]}, {:decimal, "~> 1.9 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "962d3a2028b232ee0a5373941dc411028a9442f53444a4d5d2c354f687db1835"}, @@ -32,7 +32,7 @@ "cowboy": {:hex, :cowboy, "2.14.2", "4008be1df6ade45e4f2a4e9e2d22b36d0b5aba4e20b0a0d7049e28d124e34847", [:make, :rebar3], [{:cowlib, ">= 2.16.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, ">= 1.8.0 and < 3.0.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "569081da046e7b41b5df36aa359be71a0c8874e5b9cff6f747073fc57baf1ab9"}, "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, "cowlib": {:hex, :cowlib, "2.16.0", "54592074ebbbb92ee4746c8a8846e5605052f29309d3a873468d76cdf932076f", [:make, :rebar3], [], "hexpm", "7f478d80d66b747344f0ea7708c187645cfcc08b11aa424632f78e25bf05db51"}, - "credo": {:hex, :credo, "1.7.17", "f92b6aa5b26301eaa5a35e4d48ebf5aa1e7094ac00ae38f87086c562caf8a22f", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "1eb5645c835f0b6c9b5410f94b5a185057bcf6d62a9c2b476da971cde8749645"}, + "credo": {:hex, :credo, "1.7.18", "5c5596bf7aedf9c8c227f13272ac499fe8eae6237bd326f2f07dfc173786f042", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "a189d164685fd945809e862fe76a7420c4398fa288d76257662aecb909d6b3e5"}, "csv": {:hex, :csv, "2.5.0", "c47b5a5221bf2e56d6e8eb79e77884046d7fd516280dc7d9b674251e0ae46246", [:mix], [{:parallel_stream, "~> 1.0.4 or ~> 1.1.0", [hex: :parallel_stream, repo: "hexpm", optional: false]}], "hexpm", "e821f541487045c7591a1963eeb42afff0dfa99bdcdbeb3410795a2f59c77d34"}, "dataloader": {:hex, :dataloader, "2.0.2", "c45075e0692e68638a315e14f747bd8d7065fb5f38705cf980f62d4cd344401f", [:mix], [{:ecto, ">= 3.4.3 and < 4.0.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:opentelemetry_process_propagator, "~> 0.2.1 or ~> 0.3", [hex: :opentelemetry_process_propagator, repo: "hexpm", optional: true]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4c6cabc0b55e96e7de74d14bf37f4a5786f0ab69aa06764a1f39dda40079b098"}, "db_connection": {:hex, :db_connection, "2.9.0", "a6a97c5c958a2d7091a58a9be40caf41ab496b0701d21e1d1abff3fa27a7f371", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "17d502eacaf61829db98facf6f20808ed33da6ccf495354a41e64fe42f9c509c"}, @@ -57,9 +57,9 @@ "ex_brotli": {:hex, :ex_brotli, "0.5.0", "573645db5201317b6176b8858b668ea4ca89dc5c21852e84b9867579d483c220", [:mix], [{:phoenix, ">= 0.0.0", [hex: :phoenix, repo: "hexpm", optional: true]}, {:rustler, "~> 0.29", [hex: :rustler, repo: "hexpm", optional: true]}, {:rustler_precompiled, "~> 0.6", [hex: :rustler_precompiled, repo: "hexpm", optional: false]}], "hexpm", "8447d98d51f8f312629fd38619d4f564507dcf3a03d175c3f8f4ddf98e46dd92"}, "ex_cldr": {:hex, :ex_cldr, "2.47.2", "c866f4b45523abd25eea3e5252eb91364296dd15bddf970db1c78cd38f25df9a", [:mix], [{:cldr_utils, "~> 2.29", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:gettext, "~> 0.19 or ~> 1.0", [hex: :gettext, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: true]}], "hexpm", "4a7cef380a1c2546166b45d6ee5e8e2f707ea695b12ae6dadd250201588b4f16"}, "ex_cldr_currencies": {:hex, :ex_cldr_currencies, "2.17.1", "89947c7102ff1b46fc46095624239a1c3d72499b19ed650597630771d9e4a662", [:mix], [{:ex_cldr, "~> 2.38", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "e266a0a61f4c7d83608154d49b59e4d7485b2aaa7ba1d0e17b3c55910595de51"}, - "ex_cldr_lists": {:hex, :ex_cldr_lists, "2.12.1", "8262d36a725bd77ab16cfeb8cac38efcd92f8e8039e2f8cf91164ec2cfb739a6", [:mix], [{:ex_cldr_numbers, "~> 2.25", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:ex_doc, "~> 0.18", [hex: :ex_doc, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "946bafd71bd98d8109f59bdd66ea55824b663329ab1bbf677489e144fd9ddc8d"}, + "ex_cldr_lists": {:hex, :ex_cldr_lists, "2.12.2", "731532a3c0a9bfd062acbe79fd39affa4d6e3e5aa5b08a7405524166ce1cd47a", [:mix], [{:ex_cldr_numbers, "~> 2.25", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:ex_doc, "~> 0.18", [hex: :ex_doc, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "16e0b6dc63091c71a0c9a2c50e13db64798f261c3e121d7e3fb7d354872180e8"}, "ex_cldr_numbers": {:hex, :ex_cldr_numbers, "2.38.1", "e5124e288a8e672831e10d39530ecb5329bc9af2169709ebfbadc814cae7d4fb", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:digital_token, "~> 0.3 or ~> 1.0", [hex: :digital_token, repo: "hexpm", optional: false]}, {:ex_cldr, "~> 2.45", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:ex_cldr_currencies, "~> 2.17", [hex: :ex_cldr_currencies, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "4f95738f1dc4e821485e52226666f7691c9276bf6eba49cba8d23c8a2db05e84"}, - "ex_cldr_units": {:hex, :ex_cldr_units, "3.20.2", "c9c99a5e8e921d87bfbd424f1d91c0db04cb6ea00e02161a50c57519130dc2ea", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ex_cldr_lists, "~> 2.10", [hex: :ex_cldr_lists, repo: "hexpm", optional: false]}, {:ex_cldr_numbers, "~> 2.36", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:ex_doc, "~> 0.18", [hex: :ex_doc, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "b6ae5a4bbf1252a9ddffc0dbfe31bc81c0f2545050376ad5bdf6e99292b5d9cf"}, + "ex_cldr_units": {:hex, :ex_cldr_units, "3.20.3", "3b0588a9956aa432b1df6ae502ec75f27a221d331741b4e504c09ef855cc18be", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ex_cldr_lists, "~> 2.10", [hex: :ex_cldr_lists, repo: "hexpm", optional: false]}, {:ex_cldr_numbers, "~> 2.36", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:ex_doc, "~> 0.18", [hex: :ex_doc, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "0a2e7ce4f46e4b72bbbc7a9bb7def2e4d3910b76e41cace8b7d117801cd9ddcd"}, "ex_doc": {:hex, :ex_doc, "0.40.1", "67542e4b6dde74811cfd580e2c0149b78010fd13001fda7cfeb2b2c2ffb1344d", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "bcef0e2d360d93ac19f01a85d58f91752d930c0a30e2681145feea6bd3516e00"}, "ex_eth_bls": {:hex, :ex_eth_bls, "0.1.0", "33c2bf424b360e4b64d7630dd72ec028dac63df56802d0a14ade54a23ad1c743", [:mix], [{:rustler, ">= 0.0.0", [hex: :rustler, repo: "hexpm", optional: true]}, {:rustler_precompiled, "~> 0.8", [hex: :rustler_precompiled, repo: "hexpm", optional: false]}], "hexpm", "25f6ffc36de4952e55adff1c712f0b9680850773678550f1da970d4d18329365"}, "ex_hash_ring": {:hex, :ex_hash_ring, "6.0.4", "bef9d2d796afbbe25ab5b5a7ed746e06b99c76604f558113c273466d52fa6d6b", [:mix], [], "hexpm", "89adabf31f7d3dfaa36802ce598ce918e9b5b33bae8909ac1a4d052e1e567d18"}, @@ -78,7 +78,7 @@ "file_info": {:hex, :file_info, "0.0.4", "2e0e77f211e833f38ead22cb29ce53761d457d80b3ffe0ffe0eb93880b0963b2", [:mix], [{:mimetype_parser, "~> 0.1.2", [hex: :mimetype_parser, repo: "hexpm", optional: false]}], "hexpm", "50e7ad01c2c8b9339010675fe4dc4a113b8d6ca7eddce24d1d74fd0e762781a5"}, "file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"}, "finch": {:hex, :finch, "0.18.0", "944ac7d34d0bd2ac8998f79f7a811b21d87d911e77a786bc5810adb75632ada4", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69f5045b042e531e53edc2574f15e25e735b522c37e2ddb766e15b979e03aa65"}, - "floki": {:hex, :floki, "0.38.0", "62b642386fa3f2f90713f6e231da0fa3256e41ef1089f83b6ceac7a3fd3abf33", [:mix], [], "hexpm", "a5943ee91e93fb2d635b612caf5508e36d37548e84928463ef9dd986f0d1abd9"}, + "floki": {:hex, :floki, "0.38.1", "f002ccac94b3bcb21d40d9b34cc2cc9fd88a8311879120330075b5dde657ebee", [:mix], [], "hexpm", "e744bf0db7ee34b2c8b62767f04071107af0516a81144b9a2f73fe0494200e5b"}, "flow": {:hex, :flow, "1.2.4", "1dd58918287eb286656008777cb32714b5123d3855956f29aa141ebae456922d", [:mix], [{:gen_stage, "~> 1.0", [hex: :gen_stage, repo: "hexpm", optional: false]}], "hexpm", "874adde96368e71870f3510b91e35bc31652291858c86c0e75359cbdd35eb211"}, "gen_stage": {:hex, :gen_stage, "1.2.1", "19d8b5e9a5996d813b8245338a28246307fd8b9c99d1237de199d21efc4c76a1", [:mix], [], "hexpm", "83e8be657fa05b992ffa6ac1e3af6d57aa50aace8f691fcf696ff02f8335b001"}, "gettext": {:hex, :gettext, "0.26.2", "5978aa7b21fada6deabf1f6341ddba50bc69c999e812211903b169799208f2a8", [:mix], [{:expo, "~> 0.5.1 or ~> 1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "aa978504bcf76511efdc22d580ba08e2279caab1066b76bb9aa81c4a1e0a32a5"}, @@ -126,6 +126,7 @@ "numbers": {:hex, :numbers, "5.2.4", "f123d5bb7f6acc366f8f445e10a32bd403c8469bdbce8ce049e1f0972b607080", [:mix], [{:coerce, "~> 1.0", [hex: :coerce, repo: "hexpm", optional: false]}, {:decimal, "~> 1.9 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "eeccf5c61d5f4922198395bf87a465b6f980b8b862dd22d28198c5e6fab38582"}, "nx": {:hex, :nx, "0.10.0", "128e4a094cb790f663e20e1334b127c1f2a4df54edfb8b13c22757ec33133b4f", [:mix], [{:complex, "~> 0.6", [hex: :complex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3db8892c124aeee091df0e6fbf8e5bf1b81f502eb0d4f5ba63e6378ebcae7da4"}, "oauth2": {:hex, :oauth2, "2.1.0", "beb657f393814a3a7a8a15bd5e5776ecae341fd344df425342a3b6f1904c2989", [:mix], [{:tesla, "~> 1.5", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm", "8ac07f85b3307dd1acfeb0ec852f64161b22f57d0ce0c15e616a1dfc8ebe2b41"}, + "oban": {:hex, :oban, "2.20.3", "e4d27336941955886cc7113420c32c63b70b64f10b27e08e3cf2b001153953cd", [:mix], [{:ecto_sql, "~> 3.10", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:igniter, "~> 0.5", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.20", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 1.3", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "075ffbf1279a96bec495bc63d647b08929837d70bcc0427249ffe4d1dddaec33"}, "open_api_spex": {:hex, :open_api_spex, "3.22.2", "0b3c4f572ee69cb6c936abf426b9d84d8eebd34960871fd77aead746f0d69cb0", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0 or ~> 6.0", [hex: :poison, repo: "hexpm", optional: true]}, {:ymlr, "~> 2.0 or ~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "0a4fc08472d75e9cfe96e0748c6b1565b3b4398f97bf43fcce41b41b6fd3fb33"}, "optimal": {:hex, :optimal, "0.3.6", "46bbf52fbbbd238cda81e02560caa84f93a53c75620f1fe19e81e4ae7b07d1dd", [:mix], [], "hexpm", "1a06ea6a653120226b35b283a1cd10039550f2c566edcdec22b29316d73640fd"}, "parallel_stream": {:hex, :parallel_stream, "1.1.0", "f52f73eb344bc22de335992377413138405796e0d0ad99d995d9977ac29f1ca9", [:mix], [], "hexpm", "684fd19191aedfaf387bbabbeb8ff3c752f0220c8112eb907d797f4592d6e871"}, @@ -135,7 +136,7 @@ "phoenix_html": {:hex, :phoenix_html, "4.2.1", "35279e2a39140068fc03f8874408d58eef734e488fc142153f055c5454fd1c08", [:mix], [], "hexpm", "cff108100ae2715dd959ae8f2a8cef8e20b593f8dfd031c9cba92702cf23e053"}, "phoenix_html_helpers": {:hex, :phoenix_html_helpers, "1.0.1", "7eed85c52eff80a179391036931791ee5d2f713d76a81d0d2c6ebafe1e11e5ec", [:mix], [{:phoenix_html, "~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "cffd2385d1fa4f78b04432df69ab8da63dc5cf63e07b713a4dcf36a3740e3090"}, "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.6.2", "b18b0773a1ba77f28c52decbb0f10fd1ac4d3ae5b8632399bbf6986e3b665f62", [:mix], [{:file_system, "~> 0.2.10 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "d1f89c18114c50d394721365ffb428cce24f1c13de0467ffa773e2ff4a30d5b9"}, - "phoenix_live_view": {:hex, :phoenix_live_view, "1.1.27", "9afcab28b0c82afdc51044e661bcd5b8de53d242593d34c964a37710b40a42af", [:mix], [{:igniter, ">= 0.6.16 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:lazy_html, "~> 0.1.0", [hex: :lazy_html, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0 or ~> 1.8.0-rc", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "415735d0b2c612c9104108b35654e977626a0cb346711e1e4f1ed16e3c827ede"}, + "phoenix_live_view": {:hex, :phoenix_live_view, "1.1.28", "8a8e123d018025f756605a2fb02a4854f0d3cd7b207f710fef1fd5d9d72d0254", [:mix], [{:igniter, ">= 0.6.16 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:lazy_html, "~> 0.1.0", [hex: :lazy_html, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0 or ~> 1.8.0-rc", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "24faad535b65089642c3a7d84088109dc58f49c1f1c5a978659855d643466353"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.2.0", "ff3a5616e1bed6804de7773b92cbccfc0b0f473faf1f63d7daf1206c7aeaaa6f", [:mix], [], "hexpm", "adc313a5bf7136039f63cfd9668fde73bba0765e0614cba80c06ac9460ff3e96"}, "phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"}, "phoenix_view": {:hex, :phoenix_view, "2.0.4", "b45c9d9cf15b3a1af5fb555c674b525391b6a1fe975f040fb4d913397b31abf4", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "4e992022ce14f31fe57335db27a28154afcc94e9983266835bb3040243eb620b"}, diff --git a/rel/config.exs b/rel/config.exs index d85fa7fac753..3334e903722d 100644 --- a/rel/config.exs +++ b/rel/config.exs @@ -71,7 +71,7 @@ end # will be used by default release :blockscout do - set version: "10.2.1" + set version: "11.0.0" set applications: [ :runtime_tools, block_scout_web: :permanent,