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) }