Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Versioning of on-chain Identity #1435

Merged
merged 6 commits into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
wulfraem marked this conversation as resolved.
Show resolved Hide resolved
/// 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())
}

/// 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> {
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(())
}
}
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