723 lines
19 KiB
Go
723 lines
19 KiB
Go
package gui
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"runtime"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/niubaoshu/gotiny"
|
|
"github.com/tyler-smith/go-bip39"
|
|
|
|
"github.com/p9c/p9/pkg/log"
|
|
"github.com/p9c/p9/pkg/opts/meta"
|
|
"github.com/p9c/p9/pkg/opts/text"
|
|
"github.com/p9c/p9/pkg/chainrpc/p2padvt"
|
|
"github.com/p9c/p9/pkg/pipe"
|
|
"github.com/p9c/p9/pkg/transport"
|
|
"github.com/p9c/p9/pod/state"
|
|
|
|
uberatomic "go.uber.org/atomic"
|
|
|
|
"github.com/p9c/p9/pkg/gel/gio/op/paint"
|
|
|
|
"github.com/p9c/p9/pkg/qu"
|
|
|
|
"github.com/p9c/p9/pkg/interrupt"
|
|
|
|
"github.com/p9c/p9/pkg/gel"
|
|
"github.com/p9c/p9/pkg/btcjson"
|
|
|
|
l "github.com/p9c/p9/pkg/gel/gio/layout"
|
|
|
|
"github.com/p9c/p9/cmd/gui/cfg"
|
|
"github.com/p9c/p9/pkg/apputil"
|
|
"github.com/p9c/p9/pkg/rpcclient"
|
|
"github.com/p9c/p9/pkg/util/rununit"
|
|
)
|
|
|
|
// Main is the entrypoint for the wallet GUI
|
|
func Main(cx *state.State) (e error) {
|
|
size := uberatomic.NewInt32(0)
|
|
noWallet := true
|
|
wg := &WalletGUI{
|
|
cx: cx,
|
|
invalidate: qu.Ts(16),
|
|
quit: cx.KillAll,
|
|
Size: size,
|
|
noWallet: &noWallet,
|
|
otherNodes: make(map[uint64]*nodeSpec),
|
|
certs: cx.Config.ReadCAFile(),
|
|
|
|
}
|
|
return wg.Run()
|
|
}
|
|
|
|
type BoolMap map[string]*gel.Bool
|
|
type ListMap map[string]*gel.List
|
|
type CheckableMap map[string]*gel.Checkable
|
|
type ClickableMap map[string]*gel.Clickable
|
|
type Inputs map[string]*gel.Input
|
|
type Passwords map[string]*gel.Password
|
|
type IncDecMap map[string]*gel.IncDec
|
|
|
|
type WalletGUI struct {
|
|
wg sync.WaitGroup
|
|
cx *state.State
|
|
quit qu.C
|
|
State *State
|
|
noWallet *bool
|
|
node, wallet, miner *rununit.RunUnit
|
|
walletToLock time.Time
|
|
walletLockTime int
|
|
ChainMutex, WalletMutex sync.Mutex
|
|
ChainClient, WalletClient *rpcclient.Client
|
|
WalletWatcher qu.C
|
|
*gel.Window
|
|
Size *uberatomic.Int32
|
|
MainApp *gel.App
|
|
invalidate qu.C
|
|
unlockPage *gel.App
|
|
loadingPage *gel.App
|
|
config *cfg.Config
|
|
configs cfg.GroupsMap
|
|
unlockPassword *gel.Password
|
|
sidebarButtons []*gel.Clickable
|
|
buttonBarButtons []*gel.Clickable
|
|
statusBarButtons []*gel.Clickable
|
|
receiveAddressbookClickables []*gel.Clickable
|
|
sendAddressbookClickables []*gel.Clickable
|
|
quitClickable *gel.Clickable
|
|
bools BoolMap
|
|
lists ListMap
|
|
checkables CheckableMap
|
|
clickables ClickableMap
|
|
inputs Inputs
|
|
passwords Passwords
|
|
incdecs IncDecMap
|
|
console *Console
|
|
RecentTxsWidget, TxHistoryWidget l.Widget
|
|
recentTxsClickables, txHistoryClickables []*gel.Clickable
|
|
txHistoryList []btcjson.ListTransactionsResult
|
|
openTxID, prevOpenTxID *uberatomic.String
|
|
originTxDetail string
|
|
txMx sync.Mutex
|
|
stateLoaded *uberatomic.Bool
|
|
currentReceiveQRCode *paint.ImageOp
|
|
currentReceiveAddress string
|
|
currentReceiveQR l.Widget
|
|
currentReceiveRegenClickable *gel.Clickable
|
|
currentReceiveCopyClickable *gel.Clickable
|
|
currentReceiveRegenerate *uberatomic.Bool
|
|
// currentReceiveGetNew *uberatomic.Bool
|
|
sendClickable *gel.Clickable
|
|
ready *uberatomic.Bool
|
|
mainDirection l.Direction
|
|
preRendering bool
|
|
// ReceiveAddressbook l.Widget
|
|
// SendAddressbook l.Widget
|
|
ReceivePage *ReceivePage
|
|
SendPage *SendPage
|
|
// toasts *toast.Toasts
|
|
// dialog *dialog.Dialog
|
|
createSeed []byte
|
|
createWords, showWords, createMatch string
|
|
createVerifying bool
|
|
restoring bool
|
|
lastUpdated uberatomic.Int64
|
|
multiConn *transport.Channel
|
|
otherNodes map[uint64]*nodeSpec
|
|
uuid uint64
|
|
peerCount *uberatomic.Int32
|
|
certs []byte
|
|
}
|
|
|
|
type nodeSpec struct {
|
|
time.Time
|
|
addr string
|
|
}
|
|
|
|
func (wg *WalletGUI) Run() (e error) {
|
|
wg.openTxID = uberatomic.NewString("")
|
|
var mc *transport.Channel
|
|
quit := qu.T()
|
|
// I.Ln(wg.cx.Config.MulticastPass.V(), string(wg.cx.Config.MulticastPass.
|
|
// Bytes()))
|
|
if mc, e = transport.NewBroadcastChannel(
|
|
"controller",
|
|
wg,
|
|
wg.cx.Config.MulticastPass.Bytes(),
|
|
transport.DefaultPort,
|
|
16384,
|
|
handlersMulticast,
|
|
quit,
|
|
); E.Chk(e) {
|
|
return
|
|
}
|
|
wg.multiConn = mc
|
|
wg.peerCount = uberatomic.NewInt32(0)
|
|
wg.prevOpenTxID = uberatomic.NewString("")
|
|
wg.stateLoaded = uberatomic.NewBool(false)
|
|
wg.currentReceiveRegenerate = uberatomic.NewBool(true)
|
|
wg.ready = uberatomic.NewBool(false)
|
|
wg.Window = gel.NewWindowP9(wg.quit)
|
|
wg.Dark = wg.cx.Config.DarkTheme
|
|
wg.Colors.SetDarkTheme(wg.Dark.True())
|
|
*wg.noWallet = true
|
|
wg.GetButtons()
|
|
wg.lists = wg.GetLists()
|
|
wg.clickables = wg.GetClickables()
|
|
wg.checkables = wg.GetCheckables()
|
|
before := func() { D.Ln("running before") }
|
|
after := func() { D.Ln("running after") }
|
|
I.Ln(os.Args[1:])
|
|
options := []string{os.Args[0]}
|
|
// options = append(options, wg.cx.Config.FoundArgs...)
|
|
// options = append(options, "pipelog")
|
|
wg.node = wg.GetRunUnit(
|
|
"NODE", before, after,
|
|
append(options, "node")...,
|
|
// "node",
|
|
)
|
|
wg.wallet = wg.GetRunUnit(
|
|
"WLLT", before, after,
|
|
append(options, "wallet")...,
|
|
// "wallet",
|
|
)
|
|
wg.miner = wg.GetRunUnit(
|
|
"MINE", before, after,
|
|
append(options, "kopach")...,
|
|
// "wallet",
|
|
)
|
|
// I.S(wg.node, wg.wallet, wg.miner)
|
|
wg.bools = wg.GetBools()
|
|
wg.inputs = wg.GetInputs()
|
|
wg.passwords = wg.GetPasswords()
|
|
// wg.toasts = toast.New(wg.th)
|
|
// wg.dialog = dialog.New(wg.th)
|
|
wg.console = wg.ConsolePage()
|
|
wg.quitClickable = wg.Clickable()
|
|
wg.incdecs = wg.GetIncDecs()
|
|
wg.Size = wg.Window.Width
|
|
wg.currentReceiveCopyClickable = wg.WidgetPool.GetClickable()
|
|
wg.currentReceiveRegenClickable = wg.WidgetPool.GetClickable()
|
|
wg.currentReceiveQR = func(gtx l.Context) l.Dimensions {
|
|
return l.Dimensions{}
|
|
}
|
|
wg.ReceivePage = wg.GetReceivePage()
|
|
wg.SendPage = wg.GetSendPage()
|
|
wg.MainApp = wg.GetAppWidget()
|
|
wg.State = GetNewState(wg.cx.ActiveNet, wg.MainApp.ActivePageGetAtomic())
|
|
wg.unlockPage = wg.getWalletUnlockAppWidget()
|
|
wg.loadingPage = wg.getLoadingPage()
|
|
if !apputil.FileExists(wg.cx.Config.WalletFile.V()) {
|
|
I.Ln("wallet file does not exist", wg.cx.Config.WalletFile.V())
|
|
} else {
|
|
*wg.noWallet = false
|
|
// if !*wg.cx.Config.NodeOff {
|
|
// // wg.startNode()
|
|
// wg.node.Start()
|
|
// }
|
|
if wg.cx.Config.Generate.True() && wg.cx.Config.GenThreads.V() != 0 {
|
|
// wg.startMiner()
|
|
wg.miner.Start()
|
|
}
|
|
wg.unlockPassword.Focus()
|
|
}
|
|
interrupt.AddHandler(
|
|
func() {
|
|
D.Ln("quitting wallet gui")
|
|
// consume.Kill(wg.Node)
|
|
// consume.Kill(wg.Miner)
|
|
// wg.gracefulShutdown()
|
|
wg.quit.Q()
|
|
},
|
|
)
|
|
go func() {
|
|
ticker := time.NewTicker(time.Second)
|
|
out:
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
go func() {
|
|
if e = wg.Advertise(); E.Chk(e) {
|
|
}
|
|
if wg.node.Running() {
|
|
if wg.ChainClient != nil {
|
|
if !wg.ChainClient.Disconnected() {
|
|
var pi []btcjson.GetPeerInfoResult
|
|
if pi, e = wg.ChainClient.GetPeerInfo(); E.Chk(e) {
|
|
return
|
|
}
|
|
wg.peerCount.Store(int32(len(pi)))
|
|
wg.Invalidate()
|
|
}
|
|
}
|
|
}
|
|
}()
|
|
case <-wg.invalidate.Wait():
|
|
T.Ln("invalidating render queue")
|
|
wg.Window.Window.Invalidate()
|
|
// TODO: make a more appropriate trigger for this - ie, when state actually changes.
|
|
// if wg.wallet.Running() && wg.stateLoaded.Load() {
|
|
// filename := filepath.Join(wg.cx.DataDir, "state.json")
|
|
// if e := wg.State.Save(filename, wg.cx.Config.WalletPass); E.Chk(e) {
|
|
// }
|
|
// }
|
|
case <-wg.cx.KillAll.Wait():
|
|
break out
|
|
case <-wg.quit.Wait():
|
|
break out
|
|
}
|
|
}
|
|
}()
|
|
if e := wg.Window.
|
|
Size(56, 32).
|
|
Title("ParallelCoin Wallet").
|
|
Open().
|
|
Run(
|
|
func(gtx l.Context) l.Dimensions {
|
|
return wg.Fill(
|
|
"DocBg", l.Center, 0, 0, func(gtx l.Context) l.Dimensions {
|
|
return gel.If(
|
|
*wg.noWallet,
|
|
wg.CreateWalletPage,
|
|
func(gtx l.Context) l.Dimensions {
|
|
switch {
|
|
case wg.stateLoaded.Load():
|
|
return wg.MainApp.Fn()(gtx)
|
|
default:
|
|
return wg.unlockPage.Fn()(gtx)
|
|
}
|
|
},
|
|
)(gtx)
|
|
},
|
|
).Fn(gtx)
|
|
},
|
|
wg.quit.Q,
|
|
wg.quit,
|
|
); E.Chk(e) {
|
|
}
|
|
wg.gracefulShutdown()
|
|
wg.quit.Q()
|
|
return
|
|
}
|
|
|
|
func (wg *WalletGUI) GetButtons() {
|
|
wg.sidebarButtons = make([]*gel.Clickable, 12)
|
|
// wg.walletLocked.Store(true)
|
|
for i := range wg.sidebarButtons {
|
|
wg.sidebarButtons[i] = wg.Clickable()
|
|
}
|
|
wg.buttonBarButtons = make([]*gel.Clickable, 5)
|
|
for i := range wg.buttonBarButtons {
|
|
wg.buttonBarButtons[i] = wg.Clickable()
|
|
}
|
|
wg.statusBarButtons = make([]*gel.Clickable, 8)
|
|
for i := range wg.statusBarButtons {
|
|
wg.statusBarButtons[i] = wg.Clickable()
|
|
}
|
|
}
|
|
|
|
func (wg *WalletGUI) ShuffleSeed() {
|
|
wg.createSeed = make([]byte, 32)
|
|
_, _ = rand.Read(wg.createSeed)
|
|
var e error
|
|
var wk string
|
|
if wk, e = bip39.NewMnemonic(wg.createSeed); E.Chk(e) {
|
|
panic(e)
|
|
}
|
|
wg.createWords = wk
|
|
// wg.createMatch = wk
|
|
wks := strings.Split(wk, " ")
|
|
var out string
|
|
for i := 0; i < 24; i += 4 {
|
|
out += strings.Join(wks[i:i+4], " ")
|
|
if i+4 < 24 {
|
|
out += "\n"
|
|
}
|
|
}
|
|
wg.showWords = out
|
|
}
|
|
|
|
func (wg *WalletGUI) GetInputs() Inputs {
|
|
wg.ShuffleSeed()
|
|
return Inputs{
|
|
"receiveAmount": wg.Input("", "Amount", "DocText", "PanelBg", "DocBg", func(amt string) {}, func(string) {}),
|
|
"receiveMessage": wg.Input(
|
|
"",
|
|
"Title",
|
|
"DocText",
|
|
"PanelBg",
|
|
"DocBg",
|
|
func(pass string) {},
|
|
func(string) {},
|
|
),
|
|
|
|
"sendAddress": wg.Input(
|
|
"",
|
|
"Parallelcoin Address",
|
|
"DocText",
|
|
"PanelBg",
|
|
"DocBg",
|
|
func(amt string) {},
|
|
func(string) {},
|
|
),
|
|
"sendAmount": wg.Input("", "Amount", "DocText", "PanelBg", "DocBg", func(amt string) {}, func(string) {}),
|
|
"sendMessage": wg.Input(
|
|
"",
|
|
"Title",
|
|
"DocText",
|
|
"PanelBg",
|
|
"DocBg",
|
|
func(pass string) {},
|
|
func(string) {},
|
|
),
|
|
|
|
"console": wg.Input(
|
|
"",
|
|
"enter rpc command",
|
|
"DocText",
|
|
"Transparent",
|
|
"PanelBg",
|
|
func(pass string) {},
|
|
func(string) {},
|
|
),
|
|
"walletWords": wg.Input(
|
|
/*wg.createWords*/ "", "wallet word seed", "DocText", "DocBg", "PanelBg", func(string) {},
|
|
func(seedWords string) {
|
|
wg.createMatch = seedWords
|
|
wg.Invalidate()
|
|
},
|
|
),
|
|
"walletRestore": wg.Input(
|
|
/*wg.createWords*/ "", "enter seed to restore", "DocText", "DocBg", "PanelBg", func(string) {},
|
|
func(seedWords string) {
|
|
var e error
|
|
wg.createMatch = seedWords
|
|
if wg.createSeed, e = bip39.EntropyFromMnemonic(seedWords); E.Chk(e) {
|
|
return
|
|
}
|
|
wg.createWords = seedWords
|
|
wg.Invalidate()
|
|
},
|
|
),
|
|
// "walletSeed": wg.Input(
|
|
// seedString, "wallet seed", "DocText", "DocBg", "PanelBg", func(seedHex string) {
|
|
// var e error
|
|
// if wg.createSeed, e = hex.DecodeString(seedHex); E.Chk(e) {
|
|
// return
|
|
// }
|
|
// var wk string
|
|
// if wk, e = bip39.NewMnemonic(wg.createSeed); E.Chk(e) {
|
|
// panic(e)
|
|
// }
|
|
// wg.createWords=wk
|
|
// wks := strings.Split(wk, " ")
|
|
// var out string
|
|
// for i := 0; i < 24; i += 4 {
|
|
// out += strings.Join(wks[i:i+4], " ") + "\n"
|
|
// }
|
|
// wg.showWords = out
|
|
// }, nil,
|
|
// ),
|
|
}
|
|
}
|
|
|
|
// GetPasswords returns the passwords used in the wallet GUI
|
|
func (wg *WalletGUI) GetPasswords() (passwords Passwords) {
|
|
passwords = Passwords{
|
|
"passEditor": wg.Password(
|
|
"password (minimum 8 characters length)",
|
|
text.New(meta.Data{}, ""),
|
|
"DocText",
|
|
"DocBg",
|
|
"PanelBg",
|
|
func(pass string) {},
|
|
),
|
|
"confirmPassEditor": wg.Password(
|
|
"confirm",
|
|
text.New(meta.Data{}, ""),
|
|
"DocText",
|
|
"DocBg",
|
|
"PanelBg",
|
|
func(pass string) {},
|
|
),
|
|
"publicPassEditor": wg.Password(
|
|
"public password (optional)",
|
|
wg.cx.Config.WalletPass,
|
|
"Primary",
|
|
"DocText",
|
|
"PanelBg",
|
|
func(pass string) {},
|
|
),
|
|
}
|
|
return
|
|
}
|
|
|
|
func (wg *WalletGUI) GetIncDecs() IncDecMap {
|
|
return IncDecMap{
|
|
"generatethreads": wg.IncDec().
|
|
NDigits(2).
|
|
Min(0).
|
|
Max(runtime.NumCPU()).
|
|
SetCurrent(wg.cx.Config.GenThreads.V()).
|
|
ChangeHook(
|
|
func(n int) {
|
|
D.Ln("threads value now", n)
|
|
go func() {
|
|
D.Ln("setting thread count")
|
|
if wg.miner.Running() && n != 0 {
|
|
wg.miner.Stop()
|
|
wg.miner.Start()
|
|
}
|
|
if n == 0 {
|
|
wg.miner.Stop()
|
|
}
|
|
wg.cx.Config.GenThreads.Set(n)
|
|
_ = wg.cx.Config.WriteToFile(wg.cx.Config.ConfigFile.V())
|
|
// if wg.miner.Running() {
|
|
// D.Ln("restarting miner")
|
|
// wg.miner.Stop()
|
|
// wg.miner.Start()
|
|
// }
|
|
}()
|
|
},
|
|
),
|
|
"idleTimeout": wg.IncDec().
|
|
Scale(4).
|
|
Min(60).
|
|
Max(3600).
|
|
NDigits(4).
|
|
Amount(60).
|
|
SetCurrent(300).
|
|
ChangeHook(
|
|
func(n int) {
|
|
D.Ln("idle timeout", time.Duration(n)*time.Second)
|
|
},
|
|
),
|
|
}
|
|
}
|
|
|
|
func (wg *WalletGUI) GetRunUnit(
|
|
name string, before, after func(), args ...string,
|
|
) *rununit.RunUnit {
|
|
I.Ln("getting rununit for", name, args)
|
|
// we have to copy the args otherwise further mutations affect this one
|
|
argsCopy := make([]string, len(args))
|
|
copy(argsCopy, args)
|
|
return rununit.New(name, before, after, pipe.SimpleLog(name),
|
|
pipe.FilterNone, wg.quit, argsCopy...)
|
|
}
|
|
|
|
func (wg *WalletGUI) GetLists() (o ListMap) {
|
|
return ListMap{
|
|
"createWallet": wg.List(),
|
|
"overview": wg.List(),
|
|
"balances": wg.List(),
|
|
"recent": wg.List(),
|
|
"send": wg.List(),
|
|
"sendMedium": wg.List(),
|
|
"sendAddresses": wg.List(),
|
|
"receive": wg.List(),
|
|
"receiveMedium": wg.List(),
|
|
"receiveAddresses": wg.List(),
|
|
"transactions": wg.List(),
|
|
"settings": wg.List(),
|
|
"received": wg.List(),
|
|
"history": wg.List(),
|
|
"txdetail": wg.List(),
|
|
}
|
|
}
|
|
|
|
func (wg *WalletGUI) GetClickables() ClickableMap {
|
|
return ClickableMap{
|
|
"balanceConfirmed": wg.Clickable(),
|
|
"balanceUnconfirmed": wg.Clickable(),
|
|
"balanceTotal": wg.Clickable(),
|
|
"createWallet": wg.Clickable(),
|
|
"createVerify": wg.Clickable(),
|
|
"createShuffle": wg.Clickable(),
|
|
"createRestore": wg.Clickable(),
|
|
"genesis": wg.Clickable(),
|
|
"autofill": wg.Clickable(),
|
|
"quit": wg.Clickable(),
|
|
"sendSend": wg.Clickable(),
|
|
"sendSave": wg.Clickable(),
|
|
"sendFromRequest": wg.Clickable(),
|
|
"receiveCreateNewAddress": wg.Clickable(),
|
|
"receiveClear": wg.Clickable(),
|
|
"receiveShow": wg.Clickable(),
|
|
"receiveRemove": wg.Clickable(),
|
|
"transactions10": wg.Clickable(),
|
|
"transactions30": wg.Clickable(),
|
|
"transactions50": wg.Clickable(),
|
|
"txPageForward": wg.Clickable(),
|
|
"txPageBack": wg.Clickable(),
|
|
"theme": wg.Clickable(),
|
|
}
|
|
}
|
|
|
|
func (wg *WalletGUI) GetCheckables() CheckableMap {
|
|
return CheckableMap{}
|
|
}
|
|
|
|
func (wg *WalletGUI) GetBools() BoolMap {
|
|
return BoolMap{
|
|
"runstate": wg.Bool(wg.node.Running()),
|
|
"encryption": wg.Bool(false),
|
|
"seed": wg.Bool(false),
|
|
"testnet": wg.Bool(false),
|
|
"lan": wg.Bool(false),
|
|
"solo": wg.Bool(false),
|
|
"ihaveread": wg.Bool(false),
|
|
"showGenerate": wg.Bool(true),
|
|
"showSent": wg.Bool(true),
|
|
"showReceived": wg.Bool(true),
|
|
"showImmature": wg.Bool(true),
|
|
}
|
|
}
|
|
|
|
var shuttingDown = false
|
|
|
|
func (wg *WalletGUI) gracefulShutdown() {
|
|
if shuttingDown {
|
|
D.Ln(log.Caller("already called gracefulShutdown", 1))
|
|
return
|
|
} else {
|
|
shuttingDown = true
|
|
}
|
|
D.Ln("\nquitting wallet gui\n")
|
|
if wg.miner.Running() {
|
|
D.Ln("stopping miner")
|
|
wg.miner.Stop()
|
|
wg.miner.Shutdown()
|
|
}
|
|
if wg.wallet.Running() {
|
|
D.Ln("stopping wallet")
|
|
wg.wallet.Stop()
|
|
wg.wallet.Shutdown()
|
|
wg.unlockPassword.Wipe()
|
|
// wg.walletLocked.Store(true)
|
|
}
|
|
if wg.node.Running() {
|
|
D.Ln("stopping node")
|
|
wg.node.Stop()
|
|
wg.node.Shutdown()
|
|
}
|
|
// wg.ChainMutex.Lock()
|
|
if wg.ChainClient != nil {
|
|
D.Ln("stopping chain client")
|
|
wg.ChainClient.Shutdown()
|
|
wg.ChainClient = nil
|
|
}
|
|
// wg.ChainMutex.Unlock()
|
|
// wg.WalletMutex.Lock()
|
|
if wg.WalletClient != nil {
|
|
D.Ln("stopping wallet client")
|
|
wg.WalletClient.Shutdown()
|
|
wg.WalletClient = nil
|
|
}
|
|
// wg.WalletMutex.Unlock()
|
|
// interrupt.Request()
|
|
// time.Sleep(time.Second)
|
|
wg.quit.Q()
|
|
}
|
|
|
|
var handlersMulticast = transport.Handlers{
|
|
// string(sol.Magic): processSolMsg,
|
|
string(p2padvt.Magic): processAdvtMsg,
|
|
// string(hashrate.Magic): processHashrateMsg,
|
|
}
|
|
|
|
func processAdvtMsg(
|
|
ctx interface{}, src net.Addr, dst string, b []byte,
|
|
) (e error) {
|
|
wg := ctx.(*WalletGUI)
|
|
if wg.cx.Config.Discovery.False() {
|
|
return
|
|
}
|
|
if wg.ChainClient == nil {
|
|
T.Ln("no chain client to process advertisment")
|
|
return
|
|
}
|
|
var j p2padvt.Advertisment
|
|
gotiny.Unmarshal(b, &j)
|
|
// I.S(j)
|
|
var peerUUID uint64
|
|
peerUUID = j.UUID
|
|
// I.Ln("peerUUID of advertisment", peerUUID, wg.otherNodes)
|
|
if int(peerUUID) == wg.cx.Config.UUID.V() {
|
|
D.Ln("ignoring own advertisment message")
|
|
return
|
|
}
|
|
if _, ok := wg.otherNodes[peerUUID]; !ok {
|
|
var pi []btcjson.GetPeerInfoResult
|
|
if pi, e = wg.ChainClient.GetPeerInfo(); E.Chk(e) {
|
|
}
|
|
for i := range pi {
|
|
for k := range j.IPs {
|
|
jpa := net.JoinHostPort(k, fmt.Sprint(j.P2P))
|
|
if jpa == pi[i].Addr {
|
|
I.Ln("not connecting to node already connected outbound")
|
|
return
|
|
}
|
|
if jpa == pi[i].AddrLocal {
|
|
I.Ln("not connecting to node already connected inbound")
|
|
return
|
|
}
|
|
}
|
|
}
|
|
// if we haven't already added it to the permanent peer list, we can add it now
|
|
I.Ln("connecting to lan peer with same PSK", j.IPs, peerUUID)
|
|
wg.otherNodes[peerUUID] = &nodeSpec{}
|
|
wg.otherNodes[peerUUID].Time = time.Now()
|
|
for i := range j.IPs {
|
|
addy := net.JoinHostPort(i, fmt.Sprint(j.P2P))
|
|
for j := range pi {
|
|
if addy == pi[j].Addr || addy == pi[j].AddrLocal {
|
|
// not connecting to peer we already have connected to
|
|
return
|
|
}
|
|
}
|
|
}
|
|
// try all IPs
|
|
for addr := range j.IPs {
|
|
peerIP := net.JoinHostPort(addr, fmt.Sprint(j.P2P))
|
|
if e = wg.ChainClient.AddNode(peerIP, "add"); E.Chk(e) {
|
|
continue
|
|
}
|
|
D.Ln("connected to peer via address", peerIP)
|
|
wg.otherNodes[peerUUID].addr = peerIP
|
|
break
|
|
}
|
|
I.Ln(peerUUID, "added", "otherNodes", wg.otherNodes)
|
|
} else {
|
|
// update last seen time for peerUUID for garbage collection of stale disconnected
|
|
// nodes
|
|
D.Ln("other node seen again", peerUUID, wg.otherNodes[peerUUID].addr)
|
|
wg.otherNodes[peerUUID].Time = time.Now()
|
|
}
|
|
// I.S(wg.otherNodes)
|
|
// If we lose connection for more than 9 seconds we delete and if the node
|
|
// reappears it can be reconnected
|
|
for i := range wg.otherNodes {
|
|
D.Ln(i, wg.otherNodes[i])
|
|
tn := time.Now()
|
|
if tn.Sub(wg.otherNodes[i].Time) > time.Second*6 {
|
|
// also remove from connection manager
|
|
if e = wg.ChainClient.AddNode(wg.otherNodes[i].addr, "remove"); E.Chk(e) {
|
|
}
|
|
D.Ln("deleting", tn, wg.otherNodes[i], i)
|
|
delete(wg.otherNodes, i)
|
|
}
|
|
}
|
|
// on := int32(len(wg.otherNodes))
|
|
// wg.otherNodeCount.Store(on)
|
|
return
|
|
}
|