feat(rpc): add GetRandomOutputs and SendRawTransaction endpoints
GetRandomOutputs wraps /getrandom_outs1 for ring selection decoys. SendRawTransaction wraps /sendrawtransaction for tx submission. Co-Authored-By: Charon <charon@lethean.io>
This commit is contained in:
parent
b7349a054d
commit
3a5db81e13
2 changed files with 163 additions and 0 deletions
59
rpc/wallet.go
Normal file
59
rpc/wallet.go
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
// Copyright (c) 2017-2026 Lethean (https://lt.hn)
|
||||||
|
//
|
||||||
|
// Licensed under the European Union Public Licence (EUPL) version 1.2.
|
||||||
|
// SPDX-License-Identifier: EUPL-1.2
|
||||||
|
|
||||||
|
package rpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RandomOutputEntry is a decoy output returned by getrandom_outs.
|
||||||
|
type RandomOutputEntry struct {
|
||||||
|
GlobalIndex uint64 `json:"global_index"`
|
||||||
|
PublicKey string `json:"public_key"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRandomOutputs fetches random decoy outputs for ring construction.
|
||||||
|
// Uses the legacy /getrandom_outs1 endpoint (not available via /json_rpc).
|
||||||
|
func (c *Client) GetRandomOutputs(amount uint64, count int) ([]RandomOutputEntry, error) {
|
||||||
|
params := struct {
|
||||||
|
Amount uint64 `json:"amount"`
|
||||||
|
Count int `json:"outs_count"`
|
||||||
|
}{Amount: amount, Count: count}
|
||||||
|
|
||||||
|
var resp struct {
|
||||||
|
Outs []RandomOutputEntry `json:"outs"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.legacyCall("/getrandom_outs1", params, &resp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if resp.Status != "OK" {
|
||||||
|
return nil, fmt.Errorf("getrandom_outs: status %q", resp.Status)
|
||||||
|
}
|
||||||
|
return resp.Outs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendRawTransaction submits a serialised transaction for relay.
|
||||||
|
// Uses the legacy /sendrawtransaction endpoint (not available via /json_rpc).
|
||||||
|
func (c *Client) SendRawTransaction(txBlob []byte) error {
|
||||||
|
params := struct {
|
||||||
|
TxAsHex string `json:"tx_as_hex"`
|
||||||
|
}{TxAsHex: hex.EncodeToString(txBlob)}
|
||||||
|
|
||||||
|
var resp struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.legacyCall("/sendrawtransaction", params, &resp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if resp.Status != "OK" {
|
||||||
|
return fmt.Errorf("sendrawtransaction: status %q", resp.Status)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
104
rpc/wallet_test.go
Normal file
104
rpc/wallet_test.go
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
// Copyright (c) 2017-2026 Lethean (https://lt.hn)
|
||||||
|
//
|
||||||
|
// Licensed under the European Union Public Licence (EUPL) version 1.2.
|
||||||
|
// SPDX-License-Identifier: EUPL-1.2
|
||||||
|
|
||||||
|
package rpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetRandomOutputs_Good(t *testing.T) {
|
||||||
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.Path != "/getrandom_outs1" {
|
||||||
|
t.Errorf("path: got %s, want /getrandom_outs1", r.URL.Path)
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Write([]byte(`{
|
||||||
|
"outs": [
|
||||||
|
{"global_index": 10, "public_key": "aa00000000000000000000000000000000000000000000000000000000000000"},
|
||||||
|
{"global_index": 20, "public_key": "bb00000000000000000000000000000000000000000000000000000000000000"}
|
||||||
|
],
|
||||||
|
"status": "OK"
|
||||||
|
}`))
|
||||||
|
}))
|
||||||
|
defer srv.Close()
|
||||||
|
|
||||||
|
c := NewClient(srv.URL)
|
||||||
|
outs, err := c.GetRandomOutputs(1000, 2)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetRandomOutputs: %v", err)
|
||||||
|
}
|
||||||
|
if len(outs) != 2 {
|
||||||
|
t.Fatalf("outs: got %d, want 2", len(outs))
|
||||||
|
}
|
||||||
|
if outs[0].GlobalIndex != 10 {
|
||||||
|
t.Errorf("outs[0].GlobalIndex: got %d, want 10", outs[0].GlobalIndex)
|
||||||
|
}
|
||||||
|
if outs[0].PublicKey != "aa00000000000000000000000000000000000000000000000000000000000000" {
|
||||||
|
t.Errorf("outs[0].PublicKey: got %q", outs[0].PublicKey)
|
||||||
|
}
|
||||||
|
if outs[1].GlobalIndex != 20 {
|
||||||
|
t.Errorf("outs[1].GlobalIndex: got %d, want 20", outs[1].GlobalIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetRandomOutputs_Bad_Status(t *testing.T) {
|
||||||
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(struct{ Status string }{Status: "BUSY"})
|
||||||
|
}))
|
||||||
|
defer srv.Close()
|
||||||
|
|
||||||
|
c := NewClient(srv.URL)
|
||||||
|
_, err := c.GetRandomOutputs(1000, 2)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error for non-OK status")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSendRawTransaction_Good(t *testing.T) {
|
||||||
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.Path != "/sendrawtransaction" {
|
||||||
|
t.Errorf("path: got %s, want /sendrawtransaction", r.URL.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
var req struct {
|
||||||
|
TxAsHex string `json:"tx_as_hex"`
|
||||||
|
}
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
t.Fatalf("decode request: %v", err)
|
||||||
|
}
|
||||||
|
if req.TxAsHex != "0102" {
|
||||||
|
t.Errorf("tx_as_hex: got %q, want %q", req.TxAsHex, "0102")
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Write([]byte(`{"status":"OK"}`))
|
||||||
|
}))
|
||||||
|
defer srv.Close()
|
||||||
|
|
||||||
|
c := NewClient(srv.URL)
|
||||||
|
err := c.SendRawTransaction([]byte{0x01, 0x02})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("SendRawTransaction: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSendRawTransaction_Bad_Rejected(t *testing.T) {
|
||||||
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(struct{ Status string }{Status: "Failed"})
|
||||||
|
}))
|
||||||
|
defer srv.Close()
|
||||||
|
|
||||||
|
c := NewClient(srv.URL)
|
||||||
|
err := c.SendRawTransaction([]byte{0x01})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error for rejected transaction")
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue