diff --git a/Config.cpp b/Config.cpp index c840c568e..9f742387f 100644 --- a/Config.cpp +++ b/Config.cpp @@ -34,6 +34,7 @@ Config::Config() TRANSACTION_FEE=1000; ACCOUNT_FEE=1000; + MIN_VOTES_FOR_CONSENSUS=1; } void Config::load() diff --git a/Config.h b/Config.h index f2ae454cf..d2f9d9004 100644 --- a/Config.h +++ b/Config.h @@ -24,6 +24,8 @@ public: std::string HISTORY_DIR; + int MIN_VOTES_FOR_CONSENSUS; + Config(); diff --git a/Ledger.cpp b/Ledger.cpp index a78813f37..d04f3b5a1 100644 --- a/Ledger.cpp +++ b/Ledger.cpp @@ -221,7 +221,7 @@ bool Ledger::addTransaction(TransactionPtr trans,bool checkDuplicate) } // Don't check the amounts. We will do this at the end. -void Ledger::addTransactionRecalculate(TransactionPtr trans) +void Ledger::addTransactionAllowNeg(TransactionPtr trans) { uint160 fromAddress=NewcoinAddress::protobufToInternal(trans->from()); @@ -268,47 +268,7 @@ void Ledger::addTransactionRecalculate(TransactionPtr trans) } } -// Must look for transactions to discard to make this account positive -// When we chuck transactions it might cause other accounts to need correcting -void Ledger::correctAccount(uint160& address) -{ - list effected; - // do this in reverse so we take of the higher seqnum first - for( list::reverse_iterator iter=mTransactions.rbegin(); iter != mTransactions.rend(); ) - { - TransactionPtr trans= *iter; - if(NewcoinAddress::protobufToInternal(trans->from()) == address) - { - Account fromAccount=mAccounts[address]; - assert(fromAccount.second==trans->seqnum()+1); - if(fromAccount.first<0) - { - fromAccount.first += trans->amount(); - fromAccount.second --; - - mAccounts[address]=fromAccount; - - uint160 destAddress=NewcoinAddress::protobufToInternal(trans->dest()); - Account destAccount=mAccounts[destAddress]; - destAccount.first -= trans->amount(); - mAccounts[destAddress]=destAccount; - if(destAccount.first<0) effected.push_back(destAddress); - - list::iterator temp=mTransactions.erase( --iter.base() ); - if(fromAccount.first>=0) break; - - iter=list::reverse_iterator(temp); - }else break; - }else iter--; - } - - BOOST_FOREACH(uint160& address,effected) - { - correctAccount(address); - } - -} // start from your parent and go through every transaction // calls this on its child if recursive is set @@ -333,22 +293,15 @@ void Ledger::recalculate(bool recursive) // don't check balances until the end BOOST_FOREACH(TransactionPtr trans,firstTransactions) { - addTransactionRecalculate(trans); + addTransactionAllowNeg(trans); } BOOST_FOREACH(TransactionPtr trans,secondTransactions) { - addTransactionRecalculate(trans); - } - - pair& fullAccount=pair(); - BOOST_FOREACH(fullAccount,mAccounts) - { - if(fullAccount.second.first <0 ) - { - correctAccount(fullAccount.first); - } + addTransactionAllowNeg(trans); } + correctAccounts(); + if(mChild && recursive) mChild->recalculate(); @@ -421,4 +374,85 @@ bool Ledger::hasTransaction(TransactionPtr needle) } return(false); +} + +// Ledgers are compatible if both sets of transactions merged together would lead to the same ending balance +bool Ledger::isCompatible(Ledger::pointer other) +{ + Ledger::pointer l1=Ledger::pointer(new Ledger(*this)); + Ledger::pointer l2=Ledger::pointer(new Ledger(*other)); + + l1->mergeIn(l2); + l2->mergeIn(l1); + + map a1=l1->getAccounts(); + map a2=l2->getAccounts(); + + return(a1==a2); + +} + +void Ledger::mergeIn(Ledger::pointer other) +{ + list& otherTransactions=other->getTransactions(); + BOOST_FOREACH(TransactionPtr trans,otherTransactions) + { + addTransactionAllowNeg(trans); + } + + correctAccounts(); +} + +void Ledger::correctAccounts() +{ + pair& fullAccount=pair(); + BOOST_FOREACH(fullAccount,mAccounts) + { + if(fullAccount.second.first <0 ) + { + correctAccount(fullAccount.first); + } + } +} + +// Must look for transactions to discard to make this account positive +// When we chuck transactions it might cause other accounts to need correcting +void Ledger::correctAccount(uint160& address) +{ + list effected; + + // do this in reverse so we take of the higher seqnum first + for( list::reverse_iterator iter=mTransactions.rbegin(); iter != mTransactions.rend(); ) + { + TransactionPtr trans= *iter; + if(NewcoinAddress::protobufToInternal(trans->from()) == address) + { + Account fromAccount=mAccounts[address]; + assert(fromAccount.second==trans->seqnum()+1); + if(fromAccount.first<0) + { + fromAccount.first += trans->amount(); + fromAccount.second --; + + mAccounts[address]=fromAccount; + + uint160 destAddress=NewcoinAddress::protobufToInternal(trans->dest()); + Account destAccount=mAccounts[destAddress]; + destAccount.first -= trans->amount(); + mAccounts[destAddress]=destAccount; + if(destAccount.first<0) effected.push_back(destAddress); + + list::iterator temp=mTransactions.erase( --iter.base() ); + if(fromAccount.first>=0) break; + + iter=list::reverse_iterator(temp); + }else break; + }else iter--; + } + + BOOST_FOREACH(uint160& address,effected) + { + correctAccount(address); + } + } \ No newline at end of file diff --git a/Ledger.h b/Ledger.h index c0ff96a38..73ad4f833 100644 --- a/Ledger.h +++ b/Ledger.h @@ -44,7 +44,8 @@ private: void sign(); void hash(); - void addTransactionRecalculate(TransactionPtr trans); + void addTransactionAllowNeg(TransactionPtr trans); + void correctAccounts(); void correctAccount(uint160& address); public: typedef boost::shared_ptr pointer; @@ -52,6 +53,7 @@ public: Ledger(newcoin::FullLedger& ledger); void setTo(newcoin::FullLedger& ledger); + void mergeIn(Ledger::pointer other); void save(std::string dir); bool load(std::string dir); @@ -74,11 +76,13 @@ public: uint256& getSignature(); uint32 getValidSeqNum(){ return(mValidationSeqNum); } unsigned int getNumTransactions(){ return(mTransactions.size()); } - std::map >& getAccounts(){ return(mAccounts); } + std::map& getAccounts(){ return(mAccounts); } Account* getAccount(uint160& address); newcoin::FullLedger* createFullLedger(); Ledger::pointer getParent(); + Ledger::pointer getChild(); + bool isCompatible(Ledger::pointer other); }; diff --git a/LedgerMaster.cpp b/LedgerMaster.cpp index 1c7b53c30..7da0bde0c 100644 --- a/LedgerMaster.cpp +++ b/LedgerMaster.cpp @@ -238,4 +238,40 @@ void LedgerMaster::checkLedgerProposal(Peer::pointer peer, newcoin::ProposeLedge } -} \ No newline at end of file +} + + +// TODO: optimize. this is expensive so limit the amount it is run +void LedgerMaster::checkConsensus(uint32 ledgerIndex) +{ + Ledger::pointer ourAcceptedLedger=mLedgerHistory.getAcceptedLedger(ledgerIndex); + if(ourAcceptedLedger) + { + uint256* consensusHash=theApp->getValidationCollection().getConsensusLedgerHash(ledgerIndex); + if( consensusHash && + (ourAcceptedLedger->getHash()!= *consensusHash)) + { + Ledger::pointer consensusLedger=mLedgerHistory.getLedger(*consensusHash); + if(consensusLedger) + { // see if these are compatible + if(ourAcceptedLedger->isCompatible(consensusLedger)) + { // try to merge any transactions from the consensus one into ours + ourAcceptedLedger->mergeIn(consensusLedger); + // Ledger::pointer child=ourAcceptedLedger->getChild(); + Ledger::pointer child=mLedgerHistory.getAcceptedLedger(ledgerIndex+1); + if(child) child->recalculate(); + }else + { // switch to this ledger. Re-validate + mLedgerHistory.addAcceptedLedger(consensusLedger); + consensusLedger->publishValidation(); + } + + }else + { // we don't know the consensus one. Ask peers for it + PackedMessage::pointer msg=Peer::createGetFullLedger(*consensusHash); + theApp->getConnectionPool().relayMessage(NULL,msg); + } + } + } +} + diff --git a/LedgerMaster.h b/LedgerMaster.h index c50045f83..91b084802 100644 --- a/LedgerMaster.h +++ b/LedgerMaster.h @@ -56,6 +56,7 @@ public: void sendProposal(); void endFinalization(); void checkLedgerProposal(Peer::pointer peer,newcoin::ProposeLedger& packet); + void checkConsensus(uint32 ledgerIndex); }; #endif \ No newline at end of file diff --git a/Peer.h b/Peer.h index f5f1e2ab8..3b2ead7b5 100644 --- a/Peer.h +++ b/Peer.h @@ -84,6 +84,7 @@ public: //static PackedMessage::pointer createFullLedger(Ledger::pointer ledger); static PackedMessage::pointer createLedgerProposal(Ledger::pointer ledger); static PackedMessage::pointer createValidation(Ledger::pointer ledger); + static PackedMessage::pointer createGetFullLedger(uint256& hash); }; diff --git a/ValidationCollection.cpp b/ValidationCollection.cpp index 9e6092570..898bd4498 100644 --- a/ValidationCollection.cpp +++ b/ValidationCollection.cpp @@ -1,28 +1,37 @@ #include "ValidationCollection.h" #include "Application.h" #include "NewcoinAddress.h" -#include +#include "Config.h" -bool ValidationCollection::hasValidation(uint256& ledgerHash,uint160& hanko) +#include +using namespace std; + + +bool ValidationCollection::hasValidation(uint256& ledgerHash,uint160& hanko,uint32 seqnum) { if(mValidations.count(ledgerHash)) { BOOST_FOREACH(newcoin::Validation& valid,mValidations[ledgerHash]) { - if(NewcoinAddress::protobufToInternal(valid.hanko()) == hanko) return(true); + if( valid.seqnum()==seqnum && + NewcoinAddress::protobufToInternal(valid.hanko()) == hanko) return(true); } } + if(mIgnoredValidations.count(ledgerHash)) { BOOST_FOREACH(newcoin::Validation& valid,mIgnoredValidations[ledgerHash]) { - if(NewcoinAddress::protobufToInternal(valid.hanko()) == hanko) return(true); + if( valid.seqnum()==seqnum && + NewcoinAddress::protobufToInternal(valid.hanko()) == hanko) return(true); } } return(false); } +// TODO: we are adding our own validation // TODO: when do we check if we are with the consensus? +// TODO: throw out lower seqnums void ValidationCollection::addValidation(newcoin::Validation& valid) { // TODO: make sure the validation is valid @@ -31,25 +40,135 @@ void ValidationCollection::addValidation(newcoin::Validation& valid) uint160 hanko=NewcoinAddress::protobufToInternal(valid.hanko()); // make sure we don't already have this validation - if(hasValidation(hash,hanko)) return; + if(hasValidation(hash,hanko,valid.seqnum())) return; // check if we care about this hanko if( theApp->getUNL().findHanko(valid.hanko()) ) { mValidations[hash].push_back(valid); + + theApp->getLedgerMaster().checkConsensus(valid.ledgerindex()); }else { mIgnoredValidations[hash].push_back(valid); } - - mMapIndexToValid[valid.ledgerindex()].push_back(valid); } -std::vector* ValidationCollection::getValidations(uint32 ledgerIndex) + +// TODO: optimize. We can at least cache what ledgers are compatible +// a validation can be in multiple groups since compatibility isn't transitive +// Sometimes things are just complex +void ValidationCollection::addToGroup(newcoin::Validation& newValid) { - if(mMapIndexToValid.count(ledgerIndex)) + if(mGroupValidations.count(newValid.ledgerindex())) { - return(&(mMapIndexToValid[ledgerIndex])); + bool canReturn=false; + // see if this hash is already on the list. If so add it there. + vector< vector >& groups=mGroupValidations[newValid.ledgerindex()]; + vector& groupList=vector(); + BOOST_FOREACH(groupList,groups) + { + BOOST_FOREACH(newcoin::Validation& valid,groupList) + { + if(valid.hash()==newValid.hash()) + { + groupList.push_back(newValid); + canReturn=true; + break; + } + } + } + + if(canReturn) return; + // this is a validation of a new ledger hash + + uint256 newHash=Transaction::protobufToInternalHash(newValid.hash()); + Ledger::pointer newLedger=theApp->getLedgerMaster().getLedger(newHash); + if(newLedger) + { // see if this ledger is compatible with any groups + BOOST_FOREACH(groupList,groups) + { + bool compatible=true; + BOOST_FOREACH(newcoin::Validation& valid,groupList) + { + uint256 hash=Transaction::protobufToInternalHash(valid.hash()); + Ledger::pointer ledger=theApp->getLedgerMaster().getLedger(hash); + if(ledger) + { + if(!ledger->isCompatible(newLedger)) + { // not compatible with this group + compatible=false; + break; + } + }else + { // we can't tell if it is compatible + compatible=false; + break; + } + } + + if(compatible) groupList.push_back(newValid); + } + + } + + // also add to its own group in case + groupList.push_back(newValid); + groups.push_back(groupList); + + }else + { // this is the first validation of this ledgerindex + mGroupValidations[newValid.ledgerindex()][0].push_back(newValid); + } + + +} + +vector* ValidationCollection::getValidations(uint32 ledgerIndex) +{ + if(mGroupValidations.count(ledgerIndex)) + { + return(&(mGroupValidations[ledgerIndex])); } return(NULL); +} + + +// look through all the validated hashes at that index +// put the ledgers into compatible groups +// Pick the group with the most votes +bool ValidationCollection::getConsensusLedgers(uint32 ledgerIndex, list& retHashs) +{ + vector* valids=getValidations(ledgerIndex); + if(valids) + { + vector< pair > > compatibleGroups; + + map voteCounts; + BOOST_FOREACH(newcoin::Validation valid,*valids) + { + uint256 hash=Transaction::protobufToInternalHash(valid.hash()); + Ledger::pointer testLedger=theApp->getLedgerMaster().getLedger(hash); + if(testLedger) + { + + } + + voteCounts[ ] += 1; + } + bool ret=false; + int maxVotes=theConfig.MIN_VOTES_FOR_CONSENSUS; + pair& vote=pair(); + BOOST_FOREACH(vote,voteCounts) + { + if(vote.second>maxVotes) + { + maxVotes=vote.second; + retHash=vote.first; + ret=true; + } + } + return(ret); + } + return(false); } \ No newline at end of file diff --git a/ValidationCollection.h b/ValidationCollection.h index 9006a5245..b732cbdd6 100644 --- a/ValidationCollection.h +++ b/ValidationCollection.h @@ -7,18 +7,32 @@ class ValidationCollection { + // from ledger hash to the validation std::map > mValidations; std::map > mIgnoredValidations; - std::map > mMapIndexToValid; - bool hasValidation(uint256& ledgerHash,uint160& hanko); + // this maps ledgerIndex to an array of groups. Each group is a list of validations. + // a validation can be in multiple groups since compatibility isn't transitive + // + std::map > > mGroupValidations; + + bool hasValidation(uint256& ledgerHash,uint160& hanko,uint32 seqnum); + void addToGroup(newcoin::Validation& valid); public: ValidationCollection(); void addValidation(newcoin::Validation& valid); std::vector* getValidations(uint32 ledgerIndex); + + + // It can miss some compatible ledgers of course if you don't know them + // gets a list of all the compatible ledgers that were voted for the most + // returns false if there isn't a consensus yet + bool getConsensusLedgers(uint32 ledgerIndex, std::list& retHashs); + + int getSeqNum(uint32 ledgerIndex); }; #endif \ No newline at end of file