From a645d29a0bfea4a0ad0bf479a39f8b9512310edd Mon Sep 17 00:00:00 2001 From: pthmas <9058370+pthmas@users.noreply.github.com> Date: Wed, 16 Jul 2025 11:43:01 +0200 Subject: [PATCH 01/13] Add specs to docs --- learn/specs/block-manager.md | 599 ++++++++++++++++++++++++ learn/specs/block-validity.md | 130 +++++ learn/specs/da.md | 41 ++ learn/specs/dependency-graph.drawio.svg | 4 + learn/specs/full_node.md | 99 ++++ learn/specs/header-sync.md | 109 +++++ learn/specs/out-of-order-blocks.png | Bin 0 -> 27206 bytes learn/specs/p2p.md | 60 +++ learn/specs/rollkit-dependency-graph.md | 12 + learn/specs/store.md | 92 ++++ learn/specs/template.md | 103 ++++ learn/specs/termination.png | Bin 0 -> 42225 bytes 12 files changed, 1249 insertions(+) create mode 100644 learn/specs/block-manager.md create mode 100644 learn/specs/block-validity.md create mode 100644 learn/specs/da.md create mode 100644 learn/specs/dependency-graph.drawio.svg create mode 100644 learn/specs/full_node.md create mode 100644 learn/specs/header-sync.md create mode 100644 learn/specs/out-of-order-blocks.png create mode 100644 learn/specs/p2p.md create mode 100644 learn/specs/rollkit-dependency-graph.md create mode 100644 learn/specs/store.md create mode 100644 learn/specs/template.md create mode 100644 learn/specs/termination.png diff --git a/learn/specs/block-manager.md b/learn/specs/block-manager.md new file mode 100644 index 000000000..d2a643aca --- /dev/null +++ b/learn/specs/block-manager.md @@ -0,0 +1,599 @@ +# Block Manager + +## Abstract + +The block manager is a key component of full nodes and is responsible for block production or block syncing depending on the node type: sequencer or non-sequencer. Block syncing in this context includes retrieving the published blocks from the network (P2P network or DA network), validating them to raise fraud proofs upon validation failure, updating the state, and storing the validated blocks. A full node invokes multiple block manager functionalities in parallel, such as: + +* Block Production (only for sequencer full nodes) +* Block Publication to DA network +* Block Retrieval from DA network +* Block Sync Service +* Block Publication to P2P network +* Block Retrieval from P2P network +* State Update after Block Retrieval + +```mermaid +sequenceDiagram + title Overview of Block Manager + + participant User + participant Sequencer + participant Full Node 1 + participant Full Node 2 + participant DA Layer + + User->>Sequencer: Send Tx + Sequencer->>Sequencer: Generate Block + Sequencer->>DA Layer: Publish Block + + Sequencer->>Full Node 1: Gossip Block + Sequencer->>Full Node 2: Gossip Block + Full Node 1->>Full Node 1: Verify Block + Full Node 1->>Full Node 2: Gossip Block + Full Node 1->>Full Node 1: Mark Block Soft Confirmed + + Full Node 2->>Full Node 2: Verify Block + Full Node 2->>Full Node 2: Mark Block Soft Confirmed + + DA Layer->>Full Node 1: Retrieve Block + Full Node 1->>Full Node 1: Mark Block DA Included + + DA Layer->>Full Node 2: Retrieve Block + Full Node 2->>Full Node 2: Mark Block DA Included +``` + +### Component Architecture Overview + +```mermaid +flowchart TB + subgraph Block Manager Components + BM[Block Manager] + AGG[Aggregation] + REP[Reaper] + SUB[Submitter] + RET[Retriever] + SYNC[Sync Loop] + DAI[DA Includer] + end + + subgraph External Components + EX[Executor] + SEQ[Sequencer] + DA[DA Layer] + HS[Header Store/P2P] + DS[Data Store/P2P] + ST[Local Store] + end + + REP -->|GetTxs| EX + REP -->|SubmitBatch| SEQ + REP -->|Notify| AGG + + AGG -->|CreateBlock| BM + BM -->|ApplyBlock| EX + BM -->|Save| ST + + BM -->|Headers| SUB + BM -->|Data| SUB + SUB -->|Submit| DA + + RET -->|Retrieve| DA + RET -->|Headers/Data| SYNC + + HS -->|Headers| SYNC + DS -->|Data| SYNC + + SYNC -->|Complete Blocks| BM + SYNC -->|DA Included| DAI + DAI -->|SetFinal| EX +``` + +## Protocol/Component Description + +The block manager is initialized using several parameters as defined below: + +**Name**|**Type**|**Description** +|-----|-----|-----| +signing key|crypto.PrivKey|used for signing blocks and data after creation +config|config.BlockManagerConfig|block manager configurations (see config options below) +genesis|*cmtypes.GenesisDoc|initialize the block manager with genesis state (genesis configuration defined in `config/genesis.json` file under the app directory) +store|store.Store|local datastore for storing rollup blocks and states (default local store path is `$db_dir/rollkit` and `db_dir` specified in the `config.yaml` file under the app directory) +mempool, proxyapp, eventbus|mempool.Mempool, proxy.AppConnConsensus, *cmtypes.EventBus|for initializing the executor (state transition function). mempool is also used in the manager to check for availability of transactions for lazy block production +dalc|da.DAClient|the data availability light client used to submit and retrieve blocks to DA network +headerStore|*goheaderstore.Store[*types.SignedHeader]|to store and retrieve block headers gossiped over the P2P network +dataStore|*goheaderstore.Store[*types.SignedData]|to store and retrieve block data gossiped over the P2P network +signaturePayloadProvider|types.SignaturePayloadProvider|optional custom provider for header signature payloads +sequencer|core.Sequencer|used to retrieve batches of transactions from the sequencing layer +reaper|*Reaper|component that periodically retrieves transactions from the executor and submits them to the sequencer + +Block manager configuration options: + +|Name|Type|Description| +|-----|-----|-----| +|BlockTime|time.Duration|time interval used for block production and block retrieval from block store ([`defaultBlockTime`][defaultBlockTime])| +|DABlockTime|time.Duration|time interval used for both block publication to DA network and block retrieval from DA network ([`defaultDABlockTime`][defaultDABlockTime])| +|DAStartHeight|uint64|block retrieval from DA network starts from this height| +|LazyBlockInterval|time.Duration|time interval used for block production in lazy aggregator mode even when there are no transactions ([`defaultLazyBlockTime`][defaultLazyBlockTime])| +|LazyMode|bool|when set to true, enables lazy aggregation mode which produces blocks only when transactions are available or at LazyBlockInterval intervals| +|MaxPendingHeadersAndData|uint64|maximum number of pending headers and data blocks before pausing block production (default: 100)| +|GasPrice|float64|gas price for DA submissions (-1 for automatic/default)| +|GasMultiplier|float64|multiplier for gas price on DA submission retries (default: 1.3)| +|Namespace|da.Namespace|DA namespace ID for block submissions| + +### Block Production + +When the full node is operating as a sequencer (aka aggregator), the block manager runs the block production logic. There are two modes of block production, which can be specified in the block manager configurations: `normal` and `lazy`. + +In `normal` mode, the block manager runs a timer, which is set to the `BlockTime` configuration parameter, and continuously produces blocks at `BlockTime` intervals. + +In `lazy` mode, the block manager implements a dual timer mechanism: + +```mermaid +flowchart LR + subgraph Lazy Aggregation Mode + R[Reaper] -->|GetTxs| E[Executor] + E -->|Txs Available| R + R -->|Submit to Sequencer| S[Sequencer] + R -->|NotifyNewTransactions| N[txNotifyCh] + + N --> A{Aggregation Logic} + BT[blockTimer] --> A + LT[lazyTimer] --> A + + A -->|Txs Available| P1[Produce Block with Txs] + A -->|No Txs & LazyTimer| P2[Produce Empty Block] + + P1 --> B[Block Creation] + P2 --> B + end +``` + +1. A `blockTimer` that triggers block production at regular intervals when transactions are available +2. A `lazyTimer` that ensures blocks are produced at `LazyBlockInterval` intervals even during periods of inactivity + +The block manager starts building a block when any transaction becomes available in the mempool via a notification channel (`txNotifyCh`). When the `Reaper` detects new transactions, it calls `Manager.NotifyNewTransactions()`, which performs a non-blocking signal on this channel. The block manager also produces empty blocks at regular intervals to maintain consistency with the DA layer, ensuring a 1:1 mapping between DA layer blocks and execution layer blocks. + +The Reaper component periodically retrieves transactions from the executor and submits them to the sequencer. It runs independently and notifies the block manager when new transactions are available, enabling responsive block production in lazy mode. + +#### Building the Block + +The block manager of the sequencer nodes performs the following steps to produce a block: + +```mermaid +flowchart TD + A[Timer Trigger / Transaction Notification] --> B[Retrieve Batch] + B --> C{Transactions Available?} + C -->|Yes| D[Create Block with Txs] + C -->|No| E[Create Empty Block] + D --> F[Generate Header & Data] + E --> F + F --> G[Sign Header → SignedHeader] + F --> H[Sign Data → SignedData] + G --> I[Apply Block] + H --> I + I --> J[Update State] + J --> K[Save to Store] + K --> L[Add to pendingHeaders] + K --> M[Add to pendingData] + L --> N[Broadcast Header to P2P] + M --> O[Broadcast Data to P2P] +``` + +* Retrieve a batch of transactions using `retrieveBatch()` which interfaces with the sequencer +* Call `CreateBlock` using executor with the retrieved transactions +* Create separate header and data structures from the block +* Sign the header using `signing key` to generate `SignedHeader` +* Sign the data using `signing key` to generate `SignedData` (if transactions exist) +* Call `ApplyBlock` using executor to generate an updated state +* Save the block, validators, and updated state to local store +* Add the newly generated header to `pendingHeaders` queue +* Add the newly generated data to `pendingData` queue (if not empty) +* Publish the newly generated header and data to channels to notify other components of the sequencer node (such as block and header gossip) + +Note: When no transactions are available, the block manager creates blocks with empty data using a special `dataHashForEmptyTxs` marker. The header and data separation architecture allows headers and data to be submitted and retrieved independently from the DA layer. + +### Block Publication to DA Network + +The block manager of the sequencer full nodes implements separate submission loops for headers and data, both operating at `DABlockTime` intervals: + +```mermaid +flowchart LR + subgraph Header Submission + H1[pendingHeaders Queue] --> H2[Header Submission Loop] + H2 --> H3[Marshal to Protobuf] + H3 --> H4[Submit to DA] + H4 -->|Success| H5[Remove from Queue] + H4 -->|Failure| H6[Keep in Queue & Retry] + end + + subgraph Data Submission + D1[pendingData Queue] --> D2[Data Submission Loop] + D2 --> D3[Marshal to Protobuf] + D3 --> D4[Submit to DA] + D4 -->|Success| D5[Remove from Queue] + D4 -->|Failure| D6[Keep in Queue & Retry] + end + + H2 -.->|DABlockTime| H2 + D2 -.->|DABlockTime| D2 +``` + +#### Header Submission Loop + +The `HeaderSubmissionLoop` manages the submission of signed headers to the DA network: + +* Retrieves pending headers from the `pendingHeaders` queue +* Marshals headers to protobuf format +* Submits to DA using the generic `submitToDA` helper +* On success, removes submitted headers from the pending queue +* On failure, headers remain in the queue for retry + +#### Data Submission Loop + +The `DataSubmissionLoop` manages the submission of signed data to the DA network: + +* Retrieves pending data from the `pendingData` queue +* Marshals data to protobuf format +* Submits to DA using the generic `submitToDA` helper +* On success, removes submitted data from the pending queue +* On failure, data remains in the queue for retry + +#### Generic Submission Logic + +Both loops use a shared `submitToDA` function that provides: + +* Retry logic with [`maxSubmitAttempts`][maxSubmitAttempts] attempts +* Exponential backoff starting at [`initialBackoff`][initialBackoff], doubling each attempt, capped at `DABlockTime` +* Gas price management with `GasMultiplier` applied on retries +* Comprehensive metrics tracking for attempts, successes, and failures +* Context-aware cancellation support + +The manager enforces a limit on pending headers and data through `MaxPendingHeadersAndData` configuration. When this limit is reached, block production pauses to prevent unbounded growth of the pending queues. + +### Block Retrieval from DA Network + +The block manager implements a `RetrieveLoop` that regularly pulls headers and data from the DA network: + +```mermaid +flowchart TD + A[Start RetrieveLoop] --> B[Get DA Height] + B --> C{DABlockTime Timer} + C --> D[GetHeightPair from DA] + D --> E{Result?} + E -->|Success| F[Validate Signatures] + E -->|NotFound| G[Increment Height] + E -->|Error| H[Retry Logic] + + F --> I[Check Sequencer Info] + I --> J[Mark DA Included] + J --> K[Send to Sync] + K --> L[Increment Height] + L --> M[Immediate Next Retrieval] + + G --> C + H --> N{Retries < 10?} + N -->|Yes| O[Wait 100ms] + N -->|No| P[Log Error & Stall] + O --> D + M --> D +``` + +#### Retrieval Process + +1. **Height Management**: Starts from the latest of: + * DA height from the last state in local store + * `DAStartHeight` configuration parameter + * Maintains and increments `daHeight` counter after successful retrievals + +2. **Retrieval Mechanism**: + * Executes at `DABlockTime` intervals + * Makes `GetHeightPair(daHeight)` request to get both header and data + * Handles three possible outcomes: + * `Success`: Process retrieved header and data + * `NotFound`: No rollup block at this DA height (normal case) + * `Error`: Retry with backoff + +3. **Error Handling**: + * Implements retry logic with 100ms delay between attempts + * After 10 retries, logs error and stalls retrieval + * Does not increment `daHeight` on persistent errors + +4. **Processing Retrieved Blocks**: + * Validates header and data signatures + * Checks sequencer information + * Marks blocks as DA included in caches + * Sends to sync goroutine for state update + * Successful processing triggers immediate next retrieval without waiting for timer + +#### Header and Data Caching + +The retrieval system uses persistent caches for both headers and data: + +* Prevents duplicate processing +* Tracks DA inclusion status +* Supports out-of-order block arrival +* Enables efficient sync from P2P and DA sources + +For more details on DA integration, see the [Data Availability specification](./da.md). + +#### Out-of-Order Rollup Blocks on DA + +Rollkit should support blocks arriving out-of-order on DA, like so: +![out-of-order blocks](./out-of-order-blocks.png) + +#### Termination Condition + +If the sequencer double-signs two blocks at the same height, evidence of the fault should be posted to DA. Rollkit full nodes should process the longest valid chain up to the height of the fault evidence, and terminate. See diagram: +![termination condition](./termination.png) + +### Block Sync Service + +The block sync service manages the synchronization of headers and data through separate stores and channels: + +#### Architecture + +* **Header Store**: Uses `goheader.Store[*types.SignedHeader]` for header management +* **Data Store**: Uses `goheader.Store[*types.SignedData]` for data management +* **Separation of Concerns**: Headers and data are handled independently, supporting the header/data separation architecture + +#### Synchronization Flow + +1. **Header Sync**: Headers created by the sequencer are sent to the header store for P2P gossip +2. **Data Sync**: Data blocks are sent to the data store for P2P gossip +3. **Cache Integration**: Both header and data caches track seen items to prevent duplicates +4. **DA Inclusion Tracking**: Separate tracking for header and data DA inclusion status + +### Block Publication to P2P network + +The sequencer publishes headers and data separately to the P2P network: + +#### Header Publication + +* Headers are sent through the header broadcast channel +* Written to the header store for P2P gossip +* Broadcast to network peers via header sync service + +#### Data Publication + +* Data blocks are sent through the data broadcast channel +* Written to the data store for P2P gossip +* Broadcast to network peers via data sync service + +Non-sequencer full nodes receive headers and data through the P2P sync service and do not publish blocks themselves. + +### Block Retrieval from P2P network + +Non-sequencer full nodes retrieve headers and data separately from P2P stores: + +#### Header Store Retrieval Loop + +The `HeaderStoreRetrieveLoop`: + +* Operates at `BlockTime` intervals via `headerStoreCh` signals +* Tracks `headerStoreHeight` for the last retrieved header +* Retrieves all headers between last height and current store height +* Validates sequencer information using `isUsingExpectedSingleSequencer` +* Marks headers as "seen" in the header cache +* Sends headers to sync goroutine via `headerInCh` + +#### Data Store Retrieval Loop + +The `DataStoreRetrieveLoop`: + +* Operates at `BlockTime` intervals via `dataStoreCh` signals +* Tracks `dataStoreHeight` for the last retrieved data +* Retrieves all data blocks between last height and current store height +* Validates data signatures using `isValidSignedData` +* Marks data as "seen" in the data cache +* Sends data to sync goroutine via `dataInCh` + +#### Soft Confirmations + +Headers and data retrieved from P2P are marked as soft confirmed until both: + +1. The corresponding header is seen on the DA layer +2. The corresponding data is seen on the DA layer + +Once both conditions are met, the block is marked as DA-included. + +#### About Soft Confirmations and DA Inclusions + +The block manager retrieves blocks from both the P2P network and the underlying DA network because the blocks are available in the P2P network faster and DA retrieval is slower (e.g., 1 second vs 6 seconds). +The blocks retrieved from the P2P network are only marked as soft confirmed until the DA retrieval succeeds on those blocks and they are marked DA-included. +DA-included blocks are considered to have a higher level of finality. + +**DAIncluderLoop**: +The `DAIncluderLoop` is responsible for advancing the `DAIncludedHeight` by: + +* Checking if blocks after the current height have both header and data marked as DA-included in caches +* Stopping advancement if either header or data is missing for a height +* Calling `SetFinal` on the executor when a block becomes DA-included +* Storing the Rollkit height to DA height mapping for tracking +* Ensuring only blocks with both header and data present are considered DA-included + +### State Update after Block Retrieval + +The block manager uses a `SyncLoop` to coordinate state updates from blocks retrieved via P2P or DA networks: + +```mermaid +flowchart TD + subgraph Sources + P1[P2P Header Store] --> H[headerInCh] + P2[P2P Data Store] --> D[dataInCh] + DA1[DA Header Retrieval] --> H + DA2[DA Data Retrieval] --> D + end + + subgraph SyncLoop + H --> S[Sync Goroutine] + D --> S + S --> C{Header & Data for Same Height?} + C -->|Yes| R[Reconstruct Block] + C -->|No| W[Wait for Matching Pair] + R --> V[Validate Signatures] + V --> A[ApplyBlock] + A --> CM[Commit] + CM --> ST[Store Block & State] + ST --> F{DA Included?} + F -->|Yes| FN[SetFinal] + F -->|No| E[End] + FN --> U[Update DA Height] + end +``` + +#### Sync Loop Architecture + +The `SyncLoop` processes headers and data from multiple sources: + +* Headers from `headerInCh` (P2P and DA sources) +* Data from `dataInCh` (P2P and DA sources) +* Maintains caches to track processed items +* Ensures ordered processing by height + +#### State Update Process + +When both header and data are available for a height: + +1. **Block Reconstruction**: Combines header and data into a complete block +2. **Validation**: Verifies header and data signatures match expectations +3. **ApplyBlock**: + * Validates the block against current state + * Executes transactions + * Captures validator updates + * Returns updated state +4. **Commit**: + * Persists execution results + * Updates mempool by removing included transactions + * Publishes block events +5. **Storage**: + * Stores the block, validators, and updated state + * Updates last state in manager +6. **Finalization**: + * When block is DA-included, calls `SetFinal` on executor + * Updates DA included height + +## Message Structure/Communication Format + +The communication between the block manager and executor: + +* `InitChain`: initializes the chain state with the given genesis time, initial height, and chain ID using `InitChainSync` on the executor to obtain initial `appHash` and initialize the state. +* `CreateBlock`: prepares a block with transactions from the provided batch data. +* `ApplyBlock`: validates the block, executes the block (apply transactions), captures validator updates, and returns updated state. +* `SetFinal`: marks the block as final when both its header and data are confirmed on the DA layer. +* `GetTxs`: retrieves transactions from the application (used by Reaper component). + +The communication with the sequencer: + +* `GetNextBatch`: retrieves the next batch of transactions to include in a block. +* `VerifyBatch`: validates that a batch came from the expected sequencer. + +The communication with DA layer: + +* `Submit`: submits headers or data blobs to the DA network. +* `Get`: retrieves headers or data blobs from the DA network. +* `GetHeightPair`: retrieves both header and data at a specific DA height. + +## Assumptions and Considerations + +* The block manager loads the initial state from the local store and uses genesis if not found in the local store, when the node (re)starts. +* The default mode for sequencer nodes is normal (not lazy). +* The sequencer can produce empty blocks. +* In lazy aggregation mode, the block manager maintains consistency with the DA layer by producing empty blocks at regular intervals, ensuring a 1:1 mapping between DA layer blocks and execution layer blocks. +* The lazy aggregation mechanism uses a dual timer approach: + * A `blockTimer` that triggers block production when transactions are available + * A `lazyTimer` that ensures blocks are produced even during periods of inactivity +* Empty batches are handled differently in lazy mode - instead of discarding them, they are returned with the `ErrNoBatch` error, allowing the caller to create empty blocks with proper timestamps. +* Transaction notifications from the `Reaper` to the `Manager` are handled via a non-blocking notification channel (`txNotifyCh`) to prevent backpressure. +* The block manager enforces `MaxPendingHeadersAndData` limit to prevent unbounded growth of pending queues during DA submission issues. +* Headers and data are submitted separately to the DA layer, supporting the header/data separation architecture. +* The block manager uses persistent caches for headers and data to track seen items and DA inclusion status. +* Gas price management includes automatic adjustment with `GasMultiplier` on DA submission retries. +* The block manager uses persistent storage (disk) when the `root_dir` and `db_path` configuration parameters are specified in `config.yaml` file under the app directory. If these configuration parameters are not specified, the in-memory storage is used, which will not be persistent if the node stops. +* The block manager does not re-apply blocks when they transition from soft confirmed to DA included status. The block is only marked DA included in the caches. +* Header and data stores use separate prefixes for isolation in the underlying database. +* The genesis `ChainID` is used to create separate `PubSubTopID`s for headers and data in go-header. +* Block sync over the P2P network works only when a full node is connected to the P2P network by specifying the initial seeds to connect to via `P2PConfig.Seeds` configuration parameter when starting the full node. +* Node's context is passed down to all components to support graceful shutdown and cancellation. +* The block manager supports custom signature payload providers for headers, enabling flexible signing schemes. +* The block manager supports the separation of header and data structures in Rollkit. This allows for expanding the sequencing scheme beyond single sequencing and enables the use of a decentralized sequencer mode. For detailed information on this architecture, see the [Header and Data Separation ADR](../../lazy-adr/adr-014-header-and-data-separation.md). +* The block manager processes blocks with a minimal header format, which is designed to eliminate dependency on CometBFT's header format and can be used to produce an execution layer tailored header if needed. For details on this header structure, see the [Rollkit Minimal Header](../../lazy-adr/adr-015-rollkit-minimal-header.md) specification. + +## Metrics + +The block manager exposes comprehensive metrics for monitoring: + +### Block Production Metrics + +* `last_block_produced_height`: Height of the last produced block +* `last_block_produced_time`: Timestamp of the last produced block +* `aggregation_type`: Current aggregation mode (normal/lazy) +* `block_size_bytes`: Size distribution of produced blocks +* `produced_empty_blocks_total`: Count of empty blocks produced + +### DA Metrics + +* `da_submission_attempts_total`: Total DA submission attempts +* `da_submission_success_total`: Successful DA submissions +* `da_submission_failure_total`: Failed DA submissions +* `da_retrieval_attempts_total`: Total DA retrieval attempts +* `da_retrieval_success_total`: Successful DA retrievals +* `da_retrieval_failure_total`: Failed DA retrievals +* `da_height`: Current DA retrieval height +* `pending_headers_count`: Number of headers pending DA submission +* `pending_data_count`: Number of data blocks pending DA submission + +### Sync Metrics + +* `sync_height`: Current sync height +* `da_included_height`: Height of last DA-included block +* `soft_confirmed_height`: Height of last soft confirmed block +* `header_store_height`: Current header store height +* `data_store_height`: Current data store height + +### Performance Metrics + +* `block_production_time`: Time to produce a block +* `da_submission_time`: Time to submit to DA +* `state_update_time`: Time to apply block and update state +* `channel_buffer_usage`: Usage of internal channels + +### Error Metrics + +* `errors_total`: Total errors by type and operation + +## Implementation + +See [block-manager] + +See [tutorial] for running a multi-node network with both sequencer and non-sequencer full nodes. + +## References + +[1] [Go Header][go-header] + +[2] [Block Sync][block-sync] + +[3] [Full Node][full-node] + +[4] [Block Manager][block-manager] + +[5] [Tutorial][tutorial] + +[6] [Header and Data Separation ADR](../../lazy-adr/adr-014-header-and-data-separation.md) + +[7] [Rollkit Minimal Header](../../lazy-adr/adr-015-rollkit-minimal-header.md) + +[8] [Data Availability](./da.md) + +[9] [Lazy Aggregation with DA Layer Consistency ADR](../../lazy-adr/adr-021-lazy-aggregation.md) + +[maxSubmitAttempts]: https://github.com/rollkit/rollkit/blob/main/block/manager.go#L50 +[defaultBlockTime]: https://github.com/rollkit/rollkit/blob/main/block/manager.go#L36 +[defaultDABlockTime]: https://github.com/rollkit/rollkit/blob/main/block/manager.go#L33 +[defaultLazyBlockTime]: https://github.com/rollkit/rollkit/blob/main/block/manager.go#L39 +[initialBackoff]: https://github.com/rollkit/rollkit/blob/main/block/manager.go#L59 +[go-header]: https://github.com/celestiaorg/go-header +[block-sync]: https://github.com/rollkit/rollkit/blob/main/pkg/sync/sync_service.go +[full-node]: https://github.com/rollkit/rollkit/blob/main/node/full.go +[block-manager]: https://github.com/rollkit/rollkit/blob/main/block/manager.go +[tutorial]: https://rollkit.dev/guides/full-node diff --git a/learn/specs/block-validity.md b/learn/specs/block-validity.md new file mode 100644 index 000000000..dc5820ca0 --- /dev/null +++ b/learn/specs/block-validity.md @@ -0,0 +1,130 @@ +# Block and Header Validity + +## Abstract + +Like all blockchains, rollups are defined as the chain of **valid** blocks from the genesis, to the head. Thus, the block and header validity rules define the chain. + +Verifying a block/header is done in 3 parts: + +1. Verify correct serialization according to the protobuf spec + +2. Perform basic validation of the types + +3. Perform verification of the new block against the previously accepted block + +Rollkit uses a header/data separation architecture where headers and data can be validated independently. The system has moved from a multi-validator model to a single signer model for simplified sequencer management. + +## Basic Validation + +Each type contains a `.ValidateBasic()` method, which verifies that certain basic invariants hold. The `ValidateBasic()` calls are nested for each structure. + +### SignedHeader Validation + +```go +SignedHeader.ValidateBasic() + // Make sure the SignedHeader's Header passes basic validation + Header.ValidateBasic() + verify ProposerAddress not nil + // Make sure the SignedHeader's signature passes basic validation + Signature.ValidateBasic() + // Ensure that someone signed the header + verify len(c.Signatures) not 0 + // For based rollups (sh.Signer.IsEmpty()), pass validation + If !sh.Signer.IsEmpty(): + // Verify the signer matches the proposer address + verify sh.Signer.Address == sh.ProposerAddress + // Verify signature using custom verifier if set, otherwise use default + if sh.verifier != nil: + verify sh.verifier(sh) == nil + else: + verify sh.Signature.Verify(sh.Signer.PubKey, sh.Header.MarshalBinary()) +``` + +### SignedData Validation + +```go +SignedData.ValidateBasic() + // Always passes basic validation for the Data itself + Data.ValidateBasic() // always passes + // Make sure the signature is valid + Signature.ValidateBasic() + verify len(c.Signatures) not 0 + // Verify the signer + If !sd.Signer.IsEmpty(): + verify sd.Signature.Verify(sd.Signer.PubKey, sd.Data.MarshalBinary()) +``` + +### Block Validation + +Blocks are composed of SignedHeader and Data: + +```go +// Block validation happens by validating header and data separately +// then ensuring data hash matches +verify SignedHeader.ValidateBasic() == nil +verify Data.Hash() == SignedHeader.DataHash +``` + +## Verification Against Previous Block + +```go +SignedHeader.Verify(untrustedHeader *SignedHeader) + // Basic validation is handled by go-header before this + Header.Verify(untrustedHeader) + // Verify height sequence + if untrustedHeader.Height != h.Height + 1: + if untrustedHeader.Height > h.Height + 1: + return soft verification failure + return error "headers are not adjacent" + // Verify the link to previous header + verify untrustedHeader.LastHeaderHash == h.Header.Hash() + // Verify LastCommit hash matches previous signature + verify untrustedHeader.LastCommitHash == sh.Signature.GetCommitHash(...) + // Note: ValidatorHash field exists for compatibility but is not validated +``` + +## [Data](https://github.com/rollkit/rollkit/blob/main/types/data.go) + +| **Field Name** | **Valid State** | **Validation** | +|----------------|-----------------------------------------|------------------------------------| +| Txs | Transaction data of the block | Data.Hash() == SignedHeader.DataHash | +| Metadata | Optional p2p gossiping metadata | Not validated | + +## [SignedHeader](https://github.com/rollkit/rollkit/blob/main/types/signed_header.go) + +| **Field Name** | **Valid State** | **Validation** | +|----------------|--------------------------------------------------------------------------|---------------------------------------------------------------------------------------------| +| Header | Valid header for the block | `Header` passes `ValidateBasic()` and `Verify()` | +| Signature | Valid signature from the single sequencer | `Signature` passes `ValidateBasic()`, verified against signer | +| Signer | Information about who signed the header | Must match ProposerAddress if not empty (based rollup case) | +| verifier | Optional custom signature verification function | Used instead of default verification if set | + +## [Header](https://github.com/rollkit/rollkit/blob/main/types/header.go) + +***Note***: Rollkit has moved to a single signer model. The multi-validator architecture has been replaced with a simpler single sequencer approach. + +| **Field Name** | **Valid State** | **Validation** | +|---------------------|--------------------------------------------------------------------------------------------|---------------------------------------| +| **BaseHeader** . | | | +| Height | Height of the previous accepted header, plus 1. | checked in the `Verify()`` step | +| Time | Timestamp of the block | Not validated in Rollkit | +| ChainID | The hard-coded ChainID of the chain | Should be checked as soon as the header is received | +| **Header** . | | | +| Version | unused | | +| LastHeaderHash | The hash of the previous accepted block | checked in the `Verify()`` step | +| LastCommitHash | The hash of the previous accepted block's commit | checked in the `Verify()`` step | +| DataHash | Correct hash of the block's Data field | checked in the `ValidateBasic()`` step | +| ConsensusHash | unused | | +| AppHash | The correct state root after executing the block's transactions against the accepted state | checked during block execution | +| LastResultsHash | Correct results from executing transactions | checked during block execution | +| ProposerAddress | Address of the expected proposer | Must match Signer.Address in SignedHeader | +| ValidatorHash | Compatibility field for Tendermint light client | Not validated | + +## [Signer](https://github.com/rollkit/rollkit/blob/main/types/signed_header.go) + +The Signer type replaces the previous ValidatorSet for single sequencer operation: + +| **Field Name** | **Valid State** | **Validation** | +|----------------|-----------------------------------------------------------------|-----------------------------| +| PubKey | Public key of the signer | Must not be nil if Signer is not empty | +| Address | Address derived from the public key | Must match ProposerAddress | diff --git a/learn/specs/da.md b/learn/specs/da.md new file mode 100644 index 000000000..a58235286 --- /dev/null +++ b/learn/specs/da.md @@ -0,0 +1,41 @@ +# DA + +Rollkit provides a wrapper for [go-da][go-da], a generic data availability interface for modular blockchains, called `DAClient` with wrapper functionalities like `SubmitBlocks` and `RetrieveBlocks` to help block manager interact with DA more easily. + +## Details + +`DAClient` can connect via either gRPC or JSON-RPC transports using the [go-da][go-da] [proxy/grpc][proxy/grpc] or [proxy/jsonrpc][proxy/jsonrpc] implementations. The connection can be configured using the following cli flags: + +* `--rollkit.da_address`: url address of the DA service (default: "grpc://localhost:26650") +* `--rollkit.da_auth_token`: authentication token of the DA service +* `--rollkit.da_namespace`: namespace to use when submitting blobs to the DA service + +Given a set of blocks to be submitted to DA by the block manager, the `SubmitBlocks` first encodes the blocks using protobuf (the encoded data are called blobs) and invokes the `Submit` method on the underlying DA implementation. On successful submission (`StatusSuccess`), the DA block height which included in the blocks is returned. + +To make sure that the serialised blocks don't exceed the underlying DA's blob limits, it fetches the blob size limit by calling `Config` which returns the limit as `uint64` bytes, then includes serialised blocks until the limit is reached. If the limit is reached, it submits the partial set and returns the count of successfully submitted blocks as `SubmittedCount`. The caller should retry with the remaining blocks until all the blocks are submitted. If the first block itself is over the limit, it throws an error. + +The `Submit` call may result in an error (`StatusError`) based on the underlying DA implementations on following scenarios: + +* the total blobs size exceeds the underlying DA's limits (includes empty blobs) +* the implementation specific failures, e.g., for [celestia-da-json-rpc][proxy/jsonrpc], invalid namespace, unable to create the commitment or proof, setting low gas price, etc, could return error. + +The `RetrieveBlocks` retrieves the blocks for a given DA height using [go-da][go-da] `GetIDs` and `Get` methods. If there are no blocks available for a given DA height, `StatusNotFound` is returned (which is not an error case). The retrieved blobs are converted back to blocks and returned on successful retrieval. + +Both `SubmitBlocks` and `RetrieveBlocks` may be unsuccessful if the DA node and the DA blockchain that the DA implementation is using have failures. For example, failures such as, DA mempool is full, DA submit transaction is nonce clashing with other transaction from the DA submitter account, DA node is not synced, etc. + +## Implementation + +See [da implementation] + +## References + +[1] [go-da][go-da] + +[2] [proxy/grpc][proxy/grpc] + +[3] [proxy/jsonrpc][proxy/jsonrpc] + +[da implementation]: https://github.com/rollkit/rollkit/blob/main/da/jsonrpc/client.go +[go-da]: https://github.com/rollkit/go-da +[proxy/grpc]: https://github.com/rollkit/go-da/tree/main/proxy/grpc +[proxy/jsonrpc]: https://github.com/rollkit/go-da/tree/main/proxy/jsonrpc diff --git a/learn/specs/dependency-graph.drawio.svg b/learn/specs/dependency-graph.drawio.svg new file mode 100644 index 000000000..d5325378e --- /dev/null +++ b/learn/specs/dependency-graph.drawio.svg @@ -0,0 +1,4 @@ + + + +
Header Interface
574
Blocksize independent Commitment
New Rollkit Header
579
Transaction Inclusion Proofs
Celestia Blob Inclusion Proofs
594
P2P Rollkit
Headersync
P2P DA Message Inclusion Proofs
P2P Blocksync
Full Node Soft Confirmations
Full Node Fast UX
Header Pruning
Asynchronous Celestia/Rollkit
Offchain Rollkit Light Clients
362
Multiple Rollup Blocks Per Celestia Block
State Tracing
514
State Execution Fraud Proof generation
514
Query Generation
Deep Subtrees
Start minimal cosmos-sdk with State
Trustminimized P2P Queries 
Satet Execution Fraud Proof verification
Fraud Proof Window
State Execution Fraud Proofs
No Infura
Light Client Wallet
Rollkit Frontend
Indexer
Wallet Builder
Blockchain explorer 
Ignite CLI for Wallet and Explorer defaults
Blockchain explorer builder
Block Pruning
ISR / Transaction serialization
Seriallization Fraud Proof
Centralized Sequencer
Shared Sequencer
Lazy Sequencer
Sequencing API
Light Client Soft Confirmation
Light Client Fast UX
Make Light client lighter
571
DA Interface
Credible Neutrality
Solve Dependency on Celestia Node
Solve Dependcy on cometbft
Proxy App Interface
Remove cometbft Types
Celestia Blob Inclusion Proofs by Index
Transaction / ISR Format 
Rollkit CLI
Celestia-node Fraud Proof Abstraction
Fraud Proof Gossiping 
Fraud Proof Storage
Fraud Proof Syncing
Other DA-Implementations
Fork Choice Interface
Block Storage
Block Sync
Block Gossip
bitcoin-da
avail-da
go-fraud to go-proof rebranding
Proof Gossip
Proof Storage
Proof Syncing
P2P Proof Sync
P2P Fraud Proof Sync
Based
Celestia
FARI
Faud App RU Intefrace
cosmos-sdk runtime module 
Unextend ABCI
ZARI
Zk App RU Interface
ZK-Rollups
PoA
Seperate Data and Header in 2 Namespaces 
Seperate Ordering from Execution
Centralized Execution
JTMB 007 IBC
RPC Equivalency 
conditional light clients
go-square
JTMB 008 IBC
IBC celestia light client for Inclusion Proofs
Canonical IBC light for both bridging and proofs  
JTMB 008 IBC + Inclusion Proofs
Decetralizing Execution
Execution Market
Prover Market 
PoS Exeution
Sequencer Light Node
Conditional Sequencer Light Node 
JTMB Execution 008 IBC + Inclusion Proofs + Ordering Proofs
On-chain Execution Rollkit Light Clients
 008 IBC + Inclusion Proofs + Ordering Proofs + Execution validity 
