├── .gitignore ├── README.md ├── api ├── hub.go ├── ws.go └── api.go ├── LICENSE.md ├── cmd ├── resolver │ └── main.go └── ens-lite │ └── main.go ├── cli └── cli.go └── ens.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | .idea/ 26 | *.iml 27 | .gx/ 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ens-lite 2 | Resolve ENS names into DNS records or Ethereum addresses without downloading full Ethereum blockchain. The resolver syncs the blockchain headers and verifies names against 3 | the state tree. 4 | 5 | Examples (make sure go-ethereum is installed first): 6 | ``` 7 | cd $GOPATH/src/github.com/cpacia/ens-lite/cmd/ens-lite 8 | go install 9 | ens-lite start 10 | 11 | ens-lite resolve somename.eth 12 | ens-lite lookup somename.eth 13 | ens-lite address somename.eth 14 | ``` 15 | 16 | Or: 17 | ``` 18 | curl http://localhost:31313/resolver/dns/somename.eth 19 | curl http://localhost:31313/resolver/dns/somename.eth?lookup=true 20 | curl http://localhost:31313/resolver/address/somename.eth 21 | ``` 22 | 23 | Or as a library: 24 | ```go 25 | client, _ = ens.NewENSLiteClient("/path/to/datadir") 26 | go client.start() 27 | client.ResolveDNS("somename.eth") 28 | client.ResolveAddress("somename.eth") 29 | ``` 30 | -------------------------------------------------------------------------------- /api/hub.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | type hub struct { 4 | // Registered connections 5 | connections map[*connection]bool 6 | 7 | // Inbound messages from the connections 8 | Broadcast chan []byte 9 | 10 | // Register requests from the connections 11 | register chan *connection 12 | 13 | // Unregister requests from connections 14 | unregister chan *connection 15 | } 16 | 17 | func newHub() *hub { 18 | return &hub{ 19 | Broadcast: make(chan []byte), 20 | register: make(chan *connection), 21 | unregister: make(chan *connection), 22 | connections: make(map[*connection]bool), 23 | } 24 | } 25 | 26 | func (h *hub) run() { 27 | for { 28 | select { 29 | case c := <-h.register: 30 | h.connections[c] = true 31 | case c := <-h.unregister: 32 | if _, ok := h.connections[c]; ok { 33 | delete(h.connections, c) 34 | close(c.send) 35 | } 36 | case m := <-h.Broadcast: 37 | for c := range h.connections { 38 | select { 39 | case c.send <- m: 40 | default: 41 | delete(h.connections, c) 42 | close(c.send) 43 | } 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Chris Pacia 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /cmd/resolver/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/cpacia/ens-lite" 5 | "github.com/cpacia/ens-lite/api" 6 | "github.com/mitchellh/go-homedir" 7 | "os" 8 | "os/signal" 9 | "path/filepath" 10 | "runtime" 11 | ) 12 | 13 | var client *ens.ENSLiteClient 14 | 15 | func run() error { 16 | var err error 17 | var dataDir string 18 | path, err := getRepoPath() 19 | if err != nil { 20 | return err 21 | } 22 | dataDir = path 23 | client, err = ens.NewENSLiteClient(dataDir) 24 | if err != nil { 25 | return err 26 | } 27 | go client.Start() 28 | api.ServeAPI(client) 29 | return nil 30 | } 31 | 32 | func main() { 33 | c := make(chan os.Signal, 1) 34 | signal.Notify(c, os.Interrupt) 35 | go func() { 36 | for range c { 37 | client.Stop() 38 | os.Exit(1) 39 | } 40 | }() 41 | run() 42 | } 43 | 44 | /* Returns the directory to store repo data in. 45 | It depends on the OS and whether or not we are on testnet. */ 46 | func getRepoPath() (string, error) { 47 | // Set default base path and directory name 48 | path := "~" 49 | directoryName := "ens" 50 | 51 | // Override OS-specific names 52 | switch runtime.GOOS { 53 | case "linux": 54 | directoryName = ".ens" 55 | case "darwin": 56 | path = "~/Library/Application Support" 57 | } 58 | 59 | // Join the path and directory name, then expand the home path 60 | fullPath, err := homedir.Expand(filepath.Join(path, directoryName)) 61 | if err != nil { 62 | return "", err 63 | } 64 | 65 | // Return the shortest lexical representation of the path 66 | return filepath.Clean(fullPath), nil 67 | } 68 | -------------------------------------------------------------------------------- /api/ws.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/cpacia/ens-lite" 5 | "github.com/gorilla/websocket" 6 | "net/http" 7 | "time" 8 | "fmt" 9 | ) 10 | 11 | type connection struct { 12 | // The websocket connection 13 | ws *websocket.Conn 14 | 15 | // Buffered channel of outbound messages 16 | send chan []byte 17 | 18 | // The hub 19 | h *hub 20 | } 21 | 22 | func (c *connection) reader() { 23 | for { 24 | _, message, err := c.ws.ReadMessage() 25 | if err != nil { 26 | break 27 | } 28 | 29 | // Just echo for now until we set up the API 30 | c.h.Broadcast <- message 31 | } 32 | c.ws.Close() 33 | } 34 | 35 | func (c *connection) writer() { 36 | for message := range c.send { 37 | err := c.ws.WriteMessage(websocket.TextMessage, message) 38 | if err != nil { 39 | break 40 | } 41 | } 42 | c.ws.Close() 43 | } 44 | 45 | var upgrader = &websocket.Upgrader{ 46 | ReadBufferSize: 1024, 47 | WriteBufferSize: 1024, 48 | CheckOrigin: func(r *http.Request) bool { return true }, 49 | } 50 | 51 | var handler wsHandler 52 | 53 | type wsHandler struct { 54 | h *hub 55 | client *ens.ENSLiteClient 56 | } 57 | 58 | func newWSAPIHandler(ensClient *ens.ENSLiteClient) *wsHandler { 59 | hub := newHub() 60 | go hub.run() 61 | handler = wsHandler{ 62 | h: hub, 63 | client: ensClient, 64 | } 65 | go handler.serveSyncProgress() 66 | return &handler 67 | } 68 | 69 | func (wsh wsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 70 | ws, err := upgrader.Upgrade(w, r, nil) 71 | if err != nil { 72 | return 73 | } 74 | c := &connection{send: make(chan []byte, 256), ws: ws, h: wsh.h} 75 | c.h.register <- c 76 | defer func() { c.h.unregister <- c }() 77 | go c.writer() 78 | c.reader() 79 | } 80 | 81 | func (wsh wsHandler) serveSyncProgress() { 82 | time.Sleep(time.Second*5) 83 | t := time.NewTicker(time.Millisecond * 100) 84 | for range t.C { 85 | sp, err := wsh.client.SyncProgress() 86 | if err != nil && err == ens.ErrorNodeInitializing { 87 | wsh.h.Broadcast <- []byte("Node initializing...") 88 | continue 89 | } else if sp == nil { 90 | wsh.h.Broadcast <- []byte("Fully synced") 91 | return 92 | } 93 | start := sp.StartingBlock 94 | highest := sp.HighestBlock 95 | current := sp.CurrentBlock 96 | 97 | total := highest - start 98 | downloaded := current - start 99 | 100 | progress := float64(downloaded) / float64(total) 101 | wsh.h.Broadcast <- []byte(fmt.Sprintf(`%.3f`, progress)) 102 | } 103 | } -------------------------------------------------------------------------------- /cmd/ens-lite/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/cpacia/ens-lite" 6 | "github.com/cpacia/ens-lite/api" 7 | "github.com/cpacia/ens-lite/cli" 8 | "github.com/jessevdk/go-flags" 9 | "github.com/mitchellh/go-homedir" 10 | "os" 11 | "os/signal" 12 | "path/filepath" 13 | "runtime" 14 | ) 15 | 16 | const VERSION = "0.1.0" 17 | 18 | var parser = flags.NewParser(nil, flags.Default) 19 | 20 | type Start struct { 21 | DataDir string `short:"d" long:"datadir" description:"specify the data directory to be used"` 22 | } 23 | type Version struct{} 24 | 25 | var start Start 26 | var version Version 27 | var client *ens.ENSLiteClient 28 | 29 | func main() { 30 | c := make(chan os.Signal, 1) 31 | signal.Notify(c, os.Interrupt) 32 | go func() { 33 | for range c { 34 | fmt.Println("ens-lite shutting down...") 35 | client.Stop() 36 | os.Exit(1) 37 | } 38 | }() 39 | parser.AddCommand("start", 40 | "start the resolver", 41 | "The start command starts the resolver daemon", 42 | &start) 43 | parser.AddCommand("version", 44 | "print the version number", 45 | "Print the version number and exit", 46 | &version) 47 | cli.SetupCli(parser) 48 | if _, err := parser.Parse(); err != nil { 49 | os.Exit(1) 50 | } 51 | } 52 | 53 | func (x *Version) Execute(args []string) error { 54 | fmt.Println(VERSION) 55 | return nil 56 | } 57 | 58 | func (x *Start) Execute(args []string) error { 59 | var err error 60 | var dataDir string 61 | if x.DataDir == "" { 62 | path, err := getRepoPath() 63 | if err != nil { 64 | return err 65 | } 66 | dataDir = path 67 | } else { 68 | dataDir = x.DataDir 69 | } 70 | client, err = ens.NewENSLiteClient(dataDir) 71 | if err != nil { 72 | return err 73 | } 74 | fmt.Println("Ens Resolver Running...") 75 | go client.Start() 76 | api.ServeAPI(client) 77 | return nil 78 | } 79 | 80 | /* Returns the directory to store repo data in. 81 | It depends on the OS and whether or not we are on testnet. */ 82 | func getRepoPath() (string, error) { 83 | // Set default base path and directory name 84 | path := "~" 85 | directoryName := "ens" 86 | 87 | // Override OS-specific names 88 | switch runtime.GOOS { 89 | case "linux": 90 | directoryName = ".ens" 91 | case "darwin": 92 | path = "~/Library/Application Support" 93 | } 94 | 95 | // Join the path and directory name, then expand the home path 96 | fullPath, err := homedir.Expand(filepath.Join(path, directoryName)) 97 | if err != nil { 98 | return "", err 99 | } 100 | 101 | // Return the shortest lexical representation of the path 102 | return filepath.Clean(fullPath), nil 103 | } 104 | -------------------------------------------------------------------------------- /cli/cli.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "fmt" 5 | "github.com/cpacia/ens-lite/api" 6 | "github.com/jessevdk/go-flags" 7 | "io/ioutil" 8 | "net/http" 9 | "time" 10 | ) 11 | 12 | func SetupCli(parser *flags.Parser) { 13 | // Add commands to parser 14 | parser.AddCommand("stop", 15 | "stop the resover", 16 | "The stop command disconnects from peers and shuts down the resolver", 17 | &stop) 18 | parser.AddCommand("resolve", 19 | "resolve a name", 20 | "Resolve a name. The merkle proofs will be validated automatically.", 21 | &resolve) 22 | parser.AddCommand("address", 23 | "resolve an address", 24 | "Resolve an ethereum address for a name.", 25 | &address) 26 | parser.AddCommand("lookup", 27 | "lookup DNS records", 28 | "Fetch the full DNS record for a name", 29 | &lookup) 30 | } 31 | 32 | type Stop struct{} 33 | 34 | var stop Stop 35 | 36 | func (x *Stop) Execute(args []string) error { 37 | client := &http.Client{ 38 | Timeout: 60 * time.Second, 39 | } 40 | _, err := client.Post(api.Addr, "text/plain", nil) 41 | if err != nil { 42 | return err 43 | } 44 | fmt.Println("Ens Resolver Stopping...") 45 | return nil 46 | } 47 | 48 | type Resolve struct{} 49 | 50 | var resolve Resolve 51 | 52 | func (x *Resolve) Execute(args []string) error { 53 | client := &http.Client{ 54 | Timeout: 60 * time.Second, 55 | } 56 | resp, err := client.Get("http://" + api.Addr + "/resolver/dns/" + args[0]) 57 | if err != nil || resp.StatusCode != http.StatusOK { 58 | fmt.Println("Not found") 59 | return err 60 | } 61 | h, err := ioutil.ReadAll(resp.Body) 62 | if err != nil { 63 | return err 64 | } 65 | fmt.Println(string(h)) 66 | return nil 67 | } 68 | 69 | type Address struct{} 70 | 71 | var address Address 72 | 73 | func (x *Address) Execute(args []string) error { 74 | client := &http.Client{ 75 | Timeout: 60 * time.Second, 76 | } 77 | resp, err := client.Get("http://" + api.Addr + "/resolver/address/" + args[0]) 78 | if err != nil || resp.StatusCode != http.StatusOK { 79 | fmt.Println("Not found") 80 | return err 81 | } 82 | h, err := ioutil.ReadAll(resp.Body) 83 | if err != nil { 84 | return err 85 | } 86 | fmt.Println(string(h)) 87 | return nil 88 | } 89 | 90 | type Lookup struct{} 91 | 92 | var lookup Lookup 93 | 94 | func (x *Lookup) Execute(args []string) error { 95 | client := &http.Client{ 96 | Timeout: 60 * time.Second, 97 | } 98 | resp, err := client.Get("http://" + api.Addr + "/resolver/dns/" + args[0] + "?lookup=true") 99 | if err != nil || resp.StatusCode != http.StatusOK { 100 | return err 101 | } 102 | h, err := ioutil.ReadAll(resp.Body) 103 | if err != nil { 104 | return err 105 | } 106 | fmt.Println(string(h)) 107 | return nil 108 | } 109 | -------------------------------------------------------------------------------- /api/api.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "github.com/cpacia/ens-lite" 6 | "github.com/miekg/dns" 7 | "net/http" 8 | "path" 9 | "time" 10 | "strconv" 11 | "strings" 12 | ) 13 | 14 | const Addr = "127.0.0.1:31313" 15 | 16 | var client *apiClient 17 | 18 | type apiClient struct { 19 | ensClient *ens.ENSLiteClient 20 | cache map[string]cachedRecord 21 | } 22 | 23 | type cachedRecord struct { 24 | rr dns.RR 25 | expiration time.Time 26 | } 27 | 28 | func ServeAPI(ensClient *ens.ENSLiteClient) error { 29 | topMux := http.NewServeMux() 30 | handler := new(resolverHandler) 31 | 32 | topMux.Handle("/resolver/", handler) 33 | topMux.Handle("/ws", newWSAPIHandler(ensClient)) 34 | srv := &http.Server{Addr: Addr, Handler: topMux} 35 | handler.server = srv 36 | 37 | client = &apiClient{ensClient, make(map[string]cachedRecord)} 38 | err := srv.ListenAndServe() 39 | if err != nil { 40 | return err 41 | } 42 | return nil 43 | } 44 | 45 | type resolverHandler struct { 46 | server *http.Server 47 | } 48 | 49 | func resolve(w http.ResponseWriter, r *http.Request) { 50 | urlPath, name := path.Split(r.URL.Path) 51 | _, queryType := path.Split(urlPath[:len(urlPath)-1]) 52 | 53 | if strings.ToLower(queryType) == "dns" { 54 | lookup := r.URL.Query().Get("lookup") 55 | l, _ := strconv.ParseBool(lookup) 56 | record, ok := client.cache[name] 57 | if ok { 58 | if time.Now().Before(record.expiration) { 59 | fmt.Fprint(w, record.rr.(*dns.A).A.String()) 60 | return 61 | } else { 62 | delete(client.cache, name) 63 | } 64 | } 65 | 66 | resp, err := client.ensClient.ResolveDNS(name) 67 | if err != nil && err == ens.ErrorBlockchainSyncing { 68 | w.WriteHeader(http.StatusServiceUnavailable) 69 | return 70 | } else if err != nil || len(resp) == 0 { 71 | w.WriteHeader(http.StatusNotFound) 72 | return 73 | } 74 | client.cache[name] = cachedRecord{resp[0], time.Now().Add(time.Duration(resp[0].Header().Ttl))} 75 | if l { 76 | var ret string 77 | for i, rec := range resp { 78 | ret += rec.String() 79 | if i != len(resp) - 1 { 80 | ret += "\n" 81 | } 82 | } 83 | fmt.Fprint(w, ret) 84 | } else { 85 | fmt.Fprint(w, resp[0].(*dns.A).A.String()) 86 | } 87 | } else if strings.ToLower(queryType) == "address" { 88 | resp, err := client.ensClient.ResolveAddress(name) 89 | if err != nil && err == ens.ErrorBlockchainSyncing { 90 | w.WriteHeader(http.StatusServiceUnavailable) 91 | return 92 | } else if err != nil { 93 | w.WriteHeader(http.StatusNotFound) 94 | return 95 | } 96 | fmt.Fprint(w, resp.Hex()) 97 | } 98 | } 99 | 100 | func (rh resolverHandler) shutdown() { 101 | client.ensClient.Stop() 102 | rh.server.Close() 103 | } 104 | 105 | func (rh resolverHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 106 | switch r.Method { 107 | case "GET": 108 | resolve(w, r) 109 | case "POST": 110 | rh.shutdown() 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /ens.go: -------------------------------------------------------------------------------- 1 | package ens 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/ethereum/go-ethereum" 7 | "github.com/ethereum/go-ethereum/common" 8 | "github.com/ethereum/go-ethereum/eth" 9 | "github.com/ethereum/go-ethereum/eth/downloader" 10 | "github.com/ethereum/go-ethereum/ethclient" 11 | "github.com/ethereum/go-ethereum/ethstats" 12 | "github.com/ethereum/go-ethereum/les" 13 | "github.com/ethereum/go-ethereum/mobile" 14 | "github.com/ethereum/go-ethereum/p2p" 15 | "github.com/ethereum/go-ethereum/p2p/discv5" 16 | "path/filepath" 17 | ens "github.com/Arachnid/ensdns/ens" 18 | 19 | "context" 20 | "errors" 21 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 22 | "github.com/ethereum/go-ethereum/core" 23 | "github.com/ethereum/go-ethereum/node" 24 | "github.com/ethereum/go-ethereum/p2p/nat" 25 | "github.com/ethereum/go-ethereum/params" 26 | ens2 "github.com/ethereum/go-ethereum/contracts/ens" 27 | "github.com/miekg/dns" 28 | ) 29 | 30 | var ErrorBlockchainSyncing error = errors.New("Cannot resolve names while the chain is syncing") 31 | var ErrorNodeInitializing error = errors.New("Node is still initializing") 32 | var ErrorNoRecords error = errors.New("No DNS records found") 33 | 34 | type ENSLiteClient struct { 35 | node *node.Node 36 | dnsService *ens.Registry 37 | addrService *ens2.ENS 38 | } 39 | 40 | func NewENSLiteClient(dataDir string) (*ENSLiteClient, error) { 41 | config := geth.NewNodeConfig() 42 | config.MaxPeers = 25 43 | config.BootstrapNodes = geth.FoundationBootnodes() 44 | // Create the empty networking stack 45 | 46 | v5Nodes := make([]*discv5.Node, len(params.DiscoveryV5Bootnodes)) 47 | for i, url := range params.DiscoveryV5Bootnodes { 48 | v5Nodes[i] = discv5.MustParseNode(url) 49 | } 50 | 51 | nodeConf := &node.Config{ 52 | Name: "ens-lite", 53 | Version: params.Version, 54 | DataDir: dataDir, 55 | KeyStoreDir: filepath.Join(dataDir, "keystore"), // Mobile should never use internal keystores! 56 | P2P: p2p.Config{ 57 | NoDiscovery: true, 58 | DiscoveryV5: true, 59 | DiscoveryV5Addr: ":0", 60 | BootstrapNodesV5: v5Nodes, 61 | ListenAddr: ":0", 62 | NAT: nat.Any(), 63 | MaxPeers: config.MaxPeers, 64 | }, 65 | } 66 | rawStack, err := node.New(nodeConf) 67 | if err != nil { 68 | return nil, err 69 | } 70 | 71 | var genesis *core.Genesis 72 | enc, err := json.Marshal(core.DefaultTestnetGenesisBlock()) 73 | if err != nil { 74 | return nil, err 75 | } 76 | if config.EthereumGenesis != "" { 77 | // Parse the user supplied genesis spec if not mainnet 78 | genesis = new(core.Genesis) 79 | if err := json.Unmarshal([]byte(config.EthereumGenesis), genesis); err != nil { 80 | return nil, fmt.Errorf("invalid genesis spec: %v", err) 81 | } 82 | // If we have the testnet, hard code the chain configs too 83 | if config.EthereumGenesis == string(enc) { 84 | genesis.Config = params.TestnetChainConfig 85 | if config.EthereumNetworkID == 1 { 86 | config.EthereumNetworkID = 3 87 | } 88 | } 89 | } 90 | // Register the Ethereum protocol if requested 91 | if config.EthereumEnabled { 92 | ethConf := eth.DefaultConfig 93 | ethConf.Genesis = genesis 94 | ethConf.SyncMode = downloader.LightSync 95 | ethConf.NetworkId = uint64(config.EthereumNetworkID) 96 | ethConf.DatabaseCache = config.EthereumDatabaseCache 97 | if err := rawStack.Register(func(ctx *node.ServiceContext) (node.Service, error) { 98 | return les.New(ctx, ðConf) 99 | }); err != nil { 100 | return nil, fmt.Errorf("ethereum init: %v", err) 101 | } 102 | // If netstats reporting is requested, do it 103 | if config.EthereumNetStats != "" { 104 | if err := rawStack.Register(func(ctx *node.ServiceContext) (node.Service, error) { 105 | var lesServ *les.LightEthereum 106 | ctx.Service(&lesServ) 107 | 108 | return ethstats.New(config.EthereumNetStats, nil, lesServ) 109 | }); err != nil { 110 | return nil, fmt.Errorf("netstats init: %v", err) 111 | } 112 | } 113 | } 114 | return &ENSLiteClient{rawStack, nil, nil}, nil 115 | } 116 | 117 | // Start the SPV node 118 | func (self *ENSLiteClient) Start() { 119 | self.node.Start() 120 | } 121 | 122 | // Stop the SPV node 123 | func (self *ENSLiteClient) Stop() { 124 | self.node.Stop() 125 | } 126 | 127 | // Resolve a name. The merkle proofs will be validated automatically. 128 | func (self *ENSLiteClient) ResolveDNS(name string) ([]dns.RR, error) { 129 | var rr []dns.RR 130 | rpc, err := self.node.Attach() 131 | if err != nil { 132 | return rr, err 133 | } 134 | api := ethclient.NewClient(rpc) 135 | sp, _ := api.SyncProgress(context.Background()) 136 | if sp != nil { 137 | return rr, ErrorBlockchainSyncing 138 | } 139 | if self.dnsService == nil { 140 | reg, err := ens.New(api, common.HexToAddress("0x314159265dD8dbb310642f98f50C066173C1259b"), bind.TransactOpts{}) 141 | if err != nil { 142 | return rr, err 143 | } 144 | self.dnsService = reg 145 | } 146 | resolver, err := self.dnsService.GetResolver(name) 147 | if err != nil { 148 | return rr, err 149 | } 150 | return resolver.GetRRs() 151 | } 152 | 153 | func (self *ENSLiteClient) ResolveAddress(name string) (addr common.Hash, err error) { 154 | rpc, err := self.node.Attach() 155 | if err != nil { 156 | return addr, err 157 | } 158 | api := ethclient.NewClient(rpc) 159 | sp, _ := api.SyncProgress(context.Background()) 160 | if sp != nil { 161 | return addr, ErrorBlockchainSyncing 162 | } 163 | if self.addrService == nil { 164 | reg, err := ens2.NewENS(&bind.TransactOpts{}, common.HexToAddress("0x314159265dD8dbb310642f98f50C066173C1259b"), api) 165 | if err != nil { 166 | return addr, err 167 | } 168 | self.addrService = reg 169 | } 170 | return self.addrService.Resolve(name) 171 | } 172 | 173 | func (self *ENSLiteClient) SyncProgress() (*ethereum.SyncProgress, error) { 174 | if self.node == nil { 175 | return nil, ErrorNodeInitializing 176 | } 177 | rpc, err := self.node.Attach() 178 | if err != nil { 179 | return nil, err 180 | } 181 | api := ethclient.NewClient(rpc) 182 | return api.SyncProgress(context.Background()) 183 | } 184 | --------------------------------------------------------------------------------