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 {
name string
*wallet.Wallet
TxStore *txstore.Store
TxStore *txstore.Store
lockedOutpoints map[btcwire.OutPoint]struct{}
}
// Lock locks the underlying wallet for an account.
@ -432,6 +433,44 @@ func (a *Account) exportBase64() (map[string]string, error) {
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
// each address stored in a wallet.
func (a *Account) Track() {

View file

@ -177,9 +177,10 @@ func openSavedAccount(name string, cfg *config) (*Account, error) {
wlt := new(wallet.Wallet)
txs := txstore.New()
a := &Account{
name: name,
Wallet: wlt,
TxStore: txs,
name: name,
Wallet: wlt,
TxStore: txs,
lockedOutpoints: map[btcwire.OutPoint]struct{}{},
}
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
// written immediately to disk.
a := &Account{
Wallet: wlt,
TxStore: txstore.New(),
Wallet: wlt,
TxStore: txstore.New(),
lockedOutpoints: map[btcwire.OutPoint]struct{}{},
}
if err := am.RegisterNewAccount(a); err != nil {
return err

View file

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

View file

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

View file

@ -728,6 +728,7 @@ func (s *rpcServer) PostClientRPC(w http.ResponseWriter, r *http.Request) {
id = cmd.Id()
}
if err != nil {
fmt.Printf("%s\n", rpcRequest)
_, err := w.Write(marshalError(idPointer(cmd.Id())))
if err != nil {
log.Warnf("Client sent invalid request but unable "+
@ -828,10 +829,12 @@ var rpcHandlers = map[string]requestHandler{
"importprivkey": ImportPrivKey,
"keypoolrefill": KeypoolRefill,
"listaccounts": ListAccounts,
"listlockunspent": ListLockUnspent,
"listreceivedbyaddress": ListReceivedByAddress,
"listsinceblock": ListSinceBlock,
"listtransactions": ListTransactions,
"listunspent": ListUnspent,
"lockunspent": LockUnspent,
"sendfrom": SendFrom,
"sendmany": SendMany,
"sendtoaddress": SendToAddress,
@ -853,9 +856,7 @@ var rpcHandlers = map[string]requestHandler{
"getwalletinfo": Unimplemented,
"importwallet": Unimplemented,
"listaddressgroupings": Unimplemented,
"listlockunspent": Unimplemented,
"listreceivedbyaccount": Unimplemented,
"lockunspent": Unimplemented,
"move": Unimplemented,
"setaccount": Unimplemented,
"stop": Unimplemented,
@ -1589,6 +1590,20 @@ func ListAccounts(icmd btcjson.Cmd) (interface{}, error) {
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
// a slice of objects, each one containing:
// "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)
}
// 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
// sending payment transactions.
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)
}

View file

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