Skip to content

Commit

Permalink
Use owners instead of public keys. (#3160)
Browse files Browse the repository at this point in the history
## Motivation

For #3162 we need to stop using `ChainOwnership` as a lookup table for
public keys.

In general, we are using `PublicKey` in several places where we should
be using the `Owner` type.

## Proposal

Remove public keys from `ChainOwnership`. Add the signer's public key
directly to the `BlockProposal` instead.

When creating key pairs, assigning chains and changing chain ownership
via CLI commands, node service mutations or system API calls, use
`Owner` instead of `PublicKey`.

## Test Plan

Several tests have been updated.

## Release Plan

- Nothing to do / These changes follow the usual release cycle.

## Links

- Closes #3165.
- [reviewer
checklist](https://github.com/linera-io/linera-protocol/blob/main/CONTRIBUTING.md#reviewer-checklist)
  • Loading branch information
afck authored Jan 22, 2025
1 parent 6a8789e commit 903b975
Show file tree
Hide file tree
Showing 50 changed files with 492 additions and 614 deletions.
18 changes: 9 additions & 9 deletions CLI.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ A Byzantine-fault tolerant sidechain with low-latency finality and high throughp
* `publish-and-create` — Create an application, and publish the required bytecode
* `request-application` — Request an application from another chain, so it can be used on this one
* `keygen` — Create an unassigned key-pair
* `assign` — Link a key owned by the wallet to a chain that was just created for that key
* `assign` — Link an owner with a key pair in the wallet to a chain that was created for that owner
* `retry-pending-block` — Retry a block we unsuccessfully tried to propose earlier
* `wallet` — Show the contents of the wallet
* `project` — Manage Linera projects
Expand Down Expand Up @@ -178,7 +178,7 @@ Open (i.e. activate) a new chain deriving the UID from an existing one
###### **Options:**

* `--from <CHAIN_ID>` — Chain ID (must be one of our chains)
* `--to-public-key <PUBLIC_KEY>`Public key of the new owner (otherwise create a key pair and remember it)
* `--owner <OWNER>`The new owner (otherwise create a key pair and remember it)
* `--initial-balance <BALANCE>` — The initial balance of the new chain. This is subtracted from the parent chain's balance

Default value: `0`
Expand All @@ -194,8 +194,8 @@ Open (i.e. activate) a new multi-owner chain deriving the UID from an existing o
###### **Options:**

* `--from <CHAIN_ID>` — Chain ID (must be one of our chains)
* `--super-owner-public-keys <SUPER_OWNER_PUBLIC_KEYS>`Public keys of the new super owners
* `--owner-public-keys <OWNER_PUBLIC_KEYS>`Public keys of the new regular owners
* `--super-owners <SUPER_OWNERS>`The new super owners
* `--owners <OWNERS>`The new regular owners
* `--owner-weights <OWNER_WEIGHTS>` — Weights for the new owners.

If they are specified there must be exactly one weight for each owner. If no weights are given, every owner will have weight 100.
Expand Down Expand Up @@ -230,8 +230,8 @@ Specify the complete set of new owners, by public key. Existing owners that are
###### **Options:**

* `--chain-id <CHAIN_ID>` — The ID of the chain whose owners will be changed
* `--super-owner-public-keys <SUPER_OWNER_PUBLIC_KEYS>`Public keys of the new super owners
* `--owner-public-keys <OWNER_PUBLIC_KEYS>`Public keys of the new regular owners
* `--super-owners <SUPER_OWNERS>`The new super owners
* `--owners <OWNERS>`The new regular owners
* `--owner-weights <OWNER_WEIGHTS>` — Weights for the new owners.

If they are specified there must be exactly one weight for each owner. If no weights are given, every owner will have weight 100.
Expand Down Expand Up @@ -679,13 +679,13 @@ Create an unassigned key-pair

## `linera assign`

Link a key owned by the wallet to a chain that was just created for that key
Link an owner with a key pair in the wallet to a chain that was created for that owner

**Usage:** `linera assign --key <KEY> --message-id <MESSAGE_ID>`
**Usage:** `linera assign --owner <OWNER> --message-id <MESSAGE_ID>`

###### **Options:**

* `--key <KEY>` — The public key to assign
* `--owner <OWNER>` — The owner to assign
* `--message-id <MESSAGE_ID>` — The ID of the message that created the chain. (This uniquely describes the chain and where it was created.)


Expand Down
5 changes: 1 addition & 4 deletions examples/amm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,13 +257,10 @@ In addition, we make an AMM operation per block mandatory, so owners cannot spam
with empty blocks.

```bash
PUB_KEY_AMM=fcf518d56455283ace2bbc11c71e684eb58af81bc98b96a18129e825ce24ea84
PUB_KEY_2=ca909dcf60df014c166be17eb4a9f6e2f9383314a57510206a54cd841ade455e

kill %% && sleep 1 # Kill the service so we can use CLI commands for chain 1.

linera --wait-for-outgoing-messages change-ownership \
--owner-public-keys $PUB_KEY_AMM $PUB_KEY_2
--owners $OWNER_AMM $OWNER_2

linera --wait-for-outgoing-messages change-application-permissions \
--execute-operations $AMM_APPLICATION_ID \
Expand Down
14 changes: 7 additions & 7 deletions examples/hex-game/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ APP_ID=$(linera -w0 --wait-for-outgoing-messages \
\"blockDelay\": 100000000
}")

PUB_KEY_1=$(linera -w0 keygen)
PUB_KEY_2=$(linera -w1 keygen)
OWNER_1=$(linera -w0 keygen)
OWNER_2=$(linera -w1 keygen)

linera -w0 service --port 8080 &
sleep 1
Expand All @@ -77,8 +77,8 @@ on the URL you get by running `echo "http://localhost:8080/chains/$CHAIN_1/appli
mutation {
start(
players: [
"$PUB_KEY_1",
"$PUB_KEY_2"
"$OWNER_1",
"$OWNER_2"
],
boardSize: 11,
feeBudget: "1"
Expand All @@ -101,7 +101,7 @@ It contains the temporary chain's ID, and the ID of the message that created it:
```gql,uri=http://localhost:8080/chains/$CHAIN_1/applications/$APP_ID
query {
gameChains {
entry(key: "$PUB_KEY_1") {
entry(key: "$OWNER_1") {
value {
messageId chainId
}
Expand All @@ -120,8 +120,8 @@ kill %% && sleep 1 # Kill the service so we can use CLI commands for wallet 0
HEX_CHAIN=$(echo "$QUERY_RESULT" | jq -r '.gameChains.entry.value[0].chainId')
MESSAGE_ID=$(echo "$QUERY_RESULT" | jq -r '.gameChains.entry.value[0].messageId')

linera -w0 assign --key $PUB_KEY_1 --message-id $MESSAGE_ID
linera -w1 assign --key $PUB_KEY_2 --message-id $MESSAGE_ID
linera -w0 assign --owner $OWNER_1 --message-id $MESSAGE_ID
linera -w1 assign --owner $OWNER_2 --message-id $MESSAGE_ID

linera -w0 service --port 8080 &
linera -w1 service --port 8081 &
Expand Down
20 changes: 9 additions & 11 deletions examples/hex-game/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use async_graphql::ComplexObject;
use hex_game::{Board, Clock, HexAbi, HexOutcome, Operation, Timeouts};
use linera_sdk::{
base::{
Amount, ApplicationPermissions, ChainId, ChainOwnership, Owner, PublicKey, TimeoutConfig,
Amount, ApplicationPermissions, ChainId, ChainOwnership, Owner, TimeoutConfig,
WithContractAbi,
},
views::{RootView, View},
Expand Down Expand Up @@ -75,9 +75,7 @@ impl Contract for HexContract {
} => {
let clock = Clock::new(self.runtime.system_time(), &timeouts);
self.state.clock.set(clock);
let owners = [Owner::from(&players[0]), Owner::from(&players[1])];
self.state.public_keys.set(Some(players));
self.state.owners.set(Some(owners));
self.state.owners.set(Some(players));
self.state.board.set(Board::new(board_size));
}
Message::End { winner, loser } => {
Expand Down Expand Up @@ -143,7 +141,7 @@ impl HexContract {

async fn execute_start(
&mut self,
players: [PublicKey; 2],
players: [Owner; 2],
board_size: u16,
fee_budget: Amount,
timeouts: Option<Timeouts>,
Expand All @@ -157,10 +155,10 @@ impl HexContract {
let app_id = self.runtime.application_id();
let permissions = ApplicationPermissions::new_single(app_id.forget_abi());
let (message_id, chain_id) = self.runtime.open_chain(ownership, permissions, fee_budget);
for public_key in &players {
for owner in &players {
self.state
.game_chains
.get_mut_or_default(public_key)
.get_mut_or_default(owner)
.await
.unwrap()
.insert(GameChain {
Expand All @@ -183,8 +181,8 @@ impl HexContract {
let HexOutcome::Winner(player) = outcome else {
return outcome;
};
let winner = self.state.public_keys.get().unwrap()[player.index()];
let loser = self.state.public_keys.get().unwrap()[player.other().index()];
let winner = self.state.owners.get().unwrap()[player.index()];
let loser = self.state.owners.get().unwrap()[player.other().index()];
let chain_id = self.main_chain_id();
let message = Message::End { winner, loser };
self.runtime.send_message(chain_id, message);
Expand All @@ -202,14 +200,14 @@ pub enum Message {
/// Initializes a game. Sent from the main chain to a temporary chain.
Start {
/// The players.
players: [PublicKey; 2],
players: [Owner; 2],
/// The side length of the board. A typical size is 11.
board_size: u16,
/// Settings that determine how much time the players have to think about their turns.
timeouts: Timeouts,
},
/// Reports the outcome of a game. Sent from a closed chain to the main chain.
End { winner: PublicKey, loser: PublicKey },
End { winner: Owner, loser: Owner },
}

/// This implementation is only nonempty in the service.
Expand Down
4 changes: 2 additions & 2 deletions examples/hex-game/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::iter;

use async_graphql::{Enum, InputObject, Request, Response, SimpleObject};
use linera_sdk::{
base::{Amount, ContractAbi, PublicKey, ServiceAbi, TimeDelta, Timestamp},
base::{Amount, ContractAbi, Owner, ServiceAbi, TimeDelta, Timestamp},
graphql::GraphQLMutationRoot,
};
use serde::{Deserialize, Serialize};
Expand All @@ -23,7 +23,7 @@ pub enum Operation {
/// Start a game on a new temporary chain, with the given settings.
Start {
/// The public keys of player 1 and 2, respectively.
players: [PublicKey; 2],
players: [Owner; 2],
/// The side length of the board. A typical size is 11.
board_size: u16,
/// An amount transferred to the temporary chain to cover the fees.
Expand Down
6 changes: 2 additions & 4 deletions examples/hex-game/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use std::collections::BTreeSet;
use async_graphql::SimpleObject;
use hex_game::{Board, Clock, Timeouts};
use linera_sdk::{
base::{ChainId, MessageId, Owner, PublicKey},
base::{ChainId, MessageId, Owner},
views::{linera_views, MapView, RegisterView, RootView, ViewStorageContext},
};
use serde::{Deserialize, Serialize};
Expand All @@ -27,14 +27,12 @@ pub struct GameChain {
pub struct HexState {
/// The `Owner`s controlling players `One` and `Two`.
pub owners: RegisterView<Option<[Owner; 2]>>,
/// The players' public keys.
pub public_keys: RegisterView<Option<[PublicKey; 2]>>,
/// The current game state.
pub board: RegisterView<Board>,
/// The game clock.
pub clock: RegisterView<Clock>,
/// The timeouts.
pub timeouts: RegisterView<Timeouts>,
/// Temporary chains for individual games, by player.
pub game_chains: MapView<PublicKey, BTreeSet<GameChain>>,
pub game_chains: MapView<Owner, BTreeSet<GameChain>>,
}
4 changes: 2 additions & 2 deletions examples/hex-game/tests/hex_game.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ async fn hex_game() {
.add_block(|block| {
let operation = Operation::Start {
board_size: 2,
players: [key_pair1.public(), key_pair2.public()],
players: [key_pair1.public().into(), key_pair2.public().into()],
fee_budget: Amount::ZERO,
timeouts: None,
};
Expand Down Expand Up @@ -97,7 +97,7 @@ async fn hex_game_clock() {
.add_block(|block| {
let operation = Operation::Start {
board_size: 2,
players: [key_pair1.public(), key_pair2.public()],
players: [key_pair1.public().into(), key_pair2.public().into()],
fee_budget: Amount::ZERO,
timeouts: None,
};
Expand Down
4 changes: 1 addition & 3 deletions examples/matching-engine/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,6 @@ OWNER_2=a477cb966190661c0dfbe50602616a78a48d2bef6cb5288d49deb3e05585d579
OWNER_3=d2115775b5b3c5c1ed3c1516319a7e850c75d0786a74b39f5250cf9decc88124
CHAIN_1=e476187f6ddfeb9d588c7b45d3df334d5501d6499b3f9ad5595cae86cce16a65
CHAIN_2=69705f85ac4c9fef6c02b4d83426aaaf05154c645ec1c61665f8e450f0468bc0
PUB_KEY_1=fcf518d56455283ace2bbc11c71e684eb58af81bc98b96a18129e825ce24ea84
PUB_KEY_2=ca909dcf60df014c166be17eb4a9f6e2f9383314a57510206a54cd841ade455e
```

Publish and create two `fungible` applications whose IDs will be used as a
Expand Down Expand Up @@ -170,7 +168,7 @@ Engine to close the chain.
kill %% && sleep 1 # Kill the service so we can use CLI commands for chain 1.

linera --wait-for-outgoing-messages change-ownership \
--owner-public-keys $PUB_KEY_1 $PUB_KEY_2
--owners $OWNER_1 $OWNER_2

linera --wait-for-outgoing-messages change-application-permissions \
--execute-operations $MATCHING_ENGINE \
Expand Down
59 changes: 23 additions & 36 deletions linera-base/src/ownership.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@
//! Structures defining the set of owners and super owners, as well as the consensus
//! round types and timeouts for chains.
use std::{collections::BTreeMap, iter};
use std::{
collections::{BTreeMap, BTreeSet},
iter,
};

use custom_debug_derive::Debug;
use linera_witty::{WitLoad, WitStore, WitType};
use serde::{Deserialize, Serialize};
use thiserror::Error;

use crate::{
crypto::PublicKey,
data_types::{Round, TimeDelta},
doc_scalar,
identifiers::Owner,
Expand Down Expand Up @@ -50,11 +52,11 @@ impl Default for TimeoutConfig {
)]
pub struct ChainOwnership {
/// Super owners can propose fast blocks in the first round, and regular blocks in any round.
#[debug(skip_if = BTreeMap::is_empty)]
pub super_owners: BTreeMap<Owner, PublicKey>,
#[debug(skip_if = BTreeSet::is_empty)]
pub super_owners: BTreeSet<Owner>,
/// The regular owners, with their weights that determine how often they are round leader.
#[debug(skip_if = BTreeMap::is_empty)]
pub owners: BTreeMap<Owner, (PublicKey, u64)>,
pub owners: BTreeMap<Owner, u64>,
/// The number of initial rounds after 0 in which all owners are allowed to propose blocks.
pub multi_leader_rounds: u32,
/// The timeout configuration: how long fast, multi-leader and single-leader rounds last.
Expand All @@ -63,46 +65,42 @@ pub struct ChainOwnership {

impl ChainOwnership {
/// Creates a `ChainOwnership` with a single super owner.
pub fn single_super(public_key: PublicKey) -> Self {
pub fn single_super(owner: Owner) -> Self {
ChainOwnership {
super_owners: iter::once((Owner::from(public_key), public_key)).collect(),
super_owners: iter::once(owner).collect(),
owners: BTreeMap::new(),
multi_leader_rounds: 2,
timeout_config: TimeoutConfig::default(),
}
}

/// Creates a `ChainOwnership` with a single regular owner.
pub fn single(public_key: PublicKey) -> Self {
pub fn single(owner: Owner) -> Self {
ChainOwnership {
super_owners: BTreeMap::new(),
owners: iter::once((Owner::from(public_key), (public_key, 100))).collect(),
super_owners: BTreeSet::new(),
owners: iter::once((owner, 100)).collect(),
multi_leader_rounds: 2,
timeout_config: TimeoutConfig::default(),
}
}

/// Creates a `ChainOwnership` with the specified regular owners.
pub fn multiple(
keys_and_weights: impl IntoIterator<Item = (PublicKey, u64)>,
owners_and_weights: impl IntoIterator<Item = (Owner, u64)>,
multi_leader_rounds: u32,
timeout_config: TimeoutConfig,
) -> Self {
ChainOwnership {
super_owners: BTreeMap::new(),
owners: keys_and_weights
.into_iter()
.map(|(public_key, weight)| (Owner::from(public_key), (public_key, weight)))
.collect(),
super_owners: BTreeSet::new(),
owners: owners_and_weights.into_iter().collect(),
multi_leader_rounds,
timeout_config,
}
}

/// Adds a regular owner.
pub fn with_regular_owner(mut self, public_key: PublicKey, weight: u64) -> Self {
self.owners
.insert(Owner::from(public_key), (public_key, weight));
pub fn with_regular_owner(mut self, owner: Owner, weight: u64) -> Self {
self.owners.insert(owner, weight);
self
}

Expand All @@ -113,13 +111,9 @@ impl ChainOwnership {
|| self.timeout_config.fallback_duration == TimeDelta::ZERO
}

/// Returns the given owner's public key, if they are an owner or super owner.
pub fn verify_owner(&self, owner: &Owner) -> Option<PublicKey> {
if let Some(public_key) = self.super_owners.get(owner) {
Some(*public_key)
} else {
self.owners.get(owner).map(|(public_key, _)| *public_key)
}
/// Returns `true` if this is an owner or super owner.
pub fn verify_owner(&self, owner: &Owner) -> bool {
self.super_owners.contains(owner) || self.owners.contains_key(owner)
}

/// Returns the duration of the given round.
Expand Down Expand Up @@ -157,14 +151,7 @@ impl ChainOwnership {

/// Returns an iterator over all super owners, followed by all owners.
pub fn all_owners(&self) -> impl Iterator<Item = &Owner> {
self.super_owners.keys().chain(self.owners.keys())
}

/// Returns an iterator over all super owners' keys, followed by all owners'.
pub fn all_public_keys(&self) -> impl Iterator<Item = &PublicKey> {
self.super_owners
.values()
.chain(self.owners.values().map(|(public_key, _)| public_key))
self.super_owners.iter().chain(self.owners.keys())
}

/// Returns the round following the specified one, if any.
Expand Down Expand Up @@ -207,8 +194,8 @@ mod tests {
let owner = Owner::from(pub_key);

let ownership = ChainOwnership {
super_owners: BTreeMap::from_iter([(super_owner, super_pub_key)]),
owners: BTreeMap::from_iter([(owner, (pub_key, 100))]),
super_owners: BTreeSet::from_iter([super_owner]),
owners: BTreeMap::from_iter([(owner, 100)]),
multi_leader_rounds: 10,
timeout_config: TimeoutConfig {
fast_round_duration: Some(TimeDelta::from_secs(5)),
Expand Down
Loading

0 comments on commit 903b975

Please sign in to comment.