From 1f722359c18d32dbc5d94de1080a423c6ccc7b35 Mon Sep 17 00:00:00 2001 From: Michael Toutonghi Date: Sun, 13 May 2018 18:59:06 -0700 Subject: [PATCH] Verus Proof of Stake Compete with Additional ant-fork protection on block 1 --- src/chainparams.cpp | 21 ++++-- src/consensus/params.h | 12 +++- src/crypto/haraka.h | 4 +- src/komodo_bitcoind.h | 121 +++++++++++++++++++++++++++++++---- src/komodo_globals.h | 5 ++ src/komodo_utils.h | 17 ++++- src/main.cpp | 31 ++++++--- src/miner.cpp | 26 ++++++-- src/pow.cpp | 83 ++++++++++++++++++++++-- src/pow.h | 4 +- src/primitives/block.h | 37 ++++++++++- src/primitives/transaction.h | 25 ++++++++ src/rpcmining.cpp | 2 +- src/rpcmisc.cpp | 3 + src/script/standard.cpp | 8 +-- src/txdb.cpp | 2 +- src/wallet-utility.cpp | 2 + src/wallet/rpcwallet.cpp | 5 ++ src/wallet/wallet.cpp | 100 +++++++++++++++++++++++++++++ src/wallet/wallet.h | 3 + 20 files changed, 458 insertions(+), 53 deletions(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index af943aba6..c0e1bf92f 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -80,12 +80,9 @@ void *chainparams_commandline(void *ptr); extern char ASSETCHAINS_SYMBOL[KOMODO_ASSETCHAIN_MAXLEN]; extern uint16_t ASSETCHAINS_P2PPORT,ASSETCHAINS_RPCPORT; -extern uint32_t ASSETCHAIN_INIT; -extern uint32_t ASSETCHAINS_MAGIC; -extern uint64_t ASSETCHAINS_SUPPLY; -extern uint64_t ASSETCHAINS_ALGO; -extern uint64_t ASSETCHAINS_EQUIHASH; -extern uint64_t ASSETCHAINS_VERUSHASH; +extern uint32_t ASSETCHAIN_INIT, ASSETCHAINS_MAGIC; +extern int32_t VERUS_BLOCK_POSUNITS, ASSETCHAINS_LWMAPOS; +extern uint64_t ASSETCHAINS_SUPPLY, ASSETCHAINS_ALGO, ASSETCHAINS_EQUIHASH, ASSETCHAINS_VERUSHASH; const arith_uint256 maxUint = UintToArith256(uint256S("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")); @@ -244,6 +241,18 @@ void *chainparams_commandline(void *ptr) mainParams.consensus.powAlternate = uint256S("00000f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f"); } + if (ASSETCHAINS_LWMAPOS != 0) + { + mainParams.consensus.posLimit = uint256S("000000000f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f"); + mainParams.consensus.nPOSAveragingWindow = 100; + // spacing is 1000 units per block to get better resolution, POS is 50% hard coded for now, we can vary it later + // when we get reliable integer math on nLwmaPOSAjustedWeight + mainParams.consensus.nPOSTargetSpacing = VERUS_BLOCK_POSUNITS * 2; + // nLwmaPOSAjustedWeight = (N+1)/2 * (0.9989^(500/nPOSAveragingWindow)) * nPOSTargetSpacing + // this needs to be recalculated if VERUS_BLOCK_POSUNITS is changed + mainParams.consensus.nLwmaPOSAjustedWeight = 100446; + } + checkpointData = //(Checkpoints::CCheckpointData) { boost::assign::map_list_of diff --git a/src/consensus/params.h b/src/consensus/params.h index c901547ae..61c35c837 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -97,11 +97,17 @@ struct Params { int64_t nPowMaxAdjustDown; int64_t nPowMaxAdjustUp; int64_t nPowTargetSpacing; - int64_t nMaxFutureBlockTime; - - // Verus algorithm's lwma difficulty int64_t nLwmaAjustedWeight; + /* Proof of stake parameters */ + uint256 posLimit; + int64_t nPOSAveragingWindow; // can be completely different than POW and initially trying a relatively large number, like 100 + int64_t nPOSTargetSpacing; // spacing is 1000 units per block to get better resolution, (100 % = 1000, 50% = 2000, 10% = 10000) + int64_t nLwmaPOSAjustedWeight; + + /* applied to all block times */ + int64_t nMaxFutureBlockTime; + int64_t AveragingWindowTimespan() const { return nPowAveragingWindow * nPowTargetSpacing; } int64_t MinActualTimespan() const { return (AveragingWindowTimespan() * (100 - nPowMaxAdjustUp )) / 100; } int64_t MaxActualTimespan() const { return (AveragingWindowTimespan() * (100 + nPowMaxAdjustDown)) / 100; } diff --git a/src/crypto/haraka.h b/src/crypto/haraka.h index ab56424ac..203f8fbf6 100644 --- a/src/crypto/haraka.h +++ b/src/crypto/haraka.h @@ -30,8 +30,8 @@ Optimized Implementations for Haraka256 and Haraka512 #define NUMROUNDS 5 -#define u64 unsigned long -#define u128 __m128i +typedef unsigned long u64; +typedef __m128i u128; extern u128 rc[40]; diff --git a/src/komodo_bitcoind.h b/src/komodo_bitcoind.h index 359f88e8b..7be8c749f 100644 --- a/src/komodo_bitcoind.h +++ b/src/komodo_bitcoind.h @@ -27,6 +27,7 @@ int32_t komodo_notaries(uint8_t pubkeys[64][33],int32_t height,uint32_t timestamp); int32_t komodo_electednotary(int32_t *numnotariesp,uint8_t *pubkey33,int32_t height,uint32_t timestamp); +unsigned int lwmaGetNextPOSRequired(const CBlockIndex* pindexLast, const Consensus::Params& params); //#define issue_curl(cmdstr) bitcoind_RPC(0,(char *)"curl",(char *)"http://127.0.0.1:7776",0,0,(char *)(cmdstr)) @@ -557,7 +558,7 @@ uint64_t komodo_seed(int32_t height) return(seed); } -uint32_t komodo_txtime(uint64_t *valuep,uint256 hash,int32_t n,char *destaddr) +uint32_t komodo_txtime(uint64_t *valuep,uint256 hash, int32_t n, char *destaddr) { CTxDestination address; CTransaction tx; uint256 hashBlock; *valuep = 0; @@ -666,18 +667,19 @@ int32_t komodo_block2pubkey33(uint8_t *pubkey33,CBlock *block) else memset(pubkey33,0,33); if ( block->vtx[0].vout.size() > 0 ) { -#ifdef KOMODO_ZCASH - uint8_t *ptr = (uint8_t *)block->vtx[0].vout[0].scriptPubKey.data(); -#else - uint8_t *ptr = (uint8_t *)&block->vtx[0].vout[0].scriptPubKey[0]; -#endif - //komodo_init(0); - n = block->vtx[0].vout[0].scriptPubKey.size(); - if ( n == 35 ) + txnouttype whichType; + vector> vch = vector>(); + if (Solver(block->vtx[0].vout[0].scriptPubKey, whichType, vch) && whichType == TX_PUBKEY) { - memcpy(pubkey33,ptr+1,33); - return(1); + CPubKey pubKey(vch[0]); + if (pubKey.IsValid()) + { + memcpy(pubkey33,vch[0].data(),33); + return true; + } + else memset(pubkey33,0,33); } + else memset(pubkey33,0,33); } return(0); } @@ -1256,6 +1258,95 @@ int32_t komodo_is_PoSblock(int32_t slowflag,int32_t height,CBlock *pblock,arith_ return(isPoS); } +// if slow flag is 1, this does a slower check that checks the target with consensus, otherwise quick, insecure check for internal integrity +bool verusCheckPOSBlock(int32_t slowflag, CBlock *pblock, int32_t height) +{ + CBlockIndex *pastBlockIndex; + uint256 txid, blkHash; + int32_t txn_count; + uint32_t voutNum; + bool isPOS = false; + CTxDestination voutaddress; + arith_uint256 target, hash; + CTransaction tx; + + // TODO(miketout) must initialize destaddr properly + char voutaddr[64],destaddr[64]; + + target.SetCompact(pblock->GetVerusPOSTarget()); + txn_count = pblock->vtx.size(); + + if ( txn_count > 1 ) + { + txid = pblock->vtx[txn_count-1].vin[0].prevout.hash; + voutNum = pblock->vtx[txn_count-1].vin[0].prevout.n; + +#ifndef KOMODO_ZCASH + if (!GetTransaction(txid, tx, Params().GetConsensus(), blkHash, true)) +#else + if (!GetTransaction(txid, tx, blkHash, true)) +#endif + { + fprintf(stderr,"ERROR: invalid PoS block %s - no transaction\n",blkHash.ToString().c_str()); + } + else if (!(pastBlockIndex = komodo_chainactive(height - COINBASE_MATURITY))) + { + fprintf(stderr,"ERROR: invalid PoS block %s - no past block hash\n",blkHash.ToString().c_str()); + } + else + { + hash = UintToArith256(tx.GetVerusPOSHash(voutNum, height, pastBlockIndex->GetBlockHash())) / tx.vout[voutNum].nValue; + if (hash <= target) + { + if ((mapBlockIndex.count(blkHash) == 0) || + !(pastBlockIndex = mapBlockIndex[blkHash]) || + (height - pastBlockIndex->nHeight) < VERUS_MIN_STAKEAGE) + { + fprintf(stderr,"ERROR: invalid PoS block %s - no prev block found\n",blkHash.ToString().c_str()); + } + else if ( slowflag != 0 ) + { + // make sure we have the right target + CBlockIndex *previndex; + if (!(previndex = mapBlockIndex[pblock->hashPrevBlock])) + { + fprintf(stderr,"ERROR: invalid PoS block %s - no prev block found\n",blkHash.ToString().c_str()); + } + else + { + arith_uint256 cTarget; + cTarget.SetCompact(lwmaGetNextPOSRequired(previndex, Params().GetConsensus())); + if (cTarget != target) + { + fprintf(stderr,"ERROR: invalid PoS block %s - invalid diff target\n",blkHash.ToString().c_str()); + } + else if ( ExtractDestination(pblock->vtx[txn_count-1].vout[0].scriptPubKey,voutaddress) ) + { + strcpy(voutaddr, CBitcoinAddress(voutaddress).ToString().c_str()); + if ( strcmp(destaddr,voutaddr) == 0 ) + { + isPOS = true; + } + else + { + fprintf(stderr,"ERROR: invalid PoS block %s - invalid stake destination\n",blkHash.ToString().c_str()); + } + } + } + } + else + { + // with fast check, we get true here, slow check ensures destination address + // of staking transaction matches original source and that the target + // matches the consensus target + isPOS = true; + } + } + } + } + return(isPOS); +} + int32_t komodo_checkPOW(int32_t slowflag,CBlock *pblock,int32_t height) { uint256 hash; arith_uint256 bnTarget,bhash; bool fNegative,fOverflow; uint8_t *script,pubkey33[33],pubkeys[64][33]; int32_t i,possible,PoSperc,is_PoSblock=0,n,failed = 0,notaryid = -1; int64_t checktoshis,value; CBlockIndex *pprev; @@ -1278,6 +1369,14 @@ int32_t komodo_checkPOW(int32_t slowflag,CBlock *pblock,int32_t height) if ( height == 0 ) return(0); } + if ( ASSETCHAINS_LWMAPOS != 0 && bhash > bnTarget ) + { + // if proof of stake is active, check if this is a valid PoS block before we fail + if (verusCheckPOSBlock(slowflag, pblock, height)) + { + return(0); + } + } if ( (ASSETCHAINS_SYMBOL[0] != 0 || height > 792000) && bhash > bnTarget ) { failed = 1; diff --git a/src/komodo_globals.h b/src/komodo_globals.h index 7f70c814e..a2d342d9f 100644 --- a/src/komodo_globals.h +++ b/src/komodo_globals.h @@ -78,6 +78,11 @@ uint32_t ASSETCHAINS_NONCESHIFT[] = {32,40}; uint32_t ASSETCHAINS_HASHESPERROUND[] = {1,512}; uint32_t ASSETCHAINS_ALGO = _ASSETCHAINS_EQUIHASH; +// Verus proof of stake controls +int32_t ASSETCHAINS_LWMAPOS = 0; // percentage of blocks should be PoS +int32_t VERUS_BLOCK_POSUNITS = 1000; // one block is 1000 units +int32_t VERUS_MIN_STAKEAGE = 200; // 1/2 this should also be a cap on the POS averaging window, or startup could be too easy + uint64_t KOMODO_INTERESTSUM,KOMODO_WALLETBALANCE; uint64_t ASSETCHAINS_COMMISSION,ASSETCHAINS_STAKED,ASSETCHAINS_SUPPLY = 10; diff --git a/src/komodo_utils.h b/src/komodo_utils.h index fa03f66aa..2fa46819a 100644 --- a/src/komodo_utils.h +++ b/src/komodo_utils.h @@ -1020,7 +1020,7 @@ int32_t komodo_opreturnscript(uint8_t *script,uint8_t type,uint8_t *opret,int32_ // get a pseudo random number that is the same for each block individually at all times and different // from all other blocks. the sequence is extremely likely, but not guaranteed to be unique for each block chain -uint64_t blockPRG(uint32_t nHeight) +uint64_t komodo_block_prg(uint32_t nHeight) { int i; uint8_t hashSrc[8]; @@ -1052,7 +1052,7 @@ int64_t komodo_block_unlocktime(uint32_t nHeight) unlocktime = ASSETCHAINS_TIMEUNLOCKTO; else { - unlocktime = blockPRG(nHeight) / (0xffffffffffffffff / ((ASSETCHAINS_TIMEUNLOCKTO - ASSETCHAINS_TIMEUNLOCKFROM) + 1)); + unlocktime = komodo_block_prg(nHeight) / (0xffffffffffffffff / ((ASSETCHAINS_TIMEUNLOCKTO - ASSETCHAINS_TIMEUNLOCKFROM) + 1)); // boundary and power of 2 can make it exceed to time by 1 unlocktime = unlocktime + ASSETCHAINS_TIMEUNLOCKFROM; if (unlocktime > ASSETCHAINS_TIMEUNLOCKTO) @@ -1602,6 +1602,12 @@ uint64_t komodo_ac_block_subsidy(int nHeight) } } } + if ( nHeight == 1 ) + if ( ASSETCHAINS_LASTERA == 0 ) + subsidy = ASSETCHAINS_SUPPLY * SATOSHIDEN + (ASSETCHAINS_MAGIC & 0xffffff); + else + subsidy += ASSETCHAINS_SUPPLY * SATOSHIDEN + (ASSETCHAINS_MAGIC & 0xffffff); + return(subsidy); } @@ -1701,6 +1707,8 @@ void komodo_args(char *argv0) ASSETCHAINS_OVERRIDE_PUBKEY = GetArg("-ac_pubkey",""); if ( (ASSETCHAINS_STAKED= GetArg("-ac_staked",0)) > 100 ) ASSETCHAINS_STAKED = 100; + if ( (ASSETCHAINS_LWMAPOS = GetArg("-ac_veruspos",0)) > 100 ) + ASSETCHAINS_LWMAPOS = 100; if ( strlen(ASSETCHAINS_OVERRIDE_PUBKEY.c_str()) == 66 && ASSETCHAINS_COMMISSION > 0 && ASSETCHAINS_COMMISSION <= 100000000 ) decode_hex(ASSETCHAINS_OVERRIDE_PUBKEY33,33,(char *)ASSETCHAINS_OVERRIDE_PUBKEY.c_str()); @@ -1752,6 +1760,11 @@ void komodo_args(char *argv0) extralen += iguana_rwnum(1,&extraptr[extralen],sizeof(ASSETCHAINS_ALGO),(void *)&ASSETCHAINS_ALGO); } + if ( ASSETCHAINS_LWMAPOS != 0 ) + { + extralen += iguana_rwnum(1,&extraptr[extralen],sizeof(ASSETCHAINS_LWMAPOS),(void *)&ASSETCHAINS_LWMAPOS); + } + val = ASSETCHAINS_COMMISSION | (((uint64_t)ASSETCHAINS_STAKED & 0xff) << 32) | (((uint64_t)ASSETCHAINS_CC & 0xffffff) << 40); extralen += iguana_rwnum(1,&extraptr[extralen],sizeof(val),(void *)&val); } diff --git a/src/main.cpp b/src/main.cpp index 3408647d3..2c4c176db 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -907,6 +907,14 @@ unsigned int GetP2SHSigOpCount(const CTransaction& tx, const CCoinsViewCache& in */ bool ContextualCheckCoinbaseTransaction(const CTransaction& tx, const int nHeight) { + int64_t valOut = tx.GetValueOut(), sub = komodo_ac_block_subsidy(nHeight); + if ((uint64_t)(tx.GetValueOut()) != komodo_ac_block_subsidy(nHeight)) + { + // failed with invalid coinbase output + fprintf(stderr, "ERROR: invalid block #%i, reward=%li, subsidy should be %li", nHeight, valOut, sub); + return(false); + } + // if time locks are on, ensure that this coin base is time locked exactly as it should be if ((uint64_t)(tx.GetValueOut()) >= ASSETCHAINS_TIMELOCKGTE) { @@ -1731,7 +1739,7 @@ bool ReadBlockFromDisk(int32_t height,CBlock& block, const CDiskBlockPos& pos,bo if ( 0 && checkPOW != 0 ) { komodo_block2pubkey33(pubkey33,(CBlock *)&block); - if (!(CheckEquihashSolution(&block, Params()) && CheckProofOfWork(height,pubkey33,block.GetHash(), block.nBits, Params().GetConsensus(),block.nTime))) + if (!(CheckEquihashSolution(&block, Params()) && CheckProofOfWork(block, pubkey33, height, Params().GetConsensus()))) { int32_t i; for (i=0; i<33; i++) fprintf(stderr,"%02x",pubkey33[i]); @@ -1774,10 +1782,7 @@ CAmount GetBlockSubsidy(int nHeight, const Consensus::Params& consensusParams) } else { - if ( nHeight == 1 ) - return(ASSETCHAINS_SUPPLY * COIN + (ASSETCHAINS_MAGIC & 0xffffff)); - else - return(komodo_ac_block_subsidy(nHeight)); + return(komodo_ac_block_subsidy(nHeight)); } /* // Mining slow start @@ -3328,6 +3333,13 @@ static bool ActivateBestChainStep(CValidationState &state, CBlockIndex *pindexMo bool fInvalidFound = false; const CBlockIndex *pindexOldTip = chainActive.Tip(); const CBlockIndex *pindexFork = chainActive.FindFork(pindexMostWork); + + // asset chains cannot reorg past block 1, which is specific to the asset chain + if (ASSETCHAINS_SYMBOL[0] != 0 && pindexFork && pindexFork->nHeight < 1 && chainActive.Tip()->nHeight != pindexFork->nHeight) + { + LogPrintf("Failed attempt to reorg past block 1 in asset chain %s\n", ASSETCHAINS_SYMBOL); + return false; + } // - On ChainDB initialization, pindexOldTip will be null, so there are no removable blocks. // - If pindexMostWork is in a chain that doesn't have the same genesis block as our chain, @@ -3847,10 +3859,10 @@ bool CheckBlock(int32_t *futureblockp,int32_t height,CBlockIndex *pindex,const C } if ( fCheckPOW ) { - //if ( !CheckEquihashSolution(&block, Params()) ) + //if ( !CheckEquihashSolution(&block, Params()) ) // return state.DoS(100, error("CheckBlock: Equihash solution invalid"),REJECT_INVALID, "invalid-solution"); komodo_block2pubkey33(pubkey33,(CBlock *)&block); - if ( !CheckProofOfWork(height,pubkey33,hash,block.nBits,Params().GetConsensus(),block.nTime) ) + if ( !CheckProofOfWork(block,pubkey33,height,Params().GetConsensus()) ) { int32_t z; for (z=31; z>=0; z--) fprintf(stderr,"%02x",((uint8_t *)&hash)[z]); @@ -3933,7 +3945,7 @@ bool ContextualCheckBlockHeader(const CBlockHeader& block, CValidationState& sta assert(pindexPrev); int nHeight = pindexPrev->nHeight+1; - + // Check proof of work if ( (ASSETCHAINS_SYMBOL[0] != 0 || nHeight < 235300 || nHeight > 236000) && block.nBits != GetNextWorkRequired(pindexPrev, &block, consensusParams)) { @@ -3997,6 +4009,7 @@ bool ContextualCheckBlock(const CBlock& block, CValidationState& state, CBlockIn { const int nHeight = pindexPrev == NULL ? 0 : pindexPrev->nHeight + 1; const Consensus::Params& consensusParams = Params().GetConsensus(); + bool checkBlockOne = (nHeight == 1); // Check that all transactions are finalized BOOST_FOREACH(const CTransaction& tx, block.vtx) { @@ -4005,7 +4018,7 @@ bool ContextualCheckBlock(const CBlock& block, CValidationState& state, CBlockIn if (!ContextualCheckTransaction(tx, state, nHeight, 100)) { return false; // Failure reason has been set in validation state object } - + int nLockTimeFlags = 0; int64_t nLockTimeCutoff = (nLockTimeFlags & LOCKTIME_MEDIAN_TIME_PAST) ? pindexPrev->GetMedianTimePast() diff --git a/src/miner.cpp b/src/miner.cpp index ac2b70e58..5a576f069 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -111,7 +111,7 @@ extern int32_t ASSETCHAINS_SEED,IS_KOMODO_NOTARY,USE_EXTERNAL_PUBKEY,KOMODO_CHOS extern uint64_t ASSETCHAINS_COMMISSION, ASSETCHAINS_STAKED; extern uint64_t ASSETCHAINS_REWARD[ASSETCHAINS_MAX_ERAS], ASSETCHAINS_TIMELOCKGTE, ASSETCHAINS_NONCEMASK[]; extern const char *ASSETCHAINS_ALGORITHMS[]; -extern int32_t ASSETCHAINS_ALGO, ASSETCHAINS_EQUIHASH, ASSETCHAINS_LASTERA, ASSETCHAINS_NONCESHIFT[], ASSETCHAINS_HASHESPERROUND[]; +extern int32_t ASSETCHAINS_ALGO, ASSETCHAINS_EQUIHASH, ASSETCHAINS_LASTERA, ASSETCHAINS_LWMAPOS, ASSETCHAINS_NONCESHIFT[], ASSETCHAINS_HASHESPERROUND[]; extern char ASSETCHAINS_SYMBOL[KOMODO_ASSETCHAIN_MAXLEN]; extern std::string NOTARY_PUBKEY; extern uint8_t NOTARY_PUBKEY33[33],ASSETCHAINS_OVERRIDE_PUBKEY33[33]; @@ -127,6 +127,7 @@ int32_t komodo_validate_interest(const CTransaction &tx,int32_t txheight,uint32_ int64_t komodo_block_unlocktime(uint32_t nHeight); uint64_t komodo_commission(const CBlock *block); int32_t komodo_staked(CPubKey &pubkey, 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(CPubKey &pubkey, CMutableTransaction &txNew, uint32_t &nBits, arith_uint256 &hashResult, uint8_t *utxosig); int32_t komodo_notaryvin(CMutableTransaction &txNew,uint8_t *notarypub33); CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn, bool isStake) @@ -450,11 +451,24 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn, bool isStake) } } - uint64_t txfees,utxovalue; uint32_t txtime; uint256 utxotxid,revtxid; int32_t i,siglen,numsigs,utxovout; uint8_t utxosig[128],*ptr; + uint64_t txfees,utxovalue; uint32_t txtime; uint256 utxotxid; int32_t i,siglen,numsigs,utxovout; uint8_t utxosig[128],*ptr; CMutableTransaction txStaked = CreateNewContextualCMutableTransaction(Params().GetConsensus(), chainActive.Height() + 1); + //if ( blocktime > pindexPrev->GetMedianTimePast()+60 ) // blocktime = pindexPrev->GetMedianTimePast() + 60; - if ( (siglen= komodo_staked(key, txStaked,pblock->nBits,&blocktime,&txtime,&utxotxid,&utxovout,&utxovalue,utxosig)) > 0 ) + if (ASSETCHAINS_LWMAPOS != 0) + { + uint32_t nBitsPOS; + arith_uint256 posHash; + siglen = verus_staked(key, txStaked, nBitsPOS, posHash, utxosig); + blocktime = GetAdjustedTime(); + } + else + { + siglen = komodo_staked(key, txStaked, pblock->nBits, &blocktime, &txtime, &utxotxid, &utxovout, &utxovalue, utxosig); + } + + if ( siglen > 0 ) { CAmount txfees = 0; if ( GetAdjustedTime() < blocktime-13 ) @@ -1478,7 +1492,7 @@ void static BitcoinMiner() // (x_1, x_2, ...) = A(I, V, n, k) LogPrint("pow", "Running Equihash solver \"%s\" with nNonce = %s\n",solver, pblock->nNonce.ToString()); arith_uint256 hashTarget; - if ( NOTARY_PUBKEY33[0] == 0 && ASSETCHAINS_STAKED > 0 && ASSETCHAINS_STAKED < 100 ) + if ( NOTARY_PUBKEY33[0] == 0 && ASSETCHAINS_STAKED > 0 && ASSETCHAINS_STAKED <= 100 ) hashTarget = HASHTarget_POW; else hashTarget = HASHTarget; std::function)> validBlock = @@ -1726,13 +1740,13 @@ void static BitcoinMiner() minerThreads = NULL; } - if ((nThreads == 0 && ASSETCHAINS_STAKED == 0) || !fGenerate) + if ((nThreads == 0 && ASSETCHAINS_LWMAPOS == 0) || !fGenerate) return; minerThreads = new boost::thread_group(); #ifdef ENABLE_WALLET - if (ASSETCHAINS_STAKED != 0) + if (ASSETCHAINS_LWMAPOS != 0) { minerThreads->create_thread(boost::bind(&VerusStaker, pwallet)); } diff --git a/src/pow.cpp b/src/pow.cpp index 800ec7d2c..70319ca34 100644 --- a/src/pow.cpp +++ b/src/pow.cpp @@ -22,6 +22,7 @@ uint32_t komodo_chainactive_timestamp(); extern uint32_t ASSETCHAINS_ALGO, ASSETCHAINS_EQUIHASH; +extern int32_t VERUS_BLOCK_POSUNITS; unsigned int lwmaGetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHeader *pblock, const Consensus::Params& params); unsigned int lwmaCalculateNextWorkRequired(const CBlockIndex* pindexLast, const Consensus::Params& params); @@ -142,6 +143,73 @@ unsigned int lwmaCalculateNextWorkRequired(const CBlockIndex* pindexLast, const return nextTarget.GetCompact(); } +bool DoesHashQualify(const CBlockIndex *pbindex) +{ + // if it fails hash test and PoW validation, consider it POS. it could also be invalid + arith_uint256 hash = UintToArith256(pbindex->GetBlockHash()); + // to be considered POS, we first can't qualify as POW + if (hash > hash.SetCompact(pbindex->nBits)) + { + return false; + } + return true; +} + +// the goal is to keep POS at a solve time that is a ratio of block time units. the low resolution makes a stable solution more challenging +// and requires that the averaging window be quite long. +unsigned int lwmaGetNextPOSRequired(const CBlockIndex* pindexLast, const Consensus::Params& params) +{ + arith_uint256 nextTarget {0}, sumTarget {0}, bnTmp, bnLimit; + bnLimit = UintToArith256(params.posLimit); + unsigned int nProofOfStakeLimit = bnLimit.GetCompact(); + + // Find the first block in the averaging interval as we total the linearly weighted average + // of POS solve times + const CBlockIndex* pindexFirst = pindexLast; + const CBlockIndex* pindexNext; + + int64_t t = 0, solvetime = 0, k = params.nLwmaPOSAjustedWeight, N = params.nPOSAveragingWindow; + + for (int i = 0, j = N - 1; pindexFirst && i < N; i++, j--) { + pindexNext = pindexFirst; + // we measure our solve time in passing of blocks, where one bock == VERUS_BLOCK_POSUNITS units + for (int x = 0; x < params.nPOSAveragingWindow; x++) + { + solvetime += VERUS_BLOCK_POSUNITS; + pindexFirst = pindexFirst->pprev; + // in this loop, unqualified blocks are assumed POS + if (!pindexFirst || !DoesHashQualify(pindexFirst)) + break; + } + if (!pindexFirst) + break; + + // weighted sum + t += solvetime * j; + + // Target sum divided by a factor, (k N^2). + // The factor is a part of the final equation. However we divide + // here to avoid potential overflow. + bnTmp.SetCompact(pindexNext->nBits); // TODO(miketout): this must be POS nBits + sumTarget += bnTmp / (k * N * N); + } + + // Check we have enough blocks + if (!pindexFirst) + return nProofOfStakeLimit; + + // Keep t reasonable in case strange solvetimes occurred. + if (t < N * k / 3) + t = N * k / 3; + + bnTmp = bnLimit; + nextTarget = t * sumTarget; + if (nextTarget > bnTmp) + nextTarget = bnTmp; + + return nextTarget.GetCompact(); +} + bool CheckEquihashSolution(const CBlockHeader *pblock, const CChainParams& params) { if (ASSETCHAINS_ALGO != ASSETCHAINS_EQUIHASH) @@ -184,7 +252,6 @@ bool CheckEquihashSolution(const CBlockHeader *pblock, const CChainParams& param int32_t komodo_chosennotary(int32_t *notaryidp,int32_t height,uint8_t *pubkey33,uint32_t timestamp); int32_t komodo_is_special(uint8_t pubkeys[66][33],int32_t mids[66],uint32_t blocktimes[66],int32_t height,uint8_t pubkey33[33],uint32_t blocktime); int32_t komodo_currentheight(); -CBlockIndex *komodo_chainactive(int32_t height); void komodo_index2pubkey33(uint8_t *pubkey33,CBlockIndex *pindex,int32_t height); extern int32_t KOMODO_CHOSEN_ONE; extern char ASSETCHAINS_SYMBOL[KOMODO_ASSETCHAIN_MAXLEN]; @@ -195,9 +262,10 @@ int32_t KOMODO_LOADINGBLOCKS = 1; extern std::string NOTARY_PUBKEY; -bool CheckProofOfWork(int32_t height,uint8_t *pubkey33,uint256 hash,unsigned int nBits,const Consensus::Params& params,uint32_t blocktime) +bool CheckProofOfWork(const CBlockHeader &blkHeader, uint8_t *pubkey33, int32_t height, const Consensus::Params& params) { extern int32_t KOMODO_REWIND; + uint256 hash; bool fNegative,fOverflow; uint8_t origpubkey33[33]; int32_t i,nonzpkeys=0,nonz=0,special=0,special2=0,notaryid=-1,flag = 0, mids[66]; uint32_t tiptime,blocktimes[66]; arith_uint256 bnTarget; uint8_t pubkeys[66][33]; //for (i=31; i>=0; i--) @@ -206,7 +274,7 @@ bool CheckProofOfWork(int32_t height,uint8_t *pubkey33,uint256 hash,unsigned int memcpy(origpubkey33,pubkey33,33); memset(blocktimes,0,sizeof(blocktimes)); tiptime = komodo_chainactive_timestamp(); - bnTarget.SetCompact(nBits, &fNegative, &fOverflow); + bnTarget.SetCompact(blkHeader.nBits, &fNegative, &fOverflow); if ( height == 0 ) { height = komodo_currentheight() + 1; @@ -226,7 +294,7 @@ bool CheckProofOfWork(int32_t height,uint8_t *pubkey33,uint256 hash,unsigned int return(true); // will come back via different path with pubkey set } flag = komodo_eligiblenotary(pubkeys,mids,blocktimes,&nonzpkeys,height); - special2 = komodo_is_special(pubkeys,mids,blocktimes,height,pubkey33,blocktime); + special2 = komodo_is_special(pubkeys,mids,blocktimes,height,pubkey33,blkHeader.nTime); if ( notaryid >= 0 ) { if ( height > 10000 && height < 80000 && (special != 0 || special2 > 0) ) @@ -253,11 +321,13 @@ bool CheckProofOfWork(int32_t height,uint8_t *pubkey33,uint256 hash,unsigned int arith_uint256 bnLimit = (height <= 1 || ASSETCHAINS_ALGO == ASSETCHAINS_EQUIHASH) ? UintToArith256(params.powLimit) : UintToArith256(params.powAlternate); if (fNegative || bnTarget == 0 || fOverflow || bnTarget > bnLimit) return error("CheckProofOfWork(): nBits below minimum work"); + // Check proof of work matches claimed amount - if ( UintToArith256(hash) > bnTarget ) + if ( UintToArith256(hash = blkHeader.GetHash()) > bnTarget && !blkHeader.isVerusPOSBlock() ) { if ( KOMODO_LOADINGBLOCKS != 0 ) return true; + if ( ASSETCHAINS_SYMBOL[0] != 0 || height > 792000 ) { //if ( 0 && height > 792000 ) @@ -294,6 +364,9 @@ arith_uint256 GetBlockProof(const CBlockIndex& block) bool fNegative; bool fOverflow; bnTarget.SetCompact(block.nBits, &fNegative, &fOverflow); + + // TODO(miketout): proof of stake blocks must be marked as having the minimum POW in this context + if (fNegative || fOverflow || bnTarget == 0) return 0; // We need to compute 2**256 / (bnTarget+1), but we can't represent 2**256 diff --git a/src/pow.h b/src/pow.h index 213fe228d..8221e8336 100644 --- a/src/pow.h +++ b/src/pow.h @@ -21,11 +21,13 @@ unsigned int CalculateNextWorkRequired(arith_uint256 bnAvg, int64_t nLastBlockTime, int64_t nFirstBlockTime, const Consensus::Params&); +unsigned int lwmaGetNextPOSRequired(const CBlockIndex* pindexLast, const Consensus::Params& params); + /** Check whether the Equihash solution in a block header is valid */ bool CheckEquihashSolution(const CBlockHeader *pblock, const CChainParams&); /** Check whether a block hash satisfies the proof-of-work requirement specified by nBits */ -bool CheckProofOfWork(int32_t height,uint8_t *pubkey33,uint256 hash, unsigned int nBits, const Consensus::Params&,uint32_t blocktime); +bool CheckProofOfWork(const CBlockHeader &blkHeader, uint8_t *pubkey33, int32_t height, const Consensus::Params& params); arith_uint256 GetBlockProof(const CBlockIndex& block); /** Return the time it would take to redo the work difference between from and to, assuming the current hashrate corresponds to the difficulty at tip, in seconds. */ diff --git a/src/primitives/block.h b/src/primitives/block.h index 7b5a06fe4..d43bd18d9 100644 --- a/src/primitives/block.h +++ b/src/primitives/block.h @@ -9,6 +9,7 @@ #include "primitives/transaction.h" #include "serialize.h" #include "uint256.h" +#include "arith_uint256.h" /** Nodes collect new transactions into a block, hash them into a hash tree, * and scan through nonce values to make the block's hash satisfy proof-of-work @@ -88,6 +89,41 @@ public: { return (int64_t)nTime; } + + int32_t GetVerusPOSTarget() const + { + uint32_t nBits = 0; + + for (const unsigned char *p = nNonce.begin() + 3; p >= nNonce.begin(); p--) + { + nBits += *p; + nBits <<= 8; + } + return nBits; + } + + bool isVerusPOSBlock() const + { + arith_uint256 arNonce = UintToArith256(nNonce); + arith_uint256 tmpNonce = ((arNonce << 128) >> 128); + CVerusHashWriter hashWriter = CVerusHashWriter(SER_GETHASH, PROTOCOL_VERSION); + hashWriter << ArithToUint256(tmpNonce); + return (nNonce == ArithToUint256(UintToArith256(hashWriter.GetHash()) << 128 | tmpNonce)); + } + + void SetVerusPOSTarget(int32_t nBits) + { + CVerusHashWriter hashWriter = CVerusHashWriter(SER_GETHASH, PROTOCOL_VERSION); + uint256 hash; + arith_uint256 tmpNonce; + + arith_uint256 arNonce = UintToArith256(nNonce); + arNonce = ((arNonce >> 32) << 32) | nBits; + + tmpNonce = ((arNonce << 128) >> 128); + hashWriter << ArithToUint256(tmpNonce); + nNonce = ArithToUint256(UintToArith256(hashWriter.GetHash()) << 128 | tmpNonce); + } }; @@ -108,7 +144,6 @@ public: CBlock(const CBlockHeader &header) { SetNull(); - *((CBlockHeader*)this) = header; } ADD_SERIALIZE_METHODS; diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index 112906061..41e6454f4 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -12,6 +12,7 @@ #include "serialize.h" #include "uint256.h" #include "consensus/consensus.h" +#include "hash.h" #ifndef __APPLE__ #include @@ -24,6 +25,8 @@ #include "zcash/JoinSplit.hpp" #include "zcash/Proof.hpp" +extern uint32_t ASSETCHAINS_MAGIC; + class JSDescription { public: @@ -469,6 +472,28 @@ public: return a.hash != b.hash; } + // verus hash will be the same for a given txid, output number, block height, and blockhash of 100 blocks past + static uint256 _GetVerusPOSHash(const uint256 &txid, int32_t voutNum, int32_t height, const uint256 &pastHash, int64_t value) + { + CVerusHashWriter hashWriter = CVerusHashWriter(SER_GETHASH, PROTOCOL_VERSION); + + hashWriter << ASSETCHAINS_MAGIC; + hashWriter << pastHash; + hashWriter << height; + hashWriter << txid; + hashWriter << voutNum; + return hashWriter.GetHash(); + } + + uint256 GetVerusPOSHash(int32_t voutNum, int32_t height, const uint256 &pastHash) const + { + uint256 txid = GetHash(); + if (voutNum >= vout.size()) + return uint256(); + + return _GetVerusPOSHash(txid, voutNum, height, pastHash, (uint64_t)vout[voutNum].nValue); + } + std::string ToString() const; }; diff --git a/src/rpcmining.cpp b/src/rpcmining.cpp index a419b614e..c9bbd967d 100644 --- a/src/rpcmining.cpp +++ b/src/rpcmining.cpp @@ -259,7 +259,7 @@ UniValue generate(const UniValue& params, bool fHelp) LOCK(cs_main); pblock->nSolution = soln; solutionTargetChecks.increment(); - return CheckProofOfWork(chainActive.Height(),NOTARY_PUBKEY33,pblock->GetHash(), pblock->nBits, Params().GetConsensus(),pblock->nTime); + return CheckProofOfWork(*pblock,NOTARY_PUBKEY33,chainActive.Height(),Params().GetConsensus()); }; bool found = EhBasicSolveUncancellable(n, k, curr_state, validBlock); ehSolverRuns.increment(); diff --git a/src/rpcmisc.cpp b/src/rpcmisc.cpp index 7155c69e7..bbc87871b 100644 --- a/src/rpcmisc.cpp +++ b/src/rpcmisc.cpp @@ -58,6 +58,7 @@ extern uint16_t ASSETCHAINS_P2PPORT,ASSETCHAINS_RPCPORT; extern uint32_t ASSETCHAINS_CC; extern uint32_t ASSETCHAINS_MAGIC; extern uint64_t ASSETCHAINS_COMMISSION,ASSETCHAINS_STAKED,ASSETCHAINS_SUPPLY,ASSETCHAINS_LASTERA; +extern int32_t ASSETCHAINS_LWMAPOS; extern uint64_t ASSETCHAINS_ENDSUBSIDY[],ASSETCHAINS_REWARD[],ASSETCHAINS_HALVING[],ASSETCHAINS_DECAY[]; UniValue getinfo(const UniValue& params, bool fHelp) @@ -202,6 +203,8 @@ UniValue getinfo(const UniValue& params, bool fHelp) obj.push_back(Pair("commission", ASSETCHAINS_COMMISSION)); if ( ASSETCHAINS_STAKED != 0 ) obj.push_back(Pair("staked", ASSETCHAINS_STAKED)); + if ( ASSETCHAINS_LWMAPOS != 0 ) + obj.push_back(Pair("verus proof of stake percent", ASSETCHAINS_LWMAPOS)); } return obj; } diff --git a/src/script/standard.cpp b/src/script/standard.cpp index 4eab398bb..207640504 100644 --- a/src/script/standard.cpp +++ b/src/script/standard.cpp @@ -222,10 +222,11 @@ bool IsStandard(const CScript& scriptPubKey, txnouttype& whichType) return whichType != TX_NONSTANDARD; } -bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet) +bool ExtractDestination(const CScript& _scriptPubKey, CTxDestination& addressRet) { vector vSolutions; txnouttype whichType; + CScript scriptPubKey = _scriptPubKey; // if this is a CLTV script, get the destination after CLTV if (scriptPubKey.IsCheckLockTimeVerify()) @@ -234,10 +235,7 @@ bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet) uint32_t scriptStart = pushOp + 3; // check post CLTV script - CScript postfix = CScript(scriptPubKey.size() > scriptStart ? scriptPubKey.begin() + scriptStart : scriptPubKey.end(), scriptPubKey.end()); - - // check again with only postfix subscript - return(ExtractDestination(postfix, addressRet)); + scriptPubKey = CScript(scriptPubKey.size() > scriptStart ? scriptPubKey.begin() + scriptStart : scriptPubKey.end(), scriptPubKey.end()); } if (!Solver(scriptPubKey, whichType, vSolutions)) diff --git a/src/txdb.cpp b/src/txdb.cpp index c856947ad..0651b920c 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -532,7 +532,7 @@ bool CBlockTreeDB::LoadBlockIndexGuts() { uint8_t pubkey33[33]; komodo_index2pubkey33(pubkey33,pindexNew,pindexNew->nHeight); - if (!CheckProofOfWork(pindexNew->nHeight,pubkey33,pindexNew->GetBlockHash(), pindexNew->nBits, Params().GetConsensus(),pindexNew->nTime)) + if (!CheckProofOfWork(header,pubkey33,pindexNew->nHeight,Params().GetConsensus())) return error("LoadBlockIndex(): CheckProofOfWork failed: %s", pindexNew->ToString()); } pcursor->Next(); diff --git a/src/wallet-utility.cpp b/src/wallet-utility.cpp index 3d3327139..6889d4cc1 100644 --- a/src/wallet-utility.cpp +++ b/src/wallet-utility.cpp @@ -19,6 +19,8 @@ uint32_t ASSETCHAINS_MAGIC = 2387029918; uint32_t ASSETCHAINS_EQUIHASH = 0; uint32_t ASSETCHAINS_VERUSHASH = 1; uint32_t ASSETCHAINS_ALGO = 0; +int32_t ASSETCHAINS_LWMAPOS = 0; +int32_t VERUS_BLOCK_POSUNITS = 1000; unsigned int MAX_BLOCK_SIGOPS = 20000; diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index f66a1e381..009684db6 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -4660,3 +4660,8 @@ int32_t komodo_staked(CPubKey &pubkey, CMutableTransaction &txNew,uint32_t nBits } return(siglen); } + +int32_t verus_staked(CPubKey &pubkey, CMutableTransaction &txNew, uint32_t &nBits, arith_uint256 &hashResult, uint8_t *utxosig) +{ + return pwalletMain->VerusStakeTransaction(pubkey, txNew, nBits, hashResult, utxosig); +} diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 0637d3fd4..02a31b916 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -43,6 +43,7 @@ bool fPayAtLeastCustomFee = true; extern int32_t KOMODO_EXCHANGEWALLET; extern char ASSETCHAINS_SYMBOL[KOMODO_ASSETCHAIN_MAXLEN]; +CBlockIndex *komodo_chainactive(int32_t height); /** * Fees smaller than this (in satoshi) are considered zero fee (for transaction creation) @@ -991,6 +992,105 @@ CWallet::TxItems CWallet::OrderedTxItems(std::list& acentries, return txOrdered; } +// looks through all wallet UTXOs and checks to see if any qualify to stake the block at the current height. it always returns the qualified +// UTXO with the smallest coin age if there is more than one, as larger coin age will win more often and is worth saving +// each attempt consists of taking a VerusHash of the following values: +// ASSETCHAINS_MAGIC, nHeight, txid, voutNum +bool CWallet::VerusSelectStakeOutput(arith_uint256 &hashResult, CTransaction &stakeSource, int32_t &voutNum, int32_t nHeight, const arith_uint256 &target) const +{ + arith_uint256 curHash; + vector vecOutputs; + COutput *pwinner = NULL; + CBlockIndex *pastBlockIndex; + + pwalletMain->AvailableCoins(vecOutputs, true, NULL, false, false); + + if (pastBlockIndex = komodo_chainactive(nHeight - COINBASE_MATURITY)) + { + uint256 pastHash = pastBlockIndex->GetBlockHash(); + + BOOST_FOREACH(COutput &txout, vecOutputs) + { + if ((curHash = UintToArith256(txout.tx->GetVerusPOSHash(txout.i, nHeight, pastHash)) / txout.tx->vout[txout.i].nValue) <= target && + txout.fSpendable) + { + // get the smallest winner + if (!pwinner || pwinner->tx->vout[pwinner->i].nValue > txout.tx->vout[txout.i].nValue) + pwinner = &txout; + } + } + if (pwinner) + { + stakeSource = *(pwinner->tx); + voutNum = pwinner->i; + return true; + } + } + return false; +} + +int32_t CWallet::VerusStakeTransaction(CPubKey &pubkey, CMutableTransaction &txNew, uint32_t &bnTarget, arith_uint256 &hashResult, uint8_t *utxosig) const +{ + arith_uint256 target; + CTransaction stakeSource; + int32_t voutNum, siglen = 0; + int64_t nValue; + + CBlockIndex *tipindex = chainActive.Tip(); + bnTarget = lwmaGetNextPOSRequired(tipindex, Params().GetConsensus()); + target.SetCompact(bnTarget); + + if (!VerusSelectStakeOutput(hashResult, stakeSource, voutNum, tipindex->nHeight, target)) + { + return 0; + } + + // komodo create transaction code below this line + bool signSuccess; + SignatureData sigdata; + uint64_t txfee; + auto consensusBranchId = CurrentEpochBranchId(chainActive.Height() + 1, Params().GetConsensus()); + + const CKeyStore& keystore = *pwalletMain; + txNew.vin.resize(1); + txNew.vout.resize(1); + txfee = 0; + txNew.vin[0].prevout.hash = stakeSource.GetHash(); + txNew.vin[0].prevout.n = voutNum; + + /* + uint8_t *script; + int32_t i; + uint8_t *ptr; + + txNew.vout[0].scriptPubKey.resize(35); + ptr = (uint8_t *)pubkey.begin(); + script = (uint8_t *)(txNew.vout[0].scriptPubKey.data()); + script[0] = 33; + for (i=0; i<33; i++) + script[i+1] = ptr[i]; + script[34] = OP_CHECKSIG; + */ + txNew.vout[0].scriptPubKey << ToByteVector(pubkey) << OP_CHECKSIG; + + nValue = txNew.vout[0].nValue = voutNum - txfee; + txNew.nLockTime = 0; + CTransaction txNewConst(txNew); + signSuccess = ProduceSignature(TransactionSignatureCreator(&keystore, &txNewConst, 0, nValue, SIGHASH_ALL), stakeSource.vout[voutNum].scriptPubKey, sigdata, consensusBranchId); + if (!signSuccess) + fprintf(stderr,"failed to create signature\n"); + else + { + uint8_t *ptr; + UpdateTransaction(txNew,0,sigdata); + ptr = (uint8_t *)sigdata.scriptSig.data(); + siglen = sigdata.scriptSig.size(); + for (int i=0; i