diff --git a/.gitignore b/.gitignore index b3177a0..0cd47a7 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,7 @@ target/ bin/ # Tezos wallet -.tezos-client \ No newline at end of file +.tezos-client + +# Jupyter checkpoints +.ipynb_checkpoints \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 02bcdd3..ba70e27 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1540,8 +1540,10 @@ dependencies = [ "anyhow", "bcs", "hex", + "narwhal-config", "pre-block", "serde", + "serde_json", "serde_yaml 0.9.29", "tezos-smart-rollup-core", "tezos-smart-rollup-encoding", @@ -4601,6 +4603,7 @@ dependencies = [ "serde", "serde_json", "tezos-smart-rollup-encoding", + "tezos_crypto_rs", "tezos_data_encoding", "tokio", "tonic", diff --git a/crates/pre-block/src/validator.rs b/crates/pre-block/src/validator.rs index f455f72..fdd22ed 100644 --- a/crates/pre-block/src/validator.rs +++ b/crates/pre-block/src/validator.rs @@ -22,7 +22,7 @@ pub fn validate_certificate_signature( .iter() .any(|x| (*x as usize) >= config.authorities.len()) { - anyhow::bail!("Unknown authority"); + anyhow::bail!("Unknown authority {:?}", cert.signers); } if cert.signers.len() < config.quorum_threshold() { diff --git a/docker/operator/Dockerfile b/docker/operator/Dockerfile index 323c63a..5065561 100644 --- a/docker/operator/Dockerfile +++ b/docker/operator/Dockerfile @@ -11,7 +11,7 @@ COPY . ./ RUN make build-operator FROM alpine:3.15 AS rollup -RUN apk --no-cache add binutils gcc gmp libgmpxx hidapi libc-dev libev libffi sudo +RUN apk --no-cache add binutils gcc gmp libgmpxx hidapi libc-dev libev libffi sudo jq ARG OCTEZ_PROTO COPY --from=octez /usr/local/bin/octez-smart-rollup-node-${OCTEZ_PROTO} /usr/bin/octez-smart-rollup-node COPY --from=octez /usr/local/bin/octez-client /usr/bin/octez-client diff --git a/docker/operator/entrypoint.sh b/docker/operator/entrypoint.sh index 258c88d..1a313f1 100644 --- a/docker/operator/entrypoint.sh +++ b/docker/operator/entrypoint.sh @@ -11,13 +11,19 @@ rollup_dir="/root/.tezos-smart-rollup-node" endpoint=$NODE_URI faucet="https://faucet.$NETWORK.teztnets.xyz" +min_batch_elements=1 +max_batch_elements=15 +min_batch_size=1 +max_batch_size=32607 +batcher_config="{ \"batcher\": { \"min_batch_elements\": $min_batch_elements, \"max_batch_elements\": $max_batch_elements, \"min_batch_size\": $min_batch_size, \"max_batch_size\": $max_batch_size }}" + if [ -z "$NODE_URI" ]; then if [ -z "$NETWORK" ]; then echo "NETWORK is not set" exit 1 fi - endpoint="https://rpc.$NETWORK.teztnets.xyz" - #endpoint="https://$NETWORK.ecadinfra.com" + #endpoint="https://rpc.$NETWORK.teztnets.xyz" + endpoint="https://$NETWORK.ecadinfra.com" #endpoint="https://rpc.tzkt.io/$NETWORK" fi @@ -35,6 +41,11 @@ import_key() { fi } +update_config() { + cmd="jq '. += $batcher_config' $rollup_dir/config.json" + echo "$($cmd)" > $rollup_dir/config.json +} + run_node() { import_key @@ -65,7 +76,10 @@ deploy_rollup() { echo "Found existing rollup config" if [ "$1" == "--force" ]; then echo "Overriding with new kernel" - rm -rf "$rollup_dir/*" + rm -rf "$rollup_dir/context" + rm -rf "$rollup_dir/storage" + rm "$rollup_dir/metadata" + rm "$rollup_dir/config.json" octez-client --endpoint "$endpoint" forget all smart rollups --force else exit 0 @@ -116,6 +130,9 @@ case $command in send_message) send_message $@ ;; + update_config) + update_config + ;; *) cat <, } -fn main() -> std::result::Result<(), Box> { - let mut output_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - output_path.push("../bin/kernel_config.yaml"); - +fn mock_setup() -> std::result::Result> { let fixture = NarwhalFixture::default(); let epoch = 0; let value = bcs::to_bytes(&fixture.authorities())?; - let kernel_setup = KernelSetup { + let setup = KernelSetup { instructions: vec![Instruction { set: Set { value: hex::encode(&value), @@ -39,6 +37,43 @@ fn main() -> std::result::Result<(), Box> { }, }], }; + Ok(setup) +} + +fn real_setup() -> std::result::Result> { + let mut committee = Committee::import("../launcher/defaults/committee.json")?; + committee.load(); + + let authorities: Vec = committee + .authorities() + .map(|auth| auth.protocol_key_bytes().0.to_vec()) + .collect(); + let value = bcs::to_bytes(&authorities)?; + + let setup = KernelSetup { + instructions: vec![ + // Instruction { + // set: Set { + // value: hex::encode(&value), + // to: format!("/authorities/{}", committee.epoch()), + // }, + // }, + Instruction { + set: Set { + value: hex::encode(&(0u64.to_be_bytes())), + to: format!("/index"), + }, + }, + ], + }; + Ok(setup) +} + +fn main() -> std::result::Result<(), Box> { + let mut output_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + output_path.push("../bin/kernel_config.yaml"); + + let kernel_setup = real_setup()?; let file = std::fs::File::create(output_path).expect("Could not create file"); serde_yaml::to_writer(file, &kernel_setup)?; diff --git a/kernel/src/lib.rs b/kernel/src/lib.rs index e2f6733..bf40c6c 100644 --- a/kernel/src/lib.rs +++ b/kernel/src/lib.rs @@ -15,11 +15,30 @@ mod storage; #[cfg(test)] mod tests; -use storage::{read_authorities, read_head, write_authorities, write_block, write_head, Store}; +use storage::{ + read_authorities, read_chunk, read_head, write_authorities, write_block, write_chunk, + write_head, Store, +}; const LEVELS_PER_EPOCH: u32 = 100; -pub fn apply_pre_block(host: &mut Host, pre_block: PreBlock) { +fn reconstruct_batch(host: &impl Runtime, header: &[u8]) -> anyhow::Result> { + if header.len() % 32 != 0 { + anyhow::bail!("DA header size must be multiple of 32 (chunk digest size)"); + } + + let mut batch: Vec> = Vec::with_capacity(header.len() / 32); + for hash in header.chunks(32) { + match read_chunk(host, hash) { + Some(chunk) => batch.push(chunk), + None => anyhow::bail!("Missing chunk {}", hex::encode(hash)), + } + } + + Ok(batch.concat()) +} + +fn apply_pre_block(host: &mut Host, pre_block: PreBlock) { let mut block: Vec> = Vec::new(); for tx in pre_block.into_transactions() { let tx_hash = digest_256(&tx).unwrap(); @@ -32,11 +51,23 @@ pub fn apply_pre_block(host: &mut Host, pre_block: PreBlock) { } fn process_external_message(host: &mut Host, contents: &[u8], level: u32) { - let pre_block: PreBlock = bcs::from_bytes(contents).expect("Failed to parse consensus output"); + let pre_block: PreBlock = match bcs::from_bytes(contents) { + Ok(value) => value, + Err(err) => { + host.write_debug(&format!("Failed to parse pre-block: {}", err)); + return; + } + }; host.write_debug(&format!("Incoming pre-block #{}", pre_block.index())); let epoch = 0; // (level % LEVELS_PER_EPOCH) as u64; - let authorities = read_authorities(host, epoch); + let authorities = match read_authorities(host, epoch) { + Some(value) => value, + None => { + host.write_debug(&format!("Authorities for epoch #{epoch} not initialized")); + return; + } + }; let config = DsnConfig::new(epoch, authorities); { @@ -57,54 +88,79 @@ fn process_external_message(host: &mut Host, contents: &[u8], lev } fn process_internal_message(host: &mut Host, contents: &[u8]) { - let config: DsnConfig = bcs::from_bytes(contents).expect("Failed to parse authorities"); + let config: DsnConfig = match bcs::from_bytes(contents) { + Ok(value) => value, + Err(err) => { + host.write_debug(&format!("Failed to parse DSN config: {}", err)); + return; + } + }; write_authorities(host, config.epoch, &config.authorities); + host.write_debug(&format!( + "Authorities for epoch #{0} initialized", + config.epoch + )); } pub fn kernel_loop(host: &mut Host) { + host.write_debug(&format!("Kernel loop started")); let smart_rollup_address = host.reveal_metadata().address(); - let mut chunked_message: Vec = Vec::new(); loop { match host.read_input().expect("Failed to read inbox") { Some(message) => { let bytes = message.as_ref(); - match InboxMessage::::parse(bytes).expect("Failed to parse message") - { - (_, InboxMessage::External(payload)) => { + match InboxMessage::::parse(bytes).ok() { + Some((_, InboxMessage::External(payload))) => { match ExternalMessageFrame::parse(payload) { Ok(ExternalMessageFrame::Targetted { address, contents }) if *address.hash() == smart_rollup_address => { + host.write_debug(&format!("Incoming external message")); match contents { - [0u8, chunk @ ..] => chunked_message.extend_from_slice(chunk), - [1u8, chunk @ ..] => { - chunked_message.extend_from_slice(chunk); - process_external_message( - host, - &chunked_message, - message.level, - ); - chunked_message.clear(); + [0u8, chunk @ ..] => { + let hash = write_chunk(host, chunk); + host.write_debug(&format!( + "Stashing chunk: {}", + hex::encode(&hash) + )); } + [1u8, header @ ..] => match reconstruct_batch(host, header) { + Ok(contents) => { + process_external_message( + host, + &contents, + message.level, + ); + } + Err(err) => { + host.write_debug(&format!("Invalid batch: {}", err)); + } + }, _ => panic!("Unexpected message tag"), } } _ => { /* not for us */ } } } - (_, InboxMessage::Internal(msg)) => { + Some((_, InboxMessage::Internal(msg))) => { match msg { InternalInboxMessage::Transfer(transfer) => { + host.write_debug(&format!( + "Incoming internal message: {}", + hex::encode(&transfer.payload.0) + )); process_internal_message(host, &transfer.payload.0); } _ => { /* system messages */ } } } + None => { /* not for us */ } } } _ => break, } } + host.write_debug(&format!("Kernel loop exited")); } tezos_smart_rollup_entrypoint::kernel_entry!(kernel_loop); diff --git a/kernel/src/storage.rs b/kernel/src/storage.rs index 6a57b5e..9f97f75 100644 --- a/kernel/src/storage.rs +++ b/kernel/src/storage.rs @@ -3,6 +3,7 @@ // SPDX-License-Identifier: MIT use pre_block::{Digest, PreBlockStore, PublicKey}; +use tezos_crypto_rs::blake2b::digest_256; use tezos_smart_rollup_host::{ path::{concat, OwnedPath, Path, RefPath}, runtime::Runtime, @@ -10,6 +11,7 @@ use tezos_smart_rollup_host::{ const HEAD_PATH: RefPath = RefPath::assert_from(b"/head"); const BLOCKS_PATH: RefPath = RefPath::assert_from(b"/blocks"); +const CHUNKS_PATH: RefPath = RefPath::assert_from(b"/chunks"); const INDEX_PATH: RefPath = RefPath::assert_from(b"/index"); const AUTHORITIES_PATH: RefPath = RefPath::assert_from(b"/authorities"); const CERTIFICATES_PATH: RefPath = RefPath::assert_from(b"/certificates"); @@ -68,11 +70,16 @@ fn authorities_path(epoch: u64) -> OwnedPath { concat(&AUTHORITIES_PATH, &suffix).unwrap() } -pub fn read_authorities(host: &Host, epoch: u64) -> Vec { - let bytes = host - .store_read_all(&authorities_path(epoch)) - .expect("Failed to read authorities"); - bcs::from_bytes(&bytes).expect("Failed to parse authorities") +pub fn read_authorities(host: &Host, epoch: u64) -> Option> { + let path = authorities_path(epoch); + if host.store_has(&path).unwrap().is_some() { + let bytes = host + .store_read_all(&path) + .expect("Failed to read authorities"); + Some(bcs::from_bytes(&bytes).expect("Failed to parse authorities")) + } else { + None + } } pub fn write_authorities(host: &mut Host, epoch: u64, authorities: &[PublicKey]) { @@ -104,3 +111,24 @@ pub fn write_block(host: &mut Host, level: u32, block: Vec OwnedPath { + let suffix = OwnedPath::try_from(format!("/{}", hex::encode(hash))).unwrap(); + concat(&CHUNKS_PATH, &suffix).unwrap() +} + +pub fn write_chunk(host: &mut impl Runtime, data: &[u8]) -> Vec { + let hash = digest_256(data).expect("Failed to get chunk digest"); + host.store_write_all(&chunk_path(&hash), data).unwrap(); + hash +} + +pub fn read_chunk(host: &impl Runtime, hash: &[u8]) -> Option> { + let path = chunk_path(hash); + if host.store_has(&path).unwrap().is_some() { + let bytes = host.store_read_all(&path).unwrap(); + Some(bytes) + } else { + None + } +} diff --git a/kernel/tests/committee.ipynb b/kernel/tests/committee.ipynb new file mode 100644 index 0000000..257d4ba --- /dev/null +++ b/kernel/tests/committee.ipynb @@ -0,0 +1,251 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 2, + "id": "ad7e0f3a", + "metadata": {}, + "outputs": [], + "source": [ + "from pytezos import pytezos, ContractInterface\n", + "from pytezos.operation.result import OperationResult" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "e2fbd32f", + "metadata": {}, + "outputs": [], + "source": [ + "pytezos = pytezos.using('nairobinet')" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "0d8de4c2", + "metadata": {}, + "outputs": [], + "source": [ + "ci = ContractInterface.from_file('governance.tz')" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "46df1447", + "metadata": {}, + "outputs": [], + "source": [ + "tx = pytezos.origination(ci.script()).send()" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "26dff195", + "metadata": {}, + "outputs": [], + "source": [ + "opg = pytezos.shell.blocks[-50:].find_operation(tx.hash())" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "ca202d66", + "metadata": {}, + "outputs": [], + "source": [ + "address = OperationResult.from_operation_group(opg)[0].originated_contracts[0]" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "a2dfb9bc", + "metadata": {}, + "outputs": [], + "source": [ + "ADDRESS = 'KT1S2uEmcLsTP9tMr5KQkuHFARTyTijL7UEV'" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "a5a17426", + "metadata": {}, + "outputs": [], + "source": [ + "dao = pytezos.contract(ADDRESS)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "81dedfef", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "\n", + "\n", + "Properties\n", + ".key\t\ttz1grSQDByRpnVs7sPtaprNZRp531ZKz6Jmm\n", + ".shell\t\t['https://rpc.tzkt.io/nairobinet']\n", + ".address\tKT1S2uEmcLsTP9tMr5KQkuHFARTyTijL7UEV\n", + ".block_id\thead\n", + ".entrypoint\tdefault\n", + "\n", + "Builtin\n", + "(*args, **kwargs)\t# build transaction parameters (see typedef)\n", + "\n", + "Typedef\n", + "$default:\n", + "\t( address, bytes )\n", + "\n", + "$address:\n", + "\tstr /* Base58 encoded `tz` or `KT` address */\n", + "\n", + "$bytes:\n", + "\tstr /* Hex string */ ||\n", + "\tbytes /* Python byte string */\n", + "\n", + "\n", + "Helpers\n", + ".decode()\n", + ".encode()" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dao.default" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "abcd7d4d", + "metadata": {}, + "outputs": [], + "source": [ + "ROLLUP_ADDRESS = 'sr1MYBhyxFuxVxFWSdWMVSgVG8CHW2AWnbUn'\n", + "DSN_CONFIG = '000000000000000007608217efb0d19cb9960f856319d25c3d57c6829661533f489c7db7e33d15a0195012085a2f3874b91e43ed910fb2975d6918db3d63193bf39af40c69ce5759cad9794fa64b55f7c9d092498aaf4f81ba7a4b865e8f51cd69a69669c8cd8e9304ca608f613bf3261ebe81983a54cbbb75a12faf3cbe41f68a7b6586ad5000d4a2228cb867528cc15fed6573af42359a0836bb08aac00debbf0d4074e82301a1cf914e78fdc13d80852b39d88b24c4be9a77fbd73b0f8bcff5e44e64896691e33bc1af60951406b2856fdabbe0e6bdc72c5b7aac777b7d24fc9022b5bc0078beba5b5d8a4e7b54c716d948db02ed6861b88d4e201970415c6e3a5512da895c2ec93fd686ec9f3bf190fe9bff5c8739446b4036d7ec8bcf3ae9937a1e954ab8d7fbea05d960a78869789da72a1e2b7b8b4703bd88b32479f0511533cc9172ba5c297bd45b26bc6239820fafca91504095153aeb8ffa08bf625e77703ab559894076789f224893593e7889cbb5db03d80180aeb7d2e76d540e6255f4a6f03d5ba14aa244354760ac192182731d3655685699102338ce29d07a988c61bd1c64ef18b07ab69254670abfeaf8f6d114cc2994b3cb7a43987110519d019a2fc6a5ad4f6c0afbb4aafaeb46a6cf4dbae2d548aa23036065fa1d2871cd8e7a887756801cc94cf7eb0f5060b6c9baa74c09e784b9570f63652e42b924fb76edfc0d796dc4326b200ebf3bea90c373a68fb73b761bf37a6b8bbc5829016226c8a21767eeb7b625b0390933da52b0792c0209bd736a15625168ea7b75af730ae1cd452d6b86dc0c2529b1384860b8f63f5c29dace07ca215ac250ea4afc14197c5230706b4711b6500b7bc4be11462897d80aa1e67979c5a1a196031edf06e282a03acd4dc09eebb6d4fe07d1950b4566d2fa5ab7cfb79882443068871334d2cd2a664bd5bda6c65bdc6eee01a0'" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "a67718a4", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "\n", + "\n", + "Properties\n", + ".key\t\ttz1grSQDByRpnVs7sPtaprNZRp531ZKz6Jmm\n", + ".shell\t\t['https://rpc.tzkt.io/nairobinet']\n", + ".block_id\thead\n", + "\n", + "Payload\n", + "{'branch': 'BLW1HPDPVMyX6tkPWwUsnK7obv1DVzCme85A1hbC2udT1utRTRU',\n", + " 'contents': [{'amount': '0',\n", + " 'counter': '1281',\n", + " 'destination': 'KT1S2uEmcLsTP9tMr5KQkuHFARTyTijL7UEV',\n", + " 'fee': '1100',\n", + " 'gas_limit': '1084',\n", + " 'kind': 'transaction',\n", + " 'parameters': {'entrypoint': 'default',\n", + " 'value': {'args': [{'string': 'sr1QMThifta6GxVB6roeMcwMKyJfwoXjx2Jx'},\n", + " {'bytes': '07608217efb0d19cb9960f856319d25c3d57c6829661533f489c7db7e33d15a0195012085a2f3874b91e43ed910fb2975d6918db3d63193bf39af40c69ce5759cad9794fa64b55f7c9d092498aaf4f81ba7a4b865e8f51cd69a69669c8cd8e9304ca608f613bf3261ebe81983a54cbbb75a12faf3cbe41f68a7b6586ad5000d4a2228cb867528cc15fed6573af42359a0836bb08aac00debbf0d4074e82301a1cf914e78fdc13d80852b39d88b24c4be9a77fbd73b0f8bcff5e44e64896691e33bc1af60951406b2856fdabbe0e6bdc72c5b7aac777b7d24fc9022b5bc0078beba5b5d8a4e7b54c716d948db02ed6861b88d4e201970415c6e3a5512da895c2ec93fd686ec9f3bf190fe9bff5c8739446b4036d7ec8bcf3ae9937a1e954ab8d7fbea05d960a78869789da72a1e2b7b8b4703bd88b32479f0511533cc9172ba5c297bd45b26bc6239820fafca91504095153aeb8ffa08bf625e77703ab559894076789f224893593e7889cbb5db03d80180aeb7d2e76d540e6255f4a6f03d5ba14aa244354760ac192182731d3655685699102338ce29d07a988c61bd1c64ef18b07ab69254670abfeaf8f6d114cc2994b3cb7a43987110519d019a2fc6a5ad4f6c0afbb4aafaeb46a6cf4dbae2d548aa23036065fa1d2871cd8e7a887756801cc94cf7eb0f5060b6c9baa74c09e784b9570f63652e42b924fb76edfc0d796dc4326b200ebf3bea90c373a68fb73b761bf37a6b8bbc5829016226c8a21767eeb7b625b0390933da52b0792c0209bd736a15625168ea7b75af730ae1cd452d6b86dc0c2529b1384860b8f63f5c29dace07ca215ac250ea4afc14197c5230706b4711b6500b7bc4be11462897d80aa1e67979c5a1a196031edf06e282a03acd4dc09eebb6d4fe07d1950b4566d2fa5ab7cfb79882443068871334d2cd2a664bd5bda6c65bdc6eee01a0'}],\n", + " 'prim': 'Pair'}},\n", + " 'source': 'tz1grSQDByRpnVs7sPtaprNZRp531ZKz6Jmm',\n", + " 'storage_limit': '100'}],\n", + " 'protocol': 'PtNairobiyssHuh87hEhfVBGCVrK3WnS8Z2FT4ymB5tAa4r1nQf',\n", + " 'signature': 'sigQmHq5duTrAVBmi57z6PUuSfWmLJ89o3oW4QuwuTZN6msHYbed6hMsUYGBYMKN4moqaxzV4S1NHF59fyiLgPMB4N8FdD1C'}\n", + "\n", + "Hash\n", + "opA67KNL6pG79dhQWTpKhuj9rnvN6sUzXvMju6zNMNxxGq6FvwG\n", + ".activate_account()\n", + ".autofill()\n", + ".ballot()\n", + ".binary_payload()\n", + ".delegation()\n", + ".double_baking_evidence()\n", + ".double_endorsement_evidence()\n", + ".endorsement()\n", + ".endorsement_with_slot()\n", + ".failing_noop()\n", + ".fill()\n", + ".forge()\n", + ".hash()\n", + ".inject()\n", + ".json_payload()\n", + ".message()\n", + ".operation()\n", + ".origination()\n", + ".preapply()\n", + ".proposals()\n", + ".register_global_constant()\n", + ".result()\n", + ".reveal()\n", + ".run()\n", + ".run_operation()\n", + ".seed_nonce_revelation()\n", + ".send()\n", + ".send_async()\n", + ".sign()\n", + ".smart_rollup_add_messages()\n", + ".smart_rollup_execute_outbox_message()\n", + ".transaction()\n", + ".transfer_ticket()" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "dao.default((ROLLUP_ADDRESS, AUTHORITIES)).send()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b37bfed5", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/kernel/tests/governance.tz b/kernel/tests/governance.tz new file mode 100644 index 0000000..e342188 --- /dev/null +++ b/kernel/tests/governance.tz @@ -0,0 +1,18 @@ +parameter (pair address bytes); +storage unit; +code +{ + CAR; + UNPAIR; + CONTRACT bytes; + IF_NONE { PUSH string "Invalid address"; FAILWITH } {}; + PUSH mutez 0; + DIG 2; + TRANSFER_TOKENS; + NIL operation; + SWAP; + CONS; + UNIT; + SWAP; + PAIR; +} \ No newline at end of file diff --git a/nairobi.env b/nairobi.env index b3a643c..c8e9353 100644 --- a/nairobi.env +++ b/nairobi.env @@ -1,3 +1,3 @@ -OCTEZ_TAG=v17.1 +OCTEZ_TAG=master OCTEZ_PROTO=PtNairob NETWORK=nairobinet \ No newline at end of file diff --git a/sequencer/Cargo.toml b/sequencer/Cargo.toml index 01168dd..39416e5 100644 --- a/sequencer/Cargo.toml +++ b/sequencer/Cargo.toml @@ -19,6 +19,7 @@ bytes.workspace = true bcs.workspace = true prost.workspace = true +tezos_crypto_rs = { version = "0.5.2", default-features = false } tezos_data_encoding = { version = "0.5.2" } tezos-smart-rollup-encoding = { version = "0.2.2", default_features = false, features = ["alloc", "tezos-encoding" ] } hex = "*" diff --git a/sequencer/src/da_batcher.rs b/sequencer/src/da_batcher.rs index a99727e..8629100 100644 --- a/sequencer/src/da_batcher.rs +++ b/sequencer/src/da_batcher.rs @@ -7,6 +7,7 @@ use pre_block::fixture::NarwhalFixture; use pre_block::PreBlock; use serde::Serialize; use std::{sync::mpsc, time::Duration}; +use tezos_crypto_rs::blake2b::digest_256; use tezos_data_encoding::enc::BinWriter; use tezos_smart_rollup_encoding::{inbox::ExternalMessageFrame, smart_rollup::SmartRollupAddress}; @@ -15,7 +16,7 @@ use crate::rollup_client::RollupClient; pub const MAX_MESSAGE_SIZE: usize = 2048; // minus endian tag, smart rollup address, external message tag pub const MAX_MESSAGE_PAYLOAD_SIZE: usize = 2020; -pub const BATCH_SIZE_SOFT_LIMIT: usize = 100; +pub const BATCH_SIZE_SOFT_LIMIT: usize = 1; pub type DaBatch = Vec>; @@ -25,11 +26,17 @@ pub fn batch_encode_to( batch: &mut DaBatch, ) -> anyhow::Result<()> { let payload = bcs::to_bytes(&value)?; - let num_messages = payload.len().div_ceil(MAX_MESSAGE_PAYLOAD_SIZE); + let mut header: Vec = vec![1u8]; + + let num_chunks = payload.len().div_ceil(MAX_MESSAGE_PAYLOAD_SIZE); + assert!(num_chunks < 60); // this is the max we can afford with a single header (w/o recursion) + + for chunk in payload.chunks(MAX_MESSAGE_PAYLOAD_SIZE) { + let hash = digest_256(chunk).unwrap(); + header.extend_from_slice(&hash); - for (idx, chunk) in payload.chunks(MAX_MESSAGE_PAYLOAD_SIZE).enumerate() { let mut contents = Vec::with_capacity(MAX_MESSAGE_SIZE); - contents.push(if idx == num_messages - 1 { 1u8 } else { 0u8 }); + contents.push(0u8); contents.extend_from_slice(chunk); let message = ExternalMessageFrame::Targetted { @@ -44,6 +51,17 @@ pub fn batch_encode_to( batch.push(output); } + let message = ExternalMessageFrame::Targetted { + address: smart_rollup_address.clone(), + contents: header, + }; + + let mut output = Vec::with_capacity(MAX_MESSAGE_SIZE); + message.bin_write(&mut output)?; + assert!(output.len() <= MAX_MESSAGE_SIZE); + + batch.push(output); + Ok(()) } @@ -114,7 +132,7 @@ pub async fn publish_pre_blocks( } if !batch.is_empty() { - info!("[DA publish] Sending inbox messages"); + info!("[DA publish] Sending {} messages to the inbox", batch.len()); rollup_client.inject_batch(batch).await?; } } diff --git a/sequencer/src/main.rs b/sequencer/src/main.rs index d00689c..36fbe53 100644 --- a/sequencer/src/main.rs +++ b/sequencer/src/main.rs @@ -165,7 +165,7 @@ async fn run_da_task( info!("[DA task] Starting from index #{}", from_id); tokio::select! { - res = primary_client.subscribe_pre_blocks(from_id, tx) => { + res = primary_client.subscribe_pre_blocks(from_id - 1, tx) => { if let Err(err) = res { error!("[DA fetch] Failed with: {}", err); }