Sync Specifications
Synchronization Specifications
Transaction data can be indexed from either L1 or L2. This data is used for doing the execution process which generates the state. Synchronization is the process of scanning the network for new transactions and changes in the blockchain that helps us guarantee the information of transactions and their authenticity.
The benefit of synching transactions from L2 is that we can sync the data before the transactions are batch submitted.
The benefit of synching from L2 is that the transactions are all final which will provide a fraud-proof state.
Retrieving Relevant Data
We need to retrieve the following information reliably in order to perform a reliable synchronization:
All transactions enqueued for inclusion in the CanonicalTransactionChain that are in the order in which they were enqueued.
All transactions included in the CanonicalTransactionChain that are in the order in which they were included.
All state roots included in the StateCommitmentChain that are in the order in which they were included.
All relevant data can be retrieved by parsing the data from the following functions:
CanonicalTransactionChain.enqueue
CanonicalTransactionChain.appendQueueBatch
CanonicalTransactionChain.appendSequencerBatch
StateCommitmentChain.appendStateBatch
Enqueued Transactions
Note that transactions are “enqueued” when users make calls to the CanonicalTransactionChain.enqueue
function.
All function calls to this function can be detected by searching for TransactionEnqueued events. All relevant data for transactions can be pulled out of that specific event, and here is a pseudocode function for doing this job:
Altogether, the process of parsing and indexing the data is pretty straightforward:
Listen to TransactionEnqueued events.
Parse each found event into EnqueuedTransaction structures.
Store each event based on its queueIndex field.
Transactions Via appendQueueBatch
:::Note that the appendQueueBatch
is currently disabled on the mainnet.:::
Transactions can be inserted into the Canonical Transaction Chain using either the appendQueueBatch
or appendSequencerBatch
functions.
appendQueueBatch
is used to move enqueued transactions to a canonical set of transactions. Until the process is done, these enqueued transactions are not considered as a part of the L2 history.
Once the appendQueueBatch
function has been called, a TransactionBatchAppended
event is emitted. This is followed by a QueueBatchAppended
event in the same transaction. (The index of the second event is equal to the index of the first event plus one.)
These types of transactions do not include the complete forms of various transactions that were appended by the function call. As a result, we need to pull the required information from a combination of events. Also, we can count on EnqueuedTransaction
objects that have been previously parsed from the enqueue
process.
Here are some lines of pseudocode for parsing this list of transactions that provide us with the required information:
Transactions Via appendSequencerBatch
Using appendSequencerBatch
is another method in which transactions can be inserted into the Canonical Transaction Chain. The main benefit of appendSequencerBatch
is that it uses a custom encoding scheme in order to enhance efficiency and it does not have any explicit parameters.
The function parses calldata
internally, directly using the following format:
Bytes 0-3 (4 bytes) of
calldata
are the 4-byte function selector derived from keccak256 (“appendSequencerBatch()”). Just skip these four bytes.Bytes 4-8 (5 bytes; uint40) describe the index of the Canonical Transaction Chain that the batch of transactions expects to follow.
Bytes 9-11 (3 bytes; uint24) are the total number of elements that the sequencer wants to append to the chain.
Bytes 12-14 (3 bytes; uint24) are the total number of “batch contexts” that are effective timestamp/block numbers to be assigned to a given set of transactions.
After byte 14, we have a series of encoded “batch contexts”. Each batch context is exactly 16 bytes. Note that the number of contexts comes from the bytes 12-14.
Each context follows the structure stated below:
Bytes 0-2 (3 bytes; uint24) are the number of sequencer transactions that will use this batch context.
Bytes 3-5 (3 bytes; uint24) are the number of queue transactions that will be inserted into the chain after the above-mentioned sequencer transactions.
Bytes 6-10 (5 bytes; uint40) are a timestamp that will be assigned to the above-mentioned sequencer transactions.
Bytes 11-15 (5 bytes; uint40) are the block number that will be assigned to the above-mentioned sequencer transactions.
After the batch context section, we have a series of dynamically sized transactions. Each transaction consists of the following information:
Bytes 0-2 (3 bytes; uint24) are the total size of the coming transaction data.
Some arbitrary data of a length equal to that, described by the first 3 bytes.
We can represent the input as an object roughly equivalent to the following json-ish piece of code:
The decoding function in the pseudocode format is as follows:
The encoding function in the pseudocode format is as follows:
The Method
When the sequencer calls appendQueueBatch
, contexts are processed one by one.
We first append
numSequencedTransactions
for each of these contexts which are popped off the transactions array.
Note that each of these transactions has the following format:
Next, we pull
numSubsequentQueueTransactions
in from the queue by referencing the queue. The process should be repeated for every provided context.
Note the following points when working with this process:
appendSequencerBatch
can be detected and parsed by looking for SequencerBatchAppended events. Each of these events will always be immediately preceded by a TansactionBatchAppended event.Somewhat like with
appendQueueBatch
, we have to be careful in pulling all the relevant information out from these two events and from the previously parsedEnqueuedTransactions
.We need access to the
calldata
sent toappendSequencerBatch
. We can retrieve the data by making a call to the debug_traceTransaction or an equivalent endpoint that exposes call traces. In the case that a client does not have access to thedebug_traceTransaction
endpoint, thecalldata
can only be retrieved ifappendSequencerBatch
is called directly. This call is by an externally owned account.When a client detects a
SequencerBatchAppended
event, they should pull the precedingTransactionBatchAppended
event. Then they should retrieve thecalldata
and decode it using the above-mentioned decoding scheme.The client should validate the input under the assumption that data from the L1 node is not reliable.
The code for parsing the event in the pseudocode format is as follows:
Please feel free to reach out to our Help Center if you have any technical questions.
Last updated