diff --git a/src/TransactionEngine.cpp b/src/TransactionEngine.cpp index 26aa46aff9..1201f1ac8f 100644 --- a/src/TransactionEngine.cpp +++ b/src/TransactionEngine.cpp @@ -24,31 +24,34 @@ bool transResultInfo(TransactionEngineResult terCode, std::string& strToken, std const char* cpToken; const char* cpHuman; } transResultInfoA[] = { - { tenGEN_IN_USE, "tenGEN_IN_USE", "Generator already in use." }, - { tenCREATEXNS, "tenCREATEXNS", "Can not specify non XNS for Create." }, - { tenEXPLICITXNS, "tenEXPLICITXNS", "XNS is used by default, don't specify it." }, - { tenDST_NEEDED, "tenDST_NEEDED", "Destination not specified." }, - { tenDST_IS_SRC, "tenDST_IS_SRC", "Destination may not be source." }, - { tenBAD_GEN_AUTH, "tenBAD_GEN_AUTH", "Not authorized to claim generator." }, { tenBAD_ADD_AUTH, "tenBAD_ADD_AUTH", "Not authorized to add account." }, + { tenBAD_AMOUNT, "tenBAD_AMOUNT", "Can only send positive amounts." }, + { tenBAD_AUTH_MASTER, "tenBAD_AUTH_MASTER", "Auth for unclaimed account needs correct master key." }, { tenBAD_CLAIM_ID, "tenBAD_CLAIM_ID", "Malformed." }, + { tenBAD_GEN_AUTH, "tenBAD_GEN_AUTH", "Not authorized to claim generator." }, + { tenBAD_RIPPLE, "tenBAD_RIPPLE", "Ledger prevents ripple from succeeding." }, { tenBAD_SET_ID, "tenBAD_SET_ID", "Malformed." }, - { tenDIRECT_XNS_ONLY, "tenDIRECT_XNS_ONLY", "Direct payments are non-ripple XNS only." }, - { tenRIPPLE_EMPTY, "tenRIPPLE_EMPTY", "PathSet with no paths." }, { tenCLAIMED, "tenCLAIMED", "Can not claim a previously claimed account." }, { tenCREATED, "tenCREATED", "Can't add an already created account." }, - { tenMSG_SET, "tenMSG_SET", "Can't change a message key." }, - { tenBAD_AUTH_MASTER, "tenBAD_AUTH_MASTER", "Auth for unclaimed account needs correct master key." }, - { tenBAD_RIPPLE, "tenBAD_RIPPLE", "Ledger prevents ripple from succeeding." }, - { terALREADY, "terALREADY", "The exact transaction was already in this ledger" }, + { tenCREATEXNS, "tenCREATEXNS", "Can not specify non XNS for Create." }, + { tenDST_IS_SRC, "tenDST_IS_SRC", "Destination may not be source." }, + { tenDST_NEEDED, "tenDST_NEEDED", "Destination not specified." }, + { tenEXPLICITXNS, "tenEXPLICITXNS", "XNS is used by default, don't specify it." }, { tenFAILED, "tenFAILED", "Something broke horribly" }, - { tenUNKNOWN, "tenUNKNOWN", "The transactions requires logic not implemented yet" }, + { tenGEN_IN_USE, "tenGEN_IN_USE", "Generator already in use." }, { tenINSUF_FEE_P, "tenINSUF_FEE_P", "fee totally insufficient" }, { tenINVALID, "tenINVALID", "The transaction is ill-formed" }, - { terSUCCESS, "terSUCCESS", "The transaction was applied" }, + { tenMSG_SET, "tenMSG_SET", "Can't change a message key." }, + { tenREDUNDANT, "tenREDUNDANT", "Sends same currency to self." }, + { tenRIPPLE_EMPTY, "tenRIPPLE_EMPTY", "PathSet with no paths." }, + { tenUNKNOWN, "tenUNKNOWN", "The transactions requires logic not implemented yet" }, + { terALREADY, "terALREADY", "The exact transaction was already in this ledger" }, + { terBAD_AUTH, "terBAD_AUTH", "Transaction's public key is not authorized." }, + { terBAD_RIPPLE, "terBAD_RIPPLE", "No ripple path can be satisfied." }, { terBAD_SEQ, "terBAD_SEQ", "This sequence number should be zero for prepaid transactions." }, { terCREATED, "terCREATED", "Can not create a previously created account." }, { terDIR_FULL, "terDIR_FULL", "Can not add entry to full dir." }, + { terFUNDS_SPENT, "terFUNDS_SPENT", "Can't set password, password set funds already spent." }, { terINSUF_FEE_B, "terINSUF_FEE_B", "Account balance can't pay fee" }, { terINSUF_FEE_T, "terINSUF_FEE_T", "fee insufficient now (account doesn't exist, network load)" }, { terNODE_NOT_FOUND, "terNODE_NOT_FOUND", "Can not delete a dir node." }, @@ -56,17 +59,16 @@ bool transResultInfo(TransactionEngineResult terCode, std::string& strToken, std { terNODE_NO_ROOT, "terNODE_NO_ROOT", "?" }, { terNO_ACCOUNT, "terNO_ACCOUNT", "The source account does not exist" }, { terNO_DST, "terNO_DST", "The destination does not exist" }, + { terNO_LINE_NO_ZERO, "terNO_LINE_NO_ZERO", "Can't zero non-existant line, destination might make it." }, { terNO_PATH, "terNO_PATH", "No path existed or met transaction/balance requirements" }, + { terOVER_LIMIT, "terOVER_LIMIT", "Over limit." }, { terPAST_LEDGER, "terPAST_LEDGER", "The transaction expired and can't be applied" }, { terPAST_SEQ, "terPAST_SEQ", "This sequence number has already past" }, { terPRE_SEQ, "terPRE_SEQ", "Missing/inapplicable prior transaction" }, - { terUNFUNDED, "terUNFUNDED", "Source account had insufficient balance for transaction." }, - { terNO_LINE_NO_ZERO, "terNO_LINE_NO_ZERO", "Can't zero non-existant line, destination might make it." }, { terSET_MISSING_DST, "terSET_MISSING_DST", "Can't set password, destination missing." }, - { terFUNDS_SPENT, "terFUNDS_SPENT", "Can't set password, password set funds already spent." }, + { terSUCCESS, "terSUCCESS", "The transaction was applied" }, { terUNCLAIMED, "terUNCLAIMED", "Can not use an unclaimed account." }, - { terBAD_AUTH, "terBAD_AUTH", "Transaction's public key is not authorized." }, - { terBAD_RIPPLE, "terBAD_RIPPLE", "No ripple path can be satisfied." }, + { terUNFUNDED, "terUNFUNDED", "Source account had insufficient balance for transaction." }, }; int iIndex = NUMBER(transResultInfoA); @@ -82,6 +84,7 @@ bool transResultInfo(TransactionEngineResult terCode, std::string& strToken, std return iIndex >= 0; } + // We return the uNodeDir so that on delete we can quickly know where the element is mentioned in the directory. TransactionEngineResult TransactionEngine::dirAdd( std::vector& accounts, @@ -1083,47 +1086,57 @@ TransactionEngineResult TransactionEngine::doPasswordSet(const SerializedTransac return result; } +// XXX Need to audit for things like setting accountID not having memory. TransactionEngineResult TransactionEngine::doPayment(const SerializedTransaction& txn, std::vector& accounts, const uint160& uSrcAccountID) { + // Ripple if source or destination is non-native or if there are paths. uint32 txFlags = txn.getFlags(); - uint160 uDstAccountID = txn.getITFieldAccount(sfDestination); - // XXX Could also be ripple if direct credit lines. - bool bRipple = txn.getITFieldPresent(sfPaths); bool bCreate = !!(txFlags & tfCreateAccount); - STAmount saAmount = txn.getITFieldAmount(sfAmount); - STAmount saSrcBalance = accounts[0].second->getIValueFieldAmount(sfBalance); + bool bNoRippleDirect = !!(txFlags & tfNoRippleDirect); + bool bPaths = txn.getITFieldPresent(sfPaths); + bool bMax = txn.getITFieldPresent(sfSendMax); + uint160 uDstAccountID = txn.getITFieldAccount(sfDestination); + STAmount saDstAmount = txn.getITFieldAmount(sfAmount); + STAmount saMaxAmount = bMax ? txn.getITFieldAmount(sfSendMax) : saDstAmount; + uint160 uSrcCurrency = saMaxAmount.getCurrency(); + uint160 uDstCurrency = saDstAmount.getCurrency(); if (!uDstAccountID) { - std::cerr << "doPayment: Invalid transaction: Payment destination account not specifed." << std::endl; - return tenINVALID; - } - // XXX Only bad if no currency conversion in between through other people's offer. - else if (uSrcAccountID == uDstAccountID) - { - std::cerr << "doPayment: Invalid transaction: Source account is the same as destination." << std::endl; - return tenINVALID; - } + Log(lsINFO) << "doPayment: Invalid transaction: Payment destination account not specifed."; - // XXX Allow ripple to create. + return tenDST_NEEDED; + } + else if (saDstAmount.isPositive()) + { + Log(lsINFO) << "doPayment: Invalid transaction: Redunant transaction."; + + return tenBAD_AMOUNT; + } + else if (uSrcAccountID == uDstAccountID && uSrcCurrency == uDstCurrency && !bPaths) + { + Log(lsINFO) << "doPayment: Invalid transaction: Redunant transaction."; + + return tenREDUNDANT; + } LedgerStateParms qry = lepNONE; SLE::pointer sleDst = mLedger->getAccountRoot(qry, uDstAccountID); if (!sleDst) { // Destination account does not exist. - // XXX Also make sure non-ripple dest if creating. - if (bCreate && !saAmount.isNative()) + if (bCreate && !saDstAmount.isNative()) { - std::cerr << "doPayment: Invalid transaction: Create account may only fund XNS." << std::endl; + // This restriction could be relaxed. + Log(lsINFO) << "doPayment: Invalid transaction: Create account may only fund XNS."; return tenCREATEXNS; } else if (!bCreate) { - std::cerr << "doPayment: Delay transaction: Destination account does not exist." << std::endl; + Log(lsINFO) << "doPayment: Delay transaction: Destination account does not exist."; return terNO_DST; } @@ -1140,7 +1153,8 @@ TransactionEngineResult TransactionEngine::doPayment(const SerializedTransaction // Destination exists. else if (bCreate) { - std::cerr << "doPayment: Invalid transaction: Account already created." << std::endl; + // Retryable: if account created this ledger, reordering might allow account to be made by this transaction. + Log(lsINFO) << "doPayment: Invalid transaction: Account already created."; return terCREATED; } @@ -1149,42 +1163,78 @@ TransactionEngineResult TransactionEngine::doPayment(const SerializedTransaction accounts.push_back(std::make_pair(taaMODIFY, sleDst)); } + bool bRipple = bPaths || bMax || !saDstAmount.isNative(); + if (!bRipple) { // Direct XNS payment. - if (!saAmount.isNative()) - { - std::cerr << "doPayment: Invalid transaction: direct " SYSTEM_CURRENCY_CODE " required." << std::endl; + STAmount saSrcXNSBalance = accounts[0].second->getIValueFieldAmount(sfBalance); - return tenDIRECT_XNS_ONLY; - } - - if (saSrcBalance < saAmount) + if (saSrcXNSBalance < saDstAmount) { - std::cerr << "doPayment: Delay transaction: Insufficent funds." << std::endl; + // Transaction might succeed, if applied in a different order. + Log(lsINFO) << "doPayment: Delay transaction: Insufficent funds."; return terUNFUNDED; } - accounts[0].second->setIFieldAmount(sfBalance, saSrcBalance - saAmount); - accounts[1].second->setIFieldAmount(sfBalance, accounts[1].second->getIValueFieldAmount(sfBalance) + saAmount); + accounts[0].second->setIFieldAmount(sfBalance, saSrcXNSBalance - saDstAmount); + accounts[1].second->setIFieldAmount(sfBalance, accounts[1].second->getIValueFieldAmount(sfBalance) + saDstAmount); return terSUCCESS; } // - // Try direct ripple first. + // Ripple payment // - uint160 uDstCurrency = saAmount.getCurrency(); - - qry = lepNONE; - SLE::pointer sleRippleState = mLedger->getRippleState(qry, uSrcAccountID, uDstAccountID, uDstCurrency); - - if (sleRippleState) + // Try direct ripple first. + if (!bNoRippleDirect && uSrcAccountID != uDstAccountID && uSrcCurrency == uDstCurrency) { - // There is a direct relationship. + qry = lepNONE; + SLE::pointer sleRippleState = mLedger->getRippleState(qry, uSrcAccountID, uDstAccountID, uDstCurrency); + if (sleRippleState) + { + // There is a direct credit-line of some direction. + uint160 uLowID = sleRippleState->getIValueFieldAccount(sfLowID).getAccountID(); + uint160 uHighID = sleRippleState->getIValueFieldAccount(sfHighID).getAccountID(); + bool bSendHigh = uLowID == uSrcAccountID && uHighID == uDstAccountID; + bool bSendLow = uLowID == uDstAccountID && uHighID == uSrcAccountID; + + assert(bSendLow || bSendHigh); + + STAmount saDstLimit = sleRippleState->getIValueFieldAmount(bSendLow ? sfLowLimit : sfHighLimit); + + STAmount saDstBalance = sleRippleState->getIValueFieldAmount(sfBalance); + if (bSendHigh) + { + // Put balance in dst terms. + saDstBalance.negate(); + } + + saDstBalance += saDstAmount; + if (saDstAmount > saDstLimit) + { + // Would exceed credit limit. + // YYY Note: in the future could push out other credits to make payment fit. + + Log(lsINFO) << "doPayment: Delay transaction: Over limit."; + + return terOVER_LIMIT; + } + + if (bSendHigh) + { + // Put balance in low terms. + saDstBalance.negate(); + } + + sleRippleState->setIFieldAmount(sfBalance, saDstBalance); + accounts.push_back(std::make_pair(taaMODIFY, sleRippleState)); + + return terSUCCESS; + } } STPathSet spsPaths = txn.getITFieldPathSet(sfPaths); @@ -1192,7 +1242,7 @@ TransactionEngineResult TransactionEngine::doPayment(const SerializedTransaction // XXX If we are parsing for determing forwarding check maximum path count. if (!spsPaths.getPathCount()) { - std::cerr << "doPayment: Invalid transaction: No paths." << std::endl; + Log(lsINFO) << "doPayment: Invalid transaction: No paths."; return tenRIPPLE_EMPTY; } @@ -1202,13 +1252,13 @@ TransactionEngineResult TransactionEngine::doPayment(const SerializedTransaction BOOST_FOREACH(std::vector& spPath, spsPaths) { - std::cerr << "doPayment: Implementation error: Not implemented." << std::endl; + Log(lsINFO) << "doPayment: Implementation error: Not implemented."; return tenUNKNOWN; } #endif - std::cerr << "doPayment: Delay transaction: No ripple paths could be satisfied." << std::endl; + Log(lsINFO) << "doPayment: Delay transaction: No ripple paths could be satisfied."; return terBAD_RIPPLE; } diff --git a/src/TransactionEngine.h b/src/TransactionEngine.h index 037ac19b87..f7f70b8122 100644 --- a/src/TransactionEngine.h +++ b/src/TransactionEngine.h @@ -10,43 +10,49 @@ enum TransactionEngineResult { + // Note: Numbers are currently unstable. Use tokens. + // tenCAN_NEVER_SUCCEED = <0 // Malformed: Fee claimed tenGEN_IN_USE = -300, - tenCREATEXNS, - tenEXPLICITXNS, - tenDST_NEEDED, - tenDST_IS_SRC, - tenBAD_GEN_AUTH, tenBAD_ADD_AUTH, + tenBAD_AMOUNT, tenBAD_CLAIM_ID, + tenBAD_GEN_AUTH, tenBAD_SET_ID, - tenDIRECT_XNS_ONLY, + tenCREATEXNS, + tenDST_IS_SRC, + tenDST_NEEDED, + tenEXPLICITXNS, + tenREDUNDANT, tenRIPPLE_EMPTY, // Invalid: Ledger won't allow. tenCLAIMED = -200, - tenCREATED, - tenMSG_SET, tenBAD_AUTH_MASTER, tenBAD_RIPPLE, + tenCREATED, + tenMSG_SET, terALREADY, // Other tenFAILED = -100, - tenUNKNOWN, tenINSUF_FEE_P, tenINVALID, + tenUNKNOWN, terSUCCESS = 0, // terFAILED_BUT_COULD_SUCCEED = >0 // Conflict with ledger database: Fee claimed // Might succeed if not conflict is not caused by transaction ordering. + terBAD_AUTH, + terBAD_RIPPLE, terBAD_SEQ, terCREATED, terDIR_FULL, + terFUNDS_SPENT, terINSUF_FEE_B, terINSUF_FEE_T, terNODE_NOT_FOUND, @@ -54,17 +60,15 @@ enum TransactionEngineResult terNODE_NO_ROOT, terNO_ACCOUNT, terNO_DST, + terNO_LINE_NO_ZERO, terNO_PATH, + terOVER_LIMIT, terPAST_LEDGER, terPAST_SEQ, terPRE_SEQ, - terUNFUNDED, - terNO_LINE_NO_ZERO, terSET_MISSING_DST, - terFUNDS_SPENT, terUNCLAIMED, - terBAD_AUTH, - terBAD_RIPPLE, + terUNFUNDED, }; bool transResultInfo(TransactionEngineResult terCode, std::string& strToken, std::string& strHuman); diff --git a/src/TransactionFormats.h b/src/TransactionFormats.h index d8c8fc38df..1f5b63d203 100644 --- a/src/TransactionFormats.h +++ b/src/TransactionFormats.h @@ -37,6 +37,7 @@ const int TransactionMaxLen = 1048576; // Transaction flags. const uint32 tfCreateAccount = 0x00010000; +const uint32 tfNoRippleDirect = 0x00020000; const uint32 tfUnsetEmailHash = 0x00010000; const uint32 tfUnsetWalletLocator = 0x00020000;