diff --git a/cmdmgr.go b/cmdmgr.go index ec89162..a60e74c 100644 --- a/cmdmgr.go +++ b/cmdmgr.go @@ -22,6 +22,7 @@ import ( "fmt" "github.com/conformal/btcjson" "github.com/conformal/btcwallet/wallet" + "github.com/conformal/btcwire" "sync" "time" ) @@ -568,22 +569,37 @@ func SendMany(reply chan []byte, msg *btcjson.Message) { // All three parameters are required, and must be of type string. If // the wallet specified by account already exists, an invalid account // name error is returned to the client. +// +// Wallets will be created on MainNet, or TestNet3 if btcwallet is run with +// the --testnet option. func CreateEncryptedWallet(reply chan []byte, msg *btcjson.Message) { params, ok := msg.Params.([]interface{}) + e := &InvalidParams if !ok { - log.Error("CreateEncryptedWallet: Cannot parse parameters.") + ReplyError(reply, msg.Id, e) return } - var wname string if len(params) != 3 { - ReplyError(reply, msg.Id, &InvalidParams) + e.Message = "Incorrect number of parameters" + ReplyError(reply, msg.Id, e) return } - wname, ok1 := params[0].(string) - desc, ok2 := params[1].(string) - pass, ok3 := params[2].(string) - if !ok1 || !ok2 || !ok3 { - ReplyError(reply, msg.Id, &InvalidParams) + wname, ok := params[0].(string) + if !ok { + e.Message = "Account is not a string" + ReplyError(reply, msg.Id, e) + return + } + desc, ok := params[1].(string) + if !ok { + e.Message = "Description is not a string" + ReplyError(reply, msg.Id, e) + return + } + pass, ok := params[2].(string) + if !ok { + e.Message = "Passphrase is not a string" + ReplyError(reply, msg.Id, e) return } @@ -597,7 +613,13 @@ func CreateEncryptedWallet(reply chan []byte, msg *btcjson.Message) { } wallets.RUnlock() - w, err := wallet.NewWallet(wname, desc, []byte(pass)) + var net btcwire.BitcoinNet + if cfg.TestNet3 { + net = btcwire.TestNet3 + } else { + net = btcwire.MainNet + } + w, err := wallet.NewWallet(wname, desc, []byte(pass), net) if err != nil { log.Error("Error creating wallet: " + err.Error()) ReplyError(reply, msg.Id, &InternalError) diff --git a/config.go b/config.go index 60555cd..df10f48 100644 --- a/config.go +++ b/config.go @@ -45,6 +45,7 @@ type config struct { DataDir string `short:"D" long:"datadir" description:"Directory to store wallets and transactions"` Username string `short:"u" long:"username" description:"Username for btcd authorization"` Password string `short:"P" long:"password" description:"Password for btcd authorization"` + TestNet3 bool `long:"testnet" description:"Use the test network"` } // btcwalletHomeDir returns an OS appropriate home directory for btcwallet. diff --git a/sockets.go b/sockets.go index cb79ab1..d737825 100644 --- a/sockets.go +++ b/sockets.go @@ -61,7 +61,8 @@ var ( // replyHandlers maps between a sequence number (passed as part of // the JSON Id field) and a function to handle a reply or notification // from btcd. As requests are received, this map is checked for a - // handler function to route the reply to. + // handler function to route the reply to. If the function returns + // true, the handler is removed from the map. replyHandlers = struct { sync.Mutex m map[uint64]func(interface{}, *btcjson.Error) bool @@ -178,8 +179,9 @@ func frontendReqsNotifications(ws *websocket.Conn) { // websocket and sends messages that btcwallet does not understand to // btcd. Unlike FrontendHandler, exactly one BtcdHandler goroutine runs. func BtcdHandler(ws *websocket.Conn) { + // Notification channel to return from listener goroutine when + // btcd disconnects. disconnected := make(chan int) - defer func() { close(disconnected) }() @@ -214,6 +216,7 @@ func BtcdHandler(ws *websocket.Conn) { case r := <-btcdMsgs: if err := websocket.Message.Send(ws, r); err != nil { // btcd disconnected. + log.Error("Unable to send message to btcd: %v", err) return } } @@ -446,13 +449,67 @@ func BtcdConnect(reply chan error) { close(handlerClosed) }() + BtcdHandshake(btcdws) + + <-handlerClosed + reply <- ErrConnLost +} + +// BtcdHandshake first checks that the websocket connection between +// btcwallet and btcd is valid, that is, that there are no mismatching +// settings between the two processes (such as running on different +// Bitcoin networks). If the sanity checks pass, all wallets are set to +// be tracked against chain notifications from this btcd connection. +func BtcdHandshake(ws *websocket.Conn) { + seq.Lock() + n := seq.n + seq.n++ + seq.Unlock() + msg := btcjson.Message{ + Method: "getcurrentnet", + Id: fmt.Sprintf("btcwallet(%v)", n), + } + m, _ := json.Marshal(&msg) + + correctNetwork := make(chan bool) + + replyHandlers.Lock() + replyHandlers.m[n] = func(result interface{}, err *btcjson.Error) bool { + fmt.Println("got reply") + fnet, ok := result.(float64) + if !ok { + log.Error("btcd handshake: result is not a number") + ws.Close() + correctNetwork <- false + return true + } + + var walletNetwork btcwire.BitcoinNet + if cfg.TestNet3 { + walletNetwork = btcwire.TestNet3 + } else { + walletNetwork = btcwire.MainNet + } + + correctNetwork <- btcwire.BitcoinNet(fnet) == walletNetwork + + // No additional replies expected, remove handler. + return true + } + replyHandlers.Unlock() + + btcdMsgs <- m + + if !<-correctNetwork { + log.Error("btcd and btcwallet running on different Bitcoin networks") + ws.Close() + return + } + // Begin tracking wallets against this btcd instance. wallets.RLock() for _, w := range wallets.m { w.Track() } wallets.RUnlock() - - <-handlerClosed - reply <- ErrConnLost } diff --git a/wallet/wallet.go b/wallet/wallet.go index 74268a7..86f913b 100644 --- a/wallet/wallet.go +++ b/wallet/wallet.go @@ -353,7 +353,7 @@ type Wallet struct { // desc's binary representation must not exceed 32 and 256 bytes, // respectively. All address private keys are encrypted with passphrase. // The wallet is returned unlocked. -func NewWallet(name, desc string, passphrase []byte) (*Wallet, error) { +func NewWallet(name, desc string, passphrase []byte, net btcwire.BitcoinNet) (*Wallet, error) { if binary.Size(name) > 32 { return nil, errors.New("name exceeds 32 byte maximum size") } @@ -382,7 +382,7 @@ func NewWallet(name, desc string, passphrase []byte) (*Wallet, error) { // compat with armory. w := &Wallet{ version: 0, // TODO(jrick): implement versioning - net: btcwire.MainNet, + net: net, flags: walletFlags{ useEncryption: true, watchingOnly: false, diff --git a/wallet/wallet_test.go b/wallet/wallet_test.go index b8b862a..27dc5e7 100644 --- a/wallet/wallet_test.go +++ b/wallet/wallet_test.go @@ -18,6 +18,7 @@ package wallet import ( "crypto/rand" + "github.com/conformal/btcwire" "github.com/davecgh/go-spew/spew" "os" "reflect" @@ -78,7 +79,7 @@ func TestBtcAddressSerializer(t *testing.T) { } func TestWalletCreationSerialization(t *testing.T) { - w1, err := NewWallet("banana wallet", "A wallet for testing.", []byte("banana")) + w1, err := NewWallet("banana wallet", "A wallet for testing.", []byte("banana"), btcwire.MainNet) if err != nil { t.Error("Error creating new wallet: " + err.Error()) }