Merge pull request #7464 from guggero/script-builder-capacity

input: reduce memory allocation for ScriptBuilder
This commit is contained in:
Olaoluwa Osuntokun 2023-04-11 15:07:47 -07:00 committed by GitHub
commit 158e93f114
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 270 additions and 43 deletions

View File

@ -22,6 +22,7 @@ import (
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/aliasmgr"
"github.com/lightningnetwork/lnd/batch"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
@ -3731,35 +3732,17 @@ func (c *ChannelGraph) IsPublicNode(pubKey [33]byte) (bool, error) {
// genMultiSigP2WSH generates the p2wsh'd multisig script for 2 of 2 pubkeys.
func genMultiSigP2WSH(aPub, bPub []byte) ([]byte, error) {
if len(aPub) != 33 || len(bPub) != 33 {
return nil, fmt.Errorf("pubkey size error. Compressed " +
"pubkeys only")
}
// Swap to sort pubkeys if needed. Keys are sorted in lexicographical
// order. The signatures within the scriptSig must also adhere to the
// order, ensuring that the signatures for each public key appears in
// the proper order on the stack.
if bytes.Compare(aPub, bPub) == 1 {
aPub, bPub = bPub, aPub
}
// First, we'll generate the witness script for the multi-sig.
bldr := txscript.NewScriptBuilder()
bldr.AddOp(txscript.OP_2)
bldr.AddData(aPub) // Add both pubkeys (sorted).
bldr.AddData(bPub)
bldr.AddOp(txscript.OP_2)
bldr.AddOp(txscript.OP_CHECKMULTISIG)
witnessScript, err := bldr.Script()
witnessScript, err := input.GenMultiSigScript(aPub, bPub)
if err != nil {
return nil, err
}
// With the witness script generated, we'll now turn it into a p2sh
// With the witness script generated, we'll now turn it into a p2wsh
// script:
// * OP_0 <sha256(script)>
bldr = txscript.NewScriptBuilder()
bldr := txscript.NewScriptBuilder(
txscript.WithScriptAllocSize(input.P2WSHSize),
)
bldr.AddOp(txscript.OP_0)
scriptHash := sha256.Sum256(witnessScript)
bldr.AddData(scriptHash[:])

View File

@ -46,7 +46,10 @@ https://github.com/lightningnetwork/lnd/pull/7359)
* [Return `FEE_INSUFFICIENT` before checking balance for incoming low-fee
HTLCs.](https://github.com/lightningnetwork/lnd/pull/7490).
* Optimize script allocation size in order to save
[memory](https://github.com/lightningnetwork/lnd/pull/7464).
## Spec
* [Add test vectors for

2
go.mod
View File

@ -3,7 +3,7 @@ module github.com/lightningnetwork/lnd
require (
github.com/NebulousLabs/go-upnp v0.0.0-20180202185039-29b680b06c82
github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344
github.com/btcsuite/btcd v0.23.5-0.20230125025938-be056b0a0b2f
github.com/btcsuite/btcd v0.23.5-0.20230228185050-38331963bddd
github.com/btcsuite/btcd/btcec/v2 v2.3.2
github.com/btcsuite/btcd/btcutil v1.1.3
github.com/btcsuite/btcd/btcutil/psbt v1.1.8

4
go.sum
View File

@ -68,8 +68,8 @@ github.com/btcsuite/btcd v0.22.0-beta.0.20220207191057-4dc4ff7963b4/go.mod h1:7a
github.com/btcsuite/btcd v0.23.0/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY=
github.com/btcsuite/btcd v0.23.1/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY=
github.com/btcsuite/btcd v0.23.3/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY=
github.com/btcsuite/btcd v0.23.5-0.20230125025938-be056b0a0b2f h1:UJ/S/pV25+YsK0CJRJh8RDpTgy5h1oXWjOd4fp+opvY=
github.com/btcsuite/btcd v0.23.5-0.20230125025938-be056b0a0b2f/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY=
github.com/btcsuite/btcd v0.23.5-0.20230228185050-38331963bddd h1:v2Bs8yLhwv+8XYU96OOnhZ5BEj8ZlNGownexUO0e80I=
github.com/btcsuite/btcd v0.23.5-0.20230228185050-38331963bddd/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY=
github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA=
github.com/btcsuite/btcd/btcec/v2 v2.1.1/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE=
github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE=

View File

@ -35,7 +35,9 @@ type Signature interface {
// WitnessScriptHash generates a pay-to-witness-script-hash public key script
// paying to a version 0 witness program paying to the passed redeem script.
func WitnessScriptHash(witnessScript []byte) ([]byte, error) {
bldr := txscript.NewScriptBuilder()
bldr := txscript.NewScriptBuilder(
txscript.WithScriptAllocSize(P2WSHSize),
)
bldr.AddOp(txscript.OP_0)
scriptHash := sha256.Sum256(witnessScript)
@ -47,7 +49,9 @@ func WitnessScriptHash(witnessScript []byte) ([]byte, error) {
// paying to a version 0 witness program containing the passed serialized
// public key.
func WitnessPubKeyHash(pubkey []byte) ([]byte, error) {
bldr := txscript.NewScriptBuilder()
bldr := txscript.NewScriptBuilder(
txscript.WithScriptAllocSize(P2WPKHSize),
)
bldr.AddOp(txscript.OP_0)
pkhash := btcutil.Hash160(pubkey)
@ -58,7 +62,9 @@ func WitnessPubKeyHash(pubkey []byte) ([]byte, error) {
// GenerateP2SH generates a pay-to-script-hash public key script paying to the
// passed redeem script.
func GenerateP2SH(script []byte) ([]byte, error) {
bldr := txscript.NewScriptBuilder()
bldr := txscript.NewScriptBuilder(
txscript.WithScriptAllocSize(NestedP2WPKHSize),
)
bldr.AddOp(txscript.OP_HASH160)
scripthash := btcutil.Hash160(script)
@ -70,7 +76,9 @@ func GenerateP2SH(script []byte) ([]byte, error) {
// GenerateP2PKH generates a pay-to-public-key-hash public key script paying to
// the passed serialized public key.
func GenerateP2PKH(pubkey []byte) ([]byte, error) {
bldr := txscript.NewScriptBuilder()
bldr := txscript.NewScriptBuilder(
txscript.WithScriptAllocSize(P2PKHSize),
)
bldr.AddOp(txscript.OP_DUP)
bldr.AddOp(txscript.OP_HASH160)
@ -96,7 +104,8 @@ func GenerateUnknownWitness() ([]byte, error) {
// pubkeys.
func GenMultiSigScript(aPub, bPub []byte) ([]byte, error) {
if len(aPub) != 33 || len(bPub) != 33 {
return nil, fmt.Errorf("pubkey size error: compressed pubkeys only")
return nil, fmt.Errorf("pubkey size error: compressed " +
"pubkeys only")
}
// Swap to sort pubkeys if needed. Keys are sorted in lexicographical
@ -107,7 +116,9 @@ func GenMultiSigScript(aPub, bPub []byte) ([]byte, error) {
aPub, bPub = bPub, aPub
}
bldr := txscript.NewScriptBuilder()
bldr := txscript.NewScriptBuilder(txscript.WithScriptAllocSize(
MultiSigSize,
))
bldr.AddOp(txscript.OP_2)
bldr.AddData(aPub) // Add both pubkeys (sorted).
bldr.AddData(bPub)
@ -241,7 +252,9 @@ func SenderHTLCScript(senderHtlcKey, receiverHtlcKey,
revocationKey *btcec.PublicKey, paymentHash []byte,
confirmedSpend bool) ([]byte, error) {
builder := txscript.NewScriptBuilder()
builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize(
OfferedHtlcScriptSizeConfirmed,
))
// The opening operations are used to determine if this is the receiver
// of the HTLC attempting to sweep all the funds due to a contract
@ -484,7 +497,9 @@ func ReceiverHTLCScript(cltvExpiry uint32, senderHtlcKey,
receiverHtlcKey, revocationKey *btcec.PublicKey,
paymentHash []byte, confirmedSpend bool) ([]byte, error) {
builder := txscript.NewScriptBuilder()
builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize(
AcceptedHtlcScriptSizeConfirmed,
))
// The opening operations are used to determine if this is the sender
// of the HTLC attempting to sweep all the funds due to a contract
@ -747,7 +762,9 @@ func ReceiverHtlcSpendTimeout(signer Signer, signDesc *SignDescriptor,
func SecondLevelHtlcScript(revocationKey, delayKey *btcec.PublicKey,
csvDelay uint32) ([]byte, error) {
builder := txscript.NewScriptBuilder()
builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize(
ToLocalScriptSize,
))
// If this is the revocation clause for this script is to be executed,
// the spender will push a 1, forcing us to hit the true clause of this
@ -815,7 +832,9 @@ func SecondLevelHtlcScript(revocationKey, delayKey *btcec.PublicKey,
func LeaseSecondLevelHtlcScript(revocationKey, delayKey *btcec.PublicKey,
csvDelay, cltvExpiry uint32) ([]byte, error) {
builder := txscript.NewScriptBuilder()
builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize(
ToLocalScriptSize + LeaseWitnessScriptSizeOverhead,
))
// If this is the revocation clause for this script is to be executed,
// the spender will push a 1, forcing us to hit the true clause of this
@ -998,7 +1017,9 @@ func CommitScriptToSelf(csvTimeout uint32, selfKey, revokeKey *btcec.PublicKey)
// have divulged the revocation hash, allowing them to homomorphically
// derive the proper private key which corresponds to the revoke public
// key.
builder := txscript.NewScriptBuilder()
builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize(
ToLocalScriptSize,
))
builder.AddOp(txscript.OP_IF)
@ -1054,7 +1075,9 @@ func LeaseCommitScriptToSelf(selfKey, revokeKey *btcec.PublicKey,
// have divulged the revocation hash, allowing them to homomorphically
// derive the proper private key which corresponds to the revoke public
// key.
builder := txscript.NewScriptBuilder()
builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize(
ToLocalScriptSize + LeaseWitnessScriptSizeOverhead,
))
builder.AddOp(txscript.OP_IF)
@ -1199,7 +1222,9 @@ func CommitSpendNoDelay(signer Signer, signDesc *SignDescriptor,
// p2wkh output spendable immediately, requiring no contestation period.
func CommitScriptUnencumbered(key *btcec.PublicKey) ([]byte, error) {
// This script goes to the "other" party, and is spendable immediately.
builder := txscript.NewScriptBuilder()
builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize(
P2WPKHSize,
))
builder.AddOp(txscript.OP_0)
builder.AddData(btcutil.Hash160(key.SerializeCompressed()))
@ -1219,7 +1244,9 @@ func CommitScriptUnencumbered(key *btcec.PublicKey) ([]byte, error) {
// <key> OP_CHECKSIGVERIFY
// 1 OP_CHECKSEQUENCEVERIFY
func CommitScriptToRemoteConfirmed(key *btcec.PublicKey) ([]byte, error) {
builder := txscript.NewScriptBuilder()
builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize(
ToRemoteConfirmedScriptSize,
))
// Only the given key can spend the output.
builder.AddData(key.SerializeCompressed())
@ -1248,7 +1275,7 @@ func CommitScriptToRemoteConfirmed(key *btcec.PublicKey) ([]byte, error) {
func LeaseCommitScriptToRemoteConfirmed(key *btcec.PublicKey,
leaseExpiry uint32) ([]byte, error) {
builder := txscript.NewScriptBuilder()
builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize(45))
// Only the given key can spend the output.
builder.AddData(key.SerializeCompressed())
@ -1310,7 +1337,9 @@ func CommitSpendToRemoteConfirmed(signer Signer, signDesc *SignDescriptor,
// OP_16 OP_CSV
// OP_ENDIF
func CommitScriptAnchor(key *btcec.PublicKey) ([]byte, error) {
builder := txscript.NewScriptBuilder()
builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize(
AnchorScriptSize,
))
// Spend immediately with key.
builder.AddData(key.SerializeCompressed())

View File

@ -2034,3 +2034,215 @@ func TestSpecificationKeyDerivation(t *testing.T) {
actualRevocationPrivKeyHex)
}
}
// BenchmarkScriptBuilderAlloc benchmarks the script builder's default
// allocation.
func BenchmarkScriptBuilderAlloc(t *testing.B) {
dummyData := []byte("dummy data")
randomPriv, err := btcec.NewPrivateKey()
require.NoError(t, err)
randomPub := randomPriv.PubKey()
randomPubBytes := randomPub.SerializeCompressed()
t.ReportAllocs()
t.ResetTimer()
// We run each iteration 1000 times to make the script allocation stand
// out a bit more vs. the overhead of the rest of the function calls.
for i := 0; i < t.N*1000; i++ {
err := runScriptAllocTest(dummyData, randomPubBytes, randomPub)
if err != nil {
t.Fatal(err)
}
}
}
// TestScriptBuilderAlloc makes sure the scripts generated by the utils have the
// correct length.
func TestScriptBuilderAlloc(t *testing.T) {
t.Parallel()
dummyData := []byte("dummy data")
randomPriv, err := btcec.NewPrivateKey()
require.NoError(t, err)
randomPub := randomPriv.PubKey()
randomPubBytes := randomPub.SerializeCompressed()
err = runScriptAllocTest(dummyData, randomPubBytes, randomPub)
require.NoError(t, err)
}
// runScriptAllocTest runs the script size test. We don't use the "require"
// library to assert the length to not influence the benchmark too much.
func runScriptAllocTest(dummyData, randomPubBytes []byte,
randomPub *btcec.PublicKey) error {
const (
maxCLTV = 500000000
maxCSV = (1 << 31) - 1
)
script, err := WitnessScriptHash(dummyData)
if err != nil {
return err
}
if len(script) != P2WSHSize {
return fmt.Errorf("expected script size of %d", P2WSHSize)
}
script, err = WitnessPubKeyHash(randomPubBytes)
if err != nil {
return err
}
if len(script) != P2WPKHSize {
return fmt.Errorf("expected script size of %d", P2WPKHSize)
}
script, err = GenerateP2SH(dummyData)
if err != nil {
return err
}
if len(script) != NestedP2WPKHSize {
return fmt.Errorf("expected script size of %d",
NestedP2WPKHSize)
}
script, err = GenerateP2PKH(dummyData)
if err != nil {
return err
}
if len(script) != P2PKHSize {
return fmt.Errorf("expected script size of %d", P2PKHSize)
}
script, err = GenMultiSigScript(randomPubBytes, randomPubBytes)
if err != nil {
return err
}
if len(script) != MultiSigSize {
return fmt.Errorf("expected script size of %d", MultiSigSize)
}
script, err = SenderHTLCScript(
randomPub, randomPub, randomPub, dummyData, false,
)
if err != nil {
return err
}
if len(script) != OfferedHtlcScriptSize {
return fmt.Errorf("expected script size of %d",
OfferedHtlcScriptSize)
}
script, err = SenderHTLCScript(
randomPub, randomPub, randomPub, dummyData, true,
)
if err != nil {
return err
}
if len(script) != OfferedHtlcScriptSizeConfirmed {
return fmt.Errorf("expected script size of %d",
OfferedHtlcScriptSizeConfirmed)
}
script, err = ReceiverHTLCScript(
maxCLTV, randomPub, randomPub, randomPub, dummyData, false,
)
if err != nil {
return err
}
if len(script) != AcceptedHtlcScriptSize {
return fmt.Errorf("expected script size of %d",
AcceptedHtlcScriptSize)
}
script, err = ReceiverHTLCScript(
maxCLTV, randomPub, randomPub, randomPub, dummyData, true,
)
if err != nil {
return err
}
if len(script) != AcceptedHtlcScriptSizeConfirmed {
return fmt.Errorf("expected script size of %d",
AcceptedHtlcScriptSizeConfirmed)
}
script, err = SecondLevelHtlcScript(randomPub, randomPub, maxCSV)
if err != nil {
return err
}
if len(script) != ToLocalScriptSize {
return fmt.Errorf("expected script size of %d",
ToLocalScriptSize)
}
script, err = LeaseSecondLevelHtlcScript(
randomPub, randomPub, maxCSV, maxCLTV,
)
if err != nil {
return err
}
if len(script) != ToLocalScriptSize+LeaseWitnessScriptSizeOverhead {
return fmt.Errorf("expected script size of %d",
ToLocalScriptSize+LeaseWitnessScriptSizeOverhead)
}
script, err = CommitScriptToSelf(maxCSV, randomPub, randomPub)
if err != nil {
return err
}
if len(script) != ToLocalScriptSize {
return fmt.Errorf("expected script size of %d",
ToLocalScriptSize)
}
script, err = LeaseCommitScriptToSelf(
randomPub, randomPub, maxCSV, maxCLTV,
)
if err != nil {
return err
}
if len(script) != ToLocalScriptSize+LeaseWitnessScriptSizeOverhead {
return fmt.Errorf("expected script size of %d",
ToLocalScriptSize+LeaseWitnessScriptSizeOverhead)
}
script, err = CommitScriptUnencumbered(randomPub)
if err != nil {
return err
}
if len(script) != P2WPKHSize {
return fmt.Errorf("expected script size of %d", P2WPKHSize)
}
script, err = CommitScriptToRemoteConfirmed(randomPub)
if err != nil {
return err
}
if len(script) != ToRemoteConfirmedScriptSize {
return fmt.Errorf("expected script size of %d",
ToRemoteConfirmedScriptSize)
}
script, err = LeaseCommitScriptToRemoteConfirmed(randomPub, maxCSV)
if err != nil {
return err
}
if len(script) !=
ToRemoteConfirmedScriptSize+LeaseWitnessScriptSizeOverhead {
return fmt.Errorf("expected script size of %d",
ToRemoteConfirmedScriptSize+
LeaseWitnessScriptSizeOverhead)
}
script, err = CommitScriptAnchor(randomPub)
if err != nil {
return err
}
if len(script) != AnchorScriptSize {
return fmt.Errorf("expected script size of %d",
AnchorScriptSize)
}
return nil
}