Read a `.ixprof`, partition into `num_shards` shards, and emit a manifest with per-shard cost metrics, foreign-block sets, and (delta-based) assumption roots. Optionally writes the manifest (`.ixes`). Returns a what-if report. The assumption root here is the Merkle root over the foreign *blocks* whose bodies the shard must ingress (the cost-model-tight set). The fully-sound variant for proof gene
( esp_path: &str, num_shards: usize, balance: f64, parallelism: usize, out_path: Option<&str>, )
| 1522 | fn u32(&mut self) -> Result<u32, String> { |
| 1523 | Ok(u32::from_le_bytes(self.take(4)?.try_into().unwrap())) |
| 1524 | } |
| 1525 | fn u64(&mut self) -> Result<u64, String> { |
| 1526 | Ok(u64::from_le_bytes(self.take(8)?.try_into().unwrap())) |
| 1527 | } |
| 1528 | fn u128(&mut self) -> Result<u128, String> { |
| 1529 | Ok(u128::from_le_bytes(self.take(16)?.try_into().unwrap())) |
| 1530 | } |
| 1531 | fn addr(&mut self) -> Result<Address, String> { |
| 1532 | Address::from_slice(self.take(32)?).map_err(|_| "bad address".into()) |
| 1533 | } |
| 1534 | fn addrs(&mut self) -> Result<Vec<Address>, String> { |
| 1535 | let n = self.u32()? as usize; |
| 1536 | let mut v = Vec::with_capacity(n); |
| 1537 | for _ in 0..n { |
| 1538 | v.push(self.addr()?); |
| 1539 | } |
| 1540 | Ok(v) |
| 1541 | } |
| 1542 | } |
| 1543 | |
| 1544 | /// Read a `.ixprof`, partition into `num_shards` shards, and emit a manifest with |
| 1545 | /// per-shard cost metrics, foreign-block sets, and (delta-based) assumption |
| 1546 | /// roots. Optionally writes the manifest (`.ixes`). Returns a what-if report. |
| 1547 | /// |
| 1548 | /// The assumption root here is the Merkle root over the foreign *blocks* whose |
| 1549 | /// bodies the shard must ingress (the cost-model-tight set). The fully-sound |
| 1550 | /// variant for proof generation uses the static reference closure over member |
| 1551 | /// constants and additionally needs the `.ixe`; see `plans/sharding.md` §4. |
| 1552 | pub fn shard_esp( |
| 1553 | esp_path: &str, |
| 1554 | num_shards: usize, |
| 1555 | balance: f64, |
| 1556 | parallelism: usize, |
| 1557 | out_path: Option<&str>, |
| 1558 | ) -> Result<String, String> { |
| 1559 | let bytes = |
| 1560 | std::fs::read(esp_path).map_err(|e| format!("read {esp_path}: {e}"))?; |
| 1561 | let profile = BlockProfile::from_bytes(&bytes) |
| 1562 | .map_err(|e| format!("parse {esp_path}: {e}"))?; |
| 1563 | let h = Hypergraph::from_profile(&profile); |
| 1564 | let (shard_of, tree) = h.partition_with_tree(num_shards, balance); |
| 1565 | let mut manifest = |
| 1566 | ShardManifest::build(&profile, &shard_of, num_shards).with_tree(tree); |
| 1567 | for shard in &mut manifest.shards { |
| 1568 | shard.assumption_root = |
| 1569 | ixon::merkle::merkle_root_canonical(&shard.foreign_blocks); |
| 1570 | } |
| 1571 | if let Some(op) = out_path { |
| 1572 | std::fs::write(op, manifest.to_bytes()) |
| 1573 | .map_err(|e| format!("write {op}: {e}"))?; |
| 1574 | } |
| 1575 | // The largest single block's heartbeats is the *floor* on achievable |
| 1576 | // per-shard balance: a mutual block is atomic and cannot be split, so no |