From 4bf97477eed0b52901cd9277e2051bbea5aae49b Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Tue, 28 Feb 2023 19:56:43 +0100 Subject: [PATCH 1/5] mod: bump btcd version --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ac929db6d..7d052a910 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 5a6c84677..eddeb1133 100644 --- a/go.sum +++ b/go.sum @@ -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= From a47345bd6f0485fb705b2eb95dc592a46803bbbe Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Tue, 28 Feb 2023 19:56:44 +0100 Subject: [PATCH 2/5] input: add script size unit and benchmark tests --- input/script_utils_test.go | 212 +++++++++++++++++++++++++++++++++++++ 1 file changed, 212 insertions(+) diff --git a/input/script_utils_test.go b/input/script_utils_test.go index 41f004a4b..05bc6b5d1 100644 --- a/input/script_utils_test.go +++ b/input/script_utils_test.go @@ -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 +} From 79c275253ca887f8f80002ee17d0469406603661 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Tue, 28 Feb 2023 19:56:46 +0100 Subject: [PATCH 3/5] input: set initial script allocation size The default allocation of 500 bytes for the script that is used in NewScriptBuilder is way too much for most of our scripts. With the new functional option we can tune the allocation to exactly what we need. --- input/script_utils.go | 61 +++++++++++++++++++++++++++++++------------ 1 file changed, 45 insertions(+), 16 deletions(-) diff --git a/input/script_utils.go b/input/script_utils.go index d625e69dc..7e9345ef9 100644 --- a/input/script_utils.go +++ b/input/script_utils.go @@ -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) { // 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()) From 3beaf7c4c134898b71048b5af0189951b9989c4c Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Tue, 28 Feb 2023 19:56:47 +0100 Subject: [PATCH 4/5] channeldb: use input.GenMultiSigScript --- channeldb/graph.go | 29 ++++++----------------------- 1 file changed, 6 insertions(+), 23 deletions(-) diff --git a/channeldb/graph.go b/channeldb/graph.go index 1e6dbf8d6..b5326bb8c 100644 --- a/channeldb/graph.go +++ b/channeldb/graph.go @@ -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 - bldr = txscript.NewScriptBuilder() + bldr := txscript.NewScriptBuilder( + txscript.WithScriptAllocSize(input.P2WSHSize), + ) bldr.AddOp(txscript.OP_0) scriptHash := sha256.Sum256(witnessScript) bldr.AddData(scriptHash[:]) From 26254d073032e26346ec7ee202eeb2987914c540 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Tue, 28 Feb 2023 19:56:48 +0100 Subject: [PATCH 5/5] docs: add release notes --- docs/release-notes/release-notes-0.16.1.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/release-notes-0.16.1.md b/docs/release-notes/release-notes-0.16.1.md index f3d1c48a5..7fc43e719 100644 --- a/docs/release-notes/release-notes-0.16.1.md +++ b/docs/release-notes/release-notes-0.16.1.md @@ -40,7 +40,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