Address several issues pointed out by lint and vet.

This brings the entire tree closer to but not 100% goclean.sh clean.
This commit is contained in:
Josh Rickmar 2015-02-06 00:12:48 -05:00
parent 4a1067b6f1
commit ad80e9f384
8 changed files with 119 additions and 61 deletions

View file

@ -138,14 +138,13 @@ func walletMain() error {
// that generating new wallets is ok. // that generating new wallets is ok.
server.SetWallet(nil) server.SetWallet(nil)
return return
} else { }
// If the keystore file exists but another error was // If the keystore file exists but another error was
// encountered, we cannot continue. // encountered, we cannot continue.
log.Errorf("Cannot load wallet files: %v", err) log.Errorf("Cannot load wallet files: %v", err)
walletOpenErrors <- err walletOpenErrors <- err
return return
} }
}
server.SetWallet(w) server.SetWallet(w)

View file

@ -30,6 +30,8 @@ import (
"github.com/btcsuite/btcws" "github.com/btcsuite/btcws"
) )
// Client represents a persistent client connection to a bitcoin RPC server
// for information regarding the current best block chain.
type Client struct { type Client struct {
*btcrpcclient.Client *btcrpcclient.Client
chainParams *chaincfg.Params chainParams *chaincfg.Params
@ -51,6 +53,12 @@ type Client struct {
quitMtx sync.Mutex quitMtx sync.Mutex
} }
// NewClient creates a client connection to the server described by the connect
// string. If disableTLS is false, the remote RPC certificate must be provided
// in the certs slice. The connection is not established immediately, but must
// be done using the Start method. If the remote server does not operate on
// the same bitcoin network as described by the passed chain parameters, the
// connection will be disconnected.
func NewClient(chainParams *chaincfg.Params, connect, user, pass string, certs []byte, disableTLS bool) (*Client, error) { func NewClient(chainParams *chaincfg.Params, connect, user, pass string, certs []byte, disableTLS bool) (*Client, error) {
client := Client{ client := Client{
chainParams: chainParams, chainParams: chainParams,
@ -86,6 +94,11 @@ func NewClient(chainParams *chaincfg.Params, connect, user, pass string, certs [
return &client, nil return &client, nil
} }
// Start attempts to establish a client connection with the remote server.
// If successful, handler goroutines are started to process notifications
// sent by the server. After a limited number of connection attempts, this
// function gives up, and therefore will not block forever waiting for the
// connection to be established to a server that may not exist.
func (c *Client) Start() error { func (c *Client) Start() error {
err := c.Connect(5) // attempt connection 5 tries at most err := c.Connect(5) // attempt connection 5 tries at most
if err != nil { if err != nil {
@ -112,6 +125,8 @@ func (c *Client) Start() error {
return nil return nil
} }
// Stop disconnects the client and signals the shutdown of all goroutines
// started by Start.
func (c *Client) Stop() { func (c *Client) Stop() {
c.quitMtx.Lock() c.quitMtx.Lock()
defer c.quitMtx.Unlock() defer c.quitMtx.Unlock()
@ -128,15 +143,67 @@ func (c *Client) Stop() {
} }
} }
// WaitForShutdown blocks until both the client has finished disconnecting
// and all handlers have exited.
func (c *Client) WaitForShutdown() { func (c *Client) WaitForShutdown() {
c.Client.WaitForShutdown() c.Client.WaitForShutdown()
c.wg.Wait() c.wg.Wait()
} }
// Notification types. These are defined here and processed from from reading
// a notificationChan to avoid handling these notifications directly in
// btcrpcclient callbacks, which isn't very Go-like and doesn't allow
// blocking client calls.
type (
// BlockConnected is a notification for a newly-attached block to the
// best chain.
BlockConnected keystore.BlockStamp
// BlockDisconnected is a notifcation that the block described by the
// BlockStamp was reorganized out of the best chain.
BlockDisconnected keystore.BlockStamp
// RecvTx is a notification for a transaction which pays to a wallet
// address.
RecvTx struct {
Tx *btcutil.Tx // Index is guaranteed to be set.
Block *txstore.Block // nil if unmined
}
// RedeemingTx is a notification for a transaction which spends an
// output controlled by the wallet.
RedeemingTx struct {
Tx *btcutil.Tx // Index is guaranteed to be set.
Block *txstore.Block // nil if unmined
}
// RescanProgress is a notification describing the current status
// of an in-progress rescan.
RescanProgress struct {
Hash *wire.ShaHash
Height int32
Time time.Time
}
// RescanFinished is a notification that a previous rescan request
// has finished.
RescanFinished struct {
Hash *wire.ShaHash
Height int32
Time time.Time
}
)
// Notifications returns a channel of parsed notifications sent by the remote
// bitcoin RPC server. This channel must be continually read or the process
// may abort for running out memory, as unread notifications are queued for
// later reads.
func (c *Client) Notifications() <-chan interface{} { func (c *Client) Notifications() <-chan interface{} {
return c.dequeueNotification return c.dequeueNotification
} }
// BlockStamp returns the latest block notified by the client, or an error
// if the client has been shut down.
func (c *Client) BlockStamp() (*keystore.BlockStamp, error) { func (c *Client) BlockStamp() (*keystore.BlockStamp, error) {
select { select {
case bs := <-c.currentBlock: case bs := <-c.currentBlock:
@ -146,33 +213,6 @@ func (c *Client) BlockStamp() (*keystore.BlockStamp, error) {
} }
} }
// Notification types. These are defined here and processed from from reading
// a notificationChan to avoid handling these notifications directly in
// btcrpcclient callbacks, which isn't very Go-like and doesn't allow
// blocking client calls.
type (
BlockConnected keystore.BlockStamp
BlockDisconnected keystore.BlockStamp
RecvTx struct {
Tx *btcutil.Tx // Index is guaranteed to be set.
Block *txstore.Block // nil if unmined
}
RedeemingTx struct {
Tx *btcutil.Tx // Index is guaranteed to be set.
Block *txstore.Block // nil if unmined
}
RescanProgress struct {
Hash *wire.ShaHash
Height int32
Time time.Time
}
RescanFinished struct {
Hash *wire.ShaHash
Height int32
Time time.Time
}
)
// parseBlock parses a btcws definition of the block a tx is mined it to the // parseBlock parses a btcws definition of the block a tx is mined it to the
// Block structure of the txstore package, and the block index. This is done // Block structure of the txstore package, and the block index. This is done
// here since btcrpcclient doesn't parse this nicely for us. // here since btcrpcclient doesn't parse this nicely for us.

View file

@ -89,7 +89,9 @@ func (e InsufficientFundsError) Error() string {
e.fee, e.in) e.fee, e.in)
} }
var UnsupportedTransactionType = errors.New("Only P2PKH transactions are supported") // ErrUnsupportedTransactionType represents an error where a transaction
// cannot be signed as the API only supports spending P2PKH outputs.
var ErrUnsupportedTransactionType = errors.New("Only P2PKH transactions are supported")
// ErrNonPositiveAmount represents an error where a bitcoin amount is // ErrNonPositiveAmount represents an error where a bitcoin amount is
// not positive (either negative, or zero). // not positive (either negative, or zero).
@ -103,6 +105,8 @@ var ErrNegativeFee = errors.New("fee is negative")
// measured in satoshis) added to transactions requiring a fee. // measured in satoshis) added to transactions requiring a fee.
const defaultFeeIncrement = 1e3 const defaultFeeIncrement = 1e3
// CreatedTx holds the state of a newly-created transaction and the change
// output (if one was added).
type CreatedTx struct { type CreatedTx struct {
tx *btcutil.Tx tx *btcutil.Tx
changeAddr btcutil.Address changeAddr btcutil.Address
@ -385,7 +389,7 @@ func signMsgTx(msgtx *wire.MsgTx, prevOutputs []txstore.Credit, store *keystore.
} }
apkh, ok := addrs[0].(*btcutil.AddressPubKeyHash) apkh, ok := addrs[0].(*btcutil.AddressPubKeyHash)
if !ok { if !ok {
return UnsupportedTransactionType return ErrUnsupportedTransactionType
} }
ai, err := store.Address(apkh) ai, err := store.Address(apkh)

View file

@ -114,7 +114,7 @@ const (
CreditImmature CreditImmature
) )
// category returns the category of the credit. The passed block chain height is // Category returns the category of the credit. The passed block chain height is
// used to distinguish immature from mature coinbase outputs. // used to distinguish immature from mature coinbase outputs.
func (c *Credit) Category(chainHeight int32) CreditCategory { func (c *Credit) Category(chainHeight int32) CreditCategory {
c.s.mtx.RLock() c.s.mtx.RLock()

View file

@ -1146,7 +1146,9 @@ func (u *unconfirmedStore) WriteTo(w io.Writer) (int64, error) {
return n64, nil return n64, nil
} }
// TODO: set this automatically. // MarkDirty marks that changes have been made to the transaction store.
// This should be run after any modifications are performed to the store
// or any of its records.
func (s *Store) MarkDirty() { func (s *Store) MarkDirty() {
s.mtx.Lock() s.mtx.Lock()
defer s.mtx.Unlock() defer s.mtx.Unlock()
@ -1154,6 +1156,8 @@ func (s *Store) MarkDirty() {
s.dirty = true s.dirty = true
} }
// WriteIfDirty writes the entire transaction store to permanent storage if
// the dirty flag has been set (see MarkDirty).
func (s *Store) WriteIfDirty() error { func (s *Store) WriteIfDirty() error {
s.mtx.RLock() s.mtx.RLock()
if !s.dirty { if !s.dirty {

View file

@ -589,18 +589,18 @@ func TestCannotReplaceEmpoweredSeries(t *testing.T) {
var seriesID uint32 = 1 var seriesID uint32 = 1
if err := pool.CreateSeries(1, seriesID, 3, []string{pubKey0, pubKey1, pubKey2, pubKey3}); err != nil { if err := pool.CreateSeries(1, seriesID, 3, []string{pubKey0, pubKey1, pubKey2, pubKey3}); err != nil {
t.Fatalf("Failed to create series", err) t.Fatalf("Failed to create series: %v", err)
} }
// We need to unlock the manager in order to empower a series. // We need to unlock the manager in order to empower a series.
manager.Unlock(privPassphrase) manager.Unlock(privPassphrase)
if err := pool.EmpowerSeries(seriesID, privKey1); err != nil { if err := pool.EmpowerSeries(seriesID, privKey1); err != nil {
t.Fatalf("Failed to empower series", err) t.Fatalf("Failed to empower series: %v", err)
} }
if err := pool.ReplaceSeries(1, seriesID, 2, []string{pubKey0, pubKey2, pubKey3}); err == nil { if err := pool.ReplaceSeries(1, seriesID, 2, []string{pubKey0, pubKey2, pubKey3}); err == nil {
t.Errorf("Replaced an empowered series. That should not be possible", err) t.Errorf("Replaced an empowered series. That should not be possible: %v", err)
} else { } else {
gotErr := err.(waddrmgr.ManagerError) gotErr := err.(waddrmgr.ManagerError)
wantErrCode := waddrmgr.ErrorCode(waddrmgr.ErrSeriesAlreadyEmpowered) wantErrCode := waddrmgr.ErrorCode(waddrmgr.ErrSeriesAlreadyEmpowered)
@ -693,13 +693,13 @@ func TestReplaceExistingSeries(t *testing.T) {
testID := data.testID testID := data.testID
if err := pool.CreateSeries(data.orig.version, seriesID, data.orig.reqSigs, data.orig.pubKeys); err != nil { if err := pool.CreateSeries(data.orig.version, seriesID, data.orig.reqSigs, data.orig.pubKeys); err != nil {
t.Fatalf("Test #%d: failed to create series in replace series setup", t.Fatalf("Test #%d: failed to create series in replace series setup: %v",
testID, err) testID, err)
} }
if err := pool.ReplaceSeries(data.replaceWith.version, seriesID, if err := pool.ReplaceSeries(data.replaceWith.version, seriesID,
data.replaceWith.reqSigs, data.replaceWith.pubKeys); err != nil { data.replaceWith.reqSigs, data.replaceWith.pubKeys); err != nil {
t.Errorf("Test #%d: replaceSeries failed", testID, err) t.Errorf("Test #%d: replaceSeries failed: %v", testID, err)
} }
validateReplaceSeries(t, pool, testID, data.replaceWith) validateReplaceSeries(t, pool, testID, data.replaceWith)
@ -959,7 +959,7 @@ func validateLoadAllSeries(t *testing.T, pool *votingpool.Pool, testID int, seri
sortedKeys := votingpool.CanonicalKeyOrder(seriesData.pubKeys) sortedKeys := votingpool.CanonicalKeyOrder(seriesData.pubKeys)
if !reflect.DeepEqual(publicKeys, sortedKeys) { if !reflect.DeepEqual(publicKeys, sortedKeys) {
t.Errorf("Test #%d, series #%d: public keys mismatch. Got %d, want %d", t.Errorf("Test #%d, series #%d: public keys mismatch. Got %v, want %v",
testID, seriesData.id, sortedKeys, publicKeys) testID, seriesData.id, sortedKeys, publicKeys)
} }
@ -973,7 +973,7 @@ func validateLoadAllSeries(t *testing.T, pool *votingpool.Pool, testID int, seri
foundPrivKeys = votingpool.CanonicalKeyOrder(foundPrivKeys) foundPrivKeys = votingpool.CanonicalKeyOrder(foundPrivKeys)
privKeys := votingpool.CanonicalKeyOrder(seriesData.privKeys) privKeys := votingpool.CanonicalKeyOrder(seriesData.privKeys)
if !reflect.DeepEqual(privKeys, foundPrivKeys) { if !reflect.DeepEqual(privKeys, foundPrivKeys) {
t.Errorf("Test #%d, series #%d: private keys mismatch. Got %d, want %d", t.Errorf("Test #%d, series #%d: private keys mismatch. Got %v, want %v",
testID, seriesData.id, foundPrivKeys, privKeys) testID, seriesData.id, foundPrivKeys, privKeys)
} }
} }
@ -1085,14 +1085,14 @@ func createTestPubKeys(t *testing.T, number, offset int) []*hdkeychain.ExtendedK
xpubRaw := "xpub661MyMwAqRbcFwdnYF5mvCBY54vaLdJf8c5ugJTp5p7PqF9J1USgBx12qYMnZ9yUiswV7smbQ1DSweMqu8wn7Jociz4PWkuJ6EPvoVEgMw7" xpubRaw := "xpub661MyMwAqRbcFwdnYF5mvCBY54vaLdJf8c5ugJTp5p7PqF9J1USgBx12qYMnZ9yUiswV7smbQ1DSweMqu8wn7Jociz4PWkuJ6EPvoVEgMw7"
xpubKey, err := hdkeychain.NewKeyFromString(xpubRaw) xpubKey, err := hdkeychain.NewKeyFromString(xpubRaw)
if err != nil { if err != nil {
t.Fatalf("Failed to generate new key", err) t.Fatalf("Failed to generate new key: %v", err)
} }
keys := make([]*hdkeychain.ExtendedKey, number) keys := make([]*hdkeychain.ExtendedKey, number)
for i := uint32(0); i < uint32(len(keys)); i++ { for i := uint32(0); i < uint32(len(keys)); i++ {
chPubKey, err := xpubKey.Child(i + uint32(offset)) chPubKey, err := xpubKey.Child(i + uint32(offset))
if err != nil { if err != nil {
t.Fatalf("Failed to generate child key", err) t.Fatalf("Failed to generate child key: %v", err)
} }
keys[i] = chPubKey keys[i] = chPubKey
} }
@ -1303,7 +1303,7 @@ func TestSerialization(t *testing.T) {
} }
if row.Active != test.active { if row.Active != test.active {
t.Errorf("Serialization #%d - active mismatch: got %d want %d", t.Errorf("Serialization #%d - active mismatch: got %v want %v",
testNum, row.Active, test.active) testNum, row.Active, test.active)
} }

View file

@ -44,7 +44,7 @@ const (
// the underlying hierarchical deterministic key derivation. // the underlying hierarchical deterministic key derivation.
MaxAddressesPerAccount = hdkeychain.HardenedKeyStart - 1 MaxAddressesPerAccount = hdkeychain.HardenedKeyStart - 1
// importedAddrAccount is the account number to use for all imported // ImportedAddrAccount is the account number to use for all imported
// addresses. This is useful since normal accounts are derived from the // addresses. This is useful since normal accounts are derived from the
// root hierarchical deterministic key and imported addresses do not // root hierarchical deterministic key and imported addresses do not
// fit into that model. // fit into that model.

View file

@ -36,13 +36,10 @@ import (
"github.com/btcsuite/btcwallet/txstore" "github.com/btcsuite/btcwallet/txstore"
) )
var ( // ErrNotSynced describes an error where an operation cannot complete
ErrNoWalletFiles = errors.New("no wallet files") // due wallet being out of sync (and perhaps currently syncing with)
// the remote chain server.
ErrWalletExists = errors.New("wallet already exists") var ErrNotSynced = errors.New("wallet is not synchronized with the chain server")
ErrNotSynced = errors.New("wallet is not synchronized with the chain server")
)
// networkDir returns the directory name of a network directory to hold wallet // networkDir returns the directory name of a network directory to hold wallet
// files. // files.
@ -190,7 +187,7 @@ func (w *Wallet) ListenDisconnectedBlocks() (<-chan keystore.BlockStamp, error)
return w.disconnectedBlocks, nil return w.disconnectedBlocks, nil
} }
// ListenDisconnectedBlocks returns a channel that passes the current lock state // ListenKeystoreLockStatus returns a channel that passes the current lock state
// of the wallet keystore anytime the keystore is locked or unlocked. The value // of the wallet keystore anytime the keystore is locked or unlocked. The value
// is true for locked, and false for unlocked. The channel must be read, or // is true for locked, and false for unlocked. The channel must be read, or
// other wallet methods will block. // other wallet methods will block.
@ -429,7 +426,7 @@ func (w *Wallet) WaitForChainSync() {
<-w.chainSynced <-w.chainSynced
} }
// SynchedChainTip returns the hash and height of the block of the most // SyncedChainTip returns the hash and height of the block of the most
// recently seen block in the main chain. It returns errors if the // recently seen block in the main chain. It returns errors if the
// wallet has not yet been marked as synched with the chain. // wallet has not yet been marked as synched with the chain.
func (w *Wallet) SyncedChainTip() (*keystore.BlockStamp, error) { func (w *Wallet) SyncedChainTip() (*keystore.BlockStamp, error) {
@ -522,9 +519,13 @@ out:
w.wg.Done() w.wg.Done()
} }
func (w *Wallet) CreateSimpleTx(pairs map[string]btcutil.Amount, // CreateSimpleTx creates a new signed transaction spending unspent P2PKH
minconf int) (*CreatedTx, error) { // outputs with at laest minconf confirmations spending to any number of
// address/amount pairs. Change and an appropiate transaction fee are
// automatically included, if necessary. All transaction creation through
// this function is serialized to prevent the creation of many transactions
// which spend the same outputs.
func (w *Wallet) CreateSimpleTx(pairs map[string]btcutil.Amount, minconf int) (*CreatedTx, error) {
req := createTxRequest{ req := createTxRequest{
pairs: pairs, pairs: pairs,
minconf: minconf, minconf: minconf,
@ -547,6 +548,11 @@ type (
err chan error err chan error
} }
// HeldUnlock is a tool to prevent the wallet from automatically
// locking after some timeout before an operation which needed
// the unlocked wallet has finished. Any aquired HeldUnlock
// *must* be released (preferably with a defer) or the wallet
// will forever remain unlocked.
HeldUnlock chan struct{} HeldUnlock chan struct{}
) )
@ -653,7 +659,12 @@ func (w *Wallet) Locked() bool {
return <-w.lockState return <-w.lockState
} }
// HoldUnlock prevents the wallet from being locked, // HoldUnlock prevents the wallet from being locked. The HeldUnlock object
// *must* be released, or the wallet will forever remain unlocked.
//
// TODO: To prevent the above scenario, perhaps closures should be passed
// to the walletLocker goroutine and disallow callers from explicitly
// handling the locking mechanism.
func (w *Wallet) HoldUnlock() (HeldUnlock, error) { func (w *Wallet) HoldUnlock() (HeldUnlock, error) {
req := make(chan HeldUnlock) req := make(chan HeldUnlock)
w.holdUnlockRequests <- req w.holdUnlockRequests <- req