Skip to content

Commit

Permalink
Implement lockunspent and listlockunspent.
Browse files Browse the repository at this point in the history
Closes #50.

Closes #55.
  • Loading branch information
jrick committed Jun 23, 2014
1 parent 879d2cb commit 85af882
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 11 deletions.
41 changes: 40 additions & 1 deletion account.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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() {
Expand Down
12 changes: 7 additions & 5 deletions acctmgr.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions createtx.go
Original file line number Diff line number Diff line change
Expand Up @@ -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])
}
}
Expand Down
3 changes: 2 additions & 1 deletion createtx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
Expand Down
54 changes: 52 additions & 2 deletions rpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 "+
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion txstore/serialization.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down
1 change: 0 additions & 1 deletion txstore/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down

0 comments on commit 85af882

Please sign in to comment.