Skip to content

Commit ddf9b70

Browse files
authored
Merge pull request #1224 from input-output-hk/owner_cert_jcli
Add owner stake deletagion cert creation tool to JCLI
2 parents b0f86d6 + c174728 commit ddf9b70

File tree

8 files changed

+166
-70
lines changed

8 files changed

+166
-70
lines changed

doc/stake_pool/delegating_stake.md

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,32 @@
22

33
## how to create the delegation certificate
44

5-
Stake is concentrated in accounts, and you will need your account public key to
6-
delegate its associated stake.
5+
Stake is concentrated in accounts, and you will need account public key to delegate its associated stake.
76

8-
You will need your:
7+
### for own account
8+
9+
You will need:
10+
11+
* the Stake Pool ID: an hexadecimal string identifying the stake pool you want
12+
to delegate your stake to.
13+
14+
```sh
15+
jcli certificate new owned-stake-delegation STAKE_POOL_ID > stake_delegation.cert
16+
```
17+
18+
Note that the certificate is in blaco, there's no account key used for its creation.
19+
In order for delegation to work it must be submitted to a node inside a very specific transaction:
20+
21+
* Transaction must have exactly 1 input
22+
* The input must be from account
23+
* The input value must be strictly equal to fee of the transaction
24+
* Transaction must have 0 outputs
25+
26+
The account used for input will have its stake delegated to the stake pool
27+
28+
### for any account
29+
30+
You will need:
931

1032
* account public key: a bech32 string of a public key
1133
* the Stake Pool ID: an hexadecimal string identifying the stake pool you want

jcli/src/jcli_app/certificate/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ use std::path::{Path, PathBuf};
77
use structopt::StructOpt;
88

99
mod get_stake_pool_id;
10+
mod new_owner_stake_delegation;
1011
mod new_stake_delegation;
1112
mod new_stake_pool_registration;
1213
mod sign;
14+
mod weighted_pool_ids;
1315

1416
pub(crate) use self::sign::{pool_owner_sign, stake_delegation_account_binding_sign};
1517

@@ -73,6 +75,8 @@ pub enum NewArgs {
7375
StakePoolRegistration(new_stake_pool_registration::StakePoolRegistration),
7476
/// build a stake delegation certificate
7577
StakeDelegation(new_stake_delegation::StakeDelegation),
78+
/// build an owner stake delegation certificate
79+
OwnerStakeDelegation(new_owner_stake_delegation::OwnerStakeDelegation),
7680
}
7781

