diff --git a/.gitignore b/.gitignore index 6584ee9591..5053c37ba3 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,6 @@ tmp # Ignore database directory. db/*.db + +# Ignore customized test/config.js +test/config.js diff --git a/SConstruct b/SConstruct index 5a5155483e..63c2f5ac81 100644 --- a/SConstruct +++ b/SConstruct @@ -5,7 +5,9 @@ import glob import platform -OSX = bool(platform.mac_ver()[0]) +OSX = bool(platform.mac_ver()[0]) +FreeBSD = bool('FreeBSD' == platform.system()) +Ubuntu = bool('Ubuntu' == platform.dist()) if OSX: CTAGS = '/usr/bin/ctags' @@ -43,14 +45,38 @@ for dir in ['ripple', 'database', 'json', 'websocketpp']: # Use openssl env.ParseConfig('pkg-config --cflags --libs openssl') +# The required version of boost is documented in the README file. +# +# We whitelist platforms where the non -mt version is linked with pthreads. +# This can be verified with: ldd libboost_filesystem.* +# If a threading library is included the platform can be whitelisted. +# +# FreeBSD and Ubuntu non-mt libs do link with pthreads. +if FreeBSD or Ubuntu: + env.Append( + LIBS = [ + 'boost_date_time', + 'boost_filesystem', + 'boost_program_options', + 'boost_regex', + 'boost_system', + 'boost_thread', + ] + ) +else: + env.Append( + LIBS = [ + 'boost_date_time-mt', + 'boost_filesystem-mt', + 'boost_program_options-mt', + 'boost_regex-mt', + 'boost_system-mt', + 'boost_thread-mt', + ] + ) + env.Append( LIBS = [ - 'boost_date_time-mt', - 'boost_filesystem-mt', - 'boost_program_options-mt', - 'boost_regex-mt', - 'boost_system-mt', - 'boost_thread-mt', 'protobuf', 'dl', # dynamic linking 'z' diff --git a/newcoin.vcxproj b/newcoin.vcxproj index 92e02d4a5e..1891a5d7a3 100644 --- a/newcoin.vcxproj +++ b/newcoin.vcxproj @@ -95,6 +95,7 @@ + @@ -127,17 +128,21 @@ + + + + @@ -162,16 +167,18 @@ - + + + @@ -191,6 +198,7 @@ + @@ -223,17 +231,21 @@ + + + + @@ -266,6 +278,8 @@ + + @@ -273,6 +287,7 @@ + diff --git a/newcoin.vcxproj.filters b/newcoin.vcxproj.filters index c33506595e..064de313cd 100644 --- a/newcoin.vcxproj.filters +++ b/newcoin.vcxproj.filters @@ -249,9 +249,6 @@ Source Files - - Source Files - Source Files @@ -321,6 +318,30 @@ Source Files\websocketpp + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + @@ -599,6 +620,30 @@ Header Files + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + diff --git a/ripple2010.sln b/ripple2010.sln new file mode 100644 index 0000000000..36241841d9 --- /dev/null +++ b/ripple2010.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ripple2010", "ripple2010.vcxproj", "{19465545-42EE-42FA-9CC8-F8975F8F1CC7}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {19465545-42EE-42FA-9CC8-F8975F8F1CC7}.Debug|Win32.ActiveCfg = Debug|Win32 + {19465545-42EE-42FA-9CC8-F8975F8F1CC7}.Debug|Win32.Build.0 = Debug|Win32 + {19465545-42EE-42FA-9CC8-F8975F8F1CC7}.Release|Win32.ActiveCfg = Release|Win32 + {19465545-42EE-42FA-9CC8-F8975F8F1CC7}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/ripple2010.vcxproj b/ripple2010.vcxproj new file mode 100644 index 0000000000..374a65bd62 --- /dev/null +++ b/ripple2010.vcxproj @@ -0,0 +1,317 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {19465545-42EE-42FA-9CC8-F8975F8F1CC7} + Win32Proj + newcoin + + + + Application + true + MultiByte + + + Application + false + true + MultiByte + + + + + + + + + + + + + true + + + false + + + + NotUsing + Level3 + Disabled + BOOST_TEST_ALTERNATIVE_INIT_API;BOOST_TEST_NO_MAIN;_CRT_SECURE_NO_WARNINGS;_WIN32_WINNT=0x0501;WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + .\;..\OpenSSL\include;..\boost_1_52_0;..\protobuf\src\ + ProgramDatabase + + + Console + true + ..\OpenSSL\lib\VC;..\boost_1_52_0\stage\lib;..\protobuf\vsprojects\Debug + ssleay32MDd.lib;libeay32MTd.lib;libprotobuf.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + c:/code/protoc-2.4.1-win32/protoc -I=C:\code\newcoin --cpp_out=C:\code\newcoin C:\code\newcoin/newcoin.proto + + + + + Level3 + NotUsing + MaxSpeed + true + true + BOOST_TEST_ALTERNATIVE_INIT_API;BOOST_TEST_NO_MAIN;_CRT_SECURE_NO_WARNINGS;_WIN32_WINNT=0x0501;WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + ..\OpenSSL\include;..\boost_1_47_0;..\protobuf-2.4.1\src + + + Console + true + true + true + ..\OpenSSL\lib\VC;..\boost_1_47_0\stage\lib;..\protobuf-2.4.1\vsprojects\Release + libprotobuf.lib;ssleay32MD.lib;libeay32MD.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Designer + + + + + + Document + /code/protobuf/protoc -I=..\newcoin --cpp_out=\code\newcoin\ ..\newcoin/src/cpp/ripple/ripple.proto + \code\newcoin\src\ripple.pb.h + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ripple2010.vcxproj.filters b/ripple2010.vcxproj.filters new file mode 100644 index 0000000000..9e03588ae8 --- /dev/null +++ b/ripple2010.vcxproj.filters @@ -0,0 +1,644 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {54608e0e-4ac4-44d6-af96-0c278457ac6f} + + + {c642219d-cace-47c1-828a-58ba570da63a} + + + {c717b139-5eba-454b-8888-9bf54ce0a652} + + + {77d2a621-b503-4ce4-aee8-ef0b337c4ee2} + + + {60c3631e-8855-4a61-bdd3-9892d96242d5} + + + {92775c5f-dc9f-4a97-a9a6-6d4bd4e424b4} + + + + + Source Files\database + + + Source Files\database + + + Source Files\database + + + Source Files\json + + + Source Files\json + + + Source Files\json + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files\database + + + Source Files\websocketpp + + + Source Files\websocketpp + + + Source Files\websocketpp + + + Source Files\websocketpp + + + Source Files\websocketpp + + + Source Files\websocketpp + + + Source Files\websocketpp + + + Source Files\websocketpp + + + Source Files\websocketpp + + + Source Files\websocketpp + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files\util + + + Header Files\util + + + Header Files\util + + + Header Files\util + + + Header Files\util + + + Header Files\util + + + Header Files\util + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + + html + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/cpp/ripple/AccountSetTransactor.cpp b/src/cpp/ripple/AccountSetTransactor.cpp new file mode 100644 index 0000000000..32ded2c6f5 --- /dev/null +++ b/src/cpp/ripple/AccountSetTransactor.cpp @@ -0,0 +1,119 @@ +#include "AccountSetTransactor.h" + +TER AccountSetTransactor::doApply() +{ + Log(lsINFO) << "doAccountSet>"; + + // + // EmailHash + // + + if (mTxn.isFieldPresent(sfEmailHash)) + { + uint128 uHash = mTxn.getFieldH128(sfEmailHash); + + if (!uHash) + { + Log(lsINFO) << "doAccountSet: unset email hash"; + + mTxnAccount->makeFieldAbsent(sfEmailHash); + } + else + { + Log(lsINFO) << "doAccountSet: set email hash"; + + mTxnAccount->setFieldH128(sfEmailHash, uHash); + } + } + + // + // WalletLocator + // + + if (mTxn.isFieldPresent(sfWalletLocator)) + { + uint256 uHash = mTxn.getFieldH256(sfWalletLocator); + + if (!uHash) + { + Log(lsINFO) << "doAccountSet: unset wallet locator"; + + mTxnAccount->makeFieldAbsent(sfEmailHash); + } + else + { + Log(lsINFO) << "doAccountSet: set wallet locator"; + + mTxnAccount->setFieldH256(sfWalletLocator, uHash); + } + } + + // + // MessageKey + // + + if (!mTxn.isFieldPresent(sfMessageKey)) + { + nothing(); + } + else + { + Log(lsINFO) << "doAccountSet: set message key"; + + mTxnAccount->setFieldVL(sfMessageKey, mTxn.getFieldVL(sfMessageKey)); + } + + // + // Domain + // + + if (mTxn.isFieldPresent(sfDomain)) + { + std::vector vucDomain = mTxn.getFieldVL(sfDomain); + + if (vucDomain.empty()) + { + Log(lsINFO) << "doAccountSet: unset domain"; + + mTxnAccount->makeFieldAbsent(sfDomain); + } + else + { + Log(lsINFO) << "doAccountSet: set domain"; + + mTxnAccount->setFieldVL(sfDomain, vucDomain); + } + } + + // + // TransferRate + // + + if (mTxn.isFieldPresent(sfTransferRate)) + { + uint32 uRate = mTxn.getFieldU32(sfTransferRate); + + if (!uRate || uRate == QUALITY_ONE) + { + Log(lsINFO) << "doAccountSet: unset transfer rate"; + + mTxnAccount->makeFieldAbsent(sfTransferRate); + } + else if (uRate > QUALITY_ONE) + { + Log(lsINFO) << "doAccountSet: set transfer rate"; + + mTxnAccount->setFieldU32(sfTransferRate, uRate); + } + else + { + Log(lsINFO) << "doAccountSet: bad transfer rate"; + + return temBAD_TRANSFER_RATE; + } + } + + Log(lsINFO) << "doAccountSet<"; + + return tesSUCCESS; +} \ No newline at end of file diff --git a/src/cpp/ripple/AccountSetTransactor.h b/src/cpp/ripple/AccountSetTransactor.h new file mode 100644 index 0000000000..214a32d27b --- /dev/null +++ b/src/cpp/ripple/AccountSetTransactor.h @@ -0,0 +1,9 @@ +#include "Transactor.h" + +class AccountSetTransactor : public Transactor +{ +public: + AccountSetTransactor(const SerializedTransaction& txn,TransactionEngineParams params, TransactionEngine* engine) : Transactor(txn,params,engine) {} + + TER doApply(); +}; \ No newline at end of file diff --git a/src/cpp/ripple/AccountState.h b/src/cpp/ripple/AccountState.h index f2587ac501..2b02c79205 100644 --- a/src/cpp/ripple/AccountState.h +++ b/src/cpp/ripple/AccountState.h @@ -33,12 +33,12 @@ public: bool bHaveAuthorizedKey() { - return mLedgerEntry->isFieldPresent(sfAuthorizedKey); + return mLedgerEntry->isFieldPresent(sfRegularKey); } RippleAddress getAuthorizedKey() { - return mLedgerEntry->getFieldAccount(sfAuthorizedKey); + return mLedgerEntry->getFieldAccount(sfRegularKey); } STAmount getBalance() const { return mLedgerEntry->getFieldAmount(sfBalance); } diff --git a/src/cpp/ripple/FieldNames.cpp b/src/cpp/ripple/FieldNames.cpp index d51adf8ad0..0d7617ae41 100644 --- a/src/cpp/ripple/FieldNames.cpp +++ b/src/cpp/ripple/FieldNames.cpp @@ -28,8 +28,16 @@ SField sfIndex(STI_HASH256, 258, "index"); static int initFields() { - sfTxnSignature.notSigningField(); sfTxnSignatures.notSigningField(); + sfTxnSignature.notSigningField(); + sfTxnSignatures.notSigningField(); sfSignature.notSigningField(); + + sfIndexes.setMeta(SField::sMD_Never); + sfPreviousTxnID.setMeta(SField::sMD_Never); + sfPreviousTxnLgrSeq.setMeta(SField::sMD_Never); + sfLedgerEntryType.setMeta(SField::sMD_Never); + sfRootIndex.setMeta(SField::sMD_Always); + return 0; } static const int f = initFields(); diff --git a/src/cpp/ripple/FieldNames.h b/src/cpp/ripple/FieldNames.h index 8b1d5636a5..507a2bd7f3 100644 --- a/src/cpp/ripple/FieldNames.h +++ b/src/cpp/ripple/FieldNames.h @@ -30,7 +30,8 @@ enum SOE_Flags { SOE_INVALID = -1, SOE_REQUIRED = 0, // required - SOE_OPTIONAL = 1, // optional + SOE_OPTIONAL = 1, // optional, may be present with default value + SOE_DEFAULT = 2, // optional, if present, must not have default value }; class SField @@ -39,6 +40,14 @@ public: typedef const SField& ref; typedef SField const * ptr; + static const int sMD_Never = 0x00; + static const int sMD_ChangeOrig = 0x01; // original value when it changes + static const int sMD_ChangeNew = 0x02; // new value when it changes + static const int sMD_DeleteFinal = 0x04; // final value when it is deleted + static const int sMD_Create = 0x08; // value when it's created + static const int sMD_Always = 0x10; // value when node containing it is affected at all + static const int sMD_Default = sMD_ChangeOrig | sMD_ChangeNew | sMD_DeleteFinal; + protected: static std::map codeToField; static boost::mutex mapMutex; @@ -51,23 +60,25 @@ public: const SerializedTypeID fieldType; // STI_* const int fieldValue; // Code number for protocol std::string fieldName; + int fieldMeta; bool signingField; SField(int fc, SerializedTypeID tid, int fv, const char* fn) : - fieldCode(fc), fieldType(tid), fieldValue(fv), fieldName(fn), signingField(true) + fieldCode(fc), fieldType(tid), fieldValue(fv), fieldName(fn), fieldMeta(sMD_Default), signingField(true) { boost::mutex::scoped_lock sl(mapMutex); codeToField[fieldCode] = this; } SField(SerializedTypeID tid, int fv, const char *fn) : - fieldCode(FIELD_CODE(tid, fv)), fieldType(tid), fieldValue(fv), fieldName(fn), signingField(true) + fieldCode(FIELD_CODE(tid, fv)), fieldType(tid), fieldValue(fv), fieldName(fn), + fieldMeta(sMD_Default), signingField(true) { boost::mutex::scoped_lock sl(mapMutex); codeToField[fieldCode] = this; } - SField(int fc) : fieldCode(fc), fieldType(STI_UNKNOWN), fieldValue(0) { ; } + SField(int fc) : fieldCode(fc), fieldType(STI_UNKNOWN), fieldValue(0), fieldMeta(sMD_Never) { ; } ~SField(); @@ -85,8 +96,10 @@ public: bool isBinary() const { return fieldValue < 256; } bool isDiscardable() const { return fieldValue > 256; } - bool isSigningField() const { return signingField; } - void notSigningField() { signingField = false; } + bool isSigningField() const { return signingField; } + void notSigningField() { signingField = false; } + bool shouldMeta(int c) const { return (fieldMeta & c) != 0; } + void setMeta(int c) { fieldMeta = c; } bool shouldInclude(bool withSigningField) const { return (fieldValue < 256) && (withSigningField || signingField); } diff --git a/src/cpp/ripple/Ledger.h b/src/cpp/ripple/Ledger.h index fae8b107b1..64bf9ef4a6 100644 --- a/src/cpp/ripple/Ledger.h +++ b/src/cpp/ripple/Ledger.h @@ -44,6 +44,7 @@ DEFINE_INSTANCE(Ledger); class Ledger : public boost::enable_shared_from_this, public IS_INSTANCE(Ledger) { // The basic Ledger structure, can be opened, closed, or synching friend class TransactionEngine; + friend class Transactor; public: typedef boost::shared_ptr pointer; typedef const boost::shared_ptr& ref; diff --git a/src/cpp/ripple/LedgerEntrySet.cpp b/src/cpp/ripple/LedgerEntrySet.cpp index 1aeffb14b0..1ff883fb52 100644 --- a/src/cpp/ripple/LedgerEntrySet.cpp +++ b/src/cpp/ripple/LedgerEntrySet.cpp @@ -275,7 +275,10 @@ SLE::pointer LedgerEntrySet::getForMod(const uint256& node, Ledger::ref ledger, if (it != mEntries.end()) { if (it->second.mAction == taaDELETE) + { + cLog(lsFATAL) << "Trying to thread to deleted node"; return SLE::pointer(); + } if (it->second.mAction == taaCACHED) it->second.mAction = taaMODIFY; if (it->second.mSeq != mSeq) @@ -288,7 +291,10 @@ SLE::pointer LedgerEntrySet::getForMod(const uint256& node, Ledger::ref ledger, boost::unordered_map::iterator me = newMods.find(node); if (me != newMods.end()) + { + assert(me->second); return me->second; + } SLE::pointer ret = ledger->getSLE(node); if (ret) @@ -306,6 +312,7 @@ bool LedgerEntrySet::threadTx(const RippleAddress& threadTo, Ledger::ref ledger, SLE::pointer sle = getForMod(Ledger::getAccountRootIndex(threadTo.getAccountID()), ledger, newMods); if (!sle) { + cLog(lsFATAL) << "Threading to non-existent account: " << threadTo.humanAccountID(); assert(false); return false; } @@ -400,59 +407,66 @@ void LedgerEntrySet::calcRawMeta(Serializer& s, TER result) mSet.setAffectedNode(it.first, *type, nodeType); if (type == &sfDeletedNode) - { + { // nodes was deleted assert(origNode); assert(curNode); - threadOwners(origNode, mLedger, newMod); + threadOwners(origNode, mLedger, newMod); // thread transaction to owners + STObject mods(sfPreviousFields); STObject finals(sfFinalFields); BOOST_FOREACH(const SerializedType& obj, *curNode) - { // save non-default values - if (!obj.isDefault() && (obj.getFName() != sfLedgerEntryType)) - finals.addObject(obj); - } - if (!finals.empty()) - mSet.getAffectedNode(it.first, *type).addObject(finals); - } - - if ((type == &sfDeletedNode || type == &sfModifiedNode)) - { - STObject mods(sfPreviousFields); - BOOST_FOREACH(const SerializedType& obj, *origNode) - { // search the original node for values saved on modify - if (!obj.isDefault() && (obj.getFName() != sfLedgerEntryType) && !curNode->hasMatchingEntry(obj)) + { + if (obj.getFName().shouldMeta(SField::sMD_ChangeOrig) && !curNode->hasMatchingEntry(obj)) mods.addObject(obj); + if (obj.getFName().shouldMeta(SField::sMD_Always | SField::sMD_DeleteFinal)) + finals.addObject(obj); } if (!mods.empty()) mSet.getAffectedNode(it.first, *type).addObject(mods); + if (!finals.empty()) + mSet.getAffectedNode(it.first, *type).addObject(finals); } + else if (type == &sfModifiedNode) + { + if (curNode->isThreadedType()) // thread transaction to node it modified + threadTx(curNode, mLedger, newMod); - if (type == &sfCreatedNode) // if created, thread to owner(s) + STObject mods(sfPreviousFields); + STObject finals(sfFinalFields); + BOOST_FOREACH(const SerializedType& obj, *origNode) + { // search the original node for values saved on modify + if (obj.getFName().shouldMeta(SField::sMD_ChangeOrig) && !curNode->hasMatchingEntry(obj)) + mods.addObject(obj); + if (obj.getFName().shouldMeta(SField::sMD_Always | SField::sMD_ChangeNew)) + finals.addObject(obj); + } + if (!mods.empty()) + mSet.getAffectedNode(it.first, *type).addObject(mods); + if (!finals.empty()) + mSet.getAffectedNode(it.first, *type).addObject(finals); + } + else if (type == &sfCreatedNode) // if created, thread to owner(s) { assert(!origNode); threadOwners(curNode, mLedger, newMod); + if (curNode->isThreadedType()) // always thread to self + threadTx(curNode, mLedger, newMod); STObject news(sfNewFields); BOOST_FOREACH(const SerializedType& obj, *curNode) { // save non-default values - if (!obj.isDefault() && (obj.getFName() != sfLedgerEntryType)) + if (!obj.isDefault() && obj.getFName().shouldMeta(SField::sMD_Create | SField::sMD_Always)) news.addObject(obj); } if (!news.empty()) mSet.getAffectedNode(it.first, *type).addObject(news); } - - if ((type == &sfCreatedNode) || (type == &sfModifiedNode)) - { - if (curNode->isThreadedType()) // always thread to self - threadTx(curNode, mLedger, newMod); - } } // add any new modified nodes to the modification set - for (boost::unordered_map::iterator it = newMod.begin(), end = newMod.end(); - it != end; ++it) - entryModify(it->second); + typedef std::pair u256_sle_pair; + BOOST_FOREACH(u256_sle_pair& it, newMod) + entryModify(it.second); mSet.addRaw(s, result); cLog(lsTRACE) << "Metadata:" << mSet.getJson(0); @@ -476,6 +490,7 @@ TER LedgerEntrySet::dirAdd( { // No root, make it. sleRoot = entryCreate(ltDIR_NODE, uRootIndex); + sleRoot->setFieldH256(sfRootIndex, uRootIndex); sleNode = sleRoot; uNodeDir = 0; @@ -536,6 +551,7 @@ TER LedgerEntrySet::dirAdd( // Create the new node. sleNode = entryCreate(ltDIR_NODE, Ledger::getDirNodeIndex(uRootIndex, uNodeDir)); + sleNode->setFieldH256(sfRootIndex, uRootIndex); svIndexes = STVector256(); } } @@ -1020,14 +1036,21 @@ void LedgerEntrySet::rippleCredit(const uint160& uSenderID, const uint160& uRece uint256 uIndex = Ledger::getRippleStateIndex(uSenderID, uReceiverID, saAmount.getCurrency()); SLE::pointer sleRippleState = entryCache(ltRIPPLE_STATE, uIndex); + assert(!!uSenderID && uSenderID != ACCOUNT_ONE); + assert(!!uReceiverID && uReceiverID != ACCOUNT_ONE); + if (!sleRippleState) { - cLog(lsDEBUG) << "rippleCredit: Creating ripple line: " << uIndex.ToString(); - STAmount saBalance = saAmount; saBalance.setIssuer(ACCOUNT_ONE); + cLog(lsDEBUG) << boost::str(boost::format("rippleCredit: create line: %s (%s) -> %s : %s") + % RippleAddress::createHumanAccountID(uSenderID) + % saBalance.getFullText() + % RippleAddress::createHumanAccountID(uReceiverID) + % saAmount.getFullText()); + sleRippleState = entryCreate(ltRIPPLE_STATE, uIndex); if (!bFlipped) @@ -1044,6 +1067,12 @@ void LedgerEntrySet::rippleCredit(const uint160& uSenderID, const uint160& uRece if (!bFlipped) saBalance.negate(); // Put balance in low terms. + cLog(lsDEBUG) << boost::str(boost::format("rippleCredit> %s (%s) -> %s : %s") + % RippleAddress::createHumanAccountID(uSenderID) + % saBalance.getFullText() + % RippleAddress::createHumanAccountID(uReceiverID) + % saAmount.getFullText()); + saBalance += saAmount; if (!bFlipped) @@ -1065,10 +1094,10 @@ STAmount LedgerEntrySet::rippleSend(const uint160& uSenderID, const uint160& uRe assert(!!uSenderID && !!uReceiverID); - if (uSenderID == uIssuerID || uReceiverID == uIssuerID) + if (uSenderID == uIssuerID || uReceiverID == uIssuerID || uIssuerID == ACCOUNT_ONE) { // Direct send: redeeming IOUs and/or sending own IOUs. - rippleCredit(uSenderID, uReceiverID, saAmount); + rippleCredit(uSenderID, uReceiverID, saAmount, false); saActual = saAmount; } diff --git a/src/cpp/ripple/LedgerFormats.cpp b/src/cpp/ripple/LedgerFormats.cpp index b4cd7890eb..5605cd0413 100644 --- a/src/cpp/ripple/LedgerFormats.cpp +++ b/src/cpp/ripple/LedgerFormats.cpp @@ -21,7 +21,7 @@ static bool LEFInit() << SOElement(sfBalance, SOE_REQUIRED) << SOElement(sfPreviousTxnID, SOE_REQUIRED) << SOElement(sfPreviousTxnLgrSeq, SOE_REQUIRED) - << SOElement(sfAuthorizedKey, SOE_OPTIONAL) + << SOElement(sfRegularKey, SOE_OPTIONAL) << SOElement(sfEmailHash, SOE_OPTIONAL) << SOElement(sfWalletLocator, SOE_OPTIONAL) << SOElement(sfWalletSize, SOE_OPTIONAL) @@ -39,14 +39,15 @@ static bool LEFInit() << SOElement(sfOwner, SOE_REQUIRED) << SOElement(sfExpiration, SOE_REQUIRED) << SOElement(sfBondAmount, SOE_REQUIRED) - << SOElement(sfCreateCode, SOE_REQUIRED) - << SOElement(sfFundCode, SOE_REQUIRED) - << SOElement(sfRemoveCode, SOE_REQUIRED) - << SOElement(sfExpireCode, SOE_REQUIRED) + << SOElement(sfCreateCode, SOE_OPTIONAL) + << SOElement(sfFundCode, SOE_OPTIONAL) + << SOElement(sfRemoveCode, SOE_OPTIONAL) + << SOElement(sfExpireCode, SOE_OPTIONAL) ; DECLARE_LEF(DirectoryNode, ltDIR_NODE) << SOElement(sfIndexes, SOE_REQUIRED) + << SOElement(sfRootIndex, SOE_REQUIRED) << SOElement(sfIndexNext, SOE_OPTIONAL) << SOElement(sfIndexPrevious, SOE_OPTIONAL) ; diff --git a/src/cpp/ripple/NetworkOPs.cpp b/src/cpp/ripple/NetworkOPs.cpp index dd8be86dcf..89a5d77e76 100644 --- a/src/cpp/ripple/NetworkOPs.cpp +++ b/src/cpp/ripple/NetworkOPs.cpp @@ -1006,8 +1006,8 @@ void NetworkOPs::pubProposedTransaction(Ledger::ref lpCurrent, const SerializedT ispListener->send(jvObj); } } - - pubAccountTransaction(lpCurrent,stTxn,terResult,false); + TransactionMetaSet::pointer ret; + pubAccountTransaction(lpCurrent,stTxn,terResult,false,ret); } void NetworkOPs::pubLedger(Ledger::ref lpAccepted) @@ -1051,7 +1051,10 @@ void NetworkOPs::pubLedger(Ledger::ref lpAccepted) // XXX Need to give failures too. TER terResult = tesSUCCESS; - pubAcceptedTransaction(lpAccepted, *stTxn, terResult); + SerializerIterator it(item->peekSerializer()); + + TransactionMetaSet::pointer meta = boost::make_shared(stTxn->getTransactionID(), lpAccepted->getLedgerSeq(), it.getVL()); + pubAcceptedTransaction(lpAccepted, *stTxn, terResult,meta); } } } @@ -1084,7 +1087,7 @@ Json::Value NetworkOPs::transJson(const SerializedTransaction& stTxn, TER terRes return jvObj; } -void NetworkOPs::pubAcceptedTransaction(Ledger::ref lpCurrent, const SerializedTransaction& stTxn, TER terResult) +void NetworkOPs::pubAcceptedTransaction(Ledger::ref lpCurrent, const SerializedTransaction& stTxn, TER terResult,TransactionMetaSet::pointer& meta) { Json::Value jvObj = transJson(stTxn, terResult, true, lpCurrent, "transaction"); @@ -1101,11 +1104,11 @@ void NetworkOPs::pubAcceptedTransaction(Ledger::ref lpCurrent, const SerializedT } } - pubAccountTransaction(lpCurrent,stTxn,terResult,true); + pubAccountTransaction(lpCurrent,stTxn,terResult,true,meta); } -void NetworkOPs::pubAccountTransaction(Ledger::ref lpCurrent, const SerializedTransaction& stTxn, TER terResult, bool bAccepted) +void NetworkOPs::pubAccountTransaction(Ledger::ref lpCurrent, const SerializedTransaction& stTxn, TER terResult, bool bAccepted,TransactionMetaSet::pointer& meta) { boost::unordered_set notify; diff --git a/src/cpp/ripple/NetworkOPs.h b/src/cpp/ripple/NetworkOPs.h index 56f6f428c5..778846a227 100644 --- a/src/cpp/ripple/NetworkOPs.h +++ b/src/cpp/ripple/NetworkOPs.h @@ -107,8 +107,8 @@ protected: Json::Value pubBootstrapAccountInfo(Ledger::ref lpAccepted, const RippleAddress& naAccountID); - void pubAcceptedTransaction(Ledger::ref lpCurrent, const SerializedTransaction& stTxn, TER terResult); - void pubAccountTransaction(Ledger::ref lpCurrent, const SerializedTransaction& stTxn, TER terResult,bool accepted); + void pubAcceptedTransaction(Ledger::ref lpCurrent, const SerializedTransaction& stTxn, TER terResult,TransactionMetaSet::pointer& meta); + void pubAccountTransaction(Ledger::ref lpCurrent, const SerializedTransaction& stTxn, TER terResult,bool accepted,TransactionMetaSet::pointer& meta); std::map getAffectedAccounts(const SerializedTransaction& stTxn); public: diff --git a/src/cpp/ripple/OfferCancelTransactor.cpp b/src/cpp/ripple/OfferCancelTransactor.cpp new file mode 100644 index 0000000000..2110c037e9 --- /dev/null +++ b/src/cpp/ripple/OfferCancelTransactor.cpp @@ -0,0 +1,41 @@ +#include "OfferCancelTransactor.h" +#include "Log.h" + +TER OfferCancelTransactor::doApply() +{ + TER terResult; + const uint32 uOfferSequence = mTxn.getFieldU32(sfOfferSequence); + const uint32 uAccountSequenceNext = mTxnAccount->getFieldU32(sfSequence); + + Log(lsDEBUG) << "doOfferCancel: uAccountSequenceNext=" << uAccountSequenceNext << " uOfferSequence=" << uOfferSequence; + + if (!uOfferSequence || uAccountSequenceNext-1 <= uOfferSequence) + { + Log(lsINFO) << "doOfferCancel: uAccountSequenceNext=" << uAccountSequenceNext << " uOfferSequence=" << uOfferSequence; + + terResult = temBAD_SEQUENCE; + } + else + { + const uint256 uOfferIndex = Ledger::getOfferIndex(mTxnAccountID, uOfferSequence); + SLE::pointer sleOffer = mEngine->entryCache(ltOFFER, uOfferIndex); + + if (sleOffer) + { + Log(lsWARNING) << "doOfferCancel: uOfferSequence=" << uOfferSequence; + + terResult = mEngine->getNodes().offerDelete(sleOffer, uOfferIndex, mTxnAccountID); + } + else + { + Log(lsWARNING) << "doOfferCancel: offer not found: " + << RippleAddress::createHumanAccountID(mTxnAccountID) + << " : " << uOfferSequence + << " : " << uOfferIndex.ToString(); + + terResult = tesSUCCESS; + } + } + + return terResult; +} \ No newline at end of file diff --git a/src/cpp/ripple/OfferCancelTransactor.h b/src/cpp/ripple/OfferCancelTransactor.h new file mode 100644 index 0000000000..8ddd6a5c1b --- /dev/null +++ b/src/cpp/ripple/OfferCancelTransactor.h @@ -0,0 +1,9 @@ +#include "Transactor.h" + +class OfferCancelTransactor : public Transactor +{ +public: + OfferCancelTransactor(const SerializedTransaction& txn,TransactionEngineParams params, TransactionEngine* engine) : Transactor(txn,params,engine) {} + + TER doApply(); +}; \ No newline at end of file diff --git a/src/cpp/ripple/OfferCreateTransactor.cpp b/src/cpp/ripple/OfferCreateTransactor.cpp new file mode 100644 index 0000000000..9f940b6e33 --- /dev/null +++ b/src/cpp/ripple/OfferCreateTransactor.cpp @@ -0,0 +1,440 @@ +#include "OfferCreateTransactor.h" +#include + +// 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 OfferCreateTransactor::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 = STAmount(saTakerPays.getCurrency(), saTakerPays.getIssuer()); + saTakerGot = STAmount(saTakerGets.getCurrency(), saTakerGets.getIssuer()); + + while (temUNCERTAIN == terResult) + { + SLE::pointer sleOfferDir; + uint64 uTipQuality; + + // Figure out next offer to take, if needed. + if (saTakerGets != saTakerGot && saTakerPays != saTakerPaid) + { + // Taker, still, needs to get and pay. + + sleOfferDir = mEngine->entryCache(ltDIR_NODE, mEngine->getLedger()->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 offers 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; + + mEngine->getNodes().dirFirst(uTipIndex, sleBookNode, uBookEntry, uOfferIndex); + + SLE::pointer sleOffer = mEngine->entryCache(ltOFFER, uOfferIndex); + + Log(lsINFO) << "takeOffers: considering offer : " << sleOffer->getJson(0); + + const uint160 uOfferOwnerID = sleOffer->getFieldAccount(sfAccount).getAccountID(); + STAmount saOfferPays = sleOffer->getFieldAmount(sfTakerGets); + STAmount saOfferGets = sleOffer->getFieldAmount(sfTakerPays); + + if (sleOffer->isFieldPresent(sfExpiration) && sleOffer->getFieldU32(sfExpiration) <= mEngine->getLedger()->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 = mEngine->getNodes().accountFunds(uOfferOwnerID, saOfferPays); + STAmount saTakerFunds = mEngine->getNodes().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; + STAmount saTakerIssuerFee; + STAmount saOfferIssuerFee; + + 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( + mEngine->getNodes().rippleTransferRate(uTakerAccountID, uOfferOwnerID, uTakerPaysAccountID), + mEngine->getNodes().rippleTransferRate(uOfferOwnerID, uTakerAccountID, uTakerGetsAccountID), + saOfferFunds, + saPay, // Driver XXX need to account for fees. + saOfferPays, + saOfferGets, + saTakerPays, + saTakerGets, + saSubTakerPaid, + saSubTakerGot, + saTakerIssuerFee, + saOfferIssuerFee); + + 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->setFieldAmount(sfTakerGets, saOfferPays -= saSubTakerGot); + + // Offer owner will get less. Subtract what owner just paid. + sleOffer->setFieldAmount(sfTakerPays, saOfferGets -= saSubTakerPaid); + + mEngine->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? + assert(!!saSubTakerGot.getIssuer()); + + mEngine->getNodes().accountSend(uOfferOwnerID, uTakerAccountID, saSubTakerGot); + mEngine->getNodes().accountSend(uOfferOwnerID, uTakerGetsAccountID, saOfferIssuerFee); + + saTakerGot += saSubTakerGot; + + // Taker pays offer owner. + // saSubTakerPaid.setIssuer(uTakerPaysAccountID); + assert(!!saSubTakerPaid.getIssuer()); + + mEngine->getNodes().accountSend(uTakerAccountID, uOfferOwnerID, saSubTakerPaid); + mEngine->getNodes().accountSend(uTakerAccountID, uTakerPaysAccountID, saTakerIssuerFee); + + 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 = mEngine->getNodes().offerDelete(uOfferIndex); + if (tesSUCCESS != terResult) + break; + } + } + + if (tesSUCCESS == terResult) + { + // On success, delete offers that became unfunded. + BOOST_FOREACH(const uint256& uOfferIndex, usOfferUnfundedBecame) + { + terResult = mEngine->getNodes().offerDelete(uOfferIndex); + if (tesSUCCESS != terResult) + break; + } + } + + return terResult; +} + +TER OfferCreateTransactor::doApply() +{ + Log(lsWARNING) << "doOfferCreate> " << mTxn.getJson(0); + const uint32 txFlags = mTxn.getFlags(); + const bool bPassive = isSetBit(txFlags, tfPassive); + STAmount saTakerPays = mTxn.getFieldAmount(sfTakerPays); + STAmount saTakerGets = mTxn.getFieldAmount(sfTakerGets); + + Log(lsINFO) << boost::str(boost::format("doOfferCreate: saTakerPays=%s saTakerGets=%s") + % saTakerPays.getFullText() + % saTakerGets.getFullText()); + + const uint160 uPaysIssuerID = saTakerPays.getIssuer(); + const uint160 uGetsIssuerID = saTakerGets.getIssuer(); + const uint32 uExpiration = mTxn.getFieldU32(sfExpiration); + const bool bHaveExpiration = mTxn.isFieldPresent(sfExpiration); + const uint32 uSequence = mTxn.getSequence(); + + const uint256 uLedgerIndex = Ledger::getOfferIndex(mTxnAccountID, uSequence); + SLE::pointer sleOffer = mEngine->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 && mEngine->getLedger()->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: XRP for XRP"; + + terResult = temBAD_OFFER; + } + else if (!saTakerPays.isPositive() || !saTakerGets.isPositive()) + { + 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 (!mEngine->getNodes().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 = mEngine->entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uPaysIssuerID)); + + if (!sleTakerPays) + { + Log(lsWARNING) << "doOfferCreate: delay: can't receive IOUs from non-existent issuer: " << RippleAddress::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 for %s -> %s") + % uTakeBookBase.ToString() + % saTakerGets.getFullText() + % saTakerPays.getFullText()); + + // Take using the parameters of the offer. +#if 1 + Log(lsWARNING) << "doOfferCreate: takeOffers: BEFORE saTakerGets=" << saTakerGets.getFullText(); + terResult = takeOffers( + bPassive, + uTakeBookBase, + mTxnAccountID, + mTxnAccount, + saTakerGets, + saTakerPays, + saOfferPaid, // How much was spent. + saOfferGot // How much was got. + ); +#else + terResult = tesSUCCESS; +#endif + 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: AFTER 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: mTxnAccountID=" << RippleAddress::createHumanAccountID(mTxnAccountID); + Log(lsWARNING) << "doOfferCreate: takeOffers: FUNDS=" << mEngine->getNodes().accountFunds(mTxnAccountID, saTakerGets).getFullText(); + + // Log(lsWARNING) << "doOfferCreate: takeOffers: uPaysIssuerID=" << RippleAddress::createHumanAccountID(uPaysIssuerID); + // Log(lsWARNING) << "doOfferCreate: takeOffers: uGetsIssuerID=" << RippleAddress::createHumanAccountID(uGetsIssuerID); + + if (tesSUCCESS == terResult + && saTakerPays // Still wanting something. + && saTakerGets // Still offering something. + && mEngine->getNodes().accountFunds(mTxnAccountID, saTakerGets).isPositive()) // Still funded. + { + // We need to place the remainder of the offer into its order book. + Log(lsINFO) << boost::str(boost::format("doOfferCreate: offer not fully consumed: saTakerPays=%s saTakerGets=%s") + % saTakerPays.getFullText() + % saTakerGets.getFullText()); + + // Add offer to owner's directory. + terResult = mEngine->getNodes().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() + % RippleAddress::createHumanAccountID(saTakerPays.getIssuer()) + % saTakerGets.getHumanCurrency() + % RippleAddress::createHumanAccountID(saTakerGets.getIssuer())); + + uDirectory = Ledger::getQualityIndex(uBookBase, uRate); // Use original rate. + + // Add offer to order book. + terResult = mEngine->getNodes().dirAdd(uBookNode, uDirectory, uLedgerIndex); + } + + if (tesSUCCESS == terResult) + { + Log(lsWARNING) << "doOfferCreate: sfAccount=" << RippleAddress::createHumanAccountID(mTxnAccountID); + Log(lsWARNING) << "doOfferCreate: uPaysIssuerID=" << RippleAddress::createHumanAccountID(uPaysIssuerID); + Log(lsWARNING) << "doOfferCreate: uGetsIssuerID=" << RippleAddress::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->setFieldAccount(sfAccount, mTxnAccountID); + sleOffer->setFieldU32(sfSequence, uSequence); + sleOffer->setFieldH256(sfBookDirectory, uDirectory); + sleOffer->setFieldAmount(sfTakerPays, saTakerPays); + sleOffer->setFieldAmount(sfTakerGets, saTakerGets); + sleOffer->setFieldU64(sfOwnerNode, uOwnerNode); + sleOffer->setFieldU64(sfBookNode, uBookNode); + + if (uExpiration) + sleOffer->setFieldU32(sfExpiration, uExpiration); + + if (bPassive) + sleOffer->setFlag(lsfPassive); + } + } + + Log(lsINFO) << "doOfferCreate: final sleOffer=" << sleOffer->getJson(0); + + return terResult; +} diff --git a/src/cpp/ripple/OfferCreateTransactor.h b/src/cpp/ripple/OfferCreateTransactor.h new file mode 100644 index 0000000000..02db25ca6e --- /dev/null +++ b/src/cpp/ripple/OfferCreateTransactor.h @@ -0,0 +1,22 @@ +#include "Transactor.h" + + +class OfferCreateTransactor : public Transactor +{ + TER takeOffers( + bool bPassive, + const uint256& uBookBase, + const uint160& uTakerAccountID, + const SLE::pointer& sleTakerAccount, + const STAmount& saTakerPays, + const STAmount& saTakerGets, + STAmount& saTakerPaid, + STAmount& saTakerGot); + +public: + OfferCreateTransactor(const SerializedTransaction& txn,TransactionEngineParams params, TransactionEngine* engine) : Transactor(txn,params,engine) {} + + TER doApply(); +}; + + diff --git a/src/cpp/ripple/PaymentTransactor.cpp b/src/cpp/ripple/PaymentTransactor.cpp new file mode 100644 index 0000000000..7577341cb6 --- /dev/null +++ b/src/cpp/ripple/PaymentTransactor.cpp @@ -0,0 +1,168 @@ +#include "PaymentTransactor.h" +#include "Config.h" +#include "RippleCalc.h" + +#define RIPPLE_PATHS_MAX 3 + +// TODO: only have the higher fee if the account doesn't in fact exist +void PaymentTransactor::calculateFee() +{ + if (mTxn.getFlags() & tfCreateAccount) + { + mFeeDue = theConfig.FEE_ACCOUNT_CREATE; + }else Transactor::calculateFee(); +} + +TER PaymentTransactor::doApply() +{ + // Ripple if source or destination is non-native or if there are paths. + const uint32 uTxFlags = mTxn.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 = mTxn.isFieldPresent(sfPaths); + const bool bMax = mTxn.isFieldPresent(sfSendMax); + const uint160 uDstAccountID = mTxn.getFieldAccount160(sfDestination); + const STAmount saDstAmount = mTxn.getFieldAmount(sfAmount); + const STAmount saMaxAmount = bMax + ? mTxn.getFieldAmount(sfSendMax) + : saDstAmount.isNative() + ? saDstAmount + : STAmount(saDstAmount.getCurrency(), mTxnAccountID, saDstAmount.getMantissa(), saDstAmount.getExponent(), saDstAmount.isNegative()); + 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 specified."; + + return temDST_NEEDED; + } + else if (bMax && !saMaxAmount.isPositive()) + { + Log(lsINFO) << "doPayment: Invalid transaction: bad max amount: " << saMaxAmount.getFullText(); + + return temBAD_AMOUNT; + } + else if (!saDstAmount.isPositive()) + { + Log(lsINFO) << "doPayment: Invalid transaction: bad dst amount: " << saDstAmount.getFullText(); + + return temBAD_AMOUNT; + } + else if (mTxnAccountID == uDstAccountID && uSrcCurrency == uDstCurrency && !bPaths) + { + Log(lsINFO) << boost::str(boost::format("doPayment: Invalid transaction: Redundant 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 = mEngine->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 XRP."; + + return temCREATEXRP; + } + else if (!bCreate) + { + Log(lsINFO) << "doPayment: Delay transaction: Destination account does not exist."; + + return terNO_DST; + } + + // Create the account. + sleDst = mEngine->entryCreate(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uDstAccountID)); + + sleDst->setFieldAccount(sfAccount, uDstAccountID); + sleDst->setFieldU32(sfSequence, 1); + } + else + { + mEngine->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 = mTxn.getFieldPathSet(sfPaths); + STAmount saMaxAmountAct; + STAmount saDstAmountAct; + + terResult = isSetBit(mParams, tapOPEN_LEDGER) && spsPaths.getPathCount() > RIPPLE_PATHS_MAX + ? telBAD_PATH_COUNT + : RippleCalc::rippleCalc( + mEngine->getNodes(), + saMaxAmountAct, + saDstAmountAct, + saMaxAmount, + saDstAmount, + uDstAccountID, + mTxnAccountID, + spsPaths, + bPartialPayment, + bLimitQuality, + bNoRippleDirect); + } + else + { + // Direct XRP payment. + + STAmount saSrcXRPBalance = mTxnAccount->getFieldAmount(sfBalance); + + if (saSrcXRPBalance < saDstAmount) + { + // Transaction might succeed, if applied in a different order. + Log(lsINFO) << "doPayment: Delay transaction: Insufficient funds."; + + terResult = terUNFUNDED; + } + else + { + mTxnAccount->setFieldAmount(sfBalance, saSrcXRPBalance - saDstAmount); + sleDst->setFieldAmount(sfBalance, sleDst->getFieldAmount(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; +} \ No newline at end of file diff --git a/src/cpp/ripple/PaymentTransactor.h b/src/cpp/ripple/PaymentTransactor.h new file mode 100644 index 0000000000..2c93ea713c --- /dev/null +++ b/src/cpp/ripple/PaymentTransactor.h @@ -0,0 +1,11 @@ + +#include "Transactor.h" + +class PaymentTransactor : public Transactor +{ + void calculateFee(); +public: + PaymentTransactor(const SerializedTransaction& txn,TransactionEngineParams params, TransactionEngine* engine) : Transactor(txn,params,engine) {} + + TER doApply(); +}; \ No newline at end of file diff --git a/src/cpp/ripple/ProofOfWork.cpp b/src/cpp/ripple/ProofOfWork.cpp index 8d8d50b69b..044794a888 100644 --- a/src/cpp/ripple/ProofOfWork.cpp +++ b/src/cpp/ripple/ProofOfWork.cpp @@ -18,7 +18,10 @@ const int ProofOfWork::sMaxIterations(1 << 23); bool ProofOfWork::isValid() const { - return ((mIterations <= sMaxIterations) && (mTarget >= sMinTarget)); + if ((mIterations <= sMaxIterations) && (mTarget >= sMinTarget)) + return true; + cLog(lsWARNING) << "Invalid PoW: " << mIterations << ", " << mTarget; + return false; } uint64 ProofOfWork::getDifficulty(const uint256& target, int iterations) @@ -33,7 +36,7 @@ uint64 ProofOfWork::getDifficulty(const uint256& target, int iterations) } // more iterations means more hashes per iteration but also a larger final hash - uint64 difficulty = iterations + (iterations / 4); + uint64 difficulty = iterations + (iterations / 8); // Multiply the number of hashes needed by 256 for each leading zero byte in the difficulty const unsigned char *ptr = target.begin(); @@ -70,7 +73,7 @@ uint256 ProofOfWork::solve(int maxIterations) const while (maxIterations > 0) { buf1[1] = nonce; - buf1[2] = uint256(); + buf1[2].zero(); for (int i = (mIterations - 1); i >= 0; --i) { buf1[2] = getSHA512Half(buf1); @@ -149,7 +152,7 @@ POWResult ProofOfWorkGenerator::checkProof(const std::string& token, const uint2 if (fields[4] != Serializer::getSHA512Half(v).GetHex()) { cLog(lsDEBUG) << "PoW " << token << " has a bad token"; - return powBADTOKEN; + return powCORRUPT; } uint256 challenge, target; @@ -178,6 +181,7 @@ POWResult ProofOfWorkGenerator::checkProof(const std::string& token, const uint2 { boost::mutex::scoped_lock sl(mLock); +// if (...) return powTOOEASY; if (!mSolvedChallenges.insert(powMap_vt(now, challenge)).second) { cLog(lsDEBUG) << "PoW " << token << " has been reused"; @@ -188,6 +192,77 @@ POWResult ProofOfWorkGenerator::checkProof(const std::string& token, const uint2 return powOK; } +void ProofOfWorkGenerator::sweep() +{ + time_t expire = time(NULL) - mValidTime; + + boost::mutex::scoped_lock sl(mLock); + do + { + powMap_t::left_map::iterator it = mSolvedChallenges.left.begin(); + if (it == mSolvedChallenges.left.end()) + return; + if (it->first >= expire) + return; + mSolvedChallenges.left.erase(it); + } while(1); +} + +struct PowEntry +{ + const char *target; + int iterations; +}; + +PowEntry PowEntries[32] = +{ + // FIXME: These targets are too low and iteration counts too low + // These get too difficulty before they become sufficently RAM intensive + { "0003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 256 }, // Hashes:5242880 KB=8 + { "0007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 512 }, // Hashes:5242880 KB=16 + { "0003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 512 }, // Hashes:10485760 KB=16 + { "0007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 1024 }, // Hashes:10485760 KB=32 + { "0003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 1024 }, // Hashes:20971520 KB=32 + { "0007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 2048 }, // Hashes:20971520 KB=64 + { "0003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 2048 }, // Hashes:41943040 KB=64 + { "0007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 4096 }, // Hashes:41943040 KB=128 + { "0003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 4096 }, // Hashes:83886080 KB=128 + { "0007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 8192 }, // Hashes:83886080 KB=256 + { "0003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 8192 }, // Hashes:167772160 KB=256 + { "0007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 16384 }, // Hashes:167772160 KB=512 + { "0007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 32768 }, // Hashes:335544320 MB=1 + { "0003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 32768 }, // Hashes:671088640 MB=1 + { "0007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 65536 }, // Hashes:671088640 MB=2 + { "0003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 65536 }, // Hashes:1342177280 MB=2 + { "0007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 131072 }, // Hashes:1342177280 MB=4 + { "0003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 131072 }, // Hashes:2684354560 MB=4 + { "0007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 262144 }, // Hashes:2684354560 MB=8 + { "0007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 524288 }, // Hashes:5368709120 MB=16 + { "0003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 524288 }, // Hashes:10737418240 MB=16 + { "0007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 1048576 }, // Hashes:10737418240 MB=32 + { "0003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 1048576 }, // Hashes:21474836480 MB=32 + { "0007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 2097152 }, // Hashes:21474836480 MB=64 + { "0003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 2097152 }, // Hashes:42949672960 MB=64 + { "00007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 524288 }, // Hashes:85899345920 MB=16 + { "00003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 524288 }, // Hashes:171798691840 MB=16 + { "00007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 1048576 }, // Hashes:171798691840 MB=32 + { "00003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 1048576 }, // Hashes:343597383680 MB=32 + { "00007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 2097152 }, // Hashes:343597383680 MB=64 + { "00003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 2097152 }, // Hashes:687194767360 MB=64 +}; + +void ProofOfWorkGenerator::setDifficulty(int i) +{ + assert((i >= 0) && (i <= 31)); + time_t now = time(NULL); + + boost::mutex::scoped_lock sl(mLock); + mPowEntry = i; + mIterations = PowEntries[i].iterations; + mTarget.SetHex(PowEntries[i].target); + mLastDifficultyChange = now; +} + BOOST_AUTO_TEST_SUITE(ProofOfWork_suite) BOOST_AUTO_TEST_CASE( ProofOfWork_test ) @@ -209,6 +284,27 @@ BOOST_AUTO_TEST_CASE( ProofOfWork_test ) cLog(lsDEBUG) << "A reused nonce error is expected"; if (gen.checkProof(pow.getToken(), solution) != powREUSED) BOOST_FAIL("Reuse solution not detected"); + +#ifdef SOLVE_POWS + for (int i = 0; i < 12; ++i) + { + gen.setDifficulty(i); + ProofOfWork pow = gen.getProof(); + cLog(lsINFO) << "Level: " << i << ", Estimated difficulty: " << pow.getDifficulty(); + uint256 solution = pow.solve(131072); + if (solution.isZero()) + cLog(lsINFO) << "Giving up"; + else + { + cLog(lsINFO) << "Solution found"; + if (gen.checkProof(pow.getToken(), solution) != powOK) + { + cLog(lsFATAL) << "Solution fails"; + } + } + } +#endif + } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/cpp/ripple/ProofOfWork.h b/src/cpp/ripple/ProofOfWork.h index a09eec7377..3d88ffd197 100644 --- a/src/cpp/ripple/ProofOfWork.h +++ b/src/cpp/ripple/ProofOfWork.h @@ -13,11 +13,11 @@ enum POWResult { powOK = 0, - powREUSED = 1, - powBADNONCE = 2, - powBADTOKEN = 3, - powEXPIRED = 4, - powCORRUPT = 5, + powREUSED = 1, // already submitted + powBADNONCE = 2, // you didn't solve it + powEXPIRED = 3, // time is up + powCORRUPT = 4, + powTOOEASY = 5, // the difficulty increased too much while you solved it }; class ProofOfWork @@ -62,6 +62,7 @@ protected: uint256 mTarget; time_t mLastDifficultyChange; int mValidTime; + int mPowEntry; powMap_t mSolvedChallenges; boost::mutex mLock; @@ -72,6 +73,7 @@ public: ProofOfWork getProof(); POWResult checkProof(const std::string& token, const uint256& solution); uint64 getDifficulty() { return ProofOfWork::getDifficulty(mTarget, mIterations); } + void setDifficulty(int i); void loadHigh(); void loadLow(); diff --git a/src/cpp/ripple/RPCHandler.cpp b/src/cpp/ripple/RPCHandler.cpp index 22402b394c..ba9f6f78b9 100644 --- a/src/cpp/ripple/RPCHandler.cpp +++ b/src/cpp/ripple/RPCHandler.cpp @@ -700,6 +700,8 @@ Json::Value RPCHandler::doSubmit(const Json::Value& params) { Json::Value txJSON; Json::Reader reader; + + //std::string hello=params[1u].asString(); if (reader.parse(params[1u].asString(), txJSON)) { @@ -741,10 +743,10 @@ Json::Value RPCHandler::handleJSONSubmit(const Json::Value& jvRequest) } AccountState::pointer asSrc = mNetOps->getAccountState(uint256(0), srcAddress); + if(!asSrc) return rpcError(rpcSRC_ACT_MALFORMED); - if( txJSON["type"]=="Payment") + if( txJSON["TransactionType"]=="Payment") { - txJSON["TransactionType"]=0; RippleAddress dstAccountID; @@ -764,7 +766,7 @@ Json::Value RPCHandler::handleJSONSubmit(const Json::Value& jvRequest) else txJSON["Fee"]=(int)theConfig.FEE_ACCOUNT_CREATE; } - if(!txJSON.isMember("Paths") && (!jvRequest.isMember("build_path") || jvRequest["build_path"].asBool())) + if(!txJSON.isMember("Paths") && jvRequest.isMember("build_path") ) { if(txJSON["Amount"].isObject() || txJSON.isMember("SendMax") ) { // we need a ripple path @@ -796,9 +798,12 @@ Json::Value RPCHandler::handleJSONSubmit(const Json::Value& jvRequest) Pathfinder pf(srcAddress, dstAccountID, srcCurrencyID, dstAmount); pf.findPaths(5, 1, spsPaths); - txJSON["Paths"]=spsPaths.getJson(0); - if(txJSON.isMember("Flags")) txJSON["Flags"]=txJSON["Flags"].asUInt() | 2; - else txJSON["Flags"]=2; + if(!spsPaths.isEmpty()) + { + txJSON["Paths"]=spsPaths.getJson(0); + if(txJSON.isMember("Flags")) txJSON["Flags"]=txJSON["Flags"].asUInt() | 2; + else txJSON["Flags"]=2; + } } } diff --git a/src/cpp/ripple/RegularKeySetTransactor.cpp b/src/cpp/ripple/RegularKeySetTransactor.cpp new file mode 100644 index 0000000000..eb89bed283 --- /dev/null +++ b/src/cpp/ripple/RegularKeySetTransactor.cpp @@ -0,0 +1,51 @@ +#include "RegularKeySetTransactor.h" +#include "Log.h" + + +SETUP_LOG(); + +// TODO: +TER RegularKeySetTransactor::checkSig() +{ + // Transaction's signing public key must be for the source account. + // To prove the master private key made this transaction. + if (mSigningPubKey.getAccountID() != mTxnAccountID) + { + // Signing Pub Key must be for Source Account ID. + cLog(lsWARNING) << "sourceAccountID: " << mSigningPubKey.humanAccountID(); + cLog(lsWARNING) << "txn accountID: " << mTxn.getSourceAccount().humanAccountID(); + + return temBAD_SET_ID; + } + return tesSUCCESS; +} + +// TODO: this should be default fee if flag isn't set +void RegularKeySetTransactor::calculateFee() +{ + mFeeDue = 0; +} + + +// TODO: change to take a fee if there is one there +TER RegularKeySetTransactor::doApply() +{ + std::cerr << "doRegularKeySet>" << std::endl; + + if (mTxnAccount->getFlags() & lsfPasswordSpent) + { + std::cerr << "doRegularKeySet: Delay transaction: Funds already spent." << std::endl; + + return terFUNDS_SPENT; + } + + mTxnAccount->setFlag(lsfPasswordSpent); + + uint160 uAuthKeyID=mTxn.getFieldAccount160(sfRegularKey); + mTxnAccount->setFieldAccount(sfRegularKey, uAuthKeyID); + + + std::cerr << "doRegularKeySet<" << std::endl; + + return tesSUCCESS; +} diff --git a/src/cpp/ripple/RegularKeySetTransactor.h b/src/cpp/ripple/RegularKeySetTransactor.h new file mode 100644 index 0000000000..a6df0b3569 --- /dev/null +++ b/src/cpp/ripple/RegularKeySetTransactor.h @@ -0,0 +1,11 @@ +#include "Transactor.h" + +class RegularKeySetTransactor : public Transactor +{ + void calculateFee(); +public: + RegularKeySetTransactor(const SerializedTransaction& txn,TransactionEngineParams params, TransactionEngine* engine) : Transactor(txn,params,engine) {} + TER checkFee(); + TER checkSig(); + TER doApply(); +}; diff --git a/src/cpp/ripple/RippleCalc.cpp b/src/cpp/ripple/RippleCalc.cpp index 568449eb0e..e364344698 100644 --- a/src/cpp/ripple/RippleCalc.cpp +++ b/src/cpp/ripple/RippleCalc.cpp @@ -471,15 +471,13 @@ TER RippleCalc::calcNodeDeliverRev( return terResult; } -// Deliver maximum amount of funds from previous node. -// Goal: Make progress consuming the offer. +// For current offer, get input from deliver/limbo and output to next account or deliver for next offers. TER RippleCalc::calcNodeDeliverFwd( const unsigned int uNode, // 0 < uNode < uLast PathState::ref pspCur, const bool bMultiQuality, const uint160& uInAccountID, // --> Input owner's account. - const STAmount& saInFunds, // --> Funds available for delivery and fees. - const STAmount& saInReq, // --> Limit to deliver. + const STAmount& saInReq, // --> Amount to deliver. STAmount& saInAct, // <-- Amount delivered. STAmount& saInFees) // <-- Fees charged. { @@ -490,7 +488,9 @@ TER RippleCalc::calcNodeDeliverFwd( PaymentNode& pnNxt = pspCur->vpnNodes[uNode+1]; const uint160& uNxtAccountID = pnNxt.uAccountID; + const uint160& uCurCurrencyID = pnCur.uCurrencyID; const uint160& uCurIssuerID = pnCur.uIssuerID; + const uint160& uPrvCurrencyID = pnPrv.uCurrencyID; const uint160& uPrvIssuerID = pnPrv.uIssuerID; const STAmount& saTransferRate = pnPrv.saTransferRate; @@ -498,19 +498,20 @@ TER RippleCalc::calcNodeDeliverFwd( uint256& uDirectTip = pnCur.uDirectTip; - uDirectTip = 0; // Restart book searching. + uDirectTip = 0; // Restart book searching. - saInAct.zero(saInFunds); - saInFees.zero(saInFunds); + saInAct.zero(saInReq); + saInFees.zero(saInReq); + saCurDeliverAct.zero(uCurCurrencyID, uCurIssuerID); while (tesSUCCESS == terResult - && saInAct != saInReq // Did not deliver limit. - && saInAct + saInFees != saInFunds) // Did not deliver all funds. + && saInAct + saInFees != saInReq) // Did not deliver all funds. { terResult = calcNodeAdvance(uNode, pspCur, bMultiQuality, false); // If needed, advance to next funded offer. if (tesSUCCESS == terResult) { + // Doesn't charge input. Input funds are in limbo. bool& bEntryAdvance = pnCur.bEntryAdvance; STAmount& saOfrRate = pnCur.saOfrRate; uint256& uOfferIndex = pnCur.uOfferIndex; @@ -521,9 +522,11 @@ TER RippleCalc::calcNodeDeliverFwd( STAmount& saTakerPays = pnCur.saTakerPays; STAmount& saTakerGets = pnCur.saTakerGets; - const STAmount saInFeeRate = uInAccountID == uPrvIssuerID || uOfrOwnerID == uPrvIssuerID // Issuer receiving or sending. - ? saOne // No fee. - : saTransferRate; // Transfer rate of issuer. + const STAmount saInFeeRate = !!uPrvCurrencyID + ? uInAccountID == uPrvIssuerID || uOfrOwnerID == uPrvIssuerID // Issuer receiving or sending. + ? saOne // No fee. + : saTransferRate // Transfer rate of issuer. + : saOne; // // First calculate assuming no output fees. @@ -532,11 +535,11 @@ TER RippleCalc::calcNodeDeliverFwd( STAmount saOutFunded = std::max(saOfferFunds, saTakerGets); // Offer maximum out - There are no out fees. STAmount saInFunded = STAmount::multiply(saOutFunded, saOfrRate, saInReq); // Offer maximum in - Limited by by payout. STAmount saInTotal = STAmount::multiply(saInFunded, saTransferRate); // Offer maximum in with fees. - STAmount saInSum = std::min(saInTotal, saInFunds-saInAct-saInFees); // In limited by saInFunds. + STAmount saInSum = std::min(saInTotal, saInReq-saInAct-saInFees); // In limited by saInReq. STAmount saInPassAct = STAmount::divide(saInSum, saInFeeRate); // In without fees. STAmount saOutPassMax = STAmount::divide(saInPassAct, saOfrRate, saOutFunded); // Out. - STAmount saInPassFees(saInFunds.getCurrency(), saInFunds.getIssuer()); + STAmount saInPassFees(saInReq.getCurrency(), saInReq.getIssuer()); STAmount saOutPassAct(saOfferFunds.getCurrency(), saOfferFunds.getIssuer()); cLog(lsINFO) << boost::str(boost::format("calcNodeDeliverFwd: saOutFunded=%s saInFunded=%s saInTotal=%s saInSum=%s saInPassAct=%s saOutPassMax=%s") @@ -551,22 +554,23 @@ TER RippleCalc::calcNodeDeliverFwd( { // ? --> OFFER --> account // Input fees: vary based upon the consumed offer's owner. - // Output fees: none as the destination account is the issuer. - - // XXX This doesn't claim input. - // XXX Assumes input is in limbo. XXX Check. - - // Debit offer owner. - lesActive.accountSend(uOfrOwnerID, uCurIssuerID, saOutPassMax); + // Output fees: none as XRP or the destination account is the issuer. saOutPassAct = saOutPassMax; - cLog(lsINFO) << boost::str(boost::format("calcNodeDeliverFwd: ? --> OFFER --> account: saOutPassAct=%s") - % saOutPassAct); + cLog(lsDEBUG) << boost::str(boost::format("calcNodeDeliverFwd: ? --> OFFER --> account: uOfrOwnerID=%s uNxtAccountID=%s saOutPassAct=%s") + % RippleAddress::createHumanAccountID(uOfrOwnerID) + % RippleAddress::createHumanAccountID(uNxtAccountID) + % saOutPassAct.getFullText()); + + // Debit offer owner, send XRP or non-XPR to next account. + lesActive.accountSend(uOfrOwnerID, uNxtAccountID, saOutPassAct); } else { // ? --> OFFER --> offer + // Offer to offer means current order book's output currency and issuer match next order book's input current and + // issuer. STAmount saOutPassFees; terResult = RippleCalc::calcNodeDeliverFwd( @@ -575,16 +579,22 @@ TER RippleCalc::calcNodeDeliverFwd( bMultiQuality, uOfrOwnerID, saOutPassMax, - saOutPassMax, saOutPassAct, // <-- Amount delivered. saOutPassFees); // <-- Fees charged. if (tesSUCCESS != terResult) break; - // Offer maximum in limited by next payout. + // Offer maximum in split into fees by next payout. saInPassAct = STAmount::multiply(saOutPassAct, saOfrRate); saInPassFees = STAmount::multiply(saInFunded, saInFeeRate)-saInPassAct; + + // Do outbound debiting. + // Send to issuer/limbo total amount (no fees to issuer). + lesActive.accountSend(uOfrOwnerID, !!uCurCurrencyID ? uCurIssuerID : ACCOUNT_XRP, saOutPassAct); + + cLog(lsINFO) << boost::str(boost::format("calcNodeDeliverFwd: ? --> OFFER --> offer: saOutPassAct=%s") + % saOutPassAct); } cLog(lsINFO) << boost::str(boost::format("calcNodeDeliverFwd: saTakerGets=%s saTakerPays=%s saInPassAct=%s saOutPassAct=%s") @@ -596,13 +606,12 @@ TER RippleCalc::calcNodeDeliverFwd( // Funds were spent. bFundsDirty = true; - // Credit issuer transfer fees. - lesActive.accountSend(uInAccountID, uOfrOwnerID, saInPassFees); - - // Credit offer owner from offer. - lesActive.accountSend(uInAccountID, uOfrOwnerID, saInPassAct); + // Do inbound crediting. + // Credit offer owner from in issuer/limbo (don't take transfer fees). + lesActive.accountSend(!!uPrvCurrencyID ? uInAccountID : ACCOUNT_XRP, uOfrOwnerID, saInPassAct); // Adjust offer + // Fees are considered paid from a seperate budget and are not named in the offer. sleOffer->setFieldAmount(sfTakerGets, saTakerGets - saOutPassAct); sleOffer->setFieldAmount(sfTakerPays, saTakerPays - saInPassAct); @@ -661,7 +670,7 @@ TER RippleCalc::calcNodeOfferRev( } // Called to drive the from the first offer node in a chain. -// - Offer input is limbo. +// - Offer input is in issuer/limbo. // - Current offers consumed. // - Current offer owners debited. // - Transfer fees credited to issuer. @@ -688,7 +697,6 @@ TER RippleCalc::calcNodeOfferFwd( bMultiQuality, pnPrv.uAccountID, pnPrv.saFwdDeliver, - pnPrv.saFwdDeliver, saInAct, saInFees); @@ -843,6 +851,7 @@ TER RippleCalc::calcNodeAccountRev(const unsigned int uNode, PathState::ref pspC const uint160& uCurrencyID = pnCur.uCurrencyID; + // XXX Don't look up quality for XRP const uint32 uQualityIn = uNode ? lesActive.rippleQualityIn(uCurAccountID, uPrvAccountID, uCurrencyID) : QUALITY_ONE; const uint32 uQualityOut = uNode != uLast ? lesActive.rippleQualityOut(uCurAccountID, uNxtAccountID, uCurrencyID) : QUALITY_ONE; @@ -908,15 +917,16 @@ TER RippleCalc::calcNodeAccountRev(const unsigned int uNode, PathState::ref pspC || !saNxtOwed.isNegative() // saNxtOwed >= 0: Sender not holding next IOUs, saNxtOwed < 0: Sender holding next IOUs. || -saNxtOwed == saCurRedeemReq); // If issue req, then redeem req must consume all owed. - if (bPrvAccount && bNxtAccount) + if (!uNode) { - if (!uNode) - { - // ^ --> ACCOUNT --> account|offer - // Nothing to do, there is no previous to adjust. - nothing(); - } - else if (uNode == uLast) + // ^ --> ACCOUNT --> account|offer + // Nothing to do, there is no previous to adjust. + + nothing(); + } + else if (bPrvAccount && bNxtAccount) + { + if (uNode == uLast) { // account --> ACCOUNT --> $ // Overall deliverable. @@ -1157,7 +1167,7 @@ TER RippleCalc::calcNodeAccountRev(const unsigned int uNode, PathState::ref pspC } // The reverse pass has been narrowing by credit available and inflating by fees as it worked backwards. -// Now, push through the actual amount to each node and adjust balances. +// Now, for the current account node, take the actual amount from previous and adjust forward balances. // // Perform balance adjustments between previous and current node. // - The previous node: specifies what to push through to current. @@ -1165,6 +1175,8 @@ TER RippleCalc::calcNodeAccountRev(const unsigned int uNode, PathState::ref pspC // Then, compute current node's output for next node. // - Current node: specify what to push through to next. // - Output to next node is computed as input minus quality or transfer fee. +// - If next node is an offer and output is non-XRP then we are the issuer and do not need to push funds. +// - If next node is an offer and output is XRP then we need to deliver funds to limbo. TER RippleCalc::calcNodeAccountFwd( const unsigned int uNode, // 0 <= uNode <= uLast PathState::ref pspCur, @@ -1186,6 +1198,8 @@ TER RippleCalc::calcNodeAccountFwd( const uint160& uPrvAccountID = bPrvAccount ? pnPrv.uAccountID : uCurAccountID; const uint160& uNxtAccountID = bNxtAccount ? pnNxt.uAccountID : uCurAccountID; // Offers are always issue. +// const uint160& uCurIssuerID = pnCur.uIssuerID; + const uint160& uCurrencyID = pnCur.uCurrencyID; uint32 uQualityIn = uNode ? lesActive.rippleQualityIn(uCurAccountID, uPrvAccountID, uCurrencyID) : QUALITY_ONE; @@ -1216,6 +1230,9 @@ TER RippleCalc::calcNodeAccountFwd( const STAmount& saCurDeliverReq = pnCur.saRevDeliver; STAmount& saCurDeliverAct = pnCur.saFwdDeliver; + // For !uNode + STAmount& saCurSendMaxPass = pspCur->saInPass; // Report how much pass sends. + cLog(lsINFO) << boost::str(boost::format("calcNodeAccountFwd> uNode=%d/%d saPrvRedeemReq=%s saPrvIssueReq=%s saPrvDeliverReq=%s saCurRedeemReq=%s saCurIssueReq=%s saCurDeliverReq=%s") % uNode % uLast @@ -1238,37 +1255,31 @@ TER RippleCalc::calcNodeAccountFwd( // First node, calculate amount to ripple based on what is available. - // Limit by sendmax. - const STAmount saCurSendMaxReq = pspCur->saInReq.isNegative() - ? pspCur->saInReq // Negative for no limit, doing a calculation. - : pspCur->saInReq-pspCur->saInAct; // request - done. - STAmount& saCurSendMaxPass = pspCur->saInPass; // Report how much pass sends. + saCurRedeemAct = saCurRedeemReq; + + if (!pspCur->saInReq.isNegative()) + { + // Limit by send max. + saCurRedeemAct = std::min(saCurRedeemAct, pspCur->saInReq-pspCur->saInAct); + } - saCurRedeemAct = saCurRedeemReq - // Redeem requested. - ? saCurRedeemReq.isNegative() - ? saCurRedeemReq - : std::min(saCurRedeemReq, saCurSendMaxReq) - // No redeeming. - : saCurRedeemReq; saCurSendMaxPass = saCurRedeemAct; - saCurIssueAct = (saCurIssueReq // Issue wanted. - && (saCurSendMaxReq.isNegative() // No limit. - || saCurSendMaxPass != saCurSendMaxReq)) // Not yet satisfied. - // Issue requested and pass does not meet max. - ? saCurSendMaxReq.isNegative() - ? saCurIssueReq - : std::min(saCurSendMaxReq-saCurRedeemAct, saCurIssueReq) - // No issuing. + saCurIssueAct = saCurRedeemAct == saCurRedeemReq // Fully redeemed. + ? saCurIssueReq : STAmount(saCurIssueReq); + if (!!saCurIssueAct && !pspCur->saInReq.isNegative()) + { + // Limit by send max. + saCurIssueAct = std::min(saCurIssueAct, pspCur->saInReq-pspCur->saInAct-saCurRedeemAct); + } + saCurSendMaxPass += saCurIssueAct; - cLog(lsINFO) << boost::str(boost::format("calcNodeAccountFwd: ^ --> ACCOUNT --> account : saInReq=%s saInAct=%s saCurSendMaxReq=%s saCurRedeemAct=%s saCurIssueReq=%s saCurIssueAct=%s saCurSendMaxPass=%s") + cLog(lsINFO) << boost::str(boost::format("calcNodeAccountFwd: ^ --> ACCOUNT --> account : saInReq=%s saInAct=%s saCurRedeemAct=%s saCurIssueReq=%s saCurIssueAct=%s saCurSendMaxPass=%s") % pspCur->saInReq.getFullText() % pspCur->saInAct.getFullText() - % saCurSendMaxReq.getFullText() % saCurRedeemAct.getFullText() % saCurIssueReq.getFullText() % saCurIssueAct.getFullText() @@ -1343,31 +1354,72 @@ TER RippleCalc::calcNodeAccountFwd( } else if (bPrvAccount && !bNxtAccount) { - // account --> ACCOUNT --> offer - cLog(lsINFO) << boost::str(boost::format("calcNodeAccountFwd: account --> ACCOUNT --> offer")); - - saCurDeliverAct.zero(saCurDeliverReq); - - // redeem -> issue. - // wants to redeem and current would and can issue. - // If redeeming cur to next is done, this implies can issue. - if (saPrvRedeemReq) // Previous wants to redeem. + if (uNode) { - // Rate : 1.0 : transfer_rate - calcNodeRipple(QUALITY_ONE, lesActive.rippleTransferRate(uCurAccountID), saPrvRedeemReq, saCurDeliverReq, saPrvRedeemAct, saCurDeliverAct, uRateMax); - } + // Non-XRP, current node is the issuer. + cLog(lsDEBUG) << boost::str(boost::format("calcNodeAccountFwd: account --> ACCOUNT --> offer")); - // issue -> issue - if (saPrvRedeemReq == saPrvRedeemAct // Previous done redeeming: Previous has no IOUs. - && saPrvIssueReq) // Previous wants to issue. To next must be ok. + saCurDeliverAct.zero(saCurDeliverReq); + + // redeem -> issue/deliver. + // Previous wants to redeem. + // Current is issuing to an offer so leave funds in account as "limbo". + if (saPrvRedeemReq) // Previous wants to redeem. + { + // Rate : 1.0 : transfer_rate + // XXX Is having the transfer rate here correct? + calcNodeRipple(QUALITY_ONE, lesActive.rippleTransferRate(uCurAccountID), saPrvRedeemReq, saCurDeliverReq, saPrvRedeemAct, saCurDeliverAct, uRateMax); + } + + // issue -> issue/deliver + if (saPrvRedeemReq == saPrvRedeemAct // Previous done redeeming: Previous has no IOUs. + && saPrvIssueReq) // Previous wants to issue. To next must be ok. + { + // Rate: quality in : 1.0 + calcNodeRipple(uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurDeliverReq, saPrvIssueAct, saCurDeliverAct, uRateMax); + } + + // Adjust prv --> cur balance : take all inbound + lesActive.rippleCredit(uPrvAccountID, uCurAccountID, saPrvRedeemReq + saPrvIssueReq, false); + } + else { - // Rate: quality in : 1.0 - calcNodeRipple(uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurDeliverReq, saPrvIssueAct, saCurDeliverAct, uRateMax); - } + // Delivering amount requested from downstream. + saCurDeliverAct = saCurDeliverReq; - // Adjust prv --> cur balance : take all inbound - // XXX Currency must be in amount. - lesActive.rippleCredit(uPrvAccountID, uCurAccountID, saPrvRedeemReq + saPrvIssueReq, false); + // If limited, then limit by send max and available. + if (!pspCur->saInReq.isNegative()) + { + // Limit by send max. + saCurDeliverAct = std::min(saCurDeliverAct, pspCur->saInReq-pspCur->saInAct); + + // Limit XRP by available. No limit for non-XRP as issuer. + if (!uCurAccountID) + saCurDeliverAct = std::min(saCurDeliverAct, lesActive.accountHolds(uCurAccountID, CURRENCY_XRP, ACCOUNT_XRP)); + + } + saCurSendMaxPass = saCurDeliverAct; // Record amount sent for pass. + + if (!!uCurrencyID) + { + // Non-XRP, current node is the issuer. + // We could be delivering to multiple accounts, so we don't know which ripple balance will be adjusted. Assume + // just issuing. + + cLog(lsDEBUG) << boost::str(boost::format("calcNodeAccountFwd: ^ --> ACCOUNT -- !XRP --> offer")); + + // As the issuer, would only issue. + // Don't need to actually deliver. As from delivering leave in the issuer as limbo. + nothing(); + } + else + { + cLog(lsDEBUG) << boost::str(boost::format("calcNodeAccountFwd: ^ --> ACCOUNT -- XRP --> offer")); + + // Deliver XRP to limbo. + lesActive.accountSend(uCurAccountID, ACCOUNT_XRP, saCurDeliverAct); + } + } } else if (!bPrvAccount && bNxtAccount) { diff --git a/src/cpp/ripple/RippleCalc.h b/src/cpp/ripple/RippleCalc.h index 6dffe4723d..887c5c5577 100644 --- a/src/cpp/ripple/RippleCalc.h +++ b/src/cpp/ripple/RippleCalc.h @@ -2,6 +2,7 @@ #define __RIPPLE_CALC__ #include +#include #include "LedgerEntrySet.h" @@ -163,7 +164,6 @@ public: PathState::ref pspCur, const bool bMultiQuality, const uint160& uInAccountID, - const STAmount& saInFunds, const STAmount& saInReq, STAmount& saInAct, STAmount& saInFees); diff --git a/src/cpp/ripple/SerializeProto.h b/src/cpp/ripple/SerializeProto.h index 00677319a8..8b5920f644 100644 --- a/src/cpp/ripple/SerializeProto.h +++ b/src/cpp/ripple/SerializeProto.h @@ -76,6 +76,7 @@ FIELD(PreviousTxnID, HASH256, 5) FIELD(LedgerIndex, HASH256, 6) FIELD(WalletLocator, HASH256, 7) + FIELD(RootIndex, HASH256, 8) // 256-bit (uncommon) FIELD(BookDirectory, HASH256, 16) @@ -116,7 +117,7 @@ FIELD(Destination, ACCOUNT, 3) FIELD(Issuer, ACCOUNT, 4) FIELD(Target, ACCOUNT, 7) - FIELD(AuthorizedKey, ACCOUNT, 8) + FIELD(RegularKey, ACCOUNT, 8) // path set FIELD(Paths, PATHSET, 1) diff --git a/src/cpp/ripple/SerializedObject.cpp b/src/cpp/ripple/SerializedObject.cpp index 2ef6a84f8e..1991110449 100644 --- a/src/cpp/ripple/SerializedObject.cpp +++ b/src/cpp/ripple/SerializedObject.cpp @@ -138,7 +138,7 @@ void STObject::set(const std::vector& type) BOOST_FOREACH(const SOElement::ptr& elem, type) { mType.push_back(elem); - if (elem->flags == SOE_OPTIONAL) + if (elem->flags != SOE_REQUIRED) giveObject(makeNonPresentObject(elem->e_field)); else giveObject(makeDefaultObject(elem->e_field)); @@ -159,12 +159,18 @@ bool STObject::setType(const std::vector &type) { match = true; newData.push_back(mData.release(it).release()); + if ((elem->flags == SOE_DEFAULT) && it->isDefault()) + { + cLog(lsWARNING) << "setType( " << getFName().getName() << ") invalid default " + << elem->e_field.fieldName; + valid = false; + } break; } if (!match) { - if (elem->flags != SOE_OPTIONAL) + if (elem->flags == SOE_REQUIRED) { cLog(lsWARNING) << "setType( " << getFName().getName() << ") invalid missing " << elem->e_field.fieldName; diff --git a/src/cpp/ripple/SerializedTypes.h b/src/cpp/ripple/SerializedTypes.h index 7b1e8c0cf5..3745a92190 100644 --- a/src/cpp/ripple/SerializedTypes.h +++ b/src/cpp/ripple/SerializedTypes.h @@ -688,7 +688,7 @@ public: void addPath(const STPath& e) { value.push_back(e); } virtual bool isEquivalent(const SerializedType& t) const; - virtual bool isDefault() const { return value.empty(); } + virtual bool isDefault() const { return value.empty(); } void printDebug(); diff --git a/src/cpp/ripple/Transaction.h b/src/cpp/ripple/Transaction.h index 0f208d2b58..f56fbbc96d 100644 --- a/src/cpp/ripple/Transaction.h +++ b/src/cpp/ripple/Transaction.h @@ -2,8 +2,7 @@ #define __TRANSACTION__ // -// Notes: this code contains legacy constructored sharedXYZ and setXYZ. The intent is for these functions to go away. Transactions -// should now be constructed in JSON with. Use STObject::parseJson to obtain a binary version. +// Transactions should be constructed in JSON with. Use STObject::parseJson to obtain a binary version. // #include diff --git a/src/cpp/ripple/TransactionAction.cpp b/src/cpp/ripple/TransactionAction.cpp deleted file mode 100644 index 591cfc6b04..0000000000 --- a/src/cpp/ripple/TransactionAction.cpp +++ /dev/null @@ -1,1151 +0,0 @@ -// -// 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. - // - /* JED: taking out generator stuff until we have a better idea of how people will use this - std::vector vucCipher = txn.getFieldVL(sfGenerator); - std::vector vucPubKey = txn.getFieldVL(sfPublicKey); - std::vector vucSignature = txn.getFieldVL(sfSignature); - RippleAddress naAccountPublic = RippleAddress::createAccountPublic(vucPubKey); - - // FIXME: This should be moved to the transaction's signature check and cached - 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->setFieldVL(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.getFieldAccount160(sfAuthorizedKey); // PasswordSet - - */ - uint160 uAuthKeyID=txn.getFieldAccount160(sfAuthorizedKey); - mTxnAccount->setFieldAccount(sfAuthorizedKey, uAuthKeyID); - - return tesSUCCESS; -} - -TER TransactionEngine::doAccountSet(const SerializedTransaction& txn) -{ - Log(lsINFO) << "doAccountSet>"; - - // - // EmailHash - // - - if (txn.isFieldPresent(sfEmailHash)) - { - uint128 uHash = txn.getFieldH128(sfEmailHash); - - if (!uHash) - { - Log(lsINFO) << "doAccountSet: unset email hash"; - - mTxnAccount->makeFieldAbsent(sfEmailHash); - } - else - { - Log(lsINFO) << "doAccountSet: set email hash"; - - mTxnAccount->setFieldH128(sfEmailHash, uHash); - } - } - - // - // WalletLocator - // - - if (txn.isFieldPresent(sfWalletLocator)) - { - uint256 uHash = txn.getFieldH256(sfWalletLocator); - - if (!uHash) - { - Log(lsINFO) << "doAccountSet: unset wallet locator"; - - mTxnAccount->makeFieldAbsent(sfEmailHash); - } - else - { - Log(lsINFO) << "doAccountSet: set wallet locator"; - - mTxnAccount->setFieldH256(sfWalletLocator, uHash); - } - } - - // - // MessageKey - // - - if (!txn.isFieldPresent(sfMessageKey)) - { - nothing(); - } - else - { - Log(lsINFO) << "doAccountSet: set message key"; - - mTxnAccount->setFieldVL(sfMessageKey, txn.getFieldVL(sfMessageKey)); - } - - // - // Domain - // - - if (txn.isFieldPresent(sfDomain)) - { - std::vector vucDomain = txn.getFieldVL(sfDomain); - - if (vucDomain.empty()) - { - Log(lsINFO) << "doAccountSet: unset domain"; - - mTxnAccount->makeFieldAbsent(sfDomain); - } - else - { - Log(lsINFO) << "doAccountSet: set domain"; - - mTxnAccount->setFieldVL(sfDomain, vucDomain); - } - } - - // - // TransferRate - // - - if (txn.isFieldPresent(sfTransferRate)) - { - uint32 uRate = txn.getFieldU32(sfTransferRate); - - if (!uRate || uRate == QUALITY_ONE) - { - Log(lsINFO) << "doAccountSet: unset transfer rate"; - - mTxnAccount->makeFieldAbsent(sfTransferRate); - } - else if (uRate > QUALITY_ONE) - { - Log(lsINFO) << "doAccountSet: set transfer rate"; - - mTxnAccount->setFieldU32(sfTransferRate, uRate); - } - else - { - Log(lsINFO) << "doAccountSet: bad transfer rate"; - - return temBAD_TRANSFER_RATE; - } - } - - Log(lsINFO) << "doAccountSet<"; - - return tesSUCCESS; -} - -TER TransactionEngine::doClaim(const SerializedTransaction& txn) -{ - Log(lsINFO) << "doClaim>"; - - //TER terResult = setAuthorized(txn, true); - TER terResult=tefEXCEPTION; - - Log(lsINFO) << "doClaim<"; - - return terResult; -} - -TER TransactionEngine::doTrustSet(const SerializedTransaction& txn) -{ - TER terResult = tesSUCCESS; - Log(lsINFO) << "doTrustSet>"; - - const STAmount saLimitAmount = txn.getFieldAmount(sfLimitAmount); - const bool bQualityIn = txn.isFieldPresent(sfQualityIn); - const uint32 uQualityIn = bQualityIn ? txn.getFieldU32(sfQualityIn) : 0; - const bool bQualityOut = txn.isFieldPresent(sfQualityOut); - const uint32 uQualityOut = bQualityIn ? txn.getFieldU32(sfQualityOut) : 0; - const uint160 uCurrencyID = saLimitAmount.getCurrency(); - uint160 uDstAccountID = saLimitAmount.getIssuer(); - const bool bFlipped = mTxnAccountID > uDstAccountID; // true, iff current is not lowest. - bool bDelIndex = false; - - // Check if destination makes sense. - - if (saLimitAmount.isNegative()) - { - Log(lsINFO) << "doTrustSet: Malformed transaction: Negatived credit limit."; - - return temBAD_AMOUNT; - } - else if (!uDstAccountID) - { - Log(lsINFO) << "doTrustSet: Malformed transaction: Destination account not specified."; - - return temDST_NEEDED; - } - else if (mTxnAccountID == uDstAccountID) - { - Log(lsINFO) << "doTrustSet: Malformed transaction: Can not extend credit to self."; - - return temDST_IS_SRC; - } - - SLE::pointer sleDst = entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uDstAccountID)); - if (!sleDst) - { - Log(lsINFO) << "doTrustSet: Delay transaction: Destination account does not exist."; - - return terNO_DST; - } - - STAmount saLimitAllow = saLimitAmount; - saLimitAllow.setIssuer(mTxnAccountID); - - 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->getFieldAmount(sfLowLimit).getIssuer(); - uint160 uHighID = sleRippleState->getFieldAmount(sfHighLimit).getIssuer(); - bool bLow = uLowID == uSrcAccountID; - bool bHigh = uLowID == uDstAccountID; - bool bBalanceZero = !sleRippleState->getFieldAmount(sfBalance); - STAmount saDstLimit = sleRippleState->getFieldAmount(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) - { - sleRippleState->setFieldAmount(bFlipped ? sfHighLimit: sfLowLimit, saLimitAllow); - - if (!bQualityIn) - { - nothing(); - } - else if (uQualityIn) - { - sleRippleState->setFieldU32(bFlipped ? sfLowQualityIn : sfHighQualityIn, uQualityIn); - } - else - { - sleRippleState->makeFieldAbsent(bFlipped ? sfLowQualityIn : sfHighQualityIn); - } - - if (!bQualityOut) - { - nothing(); - } - else if (uQualityOut) - { - sleRippleState->setFieldU32(bFlipped ? sfLowQualityOut : sfHighQualityOut, uQualityOut); - } - else - { - sleRippleState->makeFieldAbsent(bFlipped ? sfLowQualityOut : sfHighQualityOut); - } - - entryModify(sleRippleState); - } - - Log(lsINFO) << "doTrustSet: Modifying ripple line: bDelIndex=" << bDelIndex; - } - // Line does not exist. - else if (!saLimitAmount) - { - Log(lsINFO) << "doTrustSet: Redundant: Setting non-existent 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) << "doTrustSet: Creating ripple line: " << sleRippleState->getIndex().ToString(); - - sleRippleState->setFieldAmount(sfBalance, STAmount(uCurrencyID, ACCOUNT_ONE)); // Zero balance in currency. - sleRippleState->setFieldAmount(bFlipped ? sfHighLimit : sfLowLimit, saLimitAllow); - sleRippleState->setFieldAmount(bFlipped ? sfLowLimit : sfHighLimit, STAmount(uCurrencyID, uDstAccountID)); - - if (uQualityIn) - sleRippleState->setFieldU32(bFlipped ? sfHighQualityIn : sfLowQualityIn, uQualityIn); - if (uQualityOut) - sleRippleState->setFieldU32(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) << "doTrustSet<"; - - return terResult; -} - - -/* -TER TransactionEngine::doPasswordFund(const SerializedTransaction& txn) -{ - std::cerr << "doPasswordFund>" << std::endl; - - const uint160 uDstAccountID = txn.getFieldAccount160(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; -} -*/ - -// TODO: change to take a fee if there is one there -TER TransactionEngine::doRegularKeySet(const SerializedTransaction& txn) -{ - std::cerr << "doRegularKeySet>" << std::endl; - - if (mTxnAccount->getFlags() & lsfPasswordSpent) - { - std::cerr << "doRegularKeySet: Delay transaction: Funds already spent." << std::endl; - - return terFUNDS_SPENT; - } - - mTxnAccount->setFlag(lsfPasswordSpent); - - TER terResult = setAuthorized(txn, false); - - std::cerr << "doRegularKeySet<" << 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.isFieldPresent(sfPaths); - const bool bMax = txn.isFieldPresent(sfSendMax); - const uint160 uDstAccountID = txn.getFieldAccount160(sfDestination); - const STAmount saDstAmount = txn.getFieldAmount(sfAmount); - const STAmount saMaxAmount = bMax - ? txn.getFieldAmount(sfSendMax) - : saDstAmount.isNative() - ? saDstAmount - : STAmount(saDstAmount.getCurrency(), mTxnAccountID, saDstAmount.getMantissa(), saDstAmount.getExponent(), saDstAmount.isNegative()); - 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 specified."; - - return temDST_NEEDED; - } - else if (bMax && !saMaxAmount.isPositive()) - { - Log(lsINFO) << "doPayment: Invalid transaction: bad max amount: " << saMaxAmount.getFullText(); - - return temBAD_AMOUNT; - } - else if (!saDstAmount.isPositive()) - { - Log(lsINFO) << "doPayment: Invalid transaction: bad dst amount: " << saDstAmount.getFullText(); - - return temBAD_AMOUNT; - } - else if (mTxnAccountID == uDstAccountID && uSrcCurrency == uDstCurrency && !bPaths) - { - Log(lsINFO) << boost::str(boost::format("doPayment: Invalid transaction: Redundant 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 XRP."; - - return temCREATEXRP; - } - 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->setFieldAccount(sfAccount, uDstAccountID); - sleDst->setFieldU32(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.getFieldPathSet(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 XRP payment. - - STAmount saSrcXRPBalance = mTxnAccount->getFieldAmount(sfBalance); - - if (saSrcXRPBalance < saDstAmount) - { - // Transaction might succeed, if applied in a different order. - Log(lsINFO) << "doPayment: Delay transaction: Insufficient funds."; - - terResult = terUNFUNDED; - } - else - { - mTxnAccount->setFieldAmount(sfBalance, saSrcXRPBalance - saDstAmount); - sleDst->setFieldAmount(sfBalance, sleDst->getFieldAmount(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.getFieldVL(sfPublicKey); - const std::vector vucSignature = txn.getFieldVL(sfSignature); - const uint160 uAuthKeyID = txn.getFieldAccount160(sfAuthorizedKey); - const RippleAddress naMasterPubKey = RippleAddress::createAccountPublic(vucPubKey); - const uint160 uDstAccountID = naMasterPubKey.getAccountID(); - - // FIXME: This should be moved to the transaction's signature check logic and cached - 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.getFieldAmount(sfAmount); - STAmount saSrcBalance = mTxnAccount->getFieldAmount(sfBalance); - - if (saSrcBalance < saAmount) - { - std::cerr - << boost::str(boost::format("WalletAdd: Delay transaction: insufficient balance: balance=%s amount=%s") - % saSrcBalance.getText() - % saAmount.getText()) - << std::endl; - - return terUNFUNDED; - } - - // Deduct initial balance from source account. - mTxnAccount->setFieldAmount(sfBalance, saSrcBalance-saAmount); - - // Create the account. - sleDst = entryCreate(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uDstAccountID)); - - sleDst->setFieldAccount(sfAccount, uDstAccountID); - sleDst->setFieldU32(sfSequence, 1); - sleDst->setFieldAmount(sfBalance, saAmount); - sleDst->setFieldAccount(sfAuthorizedKey, uAuthKeyID); - - std::cerr << "WalletAdd<" << std::endl; - - return tesSUCCESS; -} - - -// 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 = STAmount(saTakerPays.getCurrency(), saTakerPays.getIssuer()); - saTakerGot = STAmount(saTakerGets.getCurrency(), saTakerGets.getIssuer()); - - while (temUNCERTAIN == terResult) - { - SLE::pointer sleOfferDir; - uint64 uTipQuality; - - // Figure out next offer to take, if needed. - if (saTakerGets != saTakerGot && saTakerPays != saTakerPaid) - { - // Taker, still, needs to get and pay. - - 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 offers 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->getFieldAccount(sfAccount).getAccountID(); - STAmount saOfferPays = sleOffer->getFieldAmount(sfTakerGets); - STAmount saOfferGets = sleOffer->getFieldAmount(sfTakerPays); - - if (sleOffer->isFieldPresent(sfExpiration) && sleOffer->getFieldU32(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; - STAmount saTakerIssuerFee; - STAmount saOfferIssuerFee; - - 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( - mNodes.rippleTransferRate(uTakerAccountID, uOfferOwnerID, uTakerPaysAccountID), - mNodes.rippleTransferRate(uOfferOwnerID, uTakerAccountID, uTakerGetsAccountID), - saOfferFunds, - saPay, // Driver XXX need to account for fees. - saOfferPays, - saOfferGets, - saTakerPays, - saTakerGets, - saSubTakerPaid, - saSubTakerGot, - saTakerIssuerFee, - saOfferIssuerFee); - - 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->setFieldAmount(sfTakerGets, saOfferPays -= saSubTakerGot); - - // Offer owner will get less. Subtract what owner just paid. - sleOffer->setFieldAmount(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? - assert(!!saSubTakerGot.getIssuer()); - - mNodes.accountSend(uOfferOwnerID, uTakerAccountID, saSubTakerGot); - mNodes.accountSend(uOfferOwnerID, uTakerGetsAccountID, saOfferIssuerFee); - - saTakerGot += saSubTakerGot; - - // Taker pays offer owner. - // saSubTakerPaid.setIssuer(uTakerPaysAccountID); - assert(!!saSubTakerPaid.getIssuer()); - - mNodes.accountSend(uTakerAccountID, uOfferOwnerID, saSubTakerPaid); - mNodes.accountSend(uTakerAccountID, uTakerPaysAccountID, saTakerIssuerFee); - - 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.getFieldAmount(sfTakerPays); - STAmount saTakerGets = txn.getFieldAmount(sfTakerGets); - -Log(lsINFO) << boost::str(boost::format("doOfferCreate: saTakerPays=%s saTakerGets=%s") - % saTakerPays.getFullText() - % saTakerGets.getFullText()); - - const uint160 uPaysIssuerID = saTakerPays.getIssuer(); - const uint160 uGetsIssuerID = saTakerGets.getIssuer(); - const uint32 uExpiration = txn.getFieldU32(sfExpiration); - const bool bHaveExpiration = txn.isFieldPresent(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: XRP for XRP"; - - terResult = temBAD_OFFER; - } - else if (!saTakerPays.isPositive() || !saTakerGets.isPositive()) - { - 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: " << RippleAddress::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 for %s -> %s") - % uTakeBookBase.ToString() - % saTakerGets.getFullText() - % saTakerPays.getFullText()); - - // Take using the parameters of the offer. -#if 1 - Log(lsWARNING) << "doOfferCreate: takeOffers: BEFORE saTakerGets=" << saTakerGets.getFullText(); - terResult = takeOffers( - bPassive, - uTakeBookBase, - mTxnAccountID, - mTxnAccount, - saTakerGets, - saTakerPays, - saOfferPaid, // How much was spent. - saOfferGot // How much was got. - ); -#else - terResult = tesSUCCESS; -#endif - 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: AFTER 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: mTxnAccountID=" << RippleAddress::createHumanAccountID(mTxnAccountID); - Log(lsWARNING) << "doOfferCreate: takeOffers: FUNDS=" << mNodes.accountFunds(mTxnAccountID, saTakerGets).getFullText(); - - // Log(lsWARNING) << "doOfferCreate: takeOffers: uPaysIssuerID=" << RippleAddress::createHumanAccountID(uPaysIssuerID); - // Log(lsWARNING) << "doOfferCreate: takeOffers: uGetsIssuerID=" << RippleAddress::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. - Log(lsINFO) << boost::str(boost::format("doOfferCreate: offer not fully consumed: saTakerPays=%s saTakerGets=%s") - % saTakerPays.getFullText() - % saTakerGets.getFullText()); - - // 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() - % RippleAddress::createHumanAccountID(saTakerPays.getIssuer()) - % saTakerGets.getHumanCurrency() - % RippleAddress::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: sfAccount=" << RippleAddress::createHumanAccountID(mTxnAccountID); - Log(lsWARNING) << "doOfferCreate: uPaysIssuerID=" << RippleAddress::createHumanAccountID(uPaysIssuerID); - Log(lsWARNING) << "doOfferCreate: uGetsIssuerID=" << RippleAddress::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->setFieldAccount(sfAccount, mTxnAccountID); - sleOffer->setFieldU32(sfSequence, uSequence); - sleOffer->setFieldH256(sfBookDirectory, uDirectory); - sleOffer->setFieldAmount(sfTakerPays, saTakerPays); - sleOffer->setFieldAmount(sfTakerGets, saTakerGets); - sleOffer->setFieldU64(sfOwnerNode, uOwnerNode); - sleOffer->setFieldU64(sfBookNode, uBookNode); - - if (uExpiration) - sleOffer->setFieldU32(sfExpiration, uExpiration); - - if (bPassive) - sleOffer->setFlag(lsfPassive); - } - } - - Log(lsINFO) << "doOfferCreate: final sleOffer=" << sleOffer->getJson(0); - - return terResult; -} - -TER TransactionEngine::doOfferCancel(const SerializedTransaction& txn) -{ - TER terResult; - const uint32 uOfferSequence = txn.getFieldU32(sfOfferSequence); - const uint32 uAccountSequenceNext = mTxnAccount->getFieldU32(sfSequence); - - Log(lsDEBUG) << "doOfferCancel: uAccountSequenceNext=" << uAccountSequenceNext << " uOfferSequence=" << uOfferSequence; - - if (!uOfferSequence || uAccountSequenceNext-1 <= uOfferSequence) - { - Log(lsINFO) << "doOfferCancel: uAccountSequenceNext=" << uAccountSequenceNext << " uOfferSequence=" << uOfferSequence; - - terResult = temBAD_SEQUENCE; - } - else - { - const uint256 uOfferIndex = Ledger::getOfferIndex(mTxnAccountID, uOfferSequence); - SLE::pointer sleOffer = entryCache(ltOFFER, uOfferIndex); - - if (sleOffer) - { - Log(lsWARNING) << "doOfferCancel: uOfferSequence=" << uOfferSequence; - - terResult = mNodes.offerDelete(sleOffer, uOfferIndex, mTxnAccountID); - } - else - { - Log(lsWARNING) << "doOfferCancel: offer not found: " - << RippleAddress::createHumanAccountID(mTxnAccountID) - << " : " << uOfferSequence - << " : " << uOfferIndex.ToString(); - - terResult = tesSUCCESS; - } - } - - return terResult; -} - -TER TransactionEngine::doContractAdd(const SerializedTransaction& txn) -{ - Log(lsWARNING) << "doContractAdd> " << txn.getJson(0); - - const uint32 expiration = txn.getFieldU32(sfExpiration); -// const uint32 bondAmount = txn.getFieldU32(sfBondAmount); -// const uint32 stampEscrow = txn.getFieldU32(sfStampEscrow); - STAmount rippleEscrow = txn.getFieldAmount(sfRippleEscrow); - std::vector createCode = txn.getFieldVL(sfCreateCode); - std::vector fundCode = txn.getFieldVL(sfFundCode); - std::vector removeCode = txn.getFieldVL(sfRemoveCode); - std::vector expireCode = txn.getFieldVL(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 diff --git a/src/cpp/ripple/TransactionEngine.cpp b/src/cpp/ripple/TransactionEngine.cpp index cb2ffe72ca..faffb090d3 100644 --- a/src/cpp/ripple/TransactionEngine.cpp +++ b/src/cpp/ripple/TransactionEngine.cpp @@ -4,8 +4,10 @@ #include #include +#include #include "TransactionEngine.h" +#include "Transactor.h" #include "../json/writer.h" @@ -88,401 +90,74 @@ TER TransactionEngine::applyTransaction(const SerializedTransaction& txn, Transa } #endif - TER terResult = tesSUCCESS; - uint256 txID = txn.getTransactionID(); - if (!txID) + Transactor::pointer transactor=Transactor::makeTransactor(txn,params,this); + if(transactor) { - cLog(lsWARNING) << "applyTransaction: invalid transaction id"; - - terResult = temINVALID; - } - - // - // Verify transaction is signed properly. - // - - // Extract signing key - // Transactions contain a signing key. This allows us to trivially verify a transaction has at least been properly signed - // without going to disk. Each transaction also notes a source account id. This is used to verify that the signing key is - // associated with the account. - // XXX This could be a lot cleaner to prevent unnecessary copying. - RippleAddress naSigningPubKey; - - if (tesSUCCESS == terResult) - naSigningPubKey = RippleAddress::createAccountPublic(txn.getSigningPubKey()); - - // Consistency: really signed. - if ((tesSUCCESS == terResult) && !isSetBit(params, tapNO_CHECK_SIGN) && !txn.checkSign(naSigningPubKey)) - { - cLog(lsWARNING) << "applyTransaction: Invalid transaction: bad signature"; - - terResult = temINVALID; - } - - STAmount saCost = theConfig.FEE_DEFAULT; - - // Customize behavior based on transaction type. - if (tesSUCCESS == terResult) - { - switch (txn.getTxnType()) + uint256 txID = txn.getTransactionID(); + if (!txID) { - case ttCLAIM: - case ttREGULAR_KEY_SET: - saCost = 0; - break; + cLog(lsWARNING) << "applyTransaction: invalid transaction id"; - case ttPAYMENT: - if (txn.getFlags() & tfCreateAccount) - { - saCost = theConfig.FEE_ACCOUNT_CREATE; - } - break; - - case ttNICKNAME_SET: - { - SLE::pointer sleNickname = entryCache(ltNICKNAME, txn.getFieldH256(sfNickname)); - - if (!sleNickname) - saCost = theConfig.FEE_NICKNAME_CREATE; - } - break; - - case ttACCOUNT_SET: - case ttTRUST_SET: - case ttOFFER_CREATE: - case ttOFFER_CANCEL: - case ttPASSWORD_FUND: - case ttWALLET_ADD: - nothing(); - break; - - case ttINVALID: - cLog(lsWARNING) << "applyTransaction: Invalid transaction: ttINVALID transaction type"; - terResult = temINVALID; - break; - - default: - cLog(lsWARNING) << "applyTransaction: Invalid transaction: unknown transaction type"; - terResult = temUNKNOWN; - break; + return temINVALID; } - } - STAmount saPaid = txn.getTransactionFee(); + TER terResult= transactor->apply(); + std::string strToken; + std::string strHuman; - if (tesSUCCESS == terResult) - { - if (saCost) + transResultInfo(terResult, strToken, strHuman); + + cLog(lsINFO) << "applyTransaction: terResult=" << strToken << " : " << terResult << " : " << strHuman; + + if (isTepPartial(terResult) && isSetBit(params, tapRETRY)) { - // Only check fee is sufficient when the ledger is open. - if (isSetBit(params, tapOPEN_LEDGER) && saPaid < saCost) + // Partial result and allowed to retry, reclassify as a retry. + terResult = terRETRY; + } + + if ((tesSUCCESS == terResult) || isTepPartial(terResult)) + { + // Transaction succeeded fully or (retries are not allowed and the transaction succeeded partially). + Serializer m; + mNodes.calcRawMeta(m, terResult); + + txnWrite(); + + Serializer s; + txn.add(s); + + if (isSetBit(params, tapOPEN_LEDGER)) { - cLog(lsINFO) << "applyTransaction: insufficient fee"; - - terResult = telINSUF_FEE_P; + if (!mLedger->addTransaction(txID, s)) + assert(false); } - } - else - { - if (saPaid) - { - // Transaction is malformed. - cLog(lsWARNING) << "applyTransaction: fee not allowed"; - - terResult = temINSUF_FEE_P; - } - } - } - - // Get source account ID. - mTxnAccountID = txn.getSourceAccount().getAccountID(); - if (tesSUCCESS == terResult && !mTxnAccountID) - { - cLog(lsWARNING) << "applyTransaction: bad source id"; - - terResult = temINVALID; - } - - if (tesSUCCESS != terResult) - return terResult; - - boost::recursive_mutex::scoped_lock sl(mLedger->mLock); - - mTxnAccount = entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(mTxnAccountID)); - - // Find source account - // If are only forwarding, due to resource limitations, we might verifying only some transactions, this would be probablistic. - - STAmount saSrcBalance; - uint32 t_seq = txn.getSequence(); - bool bHaveAuthKey = false; - - if (!mTxnAccount) - { - cLog(lsTRACE) << boost::str(boost::format("applyTransaction: Delay transaction: source account does not exist: %s") % - txn.getSourceAccount().humanAccountID()); - - terResult = terNO_ACCOUNT; - } - else - { - saSrcBalance = mTxnAccount->getFieldAmount(sfBalance); - bHaveAuthKey = mTxnAccount->isFieldPresent(sfAuthorizedKey); - } - - // Check if account claimed. - if (tesSUCCESS == terResult) - { - switch (txn.getTxnType()) - { - case ttCLAIM: - if (bHaveAuthKey) - { - cLog(lsWARNING) << "applyTransaction: Account already claimed."; - - terResult = tefCLAIMED; - } - break; - - default: - nothing(); - break; - } - } - - // Consistency: Check signature - if (tesSUCCESS == terResult) - { - switch (txn.getTxnType()) - { - case ttCLAIM: - // Transaction's signing public key must be for the source account. - // To prove the master private key made this transaction. - if (naSigningPubKey.getAccountID() != mTxnAccountID) - { - // Signing Pub Key must be for Source Account ID. - cLog(lsWARNING) << "sourceAccountID: " << naSigningPubKey.humanAccountID(); - cLog(lsWARNING) << "txn accountID: " << txn.getSourceAccount().humanAccountID(); - - terResult = tefBAD_CLAIM_ID; - } - break; - - case ttREGULAR_KEY_SET: - // Transaction's signing public key must be for the source account. - // To prove the master private key made this transaction. - if (naSigningPubKey.getAccountID() != mTxnAccountID) - { - // Signing Pub Key must be for Source Account ID. - cLog(lsWARNING) << "sourceAccountID: " << naSigningPubKey.humanAccountID(); - cLog(lsWARNING) << "txn accountID: " << txn.getSourceAccount().humanAccountID(); - - terResult = temBAD_SET_ID; - } - break; - - default: - // Verify the transaction's signing public key is the key authorized for signing. - if (bHaveAuthKey && naSigningPubKey.getAccountID() == mTxnAccount->getFieldAccount(sfAuthorizedKey).getAccountID()) - { - // Authorized to continue. - nothing(); - } - else if (naSigningPubKey.getAccountID() == mTxnAccountID) - { - // Authorized to continue. - nothing(); - } - else if (bHaveAuthKey) - { - cLog(lsINFO) << "applyTransaction: Delay: Not authorized to use account."; - - terResult = tefBAD_AUTH; - } - else - { - cLog(lsINFO) << "applyTransaction: Invalid: Not authorized to use account."; - - terResult = temBAD_AUTH_MASTER; - } - break; - } - } - - // Deduct the fee, so it's not available during the transaction. - // Will only write the account back, if the transaction succeeds. - if (tesSUCCESS != terResult || !saCost) - { - nothing(); - } - else if (saSrcBalance < saPaid) - { - cLog(lsINFO) - << boost::str(boost::format("applyTransaction: Delay: insufficient balance: balance=%s paid=%s") - % saSrcBalance.getText() - % saPaid.getText()); - - terResult = terINSUF_FEE_B; - } - else - { - mTxnAccount->setFieldAmount(sfBalance, saSrcBalance - saPaid); - } - - // Validate sequence - if (tesSUCCESS != terResult) - { - nothing(); - } - else if (saCost) - { - uint32 a_seq = mTxnAccount->getFieldU32(sfSequence); - - cLog(lsTRACE) << "Aseq=" << a_seq << ", Tseq=" << t_seq; - - if (t_seq != a_seq) - { - if (a_seq < t_seq) - { - cLog(lsINFO) << "applyTransaction: future sequence number"; - - terResult = terPRE_SEQ; - } - else if (mLedger->hasTransaction(txID)) - terResult = tefALREADY; else { - cLog(lsWARNING) << "applyTransaction: past sequence number"; + if (!mLedger->addTransaction(txID, s, m)) + assert(false); - terResult = tefPAST_SEQ; + STAmount saPaid = txn.getTransactionFee(); + // Charge whatever fee they specified. + mLedger->destroyCoins(saPaid.getNValue()); } } - else + + mTxnAccount.reset(); + mNodes.clear(); + + if (!isSetBit(params, tapOPEN_LEDGER) + && (isTemMalformed(terResult) || isTefFailure(terResult))) { - mTxnAccount->setFieldU32(sfSequence, t_seq + 1); + // XXX Malformed or failed transaction in closed ledger must bow out. } - } - else + + return terResult; + }else { - cLog(lsINFO) << "applyTransaction: Zero cost transaction"; - - if (t_seq) - { - cLog(lsINFO) << "applyTransaction: bad sequence for pre-paid transaction"; - - terResult = tefPAST_SEQ; - } + cLog(lsWARNING) << "applyTransaction: Invalid transaction: unknown transaction type"; + return temUNKNOWN; } - - if (tesSUCCESS == terResult) - { - entryModify(mTxnAccount); - - switch (txn.getTxnType()) - { - case ttACCOUNT_SET: - terResult = doAccountSet(txn); - break; - - case ttCLAIM: - terResult = doClaim(txn); - break; - - case ttTRUST_SET: - terResult = doTrustSet(txn); - break; - - case ttINVALID: - cLog(lsINFO) << "applyTransaction: invalid type"; - terResult = temINVALID; - break; - - //case ttINVOICE: - // terResult = doInvoice(txn); - // break; - - case ttOFFER_CREATE: - terResult = doOfferCreate(txn); - break; - - case ttOFFER_CANCEL: - terResult = doOfferCancel(txn); - break; - - case ttREGULAR_KEY_SET: - terResult = doRegularKeySet(txn); - break; - - case ttPAYMENT: - terResult = doPayment(txn, params); - break; - - case ttWALLET_ADD: - terResult = doWalletAdd(txn); - break; - - case ttCONTRACT: - terResult = doContractAdd(txn); - break; - case ttCONTRACT_REMOVE: - terResult = doContractRemove(txn); - break; - - default: - terResult = temUNKNOWN; - break; - } - } - - std::string strToken; - std::string strHuman; - - transResultInfo(terResult, strToken, strHuman); - - cLog(lsINFO) << "applyTransaction: terResult=" << strToken << " : " << terResult << " : " << strHuman; - - if (isTepPartial(terResult) && isSetBit(params, tapRETRY)) - { - // Partial result and allowed to retry, reclassify as a retry. - terResult = terRETRY; - } - - if ((tesSUCCESS == terResult) || isTepPartial(terResult)) - { - // Transaction succeeded fully or (retries are not allowed and the transaction succeeded partially). - Serializer m; - mNodes.calcRawMeta(m, terResult); - - txnWrite(); - - Serializer s; - txn.add(s); - - if (isSetBit(params, tapOPEN_LEDGER)) - { - if (!mLedger->addTransaction(txID, s)) - assert(false); - } - else - { - if (!mLedger->addTransaction(txID, s, m)) - assert(false); - - // Charge whatever fee they specified. - mLedger->destroyCoins(saPaid.getNValue()); - } - } - - mTxnAccount.reset(); - mNodes.clear(); - - if (!isSetBit(params, tapOPEN_LEDGER) - && (isTemMalformed(terResult) || isTefFailure(terResult))) - { - // XXX Malformed or failed transaction in closed ledger must bow out. - } - - return terResult; } // vim:ts=4 + diff --git a/src/cpp/ripple/TransactionEngine.h b/src/cpp/ripple/TransactionEngine.h index 13e5729f25..67d9706794 100644 --- a/src/cpp/ripple/TransactionEngine.h +++ b/src/cpp/ripple/TransactionEngine.h @@ -11,6 +11,10 @@ #include "TransactionErr.h" #include "InstanceCounter.h" +#include +#include +#include + DEFINE_INSTANCE(TransactionEngine); // A TransactionEngine applies serialized transactions to a ledger @@ -38,6 +42,7 @@ private: LedgerEntrySet mNodes; TER setAuthorized(const SerializedTransaction& txn, bool bMustSetGenerator); + TER checkSig(const SerializedTransaction& txn); TER takeOffers( bool bPassive, @@ -55,31 +60,26 @@ protected: uint160 mTxnAccountID; SLE::pointer mTxnAccount; + + + void txnWrite(); + + +public: + typedef boost::shared_ptr pointer; + + TransactionEngine() { ; } + TransactionEngine(Ledger::ref ledger) : mLedger(ledger) { assert(mLedger); } + + LedgerEntrySet& getNodes() { return mNodes; } + Ledger::pointer getLedger() { return mLedger; } + void setLedger(Ledger::ref ledger) { assert(ledger); mLedger = ledger; } + SLE::pointer entryCreate(LedgerEntryType type, const uint256& index) { return mNodes.entryCreate(type, index); } SLE::pointer entryCache(LedgerEntryType type, const uint256& index) { return mNodes.entryCache(type, index); } void entryDelete(SLE::ref sleEntry) { mNodes.entryDelete(sleEntry); } void entryModify(SLE::ref sleEntry) { mNodes.entryModify(sleEntry); } - void txnWrite(); - - TER doAccountSet(const SerializedTransaction& txn); - TER doClaim(const SerializedTransaction& txn); - TER doTrustSet(const SerializedTransaction& txn); - TER doOfferCreate(const SerializedTransaction& txn); - TER doOfferCancel(const SerializedTransaction& txn); - TER doRegularKeySet(const SerializedTransaction& txn); - TER doPayment(const SerializedTransaction& txn, const TransactionEngineParams params); - TER doWalletAdd(const SerializedTransaction& txn); - TER doContractAdd(const SerializedTransaction& txn); - TER doContractRemove(const SerializedTransaction& txn); - -public: - TransactionEngine() { ; } - TransactionEngine(Ledger::ref ledger) : mLedger(ledger) { assert(mLedger); } - - Ledger::pointer getLedger() { return mLedger; } - void setLedger(Ledger::ref ledger) { assert(ledger); mLedger = ledger; } - TER applyTransaction(const SerializedTransaction&, TransactionEngineParams); }; diff --git a/src/cpp/ripple/TransactionFormats.cpp b/src/cpp/ripple/TransactionFormats.cpp index 7c5a2157a7..f54cb1868f 100644 --- a/src/cpp/ripple/TransactionFormats.cpp +++ b/src/cpp/ripple/TransactionFormats.cpp @@ -45,14 +45,14 @@ static bool TFInit() ; DECLARE_TF(SetRegularKey, ttREGULAR_KEY_SET) - << SOElement(sfAuthorizedKey, SOE_REQUIRED) + << SOElement(sfRegularKey, SOE_REQUIRED) ; DECLARE_TF(Payment, ttPAYMENT) << SOElement(sfDestination, SOE_REQUIRED) << SOElement(sfAmount, SOE_REQUIRED) << SOElement(sfSendMax, SOE_OPTIONAL) - << SOElement(sfPaths, SOE_OPTIONAL) + << SOElement(sfPaths, SOE_DEFAULT) << SOElement(sfInvoiceID, SOE_OPTIONAL) ; diff --git a/src/cpp/ripple/Transactor.cpp b/src/cpp/ripple/Transactor.cpp new file mode 100644 index 0000000000..1157bf8c28 --- /dev/null +++ b/src/cpp/ripple/Transactor.cpp @@ -0,0 +1,220 @@ +#include "Transactor.h" +#include "Log.h" +#include "Config.h" +#include "PaymentTransactor.h" +#include "RegularKeySetTransactor.h" +#include "AccountSetTransactor.h" +#include "WalletAddTransactor.h" +#include "OfferCancelTransactor.h" +#include "OfferCreateTransactor.h" +#include "TrustSetTransactor.h" + +SETUP_LOG(); + +Transactor::pointer Transactor::makeTransactor(const SerializedTransaction& txn,TransactionEngineParams params, TransactionEngine* engine) +{ + switch(txn.getTxnType()) + { + case ttPAYMENT: + return( Transactor::pointer(new PaymentTransactor(txn,params,engine)) ); + case ttACCOUNT_SET: + return( Transactor::pointer(new AccountSetTransactor(txn,params,engine)) ); + case ttREGULAR_KEY_SET: + return( Transactor::pointer(new RegularKeySetTransactor(txn,params,engine)) ); + case ttTRUST_SET: + return( Transactor::pointer(new TrustSetTransactor(txn,params,engine)) ); + case ttOFFER_CREATE: + return( Transactor::pointer(new OfferCreateTransactor(txn,params,engine)) ); + case ttOFFER_CANCEL: + return( Transactor::pointer(new OfferCancelTransactor(txn,params,engine)) ); + case ttWALLET_ADD: + return( Transactor::pointer(new WalletAddTransactor(txn,params,engine)) ); + default: + return(Transactor::pointer()); + } +} + + +Transactor::Transactor(const SerializedTransaction& txn,TransactionEngineParams params, TransactionEngine* engine) : mTxn(txn), mParams(params), mEngine(engine) +{ + mHasAuthKey=false; +} + + + + +void Transactor::calculateFee() +{ + mFeeDue = theConfig.FEE_DEFAULT; +} + +TER Transactor::payFee() +{ + STAmount saPaid = mTxn.getTransactionFee(); + + // Only check fee is sufficient when the ledger is open. + if (isSetBit(mParams, tapOPEN_LEDGER) && saPaid < mFeeDue) + { + cLog(lsINFO) << "applyTransaction: insufficient fee"; + + return telINSUF_FEE_P; + } + + if( !saPaid ) return tesSUCCESS; + + // Deduct the fee, so it's not available during the transaction. + // Will only write the account back, if the transaction succeeds. + if (mSourceBalance < saPaid) + { + cLog(lsINFO) + << boost::str(boost::format("applyTransaction: Delay: insufficient balance: balance=%s paid=%s") + % mSourceBalance.getText() + % saPaid.getText()); + + return terINSUF_FEE_B; + } + + mSourceBalance -= saPaid; + mTxnAccount->setFieldAmount(sfBalance, mSourceBalance); + + return tesSUCCESS; + +} + + +TER Transactor::checkSig() +{ + // Consistency: Check signature + // Verify the transaction's signing public key is the key authorized for signing. + if (mHasAuthKey && mSigningPubKey.getAccountID() == mTxnAccount->getFieldAccount(sfRegularKey).getAccountID()) + { + // Authorized to continue. + nothing(); + } + else if (mSigningPubKey.getAccountID() == mTxnAccountID) + { + // Authorized to continue. + nothing(); + } + else if (mHasAuthKey) + { + cLog(lsINFO) << "applyTransaction: Delay: Not authorized to use account."; + + return tefBAD_AUTH; + } + else + { + cLog(lsINFO) << "applyTransaction: Invalid: Not authorized to use account."; + + return temBAD_AUTH_MASTER; + } + + return tesSUCCESS; +} + +TER Transactor::checkSeq() +{ + uint32 t_seq = mTxn.getSequence(); + uint32 a_seq = mTxnAccount->getFieldU32(sfSequence); + + cLog(lsTRACE) << "Aseq=" << a_seq << ", Tseq=" << t_seq; + + if (t_seq != a_seq) + { + if (a_seq < t_seq) + { + cLog(lsINFO) << "applyTransaction: future sequence number"; + + return terPRE_SEQ; + } + else + { + uint256 txID = mTxn.getTransactionID(); + if (mEngine->getLedger()->hasTransaction(txID)) + return tefALREADY; + } + + cLog(lsWARNING) << "applyTransaction: past sequence number"; + + return tefPAST_SEQ; + + }else + { + mTxnAccount->setFieldU32(sfSequence, t_seq + 1); + } + + return tesSUCCESS; +} + +// check stuff before you bother to lock the ledger +TER Transactor::preCheck() +{ + + mTxnAccountID = mTxn.getSourceAccount().getAccountID(); + if (!mTxnAccountID) + { + cLog(lsWARNING) << "applyTransaction: bad source id"; + + return temINVALID; + } + + // Extract signing key + // Transactions contain a signing key. This allows us to trivially verify a transaction has at least been properly signed + // without going to disk. Each transaction also notes a source account id. This is used to verify that the signing key is + // associated with the account. + // XXX This could be a lot cleaner to prevent unnecessary copying. + mSigningPubKey = RippleAddress::createAccountPublic(mTxn.getSigningPubKey()); + + // Consistency: really signed. + if ( !isSetBit(mParams, tapNO_CHECK_SIGN) && !mTxn.checkSign(mSigningPubKey)) + { + cLog(lsWARNING) << "applyTransaction: Invalid transaction: bad signature"; + + return temINVALID; + } + + return tesSUCCESS; +} + +TER Transactor::apply() +{ + TER terResult = tesSUCCESS; + terResult=preCheck(); + if(terResult != tesSUCCESS) return(terResult); + + calculateFee(); + + boost::recursive_mutex::scoped_lock sl(mEngine->getLedger()->mLock); + + mTxnAccount = mEngine->entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(mTxnAccountID)); + + // Find source account + // If are only forwarding, due to resource limitations, we might verifying only some transactions, this would be probabilistic. + + if (!mTxnAccount) + { + cLog(lsTRACE) << boost::str(boost::format("applyTransaction: Delay transaction: source account does not exist: %s") % + mTxn.getSourceAccount().humanAccountID()); + + return terNO_ACCOUNT; + } + else + { + mSourceBalance = mTxnAccount->getFieldAmount(sfBalance); + mHasAuthKey = mTxnAccount->isFieldPresent(sfRegularKey); + } + + terResult=payFee(); + if(terResult != tesSUCCESS) return(terResult); + + terResult=checkSig(); + if(terResult != tesSUCCESS) return(terResult); + + terResult=checkSeq(); + if(terResult != tesSUCCESS) return(terResult); + + mEngine->entryModify(mTxnAccount); + + return doApply(); + +} \ No newline at end of file diff --git a/src/cpp/ripple/Transactor.h b/src/cpp/ripple/Transactor.h new file mode 100644 index 0000000000..88a1a6764b --- /dev/null +++ b/src/cpp/ripple/Transactor.h @@ -0,0 +1,41 @@ +#ifndef __TRANSACTOR__ +#define __TRANSACTOR__ + +#include "SerializedTransaction.h" +#include "TransactionErr.h" +#include "TransactionEngine.h" +#include + +class Transactor +{ +protected: + const SerializedTransaction& mTxn; + TransactionEngine* mEngine; + TransactionEngineParams mParams; + + uint160 mTxnAccountID; + STAmount mFeeDue; + STAmount mSourceBalance; + SLE::pointer mTxnAccount; + bool mHasAuthKey; + RippleAddress mSigningPubKey; + + + TER preCheck(); + TER checkSeq(); + TER payFee(); + virtual void calculateFee(); + virtual TER checkSig(); + virtual TER doApply()=0; + + Transactor(const SerializedTransaction& txn, TransactionEngineParams params, TransactionEngine* engine); + +public: + typedef boost::shared_ptr pointer; + + static Transactor::pointer makeTransactor(const SerializedTransaction& txn,TransactionEngineParams params, TransactionEngine* engine); + + TER apply(); +}; + +#endif \ No newline at end of file diff --git a/src/cpp/ripple/TrustSetTransactor.cpp b/src/cpp/ripple/TrustSetTransactor.cpp new file mode 100644 index 0000000000..1d287fda97 --- /dev/null +++ b/src/cpp/ripple/TrustSetTransactor.cpp @@ -0,0 +1,149 @@ +#include "TrustSetTransactor.h" + +TER TrustSetTransactor::doApply() +{ + TER terResult = tesSUCCESS; + Log(lsINFO) << "doTrustSet>"; + + const STAmount saLimitAmount = mTxn.getFieldAmount(sfLimitAmount); + const bool bQualityIn = mTxn.isFieldPresent(sfQualityIn); + const uint32 uQualityIn = bQualityIn ? mTxn.getFieldU32(sfQualityIn) : 0; + const bool bQualityOut = mTxn.isFieldPresent(sfQualityOut); + const uint32 uQualityOut = bQualityIn ? mTxn.getFieldU32(sfQualityOut) : 0; + const uint160 uCurrencyID = saLimitAmount.getCurrency(); + uint160 uDstAccountID = saLimitAmount.getIssuer(); + const bool bFlipped = mTxnAccountID > uDstAccountID; // true, iff current is not lowest. + bool bDelIndex = false; + + // Check if destination makes sense. + + if (saLimitAmount.isNegative()) + { + Log(lsINFO) << "doTrustSet: Malformed transaction: Negatived credit limit."; + + return temBAD_AMOUNT; + } + else if (!uDstAccountID || uDstAccountID == ACCOUNT_ONE) + { + Log(lsINFO) << "doTrustSet: Malformed transaction: Destination account not specified."; + + return temDST_NEEDED; + } + else if (mTxnAccountID == uDstAccountID) + { + Log(lsINFO) << "doTrustSet: Malformed transaction: Can not extend credit to self."; + + return temDST_IS_SRC; + } + + SLE::pointer sleDst = mEngine->entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uDstAccountID)); + if (!sleDst) + { + Log(lsINFO) << "doTrustSet: Delay transaction: Destination account does not exist."; + + return terNO_DST; + } + + STAmount saLimitAllow = saLimitAmount; + saLimitAllow.setIssuer(mTxnAccountID); + + SLE::pointer sleRippleState = mEngine->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->getFieldAmount(sfLowLimit).getIssuer(); + uint160 uHighID = sleRippleState->getFieldAmount(sfHighLimit).getIssuer(); + bool bLow = uLowID == uSrcAccountID; + bool bHigh = uLowID == uDstAccountID; + bool bBalanceZero = !sleRippleState->getFieldAmount(sfBalance); + STAmount saDstLimit = sleRippleState->getFieldAmount(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) + { + sleRippleState->setFieldAmount(bFlipped ? sfHighLimit: sfLowLimit, saLimitAllow); + + if (!bQualityIn) + { + nothing(); + } + else if (uQualityIn) + { + sleRippleState->setFieldU32(bFlipped ? sfLowQualityIn : sfHighQualityIn, uQualityIn); + } + else + { + sleRippleState->makeFieldAbsent(bFlipped ? sfLowQualityIn : sfHighQualityIn); + } + + if (!bQualityOut) + { + nothing(); + } + else if (uQualityOut) + { + sleRippleState->setFieldU32(bFlipped ? sfLowQualityOut : sfHighQualityOut, uQualityOut); + } + else + { + sleRippleState->makeFieldAbsent(bFlipped ? sfLowQualityOut : sfHighQualityOut); + } + + mEngine->entryModify(sleRippleState); + } + + Log(lsINFO) << "doTrustSet: Modifying ripple line: bDelIndex=" << bDelIndex; + } + // Line does not exist. + else if (!saLimitAmount) + { + Log(lsINFO) << "doTrustSet: Redundant: Setting non-existent ripple line to 0."; + + return terNO_LINE_NO_ZERO; + } + else + { + // Create a new ripple line. + sleRippleState = mEngine->entryCreate(ltRIPPLE_STATE, Ledger::getRippleStateIndex(mTxnAccountID, uDstAccountID, uCurrencyID)); + + Log(lsINFO) << "doTrustSet: Creating ripple line: " << sleRippleState->getIndex().ToString(); + + sleRippleState->setFieldAmount(sfBalance, STAmount(uCurrencyID, ACCOUNT_ONE)); // Zero balance in currency. + sleRippleState->setFieldAmount(bFlipped ? sfHighLimit : sfLowLimit, saLimitAllow); + sleRippleState->setFieldAmount(bFlipped ? sfLowLimit : sfHighLimit, STAmount(uCurrencyID, uDstAccountID)); + + if (uQualityIn) + sleRippleState->setFieldU32(bFlipped ? sfHighQualityIn : sfLowQualityIn, uQualityIn); + if (uQualityOut) + sleRippleState->setFieldU32(bFlipped ? sfHighQualityOut : sfLowQualityOut, uQualityOut); + + uint64 uSrcRef; // Ignored, dirs never delete. + + terResult = mEngine->getNodes().dirAdd(uSrcRef, Ledger::getOwnerDirIndex(mTxnAccountID), sleRippleState->getIndex()); + + if (tesSUCCESS == terResult) + terResult = mEngine->getNodes().dirAdd(uSrcRef, Ledger::getOwnerDirIndex(uDstAccountID), sleRippleState->getIndex()); + } + + Log(lsINFO) << "doTrustSet<"; + + return terResult; +} + +// vim:ts=4 diff --git a/src/cpp/ripple/TrustSetTransactor.h b/src/cpp/ripple/TrustSetTransactor.h new file mode 100644 index 0000000000..69b09aa016 --- /dev/null +++ b/src/cpp/ripple/TrustSetTransactor.h @@ -0,0 +1,9 @@ +#include "Transactor.h" + +class TrustSetTransactor : public Transactor +{ +public: + TrustSetTransactor(const SerializedTransaction& txn,TransactionEngineParams params, TransactionEngine* engine) : Transactor(txn,params,engine) {} + + TER doApply(); +}; \ No newline at end of file diff --git a/src/cpp/ripple/WalletAddTransactor.cpp b/src/cpp/ripple/WalletAddTransactor.cpp new file mode 100644 index 0000000000..b821abd34c --- /dev/null +++ b/src/cpp/ripple/WalletAddTransactor.cpp @@ -0,0 +1,58 @@ +#include "WalletAddTransactor.h" + +TER WalletAddTransactor::doApply() +{ + std::cerr << "WalletAdd>" << std::endl; + + const std::vector vucPubKey = mTxn.getFieldVL(sfPublicKey); + const std::vector vucSignature = mTxn.getFieldVL(sfSignature); + const uint160 uAuthKeyID = mTxn.getFieldAccount160(sfRegularKey); + const RippleAddress naMasterPubKey = RippleAddress::createAccountPublic(vucPubKey); + const uint160 uDstAccountID = naMasterPubKey.getAccountID(); + + // FIXME: This should be moved to the transaction's signature check logic and cached + 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 = mEngine->entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uDstAccountID)); + + if (sleDst) + { + std::cerr << "WalletAdd: account already created" << std::endl; + + return tefCREATED; + } + + STAmount saAmount = mTxn.getFieldAmount(sfAmount); + STAmount saSrcBalance = mTxnAccount->getFieldAmount(sfBalance); + + if (saSrcBalance < saAmount) + { + std::cerr + << boost::str(boost::format("WalletAdd: Delay transaction: insufficient balance: balance=%s amount=%s") + % saSrcBalance.getText() + % saAmount.getText()) + << std::endl; + + return terUNFUNDED; + } + + // Deduct initial balance from source account. + mTxnAccount->setFieldAmount(sfBalance, saSrcBalance-saAmount); + + // Create the account. + sleDst = mEngine->entryCreate(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uDstAccountID)); + + sleDst->setFieldAccount(sfAccount, uDstAccountID); + sleDst->setFieldU32(sfSequence, 1); + sleDst->setFieldAmount(sfBalance, saAmount); + sleDst->setFieldAccount(sfRegularKey, uAuthKeyID); + + std::cerr << "WalletAdd<" << std::endl; + + return tesSUCCESS; +} \ No newline at end of file diff --git a/src/cpp/ripple/WalletAddTransactor.h b/src/cpp/ripple/WalletAddTransactor.h new file mode 100644 index 0000000000..8bed5f0fe4 --- /dev/null +++ b/src/cpp/ripple/WalletAddTransactor.h @@ -0,0 +1,10 @@ +#include "Transactor.h" + + +class WalletAddTransactor : public Transactor +{ +public: + WalletAddTransactor(const SerializedTransaction& txn,TransactionEngineParams params, TransactionEngine* engine) : Transactor(txn,params,engine) {} + + TER doApply(); +}; \ No newline at end of file diff --git a/src/cpp/ripple/utils.h b/src/cpp/ripple/utils.h index 8efdef4516..7667df578a 100644 --- a/src/cpp/ripple/utils.h +++ b/src/cpp/ripple/utils.h @@ -5,7 +5,6 @@ #include #include - #include "types.h" #define QUALITY_ONE 1000000000 // 10e9 @@ -41,6 +40,13 @@ extern uint32_t be32toh(uint32_t value); #define htole64(x) OSSwapHostToLittleInt64(x) #define be64toh(x) OSSwapBigToHostInt64(x) #define le64toh(x) OSSwapLittleToHostInt64(x) +#elif defined(__FreeBSD__) || defined(__NetBSD__) +#include +#elif defined(__OpenBSD__) +#include +#define be16toh(x) betoh16(x) +#define be32toh(x) betoh32(x) +#define be64toh(x) betoh64(x) #endif diff --git a/src/js/amount.js b/src/js/amount.js index 94bdc12c3b..e75f248378 100644 --- a/src/js/amount.js +++ b/src/js/amount.js @@ -217,6 +217,7 @@ UInt160.prototype.to_json = function () { return output; }; +// XXX Internal form should be UInt160. var Currency = function () { // Internal form: 0 = XRP. 3 letter-code. // XXX Internal should be 0 or hex with three letter annotation when valid. @@ -251,6 +252,11 @@ Currency.prototype.copyTo = function(d) { return d; }; +Currency.prototype.equals = function(d) { + return ('string' !== typeof this._value && isNaN(this._value)) + || ('string' !== typeof d._value && isNaN(d._value)) ? false : this._value === d._value; +} + // this._value = NaN on error. Currency.prototype.parse_json = function(j) { if ("" === j || "0" === j || "XRP" === j) { @@ -591,6 +597,7 @@ Amount.prototype.parse_issuer = function (issuer) { }; // Check BigInteger NaN +// Checks currency, does not check issuer. Amount.prototype.equals = function (d) { return 'string' === typeof (d) ? this.equals(Amount.from_json(d)) @@ -599,9 +606,35 @@ Amount.prototype.equals = function (d) { && this._is_native === d._is_native && (this._is_native ? this._value.equals(d._value) - : this._is_negative === d._is_negative - ? this._value.equals(d._value) - : this._value.equals(BigInteger.ZERO) && d._value.equals(BigInteger.ZERO))); + : this._currency.equals(d._currency) + ? this._is_negative === d._is_negative + ? this._value.equals(d._value) + : this._value.equals(BigInteger.ZERO) && d._value.equals(BigInteger.ZERO) + : false)); +}; + +Amount.prototype.not_equals_why = function (d) { + return 'string' === typeof (d) + ? this.not_equals_why(Amount.from_json(d)) + : this === d + ? false + : d.constructor === Amount + ? this._is_native === d._is_native + ? this._is_native + ? this._value.equals(d._value) + ? false + : "XRP value differs." + : this._currency.equals(d._currency) + ? this._is_negative === d._is_negative + ? this._value.equals(d._value) + ? false + : this._value.equals(BigInteger.ZERO) && d._value.equals(BigInteger.ZERO) + ? false + : "Non-XRP value differs." + : "Non-XRP sign differs." + : "Non-XRP currency differs (" + JSON.stringify(this._currency) + "/" + JSON.stringify(d._currency) + ")" + : "Native mismatch" + : "Wrong constructor." }; exports.Amount = Amount; diff --git a/src/js/remote.js b/src/js/remote.js index 9e3ee74979..0f990fe950 100644 --- a/src/js/remote.js +++ b/src/js/remote.js @@ -21,7 +21,7 @@ var EventEmitter = require('events').EventEmitter; var Amount = require('./amount.js').Amount; var UInt160 = require('./amount.js').UInt160; -// Request events emmitted: +// Request events emitted: // 'success' : Request successful. // 'error' : Request failed. // 'remoteError' @@ -166,22 +166,22 @@ Request.prototype.accounts = function (accounts) { // Remote - access to a remote Ripple server via websocket. // // Events: -// 'connectted' +// 'connected' // 'disconnected' // 'state': -// - 'online' : connectted and subscribed -// - 'offline' : not subscribed or not connectted. +// - 'online' : connected and subscribed +// - 'offline' : not subscribed or not connected. // 'ledger_closed': A good indicate of ready to serve. // 'subscribed' : This indicates stand-alone is available. // // --> trusted: truthy, if remote is trusted -var Remote = function (trusted, websocket_ip, websocket_port, trace) { - this.trusted = trusted; - this.websocket_ip = websocket_ip; - this.websocket_port = websocket_port; +var Remote = function (opts, trace) { + this.trusted = opts.trusted; + this.websocket_ip = opts.websocket_ip; + this.websocket_port = opts.websocket_port; this.id = 0; - this.trace = trace; + this.trace = opts.trace || trace; this._ledger_current_index = undefined; this._ledger_hash = undefined; this._ledger_time = undefined; @@ -219,10 +219,10 @@ var Remote = function (trusted, websocket_ip, websocket_port, trace) { Remote.prototype = new EventEmitter; -Remote.from_config = function (name, trace) { - var serverConfig = exports.config.servers[name]; +Remote.from_config = function (obj, trace) { + var serverConfig = 'string' === typeof obj ? exports.config.servers[obj] : obj; - var remote = new Remote(serverConfig.trusted, serverConfig.websocket_ip, serverConfig.websocket_port, trace); + var remote = new Remote(serverConfig, trace); for (var account in exports.config.accounts) { var accountInfo = exports.config.accounts[account]; @@ -268,7 +268,7 @@ Remote.fees = { 'offer' : Amount.from_json("10"), }; -// Set the emited state: 'online' or 'offline' +// Set the emitted state: 'online' or 'offline' Remote.prototype._set_state = function (state) { if (this.trace) console.log("remote: set_state: %s", state); @@ -450,12 +450,12 @@ Remote.prototype._connect_message = function (ws, json) { unexpected = true; } else if ('success' === message.status) { - if (this.trace) console.log("remote: response: %s", json); + if (this.trace) console.log("remote: response: %s", JSON.stringify(message, undefined, 2)); request.emit('success', message.result); } else if (message.error) { - if (this.trace) console.log("remote: error: %s", json); + if (this.trace) console.log("remote: error: %s", JSON.stringify(message, undefined, 2)); request.emit('error', { 'error' : 'remoteError', @@ -700,7 +700,7 @@ Remote.prototype.submit = function (transaction) { if (transaction.secret && !this.trusted) { transaction.emit('error', { - 'result' : 'serverUntrusted', + 'result' : 'tejServerUntrusted', 'result_message' : "Attempt to give a secret to an untrusted server." }); } @@ -1026,6 +1026,7 @@ var SUBMIT_LOST = 8; // Give up tracking. var Transaction = function (remote) { var self = this; + this.callback = undefined; this.remote = remote; this.secret = undefined; this.build_path = true; @@ -1108,14 +1109,25 @@ Transaction.prototype.set_state = function (state) { // XXX Don't allow a submit without knowing ledger_index. // XXX Have a network canSubmit(), post events for following. // XXX Also give broader status for tracking through network disconnects. -Transaction.prototype.submit = function () { +// callback = function (status, info) { +// // status is final status. Only works under a ledger_accepting conditions. +// switch status: +// case 'tesSUCCESS': all is well. +// case 'tejServerUntrusted': sending secret to untrusted server. +// case 'tejInvalidAccount': locally detected error. +// case 'tejLost': locally gave up looking +// default: some other TER +// } +Transaction.prototype.submit = function (callback) { var self = this; var tx_json = this.tx_json; + this.callback = callback; + if ('string' !== typeof tx_json.Account) { - this.emit('error', { - 'error' : 'invalidAccount', + (this.callback || this.emit)('error', { + 'error' : 'tejInvalidAccount', 'error_message' : 'Bad account.' }); return; @@ -1134,11 +1146,12 @@ Transaction.prototype.submit = function () { } } - if (this.listeners('final').length || this.listeners('lost').length || this.listeners('pending').length) { - // There are listeners for 'final', 'lost', or 'pending' arrange to emit them. + if (this.callback || this.listeners('final').length || this.listeners('lost').length || this.listeners('pending').length) { + // There are listeners for callback, 'final', 'lost', or 'pending' arrange to emit them. this.submit_index = this.remote._ledger_current_index; + // When a ledger closes, look for the result. var on_ledger_closed = function (ledger_hash, ledger_index) { var stop = false; @@ -1148,6 +1161,11 @@ Transaction.prototype.submit = function () { .on('success', function (message) { self.set_state(message.metadata.TransactionResult); self.emit('final', message); + + if (self.callback) + self.callback(message.metadata.TransactionResult, message); + + stop = true; }) .on('error', function (message) { if ('remoteError' === message.error @@ -1155,6 +1173,10 @@ Transaction.prototype.submit = function () { if (self.submit_index + SUBMIT_LOST < ledger_index) { self.set_state('client_lost'); // Gave up. self.emit('lost'); + + if (self.callback) + self.callback('tejLost', message); + stop = true; } else if (self.submit_index + SUBMIT_MISSING < ledger_index) { @@ -1170,12 +1192,18 @@ Transaction.prototype.submit = function () { .request(); if (stop) { - self.removeListener('ledger_closed', on_ledger_closed); + self.remote.removeListener('ledger_closed', on_ledger_closed); self.emit('final', message); } }; this.remote.on('ledger_closed', on_ledger_closed); + + if (this.callback) { + this.on('error', function (message) { + self.callback(message.error, message); + }); + } } this.set_state('client_submitted'); @@ -1355,7 +1383,7 @@ Transaction.prototype.password_fund = function (src, dst) { Transaction.prototype.password_set = function (src, authorized_key, generator, public_key, signature) { this.secret = this._account_secret(src); this.tx_json.TransactionType = 'PasswordSet'; - this.tx_json.AuthorizedKey = authorized_key; + this.tx_json.RegularKey = authorized_key; this.tx_json.Generator = generator; this.tx_json.PublicKey = public_key; this.tx_json.Signature = signature; @@ -1412,7 +1440,7 @@ Transaction.prototype.wallet_add = function (src, amount, authorized_key, public this.secret = this._account_secret(src); this.tx_json.TransactionType = 'WalletAdd'; this.tx_json.Amount = Amount.json_rewrite(amount); - this.tx_json.AuthorizedKey = authorized_key; + this.tx_json.RegularKey = authorized_key; this.tx_json.PublicKey = public_key; this.tx_json.Signature = signature; diff --git a/test/config.js b/test/config-example.js similarity index 95% rename from test/config.js rename to test/config-example.js index 425478cef2..0724941867 100644 --- a/test/config.js +++ b/test/config-example.js @@ -5,7 +5,7 @@ var path = require("path"); // Where to find the binary. -exports.rippled = path.join(process.cwd(), "build/rippled"); +exports.rippled = path.resolve("build/rippled"); exports.server_default = "alpha"; diff --git a/test/offer-test.js b/test/offer-test.js index 988136364d..fc42aac2e1 100644 --- a/test/offer-test.js +++ b/test/offer-test.js @@ -534,5 +534,87 @@ buster.testCase("Offer tests", { done(); }); }, + + "ripple cross currency payment" : + // alice --> [XRP --> carol --> USD/mtgox] --> bob + + function (done) { + var self = this; + var seq; + + // self.remote.set_trace(); + + async.waterfall([ + function (callback) { + self.what = "Create accounts."; + + testutils.create_accounts(self.remote, "root", "10000", ["alice", "bob", "carol", "mtgox"], callback); + }, + function (callback) { + self.what = "Set limits."; + + testutils.credit_limits(self.remote, + { + "carol" : "1000/USD/mtgox", + "bob" : "2000/USD/mtgox" + }, + callback); + }, + function (callback) { + self.what = "Distribute funds."; + + testutils.payments(self.remote, + { + "mtgox" : "500/USD/carol" + }, + callback); + }, + function (callback) { + self.what = "Create offer."; + + self.remote.transaction() + .offer_create("carol", "500", "50/USD/mtgox") + .on('proposed', function (m) { + // console.log("PROPOSED: offer_create: %s", JSON.stringify(m)); + callback(m.result !== 'tesSUCCESS'); + + seq = m.tx_json.Sequence; + }) + .submit(); + }, + function (callback) { + self.what = "Alice send USD/mtgox converting from XRP."; + + self.remote.transaction() + .payment("alice", "bob", "25/USD/mtgox") + .send_max("333") + .on('proposed', function (m) { + // console.log("proposed: %s", JSON.stringify(m)); + + callback(m.result !== 'tesSUCCESS'); + }) + .submit(); + }, + function (callback) { + self.what = "Verify balances."; + + testutils.verify_balances(self.remote, + { +// "alice" : [ "500" ], + "bob" : "25/USD/mtgox", + "carol" : "475/USD/mtgox", + }, + callback); + }, + function (callback) { + self.what = "Verify offer consumed."; + + testutils.verify_offer_not_found(self.remote, "bob", seq, callback); + }, + ], function (error) { + buster.refute(error, self.what); + done(); + }); + }, }); // vim:sw=2:sts=2:ts=8 diff --git a/test/send-test.js b/test/send-test.js index 77811d3015..043855714c 100644 --- a/test/send-test.js +++ b/test/send-test.js @@ -15,11 +15,34 @@ var serverDelay = 1500; buster.testRunner.timeout = 5000; +/* +buster.testCase("Simple", { + 'setUp' : testutils.build_setup({no_server: true}), // + 'tearDown' : testutils.build_teardown(), + + "simple." : + function (done) { buster.assert(1); + + this.remote.transaction() + .payment('root', 'alice', "10000") + .on('success', function (r) { + done(); + }).submit(); + + this.remote.transaction() + .payment('root', 'alice', "20000") + .on('success', function (r) { + done(); + }).submit(); + + } + }); */ + buster.testCase("Sending", { 'setUp' : testutils.build_setup(), 'tearDown' : testutils.build_teardown(), - "send XRP to non-existant account without create." : + "send XRP to non-existent account without create." : function (done) { var self = this; var ledgers = 20; @@ -77,12 +100,12 @@ buster.testCase("Sending", { }, // Also test transaction becomes lost after terNO_DST. - "credit_limit to non-existant account = terNO_DST" : + "credit_limit to non-existent account = terNO_DST" : function (done) { this.remote.transaction() .ripple_line_set("root", "100/USD/alice") .on('proposed', function (m) { - // console.log("proposed: %s", JSON.stringify(m)); + //console.log("proposed: %s", JSON.stringify(m)); buster.assert.equals(m.result, 'terNO_DST'); @@ -102,7 +125,7 @@ buster.testCase("Sending", { testutils.create_accounts(self.remote, "root", "10000", ["alice", "bob", "mtgox"], callback); }, function (callback) { - self.what = "Check a non-existant credit limit."; + self.what = "Check a non-existent credit limit."; self.remote.request_ripple_balance("alice", "mtgox", "USD", 'CURRENT') .on('ripple_state', function (m) { diff --git a/test/server.js b/test/server.js index fde2dfae03..2608c1d73e 100644 --- a/test/server.js +++ b/test/server.js @@ -52,7 +52,7 @@ Server.prototype.once = function (e, c) { }; Server.prototype.serverPath = function() { - return "tmp/server/" + this.name; + return path.resolve("tmp/server", this.name); }; Server.prototype.configPath = function() { diff --git a/test/testutils.js b/test/testutils.js index c087b3d0af..8a840bd5f0 100644 --- a/test/testutils.js +++ b/test/testutils.js @@ -22,7 +22,7 @@ var account_dump = function (remote, account, callback) { .ledger_hash(remote.ledger_hash()) .account_root("root") .on('success', function (r) { - console.log("account_root: %s", JSON.stringify(r, undefined, 2)); + //console.log("account_root: %s", JSON.stringify(r, undefined, 2)); callback(); }) @@ -106,7 +106,10 @@ var build_setup = function (opts, host) { * @param host {String} Identifier for the host configuration to be used. */ var build_teardown = function (host) { + return function (done) { + + host = host || config.server_default; var data = this.store[host]; @@ -114,16 +117,22 @@ var build_teardown = function (host) { async.series([ function disconnectWebsocketStep(callback) { + data.remote .on('disconnected', callback) .connect(false); }, function stopServerStep(callback) { - if (opts.no_server) return callback(); + + if (opts.no_server) + { + + return callback(); + } data.server.on('stopped', callback).stop(); } - ], done); + ], done); }; }; @@ -265,16 +274,16 @@ var verify_balance = function (remote, src, amount_json, callback) { else { remote.request_ripple_balance(src, amount.issuer().to_json(), amount.currency().to_json(), 'CURRENT') .once('ripple_state', function (m) { - // console.log("BALANCE: %s", JSON.stringify(m)); - // console.log("account_balance: %s", m.account_balance.to_text_full()); - // console.log("account_limit: %s", m.account_limit.to_text_full()); - // console.log("issuer_balance: %s", m.issuer_balance.to_text_full()); - // console.log("issuer_limit: %s", m.issuer_limit.to_text_full()); + // console.log("BALANCE: %s", JSON.stringify(m)); + // console.log("account_balance: %s", m.account_balance.to_text_full()); + // console.log("account_limit: %s", m.account_limit.to_text_full()); + // console.log("issuer_balance: %s", m.issuer_balance.to_text_full()); + // console.log("issuer_limit: %s", m.issuer_limit.to_text_full()); var account_balance = Amount.from_json(m.account_balance); if (!account_balance.equals(amount)) { - console.log("verify_balance: failed: %s vs %s is %s", src, amount_json, amount.to_text_full()); + console.log("verify_balance: failed: %s vs %s is %s: %s", src, account_balance.to_text_full(), amount.to_text_full(), account_balance.not_equals_why(amount)); } callback(!account_balance.equals(amount)); diff --git a/test/websocket-test.js b/test/websocket-test.js index 8c0d2940ce..6730866bb2 100644 --- a/test/websocket-test.js +++ b/test/websocket-test.js @@ -2,6 +2,7 @@ var buster = require("buster"); var Server = require("./server.js").Server; var Remote = require("../src/js/remote.js").Remote; +var config = require("./config.js"); require("../src/js/remote.js").config = require("./config.js"); @@ -9,10 +10,10 @@ buster.testRunner.timeout = 5000; buster.testCase("WebSocket connection", { 'setUp' : - function (done) { server = Server.from_config("alpha").on('started', done).start(); }, + function (done) { if (config.servers.alpha.no_server) done(); else server = Server.from_config("alpha").on('started', done).start(); }, 'tearDown' : - function (done) { server.on('stopped', done).stop(); }, + function (done) { if (config.servers.alpha.no_server) done(); else server.on('stopped', done).stop(); }, "websocket connect and disconnect" : function (done) {