From c58fd56041a122f361bc24ea7f774a7e9a0ac284 Mon Sep 17 00:00:00 2001 From: kalm Date: Wed, 25 Dec 2024 08:35:34 -0800 Subject: [PATCH] explorer: native contract source code UI This commit introduces functionality for displaying native contract source code in the DarkFi explorer application, enabling users to view available contracts from the home page, access associated source files, and examine native contract Rust code and zk proofs. This enhancement provides accessibility to native contract information. To support these updates, we have added new pages to the UI, updated styles for rendering source-related pages, and implemented new RPC routes to facilitate contract-related code retrieval. Additionally, we have restructured the application into Flask blueprints to address the growing complexity of the explorer and adhere to best practices, as app.py was becoming large with diverging functionality. This proactive change prepares us for future expansions, including better organization of graph functionalities. The organization also mirrors the rest of the explorer codebase by maintaining separate pages for contracts, transactions, and blocks, as well as corresponding RPC and service rust modules. UI Support for Native Contract Source Code - We have added source code functionality to the UI, Flask blueprints, and RPC routes defined in rpc.py, along with updated styles to effectively render source-related pages. Style Updates: - Expanded the color palette for rendering source files. - Enhanced styles for displaying contract IDs and names in the native contracts table. - Implemented layout elements for proper display of source code lists and content. - Added styles for source file list links, including hover effects. RPC Routes: - New routes have been added to facilitate requests to the DarkFi daemon for retrieving native contracts, source code paths, and contract source. UI Enhancements: - A native contract section has been introduced on the explorer home page to display native contracts. - A contract source list page allows users to view and click on source code files associated with a native contract. - A dedicated contract source page renders the source for a specific path associated with a given contract. Introducing Flask Blueprints To better manage the growing complexity of the application, we introduced Flask blueprints that organize routes according to their functionality. This restructuring not only enhances readability but also aligns with best practices for Flask applications, facilitating future development. App.py Updates: - Routes have been moved to their respective blueprints. Added Blueprints: - `__init__.py` file initializes the explorer blueprint package and exposes various blueprints used by the application. - `block.py` module defines a blueprint for handling block-related functionality. - `contract.py` module defines a blueprint for managing contract-related functionality. - explore.py` module manages general functionality related to the home page and search features. - `transaction.py` module handles transaction-related functionality. Python Documentation: - We documented the Python code and updated existing documentation to adhere to Python's best practices for using docstrings, enhancing usability and integration with documentation tools. Testing: - Manual testing has been conducted through the UI to verify the functionality of the new RPC methods, ensuring they correctly retrieve and display contract-related information. --- .../research/blockchain-explorer/site/app.py | 97 +++++++++---------- .../site/blueprints/__init__.py | 36 +++++++ .../site/blueprints/block.py | 48 +++++++++ .../site/blueprints/contract.py | 94 ++++++++++++++++++ .../site/blueprints/explore.py | 94 ++++++++++++++++++ .../site/blueprints/transaction.py | 45 +++++++++ .../blockchain-explorer/site/requirements.txt | 1 + .../research/blockchain-explorer/site/rpc.py | 68 ++++++++++--- .../blockchain-explorer/site/static/style.css | 89 +++++++++++++++++ .../site/templates/contract_source.html | 46 +++++++++ .../site/templates/contract_source_list.html | 55 +++++++++++ .../site/templates/index.html | 25 +++++ 12 files changed, 634 insertions(+), 64 deletions(-) create mode 100644 script/research/blockchain-explorer/site/blueprints/__init__.py create mode 100644 script/research/blockchain-explorer/site/blueprints/block.py create mode 100644 script/research/blockchain-explorer/site/blueprints/contract.py create mode 100644 script/research/blockchain-explorer/site/blueprints/explore.py create mode 100644 script/research/blockchain-explorer/site/blueprints/transaction.py create mode 100644 script/research/blockchain-explorer/site/templates/contract_source.html create mode 100644 script/research/blockchain-explorer/site/templates/contract_source_list.html diff --git a/script/research/blockchain-explorer/site/app.py b/script/research/blockchain-explorer/site/app.py index b7f69092c..53efe2eda 100644 --- a/script/research/blockchain-explorer/site/app.py +++ b/script/research/blockchain-explorer/site/app.py @@ -15,63 +15,62 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -import rpc -from flask import Flask, request, render_template +""" +Module: app.py -app = Flask(__name__) +This module initializes the DarkFi explorer Flask application by registering various blueprints for handling routes +related to blocks, contracts, transactions, search, and the explore section, including the home page. It also defines +error handlers, ensuring appropriate responses for these common HTTP errors. +""" -@app.route('/') -async def index(): - # Fetch data from RPC - blocks = await rpc.get_last_n_blocks("10") - basic_stats = await rpc.get_basic_statistics() +from flask import Flask, render_template - # Fetch the metric statistics - metric_stats = await rpc.get_metric_statistics() - has_metrics = metric_stats and isinstance(metric_stats, list) +from blueprints.explore import explore_bp +from blueprints.block import block_bp +from blueprints.contract import contract_bp +from blueprints.transaction import transaction_bp - # Get the latest metric statistics, or use None if no metrics are found - if has_metrics: - latest_metric_stats = metric_stats[-1] - else: - latest_metric_stats = None +def create_app(): + """ + Creates and configures the DarkFi explorer Flask application. - # Render template - return render_template( - 'index.html', - blocks=blocks, - basic_stats=basic_stats, - metric_stats=latest_metric_stats, - ) + This function creates and initializes the explorer the Flask app, + registering applicable blueprints for handling explorer-related routes, + and defining error handling for common HTTP errors. It returns a fully + configured Flask application instance. + """ + app = Flask(__name__) -@app.route('/search', methods=['GET', 'POST']) -async def search(): - search_hash = request.args.get('search_hash', '') - block = await rpc.get_block(search_hash) - transactions = await rpc.get_block_transactions(search_hash) - if transactions: - return render_template('block.html', block=block, transactions=transactions) - else: - transaction = await rpc.get_transaction(search_hash) - return render_template('transaction.html', transaction=transaction) + # Register Blueprints + app.register_blueprint(explore_bp) + app.register_blueprint(block_bp) + app.register_blueprint(contract_bp) + app.register_blueprint(transaction_bp) -@app.route('/block/') -async def block(header_hash): - block = await rpc.get_block(header_hash) - transactions = await rpc.get_block_transactions(header_hash) - return render_template('block.html', block=block, transactions=transactions) + # Define page not found error handler + @app.errorhandler(404) + def page_not_found(e): + """ + Handles 404 errors by rendering a custom 404 error page when a requested page is not found, + returning a rendered template along with a 404 status code. + Args: + e: The error object associated with the 404 error. + """ + # Render the custom 404 error page + return render_template('404.html'), 404 -@app.route('/transaction/') -async def transaction(transaction_hash): - transaction = await rpc.get_transaction(transaction_hash) - return render_template('transaction.html', transaction=transaction) + # Define internal server error handler + @app.errorhandler(500) + def internal_server_error(e): + """ + Handles 500 errors by rendering a custom 500 error page when an internal server error occurs, + returning a rendered template along with a 500 status code. -@app.errorhandler(404) -def page_not_found(e): - return render_template('404.html'), 404 - -@app.errorhandler(500) -def page_not_found(e): - return render_template('500.html'), 500 + Args: + e: The error object associated with the 500 error. + """ + # Render the custom 500 error page + return render_template('500.html'), 500 + return app diff --git a/script/research/blockchain-explorer/site/blueprints/__init__.py b/script/research/blockchain-explorer/site/blueprints/__init__.py new file mode 100644 index 000000000..f5356cb41 --- /dev/null +++ b/script/research/blockchain-explorer/site/blueprints/__init__.py @@ -0,0 +1,36 @@ +# This file is part of DarkFi (https://dark.fi) +# +# Copyright (C) 2020-2024 Dyne.org foundation +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +""" +Explorer blueprint package initializer. + +This module imports and exposes blueprints for used by the explorer +flask application. + +Exposed Blueprints: + - explore_bp: The explorer application blueprint. + - block_bp: The blueprint for block-related functionality. + - contract_bp: The blueprint for contract-related functionality. + - transaction_bp: The blueprint for transaction-related functionality. +""" +from .explore import explore_bp +from .block import block_bp +from .contract import contract_bp +from .transaction import transaction_bp + +# Expose blueprints for importing +__all__ = ["explore_bp", "block_bp", "contract_bp", "transaction_bp"] diff --git a/script/research/blockchain-explorer/site/blueprints/block.py b/script/research/blockchain-explorer/site/blueprints/block.py new file mode 100644 index 000000000..b025331b4 --- /dev/null +++ b/script/research/blockchain-explorer/site/blueprints/block.py @@ -0,0 +1,48 @@ +# This file is part of DarkFi (https://dark.fi) +# +# Copyright (C) 2020-2024 Dyne.org foundation +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +""" +Blueprint: block_bp + +This module defines a Flask blueprint (`block_bp`) for handling block-related functionality, +serving as a primary location for Flask code related to routes and features associated with blocks. +""" + +from flask import Blueprint, render_template + +import rpc + +# Create block blueprint +block_bp = Blueprint("block", __name__) + +@block_bp.route('/block/') +async def block(header_hash): + """ + Retrieves and displays details of a specific block and its associated transactions based + on the provided header hash using RPC calls to the explorer daemon, returning a rendered template. + + Path Args: + header_hash (str): The header hash of the block to retrieve. + """ + # Fetch the block details + block = await rpc.get_block(header_hash) + + # Fetch transactions associated with the block + transactions = await rpc.get_block_transactions(header_hash) + + # Render the template with the block details and associated transactions + return render_template('block.html', block=block, transactions=transactions) diff --git a/script/research/blockchain-explorer/site/blueprints/contract.py b/script/research/blockchain-explorer/site/blueprints/contract.py new file mode 100644 index 000000000..a2c81634e --- /dev/null +++ b/script/research/blockchain-explorer/site/blueprints/contract.py @@ -0,0 +1,94 @@ +# This file is part of DarkFi (https://dark.fi) +# +# Copyright (C) 2020-2024 Dyne.org foundation +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +""" +Blueprint: contract_bp + +This module defines a Flask blueprint (`contract_bp`) for handling contract-related functionality, +serving as a primary location for Flask code related to routes and related features associated with contracts. +""" + +from flask import request, render_template, Blueprint + +from pygments import highlight +from pygments.lexers import RustLexer +from pygments.formatters import HtmlFormatter + +import rpc + +# Create contract blueprint +contract_bp = Blueprint("contract", __name__) + +@contract_bp.route('/contract/') +async def contract_source_list(contract_id): + """ + Fetches and displays a list of source files for the specified contract using an RPC + call to the explorer daemon, returning a rendered template with the source code + files associated with the contract. + + Args: + contract_id (str): The Contract ID to fetch the source files for. + + Query Params: + name (str, optional): The contract name to display alongside the source files. + """ + # Obtain the contract name to display with the source + contract_name = request.args.get('name') + + # Fetch the source file list associated with the contract + source_paths = await rpc.get_contract_source_paths(contract_id) + + # Returned rendered contract source list + return render_template('contract_source_list.html', contract_id=contract_id, source_paths=source_paths, contract_name=contract_name) + +@contract_bp.route('/contract/source//') +async def contract_source(contract_id, source_path): + """ + Fetches and displays the source code for a specific file of a contract using an RPC + call to the explorer daemon, returning a rendered template with syntax-highlighted + source code. + + Path Args: + contract_id (str): The Contract ID to fetch the source file for. + source_path (str): The path of the specific source file within the contract. + + Query Params: + name (str, optional): The contract name to display alongside the source code. + """ + # Obtain the contract name to display with the source + contract_name = request.args.get('name') + + # Retrieve the contract source code + raw_source = await rpc.get_contract_source(contract_id, source_path) + + # Style the source code + formatter = HtmlFormatter(style='friendly', linenos=True) + source = highlight(raw_source, RustLexer(), formatter) + + # Generate css for styled source code + pygments_css = formatter.get_style_defs() + + # Returned rendered contract source code page + return render_template( + 'contract_source.html', + source=source, + contract_id=contract_id, + source_path=source_path, + pygments_css=pygments_css, + contract_name=contract_name + ) + diff --git a/script/research/blockchain-explorer/site/blueprints/explore.py b/script/research/blockchain-explorer/site/blueprints/explore.py new file mode 100644 index 000000000..741a1bf58 --- /dev/null +++ b/script/research/blockchain-explorer/site/blueprints/explore.py @@ -0,0 +1,94 @@ +# This file is part of DarkFi (https://dark.fi) +# +# Copyright (C) 2020-2024 Dyne.org foundation +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +""" +Blueprint: explorer_bp + +This module defines a Flask blueprint (`explore_bp`) for managing the general functionality of the explorer application. +It serves as the primary location for Flask routes related to the home page, search functionality, and other general features. +""" + +from flask import Blueprint, render_template, request + +import rpc + +# Create explore blueprint +explore_bp = Blueprint("explore", __name__) +@explore_bp.route('/', methods=["GET"]) +async def index(): + """ + Fetches and displays the explorer home page content using multiple RPC calls to the + explorer daemon, retrieving the last 10 blocks, basic statistics, metric statistics, + and DarkFi native contracts. + + Upon success, it returns a rendered template with recent blocks, basic statistics, + latest metric statistics (if available), and native contracts. + """ + # Fetch the latest 10 blocks + blocks = await rpc.get_last_n_blocks("10") + + # Retrieve basic statistics summarizing the overall chain data + basic_stats = await rpc.get_basic_statistics() + + # Fetch the metric statistics + metric_stats = await rpc.get_metric_statistics() + + # Determine if metrics exist + has_metrics = metric_stats and isinstance(metric_stats, list) + + # Get the latest metric statistics or set to None if none are found + latest_metric_stats = metric_stats[-1] if has_metrics else None + + # Retrieve the native contracts + native_contracts = await rpc.get_native_contracts() + + # Render the explorer home page + return render_template( + 'index.html', + blocks=blocks, + basic_stats=basic_stats, + metric_stats=latest_metric_stats, + native_contracts=native_contracts, + ) + +@explore_bp.route('/search', methods=['GET', 'POST']) +async def search(): + """ + Searches for a block or transaction based on the provided hash using RPC calls to the + explorer daemon. It retrieves relevant data using the search hash from provided query parameter, + returning a rendered template that displays either block details with associated transactions + or transaction details, depending on the search result. + + Query Params: + search_hash (str): The hash of the block or transaction to search for. + """ + # Get the search hash + search_hash = request.args.get('search_hash', '') + + # Fetch the block corresponding to the search hash + block = await rpc.get_block(search_hash) + + # Fetch transactions associated with the block + transactions = await rpc.get_block_transactions(search_hash) + + if transactions: + # Render block details with associated transactions if found + return render_template('block.html', block=block, transactions=transactions) + else: + # Fetch transaction details if no transactions are found for the block + transaction = await rpc.get_transaction(search_hash) + return render_template('transaction.html', transaction=transaction) \ No newline at end of file diff --git a/script/research/blockchain-explorer/site/blueprints/transaction.py b/script/research/blockchain-explorer/site/blueprints/transaction.py new file mode 100644 index 000000000..d4a736fa0 --- /dev/null +++ b/script/research/blockchain-explorer/site/blueprints/transaction.py @@ -0,0 +1,45 @@ +# This file is part of DarkFi (https://dark.fi) +# +# Copyright (C) 2020-2024 Dyne.org foundation +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +""" +Blueprint: transaction_bp + +This module defines a Flask blueprint (`transaction_bp`) for handling transaction-related functionality, +serving as a primary location for Flask code related to routes and related features associated with transactions. +""" + +from flask import Blueprint, render_template + +import rpc + +# Create transaction blueprint +transaction_bp = Blueprint("transaction", __name__) + +@transaction_bp.route('/tx/') +async def transaction(transaction_hash): + """ + Retrieves transaction details based on the provided hash using RPC calls to the explorer daemon + and returns a rendered template displaying the information. + + Path Args: + transaction_hash (str): The hash of the transaction to retrieve. + """ + # Fetch the transaction details + transaction = await rpc.get_transaction(transaction_hash) + + # Render the template using the fetched transaction details + return render_template('transaction.html', transaction=transaction) diff --git a/script/research/blockchain-explorer/site/requirements.txt b/script/research/blockchain-explorer/site/requirements.txt index fafa8e7e6..3d6964644 100644 --- a/script/research/blockchain-explorer/site/requirements.txt +++ b/script/research/blockchain-explorer/site/requirements.txt @@ -1 +1,2 @@ flask[async] +Pygments \ No newline at end of file diff --git a/script/research/blockchain-explorer/site/rpc.py b/script/research/blockchain-explorer/site/rpc.py index fdae34e47..f483e6ad5 100644 --- a/script/research/blockchain-explorer/site/rpc.py +++ b/script/research/blockchain-explorer/site/rpc.py @@ -15,27 +15,38 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +""" +Module: rpc.py + +This module provides an asynchronous interface for interacting with the DarkFi explorer daemon +using JSON-RPC. It includes functionality to create a communication channel, send requests, +and handle responses from the server. +""" + import asyncio, json, random + from flask import abort -# DarkFi blockchain-explorer daemon JSON-RPC configuration +# DarkFi explorer daemon JSON-RPC configuration URL = "127.0.0.1" PORT = 14567 -# Class representing the channel with the JSON-RPC server class Channel: + """Class representing the channel with the JSON-RPC server.""" def __init__(self, reader, writer): + """Initialize the channel with a reader and writer.""" self.reader = reader self.writer = writer async def readline(self): + """Read a line from the channel, closing it if the connection is lost.""" if not (line := await self.reader.readline()): self.writer.close() return None - # Strip the newline - return line[:-1].decode() + return line[:-1].decode() # Strip the newline async def receive(self): + """Receive and decode a message from the channel.""" if (plaintext := await self.readline()) is None: return None @@ -44,34 +55,47 @@ class Channel: return response async def send(self, obj): + """Send a JSON-encoded object to the channel.""" message = json.dumps(obj) data = message.encode() self.writer.write(data + b"\n") await self.writer.drain() -# Create a Channel for given server. async def create_channel(server_name, port): + """ + Creates a channel used to send RPC requests to the DarkFi explorer daemon. + """ try: reader, writer = await asyncio.open_connection(server_name, port) except ConnectionRefusedError: - print(f"Error: Connection Refused to '{server_name}:{port}', Either because the daemon is down, is currently syncing or wrong url.") + print( + f"Error: Connection Refused to '{server_name}:{port}', Either because the daemon is down, is currently syncing or wrong url.") abort(500) channel = Channel(reader, writer) return channel -# Execute a request towards the JSON-RPC server async def query(method, params): + """ + Execute a request towards the JSON-RPC server by constructing a JSON-RPC + request and sending it to the server. It handles connection errors and server responses, + returning the result of the query or raising an error if the request fails. + """ + # Create the channel to send RPC request channel = await create_channel(URL, PORT) + + # Prepare request request = { - "id": random.randint(0, 2**32), + "id": random.randint(0, 2 ** 32), "method": method, "params": params, "jsonrpc": "2.0", } - await channel.send(request) + # Send request and await response + await channel.send(request) response = await channel.receive() + # Closed connect returns None if response is None: print("error: connection with server was closed") @@ -86,26 +110,40 @@ async def query(method, params): return response["result"] -# Retrieve last n blocks from blockchain-explorer daemon async def get_last_n_blocks(n: str): + """Retrieves the last n blocks.""" return await query("blocks.get_last_n_blocks", [n]) -# Retrieve basic statistics from blockchain-explorer daemon async def get_basic_statistics(): + """Retrieves basic statistics.""" return await query("statistics.get_basic_statistics", []) -# Retrieve fee data statistics from blockchain-explorer daemon async def get_metric_statistics(): + """Retrieves metrics statistics.""" return await query("statistics.get_metric_statistics", []) -# Retrieve the block information of given header hash from blockchain-explorer daemon async def get_block(header_hash: str): + """Retrieves block information for a given header hash.""" return await query("blocks.get_block_by_hash", [header_hash]) -# Retrieve the transactions of given block header hash from blockchain-explorer daemon async def get_block_transactions(header_hash: str): + """Retrieves transactions associated with a given block header hash.""" return await query("transactions.get_transactions_by_header_hash", [header_hash]) -# Retrieve the transaction information of given hash from blockchain-explorer daemon + async def get_transaction(transaction_hash: str): + """Retrieves transaction information for a given transaction hash.""" return await query("transactions.get_transaction_by_hash", [transaction_hash]) + +async def get_native_contracts(): + """Retrieves native contracts.""" + return await query("contracts.get_native_contracts", []) + + +async def get_contract_source_paths(contract_id: str): + """Retrieves contract source code paths for a given contract ID.""" + return await query("contracts.get_contract_source_code_paths", [contract_id]) + +async def get_contract_source(contract_id: str, source_path): + """Retrieves the contract source file for a given contract ID and source path.""" + return await query("contracts.get_contract_source", [contract_id, source_path]) diff --git a/script/research/blockchain-explorer/site/static/style.css b/script/research/blockchain-explorer/site/static/style.css index 5a22c7b21..49c2dcd30 100644 --- a/script/research/blockchain-explorer/site/static/style.css +++ b/script/research/blockchain-explorer/site/static/style.css @@ -115,6 +115,9 @@ --color-background-table-header: #181818; --color-input-primary: #AFA99E; --color-border-primary: #83796B; + --color-source-code-link: #374151; + --color-source-code-link-bg: #393939; + --color-source-code-link-hover-bg: #313131; } /* -------------------------------------------------------------------------- */ @@ -974,3 +977,89 @@ h5 a:hover { .table thead tr { border: 1px var(--color-border-primary) solid; } + +/* -------------------------------------------------------------------------- */ +/* Native Contracts */ +/* -------------------------------------------------------------------------- */ + +.contract-id { + width: calc(27ch + 12px); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.contract-name { + width: calc(9ch + 12px); + padding: 6px; +} + +.contract-description { + width: auto; +} + +/* -------------------------------------------------------------------------- */ +/* Source Code */ +/* -------------------------------------------------------------------------- */ + +.source-code-container { + display: flex; + flex-direction: column; +} + +.source-code-display-box { + padding: 16px; + border: 1px var(--color-border-primary) solid; + overflow: hidden; + box-sizing: border-box; +} + +.source-path { + align-self: flex-start; + margin-bottom: 12px; + margin-right: 16px; +} + +.source-code-list { + list-style-type: none; + padding-left: 0; +} + +.source-code-list-item { + margin-bottom: 8px; +} + +.source-file-link { + color: var(--color-source-code-link); + text-decoration: none; + font-weight: bold; + display: block; + padding: 8px 12px; + background-color: var(--color-source-code-link-bg); + border-radius: 2px; + transition: background-color 0.3s ease; +} + +.source-file-link:hover { + background-color: var(--color-source-code-link-hover-bg); + color: var(--color-source-code-link); +} + +.pre-source { + overflow: hidden; + white-space: pre-wrap; + word-wrap: break-word; + margin: 0; + padding: 0; + line-height: 1; + border: none; +} + +.pre-source table { + width: 100%; + border-collapse: collapse; +} + +.pre-source td { + padding-right: 2px; +} diff --git a/script/research/blockchain-explorer/site/templates/contract_source.html b/script/research/blockchain-explorer/site/templates/contract_source.html new file mode 100644 index 000000000..cfde71e52 --- /dev/null +++ b/script/research/blockchain-explorer/site/templates/contract_source.html @@ -0,0 +1,46 @@ + + +{% extends "base.html" %} +{% block title %}Contract {{ contract_id }}{% endblock %} + +{% block content %} +
+ + +
+

{{ contract_name }} Contract Source Code

+
{{ contract_id }}
+
+ + +
+
+ {{ source_path }} +
+
+ + + +
{{ source|safe }}
+
+
+
+{% endblock %} + diff --git a/script/research/blockchain-explorer/site/templates/contract_source_list.html b/script/research/blockchain-explorer/site/templates/contract_source_list.html new file mode 100644 index 000000000..3f58730e8 --- /dev/null +++ b/script/research/blockchain-explorer/site/templates/contract_source_list.html @@ -0,0 +1,55 @@ + + +{% extends "base.html" %} +{% block title %}Contract {{ contract_id }}{% endblock %} + +{% block content %} +
+ + +
+

{{ contract_name }} Contract Source Code

+
{{ contract_id }}
+
+ + +
+ {% if source_paths %} + + + {% else %} + +
+ There is no source files are associated with this contract. +
+ {% endif %} +
+
+ +{% endblock %} diff --git a/script/research/blockchain-explorer/site/templates/index.html b/script/research/blockchain-explorer/site/templates/index.html index 7c19cc769..ad920d2e6 100644 --- a/script/research/blockchain-explorer/site/templates/index.html +++ b/script/research/blockchain-explorer/site/templates/index.html @@ -117,6 +117,31 @@ {% endif %} + +
+

Native Contracts

+ + + + + + + + + + {% for contract in native_contracts %} + + + + + + {% endfor %} + +
Contract IdNameDescription
+ {{ contract[0] }} + {{ contract[1] }}{{ contract[2] }}
+
+ {% endblock %}