chore: initial commit for v0.0.1
DChain single-node blockchain + React Native messenger client. Core: - PBFT consensus with multi-sig validator admission + equivocation slashing - BadgerDB + schema migration scaffold (CurrentSchemaVersion=0) - libp2p gossipsub (tx/v1, blocks/v1, relay/v1, version/v1) - Native Go contracts (username_registry) alongside WASM (wazero) - WebSocket gateway with topic-based fanout + Ed25519-nonce auth - Relay mailbox with NaCl envelope encryption (X25519 + Ed25519) - Prometheus /metrics, per-IP rate limit, body-size cap Deployment: - Single-node compose (deploy/single/) with Caddy TLS + optional Prometheus - 3-node dev compose (docker-compose.yml) with mocked internet topology - 3-validator prod compose (deploy/prod/) for federation - Auto-update from Gitea via /api/update-check + systemd timer - Build-time version injection (ldflags → node --version) - UI / Swagger toggle flags (DCHAIN_DISABLE_UI, DCHAIN_DISABLE_SWAGGER) Client (client-app/): - Expo / React Native / NativeWind - E2E NaCl encryption, typing indicator, contact requests - Auto-discovery of canonical contracts, chain_id aware, WS reconnect on node switch Documentation: - README.md, CHANGELOG.md, CONTEXT.md - deploy/single/README.md with 6 operator scenarios - deploy/UPDATE_STRATEGY.md with 4-layer forward-compat design - docs/contracts/*.md per contract
This commit is contained in:
181
node/api_common.go
Normal file
181
node/api_common.go
Normal file
@@ -0,0 +1,181 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"go-blockchain/blockchain"
|
||||
"go-blockchain/identity"
|
||||
)
|
||||
|
||||
func jsonOK(w http.ResponseWriter, v any) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
_ = json.NewEncoder(w).Encode(v)
|
||||
}
|
||||
|
||||
func jsonErr(w http.ResponseWriter, err error, code int) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
w.WriteHeader(code)
|
||||
_ = json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
func queryInt(r *http.Request, key string, def int) int {
|
||||
s := r.URL.Query().Get(key)
|
||||
if s == "" {
|
||||
return def
|
||||
}
|
||||
n, err := strconv.Atoi(s)
|
||||
if err != nil || n <= 0 {
|
||||
return def
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// queryIntMin0 parses a query param as a non-negative integer; returns 0 if absent or invalid.
|
||||
func queryIntMin0(r *http.Request, key string) int {
|
||||
s := r.URL.Query().Get(key)
|
||||
if s == "" {
|
||||
return 0
|
||||
}
|
||||
n, err := strconv.Atoi(s)
|
||||
if err != nil || n < 0 {
|
||||
return 0
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func queryUint64Optional(r *http.Request, key string) (*uint64, error) {
|
||||
raw := strings.TrimSpace(r.URL.Query().Get(key))
|
||||
if raw == "" {
|
||||
return nil, nil
|
||||
}
|
||||
n, err := strconv.ParseUint(raw, 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid %s: %s", key, raw)
|
||||
}
|
||||
return &n, nil
|
||||
}
|
||||
|
||||
func resolveAccountID(q ExplorerQuery, accountID string) (string, error) {
|
||||
if accountID == "" {
|
||||
return "", fmt.Errorf("account id required")
|
||||
}
|
||||
if strings.HasPrefix(accountID, "DC") {
|
||||
pubKey, err := q.AddressToPubKey(accountID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if pubKey == "" {
|
||||
return "", fmt.Errorf("account not found")
|
||||
}
|
||||
return pubKey, nil
|
||||
}
|
||||
return accountID, nil
|
||||
}
|
||||
|
||||
func verifyTransactionSignature(tx *blockchain.Transaction) error {
|
||||
if tx == nil {
|
||||
return fmt.Errorf("transaction is nil")
|
||||
}
|
||||
return identity.VerifyTx(tx)
|
||||
}
|
||||
|
||||
func decodeTransactionEnvelope(raw string) (*blockchain.Transaction, error) {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
return nil, fmt.Errorf("empty transaction envelope")
|
||||
}
|
||||
|
||||
tryDecodeJSON := func(data []byte) (*blockchain.Transaction, error) {
|
||||
var tx blockchain.Transaction
|
||||
if err := json.Unmarshal(data, &tx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tx.ID == "" || tx.From == "" || tx.Type == "" {
|
||||
return nil, fmt.Errorf("invalid tx payload")
|
||||
}
|
||||
return &tx, nil
|
||||
}
|
||||
|
||||
if strings.HasPrefix(raw, "{") {
|
||||
return tryDecodeJSON([]byte(raw))
|
||||
}
|
||||
|
||||
base64Decoders := []*base64.Encoding{
|
||||
base64.StdEncoding,
|
||||
base64.RawStdEncoding,
|
||||
base64.URLEncoding,
|
||||
base64.RawURLEncoding,
|
||||
}
|
||||
for _, enc := range base64Decoders {
|
||||
if b, err := enc.DecodeString(raw); err == nil {
|
||||
if tx, txErr := tryDecodeJSON(b); txErr == nil {
|
||||
return tx, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if b, err := hex.DecodeString(raw); err == nil {
|
||||
if tx, txErr := tryDecodeJSON(b); txErr == nil {
|
||||
return tx, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("failed to decode transaction envelope")
|
||||
}
|
||||
|
||||
func txMemo(tx *blockchain.Transaction) string {
|
||||
if tx == nil {
|
||||
return ""
|
||||
}
|
||||
if memo := strings.TrimSpace(tx.Memo); memo != "" {
|
||||
return memo
|
||||
}
|
||||
switch tx.Type {
|
||||
case blockchain.EventTransfer:
|
||||
var p blockchain.TransferPayload
|
||||
if err := json.Unmarshal(tx.Payload, &p); err == nil {
|
||||
return strings.TrimSpace(p.Memo)
|
||||
}
|
||||
case blockchain.EventBlockReward:
|
||||
return blockRewardReason(tx.Payload)
|
||||
case blockchain.EventRelayProof:
|
||||
return "Relay delivery fee"
|
||||
case blockchain.EventHeartbeat:
|
||||
return "Liveness heartbeat"
|
||||
case blockchain.EventRegisterRelay:
|
||||
return "Register relay service"
|
||||
case blockchain.EventBindWallet:
|
||||
return "Bind payout wallet"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func blockRewardReason(payload []byte) string {
|
||||
var p blockchain.BlockRewardPayload
|
||||
if err := json.Unmarshal(payload, &p); err != nil {
|
||||
return "Block fees"
|
||||
}
|
||||
if p.FeeReward == 0 && p.TotalReward > 0 {
|
||||
return "Genesis allocation"
|
||||
}
|
||||
return "Block fees collected"
|
||||
}
|
||||
|
||||
func decodeTxPayload(payload []byte) (any, string) {
|
||||
if len(payload) == 0 {
|
||||
return nil, ""
|
||||
}
|
||||
var decoded any
|
||||
if err := json.Unmarshal(payload, &decoded); err == nil {
|
||||
return decoded, ""
|
||||
}
|
||||
return nil, hex.EncodeToString(payload)
|
||||
}
|
||||
Reference in New Issue
Block a user