aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--contrib/spendfrom/README32
-rw-r--r--contrib/spendfrom/setup.py9
-rwxr-xr-xcontrib/spendfrom/spendfrom.py267
-rw-r--r--src/main.cpp53
-rw-r--r--src/main.h6
-rw-r--r--src/rpcrawtransaction.cpp2
-rw-r--r--src/wallet.cpp2
7 files changed, 339 insertions, 32 deletions
diff --git a/contrib/spendfrom/README b/contrib/spendfrom/README
new file mode 100644
index 0000000000..8a087a0c1e
--- /dev/null
+++ b/contrib/spendfrom/README
@@ -0,0 +1,32 @@
+Use the raw transactions API to send coins received on a particular
+address (or addresses).
+
+Depends on jsonrpc
+
+Usage:
+
+spendfrom.py --from=FROMADDRESS1[,FROMADDRESS2] --to=TOADDRESS --amount=amount \
+ --fee=fee --datadir=/path/to/.bitcoin --testnet --dry_run
+
+With no arguments, outputs a list of amounts associated with addresses.
+
+With arguments, sends coins received by the FROMADDRESS addresses to the TOADDRESS.
+
+You may explictly specify how much fee to pay (a fee more than 1% of the amount
+will fail, though, to prevent bitcoin-losing accidents). Spendfrom may fail if
+it thinks the transaction would never be confirmed (if the amount being sent is
+too small, or if the transaction is too many bytes for the fee).
+
+If a change output needs to be created, the change will be sent to the last
+FROMADDRESS (if you specify just one FROMADDRESS, change will go back to it).
+
+If --datadir is not specified, the default datadir is used.
+
+The --dry_run option will just create and sign the the transaction and print
+the transaction data (as hexadecimal), instead of broadcasting it.
+
+If the transaction is created and broadcast successfully, a transaction id
+is printed.
+
+If this was a tool for end-users and not programmers, it would have much friendlier
+error-handling.
diff --git a/contrib/spendfrom/setup.py b/contrib/spendfrom/setup.py
new file mode 100644
index 0000000000..01b9768a5b
--- /dev/null
+++ b/contrib/spendfrom/setup.py
@@ -0,0 +1,9 @@
+from distutils.core import setup
+setup(name='btcspendfrom',
+ version='1.0',
+ description='Command-line utility for bitcoin "coin control"',
+ author='Gavin Andresen',
+ author_email='gavin@bitcoinfoundation.org',
+ requires=['jsonrpc'],
+ scripts=['spendfrom.py'],
+ )
diff --git a/contrib/spendfrom/spendfrom.py b/contrib/spendfrom/spendfrom.py
new file mode 100755
index 0000000000..72ee0425eb
--- /dev/null
+++ b/contrib/spendfrom/spendfrom.py
@@ -0,0 +1,267 @@
+#!/usr/bin/env python
+#
+# Use the raw transactions API to spend bitcoins received on particular addresses,
+# and send any change back to that same address.
+#
+# Example usage:
+# spendfrom.py # Lists available funds
+# spendfrom.py --from=ADDRESS --to=ADDRESS --amount=11.00
+#
+# Assumes it will talk to a bitcoind or Bitcoin-Qt running
+# on localhost.
+#
+# Depends on jsonrpc
+#
+
+from decimal import *
+import getpass
+import math
+import os
+import os.path
+import platform
+import sys
+import time
+from jsonrpc import ServiceProxy, json
+
+BASE_FEE=Decimal("0.001")
+
+def check_json_precision():
+ """Make sure json library being used does not lose precision converting BTC values"""
+ n = Decimal("20000000.00000003")
+ satoshis = int(json.loads(json.dumps(float(n)))*1.0e8)
+ if satoshis != 2000000000000003:
+ raise RuntimeError("JSON encode/decode loses precision")
+
+def determine_db_dir():
+ """Return the default location of the bitcoin data directory"""
+ if platform.system() == "Darwin":
+ return os.path.expanduser("~/Library/Application Support/Bitcoin/")
+ elif platform.system() == "Windows":
+ return os.path.join(os.environ['APPDATA'], "Bitcoin")
+ return os.path.expanduser("~/.bitcoin")
+
+def read_bitcoin_config(dbdir):
+ """Read the bitcoin.conf file from dbdir, returns dictionary of settings"""
+ from ConfigParser import SafeConfigParser
+
+ class FakeSecHead(object):
+ def __init__(self, fp):
+ self.fp = fp
+ self.sechead = '[all]\n'
+ def readline(self):
+ if self.sechead:
+ try: return self.sechead
+ finally: self.sechead = None
+ else:
+ s = self.fp.readline()
+ if s.find('#') != -1:
+ s = s[0:s.find('#')].strip() +"\n"
+ return s
+
+ config_parser = SafeConfigParser()
+ config_parser.readfp(FakeSecHead(open(os.path.join(dbdir, "bitcoin.conf"))))
+ return dict(config_parser.items("all"))
+
+def connect_JSON(config):
+ """Connect to a bitcoin JSON-RPC server"""
+ testnet = config.get('testnet', '0')
+ testnet = (int(testnet) > 0) # 0/1 in config file, convert to True/False
+ if not 'rpcport' in config:
+ config['rpcport'] = 18332 if testnet else 8332
+ connect = "http://%s:%s@127.0.0.1:%s"%(config['rpcuser'], config['rpcpassword'], config['rpcport'])
+ try:
+ result = ServiceProxy(connect)
+ # ServiceProxy is lazy-connect, so send an RPC command mostly to catch connection errors,
+ # but also make sure the bitcoind we're talking to is/isn't testnet:
+ if result.getmininginfo()['testnet'] != testnet:
+ sys.stderr.write("RPC server at "+connect+" testnet setting mismatch\n")
+ sys.exit(1)
+ return result
+ except:
+ sys.stderr.write("Error connecting to RPC server at "+connect+"\n")
+ sys.exit(1)
+
+def unlock_wallet(bitcoind):
+ info = bitcoind.getinfo()
+ if 'unlocked_until' not in info:
+ return True # wallet is not encrypted
+ t = int(info['unlocked_until'])
+ if t <= time.time():
+ try:
+ passphrase = getpass.getpass("Wallet is locked; enter passphrase: ")
+ bitcoind.walletpassphrase(passphrase, 5)
+ except:
+ sys.stderr.write("Wrong passphrase\n")
+
+ info = bitcoind.getinfo()
+ return int(info['unlocked_until']) > time.time()
+
+def list_available(bitcoind):
+ address_summary = dict()
+
+ address_to_account = dict()
+ for info in bitcoind.listreceivedbyaddress(0):
+ address_to_account[info["address"]] = info["account"]
+
+ unspent = bitcoind.listunspent(0)
+ for output in unspent:
+ # listunspent doesn't give addresses, so:
+ rawtx = bitcoind.getrawtransaction(output['txid'], 1)
+ vout = rawtx["vout"][output['vout']]
+ pk = vout["scriptPubKey"]
+
+ # This code only deals with ordinary pay-to-bitcoin-address
+ # or pay-to-script-hash outputs right now; anything exotic is ignored.
+ if pk["type"] != "pubkeyhash" and pk["type"] != "scripthash":
+ continue
+
+ address = pk["addresses"][0]
+ if address in address_summary:
+ address_summary[address]["total"] += vout["value"]
+ address_summary[address]["outputs"].append(output)
+ else:
+ address_summary[address] = {
+ "total" : vout["value"],
+ "outputs" : [output],
+ "account" : address_to_account.get(address, "")
+ }
+
+ return address_summary
+
+def select_coins(needed, inputs):
+ # Feel free to improve this, this is good enough for my simple needs:
+ outputs = []
+ have = Decimal("0.0")
+ n = 0
+ while have < needed and n < len(inputs):
+ outputs.append({ "txid":inputs[n]["txid"], "vout":inputs[n]["vout"]})
+ have += inputs[n]["amount"]
+ n += 1
+ return (outputs, have-needed)
+
+def create_tx(bitcoind, fromaddresses, toaddress, amount, fee):
+ all_coins = list_available(bitcoind)
+
+ total_available = Decimal("0.0")
+ needed = amount+fee
+ potential_inputs = []
+ for addr in fromaddresses:
+ if addr not in all_coins:
+ continue
+ potential_inputs.extend(all_coins[addr]["outputs"])
+ total_available += all_coins[addr]["total"]
+
+ if total_available < needed:
+ sys.stderr.write("Error, only %f BTC available, need %f\n"%(total_available, needed));
+ sys.exit(1)
+
+ #
+ # Note:
+ # Python's json/jsonrpc modules have inconsistent support for Decimal numbers.
+ # Instead of wrestling with getting json.dumps() (used by jsonrpc) to encode
+ # Decimals, I'm casting amounts to float before sending them to bitcoind.
+ #
+ outputs = { toaddress : float(amount) }
+ (inputs, change_amount) = select_coins(needed, potential_inputs)
+ if change_amount > BASE_FEE: # don't bother with zero or tiny change
+ change_address = fromaddresses[-1]
+ if change_address in outputs:
+ outputs[change_address] += float(change_amount)
+ else:
+ outputs[change_address] = float(change_amount)
+
+ rawtx = bitcoind.createrawtransaction(inputs, outputs)
+ signed_rawtx = bitcoind.signrawtransaction(rawtx)
+ if not signed_rawtx["complete"]:
+ sys.stderr.write("signrawtransaction failed\n")
+ sys.exit(1)
+ txdata = signed_rawtx["hex"]
+
+ return txdata
+
+def compute_amount_in(bitcoind, txinfo):
+ result = Decimal("0.0")
+ for vin in txinfo['vin']:
+ in_info = bitcoind.getrawtransaction(vin['txid'], 1)
+ vout = in_info['vout'][vin['vout']]
+ result = result + vout['value']
+ return result
+
+def compute_amount_out(txinfo):
+ result = Decimal("0.0")
+ for vout in txinfo['vout']:
+ result = result + vout['value']
+ return result
+
+def sanity_test_fee(bitcoind, txdata_hex, max_fee):
+ class FeeError(RuntimeError):
+ pass
+ try:
+ txinfo = bitcoind.decoderawtransaction(txdata_hex)
+ total_in = compute_amount_in(bitcoind, txinfo)
+ total_out = compute_amount_out(txinfo)
+ if total_in-total_out > max_fee:
+ raise FeeError("Rejecting transaction, unreasonable fee of "+str(total_in-total_out))
+
+ tx_size = len(txdata_hex)/2
+ kb = tx_size/1000 # integer division rounds down
+ if kb > 1 and fee < BASE_FEE:
+ raise FeeError("Rejecting no-fee transaction, larger than 1000 bytes")
+ if total_in < 0.01 and fee < BASE_FEE:
+ raise FeeError("Rejecting no-fee, tiny-amount transaction")
+ # Exercise for the reader: compute transaction priority, and
+ # warn if this is a very-low-priority transaction
+
+ except FeeError as err:
+ sys.stderr.write((str(err)+"\n"))
+ sys.exit(1)
+
+def main():
+ import optparse
+
+ parser = optparse.OptionParser(usage="%prog [options]")
+ parser.add_option("--from", dest="fromaddresses", default=None,
+ help="addresses to get bitcoins from")
+ parser.add_option("--to", dest="to", default=None,
+ help="address to get send bitcoins to")
+ parser.add_option("--amount", dest="amount", default=None,
+ help="amount to send")
+ parser.add_option("--fee", dest="fee", default="0.0",
+ help="fee to include")
+ parser.add_option("--datadir", dest="datadir", default=determine_db_dir(),
+ help="location of bitcoin.conf file with RPC username/password (default: %default)")
+ parser.add_option("--testnet", dest="testnet", default=False, action="store_true",
+ help="Use the test network")
+ parser.add_option("--dry_run", dest="dry_run", default=False, action="store_true",
+ help="Don't broadcast the transaction, just create and print the transaction data")
+
+ (options, args) = parser.parse_args()
+
+ check_json_precision()
+ config = read_bitcoin_config(options.datadir)
+ if options.testnet: config['testnet'] = True
+ bitcoind = connect_JSON(config)
+
+ if options.amount is None:
+ address_summary = list_available(bitcoind)
+ for address,info in address_summary.iteritems():
+ n_transactions = len(info['outputs'])
+ if n_transactions > 1:
+ print("%s %.8f %s (%d transactions)"%(address, info['total'], info['account'], n_transactions))
+ else:
+ print("%s %.8f %s"%(address, info['total'], info['account']))
+ else:
+ fee = Decimal(options.fee)
+ amount = Decimal(options.amount)
+ while unlock_wallet(bitcoind) == False:
+ pass # Keep asking for passphrase until they get it right
+ txdata = create_tx(bitcoind, options.fromaddresses.split(","), options.to, amount, fee)
+ sanity_test_fee(bitcoind, txdata, amount*Decimal("0.01"))
+ if options.dry_run:
+ print(txdata)
+ else:
+ txid = bitcoind.sendrawtransaction(txdata)
+ print(txid)
+
+if __name__ == '__main__':
+ main()
diff --git a/src/main.cpp b/src/main.cpp
index 6c2d76202a..fe35fbaf29 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -633,7 +633,7 @@ void CTxMemPool::pruneSpent(const uint256 &hashTx, CCoins &coins)
}
}
-bool CTxMemPool::accept(CTransaction &tx, bool fCheckInputs,
+bool CTxMemPool::accept(CTransaction &tx, bool fCheckInputs, bool fLimitFree,
bool* pfMissingInputs)
{
if (pfMissingInputs)
@@ -739,7 +739,7 @@ bool CTxMemPool::accept(CTransaction &tx, bool fCheckInputs,
// Don't accept it if it can't get into a block
int64 txMinFee = tx.GetMinFee(1000, true, GMF_RELAY);
- if (nFees < txMinFee)
+ if (fLimitFree && nFees < txMinFee)
return error("CTxMemPool::accept() : not enough fees %s, %"PRI64d" < %"PRI64d,
hash.ToString().c_str(),
nFees, txMinFee);
@@ -747,25 +747,24 @@ bool CTxMemPool::accept(CTransaction &tx, bool fCheckInputs,
// Continuously rate-limit free transactions
// This mitigates 'penny-flooding' -- sending thousands of free transactions just to
// be annoying or make others' transactions take longer to confirm.
- if (nFees < MIN_RELAY_TX_FEE)
+ if (fLimitFree && nFees < MIN_RELAY_TX_FEE)
{
- static CCriticalSection cs;
static double dFreeCount;
static int64 nLastTime;
int64 nNow = GetTime();
- {
- // Use an exponentially decaying ~10-minute window:
- dFreeCount *= pow(1.0 - 1.0/600.0, (double)(nNow - nLastTime));
- nLastTime = nNow;
- // -limitfreerelay unit is thousand-bytes-per-minute
- // At default rate it would take over a month to fill 1GB
- if (dFreeCount > GetArg("-limitfreerelay", 15)*10*1000 && !IsFromMe(tx))
- return error("CTxMemPool::accept() : free transaction rejected by rate limiter");
- if (fDebug)
- printf("Rate limit dFreeCount: %g => %g\n", dFreeCount, dFreeCount+nSize);
- dFreeCount += nSize;
- }
+ LOCK(cs);
+
+ // Use an exponentially decaying ~10-minute window:
+ dFreeCount *= pow(1.0 - 1.0/600.0, (double)(nNow - nLastTime));
+ nLastTime = nNow;
+ // -limitfreerelay unit is thousand-bytes-per-minute
+ // At default rate it would take over a month to fill 1GB
+ if (dFreeCount >= GetArg("-limitfreerelay", 15)*10*1000)
+ return error("CTxMemPool::accept() : free transaction rejected by rate limiter");
+ if (fDebug)
+ printf("Rate limit dFreeCount: %g => %g\n", dFreeCount, dFreeCount+nSize);
+ dFreeCount += nSize;
}
// Check against previous transactions
@@ -799,9 +798,9 @@ bool CTxMemPool::accept(CTransaction &tx, bool fCheckInputs,
return true;
}
-bool CTransaction::AcceptToMemoryPool(bool fCheckInputs, bool* pfMissingInputs)
+bool CTransaction::AcceptToMemoryPool(bool fCheckInputs, bool fLimitFree, bool* pfMissingInputs)
{
- return mempool.accept(*this, fCheckInputs, pfMissingInputs);
+ return mempool.accept(*this, fCheckInputs, fLimitFree, pfMissingInputs);
}
bool CTxMemPool::addUnchecked(const uint256& hash, CTransaction &tx)
@@ -912,9 +911,9 @@ int CMerkleTx::GetBlocksToMaturity() const
}
-bool CMerkleTx::AcceptToMemoryPool(bool fCheckInputs)
+bool CMerkleTx::AcceptToMemoryPool(bool fCheckInputs, bool fLimitFree)
{
- return CTransaction::AcceptToMemoryPool(fCheckInputs);
+ return CTransaction::AcceptToMemoryPool(fCheckInputs, fLimitFree);
}
@@ -930,10 +929,10 @@ bool CWalletTx::AcceptWalletTransaction(bool fCheckInputs)
{
uint256 hash = tx.GetHash();
if (!mempool.exists(hash) && pcoinsTip->HaveCoins(hash))
- tx.AcceptToMemoryPool(fCheckInputs);
+ tx.AcceptToMemoryPool(fCheckInputs, false);
}
}
- return AcceptToMemoryPool(fCheckInputs);
+ return AcceptToMemoryPool(fCheckInputs, false);
}
return false;
}
@@ -1870,7 +1869,7 @@ bool SetBestChain(CBlockIndex* pindexNew)
// Resurrect memory transactions that were in the disconnected branch
BOOST_FOREACH(CTransaction& tx, vResurrect)
- tx.AcceptToMemoryPool();
+ tx.AcceptToMemoryPool(true, false);
// Delete redundant memory transactions that are in the connected branch
BOOST_FOREACH(CTransaction& tx, vDelete) {
@@ -3458,7 +3457,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
pfrom->AddInventoryKnown(inv);
bool fMissingInputs = false;
- if (tx.AcceptToMemoryPool(true, &fMissingInputs))
+ if (tx.AcceptToMemoryPool(true, true, &fMissingInputs))
{
RelayTransaction(tx, inv.hash, vMsg);
mapAlreadyAskedFor.erase(inv);
@@ -3479,7 +3478,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
CInv inv(MSG_TX, tx.GetHash());
bool fMissingInputs2 = false;
- if (tx.AcceptToMemoryPool(true, &fMissingInputs2))
+ if (tx.AcceptToMemoryPool(true, true, &fMissingInputs2))
{
printf(" accepted orphan tx %s\n", inv.hash.ToString().substr(0,10).c_str());
RelayTransaction(tx, inv.hash, vMsg);
@@ -3489,9 +3488,9 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
}
else if (!fMissingInputs2)
{
- // invalid orphan
+ // invalid or too-little-fee orphan
vEraseQueue.push_back(inv.hash);
- printf(" removed invalid orphan tx %s\n", inv.hash.ToString().substr(0,10).c_str());
+ printf(" removed orphan tx %s\n", inv.hash.ToString().substr(0,10).c_str());
}
}
}
diff --git a/src/main.h b/src/main.h
index edfea7b510..db41584b56 100644
--- a/src/main.h
+++ b/src/main.h
@@ -674,7 +674,7 @@ public:
bool CheckTransaction() const;
// Try to accept this transaction into the memory pool
- bool AcceptToMemoryPool(bool fCheckInputs=true, bool* pfMissingInputs=NULL);
+ bool AcceptToMemoryPool(bool fCheckInputs=true, bool fLimitFree = true, bool* pfMissingInputs=NULL);
protected:
static const CTxOut &GetOutputFor(const CTxIn& input, CCoinsViewCache& mapInputs);
@@ -1154,7 +1154,7 @@ public:
int GetDepthInMainChain() const { CBlockIndex *pindexRet; return GetDepthInMainChain(pindexRet); }
bool IsInMainChain() const { return GetDepthInMainChain() > 0; }
int GetBlocksToMaturity() const;
- bool AcceptToMemoryPool(bool fCheckInputs=true);
+ bool AcceptToMemoryPool(bool fCheckInputs=true, bool fLimitFree=true);
};
@@ -2035,7 +2035,7 @@ public:
std::map<uint256, CTransaction> mapTx;
std::map<COutPoint, CInPoint> mapNextTx;
- bool accept(CTransaction &tx, bool fCheckInputs, bool* pfMissingInputs);
+ bool accept(CTransaction &tx, bool fCheckInputs, bool fLimitFree, bool* pfMissingInputs);
bool addUnchecked(const uint256& hash, CTransaction &tx);
bool remove(const CTransaction &tx, bool fRecursive = false);
bool removeConflicts(const CTransaction &tx);
diff --git a/src/rpcrawtransaction.cpp b/src/rpcrawtransaction.cpp
index 09fbaa30cd..8d89c2f302 100644
--- a/src/rpcrawtransaction.cpp
+++ b/src/rpcrawtransaction.cpp
@@ -546,7 +546,7 @@ Value sendrawtransaction(const Array& params, bool fHelp)
fHave = view.GetCoins(hashTx, existingCoins);
if (!fHave) {
// push to local node
- if (!tx.AcceptToMemoryPool())
+ if (!tx.AcceptToMemoryPool(true, false))
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX rejected");
}
}
diff --git a/src/wallet.cpp b/src/wallet.cpp
index 8b2f03212a..557784e5c2 100644
--- a/src/wallet.cpp
+++ b/src/wallet.cpp
@@ -1278,7 +1278,7 @@ bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey)
mapRequestCount[wtxNew.GetHash()] = 0;
// Broadcast
- if (!wtxNew.AcceptToMemoryPool())
+ if (!wtxNew.AcceptToMemoryPool(true, false))
{
// This must not fail. The transaction has already been signed and recorded.
printf("CommitTransaction() : Error: Transaction not valid");