diff --git a/qa/replace-by-fee/.gitignore b/qa/replace-by-fee/.gitignore
new file mode 100644
index 0000000..b2c4f46
--- /dev/null
+++ b/qa/replace-by-fee/.gitignore
@@ -0,0 +1 @@
+python-bitcoinlib
diff --git a/qa/replace-by-fee/README.md b/qa/replace-by-fee/README.md
new file mode 100644
index 0000000..51bcad8
--- /dev/null
+++ b/qa/replace-by-fee/README.md
@@ -0,0 +1,14 @@
+Replace-by-fee regression test
+==============================
+
+First get version v0.3.0 of the python-bitcoinlib library. In this directory
+run:
+
+    git clone -n https://github.com/petertodd/python-bitcoinlib
+    (cd python-bitcoinlib && git checkout c481254c623cc9a002187dc23263cce3e05f5754)
+
+Then run the tests themselves with a bitcoind available running in regtest
+mode:
+
+    ./replace-by-fee-tests.py
+
diff --git a/qa/replace-by-fee/replace-by-fee-tests.py b/qa/replace-by-fee/replace-by-fee-tests.py
new file mode 100755
index 0000000..537fab3
--- /dev/null
+++ b/qa/replace-by-fee/replace-by-fee-tests.py
@@ -0,0 +1,287 @@
+#!/usr/bin/env python3
+# Copyright (c) 2015 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#
+# Test replace-by-fee
+#
+
+import os
+import sys
+
+# Add python-bitcoinlib to module search path:
+sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "python-bitcoinlib"))
+
+import unittest
+
+import bitcoin
+bitcoin.SelectParams('regtest')
+
+import bitcoin.rpc
+
+from bitcoin.core import *
+from bitcoin.core.script import *
+from bitcoin.wallet import *
+
+MAX_REPLACEMENT_LIMIT = 100
+
+class Test_ReplaceByFee(unittest.TestCase):
+    proxy = None
+
+    @classmethod
+    def setUpClass(cls):
+        if cls.proxy is None:
+            cls.proxy = bitcoin.rpc.Proxy()
+
+    @classmethod
+    def tearDownClass(cls):
+        # Make sure mining works
+        while len(cls.proxy.getrawmempool()):
+            cls.proxy.setgenerate(True, 1)
+
+    def make_txout(self, amount, scriptPubKey=CScript([1])):
+        """Create a txout with a given amount and scriptPubKey
+
+        Mines coins as needed.
+        """
+        fee = 1*COIN
+        while self.proxy.getbalance() < amount + fee:
+            self.proxy.setgenerate(True, 100)
+
+        addr = P2SHBitcoinAddress.from_redeemScript(CScript([]))
+        txid = self.proxy.sendtoaddress(addr, amount + fee)
+
+        tx1 = self.proxy.getrawtransaction(txid)
+
+        i = None
+        for i, txout in enumerate(tx1.vout):
+            if txout.scriptPubKey == addr.to_scriptPubKey():
+                break
+        assert i is not None
+
+        tx2 = CTransaction([CTxIn(COutPoint(txid, i), CScript([1, CScript([])]))],
+                           [CTxOut(amount, scriptPubKey)])
+
+        tx2_txid = self.proxy.sendrawtransaction(tx2, True)
+
+        return COutPoint(tx2_txid, 0)
+
+    def test_simple_doublespend(self):
+        """Simple doublespend"""
+        tx0_outpoint = self.make_txout(1.1*COIN)
+
+        tx1a = CTransaction([CTxIn(tx0_outpoint)],
+                            [CTxOut(1*COIN, CScript([b'a']))])
+        tx1a_txid = self.proxy.sendrawtransaction(tx1a, True)
+
+        # Should fail because we haven't changed the fee
+        tx1b = CTransaction([CTxIn(tx0_outpoint)],
+                            [CTxOut(1*COIN, CScript([b'b']))])
+
+        try:
+            tx1b_txid = self.proxy.sendrawtransaction(tx1b, True)
+        except bitcoin.rpc.JSONRPCException as exp:
+            self.assertEqual(exp.error['code'], -26) # insufficient fee
+        else:
+            self.fail()
+
+        # Extra 0.1 BTC fee
+        tx1b = CTransaction([CTxIn(tx0_outpoint)],
+                            [CTxOut(0.9*COIN, CScript([b'b']))])
+        tx1b_txid = self.proxy.sendrawtransaction(tx1b, True)
+
+        # tx1a is in fact replaced
+        with self.assertRaises(IndexError):
+            self.proxy.getrawtransaction(tx1a_txid)
+
+        self.assertEqual(tx1b, self.proxy.getrawtransaction(tx1b_txid))
+
+    def test_doublespend_chain(self):
+        """Doublespend of a long chain"""
+
+        initial_nValue = 50*COIN
+        tx0_outpoint = self.make_txout(initial_nValue)
+
+        prevout = tx0_outpoint
+        remaining_value = initial_nValue
+        chain_txids = []
+        while remaining_value > 10*COIN:
+            remaining_value -= 1*COIN
+            tx = CTransaction([CTxIn(prevout)],
+                              [CTxOut(remaining_value, CScript([1]))])
+            txid = self.proxy.sendrawtransaction(tx, True)
+            chain_txids.append(txid)
+            prevout = COutPoint(txid, 0)
+
+        # Whether the double-spend is allowed is evaluated by including all
+        # child fees - 40 BTC - so this attempt is rejected.
+        dbl_tx = CTransaction([CTxIn(tx0_outpoint)],
+                              [CTxOut(initial_nValue - 30*COIN, CScript([1]))])
+
+        try:
+            self.proxy.sendrawtransaction(dbl_tx, True)
+        except bitcoin.rpc.JSONRPCException as exp:
+            self.assertEqual(exp.error['code'], -26) # insufficient fee
+        else:
+            self.fail()
+
+        # Accepted with sufficient fee
+        dbl_tx = CTransaction([CTxIn(tx0_outpoint)],
+                              [CTxOut(1*COIN, CScript([1]))])
+        self.proxy.sendrawtransaction(dbl_tx, True)
+
+        for doublespent_txid in chain_txids:
+            with self.assertRaises(IndexError):
+                self.proxy.getrawtransaction(doublespent_txid)
+
+    def test_doublespend_tree(self):
+        """Doublespend of a big tree of transactions"""
+
+        initial_nValue = 50*COIN
+        tx0_outpoint = self.make_txout(initial_nValue)
+
+        def branch(prevout, initial_value, max_txs, *, tree_width=5, fee=0.0001*COIN, _total_txs=None):
+            if _total_txs is None:
+                _total_txs = [0]
+            if _total_txs[0] >= max_txs:
+                return
+
+            txout_value = (initial_value - fee) // tree_width
+            if txout_value < fee:
+                return
+
+            vout = [CTxOut(txout_value, CScript([i+1]))
+                    for i in range(tree_width)]
+            tx = CTransaction([CTxIn(prevout)],
+                              vout)
+
+            self.assertTrue(len(tx.serialize()) < 100000)
+            txid = self.proxy.sendrawtransaction(tx, True)
+            yield tx
+            _total_txs[0] += 1
+
+            for i, txout in enumerate(tx.vout):
+                yield from branch(COutPoint(txid, i), txout_value,
+                                  max_txs,
+                                  tree_width=tree_width, fee=fee,
+                                  _total_txs=_total_txs)
+
+        fee = 0.0001*COIN
+        n = MAX_REPLACEMENT_LIMIT
+        tree_txs = list(branch(tx0_outpoint, initial_nValue, n, fee=fee))
+        self.assertEqual(len(tree_txs), n)
+
+        # Attempt double-spend, will fail because too little fee paid
+        dbl_tx = CTransaction([CTxIn(tx0_outpoint)],
+                              [CTxOut(initial_nValue - fee*n, CScript([1]))])
+        try:
+            self.proxy.sendrawtransaction(dbl_tx, True)
+        except bitcoin.rpc.JSONRPCException as exp:
+            self.assertEqual(exp.error['code'], -26) # insufficient fee
+        else:
+            self.fail()
+
+        # 1 BTC fee is enough
+        dbl_tx = CTransaction([CTxIn(tx0_outpoint)],
+                              [CTxOut(initial_nValue - fee*n - 1*COIN, CScript([1]))])
+        self.proxy.sendrawtransaction(dbl_tx, True)
+
+        for tx in tree_txs:
+            with self.assertRaises(IndexError):
+                self.proxy.getrawtransaction(tx.GetHash())
+
+        # Try again, but with more total transactions than the "max txs
+        # double-spent at once" anti-DoS limit.
+        for n in (MAX_REPLACEMENT_LIMIT, MAX_REPLACEMENT_LIMIT*2):
+            fee = 0.0001*COIN
+            tx0_outpoint = self.make_txout(initial_nValue)
+            tree_txs = list(branch(tx0_outpoint, initial_nValue, n, fee=fee))
+            self.assertEqual(len(tree_txs), n)
+
+            dbl_tx = CTransaction([CTxIn(tx0_outpoint)],
+                                  [CTxOut(initial_nValue - fee*n, CScript([1]))])
+            try:
+                self.proxy.sendrawtransaction(dbl_tx, True)
+            except bitcoin.rpc.JSONRPCException as exp:
+                self.assertEqual(exp.error['code'], -26)
+            else:
+                self.fail()
+
+            for tx in tree_txs:
+                self.proxy.getrawtransaction(tx.GetHash())
+
+    def test_huge_chain(self):
+        """Doublespend of a huge (in size) transaction chain"""
+
+        def fat_chain(n, remaining_value):
+            fee = 0.1*COIN
+
+            initial_nValue = (n * fee) + remaining_value
+            tx0_outpoint = self.make_txout(initial_nValue)
+
+            yield tx0_outpoint
+
+            prevout = tx0_outpoint
+            prevout_nValue = initial_nValue
+
+            fat_txout = CTxOut(1, CScript([b'\xff'*999000]))
+
+            for i in range(n):
+                tx = CTransaction([CTxIn(prevout)],
+                                  [CTxOut(prevout_nValue - fee, CScript([1])),
+                                   fat_txout])
+
+                txid = self.proxy.sendrawtransaction(tx, True)
+
+                prevout = COutPoint(txid, 0)
+                prevout_nValue = tx.vout[0].nValue
+
+                yield tx
+
+        n = MAX_REPLACEMENT_LIMIT
+        tx0_outpoint, *chain_txs = fat_chain(n, 1*COIN)
+        self.assertEqual(len(chain_txs), n)
+
+        # Attempt double-spend, will fail because too little fee paid
+        dbl_tx = CTransaction([CTxIn(tx0_outpoint)],
+                              [CTxOut(2*COIN, CScript([1]))])
+        try:
+            self.proxy.sendrawtransaction(dbl_tx, True)
+        except bitcoin.rpc.JSONRPCException as exp:
+            self.assertEqual(exp.error['code'], -26) # insufficient fee
+        else:
+            self.fail()
+
+        # Fine with more fees
+        dbl_tx = CTransaction([CTxIn(tx0_outpoint)],
+                              [CTxOut(0.9*COIN, CScript([1]))])
+        self.proxy.sendrawtransaction(dbl_tx, True)
+
+        for tx in chain_txs:
+            with self.assertRaises(IndexError):
+                self.proxy.getrawtransaction(tx.GetHash())
+
+    def test_replacement_feeperkb(self):
+        """Replacement requires overall fee-per-KB to be higher"""
+        tx0_outpoint = self.make_txout(1.1*COIN)
+
+        tx1a = CTransaction([CTxIn(tx0_outpoint)],
+                            [CTxOut(1*COIN, CScript([b'a']))])
+        tx1a_txid = self.proxy.sendrawtransaction(tx1a, True)
+
+        # Higher fee, but the fee per KB is much lower, so the replacement is
+        # rejected.
+        tx1b = CTransaction([CTxIn(tx0_outpoint)],
+                            [CTxOut(0.001*COIN,
+                                    CScript([b'a'*999000]))])
+
+        try:
+            tx1b_txid = self.proxy.sendrawtransaction(tx1b, True)
+        except bitcoin.rpc.JSONRPCException as exp:
+            self.assertEqual(exp.error['code'], -26) # insufficient fee
+        else:
+            self.fail()
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/src/main.cpp b/src/main.cpp
index 4a95bb1..08ac86e 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -945,20 +945,6 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
     if (pool.exists(hash))
         return false;
 
