Three related fixes that turn the go template into a client that survives the full matrix of server restart, client restart, network blip, half-open TCP, and long outages (hours → months) — without the user writing a line of reconnect logic in process.go. 1. gRPC keepalive: Time=10s, Timeout=3s, PermitWithoutStream=true. Half-open TCP (silent server restart, resumed laptop, NAT drop) is detected within ~13s. Previously the OS TCP keepalive took ~2h to notice, leaving the client as a ghost stream while prime logged "no active gRPC connection" for every skipped transaction. 2. Exponential backoff with jitter on reconnect. Effective delay = min(max_backoff_seconds, reconnect_delay_seconds * 2^attempts) + random(0, reconnect_delay_seconds). The attempts counter resets after any session that runs healthy for 60+ seconds. Jitter desynchronises clients so a server restart doesn't trigger a thundering herd. New max_backoff_seconds config field, default 120. 3. Unified error signalling: the sender goroutine now tears down the stream's context when it hits a Send error. Previously only Recv errors triggered a reconnect — a stale stream where only Send was broken could sit there indefinitely. Also: chain_id is a required config field now and goes in the x-chain-id gRPC metadata header alongside x-api-key and x-smart-contract-id. Prime rejects streams without it with "missing chain ID", which was silently breaking every template-based client until users discovered it the hard way. README documents the durability contract so contract authors know they don't have to reimplement any of it.
6.6 KiB
Executable File
Go Smart Contract Template
A Go-based smart contract client for Dragonchain Prime that connects via gRPC.
Prerequisites
- Go 1.21 or later
- Protocol Buffers compiler (
protoc) - Go protobuf plugins
Installing Tools
# Install protoc (Ubuntu/Debian)
sudo apt install -y protobuf-compiler
# Install protoc (macOS)
brew install protobuf
# Install Go protobuf plugins
make tools
Quick Start
-
Copy this template to create your smart contract:
cp -r go /path/to/my-smart-contract cd /path/to/my-smart-contract -
Initialize your Go module and update package names:
go mod init github.com/your-org/your-project make fix-package-name -
Generate the protobuf code:
make proto -
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" -
Implement your smart contract logic in
process.goby modifying theProcessfunction. -
Build and run:
make build ./smart-contract -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.go, 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.go.
Implementing Your Smart Contract
Edit the Process function in process.go:
func Process(ctx context.Context, txJSON string, envVars, secrets map[string]string) ProcessResult {
// Parse the transaction
var tx Transaction
if err := json.Unmarshal([]byte(txJSON), &tx); err != nil {
return ProcessResult{Error: fmt.Errorf("failed to parse transaction: %w", err)}
}
// Access transaction data
txnId := tx.Header.TxnId
txnType := tx.Header.TxnType
payload := tx.Payload
// Access environment variables
scName := envVars["SMART_CONTRACT_NAME"]
dcID := envVars["DRAGONCHAIN_ID"]
// Access secrets
mySecret := secrets["SC_SECRET_MY_SECRET"]
// Implement your logic here
result := map[string]any{
"status": "success",
"data": "your result data",
}
return ProcessResult{
Data: result,
OutputToChain: true, // Set to true to persist result on chain
Error: nil,
}
}
Transaction Structure
The Transaction struct in process.go matches the Dragonchain transaction format:
type Transaction struct {
Version string `json:"version"`
Header TransactionHeader `json:"header"`
Payload map[string]any `json:"payload"`
}
type TransactionHeader struct {
Tag string `json:"tag"`
DcId string `json:"dc_id"`
TxnId string `json:"txn_id"`
BlockId string `json:"block_id"`
TxnType string `json:"txn_type"`
Timestamp string `json:"timestamp"`
Invoker string `json:"invoker"`
}
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 map with keys prefixed by SC_SECRET_.
Project Structure
.
├── main.go # Client infrastructure (do not modify)
├── process.go # Your smart contract logic (modify this)
├── proto/
│ └── remote_sc.proto # gRPC service definition
├── config.yaml # Configuration file
├── go.mod # Go module definition
├── Makefile # Build commands
└── README.md # This file
File Descriptions
process.go- Contains theProcessfunction where you implement your smart contract logic. This is the only file you need to modify for most use cases.main.go- Contains the gRPC client infrastructure, connection handling, and worker pool. You typically don't need to modify this file.
Make Commands
make proto # Generate Go code from proto files
make build # Build the binary
make run # Run with default config
make test # Run tests
make clean # Remove build artifacts
make tools # Install required tools
make deps # Download dependencies
make fix-package-name # Update package name based on go.mod
Concurrent Processing
The client uses a worker pool pattern to process multiple transactions concurrently. The number of workers is configurable via num_workers in the config file.
Error Handling
- Return errors from the
Processfunction to report failures to the server - The client automatically handles reconnection on connection failures
- Logs are captured and sent back with the response
License
[Your License Here]