initial release
make promises, make commitments, ~~make users~~ register users
This commit is contained in:
parent
411533e5f0
commit
2b5bce7f6f
4 changed files with 276 additions and 0 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -23,3 +23,6 @@ go.work.sum
|
|||
|
||||
# env file
|
||||
.env
|
||||
|
||||
lbc_client
|
||||
config/
|
||||
|
|
|
|||
8
go.mod
Normal file
8
go.mod
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
module lbc_client
|
||||
|
||||
go 1.24.3
|
||||
|
||||
require (
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/spf13/pflag v1.0.7
|
||||
)
|
||||
4
go.sum
Normal file
4
go.sum
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
|
||||
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
261
main.go
Normal file
261
main.go
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
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)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue