Skip to content

Commit 14515f1

Browse files
committed
multi-sig for governance commands
1 parent 91ff5bf commit 14515f1

File tree

9 files changed

+113
-33
lines changed

9 files changed

+113
-33
lines changed

changelog.md

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ This changelog is based on [Keep A Changelog](https://keepachangelog.com/en/1.1.
66

77
## Changed
88

9+
* Governance update command now accepts multiple old_governance_authority key hashes, multiple key hashes in the new_governance_authority list, and new_governance_threshold - the minimal number of keys required to sign a transaction.
10+
911
## Removed
1012

1113
## Fixed

docs/user-guides/governance/governance.md

+6-2
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,17 @@ In version v1.5 command to initialize governance is available in the Partner Cha
6464
--genesis-utxo <GENESIS_UTXO> \
6565
--ogmios-url <OGMIOS_URL> \
6666
--payment-key-file <PAYMENT_KEY_FILE> \
67-
--new-governance-authority <NEW_GOVERNANCE_AUTHORITY>
67+
--old-governance-authority <OLD_GOVERNANCE_AUTHORITY> \
68+
--new-governance-authority <NEW_GOVERNANCE_AUTHORITY> \
69+
--new-governance-threshold <NEW_GOVERNANCE_THRESHOLD>
6870
```
6971

7072
* `<GENESIS_UTXO>`: The genesis UTXO of the Partner Chain. Same as the one used for `governance init`.
7173
* `<OGMIOS_URL>`: The URL of the Ogmios service connected to the Cardano node, it is optional and defaults to `ws://localhost:1337`.
7274
* `<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).
73-
* `<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.
75+
* `<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.
76+
* `<NEW_GOVERNANCE_THRESHOLD>`: Number of keys required to sign a transaction.
77+
* `<OLD_GOVERNANCE_AUTHORITY>`: List of hex encoded blake2b-224 hashes of public keys related to private keys that will be required to sign governance operations before and includingthis operation. Multiple keys can be provided, separated by spaces.
7478

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

toolkit/cli/smart-contracts-commands/src/governance.rs

+13-5
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ pub struct InitGovernanceCmd {
2828
#[clap(flatten)]
2929
common_arguments: crate::CommonArguments,
3030
/// Governance authority hash to be set.
31-
#[arg(long, short = 'g')]
31+
#[arg(short, long, num_args = 1.., value_delimiter = ' ')]
3232
governance_authority: MainchainKeyHash,
3333
#[clap(flatten)]
3434
payment_key_file: PaymentFilePath,
@@ -58,9 +58,15 @@ impl InitGovernanceCmd {
5858
pub struct UpdateGovernanceCmd {
5959
#[clap(flatten)]
6060
common_arguments: crate::CommonArguments,
61-
/// Governance authority hash to be set.
62-
#[arg(long, short = 'g')]
63-
new_governance_authority: MainchainKeyHash,
61+
/// Old governance authority hash
62+
#[arg(short = 'o', long, num_args = 1.., value_delimiter = ' ')]
63+
old_governance_authority: Vec<MainchainKeyHash>,
64+
/// Governance authority hash to be set
65+
#[arg(short = 'g', long, num_args = 1.., value_delimiter = ' ')]
66+
new_governance_authority: Vec<MainchainKeyHash>,
67+
/// Governance threshold to be set
68+
#[arg(long)]
69+
new_governance_threshold: u8,
6470
#[clap(flatten)]
6571
payment_key_file: PaymentFilePath,
6672
/// Genesis UTXO of the chain
@@ -74,7 +80,9 @@ impl UpdateGovernanceCmd {
7480
let client = self.common_arguments.get_ogmios_client().await?;
7581

7682
run_update_governance(
77-
self.new_governance_authority,
83+
&self.old_governance_authority,
84+
&self.new_governance_authority,
85+
self.new_governance_threshold,
7886
&payment_key,
7987
self.genesis_utxo,
8088
&client,

toolkit/offchain/src/init_governance/transaction.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ pub(crate) fn init_governance_transaction(
1616
) -> anyhow::Result<Transaction> {
1717
let multi_sig_policy =
1818
PlutusScript::from_wrapped_cbor(raw_scripts::MULTI_SIG_POLICY, Language::new_plutus_v2())?
19-
.apply_uplc_data(multisig_governance_policy_configuration(governance_authority))?;
19+
.apply_uplc_data(multisig_governance_policy_configuration(
20+
&vec![governance_authority],
21+
1,
22+
))?;
2023
let version_oracle = version_oracle(genesis_utxo.to_domain(), ctx.network)?;
2124
let config = crate::csl::get_builder_config(ctx)?;
2225
let mut tx_builder = TransactionBuilder::new(&config);

toolkit/offchain/src/scripts_data.rs

+9-5
Original file line numberDiff line numberDiff line change
@@ -242,13 +242,17 @@ pub(crate) fn reserve_scripts(
242242
// Returns the simplest MultiSig policy configuration plutus data:
243243
// there is one required authority and it is the governance authority from sidechain params.
244244
pub(crate) fn multisig_governance_policy_configuration(
245-
governance_authority: MainchainKeyHash,
245+
governance_authority: &Vec<MainchainKeyHash>,
246+
governance_threshold: u8,
246247
) -> PlutusData {
247248
PlutusData::Array(vec![
248-
PlutusData::Array(vec![uplc::PlutusData::BoundedBytes(
249-
governance_authority.0.to_vec().into(),
250-
)]),
251-
PlutusData::BigInt(uplc::BigInt::Int(1.into())),
249+
PlutusData::Array(
250+
governance_authority
251+
.iter()
252+
.map(|hash| PlutusData::BoundedBytes(hash.0.to_vec().into()))
253+
.collect(),
254+
),
255+
PlutusData::BigInt(uplc::BigInt::Int(<u8 as Into<i64>>::into(governance_threshold).into())),
252256
])
253257
}
254258

toolkit/offchain/src/update_governance/mod.rs

+54-18
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,21 @@ use crate::{
1616
plutus_script::PlutusScript,
1717
scripts_data::multisig_governance_policy_configuration,
1818
};
19+
use anyhow::anyhow;
1920
use cardano_serialization_lib::{
2021
Language, PlutusData, Transaction, TransactionBuilder, TxInputsBuilder,
2122
};
2223
use ogmios_client::{
2324
query_ledger_state::{QueryLedgerState, QueryUtxoByUtxoId},
2425
query_network::QueryNetwork,
2526
transactions::Transactions,
26-
types::OgmiosTx,
2727
};
28-
use sidechain_domain::{MainchainKeyHash, McTxHash, UtxoId, UtxoIndex};
28+
use serde_json::json;
29+
use sidechain_domain::{
30+
MainchainKeyHash, McSmartContractResult,
31+
McSmartContractResult::{TxCBOR, TxHash},
32+
McTxHash, UtxoId, UtxoIndex,
33+
};
2934

3035
#[cfg(test)]
3136
mod test;
@@ -36,12 +41,14 @@ pub async fn run_update_governance<
3641
T: QueryLedgerState + Transactions + QueryNetwork + QueryUtxoByUtxoId,
3742
A: AwaitTx,
3843
>(
39-
new_governance_authority: MainchainKeyHash,
44+
old_governance_authority: &Vec<MainchainKeyHash>,
45+
new_governance_authority: &Vec<MainchainKeyHash>,
46+
new_governance_threshold: u8,
4047
payment_key: &CardanoPaymentSigningKey,
4148
genesis_utxo_id: UtxoId,
4249
client: &T,
4350
await_tx: A,
44-
) -> anyhow::Result<OgmiosTx> {
51+
) -> anyhow::Result<McSmartContractResult> {
4552
let ctx = TransactionContext::for_payment_key(payment_key, client).await?;
4653
let governance_data = GovernanceData::get(genesis_utxo_id, client).await?;
4754

@@ -52,7 +59,9 @@ pub async fn run_update_governance<
5259
raw_scripts::VERSION_ORACLE_VALIDATOR,
5360
raw_scripts::VERSION_ORACLE_POLICY,
5461
genesis_utxo_id,
62+
old_governance_authority,
5563
new_governance_authority,
64+
new_governance_threshold,
5665
&governance_data,
5766
costs,
5867
&ctx,
@@ -62,34 +71,57 @@ pub async fn run_update_governance<
6271
)
6372
.await?;
6473

65-
let signed_tx = ctx.sign(&tx);
66-
67-
let response = client.submit_transaction(&signed_tx.to_bytes()).await?;
68-
log::info!("Submitted transaction: {}", hex::encode(response.transaction.id));
74+
let context_pub_key_hash =
75+
MainchainKeyHash(ctx.payment_key_hash().to_bytes().try_into().unwrap());
6976

70-
await_tx
71-
.await_tx_output(
72-
client,
73-
UtxoId { tx_hash: McTxHash(response.transaction.id), index: UtxoIndex(0) },
74-
)
75-
.await?;
76-
77-
Ok(response.transaction)
77+
if old_governance_authority.clone() == vec![context_pub_key_hash] {
78+
let signed_tx = ctx.sign(&tx).to_bytes();
79+
let res = client.submit_transaction(&signed_tx).await.map_err(|e| {
80+
anyhow!(
81+
"Submit governance update transaction request failed: {}, bytes: {}",
82+
e,
83+
hex::encode(signed_tx)
84+
)
85+
})?;
86+
let tx_id = McTxHash(res.transaction.id);
87+
log::info!("Update D-parameter transaction submitted: {}", hex::encode(tx_id.0));
88+
await_tx
89+
.await_tx_output(
90+
client,
91+
UtxoId { tx_hash: McTxHash(res.transaction.id), index: UtxoIndex(0) },
92+
)
93+
.await?;
94+
Ok(TxHash(tx_id))
95+
} else {
96+
let tx_envelope = json!(
97+
{ "type": "Unwitnessed Tx ConwayEra",
98+
"description": "",
99+
"cborHex": hex::encode(tx.to_bytes())
100+
}
101+
);
102+
log::info!("Transaction envelope: {}", tx_envelope);
103+
Ok(TxCBOR(tx.to_bytes()))
104+
}
78105
}
79106

80107
fn update_governance_tx(
81108
multi_sig_policy: &[u8],
82109
version_oracle_validator: &[u8],
83110
version_oracle_policy: &[u8],
84111
genesis_utxo: UtxoId,
85-
new_governance_authority: MainchainKeyHash,
112+
old_governance_authority: &Vec<MainchainKeyHash>,
113+
new_governance_authority: &Vec<MainchainKeyHash>,
114+
new_governance_threshold: u8,
86115
governance_data: &GovernanceData,
87116
costs: Costs,
88117
ctx: &TransactionContext,
89118
) -> anyhow::Result<Transaction> {
90119
let multi_sig_policy =
91120
PlutusScript::from_wrapped_cbor(multi_sig_policy, Language::new_plutus_v2())?
92-
.apply_uplc_data(multisig_governance_policy_configuration(new_governance_authority))?;
121+
.apply_uplc_data(multisig_governance_policy_configuration(
122+
new_governance_authority,
123+
new_governance_threshold,
124+
))?;
93125
let version_oracle_validator =
94126
PlutusScript::from_wrapped_cbor(version_oracle_validator, Language::new_plutus_v2())?
95127
.apply_data(genesis_utxo)?;
@@ -127,5 +159,9 @@ fn update_governance_tx(
127159
inputs
128160
});
129161

162+
for key in old_governance_authority.into_iter() {
163+
tx_builder.add_required_signer(&key.0.into());
164+
}
165+
130166
Ok(tx_builder.balance_update_and_build(ctx)?)
131167
}

toolkit/offchain/src/update_governance/test.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ fn payment_key() -> PrivateKey {
1515
.unwrap()
1616
}
1717

18+
fn public_key_hash() -> MainchainKeyHash {
19+
let payment_key = payment_key();
20+
MainchainKeyHash::from_vkey(&payment_key.to_public().as_bytes().try_into().unwrap())
21+
}
22+
1823
fn test_address_bech32() -> String {
1924
"addr_test1vpmd59ajuvm34d723r8q2qzyz9ylq0x9pygqn7vun8qgpkgs7y5hw".into()
2025
}
@@ -130,7 +135,9 @@ fn test_update_governance_tx() -> Transaction {
130135
test_values::VERSION_ORACLE_VALIDATOR,
131136
test_values::VERSION_ORACLE_POLICY,
132137
genesis_utxo().to_domain(),
133-
new_governance_authority(),
138+
&vec![public_key_hash()],
139+
&vec![new_governance_authority()],
140+
1,
134141
&governance_data(),
135142
test_costs(),
136143
&tx_context(),

toolkit/offchain/tests/integration_tests.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,9 @@ async fn run_update_goveranance<
277277
genesis_utxo: UtxoId,
278278
) {
279279
let _ = update_governance::run_update_governance(
280-
EVE_PUBLIC_KEY_HASH,
280+
&vec![GOVERNANCE_AUTHORITY],
281+
&vec![EVE_PUBLIC_KEY_HASH],
282+
1,
281283
&governance_authority_payment_key(),
282284
genesis_utxo,
283285
client,

toolkit/primitives/domain/src/lib.rs

+14
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,14 @@ impl From<ecdsa::Public> for SidechainPublicKey {
452452
}
453453
}
454454

455+
#[derive(Clone, Encode, Decode, TypeInfo, PartialEq, Eq, Hash)]
456+
#[byte_string(debug, hex_serialize, hex_deserialize, decode_hex)]
457+
pub struct TransactionCbor(pub Vec<u8>);
458+
459+
#[derive(Clone, Encode, Decode, TypeInfo, PartialEq, Eq, Hash)]
460+
#[byte_string(debug, hex_serialize, hex_deserialize, decode_hex)]
461+
pub struct VKeyWitnessCbor(pub Vec<u8>);
462+
455463
#[derive(Clone, Encode, Decode, TypeInfo, PartialEq, Eq, Hash)]
456464
#[byte_string(debug, hex_serialize, hex_deserialize, decode_hex)]
457465
pub struct SidechainSignature(pub Vec<u8>);
@@ -591,6 +599,12 @@ impl TryFrom<Vec<u8>> for McTxHash {
591599
}
592600
}
593601

602+
#[derive(Clone, Debug)]
603+
pub enum McSmartContractResult {
604+
TxHash(McTxHash),
605+
TxCBOR(Vec<u8>),
606+
}
607+
594608
#[derive(Default, Clone, Decode, Encode, PartialEq, Eq, TypeInfo, ToDatum, MaxEncodedLen, Hash)]
595609
#[byte_string(debug, decode_hex, hex_serialize, hex_deserialize)]
596610
pub struct McBlockHash(pub [u8; 32]);

0 commit comments

Comments
 (0)