From f84500894d4fbb21843cb53d8055290752569eb5 Mon Sep 17 00:00:00 2001 From: lukechampine Date: Sat, 21 Oct 2023 16:19:49 -0400 Subject: [PATCH] all: Update for core changes, add UPnP --- api/api_test.go | 16 ++++----- cmd/walletd/main.go | 8 +++-- cmd/walletd/node.go | 70 +++++++++++++++++++++++++++++------- go.mod | 3 +- go.sum | 6 ++-- internal/walletutil/store.go | 40 +++++++++++---------- 6 files changed, 97 insertions(+), 46 deletions(-) diff --git a/api/api_test.go b/api/api_test.go index 546ac03..335807a 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -57,11 +57,11 @@ func TestWallet(t *testing.T) { } // create wallets - dbstore, tip, err := chain.NewDBStore(chain.NewMemDB(), n, genesisBlock) + dbstore, tipState, err := chain.NewDBStore(chain.NewMemDB(), n, genesisBlock) if err != nil { t.Fatal(err) } - cm := chain.NewManager(dbstore, tip.State) + cm := chain.NewManager(dbstore, tipState) wm := walletutil.NewEphemeralWalletManager(cm) sav := wallet.NewSeedAddressVault(wallet.NewSeed(), 0, 20) c, shutdown := runServer(cm, nil, wm) @@ -179,11 +179,11 @@ func TestV2(t *testing.T) { secondaryAddress := types.StandardUnlockHash(secondaryPrivateKey.PublicKey()) // create wallets - dbstore, tip, err := chain.NewDBStore(chain.NewMemDB(), n, genesisBlock) + dbstore, tipState, err := chain.NewDBStore(chain.NewMemDB(), n, genesisBlock) if err != nil { t.Fatal(err) } - cm := chain.NewManager(dbstore, tip.State) + cm := chain.NewManager(dbstore, tipState) wm := walletutil.NewEphemeralWalletManager(cm) c, shutdown := runServer(cm, nil, wm) defer shutdown() @@ -383,11 +383,11 @@ func TestP2P(t *testing.T) { secondaryAddress := types.StandardUnlockHash(secondaryPrivateKey.PublicKey()) // create wallets - dbstore1, tip, err := chain.NewDBStore(chain.NewMemDB(), n, genesisBlock) + dbstore1, tipState, err := chain.NewDBStore(chain.NewMemDB(), n, genesisBlock) if err != nil { t.Fatal(err) } - cm1 := chain.NewManager(dbstore1, tip.State) + cm1 := chain.NewManager(dbstore1, tipState) wm1 := walletutil.NewEphemeralWalletManager(cm1) l1, err := net.Listen("tcp", ":0") if err != nil { @@ -413,11 +413,11 @@ func TestP2P(t *testing.T) { t.Fatal(err) } - dbstore2, tip, err := chain.NewDBStore(chain.NewMemDB(), n, genesisBlock) + dbstore2, tipState, err := chain.NewDBStore(chain.NewMemDB(), n, genesisBlock) if err != nil { t.Fatal(err) } - cm2 := chain.NewManager(dbstore2, tip.State) + cm2 := chain.NewManager(dbstore2, tipState) wm2 := walletutil.NewEphemeralWalletManager(cm2) l2, err := net.Listen("tcp", ":0") if err != nil { diff --git a/cmd/walletd/main.go b/cmd/walletd/main.go index c8e7b85..da11cc1 100644 --- a/cmd/walletd/main.go +++ b/cmd/walletd/main.go @@ -45,7 +45,7 @@ func check(context string, err error) { func getAPIPassword() string { apiPassword := os.Getenv("WALLETD_API_PASSWORD") if apiPassword != "" { - fmt.Println("Using WALLETD_API_PASSWORD environment variable.") + fmt.Println("env: Using WALLETD_API_PASSWORD environment variable") } else { fmt.Print("Enter API password: ") pw, err := term.ReadPassword(int(os.Stdin.Fd())) @@ -61,9 +61,11 @@ func getAPIPassword() string { func main() { log.SetFlags(0) - gatewayAddr := flag.String("addr", "localhost:9981", "p2p address to listen on") + gatewayAddr := flag.String("addr", ":9981", "p2p address to listen on") apiAddr := flag.String("http", "localhost:9980", "address to serve API on") dir := flag.String("dir", ".", "directory to store node state in") + network := flag.String("network", "mainnet", "network to connect to") + upnp := flag.Bool("upnp", true, "attempt to forward ports and discover IP with UPnP") flag.Parse() log.Println("walletd v0.1.0") @@ -79,7 +81,7 @@ func main() { log.Fatal(err) } - n, err := newNode(*gatewayAddr, *dir, true) + n, err := newNode(*gatewayAddr, *dir, *network, *upnp) if err != nil { log.Fatal(err) } diff --git a/cmd/walletd/node.go b/cmd/walletd/node.go index d7cdb2e..5cbe0ce 100644 --- a/cmd/walletd/node.go +++ b/cmd/walletd/node.go @@ -1,16 +1,23 @@ package main import ( + "context" + "errors" "log" "net" "path/filepath" + "strconv" + "time" bolt "go.etcd.io/bbolt" "go.sia.tech/core/chain" + "go.sia.tech/core/consensus" "go.sia.tech/core/gateway" + "go.sia.tech/core/types" "go.sia.tech/walletd/internal/syncerutil" "go.sia.tech/walletd/internal/walletutil" "go.sia.tech/walletd/syncer" + "lukechampine.com/upnp" ) var mainnetBootstrap = []string{ @@ -108,6 +115,11 @@ func (db *boltDB) Cancel() { db.tx = nil } +func (db *boltDB) Close() error { + db.Flush() + return db.db.Close() +} + type node struct { cm *chain.Manager s *syncer.Syncer @@ -116,40 +128,72 @@ type node struct { Start func() (stop func()) } -func newNode(addr, dir string, zen bool) (*node, error) { +func newNode(addr, dir string, chainNetwork string, useUPNP bool) (*node, error) { + var network *consensus.Network + var genesisBlock types.Block + var bootstrapPeers []string + switch chainNetwork { + case "mainnet": + network, genesisBlock = chain.Mainnet() + bootstrapPeers = mainnetBootstrap + case "zen": + network, genesisBlock = chain.TestnetZen() + bootstrapPeers = zenBootstrap + default: + return nil, errors.New("invalid network: must be one of 'mainnet' or 'zen'") + } + bdb, err := bolt.Open(filepath.Join(dir, "consensus.db"), 0600, nil) if err != nil { log.Fatal(err) } - network, genesisBlock := chain.Mainnet() - if zen { - network, genesisBlock = chain.TestnetZen() - } - dbstore, tip, err := chain.NewDBStore(&boltDB{db: bdb}, network, genesisBlock) + db := &boltDB{db: bdb} + dbstore, tipState, err := chain.NewDBStore(db, network, genesisBlock) if err != nil { return nil, err } - cm := chain.NewManager(dbstore, tip.State) + cm := chain.NewManager(dbstore, tipState) l, err := net.Listen("tcp", addr) if err != nil { return nil, err } + syncerAddr := l.Addr().String() + if useUPNP { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if d, err := upnp.Discover(ctx); err != nil { + log.Println("WARN: couldn't discover UPnP device:", err) + } else { + _, portStr, _ := net.SplitHostPort(addr) + port, _ := strconv.Atoi(portStr) + if !d.IsForwarded(uint16(port), "TCP") { + if err := d.Forward(uint16(port), "TCP", "walletd"); err != nil { + log.Println("WARN: couldn't forward port:", err) + } else { + log.Println("p2p: Forwarded port", port) + } + } + if ip, err := d.ExternalIP(); err != nil { + log.Println("WARN: couldn't determine external IP:", err) + } else { + log.Println("p2p: External IP is", ip) + syncerAddr = net.JoinHostPort(ip, portStr) + } + } + } + ps, err := syncerutil.NewJSONPeerStore(filepath.Join(dir, "peers.json")) if err != nil { log.Fatal(err) } - bootstrapPeers := mainnetBootstrap - if zen { - bootstrapPeers = zenBootstrap - } for _, peer := range bootstrapPeers { ps.AddPeer(peer) } header := gateway.Header{ GenesisID: genesisBlock.ID(), UniqueID: gateway.GenerateUniqueID(), - NetAddress: l.Addr().String(), + NetAddress: syncerAddr, } s := syncer.New(l, cm, ps, header, syncer.WithLogger(log.Default())) @@ -171,7 +215,7 @@ func newNode(addr, dir string, zen bool) (*node, error) { return func() { l.Close() <-ch - bdb.Close() + db.Close() } }, }, nil diff --git a/go.mod b/go.mod index 211885d..b843405 100644 --- a/go.mod +++ b/go.mod @@ -4,11 +4,12 @@ go 1.18 require ( go.etcd.io/bbolt v1.3.7 - go.sia.tech/core v0.1.12-0.20230915021325-3ca4ff703dc6 + go.sia.tech/core v0.1.12-0.20231021194448-f1e65eb9f0d0 go.sia.tech/jape v0.9.0 go.sia.tech/web/walletd v0.9.0 golang.org/x/term v0.6.0 lukechampine.com/frand v1.4.2 + lukechampine.com/upnp v0.2.0 ) require ( diff --git a/go.sum b/go.sum index 19b938b..8c02554 100644 --- a/go.sum +++ b/go.sum @@ -7,8 +7,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ= go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= -go.sia.tech/core v0.1.12-0.20230915021325-3ca4ff703dc6 h1:WYQDfzDWBcbr9t+nnatOShOUB4aD2XSu/UGz28XdCwU= -go.sia.tech/core v0.1.12-0.20230915021325-3ca4ff703dc6/go.mod h1:D17UWSn99SEfQnEaR9G9n6Kz9+BwqMoUgZ6Cl424LsQ= +go.sia.tech/core v0.1.12-0.20231021194448-f1e65eb9f0d0 h1:2nKOKa99g9h9m3hL5UortAbmnwuwXhDcTHIhzmqBae8= +go.sia.tech/core v0.1.12-0.20231021194448-f1e65eb9f0d0/go.mod h1:3EoY+rR78w1/uGoXXVqcYdwSjSJKuEMI5bL7WROA27Q= go.sia.tech/jape v0.9.0 h1:kWgMFqALYhLMJYOwWBgJda5ko/fi4iZzRxHRP7pp8NY= go.sia.tech/jape v0.9.0/go.mod h1:4QqmBB+t3W7cNplXPj++ZqpoUb2PeiS66RLpXmEGap4= go.sia.tech/mux v1.2.0 h1:ofa1Us9mdymBbGMY2XH/lSpY8itFsKIo/Aq8zwe+GHU= @@ -30,3 +30,5 @@ golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= lukechampine.com/frand v1.4.2 h1:RzFIpOvkMXuPMBb9maa4ND4wjBn71E1Jpf8BzJHMaVw= lukechampine.com/frand v1.4.2/go.mod h1:4S/TM2ZgrKejMcKMbeLjISpJMO+/eZ1zu3vYX9dtj3s= +lukechampine.com/upnp v0.2.0 h1:FWgYN50s8cTc5BQixLHy9uNgryw+qf3dd+oPS0I+vPQ= +lukechampine.com/upnp v0.2.0/go.mod h1:sOuF+fGSDKjpUm6QI0mfb82ScRrhj8bsqsD78O5nK1k= diff --git a/internal/walletutil/store.go b/internal/walletutil/store.go index 7e68293..0340464 100644 --- a/internal/walletutil/store.go +++ b/internal/walletutil/store.go @@ -209,16 +209,6 @@ func (s *EphemeralStore) ProcessChainApplyUpdate(cau *chain.ApplyUpdate, _ bool) events := wallet.AppliedEvents(cau.State, cau.Block, cau, s.ownsAddress) s.events = append(s.events, events...) - // update proofs - for id, sce := range s.sces { - cau.UpdateElementProof(&sce.StateElement) - s.sces[id] = sce - } - for id, sfe := range s.sfes { - cau.UpdateElementProof(&sfe.StateElement) - s.sfes[id] = sfe - } - // add/remove outputs cau.ForEachSiacoinElement(func(sce types.SiacoinElement, spent bool) { if s.ownsAddress(sce.SiacoinOutput.Address) { @@ -241,6 +231,16 @@ func (s *EphemeralStore) ProcessChainApplyUpdate(cau *chain.ApplyUpdate, _ bool) } }) + // update proofs + for id, sce := range s.sces { + cau.UpdateElementProof(&sce.StateElement) + s.sces[id] = sce + } + for id, sfe := range s.sfes { + cau.UpdateElementProof(&sfe.StateElement) + s.sfes[id] = sfe + } + s.tip = cau.State.Index return nil } @@ -254,15 +254,6 @@ func (s *EphemeralStore) ProcessChainRevertUpdate(cru *chain.RevertUpdate) error numEvents := len(wallet.AppliedEvents(cru.State, cru.Block, cru, s.ownsAddress)) s.events = s.events[:len(s.events)-numEvents] - for id, sce := range s.sces { - cru.UpdateElementProof(&sce.StateElement) - s.sces[id] = sce - } - for id, sfe := range s.sfes { - cru.UpdateElementProof(&sfe.StateElement) - s.sfes[id] = sfe - } - cru.ForEachSiacoinElement(func(sce types.SiacoinElement, spent bool) { if s.ownsAddress(sce.SiacoinOutput.Address) { if !spent { @@ -283,6 +274,17 @@ func (s *EphemeralStore) ProcessChainRevertUpdate(cru *chain.RevertUpdate) error } } }) + + // update proofs + for id, sce := range s.sces { + cru.UpdateElementProof(&sce.StateElement) + s.sces[id] = sce + } + for id, sfe := range s.sfes { + cru.UpdateElementProof(&sfe.StateElement) + s.sfes[id] = sfe + } + s.tip = cru.State.Index return nil }