7882
#[derive(StructOpt)]
@@ -98,6 +102,7 @@ impl NewArgs {
98102
match self {
99103
NewArgs::StakePoolRegistration(args) => args.exec()?,
100104
NewArgs::StakeDelegation(args) => args.exec()?,
105+
NewArgs::OwnerStakeDelegation(args) => args.exec()?,
101106
}
102107
Ok(())
103108
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
use crate::jcli_app::certificate::{weighted_pool_ids::WeightedPoolIds, write_cert, Error};
2+
use chain_impl_mockchain::certificate::{Certificate, OwnerStakeDelegation as Delegation};
3+
use jormungandr_lib::interfaces::Certificate as CertificateType;
4+
use std::convert::TryInto;
5+
use std::ops::Deref;
6+
use std::path::PathBuf;
7+
use structopt::StructOpt;
8+
9+
#[derive(StructOpt)]
10+
pub struct OwnerStakeDelegation {
11+
#[structopt(flatten)]
12+
pool_ids: WeightedPoolIds,
13+
14+
/// write the output to the given file or print it to the standard output if not defined
15+
#[structopt(short = "o", long = "output")]
16+
output: Option<PathBuf>,
17+
}
18+
19+
impl OwnerStakeDelegation {
20+
pub fn exec(self) -> Result<(), Error> {
21+
let cert = Certificate::OwnerStakeDelegation(Delegation {
22+
delegation: (&self.pool_ids).try_into()?,
23+
});
24+
write_cert(
25+
self.output.as_ref().map(|x| x.deref()),
26+
CertificateType(cert),
27+
)
28+
}
29+
}
Lines changed: 6 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
1-
use crate::jcli_app::certificate::{write_cert, Error};
1+
use crate::jcli_app::certificate::{weighted_pool_ids::WeightedPoolIds, write_cert, Error};
22
use crate::jcli_app::utils::key_parser::parse_pub_key;
3-
use chain_crypto::{Blake2b256, Ed25519, PublicKey};
4-
use chain_impl_mockchain::account::{DelegationRatio, DelegationType};
5-
use chain_impl_mockchain::accounting::account::DELEGATION_RATIO_MAX_DECLS;
3+
use chain_crypto::{Ed25519, PublicKey};
64
use chain_impl_mockchain::certificate::{Certificate, StakeDelegation as Delegation};
75
use chain_impl_mockchain::transaction::UnspecifiedAccountIdentifier;
86
use jormungandr_lib::interfaces::Certificate as CertificateType;
9-
use std::convert::TryFrom;
10-
use std::error::Error as StdError;
7+
use std::convert::TryInto;
118
use std::ops::Deref;
129
use std::path::PathBuf;
13-
use std::str::FromStr;
1410
use structopt::StructOpt;
1511

1612
#[derive(StructOpt)]
@@ -19,42 +15,19 @@ pub struct StakeDelegation {
1915
#[structopt(name = "STAKE_KEY", parse(try_from_str = "parse_pub_key"))]
2016
stake_id: PublicKey<Ed25519>,
2117

22-
/// hex-encoded stake pool IDs and their numeric weights in format "pool_id:weight".
23-
/// If weight is not provided, it defaults to 1.
24-
#[structopt(name = "STAKE_POOL_IDS", raw(required = "true"))]
25-
pool_ids: Vec<WeightedPoolId>,
18+
#[structopt(flatten)]
19+
pool_ids: WeightedPoolIds,
2620

2721
/// write the output to the given file or print it to the standard output if not defined
2822
#[structopt(short = "o", long = "output")]
2923
output: Option<PathBuf>,
3024
}
3125

32-
struct WeightedPoolId {
33-
pool_id: Blake2b256,
34-
weight: u8,
35-
}
36-
37-
impl FromStr for WeightedPoolId {
38-
type Err = Box<dyn StdError>;
39-
40-
fn from_str(s: &str) -> Result<Self, Self::Err> {
41-
let mut split = s.splitn(2, ':');
42-
Ok(WeightedPoolId {
43-
pool_id: split.next().unwrap().parse()?,
44-
weight: split.next().map_or(Ok(1), str::parse)?,
45-
})
46-
}
47-
}
48-
4926
impl StakeDelegation {
5027
pub fn exec(self) -> Result<(), Error> {
51-
let delegation = match self.pool_ids.len() {
52-
1 => DelegationType::Full(self.pool_ids[0].pool_id.into()),
53-
_ => DelegationType::Ratio(delegation_ratio(&self.pool_ids)?),
54-
};
5528
let content = Delegation {
5629
account_id: UnspecifiedAccountIdentifier::from_single_account(self.stake_id.into()),
57-
delegation,
30+
delegation: (&self.pool_ids).try_into()?,
5831
};
5932
let cert = Certificate::StakeDelegation(content);
6033
write_cert(
@@ -63,32 +36,3 @@ impl StakeDelegation {
6336
)
6437
}
6538
}
66-
67-
fn delegation_ratio(pool_ids: &[WeightedPoolId]) -> Result<DelegationRatio, Error> {
68-
if pool_ids.len() > DELEGATION_RATIO_MAX_DECLS {
69-
return Err(Error::TooManyPoolDelegations {
70-
actual: pool_ids.len(),
71-
max: DELEGATION_RATIO_MAX_DECLS,
72-
});
73-
}
74-
let parts = delegation_ratio_sum(pool_ids)?;
75-
let pools = pool_ids
76-
.iter()
77-
.map(|pool_id| (pool_id.pool_id.into(), pool_id.weight))
78-
.collect();
79-
DelegationRatio::new(parts, pools).ok_or_else(|| Error::InvalidPoolDelegation)
80-
}
81-
82-
fn delegation_ratio_sum(pool_ids: &[WeightedPoolId]) -> Result<u8, Error> {
83-
let parts = pool_ids
84-
.iter()
85-
.map(|pool_id| match pool_id.weight {
86-
0 => Err(Error::PoolDelegationWithZeroWeight),
87-
weight => Ok(weight as u64),
88-
})
89-
.sum::<Result<_, _>>()?;
90-
u8::try_from(parts).map_err(|_| Error::InvalidPoolDelegationWeights {
91-
actual: parts,
92-
max: u8::max_value() as u64,
93-
})
94-
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
use crate::jcli_app::certificate::Error;
2+
use chain_crypto::Blake2b256;
3+
use chain_impl_mockchain::account::{DelegationRatio, DelegationType};
4+
use chain_impl_mockchain::accounting::account::DELEGATION_RATIO_MAX_DECLS;
5+
use std::convert::TryFrom;
6+
use std::error::Error as StdError;
7+
use std::str::FromStr;
8+
use structopt::StructOpt;
9+
10+
#[derive(StructOpt)]
11+
pub struct WeightedPoolIds {
12+
/// hex-encoded stake pool IDs and their numeric weights in format "pool_id:weight".
13+
/// If weight is not provided, it defaults to 1.
14+
#[structopt(name = "STAKE_POOL_IDS", raw(required = "true"))]
15+
pool_ids: Vec<WeightedPoolId>,
16+
}
17+
18+
struct WeightedPoolId {
19+
pool_id: Blake2b256,
20+
weight: u8,
21+
}
22+
23+
impl<'a> TryFrom<&'a WeightedPoolIds> for DelegationType {
24+
type Error = Error;
25+
26+
fn try_from(pool_ids: &'a WeightedPoolIds) -> Result<Self, Self::Error> {
27+
let pool_ids = &pool_ids.pool_ids;
28+
let delegation = match pool_ids.len() {
29+
1 => DelegationType::Full(pool_ids[0].pool_id.into()),
30+
_ => DelegationType::Ratio(delegation_ratio(pool_ids)?),
31+
};
32+
Ok(delegation)
33+
}
34+
}
35+
36+
fn delegation_ratio(pool_ids: &[WeightedPoolId]) -> Result<DelegationRatio, Error> {
37+
if pool_ids.len() > DELEGATION_RATIO_MAX_DECLS {
38+
return Err(Error::TooManyPoolDelegations {
39+
actual: pool_ids.len(),
40+
max: DELEGATION_RATIO_MAX_DECLS,
41+
});
42+
}
43+
let parts = delegation_ratio_sum(pool_ids)?;
44+
let pools = pool_ids
45+
.iter()
46+
.map(|pool_id| (pool_id.pool_id.into(), pool_id.weight))
47+
.collect();
48+
DelegationRatio::new(parts, pools).ok_or_else(|| Error::InvalidPoolDelegation)
49+
}
50+
51+
fn delegation_ratio_sum(pool_ids: &[WeightedPoolId]) -> Result<u8, Error> {
52+
let parts = pool_ids
53+
.iter()
54+
.map(|pool_id| match pool_id.weight {
55+
0 => Err(Error::PoolDelegationWithZeroWeight),
56+
weight => Ok(weight as u64),
57+
})
58+
.sum::<Result<_, _>>()?;
59+
u8::try_from(parts).map_err(|_| Error::InvalidPoolDelegationWeights {
60+
actual: parts,
61+
max: u8::max_value() as u64,
62+
})
63+
}
64+
65+
impl FromStr for WeightedPoolId {
66+
type Err = Box<dyn StdError>;
67+
68+
fn from_str(s: &str) -> Result<Self, Self::Err> {
69+
let mut split = s.splitn(2, ':');
70+
Ok(WeightedPoolId {
71+
pool_id: split.next().unwrap().parse()?,
72+
weight: split.next().map_or(Ok(1), str::parse)?,
73+
})
74+
}
75+
}

jcli/src/jcli_app/transaction/add_certificate.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ pub struct AddCertificate {
88
#[structopt(flatten)]
99
pub common: common::CommonTransaction,
1010

11-
/// the value
12-
#[structopt(name = "VALUE", parse(try_from_str))]
11+
/// bech32-encoded certificate
1312
pub certificate: Certificate,
1413
}
1514

jcli/src/jcli_app/transaction/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,11 @@ custom_error! { pub Error
131131
ExpectingOnlyOneSigningKey { got: usize }
132132
= "expecting only one signing keys but got {got}",
133133
CertificateError { error: certificate::Error } = "certificate error {error}",
134+
135+
TxWithOwnerStakeDelegationMultiInputs { inputs: usize }
136+
= "transaction has owner stake delegation, but has {inputs} inputs, should have 1",
137+
TxWithOwnerStakeDelegationHasUtxoInput = "transaction has owner stake delegation, but has UTxO input",
138+
TxWithOwnerStakeDelegationHasOutputs = "transaction has owner stake delegation, but has outputs",
134139
}
135140

136141
/*

jcli/src/jcli_app/transaction/staging.rs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ impl Staging {
192192
payload: &P,
193193
fee_algorithm: &FA,
194194
output_policy: chain::transaction::OutputPolicy,
195-
) -> Result<chain::transaction::Balance, Error>
195+
) -> Result<Balance, Error>
196196
where
197197
FA: FeeAlgorithm,
198198
P: Payload,
@@ -215,7 +215,7 @@ impl Staging {
215215
&mut self,
216216
fee_algorithm: &FA,
217217
output_policy: chain::transaction::OutputPolicy,
218-
) -> Result<chain::transaction::Balance, Error>
218+
) -> Result<Balance, Error>
219219
where
220220
FA: FeeAlgorithm,
221221
{
@@ -241,7 +241,24 @@ impl Staging {
241241
self.finalize_payload(&c, fee_algorithm, output_policy)
242242
}
243243
Certificate::OwnerStakeDelegation(c) => {
244-
self.finalize_payload(&c, fee_algorithm, output_policy)
244+
let balance = self.finalize_payload(&c, fee_algorithm, output_policy)?;
245+
match self.inputs() {
246+
[input] => match input.input {
247+
interfaces::TransactionInputType::Account(_) => (),
248+
interfaces::TransactionInputType::Utxo(_, _) => {
249+
return Err(Error::TxWithOwnerStakeDelegationHasUtxoInput)
250+
}
251+
},
252+
inputs @ _ => {
253+
return Err(Error::TxWithOwnerStakeDelegationMultiInputs {
254+
inputs: inputs.len(),
255+
})
256+
}
257+
};
258+
if self.outputs().is_empty() == false {
259+
return Err(Error::TxWithOwnerStakeDelegationHasOutputs);
260+
}
261+
Ok(balance)
245262
}
246263
},
247264
}

0 commit comments

Comments
 (0)