-    // Check for conflicts with in-memory transactions
-    {
-    LOCK(pool.cs); // protect pool.mapNextTx
-    for (unsigned int i = 0; i < tx.vin.size(); i++)
-    {
-        COutPoint outpoint = tx.vin[i].prevout;
-        if (pool.mapNextTx.count(outpoint))
-        {
-            // Disable replacement feature for now
-            return false;
-        }
-    }
-    }
-
     {
         CCoinsView dummy;
         CCoinsViewCache view(&dummy);
@@ -1084,8 +1070,119 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
             return error("AcceptToMemoryPool: : BUG! PLEASE REPORT THIS! ConnectInputs failed against MANDATORY but not STANDARD flags %s", hash.ToString());
         }
 
+        CAmount nConflictingFees = 0;
+        size_t nConflictingSize = 0;
+        {
+
+            // Now that we're sure we'd let the transaction into the mempool if not
+            // for conflicts, check if it's economically rational to mine it rather
+            // than the transactions it conflicts with. (if any)
+            LOCK(pool.cs);
+            set<uint256> sConflicts;
+
+            // Start with set of conflicting transactions we're directly
+            // double-spending.
+            BOOST_FOREACH(const CTxIn txin, tx.vin) {
+                if (pool.mapNextTx.count(txin.prevout))
+                {
+                    sConflicts.insert(pool.mapNextTx[txin.prevout].ptx->GetHash());
+                }
+            }
+
+            // Sum up conflicting size/fees for that set and all children.
+            int nTxVisited = 0;
+            while (sConflicts.size())
+            {
+                // Limit DoS potential by simply rejecting double-spends of large
+                // numbers of transactions. This can be removed when the
+                // mempool itself is limited in size - as we're just following
+                // pointers and nFees and nSize are cached this operation is
+                // very fast even with extremely large amounts of transactions
+                // changed.
+                nTxVisited++;
+                if (nTxVisited > 100)
+                {
+                    return state.DoS(0, error("AcceptToMemoryPool : too many conflicting txs for replacement; can't replace with %s",
+                                               hash.ToString()),
+                                         REJECT_DUPLICATE, "bad-txns-inputs-spent");
+                }
+
+                std::set<uint256>::iterator it;
+                it = sConflicts.begin();
+                uint256 hashChildTx = *it;
+                sConflicts.erase(it);
+
+                const CTxMemPoolEntry& entry = pool.mapTx.at(hashChildTx);
+                nConflictingFees += entry.GetFee();
+                nConflictingSize += entry.GetTxSize();
+
+                // Add tx children to the set
+                for (unsigned int i = 0; i < entry.GetTx().vout.size(); i++)
+                {
+                    COutPoint outpoint(hashChildTx, i);
+                    if (pool.mapNextTx.count(outpoint))
+                    {
+                        sConflicts.insert(pool.mapNextTx[outpoint].ptx->GetHash());
+                    }
+                }
+            }
+
+            // Replace?
+            //
+            // First of all we can't allow a replacement unless it pays greater
+            // fees than the transactions it conflicts with - if we did the
+            // bandwidth used by those conflicting transactions would not be
+            // paid for.
+            if (nFees < nConflictingFees)
+            {
+                return state.DoS(0, error("AcceptToMemoryPool : rejecting replacement %s, less fees than conflicting txs; %s < %s",
+                                          hash.ToString(), FormatMoney(nFees), FormatMoney(nConflictingFees)),
+                                 REJECT_INSUFFICIENTFEE, "insufficient fee");
+            }
+
+            // Secondly in addition to paying more fees than the conflicts the
+            // new transaction must additionally pay for its own bandwidth.
+            CAmount nDeltaFees = nFees - nConflictingFees;
+            if (nDeltaFees < ::minRelayTxFee.GetFee(nSize))
+            {
+                return state.DoS(0,
+                        error("AcceptToMemoryPool : rejecting replacement %s, not enough additional fees to relay; %s < %s",
+                              hash.ToString(),
+                              FormatMoney(nDeltaFees),
+                              FormatMoney(txMinFee)),
+                        REJECT_INSUFFICIENTFEE, "insufficient fee");
+            }
+
+            // Finally replace only if we end up with a larger fees-per-kb than
+            // the replacements.
+            CFeeRate oldFeeRate(nConflictingFees, nConflictingSize);
+            CFeeRate newFeeRate(nFees, nSize);
+            if (newFeeRate <= oldFeeRate)
+            {
+                return state.DoS(0,
+                        error("AcceptToMemoryPool : rejecting replacement %s; new fee %s <= old fee %s",
+                              hash.ToString(),
+                              newFeeRate.ToString(),
+                              oldFeeRate.ToString()),
+                        REJECT_INSUFFICIENTFEE, "insufficient fee");
+            }
+        }
+
+        // Remove conflicting transactions from the mempool
+        list<CTransaction> ltxConflicted;
+        pool.removeConflicts(tx, ltxConflicted);
+
         // Store transaction in memory
         pool.addUnchecked(hash, entry);
