Skip to content
Merged
168 changes: 168 additions & 0 deletions src/test/voting_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<CTransaction> vTxProposals = {txProposal1, txProposal2, txProposal3, txProposal4, txProposal5};
//
// vector<CTransaction> vOrderedTxProposals1;
// BOOST_CHECK_MESSAGE(proposalManager.GetDeterministicOrdering(uint256("0xba04fd9cd0e9487f1a713e80828055284c04e362167ceb5f75db70153c613736"),
// vTxProposals, vOrderedTxProposals1), CREATE_ORDER_ERROR);
//
// vector<CTransaction> 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<CTransaction> 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<CTransaction> vTxProposals = {txProposal1, txProposal2, txProposal3, txProposal4, txProposal5};
//
// vector<CTransaction> 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()
118 changes: 118 additions & 0 deletions src/voteproposalmanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,124 @@ map<uint256, VoteLocation> 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<CProposalMetaData>& vProposals, const unsigned int& nStart, const unsigned int& nEnd)
{
int nMaxOverlapQuantity = -1;
vector<Event> 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<CProposalMetaData>& 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<Event> 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<CProposalMetaData> GetOverlappingProposals(const map<uint256, CProposalMetaData>& mapProposalData,
const int& nStart, const int& nEnd)
{
vector<CProposalMetaData> 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
Expand Down