From edb03058c608232051bde9bb071d29e04f484bcc Mon Sep 17 00:00:00 2001 From: redshiftzero Date: Fri, 22 Nov 2024 11:21:21 -0500 Subject: [PATCH 1/3] UIP-4 Spend Backreferences (#4922) ## Describe your changes This implements UIP-4 as described in https://github.com/penumbra-zone/UIPs/pull/2 ## Issue ticket number and link https://github.com/penumbra-zone/UIPs/pull/2 ## Checklist before requesting a review - [ ] I have added guiding text to explain how a reviewer should test these changes. - [x] If this code contains consensus-breaking changes, I have added the "consensus-breaking" label. Otherwise, I declare my belief that there are not consensus-breaking changes, for the following reason: --- crates/bin/pcli/src/terminal.rs | 4 +- .../component/shielded-pool/src/backref.rs | 217 ++++++++++++++++++ .../core/component/shielded-pool/src/lib.rs | 3 + .../shielded-pool/src/spend/action.rs | 19 ++ .../component/shielded-pool/src/spend/plan.rs | 6 +- crates/core/keys/src/keys/fvk.rs | 7 +- crates/core/keys/src/lib.rs | 2 +- crates/core/keys/src/symmetric.rs | 17 ++ ...enumbra.core.component.shielded_pool.v1.rs | 3 + ...a.core.component.shielded_pool.v1.serde.rs | 21 ++ .../proto/src/gen/proto_descriptor.bin.no_lfs | Bin 627367 -> 627545 bytes .../shielded_pool/v1/shielded_pool.proto | 4 +- 12 files changed, 298 insertions(+), 5 deletions(-) create mode 100644 crates/core/component/shielded-pool/src/backref.rs diff --git a/crates/bin/pcli/src/terminal.rs b/crates/bin/pcli/src/terminal.rs index a13f03d65b..95bf433242 100644 --- a/crates/bin/pcli/src/terminal.rs +++ b/crates/bin/pcli/src/terminal.rs @@ -11,7 +11,7 @@ use penumbra_keys::{ }; use penumbra_proof_params::GROTH16_PROOF_LENGTH_BYTES; use penumbra_sct::Nullifier; -use penumbra_shielded_pool::{Note, NoteView}; +use penumbra_shielded_pool::{EncryptedBackref, Note, NoteView}; use penumbra_tct::structure::Hash; use penumbra_transaction::{view, ActionPlan, ActionView, TransactionPlan, TransactionView}; use termion::{color, input::TermRead}; @@ -82,6 +82,8 @@ fn pretty_print_transaction_plan( balance_commitment: dummy_commitment(), nullifier: Nullifier(Fq::default()), rk: dummy_pk(), + encrypted_backref: EncryptedBackref::try_from([0u8; 0]) + .expect("can create dummy encrypted backref"), }, auth_sig: dummy_sig(), proof: dummy_proof_spend(), diff --git a/crates/core/component/shielded-pool/src/backref.rs b/crates/core/component/shielded-pool/src/backref.rs new file mode 100644 index 0000000000..7eef7f4d96 --- /dev/null +++ b/crates/core/component/shielded-pool/src/backref.rs @@ -0,0 +1,217 @@ +use anyhow::Result; +use chacha20poly1305::{ + aead::{Aead, NewAead}, + ChaCha20Poly1305, Nonce, +}; + +use penumbra_keys::BackreferenceKey; +use penumbra_sct::Nullifier; +use penumbra_tct as tct; + +pub const ENCRYPTED_BACKREF_LEN: usize = 48; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Backref { + note_commitment: tct::StateCommitment, +} + +#[derive(Clone, Debug)] +pub struct EncryptedBackref { + /// The inner bytes can either have 0 or `ENCRYPTED_BACKREF_LEN` bytes. + bytes: Vec, +} + +impl Backref { + pub fn new(note_commitment: tct::StateCommitment) -> Self { + Self { note_commitment } + } + + pub fn encrypt(&self, brk: &BackreferenceKey, nullifier: &Nullifier) -> EncryptedBackref { + let cipher = ChaCha20Poly1305::new(&brk.0); + + // Nonce is the first 12 bytes of the nullifier + let nonce_bytes = &nullifier.to_bytes()[..12]; + let nonce = Nonce::from_slice(&nonce_bytes); + + let plaintext = self.note_commitment.0.to_bytes(); + + let ciphertext = cipher + .encrypt(nonce, plaintext.as_ref()) + .expect("encryption should succeed "); + + EncryptedBackref { bytes: ciphertext } + } +} + +impl EncryptedBackref { + pub fn is_empty(&self) -> bool { + self.bytes.is_empty() + } + + pub fn len(&self) -> usize { + self.bytes.len() + } + + pub fn dummy() -> Self { + Self { bytes: vec![] } + } + + /// Decrypts the encrypted backref, returning a backref if the decryption is successful, + /// or `None` if the encrypted backref is zero-length. + pub fn decrypt( + &self, + brk: &BackreferenceKey, + nullifier: &Nullifier, + ) -> Result> { + // We might have a 0-length encrypted backref, which + // is treated as a valid value and means that the note has no backref. + if self.is_empty() { + return Ok(None); + } + + let cipher = ChaCha20Poly1305::new(&brk.0); + + let nonce_bytes = &nullifier.to_bytes()[..12]; + let nonce = Nonce::from_slice(&nonce_bytes); + + let plaintext = cipher + .decrypt(nonce, self.bytes.as_ref()) + .map_err(|_| anyhow::anyhow!("decryption error"))?; + + let note_commitment_bytes: [u8; 32] = plaintext + .try_into() + .map_err(|_| anyhow::anyhow!("decryption error"))?; + + let backref = Backref::try_from(note_commitment_bytes) + .map_err(|_| anyhow::anyhow!("decryption error"))?; + + Ok(Some(backref)) + } +} + +impl TryFrom<[u8; 32]> for Backref { + type Error = anyhow::Error; + + fn try_from(bytes: [u8; 32]) -> Result { + Ok(Self { + note_commitment: tct::StateCommitment::try_from(bytes) + .map_err(|_| anyhow::anyhow!("invalid note commitment"))?, + }) + } +} + +// EncryptedBackrefs can either have 0 or ENCRYPTED_BACKREF_LEN bytes. + +impl TryFrom<[u8; ENCRYPTED_BACKREF_LEN]> for EncryptedBackref { + type Error = anyhow::Error; + + fn try_from(bytes: [u8; ENCRYPTED_BACKREF_LEN]) -> Result { + Ok(Self { + bytes: bytes.to_vec(), + }) + } +} + +impl TryFrom<[u8; 0]> for EncryptedBackref { + type Error = anyhow::Error; + + fn try_from(bytes: [u8; 0]) -> Result { + Ok(Self { + bytes: bytes.to_vec(), + }) + } +} + +impl From for Vec { + fn from(encrypted_backref: EncryptedBackref) -> Vec { + encrypted_backref.bytes + } +} + +#[cfg(test)] +mod tests { + use super::*; + use proptest::prelude::*; + + use penumbra_asset::{asset, Value}; + use penumbra_keys::keys::{Bip44Path, SeedPhrase, SpendKey}; + + use crate::{Note, Rseed}; + + proptest! { + #[test] + fn encrypted_backref_zero_length(seed_phrase_randomness in any::<[u8; 32]>(), amount_to_send in any::(), rseed_randomness in any::<[u8; 32]>()) { + let seed_phrase = SeedPhrase::from_randomness(&seed_phrase_randomness); + let sk = SpendKey::from_seed_phrase_bip44(seed_phrase, &Bip44Path::new(0)); + let fvk = sk.full_viewing_key(); + let brk = fvk.backref_key(); + + let ivk = fvk.incoming(); + let (sender, _dtk_d) = ivk.payment_address(0u32.into()); + + let value_to_send = Value { + amount: amount_to_send.into(), + asset_id: asset::Cache::with_known_assets() + .get_unit("upenumbra") + .unwrap() + .id(), + }; + let rseed = Rseed(rseed_randomness); + + let note = Note::from_parts(sender, value_to_send, rseed).expect("valid note"); + let note_commitment: penumbra_tct::StateCommitment = note.commit(); + let nk = *sk.nullifier_key(); + let mut sct = tct::Tree::new(); + + sct.insert(tct::Witness::Keep, note_commitment).unwrap(); + let state_commitment_proof = sct.witness(note_commitment).unwrap(); + let nullifier = Nullifier::derive(&nk, state_commitment_proof.position(), ¬e_commitment); + + let encrypted_backref = EncryptedBackref::dummy(); + assert!(encrypted_backref.is_empty()); + assert_eq!(encrypted_backref.len(), 0); + + // Decrypting a zero-length encrypted backref should return `None`. + let decrypted_backref = encrypted_backref.decrypt(&brk, &nullifier).unwrap(); + assert_eq!(decrypted_backref, None); + } + } + + proptest! { + #[test] + fn encrypted_backref_round_trip(seed_phrase_randomness in any::<[u8; 32]>(), amount_to_send in any::(), rseed_randomness in any::<[u8; 32]>()) { + let seed_phrase = SeedPhrase::from_randomness(&seed_phrase_randomness); + let sk = SpendKey::from_seed_phrase_bip44(seed_phrase, &Bip44Path::new(0)); + let fvk = sk.full_viewing_key(); + let brk = fvk.backref_key(); + + let ivk = fvk.incoming(); + let (sender, _dtk_d) = ivk.payment_address(0u32.into()); + + let value_to_send = Value { + amount: amount_to_send.into(), + asset_id: asset::Cache::with_known_assets() + .get_unit("upenumbra") + .unwrap() + .id(), + }; + let rseed = Rseed(rseed_randomness); + + let note = Note::from_parts(sender, value_to_send, rseed).expect("valid note"); + let note_commitment: penumbra_tct::StateCommitment = note.commit(); + let nk = *sk.nullifier_key(); + let mut sct = tct::Tree::new(); + + sct.insert(tct::Witness::Keep, note_commitment).unwrap(); + let state_commitment_proof = sct.witness(note_commitment).unwrap(); + let nullifier = Nullifier::derive(&nk, state_commitment_proof.position(), ¬e_commitment); + + let backref = Backref::new(note_commitment); + let encrypted_backref = backref.encrypt(&brk, &nullifier); + + let decrypted_backref = encrypted_backref.decrypt(&brk, &nullifier).unwrap(); + + assert_eq!(Some(backref), decrypted_backref); + } + } +} diff --git a/crates/core/component/shielded-pool/src/lib.rs b/crates/core/component/shielded-pool/src/lib.rs index 84d112ed52..04109d614e 100644 --- a/crates/core/component/shielded-pool/src/lib.rs +++ b/crates/core/component/shielded-pool/src/lib.rs @@ -25,6 +25,9 @@ pub mod nullifier_derivation; pub mod output; pub mod spend; +pub mod backref; +pub use backref::{Backref, EncryptedBackref}; + pub use convert::{ConvertCircuit, ConvertProof, ConvertProofPrivate, ConvertProofPublic}; pub use nullifier_derivation::{ NullifierDerivationCircuit, NullifierDerivationProof, NullifierDerivationProofPrivate, diff --git a/crates/core/component/shielded-pool/src/spend/action.rs b/crates/core/component/shielded-pool/src/spend/action.rs index 2a548a592a..83517d6748 100644 --- a/crates/core/component/shielded-pool/src/spend/action.rs +++ b/crates/core/component/shielded-pool/src/spend/action.rs @@ -9,6 +9,7 @@ use penumbra_txhash::{EffectHash, EffectingData}; use serde::{Deserialize, Serialize}; use crate::SpendProof; +use crate::{backref::ENCRYPTED_BACKREF_LEN, EncryptedBackref}; #[derive(Clone, Debug)] pub struct Spend { @@ -23,6 +24,7 @@ pub struct Body { pub balance_commitment: balance::Commitment, pub nullifier: Nullifier, pub rk: VerificationKey, + pub encrypted_backref: EncryptedBackref, } impl EffectingData for Body { @@ -91,6 +93,7 @@ impl From for pb::SpendBody { balance_commitment: Some(msg.balance_commitment.into()), nullifier: Some(msg.nullifier.into()), rk: Some(msg.rk.into()), + encrypted_backref: msg.encrypted_backref.into(), } } } @@ -117,10 +120,26 @@ impl TryFrom for Body { .try_into() .context("malformed rk")?; + // `EncryptedBackref` must have 0 or `ENCRYPTED_BACKREF_LEN` bytes. + let encrypted_backref: EncryptedBackref; + if proto.encrypted_backref.len() == ENCRYPTED_BACKREF_LEN { + let bytes: [u8; ENCRYPTED_BACKREF_LEN] = proto + .encrypted_backref + .try_into() + .map_err(|_| anyhow::anyhow!("invalid encrypted backref"))?; + encrypted_backref = EncryptedBackref::try_from(bytes) + .map_err(|_| anyhow::anyhow!("invalid encrypted backref"))?; + } else if proto.encrypted_backref.len() == 0 { + encrypted_backref = EncryptedBackref::dummy(); + } else { + return Err(anyhow::anyhow!("invalid encrypted backref length")); + } + Ok(Body { balance_commitment, nullifier, rk, + encrypted_backref, }) } } diff --git a/crates/core/component/shielded-pool/src/spend/plan.rs b/crates/core/component/shielded-pool/src/spend/plan.rs index a79fd41d99..1602badd0b 100644 --- a/crates/core/component/shielded-pool/src/spend/plan.rs +++ b/crates/core/component/shielded-pool/src/spend/plan.rs @@ -9,7 +9,7 @@ use rand_core::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; use super::{Body, Spend, SpendProof}; -use crate::{Note, Rseed, SpendProofPrivate, SpendProofPublic}; +use crate::{Backref, Note, Rseed, SpendProofPrivate, SpendProofPublic}; /// A planned [`Spend`](Spend). #[derive(Clone, Debug, Deserialize, Serialize)] @@ -75,10 +75,14 @@ impl SpendPlan { /// Construct the [`spend::Body`] described by this [`SpendPlan`]. pub fn spend_body(&self, fvk: &FullViewingKey) -> Body { + // Construct the backreference for this spend. + let backref = Backref::new(self.note.commit()); + let encrypted_backref = backref.encrypt(&fvk.backref_key(), &self.nullifier(fvk)); Body { balance_commitment: self.balance().commit(self.value_blinding), nullifier: self.nullifier(fvk), rk: self.rk(fvk), + encrypted_backref, } } diff --git a/crates/core/keys/src/keys/fvk.rs b/crates/core/keys/src/keys/fvk.rs index e9a029578f..44239700bf 100644 --- a/crates/core/keys/src/keys/fvk.rs +++ b/crates/core/keys/src/keys/fvk.rs @@ -12,7 +12,7 @@ use crate::keys::wallet_id::WalletId; use crate::{ fmd, ka, prf, rdsa::{SpendAuth, VerificationKey}, - Address, AddressView, + Address, AddressView, BackreferenceKey, }; use super::{AddressIndex, DiversifierKey, IncomingViewingKey, NullifierKey, OutgoingViewingKey}; @@ -120,6 +120,11 @@ impl FullViewingKey { &self.ak } + /// Construct the backreference key for this full viewing key. + pub fn backref_key(&self) -> BackreferenceKey { + BackreferenceKey::derive(self.outgoing()).clone() + } + /// Hashes the full viewing key into an [`WalletId`]. pub fn wallet_id(&self) -> WalletId { let hash_result = hash_2( diff --git a/crates/core/keys/src/lib.rs b/crates/core/keys/src/lib.rs index 81c8c8be7f..a880e51832 100644 --- a/crates/core/keys/src/lib.rs +++ b/crates/core/keys/src/lib.rs @@ -14,7 +14,7 @@ pub mod test_keys; pub use address::{Address, AddressVar, AddressView}; pub use keys::FullViewingKey; -pub use symmetric::PayloadKey; +pub use symmetric::{BackreferenceKey, PayloadKey}; fn fmt_hex>(data: T, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{}", hex::encode(data)) diff --git a/crates/core/keys/src/symmetric.rs b/crates/core/keys/src/symmetric.rs index 4e383d15c3..cc04585ed4 100644 --- a/crates/core/keys/src/symmetric.rs +++ b/crates/core/keys/src/symmetric.rs @@ -344,3 +344,20 @@ impl TryFrom<&[u8]> for WrappedMemoKey { Ok(Self(bytes)) } } + +/// Represents a symmetric `ChaCha20Poly1305` key used for Spend backreferences. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct BackreferenceKey(pub Key); + +impl BackreferenceKey { + pub fn derive(ovk: &OutgoingViewingKey) -> Self { + let mut kdf_params = blake2b_simd::Params::new(); + kdf_params.personal(b"Penumbra_Backref"); + kdf_params.hash_length(32); + let mut kdf = kdf_params.to_state(); + kdf.update(&ovk.to_bytes()); + + let key = kdf.finalize(); + Self(*Key::from_slice(key.as_bytes())) + } +} diff --git a/crates/proto/src/gen/penumbra.core.component.shielded_pool.v1.rs b/crates/proto/src/gen/penumbra.core.component.shielded_pool.v1.rs index 1362033de8..e287e3e5e6 100644 --- a/crates/proto/src/gen/penumbra.core.component.shielded_pool.v1.rs +++ b/crates/proto/src/gen/penumbra.core.component.shielded_pool.v1.rs @@ -420,6 +420,9 @@ pub struct SpendBody { pub rk: ::core::option::Option< super::super::super::super::crypto::decaf377_rdsa::v1::SpendVerificationKey, >, + /// An encryption of the commitment of the input note to the sender's OVK. + #[prost(bytes = "vec", tag = "7")] + pub encrypted_backref: ::prost::alloc::vec::Vec, } impl ::prost::Name for SpendBody { const NAME: &'static str = "SpendBody"; diff --git a/crates/proto/src/gen/penumbra.core.component.shielded_pool.v1.serde.rs b/crates/proto/src/gen/penumbra.core.component.shielded_pool.v1.serde.rs index 85e3edf22e..c77243e1d0 100644 --- a/crates/proto/src/gen/penumbra.core.component.shielded_pool.v1.serde.rs +++ b/crates/proto/src/gen/penumbra.core.component.shielded_pool.v1.serde.rs @@ -3789,6 +3789,9 @@ impl serde::Serialize for SpendBody { if self.rk.is_some() { len += 1; } + if !self.encrypted_backref.is_empty() { + len += 1; + } let mut struct_ser = serializer.serialize_struct("penumbra.core.component.shielded_pool.v1.SpendBody", len)?; if let Some(v) = self.balance_commitment.as_ref() { struct_ser.serialize_field("balanceCommitment", v)?; @@ -3799,6 +3802,10 @@ impl serde::Serialize for SpendBody { if let Some(v) = self.rk.as_ref() { struct_ser.serialize_field("rk", v)?; } + if !self.encrypted_backref.is_empty() { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("encryptedBackref", pbjson::private::base64::encode(&self.encrypted_backref).as_str())?; + } struct_ser.end() } } @@ -3813,6 +3820,8 @@ impl<'de> serde::Deserialize<'de> for SpendBody { "balanceCommitment", "nullifier", "rk", + "encrypted_backref", + "encryptedBackref", ]; #[allow(clippy::enum_variant_names)] @@ -3820,6 +3829,7 @@ impl<'de> serde::Deserialize<'de> for SpendBody { BalanceCommitment, Nullifier, Rk, + EncryptedBackref, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -3845,6 +3855,7 @@ impl<'de> serde::Deserialize<'de> for SpendBody { "balanceCommitment" | "balance_commitment" => Ok(GeneratedField::BalanceCommitment), "nullifier" => Ok(GeneratedField::Nullifier), "rk" => Ok(GeneratedField::Rk), + "encryptedBackref" | "encrypted_backref" => Ok(GeneratedField::EncryptedBackref), _ => Ok(GeneratedField::__SkipField__), } } @@ -3867,6 +3878,7 @@ impl<'de> serde::Deserialize<'de> for SpendBody { let mut balance_commitment__ = None; let mut nullifier__ = None; let mut rk__ = None; + let mut encrypted_backref__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::BalanceCommitment => { @@ -3887,6 +3899,14 @@ impl<'de> serde::Deserialize<'de> for SpendBody { } rk__ = map_.next_value()?; } + GeneratedField::EncryptedBackref => { + if encrypted_backref__.is_some() { + return Err(serde::de::Error::duplicate_field("encryptedBackref")); + } + encrypted_backref__ = + Some(map_.next_value::<::pbjson::private::BytesDeserialize<_>>()?.0) + ; + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -3896,6 +3916,7 @@ impl<'de> serde::Deserialize<'de> for SpendBody { balance_commitment: balance_commitment__, nullifier: nullifier__, rk: rk__, + encrypted_backref: encrypted_backref__.unwrap_or_default(), }) } } diff --git a/crates/proto/src/gen/proto_descriptor.bin.no_lfs b/crates/proto/src/gen/proto_descriptor.bin.no_lfs index f2452b1c4b474c5738ad69f0c7d1b1e5f47508b9..70880248d1fa01386236afd3ab38f2d09499eaa5 100644 GIT binary patch delta 3815 zcmYjUZ%kI_6@Sm+a=BhmcrUy!@WP8Pv}zSE40LL>?X+v0)^;XMYnLtEvRS${jc6=e zvg`v^XAE1Kur&7R2ZvCLh>b-Qf)M{imX1)M(jZd*qBhnUW86}+FU(}W^W6LJ_QU(( zcb@b6o%5XYoWrG~ec|Q93wQO)tBLO3_m}ujI`hJRjKmUOr4+^W1%JxLICiBr?XTyNC)@$v!K)Z$U5Z zh*C@6hySD~ds|$~%Hs^r`a5x1Vnb?Oz3Q&!{Kl=@w{3g7VOw2&Lu&h5sfORxrQWXJ z@%x5U{q}~sRKs>{dAF|q&AOe7-c4=ZvZ=~-=en69m8rF|3?zY0v4J{LY0}Mc*@}Ah zL5h4zF zf+}fwz-$BIdxUcG4TSU&iWi1d9Cpg}rE^i1%LD30t`)40v!mu1+)lZ;w9fPFV-y(( zTj#;^n6z#=GV&y&F16{j1?LC^Tz#7;<& z1iEQXkbuVoO1C5sL}AZ5sWy)w%1Pqjxp~Z>oRr$gl>k9GNwK*5f{p^zt7C6Vw8y;!4E{cY`Kcp4CU3OsrPXi8I~tUgbi8~R9X&r@E^Kv2$8QL#Hn z2wvdXLlpT+#lNKUxkFOUSD;iN!H3niU(&1oF!5YXg%L`_G%sNwl!hr;=Ke%ea5$oV zdx19kBgCblj|3l~WVL~yj8N?|_i0T*W5?9>3$&(c%+#ttH+Ia_3J{brsnvU=Ie_D8 z{W$Gu98Ytkr{m-lhGC?q;!86BM}>KHwNUC*+>8dt$PBE)stoP8=|fFPhZ@ zD213bdqZQ44h*iF+lXbPEuc&ihDR2OHX!`?p-EJkhbbzoNV`B|iWV#kZ;JJGh4|X+O(o_Pv%X>(dVO6@ z3kK^82HaHND_CDw(-&fct@7^@2V)r53EU;GEL4yL z?oxS$9^^QqclXrz1P1w@d9g4;=pH%2XaRz9kMg2=eZ>Xcm-V&2_I{cokNmz_T<{Sm zNlf-m9E;j|Ad72_EG}Pdo}`#wTrdWU3oa0NU?vL?0uN-eUep3uTn|<0B)wGoFinxg z^)OA5#r04q>s+?c?(Zn_Kcd7k<-e9uqIxH(eA9R26iCBuJv)zxw@6h4LFy6NdPW7J zERU$@2j&#Yk7&)S?gVQtoM-yVFVSoC+zi=z*MdYK%}{Y|NRo#zL(l%$KxoX+3%_)q zLE{TOdlyIcuo~ZNugEWi-Y#}h?n>OXP#V8mJ)WYcH|}N*wg!yw+08{MQ4=4i?dB(* z)X%8Mv-fi3FC2Wp6v1;ZJLT?P*-1qr*Q6T1rhT^DgR zLT$e!JtluFN<8}jN7~eXuFz=i0d^|2Qi&+FsL`vm&TrxHPdbcHYT-G-Q~^S%h4TyC zY6LHl*WapEU!$6rThkPo<5mv80^kEmD~EGj>e+`l@@aULQg|L>XNmhP)Ji3RcIH%~ zTJ_(eq?)`&NgsybPaclp-Olk6T_Ql}wsYBh*VkkmcBuK+soL*g4yL|TPjm+tCPRv( z(!r^6_a#liE9_K%x=ufB>@?Yz>WS_&*#m;o8DuXh0v%-z{tg16hky%0d712^yl{zu z+S#wtJxt>JizELCFP?%~hI*H4Gp=x)u{;)se&C%@r*2SX#R;sESw9)L6O7NQ=R!Z=9+f#wYg0XJ<<0uZ zlIxN7E=GZp67`g-o2E+tlxYKuh;hoa0T8!5C2d%uDL5QZXQpYXKVaIB)NL3rZ2$yi zK-#cYQxNmCir=K?{nMrmN!^CirVW6goR&5$)fAw!YVS>|sydsdNcqq5oZy2gsTb>6 z#{UjYGKERmtb^*-P5SGGLDMoA!5K6y1BCD(hb=4f?9UnhiUrG}44$8}vsl0UGAYfF z>bXTV{*WmRjPMyUr2&N6kd#I~>&j$UhLv}lR{FyxX&A+7k_H52Sdy;P6qIH}{oywG zKOZsGEYqbK;lla)nE`?_!WB=*wnh}7QS(B9@InED7Yc}IqvpDR;=J$iZNiW7%fQdV RFB3l-zpTE;x1FP1{|CP(5gY&j delta 3713 zcmZu!T}&L;72b1p7>40@2iRp77Go0QU_4M`V(d6+LmQ=Wn?y~NHfmJ0RUfLVeW?1{ zheG03q_RpBVd+{C!Ep%S)Uh#kVUc5GQil+N!FB-4*brKh-dc_AEBsQ=;jEwCbuN4lBCMMY?Z*+0fBZ? zCL*BpWy?eWTl58Gq7e=n(NBETY~e_LKRKRTAbpYz2FR)ix}@a+^9@Aq02P!P2;~8a zC)|RcX)aa=<@mb8EQbel4b-doF$2LJl!MFUJkLHxk+E=c9x{)~Fg&(E9-+!Acb_KbcveO| zJ4@eh%w#D!pk%U?98fYs*^VxkdiMJinNU-+)KL6BIYn+Wgi58s|gU?Y(xq+aZql%>acf#gno;^vCi$tl@ zq@J6g2h~45qJyPn;7m$~pNDRllrp6bU8EQMDdIvcgdlRJs3>J1bf+j??S5BNusE%f zmuR0qOi-?$H#?dPco=iS{DHO zKbL5K`&^bI)14zH5pE;XougExzSHHNeVHPw;hio==4CmjxKX*d(Wk^$!bb><-3 zvT-y(6u+z;O~xc=Tanu24FG|iu@~_ejJ%MW%@fH z8<&dK)c8E@^w)?>v^s*wS)*9686ecw2u`s}Q?Phjr59+of15aXlyP0aZStx^1u5V* zZLZbNI?kBg9relrwKU%`Hx@w%-61DQ$g3QD`&1xvKRcV5yauUI6o*85ZiR|=6byiAhzk^?Y^!$<=L-uU;wcgD zRy(g#TT{0w8bRcAo1y`s)-6TP%HNnu&whg=hgIKox={QEI}KW?Qj~hsAC{@j@8R%s z06{4Aa9;5I0HM^wrDbjtHm{Ut(yRWtOgo6wz-c%tx5{$W3EXxso&h9wCY@;w2wgWmlDg!?&El+ZV@1K`?z|O>uWL=`_<(Y zYV!M;1AC~_PrsiN>5w9&^mEN-_i0VRZ5&koXY_3QpsBt}Km9>dJs>E9LG@B1&{5{# z4<-<12)HGbm)Cxj>$VxFll?|L!!*9#IPyVw@H8@yvGb62|FpFQZ}UCo(;R$ur!6_^ zdz@S7ZpCLeptRxSSl=?6ayp!tJtJF|?685r@ClBu=*HM+YK@I+>83TgYW7LcR5T_iKF>KQmERLz) z-k|ONm>EM_k73M=0T7fi8N=h6f<4cuk8jYo{WE3^X+4HBW(a6 Date: Fri, 22 Nov 2024 11:45:04 -0500 Subject: [PATCH 2/3] UIP-5: Outbound PFM support (#4940) ## Describe your changes Implementation of the candidate UIP-5: outbound PFM support (see https://forum.penumbra.zone/t/pre-uip-outbound-packet-forwarding-middleware-support/121) Should be reviewed for correctness and adherence to the spec. Note that this only includes the required protocol changes to allow clients to use the new memo field (a prerequisite for PFM support), and does not implement outbound packet forwarding directly (that is to be done in the client). By @avahowell cherry-picked from https://github.com/penumbra-zone/penumbra/pull/4923 Co-authored-by: Ava Howell --- crates/bin/pcli/src/command/tx.rs | 1 + .../src/gen/proto_descriptor.bin.no_lfs | Bin 82425 -> 82425 bytes .../app/tests/common/ibc_tests/relayer.rs | 1 + .../shielded-pool/src/ics20_withdrawal.rs | 9 ++++++++- .../src/gen/penumbra.core.component.ibc.v1.rs | 5 +++++ .../penumbra.core.component.ibc.v1.serde.rs | 18 ++++++++++++++++++ .../proto/src/gen/proto_descriptor.bin.no_lfs | Bin 627545 -> 627860 bytes .../penumbra/core/component/ibc/v1/ibc.proto | 5 +++++ 8 files changed, 38 insertions(+), 1 deletion(-) diff --git a/crates/bin/pcli/src/command/tx.rs b/crates/bin/pcli/src/command/tx.rs index 9289d1fb5c..5f9be82c63 100644 --- a/crates/bin/pcli/src/command/tx.rs +++ b/crates/bin/pcli/src/command/tx.rs @@ -1133,6 +1133,7 @@ impl TxCmd { // TODO: impl From for ChannelId source_channel: ChannelId::from_str(format!("channel-{}", channel).as_ref())?, use_compat_address: *use_compat_address, + ics20_memo: "".to_string(), }; let plan = Planner::new(OsRng) diff --git a/crates/cnidarium/src/gen/proto_descriptor.bin.no_lfs b/crates/cnidarium/src/gen/proto_descriptor.bin.no_lfs index 09181ca63e0d92d8142235c3003f76694fd27e4c..762d818a29e60203069925bec510cfda0269d7ba 100644 GIT binary patch delta 75 zcmey_%=)vLbwl@3=EBm7$$d-JLG6AjkjwRe3B0OCX-oB#j- delta 75 zcmey_%=)vLbwl@3=G^@J$$d-JLG6AjkjwRe3B0MT|JDgXcg diff --git a/crates/core/app/tests/common/ibc_tests/relayer.rs b/crates/core/app/tests/common/ibc_tests/relayer.rs index 488e68dcca..2998d40b64 100644 --- a/crates/core/app/tests/common/ibc_tests/relayer.rs +++ b/crates/core/app/tests/common/ibc_tests/relayer.rs @@ -1485,6 +1485,7 @@ impl MockRelayer { source_channel: ChannelId::from_str("channel-0")?, // Penumbra <-> Penumbra so false use_compat_address: false, + ics20_memo: "".to_string(), }; // There will need to be `Spend` and `Output` actions // within the transaction in order for it to balance diff --git a/crates/core/component/shielded-pool/src/ics20_withdrawal.rs b/crates/core/component/shielded-pool/src/ics20_withdrawal.rs index c452d5d703..a4881edd3c 100644 --- a/crates/core/component/shielded-pool/src/ics20_withdrawal.rs +++ b/crates/core/component/shielded-pool/src/ics20_withdrawal.rs @@ -41,6 +41,11 @@ pub struct Ics20Withdrawal { // Whether to use a "compat" (bech32, non-m) address for the return address in the withdrawal, // for compatability with chains that expect to be able to parse the return address as bech32. pub use_compat_address: bool, + + // Arbitrary string data to be included in the `memo` field + // of the ICS-20 FungibleTokenPacketData for this withdrawal. + // Commonly used for packet forwarding support, or other protocols that may support usage of the memo field. + pub ics20_memo: String, } #[cfg(feature = "component")] @@ -118,6 +123,7 @@ impl From for pb::Ics20Withdrawal { timeout_time: w.timeout_time, source_channel: w.source_channel.to_string(), use_compat_address: w.use_compat_address, + ics20_memo: w.ics20_memo.to_string(), } } } @@ -148,6 +154,7 @@ impl TryFrom for Ics20Withdrawal { timeout_time: s.timeout_time, source_channel: ChannelId::from_str(&s.source_channel)?, use_compat_address: s.use_compat_address, + ics20_memo: s.ics20_memo, }) } } @@ -164,7 +171,7 @@ impl From for pb::FungibleTokenPacketData { denom: w.denom.to_string(), receiver: w.destination_chain_address, sender: return_address, - memo: "".to_string(), + memo: w.ics20_memo, } } } diff --git a/crates/proto/src/gen/penumbra.core.component.ibc.v1.rs b/crates/proto/src/gen/penumbra.core.component.ibc.v1.rs index 320e259a47..2eb7b4651d 100644 --- a/crates/proto/src/gen/penumbra.core.component.ibc.v1.rs +++ b/crates/proto/src/gen/penumbra.core.component.ibc.v1.rs @@ -73,6 +73,11 @@ pub struct Ics20Withdrawal { /// for compatability with chains that expect to be able to parse the return address as bech32. #[prost(bool, tag = "8")] pub use_compat_address: bool, + /// Arbitrary string data to be included in the `memo` field + /// of the ICS-20 FungibleTokenPacketData for this withdrawal. + /// Commonly used for packet forwarding support, or other protocols that may support usage of the memo field. + #[prost(string, tag = "9")] + pub ics20_memo: ::prost::alloc::string::String, } impl ::prost::Name for Ics20Withdrawal { const NAME: &'static str = "Ics20Withdrawal"; diff --git a/crates/proto/src/gen/penumbra.core.component.ibc.v1.serde.rs b/crates/proto/src/gen/penumbra.core.component.ibc.v1.serde.rs index b4bcbf3624..58fd339e65 100644 --- a/crates/proto/src/gen/penumbra.core.component.ibc.v1.serde.rs +++ b/crates/proto/src/gen/penumbra.core.component.ibc.v1.serde.rs @@ -1057,6 +1057,9 @@ impl serde::Serialize for Ics20Withdrawal { if self.use_compat_address { len += 1; } + if !self.ics20_memo.is_empty() { + len += 1; + } let mut struct_ser = serializer.serialize_struct("penumbra.core.component.ibc.v1.Ics20Withdrawal", len)?; if let Some(v) = self.amount.as_ref() { struct_ser.serialize_field("amount", v)?; @@ -1083,6 +1086,9 @@ impl serde::Serialize for Ics20Withdrawal { if self.use_compat_address { struct_ser.serialize_field("useCompatAddress", &self.use_compat_address)?; } + if !self.ics20_memo.is_empty() { + struct_ser.serialize_field("ics20Memo", &self.ics20_memo)?; + } struct_ser.end() } } @@ -1107,6 +1113,8 @@ impl<'de> serde::Deserialize<'de> for Ics20Withdrawal { "sourceChannel", "use_compat_address", "useCompatAddress", + "ics20_memo", + "ics20Memo", ]; #[allow(clippy::enum_variant_names)] @@ -1119,6 +1127,7 @@ impl<'de> serde::Deserialize<'de> for Ics20Withdrawal { TimeoutTime, SourceChannel, UseCompatAddress, + Ics20Memo, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -1149,6 +1158,7 @@ impl<'de> serde::Deserialize<'de> for Ics20Withdrawal { "timeoutTime" | "timeout_time" => Ok(GeneratedField::TimeoutTime), "sourceChannel" | "source_channel" => Ok(GeneratedField::SourceChannel), "useCompatAddress" | "use_compat_address" => Ok(GeneratedField::UseCompatAddress), + "ics20Memo" | "ics20_memo" => Ok(GeneratedField::Ics20Memo), _ => Ok(GeneratedField::__SkipField__), } } @@ -1176,6 +1186,7 @@ impl<'de> serde::Deserialize<'de> for Ics20Withdrawal { let mut timeout_time__ = None; let mut source_channel__ = None; let mut use_compat_address__ = None; + let mut ics20_memo__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::Amount => { @@ -1228,6 +1239,12 @@ impl<'de> serde::Deserialize<'de> for Ics20Withdrawal { } use_compat_address__ = Some(map_.next_value()?); } + GeneratedField::Ics20Memo => { + if ics20_memo__.is_some() { + return Err(serde::de::Error::duplicate_field("ics20Memo")); + } + ics20_memo__ = Some(map_.next_value()?); + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -1242,6 +1259,7 @@ impl<'de> serde::Deserialize<'de> for Ics20Withdrawal { timeout_time: timeout_time__.unwrap_or_default(), source_channel: source_channel__.unwrap_or_default(), use_compat_address: use_compat_address__.unwrap_or_default(), + ics20_memo: ics20_memo__.unwrap_or_default(), }) } } diff --git a/crates/proto/src/gen/proto_descriptor.bin.no_lfs b/crates/proto/src/gen/proto_descriptor.bin.no_lfs index 70880248d1fa01386236afd3ab38f2d09499eaa5..33adc2384712dd0c85de944fc4df6b052ce75997 100644 GIT binary patch delta 1342 zcmX|>-D?v|7{+&JHk;k4R$XoSVXLFiw2dFsdUeoh(;O(K)@mb-P-@&Z+ccPT>t;ib zR{^~c5kyefzo3F%ox?#QXb(s5QvU!iy;i7KqNw0Av%9?*y3ae$^P6{e-}J-N^pj`l zzaMPUTg|^7KinJ=fB${Cxt)@Q=dXtjqHvON&fRst>aN)}UUr*HHHVA9=hq9ucQ?Ov zFTbU>^IIhG2iYEJU-?S@SJe+~>l=Bc9<~M;t1dUjMoV?4?)I2m9X97oS(!nh{r)@A zsrj~_>6WG_tHD1#>Vfu`pX4;v$JM@Zo(-~3)UFrj=qK&kw#qb(5W}KpNl)KLm1n)B zs_)s}ZQk&`>RN@DZQtg;%aAmUKlHRTNW4V}(F=FddykEILX0qb-7SCU?#etVuKOfgm>7cQA@U^3mUGQefn9M+E zLWN%x3iR2IkOzg>Z2Cm(1bue&l%yvuZBDrw!jo{#8NCvo6!3y*uZ0PL!aFNOmxqG0 z5M3S$yh3z&!_wxJYa!2oYd+!`f>$KrM}6LGWP4DE74aJuqY1SS^ddjP z1~Gr8r7b8+L8n0nI~R;Db`?Wp3i4u*HRyb1k?1mp8X$|waOj}GSj5nsk$!BIf;R>o z&y;rjV&+Q8bd*-%FO6i_S?R~j*}c#zZf<2gXJ%(}7{W8}^ZfRGnK!#{vOivB-#y=>_dA`J zFZOcs_rvGCUnzC*c0V_Pi!JQ<7+c}{Q|-oO8;kDd@Z_c!D!Vi6Q6$8RJK|C50uG8!H$WJP|7jl}zy!6+@{I z_HDY_Es&&knDR0;6D#-&nXLW{5wu7qb&23J#ldF?Vnr`*fgn~)Pv{s#iA?JlMXWT4 z$uUaacsDLe>*N$BB#a4GHWs648Ej=Ysd!9IThaYOfLGBxC>Sf=h{8buS2;GSaF$?8 z#+3+X!L{V36wZ>kZk6O!2Fez+s_9U0R#Ta{243~#tm5I#mW`E&XT!DZjwzlkc>y`E zA`na}9xK3uM1(^Pu^@XIwXVe=K@ZNJQ*=kLRpV+zcVJs}#}(a?^qOuD0>jtz@K7+; z^zcx?)%5VLU~9&;2JJLt)^)c~pw|;e;~H{xF%>J&>;4Iz!Ap69 zts5KRvPUyR>v*N>ST0YJ8{rd=uIC%XRw>+p(Qu+=gaV_1Wn55xl$zmRj~4UIfnP3T z)5+?z34e1o&kM?rWo(68oR;#ffnRQ6%gJfK+`?A=6!+017i<%MvG1JvLz{T|X)w6# pu^k@bZmLa&RSI`tv`5mh0;9b%m!LQd$HK9392~bdm+)>q`wvrHds_ei diff --git a/proto/penumbra/penumbra/core/component/ibc/v1/ibc.proto b/proto/penumbra/penumbra/core/component/ibc/v1/ibc.proto index 733ca5a3af..de0d70d347 100644 --- a/proto/penumbra/penumbra/core/component/ibc/v1/ibc.proto +++ b/proto/penumbra/penumbra/core/component/ibc/v1/ibc.proto @@ -54,6 +54,11 @@ message Ics20Withdrawal { // Whether to use a "compat" (bech32, non-m) address for the return address in the withdrawal, // for compatability with chains that expect to be able to parse the return address as bech32. bool use_compat_address = 8; + + // Arbitrary string data to be included in the `memo` field + // of the ICS-20 FungibleTokenPacketData for this withdrawal. + // Commonly used for packet forwarding support, or other protocols that may support usage of the memo field. + string ics20_memo = 9; } message ClientData { From 2b6503cb5bf6ace90402db4bb3d28b6dd1e4481f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=BAc=C3=A1s=20Meier?= Date: Fri, 22 Nov 2024 12:57:57 -0800 Subject: [PATCH 3/3] pd: implement no op migration to v0.81.0 (#4942) This implements a migration to version 0.81.0, which doesn't require any logic. This will break PD, but targets the new release branch. The APP_VERSION has been incremented to 9, in order to satisfy the safeguard version checks from UIP-6. --------- Co-authored-by: Conor Schaefer --- COMPATIBILITY.md | 1 + crates/bin/pd/src/main.rs | 4 +- crates/bin/pd/src/migrate.rs | 7 ++ crates/bin/pd/src/migrate/mainnet2.rs | 103 +++++++++++++++++++ crates/core/app/src/app_version.rs | 2 +- crates/core/app/src/app_version/component.rs | 1 + 6 files changed, 115 insertions(+), 3 deletions(-) create mode 100644 crates/bin/pd/src/migrate/mainnet2.rs diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md index 76436ebaac..345b75ab60 100644 --- a/COMPATIBILITY.md +++ b/COMPATIBILITY.md @@ -9,3 +9,4 @@ | 7 (Testnet 78) | v0.78.x | v0.37.5 | v1 | | 7 (Mainnet) | v0.79.x | v0.37.x | v1 | | 8 (Mainnet) | v0.80.x | v0.37.x | v1 | +| 9 (Mainnet) | v0.81.x | v0.37.x | v1 | diff --git a/crates/bin/pd/src/main.rs b/crates/bin/pd/src/main.rs index 7f2ead78ca..a622895d36 100644 --- a/crates/bin/pd/src/main.rs +++ b/crates/bin/pd/src/main.rs @@ -12,7 +12,7 @@ use cnidarium::Storage; use metrics_exporter_prometheus::PrometheusBuilder; use pd::{ cli::{NetworkCommand, Opt, RootCommand}, - migrate::Migration::{Mainnet1, ReadyToStart}, + migrate::Migration::{Mainnet2, ReadyToStart}, network::{ config::{get_network_dir, parse_tm_address, url_has_necessary_parts}, generate::NetworkConfig, @@ -472,7 +472,7 @@ async fn main() -> anyhow::Result<()> { let genesis_start = pd::migrate::last_block_timestamp(pd_home.clone()).await?; tracing::info!(?genesis_start, "last block timestamp"); - Mainnet1 + Mainnet2 .migrate(pd_home.clone(), comet_home, Some(genesis_start), force) .instrument(pd_migrate_span) .await diff --git a/crates/bin/pd/src/migrate.rs b/crates/bin/pd/src/migrate.rs index ad6e612a9e..2beaaed7e3 100644 --- a/crates/bin/pd/src/migrate.rs +++ b/crates/bin/pd/src/migrate.rs @@ -5,6 +5,7 @@ //! This module declares how local `pd` state should be altered, if at all, //! in order to be compatible with the network post-chain-upgrade. mod mainnet1; +mod mainnet2; mod reset_halt_bit; mod simple; mod testnet72; @@ -56,6 +57,9 @@ pub enum Migration { /// Mainnet-1 migration: /// - Restore IBC packet commitments for improperly handled withdrawal attempts Mainnet1, + /// Mainnet-2 migration: + /// - no-op + Mainnet2, } impl Migration { @@ -94,6 +98,9 @@ impl Migration { Migration::Mainnet1 => { mainnet1::migrate(storage, pd_home.clone(), genesis_start).await?; } + Migration::Mainnet2 => { + mainnet2::migrate(storage, pd_home.clone(), genesis_start).await?; + } // We keep historical migrations around for now, this will help inform an abstracted // design. Feel free to remove it if it's causing you trouble. _ => unimplemented!("the specified migration is unimplemented"), diff --git a/crates/bin/pd/src/migrate/mainnet2.rs b/crates/bin/pd/src/migrate/mainnet2.rs new file mode 100644 index 0000000000..32a4c4d364 --- /dev/null +++ b/crates/bin/pd/src/migrate/mainnet2.rs @@ -0,0 +1,103 @@ +//! Migration for shipping consensus-breaking IBC changes, fixing +//! how withdrawals from Penumbra to Noble are handled, and ensures that IBC +//! error messages from counterparty chains are processed. +use cnidarium::{StateDelta, Storage}; +use jmt::RootHash; +use penumbra_app::app::StateReadExt as _; +use penumbra_app::app_version::migrate_app_version; +use penumbra_governance::StateWriteExt; +use penumbra_sct::component::clock::EpochManager; +use penumbra_sct::component::clock::EpochRead; +use std::path::PathBuf; +use tracing::instrument; + +use crate::network::generate::NetworkConfig; + +/// Run the full migration, emitting a new genesis event, representing historical state. +/// +/// This will have the effect of reinserting packets which had acknowledgements containing +/// errors, and erroneously removed from state, as if the acknowledgements had contained successes. +#[instrument] +pub async fn migrate( + storage: Storage, + pd_home: PathBuf, + genesis_start: Option, +) -> anyhow::Result<()> { + // Setup: + let initial_state = storage.latest_snapshot(); + let chain_id = initial_state.get_chain_id().await?; + let root_hash = initial_state + .root_hash() + .await + .expect("chain state has a root hash"); + // We obtain the pre-upgrade hash solely to log it as a result. + let pre_upgrade_root_hash: RootHash = root_hash.into(); + let pre_upgrade_height = initial_state + .get_block_height() + .await + .expect("chain state has a block height"); + let post_upgrade_height = pre_upgrade_height.wrapping_add(1); + + let mut delta = StateDelta::new(initial_state); + let (migration_duration, post_upgrade_root_hash) = { + let start_time = std::time::SystemTime::now(); + + migrate_app_version(&mut delta, 9).await?; + + // Reset the application height and halt flag. + delta.ready_to_start(); + delta.put_block_height(0u64); + + // Finally, commit the changes to the chain state. + let post_upgrade_root_hash = storage.commit_in_place(delta).await?; + tracing::info!(?post_upgrade_root_hash, "post-migration root hash"); + + ( + start_time.elapsed().expect("start is set"), + post_upgrade_root_hash, + ) + }; + storage.release().await; + + // The migration is complete, now we need to generate a genesis file. To do this, we need + // to lookup a validator view from the chain, and specify the post-upgrade app hash and + // initial height. + let app_state = penumbra_app::genesis::Content { + chain_id, + ..Default::default() + }; + let mut genesis = NetworkConfig::make_genesis(app_state.clone()).expect("can make genesis"); + genesis.app_hash = post_upgrade_root_hash + .0 + .to_vec() + .try_into() + .expect("infallible conversion"); + + genesis.initial_height = post_upgrade_height as i64; + genesis.genesis_time = genesis_start.unwrap_or_else(|| { + let now = tendermint::time::Time::now(); + tracing::info!(%now, "no genesis time provided, detecting a testing setup"); + now + }); + let checkpoint = post_upgrade_root_hash.0.to_vec(); + let genesis = NetworkConfig::make_checkpoint(genesis, Some(checkpoint)); + let genesis_json = serde_json::to_string(&genesis).expect("can serialize genesis"); + tracing::info!("genesis: {}", genesis_json); + let genesis_path = pd_home.join("genesis.json"); + std::fs::write(genesis_path, genesis_json).expect("can write genesis"); + + let validator_state_path = pd_home.join("priv_validator_state.json"); + let fresh_validator_state = crate::network::generate::NetworkValidator::initial_state(); + std::fs::write(validator_state_path, fresh_validator_state).expect("can write validator state"); + + tracing::info!( + pre_upgrade_height, + post_upgrade_height, + ?pre_upgrade_root_hash, + ?post_upgrade_root_hash, + duration = migration_duration.as_secs(), + "successful migration!" + ); + + Ok(()) +} diff --git a/crates/core/app/src/app_version.rs b/crates/core/app/src/app_version.rs index 93ad9afc47..ec1e09e13a 100644 --- a/crates/core/app/src/app_version.rs +++ b/crates/core/app/src/app_version.rs @@ -1,6 +1,6 @@ /// Representation of the Penumbra application version. Notably, this is distinct /// from the crate version(s). This number should only ever be incremented. -pub const APP_VERSION: u64 = 8; +pub const APP_VERSION: u64 = 9; cfg_if::cfg_if! { if #[cfg(feature="component")] { diff --git a/crates/core/app/src/app_version/component.rs b/crates/core/app/src/app_version/component.rs index 2ca749dd03..281dc07d6d 100644 --- a/crates/core/app/src/app_version/component.rs +++ b/crates/core/app/src/app_version/component.rs @@ -16,6 +16,7 @@ fn version_to_software_version(version: u64) -> &'static str { 6 => "v0.77.x", 7 => "v0.79.x", 8 => "v0.80.x", + 9 => "v0.81.x", _ => "unknown", } }