Refill keypool if empty and wallet is unlocked.
This commit is contained in:
parent
74d7178aa8
commit
38ed238a7f
4 changed files with 113 additions and 42 deletions
2
cmd.go
2
cmd.go
|
@ -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.
|
||||
|
|
29
cmdmgr.go
29
cmdmgr.go
|
@ -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() {
|
||||
|
|
10
createtx.go
10
createtx.go
|
@ -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
|
||||
|
|
114
wallet/wallet.go
114
wallet/wallet.go
|
@ -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.
|
||||
|
|
Loading…
Reference in a new issue