Merge pull request #5476 from LN-Zap/upstream/feat/dest-outputs

lnrpc: Add destination output information (pkScript, output index, amount and if output belongs to the node)
This commit is contained in:
Oliver Gugger 2022-03-23 12:51:04 +01:00 committed by GitHub
commit 802544b62a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 4481 additions and 4000 deletions

View File

@ -122,6 +122,10 @@
the HTLC interceptor API. This enables interception applications where every the HTLC interceptor API. This enables interception applications where every
packet must be intercepted. packet must be intercepted.
* Add [destination output information](https://github.com/lightningnetwork/lnd/pull/5476)
to the transaction structure returned from the RPC `GetTransactions` and when
subscribed with `SubscribeTransactions`.
## Database ## Database
* [Add ForAll implementation for etcd to speed up * [Add ForAll implementation for etcd to speed up
@ -182,6 +186,7 @@ gRPC performance metrics (latency to process `GetInfo`, etc)](https://github.com
* Andreas Schjønhaug * Andreas Schjønhaug
* asvdf * asvdf
* bitromortac * bitromortac
* Bjarne Magnussen
* BTCparadigm * BTCparadigm
* Carla Kirk-Cohen * Carla Kirk-Cohen
* Carsten Otto * Carsten Otto

File diff suppressed because it is too large Load Diff

View File

@ -620,6 +620,38 @@ message Utxo {
int64 confirmations = 6; int64 confirmations = 6;
} }
enum OutputScriptType {
SCRIPT_TYPE_PUBKEY_HASH = 0;
SCRIPT_TYPE_SCRIPT_HASH = 1;
SCRIPT_TYPE_WITNESS_V0_PUBKEY_HASH = 2;
SCRIPT_TYPE_WITNESS_V0_SCRIPT_HASH = 3;
SCRIPT_TYPE_PUBKEY = 4;
SCRIPT_TYPE_MULTISIG = 5;
SCRIPT_TYPE_NULLDATA = 6;
SCRIPT_TYPE_NON_STANDARD = 7;
SCRIPT_TYPE_WITNESS_UNKNOWN = 8;
}
message OutputDetail {
// The type of the output
OutputScriptType output_type = 1;
// The address
string address = 2;
// The pkscript in hex
string pk_script = 3;
// The output index used in the raw transaction
int64 output_index = 4;
// The value of the output coin in satoshis
int64 amount = 5;
// Denotes if the output is controlled by the internal wallet
bool is_our_address = 6;
}
message Transaction { message Transaction {
// The transaction hash // The transaction hash
string tx_hash = 1; string tx_hash = 1;
@ -642,8 +674,12 @@ message Transaction {
// Fees paid for this transaction // Fees paid for this transaction
int64 total_fees = 7; int64 total_fees = 7;
// Addresses that received funds for this transaction // Addresses that received funds for this transaction. Deprecated as it is
repeated string dest_addresses = 8; // now incorporated in the output_details field.
repeated string dest_addresses = 8 [deprecated = true];
// Outputs that received funds for this transaction
repeated OutputDetail output_details = 11;
// The raw transaction hex. // The raw transaction hex.
string raw_tx_hex = 9; string raw_tx_hex = 9;

View File

@ -5430,6 +5430,52 @@
} }
} }
}, },
"lnrpcOutputDetail": {
"type": "object",
"properties": {
"output_type": {
"$ref": "#/definitions/lnrpcOutputScriptType",
"title": "The type of the output"
},
"address": {
"type": "string",
"title": "The address"
},
"pk_script": {
"type": "string",
"title": "The pkscript in hex"
},
"output_index": {
"type": "string",
"format": "int64",
"title": "The output index used in the raw transaction"
},
"amount": {
"type": "string",
"format": "int64",
"title": "The value of the output coin in satoshis"
},
"is_our_address": {
"type": "boolean",
"title": "Denotes if the output is controlled by the internal wallet"
}
}
},
"lnrpcOutputScriptType": {
"type": "string",
"enum": [
"SCRIPT_TYPE_PUBKEY_HASH",
"SCRIPT_TYPE_SCRIPT_HASH",
"SCRIPT_TYPE_WITNESS_V0_PUBKEY_HASH",
"SCRIPT_TYPE_WITNESS_V0_SCRIPT_HASH",
"SCRIPT_TYPE_PUBKEY",
"SCRIPT_TYPE_MULTISIG",
"SCRIPT_TYPE_NULLDATA",
"SCRIPT_TYPE_NON_STANDARD",
"SCRIPT_TYPE_WITNESS_UNKNOWN"
],
"default": "SCRIPT_TYPE_PUBKEY_HASH"
},
"lnrpcPayReq": { "lnrpcPayReq": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -6411,7 +6457,14 @@
"items": { "items": {
"type": "string" "type": "string"
}, },
"title": "Addresses that received funds for this transaction" "description": "Addresses that received funds for this transaction. Deprecated as it is\nnow incorporated in the output_details field."
},
"output_details": {
"type": "array",
"items": {
"$ref": "#/definitions/lnrpcOutputDetail"
},
"title": "Outputs that received funds for this transaction"
}, },
"raw_tx_hex": { "raw_tx_hex": {
"type": "string", "type": "string",

View File

@ -141,3 +141,31 @@ func MarshalUtxos(utxos []*lnwallet.Utxo, activeNetParams *chaincfg.Params) (
return res, nil return res, nil
} }
// MarshallOutputType translates a txscript.ScriptClass into a
// lnrpc.OutputScriptType.
func MarshallOutputType(o txscript.ScriptClass) (ret OutputScriptType) {
// Translate txscript ScriptClass type to the proper gRPC proto
// output script type.
switch o {
case txscript.PubKeyHashTy:
ret = OutputScriptType_SCRIPT_TYPE_PUBKEY_HASH
case txscript.ScriptHashTy:
ret = OutputScriptType_SCRIPT_TYPE_SCRIPT_HASH
case txscript.WitnessV0PubKeyHashTy:
ret = OutputScriptType_SCRIPT_TYPE_WITNESS_V0_PUBKEY_HASH
case txscript.WitnessV0ScriptHashTy:
ret = OutputScriptType_SCRIPT_TYPE_WITNESS_V0_SCRIPT_HASH
case txscript.PubKeyTy:
ret = OutputScriptType_SCRIPT_TYPE_PUBKEY
case txscript.MultiSigTy:
ret = OutputScriptType_SCRIPT_TYPE_MULTISIG
case txscript.NullDataTy:
ret = OutputScriptType_SCRIPT_TYPE_NULLDATA
case txscript.NonStandardTy:
ret = OutputScriptType_SCRIPT_TYPE_NON_STANDARD
case txscript.WitnessUnknownTy:
ret = OutputScriptType_SCRIPT_TYPE_WITNESS_UNKNOWN
}
return
}

View File

@ -19,8 +19,30 @@ const (
// RPCTransaction returns a rpc transaction. // RPCTransaction returns a rpc transaction.
func RPCTransaction(tx *lnwallet.TransactionDetail) *Transaction { func RPCTransaction(tx *lnwallet.TransactionDetail) *Transaction {
var destAddresses []string var destAddresses []string
for _, destAddress := range tx.DestAddresses { // Re-package destination output information.
destAddresses = append(destAddresses, destAddress.EncodeAddress()) var outputDetails []*OutputDetail
for _, o := range tx.OutputDetails {
// Note: DestAddresses is deprecated but we keep
// populating it with addresses for backwards
// compatibility.
for _, a := range o.Addresses {
destAddresses = append(destAddresses,
a.EncodeAddress())
}
var address string
if len(o.Addresses) == 1 {
address = o.Addresses[0].EncodeAddress()
}
outputDetails = append(outputDetails, &OutputDetail{
OutputType: MarshallOutputType(o.OutputType),
Address: address,
PkScript: hex.EncodeToString(o.PkScript),
OutputIndex: int64(o.OutputIndex),
Amount: int64(o.Value),
IsOurAddress: o.IsOurAddress,
})
} }
// We also get unconfirmed transactions, so BlockHash can be nil. // We also get unconfirmed transactions, so BlockHash can be nil.
@ -38,6 +60,7 @@ func RPCTransaction(tx *lnwallet.TransactionDetail) *Transaction {
TimeStamp: tx.Timestamp, TimeStamp: tx.Timestamp,
TotalFees: tx.TotalFees, TotalFees: tx.TotalFees,
DestAddresses: destAddresses, DestAddresses: destAddresses,
OutputDetails: outputDetails,
RawTxHex: hex.EncodeToString(tx.RawTx), RawTxHex: hex.EncodeToString(tx.RawTx),
Label: tx.Label, Label: tx.Label,
} }

View File

@ -720,6 +720,52 @@
} }
} }
}, },
"lnrpcOutputDetail": {
"type": "object",
"properties": {
"output_type": {
"$ref": "#/definitions/lnrpcOutputScriptType",
"title": "The type of the output"
},
"address": {
"type": "string",
"title": "The address"
},
"pk_script": {
"type": "string",
"title": "The pkscript in hex"
},
"output_index": {
"type": "string",
"format": "int64",
"title": "The output index used in the raw transaction"
},
"amount": {
"type": "string",
"format": "int64",
"title": "The value of the output coin in satoshis"
},
"is_our_address": {
"type": "boolean",
"title": "Denotes if the output is controlled by the internal wallet"
}
}
},
"lnrpcOutputScriptType": {
"type": "string",
"enum": [
"SCRIPT_TYPE_PUBKEY_HASH",
"SCRIPT_TYPE_SCRIPT_HASH",
"SCRIPT_TYPE_WITNESS_V0_PUBKEY_HASH",
"SCRIPT_TYPE_WITNESS_V0_SCRIPT_HASH",
"SCRIPT_TYPE_PUBKEY",
"SCRIPT_TYPE_MULTISIG",
"SCRIPT_TYPE_NULLDATA",
"SCRIPT_TYPE_NON_STANDARD",
"SCRIPT_TYPE_WITNESS_UNKNOWN"
],
"default": "SCRIPT_TYPE_PUBKEY_HASH"
},
"lnrpcTransaction": { "lnrpcTransaction": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -761,7 +807,14 @@
"items": { "items": {
"type": "string" "type": "string"
}, },
"title": "Addresses that received funds for this transaction" "description": "Addresses that received funds for this transaction. Deprecated as it is\nnow incorporated in the output_details field."
},
"output_details": {
"type": "array",
"items": {
"$ref": "#/definitions/lnrpcOutputDetail"
},
"title": "Outputs that received funds for this transaction"
}, },
"raw_tx_hex": { "raw_tx_hex": {
"type": "string", "type": "string",

View File

@ -1013,18 +1013,35 @@ func minedTransactionsToDetails(
return nil, err return nil, err
} }
var destAddresses []btcutil.Address // isOurAddress is a map containing the output indices
for _, txOut := range wireTx.TxOut { // controlled by the wallet.
_, outAddresses, _, err := txscript.ExtractPkScriptAddrs( // Note: We make use of the information in `MyOutputs` provided
// by the `wallet.TransactionSummary` structure that holds
// information only if the output is controlled by the wallet.
isOurAddress := make(map[int]bool, len(tx.MyOutputs))
for _, o := range tx.MyOutputs {
isOurAddress[int(o.Index)] = true
}
var outputDetails []lnwallet.OutputDetail
for i, txOut := range wireTx.TxOut {
var addresses []btcutil.Address
sc, outAddresses, _, err := txscript.ExtractPkScriptAddrs(
txOut.PkScript, chainParams, txOut.PkScript, chainParams,
) )
if err != nil { if err == nil {
// Skip any unsupported addresses to prevent // Add supported addresses.
// other transactions from not being returned. addresses = outAddresses
continue
} }
destAddresses = append(destAddresses, outAddresses...) outputDetails = append(outputDetails, lnwallet.OutputDetail{
OutputType: sc,
Addresses: addresses,
PkScript: txOut.PkScript,
OutputIndex: i,
Value: btcutil.Amount(txOut.Value),
IsOurAddress: isOurAddress[i],
})
} }
txDetail := &lnwallet.TransactionDetail{ txDetail := &lnwallet.TransactionDetail{
@ -1034,7 +1051,7 @@ func minedTransactionsToDetails(
BlockHeight: block.Height, BlockHeight: block.Height,
Timestamp: block.Timestamp, Timestamp: block.Timestamp,
TotalFees: int64(tx.Fee), TotalFees: int64(tx.Fee),
DestAddresses: destAddresses, OutputDetails: outputDetails,
RawTx: tx.Transaction, RawTx: tx.Transaction,
Label: tx.Label, Label: tx.Label,
} }
@ -1065,24 +1082,42 @@ func unminedTransactionsToDetail(
return nil, err return nil, err
} }
var destAddresses []btcutil.Address // isOurAddress is a map containing the output indices controlled by
for _, txOut := range wireTx.TxOut { // the wallet.
_, outAddresses, _, err := // Note: We make use of the information in `MyOutputs` provided
txscript.ExtractPkScriptAddrs(txOut.PkScript, chainParams) // by the `wallet.TransactionSummary` structure that holds information
if err != nil { // only if the output is controlled by the wallet.
// Skip any unsupported addresses to prevent other isOurAddress := make(map[int]bool, len(summary.MyOutputs))
// transactions from not being returned. for _, o := range summary.MyOutputs {
continue isOurAddress[int(o.Index)] = true
}
var outputDetails []lnwallet.OutputDetail
for i, txOut := range wireTx.TxOut {
var addresses []btcutil.Address
sc, outAddresses, _, err := txscript.ExtractPkScriptAddrs(
txOut.PkScript, chainParams,
)
if err == nil {
// Add supported addresses.
addresses = outAddresses
} }
destAddresses = append(destAddresses, outAddresses...) outputDetails = append(outputDetails, lnwallet.OutputDetail{
OutputType: sc,
Addresses: addresses,
PkScript: txOut.PkScript,
OutputIndex: i,
Value: btcutil.Amount(txOut.Value),
IsOurAddress: isOurAddress[i],
})
} }
txDetail := &lnwallet.TransactionDetail{ txDetail := &lnwallet.TransactionDetail{
Hash: *summary.Hash, Hash: *summary.Hash,
TotalFees: int64(summary.Fee), TotalFees: int64(summary.Fee),
Timestamp: summary.Timestamp, Timestamp: summary.Timestamp,
DestAddresses: destAddresses, OutputDetails: outputDetails,
RawTx: summary.Transaction, RawTx: summary.Transaction,
Label: summary.Label, Label: summary.Label,
} }

