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

multi-sig for governance commands #538

Merged
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
multi-sig for governance commands
jankun4 committed Mar 19, 2025
commit 4d248aefa0f8d1b71154fedfdf37adf0efa3483f
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@ This changelog is based on [Keep A Changelog](https://keepachangelog.com/en/1.1.

## Changed

* `governance update` command now accepts multiple governance authority key hashes, not just one. It also takes `new-governance-threshold` parameter, which is the number of signatures required to perform governance action.
* `governance init` and `governance update` will set Multisig policy implemented with ALeastN Native Script, instead of custom policy implemented as Plutus Script in partner-chains-smart-contracts. This policy doesn't require to set `required_signers` field in the transaction making it more user friendly.

## Removed
6 changes: 4 additions & 2 deletions docs/user-guides/governance/governance.md
Original file line number Diff line number Diff line change
@@ -64,13 +64,15 @@ In version v1.5 command to initialize governance is available in the Partner Cha
--genesis-utxo <GENESIS_UTXO> \
--ogmios-url <OGMIOS_URL> \
--payment-key-file <PAYMENT_KEY_FILE> \
--new-governance-authority <NEW_GOVERNANCE_AUTHORITY>
--new-governance-authority <NEW_GOVERNANCE_AUTHORITY> \
--new-governance-threshold <NEW_GOVERNANCE_THRESHOLD>
```

* `<GENESIS_UTXO>`: The genesis UTXO of the Partner Chain. Same as the one used for `governance init`.
* `<OGMIOS_URL>`: The URL of the Ogmios service connected to the Cardano node, it is optional and defaults to `ws://localhost:1337`.
* `<PAYMENT_KEY_FILE>`: Cardano Shelley Payment Signing Key file (normal or extended) of the current governance authority (ie. hash of its public key should equal current governance authority hey hash).
* `<NEW_GOVERNANCE_AUTHORITY>`: Hex encoded blake2b-224 hash of public key related to private key that will be required to sign governance operations following this operation.
* `<NEW_GOVERNANCE_AUTHORITY>`: List of hex encoded blake2b-224 hashes of public keys related to private keys that will be required to sign governance operations following this operation. Multiple keys can be provided, separated by spaces.
* `<NEW_GOVERNANCE_THRESHOLD>`: Number of keys required to sign a transaction.

In version v1.4 this functionality is available in the smart contracts CLI application `pc-contracts-cli update-governance`.

1 change: 1 addition & 0 deletions toolkit/cli/smart-contracts-commands/Cargo.toml
Original file line number Diff line number Diff line change
@@ -14,6 +14,7 @@ ogmios-client = { workspace = true }
tokio = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
anyhow = { workspace = true }

[dev-dependencies]
hex-literal = { workspace = true }
22 changes: 17 additions & 5 deletions toolkit/cli/smart-contracts-commands/src/governance.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::PaymentFilePath;
use anyhow::anyhow;
use partner_chains_cardano_offchain::{
await_tx::FixedDelayRetries, init_governance::run_init_governance,
update_governance::run_update_governance,
@@ -27,7 +28,7 @@ impl GovernanceCmd {
pub struct InitGovernanceCmd {
#[clap(flatten)]
common_arguments: crate::CommonArguments,
/// Governance authority hash to be set.
/// Governance authority to be set
#[arg(long, short = 'g')]
governance_authority: MainchainKeyHash,
#[clap(flatten)]
@@ -58,9 +59,12 @@ impl InitGovernanceCmd {
pub struct UpdateGovernanceCmd {
#[clap(flatten)]
common_arguments: crate::CommonArguments,
/// Governance authority hash to be set.
#[arg(long, short = 'g')]
new_governance_authority: MainchainKeyHash,
/// New governance authorities keys hashes, hex encoded, space delimited, order does not matter
#[arg(short = 'g', long, num_args = 1.., value_delimiter = ' ')]
new_governance_authority: Vec<MainchainKeyHash>,
/// Governance threshold to be set
#[arg(long)]
new_governance_threshold: u8,
#[clap(flatten)]
payment_key_file: PaymentFilePath,
/// Genesis UTXO of the chain
@@ -70,11 +74,19 @@ pub struct UpdateGovernanceCmd {

impl UpdateGovernanceCmd {
pub async fn execute(self) -> crate::CmdResult<()> {
if self.new_governance_threshold > self.new_governance_authority.len() as u8 {
return Err(anyhow!(
"New governance threshold is greater than the number of governance authorities"
)
.into());
}

let payment_key = self.payment_key_file.read_key()?;
let client = self.common_arguments.get_ogmios_client().await?;

run_update_governance(
self.new_governance_authority,
&self.new_governance_authority,
self.new_governance_threshold,
&payment_key,
self.genesis_utxo,
&client,
67 changes: 46 additions & 21 deletions toolkit/offchain/src/update_governance/mod.rs
Original file line number Diff line number Diff line change
@@ -15,16 +15,21 @@ use crate::{
init_governance::transaction::version_oracle_datum_output,
plutus_script::PlutusScript,
};
use anyhow::anyhow;
use cardano_serialization_lib::{
Language, PlutusData, Transaction, TransactionBuilder, TxInputsBuilder,
};
use ogmios_client::{
query_ledger_state::{QueryLedgerState, QueryUtxoByUtxoId},
query_network::QueryNetwork,
transactions::Transactions,
types::OgmiosTx,
};
use sidechain_domain::{MainchainKeyHash, McTxHash, UtxoId, UtxoIndex};
use serde_json::json;
use sidechain_domain::{
MainchainKeyHash, McSmartContractResult,
McSmartContractResult::{TxCBOR, TxHash},
McTxHash, UtxoId, UtxoIndex,
};

#[cfg(test)]
mod test;
@@ -35,12 +40,13 @@ pub async fn run_update_governance<
T: QueryLedgerState + Transactions + QueryNetwork + QueryUtxoByUtxoId,
A: AwaitTx,
>(
new_governance_authority: MainchainKeyHash,
new_governance_authority: &Vec<MainchainKeyHash>,
new_governance_threshold: u8,
payment_key: &CardanoPaymentSigningKey,
genesis_utxo_id: UtxoId,
client: &T,
await_tx: A,
) -> anyhow::Result<OgmiosTx> {
) -> anyhow::Result<McSmartContractResult> {
let ctx = TransactionContext::for_payment_key(payment_key, client).await?;
let governance_data = GovernanceData::get(genesis_utxo_id, client).await?;

@@ -51,6 +57,7 @@ pub async fn run_update_governance<
raw_scripts::VERSION_ORACLE_POLICY,
genesis_utxo_id,
new_governance_authority,
new_governance_threshold,
&governance_data,
costs,
&ctx,
@@ -60,33 +67,51 @@ pub async fn run_update_governance<
)
.await?;

let signed_tx = ctx.sign(&tx);

let response = client.submit_transaction(&signed_tx.to_bytes()).await?;
log::info!("Submitted transaction: {}", hex::encode(response.transaction.id));

await_tx
.await_tx_output(
client,
UtxoId { tx_hash: McTxHash(response.transaction.id), index: UtxoIndex(0) },
)
.await?;

Ok(response.transaction)
if governance_data.policy.is_single_key_policy_for(&ctx.payment_key_hash()) {
let signed_tx = ctx.sign(&tx).to_bytes();
let res = client.submit_transaction(&signed_tx).await.map_err(|e| {
anyhow!(
"Submit governance update transaction request failed: {}, bytes: {}",
e,
hex::encode(signed_tx)
)
})?;
let tx_id = McTxHash(res.transaction.id);
log::info!("Update Governance transaction submitted: {}", hex::encode(tx_id.0));
await_tx
.await_tx_output(
client,
UtxoId { tx_hash: McTxHash(res.transaction.id), index: UtxoIndex(0) },
)
.await?;
Ok(TxHash(tx_id))
} else {
let tx_envelope = json!(
{ "type": "Unwitnessed Tx ConwayEra",
"description": "",
"cborHex": hex::encode(tx.to_bytes())
}
);
log::info!("Transaction envelope: {}", tx_envelope);
Ok(TxCBOR(tx.to_bytes()))
}
}

fn update_governance_tx(
version_oracle_validator: &[u8],
version_oracle_policy: &[u8],
genesis_utxo: UtxoId,
new_governance_authority: MainchainKeyHash,
new_governance_authority: &Vec<MainchainKeyHash>,
new_governance_threshold: u8,
governance_data: &GovernanceData,
costs: Costs,
ctx: &TransactionContext,
) -> anyhow::Result<Transaction> {
let multi_sig_policy =
SimpleAtLeastN { threshold: 1, key_hashes: vec![new_governance_authority.0] }
.to_csl_native_script();
let multi_sig_policy = SimpleAtLeastN {
threshold: new_governance_threshold.into(),
key_hashes: new_governance_authority.iter().map(|key| key.0).collect(),
}
.to_csl_native_script();
let version_oracle_validator =
PlutusScript::from_wrapped_cbor(version_oracle_validator, Language::new_plutus_v2())?
.apply_data(genesis_utxo)?;
3 changes: 2 additions & 1 deletion toolkit/offchain/src/update_governance/test.rs
Original file line number Diff line number Diff line change
@@ -125,7 +125,8 @@ fn test_update_governance_tx() -> Transaction {
test_values::VERSION_ORACLE_VALIDATOR,
test_values::VERSION_ORACLE_POLICY,
genesis_utxo().to_domain(),
new_governance_authority(),
&vec![new_governance_authority()],
1,
&governance_data(),
test_costs(),
&tx_context(),
3 changes: 2 additions & 1 deletion toolkit/offchain/tests/integration_tests.rs
Original file line number Diff line number Diff line change
@@ -296,7 +296,8 @@ async fn run_update_goveranance<
genesis_utxo: UtxoId,
) {
let _ = update_governance::run_update_governance(
EVE_PUBLIC_KEY_HASH,
&vec![EVE_PUBLIC_KEY_HASH],
1,
&governance_authority_payment_key(),
genesis_utxo,
client,