P2P Blob Inclusion proofs
Namespace Bridging
IBC Header compression
Namespace limit encoding 
Reading in transactions in from multiple namesapces
\ No newline at end of file diff --git a/learn/specs/full_node.md b/learn/specs/full_node.md new file mode 100644 index 000000000..b7e5d510b --- /dev/null +++ b/learn/specs/full_node.md @@ -0,0 +1,99 @@ +# Full Node + +## Abstract + +A Full Node is a top-level service that encapsulates different components of Rollkit and initializes/manages them. + +## Details + +### Full Node Details + +A Full Node is initialized inside the Cosmos SDK start script along with the node configuration, a private key to use in the P2P client, a private key for signing blocks as a block proposer, a client creator, a genesis document, and a logger. It uses them to initialize the components described above. The components TxIndexer, BlockIndexer, and IndexerService exist to ensure cometBFT compatibility since they are needed for most of the RPC calls from the `SignClient` interface from cometBFT. + +Note that unlike a light node which only syncs and stores block headers seen on the P2P layer, the full node also syncs and stores full blocks seen on both the P2P network and the DA layer. Full blocks contain all the transactions published as part of the block. + +The Full Node mainly encapsulates and initializes/manages the following components: + +### genesisDoc + +The [genesis] document contains information about the initial state of the chain, in particular its validator set. + +### conf + +The [node configuration] contains all the necessary settings for the node to be initialized and function properly. + +### P2P + +The [peer-to-peer client] is used to gossip transactions between full nodes in the network. + +### Store + +The [Store] is initialized with `DefaultStore`, an implementation of the [store interface] which is used for storing and retrieving blocks, commits, and state. | + +### blockManager + +The [Block Manager] is responsible for managing block-related operations including: + +- Block production (normal and lazy modes) +- Header and data submission to DA layer +- Block retrieval and synchronization +- State updates and finalization + +It implements a header/data separation architecture where headers and transaction data are handled independently. + +### dalc + +The [Data Availability Layer Client][dalc] is used to interact with the data availability layer. It is initialized with the DA Layer and DA Config specified in the node configuration. + +### hSyncService + +The [Header Sync Service] is used for syncing signed headers between nodes over P2P. It operates independently from data sync to support light clients. + +### dSyncService + +The [Data Sync Service] is used for syncing transaction data between nodes over P2P. This service is only used by full nodes, not light nodes. + +## Message Structure/Communication Format + +The Full Node communicates with other nodes in the network using the P2P client. It also communicates with the application using the ABCI proxy connections. The communication format is based on the P2P and ABCI protocols. + +## Assumptions and Considerations + +The Full Node assumes that the configuration, private keys, client creator, genesis document, and logger are correctly passed in by the Cosmos SDK. It also assumes that the P2P client, data availability layer client, block manager, and other services can be started and stopped without errors. + +## Implementation + +See [full node] + +## References + +[1] [Full Node][full node] + +[2] [Genesis Document][genesis] + +[3] [Node Configuration][node configuration] + +[4] [Peer to Peer Client][peer-to-peer client] + +[5] [Store][Store] + +[6] [Store Interface][store interface] + +[7] [Block Manager][block manager] + +[8] [Data Availability Layer Client][dalc] + +[9] [Header Sync Service][Header Sync Service] + +[10] [Data Sync Service][Data Sync Service] + +[full node]: https://github.com/rollkit/rollkit/blob/main/node/full.go +[genesis]: https://github.com/cometbft/cometbft/blob/main/spec/core/genesis.md +[node configuration]: https://github.com/rollkit/rollkit/blob/main/pkg/config/config.go +[peer-to-peer client]: https://github.com/rollkit/rollkit/blob/main/pkg/p2p/client.go +[Store]: https://github.com/rollkit/rollkit/blob/main/pkg/store/store.go +[store interface]: https://github.com/rollkit/rollkit/blob/main/pkg/store/types.go +[Block Manager]: https://github.com/rollkit/rollkit/blob/main/block/manager.go +[dalc]: https://github.com/rollkit/rollkit/blob/main/core/da/da.go +[Header Sync Service]: https://github.com/rollkit/rollkit/blob/main/pkg/sync/sync_service.go +[Data Sync Service]: https://github.com/rollkit/rollkit/blob/main/pkg/sync/sync_service.go diff --git a/learn/specs/header-sync.md b/learn/specs/header-sync.md new file mode 100644 index 000000000..dca0d81b8 --- /dev/null +++ b/learn/specs/header-sync.md @@ -0,0 +1,109 @@ +# Header and Data Sync + +## Abstract + +The nodes in the P2P network sync headers and data using separate sync services that implement the [go-header][go-header] interface. Rollkit uses a header/data separation architecture where headers and transaction data are synchronized independently through parallel services. Each sync service consists of several components as listed below. + +|Component|Description| +|---|---| +|store| a prefixed [datastore][datastore] where synced items are stored (`headerSync` prefix for headers, `dataSync` prefix for data)| +|subscriber | a [libp2p][libp2p] node pubsub subscriber for the specific data type| +|P2P server| a server for handling requests between peers in the P2P network| +|exchange| a client that enables sending in/out-bound requests from/to the P2P network| +|syncer| a service for efficient synchronization. When a P2P node falls behind and wants to catch up to the latest network head via P2P network, it can use the syncer.| + +## Details + +Rollkit implements two separate sync services: + +### Header Sync Service + +- Synchronizes `SignedHeader` structures containing block headers with signatures +- Used by all node types (sequencer, full, and light) +- Essential for maintaining the canonical view of the chain + +### Data Sync Service + +- Synchronizes `Data` structures containing transaction data +- Used only by full nodes and sequencers +- Light nodes do not run this service as they only need headers + +Both services: + +- Utilize the generic `SyncService[H header.Header[H]]` implementation +- Inherit the `ConnectionGater` from the node's P2P client for peer management +- Use `NodeConfig.BlockTime` to determine outdated items during sync +- Operate independently on separate P2P topics and datastores + +### Consumption of Sync Services + +#### Header Sync + +- Sequencer nodes publish signed headers to the P2P network after block creation +- Full and light nodes receive and store headers for chain validation +- Headers contain commitments (DataHash) that link to the corresponding data + +#### Data Sync + +- Sequencer nodes publish transaction data separately from headers +- Only full nodes receive and store data (light nodes skip this) +- Data is linked to headers through the DataHash commitment + +#### Parallel Broadcasting + +The block manager broadcasts headers and data in parallel when publishing blocks: + +- Headers are sent through `headerBroadcaster` +- Data is sent through `dataBroadcaster` +- This enables efficient network propagation of both components + +## Assumptions + +- Separate datastores are created with different prefixes: + - Headers: `headerSync` prefix on the main datastore + - Data: `dataSync` prefix on the main datastore +- Network IDs are suffixed to distinguish services: + - Header sync: `{network}-headerSync` + - Data sync: `{network}-dataSync` +- Chain IDs for pubsub topics are also separated: + - Headers: `{chainID}-headerSync` creates topic like `/gm-headerSync/header-sub/v0.0.1` + - Data: `{chainID}-dataSync` creates topic like `/gm-dataSync/header-sub/v0.0.1` +- Both stores must be initialized with genesis items before starting: + - Header store needs genesis header + - Data store needs genesis data (if applicable) +- Genesis items can be loaded via `NodeConfig.TrustedHash` or P2P network query +- Sync services work only when connected to P2P network via `P2PConfig.Seeds` +- Node context is passed to all components for graceful shutdown +- Headers and data are linked through DataHash but synced independently + +## Implementation + +The sync service implementation can be found in [pkg/sync/sync_service.go][sync-service]. The generic `SyncService[H header.Header[H]]` is instantiated as: + +- `HeaderSyncService` for syncing `*types.SignedHeader` +- `DataSyncService` for syncing `*types.Data` + +Full nodes create and start both services, while light nodes only start the header sync service. The services are created in [full][fullnode] and [light][lightnode] node implementations. + +The block manager integrates with both services through: + +- `HeaderStoreRetrieveLoop()` for retrieving headers from P2P +- `DataStoreRetrieveLoop()` for retrieving data from P2P +- Separate broadcast channels for publishing headers and data + +## References + +[1] [Header Sync][sync-service] + +[2] [Full Node][fullnode] + +[3] [Light Node][lightnode] + +[4] [go-header][go-header] + +[sync-service]: https://github.com/rollkit/rollkit/blob/main/pkg/sync/sync_service.go +[fullnode]: https://github.com/rollkit/rollkit/blob/main/node/full.go +[lightnode]: https://github.com/rollkit/rollkit/blob/main/node/light.go +[go-header]: https://github.com/celestiaorg/go-header +[libp2p]: https://github.com/libp2p/go-libp2p +[datastore]: https://github.com/ipfs/go-datastore diff --git a/learn/specs/out-of-order-blocks.png b/learn/specs/out-of-order-blocks.png new file mode 100644 index 0000000000000000000000000000000000000000..fa7a955cb97a9e5c4408bbc6f37bd1e475ec5492 GIT binary patch literal 27206 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 PLMM)y9R1V4@yfped`{D3 literal 0 HcmV?d00001 diff --git a/learn/specs/p2p.md b/learn/specs/p2p.md new file mode 100644 index 000000000..0e96f065c --- /dev/null +++ b/learn/specs/p2p.md @@ -0,0 +1,60 @@ +# P2P + +Every node (both full and light) runs a P2P client using [go-libp2p][go-libp2p] P2P networking stack for gossiping transactions in the chain's P2P network. The same P2P client is also used by the header and block sync services for gossiping headers and blocks. + +Following parameters are required for creating a new instance of a P2P client: + +* P2PConfig (described below) +* [go-libp2p][go-libp2p] private key used to create a libp2p connection and join the p2p network. +* chainID: identifier used as namespace within the p2p network for peer discovery. The namespace acts as a sub network in the p2p network, where peer connections are limited to the same namespace. +* datastore: an instance of [go-datastore][go-datastore] used for creating a connection gator and stores blocked and allowed peers. +* logger + +```go +// P2PConfig stores configuration related to peer-to-peer networking. +type P2PConfig struct { + ListenAddress string // Address to listen for incoming connections + Seeds string // Comma separated list of seed nodes to connect to + BlockedPeers string // Comma separated list of nodes to ignore + AllowedPeers string // Comma separated list of nodes to whitelist +} +``` + +A P2P client also instantiates a [connection gator][conngater] to block and allow peers specified in the `P2PConfig`. + +It also sets up a gossiper using the gossip topic `+` (`txTopicSuffix` is defined in [p2p/client.go][client.go]), a Distributed Hash Table (DHT) using the `Seeds` defined in the `P2PConfig` and peer discovery using go-libp2p's `discovery.RoutingDiscovery`. + +A P2P client provides an interface `SetTxValidator(p2p.GossipValidator)` for specifying a gossip validator which can define how to handle the incoming `GossipMessage` in the P2P network. The `GossipMessage` represents message gossiped via P2P network (e.g. transaction, Block etc). + +```go +// GossipValidator is a callback function type. +type GossipValidator func(*GossipMessage) bool +``` + +The full nodes define a transaction validator (shown below) as gossip validator for processing the gossiped transactions to add to the mempool, whereas light nodes simply pass a dummy validator as light nodes do not process gossiped transactions. + +```go +// newTxValidator creates a pubsub validator that uses the node's mempool to check the +// transaction. If the transaction is valid, then it is added to the mempool +func (n *FullNode) newTxValidator() p2p.GossipValidator { +``` + +```go +// Dummy validator that always returns a callback function with boolean `false` +func (ln *LightNode) falseValidator() p2p.GossipValidator { +``` + +## References + +[1] [client.go][client.go] + +[2] [go-datastore][go-datastore] + +[3] [go-libp2p][go-libp2p] + +[4] [conngater][conngater] + +[client.go]: https://github.com/rollkit/rollkit/blob/main/pkg/p2p/client.go +[go-datastore]: https://github.com/ipfs/go-datastore +[go-libp2p]: https://github.com/libp2p/go-libp2p +[conngater]: https://github.com/libp2p/go-libp2p/tree/master/p2p/net/conngater diff --git a/learn/specs/rollkit-dependency-graph.md b/learn/specs/rollkit-dependency-graph.md new file mode 100644 index 000000000..234a30fde --- /dev/null +++ b/learn/specs/rollkit-dependency-graph.md @@ -0,0 +1,12 @@ +# Rollkit Dependency Graph + +![Dependency Graph](./dependency-graph.drawio.svg) + +We use the following color coding in this Graph: + +- No Colour: Work not yet started +- Yellow Box: Work in progress +- Green Box: Work completed or at least unblocking the next dependency +- Red Border: Work needs to happen in cooperation with another team + +If the EPICs are not linked to the box yet, it means that this box has currently no priority or is still in the ideation phase or the dependency is unclear. diff --git a/learn/specs/store.md b/learn/specs/store.md new file mode 100644 index 000000000..9488eef5a --- /dev/null +++ b/learn/specs/store.md @@ -0,0 +1,92 @@ +# Store + +## Abstract + +The Store interface defines methods for storing and retrieving blocks, commits, and the state of the blockchain. + +## Protocol/Component Description + +The Store interface defines the following methods: + +- `Height`: Returns the height of the highest block in the store. +- `SetHeight`: Sets given height in the store if it's higher than the existing height in the store. +- `SaveBlock`: Saves a block (containing both header and data) along with its seen signature. +- `GetBlock`: Returns a block at a given height. +- `GetBlockByHash`: Returns a block with a given block header hash. + +Note: While blocks are stored as complete units in the store, the block manager handles headers and data separately during synchronization and DA layer interaction. + +- `SaveBlockResponses`: Saves block responses in the Store. +- `GetBlockResponses`: Returns block results at a given height. +- `GetSignature`: Returns a signature for a block at a given height. +- `GetSignatureByHash`: Returns a signature for a block with a given block header hash. +- `UpdateState`: Updates the state saved in the Store. Only one State is stored. +- `GetState`: Returns the last state saved with UpdateState. +- `SaveValidators`: Saves the validator set at a given height. +- `GetValidators`: Returns the validator set at a given height. + +The `TxnDatastore` interface inside [go-datastore] is used for constructing different key-value stores for the underlying storage of a full node. The are two different implementations of `TxnDatastore` in [kv.go]: + +- `NewDefaultInMemoryKVStore`: Builds a key-value store that uses the [BadgerDB] library and operates in-memory, without accessing the disk. Used only across unit tests and integration tests. + +- `NewDefaultKVStore`: Builds a key-value store that uses the [BadgerDB] library and stores the data on disk at the specified path. + +A Rollkit full node is [initialized][full_node_store_initialization] using `NewDefaultKVStore` as the base key-value store for underlying storage. To store various types of data in this base key-value store, different prefixes are used: `mainPrefix`, `dalcPrefix`, and `indexerPrefix`. The `mainPrefix` equal to `0` is used for the main node data, `dalcPrefix` equal to `1` is used for Data Availability Layer Client (DALC) data, and `indexerPrefix` equal to `2` is used for indexing related data. + +For the main node data, `DefaultStore` struct, an implementation of the Store interface, is used with the following prefixes for various types of data within it: + +- `blockPrefix` with value "b": Used to store complete blocks in the key-value store. +- `indexPrefix` with value "i": Used to index the blocks stored in the key-value store. +- `commitPrefix` with value "c": Used to store commits related to the blocks. +- `statePrefix` with value "s": Used to store the state of the blockchain. +- `responsesPrefix` with value "r": Used to store responses related to the blocks. +- `validatorsPrefix` with value "v": Used to store validator sets at a given height. + +Additional prefixes used by sync services: + +- `headerSyncPrefix` with value "hs": Used by the header sync service for P2P synced headers. +- `dataSyncPrefix` with value "ds": Used by the data sync service for P2P synced transaction data. +For example, in a call to `GetBlockByHash` for some block hash ``, the key used in the full node's base key-value store will be `/0/b/` where `0` is the main store prefix and `b` is the block prefix. Similarly, in a call to `GetValidators` for some height ``, the key used in the full node's base key-value store will be `/0/v/` where `0` is the main store prefix and `v` is the validator set prefix. + +Inside the key-value store, the value of these various types of data like `Block` is stored as a byte array which is encoded and decoded using the corresponding Protobuf [marshal and unmarshal methods][serialization]. + +The store is most widely used inside the [block manager] to perform their functions correctly. Within the block manager, since it has multiple go-routines in it, it is protected by a mutex lock, `lastStateMtx`, to synchronize read/write access to it and prevent race conditions. + +## Message Structure/Communication Format + +The Store does not communicate over the network, so there is no message structure or communication format. + +## Assumptions and Considerations + +The Store assumes that the underlying datastore is reliable and provides atomicity for transactions. It also assumes that the data passed to it for storage is valid and correctly formatted. + +## Implementation + +See [Store Interface][store_interface] and [Default Store][default_store] for its implementation. + +## References + +[1] [Store Interface][store_interface] + +[2] [Default Store][default_store] + +[3] [Full Node Store Initialization][full_node_store_initialization] + +[4] [Block Manager][block manager] + +[5] [Badger DB][BadgerDB] + +[6] [Go Datastore][go-datastore] + +[7] [Key Value Store][kv.go] + +[8 ] [Serialization][serialization] + +[store_interface]: https://github.com/rollkit/rollkit/blob/main/pkg/store/types.go#L11 +[default_store]: https://github.com/rollkit/rollkit/blob/main/pkg/store/store.go +[full_node_store_initialization]: https://github.com/rollkit/rollkit/blob/main/node/full.go#L96 +[block manager]: https://github.com/rollkit/rollkit/blob/main/block/manager.go +[BadgerDB]: https://github.com/dgraph-io/badger +[go-datastore]: https://github.com/ipfs/go-datastore +[kv.go]: https://github.com/rollkit/rollkit/blob/main/pkg/store/kv.go +[serialization]: https://github.com/rollkit/rollkit/blob/main/types/serialization.go diff --git a/learn/specs/template.md b/learn/specs/template.md new file mode 100644 index 000000000..1e170b3bc --- /dev/null +++ b/learn/specs/template.md @@ -0,0 +1,103 @@ +# Protocol/Component Name + +## Abstract + +Provide a concise description of the purpose of the component for which the +specification is written, along with its contribution to the rollkit or +other relevant parts of the system. Make sure to include proper references to +the relevant sections. + +## Protocol/Component Description + +Offer a comprehensive explanation of the protocol, covering aspects such as data +flow, communication mechanisms, and any other details necessary for +understanding the inner workings of this component. + +## Message Structure/Communication Format + +If this particular component is expected to communicate over the network, +outline the structure of the message protocol, including details such as field +interpretation, message format, and any other relevant information. + +## Assumptions and Considerations + +If there are any assumptions required for the component's correct operation, +performance, security, or other expected features, outline them here. +Additionally, provide any relevant considerations related to security or other +concerns. + +## Implementation + +Include a link to the location where the implementation of this protocol can be +found. Note that specific implementation details should be documented in the +rollkit repository rather than in the specification document. + +## References + +List any references used or cited in the document. + +## General Tips + +### How to use a mermaid diagram that you can display in a markdown + +```mermaid + +sequenceDiagram + title Example + participant A + participant B + A->>B: Example + B->>A: Example + + ``` + + ```mermaid + +graph LR + A[Example] --> B[Example] + B --> C[Example] + C --> A + + ``` + + ```mermaid + +gantt + title Example + dateFormat YYYY-MM-DD + section Example + A :done, des1, 2014-01-06,2014-01-08 + B :done, des2, 2014-01-06,2014-01-08 + C :done, des3, 2014-01-06,2014-01-08 + + ``` + +### Grammar and spelling check + +The recommendation is to use your favorite spellchecker extension in your IDE like [grammarly], to make sure that the document is free of spelling and grammar errors. + +### Use of links + +If you want to use links use proper syntax. This goes for both internal and external links like [documentation] or [external links] + +At the bottom of the document in [Reference](#references), you can add the following footnotes that will be visible in the markdown document: + +[1] [Grammarly][grammarly] + +[2] [Documentation][documentation] + +[3] [external links][external links] + +Then at the bottom add the actual links that will not be visible in the markdown document: + +[grammarly]: https://www.grammarly.com/ +[documentation]: ../README.md +[external links]: https://github.com/celestiaorg/go-header + +### Use of tables + +If you are describing variables, components or other things in a structured list that can be described in a table use the following syntax: + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `name` | `type` | Description | diff --git a/learn/specs/termination.png b/learn/specs/termination.png new file mode 100644 index 0000000000000000000000000000000000000000..0b61c8f23298531c1ac2bbfa555470fd61fec5d9 GIT binary patch literal 42225 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 H&foe!<-cS1 literal 0 HcmV?d00001 From f17e22f890f342132bab71153fc016d62f6a73de Mon Sep 17 00:00:00 2001 From: pthmas <9058370+pthmas@users.noreply.github.com> Date: Wed, 16 Jul 2025 12:29:35 +0200 Subject: [PATCH 02/13] updated DA page --- learn/specs/da.md | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/learn/specs/da.md b/learn/specs/da.md index a58235286..19f28d340 100644 --- a/learn/specs/da.md +++ b/learn/specs/da.md @@ -1,14 +1,14 @@ # DA -Rollkit provides a wrapper for [go-da][go-da], a generic data availability interface for modular blockchains, called `DAClient` with wrapper functionalities like `SubmitBlocks` and `RetrieveBlocks` to help block manager interact with DA more easily. +Rollkit provides a generic [data availability interface][da-interface] for modular blockchains. Any DA that implements this interface can be used with Rollkit. ## Details -`DAClient` can connect via either gRPC or JSON-RPC transports using the [go-da][go-da] [proxy/grpc][proxy/grpc] or [proxy/jsonrpc][proxy/jsonrpc] implementations. The connection can be configured using the following cli flags: +`Client` can connect via JSON-RPC transports using Rollkit's [jsonrpc][jsonrpc] implementations. The connection can be configured using the following cli flags: -* `--rollkit.da_address`: url address of the DA service (default: "grpc://localhost:26650") -* `--rollkit.da_auth_token`: authentication token of the DA service -* `--rollkit.da_namespace`: namespace to use when submitting blobs to the DA service +* `--rollkit.da.address`: url address of the DA service (default: "grpc://localhost:26650") +* `--rollkit.da.auth_token`: authentication token of the DA service +* `--rollkit.da.namespace`: namespace to use when submitting blobs to the DA service Given a set of blocks to be submitted to DA by the block manager, the `SubmitBlocks` first encodes the blocks using protobuf (the encoded data are called blobs) and invokes the `Submit` method on the underlying DA implementation. On successful submission (`StatusSuccess`), the DA block height which included in the blocks is returned. @@ -17,25 +17,17 @@ To make sure that the serialised blocks don't exceed the underlying DA's blob li The `Submit` call may result in an error (`StatusError`) based on the underlying DA implementations on following scenarios: * the total blobs size exceeds the underlying DA's limits (includes empty blobs) -* the implementation specific failures, e.g., for [celestia-da-json-rpc][proxy/jsonrpc], invalid namespace, unable to create the commitment or proof, setting low gas price, etc, could return error. +* the implementation specific failures, e.g., for [celestia-da-json-rpc][jsonrpc], invalid namespace, unable to create the commitment or proof, setting low gas price, etc, could return error. -The `RetrieveBlocks` retrieves the blocks for a given DA height using [go-da][go-da] `GetIDs` and `Get` methods. If there are no blocks available for a given DA height, `StatusNotFound` is returned (which is not an error case). The retrieved blobs are converted back to blocks and returned on successful retrieval. +The `RetrieveBlocks` retrieves the blocks for a given DA height using `GetIDs` and `Get` methods. If there are no blocks available for a given DA height, `StatusNotFound` is returned (which is not an error case). The retrieved blobs are converted back to blocks and returned on successful retrieval. Both `SubmitBlocks` and `RetrieveBlocks` may be unsuccessful if the DA node and the DA blockchain that the DA implementation is using have failures. For example, failures such as, DA mempool is full, DA submit transaction is nonce clashing with other transaction from the DA submitter account, DA node is not synced, etc. -## Implementation - -See [da implementation] - ## References -[1] [go-da][go-da] - -[2] [proxy/grpc][proxy/grpc] +[1] [da-interface][da-interface] -[3] [proxy/jsonrpc][proxy/jsonrpc] +[2] [jsonrpc][jsonrpc] -[da implementation]: https://github.com/rollkit/rollkit/blob/main/da/jsonrpc/client.go -[go-da]: https://github.com/rollkit/go-da -[proxy/grpc]: https://github.com/rollkit/go-da/tree/main/proxy/grpc -[proxy/jsonrpc]: https://github.com/rollkit/go-da/tree/main/proxy/jsonrpc +[da-interface]: https://github.com/rollkit/rollkit/blob/main/core/da/da.go#L11 +[jsonrpc]: https://github.com/rollkit/rollkit/tree/main/da/jsonrpc From b16e6c3814e02cc4e2f5086b1a23fbed41f6c318 Mon Sep 17 00:00:00 2001 From: pthmas <9058370+pthmas@users.noreply.github.com> Date: Wed, 16 Jul 2025 12:29:53 +0200 Subject: [PATCH 03/13] remove dependency-graph --- learn/specs/dependency-graph.drawio.svg | 4 ---- learn/specs/rollkit-dependency-graph.md | 12 ------------ 2 files changed, 16 deletions(-) delete mode 100644 learn/specs/dependency-graph.drawio.svg delete mode 100644 learn/specs/rollkit-dependency-graph.md diff --git a/learn/specs/dependency-graph.drawio.svg b/learn/specs/dependency-graph.drawio.svg deleted file mode 100644 index d5325378e..000000000 --- a/learn/specs/dependency-graph.drawio.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - -
Header Interface
574
Blocksize independent Commitment
New Rollkit Header
579
Transaction Inclusion Proofs
Celestia Blob Inclusion Proofs
594
P2P Rollkit
Headersync
P2P DA Message Inclusion Proofs
P2P Blocksync
Full Node Soft Confirmations
Full Node Fast UX
Header Pruning
Asynchronous Celestia/Rollkit
Offchain Rollkit Light Clients
362
Multiple Rollup Blocks Per Celestia Block
State Tracing
514
State Execution Fraud Proof generation
514
Query Generation
Deep Subtrees
Start minimal cosmos-sdk with State
Trustminimized P2P Queries 
Satet Execution Fraud Proof verification
Fraud Proof Window
State Execution Fraud Proofs
No Infura
Light Client Wallet
Rollkit Frontend
Indexer
Wallet Builder
Blockchain explorer 
Ignite CLI for Wallet and Explorer defaults
Blockchain explorer builder
Block Pruning
ISR / Transaction serialization
Seriallization Fraud Proof
Centralized Sequencer
Shared Sequencer
Lazy Sequencer
Sequencing API
Light Client Soft Confirmation
Light Client Fast UX
Make Light client lighter
571
DA Interface
Credible Neutrality
Solve Dependency on Celestia Node
Solve Dependcy on cometbft
Proxy App Interface
Remove cometbft Types
Celestia Blob Inclusion Proofs by Index
Transaction / ISR Format 
Rollkit CLI
Celestia-node Fraud Proof Abstraction
Fraud Proof Gossiping 
Fraud Proof Storage
Fraud Proof Syncing
Other DA-Implementations
Fork Choice Interface
Block Storage
Block Sync
Block Gossip
bitcoin-da
avail-da
go-fraud to go-proof rebranding
Proof Gossip
Proof Storage
Proof Syncing
P2P Proof Sync
P2P Fraud Proof Sync
Based
Celestia
FARI
Faud App RU Intefrace
cosmos-sdk runtime module 
Unextend ABCI
ZARI
Zk App RU Interface
ZK-Rollups
PoA
Seperate Data and Header in 2 Namespaces 
Seperate Ordering from Execution
Centralized Execution
JTMB 007 IBC
RPC Equivalency 
conditional light clients
go-square
JTMB 008 IBC
IBC celestia light client for Inclusion Proofs
Canonical IBC light for both bridging and proofs  
JTMB 008 IBC + Inclusion Proofs
Decetralizing Execution
Execution Market
Prover Market 
PoS Exeution
Sequencer Light Node
Conditional Sequencer Light Node 
JTMB Execution 008 IBC + Inclusion Proofs + Ordering Proofs
On-chain Execution Rollkit Light Clients
 008 IBC + Inclusion Proofs + Ordering Proofs + Execution validity 
