When creating new blocks, sort 'paid' area by fee-per-kb

Modify CreateNewBlock so that instead of processing all transactions
in priority order, process the first 27K of transactions in
priority order and then process the rest in fee-per-kilobyte
order.

This is the first, minimal step towards better a better fee-handling
system for both miners and end-users; this patch should be easy
to backport to the old versions of Bitcoin, and accomplishes the
most important goal-- allow users to "buy their way in" to blocks
using transaction fees.
This commit is contained in:
Gavin Andresen 2012-07-12 14:22:32 -04:00
parent 29c8fb0d93
commit c555400ca1
2 changed files with 114 additions and 31 deletions

View file

@ -279,6 +279,11 @@ std::string HelpMessage()
" -checkblocks=<n> " + _("How many blocks to check at startup (default: 2500, 0 = all)") + "\n" + " -checkblocks=<n> " + _("How many blocks to check at startup (default: 2500, 0 = all)") + "\n" +
" -checklevel=<n> " + _("How thorough the block verification is (0-6, default: 1)") + "\n" + " -checklevel=<n> " + _("How thorough the block verification is (0-6, default: 1)") + "\n" +
" -loadblock=<file> " + _("Imports blocks from external blk000?.dat file") + "\n" + " -loadblock=<file> " + _("Imports blocks from external blk000?.dat file") + "\n" +
_("\nBlock creation options:\n") +
" -blockminsize=<n> " + _("Minimum size, in bytes (default: 0)\n") +
" -blockmaxsize=<n> " + _("Maximum size, in bytes (default: 250000)\n") +
" -blockprioritysize=<n> " + _("Maximum bytes of high-priority/low-fee transactions (default: 27000)\n") +
" -? " + _("This help message") + "\n"; " -? " + _("This help message") + "\n";
strUsage += string() + strUsage += string() +

View file

