Skip to content

Commit 65436c5

Browse files
committed
feat: make storage node work e2e with cli
1 parent 374610c commit 65436c5

35 files changed

Lines changed: 4848 additions & 352 deletions

File tree

Cargo.lock

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

fendermint/actors/blobs/src/actor.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,12 @@ impl BlobsActor {
103103
Ok(InvokeContractReturn {
104104
output_data: Vec::new(),
105105
})
106+
} else if sol_blobs::is_finalize_blob_call(&input_data) {
107+
let params = sol_blobs::parse_finalize_blob_input(&input_data, rt)?;
108+
Self::finalize_blob(rt, params)?;
109+
Ok(InvokeContractReturn {
110+
output_data: Vec::new(),
111+
})
106112
} else if sol_blobs::can_handle(&input_data) {
107113
let output_data = match sol_blobs::parse_input(&input_data)? {
108114
sol_blobs::Calls::addBlob(call) => {

fendermint/actors/blobs/src/sol_facade/blobs.rs

Lines changed: 119 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
use fendermint_actor_blobs_shared::{
66
blobs::{
7-
AddBlobParams, Blob, BlobStatus, DeleteBlobParams, GetBlobParams, OverwriteBlobParams,
8-
TrimBlobExpiriesParams,
7+
AddBlobParams, Blob, BlobStatus, DeleteBlobParams, FinalizeBlobParams, GetBlobParams,
8+
OverwriteBlobParams, SubscriptionId, TrimBlobExpiriesParams,
99
},
1010
bytes::B256,
1111
execution::{
@@ -120,6 +120,8 @@ pub fn parse_input(input: &ipc_storage_actor_sdk::evm::InputData) -> Result<Call
120120
pub const REGISTER_NODE_OPERATOR_SELECTOR: [u8; 4] = [0x71, 0x3b, 0x10, 0xcf];
121121
pub const GET_OPERATOR_INFO_SELECTOR: [u8; 4] = [0x27, 0xd9, 0xab, 0x5d];
122122
pub const GET_ACTIVE_OPERATORS_SELECTOR: [u8; 4] = [0x64, 0xbd, 0xc6, 0x7e];
123+
/// keccak256("finalizeBlob(bytes32,address,bytes32,uint64,string,uint8,bytes,uint128)")
124+
pub const FINALIZE_BLOB_SELECTOR: [u8; 4] = [0xf6, 0x94, 0x17, 0x21];
123125

124126
pub struct RegisterNodeOperatorInvokeCall {
125127
pub bls_pubkey: Vec<u8>,
@@ -183,6 +185,10 @@ pub fn is_fail_job_call(input: &ipc_storage_actor_sdk::evm::InputData) -> bool {
183185
input.selector() == FAIL_JOB_SELECTOR
184186
}
185187

188+
pub fn is_finalize_blob_call(input: &ipc_storage_actor_sdk::evm::InputData) -> bool {
189+
input.selector() == FINALIZE_BLOB_SELECTOR
190+
}
191+
186192
pub fn parse_register_node_operator_input(
187193
input: &ipc_storage_actor_sdk::evm::InputData,
188194
) -> Result<RegisterNodeOperatorInvokeCall, ActorError> {
@@ -309,6 +315,54 @@ pub fn parse_fail_job_input(
309315
})
310316
}
311317

318+
/// Parses ABI-encoded calldata for `finalizeBlob(bytes32,address,bytes32,uint64,string,uint8,bytes,uint128)`.
319+
pub fn parse_finalize_blob_input(
320+
input: &ipc_storage_actor_sdk::evm::InputData,
321+
rt: &impl Runtime,
322+
) -> Result<FinalizeBlobParams, ActorError> {
323+
let calldata = input.calldata();
324+
// 8 head slots: source(32) + subscriber(32) + blobHash(32) + size(32)
325+
// + string_offset(32) + status(32) + bytes_offset(32) + signerBitmap(32)
326+
if calldata.len() < 32 * 8 {
327+
return Err(actor_error!(
328+
illegal_argument,
329+
"invalid finalizeBlob call: input too short"
330+
));
331+
}
332+
333+
let source = decode_b256_word(calldata, 0)?;
334+
let subscriber_h160 = decode_address_word(calldata, 32)?;
335+
let subscriber: Address = subscriber_h160.into();
336+
let subscriber = rt
337+
.resolve_address(&subscriber)
338+
.map(Address::new_id)
339+
.unwrap_or(subscriber);
340+
let hash = decode_b256_word(calldata, 64)?;
341+
let size = decode_u64_word(calldata, 96)?;
342+
let subscription_id_str = decode_dynamic_string(calldata, decode_offset(calldata, 128)?)?;
343+
let subscription_id: SubscriptionId = subscription_id_str.try_into().map_err(|e| {
344+
actor_error!(
345+
illegal_argument,
346+
format!("invalid finalizeBlob call: bad subscription id: {}", e)
347+
)
348+
})?;
349+
let status_u8 = decode_u8_word(calldata, 160)?;
350+
let status = solidity_enum_to_blob_status(status_u8)?;
351+
let aggregated_signature = decode_dynamic_bytes(calldata, decode_offset(calldata, 192)?)?;
352+
let signer_bitmap = decode_u128_word(calldata, 224)?;
353+
354+
Ok(FinalizeBlobParams {
355+
source,
356+
subscriber,
357+
hash,
358+
size,
359+
id: subscription_id,
360+
status,
361+
aggregated_signature,
362+
signer_bitmap,
363+
})
364+
}
365+
312366
impl From<CreateJobInvokeCall> for CreateJobParams {
313367
fn from(value: CreateJobInvokeCall) -> Self {
314368
CreateJobParams {
@@ -507,6 +561,69 @@ fn decode_b256_word(calldata: &[u8], at: usize) -> Result<B256, ActorError> {
507561
Ok(B256(out))
508562
}
509563

564+
fn decode_address_word(calldata: &[u8], at: usize) -> Result<H160, ActorError> {
565+
let end = at + 32;
566+
if end > calldata.len() {
567+
return Err(actor_error!(
568+
illegal_argument,
569+
"invalid call: malformed address word"
570+
));
571+
}
572+
let word = &calldata[at..end];
573+
if word[..12].iter().any(|b| *b != 0) {
574+
return Err(actor_error!(
575+
illegal_argument,
576+
"invalid call: malformed address"
577+
));
578+
}
579+
Ok(H160::from_slice(&word[12..32]))
580+
}
581+
582+
fn decode_u8_word(calldata: &[u8], at: usize) -> Result<u8, ActorError> {
583+
let end = at + 32;
584+
if end > calldata.len() {
585+
return Err(actor_error!(illegal_argument, "invalid call: malformed word"));
586+
}
587+
let word = &calldata[at..end];
588+
if word[..31].iter().any(|b| *b != 0) {
589+
return Err(actor_error!(
590+
illegal_argument,
591+
"invalid call: uint8 value too large"
592+
));
593+
}
594+
Ok(word[31])
595+
}
596+
597+
fn decode_u128_word(calldata: &[u8], at: usize) -> Result<u128, ActorError> {
598+
let end = at + 32;
599+
if end > calldata.len() {
600+
return Err(actor_error!(illegal_argument, "invalid call: malformed word"));
601+
}
602+
let word = &calldata[at..end];
603+
if word[..16].iter().any(|b| *b != 0) {
604+
return Err(actor_error!(
605+
illegal_argument,
606+
"invalid call: uint128 value too large"
607+
));
608+
}
609+
let mut n = [0u8; 16];
610+
n.copy_from_slice(&word[16..32]);
611+
Ok(u128::from_be_bytes(n))
612+
}
613+
614+
fn solidity_enum_to_blob_status(value: u8) -> Result<BlobStatus, ActorError> {
615+
match value {
616+
0 => Ok(BlobStatus::Added),
617+
1 => Ok(BlobStatus::Pending),
618+
2 => Ok(BlobStatus::Resolved),
619+
3 => Ok(BlobStatus::Failed),
620+
_ => Err(actor_error!(
621+
illegal_argument,
622+
format!("invalid BlobStatus enum value: {}", value)
623+
)),
624+
}
625+
}
626+
510627
fn abi_word_from_usize(value: usize) -> [u8; 32] {
511628
let mut word = [0u8; 32];
512629
word[24..32].copy_from_slice(&(value as u64).to_be_bytes());

fendermint/rpc/src/message.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,11 @@ impl SignedMessageFactory {
195195
self.inner.address()
196196
}
197197

198+
/// Set the sequence to an arbitrary value, e.g. after resyncing from chain state.
199+
pub fn set_sequence(&mut self, sequence: u64) {
200+
self.inner.set_sequence(sequence);
201+
}
202+
198203
/// Transfer tokens to another account.
199204
pub fn transfer(
200205
&mut self,

fendermint/vm/topdown/src/finality/null.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
use crate::finality::{ensure_sequential, ParentViewPayload};
55
use crate::{BlockHash, BlockHeight, Config, Error, IPCParentFinality, SequentialKeyCache};
6-
use async_stm::{abort, atomically, Stm, StmResult, TVar};
6+
use async_stm::{abort, Stm, StmResult, TVar};
77
use ipc_api::cross::IpcEnvelope;
88
use ipc_api::staking::PowerChangeRequest;
99
use std::cmp::min;

init-sub.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,28 @@
44

55
# deploy:
66
# url: "https://api.calibration.node.glif.io/rpc/v1"
7-
# from: "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"
7+
# from: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"
88
# chain-id: 314159
99

1010
node-topdown-mode: legacy
1111

1212
create:
1313
parent: /r314159
1414
chain-id: 314159
15-
from: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266
15+
from: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
1616
min-validator-stake: 1.0
1717
min-validators: 1
1818
bottomup-check-period: 50
1919
permission-mode: federated
2020
supply-source-kind: native
2121
min-cross-msg-fee: 0.1
22-
genesis-subnet-ipc-contracts-owner: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266
22+
genesis-subnet-ipc-contracts-owner: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
2323
parent-filecoin-rpc: "https://api.calibration.node.glif.io/rpc/v1"
2424

2525
activate:
2626
mode: federated
2727
validator-pubkeys:
28-
- 0x048318535b54105d4a7aae60c08fc45f9687181b4fdfc625bd1a753fa7397fed753547f11ca8696646f2f3acb08e31016afac23e630c5d11f59f61fef57b0d2aa5
28+
- 0x04d64f81dcff5f31e67263e3d2df35b0a412bb11935ae6b897018a59bf5d6f9e99ad02cbedd2bfcfbd2a56f02f6812c9f978536496e972fe2fbbf96e8743fb28dc
2929
validator-power:
3030
- 1
3131

ipc-storage/ipc-decentralized-storage/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ urlencoding.workspace = true
2929
blake3.workspace = true
3030

3131
# HTTP client dependencies
32-
reqwest = { version = "0.11", features = ["json"] }
32+
reqwest = { version = "0.11", features = ["json", "multipart"] }
33+
tempfile = "3.8"
3334

3435
# CLI dependencies
3536
clap = { workspace = true, features = ["derive"] }

ipc-storage/ipc-decentralized-storage/src/bin/gateway.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,11 @@ async fn main() -> Result<()> {
101101
.with(tracing_subscriber::fmt::layer())
102102
.init();
103103

104+
tracing::info!(
105+
"gateway v{} (build 2026-04-01-fevm-facade)",
106+
env!("CARGO_PKG_VERSION"),
107+
);
108+
104109
let args = Args::parse();
105110

106111
// Set the network for address display (f for mainnet, t for testnet)
@@ -228,15 +233,27 @@ async fn main() -> Result<()> {
228233

229234
tracing::info!("Chain ID: {}", chain_id);
230235

236+
// Prefer delegated f410 sender: FinalizeBlob now goes through the EVM facade
237+
// (InvokeContract with ABI-encoded calldata) so it works with both f1 and f410.
231238
let (from_addr, sequence) = if let Some(sequence) = get_sequence_opt(&client, &from_f410)
232239
.await
233240
.context("failed to get delegated account sequence")?
234241
{
235242
tracing::info!("Using delegated sender (f410) for gateway transactions");
236243
(from_f410, sequence)
244+
} else if let Some(sequence) = get_sequence_opt(&client, &from_f1)
245+
.await
246+
.context("failed to get native account sequence")?
247+
{
248+
tracing::info!(
249+
"Delegated f410 sender {} is not on-chain; falling back to native f1 sender",
250+
from_f410
251+
);
252+
(from_f1, sequence)
237253
} else {
238254
anyhow::bail!(
239-
"delegated sender {} not found on-chain; cross-fund this delegated address and retry (native f1 {} is intentionally not used)",
255+
"neither delegated sender {} nor native sender {} found on-chain; \
256+
fund the delegated (f410) address for gateway transaction signing",
240257
from_f410, from_f1
241258
);
242259
};

0 commit comments

Comments
 (0)