Merge branch 'dev' into Pravdyvy/pda-account-id-generation

This commit is contained in:
Pravdyvy 2026-07-01 15:34:20 +03:00
commit 9d1e873344
25 changed files with 156 additions and 39 deletions

View File

@ -2,6 +2,7 @@ on:
push:
branches:
- main
- dev
paths-ignore:
- "**.md"
- "!.github/workflows/*.yml"
@ -94,7 +95,7 @@ jobs:
uses: Swatinem/rust-cache@v2
with:
shared-key: ci-rust-cache
save-if: ${{ github.ref == 'refs/heads/main' }}
save-if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev' }}
- name: Lint workspace
env:
@ -125,7 +126,7 @@ jobs:
uses: Swatinem/rust-cache@v2
with:
shared-key: ci-rust-cache
save-if: ${{ github.ref == 'refs/heads/main' }}
save-if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev' }}
- name: Install nextest
run: cargo install --locked cargo-nextest
@ -156,7 +157,7 @@ jobs:
uses: Swatinem/rust-cache@v2
with:
shared-key: ci-rust-cache
save-if: ${{ github.ref == 'refs/heads/main' }}
save-if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev' }}
- name: Install nextest
run: cargo install --locked cargo-nextest
@ -245,7 +246,7 @@ jobs:
uses: Swatinem/rust-cache@v2
with:
shared-key: ci-rust-cache
save-if: ${{ github.ref == 'refs/heads/main' }}
save-if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev' }}
- name: Test valid proof
env:
@ -268,7 +269,7 @@ jobs:
uses: Swatinem/rust-cache@v2
with:
shared-key: ci-rust-cache
save-if: ${{ github.ref == 'refs/heads/main' }}
save-if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev' }}
- name: Install just
run: cargo install --locked just

View File

@ -57,12 +57,16 @@ Before merging a PR, consider squashing non-meaningful commits. E.g.:
Could be squashed to an empty commit if they belong to the same PR.
## Default branch
By default all PRs must be directed into the `dev` branch. This helps us to keep releases stable.
## Branch workflow
When bringing your feature branch up to date, prefer rebasing on top of `main`.
When bringing your feature branch up to date, prefer rebasing on top of `dev`.
- Preferred: `git rebase main`
- Avoid: `git merge main` in feature branches
- Preferred: `git rebase dev`
- Avoid: `git merge dev` in feature branches
This keeps commit history cleaner and makes reviews easier.

6
Cargo.lock generated
View File

