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); } } }
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; } }
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); } }
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; }