@ -3312,16 +3312,18 @@ public:
CTransaction* ptx; CTransaction* ptx;
set<uint256> setDependsOn; set<uint256> setDependsOn;
double dPriority; double dPriority;
double dFeePerKb;
COrphan(CTransaction* ptxIn) COrphan(CTransaction* ptxIn)
{ {
ptx = ptxIn; ptx = ptxIn;
dPriority = 0; dPriority = dFeePerKb = 0;
} }
void print() const void print() const
{ {
printf("COrphan(hash=%s, dPriority=%.1f)\n", ptx->GetHash().ToString().substr(0,10).c_str(), dPriority); printf("COrphan(hash=%s, dPriority=%.1f, dFeePerKb=%.1f)\n",
ptx->GetHash().ToString().substr(0,10).c_str(), dPriority, dFeePerKb);
BOOST_FOREACH(uint256 hash, setDependsOn) BOOST_FOREACH(uint256 hash, setDependsOn)
printf(" setDependsOn %s\n", hash.ToString().substr(0,10).c_str()); printf(" setDependsOn %s\n", hash.ToString().substr(0,10).c_str());
} }
@ -3331,6 +3333,30 @@ public:
uint64 nLastBlockTx = 0; uint64 nLastBlockTx = 0;
uint64 nLastBlockSize = 0; uint64 nLastBlockSize = 0;
// We want to sort transactions by priority and fee, so:
typedef boost::tuple<double, double, CTransaction*> TxPriority;
class TxPriorityCompare
{
bool byFee;
public:
TxPriorityCompare(bool _byFee) : byFee(_byFee) { }
bool operator()(const TxPriority& a, const TxPriority& b)
{
if (byFee)
{
if (a.get<1>() == b.get<1>())
return a.get<0>() < b.get<0>();
return a.get<1>() < b.get<1>();
}
else
{
if (a.get<0>() == b.get<0>())
return a.get<1>() < b.get<1>();
return a.get<0>() < b.get<0>();
}
}
};
const char* pszDummy = "\0\0"; const char* pszDummy = "\0\0";
CScript scriptDummy(std::vector<unsigned char>(pszDummy, pszDummy + sizeof(pszDummy))); CScript scriptDummy(std::vector<unsigned char>(pszDummy, pszDummy + sizeof(pszDummy)));
@ -3353,6 +3379,30 @@ CBlock* CreateNewBlock(CReserveKey& reservekey)
// Add our coinbase tx as first transaction // Add our coinbase tx as first transaction
pblock->vtx.push_back(txNew); pblock->vtx.push_back(txNew);
// Largest block you're willing to create:
unsigned int nBlockMaxSize = GetArg("-blockmaxsize", MAX_BLOCK_SIZE_GEN/2);
// Limit to betweeen 1K and MAX_BLOCK_SIZE-1K for sanity:
nBlockMaxSize = std::max((unsigned int)1000, std::min((unsigned int)(MAX_BLOCK_SIZE-1000), nBlockMaxSize));
// How much of the block should be dedicated to high-priority transactions,
// included regardless of the fees they pay
unsigned int nBlockPrioritySize = GetArg("-blockprioritysize", 27000);
nBlockPrioritySize = std::min(nBlockMaxSize, nBlockPrioritySize);
// Minimum block size you want to create; block will be filled with free transactions
// until there are no more or the block reaches this size:
unsigned int nBlockMinSize = GetArg("-blockminsize", 0);
nBlockMinSize = std::min(nBlockMaxSize, nBlockMinSize);
// Fee-per-kilobyte amount considered the same as "free"
// Be careful setting this: if you set it to zero then
// a transaction spammer can cheaply fill blocks using
// 1-satoshi-fee transactions. It should be set above the real
// cost to you of processing a transaction.
int64 nMinTxFee = MIN_TX_FEE;
if (mapArgs.count("-mintxfee"))
ParseMoney(mapArgs["-mintxfee"], nMinTxFee);
// Collect memory pool transactions into the block // Collect memory pool transactions into the block
int64 nFees = 0; int64 nFees = 0;
{ {
@ -3362,7 +3412,10 @@ CBlock* CreateNewBlock(CReserveKey& reservekey)
// Priority order to process transactions // Priority order to process transactions
list<COrphan> vOrphan; // list memory doesn't move list<COrphan> vOrphan; // list memory doesn't move
map<uint256, vector<COrphan*> > mapDependers; map<uint256, vector<COrphan*> > mapDependers;
multimap<double, CTransaction*> mapPriority;
// This vector will be sorted into a priority queue:
vector<TxPriority> vecPriority;
vecPriority.reserve(mempool.mapTx.size());
for (map<uint256, CTransaction>::iterator mi = mempool.mapTx.begin(); mi != mempool.mapTx.end(); ++mi) for (map<uint256, CTransaction>::iterator mi = mempool.mapTx.begin(); mi != mempool.mapTx.end(); ++mi)
{ {
CTransaction& tx = (*mi).second; CTransaction& tx = (*mi).second;
@ -3371,6 +3424,7 @@ CBlock* CreateNewBlock(CReserveKey& reservekey)
COrphan* porphan = NULL; COrphan* porphan = NULL;
double dPriority = 0; double dPriority = 0;
int64 nTotalIn = 0;
BOOST_FOREACH(const CTxIn& txin, tx.vin) BOOST_FOREACH(const CTxIn& txin, tx.vin)
{ {
// Read prev transaction // Read prev transaction
@ -3387,34 +3441,32 @@ CBlock* CreateNewBlock(CReserveKey& reservekey)
} }
mapDependers[txin.prevout.hash].push_back(porphan); mapDependers[txin.prevout.hash].push_back(porphan);
porphan->setDependsOn.insert(txin.prevout.hash); porphan->setDependsOn.insert(txin.prevout.hash);
nTotalIn += mempool.mapTx[txin.prevout.hash].vout[txin.prevout.n].nValue;
continue; continue;
} }
int64 nValueIn = txPrev.vout[txin.prevout.n].nValue; int64 nValueIn = txPrev.vout[txin.prevout.n].nValue;
nTotalIn += nValueIn;
// Read block header
int nConf = txindex.GetDepthInMainChain(); int nConf = txindex.GetDepthInMainChain();
dPriority += (double)nValueIn * nConf; dPriority += (double)nValueIn * nConf;
if (fDebug && GetBoolArg("-printpriority"))
printf("priority nValueIn=%-12"PRI64d" nConf=%-5d dPriority=%-20.1f\n", nValueIn, nConf, dPriority);
} }
// Priority is sum(valuein * age) / txsize // Priority is sum(valuein * age) / txsize
dPriority /= ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION); unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION);
dPriority /= nTxSize;
// This is a more accurate fee-per-kilobyte than is used by the client code, because the
// client code rounds up the size to the nearest 1K. That's good, because it gives an
// incentive to create smaller transactions.
double dFeePerKb = double(nTotalIn-tx.GetValueOut()) / (double(nTxSize)/1000.0);
if (porphan) if (porphan)
porphan->dPriority = dPriority;
else
mapPriority.insert(make_pair(-dPriority, &(*mi).second));
if (fDebug && GetBoolArg("-printpriority"))
{ {
printf("priority %-20.1f %s\n%s", dPriority, tx.GetHash().ToString().substr(0,10).c_str(), tx.ToString().c_str()); porphan->dPriority = dPriority;
if (porphan) porphan->dFeePerKb = dFeePerKb;
porphan->print();
printf("\n");
} }
else
vecPriority.push_back(TxPriority(dPriority, dFeePerKb, &(*mi).second));
} }
// Collect transactions into block // Collect transactions into block
@ -3422,16 +3474,24 @@ CBlock* CreateNewBlock(CReserveKey& reservekey)
uint64 nBlockSize = 1000; uint64 nBlockSize = 1000;
uint64 nBlockTx = 0; uint64 nBlockTx = 0;
int nBlockSigOps = 100; int nBlockSigOps = 100;
while (!mapPriority.empty()) bool fSortedByFee = (nBlockPrioritySize <= 0);
TxPriorityCompare comparer(fSortedByFee);
std::make_heap(vecPriority.begin(), vecPriority.end(), comparer);
while (!vecPriority.empty())
{ {
// Take highest priority transaction off priority queue // Take highest priority transaction off the priority queue:
double dPriority = -(*mapPriority.begin()).first; double dPriority = vecPriority.front().get<0>();
CTransaction& tx = *(*mapPriority.begin()).second; double dFeePerKb = vecPriority.front().get<1>();
mapPriority.erase(mapPriority.begin()); CTransaction& tx = *(vecPriority.front().get<2>());
std::pop_heap(vecPriority.begin(), vecPriority.end(), comparer);
vecPriority.pop_back();
// Size limits // Size limits
unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION); unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION);
if (nBlockSize + nTxSize >= MAX_BLOCK_SIZE_GEN) if (nBlockSize + nTxSize >= nBlockMaxSize)
continue; continue;
// Legacy limits on sigOps: // Legacy limits on sigOps:
@ -3439,9 +3499,19 @@ CBlock* CreateNewBlock(CReserveKey& reservekey)
if (nBlockSigOps + nTxSigOps >= MAX_BLOCK_SIGOPS) if (nBlockSigOps + nTxSigOps >= MAX_BLOCK_SIGOPS)
continue; continue;
// Transaction fee required depends on block size // Skip free transactions if we're past the minimum block size:
bool fAllowFree = (nBlockSize + nTxSize < 4000 || CTransaction::AllowFree(dPriority)); if (fSortedByFee && (dFeePerKb < nMinTxFee) && (nBlockSize + nTxSize >= nBlockMinSize))
int64 nMinFee = tx.GetMinFee(nBlockSize, fAllowFree, GMF_BLOCK); continue;
// Prioritize by fee once past the priority size or we run out of high-priority
// transactions:
if (!fSortedByFee &&
((nBlockSize + nTxSize >= nBlockPrioritySize) || (dPriority < COIN * 144 / 250)))
{
fSortedByFee = true;
comparer = TxPriorityCompare(fSortedByFee);
std::make_heap(vecPriority.begin(), vecPriority.end(), comparer);
}
// Connecting shouldn't fail due to dependency on other memory pool transactions // Connecting shouldn't fail due to dependency on other memory pool transactions
// because we're already processing them in order of dependency // because we're already processing them in order of dependency
@ -3452,8 +3522,6 @@ CBlock* CreateNewBlock(CReserveKey& reservekey)
continue; continue;
int64 nTxFees = tx.GetValueIn(mapInputs)-tx.GetValueOut(); int64 nTxFees = tx.GetValueIn(mapInputs)-tx.GetValueOut();
if (nTxFees < nMinFee)
continue;
nTxSigOps += tx.GetP2SHSigOpCount(mapInputs); nTxSigOps += tx.GetP2SHSigOpCount(mapInputs);
if (nBlockSigOps + nTxSigOps >= MAX_BLOCK_SIGOPS) if (nBlockSigOps + nTxSigOps >= MAX_BLOCK_SIGOPS)
@ -3471,6 +3539,12 @@ CBlock* CreateNewBlock(CReserveKey& reservekey)
nBlockSigOps += nTxSigOps; nBlockSigOps += nTxSigOps;
nFees += nTxFees; nFees += nTxFees;
if (fDebug && GetBoolArg("-printpriority"))
{
printf("priority %.1f feeperkb %.1f txid %s\n",
dPriority, dFeePerKb, tx.GetHash().ToString().c_str());
}
// Add transactions that depend on this one to the priority queue // Add transactions that depend on this one to the priority queue
uint256 hash = tx.GetHash(); uint256 hash = tx.GetHash();
if (mapDependers.count(hash)) if (mapDependers.count(hash))
@ -3481,7 +3555,10 @@ CBlock* CreateNewBlock(CReserveKey& reservekey)
{ {
porphan->setDependsOn.erase(hash); porphan->setDependsOn.erase(hash);
if (porphan->setDependsOn.empty()) if (porphan->setDependsOn.empty())
mapPriority.insert(make_pair(-porphan->dPriority, porphan->ptx)); {
vecPriority.push_back(TxPriority(porphan->dPriority, porphan->dFeePerKb, porphan->ptx));
std::push_heap(vecPriority.begin(), vecPriority.end(), comparer);
}
} }
} }
} }
@ -3654,7 +3731,8 @@ void static BitcoinMiner(CWallet *pwallet)
return; return;
IncrementExtraNonce(pblock.get(), pindexPrev, nExtraNonce); IncrementExtraNonce(pblock.get(), pindexPrev, nExtraNonce);
printf("Running BitcoinMiner with %d transactions in block\n", pblock->vtx.size()); printf("Running BitcoinMiner with %d transactions in block (%u bytes)\n", pblock->vtx.size(),
::GetSerializeSize(*pblock, SER_NETWORK, PROTOCOL_VERSION));
// //