python/pyproject.toml in the blockx repository pins v0.1.72); the last section shows how to call WorkerService directly from Go without the SDK.
Prerequisites
- Local worker
- Production environment
Start the worker and
examples/local_test_service/local_proxy.py as described in the Quickstart. Example 1 runs locally as-is; examples 2 to 4 read and write BlockDB tables, so point BLOCKDB_*_ADDR at a reachable BlockDB and leave STUB_BLOCKDB_DELAY_MS unset.PROXY_SOCKET_PATH. If the variable is not set, it raises RuntimeError immediately rather than silently connecting elsewhere.
Example 1: run once and get the result back
InputsCallConfig + ReturnValueHandler. This is examples/local_test_service/submit.py:
LocalTestServiceis a sample capability backend built into the worker; no BlockDB is needed locally. Any self-contained pure-computation function works just as well.- Results are collected in the completion order of the individual calls and are not guaranteed to match the row order of
callList. When you need to map results back to inputs, have the return value carry the input. - A task with
ReturnValueHandleris routed to the block cluster by the SDK, because the bundle worker does not register this handler.
Example 2: compute per block and write a block table
BlockTableCallConfig + BlockTableWriteHandler(block=...). Subscribe to new blocks of the source table and submit one task per block:
- When you pass a callable, the SDK captures the function body with
inspect.getsourceand appends the line_ = trace_to_token_transfer; you can also passFunction(source_code=...)directly, or a registered function ID string. paramsdetermines the function’s positional arguments: herechain_id="eth", andrecordis one row of the source table as a dict.BlockTableWriteHandlerprojects only the columns of the returned dict that belong to the target table;written_rowsis the number of rows written this time.- You can add
"operator": filter(...)totriggerSourcesto filter and deduplicate in the Builder phase first and reduce the number of calls.
Example 3: write a normal table
Swap the handler in example 2 forNormalTableWriteHandler and the target changes from a block table to a normal table (L1). It does not need block:
condition.column cannot be the primary key id; an invalid value raises ValueError at construction time. This handler carries no block context, so the same instance can be handed directly to the backfill in example 4.
Example 4: backfill per bundle
Historical backfill submits one task per bundle (a segment of 1000 blocks):BlockBundleCallConfig reads the parquet of the bundle partition, and BlockTableWriteHandler(block_bundle=...) overwrites the same bundle of the target table. backfill_block_bundles instantiates and submits bundle by bundle over a range for you:
The shape of
BlockTableWriteHandler must follow the cluster: the bundle cluster needs block_bundle=, the block cluster needs block=. If you send the wrong shape, the server only rejects it in the Writer phase after all calls have run, so the SDK validates before submit() and raises ValueError.Pipeline: one set of triggers + target_table, backfill(block_start, block_end) for historical backfill, and update() to fill gaps first and then switch to a real-time subscription. Bundle conversion, cluster routing, slot backoff, and multi-table alignment are all handled for you:
Reading the result
task.submit() returns a TaskResult:
Decide whether to resubmit based on
retryable; do not infer it from the error text:
Task.task_id is a fresh uuid on every build; log it when troubleshooting, since the worker’s task finished log entries are searchable by it.
Without the SDK: call gRPC directly
Go-side contributors often need to submit tasks directly to a Worker in tests or tools. The protocol isWorkerService in api/grpc/worker/worker.proto, with Go bindings in api/grpc/worker/workerpb. The program below goes through the three steps RequestTaskSlot → SubmitTask → WatchTasks against a local worker and submits the same task as example 1:
RequestTaskSlotcan be omitted: whenSubmitTaskcarries noslot_id, the Worker requests a slot inline and returnsResourceExhaustedwhen there is no capacity.- When going through the Coordinator, first call
coordinator.v1.CoordinatorService.ReserveWorkerSlot(task_id)(api/grpc/coordinator/coordinator.proto), then open a connection to the returnedworker_addrand submit withslot_id. - Repeating
SubmitTaskwith the sametask_idis idempotent: it returnsRUNNINGwhile the task is still running, and the terminal state while the result is still within the retention window. - In tests you can use the
RequestTaskSlot/SubmitTask/PollUntilTerminalhelpers frominternal/testutildirectly; they wrap exactly the calls above.
Common errors
Related docs
- Quickstart: the shortest path to a local worker and proxy.
- Task and Call:
TaskInputfields, states, and failure codes. - Call Builder and Writer Plugin: the wire shape of every config.
- Protocols and interfaces: the complete fields of
WorkerService/CoordinatorService. - Testing: submission helpers in the process / system E2E tests.
docs/api_reference.mdin the blockx-py repository: all SDK parameters and the behavior details ofPipeline.