mirror of
https://github.com/pezkuwichain/pezkuwi-fellows.git
synced 2026-04-28 14:28:06 +00:00
505 lines
50 KiB
HTML
505 lines
50 KiB
HTML
|
|
<!DOCTYPE HTML>
|
|
<html lang="en" class="polkadot" dir="ltr">
|
|
<head>
|
|
<!-- Book generated using mdBook -->
|
|
<meta charset="UTF-8">
|
|
<title>RFC-0145: Remove the host-side runtime memory allocator - Polkadot Fellowship RFCs</title>
|
|
|
|
|
|
<!-- Custom HTML head -->
|
|
|
|
<meta name="description" content="An online book of RFCs approved or proposed within the Polkadot Fellowship.">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<meta name="theme-color" content="#ffffff">
|
|
|
|
<link rel="icon" href="../favicon.svg">
|
|
<link rel="shortcut icon" href="../favicon.png">
|
|
<link rel="stylesheet" href="../css/variables.css">
|
|
<link rel="stylesheet" href="../css/general.css">
|
|
<link rel="stylesheet" href="../css/chrome.css">
|
|
<link rel="stylesheet" href="../css/print.css" media="print">
|
|
|
|
<!-- Fonts -->
|
|
<link rel="stylesheet" href="../FontAwesome/css/font-awesome.css">
|
|
<link rel="stylesheet" href="../fonts/fonts.css">
|
|
|
|
<!-- Highlight.js Stylesheets -->
|
|
<link rel="stylesheet" href="../highlight.css">
|
|
<link rel="stylesheet" href="../tomorrow-night.css">
|
|
<link rel="stylesheet" href="../ayu-highlight.css">
|
|
|
|
<!-- Custom theme stylesheets -->
|
|
<link rel="stylesheet" href="../theme/polkadot.css">
|
|
|
|
</head>
|
|
<body class="sidebar-visible no-js">
|
|
<div id="body-container">
|
|
<!-- Provide site root to javascript -->
|
|
<script>
|
|
var path_to_root = "../";
|
|
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "polkadot" : "polkadot";
|
|
</script>
|
|
|
|
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
|
<script>
|
|
try {
|
|
var theme = localStorage.getItem('mdbook-theme');
|
|
var sidebar = localStorage.getItem('mdbook-sidebar');
|
|
|
|
if (theme.startsWith('"') && theme.endsWith('"')) {
|
|
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
|
|
}
|
|
|
|
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
|
|
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
|
|
}
|
|
} catch (e) { }
|
|
</script>
|
|
|
|
<!-- Set the theme before any content is loaded, prevents flash -->
|
|
<script>
|
|
var theme;
|
|
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
|
if (theme === null || theme === undefined) { theme = default_theme; }
|
|
var html = document.querySelector('html');
|
|
html.classList.remove('polkadot')
|
|
html.classList.add(theme);
|
|
var body = document.querySelector('body');
|
|
body.classList.remove('no-js')
|
|
body.classList.add('js');
|
|
</script>
|
|
|
|
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
|
|
|
|
<!-- Hide / unhide sidebar before it is displayed -->
|
|
<script>
|
|
var body = document.querySelector('body');
|
|
var sidebar = null;
|
|
var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
|
|
if (document.body.clientWidth >= 1080) {
|
|
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
|
|
sidebar = sidebar || 'visible';
|
|
} else {
|
|
sidebar = 'hidden';
|
|
}
|
|
sidebar_toggle.checked = sidebar === 'visible';
|
|
body.classList.remove('sidebar-visible');
|
|
body.classList.add("sidebar-" + sidebar);
|
|
</script>
|
|
|
|
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
|
<div class="sidebar-scrollbox">
|
|
<ol class="chapter"><li class="chapter-item expanded affix "><a href="../introduction.html">Introduction</a></li><li class="spacer"></li><li class="chapter-item expanded affix "><li class="part-title">Newly Proposed</li><li class="chapter-item expanded "><a href="../new/xxxx-improve-the-security-of-proof-of-possession.html">RFC-XXXX: Adding customized mandatory context to proof of possession statement</a></li><li class="spacer"></li><li class="chapter-item expanded affix "><li class="part-title">Proposed</li><li class="chapter-item expanded "><a href="../proposed/0000-pre-elves_soft.html">RFC-0000: Pre-ELVES soft concensus</a></li><li class="chapter-item expanded "><a href="../proposed/0017-coretime-market-redesign.html">RFC-0017: Coretime Market Redesign</a></li><li class="chapter-item expanded "><a href="../proposed/0117-unbrick-collective.html">RFC-0117: The Unbrick Collective</a></li><li class="chapter-item expanded "><a href="../proposed/0145-remove-unnecessary-allocator-usage.html" class="active">RFC-0145: Remove the host-side runtime memory allocator</a></li><li class="chapter-item expanded "><a href="../proposed/0146-deflationary-fee-proposal.html">RFC-0146: Deflationary Transaction Fee Model for the Relay Chain and its System Parachains</a></li><li class="spacer"></li><li class="chapter-item expanded affix "><li class="part-title">Approved</li><li class="chapter-item expanded "><a href="../approved/0001-agile-coretime.html">RFC-1: Agile Coretime</a></li><li class="chapter-item expanded "><a href="../approved/0005-coretime-interface.html">RFC-5: Coretime Interface</a></li><li class="chapter-item expanded "><a href="../approved/0007-system-collator-selection.html">RFC-0007: System Collator Selection</a></li><li class="chapter-item expanded "><a href="../approved/0008-parachain-bootnodes-dht.html">RFC-0008: Store parachain bootnodes in relay chain DHT</a></li><li class="chapter-item expanded "><a href="../approved/0009-improved-net-light-client-requests.html">RFC-0009: Improved light client requests networking protocol</a></li><li class="chapter-item expanded "><a href="../approved/0010-burn-coretime-revenue.html">RFC-0010: Burn Coretime Revenue</a></li><li class="chapter-item expanded "><a href="../approved/0012-process-for-adding-new-collectives.html">RFC-0012: Process for Adding New System Collectives</a></li><li class="chapter-item expanded "><a href="../approved/0013-prepare-blockbuilder-and-core-runtime-apis-for-mbms.html">RFC-0013: Prepare Core runtime API for MBMs</a></li><li class="chapter-item expanded "><a href="../approved/0014-improve-locking-mechanism-for-parachains.html">RFC-0014: Improve locking mechanism for parachains</a></li><li class="chapter-item expanded "><a href="../approved/0022-adopt-encointer-runtime.html">RFC-0022: Adopt Encointer Runtime</a></li><li class="chapter-item expanded "><a href="../approved/0026-sassafras-consensus.html">RFC-0026: Sassafras Consensus Protocol</a></li><li class="chapter-item expanded "><a href="../approved/0032-minimal-relay.html">RFC-0032: Minimal Relay</a></li><li class="chapter-item expanded "><a href="../approved/0042-extrinsics-state-version.html">RFC-0042: Add System version that replaces StateVersion on RuntimeVersion</a></li><li class="chapter-item expanded "><a href="../approved/0043-storage-proof-size-hostfunction.html">RFC-0043: Introduce storage_proof_size Host Function for Improved Parachain Block Utilization</a></li><li class="chapter-item expanded "><a href="../approved/0045-nft-deposits-asset-hub.html">RFC-0045: Lowering NFT Deposits on Asset Hub</a></li><li class="chapter-item expanded "><a href="../approved/0047-assignment-of-availability-chunks.html">RFC-0047: Assignment of availability chunks to validators</a></li><li class="chapter-item expanded "><a href="../approved/0048-session-keys-runtime-api.html">RFC-0048: Generate ownership proof for SessionKeys</a></li><li class="chapter-item expanded "><a href="../approved/0050-fellowship-salaries.html">RFC-0050: Fellowship Salaries</a></li><li class="chapter-item expanded "><a href="../approved/0056-one-transaction-per-notification.html">RFC-0056: Enforce only one transaction per notification</a></li><li class="chapter-item expanded "><a href="../approved/0059-nodes-capabilities-discovery.html">RFC-0059: Add a discovery mechanism for nodes based on their capabilities</a></li><li class="chapter-item expanded "><a href="../approved/0078-merkleized-metadata.html">RFC-0078: Merkleized Metadata</a></li><li class="chapter-item expanded "><a href="../approved/0084-general-transaction-extrinsic-format.html">RFC-0084: General transactions in extrinsic format</a></li><li class="chapter-item expanded "><a href="../approved/0091-dht-record-creation-time.html">RFC-0091: DHT Authority discovery record creation time</a></li><li class="chapter-item expanded "><a href="../approved/0097-unbonding_queue.html">RFC-0097: Unbonding Queue</a></li><li class="chapter-item expanded "><a href="../approved/0099-transaction-extension-version.html">RFC-0099: Introduce a transaction extension version</a></li><li class="chapter-item expanded "><a href="../approved/0100-xcm-multi-type-asset-transfer.html">RFC-0100: New XCM instruction: InitiateAssetsTransfer</a></li><li class="chapter-item expanded "><a href="../approved/0101-xcm-transact-remove-max-weight-param.html">RFC-0101: XCM Transact remove require_weight_at_most parameter</a></li><li class="chapter-item expanded "><a href="../approved/0103-introduce-core-index-commitment.html">RFC-0103: Introduce a CoreIndex commitment and a SessionIndex field in candidate receipts</a></li><li class="chapter-item expanded "><a href="../approved/0105-xcm-improved-fee-mechanism.html">RFC-0105: XCM improved fee mechanism</a></li><li class="chapter-item expanded "><a href="../approved/0107-xcm-execution-hints.html">RFC-0107: XCM Execution hints</a></li><li class="chapter-item expanded "><a href="../approved/0108-xcm-remove-testnet-ids.html">RFC-0108: Remove XCM testnet NetworkIds</a></li><li class="chapter-item expanded "><a href="../approved/0122-alias-origin-on-asset-transfers.html">RFC-0122: Asset transfers can alias XCM origin on destination to original origin</a></li><li class="chapter-item expanded "><a href="../approved/0123-pending-code-as-storage-location-for-runtime-upgrades.html">RFC-0123: Introduce :pending_code as intermediate storage key for the runtime code</a></li><li class="chapter-item expanded "><a href="../approved/0125-xcm-asset-metadata.html">RFC-0125: XCM Asset Metadata</a></li><li class="chapter-item expanded "><a href="../approved/0126-introduce-pvq.html">RFC-0126: Introduce PVQ (PolkaVM Query)</a></li><li class="chapter-item expanded "><a href="../approved/0135-compressed-blob-prefixes.html">RFC-0135: Compressed Blob Prefixes</a></li><li class="chapter-item expanded "><a href="../approved/0139-faster-erasure-coding.html">RFC-0139: Faster Erasure Coding</a></li><li class="spacer"></li><li class="chapter-item expanded affix "><li class="part-title">Stale</li><li class="chapter-item expanded "><a href="../stale/0000-rewards.html">RFC-0000: Validator Rewards</a></li><li class="chapter-item expanded "><a href="../stale/0004-remove-unnecessary-allocator-usage.html">RFC-0004: Remove the host-side runtime memory allocator</a></li><li class="chapter-item expanded "><a href="../stale/0006-dynamic-pricing-for-bulk-coretime-sales.html">RFC-0006: Dynamic Pricing for Bulk Coretime Sales</a></li><li class="chapter-item expanded "><a href="../stale/0034-xcm-absolute-location-account-derivation.html">RFC-34: XCM Absolute Location Account Derivation</a></li><li class="chapter-item expanded "><a href="../stale/0035-conviction-voting-delegation-modifications.html"> RFC-0035: Conviction Voting Delegation Modifications</a></li><li class="chapter-item expanded "><a href="../stale/0044-rent-based-registration.html">RFC-0044: Rent based registration model</a></li><li class="chapter-item expanded "><a href="../stale/0054-remove-heap-pages.html">RFC-0054: Remove the concept of "heap pages" from the client</a></li><li class="chapter-item expanded "><a href="../stale/0070-x-track-kusamanetwork.html">RFC-0070: X Track for @kusamanetwork</a></li><li class="chapter-item expanded "><a href="../stale/0073-referedum-deposit-track.html">RFC-0073: Decision Deposit Referendum Track</a></li><li class="chapter-item expanded "><a href="../stale/0074-stateful-multisig-pallet.html">RFC-0074: Stateful Multisig Pallet</a></li><li class="chapter-item expanded "><a href="../stale/0077-increase-max-length-of-identity-pgp-fingerprint-value.html">RFC-0077: Increase maximum length of identity PGP fingerprint values from 20 bytes</a></li><li class="chapter-item expanded "><a href="../stale/0088-broker-pallet-slashable-deposit-purchaser-reputation-reserved-cores.html">RFC-0088: Add slashable locked deposit, purchaser reputation, and reserved cores for on-chain identities to broker pallet</a></li><li class="chapter-item expanded "><a href="../stale/00xx-secondary-marketplace-for-regions.html">RFC-0001: Secondary Market for Regions</a></li><li class="chapter-item expanded "><a href="../stale/00xx-smart-contracts-coretime-chain.html">RFC-0002: Smart Contracts on the Coretime Chain</a></li><li class="chapter-item expanded "><a href="../stale/0102-offchain-parachain-runtime-upgrades.html">RFC-0000: Feature Name Here</a></li><li class="chapter-item expanded "><a href="../stale/0106-xcm-remove-fees-mode.html">RFC-0106: Remove XCM fees mode</a></li><li class="chapter-item expanded "><a href="../stale/0111-pure-proxy-replication.html">RFC-0111: Pure Proxy Replication</a></li><li class="chapter-item expanded "><a href="../stale/0112-compress-state-response-message-in-state-sync.html">RFC-0112: Compress the State Response Message in State Sync</a></li><li class="chapter-item expanded "><a href="../stale/0114-secp256r1-hostfunction.html">RFC-0114: Introduce secp256r1_ecdsa_verify_prehashed Host Function to verify NIST-P256 elliptic curve signatures</a></li><li class="chapter-item expanded "><a href="../stale/0120-referenda-confirmation-by-candle-mechanism.html">RFC-0120: Referenda Confirmation by Candle Mechanism</a></li><li class="chapter-item expanded "><a href="../stale/0124-extrinsic-version-5.html">RFC-0124: Extrinsic version 5</a></li><li class="chapter-item expanded "><a href="../stale/0138-invulnerable-collator-election.html">RFC-0138: Election mechanism for invulnerable collators on system chains</a></li><li class="chapter-item expanded "><a href="../stale/RFC-114 Adjust Tipper Track Confirmation Periods.html">RFC-114: Adjust Tipper Track Confirmation Periods</a></li><li class="chapter-item expanded "><a href="../stale/TODO-stale-nomination-reward-curve.html">RFC-TODO: Stale Nomination Reward Curve</a></li></ol>
|
|
</div>
|
|
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
|
|
</nav>
|
|
|
|
<!-- Track and set sidebar scroll position -->
|
|
<script>
|
|
var sidebarScrollbox = document.querySelector('#sidebar .sidebar-scrollbox');
|
|
sidebarScrollbox.addEventListener('click', function(e) {
|
|
if (e.target.tagName === 'A') {
|
|
sessionStorage.setItem('sidebar-scroll', sidebarScrollbox.scrollTop);
|
|
}
|
|
}, { passive: true });
|
|
var sidebarScrollTop = sessionStorage.getItem('sidebar-scroll');
|
|
sessionStorage.removeItem('sidebar-scroll');
|
|
if (sidebarScrollTop) {
|
|
// preserve sidebar scroll position when navigating via links within sidebar
|
|
sidebarScrollbox.scrollTop = sidebarScrollTop;
|
|
} else {
|
|
// scroll sidebar to current active section when navigating via "next/previous chapter" buttons
|
|
var activeSection = document.querySelector('#sidebar .active');
|
|
if (activeSection) {
|
|
activeSection.scrollIntoView({ block: 'center' });
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<div id="page-wrapper" class="page-wrapper">
|
|
|
|
<div class="page">
|
|
<div id="menu-bar-hover-placeholder"></div>
|
|
<div id="menu-bar" class="menu-bar sticky">
|
|
<div class="left-buttons">
|
|
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
|
|
<i class="fa fa-bars"></i>
|
|
</label>
|
|
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
|
|
<i class="fa fa-paint-brush"></i>
|
|
</button>
|
|
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
|
|
<li role="none"><button role="menuitem" class="theme" id="polkadot">Polkadot</button></li>
|
|
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
|
|
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
|
|
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
|
|
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
|
|
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
|
|
</ul>
|
|
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
|
|
<i class="fa fa-search"></i>
|
|
</button>
|
|
</div>
|
|
|
|
<h1 class="menu-title">Polkadot Fellowship RFCs</h1>
|
|
|
|
<div class="right-buttons">
|
|
<a href="../print.html" title="Print this book" aria-label="Print this book">
|
|
<i id="print-button" class="fa fa-print"></i>
|
|
</a>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<div id="search-wrapper" class="hidden">
|
|
<form id="searchbar-outer" class="searchbar-outer">
|
|
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
|
|
</form>
|
|
<div id="searchresults-outer" class="searchresults-outer hidden">
|
|
<div id="searchresults-header" class="searchresults-header"></div>
|
|
<ul id="searchresults">
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
|
|
<script>
|
|
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
|
|
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
|
|
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
|
|
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
|
|
});
|
|
</script>
|
|
|
|
<div id="content" class="content">
|
|
<main>
|
|
<p><a href="https://github.com/polkadot-fellows/RFCs/pull/145">(source)</a></p>
|
|
<p><strong>Table of Contents</strong></p>
|
|
<ul>
|
|
<li><a href="#rfc-0145-remove-the-host-side-runtime-memory-allocator">RFC-0145: Remove the host-side runtime memory allocator</a>
|
|
<ul>
|
|
<li><a href="#summary">Summary</a></li>
|
|
<li><a href="#prior-art">Prior Art</a>
|
|
<ul>
|
|
<li><a href="#changes">Changes</a></li>
|
|
</ul>
|
|
</li>
|
|
<li><a href="#motivation">Motivation</a></li>
|
|
<li><a href="#stakeholders">Stakeholders</a></li>
|
|
<li><a href="#explanation">Explanation</a>
|
|
<ul>
|
|
<li><a href="#new-host-functions">New host functions</a></li>
|
|
<li><a href="#other-changes">Other changes</a></li>
|
|
</ul>
|
|
</li>
|
|
<li><a href="#unresolved-questions">Unresolved Questions</a></li>
|
|
</ul>
|
|
</li>
|
|
</ul>
|
|
<h1 id="rfc-0145-remove-the-host-side-runtime-memory-allocator"><a class="header" href="#rfc-0145-remove-the-host-side-runtime-memory-allocator">RFC-0145: Remove the host-side runtime memory allocator</a></h1>
|
|
<div class="table-wrapper"><table><thead><tr><th></th><th></th></tr></thead><tbody>
|
|
<tr><td><strong>Start Date</strong></td><td>2025-05-16</td></tr>
|
|
<tr><td><strong>Description</strong></td><td>Update the runtime-host interface to no longer make use of a host-side allocator</td></tr>
|
|
<tr><td><strong>Authors</strong></td><td>Pierre Krieger, Someone Unknown</td></tr>
|
|
</tbody></table>
|
|
</div>
|
|
<h2 id="summary"><a class="header" href="#summary">Summary</a></h2>
|
|
<p>Update the runtime-host interface so that it no longer uses the host-side allocator.</p>
|
|
<h2 id="prior-art"><a class="header" href="#prior-art">Prior Art</a></h2>
|
|
<p>The API of these new functions was heavily inspired by the API used by the C programming language.</p>
|
|
<p>This RFC is mainly based on <a href="https://github.com/polkadot-fellows/RFCs/pull/4">RFC-4</a> by @tomaka, which has never been adopted, and supercedes it.</p>
|
|
<h3 id="changes"><a class="header" href="#changes">Changes</a></h3>
|
|
<ul>
|
|
<li>The original RFC required checking if an output buffer address provided to a host function is inside the VM address space range and to stop the runtime execution if that's not the case. That requirement has been removed in this version of the RFC, as in the general case, the host doesn't have exhaustive information about the VM's memory organization. Thus, attempting to write to an out-of-bound region will result in a "normal" runtime panic.</li>
|
|
<li>Function signatures introduced by <a href="https://github.com/w3f/PPPs/pull/7">PPP#7</a> have been used in this RFC, as the PPP has already been <a href="https://github.com/paritytech/substrate/pull/11490">properly implemented</a> and <a href="https://github.com/w3f/polkadot-spec/pull/592/files">documented</a>. However, it has never been officially adopted, nor have its functions been in use.</li>
|
|
<li>For <code>*_next_key</code> input buffer is reused for output.</li>
|
|
<li>Error codes were harmonized to be always represented by negative values.</li>
|
|
<li>Return values were harmonized to <code>i64</code> everywhere where they represent either a positive outcome as a positive integer or a negative outcome as a negative error code.</li>
|
|
<li><code>ext_offchain_network_peer_id_version_1</code> now returns a result code instead of silently failing if the network status is unavailable.</li>
|
|
<li>Added new versions of <code>ext_misc_runtime_version</code> and <code>ext_offchain_random_seed</code>.</li>
|
|
<li>Addressed discussions from the original RFC-4 discussion flow.</li>
|
|
</ul>
|
|
<h2 id="motivation"><a class="header" href="#motivation">Motivation</a></h2>
|
|
<p>The heap allocation of the runtime is currently controlled by the host using a memory allocator on the host side.</p>
|
|
<p>The API of many host functions contains buffer allocations. For example, when calling <code>ext_hashing_twox_256_version_1</code>, the host allocates a 32-byte buffer using the host allocator, and returns a pointer to this buffer to the runtime. The runtime later has to call <code>ext_allocator_free_version_1</code> on this pointer to free the buffer.</p>
|
|
<p>Even though no benchmark has been done, it is pretty obvious that this design is very inefficient. To continue with the example of <code>ext_hashing_twox_256_version_1</code>, it would be more efficient to instead write the output hash to a buffer allocated by the runtime on its stack and passed by pointer to the function. Allocating a buffer on the stack in the worst-case scenario consists of simply decreasing a number; in the best-case scenario, it is free. Doing so would save many VM memory reads and writes by the allocator, and would save a function call to <code>ext_allocator_free_version_1</code>.</p>
|
|
<p>Furthermore, the existence of the host-side allocator has become questionable over time. It is implemented in a very naive way, and for determinism and backwards compatibility reasons, it needs to be implemented exactly identically in every client implementation. Runtimes make substantial use of heap memory allocations, and each allocation needs to go through the runtime <-> host boundary twice (once for allocating and once for freeing). Moving the allocator to the runtime side would be a good idea, although it would increase the runtime size. But before the host-side allocator can be deprecated, all the host functions that use it must be updated to avoid using it.</p>
|
|
<h2 id="stakeholders"><a class="header" href="#stakeholders">Stakeholders</a></h2>
|
|
<p>No attempt was made to convince stakeholders.</p>
|
|
<h2 id="explanation"><a class="header" href="#explanation">Explanation</a></h2>
|
|
<h3 id="new-host-functions"><a class="header" href="#new-host-functions">New host functions</a></h3>
|
|
<p>This section contains a list of new host functions to introduce and amendments to the existing ones.</p>
|
|
<pre><code class="language-wat">(func $ext_storage_read_version_2
|
|
(param $key i64) (param $value_out i64) (param $offset i32) (result i64))
|
|
(func $ext_default_child_storage_read_version_2
|
|
(param $child_storage_key i64) (param $key i64) (param $value_out i64)
|
|
(param $offset i32) (result i64))
|
|
</code></pre>
|
|
<p>The signature and behaviour of <code>ext_storage_read_version_2</code> and <code>ext_default_child_storage_read_version_2</code> are identical to their version 1 counterparts, but the return value has a different meaning.</p>
|
|
<p>The new functions directly return the number of bytes written into the <code>value_out</code> buffer. If the entry doesn't exist, <code>-1</code> is returned. Given that the host must never write more bytes than the size of the buffer in <code>value_out</code>, and that the size of this buffer is expressed as a 32-bit number, the 64-bit value of <code>-1</code> is not ambiguous.</p>
|
|
<pre><code class="language-wat">(func $ext_storage_next_key_version_2
|
|
(param $key_in_out i64) (return i32))
|
|
(func $ext_default_child_storage_next_key_version_2
|
|
(param $child_storage_key i64) (param $key_in_out i64) (return i32))
|
|
</code></pre>
|
|
<p>The behaviour of these functions is identical to their version 1 counterparts.</p>
|
|
<p>Instead of allocating a buffer, writing the next key to it, and returning a pointer to it, the new version of these functions accepts an <code>key_in_out</code> parameter containing <a href="https://spec.polkadot.network/chap-host-api#defn-runtime-pointer-size">a pointer-size</a> to the memory location where the host first reads the input from, and then writes the output to.</p>
|
|
<p>These functions return the size, in bytes, of the next key, or <code>0</code> if there is no next key. If the size of the next key is larger than the buffer in <code>key_in_out</code>, the bytes of the key that fit the buffer are written to <code>key_in_out</code>, and any extra bytes that don't fit are discarded.</p>
|
|
<p>Some notes:</p>
|
|
<ul>
|
|
<li>It is never possible for the next key to be an empty buffer, because an empty key has no preceding key. For this reason, a return value of <code>0</code> can unambiguously be used to indicate the lack of the next key.</li>
|
|
<li>The <code>ext_storage_next_key_version_2</code> and <code>ext_default_child_storage_next_key_version_2</code> are typically used to enumerate keys that start with a certain prefix. Since storage keys are constructed by concatenating hashes, the runtime is expected to know the size of the next key and can allocate a buffer that can fit said key. When the next key doesn't belong to the desired prefix, it might not fit the buffer, but given that the start of the key is written to the buffer anyway, this can be detected to avoid calling the function the second time with a larger buffer.</li>
|
|
</ul>
|
|
<pre><code class="language-wat">(func $ext_hashing_keccak_256_version_2
|
|
(param $data i64) (param $out i32))
|
|
(func $ext_hashing_keccak_512_version_2
|
|
(param $data i64) (param $out i32))
|
|
(func $ext_hashing_sha2_256_version_2
|
|
(param $data i64) (param $out i32))
|
|
(func $ext_hashing_blake2_128_version_2
|
|
(param $data i64) (param $out i32))
|
|
(func $ext_hashing_blake2_256_version_2
|
|
(param $data i64) (param $out i32))
|
|
(func $ext_hashing_twox_64_version_2
|
|
(param $data i64) (param $out i32))
|
|
(func $ext_hashing_twox_128_version_2
|
|
(param $data i64) (param $out i32))
|
|
(func $ext_hashing_twox_256_version_2
|
|
(param $data i64) (param $out i32))
|
|
(func $ext_trie_blake2_256_root_version_3
|
|
(param $data i64) (param $version i32) (param $out i32))
|
|
(func $ext_trie_blake2_256_ordered_root_version_3
|
|
(param $data i64) (param $version i32) (param $out i32))
|
|
(func $ext_trie_keccak_256_root_version_3
|
|
(param $data i64) (param $version i32) (param $out i32))
|
|
(func $ext_trie_keccak_256_ordered_root_version_3
|
|
(param $data i64) (param $version i32) (param $out i32))
|
|
(func $ext_crypto_ed25519_generate_version_2
|
|
(param $key_type_id i32) (param $seed i64) (param $out i32))
|
|
(func $ext_crypto_sr25519_generate_version_2
|
|
(param $key_type_id i32) (param $seed i64) (param $out i32) (return i32))
|
|
(func $ext_crypto_ecdsa_generate_version_2
|
|
(param $key_type_id i32) (param $seed i64) (param $out i32) (return i32))
|
|
</code></pre>
|
|
<p>The behaviour of these functions is identical to their version 1 or version 2 counterparts. Instead of allocating a buffer, writing the output to it, and returning a pointer to it, the new version of these functions accepts an <code>out</code> parameter containing the memory location where the host writes the output. The output is always of a size known at compilation time.</p>
|
|
<pre><code class="language-wat">(func $ext_default_child_storage_root_version_3
|
|
(param $child_storage_key i64) (param $out i32))
|
|
(func $ext_storage_root_version_3
|
|
(param $out i32))
|
|
</code></pre>
|
|
<p>The behaviour of these functions is identical to their version 1 and version 2 counterparts. Instead of allocating a buffer, writing the output to it, and returning a pointer to it, the new versions of these functions accept an <code>out</code> parameter containing the memory location where the host writes the output. The output is always of a size known at compilation time.</p>
|
|
<p>The version 1 of these functions has been taken as a base rather than the version 2, as a <a href="https://github.com/w3f/PPPs/pull/6">PPP#6</a> deprecating the version 2 of these functions has previously been accepted.</p>
|
|
<pre><code class="language-wat">(func $ext_storage_clear_prefix_version_3
|
|
(param $maybe_prefix i64) (param $maybe_limit i64)
|
|
(param $maybe_cursor_in_out i64) (param $backend_out i32)
|
|
(param $unique_out i32) (param $loops_out i32) (return i32))
|
|
(func $ext_default_child_storage_clear_prefix_version_3
|
|
(param $child_storage_key i64) (param $prefix i64) (param $maybe_limit i64)
|
|
(param $maybe_cursor_in_out i64) (param $backend_out i32)
|
|
(param $unique_out i32) (param $loops_out i32) (return i32))
|
|
(func $ext_default_child_storage_kill_version_4
|
|
(param $child_storage_key i64) (param $maybe_limit i64)
|
|
(param $maybe_cursor_in_out i64) (param $backend_out i32)
|
|
(param $unique_out i32) (param $loops_out i32) (return i32))
|
|
</code></pre>
|
|
<p>These functions amend already implemented but still unused functions introduced by <a href="https://github.com/w3f/PPPs/pull/7">PPP#7</a>, hence there's no version number change. <code>maybe_limit</code> defines the limit of backend deletions, not counting keys in the current overlay. <code>maybe_cursor_in_out</code> may be used to pass a continuation cursor. The cursor is written into the same field if the limit was reached and not all the keys were cleared; otherwise, <code>None</code> is written. (CAVEAT: It's impossible to determine appropriate buffer size; the approach is discussible). <code>backend_out</code>, <code>unique_out</code> and <code>loops_out</code> parameters contain the memory location where the output is written (respectively, the number of items removed from the backend DB; the number of unique keys removes, including overlay; the number of iterations done). Any of the output parameters may be <code>-1</code>, in which case no output is written. The functions return <code>0</code> to indicate success, or <code>1</code> if <code>maybe_cursor_in_out</code> buffer length was not enough to write the new cursor; in the latter case, <code>None</code> is written to the buffer.</p>
|
|
<pre><code class="language-wat">(func $ext_crypto_ed25519_sign_version_2
|
|
(param $key_type_id i32) (param $key i32) (param $msg i64) (param $out i32) (return i32))
|
|
(func $ext_crypto_sr25519_sign_version_2
|
|
(param $key_type_id i32) (param $key i32) (param $msg i64) (param $out i32) (return i32))
|
|
func $ext_crypto_ecdsa_sign_version_2
|
|
(param $key_type_id i32) (param $key i32) (param $msg i64) (param $out i32) (return i32))
|
|
(func $ext_crypto_ecdsa_sign_prehashed_version_2
|
|
(param $key_type_id i32) (param $key i32) (param $msg i64) (param $out i32) (return i64))
|
|
</code></pre>
|
|
<p>The behaviour of these functions is identical to their version 1 counterparts. The new versions of these functions accept an <code>out</code> parameter containing the memory location where the host writes the signature. The signatures are always of a size known at compilation time. On success, these functions return <code>0</code>. If the public key can't be found in the keystore, these functions return <code>1</code> and do not write anything to <code>out</code>.</p>
|
|
<p>Note that the return value is <code>0</code> on success and <code>1</code> on failure, while the previous version of these functions wrote <code>1</code> on success (as it represents a SCALE-encoded <code>Some</code>) and <code>0</code> on failure (as it represents a SCALE-encoded <code>None</code>). Returning <code>0</code> on success and non-zero on failure is consistent with standard practices in the C programming language and is less surprising than the opposite.</p>
|
|
<pre><code class="language-wat">(func $ext_crypto_secp256k1_ecdsa_recover_version_3
|
|
(param $sig i32) (param $msg i32) (param $out i32) (return i32))
|
|
(func $ext_crypto_secp256k1_ecdsa_recover_compressed_version_3
|
|
(param $sig i32) (param $msg i32) (param $out i32) (return i32))
|
|
</code></pre>
|
|
<p>The behaviour of these functions is identical to their version 2 counterparts. The new versions of these functions accept an <code>out</code> parameter containing the memory location where the host writes the signature. The signatures are always of a size known at compilation time. On success, these functions return <code>0</code>. On failure, these functions return a non-zero value and do not write anything to <code>out</code>.</p>
|
|
<p>The non-zero value written on failure is:</p>
|
|
<ul>
|
|
<li>1: incorrect value of R or S</li>
|
|
<li>2: incorrect value of V</li>
|
|
<li>3: invalid signature</li>
|
|
</ul>
|
|
<p>These values are equal to the values returned on error by the version 2 (see <a href="https://spec.polkadot.network/chap-host-api#defn-ecdsa-verify-error">https://spec.polkadot.network/chap-host-api#defn-ecdsa-verify-error</a>), but incremented by 1 to reserve 0 for success.</p>
|
|
<pre><code class="language-wat">(func $ext_crypto_ed25519_num_public_keys_version_1
|
|
(param $key_type_id i32) (return i32))
|
|
(func $ext_crypto_ed25519_public_key_version_1
|
|
(param $key_type_id i32) (param $key_index i32) (param $out i32))
|
|
(func $ext_crypto_sr25519_num_public_keys_version_1
|
|
(param $key_type_id i32) (return i32))
|
|
(func $ext_crypto_sr25519_public_key_version_1
|
|
(param $key_type_id i32) (param $key_index i32) (param $out i32))
|
|
(func $ext_crypto_ecdsa_num_public_keys_version_1
|
|
(param $key_type_id i32) (return i32))
|
|
(func $ext_crypto_ecdsa_public_key_version_1
|
|
(param $key_type_id i32) (param $key_index i32) (param $out i32))
|
|
</code></pre>
|
|
<p>The functions supersede the <code>ext_crypto_ed25519_public_key_version_1</code>, <code>ext_crypto_sr25519_public_key_version_1</code>, and <code>ext_crypto_ecdsa_public_key_version_1</code> host functions.</p>
|
|
<p>Instead of calling <code>ext_crypto_ed25519_public_key_version_1</code> to obtain the list of all the keys at once, the runtime should instead call <code>ext_crypto_ed25519_num_public_keys_version_1</code> to get the number of public keys available, then <code>ext_crypto_ed25519_public_key_version_1</code> repeatedly.
|
|
The <code>ext_crypto_ed25519_public_key_version_1</code> function writes the public key of the given <code>key_index</code> to the memory location designated by <code>out</code>. The <code>key_index</code> must be between 0 (included) and <code>n</code> (excluded), where <code>n</code> is the value returned by <code>ext_crypto_ed25519_num_public_keys_version_1</code>. Execution must trap if <code>n</code> is out of range.</p>
|
|
<p>The same explanations apply for <code>ext_crypto_sr25519_public_key_version_1</code> and <code>ext_crypto_ecdsa_public_key_version_1</code>.</p>
|
|
<p>Host implementers should be aware that the list of public keys (including their ordering) must not change while the runtime is running. That is most likely done by copying the list of all available keys either at the start of the execution or the first time the list is accessed.</p>
|
|
<pre><code class="language-wat">(func $ext_offchain_http_request_start_version_2
|
|
(param $method i64) (param $uri i64) (param $meta i64) (result i64))
|
|
</code></pre>
|
|
<p>The behaviour of this function is identical to its version 1 counterpart. Instead of allocating a buffer, writing the request identifier in it, and returning a pointer to it, version 2 of this function simply returns the newly-assigned identifier to the HTTP request. On failure, this function returns <code>-1</code>. An identifier of <code>-1</code> is invalid and is reserved to indicate failure.</p>
|
|
<pre><code class="language-wat">(func $ext_offchain_http_request_write_body_version_2
|
|
(param $method i64) (param $uri i64) (param $meta i64) (result i64))
|
|
(func $ext_offchain_http_response_read_body_version_2
|
|
(param $request_id i32) (param $buffer i64) (param $deadline i64) (result i64))
|
|
</code></pre>
|
|
<p>The behaviour of these functions is identical to their version 1 counterpart. Instead of allocating a buffer, writing two bytes in it, and returning a pointer to it, the new version of these functions simply indicates what happened:</p>
|
|
<ul>
|
|
<li>For <code>ext_offchain_http_request_write_body_version_2</code>, 0 on success.</li>
|
|
<li>For <code>ext_offchain_http_response_read_body_version_2</code>, 0 or a non-zero number of bytes on success.</li>
|
|
<li>-1 if the deadline was reached.</li>
|
|
<li>-2 if there was an I/O error while processing the request.</li>
|
|
<li>-3 if the identifier of the request is invalid.</li>
|
|
</ul>
|
|
<p>These values are equal to the values returned on error by version 1 (see <a href="https://spec.polkadot.network/chap-host-api#defn-http-error">https://spec.polkadot.network/chap-host-api#defn-http-error</a>), but tweaked to reserve positive numbers for success.</p>
|
|
<p>When it comes to <code>ext_offchain_http_response_read_body_version_2</code>, the host implementers must not read too much data at once to avoid ambiguity in the returned value. Given that the <code>buffer</code> size is always inferior or equal to 4 GiB, this is not a problem.</p>
|
|
<pre><code class="language-wat">(func $ext_offchain_http_response_wait_version_2
|
|
(param $ids i64) (param $deadline i64) (param $out i32))
|
|
</code></pre>
|
|
<p>The behaviour of this function is identical to its version 1 counterpart. Instead of allocating a buffer, writing the output to it, and returning a pointer to it, the new version of this function accepts an <code>out</code> parameter containing the memory location where the host writes the output.</p>
|
|
<p>The encoding of the response code is also modified compared to its version 1 counterpart, and each response code now encodes up to 4 little-endian bytes as described below:</p>
|
|
<ul>
|
|
<li>100-999: The request has finished with the given HTTP status code.</li>
|
|
<li>-1: The deadline was reached.</li>
|
|
<li>-2: There was an I/O error while processing the request.</li>
|
|
<li>-3: The identifier of the request is invalid.</li>
|
|
</ul>
|
|
<p>The buffer passed to <code>out</code> must always have a size of <code>4 * n</code> where <code>n</code> is the number of elements in the <code>ids</code>.</p>
|
|
<pre><code class="language-wat">(func $ext_offchain_http_response_header_name_version_1
|
|
(param $request_id i32) (param $header_index i32) (param $out i64) (result i64))
|
|
(func $ext_offchain_http_response_header_value_version_1
|
|
(param $request_id i32) (param $header_index i32) (param $out i64) (result i64))
|
|
</code></pre>
|
|
<p>These functions supersede the <code>ext_offchain_http_response_headers_version_1</code> host function.</p>
|
|
<p>Contrary to <code>ext_offchain_http_response_headers_version_1</code>, only one header indicated by <code>header_index</code> can be read at a time. Instead of calling <code>ext_offchain_http_response_headers_version_1</code> once, the runtime should call <code>ext_offchain_http_response_header_name_version_1</code> and <code>ext_offchain_http_response_header_value_version_1</code> multiple times with an increasing <code>header_index</code>, until a value of <code>-1</code> is returned.</p>
|
|
<p>These functions accept an <code>out</code> parameter containing <a href="https://spec.polkadot.network/chap-host-api#defn-runtime-pointer-size">a pointer-size</a> to the memory location where the header name or value should be written.</p>
|
|
<p>These functions return the size, in bytes, of the header name or header value. If the request doesn't exist or is in an invalid state (as documented for <code>ext_offchain_http_response_headers_version_1</code>) or the <code>header_index</code> is out of range, a value of <code>-1</code> is returned. Given that the host must never write more bytes than the size of the buffer in <code>out</code>, and that the size of this buffer is expressed as a 32-bit number, a 64-bit value of <code>-1</code> is not ambiguous.</p>
|
|
<p>If the buffer in <code>out</code> is too small to fit the entire header name or value, only the bytes that fit are written, and the rest are discarded.</p>
|
|
<pre><code class="language-wat">(func $ext_offchain_submit_transaction_version_2
|
|
(param $data i64) (return i32))
|
|
(func $ext_offchain_http_request_add_header_version_2
|
|
(param $request_id i32) (param $name i64) (param $value i64) (result i64))
|
|
</code></pre>
|
|
<p>Instead of allocating a buffer, writing <code>1</code> or <code>0</code> in it, and returning a pointer to it, the version 2 of these functions returns <code>0</code> or <code>-1</code>, where <code>0</code> indicates success and <code>-1</code> indicates failure.</p>
|
|
<pre><code class="language-wat">(func $ext_offchain_local_storage_read_version_1
|
|
(param $kind i32) (param $key i64) (param $value_out i64) (param $offset i32) (result i64))
|
|
</code></pre>
|
|
<p>This function supercedes the <code>ext_offchain_local_storage_get_version_1</code> host function, and uses an API and logic similar to <code>ext_storage_read_version_2</code>.</p>
|
|
<p>It reads the offchain local storage key indicated by <code>kind</code> and <code>key</code> starting at the byte indicated by <code>offset</code>, and writes the value to the <a href="https://spec.polkadot.network/chap-host-api#defn-runtime-pointer-size">pointer-size</a> indicated by <code>value_out</code>.</p>
|
|
<p>The function returns the number of bytes written into the <code>value_out</code> buffer. If the entry doesn't exist, the <code>-1</code> value is returned. Given that the host must never write more bytes than the size of the buffer in <code>value_out</code>, and that the size of this buffer is expressed as a 32-bit number, a 64-bit value of <code>-1</code> is not ambiguous.</p>
|
|
<pre><code class="language-wat">(func $ext_offchain_network_peer_id_version_1
|
|
(param $out i64) (result i64))
|
|
</code></pre>
|
|
<p>This function writes <a href="https://spec.polkadot.network/chap-networking#id-node-identities">the <code>PeerId</code> of the local node</a> to the memory location indicated by <code>out</code>. A <code>PeerId</code> is always 38 bytes long. This function returns <code>0</code> on success or <code>-1</code> if the network state is unavailable.</p>
|
|
<pre><code class="language-wat">(func $ext_misc_runtime_version_version_2
|
|
(param $wasm i64) (param $out i64) (result i64))
|
|
</code></pre>
|
|
<p>The behaviour of this function is identical to its version 1 counterpart. Instead of allocating a buffer, writing the output to it, and returning a pointer to it, the new version of this function accepts an <code>out</code> parameter containing <a href="https://spec.polkadot.network/chap-host-api#defn-runtime-pointer-size">pointer-size</a> to the memory location where the host writes the output. If the output buffer is not large enough, the version information is truncated. Returns the length of the encoded version information, or <code>-1</code> in case of any failure.</p>
|
|
<pre><code class="language-wat">(func $ext_offchain_random_seed_version_2 (param $out i32))
|
|
</code></pre>
|
|
<p>The behaviour of this function is identical to its version 1 counterpart. Instead of allocating a buffer, writing the output to it, and returning a pointer to it, the new version of this function accepts an <code>out</code> parameter containing the address of the memory location where the host writes the output. The size is output is always 32 bytes.</p>
|
|
<pre><code class="language-wat">(func $ext_misc_input_read_version_1
|
|
(param $offset i64) (param $out i64) (result i64))
|
|
</code></pre>
|
|
<p>When a runtime function is called, the host uses the allocator to allocate memory within the runtime to write some input data. The new host function provides an alternative way to access the input that doesn't use the allocator.</p>
|
|
<p>The function copies some data from the input data to the runtime's memory. The <code>offset</code> parameter indicates the offset within the input data from which to start copying, and must lie inside the output buffer provided. The <code>out</code> parameter is <a href="https://spec.polkadot.network/chap-host-api#defn-runtime-pointer-size">a pointer-size</a> and contains the buffer where to write.</p>
|
|
<p>The runtime execution stops with an error if <code>offset</code> is strictly greater than the input data size.</p>
|
|
<p>The return value is the number of bytes written unless <code>out</code> has zero length, in which case the full length of input data in bytes is returned, and nothing is written into the output buffer.</p>
|
|
<h3 id="other-changes"><a class="header" href="#other-changes">Other changes</a></h3>
|
|
<p>In addition to the new host functions, this RFC proposes two changes to the runtime-host interface:</p>
|
|
<ul>
|
|
<li>The following function signature is now also accepted for runtime entry points: <code>(func (result i64))</code>.</li>
|
|
<li>Runtimes no longer need to expose a constant named <code>__heap_base</code>.</li>
|
|
</ul>
|
|
<p>All the host functions superseded by new host functions are now considered deprecated and should no longer be used.</p>
|
|
<p>The following other host functions are also considered deprecated:</p>
|
|
<ul>
|
|
<li><code>ext_storage_get_version_1</code></li>
|
|
<li><code>ext_storage_changes_root_version_1</code></li>
|
|
<li><code>ext_default_child_storage_get_version_1</code></li>
|
|
<li><code>ext_allocator_malloc_version_1</code></li>
|
|
<li><code>ext_allocator_free_version_1</code></li>
|
|
<li><code>ext_offchain_network_state_version_1</code></li>
|
|
</ul>
|
|
<h2 id="unresolved-questions"><a class="header" href="#unresolved-questions">Unresolved Questions</a></h2>
|
|
<p>The changes in this RFC would need to be benchmarked. That involves implementing the RFC and measuring the speed difference.</p>
|
|
<p>It is expected that most host functions are faster or equal in speed to their deprecated counterparts, with the following exceptions:</p>
|
|
<ul>
|
|
<li>
|
|
<p><code>ext_misc_input_read_version_1</code> is inherently slower than obtaining a buffer with the entire data due to the two extra function calls and the extra copying. However, given that this only happens once per runtime call, the cost is expected to be negligible.</p>
|
|
</li>
|
|
<li>
|
|
<p>The <code>ext_crypto_*_public_keys</code>, <code>ext_offchain_network_state</code>, and <code>ext_offchain_http_*</code> host functions are likely slightly slower than their deprecated counterparts, but given that they are used only in offchain workers, that is acceptable.</p>
|
|
</li>
|
|
<li>
|
|
<p>It is unclear how replacing <code>ext_storage_get</code> with <code>ext_storage_read</code> and <code>ext_default_child_storage_get</code> with <code>ext_default_child_storage_read</code> will impact performance.</p>
|
|
</li>
|
|
<li>
|
|
<p>It is unclear how the changes to <code>ext_storage_next_key</code> and <code>ext_default_child_storage_next_key</code> will impact performance.</p>
|
|
</li>
|
|
</ul>
|
|
|
|
</main>
|
|
|
|
<nav class="nav-wrapper" aria-label="Page navigation">
|
|
<!-- Mobile navigation buttons -->
|
|
<a rel="prev" href="../proposed/0117-unbrick-collective.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
|
<i class="fa fa-angle-left"></i>
|
|
</a>
|
|
|
|
<a rel="next prefetch" href="../proposed/0146-deflationary-fee-proposal.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
|
<i class="fa fa-angle-right"></i>
|
|
</a>
|
|
|
|
<div style="clear: both"></div>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
|
|
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
|
<a rel="prev" href="../proposed/0117-unbrick-collective.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
|
<i class="fa fa-angle-left"></i>
|
|
</a>
|
|
|
|
<a rel="next prefetch" href="../proposed/0146-deflationary-fee-proposal.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
|
<i class="fa fa-angle-right"></i>
|
|
</a>
|
|
</nav>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<script>
|
|
window.playground_copyable = true;
|
|
</script>
|
|
|
|
|
|
<script src="../elasticlunr.min.js"></script>
|
|
<script src="../mark.min.js"></script>
|
|
<script src="../searcher.js"></script>
|
|
|
|
<script src="../clipboard.min.js"></script>
|
|
<script src="../highlight.js"></script>
|
|
<script src="../book.js"></script>
|
|
|
|
<!-- Custom JS scripts -->
|
|
|
|
|
|
</div>
|
|
</body>
|
|
</html>
|