+
+        BOOST_FOREACH(const CTransaction &txConflicted, ltxConflicted)
+        {
+            LogPrint("mempool", "replacing tx %s with %s for %s BTC additional fees, %d delta bytes\n",
+                     txConflicted.GetHash().ToString(),
+                     hash.ToString(),
+                     FormatMoney(nFees - nConflictingFees),
+                     (int)nSize - (int)nConflictingSize);
+        }
     }
 
     SyncWithWallets(tx, NULL);
@@ -3457,6 +3554,25 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
             return false;
         }
 
+        // Disconnect if we have already met our quota of non-doublespend-relaying nodes.
+        if (!(pfrom->nServices & NODE_RELAYS_DOUBLESPENDS))
+        {
+            int nNonDoubleSpendRelaying = 0;
+
+            LOCK(cs_vNodes);
+            BOOST_FOREACH(CNode* pnode, vNodes) {
+                if (!(pnode->nServices & NODE_RELAYS_DOUBLESPENDS))
+                    nNonDoubleSpendRelaying++;
+            }
+
+            if (nNonDoubleSpendRelaying > nMaxConnections / 4) {
+                LogPrint("net", "reached quota of non-doublespend-relaying nodes; disconnecting %s\n",
+                         pfrom->addr.ToString());
+                pfrom->fDisconnect = true;
+                return false;
+            }
+        }
+
         if (pfrom->nVersion == 10300)
             pfrom->nVersion = 300;
         if (!vRecv.empty())
diff --git a/src/net.cpp b/src/net.cpp
index 42b3c30..a0c7fae 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -53,7 +53,10 @@ using namespace boost;
 using namespace std;
 
 namespace {
-    const int MAX_OUTBOUND_CONNECTIONS = 8;
+    const int MAX_STD_OUTBOUND_CONNECTIONS = 8;
+    const int MIN_REPLACE_BY_FEE_OUTBOUND_CONNECTIONS = 8;
+
+    const int MAX_OUTBOUND_CONNECTIONS = MAX_STD_OUTBOUND_CONNECTIONS + MIN_REPLACE_BY_FEE_OUTBOUND_CONNECTIONS;
 
     struct ListenSocket {
         SOCKET socket;
@@ -68,7 +71,7 @@ namespace {
 //
 bool fDiscover = true;
 bool fListen = true;
-uint64_t nLocalServices = NODE_NETWORK;
+uint64_t nLocalServices = NODE_NETWORK | NODE_REPLACE_BY_FEE;
 CCriticalSection cs_mapLocalHost;
 map<CNetAddr, LocalServiceInfo> mapLocalHost;
 static bool vfReachable[NET_MAX] = {};
@@ -1250,6 +1253,7 @@ void ThreadOpenConnections()
         // Only connect out to one peer per network group (/16 for IPv4).
         // Do this here so we don't have to critsect vNodes inside mapAddresses critsect.
         int nOutbound = 0;
+        int nDoubleSpendRelayingOutbound = 0;
         set<vector<unsigned char> > setConnected;
         {
             LOCK(cs_vNodes);
@@ -1257,6 +1261,9 @@ void ThreadOpenConnections()
                 if (!pnode->fInbound) {
                     setConnected.insert(pnode->addr.GetGroup());
                     nOutbound++;
+
+                    if (pnode->nServices & NODE_RELAYS_DOUBLESPENDS)
+                        nDoubleSpendRelayingOutbound++;
                 }
             }
         }
@@ -1291,6 +1298,21 @@ void ThreadOpenConnections()
             if (addr.GetPort() != Params().GetDefaultPort() && nTries < 50)
                 continue;
 
+            // Reserve some outbound connections for nodes that relay
+            // double-spends.
+            //
+            // Unfortunately nServices seems to end up corrupted at times,
+            // leading us to adding more nodes than expected, so this is a more
+            // strict test than might otherwise be expected.
+            //
+            // Also, we'll still end up with too many at times, because we'll
+            // find out afterwords that what we thought was a node's nServices
+            // was incorrect; Bitcoin Core will even stay connected to nodes
+            // not advertising NODE_NETWORK in this case.
+            if (!((addr.nServices & NODE_RELAYS_DOUBLESPENDS) && (addr.nServices & ~NODE_RELAYS_DOUBLESPENDS) == NODE_NETWORK)
+                    && (nOutbound - nDoubleSpendRelayingOutbound >= MAX_STD_OUTBOUND_CONNECTIONS))
+                continue;
+
             addrConnect = addr;
             break;
         }
diff --git a/src/protocol.h b/src/protocol.h
index 2ac8f3d..4bbccf1 100644
--- a/src/protocol.h
+++ b/src/protocol.h
@@ -66,6 +66,7 @@ class CMessageHeader
 /** nServices flags */
 enum {
     NODE_NETWORK = (1 << 0),
+    NODE_GETUTXOS = (1 << 1),
 
     // Bits 24-31 are reserved for temporary experiments. Just pick a bit that
     // isn't getting used, or one not being used much, and notify the
@@ -74,8 +75,14 @@ enum {
     // collisions and other cases where nodes may be advertising a service they
     // do not actually support. Other service bits should be allocated via the
     // BIP process.
+
+    NODE_REPLACE_BY_FEE = (1 << 26),
 };
 
+// Mike Hearn's Bitcoin XT also relays doublespends, and can be identified by
+// the NODE_GETUTXO's bits, so connect to them as well.
+const uint64_t NODE_RELAYS_DOUBLESPENDS = NODE_REPLACE_BY_FEE | NODE_GETUTXOS;
+
 /** A CService with information about it as peer */
 class CAddress : public CService
 {
