Submitter
Submitter service responsibilities, endpoints, data formats, and integrations
Submitter
Overview
The submitter is a lightweight HTTP service responsible for:
- Compressing and publishing batch results to a Data Availability layer (Turbo DA).
- Submitting a compact commitment (accumulator root, TEE signature, recovery id) to the blockchain. The DA reference is passed to the chain client but is not persisted on-chain in the current implementation.
- Forwarding withdraw requests to the on-chain program.
It exposes two POST endpoints and delegates DA and chain interactions to dedicated clients. Configuration is read via the shared configuration loader and mapped to environment variables for the chain client.
Batch Submission Workflow
The following diagram shows the complete flow from Processor to DA layer and blockchain:
Submission Steps:
- Processor sends BatchTreeSubmission with accumulator root, TEE signature, recovery_id, and batch trees
- Submitter compresses batch_trees (JSON → zlib → base64)
- Submitter publishes compressed data to Turbo DA with
Content-Type: application/octet-stream - DA returns submission_id for retrieval reference
- Submitter sets Solana environment variables and submits commitment to blockchain
- Chain client submits accumulator root (32B), signature (64B), recovery_id (1B) on-chain
- Return success with DA submission_id
Input: BatchTreeSubmission (from Processor accumulator job) Output: DA submission_id + on-chain transaction confirmation
Endpoints
POST /v1/submit_batch
- Request body:
BatchTreeSubmission(see Data Formats). - Processing steps:
- Serialize
batch_treesto JSON usingserde_json. - Compress with zlib (default compression level via
flate2). - Base64-encode the compressed data (standard alphabet).
- Submit the base64 string to Turbo DA with
Content-Type: application/octet-streamheader and receivesubmission_id. - Set
da_submission_idin theBatchTreeSubmissionobject. - Set Solana environment variables (
SOLANA_RPC_URL,SOLANA_SUBMITTER_KEYPAIR_PATH,SOLANA_CLUSTER,SOLANA_CORE_MANAGER_PROGRAM_ID) from config and removeMOCK_SOLANA_TX. - Submit accumulator root (32 bytes), signature (64 bytes), and recovery id (1 byte) to blockchain via
BatchSubmitter::submit_batch_with_retry. Thesubmission_idis passed to the chain client but not included in the on-chain instruction data.
- Serialize
- Response (200):
{ "status": "success", "turbo_da_submission_id": "<string>", "message": "Batch submitted to Turbo DA and blockchain" } - Mock mode: Set
AVAIL_DA_MOCK=trueto skip DA submission and generate synthetic IDmock_da_<timestamp>. Blockchain submission still executes.
POST /v1/withdraw_request
- Request body:
WithdrawlRequest(fromchain_client::types). - Processing steps:
- Create
BatchSubmitterinstance (reads Solana config from environment variables). - Forward request to
BatchSubmitter::submit_withdrawl_request_with_retry(up to 3 retry attempts). - Return the original request payload as JSON upon success.
- Create
- Note: This endpoint does not modify
MOCK_SOLANA_TXenvironment variable. The batch submitter checks forMOCK_SOLANA_TXduring execution to determine whether to send real transactions or return mock transaction IDs.
Data Availability Integration
Turbo DA Client
The submitter uses TurboDAClient to interact with Turbo DA for off-chain data storage.
Endpoints:
- Submit data:
POST {base_url}/v1/submit_raw_data- Headers:
x-api-key,Content-Type: application/octet-stream - Body: String data converted to bytes via
.into_bytes() - Returns:
{ "submission_id": "<string>" }
- Headers:
- Retrieve data:
GET {base_url}/v1/get_pre_image?submission_id=<id>- Headers:
x-api-key - Returns: Raw bytes
- Headers:
- Get submission info:
GET {base_url}/v1/get_submission_info?submission_id=<id>- Headers:
x-api-key - Returns:
{ "block_hash": "<string>", "block_number": <u64>, "tx_hash": "<string>", "state": "<string>" }
- Headers:
Configuration (from config.toml via turbo_da section):
api_key(required): Authentication key for Turbo DA APIapp_id(required): Application identifierapi_url(optional): Base URL, defaults tohttps://staging.turbo-api.availproject.org
Error handling: Non-2xx responses return detailed errors with HTTP status code and response text.
Implementation:
// Trait definition [da_client.rs:15-25]
#[async_trait::async_trait]
pub trait DAClient: Send + Sync {
async fn submit_data(&self, data: String) -> anyhow::Result<String>;
async fn get_data(&self, submission_id: &str) -> anyhow::Result<Vec<u8>>;
}
// Client struct [da_client.rs:41-46]
pub struct TurboDAClient {
client: Client,
api_key: String,
app_id: String,
base_url: String,
}
// Additional method [da_client.rs:75-112]
impl TurboDAClient {
pub async fn new() -> anyhow::Result<Self> { /* loads config */ }
pub async fn get_submission_info(&self, submission_id: &str)
-> anyhow::Result<TurboDASubmissionInfo> { /* ... */ }
}
// Response types [da_client.rs:28-39]
pub struct TurboDASubmissionResponse {
pub submission_id: String,
}
pub struct TurboDASubmissionInfo {
pub block_hash: String,
pub block_number: u64,
pub tx_hash: String,
pub state: String,
}Blockchain Submission
The submitter delegates blockchain transactions to chain_client::BatchSubmitter:
- Batch commitment: Calls
submit_batch_with_retry(signature, recovery_id, acc, Some(submission_id))with up to 3 retry attempts. - Withdraw request: Calls
submit_withdrawl_request_with_retry(withdrawl_request)with up to 3 retry attempts.
Implementation:
// Struct [batch_submitter.rs:29-36]
pub struct BatchSubmitter {
client: RpcClient,
submitter_keypair: Keypair,
cluster: String,
core_manager_program_id: Pubkey,
ewrapper_program_id: Pubkey,
}
// Methods [batch_submitter.rs:38-527]
impl BatchSubmitter {
pub fn new() -> Result<Self, Box<dyn std::error::Error>> { /* reads env vars */ }
pub async fn submit_batch_with_retry(
&self,
signature: Vec<u8>,
recovery_id: u8,
acc: String,
turbo_da_submission_id: Option<&str>,
) -> Result<(), Box<dyn std::error::Error>>;
pub async fn submit_withdrawl_request_with_retry(
&self,
withdrawl_request: WithdrawlRequest,
) -> Result<(), Box<dyn std::error::Error>>;
}Environment Variables
The batch submission endpoint sets these environment variables from config.toml [solana] section before creating BatchSubmitter:
SOLANA_RPC_URL- Solana RPC endpointSOLANA_SUBMITTER_KEYPAIR_PATH- Path to submitter keypair fileSOLANA_CLUSTER- Cluster name (e.g., "devnet")SOLANA_CORE_MANAGER_PROGRAM_ID- Core manager program public keyMOCK_SOLANA_TX- Explicitly removed viaenv::remove_var()to ensure real transactions
The withdraw endpoint does not set environment variables; it relies on existing values.
BatchSubmitter::new() reads these additional variables:
SOLANA_EWORAPPER_PROGRAM_ID- Ewrapper program ID (defaults toCRx1GzdrrivpkxqasdMFXM4A5eQ9p3ktXTmrwLuhW4Rmif unset)MOCK_SOLANA_TX- If present, returns mock transaction IDs instead of submitting to chain
Current Behavior
- Retry logic:
MAX_RETRIES = 3attempts for both batch and withdraw submissions [batch_submitter.rs:26] - DA reference handling: The
submission_idis passed tosubmit_batch_with_retrybut marked as unused (_turbo_da_submission_id) in the instruction builder. Only accumulator root (32 bytes), signature (64 bytes), and recovery id (1 byte) are included in on-chain instruction data [batch_submitter.rs:262, 480-490] - Signature validation: The instruction builder validates signature length is exactly 64 bytes [batch_submitter.rs:482-488]
- Mocking: Batch submission path removes
MOCK_SOLANA_TX; withdraw path leaves it unchanged but still checks the variable during transaction building
Server
- Framework:
warpHTTP framework [main.rs:8] - Binding: Listens on
127.0.0.1:<port>where port is read fromconfig.submitter.port[main.rs:27-36] - Routes:
POST /v1/submit_batch- Batch submission handler [main.rs:15-19]POST /v1/withdraw_request- Withdraw request handler [main.rs:21-25]
- Logging: Uses
warp::log("submitter")middleware [main.rs:34]
Data Formats
BatchTreeSubmission
Defined in encifher_core::types [core/types.rs:1019-1025]:
pub struct BatchTreeSubmission {
pub acc: String, // Accumulator root (hex string, 0x-prefixed)
pub signature: Vec<u8>, // TEE ECDSA signature (64 bytes)
pub recovery_id: u8, // secp256k1 recovery ID
pub batch_trees: Vec<BatchTree>, // Merkle trees serialized to DA
pub da_submission_id: Option<String>, // Set by submitter after DA publish
}BatchTree
Defined in encifher_core::types [core/types.rs:1001-1004]:
pub struct BatchTree {
pub tree: SimpleMerkleTree<Keccak256>, // From chain_client::types
pub request_and_cipher: Vec<RequestAndCiphertext>, // Request params + encrypted results
}WithdrawlRequest
Defined in chain_client::types [chain_client/types.rs:25-31]:
pub struct WithdrawlRequest {
pub amount: u128,
pub signature: Vec<u8>,
pub plaintext_amount: u64,
pub mint: Vec<u8>,
pub to_token_account: Vec<u8>,
}Data Processing Pipeline
- Serialization: JSON via
serde_json::to_vec()[submit_batch.rs:38] - Compression: zlib using
flate2::write::ZlibEncoderwithCompression::default()[submit_batch.rs:41] - Encoding: Base64 (standard alphabet) via
base64::engine::general_purpose::STANDARD[submit_batch.rs:2, 27] - Transmission: String data converted to bytes with
Content-Type: application/octet-stream[da_client.rs:133-134]
Public Interfaces
HTTP API
- POST /v1/submit_batch: Accepts JSON
BatchTreeSubmission, returns submission result - POST /v1/withdraw_request: Accepts JSON
WithdrawlRequest, returns original request on success
DA Client Interface
trait DAClient: Send + Sync {
async fn submit_data(&self, data: String) -> anyhow::Result<String>;
async fn get_data(&self, submission_id: &str) -> anyhow::Result<Vec<u8>>;
}Implementation: TurboDAClient with config-based initialization and API key authentication
Chain Client Interface
BatchSubmitter::submit_batch_with_retry()- Batch root submission to SolanaBatchSubmitter::submit_withdrawl_request_with_retry()- Withdraw callback to ewrapper program- Configuration via environment variables (set by batch endpoint, read by BatchSubmitter)
Configuration
config.toml
[submitter]
port = 8082 # HTTP server port
[turbo_da]
api_key = "your_api_key" # Required
app_id = "your_app_id" # Required
api_url = "https://..." # Optional (defaults to staging URL)
[solana]
rpc_url = "https://api.devnet.solana.com"
submitter_keypair_path = "./solana-keys/submitter-keypair.json"
cluster = "devnet"
core_manager_program_id = "FCZY1nTfhH8V8aaZpZ5YpxBb2JGcUvtxGRAcZxkUYKNr"
# Note: ewrapper_program_id is optional (has default value)Environment Variables
Set by submitter (batch endpoint only) [submit_batch.rs:87-98]:
SOLANA_RPC_URLSOLANA_SUBMITTER_KEYPAIR_PATHSOLANA_CLUSTERSOLANA_CORE_MANAGER_PROGRAM_ID
Read by BatchSubmitter [batch_submitter.rs:42-50]:
- All of the above (required)
SOLANA_EWORAPPER_PROGRAM_ID(optional, defaults toCRx1GzdrrivpkxqasdMFXM4A5eQ9p3ktXTmrwLuhW4Rm)
Mock/Test modes:
AVAIL_DA_MOCK=true- Skip actual DA submission, return mock ID [submit_batch.rs:53]MOCK_SOLANA_TX=<any>- Return mock transaction signatures instead of submitting [batch_submitter.rs:289, 375]
Error Handling
HTTP layer [submit_batch.rs:9-12, submit_decryption.rs:8]:
- Errors wrapped in
warp::reject::custom(WarpError)with descriptive messages - Serialization errors:
"Serialization error: {e}" - Compression errors:
"Compression error: {e}" - DA client errors:
"DA client error: {e}"or"DA submission failed: {e}" - Chain client errors:
"Batch submitter error: {e}"or"Blockchain submission failed: {e}"
DA client [da_client.rs:93-100, 138-145, 174-181]:
- Non-2xx responses return detailed errors with HTTP status code and response text
- Example:
"Turbo DA submission failed (404): Not found"
Chain client [batch_submitter.rs:132-163, 324-355]:
- Retry logic logs each failure with attempt number
- Final error after MAX_RETRIES:
"Max retries (3) reached. Last error: {e}"
Tests
Turbo DA Integration [turbo_da_integration_test.rs]:
-
test_turbo_da_integration()- Round-trip test (submit → retrieve → verify)- Skipped if
TURBO_DA_API_KEYenvironment variable not set [line 7-9] - Tests data integrity by comparing submitted and retrieved data
- Skipped if
-
test_turbo_da_client_creation()- Client initialization- Creates
TurboDAClientusingconfig.toml[turbo_da] section - Note: Client reads from config file, NOT from
TURBO_DA_API_KEYenv var [da_client.rs:50-55] - Test may fail if config file missing or incomplete
- Creates