Verus Coin - this coin was backdoored by it's lead dev and should not be trusted!
https://git.hush.is/duke/backdoors/src/branch/master/vrsc.md
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3953 lines
179 KiB
3953 lines
179 KiB
// Copyright (c) 2009-2010 Satoshi Nakamoto
|
|
// Copyright (c) 2009-2014 The Bitcoin Core developers
|
|
// Copyright (c) 2018-2021 Verus Coin Foundation
|
|
// Distributed under the MIT software license, see the accompanying
|
|
// file COPYING or https://www.opensource.org/licenses/mit-license.php .
|
|
|
|
#include "miner.h"
|
|
#ifdef ENABLE_MINING
|
|
#include "pow/tromp/equi_miner.h"
|
|
#endif
|
|
|
|
#include "amount.h"
|
|
#include "chainparams.h"
|
|
#include "cc/StakeGuard.h"
|
|
#include "importcoin.h"
|
|
#include "consensus/consensus.h"
|
|
#include "consensus/upgrades.h"
|
|
#include "consensus/validation.h"
|
|
#ifdef ENABLE_MINING
|
|
#include "crypto/equihash.h"
|
|
#include "crypto/verus_hash.h"
|
|
#endif
|
|
#include "hash.h"
|
|
#include "key_io.h"
|
|
#include "main.h"
|
|
#include "metrics.h"
|
|
#include "net.h"
|
|
#include "pow.h"
|
|
#include "primitives/transaction.h"
|
|
#include "random.h"
|
|
#include "timedata.h"
|
|
#include "ui_interface.h"
|
|
#include "util.h"
|
|
#include "utilmoneystr.h"
|
|
#include "validationinterface.h"
|
|
|
|
#include "zcash/Address.hpp"
|
|
#include "transaction_builder.h"
|
|
|
|
#include "sodium.h"
|
|
|
|
#include <boost/thread.hpp>
|
|
#include <boost/tuple/tuple.hpp>
|
|
#ifdef ENABLE_MINING
|
|
#include <functional>
|
|
#endif
|
|
#include <mutex>
|
|
|
|
#include "pbaas/pbaas.h"
|
|
#include "pbaas/notarization.h"
|
|
#include "pbaas/identity.h"
|
|
#include "rpc/pbaasrpc.h"
|
|
#include "transaction_builder.h"
|
|
|
|
using namespace std;
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// BitcoinMiner
|
|
//
|
|
|
|
//
|
|
// Unconfirmed transactions in the memory pool often depend on other
|
|
// transactions in the memory pool. When we select transactions from the
|
|
// pool, we select by highest priority or fee rate, so we might consider
|
|
// transactions that depend on transactions that aren't yet in the block.
|
|
// The COrphan class keeps track of these 'temporary orphans' while
|
|
// CreateBlock is figuring out which transactions to include.
|
|
//
|
|
class COrphan
|
|
{
|
|
public:
|
|
const CTransaction* ptx;
|
|
set<uint256> setDependsOn;
|
|
CFeeRate feeRate;
|
|
double dPriority;
|
|
|
|
COrphan(const CTransaction* ptxIn) : ptx(ptxIn), feeRate(0), dPriority(0)
|
|
{
|
|
}
|
|
};
|
|
|
|
uint64_t nLastBlockTx = 0;
|
|
uint64_t nLastBlockSize = 0;
|
|
|
|
// We want to sort transactions by priority and fee rate, so:
|
|
typedef boost::tuple<double, CFeeRate, const 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>();
|
|
}
|
|
}
|
|
};
|
|
|
|
void UpdateTime(CBlockHeader* pblock, const Consensus::Params& consensusParams, const CBlockIndex* pindexPrev)
|
|
{
|
|
pblock->nTime = std::max(pindexPrev->GetMedianTimePast()+1, GetAdjustedTime());
|
|
|
|
// Updating time can change work required on testnet:
|
|
if (consensusParams.nPowAllowMinDifficultyBlocksAfterHeight != boost::none) {
|
|
pblock->nBits = GetNextWorkRequired(pindexPrev, pblock, consensusParams);
|
|
}
|
|
}
|
|
|
|
#include "komodo_defs.h"
|
|
|
|
extern CCriticalSection cs_metrics;
|
|
extern int32_t KOMODO_MININGTHREADS,KOMODO_LONGESTCHAIN,IS_KOMODO_NOTARY,USE_EXTERNAL_PUBKEY,KOMODO_CHOSEN_ONE,ASSETCHAIN_INIT,KOMODO_INITDONE,KOMODO_ON_DEMAND,KOMODO_INITDONE,KOMODO_PASSPORT_INITDONE;
|
|
extern uint64_t ASSETCHAINS_COMMISSION, ASSETCHAINS_STAKED;
|
|
extern bool VERUS_MINTBLOCKS;
|
|
extern uint64_t ASSETCHAINS_REWARD[ASSETCHAINS_MAX_ERAS], ASSETCHAINS_TIMELOCKGTE, ASSETCHAINS_NONCEMASK[];
|
|
extern const char *ASSETCHAINS_ALGORITHMS[];
|
|
extern int32_t VERUS_MIN_STAKEAGE, ASSETCHAINS_EQUIHASH, ASSETCHAINS_VERUSHASH, ASSETCHAINS_LASTERA, ASSETCHAINS_LWMAPOS, ASSETCHAINS_NONCESHIFT[], ASSETCHAINS_HASHESPERROUND[];
|
|
extern uint32_t ASSETCHAINS_ALGO;
|
|
extern char ASSETCHAINS_SYMBOL[KOMODO_ASSETCHAIN_MAXLEN];
|
|
extern uint160 ASSETCHAINS_CHAINID;
|
|
extern uint160 VERUS_CHAINID;
|
|
extern std::string VERUS_CHAINNAME;
|
|
extern int32_t PBAAS_STARTBLOCK, PBAAS_ENDBLOCK;
|
|
extern string PBAAS_HOST, PBAAS_USERPASS, ASSETCHAINS_RPCHOST, ASSETCHAINS_RPCCREDENTIALS;;
|
|
extern int32_t PBAAS_PORT;
|
|
extern uint16_t ASSETCHAINS_RPCPORT;
|
|
extern std::string NOTARY_PUBKEY,ASSETCHAINS_OVERRIDE_PUBKEY;
|
|
extern uint8_t NOTARY_PUBKEY33[33],ASSETCHAINS_OVERRIDE_PUBKEY33[33];
|
|
extern CCriticalSection smartTransactionCS;
|
|
|
|
uint32_t Mining_start, Mining_height;
|
|
int32_t My_notaryid = -1;
|
|
|
|
void vcalc_sha256(char deprecated[(256 >> 3) * 2 + 1],uint8_t hash[256 >> 3],uint8_t *src,int32_t len);
|
|
int32_t komodo_chosennotary(int32_t *notaryidp,int32_t height,uint8_t *pubkey33,uint32_t timestamp);
|
|
int32_t komodo_pax_opreturn(int32_t height,uint8_t *opret,int32_t maxsize);
|
|
int32_t komodo_baseid(char *origbase);
|
|
int32_t komodo_validate_interest(const CTransaction &tx,int32_t txheight,uint32_t nTime,int32_t dispflag);
|
|
int64_t komodo_block_unlocktime(uint32_t nHeight);
|
|
uint64_t komodo_commission(const CBlock *block);
|
|
int32_t komodo_staked(CMutableTransaction &txNew,uint32_t nBits,uint32_t *blocktimep,uint32_t *txtimep,uint256 *utxotxidp,int32_t *utxovoutp,uint64_t *utxovaluep,uint8_t *utxosig);
|
|
int32_t verus_staked(CBlock *pBlock, CMutableTransaction &txNew, uint32_t &nBits, arith_uint256 &hashResult, std::vector<unsigned char> &utxosig, CTxDestination &rewardDest);
|
|
int32_t komodo_notaryvin(CMutableTransaction &txNew,uint8_t *notarypub33);
|
|
UniValue getminingdistribution(const UniValue& params, bool fHelp);
|
|
|
|
void IncrementExtraNonce(CBlock* pblock, CBlockIndex* pindexPrev, unsigned int &nExtraNonce, bool buildMerkle, uint32_t *pSaveBits)
|
|
{
|
|
// Update nExtraNonce
|
|
static uint256 hashPrevBlock;
|
|
if (hashPrevBlock != pblock->hashPrevBlock)
|
|
{
|
|
nExtraNonce = 0;
|
|
hashPrevBlock = pblock->hashPrevBlock;
|
|
}
|
|
++nExtraNonce;
|
|
|
|
if (pSaveBits)
|
|
{
|
|
*pSaveBits = pblock->nBits;
|
|
}
|
|
|
|
int32_t nHeight = pindexPrev->GetHeight() + 1;
|
|
|
|
int solutionVersion = CConstVerusSolutionVector::activationHeight.ActiveVersion(nHeight);
|
|
|
|
if (solutionVersion >= CConstVerusSolutionVector::activationHeight.ACTIVATE_PBAAS_HEADER)
|
|
{
|
|
// coinbase should already be finalized in the new version
|
|
if (buildMerkle)
|
|
{
|
|
pblock->hashMerkleRoot = pblock->BuildMerkleTree();
|
|
pblock->SetPrevMMRRoot(ChainMerkleMountainView(chainActive.GetMMR(), pindexPrev->GetHeight()).GetRoot());
|
|
BlockMMRange mmRange(pblock->BuildBlockMMRTree());
|
|
BlockMMView mmView(mmRange);
|
|
pblock->SetBlockMMRRoot(mmView.GetRoot());
|
|
pblock->AddUpdatePBaaSHeader();
|
|
}
|
|
|
|
UpdateTime(pblock, Params().GetConsensus(), pindexPrev);
|
|
|
|
// POS blocks have already had their solution space filled, and there is no actual extra nonce, extradata is used
|
|
// for POS proof, so don't modify it
|
|
if (solutionVersion >= CConstVerusSolutionVector::activationHeight.ACTIVATE_PBAAS && !pblock->IsVerusPOSBlock())
|
|
{
|
|
pblock->AddUpdatePBaaSHeader();
|
|
|
|
uint8_t dummy;
|
|
// clear extra data to allow adding more PBaaS headers
|
|
pblock->SetExtraData(&dummy, 0);
|
|
|
|
// combine blocks and set compact difficulty if necessary
|
|
uint32_t savebits;
|
|
if ((savebits = ConnectedChains.CombineBlocks(*pblock)) && pSaveBits)
|
|
{
|
|
arith_uint256 ours, merged;
|
|
ours.SetCompact(pblock->nBits);
|
|
merged.SetCompact(savebits);
|
|
if (merged > ours)
|
|
{
|
|
*pSaveBits = savebits;
|
|
}
|
|
}
|
|
|
|
// extra nonce is kept in the header, not in the coinbase any longer
|
|
// this allows instant spend transactions to use coinbase funds for
|
|
// inputs by ensuring that once final, the coinbase transaction hash
|
|
// will not continue to change
|
|
CDataStream s(SER_NETWORK, PROTOCOL_VERSION);
|
|
s << nExtraNonce;
|
|
std::vector<unsigned char> vENonce(s.begin(), s.end());
|
|
|
|
assert(pblock->ExtraDataLen() >= vENonce.size());
|
|
pblock->SetExtraData(vENonce.data(), vENonce.size());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// finalize input of coinbase
|
|
CMutableTransaction txcb(pblock->vtx[0]);
|
|
txcb.vin[0].scriptSig = (CScript() << nHeight << CScriptNum(nExtraNonce)) + COINBASE_FLAGS;
|
|
assert(txcb.vin[0].scriptSig.size() <= 100);
|
|
pblock->vtx[0] = txcb;
|
|
if (buildMerkle)
|
|
{
|
|
pblock->hashMerkleRoot = pblock->BuildMerkleTree();
|
|
}
|
|
|
|
UpdateTime(pblock, Params().GetConsensus(), pindexPrev);
|
|
}
|
|
}
|
|
|
|
extern CWallet *pwalletMain;
|
|
|
|
CPubKey GetSolutionPubKey(const std::vector<std::vector<unsigned char>> &vSolutions, txnouttype txType)
|
|
{
|
|
CPubKey pk;
|
|
|
|
if (txType == TX_PUBKEY)
|
|
{
|
|
pk = CPubKey(vSolutions[0]);
|
|
}
|
|
else if(txType == TX_PUBKEYHASH)
|
|
{
|
|
// we need to have this in our wallet to get the public key
|
|
LOCK(pwalletMain->cs_wallet);
|
|
pwalletMain->GetPubKey(CKeyID(uint160(vSolutions[0])), pk);
|
|
}
|
|
else if (txType == TX_CRYPTOCONDITION)
|
|
{
|
|
if (vSolutions[0].size() == 33)
|
|
{
|
|
pk = CPubKey(vSolutions[0]);
|
|
}
|
|
else if (vSolutions[0].size() == 34 && vSolutions[0][0] == COptCCParams::ADDRTYPE_PK)
|
|
{
|
|
pk = CPubKey(std::vector<unsigned char>(vSolutions[0].begin() + 1, vSolutions[0].end()));
|
|
}
|
|
else if (vSolutions[0].size() == 20)
|
|
{
|
|
LOCK(pwalletMain->cs_wallet);
|
|
pwalletMain->GetPubKey(CKeyID(uint160(vSolutions[0])), pk);
|
|
}
|
|
else if (vSolutions[0].size() == 21 && vSolutions[0][0] == COptCCParams::ADDRTYPE_ID)
|
|
{
|
|
// destination is an identity, see if we can get its first public key
|
|
std::pair<CIdentityMapKey, CIdentityMapValue> identity;
|
|
|
|
if (pwalletMain->GetIdentity(CIdentityID(uint160(std::vector<unsigned char>(vSolutions[0].begin() + 1, vSolutions[0].end()))), identity) &&
|
|
identity.second.IsValidUnrevoked() &&
|
|
identity.second.primaryAddresses.size())
|
|
{
|
|
CPubKey pkTmp = boost::apply_visitor<GetPubKeyForPubKey>(GetPubKeyForPubKey(), identity.second.primaryAddresses[0]);
|
|
if (pkTmp.IsValid())
|
|
{
|
|
pk = pkTmp;
|
|
}
|
|
else
|
|
{
|
|
LOCK(pwalletMain->cs_wallet);
|
|
pwalletMain->GetPubKey(CKeyID(GetDestinationID(identity.second.primaryAddresses[0])), pk);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return pk;
|
|
}
|
|
|
|
CPubKey GetScriptPublicKey(const CScript &scriptPubKey)
|
|
{
|
|
txnouttype typeRet;
|
|
std::vector<std::vector<unsigned char>> vSolutions;
|
|
if (Solver(scriptPubKey, typeRet, vSolutions))
|
|
{
|
|
return GetSolutionPubKey(vSolutions, typeRet);
|
|
}
|
|
return CPubKey();
|
|
}
|
|
|
|
// call a chain that we consider a notary chain, meaning we call its daemon, not the other way around,
|
|
// retrieve new exports that we have not imported, and process them. Also, send any exports that are now
|
|
// provable with the available notarization on the specified chain.
|
|
void ProcessNewImports(const uint160 &sourceChainID, CPBaaSNotarization &lastConfirmed, CUTXORef &lastConfirmedUTXO, uint32_t nHeight)
|
|
{
|
|
if (CConstVerusSolutionVector::GetVersionByHeight(nHeight) < CActivationHeight::ACTIVATE_PBAAS ||
|
|
CConstVerusSolutionVector::activationHeight.IsActivationHeight(CActivationHeight::ACTIVATE_PBAAS, nHeight))
|
|
{
|
|
return;
|
|
}
|
|
|
|
uint32_t consensusBranchId = CurrentEpochBranchId(nHeight, Params().GetConsensus());
|
|
|
|
// get any pending imports from the source chain. if the source chain is this chain, we don't need notarization
|
|
CCurrencyDefinition thisChain = ConnectedChains.ThisChain();
|
|
uint160 thisChainID = thisChain.GetID();
|
|
bool isSameChain = thisChainID == sourceChainID;
|
|
|
|
CCurrencyDefinition sourceChain;
|
|
CChainNotarizationData cnd;
|
|
|
|
CTransaction lastImportTx;
|
|
|
|
// we need to find the last unspent import transaction
|
|
std::vector<CAddressUnspentDbEntry> unspentOutputs;
|
|
|
|
bool processIndex = false;
|
|
|
|
{
|
|
LOCK(cs_main);
|
|
sourceChain = ConnectedChains.GetCachedCurrency(sourceChainID);
|
|
if (!sourceChain.IsValid())
|
|
{
|
|
printf("Unrecognized source chain %s\n", EncodeDestination(CIdentityID(sourceChainID)).c_str());
|
|
return;
|
|
}
|
|
|
|
if (!(GetNotarizationData(sourceChainID, cnd) && cnd.IsConfirmed()))
|
|
{
|
|
printf("Cannot get notarization data for currency %s\n", sourceChain.name.c_str());
|
|
return;
|
|
}
|
|
|
|
lastConfirmedUTXO = cnd.vtx[cnd.lastConfirmed].first;
|
|
lastConfirmed = cnd.vtx[cnd.lastConfirmed].second;
|
|
|
|
processIndex = (!isSameChain &&
|
|
lastConfirmed.proofRoots.count(sourceChainID) &&
|
|
GetAddressUnspent(CKeyID(CCrossChainRPCData::GetConditionID(sourceChainID, CCrossChainImport::CurrencySystemImportKey())), CScript::P2IDX, unspentOutputs));
|
|
|
|
}
|
|
|
|
bool found = false;
|
|
CAddressUnspentDbEntry foundEntry;
|
|
CCrossChainImport lastCCI;
|
|
std::vector<std::pair<std::pair<CInputDescriptor, CPartialTransactionProof>, std::vector<CReserveTransfer>>> exports;
|
|
|
|
if (processIndex)
|
|
{
|
|
// if one spends the prior one, get the one that is not spent
|
|
for (auto &txidx : unspentOutputs)
|
|
{
|
|
COptCCParams p;
|
|
if (txidx.second.script.IsPayToCryptoCondition(p) &&
|
|
p.IsValid() &&
|
|
p.evalCode == EVAL_CROSSCHAIN_IMPORT &&
|
|
p.vData.size() &&
|
|
(lastCCI = CCrossChainImport(p.vData[0])).IsValid())
|
|
{
|
|
found = true;
|
|
foundEntry = txidx;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (found &&
|
|
lastCCI.sourceSystemHeight < lastConfirmed.notarizationHeight)
|
|
{
|
|
UniValue params(UniValue::VARR);
|
|
|
|
params.push_back(EncodeDestination(CIdentityID(thisChainID)));
|
|
params.push_back((int64_t)lastCCI.sourceSystemHeight);
|
|
params.push_back((int64_t)lastConfirmed.proofRoots[sourceChainID].rootHeight);
|
|
|
|
UniValue result = NullUniValue;
|
|
try
|
|
{
|
|
if (sourceChainID == thisChain.GetID())
|
|
{
|
|
UniValue getexports(const UniValue& params, bool fHelp);
|
|
result = getexports(params, false);
|
|
}
|
|
else if (ConnectedChains.IsNotaryAvailable())
|
|
{
|
|
result = find_value(RPCCallRoot("getexports", params), "result");
|
|
}
|
|
} catch (exception e)
|
|
{
|
|
LogPrint("notarization", "Could not get latest export from external chain %s for %s\n", EncodeDestination(CIdentityID(sourceChainID)).c_str(), uni_get_str(params[0]).c_str());
|
|
return;
|
|
}
|
|
|
|
// now, we should have a list of exports to import in order
|
|
if (!result.isArray() || !result.size())
|
|
{
|
|
return;
|
|
}
|
|
bool foundCurrent = false;
|
|
for (int i = 0; i < result.size(); i++)
|
|
{
|
|
uint256 exportTxId = uint256S(uni_get_str(find_value(result[i], "txid")));
|
|
if (!foundCurrent && !lastCCI.exportTxId.IsNull())
|
|
{
|
|
// when we find our export, take the next
|
|
if (exportTxId == lastCCI.exportTxId)
|
|
{
|
|
foundCurrent = true;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// create one import at a time
|
|
uint32_t notarizationHeight = uni_get_int64(find_value(result[i], "height"));
|
|
int32_t exportTxOutNum = uni_get_int(find_value(result[i], "txoutnum"));
|
|
CPartialTransactionProof txProof = CPartialTransactionProof(find_value(result[i], "partialtransactionproof"));
|
|
UniValue transferArrUni = find_value(result[i], "transfers");
|
|
if (!notarizationHeight ||
|
|
exportTxId.IsNull() ||
|
|
exportTxOutNum == -1 ||
|
|
!transferArrUni.isArray())
|
|
{
|
|
printf("Invalid export from %s\n", uni_get_str(params[0]).c_str());
|
|
return;
|
|
}
|
|
|
|
CTransaction exportTx;
|
|
uint256 blkHash;
|
|
auto proofRootIt = lastConfirmed.proofRoots.find(sourceChainID);
|
|
if (!isSameChain &&
|
|
!(txProof.IsValid() &&
|
|
!txProof.GetPartialTransaction(exportTx).IsNull() &&
|
|
txProof.TransactionHash() == exportTxId &&
|
|
proofRootIt != lastConfirmed.proofRoots.end() &&
|
|
proofRootIt->second.stateRoot == txProof.CheckPartialTransaction(exportTx) &&
|
|
exportTx.vout.size() > exportTxOutNum))
|
|
{
|
|
/* printf("%s: proofRoot: %s, checkPartialRoot: %s, proofheight: %u, ischainproof: %s, blockhash: %s\n",
|
|
__func__,
|
|
proofRootIt->second.ToUniValue().write(1,2).c_str(),
|
|
txProof.CheckPartialTransaction(exportTx).GetHex().c_str(),
|
|
txProof.GetProofHeight(),
|
|
txProof.IsChainProof() ? "true" : "false",
|
|
txProof.GetBlockHash().GetHex().c_str()); */
|
|
printf("Invalid export for %s\n", uni_get_str(params[0]).c_str());
|
|
return;
|
|
}
|
|
{
|
|
LOCK(cs_main);
|
|
if (isSameChain &&
|
|
!(myGetTransaction(exportTxId, exportTx, blkHash) &&
|
|
exportTx.vout.size() > exportTxOutNum))
|
|
{
|
|
printf("Invalid export msg2 from %s\n", uni_get_str(params[0]).c_str());
|
|
return;
|
|
}
|
|
}
|
|
if (!foundCurrent)
|
|
{
|
|
CCrossChainExport ccx(exportTx.vout[exportTxOutNum].scriptPubKey);
|
|
if (!ccx.IsValid())
|
|
{
|
|
printf("Invalid export msg3 from %s\n", uni_get_str(params[0]).c_str());
|
|
return;
|
|
}
|
|
if (ccx.IsChainDefinition())
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
std::pair<std::pair<CInputDescriptor, CPartialTransactionProof>, std::vector<CReserveTransfer>> oneExport =
|
|
std::make_pair(std::make_pair(CInputDescriptor(exportTx.vout[exportTxOutNum].scriptPubKey,
|
|
exportTx.vout[exportTxOutNum].nValue,
|
|
CTxIn(exportTxId, exportTxOutNum)),
|
|
txProof),
|
|
std::vector<CReserveTransfer>());
|
|
for (int j = 0; j < transferArrUni.size(); j++)
|
|
{
|
|
//printf("%s: onetransfer: %s\n", __func__, transferArrUni[j].write(1,2).c_str());
|
|
oneExport.second.push_back(CReserveTransfer(transferArrUni[j]));
|
|
if (!oneExport.second.back().IsValid())
|
|
{
|
|
printf("Invalid reserve transfers in export from %s\n", sourceChain.name.c_str());
|
|
return;
|
|
}
|
|
}
|
|
exports.push_back(oneExport);
|
|
}
|
|
}
|
|
std::map<uint160, std::vector<std::pair<int, CTransaction>>> newImports;
|
|
ConnectedChains.CreateLatestImports(sourceChain, lastConfirmedUTXO, exports, newImports);
|
|
}
|
|
else if (isSameChain)
|
|
{
|
|
ConnectedChains.ProcessLocalImports();
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
LogPrint("crosschain", "Could not get prior import for currency %s\n", sourceChain.name.c_str());
|
|
return;
|
|
}
|
|
}
|
|
|
|
bool CheckNotaryConnection(const CRPCChainData ¬arySystem)
|
|
{
|
|
// ensure we have connection parameters, or we fail
|
|
if (notarySystem.rpcHost == "" || notarySystem.rpcUserPass == "" || !notarySystem.rpcPort)
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool CallNotary(const CRPCChainData ¬arySystem, std::string command, const UniValue ¶ms, UniValue &result, UniValue &error)
|
|
{
|
|
// ensure we have connection parameters, or we fail
|
|
if (!CheckNotaryConnection(notarySystem))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
try
|
|
{
|
|
UniValue rpcResult = RPCCall(command, params, notarySystem.rpcUserPass, notarySystem.rpcPort, notarySystem.rpcHost);
|
|
result = find_value(rpcResult, "result");
|
|
error = find_value(rpcResult, "error");
|
|
} catch (std::exception e)
|
|
{
|
|
error = strprintf("Failed to connect to %s chain, error: %s\n", notarySystem.chainDefinition.name.c_str(), e.what());
|
|
}
|
|
return error.isNull();
|
|
}
|
|
|
|
// get initial currency state from the notary system specified
|
|
bool GetBlockOneLaunchNotarization(const CRPCChainData ¬arySystem,
|
|
const uint160 ¤cyID,
|
|
CCurrencyDefinition &curDef,
|
|
CPBaaSNotarization &launchNotarization,
|
|
CPBaaSNotarization ¬aryNotarization,
|
|
std::pair<CUTXORef, CPartialTransactionProof> ¬arizationOutputProof,
|
|
std::pair<CUTXORef, CPartialTransactionProof> &exportOutputProof,
|
|
std::vector<CReserveTransfer> &exportTransfers)
|
|
{
|
|
UniValue result, error;
|
|
bool retVal = false;
|
|
|
|
UniValue params(UniValue::VARR);
|
|
params.push_back(EncodeDestination(CIdentityID(currencyID)));
|
|
|
|
// VRSC and VRSCTEST do not start with a notary chain
|
|
if (!IsVerusActive() && ConnectedChains.IsNotaryAvailable())
|
|
{
|
|
// we are starting a PBaaS chain. We only assume that our chain definition and the first notary chain, if there is one, are setup
|
|
// in ConnectedChains. All other currencies and identities necessary to start have not been populated and must be in block 1 by
|
|
// getting the information from the notary chain.
|
|
if (CallNotary(notarySystem, "getlaunchinfo", params, result, error))
|
|
{
|
|
CCurrencyDefinition currency(find_value(result, "currencydefinition"));
|
|
CPBaaSNotarization notarization(find_value(result, "launchnotarization"));
|
|
notaryNotarization = CPBaaSNotarization(find_value(result, "notarynotarization"));
|
|
CPartialTransactionProof notarizationProof(find_value(result, "notarizationproof"));
|
|
CUTXORef notarizationUtxo(uint256S(uni_get_str(find_value(result, "notarizationtxid"))), uni_get_int(find_value(result, "notarizationvoutnum")));
|
|
|
|
CPartialTransactionProof exportProof(find_value(result, "exportproof"));
|
|
UniValue exportTransfersUni = find_value(result, "exportransfers");
|
|
|
|
bool reject = false;
|
|
if (exportTransfersUni.isArray() && exportTransfersUni.size())
|
|
{
|
|
for (int i = 0; i < exportTransfersUni.size(); i++)
|
|
{
|
|
CReserveTransfer oneTransfer(exportTransfersUni[i]);
|
|
if (!oneTransfer.IsValid())
|
|
{
|
|
reject = true;
|
|
}
|
|
else
|
|
{
|
|
exportTransfers.push_back(oneTransfer);
|
|
}
|
|
}
|
|
}
|
|
CUTXORef exportUtxo(uint256S(uni_get_str(find_value(result, "exporttxid"))), uni_get_int(find_value(result, "exportvoutnum")));
|
|
|
|
if (reject ||
|
|
!currency.IsValid() ||
|
|
!notarization.IsValid() ||
|
|
!notarizationProof.IsValid() ||
|
|
!notaryNotarization.IsValid())
|
|
{
|
|
LogPrintf("%s: invalid launch notarization for currency %s\n", __func__, EncodeDestination(CIdentityID(currencyID)).c_str());
|
|
printf("%s: invalid launch notarization for currency %s\ncurrencydefinition: %s\nnotarization: %s\ntransactionproof: %s\n",
|
|
__func__,
|
|
EncodeDestination(CIdentityID(currencyID)).c_str(),
|
|
currency.ToUniValue().write(1,2).c_str(),
|
|
notarization.ToUniValue().write(1,2).c_str(),
|
|
notarizationProof.ToUniValue().write(1,2).c_str());
|
|
}
|
|
else
|
|
{
|
|
//printf("%s: proofroot: %s\n", __func__, latestProofRoot.ToUniValue().write(1,2).c_str());
|
|
curDef = currency;
|
|
launchNotarization = notarization;
|
|
launchNotarization.proofRoots = notaryNotarization.proofRoots;
|
|
notaryNotarization.proofRoots[ASSETCHAINS_CHAINID] = CProofRoot::GetProofRoot(0);
|
|
notaryNotarization.currencyStates[ASSETCHAINS_CHAINID] = launchNotarization.currencyState;
|
|
notarizationOutputProof = std::make_pair(notarizationUtxo, notarizationProof);
|
|
exportOutputProof = std::make_pair(exportUtxo, exportProof);
|
|
retVal = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LogPrintf("%s: error calling notary chain %s\n", __func__, error.write(1,2).c_str());
|
|
printf("%s: error calling notary chain %s\n", __func__, error.write(1,2).c_str());
|
|
}
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
bool DecodeOneExport(const UniValue obj, CCrossChainExport &ccx,
|
|
std::pair<std::pair<CInputDescriptor, CPartialTransactionProof>, std::vector<CReserveTransfer>> &oneExport)
|
|
{
|
|
uint32_t exportHeight = uni_get_int64(find_value(obj, "height"));
|
|
uint256 txId = uint256S(uni_get_str(find_value(obj, "txid")));
|
|
uint32_t outNum = uni_get_int(find_value(obj, "txoutnum"));
|
|
ccx = CCrossChainExport(find_value(obj, "exportinfo"));
|
|
if (!ccx.IsValid())
|
|
{
|
|
LogPrintf("%s: invalid launch export from notary chain\n", __func__);
|
|
printf("%s: invalid launch export from notary chain\n", __func__);
|
|
return false;
|
|
}
|
|
CPartialTransactionProof partialTxProof(find_value(obj, "partialtransactionproof"));
|
|
CTransaction exportTx;
|
|
COptCCParams p;
|
|
CScript outputScript;
|
|
CAmount outputValue;
|
|
|
|
if (!partialTxProof.IsValid() ||
|
|
partialTxProof.GetPartialTransaction(exportTx).IsNull() ||
|
|
partialTxProof.TransactionHash() != txId ||
|
|
exportTx.vout.size() <= outNum ||
|
|
!exportTx.vout[outNum].scriptPubKey.IsPayToCryptoCondition(p) ||
|
|
!p.IsValid() ||
|
|
!p.evalCode == EVAL_CROSSCHAIN_EXPORT ||
|
|
(outputValue = exportTx.vout[outNum].nValue) == -1)
|
|
{
|
|
//UniValue jsonTxOut(UniValue::VOBJ);
|
|
//TxToUniv(exportTx, uint256(), jsonTxOut);
|
|
//printf("%s: proofTxRoot:%s\npartialTx: %s\n", __func__,
|
|
// partialTxProof.GetPartialTransaction(exportTx).GetHex().c_str(),
|
|
// jsonTxOut.write(1,2).c_str());
|
|
LogPrintf("%s: invalid partial transaction proof from notary chain\n", __func__);
|
|
printf("%s: invalid partial transaction proof from notary chain\n", __func__);
|
|
return false;
|
|
}
|
|
|
|
UniValue transfers = find_value(obj, "transfers");
|
|
std::vector<CReserveTransfer> reserveTransfers;
|
|
if (transfers.isArray() && transfers.size())
|
|
{
|
|
for (int i = 0; i < transfers.size(); i++)
|
|
{
|
|
CReserveTransfer rt(transfers[i]);
|
|
if (rt.IsValid())
|
|
{
|
|
reserveTransfers.push_back(rt);
|
|
}
|
|
}
|
|
}
|
|
|
|
oneExport.first.first = CInputDescriptor(outputScript, outputValue, CTxIn(txId, outNum));
|
|
oneExport.first.second = partialTxProof;
|
|
oneExport.second = reserveTransfers;
|
|
return true;
|
|
}
|
|
|
|
// get initial currency state from the notary system specified
|
|
bool GetBlockOneImports(const CRPCChainData ¬arySystem, const CPBaaSNotarization &launchNotarization, std::map<uint160, std::vector<std::pair<std::pair<CInputDescriptor, CPartialTransactionProof>, std::vector<CReserveTransfer>>>> &exports)
|
|
{
|
|
UniValue result, error;
|
|
|
|
UniValue params(UniValue::VARR);
|
|
params.push_back(EncodeDestination(CIdentityID(ASSETCHAINS_CHAINID)));
|
|
params.push_back((int)0);
|
|
if (launchNotarization.proofRoots.count(ConnectedChains.ThisChain().launchSystemID))
|
|
{
|
|
params.push_back((int64_t)launchNotarization.proofRoots.find(ConnectedChains.ThisChain().launchSystemID)->second.rootHeight);
|
|
}
|
|
|
|
// VRSC and VRSCTEST do not start with a notary chain
|
|
if (!IsVerusActive() && ConnectedChains.IsNotaryAvailable())
|
|
{
|
|
// we are starting a PBaaS chain. We only assume that our chain definition and the first notary chain, if there is one, are setup
|
|
// in ConnectedChains. All other currencies and identities necessary to start have not been populated and must be in block 1 by
|
|
// getting the information from the notary chain.
|
|
if (CallNotary(notarySystem, "getexports", params, result, error) &&
|
|
result.isArray() &&
|
|
result.size())
|
|
{
|
|
// we now have an array of exports that we should import into this system
|
|
// load up to the last launch export on each currency
|
|
for (int i = 0; i < result.size(); i++)
|
|
{
|
|
CCrossChainExport ccx;
|
|
std::pair<std::pair<CInputDescriptor, CPartialTransactionProof>, std::vector<CReserveTransfer>> oneExport;
|
|
if (DecodeOneExport(result[i], ccx, oneExport))
|
|
{
|
|
exports[ccx.destCurrencyID].push_back(oneExport);
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// This is called with either the initial currency, or the gateway converter currency
|
|
// to setup an import/export thread, transfer the initial issuance of native currency
|
|
// into the converter, and notarize the state of each currency.
|
|
// All outputs to do those things are added to the outputs vector.
|
|
bool AddOneCurrencyImport(const CCurrencyDefinition &newCurrency,
|
|
const CPBaaSNotarization &lastNotarization,
|
|
const std::pair<CUTXORef, CPartialTransactionProof> *pLaunchProof,
|
|
const std::pair<CUTXORef, CPartialTransactionProof> *pFirstExport,
|
|
const std::vector<CReserveTransfer> &_exportTransfers,
|
|
CCurrencyValueMap &gatewayDeposits,
|
|
std::vector<CTxOut> &outputs,
|
|
CCurrencyValueMap &additionalFees)
|
|
{
|
|
uint160 newCurID = newCurrency.GetID();
|
|
CPBaaSNotarization newNotarization = lastNotarization;
|
|
newNotarization.prevNotarization = CUTXORef();
|
|
newNotarization.SetBlockOneNotarization();
|
|
|
|
// each currency will get:
|
|
// * one currency definition output
|
|
// * notarization of latest currency state
|
|
|
|
CCcontract_info CC;
|
|
CCcontract_info *cp;
|
|
|
|
// make a currency definition
|
|
cp = CCinit(&CC, EVAL_CURRENCY_DEFINITION);
|
|
std::vector<CTxDestination> dests({CPubKey(ParseHex(CC.CChexstr))});
|
|
outputs.push_back(CTxOut(0, MakeMofNCCScript(CConditionObj<CCurrencyDefinition>(EVAL_CURRENCY_DEFINITION, dests, 1, &newCurrency))));
|
|
|
|
ConnectedChains.UpdateCachedCurrency(newCurrency, 1);
|
|
|
|
// import / export capable currencies include the main currency, fractional currencies on any system,
|
|
// gateway currencies. the launch system, and non-token currencies. they also get an import / export thread
|
|
if (ConnectedChains.ThisChain().launchSystemID == newCurID ||
|
|
(newCurrency.systemID == ASSETCHAINS_CHAINID &&
|
|
(newCurrency.IsFractional() ||
|
|
newCurrency.systemID == newCurID ||
|
|
(newCurrency.IsGateway() && newCurrency.GetID() == newCurrency.gatewayID))))
|
|
{
|
|
uint160 firstNotaryID = ConnectedChains.FirstNotaryChain().chainDefinition.GetID();
|
|
|
|
// first, put evidence of the notarization pre-import
|
|
int notarizationIdx = -1;
|
|
if (pLaunchProof)
|
|
{
|
|
// add notarization before other outputs
|
|
cp = CCinit(&CC, EVAL_NOTARY_EVIDENCE);
|
|
dests = std::vector<CTxDestination>({CPubKey(ParseHex(CC.CChexstr))});
|
|
// now, we need to put the launch notarization evidence, followed by the import outputs
|
|
CCrossChainProof evidenceProof;
|
|
evidenceProof << pLaunchProof->second;
|
|
CNotaryEvidence evidence = CNotaryEvidence(ConnectedChains.FirstNotaryChain().chainDefinition.GetID(),
|
|
pLaunchProof->first,
|
|
true,
|
|
evidenceProof,
|
|
CNotaryEvidence::TYPE_IMPORT_PROOF);
|
|
notarizationIdx = outputs.size();
|
|
outputs.push_back(CTxOut(0, MakeMofNCCScript(CConditionObj<CNotaryEvidence>(EVAL_NOTARY_EVIDENCE, dests, 1, &evidence))));
|
|
}
|
|
|
|
// create the import thread output
|
|
cp = CCinit(&CC, EVAL_CROSSCHAIN_IMPORT);
|
|
if (newCurrency.proofProtocol == newCurrency.PROOF_CHAINID)
|
|
{
|
|
dests = std::vector<CTxDestination>({CIdentityID(newCurID)});
|
|
}
|
|
else
|
|
{
|
|
dests = std::vector<CTxDestination>({CPubKey(ParseHex(CC.CChexstr))});
|
|
}
|
|
|
|
if ((newCurrency.systemID == ASSETCHAINS_CHAINID) && firstNotaryID == newCurrency.launchSystemID)
|
|
{
|
|
uint256 transferHash;
|
|
std::vector<CTxOut> importOutputs;
|
|
CCurrencyValueMap importedCurrency, gatewayDepositsUsed, spentCurrencyOut;
|
|
|
|
CPBaaSNotarization tempLastNotarization = lastNotarization;
|
|
tempLastNotarization.currencyState.SetLaunchCompleteMarker(false);
|
|
|
|
// get the first export for launch from the notary chain
|
|
CTransaction firstExportTx;
|
|
if (!pFirstExport || !(pFirstExport->second.IsValid() && !pFirstExport->second.GetPartialTransaction(firstExportTx).IsNull()))
|
|
{
|
|
LogPrintf("%s: invalid first export for PBaaS or converter launch\n");
|
|
return false;
|
|
}
|
|
|
|
// get the export for this import
|
|
CCrossChainExport ccx(firstExportTx.vout[pFirstExport->first.n].scriptPubKey);
|
|
if (!ccx.IsValid())
|
|
{
|
|
LogPrintf("%s: invalid export output for PBaaS or converter launch\n");
|
|
return false;
|
|
}
|
|
|
|
std::vector<CReserveTransfer> exportTransfers(_exportTransfers);
|
|
if (!tempLastNotarization.NextNotarizationInfo(ConnectedChains.FirstNotaryChain().chainDefinition,
|
|
newCurrency,
|
|
0,
|
|
1,
|
|
exportTransfers,
|
|
transferHash,
|
|
newNotarization,
|
|
importOutputs,
|
|
importedCurrency,
|
|
gatewayDepositsUsed,
|
|
spentCurrencyOut,
|
|
ccx.exporter))
|
|
{
|
|
LogPrintf("%s: invalid import for currency %s on system %s\n", __func__,
|
|
newCurrency.name.c_str(),
|
|
EncodeDestination(CIdentityID(ASSETCHAINS_CHAINID)).c_str());
|
|
return false;
|
|
}
|
|
|
|
// if fees are not converted, we will pay out original fees,
|
|
// less liquidity fees, which go into the currency reserves
|
|
bool feesConverted;
|
|
CCurrencyValueMap liquidityFees;
|
|
CCurrencyValueMap originalFees =
|
|
newNotarization.currencyState.CalculateConvertedFees(
|
|
newNotarization.currencyState.viaConversionPrice,
|
|
newNotarization.currencyState.viaConversionPrice,
|
|
ASSETCHAINS_CHAINID,
|
|
feesConverted,
|
|
liquidityFees,
|
|
additionalFees);
|
|
|
|
if (!feesConverted)
|
|
{
|
|
additionalFees += (originalFees - liquidityFees);
|
|
}
|
|
|
|
newNotarization.SetBlockOneNotarization();
|
|
|
|
// display import outputs
|
|
CMutableTransaction debugTxOut;
|
|
debugTxOut.vout = outputs;
|
|
debugTxOut.vout.insert(debugTxOut.vout.end(), importOutputs.begin(), importOutputs.end());
|
|
UniValue jsonTxOut(UniValue::VOBJ);
|
|
TxToUniv(debugTxOut, uint256(), jsonTxOut);
|
|
printf("%s: launch outputs: %s\nlast notarization: %s\nnew notarization: %s\n", __func__,
|
|
jsonTxOut.write(1,2).c_str(),
|
|
lastNotarization.ToUniValue().write(1,2).c_str(),
|
|
newNotarization.ToUniValue().write(1,2).c_str());
|
|
|
|
newNotarization.prevNotarization = CUTXORef();
|
|
newNotarization.prevHeight = 0;
|
|
|
|
// create an import based on launch conditions that covers all pre-allocations and uses the initial notarization.
|
|
// generate outputs, then fill in numOutputs
|
|
CCrossChainImport cci = CCrossChainImport(newCurrency.launchSystemID,
|
|
newNotarization.notarizationHeight,
|
|
newCurID,
|
|
ccx.totalAmounts,
|
|
CCurrencyValueMap(),
|
|
0,
|
|
ccx.hashReserveTransfers,
|
|
pFirstExport->first.hash,
|
|
pFirstExport->first.n);
|
|
cci.SetSameChain(newCurrency.launchSystemID == ASSETCHAINS_CHAINID);
|
|
cci.SetPostLaunch();
|
|
cci.SetInitialLaunchImport();
|
|
|
|
// anything we had before plus anything imported and minus all spent currency out should
|
|
// be all reserve deposits remaining under control of this currency
|
|
|
|
/* printf("%s: ccx.totalAmounts: %s\ngatewayDepositsUsed: %s\nadditionalFees: %s\noriginalFees: %s\n",
|
|
__func__,
|
|
ccx.totalAmounts.ToUniValue().write(1,2).c_str(),
|
|
gatewayDepositsUsed.ToUniValue().write(1,2).c_str(),
|
|
additionalFees.ToUniValue().write(1,2).c_str(),
|
|
originalFees.ToUniValue().write(1,2).c_str()); */
|
|
|
|
// to determine left over reserves for deposit, consider imported and emitted as the same
|
|
gatewayDeposits = CCurrencyValueMap(lastNotarization.currencyState.currencies, lastNotarization.currencyState.reserveIn);
|
|
if (!newCurrency.IsFractional())
|
|
{
|
|
gatewayDeposits += originalFees;
|
|
}
|
|
|
|
gatewayDeposits.valueMap[newCurID] += gatewayDepositsUsed.valueMap[newCurID] + newNotarization.currencyState.primaryCurrencyOut;
|
|
|
|
printf("importedcurrency %s\nspentcurrencyout %s\ngatewaydeposits %s\n",
|
|
importedCurrency.ToUniValue().write(1,2).c_str(),
|
|
spentCurrencyOut.ToUniValue().write(1,2).c_str(),
|
|
gatewayDeposits.ToUniValue().write(1,2).c_str());
|
|
|
|
gatewayDeposits = (gatewayDeposits - spentCurrencyOut).CanonicalMap();
|
|
|
|
printf("newNotarization.currencyState %s\nnewgatewaydeposits %s\n",
|
|
newNotarization.currencyState.ToUniValue().write(1,2).c_str(),
|
|
gatewayDeposits.ToUniValue().write(1,2).c_str());
|
|
|
|
// add the reserve deposit output with all deposits for this currency for the new chain
|
|
if (gatewayDeposits.valueMap.size())
|
|
{
|
|
CCcontract_info *depositCp;
|
|
CCcontract_info depositCC;
|
|
|
|
// create the import thread output
|
|
depositCp = CCinit(&depositCC, EVAL_RESERVE_DEPOSIT);
|
|
std::vector<CTxDestination> depositDests({CPubKey(ParseHex(depositCC.CChexstr))});
|
|
// put deposits under control of the launch system, where the imports using them will be coming from
|
|
CReserveDeposit rd(newCurrency.IsPBaaSChain() ? newCurrency.launchSystemID : newCurID, gatewayDeposits);
|
|
CAmount nativeOut = gatewayDeposits.valueMap.count(ASSETCHAINS_CHAINID) ? gatewayDeposits.valueMap[ASSETCHAINS_CHAINID] : 0;
|
|
outputs.push_back(CTxOut(nativeOut, MakeMofNCCScript(CConditionObj<CReserveDeposit>(EVAL_RESERVE_DEPOSIT, depositDests, 1, &rd))));
|
|
}
|
|
|
|
if (newCurrency.notaries.size())
|
|
{
|
|
// notaries all get an even share of 10% of the launch fee in the launch currency to use for notarizing
|
|
// they may also get pre-allocations
|
|
uint160 notaryNativeID = ConnectedChains.FirstNotaryChain().chainDefinition.GetID();
|
|
CAmount notaryFeeShare = ConnectedChains.FirstNotaryChain().chainDefinition.currencyRegistrationFee / 10;
|
|
additionalFees -= CCurrencyValueMap(std::vector<uint160>({notaryNativeID}), std::vector<int64_t>({notaryFeeShare}));
|
|
CAmount oneNotaryShare = notaryFeeShare / newCurrency.notaries.size();
|
|
CAmount notaryModExtra = notaryFeeShare % newCurrency.notaries.size();
|
|
for (auto &oneNotary : newCurrency.notaries)
|
|
{
|
|
CTokenOutput to(notaryNativeID, oneNotaryShare);
|
|
if (notaryModExtra)
|
|
{
|
|
to.reserveValues.valueMap[notaryNativeID]++;
|
|
notaryModExtra--;
|
|
}
|
|
outputs.push_back(CTxOut(0, MakeMofNCCScript(CConditionObj<CTokenOutput>(EVAL_RESERVE_OUTPUT,
|
|
std::vector<CTxDestination>({CIdentityID(oneNotary)}),
|
|
1,
|
|
&to))));
|
|
}
|
|
}
|
|
|
|
cci.numOutputs = importOutputs.size();
|
|
|
|
// now add the import itself
|
|
outputs.push_back(CTxOut(0, MakeMofNCCScript(CConditionObj<CCrossChainImport>(EVAL_CROSSCHAIN_IMPORT, dests, 1, &cci))));
|
|
|
|
// add notarization before other outputs
|
|
cp = CCinit(&CC, EVAL_EARNEDNOTARIZATION);
|
|
if (newCurID == ASSETCHAINS_CHAINID &&
|
|
newCurrency.notarizationProtocol == newCurrency.NOTARIZATION_NOTARY_CHAINID)
|
|
{
|
|
dests = std::vector<CTxDestination>({CIdentityID(newCurID)});
|
|
}
|
|
else
|
|
{
|
|
dests = std::vector<CTxDestination>({CPubKey(ParseHex(CC.CChexstr))});
|
|
}
|
|
outputs.push_back(CTxOut(0, MakeMofNCCScript(CConditionObj<CPBaaSNotarization>(EVAL_EARNEDNOTARIZATION, dests, 1, &newNotarization))));
|
|
|
|
// add export before other outputs
|
|
cp = CCinit(&CC, EVAL_NOTARY_EVIDENCE);
|
|
dests = std::vector<CTxDestination>({CPubKey(ParseHex(CC.CChexstr))});
|
|
|
|
// now, we need to put the export evidence, followed by the import outputs
|
|
CCrossChainProof evidenceProof;
|
|
evidenceProof << pFirstExport->second;
|
|
CNotaryEvidence evidence = CNotaryEvidence(cci.sourceSystemID,
|
|
CUTXORef(uint256(), notarizationIdx),
|
|
true,
|
|
evidenceProof,
|
|
CNotaryEvidence::TYPE_IMPORT_PROOF);
|
|
|
|
outputs.push_back(CTxOut(0, MakeMofNCCScript(CConditionObj<CNotaryEvidence>(EVAL_NOTARY_EVIDENCE, dests, 1, &evidence))));
|
|
outputs.insert(outputs.end(), importOutputs.begin(), importOutputs.end());
|
|
}
|
|
else
|
|
{
|
|
// begin with an empty import for this currency
|
|
// create an import based on launch conditions that covers all pre-allocations and uses the initial notarization
|
|
|
|
// if the currency is new and owned by this chain, its registration requires the fee, paid in its launch chain currency
|
|
// otherwise, it is being imported from another chain and requires an import fee
|
|
CCurrencyValueMap registrationFees;
|
|
CAmount registrationAmount = 0;
|
|
if (newCurrency.systemID == ASSETCHAINS_CHAINID)
|
|
{
|
|
if (newCurrency.launchSystemID != ASSETCHAINS_CHAINID)
|
|
{
|
|
registrationFees = CCurrencyValueMap(std::vector<uint160>({newCurrency.launchSystemID}),
|
|
std::vector<int64_t>({ConnectedChains.FirstNotaryChain().chainDefinition.currencyRegistrationFee}));
|
|
}
|
|
else
|
|
{
|
|
registrationAmount = ConnectedChains.ThisChain().currencyRegistrationFee;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
registrationAmount = 0;
|
|
}
|
|
|
|
CCrossChainImport cci = CCrossChainImport(ConnectedChains.ThisChain().launchSystemID,
|
|
1,
|
|
newCurID,
|
|
CCurrencyValueMap());
|
|
cci.SetDefinitionImport(true);
|
|
outputs.push_back(CTxOut(0, MakeMofNCCScript(CConditionObj<CCrossChainImport>(EVAL_CROSSCHAIN_IMPORT, dests, 1, &cci))));
|
|
|
|
// add notarization before other outputs
|
|
cp = CCinit(&CC, EVAL_EARNEDNOTARIZATION);
|
|
if (newCurID == ASSETCHAINS_CHAINID &&
|
|
newCurrency.notarizationProtocol == newCurrency.NOTARIZATION_NOTARY_CHAINID)
|
|
{
|
|
dests = std::vector<CTxDestination>({CIdentityID(newCurID)});
|
|
}
|
|
else
|
|
{
|
|
dests = std::vector<CTxDestination>({CPubKey(ParseHex(CC.CChexstr))});
|
|
}
|
|
// this currency is not launching now
|
|
newNotarization.SetLaunchConfirmed();
|
|
newNotarization.SetLaunchComplete();
|
|
outputs.push_back(CTxOut(0, MakeMofNCCScript(CConditionObj<CPBaaSNotarization>(EVAL_EARNEDNOTARIZATION, dests, 1, &newNotarization))));
|
|
|
|
CReserveTransactionDescriptor rtxd;
|
|
CCoinbaseCurrencyState importState = newNotarization.currencyState;
|
|
importState.RevertReservesAndSupply();
|
|
CCurrencyValueMap importedCurrency;
|
|
CCurrencyValueMap gatewayDepositsIn;
|
|
CCurrencyValueMap spentCurrencyOut;
|
|
CCoinbaseCurrencyState newCurrencyState;
|
|
if (!rtxd.AddReserveTransferImportOutputs(ConnectedChains.FirstNotaryChain().chainDefinition,
|
|
ConnectedChains.ThisChain(),
|
|
newCurrency,
|
|
importState,
|
|
std::vector<CReserveTransfer>(),
|
|
1,
|
|
outputs,
|
|
importedCurrency,
|
|
gatewayDepositsIn,
|
|
spentCurrencyOut,
|
|
&newCurrencyState))
|
|
{
|
|
LogPrintf("Invalid starting currency import for %s\n", ConnectedChains.ThisChain().name.c_str());
|
|
printf("Invalid starting currency import for %s\n", ConnectedChains.ThisChain().name.c_str());
|
|
return false;
|
|
}
|
|
|
|
// if fees are not converted, we will pay out original fees,
|
|
// less liquidity fees, which go into the currency reserves
|
|
bool feesConverted;
|
|
CCurrencyValueMap liquidityFees;
|
|
CCurrencyValueMap originalFees =
|
|
newCurrencyState.CalculateConvertedFees(
|
|
newCurrencyState.viaConversionPrice,
|
|
newCurrencyState.viaConversionPrice,
|
|
ASSETCHAINS_CHAINID,
|
|
feesConverted,
|
|
liquidityFees,
|
|
additionalFees);
|
|
|
|
if (!feesConverted)
|
|
{
|
|
additionalFees += (originalFees - liquidityFees);
|
|
}
|
|
}
|
|
|
|
// export thread
|
|
cp = CCinit(&CC, EVAL_CROSSCHAIN_EXPORT);
|
|
dests = std::vector<CTxDestination>({CPubKey(ParseHex(CC.CChexstr))});
|
|
CCrossChainExport ccx;
|
|
|
|
ccx = CCrossChainExport(ASSETCHAINS_CHAINID, 1, 1, newCurrency.systemID, newCurID, 0, CCurrencyValueMap(), CCurrencyValueMap(), uint256());
|
|
outputs.push_back(CTxOut(0, MakeMofNCCScript(CConditionObj<CCrossChainExport>(EVAL_CROSSCHAIN_EXPORT, dests, 1, &ccx))));
|
|
}
|
|
else
|
|
{
|
|
cp = CCinit(&CC, EVAL_EARNEDNOTARIZATION);
|
|
if (newCurID == ASSETCHAINS_CHAINID &&
|
|
newCurrency.notarizationProtocol == newCurrency.NOTARIZATION_NOTARY_CHAINID)
|
|
{
|
|
dests = std::vector<CTxDestination>({CIdentityID(newCurID)});
|
|
}
|
|
else
|
|
{
|
|
dests = std::vector<CTxDestination>({CPubKey(ParseHex(CC.CChexstr))});
|
|
}
|
|
// we notarize our notary chain here, not ourselves
|
|
if (newNotarization.currencyID == ASSETCHAINS_CHAINID)
|
|
{
|
|
if (!newNotarization.SetMirror())
|
|
{
|
|
LogPrintf("Cannot mirror our notarization from notary chain\n");
|
|
printf("Cannot mirror our notarization from notary chain\n");
|
|
return false;
|
|
}
|
|
}
|
|
newNotarization.SetBlockOneNotarization();
|
|
outputs.push_back(CTxOut(0, MakeMofNCCScript(CConditionObj<CPBaaSNotarization>(EVAL_EARNEDNOTARIZATION, dests, 1, &newNotarization))));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// create all special PBaaS outputs for block 1
|
|
bool MakeBlockOneCoinbaseOutputs(std::vector<CTxOut> &outputs,
|
|
CPBaaSNotarization &launchNotarization,
|
|
CCurrencyValueMap &additionalFees,
|
|
const Consensus::Params &consensusParams)
|
|
{
|
|
uint160 thisChainID = ConnectedChains.ThisChain().GetID();
|
|
CCurrencyDefinition &thisChain = ConnectedChains.ThisChain();
|
|
CCoinbaseCurrencyState currencyState;
|
|
std::map<uint160, std::vector<std::pair<std::pair<CInputDescriptor, CPartialTransactionProof>, std::vector<CReserveTransfer>>>> blockOneExportImports;
|
|
|
|
std::pair<CUTXORef, CPartialTransactionProof> launchNotarizationProof;
|
|
std::pair<CUTXORef, CPartialTransactionProof> launchExportProof;
|
|
std::vector<CReserveTransfer> launchExportTransfers;
|
|
CPBaaSNotarization notaryNotarization, notaryConverterNotarization;
|
|
|
|
if (!GetBlockOneLaunchNotarization(ConnectedChains.FirstNotaryChain(),
|
|
thisChainID,
|
|
thisChain,
|
|
launchNotarization,
|
|
notaryNotarization,
|
|
launchNotarizationProof,
|
|
launchExportProof,
|
|
launchExportTransfers))
|
|
{
|
|
// cannot make block 1 unless we can get the initial currency state from the first notary system
|
|
LogPrintf("Cannot find chain on notary system\n");
|
|
printf("Cannot find chain on notary system\n");
|
|
return false;
|
|
}
|
|
|
|
// we need to have a launch decision to be able to mine any blocks, prior to launch being clear,
|
|
// it is not an error. we are just not ready.
|
|
if (!launchNotarization.IsLaunchCleared())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!launchNotarization.IsLaunchConfirmed())
|
|
{
|
|
// we must reach minimums in all currencies to launch
|
|
LogPrintf("This chain did not receive the minimum currency contributions and cannot launch. Pre-launch contributions to this chain can be refunded.\n");
|
|
printf("This chain did not receive the minimum currency contributions and cannot launch. Pre-launch contributions to this chain can be refunded.\n");
|
|
return false;
|
|
}
|
|
|
|
// get initial imports
|
|
if (!GetBlockOneImports(ConnectedChains.FirstNotaryChain(), launchNotarization, blockOneExportImports))
|
|
{
|
|
// we must reach minimums in all currencies to launch
|
|
LogPrintf("Cannot retrieve initial export imports from notary system\n");
|
|
printf("Cannot retrieve initial export imports from notary system\n");
|
|
return false;
|
|
}
|
|
|
|
// get all currencies/IDs that we will need to retrieve from our notary chain
|
|
std::set<uint160> blockOneCurrencies;
|
|
std::set<uint160> blockOneIDs = {ASSETCHAINS_CHAINID};
|
|
std::set<uint160> convertersToCreate;
|
|
|
|
CPBaaSNotarization converterNotarization;
|
|
std::pair<CUTXORef, CPartialTransactionProof> converterNotarizationProof;
|
|
std::pair<CUTXORef, CPartialTransactionProof> converterExportProof;
|
|
std::vector<CReserveTransfer> converterExportTransfers;
|
|
uint160 converterCurrencyID = thisChain.GatewayConverterID();
|
|
CCurrencyDefinition converterCurDef;
|
|
|
|
// if we have a converter currency, ensure that it also meets requirements for currency launch
|
|
if (!thisChain.gatewayConverterName.empty())
|
|
{
|
|
if (!GetBlockOneLaunchNotarization(ConnectedChains.FirstNotaryChain(),
|
|
converterCurrencyID,
|
|
converterCurDef,
|
|
converterNotarization,
|
|
notaryConverterNotarization,
|
|
converterNotarizationProof,
|
|
converterExportProof,
|
|
converterExportTransfers))
|
|
{
|
|
LogPrintf("Unable to get gateway converter initial state\n");
|
|
printf("Unable to get gateway converter initial state\n");
|
|
return false;
|
|
}
|
|
|
|
notaryConverterNotarization.currencyStates[converterCurrencyID] = converterNotarization.currencyState;
|
|
|
|
// both currency and primary gateway must have their pre-launch phase complete before we can make a decision
|
|
// about launching
|
|
if (!converterNotarization.IsLaunchCleared())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// we need to have a cleared launch to be able to launch
|
|
if (!converterNotarization.IsLaunchConfirmed())
|
|
{
|
|
LogPrintf("Primary currency met requirements for launch, but gateway currency converter did not\n");
|
|
printf("Primary currency met requirements for launch, but gateway currency converter did not\n");
|
|
return false;
|
|
}
|
|
|
|
convertersToCreate.insert(converterCurrencyID);
|
|
|
|
for (auto &oneCurrency : converterCurDef.currencies)
|
|
{
|
|
blockOneCurrencies.insert(oneCurrency);
|
|
}
|
|
}
|
|
|
|
// Now, add block 1 imports, which provide a foundation of all IDs and currencies needed to launch the
|
|
// new system, including ID and currency outputs for notary chain, all currencies we accept for pre-conversion,
|
|
// native currency of system launching the chain.
|
|
for (auto &oneNotary : ConnectedChains.notarySystems)
|
|
{
|
|
// first, we need to have the native notary currency itself and its notaries, if it has them
|
|
blockOneCurrencies.insert(oneNotary.first);
|
|
blockOneIDs.insert(oneNotary.second.notaryChain.chainDefinition.notaries.begin(), oneNotary.second.notaryChain.chainDefinition.notaries.end());
|
|
}
|
|
|
|
for (auto &oneCurrency : thisChain.currencies)
|
|
{
|
|
blockOneCurrencies.insert(oneCurrency);
|
|
}
|
|
|
|
for (auto &onePrealloc : thisChain.preAllocation)
|
|
{
|
|
blockOneIDs.insert(onePrealloc.first);
|
|
}
|
|
|
|
// get this chain's notaries
|
|
auto ¬aryIDs = ConnectedChains.ThisChain().notaries;
|
|
blockOneIDs.insert(notaryIDs.begin(), notaryIDs.end());
|
|
|
|
// now retrieve IDs and currencies
|
|
std::map<uint160, std::pair<CCurrencyDefinition,CPBaaSNotarization>> currencyImports;
|
|
std::map<uint160, CIdentity> identityImports;
|
|
if (!ConnectedChains.GetNotaryCurrencies(ConnectedChains.FirstNotaryChain(), blockOneCurrencies, currencyImports) ||
|
|
!ConnectedChains.GetNotaryIDs(ConnectedChains.FirstNotaryChain(), blockOneIDs, identityImports))
|
|
{
|
|
// we must reach minimums in all currencies to launch
|
|
LogPrintf("Cannot retrieve identity and currency definitions needed to create block 1\n");
|
|
printf("Cannot retrieve identity and currency definitions needed to create block 1\n");
|
|
return false;
|
|
}
|
|
|
|
if (currencyImports.count(notaryNotarization.currencyID))
|
|
{
|
|
currencyImports[notaryNotarization.currencyID].second = notaryNotarization;
|
|
}
|
|
|
|
// add all imported currency and identity outputs, identity revocaton and recovery IDs must be explicitly imported if needed
|
|
for (auto &oneIdentity : identityImports)
|
|
{
|
|
outputs.push_back(CTxOut(0, oneIdentity.second.IdentityUpdateOutputScript(1)));
|
|
}
|
|
|
|
// calculate all issued currency on this chain for both the native and converter currencies,
|
|
// which is the only currency that can be considered a gateway deposit at launch. this can
|
|
// be used for native currency fee conversions
|
|
CCurrencyValueMap gatewayDeposits;
|
|
launchNotarization.proofRoots[ASSETCHAINS_CHAINID] = notaryNotarization.proofRoots[ASSETCHAINS_CHAINID];
|
|
bool success = AddOneCurrencyImport(thisChain,
|
|
launchNotarization,
|
|
&launchNotarizationProof,
|
|
&launchExportProof,
|
|
launchExportTransfers,
|
|
gatewayDeposits,
|
|
outputs,
|
|
additionalFees);
|
|
|
|
// now, the converter
|
|
if (success && converterCurDef.IsValid())
|
|
{
|
|
// TODO: add a new ID for the converter currency, controlled by the same primary addresses as the
|
|
// ID for this chain
|
|
CCurrencyValueMap converterDeposits;
|
|
converterNotarization.proofRoots[ASSETCHAINS_CHAINID] = notaryConverterNotarization.proofRoots[ASSETCHAINS_CHAINID];
|
|
success = AddOneCurrencyImport(converterCurDef,
|
|
converterNotarization,
|
|
&converterNotarizationProof,
|
|
&converterExportProof,
|
|
converterExportTransfers,
|
|
converterDeposits,
|
|
outputs,
|
|
additionalFees);
|
|
}
|
|
|
|
if (success)
|
|
{
|
|
currencyImports.erase(ASSETCHAINS_CHAINID);
|
|
currencyImports.erase(converterCurrencyID);
|
|
// now, add the rest of necessary currencies
|
|
for (auto &oneCurrency : currencyImports)
|
|
{
|
|
success = AddOneCurrencyImport(oneCurrency.second.first,
|
|
oneCurrency.second.second,
|
|
nullptr,
|
|
nullptr,
|
|
std::vector<CReserveTransfer>(),
|
|
gatewayDeposits,
|
|
outputs,
|
|
additionalFees);
|
|
if (!success)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return success;
|
|
}
|
|
|
|
uint256 RandomizedNonce()
|
|
{
|
|
// Randomize nonce
|
|
arith_uint256 nonce = UintToArith256(GetRandHash());
|
|
|
|
// Clear the top 16 and bottom 16 or 24 bits (for local use as thread flags and counters)
|
|
nonce <<= ASSETCHAINS_NONCESHIFT[ASSETCHAINS_ALGO];
|
|
nonce >>= 16;
|
|
return ArithToUint256(nonce);
|
|
}
|
|
|
|
CBlockTemplate* CreateNewBlock(const CChainParams& chainparams, const std::vector<CTxOut> &minerOutputs, bool isStake)
|
|
{
|
|
// instead of one scriptPubKeyIn, we take a vector of them along with relative weight. each is assigned a percentage of the block subsidy and
|
|
// mining reward based on its weight relative to the total
|
|
if (!(minerOutputs.size() && ConnectedChains.SetLatestMiningOutputs(minerOutputs) || isStake))
|
|
{
|
|
fprintf(stderr,"%s: Must have valid miner outputs, including script with valid PK, PKH, or Verus ID destination.\n", __func__);
|
|
return NULL;
|
|
}
|
|
|
|
CTxDestination firstDestination;
|
|
|
|
if (minerOutputs.size())
|
|
{
|
|
int64_t shareCheck = 0;
|
|
CTxDestination checkDest;
|
|
for (auto &output : minerOutputs)
|
|
{
|
|
shareCheck += output.nValue;
|
|
if (shareCheck < 0 ||
|
|
shareCheck > INT_MAX ||
|
|
!ExtractDestination(output.scriptPubKey, checkDest) ||
|
|
(checkDest.which() == COptCCParams::ADDRTYPE_INVALID))
|
|
{
|
|
fprintf(stderr,"Invalid miner outputs share specifications\n");
|
|
return NULL;
|
|
}
|
|
}
|
|
ExtractDestination(minerOutputs[0].scriptPubKey, firstDestination);
|
|
}
|
|
|
|
uint32_t blocktime;
|
|
//fprintf(stderr,"create new block\n");
|
|
// Create new block
|
|
std::unique_ptr<CBlockTemplate> pblocktemplate(new CBlockTemplate());
|
|
if(!pblocktemplate.get())
|
|
{
|
|
fprintf(stderr,"pblocktemplate.get() failure\n");
|
|
return NULL;
|
|
}
|
|
CBlock *pblock = &pblocktemplate->block; // pointer for convenience
|
|
|
|
pblock->nSolution.resize(Eh200_9.SolutionWidth);
|
|
|
|
pblock->SetVersionByHeight(chainActive.LastTip()->GetHeight() + 1);
|
|
|
|
// -regtest only: allow overriding block.nVersion with
|
|
// -blockversion=N to test forking scenarios
|
|
if (chainparams.MineBlocksOnDemand())
|
|
pblock->nVersion = GetArg("-blockversion", pblock->nVersion);
|
|
|
|
// Add dummy coinbase tx placeholder as first transaction
|
|
pblock->vtx.push_back(CTransaction());
|
|
|
|
pblocktemplate->vTxFees.push_back(-1); // updated at end
|
|
pblocktemplate->vTxSigOps.push_back(-1); // updated at end
|
|
|
|
// Largest block you're willing to create:
|
|
unsigned int nBlockMaxSize = GetArg("-blockmaxsize", DEFAULT_BLOCK_MAX_SIZE);
|
|
|
|
// 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));
|
|
|
|
unsigned int nMaxIDSize = nBlockMaxSize / 2;
|
|
unsigned int nCurrentIDSize = 0;
|
|
|
|
// How much of the block should be dedicated to high-priority transactions,
|
|
// included regardless of the fees they pay
|
|
unsigned int nBlockPrioritySize = GetArg("-blockprioritysize", DEFAULT_BLOCK_PRIORITY_SIZE);
|
|
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", DEFAULT_BLOCK_MIN_SIZE);
|
|
nBlockMinSize = std::min(nBlockMaxSize, nBlockMinSize);
|
|
|
|
// Collect memory pool transactions into the block
|
|
CAmount nFees = 0;
|
|
CAmount takenFees = 0;
|
|
|
|
bool isVerusActive = IsVerusActive();
|
|
CCurrencyDefinition &thisChain = ConnectedChains.ThisChain();
|
|
|
|
std::vector<CAmount> exchangeRate(thisChain.currencies.size());
|
|
|
|
// we will attempt to spend any cheats we see
|
|
CTransaction cheatTx;
|
|
boost::optional<CTransaction> cheatSpend;
|
|
uint256 cbHash;
|
|
|
|
CBlockIndex* pindexPrev = 0;
|
|
bool loop = true;
|
|
while (loop)
|
|
{
|
|
loop = false;
|
|
int nHeight;
|
|
const Consensus::Params &consensusParams = chainparams.GetConsensus();
|
|
uint32_t consensusBranchId;
|
|
int64_t nMedianTimePast = 0;
|
|
uint32_t proposedTime = 0;
|
|
|
|
{
|
|
while (proposedTime == nMedianTimePast)
|
|
{
|
|
if (proposedTime)
|
|
{
|
|
MilliSleep(20);
|
|
}
|
|
LOCK(cs_main);
|
|
pindexPrev = chainActive.LastTip();
|
|
nHeight = pindexPrev->GetHeight() + 1;
|
|
consensusBranchId = CurrentEpochBranchId(nHeight, consensusParams);
|
|
nMedianTimePast = pindexPrev->GetMedianTimePast();
|
|
proposedTime = GetAdjustedTime();
|
|
|
|
if (proposedTime == nMedianTimePast)
|
|
{
|
|
boost::this_thread::interruption_point();
|
|
}
|
|
}
|
|
}
|
|
|
|
CCoinbaseCurrencyState currencyState;
|
|
CCoinsViewCache view(pcoinsTip);
|
|
uint32_t expired; uint64_t commission;
|
|
|
|
SaplingMerkleTree sapling_tree;
|
|
|
|
{
|
|
LOCK2(cs_main, mempool.cs);
|
|
if (pindexPrev != chainActive.LastTip())
|
|
{
|
|
// try again
|
|
loop = true;
|
|
continue;
|
|
}
|
|
pblock->nTime = GetAdjustedTime();
|
|
|
|
currencyState = ConnectedChains.GetCurrencyState(nHeight);
|
|
|
|
if (!(view.GetSaplingAnchorAt(view.GetBestAnchor(SAPLING), sapling_tree)))
|
|
{
|
|
LogPrintf("%s: failed to get Sapling anchor\n", __func__);
|
|
assert(false);
|
|
}
|
|
}
|
|
|
|
// Priority order to process transactions
|
|
list<COrphan> vOrphan; // list memory doesn't move
|
|
map<uint256, vector<COrphan*> > mapDependers;
|
|
bool fPrintPriority = GetBoolArg("-printpriority", false);
|
|
|
|
// This vector will be sorted into a priority queue:
|
|
vector<TxPriority> vecPriority;
|
|
vecPriority.reserve(mempool.mapTx.size() + 1);
|
|
|
|
{
|
|
LOCK(cs_main);
|
|
|
|
// check if we should add cheat transaction
|
|
CBlockIndex *ppast;
|
|
CTransaction cb;
|
|
int cheatHeight = nHeight - COINBASE_MATURITY < 1 ? 1 : nHeight - COINBASE_MATURITY;
|
|
if (defaultSaplingDest &&
|
|
chainActive.Height() > 100 &&
|
|
(ppast = chainActive[cheatHeight]) &&
|
|
ppast->IsVerusPOSBlock() &&
|
|
cheatList.IsHeightOrGreaterInList(cheatHeight))
|
|
{
|
|
// get the block and see if there is a cheat candidate for the stake tx
|
|
CBlock b;
|
|
if (!(fHavePruned && !(ppast->nStatus & BLOCK_HAVE_DATA) && ppast->nTx > 0) && ReadBlockFromDisk(b, ppast, chainparams.GetConsensus(), 1))
|
|
{
|
|
CTransaction &stakeTx = b.vtx[b.vtx.size() - 1];
|
|
|
|
if (cheatList.IsCheatInList(stakeTx, &cheatTx))
|
|
{
|
|
// make and sign the cheat transaction to spend the coinbase to our address
|
|
CMutableTransaction mtx = CreateNewContextualCMutableTransaction(consensusParams, nHeight);
|
|
|
|
uint32_t voutNum;
|
|
// get the first vout with value
|
|
for (voutNum = 0; voutNum < b.vtx[0].vout.size(); voutNum++)
|
|
{
|
|
if (b.vtx[0].vout[voutNum].nValue > 0)
|
|
break;
|
|
}
|
|
|
|
// send to the same pub key as the destination of this block reward
|
|
if (MakeCheatEvidence(mtx, b.vtx[0], voutNum, cheatTx))
|
|
{
|
|
LOCK(pwalletMain->cs_wallet);
|
|
TransactionBuilder tb = TransactionBuilder(consensusParams, nHeight);
|
|
cb = b.vtx[0];
|
|
cbHash = cb.GetHash();
|
|
|
|
bool hasInput = false;
|
|
for (uint32_t i = 0; i < cb.vout.size(); i++)
|
|
{
|
|
// add the spends with the cheat
|
|
if (cb.vout[i].nValue > 0)
|
|
{
|
|
tb.AddTransparentInput(COutPoint(cbHash,i), cb.vout[0].scriptPubKey, cb.vout[0].nValue);
|
|
hasInput = true;
|
|
}
|
|
}
|
|
|
|
if (hasInput)
|
|
{
|
|
// this is a send from a t-address to a sapling address, which we don't have an ovk for.
|
|
// Instead, generate a common one from the HD seed. This ensures the data is
|
|
// recoverable, at least for us, while keeping it logically separate from the ZIP 32
|
|
// Sapling key hierarchy, which the user might not be using.
|
|
uint256 ovk;
|
|
HDSeed seed;
|
|
if (pwalletMain->GetHDSeed(seed)) {
|
|
ovk = ovkForShieldingFromTaddr(seed);
|
|
|
|
// send everything to Sapling address
|
|
tb.SendChangeTo(defaultSaplingDest.value(), ovk);
|
|
|
|
tb.AddOpRet(mtx.vout[mtx.vout.size() - 1].scriptPubKey);
|
|
|
|
TransactionBuilderResult buildResult(tb.Build());
|
|
if (!buildResult.IsError() && buildResult.IsTx())
|
|
{
|
|
cheatSpend = buildResult.GetTxOrThrow();
|
|
}
|
|
else
|
|
{
|
|
LogPrintf("Error building cheat catcher transaction: %s\n", buildResult.GetError().c_str());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cheatSpend)
|
|
{
|
|
LOCK2(smartTransactionCS, mempool.cs);
|
|
|
|
cheatTx = cheatSpend.value();
|
|
std::list<CTransaction> removed;
|
|
mempool.removeConflicts(cheatTx, removed);
|
|
printf("Found cheating stake! Adding cheat spend for %.8f at block #%d, coinbase tx\n%s\n",
|
|
(double)cb.GetValueOut() / (double)COIN, nHeight, cheatSpend.value().vin[0].prevout.hash.GetHex().c_str());
|
|
|
|
// add to mem pool and relay
|
|
if (myAddtomempool(cheatTx))
|
|
{
|
|
RelayTransaction(cheatTx);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now start solving the block
|
|
//
|
|
|
|
uint64_t nBlockSize = 1000; // initial size
|
|
uint64_t nBlockTx = 1; // number of transactions - always have a coinbase
|
|
uint32_t autoTxSize = 0; // extra transaction overhead that we will add while creating the block
|
|
int nBlockSigOps = 100;
|
|
|
|
// VerusPoP staking transaction data
|
|
CMutableTransaction txStaked; // if this is a stake operation, the staking transaction that goes at the end
|
|
uint32_t nStakeTxSize = 0; // serialized size of the stake transaction
|
|
|
|
// if this is not for mining, first determine if we have a right to make a block
|
|
if (isStake)
|
|
{
|
|
uint64_t txfees, utxovalue;
|
|
uint32_t txtime;
|
|
uint256 utxotxid;
|
|
int32_t i, siglen, numsigs, utxovout;
|
|
std::vector<unsigned char> utxosig;
|
|
|
|
txStaked = CreateNewContextualCMutableTransaction(Params().GetConsensus(), nHeight);
|
|
|
|
uint32_t nBitsPOS;
|
|
arith_uint256 posHash;
|
|
|
|
siglen = verus_staked(pblock, txStaked, nBitsPOS, posHash, utxosig, firstDestination);
|
|
blocktime = GetAdjustedTime();
|
|
|
|
if (siglen <= 0)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
pblock->nTime = blocktime;
|
|
nStakeTxSize = GetSerializeSize(txStaked, SER_NETWORK, PROTOCOL_VERSION);
|
|
nBlockSize += nStakeTxSize;
|
|
}
|
|
|
|
ConnectedChains.AggregateChainTransfers(DestinationToTransferDestination(firstDestination), nHeight);
|
|
|
|
// Now the coinbase -
|
|
// A PBaaS coinbase must have some additional outputs to enable certain chain state and functions to be properly
|
|
// validated. All but currency state and the first chain definition are either optional or not valid on non-fractional reserve PBaaS blockchains
|
|
// All of these are instant spend outputs that have no maturity wait time and may be spent in the same block.
|
|
//
|
|
// 1. (required) currency state - current state of currency supply and optionally reserve, premine, etc. This is primarily a data output to provide
|
|
// cross check for coin minting and burning operations, making it efficient to determine up-to-date supply, reserves, and conversions. To provide
|
|
// an extra level of supply cross-checking and fast data retrieval, this is part of all PBaaS chains' protocol, not just reserves.
|
|
// This output also includes reserve and native amounts for total conversions, less fees, of any conversions between Verus reserve and the
|
|
// native currency.
|
|
//
|
|
// 2. (block 1 required) chain definition - in order to confirm the amount of coins converted and issued within the possible range, before chain start,
|
|
// new PBaaS chains have a zero-amount, unspendable chain definition output.
|
|
//
|
|
// 3. (block 1 optional) initial import utxo - for any chain with conversion or pre-conversion, the first coinbase must include an initial import utxo.
|
|
// Pre-conversions are handled on the launch chain before the PBaaS chain starts, so they are an additional output, which begins
|
|
// as a fixed amount and is spent with as many outputs as necessary to the recipients of the pre-conversion transactions when those pre-conversions
|
|
// are imported. All pre-converted outputs get their source currency from a thread that starts with this output in block 1.
|
|
//
|
|
// 4. (block 1 optional) initial export utxo - reserve chains, or any chain that will use exports to another chain must have an initial export utxo, any chain
|
|
// may have one, but currently, they can only be spent with valid exports, which only occur on reserve chains
|
|
//
|
|
// 5. (optional) notarization output - in order to ensure that notarization can occur independent of the availability of fungible
|
|
// coins on the network, and also that the notarization can provide a spendable finalization output and possible reward
|
|
//
|
|
// In addition, each PBaaS block can be mined with optional, fee-generating transactions. Inporting transactions from the reserve chain or sending
|
|
// exported transactions to the reserve chain are optional fee-generating steps that would be easy to do when running multiple daemons.
|
|
// The types of transactions miners/stakers may facilitate or create for fees are as follows:
|
|
//
|
|
// 1. Earned notarization of Verus chain - spends the notarization instant out. must be present and spend the notarization output if there is a notarization output
|
|
//
|
|
// 2. Imported transactions from the export thread for this PBaaS chain on the Verus blockchain - imported transactions must spend the import utxo
|
|
// thread, represent the export from the alternate chain which spends the export output from the prior import transaction, carry a notary proof, and
|
|
// include outputs that map to each of its inputs on the source chain. Outputs can include unconverted reserve outputs only on fractional
|
|
// reserve chains, pre-converted outputs for any chain with launch conversion, and post launch outputs to be converted on fractional reserve
|
|
// chains. Each are handled in the following way:
|
|
// a. Unconverted outputs are left as outputs to the intended destination of Verus reserve token and do not pass through the coinbase
|
|
// b. Pre-converted outputs require that the import transaction spend the last pre-conversion output starting at block 1 as the source for
|
|
// pre-converted currency.
|
|
//
|
|
// 3. Zero or more aggregated exports that combine individual cross-chain transactions and reserve transfer outputs for export to the Verus chain.
|
|
//
|
|
// 4. Conversion distribution transactions for all native and reserve currency conversions, including reserve transfer outputs without conversion as
|
|
// a second step for reserve transfers that have conversion included. Any remaining pre-converted reserve must always remain in a change output
|
|
// until it is exhausted
|
|
CTxOut premineOut;
|
|
|
|
// size of conversion tx
|
|
std::vector<CInputDescriptor> conversionInputs;
|
|
|
|
// if we are a PBaaS chain, first make sure we don't start prematurely, and if
|
|
// we should make an earned notarization, make it and set index to non-zero value
|
|
int32_t notarizationTxIndex = 0; // index of notarization if it is added
|
|
int32_t conversionTxIndex = 0; // index of conversion transaction if it is added
|
|
|
|
// export transactions can be created here by aggregating all pending transfer requests and either getting 10 or more together, or
|
|
// waiting n (10) blocks since the last one. each export must spend the output of the one before it
|
|
std::vector<CMutableTransaction> exportTransactions;
|
|
|
|
// all transaction outputs requesting conversion to another currency (PBaaS fractional reserve only)
|
|
// these will be used to calculate conversion price, fees, and generate coinbase conversion output as well as the
|
|
// conversion output transaction
|
|
std::vector<CTxOut> reserveConversionTo;
|
|
std::vector<CTxOut> reserveConversionFrom;
|
|
|
|
int64_t pbaasTransparentIn = 0;
|
|
int64_t pbaasTransparentOut = 0;
|
|
//extern int64_t ASSETCHAINS_SUPPLY;
|
|
//printf("%lu premine\n", ASSETCHAINS_SUPPLY);
|
|
int64_t blockSubsidy = GetBlockSubsidy(nHeight, consensusParams);
|
|
|
|
uint160 thisChainID = ConnectedChains.ThisChain().GetID();
|
|
|
|
uint256 mmrRoot;
|
|
vector<CInputDescriptor> notarizationInputs;
|
|
|
|
// used as scratch for making CCs, should be reinitialized each time
|
|
CCcontract_info CC;
|
|
CCcontract_info *cp;
|
|
std::vector<CTxDestination> dests;
|
|
CPubKey pkCC;
|
|
|
|
// Create coinbase tx and set up the null input with height
|
|
CMutableTransaction coinbaseTx = CreateNewContextualCMutableTransaction(consensusParams, nHeight);
|
|
coinbaseTx.vin.push_back(CTxIn(uint256(), (uint32_t)-1, CScript() << nHeight << OP_0));
|
|
|
|
// we will update amounts and fees later, but convert the guarded output now for validity checking and size estimate
|
|
if (isStake)
|
|
{
|
|
// if there is a specific destination, use it
|
|
CTransaction stakeTx(txStaked);
|
|
CStakeParams p;
|
|
if (ValidateStakeTransaction(stakeTx, p, false))
|
|
{
|
|
if (p.Version() < p.VERSION_EXTENDED_STAKE && !p.pk.IsValid())
|
|
{
|
|
LogPrintf("CreateNewBlock: invalid public key\n");
|
|
fprintf(stderr,"CreateNewBlock: invalid public key\n");
|
|
return NULL;
|
|
}
|
|
CTxDestination guardedOutputDest = (p.Version() < p.VERSION_EXTENDED_STAKE) ? p.pk : p.delegate;
|
|
coinbaseTx.vout.push_back(CTxOut(1, CScript()));
|
|
if (!MakeGuardedOutput(1, guardedOutputDest, stakeTx, coinbaseTx.vout.back()))
|
|
{
|
|
LogPrintf("CreateNewBlock: failed to make GuardedOutput on staking coinbase\n");
|
|
fprintf(stderr,"CreateNewBlock: failed to make GuardedOutput on staking coinbase\n");
|
|
return NULL;
|
|
}
|
|
COptCCParams optP;
|
|
if (!coinbaseTx.vout.back().scriptPubKey.IsPayToCryptoCondition(optP) || !optP.IsValid())
|
|
{
|
|
MakeGuardedOutput(1, guardedOutputDest, stakeTx, coinbaseTx.vout.back());
|
|
LogPrintf("%s: created invalid staking coinbase\n", __func__);
|
|
fprintf(stderr,"%s: created invalid staking coinbase\n", __func__);
|
|
return NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LogPrintf("CreateNewBlock: invalid stake transaction\n");
|
|
fprintf(stderr,"CreateNewBlock: invalid stake transaction\n");
|
|
return NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// default outputs for mining and before stake guard or fee calculation
|
|
// store the relative weight in the amount output to convert later to a relative portion
|
|
// of the reward + fees
|
|
coinbaseTx.vout.insert(coinbaseTx.vout.end(), minerOutputs.begin(), minerOutputs.end());
|
|
}
|
|
|
|
CAmount totalEmission = blockSubsidy;
|
|
CCurrencyValueMap additionalFees;
|
|
|
|
// if we don't have a connected root PBaaS chain, we can't properly check
|
|
// and notarize the start block, so we have to pass the notarization and cross chain steps
|
|
bool notaryConnected = ConnectedChains.IsNotaryAvailable();
|
|
uint32_t solutionVersion = CConstVerusSolutionVector::GetVersionByHeight(nHeight);
|
|
|
|
if (isVerusActive &&
|
|
solutionVersion >= CActivationHeight::ACTIVATE_PBAAS &&
|
|
!notaryConnected)
|
|
{
|
|
// until we have connected to the ETH bridge, after PBaaS has launched, we check each block to see if there is now an
|
|
// ETH bridge defined
|
|
if (ConnectedChains.FirstNotaryChain().IsValid())
|
|
{
|
|
// once PBaaS is active, we attempt to connect to the Ethereum bridge, in case it is active
|
|
notaryConnected = ConnectedChains.IsNotaryAvailable(true);
|
|
}
|
|
else
|
|
{
|
|
notaryConnected = ConnectedChains.ConfigureEthBridge(true);
|
|
}
|
|
}
|
|
|
|
// at block 1 for a PBaaS chain, we validate launch conditions
|
|
if (!isVerusActive && nHeight == 1)
|
|
{
|
|
CPBaaSNotarization launchNotarization;
|
|
if (!ConnectedChains.readyToStart &&
|
|
!ConnectedChains.CheckVerusPBaaSAvailable() &&
|
|
!ConnectedChains.readyToStart)
|
|
{
|
|
return NULL;
|
|
}
|
|
if (!MakeBlockOneCoinbaseOutputs(coinbaseTx.vout, launchNotarization, additionalFees, Params().GetConsensus()))
|
|
{
|
|
// can't mine block 1 if we are not connected to a notary
|
|
printf("%s: cannot create block one coinbase outputs\n", __func__);
|
|
LogPrintf("%s: cannot create block one coinbase outputs\n", __func__);
|
|
return NULL;
|
|
}
|
|
currencyState = launchNotarization.currencyState;
|
|
}
|
|
|
|
// if we are a notary, notarize
|
|
if (nHeight > CPBaaSNotarization::MIN_BLOCKS_BEFORE_NOTARY_FINALIZED && !VERUS_NOTARYID.IsNull())
|
|
{
|
|
CValidationState state;
|
|
std::vector<TransactionBuilder> notarizationBuilders;
|
|
std::vector<CTransaction> notarizations;
|
|
bool finalized;
|
|
CTransaction notarizationTx;
|
|
const CRPCChainData ¬aryChain = ConnectedChains.FirstNotaryChain();
|
|
if (notaryChain.IsValid() &&
|
|
CPBaaSNotarization::ConfirmOrRejectNotarizations(pwalletMain, ConnectedChains.FirstNotaryChain(), state, notarizationBuilders, Mining_height, finalized) &&
|
|
notarizationBuilders.size())
|
|
{
|
|
int txCount = 0;
|
|
|
|
// check to see if there is a finalization already in the mem pool, and if so, skip making it
|
|
COptCCParams optP;
|
|
CObjectFinalization finalization;
|
|
|
|
uint160 searchID = CCrossChainRPCData::GetConditionID(CObjectFinalization::ObjectFinalizationFinalizedKey(),
|
|
finalization.output.hash,
|
|
finalization.output.n);
|
|
|
|
std::vector<std::pair<uint160, int>> addresses =
|
|
std::vector<std::pair<uint160, int>>({std::pair<uint160, int>({searchID, CScript::P2IDX})});
|
|
std::vector<std::pair<CMempoolAddressDeltaKey, CMempoolAddressDelta>> results;
|
|
|
|
if (!(notarizationBuilders.back().mtx.vout.size() &&
|
|
notarizationBuilders.back().mtx.vout.back().scriptPubKey.IsPayToCryptoCondition(optP) &&
|
|
optP.IsValid() &&
|
|
optP.evalCode == EVAL_FINALIZE_NOTARIZATION &&
|
|
optP.vData.size() &&
|
|
(finalization = CObjectFinalization(optP.vData[0])).IsValid() &&
|
|
finalization.IsConfirmed() &&
|
|
mempool.getAddressIndex(addresses, results) &&
|
|
results.size()))
|
|
{
|
|
for (int i = 0; i < notarizationBuilders.size(); i++)
|
|
{
|
|
auto ¬arizationBuilder = notarizationBuilders[i];
|
|
|
|
if (!notarizationBuilder.mtx.vin.size())
|
|
{
|
|
bool success = false;
|
|
CCurrencyValueMap reserveValueOut;
|
|
CAmount nativeValueOut = 0;
|
|
// get a native currency input capable of paying a fee, and make our notary ID the change address
|
|
std::set<std::pair<const CWalletTx *, unsigned int>> setCoinsRet;
|
|
{
|
|
LOCK2(cs_main, pwalletMain->cs_wallet);
|
|
std::vector<COutput> vCoins;
|
|
if (IsVerusActive())
|
|
{
|
|
nativeValueOut = CPBaaSNotarization::DEFAULT_NOTARIZATION_FEE;
|
|
pwalletMain->AvailableCoins(vCoins,
|
|
false,
|
|
nullptr,
|
|
false,
|
|
true,
|
|
true,
|
|
false,
|
|
false);
|
|
success = pwalletMain->SelectCoinsMinConf(CPBaaSNotarization::DEFAULT_NOTARIZATION_FEE, 0, 0, vCoins, setCoinsRet, nativeValueOut);
|
|
notarizationBuilder.SetFee(CPBaaSNotarization::DEFAULT_NOTARIZATION_FEE);
|
|
}
|
|
else
|
|
{
|
|
CCurrencyValueMap totalTxFees;
|
|
totalTxFees.valueMap[ConnectedChains.FirstNotaryChain().chainDefinition.GetID()] = CPBaaSNotarization::DEFAULT_NOTARIZATION_FEE;
|
|
notarizationBuilder.SetReserveFee(totalTxFees);
|
|
notarizationBuilder.SetFee(0);
|
|
pwalletMain->AvailableReserveCoins(vCoins,
|
|
false,
|
|
nullptr,
|
|
true,
|
|
true,
|
|
nullptr,
|
|
&totalTxFees,
|
|
false);
|
|
|
|
success = pwalletMain->SelectReserveCoinsMinConf(totalTxFees,
|
|
0,
|
|
0,
|
|
1,
|
|
vCoins,
|
|
setCoinsRet,
|
|
reserveValueOut,
|
|
nativeValueOut);
|
|
|
|
// if we don't have vrsctest
|
|
if (!success)
|
|
{
|
|
nativeValueOut = totalTxFees.valueMap[ConnectedChains.FirstNotaryChain().chainDefinition.GetID()];
|
|
totalTxFees.valueMap.erase(ConnectedChains.FirstNotaryChain().chainDefinition.GetID());
|
|
notarizationBuilder.SetReserveFee(CCurrencyValueMap());
|
|
notarizationBuilder.SetFee(nativeValueOut);
|
|
success = pwalletMain->SelectReserveCoinsMinConf(totalTxFees,
|
|
nativeValueOut,
|
|
0,
|
|
1,
|
|
vCoins,
|
|
setCoinsRet,
|
|
reserveValueOut,
|
|
nativeValueOut);
|
|
}
|
|
}
|
|
}
|
|
if (success)
|
|
{
|
|
for (auto &oneInput : setCoinsRet)
|
|
{
|
|
notarizationBuilder.AddTransparentInput(COutPoint(oneInput.first->GetHash(), oneInput.second),
|
|
oneInput.first->vout[oneInput.second].scriptPubKey,
|
|
oneInput.first->vout[oneInput.second].nValue);
|
|
}
|
|
notarizationBuilder.SendChangeTo(CTxDestination(VERUS_NOTARYID));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
notarizationBuilder.SetFee(0);
|
|
}
|
|
|
|
std::vector<TransactionBuilderResult> buildResultVec;
|
|
|
|
{
|
|
LOCK2(cs_main, pwalletMain->cs_wallet);
|
|
buildResultVec.push_back(notarizationBuilder.Build());
|
|
}
|
|
if (buildResultVec.back().IsTx())
|
|
{
|
|
notarizationTx = buildResultVec[0].GetTxOrThrow();
|
|
bool relayTx = false;
|
|
{
|
|
std::list<CTransaction> removedTxVec;
|
|
LOCK(cs_main);
|
|
LOCK2(smartTransactionCS, mempool.cs);
|
|
mempool.removeConflicts(notarizationTx, removedTxVec);
|
|
CValidationState mempoolState;
|
|
relayTx = myAddtomempool(notarizationTx, &state);
|
|
if (LogAcceptCategory("notarization"))
|
|
{
|
|
for (auto oneTx : removedTxVec)
|
|
{
|
|
if (LogAcceptCategory("verbose"))
|
|
{
|
|
UniValue jsonNTx(UniValue::VOBJ);
|
|
TxToUniv(oneTx, uint256(), jsonNTx);
|
|
LogPrintf("%s: transaction removed from mempool due to conflicts:\n%s\n", __func__, jsonNTx.write(1,2).c_str());
|
|
}
|
|
else
|
|
{
|
|
LogPrintf("%s: transaction removed from mempool due to conflicts:\n%s\n", __func__, oneTx.GetHash().GetHex().c_str());
|
|
}
|
|
}
|
|
if (!relayTx)
|
|
{
|
|
if (LogAcceptCategory("notarizationverbose"))
|
|
{
|
|
UniValue jsonNTx(UniValue::VOBJ);
|
|
TxToUniv(notarizationTx, uint256(), jsonNTx);
|
|
LogPrintf("%s: failed to add transaction to mempool:\n%s\n", __func__, jsonNTx.write(1,2).c_str());
|
|
}
|
|
else
|
|
{
|
|
LogPrintf("%s: failed to add transaction to mempool:\n%s\n", __func__, notarizationTx.GetHash().GetHex().c_str());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (relayTx)
|
|
{
|
|
notarizations.push_back(notarizationTx);
|
|
|
|
// if this is not the last transaction we make, then we add a spend of the 0th output,
|
|
// which is a rolled up finalization of prior evidence to the last tx
|
|
if ((i + 1) < notarizationBuilders.size())
|
|
{
|
|
notarizationBuilders.back().AddTransparentInput(COutPoint(notarizationTx.GetHash(), 0),
|
|
notarizationTx.vout[0].scriptPubKey,
|
|
notarizationTx.vout[0].nValue);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LogPrintf("%s: Failed to add transaction to mempool, reason: %s\n", __func__, state.GetRejectReason());
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
printf("%s: (PII) error adding notary evidence: %s\n", __func__, buildResultVec[0].GetError().c_str());
|
|
LogPrint("notarization", "%s: (PII) error adding notary evidence: %s\n", __func__, buildResultVec[0].GetError().c_str());
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (notarizations.size() == notarizationBuilders.size())
|
|
{
|
|
if (LogAcceptCategory("notarization"))
|
|
{
|
|
LogPrintf("%s: Committed %d notarization transactions to mempool\n", __func__, txCount);
|
|
}
|
|
LOCK(cs_main);
|
|
for (auto &oneTx : notarizations)
|
|
{
|
|
RelayTransaction(oneTx);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LOCK(mempool.cs);
|
|
// we failed to put them in
|
|
for (auto &oneTx : notarizations)
|
|
{
|
|
std::list<CTransaction> removed;
|
|
mempool.remove(oneTx, removed, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (notaryConnected)
|
|
{
|
|
// if we should make an earned notarization, do so
|
|
if (nHeight != 1 && !(VERUS_NOTARYID.IsNull() && VERUS_DEFAULTID.IsNull() && VERUS_NODEID.IsNull()))
|
|
{
|
|
CIdentityID proposer = VERUS_NOTARYID.IsNull() ? (VERUS_DEFAULTID.IsNull() ? VERUS_NODEID : VERUS_DEFAULTID) : VERUS_NOTARYID;
|
|
|
|
// if we have access to our notary daemon
|
|
// create a notarization if we would qualify to do so. add it to the mempool and next block
|
|
ChainMerkleMountainView mmv = chainActive.GetMMV();
|
|
mmrRoot = mmv.GetRoot();
|
|
int32_t confirmedInput = -1;
|
|
CValidationState state;
|
|
CPBaaSNotarization earnedNotarization;
|
|
|
|
int numOuts = coinbaseTx.vout.size();
|
|
if (CPBaaSNotarization::CreateEarnedNotarization(ConnectedChains.FirstNotaryChain(),
|
|
DestinationToTransferDestination(proposer),
|
|
isStake,
|
|
state,
|
|
coinbaseTx.vout,
|
|
earnedNotarization) &&
|
|
numOuts != coinbaseTx.vout.size() &&
|
|
LogAcceptCategory("notarization"))
|
|
{
|
|
LogPrintf("%s: entering earned notarization into block %u, notarization: %s\n", __func__, Mining_height, earnedNotarization.ToUniValue().write(1,2).c_str());
|
|
}
|
|
CPBaaSNotarization lastImportNotarization;
|
|
CUTXORef lastImportNotarizationUTXO;
|
|
|
|
CPBaaSNotarization::SubmitFinalizedNotarizations(ConnectedChains.FirstNotaryChain(), state);
|
|
ProcessNewImports(ConnectedChains.FirstNotaryChain().chainDefinition.GetID(), lastImportNotarization, lastImportNotarizationUTXO, nHeight);
|
|
}
|
|
}
|
|
|
|
// done calling out, take locks for the rest
|
|
LOCK(cs_main);
|
|
LOCK2(smartTransactionCS, mempool.cs);
|
|
|
|
totalEmission = GetBlockSubsidy(nHeight, consensusParams);
|
|
blockSubsidy = totalEmission;
|
|
|
|
// PBaaS chain's block 1 currency state is done by the time we get here,
|
|
// including pre-allocations, etc.
|
|
if (isVerusActive || nHeight != 1)
|
|
{
|
|
currencyState.UpdateWithEmission(totalEmission);
|
|
}
|
|
|
|
// process any imports from the current chain to itself
|
|
ConnectedChains.ProcessLocalImports();
|
|
|
|
CFeePool feePool;
|
|
if (!CFeePool::GetCoinbaseFeePool(feePool, nHeight - 1) ||
|
|
(!feePool.IsValid() && CConstVerusSolutionVector::GetVersionByHeight(nHeight - 1) >= CActivationHeight::ACTIVATE_PBAAS))
|
|
{
|
|
// we should be able to get a valid currency state, if not, fail
|
|
LogPrintf("Failure to get fee pool information for blockchain height #%d\n", nHeight - 1);
|
|
printf("Failure to get fee pool information for blockchain height #%d\n", nHeight - 1);
|
|
return NULL;
|
|
}
|
|
|
|
if (solutionVersion >= CActivationHeight::ACTIVATE_PBAAS && !feePool.IsValid())
|
|
{
|
|
// first block with a fee pool, so make it valid and empty
|
|
feePool = CFeePool();
|
|
}
|
|
|
|
// coinbase should have all necessary outputs
|
|
uint32_t nCoinbaseSize = GetSerializeSize(coinbaseTx, SER_NETWORK, PROTOCOL_VERSION);
|
|
nBlockSize += nCoinbaseSize;
|
|
|
|
// now create the priority array, including market order reserve transactions, since they can always execute, leave limits for later
|
|
bool haveReserveTransactions = false;
|
|
uint32_t reserveExchangeLimitSize = 0;
|
|
std::vector<CReserveTransactionDescriptor> limitOrders;
|
|
|
|
// now add transactions from the mem pool to the priority heap
|
|
for (CTxMemPool::indexed_transaction_set::iterator mi = mempool.mapTx.begin();
|
|
mi != mempool.mapTx.end(); ++mi)
|
|
{
|
|
const CTransaction& tx = mi->GetTx();
|
|
uint256 hash = tx.GetHash();
|
|
|
|
int64_t nLockTimeCutoff = (STANDARD_LOCKTIME_VERIFY_FLAGS & LOCKTIME_MEDIAN_TIME_PAST)
|
|
? nMedianTimePast
|
|
: pblock->GetBlockTime();
|
|
|
|
if (tx.IsCoinBase() || !IsFinalTx(tx, nHeight, nLockTimeCutoff) || IsExpiredTx(tx, nHeight))
|
|
{
|
|
//fprintf(stderr,"coinbase.%d finaltx.%d expired.%d\n",tx.IsCoinBase(),IsFinalTx(tx, nHeight, nLockTimeCutoff),IsExpiredTx(tx, nHeight));
|
|
continue;
|
|
}
|
|
|
|
if ( ASSETCHAINS_SYMBOL[0] == 0 && komodo_validate_interest(tx,nHeight,(uint32_t)pblock->nTime,0) < 0 )
|
|
{
|
|
//fprintf(stderr,"CreateNewBlock: komodo_validate_interest failure nHeight.%d nTime.%u vs locktime.%u\n",nHeight,(uint32_t)pblock->nTime,(uint32_t)tx.nLockTime);
|
|
continue;
|
|
}
|
|
|
|
COrphan* porphan = NULL;
|
|
double dPriority = 0;
|
|
CAmount nTotalIn = 0;
|
|
CCurrencyValueMap totalReserveIn;
|
|
bool fMissingInputs = false;
|
|
CReserveTransactionDescriptor rtxd;
|
|
bool isReserve = mempool.IsKnownReserveTransaction(hash, rtxd);
|
|
|
|
if (tx.IsCoinImport())
|
|
{
|
|
CAmount nValueIn = GetCoinImportValue(tx);
|
|
nTotalIn += nValueIn;
|
|
dPriority += (double)nValueIn * 1000; // flat multiplier
|
|
} else {
|
|
if (isReserve)
|
|
{
|
|
nTotalIn += rtxd.nativeIn;
|
|
totalReserveIn = rtxd.ReserveInputMap();
|
|
assert(!totalReserveIn.valueMap.count(ASSETCHAINS_CHAINID));
|
|
if (rtxd.IsIdentity() && CNameReservation(tx).IsValid())
|
|
{
|
|
nCurrentIDSize += GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION);
|
|
if (nCurrentIDSize > nMaxIDSize)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
BOOST_FOREACH(const CTxIn& txin, tx.vin)
|
|
{
|
|
CAmount nValueIn = 0;
|
|
CCurrencyValueMap reserveValueIn;
|
|
|
|
// Read prev transaction
|
|
if (!view.HaveCoins(txin.prevout.hash))
|
|
{
|
|
// This should never happen; all transactions in the memory
|
|
// pool should connect to either transactions in the chain
|
|
// or other transactions in the memory pool.
|
|
if (!mempool.mapTx.count(txin.prevout.hash))
|
|
{
|
|
LogPrintf("ERROR: mempool transaction missing input\n");
|
|
if (fDebug) assert("mempool transaction missing input" == 0);
|
|
fMissingInputs = true;
|
|
if (porphan)
|
|
vOrphan.pop_back();
|
|
break;
|
|
}
|
|
|
|
// Has to wait for dependencies
|
|
if (!porphan)
|
|
{
|
|
// Use list for automatic deletion
|
|
vOrphan.push_back(COrphan(&tx));
|
|
porphan = &vOrphan.back();
|
|
}
|
|
mapDependers[txin.prevout.hash].push_back(porphan);
|
|
porphan->setDependsOn.insert(txin.prevout.hash);
|
|
|
|
const CTransaction &otx = mempool.mapTx.find(txin.prevout.hash)->GetTx();
|
|
// consider reserve outputs and set priority according to their value here as well
|
|
if (isReserve)
|
|
{
|
|
totalReserveIn += otx.vout[txin.prevout.n].ReserveOutValue();
|
|
}
|
|
nTotalIn += otx.vout[txin.prevout.n].nValue;
|
|
continue;
|
|
}
|
|
const CCoins* coins = view.AccessCoins(txin.prevout.hash);
|
|
assert(coins);
|
|
|
|
if (isReserve)
|
|
{
|
|
reserveValueIn = coins->vout[txin.prevout.n].ReserveOutValue();
|
|
}
|
|
|
|
nValueIn = coins->vout[txin.prevout.n].nValue;
|
|
int nConf = nHeight - coins->nHeight;
|
|
|
|
// TODO: HARDENING - prioritize notarizations and finalizations for our notary chain
|
|
if (rtxd.IsNotaryPrioritized())
|
|
{
|
|
// always very high priority
|
|
dPriority += (double)(SATOSHIDEN * SATOSHIDEN);
|
|
}
|
|
else
|
|
{
|
|
dPriority += ((double)((reserveValueIn.valueMap.size() ? currencyState.ReserveToNative(reserveValueIn) : 0) + nValueIn)) * nConf;
|
|
}
|
|
|
|
if (!isReserve)
|
|
{
|
|
nTotalIn += nValueIn;
|
|
totalReserveIn += reserveValueIn;
|
|
}
|
|
}
|
|
nTotalIn += tx.GetShieldedValueIn();
|
|
}
|
|
|
|
if (fMissingInputs) continue;
|
|
|
|
// Priority is sum(valuein * age) / modified_txsize
|
|
unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION);
|
|
dPriority = tx.ComputePriority(dPriority, nTxSize);
|
|
|
|
CAmount nDeltaValueIn = nTotalIn + (totalReserveIn.valueMap.size() ? currencyState.ReserveToNative(totalReserveIn) : 0);
|
|
CAmount nFeeValueIn = nDeltaValueIn;
|
|
mempool.ApplyDeltas(hash, dPriority, nDeltaValueIn);
|
|
|
|
CAmount nativeEquivalentOut = 0;
|
|
|
|
// if there is reserve in, or this is a reserveexchange transaction, calculate fee properly
|
|
if (isReserve && rtxd.ReserveOutputMap().valueMap.size())
|
|
{
|
|
// if this has reserve currency out, convert it to native currency for fee calculation
|
|
nativeEquivalentOut = currencyState.ReserveToNative(rtxd.ReserveOutputMap());
|
|
}
|
|
|
|
CFeeRate feeRate(isReserve ? rtxd.AllFeesAsNative(currencyState) + currencyState.ReserveToNative(rtxd.ReserveConversionFeesMap()) + rtxd.nativeConversionFees :
|
|
nFeeValueIn - (tx.GetValueOut() + nativeEquivalentOut), nTxSize);
|
|
|
|
if (porphan)
|
|
{
|
|
porphan->dPriority = dPriority;
|
|
porphan->feeRate = feeRate;
|
|
}
|
|
else
|
|
vecPriority.push_back(TxPriority(dPriority, feeRate, &(mi->GetTx())));
|
|
}
|
|
|
|
//
|
|
// NOW -- REALLY START TO FILL THE BLOCK
|
|
//
|
|
// estimate number of conversions, staking transaction size, and additional coinbase outputs that will be required
|
|
|
|
int32_t maxNormalTXBlockSize = nBlockMaxSize - autoTxSize;
|
|
|
|
int64_t interest;
|
|
bool fSortedByFee = (nBlockPrioritySize <= 0);
|
|
|
|
TxPriorityCompare comparer(fSortedByFee);
|
|
std::make_heap(vecPriority.begin(), vecPriority.end(), comparer);
|
|
|
|
std::vector<int> reservePositions;
|
|
|
|
|
|
// store export counts to ensure we don't exceed any limits
|
|
std::set<uint160> newIDRegistrations;
|
|
std::map<uint160, int32_t> exportTransferCount;
|
|
std::map<uint160, int32_t> currencyExportTransferCount;
|
|
std::map<uint160, int32_t> identityExportTransferCount;
|
|
|
|
std::set<uint160> currencyImports;
|
|
std::set<std::pair<uint160, uint160>> idDestAndExport;
|
|
std::set<std::pair<uint160, uint160>> currencyDestAndExport;
|
|
|
|
// we enforce the numeric limits on transactions in precheck exports
|
|
std::map<uint160, int32_t> tmpExportTransfers;
|
|
std::map<uint160, int32_t> tmpCurrencyExportTransfers;
|
|
std::map<uint160, int32_t> tmpIdentityExportTransfers;
|
|
|
|
std::set<uint160> tmpNewIDRegistrations;
|
|
std::set<uint160> tmpCurrencyImports;
|
|
std::set<std::pair<uint160, uint160>> tmpIDDestAndExport;
|
|
std::set<std::pair<uint160, uint160>> tmpCurrencyDestAndExport;
|
|
|
|
// now loop and fill the block, leaving space for reserve exchange limit transactions
|
|
while (!vecPriority.empty())
|
|
{
|
|
// Take highest priority transaction off the priority queue:
|
|
double dPriority = vecPriority.front().get<0>();
|
|
CFeeRate feeRate = vecPriority.front().get<1>();
|
|
const CTransaction& tx = *(vecPriority.front().get<2>());
|
|
|
|
std::pop_heap(vecPriority.begin(), vecPriority.end(), comparer);
|
|
vecPriority.pop_back();
|
|
|
|
// Size limits
|
|
unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION);
|
|
if (nBlockSize + nTxSize >= maxNormalTXBlockSize) // room for extra autotx
|
|
{
|
|
LogPrint("mining","nBlockSize %d + %d nTxSize >= %d maxNormalTXBlockSize\n",(int32_t)nBlockSize,(int32_t)nTxSize,(int32_t)maxNormalTXBlockSize);
|
|
continue;
|
|
}
|
|
|
|
// Legacy limits on sigOps:
|
|
unsigned int nTxSigOps = GetLegacySigOpCount(tx);
|
|
if (nBlockSigOps + nTxSigOps >= MAX_BLOCK_SIGOPS-1)
|
|
{
|
|
//fprintf(stderr,"A nBlockSigOps %d + %d nTxSigOps >= %d MAX_BLOCK_SIGOPS-1\n",(int32_t)nBlockSigOps,(int32_t)nTxSigOps,(int32_t)MAX_BLOCK_SIGOPS);
|
|
continue;
|
|
}
|
|
// Skip free transactions if we're past the minimum block size:
|
|
const uint256& hash = tx.GetHash();
|
|
double dPriorityDelta = 0;
|
|
CAmount nFeeDelta = 0;
|
|
mempool.ApplyDeltas(hash, dPriorityDelta, nFeeDelta);
|
|
if (fSortedByFee && (dPriorityDelta <= 0) && (nFeeDelta <= 0) && (feeRate < ::minRelayTxFee) && (nBlockSize + nTxSize >= nBlockMinSize))
|
|
{
|
|
//fprintf(stderr,"fee rate skip\n");
|
|
continue;
|
|
}
|
|
|
|
// Prioritise by fee once past the priority size or we run out of high-priority
|
|
// transactions:
|
|
if (!fSortedByFee &&
|
|
((nBlockSize + nTxSize >= nBlockPrioritySize) || !AllowFree(dPriority)))
|
|
{
|
|
fSortedByFee = true;
|
|
comparer = TxPriorityCompare(fSortedByFee);
|
|
std::make_heap(vecPriority.begin(), vecPriority.end(), comparer);
|
|
}
|
|
|
|
if (!view.HaveInputs(tx))
|
|
{
|
|
//fprintf(stderr,"dont have inputs\n");
|
|
continue;
|
|
}
|
|
|
|
CAmount nTxFees;
|
|
CReserveTransactionDescriptor txDesc;
|
|
bool isReserve = mempool.IsKnownReserveTransaction(hash, txDesc);
|
|
|
|
nTxFees = view.GetValueIn(chainActive.LastTip()->GetHeight(),&interest,tx,chainActive.LastTip()->nTime) - tx.GetValueOut();
|
|
|
|
nTxSigOps += GetP2SHSigOpCount(tx, view);
|
|
if (nBlockSigOps + nTxSigOps >= MAX_BLOCK_SIGOPS-1)
|
|
{
|
|
//fprintf(stderr,"B nBlockSigOps %d + %d nTxSigOps >= %d MAX_BLOCK_SIGOPS-1\n",(int32_t)nBlockSigOps,(int32_t)nTxSigOps,(int32_t)MAX_BLOCK_SIGOPS);
|
|
continue;
|
|
}
|
|
|
|
// Note that flags: we don't want to set mempool/IsStandard()
|
|
// policy here, but we still have to ensure that the block we
|
|
// create only contains transactions that are valid in new blocks.
|
|
CValidationState state;
|
|
PrecomputedTransactionData txdata(tx);
|
|
if (!ContextualCheckInputs(tx, state, view, nHeight, true, MANDATORY_SCRIPT_VERIFY_FLAGS, true, txdata, Params().GetConsensus(), consensusBranchId))
|
|
{
|
|
//fprintf(stderr,"context failure\n");
|
|
continue;
|
|
}
|
|
|
|
bool usedImportExportIDCounters = false;
|
|
|
|
// go through all outputs and record all currency and identity definitions, either import-based definitions or
|
|
// identity reservations to check for collision, which is disallowed
|
|
bool disqualified = false;
|
|
for (int j = 0; j < tx.vout.size(); j++)
|
|
{
|
|
auto &oneOut = tx.vout[j];
|
|
COptCCParams p;
|
|
uint160 oneIdID;
|
|
if (oneOut.scriptPubKey.IsPayToCryptoCondition(p) &&
|
|
p.IsValid() &&
|
|
p.version >= p.VERSION_V3 &&
|
|
p.vData.size())
|
|
{
|
|
switch (p.evalCode)
|
|
{
|
|
case EVAL_IDENTITY_ADVANCEDRESERVATION:
|
|
{
|
|
CAdvancedNameReservation advNameRes;
|
|
if ((advNameRes = CAdvancedNameReservation(p.vData[0])).IsValid() &&
|
|
(oneIdID = advNameRes.parent, advNameRes.name == CleanName(advNameRes.name, oneIdID, true)) &&
|
|
!(oneIdID = CIdentity::GetID(advNameRes.name, oneIdID)).IsNull() &&
|
|
!newIDRegistrations.count(oneIdID) &&
|
|
!tmpNewIDRegistrations.count(oneIdID))
|
|
{
|
|
usedImportExportIDCounters = true;
|
|
tmpNewIDRegistrations.insert(oneIdID);
|
|
}
|
|
else
|
|
{
|
|
disqualified = true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case EVAL_IDENTITY_RESERVATION:
|
|
{
|
|
CNameReservation nameRes;
|
|
if ((nameRes = CNameReservation(p.vData[0])).IsValid() &&
|
|
nameRes.name == CleanName(nameRes.name, oneIdID) &&
|
|
!(oneIdID = CIdentity::GetID(nameRes.name, oneIdID)).IsNull() &&
|
|
!newIDRegistrations.count(oneIdID) &&
|
|
!tmpNewIDRegistrations.count(oneIdID))
|
|
{
|
|
usedImportExportIDCounters = true;
|
|
tmpNewIDRegistrations.insert(oneIdID);
|
|
}
|
|
else
|
|
{
|
|
disqualified = true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case EVAL_CROSSCHAIN_EXPORT:
|
|
{
|
|
// make sure we don't export the same identity or currency to the same destination more than once in any block
|
|
// we cover the single block case here, and the protocol for each must reject anything invalid on prior blocks
|
|
CCrossChainExport ccx;
|
|
int primaryExportOut;
|
|
int32_t nextOutput;
|
|
CPBaaSNotarization exportNotarization;
|
|
CCurrencyDefinition destSystem;
|
|
std::vector<CReserveTransfer> reserveTransfers;
|
|
if ((ccx = CCrossChainExport(p.vData[0])).IsValid() &&
|
|
!ccx.IsSystemThreadExport() &&
|
|
(destSystem = ConnectedChains.GetCachedCurrency(ccx.destSystemID)).IsValid() &&
|
|
(destSystem.IsGateway() || destSystem.IsPBaaSChain()) &&
|
|
destSystem.SystemOrGatewayID() != ASSETCHAINS_CHAINID &&
|
|
ccx.GetExportInfo(tx, j, primaryExportOut, nextOutput, exportNotarization, reserveTransfers, state,
|
|
(CCurrencyDefinition::EProofProtocol)(destSystem.IsGateway() ?
|
|
destSystem.proofProtocol :
|
|
ConnectedChains.ThisChain().proofProtocol)))
|
|
{
|
|
for (auto &oneTransfer : reserveTransfers)
|
|
{
|
|
if (oneTransfer.IsCurrencyExport())
|
|
{
|
|
std::pair<uint160, uint160> checkKey({ccx.destSystemID, oneTransfer.FirstCurrency()});
|
|
if (currencyDestAndExport.count(checkKey) || tmpCurrencyDestAndExport.count(checkKey))
|
|
{
|
|
// skip this in the block, but should we keep in mempool?
|
|
disqualified = true;
|
|
break;
|
|
}
|
|
usedImportExportIDCounters = true;
|
|
tmpCurrencyDestAndExport.insert(checkKey);
|
|
}
|
|
else if (oneTransfer.IsIdentityExport())
|
|
{
|
|
std::pair<uint160, uint160> checkKey({ccx.destSystemID, GetDestinationID(TransferDestinationToDestination(oneTransfer.destination))});
|
|
if (idDestAndExport.count(checkKey) || tmpIDDestAndExport.count(checkKey))
|
|
{
|
|
disqualified = true;
|
|
break;
|
|
}
|
|
usedImportExportIDCounters = true;
|
|
tmpIDDestAndExport.insert(checkKey);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case EVAL_CROSSCHAIN_IMPORT:
|
|
{
|
|
CCrossChainImport cci, sysCCI;
|
|
CCrossChainExport ccx;
|
|
int sysCCIOut, importNotarizationOut, eOutS, eOutE;
|
|
int32_t nextOutput;
|
|
CPBaaSNotarization importNotarization;
|
|
CCurrencyDefinition destSystem;
|
|
std::vector<CReserveTransfer> reserveTransfers;
|
|
if ((cci = CCrossChainImport(p.vData[0])).IsValid() &&
|
|
!cci.IsSourceSystemImport() &&
|
|
cci.GetImportInfo(tx, nHeight, j, ccx, sysCCI, sysCCIOut, importNotarization, importNotarizationOut, eOutS, eOutE, reserveTransfers, state))
|
|
{
|
|
for (auto &oneTransfer : reserveTransfers)
|
|
{
|
|
if (oneTransfer.IsCurrencyExport())
|
|
{
|
|
if (currencyImports.count(oneTransfer.FirstCurrency()) || tmpCurrencyImports.count(oneTransfer.FirstCurrency()))
|
|
{
|
|
disqualified = true;
|
|
break;
|
|
}
|
|
usedImportExportIDCounters = true;
|
|
tmpCurrencyImports.insert(oneTransfer.FirstCurrency());
|
|
}
|
|
else if (oneTransfer.IsIdentityExport())
|
|
{
|
|
uint160 checkKey = GetDestinationID(TransferDestinationToDestination(oneTransfer.destination));
|
|
if (newIDRegistrations.count(checkKey) || tmpNewIDRegistrations.count(checkKey))
|
|
{
|
|
disqualified = true;
|
|
break;
|
|
}
|
|
tmpNewIDRegistrations.insert(checkKey);
|
|
usedImportExportIDCounters = true;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case EVAL_RESERVE_TRANSFER:
|
|
{
|
|
// make sure we don't export the same identity or currency to the same destination more than once in any block
|
|
// we cover the single block case here, and the protocol for each must reject anything relating to prior blocks
|
|
CReserveTransfer rt;
|
|
CCurrencyDefinition destSystem;
|
|
if ((rt = CReserveTransfer(p.vData[0])).IsValid())
|
|
{
|
|
uint160 destCurrencyID = rt.GetImportCurrency();
|
|
CCurrencyDefinition destCurrency = ConnectedChains.GetCachedCurrency(destCurrencyID);
|
|
CCurrencyDefinition destSystem = ConnectedChains.GetCachedCurrency(destCurrency.SystemOrGatewayID());
|
|
|
|
if (!destSystem.IsValid())
|
|
{
|
|
std::list<CTransaction> removed;
|
|
mempool.remove(tx, removed, true);
|
|
disqualified = true;
|
|
break;
|
|
}
|
|
|
|
// all reserve transfers use the counters
|
|
usedImportExportIDCounters = true;
|
|
|
|
if (destCurrency.SystemOrGatewayID() != ASSETCHAINS_CHAINID)
|
|
{
|
|
if (rt.IsCurrencyExport())
|
|
{
|
|
std::pair<uint160, uint160> checkKey({destCurrency.SystemOrGatewayID(), rt.FirstCurrency()});
|
|
if (currencyDestAndExport.count(checkKey) || tmpCurrencyDestAndExport.count(checkKey))
|
|
{
|
|
disqualified = true;
|
|
break;
|
|
}
|
|
tmpCurrencyDestAndExport.insert(checkKey);
|
|
}
|
|
else if (rt.IsIdentityExport())
|
|
{
|
|
std::pair<uint160, uint160> checkKey({destCurrency.SystemOrGatewayID(), GetDestinationID(TransferDestinationToDestination(rt.destination))});
|
|
if (idDestAndExport.count(checkKey) || tmpIDDestAndExport.count(checkKey))
|
|
{
|
|
disqualified = true;
|
|
break;
|
|
}
|
|
tmpIDDestAndExport.insert(checkKey);
|
|
}
|
|
}
|
|
|
|
if ((++tmpExportTransfers[destCurrencyID] + exportTransferCount[destCurrencyID]) > destSystem.MaxTransferExportCount() ||
|
|
(rt.IsCurrencyExport() && (++tmpCurrencyExportTransfers[destCurrencyID] + currencyExportTransferCount[destCurrencyID]) > destSystem.MaxCurrencyDefinitionExportCount()) ||
|
|
(rt.IsIdentityExport() && (++tmpIdentityExportTransfers[destCurrencyID] + identityExportTransferCount[destCurrencyID]) > destSystem.MaxIdentityDefinitionExportCount()))
|
|
{
|
|
disqualified = true;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (disqualified)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (disqualified)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (usedImportExportIDCounters)
|
|
{
|
|
// update total counts
|
|
for (auto it = tmpExportTransfers.begin(); it != tmpExportTransfers.end(); it++)
|
|
{
|
|
exportTransferCount[it->first] += it->second;
|
|
}
|
|
tmpExportTransfers.clear();
|
|
for (auto it = tmpCurrencyExportTransfers.begin(); it != tmpCurrencyExportTransfers.end(); it++)
|
|
{
|
|
currencyExportTransferCount[it->first] += it->second;
|
|
}
|
|
tmpCurrencyExportTransfers.clear();
|
|
for (auto it = tmpIdentityExportTransfers.begin(); it != tmpIdentityExportTransfers.end(); it++)
|
|
{
|
|
identityExportTransferCount[it->first] += it->second;
|
|
}
|
|
tmpIdentityExportTransfers.clear();
|
|
|
|
// update import and export combinations
|
|
for (auto it = tmpNewIDRegistrations.begin(); it != tmpNewIDRegistrations.end(); it++)
|
|
{
|
|
newIDRegistrations.insert(*it);
|
|
}
|
|
tmpNewIDRegistrations.clear();
|
|
for (auto it = tmpCurrencyImports.begin(); it != tmpCurrencyImports.end(); it++)
|
|
{
|
|
currencyImports.insert(*it);
|
|
}
|
|
tmpCurrencyImports.clear();
|
|
for (auto it = tmpIDDestAndExport.begin(); it != tmpIDDestAndExport.end(); it++)
|
|
{
|
|
idDestAndExport.insert(*it);
|
|
}
|
|
tmpIDDestAndExport.clear();
|
|
for (auto it = tmpCurrencyDestAndExport.begin(); it != tmpCurrencyDestAndExport.end(); it++)
|
|
{
|
|
currencyDestAndExport.insert(*it);
|
|
}
|
|
tmpCurrencyDestAndExport.clear();
|
|
}
|
|
|
|
UpdateCoins(tx, view, nHeight);
|
|
|
|
if (isReserve)
|
|
{
|
|
reservePositions.push_back(nBlockTx);
|
|
haveReserveTransactions = true;
|
|
additionalFees += txDesc.ReserveFees();
|
|
}
|
|
|
|
BOOST_FOREACH(const OutputDescription &outDescription, tx.vShieldedOutput) {
|
|
sapling_tree.append(outDescription.cm);
|
|
}
|
|
|
|
// Added
|
|
pblock->vtx.push_back(tx);
|
|
pblocktemplate->vTxFees.push_back(nTxFees);
|
|
pblocktemplate->vTxSigOps.push_back(nTxSigOps);
|
|
nBlockSize += nTxSize;
|
|
++nBlockTx;
|
|
nBlockSigOps += nTxSigOps;
|
|
nFees += nTxFees;
|
|
if (fPrintPriority)
|
|
{
|
|
LogPrintf("priority %.1f fee %s txid %s\n",dPriority, feeRate.ToString(), tx.GetHash().ToString());
|
|
}
|
|
|
|
// Add transactions that depend on this one to the priority queue
|
|
if (mapDependers.count(hash))
|
|
{
|
|
BOOST_FOREACH(COrphan* porphan, mapDependers[hash])
|
|
{
|
|
if (!porphan->setDependsOn.empty())
|
|
{
|
|
porphan->setDependsOn.erase(hash);
|
|
if (porphan->setDependsOn.empty())
|
|
{
|
|
vecPriority.push_back(TxPriority(porphan->dPriority, porphan->feeRate, porphan->ptx));
|
|
std::push_heap(vecPriority.begin(), vecPriority.end(), comparer);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// first calculate and distribute block rewards, including fees in the minerOutputs vector
|
|
CAmount rewardTotalShareAmount = 0;
|
|
CAmount rewardFees = nFees;
|
|
if (additionalFees.valueMap.count(thisChainID))
|
|
{
|
|
rewardFees += additionalFees.valueMap[thisChainID];
|
|
additionalFees.valueMap.erase(thisChainID);
|
|
}
|
|
|
|
CAmount verusFees = 0;
|
|
if (VERUS_CHAINID != ASSETCHAINS_CHAINID && additionalFees.valueMap.count(VERUS_CHAINID))
|
|
{
|
|
verusFees += additionalFees.valueMap[VERUS_CHAINID];
|
|
additionalFees.valueMap.erase(VERUS_CHAINID);
|
|
}
|
|
|
|
if (additionalFees.valueMap.size())
|
|
{
|
|
printf("%s: burning reserve currency: %s\n", __func__, additionalFees.ToUniValue().write(1,2).c_str());
|
|
}
|
|
|
|
if (feePool.IsValid())
|
|
{
|
|
// we support only the current native currency or VRSC on PBaaS chains in the fee pool for now
|
|
feePool.reserveValues.valueMap[thisChainID] += rewardFees;
|
|
if (verusFees)
|
|
{
|
|
feePool.reserveValues.valueMap[VERUS_CHAINID] += verusFees;
|
|
}
|
|
CFeePool oneFeeShare = feePool.OneFeeShare();
|
|
rewardFees = oneFeeShare.reserveValues.valueMap[thisChainID];
|
|
feePool.reserveValues.valueMap[thisChainID] -= rewardFees;
|
|
|
|
if (VERUS_CHAINID != ASSETCHAINS_CHAINID && oneFeeShare.reserveValues.valueMap.count(VERUS_CHAINID))
|
|
{
|
|
verusFees = oneFeeShare.reserveValues.valueMap[VERUS_CHAINID];
|
|
feePool.reserveValues.valueMap[VERUS_CHAINID] -= verusFees;
|
|
}
|
|
|
|
cp = CCinit(&CC, EVAL_FEE_POOL);
|
|
pkCC = CPubKey(ParseHex(CC.CChexstr));
|
|
coinbaseTx.vout.push_back(CTxOut(0,MakeMofNCCScript(CConditionObj<CFeePool>(EVAL_FEE_POOL,{pkCC.GetID()},1,&feePool))));
|
|
}
|
|
|
|
// printf("%s: rewardfees: %ld, verusfees: %ld\n", __func__, rewardFees, verusFees);
|
|
|
|
CAmount rewardTotal = blockSubsidy + rewardFees;
|
|
|
|
// now that we have the total reward, update the coinbase outputs
|
|
if (isStake)
|
|
{
|
|
// TODO: need to add reserve output to stake coinbase to prevent burning of VRSC
|
|
coinbaseTx.vout[0].nValue = rewardTotal;
|
|
}
|
|
else
|
|
{
|
|
for (auto &outputShare : minerOutputs)
|
|
{
|
|
rewardTotalShareAmount += outputShare.nValue;
|
|
}
|
|
|
|
int cbOutIdx;
|
|
CAmount rewardLeft = rewardTotal;
|
|
CAmount verusFeeLeft = verusFees;
|
|
for (cbOutIdx = 0; cbOutIdx < minerOutputs.size(); cbOutIdx++)
|
|
{
|
|
CAmount amount = (arith_uint256(rewardTotal) * arith_uint256(minerOutputs[cbOutIdx].nValue) / arith_uint256(rewardTotalShareAmount)).GetLow64();
|
|
if (rewardLeft <= amount || (cbOutIdx + 1) == minerOutputs.size())
|
|
{
|
|
amount = rewardLeft;
|
|
}
|
|
rewardLeft -= amount;
|
|
|
|
// now make outputs for non-native, VRSC fees
|
|
if (verusFeeLeft)
|
|
{
|
|
CAmount verusFee = (arith_uint256(verusFees) * arith_uint256(minerOutputs[cbOutIdx].nValue) / arith_uint256(rewardTotalShareAmount)).GetLow64();
|
|
if (verusFeeLeft <= verusFee || (cbOutIdx + 1) == minerOutputs.size())
|
|
{
|
|
verusFee = verusFeeLeft;
|
|
}
|
|
CTxDestination minerDestination;
|
|
if (verusFee >= CFeePool::MIN_SHARE_SIZE && ExtractDestination(coinbaseTx.vout[cbOutIdx].scriptPubKey, minerDestination))
|
|
{
|
|
CTokenOutput to = CTokenOutput(VERUS_CHAINID, verusFee);
|
|
coinbaseTx.vout[cbOutIdx].scriptPubKey = MakeMofNCCScript(CConditionObj<CTokenOutput>(EVAL_RESERVE_OUTPUT,
|
|
std::vector<CTxDestination>({minerDestination}),
|
|
1,
|
|
&to));
|
|
}
|
|
verusFeeLeft -= verusFee;
|
|
}
|
|
|
|
// we had to wait to update this here to ensure it represented a correct distribution ratio
|
|
coinbaseTx.vout[cbOutIdx].nValue = amount;
|
|
}
|
|
}
|
|
|
|
nLastBlockTx = nBlockTx;
|
|
nLastBlockSize = nBlockSize;
|
|
|
|
blocktime = std::max(pindexPrev->GetMedianTimePast(), GetAdjustedTime());
|
|
|
|
pblock->nBits = GetNextWorkRequired(pindexPrev, pblock, Params().GetConsensus());
|
|
|
|
coinbaseTx.nExpiryHeight = 0;
|
|
coinbaseTx.nLockTime = blocktime;
|
|
|
|
// finalize input of coinbase
|
|
coinbaseTx.vin[0].scriptSig = (CScript() << nHeight << CScriptNum(0)) + COINBASE_FLAGS;
|
|
assert(coinbaseTx.vin[0].scriptSig.size() <= 100);
|
|
|
|
// coinbase is done
|
|
pblock->vtx[0] = coinbaseTx;
|
|
uint256 cbHash = coinbaseTx.GetHash();
|
|
|
|
// display it at block 1 for PBaaS debugging
|
|
/* if (nHeight == 1)
|
|
{
|
|
UniValue jsonTxOut(UniValue::VOBJ);
|
|
TxToUniv(coinbaseTx, uint256(), jsonTxOut);
|
|
printf("%s: new coinbase transaction: %s\n", __func__, jsonTxOut.write(1,2).c_str());
|
|
} */
|
|
|
|
// if there is a stake transaction, add it to the very end
|
|
if (isStake)
|
|
{
|
|
UpdateCoins(txStaked, view, nHeight);
|
|
pblock->vtx.push_back(txStaked);
|
|
pblocktemplate->vTxFees.push_back(0);
|
|
int txSigOps = GetLegacySigOpCount(txStaked);
|
|
pblocktemplate->vTxSigOps.push_back(txSigOps);
|
|
// already added to the block size above
|
|
++nBlockTx;
|
|
nBlockSigOps += txSigOps;
|
|
}
|
|
|
|
extern CWallet *pwalletMain;
|
|
|
|
pblock->vtx[0] = coinbaseTx;
|
|
pblocktemplate->vTxFees[0] = -nFees;
|
|
pblocktemplate->vTxSigOps[0] = GetLegacySigOpCount(pblock->vtx[0]);
|
|
|
|
// if not Verus stake, setup nonce, otherwise, leave it alone
|
|
if (!isStake || ASSETCHAINS_LWMAPOS == 0)
|
|
{
|
|
pblock->nNonce = RandomizedNonce();
|
|
}
|
|
|
|
// Fill in header
|
|
pblock->hashPrevBlock = pindexPrev->GetBlockHash();
|
|
pblock->hashFinalSaplingRoot = sapling_tree.root();
|
|
|
|
// all Verus PoS chains need this data in the block at all times
|
|
if ( ASSETCHAINS_LWMAPOS || ASSETCHAINS_SYMBOL[0] == 0 || ASSETCHAINS_STAKED == 0 || KOMODO_MININGTHREADS > 0 )
|
|
{
|
|
UpdateTime(pblock, Params().GetConsensus(), pindexPrev);
|
|
pblock->nBits = GetNextWorkRequired(pindexPrev, pblock, Params().GetConsensus());
|
|
}
|
|
|
|
if ( ASSETCHAINS_SYMBOL[0] == 0 && IS_KOMODO_NOTARY != 0 && My_notaryid >= 0 )
|
|
{
|
|
uint32_t r;
|
|
CMutableTransaction txNotary = CreateNewContextualCMutableTransaction(Params().GetConsensus(), chainActive.Height() + 1);
|
|
if ( pblock->nTime < pindexPrev->nTime+60 )
|
|
pblock->nTime = pindexPrev->nTime + 60;
|
|
|
|
if ( komodo_notaryvin(txNotary,NOTARY_PUBKEY33) > 0 )
|
|
{
|
|
CAmount txfees = 5000;
|
|
pblock->vtx.push_back(txNotary);
|
|
pblocktemplate->vTxFees.push_back(txfees);
|
|
pblocktemplate->vTxSigOps.push_back(GetLegacySigOpCount(txNotary));
|
|
nFees += txfees;
|
|
pblocktemplate->vTxFees[0] = -nFees;
|
|
//*(uint64_t *)(&pblock->vtx[0].vout[0].nValue) += txfees;
|
|
//fprintf(stderr,"added notaryvin\n");
|
|
}
|
|
else
|
|
{
|
|
fprintf(stderr,"error adding notaryvin, need to create 0.0001 utxos\n");
|
|
return(0);
|
|
}
|
|
}
|
|
else if ( ASSETCHAINS_CC == 0 && pindexPrev != 0 && ASSETCHAINS_STAKED == 0 && (ASSETCHAINS_SYMBOL[0] != 0 || IS_KOMODO_NOTARY == 0 || My_notaryid < 0) )
|
|
{
|
|
CValidationState state;
|
|
//fprintf(stderr,"check validity\n");
|
|
if (!TestBlockValidity(state, chainparams, *pblock, pindexPrev, false, false)) // invokes CC checks
|
|
{
|
|
throw std::runtime_error("CreateNewBlock(): TestBlockValidity failed");
|
|
}
|
|
//fprintf(stderr,"valid\n");
|
|
}
|
|
}
|
|
//fprintf(stderr,"done new block\n");
|
|
|
|
// setup the header and buid the Merkle tree
|
|
unsigned int extraNonce = 0;
|
|
IncrementExtraNonce(pblock, pindexPrev, extraNonce, true);
|
|
|
|
return pblocktemplate.release();
|
|
}
|
|
|
|
CBlockTemplate* CreateNewBlock(const CChainParams& chainparams, const CScript& _scriptPubKeyIn, bool isStake)
|
|
{
|
|
std::vector<CTxOut> minerOutputs = _scriptPubKeyIn.size() ? std::vector<CTxOut>({CTxOut(1, _scriptPubKeyIn)}) : std::vector<CTxOut>();
|
|
return CreateNewBlock(chainparams, minerOutputs, isStake);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Internal miner
|
|
//
|
|
|
|
#ifdef ENABLE_MINING
|
|
|
|
class MinerAddressScript : public CReserveScript
|
|
{
|
|
// CReserveScript requires implementing this function, so that if an
|
|
// internal (not-visible) wallet address is used, the wallet can mark it as
|
|
// important when a block is mined (so it then appears to the user).
|
|
// If -mineraddress is set, the user already knows about and is managing the
|
|
// address, so we don't need to do anything here.
|
|
void KeepScript() {}
|
|
};
|
|
|
|
void GetScriptForMinerAddress(boost::shared_ptr<CReserveScript> &script)
|
|
{
|
|
CTxDestination addr = DecodeDestination(GetArg("-mineraddress", ""));
|
|
if (!IsValidDestination(addr)) {
|
|
return;
|
|
}
|
|
|
|
boost::shared_ptr<MinerAddressScript> mAddr(new MinerAddressScript());
|
|
script = mAddr;
|
|
script->reserveScript = GetScriptForDestination(addr);
|
|
}
|
|
|
|
#ifdef ENABLE_WALLET
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Internal miner
|
|
//
|
|
|
|
CBlockTemplate* CreateNewBlockWithKey(CReserveKey& reservekey, int32_t nHeight, bool isStake)
|
|
{
|
|
CPubKey pubkey;
|
|
CScript scriptPubKey;
|
|
uint8_t *ptr;
|
|
int32_t i;
|
|
boost::shared_ptr<CReserveScript> coinbaseScript;
|
|
|
|
UniValue miningDistributionUni;
|
|
if (!GetArg("-miningdistribution", "").empty() &&
|
|
(miningDistributionUni = getminingdistribution(UniValue(UniValue::VARR), false)).isArray() &&
|
|
miningDistributionUni.size())
|
|
{
|
|
std::vector<CTxOut> minerOutputs;
|
|
auto rewardAddresses = miningDistributionUni.getKeys();
|
|
for (int i = 0; i < rewardAddresses.size(); i++)
|
|
{
|
|
CTxDestination oneDest = DecodeDestination(rewardAddresses[i]);
|
|
CAmount relVal = 0;
|
|
if (oneDest.which() == COptCCParams::ADDRTYPE_INVALID ||
|
|
!(relVal = AmountFromValue(find_value(miningDistributionUni, rewardAddresses[i]))))
|
|
{
|
|
continue;
|
|
}
|
|
minerOutputs.push_back(CTxOut(relVal, GetScriptForDestination(oneDest)));
|
|
}
|
|
if (minerOutputs.size())
|
|
{
|
|
return CreateNewBlock(Params(), minerOutputs, false);
|
|
}
|
|
LogPrintf("%s: invalid -miningdistrbution parameter\n", __func__);
|
|
}
|
|
if ( nHeight == 1 && ASSETCHAINS_OVERRIDE_PUBKEY33[0] != 0 )
|
|
{
|
|
scriptPubKey = CScript() << ParseHex(ASSETCHAINS_OVERRIDE_PUBKEY) << OP_CHECKSIG;
|
|
}
|
|
else if ( USE_EXTERNAL_PUBKEY != 0 )
|
|
{
|
|
//fprintf(stderr,"use notary pubkey\n");
|
|
scriptPubKey = CScript() << ParseHex(NOTARY_PUBKEY) << OP_CHECKSIG;
|
|
}
|
|
else if (GetArg("-mineraddress", "").empty() || !(GetScriptForMinerAddress(coinbaseScript), (scriptPubKey = coinbaseScript->reserveScript).size()))
|
|
{
|
|
if (!isStake)
|
|
{
|
|
if (!reservekey.GetReservedKey(pubkey))
|
|
{
|
|
return NULL;
|
|
}
|
|
scriptPubKey = GetScriptForDestination(pubkey);
|
|
}
|
|
}
|
|
return CreateNewBlock(Params(), scriptPubKey, isStake);
|
|
}
|
|
|
|
void komodo_broadcast(const CBlock *pblock,int32_t limit)
|
|
{
|
|
int32_t n = 1;
|
|
//fprintf(stderr,"broadcast new block t.%u\n",(uint32_t)time(NULL));
|
|
{
|
|
LOCK(cs_vNodes);
|
|
BOOST_FOREACH(CNode* pnode, vNodes)
|
|
{
|
|
if ( pnode->hSocket == INVALID_SOCKET )
|
|
continue;
|
|
if ( (rand() % n) == 0 )
|
|
{
|
|
pnode->PushMessage("block", *pblock);
|
|
if ( n++ > limit )
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
//fprintf(stderr,"finished broadcast new block t.%u\n",(uint32_t)time(NULL));
|
|
}
|
|
|
|
static bool ProcessBlockFound(CBlock* pblock, CWallet& wallet, CReserveKey& reservekey)
|
|
#else
|
|
static bool ProcessBlockFound(CBlock* pblock)
|
|
#endif // ENABLE_WALLET
|
|
{
|
|
int32_t height = chainActive.LastTip()->GetHeight()+1;
|
|
//LogPrintf("%s\n", pblock->ToString());
|
|
LogPrintf("generated %s height.%d\n", FormatMoney(pblock->vtx[0].vout[0].nValue), height);
|
|
|
|
// Found a solution
|
|
{
|
|
if (pblock->hashPrevBlock != chainActive.LastTip()->GetBlockHash())
|
|
{
|
|
uint256 hash; int32_t i;
|
|
hash = pblock->hashPrevBlock;
|
|
for (i=31; i>=0; i--)
|
|
fprintf(stderr,"%02x",((uint8_t *)&hash)[i]);
|
|
fprintf(stderr," <- prev (stale)\n");
|
|
hash = chainActive.LastTip()->GetBlockHash();
|
|
for (i=31; i>=0; i--)
|
|
fprintf(stderr,"%02x",((uint8_t *)&hash)[i]);
|
|
fprintf(stderr," <- chainTip (stale)\n");
|
|
|
|
return error("VerusMiner: generated block is stale");
|
|
}
|
|
}
|
|
|
|
#ifdef ENABLE_WALLET
|
|
// Remove key from key pool
|
|
if ( IS_KOMODO_NOTARY == 0 )
|
|
{
|
|
if (GetArg("-mineraddress", "").empty()) {
|
|
// Remove key from key pool
|
|
reservekey.KeepKey();
|
|
}
|
|
}
|
|
// Track how many getdata requests this block gets
|
|
//if ( 0 )
|
|
{
|
|
//fprintf(stderr,"lock cs_wallet\n");
|
|
LOCK(wallet.cs_wallet);
|
|
wallet.mapRequestCount[pblock->GetHash()] = 0;
|
|
}
|
|
#endif
|
|
//fprintf(stderr,"process new block\n");
|
|
|
|
// Process this block (almost) the same as if we had received it from another node
|
|
CValidationState state;
|
|
if (!ProcessNewBlock(1, chainActive.LastTip()->GetHeight()+1, state, Params(), NULL, pblock, true, NULL))
|
|
return error("VerusMiner: ProcessNewBlock, block not accepted");
|
|
|
|
TrackMinedBlock(pblock->GetHash());
|
|
komodo_broadcast(pblock,16);
|
|
return true;
|
|
}
|
|
|
|
int32_t komodo_baseid(char *origbase);
|
|
int32_t komodo_eligiblenotary(uint8_t pubkeys[66][33],int32_t *mids,uint32_t *blocktimes,int32_t *nonzpkeysp,int32_t height);
|
|
arith_uint256 komodo_PoWtarget(int32_t *percPoSp,arith_uint256 target,int32_t height,int32_t goalperc);
|
|
int32_t FOUND_BLOCK,KOMODO_MAYBEMINED;
|
|
extern int32_t KOMODO_LASTMINED,KOMODO_INSYNC;
|
|
int32_t roundrobin_delay;
|
|
arith_uint256 HASHTarget,HASHTarget_POW;
|
|
int32_t komodo_longestchain();
|
|
|
|
// wait for peers to connect
|
|
void waitForPeers(const CChainParams &chainparams)
|
|
{
|
|
if (chainparams.MiningRequiresPeers())
|
|
{
|
|
bool fvNodesEmpty;
|
|
{
|
|
boost::this_thread::interruption_point();
|
|
LOCK(cs_vNodes);
|
|
fvNodesEmpty = vNodes.empty();
|
|
}
|
|
int longestchain = komodo_longestchain();
|
|
int lastlongest = 0;
|
|
if (fvNodesEmpty || IsNotInSync() || (longestchain != 0 && longestchain > chainActive.LastTip()->GetHeight()))
|
|
{
|
|
int loops = 0, blockDiff = 0, newDiff = 0;
|
|
|
|
do {
|
|
if (fvNodesEmpty)
|
|
{
|
|
MilliSleep(1000 + rand() % 4000);
|
|
boost::this_thread::interruption_point();
|
|
LOCK(cs_vNodes);
|
|
fvNodesEmpty = vNodes.empty();
|
|
loops = 0;
|
|
blockDiff = 0;
|
|
lastlongest = 0;
|
|
}
|
|
else if ((newDiff = IsNotInSync()) > 0)
|
|
{
|
|
if (blockDiff != newDiff)
|
|
{
|
|
blockDiff = newDiff;
|
|
}
|
|
else
|
|
{
|
|
if (++loops <= 5)
|
|
{
|
|
MilliSleep(1000);
|
|
}
|
|
else break;
|
|
}
|
|
lastlongest = 0;
|
|
}
|
|
else if (!fvNodesEmpty && !IsNotInSync() && longestchain > chainActive.LastTip()->GetHeight())
|
|
{
|
|
// the only thing may be that we are seeing a long chain that we'll never get
|
|
// don't wait forever
|
|
if (lastlongest == 0)
|
|
{
|
|
MilliSleep(3000);
|
|
lastlongest = longestchain;
|
|
}
|
|
}
|
|
} while (fvNodesEmpty || IsNotInSync());
|
|
MilliSleep(500 + rand() % 1000);
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef ENABLE_WALLET
|
|
CBlockIndex *get_chainactive(int32_t height)
|
|
{
|
|
if ( chainActive.LastTip() != 0 )
|
|
{
|
|
if ( height <= chainActive.LastTip()->GetHeight() )
|
|
{
|
|
LOCK(cs_main);
|
|
return(chainActive[height]);
|
|
}
|
|
// else fprintf(stderr,"get_chainactive height %d > active.%d\n",height,chainActive.Tip()->GetHeight());
|
|
}
|
|
//fprintf(stderr,"get_chainactive null chainActive.Tip() height %d\n",height);
|
|
return(0);
|
|
}
|
|
|
|
/*
|
|
* A separate thread to stake, while the miner threads mine.
|
|
*/
|
|
void static VerusStaker(CWallet *pwallet)
|
|
{
|
|
LogPrintf("Verus staker thread started\n");
|
|
RenameThread("verus-staker");
|
|
|
|
const CChainParams& chainparams = Params();
|
|
auto consensusParams = chainparams.GetConsensus();
|
|
bool isNotaryConnected = ConnectedChains.CheckVerusPBaaSAvailable();
|
|
|
|
// Each thread has its own key
|
|
CReserveKey reservekey(pwallet);
|
|
|
|
// Each thread has its own counter
|
|
unsigned int nExtraNonce = 0;
|
|
|
|
uint8_t *script; uint64_t total,checktoshis; int32_t i,j;
|
|
|
|
while ( (ASSETCHAIN_INIT == 0 || KOMODO_INITDONE == 0) ) //chainActive.Tip()->GetHeight() != 235300 &&
|
|
{
|
|
sleep(1);
|
|
if ( komodo_baseid(ASSETCHAINS_SYMBOL) < 0 )
|
|
break;
|
|
}
|
|
|
|
// try a nice clean peer connection to start
|
|
CBlockIndex *pindexPrev, *pindexCur;
|
|
do {
|
|
pindexPrev = chainActive.LastTip();
|
|
MilliSleep(5000 + rand() % 5000);
|
|
waitForPeers(chainparams);
|
|
pindexCur = chainActive.LastTip();
|
|
} while (pindexPrev != pindexCur);
|
|
|
|
try {
|
|
static int32_t lastStakingHeight = 0;
|
|
|
|
while (true)
|
|
{
|
|
waitForPeers(chainparams);
|
|
CBlockIndex* pindexPrev = chainActive.LastTip();
|
|
|
|
// Create new block
|
|
unsigned int nTransactionsUpdatedLast = mempool.GetTransactionsUpdated();
|
|
|
|
if ( Mining_height != pindexPrev->GetHeight()+1 )
|
|
{
|
|
Mining_height = pindexPrev->GetHeight()+1;
|
|
Mining_start = (uint32_t)time(NULL);
|
|
}
|
|
|
|
// Check for stop or if block needs to be rebuilt
|
|
boost::this_thread::interruption_point();
|
|
|
|
// try to stake a block
|
|
CBlockTemplate *ptr = NULL;
|
|
|
|
// get height locally for consistent reporting
|
|
int32_t newHeight = Mining_height;
|
|
|
|
if (newHeight > VERUS_MIN_STAKEAGE)
|
|
ptr = CreateNewBlockWithKey(reservekey, newHeight, true);
|
|
|
|
// TODO - putting this output here tends to help mitigate announcing a staking height earlier than
|
|
// announcing the last block win when we start staking before a block's acceptance has been
|
|
// acknowledged by the mining thread - a better solution may be to put the output on the submission
|
|
// thread.
|
|
if ( ptr == 0 && newHeight != lastStakingHeight )
|
|
{
|
|
printf("Staking height %d for %s\n", newHeight, ASSETCHAINS_SYMBOL);
|
|
}
|
|
lastStakingHeight = newHeight;
|
|
|
|
if ( ptr == 0 )
|
|
{
|
|
if (newHeight == 1 && (isNotaryConnected = ConnectedChains.IsNotaryAvailable()))
|
|
{
|
|
static int outputCounter;
|
|
if (outputCounter++ % 60 == 0)
|
|
{
|
|
printf("%s: waiting for confirmation of launch at or after block %u on %s before mining block 1\n", __func__,
|
|
(uint32_t)ConnectedChains.ThisChain().startBlock,
|
|
ConnectedChains.FirstNotaryChain().chainDefinition.name.c_str());
|
|
sleep(1);
|
|
}
|
|
}
|
|
// wait to try another staking block until after the tip moves again
|
|
while ( chainActive.LastTip() == pindexPrev )
|
|
MilliSleep(250);
|
|
if (newHeight == 1)
|
|
{
|
|
sleep(10);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
unique_ptr<CBlockTemplate> pblocktemplate(ptr);
|
|
if (!pblocktemplate.get())
|
|
{
|
|
if (GetArg("-mineraddress", "").empty()) {
|
|
LogPrintf("Error in %s staker: Keypool ran out, please call keypoolrefill before restarting the mining thread\n",
|
|
ASSETCHAINS_ALGORITHMS[ASSETCHAINS_ALGO]);
|
|
} else {
|
|
// Should never reach here, because -mineraddress validity is checked in init.cpp
|
|
LogPrintf("Error in %s staker: Invalid %s -mineraddress\n", ASSETCHAINS_ALGORITHMS[ASSETCHAINS_ALGO], ASSETCHAINS_SYMBOL);
|
|
}
|
|
return;
|
|
}
|
|
|
|
CBlock *pblock = &pblocktemplate->block;
|
|
LogPrintf("Staking with %u transactions in block (%u bytes)\n", pblock->vtx.size(),::GetSerializeSize(*pblock,SER_NETWORK,PROTOCOL_VERSION));
|
|
//
|
|
// Search
|
|
//
|
|
int64_t nStart = GetTime();
|
|
|
|
if (vNodes.empty() && chainparams.MiningRequiresPeers())
|
|
{
|
|
if ( Mining_height > ASSETCHAINS_MINHEIGHT )
|
|
{
|
|
fprintf(stderr,"no nodes, attempting reconnect\n");
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (mempool.GetTransactionsUpdated() != nTransactionsUpdatedLast && GetTime() - nStart > 60)
|
|
{
|
|
fprintf(stderr,"timeout, retrying\n");
|
|
continue;
|
|
}
|
|
|
|
if ( pindexPrev != chainActive.LastTip() )
|
|
{
|
|
printf("Block %d added to chain\n", chainActive.LastTip()->GetHeight());
|
|
MilliSleep(250);
|
|
continue;
|
|
}
|
|
|
|
int32_t unlockTime = komodo_block_unlocktime(Mining_height);
|
|
int64_t subsidy = (int64_t)(pblock->vtx[0].vout[0].nValue);
|
|
|
|
uint256 hashTarget = ArithToUint256(arith_uint256().SetCompact(pblock->nBits));
|
|
|
|
pblock->nBits = GetNextWorkRequired(pindexPrev, pblock, consensusParams);
|
|
|
|
UpdateTime(pblock, consensusParams, pindexPrev);
|
|
|
|
if (ProcessBlockFound(pblock, *pwallet, reservekey))
|
|
{
|
|
LogPrintf("Using %s algorithm:\n", ASSETCHAINS_ALGORITHMS[ASSETCHAINS_ALGO]);
|
|
LogPrintf("Staked block found \n hash: %s \ntarget: %s\n", pblock->GetHash().GetHex(), hashTarget.GetHex());
|
|
printf("Found block %d \n", newHeight);
|
|
printf("staking reward %.8f %s!\n", (double)subsidy / (double)COIN, ASSETCHAINS_SYMBOL);
|
|
arith_uint256 post;
|
|
post.SetCompact(pblock->GetVerusPOSTarget());
|
|
|
|
CTransaction &sTx = pblock->vtx[pblock->vtx.size()-1];
|
|
printf("POS hash: %s \ntarget: %s\n",
|
|
CTransaction::_GetVerusPOSHash(&(pblock->nNonce),
|
|
sTx.vin[0].prevout.hash,
|
|
sTx.vin[0].prevout.n,
|
|
newHeight,
|
|
chainActive.GetVerusEntropyHash(newHeight),
|
|
sTx.vout[0].nValue).GetHex().c_str(),
|
|
ArithToUint256(post).GetHex().c_str());
|
|
if (unlockTime > newHeight && subsidy >= ASSETCHAINS_TIMELOCKGTE)
|
|
printf("- timelocked until block %i\n", unlockTime);
|
|
else
|
|
printf("\n");
|
|
}
|
|
else
|
|
{
|
|
LogPrintf("Found block rejected at staking height: %d\n", Mining_height);
|
|
printf("Found block rejected at staking height: %d\n", Mining_height);
|
|
}
|
|
|
|
// Check for stop or if block needs to be rebuilt
|
|
boost::this_thread::interruption_point();
|
|
|
|
sleep(3);
|
|
|
|
// In regression test mode, stop mining after a block is found.
|
|
if (chainparams.MineBlocksOnDemand()) {
|
|
throw boost::thread_interrupted();
|
|
}
|
|
}
|
|
}
|
|
catch (const boost::thread_interrupted&)
|
|
{
|
|
LogPrintf("VerusStaker terminated\n");
|
|
throw;
|
|
}
|
|
catch (const std::runtime_error &e)
|
|
{
|
|
LogPrintf("VerusStaker runtime error: %s\n", e.what());
|
|
return;
|
|
}
|
|
}
|
|
|
|
typedef bool (*minefunction)(CBlockHeader &bh, CVerusHashV2bWriter &vhw, uint256 &finalHash, uint256 &target, uint64_t start, uint64_t *count);
|
|
bool mine_verus_v2(CBlockHeader &bh, CVerusHashV2bWriter &vhw, uint256 &finalHash, uint256 &target, uint64_t start, uint64_t *count);
|
|
bool mine_verus_v2_port(CBlockHeader &bh, CVerusHashV2bWriter &vhw, uint256 &finalHash, uint256 &target, uint64_t start, uint64_t *count);
|
|
|
|
void static BitcoinMiner_noeq(CWallet *pwallet)
|
|
#else
|
|
void static BitcoinMiner_noeq()
|
|
#endif
|
|
{
|
|
LogPrintf("%s miner started\n", ASSETCHAINS_ALGORITHMS[ASSETCHAINS_ALGO]);
|
|
RenameThread("verushash-miner");
|
|
|
|
#ifdef ENABLE_WALLET
|
|
// Each thread has its own key
|
|
CReserveKey reservekey(pwallet);
|
|
#endif
|
|
|
|
miningTimer.clear();
|
|
|
|
const CChainParams& chainparams = Params();
|
|
// Each thread has its own counter
|
|
unsigned int nExtraNonce = 0;
|
|
|
|
uint8_t *script; uint64_t total,checktoshis; int32_t i,j;
|
|
|
|
while ( (ASSETCHAIN_INIT == 0 || KOMODO_INITDONE == 0) ) //chainActive.Tip()->GetHeight() != 235300 &&
|
|
{
|
|
sleep(1);
|
|
if ( komodo_baseid(ASSETCHAINS_SYMBOL) < 0 )
|
|
break;
|
|
}
|
|
|
|
SetThreadPriority(THREAD_PRIORITY_LOWEST);
|
|
|
|
// try a nice clean peer connection to start
|
|
CBlockIndex *pindexPrev, *pindexCur;
|
|
do {
|
|
pindexPrev = chainActive.LastTip();
|
|
MilliSleep(5000 + rand() % 5000);
|
|
waitForPeers(chainparams);
|
|
pindexCur = chainActive.LastTip();
|
|
} while (pindexPrev != pindexCur);
|
|
|
|
// make sure that we have checked for PBaaS availability
|
|
ConnectedChains.CheckVerusPBaaSAvailable();
|
|
|
|
// this will not stop printing more than once in all cases, but it will allow us to print in all cases
|
|
// and print duplicates rarely without having to synchronize
|
|
static CBlockIndex *lastChainTipPrinted;
|
|
static int32_t lastMiningHeight = 0;
|
|
|
|
miningTimer.start();
|
|
|
|
try {
|
|
printf("Mining %s with %s\n", ASSETCHAINS_SYMBOL, ASSETCHAINS_ALGORITHMS[ASSETCHAINS_ALGO]);
|
|
|
|
while (true)
|
|
{
|
|
miningTimer.stop();
|
|
waitForPeers(chainparams);
|
|
|
|
pindexPrev = chainActive.LastTip();
|
|
|
|
// prevent forking on startup before the diff algorithm kicks in,
|
|
// but only for a startup Verus test chain. PBaaS chains have the difficulty inherited from
|
|
// their parent
|
|
if (chainparams.MiningRequiresPeers() && ((IsVerusActive() && pindexPrev->GetHeight() < 50) || pindexPrev != chainActive.LastTip()))
|
|
{
|
|
do {
|
|
pindexPrev = chainActive.LastTip();
|
|
MilliSleep(2000 + rand() % 2000);
|
|
} while (pindexPrev != chainActive.LastTip());
|
|
}
|
|
|
|
// Create new block
|
|
unsigned int nTransactionsUpdatedLast = mempool.GetTransactionsUpdated();
|
|
if ( Mining_height != pindexPrev->GetHeight()+1 )
|
|
{
|
|
Mining_height = pindexPrev->GetHeight()+1;
|
|
if (lastMiningHeight != Mining_height)
|
|
{
|
|
lastMiningHeight = Mining_height;
|
|
printf("Mining %s at height %d\n", ASSETCHAINS_SYMBOL, Mining_height);
|
|
}
|
|
Mining_start = (uint32_t)time(NULL);
|
|
}
|
|
|
|
miningTimer.start();
|
|
|
|
CBlock *pblock = nullptr;
|
|
unique_ptr<CBlockTemplate> pblocktemplate(new CBlockTemplate);
|
|
|
|
boost::shared_ptr<CReserveScript> tmpScript;
|
|
if ((USE_EXTERNAL_PUBKEY != 0 ||
|
|
(!GetArg("-mineraddress", "").empty() && (GetScriptForMinerAddress(tmpScript), tmpScript->reserveScript.size())) ||
|
|
!GetArg("-miningdistribution", "").empty()) &&
|
|
pblocktemplate.get() &&
|
|
ConnectedChains.GetLastBlock(pblocktemplate->block, Mining_height))
|
|
{
|
|
pblock = &pblocktemplate->block;
|
|
}
|
|
else
|
|
{
|
|
#ifdef ENABLE_WALLET
|
|
CBlockTemplate *ptr = CreateNewBlockWithKey(reservekey, Mining_height);
|
|
#else
|
|
CBlockTemplate *ptr = CreateNewBlockWithKey();
|
|
#endif
|
|
if ( ptr == 0 )
|
|
{
|
|
static uint32_t counter;
|
|
if ( counter++ % 40 == 0 )
|
|
{
|
|
if (!IsVerusActive() &&
|
|
ConnectedChains.IsNotaryAvailable() &&
|
|
!ConnectedChains.readyToStart)
|
|
{
|
|
fprintf(stderr,"waiting for confirmation of launch at or after block %u on %s chain to start\n", (uint32_t)ConnectedChains.ThisChain().startBlock,
|
|
ConnectedChains.FirstNotaryChain().chainDefinition.name.c_str());
|
|
}
|
|
else
|
|
{
|
|
fprintf(stderr,"Unable to create valid block... will continue to try\n");
|
|
}
|
|
}
|
|
MilliSleep(2000);
|
|
continue;
|
|
}
|
|
|
|
pblocktemplate = unique_ptr<CBlockTemplate>(ptr);
|
|
if (!pblocktemplate.get())
|
|
{
|
|
if (GetArg("-mineraddress", "").empty()) {
|
|
LogPrintf("Error in %s miner: Keypool ran out, please call keypoolrefill before restarting the mining thread\n",
|
|
ASSETCHAINS_ALGORITHMS[ASSETCHAINS_ALGO]);
|
|
} else {
|
|
// Should never reach here, because -mineraddress validity is checked in init.cpp
|
|
LogPrintf("Error in %s miner: Invalid %s -mineraddress\n", ASSETCHAINS_ALGORITHMS[ASSETCHAINS_ALGO], ASSETCHAINS_SYMBOL);
|
|
}
|
|
miningTimer.stop();
|
|
miningTimer.clear();
|
|
return;
|
|
}
|
|
pblock = &pblocktemplate->block;
|
|
}
|
|
|
|
uint32_t savebits;
|
|
bool mergeMining = false;
|
|
savebits = pblock->nBits;
|
|
|
|
uint32_t solutionVersion = CConstVerusSolutionVector::Version(pblock->nSolution);
|
|
if (pblock->nVersion != CBlockHeader::VERUS_V2)
|
|
{
|
|
// must not be in sync
|
|
printf("Mining on incorrect block version.\n");
|
|
sleep(2);
|
|
continue;
|
|
}
|
|
bool verusSolutionPBaaS = solutionVersion >= CActivationHeight::ACTIVATE_PBAAS;
|
|
|
|
// v2 hash writer with adjustments for the current height
|
|
CVerusHashV2bWriter ss2 = CVerusHashV2bWriter(SER_GETHASH, PROTOCOL_VERSION, solutionVersion);
|
|
|
|
if ( ASSETCHAINS_SYMBOL[0] != 0 )
|
|
{
|
|
if ( ASSETCHAINS_REWARD[0] == 0 && !ASSETCHAINS_LASTERA )
|
|
{
|
|
if ( pblock->vtx.size() == 1 && pblock->vtx[0].vout.size() == 1 && Mining_height > ASSETCHAINS_MINHEIGHT )
|
|
{
|
|
static uint32_t counter;
|
|
if ( counter++ < 10 )
|
|
fprintf(stderr,"skip generating %s on-demand block, no tx avail\n",ASSETCHAINS_SYMBOL);
|
|
sleep(10);
|
|
continue;
|
|
} else fprintf(stderr,"%s vouts.%d mining.%d vs %d\n",ASSETCHAINS_SYMBOL,(int32_t)pblock->vtx[0].vout.size(),Mining_height,ASSETCHAINS_MINHEIGHT);
|
|
}
|
|
}
|
|
|
|
// cache the last block or copy of last
|
|
ConnectedChains.SetLastBlock(*pblock, Mining_height);
|
|
|
|
// randomize the nonce for each thread
|
|
pblock->nNonce = RandomizedNonce();
|
|
|
|
// set our easiest target, if V3+, no need to rebuild the merkle tree
|
|
IncrementExtraNonce(pblock, pindexPrev, nExtraNonce, verusSolutionPBaaS ? false : true, &savebits);
|
|
|
|
// update PBaaS header
|
|
if (verusSolutionPBaaS)
|
|
{
|
|
if (!IsVerusActive() && ConnectedChains.IsVerusPBaaSAvailable())
|
|
{
|
|
|
|
UniValue params(UniValue::VARR);
|
|
UniValue error(UniValue::VARR);
|
|
params.push_back(EncodeHexBlk(*pblock));
|
|
params.push_back(ASSETCHAINS_SYMBOL);
|
|
params.push_back(ASSETCHAINS_RPCHOST);
|
|
params.push_back(ASSETCHAINS_RPCPORT);
|
|
params.push_back(ASSETCHAINS_RPCCREDENTIALS);
|
|
try
|
|
{
|
|
ConnectedChains.lastSubmissionFailed = false;
|
|
params = RPCCallRoot("addmergedblock", params);
|
|
params = find_value(params, "result");
|
|
error = find_value(params, "error");
|
|
} catch (std::exception e)
|
|
{
|
|
printf("Failed to connect to %s chain\n", ConnectedChains.FirstNotaryChain().chainDefinition.name.c_str());
|
|
params = UniValue(e.what());
|
|
}
|
|
if (mergeMining = (params.isNull() && error.isNull()))
|
|
{
|
|
printf("Merge mining %s with %s as the hashing chain\n", ASSETCHAINS_SYMBOL, ConnectedChains.FirstNotaryChain().chainDefinition.name.c_str());
|
|
LogPrintf("Merge mining with %s as the hashing chain\n", ConnectedChains.FirstNotaryChain().chainDefinition.name.c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
LogPrintf("Running %s miner with %u transactions in block (%u bytes)\n",ASSETCHAINS_ALGORITHMS[ASSETCHAINS_ALGO],
|
|
pblock->vtx.size(),::GetSerializeSize(*pblock,SER_NETWORK,PROTOCOL_VERSION));
|
|
//
|
|
// Search
|
|
//
|
|
int64_t nStart = GetTime();
|
|
|
|
arith_uint256 hashTarget = arith_uint256().SetCompact(savebits);
|
|
uint256 uintTarget = ArithToUint256(hashTarget);
|
|
arith_uint256 ourTarget;
|
|
ourTarget.SetCompact(pblock->nBits);
|
|
|
|
Mining_start = 0;
|
|
|
|
if ( pindexPrev != chainActive.LastTip() )
|
|
{
|
|
if (lastChainTipPrinted != chainActive.LastTip())
|
|
{
|
|
lastChainTipPrinted = chainActive.LastTip();
|
|
printf("Block %d added to chain\n", lastChainTipPrinted->GetHeight());
|
|
}
|
|
MilliSleep(100);
|
|
continue;
|
|
}
|
|
|
|
uint64_t count;
|
|
uint64_t hashesToGo = 0;
|
|
uint64_t totalDone = 0;
|
|
|
|
int64_t subsidy = (int64_t)(pblock->vtx[0].vout[0].nValue);
|
|
count = ((ASSETCHAINS_NONCEMASK[ASSETCHAINS_ALGO] >> 3) + 1) / ASSETCHAINS_HASHESPERROUND[ASSETCHAINS_ALGO];
|
|
CVerusHashV2 *vh2 = &ss2.GetState();
|
|
u128 *hashKey;
|
|
verusclhasher &vclh = vh2->vclh;
|
|
minefunction mine_verus;
|
|
mine_verus = IsCPUVerusOptimized() ? &mine_verus_v2 : &mine_verus_v2_port;
|
|
|
|
while (true)
|
|
{
|
|
uint256 hashResult = uint256();
|
|
|
|
unsigned char *curBuf;
|
|
|
|
if (mergeMining)
|
|
{
|
|
// loop for a few minutes before refreshing the block
|
|
while (true)
|
|
{
|
|
uint256 ourMerkle = pblock->hashMerkleRoot;
|
|
if ( pindexPrev != chainActive.LastTip() )
|
|
{
|
|
if (lastChainTipPrinted != chainActive.LastTip())
|
|
{
|
|
lastChainTipPrinted = chainActive.LastTip();
|
|
printf("Block %d added to chain\n\n", lastChainTipPrinted->GetHeight());
|
|
arith_uint256 target;
|
|
target.SetCompact(lastChainTipPrinted->nBits);
|
|
if (ourMerkle == lastChainTipPrinted->hashMerkleRoot)
|
|
{
|
|
LogPrintf("proof-of-work found \n hash: %s \ntarget: %s\n", lastChainTipPrinted->GetBlockHash().GetHex().c_str(), ArithToUint256(ourTarget).GetHex().c_str());
|
|
printf("Found block %d \n", lastChainTipPrinted->GetHeight());
|
|
printf("mining reward %.8f %s!\n", (double)subsidy / (double)COIN, ASSETCHAINS_SYMBOL);
|
|
printf(" hash: %s\ntarget: %s\n", lastChainTipPrinted->GetBlockHash().GetHex().c_str(), ArithToUint256(ourTarget).GetHex().c_str());
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
// if PBaaS is no longer available, we can't count on merge mining
|
|
if (!ConnectedChains.IsVerusPBaaSAvailable())
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (vNodes.empty() && chainparams.MiningRequiresPeers())
|
|
{
|
|
if ( Mining_height > ASSETCHAINS_MINHEIGHT )
|
|
{
|
|
fprintf(stderr,"no nodes, attempting reconnect\n");
|
|
break;
|
|
}
|
|
}
|
|
|
|
// update every few minutes, regardless
|
|
int64_t elapsed = GetTime() - nStart;
|
|
|
|
if ((mempool.GetTransactionsUpdated() != nTransactionsUpdatedLast && elapsed > 60) || elapsed > 60 || ConnectedChains.lastSubmissionFailed)
|
|
{
|
|
break;
|
|
}
|
|
|
|
boost::this_thread::interruption_point();
|
|
MilliSleep(500);
|
|
}
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
// check NONCEMASK at a time
|
|
for (uint64_t i = 0; i < count; i++)
|
|
{
|
|
// this is the actual mining loop, which enables us to drop out and queue a header anytime we earn a block that is good enough for a
|
|
// merge mined block, but not our own
|
|
bool blockFound;
|
|
arith_uint256 arithHash;
|
|
totalDone = 0;
|
|
do
|
|
{
|
|
// pickup/remove any new/deleted headers
|
|
if (ConnectedChains.dirty || (pblock->NumPBaaSHeaders() < ConnectedChains.mergeMinedChains.size() + 1))
|
|
{
|
|
IncrementExtraNonce(pblock, pindexPrev, nExtraNonce, verusSolutionPBaaS ? false : true, &savebits);
|
|
|
|
hashTarget.SetCompact(savebits);
|
|
uintTarget = ArithToUint256(hashTarget);
|
|
}
|
|
|
|
// hashesToGo gets updated with actual number run for metrics
|
|
hashesToGo = ASSETCHAINS_HASHESPERROUND[ASSETCHAINS_ALGO];
|
|
uint64_t start = i * hashesToGo + totalDone;
|
|
hashesToGo -= totalDone;
|
|
|
|
if (verusSolutionPBaaS)
|
|
{
|
|
// mine on canonical header for merge mining
|
|
CPBaaSPreHeader savedHeader(*pblock);
|
|
|
|
pblock->ClearNonCanonicalData();
|
|
blockFound = (*mine_verus)(*pblock, ss2, hashResult, uintTarget, start, &hashesToGo);
|
|
savedHeader.SetBlockData(*pblock);
|
|
}
|
|
else
|
|
{
|
|
blockFound = (*mine_verus)(*pblock, ss2, hashResult, uintTarget, start, &hashesToGo);
|
|
}
|
|
|
|
arithHash = UintToArith256(hashResult);
|
|
totalDone += hashesToGo + 1;
|
|
if (blockFound && IsVerusActive())
|
|
{
|
|
ConnectedChains.QueueNewBlockHeader(*pblock);
|
|
if (arithHash > ourTarget)
|
|
{
|
|
// all blocks qualified with this hash will be submitted
|
|
// until we redo the block, we might as well not try again with anything over this hash
|
|
hashTarget = arithHash;
|
|
uintTarget = ArithToUint256(hashTarget);
|
|
}
|
|
}
|
|
} while (blockFound && arithHash > ourTarget);
|
|
|
|
if (!blockFound || arithHash > ourTarget)
|
|
{
|
|
// Check for stop or if block needs to be rebuilt
|
|
boost::this_thread::interruption_point();
|
|
if ( pindexPrev != chainActive.LastTip() )
|
|
{
|
|
if (lastChainTipPrinted != chainActive.LastTip())
|
|
{
|
|
lastChainTipPrinted = chainActive.LastTip();
|
|
printf("Block %d added to chain\n", lastChainTipPrinted->GetHeight());
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Check for stop or if block needs to be rebuilt
|
|
boost::this_thread::interruption_point();
|
|
|
|
if (pblock->nSolution.size() != 1344)
|
|
{
|
|
LogPrintf("ERROR: Block solution is not 1344 bytes as it should be");
|
|
break;
|
|
}
|
|
|
|
SetThreadPriority(THREAD_PRIORITY_NORMAL);
|
|
|
|
int32_t unlockTime = komodo_block_unlocktime(Mining_height);
|
|
|
|
#ifdef VERUSHASHDEBUG
|
|
std::string validateStr = hashResult.GetHex();
|
|
std::string hashStr = pblock->GetHash().GetHex();
|
|
uint256 *bhalf1 = (uint256 *)vh2->CurBuffer();
|
|
uint256 *bhalf2 = bhalf1 + 1;
|
|
#else
|
|
std::string hashStr = hashResult.GetHex();
|
|
#endif
|
|
|
|
LogPrintf("Using %s algorithm:\n", ASSETCHAINS_ALGORITHMS[ASSETCHAINS_ALGO]);
|
|
LogPrintf("proof-of-work found \n hash: %s \ntarget: %s\n", hashStr, ArithToUint256(ourTarget).GetHex());
|
|
printf("Found block %d \n", Mining_height );
|
|
printf("mining reward %.8f %s!\n", (double)subsidy / (double)COIN, ASSETCHAINS_SYMBOL);
|
|
#ifdef VERUSHASHDEBUG
|
|
printf(" hash: %s\n val: %s \ntarget: %s\n\n", hashStr.c_str(), validateStr.c_str(), ArithToUint256(ourTarget).GetHex().c_str());
|
|
printf("intermediate %lx\n", intermediate);
|
|
printf("Curbuf: %s%s\n", bhalf1->GetHex().c_str(), bhalf2->GetHex().c_str());
|
|
bhalf1 = (uint256 *)verusclhasher_key.get();
|
|
bhalf2 = bhalf1 + ((vh2->vclh.keyMask + 1) >> 5);
|
|
printf(" Key: %s%s\n", bhalf1->GetHex().c_str(), bhalf2->GetHex().c_str());
|
|
#else
|
|
printf(" hash: %s\ntarget: %s", hashStr.c_str(), ArithToUint256(ourTarget).GetHex().c_str());
|
|
#endif
|
|
if (unlockTime > Mining_height && subsidy >= ASSETCHAINS_TIMELOCKGTE)
|
|
printf(" - timelocked until block %i\n", unlockTime);
|
|
else
|
|
printf("\n");
|
|
#ifdef ENABLE_WALLET
|
|
ProcessBlockFound(pblock, *pwallet, reservekey);
|
|
#else
|
|
ProcessBlockFound(pblock);
|
|
#endif
|
|
SetThreadPriority(THREAD_PRIORITY_LOWEST);
|
|
break;
|
|
}
|
|
|
|
if ((i + 1) < count)
|
|
{
|
|
// if we haven't broken out and will not drop through, update hashcount
|
|
{
|
|
miningTimer += totalDone;
|
|
}
|
|
}
|
|
}
|
|
|
|
miningTimer += totalDone;
|
|
}
|
|
|
|
// Check for stop or if block needs to be rebuilt
|
|
boost::this_thread::interruption_point();
|
|
|
|
if (vNodes.empty() && chainparams.MiningRequiresPeers())
|
|
{
|
|
if ( Mining_height > ASSETCHAINS_MINHEIGHT )
|
|
{
|
|
fprintf(stderr,"no nodes, attempting reconnect\n");
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (mempool.GetTransactionsUpdated() != nTransactionsUpdatedLast && GetTime() - nStart > 60)
|
|
{
|
|
fprintf(stderr,"timeout, retrying\n");
|
|
break;
|
|
}
|
|
|
|
if ( pindexPrev != chainActive.LastTip() )
|
|
{
|
|
if (lastChainTipPrinted != chainActive.LastTip())
|
|
{
|
|
lastChainTipPrinted = chainActive.LastTip();
|
|
printf("Block %d added to chain\n\n", lastChainTipPrinted->GetHeight());
|
|
}
|
|
break;
|
|
}
|
|
|
|
// totalDone now has the number of hashes actually done since starting on one nonce mask worth
|
|
uint64_t hashesPerNonceMask = ASSETCHAINS_NONCEMASK[ASSETCHAINS_ALGO] >> 3;
|
|
if (!(totalDone < hashesPerNonceMask))
|
|
{
|
|
#ifdef _WIN32
|
|
printf("%llu mega hashes complete - working\n", (hashesPerNonceMask + 1) / 1048576);
|
|
#else
|
|
printf("%lu mega hashes complete - working\n", (hashesPerNonceMask + 1) / 1048576);
|
|
#endif
|
|
}
|
|
break;
|
|
|
|
}
|
|
}
|
|
}
|
|
catch (const boost::thread_interrupted&)
|
|
{
|
|
miningTimer.stop();
|
|
miningTimer.clear();
|
|
LogPrintf("%s miner terminated\n", ASSETCHAINS_ALGORITHMS[ASSETCHAINS_ALGO]);
|
|
throw;
|
|
}
|
|
catch (const std::runtime_error &e)
|
|
{
|
|
miningTimer.stop();
|
|
miningTimer.clear();
|
|
LogPrintf("%s miner runtime error: %s\n", ASSETCHAINS_ALGORITHMS[ASSETCHAINS_ALGO], e.what());
|
|
return;
|
|
}
|
|
miningTimer.stop();
|
|
miningTimer.clear();
|
|
}
|
|
|
|
#ifdef ENABLE_WALLET
|
|
void GenerateBitcoins(bool fGenerate, CWallet* pwallet, int nThreads)
|
|
#else
|
|
void GenerateBitcoins(bool fGenerate, int nThreads)
|
|
#endif
|
|
{
|
|
static CCriticalSection cs_startmining;
|
|
|
|
LOCK(cs_startmining);
|
|
if (!AreParamsInitialized())
|
|
{
|
|
return;
|
|
}
|
|
|
|
VERUS_MINTBLOCKS = (VERUS_MINTBLOCKS && ASSETCHAINS_LWMAPOS != 0);
|
|
|
|
if (fGenerate == true || VERUS_MINTBLOCKS)
|
|
{
|
|
mapArgs["-gen"] = "1";
|
|
|
|
if (VERUS_DEFAULT_ZADDR.size() > 0)
|
|
{
|
|
if (defaultSaplingDest == boost::none)
|
|
{
|
|
LogPrintf("ERROR: -defaultzaddr parameter is invalid Sapling payment address\n");
|
|
fprintf(stderr, "-defaultzaddr parameter is invalid Sapling payment address\n");
|
|
}
|
|
else
|
|
{
|
|
LogPrintf("StakeGuard searching for double stakes on %s\n", VERUS_DEFAULT_ZADDR.c_str());
|
|
fprintf(stderr, "StakeGuard searching for double stakes on %s\n", VERUS_DEFAULT_ZADDR.c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
static boost::thread_group* minerThreads = NULL;
|
|
|
|
if (nThreads < 0)
|
|
nThreads = GetNumCores();
|
|
|
|
if (minerThreads != NULL)
|
|
{
|
|
minerThreads->interrupt_all();
|
|
minerThreads->join_all();
|
|
delete minerThreads;
|
|
minerThreads = NULL;
|
|
}
|
|
|
|
//fprintf(stderr,"nThreads.%d fGenerate.%d\n",(int32_t)nThreads,fGenerate);
|
|
if ( nThreads == 0 && ASSETCHAINS_STAKED )
|
|
nThreads = 1;
|
|
|
|
if (!fGenerate)
|
|
return;
|
|
|
|
minerThreads = new boost::thread_group();
|
|
|
|
// add the PBaaS thread when mining or staking
|
|
minerThreads->create_thread(boost::bind(&CConnectedChains::SubmissionThreadStub));
|
|
|
|
#ifdef ENABLE_WALLET
|
|
if (VERUS_MINTBLOCKS && pwallet != NULL)
|
|
{
|
|
minerThreads->create_thread(boost::bind(&VerusStaker, pwallet));
|
|
}
|
|
#endif
|
|
|
|
for (int i = 0; i < nThreads; i++) {
|
|
#ifdef ENABLE_WALLET
|
|
minerThreads->create_thread(boost::bind(&BitcoinMiner_noeq, pwallet));
|
|
#else
|
|
minerThreads->create_thread(&BitcoinMiner_noeq);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#endif // ENABLE_MINING
|
|
|