mirror of
https://github.com/lightninglabs/lightning-terminal.git
synced 2026-08-13 12:33:36 +02:00
scripting: fix LND RPC authentication using lndclient wrappers
This commit fixes LND RPC authentication by switching from raw gRPC clients to lndclient wrappers that provide proper macaroon authentication. Changes: - Update LNDClients to use lndclient.LightningClient and lndclient.RouterClient instead of raw gRPC clients - Add getRawLightningClient() and getRawRouterClient() helpers that use RawClientWithMacAuth() for authenticated raw client access - Update all 21 LND builtin methods to use the new pattern - Add InMemoryStore implementation for testing - Wire up LND clients in terminal.go after LND connects - Fix integration test function signatures All 7 scripts integration tests now pass: - scripts_basic_crud - scripts_validation - scripts_execution - scripts_kv_store - scripts_lnd_access - scripts_builtins - scripts_kv_builtins
This commit is contained in:
parent
7b9c2419e7
commit
43df8e2d7d
7 changed files with 292 additions and 104 deletions
|
|
@ -15,8 +15,9 @@ import (
|
|||
)
|
||||
|
||||
// testScriptBasicCRUD tests basic script create, read, update, delete operations.
|
||||
func testScriptBasicCRUD(t *harnessTest, net *NetworkHarness) {
|
||||
ctx := context.Background()
|
||||
func testScriptBasicCRUD(ctx context.Context, net *NetworkHarness,
|
||||
t *harnessTest) {
|
||||
|
||||
ctxt, cancel := context.WithTimeout(ctx, defaultTimeout)
|
||||
defer cancel()
|
||||
|
||||
|
|
@ -98,8 +99,9 @@ def main():
|
|||
}
|
||||
|
||||
// testScriptValidation tests script syntax validation.
|
||||
func testScriptValidation(t *harnessTest, net *NetworkHarness) {
|
||||
ctx := context.Background()
|
||||
func testScriptValidation(ctx context.Context, net *NetworkHarness,
|
||||
t *harnessTest) {
|
||||
|
||||
ctxt, cancel := context.WithTimeout(ctx, defaultTimeout)
|
||||
defer cancel()
|
||||
|
||||
|
|
@ -148,8 +150,9 @@ def helper():
|
|||
}
|
||||
|
||||
// testScriptExecution tests basic script execution.
|
||||
func testScriptExecution(t *harnessTest, net *NetworkHarness) {
|
||||
ctx := context.Background()
|
||||
func testScriptExecution(ctx context.Context, net *NetworkHarness,
|
||||
t *harnessTest) {
|
||||
|
||||
ctxt, cancel := context.WithTimeout(ctx, defaultTimeout)
|
||||
defer cancel()
|
||||
|
||||
|
|
@ -220,8 +223,9 @@ def main(x=10, y=20):
|
|||
}
|
||||
|
||||
// testScriptKVStore tests the script KV store operations.
|
||||
func testScriptKVStore(t *harnessTest, net *NetworkHarness) {
|
||||
ctx := context.Background()
|
||||
func testScriptKVStore(ctx context.Context, net *NetworkHarness,
|
||||
t *harnessTest) {
|
||||
|
||||
ctxt, cancel := context.WithTimeout(ctx, defaultTimeout)
|
||||
defer cancel()
|
||||
|
||||
|
|
@ -298,8 +302,9 @@ func testScriptKVStore(t *harnessTest, net *NetworkHarness) {
|
|||
}
|
||||
|
||||
// testScriptWithLNDAccess tests scripts that access LND RPCs.
|
||||
func testScriptWithLNDAccess(t *harnessTest, net *NetworkHarness) {
|
||||
ctx := context.Background()
|
||||
func testScriptWithLNDAccess(ctx context.Context, net *NetworkHarness,
|
||||
t *harnessTest) {
|
||||
|
||||
ctxt, cancel := context.WithTimeout(ctx, defaultTimeout)
|
||||
defer cancel()
|
||||
|
||||
|
|
@ -350,8 +355,9 @@ def main():
|
|||
}
|
||||
|
||||
// testScriptBuiltins tests the built-in functions.
|
||||
func testScriptBuiltins(t *harnessTest, net *NetworkHarness) {
|
||||
ctx := context.Background()
|
||||
func testScriptBuiltins(ctx context.Context, net *NetworkHarness,
|
||||
t *harnessTest) {
|
||||
|
||||
ctxt, cancel := context.WithTimeout(ctx, defaultTimeout)
|
||||
defer cancel()
|
||||
|
||||
|
|
@ -411,8 +417,9 @@ def main():
|
|||
}
|
||||
|
||||
// testScriptWithKVBuiltins tests scripts using built-in KV functions.
|
||||
func testScriptWithKVBuiltins(t *harnessTest, net *NetworkHarness) {
|
||||
ctx := context.Background()
|
||||
func testScriptWithKVBuiltins(ctx context.Context, net *NetworkHarness,
|
||||
t *harnessTest) {
|
||||
|
||||
ctxt, cancel := context.WithTimeout(ctx, defaultTimeout)
|
||||
defer cancel()
|
||||
|
||||
|
|
|
|||
|
|
@ -5,19 +5,17 @@ import (
|
|||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
"github.com/lightninglabs/lndclient"
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
||||
"go.starlark.net/starlark"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
// LNDClients holds all the LND RPC clients needed for script execution.
|
||||
// These are the lndclient wrappers that provide authenticated access.
|
||||
type LNDClients struct {
|
||||
Lightning lnrpc.LightningClient
|
||||
Router routerrpc.RouterClient
|
||||
Invoices invoicesrpc.InvoicesClient
|
||||
Lightning lndclient.LightningClient
|
||||
Router lndclient.RouterClient
|
||||
}
|
||||
|
||||
// lndBuiltins provides LND RPC access to Starlark scripts.
|
||||
|
|
@ -82,14 +80,20 @@ func (e *Engine) registerLNDBuiltins(predeclared starlark.StringDict, clients *L
|
|||
predeclared["lnd"] = lndModule.Struct()
|
||||
}
|
||||
|
||||
// contextWithMacaroon creates a context with the script's macaroon.
|
||||
func (lb *lndBuiltins) contextWithMacaroon() context.Context {
|
||||
ctx := lb.engine.sandbox.Context()
|
||||
if lb.macaroon != "" {
|
||||
md := metadata.Pairs("macaroon", lb.macaroon)
|
||||
ctx = metadata.NewOutgoingContext(ctx, md)
|
||||
}
|
||||
return ctx
|
||||
// getRawLightningClient returns an authenticated context and the raw
|
||||
// Lightning client for making RPC calls.
|
||||
func (lb *lndBuiltins) getRawLightningClient() (context.Context, lnrpc.LightningClient) {
|
||||
parentCtx := lb.engine.sandbox.Context()
|
||||
ctx, _, client := lb.clients.Lightning.RawClientWithMacAuth(parentCtx)
|
||||
return ctx, client
|
||||
}
|
||||
|
||||
// getRawRouterClient returns an authenticated context and the raw
|
||||
// Router client for making RPC calls.
|
||||
func (lb *lndBuiltins) getRawRouterClient() (context.Context, routerrpc.RouterClient) {
|
||||
parentCtx := lb.engine.sandbox.Context()
|
||||
ctx, _, client := lb.clients.Router.RawClientWithMacAuth(parentCtx)
|
||||
return ctx, client
|
||||
}
|
||||
|
||||
// getInfo implements lnd.get_info().
|
||||
|
|
@ -100,23 +104,23 @@ func (lb *lndBuiltins) getInfo(thread *starlark.Thread, fn *starlark.Builtin,
|
|||
return nil, err
|
||||
}
|
||||
|
||||
ctx := lb.contextWithMacaroon()
|
||||
resp, err := lb.clients.Lightning.GetInfo(ctx, &lnrpc.GetInfoRequest{})
|
||||
ctx, client := lb.getRawLightningClient()
|
||||
resp, err := client.GetInfo(ctx, &lnrpc.GetInfoRequest{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get_info failed: %w", err)
|
||||
}
|
||||
|
||||
return lb.protoToDict(map[string]any{
|
||||
"identity_pubkey": resp.IdentityPubkey,
|
||||
"alias": resp.Alias,
|
||||
"num_active_channels": resp.NumActiveChannels,
|
||||
"identity_pubkey": resp.IdentityPubkey,
|
||||
"alias": resp.Alias,
|
||||
"num_active_channels": resp.NumActiveChannels,
|
||||
"num_inactive_channels": resp.NumInactiveChannels,
|
||||
"num_pending_channels": resp.NumPendingChannels,
|
||||
"num_peers": resp.NumPeers,
|
||||
"block_height": resp.BlockHeight,
|
||||
"synced_to_chain": resp.SyncedToChain,
|
||||
"synced_to_graph": resp.SyncedToGraph,
|
||||
"version": resp.Version,
|
||||
"num_pending_channels": resp.NumPendingChannels,
|
||||
"num_peers": resp.NumPeers,
|
||||
"block_height": resp.BlockHeight,
|
||||
"synced_to_chain": resp.SyncedToChain,
|
||||
"synced_to_graph": resp.SyncedToGraph,
|
||||
"version": resp.Version,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -131,8 +135,8 @@ func (lb *lndBuiltins) getNodeInfo(thread *starlark.Thread, fn *starlark.Builtin
|
|||
return nil, err
|
||||
}
|
||||
|
||||
ctx := lb.contextWithMacaroon()
|
||||
resp, err := lb.clients.Lightning.GetNodeInfo(ctx, &lnrpc.NodeInfoRequest{
|
||||
ctx, client := lb.getRawLightningClient()
|
||||
resp, err := client.GetNodeInfo(ctx, &lnrpc.NodeInfoRequest{
|
||||
PubKey: pubkey,
|
||||
IncludeChannels: includeChannels,
|
||||
})
|
||||
|
|
@ -170,8 +174,8 @@ func (lb *lndBuiltins) listChannels(thread *starlark.Thread, fn *starlark.Builti
|
|||
return nil, err
|
||||
}
|
||||
|
||||
ctx := lb.contextWithMacaroon()
|
||||
resp, err := lb.clients.Lightning.ListChannels(ctx, &lnrpc.ListChannelsRequest{
|
||||
ctx, client := lb.getRawLightningClient()
|
||||
resp, err := client.ListChannels(ctx, &lnrpc.ListChannelsRequest{
|
||||
ActiveOnly: activeOnly,
|
||||
InactiveOnly: inactiveOnly,
|
||||
PublicOnly: publicOnly,
|
||||
|
|
@ -211,8 +215,8 @@ func (lb *lndBuiltins) channelBalance(thread *starlark.Thread, fn *starlark.Buil
|
|||
return nil, err
|
||||
}
|
||||
|
||||
ctx := lb.contextWithMacaroon()
|
||||
resp, err := lb.clients.Lightning.ChannelBalance(ctx, &lnrpc.ChannelBalanceRequest{})
|
||||
ctx, client := lb.getRawLightningClient()
|
||||
resp, err := client.ChannelBalance(ctx, &lnrpc.ChannelBalanceRequest{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("channel_balance failed: %w", err)
|
||||
}
|
||||
|
|
@ -235,8 +239,8 @@ func (lb *lndBuiltins) pendingChannels(thread *starlark.Thread, fn *starlark.Bui
|
|||
return nil, err
|
||||
}
|
||||
|
||||
ctx := lb.contextWithMacaroon()
|
||||
resp, err := lb.clients.Lightning.PendingChannels(ctx, &lnrpc.PendingChannelsRequest{})
|
||||
ctx, client := lb.getRawLightningClient()
|
||||
resp, err := client.PendingChannels(ctx, &lnrpc.PendingChannelsRequest{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("pending_channels failed: %w", err)
|
||||
}
|
||||
|
|
@ -258,8 +262,8 @@ func (lb *lndBuiltins) closedChannels(thread *starlark.Thread, fn *starlark.Buil
|
|||
return nil, err
|
||||
}
|
||||
|
||||
ctx := lb.contextWithMacaroon()
|
||||
resp, err := lb.clients.Lightning.ClosedChannels(ctx, &lnrpc.ClosedChannelsRequest{})
|
||||
ctx, client := lb.getRawLightningClient()
|
||||
resp, err := client.ClosedChannels(ctx, &lnrpc.ClosedChannelsRequest{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("closed_channels failed: %w", err)
|
||||
}
|
||||
|
|
@ -314,8 +318,8 @@ func (lb *lndBuiltins) updateChannelPolicy(thread *starlark.Thread, fn *starlark
|
|||
req.Scope = &lnrpc.PolicyUpdateRequest_Global{Global: true}
|
||||
}
|
||||
|
||||
ctx := lb.contextWithMacaroon()
|
||||
resp, err := lb.clients.Lightning.UpdateChannelPolicy(ctx, req)
|
||||
ctx, client := lb.getRawLightningClient()
|
||||
resp, err := client.UpdateChannelPolicy(ctx, req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("update_channel_policy failed: %w", err)
|
||||
}
|
||||
|
|
@ -338,8 +342,8 @@ func (lb *lndBuiltins) walletBalance(thread *starlark.Thread, fn *starlark.Built
|
|||
return nil, err
|
||||
}
|
||||
|
||||
ctx := lb.contextWithMacaroon()
|
||||
resp, err := lb.clients.Lightning.WalletBalance(ctx, &lnrpc.WalletBalanceRequest{})
|
||||
ctx, client := lb.getRawLightningClient()
|
||||
resp, err := client.WalletBalance(ctx, &lnrpc.WalletBalanceRequest{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("wallet_balance failed: %w", err)
|
||||
}
|
||||
|
|
@ -366,8 +370,8 @@ func (lb *lndBuiltins) listUnspent(thread *starlark.Thread, fn *starlark.Builtin
|
|||
maxConfs = int32Max
|
||||
}
|
||||
|
||||
ctx := lb.contextWithMacaroon()
|
||||
resp, err := lb.clients.Lightning.ListUnspent(ctx, &lnrpc.ListUnspentRequest{
|
||||
ctx, client := lb.getRawLightningClient()
|
||||
resp, err := client.ListUnspent(ctx, &lnrpc.ListUnspentRequest{
|
||||
MinConfs: int32(minConfs),
|
||||
MaxConfs: int32(maxConfs),
|
||||
})
|
||||
|
|
@ -410,8 +414,8 @@ func (lb *lndBuiltins) newAddress(thread *starlark.Thread, fn *starlark.Builtin,
|
|||
return nil, fmt.Errorf("unknown address type: %s", addrType)
|
||||
}
|
||||
|
||||
ctx := lb.contextWithMacaroon()
|
||||
resp, err := lb.clients.Lightning.NewAddress(ctx, &lnrpc.NewAddressRequest{
|
||||
ctx, client := lb.getRawLightningClient()
|
||||
resp, err := client.NewAddress(ctx, &lnrpc.NewAddressRequest{
|
||||
Type: at,
|
||||
})
|
||||
if err != nil {
|
||||
|
|
@ -437,8 +441,8 @@ func (lb *lndBuiltins) sendCoins(thread *starlark.Thread, fn *starlark.Builtin,
|
|||
return nil, err
|
||||
}
|
||||
|
||||
ctx := lb.contextWithMacaroon()
|
||||
resp, err := lb.clients.Lightning.SendCoins(ctx, &lnrpc.SendCoinsRequest{
|
||||
ctx, client := lb.getRawLightningClient()
|
||||
resp, err := client.SendCoins(ctx, &lnrpc.SendCoinsRequest{
|
||||
Addr: addr,
|
||||
Amount: amount,
|
||||
SatPerVbyte: uint64(satPerVbyte),
|
||||
|
|
@ -467,8 +471,8 @@ func (lb *lndBuiltins) addInvoice(thread *starlark.Thread, fn *starlark.Builtin,
|
|||
expiry = 3600 // 1 hour default
|
||||
}
|
||||
|
||||
ctx := lb.contextWithMacaroon()
|
||||
resp, err := lb.clients.Lightning.AddInvoice(ctx, &lnrpc.Invoice{
|
||||
ctx, client := lb.getRawLightningClient()
|
||||
resp, err := client.AddInvoice(ctx, &lnrpc.Invoice{
|
||||
Memo: memo,
|
||||
Value: valueSat,
|
||||
Expiry: expiry,
|
||||
|
|
@ -499,8 +503,8 @@ func (lb *lndBuiltins) lookupInvoice(thread *starlark.Thread, fn *starlark.Built
|
|||
return nil, fmt.Errorf("invalid r_hash: %w", err)
|
||||
}
|
||||
|
||||
ctx := lb.contextWithMacaroon()
|
||||
resp, err := lb.clients.Lightning.LookupInvoice(ctx, &lnrpc.PaymentHash{
|
||||
ctx, client := lb.getRawLightningClient()
|
||||
resp, err := client.LookupInvoice(ctx, &lnrpc.PaymentHash{
|
||||
RHash: rHash,
|
||||
})
|
||||
if err != nil {
|
||||
|
|
@ -528,8 +532,8 @@ func (lb *lndBuiltins) listInvoices(thread *starlark.Thread, fn *starlark.Builti
|
|||
numMaxInvoices = 100
|
||||
}
|
||||
|
||||
ctx := lb.contextWithMacaroon()
|
||||
resp, err := lb.clients.Lightning.ListInvoices(ctx, &lnrpc.ListInvoiceRequest{
|
||||
ctx, client := lb.getRawLightningClient()
|
||||
resp, err := client.ListInvoices(ctx, &lnrpc.ListInvoiceRequest{
|
||||
PendingOnly: pendingOnly,
|
||||
IndexOffset: uint64(indexOffset),
|
||||
NumMaxInvoices: uint64(numMaxInvoices),
|
||||
|
|
@ -561,8 +565,8 @@ func (lb *lndBuiltins) decodePayReq(thread *starlark.Thread, fn *starlark.Builti
|
|||
return nil, err
|
||||
}
|
||||
|
||||
ctx := lb.contextWithMacaroon()
|
||||
resp, err := lb.clients.Lightning.DecodePayReq(ctx, &lnrpc.PayReqString{
|
||||
ctx, client := lb.getRawLightningClient()
|
||||
resp, err := client.DecodePayReq(ctx, &lnrpc.PayReqString{
|
||||
PayReq: payReq,
|
||||
})
|
||||
if err != nil {
|
||||
|
|
@ -614,8 +618,8 @@ func (lb *lndBuiltins) sendPayment(thread *starlark.Thread, fn *starlark.Builtin
|
|||
req.Amt = amtSat
|
||||
}
|
||||
|
||||
ctx := lb.contextWithMacaroon()
|
||||
stream, err := lb.clients.Router.SendPaymentV2(ctx, req)
|
||||
ctx, routerClient := lb.getRawRouterClient()
|
||||
stream, err := routerClient.SendPaymentV2(ctx, req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("send_payment failed: %w", err)
|
||||
}
|
||||
|
|
@ -672,8 +676,8 @@ func (lb *lndBuiltins) listPayments(thread *starlark.Thread, fn *starlark.Builti
|
|||
maxPayments = 100
|
||||
}
|
||||
|
||||
ctx := lb.contextWithMacaroon()
|
||||
resp, err := lb.clients.Lightning.ListPayments(ctx, &lnrpc.ListPaymentsRequest{
|
||||
ctx, client := lb.getRawLightningClient()
|
||||
resp, err := client.ListPayments(ctx, &lnrpc.ListPaymentsRequest{
|
||||
IncludeIncomplete: includeIncomplete,
|
||||
IndexOffset: uint64(indexOffset),
|
||||
MaxPayments: uint64(maxPayments),
|
||||
|
|
@ -709,8 +713,8 @@ func (lb *lndBuiltins) listPeers(thread *starlark.Thread, fn *starlark.Builtin,
|
|||
return nil, err
|
||||
}
|
||||
|
||||
ctx := lb.contextWithMacaroon()
|
||||
resp, err := lb.clients.Lightning.ListPeers(ctx, &lnrpc.ListPeersRequest{})
|
||||
ctx, client := lb.getRawLightningClient()
|
||||
resp, err := client.ListPeers(ctx, &lnrpc.ListPeersRequest{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("list_peers failed: %w", err)
|
||||
}
|
||||
|
|
@ -743,8 +747,8 @@ func (lb *lndBuiltins) connectPeer(thread *starlark.Thread, fn *starlark.Builtin
|
|||
return nil, err
|
||||
}
|
||||
|
||||
ctx := lb.contextWithMacaroon()
|
||||
_, err := lb.clients.Lightning.ConnectPeer(ctx, &lnrpc.ConnectPeerRequest{
|
||||
ctx, client := lb.getRawLightningClient()
|
||||
_, err := client.ConnectPeer(ctx, &lnrpc.ConnectPeerRequest{
|
||||
Addr: &lnrpc.LightningAddress{
|
||||
Pubkey: pubkey,
|
||||
Host: host,
|
||||
|
|
@ -767,8 +771,8 @@ func (lb *lndBuiltins) disconnectPeer(thread *starlark.Thread, fn *starlark.Buil
|
|||
return nil, err
|
||||
}
|
||||
|
||||
ctx := lb.contextWithMacaroon()
|
||||
_, err := lb.clients.Lightning.DisconnectPeer(ctx, &lnrpc.DisconnectPeerRequest{
|
||||
ctx, client := lb.getRawLightningClient()
|
||||
_, err := client.DisconnectPeer(ctx, &lnrpc.DisconnectPeerRequest{
|
||||
PubKey: pubkey,
|
||||
})
|
||||
if err != nil {
|
||||
|
|
@ -797,8 +801,8 @@ func (lb *lndBuiltins) forwardingHistory(thread *starlark.Thread, fn *starlark.B
|
|||
numMaxEvents = 100
|
||||
}
|
||||
|
||||
ctx := lb.contextWithMacaroon()
|
||||
resp, err := lb.clients.Lightning.ForwardingHistory(ctx, &lnrpc.ForwardingHistoryRequest{
|
||||
ctx, client := lb.getRawLightningClient()
|
||||
resp, err := client.ForwardingHistory(ctx, &lnrpc.ForwardingHistoryRequest{
|
||||
StartTime: uint64(startTime),
|
||||
EndTime: uint64(endTime),
|
||||
IndexOffset: uint32(indexOffset),
|
||||
|
|
@ -843,8 +847,8 @@ func (lb *lndBuiltins) estimateFee(thread *starlark.Thread, fn *starlark.Builtin
|
|||
targetConf = 6
|
||||
}
|
||||
|
||||
ctx := lb.contextWithMacaroon()
|
||||
resp, err := lb.clients.Lightning.EstimateFee(ctx, &lnrpc.EstimateFeeRequest{
|
||||
ctx, client := lb.getRawLightningClient()
|
||||
resp, err := client.EstimateFee(ctx, &lnrpc.EstimateFeeRequest{
|
||||
AddrToAmount: map[string]int64{
|
||||
"bcrt1qw508d6qejxtdg4y5r3zarvary0c5xw7kygt080": 10000,
|
||||
},
|
||||
|
|
@ -891,11 +895,10 @@ func (e *Engine) SetLNDClients(clients *LNDClients) {
|
|||
e.registerLNDBuiltins(e.predeclared, clients)
|
||||
}
|
||||
|
||||
// NewLNDClientsFromConn creates LND clients from a gRPC connection.
|
||||
func NewLNDClientsFromConn(conn *grpc.ClientConn) *LNDClients {
|
||||
// NewLNDClientsFromServices creates LND clients from lndclient services.
|
||||
func NewLNDClientsFromServices(services *lndclient.LndServices) *LNDClients {
|
||||
return &LNDClients{
|
||||
Lightning: lnrpc.NewLightningClient(conn),
|
||||
Router: routerrpc.NewRouterClient(conn),
|
||||
Invoices: invoicesrpc.NewInvoicesClient(conn),
|
||||
Lightning: services.Client,
|
||||
Router: services.Router,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,6 +48,9 @@ type EngineConfig struct {
|
|||
|
||||
// OutputCallback is called when the script produces output.
|
||||
OutputCallback OutputCallback
|
||||
|
||||
// LNDClients provides access to LND RPCs.
|
||||
LNDClients *LNDClients
|
||||
}
|
||||
|
||||
// Engine executes Starlark scripts in a sandboxed environment.
|
||||
|
|
@ -62,6 +65,7 @@ type Engine struct {
|
|||
kvStore KVStore
|
||||
rpcCaller RPCCaller
|
||||
outputCallback OutputCallback
|
||||
lndClients *LNDClients
|
||||
|
||||
// thread is the Starlark execution thread.
|
||||
thread *starlark.Thread
|
||||
|
|
@ -96,6 +100,7 @@ func NewEngine(ctx context.Context, cfg EngineConfig) *Engine {
|
|||
kvStore: cfg.KVStore,
|
||||
rpcCaller: cfg.RPCCaller,
|
||||
outputCallback: cfg.OutputCallback,
|
||||
lndClients: cfg.LNDClients,
|
||||
subscriptionHandlers: make(map[string]starlark.Callable),
|
||||
}
|
||||
|
||||
|
|
@ -104,6 +109,7 @@ func NewEngine(ctx context.Context, cfg EngineConfig) *Engine {
|
|||
e.registerStandardBuiltins(e.predeclared)
|
||||
e.registerHTTPBuiltins(e.predeclared)
|
||||
e.registerKVBuiltins(e.predeclared)
|
||||
e.registerLNDBuiltins(e.predeclared, cfg.LNDClients)
|
||||
|
||||
return e
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,10 +20,11 @@ type MacaroonBaker interface {
|
|||
|
||||
// Manager handles script CRUD operations and macaroon management.
|
||||
type Manager struct {
|
||||
store Store
|
||||
kvStore KVStore
|
||||
macBaker MacaroonBaker
|
||||
rpcCaller RPCCaller
|
||||
store Store
|
||||
kvStore KVStore
|
||||
macBaker MacaroonBaker
|
||||
rpcCaller RPCCaller
|
||||
lndClients *LNDClients
|
||||
|
||||
// runners tracks currently running scripts.
|
||||
runners map[string]*Runner
|
||||
|
|
@ -41,6 +42,17 @@ func NewManager(store Store, kvStore KVStore, macBaker MacaroonBaker, rpcCaller
|
|||
}
|
||||
}
|
||||
|
||||
// SetMacaroonBaker sets the macaroon baker after initialization.
|
||||
// This is used when the baker needs to be set after LND connects.
|
||||
func (m *Manager) SetMacaroonBaker(baker MacaroonBaker) {
|
||||
m.macBaker = baker
|
||||
}
|
||||
|
||||
// SetLNDClients sets the LND clients for script RPC access.
|
||||
func (m *Manager) SetLNDClients(clients *LNDClients) {
|
||||
m.lndClients = clients
|
||||
}
|
||||
|
||||
// CreateScript creates a new script with the specified permissions.
|
||||
func (m *Manager) CreateScript(ctx context.Context, req *litrpc.CreateScriptRequest) (*Script, error) {
|
||||
// Validate the source.
|
||||
|
|
@ -247,7 +259,7 @@ func (m *Manager) StartScript(ctx context.Context, name string, argsJSON string)
|
|||
}
|
||||
|
||||
// Create and start runner.
|
||||
runner := NewRunner(m, script, exec.ID, m.kvStore, m.rpcCaller)
|
||||
runner := NewRunner(m, script, exec.ID, m.kvStore, m.rpcCaller, m.lndClients)
|
||||
|
||||
m.runnersMu.Lock()
|
||||
m.runners[name] = runner
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ type Runner struct {
|
|||
executionID int64
|
||||
kvStore KVStore
|
||||
rpcCaller RPCCaller
|
||||
lndClients *LNDClients
|
||||
|
||||
engine *Engine
|
||||
ctx context.Context
|
||||
|
|
@ -29,7 +30,7 @@ type ScriptOutput struct {
|
|||
}
|
||||
|
||||
// NewRunner creates a new script runner.
|
||||
func NewRunner(manager *Manager, script *Script, executionID int64, kvStore KVStore, rpcCaller RPCCaller) *Runner {
|
||||
func NewRunner(manager *Manager, script *Script, executionID int64, kvStore KVStore, rpcCaller RPCCaller, lndClients *LNDClients) *Runner {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
return &Runner{
|
||||
|
|
@ -38,6 +39,7 @@ func NewRunner(manager *Manager, script *Script, executionID int64, kvStore KVSt
|
|||
executionID: executionID,
|
||||
kvStore: kvStore,
|
||||
rpcCaller: rpcCaller,
|
||||
lndClients: lndClients,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
outputs: make([]ScriptOutput, 0),
|
||||
|
|
@ -67,8 +69,9 @@ func (r *Runner) Run(ctx context.Context, args map[string]interface{}) *Executio
|
|||
MaxMemoryBytes: r.script.MaxMemoryBytes,
|
||||
TimeoutSecs: r.script.TimeoutSecs,
|
||||
},
|
||||
KVStore: r.kvStore,
|
||||
RPCCaller: r.rpcCaller,
|
||||
KVStore: r.kvStore,
|
||||
RPCCaller: r.rpcCaller,
|
||||
LNDClients: r.lndClients,
|
||||
OutputCallback: func(level, message string) {
|
||||
r.mu.Lock()
|
||||
r.outputs = append(r.outputs, ScriptOutput{
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package scripting
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
|
@ -94,6 +95,154 @@ const (
|
|||
StateStopped = "stopped"
|
||||
)
|
||||
|
||||
// InMemoryStore implements Store with in-memory storage.
|
||||
// This is useful for testing and development.
|
||||
type InMemoryStore struct {
|
||||
scripts map[string]*Script
|
||||
executions []*ScriptExecution
|
||||
running map[int64]*RunningScript
|
||||
nextID int64
|
||||
nextExecID int64
|
||||
}
|
||||
|
||||
// NewInMemoryStore creates a new in-memory script store.
|
||||
func NewInMemoryStore() *InMemoryStore {
|
||||
return &InMemoryStore{
|
||||
scripts: make(map[string]*Script),
|
||||
running: make(map[int64]*RunningScript),
|
||||
nextID: 1,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *InMemoryStore) CreateScript(ctx context.Context, script *Script) error {
|
||||
if _, exists := s.scripts[script.Name]; exists {
|
||||
return fmt.Errorf("script %s already exists", script.Name)
|
||||
}
|
||||
script.ID = s.nextID
|
||||
s.nextID++
|
||||
s.scripts[script.Name] = script
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *InMemoryStore) UpdateScript(ctx context.Context, script *Script) error {
|
||||
if _, exists := s.scripts[script.Name]; !exists {
|
||||
return fmt.Errorf("script %s not found", script.Name)
|
||||
}
|
||||
s.scripts[script.Name] = script
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *InMemoryStore) DeleteScript(ctx context.Context, name string) error {
|
||||
delete(s.scripts, name)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *InMemoryStore) GetScript(ctx context.Context, name string) (*Script, error) {
|
||||
script, exists := s.scripts[name]
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("script %s not found", name)
|
||||
}
|
||||
return script, nil
|
||||
}
|
||||
|
||||
func (s *InMemoryStore) GetScriptByID(ctx context.Context, id int64) (*Script, error) {
|
||||
for _, script := range s.scripts {
|
||||
if script.ID == id {
|
||||
return script, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("script with ID %d not found", id)
|
||||
}
|
||||
|
||||
func (s *InMemoryStore) ListScripts(ctx context.Context) ([]*Script, error) {
|
||||
result := make([]*Script, 0, len(s.scripts))
|
||||
for _, script := range s.scripts {
|
||||
result = append(result, script)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *InMemoryStore) CreateExecution(ctx context.Context, exec *ScriptExecution) error {
|
||||
s.nextExecID++
|
||||
exec.ID = s.nextExecID
|
||||
s.executions = append(s.executions, exec)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *InMemoryStore) UpdateExecution(ctx context.Context, exec *ScriptExecution) error {
|
||||
for i, e := range s.executions {
|
||||
if e.ID == exec.ID {
|
||||
s.executions[i] = exec
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("execution %d not found", exec.ID)
|
||||
}
|
||||
|
||||
func (s *InMemoryStore) GetExecution(ctx context.Context, id int64) (*ScriptExecution, error) {
|
||||
for _, e := range s.executions {
|
||||
if e.ID == id {
|
||||
return e, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("execution %d not found", id)
|
||||
}
|
||||
|
||||
func (s *InMemoryStore) ListExecutions(ctx context.Context, scriptName string, limit, offset uint32) ([]*ScriptExecution, error) {
|
||||
var result []*ScriptExecution
|
||||
for _, e := range s.executions {
|
||||
if scriptName == "" || e.ScriptName == scriptName {
|
||||
result = append(result, e)
|
||||
}
|
||||
}
|
||||
// Simple pagination
|
||||
start := int(offset)
|
||||
if start >= len(result) {
|
||||
return nil, nil
|
||||
}
|
||||
end := start + int(limit)
|
||||
if end > len(result) {
|
||||
end = len(result)
|
||||
}
|
||||
return result[start:end], nil
|
||||
}
|
||||
|
||||
func (s *InMemoryStore) MarkRunning(ctx context.Context, scriptID, executionID int64) error {
|
||||
script, err := s.GetScriptByID(ctx, scriptID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.running[scriptID] = &RunningScript{
|
||||
ScriptID: scriptID,
|
||||
ScriptName: script.Name,
|
||||
ExecutionID: executionID,
|
||||
StartedAt: time.Now(),
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *InMemoryStore) MarkStopped(ctx context.Context, scriptID int64) error {
|
||||
delete(s.running, scriptID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *InMemoryStore) GetRunningScripts(ctx context.Context) ([]*RunningScript, error) {
|
||||
result := make([]*RunningScript, 0, len(s.running))
|
||||
for _, r := range s.running {
|
||||
result = append(result, r)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *InMemoryStore) IsScriptRunning(ctx context.Context, scriptID int64) (bool, error) {
|
||||
_, exists := s.running[scriptID]
|
||||
return exists, nil
|
||||
}
|
||||
|
||||
func (s *InMemoryStore) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Store defines the interface for script storage.
|
||||
type Store interface {
|
||||
// CreateScript creates a new script.
|
||||
|
|
|
|||
28
terminal.go
28
terminal.go
|
|
@ -496,6 +496,18 @@ func (g *LightningTerminal) start(ctx context.Context) error {
|
|||
// to understand why this is necessary.
|
||||
g.sessionRpcServer = newSessionRPCServer()
|
||||
|
||||
// Initialize the scripts service early. The full dependencies (like the
|
||||
// macaroon baker) will be set after LND connects.
|
||||
scriptStore := scripting.NewInMemoryStore()
|
||||
scriptKVStore := scripting.NewInMemoryKVStore()
|
||||
g.scriptManager = scripting.NewManager(
|
||||
scriptStore,
|
||||
scriptKVStore,
|
||||
nil, // Macaroon baker will be set after LND connects
|
||||
nil, // RPC caller not yet implemented
|
||||
)
|
||||
g.scriptRpcServer = scripting.NewRPCServer(g.scriptManager, scriptKVStore)
|
||||
|
||||
// Call the "real" main in a nested manner so the defers will properly
|
||||
// be executed in the case of a graceful shutdown.
|
||||
var (
|
||||
|
|
@ -1058,18 +1070,14 @@ func (g *LightningTerminal) startInternalSubServers(ctx context.Context,
|
|||
|
||||
g.accountRpcServer.Start(g.accountService, superMacBaker)
|
||||
|
||||
// Initialize the scripts service with an in-memory KV store.
|
||||
// TODO: Replace with persistent storage when BoltDB/SQL store is ready.
|
||||
// Set the macaroon baker and LND clients for the scripts service now
|
||||
// that LND is connected. The script manager was initialized early
|
||||
// before LND setup.
|
||||
log.Infof("Starting LiT scripts server")
|
||||
scriptKVStore := scripting.NewInMemoryKVStore()
|
||||
macBaker := scripting.NewLndMacaroonBaker(g.basicClient)
|
||||
g.scriptManager = scripting.NewManager(
|
||||
nil, // Store will be set when persistent storage is implemented
|
||||
scriptKVStore,
|
||||
macBaker,
|
||||
nil, // RPC caller not yet implemented
|
||||
)
|
||||
g.scriptRpcServer = scripting.NewRPCServer(g.scriptManager, scriptKVStore)
|
||||
g.scriptManager.SetMacaroonBaker(macBaker)
|
||||
lndClients := scripting.NewLNDClientsFromServices(&g.lndClient.LndServices)
|
||||
g.scriptManager.SetLNDClients(lndClients)
|
||||
|
||||
if !g.cfg.Autopilot.Disable {
|
||||
withLndVersion := func(cfg *autopilotserver.Config) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue