Skip to content

Commit

Permalink
Versioning of on-chain Identity (#1435)
Browse files Browse the repository at this point in the history
* add package version, identity version and upgrade proposal

* rust implementation of upgrade identity proposal

* commit missing files

* review comments

* fix merge issues & emit events when dealing with upgrade proposals
  • Loading branch information
UMR1352 authored Nov 27, 2024
1 parent 40cb627 commit a970f25
Show file tree
Hide file tree
Showing 9 changed files with 253 additions and 10 deletions.
2 changes: 1 addition & 1 deletion identity_iota_core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ iota-sdk = { git = "https://github.com/iotaledger/iota.git", package = "iota-sdk
itertools = { version = "0.13.0", optional = true }
move-core-types = { git = "https://github.com/iotaledger/iota.git", package = "move-core-types", rev = "39c83ddcf07894cdee2abd146381d8704205e6e9", optional = true }
rand = { version = "0.8.5", optional = true }
secret-storage = { git = "https://github.com/iotaledger/secret-storage.git", branch = "main", optional = true }
secret-storage = { git = "https://github.com/iotaledger/secret-storage.git", tag = "v0.1.0", optional = true }
serde-aux = { version = "4.5.0", optional = true }
shared-crypto = { git = "https://github.com/iotaledger/iota.git", package = "shared-crypto", rev = "39c83ddcf07894cdee2abd146381d8704205e6e9", optional = true }
tokio = { version = "1.29.0", default-features = false, optional = true, features = ["macros", "sync", "rt", "process"] }
Expand Down
54 changes: 54 additions & 0 deletions identity_iota_core/packages/iota_identity/sources/identity.move
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ module iota_identity::identity {
transfer_proposal::{Self, Send},
borrow_proposal::{Self, Borrow},
did_deactivation_proposal::{Self, DidDeactivation},
upgrade_proposal::{Self, Upgrade},
};

const ENotADidDocument: u64 = 0;
Expand All @@ -24,6 +25,10 @@ module iota_identity::identity {
const EInvalidThreshold: u64 = 2;
/// The controller list must contain at least 1 element.
const EInvalidControllersList: u64 = 3;
/// There's no upgrade available for this identity.
const ENoUpgrade: u64 = 4;

const PACKAGE_VERSION: u64 = 0;

// ===== Events ======
/// Event emitted when an `identity`'s `Proposal` with `ID` `proposal` is created or executed by `controller`.
Expand Down Expand Up @@ -53,6 +58,8 @@ module iota_identity::identity {
created: u64,
/// Timestamp of this Identity's last update.
updated: u64,
/// Package version used by this object.
version: u64,
}

/// Creates a new DID Document with a single controller.
Expand Down Expand Up @@ -94,6 +101,7 @@ module iota_identity::identity {
did_doc: multicontroller::new_with_controller(doc, controller, can_delegate, ctx),
created: now,
updated: now,
version: PACKAGE_VERSION,
}
}

Expand All @@ -118,6 +126,7 @@ module iota_identity::identity {
did_doc: multicontroller::new_with_controllers(doc, controllers, controllers_that_can_delegate, threshold, ctx),
created: now,
updated: now,
version: PACKAGE_VERSION,
}
}

Expand Down Expand Up @@ -205,6 +214,51 @@ module iota_identity::identity {
emit_proposal_event(self.id().to_inner(), cap.id().to_inner(), proposal_id, true);
}

/// Proposes to upgrade this `Identity` to this package's version.
public fun propose_upgrade(
self: &mut Identity,
cap: &ControllerCap,
expiration: Option<u64>,
ctx: &mut TxContext,
): Option<ID> {
assert!(self.version < PACKAGE_VERSION, ENoUpgrade);
let proposal_id = self.did_doc.create_proposal(
cap,
upgrade_proposal::new(),
expiration,
ctx
);
let is_approved = self
.did_doc
.is_proposal_approved<_, Upgrade>(proposal_id);
if (is_approved) {
self.execute_upgrade(cap, proposal_id, ctx);
option::none()
} else {
emit_proposal_event(self.id().to_inner(), cap.id().to_inner(), proposal_id, false);
option::some(proposal_id)
}
}

/// Consumes a `Proposal<Upgrade>` that migrates `Identity` to this
/// package's version.
public fun execute_upgrade(
self: &mut Identity,
cap: &ControllerCap,
proposal_id: ID,
ctx: &mut TxContext,
) {
self.execute_proposal<Upgrade>(cap, proposal_id, ctx).unwrap();
self.migrate();
emit_proposal_event(self.id().to_inner(), cap.id().to_inner(), proposal_id, true);
}

/// Migrates this `Identity` to this package's version.
fun migrate(self: &mut Identity) {
// ADD migration logic when needed!
self.version = PACKAGE_VERSION;
}

/// Proposes an update to the DID Document contained in this `Identity`.
/// This function can update the DID Document right away if `cap` has
/// enough voting power.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

module iota_identity::upgrade_proposal {
/// Proposal's action used to upgrade an `Identity` to the package's current version.
public struct Upgrade has store, copy, drop {}

/// Creates a new `Upgrade` action.
public fun new(): Upgrade {
Upgrade {}
}
}
2 changes: 1 addition & 1 deletion identity_iota_core/src/document/iota_document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,7 @@ pub mod client_document {
None,
))
})?;
let (_, multi_controller, created, updated) = match unpacked {
let (_, multi_controller, created, updated, _) = match unpacked {
Some(data) => data,
None => {
return Err(Error::InvalidDoc(identity_document::Error::InvalidDocument(
Expand Down
24 changes: 16 additions & 8 deletions identity_iota_core/src/rebased/migration/identity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use std::collections::HashSet;
use std::ops::Deref;
use std::str::FromStr;

use crate::rebased::proposals::Upgrade;
use crate::rebased::sui::types::Number;
use crate::IotaDID;
use crate::IotaDocument;
Expand Down Expand Up @@ -99,6 +100,7 @@ pub struct OnChainIdentity {
id: UID,
multi_controller: Multicontroller<Vec<u8>>,
did_doc: IotaDocument,
version: u64,
}

impl Deref for OnChainIdentity {
Expand Down Expand Up @@ -169,6 +171,11 @@ impl OnChainIdentity {
ProposalBuilder::new(self, DeactivateDid::new())
}

/// Upgrades this [`OnChainIdentity`]'s version to match the package's.
pub fn upgrade_version(&mut self) -> ProposalBuilder<'_, Upgrade> {
ProposalBuilder::new(self, Upgrade::default())

Check failure on line 176 in identity_iota_core/src/rebased/migration/identity.rs

View workflow job for this annotation

GitHub Actions / clippy

use of `default` to create a unit struct

error: use of `default` to create a unit struct --> identity_iota_core/src/rebased/migration/identity.rs:176:39 | 176 | ProposalBuilder::new(self, Upgrade::default()) | ^^^^^^^^^^^ help: remove this call to `default` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#default_constructed_unit_structs = note: `-D clippy::default-constructed-unit-structs` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::default_constructed_unit_structs)]`
}

/// Sends assets owned by this [`OnChainIdentity`] to other addresses.
pub fn send_assets(&mut self) -> ProposalBuilder<'_, SendAction> {
ProposalBuilder::new(self, SendAction::default())
Expand Down Expand Up @@ -339,16 +346,13 @@ pub async fn get_identity(

// no issues with call but
let Some(data) = response.data else {
// call was successful but not data for alias id
// call was successful but no data for alias id
return Ok(None);
};

let did = IotaDID::from_alias_id(&object_id.to_string(), client.network());
let (id, multi_controller, created, updated) = match unpack_identity_data(&did, &data)? {
Some(data) => data,
None => {
return Ok(None);
}
let Some((id, multi_controller, created, updated, version)) = unpack_identity_data(&did, &data)? else {
return Ok(None);
};

let did_doc =
Expand All @@ -359,6 +363,7 @@ pub async fn get_identity(
id,
multi_controller,
did_doc,
version,
}))
}

Expand All @@ -376,7 +381,7 @@ fn is_identity(value: &IotaParsedMoveObject) -> bool {
pub(crate) fn unpack_identity_data(
did: &IotaDID,
data: &IotaObjectData,
) -> Result<Option<(UID, Multicontroller<Vec<u8>>, Timestamp, Timestamp)>, Error> {
) -> Result<Option<(UID, Multicontroller<Vec<u8>>, Timestamp, Timestamp, u64)>, Error> {

Check failure on line 384 in identity_iota_core/src/rebased/migration/identity.rs

View workflow job for this annotation

GitHub Actions / clippy

very complex type used. Consider factoring parts into `type` definitions

error: very complex type used. Consider factoring parts into `type` definitions --> identity_iota_core/src/rebased/migration/identity.rs:384:6 | 384 | ) -> Result<Option<(UID, Multicontroller<Vec<u8>>, Timestamp, Timestamp, u64)>, Error> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#type_complexity = note: `-D clippy::type-complexity` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::type_complexity)]`
let content = data
.clone()
.content
Expand All @@ -396,13 +401,15 @@ pub(crate) fn unpack_identity_data(
did_doc: Multicontroller<Vec<u8>>,
created: Number<u64>,
updated: Number<u64>,
version: Number<u64>,
}

let TempOnChainIdentity {
id,
did_doc: multi_controller,
created,
updated,
version
} = serde_json::from_value::<TempOnChainIdentity>(value.fields.to_json_value())
.map_err(|err| Error::ObjectLookup(format!("could not parse identity document with DID {did}; {err}")))?;

Expand All @@ -417,8 +424,9 @@ pub(crate) fn unpack_identity_data(
// `Timestamp` requires a timestamp expressed in seconds.
Timestamp::from_unix(timestamp_ms as i64 / 1000).expect("On-chain clock produces valid timestamps")
};
let version = version.try_into().expect("Move string-encoded u64 are valid u64");

Ok(Some((id, multi_controller, created, updated)))
Ok(Some((id, multi_controller, created, updated, version)))
}

/// Builder-style struct to create a new [`OnChainIdentity`].
Expand Down
2 changes: 2 additions & 0 deletions identity_iota_core/src/rebased/proposals/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ mod config_change;
mod deactivate_did;
mod send;
mod update_did_doc;
mod upgrade;

use std::marker::PhantomData;
use std::ops::Deref;
Expand All @@ -19,6 +20,7 @@ use crate::rebased::transaction::ProtoTransaction;
use async_trait::async_trait;
pub use borrow::*;
pub use config_change::*;
pub use upgrade::*;
pub use deactivate_did::*;
use iota_sdk::rpc_types::IotaExecutionStatus;
use iota_sdk::rpc_types::IotaObjectData;
Expand Down
109 changes: 109 additions & 0 deletions identity_iota_core/src/rebased/proposals/upgrade.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Copyright 2020-2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use std::marker::PhantomData;

use crate::rebased::client::IdentityClient;
use crate::rebased::client::IotaKeySignature;
use crate::rebased::sui::move_calls;
use async_trait::async_trait;
use iota_sdk::rpc_types::IotaTransactionBlockResponse;
use iota_sdk::types::base_types::ObjectID;
use iota_sdk::types::TypeTag;
use secret_storage::Signer;
use serde::Deserialize;
use serde::Serialize;

use crate::rebased::migration::OnChainIdentity;
use crate::rebased::migration::Proposal;
use crate::rebased::utils::MoveType;
use crate::rebased::Error;

use super::CreateProposalTx;
use super::ExecuteProposalTx;
use super::ProposalT;

/// Action for upgrading the version of an on-chain identity to the package's version.
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
pub struct Upgrade;

impl Upgrade {
/// Creates a new [`Upgrade`] action.
pub const fn new() -> Self {
Self
}
}

impl MoveType for Upgrade {
fn move_type(package: ObjectID) -> TypeTag {
format!("{package}::upgrade_proposal::Upgrade")
.parse()
.expect("valid utf8")
}
}

#[async_trait]
impl ProposalT for Proposal<Upgrade> {
type Action = Upgrade;
type Output = ();

async fn create<'i, S>(
_action: Self::Action,
expiration: Option<u64>,
identity: &'i mut OnChainIdentity,
client: &IdentityClient<S>,
) -> Result<CreateProposalTx<'i, Self::Action>, Error>
where
S: Signer<IotaKeySignature> + Sync,
{
let identity_ref = client
.get_object_ref_by_id(identity.id())
.await?
.expect("identity exists on-chain");
let controller_cap_ref = identity.get_controller_cap(client).await?;
let sender_vp = identity
.controller_voting_power(controller_cap_ref.0)
.expect("controller exists");
let chained_execution = sender_vp >= identity.threshold();
let tx =
move_calls::identity::propose_upgrade(identity_ref, controller_cap_ref, expiration, client.package_id())
.map_err(|e| Error::TransactionBuildingFailed(e.to_string()))?;

Ok(CreateProposalTx {
identity,
tx,
chained_execution,
_action: PhantomData,
})
}

async fn into_tx<'i, S>(
self,
identity: &'i mut OnChainIdentity,
client: &IdentityClient<S>,
) -> Result<ExecuteProposalTx<'i, Self::Action>, Error>
where
S: Signer<IotaKeySignature> + Sync,
{
let proposal_id = self.id();
let identity_ref = client
.get_object_ref_by_id(identity.id())
.await?
.expect("identity exists on-chain");
let controller_cap_ref = identity.get_controller_cap(client).await?;

let tx =
move_calls::identity::execute_upgrade(identity_ref, controller_cap_ref, proposal_id, client.package_id())
.map_err(|e| Error::TransactionBuildingFailed(e.to_string()))?;

Ok(ExecuteProposalTx {
identity,
tx,
_action: PhantomData,
})
}

fn parse_tx_effects(_tx_response: &IotaTransactionBlockResponse) -> Result<Self::Output, Error> {
Ok(())
}
}
2 changes: 2 additions & 0 deletions identity_iota_core/src/rebased/sui/move_calls/identity/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ mod deactivate;
pub(crate) mod proposal;
mod send_asset;
mod update;
mod upgrade;

pub(crate) use borrow_asset::*;
pub(crate) use config::*;
pub(crate) use create::*;
pub(crate) use deactivate::*;
pub(crate) use send_asset::*;
pub(crate) use update::*;
pub(crate) use upgrade::*;
Loading

0 comments on commit a970f25

Please sign in to comment.