mirror of
https://github.com/lightninglabs/lightning-terminal.git
synced 2026-08-13 12:33:36 +02:00
session+accounts: add RPC server
This commit is contained in:
parent
6af85cfb70
commit
cd741091be
4 changed files with 259 additions and 6 deletions
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"gopkg.in/macaroon-bakery.v2/bakery"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -148,6 +149,31 @@ var (
|
|||
// interceptor.
|
||||
ErrNotSupportedWithAccounts = errors.New("this RPC call is not " +
|
||||
"supported with restricted account macaroons")
|
||||
|
||||
// MacaroonPermissions are the permissions required for an account
|
||||
// macaroon.
|
||||
MacaroonPermissions = []bakery.Op{{
|
||||
Entity: "info",
|
||||
Action: "read",
|
||||
}, {
|
||||
Entity: "offchain",
|
||||
Action: "read",
|
||||
}, {
|
||||
Entity: "offchain",
|
||||
Action: "write",
|
||||
}, {
|
||||
Entity: "onchain",
|
||||
Action: "read",
|
||||
}, {
|
||||
Entity: "invoices",
|
||||
Action: "read",
|
||||
}, {
|
||||
Entity: "invoices",
|
||||
Action: "write",
|
||||
}, {
|
||||
Entity: "peers",
|
||||
Action: "read",
|
||||
}}
|
||||
)
|
||||
|
||||
// Store is the main account store interface.
|
||||
|
|
|
|||
223
accounts/rpcserver.go
Normal file
223
accounts/rpcserver.go
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
package accounts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/lightninglabs/lightning-terminal/litrpc"
|
||||
"github.com/lightninglabs/lightning-terminal/session"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/macaroons"
|
||||
"gopkg.in/macaroon-bakery.v2/bakery/checkers"
|
||||
"gopkg.in/macaroon.v2"
|
||||
)
|
||||
|
||||
// RPCServer is the main server that implements the Accounts gRPC service.
|
||||
type RPCServer struct {
|
||||
litrpc.UnimplementedAccountsServer
|
||||
|
||||
service *InterceptorService
|
||||
|
||||
superMacBaker session.MacaroonBaker
|
||||
}
|
||||
|
||||
// NewRPCServer returns a new RPC server for the given service.
|
||||
func NewRPCServer(service *InterceptorService,
|
||||
superMacBaker session.MacaroonBaker) *RPCServer {
|
||||
|
||||
return &RPCServer{
|
||||
service: service,
|
||||
superMacBaker: superMacBaker,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateAccount adds an entry to the account database. This entry represents
|
||||
// an amount of satoshis (account balance) that can be spent using off-chain
|
||||
// transactions (e.g. paying invoices).
|
||||
//
|
||||
// Macaroons can be created to be locked to an account. This makes sure that
|
||||
// the bearer of the macaroon can only spend at most that amount of satoshis
|
||||
// through the daemon that has issued the macaroon.
|
||||
//
|
||||
// Accounts only assert a maximum amount spendable. Having a certain account
|
||||
// balance does not guarantee that the node has the channel liquidity to
|
||||
// actually spend that amount.
|
||||
func (s *RPCServer) CreateAccount(ctx context.Context,
|
||||
req *litrpc.CreateAccountRequest) (*litrpc.CreateAccountResponse,
|
||||
error) {
|
||||
|
||||
log.Infof("[createaccount] balance=%d, expiration=%d",
|
||||
req.AccountBalance, req.ExpirationDate)
|
||||
|
||||
var (
|
||||
balanceMsat lnwire.MilliSatoshi
|
||||
expirationDate time.Time
|
||||
)
|
||||
|
||||
// If the expiration date was set, parse it as a unix time stamp.
|
||||
// Otherwise, we leave it nil to indicate the account has no expiration
|
||||
// date.
|
||||
if req.ExpirationDate > 0 {
|
||||
expirationDate = time.Unix(req.ExpirationDate, 0)
|
||||
}
|
||||
|
||||
// Convert from satoshis to millisatoshis for storage.
|
||||
balance := btcutil.Amount(req.AccountBalance)
|
||||
balanceMsat = lnwire.NewMSatFromSatoshis(balance)
|
||||
|
||||
// Create the actual account in the macaroon account store.
|
||||
account, err := s.service.NewAccount(balanceMsat, expirationDate)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to create account: %v", err)
|
||||
}
|
||||
|
||||
var rootKeyIdSuffix [4]byte
|
||||
copy(rootKeyIdSuffix[:], account.ID[0:4])
|
||||
macRootKey := session.NewSuperMacaroonRootKeyID(rootKeyIdSuffix)
|
||||
|
||||
accountCaveat := checkers.Condition(
|
||||
macaroons.CondLndCustom,
|
||||
fmt.Sprintf("%s %x", CondAccount, account.ID[:]),
|
||||
)
|
||||
|
||||
macHex, err := s.superMacBaker(ctx, macRootKey, &session.MacaroonRecipe{
|
||||
Permissions: MacaroonPermissions,
|
||||
Caveats: []macaroon.Caveat{{
|
||||
Id: []byte(accountCaveat),
|
||||
}},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error baking account macaroon: %v", err)
|
||||
}
|
||||
|
||||
macBytes, err := hex.DecodeString(macHex)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error decoding account macaroon: %v",
|
||||
err)
|
||||
}
|
||||
|
||||
return &litrpc.CreateAccountResponse{
|
||||
Account: marshalAccount(account),
|
||||
Macaroon: macBytes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UpdateAccount updates an existing account in the account database.
|
||||
func (s *RPCServer) UpdateAccount(_ context.Context,
|
||||
req *litrpc.UpdateAccountRequest) (*litrpc.Account, error) {
|
||||
|
||||
log.Infof("[updateaccount] id=%s, balance=%d, expiration=%d", req.Id,
|
||||
req.AccountBalance, req.ExpirationDate)
|
||||
|
||||
// Account ID is always a hex string, convert it to our account ID type.
|
||||
var accountID AccountID
|
||||
decoded, err := hex.DecodeString(req.Id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error decoding account ID: %v", err)
|
||||
}
|
||||
copy(accountID[:], decoded)
|
||||
|
||||
// Ask the service to update the account.
|
||||
account, err := s.service.UpdateAccount(
|
||||
accountID, req.AccountBalance, req.ExpirationDate,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return marshalAccount(account), nil
|
||||
}
|
||||
|
||||
// ListAccounts returns all accounts that are currently stored in the account
|
||||
// database.
|
||||
func (s *RPCServer) ListAccounts(context.Context,
|
||||
*litrpc.ListAccountsRequest) (*litrpc.ListAccountsResponse, error) {
|
||||
|
||||
log.Info("[listaccounts]")
|
||||
|
||||
// Retrieve all accounts from the macaroon account store.
|
||||
accts, err := s.service.Accounts()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to list accounts: %v", err)
|
||||
}
|
||||
|
||||
// Map the response into the proper response type and return it.
|
||||
rpcAccounts := make([]*litrpc.Account, len(accts))
|
||||
for i, acct := range accts {
|
||||
acct := acct
|
||||
|
||||
rpcAccounts[i] = marshalAccount(acct)
|
||||
}
|
||||
|
||||
return &litrpc.ListAccountsResponse{
|
||||
Accounts: rpcAccounts,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// RemoveAccount removes the given account from the account database.
|
||||
func (s *RPCServer) RemoveAccount(_ context.Context,
|
||||
req *litrpc.RemoveAccountRequest) (*litrpc.RemoveAccountResponse,
|
||||
error) {
|
||||
|
||||
log.Infof("[removeaccount] id=%v", req.Id)
|
||||
|
||||
// Account ID is always a hex string, convert it to our account ID type.
|
||||
var accountID AccountID
|
||||
decoded, err := hex.DecodeString(req.Id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error decoding account ID: %v", err)
|
||||
}
|
||||
copy(accountID[:], decoded)
|
||||
|
||||
// Now remove the account.
|
||||
err = s.service.RemoveAccount(accountID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error removing account: %v", err)
|
||||
}
|
||||
|
||||
return &litrpc.RemoveAccountResponse{}, nil
|
||||
}
|
||||
|
||||
// marshalAccount converts an account into its RPC counterpart.
|
||||
func marshalAccount(acct *OffChainBalanceAccount) *litrpc.Account {
|
||||
rpcAccount := &litrpc.Account{
|
||||
Id: hex.EncodeToString(acct.ID[:]),
|
||||
InitialBalance: uint64(acct.InitialBalance.ToSatoshis()),
|
||||
CurrentBalance: acct.CurrentBalanceSats(),
|
||||
LastUpdate: acct.LastUpdate.Unix(),
|
||||
ExpirationDate: int64(0),
|
||||
Invoices: make(
|
||||
[]*litrpc.AccountInvoice, 0, len(acct.Invoices),
|
||||
),
|
||||
Payments: make(
|
||||
[]*litrpc.AccountPayment, 0, len(acct.Payments),
|
||||
),
|
||||
}
|
||||
|
||||
for hash := range acct.Invoices {
|
||||
i := &litrpc.AccountInvoice{
|
||||
Hash: make([]byte, lntypes.HashSize),
|
||||
}
|
||||
copy(i.Hash, hash[:])
|
||||
rpcAccount.Invoices = append(rpcAccount.Invoices, i)
|
||||
}
|
||||
for hash, paymentEntry := range acct.Payments {
|
||||
p := &litrpc.AccountPayment{
|
||||
Hash: make([]byte, lntypes.HashSize),
|
||||
State: paymentEntry.Status.String(),
|
||||
FullAmount: int64(paymentEntry.FullAmount.ToSatoshis()),
|
||||
}
|
||||
copy(p.Hash, hash[:])
|
||||
rpcAccount.Payments = append(rpcAccount.Payments, p)
|
||||
}
|
||||
|
||||
if !acct.ExpirationDate.IsZero() {
|
||||
rpcAccount.ExpirationDate = acct.ExpirationDate.Unix()
|
||||
}
|
||||
|
||||
return rpcAccount
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package session
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
|
|
@ -54,6 +55,10 @@ type Session struct {
|
|||
RemotePublicKey *btcec.PublicKey
|
||||
}
|
||||
|
||||
// MacaroonBaker is a function type for baking a super macaroon.
|
||||
type MacaroonBaker func(ctx context.Context, rootKeyID uint64,
|
||||
recipe *MacaroonRecipe) (string, error)
|
||||
|
||||
// NewSession creates a new session with the given user-defined parameters.
|
||||
func NewSession(label string, typ Type, expiry time.Time, serverAddr string,
|
||||
devServer bool, perms []bakery.Op, caveats []macaroon.Caveat) (*Session,
|
||||
|
|
|
|||
|
|
@ -43,12 +43,11 @@ type sessionRpcServer struct {
|
|||
// sessionRpcServerConfig holds the values used to configure the
|
||||
// sessionRpcServer.
|
||||
type sessionRpcServerConfig struct {
|
||||
basicAuth string
|
||||
dbDir string
|
||||
grpcOptions []grpc.ServerOption
|
||||
registerGrpcServers func(server *grpc.Server)
|
||||
superMacBaker func(ctx context.Context, rootKeyID uint64,
|
||||
recipe *session.MacaroonRecipe) (string, error)
|
||||
basicAuth string
|
||||
dbDir string
|
||||
grpcOptions []grpc.ServerOption
|
||||
registerGrpcServers func(server *grpc.Server)
|
||||
superMacBaker session.MacaroonBaker
|
||||
firstConnectionDeadline time.Duration
|
||||
permMgr *perms.Manager
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue