Implement lockunspent and listlockunspent.

Closes #50.

Closes #55.
This commit is contained in:
Josh Rickmar 2014-06-23 16:59:57 -05:00
parent 879d2cb27f
commit 85af882c13
7 changed files with 108 additions and 11 deletions

View file

@ -35,7 +35,8 @@ import (
type Account struct { type Account struct {
name string name string
*wallet.Wallet *wallet.Wallet
TxStore *txstore.Store TxStore *txstore.Store
lockedOutpoints map[btcwire.OutPoint]struct{}
} }
// Lock locks the underlying wallet for an account. // Lock locks the underlying wallet for an account.
@ -432,6 +433,44 @@ func (a *Account) exportBase64() (map[string]string, error) {
return m, nil return m, nil
} }
// LockedOutpoint returns whether an outpoint has been marked as locked and
// should not be used as an input for created transactions.
func (a *Account) LockedOutpoint(op btcwire.OutPoint) bool {
_, locked := a.lockedOutpoints[op]
return locked
}
// LockOutpoint marks an outpoint as locked, that is, it should not be used as
// an input for newly created transactions.
func (a *Account) LockOutpoint(op btcwire.OutPoint) {
a.lockedOutpoints[op] = struct{}{}
}
// UnlockOutpoint marks an outpoint as unlocked, that is, it may be used as an
// input for newly created transactions.
func (a *Account) UnlockOutpoint(op btcwire.OutPoint) {
delete(a.lockedOutpoints, op)
}
// ResetLockedOutpoints resets the set of locked outpoints so all may be used
// as inputs for new transactions.
func (a *Account) ResetLockedOutpoints() {
a.lockedOutpoints = map[btcwire.OutPoint]struct{}{}
}
// LockedOutpoints returns a slice of currently locked outpoints. This is
// intended to be used by marshaling the result as a JSON array for
// listlockunspent RPC results.
func (a *Account) LockedOutpoints() []btcjson.TransactionInput {
locked := make([]btcjson.TransactionInput, len(a.lockedOutpoints))
i := 0
for op := range a.lockedOutpoints {
locked[i] = btcjson.TransactionInput{op.Hash.String(), op.Index}
i++
}
return locked
}
// Track requests btcd to send notifications of new transactions for // Track requests btcd to send notifications of new transactions for
// each address stored in a wallet. // each address stored in a wallet.
func (a *Account) Track() { func (a *Account) Track() {

View file

@ -177,9 +177,10 @@ func openSavedAccount(name string, cfg *config) (*Account, error) {
wlt := new(wallet.Wallet) wlt := new(wallet.Wallet)
txs := txstore.New() txs := txstore.New()
a := &Account{ a := &Account{
name: name, name: name,
Wallet: wlt, Wallet: wlt,
TxStore: txs, TxStore: txs,
lockedOutpoints: map[btcwire.OutPoint]struct{}{},
} }
walletPath := accountFilename("wallet.bin", name, netdir) walletPath := accountFilename("wallet.bin", name, netdir)
@ -708,8 +709,9 @@ func (am *AccountManager) CreateEncryptedWallet(passphrase []byte) error {
// manager. Registering will fail if the new account can not be // manager. Registering will fail if the new account can not be
// written immediately to disk. // written immediately to disk.
a := &Account{ a := &Account{
Wallet: wlt, Wallet: wlt,
TxStore: txstore.New(), TxStore: txstore.New(),
lockedOutpoints: map[btcwire.OutPoint]struct{}{},
} }
if err := am.RegisterNewAccount(a); err != nil { if err := am.RegisterNewAccount(a); err != nil {
return err return err

View file

@ -197,6 +197,12 @@ func (a *Account) txToPairs(pairs map[string]btcutil.Amount,
continue continue
} }
} }
// Locked unspent outputs are skipped.
if a.LockedOutpoint(*unspent[i].OutPoint()) {
continue
}
eligible = append(eligible, unspent[i]) eligible = append(eligible, unspent[i])
} }
} }

View file

@ -89,7 +89,8 @@ func TestFakeTxs(t *testing.T) {
return return
} }
a := &Account{ a := &Account{
Wallet: w, Wallet: w,
lockedOutpoints: map[btcwire.OutPoint]struct{}{},
} }
w.Unlock([]byte("banana")) w.Unlock([]byte("banana"))

View file

