diff --git a/src/test/voting_tests.cpp b/src/test/voting_tests.cpp index 1291dfe50..e9e28e987 100644 --- a/src/test/voting_tests.cpp +++ b/src/test/voting_tests.cpp @@ -16,6 +16,25 @@ using namespace std; BOOST_AUTO_TEST_SUITE(voting_tests) +// ERROR MESSAGES +const std::string CONSTRUCT_ERROR = "failed to construct tx"; +const std::string NOT_PROPOSAL_ERROR = "Transaction is not a proposal"; +const std::string COULD_NOT_DESERIALIZE_ERROR = "Failed to deserialize"; +const std::string BIT_LOCATION_ERROR = "location of proposal after it is deserialized isn't equal to its original value"; +const std::string BIT_COUNT_ERROR = "bitcount of proposal after it is deserialized isn't equal to its original value"; +const std::string SHIFT_ERROR = "shift of proposal after it is deserialized isn't equal to its original value"; +const std::string START_HEIGHT_ERROR = "start height of proposal after it is deserialized isn't equal to its original value"; +const std::string SPAN_ERROR = "check span of proposal after it is deserialized isn't equal to its original value"; +const std::string DESCRIPTION_ERROR = "description of proposal after it is deserialized isn't equal to its original value"; +const std::string NAME_ERROR = "name of proposal after it is deserialized isn't equal to its original value"; +const std::string HASH_ERROR = "hash of proposal after it is deserialized isn't equal to its original value"; +const std::string REFUND_ADDRESS_ERROR = "refund address is not equal to its original value after deserialization"; +const std::string MAX_FEE_ERROR = "the max fee of the proposal after it is deserialized isn't equal to its original value"; +const std::string DIFFERENT_ORDER_ERROR = "the deterministic ordering should be the same for both vectors but isn't"; +const std::string SAME_ORDER_ERROR = "the deterministic orderings of the same vector for two different proofhashes are the same"; +const std::string CREATE_ORDER_ERROR = "failed to construct deterministic ordering of proposals"; +const std::string REFUND_OUT_ERROR = "the refund outputs in the coinbase tx were not constructed correctly"; + // name of issue std::string strName = "proposal1"; // check version for existing proposals Shift @@ -220,4 +239,153 @@ 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/voteproposalmanager.cpp b/src/voteproposalmanager.cpp index c65186925..d06360f7f 100644 --- a/src/voteproposalmanager.cpp +++ b/src/voteproposalmanager.cpp @@ -94,6 +94,124 @@ 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 the events in order of time (defined by Compare method in Event struct + sort(vEvents.begin(), vEvents.end(), Event::Compare); + + // iterate through all events and keep track of overlapping intervals + int nCurValueCounter = 0; + for(Event event: vEvents) { + nCurValueCounter += event.start ? 1 : -1; + nMaxOverlapQuantity = max(nMaxOverlapQuantity, nCurValueCounter); + } + + // return the maximum number of overlapping intervals contained in vProposals at any point in time + return (unsigned int)nMaxOverlapQuantity; + } + + // return a deterministic 64 bit integer that represents the resource usage of a given proposal + 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())) / (28 - 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; + } +} + bool CVoteProposalManager::GetNextLocation(int nBitCount, int nStartHeight, int nCheckSpan, VoteLocation& location) { //Conflicts for block range