Refill keypool if empty and wallet is unlocked.

This commit is contained in:
Josh Rickmar 2013-11-21 11:57:28 -05:00
parent 74d7178aa8
commit 38ed238a7f
4 changed files with 113 additions and 42 deletions

2
cmd.go
View file

@ -334,7 +334,7 @@ func main() {
accounts.Unlock()
default:
log.Errorf("cannot open wallet: %v", err)
log.Warnf("cannot open wallet: %v", err)
}
// Read CA file to verify a btcd TLS connection.

View file

@ -388,8 +388,34 @@ func GetNewAddress(frontend chan []byte, icmd btcjson.Cmd) {
return
}
// Get current block's height and hash.
bs, err := GetCurBlock()
if err != nil {
e := &btcjson.Error{
Code: btcjson.ErrInternal.Code,
Message: "btcd disconnected",
}
ReplyError(frontend, cmd.Id(), e)
return
}
// Get next address from wallet.
addr, err := a.NextUnusedAddress()
addr, err := a.NextChainedAddress(&bs)
if err == wallet.ErrWalletLocked {
// The wallet is locked error may be sent if the keypool needs
// to be refilled, but the wallet is currently in a locked
// state. Notify the frontend that an unlock is needed to
// refill the keypool.
ReplyError(frontend, cmd.Id(), &btcjson.ErrWalletKeypoolRanOut)
return
} else if err != nil {
e := &btcjson.Error{
Code: btcjson.ErrWallet.Code,
Message: err.Error(),
}
ReplyError(frontend, cmd.Id(), e)
return
}
if err != nil {
// TODO(jrick): generate new addresses if the address pool is
// empty.
@ -893,6 +919,7 @@ func WalletPassphrase(frontend chan []byte, icmd btcjson.Cmd) {
&btcjson.ErrWalletPassphraseIncorrect)
return
}
// XXX
ReplySuccess(frontend, cmd.Id(), nil)
NotifyWalletLockStateChange("", false)
go func() {

View file

@ -203,9 +203,15 @@ func (w *Account) txToPairs(pairs map[string]int64, fee int64, minconf int) (*Cr
// Create a new address to spend leftover outputs to.
// TODO(jrick): use the next chained address, not the next unused.
var err error
changeAddr, err = w.NextUnusedAddress()
// Get current block's height and hash.
bs, err := GetCurBlock()
if err != nil {
return nil, fmt.Errorf("failed to get next unused address: %s", err)
return nil, err
}
changeAddr, err = w.NextChainedAddress(&bs)
if err != nil {
return nil, fmt.Errorf("failed to get next address: %s", err)
}
// Spend change

View file

@ -46,6 +46,9 @@ const (
// Maximum length in bytes of a comment that can have a size represented
// as a uint16.
maxCommentLen = (1 << 16) - 1
// Number of addresses to extend keypool by.
nKeypoolIncrement = 100
)
const (
@ -409,9 +412,6 @@ func NewWallet(name, desc string, passphrase []byte, net btcwire.BitcoinNet,
return nil, err
}
// Define number of addresses to pre-generate for keypool.
const nPregenerated = 100
// Create and fill wallet.
w := &Wallet{
version: 0, // TODO(jrick): implement versioning
@ -432,7 +432,7 @@ func NewWallet(name, desc string, passphrase []byte, net btcwire.BitcoinNet,
addrCommentMap: make(map[addressHashKey]comment),
txCommentMap: make(map[transactionHashKey]comment),
chainIdxMap: make(map[int64]addressHashKey),
lastChainIdx: nPregenerated - 1,
lastChainIdx: rootKeyChainIdx,
}
copy(w.name[:], []byte(name))
copy(w.desc[:], []byte(desc))
@ -441,30 +441,9 @@ func NewWallet(name, desc string, passphrase []byte, net btcwire.BitcoinNet,
w.addrMap[addressHashKey(w.keyGenerator.pubKeyHash[:])] = &w.keyGenerator
w.chainIdxMap[rootKeyChainIdx] = addressHashKey(w.keyGenerator.pubKeyHash[:])
// Pre-generate encrypted addresses and add to maps.
addr := &w.keyGenerator
cc := addr.chaincode[:]
for i := 0; i < nPregenerated; i++ {
// Wallet has not been returned to the caller yet, so need to
// lock and unlock the previous address's key's clear text
// private key mutex.
privkey, err := ChainedPrivKey(addr.privKeyCT.key, addr.pubKey, cc)
if err != nil {
return nil, err
}
newaddr, err := newBtcAddress(privkey, nil, createdAt, true)
if err != nil {
return nil, err
}
if err = newaddr.encrypt(aeskey); err != nil {
return nil, err
}
addrKey := addressHashKey(newaddr.pubKeyHash[:])
w.addrMap[addrKey] = newaddr
newaddr.chainIndex = addr.chainIndex + 1
w.chainIdxMap[newaddr.chainIndex] = addrKey
copy(newaddr.chaincode[:], cc) // armory does this.. but why?
addr = newaddr
// Fill keypool.
if err := w.extendKeypool(nKeypoolIncrement, aeskey, createdAt); err != nil {
return nil, err
}
return w, nil
@ -703,29 +682,88 @@ func (w *Wallet) Version() (string, int) {
return "", 0
}
// NextUnusedAddress attempts to get the next chained address.
//
// TODO(jrick): this currently relies on pre-generated addresses
// and will return an empty string if the address pool has run out.
func (w *Wallet) NextUnusedAddress() (string, error) {
// NextChainedAddress attempts to get the next chained address,
// refilling the keypool if necessary.
func (w *Wallet) NextChainedAddress(bs *BlockStamp) (string, error) {
// Attempt to get address hash of next chained address.
next160, ok := w.chainIdxMap[w.highestUsed+1]
if !ok {
// TODO(jrick): Re-fill key pool.
return "", errors.New("cannot find generated address")
// Extending the keypool requires an unlocked wallet.
aeskey := make([]byte, 32)
w.secret.Lock()
if len(w.secret.key) != 32 {
w.secret.Unlock()
return "", ErrWalletLocked
}
copy(aeskey, w.secret.key)
w.secret.Unlock()
err := w.extendKeypool(nKeypoolIncrement, aeskey, bs)
if err != nil {
return "", err
}
next160, ok = w.chainIdxMap[w.highestUsed+1]
if !ok {
return "", errors.New("chain index map inproperly updated")
}
}
w.highestUsed++
// Look up address.
addr := w.addrMap[next160]
if addr == nil {
addr, ok := w.addrMap[next160]
if !ok {
return "", errors.New("cannot find generated address")
}
w.highestUsed++
// Create and return payment address for address hash.
return addr.paymentAddress(w.net)
}
// extendKeypool grows the keypool by n addresses.
func (w *Wallet) extendKeypool(n uint, aeskey []byte, bs *BlockStamp) error {
// Get last chained address. New chained addresses will be
// chained off of this address's chaincode and private key.
addrKey := w.chainIdxMap[w.lastChainIdx]
addr, ok := w.addrMap[addrKey]
if !ok {
return errors.New("expected last chained address not found")
}
privkey, err := addr.unlock(aeskey)
if err != nil {
return err
}
cc := addr.chaincode[:]
// Create n encrypted addresses and add each to the wallet's
// bookkeeping maps.
for i := uint(0); i < n; i++ {
privkey, err = ChainedPrivKey(privkey, addr.pubKey, cc)
if err != nil {
return err
}
newaddr, err := newBtcAddress(privkey, nil, bs, true)
if err != nil {
return err
}
if err = newaddr.encrypt(aeskey); err != nil {
return err
}
addrKey := addressHashKey(newaddr.pubKeyHash[:])
w.addrMap[addrKey] = newaddr
newaddr.chainIndex = addr.chainIndex + 1
w.chainIdxMap[newaddr.chainIndex] = addrKey
w.lastChainIdx++
// armory does this.. but all the chaincodes are equal so why
// not use the root's?
copy(newaddr.chaincode[:], cc)
addr = newaddr
}
return nil
}
// addrHashForAddress decodes and returns the address hash for a
// payment address string, performing some basic sanity checking that it
// matches the Bitcoin network used by the wallet.