@ -728,6 +728,7 @@ func (s *rpcServer) PostClientRPC(w http.ResponseWriter, r *http.Request) {
id = cmd.Id() id = cmd.Id()
} }
if err != nil { if err != nil {
fmt.Printf("%s\n", rpcRequest)
_, err := w.Write(marshalError(idPointer(cmd.Id()))) _, err := w.Write(marshalError(idPointer(cmd.Id())))
if err != nil { if err != nil {
log.Warnf("Client sent invalid request but unable "+ log.Warnf("Client sent invalid request but unable "+
@ -828,10 +829,12 @@ var rpcHandlers = map[string]requestHandler{
"importprivkey": ImportPrivKey, "importprivkey": ImportPrivKey,
"keypoolrefill": KeypoolRefill, "keypoolrefill": KeypoolRefill,
"listaccounts": ListAccounts, "listaccounts": ListAccounts,
"listlockunspent": ListLockUnspent,
"listreceivedbyaddress": ListReceivedByAddress, "listreceivedbyaddress": ListReceivedByAddress,
"listsinceblock": ListSinceBlock, "listsinceblock": ListSinceBlock,
"listtransactions": ListTransactions, "listtransactions": ListTransactions,
"listunspent": ListUnspent, "listunspent": ListUnspent,
"lockunspent": LockUnspent,
"sendfrom": SendFrom, "sendfrom": SendFrom,
"sendmany": SendMany, "sendmany": SendMany,
"sendtoaddress": SendToAddress, "sendtoaddress": SendToAddress,
@ -853,9 +856,7 @@ var rpcHandlers = map[string]requestHandler{
"getwalletinfo": Unimplemented, "getwalletinfo": Unimplemented,
"importwallet": Unimplemented, "importwallet": Unimplemented,
"listaddressgroupings": Unimplemented, "listaddressgroupings": Unimplemented,
"listlockunspent": Unimplemented,
"listreceivedbyaccount": Unimplemented, "listreceivedbyaccount": Unimplemented,
"lockunspent": Unimplemented,
"move": Unimplemented, "move": Unimplemented,
"setaccount": Unimplemented, "setaccount": Unimplemented,
"stop": Unimplemented, "stop": Unimplemented,
@ -1589,6 +1590,20 @@ func ListAccounts(icmd btcjson.Cmd) (interface{}, error) {
return AcctMgr.ListAccounts(cmd.MinConf), nil return AcctMgr.ListAccounts(cmd.MinConf), nil
} }
// ListLockUnspent handles a listlockunspent request by returning an array of
// all locked outpoints.
func ListLockUnspent(icmd btcjson.Cmd) (interface{}, error) {
// Due to our poor account support, this assumes only the default
// account is available. When the keystore and account heirarchies are
// reversed, the locked outpoints mapping will cover all accounts.
a, err := AcctMgr.Account("")
if err != nil {
return nil, err
}
return a.LockedOutpoints(), nil
}
// ListReceivedByAddress handles a listreceivedbyaddress request by returning // ListReceivedByAddress handles a listreceivedbyaddress request by returning
// a slice of objects, each one containing: // a slice of objects, each one containing:
// "account": the account of the receiving address; // "account": the account of the receiving address;
@ -1851,6 +1866,41 @@ func ListUnspent(icmd btcjson.Cmd) (interface{}, error) {
return AcctMgr.ListUnspent(cmd.MinConf, cmd.MaxConf, addresses) return AcctMgr.ListUnspent(cmd.MinConf, cmd.MaxConf, addresses)
} }
// LockUnspent handles the lockunspent command.
func LockUnspent(icmd btcjson.Cmd) (interface{}, error) {
cmd, ok := icmd.(*btcjson.LockUnspentCmd)
if !ok {
return nil, btcjson.ErrInternal
}
// Due to our poor account support, this assumes only the default
// account is available. When the keystore and account heirarchies are
// reversed, the locked outpoints mapping will cover all accounts.
a, err := AcctMgr.Account("")
if err != nil {
return nil, err
}
switch {
case cmd.Unlock && len(cmd.Transactions) == 0:
a.ResetLockedOutpoints()
default:
for _, input := range cmd.Transactions {
txSha, err := btcwire.NewShaHashFromStr(input.Txid)
if err != nil {
return nil, ParseError{err}
}
op := btcwire.OutPoint{Hash: *txSha, Index: input.Vout}
if cmd.Unlock {
a.UnlockOutpoint(op)
} else {
a.LockOutpoint(op)
}
}
}
return true, nil
}
// sendPairs is a helper routine to reduce duplicated code when creating and // sendPairs is a helper routine to reduce duplicated code when creating and
// sending payment transactions. // sending payment transactions.
func sendPairs(icmd btcjson.Cmd, account string, amounts map[string]btcutil.Amount, func sendPairs(icmd btcjson.Cmd, account string, amounts map[string]btcutil.Amount,

View file

@ -566,7 +566,7 @@ func (t *txRecord) ReadFrom(r io.Reader) (int64, error) {
} }
} }
c := &credit{change, false, spentBy} c := &credit{change, spentBy}
credits = append(credits, c) credits = append(credits, c)
} }

View file

@ -250,7 +250,6 @@ type debits struct {
// credit describes a transaction output which was or is spendable by wallet. // credit describes a transaction output which was or is spendable by wallet.
type credit struct { type credit struct {
change bool change bool
locked bool
spentBy *BlockTxKey // nil if unspent spentBy *BlockTxKey // nil if unspent
} }