static void createTestAccounts(Application& app, int nbAccounts, std::function<int64(int)> getBalance, std::function<int(int)> getVote) { // set up world SecretKey root = getRoot(); SequenceNumber rootSeq = getAccountSeqNum(root, app) + 1; auto& lm = app.getLedgerManager(); auto& db = app.getDatabase(); int64 setupBalance = lm.getMinBalance(0); LedgerDelta delta(lm.getCurrentLedgerHeader()); for (int i = 0; i < nbAccounts; i++) { int64 bal = getBalance(i); if (bal >= 0) { SecretKey to = getTestAccount(i); applyPaymentTx(app, root, to, rootSeq++, setupBalance); AccountFrame act; REQUIRE(AccountFrame::loadAccount(to.getPublicKey(), act, db)); act.getAccount().balance = bal; act.getAccount().inflationDest.activate() = getTestAccount(getVote(i)).getPublicKey(); act.storeChange(delta, db); } } }
SequenceNumber getAccountSeqNum(SecretKey const& k, Application& app) { AccountFrame account; REQUIRE(AccountFrame::loadAccount(k.getPublicKey(), account, app.getDatabase())); return account.getSeqNum(); }
uint64_t getAccountBalance(SecretKey const& k, Application& app) { AccountFrame account; REQUIRE(AccountFrame::loadAccount(k.getPublicKey(), account, app.getDatabase())); return account.getBalance(); }
bool CreateAccountOpFrame::doApply(LedgerDelta& delta, LedgerManager& ledgerManager) { AccountFrame destAccount; Database& db = ledgerManager.getDatabase(); if (!AccountFrame::loadAccount(mCreateAccount.destination, destAccount, db)) { if (mCreateAccount.startingBalance < ledgerManager.getMinBalance(0)) { // not over the minBalance to make an account innerResult().code(CREATE_ACCOUNT_LOW_RESERVE); return false; } else { int64_t minBalance = mSourceAccount->getMinimumBalance(ledgerManager); if (mSourceAccount->getAccount().balance < (minBalance + mCreateAccount.startingBalance)) { // they don't have enough to send innerResult().code(CREATE_ACCOUNT_UNDERFUNDED); return false; } mSourceAccount->getAccount().balance -= mCreateAccount.startingBalance; mSourceAccount->storeChange(delta, db); destAccount.getAccount().accountID = mCreateAccount.destination; destAccount.getAccount().seqNum = delta.getHeaderFrame().getStartingSequenceNumber(); destAccount.getAccount().balance = mCreateAccount.startingBalance; destAccount.storeAdd(delta, db); innerResult().code(CREATE_ACCOUNT_SUCCESS); return true; } } else { innerResult().code(CREATE_ACCOUNT_ALREADY_EXIST); return false; } }
bool Simulation::loadAccount(AccountInfo& account) { // assumes all nodes are in sync auto app = mNodes.begin()->second; AccountFrame ret; if (!AccountFrame::loadAccount(account.mKey.getPublicKey(), ret, app->getDatabase())) { return false; } account.mBalance = ret.getBalance(); account.mSeq = ret.getSeqNum(); return true; }
void applyPaymentTx(Application& app, SecretKey& from, SecretKey& to, SequenceNumber seq, int64_t amount, PaymentResultCode result) { TransactionFramePtr txFrame; AccountFrame fromAccount; AccountFrame toAccount; bool beforeToExists = AccountFrame::loadAccount( to.getPublicKey(), toAccount, app.getDatabase()); REQUIRE(AccountFrame::loadAccount(from.getPublicKey(), fromAccount, app.getDatabase())); txFrame = createPaymentTx(from, to, seq, amount); LedgerDelta delta(app.getLedgerManager().getCurrentLedgerHeader()); txFrame->apply(delta, app); checkTransaction(*txFrame); auto txResult = txFrame->getResult(); auto innerCode = PaymentOpFrame::getInnerCode(txResult.result.results()[0]); REQUIRE(innerCode == result); REQUIRE(txResult.feeCharged == app.getLedgerManager().getTxFee()); AccountFrame toAccountAfter; bool afterToExists = AccountFrame::loadAccount( to.getPublicKey(), toAccountAfter, app.getDatabase()); if (!(innerCode == PAYMENT_SUCCESS || innerCode == PAYMENT_SUCCESS_MULTI)) { // check that the target account didn't change REQUIRE(beforeToExists == afterToExists); if (beforeToExists && afterToExists) { REQUIRE(memcmp(&toAccount.getAccount(), &toAccountAfter.getAccount(), sizeof(AccountEntry)) == 0); } } else { REQUIRE(afterToExists); } }
vector<Simulation::AccountInfoPtr> Simulation::accountsOutOfSyncWithDb() { vector<AccountInfoPtr> result; int iApp = 0; int64_t totalOffsets = 0; for (auto pair : mNodes) { iApp++; auto app = pair.second; for (auto accountIt = mAccounts.begin() + 1; accountIt != mAccounts.end(); accountIt++) { auto account = *accountIt; AccountFrame accountFrame; bool res = AccountFrame::loadAccount( account->mKey.getPublicKey(), accountFrame, app->getDatabase()); int64_t offset; if (res) { offset = accountFrame.getBalance() - static_cast<int64_t>(account->mBalance); account->mSeq = accountFrame.getSeqNum(); } else { offset = -1; } if (offset != 0) { LOG(DEBUG) << "On node " << iApp << ", account " << account->mId << " is off by " << (offset) << "\t(has " << accountFrame.getBalance() << " should have " << account->mBalance << ")"; totalOffsets += abs(offset); result.push_back(account); } } } LOG(INFO) << "Ledger has not yet caught up to the simulation. totalOffsets: " << totalOffsets; return result; }
bool PaymentOpFrame::doApply(LedgerDelta& delta, LedgerManager& ledgerManager) { AccountFrame destAccount; // if sending to self directly, just mark as success if (mPayment.destination == getSourceID() && mPayment.path.empty()) { innerResult().code(PAYMENT_SUCCESS); return true; } Database& db = ledgerManager.getDatabase(); if (!AccountFrame::loadAccount(mPayment.destination, destAccount, db)) { // this tx is creating an account if (mPayment.currency.type() == CURRENCY_TYPE_NATIVE) { if (mPayment.amount < ledgerManager.getMinBalance(0)) { // not over the minBalance to make an account innerResult().code(PAYMENT_LOW_RESERVE); return false; } else { destAccount.getAccount().accountID = mPayment.destination; destAccount.getAccount().seqNum = delta.getHeaderFrame().getStartingSequenceNumber(); destAccount.getAccount().balance = 0; destAccount.storeAdd(delta, db); } } else { // trying to send credit to an unmade account innerResult().code(PAYMENT_NO_DESTINATION); return false; } } return sendNoCreate(destAccount, delta, ledgerManager); }
bool TransactionFrame::checkSignature(AccountFrame& account, int32_t neededWeight) { vector<Signer> keyWeights; if (account.getAccount().thresholds[0]) keyWeights.push_back( Signer(account.getID(), account.getAccount().thresholds[0])); keyWeights.insert(keyWeights.end(), account.getAccount().signers.begin(), account.getAccount().signers.end()); Hash const& contentsHash = getContentsHash(); // calculate the weight of the signatures int totalWeight = 0; for (size_t i = 0; i < getEnvelope().signatures.size(); i++) { auto const& sig = getEnvelope().signatures[i]; for (auto it = keyWeights.begin(); it != keyWeights.end(); it++) { if ((std::memcmp(sig.hint.data(), (*it).pubKey.data(), sizeof(sig.hint)) == 0) && PublicKey::verifySig((*it).pubKey, sig.signature, contentsHash)) { mUsedSignatures[i] = true; totalWeight += (*it).weight; if (totalWeight >= neededWeight) return true; keyWeights.erase(it); // can't sign twice break; } } } return false; }
static void doInflation(Application& app, int nbAccounts, std::function<int64(int)> getBalance, std::function<int(int)> getVote, int expectedWinnerCount) { // simulate the expected inflation based off the current ledger state std::map<int, int64> balances; // load account balances for (int i = 0; i < nbAccounts; i++) { AccountFrame act; if (getBalance(i) < 0) { balances[i] = -1; REQUIRE(!AccountFrame::loadAccount(getTestAccount(i).getPublicKey(), act, app.getDatabase())); } else { REQUIRE(AccountFrame::loadAccount(getTestAccount(i).getPublicKey(), act, app.getDatabase())); balances[i] = act.getBalance(); // double check that inflationDest is setup properly if (act.getAccount().inflationDest) { REQUIRE(getTestAccount(getVote(i)).getPublicKey() == *act.getAccount().inflationDest); } else { REQUIRE(getVote(i) < 0); } } } LedgerManager& lm = app.getLedgerManager(); LedgerHeader& cur = lm.getCurrentLedgerHeader(); cur.feePool = 10000; int64 expectedTotcoins = cur.totalCoins; int64 expectedFees = cur.feePool; std::vector<int64> expectedBalances; auto root = getRoot(); TransactionFramePtr txFrame = createInflation(root, getAccountSeqNum(root, app) + 1); expectedFees += txFrame->getFee(app); expectedBalances = simulateInflation(nbAccounts, expectedTotcoins, expectedFees, [&](int i) { return balances[i]; }, getVote); // perform actual inflation { LedgerDelta delta(lm.getCurrentLedgerHeader()); REQUIRE(txFrame->apply(delta, app)); delta.commit(); } // verify ledger state LedgerHeader& cur2 = lm.getCurrentLedgerHeader(); REQUIRE(cur2.totalCoins == expectedTotcoins); REQUIRE(cur2.feePool == expectedFees); // verify balances InflationResult const& infResult = getFirstResult(*txFrame).tr().inflationResult(); auto const& payouts = infResult.payouts(); int actualChanges = 0; for (int i = 0; i < nbAccounts; i++) { AccountFrame act; auto const& pk = getTestAccount(i).getPublicKey(); if (expectedBalances[i] < 0) { REQUIRE(!AccountFrame::loadAccount(pk, act, app.getDatabase())); REQUIRE(balances[i] < 0); // account didn't get deleted } else { REQUIRE(AccountFrame::loadAccount(pk, act, app.getDatabase())); REQUIRE(expectedBalances[i] == act.getBalance()); if (expectedBalances[i] != balances[i]) { REQUIRE(balances[i] >= 0); actualChanges++; bool found = false; for (auto const& p : payouts) { if (p.destination == pk) { int64 computedFromResult = balances[i] + p.amount; REQUIRE(computedFromResult == expectedBalances[i]); found = true; break; } } REQUIRE(found); } } } REQUIRE(actualChanges == expectedWinnerCount); REQUIRE(expectedWinnerCount == payouts.size()); }
// work backward to determine how much they need to send to get the // specified amount of currency to the recipient bool PaymentOpFrame::sendNoCreate(AccountFrame& destination, LedgerDelta& delta, LedgerManager& ledgerManager) { Database& db = ledgerManager.getDatabase(); bool multi_mode = mPayment.path.size() != 0; if (multi_mode) { innerResult().code(PAYMENT_SUCCESS_MULTI); } else { innerResult().code(PAYMENT_SUCCESS); } // tracks the last amount that was traded int64_t curBReceived = mPayment.amount; Currency curB = mPayment.currency; // update balances, walks backwards // update last balance in the chain { if (curB.type() == CURRENCY_TYPE_NATIVE) { destination.getAccount().balance += curBReceived; destination.storeChange(delta, db); } else { TrustFrame destLine; if (!TrustFrame::loadTrustLine(destination.getID(), curB, destLine, db)) { innerResult().code(PAYMENT_NO_TRUST); return false; } if (!destLine.isAuthorized()) { innerResult().code(PAYMENT_NOT_AUTHORIZED); return false; } if (!destLine.addBalance(curBReceived)) { innerResult().code(PAYMENT_LINE_FULL); return false; } destLine.storeChange(delta, db); } if (multi_mode) { innerResult().multi().last = SimplePaymentResult(destination.getID(), curB, curBReceived); } } if (multi_mode) { // now, walk the path backwards for (int i = (int)mPayment.path.size() - 1; i >= 0; i--) { int64_t curASent, actualCurBReceived; Currency const& curA = mPayment.path[i]; OfferExchange oe(delta, ledgerManager); // curA -> curB OfferExchange::ConvertResult r = oe.convertWithOffers(curA, INT64_MAX, curASent, curB, curBReceived, actualCurBReceived, nullptr); switch (r) { case OfferExchange::eFilterStop: assert(false); // no filter -> should not happen break; case OfferExchange::eOK: if (curBReceived == actualCurBReceived) { break; } // fall through case OfferExchange::ePartial: innerResult().code(PAYMENT_TOO_FEW_OFFERS); return false; } assert(curBReceived == actualCurBReceived); curBReceived = curASent; // next round, we need to send enough curB = curA; // add offers that got taken on the way // insert in front to match the path's order auto& offers = innerResult().multi().offers; offers.insert(offers.begin(), oe.getOfferTrail().begin(), oe.getOfferTrail().end()); } } // last step: we've reached the first account in the chain, update its // balance int64_t curBSent; curBSent = curBReceived; if (curBSent > mPayment.sendMax) { // make sure not over the max innerResult().code(PAYMENT_OVER_SENDMAX); return false; } if (curB.type() == CURRENCY_TYPE_NATIVE) { int64_t minBalance = mSourceAccount->getMinimumBalance(ledgerManager); if (mSourceAccount->getAccount().balance < (minBalance + curBSent)) { // they don't have enough to send innerResult().code(PAYMENT_UNDERFUNDED); return false; } mSourceAccount->getAccount().balance -= curBSent; mSourceAccount->storeChange(delta, db); } else { AccountFrame issuer; if (!AccountFrame::loadAccount(curB.alphaNum().issuer, issuer, db)) { throw std::runtime_error("sendCredit Issuer not found"); } TrustFrame sourceLineFrame; if (!TrustFrame::loadTrustLine(getSourceID(), curB, sourceLineFrame, db)) { innerResult().code(PAYMENT_UNDERFUNDED); return false; } if (!sourceLineFrame.addBalance(-curBSent)) { innerResult().code(PAYMENT_UNDERFUNDED); return false; } sourceLineFrame.storeChange(delta, db); } return true; }