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

218 lines
6.4 KiB
Markdown
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:
```bash
cp -r python /path/to/my-smart-contract
cd /path/to/my-smart-contract
```
2. **Set up the environment**:
```bash
make setup
source venv/bin/activate
```
Or without make:
```bash
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
```
3. **Generate the protobuf code**:
```bash
make proto
```
4. **Configure your connection** by editing `config.yaml`:
```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**:
```bash
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`:
```python
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:
```python
@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
```bash
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`:
```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]