Skip to content

Commit

Permalink
[cli] Implement upgrade compatibility checks client side (#19562)
Browse files Browse the repository at this point in the history
## Description 

Introduce client side upgrade compatibility checking, allowing users to
check before TX submission if an upgrade will be successful. Improves
the output of errors by relying on the move-binary-format crates checks
to list the issues with the error to the user that are found.

## Test plan 


manually tested see comment below

---

## Release notes

- [ ] Protocol: 
- [ ] Nodes (Validators and Full nodes): 
- [ ] Indexer: 
- [ ] JSON-RPC: 
- [ ] GraphQL: 
- [x] CLI: 
User will see a different error when an upgrade error is thrown which
includes the details of each error.
- [ ] Rust SDK:
- [ ] REST API:
  • Loading branch information
jordanjennings-mysten authored Oct 7, 2024
1 parent 2c1b6e2 commit f23da66
Show file tree
Hide file tree
Showing 23 changed files with 929 additions and 13 deletions.
56 changes: 53 additions & 3 deletions crates/sui-types/src/digests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
use std::{env, fmt};

use crate::{error::SuiError, sui_serde::Readable};
use fastcrypto::encoding::{Base58, Encoding};
use fastcrypto::encoding::{Base58, Encoding, Hex};
use once_cell::sync::{Lazy, OnceCell};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -157,6 +157,9 @@ impl fmt::UpperHex for Digest {
)]
pub struct ChainIdentifier(CheckpointDigest);

pub const MAINNET_CHAIN_IDENTIFIER_BASE58: &str = "4btiuiMPvEENsttpZC7CZ53DruC3MAgfznDbASZ7DR6S";
pub const TESTNET_CHAIN_IDENTIFIER_BASE58: &str = "69WiPg3DAQiwdxfncX6wYQ2siKwAe6L9BZthQea3JNMD";

pub static MAINNET_CHAIN_IDENTIFIER: OnceCell<ChainIdentifier> = OnceCell::new();
pub static TESTNET_CHAIN_IDENTIFIER: OnceCell<ChainIdentifier> = OnceCell::new();

Expand All @@ -179,6 +182,24 @@ static SUI_PROTOCOL_CONFIG_CHAIN_OVERRIDE: Lazy<Option<Chain>> = Lazy::new(|| {
});