@ -206,9 +206,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.102"
version = "1.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3"
[[package]]
name = "arbitrary"
@ -8953,8 +8953,10 @@ name = "sequencer_service_protocol"
version = "0.1.0"
dependencies = [
"common",
"hex",
"lee",
"lee_core",
"serde_with",
]
[[package]]

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -98,6 +98,66 @@ async fn public_bridge_deposit_invocation_is_dropped() -> anyhow::Result<()> {
Ok(())
}
#[test]
async fn public_bridge_deposit_with_zero_amount_is_rejected() -> anyhow::Result<()> {
let ctx = TestContext::new().await?;
let recipient_id = ctx.existing_public_accounts()[0];
let bridge_account_id = system_accounts::bridge_account_id();
let vault_program_id = programs::vault().id();
let recipient_vault_id = vault_core::compute_vault_account_id(vault_program_id, recipient_id);
let message = public_transaction::Message::try_new(
programs::bridge().id(),
vec![bridge_account_id, recipient_vault_id],
vec![],
bridge_core::Instruction::Deposit {
l1_deposit_op_id: [0_u8; 32],
vault_program_id,
recipient_id,
amount: 0,
},
)
.context("Failed to build zero-amount public bridge deposit transaction")?;
let attack_tx = LeeTransaction::Public(lee::PublicTransaction::new(
message,
lee::public_transaction::WitnessSet::from_raw_parts(vec![]),
));
let bridge_balance_before = ctx
.sequencer_client()
.get_account_balance(bridge_account_id)
.await?;
let vault_balance_before = ctx
.sequencer_client()
.get_account_balance(recipient_vault_id)
.await?;
let tx_hash = ctx.sequencer_client().send_transaction(attack_tx).await?;
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
let bridge_balance_after = ctx
.sequencer_client()
.get_account_balance(bridge_account_id)
.await?;
let vault_balance_after = ctx
.sequencer_client()
.get_account_balance(recipient_vault_id)
.await?;
let tx_on_chain = ctx.sequencer_client().get_transaction(tx_hash).await?;
assert_eq!(bridge_balance_after, bridge_balance_before);
assert_eq!(vault_balance_after, vault_balance_before);
assert!(
tx_on_chain.is_none(),
"Public bridge::Deposit with zero amount should be rejected"
);
Ok(())
}
#[test]
async fn private_bridge_deposit_invocation_is_dropped() -> anyhow::Result<()> {
let ctx = TestContext::new().await?;

View File

@ -173,7 +173,7 @@ impl LeeTransaction {
balance: pre.balance,
..post.clone()
};
(expected_pre == pre) && (pre.balance <= post.balance)
(expected_pre == pre) && (pre.balance < post.balance)
};
if only_balance_increased {

View File

@ -4,7 +4,7 @@ use anyhow::{Context as _, Result, anyhow};
use common::block::Block;
use log::{info, warn};
pub use logos_blockchain_core::mantle::ops::channel::MsgId;
use logos_blockchain_core::mantle::ops::channel::inscribe::Inscription;
use logos_blockchain_core::mantle::ops::channel::{ChannelId, inscribe::Inscription};
pub use logos_blockchain_key_management_system_service::keys::{Ed25519Key, ZkKey};
pub use logos_blockchain_zone_sdk::sequencer::SequencerCheckpoint;
use logos_blockchain_zone_sdk::{
@ -61,11 +61,14 @@ pub trait BlockPublisherTrait: Clone {
/// Fire-and-forget publish. Zone-sdk drives the actual submission and
/// retries internally; this just hands the payload off.
async fn publish_block(&self, block: &Block, withdrawals: Vec<WithdrawArg>) -> Result<()>;
fn channel_id(&self) -> ChannelId;
}
/// Real block publisher backed by zone-sdk's `ZoneSequencer`.
#[derive(Clone)]
pub struct ZoneSdkPublisher {
channel_id: ChannelId,
publish_tx: mpsc::Sender<(Inscription, Vec<WithdrawArg>)>,
// Aborts the drive task when the last clone is dropped.
_drive_task: Arc<DriveTaskGuard>,
@ -192,6 +195,7 @@ impl BlockPublisherTrait for ZoneSdkPublisher {
.context("Zone-sdk readiness channel closed before becoming ready")?;
Ok(Self {
channel_id: config.channel_id,
publish_tx,
_drive_task: Arc::new(DriveTaskGuard(drive_task)),
})
@ -210,6 +214,10 @@ impl BlockPublisherTrait for ZoneSdkPublisher {
Ok(())
}
fn channel_id(&self) -> ChannelId {
self.channel_id
}
}
/// Deserialize inscription payload as a `Block` and return it's`block_id`.

View File

@ -729,26 +729,23 @@ fn extract_bridge_withdraw_data(tx: &LeeTransaction) -> Option<WithdrawArg> {
risc0_zkvm::serde::from_slice::<bridge_core::Instruction, u32>(&message.instruction_data)
.ok()?;
match instruction {
bridge_core::Instruction::Withdraw {
amount,
bedrock_account_pk,
} => {
let recipient_pk =
logos_blockchain_key_management_system_service::keys::ZkPublicKey::from(
BigUint::from_bytes_le(&bedrock_account_pk),
);
let bridge_core::Instruction::Withdraw {
amount,
bedrock_account_pk,
} = instruction
else {
return None;
};
Some(WithdrawArg {
outputs: logos_blockchain_core::mantle::ledger::Outputs::new(
logos_blockchain_core::mantle::Note::new(amount, recipient_pk),
),
})
}
bridge_core::Instruction::Deposit { .. } => unreachable!(
"Deposit instructions from users should never pass validation, and thus should never be seen here"
let recipient_pk = logos_blockchain_key_management_system_service::keys::ZkPublicKey::from(
BigUint::from_bytes_le(&bedrock_account_pk),
);
Some(WithdrawArg {
outputs: logos_blockchain_core::mantle::ledger::Outputs::new(
logos_blockchain_core::mantle::Note::new(amount, recipient_pk),
),
}
})
}
fn withdraw_event_reconciliation_key(

View File

@ -2,6 +2,7 @@ use std::time::Duration;
use anyhow::Result;
use common::block::Block;
use logos_blockchain_core::mantle::ops::channel::ChannelId;
use logos_blockchain_key_management_system_service::keys::Ed25519Key;
use logos_blockchain_zone_sdk::sequencer::WithdrawArg;
@ -16,11 +17,13 @@ use crate::{
pub type SequencerCoreWithMockClients = crate::SequencerCore<MockBlockPublisher>;
#[derive(Clone)]
pub struct MockBlockPublisher;
pub struct MockBlockPublisher {
channel_id: ChannelId,
}
impl BlockPublisherTrait for MockBlockPublisher {
async fn new(
_config: &BedrockConfig,
config: &BedrockConfig,
_bedrock_signing_key: Ed25519Key,
_resubmit_interval: Duration,
_initial_checkpoint: Option<SequencerCheckpoint>,
@ -29,7 +32,9 @@ impl BlockPublisherTrait for MockBlockPublisher {
_on_deposit_event: OnDepositEventSink,
_on_withdraw_event: OnWithdrawEventSink,
) -> Result<Self> {
Ok(Self)
Ok(Self {
channel_id: config.channel_id,
})
}
async fn publish_block(
@ -39,4 +44,8 @@ impl BlockPublisherTrait for MockBlockPublisher {
) -> Result<()> {
Ok(())
}
fn channel_id(&self) -> ChannelId {
self.channel_id
}
}

View File

@ -11,3 +11,6 @@ workspace = true
common.workspace = true
lee.workspace = true
lee_core.workspace = true
hex.workspace = true
serde_with.workspace = true

View File

@ -1,5 +1,28 @@
//! Reexports of types used by sequencer rpc specification.
use std::{fmt::Display, str::FromStr};
pub use common::{HashType, block::Block, transaction::LeeTransaction};
pub use lee::{Account, AccountId, ProgramId};
pub use lee_core::{BlockId, Commitment, MembershipProof, account::Nonce};
use serde_with::{DeserializeFromStr, SerializeDisplay};
#[derive(Debug, Clone, PartialEq, Eq, Hash, SerializeDisplay, DeserializeFromStr)]
pub struct ChannelId(pub [u8; 32]);
impl Display for ChannelId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let hex_string = hex::encode(self.0);
write!(f, "{hex_string}")
}
}
impl FromStr for ChannelId {
type Err = hex::FromHexError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut bytes = [0_u8; 32];
hex::decode_to_slice(s, &mut bytes)?;
Ok(Self(bytes))
}
}

View File

@ -6,8 +6,8 @@ use jsonrpsee::types::ErrorObjectOwned;
#[cfg(feature = "client")]
pub use jsonrpsee::{core::ClientError, http_client::HttpClientBuilder as SequencerClientBuilder};
use sequencer_service_protocol::{
Account, AccountId, Block, BlockId, Commitment, HashType, LeeTransaction, MembershipProof,
Nonce, ProgramId,
Account, AccountId, Block, BlockId, ChannelId, Commitment, HashType, LeeTransaction,
MembershipProof, Nonce, ProgramId,
};
#[cfg(all(not(feature = "server"), not(feature = "client")))]
@ -88,5 +88,8 @@ pub trait Rpc {
#[method(name = "getProgramIds")]
async fn get_program_ids(&self) -> Result<BTreeMap<String, ProgramId>, ErrorObjectOwned>;
#[method(name = "getChannelId")]
async fn get_channel_id(&self) -> Result<ChannelId, ErrorObjectOwned>;
// =============================================================================================
}

View File

@ -12,7 +12,8 @@ use sequencer_core::{
DbError, SequencerCore, TransactionOrigin, block_publisher::BlockPublisherTrait,
};
use sequencer_service_protocol::{
Account, AccountId, Block, BlockId, Commitment, HashType, MembershipProof, Nonce, ProgramId,
Account, AccountId, Block, BlockId, ChannelId, Commitment, HashType, MembershipProof, Nonce,
ProgramId,
};
use tokio::sync::Mutex;
@ -176,6 +177,11 @@ impl<BC: BlockPublisherTrait + Send + 'static> sequencer_service_rpc::RpcServer
);
Ok(program_ids)
}
async fn get_channel_id(&self) -> Result<ChannelId, ErrorObjectOwned> {
let channel_id = self.sequencer.lock().await.block_publisher().channel_id();
Ok(ChannelId(*channel_id.as_ref()))
}
}
fn internal_error(err: &DbError) -> ErrorObjectOwned {

View File

@ -265,7 +265,7 @@ pub unsafe extern "C" fn wallet_ffi_get_all_labels_for_account(
.storage()
.labels_for_account(account_id_with_privacy.into())
{
let Ok(label_c) = CString::from_str(label.inner()) else {
let Ok(label_c) = CString::from_str(label.as_ref()) else {
print_error(format!("Failed to cast label into C string: {label}"));
return LabelList::error(WalletFfiError::InternalError);
};

View File

@ -19,9 +19,10 @@ impl Label {
pub fn new(label: impl ToString) -> Self {
Self(label.to_string())
}
}
#[must_use]
pub fn inner(&self) -> &str {
impl AsRef<str> for Label {
fn as_ref(&self) -> &str {
&self.0
}
}