// // XXX Make sure all fields are recognized in transactions. // #include #include #include #include #include "TransactionEngine.h" #include "../json/writer.h" #include "Config.h" #include "Contract.h" #include "Interpreter.h" #include "Log.h" #include "RippleCalc.h" #include "TransactionFormats.h" #include "utils.h" #define RIPPLE_PATHS_MAX 3 // Set the authorized public key for an account. May also set the generator map. TER TransactionEngine::setAuthorized(const SerializedTransaction& txn, bool bMustSetGenerator) { // // Verify that submitter knows the private key for the generator. // Otherwise, people could deny access to generators. // std::vector vucCipher = txn.getITFieldVL(sfGenerator); std::vector vucPubKey = txn.getITFieldVL(sfPublicKey); std::vector vucSignature = txn.getITFieldVL(sfSignature); NewcoinAddress naAccountPublic = NewcoinAddress::createAccountPublic(vucPubKey); if (!naAccountPublic.accountPublicVerify(Serializer::getSHA512Half(vucCipher), vucSignature)) { Log(lsWARNING) << "createGenerator: bad signature unauthorized generator claim"; return tefBAD_GEN_AUTH; } // Create generator. uint160 hGeneratorID = naAccountPublic.getAccountID(); SLE::pointer sleGen = entryCache(ltGENERATOR_MAP, Ledger::getGeneratorIndex(hGeneratorID)); if (!sleGen) { // Create the generator. Log(lsTRACE) << "createGenerator: creating generator"; sleGen = entryCreate(ltGENERATOR_MAP, Ledger::getGeneratorIndex(hGeneratorID)); sleGen->setIFieldVL(sfGenerator, vucCipher); } else if (bMustSetGenerator) { // Doing a claim. Must set generator. // Generator is already in use. Regular passphrases limited to one wallet. Log(lsWARNING) << "createGenerator: generator already in use"; return tefGEN_IN_USE; } // Set the public key needed to use the account. uint160 uAuthKeyID = bMustSetGenerator ? hGeneratorID // Claim : txn.getITFieldAccount(sfAuthorizedKey); // PasswordSet mTxnAccount->setIFieldAccount(sfAuthorizedKey, uAuthKeyID); return tesSUCCESS; } TER TransactionEngine::doAccountSet(const SerializedTransaction& txn) { Log(lsINFO) << "doAccountSet>"; // // EmailHash // if (txn.getITFieldPresent(sfEmailHash)) { uint128 uHash = txn.getITFieldH128(sfEmailHash); if (!uHash) { Log(lsINFO) << "doAccountSet: unset email hash"; mTxnAccount->makeIFieldAbsent(sfEmailHash); } else { Log(lsINFO) << "doAccountSet: set email hash"; mTxnAccount->setIFieldH128(sfEmailHash, uHash); } } // // WalletLocator // if (txn.getITFieldPresent(sfWalletLocator)) { uint256 uHash = txn.getITFieldH256(sfWalletLocator); if (!uHash) { Log(lsINFO) << "doAccountSet: unset wallet locator"; mTxnAccount->makeIFieldAbsent(sfEmailHash); } else { Log(lsINFO) << "doAccountSet: set wallet locator"; mTxnAccount->setIFieldH256(sfWalletLocator, uHash); } } // // MessageKey // if (!txn.getITFieldPresent(sfMessageKey)) { nothing(); } else { Log(lsINFO) << "doAccountSet: set message key"; mTxnAccount->setIFieldVL(sfMessageKey, txn.getITFieldVL(sfMessageKey)); } // // Domain // if (txn.getITFieldPresent(sfDomain)) { std::vector vucDomain = txn.getITFieldVL(sfDomain); if (vucDomain.empty()) { Log(lsINFO) << "doAccountSet: unset domain"; mTxnAccount->makeIFieldAbsent(sfDomain); } else { Log(lsINFO) << "doAccountSet: set domain"; mTxnAccount->setIFieldVL(sfDomain, vucDomain); } } // // TransferRate // if (txn.getITFieldPresent(sfTransferRate)) { uint32 uRate = txn.getITFieldU32(sfTransferRate); if (!uRate) { Log(lsINFO) << "doAccountSet: unset transfer rate"; mTxnAccount->makeIFieldAbsent(sfTransferRate); } else { Log(lsINFO) << "doAccountSet: set transfer rate"; mTxnAccount->setIFieldU32(sfTransferRate, uRate); } } // // PublishHash && PublishSize // bool bPublishHash = txn.getITFieldPresent(sfPublishHash); bool bPublishSize = txn.getITFieldPresent(sfPublishSize); if (bPublishHash ^ bPublishSize) { Log(lsINFO) << "doAccountSet: bad publish"; return temBAD_PUBLISH; } else if (bPublishHash && bPublishSize) { uint256 uHash = txn.getITFieldH256(sfPublishHash); uint32 uSize = txn.getITFieldU32(sfPublishSize); if (!uHash) { Log(lsINFO) << "doAccountSet: unset publish"; mTxnAccount->makeIFieldAbsent(sfPublishHash); mTxnAccount->makeIFieldAbsent(sfPublishSize); } else { Log(lsINFO) << "doAccountSet: set publish"; mTxnAccount->setIFieldH256(sfPublishHash, uHash); mTxnAccount->setIFieldU32(sfPublishSize, uSize); } } Log(lsINFO) << "doAccountSet<"; return tesSUCCESS; } TER TransactionEngine::doClaim(const SerializedTransaction& txn) { Log(lsINFO) << "doClaim>"; TER terResult = setAuthorized(txn, true); Log(lsINFO) << "doClaim<"; return terResult; } TER TransactionEngine::doCreditSet(const SerializedTransaction& txn) { TER terResult = tesSUCCESS; Log(lsINFO) << "doCreditSet>"; // Check if destination makes sense. uint160 uDstAccountID = txn.getITFieldAccount(sfDestination); if (!uDstAccountID) { Log(lsINFO) << "doCreditSet: Invalid transaction: Destination account not specifed."; return temDST_NEEDED; } else if (mTxnAccountID == uDstAccountID) { Log(lsINFO) << "doCreditSet: Invalid transaction: Can not extend credit to self."; return temDST_IS_SRC; } SLE::pointer sleDst = entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uDstAccountID)); if (!sleDst) { Log(lsINFO) << "doCreditSet: Delay transaction: Destination account does not exist."; return terNO_DST; } const bool bFlipped = mTxnAccountID > uDstAccountID; // true, iff current is not lowest. const bool bLimitAmount = txn.getITFieldPresent(sfLimitAmount); const STAmount saLimitAmount = bLimitAmount ? txn.getITFieldAmount(sfLimitAmount) : STAmount(); const bool bQualityIn = txn.getITFieldPresent(sfQualityIn); const uint32 uQualityIn = bQualityIn ? txn.getITFieldU32(sfQualityIn) : 0; const bool bQualityOut = txn.getITFieldPresent(sfQualityOut); const uint32 uQualityOut = bQualityIn ? txn.getITFieldU32(sfQualityOut) : 0; const uint160 uCurrencyID = saLimitAmount.getCurrency(); bool bDelIndex = false; SLE::pointer sleRippleState = entryCache(ltRIPPLE_STATE, Ledger::getRippleStateIndex(mTxnAccountID, uDstAccountID, uCurrencyID)); if (sleRippleState) { // A line exists in one or more directions. #if 0 if (!saLimitAmount) { // Zeroing line. uint160 uLowID = sleRippleState->getIValueFieldAccount(sfLowID).getAccountID(); uint160 uHighID = sleRippleState->getIValueFieldAccount(sfHighID).getAccountID(); bool bLow = uLowID == uSrcAccountID; bool bHigh = uLowID == uDstAccountID; bool bBalanceZero = !sleRippleState->getIValueFieldAmount(sfBalance); STAmount saDstLimit = sleRippleState->getIValueFieldAmount(bSendLow ? sfLowLimit : sfHighLimit); bool bDstLimitZero = !saDstLimit; assert(bLow || bHigh); if (bBalanceZero && bDstLimitZero) { // Zero balance and eliminating last limit. bDelIndex = true; terResult = dirDelete(false, uSrcRef, Ledger::getOwnerDirIndex(mTxnAccountID), sleRippleState->getIndex(), false); } } #endif if (!bDelIndex) { if (bLimitAmount) sleRippleState->setIFieldAmount(bFlipped ? sfHighLimit: sfLowLimit , saLimitAmount); if (!bQualityIn) { nothing(); } else if (uQualityIn) { sleRippleState->setIFieldU32(bFlipped ? sfLowQualityIn : sfHighQualityIn, uQualityIn); } else { sleRippleState->makeIFieldAbsent(bFlipped ? sfLowQualityIn : sfHighQualityIn); } if (!bQualityOut) { nothing(); } else if (uQualityOut) { sleRippleState->setIFieldU32(bFlipped ? sfLowQualityOut : sfHighQualityOut, uQualityOut); } else { sleRippleState->makeIFieldAbsent(bFlipped ? sfLowQualityOut : sfHighQualityOut); } entryModify(sleRippleState); } Log(lsINFO) << "doCreditSet: Modifying ripple line: bDelIndex=" << bDelIndex; } // Line does not exist. else if (!saLimitAmount) { Log(lsINFO) << "doCreditSet: Redundant: Setting non-existant ripple line to 0."; return terNO_LINE_NO_ZERO; } else { // Create a new ripple line. sleRippleState = entryCreate(ltRIPPLE_STATE, Ledger::getRippleStateIndex(mTxnAccountID, uDstAccountID, uCurrencyID)); Log(lsINFO) << "doCreditSet: Creating ripple line: " << sleRippleState->getIndex().ToString(); sleRippleState->setIFieldAmount(sfBalance, STAmount(uCurrencyID, ACCOUNT_ONE)); // Zero balance in currency. sleRippleState->setIFieldAmount(bFlipped ? sfHighLimit : sfLowLimit, saLimitAmount); sleRippleState->setIFieldAmount(bFlipped ? sfLowLimit : sfHighLimit, STAmount(uCurrencyID, ACCOUNT_ONE)); sleRippleState->setIFieldAccount(bFlipped ? sfHighID : sfLowID, mTxnAccountID); sleRippleState->setIFieldAccount(bFlipped ? sfLowID : sfHighID, uDstAccountID); if (uQualityIn) sleRippleState->setIFieldU32(bFlipped ? sfHighQualityIn : sfLowQualityIn, uQualityIn); if (uQualityOut) sleRippleState->setIFieldU32(bFlipped ? sfHighQualityOut : sfLowQualityOut, uQualityOut); uint64 uSrcRef; // Ignored, dirs never delete. terResult = mNodes.dirAdd(uSrcRef, Ledger::getOwnerDirIndex(mTxnAccountID), sleRippleState->getIndex()); if (tesSUCCESS == terResult) terResult = mNodes.dirAdd(uSrcRef, Ledger::getOwnerDirIndex(uDstAccountID), sleRippleState->getIndex()); } Log(lsINFO) << "doCreditSet<"; return terResult; } TER TransactionEngine::doNicknameSet(const SerializedTransaction& txn) { std::cerr << "doNicknameSet>" << std::endl; const uint256 uNickname = txn.getITFieldH256(sfNickname); const bool bMinOffer = txn.getITFieldPresent(sfMinimumOffer); const STAmount saMinOffer = bMinOffer ? txn.getITFieldAmount(sfAmount) : STAmount(); SLE::pointer sleNickname = entryCache(ltNICKNAME, uNickname); if (sleNickname) { // Edit old entry. sleNickname->setIFieldAccount(sfAccount, mTxnAccountID); if (bMinOffer && saMinOffer) { sleNickname->setIFieldAmount(sfMinimumOffer, saMinOffer); } else { sleNickname->makeIFieldAbsent(sfMinimumOffer); } entryModify(sleNickname); } else { // Make a new entry. // XXX Need to include authorization limiting for first year. sleNickname = entryCreate(ltNICKNAME, Ledger::getNicknameIndex(uNickname)); std::cerr << "doNicknameSet: Creating nickname node: " << sleNickname->getIndex().ToString() << std::endl; sleNickname->setIFieldAccount(sfAccount, mTxnAccountID); if (bMinOffer && saMinOffer) sleNickname->setIFieldAmount(sfMinimumOffer, saMinOffer); } std::cerr << "doNicknameSet<" << std::endl; return tesSUCCESS; } TER TransactionEngine::doPasswordFund(const SerializedTransaction& txn) { std::cerr << "doPasswordFund>" << std::endl; const uint160 uDstAccountID = txn.getITFieldAccount(sfDestination); SLE::pointer sleDst = mTxnAccountID == uDstAccountID ? mTxnAccount : entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uDstAccountID)); if (!sleDst) { // Destination account does not exist. std::cerr << "doPasswordFund: Delay transaction: Destination account does not exist." << std::endl; return terSET_MISSING_DST; } if (sleDst->getFlags() & lsfPasswordSpent) { sleDst->clearFlag(lsfPasswordSpent); std::cerr << "doPasswordFund: Clearing spent." << sleDst->getFlags() << std::endl; if (mTxnAccountID != uDstAccountID) { std::cerr << "doPasswordFund: Destination modified." << std::endl; entryModify(sleDst); } } std::cerr << "doPasswordFund<" << std::endl; return tesSUCCESS; } TER TransactionEngine::doPasswordSet(const SerializedTransaction& txn) { std::cerr << "doPasswordSet>" << std::endl; if (mTxnAccount->getFlags() & lsfPasswordSpent) { std::cerr << "doPasswordSet: Delay transaction: Funds already spent." << std::endl; return terFUNDS_SPENT; } mTxnAccount->setFlag(lsfPasswordSpent); TER terResult = setAuthorized(txn, false); std::cerr << "doPasswordSet<" << std::endl; return terResult; } // XXX Need to audit for things like setting accountID not having memory. TER TransactionEngine::doPayment(const SerializedTransaction& txn, const TransactionEngineParams params) { // Ripple if source or destination is non-native or if there are paths. const uint32 uTxFlags = txn.getFlags(); const bool bCreate = isSetBit(uTxFlags, tfCreateAccount); const bool bPartialPayment = isSetBit(uTxFlags, tfPartialPayment); const bool bLimitQuality = isSetBit(uTxFlags, tfLimitQuality); const bool bNoRippleDirect = isSetBit(uTxFlags, tfNoRippleDirect); const bool bPaths = txn.getITFieldPresent(sfPaths); const bool bMax = txn.getITFieldPresent(sfSendMax); const uint160 uDstAccountID = txn.getITFieldAccount(sfDestination); const STAmount saDstAmount = txn.getITFieldAmount(sfAmount); const STAmount saMaxAmount = bMax ? txn.getITFieldAmount(sfSendMax) : saDstAmount; const uint160 uSrcCurrency = saMaxAmount.getCurrency(); const uint160 uDstCurrency = saDstAmount.getCurrency(); Log(lsINFO) << boost::str(boost::format("doPayment> saMaxAmount=%s saDstAmount=%s") % saMaxAmount.getFullText() % saDstAmount.getFullText()); if (!uDstAccountID) { Log(lsINFO) << "doPayment: Invalid transaction: Payment destination account not specifed."; return temDST_NEEDED; } else if (!saDstAmount.isPositive()) { Log(lsINFO) << "doPayment: Invalid transaction: bad amount: " << saDstAmount.getHumanCurrency() << " " << saDstAmount.getText(); return temBAD_AMOUNT; } else if (mTxnAccountID == uDstAccountID && uSrcCurrency == uDstCurrency && !bPaths) { Log(lsINFO) << boost::str(boost::format("doPayment: Invalid transaction: Redunant transaction: src=%s, dst=%s, src_cur=%s, dst_cur=%s") % mTxnAccountID.ToString() % uDstAccountID.ToString() % uSrcCurrency.ToString() % uDstCurrency.ToString()); return temREDUNDANT; } else if (bMax && ((saMaxAmount == saDstAmount && saMaxAmount.getCurrency() == saDstAmount.getCurrency()) || (saDstAmount.isNative() && saMaxAmount.isNative()))) { Log(lsINFO) << "doPayment: Invalid transaction: bad SendMax."; return temINVALID; } SLE::pointer sleDst = entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uDstAccountID)); if (!sleDst) { // Destination account does not exist. if (bCreate && !saDstAmount.isNative()) { // This restriction could be relaxed. Log(lsINFO) << "doPayment: Invalid transaction: Create account may only fund XNS."; return temCREATEXNS; } else if (!bCreate) { Log(lsINFO) << "doPayment: Delay transaction: Destination account does not exist."; return terNO_DST; } // Create the account. sleDst = entryCreate(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uDstAccountID)); sleDst->setIFieldAccount(sfAccount, uDstAccountID); sleDst->setIFieldU32(sfSequence, 1); } else { entryModify(sleDst); } TER terResult; // XXX Should bMax be sufficient to imply ripple? const bool bRipple = bPaths || bMax || !saDstAmount.isNative(); if (bRipple) { // Ripple payment STPathSet spsPaths = txn.getITFieldPathSet(sfPaths); STAmount saMaxAmountAct; STAmount saDstAmountAct; terResult = isSetBit(params, tapOPEN_LEDGER) && spsPaths.getPathCount() > RIPPLE_PATHS_MAX ? telBAD_PATH_COUNT : RippleCalc::rippleCalc( mNodes, saMaxAmountAct, saDstAmountAct, saMaxAmount, saDstAmount, uDstAccountID, mTxnAccountID, spsPaths, bPartialPayment, bLimitQuality, bNoRippleDirect); } else { // Direct XNS payment. STAmount saSrcXNSBalance = mTxnAccount->getIValueFieldAmount(sfBalance); if (saSrcXNSBalance < saDstAmount) { // Transaction might succeed, if applied in a different order. Log(lsINFO) << "doPayment: Delay transaction: Insufficent funds."; terResult = terUNFUNDED; } else { mTxnAccount->setIFieldAmount(sfBalance, saSrcXNSBalance - saDstAmount); sleDst->setIFieldAmount(sfBalance, sleDst->getIValueFieldAmount(sfBalance) + saDstAmount); terResult = tesSUCCESS; } } std::string strToken; std::string strHuman; if (transResultInfo(terResult, strToken, strHuman)) { Log(lsINFO) << boost::str(boost::format("doPayment: %s: %s") % strToken % strHuman); } else { assert(false); } return terResult; } TER TransactionEngine::doWalletAdd(const SerializedTransaction& txn) { std::cerr << "WalletAdd>" << std::endl; const std::vector vucPubKey = txn.getITFieldVL(sfPublicKey); const std::vector vucSignature = txn.getITFieldVL(sfSignature); const uint160 uAuthKeyID = txn.getITFieldAccount(sfAuthorizedKey); const NewcoinAddress naMasterPubKey = NewcoinAddress::createAccountPublic(vucPubKey); const uint160 uDstAccountID = naMasterPubKey.getAccountID(); if (!naMasterPubKey.accountPublicVerify(Serializer::getSHA512Half(uAuthKeyID.begin(), uAuthKeyID.size()), vucSignature)) { std::cerr << "WalletAdd: unauthorized: bad signature " << std::endl; return tefBAD_ADD_AUTH; } SLE::pointer sleDst = entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uDstAccountID)); if (sleDst) { std::cerr << "WalletAdd: account already created" << std::endl; return tefCREATED; } STAmount saAmount = txn.getITFieldAmount(sfAmount); STAmount saSrcBalance = mTxnAccount->getIValueFieldAmount(sfBalance); if (saSrcBalance < saAmount) { std::cerr << boost::str(boost::format("WalletAdd: Delay transaction: insufficent balance: balance=%s amount=%s") % saSrcBalance.getText() % saAmount.getText()) << std::endl; return terUNFUNDED; } // Deduct initial balance from source account. mTxnAccount->setIFieldAmount(sfBalance, saSrcBalance-saAmount); // Create the account. sleDst = entryCreate(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uDstAccountID)); sleDst->setIFieldAccount(sfAccount, uDstAccountID); sleDst->setIFieldU32(sfSequence, 1); sleDst->setIFieldAmount(sfBalance, saAmount); sleDst->setIFieldAccount(sfAuthorizedKey, uAuthKeyID); std::cerr << "WalletAdd<" << std::endl; return tesSUCCESS; } TER TransactionEngine::doInvoice(const SerializedTransaction& txn) { return temUNKNOWN; } // Take as much as possible. Adjusts account balances. Charges fees on top to taker. // --> uBookBase: The order book to take against. // --> saTakerPays: What the taker offers (w/ issuer) // --> saTakerGets: What the taker wanted (w/ issuer) // <-- saTakerPaid: What taker paid not including fees. To reduce an offer. // <-- saTakerGot: What taker got not including fees. To reduce an offer. // <-- terResult: tesSUCCESS or terNO_ACCOUNT // XXX: Fees should be paid by the source of the currency. TER TransactionEngine::takeOffers( bool bPassive, const uint256& uBookBase, const uint160& uTakerAccountID, const SLE::pointer& sleTakerAccount, const STAmount& saTakerPays, const STAmount& saTakerGets, STAmount& saTakerPaid, STAmount& saTakerGot) { assert(saTakerPays && saTakerGets); Log(lsINFO) << "takeOffers: against book: " << uBookBase.ToString(); uint256 uTipIndex = uBookBase; const uint256 uBookEnd = Ledger::getQualityNext(uBookBase); const uint64 uTakeQuality = STAmount::getRate(saTakerGets, saTakerPays); const uint160 uTakerPaysAccountID = saTakerPays.getIssuer(); const uint160 uTakerGetsAccountID = saTakerGets.getIssuer(); TER terResult = temUNCERTAIN; boost::unordered_set usOfferUnfundedFound; // Offers found unfunded. boost::unordered_set usOfferUnfundedBecame; // Offers that became unfunded. boost::unordered_set usAccountTouched; // Accounts touched. saTakerPaid = 0; saTakerGot = 0; while (temUNCERTAIN == terResult) { SLE::pointer sleOfferDir; uint64 uTipQuality; // Figure out next offer to take, if needed. if (saTakerGets != saTakerGot && saTakerPays != saTakerPaid) { // Taker has needs. sleOfferDir = entryCache(ltDIR_NODE, mLedger->getNextLedgerIndex(uTipIndex, uBookEnd)); if (sleOfferDir) { Log(lsINFO) << "takeOffers: possible counter offer found"; uTipIndex = sleOfferDir->getIndex(); uTipQuality = Ledger::getQuality(uTipIndex); } else { Log(lsINFO) << "takeOffers: counter offer book is empty: " << uTipIndex.ToString() << " ... " << uBookEnd.ToString(); } } if (!sleOfferDir // No offer directory to take. || uTakeQuality < uTipQuality // No offer's of sufficient quality available. || (bPassive && uTakeQuality == uTipQuality)) { // Done. Log(lsINFO) << "takeOffers: done"; terResult = tesSUCCESS; } else { // Have an offer directory to consider. Log(lsINFO) << "takeOffers: considering dir : " << sleOfferDir->getJson(0); SLE::pointer sleBookNode; unsigned int uBookEntry; uint256 uOfferIndex; mNodes.dirFirst(uTipIndex, sleBookNode, uBookEntry, uOfferIndex); SLE::pointer sleOffer = entryCache(ltOFFER, uOfferIndex); Log(lsINFO) << "takeOffers: considering offer : " << sleOffer->getJson(0); const uint160 uOfferOwnerID = sleOffer->getIValueFieldAccount(sfAccount).getAccountID(); STAmount saOfferPays = sleOffer->getIValueFieldAmount(sfTakerGets); STAmount saOfferGets = sleOffer->getIValueFieldAmount(sfTakerPays); if (sleOffer->getIFieldPresent(sfExpiration) && sleOffer->getIFieldU32(sfExpiration) <= mLedger->getParentCloseTimeNC()) { // Offer is expired. Expired offers are considered unfunded. Delete it. Log(lsINFO) << "takeOffers: encountered expired offer"; usOfferUnfundedFound.insert(uOfferIndex); } else if (uOfferOwnerID == uTakerAccountID) { // Would take own offer. Consider old offer expired. Delete it. Log(lsINFO) << "takeOffers: encountered taker's own old offer"; usOfferUnfundedFound.insert(uOfferIndex); } else { // Get offer funds available. Log(lsINFO) << "takeOffers: saOfferPays=" << saOfferPays.getFullText(); STAmount saOfferFunds = mNodes.accountFunds(uOfferOwnerID, saOfferPays); STAmount saTakerFunds = mNodes.accountFunds(uTakerAccountID, saTakerPays); SLE::pointer sleOfferAccount; // Owner of offer. if (!saOfferFunds.isPositive()) { // Offer is unfunded, possibly due to previous balance action. Log(lsINFO) << "takeOffers: offer unfunded: delete"; boost::unordered_set::iterator account = usAccountTouched.find(uOfferOwnerID); if (account != usAccountTouched.end()) { // Previously touched account. usOfferUnfundedBecame.insert(uOfferIndex); // Delete unfunded offer on success. } else { // Never touched source account. usOfferUnfundedFound.insert(uOfferIndex); // Delete found unfunded offer when possible. } } else { STAmount saPay = saTakerPays - saTakerPaid; if (saTakerFunds < saPay) saPay = saTakerFunds; STAmount saSubTakerPaid; STAmount saSubTakerGot; Log(lsINFO) << "takeOffers: applyOffer: saTakerPays: " << saTakerPays.getFullText(); Log(lsINFO) << "takeOffers: applyOffer: saTakerPaid: " << saTakerPaid.getFullText(); Log(lsINFO) << "takeOffers: applyOffer: saTakerFunds: " << saTakerFunds.getFullText(); Log(lsINFO) << "takeOffers: applyOffer: saOfferFunds: " << saOfferFunds.getFullText(); Log(lsINFO) << "takeOffers: applyOffer: saPay: " << saPay.getFullText(); Log(lsINFO) << "takeOffers: applyOffer: saOfferPays: " << saOfferPays.getFullText(); Log(lsINFO) << "takeOffers: applyOffer: saOfferGets: " << saOfferGets.getFullText(); Log(lsINFO) << "takeOffers: applyOffer: saTakerPays: " << saTakerPays.getFullText(); Log(lsINFO) << "takeOffers: applyOffer: saTakerGets: " << saTakerGets.getFullText(); bool bOfferDelete = STAmount::applyOffer( saOfferFunds, saPay, // Driver XXX need to account for fees. saOfferPays, saOfferGets, saTakerPays, saTakerGets, saSubTakerPaid, saSubTakerGot); Log(lsINFO) << "takeOffers: applyOffer: saSubTakerPaid: " << saSubTakerPaid.getFullText(); Log(lsINFO) << "takeOffers: applyOffer: saSubTakerGot: " << saSubTakerGot.getFullText(); // Adjust offer // Offer owner will pay less. Subtract what taker just got. sleOffer->setIFieldAmount(sfTakerGets, saOfferPays -= saSubTakerGot); // Offer owner will get less. Subtract what owner just paid. sleOffer->setIFieldAmount(sfTakerPays, saOfferGets -= saSubTakerPaid); entryModify(sleOffer); if (bOfferDelete) { // Offer now fully claimed or now unfunded. Log(lsINFO) << "takeOffers: offer claimed: delete"; usOfferUnfundedBecame.insert(uOfferIndex); // Delete unfunded offer on success. // Offer owner's account is no longer pristine. usAccountTouched.insert(uOfferOwnerID); } else { Log(lsINFO) << "takeOffers: offer partial claim."; } // Offer owner pays taker. saSubTakerGot.setIssuer(uTakerGetsAccountID); // XXX Move this earlier? mNodes.accountSend(uOfferOwnerID, uTakerAccountID, saSubTakerGot); saTakerGot += saSubTakerGot; // Taker pays offer owner. saSubTakerPaid.setIssuer(uTakerPaysAccountID); mNodes.accountSend(uTakerAccountID, uOfferOwnerID, saSubTakerPaid); saTakerPaid += saSubTakerPaid; } } } } // On storing meta data, delete offers that were found unfunded to prevent encountering them in future. if (tesSUCCESS == terResult) { BOOST_FOREACH(const uint256& uOfferIndex, usOfferUnfundedFound) { terResult = mNodes.offerDelete(uOfferIndex); if (tesSUCCESS != terResult) break; } } if (tesSUCCESS == terResult) { // On success, delete offers that became unfunded. BOOST_FOREACH(const uint256& uOfferIndex, usOfferUnfundedBecame) { terResult = mNodes.offerDelete(uOfferIndex); if (tesSUCCESS != terResult) break; } } return terResult; } TER TransactionEngine::doOfferCreate(const SerializedTransaction& txn) { Log(lsWARNING) << "doOfferCreate> " << txn.getJson(0); const uint32 txFlags = txn.getFlags(); const bool bPassive = isSetBit(txFlags, tfPassive); STAmount saTakerPays = txn.getITFieldAmount(sfTakerPays); STAmount saTakerGets = txn.getITFieldAmount(sfTakerGets); Log(lsWARNING) << "doOfferCreate: saTakerPays=" << saTakerPays.getFullText(); Log(lsWARNING) << "doOfferCreate: saTakerGets=" << saTakerGets.getFullText(); const uint160 uPaysIssuerID = saTakerPays.getIssuer(); const uint160 uGetsIssuerID = saTakerGets.getIssuer(); const uint32 uExpiration = txn.getITFieldU32(sfExpiration); const bool bHaveExpiration = txn.getITFieldPresent(sfExpiration); const uint32 uSequence = txn.getSequence(); const uint256 uLedgerIndex = Ledger::getOfferIndex(mTxnAccountID, uSequence); SLE::pointer sleOffer = entryCreate(ltOFFER, uLedgerIndex); Log(lsINFO) << "doOfferCreate: Creating offer node: " << uLedgerIndex.ToString() << " uSequence=" << uSequence; const uint160 uPaysCurrency = saTakerPays.getCurrency(); const uint160 uGetsCurrency = saTakerGets.getCurrency(); const uint64 uRate = STAmount::getRate(saTakerGets, saTakerPays); TER terResult = tesSUCCESS; uint256 uDirectory; // Delete hints. uint64 uOwnerNode; uint64 uBookNode; if (bHaveExpiration && !uExpiration) { Log(lsWARNING) << "doOfferCreate: Malformed offer: bad expiration"; terResult = temBAD_EXPIRATION; } else if (bHaveExpiration && mLedger->getParentCloseTimeNC() >= uExpiration) { Log(lsWARNING) << "doOfferCreate: Expired transaction: offer expired"; // XXX CHARGE FEE ONLY. terResult = tesSUCCESS; } else if (saTakerPays.isNative() && saTakerGets.isNative()) { Log(lsWARNING) << "doOfferCreate: Malformed offer: XNS for XNS"; terResult = temBAD_OFFER; } else if (!saTakerPays || !saTakerGets) { Log(lsWARNING) << "doOfferCreate: Malformed offer: bad amount"; terResult = temBAD_OFFER; } else if (uPaysCurrency == uGetsCurrency && uPaysIssuerID == uGetsIssuerID) { Log(lsWARNING) << "doOfferCreate: Malformed offer: redundant offer"; terResult = temREDUNDANT; } else if (saTakerPays.isNative() != !uPaysIssuerID || saTakerGets.isNative() != !uGetsIssuerID) { Log(lsWARNING) << "doOfferCreate: Malformed offer: bad issuer"; terResult = temBAD_ISSUER; } else if (!mNodes.accountFunds(mTxnAccountID, saTakerGets).isPositive()) { Log(lsWARNING) << "doOfferCreate: delay: Offers must be at least partially funded."; terResult = terUNFUNDED; } if (tesSUCCESS == terResult && !saTakerPays.isNative()) { SLE::pointer sleTakerPays = entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uPaysIssuerID)); if (!sleTakerPays) { Log(lsWARNING) << "doOfferCreate: delay: can't receive IOUs from non-existant issuer: " << NewcoinAddress::createHumanAccountID(uPaysIssuerID); terResult = terNO_ACCOUNT; } } if (tesSUCCESS == terResult) { STAmount saOfferPaid; STAmount saOfferGot; const uint256 uTakeBookBase = Ledger::getBookBase(uGetsCurrency, uGetsIssuerID, uPaysCurrency, uPaysIssuerID); Log(lsINFO) << boost::str(boost::format("doOfferCreate: take against book: %s : %s/%s -> %s/%s") % uTakeBookBase.ToString() % saTakerGets.getHumanCurrency() % NewcoinAddress::createHumanAccountID(saTakerGets.getIssuer()) % saTakerPays.getHumanCurrency() % NewcoinAddress::createHumanAccountID(saTakerPays.getIssuer())); // Take using the parameters of the offer. terResult = takeOffers( bPassive, uTakeBookBase, mTxnAccountID, mTxnAccount, saTakerGets, saTakerPays, saOfferPaid, // How much was spent. saOfferGot // How much was got. ); Log(lsWARNING) << "doOfferCreate: takeOffers=" << terResult; Log(lsWARNING) << "doOfferCreate: takeOffers: saOfferPaid=" << saOfferPaid.getFullText(); Log(lsWARNING) << "doOfferCreate: takeOffers: saOfferGot=" << saOfferGot.getFullText(); Log(lsWARNING) << "doOfferCreate: takeOffers: saTakerPays=" << saTakerPays.getFullText(); Log(lsWARNING) << "doOfferCreate: takeOffers: saTakerGets=" << saTakerGets.getFullText(); if (tesSUCCESS == terResult) { saTakerPays -= saOfferGot; // Reduce payin from takers by what offer just got. saTakerGets -= saOfferPaid; // Reduce payout to takers by what srcAccount just paid. } } Log(lsWARNING) << "doOfferCreate: takeOffers: saTakerPays=" << saTakerPays.getFullText(); Log(lsWARNING) << "doOfferCreate: takeOffers: saTakerGets=" << saTakerGets.getFullText(); Log(lsWARNING) << "doOfferCreate: takeOffers: saTakerGets=" << NewcoinAddress::createHumanAccountID(saTakerGets.getIssuer()); Log(lsWARNING) << "doOfferCreate: takeOffers: mTxnAccountID=" << NewcoinAddress::createHumanAccountID(mTxnAccountID); Log(lsWARNING) << "doOfferCreate: takeOffers: funds=" << mNodes.accountFunds(mTxnAccountID, saTakerGets).getFullText(); // Log(lsWARNING) << "doOfferCreate: takeOffers: uPaysIssuerID=" << NewcoinAddress::createHumanAccountID(uPaysIssuerID); // Log(lsWARNING) << "doOfferCreate: takeOffers: uGetsIssuerID=" << NewcoinAddress::createHumanAccountID(uGetsIssuerID); if (tesSUCCESS == terResult && saTakerPays // Still wanting something. && saTakerGets // Still offering something. && mNodes.accountFunds(mTxnAccountID, saTakerGets).isPositive()) // Still funded. { // We need to place the remainder of the offer into its order book. // Add offer to owner's directory. terResult = mNodes.dirAdd(uOwnerNode, Ledger::getOwnerDirIndex(mTxnAccountID), uLedgerIndex); if (tesSUCCESS == terResult) { uint256 uBookBase = Ledger::getBookBase(uPaysCurrency, uPaysIssuerID, uGetsCurrency, uGetsIssuerID); Log(lsINFO) << boost::str(boost::format("doOfferCreate: adding to book: %s : %s/%s -> %s/%s") % uBookBase.ToString() % saTakerPays.getHumanCurrency() % NewcoinAddress::createHumanAccountID(saTakerPays.getIssuer()) % saTakerGets.getHumanCurrency() % NewcoinAddress::createHumanAccountID(saTakerGets.getIssuer())); uDirectory = Ledger::getQualityIndex(uBookBase, uRate); // Use original rate. // Add offer to order book. terResult = mNodes.dirAdd(uBookNode, uDirectory, uLedgerIndex); } if (tesSUCCESS == terResult) { // Log(lsWARNING) << "doOfferCreate: uPaysIssuerID=" << NewcoinAddress::createHumanAccountID(uPaysIssuerID); // Log(lsWARNING) << "doOfferCreate: uGetsIssuerID=" << NewcoinAddress::createHumanAccountID(uGetsIssuerID); // Log(lsWARNING) << "doOfferCreate: saTakerPays.isNative()=" << saTakerPays.isNative(); // Log(lsWARNING) << "doOfferCreate: saTakerGets.isNative()=" << saTakerGets.isNative(); // Log(lsWARNING) << "doOfferCreate: uPaysCurrency=" << saTakerPays.getHumanCurrency(); // Log(lsWARNING) << "doOfferCreate: uGetsCurrency=" << saTakerGets.getHumanCurrency(); sleOffer->setIFieldAccount(sfAccount, mTxnAccountID); sleOffer->setIFieldU32(sfSequence, uSequence); sleOffer->setIFieldH256(sfBookDirectory, uDirectory); sleOffer->setIFieldAmount(sfTakerPays, saTakerPays); sleOffer->setIFieldAmount(sfTakerGets, saTakerGets); sleOffer->setIFieldU64(sfOwnerNode, uOwnerNode); sleOffer->setIFieldU64(sfBookNode, uBookNode); if (uExpiration) sleOffer->setIFieldU32(sfExpiration, uExpiration); if (bPassive) sleOffer->setFlag(lsfPassive); } } return terResult; } TER TransactionEngine::doOfferCancel(const SerializedTransaction& txn) { TER terResult; const uint32 uSequence = txn.getITFieldU32(sfOfferSequence); const uint256 uOfferIndex = Ledger::getOfferIndex(mTxnAccountID, uSequence); SLE::pointer sleOffer = entryCache(ltOFFER, uOfferIndex); if (sleOffer) { Log(lsWARNING) << "doOfferCancel: uSequence=" << uSequence; terResult = mNodes.offerDelete(sleOffer, uOfferIndex, mTxnAccountID); } else { Log(lsWARNING) << "doOfferCancel: offer not found: " << NewcoinAddress::createHumanAccountID(mTxnAccountID) << " : " << uSequence << " : " << uOfferIndex.ToString(); terResult = terOFFER_NOT_FOUND; } return terResult; } TER TransactionEngine::doContractAdd(const SerializedTransaction& txn) { Log(lsWARNING) << "doContractAdd> " << txn.getJson(0); const uint32 expiration = txn.getITFieldU32(sfExpiration); // const uint32 bondAmount = txn.getITFieldU32(sfBondAmount); // const uint32 stampEscrow = txn.getITFieldU32(sfStampEscrow); STAmount rippleEscrow = txn.getITFieldAmount(sfRippleEscrow); std::vector createCode = txn.getITFieldVL(sfCreateCode); std::vector fundCode = txn.getITFieldVL(sfFundCode); std::vector removeCode = txn.getITFieldVL(sfRemoveCode); std::vector expireCode = txn.getITFieldVL(sfExpireCode); // make sure // expiration hasn't passed // bond amount is enough // they have the stamps for the bond // place contract in ledger // run create code if (mLedger->getParentCloseTimeNC() >= expiration) { Log(lsWARNING) << "doContractAdd: Expired transaction: offer expired"; return(tefALREADY); } //TODO: check bond //if( txn.getSourceAccount() ) Contract contract; Script::Interpreter interpreter; TER terResult=interpreter.interpret(&contract,txn,createCode); if(tesSUCCESS != terResult) { } return(terResult); } TER TransactionEngine::doContractRemove(const SerializedTransaction& txn) { // TODO: return(tesSUCCESS); } // vim:ts=4