Networking

Wire Format

AvailableData

nametypedescription
availableDataRowsAvailableDataRow[]List of rows.

AvailableDataRow

nametypedescription
sharesShare[]Shares in a row.

ConsensusProposal

Defined as ConsensusProposal:

message ConsensusProposal {
  SignedMsgType type = 1;
  int32 round = 2;
  int32 pol_round = 3;
  // 32-byte hash
  // Proposed block header
  Header header = 4;
  AvailableDataHeader da_header = 5;
  // 64-byte signature
  bytes proposer_signature = 6;
}

When receiving a new block proposal proposal from the network, the following steps are performed in order. Must indicates that peers must be blacklisted (to prevent DoS attacks) and should indicates that the network blob can simply be ignored.

  1. proposal.type must be a SignedMsgType.
  2. proposal.round is processed identically to Tendermint.
  3. proposal.pol_round is processed identically to Tendermint.
  4. proposal.header must be well-formed.
  5. proposal.header.version.block must be VERSION_BLOCK.
  6. proposal.header.version.app must be VERSION_APP.
  7. proposal.header.height should be previous known height + 1.
  8. proposal.header.chain_id must be CHAIN_ID.
  9. proposal.header.time is processed identically to Tendermint.
  10. proposal.header.last_header_hash must be previous block's header hash.
  11. proposal.header.last_commit_hash must be the previous block's commit hash.
  12. proposal.header.consensus_hash must be the hash of consensus parameters.
  13. proposal.header.state_commitment must be the state root after applying the previous block's transactions.
  14. proposal.header.available_data_original_shares_used must be at most AVAILABLE_DATA_ORIGINAL_SQUARE_MAX ** 2.
  15. proposal.header.available_data_root must be the root of proposal.da_header.
  16. proposal.header.proposer_address must be the correct leader.
  17. proposal.da_header must be well-formed.
  18. The number of elements in proposal.da_header.row_roots and proposal.da_header.row_roots must be equal.
  19. The number of elements in proposal.da_header.row_roots must be the same as computed here.
  20. proposal.proposer_signature must be a valid digital signature over the header hash of proposal.header that recovers to proposal.header.proposer_address.
  21. For full nodes, proposal.da_header must be the result of computing the roots of the shares (received separately).
  22. For light nodes, proposal.da_header should be sampled from for availability.

MsgWirePayForData

Defined as MsgWirePayForData:

message MsgWirePayForData {
  TransactionFee fee = 1;
  uint64 nonce = 2;
  // 8-byte namespace ID
  bytes message_namespace_id = 3;
  uint64 message_size = 4;
  bytes message = 5;
  repeated MessageCommitmentAndSignature message_commitment_and_signature = 6;
}

Accepting a MsgWirePayForData into the mempool requires different logic than other transactions in Celestia, since it leverages the paradigm of block proposers being able to malleate transaction data. Unlike SignedTransactionDataMsgPayForData (the canonical data type that is included in blocks and committed to with a data root in the block header), each MsgWirePayForData (the over-the-wire representation of the same) has potentially multiple signatures.

Transaction senders who want to pay for a blob will create a SignedTransactionDataMsgPayForData object, stx, filling in the stx.blobShareCommitment field based on the blob share commitmentrules, then signing it to get a transaction tx.

Receiving a MsgWirePayForData object from the network follows the reverse process: verify using the blob share commitmentrules that the signature is valid.

Invalid Erasure Coding

If a malicious block producer incorrectly computes the 2D Reed-Solomon code for a block's data, a fraud proof for this can be presented. We assume that the light clients have the AvailableDataHeader and the Header for each block. Hence, given a ShareProof, they can verify if the rowRoot or colRoot specified by isCol and position commits to the corresponding Share. Similarly, given the height of a block, they can access all elements within the AvailableDataHeader and the Header of the block.

ShareProof

nametypedescription
shareShareThe share.
proofNamespaceMerkleTreeInclusionProofThe Merkle proof of the share in the offending row or column root.
isColboolA Boolean indicating if the proof is from a row root or column root; false if it is a row root.
positionuint64The index of the share in the offending row or column.

BadEncodingFraudProof

Defined as BadEncodingFraudProof:

// https://github.com/celestiaorg/celestia-specs/blob/master/specs/networking.md#badencodingfraudproof
message BadEncodingFraudProof {
  // height of the block with the offending row or column
  int64 height = 1;
  // the available shares in the offending row or column and their Merkle proofs
  // array of ShareProofs
  repeated ShareProof shareProofs = 2;
  // a Boolean indicating if it is an offending row or column; false if it is a row
  bool isCol = 3;
  // the index of the offending row or column in the square
  uint64 position = 4;
}
nametypedescription
heightHeightHeight of the block with the offending row or column.
shareProofsShareProof[]The available shares in the offending row or column.
isColboolA Boolean indicating if it is an offending row or column; false if it is a row.
positionuint64The index of the offending row or column in the square.

Invalid State Update

If a malicious block producer incorrectly computes the state, a fraud proof for this can be presented. We assume that the light clients have the AvailableDataHeader and the Header for each block. Hence, given a ShareProof, they can verify if the rowRoot or colRoot specified by isCol and position commits to the corresponding Share. Similarly, given the height of a block, they can access all elements within the AvailableDataHeader and the Header of the block.

StateFraudProof

Defined as StateFraudProof:

// https://github.com/celestiaorg/celestia-specs/blob/master/specs/networking.md#statefraudproof
message StateFraudProof {
  // height of the block with the intermediate state roots 
  // Subtracting one from height gives the height of the block with the transactions.
  int64 height = 1;
  // shares containing the transactions and their Merkle proofs
  // isCol within the ShareProof must be false.
  // array of ShareProofs
  repeated ShareProof transactionShareProofs = 2;
  // shares containing the intermediate state roots and their Merkle proofs
  // isCol within the ShareProof must be false.
  // array of ShareProofs
  repeated ShareProof isrShareProofs = 3;
  // index for connecting the WrappedIntermediateStateRoot and WrappedTransaction after shares are parsed
  uint64 index = 4;
  // state elements that were changed by the transactions
  // array of StateElements
  repeated StateElement intermediateStateElements = 5;
  // sparse Merkle tree inclusion proofs for the state elements
  // array of SparseMerkleTreeInclusionProofs
  repeated SparseMerkleTreeInclusionProof stateInclusionProofs = 6;
}
nametypedescription
heightHeightHeight of the block with the intermediate state roots. Subtracting one from height gives the height of the block with the transactions.
transactionShareProofsShareProof[]isCol of type bool must be false.
isrShareProofsShareProof[]isCol of type bool must be false.
indexuint64Index for connecting the WrappedIntermediateStateRoot and WrappedTransaction after shares are parsed.