diff --git a/notifications.go b/notifications.go index 55ecd3be..c8c7cc68 100644 --- a/notifications.go +++ b/notifications.go @@ -11,88 +11,161 @@ import ( ) var ( - // ErrNtfnUnexpected describes an error where an unexpected - // notification is received when unmarshaling into a concrete - // Notification variable. - ErrNtfnUnexpected = errors.New("notification unexpected") - - // ErrNtfnNotFound describes an error where a parser does not - // handle unmarshaling a notification. - ErrNtfnNotFound = errors.New("notification not found") + // ErrNotANtfn describes an error where a JSON-RPC Request + // object cannot be successfully parsed as a notification + // due to having an ID. + ErrNotANtfn = errors.New("notifications may not have IDs") ) const ( - // BlockConnectedNtfnId is the id of the btcd blockconnected + // AccountBalanceNtfnMethod is the method of the btcwallet + // accountbalance notification. + AccountBalanceNtfnMethod = "accountbalance" + + // BlockConnectedNtfnMethod is the method of the btcd + // blockconnected notification. + BlockConnectedNtfnMethod = "blockconnected" + + // BlockDisconnectedNtfnMethod is the method of the btcd + // blockdisconnected notification. + BlockDisconnectedNtfnMethod = "blockdisconnected" + + // BtcdConnectedNtfnMethod is the method of the btcwallet + // btcdconnected notification. + BtcdConnectedNtfnMethod = "btcdconnected" + + // TxMinedNtfnMethod is the method of the btcd txmined // notification. - BlockConnectedNtfnId = "btcd:blockconnected" + TxMinedNtfnMethod = "txmined" - // BlockDisconnectedNtfnId is the id of the btcd blockdisconnected + // TxNtfnMethod is the method of the btcwallet newtx // notification. - BlockDisconnectedNtfnId = "btcd:blockdisconnected" + TxNtfnMethod = "newtx" - // TxMinedNtfnId is the id of the btcd txmined notification. - TxMinedNtfnId = "btcd:txmined" - - // TxNtfnId is the id of the btcwallet newtx notification. - TxNtfnId = "btcwallet:newtx" + // WalletLockStateNtfnMethod is the method of the btcwallet + // walletlockstate notification. + WalletLockStateNtfnMethod = "walletlockstate" ) -type newNtfnFn func() Notification - -func newBlockConnectedNtfn() Notification { - return &BlockConnectedNtfn{} +// Register notifications with btcjson. +func init() { + btcjson.RegisterCustomCmd(AccountBalanceNtfnMethod, parseAccountBalanceNtfn) + btcjson.RegisterCustomCmd(BlockConnectedNtfnMethod, parseBlockConnectedNtfn) + btcjson.RegisterCustomCmd(BlockDisconnectedNtfnMethod, parseBlockDisconnectedNtfn) + btcjson.RegisterCustomCmd(BtcdConnectedNtfnMethod, parseBtcdConnectedNtfn) + btcjson.RegisterCustomCmd(TxMinedNtfnMethod, parseTxMinedNtfn) + btcjson.RegisterCustomCmd(TxNtfnMethod, parseTxNtfn) + btcjson.RegisterCustomCmd(WalletLockStateNtfnMethod, parseWalletLockStateNtfn) } -func newBlockDisconnectedNtfn() Notification { - return &BlockDisconnectedNtfn{} +// AccountBalanceNtfn is a type handling custom marshaling and +// unmarshaling of accountbalance JSON websocket notifications. +type AccountBalanceNtfn struct { + Account string + Balance float64 + Confirmed bool // Whether Balance is confirmed or unconfirmed. } -func newTxMinedNtfn() Notification { - return &TxMinedNtfn{} -} +// Enforce that AccountBalanceNtfn satisifes the btcjson.Cmd interface. +var _ btcjson.Cmd = &AccountBalanceNtfn{} -func newTxNtfn() Notification { - return &TxNtfn{} -} +// NewAccountBalanceNtfn creates a new AccountBalanceNtfn. +func NewAccountBalanceNtfn(account string, balance float64, + confirmed bool) *AccountBalanceNtfn { -var newNtfnFns = map[string]newNtfnFn{ - BlockConnectedNtfnId: newBlockConnectedNtfn, - BlockDisconnectedNtfnId: newBlockDisconnectedNtfn, - TxMinedNtfnId: newTxMinedNtfn, - TxNtfnId: newTxNtfn, -} - -// ParseMarshaledNtfn attempts to unmarshal a marshaled notification -// into the notification described by id. -func ParseMarshaledNtfn(id string, b []byte) (Notification, error) { - if newFn, ok := newNtfnFns[id]; ok { - n := newFn() - if err := n.UnmarshalJSON(b); err != nil { - return nil, err - } - return n, nil + return &AccountBalanceNtfn{ + Account: account, + Balance: balance, + Confirmed: confirmed, } - return nil, ErrNtfnNotFound } -// Notification is an interface implemented by all notification types. -type Notification interface { - json.Marshaler - json.Unmarshaler - Id() interface{} +// parseAccountBalanceNtfn parses a RawCmd into a concrete type satisifying +// the btcjson.Cmd interface. This is used when registering the notification +// with the btcjson parser. +func parseAccountBalanceNtfn(r *btcjson.RawCmd) (btcjson.Cmd, error) { + if r.Id != nil { + return nil, ErrNotANtfn + } + + if len(r.Params) != 3 { + return nil, btcjson.ErrWrongNumberOfParams + } + + account, ok := r.Params[0].(string) + if !ok { + return nil, errors.New("first parameter account must be a string") + } + balance, ok := r.Params[1].(float64) + if !ok { + return nil, errors.New("second parameter balance must be a number") + } + confirmed, ok := r.Params[2].(bool) + if !ok { + return nil, errors.New("third parameter confirmed must be a boolean") + } + + return NewAccountBalanceNtfn(account, balance, confirmed), nil +} + +// Id satisifies the btcjson.Cmd interface by returning nil for a +// notification ID. +func (n *AccountBalanceNtfn) Id() interface{} { + return nil +} + +// Method satisifies the btcjson.Cmd interface by returning the method +// of the notification. +func (n *AccountBalanceNtfn) Method() string { + return AccountBalanceNtfnMethod +} + +// MarshalJSON returns the JSON encoding of n. Part of the btcjson.Cmd +// interface. +func (n *AccountBalanceNtfn) MarshalJSON() ([]byte, error) { + ntfn := btcjson.Message{ + Jsonrpc: "1.0", + Method: n.Method(), + Params: []interface{}{ + n.Account, + n.Balance, + n.Confirmed, + }, + } + return json.Marshal(ntfn) +} + +// UnmarshalJSON unmarshals the JSON encoding of n into n. Part of +// the btcjson.Cmd interface. +func (n *AccountBalanceNtfn) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd. + var r btcjson.RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + newNtfn, err := parseAccountBalanceNtfn(&r) + if err != nil { + return err + } + + concreteNtfn, ok := newNtfn.(*AccountBalanceNtfn) + if !ok { + return btcjson.ErrInternal + } + *n = *concreteNtfn + return nil } // BlockConnectedNtfn is a type handling custom marshaling and // unmarshaling of blockconnected JSON websocket notifications. type BlockConnectedNtfn struct { - Hash string `json:"hash"` - Height int32 `json:"height"` + Hash string + Height int32 } -type blockConnectedResult BlockConnectedNtfn - -// Enforce that BlockConnectedNtfn satisfies the Notification interface. -var _ Notification = &BlockConnectedNtfn{} +// Enforce that BlockConnectedNtfn satisfies the btcjson.Cmd interface. +var _ btcjson.Cmd = &BlockConnectedNtfn{} // NewBlockConnectedNtfn creates a new BlockConnectedNtfn. func NewBlockConnectedNtfn(hash string, height int32) *BlockConnectedNtfn { @@ -102,57 +175,89 @@ func NewBlockConnectedNtfn(hash string, height int32) *BlockConnectedNtfn { } } -// Id satisifies the Notification interface by returning the id of the -// notification. -func (n *BlockConnectedNtfn) Id() interface{} { - return BlockConnectedNtfnId +// parseBlockConnectedNtfn parses a RawCmd into a concrete type satisifying +// the btcjson.Cmd interface. This is used when registering the notification +// with the btcjson parser. +func parseBlockConnectedNtfn(r *btcjson.RawCmd) (btcjson.Cmd, error) { + if r.Id != nil { + return nil, ErrNotANtfn + } + + if len(r.Params) != 2 { + return nil, btcjson.ErrWrongNumberOfParams + } + + hash, ok := r.Params[0].(string) + if !ok { + return nil, errors.New("first parameter hash must be a string") + } + fheight, ok := r.Params[1].(float64) + if !ok { + return nil, errors.New("second parameter height must be a number") + } + + return NewBlockConnectedNtfn(hash, int32(fheight)), nil } -// MarshalJSON returns the JSON encoding of n. Part of the Notification +// Id satisifies the btcjson.Cmd interface by returning nil for a +// notification ID. +func (n *BlockConnectedNtfn) Id() interface{} { + return nil +} + +// Method satisifies the btcjson.Cmd interface by returning the method +// of the notification. +func (n *BlockConnectedNtfn) Method() string { + return BlockConnectedNtfnMethod +} + +// MarshalJSON returns the JSON encoding of n. Part of the btcjson.Cmd // interface. func (n *BlockConnectedNtfn) MarshalJSON() ([]byte, error) { - id := n.Id() - reply := btcjson.Reply{ - Result: *n, - Id: &id, + ntfn := btcjson.Message{ + Jsonrpc: "1.0", + Method: n.Method(), + Params: []interface{}{ + n.Hash, + n.Height, + }, } - return json.Marshal(reply) + return json.Marshal(ntfn) } // UnmarshalJSON unmarshals the JSON encoding of n into n. Part of -// the Notification interface. +// the btcjson.Cmd interface. func (n *BlockConnectedNtfn) UnmarshalJSON(b []byte) error { - var ntfn struct { - Result blockConnectedResult `json:"result"` - Error *btcjson.Error `json:"error"` - Id interface{} `json:"id"` - } - if err := json.Unmarshal(b, &ntfn); err != nil { + // Unmarshal into a RawCmd. + var r btcjson.RawCmd + if err := json.Unmarshal(b, &r); err != nil { return err } - // Notification IDs must match expected. - if n.Id() != ntfn.Id { - return ErrNtfnUnexpected + newNtfn, err := parseBlockConnectedNtfn(&r) + if err != nil { + return err } - *n = BlockConnectedNtfn(ntfn.Result) + concreteNtfn, ok := newNtfn.(*BlockConnectedNtfn) + if !ok { + return btcjson.ErrInternal + } + *n = *concreteNtfn return nil } // BlockDisconnectedNtfn is a type handling custom marshaling and // unmarshaling of blockdisconnected JSON websocket notifications. type BlockDisconnectedNtfn struct { - Hash string `json:"hash"` - Height int32 `json:"height"` + Hash string + Height int32 } -type blockDisconnectedResult BlockDisconnectedNtfn +// Enforce that BlockDisconnectedNtfn satisfies the btcjson.Cmd interface. +var _ btcjson.Cmd = &BlockDisconnectedNtfn{} -// Enforce that BlockDisconnectedNtfn satisfies the Notification interface. -var _ Notification = &BlockDisconnectedNtfn{} - -// NewBlockDisconnectedNtfn creates a new BlockConnectedNtfn. +// NewBlockDisconnectedNtfn creates a new BlockDisconnectedNtfn. func NewBlockDisconnectedNtfn(hash string, height int32) *BlockDisconnectedNtfn { return &BlockDisconnectedNtfn{ Hash: hash, @@ -160,58 +265,172 @@ func NewBlockDisconnectedNtfn(hash string, height int32) *BlockDisconnectedNtfn } } -// Id satisifies the Notification interface by returning the id of the -// notification. -func (n *BlockDisconnectedNtfn) Id() interface{} { - return BlockDisconnectedNtfnId +// parseBlockDisconnectedNtfn parses a RawCmd into a concrete type satisifying +// the btcjson.Cmd interface. This is used when registering the notification +// with the btcjson parser. +func parseBlockDisconnectedNtfn(r *btcjson.RawCmd) (btcjson.Cmd, error) { + if r.Id != nil { + return nil, ErrNotANtfn + } + + if len(r.Params) != 2 { + return nil, btcjson.ErrWrongNumberOfParams + } + + hash, ok := r.Params[0].(string) + if !ok { + return nil, errors.New("first parameter hash must be a string") + } + fheight, ok := r.Params[1].(float64) + if !ok { + return nil, errors.New("second parameter height must be a number") + } + + return NewBlockDisconnectedNtfn(hash, int32(fheight)), nil } -// MarshalJSON returns the JSON encoding of n. Part of the Notification +// Id satisifies the btcjson.Cmd interface by returning nil for a +// notification ID. +func (n *BlockDisconnectedNtfn) Id() interface{} { + return nil +} + +// Method satisifies the btcjson.Cmd interface by returning the method +// of the notification. +func (n *BlockDisconnectedNtfn) Method() string { + return BlockDisconnectedNtfnMethod +} + +// MarshalJSON returns the JSON encoding of n. Part of the btcjson.Cmd // interface. func (n *BlockDisconnectedNtfn) MarshalJSON() ([]byte, error) { - id := n.Id() - reply := btcjson.Reply{ - Result: *n, - Id: &id, + ntfn := btcjson.Message{ + Jsonrpc: "1.0", + Method: n.Method(), + Params: []interface{}{ + n.Hash, + n.Height, + }, } - return json.Marshal(reply) + return json.Marshal(ntfn) } // UnmarshalJSON unmarshals the JSON encoding of n into n. Part of -// the Notification interface. +// the btcjson.Cmd interface. func (n *BlockDisconnectedNtfn) UnmarshalJSON(b []byte) error { - var ntfn struct { - Result blockDisconnectedResult `json:"result"` - Error *btcjson.Error `json:"error"` - Id interface{} `json:"id"` - } - if err := json.Unmarshal(b, &ntfn); err != nil { + // Unmarshal into a RawCmd. + var r btcjson.RawCmd + if err := json.Unmarshal(b, &r); err != nil { return err } - // Notification IDs must match expected. - if n.Id() != ntfn.Id { - return ErrNtfnUnexpected + newNtfn, err := parseBlockDisconnectedNtfn(&r) + if err != nil { + return err } - *n = BlockDisconnectedNtfn(ntfn.Result) + concreteNtfn, ok := newNtfn.(*BlockDisconnectedNtfn) + if !ok { + return btcjson.ErrInternal + } + *n = *concreteNtfn + return nil +} + +// BtcdConnectedNtfn is a type handling custom marshaling and +// unmarshaling of btcdconnected JSON websocket notifications. +type BtcdConnectedNtfn struct { + Connected bool +} + +// Enforce that BtcdConnectedNtfn satisifies the btcjson.Cmd +// interface. +var _ btcjson.Cmd = &BtcdConnectedNtfn{} + +// NewBtcdConnectedNtfn creates a new BtcdConnectedNtfn. +func NewBtcdConnectedNtfn(connected bool) *BtcdConnectedNtfn { + return &BtcdConnectedNtfn{connected} +} + +// parseBtcdConnectedNtfn parses a RawCmd into a concrete type satisifying +// the btcjson.Cmd interface. This is used when registering the notification +// with the btcjson parser. +func parseBtcdConnectedNtfn(r *btcjson.RawCmd) (btcjson.Cmd, error) { + if r.Id != nil { + return nil, ErrNotANtfn + } + + if len(r.Params) != 1 { + return nil, btcjson.ErrWrongNumberOfParams + } + + connected, ok := r.Params[0].(bool) + if !ok { + return nil, errors.New("first parameter connected is not a boolean") + } + + return NewBtcdConnectedNtfn(connected), nil +} + +// Id satisifies the btcjson.Cmd interface by returning nil for a +// notification ID. +func (n *BtcdConnectedNtfn) Id() interface{} { + return nil +} + +// Method satisifies the btcjson.Cmd interface by returning the method +// of the notification. +func (n *BtcdConnectedNtfn) Method() string { + return BtcdConnectedNtfnMethod +} + +// MarshalJSON returns the JSON encoding of n. Part of the btcjson.Cmd +// interface. +func (n *BtcdConnectedNtfn) MarshalJSON() ([]byte, error) { + ntfn := btcjson.Message{ + Jsonrpc: "1.0", + Method: n.Method(), + Params: []interface{}{ + n.Connected, + }, + } + return json.Marshal(ntfn) +} + +// UnmarshalJSON unmarshals the JSON encoding of n into n. Part of +// the btcjson.Cmd interface. +func (n *BtcdConnectedNtfn) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd. + var r btcjson.RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + newNtfn, err := parseTxMinedNtfn(&r) + if err != nil { + return err + } + + concreteNtfn, ok := newNtfn.(*BtcdConnectedNtfn) + if !ok { + return btcjson.ErrInternal + } + *n = *concreteNtfn return nil } // TxMinedNtfn is a type handling custom marshaling and // unmarshaling of txmined JSON websocket notifications. type TxMinedNtfn struct { - TxID string `json:"txid"` - BlockHash string `json:"blockhash"` - BlockHeight int32 `json:"blockheight"` - BlockTime int64 `json:"blocktime"` - Index int `json:"index"` + TxID string + BlockHash string + BlockHeight int32 + BlockTime int64 + Index int } -type txMinedResult TxMinedNtfn - -// Enforce that TxMinedNtfn satisfies the Notification interface. -var _ Notification = &TxMinedNtfn{} +// Enforce that TxMinedNtfn satisifies the btcjson.Cmd interface. +var _ btcjson.Cmd = &TxMinedNtfn{} // NewTxMinedNtfn creates a new TxMinedNtfn. func NewTxMinedNtfn(txid, blockhash string, blockheight int32, @@ -226,55 +445,103 @@ func NewTxMinedNtfn(txid, blockhash string, blockheight int32, } } -// Id satisifies the Notification interface by returning the id of the -// notification. -func (n *TxMinedNtfn) Id() interface{} { - return TxMinedNtfnId +// parseTxMinedNtfn parses a RawCmd into a concrete type satisifying +// the btcjson.Cmd interface. This is used when registering the notification +// with the btcjson parser. +func parseTxMinedNtfn(r *btcjson.RawCmd) (btcjson.Cmd, error) { + if r.Id != nil { + return nil, ErrNotANtfn + } + + if len(r.Params) != 5 { + return nil, btcjson.ErrWrongNumberOfParams + } + + txid, ok := r.Params[0].(string) + if !ok { + return nil, errors.New("first parameter txid must be a string") + } + blockhash, ok := r.Params[1].(string) + if !ok { + return nil, errors.New("second parameter blockhash must be a string") + } + fblockheight, ok := r.Params[2].(float64) + if !ok { + return nil, errors.New("third parameter blockheight must be a number") + } + fblocktime, ok := r.Params[3].(float64) + if !ok { + return nil, errors.New("fourth parameter blocktime must be a number") + } + findex, ok := r.Params[4].(float64) + if !ok { + return nil, errors.New("fifth parameter index must be a number") + } + + return NewTxMinedNtfn(txid, blockhash, int32(fblockheight), + int64(fblocktime), int(findex)), nil } -// MarshalJSON returns the JSON encoding of n. Part of the Notification +// Id satisifies the btcjson.Cmd interface by returning nil for a +// notification ID. +func (n *TxMinedNtfn) Id() interface{} { + return nil +} + +// Method satisifies the btcjson.Cmd interface by returning the method +// of the notification. +func (n *TxMinedNtfn) Method() string { + return TxMinedNtfnMethod +} + +// MarshalJSON returns the JSON encoding of n. Part of the btcjson.Cmd // interface. func (n *TxMinedNtfn) MarshalJSON() ([]byte, error) { - id := n.Id() - reply := btcjson.Reply{ - Result: *n, - Id: &id, + ntfn := btcjson.Message{ + Jsonrpc: "1.0", + Method: n.Method(), + Params: []interface{}{ + n.TxID, + n.BlockHash, + n.BlockHeight, + n.BlockTime, + n.Index, + }, } - return json.Marshal(reply) + return json.Marshal(ntfn) } // UnmarshalJSON unmarshals the JSON encoding of n into n. Part of -// the Notification interface. +// the btcjson.Cmd interface. func (n *TxMinedNtfn) UnmarshalJSON(b []byte) error { - var ntfn struct { - Result txMinedResult `json:"result"` - Error *btcjson.Error `json:"error"` - Id interface{} `json:"id"` - } - if err := json.Unmarshal(b, &ntfn); err != nil { + // Unmarshal into a RawCmd. + var r btcjson.RawCmd + if err := json.Unmarshal(b, &r); err != nil { return err } - // Notification IDs must match expected. - if n.Id() != ntfn.Id { - return ErrNtfnUnexpected + newNtfn, err := parseTxMinedNtfn(&r) + if err != nil { + return err } - *n = TxMinedNtfn(ntfn.Result) + concreteNtfn, ok := newNtfn.(*TxMinedNtfn) + if !ok { + return btcjson.ErrInternal + } + *n = *concreteNtfn return nil } // TxNtfn is a type handling custom marshaling and // unmarshaling of newtx JSON websocket notifications. type TxNtfn struct { - Account string `json:"account"` - Details map[string]interface{} `json:"details"` + Account string + Details map[string]interface{} } -type txNtfnResult TxNtfn - -// Enforce that TxNtfn satisfies the Notification interface. -var _ Notification = &TxNtfn{} +// Enforce that TxNtfn satisifies the btcjson.Cmd interface. +var _ btcjson.Cmd = &TxNtfn{} // NewTxNtfn creates a new TxNtfn. func NewTxNtfn(account string, details map[string]interface{}) *TxNtfn { @@ -284,40 +551,167 @@ func NewTxNtfn(account string, details map[string]interface{}) *TxNtfn { } } -// Id satisifies the Notification interface by returning the id of the -// notification. -func (n *TxNtfn) Id() interface{} { - return TxNtfnId +// parseTxNtfn parses a RawCmd into a concrete type satisifying +// the btcjson.Cmd interface. This is used when registering the notification +// with the btcjson parser. +func parseTxNtfn(r *btcjson.RawCmd) (btcjson.Cmd, error) { + if r.Id != nil { + return nil, ErrNotANtfn + } + + if len(r.Params) != 2 { + return nil, btcjson.ErrWrongNumberOfParams + } + + account, ok := r.Params[0].(string) + if !ok { + return nil, errors.New("first parameter account must be a string") + } + details, ok := r.Params[1].(map[string]interface{}) + if !ok { + return nil, errors.New("second parameter details must be a JSON object") + } + + return NewTxNtfn(account, details), nil } -// MarshalJSON returns the JSON encoding of n. Part of the Notification +// Id satisifies the btcjson.Cmd interface by returning nil for a +// notification ID. +func (n *TxNtfn) Id() interface{} { + return nil +} + +// Method satisifies the btcjson.Cmd interface by returning the method +// of the notification. +func (n *TxNtfn) Method() string { + return TxNtfnMethod +} + +// MarshalJSON returns the JSON encoding of n. Part of the btcjson.Cmd // interface. func (n *TxNtfn) MarshalJSON() ([]byte, error) { - id := n.Id() - reply := btcjson.Reply{ - Result: *n, - Id: &id, + ntfn := btcjson.Message{ + Jsonrpc: "1.0", + Method: n.Method(), + Params: []interface{}{ + n.Account, + n.Details, + }, } - return json.Marshal(reply) + return json.Marshal(ntfn) } // UnmarshalJSON unmarshals the JSON encoding of n into n. Part of -// the Notification interface. +// the btcjson.Cmd interface. func (n *TxNtfn) UnmarshalJSON(b []byte) error { - var ntfn struct { - Result txNtfnResult `json:"result"` - Error *btcjson.Error `json:"error"` - Id interface{} `json:"id"` - } - if err := json.Unmarshal(b, &ntfn); err != nil { + // Unmarshal into a RawCmd. + var r btcjson.RawCmd + if err := json.Unmarshal(b, &r); err != nil { return err } - // Notification IDs must match expected. - if n.Id() != ntfn.Id { - return ErrNtfnUnexpected + newNtfn, err := parseTxNtfn(&r) + if err != nil { + return err } - *n = TxNtfn(ntfn.Result) + concreteNtfn, ok := newNtfn.(*TxNtfn) + if !ok { + return btcjson.ErrInternal + } + *n = *concreteNtfn + return nil +} + +// WalletLockStateNtfn is a type handling custom marshaling and +// unmarshaling of walletlockstate JSON websocket notifications. +type WalletLockStateNtfn struct { + Account string + Locked bool +} + +// Enforce that WalletLockStateNtfnMethod satisifies the btcjson.Cmd +// interface. +var _ btcjson.Cmd = &WalletLockStateNtfn{} + +// NewWalletLockStateNtfn creates a new WalletLockStateNtfn. +func NewWalletLockStateNtfn(account string, + locked bool) *WalletLockStateNtfn { + + return &WalletLockStateNtfn{ + Account: account, + Locked: locked, + } +} + +// parseWalletLockStateNtfn parses a RawCmd into a concrete type +// satisifying the btcjson.Cmd interface. This is used when registering +// the notification with the btcjson parser. +func parseWalletLockStateNtfn(r *btcjson.RawCmd) (btcjson.Cmd, error) { + if r.Id != nil { + return nil, ErrNotANtfn + } + + if len(r.Params) != 2 { + return nil, btcjson.ErrWrongNumberOfParams + } + + account, ok := r.Params[0].(string) + if !ok { + return nil, errors.New("first parameter account must be a string") + } + locked, ok := r.Params[1].(bool) + if !ok { + return nil, errors.New("second parameter locked must be a boolean") + } + + return NewWalletLockStateNtfn(account, locked), nil +} + +// Id satisifies the btcjson.Cmd interface by returning nil for a +// notification ID. +func (n *WalletLockStateNtfn) Id() interface{} { + return nil +} + +// Method satisifies the btcjson.Cmd interface by returning the method +// of the notification. +func (n *WalletLockStateNtfn) Method() string { + return WalletLockStateNtfnMethod +} + +// MarshalJSON returns the JSON encoding of n. Part of the btcjson.Cmd +// interface. +func (n *WalletLockStateNtfn) MarshalJSON() ([]byte, error) { + ntfn := btcjson.Message{ + Jsonrpc: "1.0", + Method: n.Method(), + Params: []interface{}{ + n.Account, + n.Locked, + }, + } + return json.Marshal(ntfn) +} + +// UnmarshalJSON unmarshals the JSON encoding of n into n. Part of +// the btcjson.Cmd interface. +func (n *WalletLockStateNtfn) UnmarshalJSON(b []byte) error { + // Unmarshal into a RawCmd. + var r btcjson.RawCmd + if err := json.Unmarshal(b, &r); err != nil { + return err + } + + newNtfn, err := parseWalletLockStateNtfn(&r) + if err != nil { + return err + } + + concreteNtfn, ok := newNtfn.(*WalletLockStateNtfn) + if !ok { + return btcjson.ErrInternal + } + *n = *concreteNtfn return nil } diff --git a/notifications_test.go b/notifications_test.go new file mode 100644 index 00000000..a3de913a --- /dev/null +++ b/notifications_test.go @@ -0,0 +1,161 @@ +// Copyright (c) 2013 Conformal Systems LLC. +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btcws_test + +import ( + "github.com/conformal/btcjson" + "github.com/conformal/btcws" + "github.com/davecgh/go-spew/spew" + "reflect" + "testing" +) + +var ntfntests = []struct { + name string + f func() btcjson.Cmd + result btcjson.Cmd // after marshal and unmarshal +}{ + { + name: "accountbalance", + f: func() btcjson.Cmd { + return btcws.NewAccountBalanceNtfn("abcde", 1.2345, true) + }, + result: &btcws.AccountBalanceNtfn{ + Account: "abcde", + Balance: 1.2345, + Confirmed: true, + }, + }, + { + name: "blockconnected", + f: func() btcjson.Cmd { + return btcws.NewBlockConnectedNtfn( + "000000004811dda1c320ad5d0ea184a20a53acd92292c5f1cb926c3ee82abf70", + 153469) + }, + result: &btcws.BlockConnectedNtfn{ + Hash: "000000004811dda1c320ad5d0ea184a20a53acd92292c5f1cb926c3ee82abf70", + Height: 153469, + }, + }, + { + name: "blockdisconnected", + f: func() btcjson.Cmd { + return btcws.NewBlockDisconnectedNtfn( + "000000004811dda1c320ad5d0ea184a20a53acd92292c5f1cb926c3ee82abf70", + 153469) + }, + result: &btcws.BlockDisconnectedNtfn{ + Hash: "000000004811dda1c320ad5d0ea184a20a53acd92292c5f1cb926c3ee82abf70", + Height: 153469, + }, + }, + { + name: "btcdconnected", + f: func() btcjson.Cmd { + return btcws.NewBtcdConnectedNtfn(true) + }, + result: &btcws.BtcdConnectedNtfn{ + Connected: true, + }, + }, + { + name: "txmined", + f: func() btcjson.Cmd { + return btcws.NewTxMinedNtfn( + "062f2b5f7d28c787e0f3aee382132241cd590efb7b83bd2c7f506de5aa4ef275", + "000000004811dda1c320ad5d0ea184a20a53acd92292c5f1cb926c3ee82abf70", + 153469, + 1386944019, + 0) + }, + result: &btcws.TxMinedNtfn{ + TxID: "062f2b5f7d28c787e0f3aee382132241cd590efb7b83bd2c7f506de5aa4ef275", + BlockHash: "000000004811dda1c320ad5d0ea184a20a53acd92292c5f1cb926c3ee82abf70", + BlockHeight: 153469, + BlockTime: 1386944019, + Index: 0, + }, + }, + { + name: "newtx", + f: func() btcjson.Cmd { + details := map[string]interface{}{ + "key1": float64(12345), + "key2": true, + "key3": "lalala", + "key4": []interface{}{"abcde", float64(12345)}, + } + return btcws.NewTxNtfn("abcde", details) + }, + result: &btcws.TxNtfn{ + Account: "abcde", + Details: map[string]interface{}{ + "key1": float64(12345), + "key2": true, + "key3": "lalala", + "key4": []interface{}{"abcde", float64(12345)}, + }, + }, + }, + { + name: "walletlockstate", + f: func() btcjson.Cmd { + return btcws.NewWalletLockStateNtfn("abcde", true) + }, + result: &btcws.WalletLockStateNtfn{ + Account: "abcde", + Locked: true, + }, + }, +} + +func TestNtfns(t *testing.T) { + for _, test := range ntfntests { + // create notification. + n := test.f() + + // verify that id is nil. + if n.Id() != nil { + t.Error("%s: notification should not have non-nil id %v", + test.name, n.Id()) + continue + } + + mn, err := n.MarshalJSON() + if err != nil { + t.Errorf("%s: failed to marshal notification: %v", + test.name, err) + continue + } + + n2, err := btcjson.ParseMarshaledCmd(mn) + if err != nil { + t.Errorf("%s: failed to ummarshal cmd: %v", + test.name, err) + continue + } + + if !reflect.DeepEqual(test.result, n2) { + t.Errorf("%s: unmarshal not as expected. "+ + "got %v wanted %v", test.name, spew.Sdump(n2), + spew.Sdump(test.result)) + } + if !reflect.DeepEqual(n, n2) { + t.Errorf("%s: unmarshal not as we started with. "+ + "got %v wanted %v", test.name, spew.Sdump(n2), + spew.Sdump(n)) + } + + // Read marshaled notification back into n. Should still + // match result. + n.UnmarshalJSON(mn) + if !reflect.DeepEqual(test.result, n) { + t.Errorf("%s: unmarshal not as expected. "+ + "got %v wanted %v", test.name, spew.Sdump(n), + spew.Sdump(test.result)) + } + } +} diff --git a/test_coverage.txt b/test_coverage.txt new file mode 100644 index 00000000..4f123d4b --- /dev/null +++ b/test_coverage.txt @@ -0,0 +1,107 @@ + +github.com/conformal/btcws/cmds.go init 100.00% (10/10) +github.com/conformal/btcws/notifications.go init 100.00% (7/7) +github.com/conformal/btcws/notifications.go BtcdConnectedNtfn.MarshalJSON 100.00% (2/2) +github.com/conformal/btcws/notifications.go BlockDisconnectedNtfn.MarshalJSON 100.00% (2/2) +github.com/conformal/btcws/notifications.go TxMinedNtfn.MarshalJSON 100.00% (2/2) +github.com/conformal/btcws/notifications.go TxNtfn.MarshalJSON 100.00% (2/2) +github.com/conformal/btcws/notifications.go AccountBalanceNtfn.MarshalJSON 100.00% (2/2) +github.com/conformal/btcws/notifications.go WalletLockStateNtfn.MarshalJSON 100.00% (2/2) +github.com/conformal/btcws/notifications.go BlockConnectedNtfn.MarshalJSON 100.00% (2/2) +github.com/conformal/btcws/notifications.go BlockDisconnectedNtfn.Method 100.00% (1/1) +github.com/conformal/btcws/notifications.go WalletLockStateNtfn.Method 100.00% (1/1) +github.com/conformal/btcws/notifications.go WalletLockStateNtfn.Id 100.00% (1/1) +github.com/conformal/btcws/notifications.go NewWalletLockStateNtfn 100.00% (1/1) +github.com/conformal/btcws/notifications.go TxNtfn.Method 100.00% (1/1) +github.com/conformal/btcws/notifications.go TxNtfn.Id 100.00% (1/1) +github.com/conformal/btcws/notifications.go NewTxNtfn 100.00% (1/1) +github.com/conformal/btcws/notifications.go TxMinedNtfn.Method 100.00% (1/1) +github.com/conformal/btcws/notifications.go NewTxMinedNtfn 100.00% (1/1) +github.com/conformal/btcws/notifications.go TxMinedNtfn.Id 100.00% (1/1) +github.com/conformal/btcws/notifications.go BtcdConnectedNtfn.Id 100.00% (1/1) +github.com/conformal/btcws/notifications.go NewBtcdConnectedNtfn 100.00% (1/1) +github.com/conformal/btcws/notifications.go BtcdConnectedNtfn.Method 100.00% (1/1) +github.com/conformal/btcws/notifications.go BlockDisconnectedNtfn.Id 100.00% (1/1) +github.com/conformal/btcws/notifications.go NewBlockDisconnectedNtfn 100.00% (1/1) +github.com/conformal/btcws/notifications.go BlockConnectedNtfn.Method 100.00% (1/1) +github.com/conformal/btcws/notifications.go BlockConnectedNtfn.Id 100.00% (1/1) +github.com/conformal/btcws/notifications.go NewBlockConnectedNtfn 100.00% (1/1) +github.com/conformal/btcws/notifications.go AccountBalanceNtfn.Id 100.00% (1/1) +github.com/conformal/btcws/notifications.go NewAccountBalanceNtfn 100.00% (1/1) +github.com/conformal/btcws/notifications.go AccountBalanceNtfn.Method 100.00% (1/1) +github.com/conformal/btcws/notifications.go TxNtfn.UnmarshalJSON 72.73% (8/11) +github.com/conformal/btcws/notifications.go AccountBalanceNtfn.UnmarshalJSON 72.73% (8/11) +github.com/conformal/btcws/notifications.go WalletLockStateNtfn.UnmarshalJSON 72.73% (8/11) +github.com/conformal/btcws/notifications.go BlockDisconnectedNtfn.UnmarshalJSON 72.73% (8/11) +github.com/conformal/btcws/notifications.go TxMinedNtfn.UnmarshalJSON 72.73% (8/11) +github.com/conformal/btcws/notifications.go BlockConnectedNtfn.UnmarshalJSON 72.73% (8/11) +github.com/conformal/btcws/notifications.go parseTxMinedNtfn 70.00% (14/20) +github.com/conformal/btcws/notifications.go parseAccountBalanceNtfn 64.29% (9/14) +github.com/conformal/btcws/notifications.go parseTxNtfn 63.64% (7/11) +github.com/conformal/btcws/notifications.go parseWalletLockStateNtfn 63.64% (7/11) +github.com/conformal/btcws/notifications.go parseBlockConnectedNtfn 63.64% (7/11) +github.com/conformal/btcws/notifications.go parseBlockDisconnectedNtfn 63.64% (7/11) +github.com/conformal/btcws/notifications.go parseBtcdConnectedNtfn 62.50% (5/8) +github.com/conformal/btcws/notifications.go BtcdConnectedNtfn.UnmarshalJSON 45.45% (5/11) +github.com/conformal/btcws/cmds.go parseRescanCmd 0.00% (0/18) +github.com/conformal/btcws/cmds.go parseNotifySpentCmd 0.00% (0/15) +github.com/conformal/btcws/cmds.go parseCreateEncryptedWalletCmd 0.00% (0/13) +github.com/conformal/btcws/cmds.go parseNotifyNewTXsCmd 0.00% (0/12) +github.com/conformal/btcws/cmds.go RescanCmd.UnmarshalJSON 0.00% (0/11) +github.com/conformal/btcws/cmds.go CreateEncryptedWalletCmd.UnmarshalJSON 0.00% (0/11) +github.com/conformal/btcws/cmds.go GetCurrentNetCmd.UnmarshalJSON 0.00% (0/11) +github.com/conformal/btcws/cmds.go GetAddressBalanceCmd.UnmarshalJSON 0.00% (0/11) +github.com/conformal/btcws/cmds.go GetBalancesCmd.UnmarshalJSON 0.00% (0/11) +github.com/conformal/btcws/cmds.go GetBestBlockCmd.UnmarshalJSON 0.00% (0/11) +github.com/conformal/btcws/cmds.go NotifyNewTXsCmd.UnmarshalJSON 0.00% (0/11) +github.com/conformal/btcws/cmds.go ListAllTransactionsCmd.UnmarshalJSON 0.00% (0/11) +github.com/conformal/btcws/cmds.go parseGetAddressBalanceCmd 0.00% (0/11) +github.com/conformal/btcws/cmds.go WalletIsLockedCmd.UnmarshalJSON 0.00% (0/11) +github.com/conformal/btcws/cmds.go NotifySpentCmd.UnmarshalJSON 0.00% (0/11) +github.com/conformal/btcws/cmds.go parseListAllTransactionsCmd 0.00% (0/8) +github.com/conformal/btcws/cmds.go parseWalletIsLockedCmd 0.00% (0/8) +github.com/conformal/btcws/cmds.go NewRescanCmd 0.00% (0/6) +github.com/conformal/btcws/cmds.go NewListAllTransactionsCmd 0.00% (0/6) +github.com/conformal/btcws/cmds.go NewGetAddressBalanceCmd 0.00% (0/6) +github.com/conformal/btcws/cmds.go NewWalletIsLockedCmd 0.00% (0/6) +github.com/conformal/btcws/cmds.go WalletIsLockedCmd.MarshalJSON 0.00% (0/4) +github.com/conformal/btcws/cmds.go RescanCmd.MarshalJSON 0.00% (0/4) +github.com/conformal/btcws/cmds.go ListAllTransactionsCmd.MarshalJSON 0.00% (0/4) +github.com/conformal/btcws/cmds.go GetAddressBalanceCmd.MarshalJSON 0.00% (0/4) +github.com/conformal/btcws/cmds.go parseGetBalancesCmd 0.00% (0/3) +github.com/conformal/btcws/cmds.go parseGetBestBlockCmd 0.00% (0/3) +github.com/conformal/btcws/cmds.go parseGetCurrentNetCmd 0.00% (0/3) +github.com/conformal/btcws/cmds.go NotifyNewTXsCmd.MarshalJSON 0.00% (0/2) +github.com/conformal/btcws/cmds.go GetBalancesCmd.MarshalJSON 0.00% (0/2) +github.com/conformal/btcws/cmds.go CreateEncryptedWalletCmd.MarshalJSON 0.00% (0/2) +github.com/conformal/btcws/cmds.go NotifySpentCmd.MarshalJSON 0.00% (0/2) +github.com/conformal/btcws/cmds.go GetBestBlockCmd.MarshalJSON 0.00% (0/2) +github.com/conformal/btcws/cmds.go GetCurrentNetCmd.MarshalJSON 0.00% (0/2) +github.com/conformal/btcws/cmds.go NotifySpentCmd.Id 0.00% (0/1) +github.com/conformal/btcws/cmds.go CreateEncryptedWalletCmd.Id 0.00% (0/1) +github.com/conformal/btcws/cmds.go NewNotifySpentCmd 0.00% (0/1) +github.com/conformal/btcws/cmds.go CreateEncryptedWalletCmd.Method 0.00% (0/1) +github.com/conformal/btcws/cmds.go NewGetBalancesCmd 0.00% (0/1) +github.com/conformal/btcws/cmds.go NotifyNewTXsCmd.Method 0.00% (0/1) +github.com/conformal/btcws/cmds.go NotifyNewTXsCmd.Id 0.00% (0/1) +github.com/conformal/btcws/cmds.go GetBalancesCmd.Method 0.00% (0/1) +github.com/conformal/btcws/cmds.go NewNotifyNewTXsCmd 0.00% (0/1) +github.com/conformal/btcws/cmds.go ListAllTransactionsCmd.Method 0.00% (0/1) +github.com/conformal/btcws/cmds.go WalletIsLockedCmd.Method 0.00% (0/1) +github.com/conformal/btcws/cmds.go RescanCmd.Method 0.00% (0/1) +github.com/conformal/btcws/cmds.go RescanCmd.Id 0.00% (0/1) +github.com/conformal/btcws/cmds.go GetAddressBalanceCmd.Method 0.00% (0/1) +github.com/conformal/btcws/cmds.go NewGetCurrentNetCmd 0.00% (0/1) +github.com/conformal/btcws/cmds.go GetBestBlockCmd.Method 0.00% (0/1) +github.com/conformal/btcws/cmds.go GetBestBlockCmd.Id 0.00% (0/1) +github.com/conformal/btcws/cmds.go GetAddressBalanceCmd.Id 0.00% (0/1) +github.com/conformal/btcws/cmds.go NewGetBestBlockCmd 0.00% (0/1) +github.com/conformal/btcws/cmds.go WalletIsLockedCmd.Id 0.00% (0/1) +github.com/conformal/btcws/cmds.go GetBalancesCmd.Id 0.00% (0/1) +github.com/conformal/btcws/cmds.go GetCurrentNetCmd.Method 0.00% (0/1) +github.com/conformal/btcws/cmds.go GetCurrentNetCmd.Id 0.00% (0/1) +github.com/conformal/btcws/cmds.go NewCreateEncryptedWalletCmd 0.00% (0/1) +github.com/conformal/btcws/cmds.go ListAllTransactionsCmd.Id 0.00% (0/1) +github.com/conformal/btcws/cmds.go NotifySpentCmd.Method 0.00% (0/1) +github.com/conformal/btcws -------------------------------------- 32.39% (161/497) +