Skip to content

Commit c582c2e

Browse files
authored
Multichain API for Web (#5143) (#5157)
## Motivation We have several internal and external use cases for dealing with more than one chain at once. ## Proposal Add chain-specific API, and have `Client` only initialize the client rather than creating a default chain. This provides a place to put the (currently hardcoded) client options, which I do, closing #4740. ## Test Plan Tested locally on all our Web examples. ## Release Plan - These changes should be backported to the latest `testnet` branch, then - be released in a new SDK, ## Links - [reviewer checklist](https://github.com/linera-io/linera-protocol/blob/main/CONTRIBUTING.md#reviewer-checklist)
1 parent 4fd9f5f commit c582c2e

33 files changed

Lines changed: 1030 additions & 826 deletions

File tree

CLI.md

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,6 @@ Client implementation and command-line tool for the Linera blockchain
121121

122122
###### **Options:**
123123

124-
* `--wallet <WALLET_STATE_PATH>` — Sets the file storing the private state of user chains (an empty one will be created if missing)
125-
* `--keystore <KEYSTORE_PATH>` — Sets the file storing the keystore state
126-
* `-w`, `--with-wallet <WITH_WALLET>` — Given an ASCII alphanumeric parameter `X`, read the wallet state and the wallet storage config from the environment variables `LINERA_WALLET_{X}` and `LINERA_STORAGE_{X}` instead of `LINERA_WALLET` and `LINERA_STORAGE`
127124
* `--send-timeout-ms <SEND_TIMEOUT>` — Timeout for sending queries (milliseconds)
128125

129126
Default value: `4000`
@@ -145,9 +142,6 @@ Client implementation and command-line tool for the Linera blockchain
145142
* `--max-retries <MAX_RETRIES>` — Number of times to retry connecting to a validator
146143

147144
Default value: `10`
148-
* `--chrome-trace-exporter` — Enable OpenTelemetry Chrome JSON exporter for trace data analysis
149-
* `--chrome-trace-file <CHROME_TRACE_FILE>` — Output file path for Chrome trace JSON format. Can be visualized in chrome://tracing or Perfetto UI
150-
* `--otlp-exporter-endpoint <OTLP_EXPORTER_ENDPOINT>` — OpenTelemetry OTLP exporter endpoint (requires opentelemetry feature)
151145
* `--wait-for-outgoing-messages` — Whether to wait until a quorum of validators has confirmed that all sent cross-chain messages have been delivered
152146
* `--long-lived-services` — (EXPERIMENTAL) Whether application services can persist in some cases between queries
153147
* `--blanket-message-policy <BLANKET_MESSAGE_POLICY>` — The policy for handling incoming messages
@@ -205,6 +199,19 @@ Client implementation and command-line tool for the Linera blockchain
205199
* `--alternative-peers-retry-delay-ms <ALTERNATIVE_PEERS_RETRY_DELAY_MS>` — Delay in milliseconds between starting requests to different peers. This helps to stagger requests and avoid overwhelming the network
206200

207201
Default value: `150`
202+
* `--listener-skip-process-inbox` — Do not create blocks automatically to receive incoming messages. Instead, wait for an explicit mutation `processInbox`
203+
* `--listener-delay-before-ms <DELAY_BEFORE_MS>` — Wait before processing any notification (useful for testing)
204+
205+
Default value: `0`
206+
* `--listener-delay-after-ms <DELAY_AFTER_MS>` — Wait after processing any notification (useful for rate limiting)
207+
208+
Default value: `0`
209+
* `--wallet <WALLET_STATE_PATH>` — Sets the file storing the private state of user chains (an empty one will be created if missing)
210+
* `--keystore <KEYSTORE_PATH>` — Sets the file storing the keystore state
211+
* `-w`, `--with-wallet <WITH_WALLET>` — Given an ASCII alphanumeric parameter `X`, read the wallet state and the wallet storage config from the environment variables `LINERA_WALLET_{X}` and `LINERA_STORAGE_{X}` instead of `LINERA_WALLET` and `LINERA_STORAGE`
212+
* `--chrome-trace-exporter` — Enable OpenTelemetry Chrome JSON exporter for trace data analysis
213+
* `--chrome-trace-file <CHROME_TRACE_FILE>` — Output file path for Chrome trace JSON format. Can be visualized in chrome://tracing or Perfetto UI
214+
* `--otlp-exporter-endpoint <OTLP_EXPORTER_ENDPOINT>` — OpenTelemetry OTLP exporter endpoint (requires opentelemetry feature)
208215
* `--storage <STORAGE_CONFIG>` — Storage configuration for the blockchain history
209216
* `--storage-max-concurrent-queries <STORAGE_MAX_CONCURRENT_QUERIES>` — The maximal number of simultaneous queries to the database
210217
* `--storage-max-stream-queries <STORAGE_MAX_STREAM_QUERIES>` — The maximal number of simultaneous stream queries to the database

Cargo.lock

Lines changed: 46 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,7 @@ tracing-subscriber = { version = "0.3.18", default-features = false, features =
275275
tracing-web = "0.1.3"
276276
trait-set = "0.3.0"
277277
trait-variant = "0.1.1"
278+
tsify-next = { version = "0.5.6", default-features = false, features = ["js"] }
278279
url = "2.4"
279280
wasm-bindgen = "0.2.92"
280281
wasm-bindgen-futures = "0.4.42"

examples/Cargo.lock

Lines changed: 51 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/counter/index.html

Lines changed: 41 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -48,67 +48,53 @@ <h2>Chain history for <code id="chain-id" class="hex">requesting chain…</code>
4848
</div>
4949
</div>
5050

51-
<script type="importmap">
52-
{
53-
"imports": {
54-
"@linera/client": "./js/@linera/client/index.js"
55-
}
56-
}
57-
</script>
58-
5951
<script type="module">
60-
import * as linera from '@linera/client';
61-
import { ethers } from 'ethers';
52+
import * as linera from '@linera/client';
53+
import { ethers } from 'ethers';
6254

63-
async function run() {
64-
console.log(linera);
65-
await linera.initialize();
66-
const faucet = await new linera.Faucet(import.meta.env.LINERA_FAUCET_URL);
67-
const signer = linera.signer.PrivateKey.createRandom();
68-
const wallet = await faucet.createWallet();
69-
const owner = await signer.address();
70-
document.getElementById('owner').innerText = owner;
71-
document.getElementById('chain-id').innerText = await faucet.claimChain(wallet, owner);
72-
const client = await new linera.Client(wallet, signer);
73-
const counter = await client.application(import.meta.env.LINERA_APPLICATION_ID);
74-
const logs = document.getElementById('logs');
75-
const incrementButton = document.getElementById('increment-btn');
76-
const blockTemplate = document.getElementById('block-template');
55+
await linera.initialize();
56+
const faucet = await new linera.Faucet(import.meta.env.LINERA_FAUCET_URL);
57+
const signer = linera.signer.PrivateKey.createRandom();
58+
const wallet = await faucet.createWallet();
59+
const owner = await signer.address();
60+
const chainId = await faucet.claimChain(wallet, owner);
61+
document.getElementById('owner').innerText = owner;
62+
document.getElementById('chain-id').innerText = chainId;
63+
const client = await new linera.Client(wallet, signer);
64+
let chain = await client.chain(chainId);
65+
const counter = await chain.application(import.meta.env.LINERA_APPLICATION_ID);
66+
const logs = document.getElementById('logs');
67+
const incrementButton = document.getElementById('increment-btn');
68+
const blockTemplate = document.getElementById('block-template');
7769

78-
function addLogEntry(block) {
79-
const entry = logs.getElementsByTagName('template')[0].content.cloneNode(true);
80-
entry.querySelector('.height').textContent = block.height;
81-
entry.querySelector('.hash').textContent = block.hash;
82-
logs.insertBefore(entry, logs.firstChild);
83-
}
70+
function addLogEntry(block) {
71+
const entry = logs.getElementsByTagName('template')[0].content.cloneNode(true);
72+
entry.querySelector('.height').textContent = block.height;
73+
entry.querySelector('.hash').textContent = block.hash;
74+
logs.insertBefore(entry, logs.firstChild);
75+
}
8476

85-
async function updateCount(blockHash) {
86-
const response = await counter.query('{ "query": "query { value }" }', { blockHash });
87-
document.getElementById('count').innerText
88-
= JSON.parse(response).data.value;
89-
}
77+
async function updateCount(blockHash) {
78+
const response = await counter.query('{ "query": "query { value }" }', { blockHash });
79+
document.getElementById('count').innerText
80+
= JSON.parse(response).data.value;
81+
}
9082

91-
updateCount();
92-
client.onNotification(notification => {
93-
let newBlock = notification.reason.NewBlock;
94-
if (notification.reason.BlockExecuted) {
95-
let hash = notification.reason.BlockExecuted.hash;
96-
updateCount(hash);
97-
} else if (newBlock) {
98-
addLogEntry(newBlock);
99-
updateCount(null);
100-
}
101-
});
102-
103-
incrementButton.addEventListener('click', () => {
104-
counter.query('{ "query": "mutation { increment(value: 1) }" }');
105-
});
106-
}
83+
updateCount();
84+
chain.onNotification(notification => {
85+
let newBlock = notification.reason.NewBlock;
86+
if (notification.reason.BlockExecuted) {
87+
let hash = notification.reason.BlockExecuted.hash;
88+
updateCount(hash);
89+
} else if (newBlock) {
90+
addLogEntry(newBlock);
91+
updateCount(null);
92+
}
93+
});
10794

108-
if (document.readyState === 'loading')
109-
document.addEventListener('DOMContentLoaded', run);
110-
else
111-
run();
95+
incrementButton.addEventListener('click', () => {
96+
counter.query('{ "query": "mutation { increment(value: 1) }" }');
97+
});
11298
</script>
11399
</body>
114100
</html>

examples/counter/metamask/index.html

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -65,20 +65,23 @@ <h2>Chain history for <code id="chain-id" class="hex">requesting chain…</code>
6565
const signer = await new linera_metamask.Signer();
6666
const wallet = await faucet.createWallet();
6767
const owner = await signer.address();
68-
const chain = await faucet.claimChain(wallet, owner);
68+
const chainId = await faucet.claimChain(wallet, owner);
6969
document.getElementById('owner').innerText = owner;
70-
document.getElementById('chain-id').innerText = chain;
70+
document.getElementById('chain-id').innerText = chainId;
7171

7272
// For autosigning: first we set up a local (in-memory) signer, and provide it to the
7373
// client along with the MetaMask signer
7474
const autosigner = linera.signer.PrivateKey.createRandom();
7575
const client = await new linera.Client(wallet, new linera.signer.Composite(autosigner, signer));
7676

77-
// Connect to the counter application.
78-
const counter = await client.application(import.meta.env.LINERA_APPLICATION_ID);
77+
// Connect to our user chain.
78+
const chain = await client.chain(chainId);
79+
80+
// Connect to the counter application on the chain.
81+
const counter = await chain.application(import.meta.env.LINERA_APPLICATION_ID);
7982

8083
// When we get a new block, show it in the UI and update the counter from the chain state.
81-
client.onNotification(notification => {
84+
chain.onNotification(notification => {
8285
let newBlock = notification.reason.NewBlock;
8386
if (!newBlock) return;
8487
addLogEntry(newBlock);
@@ -88,8 +91,8 @@ <h2>Chain history for <code id="chain-id" class="hex">requesting chain…</code>
8891
// For autosigning: we then add the in-memory signer as an owner of the chain in the
8992
// wallet, and set it as the default owner (so it will be used to process messages and
9093
// events).
91-
await client.addOwner(autosigner.address());
92-
wallet.setOwner(chain, autosigner.address());
94+
await chain.addOwner(autosigner.address());
95+
wallet.setOwner(chainId, autosigner.address());
9396

9497
function addLogEntry(block) {
9598
const entry = logs.getElementsByTagName('template')[0].content.cloneNode(true);

0 commit comments

Comments
 (0)