Complete rewrite of consensus code for mn/budget payments

- Added FindProposal and FindFinalBudget to budgeting class
- Added 2 new sporks for Proposals and Budget payment enforcement. This is outside of the decentralized code so we can turn it off if there's a problem.
- Detect budget blocks and pay correct amounts in super blocks
This commit is contained in:
Evan Duffield 2015-05-30 10:27:51 -07:00
parent bd4a7f2fad
commit eaf7b940a6
10 changed files with 208 additions and 65 deletions

View File

@ -2093,11 +2093,12 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
int64_t nTime1 = GetTimeMicros(); nTimeConnect += nTime1 - nTimeStart;
LogPrint("bench", " - Connect %u transactions: %.2fms (%.3fms/tx, %.3fms/txin) [%.2fs]\n", (unsigned)block.vtx.size(), 0.001 * (nTime1 - nTimeStart), 0.001 * (nTime1 - nTimeStart) / block.vtx.size(), nInputs <= 1 ? 0 : 0.001 * (nTime1 - nTimeStart) / (nInputs-1), nTimeConnect * 0.000001);
if (block.vtx[0].GetValueOut() > GetBlockValue(pindex->pprev->nBits, pindex->pprev->nHeight, nFees))
if(!IsBlockValueValid(block.vtx[0].GetValueOut(), GetBlockValue(pindex->pprev->nBits, pindex->pprev->nHeight, nFees))){
return state.DoS(100,
error("ConnectBlock() : coinbase pays too much (actual=%d vs limit=%d)",
block.vtx[0].GetValueOut(), GetBlockValue(pindex->pprev->nBits, pindex->pprev->nHeight, nFees)),
REJECT_INVALID, "bad-cb-amount");
}
if (!control.Wait())
return state.DoS(100, false);
@ -3269,9 +3270,9 @@ bool TestBlockValidity(CValidationState &state, const CBlock& block, CBlockIndex
if (!ContextualCheckBlockHeader(block, state, pindexPrev))
return false;
if (!CheckBlock(block, state, fCheckPOW, fCheckMerkleRoot))
return false;
return false;
if (!ContextualCheckBlock(block, state, pindexPrev))
return false;
return false;
if (!ConnectBlock(block, state, &indexDummy, viewNew, true))
return false;
assert(state.IsValid());

View File

@ -184,7 +184,7 @@ void DumpBudgets()
LogPrintf("Masternode dump finished %dms\n", GetTimeMillis() - nStart);
}
CBudgetProposal *CBudgetManager::Find(const std::string &strProposalName)
CBudgetProposal *CBudgetManager::FindProposal(const std::string &strProposalName)
{
//find the prop with the highest yes count
@ -330,6 +330,68 @@ void CBudgetVote::Relay()
}
}
void CBudgetManager::FillBlockPayee(CMutableTransaction& txNew, int64_t nFees)
{
CBlockIndex* pindexPrev = chainActive.Tip();
if(!pindexPrev) return;
int nHighestCount = 0;
CScript payee;
int64_t nAmount = 0;
// ------- Grab The Highest Count
std::map<uint256, CFinalizedBudget>::iterator it = mapFinalizedBudgets.begin();
while(it != mapFinalizedBudgets.end())
{
CFinalizedBudget* prop = &((*it).second);
if(prop->GetVoteCount() > nHighestCount){
if(pindexPrev->nHeight+1 >= prop->GetBlockStart() && pindexPrev->nHeight+1 <= prop->GetBlockEnd()){
if(prop->GetPayeeAndAmount(pindexPrev->nHeight+1, payee, nAmount)){
nHighestCount = prop->GetVoteCount();
}
}
}
it++;
}
CAmount blockValue = GetBlockValue(pindexPrev->nBits, pindexPrev->nHeight, nFees);
//miners get the full amount on these blocks
txNew.vout[0].nValue = blockValue;
if(nHighestCount > 0){
txNew.vout.resize(2);
//these are super blocks, so their value can be much larger than normal
txNew.vout[1].scriptPubKey = payee;
txNew.vout[1].nValue = nAmount;
CTxDestination address1;
ExtractDestination(payee, address1);
CBitcoinAddress address2(address1);
LogPrintf("Budget payment to %s for %lld\n", address2.ToString().c_str(), nAmount);
}
}
bool CFinalizedBudget::GetPayeeAndAmount(int64_t nBlockHeight, CScript& payee, int64_t& nAmount)
{
uint256 nProp = GetProposalByBlock(nBlockHeight);
CBudgetProposal* prop = budget.FindProposal(nProp);
if(prop){
payee = prop->GetPayee();
nAmount = prop->GetAmount();
return true;
}
LogPrintf("GetPayeeAndAmount - Couldn't find budget! %s\n", nProp.ToString().c_str());
return false;
}
void CBudgetManager::ProcessMessage(CNode* pfrom, std::string& strCommand, CDataStream& vRecv)
{
// lite mode is not supported
@ -790,7 +852,7 @@ std::string CFinalizedBudget::GetProposals() {
std::string ret = "aeu";
BOOST_FOREACH(uint256& nHash, vecProposals){
CFinalizedBudget* prop = budget.Find(nHash);
CFinalizedBudget* prop = budget.FindFinalizedBudget(nHash);
std::string token = nHash.ToString();
if(prop) token = prop->GetName();
@ -801,7 +863,7 @@ std::string CFinalizedBudget::GetProposals() {
return ret;
}
CFinalizedBudget *CBudgetManager::Find(uint256 nHash)
CFinalizedBudget *CBudgetManager::FindFinalizedBudget(uint256 nHash)
{
if(mapFinalizedBudgets.count(nHash))
return &mapFinalizedBudgets[nHash];
@ -809,6 +871,13 @@ CFinalizedBudget *CBudgetManager::Find(uint256 nHash)
return NULL;
}
CBudgetProposal *CBudgetManager::FindProposal(uint256 nHash)
{
if(mapProposals.count(nHash))
return &mapProposals[nHash];
return NULL;
}
bool CBudgetManager::PropExists(uint256 nHash)
{
@ -950,6 +1019,7 @@ bool CFinalizedBudget::IsValid()
//must be the correct block for payment to happen (once a month)
if(nBlockStart % GetBudgetPaymentCycleBlocks() != 0) return false;
if(GetBlockEnd() - nBlockStart > 100) return false;
if(vecProposals.size() > 100) return false;
//make sure all prop names exist
BOOST_FOREACH(uint256 nHash, vecProposals){

View File

@ -1,5 +1,5 @@
// Copyright (c) 2014-2015 The Dash developers
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef MASTERNODE_BUDGET_H
@ -90,8 +90,9 @@ public:
void Calculate();
void ProcessMessage(CNode* pfrom, std::string& strCommand, CDataStream& vRecv);
void NewBlock();
CBudgetProposal *Find(const std::string &strProposalName);
CFinalizedBudget *Find(uint256 nHash);
CBudgetProposal *FindProposal(const std::string &strProposalName);
CBudgetProposal *FindProposal(uint256 nHash);
CFinalizedBudget *FindFinalizedBudget(uint256 nHash);
std::pair<std::string, std::string> GetVotes(std::string strProposalName);
@ -108,6 +109,7 @@ public:
bool PropExists(uint256 nHash);
bool IsTransactionValid(const CTransaction& txNew, int nBlockHeight);
std::string GetRequiredPaymentsString(int64_t nBlockHeight);
void FillBlockPayee(CMutableTransaction& txNew, int64_t nFees);
void Clear(){
printf("Not implemented\n");
@ -177,6 +179,7 @@ public:
if(i > (int)vecProposals.size()-1) return 0;
return vecProposals[i];
}
bool GetPayeeAndAmount(int64_t nBlockHeight, CScript& payee, int64_t& nAmount);
uint256 GetHash(){
/*

View File

@ -8,6 +8,7 @@
#include "darksend.h"
#include "util.h"
#include "sync.h"
#include "spork.h"
#include "addrman.h"
#include <boost/lexical_cast.hpp>
@ -18,12 +19,44 @@ CMasternodePayments masternodePayments;
map<uint256, CMasternodePaymentWinner> mapMasternodePayeeVotes;
map<uint256, CMasternodeBlockPayees> mapMasternodeBlocks;
bool IsBlockValueValid(int64_t nBlockValue, int64_t nExpectedValue){
CBlockIndex* pindexPrev = chainActive.Tip();
if(pindexPrev == NULL) return true;
//while syncing take the longest chain
if (fImporting || fReindex || pindexPrev->nHeight+1 < Checkpoints::GetTotalBlocksEstimate()) {
//super blocks will always be on these blocks, max 100 per budgeting
if(pindexPrev->nHeight+1 % GetBudgetPaymentCycleBlocks() < 100){
return true;
} else {
if(nBlockValue > nExpectedValue) return false;
}
} else { // we're synced so check the budget schedule
if(budget.IsBudgetPaymentBlock(pindexPrev->nHeight+1)){
//the value of the block is evaluated in CheckBlock
return true;
} else {
if(nBlockValue > nExpectedValue) return false;
}
}
return true;
}
bool IsBlockPayeeValid(const CTransaction& txNew, int64_t nBlockHeight)
{
//check if it's a budget block
if(budget.IsBudgetPaymentBlock(nBlockHeight)){
if(budget.IsTransactionValid(txNew, nBlockHeight)){
return true;
} else {
LogPrintf("Invalid budget payment detected %s\n", txNew.ToString().c_str());
if(IsSporkActive(SPORK_9_MASTERNODE_BUDGET_ENFORCEMENT)){
return false;
} else {
LogPrintf("Budget enforcement is disabled, accepting block");
return true;
}
}
}
@ -31,11 +64,32 @@ bool IsBlockPayeeValid(const CTransaction& txNew, int64_t nBlockHeight)
if(masternodePayments.IsTransactionValid(txNew, nBlockHeight))
{
return true;
} else {
LogPrintf("Invalid mn payment detected %s\n", txNew.ToString().c_str());
if(IsSporkActive(SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT)){
return false;
} else {
LogPrintf("Masternode payment enforcement is disabled, accepting block");
return true;
}
}
return false;
}
void FillBlockPayee(CMutableTransaction& txNew, int64_t nFees)
{
CBlockIndex* pindexPrev = chainActive.Tip();
if(!pindexPrev) return;
if(budget.IsBudgetPaymentBlock(pindexPrev->nHeight+1)){
budget.FillBlockPayee(txNew, nFees);
} else {
masternodePayments.FillBlockPayee(txNew, nFees);
}
}
std::string GetRequiredPaymentsString(int64_t nBlockHeight)
{
if(budget.IsBudgetPaymentBlock(nBlockHeight)){
@ -45,6 +99,47 @@ std::string GetRequiredPaymentsString(int64_t nBlockHeight)
}
}
void CMasternodePayments::FillBlockPayee(CMutableTransaction& txNew, int64_t nFees)
{
CBlockIndex* pindexPrev = chainActive.Tip();
if(!pindexPrev) return;
bool hasPayment = true;
CScript payee;
//spork
if(!masternodePayments.GetBlockPayee(pindexPrev->nHeight+1, payee)){
//no masternode detected
CMasternode* winningNode = mnodeman.GetCurrentMasterNode(1);
if(winningNode){
payee = GetScriptForDestination(winningNode->pubkey.GetID());
} else {
LogPrintf("CreateNewBlock: Failed to detect masternode to pay\n");
hasPayment = false;
}
}
CAmount blockValue = GetBlockValue(pindexPrev->nBits, pindexPrev->nHeight, nFees);
CAmount masternodePayment = GetMasternodePayment(pindexPrev->nHeight+1, blockValue);
txNew.vout[0].nValue = blockValue;
if(hasPayment){
txNew.vout.resize(2);
txNew.vout[1].scriptPubKey = payee;
txNew.vout[1].nValue = masternodePayment;
txNew.vout[0].nValue -= masternodePayment;
CTxDestination address1;
ExtractDestination(payee, address1);
CBitcoinAddress address2(address1);
LogPrintf("Masternode payment to %s\n", address2.ToString().c_str());
}
}
void CMasternodePayments::ProcessMessageMasternodePayments(CNode* pfrom, std::string& strCommand, CDataStream& vRecv)
{
if(IsInitialBlockDownload()) return;
@ -304,13 +399,13 @@ bool CMasternodePaymentWinner::IsValid()
if(n == -1)
{
if(fDebug) LogPrintf("CMasternodePaymentWinner::IsValid - Unknown Masternode\n");
LogPrintf("CMasternodePaymentWinner::IsValid - Unknown Masternode\n");
return false;
}
if(n > MNPAYMENTS_SIGNATURES_TOTAL)
{
if(fDebug) LogPrintf("CMasternodePaymentWinner::IsValid - Masternode not in the top %d (%d)\n", MNPAYMENTS_SIGNATURES_TOTAL, n);
LogPrintf("CMasternodePaymentWinner::IsValid - Masternode not in the top %d (%d)\n", MNPAYMENTS_SIGNATURES_TOTAL, n);
return false;
}

View File

@ -29,6 +29,8 @@ void ProcessMessageMasternodePayments(CNode* pfrom, std::string& strCommand, CDa
bool IsReferenceNode(CTxIn& vin);
bool IsBlockPayeeValid(const CTransaction& txNew, int64_t nBlockHeight);
std::string GetRequiredPaymentsString(int64_t nBlockHeight);
bool IsBlockValueValid(int64_t nBlockValue, int64_t nExpectedValue);
void FillBlockPayee(CMutableTransaction& txNew, int64_t nFees);
class CMasternodePayee : public CTxOut
{
@ -194,6 +196,7 @@ public:
void ProcessMessageMasternodePayments(CNode* pfrom, std::string& strCommand, CDataStream& vRecv);
std::string GetRequiredPaymentsString(int nBlockHeight);
void FillBlockPayee(CMutableTransaction& txNew, int64_t nFees);
};

View File

@ -379,6 +379,9 @@ CMasternode* CMasternodeMan::GetNextMasternodeInQueueForPayment()
//it's in the list -- so let's skip it
if(masternodePayments.IsScheduled(mn)) continue;
//make sure it has as many confirmations as there are masternodes
if(mn.GetMasternodeInputAge() < CountEnabled()) continue;
if(pOldestMasternode == NULL || pOldestMasternode->SecondsSincePayment() < mn.SecondsSincePayment()){
pOldestMasternode = &mn;
}

View File

@ -99,7 +99,6 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn)
return NULL;
CBlock *pblock = &pblocktemplate->block; // pointer for convenience
int payments = 1;
// -regtest only: allow overriding block.nVersion with
// -blockversion=N to test forking scenarios
if (Params().MineBlocksOnDemand())
@ -127,9 +126,6 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn)
unsigned int nBlockMinSize = GetArg("-blockminsize", DEFAULT_BLOCK_MIN_SIZE);
nBlockMinSize = std::min(nBlockMaxSize, nBlockMinSize);
// start masternode payments
bool bMasterNodePayment = GetTimeMicros() > Params().StartMasternodePayments();
// Collect memory pool transactions into the block
CAmount nFees = 0;
@ -139,35 +135,6 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn)
const int nHeight = pindexPrev->nHeight + 1;
CCoinsViewCache view(pcoinsTip);
if(bMasterNodePayment) {
bool hasPayment = true;
//spork
if(!masternodePayments.GetBlockPayee(pindexPrev->nHeight+1, pblock->payee)){
//no masternode detected
CMasternode* winningNode = mnodeman.GetCurrentMasterNode(1);
if(winningNode){
pblock->payee = GetScriptForDestination(winningNode->pubkey.GetID());
} else {
LogPrintf("CreateNewBlock: Failed to detect masternode to pay\n");
hasPayment = false;
}
}
if(hasPayment){
payments++;
txNew.vout.resize(payments);
txNew.vout[payments-1].scriptPubKey = pblock->payee;
txNew.vout[payments-1].nValue = 0;
CTxDestination address1;
ExtractDestination(pblock->payee, address1);
CBitcoinAddress address2(address1);
LogPrintf("Masternode payment to %s\n", address2.ToString().c_str());
}
}
// Add our coinbase tx as first transaction
pblock->vtx.push_back(txNew);
pblocktemplate->vTxFees.push_back(-1); // updated at end
@ -351,18 +318,12 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn)
}
}
// Masternode and general budget payments
FillBlockPayee(txNew, nFees);
nLastBlockTx = nBlockTx;
nLastBlockSize = nBlockSize;
LogPrintf("CreateNewBlock(): total size %u\n", nBlockSize);
CAmount blockValue = GetBlockValue(pindexPrev->nBits, pindexPrev->nHeight, nFees);
CAmount masternodePayment = GetMasternodePayment(pindexPrev->nHeight+1, blockValue);
//create masternode payment
if(payments > 1){
txNew.vout[payments-1].nValue = masternodePayment;
blockValue -= masternodePayment;
}
txNew.vout[0].nValue = blockValue;
// Compute final coinbase transaction.
txNew.vin[0].scriptSig = CScript() << nHeight << OP_0;

View File

@ -248,7 +248,7 @@ Value mnbudget(const Array& params, bool fHelp)
std::string strProposalName = params[1].get_str();
CBudgetProposal* prop = budget.Find(strProposalName);
CBudgetProposal* prop = budget.FindProposal(strProposalName);
if(prop == NULL) return "Unknown proposal name";
@ -283,7 +283,7 @@ Value mnbudget(const Array& params, bool fHelp)
Object obj;
CBudgetProposal* prop = budget.Find(strProposalName);
CBudgetProposal* prop = budget.FindProposal(strProposalName);
if(prop == NULL) return "Unknown proposal name";

View File

@ -81,11 +81,12 @@ bool IsSporkActive(int nSporkID)
if(mapSporksActive.count(nSporkID)){
r = mapSporksActive[nSporkID].nValue;
} else {
if(nSporkID == SPORK_1_MASTERNODE_PAYMENTS_ENFORCEMENT) r = SPORK_1_MASTERNODE_PAYMENTS_ENFORCEMENT_DEFAULT;
if(nSporkID == SPORK_2_INSTANTX) r = SPORK_2_INSTANTX_DEFAULT;
if(nSporkID == SPORK_3_INSTANTX_BLOCK_FILTERING) r = SPORK_3_INSTANTX_BLOCK_FILTERING_DEFAULT;
if(nSporkID == SPORK_5_MAX_VALUE) r = SPORK_5_MAX_VALUE_DEFAULT;
if(nSporkID == SPORK_7_MASTERNODE_SCANNING) r = SPORK_7_MASTERNODE_SCANNING;
if(nSporkID == SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT) r = SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT;
if(nSporkID == SPORK_9_MASTERNODE_BUDGET_ENFORCEMENT) r = SPORK_9_MASTERNODE_BUDGET_ENFORCEMENT;
if(r == 0) LogPrintf("GetSpork::Unknown Spork %d\n", nSporkID);
}
@ -102,11 +103,12 @@ int GetSporkValue(int nSporkID)
if(mapSporksActive.count(nSporkID)){
r = mapSporksActive[nSporkID].nValue;
} else {
if(nSporkID == SPORK_1_MASTERNODE_PAYMENTS_ENFORCEMENT) r = SPORK_1_MASTERNODE_PAYMENTS_ENFORCEMENT_DEFAULT;
if(nSporkID == SPORK_2_INSTANTX) r = SPORK_2_INSTANTX_DEFAULT;
if(nSporkID == SPORK_3_INSTANTX_BLOCK_FILTERING) r = SPORK_3_INSTANTX_BLOCK_FILTERING_DEFAULT;
if(nSporkID == SPORK_5_MAX_VALUE) r = SPORK_5_MAX_VALUE_DEFAULT;
if(nSporkID == SPORK_7_MASTERNODE_SCANNING) r = SPORK_7_MASTERNODE_SCANNING;
if(nSporkID == SPORK_7_MASTERNODE_SCANNING) r = SPORK_7_MASTERNODE_SCANNING_DEFAULT;
if(nSporkID == SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT) r = SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT_DEFAULT;
if(nSporkID == SPORK_9_MASTERNODE_BUDGET_ENFORCEMENT) r = SPORK_9_MASTERNODE_BUDGET_ENFORCEMENT_DEFAULT;
if(r == 0) LogPrintf("GetSpork::Unknown Spork %d\n", nSporkID);
}
@ -209,22 +211,24 @@ bool CSporkManager::SetPrivKey(std::string strPrivKey)
int CSporkManager::GetSporkIDByName(std::string strName)
{
if(strName == "SPORK_1_MASTERNODE_PAYMENTS_ENFORCEMENT") return SPORK_1_MASTERNODE_PAYMENTS_ENFORCEMENT;
if(strName == "SPORK_2_INSTANTX") return SPORK_2_INSTANTX;
if(strName == "SPORK_3_INSTANTX_BLOCK_FILTERING") return SPORK_3_INSTANTX_BLOCK_FILTERING;
if(strName == "SPORK_5_MAX_VALUE") return SPORK_5_MAX_VALUE;
if(strName == "SPORK_7_MASTERNODE_SCANNING") return SPORK_7_MASTERNODE_SCANNING;
if(strName == "SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT") return SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT;
if(strName == "SPORK_9_MASTERNODE_BUDGET_ENFORCEMENT") return SPORK_9_MASTERNODE_BUDGET_ENFORCEMENT;
return -1;
}
std::string CSporkManager::GetSporkNameByID(int id)
{
if(id == SPORK_1_MASTERNODE_PAYMENTS_ENFORCEMENT) return "SPORK_1_MASTERNODE_PAYMENTS_ENFORCEMENT";
if(id == SPORK_2_INSTANTX) return "SPORK_2_INSTANTX";
if(id == SPORK_3_INSTANTX_BLOCK_FILTERING) return "SPORK_3_INSTANTX_BLOCK_FILTERING";
if(id == SPORK_5_MAX_VALUE) return "SPORK_5_MAX_VALUE";
if(id == SPORK_7_MASTERNODE_SCANNING) return "SPORK_7_MASTERNODE_SCANNING";
if(id == SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT) return "SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT";
if(id == SPORK_9_MASTERNODE_BUDGET_ENFORCEMENT) return "SPORK_9_MASTERNODE_BUDGET_ENFORCEMENT";
return "Unknown";
}

View File

@ -19,20 +19,23 @@
using namespace std;
using namespace boost;
// Don't ever reuse these IDs for other sporks
#define SPORK_1_MASTERNODE_PAYMENTS_ENFORCEMENT 10000
/*
Don't ever reuse these IDs for other sporks
- This would result in old clients getting confused about which spork is for what
*/
#define SPORK_2_INSTANTX 10001
#define SPORK_3_INSTANTX_BLOCK_FILTERING 10002
#define SPORK_4_NOTUSED 10003
#define SPORK_5_MAX_VALUE 10004
#define SPORK_6_NOTUSED 10005
#define SPORK_7_MASTERNODE_SCANNING 10006
#define SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT 10007
#define SPORK_9_MASTERNODE_BUDGET_ENFORCEMENT 10008
#define SPORK_1_MASTERNODE_PAYMENTS_ENFORCEMENT_DEFAULT 1424217600 //2015-2-18
#define SPORK_2_INSTANTX_DEFAULT 978307200 //2001-1-1
#define SPORK_3_INSTANTX_BLOCK_FILTERING_DEFAULT 1424217600 //2015-2-18
#define SPORK_5_MAX_VALUE_DEFAULT 1000 //1000 DASH
#define SPORK_7_MASTERNODE_SCANNING_DEFAULT 978307200 //2001-1-1
#define SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT_DEFAULT 1434326400 //2015-6-15
#define SPORK_9_MASTERNODE_BUDGET_ENFORCEMENT_DEFAULT 1434326400 //2015-6-15
class CSporkMessage;
class CSporkManager;