Files
sc-templates/go/README.md
Andrew Miller 2bc57c073d go: keepalive, exponential backoff, chain_id metadata, durability guarantees
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.
2026-04-19 21:23:47 -04:00

205 lines
6.6 KiB
Markdown
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
```bash
# Install protoc (Ubuntu/Debian)
sudo apt install -y protobuf-compiler
# Install protoc (macOS)
brew install protobuf
# Install Go protobuf plugins
make tools
```
## Quick Start
1. **Copy this template** to create your smart contract:
```bash
cp -r go /path/to/my-smart-contract
cd /path/to/my-smart-contract
```
2. **Initialize your Go module and update package names**:
```bash
go mod init github.com/your-org/your-project
make fix-package-name
```
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.go` by modifying the `Process` function.
6. **Build and run**:
```bash
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`:
```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:
```go
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 the `Process` function 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
```bash
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 `Process` function 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]