impl ChainIdentifier {
/// take a short 4 byte identifier and convert it into a ChainIdentifier
/// short ids come from the JSON RPC getChainIdentifier and are encoded in hex
pub fn from_chain_short_id(short_id: &String) -> Option<Self> {
if Hex::from_bytes(&Base58::decode(MAINNET_CHAIN_IDENTIFIER_BASE58).ok()?)
.encoded_with_format()
.starts_with(&format!("0x{}", short_id))
{
Some(get_mainnet_chain_identifier())
} else if Hex::from_bytes(&Base58::decode(TESTNET_CHAIN_IDENTIFIER_BASE58).ok()?)
.encoded_with_format()
.starts_with(&format!("0x{}", short_id))
{
Some(get_testnet_chain_identifier())
} else {
None
}
}

pub fn chain(&self) -> Chain {
let mainnet_id = get_mainnet_chain_identifier();
let testnet_id = get_testnet_chain_identifier();
Expand Down Expand Up @@ -206,7 +227,7 @@ impl ChainIdentifier {
pub fn get_mainnet_chain_identifier() -> ChainIdentifier {
let digest = MAINNET_CHAIN_IDENTIFIER.get_or_init(|| {
let digest = CheckpointDigest::new(
Base58::decode("4btiuiMPvEENsttpZC7CZ53DruC3MAgfznDbASZ7DR6S")
Base58::decode(MAINNET_CHAIN_IDENTIFIER_BASE58)
.expect("mainnet genesis checkpoint digest literal is invalid")
.try_into()
.expect("Mainnet genesis checkpoint digest literal has incorrect length"),
Expand All @@ -219,7 +240,7 @@ pub fn get_mainnet_chain_identifier() -> ChainIdentifier {
pub fn get_testnet_chain_identifier() -> ChainIdentifier {
let digest = TESTNET_CHAIN_IDENTIFIER.get_or_init(|| {
let digest = CheckpointDigest::new(
Base58::decode("69WiPg3DAQiwdxfncX6wYQ2siKwAe6L9BZthQea3JNMD")
Base58::decode(TESTNET_CHAIN_IDENTIFIER_BASE58)
.expect("testnet genesis checkpoint digest literal is invalid")
.try_into()
.expect("Testnet genesis checkpoint digest literal has incorrect length"),
Expand Down Expand Up @@ -1043,3 +1064,32 @@ impl fmt::Debug for ConsensusCommitDigest {
.finish()
}
}

mod test {
#[allow(unused_imports)]
use crate::digests::ChainIdentifier;
// check that the chain id returns mainnet
#[test]
fn test_chain_id_mainnet() {
let chain_id = ChainIdentifier::from_chain_short_id(&String::from("35834a8a"));
assert_eq!(
chain_id.unwrap().chain(),
sui_protocol_config::Chain::Mainnet
);
}

#[test]
fn test_chain_id_testnet() {
let chain_id = ChainIdentifier::from_chain_short_id(&String::from("4c78adac"));
assert_eq!(
chain_id.unwrap().chain(),
sui_protocol_config::Chain::Testnet
);
}

#[test]
fn test_chain_id_unknown() {
let chain_id = ChainIdentifier::from_chain_short_id(&String::from("unknown"));
assert_eq!(chain_id, None);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[package]
name = "upgrades"
edition = "2024.beta" # edition = "legacy" to use legacy (pre-2024) Move

[addresses]
upgrades = "0x0"
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

/// Module: UpgradeErrors

#[allow(unused_field)]
module upgrades::upgrades {
// struct missing
public struct StructToBeRemoved {
b: u64
}

// struct ability mismatch (add)
public struct StructAbilityMismatchAdd {}

// struct ability mismatch (remove)
public struct StructAbilityMismatchRemove has copy {}

// struct ability mismatch (change)
public struct StructAbilityMismatchChange has copy {}

// struct type param mismatch
public struct StructTypeParamMismatch<S, T> { a: S }

// struct field mismatch (add)
public struct StructFieldMismatchAdd {
a: u64,
b: u64
}

// struct field mismatch (remove)
public struct StructFieldMismatchRemove {
a: u64,
b: u64
}

// struct field mismatch (change)
public struct StructFieldMismatchChange {
a: u64,
b: u64
}

// enum missing
public enum EnumToBeRemoved {
A,
B
}

// enum ability mismatch (add)
public enum EnumAbilityMismatchAdd {
A,
}

// enum ability mismatch (remove)
public enum EnumAbilityMismatchRemove has copy {
A,
}

// enum ability mismatch (change)
public enum EnumAbilityMismatchChange has copy {
A,
}

// enum new variant
public enum EnumNewVariant {
A,
B,
C
}

// enum variant missing
public enum EnumVariantMissing {
A,
B,
}

// function missing public
public fun function_to_have_public_removed() {}

// function missing friend
public(package) fun function_to_have_friend_removed() {}

// function missing entry


// function signature mismatch (add)
public fun function_add_arg() {}

// function signature mismatch (remove)
public fun function_remove_arg(a: u64) {}

// function signature mismatch (change)
public fun function_change_arg(a: u64) {}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[package]
name = "upgrades"
edition = "2024.beta" # edition = "legacy" to use legacy (pre-2024) Move

[addresses]
upgrades = "0x0"
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0
/// Module: UpgradeErrors

#[allow(unused_field)]
module upgrades::upgrades {
// struct missing
// public struct StructToBeRemoved {}

// struct ability mismatch (add)
public struct StructAbilityMismatchAdd has copy {} // added the copy ability where none existed

// struct field mismatch (remove)
public struct StructAbilityMismatchRemove {} // removed the copy ability

// struct field mismatch (change)
public struct StructAbilityMismatchChange has drop {} // changed from drop to copy

// struct type param mismatch
public struct StructTypeParamMismatch<T> { a: T } // changed S to T

// struct field mismatch (add)
public struct StructFieldMismatchAdd {
a: u64,
b: u64,
c: u64, // added
}

// struct field mismatch (remove)
public struct StructFieldMismatchRemove {
a: u64,
// removed b: u64
}

// struct field mismatch (change)
public struct StructFieldMismatchChange {
a: u64,
b: u8 // changed b from u64 to u8
}

// enum missing
// public enum EnumToBeRemoved {}

// enum ability mismatch (add)
public enum EnumAbilityMismatchAdd has copy {
A,
}

// enum ability mismatch (remove)
public enum EnumAbilityMismatchRemove {
A,
}

// enum ability mismatch (change)
public enum EnumAbilityMismatchChange has drop {
A,
}

// enum new variant
public enum EnumNewVariant {
A,
B,
C,
D // new variant
}

// enum variant missing
public enum EnumVariantMissing {
A,
// remove B,
}

// function missing public
fun function_to_have_public_removed() {}

// function missing friend
fun function_to_have_friend_removed() {}

// function missing entry

// function signature mismatch (add)
public fun function_add_arg(a: u64) {}

// function signature mismatch (remove)
public fun function_remove_arg() {}

// function signature mismatch (change)
public fun function_change_arg(a: u8) {} // now has u8 instead of u64
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[package]
name = "upgrades"
edition = "2024.beta" # edition = "legacy" to use legacy (pre-2024) Move

[addresses]
upgrades = "0x0"
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

/// Module: UpgradeErrors

#[allow(unused_field)]
module upgrades::upgrades {
entry fun entry_to_be_removed() {}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[package]
name = "upgrades"
edition = "2024.beta" # edition = "legacy" to use legacy (pre-2024) Move

[addresses]
upgrades = "0x0"
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

/// Module: UpgradeErrors

#[allow(unused_field)]
module upgrades::upgrades {
fun entry_to_be_removed() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[package]
name = "upgrades"
edition = "2024.beta" # edition = "legacy" to use legacy (pre-2024) Move

[addresses]
upgrades = "0x0"
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

/// Module: UpgradeErrors

#[allow(unused_field)]
module upgrades::upgrades {
fun call_friend() {
upgrades::upgrades_friend::friend_to_be_dropped();
}
}

module upgrades::upgrades_friend {
public(package) fun friend_to_be_dropped() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[package]
name = "upgrades"
edition = "2024.beta" # edition = "legacy" to use legacy (pre-2024) Move

[addresses]
upgrades = "0x0"
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

/// Module: UpgradeErrors

#[allow(unused_field)]
module upgrades::upgrades {
fun call_friend() {}
}


module upgrades::upgrades_friend {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[package]
name = "upgrades"
edition = "2024.beta" # edition = "legacy" to use legacy (pre-2024) Move

[addresses]
upgrades = "0x0"
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

/// Module: UpgradeErrors

#[allow(unused_field)]
module upgrades::upgrades {
// struct missing
public struct StructToBeRemoved {
b: u64
}
}

Loading

0 comments on commit f23da66

Please sign in to comment.