Skip to content

Commit 8e7ba7f

Browse files
committed
HACK: Wire per-swap BTC/XMR adapters into startAdaptorMatches.
Core.New builds the AdaptorSwapManager with Send:NoopSender and no BTC or XMR asset adapters, with a comment saying they'd be installed later by per-asset wiring - but no wiring code ever landed. Every orchestrator started from a match therefore got AssetBTC=nil and panicked on the first FundBroadcastTaproot call: panic: runtime error: invalid memory address or nil pointer ...(*Orchestrator).handleKeysReceived orchestrator.go:620 Work around it with two per-swap builders invoked inline before StartSwap: - client/core/adaptorswap_bridge.go: buildAdaptorBTCAdapter reads DCRDEX_ADAPTOR_BTC_{RPC,USER,PASS} env vars, constructs a btcd/rpcclient.Client against the simnet bitcoind RPC, and wraps it in BTCRPCAdapter with a poll-for-confirm waitConfirm. Returns nil if env vars unset. - client/core/adaptorswap_xmr_bridge.go (xmr tag): buildAdaptorXMR Adapter looks up the connected xmr.ExchangeWallet from Core's wallets map and wraps it in XMRWalletAdapter. - client/core/adaptorswap_xmr_bridge_noxmr.go (!xmr tag): stub that returns nil so non-xmr builds still compile. - startAdaptorMatches: sets cfg.AssetBTC / cfg.AssetXMR from the builders before calling StartSwap. With these wired, the initiator can fund + broadcast the lockTx and both sides can exercise the XMR wallet adapter. Still needed for production (README TODO #5): expose the BTC wallet's rpcclient.Client on bisonw's BTC asset wallet so env-var side-channels are not required. Drop the env lookup and source the client from the connected wallet.
1 parent 0e6450f commit 8e7ba7f

3 files changed

Lines changed: 124 additions & 0 deletions

File tree

client/core/adaptorswap_bridge.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"encoding/json"
1919
"errors"
2020
"fmt"
21+
"os"
2122
"sync"
2223
"time"
2324

@@ -401,6 +402,34 @@ func (c *Core) startAdaptorMatches(tracker *trackedTrade, msgMatches []*msgjson.
401402
} else {
402403
ownBTCAddr = c.adaptorOwnDepositAddr(pairBTC)
403404
}
405+
xmrWallet, _ := c.wallet(pairXMR)
406+
// HACK (adaptor swaps): per-swap BTC/XMR adapters. Prefer
407+
// adapters pre-installed on the manager (tests inject mocks
408+
// that way); fall back to env-var BTC RPC + connected XMR
409+
// wallet otherwise. Until per-wallet adapter plumbing lands
410+
// (README TODO #5), this is how the orchestrator gets working
411+
// asset access. Fail fast here instead of letting the
412+
// orchestrator panic later on a typed-nil interface.
413+
var btcAdapter adaptorswap.BTCAssetAdapter = c.adaptorMgr.btc
414+
if btcAdapter == nil {
415+
if a := buildAdaptorBTCAdapter(c.log); a != nil {
416+
btcAdapter = a
417+
}
418+
}
419+
if btcAdapter == nil {
420+
c.log.Errorf("adaptor match %s: BTC adapter unavailable; "+
421+
"set DCRDEX_ADAPTOR_BTC_{RPC,USER,PASS}", matchID)
422+
continue
423+
}
424+
xmrAdapter := c.adaptorMgr.xmr
425+
if xmrAdapter == nil {
426+
xmrAdapter = buildAdaptorXMRAdapter(c.ctx, xmrWallet)
427+
}
428+
if xmrAdapter == nil {
429+
c.log.Errorf("adaptor match %s: XMR adapter unavailable; "+
430+
"connect the XMR wallet (build with -tags xmr)", matchID)
431+
continue
432+
}
404433
cfg := &adaptorswap.Config{
405434
SwapID: swapID,
406435
OrderID: oid,
@@ -414,6 +443,8 @@ func (c *Core) startAdaptorMatches(tracker *trackedTrade, msgMatches []*msgjson.
414443
XmrNetTag: xmrNetTagForNet(c.net),
415444
OwnXMRSweepDest: ownXMRDest,
416445
OwnBTCPayoutAddr: ownBTCAddr,
446+
AssetBTC: btcAdapter,
447+
AssetXMR: xmrAdapter,
417448
// DecodeBTCAddr lets the initiator translate the
418449
// participant's BTCPayoutAddr (received in AdaptorSetupPart)
419450
// into a pkScript without the orchestrator needing to
@@ -618,6 +649,67 @@ func (b *BTCRPCAdapter) CurrentHeight() (int64, error) {
618649
return b.client.GetBlockCount()
619650
}
620651

652+
// HACK (adaptor swaps): buildAdaptorBTCAdapter reads simnet BTC RPC
653+
// credentials from the environment and returns a BTCRPCAdapter. The
654+
// adaptor orchestrator needs an *rpcclient.Client that can FundRaw /
655+
// SignRaw / SendRaw, and bisonw's BTC wallet does not expose its own
656+
// client. Side-channel the connection via env vars until per-wallet
657+
// adapter plumbing lands (README TODO #5). Returns nil if unset.
658+
//
659+
// Env vars:
660+
//
661+
// DCRDEX_ADAPTOR_BTC_RPC host:port, e.g. 127.0.0.1:20556
662+
// DCRDEX_ADAPTOR_BTC_USER rpc user
663+
// DCRDEX_ADAPTOR_BTC_PASS rpc password
664+
func buildAdaptorBTCAdapter(log dex.Logger) *BTCRPCAdapter {
665+
host := os.Getenv("DCRDEX_ADAPTOR_BTC_RPC")
666+
user := os.Getenv("DCRDEX_ADAPTOR_BTC_USER")
667+
pass := os.Getenv("DCRDEX_ADAPTOR_BTC_PASS")
668+
if host == "" || user == "" || pass == "" {
669+
return nil
670+
}
671+
cl, err := rpcclient.New(&rpcclient.ConnConfig{
672+
Host: host,
673+
User: user,
674+
Pass: pass,
675+
HTTPPostMode: true,
676+
DisableTLS: true,
677+
}, nil)
678+
if err != nil {
679+
log.Errorf("adaptor btc rpc setup: %v", err)
680+
return nil
681+
}
682+
waitConfirm := func(ctx context.Context, txid *chainhash.Hash) (int64, error) {
683+
tick := time.NewTicker(3 * time.Second)
684+
defer tick.Stop()
685+
for {
686+
select {
687+
case <-ctx.Done():
688+
return 0, ctx.Err()
689+
case <-tick.C:
690+
}
691+
res, err := cl.GetRawTransactionVerbose(txid)
692+
if err != nil {
693+
continue
694+
}
695+
if res.Confirmations < 1 || res.BlockHash == "" {
696+
continue
697+
}
698+
bh, err := chainhash.NewHashFromStr(res.BlockHash)
699+
if err != nil {
700+
return 0, err
701+
}
702+
hdr, err := cl.GetBlockHeaderVerbose(bh)
703+
if err != nil {
704+
return 0, err
705+
}
706+
return int64(hdr.Height), nil
707+
}
708+
}
709+
log.Infof("adaptor btc rpc adapter: host=%s", host)
710+
return NewBTCRPCAdapter(cl, waitConfirm)
711+
}
712+
621713
// ----- Message router (no-op default) -----
622714

623715
// NoopSender is a message sender that drops outgoing messages. Used

client/core/adaptorswap_xmr_bridge.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,18 @@ func (a *XMRWalletAdapter) SweepSharedAddress(swapID, addr, spendKeyHex, viewKey
5959

6060
return a.w.SweepSharedAddress(a.ctx, swapID, addr, spendKeyHex, viewKeyHex, restoreHeight, dest)
6161
}
62+
63+
// buildAdaptorXMRAdapter wraps a connected XMR wallet for use by the
64+
// adaptor-swap orchestrator. Returns nil if the wallet is not the
65+
// expected *xmr.ExchangeWallet type (e.g. not connected). The xmr
66+
// build tag is required; see the !xmr stub in the companion file.
67+
func buildAdaptorXMRAdapter(ctx context.Context, w *xcWallet) adaptorswap.XMRAssetAdapter {
68+
if w == nil {
69+
return nil
70+
}
71+
ew, ok := w.Wallet.(*xmr.ExchangeWallet)
72+
if !ok {
73+
return nil
74+
}
75+
return NewXMRWalletAdapter(ctx, ew)
76+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//go:build !xmr
2+
3+
// Non-xmr build: the xmr package is not compiled in, so the XMR
4+
// asset adapter cannot be constructed. Returns nil; the adaptor
5+
// orchestrator will fail cleanly on the first XMR call.
6+
7+
package core
8+
9+
import (
10+
"context"
11+
12+
"decred.org/dcrdex/client/core/adaptorswap"
13+
)
14+
15+
func buildAdaptorXMRAdapter(ctx context.Context, w *xcWallet) adaptorswap.XMRAssetAdapter {
16+
return nil
17+
}

0 commit comments

Comments
 (0)