View File

@ -12,6 +12,7 @@ import (
"github.com/btcsuite/btcd/btcutil/hdkeychain" "github.com/btcsuite/btcd/btcutil/hdkeychain"
"github.com/btcsuite/btcd/btcutil/psbt" "github.com/btcsuite/btcd/btcutil/psbt"
"github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/waddrmgr"
"github.com/btcsuite/btcwallet/wallet/txauthor" "github.com/btcsuite/btcwallet/wallet/txauthor"
@ -83,6 +84,16 @@ type Utxo struct {
PrevTx *wire.MsgTx PrevTx *wire.MsgTx
} }
// OutputDetail contains additional information on a destination address.
type OutputDetail struct {
OutputType txscript.ScriptClass
Addresses []btcutil.Address
PkScript []byte
OutputIndex int
Value btcutil.Amount
IsOurAddress bool
}
// TransactionDetail describes a transaction with either inputs which belong to // TransactionDetail describes a transaction with either inputs which belong to
// the wallet, or has outputs that pay to the wallet. // the wallet, or has outputs that pay to the wallet.
type TransactionDetail struct { type TransactionDetail struct {
@ -117,8 +128,9 @@ type TransactionDetail struct {
// TotalFees is the total fee in satoshis paid by this transaction. // TotalFees is the total fee in satoshis paid by this transaction.
TotalFees int64 TotalFees int64
// DestAddresses are the destinations for a transaction // OutputDetails contains output data for each destination address, such
DestAddresses []btcutil.Address // as the output script and amount.
OutputDetails []OutputDetail
// RawTx returns the raw serialized transaction. // RawTx returns the raw serialized transaction.
RawTx []byte RawTx []byte

View File

@ -1140,6 +1140,7 @@ func testListTransactionDetails(miner *rpctest.Harness,
// Create 5 new outputs spendable by the wallet. // Create 5 new outputs spendable by the wallet.
const numTxns = 5 const numTxns = 5
const outputAmt = btcutil.SatoshiPerBitcoin const outputAmt = btcutil.SatoshiPerBitcoin
isOurAddress := make(map[string]bool)
txids := make(map[chainhash.Hash]struct{}) txids := make(map[chainhash.Hash]struct{})
for i := 0; i < numTxns; i++ { for i := 0; i < numTxns; i++ {
addr, err := alice.NewAddress( addr, err := alice.NewAddress(
@ -1149,6 +1150,7 @@ func testListTransactionDetails(miner *rpctest.Harness,
if err != nil { if err != nil {
t.Fatalf("unable to create new address: %v", err) t.Fatalf("unable to create new address: %v", err)
} }
isOurAddress[addr.EncodeAddress()] = true
script, err := txscript.PayToAddrScript(addr) script, err := txscript.PayToAddrScript(addr)
if err != nil { if err != nil {
t.Fatalf("unable to create output script: %v", err) t.Fatalf("unable to create output script: %v", err)
@ -1245,20 +1247,27 @@ func testListTransactionDetails(miner *rpctest.Harness,
t.Fatalf("tx (%v) not found in block (%v)", t.Fatalf("tx (%v) not found in block (%v)",
txDetail.Hash, txDetail.BlockHash) txDetail.Hash, txDetail.BlockHash)
} else { } else {
var destinationAddresses []btcutil.Address var destinationOutputs []lnwallet.OutputDetail
for _, txOut := range txOuts { for i, txOut := range txOuts {
_, addrs, _, err := sc, addrs, _, err :=
txscript.ExtractPkScriptAddrs(txOut.PkScript, &alice.Cfg.NetParams) txscript.ExtractPkScriptAddrs(txOut.PkScript, &alice.Cfg.NetParams)
if err != nil { if err != nil {
t.Fatalf("err extract script addresses: %s", err) t.Fatalf("err extract script addresses: %s", err)
} }
destinationAddresses = append(destinationAddresses, addrs...) destinationOutputs = append(destinationOutputs, lnwallet.OutputDetail{
OutputType: sc,
Addresses: addrs,
PkScript: txOut.PkScript,
OutputIndex: i,
Value: btcutil.Amount(txOut.Value),
IsOurAddress: isOurAddress[addrs[0].EncodeAddress()],
})
} }
if !reflect.DeepEqual(txDetail.DestAddresses, destinationAddresses) { if !reflect.DeepEqual(txDetail.OutputDetails, destinationOutputs) {
t.Fatalf("destination addresses mismatch, got %v expected %v", t.Fatalf("destination outputs mismatch, got %v expected %v",
txDetail.DestAddresses, destinationAddresses) txDetail.OutputDetails, destinationOutputs)
} }
} }
@ -1330,10 +1339,12 @@ func testListTransactionDetails(miner *rpctest.Harness,
// that even when we have 0 confirmation transactions, the destination // that even when we have 0 confirmation transactions, the destination
// addresses are returned. // addresses are returned.
var match bool var match bool
for _, addr := range txDetail.DestAddresses { for _, o := range txDetail.OutputDetails {
if addr.String() == minerAddr.String() { for _, addr := range o.Addresses {
match = true if addr.String() == minerAddr.String() {
break match = true
break
}
} }
} }
if !match { if !match {