Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -1316,6 +1316,12 @@ func ValidateConfig(cfg Config, interceptor signal.Interceptor, fileParser,
numNets++
cfg.ActiveNetParams = chainreg.BitcoinSigNetParams

err := validateSigNetBackendOptions(cfg.Bitcoin)
if err != nil {
return nil, mkErr("error validating bitcoin "+
"params: %v", err)
}

// Let the user overwrite the default signet parameters.
// The challenge defines the actual signet network to
// join and the seed nodes are needed for network
Expand Down Expand Up @@ -1349,6 +1355,20 @@ func ValidateConfig(cfg Config, interceptor signal.Interceptor, fileParser,
chainParams := chaincfg.CustomSignetParams(
sigNetChallenge, sigNetSeeds,
)
if cfg.Bitcoin.SigNetBlockTime != 0 {
if cfg.Bitcoin.SigNetChallenge == "" {
return nil, mkErr("signet block time " +
"requires custom signet challenge")
}

err := applySigNetBlockTime(
&chainParams, cfg.Bitcoin.SigNetBlockTime,
)
if err != nil {
return nil, mkErr("invalid signet block "+
"time: %v", err)
}
}
cfg.ActiveNetParams.Params = &chainParams
}
if numNets > 1 {
Expand Down Expand Up @@ -2501,6 +2521,69 @@ func configToFlatMap(cfg Config) (map[string]string,
return result, deprecated, nil
}

// validateSigNetBackendOptions validates custom signet options against the
// selected chain backend.
func validateSigNetBackendOptions(cfg *lncfg.Chain) error {
if cfg == nil {
return fmt.Errorf("bitcoin config cannot be nil")
}

if !cfg.SigNet {
return nil
}

switch cfg.Node {
case bitcoindBackendName:
switch {
case cfg.SigNetChallenge != "":
return fmt.Errorf("bitcoin.signetchallenge must not " +
"be set with bitcoin.node=bitcoind; " +
"configure custom signet consensus options " +
"on bitcoind instead")

case cfg.SigNetBlockTime != 0:
return fmt.Errorf("bitcoin.signetblocktime must not " +
"be set with bitcoin.node=bitcoind; " +
"configure custom signet consensus options " +
"on bitcoind instead")
}

case btcdBackendName:
if cfg.SigNetBlockTime != 0 {
return fmt.Errorf("bitcoin.signetblocktime is not " +
"supported with bitcoin.node=btcd; btcd does " +
"not currently support custom signet block " +
"intervals")
}
}

return nil
}

// applySigNetBlockTime updates the expected block interval used by custom
// signet header validation. This is needed for custom signets whose backing
// bitcoind nodes were started with -signetblocktime.
func applySigNetBlockTime(params *chaincfg.Params,
blockTime time.Duration) error {

if params == nil {
return fmt.Errorf("params cannot be nil")
}

if blockTime < time.Second {
return fmt.Errorf("must be at least one second")
}
Comment thread
tee8z marked this conversation as resolved.

if blockTime > params.TargetTimespan {
return fmt.Errorf("must not exceed target timespan %v",
params.TargetTimespan)
}

params.TargetTimePerBlock = blockTime
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are other fields of type time.Duration in chaincfg.Params

// TargetTimespan is the desired amount of time that should elapse
// before the block difficulty requirement is examined to determine how
// it should be changed in order to maintain the desired block
// generation rate.
TargetTimespan time.Duration

TargetTimespan should be changed so that difficulty is re-targeted every 2016 blocks.

Also I see MinDiffReductionTime, but it seems to be disabled in signet.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, TargetTimespan is another consensus-critical option we could expose. For this PR, I think lnd should mirror the Mutinynet fork directly and only override TargetTimePerBlock:

benthecarman/bitcoin@2fda7bf

Supporting custom TargetTimespan values seems like a separate option that we should add when there is a live custom signet that needs it.


return nil
}

// logWarningsForDeprecation logs a warning if a deprecated config option is
// set.
func logWarningsForDeprecation(cfg Config) {
Expand Down
243 changes: 243 additions & 0 deletions config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,50 @@ package lnd
import (
"fmt"
"testing"
"time"

"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/txscript"
flags "github.com/jessevdk/go-flags"
"github.com/lightningnetwork/lnd/build"
"github.com/lightningnetwork/lnd/chainreg"
"github.com/lightningnetwork/lnd/routing"
"github.com/lightningnetwork/lnd/signal"
"github.com/stretchr/testify/require"
)

// testSigNetChallengeHex is OP_TRUE encoded as Bitcoin Script hex.
const testSigNetChallengeHex = "51"

var (
testPassword = "testpassword"
redactedPassword = "[redacted]"
)

// testSigNetChallenge is OP_TRUE encoded as Bitcoin Script bytes.
var testSigNetChallenge = []byte{txscript.OP_TRUE}

// validateTestConfig runs ValidateConfig with isolated test logging and closes
// the log rotator that ValidateConfig starts on successful validation.
func validateTestConfig(t *testing.T, cfg Config) (*Config, error) {
Comment thread
tee8z marked this conversation as resolved.
t.Helper()

cfg.SubLogMgr = build.NewSubLoggerManager()

fileParser := flags.NewParser(&cfg, flags.Default)
flagParser := flags.NewParser(&cfg, flags.Default)
cleanCfg, err := ValidateConfig(
cfg, signal.Interceptor{}, fileParser, flagParser,
)
if err != nil {
return cleanCfg, err
}

require.NoError(t, cleanCfg.LogRotator.Close())

return cleanCfg, nil
}

// TestConfigToFlatMap tests that the configToFlatMap function works as
// expected on the default configuration.
func TestConfigToFlatMap(t *testing.T) {
Expand Down Expand Up @@ -116,6 +149,216 @@ func TestSupplyEnvValue(t *testing.T) {
}
}

// TestApplySigNetBlockTime tests that custom signet block times update the
// target block interval used for header difficulty validation.
func TestApplySigNetBlockTime(t *testing.T) {
t.Parallel()

t.Run("valid block time", func(t *testing.T) {
t.Parallel()

params := chaincfg.CustomSignetParams(
chaincfg.DefaultSignetChallenge,
chaincfg.DefaultSignetDNSSeeds,
)
require.NoError(
t, applySigNetBlockTime(&params, 30*time.Second),
)

require.Equal(t, 30*time.Second, params.TargetTimePerBlock)
require.Equal(t, 14*24*time.Hour, params.TargetTimespan)
require.Equal(
t, int64(40320),
int64(params.TargetTimespan/params.TargetTimePerBlock),
)
require.False(t, params.ReduceMinDifficulty)
})

t.Run("nil params", func(t *testing.T) {
t.Parallel()

err := applySigNetBlockTime(nil, 30*time.Second)
require.ErrorContains(t, err, "params cannot be nil")
})

t.Run("sub-second block time", func(t *testing.T) {
t.Parallel()

params := chaincfg.CustomSignetParams(
chaincfg.DefaultSignetChallenge,
chaincfg.DefaultSignetDNSSeeds,
)
err := applySigNetBlockTime(&params, time.Millisecond)
require.ErrorContains(t, err, "at least one second")
})

t.Run("block time exceeds target timespan", func(t *testing.T) {
t.Parallel()

params := chaincfg.CustomSignetParams(
chaincfg.DefaultSignetChallenge,
chaincfg.DefaultSignetDNSSeeds,
)
blockTime := params.TargetTimespan + time.Second
err := applySigNetBlockTime(&params, blockTime)
require.ErrorContains(t, err, "must not exceed target timespan")
})
}

// TestValidateConfigSigNetBlockTime tests that custom signet block times are
// only applied as an optional addition to a custom signet challenge.
func TestValidateConfigSigNetBlockTime(t *testing.T) {
tests := []struct {
name string
challenge string
blockTime time.Duration
expectError string
expectTime time.Duration
}{
{
name: "default signet",
expectTime: chaincfg.SigNetParams.TargetTimePerBlock,
},
{
name: "custom challenge only",
challenge: testSigNetChallengeHex,
expectTime: chaincfg.SigNetParams.TargetTimePerBlock,
},
{
name: "custom challenge with block time",
challenge: testSigNetChallengeHex,
blockTime: 30 * time.Second,
expectTime: 30 * time.Second,
},
{
name: "block time without custom challenge",
blockTime: 30 * time.Second,
expectError: "requires custom signet challenge",
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
cfg := DefaultConfig()
cfg.LndDir = t.TempDir()
cfg.Bitcoin.Node = neutrinoBackendName
cfg.Bitcoin.SigNet = true
cfg.Bitcoin.SigNetChallenge = tc.challenge
cfg.Bitcoin.SigNetBlockTime = tc.blockTime

cleanCfg, err := validateTestConfig(t, cfg)

if tc.expectError != "" {
require.ErrorContains(t, err, tc.expectError)
return
}

require.NoError(t, err)
require.Equal(
t, tc.expectTime,
cleanCfg.ActiveNetParams.Params.
TargetTimePerBlock,
)
})
}
}

// TestValidateConfigSigNetBackendOptions tests that custom signet options are
// only accepted for backends that can use them.
func TestValidateConfigSigNetBackendOptions(t *testing.T) {
err := validateSigNetBackendOptions(nil)
require.ErrorContains(t, err, "bitcoin config cannot be nil")

tests := []struct {
name string
node string
challenge string
blockTime time.Duration
expectError string
expectNet chaincfg.Params
}{
{
name: "bitcoind default signet",
node: bitcoindBackendName,
expectNet: chaincfg.SigNetParams,
},
{
name: "bitcoind custom challenge",
node: bitcoindBackendName,
challenge: testSigNetChallengeHex,
expectError: "bitcoin.signetchallenge must not be " +
"set with bitcoin.node=bitcoind",
},
{
name: "bitcoind custom challenge and block time",
node: bitcoindBackendName,
challenge: testSigNetChallengeHex,
blockTime: 30 * time.Second,
expectError: "bitcoin.signetchallenge must not be " +
"set with bitcoin.node=bitcoind",
},
{
name: "bitcoind custom block time",
node: bitcoindBackendName,
blockTime: 30 * time.Second,
expectError: "bitcoin.signetblocktime must not be " +
"set with bitcoin.node=bitcoind",
},
{
name: "btcd custom challenge",
node: btcdBackendName,
challenge: testSigNetChallengeHex,
expectNet: chaincfg.CustomSignetParams(
testSigNetChallenge,
chaincfg.DefaultSignetDNSSeeds,
),
},
{
name: "btcd custom block time",
node: btcdBackendName,
challenge: testSigNetChallengeHex,
blockTime: 30 * time.Second,
expectError: "bitcoin.signetblocktime is not " +
"supported with bitcoin.node=btcd",
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
cfg := DefaultConfig()
cfg.LndDir = t.TempDir()
cfg.Bitcoin.Node = tc.node
cfg.Bitcoin.SigNet = true
cfg.Bitcoin.SigNetChallenge = tc.challenge
cfg.Bitcoin.SigNetBlockTime = tc.blockTime
cfg.BtcdMode.RPCUser = "user"
cfg.BtcdMode.RPCPass = "pass"
cfg.BitcoindMode.RPCUser = "user"
cfg.BitcoindMode.RPCPass = "pass"
cfg.BitcoindMode.RPCPolling = true

cleanCfg, err := validateTestConfig(t, cfg)

if tc.expectError != "" {
require.ErrorContains(t, err, tc.expectError)
return
}

require.NoError(t, err)
require.Equal(t, tc.node, cleanCfg.Bitcoin.Node)
require.Equal(
t, tc.expectNet.Net,
cleanCfg.ActiveNetParams.Params.Net,
)
require.Equal(
t, tc.expectNet.TargetTimePerBlock,
cleanCfg.ActiveNetParams.Params.
TargetTimePerBlock,
)
})
}
}

// TestValidateConfigTrickleDelay tests that the TrickleDelay configuration
// is properly validated and defaulted in ValidateConfig. This test directly
// verifies the validation logic without going through the full ValidateConfig
Expand Down
7 changes: 7 additions & 0 deletions docs/release-notes/release-notes-0.22.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@

## Functional Enhancements

* A new [`bitcoin.signetblocktime`
config option](https://github.com/lightningnetwork/lnd/pull/10864) allows
neutrino-backed custom signet nodes to override the expected block interval
used for header validation, matching Bitcoin Core's `-signetblocktime`
setting.
Comment thread
tee8z marked this conversation as resolved.

## RPC Additions

* The `routerrpc.EstimateRouteFee` RPC now supports [restricting fee estimates
Expand Down Expand Up @@ -89,3 +95,4 @@

* Boris Nagaev
* Erick Cestari
* Tee8z
Comment thread
tee8z marked this conversation as resolved.
Loading
Loading