Skip to content

Commit

Permalink
Merge #103: Switch to rpcclient based on current upstream Conformal
Browse files Browse the repository at this point in the history
2b1eed8 Switch to new rpcclient based on latest upstream Conformal (JeremyRand)

Pull request description:

  Namecoin's fork of Conformal's RPC client library is ancient and unmaintained; switching to upstream eliminates a major source of potential bugs (and known bugs, e.g. the ConsensusJ and Electrum compatibility bugs that we had to patch).

  Fixes #9

  TODO:

  - [ ] Wait for Conformal to merge btcsuite/btcd#1457
  - [ ] Wait for Conformal to merge btcsuite/btcd#1460
  - [x] Push `name_show` support for `ncjson` and `ncrpcclient`
  - [x] Implement cookie authentication
  - [x] Test cookie authentication

ACKs for commit 2b1eed:

Tree-SHA512: 157780613661af240d83a78d66386c66fcfeed0700088d263a20389c45b200db1b651ea985f345de2ac2f250bdafcc4cd5901579e9f5a97ddc13e77e7a7bcf39
  • Loading branch information
JeremyRand committed Oct 27, 2019
2 parents 1e2eea5 + 2b1eed8 commit 4a1381c
Show file tree
Hide file tree
Showing 9 changed files with 111 additions and 291 deletions.
11 changes: 4 additions & 7 deletions backend/backend.go
Expand Up @@ -18,7 +18,7 @@ import "time"
// Provides an abstract zone file for the Namecoin .bit TLD.
type Backend struct {
//s *Server
nc namecoin.Conn
nc *namecoin.Client
// caches map keys are stream isolation ID's; items are of type *Domain
caches map[string]*lru.Cache
cacheMutex sync.Mutex
Expand All @@ -29,7 +29,7 @@ var log, Log = xlog.New("ncdns.backend")

// Backend configuration.
type Config struct {
NamecoinConn namecoin.Conn
NamecoinConn *namecoin.Client

// Timeout (in milliseconds) for Namecoin RPC requests
NamecoinTimeout int
Expand Down Expand Up @@ -63,9 +63,6 @@ func New(cfg *Config) (backend *Backend, err error) {

b.cfg = *cfg
b.nc = b.cfg.NamecoinConn
//b.nc.Username = cfg.RPCUsername
//b.nc.Password = cfg.RPCPassword
//b.nc.Server = cfg.RPCAddress

b.caches = make(map[string]*lru.Cache)

Expand Down Expand Up @@ -355,13 +352,13 @@ func (b *Backend) resolveName(name, streamIsolationID string) (jsonValue string,
return fv, nil
}

// The btcjson package has quite a long timeout, far in excess of standard
// The rpcclient package has quite a long timeout, far in excess of standard
// DNS timeouts. We need to return an error response rapidly if we can't
// query the backend. Be generous with the timeout as responses from the
// Namecoin JSON-RPC seem sluggish sometimes.
result := make(chan struct{}, 1)
go func() {
jsonValue, err = b.nc.Query(name, streamIsolationID)
jsonValue, err = b.nc.NameQuery(name, streamIsolationID)
log.Errore(err, "failed to query namecoin")
result <- struct{}{}
}()
Expand Down
223 changes: 29 additions & 194 deletions namecoin/namecoin.go
@@ -1,220 +1,55 @@
package namecoin

// btcjson had to be modified a bit to get correct error reporting.
import (
extratypes "github.com/hlandau/ncbtcjsontypes"
"github.com/hlandauf/btcjson"
"gopkg.in/hlandau/madns.v2/merr"

"expvar"
"fmt"
"sync/atomic"
)

var cQueryCalls = expvar.NewInt("ncdns.namecoin.numQueryCalls")
var cSyncCalls = expvar.NewInt("ncdns.namecoin.numSyncCalls")
var cFilterCalls = expvar.NewInt("ncdns.namecoin.numFilterCalls")
var cScanCalls = expvar.NewInt("ncdns.namecoin.numScanCalls")
var cCurHeightCalls = expvar.NewInt("ncdns.namecoin.numCurHeightCalls")

// Used for generating IDs for JSON-RPC requests.
var idCounter int32

func newID() int32 {
return atomic.AddInt32(&idCounter, 1)
}

// Used to query a Namecoin JSON-RPC interface. Initialize the struct with a
// username, password, and address (hostname:port).
type Conn struct {
Username string
Password string

// If set, this is called to obtain the username and password instead of
// using the Username and Password fields.
GetAuth func() (username, password string, err error)

Server string
}
"github.com/namecoin/btcd/btcjson"
"github.com/namecoin/btcd/rpcclient"
"gopkg.in/hlandau/madns.v2/merr"

func (nc *Conn) getAuth() (username string, password string, err error) {
if nc.GetAuth == nil {
return nc.Username, nc.Password, nil
}
"github.com/namecoin/ncrpcclient"
)

return nc.GetAuth()
// Client represents an ncrpcclient.Client with an additional DNS-friendly
// convenience wrapper around NameShow.
type Client struct {
*ncrpcclient.Client
}

func (nc *Conn) rpcSend(cmd btcjson.Cmd) (btcjson.Reply, error) {
username, password, err := nc.getAuth()
func New(config *rpcclient.ConnConfig, ntfnHandlers *rpcclient.NotificationHandlers) (*Client, error) {
ncClient, err := ncrpcclient.New(config, ntfnHandlers)
if err != nil {
return btcjson.Reply{}, err
return nil, err
}

return btcjson.RpcSend(username, password, nc.Server, cmd)
return &Client{ncClient}, nil
}

// Query the Namecoin daemon for a Namecoin domain (e.g. d/example).
// If the domain exists, returns the value stored in Namecoin, which should be JSON.
// Note that this will return domain data even if the domain is expired.
func (nc *Conn) Query(name string, streamIsolationID string) (v string, err error) {
cQueryCalls.Add(1)

// NameQuery returns the value of a name. If the name doesn't exist, the error
// returned will be merr.ErrNoSuchDomain.
func (c *Client) NameQuery(name string, streamIsolationID string) (string, error) {
// TODO: Pass stream isolation ID to namecoind, and remove this error
if streamIsolationID != "" {
return "", fmt.Errorf("Stream isolation ID '%s' is not yet passed to namecoind", streamIsolationID)
}

cmd, err := extratypes.NewNameShowCmd(newID(), name)
if err != nil {
//log.Info("NC NEWCMD ", err)
return "", err
}

r, err := nc.rpcSend(cmd)
if err != nil {
return "", err
}

if r.Error != nil {
//log.Info("RPC error: ", r.Error)
if r.Error.Code == -4 {
return "", merr.ErrNoSuchDomain
}
return "", r.Error
}

if r.Result == nil {
//log.Info("NC NILRESULT")
return "", fmt.Errorf("got nil result")
}

if nsr, ok := r.Result.(*extratypes.NameShowReply); ok {
//log.Info("NC OK")
return nsr.Value, nil
}

//log.Info("NC BADREPLY")
return "", fmt.Errorf("bad reply")
}

var ErrSyncNoSuchBlock = fmt.Errorf("no block exists with given hash")

const rpcInvalidAddressOrKey = -5

func (nc *Conn) Sync(hash string, count int, wait bool) ([]extratypes.NameSyncEvent, error) {
cSyncCalls.Add(1)

cmd, err := extratypes.NewNameSyncCmd(newID(), hash, count, wait)
if err != nil {
return nil, err
}

r, err := nc.rpcSend(cmd)
nameData, err := c.NameShow(name)
if err != nil {
return nil, err
}

if r.Error != nil {
if r.Error.Code == rpcInvalidAddressOrKey {
return nil, ErrSyncNoSuchBlock
if jerr, ok := err.(*btcjson.RPCError); ok {
if jerr.Code == btcjson.ErrRPCWallet {
// ErrRPCWallet from name_show indicates that
// the name does not exist.
return "", merr.ErrNoSuchDomain
}
}
return nil, r.Error
}

if r.Result == nil {
return nil, fmt.Errorf("got nil result")
}

if nsr, ok := r.Result.(extratypes.NameSyncReply); ok {
return []extratypes.NameSyncEvent(nsr), nil
}

return nil, fmt.Errorf("bad reply")
}

func (nc *Conn) CurHeight() (int, error) {
cCurHeightCalls.Add(1)

cmd, err := btcjson.NewGetInfoCmd(newID())
if err != nil {
return 0, err
}

r, err := nc.rpcSend(cmd)
if err != nil {
return 0, err
}

if r.Error != nil {
return 0, r.Error
}

if r.Result == nil {
return 0, fmt.Errorf("got nil result")
}

if rep, ok := r.Result.(*btcjson.InfoResult); ok {
return int(rep.Blocks), nil
}

return 0, fmt.Errorf("bad reply")
}

func (nc *Conn) Filter(regexp string, maxage, from, count int) (names []extratypes.NameFilterItem, err error) {
cFilterCalls.Add(1)

cmd, err := extratypes.NewNameFilterCmd(newID(), regexp, maxage, from, count)
if err != nil {
return nil, err
}

r, err := nc.rpcSend(cmd)
if err != nil {
return nil, err
}

if r.Error != nil {
return nil, r.Error
}

if r.Result == nil {
return nil, fmt.Errorf("got nil result")
}

if nsr, ok := r.Result.(extratypes.NameFilterReply); ok {
return []extratypes.NameFilterItem(nsr), nil
}

return nil, fmt.Errorf("bad reply")
}

func (nc *Conn) Scan(from string, count int) (names []extratypes.NameFilterItem, err error) {
cScanCalls.Add(1)

cmd, err := extratypes.NewNameScanCmd(newID(), from, count)
if err != nil {
return nil, err
}

r, err := nc.rpcSend(cmd)
if err != nil {
return nil, err
}

if r.Error != nil {
return nil, r.Error
// Some error besides NXDOMAIN happened; pass that error
// through unaltered.
return "", err
}

if r.Result == nil {
return nil, fmt.Errorf("got nil result")
}
// TODO: check the "value_error" field for errors and report those to the caller.

if nsr, ok := r.Result.(extratypes.NameFilterReply); ok {
return []extratypes.NameFilterItem(nsr), nil
}

return nil, fmt.Errorf("bad reply")
// We got the name data. Return the value.
return nameData.Value, nil
}

// © 2014 Hugo Landau <hlandau@devever.net> GPLv3 or later
32 changes: 26 additions & 6 deletions ncdt/ncdt.go
Expand Up @@ -7,12 +7,14 @@ import "fmt"
import "os"
import "strconv"
import "io/ioutil"
import "github.com/namecoin/btcd/rpcclient"
import "github.com/namecoin/ncdns/util"

var rpchost = flag.String("rpchost", "", "Namecoin RPC host:port")
var rpcuser = flag.String("rpcuser", "", "Namecoin RPC username")
var rpcpass = flag.String("rpcpass", "", "Namecoin RPC password")
var conn namecoin.Conn
var rpccookiepath = flag.String("rpccookiepath", "", "Namecoin RPC cookie path (used if password is unspecified)")
var conn *namecoin.Client

func usage() {
fmt.Fprintf(os.Stderr, "Usage: ncdt [options] <d/example> <JSON value> [<d/imported-example> <JSON value> ...]\n")
Expand All @@ -21,6 +23,7 @@ func usage() {
fmt.Fprintf(os.Stderr, " -rpchost=host:port Namecoin RPC server address } only required for RPC retrieval\n")
fmt.Fprintf(os.Stderr, " -rpcuser=username Namecoin RPC username }\n")
fmt.Fprintf(os.Stderr, " -rpcpass=password Namecoin RPC password }\n")
fmt.Fprintf(os.Stderr, " -rpccookiepath=path Namecoin RPC cookie path }\n")
os.Exit(2)
}

Expand All @@ -41,7 +44,7 @@ func translateValue(k, v string) (string, error) {

f = os.NewFile(uintptr(n), "-")
} else if len(v) == 1 {
return conn.Query(k, "")
return conn.NameQuery(k, "")
} else {
f, err = os.Open(v)
}
Expand Down Expand Up @@ -71,9 +74,26 @@ func main() {
usage()
}

conn.Username = *rpcuser
conn.Password = *rpcpass
conn.Server = *rpchost
// Connect to local namecoin core RPC server using HTTP POST mode.
connCfg := &rpcclient.ConnConfig{
Host: *rpchost,
User: *rpcuser,
Pass: *rpcpass,
CookiePath: *rpccookiepath,
HTTPPostMode: true, // Namecoin core only supports HTTP POST mode
DisableTLS: true, // Namecoin core does not provide TLS by default
}

var err error

// Notice the notification parameter is nil since notifications are
// not supported in HTTP POST mode.
conn, err = namecoin.New(connCfg, nil)
if err != nil {
fmt.Fprintf(os.Stderr, "Error creating RPC client: %v\n", err)
os.Exit(1)
}
defer conn.Shutdown()

for i := 0; i+1 < len(args); i += 2 {
k := args[i]
Expand All @@ -83,7 +103,7 @@ func main() {
os.Exit(1)
}

v, err := translateValue(k, v)
v, err = translateValue(k, v)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to translate value: %v\n", err)
os.Exit(1)
Expand Down

0 comments on commit 4a1381c

Please sign in to comment.