Files
sc-templates/python/README.md
Andrew Miller f22fb29964 python + typescript + bash: mirror the durability fixes from go/
Parity pass on the other three language templates. Same guarantees as
go/: survive server restart, client restart, half-open TCP, and long
outages; rejoin and drain prime-side backlog on reconnect, without
the user writing any of this in process.*.

python/main.py:
- grpc.keepalive_time_ms=10000, keepalive_timeout_ms=3000,
  keepalive_permit_without_calls=1 on the channel. Half-open TCP is
  detected within ~13s instead of the OS default ~2h.
- Exponential backoff with jitter; max_backoff_seconds config ceiling
  (default 120). Attempts counter resets after a session runs
  healthy for 60s so transient restarts don't escalate the delay.
- chain_id added as a required config field and sent as the
  x-chain-id gRPC metadata header (prime rejects streams without it).

typescript/src/main.ts:
- Same keepalive options on the @grpc/grpc-js client.
- Same exponential backoff + jitter logic.
- chain_id added to Config + metadata.

bash/:
- Config + README updated. The bash template uses Python's main.py
  as its runtime, so the behavioural changes above flow through
  without a separate main per language.

Docs: each README gains a "Durability guarantees" section so contract
authors see the invariants without reading the runtime code.
2026-04-19 21:32:24 -04:00

6.4 KiB
Executable File

Python Smart Contract Template

A Python-based smart contract client for Dragonchain Prime that connects via gRPC.

Prerequisites

  • Python 3.10 or later
  • pip

Quick Start

  1. Copy this template to create your smart contract:

    cp -r python /path/to/my-smart-contract
    cd /path/to/my-smart-contract
    
  2. Set up the environment:

    make setup
    source venv/bin/activate
    

    Or without make:

    python3 -m venv venv
    source venv/bin/activate
    pip install -r requirements.txt
    
  3. Generate the protobuf code:

    make proto
    
  4. Configure your connection by editing config.yaml:

    server_address: "your-dragonchain-server:50051"
    chain_id: "your-chain-public-id"
    smart_contract_id: "your-smart-contract-id"
    api_key: "your-api-key"
    
  5. Implement your smart contract logic in process.py by modifying the process function.

  6. Run:

    python main.py --config config.yaml
    

Configuration

Field Description Default
server_address gRPC server address Required
chain_id Public chain id the SC is registered on (sent as x-chain-id metadata) Required
smart_contract_id Your smart contract ID Required
api_key API key for authentication Required
use_tls Enable TLS encryption false
tls_cert_path Path to TLS certificate -
num_workers Concurrent transaction processors 10
reconnect_delay_seconds Base delay for exponential backoff between reconnect attempts 3
max_backoff_seconds Ceiling for the exponential backoff 120
max_reconnect_attempts Max reconnect attempts (0 = infinite, recommended) 0

Durability guarantees (provided by main.py, no work for you)

  • Server restart, update, crash, or network blip → the client auto-reconnects and resumes processing. Transactions observed while the stream was down stay queued on the Dragonchain Prime side and are delivered (oldest first) on reconnect.
  • Client restart or long outage → when this process comes back up (minutes, hours, months later), it rejoins the stream and prime re-delivers every still-pending transaction that should have invoked it.
  • Half-open TCP (silent peer, resumed laptop, corporate NAT dropping idle flows) is detected within ~13 seconds via gRPC keepalive and triggers a reconnect. No dangling ghost streams.
  • Reconnect storms are avoided: exponential backoff with jitter means many clients reconnecting after a server restart don't all slam accept() at the same instant. The timer resets after a stream has been healthy for 60 seconds.

These are invariants of the template — you do not add any of this in process.py.

Implementing Your Smart Contract

Edit the process function in process.py:

def process(
    tx_json: str,
    env_vars: dict[str, str],
    secrets: dict[str, str],
) -> ProcessResult:
    # Parse the transaction
    tx = Transaction.from_json(tx_json)

    # Access transaction data
    txn_id = tx.header.txn_id
    txn_type = tx.header.txn_type
    payload = tx.payload

    # Access environment variables
    sc_name = env_vars.get("SMART_CONTRACT_NAME")
    dc_id = env_vars.get("DRAGONCHAIN_ID")

    # Access secrets
    my_secret = secrets.get("SC_SECRET_MY_SECRET")

    # Implement your logic here
    result = {
        "status": "success",
        "data": "your result data",
    }

    return ProcessResult(
        data=result,
        output_to_chain=True,  # Set to True to persist result on chain
        error=None,
    )

Transaction Structure

The Transaction class in process.py matches the Dragonchain transaction format:

@dataclass
class Transaction:
    version: str
    header: TransactionHeader
    payload: dict[str, Any]

@dataclass
class TransactionHeader:
    tag: str
    dc_id: str
    txn_id: str
    block_id: str
    txn_type: str
    timestamp: str
    invoker: str

Available Environment Variables

Variable Description
TZ Timezone
ENVIRONMENT Deployment environment
INTERNAL_ID Internal identifier
DRAGONCHAIN_ID Dragonchain ID
DRAGONCHAIN_ENDPOINT Dragonchain API endpoint
SMART_CONTRACT_ID This smart contract's ID
SMART_CONTRACT_NAME This smart contract's name
SC_ENV_* Custom environment variables

Secrets

Secrets are provided in the secrets dict with keys prefixed by SC_SECRET_.

Project Structure

.
├── main.py              # Client infrastructure (do not modify)
├── process.py           # Your smart contract logic (modify this)
├── proto/
│   └── remote_sc.proto  # gRPC service definition
├── config.yaml          # Configuration file
├── requirements.txt     # Python dependencies
├── Makefile             # Build commands
└── README.md            # This file

File Descriptions

  • process.py - Contains the process function where you implement your smart contract logic. This is the only file you need to modify for most use cases.
  • main.py - Contains the gRPC client infrastructure, connection handling, and worker pool. You typically don't need to modify this file.

Make Commands

make setup   # Create venv and install dependencies
make proto   # Generate Python code from proto files
make run     # Run with default config
make test    # Run tests
make clean   # Remove generated files and venv
make deps    # Install dependencies (no venv)
make format  # Format code with black and isort

Concurrent Processing

The client uses a thread pool to process multiple transactions concurrently. The number of workers is configurable via num_workers in the config file.

Error Handling

  • Return errors from the process function via ProcessResult.error to report failures
  • The client automatically handles reconnection on connection failures
  • Logs are captured and sent back with the response

Docker

Example Dockerfile:

FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .
RUN python -m grpc_tools.protoc \
    -I./proto \
    --python_out=. \
    --grpc_python_out=. \
    proto/remote_sc.proto

CMD ["python", "main.py", "--config", "config.yaml"]

License

[Your License Here]