Initial commit: smart contract templates for bash, go, python, and typescript
This commit is contained in:
27
bash/.gitignore
vendored
Normal file
27
bash/.gitignore
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
# Python (infrastructure runtime)
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# Virtual environments
|
||||
venv/
|
||||
ENV/
|
||||
env/
|
||||
.venv/
|
||||
|
||||
# Generated proto files
|
||||
*_pb2.py
|
||||
*_pb2_grpc.py
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# Test config (contains credentials)
|
||||
test-config.yaml
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
53
bash/Makefile
Normal file
53
bash/Makefile
Normal file
@@ -0,0 +1,53 @@
|
||||
.PHONY: proto run clean setup venv test deps check
|
||||
|
||||
# Generate Python code from proto files (for the gRPC infrastructure)
|
||||
proto:
|
||||
python -m grpc_tools.protoc \
|
||||
-I./proto \
|
||||
--python_out=. \
|
||||
--grpc_python_out=. \
|
||||
proto/remote_sc.proto
|
||||
|
||||
# Create virtual environment and install dependencies
|
||||
setup: venv
|
||||
. venv/bin/activate && pip install -r requirements.txt
|
||||
@echo ""
|
||||
@echo "Setup complete. Activate the virtual environment with:"
|
||||
@echo " source venv/bin/activate"
|
||||
|
||||
# Create virtual environment
|
||||
venv:
|
||||
python3 -m venv venv
|
||||
|
||||
# Run the smart contract
|
||||
run:
|
||||
python main.py --config config.yaml
|
||||
|
||||
# Clean generated files
|
||||
clean:
|
||||
rm -f *_pb2.py *_pb2_grpc.py
|
||||
rm -rf __pycache__
|
||||
rm -rf venv
|
||||
|
||||
# Run tests
|
||||
test:
|
||||
bash -n process.sh
|
||||
@echo "Syntax check passed"
|
||||
@echo "Running process.sh with sample transaction..."
|
||||
echo '{"version":"1","header":{"tag":"test","dc_id":"test-dc","txn_id":"test-txn-123","block_id":"1","txn_type":"test","timestamp":"2024-01-01T00:00:00Z","invoker":"test-user"},"payload":{"action":"test","value":42}}' | \
|
||||
bash process.sh "$$(cat /dev/stdin)" 2>/dev/null && echo "Test passed" || echo "Test failed"
|
||||
|
||||
# Install dependencies (without venv)
|
||||
deps:
|
||||
pip install -r requirements.txt
|
||||
|
||||
# Check that required tools are available
|
||||
check:
|
||||
@command -v python3 >/dev/null 2>&1 || { echo "python3 is required but not installed"; exit 1; }
|
||||
@command -v bash >/dev/null 2>&1 || { echo "bash is required but not installed"; exit 1; }
|
||||
@command -v jq >/dev/null 2>&1 || { echo "jq is required but not installed"; exit 1; }
|
||||
@echo "All required tools are available"
|
||||
|
||||
# Format process.sh with shfmt (if available)
|
||||
format:
|
||||
@command -v shfmt >/dev/null 2>&1 && shfmt -w process.sh || echo "shfmt not installed, skipping format"
|
||||
243
bash/README.md
Normal file
243
bash/README.md
Normal file
@@ -0,0 +1,243 @@
|
||||
# Bash Smart Contract Template
|
||||
|
||||
A Bash-based smart contract client for Dragonchain Prime that connects via gRPC.
|
||||
|
||||
This template uses a thin Python gRPC infrastructure layer to handle the network protocol, while your smart contract logic lives entirely in `process.sh`.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Bash 4.0+
|
||||
- Python 3.10+ (for gRPC infrastructure)
|
||||
- pip
|
||||
- jq (for JSON processing in bash)
|
||||
|
||||
## Quick Start
|
||||
|
||||
1. **Copy this template** to create your smart contract:
|
||||
```bash
|
||||
cp -r bash /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"
|
||||
smart_contract_id: "your-smart-contract-id"
|
||||
api_key: "your-api-key"
|
||||
```
|
||||
|
||||
5. **Implement your smart contract logic** in `process.sh`.
|
||||
|
||||
6. **Run**:
|
||||
```bash
|
||||
python main.py --config config.yaml
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
| Field | Description | Default |
|
||||
|-------|-------------|---------|
|
||||
| `server_address` | gRPC server address | 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` | Delay between reconnection attempts | `5` |
|
||||
| `max_reconnect_attempts` | Max reconnect attempts (0 = infinite) | `0` |
|
||||
|
||||
## Implementing Your Smart Contract
|
||||
|
||||
Edit `process.sh`. The script receives the transaction JSON as its first argument (`$1`) and must output a JSON result to stdout.
|
||||
|
||||
### Interface
|
||||
|
||||
**Input:**
|
||||
- `$1` - Transaction JSON string
|
||||
- Environment variables - Server env vars and secrets are exported
|
||||
|
||||
**Output (stdout):**
|
||||
```json
|
||||
{
|
||||
"data": { "your": "result" },
|
||||
"output_to_chain": true,
|
||||
"error": ""
|
||||
}
|
||||
```
|
||||
|
||||
**Logs (stderr):** Anything written to stderr is captured and returned as logs.
|
||||
|
||||
**Exit code:** 0 = success, non-zero = error (stderr used as error message).
|
||||
|
||||
### Example
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
TX_JSON="$1"
|
||||
|
||||
# Parse transaction fields with jq
|
||||
TXN_ID=$(echo "$TX_JSON" | jq -r '.header.txn_id')
|
||||
TXN_TYPE=$(echo "$TX_JSON" | jq -r '.header.txn_type')
|
||||
PAYLOAD=$(echo "$TX_JSON" | jq -c '.payload')
|
||||
|
||||
# Access environment variables
|
||||
SC_NAME="${SMART_CONTRACT_NAME:-}"
|
||||
DC_ID="${DRAGONCHAIN_ID:-}"
|
||||
|
||||
# Access secrets
|
||||
MY_SECRET="${SC_SECRET_MY_SECRET:-}"
|
||||
|
||||
# Log to stderr
|
||||
echo "Processing transaction $TXN_ID" >&2
|
||||
|
||||
# Process based on payload action
|
||||
ACTION=$(echo "$TX_JSON" | jq -r '.payload.action // empty')
|
||||
case "$ACTION" in
|
||||
create)
|
||||
RESULT='{"status": "created"}'
|
||||
;;
|
||||
update)
|
||||
RESULT='{"status": "updated"}'
|
||||
;;
|
||||
*)
|
||||
RESULT='{"status": "unknown"}'
|
||||
;;
|
||||
esac
|
||||
|
||||
# Output result as JSON
|
||||
jq -n --argjson result "$RESULT" '{
|
||||
"data": $result,
|
||||
"output_to_chain": true,
|
||||
"error": ""
|
||||
}'
|
||||
```
|
||||
|
||||
### Transaction Structure
|
||||
|
||||
The transaction JSON passed to your script has this format:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "1",
|
||||
"header": {
|
||||
"tag": "my-tag",
|
||||
"dc_id": "dragonchain-id",
|
||||
"txn_id": "transaction-id",
|
||||
"block_id": "block-id",
|
||||
"txn_type": "my-type",
|
||||
"timestamp": "2024-01-01T00:00:00Z",
|
||||
"invoker": "user-id"
|
||||
},
|
||||
"payload": {
|
||||
"your": "custom data"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 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 exported as environment variables with keys prefixed by `SC_SECRET_`.
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
.
|
||||
├── main.py # gRPC infrastructure (do not modify)
|
||||
├── process.sh # Your smart contract logic (modify this)
|
||||
├── proto/
|
||||
│ └── remote_sc.proto # gRPC service definition
|
||||
├── config.yaml # Configuration file
|
||||
├── requirements.txt # Python dependencies (for infrastructure)
|
||||
├── Makefile # Build commands
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
### File Descriptions
|
||||
|
||||
- **`process.sh`** - Your smart contract logic. This is the only file you need to modify for most use cases.
|
||||
- **`main.py`** - gRPC client infrastructure that invokes `process.sh` for each transaction. 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 # Syntax check and sample run of process.sh
|
||||
make clean # Remove generated files and venv
|
||||
make deps # Install dependencies (no venv)
|
||||
make check # Verify required tools (python3, bash, jq)
|
||||
make format # Format process.sh with shfmt (if installed)
|
||||
```
|
||||
|
||||
## Concurrent Processing
|
||||
|
||||
The client uses a thread pool to process multiple transactions concurrently. Each worker invokes a separate instance of `process.sh`. The number of workers is configurable via `num_workers` in the config file.
|
||||
|
||||
## Error Handling
|
||||
|
||||
- Return errors by setting the `error` field in your JSON output, or exit with a non-zero code
|
||||
- Anything written to stderr is captured as logs
|
||||
- The client automatically handles reconnection on connection failures
|
||||
|
||||
## Docker
|
||||
|
||||
Example `Dockerfile`:
|
||||
|
||||
```dockerfile
|
||||
FROM python:3.11-slim
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends jq bash && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY . .
|
||||
RUN chmod +x process.sh
|
||||
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]
|
||||
24
bash/config.yaml
Normal file
24
bash/config.yaml
Normal file
@@ -0,0 +1,24 @@
|
||||
# Smart Contract Client Configuration
|
||||
# Copy this file and fill in your values
|
||||
|
||||
# The gRPC server address to connect to
|
||||
server_address: "localhost:50051"
|
||||
|
||||
# Your smart contract ID (provided by Dragonchain)
|
||||
smart_contract_id: "your-smart-contract-id"
|
||||
|
||||
# API key for authentication (provided by Dragonchain)
|
||||
api_key: "your-api-key"
|
||||
|
||||
# Whether to use TLS for the connection
|
||||
use_tls: false
|
||||
|
||||
# Path to TLS certificate (required if use_tls is true)
|
||||
# tls_cert_path: "/path/to/cert.pem"
|
||||
|
||||
# Number of worker threads for processing transactions concurrently
|
||||
num_workers: 10
|
||||
|
||||
# Reconnect settings
|
||||
reconnect_delay_seconds: 5
|
||||
max_reconnect_attempts: 0 # 0 = infinite retries
|
||||
89
bash/process.sh
Executable file
89
bash/process.sh
Executable file
@@ -0,0 +1,89 @@
|
||||
#!/usr/bin/env bash
|
||||
# =============================================================================
|
||||
# Smart Contract Processing Logic
|
||||
#
|
||||
# This file contains the transaction processing logic for your smart contract.
|
||||
# Modify this script to implement your business logic.
|
||||
#
|
||||
# Input:
|
||||
# $1 - Transaction JSON string
|
||||
# Environment - Server env vars and secrets are exported as environment variables
|
||||
#
|
||||
# Output (stdout):
|
||||
# A JSON object with the following fields:
|
||||
# {
|
||||
# "data": { ... }, // Your result data (any JSON object)
|
||||
# "output_to_chain": true, // Whether to persist the result on chain
|
||||
# "error": "" // Error message (empty string if no error)
|
||||
# }
|
||||
#
|
||||
# Logs (stderr):
|
||||
# Anything written to stderr is captured and sent back as logs.
|
||||
#
|
||||
# Exit code:
|
||||
# 0 = success (stdout is parsed as JSON result)
|
||||
# non-zero = error (stderr is used as the error message)
|
||||
# =============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Transaction JSON is passed as the first argument
|
||||
TX_JSON="$1"
|
||||
|
||||
# =============================================================================
|
||||
# TODO: Implement your smart contract logic here
|
||||
# =============================================================================
|
||||
#
|
||||
# Parse transaction fields using jq:
|
||||
# TXN_ID=$(echo "$TX_JSON" | jq -r '.header.txn_id')
|
||||
# TXN_TYPE=$(echo "$TX_JSON" | jq -r '.header.txn_type')
|
||||
# PAYLOAD=$(echo "$TX_JSON" | jq '.payload')
|
||||
#
|
||||
# Access environment variables (set by the server):
|
||||
# echo "Smart Contract Name: $SMART_CONTRACT_NAME" >&2
|
||||
# echo "Dragonchain ID: $DRAGONCHAIN_ID" >&2
|
||||
#
|
||||
# Access secrets (set by the server):
|
||||
# MY_SECRET="${SC_SECRET_MY_SECRET:-}"
|
||||
#
|
||||
# Process based on payload action:
|
||||
# ACTION=$(echo "$TX_JSON" | jq -r '.payload.action // empty')
|
||||
# case "$ACTION" in
|
||||
# create)
|
||||
# # Handle create operation
|
||||
# ;;
|
||||
# update)
|
||||
# # Handle update operation
|
||||
# ;;
|
||||
# *)
|
||||
# echo '{"data": null, "output_to_chain": false, "error": "Unknown action: '"$ACTION"'"}'
|
||||
# exit 0
|
||||
# ;;
|
||||
# esac
|
||||
#
|
||||
# Log messages to stderr (captured and sent back with response):
|
||||
# echo "Processing transaction..." >&2
|
||||
|
||||
# Parse transaction data
|
||||
TXN_ID=$(echo "$TX_JSON" | jq -r '.header.txn_id')
|
||||
TXN_TYPE=$(echo "$TX_JSON" | jq -r '.header.txn_type')
|
||||
PAYLOAD=$(echo "$TX_JSON" | jq -c '.payload')
|
||||
|
||||
echo "Processing transaction $TXN_ID (type: $TXN_TYPE)" >&2
|
||||
|
||||
# Default implementation: echo back the transaction
|
||||
jq -n \
|
||||
--arg txn_id "$TXN_ID" \
|
||||
--arg txn_type "$TXN_TYPE" \
|
||||
--argjson payload "$PAYLOAD" \
|
||||
'{
|
||||
"data": {
|
||||
"status": "processed",
|
||||
"transaction_id": $txn_id,
|
||||
"txn_type": $txn_type,
|
||||
"payload": $payload,
|
||||
"message": "Transaction processed successfully"
|
||||
},
|
||||
"output_to_chain": true,
|
||||
"error": ""
|
||||
}'
|
||||
48
bash/proto/remote_sc.proto
Normal file
48
bash/proto/remote_sc.proto
Normal file
@@ -0,0 +1,48 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package remote_sc;
|
||||
|
||||
// SmartContractService defines the bi-directional streaming service for remote
|
||||
// smart contract execution. External workers connect to this service to receive
|
||||
// execution tasks and send back results.
|
||||
service SmartContractService {
|
||||
// Run establishes a bi-directional stream. The server sends SmartContractRequest
|
||||
// messages to the client for execution, and the client sends back
|
||||
// SmartContractResponse messages with results.
|
||||
rpc Run(stream SmartContractResponse) returns (stream SmartContractRequest);
|
||||
}
|
||||
|
||||
// SmartContractRequest is sent from the server to the connected worker
|
||||
// to request execution of a smart contract.
|
||||
message SmartContractRequest {
|
||||
// Unique identifier for this execution request, used to correlate responses
|
||||
string transaction_id = 1;
|
||||
|
||||
// Full transaction JSON to be processed by the smart contract
|
||||
string transaction_json = 2;
|
||||
|
||||
// Environment variables to set for the smart contract execution
|
||||
map<string, string> env_vars = 3;
|
||||
|
||||
// Secrets to be made available to the smart contract
|
||||
map<string, string> secrets = 4;
|
||||
}
|
||||
|
||||
// SmartContractResponse is sent from the worker back to the server
|
||||
// with the results of smart contract execution.
|
||||
message SmartContractResponse {
|
||||
// The transaction_id from the original request, for correlation
|
||||
string transaction_id = 1;
|
||||
|
||||
// The result data from the smart contract execution as JSON
|
||||
string result_json = 2;
|
||||
|
||||
// Logs captured during smart contract execution
|
||||
string logs = 3;
|
||||
|
||||
// Whether to persist the output to the chain
|
||||
bool output_to_chain = 4;
|
||||
|
||||
// Error message if execution failed
|
||||
string error = 5;
|
||||
}
|
||||
Reference in New Issue
Block a user