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) {