Skip to content

Commit

Permalink
multi: Introduce --offline sync mode
Browse files Browse the repository at this point in the history
This adds the --offline sync mode to the wallet. This mode allows the
wallet to run without connecting to the network through either SPV or
RPC modes. Calls that do not require access to the network can still be
executed.

This is mostly useful for running wallets which are meant only to
generate addresses and sign transactions without actually tracking
balances, such as air-gapped wallets.

Previously, such wallets required running with an unconnected dcrd
instance or in SPV mode with --spvconnect specified to an address that
would not provide network services, but these methods are prone to be
misused and recent changes have broken their correct behavior.

The offline mode is introduced by writing a custom NetworkBackend
implementation that is guaranteed to never attempt any external
connections.
  • Loading branch information
matheusd committed Apr 5, 2024
1 parent e4acd44 commit 3b70812
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 2 deletions.
9 changes: 9 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ type config struct {
dial func(ctx context.Context, network, address string) (net.Conn, error)
lookup func(name string) ([]net.IP, error)

// Offline mode.
Offline bool `long:"offline" description:"Do not sync the wallet"`

// SPV options
SPV bool `long:"spv" description:"Sync using simplified payment verification"`
SPVConnect []string `long:"spvconnect" description:"SPV sync only with specified peers; disables DNS seeding"`
Expand Down Expand Up @@ -869,6 +872,12 @@ func loadConfig(ctx context.Context) (*config, []string, error) {
}
}

if cfg.SPV && cfg.Offline {
err := errors.E("SPV and Offline mode cannot be specified at the same time")
fmt.Fprintln(os.Stderr, err)
return loadConfigError(err)
}

if cfg.SPV && cfg.EnableVoting {
err := errors.E("SPV voting is not possible: disable --spv or --enablevoting")
fmt.Fprintln(os.Stderr, err)
Expand Down
7 changes: 5 additions & 2 deletions dcrwallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -443,9 +443,12 @@ func run(ctx context.Context) error {
}
}

if cfg.SPV {
switch {
case cfg.Offline:
w.SetNetworkBackend(wallet.OfflineNetworkBackend{})
case cfg.SPV:
spvLoop(ctx, w)
} else {
default:
rpcSyncLoop(ctx, w)
}
})
Expand Down
3 changes: 3 additions & 0 deletions sample-dcrwallet.conf
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@
; File containing root certificates to authenticate TLS connections with dcrd
; cafile=~/.dcrwallet/dcrd.cert

; When enabled, do not perform any sync with the network, either through RPC or
; SPV modes. Useful when this is an air-gapped wallet.
; offline=0


; ------------------------------------------------------------------------------
Expand Down
38 changes: 38 additions & 0 deletions wallet/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,41 @@ type Caller interface {
// a result (if any), or nil if no result is needed.
Call(ctx context.Context, method string, res any, args ...any) error
}

var errOfflineNetworkBackend = errors.New("operation not supported in offline mode")

// OfflineNetworkBackend is a NetworkBackend that fails every call. It is meant
// to be used in wallets which will only perform local operations.
type OfflineNetworkBackend struct{}

func (o OfflineNetworkBackend) Blocks(ctx context.Context, blockHashes []*chainhash.Hash) ([]*wire.MsgBlock, error) {
return nil, errOfflineNetworkBackend
}

func (o OfflineNetworkBackend) CFiltersV2(ctx context.Context, blockHashes []*chainhash.Hash) ([]FilterProof, error) {
return nil, errOfflineNetworkBackend
}

func (o OfflineNetworkBackend) PublishTransactions(ctx context.Context, txs ...*wire.MsgTx) error {
return errOfflineNetworkBackend
}

func (o OfflineNetworkBackend) LoadTxFilter(ctx context.Context, reload bool, addrs []stdaddr.Address, outpoints []wire.OutPoint) error {
return errOfflineNetworkBackend
}

func (o OfflineNetworkBackend) Rescan(ctx context.Context, blocks []chainhash.Hash, save func(block *chainhash.Hash, txs []*wire.MsgTx) error) error {
return errOfflineNetworkBackend
}

func (o OfflineNetworkBackend) StakeDifficulty(ctx context.Context) (dcrutil.Amount, error) {
return 0, errOfflineNetworkBackend
}

func (o OfflineNetworkBackend) Synced(ctx context.Context) (bool, int32) {
return true, 0
}

// Compile time check to ensure OfflineNetworkBackend fulfills the
// NetworkBackend interface.
var _ NetworkBackend = OfflineNetworkBackend{}

0 comments on commit 3b70812

Please sign in to comment.