Skip to content

Commit 56a528a

Browse files
authored
Autosigning improvements (#5126) (#5146)
## Motivation @afck pointed out that having a lot of owners with non-zero weights would slow the chain down in single-leader rounds. @ma2bd pointed out that we need to make mutations using the original owner. ## Proposal Add a `weight` option to `addOwner` in an options object, and default it to zero. Add an optional `owner` option to `query` in an options object, and use it to sign mutations. Also, document the above inline. ## Test Plan Tested locally. Also, CI should test some of the API (but sadly not yet as much as we'd like it to). ## 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 3a928c1 commit 56a528a

4 files changed

Lines changed: 85 additions & 57 deletions

File tree

examples/counter/index.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,19 +82,19 @@ <h2>Chain history for <code id="chain-id" class="hex">requesting chain…</code>
8282
logs.insertBefore(entry, logs.firstChild);
8383
}
8484

85-
async function updateCount(block_hash) {
86-
const response = await counter.query('{ "query": "query { value }" }', block_hash);
85+
async function updateCount(blockHash) {
86+
const response = await counter.query('{ "query": "query { value }" }', { blockHash });
8787
document.getElementById('count').innerText
8888
= JSON.parse(response).data.value;
8989
}
9090

91-
updateCount(null);
91+
updateCount();
9292
client.onNotification(notification => {
9393
let newBlock = notification.reason.NewBlock;
9494
if (notification.reason.BlockExecuted) {
9595
let hash = notification.reason.BlockExecuted.hash;
9696
updateCount(hash);
97-
} else if(newBlock) {
97+
} else if (newBlock) {
9898
addLogEntry(newBlock);
9999
updateCount(null);
100100
}

examples/counter/metamask/index.html

Lines changed: 50 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -52,61 +52,64 @@ <h2>Chain history for <code id="chain-id" class="hex">requesting chain…</code>
5252
</div>
5353

5454
<script type="module">
55-
import * as linera from '@linera/client';
56-
import * as linera_metamask from '@linera/metamask';
55+
import * as linera from '@linera/client';
56+
import * as linera_metamask from '@linera/metamask';
5757

58-
async function run() {
59-
await linera.initialize();
60-
const faucet = await new linera.Faucet(import.meta.env.LINERA_FAUCET_URL);
61-
const signer = await new linera_metamask.Signer();
62-
const wallet = await faucet.createWallet();
63-
const owner = await signer.address();
64-
const chain = await faucet.claimChain(wallet, owner);
65-
document.getElementById('owner').innerText = owner;
66-
document.getElementById('chain-id').innerText = chain;
58+
const logs = document.getElementById('logs');
59+
const incrementButton = document.getElementById('increment-btn');
60+
const blockTemplate = document.getElementById('block-template');
6761

68-
// add a new local wallet to the chain that can autosign blocks
69-
// without prompting every time
70-
const autosigner = linera.signer.PrivateKey.createRandom();
71-
const client = await new linera.Client(wallet, new linera.signer.Composite(autosigner, signer));
72-
await client.addOwner(autosigner.address());
73-
wallet.setOwner(chain, autosigner.address());
62+
// Initialize the Linera client and set up the MetaMask signer.
63+
await linera.initialize();
64+
const faucet = await new linera.Faucet(import.meta.env.LINERA_FAUCET_URL);
65+
const signer = await new linera_metamask.Signer();
66+
const wallet = await faucet.createWallet();
67+
const owner = await signer.address();
68+
const chain = await faucet.claimChain(wallet, owner);
69+
document.getElementById('owner').innerText = owner;
70+
document.getElementById('chain-id').innerText = chain;
7471

75-
const counter = await client.application(import.meta.env.LINERA_APPLICATION_ID);
76-
const logs = document.getElementById('logs');
77-
const incrementButton = document.getElementById('increment-btn');
78-
const blockTemplate = document.getElementById('block-template');
72+
// For autosigning: first we set up a local (in-memory) signer, and provide it to the
73+
// client along with the MetaMask signer
74+
const autosigner = linera.signer.PrivateKey.createRandom();
75+
const client = await new linera.Client(wallet, new linera.signer.Composite(autosigner, signer));
7976

80-
function addLogEntry(block) {
81-
const entry = logs.getElementsByTagName('template')[0].content.cloneNode(true);
82-
entry.querySelector('.height').textContent = block.height;
83-
entry.querySelector('.hash').textContent = block.hash;
84-
logs.insertBefore(entry, logs.firstChild);
85-
}
77+
// Connect to the counter application.
78+
const counter = await client.application(import.meta.env.LINERA_APPLICATION_ID);
8679

87-
async function updateCount() {
88-
const response = await counter.query('{ "query": "query { value }" }');
89-
document.getElementById('count').innerText
90-
= JSON.parse(response).data.value;
91-
}
80+
// When we get a new block, show it in the UI and update the counter from the chain state.
81+
client.onNotification(notification => {
82+
let newBlock = notification.reason.NewBlock;
83+
if (!newBlock) return;
84+
addLogEntry(newBlock);
85+
updateCount(newBlock.hash);
86+
});
9287

93-
updateCount();
94-
client.onNotification(notification => {
95-
let newBlock = notification.reason.NewBlock;
96-
if (!newBlock) return;
97-
addLogEntry(newBlock);
98-
updateCount();
99-
});
88+
// For autosigning: we then add the in-memory signer as an owner of the chain in the
89+
// wallet, and set it as the default owner (so it will be used to process messages and
90+
// events).
91+
await client.addOwner(autosigner.address());
92+
wallet.setOwner(chain, autosigner.address());
10093

101-
incrementButton.addEventListener('click', () => {
102-
counter.query('{ "query": "mutation { increment(value: 1) }" }');
103-
});
104-
}
94+
function addLogEntry(block) {
95+
const entry = logs.getElementsByTagName('template')[0].content.cloneNode(true);
96+
entry.querySelector('.height').textContent = block.height;
97+
entry.querySelector('.hash').textContent = block.hash;
98+
logs.insertBefore(entry, logs.firstChild);
99+
}
100+
101+
async function updateCount(blockHash) {
102+
const response = await counter.query('{ "query": "query { value }" }', { blockHash });
103+
document.getElementById('count').innerText = JSON.parse(response).data.value;
104+
}
105+
106+
updateCount();
105107

106-
if (document.readyState === 'loading')
107-
document.addEventListener('DOMContentLoaded', run);
108-
else
109-
run();
108+
incrementButton.addEventListener('click', () => {
109+
// For autosigning: when we make user-initiated mutations, we explicitly make them with
110+
// the original (MetaMask) owner by providing the `owner` option to the `query` call.
111+
counter.query('{ "query": "mutation { increment(value: 1) }" }', { owner });
112+
});
110113
</script>
111114
</body>
112115
</html>

examples/native-fungible/index.html

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,10 @@ <h2>Chain history for <code id="chain-id" class="hex">requesting a new microchai
164164
}
165165

166166
async function updateBalance(application, owner, blockHash) {
167-
const response = JSON.parse(await application.query(gql(`query { tickerSymbol, accounts { entry(key: "${owner}") { value } } }`), blockHash));
167+
const response = JSON.parse(await application.query(
168+
gql(`query { tickerSymbol, accounts { entry(key: "${owner}") { value } } }`),
169+
{ blockHash },
170+
));
168171
console.debug('application response:', response);
169172
document.querySelector('#ticker-symbol').textContent = response.data.tickerSymbol;
170173
document.querySelector('#balance').textContent = (+(response?.data?.accounts?.entry.value || 0)).toFixed(2);

web/@linera/client/src/lib.rs

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,13 @@ struct TransferParams {
133133
recipient: linera_base::identifiers::Account,
134134
}
135135

136+
#[derive(Default, serde::Deserialize)]
137+
#[serde(rename_all = "camelCase")]
138+
struct QueryOptions {
139+
block_hash: Option<String>,
140+
owner: Option<AccountOwner>,
141+
}
142+
136143
#[wasm_bindgen]
137144
impl Client {
138145
/// Creates a new client and connects to the network.
@@ -313,11 +320,21 @@ impl Client {
313320
///
314321
/// If the owner is in the wrong format, or the chain client can't be instantiated.
315322
#[wasm_bindgen(js_name = addOwner)]
316-
pub async fn add_owner(&self, owner: JsValue) -> JsResult<()> {
323+
pub async fn add_owner(&self, owner: JsValue, options: JsValue) -> JsResult<()> {
324+
#[derive(Default, serde::Deserialize)]
325+
struct Options {
326+
#[serde(default)]
327+
weight: u64,
328+
}
329+
317330
let owner = serde_wasm_bindgen::from_value(owner)?;
331+
let Options { weight } =
332+
serde_wasm_bindgen::from_value::<Option<_>>(options)?.unwrap_or_default();
318333
let chain_client = self.default_chain_client().await?;
319-
self.apply_client_command(&chain_client, || chain_client.share_ownership(owner, 100))
320-
.await??;
334+
self.apply_client_command(&chain_client, || {
335+
chain_client.share_ownership(owner, weight)
336+
})
337+
.await??;
321338
Ok(())
322339
}
323340

@@ -407,9 +424,14 @@ impl Application {
407424
#[wasm_bindgen]
408425
// TODO(#14) allow passing bytes here rather than just strings
409426
// TODO(#15) a lot of this logic is shared with `linera_service::node_service`
410-
pub async fn query(&self, query: &str, block_hash: Option<String>) -> JsResult<String> {
427+
pub async fn query(&self, query: &str, options: JsValue) -> JsResult<String> {
411428
tracing::debug!("querying application: {query}");
412-
let chain_client = self.client.default_chain_client().await?;
429+
let QueryOptions { block_hash, owner } =
430+
serde_wasm_bindgen::from_value::<Option<_>>(options)?.unwrap_or_default();
431+
let mut chain_client = self.client.default_chain_client().await?;
432+
if let Some(owner) = owner {
433+
chain_client.set_preferred_owner(owner);
434+
}
413435
let block_hash = if let Some(hash) = block_hash {
414436
Some(hash.as_str().parse()?)
415437
} else {

0 commit comments

Comments
 (0)