Add auto-unlocker (#347)

* Add auto-unlocker

* Fixes
This commit is contained in:
Pietralberto Mazza
2024-10-04 17:08:43 +02:00
committed by GitHub
parent d37af7daf5
commit 1d40892196
7 changed files with 138 additions and 11 deletions

View File

@@ -79,19 +79,21 @@ func mainAction(_ *cli.Context) error {
BitcoindRpcPass: cfg.BitcoindRpcPass,
BitcoindRpcHost: cfg.BitcoindRpcHost,
BoardingExitDelay: cfg.BoardingExitDelay,
UnlockerType: cfg.UnlockerType,
UnlockerFilePath: cfg.UnlockerFilePath,
}
svc, err := grpcservice.NewService(svcConfig, appConfig)
if err != nil {
return err
}
log.RegisterExitHandler(svc.Stop)
log.Info("starting service...")
if err := svc.Start(); err != nil {
return err
}
log.RegisterExitHandler(svc.Stop)
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT, os.Interrupt)
<-sigChan

View File

@@ -11,6 +11,7 @@ import (
scheduler "github.com/ark-network/ark/server/internal/infrastructure/scheduler/gocron"
txbuilder "github.com/ark-network/ark/server/internal/infrastructure/tx-builder/covenant"
cltxbuilder "github.com/ark-network/ark/server/internal/infrastructure/tx-builder/covenantless"
fileunlocker "github.com/ark-network/ark/server/internal/infrastructure/unlocker/file"
btcwallet "github.com/ark-network/ark/server/internal/infrastructure/wallet/btc-embedded"
liquidwallet "github.com/ark-network/ark/server/internal/infrastructure/wallet/liquid-standalone"
log "github.com/sirupsen/logrus"
@@ -37,6 +38,9 @@ var (
"ocean": {},
"btcwallet": {},
}
supportedUnlockers = supportedType{
"file": {},
}
supportedNetworks = supportedType{
common.Bitcoin.Name: {},
common.BitcoinTestNet.Name: {},
@@ -70,6 +74,9 @@ type Config struct {
BitcoindRpcPass string
BitcoindRpcHost string
UnlockerType string
UnlockerFilePath string
repo ports.RepoManager
svc application.Service
adminSvc application.AdminService
@@ -77,6 +84,7 @@ type Config struct {
txBuilder ports.TxBuilder
scanner ports.BlockchainScanner
scheduler ports.SchedulerService
unlocker ports.Unlocker
}
func (c *Config) Validate() error {
@@ -95,6 +103,9 @@ func (c *Config) Validate() error {
if !supportedScanners.supports(c.BlockchainScannerType) {
return fmt.Errorf("blockchain scanner type not supported, please select one of: %s", supportedScanners)
}
if len(c.UnlockerType) > 0 && !supportedUnlockers.supports(c.UnlockerType) {
return fmt.Errorf("unlocker type not supported, please select one of: %s", supportedUnlockers)
}
if c.RoundInterval < 2 {
return fmt.Errorf("invalid round interval, must be at least 2 seconds")
}
@@ -151,7 +162,7 @@ func (c *Config) Validate() error {
return err
}
if err := c.walletService(); err != nil {
return fmt.Errorf("failed to connect to wallet: %s", err)
return err
}
if err := c.txBuilderService(); err != nil {
return err
@@ -165,6 +176,9 @@ func (c *Config) Validate() error {
if err := c.adminService(); err != nil {
return err
}
if err := c.unlockerService(); err != nil {
return err
}
return nil
}
@@ -185,6 +199,10 @@ func (c *Config) WalletService() ports.WalletService {
return c.wallet
}
func (c *Config) UnlockerService() ports.Unlocker {
return c.unlocker
}
func (c *Config) repoManager() error {
var svc ports.RepoManager
var err error
@@ -227,7 +245,7 @@ func (c *Config) walletService() error {
if common.IsLiquid(c.Network) {
svc, err := liquidwallet.NewService(c.WalletAddr)
if err != nil {
return err
return fmt.Errorf("failed to connect to wallet: %s", err)
}
c.wallet = svc
@@ -353,6 +371,26 @@ func (c *Config) adminService() error {
return nil
}
func (c *Config) unlockerService() error {
if len(c.UnlockerType) <= 0 {
return nil
}
var svc ports.Unlocker
var err error
switch c.UnlockerType {
case "file":
svc, err = fileunlocker.NewService(c.UnlockerFilePath)
default:
err = fmt.Errorf("unknown unlocker type")
}
if err != nil {
return err
}
c.unlocker = svc
return nil
}
type supportedType map[string]struct{}
func (t supportedType) String() string {

View File

@@ -36,6 +36,8 @@ type Config struct {
BitcoindRpcHost string
TLSExtraIPs []string
TLSExtraDomains []string
UnlockerType string
UnlockerFilePath string
}
var (
@@ -59,12 +61,14 @@ var (
// #nosec G101
BitcoindRpcUser = "BITCOIND_RPC_USER"
// #nosec G101
BitcoindRpcPass = "BITCOIND_RPC_PASS"
BitcoindRpcHost = "BITCOIND_RPC_HOST"
NoMacaroons = "NO_MACAROONS"
NoTLS = "NO_TLS"
TLSExtraIP = "TLS_EXTRA_IP"
TLSExtraDomain = "TLS_EXTRA_DOMAIN"
BitcoindRpcPass = "BITCOIND_RPC_PASS"
BitcoindRpcHost = "BITCOIND_RPC_HOST"
NoMacaroons = "NO_MACAROONS"
NoTLS = "NO_TLS"
TLSExtraIP = "TLS_EXTRA_IP"
TLSExtraDomain = "TLS_EXTRA_DOMAIN"
UnlockerType = "UNLOCKER_TYPE"
UnlockerFilePath = "UNLOCKER_FILE_PATH"
defaultDatadir = common.AppDataDir("arkd", false)
defaultRoundInterval = 5
@@ -142,6 +146,8 @@ func LoadConfig() (*Config, error) {
NoMacaroons: viper.GetBool(NoMacaroons),
TLSExtraIPs: viper.GetStringSlice(TLSExtraIP),
TLSExtraDomains: viper.GetStringSlice(TLSExtraDomain),
UnlockerType: viper.GetString(UnlockerType),
UnlockerFilePath: viper.GetString(UnlockerFilePath),
}, nil
}

View File

@@ -0,0 +1,7 @@
package ports
import "context"
type Unlocker interface {
GetPassword(ctx context.Context) (string, error)
}

View File

@@ -0,0 +1,37 @@
package fileunlocker
import (
"bytes"
"context"
"fmt"
"os"
"github.com/ark-network/ark/server/internal/core/ports"
)
type service struct {
filePath string
}
func NewService(filePath string) (ports.Unlocker, error) {
if _, err := os.Stat(filePath); err != nil {
if os.IsNotExist(err) {
return nil, fmt.Errorf("password file not found at path %s", filePath)
}
return nil, err
}
return &service{filePath: filePath}, nil
}
func (s *service) GetPassword(_ context.Context) (string, error) {
buf, err := os.ReadFile(s.filePath)
if err != nil {
return "", err
}
password := bytes.TrimFunc(buf, func(r rune) bool {
return r == 10 || r == 13 || r == 32
})
return string(password), nil
}

View File

@@ -304,6 +304,10 @@ func (s *service) Restore(_ context.Context, seed, password string) error {
}
func (s *service) Unlock(_ context.Context, password string) error {
if !s.walletInitialized() {
return fmt.Errorf("wallet not initialized")
}
if !s.walletLoaded() {
pwd := []byte(password)
opt := btcwallet.LoaderWithLocalWalletDB(s.cfg.Datadir, false, time.Minute)

View File

@@ -94,7 +94,13 @@ func NewService(
func (s *service) Start() error {
withoutAppSvc := false
return s.start(withoutAppSvc)
if err := s.start(withoutAppSvc); err != nil {
return err
}
if s.appConfig.UnlockerService() != nil {
return s.autoUnlock()
}
return nil
}
func (s *service) Stop() {
@@ -314,6 +320,33 @@ func (s *service) onInit(password string) {
log.Debugf("generated macaroons at path %s", datadir)
}
func (s *service) autoUnlock() error {
ctx := context.Background()
wallet := s.appConfig.WalletService()
status, err := wallet.Status(ctx)
if err != nil {
return fmt.Errorf("failed to get wallet status: %s", err)
}
if !status.IsInitialized() {
log.Debug("wallet not initiialized, skipping auto unlock")
return nil
}
password, err := s.appConfig.UnlockerService().GetPassword(ctx)
if err != nil {
return fmt.Errorf("failed to get password: %s", err)
}
if err := wallet.Unlock(ctx, password); err != nil {
return fmt.Errorf("failed to auto unlock: %s", err)
}
go s.onUnlock(password)
log.Debug("service auto unlocked")
return nil
}
func router(
grpcServer *grpc.Server, grpcGateway http.Handler,
) http.Handler {