mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-19 18:45:52 +00:00
and SQL code. This uses some of the 'magic' properties of elliptic curves to create related families of public and private keys. Wallet encryption is not needed because the private keys do not need to be stored.
535 lines
14 KiB
C++
535 lines
14 KiB
C++
|
|
#include <iostream>
|
|
#include <fstream>
|
|
|
|
#include <boost/lexical_cast.hpp>
|
|
|
|
#include "Application.h"
|
|
#include "Ledger.h"
|
|
#include "newcoin.pb.h"
|
|
#include "PackedMessage.h"
|
|
#include "Config.h"
|
|
#include "Conversion.h"
|
|
#include "BitcoinUtil.h"
|
|
#include "Wallet.h"
|
|
|
|
Ledger::Ledger(const uint160& masterID, uint64 startAmount) :
|
|
mFeeHeld(0), mTimeStamp(0), mLedgerSeq(0),
|
|
mClosed(false), mValidHash(false), mAccepted(false)
|
|
{
|
|
mTransactionMap=SHAMap::pointer(new SHAMap());
|
|
mAccountStateMap=SHAMap::pointer(new SHAMap());
|
|
|
|
AccountState::pointer startAccount=AccountState::pointer(new AccountState(masterID));
|
|
startAccount->credit(startAmount);
|
|
if(!addAccountState(startAccount))
|
|
assert(false);
|
|
}
|
|
|
|
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),
|
|
mClosed(false), mValidHash(false), mAccepted(false)
|
|
{
|
|
updateHash();
|
|
}
|
|
|
|
Ledger::Ledger(Ledger &prevLedger, uint64 ts) : mTimeStamp(ts),
|
|
mClosed(false), mValidHash(false), mAccepted(false),
|
|
mTransactionMap(new SHAMap()), mAccountStateMap(prevLedger.mAccountStateMap)
|
|
{
|
|
mParentHash=prevLedger.getHash();
|
|
mLedgerSeq=prevLedger.mLedgerSeq+1;
|
|
}
|
|
|
|
void Ledger::updateHash()
|
|
{
|
|
Serializer s(116);
|
|
addRaw(s);
|
|
mHash=s.getSHA512Half();
|
|
mValidHash=true;
|
|
}
|
|
|
|
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)
|
|
{
|
|
#ifdef DEBUG
|
|
std::cerr << "Ledger:getAccountState(" << accountID.GetHex() << ")" << std::endl;
|
|
#endif
|
|
ScopedLock l(mTransactionMap->Lock());
|
|
SHAMapItem::pointer item=mAccountStateMap->peekItem(accountID.to256());
|
|
if(!item)
|
|
{
|
|
#ifdef DEBUG
|
|
std::cerr << " notfound" << std::endl;
|
|
#endif
|
|
return AccountState::pointer();
|
|
}
|
|
return AccountState::pointer(new AccountState(item->getData()));
|
|
}
|
|
|
|
uint64 Ledger::getBalance(const uint160& accountID)
|
|
{
|
|
ScopedLock l(mTransactionMap->Lock());
|
|
SHAMapItem::pointer item=mAccountStateMap->peekItem(accountID.to256());
|
|
if(!item) return 0;
|
|
return AccountState(item->getData()).getBalance();
|
|
}
|
|
|
|
bool Ledger::updateAccountState(AccountState::pointer state)
|
|
{
|
|
assert(!mAccepted);
|
|
SHAMapItem::pointer item(new SHAMapItem(state->getAccountID(), state->getRaw()));
|
|
return mAccountStateMap->updateGiveItem(item);
|
|
}
|
|
|
|
bool Ledger::addAccountState(AccountState::pointer state)
|
|
{
|
|
assert(!mAccepted);
|
|
SHAMapItem::pointer item(new SHAMapItem(state->getAccountID(), state->getRaw()));
|
|
return mAccountStateMap->addGiveItem(item);
|
|
}
|
|
|
|
bool Ledger::addTransaction(Transaction::pointer trans)
|
|
{ // low-level - just add to table
|
|
assert(!mAccepted);
|
|
assert(!!trans->getID());
|
|
SHAMapItem::pointer item(new SHAMapItem(trans->getID(), trans->getSigned()->getData()));
|
|
return mTransactionMap->addGiveItem(item);
|
|
}
|
|
|
|
bool Ledger::delTransaction(const uint256& transID)
|
|
{
|
|
assert(!mAccepted);
|
|
return mTransactionMap->delItem(transID);
|
|
}
|
|
|
|
Transaction::pointer Ledger::getTransaction(const uint256& transID)
|
|
{
|
|
ScopedLock l(mTransactionMap->Lock());
|
|
SHAMapItem::pointer item=mTransactionMap->peekItem(transID);
|
|
if(!item) return Transaction::pointer();
|
|
Transaction::pointer trans(new Transaction(item->getData(), true));
|
|
if(trans->getStatus()==NEW) trans->setStatus(mClosed ? COMMITTED : INCLUDED, mLedgerSeq);
|
|
return trans;
|
|
}
|
|
|
|
Ledger::TransResult Ledger::applyTransaction(Transaction::pointer trans)
|
|
{
|
|
assert(!mAccepted);
|
|
boost::recursive_mutex::scoped_lock sl(mLock);
|
|
if(trans->getSourceLedger()>mLedgerSeq) return TR_BADLSEQ;
|
|
|
|
if(trans->getAmount()<trans->getFee())
|
|
{
|
|
#ifdef DEBUG
|
|
std::cerr << "Transaction for " << trans->getAmount() << ", but fee is " <<
|
|
trans->getFee() << std::endl;
|
|
#endif
|
|
return TR_TOOSMALL;
|
|
}
|
|
|
|
if(!mTransactionMap || !mAccountStateMap) return TR_ERROR;
|
|
try
|
|
{
|
|
// already applied?
|
|
Transaction::pointer dupTrans=getTransaction(trans->getID());
|
|
if(dupTrans) return TR_ALREADY;
|
|
|
|
// accounts exist?
|
|
AccountState::pointer fromAccount=getAccountState(trans->getFromAccount());
|
|
AccountState::pointer toAccount=getAccountState(trans->getToAccount());
|
|
|
|
// temporary code -- if toAccount doesn't exist but fromAccount does, create it
|
|
if(!!fromAccount && !toAccount)
|
|
{
|
|
toAccount=AccountState::pointer(new AccountState(trans->getToAccount()));
|
|
updateAccountState(toAccount);
|
|
}
|
|
|
|
if(!fromAccount || !toAccount) return TR_BADACCT;
|
|
|
|
// pass sanity checks?
|
|
if(fromAccount->getBalance()<trans->getAmount())
|
|
{
|
|
#ifdef DEBUG
|
|
std::cerr << "Transaction for " << trans->getAmount() << ", but account has " <<
|
|
fromAccount->getBalance() << std::endl;
|
|
#endif
|
|
return TR_INSUFF;
|
|
}
|
|
#ifdef DEBUG
|
|
if(fromAccount->getSeq()!=trans->getFromAccountSeq())
|
|
std::cerr << "aSeq=" << fromAccount->getSeq() << ", tSeq=" << trans->getFromAccountSeq() << std::endl;
|
|
#endif
|
|
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
|
|
assert(!mAccepted);
|
|
boost::recursive_mutex::scoped_lock sl(mLock);
|
|
if(!mTransactionMap || !mAccountStateMap) return TR_ERROR;
|
|
try
|
|
{
|
|
Transaction::pointer ourTrans=getTransaction(trans->getID());
|
|
if(!ourTrans) return TR_NOTFOUND;
|
|
|
|
// accounts exist
|
|
AccountState::pointer fromAccount=getAccountState(trans->getFromAccount());
|
|
AccountState::pointer toAccount=getAccountState(trans->getToAccount());
|
|
if(!fromAccount || !toAccount) return TR_BADACCT;
|
|
|
|
// pass sanity checks?
|
|
if(toAccount->getBalance()<trans->getAmount()) return TR_INSUFF;
|
|
if(fromAccount->getSeq()!=(trans->getFromAccountSeq()+1)) return TR_PASTASEQ;
|
|
|
|
// reverse
|
|
fromAccount->credit(trans->getAmount());
|
|
fromAccount->decSeq();
|
|
toAccount->charge(trans->getAmount()-trans->getFee());
|
|
mFeeHeld-=trans->getFee();
|
|
trans->setStatus(REMOVED, mLedgerSeq);
|
|
|
|
if(!delTransaction(trans->getID()))
|
|
{
|
|
assert(false);
|
|
return TR_ERROR;
|
|
}
|
|
updateAccountState(fromAccount);
|
|
updateAccountState(toAccount);
|
|
return TR_SUCCESS;
|
|
}
|
|
catch (SHAMapException)
|
|
{
|
|
return TR_ERROR;
|
|
}
|
|
}
|
|
|
|
Ledger::TransResult Ledger::hasTransaction(Transaction::pointer trans)
|
|
{
|
|
boost::recursive_mutex::scoped_lock sl(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;
|
|
}
|
|
}
|
|
|
|
Ledger::pointer Ledger::closeLedger(uint64 timeStamp)
|
|
{ // close this ledger, return a pointer to the next ledger
|
|
// CAUTION: New ledger needs its SHAMap's connected to storage
|
|
setClosed();
|
|
return Ledger::pointer(new Ledger(*this, timeStamp));
|
|
}
|
|
|
|
bool Ledger::unitTest()
|
|
{
|
|
uint160 la1=theApp->getWallet().addFamily(CKey::PassPhraseToKey("This is my payphrase!"), false);
|
|
uint160 la2=theApp->getWallet().addFamily(CKey::PassPhraseToKey("Another payphrase"), false);
|
|
|
|
LocalAccount::pointer l1=theApp->getWallet().getLocalAccount(la1, 0);
|
|
LocalAccount::pointer l2=theApp->getWallet().getLocalAccount(la2, 0);
|
|
|
|
assert(l1->getAddress()==la1);
|
|
|
|
#ifdef DEBUG
|
|
std::cerr << "Account1: " << la1.GetHex() << std::endl;
|
|
std::cerr << "Account2: " << la2.GetHex() << std::endl;
|
|
#endif
|
|
|
|
Ledger::pointer ledger(new Ledger(la1, 100000));
|
|
|
|
ledger=Ledger::pointer(new Ledger(*ledger, 0));
|
|
|
|
AccountState::pointer as=ledger->getAccountState(la1);
|
|
assert(as);
|
|
assert(as->getBalance()==100000);
|
|
assert(as->getSeq()==0);
|
|
as=ledger->getAccountState(la2);
|
|
assert(!as);
|
|
|
|
Transaction::pointer t(new Transaction(NEW, l1, l1->getAcctSeq(), l2->getAddress(), 2500, 0, 1));
|
|
assert(!!t->getID());
|
|
|
|
Ledger::TransResult tr=ledger->applyTransaction(t);
|
|
#ifdef DEBUG
|
|
std::cerr << "Transaction: " << tr << std::endl;
|
|
#endif
|
|
assert(tr==TR_SUCCESS);
|
|
|
|
return true;
|
|
}
|
|
|
|
uint256 Ledger::getHash()
|
|
{
|
|
if(!mValidHash) updateHash();
|
|
return(mHash);
|
|
}
|
|
|
|
void Ledger::saveAcceptedLedger(Ledger::pointer ledger)
|
|
{
|
|
std::string sql="INSERT INTO Ledgers "
|
|
"(LedgerHash,LedgerSeq,PrevHash,FeeHeld,ClosingTime,AccountSetHash,TransSetHash) VALUES ('";
|
|
sql.append(ledger->getHash().GetHex());
|
|
sql.append("','");
|
|
sql.append(boost::lexical_cast<std::string>(ledger->mLedgerSeq));
|
|
sql.append("','");
|
|
sql.append(ledger->mParentHash.GetHex());
|
|
sql.append("','");
|
|
sql.append(boost::lexical_cast<std::string>(ledger->mFeeHeld));
|
|
sql.append("','");
|
|
sql.append(boost::lexical_cast<std::string>(ledger->mTimeStamp));
|
|
sql.append("','");
|
|
sql.append(ledger->mAccountHash.GetHex());
|
|
sql.append("','");
|
|
sql.append(ledger->mTransHash.GetHex());
|
|
sql.append("');");
|
|
|
|
ScopedLock sl(theApp->getDBLock());
|
|
theApp->getDB()->executeSQL(sql.c_str());
|
|
|
|
// write out dirty nodes
|
|
while(ledger->mTransactionMap->flushDirty(64, TRANSACTION_NODE, ledger->mLedgerSeq))
|
|
{ ; }
|
|
while(ledger->mAccountStateMap->flushDirty(64, ACCOUNT_NODE, ledger->mLedgerSeq))
|
|
{ ; }
|
|
|
|
}
|
|
|
|
Ledger::pointer Ledger::getSQL(const std::string& sql)
|
|
{
|
|
uint256 ledgerHash, prevHash, accountHash, transHash;
|
|
uint64 feeHeld, closingTime;
|
|
uint32 ledgerSeq;
|
|
std::string hash;
|
|
|
|
if(1)
|
|
{
|
|
ScopedLock sl(theApp->getDBLock());
|
|
Database *db=theApp->getDB();
|
|
if(!db->executeSQL(sql.c_str()) || !db->getNextRow())
|
|
return Ledger::pointer();
|
|
|
|
db->getStr("LedgerHash", hash);
|
|
ledgerHash.SetHex(hash);
|
|
db->getStr("PrevHash", hash);
|
|
prevHash.SetHex(hash);
|
|
db->getStr("AccountSetHash", hash);
|
|
accountHash.SetHex(hash);
|
|
db->getStr("TransSetHash", hash);
|
|
transHash.SetHex(hash);
|
|
feeHeld=db->getBigInt("FeeHeld");
|
|
closingTime=db->getBigInt("ClosingTime");
|
|
ledgerSeq=db->getBigInt("LedgerSeq");
|
|
}
|
|
|
|
Ledger::pointer ret(new Ledger(prevHash, transHash, accountHash, feeHeld, closingTime, ledgerSeq));
|
|
if(ret->getHash()!=ledgerHash)
|
|
{
|
|
assert(false);
|
|
return Ledger::pointer();
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
Ledger::pointer Ledger::loadByIndex(uint32 ledgerIndex)
|
|
{
|
|
std::string sql="SELECT * from Ledgers WHERE LedgerSeq='";
|
|
sql.append(boost::lexical_cast<std::string>(ledgerIndex));
|
|
sql.append("';");
|
|
return getSQL(sql);
|
|
}
|
|
|
|
Ledger::pointer Ledger::loadByHash(const uint256& ledgerHash)
|
|
{
|
|
std::string sql="SELECT * from Ledgers WHERE LedgerHash='";
|
|
sql.append(ledgerHash.GetHex());
|
|
sql.append("';");
|
|
return getSQL(sql);
|
|
}
|
|
|
|
#if 0
|
|
// 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 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
|