diff --git a/src/bitcoinrpc.cpp b/src/bitcoinrpc.cpp index 97b662895..2cdd2adc6 100644 --- a/src/bitcoinrpc.cpp +++ b/src/bitcoinrpc.cpp @@ -1240,7 +1240,7 @@ Array RPCConvertValues(const std::string &strMethod, const std::vector 1) ConvertTo(params[1]); if (strMethod == "createproposal" && n > 2) ConvertTo(params[2]); if (strMethod == "createproposal" && n > 3) ConvertTo(params[3]); - if (strMethod == "createproposal" && n > 4) ConvertTo(params[4]); + if (strMethod == "createproposal" && n > 5) ConvertTo(params[5]); if (strMethod == "setvote" && n > 1) ConvertTo(params[1]); return params; } diff --git a/src/main.cpp b/src/main.cpp index 1bd9460d3..88d029dae 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -333,11 +333,19 @@ bool CTransaction::IsProposal() const for (unsigned int i = 0; i < vout.size(); i++) { CScript scriptPubKey = vout[i].scriptPubKey; if (scriptPubKey.IsDataCarrier()) { - if (scriptPubKey.size() >= 5) { + if (scriptPubKey.size() > 0x4c) { + // "PROP" in ascii + if (scriptPubKey.at(3) == 0x70 && scriptPubKey.at(4) == 0x72 && scriptPubKey.at(5) == 0x6f && + scriptPubKey.at(6) == 0x70) { + return true; + } + } + else if(scriptPubKey.size() > 6) { // "PROP" in ascii if (scriptPubKey.at(2) == 0x70 && scriptPubKey.at(3) == 0x72 && scriptPubKey.at(4) == 0x6f && - scriptPubKey.at(5) == 0x70) + scriptPubKey.at(5) == 0x70) { return true; + } } } } @@ -1468,7 +1476,9 @@ bool CTransaction::ConnectInputs(CTxDB& txdb, MapPrevTx inputs, uint64 nCoinAge; if (!GetCoinAge(txdb, nCoinAge)) return error("ConnectInputs() : %s unable to get coin age for coinstake", GetHash().ToString().substr(0,10).c_str()); + int64 nStakeReward = GetValueOut() - nValueIn; + if (nStakeReward > GetProofOfStakeReward(nCoinAge, pindexBlock->nBits, nTime, pindexBlock->nHeight) - GetMinFee() + MIN_TX_FEE) return DoS(100, error("ConnectInputs() : %s stake reward exceeded", GetHash().ToString().substr(0,10).c_str())); } @@ -1491,6 +1501,21 @@ bool CTransaction::ConnectInputs(CTxDB& txdb, MapPrevTx inputs, } } + // If the given transaction is coinbase then check for correct refund outputs + else if (pindexBlock->nHeight >= VOTING_START){ + CBlock block; + if (!block.ReadFromDisk(pindexBlock)) + return error("ConnectInputs() : ReadFromDisk for connect failed"); + + vector vtxProposals; + if (!proposalManager.GetDeterministicOrdering(pindexBlock->hashProofOfStake, block.vtx, vtxProposals)) + return error("ConnectInputs() : Fetching deterministic ordering of tx proposals failed"); + + if (!proposalManager.CheckRefundTransaction(vtxProposals, *this)) { + return error("ConnectInputs() : Invalid refund outputs in coinbase transaction"); + } + } + return true; } @@ -1597,7 +1622,7 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck) else nTxPos = pindex->nBlockPos + ::GetSerializeSize(CBlock(), SER_DISK, CLIENT_VERSION) - (2 * GetSizeOfCompactSize(0)) + GetSizeOfCompactSize(vtx.size()); - vector vQueuedProposals; + vector vQueuedTxProposals; map mapQueuedChanges; int64 nFees = 0; int64 nValueIn = 0; @@ -1654,9 +1679,12 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck) //Track vote proposals if (tx.IsProposal()) { - //Needs to have the proper fee or else it will not be counted - if (nTxValueIn - nTxValueOut >= CVoteProposal::FEE - MIN_TXOUT_AMOUNT) - vQueuedProposals.push_back(hashTx); + CVoteProposal proposal; + if(!ProposalFromTransaction(tx, proposal)) { + return error("Proposal was not successfully extracted from transaction. This shouldn't happen."); + } + + vQueuedTxProposals.push_back(tx); } } @@ -1675,21 +1703,34 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck) if (fJustCheck) return true; + //TODO: LOG ERRORS OR TERMINATE METHOD AND RETURN ERROR? + vector vOrderedTxProposals; + if(!proposalManager.GetDeterministicOrdering(pindex->pprev->hashProofOfStake, vQueuedTxProposals, vOrderedTxProposals)) { + printf("ConnectBlock() : encountered error when determining deterministic ordering of proposals."); + } + + vector vAcceptedTxProposals; + if(!proposalManager.GetAcceptedTxProposals(vtx[0], vOrderedTxProposals, vAcceptedTxProposals)) { + printf("ConnectBlock() : encountered error when extracting accepted proposals from coinbase"); + } + // Keep track of any vote proposals that were added to the blockchain CVoteDB voteDB; - if (vQueuedProposals.size()) { - for (const CTransaction& tx : vtx) { - uint256 txid = tx.GetHash(); - if (count(vQueuedProposals.begin(), vQueuedProposals.end(), txid)) { - CVoteProposal proposal; - if (ProposalFromTransaction(tx, proposal)) { - mapProposals[txid] = proposal.GetHash(); - if (!voteDB.WriteProposal(txid, proposal)) - printf("%s : failed to record proposal to db\n", __func__); - else if (!proposalManager.Add(proposal)) - printf("%s: failed to add proposal %s to manager\n", __func__, txid.GetHex().c_str()); - } - } + for (const CTransaction& txProposal: vAcceptedTxProposals) { + + // if the proposal isn't able to be extracted from transaction then skip it + CVoteProposal proposal; + if(!ProposalFromTransaction(txProposal, proposal)) { + printf("Proposal was not successfully extracted from transaction. This shouldn't happen."); + continue; + } + + // store proposal hash in mapProposals and store proposal on disk for later use + mapProposals[txProposal.GetHash()] = proposal.GetHash(); + if (!voteDB.WriteProposal(txProposal.GetHash(), proposal)) { + printf("%s : failed to record proposal to db\n", __func__); + } else if (!proposalManager.Add(proposal)) { + printf("%s: failed to add proposal %s to manager\n", __func__, txProposal.GetHash().GetHex().c_str()); } } diff --git a/src/miner.cpp b/src/miner.cpp index 5ee773e54..083f0a4a9 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -405,6 +405,57 @@ CBlock* CreateNewBlock(CWallet* pwallet, bool fProofOfStake) } } + // TODO: ADD BLOCK HEIGHT FOR VOTING HARD FORK + // TODO: MIGHT NEED TO CHECK BLOCK SIZE CONSTRAINTS + // TODO: PUT THIS IN ANOTHER METHOD TO IMPROVE MODULARIZATION AND CODE REUSE + if(pindexPrev->nHeight >= VOTING_START) { + std::vector vProposalTransactions; + for(CTransaction tx: pblock->vtx) { + if(tx.IsProposal()) { + vProposalTransactions.emplace_back(tx); + } + } + + std::vector vOrderedProposalTransactions; + proposalManager.GetDeterministicOrdering(pindexPrev->hashProofOfStake, vProposalTransactions, + vOrderedProposalTransactions); + for (CTransaction txProposal: vOrderedProposalTransactions) { + // output variables + int nRequiredFee; + CVoteProposal proposal; + VoteLocation location; + CTransaction txCoinBase = pblock->vtx[0]; + + // Skip this txProposal if a proposal object cannot be extracted from it + if (!ProposalFromTransaction(txProposal, proposal)) { + continue; + } + + // input variables + int nTxFee = CVoteProposal::BASE_FEE; //TODO: DETERMINE THE BEST VALUE FROM TRIALS + int nBitCount = proposal.GetBitCount(); + int nStartHeight = proposal.GetStartHeight(); + int nCheckSpan = proposal.GetCheckSpan(); + + // If a valid voting location cannot be found then create an unaccepted proposal refund + if (!proposalManager.GetNextLocation(nBitCount, nStartHeight, nCheckSpan, location)) { + proposalManager.AddRefundToCoinBase(proposal, nRequiredFee, nTxFee, false, txCoinBase); + } else { + // If a fee cannot be calculated then skip this proposal without creating a refund tx + proposal.SetLocation(location); + if (!proposalManager.GetFee(proposal, nRequiredFee)) continue; + + // If the maximum fee provided by the proposal creator is less than the required fee + // then create an unaccepted proposal refund + if (nRequiredFee > proposal.GetMaxFee()) { + proposalManager.AddRefundToCoinBase(proposal, nRequiredFee, nTxFee, false, txCoinBase); + } else { + proposalManager.AddRefundToCoinBase(proposal, nRequiredFee, nTxFee, true, txCoinBase); + } + } + } + } + nLastBlockTx = nBlockTx; nLastBlockSize = nBlockSize; diff --git a/src/qt/createproposaldialog.cpp b/src/qt/createproposaldialog.cpp index 0f0fde106..a4219234e 100644 --- a/src/qt/createproposaldialog.cpp +++ b/src/qt/createproposaldialog.cpp @@ -5,6 +5,7 @@ #include "walletmodel.h" #include "voteproposal.h" #include "voteobject.h" +#include "base58.h" #include #include @@ -61,23 +62,38 @@ void CreateProposalDialog::on_button_CreateProposal_clicked() return; } + int nMaxFee = ui->lineEdit_Max_Fee->text().toInt(); + if (nMaxFee < CVoteProposal::BASE_FEE) { + QMessageBox msg; + msg.setText(tr("Max Fee must be greater than or equal to %1").arg(CVoteProposal::BASE_FEE)); + msg.exec(); + return; + } + + std::string strRefundAddress = ui->lineEdit_Refund_Address->text().toStdString(); + CBitcoinAddress address; + if (strRefundAddress.empty() || !address.SetString(strRefundAddress)) { + QMessageBox msg; + msg.setText(tr("The provided refund address is invalid")); + msg.exec(); + return; + } + //Right now only supporting 2 bit votes int nBitCount = 2; QString strSize = QString::number(nBitCount); ui->label_Size_result->setText(strSize); - //Set bit location in dialog VoteLocation location; - if (!proposalManager.GetNextLocation(nBitCount, nStartHeight, nCheckSpan, location)) { + if(!proposalManager.GetNextLocation(nBitCount, nStartHeight, nCheckSpan, location)) { QMessageBox msg; - msg.setText(tr("Failed to get next location from the proposal manager")); + msg.setText(tr("The specified voting span is already full. Try a different start and span.")); msg.exec(); return; } - ui->label_Location_result->setText(QString::number(location.nLeastSignificantBit)); //Create the actual proposal - this->proposal = new CVoteProposal(strName.toStdString(), nStartHeight, nCheckSpan, strAbstract.toStdString(), location); + this->proposal = new CVoteProposal(strName.toStdString(), nStartHeight, nCheckSpan, strAbstract.toStdString(), nMaxFee, strRefundAddress); //Set proposal hash in dialog uint256 hashProposal = proposal->GetHash(); @@ -112,9 +128,6 @@ void CreateProposalDialog::Clear() ui->lineEdit_Length->clear(); ui->lineEdit_Name->clear(); ui->lineEdit_StartBlock->clear(); - ui->label_Fee_result->setText(QString::fromStdString(FormatMoney(CVoteProposal::FEE))); ui->label_Hash_result->setText("(Automatically Generated)"); - ui->label_Location_result->setText("(Automatically Generated)"); - ui->label_Location_result->setText("(Automatically Generated)"); ui->button_SendProposal->setEnabled(false); } diff --git a/src/qt/forms/createproposaldialog.ui b/src/qt/forms/createproposaldialog.ui index eb5ae1b68..8be5e5780 100644 --- a/src/qt/forms/createproposaldialog.ui +++ b/src/qt/forms/createproposaldialog.ui @@ -57,60 +57,52 @@ - + - Proposal Hash: + Max Fee: - - - (Automatically Generated) - - + - - + + - Bit Location: + Proposal Hash: - - + + (Automatically Generated) - + Bit Size: - + (Automatically Generated) - - + + - Fee: + Refund Address: - - - - (Automatically Generated) - - + + diff --git a/src/rpcblockchain.cpp b/src/rpcblockchain.cpp index 86f1213a8..a9d861082 100644 --- a/src/rpcblockchain.cpp +++ b/src/rpcblockchain.cpp @@ -309,26 +309,32 @@ Value createproposal(const Array& params, bool fHelp) { if (fHelp || params.size() != 6) throw runtime_error( - "createproposal \n\n\n\n\n\n\n" + "createproposal \n\n\n\n\n\n\n vchSig; - if (!key.Sign(hash, vchSig)) + if (!key.Sign(hash, vchSig)) { return false; + } vchSig.push_back((unsigned char)nHashType); scriptSigRet << vchSig; @@ -1393,7 +1395,7 @@ bool Solver(const CKeyStore& keystore, const CScript& scriptPubKey, uint256 hash scriptSigRet << OP_0; // workaround CHECKMULTISIG bug return (SignN(vSolutions, keystore, hash, nHashType, scriptSigRet)); case TX_NULL_DATA: - return true; + return true; } return false; } @@ -1613,8 +1615,9 @@ bool SignSignature(const CKeyStore &keystore, const CScript& fromPubKey, CTransa uint256 hash = SignatureHash(fromPubKey, txTo, nIn, nHashType); txnouttype whichType; - if (!Solver(keystore, fromPubKey, hash, nHashType, txin.scriptSig, whichType)) + if (!Solver(keystore, fromPubKey, hash, nHashType, txin.scriptSig, whichType)) { return false; + } if (whichType == TX_SCRIPTHASH) { diff --git a/src/test/voting_tests.cpp b/src/test/voting_tests.cpp index 1291dfe50..1c4ce5551 100644 --- a/src/test/voting_tests.cpp +++ b/src/test/voting_tests.cpp @@ -1,10 +1,10 @@ #include #include #include -#include "json/json_spirit_writer_template.h" +#include "../json/json_spirit_writer_template.h" -#include "main.h" -#include "wallet.h" +#include "../main.h" +#include "../wallet.h" #include "../voteproposal.h" #include "../voteobject.h" #include "../votetally.h" @@ -16,57 +16,68 @@ using namespace std; BOOST_AUTO_TEST_SUITE(voting_tests) +std::string CONSTRUCT_ERROR = "failed to construct tx"; +std::string NOT_PROPOSAL_ERROR = "Transaction is not a proposal"; +std::string COULD_NOT_DESERIALIZE_ERROR = "Failed to deserialize"; +std::string BIT_LOCATION_ERROR = "location of proposal after it is deserialized isn't equal to its original value"; +std::string BIT_COUNT_ERROR = "bitcount of proposal after it is deserialized isn't equal to its original value"; +std::string SHIFT_ERROR = "shift of proposal after it is deserialized isn't equal to its original value"; +std::string START_HEIGHT_ERROR = "start height of proposal after it is deserialized isn't equal to its original value"; +std::string SPAN_ERROR = "check span of proposal after it is deserialized isn't equal to its original value"; +std::string DESCRIPTION_ERROR = "description of proposal after it is deserialized isn't equal to its original value"; +std::string NAME_ERROR = "name of proposal after it is deserialized isn't equal to its original value"; +std::string HASH_ERROR = "hash of proposal after it is deserialized isn't equal to its original value"; +std::string REFUND_ADDRESS_ERROR = "refund address is not equal to its original value after deserialization"; +std::string MAX_FEE_ERROR = "the max fee of the proposal after it is deserialized isn't equal to its original value"; +std::string DIFFERENT_ORDER_ERROR = "the deterministic ordering should be the same for both vectors but isn't"; +std::string SAME_ORDER_ERROR = "the deterministic orderings of the same vector for two different proofhashes are the same"; +std::string CREATE_ORDER_ERROR = "failed to construct deterministic ordering of proposals"; +std::string REFUND_OUT_ERROR = "the refund outputs in the coinbase tx were not constructed correctly"; + // name of issue -std::string strName = "proposal1"; +std::string strName = "ppppppppp"; // check version for existing proposals Shift uint8_t nShift = 20; // start time - will be changed to int StartHeight. unix time stamp -int64 nStartTime = 10000000; +int64 nStartHeight = 1; // number of blocks with votes to count int nCheckSpan = 1000; // cardinal items to vote on - convert to uint8 CheckSpan uint8_t nCardinals = 2; // description of issue - will go in different tx -std::string strDescription = "test_description"; +std::string strDescription = "ppppppppppppppppppppppppppp"; BOOST_AUTO_TEST_CASE(proposal_serialization) { std::cout << "testing proposal serialization\n"; CVoteProposalManager manager; - VoteLocation location; - manager.GetNextLocation(nCardinals, nStartTime, nCheckSpan, location); - CVoteProposal proposal(strName, nStartTime, nCheckSpan, strDescription, location); + manager.GetNextLocation(nCardinals, nStartHeight, nCheckSpan, location); + CVoteProposal proposal(strName, nStartHeight, nCheckSpan, strDescription, 5000000, "kxpSiwFkHJdNnYFojNDAvP3iiYyw3GH6ZL"); manager.Add(proposal); //! Add the constructed proposal to a partial transaction CTransaction tx; - BOOST_CHECK_MESSAGE(proposal.ConstructTransaction(tx), "failed to construct tx"); + BOOST_CHECK_MESSAGE(proposal.ConstructTransaction(tx), CONSTRUCT_ERROR); - BOOST_CHECK_MESSAGE(tx.IsProposal(), "Transaction is not a proposal!"); + BOOST_CHECK_MESSAGE(tx.IsProposal(), NOT_PROPOSAL_ERROR); CVoteProposal proposal2; - BOOST_CHECK_MESSAGE(ProposalFromTransaction(tx, proposal2), "Failed to deserialize"); - - //CHECKS TO DETERMINE IF DESERIALIZATION WORKS AS INTENDED - BOOST_CHECK_MESSAGE(proposal2.GetHash() == proposal.GetHash(), "hash of proposal after it is deserialized isn't equal" - "to its original value"); - BOOST_CHECK_MESSAGE(proposal2.GetLocation().nLeastSignificantBit == proposal.GetLocation().nLeastSignificantBit - && proposal2.GetLocation().nMostSignificantBit == proposal.GetLocation().nMostSignificantBit, - "location of proposal after it is deserialized isn't equal to its original value"); - BOOST_CHECK_MESSAGE(proposal2.GetBitCount() == proposal.GetBitCount(), "bitcount of proposal after it is deserialized isn't equal" - "to its original value"); - BOOST_CHECK_MESSAGE(proposal2.GetShift() == proposal.GetShift(), "shift of proposal after it is deserialized isn't equal" - "to its original value"); - BOOST_CHECK_MESSAGE(proposal2.GetStartHeight() == proposal.GetStartHeight(), "start height of proposal after it is deserialized isn't equal" - "to its original value"); - BOOST_CHECK_MESSAGE(proposal2.GetCheckSpan() == proposal.GetCheckSpan(), "check span of proposal after it is deserialized isn't equal" - "to its original value"); - BOOST_CHECK_MESSAGE(proposal2.GetDescription() == proposal.GetDescription(), "description of proposal after it is deserialized isn't equal" - "to its original value"); - BOOST_CHECK_MESSAGE(proposal2.GetName() == proposal.GetName(), "name of proposal after it is deserialized isn't equal" - "to its original value"); + BOOST_CHECK_MESSAGE(ProposalFromTransaction(tx, proposal2), COULD_NOT_DESERIALIZE_ERROR); + + //////////////////////////////////////////////////////////// + //CHECKS TO DETERMINE IF DESERIALIZATION WORKS AS INTENDED// + //////////////////////////////////////////////////////////// + BOOST_CHECK_MESSAGE(proposal2.GetHash() == proposal.GetHash(), HASH_ERROR); + BOOST_CHECK_MESSAGE(proposal2.GetRefundAddress() == proposal.GetRefundAddress(), REFUND_ADDRESS_ERROR); + BOOST_CHECK_MESSAGE(proposal2.GetMaxFee() == proposal.GetMaxFee(), MAX_FEE_ERROR); + BOOST_CHECK_MESSAGE(proposal2.GetBitCount() == proposal.GetBitCount(), BIT_COUNT_ERROR); + BOOST_CHECK_MESSAGE(proposal2.GetShift() == proposal.GetShift(), SHIFT_ERROR); + BOOST_CHECK_MESSAGE(proposal2.GetStartHeight() == proposal.GetStartHeight(), START_HEIGHT_ERROR); + BOOST_CHECK_MESSAGE(proposal2.GetCheckSpan() == proposal.GetCheckSpan(), SPAN_ERROR); + BOOST_CHECK_MESSAGE(proposal2.GetDescription() == proposal.GetDescription(), DESCRIPTION_ERROR); + BOOST_CHECK_MESSAGE(proposal2.GetName() == proposal.GetName(), NAME_ERROR); //! Create a tx that can be used as an input in the actual proposal tx CTransaction txFunding; @@ -162,28 +173,28 @@ BOOST_AUTO_TEST_CASE(vote_charset) CVoteProposalManager manager; VoteLocation location1; - manager.GetNextLocation(nCardinals, nStartTime, nCheckSpan, location1); - CVoteProposal proposal1(strName, nStartTime, nCheckSpan, strDescription, location1); + manager.GetNextLocation(nCardinals, nStartHeight, nCheckSpan, location1); + CVoteProposal proposal1(strName, nStartHeight, nCheckSpan, strDescription, location1); manager.Add(proposal1); VoteLocation location2; - manager.GetNextLocation(nCardinals, nStartTime, nCheckSpan, location2); - CVoteProposal proposal2(strName, nStartTime, nCheckSpan, strDescription, location2); + manager.GetNextLocation(nCardinals, nStartHeight, nCheckSpan, location2); + CVoteProposal proposal2(strName, nStartHeight, nCheckSpan, strDescription, location2); manager.Add(proposal2); VoteLocation location3; - manager.GetNextLocation(nCardinals, nStartTime, nCheckSpan, location3); - CVoteProposal proposal3(strName, nStartTime, nCheckSpan, strDescription, location3); + manager.GetNextLocation(nCardinals, nStartHeight, nCheckSpan, location3); + CVoteProposal proposal3(strName, nStartHeight, nCheckSpan, strDescription, location3); manager.Add(proposal3); VoteLocation location4; - manager.GetNextLocation(nCardinals, nStartTime, nCheckSpan, location4); - CVoteProposal proposal4(strName, nStartTime, nCheckSpan, strDescription, location4); + manager.GetNextLocation(nCardinals, nStartHeight, nCheckSpan, location4); + CVoteProposal proposal4(strName, nStartHeight, nCheckSpan, strDescription, location4); manager.Add(proposal4); VoteLocation location5; - manager.GetNextLocation(nCardinals, nStartTime, nCheckSpan, location5); - CVoteProposal proposal5(strName, nStartTime, nCheckSpan, strDescription, location5); + manager.GetNextLocation(nCardinals, nStartHeight, nCheckSpan, location5); + CVoteProposal proposal5(strName, nStartHeight, nCheckSpan, strDescription, location5); manager.Add(proposal5); uint32_t nVersion = 0x50000000; @@ -220,4 +231,152 @@ BOOST_AUTO_TEST_CASE(vote_charset) BOOST_CHECK_MESSAGE(nVersion == correctResult, "votes were not combined correctly"); } +BOOST_AUTO_TEST_CASE(vote_deterministic_ordering) { + + std::cout << "testing deterministic ordering of proposal transactions" << endl; + + CVoteProposalManager manager; + + std::string strName = "prop"; + std::string strDescription = "this is the description"; + std::string strRefundAddress = "kxpSiwFkHJdNnYFojNDAvP3iiYyw3GH6ZL"; + int nMaxFee = 5000000; + + CVoteProposal proposal1(strName, nStartHeight, nCheckSpan, strDescription + "1", nMaxFee, strRefundAddress); + CVoteProposal proposal2(strName, nStartHeight, nCheckSpan, strDescription + "2", nMaxFee, strRefundAddress); + CVoteProposal proposal3(strName, nStartHeight, nCheckSpan, strDescription + "3", nMaxFee, strRefundAddress); + CVoteProposal proposal4(strName, nStartHeight, nCheckSpan, strDescription + "4", nMaxFee, strRefundAddress); + CVoteProposal proposal5(strName, nStartHeight, nCheckSpan, strDescription + "5", nMaxFee, strRefundAddress); + + CTransaction txProposal1; + BOOST_CHECK_MESSAGE(proposal1.ConstructTransaction(txProposal1), CONSTRUCT_ERROR); + + CTransaction txProposal2; + BOOST_CHECK_MESSAGE(proposal2.ConstructTransaction(txProposal2), CONSTRUCT_ERROR); + + CTransaction txProposal3; + BOOST_CHECK_MESSAGE(proposal3.ConstructTransaction(txProposal3), CONSTRUCT_ERROR); + + CTransaction txProposal4; + BOOST_CHECK_MESSAGE(proposal4.ConstructTransaction(txProposal4), CONSTRUCT_ERROR); + + CTransaction txProposal5; + BOOST_CHECK_MESSAGE(proposal5.ConstructTransaction(txProposal5), CONSTRUCT_ERROR); + + vector vTxProposals = {txProposal1, txProposal2, txProposal3, txProposal4, txProposal5}; + + vector vOrderedTxProposals1; + BOOST_CHECK_MESSAGE(proposalManager.GetDeterministicOrdering(uint256("0xba04fd9cd0e9487f1a713e80828055284c04e362167ceb5f75db70153c613736"), + vTxProposals, vOrderedTxProposals1), CREATE_ORDER_ERROR); + + vector vOrderedTxProposals2; + BOOST_CHECK_MESSAGE(proposalManager.GetDeterministicOrdering(uint256("0xba04fd9cd0e9487f1a713e80828055284c04e362167ceb5f75db70153c613736"), + vTxProposals, vOrderedTxProposals2), CREATE_ORDER_ERROR); + + // verify that both orderings of tx proposals are the same + for(int i = 0; i < vTxProposals.size(); i++) { + BOOST_CHECK_MESSAGE(vOrderedTxProposals1.at(i).GetHash() == vOrderedTxProposals2.at(i).GetHash(), + DIFFERENT_ORDER_ERROR); + } + + vector vDifferentOrderedTxProposals; + BOOST_CHECK_MESSAGE(proposalManager.GetDeterministicOrdering(uint256("0xd7ec1705209f0212de02409cea9a9ca565571ae2977241f690dac11a7889d3f4"), + vTxProposals, vDifferentOrderedTxProposals), CREATE_ORDER_ERROR); + + // verify that the ordering of the tx proposals is different for a new proof hash + bool bSameOrder = true; + for(int i = 0; i < vTxProposals.size(); i++) { + bSameOrder = bSameOrder && (vOrderedTxProposals1.at(i).GetHash() == vDifferentOrderedTxProposals.at(i).GetHash()); + } + + BOOST_CHECK_MESSAGE(!bSameOrder, SAME_ORDER_ERROR); +} + +BOOST_AUTO_TEST_CASE(vote_refund_process) { + std::cout << "testing refund process for proposal requests" << endl; + + CVoteProposalManager manager; + + std::string strName = "prop"; + std::string strDescription = "this is the description"; + std::string strRefundAddress = "kxpSiwFkHJdNnYFojNDAvP3iiYyw3GH6ZL"; + int nMaxFee1 = 5000000; + int nMaxFee2 = 938049809; + int nMaxFee3 = 3; + int nMaxFee4 = 1000000000; + int nMaxFee5 = 6000000; + + CTransaction txCoinBase; + txCoinBase.vin.resize(1); + txCoinBase.vin[0].prevout.SetNull(); + txCoinBase.vout.resize(1); + + CVoteProposal proposal1(strName, nStartHeight, nCheckSpan, strDescription, nMaxFee1, strRefundAddress); + CVoteProposal proposal2(strName, nStartHeight, nCheckSpan, strDescription, nMaxFee2, strRefundAddress); + CVoteProposal proposal3(strName, nStartHeight, nCheckSpan, strDescription, nMaxFee3, strRefundAddress); + CVoteProposal proposal4(strName, nStartHeight, nCheckSpan, strDescription, nMaxFee4, strRefundAddress); + CVoteProposal proposal5(strName, nStartHeight, nCheckSpan, strDescription, nMaxFee5, strRefundAddress); + + CTransaction txProposal1; + BOOST_CHECK_MESSAGE(proposal1.ConstructTransaction(txProposal1), CONSTRUCT_ERROR); + + CTransaction txProposal2; + BOOST_CHECK_MESSAGE(proposal2.ConstructTransaction(txProposal2), CONSTRUCT_ERROR); + + CTransaction txProposal3; + BOOST_CHECK_MESSAGE(proposal3.ConstructTransaction(txProposal3), CONSTRUCT_ERROR); + + CTransaction txProposal4; + BOOST_CHECK_MESSAGE(proposal4.ConstructTransaction(txProposal4), CONSTRUCT_ERROR); + + CTransaction txProposal5; + BOOST_CHECK_MESSAGE(proposal5.ConstructTransaction(txProposal5), CONSTRUCT_ERROR); + + vector vTxProposals = {txProposal1, txProposal2, txProposal3, txProposal4, txProposal5}; + + vector vOrderedTxProposals; + BOOST_CHECK_MESSAGE(proposalManager.GetDeterministicOrdering(uint256("0xba04fd9cd0e9487f1a713e80828055284c04e362167ceb5f75db70153c613736"), + vTxProposals, vOrderedTxProposals), CREATE_ORDER_ERROR); + + for (CTransaction txProposal: vOrderedTxProposals) { + // output variables + int nRequiredFee; + CVoteProposal proposal; + VoteLocation location; + + // Skip this txProposal if a proposal object cannot be extracted from it + BOOST_CHECK_MESSAGE(ProposalFromTransaction(txProposal, proposal), CONSTRUCT_ERROR); + proposalManager.Add(proposal); + + // input variables + int nTxFee = CVoteProposal::BASE_FEE; + int nBitCount = proposal.GetBitCount(); + int nStartHeight = proposal.GetStartHeight(); + int nCheckSpan = proposal.GetCheckSpan(); + + // If a valid voting location cannot be found then create an unaccepted proposal refund + if (!proposalManager.GetNextLocation(nBitCount, nStartHeight, nCheckSpan, location)) { + proposalManager.AddRefundToCoinBase(proposal, nRequiredFee, nTxFee, false, txCoinBase); + } else { + BOOST_CHECK(true); + // If a fee cannot be calculated then skip this proposal without creating a refund tx + proposal.SetLocation(location); + BOOST_CHECK_MESSAGE(proposalManager.GetFee(proposal, nRequiredFee), "could not calculate fee."); + + BOOST_CHECK(true); + // If the maximum fee provided by the proposal creator is less than the required fee + // then create an unaccepted proposal refund + if (nRequiredFee > proposal.GetMaxFee()) { + BOOST_CHECK(true); + proposalManager.AddRefundToCoinBase(proposal, nRequiredFee, nTxFee, false, txCoinBase); + } else { + BOOST_CHECK(true); + proposalManager.AddRefundToCoinBase(proposal, nRequiredFee, nTxFee, true, txCoinBase); + } + } + } + + BOOST_CHECK_MESSAGE(proposalManager.CheckRefundTransaction(vOrderedTxProposals, txCoinBase), REFUND_OUT_ERROR); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/voteproposal.cpp b/src/voteproposal.cpp index 6ff2e2ee0..2dab43cb8 100644 --- a/src/voteproposal.cpp +++ b/src/voteproposal.cpp @@ -11,6 +11,23 @@ uint256 CVoteProposal::GetHash() const return SerializeHash(*this); } +bool CVoteProposal::IsValid() const +{ + if (strName.empty() || strName.size() > MAX_CHAR_NAME) { + return error("Name needs to be between 1 and %d characters long", MAX_CHAR_NAME); + } + + if (strDescription.empty() || strDescription.size() > MAX_CHAR_ABSTRACT) { + return error("Abstract needs to be between 1 and %d characters long", MAX_CHAR_ABSTRACT); + } + + if (!nCheckSpan || nCheckSpan > MAX_CHECKSPAN) { + return error("Voting length needs to be between 1 and %d blocks", MAX_CHECKSPAN); + } + + return true; +} + /** * The vote proposal is serialized and added to a CTransaction as a data object via OP_RETURN transaction output. * The transaction is marked as a proposal by marking the first 4 bytes as "PROP" in ASCII @@ -63,7 +80,9 @@ bool ProposalFromTransaction(const CTransaction& tx, CVoteProposal& proposal) vector vchProposal; CScript scriptProposal = tx.vout[0].scriptPubKey; - vchProposal.insert(vchProposal.end(), scriptProposal.begin() + 6, scriptProposal.end()); + + vchProposal.insert(vchProposal.end(), scriptProposal.begin() + (scriptProposal.size() > 0x04c ? 7 : 6), scriptProposal.end()); + CDataStream ss(vchProposal, SER_NETWORK, 0); try { diff --git a/src/voteproposal.h b/src/voteproposal.h index 88c575f28..13ad31f21 100644 --- a/src/voteproposal.h +++ b/src/voteproposal.h @@ -15,6 +15,7 @@ #define MAX_CHAR_ABSTRACT 30 #define MAX_BLOCKS_IN_FUTURE 28800 #define MAX_CHECKSPAN 28800 +#define MAX_BITCOUNT 28 //TODO: update #define MOST_RECENT_VERSION 1 @@ -25,6 +26,9 @@ class CVoteProposal // proposal version int nVersion; + // proposal max fee provided by creator of proposal + int nMaxFee; + // what to call the proposal std::string strName; @@ -39,17 +43,22 @@ class CVoteProposal // description of the proposal; may link to additional transactions std::string strDescription; + + // refund address + std::string strRefundAddress; public: // the amount of HYP burnt when a proposal is made - static const int64 FEE = 5 * COIN; + static const int64 BASE_FEE = 5 * COIN; void SetNull() { nVersion = 0; + nMaxFee = 0; strName = ""; nStartHeight = 0; nCheckSpan = 0; strDescription = ""; + strRefundAddress = ""; } bool IsNull () const { return strName.empty(); } @@ -59,9 +68,27 @@ class CVoteProposal SetNull(); } + CVoteProposal(std::string strName, unsigned int nStartHeight, unsigned int nCheckSpan, std::string strDescription, + int nMaxFee, std::string strRefundAddress, int nVersion = MOST_RECENT_VERSION) + { + this->nVersion = nVersion; + this->strName = strName; + this->nStartHeight = nStartHeight; + this->nCheckSpan = nCheckSpan; + this->strDescription = strDescription; + this->nMaxFee = nMaxFee; + this->strRefundAddress = strRefundAddress; + //VoteLocation location; + this->bitLocation = VoteLocation(); + + //VoteLocation will be set when the proposal is accepted by the network and the dynamic fee is determined + } + + //DEPRECATED: USED FOR TESTING CVoteProposal(std::string strName, unsigned int nStartHeight, unsigned int nCheckSpan, std::string strDescription, VoteLocation location, int nVersion = MOST_RECENT_VERSION) { + SetNull(); this->nVersion = nVersion; this->strName = strName; this->nStartHeight = nStartHeight; @@ -73,13 +100,16 @@ class CVoteProposal IMPLEMENT_SERIALIZE ( READWRITE(nVersion); + READWRITE(nMaxFee); READWRITE(strName); READWRITE(nStartHeight); READWRITE(nCheckSpan); READWRITE(strDescription); READWRITE(bitLocation); + READWRITE(strRefundAddress); ) + bool IsValid() const; bool ConstructTransaction (CTransaction& tx) const; int GetShift() const { return bitLocation.GetShift(); } uint8_t GetBitCount() const { return bitLocation.GetBitCount(); } @@ -88,8 +118,11 @@ class CVoteProposal std::string GetDescription() const { return strDescription; } unsigned int GetStartHeight() const { return nStartHeight; } VoteLocation GetLocation() const { return bitLocation; } + int GetMaxFee() const { return nMaxFee; } + std::string GetRefundAddress() const { return strRefundAddress; } uint256 GetHash() const; + void SetLocation(VoteLocation location) { this->bitLocation = location; } }; bool ProposalFromTransaction(const CTransaction& tx, CVoteProposal& proposal); diff --git a/src/voteproposalmanager.cpp b/src/voteproposalmanager.cpp index c65186925..ade4dae8c 100644 --- a/src/voteproposalmanager.cpp +++ b/src/voteproposalmanager.cpp @@ -4,6 +4,7 @@ #include "voteproposalmanager.h" #include "voteproposal.h" +#include "base58.h" using namespace std; @@ -94,19 +95,308 @@ map CVoteProposalManager::GetActive(int nHeight) return mapActive; } +namespace +{ + //An Event is either the beginning or end of a vote proposal span. + //This struct is used for GetMaxOverlap + struct Event + { + bool start; + int position; + int bitCount; + + //default constructor + Event() {} + + Event(bool start, int position, int bitCount = 0) + { + this->start = start; + this->position = position; + this->bitCount = bitCount; + } + + static bool Compare(const Event& lhs, const Event& rhs) + { + return lhs.position < rhs.position; + } + }; + + //returns the maximum number of proposals overlapping at any point within the given range + /*unsigned int GetMaxOverlap(const vector& vProposals, const unsigned int& nStart, const unsigned int& nEnd) + { + int nMaxOverlapQuantity = -1; + vector vEvents(2 * vProposals.size()); + + for(auto proposalData: vProposals) { + if(proposalData.nHeightEnd < nStart) continue; + if(proposalData.nHeightStart > nEnd) continue; + + vEvents.emplace_back(Event(true, proposalData.nHeightStart)); + vEvents.emplace_back(Event(false, proposalData.nHeightEnd)); + } + + sort(vEvents.begin(), vEvents.end(), Event::Compare); + + int nCurValueCounter = 0; + for(Event event: vEvents) { + nCurValueCounter += event.start ? 1 : -1; + nMaxOverlapQuantity = max(nMaxOverlapQuantity, nCurValueCounter); + } + + return (unsigned int)nMaxOverlapQuantity; + }*/ + + long GetResourceUsageHeuristic(const vector& vProposals, const CVoteProposal& proposal) + { + long nHeuristic = 0; + int nStart = proposal.GetStartHeight(); + int nEnd = proposal.GetStartHeight() + proposal.GetCheckSpan(); + + // An event is defined as either a proposal interval start or a proposal interval ending. + vector vEvents(2 * vProposals.size()); + + // For each proposal in vProposals that overlaps with the given proposal, create a start and end event then + // add it to vEvents. This vector will be used to determine the number of overlapping voting intervals efficiently. + for(auto proposalData: vProposals) { + if(proposalData.nHeightEnd < nStart) continue; + if(proposalData.nHeightStart > nEnd) continue; + + Event startEvent(true, proposalData.nHeightStart, proposalData.location.GetBitCount()); + Event endEvent(false, proposalData.nHeightEnd + 1, proposalData.location.GetBitCount()); + + vEvents.emplace_back(startEvent); + vEvents.emplace_back(endEvent); + } + + // sort the events so that those that happen earlier appear first in the vector + sort(vEvents.begin(), vEvents.end(), Event::Compare); + + // iterate through events in sorted order and keep a running counter of how many bits are consumed + int nCurValueCounter = 0; + for(unsigned int i = 0; i < vEvents.size() - 1; i++) { + Event curEvent = vEvents.at(i); + Event nextEvent = vEvents.at(i + 1); + + nCurValueCounter += curEvent.start ? curEvent.bitCount : -1 * curEvent.bitCount; + + // only start the counter when we have entered the voting intervals of the given proposal + if(nextEvent.position <= nStart) continue; + if(curEvent.position > nEnd) break; + + // the number of bits used is guaranteed to be constant for every block between these two events + int gap = min(nEnd, nextEvent.position) - max(nStart, curEvent.position); + + // TODO: heuristic updated; this is subject to change + nHeuristic += (100000 * ((long) proposal.GetBitCount())) / (MAX_BITCOUNT - nCurValueCounter) * gap; + } + + return nHeuristic; + } + + //returns a vector of proposals that overlap with the given range + vector GetOverlappingProposals(const map& mapProposalData, + const int& nStart, const int& nEnd) + { + vector vConflictingTime; + for (auto it : mapProposalData) { + CProposalMetaData data = it.second; + if ((int)data.nHeightEnd < nStart) + continue; + if ((int)data.nHeightStart > nEnd) + continue; + vConflictingTime.emplace_back(data); + } + + return vConflictingTime; + } +} + +//TODO: test fee output for different inputs. Might change this to grow exponentially +bool CVoteProposalManager::GetFee(const CVoteProposal& proposal, int& nFee) +{ + if(!proposal.IsValid()){ + return error("Proposal is not valid"); + } + + //set the boundaries of the voting interval + unsigned int nStartHeight = proposal.GetStartHeight(); + unsigned int nEndHeight = nStartHeight + proposal.GetCheckSpan() - 1; + + //get conflicting proposals + vector vConflictingTime = GetOverlappingProposals(mapProposalData, nStartHeight, nEndHeight); + + //determine the maximum number of overlapping proposals at any point in the voting interval + long nHeuristic = GetResourceUsageHeuristic(vConflictingTime, proposal) / 100000; + + //TODO: this will need to be tested + nFee = (unsigned int)(nHeuristic * CVoteProposal::BASE_FEE); + + if(nFee < 0) { + return error("Fee should not be negative"); + } + + return true; +} + +bool CVoteProposalManager::GetDeterministicOrdering(const uint256 &proofhash, std::vector vProposalTransactions, + std::vector &vOrderedProposalTransactions) +{ + uint256 nMask(0x000FFFFF); + int nSegmentOffset = 0; + while(!vProposalTransactions.empty()) { + uint256 nFormattedMask = nMask << (nSegmentOffset); + int segment = (int)((proofhash & nFormattedMask) >> (nSegmentOffset)).Get64(); + int index = (int)(segment % vProposalTransactions.size()); + + if(segment < 0 || index < 0) { + return error("Generated index is invalid"); + } + + vOrderedProposalTransactions.emplace_back(vProposalTransactions.at(index)); + + vProposalTransactions.erase(vProposalTransactions.begin() + index); + nSegmentOffset = (nSegmentOffset + segment) % 235; + } + + return true; +} + +//TODO: MAX TRANSACTION SIZE CHECK +bool CVoteProposalManager::AddRefundToCoinBase(const CVoteProposal &proposal, const int &nRequiredFee, const int &nTxFee, + const bool bProposalAccepted, CTransaction &txCoinBase) +{ + if (!txCoinBase.IsCoinBase()) { + return error("AddRefundToCoinBase() : Given transaction is the a coinbase transaction."); + } + + CBitcoinAddress refundAddress; + if (!refundAddress.SetString(proposal.GetRefundAddress())) { + return error("AddRefundToCoinBase() : Refund Address of proposal is not valid"); + } + + CTxOut refundTxOut; + refundTxOut.nValue = bProposalAccepted ? proposal.GetMaxFee() - nRequiredFee - nTxFee : proposal.GetMaxFee() - nTxFee; + refundTxOut.scriptPubKey.SetDestination(refundAddress.Get()); + txCoinBase.vout.emplace_back(refundTxOut); + + return true; +} + +//TODO: REFACTOR AND COMMENT +bool CVoteProposalManager::CheckRefundTransaction(const std::vector &vOrderedTxProposals, + const CTransaction &txCoinBase) +{ + if (!txCoinBase.IsCoinBase()) { + return error("CheckRefundTransaction() : Given transaction is not a coinbase."); + } + + CTransaction txExpectedCoinBase; + txExpectedCoinBase.vin.resize(1); + txExpectedCoinBase.vin[0].prevout.SetNull(); + txExpectedCoinBase.vout.resize(1); + + for(auto txProposal: vOrderedTxProposals) { + // output variables + int nRequiredFee; + CVoteProposal proposal; + VoteLocation location; + + // return error if a proposal object cannot be extracted from the tx + if(!ProposalFromTransaction(txProposal, proposal)) { + return error("CheckRefundTransaction() : Proposal was not able to be extracted from transaction."); + } + + // input variables + int nTxFee = (int)CVoteProposal::BASE_FEE; //TODO: MAKE THIS HIGHER + int nBitCount = proposal.GetBitCount(); + int nStartHeight = proposal.GetStartHeight(); + int nCheckSpan = proposal.GetCheckSpan(); + + // If a valid voting location cannot be found then create an unaccepted proposal refund + if(!GetNextLocation(nBitCount, nStartHeight, nCheckSpan, location)) { + AddRefundToCoinBase(proposal, nRequiredFee, nTxFee, false, txExpectedCoinBase); + } else { + // If a fee cannot be calculated then return error + proposal.SetLocation(location); + if (!GetFee(proposal, nRequiredFee)) { + return error("CheckRefundTransaction() : Calculating fee for proposal failed."); + } + + // If the maximum fee provided by the proposal creator is less than the required fee + // then create an unaccepted proposal refund + if (nRequiredFee > proposal.GetMaxFee()) { + AddRefundToCoinBase(proposal, nRequiredFee, nTxFee, false, txExpectedCoinBase); + } else { + AddRefundToCoinBase(proposal, nRequiredFee, nTxFee, true, txExpectedCoinBase); + } + } + } + + if (txCoinBase.vout.size() != txExpectedCoinBase.vout.size()) { + return error("CheckRefundTransaction() : The output vector of the coinbase transaction isn't the correct size."); + } + + for(int i = 0; i < txCoinBase.vout.size(); i++) { + if(txCoinBase.vout.at(i).scriptPubKey.GetID().GetHex() != txExpectedCoinBase.vout.at(i).scriptPubKey.GetID().GetHex()) { + return error("CheckRefundTransaction() : The scriptPubKey of the refund transaction isn't what it should be" + "according to the deterministic ordering."); + } + + if(txCoinBase.vout.at(i).nValue != txExpectedCoinBase.vout.at(i).nValue) { + return error("CheckRefundTransaction() : The value of the refund isn't what it should be according to the" + "deterministic ordering."); + } + } + + return true; +} + +bool CVoteProposalManager::GetAcceptedTxProposals(const CTransaction& txCoinBase, const std::vector& vOrderedTxProposals, + std::vector& vAcceptedTxProposals) +{ + if (!txCoinBase.IsCoinBase()) { + return error("GetAcceptedTxProposals() : Given transaction is not a coinbase."); + } + + vAcceptedTxProposals.clear(); + + for(auto txProposal: vOrderedTxProposals) { + // output variables + int nRequiredFee; + CVoteProposal proposal; + VoteLocation location; + + // return error if a proposal object cannot be extracted from the tx + if(!ProposalFromTransaction(txProposal, proposal)) { + return error("GetAcceptedTxProposals() : Proposal was not able to be extracted from transaction."); + } + + // input variables + int nBitCount = proposal.GetBitCount(); + int nStartHeight = proposal.GetStartHeight(); + int nCheckSpan = proposal.GetCheckSpan(); + + if(GetNextLocation(nBitCount, nStartHeight, nCheckSpan, location)){ + // If a fee cannot be calculated then return error + proposal.SetLocation(location); + if (!GetFee(proposal, nRequiredFee)) { + return error("GetAcceptedTxProposals() : Calculating fee for proposal failed."); + } + + // If the max fee provided by the txProposal exceeds the required fee the accept the tx as a valid proposal + if (nRequiredFee >= proposal.GetMaxFee()) { + vAcceptedTxProposals.emplace_back(txProposal); + } + } + } + + return true; +} + bool CVoteProposalManager::GetNextLocation(int nBitCount, int nStartHeight, int nCheckSpan, VoteLocation& location) { //Conflicts for block range - vector vConflictingTime; - for (auto it : mapProposalData) { - CProposalMetaData data = it.second; - int nEndHeight = nStartHeight + nCheckSpan; - if ((int)data.nHeightEnd < nStartHeight) - continue; - if ((int)data.nHeightStart > nEndHeight) - continue; - vConflictingTime.emplace_back(data); - } + vector vConflictingTime = GetOverlappingProposals(mapProposalData, nStartHeight, nStartHeight + nCheckSpan - 1); //Find an open location for the new proposal, return left most bits if (vConflictingTime.empty()) { @@ -138,4 +428,28 @@ bool CVoteProposalManager::GetNextLocation(int nBitCount, int nStartHeight, int } } return false; -} \ No newline at end of file +} + +bool CVoteProposalManager::GetRefundOutputSize(const CTransaction& txProposal, int& nSize) const +{ + + CTransaction txEmpty; + unsigned int nBaseSize = ::GetSerializeSize(txEmpty, SER_NETWORK, PROTOCOL_VERSION); + + if (!txProposal.IsProposal()) { + return error("GetRefundOutputSize() : Given transaction must be a proposal."); + } + + CVoteProposal proposal; + if (!ProposalFromTransaction(txProposal, proposal)) { + return error("GetRefundOutputSize() : Failed to extract proposal from transaction."); + } + + // Every refund output should increase the size of the coinbase tx by the same amount. + // 0, 0, and false are just filler values. + proposalManager.AddRefundToCoinBase(proposal, 0, 0, false, txEmpty); + + nSize = ::GetSerializeSize(txEmpty, SER_NETWORK, PROTOCOL_VERSION) - nBaseSize; + + return true; +} diff --git a/src/voteproposalmanager.h b/src/voteproposalmanager.h index 99d92b1ab..052c4ff05 100644 --- a/src/voteproposalmanager.h +++ b/src/voteproposalmanager.h @@ -9,11 +9,19 @@ #include "voteobject.h" class CVoteProposal; +class CTransaction; +class CBitcoinAddress; + +//1. calculate fee for proposal +//2. fee is a function of bits used and range with a restriction on start time +//3. the input to the fee function is determined by most overlapped section of range struct CProposalMetaData { uint256 hash; VoteLocation location; + + //this range must be inclusive unsigned int nHeightStart; unsigned int nHeightEnd; }; @@ -23,10 +31,23 @@ class CVoteProposalManager private: std::map mapProposalData; public: + bool Add(const CVoteProposal& proposal); void Remove(const uint256& hashProposal); std::map GetActive(int nHeight); + + //methods used in dynamic fee calculation + bool GetFee(const CVoteProposal& proposal, int& nFee); + bool GetDeterministicOrdering(const uint256& proofhash, std::vector vProposalTransactions, + std::vector& vOrderedProposalTransactions); bool GetNextLocation(int nBitCount, int nStartHeight, int nCheckSpan, VoteLocation& location); + bool AddRefundToCoinBase(const CVoteProposal &proposal, const int &nRequiredFee, const int &nTxFee, + const bool bProposalAccepted, CTransaction &txCoinBase); + bool CheckRefundTransaction(const std::vector &vOrderedTxProposals, const CTransaction &txCoinBase); + bool GetAcceptedTxProposals(const CTransaction& txCoinBase, const std::vector& vOrderedTxProposals, + std::vector& vAcceptedTxProposals); + bool GetRefundOutputSize(const CTransaction& txProposal, int& nSize) const; + std::map GetAllProposals() const { return mapProposalData; }; bool CheckProposal (const CVoteProposal& proposal); }; diff --git a/src/wallet.cpp b/src/wallet.cpp index c23f7decc..f56cc967f 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -1927,7 +1927,15 @@ bool CWallet::FinalizeProposal(CTransaction& txProposal) //! Choose coins to use set > setCoins; int64 nValueIn = 0; - if (!SelectCoins(5 * COIN, GetTime(), setCoins, nValueIn, NULL) || nValueIn < CVoteProposal::FEE) + + CVoteProposal proposal; + if(!ProposalFromTransaction(txProposal, proposal)) { + return error("Proposal was not successfully extracted from transaction. This shouldn't happen."); + } + + int nFee = CVoteProposal::BASE_FEE; + + if (!SelectCoins(5 * COIN, GetTime(), setCoins, nValueIn, NULL) || nValueIn < nFee) return error("Failed to select coins to spend"); //! Select one of the addresses to send the change to, and add inputs to the proposal tx @@ -1940,8 +1948,8 @@ bool CWallet::FinalizeProposal(CTransaction& txProposal) } //! Add change output - if (nValueIn > CVoteProposal::FEE + CENT) { - CTxOut out(nValueIn - CVoteProposal::FEE, scriptChange); + if (nValueIn > nFee + CENT) { + CTxOut out(nValueIn - nFee, scriptChange); txProposal.vout.push_back(out); } @@ -2556,7 +2564,8 @@ bool CWallet::SendProposal(const CVoteProposal& proposal, uint256& txid) printf("*** after available coins\n"); - int64 nFee = CVoteProposal::FEE; + int nFee = proposal.GetMaxFee(); + int64 nValueIn = 0; set > setCoins; @@ -2577,8 +2586,9 @@ bool CWallet::SendProposal(const CVoteProposal& proposal, uint256& txid) //!Lookup the address of one of the inputs and return the change to that address uint256 hashBlock; CTransaction txPrev; - if(!::GetTransaction(wtx.vin[0].prevout.hash, txPrev, hashBlock)) + if(!::GetTransaction(wtx.vin[0].prevout.hash, txPrev, hashBlock)) { return error("%s: Failed to select coins", __func__); + } CScript scriptReturn = txPrev.vout[wtx.vin[0].prevout.n].scriptPubKey; CTxOut out(nChange, scriptReturn); @@ -2590,14 +2600,16 @@ bool CWallet::SendProposal(const CVoteProposal& proposal, uint256& txid) //! Sign the transaction int nIn = 0; for (const pair& coin : setCoins) { - if (!SignSignature(*this, *coin.first, wtx, nIn++)) + if (!SignSignature(*this, *coin.first, wtx, nIn++)) { return false; + } } //! Broadcast the transaction to the network CReserveKey reserveKey = CReserveKey(this); - if (!CommitTransaction(wtx, reserveKey)) + if (!CommitTransaction(wtx, reserveKey)) { return error("%s: Failed to commit transaction", __func__); + } txid = wtx.GetHash();