lbc_client_go/main.go
Gregory Bednov 2b5bce7f6f initial release
make promises, make commitments, ~~make users~~ register users
2025-08-10 10:55:23 +03:00

261 lines
6.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import (
"bytes"
"crypto/ed25519"
"crypto/rand"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"time"
"github.com/google/uuid"
"github.com/spf13/pflag"
)
type CommiterTxBody struct {
Type string `json:"type"`
ID string `json:"id"`
Name string `json:"name"`
CommiterPubKey string `json:"commiter_pubkey"`
}
type PromiseTxBody struct {
Type string `json:"type"`
ID string `json:"id"`
Description string `json:"description"`
Timestamp int64 `json:"timestamp"`
}
type CommitmentTxBody struct {
Type string `json:"type"`
ID string `json:"id"`
PromiseID string `json:"promise_id"`
CommiterID string `json:"commiter_id"`
}
type SignedTx struct {
Body any `json:"body"`
Signature string `json:"signature"`
}
type rpcResp struct {
JSONRPC string `json:"jsonrpc"`
ID any `json:"id"`
Error *struct {
Code int `json:"code"`
Message string `json:"message"`
Data string `json:"data"`
} `json:"error"`
Result *struct {
CheckTx struct {
Code uint32 `json:"code"`
Log string `json:"log"`
} `json:"check_tx"`
DeliverTx struct {
Code uint32 `json:"code"`
Log string `json:"log"`
} `json:"deliver_tx"`
} `json:"result"`
}
type CompoundTx struct {
Body struct {
Promise *PromiseTxBody `json:"promise"`
Commitment *CommitmentTxBody `json:"commitment"`
} `json:"body"`
Signature string `json:"signature"`
}
const configDir = "./config"
const privKeyPath = configDir + "/ed25519.key"
const pubKeyPath = configDir + "/ed25519.pub"
func parseRPCResult(data []byte) error {
var r rpcResp
_ = json.Unmarshal(data, &r)
if r.Error != nil {
return fmt.Errorf("RPC error: %d %s (%s)", r.Error.Code, r.Error.Message, r.Error.Data)
}
if r.Result == nil {
return fmt.Errorf("empty result")
}
if r.Result.CheckTx.Code != 0 {
return fmt.Errorf("CheckTx failed: %s", r.Result.CheckTx.Log)
}
if r.Result.DeliverTx.Code != 0 {
return fmt.Errorf("DeliverTx failed: %s", r.Result.DeliverTx.Log)
}
return nil
}
func ensureKeypair() (ed25519.PublicKey, ed25519.PrivateKey, error) {
if _, err := os.Stat(privKeyPath); os.IsNotExist(err) {
fmt.Println("🔐 Generating new ed25519 keypair...")
pub, priv, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
return nil, nil, err
}
os.MkdirAll(configDir, 0700)
os.WriteFile(privKeyPath, priv, 0600)
os.WriteFile(pubKeyPath, pub, 0644)
return pub, priv, nil
}
priv, _ := os.ReadFile(privKeyPath)
pub, _ := os.ReadFile(pubKeyPath)
return ed25519.PublicKey(pub), ed25519.PrivateKey(priv), nil
}
func sendTx(tx SignedTx, rpcURL string) error {
txBytes, _ := json.Marshal(tx)
txB64 := base64.StdEncoding.EncodeToString(txBytes)
final := map[string]any{
"jsonrpc": "2.0",
"id": uuid.NewString(),
"method": "broadcast_tx_commit",
"params": map[string]string{
"tx": txB64,
},
}
finalBytes, _ := json.Marshal(final)
fmt.Println("🚧 Raw tx JSON:", string(txBytes))
fmt.Printf("🚧 Final RPC body:\n%s\n", string(finalBytes))
resp, err := http.Post(rpcURL, "application/json", bytes.NewReader(finalBytes))
if err != nil {
return err
}
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println("📡 Response:")
fmt.Println(string(data))
if err := parseRPCResult(data); err != nil {
return err
}
return nil
}
func registerCommiter(name, rpcURL string) error {
pub, priv, _ := ensureKeypair()
pubB64 := base64.StdEncoding.EncodeToString(pub)
id := "commiter:" + pubB64
body := CommiterTxBody{
Type: "commiter",
ID: id,
Name: name,
CommiterPubKey: pubB64, // ← ты думаешь, а на деле может быть иначе
}
bodyBytes, _ := json.Marshal(body)
sig := ed25519.Sign(priv, bodyBytes)
tx := SignedTx{Body: body, Signature: base64.StdEncoding.EncodeToString(sig)}
return sendTx(tx, rpcURL)
}
func createPromise(desc, rpcURL string) error {
pub, priv, _ := ensureKeypair()
pubB64 := base64.StdEncoding.EncodeToString(pub)
commiterID := "commiter:" + pubB64
promiseID := "promise:" + uuid.NewString()
commitmentID := "commitment:" + uuid.NewString()
// Объекты тела
promise := &PromiseTxBody{
Type: "promise",
ID: promiseID,
Description: desc,
Timestamp: time.Now().Unix(),
}
commitment := &CommitmentTxBody{
Type: "commitment",
ID: commitmentID,
PromiseID: promiseID,
CommiterID: commiterID,
}
// Сборка тела для подписи
bodyStruct := struct {
Promise *PromiseTxBody `json:"promise"`
Commitment *CommitmentTxBody `json:"commitment"`
}{
Promise: promise,
Commitment: commitment,
}
// Сериализация и подпись
bodyBytes, err := json.Marshal(bodyStruct)
if err != nil {
return fmt.Errorf("failed to marshal compound body: %w", err)
}
sig := ed25519.Sign(priv, bodyBytes)
sigB64 := base64.StdEncoding.EncodeToString(sig)
// Формирование транзакции
compound := CompoundTx{
Body: bodyStruct,
Signature: sigB64,
}
// Отправка
txBytes, err := json.Marshal(compound)
if err != nil {
return fmt.Errorf("failed to marshal compound tx: %w", err)
}
txB64 := base64.StdEncoding.EncodeToString(txBytes)
final := map[string]any{
"jsonrpc": "2.0",
"id": uuid.NewString(),
"method": "broadcast_tx_commit",
"params": map[string]string{
"tx": txB64,
},
}
finalBytes, _ := json.Marshal(final)
fmt.Printf("🚧 Final RPC body:\n%s\n", string(finalBytes))
resp, err := http.Post(rpcURL, "application/json", bytes.NewReader(finalBytes))
if err != nil {
return err
}
defer resp.Body.Close()
data, _ := io.ReadAll(resp.Body)
fmt.Println("📡 Response:")
fmt.Println(string(data))
if err := parseRPCResult(data); err != nil {
return err
}
return nil
}
func main() {
var name, desc, rpc string
pflag.StringVar(&name, "name", "", "register commiter name")
pflag.StringVar(&desc, "desc", "", "create promise description")
pflag.StringVar(&rpc, "rpc", "http://localhost:26657", "Tendermint RPC URL")
pflag.Parse()
if name != "" {
err := registerCommiter(name, rpc)
if err != nil {
fmt.Fprintf(os.Stderr, "❌ Error: %v\n", err)
os.Exit(1)
}
fmt.Println("✅ Commiter registered successfully")
return
}
if desc != "" {
err := createPromise(desc, rpc)
if err != nil {
fmt.Fprintf(os.Stderr, "❌ Error: %v\n", err)
os.Exit(1)
}
fmt.Println("✅ Promise and Commitment created successfully")
return
}
fmt.Println("⛔ Please provide either --name or --desc")
os.Exit(1)
}