P2P Blob Inclusion proofs
Namespace Bridging
IBC Header compression
Namespace limit encoding 
Reading in transactions in from multiple namesapces
\ No newline at end of file diff --git a/learn/specs/rollkit-dependency-graph.md b/learn/specs/rollkit-dependency-graph.md deleted file mode 100644 index 234a30fde..000000000 --- a/learn/specs/rollkit-dependency-graph.md +++ /dev/null @@ -1,12 +0,0 @@ -# Rollkit Dependency Graph - -![Dependency Graph](./dependency-graph.drawio.svg) - -We use the following color coding in this Graph: - -- No Colour: Work not yet started -- Yellow Box: Work in progress -- Green Box: Work completed or at least unblocking the next dependency -- Red Border: Work needs to happen in cooperation with another team - -If the EPICs are not linked to the box yet, it means that this box has currently no priority or is still in the ideation phase or the dependency is unclear. From d802271054b606ec9e79b3dab798bee9d4ca39ca Mon Sep 17 00:00:00 2001 From: pthmas <9058370+pthmas@users.noreply.github.com> Date: Wed, 16 Jul 2025 16:42:50 +0200 Subject: [PATCH 04/13] add specs to config.ts --- .vitepress/config.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.vitepress/config.ts b/.vitepress/config.ts index 1abf29877..0f67ed64e 100644 --- a/.vitepress/config.ts +++ b/.vitepress/config.ts @@ -206,10 +206,17 @@ function sidebarHome() { link: "/learn/execution" }, { - text: "Resources", + text: "Technical Specifications", collapsed: true, items: [ - { text: "Technical specifications", link: "/learn/specifications" }, + { text: "template.md", link: "/learn/specs/template" }, + { text: "block-manager.md", link: "/learn/specs/block-manager" }, + { text: "block-validity.md", link: "/learn/specs/block-validity" }, + { text: "da.md", link: "/learn/specs/da" }, + { text: "full_node.md", link: "/learn/specs/full_node" }, + { text: "header-sync.md", link: "/learn/specs/header-sync" }, + { text: "p2p.md", link: "/learn/specs/p2p" }, + { text: "store.md", link: "/learn/specs/store" }, ], }, ], From 90e35a7a64d989f555821880c3964065709c43d9 Mon Sep 17 00:00:00 2001 From: pthmas <9058370+pthmas@users.noreply.github.com> Date: Thu, 17 Jul 2025 16:22:15 +0200 Subject: [PATCH 05/13] remove link page to technical specs --- learn/specifications.md | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 learn/specifications.md diff --git a/learn/specifications.md b/learn/specifications.md deleted file mode 100644 index 2bde150e6..000000000 --- a/learn/specifications.md +++ /dev/null @@ -1,5 +0,0 @@ -# Technical specifications - -[Rollkit specifications](https://rollkit.github.io/rollkit/index.html) - is comprehensive documentation on the inner components of Rollkit, including data storage, transaction processing, and more. It’s an essential resource for developers looking to understand, contribute to and leverage the full capabilities of Rollkit. - -Additional Rollkit documentation can be found in the [Rollkit godocs](https://pkg.go.dev/github.com/rollkit/rollkit). From 084629bc96d113c1ac7e441dc267d1db3adf841f Mon Sep 17 00:00:00 2001 From: Pierrick <9058370+pthmas@users.noreply.github.com> Date: Thu, 17 Jul 2025 16:26:49 +0200 Subject: [PATCH 06/13] Update learn/specs/block-validity.md Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- learn/specs/block-validity.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/learn/specs/block-validity.md b/learn/specs/block-validity.md index dc5820ca0..f6f84dd4a 100644 --- a/learn/specs/block-validity.md +++ b/learn/specs/block-validity.md @@ -30,7 +30,7 @@ SignedHeader.ValidateBasic() // Ensure that someone signed the header verify len(c.Signatures) not 0 // For based rollups (sh.Signer.IsEmpty()), pass validation - If !sh.Signer.IsEmpty(): + if !sh.Signer.IsEmpty(): // Verify the signer matches the proposer address verify sh.Signer.Address == sh.ProposerAddress // Verify signature using custom verifier if set, otherwise use default From bf5087e770fe0c97dee57a13bea609730c8565b9 Mon Sep 17 00:00:00 2001 From: Pierrick <9058370+pthmas@users.noreply.github.com> Date: Thu, 17 Jul 2025 16:27:14 +0200 Subject: [PATCH 07/13] Update learn/specs/block-validity.md Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- learn/specs/block-validity.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/learn/specs/block-validity.md b/learn/specs/block-validity.md index f6f84dd4a..f06e569e3 100644 --- a/learn/specs/block-validity.md +++ b/learn/specs/block-validity.md @@ -105,7 +105,7 @@ SignedHeader.Verify(untrustedHeader *SignedHeader) | **Field Name** | **Valid State** | **Validation** | |---------------------|--------------------------------------------------------------------------------------------|---------------------------------------| -| **BaseHeader** . | | | +| **BaseHeader** | | | | Height | Height of the previous accepted header, plus 1. | checked in the `Verify()`` step | | Time | Timestamp of the block | Not validated in Rollkit | | ChainID | The hard-coded ChainID of the chain | Should be checked as soon as the header is received | From 651283869612a8798287ca32f03ba3ebbfbbc1b3 Mon Sep 17 00:00:00 2001 From: Pierrick <9058370+pthmas@users.noreply.github.com> Date: Thu, 17 Jul 2025 16:27:39 +0200 Subject: [PATCH 08/13] Update learn/specs/store.md Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- learn/specs/store.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/learn/specs/store.md b/learn/specs/store.md index 9488eef5a..45386461b 100644 --- a/learn/specs/store.md +++ b/learn/specs/store.md @@ -25,7 +25,7 @@ Note: While blocks are stored as complete units in the store, the block manager - `SaveValidators`: Saves the validator set at a given height. - `GetValidators`: Returns the validator set at a given height. -The `TxnDatastore` interface inside [go-datastore] is used for constructing different key-value stores for the underlying storage of a full node. The are two different implementations of `TxnDatastore` in [kv.go]: +The `TxnDatastore` interface inside [go-datastore] is used for constructing different key-value stores for the underlying storage of a full node. There are two different implementations of `TxnDatastore` in [kv.go]: - `NewDefaultInMemoryKVStore`: Builds a key-value store that uses the [BadgerDB] library and operates in-memory, without accessing the disk. Used only across unit tests and integration tests. From 4919db78f3c318fe5d3c05e817af248f56566e6f Mon Sep 17 00:00:00 2001 From: Pierrick <9058370+pthmas@users.noreply.github.com> Date: Thu, 17 Jul 2025 16:27:53 +0200 Subject: [PATCH 09/13] Update learn/specs/store.md Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- learn/specs/store.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/learn/specs/store.md b/learn/specs/store.md index 45386461b..b92c8c271 100644 --- a/learn/specs/store.md +++ b/learn/specs/store.md @@ -80,7 +80,7 @@ See [Store Interface][store_interface] and [Default Store][default_store] for it [7] [Key Value Store][kv.go] -[8 ] [Serialization][serialization] +[8] [Serialization][serialization] [store_interface]: https://github.com/rollkit/rollkit/blob/main/pkg/store/types.go#L11 [default_store]: https://github.com/rollkit/rollkit/blob/main/pkg/store/store.go From b3930f61effc433f504bd52e3a593319136494c4 Mon Sep 17 00:00:00 2001 From: Pierrick <9058370+pthmas@users.noreply.github.com> Date: Thu, 17 Jul 2025 16:28:18 +0200 Subject: [PATCH 10/13] Update learn/specs/template.md Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- learn/specs/template.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/learn/specs/template.md b/learn/specs/template.md index 1e170b3bc..2effeb880 100644 --- a/learn/specs/template.md +++ b/learn/specs/template.md @@ -80,7 +80,7 @@ The recommendation is to use your favorite spellchecker extension in your IDE li If you want to use links use proper syntax. This goes for both internal and external links like [documentation] or [external links] -At the bottom of the document in [Reference](#references), you can add the following footnotes that will be visible in the markdown document: +At the bottom of the document in the [References](#references) section, you can add the following footnotes that will be visible in the markdown document: [1] [Grammarly][grammarly] From a43ca0bc21298d21de63942e9ee36129c44617e7 Mon Sep 17 00:00:00 2001 From: Marko Date: Thu, 17 Jul 2025 17:11:07 +0200 Subject: [PATCH 11/13] Apply suggestion from @gemini-code-assist[bot] Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- learn/specs/header-sync.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/learn/specs/header-sync.md b/learn/specs/header-sync.md index dca0d81b8..f497ef65b 100644 --- a/learn/specs/header-sync.md +++ b/learn/specs/header-sync.md @@ -7,7 +7,7 @@ The nodes in the P2P network sync headers and data using separate sync services |Component|Description| |---|---| |store| a prefixed [datastore][datastore] where synced items are stored (`headerSync` prefix for headers, `dataSync` prefix for data)| -|subscriber | a [libp2p][libp2p] node pubsub subscriber for the specific data type| +|subscriber| a [libp2p][libp2p] node pubsub subscriber for the specific data type| |P2P server| a server for handling requests between peers in the P2P network| |exchange| a client that enables sending in/out-bound requests from/to the P2P network| |syncer| a service for efficient synchronization. When a P2P node falls behind and wants to catch up to the latest network head via P2P network, it can use the syncer.| From b92944631350b068dd78d75fe14079139756171a Mon Sep 17 00:00:00 2001 From: pthmas <9058370+pthmas@users.noreply.github.com> Date: Thu, 17 Jul 2025 17:30:03 +0200 Subject: [PATCH 12/13] remove template for the docs --- .vitepress/config.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/.vitepress/config.ts b/.vitepress/config.ts index 0f67ed64e..1ed501dca 100644 --- a/.vitepress/config.ts +++ b/.vitepress/config.ts @@ -209,7 +209,6 @@ function sidebarHome() { text: "Technical Specifications", collapsed: true, items: [ - { text: "template.md", link: "/learn/specs/template" }, { text: "block-manager.md", link: "/learn/specs/block-manager" }, { text: "block-validity.md", link: "/learn/specs/block-validity" }, { text: "da.md", link: "/learn/specs/da" }, From 1315a55b5064e36801631c0f083d7bdb9c7791b7 Mon Sep 17 00:00:00 2001 From: pthmas <9058370+pthmas@users.noreply.github.com> Date: Thu, 17 Jul 2025 17:31:32 +0200 Subject: [PATCH 13/13] add overview and update config.ts --- .vitepress/config.ts | 15 ++++++++------- specs/overview.md | 17 +++++++++++++++++ 2 files changed, 25 insertions(+), 7 deletions(-) create mode 100644 specs/overview.md diff --git a/.vitepress/config.ts b/.vitepress/config.ts index 1ed501dca..276fba0fa 100644 --- a/.vitepress/config.ts +++ b/.vitepress/config.ts @@ -209,13 +209,14 @@ function sidebarHome() { text: "Technical Specifications", collapsed: true, items: [ - { text: "block-manager.md", link: "/learn/specs/block-manager" }, - { text: "block-validity.md", link: "/learn/specs/block-validity" }, - { text: "da.md", link: "/learn/specs/da" }, - { text: "full_node.md", link: "/learn/specs/full_node" }, - { text: "header-sync.md", link: "/learn/specs/header-sync" }, - { text: "p2p.md", link: "/learn/specs/p2p" }, - { text: "store.md", link: "/learn/specs/store" }, + { text: "overview", link: "/learn/specs/overview" }, + { text: "block-manager", link: "/learn/specs/block-manager" }, + { text: "block-validity", link: "/learn/specs/block-validity" }, + { text: "da", link: "/learn/specs/da" }, + { text: "full_node", link: "/learn/specs/full_node" }, + { text: "header-sync", link: "/learn/specs/header-sync" }, + { text: "p2p", link: "/learn/specs/p2p" }, + { text: "store", link: "/learn/specs/store" }, ], }, ], diff --git a/specs/overview.md b/specs/overview.md new file mode 100644 index 000000000..6de489024 --- /dev/null +++ b/specs/overview.md @@ -0,0 +1,17 @@ +# Specs Overview + +Welcome to the Rollkit Technical Specifications. + +This is comprehensive documentation on the inner components of Rollkit, including data storage, transaction processing, and more. It’s an essential resource for developers looking to understand, contribute to, and leverage the full capabilities of Rollkit. + +Each file in this folder covers a specific aspect of the system, from block management to data availability and networking. Use this page as a starting point to explore the technical details and architecture of Rollkit. + +## Table of Contents + +- [Block Manager](/learn/specs/block-manager.md): Explains the responsibilities and logic of the block manager in Rollkit. +- [Block Validity](/learn/specs/block-validity.md): Details the rules and checks for block validity within the protocol. +- [Data Availability (DA)](/learn/specs/da.md): Describes how Rollkit ensures data availability and integrates with DA layers. +- [Full Node](/learn/specs/full_node.md): Outlines the architecture and operation of a full node in Rollkit. +- [Header Sync](/learn/specs/header-sync.md): Covers the process and protocol for synchronizing block headers. +- [P2P](/learn/specs/p2p.md): Documents the peer-to-peer networking layer and its protocols. +- [Store](/learn/specs/store.md): Provides information about the storage subsystem and data management.