mirror of
https://github.com/lightninglabs/pool.git
synced 2026-08-13 12:33:04 +02:00
account: expose historical account modification fees via RPC
This commit adds a new RPC endpoint which returns a per-account list of account modification fees.
This commit is contained in:
parent
87f76c7d80
commit
cd632058b0
10 changed files with 1439 additions and 712 deletions
|
|
@ -30,6 +30,7 @@ import (
|
|||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/lnrpc"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/verrpc"
|
||||
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
|
|
@ -52,6 +53,8 @@ const (
|
|||
// both extremes for valid account expirations.
|
||||
minAccountExpiry = 144 // One day worth of blocks.
|
||||
maxAccountExpiry = 144 * 365 // A year worth of blocks.
|
||||
|
||||
txLabelPrefixTag = "poold -- "
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -148,27 +151,46 @@ type AccountTxLabel struct {
|
|||
func actionTxLabel(account *Account, action Action, isExpirySpend bool,
|
||||
txFee *btcutil.Amount, balanceDiff btcutil.Amount) string {
|
||||
|
||||
prefixTag := "poold -- %s"
|
||||
|
||||
acctKey := account.TraderKey.PubKey.SerializeCompressed()
|
||||
key := fmt.Sprintf("%x", acctKey)
|
||||
label := AccountTxLabel{
|
||||
Key: key,
|
||||
Action: action,
|
||||
ExpiryHeight: account.Expiry,
|
||||
OutputIndex: account.OutPoint.Index,
|
||||
IsExpirySpend: isExpirySpend,
|
||||
TxFee: txFee,
|
||||
BalanceDiff: balanceDiff,
|
||||
label := TxLabel{
|
||||
Account: AccountTxLabel{
|
||||
Key: key,
|
||||
Action: action,
|
||||
ExpiryHeight: account.Expiry,
|
||||
OutputIndex: account.OutPoint.Index,
|
||||
IsExpirySpend: isExpirySpend,
|
||||
TxFee: txFee,
|
||||
BalanceDiff: balanceDiff,
|
||||
},
|
||||
}
|
||||
labelJson, err := json.Marshal(label)
|
||||
if err != nil {
|
||||
log.Errorf("Internal error: failed to serialize json "+
|
||||
"from %v: %v", label, err)
|
||||
return fmt.Sprintf(prefixTag, action)
|
||||
return fmt.Sprintf("%s%s", txLabelPrefixTag, action)
|
||||
}
|
||||
|
||||
return fmt.Sprintf(prefixTag, labelJson)
|
||||
return fmt.Sprintf("%s%s", txLabelPrefixTag, labelJson)
|
||||
}
|
||||
|
||||
// IsPoolTx returns true if the given transaction is related to pool.
|
||||
func IsPoolTx(tx *lnrpc.Transaction) bool {
|
||||
return strings.HasPrefix(tx.Label, txLabelPrefixTag)
|
||||
}
|
||||
|
||||
// ParseTxLabel parses and returns data fields stored in a given transaction
|
||||
// label.
|
||||
func ParseTxLabel(label string) (*TxLabel, error) {
|
||||
label = strings.TrimPrefix(label, txLabelPrefixTag)
|
||||
|
||||
var data TxLabel
|
||||
err := json.Unmarshal([]byte(label), &data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &data, nil
|
||||
}
|
||||
|
||||
// ManagerConfig contains all of the required dependencies for the Manager to
|
||||
|
|
|
|||
|
|
@ -49,12 +49,14 @@ var (
|
|||
)
|
||||
|
||||
type testCase struct {
|
||||
name string
|
||||
feeExpr FeeExpr
|
||||
fee btcutil.Amount
|
||||
version Version
|
||||
newVersion Version
|
||||
expectedErr string
|
||||
name string
|
||||
feeExpr FeeExpr
|
||||
fee btcutil.Amount
|
||||
version Version
|
||||
newVersion Version
|
||||
expectedErr string
|
||||
action Action
|
||||
isExpirySpend bool
|
||||
|
||||
// The following fields are only used by deposit tests.
|
||||
fundedOutputAmount btcutil.Amount
|
||||
|
|
@ -1430,3 +1432,55 @@ func TestMakeTxnLabel(t *testing.T) {
|
|||
require.Equal(t, genLabel, testCase.label)
|
||||
}
|
||||
}
|
||||
|
||||
// TestParseTxLabel tests whether an account transaction labels can be
|
||||
// parsed correctly.
|
||||
func TestParseTxLabel(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cases := []*testCase{
|
||||
{
|
||||
fee: btcutil.Amount(1027),
|
||||
action: WITHDRAW,
|
||||
isExpirySpend: false,
|
||||
},
|
||||
{
|
||||
fee: btcutil.Amount(42),
|
||||
action: DEPOSIT,
|
||||
isExpirySpend: false,
|
||||
},
|
||||
{
|
||||
fee: btcutil.Amount(42),
|
||||
action: RENEW,
|
||||
isExpirySpend: true,
|
||||
},
|
||||
}
|
||||
|
||||
runSubTests(t, cases, func(t *testing.T, h *testHarness, tc *testCase) {
|
||||
expiryHeight := uint32(bestHeight + maxAccountExpiry)
|
||||
account := h.openAccount(
|
||||
maxAccountValue, expiryHeight, bestHeight, tc.version,
|
||||
)
|
||||
acctKey := account.TraderKey.PubKey.SerializeCompressed()
|
||||
key := fmt.Sprintf("%x", acctKey)
|
||||
label := actionTxLabel(
|
||||
account, tc.action, tc.isExpirySpend, &tc.fee,
|
||||
btcutil.Amount(10000),
|
||||
)
|
||||
actual, err := ParseTxLabel(label)
|
||||
|
||||
expected := &TxLabel{
|
||||
AccountTxLabel{
|
||||
Key: key,
|
||||
Action: tc.action,
|
||||
ExpiryHeight: expiryHeight,
|
||||
OutputIndex: 0,
|
||||
IsExpirySpend: tc.isExpirySpend,
|
||||
TxFee: &tc.fee,
|
||||
BalanceDiff: btcutil.Amount(10000),
|
||||
},
|
||||
}
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, expected, actual)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ var accountsCommands = []cli.Command{
|
|||
withdrawAccountCommand,
|
||||
renewAccountCommand,
|
||||
closeAccountCommand,
|
||||
listAccountFeesCommand,
|
||||
bumpAccountFeeCommand,
|
||||
recoverAccountsCommand,
|
||||
},
|
||||
|
|
@ -819,6 +820,35 @@ func bumpAccountFee(ctx *cli.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
var listAccountFeesCommand = cli.Command{
|
||||
Name: "listfees",
|
||||
ShortName: "f",
|
||||
Usage: "list the account modification transaction fees",
|
||||
Description: `
|
||||
This command prints a map from account key to an ordered list of account
|
||||
modification transaction fees.
|
||||
`,
|
||||
Action: listAccountFees,
|
||||
}
|
||||
|
||||
func listAccountFees(ctx *cli.Context) error {
|
||||
client, cleanup, err := getClient(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
resp, err := client.AccountModificationFees(
|
||||
context.Background(), &poolrpc.AccountModificationFeesRequest{},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
printRespJSON(resp)
|
||||
return nil
|
||||
}
|
||||
|
||||
var recoverAccountsCommand = cli.Command{
|
||||
Name: "recover",
|
||||
Usage: "recover accounts after data loss with the help of the " +
|
||||
|
|
|
|||
|
|
@ -58,6 +58,10 @@ var RequiredPermissions = map[string][]bakery.Op{
|
|||
Entity: "account",
|
||||
Action: "write",
|
||||
}},
|
||||
"/poolrpc.Trader/AccountModificationFees": {{
|
||||
Entity: "account",
|
||||
Action: "read",
|
||||
}},
|
||||
"/poolrpc.Trader/SubmitOrder": {{
|
||||
Entity: "order",
|
||||
Action: "write",
|
||||
|
|
|
|||
1758
poolrpc/trader.pb.go
1758
poolrpc/trader.pb.go
File diff suppressed because it is too large
Load diff
|
|
@ -297,6 +297,31 @@ func RegisterTraderJSONCallbacks(registry map[string]func(ctx context.Context,
|
|||
callback(string(respBytes), nil)
|
||||
}
|
||||
|
||||
registry["poolrpc.Trader.AccountModificationFees"] = func(ctx context.Context,
|
||||
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
|
||||
|
||||
req := &AccountModificationFeesRequest{}
|
||||
err := marshaler.Unmarshal([]byte(reqJSON), req)
|
||||
if err != nil {
|
||||
callback("", err)
|
||||
return
|
||||
}
|
||||
|
||||
client := NewTraderClient(conn)
|
||||
resp, err := client.AccountModificationFees(ctx, req)
|
||||
if err != nil {
|
||||
callback("", err)
|
||||
return
|
||||
}
|
||||
|
||||
respBytes, err := marshaler.Marshal(resp)
|
||||
if err != nil {
|
||||
callback("", err)
|
||||
return
|
||||
}
|
||||
callback(string(respBytes), nil)
|
||||
}
|
||||
|
||||
registry["poolrpc.Trader.SubmitOrder"] = func(ctx context.Context,
|
||||
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
|
||||
|
||||
|
|
|
|||
|
|
@ -78,6 +78,13 @@ service Trader {
|
|||
rpc RecoverAccounts (RecoverAccountsRequest)
|
||||
returns (RecoverAccountsResponse);
|
||||
|
||||
/* pool: `accounts listfees`
|
||||
AccountModificationFees returns a map from account key to an ordered list of
|
||||
account action modification fees.
|
||||
*/
|
||||
rpc AccountModificationFees (AccountModificationFeesRequest)
|
||||
returns (AccountModificationFeesResponse);
|
||||
|
||||
/* pool: `orders submit`
|
||||
SubmitOrder creates a new ask or bid order and submits for the given account
|
||||
and submits it to the auction server for matching.
|
||||
|
|
@ -1011,6 +1018,45 @@ message RecoverAccountsResponse {
|
|||
uint32 num_recovered_accounts = 1;
|
||||
}
|
||||
|
||||
message AccountModificationFeesRequest {
|
||||
}
|
||||
|
||||
message AccountModificationFee {
|
||||
// Modification action type.
|
||||
string action = 1;
|
||||
|
||||
// Transaction ID.
|
||||
string txid = 2;
|
||||
|
||||
// Action transaction block height.
|
||||
int32 block_height = 3;
|
||||
|
||||
// Action transaction timestamp.
|
||||
int64 timestamp = 4;
|
||||
|
||||
// Action transaction output amount.
|
||||
int64 output_amount = 5;
|
||||
|
||||
// Action transaction fee.
|
||||
oneof fee {
|
||||
// A flag which is true if fee value has not been set, and is otherwise
|
||||
// false.
|
||||
bool fee_null = 6;
|
||||
|
||||
// Action transaction fee value.
|
||||
int64 fee_value = 7;
|
||||
}
|
||||
}
|
||||
|
||||
message ListOfAccountModificationFees {
|
||||
repeated AccountModificationFee modification_fees = 1;
|
||||
}
|
||||
|
||||
message AccountModificationFeesResponse {
|
||||
// A map from account key to an ordered list of account modification fees.
|
||||
map<string, ListOfAccountModificationFees> accounts = 1;
|
||||
}
|
||||
|
||||
message AuctionFeeRequest {
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1018,6 +1018,55 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"poolrpcAccountModificationFee": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"action": {
|
||||
"type": "string",
|
||||
"description": "Modification action type."
|
||||
},
|
||||
"txid": {
|
||||
"type": "string",
|
||||
"description": "Transaction ID."
|
||||
},
|
||||
"block_height": {
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"description": "Action transaction block height."
|
||||
},
|
||||
"timestamp": {
|
||||
"type": "string",
|
||||
"format": "int64",
|
||||
"description": "Action transaction timestamp."
|
||||
},
|
||||
"output_amount": {
|
||||
"type": "string",
|
||||
"format": "int64",
|
||||
"description": "Action transaction output amount."
|
||||
},
|
||||
"fee_null": {
|
||||
"type": "boolean",
|
||||
"description": "A flag which is true if fee value has not been set, and is otherwise\nfalse."
|
||||
},
|
||||
"fee_value": {
|
||||
"type": "string",
|
||||
"format": "int64",
|
||||
"description": "Action transaction fee value."
|
||||
}
|
||||
}
|
||||
},
|
||||
"poolrpcAccountModificationFeesResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"accounts": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"$ref": "#/definitions/poolrpcListOfAccountModificationFees"
|
||||
},
|
||||
"description": "A map from account key to an ordered list of account modification fees."
|
||||
}
|
||||
}
|
||||
},
|
||||
"poolrpcAccountState": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
|
|
@ -1737,6 +1786,17 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"poolrpcListOfAccountModificationFees": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"modification_fees": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/poolrpcAccountModificationFee"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"poolrpcListOrdersResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
|||
|
|
@ -66,6 +66,10 @@ type TraderClient interface {
|
|||
//RecoverAccounts queries the auction server for this trader daemon's accounts
|
||||
//in case we lost our local account database.
|
||||
RecoverAccounts(ctx context.Context, in *RecoverAccountsRequest, opts ...grpc.CallOption) (*RecoverAccountsResponse, error)
|
||||
// pool: `accounts listfees`
|
||||
//AccountModificationFees returns a map from account key to an ordered list of
|
||||
//account action modification fees.
|
||||
AccountModificationFees(ctx context.Context, in *AccountModificationFeesRequest, opts ...grpc.CallOption) (*AccountModificationFeesResponse, error)
|
||||
// pool: `orders submit`
|
||||
//SubmitOrder creates a new ask or bid order and submits for the given account
|
||||
//and submits it to the auction server for matching.
|
||||
|
|
@ -266,6 +270,15 @@ func (c *traderClient) RecoverAccounts(ctx context.Context, in *RecoverAccountsR
|
|||
return out, nil
|
||||
}
|
||||
|
||||
func (c *traderClient) AccountModificationFees(ctx context.Context, in *AccountModificationFeesRequest, opts ...grpc.CallOption) (*AccountModificationFeesResponse, error) {
|
||||
out := new(AccountModificationFeesResponse)
|
||||
err := c.cc.Invoke(ctx, "/poolrpc.Trader/AccountModificationFees", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *traderClient) SubmitOrder(ctx context.Context, in *SubmitOrderRequest, opts ...grpc.CallOption) (*SubmitOrderResponse, error) {
|
||||
out := new(SubmitOrderResponse)
|
||||
err := c.cc.Invoke(ctx, "/poolrpc.Trader/SubmitOrder", in, out, opts...)
|
||||
|
|
@ -479,6 +492,10 @@ type TraderServer interface {
|
|||
//RecoverAccounts queries the auction server for this trader daemon's accounts
|
||||
//in case we lost our local account database.
|
||||
RecoverAccounts(context.Context, *RecoverAccountsRequest) (*RecoverAccountsResponse, error)
|
||||
// pool: `accounts listfees`
|
||||
//AccountModificationFees returns a map from account key to an ordered list of
|
||||
//account action modification fees.
|
||||
AccountModificationFees(context.Context, *AccountModificationFeesRequest) (*AccountModificationFeesResponse, error)
|
||||
// pool: `orders submit`
|
||||
//SubmitOrder creates a new ask or bid order and submits for the given account
|
||||
//and submits it to the auction server for matching.
|
||||
|
|
@ -610,6 +627,9 @@ func (UnimplementedTraderServer) BumpAccountFee(context.Context, *BumpAccountFee
|
|||
func (UnimplementedTraderServer) RecoverAccounts(context.Context, *RecoverAccountsRequest) (*RecoverAccountsResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method RecoverAccounts not implemented")
|
||||
}
|
||||
func (UnimplementedTraderServer) AccountModificationFees(context.Context, *AccountModificationFeesRequest) (*AccountModificationFeesResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method AccountModificationFees not implemented")
|
||||
}
|
||||
func (UnimplementedTraderServer) SubmitOrder(context.Context, *SubmitOrderRequest) (*SubmitOrderResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method SubmitOrder not implemented")
|
||||
}
|
||||
|
|
@ -875,6 +895,24 @@ func _Trader_RecoverAccounts_Handler(srv interface{}, ctx context.Context, dec f
|
|||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Trader_AccountModificationFees_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(AccountModificationFeesRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(TraderServer).AccountModificationFees(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/poolrpc.Trader/AccountModificationFees",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(TraderServer).AccountModificationFees(ctx, req.(*AccountModificationFeesRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Trader_SubmitOrder_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(SubmitOrderRequest)
|
||||
if err := dec(in); err != nil {
|
||||
|
|
@ -1250,6 +1288,10 @@ var Trader_ServiceDesc = grpc.ServiceDesc{
|
|||
MethodName: "RecoverAccounts",
|
||||
Handler: _Trader_RecoverAccounts_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "AccountModificationFees",
|
||||
Handler: _Trader_AccountModificationFees_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "SubmitOrder",
|
||||
Handler: _Trader_SubmitOrder_Handler,
|
||||
|
|
|
|||
74
rpcserver.go
74
rpcserver.go
|
|
@ -3030,6 +3030,80 @@ func (s *rpcServer) determineAccountVersion(
|
|||
return account.VersionTaprootEnabled, nil
|
||||
}
|
||||
|
||||
// AccountModificationFees returns a map from account key to an ordered list of
|
||||
// account modifying action fees.
|
||||
func (s *rpcServer) AccountModificationFees(ctx context.Context,
|
||||
req *poolrpc.AccountModificationFeesRequest) (
|
||||
*poolrpc.AccountModificationFeesResponse, error) {
|
||||
|
||||
// Retrieve all lightning wallet transactions.
|
||||
transactionDetails, err := s.lndClient.GetTransactions(
|
||||
ctx, &lnrpc.GetTransactionsRequest{},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error retrieving transactions: %v", err)
|
||||
}
|
||||
|
||||
// A map from account key to an ordered list of account action fees.
|
||||
accountActions := make(map[string][]*poolrpc.AccountModificationFee)
|
||||
|
||||
for _, tx := range transactionDetails.Transactions {
|
||||
// Skip transactions which are not related to pool.
|
||||
if !account.IsPoolTx(tx) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Parse account data from transaction label.
|
||||
labelData, err := account.ParseTxLabel(tx.Label)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse transaction "+
|
||||
"label: %v (error: %v)", tx.Label, err)
|
||||
}
|
||||
|
||||
// Ignore transaction if account modification not found.
|
||||
if labelData == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Select the account specific transaction output. Each account
|
||||
// action transaction should include an account specific output.
|
||||
output := tx.OutputDetails[labelData.Account.OutputIndex]
|
||||
|
||||
// Construct account modification fee structure.
|
||||
acctModFee := &poolrpc.AccountModificationFee{
|
||||
Action: string(labelData.Account.Action),
|
||||
Txid: tx.TxHash,
|
||||
BlockHeight: tx.BlockHeight,
|
||||
Timestamp: tx.TimeStamp,
|
||||
OutputAmount: output.Amount,
|
||||
}
|
||||
acctModFee.Fee = &poolrpc.AccountModificationFee_FeeNull{
|
||||
FeeNull: true,
|
||||
}
|
||||
if labelData.Account.TxFee != nil {
|
||||
acctModFee.Fee = &poolrpc.AccountModificationFee_FeeValue{
|
||||
FeeValue: int64(*labelData.Account.TxFee),
|
||||
}
|
||||
}
|
||||
|
||||
// Handle all non-create account actions.
|
||||
accountActions[labelData.Account.Key] = append(
|
||||
accountActions[labelData.Account.Key], acctModFee,
|
||||
)
|
||||
}
|
||||
|
||||
result := make(map[string]*poolrpc.ListOfAccountModificationFees)
|
||||
for traderKey, modificationFees := range accountActions {
|
||||
result[traderKey] = &poolrpc.ListOfAccountModificationFees{
|
||||
ModificationFees: modificationFees,
|
||||
}
|
||||
}
|
||||
|
||||
return &poolrpc.AccountModificationFeesResponse{
|
||||
Accounts: result,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// rpcOrderStateToDBState maps the order state as received over the RPC
|
||||
// protocol to the local state that we use in the database.
|
||||
func rpcOrderStateToDBState(state auctioneerrpc.OrderState) (order.State,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue