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.
205 lines
6.6 KiB
Markdown
Executable File
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]
|