Files
rippled/Ledger.cpp

642 lines
16 KiB
C++

#include "Ledger.h"
#include "newcoin.pb.h"
#include "PackedMessage.h"
#include "Config.h"
#include "Conversion.h"
#include "BitcoinUtil.h"
#include <boost/foreach.hpp>
#include <iostream>
#include <fstream>
using namespace boost;
using namespace std;
Ledger::Ledger(uint32 index) : mFeeHeld(0), mTimeStamp(0), mLedgerSeq(index), mCurrent(true)
{
}
Ledger::Ledger(const uint256 &parentHash, const uint256 &transHash, const uint256 &accountHash,
uint64 feeHeld, uint64 timeStamp, uint32 ledgerSeq)
: mParentHash(parentHash), mTransHash(transHash), mAccountHash(accountHash),
mFeeHeld(feeHeld), mTimeStamp(timeStamp), mLedgerSeq(ledgerSeq), mCurrent(false)
{
updateHash();
}
void Ledger::updateHash()
{
Serializer s(116);
addRaw(s);
mHash=s.getSHA512Half();
}
void Ledger::addRaw(Serializer &s)
{
s.add32(mLedgerSeq);
s.add64(mFeeHeld);
s.add256(mParentHash);
s.add256(mTransHash);
s.add256(mAccountHash);
s.add64(mTimeStamp);
}
AccountState::pointer Ledger::getAccountState(const uint160& accountID)
{
ScopedLock l(mTransactionMap->Lock());
SHAMapItem::pointer item=mTransactionMap->peekItem(uint160to256(accountID));
if(item==NULL) return AccountState::pointer();
return AccountState::pointer(new AccountState(item->getData()));
}
bool Ledger::updateAccountState(AccountState::pointer state)
{
SHAMapItem::pointer item(new SHAMapItem(state->getAccountID(), state->getRaw()));
return mAccountStateMap->updateGiveItem(item);
}
bool Ledger::addTransaction(Transaction::pointer trans)
{ // low-level - just add to table
SHAMapItem::pointer item(new SHAMapItem(trans->getID(), trans->getSigned()->getData()));
return mTransactionMap->addGiveItem(item);
}
Transaction::pointer Ledger::getTransaction(const uint256& transID)
{
ScopedLock l(mTransactionMap->Lock());
SHAMapItem::pointer item=mTransactionMap->peekItem(transID);
if(item==NULL) return Transaction::pointer();
Transaction *t=new Transaction(item->getData(), true);
if(t->getStatus()==NEW) t->setStatus(mCurrent ? INCLUDED : COMMITTED, mLedgerSeq);
return Transaction::pointer(t);
}
Ledger::TransResult Ledger::applyTransaction(Transaction::pointer trans)
{
ScopedLock l(mLock);
if(trans->getSourceLedger()<mLedgerSeq) return TR_BADLSEQ;
if(trans->getAmount()<trans->getFee()) return TR_TOOSMALL;
if((mTransactionMap==NULL) || (mAccountStateMap==NULL)) return TR_ERROR;
try
{
// already applied?
Transaction::pointer dupTrans=getTransaction(trans->getID());
if(dupTrans!=NULL) return TR_ALREADY;
// accounts exist?
AccountState::pointer fromAccount=getAccountState(trans->getFromAccount());
AccountState::pointer toAccount=getAccountState(trans->getToAccount());
if((fromAccount==NULL)||(toAccount==NULL)) return TR_BADACCT;
// pass sanity checks?
if(fromAccount->getBalance()<trans->getAmount()) return TR_INSUFF;
if(fromAccount->getSeq()>trans->getFromAccountSeq()) return TR_PASTASEQ;
if(fromAccount->getSeq()<trans->getFromAccountSeq()) return TR_PREASEQ;
// apply
fromAccount->charge(trans->getAmount());
fromAccount->incSeq();
toAccount->credit(trans->getAmount()-trans->getFee());
mFeeHeld+=trans->getFee();
trans->setStatus(INCLUDED, mLedgerSeq);
updateAccountState(fromAccount);
updateAccountState(toAccount);
addTransaction(trans);
return TR_SUCCESS;
}
catch (SHAMapException)
{
return TR_ERROR;
}
}
Ledger::TransResult Ledger::removeTransaction(Transaction::pointer trans)
{ // high-level - reverse application of transaction
}
Ledger::TransResult Ledger::hasTransaction(Transaction::pointer trans)
{
ScopedLock l(mLock);
if(mTransactionMap==NULL) return TR_ERROR;
try
{
Transaction::pointer t=getTransaction(trans->getID());
if(t==NULL) return TR_NOTFOUND;
return TR_SUCCESS;
}
catch (SHAMapException)
{
return TR_ERROR;
}
}
#if 0
// TODO: we should probably make a shared pointer type for each of these PB types
newcoin::FullLedger* Ledger::createFullLedger()
{
newcoin::FullLedger* ledger=new newcoin::FullLedger();
ledger->set_index(mIndex);
ledger->set_hash(getHash().begin(),getHash().GetSerializeSize());
ledger->set_parenthash(mParentHash.begin(),mParentHash.GetSerializeSize());
BOOST_FOREACH(PAIR(const uint160, Account)& account, mAccounts)
{
newcoin::Account* saveAccount=ledger->add_accounts();
saveAccount->set_address(account.first.begin(),account.first.GetSerializeSize());
saveAccount->set_amount(account.second.first);
saveAccount->set_seqnum(account.second.second);
}
return(ledger);
}
void Ledger::setTo(newcoin::FullLedger& ledger)
{
mIndex=ledger.index();
mTransactions.clear();
mDiscardedTransactions.clear();
mAccounts.clear();
mValidSig=false;
mValidHash=false;
mParentHash=protobufTo256(ledger.parenthash());
int numAccounts=ledger.accounts_size();
for(int n=0; n<numAccounts; n++)
{
const newcoin::Account& account=ledger.accounts(n);
mAccounts[ protobufTo160(account.address()) ] = Account(account.amount(),account.seqnum());
}
int numTrans=ledger.transactions_size();
for(int n=0; n<numTrans; n++)
{
const newcoin::Transaction& trans=ledger.transactions(n);
mTransactions.push_back(Transaction::pointer(new newcoin::Transaction(trans)));
}
}
Ledger::pointer Ledger::getParent()
{
if(!mParent)
{
mParent=theApp->getLedgerMaster().getLedger(mParentHash);
}
return(mParent);
}
// TODO: we can optimize so the ledgers only hold the delta from the accepted ledger
// TODO: check to make sure the ledger is consistent after we load it
bool Ledger::load(const uint256& hash)
{
Database* db=theApp->getDB();
string sql="SELECT * from Ledgers where hash=";
string hashStr;
db->escape(hash.begin(),hash.GetSerializeSize(),hashStr);
sql.append(hashStr);
if(db->executeSQL(sql.c_str()))
{
if(db->getNextRow())
{
mIndex=db->getInt("LedgerIndex");
mHash=hash;
mValidSig=false;
mAccounts.clear();
mTransactions.clear();
mDiscardedTransactions.clear();
db->getBinary("ParentHash",mParentHash.begin(),mParentHash.GetSerializeSize());
mFeeHeld=db->getBigInt("FeeHeld");
char buf[100];
sql="SELECT Transactions.* from Transactions,LedgerTransactionMap where Transactions.TransactionID=LedgerTransactionMap.TransactionID and LedgerTransactionMap.LedgerID=";
sprintf(buf, "%d", db->getInt(0));
sql.append(buf);
if(db->executeSQL(sql.c_str()))
{
unsigned char tbuf[1000];
while(db->getNextRow())
{
Transaction::pointer trans=Transaction::pointer(new newcoin::Transaction());
trans->set_amount( db->getBigInt("Amount"));
trans->set_seqnum( db->getInt("seqnum"));
trans->set_ledgerindex( db->getInt("ledgerIndex"));
db->getBinary("from",tbuf,1000);
trans->set_from(tbuf,20);
db->getBinary("dest",tbuf,1000);
trans->set_dest(tbuf,20);
db->getBinary("pubkey",tbuf,1000);
trans->set_pubkey(tbuf,128);
db->getBinary("sig",tbuf,1000);
trans->set_sig(tbuf,32);
mTransactions.push_back(trans);
}
}
sql="SELECT Accounts.* from Acconts,LedgerAcountMap where Accounts.AccountID=LedgerAccountMap.AccountID and LedgerAccountMap.LedgerID=";
sql.append(buf);
if(db->executeSQL(sql.c_str()))
{
while(db->getNextRow())
{
uint160 address;
db->getBinary("Address",address.begin(),address.GetSerializeSize());
mAccounts[address].first=db->getBigInt("Amount");
mAccounts[address].second=db->getInt("SeqNum");
}
}
return(true);
}
}
return(false);
}
void Ledger::save()
{
Database* db=theApp->getDB();
string sql="SELECT ledgerID from Ledgers where hash=";
string hashStr;
db->escape(mHash.begin(),mHash.GetSerializeSize(),hashStr);
sql.append(hashStr);
if(db->executeSQL(sql.c_str()))
{
db->startIterRows();
if(db->getNextRow())
{ // this Ledger is already in the DB. We don't need to do anything since the hashes are the same
db->endIterRows();
}else
{ // this ledger isn't in the DB
char buf[100];
sql="INSERT INTO Ledgers (LedgerIndex,Hash,ParentHash,FeeHeld) values (";
sprintf(buf, "%d", mIndex);
sql.append(buf);
sql.append(",");
sql.append(buf);
sql.append(",");
sql.append(buf);
sql.append(",");
sprintf(buf, "%llu", mFeeHeld);
sql.append(buf);
sql.append(")");
sql="SELECT LAST_INSERT_ID()";
}
}
}
int64 Ledger::getAmountHeld(const uint160& address)
{
if(mAccounts.count(address))
{
return(mAccounts[address].first);
}
return(0);
}
Ledger::Account* Ledger::getAccount(const uint160& address)
{
if(mAccounts.count(address))
{
return(&(mAccounts[address]));
}
return(NULL);
}
uint256& Ledger::getHash()
{
if(!mValidHash) hash();
return(mHash);
}
uint256& Ledger::getSignature()
{
if(!mValidSig) sign();
return(mSignature);
}
void Ledger::publishValidation()
{
PackedMessage::pointer packet=Peer::createValidation(shared_from_this());
theApp->getConnectionPool().relayMessage(NULL,packet);
}
void Ledger::sign()
{
// TODO: Ledger::sign()
}
void Ledger::hash()
{
// TODO: Ledger::hash()
}
/*
uint64 Ledger::getAmount(std::string address)
{
return(mAccounts[NewcoinAddress:: address].first);
}*/
// returns true if the from account has enough for the transaction and seq num is correct
bool Ledger::addTransaction(Transaction::pointer trans,bool checkDuplicate)
{
if(checkDuplicate && hasTransaction(trans)) return(false);
if(mParent)
{ // check the lineage of the from addresses
uint160 address=protobufTo160(trans->from());
if(mAccounts.count(address))
{
pair<uint64,uint32> account=mAccounts[address];
if( (account.first<trans->amount()) &&
(trans->seqnum()==account.second) )
{
account.first -= trans->amount();
account.second++;
mAccounts[address]=account;
uint160 destAddress=protobufTo160(trans->dest());
Account destAccount=mAccounts[destAddress];
destAccount.first += trans->amount();
mAccounts[destAddress]=destAccount;
mValidSig=false;
mValidHash=false;
mTransactions.push_back(trans);
if(mChild)
{
mChild->parentAddedTransaction(trans);
}
return(true);
}else
{
mDiscardedTransactions.push_back(trans);
return false;
}
}else
{
mDiscardedTransactions.push_back(trans);
return false;
}
}else
{ // we have no way to know so just hold on to it but don't add to the accounts
mValidSig=false;
mValidHash=false;
mDiscardedTransactions.push_back(trans);
return(true);
}
}
// Don't check the amounts. We will do this at the end.
void Ledger::addTransactionAllowNeg(Transaction::pointer trans)
{
uint160 fromAddress=protobufTo160(trans->from());
if(mAccounts.count(fromAddress))
{
Account fromAccount=mAccounts[fromAddress];
if(trans->seqnum()==fromAccount.second)
{
fromAccount.first -= trans->amount();
fromAccount.second++;
mAccounts[fromAddress]=fromAccount;
uint160 destAddress=protobufTo160(trans->dest());
Account destAccount=mAccounts[destAddress];
destAccount.first += trans->amount();
mAccounts[destAddress]=destAccount;
mTransactions.push_back(trans);
}else
{ // invalid seqnum
mDiscardedTransactions.push_back(trans);
}
}else
{
if(trans->seqnum()==0)
{
mAccounts[fromAddress]=Account(-((int64)trans->amount()),1);
uint160 destAddress=protobufTo160(trans->dest());
Account destAccount=mAccounts[destAddress];
destAccount.first += trans->amount();
mAccounts[destAddress]=destAccount;
mTransactions.push_back(trans);
}else
{
mDiscardedTransactions.push_back(trans);
}
}
}
// start from your parent and go through every transaction
// calls this on its child if recursive is set
void Ledger::recalculate(bool recursive)
{
if(mParent)
{
mValidSig=false;
mValidHash=false;
mAccounts.clear();
mAccounts=mParent->getAccounts();
list<Transaction::pointer> firstTransactions=mTransactions;
list<Transaction::pointer> secondTransactions=mDiscardedTransactions;
mTransactions.clear();
mDiscardedTransactions.clear();
firstTransactions.sort(gTransactionSorter);
secondTransactions.sort(gTransactionSorter);
// don't check balances until the end
BOOST_FOREACH(Transaction::pointer trans,firstTransactions)
{
addTransactionAllowNeg(trans);
}
BOOST_FOREACH(Transaction::pointer trans,secondTransactions)
{
addTransactionAllowNeg(trans);
}
correctAccounts();
if(mChild && recursive) mChild->recalculate();
}else
{
cout << "Can't recalculate if there is no parent" << endl;
}
}
void Ledger::parentAddedTransaction(Transaction::pointer cause)
{
// TODO: optimize we can make this more efficient at some point. For now just redo everything
recalculate();
/*
// IMPORTANT: these changes can't change the sequence number. This means we only need to check the dest account
// If there was a seqnum change we have to re-do all the transactions again
// There was a change to the balances of the parent ledger
// This could cause:
// an account to now be negative so we have to discard one
// a discarded transaction to be pulled back in
// seqnum invalidation
uint160 fromAddress=protobufTo160(cause->from());
uint160 destAddress=protobufTo160(cause->dest());
Account* fromAccount=getAccount(fromAddress);
Account* destAccount=getAccount(destAddress);
if(fromAccount)
{
if(fromAccount->first<cause->amount())
{
fromAccount->first -= cause->amount();
fromAccount->second = cause->seqnum()+1;
mAccounts[fromAddress] = *fromAccount;
}else cout << "This shouldn't happen2" << endl;
}else
{
cout << "This shouldn't happen" << endl;
}
if(destAccount)
{
destAccount->first += cause->amount();
mAccounts[destAddress]= *destAccount;
}else
{
mAccounts[destAddress]=pair<uint64,uint32>(cause->amount(),cause->seqnum());
}
// look for discarded transactions
BOOST_FOREACH(Transaction::pointer trans,)
*/
}
bool Ledger::hasTransaction(Transaction::pointer needle)
{
BOOST_FOREACH(Transaction::pointer trans,mTransactions)
{
if( Transaction::isEqual(needle,trans) ) return(true);
}
BOOST_FOREACH(Transaction::pointer disTrans,mDiscardedTransactions)
{
if( Transaction::isEqual(needle,disTrans) ) return(true);
}
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<uint160, Account > a1=l1->getAccounts();
map<uint160, Account > a2=l2->getAccounts();
return(a1==a2);
}
void Ledger::mergeIn(Ledger::pointer other)
{
list<Transaction::pointer>& otherTransactions=other->getTransactions();
BOOST_FOREACH(Transaction::pointer trans,otherTransactions)
{
addTransactionAllowNeg(trans);
}
correctAccounts();
}
void Ledger::correctAccounts()
{
BOOST_FOREACH(PAIR(const uint160, Account)& 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(const uint160& address)
{
list<uint160> effected;
// do this in reverse so we take of the higher seqnum first
for( list<Transaction::pointer>::reverse_iterator iter=mTransactions.rbegin(); iter != mTransactions.rend(); )
{
Transaction::pointer trans= *iter;
if(protobufTo160(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=protobufTo160(trans->dest());
Account destAccount=mAccounts[destAddress];
destAccount.first -= trans->amount();
mAccounts[destAddress]=destAccount;
if(destAccount.first<0) effected.push_back(destAddress);
list<Transaction::pointer>::iterator temp=mTransactions.erase( --iter.base() );
if(fromAccount.first>=0) break;
iter=list<Transaction::pointer>::reverse_iterator(temp);
}else break;
}else iter--;
}
BOOST_FOREACH(uint160& address,effected)
{
correctAccount(address);
}
}
#endif