diff --git a/.gitignore b/.gitignore index 2530f7eb82..92eb7bb785 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,10 @@ *.o obj/* bin/newcoind - newcoind +# Ignore locally installed node_modules +node_modules + +# Ignore tmp directory. +tmp diff --git a/Debug/newcoin.exe.embed.manifest.res b/Debug/newcoin.exe.embed.manifest.res deleted file mode 100644 index 9c8df0e3c8..0000000000 Binary files a/Debug/newcoin.exe.embed.manifest.res and /dev/null differ diff --git a/Debug/newcoin_manifest.rc b/Debug/newcoin_manifest.rc deleted file mode 100644 index 20da22ef1c..0000000000 Binary files a/Debug/newcoin_manifest.rc and /dev/null differ diff --git a/js/remote.js b/js/remote.js new file mode 100644 index 0000000000..ffab43eaad --- /dev/null +++ b/js/remote.js @@ -0,0 +1,205 @@ +// Remote access to a server. +// - We never send binary data. +// - We use the W3C interface for node and browser compatibility: +// http://www.w3.org/TR/websockets/#the-websocket-interface +// +// YYY Will later provide a network access which use multiple instances of this. +// YYY A better model might be to allow requesting a target state: keep connected or not. +// + +var util = require('util'); + +var WebSocket = require('ws'); + +// --> 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; + this.id = 0; + this.trace = trace; +}; + +var remoteConfig = function(config, server, trace) { + var serverConfig = config.servers[server]; + + return new Remote(serverConfig.trusted, serverConfig.websocket_ip, serverConfig.websocket_port, trace); +}; + +Remote.method('connect_helper', function() { + var self = this; + + if (this.trace) + console.log("remote: connect: %s", this.url); + + this.ws = new WebSocket(this.url); + + var ws = this.ws; + + ws.response = {}; + + ws.onopen = function() { + if (this.trace) + console.log("remote: onopen: %s", ws.readyState); + + ws.onclose = undefined; + ws.onerror = undefined; + + self.done(ws.readyState); + }; + + ws.onerror = function() { + if (this.trace) + console.log("remote: onerror: %s", ws.readyState); + + ws.onclose = undefined; + + if (self.expire) { + if (this.trace) + console.log("remote: was expired"); + + self.done(ws.readyState); + } + else + { + // Delay and retry. + setTimeout(function() { + if (this.trace) + console.log("remote: retry"); + + self.connect_helper(); + }, 50); // Retry rate 50ms. + } + }; + + // Covers failure to open. + ws.onclose = function() { + if (this.trace) + console.log("remote: onclose: %s", ws.readyState); + + ws.onerror = undefined; + + self.done(ws.readyState); + }; + + // Node's ws module doesn't pass arguments to onmessage. + ws.on('message', function(json, flags) { + var message = JSON.parse(json); + // console.log("message: %s", json); + + if (message.type !== 'response') { + console.log("unexpected message: %s", json); + + } else { + var done = ws.response[message.id]; + + if (done) { + done(message); + + } else { + console.log("unexpected message id: %s", json); + } + } + }); +}); + +// Target state is connectted. +// done(readyState): +// --> readyState: OPEN, CLOSED +Remote.method('connect', function(done, timeout) { + var self = this; + + this.url = util.format("ws://%s:%s", this.websocket_ip, this.websocket_port); + this.done = done; + + if (timeout) { + if (this.trace) + console.log("remote: expire: false"); + + this.expire = false; + + setTimeout(function () { + if (this.trace) + console.log("remote: expire: timeout"); + + self.expire = true; + }, timeout); + } + else { + if (this.trace) + console.log("remote: expire: false"); + + this.expire = true; + } + + this.connect_helper(); + +}); + +// Target stated is disconnected. +Remote.method('disconnect', function(done) { + var ws = this.ws; + + ws.onclose = function() { + if (this.trace) + console.log("remote: onclose: %s", ws.readyState); + + done(ws.readyState); + }; + + ws.close(); +}); + +// Send a command. The comman should lack the id. +// <-> command: what to send, consumed. +Remote.method('request', function(command, done) { + this.id += 1; // Advance id. + + var ws = this.ws; + + command.id = this.id; + + ws.response[command.id] = done; + + if (this.trace) + console.log("remote: send: %s", JSON.stringify(command)); + + ws.send(JSON.stringify(command)); +}); + +Remote.method('ledger_closed', function(done) { + assert(this.trusted); // If not trusted, need to check proof. + + this.request({ 'command' : 'ledger_closed' }, done); +}); + +// Get the current proposed ledger entry. May be closed (and revised) at any time (even before returning). +// Only for use by unit tests. +Remote.method('ledger_current', function(done) { + this.request({ 'command' : 'ledger_current' }, done); +}); + +// <-> params: +// --> ledger : optional +// --> ledger_index : optional +Remote.method('ledger_entry', function(params, done) { + assert(this.trusted); // If not trusted, need to check proof, maybe talk packet protocol. + + params.command = 'ledger_entry'; + + this.request(params, done); +}); + +// Submit a json transaction. +// done(value) +// <-> value: { 'status', status, 'result' : result, ... } +// done may be called up to 3 times. +Remote.method('submit', function(json, done) { +// this.request(..., function() { +// }); +}); + +exports.Remote = Remote; +exports.remoteConfig = remoteConfig; + +// vim:ts=4 diff --git a/js/serializer.js b/js/serializer.js new file mode 100644 index 0000000000..00f9e070b5 --- /dev/null +++ b/js/serializer.js @@ -0,0 +1,44 @@ +// + +var serializer = {}; + +serializer.addUInt16 = function(value) { + switch (typeof value) { + case 'string': + addUInt16(value.charCodeAt(0)); + break; + + case 'integer': + for (i = 16/8; i; i -=1) { + raw.push(value & 255); + value >>= 8; + } + break; + + default: + throw 'UNEXPECTED_TYPE'; + } +}; + +serializer.addUInt160 = function(value) { + switch (typeof value) { + case 'array': + raw.concat(value); + break; + + case 'integer': + for (i = 160/8; i; i -=1) { + raw.push(value & 255); + value >>= 8; + } + break; + + default: + throw 'UNEXPECTED_TYPE'; + } +}; + +serializer.getSHA512Half = function() { +}; + +// vim:ts=4 diff --git a/js/utils.js b/js/utils.js new file mode 100644 index 0000000000..6bb4ae7a2d --- /dev/null +++ b/js/utils.js @@ -0,0 +1,137 @@ +// YYY Should probably have two versions: node vs browser + +var fs = require("fs"); +var path = require("path"); + +Function.prototype.method = function(name,func) { + this.prototype[name] = func; + + return this; +}; + +var filterErr = function(code, done) { + return function(e) { + done(e.code !== code ? e : undefined); + }; +}; + +var throwErr = function(done) { + return function(e) { + if (e) + throw e; + + done(); + }; +}; + +// apply function to elements of array. Return first true value to done or undefined. +var mapOr = function(func, array, done) { + if (array.length) { + func(array[array.length-1], function(v) { + if (v) { + done(v); + } + else { + array.length -= 1; + mapOr(func, array, done); + } + }); + } + else { + done(); + } +}; + +// Make a directory and sub-directories. +var mkPath = function(dirPath, mode, done) { + fs.mkdir(dirPath, typeof mode === "string" ? parseInt(mode, 8) : mode, function(e) { + if (!e || e.code === "EEXIST") { + // Created or already exists, done. + done(); + } + else if (e.code === "ENOENT") { + // Missing sub dir. + + mkPath(path.dirname(dirPath), mode, function(e) { + if (e) { + throw e; + } + else { + mkPath(dirPath, mode, done); + } + }); + } + else { + throw e; + } + }); +}; + +// Empty a directory. +var emptyPath = function(dirPath, done) { + fs.readdir(dirPath, function(err, files) { + if (err) { + done(err); + } + else { + mapOr(rmPath, files.map(function(f) { return path.join(dirPath, f); }), done); + } + }); +}; + +// Remove path recursively. +var rmPath = function(dirPath, done) { +// console.log("rmPath: %s", dirPath); + + fs.lstat(dirPath, function(err, stats) { + if (err && err.code == "ENOENT") { + done(); + } + if (err) { + done(err); + } + else if (stats.isDirectory()) { + emptyPath(dirPath, function(e) { + if (e) { + done(e); + } + else { +// console.log("rmdir: %s", dirPath); done(); + fs.rmdir(dirPath, done); + } + }); + } + else { +// console.log("unlink: %s", dirPath); done(); + fs.unlink(dirPath, done); + } + }); +}; + +// Create directory if needed and empty if needed. +var resetPath = function(dirPath, mode, done) { + mkPath(dirPath, mode, function(e) { + if (e) { + done(e); + } + else { + emptyPath(dirPath, done); + } + }); +}; + +var trace = function(comment, func) { + return function() { + console.log("%s: %s", trace, arguments.toString); + func(arguments); + }; +}; + +exports.emptyPath = emptyPath; +exports.mapOr = mapOr; +exports.mkPath = mkPath; +exports.resetPath = resetPath; +exports.rmPath = rmPath; +exports.trace = trace; + +// vim:ts=4 diff --git a/newcoin.vcxproj b/newcoin.vcxproj index aafa214ad2..4a7bdee1dd 100644 --- a/newcoin.vcxproj +++ b/newcoin.vcxproj @@ -50,6 +50,7 @@ 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_47_0;..\protobuf-2.4.1\src\ + ProgramDatabase Console @@ -101,21 +102,21 @@ + + - - @@ -123,19 +124,25 @@ + + + + + + @@ -146,6 +153,7 @@ + @@ -188,11 +196,13 @@ + + @@ -207,19 +217,25 @@ + + + + + + @@ -259,6 +275,13 @@ + + + + + + + diff --git a/newcoin.vcxproj.filters b/newcoin.vcxproj.filters index 9a6fa8ac81..f569d1ac42 100644 --- a/newcoin.vcxproj.filters +++ b/newcoin.vcxproj.filters @@ -93,15 +93,9 @@ Source Files - - Source Files - Source Files - - Source Files - Source Files @@ -273,6 +267,33 @@ Source Files + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + @@ -503,6 +524,30 @@ Header Files + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + @@ -512,7 +557,6 @@ - diff --git a/newcoind.cfg b/newcoind.cfg index 774a32129d..689b276923 100644 --- a/newcoind.cfg +++ b/newcoind.cfg @@ -5,31 +5,8 @@ # or Mac style end of lines. Blank lines and lines beginning with '#' are # ignored. Undefined sections are reserved. No escapes are currently defined. # -# When you launch newcoind, it will attempt to find this file. -# -# --conf=: -# You may specify the location of this file with --conf=. The config -# directory is the directory containing this file. The data directory is a -# the subdirectory named "dbs". -# -# Windows and no --conf: -# The config directory is the same directory as the newcoind program. The -# data directory is a the subdirectory named "dbs". -# -# Other OSes and no --conf: -# This file will be looked for in these places in the following order: -# ./newcoind.cfg -# $XDG_CONFIG_HOME/newcoin/newcoind.cfg -# -# If newcoind.cfg, is found in the current working directory, the directory -# will be used as the config directory. The data directory is a the -# subdirectory named "dbs". -# -# Otherwise, the data directory data is: -# $XDG_DATA_HOME/newcoin/ -# -# Note: $XDG_CONFIG_HOME defaults to $HOME/.config -# $XDG_DATA_HOME defaults to $HOME/.local/share +# When you launch newcoind, it will attempt to find this file. For details, +# refer to the manual page for --conf command line option. # # [debug_logfile] # Specifies were a debug logfile is kept. By default, no debug log is kept @@ -142,7 +119,7 @@ 1 [debug_logfile] -debug.log +log/debug.log [sntp_servers] time.windows.com diff --git a/src/AccountState.cpp b/src/AccountState.cpp index 5e56bd9965..9aee8f8694 100644 --- a/src/AccountState.cpp +++ b/src/AccountState.cpp @@ -9,23 +9,28 @@ #include "Ledger.h" #include "Serializer.h" +#include "Log.h" -AccountState::AccountState(const NewcoinAddress& id) : mAccountID(id), mValid(false) +AccountState::AccountState(const NewcoinAddress& naAccountID) : mAccountID(naAccountID), mValid(false) { - if (!id.isValid()) return; + if (!naAccountID.isValid()) return; + mLedgerEntry = boost::make_shared(ltACCOUNT_ROOT); - mLedgerEntry->setIndex(Ledger::getAccountRootIndex(id)); - mLedgerEntry->setIFieldAccount(sfAccount, id); + mLedgerEntry->setIndex(Ledger::getAccountRootIndex(naAccountID)); + mLedgerEntry->setFieldAccount(sfAccount, naAccountID.getAccountID()); + mValid = true; } -AccountState::AccountState(SerializedLedgerEntry::pointer ledgerEntry) : mLedgerEntry(ledgerEntry), mValid(false) +AccountState::AccountState(SLE::ref ledgerEntry, const NewcoinAddress& naAccountID) : + mAccountID(naAccountID), mLedgerEntry(ledgerEntry), mValid(false) { - if (!mLedgerEntry) return; - if (mLedgerEntry->getType() != ltACCOUNT_ROOT) return; - mAccountID = mLedgerEntry->getIValueFieldAccount(sfAccount); - if (mAccountID.isValid()) - mValid = true; + if (!mLedgerEntry) + return; + if (mLedgerEntry->getType() != ltACCOUNT_ROOT) + return; + + mValid = true; } std::string AccountState::createGravatarUrl(uint128 uEmailHash) @@ -41,20 +46,22 @@ void AccountState::addJson(Json::Value& val) { val = mLedgerEntry->getJson(0); - if (!mValid) val["Invalid"] = true; - - if (mLedgerEntry->getIFieldPresent(sfEmailHash)) - val["UrlGravatar"] = createGravatarUrl(mLedgerEntry->getIFieldH128(sfEmailHash)); + if (mValid) + { + if (mLedgerEntry->isFieldPresent(sfEmailHash)) + val["UrlGravatar"] = createGravatarUrl(mLedgerEntry->getFieldH128(sfEmailHash)); + } + else + { + val["Invalid"] = true; + } } void AccountState::dump() { Json::Value j(Json::objectValue); - addJson(j); - - Json::StyledStreamWriter ssw; - ssw.write(std::cerr, j); + Log(lsINFO) << j; } // vim:ts=4 diff --git a/src/AccountState.h b/src/AccountState.h index d51898e5dd..728d5ae96a 100644 --- a/src/AccountState.h +++ b/src/AccountState.h @@ -28,22 +28,21 @@ private: bool mValid; public: - AccountState(const NewcoinAddress& AccountID); // For new accounts - AccountState(SerializedLedgerEntry::pointer ledgerEntry); // For accounts in a ledger + AccountState(const NewcoinAddress& naAccountID); // For new accounts + AccountState(SLE::ref ledgerEntry,const NewcoinAddress& naAccountI); // For accounts in a ledger bool bHaveAuthorizedKey() { - return mLedgerEntry->getIFieldPresent(sfAuthorizedKey); + return mLedgerEntry->isFieldPresent(sfAuthorizedKey); } NewcoinAddress getAuthorizedKey() { - return mLedgerEntry->getIValueFieldAccount(sfAuthorizedKey); + return mLedgerEntry->getFieldAccount(sfAuthorizedKey); } - const NewcoinAddress& getAccountID() const { return mAccountID; } - STAmount getBalance() const { return mLedgerEntry->getIValueFieldAmount(sfBalance); } - uint32 getSeq() const { return mLedgerEntry->getIFieldU32(sfSequence); } + STAmount getBalance() const { return mLedgerEntry->getFieldAmount(sfBalance); } + uint32 getSeq() const { return mLedgerEntry->getFieldU32(sfSequence); } SerializedLedgerEntry::pointer getSLE() { return mLedgerEntry; } const SerializedLedgerEntry& peekSLE() const { return *mLedgerEntry; } diff --git a/src/Amount.cpp b/src/Amount.cpp index 7d95eeffca..9c5d0df513 100644 --- a/src/Amount.cpp +++ b/src/Amount.cpp @@ -3,6 +3,8 @@ #include #include +#include +#include #include #include "Config.h" @@ -10,6 +12,9 @@ #include "SerializedTypes.h" #include "utils.h" +uint64 STAmount::uRateOne = STAmount::getRate(STAmount(1), STAmount(1)); + +// --> sCurrency: "", "XNS", or three letter ISO code. bool STAmount::currencyFromString(uint160& uDstCurrency, const std::string& sCurrency) { bool bSuccess = true; @@ -51,6 +56,98 @@ std::string STAmount::getHumanCurrency() const return createHumanCurrency(mCurrency); } +STAmount::STAmount(SField::ref n, const Json::Value& v) + : SerializedType(n), mValue(0), mOffset(0), mIsNegative(false) +{ + Json::Value value, currency, issuer; + + if (v.isObject()) + { + value = v["value"]; + currency = v["currency"]; + issuer = v["issuer"]; + } + else if (v.isArray()) + { + value = v.get(Json::UInt(0), 0); + currency = v.get(Json::UInt(1), Json::nullValue); + issuer = v.get(Json::UInt(2), Json::nullValue); + } + else if (v.isString()) + { + std::string val = v.asString(); + std::vector elements; + boost::split(elements, val, boost::is_any_of("\t\n\r ,/")); + + if ((elements.size() < 0) || (elements.size() > 3)) + throw std::runtime_error("invalid amount string"); + + value = elements[0]; + if (elements.size() > 0) + currency = elements[1]; + if (elements.size() > 1) + issuer = elements[2]; + } + else + value = v; + + mIsNative = !currency.isString() || currency.asString().empty() || (currency.asString() == SYSTEM_CURRENCY_CODE); + + if (value.isInt()) + { + if (value.asInt() >= 0) + mValue = value.asInt(); + else + { + mValue = -value.asInt(); + mIsNegative = true; + } + } + else if (value.isUInt()) + mValue = v.asUInt(); + else if (value.isString()) + { + if (mIsNative) + { + int64 val = lexical_cast_st(value.asString()); + if (val >= 0) + mValue = val; + else + { + mValue = -val; + mIsNegative = true; + } + } + else + setValue(value.asString()); + } + else + throw std::runtime_error("invalid amount type"); + + if (mIsNative) + return; + + if (!currencyFromString(mCurrency, currency.asString())) + throw std::runtime_error("invalid currency"); + + if (!issuer.isString()) + throw std::runtime_error("invalid issuer"); + + if (issuer.size() == (160/4)) + mIssuer.SetHex(issuer.asString()); + else + { + NewcoinAddress is; + if(!is.setAccountID(issuer.asString())) + throw std::runtime_error("invalid issuer"); + mIssuer = is.getAccountID(); + } + if (mIssuer.isZero()) + throw std::runtime_error("invalid issuer"); + + canonicalize(); +} + std::string STAmount::createHumanCurrency(const uint160& uCurrency) { std::string sCurrency; @@ -59,6 +156,10 @@ std::string STAmount::createHumanCurrency(const uint160& uCurrency) { return SYSTEM_CURRENCY_CODE; } + else if (uCurrency == CURRENCY_ONE) + { + return "1"; + } else { Serializer s(160/8); @@ -74,15 +175,15 @@ std::string STAmount::createHumanCurrency(const uint160& uCurrency) if (!::isZero(vucZeros.begin(), vucZeros.size())) { - throw std::runtime_error("bad currency: zeros"); + throw std::runtime_error(boost::str(boost::format("bad currency: zeros: %s") % uCurrency)); } else if (!::isZero(vucVersion.begin(), vucVersion.size())) { - throw std::runtime_error("bad currency: version"); + throw std::runtime_error(boost::str(boost::format("bad currency: version: %s") % uCurrency)); } else if (!::isZero(vucReserved.begin(), vucReserved.size())) { - throw std::runtime_error("bad currency: reserved"); + throw std::runtime_error(boost::str(boost::format("bad currency: reserved: %s") % uCurrency)); } else { @@ -93,50 +194,32 @@ std::string STAmount::createHumanCurrency(const uint160& uCurrency) return sCurrency; } -// Not meant to be the ultimate parser. For use by RPC which is supposed to be sane and trusted. -// Native has special handling: -// - Integer values are in base units. -// - Float values are in float units. -// - To avoid a mistake float value for native are specified with a "^" in place of a "." -// <-- bValid: true = valid -bool STAmount::setFullValue(const std::string& sAmount, const std::string& sCurrency, const std::string& sIssuer) -{ - // - // Figure out the currency. - // - if (!currencyFromString(mCurrency, sCurrency)) - return false; - - mIsNative = !mCurrency; - - // - // Figure out the issuer. - // - NewcoinAddress naIssuerID; - - // Issuer must be "" or a valid account string. - if (!naIssuerID.setAccountID(sIssuer)) - return false; - - mIssuer = naIssuerID.getAccountID(); - - // Stamps not must have an issuer. - if (mIsNative && !mIssuer.isZero()) - return false; - +bool STAmount::setValue(const std::string& sAmount) +{ // Note: mIsNative must be set already! uint64 uValue; int iOffset; size_t uDecimal = sAmount.find_first_of(mIsNative ? "^" : "."); bool bInteger = uDecimal == std::string::npos; + mIsNegative = false; if (bInteger) { try { - uValue = sAmount.empty() ? 0 : boost::lexical_cast(sAmount); + int64 a = sAmount.empty() ? 0 : lexical_cast_st(sAmount); + if (a >= 0) + uValue = static_cast(a); + else + { + uValue = static_cast(-a); + mIsNegative = true; + } + } catch (...) { + Log(lsINFO) << "Bad integer amount: " << sAmount; + return false; } iOffset = 0; @@ -147,16 +230,26 @@ bool STAmount::setFullValue(const std::string& sAmount, const std::string& sCurr // ^1 2 0 2 -1 // 123^ 4 3 1 0 // 1^23 4 1 3 -2 - iOffset = -int(sAmount.size()-uDecimal-1); + iOffset = -int(sAmount.size() - uDecimal - 1); // Issolate integer and fraction. - uint64 uInteger = uDecimal ? boost::lexical_cast(sAmount.substr(0, uDecimal)) : 0; - uint64 uFraction = iOffset ? boost::lexical_cast(sAmount.substr(uDecimal+1)) : 0; + uint64 uInteger; + int64 iInteger = uDecimal ? lexical_cast_st(sAmount.substr(0, uDecimal)) : 0; + if (iInteger >= 0) + uInteger = static_cast(iInteger); + else + { + uInteger = static_cast(-iInteger); + mIsNegative = true; + } + + + uint64 uFraction = iOffset ? lexical_cast_st(sAmount.substr(uDecimal+1)) : 0; // Scale the integer portion to the same offset as the fraction. uValue = uInteger; - for (int i=-iOffset; i--;) + for (int i = -iOffset; i--;) uValue *= 10; // Add in the fraction. @@ -186,10 +279,55 @@ bool STAmount::setFullValue(const std::string& sAmount, const std::string& sCurr mOffset = iOffset; canonicalize(); } - return true; } +// Not meant to be the ultimate parser. For use by RPC which is supposed to be sane and trusted. +// Native has special handling: +// - Integer values are in base units. +// - Float values are in float units. +// - To avoid a mistake float value for native are specified with a "^" in place of a "." +// <-- bValid: true = valid +bool STAmount::setFullValue(const std::string& sAmount, const std::string& sCurrency, const std::string& sIssuer) +{ + // + // Figure out the currency. + // + if (!currencyFromString(mCurrency, sCurrency)) + { + Log(lsINFO) << "Currency malformed: " << sCurrency; + + return false; + } + + mIsNative = !mCurrency; + + // + // Figure out the issuer. + // + NewcoinAddress naIssuerID; + + // Issuer must be "" or a valid account string. + if (!naIssuerID.setAccountID(sIssuer)) + { + Log(lsINFO) << "Issuer malformed: " << sIssuer; + + return false; + } + + mIssuer = naIssuerID.getAccountID(); + + // Stamps not must have an issuer. + if (mIsNative && !mIssuer.isZero()) + { + Log(lsINFO) << "Issuer specified for stamps: " << sIssuer; + + return false; + } + + return setValue(sAmount); +} + // amount = value * [10 ^ offset] // representation range is 10^80 - 10^(-80) // on the wire, high 8 bits are (offset+142), low 56 bits are value @@ -274,21 +412,15 @@ void STAmount::add(Serializer& s) const s.add64(mValue | (static_cast(mOffset + 512 + 256 + 97) << (64 - 10))); s.add160(mCurrency); + s.add160(mIssuer); } } -STAmount::STAmount(const char* name, int64 value) : SerializedType(name), mOffset(0), mIsNative(true) +STAmount STAmount::createFromInt64(SField::ref name, int64 value) { - if (value >= 0) - { - mIsNegative = false; - mValue = static_cast(value); - } - else - { - mIsNegative = true; - mValue = static_cast(-value); - } + return value >= 0 + ? STAmount(name, static_cast(value), false) + : STAmount(name, static_cast(-value), true); } void STAmount::setValue(const STAmount &a) @@ -303,13 +435,14 @@ void STAmount::setValue(const STAmount &a) uint64 STAmount::toUInt64() const { // makes them sort easily - if (mValue == 0) return 0x4000000000000000ull; + if (mValue == 0) + return 0x4000000000000000ull; if (mIsNegative) return mValue | (static_cast(mOffset + 97) << (64 - 10)); return mValue | (static_cast(mOffset + 256 + 97) << (64 - 10)); } -STAmount* STAmount::construct(SerializerIterator& sit, const char *name) +STAmount* STAmount::construct(SerializerIterator& sit, SField::ref name) { uint64 value = sit.get64(); @@ -320,25 +453,28 @@ STAmount* STAmount::construct(SerializerIterator& sit, const char *name) return new STAmount(name, value, true); // negative } - uint160 currency = sit.get160(); - if (!currency) + uint160 uCurrencyID = sit.get160(); + if (!uCurrencyID) throw std::runtime_error("invalid native currency"); + uint160 uIssuerID = sit.get160(); + int offset = static_cast(value >> (64 - 10)); // 10 bits for the offset, sign and "not native" flag value &= ~(1023ull << (64-10)); - if (value == 0) + if (value) { - if (offset != 512) + bool isNegative = (offset & 256) == 0; + offset = (offset & 255) - 97; // center the range + if ((value < cMinValue) || (value > cMaxValue) || (offset < cMinOffset) || (offset > cMaxOffset)) throw std::runtime_error("invalid currency value"); - return new STAmount(name, currency); + + return new STAmount(name, uCurrencyID, uIssuerID, value, offset, isNegative); } - bool isNegative = (offset & 256) == 0; - offset = (offset & 255) - 97; // center the range - if ((value < cMinValue) || (value > cMaxValue) || (offset < cMinOffset) || (offset > cMaxOffset)) + if (offset != 512) throw std::runtime_error("invalid currency value"); - return new STAmount(name, currency, value, offset, isNegative); + return new STAmount(name, uCurrencyID, uIssuerID); } int64 STAmount::getSNValue() const @@ -368,14 +504,14 @@ std::string STAmount::getRaw() const if (mValue == 0) return "0"; if (mIsNative) { - if (mIsNegative) return std::string("-") + boost::lexical_cast(mValue); - else return boost::lexical_cast(mValue); + if (mIsNegative) return std::string("-") + lexical_cast_i(mValue); + else return lexical_cast_i(mValue); } if (mIsNegative) return mCurrency.GetHex() + ": -" + - boost::lexical_cast(mValue) + "e" + boost::lexical_cast(mOffset); + lexical_cast_i(mValue) + "e" + lexical_cast_i(mOffset); else return mCurrency.GetHex() + ": " + - boost::lexical_cast(mValue) + "e" + boost::lexical_cast(mOffset); + lexical_cast_i(mValue) + "e" + lexical_cast_i(mOffset); } std::string STAmount::getText() const @@ -384,20 +520,20 @@ std::string STAmount::getText() const if (mIsNative) { if (mIsNegative) - return std::string("-") + boost::lexical_cast(mValue); - else return boost::lexical_cast(mValue); + return std::string("-") + lexical_cast_i(mValue); + else return lexical_cast_i(mValue); } if ((mOffset < -25) || (mOffset > -5)) { if (mIsNegative) - return std::string("-") + boost::lexical_cast(mValue) + - "e" + boost::lexical_cast(mOffset); + return std::string("-") + lexical_cast_i(mValue) + + "e" + lexical_cast_i(mOffset); else - return boost::lexical_cast(mValue) + "e" + boost::lexical_cast(mOffset); + return lexical_cast_i(mValue) + "e" + lexical_cast_i(mOffset); } std::string val = "000000000000000000000000000"; - val += boost::lexical_cast(mValue); + val += lexical_cast_i(mValue); val += "00000000000000000000000"; std::string pre = val.substr(0, mOffset + 43); @@ -446,7 +582,7 @@ bool STAmount::operator==(const STAmount& a) const bool STAmount::operator!=(const STAmount& a) const { - return (mOffset != a.mOffset) || (mValue != a.mValue) || (mIsNegative!= a.mIsNegative) || !isComparable(a); + return (mOffset != a.mOffset) || (mValue != a.mValue) || (mIsNegative != a.mIsNegative) || !isComparable(a); } bool STAmount::operator<(const STAmount& a) const @@ -488,19 +624,7 @@ STAmount& STAmount::operator-=(const STAmount& a) STAmount STAmount::operator-(void) const { if (mValue == 0) return *this; - return STAmount(name, mCurrency, mValue, mOffset, mIsNative, !mIsNegative); -} - -STAmount& STAmount::operator=(const STAmount& a) -{ - mValue = a.mValue; - mOffset = a.mOffset; - mIssuer = a.mIssuer; - mCurrency = a.mCurrency; - mIsNative = a.mIsNative; - mIsNegative = a.mIsNegative; - - return *this; + return STAmount(getFName(), mCurrency, mIssuer, mValue, mOffset, mIsNative, !mIsNegative); } STAmount& STAmount::operator=(uint64 v) @@ -553,12 +677,12 @@ bool STAmount::operator>=(uint64 v) const STAmount STAmount::operator+(uint64 v) const { - return STAmount(name, getSNValue() + static_cast(v)); + return STAmount(getFName(), getSNValue() + static_cast(v)); } STAmount STAmount::operator-(uint64 v) const { - return STAmount(name, getSNValue() - static_cast(v)); + return STAmount(getFName(), getSNValue() - static_cast(v)); } STAmount::operator double() const @@ -576,7 +700,7 @@ STAmount operator+(const STAmount& v1, const STAmount& v2) v1.throwComparable(v2); if (v1.mIsNative) - return STAmount(v1.name, v1.getSNValue() + v2.getSNValue()); + return STAmount(v1.getFName(), v1.getSNValue() + v2.getSNValue()); int ov1 = v1.mOffset, ov2 = v2.mOffset; @@ -598,9 +722,9 @@ STAmount operator+(const STAmount& v1, const STAmount& v2) int64 fv = vv1 + vv2; if (fv >= 0) - return STAmount(v1.name, v1.mCurrency, fv, ov1, false); + return STAmount(v1.getFName(), v1.mCurrency, v1.mIssuer, fv, ov1, false); else - return STAmount(v1.name, v1.mCurrency, -fv, ov1, true); + return STAmount(v1.getFName(), v1.mCurrency, v1.mIssuer, -fv, ov1, true); } STAmount operator-(const STAmount& v1, const STAmount& v2) @@ -609,7 +733,10 @@ STAmount operator-(const STAmount& v1, const STAmount& v2) v1.throwComparable(v2); if (v2.mIsNative) - return STAmount(v1.name, v1.getSNValue() - v2.getSNValue()); + { + // XXX This could be better, check for overflow and that maximum range is covered. + return STAmount::createFromInt64(v1.getFName(), v1.getSNValue() - v2.getSNValue()); + } int ov1 = v1.mOffset, ov2 = v2.mOffset; int64 vv1 = static_cast(v1.mValue), vv2 = static_cast(v2.mValue); @@ -630,15 +757,15 @@ STAmount operator-(const STAmount& v1, const STAmount& v2) int64 fv = vv1 - vv2; if (fv >= 0) - return STAmount(v1.name, v1.mCurrency, fv, ov1, false); + return STAmount(v1.getFName(), v1.mCurrency, v1.mIssuer, fv, ov1, false); else - return STAmount(v1.name, v1.mCurrency, -fv, ov1, true); + return STAmount(v1.getFName(), v1.mCurrency, v1.mIssuer, -fv, ov1, true); } -STAmount STAmount::divide(const STAmount& num, const STAmount& den, const uint160& currencyOut) +STAmount STAmount::divide(const STAmount& num, const STAmount& den, const uint160& uCurrencyID, const uint160& uIssuerID) { if (den.isZero()) throw std::runtime_error("division by zero"); - if (num.isZero()) return STAmount(currencyOut); + if (num.isZero()) return STAmount(uCurrencyID, uIssuerID); uint64 numVal = num.mValue, denVal = den.mValue; int numOffset = num.mOffset, denOffset = den.mOffset; @@ -674,17 +801,17 @@ STAmount STAmount::divide(const STAmount& num, const STAmount& den, const uint16 assert(BN_num_bytes(&v) <= 64); if (num.mIsNegative != den.mIsNegative) - return -STAmount(currencyOut, v.getulong(), finOffset); - else return STAmount(currencyOut, v.getulong(), finOffset); + return -STAmount(uCurrencyID, uIssuerID, v.getulong(), finOffset); + else return STAmount(uCurrencyID, uIssuerID, v.getulong(), finOffset); } -STAmount STAmount::multiply(const STAmount& v1, const STAmount& v2, const uint160& currencyOut) +STAmount STAmount::multiply(const STAmount& v1, const STAmount& v2, const uint160& uCurrencyID, const uint160& uIssuerID) { if (v1.isZero() || v2.isZero()) - return STAmount(currencyOut); + return STAmount(uCurrencyID, uIssuerID); if (v1.mIsNative && v2.mIsNative) // FIXME: overflow - return STAmount(v1.name, v1.getSNValue() * v2.getSNValue()); + return STAmount(v1.getFName(), v1.getSNValue() * v2.getSNValue()); uint64 value1 = v1.mValue, value2 = v2.mValue; int offset1 = v1.mOffset, offset2 = v2.mOffset; @@ -737,8 +864,8 @@ STAmount STAmount::multiply(const STAmount& v1, const STAmount& v2, const uint16 assert(BN_num_bytes(&v) <= 64); if (v1.mIsNegative != v2.mIsNegative) - return -STAmount(currencyOut, v.getulong(), offset1 + offset2 + 14); - else return STAmount(currencyOut, v.getulong(), offset1 + offset2 + 14); + return -STAmount(uCurrencyID, uIssuerID, v.getulong(), offset1 + offset2 + 14); + else return STAmount(uCurrencyID, uIssuerID, v.getulong(), offset1 + offset2 + 14); } // Convert an offer into an index amount so they sort by rate. @@ -753,7 +880,7 @@ uint64 STAmount::getRate(const STAmount& offerOut, const STAmount& offerIn) { if (offerOut.isZero()) throw std::runtime_error("Worthless offer"); - STAmount r = divide(offerIn, offerOut, uint160(1)); + STAmount r = divide(offerIn, offerOut, CURRENCY_ONE, ACCOUNT_ONE); assert((r.getExponent() >= -100) && (r.getExponent() <= 155)); @@ -762,41 +889,65 @@ uint64 STAmount::getRate(const STAmount& offerOut, const STAmount& offerIn) return (ret << (64 - 8)) | r.getMantissa(); } +STAmount STAmount::setRate(uint64 rate) +{ + uint64 mantissa = rate & ~(255ull << (64 - 8)); + int exponent = static_cast(rate >> (64 - 8)) - 100; + + return STAmount(CURRENCY_ONE, ACCOUNT_ONE, mantissa, exponent); +} + // Taker gets all taker can pay for with saTakerFunds, limited by saOfferPays and saOfferFunds. -// --> saOfferFunds: Limit for saOfferPays -// --> saTakerFunds: Limit for saOfferGets : How much taker really wants. : Driver -// --> saOfferPays: Request : this should be reduced as the offer is fullfilled. -// --> saOfferGets: Request : this should be reduced as the offer is fullfilled. -// --> saTakerPays: Total : Used to know the approximate ratio of the exchange. -// --> saTakerGets: Total : Used to know the approximate ratio of the exchange. -// <-- saTakerPaid: Actual -// <-- saTakerGot: Actual -// <-- bRemove: remove offer it is either fullfilled or unfunded +// --> uTakerPaysRate: >= QUALITY_ONE +// --> uOfferPaysRate: >= QUALITY_ONE +// --> saOfferFunds: Limit for saOfferPays +// --> saTakerFunds: Limit for saOfferGets : How much taker really wants. : Driver +// --> saOfferPays: Request : this should be reduced as the offer is fullfilled. +// --> saOfferGets: Request : this should be reduced as the offer is fullfilled. +// --> saTakerPays: Total : Used to know the approximate ratio of the exchange. +// --> saTakerGets: Total : Used to know the approximate ratio of the exchange. +// <-- saTakerPaid: Actual +// <-- saTakerGot: Actual +// <-- saTakerIssuerFee: Actual +// <-- saOfferIssuerFee: Actual +// <-- bRemove: remove offer it is either fullfilled or unfunded bool STAmount::applyOffer( + const uint32 uTakerPaysRate, const uint32 uOfferPaysRate, const STAmount& saOfferFunds, const STAmount& saTakerFunds, const STAmount& saOfferPays, const STAmount& saOfferGets, const STAmount& saTakerPays, const STAmount& saTakerGets, - STAmount& saTakerPaid, STAmount& saTakerGot) + STAmount& saTakerPaid, STAmount& saTakerGot, + STAmount& saTakerIssuerFee, STAmount& saOfferIssuerFee) { saOfferGets.throwComparable(saTakerPays); assert(!saOfferFunds.isZero() && !saTakerFunds.isZero()); // Must have funds. assert(!saOfferGets.isZero() && !saOfferPays.isZero()); // Must not be a null offer. + // Amount offer can pay out, limited by funds and fees. + STAmount saOfferFundsAvailable = QUALITY_ONE == uOfferPaysRate + ? saOfferFunds + : STAmount::divide(saOfferFunds, STAmount(CURRENCY_ONE, uOfferPaysRate, -9)); + // Amount offer can pay out, limited by offer and funds. - STAmount saOfferPaysAvailable = saOfferFunds < saOfferPays ? saOfferFunds : saOfferPays; + STAmount saOfferPaysAvailable = std::min(saOfferFundsAvailable, saOfferPays); // Amount offer can get in proportion, limited by offer funds. STAmount saOfferGetsAvailable = - saOfferFunds == saOfferPays + saOfferFundsAvailable == saOfferPays ? saOfferGets // Offer was fully funded, avoid shenanigans. - : divide(multiply(saTakerPays, saOfferPaysAvailable, uint160(1)), saTakerGets, saOfferGets.getCurrency()); + : divide(multiply(saTakerPays, saOfferPaysAvailable, CURRENCY_ONE, ACCOUNT_ONE), saTakerGets, saOfferGets.getCurrency(), saOfferGets.getIssuer()); - if (saOfferGets == saOfferGetsAvailable && saTakerFunds >= saOfferGets) + // Amount taker can spend, limited by funds and fees. + STAmount saTakerFundsAvailable = QUALITY_ONE == uTakerPaysRate + ? saTakerFunds + : STAmount::divide(saTakerFunds, STAmount(CURRENCY_ONE, uTakerPaysRate, -9)); + + if (saOfferGets == saOfferGetsAvailable && saTakerFundsAvailable >= saOfferGets) { // Taker gets all of offer available. - saTakerPaid = saOfferGets; // Taker paid what offer could get. - saTakerGot = saOfferPays; // Taker got what offer could pay. + saTakerPaid = saOfferGets; // Taker paid what offer could get. + saTakerGot = saOfferPays; // Taker got what offer could pay. Log(lsINFO) << "applyOffer: took all outright"; } @@ -812,19 +963,43 @@ bool STAmount::applyOffer( { // Taker only get's a portion of offer. saTakerPaid = saTakerFunds; // Taker paid all he had. - saTakerGot = divide(multiply(saTakerFunds, saOfferPaysAvailable, uint160(1)), saOfferGetsAvailable, saOfferPays.getCurrency()); + saTakerGot = divide(multiply(saTakerFunds, saOfferPaysAvailable, CURRENCY_ONE, ACCOUNT_ONE), saOfferGetsAvailable, saOfferPays.getCurrency(), saOfferPays.getIssuer()); Log(lsINFO) << "applyOffer: saTakerGot=" << saTakerGot.getFullText(); Log(lsINFO) << "applyOffer: saOfferPaysAvailable=" << saOfferPaysAvailable.getFullText(); } + if (uTakerPaysRate == QUALITY_ONE) + { + saTakerIssuerFee = STAmount(saTakerPaid.getCurrency(), saTakerPaid.getIssuer()); + } + else + { + // Compute fees in a rounding safe way. + STAmount saTotal = STAmount::multiply(saTakerPaid, STAmount(CURRENCY_ONE, uTakerPaysRate, -9)); + + saTakerIssuerFee = (saTotal > saTakerFunds) ? saTakerFunds-saTakerPaid : saTotal - saTakerPaid; + } + + if (uOfferPaysRate == QUALITY_ONE) + { + saOfferIssuerFee = STAmount(saTakerGot.getCurrency(), saTakerGot.getIssuer()); + } + else + { + // Compute fees in a rounding safe way. + STAmount saTotal = STAmount::multiply(saTakerPaid, STAmount(CURRENCY_ONE, uTakerPaysRate, -9)); + + saOfferIssuerFee = (saTotal > saOfferFunds) ? saOfferFunds-saTakerGot : saTotal - saTakerGot; + } + return saTakerGot >= saOfferPays; } STAmount STAmount::getPay(const STAmount& offerOut, const STAmount& offerIn, const STAmount& needed) { // Someone wants to get (needed) out of the offer, how much should they pay in? if (offerOut.isZero()) - return STAmount(offerIn.getCurrency()); + return STAmount(offerIn.getCurrency(), offerIn.getIssuer()); if (needed >= offerOut) { @@ -832,7 +1007,7 @@ STAmount STAmount::getPay(const STAmount& offerOut, const STAmount& offerIn, con return needed; } - STAmount ret = divide(multiply(needed, offerIn, uint160(1)), offerOut, offerIn.getCurrency()); + STAmount ret = divide(multiply(needed, offerIn, CURRENCY_ONE, ACCOUNT_ONE), offerOut, offerIn.getCurrency(), offerIn.getIssuer()); return (ret > offerIn) ? offerIn : ret; } @@ -855,23 +1030,43 @@ uint64 STAmount::convertToDisplayAmount(const STAmount& internalAmount, uint64 t return muldiv(internalAmount.getNValue(), totalInit, totalNow); } -STAmount STAmount::convertToInternalAmount(uint64 displayAmount, uint64 totalNow, uint64 totalInit, - const char *name) +STAmount STAmount::convertToInternalAmount(uint64 displayAmount, uint64 totalNow, uint64 totalInit, SField::ref name) { // Convert a display/request currency amount to an internal amount return STAmount(name, muldiv(displayAmount, totalNow, totalInit)); } STAmount STAmount::deserialize(SerializerIterator& it) { - STAmount *s = construct(it); + std::auto_ptr s(dynamic_cast(construct(it, sfGeneric))); STAmount ret(*s); - - delete s; - return ret; } std::string STAmount::getFullText() const +{ + if (mIsNative) + { + return str(boost::format("%s/" SYSTEM_CURRENCY_CODE) % getText()); + } + else if (!mIssuer) + { + return str(boost::format("%s/%s/0") % getText() % getHumanCurrency()); + } + else if (mIssuer == ACCOUNT_ONE) + { + return str(boost::format("%s/%s/1") % getText() % getHumanCurrency()); + } + else + { + return str(boost::format("%s/%s/%s") + % getText() + % getHumanCurrency() + % NewcoinAddress::createHumanAccountID(mIssuer)); + } +} + +#if 0 +std::string STAmount::getExtendedText() const { if (mIsNative) { @@ -879,7 +1074,7 @@ std::string STAmount::getFullText() const } else { - return str(boost::format("%s %s/%s %dE%d" ) + return str(boost::format("%s/%s/%s %dE%d" ) % getText() % getHumanCurrency() % NewcoinAddress::createHumanAccountID(mIssuer) @@ -887,6 +1082,7 @@ std::string STAmount::getFullText() const % getExponent()); } } +#endif Json::Value STAmount::getJson(int) const { @@ -1014,8 +1210,7 @@ BOOST_AUTO_TEST_CASE( NativeCurrency_test ) BOOST_AUTO_TEST_CASE( CustomCurrency_test ) { - uint160 currency(1); - STAmount zero(currency), one(currency, 1), hundred(currency, 100); + STAmount zero(CURRENCY_ONE, ACCOUNT_ONE), one(CURRENCY_ONE, ACCOUNT_ONE, 1), hundred(CURRENCY_ONE, ACCOUNT_ONE, 100); serdes(one).getRaw(); @@ -1082,29 +1277,35 @@ BOOST_AUTO_TEST_CASE( CustomCurrency_test ) if (!(hundred != zero)) BOOST_FAIL("STAmount fail"); if (!(hundred != one)) BOOST_FAIL("STAmount fail"); if ((hundred != hundred)) BOOST_FAIL("STAmount fail"); - if (STAmount(currency).getText() != "0") BOOST_FAIL("STAmount fail"); - if (STAmount(currency,31).getText() != "31") BOOST_FAIL("STAmount fail"); - if (STAmount(currency,31,1).getText() != "310") BOOST_FAIL("STAmount fail"); - if (STAmount(currency,31,-1).getText() != "3.1") BOOST_FAIL("STAmount fail"); - if (STAmount(currency,31,-2).getText() != "0.31") BOOST_FAIL("STAmount fail"); + if (STAmount(CURRENCY_ONE, ACCOUNT_ONE).getText() != "0") BOOST_FAIL("STAmount fail"); + if (STAmount(CURRENCY_ONE, ACCOUNT_ONE, 31).getText() != "31") BOOST_FAIL("STAmount fail"); + if (STAmount(CURRENCY_ONE, ACCOUNT_ONE, 31,1).getText() != "310") BOOST_FAIL("STAmount fail"); + if (STAmount(CURRENCY_ONE, ACCOUNT_ONE, 31,-1).getText() != "3.1") BOOST_FAIL("STAmount fail"); + if (STAmount(CURRENCY_ONE, ACCOUNT_ONE, 31,-2).getText() != "0.31") BOOST_FAIL("STAmount fail"); - if (STAmount::multiply(STAmount(currency, 20), STAmount(3), currency).getText() != "60") + if (STAmount::multiply(STAmount(CURRENCY_ONE, ACCOUNT_ONE, 20), STAmount(3), CURRENCY_ONE, ACCOUNT_ONE).getText() != "60") BOOST_FAIL("STAmount multiply fail"); - if (STAmount::multiply(STAmount(currency, 20), STAmount(3), uint160()).getText() != "60") + if (STAmount::multiply(STAmount(CURRENCY_ONE, ACCOUNT_ONE, 20), STAmount(3), uint160(), ACCOUNT_XNS).getText() != "60") BOOST_FAIL("STAmount multiply fail"); - if (STAmount::multiply(STAmount(20), STAmount(3), currency).getText() != "60") + if (STAmount::multiply(STAmount(20), STAmount(3), CURRENCY_ONE, ACCOUNT_ONE).getText() != "60") BOOST_FAIL("STAmount multiply fail"); - if (STAmount::multiply(STAmount(20), STAmount(3), uint160()).getText() != "60") + if (STAmount::multiply(STAmount(20), STAmount(3), uint160(), ACCOUNT_XNS).getText() != "60") BOOST_FAIL("STAmount multiply fail"); - if (STAmount::divide(STAmount(currency, 60) , STAmount(3), currency).getText() != "20") + if (STAmount::divide(STAmount(CURRENCY_ONE, ACCOUNT_ONE, 60), STAmount(3), CURRENCY_ONE, ACCOUNT_ONE).getText() != "20") BOOST_FAIL("STAmount divide fail"); - if (STAmount::divide(STAmount(currency, 60) , STAmount(3), uint160()).getText() != "20") + if (STAmount::divide(STAmount(CURRENCY_ONE, ACCOUNT_ONE, 60), STAmount(3), uint160(), ACCOUNT_XNS).getText() != "20") BOOST_FAIL("STAmount divide fail"); - if (STAmount::divide(STAmount(currency, 60) , STAmount(currency, 3), currency).getText() != "20") + if (STAmount::divide(STAmount(CURRENCY_ONE, ACCOUNT_ONE, 60), STAmount(CURRENCY_ONE, ACCOUNT_ONE, 3), CURRENCY_ONE, ACCOUNT_ONE).getText() != "20") BOOST_FAIL("STAmount divide fail"); - if (STAmount::divide(STAmount(currency, 60) , STAmount(currency, 3), uint160()).getText() != "20") + if (STAmount::divide(STAmount(CURRENCY_ONE, ACCOUNT_ONE, 60), STAmount(CURRENCY_ONE, ACCOUNT_ONE, 3), uint160(), ACCOUNT_XNS).getText() != "20") BOOST_FAIL("STAmount divide fail"); + STAmount a1(CURRENCY_ONE, ACCOUNT_ONE, 60), a2 (CURRENCY_ONE, ACCOUNT_ONE, 10, -1); + if (STAmount::divide(a2, a1, CURRENCY_ONE, ACCOUNT_ONE) != STAmount::setRate(STAmount::getRate(a1, a2))) + BOOST_FAIL("STAmount setRate(getRate) fail"); + if (STAmount::divide(a1, a2, CURRENCY_ONE, ACCOUNT_ONE) != STAmount::setRate(STAmount::getRate(a2, a1))) + BOOST_FAIL("STAmount setRate(getRate) fail"); + BOOST_TEST_MESSAGE("Amount CC Complete"); } @@ -1113,23 +1314,22 @@ BOOST_AUTO_TEST_CASE( CurrencyMulDivTests ) // Test currency multiplication and division operations such as // convertToDisplayAmount, convertToInternalAmount, getRate, getClaimed, and getNeeded - uint160 c(1); if (STAmount::getRate(STAmount(1), STAmount(10)) != (((100ul-14)<<(64-8))|1000000000000000ul)) - BOOST_FAIL("STAmount getrate fail"); + BOOST_FAIL("STAmount getRate fail"); if (STAmount::getRate(STAmount(10), STAmount(1)) != (((100ul-16)<<(64-8))|1000000000000000ul)) - BOOST_FAIL("STAmount getrate fail"); - if (STAmount::getRate(STAmount(c, 1), STAmount(c, 10)) != (((100ul-14)<<(64-8))|1000000000000000ul)) - BOOST_FAIL("STAmount getrate fail"); - if (STAmount::getRate(STAmount(c, 10), STAmount(c, 1)) != (((100ul-16)<<(64-8))|1000000000000000ul)) - BOOST_FAIL("STAmount getrate fail"); - if (STAmount::getRate(STAmount(c, 1), STAmount(10)) != (((100ul-14)<<(64-8))|1000000000000000ul)) - BOOST_FAIL("STAmount getrate fail"); - if (STAmount::getRate(STAmount(c, 10), STAmount(1)) != (((100ul-16)<<(64-8))|1000000000000000ul)) - BOOST_FAIL("STAmount getrate fail"); - if (STAmount::getRate(STAmount(1), STAmount(c, 10)) != (((100ul-14)<<(64-8))|1000000000000000ul)) - BOOST_FAIL("STAmount getrate fail"); - if (STAmount::getRate(STAmount(10), STAmount(c, 1)) != (((100ul-16)<<(64-8))|1000000000000000ul)) - BOOST_FAIL("STAmount getrate fail"); + BOOST_FAIL("STAmount getRate fail"); + if (STAmount::getRate(STAmount(CURRENCY_ONE, ACCOUNT_ONE, 1), STAmount(CURRENCY_ONE, ACCOUNT_ONE, 10)) != (((100ul-14)<<(64-8))|1000000000000000ul)) + BOOST_FAIL("STAmount getRate fail"); + if (STAmount::getRate(STAmount(CURRENCY_ONE, ACCOUNT_ONE, 10), STAmount(CURRENCY_ONE, ACCOUNT_ONE, 1)) != (((100ul-16)<<(64-8))|1000000000000000ul)) + BOOST_FAIL("STAmount getRate fail"); + if (STAmount::getRate(STAmount(CURRENCY_ONE, ACCOUNT_ONE, 1), STAmount(10)) != (((100ul-14)<<(64-8))|1000000000000000ul)) + BOOST_FAIL("STAmount getRate fail"); + if (STAmount::getRate(STAmount(CURRENCY_ONE, ACCOUNT_ONE, 10), STAmount(1)) != (((100ul-16)<<(64-8))|1000000000000000ul)) + BOOST_FAIL("STAmount getRate fail"); + if (STAmount::getRate(STAmount(1), STAmount(CURRENCY_ONE, ACCOUNT_ONE, 10)) != (((100ul-14)<<(64-8))|1000000000000000ul)) + BOOST_FAIL("STAmount getRate fail"); + if (STAmount::getRate(STAmount(10), STAmount(CURRENCY_ONE, ACCOUNT_ONE, 1)) != (((100ul-16)<<(64-8))|1000000000000000ul)) + BOOST_FAIL("STAmount getRate fail"); } diff --git a/src/Application.cpp b/src/Application.cpp index 89fe9d2ddc..2011ca8e78 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -36,9 +36,9 @@ DatabaseCon::~DatabaseCon() } Application::Application() : - mUNL(mIOService), + mIOWork(mIOService), mAuxWork(mAuxService), mUNL(mIOService), mNetOps(mIOService, &mMasterLedger), mTempNodeCache(16384, 90), mHashedObjectStore(16384, 300), - mSNTPClient(mIOService), mRpcDB(NULL), mTxnDB(NULL), mLedgerDB(NULL), mWalletDB(NULL), + mSNTPClient(mAuxService), mRpcDB(NULL), mTxnDB(NULL), mLedgerDB(NULL), mWalletDB(NULL), mHashNodeDB(NULL), mNetNodeDB(NULL), mConnectionPool(mIOService), mPeerDoor(NULL), mRPCDoor(NULL) { @@ -54,6 +54,7 @@ void Application::stop() mIOService.stop(); mHashedObjectStore.bulkWrite(); mValidations.flush(); + mAuxService.stop(); Log(lsINFO) << "Stopped: " << mIOService.stopped(); } @@ -69,6 +70,12 @@ void Application::run() if (!theConfig.DEBUG_LOGFILE.empty()) Log::setLogFile(theConfig.DEBUG_LOGFILE); + boost::thread auxThread(boost::bind(&boost::asio::io_service::run, &mAuxService)); + auxThread.detach(); + + if (!theConfig.RUN_STANDALONE) + mSNTPClient.init(theConfig.SNTP_SERVERS); + // // Construct databases. // @@ -89,12 +96,14 @@ void Application::run() // // Set up UNL. // - getUNL().nodeBootstrap(); + if (!theConfig.RUN_STANDALONE) + getUNL().nodeBootstrap(); + // // Allow peer connections. // - if (!theConfig.PEER_IP.empty() && theConfig.PEER_PORT) + if (!theConfig.RUN_STANDALONE && !theConfig.PEER_IP.empty() && theConfig.PEER_PORT) { mPeerDoor = new PeerDoor(mIOService); } @@ -120,7 +129,8 @@ void Application::run() // // Begin connecting to network. // - mConnectionPool.start(); + if (!theConfig.RUN_STANDALONE) + mConnectionPool.start(); // New stuff. NewcoinAddress rootSeedMaster = NewcoinAddress::createSeedGeneric("masterpassphrase"); @@ -144,12 +154,18 @@ void Application::run() secondLedger->setAccepted(); mMasterLedger.pushLedger(secondLedger, boost::make_shared(true, boost::ref(*secondLedger))); assert(!!secondLedger->getAccountState(rootAddress)); - mNetOps.setLastCloseNetTime(secondLedger->getCloseTimeNC()); + mNetOps.setLastCloseTime(secondLedger->getCloseTimeNC()); } - mSNTPClient.init(theConfig.SNTP_SERVERS); - mNetOps.setStateTimer(); + if (theConfig.RUN_STANDALONE) + { + Log(lsWARNING) << "Running in standalone mode"; + mNetOps.setStandAlone(); + mMasterLedger.runStandAlone(); + } + else + mNetOps.setStateTimer(); mIOService.run(); // This blocks diff --git a/src/Application.h b/src/Application.h index 9efbc89fc1..8ab7f83ed2 100644 --- a/src/Application.h +++ b/src/Application.h @@ -39,7 +39,8 @@ public: class Application { - boost::asio::io_service mIOService; + boost::asio::io_service mIOService, mAuxService; + boost::asio::io_service::work mIOWork, mAuxWork; Wallet mWallet; UniqueNodeList mUNL; @@ -78,6 +79,7 @@ public: NetworkOPs& getOPs() { return mNetOps; } boost::asio::io_service& getIOService() { return mIOService; } + boost::asio::io_service& getAuxService() { return mAuxService; } LedgerMaster& getMasterLedger() { return mMasterLedger; } LedgerAcquireMaster& getMasterLedgerAcquire() { return mMasterLedgerAcquire; } diff --git a/src/CallRPC.cpp b/src/CallRPC.cpp index 38f697a1e7..50896b858c 100644 --- a/src/CallRPC.cpp +++ b/src/CallRPC.cpp @@ -112,7 +112,8 @@ Json::Value callRPC(const std::string& strMethod, const Json::Value& params) "If the file does not exist, create it with owner-readable-only file permissions."); // Connect to localhost - std::cout << "Connecting to port:" << theConfig.RPC_PORT << std::endl; + std::cout << "Connecting to: " << theConfig.RPC_IP << ":" << theConfig.RPC_PORT << std::endl; + boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::address::from_string(theConfig.RPC_IP), theConfig.RPC_PORT); boost::asio::ip::tcp::iostream stream; diff --git a/src/CanonicalTXSet.cpp b/src/CanonicalTXSet.cpp index c46c089374..e3af1e4870 100644 --- a/src/CanonicalTXSet.cpp +++ b/src/CanonicalTXSet.cpp @@ -37,7 +37,7 @@ bool CanonicalTXKey::operator>=(const CanonicalTXKey& key)const return mTXid >= key.mTXid; } -void CanonicalTXSet::push_back(SerializedTransaction::pointer txn) +void CanonicalTXSet::push_back(const SerializedTransaction::pointer& txn) { uint256 effectiveAccount = mSetHash; effectiveAccount ^= txn->getSourceAccount().getAccountID().to256(); diff --git a/src/CanonicalTXSet.h b/src/CanonicalTXSet.h index 6978859af7..3315625a3e 100644 --- a/src/CanonicalTXSet.h +++ b/src/CanonicalTXSet.h @@ -38,7 +38,7 @@ protected: public: CanonicalTXSet(const uint256& lclHash) : mSetHash(lclHash) { ; } - void push_back(SerializedTransaction::pointer txn); + void push_back(const SerializedTransaction::pointer& txn); iterator erase(const iterator& it); iterator begin() { return mMap.begin(); } diff --git a/src/Config.cpp b/src/Config.cpp index cf9c135bfe..4e4cb8e910 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #define SECTION_ACCOUNT_PROBE_MAX "account_probe_max" #define SECTION_DEBUG_LOGFILE "debug_logfile" @@ -12,6 +13,7 @@ #define SECTION_FEE_DEFAULT "fee_default" #define SECTION_FEE_NICKNAME_CREATE "fee_nickname_create" #define SECTION_FEE_OFFER "fee_offer" +#define SECTION_FEE_OPERATION "fee_operation" #define SECTION_IPS "ips" #define SECTION_NETWORK_QUORUM "network_quorum" #define SECTION_PEER_CONNECT_LOW_WATER "peer_connect_low_water" @@ -37,6 +39,7 @@ #define DEFAULT_FEE_ACCOUNT_CREATE 1000 #define DEFAULT_FEE_NICKNAME_CREATE 1000 #define DEFAULT_FEE_OFFER DEFAULT_FEE_DEFAULT +#define DEFAULT_FEE_OPERATION 1 Config theConfig; @@ -145,11 +148,14 @@ void Config::setup(const std::string& strConf) FEE_NICKNAME_CREATE = DEFAULT_FEE_NICKNAME_CREATE; FEE_OFFER = DEFAULT_FEE_OFFER; FEE_DEFAULT = DEFAULT_FEE_DEFAULT; + FEE_CONTRACT_OPERATION = DEFAULT_FEE_OPERATION; ACCOUNT_PROBE_MAX = 10; VALIDATORS_SITE = DEFAULT_VALIDATORS_SITE; + RUN_STANDALONE = false; + load(); } @@ -230,19 +236,19 @@ void Config::load() if (sectionSingleB(secConfig, SECTION_PEER_SCAN_INTERVAL_MIN, strTemp)) // Minimum for min is 60 seconds. - PEER_SCAN_INTERVAL_MIN = MAX(60, boost::lexical_cast(strTemp)); + PEER_SCAN_INTERVAL_MIN = std::max(60, boost::lexical_cast(strTemp)); if (sectionSingleB(secConfig, SECTION_PEER_START_MAX, strTemp)) - PEER_START_MAX = MAX(1, boost::lexical_cast(strTemp)); + PEER_START_MAX = std::max(1, boost::lexical_cast(strTemp)); if (sectionSingleB(secConfig, SECTION_PEER_CONNECT_LOW_WATER, strTemp)) - PEER_CONNECT_LOW_WATER = MAX(1, boost::lexical_cast(strTemp)); + PEER_CONNECT_LOW_WATER = std::max(1, boost::lexical_cast(strTemp)); if (sectionSingleB(secConfig, SECTION_NETWORK_QUORUM, strTemp)) - NETWORK_QUORUM = MAX(0, boost::lexical_cast(strTemp)); + NETWORK_QUORUM = std::max(0, boost::lexical_cast(strTemp)); if (sectionSingleB(secConfig, SECTION_VALIDATION_QUORUM, strTemp)) - VALIDATION_QUORUM = MAX(0, boost::lexical_cast(strTemp)); + VALIDATION_QUORUM = std::max(0, boost::lexical_cast(strTemp)); if (sectionSingleB(secConfig, SECTION_FEE_ACCOUNT_CREATE, strTemp)) FEE_ACCOUNT_CREATE = boost::lexical_cast(strTemp); @@ -256,6 +262,9 @@ void Config::load() if (sectionSingleB(secConfig, SECTION_FEE_DEFAULT, strTemp)) FEE_DEFAULT = boost::lexical_cast(strTemp); + if (sectionSingleB(secConfig, SECTION_FEE_OPERATION, strTemp)) + FEE_CONTRACT_OPERATION = boost::lexical_cast(strTemp); + if (sectionSingleB(secConfig, SECTION_ACCOUNT_PROBE_MAX, strTemp)) ACCOUNT_PROBE_MAX = boost::lexical_cast(strTemp); diff --git a/src/Config.h b/src/Config.h index 585980b92b..6fc42dca35 100644 --- a/src/Config.h +++ b/src/Config.h @@ -63,6 +63,7 @@ public: int LEDGER_PROPOSAL_DELAY_SECONDS; int LEDGER_AVALANCHE_SECONDS; bool LEDGER_CREATOR; // should be false unless we are starting a new ledger + bool RUN_STANDALONE; // Note: The following parameters do not relate to the UNL or trust at all unsigned int NETWORK_QUORUM; // Minimum number of nodes to consider the network present @@ -96,6 +97,7 @@ public: uint64 FEE_ACCOUNT_CREATE; // Fee to create an account. uint64 FEE_NICKNAME_CREATE; // Fee to create a nickname. uint64 FEE_OFFER; // Rate per day. + int FEE_CONTRACT_OPERATION; // fee for each contract operation // Client behavior int ACCOUNT_PROBE_MAX; // How far to scan for accounts. diff --git a/src/ConnectionPool.cpp b/src/ConnectionPool.cpp index 2a7f38bb7e..6285d45551 100644 --- a/src/ConnectionPool.cpp +++ b/src/ConnectionPool.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "Config.h" #include "Peer.h" @@ -42,6 +43,9 @@ ConnectionPool::ConnectionPool(boost::asio::io_service& io_service) : void ConnectionPool::start() { + if (theConfig.RUN_STANDALONE) + return; + // Start running policy. policyEnforce(); @@ -223,8 +227,9 @@ void ConnectionPool::policyHandler(const boost::system::error_code& ecResult) // YYY: Should probably do this in the background. // YYY: Might end up sending to disconnected peer? -void ConnectionPool::relayMessage(Peer* fromPeer, PackedMessage::pointer msg) +int ConnectionPool::relayMessage(Peer* fromPeer, const PackedMessage::pointer& msg) { + int sentTo = 0; boost::mutex::scoped_lock sl(mPeerLock); BOOST_FOREACH(naPeer pair, mConnectedMap) @@ -233,8 +238,13 @@ void ConnectionPool::relayMessage(Peer* fromPeer, PackedMessage::pointer msg) if (!peer) std::cerr << "CP::RM null peer in list" << std::endl; else if ((!fromPeer || !(peer.get() == fromPeer)) && peer->isConnected()) + { + ++sentTo; peer->sendPacket(msg); + } } + + return sentTo; } // Schedule a connection via scanning. @@ -243,6 +253,8 @@ void ConnectionPool::relayMessage(Peer* fromPeer, PackedMessage::pointer msg) // Requires sane IP and port. void ConnectionPool::connectTo(const std::string& strIp, int iPort) { + if (theConfig.RUN_STANDALONE) + return; { Database* db = theApp->getWalletDB()->getDB(); ScopedLock sl(theApp->getWalletDB()->getDBLock()); @@ -334,7 +346,8 @@ std::vector ConnectionPool::getPeerVector() // Now know peer's node public key. Determine if we want to stay connected. // <-- bNew: false = redundant -bool ConnectionPool::peerConnected(Peer::pointer peer, const NewcoinAddress& naPeer, const std::string& strIP, int iPort) +bool ConnectionPool::peerConnected(Peer::ref peer, const NewcoinAddress& naPeer, + const std::string& strIP, int iPort) { bool bNew = false; @@ -392,7 +405,7 @@ bool ConnectionPool::peerConnected(Peer::pointer peer, const NewcoinAddress& naP } // We maintain a map of public key to peer for connected and verified peers. Maintain it. -void ConnectionPool::peerDisconnected(Peer::pointer peer, const NewcoinAddress& naPeer) +void ConnectionPool::peerDisconnected(Peer::ref peer, const NewcoinAddress& naPeer) { if (naPeer.isValid()) { @@ -479,7 +492,7 @@ bool ConnectionPool::peerScanSet(const std::string& strIp, int iPort) } // --> strIp: not empty -void ConnectionPool::peerClosed(Peer::pointer peer, const std::string& strIp, int iPort) +void ConnectionPool::peerClosed(Peer::ref peer, const std::string& strIp, int iPort) { ipPort ipPeer = make_pair(strIp, iPort); bool bScanRefresh = false; @@ -534,7 +547,7 @@ void ConnectionPool::peerClosed(Peer::pointer peer, const std::string& strIp, in scanRefresh(); } -void ConnectionPool::peerVerified(Peer::pointer peer) +void ConnectionPool::peerVerified(Peer::ref peer) { if (mScanning && mScanning == peer) { @@ -639,7 +652,7 @@ void ConnectionPool::scanRefresh() (void) mScanTimer.cancel(); - iInterval = MAX(iInterval, theConfig.PEER_SCAN_INTERVAL_MIN); + iInterval = std::max(iInterval, theConfig.PEER_SCAN_INTERVAL_MIN); tpNext = tpNow + boost::posix_time::seconds(iInterval); diff --git a/src/ConnectionPool.h b/src/ConnectionPool.h index c71c59fef6..4db0239853 100644 --- a/src/ConnectionPool.h +++ b/src/ConnectionPool.h @@ -58,7 +58,7 @@ public: void start(); // Send message to network. - void relayMessage(Peer* fromPeer, PackedMessage::pointer msg); + int relayMessage(Peer* fromPeer, const PackedMessage::pointer& msg); // Manual connection request. // Queue for immediate scanning. @@ -72,16 +72,16 @@ public: // We know peers node public key. // <-- bool: false=reject - bool peerConnected(Peer::pointer peer, const NewcoinAddress& naPeer, const std::string& strIP, int iPort); + bool peerConnected(Peer::ref peer, const NewcoinAddress& naPeer, const std::string& strIP, int iPort); // No longer connected. - void peerDisconnected(Peer::pointer peer, const NewcoinAddress& naPeer); + void peerDisconnected(Peer::ref peer, const NewcoinAddress& naPeer); // As client accepted. - void peerVerified(Peer::pointer peer); + void peerVerified(Peer::ref peer); // As client failed connect and be accepted. - void peerClosed(Peer::pointer peer, const std::string& strIp, int iPort); + void peerClosed(Peer::ref peer, const std::string& strIp, int iPort); Json::Value getPeersJson(); std::vector getPeerVector(); @@ -98,11 +98,6 @@ public: void policyLowWater(); void policyEnforce(); -#if 0 - //std::vector > mBroadcastMessages; - - bool isMessageKnown(PackedMessage::pointer msg); -#endif }; extern void splitIpPort(const std::string& strIpPort, std::string& strIp, int& iPort); diff --git a/src/Contract.cpp b/src/Contract.cpp new file mode 100644 index 0000000000..09c3a4ecfc --- /dev/null +++ b/src/Contract.cpp @@ -0,0 +1,35 @@ +#include "Contract.h" +#include "Interpreter.h" + +using namespace Script; +/* +JED: V III +*/ + +Contract::Contract() +{ + +} + + +void Contract::executeCreate() +{ + +} +void Contract::executeRemove() +{ + +} +void Contract::executeFund() +{ + +} +void Contract::executeAccept() +{ + //std::vector code; + + //Interpreter interpreter; + //interpreter.interpret(this,code); +} + + diff --git a/src/Contract.h b/src/Contract.h new file mode 100644 index 0000000000..76d30dc54a --- /dev/null +++ b/src/Contract.h @@ -0,0 +1,30 @@ +#ifndef __CONTRACT__ +#define __CONTRACT__ + +#include "SerializedLedger.h" +#include +#include "ScriptData.h" +/* + Encapsulates the SLE for a Contract +*/ + +class Contract +{ +public: + Contract(); + + uint160& getIssuer(); + uint160& getOwner(); + STAmount& getRippleEscrow(); + uint32 getEscrow(); + uint32 getBond(); + + Script::Data getData(int index); + + void executeCreate(); + void executeRemove(); + void executeFund(); + void executeAccept(); +}; + +#endif \ No newline at end of file diff --git a/src/Conversion.cpp b/src/Conversion.cpp deleted file mode 100644 index 7f9de9029b..0000000000 --- a/src/Conversion.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#include "Conversion.h" -#include "base58.h" -using namespace std; - -#if 0 - -uint160 protobufTo160(const std::string& buf) -{ - uint160 ret; - // TODO: - return(ret); -} - -uint256 protobufTo256(const std::string& hash) -{ - uint256 ret; - // TODO: - return(ret); -} - -uint160 humanTo160(const std::string& buf) -{ - vector retVec; - DecodeBase58(buf,retVec); - uint160 ret; - memcpy(ret.begin(), &retVec[0], ret.GetSerializeSize()); - - - return(ret); -} - -bool humanToPK(const std::string& buf,std::vector& retVec) -{ - return(DecodeBase58(buf,retVec)); -} - -bool u160ToHuman(uint160& buf, std::string& retStr) -{ - retStr=EncodeBase58(buf.begin(),buf.end()); - return(true); -} - -#endif - -base_uint256 uint160::to256() const -{ - uint256 m; - memcpy(m.begin(), begin(), size()); - return m; -} diff --git a/src/Conversion.h b/src/Conversion.h deleted file mode 100644 index c2ceb5339a..0000000000 --- a/src/Conversion.h +++ /dev/null @@ -1,11 +0,0 @@ -#include "uint256.h" -#include - -extern uint160 protobufTo160(const std::string& buf); -extern uint256 protobufTo256(const std::string& hash); -extern uint160 humanTo160(const std::string& buf); -extern bool humanToPK(const std::string& buf,std::vector& retVec); - - -extern bool u160ToHuman(uint160& buf, std::string& retStr); - diff --git a/src/DBInit.cpp b/src/DBInit.cpp index 245b2c5f78..c0f7086b78 100644 --- a/src/DBInit.cpp +++ b/src/DBInit.cpp @@ -14,6 +14,7 @@ const char *TxnDBInit[] = { LedgerSeq BIGINT UNSIGNED, \ Status CHARACTER(1), \ RawTxn BLOB \ + TxnMeta BLOB \ );", "CREATE TABLE PubKeys ( \ ID CHARACTER(35) PRIMARY KEY, \ @@ -56,7 +57,7 @@ const char *LedgerDBInit[] = { LedgerHash CHARACTER(64), \ NodePubKey CHARACTER(56), \ Flags BIGINT UNSIGNED, \ - CloseTime BIGINT UNSIGNED, \ + SignTime BIGINT UNSIGNED, \ Signature BLOB \ );", "CREATE INDEX ValidationByHash ON \ diff --git a/src/FieldNames.cpp b/src/FieldNames.cpp new file mode 100644 index 0000000000..612d250c97 --- /dev/null +++ b/src/FieldNames.cpp @@ -0,0 +1,97 @@ + +#include "FieldNames.h" + +#include + +#include +#include +#include + + +// These must stay at the top of this file +std::map SField::codeToField; +boost::mutex SField::mapMutex; + +SField sfInvalid(-1), sfGeneric(0); +SField sfLedgerEntry(FIELD_CODE(STI_LEDGERENTRY, 1), STI_LEDGERENTRY, 1, "LedgerEntry"); +SField sfTransaction(FIELD_CODE(STI_TRANSACTION, 1), STI_TRANSACTION, 1, "Transaction"); +SField sfValidation(FIELD_CODE(STI_VALIDATION, 1), STI_VALIDATION, 1, "Validation"); + +#define FIELD(name, type, index) SField sf##name(FIELD_CODE(STI_##type, index), STI_##type, index, #name); +#define TYPE(name, type, index) +#include "SerializeProto.h" +#undef FIELD +#undef TYPE + + +SField::ref SField::getField(int code) +{ + int type = code >> 16; + int field = code % 0xffff; + + if ((type <= 0) || (type >= 256) || (field <= 0) || (field >= 256)) + return sfInvalid; + + boost::mutex::scoped_lock sl(mapMutex); + + std::map::iterator it = codeToField.find(code); + if (it != codeToField.end()) + return *(it->second); + + switch (type) + { // types we are willing to dynamically extend + +#define FIELD(name, type, index) +#define TYPE(name, type, index) case STI_##type: +#include "SerializeProto.h" +#undef FIELD +#undef TYPE + + break; + default: + return sfInvalid; + } + + return *(new SField(code, static_cast(type), field, NULL)); +} + +int SField::compare(SField::ref f1, SField::ref f2) +{ // -1 = f1 comes before f2, 0 = illegal combination, 1 = f1 comes after f2 + if ((f1.fieldCode <= 0) || (f2.fieldCode <= 0)) + return 0; + + if (f1.fieldCode < f2.fieldCode) + return -1; + + if (f2.fieldCode < f1.fieldCode) + return 1; + + return 0; +} + +SField::ref SField::getField(int type, int value) +{ + return getField(FIELD_CODE(type, value)); +} + +std::string SField::getName() const +{ + if (!fieldName.empty()) + return fieldName; + if (fieldValue == 0) + return ""; + return boost::lexical_cast(static_cast(fieldType)) + "/" + + boost::lexical_cast(fieldValue); +} + +SField::ref SField::getField(const std::string& fieldName) +{ // OPTIMIZEME me with a map. CHECKME this is case sensitive + boost::mutex::scoped_lock sl(mapMutex); + typedef std::pair int_sfref_pair; + BOOST_FOREACH(const int_sfref_pair& fieldPair, codeToField) + { + if (fieldPair.second->fieldName == fieldName) + return *(fieldPair.second); + } + return sfInvalid; +} diff --git a/src/FieldNames.h b/src/FieldNames.h new file mode 100644 index 0000000000..784900c148 --- /dev/null +++ b/src/FieldNames.h @@ -0,0 +1,85 @@ +#ifndef __FIELDNAMES__ +#define __FIELDNAMES__ + +#include + +#include + +#define FIELD_CODE(type, index) ((static_cast(type) << 16) | index) + +enum SerializedTypeID +{ + // special types + STI_UNKNOWN = -2, + STI_DONE = -1, + STI_NOTPRESENT = 0, + +#define TYPE(name, field, value) STI_##field = value, +#define FIELD(name, field, value) +#include "SerializeProto.h" +#undef TYPE +#undef FIELD + + // high level types + STI_TRANSACTION = 10001, + STI_LEDGERENTRY = 10002, + STI_VALIDATION = 10003, +}; + +enum SOE_Flags +{ + SOE_INVALID = -1, + SOE_REQUIRED = 0, // required + SOE_OPTIONAL = 1, // optional +}; + +class SField +{ +public: + typedef const SField& ref; + typedef SField const * ptr; + +protected: + static std::map codeToField; + static boost::mutex mapMutex; + +public: + + const int fieldCode; // (type<<16)|index + const SerializedTypeID fieldType; // STI_* + const int fieldValue; // Code number for protocol + std::string fieldName; + + SField(int fc, SerializedTypeID tid, int fv, const char* fn) : + fieldCode(fc), fieldType(tid), fieldValue(fv), fieldName(fn) + { codeToField[fc] = this; } + + SField(int fc) : fieldCode(fc), fieldType(STI_UNKNOWN), fieldValue(0) { ; } + + static SField::ref getField(int fieldCode); + static SField::ref getField(int fieldType, int fieldValue); + static SField::ref getField(const std::string& fieldName); + static SField::ref getField(SerializedTypeID type, int value) { return getField(FIELD_CODE(type, value)); } + + std::string getName() const; + bool hasName() const { return !fieldName.empty(); } + + bool isGeneric() const { return fieldCode == 0; } + bool isInvalid() const { return fieldCode == -1; } + bool isKnown() const { return fieldType != STI_UNKNOWN; } + + bool operator==(const SField& f) const { return fieldCode == f.fieldCode; } + bool operator!=(const SField& f) const { return fieldCode != f.fieldCode; } + + static int compare(SField::ref f1, SField::ref f2); +}; + +extern SField sfInvalid, sfGeneric, sfLedgerEntry, sfTransaction, sfValidation; + +#define FIELD(name, type, index) extern SField sf##name; +#define TYPE(name, type, index) +#include "SerializeProto.h" +#undef FIELD +#undef TYPE + +#endif diff --git a/src/HashedObject.cpp b/src/HashedObject.cpp index 8c9ef0ac7c..69e855ddc6 100644 --- a/src/HashedObject.cpp +++ b/src/HashedObject.cpp @@ -2,6 +2,7 @@ #include "HashedObject.h" #include +#include #include "Serializer.h" #include "Application.h" @@ -21,7 +22,9 @@ bool HashedObjectStore::store(HashedObjectType type, uint32 index, if (!theApp->getHashNodeDB()) return true; if (mCache.touch(hash)) { - Log(lsTRACE) << "HOS: " << hash.GetHex() << " store: incache"; +#ifdef HS_DEBUG + Log(lsTRACE) << "HOS: " << hash << " store: incache"; +#endif return false; } @@ -37,7 +40,6 @@ bool HashedObjectStore::store(HashedObjectType type, uint32 index, t.detach(); } } - Log(lsTRACE) << "HOS: " << hash.GetHex() << " store: deferred"; return true; } @@ -62,23 +64,22 @@ void HashedObjectStore::bulkWrite() db->executeSQL("BEGIN TRANSACTION;"); - for (std::vector< boost::shared_ptr >::iterator it = set.begin(), end = set.end(); it != end; ++it) + BOOST_FOREACH(const boost::shared_ptr& it, set) { - HashedObject& obj = **it; - if (!SQL_EXISTS(db, boost::str(fExists % obj.getHash().GetHex()))) + if (!SQL_EXISTS(db, boost::str(fExists % it->getHash().GetHex()))) { char type; - switch(obj.getType()) + switch(it->getType()) { - case LEDGER: type = 'L'; break; - case TRANSACTION: type = 'T'; break; - case ACCOUNT_NODE: type = 'A'; break; - case TRANSACTION_NODE: type = 'N'; break; - default: type = 'U'; + case hotLEDGER: type= 'L'; break; + case hotTRANSACTION: type = 'T'; break; + case hotACCOUNT_NODE: type = 'A'; break; + case hotTRANSACTION_NODE: type = 'N'; break; + default: type = 'U'; } std::string rawData; - db->escape(&(obj.getData().front()), obj.getData().size(), rawData); - db->executeSQL(boost::str(fAdd % obj.getHash().GetHex() % type % obj.getIndex() % rawData )); + db->escape(&(it->getData().front()), it->getData().size(), rawData); + db->executeSQL(boost::str(fAdd % it->getHash().GetHex() % type % it->getIndex() % rawData )); } } @@ -93,7 +94,7 @@ HashedObject::pointer HashedObjectStore::retrieve(const uint256& hash) obj = mCache.fetch(hash); if (obj) { - Log(lsTRACE) << "HOS: " << hash.GetHex() << " fetch: incache"; + Log(lsTRACE) << "HOS: " << hash << " fetch: incache"; return obj; } } @@ -103,8 +104,6 @@ HashedObject::pointer HashedObjectStore::retrieve(const uint256& hash) sql.append(hash.GetHex()); sql.append("';"); - std::string type; - uint32 index; std::vector data; { ScopedLock sl(theApp->getHashNodeDB()->getDBLock()); @@ -112,7 +111,7 @@ HashedObject::pointer HashedObjectStore::retrieve(const uint256& hash) if (!db->executeSQL(sql) || !db->startIterRows()) { - Log(lsTRACE) << "HOS: " << hash.GetHex() << " fetch: not in db"; + Log(lsTRACE) << "HOS: " << hash << " fetch: not in db"; return HashedObject::pointer(); } @@ -120,7 +119,7 @@ HashedObject::pointer HashedObjectStore::retrieve(const uint256& hash) db->getStr("ObjType", type); if (type.size() == 0) return HashedObject::pointer(); - index = db->getBigInt("LedgerIndex"); + uint32 index = db->getBigInt("LedgerIndex"); int size = db->getBinary("Object", NULL, 0); data.resize(size); @@ -129,13 +128,13 @@ HashedObject::pointer HashedObjectStore::retrieve(const uint256& hash) assert(Serializer::getSHA512Half(data) == hash); - HashedObjectType htype = UNKNOWN; - switch(type[0]) + HashedObjectType htype = hotUNKNOWN; + switch (type[0]) { - case 'L': htype = LEDGER; break; - case 'T': htype = TRANSACTION; break; - case 'A': htype = ACCOUNT_NODE; break; - case 'N': htype = TRANSACTION_NODE; break; + case 'L': htype = hotLEDGER; break; + case 'T': htype = hotTRANSACTION; break; + case 'A': htype = hotACCOUNT_NODE; break; + case 'N': htype = hotTRANSACTION_NODE; break; default: Log(lsERROR) << "Invalid hashed object"; return HashedObject::pointer(); @@ -144,7 +143,7 @@ HashedObject::pointer HashedObjectStore::retrieve(const uint256& hash) obj = boost::make_shared(htype, index, data, hash); mCache.canonicalize(hash, obj); } - Log(lsTRACE) << "HOS: " << hash.GetHex() << " fetch: in db"; + Log(lsTRACE) << "HOS: " << hash << " fetch: in db"; return obj; } diff --git a/src/HashedObject.h b/src/HashedObject.h index 0331cfa9b3..f403d5042a 100644 --- a/src/HashedObject.h +++ b/src/HashedObject.h @@ -10,11 +10,11 @@ enum HashedObjectType { - UNKNOWN = 0, - LEDGER = 1, - TRANSACTION = 2, - ACCOUNT_NODE = 3, - TRANSACTION_NODE = 4 + hotUNKNOWN = 0, + hotLEDGER = 1, + hotTRANSACTION = 2, + hotACCOUNT_NODE = 3, + hotTRANSACTION_NODE = 4 }; class HashedObject diff --git a/src/Interpreter.cpp b/src/Interpreter.cpp new file mode 100644 index 0000000000..c97b8780c9 --- /dev/null +++ b/src/Interpreter.cpp @@ -0,0 +1,200 @@ +#include "Interpreter.h" +#include "Operation.h" +#include "Config.h" + +/* +We also need to charge for each op + +*/ + +namespace Script { + +Interpreter::Interpreter() +{ + mContract=NULL; + mCode=NULL; + mInstructionPointer=0; + mTotalFee=0; + + mInBlock=false; + mBlockSuccess=true; + mBlockJump=0; + + mFunctionTable.resize(NUM_OF_OPS); + /* + mFunctionTable[INT_OP]=new IntOp(); + mFunctionTable[FLOAT_OP]=new FloatOp(); + mFunctionTable[UINT160_OP]=new Uint160Op(); + mFunctionTable[BOOL_OP]=new Uint160Op(); + mFunctionTable[PATH_OP]=new Uint160Op(); + + mFunctionTable[ADD_OP]=new AddOp(); + mFunctionTable[SUB_OP]=new SubOp(); + mFunctionTable[MUL_OP]=new MulOp(); + mFunctionTable[DIV_OP]=new DivOp(); + mFunctionTable[MOD_OP]=new ModOp(); + mFunctionTable[GTR_OP]=new GtrOp(); + mFunctionTable[LESS_OP]=new LessOp(); + mFunctionTable[EQUAL_OP]=new SubOp(); + mFunctionTable[NOT_EQUAL_OP]=new SubOp(); + mFunctionTable[AND_OP]=new SubOp(); + mFunctionTable[OR_OP]=new SubOp(); + mFunctionTable[NOT_OP]=new SubOp(); + mFunctionTable[JUMP_OP]=new SubOp(); + mFunctionTable[JUMPIF_OP]=new SubOp(); + mFunctionTable[STOP_OP]=new SubOp(); + mFunctionTable[CANCEL_OP]=new SubOp(); + mFunctionTable[BLOCK_OP]=new SubOp(); + mFunctionTable[BLOCK_END_OP]=new SubOp(); + mFunctionTable[SEND_XNS_OP]=new SendXNSOp(); + mFunctionTable[SEND_OP]=new SendOp(); + mFunctionTable[REMOVE_CONTRACT_OP]=new SubOp(); + mFunctionTable[FEE_OP]=new SubOp(); + mFunctionTable[CHANGE_CONTRACT_OWNER_OP]=new SubOp(); + mFunctionTable[STOP_REMOVE_OP]=new SubOp(); + mFunctionTable[SET_DATA_OP]=new SubOp(); + mFunctionTable[GET_DATA_OP]=new SubOp(); + mFunctionTable[GET_NUM_DATA_OP]=new SubOp(); + mFunctionTable[SET_REGISTER_OP]=new SubOp(); + mFunctionTable[GET_REGISTER_OP]=new SubOp(); + mFunctionTable[GET_ISSUER_ID_OP]=new SubOp(); + mFunctionTable[GET_OWNER_ID_OP]=new SubOp(); + mFunctionTable[GET_LEDGER_TIME_OP]=new SubOp(); + mFunctionTable[GET_LEDGER_NUM_OP]=new SubOp(); + mFunctionTable[GET_RAND_FLOAT_OP]=new SubOp(); + mFunctionTable[GET_XNS_ESCROWED_OP]=new SubOp(); + mFunctionTable[GET_RIPPLE_ESCROWED_OP]=new SubOp(); + mFunctionTable[GET_RIPPLE_ESCROWED_CURRENCY_OP]=new SubOp(); + mFunctionTable[GET_RIPPLE_ESCROWED_ISSUER]=new GetRippleEscrowedIssuerOp(); + mFunctionTable[GET_ACCEPT_DATA_OP]=new AcceptDataOp(); + mFunctionTable[GET_ACCEPTOR_ID_OP]=new GetAcceptorIDOp(); + mFunctionTable[GET_CONTRACT_ID_OP]=new GetContractIDOp(); + */ +} + +Data::pointer Interpreter::popStack() +{ + if(mStack.size()) + { + Data::pointer item=mStack[mStack.size()-1]; + mStack.pop_back(); + return(item); + }else + { + return(Data::pointer(new ErrorData())); + } +} + +void Interpreter::pushStack(Data::pointer data) +{ + mStack.push_back(data); +} + + +// offset is where to jump to if the block fails +bool Interpreter::startBlock(int offset) +{ + if(mInBlock) return(false); // can't nest blocks + mBlockSuccess=true; + mInBlock=true; + mBlockJump=offset+mInstructionPointer; + return(true); +} + +bool Interpreter::endBlock() +{ + if(!mInBlock) return(false); + mInBlock=false; + mBlockJump=0; + pushStack(Data::pointer(new BoolData(mBlockSuccess))); + return(true); +} + +TER Interpreter::interpret(Contract* contract,const SerializedTransaction& txn,std::vector& code) +{ + mContract=contract; + mCode=&code; + mTotalFee=0; + mInstructionPointer=0; + while(mInstructionPointer=mFunctionTable.size()) + { + // TODO: log + return(temMALFORMED); // TODO: is this actually what we want to do? + } + + mTotalFee += mFunctionTable[ fun ]->getFee(); + if(mTotalFee>txn.getTransactionFee().getNValue()) + { + // TODO: log + return(telINSUF_FEE_P); + }else + { + if(!mFunctionTable[ fun ]->work(this)) + { + // TODO: log + return(temMALFORMED); // TODO: is this actually what we want to do? + } + } + } + return(tesSUCCESS); +} + + +Data::pointer Interpreter::getIntData() +{ + int value=0; // TODO + mInstructionPointer += 4; + return(Data::pointer(new IntData(value))); +} + +Data::pointer Interpreter::getFloatData() +{ + float value=0; // TODO + mInstructionPointer += 4; + return(Data::pointer(new FloatData(value))); +} + +Data::pointer Interpreter::getUint160Data() +{ + uint160 value; // TODO + mInstructionPointer += 20; + return(Data::pointer(new Uint160Data(value))); +} + + + +bool Interpreter::jumpTo(int offset) +{ + mInstructionPointer += offset; + if( (mInstructionPointer<0) || (mInstructionPointer>mCode->size()) ) + { + mInstructionPointer -= offset; + return(false); + } + return(true); +} + +void Interpreter::stop() +{ + mInstructionPointer=mCode->size(); +} + +Data::pointer Interpreter::getContractData(int index) +{ + return(Data::pointer(new ErrorData())); +} + +bool Interpreter::canSign(const uint160& signer) +{ + return(true); +} + + + + +} // end namespace diff --git a/src/Interpreter.h b/src/Interpreter.h new file mode 100644 index 0000000000..ad7d2cb855 --- /dev/null +++ b/src/Interpreter.h @@ -0,0 +1,82 @@ +#ifndef __INTERPRETER__ +#define __INTERPRETER__ + +#include "uint256.h" +#include "Contract.h" +#include +#include +#include "ScriptData.h" +#include "TransactionEngine.h" + +namespace Script { + +class Operation; +// Contracts are non typed have variable data types + +class Interpreter +{ + std::vector mFunctionTable; + + std::vector mStack; + + Contract* mContract; + std::vector* mCode; + unsigned int mInstructionPointer; + int mTotalFee; + + bool mInBlock; + int mBlockJump; + bool mBlockSuccess; + +public: + + + enum { INT_OP=1,FLOAT_OP,UINT160_OP,BOOL_OP,PATH_OP, + ADD_OP,SUB_OP,MUL_OP,DIV_OP,MOD_OP, + GTR_OP,LESS_OP,EQUAL_OP,NOT_EQUAL_OP, + AND_OP,OR_OP,NOT_OP, + JUMP_OP, JUMPIF_OP, + STOP_OP, CANCEL_OP, + + BLOCK_OP, BLOCK_END_OP, + SEND_XNS_OP,SEND_OP,REMOVE_CONTRACT_OP,FEE_OP,CHANGE_CONTRACT_OWNER_OP, + STOP_REMOVE_OP, + SET_DATA_OP,GET_DATA_OP, GET_NUM_DATA_OP, + SET_REGISTER_OP,GET_REGISTER_OP, + GET_ISSUER_ID_OP, GET_OWNER_ID_OP, GET_LEDGER_TIME_OP, GET_LEDGER_NUM_OP, GET_RAND_FLOAT_OP, + GET_XNS_ESCROWED_OP, GET_RIPPLE_ESCROWED_OP, GET_RIPPLE_ESCROWED_CURRENCY_OP, GET_RIPPLE_ESCROWED_ISSUER, + GET_ACCEPT_DATA_OP, GET_ACCEPTOR_ID_OP, GET_CONTRACT_ID_OP, + NUM_OF_OPS }; + + Interpreter(); + + // returns a TransactionEngineResult + TER interpret(Contract* contract,const SerializedTransaction& txn,std::vector& code); + + void stop(); + + bool canSign(const uint160& signer); + + int getInstructionPointer(){ return(mInstructionPointer); } + void setInstructionPointer(int n){ mInstructionPointer=n;} + + Data::pointer popStack(); + void pushStack(Data::pointer data); + bool jumpTo(int offset); + + bool startBlock(int offset); + bool endBlock(); + + + Data::pointer getIntData(); + Data::pointer getFloatData(); + Data::pointer getUint160Data(); + Data::pointer getAcceptData(int index); + Data::pointer getContractData(int index); + + +}; + +} // end namespace + +#endif \ No newline at end of file diff --git a/src/Ledger.cpp b/src/Ledger.cpp index e94c6dc5b1..2f3c8cfdf1 100644 --- a/src/Ledger.cpp +++ b/src/Ledger.cpp @@ -13,22 +13,22 @@ #include "../obj/src/newcoin.pb.h" #include "PackedMessage.h" #include "Config.h" -#include "Conversion.h" #include "BitcoinUtil.h" #include "Wallet.h" #include "LedgerTiming.h" #include "HashPrefixes.h" #include "Log.h" -Ledger::Ledger(const NewcoinAddress& masterID, uint64 startAmount) : mTotCoins(startAmount), mLedgerSeq(0), + +Ledger::Ledger(const NewcoinAddress& masterID, uint64 startAmount) : mTotCoins(startAmount), mLedgerSeq(1), mCloseTime(0), mParentCloseTime(0), mCloseResolution(LEDGER_TIME_ACCURACY), mCloseFlags(0), mClosed(false), mValidHash(false), mAccepted(false), mImmutable(false), mTransactionMap(new SHAMap()), mAccountStateMap(new SHAMap()) { // special case: put coins in root account AccountState::pointer startAccount = boost::make_shared(masterID); - startAccount->peekSLE().setIFieldAmount(sfBalance, startAmount); - startAccount->peekSLE().setIFieldU32(sfSequence, 1); + startAccount->peekSLE().setFieldAmount(sfBalance, startAmount); + startAccount->peekSLE().setFieldU32(sfSequence, 1); writeBack(lepCREATE, startAccount->getSLE()); #if 0 std::cerr << "Root account:"; @@ -57,7 +57,7 @@ Ledger::Ledger(Ledger& ledger, bool isMutable) : mTotCoins(ledger.mTotCoins), mL } -Ledger::Ledger(bool dummy, Ledger& prevLedger) : +Ledger::Ledger(bool /* dummy */, Ledger& prevLedger) : mTotCoins(prevLedger.mTotCoins), mLedgerSeq(prevLedger.mLedgerSeq + 1), mParentCloseTime(prevLedger.mCloseTime), mCloseResolution(prevLedger.mCloseResolution), mCloseFlags(0), mClosed(false), mValidHash(false), mAccepted(false), mImmutable(false), @@ -71,7 +71,7 @@ Ledger::Ledger(bool dummy, Ledger& prevLedger) : prevLedger.getCloseAgree(), mLedgerSeq); if (prevLedger.mCloseTime == 0) { - mCloseTime = theApp->getOPs().getCloseTimeNC(); + mCloseTime = theApp->getOPs().getCloseTimeNC() - mCloseResolution; mCloseTime -= (mCloseTime % mCloseResolution); } else @@ -153,7 +153,7 @@ void Ledger::setAccepted(uint32 closeTime, int closeResolution, bool correctClos void Ledger::setAccepted() { // used when we acquired the ledger - assert(mClosed && (mCloseResolution != 0) && (mCloseResolution != 0)); + // FIXME assert(mClosed && (mCloseTime != 0) && (mCloseResolution != 0)); mCloseTime -= mCloseTime % mCloseResolution; updateHash(); mAccepted = true; @@ -177,7 +177,7 @@ AccountState::pointer Ledger::getAccountState(const NewcoinAddress& accountID) SerializedLedgerEntry::pointer sle = boost::make_shared(item->peekSerializer(), item->getTag()); if (sle->getType() != ltACCOUNT_ROOT) return AccountState::pointer(); - return boost::make_shared(sle); + return boost::make_shared(sle,accountID); } NicknameState::pointer Ledger::getNicknameState(const uint256& uNickname) @@ -210,35 +210,51 @@ RippleState::pointer Ledger::accessRippleState(const uint256& uNode) return boost::make_shared(sle); } -bool Ledger::addTransaction(Transaction::pointer trans) +bool Ledger::addTransaction(const uint256& txID, const Serializer& txn) { // low-level - just add to table - assert(!mAccepted); - assert(trans->getID().isNonZero()); - Serializer s; - trans->getSTransaction()->add(s); - SHAMapItem::pointer item = boost::make_shared(trans->getID(), s.peekData()); - if (!mTransactionMap->addGiveItem(item, true, false)) // FIXME: TX metadata + SHAMapItem::pointer item = boost::make_shared(txID, txn.peekData()); + if (!mTransactionMap->addGiveItem(item, true, false)) return false; return true; } -bool Ledger::addTransaction(const uint256& txID, const Serializer& txn) +bool Ledger::addTransaction(const uint256& txID, const Serializer& txn, const Serializer& md) { // low-level - just add to table - SHAMapItem::pointer item = boost::make_shared(txID, txn.peekData()); - if (!mTransactionMap->addGiveItem(item, true, false)) // FIXME: TX metadata + Serializer s(txn.getDataLength() + md.getDataLength() + 64); + s.addVL(txn.peekData()); + s.addVL(md.peekData()); + SHAMapItem::pointer item = boost::make_shared(txID, s.peekData()); + if (!mTransactionMap->addGiveItem(item, true, true)) return false; return true; } Transaction::pointer Ledger::getTransaction(const uint256& transID) const { - SHAMapItem::pointer item = mTransactionMap->peekItem(transID); + SHAMapTreeNode::TNType type; + SHAMapItem::pointer item = mTransactionMap->peekItem(transID, type); if (!item) return Transaction::pointer(); Transaction::pointer txn = theApp->getMasterTransaction().fetch(transID, false); - if (txn) return txn; + if (txn) + return txn; + + if (type == SHAMapTreeNode::tnTRANSACTION_NM) + txn = Transaction::sharedTransaction(item->getData(), true); + else if (type == SHAMapTreeNode::tnTRANSACTION_MD) + { + std::vector txnData; + int txnLength; + if (!item->peekSerializer().getVL(txnData, 0, txnLength)) + return Transaction::pointer(); + txn = Transaction::sharedTransaction(txnData, false); + } + else + { + assert(false); + return Transaction::pointer(); + } - txn = Transaction::sharedTransaction(item->getData(), true); if (txn->getStatus() == NEW) txn->setStatus(mClosed ? COMMITTED : INCLUDED, mLedgerSeq); @@ -246,6 +262,39 @@ Transaction::pointer Ledger::getTransaction(const uint256& transID) const return txn; } +bool Ledger::getTransaction(const uint256& txID, Transaction::pointer& txn, TransactionMetaSet::pointer& meta) +{ + SHAMapTreeNode::TNType type; + SHAMapItem::pointer item = mTransactionMap->peekItem(txID, type); + if (!item) + return false; + + if (type == SHAMapTreeNode::tnTRANSACTION_NM) + { // in tree with no metadata + txn = theApp->getMasterTransaction().fetch(txID, false); + meta = TransactionMetaSet::pointer(); + if (!txn) + txn = Transaction::sharedTransaction(item->getData(), true); + } + else if (type == SHAMapTreeNode::tnTRANSACTION_MD) + { // in tree with metadata + SerializerIterator it(item->getData()); + txn = theApp->getMasterTransaction().fetch(txID, false); + if (!txn) + txn = Transaction::sharedTransaction(it.getVL(), true); + else + it.getVL(); // skip transaction + meta = boost::make_shared(mLedgerSeq, it.getVL()); + } + else + return false; + + if (txn->getStatus() == NEW) + txn->setStatus(mClosed ? COMMITTED : INCLUDED, mLedgerSeq); + theApp->getMasterTransaction().canonicalize(txn, false); + return true; +} + bool Ledger::unitTest() { return true; @@ -257,7 +306,7 @@ uint256 Ledger::getHash() return(mHash); } -void Ledger::saveAcceptedLedger(Ledger::pointer ledger) +void Ledger::saveAcceptedLedger(Ledger::ref ledger) { static boost::format ledgerExists("SELECT LedgerSeq FROM Ledgers where LedgerSeq = %d;"); static boost::format deleteLedger("DELETE FROM Ledgers WHERE LedgerSeq = %d;"); @@ -278,9 +327,9 @@ void Ledger::saveAcceptedLedger(Ledger::pointer ledger) ledger->mAccountHash.GetHex() % ledger->mTransHash.GetHex())); // write out dirty nodes - while(ledger->mTransactionMap->flushDirty(256, TRANSACTION_NODE, ledger->mLedgerSeq)) + while(ledger->mTransactionMap->flushDirty(256, hotTRANSACTION_NODE, ledger->mLedgerSeq)) { ; } - while(ledger->mAccountStateMap->flushDirty(256, ACCOUNT_NODE, ledger->mLedgerSeq)) + while(ledger->mAccountStateMap->flushDirty(256, hotACCOUNT_NODE, ledger->mLedgerSeq)) { ; } ledger->disarmDirty(); @@ -416,34 +465,60 @@ void Ledger::addJson(Json::Value& ret, int options) bool full = (options & LEDGER_JSON_FULL) != 0; if(mClosed || full) { + if (mClosed) + ledger["closed"] = true; ledger["hash"] = mHash.GetHex(); ledger["transactionHash"] = mTransHash.GetHex(); ledger["accountHash"] = mAccountHash.GetHex(); - if (mClosed) ledger["closed"] = true; ledger["accepted"] = mAccepted; ledger["totalCoins"] = boost::lexical_cast(mTotCoins); - if ((mCloseFlags & sLCF_NoConsensusTime) != 0) - ledger["closeTimeEstimate"] = mCloseTime; - else + if (mCloseTime != 0) { - ledger["closeTime"] = mCloseTime; - ledger["closeTimeResolution"] = mCloseResolution; + if ((mCloseFlags & sLCF_NoConsensusTime) != 0) + ledger["closeTimeEstimate"] = boost::posix_time::to_simple_string(ptFromSeconds(mCloseTime)); + else + { + ledger["closeTime"] = boost::posix_time::to_simple_string(ptFromSeconds(mCloseTime)); + ledger["closeTimeResolution"] = mCloseResolution; + } } } - else ledger["closed"] = false; - if (mCloseTime != 0) - ledger["closeTime"] = boost::posix_time::to_simple_string(ptFromSeconds(mCloseTime)); + else + ledger["closed"] = false; if (mTransactionMap && (full || ((options & LEDGER_JSON_DUMP_TXNS) != 0))) { Json::Value txns(Json::arrayValue); - for (SHAMapItem::pointer item = mTransactionMap->peekFirstItem(); !!item; - item = mTransactionMap->peekNextItem(item->getTag())) + SHAMapTreeNode::TNType type; + for (SHAMapItem::pointer item = mTransactionMap->peekFirstItem(type); !!item; + item = mTransactionMap->peekNextItem(item->getTag(), type)) { if (full) { - SerializerIterator sit(item->peekSerializer()); - SerializedTransaction txn(sit); - txns.append(txn.getJson(0)); + if (type == SHAMapTreeNode::tnTRANSACTION_NM) + { + SerializerIterator sit(item->peekSerializer()); + SerializedTransaction txn(sit); + txns.append(txn.getJson(0)); + } + else if (type == SHAMapTreeNode::tnTRANSACTION_MD) + { + SerializerIterator sit(item->peekSerializer()); + Serializer sTxn(sit.getVL()); + + SerializerIterator tsit(sTxn); + SerializedTransaction txn(tsit); + + TransactionMetaSet meta(mLedgerSeq, sit.getVL()); + Json::Value txJson = txn.getJson(0); + txJson["metaData"] = meta.getJson(0); + txns.append(txJson); + } + else + { + Json::Value error = Json::objectValue; + error[item->getTag().GetHex()] = type; + txns.append(error); + } } else txns.append(item->getTag().GetHex()); } @@ -503,4 +578,345 @@ void Ledger::setCloseTime(boost::posix_time::ptime ptm) mCloseTime = iToSeconds(ptm); } +// XXX Use shared locks where possible? +LedgerStateParms Ledger::writeBack(LedgerStateParms parms, SLE::ref entry) +{ + ScopedLock l(mAccountStateMap->Lock()); + bool create = false; + + if (!mAccountStateMap->hasItem(entry->getIndex())) + { + if ((parms & lepCREATE) == 0) + { + Log(lsERROR) << "WriteBack non-existent node without create"; + return lepMISSING; + } + create = true; + } + + SHAMapItem::pointer item = boost::make_shared(entry->getIndex()); + entry->add(item->peekSerializer()); + + if (create) + { + assert(!mAccountStateMap->hasItem(entry->getIndex())); + if(!mAccountStateMap->addGiveItem(item, false, false)) // FIXME: TX metadata + { + assert(false); + return lepERROR; + } + return lepCREATED; + } + + if (!mAccountStateMap->updateGiveItem(item, false, false)) // FIXME: TX metadata + { + assert(false); + return lepERROR; + } + return lepOKAY; +} + +SLE::pointer Ledger::getSLE(const uint256& uHash) +{ + SHAMapItem::pointer node = mAccountStateMap->peekItem(uHash); + if (!node) + return SLE::pointer(); + return boost::make_shared(node->peekSerializer(), node->getTag()); +} + +uint256 Ledger::getFirstLedgerIndex() +{ + SHAMapItem::pointer node = mAccountStateMap->peekFirstItem(); + return node ? node->getTag() : uint256(); +} + +uint256 Ledger::getLastLedgerIndex() +{ + SHAMapItem::pointer node = mAccountStateMap->peekLastItem(); + return node ? node->getTag() : uint256(); +} + +uint256 Ledger::getNextLedgerIndex(const uint256& uHash) +{ + SHAMapItem::pointer node = mAccountStateMap->peekNextItem(uHash); + return node ? node->getTag() : uint256(); +} + +uint256 Ledger::getNextLedgerIndex(const uint256& uHash, const uint256& uEnd) +{ + SHAMapItem::pointer node = mAccountStateMap->peekNextItem(uHash); + if ((!node) || (node->getTag() > uEnd)) + return uint256(); + return node->getTag(); +} + +uint256 Ledger::getPrevLedgerIndex(const uint256& uHash) +{ + SHAMapItem::pointer node = mAccountStateMap->peekPrevItem(uHash); + return node ? node->getTag() : uint256(); +} + +uint256 Ledger::getPrevLedgerIndex(const uint256& uHash, const uint256& uBegin) +{ + SHAMapItem::pointer node = mAccountStateMap->peekNextItem(uHash); + if ((!node) || (node->getTag() < uBegin)) + return uint256(); + return node->getTag(); +} + +SLE::pointer Ledger::getASNode(LedgerStateParms& parms, const uint256& nodeID, + LedgerEntryType let ) +{ + SHAMapItem::pointer account = mAccountStateMap->peekItem(nodeID); + + if (!account) + { + if ( (parms & lepCREATE) == 0 ) + { + parms = lepMISSING; + return SLE::pointer(); + } + + parms = parms | lepCREATED | lepOKAY; + SLE::pointer sle=boost::make_shared(let); + sle->setIndex(nodeID); + + return sle; + } + + SLE::pointer sle = + boost::make_shared(account->peekSerializer(), nodeID); + + if (sle->getType() != let) + { // maybe it's a currency or something + parms = parms | lepWRONGTYPE; + return SLE::pointer(); + } + + parms = parms | lepOKAY; + + return sle; +} + +SLE::pointer Ledger::getAccountRoot(const uint160& accountID) +{ + LedgerStateParms qry = lepNONE; + + return getASNode(qry, getAccountRootIndex(accountID), ltACCOUNT_ROOT); +} + +SLE::pointer Ledger::getAccountRoot(const NewcoinAddress& naAccountID) +{ + LedgerStateParms qry = lepNONE; + + return getASNode(qry, getAccountRootIndex(naAccountID.getAccountID()), ltACCOUNT_ROOT); +} + +// +// Directory +// + +SLE::pointer Ledger::getDirNode(LedgerStateParms& parms, const uint256& uNodeIndex) +{ + ScopedLock l(mAccountStateMap->Lock()); + + return getASNode(parms, uNodeIndex, ltDIR_NODE); +} + +// +// Generator Map +// + +SLE::pointer Ledger::getGenerator(LedgerStateParms& parms, const uint160& uGeneratorID) +{ + ScopedLock l(mAccountStateMap->Lock()); + + return getASNode(parms, getGeneratorIndex(uGeneratorID), ltGENERATOR_MAP); +} + +// +// Nickname +// + +SLE::pointer Ledger::getNickname(LedgerStateParms& parms, const uint256& uNickname) +{ + ScopedLock l(mAccountStateMap->Lock()); + + return getASNode(parms, uNickname, ltNICKNAME); +} + +// +// Offer +// + + +SLE::pointer Ledger::getOffer(LedgerStateParms& parms, const uint256& uIndex) +{ + ScopedLock l(mAccountStateMap->Lock()); + + return getASNode(parms, uIndex, ltOFFER); +} + +// +// Ripple State +// + +SLE::pointer Ledger::getRippleState(LedgerStateParms& parms, const uint256& uNode) +{ + ScopedLock l(mAccountStateMap->Lock()); + + return getASNode(parms, uNode, ltRIPPLE_STATE); +} + +// For an entry put in the 64 bit index or quality. +uint256 Ledger::getQualityIndex(const uint256& uBase, const uint64 uNodeDir) +{ + // Indexes are stored in big endian format: they print as hex as stored. + // Most significant bytes are first. Least significant bytes represent adjacent entries. + // We place uNodeDir in the 8 right most bytes to be adjacent. + // Want uNodeDir in big endian format so ++ goes to the next entry for indexes. + uint256 uNode(uBase); + + ((uint64*) uNode.end())[-1] = htobe64(uNodeDir); + + return uNode; +} + +// Return the last 64 bits. +uint64 Ledger::getQuality(const uint256& uBase) +{ + return be64toh(((uint64*) uBase.end())[-1]); +} + +uint256 Ledger::getQualityNext(const uint256& uBase) +{ + static uint256 uNext("10000000000000000"); + + uint256 uResult = uBase; + + uResult += uNext; + + return uResult; +} + +uint256 Ledger::getAccountRootIndex(const uint160& uAccountID) +{ + Serializer s(22); + + s.add16(spaceAccount); // 2 + s.add160(uAccountID); // 20 + + return s.getSHA512Half(); +} + +uint256 Ledger::getBookBase(const uint160& uTakerPaysCurrency, const uint160& uTakerPaysIssuerID, + const uint160& uTakerGetsCurrency, const uint160& uTakerGetsIssuerID) +{ + bool bInNative = uTakerPaysCurrency.isZero(); + bool bOutNative = uTakerGetsCurrency.isZero(); + + assert(!bInNative || !bOutNative); // Stamps to stamps not allowed. + assert(bInNative == uTakerPaysIssuerID.isZero()); // Make sure issuer is specified as needed. + assert(bOutNative == uTakerGetsIssuerID.isZero()); // Make sure issuer is specified as needed. + assert(uTakerPaysCurrency != uTakerGetsCurrency || uTakerPaysIssuerID != uTakerGetsIssuerID); // Currencies or accounts must differ. + + Serializer s(82); + + s.add16(spaceBookDir); // 2 + s.add160(uTakerPaysCurrency); // 20 + s.add160(uTakerGetsCurrency); // 20 + s.add160(uTakerPaysIssuerID); // 20 + s.add160(uTakerGetsIssuerID); // 20 + + uint256 uBaseIndex = getQualityIndex(s.getSHA512Half()); // Return with quality 0. + + Log(lsINFO) << str(boost::format("getBookBase(%s,%s,%s,%s) = %s") + % STAmount::createHumanCurrency(uTakerPaysCurrency) + % NewcoinAddress::createHumanAccountID(uTakerPaysIssuerID) + % STAmount::createHumanCurrency(uTakerGetsCurrency) + % NewcoinAddress::createHumanAccountID(uTakerGetsIssuerID) + % uBaseIndex.ToString()); + + return uBaseIndex; +} + +uint256 Ledger::getDirNodeIndex(const uint256& uDirRoot, const uint64 uNodeIndex) +{ + if (uNodeIndex) + { + Serializer s(42); + + s.add16(spaceDirNode); // 2 + s.add256(uDirRoot); // 32 + s.add64(uNodeIndex); // 8 + + return s.getSHA512Half(); + } + else + { + return uDirRoot; + } +} + +uint256 Ledger::getGeneratorIndex(const uint160& uGeneratorID) +{ + Serializer s(22); + + s.add16(spaceGenerator); // 2 + s.add160(uGeneratorID); // 20 + + return s.getSHA512Half(); +} + +// What is important: +// --> uNickname: is a Sha256 +// <-- SHA512/2: for consistency and speed in generating indexes. +uint256 Ledger::getNicknameIndex(const uint256& uNickname) +{ + Serializer s(34); + + s.add16(spaceNickname); // 2 + s.add256(uNickname); // 32 + + return s.getSHA512Half(); +} + +uint256 Ledger::getOfferIndex(const uint160& uAccountID, uint32 uSequence) +{ + Serializer s(26); + + s.add16(spaceOffer); // 2 + s.add160(uAccountID); // 20 + s.add32(uSequence); // 4 + + return s.getSHA512Half(); +} + +uint256 Ledger::getOwnerDirIndex(const uint160& uAccountID) +{ + Serializer s(22); + + s.add16(spaceOwnerDir); // 2 + s.add160(uAccountID); // 20 + + return s.getSHA512Half(); +} + +uint256 Ledger::getRippleStateIndex(const NewcoinAddress& naA, const NewcoinAddress& naB, const uint160& uCurrency) +{ + uint160 uAID = naA.getAccountID(); + uint160 uBID = naB.getAccountID(); + bool bAltB = uAID < uBID; + Serializer s(62); + + s.add16(spaceRipple); // 2 + s.add160(bAltB ? uAID : uBID); // 20 + s.add160(bAltB ? uBID : uAID); // 20 + s.add160(uCurrency); // 20 + + return s.getSHA512Half(); +} + + + // vim:ts=4 diff --git a/src/Ledger.h b/src/Ledger.h index f830124e22..d059f01581 100644 --- a/src/Ledger.h +++ b/src/Ledger.h @@ -11,6 +11,7 @@ #include "../json/value.h" #include "Transaction.h" +#include "TransactionMeta.h" #include "AccountState.h" #include "RippleState.h" #include "NicknameState.h" @@ -41,7 +42,8 @@ class Ledger : public boost::enable_shared_from_this { // The basic Ledger structure, can be opened, closed, or synching friend class TransactionEngine; public: - typedef boost::shared_ptr pointer; + typedef boost::shared_ptr pointer; + typedef const boost::shared_ptr& ref; enum TransResult { @@ -55,7 +57,7 @@ public: TR_PASTASEQ = 6, // account is past this transaction TR_PREASEQ = 7, // account is missing transactions before this TR_BADLSEQ = 8, // ledger too early - TR_TOOSMALL = 9, // amount is less than Tx fee + TR_TOOSMALL = 9, // amount is less than Tx fee }; // ledger close flags @@ -81,8 +83,6 @@ private: protected: - bool addTransaction(Transaction::pointer); - bool addTransaction(const uint256& id, const Serializer& txn); static Ledger::pointer getSQL(const std::string& sqlStatement); @@ -115,7 +115,7 @@ public: void disarmDirty() { mTransactionMap->disarmDirty(); mAccountStateMap->disarmDirty(); } // This ledger has closed, will never be accepted, and is accepting - // new transactions to be re-repocessed when do accept a new last-closed ledger + // new transactions to be re-reprocessed when do accept a new last-closed ledger void bumpSeq() { mClosed = true; mLedgerSeq++; } // ledger signature operations @@ -151,17 +151,20 @@ public: bool isAcquiringAS(void); // Transaction Functions + bool addTransaction(const uint256& id, const Serializer& txn); + bool addTransaction(const uint256& id, const Serializer& txn, const Serializer& metaData); bool hasTransaction(const uint256& TransID) const { return mTransactionMap->hasItem(TransID); } Transaction::pointer getTransaction(const uint256& transID) const; + bool getTransaction(const uint256& transID, Transaction::pointer& txn, TransactionMetaSet::pointer& txMeta); // high-level functions AccountState::pointer getAccountState(const NewcoinAddress& acctID); - LedgerStateParms writeBack(LedgerStateParms parms, SLE::pointer); + LedgerStateParms writeBack(LedgerStateParms parms, SLE::ref); SLE::pointer getAccountRoot(const uint160& accountID); SLE::pointer getAccountRoot(const NewcoinAddress& naAccountID); // database functions - static void saveAcceptedLedger(Ledger::pointer); + static void saveAcceptedLedger(Ledger::ref); static Ledger::pointer loadByIndex(uint32 ledgerIndex); static Ledger::pointer loadByHash(const uint256& ledgerHash); @@ -260,14 +263,11 @@ public: // Ripple functions : credit lines // - // Index of node which is the ripple state between to accounts for a currency. + // Index of node which is the ripple state between two accounts for a currency. static uint256 getRippleStateIndex(const NewcoinAddress& naA, const NewcoinAddress& naB, const uint160& uCurrency); static uint256 getRippleStateIndex(const uint160& uiA, const uint160& uiB, const uint160& uCurrency) { return getRippleStateIndex(NewcoinAddress::createAccountID(uiA), NewcoinAddress::createAccountID(uiB), uCurrency); } - // Directory of lines indexed by an account (not all lines are indexed) - static uint256 getRippleDirIndex(const uint160& uAccountID); - RippleState::pointer accessRippleState(const uint256& uNode); SLE::pointer getRippleState(LedgerStateParms& parms, const uint256& uNode); @@ -284,12 +284,6 @@ public: SLE::pointer getRippleState(const uint160& uiA, const uint160& uiB, const uint160& uCurrency) { return getRippleState(getRippleStateIndex(NewcoinAddress::createAccountID(uiA), NewcoinAddress::createAccountID(uiB), uCurrency)); } - // - // Misc - // - bool isCompatible(boost::shared_ptr other); -// bool signLedger(std::vector &signature, const LocalHanko &hanko); - void addJson(Json::Value&, int options); static bool unitTest(); diff --git a/src/LedgerAcquire.cpp b/src/LedgerAcquire.cpp index 16cc1abe4f..76a43e7276 100644 --- a/src/LedgerAcquire.cpp +++ b/src/LedgerAcquire.cpp @@ -11,14 +11,16 @@ #include "HashPrefixes.h" // #define LA_DEBUG -#define LEDGER_ACQUIRE_TIMEOUT 2 +#define LEDGER_ACQUIRE_TIMEOUT 750 #define TRUST_NETWORK PeerSet::PeerSet(const uint256& hash, int interval) : mHash(hash), mTimerInterval(interval), mTimeouts(0), mComplete(false), mFailed(false), mProgress(true), mTimer(theApp->getIOService()) -{ ; } +{ + assert((mTimerInterval > 10) && (mTimerInterval < 30000)); +} -void PeerSet::peerHas(Peer::pointer ptr) +void PeerSet::peerHas(Peer::ref ptr) { boost::recursive_mutex::scoped_lock sl(mLock); std::vector< boost::weak_ptr >::iterator it = mPeers.begin(); @@ -38,7 +40,7 @@ void PeerSet::peerHas(Peer::pointer ptr) newPeer(ptr); } -void PeerSet::badPeer(Peer::pointer ptr) +void PeerSet::badPeer(Peer::ref ptr) { boost::recursive_mutex::scoped_lock sl(mLock); std::vector< boost::weak_ptr >::iterator it = mPeers.begin(); @@ -61,7 +63,7 @@ void PeerSet::badPeer(Peer::pointer ptr) void PeerSet::resetTimer() { - mTimer.expires_from_now(boost::posix_time::seconds(mTimerInterval)); + mTimer.expires_from_now(boost::posix_time::milliseconds(mTimerInterval)); mTimer.async_wait(boost::bind(&PeerSet::TimerEntry, pmDowncast(), boost::asio::placeholders::error)); } @@ -70,7 +72,7 @@ void PeerSet::invokeOnTimer() if (!mProgress) { ++mTimeouts; - Log(lsWARNING) << "Timeout " << mTimeouts << " acquiring " << mHash.GetHex(); + Log(lsWARNING) << "Timeout " << mTimeouts << " acquiring " << mHash; } else mProgress = false; @@ -82,16 +84,15 @@ void PeerSet::TimerEntry(boost::weak_ptr wptr, const boost::system::err if (result == boost::asio::error::operation_aborted) return; boost::shared_ptr ptr = wptr.lock(); - if (!ptr) - return; - ptr->invokeOnTimer(); + if (ptr) + ptr->invokeOnTimer(); } LedgerAcquire::LedgerAcquire(const uint256& hash) : PeerSet(hash, LEDGER_ACQUIRE_TIMEOUT), mHaveBase(false), mHaveState(false), mHaveTransactions(false), mAborted(false), mSignaled(false) { #ifdef LA_DEBUG - Log(lsTRACE) << "Acquiring ledger " << mHash.GetHex(); + Log(lsTRACE) << "Acquiring ledger " << mHash; #endif } @@ -102,12 +103,12 @@ void LedgerAcquire::onTimer() setFailed(); done(); } - else trigger(Peer::pointer()); + else trigger(Peer::pointer(), true); } boost::weak_ptr LedgerAcquire::pmDowncast() { - return boost::shared_polymorphic_downcast(shared_from_this()); + return boost::shared_polymorphic_downcast(shared_from_this()); } void LedgerAcquire::done() @@ -116,7 +117,7 @@ void LedgerAcquire::done() return; mSignaled = true; #ifdef LA_DEBUG - Log(lsTRACE) << "Done acquiring ledger " << mHash.GetHex(); + Log(lsTRACE) << "Done acquiring ledger " << mHash; #endif std::vector< boost::function > triggers; @@ -129,7 +130,7 @@ void LedgerAcquire::done() if (mLedger) theApp->getMasterLedger().storeLedger(mLedger); - for (int i = 0; i < triggers.size(); ++i) + for (unsigned int i = 0; i < triggers.size(); ++i) triggers[i](shared_from_this()); } @@ -140,31 +141,25 @@ void LedgerAcquire::addOnComplete(boost::function mLock.unlock(); } -void LedgerAcquire::trigger(Peer::pointer peer) +void LedgerAcquire::trigger(Peer::ref peer, bool timer) { if (mAborted || mComplete || mFailed) return; #ifdef LA_DEBUG - if(peer) Log(lsTRACE) << "Trigger acquiring ledger " << mHash.GetHex() << " from " << peer->getIP(); - else Log(lsTRACE) << "Trigger acquiring ledger " << mHash.GetHex(); - Log(lsTRACE) << "complete=" << mComplete << " failed=" << mFailed; - Log(lsTRACE) << "base=" << mHaveBase << " tx=" << mHaveTransactions << " as=" << mHaveState; + if(peer) Log(lsTRACE) << "Trigger acquiring ledger " << mHash << " from " << peer->getIP(); + else Log(lsTRACE) << "Trigger acquiring ledger " << mHash; + if (mComplete || mFailed) + Log(lsTRACE) << "complete=" << mComplete << " failed=" << mFailed; + else + Log(lsTRACE) << "base=" << mHaveBase << " tx=" << mHaveTransactions << " as=" << mHaveState; #endif if (!mHaveBase) { -#ifdef LA_DEBUG - Log(lsTRACE) << "need base"; -#endif newcoin::TMGetLedger tmGL; tmGL.set_ledgerhash(mHash.begin(), mHash.size()); tmGL.set_itype(newcoin::liBASE); *(tmGL.add_nodeids()) = SHAMapNode().getRawString(); - if (peer) - { - sendRequest(tmGL, peer); - return; - } - else sendRequest(tmGL); + sendRequest(tmGL, peer); } if (mHaveBase && !mHaveTransactions) @@ -180,12 +175,7 @@ void LedgerAcquire::trigger(Peer::pointer peer) tmGL.set_ledgerseq(mLedger->getLedgerSeq()); tmGL.set_itype(newcoin::liTX_NODE); *(tmGL.add_nodeids()) = SHAMapNode().getRawString(); - if (peer) - { - sendRequest(tmGL, peer); - return; - } - sendRequest(tmGL); + sendRequest(tmGL, peer); } else { @@ -199,7 +189,8 @@ void LedgerAcquire::trigger(Peer::pointer peer) else { mHaveTransactions = true; - if (mHaveState) mComplete = true; + if (mHaveState) + mComplete = true; } } else @@ -210,12 +201,7 @@ void LedgerAcquire::trigger(Peer::pointer peer) tmGL.set_itype(newcoin::liTX_NODE); for (std::vector::iterator it = nodeIDs.begin(); it != nodeIDs.end(); ++it) *(tmGL.add_nodeids()) = it->getRawString(); - if (peer) - { - sendRequest(tmGL, peer); - return; - } - sendRequest(tmGL); + sendRequest(tmGL, peer); } } } @@ -233,12 +219,7 @@ void LedgerAcquire::trigger(Peer::pointer peer) tmGL.set_ledgerseq(mLedger->getLedgerSeq()); tmGL.set_itype(newcoin::liAS_NODE); *(tmGL.add_nodeids()) = SHAMapNode().getRawString(); - if (peer) - { - sendRequest(tmGL, peer); - return; - } - sendRequest(tmGL); + sendRequest(tmGL, peer); } else { @@ -252,7 +233,8 @@ void LedgerAcquire::trigger(Peer::pointer peer) else { mHaveState = true; - if (mHaveTransactions) mComplete = true; + if (mHaveTransactions) + mComplete = true; } } else @@ -263,31 +245,30 @@ void LedgerAcquire::trigger(Peer::pointer peer) tmGL.set_itype(newcoin::liAS_NODE); for (std::vector::iterator it = nodeIDs.begin(); it != nodeIDs.end(); ++it) *(tmGL.add_nodeids()) = it->getRawString(); - if (peer) - { - sendRequest(tmGL, peer); - return; - } - sendRequest(tmGL); + sendRequest(tmGL, peer); } } } if (mComplete || mFailed) done(); - else + else if (timer) resetTimer(); } -void PeerSet::sendRequest(const newcoin::TMGetLedger& tmGL, Peer::pointer peer) +void PeerSet::sendRequest(const newcoin::TMGetLedger& tmGL, Peer::ref peer) { - peer->sendPacket(boost::make_shared(tmGL, newcoin::mtGET_LEDGER)); + if (!peer) + sendRequest(tmGL); + else + peer->sendPacket(boost::make_shared(tmGL, newcoin::mtGET_LEDGER)); } void PeerSet::sendRequest(const newcoin::TMGetLedger& tmGL) { boost::recursive_mutex::scoped_lock sl(mLock); - if (mPeers.empty()) return; + if (mPeers.empty()) + return; PackedMessage::pointer packet = boost::make_shared(tmGL, newcoin::mtGET_LEDGER); @@ -309,7 +290,7 @@ void PeerSet::sendRequest(const newcoin::TMGetLedger& tmGL) bool LedgerAcquire::takeBase(const std::string& data) { // Return value: true=normal, false=bad data #ifdef LA_DEBUG - Log(lsTRACE) << "got base acquiring ledger " << mHash.GetHex(); + Log(lsTRACE) << "got base acquiring ledger " << mHash; #endif boost::recursive_mutex::scoped_lock sl(mLock); if (mHaveBase) return true; @@ -317,7 +298,7 @@ bool LedgerAcquire::takeBase(const std::string& data) if (mLedger->getHash() != mHash) { Log(lsWARNING) << "Acquire hash mismatch"; - Log(lsWARNING) << mLedger->getHash().GetHex() << "!=" << mHash.GetHex(); + Log(lsWARNING) << mLedger->getHash() << "!=" << mHash; mLedger = Ledger::pointer(); #ifdef TRUST_NETWORK assert(false); @@ -329,11 +310,13 @@ bool LedgerAcquire::takeBase(const std::string& data) Serializer s(data.size() + 4); s.add32(sHP_Ledger); s.addRaw(data); - theApp->getHashedObjectStore().store(LEDGER, mLedger->getLedgerSeq(), s.peekData(), mHash); + theApp->getHashedObjectStore().store(hotLEDGER, mLedger->getLedgerSeq(), s.peekData(), mHash); progress(); - if (!mLedger->getTransHash()) mHaveTransactions = true; - if (!mLedger->getAccountHash()) mHaveState = true; + if (!mLedger->getTransHash()) + mHaveTransactions = true; + if (!mLedger->getAccountHash()) + mHaveState = true; mLedger->setAcquiring(); return true; } @@ -349,7 +332,7 @@ bool LedgerAcquire::takeTxNode(const std::list& nodeIDs, { if (nodeIDit->isRoot()) { - if (!mLedger->peekTransactionMap()->addRootNode(mLedger->getTransHash(), *nodeDatait, STN_ARF_WIRE)) + if (!mLedger->peekTransactionMap()->addRootNode(mLedger->getTransHash(), *nodeDatait, snfWIRE)) return false; } else if (!mLedger->peekTransactionMap()->addKnownNode(*nodeIDit, *nodeDatait, &tFilter)) @@ -374,7 +357,7 @@ bool LedgerAcquire::takeAsNode(const std::list& nodeIDs, const std::list< std::vector >& data) { #ifdef LA_DEBUG - Log(lsTRACE) << "got ASdata acquiring ledger " << mHash.GetHex(); + Log(lsTRACE) << "got ASdata acquiring ledger " << mHash; #endif if (!mHaveBase) return false; std::list::const_iterator nodeIDit = nodeIDs.begin(); @@ -384,7 +367,7 @@ bool LedgerAcquire::takeAsNode(const std::list& nodeIDs, { if (nodeIDit->isRoot()) { - if (!mLedger->peekAccountStateMap()->addRootNode(mLedger->getAccountHash(), *nodeDatait, STN_ARF_WIRE)) + if (!mLedger->peekAccountStateMap()->addRootNode(mLedger->getAccountHash(), *nodeDatait, snfWIRE)) return false; } else if (!mLedger->peekAccountStateMap()->addKnownNode(*nodeIDit, *nodeDatait, &tFilter)) @@ -408,13 +391,13 @@ bool LedgerAcquire::takeAsNode(const std::list& nodeIDs, bool LedgerAcquire::takeAsRootNode(const std::vector& data) { if (!mHaveBase) return false; - return mLedger->peekAccountStateMap()->addRootNode(mLedger->getAccountHash(), data, STN_ARF_WIRE); + return mLedger->peekAccountStateMap()->addRootNode(mLedger->getAccountHash(), data, snfWIRE); } bool LedgerAcquire::takeTxRootNode(const std::vector& data) { if (!mHaveBase) return false; - return mLedger->peekTransactionMap()->addRootNode(mLedger->getTransHash(), data, STN_ARF_WIRE); + return mLedger->peekTransactionMap()->addRootNode(mLedger->getTransHash(), data, snfWIRE); } LedgerAcquire::pointer LedgerAcquireMaster::findCreate(const uint256& hash) @@ -422,7 +405,8 @@ LedgerAcquire::pointer LedgerAcquireMaster::findCreate(const uint256& hash) assert(hash.isNonZero()); boost::mutex::scoped_lock sl(mLock); LedgerAcquire::pointer& ptr = mLedgers[hash]; - if (ptr) return ptr; + if (ptr) + return ptr; ptr = boost::make_shared(hash); assert(mLedgers[hash] == ptr); ptr->resetTimer(); // Cannot call in constructor @@ -434,7 +418,8 @@ LedgerAcquire::pointer LedgerAcquireMaster::find(const uint256& hash) assert(hash.isNonZero()); boost::mutex::scoped_lock sl(mLock); std::map::iterator it = mLedgers.find(hash); - if (it != mLedgers.end()) return it->second; + if (it != mLedgers.end()) + return it->second; return LedgerAcquire::pointer(); } @@ -448,11 +433,11 @@ bool LedgerAcquireMaster::hasLedger(const uint256& hash) void LedgerAcquireMaster::dropLedger(const uint256& hash) { assert(hash.isNonZero()); - boost::mutex::scoped_lock sl(mLock); + boost::mutex::scoped_lock sl(mLock); mLedgers.erase(hash); } -bool LedgerAcquireMaster::gotLedgerData(newcoin::TMLedgerData& packet, Peer::pointer peer) +bool LedgerAcquireMaster::gotLedgerData(newcoin::TMLedgerData& packet, Peer::ref peer) { #ifdef LA_DEBUG Log(lsTRACE) << "got data for acquiring ledger "; @@ -465,7 +450,7 @@ bool LedgerAcquireMaster::gotLedgerData(newcoin::TMLedgerData& packet, Peer::poi } memcpy(hash.begin(), packet.ledgerhash().data(), 32); #ifdef LA_DEBUG - Log(lsTRACE) << hash.GetHex(); + Log(lsTRACE) << hash; #endif LedgerAcquire::pointer ledger = find(hash); @@ -480,7 +465,7 @@ bool LedgerAcquireMaster::gotLedgerData(newcoin::TMLedgerData& packet, Peer::poi return false; if (packet.nodes_size() == 1) { - ledger->trigger(peer); + ledger->trigger(peer, false); return true; } if (!ledger->takeAsRootNode(strCopy(packet.nodes(1).nodedata()))) @@ -489,12 +474,12 @@ bool LedgerAcquireMaster::gotLedgerData(newcoin::TMLedgerData& packet, Peer::poi } if (packet.nodes().size() == 2) { - ledger->trigger(peer); + ledger->trigger(peer, false); return true; } if (!ledger->takeTxRootNode(strCopy(packet.nodes(2).nodedata()))) Log(lsWARNING) << "Invcluded TXbase invalid"; - ledger->trigger(peer); + ledger->trigger(peer, false); return true; } @@ -518,7 +503,7 @@ bool LedgerAcquireMaster::gotLedgerData(newcoin::TMLedgerData& packet, Peer::poi else ret = ledger->takeAsNode(nodeIDs, nodeData); if (ret) - ledger->trigger(peer); + ledger->trigger(peer, false); return ret; } diff --git a/src/LedgerAcquire.h b/src/LedgerAcquire.h index fbcac6c7e3..b589618299 100644 --- a/src/LedgerAcquire.h +++ b/src/LedgerAcquire.h @@ -31,7 +31,7 @@ protected: virtual ~PeerSet() { ; } void sendRequest(const newcoin::TMGetLedger& message); - void sendRequest(const newcoin::TMGetLedger& message, Peer::pointer peer); + void sendRequest(const newcoin::TMGetLedger& message, Peer::ref peer); public: const uint256& getHash() const { return mHash; } @@ -41,12 +41,12 @@ public: void progress() { mProgress = true; } - void peerHas(Peer::pointer); - void badPeer(Peer::pointer); + void peerHas(Peer::ref); + void badPeer(Peer::ref); void resetTimer(); protected: - virtual void newPeer(Peer::pointer) = 0; + virtual void newPeer(Peer::ref) = 0; virtual void onTimer(void) = 0; virtual boost::weak_ptr pmDowncast() = 0; @@ -72,12 +72,13 @@ protected: void done(); void onTimer(); - void newPeer(Peer::pointer peer) { trigger(peer); } + void newPeer(Peer::ref peer) { trigger(peer, false); } boost::weak_ptr pmDowncast(); public: LedgerAcquire(const uint256& hash); + virtual ~LedgerAcquire() { ; } bool isBase() const { return mHaveBase; } bool isAcctStComplete() const { return mHaveState; } @@ -92,7 +93,7 @@ public: bool takeTxRootNode(const std::vector& data); bool takeAsNode(const std::list& IDs, const std::list >& data); bool takeAsRootNode(const std::vector& data); - void trigger(Peer::pointer); + void trigger(Peer::ref, bool timer); }; class LedgerAcquireMaster @@ -108,7 +109,7 @@ public: LedgerAcquire::pointer find(const uint256& hash); bool hasLedger(const uint256& ledgerHash); void dropLedger(const uint256& ledgerHash); - bool gotLedgerData(newcoin::TMLedgerData& packet, Peer::pointer); + bool gotLedgerData(newcoin::TMLedgerData& packet, Peer::ref); }; #endif diff --git a/src/LedgerConsensus.cpp b/src/LedgerConsensus.cpp index 3b0d9b41e9..b96589212a 100644 --- a/src/LedgerConsensus.cpp +++ b/src/LedgerConsensus.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "../json/writer.h" @@ -13,44 +14,59 @@ #include "Log.h" #include "SHAMapSync.h" +#define TX_ACQUIRE_TIMEOUT 250 + #define TRUST_NETWORK // #define LC_DEBUG -TransactionAcquire::TransactionAcquire(const uint256& hash) : PeerSet(hash, 1), mHaveRoot(false) +typedef std::pair u160_prop_pair; +typedef std::pair u256_lct_pair; + +TransactionAcquire::TransactionAcquire(const uint256& hash) : PeerSet(hash, TX_ACQUIRE_TIMEOUT), mHaveRoot(false) { - mMap = boost::make_shared(); - mMap->setSynching(); + mMap = boost::make_shared(hash); } void TransactionAcquire::done() { if (mFailed) + { + Log(lsWARNING) << "Failed to acquire TXs " << mHash; theApp->getOPs().mapComplete(mHash, SHAMap::pointer()); + } else + { + mMap->setImmutable(); theApp->getOPs().mapComplete(mHash, mMap); + } } boost::weak_ptr TransactionAcquire::pmDowncast() { - return boost::shared_polymorphic_downcast(shared_from_this()); + return boost::shared_polymorphic_downcast(shared_from_this()); } -void TransactionAcquire::trigger(Peer::pointer peer) +void TransactionAcquire::trigger(Peer::ref peer, bool timer) { if (mComplete || mFailed) + { + Log(lsINFO) << "complete or failed"; return; + } if (!mHaveRoot) { + Log(lsINFO) << "have no root"; newcoin::TMGetLedger tmGL; tmGL.set_ledgerhash(mHash.begin(), mHash.size()); tmGL.set_itype(newcoin::liTS_CANDIDATE); *(tmGL.add_nodeids()) = SHAMapNode().getRawString(); sendRequest(tmGL, peer); } - if (mHaveRoot) + else { - std::vector nodeIDs; std::vector nodeHashes; + std::vector nodeIDs; + std::vector nodeHashes; ConsensusTransSetSF sf; mMap->getMissingNodes(nodeIDs, nodeHashes, 256, &sf); if (nodeIDs.empty()) @@ -59,6 +75,8 @@ void TransactionAcquire::trigger(Peer::pointer peer) mComplete = true; else mFailed = true; + done(); + return; } else { @@ -67,21 +85,15 @@ void TransactionAcquire::trigger(Peer::pointer peer) tmGL.set_itype(newcoin::liTS_CANDIDATE); for (std::vector::iterator it = nodeIDs.begin(); it != nodeIDs.end(); ++it) *(tmGL.add_nodeids()) = it->getRawString(); - if (peer) - sendRequest(tmGL, peer); - else - sendRequest(tmGL); - return; + sendRequest(tmGL, peer); } } - if (mComplete || mFailed) - done(); - else + if (timer) resetTimer(); } bool TransactionAcquire::takeNodes(const std::list& nodeIDs, - const std::list< std::vector >& data, Peer::pointer peer) + const std::list< std::vector >& data, Peer::ref peer) { if (mComplete) return true; @@ -101,16 +113,17 @@ bool TransactionAcquire::takeNodes(const std::list& nodeIDs, Log(lsWARNING) << "Got root TXS node, already have it"; return false; } - if (!mMap->addRootNode(getHash(), *nodeDatait, STN_ARF_WIRE)) + if (!mMap->addRootNode(getHash(), *nodeDatait, snfWIRE)) return false; - else mHaveRoot = true; + else + mHaveRoot = true; } else if (!mMap->addKnownNode(*nodeIDit, *nodeDatait, &sf)) return false; ++nodeIDit; ++nodeDatait; } - trigger(peer); + trigger(peer, false); progress(); return true; } @@ -122,7 +135,7 @@ bool TransactionAcquire::takeNodes(const std::list& nodeIDs, } void LCTransaction::setVote(const uint160& peer, bool votesYes) -{ +{ // Tracke a peer's yes/no vote on a particular disputed transaction std::pair::iterator, bool> res = mVotes.insert(std::make_pair(peer, votesYes)); @@ -130,72 +143,89 @@ void LCTransaction::setVote(const uint160& peer, bool votesYes) { // new vote if (votesYes) { - Log(lsTRACE) << "Peer " << peer.GetHex() << " votes YES on " << mTransactionID.GetHex(); + Log(lsTRACE) << "Peer " << peer << " votes YES on " << mTransactionID; ++mYays; } else { - Log(lsTRACE) << "Peer " << peer.GetHex() << " votes NO on " << mTransactionID.GetHex(); + Log(lsTRACE) << "Peer " << peer << " votes NO on " << mTransactionID; ++mNays; } } else if (votesYes && !res.first->second) { // changes vote to yes - Log(lsTRACE) << "Peer " << peer.GetHex() << " now votes YES on " << mTransactionID.GetHex(); + Log(lsTRACE) << "Peer " << peer << " now votes YES on " << mTransactionID; --mNays; ++mYays; res.first->second = true; } - else if(!votesYes && res.first->second) + else if (!votesYes && res.first->second) { // changes vote to no - Log(lsTRACE) << "Peer " << peer.GetHex() << " now votes NO on " << mTransactionID.GetHex(); + Log(lsTRACE) << "Peer " << peer << " now votes NO on " << mTransactionID; ++mNays; --mYays; res.first->second = false; } } -bool LCTransaction::updatePosition(int percentTime, bool proposing) -{ // this many seconds after close, should our position change - if (mOurPosition && (mNays == 0)) +void LCTransaction::unVote(const uint160& peer) +{ // Remove a peer's vote on this disputed transasction + boost::unordered_map::iterator it = mVotes.find(peer); + if (it != mVotes.end()) + { + if (it->second) + --mYays; + else + --mNays; + mVotes.erase(it); + } +} + +bool LCTransaction::updateVote(int percentTime, bool proposing) +{ + if (mOurVote && (mNays == 0)) return false; - if (!mOurPosition && (mYays == 0)) + if (!mOurVote && (mYays == 0)) return false; bool newPosition; + int weight; if (proposing) // give ourselves full weight { // This is basically the percentage of nodes voting 'yes' (including us) - int weight = (mYays * 100 + (mOurPosition ? 100 : 0)) / (mNays + mYays + 1); + weight = (mYays * 100 + (mOurVote ? 100 : 0)) / (mNays + mYays + 1); // To prevent avalanche stalls, we increase the needed weight slightly over time if (percentTime < AV_MID_CONSENSUS_TIME) newPosition = weight > AV_INIT_CONSENSUS_PCT; else if (percentTime < AV_LATE_CONSENSUS_TIME) newPosition = weight > AV_MID_CONSENSUS_PCT; else newPosition = weight > AV_LATE_CONSENSUS_PCT; } - else // don't let us outweight a proposing node, just recognize consensus + else // don't let us outweigh a proposing node, just recognize consensus + { + weight = -1; newPosition = mYays > mNays; + } - if (newPosition == mOurPosition) + if (newPosition == mOurVote) { #ifdef LC_DEBUG - Log(lsTRACE) << "No change (" << (mOurPosition ? "YES" : "NO") << ") : weight " + Log(lsTRACE) << "No change (" << (mOurVote ? "YES" : "NO") << ") : weight " << weight << ", percent " << percentTime; #endif return false; } - mOurPosition = newPosition; - Log(lsTRACE) << "We now vote " << (mOurPosition ? "YES" : "NO") << " on " << mTransactionID.GetHex(); + mOurVote = newPosition; + Log(lsTRACE) << "We now vote " << (mOurVote ? "YES" : "NO") << " on " << mTransactionID; return true; } -LedgerConsensus::LedgerConsensus(const uint256& prevLCLHash, Ledger::pointer previousLedger, uint32 closeTime) - : mState(lcsPRE_CLOSE), mCloseTime(closeTime), mPrevLedgerHash(prevLCLHash), mPreviousLedger(previousLedger), - mCurrentMSeconds(0), mClosePercent(0), mHaveCloseTimeConsensus(false) +LedgerConsensus::LedgerConsensus(const uint256& prevLCLHash, Ledger::ref previousLedger, uint32 closeTime) + : mState(lcsPRE_CLOSE), mCloseTime(closeTime), mPrevLedgerHash(prevLCLHash), mPreviousLedger(previousLedger), + mValSeed(theConfig.VALIDATION_SEED), mCurrentMSeconds(0), mClosePercent(0), mHaveCloseTimeConsensus(false), + mConsensusStartTime(boost::posix_time::microsec_clock::universal_time()) { - mValSeed = theConfig.VALIDATION_SEED; Log(lsDEBUG) << "Creating consensus object"; - Log(lsTRACE) << "LCL:" << previousLedger->getHash().GetHex() <<", ct=" << closeTime; + Log(lsTRACE) << "LCL:" << previousLedger->getHash() <<", ct=" << closeTime; mPreviousProposers = theApp->getOPs().getPreviousProposers(); mPreviousMSeconds = theApp->getOPs().getPreviousConvergeTime(); assert(mPreviousMSeconds); @@ -203,155 +233,231 @@ LedgerConsensus::LedgerConsensus(const uint256& prevLCLHash, Ledger::pointer pre mCloseResolution = ContinuousLedgerTiming::getNextLedgerTimeResolution( mPreviousLedger->getCloseResolution(), mPreviousLedger->getCloseAgree(), previousLedger->getLedgerSeq() + 1); - mHaveCorrectLCL = previousLedger->getHash() == prevLCLHash; - - if (!mHaveCorrectLCL) - { - Log(lsINFO) << "Entering consensus with: " << previousLedger->getHash().GetHex(); - Log(lsINFO) << "Correct LCL is: " << prevLCLHash.GetHex(); - mHaveCorrectLCL = mProposing = mValidating = false; - mAcquiringLedger = theApp->getMasterLedgerAcquire().findCreate(prevLCLHash); - std::vector peerList = theApp->getConnectionPool().getPeerVector(); - for (std::vector::const_iterator it = peerList.begin(), end = peerList.end(); it != end; ++it) - if ((*it)->hasLedger(prevLCLHash)) - mAcquiringLedger->peerHas(*it); - } - else if (mValSeed.isValid()) + if (mValSeed.isValid()) { Log(lsINFO) << "Entering consensus process, validating"; - mHaveCorrectLCL = mValidating = true; + mValidating = true; mProposing = theApp->getOPs().getOperatingMode() == NetworkOPs::omFULL; } else { - Log(lsINFO) << "Entering consensus process, proposing"; - mHaveCorrectLCL = true; + Log(lsINFO) << "Entering consensus process, watching"; mProposing = mValidating = false; } + + handleLCL(prevLCLHash); + if (!mHaveCorrectLCL) + { + Log(lsINFO) << "Entering consensus with: " << previousLedger->getHash(); + Log(lsINFO) << "Correct LCL is: " << prevLCLHash; + } } void LedgerConsensus::checkLCL() { uint256 netLgr = mPrevLedgerHash; int netLgrCount = 0; - { - boost::unordered_map vals = theApp->getValidations().getCurrentValidations(); - for (boost::unordered_map::iterator it = vals.begin(), end = vals.end(); it != end; ++it) - if ((it->second > netLgrCount) && !theApp->getValidations().isDeadLedger(it->first)) - { - netLgr = it->first; - netLgrCount = it->second; - } - } + + uint256 favoredLedger = (mState == lcsPRE_CLOSE) ? uint256() : mPrevLedgerHash; // Don't get stuck one ledger behind + boost::unordered_map vals = + theApp->getValidations().getCurrentValidations(favoredLedger); + + typedef std::pair u256_int_pair; + BOOST_FOREACH(u256_int_pair& it, vals) + if (it.second > netLgrCount) + { + netLgr = it.first; + netLgrCount = it.second; + } + if (netLgr != mPrevLedgerHash) { // LCL change - Log(lsWARNING) << "View of consensus changed during consensus (" << netLgrCount << ")"; - mPrevLedgerHash = netLgr; + const char *status; + switch (mState) + { + case lcsPRE_CLOSE: status = "PreClose"; break; + case lcsESTABLISH: status = "Establish"; break; + case lcsFINISHED: status = "Finised"; break; + case lcsACCEPTED: status = "Accepted"; break; + default: status = "unknown"; + } + + Log(lsWARNING) << "View of consensus changed during consensus (" << netLgrCount << ") status=" + << status << ", " << (mHaveCorrectLCL ? "CorrectLCL" : "IncorrectLCL"); + Log(lsWARNING) << mPrevLedgerHash << " to " << netLgr; + +#ifdef DEBUG + BOOST_FOREACH(u256_int_pair& it, vals) + Log(lsDEBUG) << "V: " << it.first << ", " << it.second; +#endif + + if (mHaveCorrectLCL) + theApp->getOPs().consensusViewChange(); + handleLCL(netLgr); + } +} + +void LedgerConsensus::handleLCL(const uint256& lclHash) +{ + mPrevLedgerHash = lclHash; + if (mPreviousLedger->getHash() == mPrevLedgerHash) + return; + + Ledger::pointer newLCL = theApp->getMasterLedger().getLedgerByHash(lclHash); + if (newLCL) + mPreviousLedger = newLCL; + else if (mAcquiringLedger && (mAcquiringLedger->getHash() == mPrevLedgerHash)) + return; + else + { + Log(lsWARNING) << "Need consensus ledger " << mPrevLedgerHash; + mAcquiringLedger = theApp->getMasterLedgerAcquire().findCreate(mPrevLedgerHash); std::vector peerList = theApp->getConnectionPool().getPeerVector(); + bool found = false; - for (std::vector::const_iterator it = peerList.begin(), end = peerList.end(); it != end; ++it) - if ((*it)->hasLedger(mPrevLedgerHash)) + BOOST_FOREACH(Peer::ref peer, peerList) + { + if (peer->hasLedger(mPrevLedgerHash)) { found = true; - mAcquiringLedger->peerHas(*it); + mAcquiringLedger->peerHas(peer); } + } + if (!found) - for (std::vector::const_iterator it = peerList.begin(), end = peerList.end(); it != end; ++it) - mAcquiringLedger->peerHas(*it); + { + BOOST_FOREACH(Peer::ref peer, peerList) + mAcquiringLedger->peerHas(peer); + } + + if (mHaveCorrectLCL && mProposing && mOurPosition) + { + Log(lsINFO) << "Bowing out of consensus"; + mOurPosition->bowOut(); + propose(); + } + mHaveCorrectLCL = false; + mProposing = false; + mValidating = false; + mCloseTimes.clear(); + mPeerPositions.clear(); + mDisputes.clear(); + mDeadNodes.clear(); + playbackProposals(); + return; } + + Log(lsINFO) << "Acquired the consensus ledger " << mPrevLedgerHash; + mHaveCorrectLCL = true; + mAcquiringLedger = LedgerAcquire::pointer(); + mCloseResolution = ContinuousLedgerTiming::getNextLedgerTimeResolution( + mPreviousLedger->getCloseResolution(), mPreviousLedger->getCloseAgree(), + mPreviousLedger->getLedgerSeq() + 1); + playbackProposals(); } void LedgerConsensus::takeInitialPosition(Ledger& initialLedger) { SHAMap::pointer initialSet = initialLedger.peekTransactionMap()->snapShot(false); uint256 txSet = initialSet->getHash(); - assert (!mHaveCorrectLCL || (initialLedger.getParentHash() == mPreviousLedger->getHash())); - - // if any peers have taken a contrary position, process disputes - boost::unordered_set found; - for(boost::unordered_map::iterator it = mPeerPositions.begin(), - end = mPeerPositions.end(); it != end; ++it) - { - uint256 set = it->second->getCurrentHash(); - if (found.insert(set).second) - { - boost::unordered_map::iterator it = mComplete.find(set); - if (it != mComplete.end()) - createDisputes(initialSet, it->second); - } - } + Log(lsINFO) << "initial position " << txSet; + mapComplete(txSet, initialSet, false); if (mValidating) mOurPosition = boost::make_shared (mValSeed, initialLedger.getParentHash(), txSet, mCloseTime); else mOurPosition = boost::make_shared(initialLedger.getParentHash(), txSet, mCloseTime); - mapComplete(txSet, initialSet, false); + + BOOST_FOREACH(u256_lct_pair& it, mDisputes) + { + it.second->setOurVote(initialLedger.hasTransaction(it.first)); + } + + // if any peers have taken a contrary position, process disputes + boost::unordered_set found; + BOOST_FOREACH(u160_prop_pair& it, mPeerPositions) + { + uint256 set = it.second->getCurrentHash(); + if (found.insert(set).second) + { + boost::unordered_map::iterator iit = mAcquired.find(set); + if (iit != mAcquired.end()) + createDisputes(initialSet, iit->second); + } + } + if (mProposing) - propose(std::vector(), std::vector()); + propose(); } -void LedgerConsensus::createDisputes(SHAMap::pointer m1, SHAMap::pointer m2) +void LedgerConsensus::createDisputes(SHAMap::ref m1, SHAMap::ref m2) { SHAMap::SHAMapDiff differences; m1->compare(m2, differences, 16384); - for(SHAMap::SHAMapDiff::iterator pos = differences.begin(), end = differences.end(); pos != end; ++pos) + for (SHAMap::SHAMapDiff::iterator pos = differences.begin(), end = differences.end(); pos != end; ++pos) { // create disputed transactions (from the ledger that has them) if (pos->second.first) - { + { // transaction is in first map assert(!pos->second.second); addDisputedTransaction(pos->first, pos->second.first->peekData()); } - else if(pos->second.second) - { + else if (pos->second.second) + { // transaction is in second map assert(!pos->second.first); addDisputedTransaction(pos->first, pos->second.second->peekData()); } - else assert(false); + else // No other disagreement over a transaction should be possible + assert(false); } } -void LedgerConsensus::mapComplete(const uint256& hash, SHAMap::pointer map, bool acquired) +void LedgerConsensus::mapComplete(const uint256& hash, SHAMap::ref map, bool acquired) { if (acquired) - Log(lsINFO) << "We have acquired TXS " << hash.GetHex(); - mAcquiring.erase(hash); + Log(lsINFO) << "We have acquired TXS " << hash; if (!map) { // this is an invalid/corrupt map - mComplete[hash] = map; + mAcquired[hash] = map; + mAcquiring.erase(hash); Log(lsWARNING) << "A trusted node directed us to acquire an invalid TXN map"; return; } + assert(hash == map->getHash()); - if (mComplete.find(hash) != mComplete.end()) + if (mAcquired.find(hash) != mAcquired.end()) + { + mAcquiring.erase(hash); return; // we already have this map + } - if (mOurPosition && (map->getHash() != mOurPosition->getCurrentHash())) + if (mOurPosition && (!mOurPosition->isBowOut()) && (hash != mOurPosition->getCurrentHash())) { // this could create disputed transactions - boost::unordered_map::iterator it2 = mComplete.find(mOurPosition->getCurrentHash()); - if (it2 != mComplete.end()) + boost::unordered_map::iterator it2 = mAcquired.find(mOurPosition->getCurrentHash()); + if (it2 != mAcquired.end()) { assert((it2->first == mOurPosition->getCurrentHash()) && it2->second); createDisputes(it2->second, map); } - else assert(false); // We don't have our own position?! + else + assert(false); // We don't have our own position?! } - mComplete[map->getHash()] = map; + mAcquired[hash] = map; + mAcquiring.erase(hash); // Adjust tracking for each peer that takes this position std::vector peers; - for (boost::unordered_map::iterator it = mPeerPositions.begin(), - end = mPeerPositions.end(); it != end; ++it) + BOOST_FOREACH(u160_prop_pair& it, mPeerPositions) { - if (it->second->getCurrentHash() == map->getHash()) - peers.push_back(it->second->getPeerID()); + if (it.second->getCurrentHash() == map->getHash()) + peers.push_back(it.second->getPeerID()); } if (!peers.empty()) adjustCount(map, peers); else if (acquired) - Log(lsWARNING) << "By the time we got the map " << hash.GetHex() << " no peers were proposing it"; + Log(lsWARNING) << "By the time we got the map " << hash << " no peers were proposing it"; sendHaveTxSet(hash, true); } @@ -365,14 +471,13 @@ void LedgerConsensus::sendHaveTxSet(const uint256& hash, bool direct) theApp->getConnectionPool().relayMessage(NULL, packet); } -void LedgerConsensus::adjustCount(SHAMap::pointer map, const std::vector& peers) +void LedgerConsensus::adjustCount(SHAMap::ref map, const std::vector& peers) { // Adjust the counts on all disputed transactions based on the set of peers taking this position - for (boost::unordered_map::iterator it = mDisputes.begin(), end = mDisputes.end(); - it != end; ++it) + BOOST_FOREACH(u256_lct_pair& it, mDisputes) { - bool setHas = map->hasItem(it->second->getTransactionID()); - for(std::vector::const_iterator pit = peers.begin(), pend = peers.end(); pit != pend; ++pit) - it->second->setVote(*pit, setHas); + bool setHas = map->hasItem(it.second->getTransactionID()); + BOOST_FOREACH(const uint160& pit, peers) + it.second->setVote(pit, setHas); } } @@ -405,16 +510,29 @@ void LedgerConsensus::statePreClose() int proposersClosed = mPeerPositions.size(); // This ledger is open. This computes how long since the last ledger closed - int sinceClose = 1000 * (theApp->getOPs().getCloseTimeNC() - theApp->getOPs().getLastCloseNetTime()); + int sinceClose; + int idleInterval = 0; - if (sinceClose >= ContinuousLedgerTiming::shouldClose(anyTransactions, mPreviousProposers, proposersClosed, - mPreviousMSeconds, sinceClose)) + if (mHaveCorrectLCL && mPreviousLedger->getCloseAgree()) + { // we can use consensus timing + sinceClose = 1000 * (theApp->getOPs().getCloseTimeNC() - mPreviousLedger->getCloseTimeNC()); + idleInterval = 2 * mPreviousLedger->getCloseResolution(); + if (idleInterval < LEDGER_IDLE_INTERVAL) + idleInterval = LEDGER_IDLE_INTERVAL; + } + else + { + sinceClose = 1000 * (theApp->getOPs().getCloseTimeNC() - theApp->getOPs().getLastCloseTime()); + idleInterval = LEDGER_IDLE_INTERVAL; + } + + if (ContinuousLedgerTiming::shouldClose(anyTransactions, mPreviousProposers, proposersClosed, + mPreviousMSeconds, sinceClose, idleInterval)) { // it is time to close the ledger - Log(lsINFO) << "CLC: closing ledger"; mState = lcsESTABLISH; - mConsensusStartTime = boost::posix_time::second_clock::universal_time(); + mConsensusStartTime = boost::posix_time::microsec_clock::universal_time(); mCloseTime = theApp->getOPs().getCloseTimeNC(); - theApp->getOPs().setLastCloseNetTime(mCloseTime); + theApp->getOPs().setLastCloseTime(mCloseTime); statusChange(newcoin::neCLOSING_LEDGER, *mPreviousLedger); takeInitialPosition(*theApp->getMasterLedger().closeLedger()); } @@ -427,11 +545,12 @@ void LedgerConsensus::stateEstablish() updateOurPositions(); if (!mHaveCloseTimeConsensus) { - Log(lsINFO) << "No close time consensus"; + if (haveConsensus()) + Log(lsINFO) << "We have TX consensus but not CT consensus"; } else if (haveConsensus()) { - Log(lsINFO) << "Converge cutoff"; + Log(lsINFO) << "Converge cutoff (" << mPeerPositions.size() << " participants)"; mState = lcsFINISHED; beginAccept(); } @@ -440,9 +559,7 @@ void LedgerConsensus::stateEstablish() void LedgerConsensus::stateFinished() { // we are processing the finished ledger // logic of calculating next ledger advances us out of this state - - // CHECKME: Should we count proposers that didn't converge to our consensus set? - theApp->getOPs().newLCL(mPeerPositions.size(), mCurrentMSeconds, mNewLedgerHash); + // nothing to do } void LedgerConsensus::stateAccepted() @@ -452,32 +569,16 @@ void LedgerConsensus::stateAccepted() void LedgerConsensus::timerEntry() { - if (!mHaveCorrectLCL) - { + if ((mState != lcsFINISHED) && (mState != lcsACCEPTED)) checkLCL(); - Log(lsINFO) << "Checking for consensus ledger " << mPrevLedgerHash.GetHex(); - Ledger::pointer consensus = theApp->getMasterLedger().getLedgerByHash(mPrevLedgerHash); - if (consensus) - { - Log(lsINFO) << "We have acquired the consensus ledger"; - if (theApp->getMasterLedger().getClosedLedger()->getHash() != mPrevLedgerHash) - theApp->getOPs().switchLastClosedLedger(consensus, true); - mPreviousLedger = consensus; - mHaveCorrectLCL = true; - mCloseResolution = ContinuousLedgerTiming::getNextLedgerTimeResolution( - mPreviousLedger->getCloseResolution(), mPreviousLedger->getCloseAgree(), - mPreviousLedger->getLedgerSeq() + 1); - } - else Log(lsINFO) << "We still don't have it"; - } - mCurrentMSeconds = (mCloseTime == 0) ? 0 : - (boost::posix_time::second_clock::universal_time() - mConsensusStartTime).total_milliseconds(); + mCurrentMSeconds = + (boost::posix_time::microsec_clock::universal_time() - mConsensusStartTime).total_milliseconds(); mClosePercent = mCurrentMSeconds * 100 / mPreviousMSeconds; switch (mState) { - case lcsPRE_CLOSE: statePreClose(); if (mState != lcsESTABLISH) return; fallthru(); + case lcsPRE_CLOSE: statePreClose(); return; case lcsESTABLISH: stateEstablish(); if (mState != lcsFINISHED) return; fallthru(); case lcsFINISHED: stateFinished(); if (mState != lcsACCEPTED) return; fallthru(); case lcsACCEPTED: stateAccepted(); return; @@ -487,38 +588,58 @@ void LedgerConsensus::timerEntry() void LedgerConsensus::updateOurPositions() { - Log(lsINFO) << "Updating our positions"; + boost::posix_time::ptime peerCutoff = boost::posix_time::second_clock::universal_time(); + boost::posix_time::ptime ourCutoff = peerCutoff - boost::posix_time::seconds(PROPOSE_INTERVAL); + peerCutoff -= boost::posix_time::seconds(PROPOSE_FRESHNESS); + bool changes = false; SHAMap::pointer ourPosition; - std::vector addedTx, removedTx; +// std::vector addedTx, removedTx; - for(boost::unordered_map::iterator it = mDisputes.begin(), - end = mDisputes.end(); it != end; ++it) + // Verify freshness of peer positions and compute close times + std::map closeTimes; + boost::unordered_map::iterator + it = mPeerPositions.begin(), end = mPeerPositions.end(); + while (it != end) { - if (it->second->updatePosition(mClosePercent, mProposing)) + if (it->second->isStale(peerCutoff)) + { // proposal is stale + uint160 peerID = it->second->getPeerID(); + Log(lsWARNING) << "Removing stale proposal from " << peerID; + BOOST_FOREACH(u256_lct_pair& it, mDisputes) + it.second->unVote(peerID); + mPeerPositions.erase(it++); + } + else + { // proposal is still fresh + ++closeTimes[roundCloseTime(it->second->getCloseTime())]; + ++it; + } + } + + BOOST_FOREACH(u256_lct_pair& it, mDisputes) + { + if (it.second->updateVote(mClosePercent, mProposing)) { if (!changes) { - ourPosition = mComplete[mOurPosition->getCurrentHash()]->snapShot(true); + ourPosition = mAcquired[mOurPosition->getCurrentHash()]->snapShot(true); + assert(ourPosition); changes = true; } - if (it->second->getOurPosition()) // now a yes + if (it.second->getOurVote()) // now a yes { - ourPosition->addItem(SHAMapItem(it->first, it->second->peekTransaction()), true, false); - addedTx.push_back(it->first); + ourPosition->addItem(SHAMapItem(it.first, it.second->peekTransaction()), true, false); +// addedTx.push_back(it.first); } else // now a no { - ourPosition->delItem(it->first); - removedTx.push_back(it->first); + ourPosition->delItem(it.first); +// removedTx.push_back(it.first); } } } - std::map closeTimes; - for (boost::unordered_map::iterator it = mPeerPositions.begin(), - end = mPeerPositions.end(); it != end; ++it) - ++closeTimes[it->second->getCloseTime() - (it->second->getCloseTime() % mCloseResolution)]; int neededWeight; if (mClosePercent < AV_MID_CONSENSUS_TIME) @@ -527,65 +648,93 @@ void LedgerConsensus::updateOurPositions() neededWeight = AV_MID_CONSENSUS_PCT; else neededWeight = AV_LATE_CONSENSUS_PCT; - int thresh = mPeerPositions.size(); - if (mProposing) - { - ++closeTimes[mOurPosition->getCloseTime() - (mOurPosition->getCloseTime() % mCloseResolution)]; - ++thresh; - } - thresh = thresh * neededWeight / 100; - uint32 closeTime = 0; mHaveCloseTimeConsensus = false; - for (std::map::iterator it = closeTimes.begin(), end = closeTimes.end(); it != end; ++it) + + int thresh = mPeerPositions.size(); + if (thresh == 0) + { // no other times + mHaveCloseTimeConsensus = true; + closeTime = roundCloseTime(mOurPosition->getCloseTime()); + } + else { - Log(lsINFO) << "CCTime: " << it->first << " has " << it->second << " out of " << thresh; - if (it->second > thresh) + if (mProposing) { - Log(lsINFO) << "Close time consensus reached: " << closeTime; - mHaveCloseTimeConsensus = true; - closeTime = it->first; + ++closeTimes[roundCloseTime(mOurPosition->getCloseTime())]; + ++thresh; } + thresh = ((thresh * neededWeight) + (neededWeight / 2)) / 100; + if (thresh == 0) + thresh = 1; + + for (std::map::iterator it = closeTimes.begin(), end = closeTimes.end(); it != end; ++it) + { + Log(lsINFO) << "CCTime: " << it->first << " has " << it->second << ", " << thresh << " required"; + if (it->second > thresh) + { + Log(lsINFO) << "Close time consensus reached: " << it->first; + mHaveCloseTimeConsensus = true; + closeTime = it->first; + thresh = it->second; + } + } + if (!mHaveCloseTimeConsensus) + Log(lsDEBUG) << "No CT consensus: Proposers:" << mPeerPositions.size() << " Proposing:" << + (mProposing ? "yes" : "no") << " Thresh:" << thresh << " Pos:" << closeTime; } - if (closeTime != (mOurPosition->getCloseTime() - (mOurPosition->getCloseTime() % mCloseResolution))) - { - ourPosition = mComplete[mOurPosition->getCurrentHash()]->snapShot(true); + if ((!changes) && + ((closeTime != (roundCloseTime(mOurPosition->getCloseTime()))) || + (mOurPosition->isStale(ourCutoff)))) + { // close time changed or our position is stale + ourPosition = mAcquired[mOurPosition->getCurrentHash()]->snapShot(true); + assert(ourPosition); changes = true; } if (changes) { uint256 newHash = ourPosition->getHash(); - mOurPosition->changePosition(newHash, closeTime); - if (mProposing) propose(addedTx, removedTx); - mapComplete(newHash, ourPosition, false); - Log(lsINFO) << "We change our position to " << newHash.GetHex(); - Log(lsINFO) << " Close time " << closeTime; + Log(lsINFO) << "Position change: CTime " << closeTime << ", tx " << newHash; + if (mOurPosition->changePosition(newHash, closeTime)) + { + if (mProposing) + propose(); + mapComplete(newHash, ourPosition, false); + } } } bool LedgerConsensus::haveConsensus() -{ +{ // FIXME: Should check for a supermajority on each disputed transaction + // counting unacquired TX sets as disagreeing int agree = 0, disagree = 0; uint256 ourPosition = mOurPosition->getCurrentHash(); - for (boost::unordered_map::iterator it = mPeerPositions.begin(), - end = mPeerPositions.end(); it != end; ++it) + BOOST_FOREACH(u160_prop_pair& it, mPeerPositions) { - if (it->second->getCurrentHash() == ourPosition) - ++agree; - else - ++disagree; + if (!it.second->isBowOut()) + { + if (it.second->getCurrentHash() == ourPosition) + ++agree; + else + ++disagree; + } } - int currentValidations = theApp->getValidations().getCurrentValidationCount(mPreviousLedger->getCloseTimeNC()); + int currentValidations = theApp->getValidations().getNodesAfter(mPrevLedgerHash); + +#ifdef LC_DEBUG + Log(lsINFO) << "Checking for TX consensus: agree=" << agree << ", disagree=" << disagree; +#endif + return ContinuousLedgerTiming::haveConsensus(mPreviousProposers, agree + disagree, agree, currentValidations, mPreviousMSeconds, mCurrentMSeconds); } SHAMap::pointer LedgerConsensus::getTransactionTree(const uint256& hash, bool doAcquire) { - boost::unordered_map::iterator it = mComplete.find(hash); - if (it == mComplete.end()) + boost::unordered_map::iterator it = mAcquired.find(hash); + if (it == mAcquired.end()) { // we have not completed acquiring this ledger if (mState == lcsPRE_CLOSE) @@ -620,7 +769,7 @@ SHAMap::pointer LedgerConsensus::getTransactionTree(const uint256& hash, bool do return it->second; } -void LedgerConsensus::startAcquiring(TransactionAcquire::pointer acquire) +void LedgerConsensus::startAcquiring(const TransactionAcquire::pointer& acquire) { boost::unordered_map< uint256, std::vector< boost::weak_ptr > >::iterator it = mPeerData.find(acquire->getHash()); @@ -641,11 +790,20 @@ void LedgerConsensus::startAcquiring(TransactionAcquire::pointer acquire) } } } + + std::vector peerList = theApp->getConnectionPool().getPeerVector(); + BOOST_FOREACH(Peer::ref peer, peerList) + { + if (peer->hasTxSet(acquire->getHash()) + acquire->peerHash(peer); + } + + acquire->resetTimer(); } -void LedgerConsensus::propose(const std::vector& added, const std::vector& removed) +void LedgerConsensus::propose() { - Log(lsTRACE) << "We propose: " << mOurPosition->getCurrentHash().GetHex(); + Log(lsTRACE) << "We propose: " << mOurPosition->getCurrentHash(); newcoin::TMProposeSet prop; prop.set_currenttxhash(mOurPosition->getCurrentHash().begin(), 256 / 8); prop.set_proposeseq(mOurPosition->getProposeSeq()); @@ -661,77 +819,99 @@ void LedgerConsensus::propose(const std::vector& added, const std::vect void LedgerConsensus::addDisputedTransaction(const uint256& txID, const std::vector& tx) { - Log(lsTRACE) << "Transaction " << txID.GetHex() << " is disputed"; + Log(lsTRACE) << "Transaction " << txID << " is disputed"; boost::unordered_map::iterator it = mDisputes.find(txID); - if (it != mDisputes.end()) return; + if (it != mDisputes.end()) + return; - bool ourPosition = false; + bool ourVote = false; if (mOurPosition) { - boost::unordered_map::iterator mit = mComplete.find(mOurPosition->getCurrentHash()); - if (mit != mComplete.end()) - ourPosition = mit->second->hasItem(txID); - else assert(false); // We don't have our own position? + boost::unordered_map::iterator mit = mAcquired.find(mOurPosition->getCurrentHash()); + if (mit != mAcquired.end()) + ourVote = mit->second->hasItem(txID); + else + assert(false); // We don't have our own position? } - LCTransaction::pointer txn = boost::make_shared(txID, tx, ourPosition); + LCTransaction::pointer txn = boost::make_shared(txID, tx, ourVote); mDisputes[txID] = txn; - for (boost::unordered_map::iterator pit = mPeerPositions.begin(), - pend = mPeerPositions.end(); pit != pend; ++pit) + BOOST_FOREACH(u160_prop_pair& pit, mPeerPositions) { boost::unordered_map::const_iterator cit = - mComplete.find(pit->second->getCurrentHash()); - if (cit != mComplete.end() && cit->second) - txn->setVote(pit->first, cit->second->hasItem(txID)); + mAcquired.find(pit.second->getCurrentHash()); + if (cit != mAcquired.end() && cit->second) + txn->setVote(pit.first, cit->second->hasItem(txID)); + } + + if (!ourVote && theApp->isNew(txID)) + { + newcoin::TMTransaction msg; + msg.set_rawtransaction(&(tx.front()), tx.size()); + msg.set_status(newcoin::tsNEW); + msg.set_receivetimestamp(theApp->getOPs().getNetworkTimeNC()); + PackedMessage::pointer packet = boost::make_shared(msg, newcoin::mtTRANSACTION); + theApp->getConnectionPool().relayMessage(NULL, packet); } } -bool LedgerConsensus::peerPosition(LedgerProposal::pointer newPosition) +bool LedgerConsensus::peerPosition(const LedgerProposal::pointer& newPosition) { - LedgerProposal::pointer& currentPosition = mPeerPositions[newPosition->getPeerID()]; + uint160 peerID = newPosition->getPeerID(); + if (mDeadNodes.find(peerID) != mDeadNodes.end()) + { + Log(lsINFO) << "Position from dead node"; + return false; + } + + LedgerProposal::pointer& currentPosition = mPeerPositions[peerID]; if (currentPosition) { - assert(newPosition->getPeerID() == currentPosition->getPeerID()); + assert(peerID == currentPosition->getPeerID()); if (newPosition->getProposeSeq() <= currentPosition->getProposeSeq()) return false; - if (newPosition->getCurrentHash() == currentPosition->getCurrentHash()) - { // we missed an intermediary change - Log(lsINFO) << "We missed an intermediary position"; - currentPosition = newPosition; - return true; - } } - else if (newPosition->getProposeSeq() == 0) + + if (newPosition->getProposeSeq() == 0) { // new initial close time estimate Log(lsTRACE) << "Peer reports close time as " << newPosition->getCloseTime(); ++mCloseTimes[newPosition->getCloseTime()]; } - Log(lsINFO) << "Processing peer proposal " << newPosition->getProposeSeq() << "/" - << newPosition->getCurrentHash().GetHex(); + else if (newPosition->getProposeSeq() == LedgerProposal::seqLeave) + { + BOOST_FOREACH(u256_lct_pair& it, mDisputes) + it.second->unVote(peerID); + mPeerPositions.erase(peerID); + mDeadNodes.insert(peerID); + return true; + } + + + Log(lsINFO) << "Processing peer proposal " << newPosition->getProposeSeq() << "/" << newPosition->getCurrentHash(); currentPosition = newPosition; + SHAMap::pointer set = getTransactionTree(newPosition->getCurrentHash(), true); if (set) { - Log(lsTRACE) << "Have that set"; - for (boost::unordered_map::iterator it = mDisputes.begin(), - end = mDisputes.end(); it != end; ++it) - it->second->setVote(newPosition->getPeerID(), set->hasItem(it->first)); + BOOST_FOREACH(u256_lct_pair& it, mDisputes) + it.second->setVote(peerID, set->hasItem(it.first)); } - else Log(lsTRACE) << "Don't have that set"; + else + Log(lsTRACE) << "Don't have that tx set"; return true; } -bool LedgerConsensus::peerHasSet(Peer::pointer peer, const uint256& hashSet, newcoin::TxSetStatus status) +bool LedgerConsensus::peerHasSet(Peer::ref peer, const uint256& hashSet, newcoin::TxSetStatus status) { if (status != newcoin::tsHAVE) // Indirect requests are for future support return true; std::vector< boost::weak_ptr >& set = mPeerData[hashSet]; - for (std::vector< boost::weak_ptr >::iterator iit = set.begin(), iend = set.end(); iit != iend; ++iit) - if (iit->lock() == peer) + BOOST_FOREACH(boost::weak_ptr& iit, set) + if (iit.lock() == peer) return false; set.push_back(peer); @@ -741,17 +921,19 @@ bool LedgerConsensus::peerHasSet(Peer::pointer peer, const uint256& hashSet, new return true; } -bool LedgerConsensus::peerGaveNodes(Peer::pointer peer, const uint256& setHash, +bool LedgerConsensus::peerGaveNodes(Peer::ref peer, const uint256& setHash, const std::list& nodeIDs, const std::list< std::vector >& nodeData) { boost::unordered_map::iterator acq = mAcquiring.find(setHash); - if (acq == mAcquiring.end()) return false; - return acq->second->takeNodes(nodeIDs, nodeData, peer); + if (acq == mAcquiring.end()) + return false; + TransactionAcquire::pointer set = acq->second; // We must keep the set around during the function + return set->takeNodes(nodeIDs, nodeData, peer); } void LedgerConsensus::beginAccept() { - SHAMap::pointer consensusSet = mComplete[mOurPosition->getCurrentHash()]; + SHAMap::pointer consensusSet = mAcquired[mOurPosition->getCurrentHash()]; if (!consensusSet) { Log(lsFATAL) << "We don't have a consensus set"; @@ -759,8 +941,8 @@ void LedgerConsensus::beginAccept() return; } - boost::thread thread(boost::bind(&LedgerConsensus::Saccept, shared_from_this(), consensusSet)); - thread.detach(); + theApp->getOPs().newLCL(mPeerPositions.size(), mCurrentMSeconds, mNewLedgerHash); + theApp->getIOService().post(boost::bind(&LedgerConsensus::Saccept, shared_from_this(), consensusSet)); } void LedgerConsensus::Saccept(boost::shared_ptr This, SHAMap::pointer txSet) @@ -768,30 +950,62 @@ void LedgerConsensus::Saccept(boost::shared_ptr This, SHAMap::p This->accept(txSet); } -void LedgerConsensus::applyTransaction(TransactionEngine& engine, SerializedTransaction::pointer txn, - Ledger::pointer ledger, CanonicalTXSet& failedTransactions, bool final) +void LedgerConsensus::deferProposal(const LedgerProposal::pointer& proposal, const NewcoinAddress& peerPublic) { - TransactionEngineParams parms = final ? (tepNO_CHECK_FEE | tepUPDATE_TOTAL | tepMETADATA) : tepNONE; + std::list& props = mDeferredProposals[peerPublic.getNodeID()]; + if (props.size() >= (mPreviousProposers + 10)) + props.pop_front(); + props.push_back(proposal); +} + +void LedgerConsensus::playbackProposals() +{ + for (boost::unordered_map< uint160, std::list >::iterator + it = mDeferredProposals.begin(), end = mDeferredProposals.end(); it != end; ++it) + { + BOOST_FOREACH(const LedgerProposal::pointer& proposal, it->second) + { + if (proposal->hasSignature()) + { // we have the signature but don't know the ledger so couldn't verify + proposal->setPrevLedger(mPrevLedgerHash); + if (proposal->checkSign()) + { + Log(lsINFO) << "Applying deferred proposal"; + peerPosition(proposal); + } + } + else if (proposal->isPrevLedger(mPrevLedgerHash)) + peerPosition(proposal); + } + } +} + +void LedgerConsensus::applyTransaction(TransactionEngine& engine, const SerializedTransaction::pointer& txn, + Ledger::ref ledger, CanonicalTXSet& failedTransactions, bool openLedger) +{ + TransactionEngineParams parms = openLedger ? tapOPEN_LEDGER : tapNONE; #ifndef TRUST_NETWORK try { #endif - TransactionEngineResult result = engine.applyTransaction(*txn, parms); - if (result > 0) + TER result = engine.applyTransaction(*txn, parms); + if (isTerRetry(result)) { Log(lsINFO) << " retry"; assert(!ledger->hasTransaction(txn->getTransactionID())); failedTransactions.push_back(txn); } - else if (result == 0) + else if (isTepSuccess(result)) // FIXME: Need to do partial success { Log(lsTRACE) << " success"; assert(ledger->hasTransaction(txn->getTransactionID())); } - else + else if (isTemMalformed(result) || isTefFailure(result)) { Log(lsINFO) << " hard fail"; } + else + assert(false); #ifndef TRUST_NETWORK } catch (...) @@ -801,24 +1015,24 @@ void LedgerConsensus::applyTransaction(TransactionEngine& engine, SerializedTran #endif } -void LedgerConsensus::applyTransactions(SHAMap::pointer set, Ledger::pointer applyLedger, Ledger::pointer checkLedger, - CanonicalTXSet& failedTransactions, bool final) +void LedgerConsensus::applyTransactions(SHAMap::ref set, Ledger::ref applyLedger, + Ledger::ref checkLedger, CanonicalTXSet& failedTransactions, bool openLgr) { - TransactionEngineParams parms = final ? (tepNO_CHECK_FEE | tepUPDATE_TOTAL) : tepNONE; + TransactionEngineParams parms = openLgr ? tapOPEN_LEDGER : tapNONE; TransactionEngine engine(applyLedger); for (SHAMapItem::pointer item = set->peekFirstItem(); !!item; item = set->peekNextItem(item->getTag())) { if (!checkLedger->hasTransaction(item->getTag())) { - Log(lsINFO) << "Processing candidate transaction: " << item->getTag().GetHex(); + Log(lsINFO) << "Processing candidate transaction: " << item->getTag(); #ifndef TRUST_NETWORK try { #endif SerializerIterator sit(item->peekSerializer()); SerializedTransaction::pointer txn = boost::make_shared(boost::ref(sit)); - applyTransaction(engine, txn, applyLedger, failedTransactions, final); + applyTransaction(engine, txn, applyLedger, failedTransactions, openLgr); #ifndef TRUST_NETWORK } catch (...) @@ -838,7 +1052,7 @@ void LedgerConsensus::applyTransactions(SHAMap::pointer set, Ledger::pointer app { try { - TransactionEngineResult result = engine.applyTransaction(*it->second, parms); + TER result = engine.applyTransaction(*it->second, parms); if (result <= 0) { if (result == 0) ++successes; @@ -858,62 +1072,73 @@ void LedgerConsensus::applyTransactions(SHAMap::pointer set, Ledger::pointer app } while (successes > 0); } -void LedgerConsensus::accept(SHAMap::pointer set) +uint32 LedgerConsensus::roundCloseTime(uint32 closeTime) +{ + return closeTime - (closeTime % mCloseResolution); +} + +void LedgerConsensus::accept(SHAMap::ref set) { assert(set->getHash() == mOurPosition->getCurrentHash()); + + uint32 closeTime = roundCloseTime(mOurPosition->getCloseTime()); + Log(lsINFO) << "Computing new LCL based on network consensus"; - Log(lsDEBUG) << "Consensus " << mOurPosition->getCurrentHash().GetHex(); - Log(lsDEBUG) << "Previous LCL " << mPrevLedgerHash.GetHex(); + if (mHaveCorrectLCL) + { + Log(lsINFO) << "CNF tx " << mOurPosition->getCurrentHash() << ", close " << closeTime; + Log(lsINFO) << "CNF mode " << theApp->getOPs().getOperatingMode() << ", oldLCL " << mPrevLedgerHash; + } Ledger::pointer newLCL = boost::make_shared(false, boost::ref(*mPreviousLedger)); newLCL->armDirty(); CanonicalTXSet failedTransactions(set->getHash()); - applyTransactions(set, newLCL, newLCL, failedTransactions, true); + applyTransactions(set, newLCL, newLCL, failedTransactions, false); newLCL->setClosed(); - uint32 closeTime = mOurPosition->getCloseTime(); bool closeTimeCorrect = true; if (closeTime == 0) - { // we didn't agree + { // we agreed to disagree closeTimeCorrect = false; closeTime = mPreviousLedger->getCloseTimeNC() + 1; + Log(lsINFO) << "CNF badclose " << closeTime; } newLCL->setAccepted(closeTime, mCloseResolution, closeTimeCorrect); newLCL->updateHash(); uint256 newLCLHash = newLCL->getHash(); - Log(lsTRACE) << "newLCL " << newLCLHash.GetHex(); statusChange(newcoin::neACCEPTED_LEDGER, *newLCL); if (mValidating) { SerializedValidation::pointer v = boost::make_shared - (newLCLHash, newLCL->getCloseTimeNC(), mValSeed, mProposing); + (newLCLHash, theApp->getOPs().getValidationTimeNC(), mValSeed, mProposing); v->setTrusted(); + Log(lsINFO) << "CNF Val " << newLCLHash; theApp->getValidations().addValidation(v); std::vector validation = v->getSigned(); newcoin::TMValidation val; val.set_validation(&validation[0], validation.size()); theApp->getConnectionPool().relayMessage(NULL, boost::make_shared(val, newcoin::mtVALIDATION)); - Log(lsINFO) << "Validation sent " << newLCLHash.GetHex(); } + else + Log(lsINFO) << "CNF newLCL " << newLCLHash; Ledger::pointer newOL = boost::make_shared(true, boost::ref(*newLCL)); ScopedLock sl = theApp->getMasterLedger().getLock(); // Apply disputed transactions that didn't get in TransactionEngine engine(newOL); - for (boost::unordered_map::iterator it = mDisputes.begin(), - end = mDisputes.end(); it != end; ++it) + BOOST_FOREACH(u256_lct_pair& it, mDisputes) { - if (!it->second->getOurPosition()) + if (!it.second->getOurVote()) { // we voted NO try { Log(lsINFO) << "Test applying disputed transaction that did not get in"; - SerializerIterator sit(it->second->peekTransaction()); + SerializerIterator sit(it.second->peekTransaction()); SerializedTransaction::pointer txn = boost::make_shared(boost::ref(sit)); - applyTransaction(engine, txn, newOL, failedTransactions, false); + applyTransaction(engine, txn, newOL, failedTransactions, true); } catch (...) { @@ -924,18 +1149,19 @@ void LedgerConsensus::accept(SHAMap::pointer set) Log(lsINFO) << "Applying transactions from current ledger"; applyTransactions(theApp->getMasterLedger().getCurrentLedger()->peekTransactionMap(), newOL, newLCL, - failedTransactions, false); + failedTransactions, true); theApp->getMasterLedger().pushLedger(newLCL, newOL); mNewLedgerHash = newLCL->getHash(); mState = lcsACCEPTED; sl.unlock(); - { + if (mValidating) + { // see how close our close time is to other node's close time reports Log(lsINFO) << "We closed at " << boost::lexical_cast(mCloseTime); uint64 closeTotal = mCloseTime; int closeCount = 1; - for(std::map::iterator it = mCloseTimes.begin(), end = mCloseTimes.end(); it != end; ++it) - { + for (std::map::iterator it = mCloseTimes.begin(), end = mCloseTimes.end(); it != end; ++it) + { // FIXME: Use median, not average Log(lsINFO) << boost::lexical_cast(it->second) << " time votes for " << boost::lexical_cast(it->first); closeCount += it->second; @@ -945,12 +1171,12 @@ void LedgerConsensus::accept(SHAMap::pointer set) closeTotal /= closeCount; int offset = static_cast(closeTotal) - static_cast(mCloseTime); Log(lsINFO) << "Our close offset is estimated at " << offset << " (" << closeCount << ")"; + theApp->getOPs().closeTimeOffset(offset); } #ifdef DEBUG - Json::StyledStreamWriter ssw; - if (1) { + Json::StyledStreamWriter ssw; Log(lsTRACE) << "newLCL"; Json::Value p; newLCL->addJson(p, LEDGER_JSON_DUMP_TXNS | LEDGER_JSON_DUMP_STATE); diff --git a/src/LedgerConsensus.h b/src/LedgerConsensus.h index 205a75cd1a..44b4db409f 100644 --- a/src/LedgerConsensus.h +++ b/src/LedgerConsensus.h @@ -27,21 +27,21 @@ protected: SHAMap::pointer mMap; bool mHaveRoot; - void onTimer() { trigger(Peer::pointer()); } - void newPeer(Peer::pointer peer) { trigger(peer); } + void onTimer() { trigger(Peer::pointer(), true); } + void newPeer(Peer::ref peer) { trigger(peer, false); } void done(); - void trigger(Peer::pointer); + void trigger(Peer::ref, bool timer); boost::weak_ptr pmDowncast(); public: TransactionAcquire(const uint256& hash); + virtual ~TransactionAcquire() { ; } SHAMap::pointer getMap() { return mMap; } - bool takeNodes(const std::list& IDs, const std::list< std::vector >& data, - Peer::pointer); + bool takeNodes(const std::list& IDs, const std::list< std::vector >& data, Peer::ref); }; class LCTransaction @@ -49,23 +49,25 @@ class LCTransaction protected: uint256 mTransactionID; int mYays, mNays; - bool mOurPosition; + bool mOurVote; Serializer transaction; boost::unordered_map mVotes; public: typedef boost::shared_ptr pointer; - LCTransaction(const uint256 &txID, const std::vector& tx, bool ourPosition) : - mTransactionID(txID), mYays(0), mNays(0), mOurPosition(ourPosition), transaction(tx) { ; } + LCTransaction(const uint256 &txID, const std::vector& tx, bool ourVote) : + mTransactionID(txID), mYays(0), mNays(0), mOurVote(ourVote), transaction(tx) { ; } const uint256& getTransactionID() const { return mTransactionID; } - bool getOurPosition() const { return mOurPosition; } + bool getOurVote() const { return mOurVote; } Serializer& peekTransaction() { return transaction; } + void setOurVote(bool o) { mOurVote = o; } void setVote(const uint160& peer, bool votesYes); + void unVote(const uint160& peer); - bool updatePosition(int percentTime, bool proposing); + bool updateVote(int percentTime, bool proposing); }; enum LCState @@ -99,7 +101,7 @@ protected: boost::unordered_map mPeerPositions; // Transaction Sets, indexed by hash of transaction tree - boost::unordered_map mComplete; + boost::unordered_map mAcquired; boost::unordered_map mAcquiring; // Peer sets @@ -111,37 +113,47 @@ protected: // Close time estimates std::map mCloseTimes; + // deferred proposals (node ID -> proposals from that peer) + boost::unordered_map< uint160, std::list > mDeferredProposals; + + // nodes that have bowed out of this consensus process + boost::unordered_set mDeadNodes; + // final accept logic static void Saccept(boost::shared_ptr This, SHAMap::pointer txSet); - void accept(SHAMap::pointer txSet); + void accept(SHAMap::ref txSet); - void weHave(const uint256& id, Peer::pointer avoidPeer); - void startAcquiring(TransactionAcquire::pointer); + void weHave(const uint256& id, Peer::ref avoidPeer); + void startAcquiring(const TransactionAcquire::pointer&); SHAMap::pointer find(const uint256& hash); - void createDisputes(SHAMap::pointer, SHAMap::pointer); + void createDisputes(SHAMap::ref, SHAMap::ref); void addDisputedTransaction(const uint256&, const std::vector& transaction); - void adjustCount(SHAMap::pointer map, const std::vector& peers); - void propose(const std::vector& addedTx, const std::vector& removedTx); + void adjustCount(SHAMap::ref map, const std::vector& peers); + void propose(); void addPosition(LedgerProposal&, bool ours); void removePosition(LedgerProposal&, bool ours); void sendHaveTxSet(const uint256& set, bool direct); - void applyTransactions(SHAMap::pointer transactionSet, Ledger::pointer targetLedger, Ledger::pointer checkLedger, - CanonicalTXSet& failedTransactions, bool final); - void applyTransaction(TransactionEngine& engine, SerializedTransaction::pointer txn, Ledger::pointer targetLedger, - CanonicalTXSet& failedTransactions, bool final); + void applyTransactions(SHAMap::ref transactionSet, Ledger::ref targetLedger, + Ledger::ref checkLedger, CanonicalTXSet& failedTransactions, bool openLgr); + void applyTransaction(TransactionEngine& engine, const SerializedTransaction::pointer& txn, + Ledger::ref targetLedger, CanonicalTXSet& failedTransactions, bool openLgr); + + uint32 roundCloseTime(uint32 closeTime); // manipulating our own position void statusChange(newcoin::NodeEvent, Ledger& ledger); void takeInitialPosition(Ledger& initialLedger); void updateOurPositions(); + void playbackProposals(); int getThreshold(); + void beginAccept(); void endConsensus(); public: - LedgerConsensus(const uint256& prevLCLHash, Ledger::pointer previousLedger, uint32 closeTime); + LedgerConsensus(const uint256& prevLCLHash, Ledger::ref previousLedger, uint32 closeTime); int startup(); Json::Value getJson(); @@ -151,8 +163,9 @@ public: SHAMap::pointer getTransactionTree(const uint256& hash, bool doAcquire); TransactionAcquire::pointer getAcquiring(const uint256& hash); - void mapComplete(const uint256& hash, SHAMap::pointer map, bool acquired); + void mapComplete(const uint256& hash, SHAMap::ref map, bool acquired); void checkLCL(); + void handleLCL(const uint256& lclHash); void timerEntry(); @@ -165,12 +178,17 @@ public: bool haveConsensus(); - bool peerPosition(LedgerProposal::pointer); + bool peerPosition(const LedgerProposal::pointer&); + void deferProposal(const LedgerProposal::pointer& proposal, const NewcoinAddress& peerPublic); - bool peerHasSet(Peer::pointer peer, const uint256& set, newcoin::TxSetStatus status); + bool peerHasSet(Peer::ref peer, const uint256& set, newcoin::TxSetStatus status); - bool peerGaveNodes(Peer::pointer peer, const uint256& setHash, + bool peerGaveNodes(Peer::ref peer, const uint256& setHash, const std::list& nodeIDs, const std::list< std::vector >& nodeData); + + void swapDefer(boost::unordered_map< uint160, std::list > &n) + { mDeferredProposals.swap(n); } + }; diff --git a/src/LedgerEntrySet.cpp b/src/LedgerEntrySet.cpp index fb4903ff4c..478290bb33 100644 --- a/src/LedgerEntrySet.cpp +++ b/src/LedgerEntrySet.cpp @@ -1,10 +1,17 @@ + #include "LedgerEntrySet.h" #include -void LedgerEntrySet::init(const uint256& transactionID, uint32 ledgerID) +#include "Log.h" + +// Small for testing, should likely be 32 or 64. +#define DIR_NODE_MAX 2 + +void LedgerEntrySet::init(Ledger::ref ledger, const uint256& transactionID, uint32 ledgerID) { mEntries.clear(); + mLedger = ledger; mSet.init(transactionID, ledgerID); mSeq = 0; } @@ -15,21 +22,23 @@ void LedgerEntrySet::clear() mSet.clear(); } -LedgerEntrySet LedgerEntrySet::duplicate() +LedgerEntrySet LedgerEntrySet::duplicate() const { - return LedgerEntrySet(mEntries, mSet, mSeq + 1); + return LedgerEntrySet(mLedger, mEntries, mSet, mSeq + 1); } -void LedgerEntrySet::setTo(LedgerEntrySet& e) +void LedgerEntrySet::setTo(const LedgerEntrySet& e) { mEntries = e.mEntries; mSet = e.mSet; mSeq = e.mSeq; + mLedger = e.mLedger; } void LedgerEntrySet::swapWith(LedgerEntrySet& e) { std::swap(mSeq, e.mSeq); + std::swap(mLedger, e.mLedger); mSet.swap(e.mSet); mEntries.swap(e.mEntries); } @@ -53,6 +62,36 @@ SLE::pointer LedgerEntrySet::getEntry(const uint256& index, LedgerEntryAction& a return it->second.mEntry; } +SLE::pointer LedgerEntrySet::entryCreate(LedgerEntryType letType, const uint256& index) +{ + assert(index.isNonZero()); + SLE::pointer sleNew = boost::make_shared(letType); + sleNew->setIndex(index); + entryCreate(sleNew); + return sleNew; +} + +SLE::pointer LedgerEntrySet::entryCache(LedgerEntryType letType, const uint256& index) +{ + SLE::pointer sleEntry; + if (index.isNonZero()) + { + LedgerEntryAction action; + sleEntry = getEntry(index, action); + if (!sleEntry) + { + sleEntry = mLedger->getSLE(index); + if (sleEntry) + entryCache(sleEntry); + } + else if (action == taaDELETE) + { + assert(false); + } + } + return sleEntry; +} + LedgerEntryAction LedgerEntrySet::hasEntry(const uint256& index) const { boost::unordered_map::const_iterator it = mEntries.find(index); @@ -61,7 +100,7 @@ LedgerEntryAction LedgerEntrySet::hasEntry(const uint256& index) const return it->second.mAction; } -void LedgerEntrySet::entryCache(SLE::pointer& sle) +void LedgerEntrySet::entryCache(SLE::ref sle) { boost::unordered_map::iterator it = mEntries.find(sle->getIndex()); if (it == mEntries.end()) @@ -82,7 +121,7 @@ void LedgerEntrySet::entryCache(SLE::pointer& sle) } } -void LedgerEntrySet::entryCreate(SLE::pointer& sle) +void LedgerEntrySet::entryCreate(SLE::ref sle) { boost::unordered_map::iterator it = mEntries.find(sle->getIndex()); if (it == mEntries.end()) @@ -112,7 +151,7 @@ void LedgerEntrySet::entryCreate(SLE::pointer& sle) } } -void LedgerEntrySet::entryModify(SLE::pointer& sle) +void LedgerEntrySet::entryModify(SLE::ref sle) { boost::unordered_map::iterator it = mEntries.find(sle->getIndex()); if (it == mEntries.end()) @@ -147,7 +186,7 @@ void LedgerEntrySet::entryModify(SLE::pointer& sle) } } -void LedgerEntrySet::entryDelete(SLE::pointer& sle, bool unfunded) +void LedgerEntrySet::entryDelete(SLE::ref sle) { boost::unordered_map::iterator it = mEntries.find(sle->getIndex()); if (it == mEntries.end()) @@ -166,15 +205,6 @@ void LedgerEntrySet::entryDelete(SLE::pointer& sle, bool unfunded) it->second.mSeq = mSeq; it->second.mEntry = sle; it->second.mAction = taaDELETE; - if (unfunded) - { - assert(sle->getType() == ltOFFER); // only offers can be unfunded -#if 0 - mSet.deleteUnfunded(sle->getIndex(), - sle->getIValueFieldAmount(sfTakerPays), - sle->getIValueFieldAmount(sfTakerGets)); -#endif - } break; case taaCREATE: @@ -189,4 +219,929 @@ void LedgerEntrySet::entryDelete(SLE::pointer& sle, bool unfunded) } } +bool LedgerEntrySet::intersect(const LedgerEntrySet& lesLeft, const LedgerEntrySet& lesRight) +{ + return true; // XXX Needs implementation +} + +Json::Value LedgerEntrySet::getJson(int) const +{ + Json::Value ret(Json::objectValue); + + Json::Value nodes(Json::arrayValue); + for (boost::unordered_map::const_iterator it = mEntries.begin(), + end = mEntries.end(); it != end; ++it) + { + Json::Value entry(Json::objectValue); + entry["node"] = it->first.GetHex(); + switch (it->second.mEntry->getType()) + { + case ltINVALID: entry["type"] = "invalid"; break; + case ltACCOUNT_ROOT: entry["type"] = "acccount_root"; break; + case ltDIR_NODE: entry["type"] = "dir_node"; break; + case ltGENERATOR_MAP: entry["type"] = "generator_map"; break; + case ltRIPPLE_STATE: entry["type"] = "ripple_state"; break; + case ltNICKNAME: entry["type"] = "nickname"; break; + case ltOFFER: entry["type"] = "offer"; break; + default: assert(false); + } + switch (it->second.mAction) + { + case taaCACHED: entry["action"] = "cache"; break; + case taaMODIFY: entry["action"] = "modify"; break; + case taaDELETE: entry["action"] = "delete"; break; + case taaCREATE: entry["action"] = "create"; break; + default: assert(false); + } + nodes.append(entry); + } + ret["nodes" ] = nodes; + + ret["metaData"] = mSet.getJson(0); + + return ret; +} + +SLE::pointer LedgerEntrySet::getForMod(const uint256& node, Ledger::ref ledger, + boost::unordered_map& newMods) +{ + boost::unordered_map::iterator it = mEntries.find(node); + if (it != mEntries.end()) + { + if (it->second.mAction == taaDELETE) + return SLE::pointer(); + if (it->second.mAction == taaCACHED) + it->second.mAction = taaMODIFY; + if (it->second.mSeq != mSeq) + { + it->second.mEntry = boost::make_shared(*it->second.mEntry); + it->second.mSeq = mSeq; + } + return it->second.mEntry; + } + + boost::unordered_map::iterator me = newMods.find(node); + if (me != newMods.end()) + return me->second; + + SLE::pointer ret = ledger->getSLE(node); + if (ret) + newMods.insert(std::make_pair(node, ret)); + return ret; + +} + +bool LedgerEntrySet::threadTx(const NewcoinAddress& threadTo, Ledger::ref ledger, + boost::unordered_map& newMods) +{ + Log(lsTRACE) << "Thread to " << threadTo.getAccountID(); + SLE::pointer sle = getForMod(Ledger::getAccountRootIndex(threadTo.getAccountID()), ledger, newMods); + if (!sle) + { + assert(false); + return false; + } + return threadTx(sle, ledger, newMods); +} + +bool LedgerEntrySet::threadTx(SLE::ref threadTo, Ledger::ref ledger, boost::unordered_map& newMods) +{ // node = the node that was modified/deleted/created + // threadTo = the node that needs to know + uint256 prevTxID; + uint32 prevLgrID; + if (!threadTo->thread(mSet.getTxID(), mSet.getLgrSeq(), prevTxID, prevLgrID)) + return false; + if (mSet.getAffectedNode(threadTo->getIndex(), TMNModifiedNode, false).thread(prevTxID, prevLgrID)) + return true; + assert(false); + return false; +} + +bool LedgerEntrySet::threadOwners(SLE::ref node, Ledger::ref ledger, boost::unordered_map& newMods) +{ // thread new or modified node to owner or owners + if (node->hasOneOwner()) // thread to owner's account + { + Log(lsTRACE) << "Thread to single owner"; + return threadTx(node->getOwner(), ledger, newMods); + } + else if (node->hasTwoOwners()) // thread to owner's accounts] + { + Log(lsTRACE) << "Thread to two owners"; + return + threadTx(node->getFirstOwner(), ledger, newMods) && + threadTx(node->getSecondOwner(), ledger, newMods); + } + else + return false; +} + +void LedgerEntrySet::calcRawMeta(Serializer& s) +{ // calculate the raw meta data and return it. This must be called before the set is committed + + // Entries modified only as a result of building the transaction metadata + boost::unordered_map newMod; + + for (boost::unordered_map::const_iterator it = mEntries.begin(), + end = mEntries.end(); it != end; ++it) + { + int nType = TMNEndOfMetadata; + + switch (it->second.mAction) + { + case taaMODIFY: + Log(lsTRACE) << "Modified Node " << it->first; + nType = TMNModifiedNode; + break; + + case taaDELETE: + Log(lsTRACE) << "Deleted Node " << it->first; + nType = TMNDeletedNode; + break; + + case taaCREATE: + Log(lsTRACE) << "Created Node " << it->first; + nType = TMNCreatedNode; + break; + + default: // ignore these + break; + } + + if (nType == TMNEndOfMetadata) + continue; + + SLE::pointer origNode = mLedger->getSLE(it->first); + + if (origNode && (origNode->getType() == ltDIR_NODE)) // No metadata for dir nodes + continue; + + SLE::pointer curNode = it->second.mEntry; + TransactionMetaNode &metaNode = mSet.getAffectedNode(it->first, nType, true); + + if (nType == TMNDeletedNode) + { + assert(origNode); + threadOwners(origNode, mLedger, newMod); + + if (origNode->isFieldPresent(sfAmount)) + { // node has an amount, covers ripple state nodes + STAmount amount = origNode->getFieldAmount(sfAmount); + if (amount.isNonZero()) + metaNode.addAmount(TMSPrevBalance, amount); + amount = curNode->getFieldAmount(sfAmount); + if (amount.isNonZero()) + metaNode.addAmount(TMSFinalBalance, amount); + + if (origNode->getType() == ltRIPPLE_STATE) + { + metaNode.addAccount(TMSLowID, NewcoinAddress::createAccountID(origNode->getFieldAmount(sfLowLimit).getIssuer())); + metaNode.addAccount(TMSHighID, NewcoinAddress::createAccountID(origNode->getFieldAmount(sfHighLimit).getIssuer())); + } + } + + if (origNode->getType() == ltOFFER) + { // check for non-zero balances + STAmount amount = origNode->getFieldAmount(sfTakerPays); + if (amount.isNonZero()) + metaNode.addAmount(TMSFinalTakerPays, amount); + amount = origNode->getFieldAmount(sfTakerGets); + if (amount.isNonZero()) + metaNode.addAmount(TMSFinalTakerGets, amount); + } + + } + + if (nType == TMNCreatedNode) // if created, thread to owner(s) + { + assert(!origNode); + threadOwners(curNode, mLedger, newMod); + } + + if ((nType == TMNCreatedNode) || (nType == TMNModifiedNode)) + { + if (curNode->isThreadedType()) // always thread to self + { + Log(lsTRACE) << "Thread to self"; + threadTx(curNode, mLedger, newMod); + } + } + + if (nType == TMNModifiedNode) + { + assert(origNode); + if (origNode->isFieldPresent(sfAmount)) + { // node has an amount, covers account root nodes and ripple nodes + STAmount amount = origNode->getFieldAmount(sfAmount); + if (amount != curNode->getFieldAmount(sfAmount)) + metaNode.addAmount(TMSPrevBalance, amount); + } + + if (origNode->getType() == ltOFFER) + { + STAmount amount = origNode->getFieldAmount(sfTakerPays); + if (amount != curNode->getFieldAmount(sfTakerPays)) + metaNode.addAmount(TMSPrevTakerPays, amount); + amount = origNode->getFieldAmount(sfTakerGets); + if (amount != curNode->getFieldAmount(sfTakerGets)) + metaNode.addAmount(TMSPrevTakerGets, amount); + } + + } + } + + // 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); + +#ifdef DEBUG + Log(lsINFO) << "Metadata:" << mSet.getJson(0); +#endif + + mSet.addRaw(s); +} + +// <-- uNodeDir: For deletion, present to make dirDelete efficient. +// --> uRootIndex: The index of the base of the directory. Nodes are based off of this. +// --> uLedgerIndex: Value to add to directory. +// We only append. This allow for things that watch append only structure to just monitor from the last node on ward. +// Within a node with no deletions order of elements is sequential. Otherwise, order of elements is random. +TER LedgerEntrySet::dirAdd( + uint64& uNodeDir, + const uint256& uRootIndex, + const uint256& uLedgerIndex) +{ + SLE::pointer sleNode; + STVector256 svIndexes; + SLE::pointer sleRoot = entryCache(ltDIR_NODE, uRootIndex); + + if (!sleRoot) + { + // No root, make it. + sleRoot = entryCreate(ltDIR_NODE, uRootIndex); + + sleNode = sleRoot; + uNodeDir = 0; + } + else + { + uNodeDir = sleRoot->getFieldU64(sfIndexPrevious); // Get index to last directory node. + + if (uNodeDir) + { + // Try adding to last node. + sleNode = entryCache(ltDIR_NODE, Ledger::getDirNodeIndex(uRootIndex, uNodeDir)); + + assert(sleNode); + } + else + { + // Try adding to root. Didn't have a previous set to the last node. + sleNode = sleRoot; + } + + svIndexes = sleNode->getFieldV256(sfIndexes); + + if (DIR_NODE_MAX != svIndexes.peekValue().size()) + { + // Add to current node. + entryModify(sleNode); + } + // Add to new node. + else if (!++uNodeDir) + { + return terDIR_FULL; + } + else + { + // Have old last point to new node, if it was not root. + if (uNodeDir == 1) + { + // Previous node is root node. + + sleRoot->setFieldU64(sfIndexNext, uNodeDir); + } + else + { + // Previous node is not root node. + + SLE::pointer slePrevious = entryCache(ltDIR_NODE, Ledger::getDirNodeIndex(uRootIndex, uNodeDir-1)); + + slePrevious->setFieldU64(sfIndexNext, uNodeDir); + entryModify(slePrevious); + + sleNode->setFieldU64(sfIndexPrevious, uNodeDir-1); + } + + // Have root point to new node. + sleRoot->setFieldU64(sfIndexPrevious, uNodeDir); + entryModify(sleRoot); + + // Create the new node. + sleNode = entryCreate(ltDIR_NODE, Ledger::getDirNodeIndex(uRootIndex, uNodeDir)); + svIndexes = STVector256(); + } + } + + svIndexes.peekValue().push_back(uLedgerIndex); // Append entry. + sleNode->setFieldV256(sfIndexes, svIndexes); // Save entry. + + Log(lsINFO) << "dirAdd: creating: root: " << uRootIndex.ToString(); + Log(lsINFO) << "dirAdd: appending: Entry: " << uLedgerIndex.ToString(); + Log(lsINFO) << "dirAdd: appending: Node: " << strHex(uNodeDir); + // Log(lsINFO) << "dirAdd: appending: PREV: " << svIndexes.peekValue()[0].ToString(); + + return tesSUCCESS; +} + +// Ledger must be in a state for this to work. +TER LedgerEntrySet::dirDelete( + const bool bKeepRoot, // --> True, if we never completely clean up, after we overflow the root node. + const uint64& uNodeDir, // --> Node containing entry. + const uint256& uRootIndex, // --> The index of the base of the directory. Nodes are based off of this. + const uint256& uLedgerIndex, // --> Value to add to directory. + const bool bStable) // --> True, not to change relative order of entries. +{ + uint64 uNodeCur = uNodeDir; + SLE::pointer sleNode = entryCache(ltDIR_NODE, uNodeCur ? Ledger::getDirNodeIndex(uRootIndex, uNodeCur) : uRootIndex); + + assert(sleNode); + + if (!sleNode) + { + Log(lsWARNING) << "dirDelete: no such node"; + + return tefBAD_LEDGER; + } + + STVector256 svIndexes = sleNode->getFieldV256(sfIndexes); + std::vector& vuiIndexes = svIndexes.peekValue(); + std::vector::iterator it; + + it = std::find(vuiIndexes.begin(), vuiIndexes.end(), uLedgerIndex); + + assert(vuiIndexes.end() != it); + if (vuiIndexes.end() == it) + { + assert(false); + + Log(lsWARNING) << "dirDelete: no such entry"; + + return tefBAD_LEDGER; + } + + // Remove the element. + if (vuiIndexes.size() > 1) + { + if (bStable) + { + vuiIndexes.erase(it); + } + else + { + *it = vuiIndexes[vuiIndexes.size()-1]; + vuiIndexes.resize(vuiIndexes.size()-1); + } + } + else + { + vuiIndexes.clear(); + } + + sleNode->setFieldV256(sfIndexes, svIndexes); + entryModify(sleNode); + + if (vuiIndexes.empty()) + { + // May be able to delete nodes. + uint64 uNodePrevious = sleNode->getFieldU64(sfIndexPrevious); + uint64 uNodeNext = sleNode->getFieldU64(sfIndexNext); + + if (!uNodeCur) + { + // Just emptied root node. + + if (!uNodePrevious) + { + // Never overflowed the root node. Delete it. + entryDelete(sleNode); + } + // Root overflowed. + else if (bKeepRoot) + { + // If root overflowed and not allowed to delete overflowed root node. + + nothing(); + } + else if (uNodePrevious != uNodeNext) + { + // Have more than 2 nodes. Can't delete root node. + + nothing(); + } + else + { + // Have only a root node and a last node. + SLE::pointer sleLast = entryCache(ltDIR_NODE, Ledger::getDirNodeIndex(uRootIndex, uNodeNext)); + + assert(sleLast); + + if (sleLast->getFieldV256(sfIndexes).peekValue().empty()) + { + // Both nodes are empty. + + entryDelete(sleNode); // Delete root. + entryDelete(sleLast); // Delete last. + } + else + { + // Have an entry, can't delete root node. + + nothing(); + } + } + } + // Just emptied a non-root node. + else if (uNodeNext) + { + // Not root and not last node. Can delete node. + + SLE::pointer slePrevious = entryCache(ltDIR_NODE, uNodePrevious ? Ledger::getDirNodeIndex(uRootIndex, uNodePrevious) : uRootIndex); + + assert(slePrevious); + + SLE::pointer sleNext = entryCache(ltDIR_NODE, uNodeNext ? Ledger::getDirNodeIndex(uRootIndex, uNodeNext) : uRootIndex); + + assert(slePrevious); + assert(sleNext); + + if (!slePrevious) + { + Log(lsWARNING) << "dirDelete: previous node is missing"; + + return tefBAD_LEDGER; + } + + if (!sleNext) + { + Log(lsWARNING) << "dirDelete: next node is missing"; + + return tefBAD_LEDGER; + } + + // Fix previous to point to its new next. + slePrevious->setFieldU64(sfIndexNext, uNodeNext); + entryModify(slePrevious); + + // Fix next to point to its new previous. + sleNext->setFieldU64(sfIndexPrevious, uNodePrevious); + entryModify(sleNext); + } + // Last node. + else if (bKeepRoot || uNodePrevious) + { + // Not allowed to delete last node as root was overflowed. + // Or, have pervious entries preventing complete delete. + + nothing(); + } + else + { + // Last and only node besides the root. + SLE::pointer sleRoot = entryCache(ltDIR_NODE, uRootIndex); + + assert(sleRoot); + + if (sleRoot->getFieldV256(sfIndexes).peekValue().empty()) + { + // Both nodes are empty. + + entryDelete(sleRoot); // Delete root. + entryDelete(sleNode); // Delete last. + } + else + { + // Root has an entry, can't delete. + + nothing(); + } + } + } + + return tesSUCCESS; +} + +// Return the first entry and advance uDirEntry. +// <-- true, if had a next entry. +bool LedgerEntrySet::dirFirst( + const uint256& uRootIndex, // --> Root of directory. + SLE::pointer& sleNode, // <-- current node + unsigned int& uDirEntry, // <-- next entry + uint256& uEntryIndex) // <-- The entry, if available. Otherwise, zero. +{ + sleNode = entryCache(ltDIR_NODE, uRootIndex); + uDirEntry = 0; + + assert(sleNode); // We never probe for directories. + + return LedgerEntrySet::dirNext(uRootIndex, sleNode, uDirEntry, uEntryIndex); +} + +// Return the current entry and advance uDirEntry. +// <-- true, if had a next entry. +bool LedgerEntrySet::dirNext( + const uint256& uRootIndex, // --> Root of directory + SLE::pointer& sleNode, // <-> current node + unsigned int& uDirEntry, // <-> next entry + uint256& uEntryIndex) // <-- The entry, if available. Otherwise, zero. +{ + STVector256 svIndexes = sleNode->getFieldV256(sfIndexes); + std::vector& vuiIndexes = svIndexes.peekValue(); + + if (uDirEntry == vuiIndexes.size()) + { + uint64 uNodeNext = sleNode->getFieldU64(sfIndexNext); + + if (!uNodeNext) + { + uEntryIndex.zero(); + + return false; + } + else + { + sleNode = entryCache(ltDIR_NODE, Ledger::getDirNodeIndex(uRootIndex, uNodeNext)); + uDirEntry = 0; + + return dirNext(uRootIndex, sleNode, uDirEntry, uEntryIndex); + } + } + + uEntryIndex = vuiIndexes[uDirEntry++]; +Log(lsINFO) << boost::str(boost::format("dirNext: uDirEntry=%d uEntryIndex=%s") % uDirEntry % uEntryIndex); + + return true; +} + +TER LedgerEntrySet::offerDelete(const SLE::pointer& sleOffer, const uint256& uOfferIndex, const uint160& uOwnerID) +{ + uint64 uOwnerNode = sleOffer->getFieldU64(sfOwnerNode); + TER terResult = dirDelete(false, uOwnerNode, Ledger::getOwnerDirIndex(uOwnerID), uOfferIndex, false); + + if (tesSUCCESS == terResult) + { + uint256 uDirectory = sleOffer->getFieldH256(sfBookDirectory); + uint64 uBookNode = sleOffer->getFieldU64(sfBookNode); + + terResult = dirDelete(false, uBookNode, uDirectory, uOfferIndex, true); + } + + entryDelete(sleOffer); + + return terResult; +} + +TER LedgerEntrySet::offerDelete(const uint256& uOfferIndex) +{ + SLE::pointer sleOffer = entryCache(ltOFFER, uOfferIndex); + const uint160 uOwnerID = sleOffer->getFieldAccount(sfAccount).getAccountID(); + + return offerDelete(sleOffer, uOfferIndex, uOwnerID); +} + +// Returns amount owed by uToAccountID to uFromAccountID. +// <-- $owed/uCurrencyID/uToAccountID: positive: uFromAccountID holds IOUs., negative: uFromAccountID owes IOUs. +STAmount LedgerEntrySet::rippleOwed(const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID) +{ + STAmount saBalance; + SLE::pointer sleRippleState = entryCache(ltRIPPLE_STATE, Ledger::getRippleStateIndex(uToAccountID, uFromAccountID, uCurrencyID)); + + if (sleRippleState) + { + saBalance = sleRippleState->getFieldAmount(sfBalance); + if (uToAccountID < uFromAccountID) + saBalance.negate(); + saBalance.setIssuer(uToAccountID); + } + else + { + Log(lsINFO) << "rippleOwed: No credit line between " + << NewcoinAddress::createHumanAccountID(uFromAccountID) + << " and " + << NewcoinAddress::createHumanAccountID(uToAccountID) + << " for " + << STAmount::createHumanCurrency(uCurrencyID) + << "." ; + + assert(false); + } + + return saBalance; +} + +// Maximum amount of IOUs uToAccountID will hold from uFromAccountID. +// <-- $amount/uCurrencyID/uToAccountID. +STAmount LedgerEntrySet::rippleLimit(const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID) +{ + STAmount saLimit; + SLE::pointer sleRippleState = entryCache(ltRIPPLE_STATE, Ledger::getRippleStateIndex(uToAccountID, uFromAccountID, uCurrencyID)); + + assert(sleRippleState); + if (sleRippleState) + { + saLimit = sleRippleState->getFieldAmount(uToAccountID < uFromAccountID ? sfLowLimit : sfHighLimit); + saLimit.setIssuer(uToAccountID); + } + + return saLimit; + +} + +uint32 LedgerEntrySet::rippleTransferRate(const uint160& uIssuerID) +{ + SLE::pointer sleAccount = entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uIssuerID)); + + uint32 uQuality = sleAccount && sleAccount->isFieldPresent(sfTransferRate) + ? sleAccount->getFieldU32(sfTransferRate) + : QUALITY_ONE; + + Log(lsINFO) << boost::str(boost::format("rippleTransferRate: uIssuerID=%s account_exists=%d transfer_rate=%f") + % NewcoinAddress::createHumanAccountID(uIssuerID) + % !!sleAccount + % (uQuality/1000000000.0)); + + assert(sleAccount); + + return uQuality; +} + +uint32 LedgerEntrySet::rippleTransferRate(const uint160& uSenderID, const uint160& uReceiverID, const uint160& uIssuerID) +{ + uint32 uQuality; + + return uSenderID == uIssuerID || uReceiverID == uIssuerID + ? QUALITY_ONE + : rippleTransferRate(uIssuerID); +} + +// XXX Might not need this, might store in nodes on calc reverse. +uint32 LedgerEntrySet::rippleQualityIn(const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID, SField::ref sfLow, SField::ref sfHigh) +{ + uint32 uQuality = QUALITY_ONE; + SLE::pointer sleRippleState; + + if (uToAccountID == uFromAccountID) + { + nothing(); + } + else + { + sleRippleState = entryCache(ltRIPPLE_STATE, Ledger::getRippleStateIndex(uToAccountID, uFromAccountID, uCurrencyID)); + + if (sleRippleState) + { + SField::ref sfField = uToAccountID < uFromAccountID ? sfLow: sfHigh; + + uQuality = sleRippleState->isFieldPresent(sfField) + ? sleRippleState->getFieldU32(sfField) + : QUALITY_ONE; + + if (!uQuality) + uQuality = 1; // Avoid divide by zero. + } + } + + Log(lsINFO) << boost::str(boost::format("rippleQuality: %s uToAccountID=%s uFromAccountID=%s uCurrencyID=%s bLine=%d uQuality=%f") + % (sfLow == sfLowQualityIn ? "in" : "out") + % NewcoinAddress::createHumanAccountID(uToAccountID) + % NewcoinAddress::createHumanAccountID(uFromAccountID) + % STAmount::createHumanCurrency(uCurrencyID) + % !!sleRippleState + % (uQuality/1000000000.0)); + + assert(uToAccountID == uFromAccountID || !!sleRippleState); + + return uQuality; +} + +// Return how much of uIssuerID's uCurrencyID IOUs that uAccountID holds. May be negative. +// <-- IOU's uAccountID has of uIssuerID +STAmount LedgerEntrySet::rippleHolds(const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID) +{ + STAmount saBalance; + SLE::pointer sleRippleState = entryCache(ltRIPPLE_STATE, Ledger::getRippleStateIndex(uAccountID, uIssuerID, uCurrencyID)); + + if (sleRippleState) + { + saBalance = sleRippleState->getFieldAmount(sfBalance); + + if (uAccountID > uIssuerID) + saBalance.negate(); // Put balance in uAccountID terms. + } + + return saBalance; +} + +// <-- saAmount: amount of uCurrencyID held by uAccountID. May be negative. +STAmount LedgerEntrySet::accountHolds(const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID) +{ + STAmount saAmount; + + if (!uCurrencyID) + { + SLE::pointer sleAccount = entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uAccountID)); + + saAmount = sleAccount->getFieldAmount(sfBalance); + } + else + { + saAmount = rippleHolds(uAccountID, uCurrencyID, uIssuerID); + } + + Log(lsINFO) << boost::str(boost::format("accountHolds: uAccountID=%s saAmount=%s") + % NewcoinAddress::createHumanAccountID(uAccountID) + % saAmount.getFullText()); + + return saAmount; +} + +// Returns the funds available for uAccountID for a currency/issuer. +// Use when you need a default for rippling uAccountID's currency. +// --> saDefault/currency/issuer +// <-- saFunds: Funds available. May be negative. +// If the issuer is the same as uAccountID, funds are unlimited, use result is saDefault. +STAmount LedgerEntrySet::accountFunds(const uint160& uAccountID, const STAmount& saDefault) +{ + STAmount saFunds; + + if (!saDefault.isNative() && saDefault.getIssuer() == uAccountID) + { + saFunds = saDefault; + + Log(lsINFO) << boost::str(boost::format("accountFunds: uAccountID=%s saDefault=%s SELF-FUNDED") + % NewcoinAddress::createHumanAccountID(uAccountID) + % saDefault.getFullText()); + } + else + { + saFunds = accountHolds(uAccountID, saDefault.getCurrency(), saDefault.getIssuer()); + + Log(lsINFO) << boost::str(boost::format("accountFunds: uAccountID=%s saDefault=%s saFunds=%s") + % NewcoinAddress::createHumanAccountID(uAccountID) + % saDefault.getFullText() + % saFunds.getFullText()); + } + + return saFunds; +} + +// Calculate transit fee. +STAmount LedgerEntrySet::rippleTransferFee(const uint160& uSenderID, const uint160& uReceiverID, const uint160& uIssuerID, const STAmount& saAmount) +{ + STAmount saTransitFee; + + if (uSenderID != uIssuerID && uReceiverID != uIssuerID) + { + uint32 uTransitRate = rippleTransferRate(uIssuerID); + + if (QUALITY_ONE != uTransitRate) + { + STAmount saTransitRate(CURRENCY_ONE, uTransitRate, -9); + + saTransitFee = STAmount::multiply(saAmount, saTransitRate, saAmount.getCurrency(), saAmount.getIssuer()); + } + } + + return saTransitFee; +} + +// Direct send w/o fees: redeeming IOUs and/or sending own IOUs. +void LedgerEntrySet::rippleCredit(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount, bool bCheckIssuer) +{ + uint160 uIssuerID = saAmount.getIssuer(); + uint160 uCurrencyID = saAmount.getCurrency(); + + assert(!bCheckIssuer || uSenderID == uIssuerID || uReceiverID == uIssuerID); + + bool bFlipped = uSenderID > uReceiverID; + uint256 uIndex = Ledger::getRippleStateIndex(uSenderID, uReceiverID, saAmount.getCurrency()); + SLE::pointer sleRippleState = entryCache(ltRIPPLE_STATE, uIndex); + + if (!sleRippleState) + { + Log(lsINFO) << "rippleCredit: Creating ripple line: " << uIndex.ToString(); + + STAmount saBalance = saAmount; + + saBalance.setIssuer(ACCOUNT_ONE); + + sleRippleState = entryCreate(ltRIPPLE_STATE, uIndex); + + if (!bFlipped) + saBalance.negate(); + + sleRippleState->setFieldAmount(sfBalance, saBalance); + sleRippleState->setFieldAmount(bFlipped ? sfHighLimit : sfLowLimit, STAmount(uCurrencyID, uSenderID)); + sleRippleState->setFieldAmount(bFlipped ? sfLowLimit : sfHighLimit, STAmount(uCurrencyID, uReceiverID)); + } + else + { + STAmount saBalance = sleRippleState->getFieldAmount(sfBalance); + + if (!bFlipped) + saBalance.negate(); // Put balance in low terms. + + saBalance += saAmount; + + if (!bFlipped) + saBalance.negate(); + + sleRippleState->setFieldAmount(sfBalance, saBalance); + + entryModify(sleRippleState); + } +} + +// Send regardless of limits. +// --> saAmount: Amount/currency/issuer for receiver to get. +// <-- saActual: Amount actually sent. Sender pay's fees. +STAmount LedgerEntrySet::rippleSend(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount) +{ + STAmount saActual; + const uint160 uIssuerID = saAmount.getIssuer(); + + assert(!!uSenderID && !!uReceiverID); + + if (uSenderID == uIssuerID || uReceiverID == uIssuerID) + { + // Direct send: redeeming IOUs and/or sending own IOUs. + rippleCredit(uSenderID, uReceiverID, saAmount); + + saActual = saAmount; + } + else + { + // Sending 3rd party IOUs: transit. + + STAmount saTransitFee = rippleTransferFee(uSenderID, uReceiverID, uIssuerID, saAmount); + + saActual = !saTransitFee ? saAmount : saAmount+saTransitFee; + + saActual.setIssuer(uIssuerID); // XXX Make sure this done in + above. + + rippleCredit(uIssuerID, uReceiverID, saAmount); + rippleCredit(uSenderID, uIssuerID, saActual); + } + + return saActual; +} + +void LedgerEntrySet::accountSend(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount) +{ + assert(!saAmount.isNegative()); + + if (!saAmount) + { + nothing(); + } + else if (saAmount.isNative()) + { + SLE::pointer sleSender = !!uSenderID + ? entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uSenderID)) + : SLE::pointer(); + SLE::pointer sleReceiver = !!uReceiverID + ? entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uReceiverID)) + : SLE::pointer(); + + Log(lsINFO) << boost::str(boost::format("accountSend> %s (%s) -> %s (%s) : %s") + % NewcoinAddress::createHumanAccountID(uSenderID) + % (sleSender ? (sleSender->getFieldAmount(sfBalance)).getFullText() : "-") + % NewcoinAddress::createHumanAccountID(uReceiverID) + % (sleReceiver ? (sleReceiver->getFieldAmount(sfBalance)).getFullText() : "-") + % saAmount.getFullText()); + + if (sleSender) + { + sleSender->setFieldAmount(sfBalance, sleSender->getFieldAmount(sfBalance) - saAmount); + entryModify(sleSender); + } + + if (sleReceiver) + { + sleReceiver->setFieldAmount(sfBalance, sleReceiver->getFieldAmount(sfBalance) + saAmount); + entryModify(sleReceiver); + } + + Log(lsINFO) << boost::str(boost::format("accountSend< %s (%s) -> %s (%s) : %s") + % NewcoinAddress::createHumanAccountID(uSenderID) + % (sleSender ? (sleSender->getFieldAmount(sfBalance)).getFullText() : "-") + % NewcoinAddress::createHumanAccountID(uReceiverID) + % (sleReceiver ? (sleReceiver->getFieldAmount(sfBalance)).getFullText() : "-") + % saAmount.getFullText()); + } + else + { + rippleSend(uSenderID, uReceiverID, saAmount); + } +} // vim:ts=4 diff --git a/src/LedgerEntrySet.h b/src/LedgerEntrySet.h index e600060f33..be44b0e973 100644 --- a/src/LedgerEntrySet.h +++ b/src/LedgerEntrySet.h @@ -5,6 +5,8 @@ #include "SerializedLedger.h" #include "TransactionMeta.h" +#include "Ledger.h" +#include "TransactionErr.h" enum LedgerEntryAction { @@ -15,7 +17,6 @@ enum LedgerEntryAction taaCREATE, // Newly created. }; - class LedgerEntrySetEntry { public: @@ -23,47 +24,112 @@ public: LedgerEntryAction mAction; int mSeq; - LedgerEntrySetEntry(SLE::pointer e, LedgerEntryAction a, int s) : mEntry(e), mAction(a), mSeq(s) { ; } + LedgerEntrySetEntry(SLE::ref e, LedgerEntryAction a, int s) : mEntry(e), mAction(a), mSeq(s) { ; } }; class LedgerEntrySet { protected: + Ledger::pointer mLedger; boost::unordered_map mEntries; TransactionMetaSet mSet; int mSeq; - LedgerEntrySet(const boost::unordered_map &e, TransactionMetaSet& s, int m) : - mEntries(e), mSet(s), mSeq(m) { ; } + LedgerEntrySet(Ledger::ref ledger, const boost::unordered_map &e, + const TransactionMetaSet& s, int m) : mLedger(ledger), mEntries(e), mSet(s), mSeq(m) { ; } + + SLE::pointer getForMod(const uint256& node, Ledger::ref ledger, + boost::unordered_map& newMods); + + bool threadTx(const NewcoinAddress& threadTo, Ledger::ref ledger, + boost::unordered_map& newMods); + + bool threadTx(SLE::ref threadTo, Ledger::ref ledger, boost::unordered_map& newMods); + + bool threadOwners(SLE::ref node, Ledger::ref ledger, boost::unordered_map& newMods); public: + + LedgerEntrySet(Ledger::ref ledger) : mLedger(ledger), mSeq(0) { ; } + LedgerEntrySet() : mSeq(0) { ; } // set functions - LedgerEntrySet duplicate(); // Make a duplicate of this set - void setTo(LedgerEntrySet&); // Set this set to have the same contents as another + LedgerEntrySet duplicate() const; // Make a duplicate of this set + void setTo(const LedgerEntrySet&); // Set this set to have the same contents as another void swapWith(LedgerEntrySet&); // Swap the contents of two sets int getSeq() const { return mSeq; } void bumpSeq() { ++mSeq; } - void init(const uint256& transactionID, uint32 ledgerID); + void init(Ledger::ref ledger, const uint256& transactionID, uint32 ledgerID); void clear(); + Ledger::pointer& getLedger() { return mLedger; } + const Ledger::pointer& getLedgerRef() const { return mLedger; } + // basic entry functions SLE::pointer getEntry(const uint256& index, LedgerEntryAction&); LedgerEntryAction hasEntry(const uint256& index) const; - void entryCache(SLE::pointer&); // Add this entry to the cache - void entryCreate(SLE::pointer&); // This entry will be created - void entryDelete(SLE::pointer&, bool unfunded); - void entryModify(SLE::pointer&); // This entry will be modified + void entryCache(SLE::ref); // Add this entry to the cache + void entryCreate(SLE::ref); // This entry will be created + void entryDelete(SLE::ref); // This entry will be deleted + void entryModify(SLE::ref); // This entry will be modified + + // higher-level ledger functions + SLE::pointer entryCreate(LedgerEntryType letType, const uint256& uIndex); + SLE::pointer entryCache(LedgerEntryType letType, const uint256& uIndex); + + // Directory functions. + TER dirAdd( + uint64& uNodeDir, // Node of entry. + const uint256& uRootIndex, + const uint256& uLedgerIndex); + + TER dirDelete( + const bool bKeepRoot, + const uint64& uNodeDir, // Node item is mentioned in. + const uint256& uRootIndex, + const uint256& uLedgerIndex, // Item being deleted + const bool bStable); + + bool dirFirst(const uint256& uRootIndex, SLE::pointer& sleNode, unsigned int& uDirEntry, uint256& uEntryIndex); + bool dirNext(const uint256& uRootIndex, SLE::pointer& sleNode, unsigned int& uDirEntry, uint256& uEntryIndex); + + // Offer functions. + TER offerDelete(const uint256& uOfferIndex); + TER offerDelete(const SLE::pointer& sleOffer, const uint256& uOfferIndex, const uint160& uOwnerID); + + // Balance functions. + uint32 rippleTransferRate(const uint160& uIssuerID); + uint32 rippleTransferRate(const uint160& uSenderID, const uint160& uReceiverID, const uint160& uIssuerID); + STAmount rippleOwed(const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID); + STAmount rippleLimit(const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID); + uint32 rippleQualityIn(const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID, + SField::ref sfLow = sfLowQualityIn, SField::ref sfHigh = sfHighQualityIn); + uint32 rippleQualityOut(const uint160& uToAccountID, const uint160& uFromAccountID, const uint160& uCurrencyID) + { return rippleQualityIn(uToAccountID, uFromAccountID, uCurrencyID, sfLowQualityOut, sfHighQualityOut); } + + STAmount rippleHolds(const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID); + STAmount rippleTransferFee(const uint160& uSenderID, const uint160& uReceiverID, const uint160& uIssuerID, const STAmount& saAmount); + void rippleCredit(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount, bool bCheckIssuer=true); + STAmount rippleSend(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount); + + STAmount accountHolds(const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID); + void accountSend(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount); + STAmount accountFunds(const uint160& uAccountID, const STAmount& saDefault); + + Json::Value getJson(int) const; + void calcRawMeta(Serializer&); // iterator functions - bool isEmpty() const { return mEntries.empty(); } + bool isEmpty() const { return mEntries.empty(); } boost::unordered_map::const_iterator begin() const { return mEntries.begin(); } boost::unordered_map::const_iterator end() const { return mEntries.end(); } boost::unordered_map::iterator begin() { return mEntries.begin(); } boost::unordered_map::iterator end() { return mEntries.end(); } + + static bool intersect(const LedgerEntrySet& lesLeft, const LedgerEntrySet& lesRight); }; #endif diff --git a/src/LedgerFormats.cpp b/src/LedgerFormats.cpp index 8cc358b58c..c696d72ed2 100644 --- a/src/LedgerFormats.cpp +++ b/src/LedgerFormats.cpp @@ -1,89 +1,118 @@ #include "LedgerFormats.h" -#define S_FIELD(x) sf##x, #x +std::map LedgerEntryFormat::byType; +std::map LedgerEntryFormat::byName; -LedgerEntryFormat LedgerFormats[]= -{ - { "AccountRoot", ltACCOUNT_ROOT, { - { S_FIELD(Flags), STI_UINT32, SOE_FLAGS, 0 }, - { S_FIELD(Account), STI_ACCOUNT, SOE_REQUIRED, 0 }, - { S_FIELD(Sequence), STI_UINT32, SOE_REQUIRED, 0 }, - { S_FIELD(Balance), STI_AMOUNT, SOE_REQUIRED, 0 }, - { S_FIELD(LastReceive), STI_UINT32, SOE_REQUIRED, 0 }, - { S_FIELD(LastTxn), STI_UINT32, SOE_REQUIRED, 0 }, - { S_FIELD(AuthorizedKey), STI_ACCOUNT, SOE_IFFLAG, 1 }, - { S_FIELD(EmailHash), STI_HASH128, SOE_IFFLAG, 2 }, - { S_FIELD(WalletLocator), STI_HASH256, SOE_IFFLAG, 4 }, - { S_FIELD(MessageKey), STI_VL, SOE_IFFLAG, 8 }, - { S_FIELD(TransferRate), STI_UINT32, SOE_IFFLAG, 16 }, - { S_FIELD(Domain), STI_VL, SOE_IFFLAG, 32 }, - { S_FIELD(PublishHash), STI_HASH256, SOE_IFFLAG, 64 }, - { S_FIELD(PublishSize), STI_UINT32, SOE_IFFLAG, 128 }, - { S_FIELD(Extensions), STI_TL, SOE_IFFLAG, 0x01000000 }, - { sfInvalid, NULL, STI_DONE, SOE_NEVER, -1 } } - }, - { "DirectoryNode", ltDIR_NODE, { - { S_FIELD(Flags), STI_UINT32, SOE_FLAGS, 0 }, - { S_FIELD(Indexes), STI_VECTOR256, SOE_REQUIRED, 0 }, - { S_FIELD(IndexNext), STI_UINT64, SOE_IFFLAG, 1 }, - { S_FIELD(IndexPrevious), STI_UINT64, SOE_IFFLAG, 2 }, - { S_FIELD(Extensions), STI_TL, SOE_IFFLAG, 0x01000000 }, - { sfInvalid, NULL, STI_DONE, SOE_NEVER, -1 } } - }, - { "GeneratorMap", ltGENERATOR_MAP, { - { S_FIELD(Flags), STI_UINT32, SOE_FLAGS, 0 }, - { S_FIELD(Generator), STI_VL, SOE_REQUIRED, 0 }, - { S_FIELD(Extensions), STI_TL, SOE_IFFLAG, 0x01000000 }, - { sfInvalid, NULL, STI_DONE, SOE_NEVER, -1 } } - }, - { "Nickname", ltNICKNAME, { - { S_FIELD(Flags), STI_UINT32, SOE_FLAGS, 0 }, - { S_FIELD(Account), STI_ACCOUNT, SOE_REQUIRED, 0 }, - { S_FIELD(MinimumOffer), STI_AMOUNT, SOE_IFFLAG, 1 }, - { S_FIELD(Extensions), STI_TL, SOE_IFFLAG, 0x01000000 }, - { sfInvalid, NULL, STI_DONE, SOE_NEVER, -1 } } - }, - { "Offer", ltOFFER, { - { S_FIELD(Flags), STI_UINT32, SOE_FLAGS, 0 }, - { S_FIELD(Account), STI_ACCOUNT, SOE_REQUIRED, 0 }, - { S_FIELD(Sequence), STI_UINT32, SOE_REQUIRED, 0 }, - { S_FIELD(TakerPays), STI_AMOUNT, SOE_REQUIRED, 0 }, - { S_FIELD(TakerGets), STI_AMOUNT, SOE_REQUIRED, 0 }, - { S_FIELD(BookDirectory), STI_HASH256, SOE_REQUIRED, 0 }, - { S_FIELD(BookNode), STI_UINT64, SOE_REQUIRED, 0 }, - { S_FIELD(OwnerNode), STI_UINT64, SOE_REQUIRED, 0 }, - { S_FIELD(PaysIssuer), STI_ACCOUNT, SOE_IFFLAG, 1 }, - { S_FIELD(GetsIssuer), STI_ACCOUNT, SOE_IFFLAG, 2 }, - { S_FIELD(Expiration), STI_UINT32, SOE_IFFLAG, 4 }, - { S_FIELD(Extensions), STI_TL, SOE_IFFLAG, 0x01000000 }, - { sfInvalid, NULL, STI_DONE, SOE_NEVER, -1 } } - }, - { "RippleState", ltRIPPLE_STATE, { - { S_FIELD(Flags), STI_UINT32, SOE_FLAGS, 0 }, - { S_FIELD(Balance), STI_AMOUNT, SOE_REQUIRED, 0 }, - { S_FIELD(LowID), STI_ACCOUNT, SOE_REQUIRED, 0 }, - { S_FIELD(LowLimit), STI_AMOUNT, SOE_REQUIRED, 0 }, - { S_FIELD(HighID), STI_ACCOUNT, SOE_REQUIRED, 0 }, - { S_FIELD(HighLimit), STI_AMOUNT, SOE_REQUIRED, 0 }, - { S_FIELD(LowQualityIn), STI_UINT32, SOE_IFFLAG, 1 }, - { S_FIELD(LowQualityOut), STI_UINT32, SOE_IFFLAG, 2 }, - { S_FIELD(HighQualityIn), STI_UINT32, SOE_IFFLAG, 4 }, - { S_FIELD(HighQualityOut), STI_UINT32, SOE_IFFLAG, 8 }, - { S_FIELD(Extensions), STI_TL, SOE_IFFLAG, 0x01000000 }, - { sfInvalid, NULL, STI_DONE, SOE_NEVER, -1 } } - }, - { NULL, ltINVALID } -}; +#define LEF_BASE \ + << SOElement(sfLedgerIndex, SOE_OPTIONAL) \ + << SOElement(sfLedgerEntryType, SOE_REQUIRED) \ + << SOElement(sfFlags, SOE_REQUIRED) -LedgerEntryFormat* getLgrFormat(LedgerEntryType t) +#define DECLARE_LEF(name, type) lef = new LedgerEntryFormat(#name, type); (*lef) LEF_BASE + +static bool LEFInit() { - LedgerEntryFormat* f = LedgerFormats; - while (f->t_name != NULL) - { - if (f->t_type == t) return f; - ++f; - } - return NULL; + LedgerEntryFormat* lef; + + DECLARE_LEF(AccountRoot, ltACCOUNT_ROOT) + << SOElement(sfAccount, SOE_REQUIRED) + << SOElement(sfSequence, SOE_REQUIRED) + << SOElement(sfBalance, SOE_REQUIRED) + << SOElement(sfLastTxnID, SOE_REQUIRED) + << SOElement(sfLastTxnSeq, SOE_REQUIRED) + << SOElement(sfAuthorizedKey, SOE_OPTIONAL) + << SOElement(sfEmailHash, SOE_OPTIONAL) + << SOElement(sfWalletLocator, SOE_OPTIONAL) + << SOElement(sfMessageKey, SOE_OPTIONAL) + << SOElement(sfTransferRate, SOE_OPTIONAL) + << SOElement(sfDomain, SOE_OPTIONAL) + << SOElement(sfPublishHash, SOE_OPTIONAL) + << SOElement(sfPublishSize, SOE_OPTIONAL) + ; + + DECLARE_LEF(Contract, ltCONTRACT) + << SOElement(sfAccount, SOE_REQUIRED) + << SOElement(sfBalance, SOE_REQUIRED) + << SOElement(sfLastTxnID, SOE_REQUIRED) + << SOElement(sfLastTxnSeq, SOE_REQUIRED) + << SOElement(sfIssuer, SOE_REQUIRED) + << 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) + ; + + DECLARE_LEF(DirectoryNode, ltDIR_NODE) + << SOElement(sfIndexes, SOE_REQUIRED) + << SOElement(sfIndexNext, SOE_OPTIONAL) + << SOElement(sfIndexPrevious, SOE_OPTIONAL) + ; + + DECLARE_LEF(GeneratorMap, ltGENERATOR_MAP) + << SOElement(sfGenerator, SOE_REQUIRED) + ; + + DECLARE_LEF(Nickname, ltNICKNAME) + << SOElement(sfAccount, SOE_REQUIRED) + << SOElement(sfMinimumOffer, SOE_OPTIONAL) + ; + + DECLARE_LEF(Offer, ltOFFER) + << SOElement(sfAccount, SOE_REQUIRED) + << SOElement(sfSequence, SOE_REQUIRED) + << SOElement(sfTakerPays, SOE_REQUIRED) + << SOElement(sfTakerGets, SOE_REQUIRED) + << SOElement(sfBookDirectory, SOE_REQUIRED) + << SOElement(sfBookNode, SOE_REQUIRED) + << SOElement(sfOwnerNode, SOE_REQUIRED) + << SOElement(sfLastTxnID, SOE_REQUIRED) + << SOElement(sfLastTxnSeq, SOE_REQUIRED) + << SOElement(sfExpiration, SOE_OPTIONAL) + ; + + DECLARE_LEF(RippleState, ltRIPPLE_STATE) + << SOElement(sfBalance, SOE_REQUIRED) + << SOElement(sfLowLimit, SOE_REQUIRED) + << SOElement(sfHighLimit, SOE_REQUIRED) + << SOElement(sfLastTxnID, SOE_REQUIRED) + << SOElement(sfLastTxnSeq, SOE_REQUIRED) + << SOElement(sfLowQualityIn, SOE_OPTIONAL) + << SOElement(sfLowQualityOut, SOE_OPTIONAL) + << SOElement(sfHighQualityIn, SOE_OPTIONAL) + << SOElement(sfHighQualityOut, SOE_OPTIONAL) + ; + + return true; } + +bool LEFInitComplete = LEFInit(); + +LedgerEntryFormat* LedgerEntryFormat::getLgrFormat(LedgerEntryType t) +{ + std::map::iterator it = byType.find(static_cast(t)); + if (it == byType.end()) + return NULL; + return it->second; +} + +LedgerEntryFormat* LedgerEntryFormat::getLgrFormat(int t) +{ + std::map::iterator it = byType.find((t)); + if (it == byType.end()) + return NULL; + return it->second; +} + +LedgerEntryFormat* LedgerEntryFormat::getLgrFormat(const std::string& t) +{ + std::map::iterator it = byName.find((t)); + if (it == byName.end()) + return NULL; + return it->second; +} + // vim:ts=4 diff --git a/src/LedgerFormats.h b/src/LedgerFormats.h index 230e557c48..947cb65be5 100644 --- a/src/LedgerFormats.h +++ b/src/LedgerFormats.h @@ -13,6 +13,7 @@ enum LedgerEntryType ltRIPPLE_STATE = 'r', ltNICKNAME = 'n', ltOFFER = 'o', + ltCONTRACT = 'c', }; // Used as a prefix for computing ledger indexes (keys). @@ -23,12 +24,10 @@ enum LedgerNameSpace spaceGenerator = 'g', spaceNickname = 'n', spaceRipple = 'r', - spaceRippleDir = 'R', spaceOffer = 'o', // Entry for an offer. spaceOwnerDir = 'O', // Directory of things owned by an account. spaceBookDir = 'B', // Directory of order books. - spaceBond = 'b', - spaceInvoice = 'i', + spaceContract = 'c', }; enum LedgerSpecificFlags @@ -38,20 +37,33 @@ enum LedgerSpecificFlags // ltOFFER lsfPassive = 0x00010000, - - // ltRIPPLE_STATE - lsfLowIndexed = 0x00010000, - lsfHighIndexed = 0x00020000, }; -struct LedgerEntryFormat +class LedgerEntryFormat { - const char *t_name; - LedgerEntryType t_type; - SOElement elements[20]; +public: + std::string t_name; + LedgerEntryType t_type; + std::vector elements; + + static std::map byType; + static std::map byName; + + LedgerEntryFormat(const char *name, LedgerEntryType type) : t_name(name), t_type(type) + { + byName[name] = this; + byType[type] = this; + } + LedgerEntryFormat& operator<<(const SOElement& el) + { + elements.push_back(new SOElement(el)); + return *this; + } + + static LedgerEntryFormat* getLgrFormat(LedgerEntryType t); + static LedgerEntryFormat* getLgrFormat(const std::string& t); + static LedgerEntryFormat* getLgrFormat(int t); }; -extern LedgerEntryFormat LedgerFormats[]; -extern LedgerEntryFormat* getLgrFormat(LedgerEntryType t); #endif // vim:ts=4 diff --git a/src/LedgerHistory.cpp b/src/LedgerHistory.cpp index 8f9b1bec9e..6dd3a9c4a3 100644 --- a/src/LedgerHistory.cpp +++ b/src/LedgerHistory.cpp @@ -23,7 +23,7 @@ LedgerHistory::LedgerHistory() : mLedgersByHash(CACHED_LEDGER_NUM, CACHED_LEDGER void LedgerHistory::addLedger(Ledger::pointer ledger) { - mLedgersByHash.canonicalize(ledger->getHash(), ledger); + mLedgersByHash.canonicalize(ledger->getHash(), ledger, true); } void LedgerHistory::addAcceptedLedger(Ledger::pointer ledger) @@ -31,8 +31,10 @@ void LedgerHistory::addAcceptedLedger(Ledger::pointer ledger) assert(ledger && ledger->isAccepted()); uint256 h(ledger->getHash()); boost::recursive_mutex::scoped_lock sl(mLedgersByHash.peekMutex()); - mLedgersByHash.canonicalize(h, ledger); - assert(ledger && ledger->isAccepted() && ledger->isImmutable()); + mLedgersByHash.canonicalize(h, ledger, true); + assert(ledger); + assert(ledger->isAccepted()); + assert(ledger->isImmutable()); mLedgersByIndex.insert(std::make_pair(ledger->getLedgerSeq(), ledger)); boost::thread thread(boost::bind(&Ledger::saveAcceptedLedger, ledger)); thread.detach(); @@ -88,7 +90,8 @@ Ledger::pointer LedgerHistory::canonicalizeLedger(Ledger::pointer ledger, bool s if (!save) { // return input ledger if not in map, otherwise, return corresponding map ledger Ledger::pointer ret = mLedgersByHash.fetch(h); - if (ret) return ret; + if (ret) + return ret; return ledger; } diff --git a/src/LedgerIndex.cpp b/src/LedgerIndex.cpp deleted file mode 100644 index 8a27e7938b..0000000000 --- a/src/LedgerIndex.cpp +++ /dev/null @@ -1,154 +0,0 @@ - -#include "Ledger.h" - -// For an entry put in the 64 bit index or quality. -uint256 Ledger::getQualityIndex(const uint256& uBase, const uint64 uNodeDir) -{ - // Indexes are stored in big endian format: they print as hex as stored. - // Most significant bytes are first. Least significant bytes represent adjacent entries. - // We place uNodeDir in the 8 right most bytes to be adjacent. - // Want uNodeDir in big endian format so ++ goes to the next entry for indexes. - uint256 uNode(uBase); - - ((uint64*) uNode.end())[-1] = htobe64(uNodeDir); - - return uNode; -} - -// Return the last 64 bits. -uint64 Ledger::getQuality(const uint256& uBase) -{ - return be64toh(((uint64*) uBase.end())[-1]); -} - -uint256 Ledger::getQualityNext(const uint256& uBase) -{ - static uint256 uNext("10000000000000000"); - - uint256 uResult = uBase; - - uResult += uNext; - - return uResult; -} - -uint256 Ledger::getAccountRootIndex(const uint160& uAccountID) -{ - Serializer s(22); - - s.add16(spaceAccount); // 2 - s.add160(uAccountID); // 20 - - return s.getSHA512Half(); -} - -uint256 Ledger::getBookBase(const uint160& uTakerPaysCurrency, const uint160& uTakerPaysIssuerID, - const uint160& uTakerGetsCurrency, const uint160& uTakerGetsIssuerID) -{ - bool bInNative = uTakerPaysCurrency.isZero(); - bool bOutNative = uTakerGetsCurrency.isZero(); - - assert(!bInNative || !bOutNative); // Stamps to stamps not allowed. - assert(bInNative == uTakerPaysIssuerID.isZero()); // Make sure issuer is specified as needed. - assert(bOutNative == uTakerGetsIssuerID.isZero()); // Make sure issuer is specified as needed. - assert(uTakerPaysCurrency != uTakerGetsCurrency || uTakerPaysIssuerID != uTakerGetsIssuerID); // Currencies or accounts must differ. - - Serializer s(82); - - s.add16(spaceBookDir); // 2 - s.add160(uTakerPaysCurrency); // 20 - s.add160(uTakerGetsCurrency); // 20 - s.add160(uTakerPaysIssuerID); // 20 - s.add160(uTakerGetsIssuerID); // 20 - - return getQualityIndex(s.getSHA512Half()); // Return with quality 0. -} - -uint256 Ledger::getDirNodeIndex(const uint256& uDirRoot, const uint64 uNodeIndex) -{ - if (uNodeIndex) - { - Serializer s(42); - - s.add16(spaceDirNode); // 2 - s.add256(uDirRoot); // 32 - s.add64(uNodeIndex); // 8 - - return s.getSHA512Half(); - } - else - { - return uDirRoot; - } -} - -uint256 Ledger::getGeneratorIndex(const uint160& uGeneratorID) -{ - Serializer s(22); - - s.add16(spaceGenerator); // 2 - s.add160(uGeneratorID); // 20 - - return s.getSHA512Half(); -} - -// What is important: -// --> uNickname: is a Sha256 -// <-- SHA512/2: for consistency and speed in generating indexes. -uint256 Ledger::getNicknameIndex(const uint256& uNickname) -{ - Serializer s(34); - - s.add16(spaceNickname); // 2 - s.add256(uNickname); // 32 - - return s.getSHA512Half(); -} - -uint256 Ledger::getOfferIndex(const uint160& uAccountID, uint32 uSequence) -{ - Serializer s(26); - - s.add16(spaceOffer); // 2 - s.add160(uAccountID); // 20 - s.add32(uSequence); // 4 - - return s.getSHA512Half(); -} - -uint256 Ledger::getOwnerDirIndex(const uint160& uAccountID) -{ - Serializer s(22); - - s.add16(spaceOwnerDir); // 2 - s.add160(uAccountID); // 20 - - return s.getSHA512Half(); -} - -uint256 Ledger::getRippleDirIndex(const uint160& uAccountID) -{ - Serializer s(22); - - s.add16(spaceRippleDir); // 2 - s.add160(uAccountID); // 20 - - return s.getSHA512Half(); -} - -uint256 Ledger::getRippleStateIndex(const NewcoinAddress& naA, const NewcoinAddress& naB, const uint160& uCurrency) -{ - uint160 uAID = naA.getAccountID(); - uint160 uBID = naB.getAccountID(); - bool bAltB = uAID < uBID; - Serializer s(62); - - s.add16(spaceRipple); // 2 - s.add160(bAltB ? uAID : uBID); // 20 - s.add160(bAltB ? uBID : uAID); // 20 - s.add160(uCurrency); // 20 - - return s.getSHA512Half(); -} - -// vim:ts=4 diff --git a/src/LedgerMaster.cpp b/src/LedgerMaster.cpp index f5bb19646a..e0adb16ade 100644 --- a/src/LedgerMaster.cpp +++ b/src/LedgerMaster.cpp @@ -5,7 +5,6 @@ #include "Application.h" #include "NewcoinAddress.h" -#include "Conversion.h" #include "Log.h" uint32 LedgerMaster::getCurrentLedgerIndex() @@ -13,37 +12,39 @@ uint32 LedgerMaster::getCurrentLedgerIndex() return mCurrentLedger->getLedgerSeq(); } -bool LedgerMaster::addHeldTransaction(Transaction::pointer transaction) +bool LedgerMaster::addHeldTransaction(const Transaction::pointer& transaction) { // returns true if transaction was added boost::recursive_mutex::scoped_lock ml(mLock); return mHeldTransactionsByID.insert(std::make_pair(transaction->getID(), transaction)).second; } -void LedgerMaster::pushLedger(Ledger::pointer newLedger) +void LedgerMaster::pushLedger(Ledger::ref newLedger) { // Caller should already have properly assembled this ledger into "ready-to-close" form -- // all candidate transactions must already be appled - Log(lsINFO) << "PushLedger: " << newLedger->getHash().GetHex(); + Log(lsINFO) << "PushLedger: " << newLedger->getHash(); ScopedLock sl(mLock); if (!!mFinalizedLedger) { mFinalizedLedger->setClosed(); - Log(lsTRACE) << "Finalizes: " << mFinalizedLedger->getHash().GetHex(); + Log(lsTRACE) << "Finalizes: " << mFinalizedLedger->getHash(); } mFinalizedLedger = mCurrentLedger; mCurrentLedger = newLedger; mEngine.setLedger(newLedger); } -void LedgerMaster::pushLedger(Ledger::pointer newLCL, Ledger::pointer newOL) +void LedgerMaster::pushLedger(Ledger::ref newLCL, Ledger::ref newOL) { assert(newLCL->isClosed() && newLCL->isAccepted()); assert(!newOL->isClosed() && !newOL->isAccepted()); if (newLCL->isAccepted()) { + assert(newLCL->isClosed()); + assert(newLCL->isImmutable()); mLedgerHistory.addAcceptedLedger(newLCL); - Log(lsINFO) << "StashAccepted: " << newLCL->getHash().GetHex(); + Log(lsINFO) << "StashAccepted: " << newLCL->getHash(); } mFinalizedLedger = newLCL; @@ -52,7 +53,7 @@ void LedgerMaster::pushLedger(Ledger::pointer newLCL, Ledger::pointer newOL) mEngine.setLedger(newOL); } -void LedgerMaster::switchLedgers(Ledger::pointer lastClosed, Ledger::pointer current) +void LedgerMaster::switchLedgers(Ledger::ref lastClosed, Ledger::ref current) { assert(lastClosed && current); mFinalizedLedger = lastClosed; @@ -64,7 +65,7 @@ void LedgerMaster::switchLedgers(Ledger::pointer lastClosed, Ledger::pointer cur mEngine.setLedger(mCurrentLedger); } -void LedgerMaster::storeLedger(Ledger::pointer ledger) +void LedgerMaster::storeLedger(Ledger::ref ledger) { mLedgerHistory.addLedger(ledger); } @@ -78,10 +79,9 @@ Ledger::pointer LedgerMaster::closeLedger() return closingLedger; } -TransactionEngineResult LedgerMaster::doTransaction(const SerializedTransaction& txn, uint32 targetLedger, - TransactionEngineParams params) +TER LedgerMaster::doTransaction(const SerializedTransaction& txn, TransactionEngineParams params) { - TransactionEngineResult result = mEngine.applyTransaction(txn, params); + TER result = mEngine.applyTransaction(txn, params); theApp->getOPs().pubTransaction(mEngine.getLedger(), txn, result); return result; } diff --git a/src/LedgerMaster.h b/src/LedgerMaster.h index 9f9603f1bc..76e6327381 100644 --- a/src/LedgerMaster.h +++ b/src/LedgerMaster.h @@ -26,8 +26,8 @@ class LedgerMaster std::map mHeldTransactionsByID; void applyFutureTransactions(uint32 ledgerIndex); - bool isValidTransaction(Transaction::pointer trans); - bool isTransactionOnFutureList(Transaction::pointer trans); + bool isValidTransaction(const Transaction::pointer& trans); + bool isTransactionOnFutureList(const Transaction::pointer& trans); public: @@ -43,14 +43,15 @@ public: // The finalized ledger is the last closed/accepted ledger Ledger::pointer getClosedLedger() { return mFinalizedLedger; } - TransactionEngineResult doTransaction(const SerializedTransaction& txn, uint32 targetLedger, - TransactionEngineParams params); + void runStandAlone() { mFinalizedLedger = mCurrentLedger; } - void pushLedger(Ledger::pointer newLedger); - void pushLedger(Ledger::pointer newLCL, Ledger::pointer newOL); - void storeLedger(Ledger::pointer); + TER doTransaction(const SerializedTransaction& txn, TransactionEngineParams params); - void switchLedgers(Ledger::pointer lastClosed, Ledger::pointer newCurrent); + void pushLedger(Ledger::ref newLedger); + void pushLedger(Ledger::ref newLCL, Ledger::ref newOL); + void storeLedger(Ledger::ref); + + void switchLedgers(Ledger::ref lastClosed, Ledger::ref newCurrent); Ledger::pointer closeLedger(); @@ -65,14 +66,19 @@ public: Ledger::pointer getLedgerByHash(const uint256& hash) { + if (!hash) + return mCurrentLedger; + if (mCurrentLedger && (mCurrentLedger->getHash() == hash)) return mCurrentLedger; + if (mFinalizedLedger && (mFinalizedLedger->getHash() == hash)) return mFinalizedLedger; + return mLedgerHistory.getLedgerByHash(hash); } - bool addHeldTransaction(Transaction::pointer trans); + bool addHeldTransaction(const Transaction::pointer& trans); }; #endif diff --git a/src/LedgerNode.cpp b/src/LedgerNode.cpp deleted file mode 100644 index bbb4b7e969..0000000000 --- a/src/LedgerNode.cpp +++ /dev/null @@ -1,200 +0,0 @@ - -#include "Ledger.h" - -#include - -#include "utils.h" -#include "Log.h" - -// XXX Use shared locks where possible? - -LedgerStateParms Ledger::writeBack(LedgerStateParms parms, SLE::pointer entry) -{ - ScopedLock l(mAccountStateMap->Lock()); - bool create = false; - - if (!mAccountStateMap->hasItem(entry->getIndex())) - { - if ((parms & lepCREATE) == 0) - { - Log(lsERROR) << "WriteBack non-existent node without create"; - return lepMISSING; - } - create = true; - } - - SHAMapItem::pointer item = boost::make_shared(entry->getIndex()); - entry->add(item->peekSerializer()); - - if (create) - { - assert(!mAccountStateMap->hasItem(entry->getIndex())); - if(!mAccountStateMap->addGiveItem(item, false, false)) // FIXME: TX metadata - { - assert(false); - return lepERROR; - } - return lepCREATED; - } - - if (!mAccountStateMap->updateGiveItem(item, false, false)) // FIXME: TX metadata - { - assert(false); - return lepERROR; - } - return lepOKAY; -} - -SLE::pointer Ledger::getSLE(const uint256& uHash) -{ - SHAMapItem::pointer node = mAccountStateMap->peekItem(uHash); - if (!node) - return SLE::pointer(); - return boost::make_shared(node->peekSerializer(), node->getTag()); -} - -uint256 Ledger::getFirstLedgerIndex() -{ - SHAMapItem::pointer node = mAccountStateMap->peekFirstItem(); - return node ? node->getTag() : uint256(); -} - -uint256 Ledger::getLastLedgerIndex() -{ - SHAMapItem::pointer node = mAccountStateMap->peekLastItem(); - return node ? node->getTag() : uint256(); -} - -uint256 Ledger::getNextLedgerIndex(const uint256& uHash) -{ - SHAMapItem::pointer node = mAccountStateMap->peekNextItem(uHash); - return node ? node->getTag() : uint256(); -} - -uint256 Ledger::getNextLedgerIndex(const uint256& uHash, const uint256& uEnd) -{ - SHAMapItem::pointer node = mAccountStateMap->peekNextItem(uHash); - if ((!node) || (node->getTag() > uEnd)) - return uint256(); - return node->getTag(); -} - -uint256 Ledger::getPrevLedgerIndex(const uint256& uHash) -{ - SHAMapItem::pointer node = mAccountStateMap->peekPrevItem(uHash); - return node ? node->getTag() : uint256(); -} - -uint256 Ledger::getPrevLedgerIndex(const uint256& uHash, const uint256& uBegin) -{ - SHAMapItem::pointer node = mAccountStateMap->peekNextItem(uHash); - if ((!node) || (node->getTag() < uBegin)) - return uint256(); - return node->getTag(); -} - -SLE::pointer Ledger::getASNode(LedgerStateParms& parms, const uint256& nodeID, - LedgerEntryType let ) -{ - SHAMapItem::pointer account = mAccountStateMap->peekItem(nodeID); - - if (!account) - { - if ( (parms & lepCREATE) == 0 ) - { - parms = lepMISSING; - return SLE::pointer(); - } - - parms = parms | lepCREATED | lepOKAY; - SLE::pointer sle=boost::make_shared(let); - sle->setIndex(nodeID); - - return sle; - } - - SLE::pointer sle = - boost::make_shared(account->peekSerializer(), nodeID); - - if (sle->getType() != let) - { // maybe it's a currency or something - parms = parms | lepWRONGTYPE; - return SLE::pointer(); - } - - parms = parms | lepOKAY; - - return sle; -} - -SLE::pointer Ledger::getAccountRoot(const uint160& accountID) -{ - LedgerStateParms qry = lepNONE; - - return getASNode(qry, getAccountRootIndex(accountID), ltACCOUNT_ROOT); -} - -SLE::pointer Ledger::getAccountRoot(const NewcoinAddress& naAccountID) -{ - LedgerStateParms qry = lepNONE; - - return getASNode(qry, getAccountRootIndex(naAccountID.getAccountID()), ltACCOUNT_ROOT); -} - -// -// Directory -// - -SLE::pointer Ledger::getDirNode(LedgerStateParms& parms, const uint256& uNodeIndex) -{ - ScopedLock l(mAccountStateMap->Lock()); - - return getASNode(parms, uNodeIndex, ltDIR_NODE); -} - -// -// Generator Map -// - -SLE::pointer Ledger::getGenerator(LedgerStateParms& parms, const uint160& uGeneratorID) -{ - ScopedLock l(mAccountStateMap->Lock()); - - return getASNode(parms, getGeneratorIndex(uGeneratorID), ltGENERATOR_MAP); -} - -// -// Nickname -// - -SLE::pointer Ledger::getNickname(LedgerStateParms& parms, const uint256& uNickname) -{ - ScopedLock l(mAccountStateMap->Lock()); - - return getASNode(parms, uNickname, ltNICKNAME); -} - -// -// Offer -// - - -SLE::pointer Ledger::getOffer(LedgerStateParms& parms, const uint256& uIndex) -{ - ScopedLock l(mAccountStateMap->Lock()); - - return getASNode(parms, uIndex, ltOFFER); -} - -// -// Ripple State -// - -SLE::pointer Ledger::getRippleState(LedgerStateParms& parms, const uint256& uNode) -{ - ScopedLock l(mAccountStateMap->Lock()); - - return getASNode(parms, uNode, ltRIPPLE_STATE); -} - -// vim:ts=4 diff --git a/src/LedgerProposal.cpp b/src/LedgerProposal.cpp index 70d9458f6c..a334a612f7 100644 --- a/src/LedgerProposal.cpp +++ b/src/LedgerProposal.cpp @@ -9,14 +9,14 @@ LedgerProposal::LedgerProposal(const uint256& pLgr, uint32 seq, const uint256& tx, uint32 closeTime, const NewcoinAddress& naPeerPublic) : - mPreviousLedger(pLgr), mCurrentHash(tx), mCloseTime(closeTime), mProposeSeq(seq) + mPreviousLedger(pLgr), mCurrentHash(tx), mCloseTime(closeTime), mProposeSeq(seq), mPublicKey(naPeerPublic) { - mPublicKey = naPeerPublic; // XXX Validate key. // if (!mKey->SetPubKey(pubKey)) // throw std::runtime_error("Invalid public key in proposal"); mPeerID = mPublicKey.getNodeID(); + mTime = boost::posix_time::second_clock::universal_time(); } @@ -27,12 +27,13 @@ LedgerProposal::LedgerProposal(const NewcoinAddress& naSeed, const uint256& prev mPublicKey = NewcoinAddress::createNodePublic(naSeed); mPrivateKey = NewcoinAddress::createNodePrivate(naSeed); mPeerID = mPublicKey.getNodeID(); + mTime = boost::posix_time::second_clock::universal_time(); } LedgerProposal::LedgerProposal(const uint256& prevLgr, const uint256& position, uint32 closeTime) : mPreviousLedger(prevLgr), mCurrentHash(position), mCloseTime(closeTime), mProposeSeq(0) { - ; + mTime = boost::posix_time::second_clock::universal_time(); } uint256 LedgerProposal::getSigningHash() const @@ -53,11 +54,22 @@ bool LedgerProposal::checkSign(const std::string& signature, const uint256& sign return mPublicKey.verifyNodePublic(signingHash, signature); } -void LedgerProposal::changePosition(const uint256& newPosition, uint32 closeTime) +bool LedgerProposal::changePosition(const uint256& newPosition, uint32 closeTime) { - mCurrentHash = newPosition; - mCloseTime = closeTime; + if (mProposeSeq == seqLeave) + return false; + + mCurrentHash = newPosition; + mCloseTime = closeTime; + mTime = boost::posix_time::second_clock::universal_time(); ++mProposeSeq; + return true; +} + +void LedgerProposal::bowOut() +{ + mTime = boost::posix_time::second_clock::universal_time(); + mProposeSeq = seqLeave; } std::vector LedgerProposal::sign(void) @@ -76,9 +88,14 @@ Json::Value LedgerProposal::getJson() const { Json::Value ret = Json::objectValue; ret["previous_ledger"] = mPreviousLedger.GetHex(); - ret["transaction_hash"] = mCurrentHash.GetHex(); + + if (mProposeSeq != seqLeave) + { + ret["transaction_hash"] = mCurrentHash.GetHex(); + ret["propose_seq"] = mProposeSeq; + } + ret["close_time"] = mCloseTime; - ret["propose_seq"] = mProposeSeq; if (mPublicKey.isValid()) ret["peer_id"] = mPublicKey.humanNodePublic(); diff --git a/src/LedgerProposal.h b/src/LedgerProposal.h index 790b61fb4d..fb7df34309 100644 --- a/src/LedgerProposal.h +++ b/src/LedgerProposal.h @@ -1,7 +1,8 @@ #ifndef __PROPOSELEDGER__ -#define __PROPOSELEDEGR__ +#define __PROPOSELEDGER__ #include +#include #include @@ -21,7 +22,11 @@ protected: NewcoinAddress mPublicKey; NewcoinAddress mPrivateKey; // If ours + std::string mSignature; // set only if needed + boost::posix_time::ptime mTime; + public: + static const uint32 seqLeave = 0xffffffff; // leaving the consensus process typedef boost::shared_ptr pointer; @@ -39,17 +44,28 @@ public: uint256 getSigningHash() const; bool checkSign(const std::string& signature, const uint256& signingHash); bool checkSign(const std::string& signature) { return checkSign(signature, getSigningHash()); } + bool checkSign() { return checkSign(mSignature, getSigningHash()); } const uint160& getPeerID() const { return mPeerID; } const uint256& getCurrentHash() const { return mCurrentHash; } const uint256& getPrevLedger() const { return mPreviousLedger; } uint32 getProposeSeq() const { return mProposeSeq; } uint32 getCloseTime() const { return mCloseTime; } - const NewcoinAddress& peekPublic() const { return mPublicKey; } - std::vector getPubKey() const { return mPublicKey.getNodePublic(); } + const NewcoinAddress& peekPublic() const { return mPublicKey; } + std::vector getPubKey() const { return mPublicKey.getNodePublic(); } std::vector sign(); - void changePosition(const uint256& newPosition, uint32 newCloseTime); + void setPrevLedger(const uint256& prevLedger) { mPreviousLedger = prevLedger; } + void setSignature(const std::string& signature) { mSignature = signature; } + bool hasSignature() { return !mSignature.empty(); } + bool isPrevLedger(const uint256& pl) { return mPreviousLedger == pl; } + bool isBowOut() { return mProposeSeq == seqLeave; } + + const boost::posix_time::ptime getCreateTime() { return mTime; } + bool isStale(boost::posix_time::ptime cutoff) { return mTime <= cutoff; } + + bool changePosition(const uint256& newPosition, uint32 newCloseTime); + void bowOut(); Json::Value getJson() const; }; diff --git a/src/LedgerTiming.cpp b/src/LedgerTiming.cpp index e3ea061640..9ce760fc4d 100644 --- a/src/LedgerTiming.cpp +++ b/src/LedgerTiming.cpp @@ -12,46 +12,45 @@ int ContinuousLedgerTiming::LedgerTimeResolution[] = { 10, 10, 20, 30, 60, 90, 1 // Called when a ledger is open and no close is in progress -- when a transaction is received and no close // is in process, or when a close completes. Returns the number of seconds the ledger should be be open. -int ContinuousLedgerTiming::shouldClose( +bool ContinuousLedgerTiming::shouldClose( bool anyTransactions, int previousProposers, // proposers in the last closing int proposersClosed, // proposers who have currently closed this ledgers int previousMSeconds, // seconds the previous ledger took to reach consensus - int currentMSeconds) // seconds since the previous ledger closed + int currentMSeconds, // seconds since the previous ledger closed + int idleInterval) // network's desired idle interval { - assert((previousMSeconds > 0) && (previousMSeconds < 600000)); - assert((currentMSeconds >= 0) && (currentMSeconds < 600000)); - -#if 0 - Log(lsTRACE) << boost::str(boost::format("CLC::shouldClose Trans=%s, Prop: %d/%d, Secs: %d (last:%d)") % - (anyTransactions ? "yes" : "no") % previousProposers % proposersClosed % currentMSeconds % previousMSeconds); -#endif + if ((previousMSeconds < -1000) || (previousMSeconds > 600000) || + (currentMSeconds < -1000) || (currentMSeconds > 600000)) + { + Log(lsWARNING) << + boost::str(boost::format("CLC::shouldClose range Trans=%s, Prop: %d/%d, Secs: %d (last:%d)") + % (anyTransactions ? "yes" : "no") % previousProposers % proposersClosed + % currentMSeconds % previousMSeconds); + return true; + } if (!anyTransactions) { // no transactions so far this interval if (proposersClosed > (previousProposers / 4)) // did we miss a transaction? { - Log(lsTRACE) << "no transactions, many proposers: now"; - return currentMSeconds; + Log(lsTRACE) << "no transactions, many proposers: now (" << proposersClosed << " closed, " + << previousProposers << " before)"; + return true; } +#if 0 // This false triggers on the genesis ledger if (previousMSeconds > (1000 * (LEDGER_IDLE_INTERVAL + 2))) // the last ledger was very slow to close { - Log(lsTRACE) << "slow to close"; + Log(lsTRACE) << "was slow to converge (p=" << (previousMSeconds) << ")"; if (previousMSeconds < 2000) return previousMSeconds; return previousMSeconds - 1000; } - return LEDGER_IDLE_INTERVAL * 1000; // normal idle +#endif + return currentMSeconds >= (idleInterval * 1000); // normal idle } - if (previousMSeconds == (1000 * LEDGER_IDLE_INTERVAL)) // coming out of idle, close now - { - Log(lsTRACE) << "leaving idle, close now"; - return currentMSeconds; - } - - Log(lsTRACE) << "close now"; - return currentMSeconds; // this ledger should close now + return true; // this ledger should close now } // Returns whether we have a consensus or not. If so, we expect all honest nodes @@ -101,7 +100,6 @@ bool ContinuousLedgerTiming::haveConsensus( int ContinuousLedgerTiming::getNextLedgerTimeResolution(int previousResolution, bool previousAgree, int ledgerSeq) { assert(ledgerSeq); - assert(previousAgree); // TEMPORARY if ((!previousAgree) && ((ledgerSeq % LEDGER_RES_DECREASE) == 0)) { // reduce resolution int i = 1; diff --git a/src/LedgerTiming.h b/src/LedgerTiming.h index 41e85762a1..551c9bfce6 100644 --- a/src/LedgerTiming.h +++ b/src/LedgerTiming.h @@ -4,8 +4,14 @@ // The number of seconds a ledger may remain idle before closing # define LEDGER_IDLE_INTERVAL 15 -// The number of seconds a validation remains current -# define LEDGER_MAX_INTERVAL (LEDGER_IDLE_INTERVAL * 4) +// The number of seconds a validation remains current after its ledger's close time +// This is a safety to protect against very old validations and the time it takes to adjust +// the close time accuracy window +# define LEDGER_VAL_INTERVAL 600 + +// The number of seconds before a close time that we consider a validation acceptable +// This protects against extreme clock errors +# define LEDGER_EARLY_INTERVAL 240 // The number of milliseconds we wait minimum to ensure participation # define LEDGER_MIN_CONSENSUS 2000 @@ -22,6 +28,16 @@ // How often we check state or change positions (in milliseconds) # define LEDGER_GRANULARITY 1000 +// The percentage of active trusted validators that must be able to +// keep up with the network or we consider the network overloaded +# define LEDGER_NET_RATIO 70 + +// How long we consider a proposal fresh +# define PROPOSE_FRESHNESS 20 + +// How often we force generating a new proposal to keep ours fresh +# define PROPOSE_INTERVAL 12 + // Avalanche tuning #define AV_INIT_CONSENSUS_PCT 50 // percentage of nodes on our UNL that must vote yes @@ -40,10 +56,11 @@ public: // Returns the number of seconds the ledger was or should be open // Call when a consensus is reached and when any transaction is relayed to be added - static int shouldClose( + static bool shouldClose( bool anyTransactions, int previousProposers, int proposersClosed, - int previousSeconds, int currentSeconds); + int previousSeconds, int currentSeconds, + int idleInterval); static bool haveConsensus( int previousProposers, int currentProposers, diff --git a/src/Log.cpp b/src/Log.cpp index bdd56959b2..948552cbf9 100644 --- a/src/Log.cpp +++ b/src/Log.cpp @@ -10,6 +10,8 @@ boost::recursive_mutex Log::sLock; LogSeverity Log::sMinSeverity = lsINFO; std::ofstream* Log::outStream = NULL; +boost::filesystem::path *Log::pathToLog = NULL; +uint32 Log::logRotateCounter = 0; Log::~Log() { @@ -31,6 +33,48 @@ Log::~Log() (*outStream) << logMsg << std::endl; } + +std::string Log::rotateLog(void) +{ + boost::recursive_mutex::scoped_lock sl(sLock); + boost::filesystem::path abs_path; + std::string abs_path_str; + + uint32 failsafe = 0; + + std::string abs_new_path_str; + do { + std::string s; + std::stringstream out; + + failsafe++; + if (failsafe == std::numeric_limits::max()) { + return "unable to create new log file; too many log files!"; + } + abs_path = boost::filesystem::absolute(""); + abs_path /= *pathToLog; + abs_path_str = abs_path.parent_path().string(); + out << logRotateCounter; + s = out.str(); + + + abs_new_path_str = abs_path_str + "/" + s + + "_" + pathToLog->filename().string(); + + logRotateCounter++; + + } while (boost::filesystem::exists(boost::filesystem::path(abs_new_path_str))); + + outStream->close(); + boost::filesystem::rename(abs_path, boost::filesystem::path(abs_new_path_str)); + + + + setLogFile(*pathToLog); + + return abs_new_path_str; + +} + void Log::setMinSeverity(LogSeverity s) { boost::recursive_mutex::scoped_lock sl(sLock); @@ -52,4 +96,6 @@ void Log::setLogFile(boost::filesystem::path path) outStream = newStream; if (outStream) Log(lsINFO) << "Starting up"; + + pathToLog = new boost::filesystem::path(path); } diff --git a/src/Log.h b/src/Log.h index 42c8bb00c9..fd3db13fd3 100644 --- a/src/Log.h +++ b/src/Log.h @@ -6,6 +6,12 @@ #include #include +// Ensure that we don't get value.h without writer.h +#include "../json/json.h" + +#include "types.h" +#include + enum LogSeverity { lsTRACE = 0, @@ -30,6 +36,9 @@ protected: mutable std::ostringstream oss; LogSeverity mSeverity; + static boost::filesystem::path *pathToLog; + static uint32 logRotateCounter; + public: Log(LogSeverity s) : mSeverity(s) { ; } @@ -48,6 +57,7 @@ public: static void setMinSeverity(LogSeverity); static void setLogFile(boost::filesystem::path); + static std::string rotateLog(void); }; #endif diff --git a/src/NetworkOPs.cpp b/src/NetworkOPs.cpp index 875834a1e5..4f24651c92 100644 --- a/src/NetworkOPs.cpp +++ b/src/NetworkOPs.cpp @@ -25,7 +25,7 @@ NetworkOPs::NetworkOPs(boost::asio::io_service& io_service, LedgerMaster* pLedgerMaster) : mMode(omDISCONNECTED),mNetTimer(io_service), mLedgerMaster(pLedgerMaster), mCloseTimeOffset(0), - mLastCloseProposers(0), mLastCloseConvergeTime(LEDGER_IDLE_INTERVAL) + mLastCloseProposers(0), mLastCloseConvergeTime(1000 * LEDGER_IDLE_INTERVAL), mLastValidationTime(0) { } @@ -33,7 +33,7 @@ boost::posix_time::ptime NetworkOPs::getNetworkTimePT() { int offset = 0; theApp->getSystemTimeOffset(offset); - return boost::posix_time::second_clock::universal_time() + boost::posix_time::seconds(offset); + return boost::posix_time::microsec_clock::universal_time() + boost::posix_time::seconds(offset); } uint32 NetworkOPs::getNetworkTimeNC() @@ -46,32 +46,62 @@ uint32 NetworkOPs::getCloseTimeNC() return iToSeconds(getNetworkTimePT() + boost::posix_time::seconds(mCloseTimeOffset)); } +uint32 NetworkOPs::getValidationTimeNC() +{ + uint32 vt = getNetworkTimeNC(); + if (vt <= mLastValidationTime) + vt = mLastValidationTime + 1; + mLastValidationTime = vt; + return vt; +} + +void NetworkOPs::closeTimeOffset(int offset) +{ // take large offsets, ignore small offsets, push towards our wall time + if (offset > 1) + mCloseTimeOffset += (offset + 3) / 4; + else if (offset < -1) + mCloseTimeOffset += (offset - 3) / 4; + else + mCloseTimeOffset = (mCloseTimeOffset * 3) / 4; + if (mCloseTimeOffset) + Log(lsINFO) << "Close time offset now " << mCloseTimeOffset; +} + +uint32 NetworkOPs::getLedgerID(const uint256& hash) +{ + Ledger::ref lrLedger = mLedgerMaster->getLedgerByHash(hash); + + return lrLedger ? lrLedger->getLedgerSeq() : 0; +} + uint32 NetworkOPs::getCurrentLedgerID() { return mLedgerMaster->getCurrentLedger()->getLedgerSeq(); } // Sterilize transaction through serialization. -Transaction::pointer NetworkOPs::submitTransaction(Transaction::pointer tpTrans) +Transaction::pointer NetworkOPs::submitTransaction(const Transaction::pointer& tpTrans) { Serializer s; - tpTrans->getSTransaction()->add(s); - std::vector vucTransaction = s.getData(); - - SerializerIterator sit(s); - Transaction::pointer tpTransNew = Transaction::sharedTransaction(s.getData(), true); - assert(tpTransNew); + if(!tpTransNew->getSTransaction()->isEquivalent(*tpTrans->getSTransaction())) + { + Log(lsFATAL) << "Transaction reconstruction failure"; + Log(lsFATAL) << tpTransNew->getSTransaction()->getJson(0); + Log(lsFATAL) << tpTrans->getSTransaction()->getJson(0); + assert(false); + } + (void) NetworkOPs::processTransaction(tpTransNew); return tpTransNew; } -Transaction::pointer NetworkOPs::processTransaction(Transaction::pointer trans, uint32 tgtLedger, Peer* source) +Transaction::pointer NetworkOPs::processTransaction(Transaction::pointer trans, Peer* source) { Transaction::pointer dbtx = theApp->getMasterTransaction().fetch(trans->getID(), true); if (dbtx) return dbtx; @@ -83,8 +113,19 @@ Transaction::pointer NetworkOPs::processTransaction(Transaction::pointer trans, return trans; } - TransactionEngineResult r = mLedgerMaster->doTransaction(*trans->getSTransaction(), tgtLedger, tepNONE); - if (r == tenFAILED) throw Fault(IO_ERROR); + TER r = mLedgerMaster->doTransaction(*trans->getSTransaction(), tapOPEN_LEDGER); + +#ifdef DEBUG + if (r != tesSUCCESS) + { + std::string token, human; + if (transResultInfo(r, token, human)) + Log(lsINFO) << "TransactionResult: " << token << ": " << human; + } +#endif + + if (r == tefFAILURE) + throw Fault(IO_ERROR); if (r == terPRE_SEQ) { // transaction should be held @@ -94,14 +135,14 @@ Transaction::pointer NetworkOPs::processTransaction(Transaction::pointer trans, mLedgerMaster->addHeldTransaction(trans); return trans; } - if ((r == terPAST_SEQ) || (r == terPAST_LEDGER)) + if ((r == tefPAST_SEQ)) { // duplicate or conflict Log(lsINFO) << "Transaction is obsolete"; trans->setStatus(OBSOLETE); return trans; } - if (r == terSUCCESS) + if (r == tesSUCCESS) { Log(lsINFO) << "Transaction is now included"; trans->setStatus(INCLUDED); @@ -118,10 +159,10 @@ Transaction::pointer NetworkOPs::processTransaction(Transaction::pointer trans, tx.set_rawtransaction(&s.getData().front(), s.getLength()); tx.set_status(newcoin::tsCURRENT); tx.set_receivetimestamp(getNetworkTimeNC()); - tx.set_ledgerindexpossible(trans->getLedger()); PackedMessage::pointer packet = boost::make_shared(tx, newcoin::mtTRANSACTION); - theApp->getConnectionPool().relayMessage(source, packet); + int sentTo = theApp->getConnectionPool().relayMessage(source, packet); + Log(lsINFO) << "Transaction relayed to " << sentTo << " node(s)"; return trans; } @@ -135,7 +176,6 @@ Transaction::pointer NetworkOPs::processTransaction(Transaction::pointer trans, tx.set_rawtransaction(&s.getData().front(), s.getLength()); tx.set_status(newcoin::tsCURRENT); tx.set_receivetimestamp(getNetworkTimeNC()); - tx.set_ledgerindexpossible(tgtLedger); PackedMessage::pointer packet = boost::make_shared(tx, newcoin::mtTRANSACTION); theApp->getConnectionPool().relayMessage(source, packet); } @@ -191,7 +231,11 @@ SLE::pointer NetworkOPs::getGenerator(const uint256& uLedger, const uint160& uGe { LedgerStateParms qry = lepNONE; - return mLedgerMaster->getLedgerByHash(uLedger)->getGenerator(qry, uGeneratorID); + Ledger::pointer ledger = mLedgerMaster->getLedgerByHash(uLedger); + if (!ledger) + return SLE::pointer(); + else + return ledger->getGenerator(qry, uGeneratorID); } // @@ -213,12 +257,12 @@ STVector256 NetworkOPs::getDirNodeInfo( { Log(lsDEBUG) << "getDirNodeInfo: node index: " << uNodeIndex.ToString(); - Log(lsTRACE) << "getDirNodeInfo: first: " << strHex(sleNode->getIFieldU64(sfIndexPrevious)); - Log(lsTRACE) << "getDirNodeInfo: last: " << strHex(sleNode->getIFieldU64(sfIndexNext)); + Log(lsTRACE) << "getDirNodeInfo: first: " << strHex(sleNode->getFieldU64(sfIndexPrevious)); + Log(lsTRACE) << "getDirNodeInfo: last: " << strHex(sleNode->getFieldU64(sfIndexNext)); - uNodePrevious = sleNode->getIFieldU64(sfIndexPrevious); - uNodeNext = sleNode->getIFieldU64(sfIndexNext); - svIndexes = sleNode->getIFieldV256(sfIndexes); + uNodePrevious = sleNode->getFieldU64(sfIndexPrevious); + uNodeNext = sleNode->getFieldU64(sfIndexNext); + svIndexes = sleNode->getFieldV256(sfIndexes); Log(lsTRACE) << "getDirNodeInfo: first: " << strHex(uNodePrevious); Log(lsTRACE) << "getDirNodeInfo: last: " << strHex(uNodeNext); @@ -254,7 +298,7 @@ Json::Value NetworkOPs::getOwnerInfo(const uint256& uLedger, const NewcoinAddres Json::Value NetworkOPs::getOwnerInfo(Ledger::pointer lpLedger, const NewcoinAddress& naAccount) { - Json::Value jvObjects(Json::arrayValue); + Json::Value jvObjects(Json::objectValue); uint256 uRootIndex = lpLedger->getOwnerDirIndex(naAccount.getAccountID()); @@ -267,18 +311,40 @@ Json::Value NetworkOPs::getOwnerInfo(Ledger::pointer lpLedger, const NewcoinAddr do { - STVector256 svIndexes = sleNode->getIFieldV256(sfIndexes); + STVector256 svIndexes = sleNode->getFieldV256(sfIndexes); const std::vector& vuiIndexes = svIndexes.peekValue(); BOOST_FOREACH(const uint256& uDirEntry, vuiIndexes) { - LedgerStateParms lspOffer = lepNONE; - SLE::pointer sleOffer = lpLedger->getOffer(lspOffer, uDirEntry); + SLE::pointer sleCur = lpLedger->getSLE(uDirEntry); - jvObjects.append(sleOffer->getJson(0)); + switch (sleCur->getType()) + { + case ltOFFER: + if (!jvObjects.isMember("offers")) + jvObjects["offers"] = Json::Value(Json::arrayValue); + + jvObjects["offers"].append(sleCur->getJson(0)); + break; + + case ltRIPPLE_STATE: + if (!jvObjects.isMember("ripple_lines")) + jvObjects["ripple_lines"] = Json::Value(Json::arrayValue); + + jvObjects["ripple_lines"].append(sleCur->getJson(0)); + break; + + case ltACCOUNT_ROOT: + case ltDIR_NODE: + case ltGENERATOR_MAP: + case ltNICKNAME: + default: + assert(false); + break; + } } - uNodeDir = sleNode->getIFieldU64(sfIndexNext); + uNodeDir = sleNode->getFieldU64(sfIndexNext); if (uNodeDir) { lspNode = lepNONE; @@ -292,15 +358,6 @@ Json::Value NetworkOPs::getOwnerInfo(Ledger::pointer lpLedger, const NewcoinAddr return jvObjects; } -// -// Ripple functions -// - -RippleState::pointer NetworkOPs::accessRippleState(const uint256& uLedger, const uint256& uIndex) -{ - return mLedgerMaster->getLedgerByHash(uLedger)->accessRippleState(uIndex); -} - // // Other // @@ -322,16 +379,20 @@ public: { if (trustedValidations > v.trustedValidations) return true; if (trustedValidations < v.trustedValidations) return false; - if (nodesUsing > v.nodesUsing) return true; - if (nodesUsing < v.nodesUsing) return false; + if (trustedValidations == 0) + { + if (nodesUsing > v.nodesUsing) return true; + if (nodesUsing < v.nodesUsing) return false; + } return highNode > v.highNode; } }; void NetworkOPs::checkState(const boost::system::error_code& result) { // Network state machine - if (result == boost::asio::error::operation_aborted) + if ((result == boost::asio::error::operation_aborted) || theConfig.RUN_STANDALONE) return; + setStateTimer(); std::vector peerList = theApp->getConnectionPool().getPeerVector(); @@ -344,7 +405,6 @@ void NetworkOPs::checkState(const boost::system::error_code& result) Log(lsWARNING) << "Node count (" << peerList.size() << ") has fallen below quorum (" << theConfig.NETWORK_QUORUM << ")."; } - setStateTimer(); return; } if (mMode == omDISCONNECTED) @@ -356,7 +416,6 @@ void NetworkOPs::checkState(const boost::system::error_code& result) if (mConsensus) { mConsensus->timerEntry(); - setStateTimer(); return; } @@ -383,15 +442,13 @@ void NetworkOPs::checkState(const boost::system::error_code& result) // check if the ledger is good enough to go to omFULL // Note: Do not go to omFULL if we don't have the previous ledger // check if the ledger is bad enough to go to omCONNECTED -- TODO - if (theApp->getOPs().getNetworkTimeNC() < - (theApp->getMasterLedger().getCurrentLedger()->getCloseTimeNC() + 4)) + if (theApp->getOPs().getNetworkTimeNC() < theApp->getMasterLedger().getCurrentLedger()->getCloseTimeNC()) setMode(omFULL); - else - Log(lsINFO) << "Will try to go to FULL in consensus window"; } if (mMode == omFULL) { + // WRITEME // check if the ledger is bad enough to go to omTRACKING } @@ -399,7 +456,6 @@ void NetworkOPs::checkState(const boost::system::error_code& result) beginConsensus(networkClosed, theApp->getMasterLedger().getCurrentLedger()); if (mConsensus) mConsensus->timerEntry(); - setStateTimer(); } bool NetworkOPs::checkLastClosedLedger(const std::vector& peerList, uint256& networkClosed) @@ -412,17 +468,18 @@ bool NetworkOPs::checkLastClosedLedger(const std::vector& peerLis // node is using. THis is kind of fundamental. Log(lsTRACE) << "NetworkOPs::checkLastClosedLedger"; - boost::unordered_map ledgers; - - { - boost::unordered_map current = theApp->getValidations().getCurrentValidations(); - for (boost::unordered_map::iterator it = current.begin(), end = current.end(); it != end; ++it) - ledgers[it->first].trustedValidations += it->second; - } - Ledger::pointer ourClosed = mLedgerMaster->getClosedLedger(); uint256 closedLedger = ourClosed->getHash(); uint256 prevClosedLedger = ourClosed->getParentHash(); + + boost::unordered_map ledgers; + { + boost::unordered_map current = theApp->getValidations().getCurrentValidations(closedLedger); + typedef std::pair u256_int_pair; + BOOST_FOREACH(u256_int_pair& it, current) + ledgers[it.first].trustedValidations += it.second; + } + ValidationCount& ourVC = ledgers[closedLedger]; if ((theConfig.LEDGER_CREATOR) && (mMode >= omTRACKING)) @@ -431,23 +488,22 @@ bool NetworkOPs::checkLastClosedLedger(const std::vector& peerLis ourVC.highNode = theApp->getWallet().getNodePublic(); } - for (std::vector::const_iterator it = peerList.begin(), end = peerList.end(); it != end; ++it) + BOOST_FOREACH(Peer::ref it, peerList) { - if (!*it) + if (!it) { Log(lsDEBUG) << "NOP::CS Dead pointer in peer list"; } - else if ((*it)->isConnected()) + else if (it->isConnected()) { - uint256 peerLedger = (*it)->getClosedLedgerHash(); + uint256 peerLedger = it->getClosedLedgerHash(); if (peerLedger.isNonZero()) { ValidationCount& vc = ledgers[peerLedger]; - if ((vc.nodesUsing == 0) || ((*it)->getNodePublic() > vc.highNode)) - vc.highNode = (*it)->getNodePublic(); + if ((vc.nodesUsing == 0) || (it->getNodePublic() > vc.highNode)) + vc.highNode = it->getNodePublic(); ++vc.nodesUsing; } - else Log(lsTRACE) << "Connected peer announces no LCL " << (*it)->getIP(); } } @@ -458,9 +514,9 @@ bool NetworkOPs::checkLastClosedLedger(const std::vector& peerLis for (boost::unordered_map::iterator it = ledgers.begin(), end = ledgers.end(); it != end; ++it) { - Log(lsTRACE) << "L: " << it->first.GetHex() << - " t=" << it->second.trustedValidations << ", n=" << it->second.nodesUsing; - if ((it->second > bestVC) && !theApp->getValidations().isDeadLedger(it->first)) + Log(lsTRACE) << "L: " << it->first << " t=" << it->second.trustedValidations << + ", n=" << it->second.nodesUsing; + if (it->second > bestVC) { bestVC = it->second; closedLedger = it->first; @@ -488,16 +544,17 @@ bool NetworkOPs::checkLastClosedLedger(const std::vector& peerLis } Log(lsWARNING) << "We are not running on the consensus ledger"; - Log(lsINFO) << "Our LCL " << ourClosed->getHash().GetHex(); - Log(lsINFO) << "Net LCL " << closedLedger.GetHex(); + Log(lsINFO) << "Our LCL " << ourClosed->getHash(); + Log(lsINFO) << "Net LCL " << closedLedger; if ((mMode == omTRACKING) || (mMode == omFULL)) setMode(omCONNECTED); Ledger::pointer consensus = mLedgerMaster->getLedgerByHash(closedLedger); if (!consensus) { - Log(lsINFO) << "Acquiring consensus ledger " << closedLedger.GetHex(); - LedgerAcquire::pointer mAcquiringLedger = theApp->getMasterLedgerAcquire().findCreate(closedLedger); + Log(lsINFO) << "Acquiring consensus ledger " << closedLedger; + if (!mAcquiringLedger || (mAcquiringLedger->getHash() != closedLedger)) + mAcquiringLedger = theApp->getMasterLedgerAcquire().findCreate(closedLedger); if (!mAcquiringLedger || mAcquiringLedger->isFailed()) { theApp->getMasterLedgerAcquire().dropLedger(closedLedger); @@ -508,23 +565,19 @@ bool NetworkOPs::checkLastClosedLedger(const std::vector& peerLis { // add more peers int count = 0; std::vector peers=theApp->getConnectionPool().getPeerVector(); - for (std::vector::const_iterator it = peerList.begin(), end = peerList.end(); - it != end; ++it) + BOOST_FOREACH(Peer::ref it, peerList) { - if ((*it)->getClosedLedgerHash() == closedLedger) + if (it->getClosedLedgerHash() == closedLedger) { ++count; - mAcquiringLedger->peerHas(*it); + mAcquiringLedger->peerHas(it); } } if (!count) { // just ask everyone - for (std::vector::const_iterator it = peerList.begin(), end = peerList.end(); - it != end; ++it) - { - if ((*it)->isConnected()) - mAcquiringLedger->peerHas(*it); - } + BOOST_FOREACH(Peer::ref it, peerList) + if (it->isConnected()) + mAcquiringLedger->peerHas(it); } return true; } @@ -542,9 +595,9 @@ void NetworkOPs::switchLastClosedLedger(Ledger::pointer newLedger, bool duringCo { // set the newledger as our last closed ledger -- this is abnormal code if (duringConsensus) - Log(lsERROR) << "JUMPdc last closed ledger to " << newLedger->getHash().GetHex(); + Log(lsERROR) << "JUMPdc last closed ledger to " << newLedger->getHash(); else - Log(lsERROR) << "JUMP last closed ledger to " << newLedger->getHash().GetHex(); + Log(lsERROR) << "JUMP last closed ledger to " << newLedger->getHash(); newLedger->setClosed(); Ledger::pointer openLedger = boost::make_shared(false, boost::ref(*newLedger)); @@ -565,7 +618,7 @@ void NetworkOPs::switchLastClosedLedger(Ledger::pointer newLedger, bool duringCo int NetworkOPs::beginConsensus(const uint256& networkClosed, Ledger::pointer closingLedger) { Log(lsINFO) << "Consensus time for ledger " << closingLedger->getLedgerSeq(); - Log(lsINFO) << " LCL is " << closingLedger->getParentHash().GetHex(); + Log(lsINFO) << " LCL is " << closingLedger->getParentHash(); Ledger::pointer prevLedger = mLedgerMaster->getLedgerByHash(closingLedger->getParentHash()); if (!prevLedger) @@ -582,14 +635,33 @@ int NetworkOPs::beginConsensus(const uint256& networkClosed, Ledger::pointer clo prevLedger->setImmutable(); mConsensus = boost::make_shared( networkClosed, prevLedger, theApp->getMasterLedger().getCurrentLedger()->getCloseTimeNC()); + mConsensus->swapDefer(mDeferredProposals); Log(lsDEBUG) << "Initiating consensus engine"; return mConsensus->startup(); } +bool NetworkOPs::haveConsensusObject() +{ + if (mConsensus) + return true; + if (mMode != omFULL) + return false; + + uint256 networkClosed; + std::vector peerList = theApp->getConnectionPool().getPeerVector(); + bool ledgerChange = checkLastClosedLedger(peerList, networkClosed); + if (!ledgerChange) + { + Log(lsWARNING) << "Beginning consensus due to peer action"; + beginConsensus(networkClosed, theApp->getMasterLedger().getCurrentLedger()); + } + return mConsensus; +} + // <-- bool: true to relay -bool NetworkOPs::recvPropose(uint32 proposeSeq, const uint256& proposeHash, uint32 closeTime, - const std::string& pubKey, const std::string& signature) +bool NetworkOPs::recvPropose(uint32 proposeSeq, const uint256& proposeHash, const uint256& prevLedger, + uint32 closeTime, const std::string& pubKey, const std::string& signature, const NewcoinAddress& nodePublic) { // JED: does mConsensus need to be locked? @@ -597,89 +669,123 @@ bool NetworkOPs::recvPropose(uint32 proposeSeq, const uint256& proposeHash, uint // XXX Take a vuc for pubkey. // Get a preliminary hash to use to suppress duplicates - Serializer s(128); + Serializer s(256); + s.add256(proposeHash); + s.add256(prevLedger); s.add32(proposeSeq); - s.add32(getCurrentLedgerID()); + s.add32(closeTime); s.addRaw(pubKey); + s.addRaw(signature); if (!theApp->isNew(s.getSHA512Half())) return false; - if (!mConsensus) - { // FIXME: CLC - Log(lsWARNING) << "Received proposal when full but not during consensus window"; + NewcoinAddress naPeerPublic = NewcoinAddress::createNodePublic(strCopy(pubKey)); + + if (!haveConsensusObject()) + { + Log(lsINFO) << "Received proposal outside consensus window"; + return mMode != omFULL; + } + + // Is this node on our UNL? + if (!theApp->getUNL().nodeInUNL(naPeerPublic)) + { + Log(lsINFO) << "Untrusted proposal: " << naPeerPublic.humanNodePublic() << " " << proposeHash; + return true; + } + + if (prevLedger.isNonZero()) + { // proposal includes a previous ledger + LedgerProposal::pointer proposal = + boost::make_shared(prevLedger, proposeSeq, proposeHash, closeTime, naPeerPublic); + if (!proposal->checkSign(signature)) + { + Log(lsWARNING) << "New-style ledger proposal fails signature check"; + return false; + } + if (prevLedger == mConsensus->getLCL()) + return mConsensus->peerPosition(proposal); + mConsensus->deferProposal(proposal, nodePublic); return false; } - NewcoinAddress naPeerPublic = NewcoinAddress::createNodePublic(strCopy(pubKey)); LedgerProposal::pointer proposal = boost::make_shared(mConsensus->getLCL(), proposeSeq, proposeHash, closeTime, naPeerPublic); if (!proposal->checkSign(signature)) { // Note that if the LCL is different, the signature check will fail Log(lsWARNING) << "Ledger proposal fails signature check"; + proposal->setSignature(signature); + mConsensus->deferProposal(proposal, nodePublic); return false; } - - // Is this node on our UNL? - if (!theApp->getUNL().nodeInUNL(proposal->peekPublic())) - { - Log(lsINFO) << "Untrusted proposal: " << naPeerPublic.humanNodePublic() << " " << - proposal->getCurrentHash().GetHex(); - return true; - } - return mConsensus->peerPosition(proposal); } SHAMap::pointer NetworkOPs::getTXMap(const uint256& hash) { - if (!mConsensus) return SHAMap::pointer(); + if (!haveConsensusObject()) + return SHAMap::pointer(); return mConsensus->getTransactionTree(hash, false); } -bool NetworkOPs::gotTXData(boost::shared_ptr peer, const uint256& hash, +bool NetworkOPs::gotTXData(const boost::shared_ptr& peer, const uint256& hash, const std::list& nodeIDs, const std::list< std::vector >& nodeData) { - if (!mConsensus) return false; + if (!haveConsensusObject()) + return false; return mConsensus->peerGaveNodes(peer, hash, nodeIDs, nodeData); } -bool NetworkOPs::hasTXSet(boost::shared_ptr peer, const uint256& set, newcoin::TxSetStatus status) +bool NetworkOPs::hasTXSet(const boost::shared_ptr& peer, const uint256& set, newcoin::TxSetStatus status) { - if (!mConsensus) return false; + if (!haveConsensusObject()) + { + Log(lsINFO) << "Peer has TX set, not during consensus"; + return false; + } return mConsensus->peerHasSet(peer, set, status); } -void NetworkOPs::mapComplete(const uint256& hash, SHAMap::pointer map) +void NetworkOPs::mapComplete(const uint256& hash, SHAMap::ref map) { - if (mConsensus) + if (!haveConsensusObject()) mConsensus->mapComplete(hash, map, true); } void NetworkOPs::endConsensus(bool correctLCL) { uint256 deadLedger = theApp->getMasterLedger().getClosedLedger()->getParentHash(); - Log(lsTRACE) << "Ledger " << deadLedger.GetHex() << " is now dead"; - theApp->getValidations().addDeadLedger(deadLedger); std::vector peerList = theApp->getConnectionPool().getPeerVector(); - for (std::vector::const_iterator it = peerList.begin(), end = peerList.end(); it != end; ++it) - if (*it && ((*it)->getClosedLedgerHash() == deadLedger)) - { - Log(lsTRACE) << "Killing obsolete peer status"; - (*it)->cycleStatus(); - } + BOOST_FOREACH(Peer::ref it, peerList) + if (it && (it->getClosedLedgerHash() == deadLedger)) + { + Log(lsTRACE) << "Killing obsolete peer status"; + it->cycleStatus(); + } + mConsensus->swapDefer(mDeferredProposals); mConsensus = boost::shared_ptr(); } +void NetworkOPs::consensusViewChange() +{ + if ((mMode == omFULL) || (mMode == omTRACKING)) + setMode(omCONNECTED); +} + void NetworkOPs::setMode(OperatingMode om) { if (mMode == om) return; if ((om >= omCONNECTED) && (mMode == omDISCONNECTED)) mConnectTime = boost::posix_time::second_clock::universal_time(); - Log l((om < mMode) ? lsWARNING : lsINFO); - if (om == omDISCONNECTED) l << "STATE->Disonnected"; - else if (om == omCONNECTED) l << "STATE->Connected"; - else if (om == omTRACKING) l << "STATE->Tracking"; - else l << "STATE->Full"; + Log lg((om < mMode) ? lsWARNING : lsINFO); + if (om == omDISCONNECTED) + lg << "STATE->Disconnected"; + else if (om == omCONNECTED) + lg << "STATE->Connected"; + else if (om == omTRACKING) + lg << "STATE->Tracking"; + else + lg << "STATE->Full"; mMode = om; } @@ -726,9 +832,9 @@ std::vector return accounts; } -bool NetworkOPs::recvValidation(SerializedValidation::pointer val) +bool NetworkOPs::recvValidation(const SerializedValidation::pointer& val) { - Log(lsINFO) << "recvValidation " << val->getLedgerHash().GetHex(); + Log(lsINFO) << "recvValidation " << val->getLedgerHash(); return theApp->getValidations().addValidation(val); } @@ -748,6 +854,11 @@ Json::Value NetworkOPs::getServerInfo() if (!theConfig.VALIDATION_SEED.isValid()) info["serverState"] = "none"; else info["validationPKey"] = NewcoinAddress::createNodePublic(theConfig.VALIDATION_SEED).humanNodePublic(); + Json::Value lastClose = Json::objectValue; + lastClose["proposers"] = theApp->getOPs().getPreviousProposers(); + lastClose["convergeTime"] = theApp->getOPs().getPreviousConvergeTime(); + info["lastClose"] = lastClose; + if (mConsensus) info["consensus"] = mConsensus->getJson(); @@ -758,7 +869,7 @@ Json::Value NetworkOPs::getServerInfo() // Monitoring: publisher side // -Json::Value NetworkOPs::pubBootstrapAccountInfo(const Ledger::pointer& lpAccepted, const NewcoinAddress& naAccountID) +Json::Value NetworkOPs::pubBootstrapAccountInfo(Ledger::ref lpAccepted, const NewcoinAddress& naAccountID) { Json::Value jvObj(Json::objectValue); @@ -793,7 +904,7 @@ void NetworkOPs::pubAccountInfo(const NewcoinAddress& naAccountID, const Json::V } } -void NetworkOPs::pubLedger(const Ledger::pointer& lpAccepted) +void NetworkOPs::pubLedger(Ledger::ref lpAccepted) { { boost::interprocess::sharable_lock sl(mMonitorLock); @@ -854,7 +965,7 @@ void NetworkOPs::pubLedger(const Ledger::pointer& lpAccepted) SerializedTransaction::pointer stTxn = theApp->getMasterTransaction().fetch(item, false, 0); // XXX Need to support other results. // XXX Need to give failures too. - TransactionEngineResult terResult = terSUCCESS; + TER terResult = tesSUCCESS; if (bAll) { @@ -888,7 +999,7 @@ void NetworkOPs::pubLedger(const Ledger::pointer& lpAccepted) // XXX Publish delta information for accounts. } -Json::Value NetworkOPs::transJson(const SerializedTransaction& stTxn, TransactionEngineResult terResult, const std::string& strStatus, int iSeq, const std::string& strType) +Json::Value NetworkOPs::transJson(const SerializedTransaction& stTxn, TER terResult, const std::string& strStatus, int iSeq, const std::string& strType) { Json::Value jvObj(Json::objectValue); std::string strToken; @@ -906,7 +1017,7 @@ Json::Value NetworkOPs::transJson(const SerializedTransaction& stTxn, Transactio return jvObj; } -void NetworkOPs::pubTransactionAll(const Ledger::pointer& lpCurrent, const SerializedTransaction& stTxn, TransactionEngineResult terResult, const char* pState) +void NetworkOPs::pubTransactionAll(Ledger::ref lpCurrent, const SerializedTransaction& stTxn, TER terResult, const char* pState) { Json::Value jvObj = transJson(stTxn, terResult, pState, lpCurrent->getLedgerSeq(), "transaction"); @@ -916,7 +1027,7 @@ void NetworkOPs::pubTransactionAll(const Ledger::pointer& lpCurrent, const Seria } } -void NetworkOPs::pubTransactionAccounts(const Ledger::pointer& lpCurrent, const SerializedTransaction& stTxn, TransactionEngineResult terResult, const char* pState) +void NetworkOPs::pubTransactionAccounts(Ledger::ref lpCurrent, const SerializedTransaction& stTxn, TER terResult, const char* pState) { boost::unordered_set usisNotify; @@ -951,7 +1062,7 @@ void NetworkOPs::pubTransactionAccounts(const Ledger::pointer& lpCurrent, const } } -void NetworkOPs::pubTransaction(const Ledger::pointer& lpCurrent, const SerializedTransaction& stTxn, TransactionEngineResult terResult) +void NetworkOPs::pubTransaction(Ledger::ref lpCurrent, const SerializedTransaction& stTxn, TER terResult) { boost::interprocess::sharable_lock sl(mMonitorLock); diff --git a/src/NetworkOPs.h b/src/NetworkOPs.h index 8e8e45c24c..4c64374f17 100644 --- a/src/NetworkOPs.h +++ b/src/NetworkOPs.h @@ -1,17 +1,18 @@ #ifndef __NETWORK_OPS__ #define __NETWORK_OPS__ +#include +#include +#include +#include + #include "AccountState.h" #include "LedgerMaster.h" #include "NicknameState.h" #include "RippleState.h" #include "SerializedValidation.h" #include "LedgerAcquire.h" - -#include -#include -#include -#include +#include "LedgerProposal.h" // Operations that clients may wish to perform against the network // Master operational handler, server sequencer, network tracker @@ -54,6 +55,8 @@ protected: boost::posix_time::ptime mConnectTime; boost::asio::deadline_timer mNetTimer; boost::shared_ptr mConsensus; + boost::unordered_map > mDeferredProposals; LedgerMaster* mLedgerMaster; LedgerAcquire::pointer mAcquiringLedger; @@ -63,7 +66,8 @@ protected: // last ledger close int mLastCloseProposers, mLastCloseConvergeTime; uint256 mLastCloseHash; - uint32 mLastCloseNetTime; + uint32 mLastCloseTime; + uint32 mLastValidationTime; // XXX Split into more locks. boost::interprocess::interprocess_upgradable_mutex mMonitorLock; @@ -77,11 +81,12 @@ protected: void setMode(OperatingMode); - Json::Value transJson(const SerializedTransaction& stTxn, TransactionEngineResult terResult, const std::string& strStatus, int iSeq, const std::string& strType); - void pubTransactionAll(const Ledger::pointer& lpCurrent, const SerializedTransaction& stTxn, TransactionEngineResult terResult, const char* pState); - void pubTransactionAccounts(const Ledger::pointer& lpCurrent, const SerializedTransaction& stTxn, TransactionEngineResult terResult, const char* pState); + Json::Value transJson(const SerializedTransaction& stTxn, TER terResult, const std::string& strStatus, int iSeq, const std::string& strType); + void pubTransactionAll(Ledger::ref lpCurrent, const SerializedTransaction& stTxn, TER terResult, const char* pState); + void pubTransactionAccounts(Ledger::ref lpCurrent, const SerializedTransaction& stTxn, TER terResult, const char* pState); + bool haveConsensusObject(); - Json::Value pubBootstrapAccountInfo(const Ledger::pointer& lpAccepted, const NewcoinAddress& naAccountID); + Json::Value pubBootstrapAccountInfo(Ledger::ref lpAccepted, const NewcoinAddress& naAccountID); public: NetworkOPs(boost::asio::io_service& io_service, LedgerMaster* pLedgerMaster); @@ -89,7 +94,10 @@ public: // network information uint32 getNetworkTimeNC(); uint32 getCloseTimeNC(); + uint32 getValidationTimeNC(); + void closeTimeOffset(int); boost::posix_time::ptime getNetworkTimePT(); + uint32 getLedgerID(const uint256& hash); uint32 getCurrentLedgerID(); OperatingMode getOperatingMode() { return mMode; } inline bool available() { @@ -97,21 +105,21 @@ public: return mMode >= omTRACKING; } + Ledger::pointer getCurrentLedger() { return mLedgerMaster->getCurrentLedger(); } + Ledger::pointer getLedgerByHash(const uint256& hash) { return mLedgerMaster->getLedgerByHash(hash); } + Ledger::pointer getLedgerBySeq(const uint32 seq) { return mLedgerMaster->getLedgerBySeq(seq); } + uint256 getClosedLedger() { return mLedgerMaster->getClosedLedger()->getHash(); } - // FIXME: This function is basically useless since the hash is constantly changing and the ledger - // is ephemeral and mutable. - uint256 getCurrentLedger() - { return mLedgerMaster->getCurrentLedger()->getHash(); } + SLE::pointer getSLE(Ledger::pointer lpLedger, const uint256& uHash) { return lpLedger->getSLE(uHash); } // // Transaction operations // - Transaction::pointer submitTransaction(Transaction::pointer tpTrans); + Transaction::pointer submitTransaction(const Transaction::pointer& tpTrans); - Transaction::pointer processTransaction(Transaction::pointer transaction, uint32 targetLedger = 0, - Peer* source = NULL); + Transaction::pointer processTransaction(Transaction::pointer transaction, Peer* source = NULL); Transaction::pointer findTransactionByID(const uint256& transactionID); int findTransactionsBySource(const uint256& uLedger, std::list&, const NewcoinAddress& sourceAccount, uint32 minSeq, uint32 maxSeq); @@ -129,8 +137,8 @@ public: // Directory functions // - STVector256 getDirNodeInfo(const uint256& uLedger, const uint256& uRootIndex, - uint64& uNodePrevious, uint64& uNodeNext); + STVector256 getDirNodeInfo(const uint256& uLedger, const uint256& uRootIndex, + uint64& uNodePrevious, uint64& uNodeNext); // // Nickname functions @@ -145,21 +153,6 @@ public: Json::Value getOwnerInfo(const uint256& uLedger, const NewcoinAddress& naAccount); Json::Value getOwnerInfo(Ledger::pointer lpLedger, const NewcoinAddress& naAccount); - // - // Ripple functions - // - - bool getDirLineInfo(const uint256& uLedger, const NewcoinAddress& naAccount, uint256& uRootIndex) - { - LedgerStateParms lspNode = lepNONE; - - uRootIndex = Ledger::getRippleDirIndex(naAccount.getAccountID()); - - return !!mLedgerMaster->getLedgerByHash(uLedger)->getDirNode(lspNode, uRootIndex); - } - - RippleState::pointer accessRippleState(const uint256& uLedger, const uint256& uIndex); - // raw object operations bool findRawLedger(const uint256& ledgerHash, std::vector& rawLedger); bool findRawTransaction(const uint256& transactionHash, std::vector& rawTransaction); @@ -173,14 +166,14 @@ public: const std::vector& myNode, std::list< std::vector >& newNodes); // ledger proposal/close functions - bool recvPropose(uint32 proposeSeq, const uint256& proposeHash, uint32 closeTime, - const std::string& pubKey, const std::string& signature); - bool gotTXData(boost::shared_ptr peer, const uint256& hash, + bool recvPropose(uint32 proposeSeq, const uint256& proposeHash, const uint256& prevLedger, uint32 closeTime, + const std::string& pubKey, const std::string& signature, const NewcoinAddress& nodePublic); + bool gotTXData(const boost::shared_ptr& peer, const uint256& hash, const std::list& nodeIDs, const std::list< std::vector >& nodeData); - bool recvValidation(SerializedValidation::pointer val); + bool recvValidation(const SerializedValidation::pointer& val); SHAMap::pointer getTXMap(const uint256& hash); - bool hasTXSet(boost::shared_ptr peer, const uint256& set, newcoin::TxSetStatus status); - void mapComplete(const uint256& hash, SHAMap::pointer map); + bool hasTXSet(const boost::shared_ptr& peer, const uint256& set, newcoin::TxSetStatus status); + void mapComplete(const uint256& hash, SHAMap::ref map); // network state machine void checkState(const boost::system::error_code& result); @@ -188,12 +181,14 @@ public: bool checkLastClosedLedger(const std::vector&, uint256& networkClosed); int beginConsensus(const uint256& networkClosed, Ledger::pointer closingLedger); void endConsensus(bool correctLCL); + void setStandAlone() { setMode(omFULL); } void setStateTimer(); void newLCL(int proposers, int convergeTime, const uint256& ledgerHash); + void consensusViewChange(); int getPreviousProposers() { return mLastCloseProposers; } int getPreviousConvergeTime() { return mLastCloseConvergeTime; } - uint32 getLastCloseNetTime() { return mLastCloseNetTime; } - void setLastCloseNetTime(uint32 t) { mLastCloseNetTime = t; } + uint32 getLastCloseTime() { return mLastCloseTime; } + void setLastCloseTime(uint32 t) { mLastCloseTime = t; } Json::Value getServerInfo(); // client information retrieval functions @@ -207,8 +202,8 @@ public: // void pubAccountInfo(const NewcoinAddress& naAccountID, const Json::Value& jvObj); - void pubLedger(const Ledger::pointer& lpAccepted); - void pubTransaction(const Ledger::pointer& lpLedger, const SerializedTransaction& stTxn, TransactionEngineResult terResult); + void pubLedger(Ledger::ref lpAccepted); + void pubTransaction(Ledger::ref lpLedger, const SerializedTransaction& stTxn, TER terResult); // // Monitoring: subscriber side diff --git a/src/NewcoinAddress.cpp b/src/NewcoinAddress.cpp index 29b87fe378..be7583dcd7 100644 --- a/src/NewcoinAddress.cpp +++ b/src/NewcoinAddress.cpp @@ -95,6 +95,15 @@ NewcoinAddress NewcoinAddress::createNodePublic(const std::vector return naNew; } +NewcoinAddress NewcoinAddress::createNodePublic(const std::string& strPublic) +{ + NewcoinAddress naNew; + + naNew.setNodePublic(strPublic); + + return naNew; +} + uint160 NewcoinAddress::getNodeID() const { switch (nVersion) { diff --git a/src/NewcoinAddress.h b/src/NewcoinAddress.h index c591d1b0d7..707a976c52 100644 --- a/src/NewcoinAddress.h +++ b/src/NewcoinAddress.h @@ -46,6 +46,7 @@ public: static NewcoinAddress createNodePublic(const NewcoinAddress& naSeed); static NewcoinAddress createNodePublic(const std::vector& vPublic); + static NewcoinAddress createNodePublic(const std::string& strPublic); // // Node Private @@ -72,6 +73,9 @@ public: bool setAccountID(const std::string& strAccountID); void setAccountID(const uint160& hash160In); + static NewcoinAddress createAccountID(const std::string& strAccountID) + { NewcoinAddress na; na.setAccountID(strAccountID); return na; } + static NewcoinAddress createAccountID(const uint160& uiAccountID); static std::string createHumanAccountID(const uint160& uiAccountID) diff --git a/src/NicknameState.cpp b/src/NicknameState.cpp index 9b2cb3e857..72fbaa662f 100644 --- a/src/NicknameState.cpp +++ b/src/NicknameState.cpp @@ -8,19 +8,19 @@ NicknameState::NicknameState(SerializedLedgerEntry::pointer ledgerEntry) : bool NicknameState::haveMinimumOffer() const { - return mLedgerEntry->getIFieldPresent(sfMinimumOffer); + return mLedgerEntry->isFieldPresent(sfMinimumOffer); } STAmount NicknameState::getMinimumOffer() const { - return mLedgerEntry->getIFieldPresent(sfMinimumOffer) - ? mLedgerEntry->getIValueFieldAmount(sfMinimumOffer) + return mLedgerEntry->isFieldPresent(sfMinimumOffer) + ? mLedgerEntry->getFieldAmount(sfMinimumOffer) : STAmount(); } NewcoinAddress NicknameState::getAccountID() const { - return mLedgerEntry->getIValueFieldAccount(sfAccount); + return mLedgerEntry->getFieldAccount(sfAccount); } void NicknameState::addJson(Json::Value& val) diff --git a/src/Operation.cpp b/src/Operation.cpp new file mode 100644 index 0000000000..b0661f57a2 --- /dev/null +++ b/src/Operation.cpp @@ -0,0 +1,17 @@ +#include "Operation.h" +#include "Config.h" + +/* +We also need to charge for each op + +*/ + +namespace Script { + + +int Operation::getFee() +{ + return(theConfig.FEE_CONTRACT_OPERATION); +} + +} \ No newline at end of file diff --git a/src/Operation.h b/src/Operation.h new file mode 100644 index 0000000000..11c37d38f3 --- /dev/null +++ b/src/Operation.h @@ -0,0 +1,222 @@ +#include "Interpreter.h" + +namespace Script { + +// Contracts are non typed have variable data types + + +class Operation +{ +public: + // returns false if there was an error + virtual bool work(Interpreter* interpreter)=0; + + virtual int getFee(); + + virtual ~Operation() { ; } +}; + +// this is just an Int in the code +class IntOp : public Operation +{ +public: + bool work(Interpreter* interpreter) + { + Data::pointer data=interpreter->getIntData(); + if(data->isInt32()) + { + interpreter->pushStack( data ); + return(true); + } + return(false); + } +}; + +class FloatOp : public Operation +{ +public: + bool work(Interpreter* interpreter) + { + Data::pointer data=interpreter->getFloatData(); + if(data->isFloat()) + { + interpreter->pushStack( data ); + return(true); + } + return(false); + } +}; + +class Uint160Op : public Operation +{ +public: + bool work(Interpreter* interpreter) + { + Data::pointer data=interpreter->getUint160Data(); + if(data->isUint160()) + { + interpreter->pushStack( data ); + return(true); + } + return(false); + } +}; + +class AddOp : public Operation +{ +public: + bool work(Interpreter* interpreter) + { + Data::pointer data1=interpreter->popStack(); + Data::pointer data2=interpreter->popStack(); + if( (data1->isInt32() || data1->isFloat()) && + (data2->isInt32() || data2->isFloat()) ) + { + if(data1->isFloat() || data2->isFloat()) interpreter->pushStack(Data::pointer(new FloatData(data1->getFloat()+data2->getFloat()))); + else interpreter->pushStack(Data::pointer(new IntData(data1->getInt()+data2->getInt()))); + return(true); + }else + { + return(false); + } + } +}; + +class SubOp : public Operation +{ +public: + bool work(Interpreter* interpreter) + { + Data::pointer data1=interpreter->popStack(); + Data::pointer data2=interpreter->popStack(); + if( (data1->isInt32() || data1->isFloat()) && + (data2->isInt32() || data2->isFloat()) ) + { + if(data1->isFloat() || data2->isFloat()) interpreter->pushStack(Data::pointer(new FloatData(data1->getFloat()-data2->getFloat()))); + else interpreter->pushStack(Data::pointer(new IntData(data1->getInt()-data2->getInt()))); + return(true); + }else + { + return(false); + } + } +}; + + +class StartBlockOp : public Operation +{ +public: + bool work(Interpreter* interpreter) + { + Data::pointer offset=interpreter->getIntData(); + return(interpreter->startBlock(offset->getInt())); + } +}; + +class EndBlockOp : public Operation +{ +public: + bool work(Interpreter* interpreter) + { + return(interpreter->endBlock()); + } +}; + +class StopOp : public Operation +{ +public: + bool work(Interpreter* interpreter) + { + interpreter->stop(); + return(true); + } +}; + +class AcceptDataOp : public Operation +{ +public: + bool work(Interpreter* interpreter) + { + Data::pointer data=interpreter->popStack(); + if(data->isInt32()) + { + interpreter->pushStack( interpreter->getAcceptData(data->getInt()) ); + return(true); + } + return(false); + } +}; + +class JumpIfOp : public Operation +{ +public: + bool work(Interpreter* interpreter) + { + Data::pointer offset=interpreter->getIntData(); + Data::pointer cond=interpreter->popStack(); + if(cond->isBool() && offset->isInt32()) + { + if(cond->isTrue()) + { + return(interpreter->jumpTo(offset->getInt())); + } + return(true); + } + return(false); + } +}; + +class JumpOp : public Operation +{ +public: + bool work(Interpreter* interpreter) + { + Data::pointer offset=interpreter->getIntData(); + if(offset->isInt32()) + { + return(interpreter->jumpTo(offset->getInt())); + } + return(false); + } +}; + +class SendXNSOp : public Operation +{ +public: + bool work(Interpreter* interpreter) + { + Data::pointer sourceID=interpreter->popStack(); + Data::pointer destID=interpreter->popStack(); + Data::pointer amount=interpreter->popStack(); + if(sourceID->isUint160() && destID->isUint160() && amount->isInt32() && interpreter->canSign(sourceID->getUint160())) + { + // make sure: + // source is either, this contract, issuer, or acceptor + + // TODO do the send + //interpreter->pushStack( send result); + + return(true); + } + + return(false); + } +}; + +class GetDataOp : public Operation +{ +public: + bool work(Interpreter* interpreter) + { + Data::pointer index=interpreter->popStack(); + if(index->isInt32()) + { + interpreter->pushStack( interpreter->getContractData(index->getInt())); + return(true); + } + + return(false); + } +}; + +} \ No newline at end of file diff --git a/src/OrderBook.cpp b/src/OrderBook.cpp new file mode 100644 index 0000000000..2cf716257e --- /dev/null +++ b/src/OrderBook.cpp @@ -0,0 +1,23 @@ +#include "OrderBook.h" +#include "Ledger.h" + +OrderBook::pointer OrderBook::newOrderBook(SerializedLedgerEntry::pointer ledgerEntry) +{ + if(ledgerEntry->getType() != ltOFFER) return( OrderBook::pointer()); + + return( OrderBook::pointer(new OrderBook(ledgerEntry))); +} + +OrderBook::OrderBook(SerializedLedgerEntry::pointer ledgerEntry) +{ + const STAmount saTakerGets = ledgerEntry->getFieldAmount(sfTakerGets); + const STAmount saTakerPays = ledgerEntry->getFieldAmount(sfTakerPays); + + mCurrencyIn = saTakerGets.getCurrency(); + mCurrencyOut = saTakerPays.getCurrency(); + mIssuerIn = saTakerGets.getIssuer(); + mIssuerOut = saTakerPays.getIssuer(); + + mBookBase=Ledger::getBookBase(mCurrencyOut,mIssuerOut,mCurrencyIn,mIssuerIn); +} +// vim:ts=4 diff --git a/src/OrderBook.h b/src/OrderBook.h new file mode 100644 index 0000000000..e143589f48 --- /dev/null +++ b/src/OrderBook.h @@ -0,0 +1,34 @@ +#include "SerializedLedger.h" +#include +/* + Encapsulates the SLE for an orderbook +*/ +class OrderBook +{ + uint256 mBookBase; + + uint160 mCurrencyIn; + uint160 mCurrencyOut; + uint160 mIssuerIn; + uint160 mIssuerOut; + + //SerializedLedgerEntry::pointer mLedgerEntry; + OrderBook(SerializedLedgerEntry::pointer ledgerEntry); // For accounts in a ledger +public: + typedef boost::shared_ptr pointer; + + // returns NULL if ledgerEntry doesn't point to an order + // if ledgerEntry is an Order it creates the OrderBook this order would live in + static OrderBook::pointer newOrderBook(SerializedLedgerEntry::pointer ledgerEntry); + + uint256& getBookBase(){ return(mBookBase); } + uint160& getCurrencyIn(){ return(mCurrencyIn); } + uint160& getCurrencyOut(){ return(mCurrencyOut); } + uint160& getIssuerIn(){ return(mIssuerIn); } + uint160& getIssuerOut(){ return(mIssuerOut); } + + // looks through the best offers to see how much it would cost to take the given amount + STAmount& getTakePrice(STAmount& takeAmount); + + +}; \ No newline at end of file diff --git a/src/OrderBookDB.cpp b/src/OrderBookDB.cpp new file mode 100644 index 0000000000..4f49f01338 --- /dev/null +++ b/src/OrderBookDB.cpp @@ -0,0 +1,56 @@ +#include "OrderBookDB.h" +#include "Log.h" +#include + + +// TODO: this would be way faster if we could just look under the order dirs +OrderBookDB::OrderBookDB(Ledger::pointer ledger) +{ + // walk through the entire ledger looking for orderbook entries + uint256 currentIndex=ledger->getFirstLedgerIndex(); + while(currentIndex.isNonZero()) + { + SLE::pointer entry=ledger->getSLE(currentIndex); + + OrderBook::pointer book=OrderBook::newOrderBook(entry); + if(book) + { + if( mKnownMap.find(book->getBookBase()) != mKnownMap.end() ) + { + mKnownMap[book->getBookBase()]=true; + + if(!book->getCurrencyIn()) + { // XNS + mXNSOrders.push_back(book); + }else + { + mIssuerMap[book->getIssuerIn()].push_back(book); + } + } + } + + currentIndex=ledger->getNextLedgerIndex(currentIndex); + } +} + +// return list of all orderbooks that want IssuerID +std::vector& OrderBookDB::getBooks(const uint160& issuerID) +{ + if( mIssuerMap.find(issuerID) == mIssuerMap.end() ) return mEmptyVector; + else return( mIssuerMap[issuerID]); +} + +// return list of all orderbooks that want this issuerID and currencyID +void OrderBookDB::getBooks(const uint160& issuerID, const uint160& currencyID, std::vector& bookRet) +{ + if( mIssuerMap.find(issuerID) == mIssuerMap.end() ) + { + BOOST_FOREACH(OrderBook::pointer book, mIssuerMap[issuerID]) + { + if(book->getCurrencyIn()==currencyID) + { + bookRet.push_back(book); + } + } + } +} \ No newline at end of file diff --git a/src/OrderBookDB.h b/src/OrderBookDB.h new file mode 100644 index 0000000000..c8e598d186 --- /dev/null +++ b/src/OrderBookDB.h @@ -0,0 +1,30 @@ +#include "Ledger.h" +#include "OrderBook.h" + +/* +we can eventually make this cached and just update it as transactions come in. +But for now it is probably faster to just generate it each time +*/ + +class OrderBookDB +{ + std::vector mEmptyVector; + std::vector mXNSOrders; + std::map > mIssuerMap; + + std::map mKnownMap; + +public: + OrderBookDB(Ledger::pointer ledger); + + // return list of all orderbooks that want XNS + std::vector& getXNSInBooks(){ return mXNSOrders; } + // return list of all orderbooks that want IssuerID + std::vector& getBooks(const uint160& issuerID); + // return list of all orderbooks that want this issuerID and currencyID + void getBooks(const uint160& issuerID, const uint160& currencyID, std::vector& bookRet); + + // returns the best rate we can find + float getPrice(uint160& currencyIn,uint160& currencyOut); + +}; \ No newline at end of file diff --git a/src/Pathfinder.cpp b/src/Pathfinder.cpp new file mode 100644 index 0000000000..4f0276a451 --- /dev/null +++ b/src/Pathfinder.cpp @@ -0,0 +1,201 @@ +#include "Pathfinder.h" +#include "Application.h" +#include "RippleLines.h" +#include "Log.h" +#include + +/* +JED: V IIII + +we just need to find a succession of the highest quality paths there until we find enough width + +Don't do branching within each path + +We have a list of paths we are working on but how do we compare the ones that are terminating in a different currency? + +Loops + +TODO: what is a good way to come up with multiple paths? + Maybe just change the sort criteria? + first a low cost one and then a fat short one? + + +OrderDB: + getXNSOffers(); + + // return list of all orderbooks that want XNS + // return list of all orderbooks that want IssuerID + // return list of all orderbooks that want this issuerID and currencyID +*/ + +/* +Test sending to XNS +Test XNS to XNS +Test offer in middle +Test XNS to USD +Test USD to EUR +*/ + + +// we sort the options by: +// cost of path +// length of path +// width of path +// correct currency at the end + + + +bool sortPathOptions(PathOption::pointer first, PathOption::pointer second) +{ + if(first->mTotalCostmTotalCost) return(true); + if(first->mTotalCost>second->mTotalCost) return(false); + + if(first->mCorrectCurrency && !second->mCorrectCurrency) return(true); + if(!first->mCorrectCurrency && second->mCorrectCurrency) return(false); + + if(first->mPath.getElementCount()mPath.getElementCount()) return(true); + if(first->mPath.getElementCount()>second->mPath.getElementCount()) return(false); + + if(first->mMinWidthmMinWidth) return true; + + return false; +} + +PathOption::PathOption(uint160& srcAccount,uint160& srcCurrencyID,const uint160& dstCurrencyID) +{ + mCurrentAccount=srcAccount; + mCurrencyID=srcCurrencyID; + mCorrectCurrency=(srcCurrencyID==dstCurrencyID); + mQuality=0; + mMinWidth=STAmount(dstCurrencyID,99999,80); // this will get lowered when we convert back to the correct currency +} + +PathOption::PathOption(PathOption::pointer other) +{ + // TODO: +} + + +Pathfinder::Pathfinder(NewcoinAddress& srcAccountID, NewcoinAddress& dstAccountID, uint160& srcCurrencyID, STAmount dstAmount) : + mSrcAccountID(srcAccountID.getAccountID()), mDstAccountID(dstAccountID.getAccountID()), mDstAmount(dstAmount), mSrcCurrencyID(srcCurrencyID), mOrderBook(theApp->getMasterLedger().getCurrentLedger()) +{ + mLedger=theApp->getMasterLedger().getCurrentLedger(); +} + +bool Pathfinder::findPaths(int maxSearchSteps, int maxPay, STPathSet& retPathSet) +{ + if(mLedger) + { + PathOption::pointer head(new PathOption(mSrcAccountID,mSrcCurrencyID,mDstAmount.getCurrency())); + addOptions(head); + + for(int n=0; n tempPaths=mBuildingPaths; + mBuildingPaths.clear(); + BOOST_FOREACH(PathOption::pointer path,tempPaths) + { + addOptions(path); + } + if(checkComplete(retPathSet)) return(true); + } + } + + return(false); +} + +bool Pathfinder::checkComplete(STPathSet& retPathSet) +{ + if(mCompletePaths.size()) + { // TODO: look through these and pick the most promising + int count=0; + BOOST_FOREACH(PathOption::pointer pathOption,mCompletePaths) + { + retPathSet.addPath(pathOption->mPath); + count++; + if(count>2) return(true); + } + return(true); + } + return(false); +} + + +// get all the options from this accountID +// if source is XNS +// every offer that wants XNS +// else +// every ripple line that starts with the source currency +// every offer that we can take that wants the source currency + +void Pathfinder::addOptions(PathOption::pointer tail) +{ + if(!tail->mCurrencyID) + { // source XNS + BOOST_FOREACH(OrderBook::pointer book,mOrderBook.getXNSInBooks()) + { + PathOption::pointer pathOption(new PathOption(tail)); + + STPathElement ele(uint160(), book->getCurrencyOut(), book->getIssuerOut()); + pathOption->mPath.addElement(ele); + + pathOption->mCurrentAccount=book->getIssuerOut(); + pathOption->mCurrencyID=book->getCurrencyOut(); + addPathOption(pathOption); + } + }else + { // ripple + RippleLines rippleLines(tail->mCurrentAccount); + BOOST_FOREACH(RippleState::pointer line,rippleLines.getLines()) + { + // TODO: make sure we can move in the correct direction + STAmount balance=line->getBalance(); + if(balance.getCurrency()==tail->mCurrencyID) + { // we have a ripple line from the tail to somewhere else + PathOption::pointer pathOption(new PathOption(tail)); + + STPathElement ele(line->getAccountIDPeer().getAccountID(), uint160(), uint160()); + pathOption->mPath.addElement(ele); + + + pathOption->mCurrentAccount=line->getAccountIDPeer().getAccountID(); + addPathOption(pathOption); + } + } + + // every offer that wants the source currency + std::vector books; + mOrderBook.getBooks(tail->mCurrentAccount, tail->mCurrencyID, books); + + BOOST_FOREACH(OrderBook::pointer book,books) + { + PathOption::pointer pathOption(new PathOption(tail)); + + STPathElement ele(uint160(), book->getCurrencyOut(), book->getIssuerOut()); + pathOption->mPath.addElement(ele); + + pathOption->mCurrentAccount=book->getIssuerOut(); + pathOption->mCurrencyID=book->getCurrencyOut(); + addPathOption(pathOption); + } + } +} + +void Pathfinder::addPathOption(PathOption::pointer pathOption) +{ + if(pathOption->mCurrencyID==mDstAmount.getCurrency()) + { + pathOption->mCorrectCurrency=true; + + if(pathOption->mCurrentAccount==mDstAccountID) + { // this path is complete + mCompletePaths.push_back(pathOption); + }else mBuildingPaths.push_back(pathOption); + } + else + { + pathOption->mCorrectCurrency=false; + mBuildingPaths.push_back(pathOption); + } +} +// vim:ts=4 diff --git a/src/Pathfinder.h b/src/Pathfinder.h new file mode 100644 index 0000000000..cc900e1e13 --- /dev/null +++ b/src/Pathfinder.h @@ -0,0 +1,52 @@ +#include "SerializedTypes.h" +#include "NewcoinAddress.h" +#include "OrderBookDB.h" +#include + +/* this is a very simple implementation. This can be made way better. +We are simply flooding from the start. And doing an exhaustive search of all paths under maxSearchSteps. An easy improvement would be to flood from both directions +*/ +class PathOption +{ +public: + typedef boost::shared_ptr pointer; + + STPath mPath; + bool mCorrectCurrency; // for the sorting + uint160 mCurrencyID; // what currency we currently have at the end of the path + uint160 mCurrentAccount; // what account is at the end of the path + int mTotalCost; // in send currency + STAmount mMinWidth; // in dest currency + float mQuality; + + PathOption(uint160& srcAccount,uint160& srcCurrencyID,const uint160& dstCurrencyID); + PathOption(PathOption::pointer other); +}; + +class Pathfinder +{ + uint160 mSrcAccountID; + uint160 mDstAccountID; + STAmount mDstAmount; + uint160 mSrcCurrencyID; + + OrderBookDB mOrderBook; + Ledger::pointer mLedger; + + std::list mBuildingPaths; + std::list mCompletePaths; + + void addOptions(PathOption::pointer tail); + + // returns true if any building paths are now complete? + bool checkComplete(STPathSet& retPathSet); + + void addPathOption(PathOption::pointer pathOption); + +public: + Pathfinder(NewcoinAddress& srcAccountID, NewcoinAddress& dstAccountID, uint160& srcCurrencyID, STAmount dstAmount); + + // returns false if there is no path. otherwise fills out retPath + bool findPaths(int maxSearchSteps, int maxPay, STPathSet& retPathSet); +}; +// vim:ts=4 diff --git a/src/Peer.cpp b/src/Peer.cpp index 7c822dd229..2397071ae1 100644 --- a/src/Peer.cpp +++ b/src/Peer.cpp @@ -12,7 +12,6 @@ #include "Peer.h" #include "Config.h" #include "Application.h" -#include "Conversion.h" #include "SerializedTransaction.h" #include "utils.h" #include "Log.h" @@ -140,7 +139,7 @@ void Peer::handleVerifyTimer(const boost::system::error_code& ecResult) // Begin trying to connect. We are not connected till we know and accept peer's public key. // Only takes IP addresses (not domains). -void Peer::connect(const std::string strIp, int iPort) +void Peer::connect(const std::string& strIp, int iPort) { int iPortAct = (iPort <= 0) ? SYSTEM_PEER_PORT : iPort; @@ -260,7 +259,7 @@ void Peer::connected(const boost::system::error_code& error) } } -void Peer::sendPacketForce(PackedMessage::pointer packet) +void Peer::sendPacketForce(const PackedMessage::pointer& packet) { if (!mDetaching) { @@ -273,7 +272,7 @@ void Peer::sendPacketForce(PackedMessage::pointer packet) } } -void Peer::sendPacket(PackedMessage::pointer packet) +void Peer::sendPacket(const PackedMessage::pointer& packet) { if (packet) { @@ -572,8 +571,8 @@ void Peer::recvHello(newcoin::TMHello& packet) (void) mVerifyTimer.cancel(); uint32 ourTime = theApp->getOPs().getNetworkTimeNC(); - uint32 minTime = ourTime - 10; - uint32 maxTime = ourTime + 10; + uint32 minTime = ourTime - 20; + uint32 maxTime = ourTime + 20; #ifdef DEBUG if (packet.has_nettime()) @@ -586,7 +585,10 @@ void Peer::recvHello(newcoin::TMHello& packet) if (packet.has_nettime() && ((packet.nettime() < minTime) || (packet.nettime() > maxTime))) { - Log(lsINFO) << "Recv(Hello): Disconnect: Clock is far off"; + if (packet.nettime() > maxTime) + Log(lsINFO) << "Recv(Hello): " << getIP() << " :Clock far off +" << packet.nettime() - ourTime; + else if(packet.nettime() < minTime) + Log(lsINFO) << "Recv(Hello): " << getIP() << " :Clock far off -" << ourTime - packet.nettime(); } else if (packet.protoversionmin() < MAKE_VERSION_INT(MIN_PROTO_MAJOR, MIN_PROTO_MINOR)) { @@ -646,9 +648,11 @@ void Peer::recvHello(newcoin::TMHello& packet) { memcpy(mClosedLedgerHash.begin(), packet.ledgerclosed().data(), 256 / 8); if ((packet.has_ledgerprevious()) && (packet.ledgerprevious().size() == (256 / 8))) + { memcpy(mPreviousLedgerHash.begin(), packet.ledgerprevious().data(), 256 / 8); + addLedger(mPreviousLedgerHash); + } else mPreviousLedgerHash.zero(); - mClosedLedgerTime = boost::posix_time::second_clock::universal_time(); } bDetach = false; @@ -697,13 +701,7 @@ void Peer::recvTransaction(newcoin::TMTransaction& packet) } #endif - uint32 targetLedger = 0; - if (packet.has_ledgerindexfinal()) - targetLedger = packet.ledgerindexfinal(); - else if (packet.has_ledgerindexpossible()) - targetLedger = packet.ledgerindexpossible(); - - tx = theApp->getOPs().processTransaction(tx, targetLedger, this); + tx = theApp->getOPs().processTransaction(tx, this); if(tx->getStatus() != INCLUDED) { // transaction wasn't accepted into ledger @@ -722,12 +720,14 @@ void Peer::recvPropose(newcoin::TMProposeSet& packet) return; } - uint32 proposeSeq = packet.proposeseq(); - uint256 currentTxHash; + uint256 currentTxHash, prevLedger; memcpy(currentTxHash.begin(), packet.currenttxhash().data(), 32); - if(theApp->getOPs().recvPropose(proposeSeq, currentTxHash, packet.closetime(), - packet.nodepubkey(), packet.signature())) + if ((packet.has_previousledger()) && (packet.previousledger().size() == 32)) + memcpy(prevLedger.begin(), packet.previousledger().data(), 32); + + if(theApp->getOPs().recvPropose(packet.proposeseq(), currentTxHash, prevLedger, packet.closetime(), + packet.nodepubkey(), packet.signature(), mNodePublic)) { // FIXME: Not all nodes will want proposals PackedMessage::pointer message = boost::make_shared(packet, newcoin::mtPROPOSE_LEDGER); theApp->getConnectionPool().relayMessage(this, message); @@ -744,8 +744,11 @@ void Peer::recvHaveTxSet(newcoin::TMHaveTransactionSet& packet) punishPeer(PP_INVALID_REQUEST); return; } - memcpy(hashes.begin(), packet.hash().data(), 32); - if (!theApp->getOPs().hasTXSet(shared_from_this(), hashes, packet.status())) + uint256 hash; + memcpy(hash.begin(), packet.hash().data(), 32); + if (packet.status() == newcoin::tsHAVE) + addTxSet(hash); + if (!theApp->getOPs().hasTXSet(shared_from_this(), hash, packet.status())) punishPeer(PP_UNWANTED_DATA); } @@ -900,16 +903,19 @@ void Peer::recvStatus(newcoin::TMStatusChange& packet) if (packet.newevent() == newcoin::neLOST_SYNC) { - Log(lsTRACE) << "peer has lost sync " << getIP(); + if (!mClosedLedgerHash.isZero()) + { + Log(lsTRACE) << "peer has lost sync " << getIP(); + mClosedLedgerHash.zero(); + } mPreviousLedgerHash.zero(); - mClosedLedgerHash.zero(); return; } if (packet.has_ledgerhash() && (packet.ledgerhash().size() == (256 / 8))) { // a peer has changed ledgers memcpy(mClosedLedgerHash.begin(), packet.ledgerhash().data(), 256 / 8); - mClosedLedgerTime = ptFromSeconds(packet.networktime()); - Log(lsTRACE) << "peer LCL is " << mClosedLedgerHash.GetHex() << " " << getIP(); + addLedger(mClosedLedgerHash); + Log(lsTRACE) << "peer LCL is " << mClosedLedgerHash << " " << getIP(); } else { @@ -920,6 +926,7 @@ void Peer::recvStatus(newcoin::TMStatusChange& packet) if (packet.has_ledgerhashprevious() && packet.ledgerhashprevious().size() == (256 / 8)) { memcpy(mPreviousLedgerHash.begin(), packet.ledgerhashprevious().data(), 256 / 8); + addLedger(mPreviousLedgerHash); } else mPreviousLedgerHash.zero(); } @@ -928,12 +935,11 @@ void Peer::recvGetLedger(newcoin::TMGetLedger& packet) { SHAMap::pointer map; newcoin::TMLedgerData reply; - bool fatLeaves = true; + bool fatLeaves = true, fatRoot = false; if (packet.itype() == newcoin::liTS_CANDIDATE) { // Request is for a transaction candidate set Log(lsINFO) << "Received request for TX candidate set data " << getIP(); - Ledger::pointer ledger; if ((!packet.has_ledgerhash() || packet.ledgerhash().size() != 32)) { punishPeer(PP_INVALID_REQUEST); @@ -944,7 +950,7 @@ void Peer::recvGetLedger(newcoin::TMGetLedger& packet) map = theApp->getOPs().getTXMap(txHash); if (!map) { - Log(lsERROR) << "We do not hav the map our peer wants"; + Log(lsERROR) << "We do not have the map our peer wants"; punishPeer(PP_INVALID_REQUEST); return; } @@ -952,6 +958,7 @@ void Peer::recvGetLedger(newcoin::TMGetLedger& packet) reply.set_ledgerhash(txHash.begin(), txHash.size()); reply.set_type(newcoin::liTS_CANDIDATE); fatLeaves = false; // We'll already have most transactions + fatRoot = true; // Save a pass } else { // Figure out what ledger they want @@ -968,6 +975,8 @@ void Peer::recvGetLedger(newcoin::TMGetLedger& packet) } memcpy(ledgerhash.begin(), packet.ledgerhash().data(), 32); ledger = theApp->getMasterLedger().getLedgerByHash(ledgerhash); + if (!ledger) + Log(lsINFO) << "Don't have ledger " << ledgerhash; } else if (packet.has_ledgerseq()) ledger = theApp->getMasterLedger().getLedgerBySeq(packet.ledgerseq()); @@ -986,7 +995,7 @@ void Peer::recvGetLedger(newcoin::TMGetLedger& packet) return; } - if ((!ledger) || (packet.has_ledgerseq() && (packet.ledgerseq()!=ledger->getLedgerSeq()))) + if ((!ledger) || (packet.has_ledgerseq() && (packet.ledgerseq() != ledger->getLedgerSeq()))) { punishPeer(PP_UNKNOWN_REQUEST); Log(lsWARNING) << "Can't find the ledger they want"; @@ -1007,13 +1016,13 @@ void Peer::recvGetLedger(newcoin::TMGetLedger& packet) reply.add_nodes()->set_nodedata(nData.getDataPtr(), nData.getLength()); if (packet.nodeids().size() != 0) - { + { // new-style root request Log(lsINFO) << "Ledger root w/map roots request"; SHAMap::pointer map = ledger->peekAccountStateMap(); if (map) - { + { // return account state root node if possible Serializer rootNode(768); - if (map->getRootNode(rootNode, STN_ARF_WIRE)) + if (map->getRootNode(rootNode, snfWIRE)) { reply.add_nodes()->set_nodedata(rootNode.getDataPtr(), rootNode.getLength()); if (ledger->getTransHash().isNonZero()) @@ -1022,7 +1031,7 @@ void Peer::recvGetLedger(newcoin::TMGetLedger& packet) if (map) { rootNode.resize(0); - if (map->getRootNode(rootNode, STN_ARF_WIRE)) + if (map->getRootNode(rootNode, snfWIRE)) reply.add_nodes()->set_nodedata(rootNode.getDataPtr(), rootNode.getLength()); } } @@ -1057,7 +1066,7 @@ void Peer::recvGetLedger(newcoin::TMGetLedger& packet) } std::vector nodeIDs; std::list< std::vector > rawNodes; - if(map->getNodeFat(mn, nodeIDs, rawNodes, fatLeaves)) + if(map->getNodeFat(mn, nodeIDs, rawNodes, fatRoot, fatLeaves)) { std::vector::iterator nodeIDIterator; std::list< std::vector >::iterator rawNodeIterator; @@ -1074,7 +1083,8 @@ void Peer::recvGetLedger(newcoin::TMGetLedger& packet) } } } - if (packet.has_requestcookie()) reply.set_requestcookie(packet.requestcookie()); + if (packet.has_requestcookie()) + reply.set_requestcookie(packet.requestcookie()); PackedMessage::pointer oPacket = boost::make_shared(reply, newcoin::mtLEDGER_DATA); sendPacket(oPacket); } @@ -1124,7 +1134,38 @@ void Peer::recvLedger(newcoin::TMLedgerData& packet) bool Peer::hasLedger(const uint256& hash) const { - return (hash == mClosedLedgerHash) || (hash == mPreviousLedgerHash); + BOOST_FOREACH(const uint256& ledger, mRecentLedgers) + if (ledger == hash) + return true; + return false; +} + +void Peer::addLedger(const uint256& hash) +{ + BOOST_FOREACH(const uint256& ledger, mRecentLedgers) + if (ledger == hash) + return; + if (mRecentLedgers.size() == 128) + mRecentLedgers.pop_front(); + mRecentLedgers.push_back(hash); +} + +bool Peer::hasTxSet(const uint256& hash) const +{ + BOOST_FOREACH(const uint256& set, mRecentTxSets) + if (set == hash) + return true; + return false; +} + +void Peer::addTxSet(const uint256& hash) +{ + BOOST_FOREACH(const uint256& set, mRecentTxSets) + if (set == hash) + return; + if (mRecentTxSets.size() == 128) + mRecentTxSets.pop_front(); + mRecentTxSets.push_back(hash); } // Get session information we can sign to prevent man in the middle attack. @@ -1212,7 +1253,8 @@ Json::Value Peer::getJson() //ret["this"] = ADDRESS(this); ret["public_key"] = mNodePublic.ToString(); ret["ip"] = mIpPortConnect.first; - ret["port"] = mIpPortConnect.second; + //ret["port"] = mIpPortConnect.second; + ret["port"] = mIpPort.second; if (mHello.has_fullversion()) ret["version"] = mHello.fullversion(); diff --git a/src/Peer.h b/src/Peer.h index 13cf6b79bc..0b28a1dc9b 100644 --- a/src/Peer.h +++ b/src/Peer.h @@ -24,13 +24,14 @@ typedef std::pair ipPort; class Peer : public boost::enable_shared_from_this { public: - typedef boost::shared_ptr pointer; + typedef boost::shared_ptr pointer; + typedef const boost::shared_ptr& ref; static const int psbGotHello = 0, psbSentHello = 1, psbInMap = 2, psbTrusted = 3; static const int psbNoLedgers = 4, psbNoTransactions = 5, psbDownLevel = 6; void handleConnect(const boost::system::error_code& error, boost::asio::ip::tcp::resolver::iterator it); - static void sHandleConnect(Peer::pointer ptr, const boost::system::error_code& error, + static void sHandleConnect(Peer::ref ptr, const boost::system::error_code& error, boost::asio::ip::tcp::resolver::iterator it) { ptr->handleConnect(error, it); } @@ -43,20 +44,20 @@ private: ipPort mIpPortConnect; uint256 mCookieHash; - // network state information - uint256 mClosedLedgerHash, mPreviousLedgerHash; - boost::posix_time::ptime mClosedLedgerTime; + uint256 mClosedLedgerHash, mPreviousLedgerHash; + std::list mRecentLedgers; + std::list mRecentTxSets; boost::asio::ssl::stream mSocketSsl; boost::asio::deadline_timer mVerifyTimer; void handleStart(const boost::system::error_code& ecResult); - static void sHandleStart(Peer::pointer ptr, const boost::system::error_code& ecResult) + static void sHandleStart(Peer::ref ptr, const boost::system::error_code& ecResult) { ptr->handleStart(ecResult); } void handleVerifyTimer(const boost::system::error_code& ecResult); - static void sHandleVerifyTimer(Peer::pointer ptr, const boost::system::error_code& ecResult) + static void sHandleVerifyTimer(Peer::ref ptr, const boost::system::error_code& ecResult) { ptr->handleVerifyTimer(ecResult); } protected: @@ -70,26 +71,26 @@ protected: Peer(boost::asio::io_service& io_service, boost::asio::ssl::context& ctx); void handleShutdown(const boost::system::error_code& error) { ; } - static void sHandleShutdown(Peer::pointer ptr, const boost::system::error_code& error) + static void sHandleShutdown(Peer::ref ptr, const boost::system::error_code& error) { ptr->handleShutdown(error); } void handle_write(const boost::system::error_code& error, size_t bytes_transferred); - static void sHandle_write(Peer::pointer ptr, const boost::system::error_code& error, size_t bytes_transferred) + static void sHandle_write(Peer::ref ptr, const boost::system::error_code& error, size_t bytes_transferred) { ptr->handle_write(error, bytes_transferred); } void handle_read_header(const boost::system::error_code& error); - static void sHandle_read_header(Peer::pointer ptr, const boost::system::error_code& error) + static void sHandle_read_header(Peer::ref ptr, const boost::system::error_code& error) { ptr->handle_read_header(error); } void handle_read_body(const boost::system::error_code& error); - static void sHandle_read_body(Peer::pointer ptr, const boost::system::error_code& error) + static void sHandle_read_body(Peer::ref ptr, const boost::system::error_code& error) { ptr->handle_read_body(error); } void processReadBuffer(); void start_read_header(); void start_read_body(unsigned msg_len); - void sendPacketForce(PackedMessage::pointer packet); + void sendPacketForce(const PackedMessage::pointer& packet); void sendHello(); @@ -117,6 +118,9 @@ protected: void getSessionCookie(std::string& strDst); + void addLedger(const uint256& ledger); + void addTxSet(const uint256& TxSet); + public: //bool operator == (const Peer& other); @@ -136,31 +140,27 @@ public: return mSocketSsl.lowest_layer(); } - void connect(const std::string strIp, int iPort); + void connect(const std::string& strIp, int iPort); void connected(const boost::system::error_code& error); void detach(const char *); - bool samePeer(Peer::pointer p) { return samePeer(*p); } - bool samePeer(const Peer& p) { return this == &p; } + bool samePeer(Peer::ref p) { return samePeer(*p); } + bool samePeer(const Peer& p) { return this == &p; } - void sendPacket(PackedMessage::pointer packet); - void sendLedgerProposal(Ledger::pointer ledger); - void sendFullLedger(Ledger::pointer ledger); + void sendPacket(const PackedMessage::pointer& packet); + void sendLedgerProposal(Ledger::ref ledger); + void sendFullLedger(Ledger::ref ledger); void sendGetFullLedger(uint256& hash); void sendGetPeers(); void punishPeer(PeerPunish pp); Json::Value getJson(); - bool isConnected() const { return mHelloed && !mDetaching; } + bool isConnected() const { return mHelloed && !mDetaching; } - //static PackedMessage::pointer createFullLedger(Ledger::pointer ledger); - static PackedMessage::pointer createLedgerProposal(Ledger::pointer ledger); - static PackedMessage::pointer createValidation(Ledger::pointer ledger); - static PackedMessage::pointer createGetFullLedger(uint256& hash); - - uint256 getClosedLedgerHash() const { return mClosedLedgerHash; } + uint256 getClosedLedgerHash() const { return mClosedLedgerHash; } bool hasLedger(const uint256& hash) const; - NewcoinAddress getNodePublic() const { return mNodePublic; } + bool hasTxSet(const uint256& hash) const; + NewcoinAddress getNodePublic() const { return mNodePublic; } void cycleStatus() { mPreviousLedgerHash = mClosedLedgerHash; mClosedLedgerHash.zero(); } }; diff --git a/src/PubKeyCache.cpp b/src/PubKeyCache.cpp index 0119969544..503fefc3d1 100644 --- a/src/PubKeyCache.cpp +++ b/src/PubKeyCache.cpp @@ -6,7 +6,6 @@ CKey::pointer PubKeyCache::locate(const NewcoinAddress& id) { - if(1) { // is it in cache boost::mutex::scoped_lock sl(mLock); std::map::iterator it(mCache.find(id)); @@ -43,7 +42,7 @@ CKey::pointer PubKeyCache::locate(const NewcoinAddress& id) return ckp; } -CKey::pointer PubKeyCache::store(const NewcoinAddress& id, CKey::pointer key) +CKey::pointer PubKeyCache::store(const NewcoinAddress& id, const CKey::pointer& key) { // stored if needed, returns cached copy (possibly the original) { boost::mutex::scoped_lock sl(mLock); @@ -52,14 +51,14 @@ CKey::pointer PubKeyCache::store(const NewcoinAddress& id, CKey::pointer key) return pit.first->second; } - std::vector pk=key->GetPubKey(); + std::vector pk = key->GetPubKey(); std::string encodedPK; theApp->getTxnDB()->getDB()->escape(&(pk.front()), pk.size(), encodedPK); - std::string sql="INSERT INTO PubKeys (ID,PubKey) VALUES ('"; - sql+=id.humanAccountID(); - sql+="',"; - sql+=encodedPK; + std::string sql = "INSERT INTO PubKeys (ID,PubKey) VALUES ('"; + sql += id.humanAccountID(); + sql += "',"; + sql += encodedPK; sql.append(");"); ScopedLock dbl(theApp->getTxnDB()->getDBLock()); diff --git a/src/PubKeyCache.h b/src/PubKeyCache.h index bf6995875c..69c19b2be6 100644 --- a/src/PubKeyCache.h +++ b/src/PubKeyCache.h @@ -18,7 +18,7 @@ public: PubKeyCache() { ; } CKey::pointer locate(const NewcoinAddress& id); - CKey::pointer store(const NewcoinAddress& id, CKey::pointer key); + CKey::pointer store(const NewcoinAddress& id, const CKey::pointer& key); void clear(); }; diff --git a/src/RPCServer.cpp b/src/RPCServer.cpp index 0ac81e6b54..3790691058 100644 --- a/src/RPCServer.cpp +++ b/src/RPCServer.cpp @@ -6,12 +6,14 @@ #include "Application.h" #include "RPC.h" #include "Wallet.h" -#include "Conversion.h" #include "NewcoinAddress.h" #include "AccountState.h" #include "NicknameState.h" #include "utils.h" #include "Log.h" +#include "RippleLines.h" + +#include "Pathfinder.h" #include @@ -19,6 +21,7 @@ #include #include #include +#include #include @@ -43,10 +46,11 @@ Json::Value RPCServer::RPCError(int iError) { rpcACT_NOT_FOUND, "actNotFound", "Account not found." }, { rpcBAD_SEED, "badSeed", "Disallowed seed." }, { rpcDST_ACT_MALFORMED, "dstActMalformed", "Destination account is malformed." }, - { rpcDST_AMT_MALFORMED, "dstAmtMalformed", "Destination amount/currency is malformed." }, + { rpcDST_ACT_MISSING, "dstActMissing", "Destination account does not exists." }, + { rpcDST_AMT_MALFORMED, "dstAmtMalformed", "Destination amount/currency/issuer is malformed." }, { rpcFAIL_GEN_DECRPYT, "failGenDecrypt", "Failed to decrypt generator." }, { rpcGETS_ACT_MALFORMED, "getsActMalformed", "Gets account malformed." }, - { rpcGETS_AMT_MALFORMED, "getsAmtMalformed", "Gets ammount malformed." }, + { rpcGETS_AMT_MALFORMED, "getsAmtMalformed", "Gets amount malformed." }, { rpcHOST_IP_MALFORMED, "hostIpMalformed", "Host IP is malformed." }, { rpcINSUF_FUNDS, "insufFunds", "Insufficient funds." }, { rpcINTERNAL, "internal", "Internal error." }, @@ -69,9 +73,10 @@ Json::Value RPCServer::RPCError(int iError) { rpcPAYS_AMT_MALFORMED, "paysAmtMalformed", "Pays amount malformed." }, { rpcPORT_MALFORMED, "portMalformed", "Port is malformed." }, { rpcPUBLIC_MALFORMED, "publicMalformed", "Public key is malformed." }, + { rpcQUALITY_MALFORMED, "qualityMalformed", "Quality malformed." }, { rpcSRC_ACT_MALFORMED, "srcActMalformed", "Source account is malformed." }, - { rpcSRC_AMT_MALFORMED, "srcAmtMalformed", "Source amount/currency is malformed." }, - { rpcSRC_MISSING, "srcMissing", "Source account does not exist." }, + { rpcSRC_ACT_MISSING, "srcActMissing", "Source account does not exist." }, + { rpcSRC_AMT_MALFORMED, "srcAmtMalformed", "Source amount/currency/issuer is malformed." }, { rpcSRC_UNCLAIMED, "srcUnclaimed", "Source account is not claimed." }, { rpcSUCCESS, "success", "Success." }, { rpcTXN_NOT_FOUND, "txnNotFound", "Transaction not found." }, @@ -248,7 +253,7 @@ Json::Value RPCServer::getMasterGenerator(const uint256& uLedger, const NewcoinA return RPCError(rpcNO_ACCOUNT); } - std::vector vucCipher = sleGen->getIFieldVL(sfGenerator); + std::vector vucCipher = sleGen->getFieldVL(sfGenerator); std::vector vucMasterGenerator = na0Private.accountPrivateDecrypt(na0Public, vucCipher); if (vucMasterGenerator.empty()) { @@ -279,7 +284,7 @@ Json::Value RPCServer::authorize(const uint256& uLedger, asSrc = mNetOps->getAccountState(uLedger, naSrcAccountID); if (!asSrc) { - return RPCError(rpcSRC_MISSING); + return RPCError(rpcSRC_ACT_MISSING); } NewcoinAddress naMasterGenerator; @@ -314,7 +319,7 @@ Json::Value RPCServer::authorize(const uint256& uLedger, { naMasterAccountPublic.setAccountPublic(naMasterGenerator, iIndex); - Log(lsINFO) << "authorize: " << iIndex << " : " << naMasterAccountPublic.humanAccountID() << " : " << naSrcAccountID.humanAccountID(); + Log(lsDEBUG) << "authorize: " << iIndex << " : " << naMasterAccountPublic.humanAccountID() << " : " << naSrcAccountID.humanAccountID(); bFound = naSrcAccountID.getAccountID() == naMasterAccountPublic.getAccountID(); if (!bFound) @@ -354,8 +359,7 @@ Json::Value RPCServer::authorize(const uint256& uLedger, saSrcBalance -= saFee; } - Json::Value obj; - return obj; + return Json::Value(); } // --> strIdent: public key, account ID, or regular seed. @@ -396,7 +400,7 @@ Json::Value RPCServer::accountFromString(const uint256& uLedger, NewcoinAddress& else { // Found master public key. - std::vector vucCipher = sleGen->getIFieldVL(sfGenerator); + std::vector vucCipher = sleGen->getFieldVL(sfGenerator); std::vector vucMasterGenerator = naRegular0Private.accountPrivateDecrypt(naRegular0Public, vucCipher); if (vucMasterGenerator.empty()) { @@ -419,7 +423,6 @@ Json::Value RPCServer::doAccountDomainSet(const Json::Value ¶ms) { NewcoinAddress naSrcAccountID; NewcoinAddress naSeed; - uint256 uLedger = mNetOps->getCurrentLedger(); if (!naSeed.setSeedGeneric(params[0u].asString())) { @@ -435,7 +438,7 @@ Json::Value RPCServer::doAccountDomainSet(const Json::Value ¶ms) NewcoinAddress naAccountPrivate; AccountState::pointer asSrc; STAmount saSrcBalance; - Json::Value obj = authorize(uLedger, naSeed, naSrcAccountID, naAccountPublic, naAccountPrivate, + Json::Value obj = authorize(uint256(0), naSeed, naSrcAccountID, naAccountPublic, naAccountPrivate, saSrcBalance, theConfig.FEE_DEFAULT, asSrc, naVerifyGenerator); if (!obj.empty()) @@ -473,7 +476,6 @@ Json::Value RPCServer::doAccountEmailSet(const Json::Value ¶ms) { NewcoinAddress naSrcAccountID; NewcoinAddress naSeed; - uint256 uLedger = mNetOps->getCurrentLedger(); if (!naSeed.setSeedGeneric(params[0u].asString())) { @@ -489,7 +491,7 @@ Json::Value RPCServer::doAccountEmailSet(const Json::Value ¶ms) NewcoinAddress naAccountPrivate; AccountState::pointer asSrc; STAmount saSrcBalance; - Json::Value obj = authorize(uLedger, naSeed, naSrcAccountID, naAccountPublic, naAccountPrivate, + Json::Value obj = authorize(uint256(0), naSeed, naSrcAccountID, naAccountPublic, naAccountPrivate, saSrcBalance, theConfig.FEE_DEFAULT, asSrc, naVerifyGenerator); if (!obj.empty()) @@ -546,7 +548,7 @@ Json::Value RPCServer::doAccountInfo(const Json::Value ¶ms) { std::string strIdent = params[0u].asString(); bool bIndex; - int iIndex = 2 == params.size()? lexical_cast_s(params[1u].asString()) : 0; + int iIndex = 2 == params.size() ? lexical_cast_s(params[1u].asString()) : 0; NewcoinAddress naAccount; Json::Value ret; @@ -566,12 +568,11 @@ Json::Value RPCServer::doAccountInfo(const Json::Value ¶ms) ret["accepted"] = jAccepted; - uint256 uCurrent = mNetOps->getCurrentLedger(); - Json::Value jCurrent = accountFromString(uCurrent, naAccount, bIndex, strIdent, iIndex); + Json::Value jCurrent = accountFromString(uint256(0), naAccount, bIndex, strIdent, iIndex); if (jCurrent.empty()) { - AccountState::pointer asCurrent = mNetOps->getAccountState(uCurrent, naAccount); + AccountState::pointer asCurrent = mNetOps->getAccountState(uint256(0), naAccount); if (asCurrent) asCurrent->addJson(jCurrent); @@ -595,7 +596,6 @@ Json::Value RPCServer::doAccountInfo(const Json::Value ¶ms) Json::Value RPCServer::doAccountMessageSet(const Json::Value& params) { NewcoinAddress naSrcAccountID; NewcoinAddress naSeed; - uint256 uLedger = mNetOps->getCurrentLedger(); NewcoinAddress naMessagePubKey; if (!naSeed.setSeedGeneric(params[0u].asString())) @@ -616,7 +616,7 @@ Json::Value RPCServer::doAccountMessageSet(const Json::Value& params) { NewcoinAddress naAccountPrivate; AccountState::pointer asSrc; STAmount saSrcBalance; - Json::Value obj = authorize(uLedger, naSeed, naSrcAccountID, naAccountPublic, naAccountPrivate, + Json::Value obj = authorize(uint256(0), naSeed, naSrcAccountID, naAccountPublic, naAccountPrivate, saSrcBalance, theConfig.FEE_DEFAULT, asSrc, naVerifyGenerator); std::vector vucDomain; @@ -656,7 +656,6 @@ Json::Value RPCServer::doAccountPublishSet(const Json::Value ¶ms) { NewcoinAddress naSrcAccountID; NewcoinAddress naSeed; - uint256 uLedger = mNetOps->getCurrentLedger(); if (!naSeed.setSeedGeneric(params[0u].asString())) { @@ -672,7 +671,7 @@ Json::Value RPCServer::doAccountPublishSet(const Json::Value ¶ms) NewcoinAddress naAccountPrivate; AccountState::pointer asSrc; STAmount saSrcBalance; - Json::Value obj = authorize(uLedger, naSeed, naSrcAccountID, naAccountPublic, naAccountPrivate, + Json::Value obj = authorize(uint256(0), naSeed, naSrcAccountID, naAccountPublic, naAccountPrivate, saSrcBalance, theConfig.FEE_DEFAULT, asSrc, naVerifyGenerator); if (!obj.empty()) @@ -714,7 +713,6 @@ Json::Value RPCServer::doAccountRateSet(const Json::Value ¶ms) { NewcoinAddress naSrcAccountID; NewcoinAddress naSeed; - uint256 uLedger = mNetOps->getCurrentLedger(); if (!naSeed.setSeedGeneric(params[0u].asString())) { @@ -730,7 +728,7 @@ Json::Value RPCServer::doAccountRateSet(const Json::Value ¶ms) NewcoinAddress naAccountPrivate; AccountState::pointer asSrc; STAmount saSrcBalance; - Json::Value obj = authorize(uLedger, naSeed, naSrcAccountID, naAccountPublic, naAccountPrivate, + Json::Value obj = authorize(uint256(0), naSeed, naSrcAccountID, naAccountPublic, naAccountPrivate, saSrcBalance, theConfig.FEE_DEFAULT, asSrc, naVerifyGenerator); if (!obj.empty()) @@ -770,7 +768,6 @@ Json::Value RPCServer::doAccountRateSet(const Json::Value ¶ms) Json::Value RPCServer::doAccountWalletSet(const Json::Value& params) { NewcoinAddress naSrcAccountID; NewcoinAddress naSeed; - uint256 uLedger = mNetOps->getCurrentLedger(); if (!naSeed.setSeedGeneric(params[0u].asString())) { @@ -786,7 +783,7 @@ Json::Value RPCServer::doAccountWalletSet(const Json::Value& params) { NewcoinAddress naAccountPrivate; AccountState::pointer asSrc; STAmount saSrcBalance; - Json::Value obj = authorize(uLedger, naSeed, naSrcAccountID, naAccountPublic, naAccountPrivate, + Json::Value obj = authorize(uint256(0), naSeed, naSrcAccountID, naAccountPublic, naAccountPrivate, saSrcBalance, theConfig.FEE_DEFAULT, asSrc, naMasterGenerator); std::vector vucDomain; @@ -831,6 +828,9 @@ Json::Value RPCServer::doAccountWalletSet(const Json::Value& params) { Json::Value RPCServer::doConnect(const Json::Value& params) { + if (theConfig.RUN_STANDALONE) + return "cannot connect in standalone mode"; + // connect [port] std::string strIp; int iPort = -1; @@ -915,8 +915,6 @@ Json::Value RPCServer::doDataStore(const Json::Value& params) // Note: Nicknames are not automatically looked up by commands as they are advisory and can be changed. Json::Value RPCServer::doNicknameInfo(const Json::Value& params) { - uint256 uLedger = mNetOps->getCurrentLedger(); - std::string strNickname = params[0u].asString(); boost::trim(strNickname); @@ -925,7 +923,7 @@ Json::Value RPCServer::doNicknameInfo(const Json::Value& params) return RPCError(rpcNICKNAME_MALFORMED); } - NicknameState::pointer nsSrc = mNetOps->getNicknameState(uLedger, strNickname); + NicknameState::pointer nsSrc = mNetOps->getNicknameState(uint256(0), strNickname); if (!nsSrc) { return RPCError(rpcNICKNAME_MISSING); @@ -945,7 +943,6 @@ Json::Value RPCServer::doNicknameSet(const Json::Value& params) { NewcoinAddress naSrcAccountID; NewcoinAddress naSeed; - uint256 uLedger = mNetOps->getCurrentLedger(); if (!naSeed.setSeedGeneric(params[0u].asString())) { @@ -961,7 +958,6 @@ Json::Value RPCServer::doNicknameSet(const Json::Value& params) std::string strOfferCurrency; std::string strNickname = params[2u].asString(); boost::trim(strNickname); - std::vector vucSignature; if (strNickname.empty()) { @@ -973,7 +969,7 @@ Json::Value RPCServer::doNicknameSet(const Json::Value& params) } STAmount saFee; - NicknameState::pointer nsSrc = mNetOps->getNicknameState(uLedger, strNickname); + NicknameState::pointer nsSrc = mNetOps->getNicknameState(uint256(0), strNickname); if (!nsSrc) { @@ -996,7 +992,7 @@ Json::Value RPCServer::doNicknameSet(const Json::Value& params) NewcoinAddress naAccountPrivate; AccountState::pointer asSrc; STAmount saSrcBalance; - Json::Value obj = authorize(uLedger, naSeed, naSrcAccountID, naAccountPublic, naAccountPrivate, + Json::Value obj = authorize(uint256(0), naSeed, naSrcAccountID, naAccountPublic, naAccountPrivate, saSrcBalance, saFee, asSrc, naMasterGenerator); if (!obj.empty()) @@ -1013,8 +1009,7 @@ Json::Value RPCServer::doNicknameSet(const Json::Value& params) 0, // YYY No source tag Ledger::getNicknameHash(strNickname), bSetOffer, - saMinimumOffer, - vucSignature); + saMinimumOffer); trans = mNetOps->submitTransaction(trans); @@ -1062,7 +1057,7 @@ Json::Value RPCServer::doOfferCreate(const Json::Value ¶ms) NewcoinAddress naAccountPrivate; AccountState::pointer asSrc; STAmount saSrcBalance; - Json::Value obj = authorize(mNetOps->getCurrentLedger(), naSeed, naSrcAccountID, naAccountPublic, naAccountPrivate, + Json::Value obj = authorize(uint256(0), naSeed, naSrcAccountID, naAccountPublic, naAccountPrivate, saSrcBalance, theConfig.FEE_DEFAULT, asSrc, naMasterGenerator); if (!obj.empty()) @@ -1108,7 +1103,7 @@ Json::Value RPCServer::doOfferCancel(const Json::Value ¶ms) NewcoinAddress naAccountPrivate; AccountState::pointer asSrc; STAmount saSrcBalance; - Json::Value obj = authorize(mNetOps->getCurrentLedger(), naSeed, naSrcAccountID, naAccountPublic, naAccountPrivate, + Json::Value obj = authorize(uint256(0), naSeed, naSrcAccountID, naAccountPublic, naAccountPrivate, saSrcBalance, theConfig.FEE_DEFAULT, asSrc, naMasterGenerator); if (!obj.empty()) @@ -1146,22 +1141,11 @@ Json::Value RPCServer::doOwnerInfo(const Json::Value& params) uint256 uAccepted = mNetOps->getClosedLedger(); Json::Value jAccepted = accountFromString(uAccepted, naAccount, bIndex, strIdent, iIndex); - if (jAccepted.empty()) - { - jAccepted["offers"] = mNetOps->getOwnerInfo(uAccepted, naAccount); - } + ret["accepted"] = jAccepted.empty() ? mNetOps->getOwnerInfo(uAccepted, naAccount) : jAccepted; - ret["accepted"] = jAccepted; + Json::Value jCurrent = accountFromString(uint256(0), naAccount, bIndex, strIdent, iIndex); - uint256 uCurrent = mNetOps->getCurrentLedger(); - Json::Value jCurrent = accountFromString(uCurrent, naAccount, bIndex, strIdent, iIndex); - - if (jCurrent.empty()) - { - jCurrent["offers"] = mNetOps->getOwnerInfo(uCurrent, naAccount); - } - - ret["current"] = jCurrent; + ret["current"] = jCurrent.empty() ? mNetOps->getOwnerInfo(uint256(0), naAccount) : jCurrent; return ret; } @@ -1172,7 +1156,6 @@ Json::Value RPCServer::doPasswordFund(const Json::Value ¶ms) NewcoinAddress naSrcAccountID; NewcoinAddress naDstAccountID; NewcoinAddress naSeed; - uint256 uLedger = mNetOps->getCurrentLedger(); if (!naSeed.setSeedGeneric(params[0u].asString())) { @@ -1192,7 +1175,7 @@ Json::Value RPCServer::doPasswordFund(const Json::Value ¶ms) NewcoinAddress naAccountPrivate; AccountState::pointer asSrc; STAmount saSrcBalance; - Json::Value obj = authorize(uLedger, naSeed, naSrcAccountID, naAccountPublic, naAccountPrivate, + Json::Value obj = authorize(uint256(0), naSeed, naSrcAccountID, naAccountPublic, naAccountPrivate, saSrcBalance, theConfig.FEE_DEFAULT, asSrc, naMasterGenerator); if (!obj.empty()) @@ -1319,6 +1302,356 @@ Json::Value RPCServer::doPeers(const Json::Value& params) return obj; } +// profile offers [submit] +// profile 0:offers 1:pass_a 2:account_a 3:currency_offer_a 4:pass_b 5:account_b 6:currency_offer_b 7: 8:[submit] +// issuer is the offering account +// the amount of each offer will be 1. +// --> count: defaults to 100, does 2 offers per iteration. +// --> submit: 'submit|true|false': defaults to false +// Prior to running allow each to have a credit line of what they will be getting from the other account. +Json::Value RPCServer::doProfile(const Json::Value ¶ms) +{ + int iArgs = params.size(); + NewcoinAddress naSeedA; + NewcoinAddress naAccountA; + uint160 uCurrencyOfferA; + NewcoinAddress naSeedB; + NewcoinAddress naAccountB; + uint160 uCurrencyOfferB; + uint32 iCount = 100; + bool bSubmit = false; + + if (iArgs < 7 || "offers" != params[0u].asString()) + { + return RPCError(rpcINVALID_PARAMS); + } + + if (!naSeedA.setSeedGeneric(params[1u].asString())) // + return RPCError(rpcINVALID_PARAMS); + + naAccountA.setAccountID(params[2u].asString()); // + + if (!STAmount::currencyFromString(uCurrencyOfferA, params[3u].asString())) // + return RPCError(rpcINVALID_PARAMS); + + if (!naSeedB.setSeedGeneric(params[4u].asString())) // + return RPCError(rpcINVALID_PARAMS); + + naAccountB.setAccountID(params[5u].asString()); // + if (!STAmount::currencyFromString(uCurrencyOfferB, params[6u].asString())) // + return RPCError(rpcINVALID_PARAMS); + + if (iArgs >= 8) + iCount = lexical_cast_s(params[7u].asString()); + + if (iArgs >= 9 && "false" != params[8u].asString()) + bSubmit = true; + + boost::posix_time::ptime ptStart(boost::posix_time::microsec_clock::local_time()); + + for (int i = iCount; i-- >= 0;) { + NewcoinAddress naMasterGeneratorA; + NewcoinAddress naAccountPublicA; + NewcoinAddress naAccountPrivateA; + AccountState::pointer asSrcA; + STAmount saSrcBalanceA; + Json::Value jvObjA = authorize(uint256(0), naSeedA, naAccountA, naAccountPublicA, naAccountPrivateA, + saSrcBalanceA, theConfig.FEE_DEFAULT, asSrcA, naMasterGeneratorA); + + if (!jvObjA.empty()) + return jvObjA; + + Transaction::pointer tpOfferA = Transaction::sharedOfferCreate( + naAccountPublicA, naAccountPrivateA, + naAccountA, // naSourceAccount, + asSrcA->getSeq(), // uSeq + theConfig.FEE_DEFAULT, + 0, // uSourceTag, + false, // bPassive + STAmount(uCurrencyOfferA, naAccountA.getAccountID(), 1), // saTakerPays + STAmount(uCurrencyOfferB, naAccountB.getAccountID(), 1), // saTakerGets + 0); // uExpiration + + if (bSubmit) + tpOfferA = mNetOps->submitTransaction(tpOfferA); + + NewcoinAddress naMasterGeneratorB; + NewcoinAddress naAccountPublicB; + NewcoinAddress naAccountPrivateB; + AccountState::pointer asSrcB; + STAmount saSrcBalanceB; + Json::Value jvObjB = authorize(uint256(0), naSeedB, naAccountB, naAccountPublicB, naAccountPrivateB, + saSrcBalanceB, theConfig.FEE_DEFAULT, asSrcB, naMasterGeneratorB); + + if (!jvObjB.empty()) + return jvObjB; + + Transaction::pointer tpOfferB = Transaction::sharedOfferCreate( + naAccountPublicB, naAccountPrivateB, + naAccountB, // naSourceAccount, + asSrcB->getSeq(), // uSeq + theConfig.FEE_DEFAULT, + 0, // uSourceTag, + false, // bPassive + STAmount(uCurrencyOfferB, naAccountB.getAccountID(), 1), // saTakerPays + STAmount(uCurrencyOfferA, naAccountA.getAccountID(), 1), // saTakerGets + 0); // uExpiration + + if (bSubmit) + tpOfferB = mNetOps->submitTransaction(tpOfferB); + } + + boost::posix_time::ptime ptEnd(boost::posix_time::microsec_clock::local_time()); + boost::posix_time::time_duration tdInterval = ptEnd-ptStart; + long lMicroseconds = tdInterval.total_microseconds(); + int iTransactions = iCount*2; + float fRate = lMicroseconds ? iTransactions/(lMicroseconds/1000000.0) : 0.0; + + Json::Value obj(Json::objectValue); + + obj["transactions"] = iTransactions; + obj["submit"] = bSubmit; + obj["start"] = boost::posix_time::to_simple_string(ptStart); + obj["end"] = boost::posix_time::to_simple_string(ptEnd); + obj["interval"] = boost::posix_time::to_simple_string(tdInterval); + obj["rate_per_second"] = fRate; + + return obj; +} + +// ripple +// [] +// + +// full|partial limit|average [] +// +// path: +// path + +// +// path_element: +// account [] [] +// offer [] +Json::Value RPCServer::doRipple(const Json::Value ¶ms) +{ + NewcoinAddress naSeed; + STAmount saSrcAmountMax; + uint160 uSrcCurrencyID; + NewcoinAddress naSrcAccountID; + NewcoinAddress naSrcIssuerID; + bool bPartial; + bool bFull; + bool bLimit; + bool bAverage; + NewcoinAddress naDstAccountID; + STAmount saDstAmount; + uint160 uDstCurrencyID; + + STPathSet spsPaths; + + naSrcIssuerID.setAccountID(params[4u].asString()); // + + if (!naSeed.setSeedGeneric(params[0u].asString())) // + { + return RPCError(rpcBAD_SEED); + } + else if (!naSrcAccountID.setAccountID(params[1u].asString())) // + { + return RPCError(rpcSRC_ACT_MALFORMED); + } + // [] + else if (!saSrcAmountMax.setFullValue(params[2u].asString(), params[3u].asString(), params[naSrcIssuerID.isValid() ? 4u : 1u].asString())) + { + // Log(lsINFO) << "naSrcIssuerID.isValid(): " << naSrcIssuerID.isValid(); + // Log(lsINFO) << "source_max: " << params[2u].asString(); + // Log(lsINFO) << "source_currency: " << params[3u].asString(); + // Log(lsINFO) << "source_issuer: " << params[naSrcIssuerID.isValid() ? 4u : 2u].asString(); + + return RPCError(rpcSRC_AMT_MALFORMED); + } + + int iArg = 4 + naSrcIssuerID.isValid(); + + // XXX bSrcRedeem & bSrcIssue not used. + STPath spPath; + + while (params.size() != iArg && params[iArg].asString() == "path") // path + { + Log(lsINFO) << "Path>"; + ++iArg; + + while (params.size() != iArg + && (params[iArg].asString() == "offer" || params[iArg].asString() == "account")) + { + if (params.size() >= iArg + 3 && params[iArg].asString() == "offer") // offer + { + Log(lsINFO) << "Offer>"; + uint160 uCurrencyID; + NewcoinAddress naIssuerID; + + ++iArg; + + if (!STAmount::currencyFromString(uCurrencyID, params[iArg++].asString())) // + { + return RPCError(rpcINVALID_PARAMS); + } + else if (naIssuerID.setAccountID(params[iArg].asString())) // [] + { + ++iArg; + } + + spPath.addElement(STPathElement( + uint160(0), + uCurrencyID, + naIssuerID.isValid() ? naIssuerID.getAccountID() : uint160(0))); + } + else if (params.size() >= iArg + 2 && params[iArg].asString() == "account") // account + { + Log(lsINFO) << "Account>"; + NewcoinAddress naAccountID; + uint160 uCurrencyID; + NewcoinAddress naIssuerID; + + ++iArg; + + if (!naAccountID.setAccountID(params[iArg++].asString())) // + { + return RPCError(rpcINVALID_PARAMS); + } + + if (params.size() != iArg && STAmount::currencyFromString(uCurrencyID, params[iArg].asString())) // [] + { + ++iArg; + } + + if (params.size() != iArg && naIssuerID.setAccountID(params[iArg].asString())) // [] + { + ++iArg; + } + + spPath.addElement(STPathElement( + naAccountID.getAccountID(), + uCurrencyID, + naIssuerID.isValid() ? naIssuerID.getAccountID() : uint160(0))); + } + else + { + return RPCError(rpcINVALID_PARAMS); + } + } + + if (spPath.isEmpty()) + { + return RPCError(rpcINVALID_PARAMS); + } + else + { + spsPaths.addPath(spPath); + spPath.clear(); + } + } + + // full|partial + bPartial = params.size() != iArg ? params[iArg].asString() == "partial" : false; + bFull = params.size() != iArg ? params[iArg].asString() == "full" : false; + + if (!bPartial && !bFull) + { + return RPCError(rpcINVALID_PARAMS); + } + else + { + ++iArg; + } + + // limit|average + bLimit = params.size() != iArg ? params[iArg].asString() == "limit" : false; + bAverage = params.size() != iArg ? params[iArg].asString() == "average" : false; + + if (!bLimit && !bAverage) + { + return RPCError(rpcINVALID_PARAMS); + } + else + { + ++iArg; + } + + if (params.size() != iArg && !naDstAccountID.setAccountID(params[iArg++].asString())) // + { + return RPCError(rpcDST_ACT_MALFORMED); + } + + const unsigned int uDstIssuer = params.size() == iArg + 3 ? iArg+2 : iArg-1; + + // + if (params.size() != iArg + 2 && params.size() != iArg + 3) + { + // Log(lsINFO) << "params.size(): " << params.size(); + + return RPCError(rpcDST_AMT_MALFORMED); + } + else if (!saDstAmount.setFullValue(params[iArg].asString(), params[iArg+1].asString(), params[uDstIssuer].asString())) + { + // Log(lsINFO) << " Amount: " << params[iArg].asString(); + // Log(lsINFO) << "Currency: " << params[iArg+1].asString(); + // Log(lsINFO) << " Issuer: " << params[uDstIssuer].asString(); + + return RPCError(rpcDST_AMT_MALFORMED); + } + + AccountState::pointer asDst = mNetOps->getAccountState(uint256(0), naDstAccountID); + STAmount saFee = theConfig.FEE_DEFAULT; + + NewcoinAddress naVerifyGenerator; + NewcoinAddress naAccountPublic; + NewcoinAddress naAccountPrivate; + AccountState::pointer asSrc; + STAmount saSrcBalance; + Json::Value obj = authorize(uint256(0), naSeed, naSrcAccountID, naAccountPublic, naAccountPrivate, + saSrcBalance, saFee, asSrc, naVerifyGenerator); + + if (!obj.empty()) + return obj; + + // YYY Could do some checking: source has funds or credit, dst exists and has sufficent credit limit. + // YYY Currency from same source or loops not allowed. + // YYY Limit paths length and count. + if (!asDst) + { + Log(lsINFO) << "naDstAccountID: " << naDstAccountID.humanAccountID(); + + return RPCError(rpcDST_ACT_MISSING); + } + + Transaction::pointer trans = Transaction::sharedPayment( + naAccountPublic, naAccountPrivate, + naSrcAccountID, + asSrc->getSeq(), + saFee, + 0, // YYY No source tag + naDstAccountID, + saDstAmount, + saSrcAmountMax, + spsPaths, + bPartial, + bLimit); + + trans = mNetOps->submitTransaction(trans); + + obj["transaction"] = trans->getSTransaction()->getJson(0); + obj["status"] = trans->getStatus(); + obj["seed"] = naSeed.humanSeed(); + obj["fee"] = saFee.getText(); + obj["srcAccountID"] = naSrcAccountID.humanAccountID(); + obj["dstAccountID"] = naDstAccountID.humanAccountID(); + obj["srcAmountMax"] = saSrcAmountMax.getText(); + obj["srcISO"] = saSrcAmountMax.getHumanCurrency(); + obj["dstAmount"] = saDstAmount.getText(); + obj["dstISO"] = saDstAmount.getHumanCurrency(); + obj["paths"] = spsPaths.getText(); + + return obj; +} + // ripple_line_set [] [] [] Json::Value RPCServer::doRippleLineSet(const Json::Value& params) { @@ -1326,12 +1659,10 @@ Json::Value RPCServer::doRippleLineSet(const Json::Value& params) NewcoinAddress naSrcAccountID; NewcoinAddress naDstAccountID; STAmount saLimitAmount; - uint256 uLedger = mNetOps->getCurrentLedger(); - bool bLimitAmount = true; bool bQualityIn = params.size() >= 6; bool bQualityOut = params.size() >= 7; - uint32 uQualityIn = bQualityIn ? lexical_cast_s(params[5u].asString()) : 0; - uint32 uQualityOut = bQualityOut ? lexical_cast_s(params[6u].asString()) : 0; + uint32 uQualityIn = 0; + uint32 uQualityOut = 0; if (!naSeed.setSeedGeneric(params[0u].asString())) { @@ -1345,10 +1676,18 @@ Json::Value RPCServer::doRippleLineSet(const Json::Value& params) { return RPCError(rpcDST_ACT_MALFORMED); } - else if (!saLimitAmount.setFullValue(params[3u].asString(), params.size() >= 5 ? params[4u].asString() : "")) + else if (!saLimitAmount.setFullValue(params[3u].asString(), params.size() >= 5 ? params[4u].asString() : "", params[2u].asString())) { return RPCError(rpcSRC_AMT_MALFORMED); } + else if (bQualityIn && !parseQuality(params[5u].asString(), uQualityIn)) + { + return RPCError(rpcQUALITY_MALFORMED); + } + else if (bQualityOut && !parseQuality(params[6u].asString(), uQualityOut)) + { + return RPCError(rpcQUALITY_MALFORMED); + } else { NewcoinAddress naMasterGenerator; @@ -1356,7 +1695,7 @@ Json::Value RPCServer::doRippleLineSet(const Json::Value& params) NewcoinAddress naAccountPrivate; AccountState::pointer asSrc; STAmount saSrcBalance; - Json::Value obj = authorize(uLedger, naSeed, naSrcAccountID, naAccountPublic, naAccountPrivate, + Json::Value obj = authorize(uint256(0), naSeed, naSrcAccountID, naAccountPublic, naAccountPrivate, saSrcBalance, theConfig.FEE_DEFAULT, asSrc, naMasterGenerator); if (!obj.empty()) @@ -1368,8 +1707,7 @@ Json::Value RPCServer::doRippleLineSet(const Json::Value& params) asSrc->getSeq(), theConfig.FEE_DEFAULT, 0, // YYY No source tag - naDstAccountID, - bLimitAmount, saLimitAmount, + saLimitAmount, bQualityIn, uQualityIn, bQualityOut, uQualityOut); @@ -1389,17 +1727,16 @@ Json::Value RPCServer::doRippleLineSet(const Json::Value& params) Json::Value RPCServer::doRippleLinesGet(const Json::Value ¶ms) { // uint256 uAccepted = mNetOps->getClosedLedger(); - uint256 uCurrent = mNetOps->getCurrentLedger(); std::string strIdent = params[0u].asString(); bool bIndex; - int iIndex = 2 == params.size()? lexical_cast_s(params[1u].asString()) : 0; + int iIndex = 2 == params.size() ? lexical_cast_s(params[1u].asString()) : 0; NewcoinAddress naAccount; Json::Value ret; - ret = accountFromString(uCurrent, naAccount, bIndex, strIdent, iIndex); + ret = accountFromString(uint256(0), naAccount, bIndex, strIdent, iIndex); if (!ret.empty()) return ret; @@ -1411,7 +1748,7 @@ Json::Value RPCServer::doRippleLinesGet(const Json::Value ¶ms) if (bIndex) ret["index"] = iIndex; - AccountState::pointer as = mNetOps->getAccountState(uCurrent, naAccount); + AccountState::pointer as = mNetOps->getAccountState(uint256(0), naAccount); if (as) { Json::Value jsonLines(Json::arrayValue); @@ -1420,70 +1757,29 @@ Json::Value RPCServer::doRippleLinesGet(const Json::Value ¶ms) // XXX This is wrong, we do access the current ledger and do need to worry about changes. // We access a committed ledger and need not worry about changes. - uint256 uRootIndex; - if (mNetOps->getDirLineInfo(uCurrent, naAccount, uRootIndex)) + RippleLines rippleLines(naAccount.getAccountID()); + BOOST_FOREACH(RippleState::pointer line, rippleLines.getLines()) { - Log(lsINFO) << "doRippleLinesGet: dir root index: " << uRootIndex.ToString(); - bool bDone = false; + STAmount saBalance = line->getBalance(); + STAmount saLimit = line->getLimit(); + STAmount saLimitPeer = line->getLimitPeer(); - while (!bDone) - { - uint64 uNodePrevious; - uint64 uNodeNext; - STVector256 svRippleNodes = mNetOps->getDirNodeInfo(uCurrent, uRootIndex, uNodePrevious, uNodeNext); + Json::Value jPeer = Json::Value(Json::objectValue); - Log(lsINFO) << "doRippleLinesGet: previous: " << strHex(uNodePrevious); - Log(lsINFO) << "doRippleLinesGet: next: " << strHex(uNodeNext); - Log(lsINFO) << "doRippleLinesGet: lines: " << svRippleNodes.peekValue().size(); + //jPeer["node"] = uNode.ToString(); - BOOST_FOREACH(uint256& uNode, svRippleNodes.peekValue()) - { - Log(lsINFO) << "doRippleLinesGet: line index: " << uNode.ToString(); + jPeer["account"] = line->getAccountIDPeer().humanAccountID(); + // Amount reported is positive if current account holds other account's IOUs. + // Amount reported is negative if other account holds current account's IOUs. + jPeer["balance"] = saBalance.getText(); + jPeer["currency"] = saBalance.getHumanCurrency(); + jPeer["limit"] = saLimit.getText(); + jPeer["limit_peer"] = saLimitPeer.getText(); + jPeer["quality_in"] = static_cast(line->getQualityIn()); + jPeer["quality_out"] = static_cast(line->getQualityOut()); - RippleState::pointer rsLine = mNetOps->accessRippleState(uCurrent, uNode); - - if (rsLine) - { - rsLine->setViewAccount(naAccount); - - STAmount saBalance = rsLine->getBalance(); - STAmount saLimit = rsLine->getLimit(); - STAmount saLimitPeer = rsLine->getLimitPeer(); - - Json::Value jPeer = Json::Value(Json::objectValue); - - jPeer["node"] = uNode.ToString(); - - jPeer["account"] = rsLine->getAccountIDPeer().humanAccountID(); - // Amount reported is positive if current account hold's other account's IOUs. - // Amount reported is negative if other account hold's current account's IOUs. - jPeer["balance"] = saBalance.getText(); - jPeer["currency"] = saBalance.getHumanCurrency(); - jPeer["limit"] = saLimit.getText(); - jPeer["limit_peer"] = saLimitPeer.getText(); - - jsonLines.append(jPeer); - } - else - { - Log(lsWARNING) << "doRippleLinesGet: Bad index: " << uNode.ToString(); - } - } - - if (uNodeNext) - { - uCurrent = Ledger::getDirNodeIndex(uRootIndex, uNodeNext); - } - else - { - bDone = true; - } - } - } - else - { - Log(lsINFO) << "doRippleLinesGet: no directory: " << uRootIndex.ToString(); + jsonLines.append(jPeer); } ret["lines"] = jsonLines; } @@ -1495,23 +1791,33 @@ Json::Value RPCServer::doRippleLinesGet(const Json::Value ¶ms) return ret; } -// send regular_seed paying_account account_id amount [currency] [send_max] [send_currency] +// send regular_seed paying_account account_id amount [currency] [issuer] [send_max] [send_currency] [send_issuer] Json::Value RPCServer::doSend(const Json::Value& params) { NewcoinAddress naSeed; NewcoinAddress naSrcAccountID; NewcoinAddress naDstAccountID; - STAmount saSrcAmount; + STAmount saSrcAmountMax; STAmount saDstAmount; std::string sSrcCurrency; std::string sDstCurrency; - uint256 uLedger = mNetOps->getCurrentLedger(); + std::string sSrcIssuer; + std::string sDstIssuer; if (params.size() >= 5) sDstCurrency = params[4u].asString(); - if (params.size() >= 7) - sSrcCurrency = params[6u].asString(); + if (params.size() >= 6) + sDstIssuer = params[5u].asString(); + + if (params.size() >= 8) + sSrcCurrency = params[7u].asString(); + + if (params.size() >= 9) + sSrcIssuer = params[8u].asString(); + + if (params.size() >= 9) + sSrcIssuer = params[8u].asString(); if (!naSeed.setSeedGeneric(params[0u].asString())) { @@ -1525,17 +1831,17 @@ Json::Value RPCServer::doSend(const Json::Value& params) { return RPCError(rpcDST_ACT_MALFORMED); } - else if (!saDstAmount.setFullValue(params[3u].asString(), sDstCurrency)) + else if (!saDstAmount.setFullValue(params[3u].asString(), sDstCurrency, sDstIssuer)) { return RPCError(rpcDST_AMT_MALFORMED); } - else if (params.size() >= 6 && !saSrcAmount.setFullValue(params[5u].asString(), sSrcCurrency)) + else if (params.size() >= 7 && !saSrcAmountMax.setFullValue(params[6u].asString(), sSrcCurrency, sSrcIssuer)) { return RPCError(rpcSRC_AMT_MALFORMED); } else { - AccountState::pointer asDst = mNetOps->getAccountState(uLedger, naDstAccountID); + AccountState::pointer asDst = mNetOps->getAccountState(uint256(0), naDstAccountID); bool bCreate = !asDst; STAmount saFee = bCreate ? theConfig.FEE_ACCOUNT_CREATE : theConfig.FEE_DEFAULT; @@ -1544,17 +1850,23 @@ Json::Value RPCServer::doSend(const Json::Value& params) NewcoinAddress naAccountPrivate; AccountState::pointer asSrc; STAmount saSrcBalance; - Json::Value obj = authorize(uLedger, naSeed, naSrcAccountID, naAccountPublic, naAccountPrivate, + Json::Value obj = authorize(uint256(0), naSeed, naSrcAccountID, naAccountPublic, naAccountPrivate, saSrcBalance, saFee, asSrc, naVerifyGenerator); + // Log(lsINFO) << boost::str(boost::format("doSend: sSrcIssuer=%s sDstIssuer=%s saSrcAmountMax=%s saDstAmount=%s") + // % sSrcIssuer + // % sDstIssuer + // % saSrcAmountMax.getFullText() + // % saDstAmount.getFullText()); + if (!obj.empty()) return obj; - if (params.size() < 6) - saSrcAmount = saDstAmount; + if (params.size() < 7) + saSrcAmountMax = saDstAmount; // Do a few simple checks. - if (!saSrcAmount.isNative()) + if (!saSrcAmountMax.isNative()) { Log(lsINFO) << "doSend: Ripple"; @@ -1567,11 +1879,11 @@ Json::Value RPCServer::doSend(const Json::Value& params) return RPCError(rpcINSUF_FUNDS); } - else if (saDstAmount.isNative() && saSrcAmount < saDstAmount) + else if (saDstAmount.isNative() && saSrcAmountMax < saDstAmount) { // Not enough native currency. - Log(lsINFO) << "doSend: Insufficient funds: src=" << saSrcAmount.getText() << " dst=" << saDstAmount.getText(); + Log(lsINFO) << "doSend: Insufficient funds: src=" << saSrcAmountMax.getText() << " dst=" << saDstAmount.getText(); return RPCError(rpcINSUF_FUNDS); } @@ -1582,7 +1894,18 @@ Json::Value RPCServer::doSend(const Json::Value& params) if (asDst) { // Destination exists, ordinary send. - STPathSet spPaths; + STPathSet spsPaths; + uint160 srcCurrencyID; +// bool ret_b; +// ret_b = false; + + if (!saSrcAmountMax.isNative() || !saDstAmount.isNative()) + { + STAmount::currencyFromString(srcCurrencyID, sSrcCurrency); + Pathfinder pf(naSrcAccountID, naDstAccountID, srcCurrencyID, saDstAmount); +// ret_b = pf.findPaths(5, 1, spsPaths); + pf.findPaths(5, 1, spsPaths); + } trans = Transaction::sharedPayment( naAccountPublic, naAccountPrivate, @@ -1592,8 +1915,8 @@ Json::Value RPCServer::doSend(const Json::Value& params) 0, // YYY No source tag naDstAccountID, saDstAmount, - saSrcAmount, - spPaths); + saSrcAmountMax, + spsPaths); } else { @@ -1618,8 +1941,8 @@ Json::Value RPCServer::doSend(const Json::Value& params) obj["create"] = bCreate; obj["srcAccountID"] = naSrcAccountID.humanAccountID(); obj["dstAccountID"] = naDstAccountID.humanAccountID(); - obj["srcAmount"] = saSrcAmount.getText(); - obj["srcISO"] = saSrcAmount.getHumanCurrency(); + obj["srcAmountMax"] = saSrcAmountMax.getText(); + obj["srcISO"] = saSrcAmountMax.getHumanCurrency(); obj["dstAmount"] = saDstAmount.getText(); obj["dstISO"] = saDstAmount.getHumanCurrency(); @@ -1870,7 +2193,6 @@ Json::Value RPCServer::accounts(const uint256& uLedger, const NewcoinAddress& na Json::Value RPCServer::doWalletAccounts(const Json::Value& params) { NewcoinAddress naSeed; - uint256 uLedger = mNetOps->getCurrentLedger(); if (!naSeed.setSeedGeneric(params[0u].asString())) { @@ -1880,17 +2202,17 @@ Json::Value RPCServer::doWalletAccounts(const Json::Value& params) // Try the seed as a master seed. NewcoinAddress naMasterGenerator = NewcoinAddress::createGeneratorPublic(naSeed); - Json::Value jsonAccounts = accounts(uLedger, naMasterGenerator); + Json::Value jsonAccounts = accounts(uint256(0), naMasterGenerator); if (jsonAccounts.empty()) { // No account via seed as master, try seed a regular. - Json::Value ret = getMasterGenerator(uLedger, naSeed, naMasterGenerator); + Json::Value ret = getMasterGenerator(uint256(0), naSeed, naMasterGenerator); if (!ret.empty()) return ret; - ret["accounts"] = accounts(uLedger, naMasterGenerator); + ret["accounts"] = accounts(uint256(0), naMasterGenerator); return ret; } @@ -1913,7 +2235,6 @@ Json::Value RPCServer::doWalletAdd(const Json::Value& params) NewcoinAddress naSrcAccountID; STAmount saAmount; std::string sDstCurrency; - uint256 uLedger = mNetOps->getCurrentLedger(); if (!naRegularSeed.setSeedGeneric(params[0u].asString())) { @@ -1940,7 +2261,7 @@ Json::Value RPCServer::doWalletAdd(const Json::Value& params) NewcoinAddress naAccountPrivate; AccountState::pointer asSrc; STAmount saSrcBalance; - Json::Value obj = authorize(uLedger, naRegularSeed, naSrcAccountID, naAccountPublic, naAccountPrivate, + Json::Value obj = authorize(uint256(0), naRegularSeed, naSrcAccountID, naAccountPublic, naAccountPrivate, saSrcBalance, theConfig.FEE_ACCOUNT_CREATE, asSrc, naMasterGenerator); if (!obj.empty()) @@ -1966,7 +2287,7 @@ Json::Value RPCServer::doWalletAdd(const Json::Value& params) ++iIndex; naNewAccountPublic.setAccountPublic(naMasterGenerator, iIndex); - asNew = mNetOps->getAccountState(uLedger, naNewAccountPublic); + asNew = mNetOps->getAccountState(uint256(0), naNewAccountPublic); if (!asNew) bAgain = false; } while (bAgain); @@ -2101,7 +2422,6 @@ Json::Value RPCServer::doWalletCreate(const Json::Value& params) NewcoinAddress naSrcAccountID; NewcoinAddress naDstAccountID; NewcoinAddress naSeed; - uint256 uLedger = mNetOps->getCurrentLedger(); if (!naSeed.setSeedGeneric(params[0u].asString())) { @@ -2115,7 +2435,7 @@ Json::Value RPCServer::doWalletCreate(const Json::Value& params) { return RPCError(rpcDST_ACT_MALFORMED); } - else if (mNetOps->getAccountState(uLedger, naDstAccountID)) + else if (mNetOps->getAccountState(uint256(0), naDstAccountID)) { return RPCError(rpcACT_EXISTS); } @@ -2128,7 +2448,7 @@ Json::Value RPCServer::doWalletCreate(const Json::Value& params) NewcoinAddress naAccountPrivate; AccountState::pointer asSrc; STAmount saSrcBalance; - Json::Value obj = authorize(uLedger, naSeed, naSrcAccountID, naAccountPublic, naAccountPrivate, + Json::Value obj = authorize(uint256(0), naSeed, naSrcAccountID, naAccountPublic, naAccountPrivate, saSrcBalance, theConfig.FEE_ACCOUNT_CREATE, asSrc, naMasterGenerator); if (!obj.empty()) @@ -2156,13 +2476,21 @@ Json::Value RPCServer::doWalletCreate(const Json::Value& params) return obj; } -// wallet_propose +// wallet_propose [] +// is only for testing. Master seeds should only be generated randomly. Json::Value RPCServer::doWalletPropose(const Json::Value& params) { NewcoinAddress naSeed; NewcoinAddress naAccount; - naSeed.setSeedRandom(); + if (params.empty()) + { + naSeed.setSeedRandom(); + } + else + { + naSeed = NewcoinAddress::createSeedGeneric(params[0u].asString()); + } NewcoinAddress naGenerator = NewcoinAddress::createGeneratorPublic(naSeed); naAccount.setAccountPublic(naGenerator, 0); @@ -2299,6 +2627,11 @@ Json::Value RPCServer::doLogin(const Json::Value& params) } } +Json::Value RPCServer::doLogRotate(const Json::Value& params) +{ + return Log::rotateLog(); +} + Json::Value RPCServer::doCommand(const std::string& command, Json::Value& params) { Log(lsTRACE) << "RPC:" << command; @@ -2324,6 +2657,7 @@ Json::Value RPCServer::doCommand(const std::string& command, Json::Value& params { "data_fetch", &RPCServer::doDataFetch, 1, 1, true }, { "data_store", &RPCServer::doDataStore, 2, 2, true }, { "ledger", &RPCServer::doLedger, 0, 2, false, optNetwork }, + { "logrotate", &RPCServer::doLogRotate, 0, 0, true }, { "nickname_info", &RPCServer::doNicknameInfo, 1, 1, false, optCurrent }, { "nickname_set", &RPCServer::doNicknameSet, 2, 3, false, optCurrent }, { "offer_create", &RPCServer::doOfferCreate, 9, 10, false, optCurrent }, @@ -2332,9 +2666,11 @@ Json::Value RPCServer::doCommand(const std::string& command, Json::Value& params { "password_fund", &RPCServer::doPasswordFund, 2, 3, false, optCurrent }, { "password_set", &RPCServer::doPasswordSet, 2, 3, false, optNetwork }, { "peers", &RPCServer::doPeers, 0, 0, true }, - { "ripple_lines_get", &RPCServer::doRippleLinesGet, 1, 2, false, optCurrent|optClosed }, + { "profile", &RPCServer::doProfile, 1, 9, false, optCurrent }, + { "ripple", &RPCServer::doRipple, 9, -1, false, optCurrent|optClosed }, + { "ripple_lines_get", &RPCServer::doRippleLinesGet, 1, 2, false, optCurrent }, { "ripple_line_set", &RPCServer::doRippleLineSet, 4, 7, false, optCurrent }, - { "send", &RPCServer::doSend, 3, 7, false, optCurrent }, + { "send", &RPCServer::doSend, 3, 9, false, optCurrent }, { "server_info", &RPCServer::doServerInfo, 0, 0, true }, { "stop", &RPCServer::doStop, 0, 0, true }, { "tx", &RPCServer::doTx, 1, 1, true }, @@ -2354,7 +2690,7 @@ Json::Value RPCServer::doCommand(const std::string& command, Json::Value& params { "wallet_add", &RPCServer::doWalletAdd, 3, 5, false, optCurrent }, { "wallet_claim", &RPCServer::doWalletClaim, 2, 4, false, optNetwork }, { "wallet_create", &RPCServer::doWalletCreate, 3, 4, false, optCurrent }, - { "wallet_propose", &RPCServer::doWalletPropose, 0, 0, false, }, + { "wallet_propose", &RPCServer::doWalletPropose, 0, 1, false, }, { "wallet_seed", &RPCServer::doWalletSeed, 0, 1, false, }, { "login", &RPCServer::doLogin, 2, 2, true }, @@ -2373,7 +2709,8 @@ Json::Value RPCServer::doCommand(const std::string& command, Json::Value& params { return RPCError(rpcNO_PERMISSION); } - else if (params.size() < commandsA[i].iMinParams || params.size() > commandsA[i].iMaxParams) + else if (params.size() < commandsA[i].iMinParams + || (commandsA[i].iMaxParams >= 0 && params.size() > commandsA[i].iMaxParams)) { return RPCError(rpcINVALID_PARAMS); } @@ -2381,7 +2718,8 @@ Json::Value RPCServer::doCommand(const std::string& command, Json::Value& params { return RPCError(rpcNO_NETWORK); } - else if ((commandsA[i].iOptions & optCurrent) && mNetOps->getCurrentLedger().isZero()) + // XXX Should verify we have a current ledger. + else if ((commandsA[i].iOptions & optCurrent) && false) { return RPCError(rpcNO_CURRENT); } @@ -2391,7 +2729,13 @@ Json::Value RPCServer::doCommand(const std::string& command, Json::Value& params } else { - return (this->*(commandsA[i].dfpFunc))(params); + try { + return (this->*(commandsA[i].dfpFunc))(params); + } + catch (...) + { + return RPCError(rpcINTERNAL); + } } } diff --git a/src/RPCServer.h b/src/RPCServer.h index 539dcf2e7d..d203cbc3ca 100644 --- a/src/RPCServer.h +++ b/src/RPCServer.h @@ -48,8 +48,10 @@ public: // Bad parameter rpcACT_MALFORMED, + rpcQUALITY_MALFORMED, rpcBAD_SEED, rpcDST_ACT_MALFORMED, + rpcDST_ACT_MISSING, rpcDST_AMT_MALFORMED, rpcGETS_ACT_MALFORMED, rpcGETS_AMT_MALFORMED, @@ -63,6 +65,7 @@ public: rpcPORT_MALFORMED, rpcPUBLIC_MALFORMED, rpcSRC_ACT_MALFORMED, + rpcSRC_ACT_MISSING, rpcSRC_AMT_MALFORMED, // Internal error (should never happen) @@ -138,6 +141,7 @@ private: Json::Value doDataFetch(const Json::Value& params); Json::Value doDataStore(const Json::Value& params); Json::Value doLedger(const Json::Value& params); + Json::Value doLogRotate(const Json::Value& params); Json::Value doNicknameInfo(const Json::Value& params); Json::Value doNicknameSet(const Json::Value& params); Json::Value doOfferCreate(const Json::Value& params); @@ -145,7 +149,9 @@ private: Json::Value doOwnerInfo(const Json::Value& params); Json::Value doPasswordFund(const Json::Value& params); Json::Value doPasswordSet(const Json::Value& params); + Json::Value doProfile(const Json::Value& params); Json::Value doPeers(const Json::Value& params); + Json::Value doRipple(const Json::Value ¶ms); Json::Value doRippleLinesGet(const Json::Value ¶ms); Json::Value doRippleLineSet(const Json::Value& params); Json::Value doSend(const Json::Value& params); @@ -180,6 +186,7 @@ private: Json::Value doLogin(const Json::Value& params); + public: static pointer create(boost::asio::io_service& io_service, NetworkOPs* mNetOps) { diff --git a/src/RippleCalc.cpp b/src/RippleCalc.cpp new file mode 100644 index 0000000000..e768d8180a --- /dev/null +++ b/src/RippleCalc.cpp @@ -0,0 +1,2400 @@ + +#include +#include +#include + +#include "RippleCalc.h" +#include "Log.h" + +#include "../json/writer.h" + +std::size_t hash_value(const aciSource& asValue) +{ + std::size_t seed = 0; + + asValue.get<0>().hash_combine(seed); + asValue.get<1>().hash_combine(seed); + asValue.get<2>().hash_combine(seed); + + return seed; +} + +// If needed, advance to next funded offer. +// - Automatically advances to first offer. +// - Set bEntryAdvance to advance to next entry. +// <-- uOfferIndex : 0=end of list. +TER RippleCalc::calcNodeAdvance( + const unsigned int uIndex, // 0 < uIndex < uLast + const PathState::pointer& pspCur, + const bool bMultiQuality, + const bool bReverse) +{ + PaymentNode& pnPrv = pspCur->vpnNodes[uIndex-1]; + PaymentNode& pnCur = pspCur->vpnNodes[uIndex]; + + const uint160& uPrvCurrencyID = pnPrv.uCurrencyID; + const uint160& uPrvIssuerID = pnPrv.uIssuerID; + const uint160& uCurCurrencyID = pnCur.uCurrencyID; + const uint160& uCurIssuerID = pnCur.uIssuerID; + + uint256& uDirectTip = pnCur.uDirectTip; + uint256 uDirectEnd = pnCur.uDirectEnd; + bool& bDirectAdvance = pnCur.bDirectAdvance; + SLE::pointer& sleDirectDir = pnCur.sleDirectDir; + STAmount& saOfrRate = pnCur.saOfrRate; + + bool& bEntryAdvance = pnCur.bEntryAdvance; + unsigned int& uEntry = pnCur.uEntry; + uint256& uOfferIndex = pnCur.uOfferIndex; + SLE::pointer& sleOffer = pnCur.sleOffer; + uint160& uOfrOwnerID = pnCur.uOfrOwnerID; + STAmount& saOfferFunds = pnCur.saOfferFunds; + STAmount& saTakerPays = pnCur.saTakerPays; + STAmount& saTakerGets = pnCur.saTakerGets; + bool& bFundsDirty = pnCur.bFundsDirty; + + TER terResult = tesSUCCESS; + + do + { + bool bDirectDirDirty = false; + + if (!uDirectEnd) + { + // Need to initialize current node. + + uDirectTip = Ledger::getBookBase(uPrvCurrencyID, uPrvIssuerID, uCurCurrencyID, uCurIssuerID); + uDirectEnd = Ledger::getQualityNext(uDirectTip); + sleDirectDir = lesActive.entryCache(ltDIR_NODE, uDirectTip); + bDirectAdvance = !sleDirectDir; + bDirectDirDirty = true; + + Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: Initialize node: uDirectTip=%s uDirectEnd=%s bDirectAdvance=%d") % uDirectTip % uDirectEnd % bDirectAdvance); + } + + if (bDirectAdvance) + { + // Get next quality. + uDirectTip = lesActive.getLedger()->getNextLedgerIndex(uDirectTip, uDirectEnd); + bDirectDirDirty = true; + bDirectAdvance = false; + + if (!!uDirectTip) + { + // Have another quality directory. + Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: Quality advance: uDirectTip=%s") % uDirectTip); + + sleDirectDir = lesActive.entryCache(ltDIR_NODE, uDirectTip); + } + else if (bReverse) + { + Log(lsINFO) << "calcNodeAdvance: No more offers."; + + uOfferIndex = 0; + break; + } + else + { + // No more offers. Should be done rather than fall off end of book. + Log(lsINFO) << "calcNodeAdvance: Unreachable: Fell off end of order book."; + assert(false); + + terResult = tefEXCEPTION; + } + } + + if (bDirectDirDirty) + { + saOfrRate = STAmount::setRate(Ledger::getQuality(uDirectTip)); // For correct ratio + uEntry = 0; + bEntryAdvance = true; + + Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: directory dirty: saOfrRate=%s") % saOfrRate); + } + + if (!bEntryAdvance) + { + if (bFundsDirty) + { + saTakerPays = sleOffer->getFieldAmount(sfTakerPays); + saTakerGets = sleOffer->getFieldAmount(sfTakerGets); + + saOfferFunds = lesActive.accountFunds(uOfrOwnerID, saTakerGets); // Funds left. + bFundsDirty = false; + + Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: directory dirty: saOfrRate=%s") % saOfrRate); + } + else + { + Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: as is")); + nothing(); + } + } + else if (!lesActive.dirNext(uDirectTip, sleDirectDir, uEntry, uOfferIndex)) + { + // Failed to find an entry in directory. + + uOfferIndex = 0; + + // Do another cur directory iff bMultiQuality + if (bMultiQuality) + { + Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: next quality")); + bDirectAdvance = true; + } + else if (!bReverse) + { + Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: unreachable: ran out of offers")); + assert(false); // Can't run out of offers in forward direction. + terResult = tefEXCEPTION; + } + } + else + { + // Got a new offer. + sleOffer = lesActive.entryCache(ltOFFER, uOfferIndex); + uOfrOwnerID = sleOffer->getFieldAccount(sfAccount).getAccountID(); + + const aciSource asLine = boost::make_tuple(uOfrOwnerID, uCurCurrencyID, uCurIssuerID); + + Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: uOfrOwnerID=%s") % NewcoinAddress::createHumanAccountID(uOfrOwnerID)); + + if (sleOffer->isFieldPresent(sfExpiration) && sleOffer->getFieldU32(sfExpiration) <= lesActive.getLedger()->getParentCloseTimeNC()) + { + // Offer is expired. + Log(lsINFO) << "calcNodeAdvance: expired offer"; + + assert(musUnfundedFound.find(uOfferIndex) != musUnfundedFound.end()); // Verify reverse found it too. + bEntryAdvance = true; + continue; + } + + // Allowed to access source from this node? + // XXX This can get called multiple times for same source in a row, caching result would be nice. + curIssuerNodeConstIterator itForward = pspCur->umForward.find(asLine); + const bool bFoundForward = itForward != pspCur->umForward.end(); + + if (bFoundForward && itForward->second != uIndex) + { + // Temporarily unfunded. Another node uses this source, ignore in this offer. + Log(lsINFO) << "calcNodeAdvance: temporarily unfunded offer (forward)"; + + bEntryAdvance = true; + continue; + } + + curIssuerNodeConstIterator itPast = mumSource.find(asLine); + bool bFoundPast = itPast != mumSource.end(); + + if (bFoundPast && itPast->second != uIndex) + { + // Temporarily unfunded. Another node uses this source, ignore in this offer. + Log(lsINFO) << "calcNodeAdvance: temporarily unfunded offer (past)"; + + bEntryAdvance = true; + continue; + } + + curIssuerNodeConstIterator itReverse = pspCur->umReverse.find(asLine); + bool bFoundReverse = itReverse != pspCur->umReverse.end(); + + if (bFoundReverse && itReverse->second != uIndex) + { + // Temporarily unfunded. Another node uses this source, ignore in this offer. + Log(lsINFO) << "calcNodeAdvance: temporarily unfunded offer (reverse)"; + + bEntryAdvance = true; + continue; + } + + saTakerPays = sleOffer->getFieldAmount(sfTakerPays); + saTakerGets = sleOffer->getFieldAmount(sfTakerGets); + + saOfferFunds = lesActive.accountFunds(uOfrOwnerID, saTakerGets); // Funds left. + + if (!saOfferFunds.isPositive()) + { + // Offer is unfunded. + Log(lsINFO) << "calcNodeAdvance: unfunded offer"; + + if (bReverse && !bFoundReverse && !bFoundPast) + { + // Never mentioned before: found unfunded. + musUnfundedFound.insert(uOfferIndex); // Mark offer for always deletion. + } + + // YYY Could verify offer is correct place for unfundeds. + bEntryAdvance = true; + continue; + } + + if (bReverse // Need to remember reverse mention. + && !bFoundPast // Not mentioned in previous passes. + && !bFoundReverse) // Not mentioned for pass. + { + // Consider source mentioned by current path state. + Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: remember=%s/%s/%s") + % NewcoinAddress::createHumanAccountID(uOfrOwnerID) + % STAmount::createHumanCurrency(uCurCurrencyID) + % NewcoinAddress::createHumanAccountID(uCurIssuerID)); + + pspCur->umReverse.insert(std::make_pair(asLine, uIndex)); + } + + bFundsDirty = false; + bEntryAdvance = false; + } + } + while (tesSUCCESS == terResult && (bEntryAdvance || bDirectAdvance)); + + if (tesSUCCESS == terResult) + { + Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: uOfferIndex=%s") % uOfferIndex); + } + else + { + Log(lsINFO) << boost::str(boost::format("calcNodeAdvance: terResult=%s") % transToken(terResult)); + } + + return terResult; +} + +// Between offer nodes, the fee charged may vary. Therefore, process one inbound offer at a time. +// Propagate the inbound offer's requirements to the previous node. The previous node adjusts the amount output and the +// amount spent on fees. +// Continue process till request is satisified while we the rate does not increase past the initial rate. +TER RippleCalc::calcNodeDeliverRev( + const unsigned int uIndex, // 0 < uIndex < uLast + const PathState::pointer& pspCur, + const bool bMultiQuality, + const uint160& uOutAccountID, // --> Output owner's account. + const STAmount& saOutReq, // --> Funds wanted. + STAmount& saOutAct) // <-- Funds delivered. +{ + TER terResult = tesSUCCESS; + + PaymentNode& pnPrv = pspCur->vpnNodes[uIndex-1]; + PaymentNode& pnCur = pspCur->vpnNodes[uIndex]; + + const uint160& uCurIssuerID = pnCur.uIssuerID; + const uint160& uPrvAccountID = pnPrv.uAccountID; + const STAmount& saTransferRate = pnCur.saTransferRate; + + STAmount& saPrvDlvReq = pnPrv.saRevDeliver; // To be adjusted. + + saOutAct = 0; + + while (saOutAct != saOutReq) // Did not deliver limit. + { + bool& bEntryAdvance = pnCur.bEntryAdvance; + STAmount& saOfrRate = pnCur.saOfrRate; + uint256& uOfferIndex = pnCur.uOfferIndex; + SLE::pointer& sleOffer = pnCur.sleOffer; + const uint160& uOfrOwnerID = pnCur.uOfrOwnerID; + bool& bFundsDirty = pnCur.bFundsDirty; + STAmount& saOfferFunds = pnCur.saOfferFunds; + STAmount& saTakerPays = pnCur.saTakerPays; + STAmount& saTakerGets = pnCur.saTakerGets; + STAmount& saRateMax = pnCur.saRateMax; + + terResult = calcNodeAdvance(uIndex, pspCur, bMultiQuality, true); // If needed, advance to next funded offer. + + if (tesSUCCESS != terResult || !uOfferIndex) + { + // Error or out of offers. + break; + } + + const STAmount saOutFeeRate = uOfrOwnerID == uCurIssuerID || uOutAccountID == uCurIssuerID // Issuer receiving or sending. + ? saOne // No fee. + : saTransferRate; // Transfer rate of issuer. + Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: uOfrOwnerID=%s uOutAccountID=%s uCurIssuerID=%s saTransferRate=%s saOutFeeRate=%s") + % NewcoinAddress::createHumanAccountID(uOfrOwnerID) + % NewcoinAddress::createHumanAccountID(uOutAccountID) + % NewcoinAddress::createHumanAccountID(uCurIssuerID) + % saTransferRate.getFullText() + % saOutFeeRate.getFullText()); + + if (!saRateMax) + { + // Set initial rate. + saRateMax = saOutFeeRate; + + Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: Set initial rate: saRateMax=%s saOutFeeRate=%s") + % saRateMax + % saOutFeeRate); + } + else if (saRateMax < saOutFeeRate) + { + // Offer exceeds initial rate. + Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: Offer exceeds initial rate: saRateMax=%s saOutFeeRate=%s") + % saRateMax + % saOutFeeRate); + + nothing(); + break; + } + else if (saOutFeeRate < saRateMax) + { + // Reducing rate. + + saRateMax = saOutFeeRate; + + Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: Reducing rate: saRateMax=%s") + % saRateMax); + } + + STAmount saOutPass = std::min(std::min(saOfferFunds, saTakerGets), saOutReq-saOutAct); // Offer maximum out - assuming no out fees. + STAmount saOutPlusFees = STAmount::multiply(saOutPass, saOutFeeRate); // Offer out with fees. + + Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: saOutReq=%s saOutAct=%s saTakerGets=%s saOutPass=%s saOutPlusFees=%s saOfferFunds=%s") + % saOutReq + % saOutAct + % saTakerGets + % saOutPass + % saOutPlusFees + % saOfferFunds); + + if (saOutPlusFees > saOfferFunds) + { + // Offer owner can not cover all fees, compute saOutPass based on saOfferFunds. + + saOutPlusFees = saOfferFunds; + saOutPass = STAmount::divide(saOutPlusFees, saOutFeeRate); + + Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: Total exceeds fees: saOutPass=%s saOutPlusFees=%s saOfferFunds=%s") + % saOutPass + % saOutPlusFees + % saOfferFunds); + } + + // Compute portion of input needed to cover output. + + STAmount saInPassReq = STAmount::multiply(saOutPass, saOfrRate, saTakerPays); + STAmount saInPassAct; + + Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: saInPassReq=%s saOfrRate=%s saOutPass=%s saOutPlusFees=%s") + % saInPassReq + % saOfrRate + % saOutPass + % saOutPlusFees); + + // Find out input amount actually available at current rate. + if (!!uPrvAccountID) + { + // account --> OFFER --> ? + // Previous is the issuer and receiver is an offer, so no fee or quality. + // Previous is the issuer and has unlimited funds. + // Offer owner is obtaining IOUs via an offer, so credit line limits are ignored. + // As limits are ignored, don't need to adjust previous account's balance. + + saInPassAct = saInPassReq; + + Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: account --> OFFER --> ? : saInPassAct=%s") + % saPrvDlvReq); + } + else + { + // offer --> OFFER --> ? + + terResult = calcNodeDeliverRev( + uIndex-1, + pspCur, + bMultiQuality, + uOfrOwnerID, + saInPassReq, + saInPassAct); + + Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: offer --> OFFER --> ? : saInPassAct=%s") + % saInPassAct); + } + + if (tesSUCCESS != terResult) + break; + + if (saInPassAct != saInPassReq) + { + // Adjust output to conform to limited input. + saOutPass = STAmount::divide(saInPassAct, saOfrRate, saTakerGets); + saOutPlusFees = STAmount::multiply(saOutPass, saOutFeeRate); + + Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: adjusted: saOutPass=%s saOutPlusFees=%s") + % saOutPass + % saOutPlusFees); + } + + // Funds were spent. + bFundsDirty = true; + + // Deduct output, don't actually need to send. + lesActive.accountSend(uOfrOwnerID, uCurIssuerID, saOutPass); + + // Adjust offer + sleOffer->setFieldAmount(sfTakerGets, saTakerGets - saOutPass); + sleOffer->setFieldAmount(sfTakerPays, saTakerPays - saInPassAct); + + lesActive.entryModify(sleOffer); + + if (saOutPass == saTakerGets) + { + // Offer became unfunded. + Log(lsINFO) << boost::str(boost::format("calcNodeDeliverRev: offer became unfunded.")); + + bEntryAdvance = true; + } + + saOutAct += saOutPass; + saPrvDlvReq += saInPassAct; + } + + if (!saOutAct) + terResult = tepPATH_DRY; + + return terResult; +} + +// Deliver maximum amount of funds from previous node. +// Goal: Make progress consuming the offer. +TER RippleCalc::calcNodeDeliverFwd( + const unsigned int uIndex, // 0 < uIndex < uLast + const PathState::pointer& 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. + STAmount& saInAct, // <-- Amount delivered. + STAmount& saInFees) // <-- Fees charged. +{ + TER terResult = tesSUCCESS; + + PaymentNode& pnPrv = pspCur->vpnNodes[uIndex-1]; + PaymentNode& pnCur = pspCur->vpnNodes[uIndex]; + PaymentNode& pnNxt = pspCur->vpnNodes[uIndex+1]; + + const uint160& uNxtAccountID = pnNxt.uAccountID; + const uint160& uCurIssuerID = pnCur.uIssuerID; + const uint160& uPrvIssuerID = pnPrv.uIssuerID; + const STAmount& saTransferRate = pnPrv.saTransferRate; + + saInAct = 0; + saInFees = 0; + + while (tesSUCCESS == terResult + && saInAct != saInReq // Did not deliver limit. + && saInAct + saInFees != saInFunds) // Did not deliver all funds. + { + terResult = calcNodeAdvance(uIndex, pspCur, bMultiQuality, false); // If needed, advance to next funded offer. + + if (tesSUCCESS == terResult) + { + bool& bEntryAdvance = pnCur.bEntryAdvance; + STAmount& saOfrRate = pnCur.saOfrRate; + uint256& uOfferIndex = pnCur.uOfferIndex; + SLE::pointer& sleOffer = pnCur.sleOffer; + const uint160& uOfrOwnerID = pnCur.uOfrOwnerID; + bool& bFundsDirty = pnCur.bFundsDirty; + STAmount& saOfferFunds = pnCur.saOfferFunds; + 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. + + // + // First calculate assuming no output fees. + // XXX Make sure derived in does not exceed actual saTakerPays due to rounding. + + 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 saInPassAct = STAmount::divide(saInSum, saInFeeRate); // In without fees. + STAmount saOutPassMax = STAmount::divide(saInPassAct, saOfrRate, saOutFunded); // Out. + + STAmount saInPassFees; + STAmount saOutPassAct; + + Log(lsINFO) << boost::str(boost::format("calcNodeDeliverFwd: saOutFunded=%s saInFunded=%s saInTotal=%s saInSum=%s saInPassAct=%s saOutPassMax=%s") + % saOutFunded + % saInFunded + % saInTotal + % saInSum + % saInPassAct + % saOutPassMax); + + if (!!uNxtAccountID) + { + // ? --> 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); + + saOutPassAct = saOutPassMax; + + Log(lsINFO) << boost::str(boost::format("calcNodeDeliverFwd: ? --> OFFER --> account: saOutPassAct=%s") + % saOutPassAct); + } + else + { + // ? --> OFFER --> offer + STAmount saOutPassFees; + + terResult = RippleCalc::calcNodeDeliverFwd( + uIndex+1, + pspCur, + bMultiQuality, + uOfrOwnerID, + saOutPassMax, + saOutPassMax, + saOutPassAct, // <-- Amount delivered. + saOutPassFees); // <-- Fees charged. + + if (tesSUCCESS != terResult) + break; + + // Offer maximum in limited by next payout. + saInPassAct = STAmount::multiply(saOutPassAct, saOfrRate); + saInPassFees = STAmount::multiply(saInFunded, saInFeeRate)-saInPassAct; + } + + Log(lsINFO) << boost::str(boost::format("calcNodeDeliverFwd: saTakerGets=%s saTakerPays=%s saInPassAct=%s saOutPassAct=%s") + % saTakerGets.getFullText() + % saTakerPays.getFullText() + % saInPassAct.getFullText() + % saOutPassAct.getFullText()); + + // Funds were spent. + bFundsDirty = true; + + // Credit issuer transfer fees. + lesActive.accountSend(uInAccountID, uOfrOwnerID, saInPassFees); + + // Credit offer owner from offer. + lesActive.accountSend(uInAccountID, uOfrOwnerID, saInPassAct); + + // Adjust offer + sleOffer->setFieldAmount(sfTakerGets, saTakerGets - saOutPassAct); + sleOffer->setFieldAmount(sfTakerPays, saTakerPays - saInPassAct); + + lesActive.entryModify(sleOffer); + + if (saOutPassAct == saTakerGets) + { + // Offer became unfunded. + pspCur->vUnfundedBecame.push_back(uOfferIndex); + bEntryAdvance = true; + } + + saInAct += saInPassAct; + saInFees += saInPassFees; + } + } + + return terResult; +} + +// Called to drive from the last offer node in a chain. +TER RippleCalc::calcNodeOfferRev( + const unsigned int uIndex, // 0 < uIndex < uLast + const PathState::pointer& pspCur, + const bool bMultiQuality) +{ + TER terResult; + + PaymentNode& pnCur = pspCur->vpnNodes[uIndex]; + PaymentNode& pnNxt = pspCur->vpnNodes[uIndex+1]; + + if (!!pnNxt.uAccountID) + { + // Next is an account node, resolve current offer node's deliver. + STAmount saDeliverAct; + + terResult = calcNodeDeliverRev( + uIndex, + pspCur, + bMultiQuality, + + pnNxt.uAccountID, + pnCur.saRevDeliver, + saDeliverAct); + } + else + { + // Next is an offer. Deliver has already been resolved. + terResult = tesSUCCESS; + } + + return terResult; +} + +// Called to drive the from the first offer node in a chain. +// - Offer input is limbo. +// - Current offers consumed. +// - Current offer owners debited. +// - Transfer fees credited to issuer. +// - Payout to issuer or limbo. +// - Deliver is set without transfer fees. +TER RippleCalc::calcNodeOfferFwd( + const unsigned int uIndex, // 0 < uIndex < uLast + const PathState::pointer& pspCur, + const bool bMultiQuality + ) +{ + TER terResult; + PaymentNode& pnPrv = pspCur->vpnNodes[uIndex-1]; + + if (!!pnPrv.uAccountID) + { + // Previous is an account node, resolve its deliver. + STAmount saInAct; + STAmount saInFees; + + terResult = calcNodeDeliverFwd( + uIndex, + pspCur, + bMultiQuality, + pnPrv.uAccountID, + pnPrv.saFwdDeliver, + pnPrv.saFwdDeliver, + saInAct, + saInFees); + + assert(tesSUCCESS != terResult || pnPrv.saFwdDeliver == saInAct+saInFees); + } + else + { + // Previous is an offer. Deliver has already been resolved. + terResult = tesSUCCESS; + } + + return terResult; + +} + +// Cur is the driver and will be filled exactly. +// uQualityIn -> uQualityOut +// saPrvReq -> saCurReq +// sqPrvAct -> saCurAct +// This is a minimizing routine: moving in reverse it propagates the send limit to the sender, moving forward it propagates the +// actual send toward the receiver. +// This routine works backwards as it calculates previous wants based on previous credit limits and current wants. +// This routine works forwards as it calculates current deliver based on previous delivery limits and current wants. +// XXX Deal with uQualityIn or uQualityOut = 0 +void RippleCalc::calcNodeRipple( + const uint32 uQualityIn, + const uint32 uQualityOut, + const STAmount& saPrvReq, // --> in limit including fees, <0 = unlimited + const STAmount& saCurReq, // --> out limit (driver) + STAmount& saPrvAct, // <-> in limit including achieved + STAmount& saCurAct, // <-> out limit achieved. + uint64& uRateMax) +{ + Log(lsINFO) << boost::str(boost::format("calcNodeRipple> uQualityIn=%d uQualityOut=%d saPrvReq=%s saCurReq=%s saPrvAct=%s saCurAct=%s") + % uQualityIn + % uQualityOut + % saPrvReq.getFullText() + % saCurReq.getFullText() + % saPrvAct.getFullText() + % saCurAct.getFullText()); + + assert(saPrvReq.getCurrency() == saCurReq.getCurrency()); + + const bool bPrvUnlimited = saPrvReq.isNegative(); + const STAmount saPrv = bPrvUnlimited ? STAmount(saPrvReq) : saPrvReq-saPrvAct; + const STAmount saCur = saCurReq-saCurAct; + +#if 0 + Log(lsINFO) << boost::str(boost::format("calcNodeRipple: bPrvUnlimited=%d saPrv=%s saCur=%s") + % bPrvUnlimited + % saPrv.getFullText() + % saCur.getFullText()); +#endif + + if (uQualityIn >= uQualityOut) + { + // No fee. + Log(lsINFO) << boost::str(boost::format("calcNodeRipple: No fees")); + + if (!uRateMax || STAmount::uRateOne <= uRateMax) + { + STAmount saTransfer = bPrvUnlimited ? saCur : std::min(saPrv, saCur); + + saPrvAct += saTransfer; + saCurAct += saTransfer; + + if (!uRateMax) + uRateMax = STAmount::uRateOne; + } + } + else + { + // Fee. + Log(lsINFO) << boost::str(boost::format("calcNodeRipple: Fee")); + + uint64 uRate = STAmount::getRate(STAmount(uQualityIn), STAmount(uQualityOut)); + + if (!uRateMax || uRate <= uRateMax) + { + const uint160 uCurrencyID = saCur.getCurrency(); + const uint160 uCurIssuerID = saCur.getIssuer(); + // const uint160 uPrvIssuerID = saPrv.getIssuer(); + + STAmount saCurIn = STAmount::divide(STAmount::multiply(saCur, uQualityOut, uCurrencyID, uCurIssuerID), uQualityIn, uCurrencyID, uCurIssuerID); + + Log(lsINFO) << boost::str(boost::format("calcNodeRipple: bPrvUnlimited=%d saPrv=%s saCurIn=%s") % bPrvUnlimited % saPrv.getFullText() % saCurIn.getFullText()); + if (bPrvUnlimited || saCurIn <= saPrv) + { + // All of cur. Some amount of prv. + saCurAct += saCur; + saPrvAct += saCurIn; + Log(lsINFO) << boost::str(boost::format("calcNodeRipple:3c: saCurReq=%s saPrvAct=%s") % saCurReq.getFullText() % saPrvAct.getFullText()); + } + else + { + // A part of cur. All of prv. (cur as driver) + STAmount saCurOut = STAmount::divide(STAmount::multiply(saPrv, uQualityIn, uCurrencyID, uCurIssuerID), uQualityOut, uCurrencyID, uCurIssuerID); + Log(lsINFO) << boost::str(boost::format("calcNodeRipple:4: saCurReq=%s") % saCurReq.getFullText()); + + saCurAct += saCurOut; + saPrvAct = saPrvReq; + + if (!uRateMax) + uRateMax = uRate; + } + } + } + + Log(lsINFO) << boost::str(boost::format("calcNodeRipple< uQualityIn=%d uQualityOut=%d saPrvReq=%s saCurReq=%s saPrvAct=%s saCurAct=%s") + % uQualityIn + % uQualityOut + % saPrvReq.getFullText() + % saCurReq.getFullText() + % saPrvAct.getFullText() + % saCurAct.getFullText()); +} + +// Calculate saPrvRedeemReq, saPrvIssueReq, saPrvDeliver from saCur... +// <-- tesSUCCESS or tepPATH_DRY +TER RippleCalc::calcNodeAccountRev(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality) +{ + TER terResult = tesSUCCESS; + const unsigned int uLast = pspCur->vpnNodes.size() - 1; + + uint64 uRateMax = 0; + + PaymentNode& pnPrv = pspCur->vpnNodes[uIndex ? uIndex-1 : 0]; + PaymentNode& pnCur = pspCur->vpnNodes[uIndex]; + PaymentNode& pnNxt = pspCur->vpnNodes[uIndex == uLast ? uLast : uIndex+1]; + + // Current is allowed to redeem to next. + const bool bPrvAccount = !uIndex || isSetBit(pnPrv.uFlags, STPathElement::typeAccount); + const bool bNxtAccount = uIndex == uLast || isSetBit(pnNxt.uFlags, STPathElement::typeAccount); + + const uint160& uCurAccountID = pnCur.uAccountID; + const uint160& uPrvAccountID = bPrvAccount ? pnPrv.uAccountID : uCurAccountID; + const uint160& uNxtAccountID = bNxtAccount ? pnNxt.uAccountID : uCurAccountID; // Offers are always issue. + + const uint160& uCurrencyID = pnCur.uCurrencyID; + + const uint32 uQualityIn = uIndex ? lesActive.rippleQualityIn(uCurAccountID, uPrvAccountID, uCurrencyID) : QUALITY_ONE; + const uint32 uQualityOut = uIndex != uLast ? lesActive.rippleQualityOut(uCurAccountID, uNxtAccountID, uCurrencyID) : QUALITY_ONE; + + // For bPrvAccount + const STAmount saPrvOwed = bPrvAccount && uIndex // Previous account is owed. + ? lesActive.rippleOwed(uCurAccountID, uPrvAccountID, uCurrencyID) + : STAmount(uCurrencyID, uCurAccountID); + + const STAmount saPrvLimit = bPrvAccount && uIndex // Previous account may owe. + ? lesActive.rippleLimit(uCurAccountID, uPrvAccountID, uCurrencyID) + : STAmount(uCurrencyID, uCurAccountID); + + const STAmount saNxtOwed = bNxtAccount && uIndex != uLast // Next account is owed. + ? lesActive.rippleOwed(uCurAccountID, uNxtAccountID, uCurrencyID) + : STAmount(uCurrencyID, uCurAccountID); + + Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev> uIndex=%d/%d uPrvAccountID=%s uCurAccountID=%s uNxtAccountID=%s uCurrencyID=%s uQualityIn=%d uQualityOut=%d saPrvOwed=%s saPrvLimit=%s") + % uIndex + % uLast + % NewcoinAddress::createHumanAccountID(uPrvAccountID) + % NewcoinAddress::createHumanAccountID(uCurAccountID) + % NewcoinAddress::createHumanAccountID(uNxtAccountID) + % STAmount::createHumanCurrency(uCurrencyID) + % uQualityIn + % uQualityOut + % saPrvOwed.getFullText() + % saPrvLimit.getFullText()); + + // Previous can redeem the owed IOUs it holds. + const STAmount saPrvRedeemReq = saPrvOwed.isPositive() ? saPrvOwed : STAmount(uCurrencyID, 0); + STAmount& saPrvRedeemAct = pnPrv.saRevRedeem; + + // Previous can issue up to limit minus whatever portion of limit already used (not including redeemable amount). + const STAmount saPrvIssueReq = saPrvOwed.isNegative() ? saPrvLimit+saPrvOwed : saPrvLimit; + STAmount& saPrvIssueAct = pnPrv.saRevIssue; + + // For !bPrvAccount + const STAmount saPrvDeliverReq = STAmount::saFromSigned(uCurrencyID, uCurAccountID, -1); // Unlimited. + STAmount& saPrvDeliverAct = pnPrv.saRevDeliver; + + // For bNxtAccount + const STAmount& saCurRedeemReq = pnCur.saRevRedeem; + STAmount saCurRedeemAct(saCurRedeemReq.getCurrency(), saCurRedeemReq.getIssuer()); + + const STAmount& saCurIssueReq = pnCur.saRevIssue; + STAmount saCurIssueAct(saCurIssueReq.getCurrency(), saCurIssueReq.getIssuer()); // Track progress. + + // For !bNxtAccount + const STAmount& saCurDeliverReq = pnCur.saRevDeliver; + STAmount saCurDeliverAct(saCurDeliverReq.getCurrency(), saCurDeliverReq.getIssuer()); + + Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: saPrvRedeemReq=%s saPrvIssueReq=%s saCurRedeemReq=%s saNxtOwed=%s") + % saPrvRedeemReq.getFullText() + % saPrvIssueReq.getFullText() + % saCurRedeemReq.getFullText() + % saNxtOwed.getFullText()); + + Log(lsINFO) << pspCur->getJson(); + + assert(!saCurRedeemReq || (-saNxtOwed) >= saCurRedeemReq); // Current redeem req can't be more than IOUs on hand. + assert(!saCurIssueReq || !saNxtOwed.isPositive() || saNxtOwed == saCurRedeemReq); // If issue req, then redeem req must consume all owed. + + if (bPrvAccount && bNxtAccount) + { + if (!uIndex) + { + // ^ --> ACCOUNT --> account|offer + // Nothing to do, there is no previous to adjust. + nothing(); + } + else if (uIndex == uLast) + { + // account --> ACCOUNT --> $ + // Overall deliverable. + const STAmount& saCurWantedReq = bPrvAccount + ? std::min(pspCur->saOutReq, saPrvLimit+saPrvOwed) // If previous is an account, limit. + : pspCur->saOutReq; // Previous is an offer, no limit: redeem own IOUs. + STAmount saCurWantedAct(saCurWantedReq.getCurrency(), saCurWantedReq.getIssuer()); + + Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: account --> ACCOUNT --> $ : saCurWantedReq=%s") + % saCurWantedReq.getFullText()); + + // Calculate redeem + if (saPrvRedeemReq) // Previous has IOUs to redeem. + { + // Redeem at 1:1 + Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: Redeem at 1:1")); + + saCurWantedAct = std::min(saPrvRedeemReq, saCurWantedReq); + saPrvRedeemAct = saCurWantedAct; + + uRateMax = STAmount::uRateOne; + } + + // Calculate issuing. + if (saCurWantedReq != saCurWantedAct // Need more. + && saPrvIssueReq) // Will accept IOUs from prevous. + { + // Rate: quality in : 1.0 + Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: Rate: quality in : 1.0")); + + // If we previously redeemed and this has a poorer rate, this won't be included the current increment. + calcNodeRipple(uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurWantedReq, saPrvIssueAct, saCurWantedAct, uRateMax); + } + + if (!saCurWantedAct) + { + // Must have processed something. + terResult = tepPATH_DRY; + } + } + else + { + // ^|account --> ACCOUNT --> account + + // redeem (part 1) -> redeem + if (saCurRedeemReq // Next wants IOUs redeemed. + && saPrvRedeemReq) // Previous has IOUs to redeem. + { + // Rate : 1.0 : quality out + Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: Rate : 1.0 : quality out")); + + calcNodeRipple(QUALITY_ONE, uQualityOut, saPrvRedeemReq, saCurRedeemReq, saPrvRedeemAct, saCurRedeemAct, uRateMax); + } + + // issue (part 1) -> redeem + if (saCurRedeemReq != saCurRedeemAct // Next wants more IOUs redeemed. + && saPrvRedeemAct == saPrvRedeemReq) // Previous has no IOUs to redeem remaining. + { + // Rate: quality in : quality out + Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: Rate: quality in : quality out")); + + calcNodeRipple(uQualityIn, uQualityOut, saPrvIssueReq, saCurRedeemReq, saPrvIssueAct, saCurRedeemAct, uRateMax); + } + + // redeem (part 2) -> issue. + if (saCurIssueReq // Next wants IOUs issued. + && saCurRedeemAct == saCurRedeemReq // Can only issue if completed redeeming. + && saPrvRedeemAct != saPrvRedeemReq) // Did not complete redeeming previous IOUs. + { + // Rate : 1.0 : transfer_rate + Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: Rate : 1.0 : transfer_rate")); + + calcNodeRipple(QUALITY_ONE, lesActive.rippleTransferRate(uCurAccountID), saPrvRedeemReq, saCurIssueReq, saPrvRedeemAct, saCurIssueAct, uRateMax); + } + + // issue (part 2) -> issue + if (saCurIssueReq != saCurIssueAct // Need wants more IOUs issued. + && saCurRedeemAct == saCurRedeemReq // Can only issue if completed redeeming. + && saPrvRedeemReq == saPrvRedeemAct) // Previously redeemed all owed IOUs. + { + // Rate: quality in : 1.0 + Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: Rate: quality in : 1.0")); + + calcNodeRipple(uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurIssueReq, saPrvIssueAct, saCurIssueAct, uRateMax); + } + + if (!saCurRedeemAct && !saCurIssueAct) + { + // Must want something. + terResult = tepPATH_DRY; + } + + Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: ^|account --> ACCOUNT --> account : saCurRedeemReq=%s saCurIssueReq=%s saPrvOwed=%s saCurRedeemAct=%s saCurIssueAct=%s") + % saCurRedeemReq.getFullText() + % saCurIssueReq.getFullText() + % saPrvOwed.getFullText() + % saCurRedeemAct.getFullText() + % saCurIssueAct.getFullText()); + } + } + else if (bPrvAccount && !bNxtAccount) + { + // account --> ACCOUNT --> offer + // Note: deliver is always issue as ACCOUNT is the issuer for the offer input. + Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: account --> ACCOUNT --> offer")); + + // redeem -> deliver/issue. + if (saPrvOwed.isPositive() // Previous has IOUs to redeem. + && saCurDeliverReq) // Need some issued. + { + // Rate : 1.0 : transfer_rate + calcNodeRipple(QUALITY_ONE, lesActive.rippleTransferRate(uCurAccountID), saPrvRedeemReq, saCurDeliverReq, saPrvRedeemAct, saCurDeliverAct, uRateMax); + } + + // issue -> deliver/issue + if (saPrvRedeemReq == saPrvRedeemAct // Previously redeemed all owed. + && saCurDeliverReq != saCurDeliverAct) // Still need some issued. + { + // Rate: quality in : 1.0 + calcNodeRipple(uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurDeliverReq, saPrvIssueAct, saCurDeliverAct, uRateMax); + } + + if (!saCurDeliverAct) + { + // Must want something. + terResult = tepPATH_DRY; + } + + Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: saCurDeliverReq=%s saCurDeliverAct=%s saPrvOwed=%s") + % saCurDeliverReq.getFullText() + % saCurDeliverAct.getFullText() + % saPrvOwed.getFullText()); + } + else if (!bPrvAccount && bNxtAccount) + { + if (uIndex == uLast) + { + // offer --> ACCOUNT --> $ + const STAmount& saCurWantedReq = bPrvAccount + ? std::min(pspCur->saOutReq, saPrvLimit+saPrvOwed) // If previous is an account, limit. + : pspCur->saOutReq; // Previous is an offer, no limit: redeem own IOUs. + STAmount saCurWantedAct(saCurWantedReq.getCurrency(), saCurWantedReq.getIssuer()); + + Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: offer --> ACCOUNT --> $ : saCurWantedReq=%s") + % saCurWantedReq.getFullText()); + + // Rate: quality in : 1.0 + calcNodeRipple(uQualityIn, QUALITY_ONE, saPrvDeliverReq, saCurWantedReq, saPrvDeliverAct, saCurWantedAct, uRateMax); + + if (!saCurWantedAct) + { + // Must have processed something. + terResult = tepPATH_DRY; + } + } + else + { + // offer --> ACCOUNT --> account + // Note: offer is always delivering(redeeming) as account is issuer. + Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: offer --> ACCOUNT --> account")); + + // deliver -> redeem + if (saCurRedeemReq) // Next wants us to redeem. + { + // Rate : 1.0 : quality out + calcNodeRipple(QUALITY_ONE, uQualityOut, saPrvDeliverReq, saCurRedeemReq, saPrvDeliverAct, saCurRedeemAct, uRateMax); + } + + // deliver -> issue. + if (saCurRedeemReq == saCurRedeemAct // Can only issue if previously redeemed all. + && saCurIssueReq) // Need some issued. + { + // Rate : 1.0 : transfer_rate + calcNodeRipple(QUALITY_ONE, lesActive.rippleTransferRate(uCurAccountID), saPrvDeliverReq, saCurIssueReq, saPrvDeliverAct, saCurIssueAct, uRateMax); + } + + Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: saCurRedeemReq=%s saCurIssueAct=%s saCurIssueReq=%s saPrvDeliverAct=%s") + % saCurRedeemReq.getFullText() + % saCurRedeemAct.getFullText() + % saCurIssueReq.getFullText() + % saPrvDeliverAct.getFullText()); + + if (!saPrvDeliverAct) + { + // Must want something. + terResult = tepPATH_DRY; + } + } + } + else + { + // offer --> ACCOUNT --> offer + // deliver/redeem -> deliver/issue. + Log(lsINFO) << boost::str(boost::format("calcNodeAccountRev: offer --> ACCOUNT --> offer")); + + // Rate : 1.0 : transfer_rate + calcNodeRipple(QUALITY_ONE, lesActive.rippleTransferRate(uCurAccountID), saPrvDeliverReq, saCurDeliverReq, saPrvDeliverAct, saCurDeliverAct, uRateMax); + + if (!saCurDeliverAct) + { + // Must want something. + terResult = tepPATH_DRY; + } + } + + return terResult; +} + +// Perfrom balance adjustments between previous and current node. +// - The previous node: specifies what to push through to current. +// - All of previous output is consumed. +// Then, compute 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. +TER RippleCalc::calcNodeAccountFwd( + const unsigned int uIndex, // 0 <= uIndex <= uLast + const PathState::pointer& pspCur, + const bool bMultiQuality) +{ + TER terResult = tesSUCCESS; + const unsigned int uLast = pspCur->vpnNodes.size() - 1; + + uint64 uRateMax = 0; + + PaymentNode& pnPrv = pspCur->vpnNodes[uIndex ? uIndex-1 : 0]; + PaymentNode& pnCur = pspCur->vpnNodes[uIndex]; + PaymentNode& pnNxt = pspCur->vpnNodes[uIndex == uLast ? uLast : uIndex+1]; + + const bool bPrvAccount = isSetBit(pnPrv.uFlags, STPathElement::typeAccount); + const bool bNxtAccount = isSetBit(pnNxt.uFlags, STPathElement::typeAccount); + + const uint160& uCurAccountID = pnCur.uAccountID; + const uint160& uPrvAccountID = bPrvAccount ? pnPrv.uAccountID : uCurAccountID; + const uint160& uNxtAccountID = bNxtAccount ? pnNxt.uAccountID : uCurAccountID; // Offers are always issue. + + const uint160& uCurrencyID = pnCur.uCurrencyID; + + uint32 uQualityIn = uIndex ? lesActive.rippleQualityIn(uCurAccountID, uPrvAccountID, uCurrencyID) : QUALITY_ONE; + uint32 uQualityOut = uIndex == uLast ? lesActive.rippleQualityOut(uCurAccountID, uNxtAccountID, uCurrencyID) : QUALITY_ONE; + + // For bNxtAccount + const STAmount& saPrvRedeemReq = pnPrv.saFwdRedeem; + STAmount saPrvRedeemAct(saPrvRedeemReq.getCurrency(), saPrvRedeemReq.getIssuer()); + + const STAmount& saPrvIssueReq = pnPrv.saFwdIssue; + STAmount saPrvIssueAct(saPrvIssueReq.getCurrency(), saPrvIssueReq.getIssuer()); + + // For !bPrvAccount + const STAmount& saPrvDeliverReq = pnPrv.saRevDeliver; + STAmount saPrvDeliverAct(saPrvDeliverReq.getCurrency(), saPrvDeliverReq.getIssuer()); + + // For bNxtAccount + const STAmount& saCurRedeemReq = pnCur.saRevRedeem; + STAmount& saCurRedeemAct = pnCur.saFwdRedeem; + + const STAmount& saCurIssueReq = pnCur.saRevIssue; + STAmount& saCurIssueAct = pnCur.saFwdIssue; + + // For !bNxtAccount + const STAmount& saCurDeliverReq = pnCur.saRevDeliver; + STAmount& saCurDeliverAct = pnCur.saFwdDeliver; + + Log(lsINFO) << boost::str(boost::format("calcNodeAccountFwd> uIndex=%d/%d saPrvRedeemReq=%s saPrvIssueReq=%s saPrvDeliverReq=%s saCurRedeemReq=%s saCurIssueReq=%s saCurDeliverReq=%s") + % uIndex + % uLast + % saPrvRedeemReq.getFullText() + % saPrvIssueReq.getFullText() + % saPrvDeliverReq.getFullText() + % saCurRedeemReq.getFullText() + % saCurIssueReq.getFullText() + % saCurDeliverReq.getFullText()); + + // Ripple through account. + + if (bPrvAccount && bNxtAccount) + { + if (!uIndex) + { + // ^ --> ACCOUNT --> account + + // First node, calculate amount to send. + // XXX Use stamp/ripple balance + PaymentNode& pnCur = pspCur->vpnNodes[uIndex]; + + const STAmount& saCurRedeemReq = pnCur.saRevRedeem; + STAmount& saCurRedeemAct = pnCur.saFwdRedeem; + const STAmount& saCurIssueReq = pnCur.saRevIssue; + STAmount& saCurIssueAct = pnCur.saFwdIssue; + + const STAmount& saCurSendMaxReq = pspCur->saInReq; // Negative for no limit, doing a calculation. + STAmount& saCurSendMaxAct = pspCur->saInAct; // Report to user how much this sends. + + if (saCurRedeemReq) + { + // Redeem requested. + saCurRedeemAct = saCurRedeemReq.isNegative() + ? saCurRedeemReq + : std::min(saCurRedeemReq, saCurSendMaxReq); + } + else + { + saCurRedeemAct = STAmount(saCurRedeemReq); + } + saCurSendMaxAct = saCurRedeemAct; + + if (saCurIssueReq && (saCurSendMaxReq.isNegative() || saCurSendMaxReq != saCurRedeemAct)) + { + // Issue requested and not over budget. + saCurIssueAct = saCurSendMaxReq.isNegative() + ? saCurIssueReq + : std::min(saCurSendMaxReq-saCurRedeemAct, saCurIssueReq); + } + else + { + saCurIssueAct = STAmount(saCurIssueReq); + } + saCurSendMaxAct += saCurIssueAct; + + Log(lsINFO) << boost::str(boost::format("calcNodeAccountFwd: ^ --> ACCOUNT --> account : saCurSendMaxReq=%s saCurRedeemAct=%s saCurIssueReq=%s saCurIssueAct=%s") + % saCurSendMaxReq.getFullText() + % saCurRedeemAct.getFullText() + % saCurIssueReq.getFullText() + % saCurIssueAct.getFullText()); + } + else if (uIndex == uLast) + { + // account --> ACCOUNT --> $ + Log(lsINFO) << boost::str(boost::format("calcNodeAccountFwd: account --> ACCOUNT --> $ : uPrvAccountID=%s uCurAccountID=%s saPrvRedeemReq=%s saPrvIssueReq=%s") + % NewcoinAddress::createHumanAccountID(uPrvAccountID) + % NewcoinAddress::createHumanAccountID(uCurAccountID) + % saPrvRedeemReq.getFullText() + % saPrvIssueReq.getFullText()); + + // Last node. Accept all funds. Calculate amount actually to credit. + + STAmount& saCurReceive = pspCur->saOutAct; + + STAmount saIssueCrd = uQualityIn >= QUALITY_ONE + ? saPrvIssueReq // No fee. + : STAmount::multiply(saPrvIssueReq, uQualityIn, uCurrencyID, saPrvIssueReq.getIssuer()); // Fee. + + // Amount to credit. + saCurReceive = saPrvRedeemReq+saIssueCrd; + + // Actually receive. + lesActive.rippleCredit(uPrvAccountID, uCurAccountID, saPrvRedeemReq+saPrvIssueReq, false); + } + else + { + // account --> ACCOUNT --> account + Log(lsINFO) << boost::str(boost::format("calcNodeAccountFwd: account --> ACCOUNT --> account")); + + // Previous redeem part 1: redeem -> redeem + if (saPrvRedeemReq != saPrvRedeemAct) // Previous wants to redeem. To next must be ok. + { + // Rate : 1.0 : quality out + calcNodeRipple(QUALITY_ONE, uQualityOut, saPrvRedeemReq, saCurRedeemReq, saPrvRedeemAct, saCurRedeemAct, uRateMax); + } + + // Previous issue part 1: issue -> redeem + if (saPrvIssueReq != saPrvIssueAct // Previous wants to issue. + && saCurRedeemReq != saCurRedeemAct) // Current has more to redeem to next. + { + // Rate: quality in : quality out + calcNodeRipple(uQualityIn, uQualityOut, saPrvIssueReq, saCurRedeemReq, saPrvIssueAct, saCurRedeemAct, uRateMax); + } + + // Previous redeem part 2: redeem -> issue. + // wants to redeem and current would and can issue. + // If redeeming cur to next is done, this implies can issue. + if (saPrvRedeemReq != saPrvRedeemAct // Previous still wants to redeem. + && saCurRedeemReq == saCurRedeemAct // Current has no more to redeem to next. + && saCurIssueReq) + { + // Rate : 1.0 : transfer_rate + calcNodeRipple(QUALITY_ONE, lesActive.rippleTransferRate(uCurAccountID), saPrvRedeemReq, saCurIssueReq, saPrvRedeemAct, saCurIssueAct, uRateMax); + } + + // Previous issue part 2 : issue -> issue + if (saPrvIssueReq != saPrvIssueAct) // Previous wants to issue. To next must be ok. + { + // Rate: quality in : 1.0 + calcNodeRipple(uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurIssueReq, saPrvIssueAct, saCurIssueAct, uRateMax); + } + + // Adjust prv --> cur balance : take all inbound + // XXX Currency must be in amount. + lesActive.rippleCredit(uPrvAccountID, uCurAccountID, saPrvRedeemReq + saPrvIssueReq, false); + } + } + else if (bPrvAccount && !bNxtAccount) + { + // account --> ACCOUNT --> offer + Log(lsINFO) << boost::str(boost::format("calcNodeAccountFwd: account --> ACCOUNT --> offer")); + + // 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. + { + // Rate : 1.0 : transfer_rate + calcNodeRipple(QUALITY_ONE, lesActive.rippleTransferRate(uCurAccountID), saPrvRedeemReq, saCurDeliverReq, saPrvRedeemAct, saCurDeliverAct, uRateMax); + } + + // issue -> issue + 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 + // XXX Currency must be in amount. + lesActive.rippleCredit(uPrvAccountID, uCurAccountID, saPrvRedeemReq + saPrvIssueReq, false); + } + else if (!bPrvAccount && bNxtAccount) + { + if (uIndex == uLast) + { + // offer --> ACCOUNT --> $ + Log(lsINFO) << boost::str(boost::format("calcNodeAccountFwd: offer --> ACCOUNT --> $")); + + STAmount& saCurReceive = pspCur->saOutAct; + + // Amount to credit. + saCurReceive = saPrvDeliverAct; + + // No income balance adjustments necessary. The paying side inside the offer paid to this account. + } + else + { + // offer --> ACCOUNT --> account + Log(lsINFO) << boost::str(boost::format("calcNodeAccountFwd: offer --> ACCOUNT --> account")); + + // deliver -> redeem + if (saPrvDeliverReq) // Previous wants to deliver. + { + // Rate : 1.0 : quality out + calcNodeRipple(QUALITY_ONE, uQualityOut, saPrvDeliverReq, saCurRedeemReq, saPrvDeliverAct, saCurRedeemAct, uRateMax); + } + + // deliver -> issue + // Wants to redeem and current would and can issue. + if (saPrvDeliverReq != saPrvDeliverAct // Previous still wants to deliver. + && saCurRedeemReq == saCurRedeemAct // Current has more to redeem to next. + && saCurIssueReq) // Current wants issue. + { + // Rate : 1.0 : transfer_rate + calcNodeRipple(QUALITY_ONE, lesActive.rippleTransferRate(uCurAccountID), saPrvDeliverReq, saCurIssueReq, saPrvDeliverAct, saCurIssueAct, uRateMax); + } + + // No income balance adjustments necessary. The paying side inside the offer paid and the next link will receive. + } + } + else + { + // offer --> ACCOUNT --> offer + // deliver/redeem -> deliver/issue. + Log(lsINFO) << boost::str(boost::format("calcNodeAccountFwd: offer --> ACCOUNT --> offer")); + + if (saPrvDeliverReq // Previous wants to deliver + && saCurIssueReq) // Current wants issue. + { + // Rate : 1.0 : transfer_rate + calcNodeRipple(QUALITY_ONE, lesActive.rippleTransferRate(uCurAccountID), saPrvDeliverReq, saCurDeliverReq, saPrvDeliverAct, saCurDeliverAct, uRateMax); + } + + // No income balance adjustments necessary. The paying side inside the offer paid and the next link will receive. + } + + return terResult; +} + +// Return true, iff lhs has less priority than rhs. +bool PathState::lessPriority(const PathState::pointer& lhs, const PathState::pointer& rhs) +{ + if (lhs->uQuality != rhs->uQuality) + return lhs->uQuality > rhs->uQuality; // Bigger is worse. + + // Best quanity is second rank. + if (lhs->saOutAct != rhs->saOutAct) + return lhs->saOutAct < rhs->saOutAct; // Smaller is worse. + + // Path index is third rank. + return lhs->mIndex > rhs->mIndex; // Bigger is worse. +} + +// Make sure the path delivers to uAccountID: uCurrencyID from uIssuerID. +// +// Rules: +// - Currencies must be converted via an offer. +// - A node names it's output. +// - A ripple nodes output issuer must be the node's account or the next node's account. +// - Offers can only go directly to another offer if the currency and issuer are an exact match. +TER PathState::pushImply( + const uint160& uAccountID, // --> Delivering to this account. + const uint160& uCurrencyID, // --> Delivering this currency. + const uint160& uIssuerID) // --> Delivering this issuer. +{ + const PaymentNode& pnPrv = vpnNodes.back(); + TER terResult = tesSUCCESS; + + Log(lsINFO) << "pushImply> " + << NewcoinAddress::createHumanAccountID(uAccountID) + << " " << STAmount::createHumanCurrency(uCurrencyID) + << " " << NewcoinAddress::createHumanAccountID(uIssuerID); + + if (pnPrv.uCurrencyID != uCurrencyID) + { + // Currency is different, need to convert via an offer. + + terResult = pushNode( + STPathElement::typeCurrency // Offer. + | STPathElement::typeIssuer, + ACCOUNT_ONE, // Placeholder for offers. + uCurrencyID, // The offer's output is what is now wanted. + uIssuerID); + + } + + // For ripple, non-stamps, ensure the issuer is on at least one side of the transaction. + if (tesSUCCESS == terResult + && !!uCurrencyID // Not stamps. + && (pnPrv.uAccountID != uIssuerID // Previous is not issuing own IOUs. + && uAccountID != uIssuerID)) // Current is not receiving own IOUs. + { + // Need to ripple through uIssuerID's account. + + terResult = pushNode( + STPathElement::typeAccount, + uIssuerID, // Intermediate account is the needed issuer. + uCurrencyID, + uIssuerID); + } + + Log(lsINFO) << "pushImply< " << terResult; + + return terResult; +} + +// Append a node and insert before it any implied nodes. +// <-- terResult: tesSUCCESS, temBAD_PATH, terNO_LINE +TER PathState::pushNode( + const int iType, + const uint160& uAccountID, + const uint160& uCurrencyID, + const uint160& uIssuerID) +{ + Log(lsINFO) << "pushNode> " + << NewcoinAddress::createHumanAccountID(uAccountID) + << " " << STAmount::createHumanCurrency(uCurrencyID) + << "/" << NewcoinAddress::createHumanAccountID(uIssuerID); + PaymentNode pnCur; + const bool bFirst = vpnNodes.empty(); + const PaymentNode& pnPrv = bFirst ? PaymentNode() : vpnNodes.back(); + // true, iff node is a ripple account. false, iff node is an offer node. + const bool bAccount = isSetBit(iType, STPathElement::typeAccount); + // true, iff currency supplied. + // Currency is specified for the output of the current node. + const bool bCurrency = isSetBit(iType, STPathElement::typeCurrency); + // Issuer is specified for the output of the current node. + const bool bIssuer = isSetBit(iType, STPathElement::typeIssuer); + TER terResult = tesSUCCESS; + + pnCur.uFlags = iType; + + if (iType & ~STPathElement::typeValidBits) + { + Log(lsINFO) << "pushNode: bad bits."; + + terResult = temBAD_PATH; + } + else if (bAccount) + { + // Account link + + pnCur.uAccountID = uAccountID; + pnCur.uCurrencyID = bCurrency ? uCurrencyID : pnPrv.uCurrencyID; + pnCur.uIssuerID = bIssuer ? uIssuerID : uAccountID; + pnCur.saRevRedeem = STAmount(uCurrencyID, uAccountID); + pnCur.saRevIssue = STAmount(uCurrencyID, uAccountID); + + if (!bFirst) + { + // Add required intermediate nodes to deliver to current account. + terResult = pushImply( + pnCur.uAccountID, // Current account. + pnCur.uCurrencyID, // Wanted currency. + !!pnCur.uCurrencyID ? uAccountID : ACCOUNT_XNS); // Account as issuer. + } + + if (tesSUCCESS == terResult && !vpnNodes.empty()) + { + const PaymentNode& pnBck = vpnNodes.back(); + bool bBckAccount = isSetBit(pnBck.uFlags, STPathElement::typeAccount); + + if (bBckAccount) + { + SLE::pointer sleRippleState = mLedger->getSLE(Ledger::getRippleStateIndex(pnBck.uAccountID, pnCur.uAccountID, pnPrv.uCurrencyID)); + + if (!sleRippleState) + { + Log(lsINFO) << "pushNode: No credit line between " + << NewcoinAddress::createHumanAccountID(pnBck.uAccountID) + << " and " + << NewcoinAddress::createHumanAccountID(pnCur.uAccountID) + << " for " + << STAmount::createHumanCurrency(pnPrv.uCurrencyID) + << "." ; + + Log(lsINFO) << getJson(); + + terResult = terNO_LINE; + } + else + { + Log(lsINFO) << "pushNode: Credit line found between " + << NewcoinAddress::createHumanAccountID(pnBck.uAccountID) + << " and " + << NewcoinAddress::createHumanAccountID(pnCur.uAccountID) + << " for " + << STAmount::createHumanCurrency(pnPrv.uCurrencyID) + << "." ; + } + } + } + + if (tesSUCCESS == terResult) + vpnNodes.push_back(pnCur); + } + else + { + // Offer link + // Offers bridge a change in currency & issuer or just a change in issuer. + pnCur.uCurrencyID = bCurrency ? uCurrencyID : pnPrv.uCurrencyID; + pnCur.uIssuerID = bIssuer ? uIssuerID : pnCur.uAccountID; + pnCur.saRateMax = saZero; + + if (!!pnPrv.uAccountID) + { + // Previous is an account. + + // Insert intermediary issuer account if needed. + terResult = pushImply( + !!pnPrv.uCurrencyID + ? ACCOUNT_ONE // Rippling, but offer's don't have an account. + : ACCOUNT_XNS, + pnPrv.uCurrencyID, + pnPrv.uIssuerID); + } + + if (tesSUCCESS == terResult) + { + vpnNodes.push_back(pnCur); + } + } + Log(lsINFO) << "pushNode< " << terResult; + + return terResult; +} + +PathState::PathState( + const int iIndex, + const LedgerEntrySet& lesSource, + const STPath& spSourcePath, + const uint160& uReceiverID, + const uint160& uSenderID, + const STAmount& saSend, + const STAmount& saSendMax + ) + : mLedger(lesSource.getLedgerRef()), mIndex(iIndex), uQuality(0) +{ + const uint160 uInCurrencyID = saSendMax.getCurrency(); + const uint160 uOutCurrencyID = saSend.getCurrency(); + const uint160 uInIssuerID = !!uInCurrencyID ? saSendMax.getIssuer() : ACCOUNT_XNS; + const uint160 uOutIssuerID = !!uOutCurrencyID ? saSend.getIssuer() : ACCOUNT_XNS; + + lesEntries = lesSource.duplicate(); + + saInReq = saSendMax; + saOutReq = saSend; + + // Push sending node. + terStatus = pushNode( + STPathElement::typeAccount + | STPathElement::typeCurrency + | STPathElement::typeIssuer, + uSenderID, + uInCurrencyID, + uInIssuerID); + + BOOST_FOREACH(const STPathElement& speElement, spSourcePath) + { + if (tesSUCCESS == terStatus) + terStatus = pushNode(speElement.getNodeType(), speElement.getAccountID(), speElement.getCurrency(), speElement.getIssuerID()); + } + + if (tesSUCCESS == terStatus) + { + // Create receiver node. + + terStatus = pushImply(uReceiverID, uOutCurrencyID, uOutIssuerID); + if (tesSUCCESS == terStatus) + { + terStatus = pushNode( + STPathElement::typeAccount // Last node is always an account. + | STPathElement::typeCurrency + | STPathElement::typeIssuer, + uReceiverID, // Receive to output + uOutCurrencyID, // Desired currency + uOutIssuerID); + } + } + + if (tesSUCCESS == terStatus) + { + // Look for first mention of source in nodes and detect loops. + // Note: The output is not allowed to be a source. + + const unsigned int uNodes = vpnNodes.size(); + + for (unsigned int uIndex = 0; tesSUCCESS == terStatus && uIndex != uNodes; ++uIndex) + { + const PaymentNode& pnCur = vpnNodes[uIndex]; + + if (!!pnCur.uAccountID) + { + // Source is a ripple line + nothing(); + } + else if (!umForward.insert(std::make_pair(boost::make_tuple(pnCur.uAccountID, pnCur.uCurrencyID, pnCur.uIssuerID), uIndex)).second) + { + // Failed to insert. Have a loop. + Log(lsINFO) << boost::str(boost::format("PathState: loop detected: %s") + % getJson()); + + terStatus = temBAD_PATH_LOOP; + } + } + } + + Log(lsINFO) << boost::str(boost::format("PathState: in=%s/%s out=%s/%s %s") + % STAmount::createHumanCurrency(uInCurrencyID) + % NewcoinAddress::createHumanAccountID(uInIssuerID) + % STAmount::createHumanCurrency(uOutCurrencyID) + % NewcoinAddress::createHumanAccountID(uOutIssuerID) + % getJson()); +} + +Json::Value PathState::getJson() const +{ + Json::Value jvPathState(Json::objectValue); + Json::Value jvNodes(Json::arrayValue); + + BOOST_FOREACH(const PaymentNode& pnNode, vpnNodes) + { + Json::Value jvNode(Json::objectValue); + + Json::Value jvFlags(Json::arrayValue); + + if (pnNode.uFlags & STPathElement::typeAccount) + jvFlags.append("account"); + + jvNode["flags"] = jvFlags; + + if (pnNode.uFlags & STPathElement::typeAccount) + jvNode["account"] = NewcoinAddress::createHumanAccountID(pnNode.uAccountID); + + if (!!pnNode.uCurrencyID) + jvNode["currency"] = STAmount::createHumanCurrency(pnNode.uCurrencyID); + + if (!!pnNode.uIssuerID) + jvNode["issuer"] = NewcoinAddress::createHumanAccountID(pnNode.uIssuerID); + + // if (pnNode.saRevRedeem) + jvNode["rev_redeem"] = pnNode.saRevRedeem.getFullText(); + + // if (pnNode.saRevIssue) + jvNode["rev_issue"] = pnNode.saRevIssue.getFullText(); + + // if (pnNode.saRevDeliver) + jvNode["rev_deliver"] = pnNode.saRevDeliver.getFullText(); + + // if (pnNode.saFwdRedeem) + jvNode["fwd_redeem"] = pnNode.saFwdRedeem.getFullText(); + + // if (pnNode.saFwdIssue) + jvNode["fwd_issue"] = pnNode.saFwdIssue.getFullText(); + + // if (pnNode.saFwdDeliver) + jvNode["fwd_deliver"] = pnNode.saFwdDeliver.getFullText(); + + jvNodes.append(jvNode); + } + + jvPathState["status"] = terStatus; + jvPathState["index"] = mIndex; + jvPathState["nodes"] = jvNodes; + + if (saInReq) + jvPathState["in_req"] = saInReq.getJson(0); + + if (saInAct) + jvPathState["in_act"] = saInAct.getJson(0); + + if (saOutReq) + jvPathState["out_req"] = saOutReq.getJson(0); + + if (saOutAct) + jvPathState["out_act"] = saOutAct.getJson(0); + + if (uQuality) + jvPathState["uQuality"] = Json::Value::UInt(uQuality); + + return jvPathState; +} + +TER RippleCalc::calcNodeFwd(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality) +{ + const PaymentNode& pnCur = pspCur->vpnNodes[uIndex]; + const bool bCurAccount = isSetBit(pnCur.uFlags, STPathElement::typeAccount); + + Log(lsINFO) << boost::str(boost::format("calcNodeFwd> uIndex=%d") % uIndex); + + TER terResult = bCurAccount + ? calcNodeAccountFwd(uIndex, pspCur, bMultiQuality) + : calcNodeOfferFwd(uIndex, pspCur, bMultiQuality); + + if (tesSUCCESS == terResult && uIndex + 1 != pspCur->vpnNodes.size()) + { + terResult = calcNodeFwd(uIndex+1, pspCur, bMultiQuality); + } + + Log(lsINFO) << boost::str(boost::format("calcNodeFwd< uIndex=%d terResult=%d") % uIndex % terResult); + + return terResult; +} + +// Calculate a node and its previous nodes. +// From the destination work in reverse towards the source calculating how much must be asked for. +// Then work forward, figuring out how much can actually be delivered. +// <-- terResult: tesSUCCESS or tepPATH_DRY +// <-> pnNodes: +// --> [end]saWanted.mAmount +// --> [all]saWanted.mCurrency +// --> [all]saAccount +// <-> [0]saWanted.mAmount : --> limit, <-- actual +TER RippleCalc::calcNodeRev(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality) +{ + PaymentNode& pnCur = pspCur->vpnNodes[uIndex]; + const bool bCurAccount = isSetBit(pnCur.uFlags, STPathElement::typeAccount); + TER terResult; + + // Do current node reverse. + const uint160& uCurIssuerID = pnCur.uIssuerID; + STAmount& saTransferRate = pnCur.saTransferRate; + + saTransferRate = STAmount::saFromRate(lesActive.rippleTransferRate(uCurIssuerID)); + + Log(lsINFO) << boost::str(boost::format("calcNodeRev> uIndex=%d uIssuerID=%s saTransferRate=%s") + % uIndex + % NewcoinAddress::createHumanAccountID(uCurIssuerID) + % saTransferRate.getFullText()); + + terResult = bCurAccount + ? calcNodeAccountRev(uIndex, pspCur, bMultiQuality) + : calcNodeOfferRev(uIndex, pspCur, bMultiQuality); + + // Do previous. + if (tesSUCCESS != terResult) + { + // Error, don't continue. + nothing(); + } + else if (uIndex) + { + // Continue in reverse. + + terResult = calcNodeRev(uIndex-1, pspCur, bMultiQuality); + } + + Log(lsINFO) << boost::str(boost::format("calcNodeRev< uIndex=%d terResult=%s/%d") % uIndex % transToken(terResult) % terResult); + + return terResult; +} + +// Calculate the next increment of a path. +// The increment is what can satisfy a portion or all of the requested output at the best quality. +// <-- pspCur->uQuality +void RippleCalc::pathNext(const PathState::pointer& pspCur, const int iPaths, const LedgerEntrySet& lesCheckpoint, LedgerEntrySet& lesCurrent) +{ + // The next state is what is available in preference order. + // This is calculated when referenced accounts changed. + const bool bMultiQuality = iPaths == 1; + const unsigned int uLast = pspCur->vpnNodes.size() - 1; + + Log(lsINFO) << "Path In: " << pspCur->getJson(); + + assert(pspCur->vpnNodes.size() >= 2); + + pspCur->vUnfundedBecame.clear(); + pspCur->umReverse.clear(); + + lesCurrent = lesCheckpoint; // Restore from checkpoint. + lesCurrent.bumpSeq(); // Begin ledger varance. + + pspCur->terStatus = calcNodeRev(uLast, pspCur, bMultiQuality); + + Log(lsINFO) << "Path after reverse: " << pspCur->getJson(); + + if (tesSUCCESS == pspCur->terStatus) + { + // Do forward. + lesCurrent = lesCheckpoint; // Restore from checkpoint. + lesCurrent.bumpSeq(); // Begin ledger varance. + + pspCur->terStatus = calcNodeFwd(0, pspCur, bMultiQuality); + + pspCur->uQuality = tesSUCCESS == pspCur->terStatus + ? STAmount::getRate(pspCur->saOutAct, pspCur->saInAct) // Calculate relative quality. + : 0; // Mark path as inactive. + + Log(lsINFO) << "Path after forward: " << pspCur->getJson(); + } +} + +// XXX Stand alone calculation not implemented, does not calculate required input. +TER RippleCalc::rippleCalc( + LedgerEntrySet& lesActive, // <-> --> = Fee applied to src balance. + STAmount& saMaxAmountAct, // <-- The computed input amount. + STAmount& saDstAmountAct, // <-- The computed output amount. + const STAmount& saMaxAmountReq, // --> -1 = no limit. + const STAmount& saDstAmountReq, + const uint160& uDstAccountID, + const uint160& uSrcAccountID, + const STPathSet& spsPaths, + const bool bPartialPayment, + const bool bLimitQuality, + const bool bNoRippleDirect + ) +{ + RippleCalc rc(lesActive); + + TER terResult = temUNCERTAIN; + + // YYY Might do basic checks on src and dst validity as per doPayment. + + if (bNoRippleDirect && spsPaths.isEmpty()) + { + Log(lsINFO) << "doPayment: Invalid transaction: No paths and direct ripple not allowed."; + + return temRIPPLE_EMPTY; + } + + // Incrementally search paths. + std::vector vpsPaths; + + if (!bNoRippleDirect) + { + // Direct path. + // XXX Might also make a stamp bridge by default. + Log(lsINFO) << "doPayment: Build direct:"; + + PathState::pointer pspDirect = PathState::createPathState( + vpsPaths.size(), + lesActive, + STPath(), + uDstAccountID, + uSrcAccountID, + saDstAmountReq, + saMaxAmountReq); + + if (pspDirect) + { + // Return if malformed. + if (pspDirect->terStatus >= temMALFORMED && pspDirect->terStatus < tefFAILURE) + return pspDirect->terStatus; + + if (tesSUCCESS == pspDirect->terStatus) + { + // Had a success. + terResult = tesSUCCESS; + + vpsPaths.push_back(pspDirect); + } + } + } + + Log(lsINFO) << "doPayment: Paths in set: " << spsPaths.getPathCount(); + + BOOST_FOREACH(const STPath& spPath, spsPaths) + { + Log(lsINFO) << "doPayment: Build path:"; + + PathState::pointer pspExpanded = PathState::createPathState( + vpsPaths.size(), + lesActive, + spPath, + uDstAccountID, + uSrcAccountID, + saDstAmountReq, + saMaxAmountReq); + + if (pspExpanded) + { + // Return if malformed. + if (pspExpanded->terStatus >= temMALFORMED && pspExpanded->terStatus < tefFAILURE) + return pspExpanded->terStatus; + + if (tesSUCCESS == pspExpanded->terStatus) + { + // Had a success. + terResult = tesSUCCESS; + } + + vpsPaths.push_back(pspExpanded); + } + } + + if (vpsPaths.empty()) + { + return tefEXCEPTION; + } + else if (tesSUCCESS != terResult) + { + // No path successes. + + return vpsPaths[0]->terStatus; + } + else + { + terResult = temUNCERTAIN; + } + + STAmount saPaid; + STAmount saWanted; + const LedgerEntrySet lesBase = lesActive; // Checkpoint with just fees paid. + const uint64 uQualityLimit = bLimitQuality ? STAmount::getRate(saDstAmountReq, saMaxAmountReq) : 0; + // When processing, don't want to complicate directory walking with deletion. + std::vector vuUnfundedBecame; // Offers that became unfunded. + + while (temUNCERTAIN == terResult) + { + PathState::pointer pspBest; + const LedgerEntrySet lesCheckpoint = lesActive; + + // Find the best path. + BOOST_FOREACH(PathState::pointer& pspCur, vpsPaths) + { + rc.pathNext(pspCur, vpsPaths.size(), lesCheckpoint, lesActive); // Compute increment. + + if ((!bLimitQuality || pspCur->uQuality <= uQualityLimit) // Quality is not limted or increment has allowed quality. + || !pspBest // Best is not yet set. + || (pspCur->uQuality && PathState::lessPriority(pspBest, pspCur))) // Current is better than set. + { + lesActive.swapWith(pspCur->lesEntries); // For the path, save ledger state. + pspBest = pspCur; + } + } + + if (pspBest) + { + // Apply best path. + + // Record best pass' offers that became unfunded for deletion on success. + vuUnfundedBecame.insert(vuUnfundedBecame.end(), pspBest->vUnfundedBecame.begin(), pspBest->vUnfundedBecame.end()); + + // Record best pass' LedgerEntrySet to build off of and potentially return. + lesActive.swapWith(pspBest->lesEntries); + + // Figure out if done. + if (temUNCERTAIN == terResult && saPaid == saWanted) + { + terResult = tesSUCCESS; + } + else + { + // Prepare for next pass. + + // Merge best pass' umReverse. + rc.mumSource.insert(pspBest->umReverse.begin(), pspBest->umReverse.end()); + } + } + // Not done and ran out of paths. + else if (!bPartialPayment) + { + // Partial payment not allowed. + terResult = tepPATH_PARTIAL; + lesActive = lesBase; // Revert to just fees charged. + } + // Partial payment ok. + else if (!saPaid) + { + // No payment at all. + terResult = tepPATH_DRY; + lesActive = lesBase; // Revert to just fees charged. + } + else + { + terResult = tesSUCCESS; + } + } + + if (tesSUCCESS == terResult) + { + // Delete became unfunded offers. + BOOST_FOREACH(const uint256& uOfferIndex, vuUnfundedBecame) + { + if (tesSUCCESS == terResult) + terResult = lesActive.offerDelete(uOfferIndex); + } + } + + // Delete found unfunded offers. + BOOST_FOREACH(const uint256& uOfferIndex, rc.musUnfundedFound) + { + if (tesSUCCESS == terResult) + terResult = lesActive.offerDelete(uOfferIndex); + } + + return terResult; +} + +#if 0 +// XXX Need to adjust for fees. +// Find offers to satisfy pnDst. +// - Does not adjust any balances as there is at least a forward pass to come. +// --> pnDst.saWanted: currency and amount wanted +// --> pnSrc.saIOURedeem.mCurrency: use this before saIOUIssue, limit to use. +// --> pnSrc.saIOUIssue.mCurrency: use this after saIOURedeem, limit to use. +// <-- pnDst.saReceive +// <-- pnDst.saIOUForgive +// <-- pnDst.saIOUAccept +// <-- terResult : tesSUCCESS = no error and if !bAllowPartial complelely satisfied wanted. +// <-> usOffersDeleteAlways: +// <-> usOffersDeleteOnSuccess: +TER calcOfferFill(PaymentNode& pnSrc, PaymentNode& pnDst, bool bAllowPartial) +{ + TER terResult; + + if (pnDst.saWanted.isNative()) + { + // Transfer stamps. + + STAmount saSrcFunds = pnSrc.saAccount->accountHolds(pnSrc.saAccount, uint160(0), uint160(0)); + + if (saSrcFunds && (bAllowPartial || saSrcFunds > pnDst.saWanted)) + { + pnSrc.saSend = min(saSrcFunds, pnDst.saWanted); + pnDst.saReceive = pnSrc.saSend; + } + else + { + terResult = terINSUF_PATH; + } + } + else + { + // Ripple funds. + + // Redeem to limit. + terResult = calcOfferFill( + accountHolds(pnSrc.saAccount, pnDst.saWanted.getCurrency(), pnDst.saWanted.getIssuer()), + pnSrc.saIOURedeem, + pnDst.saIOUForgive, + bAllowPartial); + + if (tesSUCCESS == terResult) + { + // Issue to wanted. + terResult = calcOfferFill( + pnDst.saWanted, // As much as wanted is available, limited by credit limit. + pnSrc.saIOUIssue, + pnDst.saIOUAccept, + bAllowPartial); + } + + if (tesSUCCESS == terResult && !bAllowPartial) + { + STAmount saTotal = pnDst.saIOUForgive + pnSrc.saIOUAccept; + + if (saTotal != saWanted) + terResult = terINSUF_PATH; + } + } + + return terResult; +} +#endif + +#if 0 +// Get the next offer limited by funding. +// - Stop when becomes unfunded. +void TransactionEngine::calcOfferBridgeNext( + const uint256& uBookRoot, // --> Which order book to look in. + const uint256& uBookEnd, // --> Limit of how far to look. + uint256& uBookDirIndex, // <-> Current directory. <-- 0 = no offer available. + uint64& uBookDirNode, // <-> Which node. 0 = first. + unsigned int& uBookDirEntry, // <-> Entry in node. 0 = first. + STAmount& saOfferIn, // <-- How much to pay in, fee inclusive, to get saOfferOut out. + STAmount& saOfferOut // <-- How much offer pays out. + ) +{ + saOfferIn = 0; // XXX currency & issuer + saOfferOut = 0; // XXX currency & issuer + + bool bDone = false; + + while (!bDone) + { + uint256 uOfferIndex; + + // Get uOfferIndex. + mNodes.dirNext(uBookRoot, uBookEnd, uBookDirIndex, uBookDirNode, uBookDirEntry, uOfferIndex); + + SLE::pointer sleOffer = entryCache(ltOFFER, uOfferIndex); + + 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. + Log(lsINFO) << "calcOfferFirst: encountered expired offer"; + } + else + { + STAmount saOfferFunds = accountFunds(uOfferOwnerID, saOfferPays); + // Outbound fees are paid by offer owner. + // XXX Calculate outbound fee rate. + + if (saOfferPays.isNative()) + { + // No additional fees for stamps. + + nothing(); + } + else if (saOfferPays.getIssuer() == uOfferOwnerID) + { + // Offerer is issue own IOUs. + // No fees at this exact point, XXX receiving node may charge a fee. + // XXX Make sure has a credit line with receiver, limit by credit line. + + nothing(); + // XXX Broken - could be issuing or redeeming or both. + } + else + { + // Offer must be redeeming IOUs. + + // No additional + // XXX Broken + } + + if (!saOfferFunds.isPositive()) + { + // Offer is unfunded. + Log(lsINFO) << "calcOfferFirst: offer unfunded: delete"; + } + else if (saOfferFunds >= saOfferPays) + { + // Offer fully funded. + + // Account transfering funds in to offer always pays inbound fees. + + saOfferIn = saOfferGets; // XXX Add in fees? + + saOfferOut = saOfferPays; + + bDone = true; + } + else + { + // Offer partially funded. + + // saOfferIn/saOfferFunds = saOfferGets/saOfferPays + // XXX Round such that all saOffer funds are exhausted. + saOfferIn = (saOfferFunds*saOfferGets)/saOfferPays; // XXX Add in fees? + saOfferOut = saOfferFunds; + + bDone = true; + } + } + + if (!bDone) + { + // musUnfundedFound.insert(uOfferIndex); + } + } + while (bNext); +} +#endif + +#if 0 +// If either currency is not stamps, then also calculates vs stamp bridge. +// --> saWanted: Limit of how much is wanted out. +// <-- saPay: How much to pay into the offer. +// <-- saGot: How much to the offer pays out. Never more than saWanted. +// Given two value's enforce a minimum: +// - reverse: prv is maximum to pay in (including fee) - cur is what is wanted: generally, minimizing prv +// - forward: prv is actual amount to pay in (including fee) - cur is what is wanted: generally, minimizing cur +// Value in is may be rippled or credited from limbo. Value out is put in limbo. +// If next is an offer, the amount needed is in cur reedem. +// XXX What about account mentioned multiple times via offers? +void TransactionEngine::calcNodeOffer( + bool bForward, + bool bMultiQuality, // True, if this is the only active path: we can do multiple qualities in this pass. + const uint160& uPrvAccountID, // If 0, then funds from previous offer's limbo + const uint160& uPrvCurrencyID, + const uint160& uPrvIssuerID, + const uint160& uCurCurrencyID, + const uint160& uCurIssuerID, + + const STAmount& uPrvRedeemReq, // --> In limit. + STAmount& uPrvRedeemAct, // <-> In limit achived. + const STAmount& uCurRedeemReq, // --> Out limit. Driver when uCurIssuerID == uNxtIssuerID (offer would redeem to next) + STAmount& uCurRedeemAct, // <-> Out limit achived. + + const STAmount& uCurIssueReq, // --> In limit. + STAmount& uCurIssueAct, // <-> In limit achived. + const STAmount& uCurIssueReq, // --> Out limit. Driver when uCurIssueReq != uNxtIssuerID (offer would effectively issue or transfer to next) + STAmount& uCurIssueAct, // <-> Out limit achived. + + STAmount& saPay, + STAmount& saGot + ) const +{ + TER terResult = temUNKNOWN; + + // Direct: not bridging via XNS + bool bDirectNext = true; // True, if need to load. + uint256 uDirectQuality; + uint256 uDirectTip = Ledger::getBookBase(uGetsCurrency, uGetsIssuerID, uPaysCurrency, uPaysIssuerID); + uint256 uDirectEnd = Ledger::getQualityNext(uDirectTip); + + // Bridging: bridging via XNS + bool bBridge = true; // True, if bridging active. False, missing an offer. + uint256 uBridgeQuality; + STAmount saBridgeIn; // Amount available. + STAmount saBridgeOut; + + bool bInNext = true; // True, if need to load. + STAmount saInIn; // Amount available. Consumed in loop. Limited by offer funding. + STAmount saInOut; + uint256 uInTip; // Current entry. + uint256 uInEnd; + unsigned int uInEntry; + + bool bOutNext = true; + STAmount saOutIn; + STAmount saOutOut; + uint256 uOutTip; + uint256 uOutEnd; + unsigned int uOutEntry; + + saPay.zero(); + saPay.setCurrency(uPrvCurrencyID); + saPay.setIssuer(uPrvIssuerID); + + saNeed = saWanted; + + if (!uCurCurrencyID && !uPrvCurrencyID) + { + // Bridging: Neither currency is XNS. + uInTip = Ledger::getBookBase(uPrvCurrencyID, uPrvIssuerID, CURRENCY_XNS, ACCOUNT_XNS); + uInEnd = Ledger::getQualityNext(uInTip); + uOutTip = Ledger::getBookBase(CURRENCY_XNS, ACCOUNT_XNS, uCurCurrencyID, uCurIssuerID); + uOutEnd = Ledger::getQualityNext(uInTip); + } + + // Find our head offer. + + bool bRedeeming = false; + bool bIssuing = false; + + // The price varies as we change between issuing and transfering, so unless bMultiQuality, we must stick with a mode once it + // is determined. + + if (bBridge && (bInNext || bOutNext)) + { + // Bridging and need to calculate next bridge rate. + // A bridge can consist of multiple offers. As offer's are consumed, the effective rate changes. + + if (bInNext) + { +// sleInDir = entryCache(ltDIR_NODE, mLedger->getNextLedgerIndex(uInIndex, uInEnd)); + // Get the next funded offer. + offerBridgeNext(uInIndex, uInEnd, uInEntry, saInIn, saInOut); // Get offer limited by funding. + bInNext = false; + } + + if (bOutNext) + { +// sleOutDir = entryCache(ltDIR_NODE, mLedger->getNextLedgerIndex(uOutIndex, uOutEnd)); + offerNext(uOutIndex, uOutEnd, uOutEntry, saOutIn, saOutOut); + bOutNext = false; + } + + if (!uInIndex || !uOutIndex) + { + bBridge = false; // No more offers to bridge. + } + else + { + // Have bridge in and out entries. + // Calculate bridge rate. Out offer pay ripple fee. In offer fee is added to in cost. + + saBridgeOut.zero(); + + if (saInOut < saOutIn) + { + // Limit by in. + + // XXX Need to include fees in saBridgeIn. + saBridgeIn = saInIn; // All of in + // Limit bridge out: saInOut/saBridgeOut = saOutIn/saOutOut + // Round such that we would take all of in offer, otherwise would have leftovers. + saBridgeOut = (saInOut * saOutOut) / saOutIn; + } + else if (saInOut > saOutIn) + { + // Limit by out, if at all. + + // XXX Need to include fees in saBridgeIn. + // Limit bridge in:saInIn/saInOuts = aBridgeIn/saOutIn + // Round such that would take all of out offer. + saBridgeIn = (saInIn * saOutIn) / saInOuts; + saBridgeOut = saOutOut; // All of out. + } + else + { + // Entries match, + + // XXX Need to include fees in saBridgeIn. + saBridgeIn = saInIn; // All of in + saBridgeOut = saOutOut; // All of out. + } + + uBridgeQuality = STAmount::getRate(saBridgeIn, saBridgeOut); // Inclusive of fees. + } + } + + if (bBridge) + { + bUseBridge = !uDirectTip || (uBridgeQuality < uDirectQuality) + } + else if (!!uDirectTip) + { + bUseBridge = false + } + else + { + // No more offers. Declare success, even if none returned. + saGot = saWanted-saNeed; + terResult = tesSUCCESS; + } + + if (tesSUCCESS != terResult) + { + STAmount& saAvailIn = bUseBridge ? saBridgeIn : saDirectIn; + STAmount& saAvailOut = bUseBridge ? saBridgeOut : saDirectOut; + + if (saAvailOut > saNeed) + { + // Consume part of offer. Done. + + saNeed = 0; + saPay += (saNeed*saAvailIn)/saAvailOut; // Round up, prefer to pay more. + } + else + { + // Consume entire offer. + + saNeed -= saAvailOut; + saPay += saAvailIn; + + if (bUseBridge) + { + // Consume bridge out. + if (saOutOut == saAvailOut) + { + // Consume all. + saOutOut = 0; + saOutIn = 0; + bOutNext = true; + } + else + { + // Consume portion of bridge out, must be consuming all of bridge in. + // saOutIn/saOutOut = saSpent/saAvailOut + // Round? + saOutIn -= (saOutIn*saAvailOut)/saOutOut; + saOutOut -= saAvailOut; + } + + // Consume bridge in. + if (saOutIn == saAvailIn) + { + // Consume all. + saInOut = 0; + saInIn = 0; + bInNext = true; + } + else + { + // Consume portion of bridge in, must be consuming all of bridge out. + // saInIn/saInOut = saAvailIn/saPay + // Round? + saInOut -= (saInOut*saAvailIn)/saInIn; + saInIn -= saAvailIn; + } + } + else + { + bDirectNext = true; + } + } + } +} +#endif + +// vim:ts=4 diff --git a/src/RippleCalc.h b/src/RippleCalc.h new file mode 100644 index 0000000000..673393b5d5 --- /dev/null +++ b/src/RippleCalc.h @@ -0,0 +1,190 @@ +#ifndef __RIPPLE_CALC__ +#define __RIPPLE_CALC__ + +#include + +#include "LedgerEntrySet.h" + +class PaymentNode { +protected: + friend class RippleCalc; + friend class PathState; + + uint16 uFlags; // --> From path. + + uint160 uAccountID; // --> Accounts: Recieving/sending account. + uint160 uCurrencyID; // --> Accounts: Receive and send, Offers: send. + // --- For offer's next has currency out. + uint160 uIssuerID; // --> Currency's issuer + + STAmount saTransferRate; // Transfer rate for uIssuerID. + + // Computed by Reverse. + STAmount saRevRedeem; // <-- Amount to redeem to next. + STAmount saRevIssue; // <-- Amount to issue to next limited by credit and outstanding IOUs. + // Issue isn't used by offers. + STAmount saRevDeliver; // <-- Amount to deliver to next regardless of fee. + + // Computed by forward. + STAmount saFwdRedeem; // <-- Amount node will redeem to next. + STAmount saFwdIssue; // <-- Amount node will issue to next. + // Issue isn't used by offers. + STAmount saFwdDeliver; // <-- Amount to deliver to next regardless of fee. + + // For offers: + + STAmount saRateMax; // XXX Should rate be sticky for forward too? + + // Directory + uint256 uDirectTip; // Current directory. + uint256 uDirectEnd; // Next order book. + bool bDirectAdvance; // Need to advance directory. + SLE::pointer sleDirectDir; + STAmount saOfrRate; // For correct ratio. + + // Node + bool bEntryAdvance; // Need to advance entry. + unsigned int uEntry; + uint256 uOfferIndex; + SLE::pointer sleOffer; + uint160 uOfrOwnerID; + bool bFundsDirty; // Need to refresh saOfferFunds, saTakerPays, & saTakerGets. + STAmount saOfferFunds; + STAmount saTakerPays; + STAmount saTakerGets; +}; + +// account id, currency id, issuer id :: node +typedef boost::tuple aciSource; +typedef boost::unordered_map curIssuerNode; // Map of currency, issuer to node index. +typedef boost::unordered_map::const_iterator curIssuerNodeConstIterator; + +extern std::size_t hash_value(const aciSource& asValue); + +// Holds a path state under incremental application. +class PathState +{ +protected: + const Ledger::pointer& mLedger; + + TER pushNode(const int iType, const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID); + TER pushImply(const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID); + +public: + typedef boost::shared_ptr pointer; + + TER terStatus; + std::vector vpnNodes; + + // When processing, don't want to complicate directory walking with deletion. + std::vector vUnfundedBecame; // Offers that became unfunded or were completely consumed. + + // First time scanning foward, as part of path contruction, a funding source was mentioned for accounts. Source may only be + // used there. + curIssuerNode umForward; // Map of currency, issuer to node index. + + // First time working in reverse a funding source was used. + // Source may only be used there if not mentioned by an account. + curIssuerNode umReverse; // Map of currency, issuer to node index. + + LedgerEntrySet lesEntries; + + int mIndex; + uint64 uQuality; // 0 = none. + STAmount saInReq; // Max amount to spend by sender + STAmount saInAct; // Amount spent by sender (calc output) + STAmount saOutReq; // Amount to send (calc input) + STAmount saOutAct; // Amount actually sent (calc output). + + PathState( + const int iIndex, + const LedgerEntrySet& lesSource, + const STPath& spSourcePath, + const uint160& uReceiverID, + const uint160& uSenderID, + const STAmount& saSend, + const STAmount& saSendMax + ); + + Json::Value getJson() const; + + static PathState::pointer createPathState( + const int iIndex, + const LedgerEntrySet& lesSource, + const STPath& spSourcePath, + const uint160& uReceiverID, + const uint160& uSenderID, + const STAmount& saSend, + const STAmount& saSendMax + ) + { + return boost::make_shared(iIndex, lesSource, spSourcePath, uReceiverID, uSenderID, saSend, saSendMax); + } + + static bool lessPriority(const PathState::pointer& lhs, const PathState::pointer& rhs); +}; + +class RippleCalc +{ +protected: + LedgerEntrySet& lesActive; + +public: + // First time working in reverse a funding source was mentioned. Source may only be used there. + curIssuerNode mumSource; // Map of currency, issuer to node index. + + // If the transaction fails to meet some constraint, still need to delete unfunded offers. + boost::unordered_set musUnfundedFound; // Offers that were found unfunded. + + PathState::pointer pathCreate(const STPath& spPath); + void pathNext(const PathState::pointer& pspCur, const int iPaths, const LedgerEntrySet& lesCheckpoint, LedgerEntrySet& lesCurrent); + TER calcNode(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality); + TER calcNodeRev(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality); + TER calcNodeFwd(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality); + TER calcNodeOfferRev(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality); + TER calcNodeOfferFwd(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality); + TER calcNodeAccountRev(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality); + TER calcNodeAccountFwd(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality); + TER calcNodeAdvance(const unsigned int uIndex, const PathState::pointer& pspCur, const bool bMultiQuality, const bool bReverse); + TER calcNodeDeliverRev( + const unsigned int uIndex, + const PathState::pointer& pspCur, + const bool bMultiQuality, + const uint160& uOutAccountID, + const STAmount& saOutReq, + STAmount& saOutAct); + + TER calcNodeDeliverFwd( + const unsigned int uIndex, + const PathState::pointer& pspCur, + const bool bMultiQuality, + const uint160& uInAccountID, + const STAmount& saInFunds, + const STAmount& saInReq, + STAmount& saInAct, + STAmount& saInFees); + + void calcNodeRipple(const uint32 uQualityIn, const uint32 uQualityOut, + const STAmount& saPrvReq, const STAmount& saCurReq, + STAmount& saPrvAct, STAmount& saCurAct, + uint64& uRateMax); + + RippleCalc(LedgerEntrySet& lesNodes) : lesActive(lesNodes) { ; } + + static TER rippleCalc( + LedgerEntrySet& lesActive, + STAmount& saMaxAmountAct, + STAmount& saDstAmountAct, + const STAmount& saDstAmountReq, + const STAmount& saMaxAmountReq, + const uint160& uDstAccountID, + const uint160& uSrcAccountID, + const STPathSet& spsPaths, + const bool bPartialPayment, + const bool bLimitQuality, + const bool bNoRippleDirect + ); +}; + +#endif +// vim:ts=4 diff --git a/src/RippleLines.cpp b/src/RippleLines.cpp new file mode 100644 index 0000000000..bae60414b1 --- /dev/null +++ b/src/RippleLines.cpp @@ -0,0 +1,54 @@ +#include "RippleLines.h" +#include "Application.h" +#include "Log.h" +#include + +RippleLines::RippleLines(const uint160& accountID, Ledger::pointer ledger) +{ + fillLines(accountID,ledger); +} + +RippleLines::RippleLines(const uint160& accountID ) +{ + fillLines(accountID,theApp->getMasterLedger().getCurrentLedger()); +} + +void RippleLines::fillLines(const uint160& accountID, Ledger::pointer ledger) +{ + uint256 rootIndex = Ledger::getOwnerDirIndex(accountID); + uint256 currentIndex = rootIndex; + + LedgerStateParms lspNode = lepNONE; + + while (1) + { + SLE::pointer rippleDir=ledger->getDirNode(lspNode, currentIndex); + if (!rippleDir) return; + + STVector256 svOwnerNodes = rippleDir->getFieldV256(sfIndexes); + BOOST_FOREACH(uint256& uNode, svOwnerNodes.peekValue()) + { + SLE::pointer sleCur = ledger->getSLE(uNode); + + if (ltRIPPLE_STATE == sleCur->getType()) + { + RippleState::pointer rsLine = ledger->accessRippleState(uNode); + if (rsLine) + { + rsLine->setViewAccount(accountID); + mLines.push_back(rsLine); + } + else + { + Log(lsWARNING) << "doRippleLinesGet: Bad index: " << uNode.ToString(); + } + } + } + + uint64 uNodeNext = rippleDir->getFieldU64(sfIndexNext); + if (!uNodeNext) return; + + currentIndex = Ledger::getDirNodeIndex(rootIndex, uNodeNext); + } +} +// vim:ts=4 diff --git a/src/RippleLines.h b/src/RippleLines.h new file mode 100644 index 0000000000..13ea7f5dc4 --- /dev/null +++ b/src/RippleLines.h @@ -0,0 +1,19 @@ +#include "Ledger.h" +#include "RippleState.h" + +/* +This pulls all the ripple lines of a given account out of the ledger. +It provides a vector so you to easily iterate through them +*/ + +class RippleLines +{ + std::vector mLines; + void fillLines(const uint160& accountID, Ledger::pointer ledger); +public: + + RippleLines(const uint160& accountID, Ledger::pointer ledger); + RippleLines(const uint160& accountID ); // looks in the current ledger + + std::vector& getLines(){ return(mLines); } +}; \ No newline at end of file diff --git a/src/RippleState.cpp b/src/RippleState.cpp index 0b319faab7..673d563b3d 100644 --- a/src/RippleState.cpp +++ b/src/RippleState.cpp @@ -7,20 +7,26 @@ RippleState::RippleState(SerializedLedgerEntry::pointer ledgerEntry) : { if (!mLedgerEntry || mLedgerEntry->getType() != ltRIPPLE_STATE) return; - mLowID = mLedgerEntry->getIValueFieldAccount(sfLowID); - mHighID = mLedgerEntry->getIValueFieldAccount(sfHighID); + mLowLimit = mLedgerEntry->getFieldAmount(sfLowLimit); + mHighLimit = mLedgerEntry->getFieldAmount(sfHighLimit); - mLowLimit = mLedgerEntry->getIValueFieldAmount(sfLowLimit); - mHighLimit = mLedgerEntry->getIValueFieldAmount(sfHighLimit); + mLowID = NewcoinAddress::createAccountID(mLowLimit.getIssuer()); + mHighID = NewcoinAddress::createAccountID(mHighLimit.getIssuer()); - mBalance = mLedgerEntry->getIValueFieldAmount(sfBalance); + mLowQualityIn = mLedgerEntry->getFieldU32(sfLowQualityIn); + mLowQualityOut = mLedgerEntry->getFieldU32(sfLowQualityOut); + + mHighQualityIn = mLedgerEntry->getFieldU32(sfHighQualityIn); + mHighQualityOut = mLedgerEntry->getFieldU32(sfHighQualityOut); + + mBalance = mLedgerEntry->getFieldAmount(sfBalance); mValid = true; } -void RippleState::setViewAccount(const NewcoinAddress& naView) +void RippleState::setViewAccount(const uint160& accountID) { - bool bViewLowestNew = mLowID.getAccountID() == naView.getAccountID(); + bool bViewLowestNew = mLowID.getAccountID() == accountID; if (bViewLowestNew != mViewLowest) { diff --git a/src/RippleState.h b/src/RippleState.h index 8efbae945f..f17daf31ca 100644 --- a/src/RippleState.h +++ b/src/RippleState.h @@ -24,6 +24,11 @@ private: STAmount mLowLimit; STAmount mHighLimit; + uint64 mLowQualityIn; + uint64 mLowQualityOut; + uint64 mHighQualityIn; + uint64 mHighQualityOut; + STAmount mBalance; bool mValid; @@ -32,7 +37,7 @@ private: public: RippleState(SerializedLedgerEntry::pointer ledgerEntry); // For accounts in a ledger - void setViewAccount(const NewcoinAddress& naView); + void setViewAccount(const uint160& accountID); const NewcoinAddress getAccountID() const { return mViewLowest ? mLowID : mHighID; } const NewcoinAddress getAccountIDPeer() const { return mViewLowest ? mHighID : mLowID; } @@ -42,6 +47,9 @@ public: STAmount getLimit() const { return mViewLowest ? mLowLimit : mHighLimit; } STAmount getLimitPeer() const { return mViewLowest ? mHighLimit : mLowLimit; } + uint32 getQualityIn() const { return((uint32) (mViewLowest ? mLowQualityIn : mHighQualityIn)); } + uint32 getQualityOut() const { return((uint32) (mViewLowest ? mLowQualityOut : mHighQualityOut)); } + SerializedLedgerEntry::pointer getSLE() { return mLedgerEntry; } const SerializedLedgerEntry& peekSLE() const { return *mLedgerEntry; } SerializedLedgerEntry& peekSLE() { return *mLedgerEntry; } diff --git a/src/SHAMap.cpp b/src/SHAMap.cpp index 738d1858ef..652326f177 100644 --- a/src/SHAMap.cpp +++ b/src/SHAMap.cpp @@ -39,14 +39,14 @@ std::size_t hash_value(const uint160& u) } -SHAMap::SHAMap(uint32 seq) : mSeq(seq), mState(Modifying) +SHAMap::SHAMap(uint32 seq) : mSeq(seq), mState(smsModifying) { root = boost::make_shared(mSeq, SHAMapNode(0, uint256())); root->makeInner(); mTNByID[*root] = root; } -SHAMap::SHAMap(const uint256& hash) : mSeq(0), mState(Synching) +SHAMap::SHAMap(const uint256& hash) : mSeq(0), mState(smsSynching) { // FIXME: Need to acquire root node root = boost::make_shared(mSeq, SHAMapNode(0, uint256())); root->makeInner(); @@ -62,7 +62,7 @@ SHAMap::pointer SHAMap::snapShot(bool isMutable) newMap.mTNByID = mTNByID; newMap.root = root; if (!isMutable) - newMap.mState = Immutable; + newMap.mState = smsImmutable; return ret; } @@ -105,7 +105,7 @@ void SHAMap::dirtyUp(std::stack& stack, const uint256& { // walk the tree up from through the inner nodes to the root // update linking hashes and add nodes to dirty list - assert((mState != Synching) && (mState != Immutable)); + assert((mState != smsSynching) && (mState != smsImmutable)); while (!stack.empty()) { @@ -125,7 +125,7 @@ void SHAMap::dirtyUp(std::stack& stack, const uint256& return; } #ifdef ST_DEBUG - std::cerr << "dirtyUp sets branch " << branch << " to " << prevHash.GetHex() << std::endl; + std::cerr << "dirtyUp sets branch " << branch << " to " << prevHash << std::endl; #endif prevHash = node->getNodeHash(); assert(prevHash.isNonZero()); @@ -187,9 +187,9 @@ SHAMapTreeNode::pointer SHAMap::getNode(const SHAMapNode& id, const uint256& has if (node->getNodeHash() != hash) { std::cerr << "Attempt to get node, hash not in tree" << std::endl; - std::cerr << "ID: " << id.getString() << std::endl; - std::cerr << "TgtHash " << hash.GetHex() << std::endl; - std::cerr << "NodHash " << node->getNodeHash().GetHex() << std::endl; + std::cerr << "ID: " << id << std::endl; + std::cerr << "TgtHash " << hash << std::endl; + std::cerr << "NodHash " << node->getNodeHash() << std::endl; dump(); throw std::runtime_error("invalid node"); } @@ -238,42 +238,44 @@ SHAMapItem::SHAMapItem(const uint256& tag, const Serializer& data) : mTag(tag), mData(data.peekData()) { ; } -SHAMapItem::pointer SHAMap::firstBelow(SHAMapTreeNode* node) +SHAMapTreeNode* SHAMap::firstBelow(SHAMapTreeNode* node) { // Return the first item below this node #ifdef ST_DEBUG - std::cerr << "firstBelow(" << node->getString() << ")" << std::endl; + std::cerr << "firstBelow(" << *node << ")" << std::endl; #endif do { // Walk down the tree - if (node->hasItem()) return node->peekItem(); + if (node->hasItem()) return node; bool foundNode = false; for (int i = 0; i < 16; ++i) if (!node->isEmptyBranch(i)) { #ifdef ST_DEBUG - std::cerr << " FB: node " << node->getString() << std::endl; + std::cerr << " FB: node " << *node << std::endl; std::cerr << " has non-empty branch " << i << " : " << - node->getChildNodeID(i).getString() << ", " << node->getChildHash(i).GetHex() << std::endl; + node->getChildNodeID(i) << ", " << node->getChildHash(i) << std::endl; #endif node = getNodePointer(node->getChildNodeID(i), node->getChildHash(i)); foundNode = true; break; } - if (!foundNode) return SHAMapItem::pointer(); - } while (1); + if (!foundNode) + return NULL; + } while (true); } -SHAMapItem::pointer SHAMap::lastBelow(SHAMapTreeNode* node) +SHAMapTreeNode* SHAMap::lastBelow(SHAMapTreeNode* node) { #ifdef DEBUG - std::cerr << "lastBelow(" << node->getString() << ")" << std::endl; + std::cerr << "lastBelow(" << *node << ")" << std::endl; #endif do { // Walk down the tree - if (node->hasItem()) return node->peekItem(); + if (node->hasItem()) + return node; bool foundNode = false; for (int i = 15; i >= 0; ++i) @@ -283,8 +285,9 @@ SHAMapItem::pointer SHAMap::lastBelow(SHAMapTreeNode* node) foundNode = true; break; } - if (!foundNode) return SHAMapItem::pointer(); - } while (1); + if (!foundNode) + return NULL; + } while (true); } SHAMapItem::pointer SHAMap::onlyBelow(SHAMapTreeNode* node) @@ -306,7 +309,7 @@ SHAMapItem::pointer SHAMap::onlyBelow(SHAMapTreeNode* node) if (!found) { - std::cerr << node->getString() << std::endl; + std::cerr << *node << std::endl; assert(false); return SHAMapItem::pointer(); } @@ -345,38 +348,69 @@ void SHAMap::eraseChildren(SHAMapTreeNode::pointer node) SHAMapItem::pointer SHAMap::peekFirstItem() { boost::recursive_mutex::scoped_lock sl(mLock); - return firstBelow(root.get()); + SHAMapTreeNode *node = firstBelow(root.get()); + if (!node) + return SHAMapItem::pointer(); + return node->peekItem(); +} + +SHAMapItem::pointer SHAMap::peekFirstItem(SHAMapTreeNode::TNType& type) +{ + boost::recursive_mutex::scoped_lock sl(mLock); + SHAMapTreeNode *node = firstBelow(root.get()); + if (!node) + return SHAMapItem::pointer(); + type = node->getType(); + return node->peekItem(); } SHAMapItem::pointer SHAMap::peekLastItem() { boost::recursive_mutex::scoped_lock sl(mLock); - return lastBelow(root.get()); + SHAMapTreeNode *node = lastBelow(root.get()); + if (!node) + return SHAMapItem::pointer(); + return node->peekItem(); } SHAMapItem::pointer SHAMap::peekNextItem(const uint256& id) +{ + SHAMapTreeNode::TNType type; + return peekNextItem(id, type); +} + + +SHAMapItem::pointer SHAMap::peekNextItem(const uint256& id, SHAMapTreeNode::TNType& type) { // Get a pointer to the next item in the tree after a given item - item must be in tree boost::recursive_mutex::scoped_lock sl(mLock); std::stack stack = getStack(id, true, false); - while(!stack.empty()) + while (!stack.empty()) { SHAMapTreeNode::pointer node = stack.top(); stack.pop(); - if(node->isLeaf()) + if (node->isLeaf()) { - if(node->peekItem()->getTag()>id) - return node->peekItem(); - } - else for(int i = node->selectBranch(id) + 1; i < 16; ++i) - if(!node->isEmptyBranch(i)) + if (node->peekItem()->getTag() > id) { - node = getNode(node->getChildNodeID(i), node->getChildHash(i), false); - SHAMapItem::pointer item = firstBelow(node.get()); - if (!item) throw std::runtime_error("missing node"); - return item; + type = node->getType(); + return node->peekItem(); } + } + else + for (int i = node->selectBranch(id) + 1; i < 16; ++i) + if (!node->isEmptyBranch(i)) + { + SHAMapTreeNode *firstNode = getNodePointer(node->getChildNodeID(i), node->getChildHash(i)); + if (!firstNode) + throw std::runtime_error("missing node"); + firstNode = firstBelow(firstNode); + if (!firstNode) + throw std::runtime_error("missing node"); + type = firstNode->getType(); + return firstNode->peekItem(); + } } // must be last item return SHAMapItem::pointer(); @@ -392,18 +426,19 @@ SHAMapItem::pointer SHAMap::peekPrevItem(const uint256& id) SHAMapTreeNode::pointer node = stack.top(); stack.pop(); - if(node->isLeaf()) + if (node->isLeaf()) { - if(node->peekItem()->getTag()peekItem()->getTag() < id) return node->peekItem(); } - else for(int i = node->selectBranch(id) - 1; i >= 0; --i) - if(!node->isEmptyBranch(i)) + else for (int i = node->selectBranch(id) - 1; i >= 0; --i) + if (!node->isEmptyBranch(i)) { node = getNode(node->getChildNodeID(i), node->getChildHash(i), false); - SHAMapItem::pointer item = firstBelow(node.get()); - if (!item) throw std::runtime_error("missing node"); - return item; + SHAMapTreeNode* item = firstBelow(node.get()); + if (!item) + throw std::runtime_error("missing node"); + return item->peekItem(); } } // must be last item @@ -414,7 +449,18 @@ SHAMapItem::pointer SHAMap::peekItem(const uint256& id) { boost::recursive_mutex::scoped_lock sl(mLock); SHAMapTreeNode* leaf = walkToPointer(id); - if (!leaf) return SHAMapItem::pointer(); + if (!leaf) + return SHAMapItem::pointer(); + return leaf->peekItem(); +} + +SHAMapItem::pointer SHAMap::peekItem(const uint256& id, SHAMapTreeNode::TNType& type) +{ + boost::recursive_mutex::scoped_lock sl(mLock); + SHAMapTreeNode* leaf = walkToPointer(id); + if (!leaf) + return SHAMapItem::pointer(); + type = leaf->getType(); return leaf->peekItem(); } @@ -429,10 +475,10 @@ bool SHAMap::hasItem(const uint256& id) bool SHAMap::delItem(const uint256& id) { // delete the item with this ID boost::recursive_mutex::scoped_lock sl(mLock); - assert(mState != Immutable); + assert(mState != smsImmutable); std::stack stack = getStack(id, true, false); - if(stack.empty()) + if (stack.empty()) throw std::runtime_error("missing node"); SHAMapTreeNode::pointer leaf=stack.top(); @@ -442,7 +488,7 @@ bool SHAMap::delItem(const uint256& id) SHAMapTreeNode::TNType type=leaf->getType(); returnNode(leaf, true); - if(mTNByID.erase(*leaf)==0) + if (mTNByID.erase(*leaf) == 0) assert(false); uint256 prevHash; @@ -460,8 +506,8 @@ bool SHAMap::delItem(const uint256& id) } if (!node->isRoot()) { // we may have made this a node with 1 or 0 children - int bc=node->getBranchCount(); - if(bc==0) + int bc = node->getBranchCount(); + if (bc == 0) { #ifdef DEBUG std::cerr << "delItem makes empty node" << std::endl; @@ -470,14 +516,14 @@ bool SHAMap::delItem(const uint256& id) if (!mTNByID.erase(*node)) assert(false); } - else if(bc==1) + else if (bc == 1) { // pull up on the thread SHAMapItem::pointer item = onlyBelow(node.get()); - if(item) + if (item) { eraseChildren(node); #ifdef ST_DEBUG - std::cerr << "Making item node " << node->getString() << std::endl; + std::cerr << "Making item node " << *node << std::endl; #endif node->setItem(item, type); } @@ -495,10 +541,10 @@ bool SHAMap::delItem(const uint256& id) return true; } -bool SHAMap::addGiveItem(SHAMapItem::pointer item, bool isTransaction, bool hasMeta) +bool SHAMap::addGiveItem(const SHAMapItem::pointer& item, bool isTransaction, bool hasMeta) { // add the specified item, does not update #ifdef ST_DEBUG - std::cerr << "aGI " << item->getTag().GetHex() << std::endl; + std::cerr << "aGI " << item->getTag() << std::endl; #endif uint256 tag = item->getTag(); @@ -506,7 +552,7 @@ bool SHAMap::addGiveItem(SHAMapItem::pointer item, bool isTransaction, bool hasM (hasMeta ? SHAMapTreeNode::tnTRANSACTION_MD : SHAMapTreeNode::tnTRANSACTION_NM); boost::recursive_mutex::scoped_lock sl(mLock); - assert(mState != Immutable); + assert(mState != smsImmutable); std::stack stack = getStack(tag, true, false); if (stack.empty()) @@ -521,19 +567,19 @@ bool SHAMap::addGiveItem(SHAMapItem::pointer item, bool isTransaction, bool hasM uint256 prevHash; returnNode(node, true); - if(node->isInner()) + if (node->isInner()) { // easy case, we end on an inner node #ifdef ST_DEBUG - std::cerr << "aGI inner " << node->getString() << std::endl; + std::cerr << "aGI inner " << *node << std::endl; #endif int branch = node->selectBranch(tag); assert(node->isEmptyBranch(branch)); SHAMapTreeNode::pointer newNode = boost::make_shared(node->getChildNodeID(branch), item, type, mSeq); - if(!mTNByID.insert(std::make_pair(SHAMapNode(*newNode), newNode)).second) + if (!mTNByID.insert(std::make_pair(SHAMapNode(*newNode), newNode)).second) { - std::cerr << "Node: " << node->getString() << std::endl; - std::cerr << "NewNode: " << newNode->getString() << std::endl; + std::cerr << "Node: " << *node << std::endl; + std::cerr << "NewNode: " << *newNode << std::endl; dump(); assert(false); throw std::runtime_error("invalid inner node"); @@ -543,8 +589,8 @@ bool SHAMap::addGiveItem(SHAMapItem::pointer item, bool isTransaction, bool hasM else { // this is a leaf node that has to be made an inner node holding two items #ifdef ST_DEBUG - std::cerr << "aGI leaf " << node->getString() << std::endl; - std::cerr << "Existing: " << node->peekItem()->getTag().GetHex() << std::endl; + std::cerr << "aGI leaf " << *node << std::endl; + std::cerr << "Existing: " << node->peekItem()->getTag() << std::endl; #endif SHAMapItem::pointer otherItem = node->peekItem(); assert(otherItem && (tag != otherItem->getTag())); @@ -562,7 +608,7 @@ bool SHAMap::addGiveItem(SHAMapItem::pointer item, bool isTransaction, bool hasM SHAMapTreeNode::pointer newNode = boost::make_shared(mSeq, node->getChildNodeID(b1)); newNode->makeInner(); - if(!mTNByID.insert(std::make_pair(SHAMapNode(*newNode), newNode)).second) + if (!mTNByID.insert(std::make_pair(SHAMapNode(*newNode), newNode)).second) assert(false); stack.push(node); node = newNode; @@ -579,7 +625,7 @@ bool SHAMap::addGiveItem(SHAMapItem::pointer item, bool isTransaction, bool hasM newNode = boost::make_shared(node->getChildNodeID(b2), otherItem, type, mSeq); assert(newNode->isValid() && newNode->isLeaf()); - if(!mTNByID.insert(std::make_pair(SHAMapNode(*newNode), newNode)).second) + if (!mTNByID.insert(std::make_pair(SHAMapNode(*newNode), newNode)).second) assert(false); node->setChildHash(b2, newNode->getNodeHash()); } @@ -593,12 +639,12 @@ bool SHAMap::addItem(const SHAMapItem& i, bool isTransaction, bool hasMetaData) return addGiveItem(boost::make_shared(i), isTransaction, hasMetaData); } -bool SHAMap::updateGiveItem(SHAMapItem::pointer item, bool isTransaction, bool hasMeta) +bool SHAMap::updateGiveItem(const SHAMapItem::pointer& item, bool isTransaction, bool hasMeta) { // can't change the tag but can change the hash uint256 tag = item->getTag(); boost::recursive_mutex::scoped_lock sl(mLock); - assert(mState != Immutable); + assert(mState != smsImmutable); std::stack stack = getStack(tag, true, false); if (stack.empty()) throw std::runtime_error("missing node"); @@ -626,7 +672,7 @@ bool SHAMap::updateGiveItem(SHAMapItem::pointer item, bool isTransaction, bool h void SHAMapItem::dump() { - std::cerr << "SHAMapItem(" << mTag.GetHex() << ") " << mData.size() << "bytes" << std::endl; + std::cerr << "SHAMapItem(" << mTag << ") " << mData.size() << "bytes" << std::endl; } SHAMapTreeNode::pointer SHAMap::fetchNodeExternal(const SHAMapNode& id, const uint256& hash) @@ -635,13 +681,13 @@ SHAMapTreeNode::pointer SHAMap::fetchNodeExternal(const SHAMapNode& id, const ui throw SHAMapMissingNode(id, hash); HashedObject::pointer obj(theApp->getHashedObjectStore().retrieve(hash)); - if(!obj) + if (!obj) throw SHAMapMissingNode(id, hash); assert(Serializer::getSHA512Half(obj->getData()) == hash); try { - SHAMapTreeNode::pointer ret = boost::make_shared(id, obj->getData(), mSeq, STN_ARF_PREFIXED); + SHAMapTreeNode::pointer ret = boost::make_shared(id, obj->getData(), mSeq, snfPREFIX); #ifdef DEBUG assert((ret->getNodeHash() == hash) && (id == *ret)); #endif @@ -649,7 +695,7 @@ SHAMapTreeNode::pointer SHAMap::fetchNodeExternal(const SHAMapNode& id, const ui } catch (...) { - Log(lsWARNING) << "fetchNodeExternal gets an invalid node: " << hash.GetHex(); + Log(lsWARNING) << "fetchNodeExternal gets an invalid node: " << hash; throw SHAMapMissingNode(id, hash); } } @@ -665,14 +711,14 @@ int SHAMap::flushDirty(int maxNodes, HashedObjectType t, uint32 seq) int flushed = 0; Serializer s; - if(mDirtyNodes) + if (mDirtyNodes) { boost::unordered_map& dirtyNodes = *mDirtyNodes; boost::unordered_map::iterator it = dirtyNodes.begin(); while (it != dirtyNodes.end()) { s.erase(); - it->second->addRaw(s, STN_ARF_PREFIXED); + it->second->addRaw(s, snfPREFIX); theApp->getHashedObjectStore().store(t, seq, s.peekData(), s.getSHA512Half()); if (flushed++ >= maxNodes) return flushed; @@ -714,10 +760,10 @@ void SHAMap::dump(bool hash) #if 0 std::cerr << "SHAMap::dump" << std::endl; SHAMapItem::pointer i=peekFirstItem(); - while(i) + while (i) { - std::cerr << "Item: id=" << i->getTag().GetHex() << std::endl; - i=peekNextItem(i->getTag()); + std::cerr << "Item: id=" << i->getTag() << std::endl; + i = peekNextItem(i->getTag()); } std::cerr << "SHAMap::dump done" << std::endl; #endif @@ -728,7 +774,8 @@ void SHAMap::dump(bool hash) it != mTNByID.end(); ++it) { std::cerr << it->second->getString() << std::endl; - if(hash) std::cerr << " " << it->second->getNodeHash().GetHex() << std::endl; + if (hash) + std::cerr << " " << it->second->getNodeHash() << std::endl; } } @@ -756,8 +803,8 @@ BOOST_AUTO_TEST_CASE( SHAMap_test ) SHAMap sMap; SHAMapItem i1(h1, IntToVUC(1)), i2(h2, IntToVUC(2)), i3(h3, IntToVUC(3)), i4(h4, IntToVUC(4)), i5(h5, IntToVUC(5)); - if(!sMap.addItem(i2, true, false)) BOOST_FAIL("no add"); - if(!sMap.addItem(i1, true, false)) BOOST_FAIL("no add"); + if (!sMap.addItem(i2, true, false)) BOOST_FAIL("no add"); + if (!sMap.addItem(i1, true, false)) BOOST_FAIL("no add"); SHAMapItem::pointer i; diff --git a/src/SHAMap.h b/src/SHAMap.h index 0130a624d7..e7f5046d5c 100644 --- a/src/SHAMap.h +++ b/src/SHAMap.h @@ -24,7 +24,8 @@ class SHAMap; class SHAMapNode { // Identifies a node in a SHA256 hash public: - typedef boost::shared_ptr pointer; + typedef boost::shared_ptr pointer; + typedef const boost::shared_ptr& ref; private: static uint256 smMasks[65]; // AND with hash to get node id @@ -78,6 +79,8 @@ public: extern std::size_t hash_value(const SHAMapNode& mn); +inline std::ostream& operator<<(std::ostream& out, const SHAMapNode& node) { return out << node.getString(); } + class SHAMapItem { // an item stored in a SHAMap public: @@ -122,6 +125,12 @@ public: virtual void dump(); }; +enum SHANodeFormat +{ + snfPREFIX = 1, // Form that hashes to its official hash + snfWIRE = 2, // Compressed form used on the wire +}; + class SHAMapTreeNode : public SHAMapNode { friend class SHAMap; @@ -154,14 +163,11 @@ private: public: SHAMapTreeNode(uint32 seq, const SHAMapNode& nodeID); // empty node SHAMapTreeNode(const SHAMapTreeNode& node, uint32 seq); // copy node from older tree - SHAMapTreeNode(const SHAMapNode& nodeID, SHAMapItem::pointer item, TNType type, uint32 seq); - -#define STN_ARF_PREFIXED 1 -#define STN_ARF_WIRE 2 + SHAMapTreeNode(const SHAMapNode& nodeID, const SHAMapItem::pointer& item, TNType type, uint32 seq); // raw node functions - SHAMapTreeNode(const SHAMapNode& id, const std::vector& contents, uint32 seq, int format); - void addRaw(Serializer &, int format); + SHAMapTreeNode(const SHAMapNode& id, const std::vector& data, uint32 seq, SHANodeFormat format); + void addRaw(Serializer &, SHANodeFormat format); virtual bool isPopulated() const { return true; } @@ -196,7 +202,7 @@ public: bool hasItem() const { return !!mItem; } SHAMapItem::pointer peekItem() { return mItem; } SHAMapItem::pointer getItem() const; - bool setItem(SHAMapItem::pointer& i, TNType type); + bool setItem(const SHAMapItem::pointer& i, TNType type); const uint256& getTag() const { return mItem->getTag(); } const std::vector& peekData() { return mItem->peekData(); } std::vector getData() const { return mItem->getData(); } @@ -211,11 +217,11 @@ public: enum SHAMapState { - Modifying = 0, // Objects can be added and removed (like an open ledger) - Immutable = 1, // Map cannot be changed (like a closed ledger) - Synching = 2, // Map's hash is locked in, valid nodes can be added (like a peer's closing ledger) - Floating = 3, // Map is free to change hash (like a synching open ledger) - Invalid = 4, // Map is known not to be valid (usually synching a corrupt ledger) + smsModifying = 0, // Objects can be added and removed (like an open ledger) + smsImmutable = 1, // Map cannot be changed (like a closed ledger) + smsSynching = 2, // Map's hash is locked in, valid nodes can be added (like a peer's closing ledger) + smsFloating = 3, // Map is free to change hash (like a synching open ledger) + smsInvalid = 4, // Map is known not to be valid (usually synching a corrupt ledger) }; class SHAMapSyncFilter @@ -223,9 +229,11 @@ class SHAMapSyncFilter public: SHAMapSyncFilter() { ; } virtual ~SHAMapSyncFilter() { ; } + virtual void gotNode(const SHAMapNode& id, const uint256& nodeHash, const std::vector& nodeData, bool isLeaf) { ; } + virtual bool haveNode(const SHAMapNode& id, const uint256& nodeHash, std::vector& nodeData) { return false; } }; @@ -242,6 +250,7 @@ public: { ; } virtual ~SHAMapMissingNode() throw() { ; } + const SHAMapNode& getNodeID() const { return mNodeID; } const uint256& getNodeHash() const { return mNodeHash; } }; @@ -250,6 +259,8 @@ class SHAMap { public: typedef boost::shared_ptr pointer; + typedef const boost::shared_ptr& ref; + typedef std::map > SHAMapDiff; private: @@ -275,9 +286,9 @@ protected: SHAMapTreeNode::pointer getNode(const SHAMapNode& id); SHAMapTreeNode::pointer getNode(const SHAMapNode& id, const uint256& hash, bool modify); SHAMapTreeNode* getNodePointer(const SHAMapNode& id, const uint256& hash); + SHAMapTreeNode* firstBelow(SHAMapTreeNode*); + SHAMapTreeNode* lastBelow(SHAMapTreeNode*); - SHAMapItem::pointer firstBelow(SHAMapTreeNode*); - SHAMapItem::pointer lastBelow(SHAMapTreeNode*); SHAMapItem::pointer onlyBelow(SHAMapTreeNode*); void eraseChildren(SHAMapTreeNode::pointer); @@ -290,6 +301,8 @@ public: SHAMap(uint32 seq = 0); SHAMap(const uint256& hash); + ~SHAMap() { mState = smsInvalid; } + // Returns a new map that's a snapshot of this one. Force CoW SHAMap::pointer snapShot(bool isMutable); @@ -308,41 +321,44 @@ public: uint256 getHash() { return root->getNodeHash(); } // save a copy if you have a temporary anyway - bool updateGiveItem(SHAMapItem::pointer, bool isTransaction, bool hasMeta); - bool addGiveItem(SHAMapItem::pointer, bool isTransaction, bool hasMeta); + bool updateGiveItem(const SHAMapItem::pointer&, bool isTransaction, bool hasMeta); + bool addGiveItem(const SHAMapItem::pointer&, bool isTransaction, bool hasMeta); // save a copy if you only need a temporary SHAMapItem::pointer peekItem(const uint256& id); + SHAMapItem::pointer peekItem(const uint256& id, SHAMapTreeNode::TNType& type); // traverse functions SHAMapItem::pointer peekFirstItem(); + SHAMapItem::pointer peekFirstItem(SHAMapTreeNode::TNType& type); SHAMapItem::pointer peekLastItem(); SHAMapItem::pointer peekNextItem(const uint256&); + SHAMapItem::pointer peekNextItem(const uint256&, SHAMapTreeNode::TNType& type); SHAMapItem::pointer peekPrevItem(const uint256&); // comparison/sync functions void getMissingNodes(std::vector& nodeIDs, std::vector& hashes, int max, SHAMapSyncFilter* filter); bool getNodeFat(const SHAMapNode& node, std::vector& nodeIDs, - std::list >& rawNode, bool fatLeaves); - bool getRootNode(Serializer& s, int format); - bool addRootNode(const uint256& hash, const std::vector& rootNode, int format); - bool addRootNode(const std::vector& rootNode, int format); + std::list >& rawNode, bool fatRoot, bool fatLeaves); + bool getRootNode(Serializer& s, SHANodeFormat format); + bool addRootNode(const uint256& hash, const std::vector& rootNode, SHANodeFormat format); + bool addRootNode(const std::vector& rootNode, SHANodeFormat format); bool addKnownNode(const SHAMapNode& nodeID, const std::vector& rawNode, SHAMapSyncFilter* filter); // status functions - void setImmutable(void) { assert(mState != Invalid); mState = Immutable; } - void clearImmutable(void) { mState = Modifying; } - bool isSynching(void) const { return (mState == Floating) || (mState == Synching); } - void setSynching(void) { mState = Synching; } - void setFloating(void) { mState = Floating; } - void clearSynching(void) { mState = Modifying; } - bool isValid(void) { return mState != Invalid; } + void setImmutable() { assert(mState != smsInvalid); mState = smsImmutable; } + void clearImmutable() { mState = smsModifying; } + bool isSynching() const { return (mState == smsFloating) || (mState == smsSynching); } + void setSynching() { mState = smsSynching; } + void setFloating() { mState = smsFloating; } + void clearSynching() { mState = smsModifying; } + bool isValid() { return mState != smsInvalid; } // caution: otherMap must be accessed only by this function // return value: true=successfully completed, false=too different - bool compare(SHAMap::pointer otherMap, SHAMapDiff& differences, int maxCount); + bool compare(SHAMap::ref otherMap, SHAMapDiff& differences, int maxCount); void armDirty(); int flushDirty(int maxNodes, HashedObjectType t, uint32 seq); diff --git a/src/SHAMapDiff.cpp b/src/SHAMapDiff.cpp index d1f3a32145..3f5adb3e0d 100644 --- a/src/SHAMapDiff.cpp +++ b/src/SHAMapDiff.cpp @@ -91,17 +91,22 @@ bool SHAMap::walkBranch(SHAMapTreeNode* node, SHAMapItem::pointer otherMapItem, return true; } -bool SHAMap::compare(SHAMap::pointer otherMap, SHAMapDiff& differences, int maxCount) +bool SHAMap::compare(SHAMap::ref otherMap, SHAMapDiff& differences, int maxCount) { // compare two hash trees, add up to maxCount differences to the difference table // return value: true=complete table of differences given, false=too many differences // throws on corrupt tables or missing nodes + // CAUTION: otherMap is not locked and must be immutable + + assert(isValid() && otherMap && otherMap->isValid()); std::stack nodeStack; // track nodes we've pushed - ScopedLock sl(Lock()); - if (getHash() == otherMap->getHash()) return true; - nodeStack.push(SHAMapDiffNode(SHAMapNode(), getHash(), otherMap->getHash())); + boost::recursive_mutex::scoped_lock sl(mLock); + if (getHash() == otherMap->getHash()) + return true; + + nodeStack.push(SHAMapDiffNode(SHAMapNode(), getHash(), otherMap->getHash())); while (!nodeStack.empty()) { SHAMapDiffNode dNode(nodeStack.top()); @@ -109,6 +114,11 @@ bool SHAMap::compare(SHAMap::pointer otherMap, SHAMapDiff& differences, int maxC SHAMapTreeNode* ourNode = getNodePointer(dNode.mNodeID, dNode.mOurHash); SHAMapTreeNode* otherNode = otherMap->getNodePointer(dNode.mNodeID, dNode.mOtherHash); + if (!ourNode || !otherNode) + { + assert(false); + throw SHAMapMissingNode(dNode.mNodeID, uint256()); + } if (ourNode->isLeaf() && otherNode->isLeaf()) { // two leaves @@ -118,17 +128,20 @@ bool SHAMap::compare(SHAMap::pointer otherMap, SHAMapDiff& differences, int maxC { differences.insert(std::make_pair(ourNode->getTag(), std::make_pair(ourNode->getItem(), otherNode->getItem()))); - if (--maxCount <= 0) return false; + if (--maxCount <= 0) + return false; } } else { differences.insert(std::make_pair(ourNode->getTag(), std::make_pair(ourNode->getItem(), SHAMapItem::pointer()))); - if (--maxCount <= 0) return false; + if (--maxCount <= 0) + return false; differences.insert(std::make_pair(otherNode->getTag(), std::make_pair(SHAMapItem::pointer(), otherNode->getItem()))); - if (--maxCount <= 0) return false; + if (--maxCount <= 0) + return false; } } else if (ourNode->isInner() && otherNode->isLeaf()) @@ -164,7 +177,8 @@ bool SHAMap::compare(SHAMap::pointer otherMap, SHAMapDiff& differences, int maxC ourNode->getChildHash(i), otherNode->getChildHash(i))); } } - else assert(false); + else + assert(false); } return true; diff --git a/src/SHAMapNodes.cpp b/src/SHAMapNodes.cpp index 95fdcb1f9d..746176aba3 100644 --- a/src/SHAMapNodes.cpp +++ b/src/SHAMapNodes.cpp @@ -27,58 +27,58 @@ uint256 SHAMapNode::smMasks[65]; bool SHAMapNode::operator<(const SHAMapNode &s) const { - if(s.mDepthmDepth) return false; - return mNodeID mDepth) return false; + return mNodeID < s.mNodeID; } bool SHAMapNode::operator>(const SHAMapNode &s) const { - if(s.mDepthmDepth) return true; - return mNodeID>s.mNodeID; + if (s.mDepth < mDepth) return false; + if (s.mDepth > mDepth) return true; + return mNodeID > s.mNodeID; } bool SHAMapNode::operator<=(const SHAMapNode &s) const { - if(s.mDepthmDepth) return false; - return mNodeID<=s.mNodeID; + if (s.mDepth < mDepth) return true; + if (s.mDepth > mDepth) return false; + return mNodeID <= s.mNodeID; } bool SHAMapNode::operator>=(const SHAMapNode &s) const { - if(s.mDepthmDepth) return true; - return mNodeID>=s.mNodeID; + if (s.mDepth < mDepth) return false; + if (s.mDepth > mDepth) return true; + return mNodeID >= s.mNodeID; } bool SHAMapNode::operator==(const SHAMapNode &s) const { - return (s.mDepth==mDepth) && (s.mNodeID==mNodeID); + return (s.mDepth == mDepth) && (s.mNodeID == mNodeID); } bool SHAMapNode::operator!=(const SHAMapNode &s) const { - return (s.mDepth!=mDepth) || (s.mNodeID!=mNodeID); + return (s.mDepth != mDepth) || (s.mNodeID != mNodeID); } bool SHAMapNode::operator==(const uint256 &s) const { - return s==mNodeID; + return s == mNodeID; } bool SHAMapNode::operator!=(const uint256 &s) const { - return s!=mNodeID; + return s != mNodeID; } -static bool j = SHAMapNode::ClassInit(); +bool SMN_j = SHAMapNode::ClassInit(); bool SHAMapNode::ClassInit() { // set up the depth masks uint256 selector; - for(int i = 0; i < 64; i += 2) + for (int i = 0; i < 64; i += 2) { smMasks[i] = selector; *(selector.begin() + (i / 2)) = 0xF0; @@ -147,7 +147,7 @@ int SHAMapNode::selectBranch(const uint256& hash) const if ((hash & smMasks[mDepth]) != mNodeID) { std::cerr << "selectBranch(" << getString() << std::endl; - std::cerr << " " << hash.GetHex() << " off branch" << std::endl; + std::cerr << " " << hash << " off branch" << std::endl; assert(false); return -1; // does not go under this node } @@ -182,17 +182,17 @@ SHAMapTreeNode::SHAMapTreeNode(const SHAMapTreeNode& node, uint32 seq) : SHAMapN memcpy(mHashes, node.mHashes, sizeof(mHashes)); } -SHAMapTreeNode::SHAMapTreeNode(const SHAMapNode& node, SHAMapItem::pointer item, TNType type, uint32 seq) : +SHAMapTreeNode::SHAMapTreeNode(const SHAMapNode& node, const SHAMapItem::pointer& item, TNType type, uint32 seq) : SHAMapNode(node), mItem(item), mSeq(seq), mType(type), mFullBelow(true) { assert(item->peekData().size() >= 12); updateHash(); } -SHAMapTreeNode::SHAMapTreeNode(const SHAMapNode& id, const std::vector& rawNode, uint32 seq, int format) - : SHAMapNode(id), mSeq(seq), mType(tnERROR), mFullBelow(false) +SHAMapTreeNode::SHAMapTreeNode(const SHAMapNode& id, const std::vector& rawNode, uint32 seq, + SHANodeFormat format) : SHAMapNode(id), mSeq(seq), mType(tnERROR), mFullBelow(false) { - if (format == STN_ARF_WIRE) + if (format == snfWIRE) { Serializer s(rawNode); int type = s.removeLastByte(); @@ -256,7 +256,7 @@ SHAMapTreeNode::SHAMapTreeNode(const SHAMapNode& id, const std::vector(txID, s.peekData()); mType = tnTRANSACTION_MD; } @@ -350,14 +351,14 @@ bool SHAMapTreeNode::updateHash() return true; } -void SHAMapTreeNode::addRaw(Serializer& s, int format) +void SHAMapTreeNode::addRaw(Serializer& s, SHANodeFormat format) { - assert((format == STN_ARF_PREFIXED) || (format == STN_ARF_WIRE)); + assert((format == snfPREFIX) || (format == snfWIRE)); if (mType == tnERROR) throw std::runtime_error("invalid I node type"); if (mType == tnINNER) { - if (format == STN_ARF_PREFIXED) + if (format == snfPREFIX) { s.add32(sHP_InnerNode); for (int i = 0; i < 16; ++i) @@ -385,7 +386,7 @@ void SHAMapTreeNode::addRaw(Serializer& s, int format) } else if (mType == tnACCOUNT_STATE) { - if (format == STN_ARF_PREFIXED) + if (format == snfPREFIX) { s.add32(sHP_LeafNode); mItem->addRaw(s); @@ -400,7 +401,7 @@ void SHAMapTreeNode::addRaw(Serializer& s, int format) } else if (mType == tnTRANSACTION_NM) { - if (format == STN_ARF_PREFIXED) + if (format == snfPREFIX) { s.add32(sHP_TransactionID); mItem->addRaw(s); @@ -413,7 +414,7 @@ void SHAMapTreeNode::addRaw(Serializer& s, int format) } else if (mType == tnTRANSACTION_MD) { - if (format == STN_ARF_PREFIXED) + if (format == snfPREFIX) { s.add32(sHP_TransactionNode); mItem->addRaw(s); @@ -429,7 +430,7 @@ void SHAMapTreeNode::addRaw(Serializer& s, int format) assert(false); } -bool SHAMapTreeNode::setItem(SHAMapItem::pointer& i, TNType type) +bool SHAMapTreeNode::setItem(const SHAMapItem::pointer& i, TNType type) { uint256 hash = getNodeHash(); mType = type; @@ -464,7 +465,7 @@ void SHAMapTreeNode::makeInner() void SHAMapTreeNode::dump() { - Log(lsDEBUG) << "SHAMapTreeNode(" << getNodeID().GetHex() << ")"; + Log(lsDEBUG) << "SHAMapTreeNode(" << getNodeID() << ")"; } std::string SHAMapTreeNode::getString() const @@ -476,7 +477,7 @@ std::string SHAMapTreeNode::getString() const ret += ")"; if (isInner()) { - for(int i = 0; i < 16; ++i) + for (int i = 0; i < 16; ++i) if (!isEmptyBranch(i)) { ret += "\nb"; diff --git a/src/SHAMapSync.cpp b/src/SHAMapSync.cpp index ab230c79fb..5ae0af44c5 100644 --- a/src/SHAMapSync.cpp +++ b/src/SHAMapSync.cpp @@ -58,7 +58,7 @@ void SHAMap::getMissingNodes(std::vector& nodeIDs, std::vector nodeData; if (filter->haveNode(childID, childHash, nodeData)) { - d = boost::make_shared(childID, nodeData, mSeq, STN_ARF_PREFIXED); + d = boost::make_shared(childID, nodeData, mSeq, snfPREFIX); if (childHash != d->getNodeHash()) { Log(lsERROR) << "Wrong hash from cached object"; @@ -66,7 +66,7 @@ void SHAMap::getMissingNodes(std::vector& nodeIDs, std::vectorgetString(); + Log(lsTRACE) << "Got sync node from cache: " << *d; mTNByID[*d] = d; } } @@ -86,7 +86,7 @@ void SHAMap::getMissingNodes(std::vector& nodeIDs, std::vector& nodeIDs, - std::list >& rawNodes, bool fatLeaves) + std::list >& rawNodes, bool fatRoot, bool fatLeaves) { // Gets a node and some of its children boost::recursive_mutex::scoped_lock sl(mLock); @@ -99,10 +99,10 @@ bool SHAMap::getNodeFat(const SHAMapNode& wanted, std::vector& nodeI nodeIDs.push_back(*node); Serializer s; - node->addRaw(s, STN_ARF_WIRE); + node->addRaw(s, snfWIRE); rawNodes.push_back(s.peekData()); - if (node->isRoot() || node->isLeaf()) // don't get a fat root, can't get a fat leaf + if ((!fatRoot && node->isRoot()) || node->isLeaf()) // don't get a fat root, can't get a fat leaf return true; for (int i = 0; i < 16; ++i) @@ -114,7 +114,7 @@ bool SHAMap::getNodeFat(const SHAMapNode& wanted, std::vector& nodeI { nodeIDs.push_back(*nextNode); Serializer s; - nextNode->addRaw(s, STN_ARF_WIRE); + nextNode->addRaw(s, snfWIRE); rawNodes.push_back(s.peekData()); } } @@ -122,14 +122,14 @@ bool SHAMap::getNodeFat(const SHAMapNode& wanted, std::vector& nodeI return true; } -bool SHAMap::getRootNode(Serializer& s, int format) +bool SHAMap::getRootNode(Serializer& s, SHANodeFormat format) { boost::recursive_mutex::scoped_lock sl(mLock); root->addRaw(s, format); return true; } -bool SHAMap::addRootNode(const std::vector& rootNode, int format) +bool SHAMap::addRootNode(const std::vector& rootNode, SHANodeFormat format) { boost::recursive_mutex::scoped_lock sl(mLock); @@ -141,7 +141,8 @@ bool SHAMap::addRootNode(const std::vector& rootNode, int format) } SHAMapTreeNode::pointer node = boost::make_shared(SHAMapNode(), rootNode, 0, format); - if (!node) return false; + if (!node) + return false; #ifdef DEBUG node->dump(); @@ -160,7 +161,7 @@ bool SHAMap::addRootNode(const std::vector& rootNode, int format) return true; } -bool SHAMap::addRootNode(const uint256& hash, const std::vector& rootNode, int format) +bool SHAMap::addRootNode(const uint256& hash, const std::vector& rootNode, SHANodeFormat format) { boost::recursive_mutex::scoped_lock sl(mLock); @@ -221,8 +222,8 @@ bool SHAMap::addKnownNode(const SHAMapNode& node, const std::vectorgetDepth() != (node.getDepth() - 1)) { // Either this node is broken or we didn't request it (yet) - Log(lsINFO) << "unable to hook node " << node.getString(); - Log(lsINFO) << " stuck at " << iNode->getString(); + Log(lsINFO) << "unable to hook node " << node; + Log(lsINFO) << " stuck at " << *iNode; Log(lsINFO) << "got depth=" << node.getDepth() << ", walked to= " << iNode->getDepth(); return false; } @@ -236,14 +237,14 @@ bool SHAMap::addKnownNode(const SHAMapNode& node, const std::vectorgetChildHash(branch); if (!hash) return false; - SHAMapTreeNode::pointer newNode = boost::make_shared(node, rawNode, mSeq, STN_ARF_WIRE); + SHAMapTreeNode::pointer newNode = boost::make_shared(node, rawNode, mSeq, snfWIRE); if (hash != newNode->getNodeHash()) // these aren't the droids we're looking for return false; if (filter) { Serializer s; - newNode->addRaw(s, STN_ARF_PREFIXED); + newNode->addRaw(s, snfPREFIX); filter->gotNode(node, hash, s.peekData(), newNode->isLeaf()); } @@ -303,7 +304,7 @@ bool SHAMap::deepCompare(SHAMap& other) return false; } -// Log(lsTRACE) << "Comparing inner nodes " << node->getString(); +// Log(lsTRACE) << "Comparing inner nodes " << *node; if (node->getNodeHash() != otherNode->getNodeHash()) return false; @@ -399,7 +400,7 @@ std::list > SHAMap::getTrustedPath(const uint256& ind Serializer s; while (!stack.empty()) { - stack.top()->addRaw(s, STN_ARF_WIRE); + stack.top()->addRaw(s, snfWIRE); path.push_back(s.getData()); s.erase(); stack.pop(); @@ -444,17 +445,17 @@ BOOST_AUTO_TEST_CASE( SHAMapSync_test ) destination.setSynching(); - if (!source.getNodeFat(SHAMapNode(), nodeIDs, gotNodes, (rand() % 2) == 0)) + if (!source.getNodeFat(SHAMapNode(), nodeIDs, gotNodes, (rand() % 2) == 0, (rand() % 2) == 0)) { Log(lsFATAL) << "GetNodeFat(root) fails"; BOOST_FAIL("GetNodeFat"); } - if (gotNodes.size() != 1) + if (gotNodes.size() < 1) { Log(lsFATAL) << "Didn't get root node " << gotNodes.size(); BOOST_FAIL("NodeSize"); } - if (!destination.addRootNode(*gotNodes.begin(), STN_ARF_WIRE)) + if (!destination.addRootNode(*gotNodes.begin(), snfWIRE)) { Log(lsFATAL) << "AddRootNode fails"; BOOST_FAIL("AddRootNode"); @@ -481,7 +482,7 @@ BOOST_AUTO_TEST_CASE( SHAMapSync_test ) // get as many nodes as possible based on this information for (nodeIDIterator = nodeIDs.begin(); nodeIDIterator != nodeIDs.end(); ++nodeIDIterator) { - if (!source.getNodeFat(*nodeIDIterator, gotNodeIDs, gotNodes, (rand() % 2) == 0)) + if (!source.getNodeFat(*nodeIDIterator, gotNodeIDs, gotNodes, (rand() % 2) == 0, (rand() % 2) == 0)) { Log(lsFATAL) << "GetNodeFat fails"; BOOST_FAIL("GetNodeFat"); diff --git a/src/SHAMapSync.h b/src/SHAMapSync.h index 2cd6ffc1e4..3d673e37a3 100644 --- a/src/SHAMapSync.h +++ b/src/SHAMapSync.h @@ -37,7 +37,7 @@ public: virtual void gotNode(const SHAMapNode& id, const uint256& nodeHash, const std::vector& nodeData, bool isLeaf) { - theApp->getHashedObjectStore().store(ACCOUNT_NODE, mLedgerSeq, nodeData, nodeHash); + theApp->getHashedObjectStore().store(hotACCOUNT_NODE, mLedgerSeq, nodeData, nodeHash); } virtual bool haveNode(const SHAMapNode& id, const uint256& nodeHash, std::vector& nodeData) { // fetchNodeExternal already tried @@ -58,7 +58,8 @@ public: virtual void gotNode(const SHAMapNode& id, const uint256& nodeHash, const std::vector& nodeData, bool isLeaf) { - theApp->getHashedObjectStore().store(isLeaf ? TRANSACTION : TRANSACTION_NODE, mLedgerSeq, nodeData, nodeHash); + theApp->getHashedObjectStore().store(isLeaf ? hotTRANSACTION : hotTRANSACTION_NODE, mLedgerSeq, + nodeData, nodeHash); } virtual bool haveNode(const SHAMapNode& id, const uint256& nodeHash, std::vector& nodeData) { // fetchNodeExternal already tried diff --git a/src/SNTPClient.cpp b/src/SNTPClient.cpp index d84cfa3c57..cdafc0698f 100644 --- a/src/SNTPClient.cpp +++ b/src/SNTPClient.cpp @@ -6,15 +6,19 @@ #include #include "utils.h" +#include "Config.h" #include "Log.h" -static uint8_t SNTPQueryData[48] = { - 0x1B,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 -}; +// #define SNTP_DEBUG -// NTP query frequency - 5 minutes -#define NTP_QUERY_FREQUENCY (5 * 60) +static uint8_t SNTPQueryData[48] = +{ 0x1B,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }; + +// NTP query frequency - 4 minutes +#define NTP_QUERY_FREQUENCY (4 * 60) + +// NTP minimum interval to query same servers - 3 minutes +#define NTP_MIN_QUERY (3 * 60) // NTP sample window (should be odd) #define NTP_SAMPLE_WINDOW 9 @@ -22,6 +26,9 @@ static uint8_t SNTPQueryData[48] = { // NTP timestamp constant #define NTP_UNIX_OFFSET 0x83AA7E80 +// NTP timestamp validity +#define NTP_TIMESTAMP_VALID ((NTP_QUERY_FREQUENCY + NTP_MIN_QUERY) * 2) + // SNTP packet offsets #define NTP_OFF_INFO 0 #define NTP_OFF_ROOTDELAY 1 @@ -37,15 +44,14 @@ static uint8_t SNTPQueryData[48] = { #define NTP_OFF_XMITTS_FRAC 11 -SNTPClient::SNTPClient(boost::asio::io_service& service) : - mIOService(service), mSocket(service), mTimer(service), mResolver(service), +SNTPClient::SNTPClient(boost::asio::io_service& service) : mSocket(service), mTimer(service), mResolver(service), mOffset(0), mLastOffsetUpdate((time_t) -1), mReceiveBuffer(256) { mSocket.open(boost::asio::ip::udp::v4()); mSocket.async_receive_from(boost::asio::buffer(mReceiveBuffer, 256), mReceiveEndpoint, boost::bind(&SNTPClient::receivePacket, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)); - + mTimer.expires_from_now(boost::posix_time::seconds(NTP_QUERY_FREQUENCY)); mTimer.async_wait(boost::bind(&SNTPClient::timerEntry, this, boost::asio::placeholders::error)); } @@ -65,7 +71,7 @@ void SNTPClient::resolveComplete(const boost::system::error_code& error, boost:: SNTPQuery& query = mQueries[*sel]; time_t now = time(NULL); if ((query.mLocalTimeSent == now) || ((query.mLocalTimeSent + 1) == now)) - { + { // This can happen if the same IP address is reached through multiple names Log(lsTRACE) << "SNTP: Redundant query suppressed"; return; } @@ -86,21 +92,24 @@ void SNTPClient::receivePacket(const boost::system::error_code& error, std::size if (!error) { boost::mutex::scoped_lock sl(mLock); +#ifdef SNTP_DEBUG Log(lsTRACE) << "SNTP: Packet from " << mReceiveEndpoint; +#endif std::map::iterator query = mQueries.find(mReceiveEndpoint); if (query == mQueries.end()) - Log(lsDEBUG) << "SNTP: Reply found without matching query"; + Log(lsDEBUG) << "SNTP: Reply from " << mReceiveEndpoint << " found without matching query"; else if (query->second.mReceivedReply) - Log(lsDEBUG) << "SNTP: Duplicate response to query"; + Log(lsDEBUG) << "SNTP: Duplicate response from " << mReceiveEndpoint; else { query->second.mReceivedReply = true; if (time(NULL) > (query->second.mLocalTimeSent + 1)) - Log(lsWARNING) << "SNTP: Late response"; + Log(lsWARNING) << "SNTP: Late response from " << mReceiveEndpoint; else if (bytes_xferd < 48) - Log(lsWARNING) << "SNTP: Short reply (" << bytes_xferd << ") " << mReceiveBuffer.size(); + Log(lsWARNING) << "SNTP: Short reply from " << mReceiveEndpoint + << " (" << bytes_xferd << ") " << mReceiveBuffer.size(); else if (reinterpret_cast(&mReceiveBuffer[0])[NTP_OFF_ORGTS_FRAC] != query->second.mQueryNonce) - Log(lsWARNING) << "SNTP: Reply had wrong nonce"; + Log(lsWARNING) << "SNTP: Reply from " << mReceiveEndpoint << "had wrong nonce"; else processReply(); } @@ -128,12 +137,12 @@ void SNTPClient::processReply() if ((info >> 30) == 3) { - Log(lsINFO) << "SNTP: Alarm condition"; + Log(lsINFO) << "SNTP: Alarm condition " << mReceiveEndpoint; return; } if ((stratum == 0) || (stratum > 14)) { - Log(lsINFO) << "SNTP: Unreasonable stratum"; + Log(lsINFO) << "SNTP: Unreasonable stratum (" << stratum << ") from " << mReceiveEndpoint; return; } @@ -161,7 +170,10 @@ void SNTPClient::processReply() if ((mOffset == -1) || (mOffset == 1)) // small corrections likely do more harm than good mOffset = 0; - Log(lsTRACE) << "SNTP: Offset is " << timev << ", new system offset is " << mOffset; +#ifndef SNTP_DEBUG + if (timev || mOffset) +#endif + Log(lsTRACE) << "SNTP: Offset is " << timev << ", new system offset is " << mOffset; } void SNTPClient::timerEntry(const boost::system::error_code& error) @@ -196,14 +208,14 @@ void SNTPClient::init(const std::vector& servers) void SNTPClient::queryAll() { - while(doQuery()) + while (doQuery()) nothing(); } bool SNTPClient::getOffset(int& offset) { boost::mutex::scoped_lock sl(mLock); - if ((mLastOffsetUpdate == (time_t) -1) || ((mLastOffsetUpdate + 90) < time(NULL))) + if ((mLastOffsetUpdate == (time_t) -1) || ((mLastOffsetUpdate + NTP_TIMESTAMP_VALID) < time(NULL))) return false; offset = mOffset; return true; @@ -223,7 +235,7 @@ bool SNTPClient::doQuery() return false; } time_t now = time(NULL); - if ((best->second == now) || (best->second == (now - 1))) + if ((best->second != (time_t) -1) && ((best->second + NTP_MIN_QUERY) >= now)) { Log(lsTRACE) << "SNTP: All servers recently queried"; return false; @@ -234,6 +246,9 @@ bool SNTPClient::doQuery() mResolver.async_resolve(query, boost::bind(&SNTPClient::resolveComplete, this, boost::asio::placeholders::error, boost::asio::placeholders::iterator)); +#ifdef SNTP_DEBUG Log(lsTRACE) << "SNTP: Resolve pending for " << best->first; +#endif return true; } +// vim:ts=4 diff --git a/src/SNTPClient.h b/src/SNTPClient.h index ea7b0e7c8a..ea01bcf7aa 100644 --- a/src/SNTPClient.h +++ b/src/SNTPClient.h @@ -9,26 +9,24 @@ #include #include +#include "types.h" + class SNTPQuery { public: bool mReceivedReply; time_t mLocalTimeSent; - int mQueryNonce; + uint32 mQueryNonce; SNTPQuery(time_t j = (time_t) -1) : mReceivedReply(false), mLocalTimeSent(j) { ; } }; class SNTPClient { -public: - typedef boost::shared_ptr pointer; - protected: std::map mQueries; boost::mutex mLock; - boost::asio::io_service& mIOService; boost::asio::ip::udp::socket mSocket; boost::asio::deadline_timer mTimer; boost::asio::ip::udp::resolver mResolver; @@ -60,3 +58,4 @@ public: }; #endif +// vim:ts=4 diff --git a/src/ScriptData.cpp b/src/ScriptData.cpp new file mode 100644 index 0000000000..52442c0d17 --- /dev/null +++ b/src/ScriptData.cpp @@ -0,0 +1 @@ +#include "ScriptData.h" \ No newline at end of file diff --git a/src/ScriptData.h b/src/ScriptData.h new file mode 100644 index 0000000000..66fe2ed3e4 --- /dev/null +++ b/src/ScriptData.h @@ -0,0 +1,96 @@ +#ifndef __SCRIPT_DATA__ +#define __SCRIPT_DATA__ +#include "uint256.h" +#include + +namespace Script { +class Data +{ +public: + typedef boost::shared_ptr pointer; + + virtual ~Data(){ ; } + + virtual bool isInt32(){ return(false); } + virtual bool isFloat(){ return(false); } + virtual bool isUint160(){ return(false); } + virtual bool isError(){ return(false); } + virtual bool isTrue(){ return(false); } + virtual bool isBool(){ return(false); } + //virtual bool isBlockEnd(){ return(false); } + + virtual int getInt(){ return(0); } + virtual float getFloat(){ return(0); } + virtual uint160 getUint160(){ return(0); } + + //virtual bool isCurrency(){ return(false); } +}; + +class IntData : public Data +{ + int mValue; +public: + IntData(int value) + { + mValue=value; + } + bool isInt32(){ return(true); } + int getInt(){ return(mValue); } + float getFloat(){ return((float)mValue); } + bool isTrue(){ return(mValue!=0); } +}; + +class FloatData : public Data +{ + float mValue; +public: + FloatData(float value) + { + mValue=value; + } + bool isFloat(){ return(true); } + float getFloat(){ return(mValue); } + bool isTrue(){ return(mValue!=0); } +}; + +class Uint160Data : public Data +{ + uint160 mValue; +public: + Uint160Data(uint160 value) + { + mValue=value; + } + bool isUint160(){ return(true); } + uint160 getUint160(){ return(mValue); } +}; + +class BoolData : public Data +{ + bool mValue; +public: + BoolData(bool value) + { + mValue=value; + } + bool isBool(){ return(true); } + bool isTrue(){ return(mValue); } +}; + +class ErrorData : public Data +{ +public: + bool isError(){ return(true); } +}; + +class BlockEndData : public Data +{ +public: + bool isBlockEnd(){ return(true); } +}; + + +} + + +#endif \ No newline at end of file diff --git a/src/SerializeProto.h b/src/SerializeProto.h new file mode 100644 index 0000000000..23a2260bb7 --- /dev/null +++ b/src/SerializeProto.h @@ -0,0 +1,134 @@ +// This is not really a header file, but it can be used as one with +// appropriate #define statements. + + // types (common) + TYPE("Int16", UINT16, 1) + TYPE("Int32", UINT32, 2) + TYPE("Int64", UINT64, 3) + TYPE("Hash128", HASH128, 4) + TYPE("Hash256", HASH256, 5) + TYPE("Amount", AMOUNT, 6) + TYPE("VariableLength", VL, 7) + TYPE("Account", ACCOUNT, 8) + // 9-13 are reserved + TYPE("Object", OBJECT, 14) + TYPE("Array", ARRAY, 15) + + // types (uncommon) + TYPE("Int8", UINT8, 16) + TYPE("Hash160", HASH160, 17) + TYPE("PathSet", PATHSET, 18) + TYPE("Vector256", VECTOR256, 19) + + + + // 8-bit integers + FIELD(CloseResolution, UINT8, 1) + + // 16-bit integers + FIELD(LedgerEntryType, UINT16, 1) + FIELD(TransactionType, UINT16, 2) + + // 32-bit integers (common) + FIELD(ObjectType, UINT32, 1) + FIELD(Flags, UINT32, 2) + FIELD(SourceTag, UINT32, 3) + FIELD(Sequence, UINT32, 4) + FIELD(LastTxnSeq, UINT32, 5) + FIELD(LedgerSequence, UINT32, 6) + FIELD(CloseTime, UINT32, 7) + FIELD(ParentCloseTime, UINT32, 8) + FIELD(SigningTime, UINT32, 9) + FIELD(Expiration, UINT32, 10) + FIELD(TransferRate, UINT32, 11) + FIELD(PublishSize, UINT32, 12) + + // 32-bit integers (uncommon) + FIELD(HighQualityIn, UINT32, 16) + FIELD(HighQualityOut, UINT32, 17) + FIELD(LowQualityIn, UINT32, 18) + FIELD(LowQualityOut, UINT32, 19) + FIELD(QualityIn, UINT32, 20) + FIELD(QualityOut, UINT32, 21) + FIELD(StampEscrow, UINT32, 22) + FIELD(BondAmount, UINT32, 23) + FIELD(LoadFee, UINT32, 24) + FIELD(OfferSequence, UINT32, 25) + + // 64-bit integers + FIELD(IndexNext, UINT64, 1) + FIELD(IndexPrevious, UINT64, 2) + FIELD(BookNode, UINT64, 3) + FIELD(OwnerNode, UINT64, 4) + FIELD(BaseFee, UINT64, 5) + + // 128-bit + FIELD(EmailHash, HASH128, 1) + + // 256-bit (common) + FIELD(LedgerHash, HASH256, 1) + FIELD(ParentHash, HASH256, 2) + FIELD(TransactionHash, HASH256, 3) + FIELD(AccountHash, HASH256, 4) + FIELD(LastTxnID, HASH256, 5) + FIELD(LedgerIndex, HASH256, 6) + FIELD(WalletLocator, HASH256, 7) + FIELD(PublishHash, HASH256, 8) + + // 256-bit (uncommon) + FIELD(BookDirectory, HASH256, 16) + FIELD(InvoiceID, HASH256, 17) + FIELD(Nickname, HASH256, 18) + + // currency amount (common) + FIELD(Amount, AMOUNT, 1) + FIELD(Balance, AMOUNT, 2) + FIELD(LimitAmount, AMOUNT, 3) + FIELD(TakerPays, AMOUNT, 4) + FIELD(TakerGets, AMOUNT, 5) + FIELD(LowLimit, AMOUNT, 6) + FIELD(HighLimit, AMOUNT, 7) + FIELD(Fee, AMOUNT, 8) + FIELD(SendMax, AMOUNT, 9) + + // current amount (uncommon) + FIELD(MinimumOffer, AMOUNT, 16) + FIELD(RippleEscrow, AMOUNT, 17) + + // variable length + FIELD(PublicKey, VL, 1) + FIELD(MessageKey, VL, 2) + FIELD(SigningPubKey, VL, 3) + FIELD(TxnSignature, VL, 4) + FIELD(Generator, VL, 5) + FIELD(Signature, VL, 6) + FIELD(Domain, VL, 7) + FIELD(FundCode, VL, 8) + FIELD(RemoveCode, VL, 9) + FIELD(ExpireCode, VL, 10) + FIELD(CreateCode, VL, 11) + + // account + FIELD(Account, ACCOUNT, 1) + FIELD(Owner, ACCOUNT, 2) + FIELD(Destination, ACCOUNT, 3) + FIELD(Issuer, ACCOUNT, 4) + FIELD(HighID, ACCOUNT, 5) + FIELD(LowID, ACCOUNT, 6) + FIELD(Target, ACCOUNT, 7) + FIELD(AuthorizedKey, ACCOUNT, 8) + + // path set + FIELD(Paths, PATHSET, 1) + + // vector of 256-bit + FIELD(Indexes, VECTOR256, 1) + + // inner object + // OBJECT/1 is reserved for end of object + + // array of objects + // ARRAY/1 is reserved for end of array + FIELD(SigningAccounts, ARRAY, 2) + FIELD(TxnSignatures, ARRAY, 3) + FIELD(Signatures, ARRAY, 4) diff --git a/src/SerializedLedger.cpp b/src/SerializedLedger.cpp index 372e9cd730..acc58da3c1 100644 --- a/src/SerializedLedger.cpp +++ b/src/SerializedLedger.cpp @@ -2,36 +2,47 @@ #include +#include "Ledger.h" +#include "Log.h" + SerializedLedgerEntry::SerializedLedgerEntry(SerializerIterator& sit, const uint256& index) - : SerializedType("LedgerEntry"), mIndex(index) + : STObject(sfLedgerEntry), mIndex(index) { - uint16 type = sit.get16(); - mFormat = getLgrFormat(static_cast(type)); - if (mFormat == NULL) throw std::runtime_error("invalid ledger entry type"); + set(sit); + uint16 type = getFieldU16(sfLedgerEntryType); + mFormat = LedgerEntryFormat::getLgrFormat(static_cast(type)); + if (mFormat == NULL) + throw std::runtime_error("invalid ledger entry type"); mType = mFormat->t_type; - mVersion.setValue(type); - mObject = STObject(mFormat->elements, sit); + if (!setType(mFormat->elements)) + throw std::runtime_error("ledger entry not valid for type"); } SerializedLedgerEntry::SerializedLedgerEntry(const Serializer& s, const uint256& index) - : SerializedType("LedgerEntry"), mIndex(index) + : STObject(sfLedgerEntry), mIndex(index) { SerializerIterator sit(s); + set(sit); - uint16 type = sit.get16(); - mFormat = getLgrFormat(static_cast(type)); - if (mFormat == NULL) throw std::runtime_error("invalid ledger entry type"); + uint16 type = getFieldU16(sfLedgerEntryType); + mFormat = LedgerEntryFormat::getLgrFormat(static_cast(type)); + if (mFormat == NULL) + throw std::runtime_error("invalid ledger entry type"); mType = mFormat->t_type; - mVersion.setValue(type); - mObject.set(mFormat->elements, sit); + if (!setType(mFormat->elements)) + { + Log(lsWARNING) << "Ledger entry not valid for type " << mFormat->t_name; + Log(lsWARNING) << getJson(0); + throw std::runtime_error("ledger entry not valid for type"); + } } -SerializedLedgerEntry::SerializedLedgerEntry(LedgerEntryType type) : SerializedType("LedgerEntry"), mType(type) +SerializedLedgerEntry::SerializedLedgerEntry(LedgerEntryType type) : STObject(sfLedgerEntry), mType(type) { - mFormat = getLgrFormat(type); + mFormat = LedgerEntryFormat::getLgrFormat(type); if (mFormat == NULL) throw std::runtime_error("invalid ledger entry type"); - mVersion.setValue(static_cast(mFormat->t_type)); - mObject.set(mFormat->elements); + set(mFormat->elements); + setFieldU16(sfLedgerEntryType, static_cast(mFormat->t_type)); } std::string SerializedLedgerEntry::getFullText() const @@ -41,36 +52,113 @@ std::string SerializedLedgerEntry::getFullText() const ret += "\" = { "; ret += mFormat->t_name; ret += ", "; - ret += mObject.getFullText(); + ret += STObject::getFullText(); ret += "}"; return ret; } std::string SerializedLedgerEntry::getText() const { - return str(boost::format("{ %s, %s, %s }") + return str(boost::format("{ %s, %s }") % mIndex.GetHex() - % mVersion.getText() - % mObject.getText()); + % STObject::getText()); } Json::Value SerializedLedgerEntry::getJson(int options) const { - Json::Value ret(mObject.getJson(options)); + Json::Value ret(STObject::getJson(options)); - ret["type"] = mFormat->t_name; ret["index"] = mIndex.GetHex(); - ret["version"] = std::string(1, mVersion); return ret; } -bool SerializedLedgerEntry::isEquivalent(const SerializedType& t) const -{ // locators are not compared - const SerializedLedgerEntry* v = dynamic_cast(&t); - if (!v) return false; - if (mType != v->mType) return false; - if (mObject != v->mObject) return false; +bool SerializedLedgerEntry::isThreadedType() +{ + return getFieldIndex(sfLastTxnID) != -1; +} + +bool SerializedLedgerEntry::isThreaded() +{ + return isFieldPresent(sfLastTxnID); +} + +uint256 SerializedLedgerEntry::getThreadedTransaction() +{ + return getFieldH256(sfLastTxnID); +} + +uint32 SerializedLedgerEntry::getThreadedLedger() +{ + return getFieldU32(sfLastTxnSeq); +} + +bool SerializedLedgerEntry::thread(const uint256& txID, uint32 ledgerSeq, uint256& prevTxID, uint32& prevLedgerID) +{ + uint256 oldPrevTxID = getFieldH256(sfLastTxnID); + Log(lsTRACE) << "Thread Tx:" << txID << " prev:" << oldPrevTxID; + if (oldPrevTxID == txID) + return false; + prevTxID = oldPrevTxID; + prevLedgerID = getFieldU32(sfLastTxnSeq); + assert(prevTxID != txID); + setFieldH256(sfLastTxnID, txID); + setFieldU32(sfLastTxnSeq, ledgerSeq); return true; } + +bool SerializedLedgerEntry::hasOneOwner() +{ + return (mType != ltACCOUNT_ROOT) && (getFieldIndex(sfAccount) != -1); +} + +bool SerializedLedgerEntry::hasTwoOwners() +{ + return mType == ltRIPPLE_STATE; +} + +NewcoinAddress SerializedLedgerEntry::getOwner() +{ + return getFieldAccount(sfAccount); +} + +NewcoinAddress SerializedLedgerEntry::getFirstOwner() +{ + return NewcoinAddress::createAccountID(getFieldAmount(sfLowLimit).getIssuer()); +} + +NewcoinAddress SerializedLedgerEntry::getSecondOwner() +{ + return NewcoinAddress::createAccountID(getFieldAmount(sfHighLimit).getIssuer()); +} + +std::vector SerializedLedgerEntry::getOwners() +{ + std::vector owners; + uint160 account; + + for (int i = 0, fields = getCount(); i < fields; ++i) + { + SField::ref fc = getFieldSType(i); + if ((fc == sfAccount) || (fc == sfOwner)) + { + const STAccount* entry = dynamic_cast(peekAtPIndex(i)); + if ((entry != NULL) && entry->getValueH160(account)) + owners.push_back(Ledger::getAccountRootIndex(account)); + } + if ((fc == sfLowLimit) || (fc == sfHighLimit)) + { + const STAmount* entry = dynamic_cast(peekAtPIndex(i)); + if ((entry != NULL)) + { + uint160 issuer = entry->getIssuer(); + if (issuer.isNonZero()) + owners.push_back(Ledger::getAccountRootIndex(issuer)); + } + } + } + + return owners; +} + // vim:ts=4 diff --git a/src/SerializedLedger.h b/src/SerializedLedger.h index 6df79d7e88..fb5bad0dc6 100644 --- a/src/SerializedLedger.h +++ b/src/SerializedLedger.h @@ -5,17 +5,16 @@ #include "LedgerFormats.h" #include "NewcoinAddress.h" -class SerializedLedgerEntry : public SerializedType +class SerializedLedgerEntry : public STObject { public: - typedef boost::shared_ptr pointer; + typedef boost::shared_ptr pointer; + typedef const boost::shared_ptr& ref; protected: - uint256 mIndex; - LedgerEntryType mType; - STUInt16 mVersion; - STObject mObject; - const LedgerEntryFormat* mFormat; + uint256 mIndex; + LedgerEntryType mType; + const LedgerEntryFormat* mFormat; SerializedLedgerEntry* duplicate() const { return new SerializedLedgerEntry(*this); } @@ -24,66 +23,29 @@ public: SerializedLedgerEntry(SerializerIterator& sit, const uint256& index); SerializedLedgerEntry(LedgerEntryType type); - int getLength() const { return mVersion.getLength() + mObject.getLength(); } SerializedTypeID getSType() const { return STI_LEDGERENTRY; } std::string getFullText() const; std::string getText() const; Json::Value getJson(int options) const; - void add(Serializer& s) const { mVersion.add(s); mObject.add(s); } - virtual bool isEquivalent(const SerializedType& t) const; - bool setFlag(uint32 uSet) { return mObject.setFlag(uSet); } - bool clearFlag(uint32 uClear) { return mObject.clearFlag(uClear); } - uint32 getFlags() const { return mObject.getFlags(); } - - const uint256& getIndex() const { return mIndex; } - void setIndex(const uint256& i) { mIndex = i; } + const uint256& getIndex() const { return mIndex; } + void setIndex(const uint256& i) { mIndex = i; } LedgerEntryType getType() const { return mType; } - uint16 getVersion() const { return mVersion.getValue(); } + uint16 getVersion() const { return getFieldU16(sfLedgerEntryType); } const LedgerEntryFormat* getFormat() { return mFormat; } - int getIFieldIndex(SOE_Field field) const { return mObject.getFieldIndex(field); } - int getIFieldCount() const { return mObject.getCount(); } - const SerializedType& peekIField(SOE_Field field) const { return mObject.peekAtField(field); } - SerializedType& getIField(SOE_Field field) { return mObject.getField(field); } - - std::string getIFieldString(SOE_Field field) const { return mObject.getFieldString(field); } - unsigned char getIFieldU8(SOE_Field field) const { return mObject.getValueFieldU8(field); } - uint16 getIFieldU16(SOE_Field field) const { return mObject.getValueFieldU16(field); } - uint32 getIFieldU32(SOE_Field field) const { return mObject.getValueFieldU32(field); } - uint64 getIFieldU64(SOE_Field field) const { return mObject.getValueFieldU64(field); } - uint128 getIFieldH128(SOE_Field field) const { return mObject.getValueFieldH128(field); } - uint160 getIFieldH160(SOE_Field field) const { return mObject.getValueFieldH160(field); } - uint256 getIFieldH256(SOE_Field field) const { return mObject.getValueFieldH256(field); } - std::vector getIFieldVL(SOE_Field field) const { return mObject.getValueFieldVL(field); } - std::vector getIFieldTL(SOE_Field field) const { return mObject.getValueFieldTL(field); } - NewcoinAddress getIValueFieldAccount(SOE_Field field) const { return mObject.getValueFieldAccount(field); } - STAmount getIValueFieldAmount(SOE_Field field) const { return mObject.getValueFieldAmount(field); } - STVector256 getIFieldV256(SOE_Field field) { return mObject.getValueFieldV256(field); } - - void setIFieldU8(SOE_Field field, unsigned char v) { return mObject.setValueFieldU8(field, v); } - void setIFieldU16(SOE_Field field, uint16 v) { return mObject.setValueFieldU16(field, v); } - void setIFieldU32(SOE_Field field, uint32 v) { return mObject.setValueFieldU32(field, v); } - void setIFieldU64(SOE_Field field, uint64 v) { return mObject.setValueFieldU64(field, v); } - void setIFieldH128(SOE_Field field, const uint128& v) { return mObject.setValueFieldH128(field, v); } - void setIFieldH160(SOE_Field field, const uint160& v) { return mObject.setValueFieldH160(field, v); } - void setIFieldH256(SOE_Field field, const uint256& v) { return mObject.setValueFieldH256(field, v); } - void setIFieldVL(SOE_Field field, const std::vector& v) - { return mObject.setValueFieldVL(field, v); } - void setIFieldTL(SOE_Field field, const std::vector& v) - { return mObject.setValueFieldTL(field, v); } - void setIFieldAccount(SOE_Field field, const uint160& account) - { return mObject.setValueFieldAccount(field, account); } - void setIFieldAccount(SOE_Field field, const NewcoinAddress& account) - { return mObject.setValueFieldAccount(field, account); } - void setIFieldAmount(SOE_Field field, const STAmount& amount) - { return mObject.setValueFieldAmount(field, amount); } - void setIFieldV256(SOE_Field field, const STVector256& v) { return mObject.setValueFieldV256(field, v); } - - bool getIFieldPresent(SOE_Field field) const { return mObject.isFieldPresent(field); } - void makeIFieldPresent(SOE_Field field) { mObject.makeFieldPresent(field); } - void makeIFieldAbsent(SOE_Field field) { return mObject.makeFieldAbsent(field); } + bool isThreadedType(); // is this a ledger entry that can be threaded + bool isThreaded(); // is this ledger entry actually threaded + bool hasOneOwner(); // This node has one other node that owns it (like nickname) + bool hasTwoOwners(); // This node has two nodes that own it (like ripple balance) + NewcoinAddress getOwner(); + NewcoinAddress getFirstOwner(); + NewcoinAddress getSecondOwner(); + uint256 getThreadedTransaction(); + uint32 getThreadedLedger(); + bool thread(const uint256& txID, uint32 ledgerSeq, uint256& prevTxID, uint32& prevLedgerID); + std::vector getOwners(); // nodes notified if this node is deleted }; typedef SerializedLedgerEntry SLE; diff --git a/src/SerializedObject.cpp b/src/SerializedObject.cpp index 6fbd45e63f..baf9ba1cbc 100644 --- a/src/SerializedObject.cpp +++ b/src/SerializedObject.cpp @@ -3,11 +3,18 @@ #include #include +#include #include "../json/writer.h" -std::auto_ptr STObject::makeDefaultObject(SerializedTypeID id, const char *name) +#include "Log.h" +#include "LedgerFormats.h" +#include "TransactionFormats.h" + +std::auto_ptr STObject::makeDefaultObject(SerializedTypeID id, SField::ref name) { + assert((id == STI_NOTPRESENT) || (id == name.fieldType)); + switch(id) { case STI_NOTPRESENT: @@ -40,22 +47,25 @@ std::auto_ptr STObject::makeDefaultObject(SerializedTypeID id, c case STI_VL: return std::auto_ptr(new STVariableLength(name)); - case STI_TL: - return std::auto_ptr(new STTaggedList(name)); - case STI_ACCOUNT: return std::auto_ptr(new STAccount(name)); case STI_PATHSET: return std::auto_ptr(new STPathSet(name)); + case STI_OBJECT: + return std::auto_ptr(new STObject(name)); + + case STI_ARRAY: + return std::auto_ptr(new STArray(name)); + default: throw std::runtime_error("Unknown object type"); } } -std::auto_ptr STObject::makeDeserializedObject(SerializedTypeID id, const char *name, - SerializerIterator& sit) +std::auto_ptr STObject::makeDeserializedObject(SerializedTypeID id, SField::ref name, + SerializerIterator& sit, int depth) { switch(id) { @@ -89,142 +99,190 @@ std::auto_ptr STObject::makeDeserializedObject(SerializedTypeID case STI_VL: return STVariableLength::deserialize(sit, name); - case STI_TL: - return STTaggedList::deserialize(sit, name); - case STI_ACCOUNT: return STAccount::deserialize(sit, name); case STI_PATHSET: return STPathSet::deserialize(sit, name); + case STI_ARRAY: + return STArray::deserialize(sit, name); + + case STI_OBJECT: + return STObject::deserialize(sit, name); + default: throw std::runtime_error("Unknown object type"); } } -void STObject::set(const SOElement* elem) +void STObject::set(const std::vector& type) { mData.empty(); mType.empty(); - mFlagIdx = -1; - while (elem->e_id != STI_DONE) + BOOST_FOREACH(const SOElement::ptr& elem, type) { - if (elem->e_type == SOE_FLAGS) mFlagIdx = mType.size(); mType.push_back(elem); - if (elem->e_type == SOE_IFFLAG) - giveObject(makeDefaultObject(STI_NOTPRESENT, elem->e_name)); + if (elem->flags == SOE_OPTIONAL) + giveObject(makeNonPresentObject(elem->e_field)); else - giveObject(makeDefaultObject(elem->e_id, elem->e_name)); - ++elem; + giveObject(makeDefaultObject(elem->e_field)); } } -STObject::STObject(const SOElement* elem, const char *name) : SerializedType(name) +bool STObject::setType(const std::vector &type) { - set(elem); -} + boost::ptr_vector newData; + bool valid = true; -void STObject::set(const SOElement* elem, SerializerIterator& sit) -{ - mData.empty(); mType.empty(); - mFlagIdx = -1; - - int flags = -1; - while (elem->e_id != STI_DONE) + BOOST_FOREACH(const SOElement::ptr& elem, type) { + bool match = false; + for (boost::ptr_vector::iterator it = mData.begin(); it != mData.end(); ++it) + if (it->getFName() == elem->e_field) + { + match = true; + newData.push_back(mData.release(it).release()); + break; + } + + if (!match) + { + if (elem->flags != SOE_OPTIONAL) + { + Log(lsTRACE) << "setType !valid missing"; + valid = false; + } + newData.push_back(makeNonPresentObject(elem->e_field)); + } + mType.push_back(elem); - bool done = false; - if (elem->e_type == SOE_IFFLAG) - { - assert(flags >= 0); - if ((flags&elem->e_flags) == 0) - { - done = true; - giveObject(makeDefaultObject(STI_NOTPRESENT, elem->e_name)); - } - } - else if (elem->e_type == SOE_IFNFLAG) - { - assert(flags >= 0); - if ((flags&elem->e_flags) != 0) - { - done = true; - giveObject(makeDefaultObject(elem->e_id, elem->e_name)); - } - } - else if (elem->e_type == SOE_FLAGS) - { - assert(elem->e_id == STI_UINT32); - flags = sit.get32(); - mFlagIdx = giveObject(new STUInt32(elem->e_name, flags)); - done = true; - } - if (!done) - giveObject(makeDeserializedObject(elem->e_id, elem->e_name, sit)); - elem++; } + if (mData.size() != 0) + { + Log(lsTRACE) << "setType !valid leftover"; + valid = false; + } + mData.swap(newData); + return valid; } -STObject::STObject(const SOElement* elem, SerializerIterator& sit, const char *name) - : SerializedType(name), mFlagIdx(-1) +bool STObject::isValidForType() { - set(elem, sit); + boost::ptr_vector::iterator it = mData.begin(); + BOOST_FOREACH(SOElement::ptr elem, mType) + { + if (it == mData.end()) + return false; + if (elem->e_field != it->getFName()) + return false; + ++it; + } + + return true; +} + +bool STObject::isFieldAllowed(SField::ref field) +{ + BOOST_FOREACH(SOElement::ptr elem, mType) + { // are any required elemnents missing + if (elem->e_field == field) + return true; + } + return false; +} + +bool STObject::set(SerializerIterator& sit, int depth) +{ // return true = terminated with end-of-object + mData.empty(); + while (!sit.empty()) + { + int type, field; + sit.getFieldID(type, field); + if ((type == STI_OBJECT) && (field == 1)) // end of object indicator + return true; + SField::ref fn = SField::getField(type, field); + if (fn.isInvalid()) + throw std::runtime_error("Unknown field"); + giveObject(makeDeserializedObject(fn.fieldType, fn, sit, depth + 1)); + } + return false; +} + +std::auto_ptr STObject::deserialize(SerializerIterator& sit, SField::ref name) +{ + STObject *o; + std::auto_ptr object(o = new STObject(name)); + o->set(sit, 1); + return object; } std::string STObject::getFullText() const { std::string ret; bool first = true; - if (name != NULL) + if (fName->hasName()) { - ret = name; + ret = fName->getName(); ret += " = {"; } else ret = "{"; - for (boost::ptr_vector::const_iterator it = mData.begin(), end = mData.end(); it != end; ++it) - { - if (it->getSType() != STI_NOTPRESENT) + BOOST_FOREACH(const SerializedType& it, mData) + if (it.getSType() != STI_NOTPRESENT) { if (!first) ret += ", "; else first = false; - ret += it->getFullText(); + ret += it.getFullText(); } - } ret += "}"; return ret; } -int STObject::getLength() const +void STObject::add(Serializer& s, bool withSigningFields) const { - int ret = 0; - for (boost::ptr_vector::const_iterator it = mData.begin(), end = mData.end(); it != end; ++it) - ret += it->getLength(); - return ret; -} + std::map fields; -void STObject::add(Serializer& s) const -{ - for (boost::ptr_vector::const_iterator it = mData.begin(), end = mData.end(); it != end; ++it) - it->add(s); + BOOST_FOREACH(const SerializedType& it, mData) + { // pick out the fields and sort them + if (it.getSType() != STI_NOTPRESENT) + { + SField::ref fName = it.getFName(); + if (withSigningFields || ((fName != sfTxnSignature) && (fName != sfTxnSignatures))) + fields.insert(std::make_pair(it.getFName().fieldCode, &it)); + } + } + + + typedef std::pair field_iterator; + BOOST_FOREACH(field_iterator& it, fields) + { // insert them in sorted order + const SerializedType* field = it.second; + + field->addFieldID(s); + field->add(s); + if (dynamic_cast(field) != NULL) + s.addFieldID(STI_ARRAY, 1); + else if (dynamic_cast(field) != NULL) + s.addFieldID(STI_OBJECT, 1); + } } std::string STObject::getText() const { std::string ret = "{"; bool first = false; - for (boost::ptr_vector::const_iterator it = mData.begin(), end = mData.end(); it != end; ++it) + BOOST_FOREACH(const SerializedType& it, mData) { if (!first) { ret += ", "; first = false; } - ret += it->getText(); + ret += it.getText(); } ret += "}"; return ret; @@ -233,7 +291,8 @@ std::string STObject::getText() const bool STObject::isEquivalent(const SerializedType& t) const { const STObject* v = dynamic_cast(&t); - if (!v) return false; + if (!v) + return false; boost::ptr_vector::const_iterator it1 = mData.begin(), end1 = mData.end(); boost::ptr_vector::const_iterator it2 = v->mData.begin(), end2 = v->mData.end(); while ((it1 != end1) && (it2 != end2)) @@ -246,119 +305,153 @@ bool STObject::isEquivalent(const SerializedType& t) const return (it1 == end1) && (it2 == end2); } -int STObject::getFieldIndex(SOE_Field field) const +uint256 STObject::getHash(uint32 prefix) const +{ + Serializer s; + s.add32(prefix); + add(s, true); + return s.getSHA512Half(); +} + +uint256 STObject::getSigningHash(uint32 prefix) const +{ + Serializer s; + s.add32(prefix); + add(s, false); + return s.getSHA512Half(); +} + +int STObject::getFieldIndex(SField::ref field) const { int i = 0; - for (std::vector::const_iterator it = mType.begin(), end = mType.end(); it != end; ++it, ++i) - if ((*it)->e_field == field) return i; + BOOST_FOREACH(const SerializedType& elem, mData) + { + if (elem.getFName() == field) + return i; + ++i; + } return -1; } -const SerializedType& STObject::peekAtField(SOE_Field field) const +const SerializedType& STObject::peekAtField(SField::ref field) const { int index = getFieldIndex(field); - if (index == -1) throw std::runtime_error("Field not found"); + if (index == -1) + throw std::runtime_error("Field not found"); return peekAtIndex(index); } -SerializedType& STObject::getField(SOE_Field field) +SerializedType& STObject::getField(SField::ref field) { int index = getFieldIndex(field); - if (index==-1) throw std::runtime_error("Field not found"); + if (index == -1) + throw std::runtime_error("Field not found"); return getIndex(index); } -const SerializedType* STObject::peekAtPField(SOE_Field field) const +SField::ref STObject::getFieldSType(int index) const +{ + return mData[index].getFName(); +} + +const SerializedType* STObject::peekAtPField(SField::ref field) const { int index = getFieldIndex(field); - if (index == -1) return NULL; + if (index == -1) + return NULL; return peekAtPIndex(index); } -SerializedType* STObject::getPField(SOE_Field field) +SerializedType* STObject::getPField(SField::ref field) { int index = getFieldIndex(field); - if (index == -1) return NULL; + if (index == -1) + return NULL; return getPIndex(index); } -bool STObject::isFieldPresent(SOE_Field field) const +bool STObject::isFieldPresent(SField::ref field) const { int index = getFieldIndex(field); - if (index == -1) return false; + if (index == -1) + return false; return peekAtIndex(index).getSType() != STI_NOTPRESENT; } bool STObject::setFlag(uint32 f) { - if (mFlagIdx < 0) return false; - STUInt32* t = dynamic_cast(getPIndex(mFlagIdx)); - assert(t); + STUInt32* t = dynamic_cast(getPField(sfFlags)); + if (!t) + return false; t->setValue(t->getValue() | f); return true; } bool STObject::clearFlag(uint32 f) { - if (mFlagIdx < 0) return false; - STUInt32* t = dynamic_cast(getPIndex(mFlagIdx)); - assert(t); + STUInt32* t = dynamic_cast(getPField(sfFlags)); + if (!t) + return false; t->setValue(t->getValue() & ~f); return true; } uint32 STObject::getFlags(void) const { - if (mFlagIdx < 0) return 0; - const STUInt32* t = dynamic_cast(peekAtPIndex(mFlagIdx)); - assert(t); + const STUInt32* t = dynamic_cast(peekAtPField(sfFlags)); + if (!t) + return 0; return t->getValue(); } -SerializedType* STObject::makeFieldPresent(SOE_Field field) +SerializedType* STObject::makeFieldPresent(SField::ref field) { int index = getFieldIndex(field); - if (index == -1) throw std::runtime_error("Field not found"); - if ((mType[index]->e_type != SOE_IFFLAG) && (mType[index]->e_type != SOE_IFNFLAG)) - throw std::runtime_error("field is not optional"); + if (index == -1) + throw std::runtime_error("Field not found"); SerializedType* f = getPIndex(index); - if (f->getSType() != STI_NOTPRESENT) return f; - mData.replace(index, makeDefaultObject(mType[index]->e_id, mType[index]->e_name)); - f = getPIndex(index); - - if (mType[index]->e_type == SOE_IFFLAG) - setFlag(mType[index]->e_flags); - else if (mType[index]->e_type == SOE_IFNFLAG) - clearFlag(mType[index]->e_flags); - - return f; + if (f->getSType() != STI_NOTPRESENT) + return f; + mData.replace(index, makeDefaultObject(f->getFName())); + return getPIndex(index); } -void STObject::makeFieldAbsent(SOE_Field field) +void STObject::makeFieldAbsent(SField::ref field) { int index = getFieldIndex(field); - if (index == -1) throw std::runtime_error("Field not found"); - if ((mType[index]->e_type != SOE_IFFLAG) && (mType[index]->e_type != SOE_IFNFLAG)) - throw std::runtime_error("field is not optional"); + if (index == -1) + throw std::runtime_error("Field not found"); - if (peekAtIndex(index).getSType() == STI_NOTPRESENT) return; - mData.replace(index, new SerializedType(mType[index]->e_name)); + const SerializedType& f = peekAtIndex(index); + if (f.getSType() == STI_NOTPRESENT) + return; - if (mType[index]->e_type == SOE_IFFLAG) - clearFlag(mType[index]->e_flags); - else if (mType[index]->e_type == SOE_IFNFLAG) - setFlag(mType[index]->e_flags); + mData.replace(index, makeDefaultObject(f.getFName())); } -std::string STObject::getFieldString(SOE_Field field) const +bool STObject::delField(SField::ref field) +{ + int index = getFieldIndex(field); + if (index == -1) + return false; + delField(index); + return true; +} + +void STObject::delField(int index) +{ + mData.erase(mData.begin() + index); +} + +std::string STObject::getFieldString(SField::ref field) const { const SerializedType* rf = peekAtPField(field); if (!rf) throw std::runtime_error("Field not found"); return rf->getText(); } -unsigned char STObject::getValueFieldU8(SOE_Field field) const +unsigned char STObject::getFieldU8(SField::ref field) const { const SerializedType* rf = peekAtPField(field); if (!rf) throw std::runtime_error("Field not found"); @@ -369,7 +462,7 @@ unsigned char STObject::getValueFieldU8(SOE_Field field) const return cf->getValue(); } -uint16 STObject::getValueFieldU16(SOE_Field field) const +uint16 STObject::getFieldU16(SField::ref field) const { const SerializedType* rf = peekAtPField(field); if (!rf) throw std::runtime_error("Field not found"); @@ -380,7 +473,7 @@ uint16 STObject::getValueFieldU16(SOE_Field field) const return cf->getValue(); } -uint32 STObject::getValueFieldU32(SOE_Field field) const +uint32 STObject::getFieldU32(SField::ref field) const { const SerializedType* rf = peekAtPField(field); if (!rf) throw std::runtime_error("Field not found"); @@ -391,7 +484,7 @@ uint32 STObject::getValueFieldU32(SOE_Field field) const return cf->getValue(); } -uint64 STObject::getValueFieldU64(SOE_Field field) const +uint64 STObject::getFieldU64(SField::ref field) const { const SerializedType* rf = peekAtPField(field); if (!rf) throw std::runtime_error("Field not found"); @@ -402,7 +495,7 @@ uint64 STObject::getValueFieldU64(SOE_Field field) const return cf->getValue(); } -uint128 STObject::getValueFieldH128(SOE_Field field) const +uint128 STObject::getFieldH128(SField::ref field) const { const SerializedType* rf = peekAtPField(field); if (!rf) throw std::runtime_error("Field not found"); @@ -413,7 +506,7 @@ uint128 STObject::getValueFieldH128(SOE_Field field) const return cf->getValue(); } -uint160 STObject::getValueFieldH160(SOE_Field field) const +uint160 STObject::getFieldH160(SField::ref field) const { const SerializedType* rf = peekAtPField(field); if (!rf) throw std::runtime_error("Field not found"); @@ -424,7 +517,7 @@ uint160 STObject::getValueFieldH160(SOE_Field field) const return cf->getValue(); } -uint256 STObject::getValueFieldH256(SOE_Field field) const +uint256 STObject::getFieldH256(SField::ref field) const { const SerializedType* rf = peekAtPField(field); if (!rf) throw std::runtime_error("Field not found"); @@ -435,7 +528,7 @@ uint256 STObject::getValueFieldH256(SOE_Field field) const return cf->getValue(); } -NewcoinAddress STObject::getValueFieldAccount(SOE_Field field) const +NewcoinAddress STObject::getFieldAccount(SField::ref field) const { const SerializedType* rf = peekAtPField(field); if (!rf) @@ -453,7 +546,29 @@ NewcoinAddress STObject::getValueFieldAccount(SOE_Field field) const return cf->getValueNCA(); } -std::vector STObject::getValueFieldVL(SOE_Field field) const +uint160 STObject::getFieldAccount160(SField::ref field) const +{ + uint160 a; + const SerializedType* rf = peekAtPField(field); + if (!rf) + { +#ifdef DEBUG + std::cerr << "Account field not found" << std::endl; + std::cerr << getFullText() << std::endl; +#endif + throw std::runtime_error("Field not found"); + } + SerializedTypeID id = rf->getSType(); + if (id != STI_NOTPRESENT) + { + const STAccount* cf = dynamic_cast(rf); + if (!cf) throw std::runtime_error("Wrong field type"); + cf->getValueH160(a); + } + return a; +} + +std::vector STObject::getFieldVL(SField::ref field) const { const SerializedType* rf = peekAtPField(field); if (!rf) throw std::runtime_error("Field not found"); @@ -464,18 +579,7 @@ std::vector STObject::getValueFieldVL(SOE_Field field) const return cf->getValue(); } -std::vector STObject::getValueFieldTL(SOE_Field field) const -{ - const SerializedType* rf = peekAtPField(field); - if (!rf) throw std::runtime_error("Field not found"); - SerializedTypeID id = rf->getSType(); - if (id == STI_NOTPRESENT) return std::vector(); // optional field not present - const STTaggedList *cf = dynamic_cast(rf); - if (!cf) throw std::runtime_error("Wrong field type"); - return cf->getValue(); -} - -STAmount STObject::getValueFieldAmount(SOE_Field field) const +STAmount STObject::getFieldAmount(SField::ref field) const { const SerializedType* rf = peekAtPField(field); if (!rf) throw std::runtime_error("Field not found"); @@ -486,7 +590,7 @@ STAmount STObject::getValueFieldAmount(SOE_Field field) const return *cf; } -STPathSet STObject::getValueFieldPathSet(SOE_Field field) const +STPathSet STObject::getFieldPathSet(SField::ref field) const { const SerializedType* rf = peekAtPField(field); if (!rf) throw std::runtime_error("Field not found"); @@ -497,7 +601,7 @@ STPathSet STObject::getValueFieldPathSet(SOE_Field field) const return *cf; } -STVector256 STObject::getValueFieldV256(SOE_Field field) const +STVector256 STObject::getFieldV256(SField::ref field) const { const SerializedType* rf = peekAtPField(field); if (!rf) throw std::runtime_error("Field not found"); @@ -508,7 +612,7 @@ STVector256 STObject::getValueFieldV256(SOE_Field field) const return *cf; } -void STObject::setValueFieldU8(SOE_Field field, unsigned char v) +void STObject::setFieldU8(SField::ref field, unsigned char v) { SerializedType* rf = getPField(field); if (!rf) throw std::runtime_error("Field not found"); @@ -518,7 +622,7 @@ void STObject::setValueFieldU8(SOE_Field field, unsigned char v) cf->setValue(v); } -void STObject::setValueFieldU16(SOE_Field field, uint16 v) +void STObject::setFieldU16(SField::ref field, uint16 v) { SerializedType* rf = getPField(field); if (!rf) throw std::runtime_error("Field not found"); @@ -528,7 +632,7 @@ void STObject::setValueFieldU16(SOE_Field field, uint16 v) cf->setValue(v); } -void STObject::setValueFieldU32(SOE_Field field, uint32 v) +void STObject::setFieldU32(SField::ref field, uint32 v) { SerializedType* rf = getPField(field); if (!rf) throw std::runtime_error("Field not found"); @@ -538,7 +642,7 @@ void STObject::setValueFieldU32(SOE_Field field, uint32 v) cf->setValue(v); } -void STObject::setValueFieldU64(SOE_Field field, uint64 v) +void STObject::setFieldU64(SField::ref field, uint64 v) { SerializedType* rf = getPField(field); if (!rf) throw std::runtime_error("Field not found"); @@ -548,7 +652,7 @@ void STObject::setValueFieldU64(SOE_Field field, uint64 v) cf->setValue(v); } -void STObject::setValueFieldH128(SOE_Field field, const uint128& v) +void STObject::setFieldH128(SField::ref field, const uint128& v) { SerializedType* rf = getPField(field); if (!rf) throw std::runtime_error("Field not found"); @@ -558,7 +662,7 @@ void STObject::setValueFieldH128(SOE_Field field, const uint128& v) cf->setValue(v); } -void STObject::setValueFieldH160(SOE_Field field, const uint160& v) +void STObject::setFieldH160(SField::ref field, const uint160& v) { SerializedType* rf = getPField(field); if (!rf) throw std::runtime_error("Field not found"); @@ -568,7 +672,7 @@ void STObject::setValueFieldH160(SOE_Field field, const uint160& v) cf->setValue(v); } -void STObject::setValueFieldH256(SOE_Field field, const uint256& v) +void STObject::setFieldH256(SField::ref field, const uint256& v) { SerializedType* rf = getPField(field); if (!rf) throw std::runtime_error("Field not found"); @@ -578,7 +682,7 @@ void STObject::setValueFieldH256(SOE_Field field, const uint256& v) cf->setValue(v); } -void STObject::setValueFieldV256(SOE_Field field, const STVector256& v) +void STObject::setFieldV256(SField::ref field, const STVector256& v) { SerializedType* rf = getPField(field); if (!rf) throw std::runtime_error("Field not found"); @@ -588,7 +692,7 @@ void STObject::setValueFieldV256(SOE_Field field, const STVector256& v) cf->setValue(v); } -void STObject::setValueFieldAccount(SOE_Field field, const uint160& v) +void STObject::setFieldAccount(SField::ref field, const uint160& v) { SerializedType* rf = getPField(field); if (!rf) throw std::runtime_error("Field not found"); @@ -598,7 +702,7 @@ void STObject::setValueFieldAccount(SOE_Field field, const uint160& v) cf->setValueH160(v); } -void STObject::setValueFieldVL(SOE_Field field, const std::vector& v) +void STObject::setFieldVL(SField::ref field, const std::vector& v) { SerializedType* rf = getPField(field); if (!rf) throw std::runtime_error("Field not found"); @@ -608,17 +712,7 @@ void STObject::setValueFieldVL(SOE_Field field, const std::vector cf->setValue(v); } -void STObject::setValueFieldTL(SOE_Field field, const std::vector& v) -{ - SerializedType* rf = getPField(field); - if (!rf) throw std::runtime_error("Field not found"); - if (rf->getSType() == STI_NOTPRESENT) rf = makeFieldPresent(field); - STTaggedList* cf = dynamic_cast(rf); - if (!cf) throw std::runtime_error("Wrong field type"); - cf->setValue(v); -} - -void STObject::setValueFieldAmount(SOE_Field field, const STAmount &v) +void STObject::setFieldAmount(SField::ref field, const STAmount &v) { SerializedType* rf = getPField(field); if (!rf) throw std::runtime_error("Field not found"); @@ -628,7 +722,7 @@ void STObject::setValueFieldAmount(SOE_Field field, const STAmount &v) (*cf) = v; } -void STObject::setValueFieldPathSet(SOE_Field field, const STPathSet &v) +void STObject::setFieldPathSet(SField::ref field, const STPathSet &v) { SerializedType* rf = getPField(field); if (!rf) throw std::runtime_error("Field not found"); @@ -642,14 +736,14 @@ Json::Value STObject::getJson(int options) const { Json::Value ret(Json::objectValue); int index = 1; - for(boost::ptr_vector::const_iterator it = mData.begin(), end = mData.end(); it != end; - ++it, ++index) + BOOST_FOREACH(const SerializedType& it, mData) { - if (it->getSType() != STI_NOTPRESENT) + if (it.getSType() != STI_NOTPRESENT) { - if (it->getName() == NULL) - ret[boost::lexical_cast(index)] = it->getJson(options); - else ret[it->getName()] = it->getJson(options); + if (!it.getFName().hasName()) + ret[lexical_cast_i(index)] = it.getJson(options); + else + ret[it.getName()] = it.getJson(options); } } return ret; @@ -660,13 +754,323 @@ Json::Value STVector256::getJson(int options) const Json::Value ret(Json::arrayValue); BOOST_FOREACH(std::vector::const_iterator::value_type vEntry, mValue) - { ret.append(vEntry.ToString()); - } return ret; } +std::string STArray::getFullText() const +{ + return "WRITEME"; +} + +std::string STArray::getText() const +{ + return "WRITEME"; +} + +Json::Value STArray::getJson(int) const +{ + return Json::Value("WRITEME"); +} + +void STArray::add(Serializer& s) const +{ + BOOST_FOREACH(const STObject& object, value) + { + object.addFieldID(s); + object.add(s); + s.addFieldID(STI_OBJECT, 1); + } +} + +bool STArray::isEquivalent(const SerializedType& t) const +{ + const STArray* v = dynamic_cast(&t); + if (!v) + return false; + return value == v->value; +} + +STArray* STArray::construct(SerializerIterator& sit, SField::ref field) +{ + vector value; + + while (!sit.empty()) + { + int type, field; + sit.getFieldID(type, field); + if ((type == STI_ARRAY) && (field == 1)) + break; + + SField::ref fn = SField::getField(type, field); + if (fn.isInvalid()) + throw std::runtime_error("Unknown field"); + + value.push_back(STObject(fn)); + value.rbegin()->set(sit, 1); + } + + return new STArray(field, value); +} + +std::auto_ptr STObject::parseJson(const Json::Value& object, SField::ref inName, int depth) +{ + if (!object.isObject()) + throw std::runtime_error("Value is not an object"); + + SField::ptr name = &inName; + + boost::ptr_vector data; + Json::Value::Members members(object.getMemberNames()); + for (Json::Value::Members::iterator it = members.begin(), end = members.end(); it != end; ++it) + { + const std::string& fieldName = *it; + const Json::Value& value = object[fieldName]; + + SField::ref field = SField::getField(fieldName); + if (field == sfInvalid) + throw std::runtime_error("Unknown field: " + fieldName); + + switch (field.fieldType) + { + case STI_UINT8: + if (value.isString()) + data.push_back(new STUInt8(field, lexical_cast_st(value.asString()))); + else if (value.isInt()) + { + if (value.asInt() < 0 || value.asInt() > 255) + throw std::runtime_error("value out of rand"); + data.push_back(new STUInt8(field, range_check_cast(value.asInt(), 0, 255))); + } + else if (value.isUInt()) + { + if (value.asUInt() > 255) + throw std::runtime_error("value out of rand"); + data.push_back(new STUInt8(field, range_check_cast(value.asUInt(), 0, 255))); + } + else + throw std::runtime_error("Incorrect type"); + break; + + case STI_UINT16: + if (value.isString()) + { + std::string strValue = value.asString(); + if (!strValue.empty() && ((strValue[0] < '0') || (strValue[0] > '9'))) + { + if (field == sfTransactionType) + { + TransactionFormat* f = TransactionFormat::getTxnFormat(strValue); + if (!f) + throw std::runtime_error("Unknown transaction type"); + data.push_back(new STUInt16(field, static_cast(f->t_type))); + if (*name == sfGeneric) + name = &sfTransaction; + } + else if (field == sfLedgerEntryType) + { + LedgerEntryFormat* f = LedgerEntryFormat::getLgrFormat(strValue); + if (!f) + throw std::runtime_error("Unknown ledger entry type"); + data.push_back(new STUInt16(field, static_cast(f->t_type))); + if (*name == sfGeneric) + name = &sfLedgerEntry; + } + else + throw std::runtime_error("Invalid field data"); + } + else + data.push_back(new STUInt16(field, lexical_cast_st(strValue))); + } + else if (value.isInt()) + data.push_back(new STUInt16(field, range_check_cast(value.asInt(), 0, 65535))); + else if (value.isUInt()) + data.push_back(new STUInt16(field, range_check_cast(value.asUInt(), 0, 65535))); + else + throw std::runtime_error("Incorrect type"); + break; + + case STI_UINT32: + if (value.isString()) + data.push_back(new STUInt32(field, lexical_cast_st(value.asString()))); + else if (value.isInt()) + data.push_back(new STUInt32(field, range_check_cast(value.asInt(), 0, 4294967295))); + else if (value.isUInt()) + data.push_back(new STUInt32(field, static_cast(value.asUInt()))); + else + throw std::runtime_error("Incorrect type"); + break; + + case STI_UINT64: + if (value.isString()) + data.push_back(new STUInt64(field, lexical_cast_st(value.asString()))); + else if (value.isInt()) + data.push_back(new STUInt64(field, + range_check_cast(value.asInt(), 0, 18446744073709551615ull))); + else if (value.isUInt()) + data.push_back(new STUInt64(field, static_cast(value.asUInt()))); + else + throw std::runtime_error("Incorrect type"); + break; + + + case STI_HASH128: + if (value.isString()) + data.push_back(new STHash128(field, value.asString())); + else + throw std::runtime_error("Incorrect type"); + break; + + case STI_HASH160: + if (value.isString()) + data.push_back(new STHash160(field, value.asString())); + else + throw std::runtime_error("Incorrect type"); + break; + + case STI_HASH256: + if (value.isString()) + data.push_back(new STHash256(field, value.asString())); + else + throw std::runtime_error("Incorrect type"); + break; + + case STI_VL: + if (!value.isString()) + data.push_back(new STVariableLength(field, strUnHex(value.asString()))); + break; + + case STI_AMOUNT: + data.push_back(new STAmount(field, value)); + break; + + case STI_VECTOR256: + if (!value.isArray()) + throw std::runtime_error("Incorrect type"); + { + data.push_back(new STVector256(field)); + STVector256* tail = dynamic_cast(&data.back()); + assert(tail); + for (Json::UInt i = 0; !object.isValidIndex(i); ++i) + { + uint256 s; + s.SetHex(object[i].asString()); + tail->addValue(s); + } + } + break; + + case STI_PATHSET: + if (!value.isArray()) + throw std::runtime_error("Path set must be array"); + { + data.push_back(new STPathSet(field)); + STPathSet* tail = dynamic_cast(&data.back()); + assert(tail); + for (Json::UInt i = 0; !object.isValidIndex(i); ++i) + { + STPath p; + if (!object[i].isArray()) + throw std::runtime_error("Path must be array"); + for (Json::UInt j = 0; !object[i].isValidIndex(j); ++j) + { // each element in this path has some combination of account, currency, or issuer + + Json::Value pathEl = object[i][j]; + if (!pathEl.isObject()) + throw std::runtime_error("Path elements must be objects"); + const Json::Value& account = pathEl["account"]; + const Json::Value& currency = pathEl["currency"]; + const Json::Value& issuer = pathEl["issuer"]; + + uint160 uAccount, uCurrency, uIssuer; + bool hasCurrency; + if (!account.isNull()) + { // human account id + if (!account.isString()) + throw std::runtime_error("path element accounts must be strings"); + std::string strValue = account.asString(); + if (value.size() == 40) // 160-bit hex account value + uAccount.SetHex(strValue); + { + NewcoinAddress a; + if (!a.setAccountPublic(strValue)) + throw std::runtime_error("Account in path element invalid"); + uAccount = a.getAccountID(); + } + } + if (!currency.isNull()) + { // human currency + if (!currency.isString()) + throw std::runtime_error("path element currencies must be strings"); + hasCurrency = true; + // WRITEME + } + if (!issuer.isNull()) + { // human account id + if (!issuer.isString()) + throw std::runtime_error("path element issuers must be strings"); + // WRITEME + } + p.addElement(STPathElement(uAccount, uCurrency, uIssuer, hasCurrency)); + } + tail->addPath(p); + } + } + break; + + case STI_ACCOUNT: + { + if (!value.isString()) + throw std::runtime_error("Incorrect type"); + std::string strValue = value.asString(); + if (value.size() == 40) // 160-bit hex account value + { + uint160 v; + v.SetHex(strValue); + data.push_back(new STAccount(field, v)); + } + else + { // newcoin addres + NewcoinAddress a; + if (!a.setAccountPublic(strValue)) + throw std::runtime_error("Account invalid"); + data.push_back(new STAccount(field, a.getAccountID())); + } + } + break; + + case STI_OBJECT: + case STI_TRANSACTION: + case STI_LEDGERENTRY: + case STI_VALIDATION: + if (!value.isObject()) + throw std::runtime_error("Inner value is not an object"); + if (depth > 64) + throw std::runtime_error("Json nest depth exceeded"); + data.push_back(parseJson(value, field, depth + 1)); + break; + + case STI_ARRAY: + if (!value.isArray()) + throw std::runtime_error("Inner value is not an array"); + { + data.push_back(new STArray(field)); + STArray* tail = dynamic_cast(&data.back()); + assert(tail); + for (Json::UInt i = 0; !object.isValidIndex(i); ++i) + tail->push_back(*STObject::parseJson(object[i], sfGeneric, depth + 1)); + } + + default: + throw std::runtime_error("Invalid field type"); + } + } + return std::auto_ptr(new STObject(*name, data)); +} + +#if 0 + static SOElement testSOElements[2][16] = { // field, name, id, type, flags { @@ -691,7 +1095,7 @@ void STObject::unitTest() if (!object1.isFieldPresent(sfTest2)) throw std::runtime_error("STObject Error"); if ((object1.getFlags() != 1) || (object2.getFlags() != 0)) throw std::runtime_error("STObject error"); - if (object1.getValueFieldH256(sfTest2) != uint256()) throw std::runtime_error("STObject error"); + if (object1.getFieldH256(sfTest2) != uint256()) throw std::runtime_error("STObject error"); if (object1.getSerializer() == object2.getSerializer()) throw std::runtime_error("STObject error"); object1.makeFieldAbsent(sfTest2); @@ -703,28 +1107,29 @@ void STObject::unitTest() if (object1.isFieldPresent(sfTest2)) throw std::runtime_error("STObject error"); if (copy.isFieldPresent(sfTest2)) throw std::runtime_error("STObject error"); if (object1.getSerializer() != copy.getSerializer()) throw std::runtime_error("STObject error"); - copy.setValueFieldU32(sfTest3, 1); + copy.setFieldU32(sfTest3, 1); if (object1.getSerializer() == copy.getSerializer()) throw std::runtime_error("STObject error"); #ifdef DEBUG - Json::StyledStreamWriter ssw; - ssw.write(std::cerr, copy.getJson(0)); + Log(lsDEBUG) << copy.getJson(0); #endif for (int i = 0; i < 1000; i++) { std::cerr << "tol: i=" << i << std::endl; std::vector j(i, 2); - object1.setValueFieldVL(sfTest1, j); + object1.setFieldVL(sfTest1, j); Serializer s; object1.add(s); SerializerIterator it(s); STObject object3(testSOElements[0], it, "TestElement3"); - if (object1.getValueFieldVL(sfTest1) != j) throw std::runtime_error("STObject error"); - if (object3.getValueFieldVL(sfTest1) != j) throw std::runtime_error("STObject error"); + if (object1.getFieldVL(sfTest1) != j) throw std::runtime_error("STObject error"); + if (object3.getFieldVL(sfTest1) != j) throw std::runtime_error("STObject error"); } } +#endif + // vim:ts=4 diff --git a/src/SerializedObject.h b/src/SerializedObject.h index 0480266bee..ed8d06560e 100644 --- a/src/SerializedObject.h +++ b/src/SerializedObject.h @@ -9,145 +9,71 @@ #include "SerializedTypes.h" -enum SOE_Type -{ - SOE_NEVER = -1, // never occurs (marks end of object) - SOE_REQUIRED = 0, // required - SOE_FLAGS = 1, // flags field - SOE_IFFLAG = 2, // present if flag set - SOE_IFNFLAG = 3 // present if flag not set -}; +// Serializable object/array types -enum SOE_Field -{ - sfInvalid = -1, - sfGeneric = 0, - - // common fields - sfAcceptExpire, - sfAcceptRate, - sfAcceptStart, - sfAccount, - sfAccountID, - sfAmount, - sfAuthorizedKey, - sfBalance, - sfBookDirectory, - sfBookNode, - sfBorrowExpire, - sfBorrowRate, - sfBorrowStart, - sfBorrower, - sfCloseTime, - sfCurrency, - sfCurrencyIn, - sfCurrencyOut, - sfDestination, - sfDomain, - sfEmailHash, - sfExpiration, - sfExtensions, - sfFirstNode, - sfFlags, - sfGenerator, - sfGeneratorID, - sfGetsIssuer, - sfHash, - sfHighID, - sfHighLimit, - sfHighQualityIn, - sfHighQualityOut, - sfIdentifier, - sfIndexes, - sfIndexNext, - sfIndexPrevious, - sfInvoiceID, - sfLastNode, - sfLastReceive, - sfLastTxn, - sfLedgerHash, - sfLimitAmount, - sfLowID, - sfLowLimit, - sfLowQualityIn, - sfLowQualityOut, - sfMessageKey, - sfMinimumOffer, - sfNextAcceptExpire, - sfNextAcceptRate, - sfNextAcceptStart, - sfNextTransitExpire, - sfNextTransitRate, - sfNextTransitStart, - sfNickname, - sfOfferSequence, - sfOwnerNode, - sfPaths, - sfPaysIssuer, - sfPubKey, - sfPublishHash, - sfPublishSize, - sfQualityIn, - sfQualityOut, - sfSendMax, - sfSequence, - sfSignature, - sfSigningKey, - sfSourceTag, - sfTakerGets, - sfTakerPays, - sfTarget, - sfTargetLedger, - sfTransferRate, - sfVersion, - sfWalletLocator, - - // test fields - sfTest1, sfTest2, sfTest3, sfTest4 -}; - -struct SOElement +class SOElement { // An element in the description of a serialized object - SOE_Field e_field; - const char *e_name; - SerializedTypeID e_id; - SOE_Type e_type; - int e_flags; +public: + typedef SOElement const * ptr; // used to point to one element + + SField::ref e_field; + const SOE_Flags flags; + + SOElement(SField::ref fi, SOE_Flags fl) : e_field(fi), flags(fl) { ; } }; class STObject : public SerializedType { protected: - int mFlagIdx; // the offset to the flags object, -1 if none boost::ptr_vector mData; - std::vector mType; + std::vector mType; STObject* duplicate() const { return new STObject(*this); } + STObject(SField::ref name, boost::ptr_vector& data) : SerializedType(name) { mData.swap(data); } public: - STObject(const char *n = NULL) : SerializedType(n), mFlagIdx(-1) { ; } - STObject(const SOElement *t, const char *n = NULL); - STObject(const SOElement *t, SerializerIterator& u, const char *n = NULL); + STObject() { ; } + + STObject(SField::ref name) : SerializedType(name) { ; } + + STObject(const std::vector& type, SField::ref name) : SerializedType(name) + { set(type); } + + STObject(const std::vector& type, SerializerIterator& sit, SField::ref name) : SerializedType(name) + { set(sit); setType(type); } + + static std::auto_ptr parseJson(const Json::Value& value, SField::ref name = sfGeneric, int depth = 0); + virtual ~STObject() { ; } - void set(const SOElement* t); - void set(const SOElement* t, SerializerIterator& u); + static std::auto_ptr deserialize(SerializerIterator& sit, SField::ref name); + + bool setType(const std::vector& type); + bool isValidForType(); + bool isFieldAllowed(SField::ref); + + void set(const std::vector&); + bool set(SerializerIterator& u, int depth = 0); - int getLength() const; virtual SerializedTypeID getSType() const { return STI_OBJECT; } virtual bool isEquivalent(const SerializedType& t) const; - void add(Serializer& s) const; + virtual void add(Serializer& s) const { add(s, true); } // just inner elements + void add(Serializer& s, bool withSignature) const; Serializer getSerializer() const { Serializer s; add(s); return s; } std::string getFullText() const; std::string getText() const; virtual Json::Value getJson(int options) const; - int addObject(const SerializedType& t) { mData.push_back(t.clone()); return mData.size() - 1; } - int giveObject(std::auto_ptr t) { mData.push_back(t); return mData.size() - 1; } - int giveObject(SerializedType* t) { mData.push_back(t); return mData.size() - 1; } + int addObject(const SerializedType& t) { mData.push_back(t.clone()); return mData.size() - 1; } + int giveObject(std::auto_ptr t) { mData.push_back(t); return mData.size() - 1; } + int giveObject(SerializedType* t) { mData.push_back(t); return mData.size() - 1; } const boost::ptr_vector& peekData() const { return mData; } - boost::ptr_vector& peekData() { return mData; } + boost::ptr_vector& peekData() { return mData; } + SerializedType& front() { return mData.front(); } + const SerializedType& front() const { return mData.front(); } + SerializedType& back() { return mData.back(); } + const SerializedType& back() const { return mData.back(); } int getCount() const { return mData.size(); } @@ -155,63 +81,137 @@ public: bool clearFlag(uint32); uint32 getFlags() const; + uint256 getHash(uint32 prefix) const; + uint256 getSigningHash(uint32 prefix) const; + const SerializedType& peekAtIndex(int offset) const { return mData[offset]; } SerializedType& getIndex(int offset) { return mData[offset]; } const SerializedType* peekAtPIndex(int offset) const { return &(mData[offset]); } SerializedType* getPIndex(int offset) { return &(mData[offset]); } - int getFieldIndex(SOE_Field field) const; + int getFieldIndex(SField::ref field) const; + SField::ref getFieldSType(int index) const; - const SerializedType& peekAtField(SOE_Field field) const; - SerializedType& getField(SOE_Field field); - const SerializedType* peekAtPField(SOE_Field field) const; - SerializedType* getPField(SOE_Field field); - const SOElement* getFieldType(SOE_Field field) const; + const SerializedType& peekAtField(SField::ref field) const; + SerializedType& getField(SField::ref field); + const SerializedType* peekAtPField(SField::ref field) const; + SerializedType* getPField(SField::ref field); // these throw if the field type doesn't match, or return default values if the // field is optional but not present - std::string getFieldString(SOE_Field field) const; - unsigned char getValueFieldU8(SOE_Field field) const; - uint16 getValueFieldU16(SOE_Field field) const; - uint32 getValueFieldU32(SOE_Field field) const; - uint64 getValueFieldU64(SOE_Field field) const; - uint128 getValueFieldH128(SOE_Field field) const; - uint160 getValueFieldH160(SOE_Field field) const; - uint256 getValueFieldH256(SOE_Field field) const; - NewcoinAddress getValueFieldAccount(SOE_Field field) const; - std::vector getValueFieldVL(SOE_Field field) const; - std::vector getValueFieldTL(SOE_Field field) const; - STAmount getValueFieldAmount(SOE_Field field) const; - STPathSet getValueFieldPathSet(SOE_Field field) const; - STVector256 getValueFieldV256(SOE_Field field) const; + std::string getFieldString(SField::ref field) const; + unsigned char getFieldU8(SField::ref field) const; + uint16 getFieldU16(SField::ref field) const; + uint32 getFieldU32(SField::ref field) const; + uint64 getFieldU64(SField::ref field) const; + uint128 getFieldH128(SField::ref field) const; + uint160 getFieldH160(SField::ref field) const; + uint256 getFieldH256(SField::ref field) const; + NewcoinAddress getFieldAccount(SField::ref field) const; + uint160 getFieldAccount160(SField::ref field) const; + std::vector getFieldVL(SField::ref field) const; + std::vector getFieldTL(SField::ref field) const; + STAmount getFieldAmount(SField::ref field) const; + STPathSet getFieldPathSet(SField::ref field) const; + STVector256 getFieldV256(SField::ref field) const; - void setValueFieldU8(SOE_Field field, unsigned char); - void setValueFieldU16(SOE_Field field, uint16); - void setValueFieldU32(SOE_Field field, uint32); - void setValueFieldU64(SOE_Field field, uint64); - void setValueFieldH128(SOE_Field field, const uint128&); - void setValueFieldH160(SOE_Field field, const uint160&); - void setValueFieldH256(SOE_Field field, const uint256&); - void setValueFieldVL(SOE_Field field, const std::vector&); - void setValueFieldTL(SOE_Field field, const std::vector&); - void setValueFieldAccount(SOE_Field field, const uint160&); - void setValueFieldAccount(SOE_Field field, const NewcoinAddress& addr) - { setValueFieldAccount(field, addr.getAccountID()); } - void setValueFieldAmount(SOE_Field field, const STAmount&); - void setValueFieldPathSet(SOE_Field field, const STPathSet&); - void setValueFieldV256(SOE_Field field, const STVector256& v); + void setFieldU8(SField::ref field, unsigned char); + void setFieldU16(SField::ref field, uint16); + void setFieldU32(SField::ref field, uint32); + void setFieldU64(SField::ref field, uint64); + void setFieldH128(SField::ref field, const uint128&); + void setFieldH160(SField::ref field, const uint160&); + void setFieldH256(SField::ref field, const uint256&); + void setFieldVL(SField::ref field, const std::vector&); + void setFieldTL(SField::ref field, const std::vector&); + void setFieldAccount(SField::ref field, const uint160&); + void setFieldAccount(SField::ref field, const NewcoinAddress& addr) + { setFieldAccount(field, addr.getAccountID()); } + void setFieldAmount(SField::ref field, const STAmount&); + void setFieldPathSet(SField::ref field, const STPathSet&); + void setFieldV256(SField::ref field, const STVector256& v); - bool isFieldPresent(SOE_Field field) const; - SerializedType* makeFieldPresent(SOE_Field field); - void makeFieldAbsent(SOE_Field field); + bool isFieldPresent(SField::ref field) const; + SerializedType* makeFieldPresent(SField::ref field); + void makeFieldAbsent(SField::ref field); + bool delField(SField::ref field); + void delField(int index); - static std::auto_ptr makeDefaultObject(SerializedTypeID id, const char *name); - static std::auto_ptr makeDeserializedObject(SerializedTypeID id, const char *name, - SerializerIterator&); + static std::auto_ptr makeDefaultObject(SerializedTypeID id, SField::ref name); + static std::auto_ptr makeDeserializedObject(SerializedTypeID id, SField::ref name, + SerializerIterator&, int depth); + + static std::auto_ptr makeNonPresentObject(SField::ref name) + { return makeDefaultObject(STI_NOTPRESENT, name); } + static std::auto_ptr makeDefaultObject(SField::ref name) + { return makeDefaultObject(name.fieldType, name); } static void unitTest(); }; +class STArray : public SerializedType +{ +public: + typedef std::vector vector; + typedef std::vector::iterator iterator; + typedef std::vector::const_iterator const_iterator; + typedef std::vector::reverse_iterator reverse_iterator; + typedef std::vector::const_reverse_iterator const_reverse_iterator; + typedef std::vector::size_type size_type; + +protected: + + vector value; + + STArray* duplicate() const { return new STArray(*this); } + static STArray* construct(SerializerIterator&, SField::ref); + +public: + + STArray() { ; } + STArray(SField::ref f) : SerializedType(f) { ; } + STArray(SField::ref f, const vector& v) : SerializedType(f), value(v) { ; } + STArray(vector& v) : value(v) { ; } + + static std::auto_ptr deserialize(SerializerIterator& sit, SField::ref name) + { return std::auto_ptr(construct(sit, name)); } + + const vector& getValue() const { return value; } + vector& getValue() { return value; } + + // vector-like functions + void push_back(const STObject& object) { value.push_back(object); } + STObject& operator[](int j) { return value[j]; } + const STObject& operator[](int j) const { return value[j]; } + iterator begin() { return value.begin(); } + const_iterator begin() const { return value.begin(); } + iterator end() { return value.end(); } + const_iterator end() const { return value.end(); } + size_type size() const { return value.size(); } + reverse_iterator rbegin() { return value.rbegin(); } + const_reverse_iterator rbegin() const { return value.rbegin(); } + reverse_iterator rend() { return value.rend(); } + const_reverse_iterator rend() const { return value.rend(); } + iterator erase(iterator pos) { return value.erase(pos); } + STObject& front() { return value.front(); } + const STObject& front() const { return value.front(); } + STObject& back() { return value.back(); } + const STObject& back() const { return value.back(); } + void pop_back() { value.pop_back(); } + bool empty() const { return value.empty(); } + void clear() { value.clear(); } + + virtual std::string getFullText() const; + virtual std::string getText() const; + virtual Json::Value getJson(int) const; + virtual void add(Serializer& s) const; + + bool operator==(const STArray &s) { return value == s.value; } + bool operator!=(const STArray &s) { return value != s.value; } + + virtual SerializedTypeID getSType() const { return STI_ARRAY; } + virtual bool isEquivalent(const SerializedType& t) const; +}; #endif // vim:ts=4 diff --git a/src/SerializedTransaction.cpp b/src/SerializedTransaction.cpp index bab0a4a9f5..617125c02e 100644 --- a/src/SerializedTransaction.cpp +++ b/src/SerializedTransaction.cpp @@ -1,25 +1,22 @@ #include "SerializedTransaction.h" +#include + #include "Application.h" #include "Log.h" #include "HashPrefixes.h" -SerializedTransaction::SerializedTransaction(TransactionType type) : mType(type) +SerializedTransaction::SerializedTransaction(TransactionType type) : STObject(sfTransaction), mType(type) { - mFormat = getTxnFormat(type); - if (mFormat == NULL) throw std::runtime_error("invalid transaction type"); - - mMiddleTxn.giveObject(new STVariableLength("SigningPubKey")); - mMiddleTxn.giveObject(new STAccount("SourceAccount")); - mMiddleTxn.giveObject(new STUInt32("Sequence")); - mMiddleTxn.giveObject(new STUInt16("Type", static_cast(type))); - mMiddleTxn.giveObject(new STAmount("Fee")); - - mInnerTxn = STObject(mFormat->elements, "InnerTransaction"); + mFormat = TransactionFormat::getTxnFormat(type); + if (mFormat == NULL) + throw std::runtime_error("invalid transaction type"); + set(mFormat->elements); + setFieldU16(sfTransactionType, mFormat->t_type); } -SerializedTransaction::SerializedTransaction(SerializerIterator& sit) +SerializedTransaction::SerializedTransaction(SerializerIterator& sit) : STObject(sfTransaction) { int length = sit.getBytesLeft(); if ((length < TransactionMinLen) || (length > TransactionMaxLen)) @@ -28,32 +25,17 @@ SerializedTransaction::SerializedTransaction(SerializerIterator& sit) throw std::runtime_error("Transaction length invalid"); } - mSignature.setValue(sit.getVL()); + set(sit); + mType = static_cast(getFieldU16(sfTransactionType)); - mMiddleTxn.giveObject(new STVariableLength("SigningPubKey", sit.getVL())); - - STAccount sa("SourceAccount", sit.getVL()); - mSourceAccount = sa.getValueNCA(); - mMiddleTxn.giveObject(new STAccount(sa)); - - mMiddleTxn.giveObject(new STUInt32("Sequence", sit.get32())); - - mType = static_cast(sit.get16()); - mMiddleTxn.giveObject(new STUInt16("Type", static_cast(mType))); - mFormat = getTxnFormat(mType); + mFormat = TransactionFormat::getTxnFormat(mType); if (!mFormat) + throw std::runtime_error("invalid transaction type"); + if (!setType(mFormat->elements)) { - Log(lsERROR) << "Transaction has invalid type"; - throw std::runtime_error("Transaction has invalid type"); + assert(false); + throw std::runtime_error("transaction not valid"); } - mMiddleTxn.giveObject(STAmount::deserialize(sit, "Fee")); - - mInnerTxn = STObject(mFormat->elements, sit, "InnerTransaction"); -} - -int SerializedTransaction::getLength() const -{ - return mSignature.getLength() + mMiddleTxn.getLength() + mInnerTxn.getLength(); } std::string SerializedTransaction::getFullText() const @@ -61,32 +43,23 @@ std::string SerializedTransaction::getFullText() const std::string ret = "\""; ret += getTransactionID().GetHex(); ret += "\" = {"; - ret += mSignature.getFullText(); - ret += mMiddleTxn.getFullText(); - ret += mInnerTxn.getFullText(); + ret += STObject::getFullText(); ret += "}"; return ret; } std::string SerializedTransaction::getText() const { - std::string ret = "{"; - ret += mSignature.getText(); - ret += mMiddleTxn.getText(); - ret += mInnerTxn.getText(); - ret += "}"; - return ret; + return STObject::getText(); } std::vector SerializedTransaction::getAffectedAccounts() const { std::vector accounts; - accounts.push_back(mSourceAccount); - for(boost::ptr_vector::const_iterator it = mInnerTxn.peekData().begin(), - end = mInnerTxn.peekData().end(); it != end ; ++it) + BOOST_FOREACH(const SerializedType& it, peekData()) { - const STAccount* sa = dynamic_cast(&*it); + const STAccount* sa = dynamic_cast(&it); if (sa != NULL) { bool found = false; @@ -107,197 +80,61 @@ std::vector SerializedTransaction::getAffectedAccounts() const return accounts; } -void SerializedTransaction::add(Serializer& s) const -{ - mSignature.add(s); - mMiddleTxn.add(s); - mInnerTxn.add(s); -} - -bool SerializedTransaction::isEquivalent(const SerializedType& t) const -{ // Signatures are not compared - const SerializedTransaction* v = dynamic_cast(&t); - if (!v) return false; - if (mType != v->mType) return false; - if (mMiddleTxn != v->mMiddleTxn) return false; - if (mInnerTxn != v->mInnerTxn) return false; - return true; -} - uint256 SerializedTransaction::getSigningHash() const { - Serializer s; - s.add32(sHP_TransactionSign); - mMiddleTxn.add(s); - mInnerTxn.add(s); - return s.getSHA512Half(); + return STObject::getSigningHash(sHP_TransactionSign); } uint256 SerializedTransaction::getTransactionID() const { // perhaps we should cache this - Serializer s; - s.add32(sHP_TransactionID); - mSignature.add(s); - mMiddleTxn.add(s); - mInnerTxn.add(s); - return s.getSHA512Half(); + return getHash(sHP_TransactionID); } std::vector SerializedTransaction::getSignature() const { - return mSignature.getValue(); + try + { + return getFieldVL(sfTxnSignature); + } + catch (...) + { + return std::vector(); + } } -const std::vector& SerializedTransaction::peekSignature() const +void SerializedTransaction::sign(const NewcoinAddress& naAccountPrivate) { - return mSignature.peekValue(); -} - -bool SerializedTransaction::sign(const NewcoinAddress& naAccountPrivate) -{ - return naAccountPrivate.accountPrivateSign(getSigningHash(), mSignature.peekValue()); + std::vector signature; + naAccountPrivate.accountPrivateSign(getSigningHash(), signature); + setFieldVL(sfTxnSignature, signature); } bool SerializedTransaction::checkSign(const NewcoinAddress& naAccountPublic) const { - return naAccountPublic.accountPublicVerify(getSigningHash(), mSignature.getValue()); + try + { + return naAccountPublic.accountPublicVerify(getSigningHash(), getFieldVL(sfTxnSignature)); + } + catch (...) + { + return false; + } } -void SerializedTransaction::setSignature(const std::vector& sig) +void SerializedTransaction::setSigningPubKey(const NewcoinAddress& naSignPubKey) { - mSignature.setValue(sig); + setFieldVL(sfSigningPubKey, naSignPubKey.getAccountPublic()); } -STAmount SerializedTransaction::getTransactionFee() const +void SerializedTransaction::setSourceAccount(const NewcoinAddress& naSource) { - const STAmount* v = dynamic_cast(mMiddleTxn.peekAtPIndex(TransactionIFee)); - if (!v) throw std::runtime_error("corrupt transaction"); - return *v; -} - -void SerializedTransaction::setTransactionFee(const STAmount& fee) -{ - STAmount* v = dynamic_cast(mMiddleTxn.getPIndex(TransactionIFee)); - if (!v) throw std::runtime_error("corrupt transaction"); - v->setValue(fee); -} - -uint32 SerializedTransaction::getSequence() const -{ - const STUInt32* v = dynamic_cast(mMiddleTxn.peekAtPIndex(TransactionISequence)); - if (!v) throw std::runtime_error("corrupt transaction"); - return v->getValue(); -} - -void SerializedTransaction::setSequence(uint32 seq) -{ - STUInt32* v = dynamic_cast(mMiddleTxn.getPIndex(TransactionISequence)); - if (!v) throw std::runtime_error("corrupt transaction"); - v->setValue(seq); -} - -std::vector SerializedTransaction::getSigningPubKey() const -{ - const STVariableLength* v = - dynamic_cast(mMiddleTxn.peekAtPIndex(TransactionISigningPubKey)); - if (!v) throw std::runtime_error("corrupt transaction"); - return v->getValue(); -} - -const std::vector& SerializedTransaction::peekSigningPubKey() const -{ - const STVariableLength* v= - dynamic_cast(mMiddleTxn.peekAtPIndex(TransactionISigningPubKey)); - if (!v) throw std::runtime_error("corrupt transaction"); - return v->peekValue(); -} - -std::vector& SerializedTransaction::peekSigningPubKey() -{ - STVariableLength* v = dynamic_cast(mMiddleTxn.getPIndex(TransactionISigningPubKey)); - if (!v) throw std::runtime_error("corrupt transaction"); - return v->peekValue(); -} - -const NewcoinAddress& SerializedTransaction::setSigningPubKey(const NewcoinAddress& naSignPubKey) -{ - mSignPubKey = naSignPubKey; - - STVariableLength* v = dynamic_cast(mMiddleTxn.getPIndex(TransactionISigningPubKey)); - if (!v) throw std::runtime_error("corrupt transaction"); - v->setValue(mSignPubKey.getAccountPublic()); - - return mSignPubKey; -} - -const NewcoinAddress& SerializedTransaction::setSourceAccount(const NewcoinAddress& naSource) -{ - mSourceAccount = naSource; - - STAccount* v = dynamic_cast(mMiddleTxn.getPIndex(TransactionISourceID)); - if (!v) throw std::runtime_error("corrupt transaction"); - v->setValueNCA(mSourceAccount); - return mSourceAccount; -} - -uint160 SerializedTransaction::getITFieldAccount(SOE_Field field) const -{ - uint160 r; - const SerializedType* st = mInnerTxn.peekAtPField(field); - if (!st) return r; - - const STAccount* ac = dynamic_cast(st); - if (!ac) return r; - ac->getValueH160(r); - return r; -} - -int SerializedTransaction::getITFieldIndex(SOE_Field field) const -{ - return mInnerTxn.getFieldIndex(field); -} - -int SerializedTransaction::getITFieldCount() const -{ - return mInnerTxn.getCount(); -} - -bool SerializedTransaction::getITFieldPresent(SOE_Field field) const -{ - return mInnerTxn.isFieldPresent(field); -} - -const SerializedType& SerializedTransaction::peekITField(SOE_Field field) const -{ - return mInnerTxn.peekAtField(field); -} - -SerializedType& SerializedTransaction::getITField(SOE_Field field) -{ - return mInnerTxn.getField(field); -} - -void SerializedTransaction::makeITFieldPresent(SOE_Field field) -{ - mInnerTxn.makeFieldPresent(field); -} - -void SerializedTransaction::makeITFieldAbsent(SOE_Field field) -{ - return mInnerTxn.makeFieldAbsent(field); + setFieldAccount(sfAccount, naSource); } Json::Value SerializedTransaction::getJson(int options) const { - Json::Value ret = Json::objectValue; + Json::Value ret = STObject::getJson(0); ret["id"] = getTransactionID().GetHex(); - ret["signature"] = mSignature.getText(); - - Json::Value middle = mMiddleTxn.getJson(options); - middle["type"] = mFormat->t_name; - ret["middle"] = middle; - - ret["inner"] = mInnerTxn.getJson(options); return ret; } diff --git a/src/SerializedTransaction.h b/src/SerializedTransaction.h index 59f1650ca1..0d9f59e529 100644 --- a/src/SerializedTransaction.h +++ b/src/SerializedTransaction.h @@ -17,18 +17,14 @@ #define TXN_SQL_INCLUDED 'I' #define TXN_SQL_UNKNOWN 'U' -class SerializedTransaction : public SerializedType +class SerializedTransaction : public STObject { public: typedef boost::shared_ptr pointer; protected: - NewcoinAddress mSignPubKey; - NewcoinAddress mSourceAccount; TransactionType mType; - STVariableLength mSignature; - STObject mMiddleTxn, mInnerTxn; - TransactionFormat* mFormat; + const TransactionFormat* mFormat; SerializedTransaction* duplicate() const { return new SerializedTransaction(*this); } @@ -37,84 +33,27 @@ public: SerializedTransaction(TransactionType type); // STObject functions - int getLength() const; SerializedTypeID getSType() const { return STI_TRANSACTION; } std::string getFullText() const; std::string getText() const; - void add(Serializer& s) const; - virtual bool isEquivalent(const SerializedType& t) const; // outer transaction functions / signature functions std::vector getSignature() const; - const std::vector& peekSignature() const; - void setSignature(const std::vector& s); + void setSignature(const std::vector& s) { setFieldVL(sfTxnSignature, s); } uint256 getSigningHash() const; - TransactionType getTxnType() const { return mType; } - STAmount getTransactionFee() const; - void setTransactionFee(const STAmount& fee); + TransactionType getTxnType() const { return mType; } + STAmount getTransactionFee() const { return getFieldAmount(sfFee); } + void setTransactionFee(const STAmount& fee) { setFieldAmount(sfFee, fee); } - const NewcoinAddress& getSourceAccount() const { return mSourceAccount; } - std::vector getSigningPubKey() const; - const std::vector& peekSigningPubKey() const; - std::vector& peekSigningPubKey(); - const NewcoinAddress& setSigningPubKey(const NewcoinAddress& naSignPubKey); - const NewcoinAddress& setSourceAccount(const NewcoinAddress& naSource); + NewcoinAddress getSourceAccount() const { return getFieldAccount(sfAccount); } + std::vector getSigningPubKey() const { return getFieldVL(sfSigningPubKey); } + void setSigningPubKey(const NewcoinAddress& naSignPubKey); + void setSourceAccount(const NewcoinAddress& naSource); std::string getTransactionType() const { return mFormat->t_name; } - // inner transaction functions - uint32 getFlags() const { return mInnerTxn.getFlags(); } - void setFlag(uint32 v) { mInnerTxn.setFlag(v); } - void clearFlag(uint32 v) { mInnerTxn.clearFlag(v); } - - uint32 getSequence() const; - void setSequence(uint32); - - // inner transaction field functions - int getITFieldIndex(SOE_Field field) const; - int getITFieldCount() const; - const SerializedType& peekITField(SOE_Field field) const; - SerializedType& getITField(SOE_Field field); - - // inner transaction field value functions - std::string getITFieldString(SOE_Field field) const { return mInnerTxn.getFieldString(field); } - unsigned char getITFieldU8(SOE_Field field) const { return mInnerTxn.getValueFieldU8(field); } - uint16 getITFieldU16(SOE_Field field) const { return mInnerTxn.getValueFieldU16(field); } - uint32 getITFieldU32(SOE_Field field) const { return mInnerTxn.getValueFieldU32(field); } - uint64 getITFieldU64(SOE_Field field) const { return mInnerTxn.getValueFieldU64(field); } - uint128 getITFieldH128(SOE_Field field) const { return mInnerTxn.getValueFieldH128(field); } - uint160 getITFieldH160(SOE_Field field) const { return mInnerTxn.getValueFieldH160(field); } - uint160 getITFieldAccount(SOE_Field field) const; - uint256 getITFieldH256(SOE_Field field) const { return mInnerTxn.getValueFieldH256(field); } - std::vector getITFieldVL(SOE_Field field) const { return mInnerTxn.getValueFieldVL(field); } - std::vector getITFieldTL(SOE_Field field) const { return mInnerTxn.getValueFieldTL(field); } - STAmount getITFieldAmount(SOE_Field field) const { return mInnerTxn.getValueFieldAmount(field); } - STPathSet getITFieldPathSet(SOE_Field field) const { return mInnerTxn.getValueFieldPathSet(field); } - - void setITFieldU8(SOE_Field field, unsigned char v) { return mInnerTxn.setValueFieldU8(field, v); } - void setITFieldU16(SOE_Field field, uint16 v) { return mInnerTxn.setValueFieldU16(field, v); } - void setITFieldU32(SOE_Field field, uint32 v) { return mInnerTxn.setValueFieldU32(field, v); } - void setITFieldU64(SOE_Field field, uint32 v) { return mInnerTxn.setValueFieldU64(field, v); } - void setITFieldH128(SOE_Field field, const uint128& v) { return mInnerTxn.setValueFieldH128(field, v); } - void setITFieldH160(SOE_Field field, const uint160& v) { return mInnerTxn.setValueFieldH160(field, v); } - void setITFieldH256(SOE_Field field, const uint256& v) { return mInnerTxn.setValueFieldH256(field, v); } - void setITFieldVL(SOE_Field field, const std::vector& v) - { return mInnerTxn.setValueFieldVL(field, v); } - void setITFieldTL(SOE_Field field, const std::vector& v) - { return mInnerTxn.setValueFieldTL(field, v); } - void setITFieldAccount(SOE_Field field, const uint160& v) - { return mInnerTxn.setValueFieldAccount(field, v); } - void setITFieldAccount(SOE_Field field, const NewcoinAddress& v) - { return mInnerTxn.setValueFieldAccount(field, v); } - void setITFieldAmount(SOE_Field field, const STAmount& v) - { return mInnerTxn.setValueFieldAmount(field, v); } - void setITFieldPathSet(SOE_Field field, const STPathSet& v) - { return mInnerTxn.setValueFieldPathSet(field, v); } - - // optional field functions - bool getITFieldPresent(SOE_Field field) const; - void makeITFieldPresent(SOE_Field field); - void makeITFieldAbsent(SOE_Field field); + uint32 getSequence() const { return getFieldU32(sfSequence); } + void setSequence(uint32 seq) { return setFieldU32(sfSequence, seq); } std::vector getAffectedAccounts() const; @@ -122,7 +61,7 @@ public: virtual Json::Value getJson(int options) const; - bool sign(const NewcoinAddress& naAccountPrivate); + void sign(const NewcoinAddress& naAccountPrivate); bool checkSign(const NewcoinAddress& naAccountPublic) const; // SQL Functions diff --git a/src/SerializedTypes.cpp b/src/SerializedTypes.cpp index 7177f0fb2c..92316d8b96 100644 --- a/src/SerializedTypes.cpp +++ b/src/SerializedTypes.cpp @@ -5,17 +5,23 @@ #include "SerializedTypes.h" #include "SerializedObject.h" #include "TransactionFormats.h" +#include "LedgerFormats.h" +#include "FieldNames.h" +#include "Log.h" #include "NewcoinAddress.h" #include "utils.h" +STAmount saZero(CURRENCY_ONE, ACCOUNT_ONE, 0); +STAmount saOne(CURRENCY_ONE, ACCOUNT_ONE, 1); + std::string SerializedType::getFullText() const { std::string ret; if (getSType() != STI_NOTPRESENT) { - if(name != NULL) + if(fName->hasName()) { - ret = name; + ret = fName->fieldName; ret += " = "; } ret += getText(); @@ -23,7 +29,7 @@ std::string SerializedType::getFullText() const return ret; } -STUInt8* STUInt8::construct(SerializerIterator& u, const char *name) +STUInt8* STUInt8::construct(SerializerIterator& u, SField::ref name) { return new STUInt8(name, u.get8()); } @@ -39,13 +45,25 @@ bool STUInt8::isEquivalent(const SerializedType& t) const return v && (value == v->value); } -STUInt16* STUInt16::construct(SerializerIterator& u, const char *name) +STUInt16* STUInt16::construct(SerializerIterator& u, SField::ref name) { return new STUInt16(name, u.get16()); } std::string STUInt16::getText() const { + if (getFName() == sfLedgerEntryType) + { + LedgerEntryFormat *f = LedgerEntryFormat::getLgrFormat(value); + if (f != NULL) + return f->t_name; + } + if (getFName() == sfTransactionType) + { + TransactionFormat *f = TransactionFormat::getTxnFormat(value); + if (f != NULL) + return f->t_name; + } return boost::lexical_cast(value); } @@ -55,7 +73,7 @@ bool STUInt16::isEquivalent(const SerializedType& t) const return v && (value == v->value); } -STUInt32* STUInt32::construct(SerializerIterator& u, const char *name) +STUInt32* STUInt32::construct(SerializerIterator& u, SField::ref name) { return new STUInt32(name, u.get32()); } @@ -71,7 +89,7 @@ bool STUInt32::isEquivalent(const SerializedType& t) const return v && (value == v->value); } -STUInt64* STUInt64::construct(SerializerIterator& u, const char *name) +STUInt64* STUInt64::construct(SerializerIterator& u, SField::ref name) { return new STUInt64(name, u.get64()); } @@ -87,7 +105,7 @@ bool STUInt64::isEquivalent(const SerializedType& t) const return v && (value == v->value); } -STHash128* STHash128::construct(SerializerIterator& u, const char *name) +STHash128* STHash128::construct(SerializerIterator& u, SField::ref name) { return new STHash128(name, u.get128()); } @@ -103,7 +121,7 @@ bool STHash128::isEquivalent(const SerializedType& t) const return v && (value == v->value); } -STHash160* STHash160::construct(SerializerIterator& u, const char *name) +STHash160* STHash160::construct(SerializerIterator& u, SField::ref name) { return new STHash160(name, u.get160()); } @@ -119,7 +137,7 @@ bool STHash160::isEquivalent(const SerializedType& t) const return v && (value == v->value); } -STHash256* STHash256::construct(SerializerIterator& u, const char *name) +STHash256* STHash256::construct(SerializerIterator& u, SField::ref name) { return new STHash256(name, u.get256()); } @@ -135,7 +153,7 @@ bool STHash256::isEquivalent(const SerializedType& t) const return v && (value == v->value); } -STVariableLength::STVariableLength(SerializerIterator& st, const char *name) : SerializedType(name) +STVariableLength::STVariableLength(SerializerIterator& st, SField::ref name) : SerializedType(name) { value = st.getVL(); } @@ -145,16 +163,11 @@ std::string STVariableLength::getText() const return strHex(value); } -STVariableLength* STVariableLength::construct(SerializerIterator& u, const char *name) +STVariableLength* STVariableLength::construct(SerializerIterator& u, SField::ref name) { return new STVariableLength(name, u.getVL()); } -int STVariableLength::getLength() const -{ - return Serializer::encodeLengthLength(value.size()) + value.size(); -} - bool STVariableLength::isEquivalent(const SerializedType& t) const { const STVariableLength* v = dynamic_cast(&t); @@ -172,7 +185,7 @@ std::string STAccount::getText() const return a.humanAccountID(); } -STAccount* STAccount::construct(SerializerIterator& u, const char *name) +STAccount* STAccount::construct(SerializerIterator& u, SField::ref name) { return new STAccount(name, u.getVL()); } @@ -182,7 +195,7 @@ STAccount* STAccount::construct(SerializerIterator& u, const char *name) // // Return a new object from a SerializerIterator. -STVector256* STVector256::construct(SerializerIterator& u, const char *name) +STVector256* STVector256::construct(SerializerIterator& u, SField::ref name) { std::vector data = u.getVL(); std::vector value; @@ -218,9 +231,14 @@ bool STVector256::isEquivalent(const SerializedType& t) const // STAccount // +STAccount::STAccount(SField::ref n, const uint160& v) : STVariableLength(n) +{ + peekValue().insert(peekValue().end(), v.begin(), v.end()); +} + bool STAccount::isValueH160() const { - return peekValue().size() == (160/8); + return peekValue().size() == (160 / 8); } void STAccount::setValueH160(const uint160& v) @@ -251,93 +269,91 @@ void STAccount::setValueNCA(const NewcoinAddress& nca) setValueH160(nca.getAccountID()); } -std::string STTaggedList::getText() const -{ - std::string ret; - for (std::vector::const_iterator it=value.begin(); it!=value.end(); ++it) - { - ret += boost::lexical_cast(it->first); - ret += ","; - ret += strHex(it->second); - } - return ret; -} - -Json::Value STTaggedList::getJson(int) const -{ - Json::Value ret(Json::arrayValue); - - for (std::vector::const_iterator it=value.begin(); it!=value.end(); ++it) - { - Json::Value elem(Json::arrayValue); - elem.append(it->first); - elem.append(strHex(it->second)); - ret.append(elem); - } - - return ret; -} - -STTaggedList* STTaggedList::construct(SerializerIterator& u, const char *name) -{ - return new STTaggedList(name, u.getTaggedList()); -} - -int STTaggedList::getLength() const -{ - int ret = Serializer::getTaggedListLength(value); - if (ret<0) throw std::overflow_error("bad TL length"); - return ret; -} - -bool STTaggedList::isEquivalent(const SerializedType& t) const -{ - const STTaggedList* v = dynamic_cast(&t); - return v && (value == v->value); -} - -STPathSet* STPathSet::construct(SerializerIterator& s, const char *name) +STPathSet* STPathSet::construct(SerializerIterator& s, SField::ref name) { std::vector paths; std::vector path; + do { - switch(s.get8()) + int iType = s.get8(); + + if (iType == STPathElement::typeEnd || iType == STPathElement::typeBoundary) { - case STPathElement::typeEnd: - if (path.empty()) - { - if (!paths.empty()) - throw std::runtime_error("empty last path"); - } - else paths.push_back(path); + if (path.empty()) + { + Log(lsINFO) << "STPathSet: Empty path."; + + throw std::runtime_error("empty path"); + } + + paths.push_back(path); + path.clear(); + + if (iType == STPathElement::typeEnd) + { return new STPathSet(name, paths); + } + } + else if (iType & ~STPathElement::typeValidBits) + { + Log(lsINFO) << "STPathSet: Bad path element: " << iType; - case STPathElement::typeBoundary: - if (path.empty()) - throw std::runtime_error("empty path"); - paths.push_back(path); - path.clear(); + throw std::runtime_error("bad path element"); + } + else + { + const bool bAccount = !!(iType & STPathElement::typeAccount); + const bool bCurrency = !!(iType & STPathElement::typeCurrency); + const bool bIssuer = !!(iType & STPathElement::typeIssuer); - case STPathElement::typeAccount: - path.push_back(STPathElement(STPathElement::typeAccount, s.get160())); - break; + uint160 uAccountID; + uint160 uCurrency; + uint160 uIssuerID; - case STPathElement::typeOffer: - path.push_back(STPathElement(STPathElement::typeOffer, s.get160())); - break; + if (bAccount) + uAccountID = s.get160(); - default: throw std::runtime_error("Unknown path element"); + if (bCurrency) + uCurrency = s.get160(); + + if (bIssuer) + uIssuerID = s.get160(); + + path.push_back(STPathElement(uAccountID, uCurrency, uIssuerID)); } } while(1); } -int STPathSet::getLength() const +bool STPathSet::isEquivalent(const SerializedType& t) const { - int ret = 0; - for (std::vector::const_iterator it = value.begin(), end = value.end(); it != end; ++it) - ret += it->getSerializeSize(); - return (ret != 0) ? ret : (ret + 1); + const STPathSet* v = dynamic_cast(&t); + return v && (value == v->value); +} + +int STPath::getSerializeSize() const +{ + int iBytes = 0; + + BOOST_FOREACH(const STPathElement& speElement, mPath) + { + int iType = speElement.getNodeType(); + + iBytes += 1; // mType + + if (iType & STPathElement::typeAccount) + iBytes += 160/8; + + if (iType & STPathElement::typeCurrency) + iBytes += 160/8; + + if (iType & STPathElement::typeIssuer) + iBytes += 160/8; + } + + iBytes += 1; // typeBoundary | typeEnd + + return iBytes; } Json::Value STPath::getJson(int) const @@ -346,30 +362,22 @@ Json::Value STPath::getJson(int) const BOOST_FOREACH(std::vector::const_iterator::value_type it, mPath) { - switch (it.getNodeType()) - { - case STPathElement::typeAccount: - { - Json::Value elem(Json::objectValue); + Json::Value elem(Json::objectValue); + int iType = it.getNodeType(); - elem["account"] = NewcoinAddress::createHumanAccountID(it.getNode()); + elem["type"] = iType; + elem["type_hex"] = strHex(iType); - ret.append(elem); - break; - } + if (iType & STPathElement::typeAccount) + elem["account"] = NewcoinAddress::createHumanAccountID(it.getAccountID()); - case STPathElement::typeOffer: - { - Json::Value elem(Json::objectValue); + if (iType & STPathElement::typeCurrency) + elem["currency"] = STAmount::createHumanCurrency(it.getCurrency()); - elem["offer"] = it.getNode().GetHex(); + if (iType & STPathElement::typeIssuer) + elem["issuer"] = NewcoinAddress::createHumanAccountID(it.getIssuerID()); - ret.append(elem); - break; - } - - default: throw std::runtime_error("Unknown path element"); - } + ret.append(elem); } return ret; @@ -385,12 +393,13 @@ Json::Value STPathSet::getJson(int options) const return ret; } +#if 0 std::string STPath::getText() const { std::string ret("["); bool first = true; - BOOST_FOREACH(std::vector::const_iterator::value_type it, mPath) + BOOST_FOREACH(const STPathElement& it, mPath) { if (!first) ret += ", "; switch (it.getNodeType()) @@ -416,7 +425,9 @@ std::string STPath::getText() const return ret + "]"; } +#endif +#if 0 std::string STPathSet::getText() const { std::string ret("{"); @@ -433,23 +444,34 @@ std::string STPathSet::getText() const } return ret + "}"; } +#endif void STPathSet::add(Serializer& s) const { - bool firstPath = true; + bool bFirst = true; - BOOST_FOREACH(std::vector::const_iterator::value_type pit, value) + BOOST_FOREACH(const STPath& spPath, value) { - if (!firstPath) + if (!bFirst) { s.add8(STPathElement::typeBoundary); - firstPath = false; + bFirst = false; } - for (std::vector::const_iterator eit = pit.begin(), eend = pit.end(); eit != eend; ++eit) + BOOST_FOREACH(const STPathElement& speElement, spPath) { - s.add8(eit->getNodeType()); - s.add160(eit->getNode()); + int iType = speElement.getNodeType(); + + s.add8(iType); + + if (iType & STPathElement::typeAccount) + s.add160(speElement.getAccountID()); + + if (iType & STPathElement::typeCurrency) + s.add160(speElement.getCurrency()); + + if (iType & STPathElement::typeIssuer) + s.add160(speElement.getIssuerID()); } } s.add8(STPathElement::typeEnd); diff --git a/src/SerializedTypes.h b/src/SerializedTypes.h index 9a1c34b3f8..0c1cd3bac2 100644 --- a/src/SerializedTypes.h +++ b/src/SerializedTypes.h @@ -8,38 +8,13 @@ #include "uint256.h" #include "Serializer.h" +#include "FieldNames.h" -enum SerializedTypeID -{ - // special types - STI_DONE = -1, - STI_NOTPRESENT = 0, - - // standard types - STI_OBJECT = 1, - STI_UINT8 = 2, - STI_UINT16 = 3, - STI_UINT32 = 4, - STI_UINT64 = 5, - STI_HASH128 = 6, - STI_HASH160 = 7, - STI_HASH256 = 8, - STI_VL = 9, - STI_TL = 10, - STI_AMOUNT = 11, - STI_PATHSET = 12, - STI_VECTOR256 = 13, - - // high level types - STI_ACCOUNT = 100, - STI_TRANSACTION = 101, - STI_LEDGERENTRY = 102 -}; enum PathFlags { PF_END = 0x00, // End of current path & path list. - PF_BOUNDRY = 0xFF, // End of current path & new path follows. + PF_BOUNDARY = 0xFF, // End of current path & new path follows. PF_ACCOUNT = 0x01, PF_OFFER = 0x02, @@ -50,27 +25,33 @@ enum PathFlags PF_ISSUE = 0x80, }; +#define CURRENCY_XNS uint160(0) +#define CURRENCY_ONE uint160(1) // Used as a place holder +#define ACCOUNT_XNS uint160(0) +#define ACCOUNT_ONE uint160(1) // Used as a place holder + class SerializedType { protected: - const char* name; + SField::ptr fName; - virtual SerializedType* duplicate() const { return new SerializedType(name); } + virtual SerializedType* duplicate() const { return new SerializedType(*fName); } + SerializedType(SField::ptr n) : fName(n) { assert(fName); } public: - SerializedType() : name(NULL) { ; } - SerializedType(const char* n) : name(n) { ; } - SerializedType(const SerializedType& n) : name(n.name) { ; } + SerializedType() : fName(&sfGeneric) { ; } + SerializedType(SField::ref n) : fName(&n) { assert(fName); } + SerializedType(const SerializedType& n) : fName(n.fName) { ; } virtual ~SerializedType() { ; } - static std::auto_ptr deserialize(const char* name) + static std::auto_ptr deserialize(SField::ref name) { return std::auto_ptr(new SerializedType(name)); } - void setName(const char* n) { name=n; } - const char *getName() const { return name; } + void setFName(SField::ref n) { fName = &n; assert(fName); } + SField::ref getFName() const { return *fName; } + std::string getName() const { return fName->fieldName; } - virtual int getLength() const { return 0; } virtual SerializedTypeID getSType() const { return STI_NOTPRESENT; } std::auto_ptr clone() const { return std::auto_ptr(duplicate()); } @@ -80,11 +61,15 @@ public: virtual Json::Value getJson(int) const { return getText(); } - virtual void add(Serializer& s) const { return; } + virtual void add(Serializer& s) const { ; } virtual bool isEquivalent(const SerializedType& t) const { assert(getSType() == STI_NOTPRESENT); return t.getSType() == STI_NOTPRESENT; } + void addFieldID(Serializer& s) const { s.addFieldID(fName->fieldType, fName->fieldValue); } + + SerializedType& operator=(const SerializedType& t) + { if (!fName->fieldCode) fName = t.fName; return *this; } bool operator==(const SerializedType& t) const { return (getSType() == t.getSType()) && isEquivalent(t); } bool operator!=(const SerializedType& t) const @@ -93,32 +78,31 @@ public: inline SerializedType* new_clone(const SerializedType& s) { return s.clone().release(); } inline void delete_clone(const SerializedType* s) { boost::checked_delete(s); } +inline std::ostream& operator<<(std::ostream& out, const SerializedType& t) { return out << t.getFullText(); } class STUInt8 : public SerializedType { protected: unsigned char value; - STUInt8* duplicate() const { return new STUInt8(name, value); } - static STUInt8* construct(SerializerIterator&, const char* name = NULL); + STUInt8* duplicate() const { return new STUInt8(*this); } + static STUInt8* construct(SerializerIterator&, SField::ref f); public: - STUInt8(unsigned char v=0) : value(v) { ; } - STUInt8(const char* n, unsigned char v=0) : SerializedType(n), value(v) { ; } - static std::auto_ptr deserialize(SerializerIterator& sit, const char* name) + STUInt8(unsigned char v = 0) : value(v) { ; } + STUInt8(SField::ref n, unsigned char v = 0) : SerializedType(n), value(v) { ; } + static std::auto_ptr deserialize(SerializerIterator& sit, SField::ref name) { return std::auto_ptr(construct(sit, name)); } - int getLength() const { return 1; } SerializedTypeID getSType() const { return STI_UINT8; } std::string getText() const; void add(Serializer& s) const { s.add8(value); } unsigned char getValue() const { return value; } - void setValue(unsigned char v) { value=v; } + void setValue(unsigned char v) { value = v; } operator unsigned char() const { return value; } - STUInt8& operator=(unsigned char v) { value=v; return *this; } virtual bool isEquivalent(const SerializedType& t) const; }; @@ -127,17 +111,16 @@ class STUInt16 : public SerializedType protected: uint16 value; - STUInt16* duplicate() const { return new STUInt16(name, value); } - static STUInt16* construct(SerializerIterator&, const char* name = NULL); + STUInt16* duplicate() const { return new STUInt16(*this); } + static STUInt16* construct(SerializerIterator&, SField::ref name); public: - STUInt16(uint16 v=0) : value(v) { ; } - STUInt16(const char* n, uint16 v=0) : SerializedType(n), value(v) { ; } - static std::auto_ptr deserialize(SerializerIterator& sit, const char* name) + STUInt16(uint16 v = 0) : value(v) { ; } + STUInt16(SField::ref n, uint16 v = 0) : SerializedType(n), value(v) { ; } + static std::auto_ptr deserialize(SerializerIterator& sit, SField::ref name) { return std::auto_ptr(construct(sit, name)); } - int getLength() const { return 2; } SerializedTypeID getSType() const { return STI_UINT16; } std::string getText() const; void add(Serializer& s) const { s.add16(value); } @@ -146,7 +129,6 @@ public: void setValue(uint16 v) { value=v; } operator uint16() const { return value; } - STUInt16& operator=(uint16 v) { value=v; return *this; } virtual bool isEquivalent(const SerializedType& t) const; }; @@ -155,17 +137,16 @@ class STUInt32 : public SerializedType protected: uint32 value; - STUInt32* duplicate() const { return new STUInt32(name, value); } - static STUInt32* construct(SerializerIterator&, const char* name = NULL); + STUInt32* duplicate() const { return new STUInt32(*this); } + static STUInt32* construct(SerializerIterator&, SField::ref name); public: - STUInt32(uint32 v=0) : value(v) { ; } - STUInt32(const char* n, uint32 v=0) : SerializedType(n), value(v) { ; } - static std::auto_ptr deserialize(SerializerIterator& sit, const char* name) + STUInt32(uint32 v = 0) : value(v) { ; } + STUInt32(SField::ref n, uint32 v = 0) : SerializedType(n), value(v) { ; } + static std::auto_ptr deserialize(SerializerIterator& sit, SField::ref name) { return std::auto_ptr(construct(sit, name)); } - int getLength() const { return 4; } SerializedTypeID getSType() const { return STI_UINT32; } std::string getText() const; void add(Serializer& s) const { s.add32(value); } @@ -174,7 +155,6 @@ public: void setValue(uint32 v) { value=v; } operator uint32() const { return value; } - STUInt32& operator=(uint32 v) { value=v; return *this; } virtual bool isEquivalent(const SerializedType& t) const; }; @@ -183,17 +163,16 @@ class STUInt64 : public SerializedType protected: uint64 value; - STUInt64* duplicate() const { return new STUInt64(name, value); } - static STUInt64* construct(SerializerIterator&, const char* name = NULL); + STUInt64* duplicate() const { return new STUInt64(*this); } + static STUInt64* construct(SerializerIterator&, SField::ref name); public: - STUInt64(uint64 v=0) : value(v) { ; } - STUInt64(const char* n, uint64 v=0) : SerializedType(n), value(v) { ; } - static std::auto_ptr deserialize(SerializerIterator& sit, const char* name) + STUInt64(uint64 v = 0) : value(v) { ; } + STUInt64(SField::ref n, uint64 v = 0) : SerializedType(n), value(v) { ; } + static std::auto_ptr deserialize(SerializerIterator& sit, SField::ref name) { return std::auto_ptr(construct(sit, name)); } - int getLength() const { return 8; } SerializedTypeID getSType() const { return STI_UINT64; } std::string getText() const; void add(Serializer& s) const { s.add64(value); } @@ -202,7 +181,6 @@ public: void setValue(uint64 v) { value=v; } operator uint64() const { return value; } - STUInt64& operator=(uint64 v) { value=v; return *this; } virtual bool isEquivalent(const SerializedType& t) const; }; @@ -230,7 +208,7 @@ protected: void canonicalize(); STAmount* duplicate() const { return new STAmount(*this); } - static STAmount* construct(SerializerIterator&, const char* name = NULL); + static STAmount* construct(SerializerIterator&, SField::ref name); static const int cMinOffset = -96, cMaxOffset = 80; static const uint64 cMinValue = 1000000000000000ull, cMaxValue = 9999999999999999ull; @@ -238,40 +216,46 @@ protected: static const uint64 cNotNative = 0x8000000000000000ull; static const uint64 cPosNative = 0x4000000000000000ull; - STAmount(bool isNeg, uint64 value) : mValue(value), mOffset(0), mIsNative(true), mIsNegative(isNeg) { ; } - - STAmount(const char *name, uint64 value, bool isNegative) - : SerializedType(name), mValue(value), mOffset(0), mIsNative(true), mIsNegative(isNegative) - { ; } - STAmount(const char *n, const uint160& cur, uint64 val, int off, bool isNative, bool isNegative) - : SerializedType(n), mCurrency(cur), mValue(val), mOffset(off), mIsNative(isNative), mIsNegative(isNegative) - { ; } + STAmount(SField::ref name, const uint160& cur, const uint160& iss, uint64 val, int off, bool isNat, bool isNeg) + : SerializedType(name), mCurrency(cur), mIssuer(iss), mValue(val), mOffset(off), + mIsNative(isNat), mIsNegative(isNeg) { ; } uint64 toUInt64() const; static uint64 muldiv(uint64, uint64, uint64); public: - STAmount(uint64 v = 0, bool isNeg = false) : mValue(v), mOffset(0), mIsNative(true), mIsNegative(isNeg) - { if (v==0) mIsNegative = false; } + static uint64 uRateOne; - STAmount(const char* n, uint64 v = 0) - : SerializedType(n), mValue(v), mOffset(0), mIsNative(true), mIsNegative(false) + STAmount(uint64 v = 0, bool isNeg = false) : mValue(v), mOffset(0), mIsNative(true), mIsNegative(isNeg) + { if (v == 0) mIsNegative = false; } + + STAmount(SField::ref n, uint64 v = 0, bool isNeg = false) + : SerializedType(n), mValue(v), mOffset(0), mIsNative(true), mIsNegative(isNeg) { ; } - STAmount(const uint160& currency, uint64 v = 0, int off = 0) - : mCurrency(currency), mValue(v), mOffset(off), mIsNegative(false) + STAmount(const uint160& uCurrencyID, const uint160& uIssuerID, uint64 uV = 0, int iOff = 0, bool bNegative = false) + : mCurrency(uCurrencyID), mIssuer(uIssuerID), mValue(uV), mOffset(iOff), mIsNegative(bNegative) { canonicalize(); } - STAmount(const char* n, const uint160& currency, uint64 v = 0, int off = 0, bool isNeg = false) : - SerializedType(n), mCurrency(currency), mValue(v), mOffset(off), mIsNegative(isNeg) + // YYY This should probably require issuer too. + STAmount(SField::ref n, const uint160& currency, const uint160& issuer, + uint64 v = 0, int off = 0, bool isNeg = false) : + SerializedType(n), mCurrency(currency), mIssuer(issuer), mValue(v), mOffset(off), mIsNegative(isNeg) { canonicalize(); } - STAmount(const char* n, int64 v); + STAmount(SField::ref, const Json::Value&); - static std::auto_ptr deserialize(SerializerIterator& sit, const char* name) + static STAmount createFromInt64(SField::ref n, int64 v); + + static std::auto_ptr deserialize(SerializerIterator& sit, SField::ref name) { return std::auto_ptr(construct(sit, name)); } - int getLength() const { return mIsNative ? 8 : 28; } + static STAmount saFromRate(uint64 uRate = 0) + { return STAmount(CURRENCY_ONE, ACCOUNT_ONE, uRate, -9, false); } + + static STAmount saFromSigned(const uint160& uCurrencyID, const uint160& uIssuerID, int64 iV = 0, int iOff = 0) + { return STAmount(uCurrencyID, uIssuerID, iV < 0 ? -iV : iV, iOff, iV < 0); } + SerializedTypeID getSType() const { return STI_AMOUNT; } std::string getText() const; std::string getRaw() const; @@ -281,6 +265,7 @@ public: int getExponent() const { return mOffset; } uint64 getMantissa() const { return mValue; } + // When the currency is XNS, the value in raw units. S=signed uint64 getNValue() const { if (!mIsNative) throw std::runtime_error("not native"); return mValue; } void setNValue(uint64 v) { if (!mIsNative) throw std::runtime_error("not native"); mValue = v; } int64 getSNValue() const; @@ -290,6 +275,7 @@ public: bool isNative() const { return mIsNative; } bool isZero() const { return mValue == 0; } + bool isNonZero() const { return mValue != 0; } bool isNegative() const { return mIsNegative && !isZero(); } bool isPositive() const { return !mIsNegative && !isZero(); } bool isGEZero() const { return !mIsNegative; } @@ -302,6 +288,7 @@ public: void setIssuer(const uint160& uIssuer) { mIssuer = uIssuer; } const uint160& getCurrency() const { return mCurrency; } + bool setValue(const std::string& sAmount); bool setFullValue(const std::string& sAmount, const std::string& sCurrency = "", const std::string& sIssuer = ""); void setValue(const STAmount &); @@ -327,7 +314,6 @@ public: STAmount& operator+=(const STAmount&); STAmount& operator-=(const STAmount&); - STAmount& operator=(const STAmount&); STAmount& operator+=(uint64); STAmount& operator-=(uint64); STAmount& operator=(uint64); @@ -337,19 +323,32 @@ public: friend STAmount operator+(const STAmount& v1, const STAmount& v2); friend STAmount operator-(const STAmount& v1, const STAmount& v2); - static STAmount divide(const STAmount& v1, const STAmount& v2, const uint160& currencyOut); - static STAmount multiply(const STAmount& v1, const STAmount& v2, const uint160& currencyOut); + static STAmount divide(const STAmount& v1, const STAmount& v2, const uint160& uCurrencyID, const uint160& uIssuerID); + static STAmount divide(const STAmount& v1, const STAmount& v2, const STAmount& saUnit) + { return divide(v1, v2, saUnit.getCurrency(), saUnit.getIssuer()); } + static STAmount divide(const STAmount& v1, const STAmount& v2) + { return divide(v1, v2, v1); } + + static STAmount multiply(const STAmount& v1, const STAmount& v2, const uint160& uCurrencyID, const uint160& uIssuerID); + static STAmount multiply(const STAmount& v1, const STAmount& v2, const STAmount& saUnit) + { return multiply(v1, v2, saUnit.getCurrency(), saUnit.getIssuer()); } + static STAmount multiply(const STAmount& v1, const STAmount& v2) + { return multiply(v1, v2, v1); } // Someone is offering X for Y, what is the rate? + // Rate: smaller is better, the taker wants the most out: in/out static uint64 getRate(const STAmount& offerOut, const STAmount& offerIn); + static STAmount setRate(uint64 rate); // Someone is offering X for Y, I try to pay Z, how much do I get? // And what's left of the offer? And how much do I actually pay? static bool applyOffer( + const uint32 uTakerPaysRate, const uint32 uOfferPaysRate, const STAmount& saOfferFunds, const STAmount& saTakerFunds, const STAmount& saOfferPays, const STAmount& saOfferGets, const STAmount& saTakerPays, const STAmount& saTakerGets, - STAmount& saTakerPaid, STAmount& saTakerGot); + STAmount& saTakerPaid, STAmount& saTakerGot, + STAmount& saTakerIssuerFee, STAmount& saOfferIssuerFee); // Someone is offering X for Y, I need Z, how much do I pay static STAmount getPay(const STAmount& offerOut, const STAmount& offerIn, const STAmount& needed); @@ -357,7 +356,7 @@ public: // Native currency conversions, to/from display format static uint64 convertToDisplayAmount(const STAmount& internalAmount, uint64 totalNow, uint64 totalInit); static STAmount convertToInternalAmount(uint64 displayAmount, uint64 totalNow, uint64 totalInit, - const char* name = NULL); + SField::ref name = sfGeneric); static std::string createHumanCurrency(const uint160& uCurrency); static STAmount deserialize(SerializerIterator&); @@ -366,24 +365,28 @@ public: Json::Value getJson(int) const; }; +extern STAmount saZero; +extern STAmount saOne; + class STHash128 : public SerializedType { protected: uint128 value; - STHash128* duplicate() const { return new STHash128(name, value); } - static STHash128* construct(SerializerIterator&, const char* name = NULL); + STHash128* duplicate() const { return new STHash128(*this); } + static STHash128* construct(SerializerIterator&, SField::ref name); public: STHash128(const uint128& v) : value(v) { ; } - STHash128(const char* n, const uint128& v) : SerializedType(n), value(v) { ; } - STHash128(const char* n) : SerializedType(n) { ; } + STHash128(SField::ref n, const uint128& v) : SerializedType(n), value(v) { ; } + STHash128(SField::ref n, const char *v) : SerializedType(n) { value.SetHex(v); } + STHash128(SField::ref n, const std::string &v) : SerializedType(n) { value.SetHex(v); } + STHash128(SField::ref n) : SerializedType(n) { ; } STHash128() { ; } - static std::auto_ptr deserialize(SerializerIterator& sit, const char* name) + static std::auto_ptr deserialize(SerializerIterator& sit, SField::ref name) { return std::auto_ptr(construct(sit, name)); } - int getLength() const { return 20; } SerializedTypeID getSType() const { return STI_HASH128; } virtual std::string getText() const; void add(Serializer& s) const { s.add128(value); } @@ -392,7 +395,6 @@ public: void setValue(const uint128& v) { value=v; } operator uint128() const { return value; } - STHash128& operator=(const uint128& v) { value=v; return *this; } virtual bool isEquivalent(const SerializedType& t) const; }; @@ -401,19 +403,20 @@ class STHash160 : public SerializedType protected: uint160 value; - STHash160* duplicate() const { return new STHash160(name, value); } - static STHash160* construct(SerializerIterator&, const char* name = NULL); + STHash160* duplicate() const { return new STHash160(*this); } + static STHash160* construct(SerializerIterator&, SField::ref name); public: STHash160(const uint160& v) : value(v) { ; } - STHash160(const char* n, const uint160& v) : SerializedType(n), value(v) { ; } - STHash160(const char* n) : SerializedType(n) { ; } + STHash160(SField::ref n, const uint160& v) : SerializedType(n), value(v) { ; } + STHash160(SField::ref n, const char *v) : SerializedType(n) { value.SetHex(v); } + STHash160(SField::ref n, const std::string &v) : SerializedType(n) { value.SetHex(v); } + STHash160(SField::ref n) : SerializedType(n) { ; } STHash160() { ; } - static std::auto_ptr deserialize(SerializerIterator& sit, const char* name) + static std::auto_ptr deserialize(SerializerIterator& sit, SField::ref name) { return std::auto_ptr(construct(sit, name)); } - int getLength() const { return 20; } SerializedTypeID getSType() const { return STI_HASH160; } virtual std::string getText() const; void add(Serializer& s) const { s.add160(value); } @@ -422,7 +425,6 @@ public: void setValue(const uint160& v) { value=v; } operator uint160() const { return value; } - STHash160& operator=(const uint160& v) { value=v; return *this; } virtual bool isEquivalent(const SerializedType& t) const; }; @@ -431,19 +433,20 @@ class STHash256 : public SerializedType protected: uint256 value; - STHash256* duplicate() const { return new STHash256(name, value); } - static STHash256* construct(SerializerIterator&, const char* name = NULL); + STHash256* duplicate() const { return new STHash256(*this); } + static STHash256* construct(SerializerIterator&, SField::ref); public: STHash256(const uint256& v) : value(v) { ; } - STHash256(const char* n, const uint256& v) : SerializedType(n), value(v) { ; } - STHash256(const char* n) : SerializedType(n) { ; } + STHash256(SField::ref n, const uint256& v) : SerializedType(n), value(v) { ; } + STHash256(SField::ref n, const char *v) : SerializedType(n) { value.SetHex(v); } + STHash256(SField::ref n, const std::string &v) : SerializedType(n) { value.SetHex(v); } + STHash256(SField::ref n) : SerializedType(n) { ; } STHash256() { ; } - static std::auto_ptr deserialize(SerializerIterator& sit, const char* name) + static std::auto_ptr deserialize(SerializerIterator& sit, SField::ref name) { return std::auto_ptr(construct(sit, name)); } - int getLength() const { return 32; } SerializedTypeID getSType() const { return STI_HASH256; } std::string getText() const; void add(Serializer& s) const { s.add256(value); } @@ -452,7 +455,6 @@ public: void setValue(const uint256& v) { value=v; } operator uint256() const { return value; } - STHash256& operator=(const uint256& v) { value=v; return *this; } virtual bool isEquivalent(const SerializedType& t) const; }; @@ -461,20 +463,19 @@ class STVariableLength : public SerializedType protected: std::vector value; - virtual STVariableLength* duplicate() const { return new STVariableLength(name, value); } - static STVariableLength* construct(SerializerIterator&, const char* name = NULL); + virtual STVariableLength* duplicate() const { return new STVariableLength(*this); } + static STVariableLength* construct(SerializerIterator&, SField::ref); public: STVariableLength(const std::vector& v) : value(v) { ; } - STVariableLength(const char* n, const std::vector& v) : SerializedType(n), value(v) { ; } - STVariableLength(const char* n) : SerializedType(n) { ; } - STVariableLength(SerializerIterator&, const char* name = NULL); + STVariableLength(SField::ref n, const std::vector& v) : SerializedType(n), value(v) { ; } + STVariableLength(SField::ref n) : SerializedType(n) { ; } + STVariableLength(SerializerIterator&, SField::ref name = sfGeneric); STVariableLength() { ; } - static std::auto_ptr deserialize(SerializerIterator& sit, const char* name) + static std::auto_ptr deserialize(SerializerIterator& sit, SField::ref name) { return std::auto_ptr(construct(sit, name)); } - int getLength() const; virtual SerializedTypeID getSType() const { return STI_VL; } virtual std::string getText() const; void add(Serializer& s) const { s.addVL(value); } @@ -485,23 +486,23 @@ public: void setValue(const std::vector& v) { value=v; } operator std::vector() const { return value; } - STVariableLength& operator=(const std::vector& v) { value=v; return *this; } virtual bool isEquivalent(const SerializedType& t) const; }; class STAccount : public STVariableLength { protected: - virtual STAccount* duplicate() const { return new STAccount(name, value); } - static STAccount* construct(SerializerIterator&, const char* name = NULL); + virtual STAccount* duplicate() const { return new STAccount(*this); } + static STAccount* construct(SerializerIterator&, SField::ref); public: STAccount(const std::vector& v) : STVariableLength(v) { ; } - STAccount(const char* n, const std::vector& v) : STVariableLength(n, v) { ; } - STAccount(const char* n) : STVariableLength(n) { ; } + STAccount(SField::ref n, const std::vector& v) : STVariableLength(n, v) { ; } + STAccount(SField::ref n, const uint160& v); + STAccount(SField::ref n) : STVariableLength(n) { ; } STAccount() { ; } - static std::auto_ptr deserialize(SerializerIterator& sit, const char* name) + static std::auto_ptr deserialize(SerializerIterator& sit, SField::ref name) { return std::auto_ptr(construct(sit, name)); } SerializedTypeID getSType() const { return STI_ACCOUNT; } @@ -518,26 +519,50 @@ public: class STPathElement { public: - static const int typeEnd = 0x00; - static const int typeAccount = 0x01; // Rippling through an account - static const int typeOffer = 0x02; // Claiming an offer - static const int typeBoundary = 0xFF; // boundary between alternate paths + enum { + typeEnd = 0x00, + typeAccount = 0x01, // Rippling through an account (vs taking an offer). + typeCurrency = 0x10, // Currency follows. + typeIssuer = 0x20, // Issuer follows. + typeBoundary = 0xFF, // Boundary between alternate paths. + typeValidBits = ( + typeAccount + | typeCurrency + | typeIssuer + ), // Bits that may be non-zero. + }; protected: - int mType; - uint160 mNode; + int mType; + uint160 mAccountID; + uint160 mCurrencyID; + uint160 mIssuerID; public: - STPathElement(int type, const uint160& node) : mType(type), mNode(node) { ; } - int getNodeType() const { return mType; } - bool isAccount() const { return mType == typeAccount; } - bool isOffer() const { return mType == typeOffer; } + STPathElement(const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID, + bool forceCurrency = false) + : mAccountID(uAccountID), mCurrencyID(uCurrencyID), mIssuerID(uIssuerID) + { + mType = + (uAccountID.isZero() ? 0 : STPathElement::typeAccount) + | ((uCurrencyID.isZero() && !forceCurrency) ? 0 : STPathElement::typeCurrency) + | (uIssuerID.isZero() ? 0 : STPathElement::typeIssuer); + } + + int getNodeType() const { return mType; } + bool isOffer() const { return mAccountID.isZero(); } + bool isAccount() const { return !isOffer(); } // Nodes are either an account ID or a offer prefix. Offer prefixs denote a class of offers. - const uint160& getNode() const { return mNode; } + const uint160& getAccountID() const { return mAccountID; } + const uint160& getCurrency() const { return mCurrencyID; } + const uint160& getIssuerID() const { return mIssuerID; } - void setType(int type) { mType = type; } - void setNode(const uint160& n) { mNode = n; } + bool operator==(const STPathElement& t) const + { + return mType == t.mType && mAccountID == t.mAccountID && mCurrencyID == t.mCurrencyID && + mIssuerID == t.mIssuerID; + } }; class STPath @@ -555,32 +580,71 @@ public: const STPathElement& getElemet(int offset) { return mPath[offset]; } void addElement(const STPathElement& e) { mPath.push_back(e); } void clear() { mPath.clear(); } - int getSerializeSize() const { return 1 + mPath.size() * 21; } - std::string getText() const; + int getSerializeSize() const; +// std::string getText() const; Json::Value getJson(int) const; + + std::vector::iterator begin() { return mPath.begin(); } + std::vector::iterator end() { return mPath.end(); } std::vector::const_iterator begin() const { return mPath.begin(); } std::vector::const_iterator end() const { return mPath.end(); } + + bool operator==(const STPath& t) const { return mPath == t.mPath; } }; +inline std::vector::iterator range_begin(STPath & x) +{ + return x.begin(); +} + +inline std::vector::iterator range_end(STPath & x) +{ + return x.end(); +} + +inline std::vector::const_iterator range_begin(const STPath& x) +{ + return x.begin(); +} + +inline std::vector::const_iterator range_end(const STPath& x) +{ + return x.end(); +} + +namespace boost +{ + template<> + struct range_mutable_iterator< STPath > + { + typedef std::vector::iterator type; + }; + + template<> + struct range_const_iterator< STPath > + { + typedef std::vector::const_iterator type; + }; +} + class STPathSet : public SerializedType { // A set of zero or more payment paths protected: std::vector value; - STPathSet* duplicate() const { return new STPathSet(name, value); } - static STPathSet* construct(SerializerIterator&, const char* name = NULL); + STPathSet* duplicate() const { return new STPathSet(*this); } + static STPathSet* construct(SerializerIterator&, SField::ref); public: STPathSet() { ; } - STPathSet(const char* n) : SerializedType(n) { ; } + STPathSet(SField::ref n) : SerializedType(n) { ; } STPathSet(const std::vector& v) : value(v) { ; } - STPathSet(const char* n, const std::vector& v) : SerializedType(n), value(v) { ; } - static std::auto_ptr deserialize(SerializerIterator& sit, const char* name) + STPathSet(SField::ref n, const std::vector& v) : SerializedType(n), value(v) { ; } + static std::auto_ptr deserialize(SerializerIterator& sit, SField::ref name) { return std::auto_ptr(construct(sit, name)); } - int getLength() const; - std::string getText() const; +// std::string getText() const; void add(Serializer& s) const; virtual Json::Value getJson(int) const; @@ -592,6 +656,8 @@ public: void clear() { value.clear(); } void addPath(const STPath& e) { value.push_back(e); } + virtual bool isEquivalent(const SerializedType& t) const; + std::vector::iterator begin() { return value.begin(); } std::vector::iterator end() { return value.end(); } std::vector::const_iterator begin() const { return value.begin(); } @@ -633,77 +699,35 @@ namespace boost }; } -class STTaggedList : public SerializedType -{ -protected: - std::vector value; - - STTaggedList* duplicate() const { return new STTaggedList(name, value); } - static STTaggedList* construct(SerializerIterator&, const char* name = NULL); - -public: - - STTaggedList() { ; } - STTaggedList(const char* n) : SerializedType(n) { ; } - STTaggedList(const std::vector& v) : value(v) { ; } - STTaggedList(const char* n, const std::vector& v) : SerializedType(n), value(v) { ; } - static std::auto_ptr deserialize(SerializerIterator& sit, const char* name) - { return std::auto_ptr(construct(sit, name)); } - - int getLength() const; - SerializedTypeID getSType() const { return STI_TL; } - std::string getText() const; - void add(Serializer& s) const { if(s.addTaggedList(value)<0) throw(0); } - - const std::vector& peekValue() const { return value; } - std::vector& peekValue() { return value; } - std::vector getValue() const { return value; } - virtual Json::Value getJson(int) const; - - void setValue(const std::vector& v) { value=v; } - - int getItemCount() const { return value.size(); } - bool isEmpty() const { return value.empty(); } - - void clear() { value.erase(value.begin(), value.end()); } - void addItem(const TaggedListItem& v) { value.push_back(v); } - - operator std::vector() const { return value; } - STTaggedList& operator=(const std::vector& v) { value=v; return *this; } - virtual bool isEquivalent(const SerializedType& t) const; -}; - class STVector256 : public SerializedType { protected: std::vector mValue; - STVector256* duplicate() const { return new STVector256(name, mValue); } - static STVector256* construct(SerializerIterator&, const char* name = NULL); + STVector256* duplicate() const { return new STVector256(*this); } + static STVector256* construct(SerializerIterator&, SField::ref); public: STVector256() { ; } - STVector256(const char* n) : SerializedType(n) { ; } - STVector256(const char* n, const std::vector& v) : SerializedType(n), mValue(v) { ; } + STVector256(SField::ref n) : SerializedType(n) { ; } + STVector256(SField::ref n, const std::vector& v) : SerializedType(n), mValue(v) { ; } STVector256(const std::vector& vector) : mValue(vector) { ; } SerializedTypeID getSType() const { return STI_VECTOR256; } - int getLength() const { return Serializer::lengthVL(mValue.size() * (256 / 8)); } void add(Serializer& s) const; - static std::auto_ptr deserialize(SerializerIterator& sit, const char* name) + static std::auto_ptr deserialize(SerializerIterator& sit, SField::ref name) { return std::auto_ptr(construct(sit, name)); } const std::vector& peekValue() const { return mValue; } std::vector& peekValue() { return mValue; } virtual bool isEquivalent(const SerializedType& t) const; - std::vector getValue() const { return mValue; } - - bool isEmpty() const { return mValue.empty(); } - - void setValue(const STVector256& v) { mValue = v.mValue; } - void setValue(const std::vector& v) { mValue = v; } + std::vector getValue() const { return mValue; } + bool isEmpty() const { return mValue.empty(); } + void setValue(const STVector256& v) { mValue = v.mValue; } + void setValue(const std::vector& v) { mValue = v; } + void addValue(const uint256& v) { mValue.push_back(v); } Json::Value getJson(int) const; }; diff --git a/src/SerializedValidation.cpp b/src/SerializedValidation.cpp index a87aa141e5..84b40eb8ac 100644 --- a/src/SerializedValidation.cpp +++ b/src/SerializedValidation.cpp @@ -3,30 +3,39 @@ #include "HashPrefixes.h" -SOElement SerializedValidation::sValidationFormat[] = { - { sfFlags, "Flags", STI_UINT32, SOE_FLAGS, 0 }, - { sfLedgerHash, "LedgerHash", STI_HASH256, SOE_REQUIRED, 0 }, - { sfCloseTime, "CloseTime", STI_UINT32, SOE_REQUIRED, 0 }, - { sfSigningKey, "SigningKey", STI_VL, SOE_REQUIRED, 0 }, - { sfInvalid, NULL, STI_DONE, SOE_NEVER, -1 }, +std::vector sValidationFormat; + +static bool SVFInit() +{ + sValidationFormat.push_back(new SOElement(sfFlags, SOE_REQUIRED)); + sValidationFormat.push_back(new SOElement(sfLedgerHash, SOE_REQUIRED)); + sValidationFormat.push_back(new SOElement(sfLedgerSequence, SOE_OPTIONAL)); + sValidationFormat.push_back(new SOElement(sfCloseTime, SOE_OPTIONAL)); + sValidationFormat.push_back(new SOElement(sfLoadFee, SOE_OPTIONAL)); + sValidationFormat.push_back(new SOElement(sfBaseFee, SOE_OPTIONAL)); + sValidationFormat.push_back(new SOElement(sfSigningTime, SOE_REQUIRED)); + sValidationFormat.push_back(new SOElement(sfSigningPubKey, SOE_REQUIRED)); + return true; }; +bool SVFinitComplete = SVFInit(); + const uint32 SerializedValidation::sFullFlag = 0x00010000; SerializedValidation::SerializedValidation(SerializerIterator& sit, bool checkSignature) - : STObject(sValidationFormat, sit), mSignature(sit, "Signature"), mTrusted(false) + : STObject(sValidationFormat, sit, sfValidation), mSignature(sit, sfSignature), mTrusted(false) { if (checkSignature && !isValid()) throw std::runtime_error("Invalid validation"); } -SerializedValidation::SerializedValidation(const uint256& ledgerHash, uint32 closeTime, +SerializedValidation::SerializedValidation(const uint256& ledgerHash, uint32 signTime, const NewcoinAddress& naSeed, bool isFull) - : STObject(sValidationFormat), mSignature("Signature"), mTrusted(false) + : STObject(sValidationFormat, sfValidation), mSignature(sfSignature), mTrusted(false) { - setValueFieldH256(sfLedgerHash, ledgerHash); - setValueFieldU32(sfCloseTime, closeTime); + setFieldH256(sfLedgerHash, ledgerHash); + setFieldU32(sfSigningTime, signTime); if (naSeed.isValid()) - setValueFieldVL(sfSigningKey, NewcoinAddress::createNodePublic(naSeed).getNodePublic()); + setFieldVL(sfSigningPubKey, NewcoinAddress::createNodePublic(naSeed).getNodePublic()); if (!isFull) setFlag(sFullFlag); NewcoinAddress::createNodePrivate(naSeed).signNodePrivate(getSigningHash(), mSignature.peekValue()); @@ -47,17 +56,17 @@ uint256 SerializedValidation::getSigningHash() const uint256 SerializedValidation::getLedgerHash() const { - return getValueFieldH256(sfLedgerHash); + return getFieldH256(sfLedgerHash); } -uint32 SerializedValidation::getCloseTime() const +uint32 SerializedValidation::getSignTime() const { - return getValueFieldU32(sfCloseTime); + return getFieldU32(sfSigningTime); } uint32 SerializedValidation::getFlags() const { - return getValueFieldU32(sfFlags); + return getFieldU32(sfFlags); } bool SerializedValidation::isValid() const @@ -69,7 +78,7 @@ bool SerializedValidation::isValid(const uint256& signingHash) const { try { - NewcoinAddress naPublicKey = NewcoinAddress::createNodePublic(getValueFieldVL(sfSigningKey)); + NewcoinAddress naPublicKey = NewcoinAddress::createNodePublic(getFieldVL(sfSigningPubKey)); return naPublicKey.isValid() && naPublicKey.verifyNodePublic(signingHash, mSignature.peekValue()); } catch (...) @@ -81,7 +90,7 @@ bool SerializedValidation::isValid(const uint256& signingHash) const NewcoinAddress SerializedValidation::getSignerPublic() const { NewcoinAddress a; - a.setNodePublic(getValueFieldVL(sfSigningKey)); + a.setNodePublic(getFieldVL(sfSigningPubKey)); return a; } diff --git a/src/SerializedValidation.h b/src/SerializedValidation.h index dddd1fdce6..c76b233c54 100644 --- a/src/SerializedValidation.h +++ b/src/SerializedValidation.h @@ -8,24 +8,25 @@ class SerializedValidation : public STObject { protected: STVariableLength mSignature; + uint256 mPreviousHash; bool mTrusted; void setNode(); public: - typedef boost::shared_ptr pointer; + typedef boost::shared_ptr pointer; + typedef const boost::shared_ptr& ref; - static SOElement sValidationFormat[16]; static const uint32 sFullFlag; // These throw if the object is not valid SerializedValidation(SerializerIterator& sit, bool checkSignature = true); SerializedValidation(const Serializer& s, bool checkSignature = true); - SerializedValidation(const uint256& ledgerHash, uint32 closeTime, const NewcoinAddress& naSeed, bool isFull); + SerializedValidation(const uint256& ledgerHash, uint32 signTime, const NewcoinAddress& naSeed, bool isFull); uint256 getLedgerHash() const; - uint32 getCloseTime() const; + uint32 getSignTime() const; uint32 getFlags() const; NewcoinAddress getSignerPublic() const; bool isValid() const; @@ -39,6 +40,11 @@ public: void addSignature(Serializer&) const; std::vector getSigned() const; std::vector getSignature() const; + + // The validation this replaced + const uint256& getPreviousHash() { return mPreviousHash; } + bool isPreviousHash(const uint256& h) { return mPreviousHash == h; } + void setPreviousHash(const uint256& h) { mPreviousHash = h; } }; #endif diff --git a/src/Serializer.cpp b/src/Serializer.cpp index 51fd734668..cd7e0d6a8e 100644 --- a/src/Serializer.cpp +++ b/src/Serializer.cpp @@ -154,6 +154,57 @@ uint256 Serializer::get256(int offset) const return ret; } +int Serializer::addFieldID(int type, int name) +{ + int ret = mData.size(); + assert((type > 0) && (type < 256) && (name > 0) && (name < 256)); + if (type < 16) + { + if (name < 16) // common type, common name + mData.push_back(static_cast((type << 4) | name)); + else + { // common type, uncommon name + mData.push_back(static_cast(type << 4)); + mData.push_back(static_cast(name)); + } + } + else if (name < 16) + { // uncommon type, common name + mData.push_back(static_cast(name)); + mData.push_back(static_cast(type)); + } + else + { // uncommon type, uncommon name + mData.push_back(static_cast(0)); + mData.push_back(static_cast(type)); + mData.push_back(static_cast(name)); + } + return ret; +} + +bool Serializer::getFieldID(int& type, int & name, int offset) const +{ + if (!get8(type, offset)) + return false; + name = type & 15; + type >>= 4; + if (type == 0) + { // uncommon type + if (!get8(type, ++offset)) + return false; + if ((type == 0) || (type < 16)) + return false; + } + if (name == 0) + { // uncommon name + if (!get8(name, ++offset)) + return false; + if ((name == 0) || (name < 16)) + return false; + } + return true; +} + int Serializer::add8(unsigned char byte) { int ret = mData.size(); @@ -535,6 +586,17 @@ int SerializerIterator::getBytesLeft() return mSerializer.size() - mPos; } +void SerializerIterator::getFieldID(int& type, int& field) +{ + if (!mSerializer.getFieldID(type, field, mPos)) + throw std::runtime_error("invalid serializer getFieldID"); + ++mPos; + if (type >= 16) + ++mPos; + if (field >= 16) + ++mPos; +} + unsigned char SerializerIterator::get8() { int val; diff --git a/src/Serializer.h b/src/Serializer.h index 5c687e2984..8666efe4c3 100644 --- a/src/Serializer.h +++ b/src/Serializer.h @@ -9,6 +9,7 @@ #include "key.h" #include "uint256.h" +#include "FieldNames.h" typedef std::pair > TaggedListItem; @@ -67,6 +68,10 @@ public: bool getTaggedList(std::list&, int offset, int& length) const; bool getTaggedList(std::vector&, int offset, int& length) const; + bool getFieldID(int& type, int& name, int offset) const; + int addFieldID(int type, int name); + int addFieldID(SerializedTypeID type, int name) { return addFieldID(static_cast(type), name); } + // normal hash functions uint160 getRIPEMD160(int size=-1) const; uint256 getSHA256(int size=-1) const; @@ -89,7 +94,7 @@ public: int getDataLength() const { return mData.size(); } const void* getDataPtr() const { return &mData.front(); } void* getDataPtr() { return &mData.front(); } - int getLength() { return mData.size(); } + int getLength() const { return mData.size(); } const std::vector& peekData() const { return mData; } std::vector getData() const { return mData; } std::string getString() const { return std::string(static_cast(getDataPtr()), size()); } @@ -140,11 +145,12 @@ protected: public: SerializerIterator(const Serializer& s) : mSerializer(s), mPos(0) { ; } - void reset(void) { mPos=0; } + void reset(void) { mPos = 0; } void setPos(int p) { mPos = p; } const Serializer& operator*(void) { return mSerializer; } int getPos(void) { return mPos; } + bool empty() { return mPos == mSerializer.getLength(); } int getBytesLeft(); // get functions throw on error @@ -156,6 +162,8 @@ public: uint160 get160(); uint256 get256(); + void getFieldID(int& type, int& field); + std::vector getRaw(int iLength); std::vector getVL(); diff --git a/src/Suppression.cpp b/src/Suppression.cpp index c285d5f349..4b7cad5711 100644 --- a/src/Suppression.cpp +++ b/src/Suppression.cpp @@ -1,6 +1,7 @@ - #include "Suppression.h" +#include + bool SuppressionTable::addSuppression(const uint160& suppression) { boost::mutex::scoped_lock sl(mSuppressionMutex); @@ -9,15 +10,16 @@ bool SuppressionTable::addSuppression(const uint160& suppression) return false; time_t now = time(NULL); + time_t expireTime = now - mHoldTime; - boost::unordered_map< time_t, std::list >::iterator it = mSuppressionTimes.begin(); - while (it != mSuppressionTimes.end()) + boost::unordered_map< time_t, std::list >::iterator + it = mSuppressionTimes.begin(), end = mSuppressionTimes.end(); + while (it != end) { - if ((it->first + mHoldTime) < now) + if (it->first <= expireTime) { - for (std::list::iterator lit = it->second.begin(), end = it->second.end(); - lit != end; ++lit) - mSuppressionMap.erase(*lit); + BOOST_FOREACH(const uint160& lit, it->second) + mSuppressionMap.erase(lit); it = mSuppressionTimes.erase(it); } else ++it; diff --git a/src/TaggedCache.h b/src/TaggedCache.h index bb5dc36667..fb31a157b5 100644 --- a/src/TaggedCache.h +++ b/src/TaggedCache.h @@ -1,8 +1,6 @@ #ifndef __TAGGEDCACHE__ #define __TAGGEDCACHE__ -#include - #include #include #include @@ -21,18 +19,18 @@ template class TaggedCache { public: - typedef c_Key key_type; - typedef c_Data data_type; + typedef c_Key key_type; + typedef c_Data data_type; - typedef boost::weak_ptr weak_data_ptr; - typedef boost::shared_ptr data_ptr; - - typedef std::pair cache_entry; + typedef boost::weak_ptr weak_data_ptr; + typedef boost::shared_ptr data_ptr; + typedef std::pair cache_entry; protected: mutable boost::recursive_mutex mLock; int mTargetSize, mTargetAge; + boost::unordered_map mCache; // Hold strong reference to recent objects time_t mLastSweep; @@ -40,6 +38,7 @@ protected: public: TaggedCache(int size, int age) : mTargetSize(size), mTargetAge(age), mLastSweep(time(NULL)) { ; } + int getTargetSize() const; int getTargetAge() const; @@ -52,7 +51,7 @@ public: bool touch(const key_type& key); bool del(const key_type& key); - bool canonicalize(const key_type& key, boost::shared_ptr& data); + bool canonicalize(const key_type& key, boost::shared_ptr& data, bool replace = false); bool store(const key_type& key, const c_Data& data); boost::shared_ptr fetch(const key_type& key); bool retrieve(const key_type& key, c_Data& data); @@ -82,10 +81,12 @@ template void TaggedCache::sweep { boost::recursive_mutex::scoped_lock sl(mLock); - if (mCache.size() < mTargetSize) return; + if (mCache.size() < mTargetSize) + return; time_t now = time(NULL); - if ((mLastSweep + 10) < now) return; + if ((mLastSweep + 10) < now) + return; mLastSweep = now; time_t target = now - mTargetAge; @@ -99,7 +100,8 @@ template void TaggedCache::sweep typename boost::unordered_map::iterator tmp = cit++; mCache.erase(tmp); } - else ++cit; + else + ++cit; } // Pass 2, remove dead objects from map @@ -109,9 +111,10 @@ template void TaggedCache::sweep if (mit->second->expired()) { typename boost::unordered_map::iterator tmp = mit++; - mMap.erase(mit); + mMap.erase(mit++); } - else ++mit; + else + ++mit; } } @@ -121,7 +124,8 @@ template bool TaggedCache::touch // Is the object in the map? typename boost::unordered_map::iterator mit = mMap.find(key); - if (mit == mMap.end()) return false; + if (mit == mMap.end()) + return false; if (mit->second.expired()) { // in map, but expired mMap.erase(mit); @@ -146,13 +150,14 @@ template bool TaggedCache::del(c boost::recursive_mutex::scoped_lock sl(mLock); typename boost::unordered_map::iterator cit = mCache.find(key); - if (cit == mCache.end()) return false; + if (cit == mCache.end()) + return false; mCache.erase(cit); return true; } template -bool TaggedCache::canonicalize(const key_type& key, boost::shared_ptr& data) +bool TaggedCache::canonicalize(const key_type& key, boost::shared_ptr& data, bool replace) { // Return canonical value, store if needed, refresh in cache // Return values: true=we had the data already boost::recursive_mutex::scoped_lock sl(mLock); @@ -172,13 +177,21 @@ bool TaggedCache::canonicalize(const key_type& key, boost::shared mCache.insert(std::make_pair(key, std::make_pair(time(NULL), data))); return false; } - - data = cachedData; + + // in map and cache, canonicalize + if (replace) + mit->second = data; + else + data = cachedData; // Valid in map, is it in cache? typename boost::unordered_map::iterator cit = mCache.find(key); if (cit != mCache.end()) + { cit->second.first = time(NULL); // Yes, refesh + if (replace) + cit->second.second = data; + } else // no, add to cache mCache.insert(std::make_pair(key, std::make_pair(time(NULL), data))); @@ -192,7 +205,8 @@ boost::shared_ptr TaggedCache::fetch(const key_type& key) // Is it in the map? typename boost::unordered_map::iterator mit = mMap.find(key); - if (mit == mMap.end()) return data_ptr(); // No, we're done + if (mit == mMap.end()) + return data_ptr(); // No, we're done boost::shared_ptr cachedData = mit->second.lock(); if (!cachedData) diff --git a/src/Transaction.cpp b/src/Transaction.cpp index d600bfefae..e56e12f0f0 100644 --- a/src/Transaction.cpp +++ b/src/Transaction.cpp @@ -13,12 +13,12 @@ #include "SerializedTransaction.h" #include "Log.h" -Transaction::Transaction(const SerializedTransaction::pointer sit, bool bValidate) +Transaction::Transaction(const SerializedTransaction::pointer& sit, bool bValidate) : mInLedger(0), mStatus(INVALID), mTransaction(sit) { try { - mFromPubKey.setAccountPublic(mTransaction->peekSigningPubKey()); + mFromPubKey.setAccountPublic(mTransaction->getSigningPubKey()); mTransactionID = mTransaction->getTransactionID(); mAccountFrom = mTransaction->getSourceAccount(); } @@ -68,8 +68,8 @@ Transaction::Transaction( mTransaction = boost::make_shared(ttKind); - Log(lsINFO) << str(boost::format("Transaction: account: %s") % naSourceAccount.humanAccountID()); - Log(lsINFO) << str(boost::format("Transaction: mAccountFrom: %s") % mAccountFrom.humanAccountID()); + // Log(lsINFO) << str(boost::format("Transaction: account: %s") % naSourceAccount.humanAccountID()); + // Log(lsINFO) << str(boost::format("Transaction: mAccountFrom: %s") % mAccountFrom.humanAccountID()); mTransaction->setSigningPubKey(mFromPubKey); mTransaction->setSourceAccount(mAccountFrom); @@ -78,8 +78,8 @@ Transaction::Transaction( if (uSourceTag) { - mTransaction->makeITFieldPresent(sfSourceTag); - mTransaction->setITFieldU32(sfSourceTag, uSourceTag); + mTransaction->makeFieldPresent(sfSourceTag); + mTransaction->setFieldU32(sfSourceTag, uSourceTag); } } @@ -92,12 +92,7 @@ bool Transaction::sign(const NewcoinAddress& naAccountPrivate) Log(lsWARNING) << "No private key for signing"; bResult = false; } - else if (!getSTransaction()->sign(naAccountPrivate)) - { - Log(lsWARNING) << "Failed to make signature"; - assert(false); - bResult = false; - } + getSTransaction()->sign(naAccountPrivate); if (bResult) { @@ -132,24 +127,24 @@ Transaction::pointer Transaction::setAccountSet( ) { if (!bEmailHash) - mTransaction->setITFieldH128(sfEmailHash, uEmailHash); + mTransaction->setFieldH128(sfEmailHash, uEmailHash); if (!bWalletLocator) - mTransaction->setITFieldH256(sfWalletLocator, uWalletLocator); + mTransaction->setFieldH256(sfWalletLocator, uWalletLocator); if (naMessagePublic.isValid()) - mTransaction->setITFieldVL(sfMessageKey, naMessagePublic.getAccountPublic()); + mTransaction->setFieldVL(sfMessageKey, naMessagePublic.getAccountPublic()); if (bDomain) - mTransaction->setITFieldVL(sfDomain, vucDomain); + mTransaction->setFieldVL(sfDomain, vucDomain); if (bTransferRate) - mTransaction->setITFieldU32(sfTransferRate, uTransferRate); + mTransaction->setFieldU32(sfTransferRate, uTransferRate); if (bPublish) { - mTransaction->setITFieldH256(sfPublishHash, uPublishHash); - mTransaction->setITFieldU32(sfPublishSize, uPublishSize); + mTransaction->setFieldH256(sfPublishHash, uPublishHash); + mTransaction->setFieldU32(sfPublishSize, uPublishSize); } sign(naPrivateKey); @@ -193,9 +188,9 @@ Transaction::pointer Transaction::setClaim( const std::vector& vucPubKey, const std::vector& vucSignature) { - mTransaction->setITFieldVL(sfGenerator, vucGenerator); - mTransaction->setITFieldVL(sfPubKey, vucPubKey); - mTransaction->setITFieldVL(sfSignature, vucSignature); + mTransaction->setFieldVL(sfGenerator, vucGenerator); + mTransaction->setFieldVL(sfPublicKey, vucPubKey); + mTransaction->setFieldVL(sfSignature, vucSignature); sign(naPrivateKey); @@ -227,9 +222,9 @@ Transaction::pointer Transaction::setCreate( const NewcoinAddress& naCreateAccountID, const STAmount& saFund) { - mTransaction->setITFieldU32(sfFlags, tfCreateAccount); - mTransaction->setITFieldAccount(sfDestination, naCreateAccountID); - mTransaction->setITFieldAmount(sfAmount, saFund); + mTransaction->setFieldU32(sfFlags, tfCreateAccount); + mTransaction->setFieldAccount(sfDestination, naCreateAccountID); + mTransaction->setFieldAmount(sfAmount, saFund); sign(naPrivateKey); @@ -256,24 +251,19 @@ Transaction::pointer Transaction::sharedCreate( Transaction::pointer Transaction::setCreditSet( const NewcoinAddress& naPrivateKey, - const NewcoinAddress& naDstAccountID, - bool bLimitAmount, const STAmount& saLimitAmount, bool bQualityIn, uint32 uQualityIn, bool bQualityOut, uint32 uQualityOut) { - mTransaction->setITFieldAccount(sfDestination, naDstAccountID); - - if (bLimitAmount) - mTransaction->setITFieldAmount(sfLimitAmount, saLimitAmount); + mTransaction->setFieldAmount(sfLimitAmount, saLimitAmount); if (bQualityIn) - mTransaction->setITFieldU32(sfAcceptRate, uQualityIn); + mTransaction->setFieldU32(sfQualityIn, uQualityIn); if (bQualityOut) - mTransaction->setITFieldU32(sfAcceptRate, uQualityOut); + mTransaction->setFieldU32(sfQualityOut, uQualityOut); sign(naPrivateKey); @@ -286,8 +276,6 @@ Transaction::pointer Transaction::sharedCreditSet( uint32 uSeq, const STAmount& saFee, uint32 uSourceTag, - const NewcoinAddress& naDstAccountID, - bool bLimitAmount, const STAmount& saLimitAmount, bool bQualityIn, uint32 uQualityIn, @@ -296,8 +284,8 @@ Transaction::pointer Transaction::sharedCreditSet( { pointer tResult = boost::make_shared(ttCREDIT_SET, naPublicKey, naSourceAccount, uSeq, saFee, uSourceTag); - return tResult->setCreditSet(naPrivateKey, naDstAccountID, - bLimitAmount, saLimitAmount, + return tResult->setCreditSet(naPrivateKey, + saLimitAmount, bQualityIn, uQualityIn, bQualityOut, uQualityOut); } @@ -310,17 +298,13 @@ Transaction::pointer Transaction::setNicknameSet( const NewcoinAddress& naPrivateKey, const uint256& uNickname, bool bSetOffer, - const STAmount& saMinimumOffer, - const std::vector& vucSignature) + const STAmount& saMinimumOffer) { - mTransaction->setITFieldH256(sfNickname, uNickname); + mTransaction->setFieldH256(sfNickname, uNickname); // XXX Make sure field is present even for 0! if (bSetOffer) - mTransaction->setITFieldAmount(sfMinimumOffer, saMinimumOffer); - - if (!vucSignature.empty()) - mTransaction->setITFieldVL(sfSignature, vucSignature); + mTransaction->setFieldAmount(sfMinimumOffer, saMinimumOffer); sign(naPrivateKey); @@ -337,12 +321,11 @@ Transaction::pointer Transaction::sharedNicknameSet( uint32 uSourceTag, const uint256& uNickname, bool bSetOffer, - const STAmount& saMinimumOffer, - const std::vector& vucSignature) + const STAmount& saMinimumOffer) { pointer tResult = boost::make_shared(ttNICKNAME_SET, naPublicKey, naSourceAccount, uSeq, saFee, uSourceTag); - return tResult->setNicknameSet(naPrivateKey, uNickname, bSetOffer, saMinimumOffer, vucSignature); + return tResult->setNicknameSet(naPrivateKey, uNickname, bSetOffer, saMinimumOffer); } // @@ -357,19 +340,13 @@ Transaction::pointer Transaction::setOfferCreate( uint32 uExpiration) { if (bPassive) - mTransaction->setITFieldU32(sfFlags, tfPassive); + mTransaction->setFieldU32(sfFlags, tfPassive); - mTransaction->setITFieldAmount(sfTakerPays, saTakerPays); - mTransaction->setITFieldAmount(sfTakerGets, saTakerGets); - - if (!saTakerPays.isNative()) - mTransaction->setITFieldAccount(sfPaysIssuer, saTakerPays.getIssuer()); - - if (!saTakerGets.isNative()) - mTransaction->setITFieldAccount(sfGetsIssuer, saTakerGets.getIssuer()); + mTransaction->setFieldAmount(sfTakerPays, saTakerPays); + mTransaction->setFieldAmount(sfTakerGets, saTakerGets); if (uExpiration) - mTransaction->setITFieldU32(sfExpiration, uExpiration); + mTransaction->setFieldU32(sfExpiration, uExpiration); sign(naPrivateKey); @@ -400,7 +377,7 @@ Transaction::pointer Transaction::setOfferCancel( const NewcoinAddress& naPrivateKey, uint32 uSequence) { - mTransaction->setITFieldU32(sfOfferSequence, uSequence); + mTransaction->setFieldU32(sfOfferSequence, uSequence); sign(naPrivateKey); @@ -428,7 +405,7 @@ Transaction::pointer Transaction::setPasswordFund( const NewcoinAddress& naPrivateKey, const NewcoinAddress& naDstAccountID) { - mTransaction->setITFieldAccount(sfDestination, naDstAccountID); + mTransaction->setFieldAccount(sfDestination, naDstAccountID); sign(naPrivateKey); @@ -459,10 +436,10 @@ Transaction::pointer Transaction::setPasswordSet( const std::vector& vucPubKey, const std::vector& vucSignature) { - mTransaction->setITFieldAccount(sfAuthorizedKey, naAuthKeyID); - mTransaction->setITFieldVL(sfGenerator, vucGenerator); - mTransaction->setITFieldVL(sfPubKey, vucPubKey); - mTransaction->setITFieldVL(sfSignature, vucSignature); + mTransaction->setFieldAccount(sfAuthorizedKey, naAuthKeyID); + mTransaction->setFieldVL(sfGenerator, vucGenerator); + mTransaction->setFieldVL(sfPublicKey, vucPubKey); + mTransaction->setFieldVL(sfSignature, vucSignature); sign(naPrivateKey); @@ -495,19 +472,21 @@ Transaction::pointer Transaction::setPayment( const NewcoinAddress& naDstAccountID, const STAmount& saAmount, const STAmount& saSendMax, - const STPathSet& spPaths) + const STPathSet& spsPaths, + const bool bPartial, + const bool bLimit) { - mTransaction->setITFieldAccount(sfDestination, naDstAccountID); - mTransaction->setITFieldAmount(sfAmount, saAmount); + mTransaction->setFieldAccount(sfDestination, naDstAccountID); + mTransaction->setFieldAmount(sfAmount, saAmount); - if (saAmount != saSendMax) + if (saAmount != saSendMax || saAmount.getCurrency() != saSendMax.getCurrency()) { - mTransaction->setITFieldAmount(sfSendMax, saSendMax); + mTransaction->setFieldAmount(sfSendMax, saSendMax); } - if (spPaths.getPathCount()) + if (spsPaths.getPathCount()) { - mTransaction->setITFieldPathSet(sfPaths, spPaths); + mTransaction->setFieldPathSet(sfPaths, spsPaths); } sign(naPrivateKey); @@ -524,11 +503,13 @@ Transaction::pointer Transaction::sharedPayment( const NewcoinAddress& naDstAccountID, const STAmount& saAmount, const STAmount& saSendMax, - const STPathSet& saPaths) + const STPathSet& spsPaths, + const bool bPartial, + const bool bLimit) { pointer tResult = boost::make_shared(ttPAYMENT, naPublicKey, naSourceAccount, uSeq, saFee, uSourceTag); - return tResult->setPayment(naPrivateKey, naDstAccountID, saAmount, saSendMax, saPaths); + return tResult->setPayment(naPrivateKey, naDstAccountID, saAmount, saSendMax, spsPaths, bPartial, bLimit); } // @@ -542,10 +523,10 @@ Transaction::pointer Transaction::setWalletAdd( const NewcoinAddress& naNewPubKey, const std::vector& vucSignature) { - mTransaction->setITFieldAmount(sfAmount, saAmount); - mTransaction->setITFieldAccount(sfAuthorizedKey, naAuthKeyID); - mTransaction->setITFieldVL(sfPubKey, naNewPubKey.getAccountPublic()); - mTransaction->setITFieldVL(sfSignature, vucSignature); + mTransaction->setFieldAmount(sfAmount, saAmount); + mTransaction->setFieldAccount(sfAuthorizedKey, naAuthKeyID); + mTransaction->setFieldVL(sfPublicKey, naNewPubKey.getAccountPublic()); + mTransaction->setFieldVL(sfSignature, vucSignature); sign(naPrivateKey); @@ -584,7 +565,7 @@ void Transaction::setStatus(TransStatus ts, uint32 lseq) mInLedger = lseq; } -void Transaction::saveTransaction(Transaction::pointer txn) +void Transaction::saveTransaction(const Transaction::pointer& txn) { txn->save(); } diff --git a/src/Transaction.h b/src/Transaction.h index 745dce6d93..9ae05685d6 100644 --- a/src/Transaction.h +++ b/src/Transaction.h @@ -74,8 +74,6 @@ private: Transaction::pointer setCreditSet( const NewcoinAddress& naPrivateKey, - const NewcoinAddress& naDstAccountID, - bool bLimitAmount, const STAmount& saLimitAmount, bool bQualityIn, uint32 uQualityIn, @@ -86,8 +84,7 @@ private: const NewcoinAddress& naPrivateKey, const uint256& uNickname, bool bSetOffer, - const STAmount& saMinimumOffer, - const std::vector& vucSignature); + const STAmount& saMinimumOffer); Transaction::pointer setOfferCreate( const NewcoinAddress& naPrivateKey, @@ -116,7 +113,9 @@ private: const NewcoinAddress& naDstAccountID, const STAmount& saAmount, const STAmount& saSendMax, - const STPathSet& spPaths); + const STPathSet& spsPaths, + const bool bPartial, + const bool bLimit); Transaction::pointer setWalletAdd( const NewcoinAddress& naPrivateKey, @@ -126,7 +125,7 @@ private: const std::vector& vucSignature); public: - Transaction(const SerializedTransaction::pointer st, bool bValidate); + Transaction(const SerializedTransaction::pointer& st, bool bValidate); static Transaction::pointer sharedTransaction(const std::vector&vucTransaction, bool bValidate); @@ -183,8 +182,6 @@ public: uint32 uSeq, const STAmount& saFee, uint32 uSourceTag, - const NewcoinAddress& naDstAccountID, - bool bLimitAmount, const STAmount& saLimitAmount, bool bQualityIn, uint32 uQualityIn, @@ -200,8 +197,7 @@ public: uint32 uSourceTag, const uint256& uNickname, bool bSetOffer, - const STAmount& saMinimumOffer, - const std::vector& vucSignature); + const STAmount& saMinimumOffer); // Pre-fund password change. static Transaction::pointer sharedPasswordFund( @@ -231,7 +227,9 @@ public: const NewcoinAddress& naDstAccountID, const STAmount& saAmount, const STAmount& saSendMax, - const STPathSet& saPaths); + const STPathSet& spsPaths, + const bool bPartial = false, + const bool bLimit = false); // Place an offer. static Transaction::pointer sharedOfferCreate( @@ -272,23 +270,22 @@ public: SerializedTransaction::pointer getSTransaction() { return mTransaction; } - const uint256& getID() const { return mTransactionID; } - const NewcoinAddress& getFromAccount() const { return mAccountFrom; } - STAmount getAmount() const { return mTransaction->getITFieldU64(sfAmount); } - STAmount getFee() const { return mTransaction->getTransactionFee(); } - uint32 getFromAccountSeq() const { return mTransaction->getSequence(); } - uint32 getSourceLedger() const { return mTransaction->getITFieldU32(sfTargetLedger); } - uint32 getIdent() const { return mTransaction->getITFieldU32(sfSourceTag); } - std::vector getSignature() const { return mTransaction->getSignature(); } - uint32 getLedger() const { return mInLedger; } - TransStatus getStatus() const { return mStatus; } + const uint256& getID() const { return mTransactionID; } + const NewcoinAddress& getFromAccount() const { return mAccountFrom; } + STAmount getAmount() const { return mTransaction->getFieldU64(sfAmount); } + STAmount getFee() const { return mTransaction->getTransactionFee(); } + uint32 getFromAccountSeq() const { return mTransaction->getSequence(); } + uint32 getIdent() const { return mTransaction->getFieldU32(sfSourceTag); } + std::vector getSignature() const { return mTransaction->getSignature(); } + uint32 getLedger() const { return mInLedger; } + TransStatus getStatus() const { return mStatus; } void setStatus(TransStatus status, uint32 ledgerSeq); void setStatus(TransStatus status) { mStatus=status; } void setLedger(uint32 ledger) { mInLedger = ledger; } // database functions - static void saveTransaction(Transaction::pointer); + static void saveTransaction(const Transaction::pointer&); bool save(); static Transaction::pointer load(const uint256& id); static Transaction::pointer findFrom(const NewcoinAddress& fromID, uint32 seq); diff --git a/src/TransactionAction.cpp b/src/TransactionAction.cpp new file mode 100644 index 0000000000..35ed8397dc --- /dev/null +++ b/src/TransactionAction.cpp @@ -0,0 +1,1197 @@ +// +// XXX Make sure all fields are recognized in transactions. +// + +#include +#include +#include +#include + +#include "TransactionEngine.h" + +#include "../json/writer.h" + +#include "Config.h" +#include "Contract.h" +#include "Interpreter.h" +#include "Log.h" +#include "RippleCalc.h" +#include "TransactionFormats.h" +#include "utils.h" + +#define RIPPLE_PATHS_MAX 3 + +// Set the authorized public key for an account. May also set the generator map. +TER TransactionEngine::setAuthorized(const SerializedTransaction& txn, bool bMustSetGenerator) +{ + // + // Verify that submitter knows the private key for the generator. + // Otherwise, people could deny access to generators. + // + + std::vector vucCipher = txn.getFieldVL(sfGenerator); + std::vector vucPubKey = txn.getFieldVL(sfPublicKey); + std::vector vucSignature = txn.getFieldVL(sfSignature); + NewcoinAddress naAccountPublic = NewcoinAddress::createAccountPublic(vucPubKey); + + if (!naAccountPublic.accountPublicVerify(Serializer::getSHA512Half(vucCipher), vucSignature)) + { + Log(lsWARNING) << "createGenerator: bad signature unauthorized generator claim"; + + return tefBAD_GEN_AUTH; + } + + // Create generator. + uint160 hGeneratorID = naAccountPublic.getAccountID(); + + SLE::pointer sleGen = entryCache(ltGENERATOR_MAP, Ledger::getGeneratorIndex(hGeneratorID)); + if (!sleGen) + { + // Create the generator. + Log(lsTRACE) << "createGenerator: creating generator"; + + sleGen = entryCreate(ltGENERATOR_MAP, Ledger::getGeneratorIndex(hGeneratorID)); + + sleGen->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 + + 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; + } + } + + // + // PublishHash && PublishSize + // + + bool bPublishHash = txn.isFieldPresent(sfPublishHash); + bool bPublishSize = txn.isFieldPresent(sfPublishSize); + + if (bPublishHash ^ bPublishSize) + { + Log(lsINFO) << "doAccountSet: bad publish"; + + return temBAD_PUBLISH; + } + else if (bPublishHash && bPublishSize) + { + uint256 uHash = txn.getFieldH256(sfPublishHash); + uint32 uSize = txn.getFieldU32(sfPublishSize); + + if (!uHash) + { + Log(lsINFO) << "doAccountSet: unset publish"; + + mTxnAccount->makeFieldAbsent(sfPublishHash); + mTxnAccount->makeFieldAbsent(sfPublishSize); + } + else + { + Log(lsINFO) << "doAccountSet: set publish"; + + mTxnAccount->setFieldH256(sfPublishHash, uHash); + mTxnAccount->setFieldU32(sfPublishSize, uSize); + } + } + + Log(lsINFO) << "doAccountSet<"; + + return tesSUCCESS; +} + +TER TransactionEngine::doClaim(const SerializedTransaction& txn) +{ + Log(lsINFO) << "doClaim>"; + + TER terResult = setAuthorized(txn, true); + + Log(lsINFO) << "doClaim<"; + + return terResult; +} + +TER TransactionEngine::doCreditSet(const SerializedTransaction& txn) +{ + TER terResult = tesSUCCESS; + Log(lsINFO) << "doCreditSet>"; + + 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 (!uDstAccountID) + { + Log(lsINFO) << "doCreditSet: Malformed transaction: Destination account not specifed."; + + return temDST_NEEDED; + } + else if (mTxnAccountID == uDstAccountID) + { + Log(lsINFO) << "doCreditSet: 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) << "doCreditSet: 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) << "doCreditSet: Modifying ripple line: bDelIndex=" << bDelIndex; + } + // Line does not exist. + else if (!saLimitAmount) + { + Log(lsINFO) << "doCreditSet: Redundant: Setting non-existant ripple line to 0."; + + return terNO_LINE_NO_ZERO; + } + else + { + // Create a new ripple line. + sleRippleState = entryCreate(ltRIPPLE_STATE, Ledger::getRippleStateIndex(mTxnAccountID, uDstAccountID, uCurrencyID)); + + Log(lsINFO) << "doCreditSet: Creating ripple line: " << sleRippleState->getIndex().ToString(); + + sleRippleState->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) << "doCreditSet<"; + + return terResult; +} + +TER TransactionEngine::doNicknameSet(const SerializedTransaction& txn) +{ + std::cerr << "doNicknameSet>" << std::endl; + + const uint256 uNickname = txn.getFieldH256(sfNickname); + const bool bMinOffer = txn.isFieldPresent(sfMinimumOffer); + const STAmount saMinOffer = bMinOffer ? txn.getFieldAmount(sfAmount) : STAmount(); + + SLE::pointer sleNickname = entryCache(ltNICKNAME, uNickname); + + if (sleNickname) + { + // Edit old entry. + sleNickname->setFieldAccount(sfAccount, mTxnAccountID); + + if (bMinOffer && saMinOffer) + { + sleNickname->setFieldAmount(sfMinimumOffer, saMinOffer); + } + else + { + sleNickname->makeFieldAbsent(sfMinimumOffer); + } + + entryModify(sleNickname); + } + else + { + // Make a new entry. + // XXX Need to include authorization limiting for first year. + + sleNickname = entryCreate(ltNICKNAME, Ledger::getNicknameIndex(uNickname)); + + std::cerr << "doNicknameSet: Creating nickname node: " << sleNickname->getIndex().ToString() << std::endl; + + sleNickname->setFieldAccount(sfAccount, mTxnAccountID); + + if (bMinOffer && saMinOffer) + sleNickname->setFieldAmount(sfMinimumOffer, saMinOffer); + } + + std::cerr << "doNicknameSet<" << std::endl; + + return tesSUCCESS; +} + +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; +} + +TER TransactionEngine::doPasswordSet(const SerializedTransaction& txn) +{ + std::cerr << "doPasswordSet>" << std::endl; + + if (mTxnAccount->getFlags() & lsfPasswordSpent) + { + std::cerr << "doPasswordSet: Delay transaction: Funds already spent." << std::endl; + + return terFUNDS_SPENT; + } + + mTxnAccount->setFlag(lsfPasswordSpent); + + TER terResult = setAuthorized(txn, false); + + std::cerr << "doPasswordSet<" << std::endl; + + return terResult; +} + + +// XXX Need to audit for things like setting accountID not having memory. +TER TransactionEngine::doPayment(const SerializedTransaction& txn, const TransactionEngineParams params) +{ + // Ripple if source or destination is non-native or if there are paths. + const uint32 uTxFlags = txn.getFlags(); + const bool bCreate = isSetBit(uTxFlags, tfCreateAccount); + const bool bPartialPayment = isSetBit(uTxFlags, tfPartialPayment); + const bool bLimitQuality = isSetBit(uTxFlags, tfLimitQuality); + const bool bNoRippleDirect = isSetBit(uTxFlags, tfNoRippleDirect); + const bool bPaths = txn.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; + const uint160 uSrcCurrency = saMaxAmount.getCurrency(); + const uint160 uDstCurrency = saDstAmount.getCurrency(); + + Log(lsINFO) << boost::str(boost::format("doPayment> saMaxAmount=%s saDstAmount=%s") + % saMaxAmount.getFullText() + % saDstAmount.getFullText()); + + if (!uDstAccountID) + { + Log(lsINFO) << "doPayment: Invalid transaction: Payment destination account not specifed."; + + return temDST_NEEDED; + } + else if (!saDstAmount.isPositive()) + { + Log(lsINFO) << "doPayment: Invalid transaction: bad amount: " << saDstAmount.getHumanCurrency() << " " << saDstAmount.getText(); + + return temBAD_AMOUNT; + } + else if (mTxnAccountID == uDstAccountID && uSrcCurrency == uDstCurrency && !bPaths) + { + Log(lsINFO) << boost::str(boost::format("doPayment: Invalid transaction: Redunant transaction: src=%s, dst=%s, src_cur=%s, dst_cur=%s") + % mTxnAccountID.ToString() + % uDstAccountID.ToString() + % uSrcCurrency.ToString() + % uDstCurrency.ToString()); + + return temREDUNDANT; + } + else if (bMax + && ((saMaxAmount == saDstAmount && saMaxAmount.getCurrency() == saDstAmount.getCurrency()) + || (saDstAmount.isNative() && saMaxAmount.isNative()))) + { + Log(lsINFO) << "doPayment: Invalid transaction: bad SendMax."; + + return temINVALID; + } + + SLE::pointer sleDst = entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uDstAccountID)); + if (!sleDst) + { + // Destination account does not exist. + if (bCreate && !saDstAmount.isNative()) + { + // This restriction could be relaxed. + Log(lsINFO) << "doPayment: Invalid transaction: Create account may only fund XNS."; + + return temCREATEXNS; + } + else if (!bCreate) + { + Log(lsINFO) << "doPayment: Delay transaction: Destination account does not exist."; + + return terNO_DST; + } + + // Create the account. + sleDst = entryCreate(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uDstAccountID)); + + sleDst->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 XNS payment. + + STAmount saSrcXNSBalance = mTxnAccount->getFieldAmount(sfBalance); + + if (saSrcXNSBalance < saDstAmount) + { + // Transaction might succeed, if applied in a different order. + Log(lsINFO) << "doPayment: Delay transaction: Insufficent funds."; + + terResult = terUNFUNDED; + } + else + { + mTxnAccount->setFieldAmount(sfBalance, saSrcXNSBalance - 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 NewcoinAddress naMasterPubKey = NewcoinAddress::createAccountPublic(vucPubKey); + const uint160 uDstAccountID = naMasterPubKey.getAccountID(); + + if (!naMasterPubKey.accountPublicVerify(Serializer::getSHA512Half(uAuthKeyID.begin(), uAuthKeyID.size()), vucSignature)) + { + std::cerr << "WalletAdd: unauthorized: bad signature " << std::endl; + + return tefBAD_ADD_AUTH; + } + + SLE::pointer sleDst = entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uDstAccountID)); + + if (sleDst) + { + std::cerr << "WalletAdd: account already created" << std::endl; + + return tefCREATED; + } + + STAmount saAmount = txn.getFieldAmount(sfAmount); + STAmount saSrcBalance = mTxnAccount->getFieldAmount(sfBalance); + + if (saSrcBalance < saAmount) + { + std::cerr + << boost::str(boost::format("WalletAdd: Delay transaction: insufficent balance: balance=%s amount=%s") + % saSrcBalance.getText() + % saAmount.getText()) + << std::endl; + + return terUNFUNDED; + } + + // Deduct initial balance from source account. + mTxnAccount->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; +} + +TER TransactionEngine::doInvoice(const SerializedTransaction& txn) +{ + return temUNKNOWN; +} + +// Take as much as possible. Adjusts account balances. Charges fees on top to taker. +// --> uBookBase: The order book to take against. +// --> saTakerPays: What the taker offers (w/ issuer) +// --> saTakerGets: What the taker wanted (w/ issuer) +// <-- saTakerPaid: What taker paid not including fees. To reduce an offer. +// <-- saTakerGot: What taker got not including fees. To reduce an offer. +// <-- terResult: tesSUCCESS or terNO_ACCOUNT +// XXX: Fees should be paid by the source of the currency. +TER TransactionEngine::takeOffers( + bool bPassive, + const uint256& uBookBase, + const uint160& uTakerAccountID, + const SLE::pointer& sleTakerAccount, + const STAmount& saTakerPays, + const STAmount& saTakerGets, + STAmount& saTakerPaid, + STAmount& saTakerGot) +{ + assert(saTakerPays && saTakerGets); + + Log(lsINFO) << "takeOffers: against book: " << uBookBase.ToString(); + + uint256 uTipIndex = uBookBase; + const uint256 uBookEnd = Ledger::getQualityNext(uBookBase); + const uint64 uTakeQuality = STAmount::getRate(saTakerGets, saTakerPays); + const uint160 uTakerPaysAccountID = saTakerPays.getIssuer(); + const uint160 uTakerGetsAccountID = saTakerGets.getIssuer(); + TER terResult = temUNCERTAIN; + + boost::unordered_set usOfferUnfundedFound; // Offers found unfunded. + boost::unordered_set usOfferUnfundedBecame; // Offers that became unfunded. + boost::unordered_set usAccountTouched; // Accounts touched. + + saTakerPaid = 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 offer's of sufficient quality available. + || (bPassive && uTakeQuality == uTipQuality)) + { + // Done. + Log(lsINFO) << "takeOffers: done"; + + terResult = tesSUCCESS; + } + else + { + // Have an offer directory to consider. + Log(lsINFO) << "takeOffers: considering dir: " << sleOfferDir->getJson(0); + + SLE::pointer sleBookNode; + unsigned int uBookEntry; + uint256 uOfferIndex; + + mNodes.dirFirst(uTipIndex, sleBookNode, uBookEntry, uOfferIndex); + + SLE::pointer sleOffer = entryCache(ltOFFER, uOfferIndex); + + Log(lsINFO) << "takeOffers: considering offer : " << sleOffer->getJson(0); + + const uint160 uOfferOwnerID = sleOffer->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: XNS for XNS"; + + 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: " << NewcoinAddress::createHumanAccountID(uPaysIssuerID); + + terResult = terNO_ACCOUNT; + } + } + + if (tesSUCCESS == terResult) + { + STAmount saOfferPaid; + STAmount saOfferGot; + const uint256 uTakeBookBase = Ledger::getBookBase(uGetsCurrency, uGetsIssuerID, uPaysCurrency, uPaysIssuerID); + + Log(lsINFO) << boost::str(boost::format("doOfferCreate: take against book: %s 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=" << NewcoinAddress::createHumanAccountID(mTxnAccountID); + Log(lsWARNING) << "doOfferCreate: takeOffers: FUNDS=" << mNodes.accountFunds(mTxnAccountID, saTakerGets).getFullText(); + + // Log(lsWARNING) << "doOfferCreate: takeOffers: uPaysIssuerID=" << NewcoinAddress::createHumanAccountID(uPaysIssuerID); + // Log(lsWARNING) << "doOfferCreate: takeOffers: uGetsIssuerID=" << NewcoinAddress::createHumanAccountID(uGetsIssuerID); + + if (tesSUCCESS == terResult + && saTakerPays // Still wanting something. + && saTakerGets // Still offering something. + && mNodes.accountFunds(mTxnAccountID, saTakerGets).isPositive()) // Still funded. + { + // We need to place the remainder of the offer into its order book. + 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() + % NewcoinAddress::createHumanAccountID(saTakerPays.getIssuer()) + % saTakerGets.getHumanCurrency() + % NewcoinAddress::createHumanAccountID(saTakerGets.getIssuer())); + + uDirectory = Ledger::getQualityIndex(uBookBase, uRate); // Use original rate. + + // Add offer to order book. + terResult = mNodes.dirAdd(uBookNode, uDirectory, uLedgerIndex); + } + + if (tesSUCCESS == terResult) + { + Log(lsWARNING) << "doOfferCreate: sfAccount=" << NewcoinAddress::createHumanAccountID(mTxnAccountID); + Log(lsWARNING) << "doOfferCreate: uPaysIssuerID=" << NewcoinAddress::createHumanAccountID(uPaysIssuerID); + Log(lsWARNING) << "doOfferCreate: uGetsIssuerID=" << NewcoinAddress::createHumanAccountID(uGetsIssuerID); + Log(lsWARNING) << "doOfferCreate: saTakerPays.isNative()=" << saTakerPays.isNative(); + Log(lsWARNING) << "doOfferCreate: saTakerGets.isNative()=" << saTakerGets.isNative(); + Log(lsWARNING) << "doOfferCreate: uPaysCurrency=" << saTakerPays.getHumanCurrency(); + Log(lsWARNING) << "doOfferCreate: uGetsCurrency=" << saTakerGets.getHumanCurrency(); + + sleOffer->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 uSequence = txn.getFieldU32(sfOfferSequence); + const uint256 uOfferIndex = Ledger::getOfferIndex(mTxnAccountID, uSequence); + SLE::pointer sleOffer = entryCache(ltOFFER, uOfferIndex); + + if (sleOffer) + { + Log(lsWARNING) << "doOfferCancel: uSequence=" << uSequence; + + terResult = mNodes.offerDelete(sleOffer, uOfferIndex, mTxnAccountID); + } + else + { + Log(lsWARNING) << "doOfferCancel: offer not found: " + << NewcoinAddress::createHumanAccountID(mTxnAccountID) + << " : " << uSequence + << " : " << uOfferIndex.ToString(); + + terResult = terOFFER_NOT_FOUND; + } + + return terResult; +} + +TER TransactionEngine::doContractAdd(const SerializedTransaction& txn) +{ + Log(lsWARNING) << "doContractAdd> " << txn.getJson(0); + + const uint32 expiration = txn.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/TransactionEngine.cpp b/src/TransactionEngine.cpp index 8fdf965608..2994034c89 100644 --- a/src/TransactionEngine.cpp +++ b/src/TransactionEngine.cpp @@ -1,15 +1,11 @@ // -// XXX Should make sure all fields and are recognized on a transactions. -// XXX Make sure fee is claimed for failed transactions. -// XXX Might uses an unordered set for vector. +// XXX Make sure all fields are recognized in transactions. // +#include + #include "TransactionEngine.h" -#include -#include -#include - #include "../json/writer.h" #include "Config.h" @@ -17,732 +13,9 @@ #include "TransactionFormats.h" #include "utils.h" -// Small for testing, should likely be 32 or 64. -#define DIR_NODE_MAX 2 -#define RIPPLE_PATHS_MAX 3 - -bool transResultInfo(TransactionEngineResult terCode, std::string& strToken, std::string& strHuman) -{ - static struct { - TransactionEngineResult terCode; - const char* cpToken; - const char* cpHuman; - } transResultInfoA[] = { - { tenBAD_ADD_AUTH, "tenBAD_ADD_AUTH", "Not authorized to add account." }, - { tenBAD_AMOUNT, "tenBAD_AMOUNT", "Can only send positive amounts." }, - { tenBAD_CLAIM_ID, "tenBAD_CLAIM_ID", "Malformed." }, - { tenBAD_EXPIRATION, "tenBAD_EXPIRATION", "Malformed." }, - { tenBAD_GEN_AUTH, "tenBAD_GEN_AUTH", "Not authorized to claim generator." }, - { tenBAD_ISSUER, "tenBAD_ISSUER", "Malformed." }, - { tenBAD_OFFER, "tenBAD_OFFER", "Malformed." }, - { tenBAD_PATH_COUNT, "tenBAD_PATH_COUNT", "Malformed: too many paths." }, - { tenBAD_PUBLISH, "tenBAD_PUBLISH", "Malformed: bad publish." }, - { tenBAD_RIPPLE, "tenBAD_RIPPLE", "Ledger prevents ripple from succeeding." }, - { tenBAD_SET_ID, "tenBAD_SET_ID", "Malformed." }, - { tenCLAIMED, "tenCLAIMED", "Can not claim a previously claimed account." }, - { tenCREATED, "tenCREATED", "Can't add an already created account." }, - { tenCREATEXNS, "tenCREATEXNS", "Can not specify non XNS for Create." }, - { tenDST_IS_SRC, "tenDST_IS_SRC", "Destination may not be source." }, - { tenDST_NEEDED, "tenDST_NEEDED", "Destination not specified." }, - { tenEXPIRED, "tenEXPIRED", "Won't add an expired offer." }, - { tenEXPLICITXNS, "tenEXPLICITXNS", "XNS is used by default, don't specify it." }, - { tenFAILED, "tenFAILED", "Something broke horribly" }, - { tenGEN_IN_USE, "tenGEN_IN_USE", "Generator already in use." }, - { tenINSUF_FEE_P, "tenINSUF_FEE_P", "fee totally insufficient" }, - { tenINVALID, "tenINVALID", "The transaction is ill-formed" }, - { tenMSG_SET, "tenMSG_SET", "Can't change a message key." }, - { tenREDUNDANT, "tenREDUNDANT", "Sends same currency to self." }, - { tenRIPPLE_EMPTY, "tenRIPPLE_EMPTY", "PathSet with no paths." }, - { tenUNKNOWN, "tenUNKNOWN", "The transactions requires logic not implemented yet" }, - { terALREADY, "terALREADY", "The exact transaction was already in this ledger" }, - { terBAD_AUTH, "terBAD_AUTH", "Transaction's public key is not authorized." }, - { terBAD_AUTH_MASTER, "terBAD_AUTH_MASTER", "Auth for unclaimed account needs correct master key." }, - { terBAD_LEDGER, "terBAD_LEDGER", "Ledger in unexpected state." }, - { terBAD_RIPPLE, "terBAD_RIPPLE", "No ripple path can be satisfied." }, - { terBAD_SEQ, "terBAD_SEQ", "This sequence number should be zero for prepaid transactions." }, - { terCREATED, "terCREATED", "Can not create a previously created account." }, - { terDIR_FULL, "terDIR_FULL", "Can not add entry to full dir." }, - { terFUNDS_SPENT, "terFUNDS_SPENT", "Can't set password, password set funds already spent." }, - { terINSUF_FEE_B, "terINSUF_FEE_B", "Account balance can't pay fee" }, - { terINSUF_FEE_T, "terINSUF_FEE_T", "fee insufficient now (account doesn't exist, network load)" }, - { terNODE_NOT_FOUND, "terNODE_NOT_FOUND", "Can not delete a directory node." }, - { terNODE_NOT_MENTIONED, "terNODE_NOT_MENTIONED", "Could not remove node from a directory." }, - { terNODE_NO_ROOT, "terNODE_NO_ROOT", "Directory doesn't exist." }, - { terNO_ACCOUNT, "terNO_ACCOUNT", "The source account does not exist" }, - { terNO_DST, "terNO_DST", "The destination does not exist" }, - { terNO_LINE_NO_ZERO, "terNO_LINE_NO_ZERO", "Can't zero non-existant line, destination might make it." }, - { terNO_PATH, "terNO_PATH", "No path existed or met transaction/balance requirements" }, - { terOFFER_NOT_FOUND, "terOFFER_NOT_FOUND", "Can not cancel offer." }, - { terOVER_LIMIT, "terOVER_LIMIT", "Over limit." }, - { terPAST_LEDGER, "terPAST_LEDGER", "The transaction expired and can't be applied" }, - { terPAST_SEQ, "terPAST_SEQ", "This sequence number has already past" }, - { terPATH_EMPTY, "terPATH_EMPTY", "Path could not send partial amount." }, - { terPATH_PARTIAL, "terPATH_PARTIAL", "Path could not send full amount." }, - { terPRE_SEQ, "terPRE_SEQ", "Missing/inapplicable prior transaction" }, - { terSET_MISSING_DST, "terSET_MISSING_DST", "Can't set password, destination missing." }, - { terSUCCESS, "terSUCCESS", "The transaction was applied" }, - { terUNCLAIMED, "terUNCLAIMED", "Can not use an unclaimed account." }, - { terUNFUNDED, "terUNFUNDED", "Source account had insufficient balance for transaction." }, - }; - - int iIndex = NUMBER(transResultInfoA); - - while (iIndex-- && transResultInfoA[iIndex].terCode != terCode) - ; - - if (iIndex >= 0) - { - strToken = transResultInfoA[iIndex].cpToken; - strHuman = transResultInfoA[iIndex].cpHuman; - } - - return iIndex >= 0; -} - -// Return how much of uIssuerID's uCurrency IOUs that uAccountID holds. May be negative. -// <-- IOU's uAccountID has of uIssuerID -STAmount TransactionEngine::rippleHolds(const uint160& uAccountID, const uint160& uCurrency, const uint160& uIssuerID) -{ - STAmount saBalance; - SLE::pointer sleRippleState = entryCache(ltRIPPLE_STATE, Ledger::getRippleStateIndex(uAccountID, uIssuerID, uCurrency)); - - if (sleRippleState) - { - saBalance = sleRippleState->getIValueFieldAmount(sfBalance); - - if (uAccountID < uIssuerID) - saBalance.negate(); // Put balance in low terms. - } - - return saBalance; -} - -// <-- saAmount: amount of uCurrency held by uAccountID. May be negative. -STAmount TransactionEngine::accountHolds(const uint160& uAccountID, const uint160& uCurrency, const uint160& uIssuerID) -{ - STAmount saAmount; - - if (uCurrency.isZero()) - { - SLE::pointer sleAccount = entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uAccountID)); - - saAmount = sleAccount->getIValueFieldAmount(sfBalance); - - Log(lsINFO) << "accountHolds: stamps: " << saAmount.getText(); - } - else - { - saAmount = rippleHolds(uAccountID, uCurrency, uIssuerID); - - Log(lsINFO) << "accountHolds: " - << saAmount.getFullText() - << " : " - << STAmount::createHumanCurrency(uCurrency) - << "/" - << NewcoinAddress::createHumanAccountID(uIssuerID); - } - - return saAmount; -} - -// Returns the funds available for uAccountID for a currency/issuer. -// Use when you need a default for rippling uAccountID's currency. -// --> saDefault/currency/issuer -// <-- saFunds: Funds available. May be negative. -// If the issuer is the same as uAccountID, result is Default. -STAmount TransactionEngine::accountFunds(const uint160& uAccountID, const STAmount& saDefault) -{ - STAmount saFunds; - - Log(lsINFO) << "accountFunds: uAccountID=" - << NewcoinAddress::createHumanAccountID(uAccountID); - Log(lsINFO) << "accountFunds: saDefault.isNative()=" << saDefault.isNative(); - Log(lsINFO) << "accountFunds: saDefault.getIssuer()=" - << NewcoinAddress::createHumanAccountID(saDefault.getIssuer()); - - if (!saDefault.isNative() && saDefault.getIssuer() == uAccountID) - { - saFunds = saDefault; - - Log(lsINFO) << "accountFunds: offer funds: ripple self-funded: " << saFunds.getText(); - } - else - { - saFunds = accountHolds(uAccountID, saDefault.getCurrency(), saDefault.getIssuer()); - - Log(lsINFO) << "accountFunds: offer funds: uAccountID =" - << NewcoinAddress::createHumanAccountID(uAccountID) - << " : " - << saFunds.getText() - << "/" - << saDefault.getHumanCurrency() - << "/" - << NewcoinAddress::createHumanAccountID(saDefault.getIssuer()); - } - - return saFunds; -} - -// Calculate transit fee. -STAmount TransactionEngine::rippleTransit(const uint160& uSenderID, const uint160& uReceiverID, const uint160& uIssuerID, const STAmount& saAmount) -{ - STAmount saTransitFee; - - if (uSenderID != uIssuerID && uReceiverID != uIssuerID) - { - SLE::pointer sleIssuerAccount = entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uIssuerID)); - uint32 uTransitRate; - - if (sleIssuerAccount->getIFieldPresent(sfTransferRate)) - uTransitRate = sleIssuerAccount->getIFieldU32(sfTransferRate); - - if (uTransitRate) - { - - STAmount saTransitRate(uint160(1), uTransitRate, -9); - - saTransitFee = STAmount::multiply(saAmount, saTransitRate, saAmount.getCurrency()); - } - } - - return saTransitFee; -} - -// Send regardless of limits. -// --> saAmount: Amount/currency/issuer for receiver to get. -// <-- saActual: Amount actually sent. Sender pay's fees. -STAmount TransactionEngine::rippleSend(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount) -{ - STAmount saActual; - uint160 uIssuerID = saAmount.getIssuer(); - - if (uSenderID == uIssuerID || uReceiverID == uIssuerID) - { - // Direct send: redeeming IOUs and/or sending own IOUs. - - bool bFlipped = uSenderID > uReceiverID; - uint256 uIndex = Ledger::getRippleStateIndex(uSenderID, uReceiverID, saAmount.getCurrency()); - SLE::pointer sleRippleState = entryCache(ltRIPPLE_STATE, uIndex); - - if (!sleRippleState) - { - Log(lsINFO) << "rippleSend: Creating ripple line: " << uIndex.ToString(); - - STAmount saBalance = saAmount; - - sleRippleState = entryCreate(ltRIPPLE_STATE, uIndex); - - if (!bFlipped) - saBalance.negate(); - - sleRippleState->setIFieldAmount(sfBalance, saBalance); - sleRippleState->setIFieldAccount(bFlipped ? sfHighID : sfLowID, uSenderID); - sleRippleState->setIFieldAccount(bFlipped ? sfLowID : sfHighID, uReceiverID); - } - else - { - STAmount saBalance = sleRippleState->getIValueFieldAmount(sfBalance); - - if (!bFlipped) - saBalance.negate(); // Put balance in low terms. - - saBalance += saAmount; - - if (!bFlipped) - saBalance.negate(); - - sleRippleState->setIFieldAmount(sfBalance, saBalance); - - entryModify(sleRippleState); - } - - saActual = saAmount; - } - else - { - // Sending 3rd party IOUs: transit. - - STAmount saTransitFee = rippleTransit(uSenderID, uReceiverID, uIssuerID, saAmount); - - saActual = saTransitFee.isZero() ? saAmount : saAmount+saTransitFee; - - saActual.setIssuer(uIssuerID); // XXX Make sure this done in + above. - - rippleSend(uIssuerID, uReceiverID, saAmount); - rippleSend(uSenderID, uIssuerID, saActual); - } - - return saActual; -} - -STAmount TransactionEngine::accountSend(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount) -{ - STAmount saActualCost; - - if (saAmount.isNative()) - { - SLE::pointer sleSender = entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uSenderID)); - SLE::pointer sleReceiver = entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uReceiverID)); - - Log(lsINFO) << str(boost::format("accountSend> %s (%s) -> %s (%s) : %s") - % NewcoinAddress::createHumanAccountID(uSenderID) - % (sleSender->getIValueFieldAmount(sfBalance)).getFullText() - % NewcoinAddress::createHumanAccountID(uReceiverID) - % (sleReceiver->getIValueFieldAmount(sfBalance)).getFullText() - % saAmount.getFullText()); - - sleSender->setIFieldAmount(sfBalance, sleSender->getIValueFieldAmount(sfBalance) - saAmount); - sleReceiver->setIFieldAmount(sfBalance, sleReceiver->getIValueFieldAmount(sfBalance) + saAmount); - - Log(lsINFO) << str(boost::format("accountSend< %s (%s) -> %s (%s) : %s") - % NewcoinAddress::createHumanAccountID(uSenderID) - % (sleSender->getIValueFieldAmount(sfBalance)).getFullText() - % NewcoinAddress::createHumanAccountID(uReceiverID) - % (sleReceiver->getIValueFieldAmount(sfBalance)).getFullText() - % saAmount.getFullText()); - - entryModify(sleSender); - entryModify(sleReceiver); - - saActualCost = saAmount; - } - else - { - saActualCost = rippleSend(uSenderID, uReceiverID, saAmount); - } - - return saActualCost; -} - -TransactionEngineResult TransactionEngine::offerDelete(const SLE::pointer& sleOffer, const uint256& uOfferIndex, const uint160& uOwnerID) -{ - uint64 uOwnerNode = sleOffer->getIFieldU64(sfOwnerNode); - TransactionEngineResult terResult = dirDelete(false, uOwnerNode, Ledger::getOwnerDirIndex(uOwnerID), uOfferIndex); - - if (terSUCCESS == terResult) - { - uint256 uDirectory = sleOffer->getIFieldH256(sfBookDirectory); - uint64 uBookNode = sleOffer->getIFieldU64(sfBookNode); - - terResult = dirDelete(false, uBookNode, uDirectory, uOfferIndex); - } - - entryDelete(sleOffer); - - return terResult; -} - -// <-- uNodeDir: For deletion, present to make dirDelete efficient. -// --> uRootIndex: The index of the base of the directory. Nodes are based off of this. -// --> uLedgerIndex: Value to add to directory. -// We only append. This allow for things that watch append only structure to just monitor from the last node on ward. -// Within a node with no deletions order of elements is sequential. Otherwise, order of elements is random. -TransactionEngineResult TransactionEngine::dirAdd( - uint64& uNodeDir, - const uint256& uRootIndex, - const uint256& uLedgerIndex) -{ - SLE::pointer sleNode; - STVector256 svIndexes; - SLE::pointer sleRoot = entryCache(ltDIR_NODE, uRootIndex); - - if (!sleRoot) - { - // No root, make it. - sleRoot = entryCreate(ltDIR_NODE, uRootIndex); - - sleNode = sleRoot; - uNodeDir = 0; - } - else - { - uNodeDir = sleRoot->getIFieldU64(sfIndexPrevious); // Get index to last directory node. - - if (uNodeDir) - { - // Try adding to last node. - sleNode = entryCache(ltDIR_NODE, Ledger::getDirNodeIndex(uRootIndex, uNodeDir)); - - assert(sleNode); - } - else - { - // Try adding to root. Didn't have a previous set to the last node. - sleNode = sleRoot; - } - - svIndexes = sleNode->getIFieldV256(sfIndexes); - - if (DIR_NODE_MAX != svIndexes.peekValue().size()) - { - // Add to current node. - entryModify(sleNode); - } - // Add to new node. - else if (!++uNodeDir) - { - return terDIR_FULL; - } - else - { - // Have old last point to new node, if it was not root. - if (uNodeDir == 1) - { - // Previous node is root node. - - sleRoot->setIFieldU64(sfIndexNext, uNodeDir); - } - else - { - // Previous node is not root node. - - SLE::pointer slePrevious = entryCache(ltDIR_NODE, Ledger::getDirNodeIndex(uRootIndex, uNodeDir-1)); - - slePrevious->setIFieldU64(sfIndexNext, uNodeDir); - entryModify(slePrevious); - - sleNode->setIFieldU64(sfIndexPrevious, uNodeDir-1); - } - - // Have root point to new node. - sleRoot->setIFieldU64(sfIndexPrevious, uNodeDir); - entryModify(sleRoot); - - // Create the new node. - sleNode = entryCreate(ltDIR_NODE, Ledger::getDirNodeIndex(uRootIndex, uNodeDir)); - svIndexes = STVector256(); - } - } - - svIndexes.peekValue().push_back(uLedgerIndex); // Append entry. - sleNode->setIFieldV256(sfIndexes, svIndexes); // Save entry. - - Log(lsINFO) << "dirAdd: creating: root: " << uRootIndex.ToString(); - Log(lsINFO) << "dirAdd: appending: Entry: " << uLedgerIndex.ToString(); - Log(lsINFO) << "dirAdd: appending: Node: " << strHex(uNodeDir); - // Log(lsINFO) << "dirAdd: appending: PREV: " << svIndexes.peekValue()[0].ToString(); - - return terSUCCESS; -} - -// --> bKeepRoot: True, if we never completely clean up, after we overflow the root node. -// --> uNodeDir: Node containing entry. -// --> uRootIndex: The index of the base of the directory. Nodes are based off of this. -// --> uLedgerIndex: Value to add to directory. -// Ledger must be in a state for this to work. -TransactionEngineResult TransactionEngine::dirDelete( - bool bKeepRoot, - const uint64& uNodeDir, - const uint256& uRootIndex, - const uint256& uLedgerIndex) -{ - uint64 uNodeCur = uNodeDir; - SLE::pointer sleNode = entryCache(ltDIR_NODE, uNodeCur ? Ledger::getDirNodeIndex(uRootIndex, uNodeCur) : uRootIndex); - - assert(sleNode); - - if (!sleNode) - { - Log(lsWARNING) << "dirDelete: no such node"; - - return terBAD_LEDGER; - } - - STVector256 svIndexes = sleNode->getIFieldV256(sfIndexes); - std::vector& vuiIndexes = svIndexes.peekValue(); - std::vector::iterator it; - - it = std::find(vuiIndexes.begin(), vuiIndexes.end(), uLedgerIndex); - - assert(vuiIndexes.end() != it); - if (vuiIndexes.end() == it) - { - assert(false); - - Log(lsWARNING) << "dirDelete: no such entry"; - - return terBAD_LEDGER; - } - - // Remove the element. - if (vuiIndexes.size() > 1) - *it = vuiIndexes[vuiIndexes.size()-1]; - - vuiIndexes.resize(vuiIndexes.size()-1); - - sleNode->setIFieldV256(sfIndexes, svIndexes); - entryModify(sleNode); - - if (vuiIndexes.empty()) - { - // May be able to delete nodes. - uint64 uNodePrevious = sleNode->getIFieldU64(sfIndexPrevious); - uint64 uNodeNext = sleNode->getIFieldU64(sfIndexNext); - - if (!uNodeCur) - { - // Just emptied root node. - - if (!uNodePrevious) - { - // Never overflowed the root node. Delete it. - entryDelete(sleNode); - } - // Root overflowed. - else if (bKeepRoot) - { - // If root overflowed and not allowed to delete overflowed root node. - - nothing(); - } - else if (uNodePrevious != uNodeNext) - { - // Have more than 2 nodes. Can't delete root node. - - nothing(); - } - else - { - // Have only a root node and a last node. - SLE::pointer sleLast = entryCache(ltDIR_NODE, Ledger::getDirNodeIndex(uRootIndex, uNodeNext)); - - assert(sleLast); - - if (sleLast->getIFieldV256(sfIndexes).peekValue().empty()) - { - // Both nodes are empty. - - entryDelete(sleNode); // Delete root. - entryDelete(sleLast); // Delete last. - } - else - { - // Have an entry, can't delete root node. - - nothing(); - } - } - } - // Just emptied a non-root node. - else if (uNodeNext) - { - // Not root and not last node. Can delete node. - - SLE::pointer slePrevious = entryCache(ltDIR_NODE, uNodePrevious ? Ledger::getDirNodeIndex(uRootIndex, uNodePrevious) : uRootIndex); - - assert(slePrevious); - - SLE::pointer sleNext = entryCache(ltDIR_NODE, uNodeNext ? Ledger::getDirNodeIndex(uRootIndex, uNodeNext) : uRootIndex); - - assert(slePrevious); - assert(sleNext); - - if (!slePrevious) - { - Log(lsWARNING) << "dirDelete: previous node is missing"; - - return terBAD_LEDGER; - } - - if (!sleNext) - { - Log(lsWARNING) << "dirDelete: next node is missing"; - - return terBAD_LEDGER; - } - - // Fix previous to point to its new next. - slePrevious->setIFieldU64(sfIndexNext, uNodeNext); - entryModify(slePrevious); - - // Fix next to point to its new previous. - sleNext->setIFieldU64(sfIndexPrevious, uNodePrevious); - entryModify(sleNext); - } - // Last node. - else if (bKeepRoot || uNodePrevious) - { - // Not allowed to delete last node as root was overflowed. - // Or, have pervious entries preventing complete delete. - - nothing(); - } - else - { - // Last and only node besides the root. - SLE::pointer sleRoot = entryCache(ltDIR_NODE, uRootIndex); - - assert(sleRoot); - - if (sleRoot->getIFieldV256(sfIndexes).peekValue().empty()) - { - // Both nodes are empty. - - entryDelete(sleRoot); // Delete root. - entryDelete(sleNode); // Delete last. - } - else - { - // Root has an entry, can't delete. - - nothing(); - } - } - } - - return terSUCCESS; -} - -// <-- true, if had a next entry. -bool TransactionEngine::dirFirst( - const uint256& uRootIndex, // --> Root of directory. - SLE::pointer& sleNode, // <-> current node - unsigned int& uDirEntry, // <-- next entry - uint256& uEntryIndex) // <-- The entry, if available. Otherwise, zero. -{ - sleNode = entryCache(ltDIR_NODE, uRootIndex); - uDirEntry = 0; - - assert(sleNode); // We never probe for directories. - - return TransactionEngine::dirNext(uRootIndex, sleNode, uDirEntry, uEntryIndex); -} - -// <-- true, if had a next entry. -bool TransactionEngine::dirNext( - const uint256& uRootIndex, // --> Root of directory - SLE::pointer& sleNode, // <-> current node - unsigned int& uDirEntry, // <-> next entry - uint256& uEntryIndex) // <-- The entry, if available. Otherwise, zero. -{ - STVector256 svIndexes = sleNode->getIFieldV256(sfIndexes); - std::vector& vuiIndexes = svIndexes.peekValue(); - - if (uDirEntry == vuiIndexes.size()) - { - uint64 uNodeNext = sleNode->getIFieldU64(sfIndexNext); - - if (!uNodeNext) - { - uEntryIndex.zero(); - - return false; - } - else - { - sleNode = entryCache(ltDIR_NODE, Ledger::getDirNodeIndex(uRootIndex, uNodeNext)); - uDirEntry = 0; - - return dirNext(uRootIndex, sleNode, uDirEntry, uEntryIndex); - } - } - - uEntryIndex = vuiIndexes[uDirEntry++]; - - return true; -} - -// Set the authorized public key for an account. May also set the generator map. -TransactionEngineResult TransactionEngine::setAuthorized(const SerializedTransaction& txn, bool bMustSetGenerator) -{ - // - // Verify that submitter knows the private key for the generator. - // Otherwise, people could deny access to generators. - // - - std::vector vucCipher = txn.getITFieldVL(sfGenerator); - std::vector vucPubKey = txn.getITFieldVL(sfPubKey); - std::vector vucSignature = txn.getITFieldVL(sfSignature); - NewcoinAddress naAccountPublic = NewcoinAddress::createAccountPublic(vucPubKey); - - if (!naAccountPublic.accountPublicVerify(Serializer::getSHA512Half(vucCipher), vucSignature)) - { - Log(lsWARNING) << "createGenerator: bad signature unauthorized generator claim"; - - return tenBAD_GEN_AUTH; - } - - // Create generator. - uint160 hGeneratorID = naAccountPublic.getAccountID(); - - SLE::pointer sleGen = entryCache(ltGENERATOR_MAP, Ledger::getGeneratorIndex(hGeneratorID)); - if (!sleGen) - { - // Create the generator. - Log(lsTRACE) << "createGenerator: creating generator"; - - sleGen = entryCreate(ltGENERATOR_MAP, Ledger::getGeneratorIndex(hGeneratorID)); - - sleGen->setIFieldVL(sfGenerator, vucCipher); - } - else if (bMustSetGenerator) - { - // Doing a claim. Must set generator. - // Generator is already in use. Regular passphrases limited to one wallet. - Log(lsWARNING) << "createGenerator: generator already in use"; - - return tenGEN_IN_USE; - } - - // Set the public key needed to use the account. - uint160 uAuthKeyID = bMustSetGenerator - ? hGeneratorID // Claim - : txn.getITFieldAccount(sfAuthorizedKey); // PasswordSet - - mTxnAccount->setIFieldAccount(sfAuthorizedKey, uAuthKeyID); - - return terSUCCESS; -} - -SLE::pointer TransactionEngine::entryCache(LedgerEntryType letType, const uint256& uIndex) -{ - SLE::pointer sleEntry; - - if (!uIndex.isZero()) - { - LedgerEntryAction action; - sleEntry = mNodes.getEntry(uIndex, action); - if (!sleEntry) - { - sleEntry = mLedger->getSLE(uIndex); - if (sleEntry) - { - mNodes.entryCache(sleEntry); - mOrigNodes.entryCache(sleEntry); // So the metadata code can compare to the original - } - } - else if(action == taaDELETE) - assert(false); - } - - return sleEntry; -} - -SLE::pointer TransactionEngine::entryCreate(LedgerEntryType letType, const uint256& uIndex) -{ - assert(!uIndex.isZero()); - - SLE::pointer sleNew = boost::make_shared(letType); - sleNew->setIndex(uIndex); - mNodes.entryCreate(sleNew); - - return sleNew; -} - - -void TransactionEngine::entryDelete(SLE::pointer sleEntry, bool unfunded) -{ - mNodes.entryDelete(sleEntry, unfunded); -} - -void TransactionEngine::entryModify(SLE::pointer sleEntry) -{ - mNodes.entryModify(sleEntry); -} - void TransactionEngine::txnWrite() { - // Write back the account states and add the transaction to the ledger + // Write back the account states for (boost::unordered_map::iterator it = mNodes.begin(), end = mNodes.end(); it != end; ++it) { @@ -787,20 +60,11 @@ void TransactionEngine::txnWrite() } } -// This is for when a transaction fails from the issuer's point of view and the current changes need to be cleared so other -// actions can be applied to the ledger. -void TransactionEngine::entryReset() -{ - mNodes.setTo(mOrigNodes); -} - -TransactionEngineResult TransactionEngine::applyTransaction(const SerializedTransaction& txn, - TransactionEngineParams params) +TER TransactionEngine::applyTransaction(const SerializedTransaction& txn, TransactionEngineParams params) { Log(lsTRACE) << "applyTransaction>"; assert(mLedger); - mLedgerParentCloseTime = mLedger->getParentCloseTimeNC(); - mNodes.init(txn.getTransactionID(), mLedger->getLedgerSeq()); + mNodes.init(mLedger, txn.getTransactionID(), mLedger->getLedgerSeq()); #ifdef DEBUG if (1) @@ -820,14 +84,13 @@ TransactionEngineResult TransactionEngine::applyTransaction(const SerializedTran } #endif - TransactionEngineResult terResult = terSUCCESS; - - uint256 txID = txn.getTransactionID(); + TER terResult = tesSUCCESS; + uint256 txID = txn.getTransactionID(); if (!txID) { Log(lsWARNING) << "applyTransaction: invalid transaction id"; - terResult = tenINVALID; + terResult = temINVALID; } // @@ -841,21 +104,21 @@ TransactionEngineResult TransactionEngine::applyTransaction(const SerializedTran // XXX This could be a lot cleaner to prevent unnecessary copying. NewcoinAddress naSigningPubKey; - if (terSUCCESS == terResult) - naSigningPubKey = NewcoinAddress::createAccountPublic(txn.peekSigningPubKey()); + if (tesSUCCESS == terResult) + naSigningPubKey = NewcoinAddress::createAccountPublic(txn.getSigningPubKey()); // Consistency: really signed. - if ((terSUCCESS == terResult) && ((params & tepNO_CHECK_SIGN) == 0) && !txn.checkSign(naSigningPubKey)) + if ((tesSUCCESS == terResult) && !isSetBit(params, tapNO_CHECK_SIGN) && !txn.checkSign(naSigningPubKey)) { Log(lsWARNING) << "applyTransaction: Invalid transaction: bad signature"; - terResult = tenINVALID; + terResult = temINVALID; } STAmount saCost = theConfig.FEE_DEFAULT; - // Customize behavoir based on transaction type. - if (terSUCCESS == terResult) + // Customize behavior based on transaction type. + if (tesSUCCESS == terResult) { switch (txn.getTxnType()) { @@ -873,7 +136,7 @@ TransactionEngineResult TransactionEngine::applyTransaction(const SerializedTran case ttNICKNAME_SET: { - SLE::pointer sleNickname = entryCache(ltNICKNAME, txn.getITFieldH256(sfNickname)); + SLE::pointer sleNickname = entryCache(ltNICKNAME, txn.getFieldH256(sfNickname)); if (!sleNickname) saCost = theConfig.FEE_NICKNAME_CREATE; @@ -882,7 +145,6 @@ TransactionEngineResult TransactionEngine::applyTransaction(const SerializedTran case ttACCOUNT_SET: case ttCREDIT_SET: - case ttINVOICE: case ttOFFER_CREATE: case ttOFFER_CANCEL: case ttPASSWORD_FUND: @@ -892,51 +154,52 @@ TransactionEngineResult TransactionEngine::applyTransaction(const SerializedTran case ttINVALID: Log(lsWARNING) << "applyTransaction: Invalid transaction: ttINVALID transaction type"; - terResult = tenINVALID; + terResult = temINVALID; break; default: Log(lsWARNING) << "applyTransaction: Invalid transaction: unknown transaction type"; - terResult = tenUNKNOWN; + terResult = temUNKNOWN; break; } } STAmount saPaid = txn.getTransactionFee(); - if (terSUCCESS == terResult && (params & tepNO_CHECK_FEE) == tepNONE) + if (tesSUCCESS == terResult) { - if (!saCost.isZero()) + if (saCost) { - if (saPaid < saCost) + // Only check fee is sufficient when the ledger is open. + if (isSetBit(params, tapOPEN_LEDGER) && saPaid < saCost) { Log(lsINFO) << "applyTransaction: insufficient fee"; - terResult = tenINSUF_FEE_P; + terResult = telINSUF_FEE_P; } } else { - if (!saPaid.isZero()) + if (saPaid) { // Transaction is malformed. Log(lsWARNING) << "applyTransaction: fee not allowed"; - terResult = tenINSUF_FEE_P; + terResult = temINSUF_FEE_P; } } } // Get source account ID. mTxnAccountID = txn.getSourceAccount().getAccountID(); - if (terSUCCESS == terResult && !mTxnAccountID) + if (tesSUCCESS == terResult && !mTxnAccountID) { Log(lsWARNING) << "applyTransaction: bad source id"; - terResult = tenINVALID; + terResult = temINVALID; } - if (terSUCCESS != terResult) + if (tesSUCCESS != terResult) return terResult; boost::recursive_mutex::scoped_lock sl(mLedger->mLock); @@ -952,19 +215,19 @@ TransactionEngineResult TransactionEngine::applyTransaction(const SerializedTran if (!mTxnAccount) { - Log(lsTRACE) << str(boost::format("applyTransaction: Delay transaction: source account does not exist: %s") % + Log(lsTRACE) << boost::str(boost::format("applyTransaction: Delay transaction: source account does not exist: %s") % txn.getSourceAccount().humanAccountID()); terResult = terNO_ACCOUNT; } else { - saSrcBalance = mTxnAccount->getIValueFieldAmount(sfBalance); - bHaveAuthKey = mTxnAccount->getIFieldPresent(sfAuthorizedKey); + saSrcBalance = mTxnAccount->getFieldAmount(sfBalance); + bHaveAuthKey = mTxnAccount->isFieldPresent(sfAuthorizedKey); } // Check if account claimed. - if (terSUCCESS == terResult) + if (tesSUCCESS == terResult) { switch (txn.getTxnType()) { @@ -973,7 +236,7 @@ TransactionEngineResult TransactionEngine::applyTransaction(const SerializedTran { Log(lsWARNING) << "applyTransaction: Account already claimed."; - terResult = tenCLAIMED; + terResult = tefCLAIMED; } break; @@ -984,7 +247,7 @@ TransactionEngineResult TransactionEngine::applyTransaction(const SerializedTran } // Consistency: Check signature - if (terSUCCESS == terResult) + if (tesSUCCESS == terResult) { switch (txn.getTxnType()) { @@ -997,7 +260,7 @@ TransactionEngineResult TransactionEngine::applyTransaction(const SerializedTran Log(lsWARNING) << "sourceAccountID: " << naSigningPubKey.humanAccountID(); Log(lsWARNING) << "txn accountID: " << txn.getSourceAccount().humanAccountID(); - terResult = tenBAD_CLAIM_ID; + terResult = tefBAD_CLAIM_ID; } break; @@ -1010,13 +273,13 @@ TransactionEngineResult TransactionEngine::applyTransaction(const SerializedTran Log(lsWARNING) << "sourceAccountID: " << naSigningPubKey.humanAccountID(); Log(lsWARNING) << "txn accountID: " << txn.getSourceAccount().humanAccountID(); - terResult = tenBAD_SET_ID; + terResult = temBAD_SET_ID; } break; default: // Verify the transaction's signing public key is the key authorized for signing. - if (bHaveAuthKey && naSigningPubKey.getAccountID() == mTxnAccount->getIValueFieldAccount(sfAuthorizedKey).getAccountID()) + if (bHaveAuthKey && naSigningPubKey.getAccountID() == mTxnAccount->getFieldAccount(sfAuthorizedKey).getAccountID()) { // Authorized to continue. nothing(); @@ -1030,13 +293,13 @@ TransactionEngineResult TransactionEngine::applyTransaction(const SerializedTran { Log(lsINFO) << "applyTransaction: Delay: Not authorized to use account."; - terResult = terBAD_AUTH; + terResult = tefBAD_AUTH; } else { Log(lsINFO) << "applyTransaction: Invalid: Not authorized to use account."; - terResult = terBAD_AUTH_MASTER; + terResult = temBAD_AUTH_MASTER; } break; } @@ -1044,14 +307,14 @@ TransactionEngineResult TransactionEngine::applyTransaction(const SerializedTran // Deduct the fee, so it's not available during the transaction. // Will only write the account back, if the transaction succeeds. - if (terSUCCESS != terResult || saCost.isZero()) + if (tesSUCCESS != terResult || !saCost) { nothing(); } else if (saSrcBalance < saPaid) { Log(lsINFO) - << str(boost::format("applyTransaction: Delay: insufficent balance: balance=%s paid=%s") + << boost::str(boost::format("applyTransaction: Delay: insufficient balance: balance=%s paid=%s") % saSrcBalance.getText() % saPaid.getText()); @@ -1059,17 +322,17 @@ TransactionEngineResult TransactionEngine::applyTransaction(const SerializedTran } else { - mTxnAccount->setIFieldAmount(sfBalance, saSrcBalance - saPaid); + mTxnAccount->setFieldAmount(sfBalance, saSrcBalance - saPaid); } // Validate sequence - if (terSUCCESS != terResult) + if (tesSUCCESS != terResult) { nothing(); } - else if (!saCost.isZero()) + else if (saCost) { - uint32 a_seq = mTxnAccount->getIFieldU32(sfSequence); + uint32 a_seq = mTxnAccount->getFieldU32(sfSequence); Log(lsTRACE) << "Aseq=" << a_seq << ", Tseq=" << t_seq; @@ -1082,21 +345,17 @@ TransactionEngineResult TransactionEngine::applyTransaction(const SerializedTran terResult = terPRE_SEQ; } else if (mLedger->hasTransaction(txID)) - { - Log(lsWARNING) << "applyTransaction: duplicate sequence number"; - - terResult = terALREADY; - } + terResult = tefALREADY; else { Log(lsWARNING) << "applyTransaction: past sequence number"; - terResult = terPAST_SEQ; + terResult = tefPAST_SEQ; } } else { - mTxnAccount->setIFieldU32(sfSequence, t_seq + 1); + mTxnAccount->setFieldU32(sfSequence, t_seq + 1); } } else @@ -1107,14 +366,13 @@ TransactionEngineResult TransactionEngine::applyTransaction(const SerializedTran { Log(lsINFO) << "applyTransaction: bad sequence for pre-paid transaction"; - terResult = terPAST_SEQ; + terResult = tefPAST_SEQ; } } - if (terSUCCESS == terResult) + if (tesSUCCESS == terResult) { entryModify(mTxnAccount); - mOrigNodes = mNodes.duplicate(); switch (txn.getTxnType()) { @@ -1132,12 +390,12 @@ TransactionEngineResult TransactionEngine::applyTransaction(const SerializedTran case ttINVALID: Log(lsINFO) << "applyTransaction: invalid type"; - terResult = tenINVALID; + terResult = temINVALID; break; - case ttINVOICE: - terResult = doInvoice(txn); - break; + //case ttINVOICE: + // terResult = doInvoice(txn); + // break; case ttOFFER_CREATE: terResult = doOfferCreate(txn); @@ -1160,15 +418,22 @@ TransactionEngineResult TransactionEngine::applyTransaction(const SerializedTran break; case ttPAYMENT: - terResult = doPayment(txn); + 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 = tenUNKNOWN; + terResult = temUNKNOWN; break; } } @@ -1180,1783 +445,48 @@ TransactionEngineResult TransactionEngine::applyTransaction(const SerializedTran Log(lsINFO) << "applyTransaction: terResult=" << strToken << " : " << terResult << " : " << strHuman; - if (terSUCCESS == terResult) + 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); + txnWrite(); Serializer s; - txn.add(s); - // XXX add failed status too - // XXX do fees as need. - if (!mLedger->addTransaction(txID, s)) - assert(false); + if (isSetBit(params, tapOPEN_LEDGER)) + { + if (!mLedger->addTransaction(txID, s)) + assert(false); + } + else + { + if (!mLedger->addTransaction(txID, s, m)) + assert(false); - if ((params & tepUPDATE_TOTAL) != tepNONE) + // Charge whatever fee they specified. mLedger->destroyCoins(saPaid.getNValue()); + } } mTxnAccount = SLE::pointer(); mNodes.clear(); - mOrigNodes.clear(); - mUnfunded.clear(); - return terResult; -} - -TransactionEngineResult TransactionEngine::doAccountSet(const SerializedTransaction& txn) -{ - Log(lsINFO) << "doAccountSet>"; - - // - // EmailHash - // - - if (txn.getITFieldPresent(sfEmailHash)) + if (!isSetBit(params, tapOPEN_LEDGER) + && (isTemMalformed(terResult) || isTefFailure(terResult))) { - uint128 uHash = txn.getITFieldH128(sfEmailHash); - - if (uHash.isZero()) - { - Log(lsINFO) << "doAccountSet: unset email hash"; - - mTxnAccount->makeIFieldAbsent(sfEmailHash); - } - else - { - Log(lsINFO) << "doAccountSet: set email hash"; - - mTxnAccount->setIFieldH128(sfEmailHash, uHash); - } - } - - // - // WalletLocator - // - - if (txn.getITFieldPresent(sfWalletLocator)) - { - uint256 uHash = txn.getITFieldH256(sfWalletLocator); - - if (uHash.isZero()) - { - Log(lsINFO) << "doAccountSet: unset wallet locator"; - - mTxnAccount->makeIFieldAbsent(sfEmailHash); - } - else - { - Log(lsINFO) << "doAccountSet: set wallet locator"; - - mTxnAccount->setIFieldH256(sfWalletLocator, uHash); - } - } - - // - // MessageKey - // - - if (!txn.getITFieldPresent(sfMessageKey)) - { - nothing(); - } - else if (mTxnAccount->getIFieldPresent(sfMessageKey)) - { - Log(lsINFO) << "doAccountSet: can not change message key"; - - return tenMSG_SET; - } - else - { - Log(lsINFO) << "doAccountSet: set message key"; - - mTxnAccount->setIFieldVL(sfMessageKey, txn.getITFieldVL(sfMessageKey)); - } - - // - // Domain - // - - if (txn.getITFieldPresent(sfDomain)) - { - std::vector vucDomain = txn.getITFieldVL(sfDomain); - - if (vucDomain.empty()) - { - Log(lsINFO) << "doAccountSet: unset domain"; - - mTxnAccount->makeIFieldAbsent(sfDomain); - } - else - { - Log(lsINFO) << "doAccountSet: set domain"; - - mTxnAccount->setIFieldVL(sfDomain, vucDomain); - } - } - - // - // TransferRate - // - - if (txn.getITFieldPresent(sfTransferRate)) - { - uint32 uRate = txn.getITFieldU32(sfTransferRate); - - if (!uRate) - { - Log(lsINFO) << "doAccountSet: unset transfer rate"; - - mTxnAccount->makeIFieldAbsent(sfTransferRate); - } - else - { - Log(lsINFO) << "doAccountSet: set transfer rate"; - - mTxnAccount->setIFieldU32(sfTransferRate, uRate); - } - } - - // - // PublishHash && PublishSize - // - - bool bPublishHash = txn.getITFieldPresent(sfPublishHash); - bool bPublishSize = txn.getITFieldPresent(sfPublishSize); - - if (bPublishHash ^ bPublishSize) - { - Log(lsINFO) << "doAccountSet: bad publish"; - - return tenBAD_PUBLISH; - } - else if (bPublishHash && bPublishSize) - { - uint256 uHash = txn.getITFieldH256(sfPublishHash); - uint32 uSize = txn.getITFieldU32(sfPublishSize); - - if (uHash.isZero()) - { - Log(lsINFO) << "doAccountSet: unset publish"; - - mTxnAccount->makeIFieldAbsent(sfPublishHash); - mTxnAccount->makeIFieldAbsent(sfPublishSize); - } - else - { - Log(lsINFO) << "doAccountSet: set publish"; - - mTxnAccount->setIFieldH256(sfPublishHash, uHash); - mTxnAccount->setIFieldU32(sfPublishSize, uSize); - } - } - - Log(lsINFO) << "doAccountSet<"; - - return terSUCCESS; -} - -TransactionEngineResult TransactionEngine::doClaim(const SerializedTransaction& txn) -{ - Log(lsINFO) << "doClaim>"; - - TransactionEngineResult terResult = setAuthorized(txn, true); - - Log(lsINFO) << "doClaim<"; - - return terResult; -} - -TransactionEngineResult TransactionEngine::doCreditSet(const SerializedTransaction& txn) -{ - TransactionEngineResult terResult = terSUCCESS; - Log(lsINFO) << "doCreditSet>"; - - // Check if destination makes sense. - uint160 uDstAccountID = txn.getITFieldAccount(sfDestination); - - if (!uDstAccountID) - { - Log(lsINFO) << "doCreditSet: Invalid transaction: Destination account not specifed."; - - return tenDST_NEEDED; - } - else if (mTxnAccountID == uDstAccountID) - { - Log(lsINFO) << "doCreditSet: Invalid transaction: Can not extend credit to self."; - - return tenDST_IS_SRC; - } - - SLE::pointer sleDst = entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uDstAccountID)); - if (!sleDst) - { - Log(lsINFO) << "doCreditSet: Delay transaction: Destination account does not exist."; - - return terNO_DST; - } - - bool bFlipped = mTxnAccountID > uDstAccountID; - uint32 uFlags = bFlipped ? lsfLowIndexed : lsfHighIndexed; - bool bLimitAmount = txn.getITFieldPresent(sfLimitAmount); - STAmount saLimitAmount = bLimitAmount ? txn.getITFieldAmount(sfLimitAmount) : STAmount(); - bool bQualityIn = txn.getITFieldPresent(sfQualityIn); - uint32 uQualityIn = bQualityIn ? txn.getITFieldU32(sfQualityIn) : 0; - bool bQualityOut = txn.getITFieldPresent(sfQualityOut); - uint32 uQualityOut = bQualityIn ? txn.getITFieldU32(sfQualityOut) : 0; - uint160 uCurrency = saLimitAmount.getCurrency(); - STAmount saBalance(uCurrency); - bool bAddIndex = false; - bool bDelIndex = false; - - SLE::pointer sleRippleState = entryCache(ltRIPPLE_STATE, Ledger::getRippleStateIndex(mTxnAccountID, uDstAccountID, uCurrency)); - if (sleRippleState) - { - // A line exists in one or more directions. -#if 0 - if (saLimitAmount.isZero()) - { - // Zeroing line. - uint160 uLowID = sleRippleState->getIValueFieldAccount(sfLowID).getAccountID(); - uint160 uHighID = sleRippleState->getIValueFieldAccount(sfHighID).getAccountID(); - bool bLow = uLowID == uSrcAccountID; - bool bHigh = uLowID == uDstAccountID; - bool bBalanceZero = sleRippleState->getIValueFieldAmount(sfBalance).isZero(); - STAmount saDstLimit = sleRippleState->getIValueFieldAmount(bSendLow ? sfLowLimit : sfHighLimit); - bool bDstLimitZero = saDstLimit.isZero(); - - assert(bLow || bHigh); - - if (bBalanceZero && bDstLimitZero) - { - // Zero balance and eliminating last limit. - - bDelIndex = true; - terResult = dirDelete(false, uSrcRef, Ledger::getRippleDirIndex(mTxnAccountID), sleRippleState->getIndex()); - } - } -#endif - - if (!bDelIndex) - { - if (bLimitAmount) - sleRippleState->setIFieldAmount(bFlipped ? sfHighLimit: sfLowLimit , saLimitAmount); - - if (!bQualityIn) - { - nothing(); - } - else if (uQualityIn) - { - sleRippleState->setIFieldU32(bFlipped ? sfLowQualityIn : sfHighQualityIn, uQualityIn); - } - else - { - sleRippleState->makeIFieldAbsent(bFlipped ? sfLowQualityIn : sfHighQualityIn); - } - - if (!bQualityOut) - { - nothing(); - } - else if (uQualityOut) - { - sleRippleState->setIFieldU32(bFlipped ? sfLowQualityOut : sfHighQualityOut, uQualityOut); - } - else - { - sleRippleState->makeIFieldAbsent(bFlipped ? sfLowQualityOut : sfHighQualityOut); - } - - bAddIndex = !(sleRippleState->getFlags() & uFlags); - - if (bAddIndex) - sleRippleState->setFlag(uFlags); - - entryModify(sleRippleState); - } - - Log(lsINFO) << "doCreditSet: Modifying ripple line: bAddIndex=" << bAddIndex << " bDelIndex=" << bDelIndex; - } - // Line does not exist. - else if (saLimitAmount.isZero()) - { - Log(lsINFO) << "doCreditSet: Redundant: Setting non-existant ripple line to 0."; - - return terNO_LINE_NO_ZERO; - } - else - { - // Create a new ripple line. - STAmount saZero(uCurrency); - - bAddIndex = true; - sleRippleState = entryCreate(ltRIPPLE_STATE, Ledger::getRippleStateIndex(mTxnAccountID, uDstAccountID, uCurrency)); - - Log(lsINFO) << "doCreditSet: Creating ripple line: " << sleRippleState->getIndex().ToString(); - - sleRippleState->setFlag(uFlags); - sleRippleState->setIFieldAmount(sfBalance, saZero); // Zero balance in currency. - sleRippleState->setIFieldAmount(bFlipped ? sfHighLimit : sfLowLimit, saLimitAmount); - sleRippleState->setIFieldAmount(bFlipped ? sfLowLimit : sfHighLimit, saZero); - sleRippleState->setIFieldAccount(bFlipped ? sfHighID : sfLowID, mTxnAccountID); - sleRippleState->setIFieldAccount(bFlipped ? sfLowID : sfHighID, uDstAccountID); - if (uQualityIn) - sleRippleState->setIFieldU32(bFlipped ? sfLowQualityIn : sfHighQualityIn, uQualityIn); - if (uQualityOut) - sleRippleState->setIFieldU32(bFlipped ? sfLowQualityOut : sfHighQualityOut, uQualityOut); - } - - if (bAddIndex) - { - uint64 uSrcRef; // Ignored, ripple_state dirs never delete. - - // XXX Make dirAdd more flexiable to take vector. - terResult = dirAdd(uSrcRef, Ledger::getRippleDirIndex(mTxnAccountID), sleRippleState->getIndex()); - } - - Log(lsINFO) << "doCreditSet<"; - - return terResult; -} - -TransactionEngineResult TransactionEngine::doNicknameSet(const SerializedTransaction& txn) -{ - std::cerr << "doNicknameSet>" << std::endl; - - uint256 uNickname = txn.getITFieldH256(sfNickname); - bool bMinOffer = txn.getITFieldPresent(sfMinimumOffer); - STAmount saMinOffer = bMinOffer ? txn.getITFieldAmount(sfAmount) : STAmount(); - - SLE::pointer sleNickname = entryCache(ltNICKNAME, uNickname); - - if (sleNickname) - { - // Edit old entry. - sleNickname->setIFieldAccount(sfAccount, mTxnAccountID); - - if (bMinOffer && !saMinOffer.isZero()) - { - sleNickname->setIFieldAmount(sfMinimumOffer, saMinOffer); - } - else - { - sleNickname->makeIFieldAbsent(sfMinimumOffer); - } - - entryModify(sleNickname); - } - else - { - // Make a new entry. - // XXX Need to include authorization limiting for first year. - - sleNickname = entryCreate(ltNICKNAME, Ledger::getNicknameIndex(uNickname)); - - std::cerr << "doNicknameSet: Creating nickname node: " << sleNickname->getIndex().ToString() << std::endl; - - sleNickname->setIFieldAccount(sfAccount, mTxnAccountID); - - if (bMinOffer && !saMinOffer.isZero()) - sleNickname->setIFieldAmount(sfMinimumOffer, saMinOffer); - } - - std::cerr << "doNicknameSet<" << std::endl; - - return terSUCCESS; -} - -TransactionEngineResult TransactionEngine::doPasswordFund(const SerializedTransaction& txn) -{ - std::cerr << "doPasswordFund>" << std::endl; - - uint160 uDstAccountID = txn.getITFieldAccount(sfDestination); - SLE::pointer sleDst = mTxnAccountID == uDstAccountID - ? mTxnAccount - : entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uDstAccountID)); - if (!sleDst) - { - // Destination account does not exist. - std::cerr << "doPasswordFund: Delay transaction: Destination account does not exist." << std::endl; - - return terSET_MISSING_DST; - } - - if (sleDst->getFlags() & lsfPasswordSpent) - { - sleDst->clearFlag(lsfPasswordSpent); - - std::cerr << "doPasswordFund: Clearing spent." << sleDst->getFlags() << std::endl; - - if (mTxnAccountID != uDstAccountID) { - std::cerr << "doPasswordFund: Destination modified." << std::endl; - - entryModify(sleDst); - } - } - - std::cerr << "doPasswordFund<" << std::endl; - - return terSUCCESS; -} - -TransactionEngineResult TransactionEngine::doPasswordSet(const SerializedTransaction& txn) -{ - std::cerr << "doPasswordSet>" << std::endl; - - if (mTxnAccount->getFlags() & lsfPasswordSpent) - { - std::cerr << "doPasswordSet: Delay transaction: Funds already spent." << std::endl; - - return terFUNDS_SPENT; - } - - mTxnAccount->setFlag(lsfPasswordSpent); - - TransactionEngineResult terResult = setAuthorized(txn, false); - - std::cerr << "doPasswordSet<" << std::endl; - - return terResult; -} - -#ifdef WORK_IN_PROGRESS -// XXX Need to adjust for fees. -// Find offers to satisfy pnDst. -// - Does not adjust any balances as there is at least a forward pass to come. -// --> pnDst.saWanted: currency and amount wanted -// --> pnSrc.saIOURedeem.mCurrency: use this before saIOUIssue, limit to use. -// --> pnSrc.saIOUIssue.mCurrency: use this after saIOURedeem, limit to use. -// <-- pnDst.saReceive -// <-- pnDst.saIOUForgive -// <-- pnDst.saIOUAccept -// <-- terResult : terSUCCESS = no error and if !bAllowPartial complelely satisfied wanted. -// <-> usOffersDeleteAlways: -// <-> usOffersDeleteOnSuccess: -TransactionEngineResult calcOfferFill(paymentNode& pnSrc, paymentNode& pnDst, bool bAllowPartial) -{ - TransactionEngineResult terResult; - - if (pnDst.saWanted.isNative()) - { - // Transfer stamps. - - STAmount saSrcFunds = pnSrc.saAccount->accountHolds(pnSrc.saAccount, uint160(0), uint160(0)); - - if (saSrcFunds && (bAllowPartial || saSrcFunds > pnDst.saWanted)) - { - pnSrc.saSend = min(saSrcFunds, pnDst.saWanted); - pnDst.saReceive = pnSrc.saSend; - } - else - { - terResult = terINSUF_PATH; - } - } - else - { - // Ripple funds. - { - prv->saSend = min(prv->account->saBalance(), cur->saWanted); - // Redeem to limit. - terResult = calcOfferFill( - accountHolds(pnSrc.saAccount, pnDst.saWanted.getCurrency(), pnDst.saWanted.getIssuer()), - pnSrc.saIOURedeem, - pnDst.saIOUForgive, - bAllowPartial); - - if (terSUCCESS == terResult) - { - // Issue to wanted. - terResult = calcOfferFill( - pnDst.saWanted, // As much as wanted is available, limited by credit limit. - pnSrc.saIOUIssue, - pnDst.saIOUAccept, - bAllowPartial); - } - - if (terSUCCESS == terResult && !bAllowPartial) - { - STAmount saTotal = pnDst.saIOUForgive + pnSrc.saIOUAccept; - - if (saTotal != saWanted) - terResult = terINSUF_PATH; - } + // XXX Malformed or failed transaction in closed ledger must bow out. } return terResult; } -// Get the next offer limited by funding. -// - Stop when becomes unfunded. -void TransactionEngine::calcOfferBridgeNext( - const uint256& uBookRoot, // --> Which order book to look in. - const uint256& uBookEnd, // --> Limit of how far to look. - uint256& uBookDirIndex, // <-> Current directory. <-- 0 = no offer available. - uint64& uBookDirNode, // <-> Which node. 0 = first. - unsigned int& uBookDirEntry, // <-> Entry in node. 0 = first. - STAmount& saOfferIn, // <-- How much to pay in, fee inclusive, to get saOfferOut out. - STAmount& saOfferOut // <-- How much offer pays out. - ) -{ - saOfferIn = 0; // XXX currency & issuer - saOfferOut = 0; // XXX currency & issuer - - bool bDone = false; - - while (!bDone) - { - uint256 uOfferIndex; - - // Get uOfferIndex. - dirNext(uBookRoot, uBookEnd, uBookDirIndex, uBookDirNode, uBookDirEntry, uOfferIndex); - - SLE::pointer sleOffer = entryCache(ltOFFER, uOfferIndex); - - uint160 uOfferOwnerID = sleOffer->getIValueFieldAccount(sfAccount).getAccountID(); - STAmount saOfferPays = sleOffer->getIValueFieldAmount(sfTakerGets); - STAmount saOfferGets = sleOffer->getIValueFieldAmount(sfTakerPays); - - if (sleOffer->getIFieldPresent(sfGetsIssuer)) - saOfferPays.setIssuer(sleOffer->getIValueFieldAccount(sfGetsIssuer).getAccountID()); - - if (sleOffer->getIFieldPresent(sfPaysIssuer)) - saOfferGets.setIssuer(sleOffer->getIValueFieldAccount(sfPaysIssuer).getAccountID()); - - if (sleOffer->getIFieldPresent(sfExpiration) && sleOffer->getIFieldU32(sfExpiration) <= mLedger->getParentCloseTimeNC()) - { - // Offer is expired. - Log(lsINFO) << "calcOfferFirst: encountered expired offer"; - } - else - { - STAmount saOfferFunds = accountFunds(uOfferOwnerID, saOfferPays); - // Outbound fees are paid by offer owner. - // XXX Calculate outbound fee rate. - - if (saOfferPays.isNative()) - { - // No additional fees for stamps. - - nothing(); - } - else if (saOfferPays.getIssuer() == uOfferOwnerID) - { - // Offerer is issue own IOUs. - // No fees at this exact point, XXX receiving node may charge a fee. - // XXX Make sure has a credit line with receiver, limit by credit line. - - nothing(); - // XXX Broken - could be issuing or redeeming or both. - } - else - { - // Offer must be redeeming IOUs. - - // No additional - // XXX Broken - } - - if (!saOfferFunds.isPositive()) - { - // Offer is unfunded. - Log(lsINFO) << "calcOfferFirst: offer unfunded: delete"; - } - else if (saOfferFunds >= saOfferPays) - { - // Offer fully funded. - - // Account transfering funds in to offer always pays inbound fees. - // - saOfferIn = saOfferGets; // XXX Add in fees? - - saOfferOut = saOfferPays; - - bDone = true; - } - else - { - // Offer partially funded. - - // saOfferIn/saOfferFunds = saOfferGets/saOfferPays - // XXX Round such that all saOffer funds are exhausted. - saOfferIn = (saOfferFunds*saOfferGets)/saOfferPays; // XXX Add in fees? - saOfferOut = saOfferFunds; - - bDone = true; - } - } - - if (!bDone) - { - // mUnfunded.insert(uOfferIndex); - } - } - while (bNext); -} - -// If either currency is not stamps, then also calculates vs stamp bridge. -// --> saWanted: Limit of how much is wanted out. -// <-- saPay: How much to pay into the offer. -// <-- saGot: How much to the offer pays out. Never more than saWanted. -void TransactionEngine::calcNodeOfferReverse( - const uint160& uPayCurrency, - const uint160& uPayIssuerID, - const STAmount& saWanted, // Driver - - STAmount& saPay, - STAmount& saGot - ) const -{ - TransactionEngineResult terResult = tenUNKNOWN; - - bool bDirectNext = true; // True, if need to load. - uint256 uDirectQuality; - uint256 uDirectTip = Ledger::getBookBase(uGetsCurrency, uGetsIssuerID, uPaysCurrency, uPaysIssuerID); - uint256 uDirectEnd = Ledger::getQualityNext(uDirectTip); - - bool bBridge = true; // True, if bridging active. False, missing an offer. - uint256 uBridgeQuality; - STAmount saBridgeIn; // Amount available. - STAmount saBridgeOut; - - bool bInNext = true; // True, if need to load. - STAmount saInIn; // Amount available. Consumed in loop. Limited by offer funding. - STAmount saInOut; - uint256 uInTip; // Current entry. - uint256 uInEnd; - unsigned int uInEntry; - - bool bOutNext = true; - STAmount saOutIn; - STAmount saOutOut; - uint256 uOutTip; - uint256 uOutEnd; - unsigned int uOutEntry; - - saPay.zero(); - saPay.setCurrency(uPayCurrency); - saPay.setIssuer(uPayIssuerID); - - saNeed = saWanted; - - if (!saWanted.isNative() && !uTakerCurrency.isZero()) - { - // Bridging - uInTip = Ledger::getBookBase(uPayCurrency, uPayIssuerID, uint160(0), uint160(0)); - uInEnd = Ledger::getQualityNext(uInTip); - uOutTip = Ledger::getBookBase(uint160(0), uint160(0), saWanted.getCurrency(), saWanted.getIssuer()); - uOutEnd = Ledger::getQualityNext(uInTip); - } - - while (tenUNKNOWN == terResult) - { - if (saNeed == saWanted) - { - // Got all. - saGot = saWanted; - terResult = terSUCCESS; - } - else - { - // Calculate next tips, if needed. - - if (bDirectNext) - { - // Find next direct offer. - uDirectTip = mLedger->getNextLedgerIndex(uDirectTip, uDirectEnd); - if (!!uDirectTip) - { - sleDirectDir = entryCache(ltDIR_NODE, uDirectTip); - - // XXX Need to calculate the real quality: including fees. - uDirectQuality = STAmount::getQualityNext(uDirectTip); - } - - bDirectNext = false; - } - - if (bBridge && (bInNext || bOutNext)) - { - // Bridging and need to calculate next bridge rate. - // A bridge can consist of multiple offers. As offer's are consumed, the effective rate changes. - - if (bInNext) - { -// sleInDir = entryCache(ltDIR_NODE, mLedger->getNextLedgerIndex(uInIndex, uInEnd)); - // Get the next funded offer. - offerBridgeNext(uInIndex, uInEnd, uInEntry, saInIn, saInOut); // Get offer limited by funding. - bInNext = false; - } - - if (bOutNext) - { -// sleOutDir = entryCache(ltDIR_NODE, mLedger->getNextLedgerIndex(uOutIndex, uOutEnd)); - offerNext(uOutIndex, uOutEnd, uOutEntry, saOutIn, saOutOut); - bOutNext = false; - } - - if (!uInIndex || !uOutIndex) - { - bBridge = false; // No more offers to bridge. - } - else - { - // Have bridge in and out entries. - // Calculate bridge rate. Out offer pay ripple fee. In offer fee is added to in cost. - - saBridgeOut.zero(); - - if (saInOut < saOutIn) - { - // Limit by in. - - // XXX Need to include fees in saBridgeIn. - saBridgeIn = saInIn; // All of in - // Limit bridge out: saInOut/saBridgeOut = saOutIn/saOutOut - // Round such that we would take all of in offer, otherwise would have leftovers. - saBridgeOut = (saInOut * saOutOut) / saOutIn; - } - else if (saInOut > saOutIn) - { - // Limit by out, if at all. - - // XXX Need to include fees in saBridgeIn. - // Limit bridge in:saInIn/saInOuts = aBridgeIn/saOutIn - // Round such that would take all of out offer. - saBridgeIn = (saInIn * saOutIn) / saInOuts; - saBridgeOut = saOutOut; // All of out. - } - else - { - // Entries match, - - // XXX Need to include fees in saBridgeIn. - saBridgeIn = saInIn; // All of in - saBridgeOut = saOutOut; // All of out. - } - - uBridgeQuality = STAmount::getRate(saBridgeIn, saBridgeOut); // Inclusive of fees. - } - } - - if (bBridge) - { - bUseBridge = !uDirectTip || (uBridgeQuality < uDirectQuality) - } - else if (!!uDirectTip) - { - bUseBridge = false - } - else - { - // No more offers. Declare success, even if none returned. - saGot = saWanted-saNeed; - terResult = terSUCCESS; - } - - if (terSUCCESS != terResult) - { - STAmount& saAvailIn = bUseBridge ? saBridgeIn : saDirectIn; - STAmount& saAvailOut = bUseBridge ? saBridgeOut : saDirectOut; - - if (saAvailOut > saNeed) - { - // Consume part of offer. Done. - - saNeed = 0; - saPay += (saNeed*saAvailIn)/saAvailOut; // Round up, prefer to pay more. - } - else - { - // Consume entire offer. - - saNeed -= saAvailOut; - saPay += saAvailIn; - - if (bUseBridge) - { - // Consume bridge out. - if (saOutOut == saAvailOut) - { - // Consume all. - saOutOut = 0; - saOutIn = 0; - bOutNext = true; - } - else - { - // Consume portion of bridge out, must be consuming all of bridge in. - // saOutIn/saOutOut = saSpent/saAvailOut - // Round? - saOutIn -= (saOutIn*saAvailOut)/saOutOut; - saOutOut -= saAvailOut; - } - - // Consume bridge in. - if (saOutIn == saAvailIn) - { - // Consume all. - saInOut = 0; - saInIn = 0; - bInNext = true; - } - else - { - // Consume portion of bridge in, must be consuming all of bridge out. - // saInIn/saInOut = saAvailIn/saPay - // Round? - saInOut -= (saInOut*saAvailIn)/saInIn; - saInIn -= saAvailIn; - } - } - else - { - bDirectNext = true; - } - } - } - } - } -} - -// From the destination work towards the source calculating how much must be asked for. -// --> bAllowPartial: If false, fail if can't meet requirements. -// <-- bSuccess: true=success, false=insufficient funds. -// <-> pnNodes: -// --> [end]saWanted.mAmount -// --> [all]saWanted.mCurrency -// --> [all]saAccount -// <-> [0]saWanted.mAmount : --> limit, <-- actual -// XXX Disallow looping. -// XXX With multiple path and due to offers, must consider consumed. -bool calcPaymentReverse(std::vector& pnNodes, bool bAllowPartial) -{ - TransactionEngineResult terResult = tenUNKNOWN; - - uIndex = pnNodes.size(); - - while (tenUNKNOWN == terResult && uIndex--) - { - // Calculate (1) sending by fullfilling next wants and (2) setting current wants. - - paymentNode& curPN = pnNodes[uIndex]; - paymentNode& prvPN = pnNodes[uIndex-1]; - paymentNode& nxtPN = pnNodes[uIndex+1]; - - if (!(uFlags & (PF_REDEEM|PF_ISSUE))) - { - // Redeem IOUs - terResult = tenBAD_PATH; - } - else if (curPN.saWanted.isZero()) - { - // Must want something. - terResult = tenBAD_AMOUNT; - } - else if (curPN->uFlags & PF_ACCOUNT) - { - // Account node. - // Rippling through this accounts balances. - // No currency change. - // Issuer change possible. - - SLE::pointer sleRippleCur = ; - SLE::pointer sleRippleNxt = ; - STAmount saBalanceCur = ; - - if ((uFlags & PF_REDEEM) && saBalanceCur.isPositive()) - { - // Redeem IOUs - - // XXX - curPN.saWanted += ___; - - bSent = true; - } - - if ((uFlags & PF_ISSUE) // Allowed to issue. - && !saWantedNxt.isZero() // Need to issue. - && !saBalanceCur.isPositive()) // Can issue. - { - // Issue IOUs - - // XXX - curPN.saWanted += ___; - bSent = true; - } - - } - else if (curPN->uFlags & PF_OFFER) - { - // Offer node. - // Ripple or transfering from previous node through this offer to next node. - // Current node has a credit line with next node. - // Next node will receive either its own IOUs or this nodes IOUs. - // We limit what this node sends by this nodes redeem and issue max. - // This allows path lists to be strictly redeem. - // XXX Make sure offer book was not previously mentioned. - - uint160 uPrvCurrency = curPN->uFlags & PF_WANTED_CURRENCY - ? curPN->saWanted.getCurrency() - : saSendMax.getCurrency(); - - uint160 uPrvIssuer = curPN->uFlags & PF_WANTED_ISSUER - ? curPN->saWanted.getIssuer() - : saSendMax.getIssuer(); - - calcNodeOfferReverse( - uTakerCurrency, - uTakerIssuer, - nxtPN->saWanted, // Driver. - - uTakerPaid, - uTakerGot, - uOwnerPaid, - uOwnerGot, - ); - - if (uOwnerPaid.isZero()) - { - terResult = terZERO; // Path contributes nothing. - } - else - { - // Update wanted. - - // Save sent amount - } - } - else - { - assert(false); - } - - if (tenUNKNOWN == terResult == curPN.saWanted.isZero()) - terResult = terZERO; // Path contributes nothing. - } -} - -// From the source work toward the destination calculate how much is transfered at each step and finally. -// <-> pnNodes: -// --> [0]saWanted.mAmount -// --> [all]saWanted.saSend -// --> [all]saWanted.IOURedeem -// --> [all]saWanted.IOUIssue -// --> [all]saAccount -bool calcPaymentForward(std::vector& pnNodes) -{ - cur = src; - - if (!cur->saSend.isZero()) - { - // Sending stamps - always final step. - assert(!cur->next); - nxt->saReceive = cur->saSend; - bDone = true; - } - else - { - // Rippling. - - } -} -#endif - -// Ensure sort order is complelely deterministic. -class PathStateCompare -{ -public: - bool operator()(const PathState::pointer& lhs, const PathState::pointer& rhs) - { - // Return true, iff lhs has less priority than rhs. - - if (lhs->uQuality != rhs->uQuality) - return lhs->uQuality > rhs->uQuality; // Bigger is worse. - - // Best quanity is second rank. - if (lhs->saOut != rhs->saOut) - return lhs->saOut < rhs->saOut; // Smaller is worse. - - // Path index is third rank. - return lhs->mIndex > rhs->mIndex; // Bigger is worse. - } -}; - -PathState::pointer TransactionEngine::pathCreate(const STPath& spPath) -{ - return PathState::pointer(); -} - -// Calcuate the next increment of a path. -void TransactionEngine::pathNext(PathState::pointer pspCur) -{ - -} - -// Apply an increment of the path, then calculate the next increment. -void TransactionEngine::pathApply(PathState::pointer pspCur) -{ - - pathNext(pspCur); -} - -// XXX Need to audit for things like setting accountID not having memory. -TransactionEngineResult TransactionEngine::doPayment(const SerializedTransaction& txn) -{ - // Ripple if source or destination is non-native or if there are paths. - uint32 txFlags = txn.getFlags(); - bool bCreate = !!(txFlags & tfCreateAccount); - bool bNoRippleDirect = !!(txFlags & tfNoRippleDirect); - bool bPaths = txn.getITFieldPresent(sfPaths); - bool bMax = txn.getITFieldPresent(sfSendMax); - uint160 uDstAccountID = txn.getITFieldAccount(sfDestination); - STAmount saDstAmount = txn.getITFieldAmount(sfAmount); - STAmount saMaxAmount = bMax ? txn.getITFieldAmount(sfSendMax) : saDstAmount; - uint160 uSrcCurrency = saMaxAmount.getCurrency(); - uint160 uDstCurrency = saDstAmount.getCurrency(); - - if (!uDstAccountID) - { - Log(lsINFO) << "doPayment: Invalid transaction: Payment destination account not specifed."; - - return tenDST_NEEDED; - } - else if (!saDstAmount.isPositive()) - { - Log(lsINFO) << "doPayment: Invalid transaction: bad amount: " << saDstAmount.getHumanCurrency() << " " << saDstAmount.getText(); - - return tenBAD_AMOUNT; - } - else if (mTxnAccountID == uDstAccountID && uSrcCurrency == uDstCurrency && !bPaths) - { - Log(lsINFO) << boost::str(boost::format("doPayment: Invalid transaction: Redunant transaction: src=%s, dst=%s, src_cur=%s, dst_cur=%s") - % mTxnAccountID.ToString() - % uDstAccountID.ToString() - % uSrcCurrency.ToString() - % uDstCurrency.ToString()); - - return tenREDUNDANT; - } - - SLE::pointer sleDst = entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uDstAccountID)); - if (!sleDst) - { - // Destination account does not exist. - if (bCreate && !saDstAmount.isNative()) - { - // This restriction could be relaxed. - Log(lsINFO) << "doPayment: Invalid transaction: Create account may only fund XNS."; - - return tenCREATEXNS; - } - else if (!bCreate) - { - Log(lsINFO) << "doPayment: Delay transaction: Destination account does not exist."; - - return terNO_DST; - } - - // Create the account. - sleDst = entryCreate(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uDstAccountID)); - - sleDst->setIFieldAccount(sfAccount, uDstAccountID); - sleDst->setIFieldU32(sfSequence, 1); - } - // Destination exists. - else if (bCreate) - { - // Retryable: if account created this ledger, reordering might allow account to be made by this transaction. - Log(lsINFO) << "doPayment: Invalid transaction: Account already created."; - - return terCREATED; - } - else - { - entryModify(sleDst); - } - - bool bRipple = bPaths || bMax || !saDstAmount.isNative(); - - if (!bRipple) - { - // Direct XNS payment. - STAmount saSrcXNSBalance = mTxnAccount->getIValueFieldAmount(sfBalance); - - if (saSrcXNSBalance < saDstAmount) - { - // Transaction might succeed, if applied in a different order. - Log(lsINFO) << "doPayment: Delay transaction: Insufficent funds."; - - return terUNFUNDED; - } - - mTxnAccount->setIFieldAmount(sfBalance, saSrcXNSBalance - saDstAmount); - sleDst->setIFieldAmount(sfBalance, sleDst->getIValueFieldAmount(sfBalance) + saDstAmount); - - return terSUCCESS; - } - - // - // Ripple payment - // - // XXX Disallow loops in ripple paths - - // Try direct ripple first. - if (!bNoRippleDirect && mTxnAccountID != uDstAccountID && uSrcCurrency == uDstCurrency) - { - SLE::pointer sleRippleState = entryCache(ltRIPPLE_STATE, Ledger::getRippleStateIndex(mTxnAccountID, uDstAccountID, uDstCurrency)); - - if (sleRippleState) - { - // There is a direct credit-line of some direction. - // - We can always pay IOUs back. - // - We can issue IOUs to the limit. - uint160 uLowID = sleRippleState->getIValueFieldAccount(sfLowID).getAccountID(); - uint160 uHighID = sleRippleState->getIValueFieldAccount(sfHighID).getAccountID(); - bool bSendHigh = uLowID == mTxnAccountID && uHighID == uDstAccountID; - bool bSendLow = uLowID == uDstAccountID && uHighID == mTxnAccountID; - // Flag we need if we end up owing IOUs. - uint32 uFlags = bSendHigh ? lsfLowIndexed : lsfHighIndexed; - - assert(bSendLow || bSendHigh); - - STAmount saDstLimit = sleRippleState->getIValueFieldAmount(bSendLow ? sfLowLimit : sfHighLimit); - - STAmount saDstBalance = sleRippleState->getIValueFieldAmount(sfBalance); - if (bSendHigh) - { - // Put balance in dst terms. - saDstBalance.negate(); - } - - saDstBalance += saDstAmount; - if (saDstBalance > saDstLimit) - { - // Would exceed credit limit. - // YYY Note: in the future could push out other credits to make payment fit. - - Log(lsINFO) << "doPayment: Delay transaction: Over limit: proposed balance=" - << saDstBalance.getText() - << " limit=" - << saDstLimit.getText(); - - return terOVER_LIMIT; - } - - if (saDstBalance.isZero()) - { - // XXX May be able to delete indexes for credit limits which are zero. - nothing(); - } - else if (saDstBalance.isNegative()) - { - // dst still has outstanding IOUs, sle already indexed. - nothing(); - } - // src has outstanding IOUs, sle should be indexed. - else if (! (sleRippleState->getFlags() & uFlags)) - { - // Need to add index. - TransactionEngineResult terResult = terSUCCESS; - uint64 uSrcRef; // Ignored, ripple_state dirs never delete. - - terResult = dirAdd( - uSrcRef, - Ledger::getRippleDirIndex(mTxnAccountID), // The source ended up owing. - sleRippleState->getIndex()); // Adding current entry. - - if (terSUCCESS != terResult) - return terResult; - - sleRippleState->setFlag(uFlags); // Note now indexed. - } - - if (bSendHigh) - { - // Put balance in low terms. - saDstBalance.negate(); - } - - sleRippleState->setIFieldAmount(sfBalance, saDstBalance); - entryModify(sleRippleState); - - return terSUCCESS; - } - } - - STPathSet spsPaths = txn.getITFieldPathSet(sfPaths); - - // XXX If we are parsing for determing forwarding check maximum path count. - if (!spsPaths.isEmpty()) - { - Log(lsINFO) << "doPayment: Invalid transaction: No paths."; - - return tenRIPPLE_EMPTY; - } - else if (spsPaths.getPathCount() > RIPPLE_PATHS_MAX) - { - return tenBAD_PATH_COUNT; - } - - // Incrementally search paths. - std::priority_queue, PathStateCompare> pqPaths; -#if 0 - BOOST_FOREACH(std::vector::const_iterator::value_type spPath, spsPaths) - { - PathState::pointer pspCur = pathCreate(spPath); - - pqPaths.push(pspCur); - } -#endif - TransactionEngineResult terResult; - STAmount saPaid; - STAmount saWanted; - uint32 uFlags = txn.getFlags(); // XXX Redundant. - - terResult = tenUNKNOWN; - while (tenUNKNOWN == terResult) - { - if (!pqPaths.empty()) - { - // Have a path to contribute. - PathState::pointer pspCur = pqPaths.top(); - - pqPaths.pop(); - - pathApply(pspCur); - - if (tenUNKNOWN == terResult && saPaid == saWanted) - { - terResult = terSUCCESS; - } - - if (tenUNKNOWN == terResult && pspCur->uQuality) - { - // Current path still has something to contribute. - pqPaths.push(pspCur); - } - } - // Ran out of paths. - else if ((!uFlags & tfPartialPayment)) - { - // Partial payment not allowed. - terResult = terPATH_PARTIAL; // XXX No effect, except unfunded and charge fee. - } - else if (saPaid.isZero()) - { - // Nothing claimed. - terResult = terPATH_EMPTY; // XXX No effect except unfundeds and charge fee. - // XXX - } - else - { - terResult = terSUCCESS; - } - } - - Log(lsINFO) << "doPayment: Delay transaction: No ripple paths could be satisfied."; - - return terBAD_RIPPLE; -} - -TransactionEngineResult TransactionEngine::doWalletAdd(const SerializedTransaction& txn) -{ - std::cerr << "WalletAdd>" << std::endl; - - std::vector vucPubKey = txn.getITFieldVL(sfPubKey); - std::vector vucSignature = txn.getITFieldVL(sfSignature); - uint160 uAuthKeyID = txn.getITFieldAccount(sfAuthorizedKey); - NewcoinAddress naMasterPubKey = NewcoinAddress::createAccountPublic(vucPubKey); - uint160 uDstAccountID = naMasterPubKey.getAccountID(); - - if (!naMasterPubKey.accountPublicVerify(Serializer::getSHA512Half(uAuthKeyID.begin(), uAuthKeyID.size()), vucSignature)) - { - std::cerr << "WalletAdd: unauthorized: bad signature " << std::endl; - - return tenBAD_ADD_AUTH; - } - - SLE::pointer sleDst = entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uDstAccountID)); - - if (sleDst) - { - std::cerr << "WalletAdd: account already created" << std::endl; - - return tenCREATED; - } - - STAmount saAmount = txn.getITFieldAmount(sfAmount); - STAmount saSrcBalance = mTxnAccount->getIValueFieldAmount(sfBalance); - - if (saSrcBalance < saAmount) - { - std::cerr - << str(boost::format("WalletAdd: Delay transaction: insufficent balance: balance=%s amount=%s") - % saSrcBalance.getText() - % saAmount.getText()) - << std::endl; - - return terUNFUNDED; - } - - // Deduct initial balance from source account. - mTxnAccount->setIFieldAmount(sfBalance, saSrcBalance-saAmount); - - // Create the account. - sleDst = entryCreate(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uDstAccountID)); - - sleDst->setIFieldAccount(sfAccount, uDstAccountID); - sleDst->setIFieldU32(sfSequence, 1); - sleDst->setIFieldAmount(sfBalance, saAmount); - sleDst->setIFieldAccount(sfAuthorizedKey, uAuthKeyID); - - std::cerr << "WalletAdd<" << std::endl; - - return terSUCCESS; -} - -TransactionEngineResult TransactionEngine::doInvoice(const SerializedTransaction& txn) -{ - return tenUNKNOWN; -} - -// 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: terSUCCESS or terNO_ACCOUNT -// Note: All SLE modifications must always occur even on failure. -// XXX: Fees should be paid by the source of the currency. -TransactionEngineResult 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.isZero() && !saTakerGets.isZero()); - - Log(lsINFO) << "takeOffers: against book: " << uBookBase.ToString(); - - uint256 uTipIndex = uBookBase; - uint256 uBookEnd = Ledger::getQualityNext(uBookBase); - uint64 uTakeQuality = STAmount::getRate(saTakerGets, saTakerPays); - uint160 uTakerPaysAccountID = saTakerPays.getIssuer(); - uint160 uTakerPaysCurrency = saTakerPays.getCurrency(); - uint160 uTakerGetsAccountID = saTakerGets.getIssuer(); - uint160 uTakerGetsCurrency = saTakerGets.getCurrency(); - TransactionEngineResult terResult = tenUNKNOWN; - - saTakerPaid = 0; - saTakerGot = 0; - - while (tenUNKNOWN == terResult) - { - SLE::pointer sleOfferDir; - uint64 uTipQuality; - - // Figure out next offer to take, if needed. - if (saTakerGets != saTakerGot && saTakerPays != saTakerPaid) - { - // Taker has needs. - - sleOfferDir = entryCache(ltDIR_NODE, mLedger->getNextLedgerIndex(uTipIndex, uBookEnd)); - if (sleOfferDir) - { - Log(lsINFO) << "takeOffers: possible counter offer found"; - - uTipIndex = sleOfferDir->getIndex(); - uTipQuality = Ledger::getQuality(uTipIndex); - } - else - { - Log(lsINFO) << "takeOffers: counter offer book is empty: " - << uTipIndex.ToString() - << " ... " - << uBookEnd.ToString(); - } - } - - if (!sleOfferDir || uTakeQuality < uTipQuality || (bPassive && uTakeQuality == uTipQuality)) - { - // Done. - Log(lsINFO) << "takeOffers: done"; - - terResult = terSUCCESS; - } - else - { - // Have an offer to consider. - Log(lsINFO) << "takeOffers: considering dir : " << sleOfferDir->getJson(0); - - SLE::pointer sleBookNode; - unsigned int uBookEntry; - uint256 uOfferIndex; - - dirFirst(uTipIndex, sleBookNode, uBookEntry, uOfferIndex); - - SLE::pointer sleOffer = entryCache(ltOFFER, uOfferIndex); - - Log(lsINFO) << "takeOffers: considering offer : " << sleOffer->getJson(0); - - uint160 uOfferOwnerID = sleOffer->getIValueFieldAccount(sfAccount).getAccountID(); - STAmount saOfferPays = sleOffer->getIValueFieldAmount(sfTakerGets); - STAmount saOfferGets = sleOffer->getIValueFieldAmount(sfTakerPays); - - if (sleOffer->getIFieldPresent(sfGetsIssuer)) - saOfferPays.setIssuer(sleOffer->getIValueFieldAccount(sfGetsIssuer).getAccountID()); - - if (sleOffer->getIFieldPresent(sfPaysIssuer)) - saOfferGets.setIssuer(sleOffer->getIValueFieldAccount(sfPaysIssuer).getAccountID()); - - if (sleOffer->getIFieldPresent(sfExpiration) && sleOffer->getIFieldU32(sfExpiration) <= mLedger->getParentCloseTimeNC()) - { - // Offer is expired. Delete it. - Log(lsINFO) << "takeOffers: encountered expired offer"; - - offerDelete(sleOffer, uOfferIndex, uOfferOwnerID); - - mUnfunded.insert(uOfferIndex); - } - else if (uOfferOwnerID == uTakerAccountID) - { - // Would take own offer. Consider old offer unfunded. - Log(lsINFO) << "takeOffers: encountered taker's own old offer"; - - offerDelete(sleOffer, uOfferIndex, uOfferOwnerID); - } - else - { - // Get offer funds available. - - Log(lsINFO) << "takeOffers: saOfferPays=" << saOfferPays.getFullText(); - - STAmount saOfferFunds = accountFunds(uOfferOwnerID, saOfferPays); - STAmount saTakerFunds = 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"; - - offerDelete(sleOffer, uOfferIndex, uOfferOwnerID); - - mUnfunded.insert(uOfferIndex); - } - else - { - STAmount saPay = saTakerPays - saTakerPaid; - if (saTakerFunds < saPay) - saPay = saTakerFunds; - STAmount saSubTakerPaid; - STAmount saSubTakerGot; - - Log(lsINFO) << "takeOffers: applyOffer: saTakerPays: " << saTakerPays.getFullText(); - Log(lsINFO) << "takeOffers: applyOffer: saTakerPaid: " << saTakerPaid.getFullText(); - Log(lsINFO) << "takeOffers: applyOffer: saTakerFunds: " << saTakerFunds.getFullText(); - Log(lsINFO) << "takeOffers: applyOffer: saOfferFunds: " << saOfferFunds.getFullText(); - Log(lsINFO) << "takeOffers: applyOffer: saPay: " << saPay.getFullText(); - Log(lsINFO) << "takeOffers: applyOffer: saOfferPays: " << saOfferPays.getFullText(); - Log(lsINFO) << "takeOffers: applyOffer: saOfferGets: " << saOfferGets.getFullText(); - Log(lsINFO) << "takeOffers: applyOffer: saTakerPays: " << saTakerPays.getFullText(); - Log(lsINFO) << "takeOffers: applyOffer: saTakerGets: " << saTakerGets.getFullText(); - - bool bOfferDelete = STAmount::applyOffer( - saOfferFunds, - saPay, // Driver XXX need to account for fees. - saOfferPays, - saOfferGets, - saTakerPays, - saTakerGets, - saSubTakerPaid, - saSubTakerGot); - - Log(lsINFO) << "takeOffers: applyOffer: saSubTakerPaid: " << saSubTakerPaid.getFullText(); - Log(lsINFO) << "takeOffers: applyOffer: saSubTakerGot: " << saSubTakerGot.getFullText(); - - // Adjust offer - if (bOfferDelete) - { - // Offer now fully claimed or now unfunded. - Log(lsINFO) << "takeOffers: offer claimed: delete"; - - offerDelete(sleOffer, uOfferIndex, uOfferOwnerID); - } - else - { - Log(lsINFO) << "takeOffers: offer partial claim: modify"; - - // Offer owner will pay less. Subtract what taker just got. - sleOffer->setIFieldAmount(sfTakerGets, saOfferPays -= saSubTakerGot); - - // Offer owner will get less. Subtract what owner just paid. - sleOffer->setIFieldAmount(sfTakerPays, saOfferGets -= saSubTakerPaid); - - entryModify(sleOffer); - } - - // Offer owner pays taker. - saSubTakerGot.setIssuer(uTakerGetsAccountID); // XXX Move this earlier? - - accountSend(uOfferOwnerID, uTakerAccountID, saSubTakerGot); - - saTakerGot += saSubTakerGot; - - // Taker pays offer owner. - saSubTakerPaid.setIssuer(uTakerPaysAccountID); - - accountSend(uTakerAccountID, uOfferOwnerID, saSubTakerPaid); - - saTakerPaid += saSubTakerPaid; - } - } - } - } - - return terResult; -} - -TransactionEngineResult TransactionEngine::doOfferCreate(const SerializedTransaction& txn) -{ -Log(lsWARNING) << "doOfferCreate> " << txn.getJson(0); - uint32 txFlags = txn.getFlags(); - bool bPassive = !!(txFlags & tfPassive); - uint160 uPaysIssuerID = txn.getITFieldAccount(sfPaysIssuer); - uint160 uGetsIssuerID = txn.getITFieldAccount(sfGetsIssuer); - STAmount saTakerPays = txn.getITFieldAmount(sfTakerPays); - saTakerPays.setIssuer(uPaysIssuerID); -Log(lsWARNING) << "doOfferCreate: saTakerPays=" << saTakerPays.getFullText(); - STAmount saTakerGets = txn.getITFieldAmount(sfTakerGets); - saTakerGets.setIssuer(uGetsIssuerID); -Log(lsWARNING) << "doOfferCreate: saTakerGets=" << saTakerGets.getFullText(); - uint32 uExpiration = txn.getITFieldU32(sfExpiration); - bool bHaveExpiration = txn.getITFieldPresent(sfExpiration); - uint32 uSequence = txn.getSequence(); - - uint256 uLedgerIndex = Ledger::getOfferIndex(mTxnAccountID, uSequence); - SLE::pointer sleOffer = entryCreate(ltOFFER, uLedgerIndex); - - Log(lsINFO) << "doOfferCreate: Creating offer node: " << uLedgerIndex.ToString() << " uSequence=" << uSequence; - - uint160 uPaysCurrency = saTakerPays.getCurrency(); - uint160 uGetsCurrency = saTakerGets.getCurrency(); - uint64 uRate = STAmount::getRate(saTakerGets, saTakerPays); - - TransactionEngineResult terResult = terSUCCESS; - uint256 uDirectory; // Delete hints. - uint64 uOwnerNode; - uint64 uBookNode; - - if (bHaveExpiration && !uExpiration) - { - Log(lsWARNING) << "doOfferCreate: Malformed offer: bad expiration"; - - terResult = tenBAD_EXPIRATION; - } - else if (bHaveExpiration && mLedger->getParentCloseTimeNC() >= uExpiration) - { - Log(lsWARNING) << "doOfferCreate: Expired transaction: offer expired"; - - terResult = tenEXPIRED; - } - else if (saTakerPays.isNative() && saTakerGets.isNative()) - { - Log(lsWARNING) << "doOfferCreate: Malformed offer: XNS for XNS"; - - terResult = tenBAD_OFFER; - } - else if (saTakerPays.isZero() || saTakerGets.isZero()) - { - Log(lsWARNING) << "doOfferCreate: Malformed offer: bad amount"; - - terResult = tenBAD_OFFER; - } - else if (uPaysCurrency == uGetsCurrency && uPaysIssuerID == uGetsIssuerID) - { - Log(lsWARNING) << "doOfferCreate: Malformed offer: redundant offer"; - - terResult = tenREDUNDANT; - } - else if (saTakerPays.isNative() != uPaysIssuerID.isZero() || saTakerGets.isNative() != uGetsIssuerID.isZero()) - { - Log(lsWARNING) << "doOfferCreate: Malformed offer: bad issuer"; - - terResult = tenBAD_ISSUER; - } - else if (!accountFunds(mTxnAccountID, saTakerGets).isPositive()) - { - Log(lsWARNING) << "doOfferCreate: delay: offers must be funded"; - - terResult = terUNFUNDED; - } - - if (terSUCCESS == terResult && !saTakerPays.isNative()) - { - SLE::pointer sleTakerPays = entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uPaysIssuerID)); - - if (!sleTakerPays) - { - Log(lsWARNING) << "doOfferCreate: delay: can't receive IOUs from non-existant issuer: " << NewcoinAddress::createHumanAccountID(uPaysIssuerID); - - terResult = terNO_ACCOUNT; - } - } - - if (terSUCCESS == terResult) - { - STAmount saOfferPaid; - STAmount saOfferGot; - uint256 uTakeBookBase = Ledger::getBookBase(uGetsCurrency, uGetsIssuerID, uPaysCurrency, uPaysIssuerID); - - Log(lsINFO) << str(boost::format("doOfferCreate: take against book: %s : %s/%s -> %s/%s") - % uTakeBookBase.ToString() - % saTakerGets.getHumanCurrency() - % NewcoinAddress::createHumanAccountID(saTakerGets.getIssuer()) - % saTakerPays.getHumanCurrency() - % NewcoinAddress::createHumanAccountID(saTakerPays.getIssuer())); - - // Take using the parameters of the offer. - terResult = takeOffers( - bPassive, - uTakeBookBase, - mTxnAccountID, - mTxnAccount, - saTakerGets, - saTakerPays, - saOfferPaid, // How much was spent. - saOfferGot // How much was got. - ); - - Log(lsWARNING) << "doOfferCreate: takeOffers=" << terResult; - Log(lsWARNING) << "doOfferCreate: takeOffers: saOfferPaid=" << saOfferPaid.getFullText(); - Log(lsWARNING) << "doOfferCreate: takeOffers: saOfferGot=" << saOfferGot.getFullText(); - Log(lsWARNING) << "doOfferCreate: takeOffers: saTakerPays=" << saTakerPays.getFullText(); - Log(lsWARNING) << "doOfferCreate: takeOffers: saTakerGets=" << saTakerGets.getFullText(); - - if (terSUCCESS == terResult) - { - saTakerPays -= saOfferGot; // Reduce payin from takers by what offer just got. - saTakerGets -= saOfferPaid; // Reduce payout to takers by what srcAccount just paid. - } - } - - Log(lsWARNING) << "doOfferCreate: takeOffers: saTakerPays=" << saTakerPays.getFullText(); - Log(lsWARNING) << "doOfferCreate: takeOffers: saTakerGets=" << saTakerGets.getFullText(); - Log(lsWARNING) << "doOfferCreate: takeOffers: saTakerGets=" << NewcoinAddress::createHumanAccountID(saTakerGets.getIssuer()); - Log(lsWARNING) << "doOfferCreate: takeOffers: mTxnAccountID=" << NewcoinAddress::createHumanAccountID(mTxnAccountID); - Log(lsWARNING) << "doOfferCreate: takeOffers: funds=" << accountFunds(mTxnAccountID, saTakerGets).getFullText(); - - // Log(lsWARNING) << "doOfferCreate: takeOffers: uPaysIssuerID=" << NewcoinAddress::createHumanAccountID(uPaysIssuerID); - // Log(lsWARNING) << "doOfferCreate: takeOffers: uGetsIssuerID=" << NewcoinAddress::createHumanAccountID(uGetsIssuerID); - - if (terSUCCESS == terResult - && !saTakerPays.isZero() // Still wanting something. - && !saTakerGets.isZero() // Still offering something. - && accountFunds(mTxnAccountID, saTakerGets).isPositive()) // Still funded. - { - // We need to place the remainder of the offer into its order book. - - // Add offer to owner's directory. - terResult = dirAdd(uOwnerNode, Ledger::getOwnerDirIndex(mTxnAccountID), uLedgerIndex); - - if (terSUCCESS == terResult) - { - uint256 uBookBase = Ledger::getBookBase(uPaysCurrency, uPaysIssuerID, uGetsCurrency, uGetsIssuerID); - - Log(lsINFO) << str(boost::format("doOfferCreate: adding to book: %s : %s/%s -> %s/%s") - % uBookBase.ToString() - % saTakerPays.getHumanCurrency() - % NewcoinAddress::createHumanAccountID(saTakerPays.getIssuer()) - % saTakerGets.getHumanCurrency() - % NewcoinAddress::createHumanAccountID(saTakerGets.getIssuer())); - - uDirectory = Ledger::getQualityIndex(uBookBase, uRate); // Use original rate. - - // Add offer to order book. - terResult = dirAdd(uBookNode, uDirectory, uLedgerIndex); - } - - if (terSUCCESS == terResult) - { - // Log(lsWARNING) << "doOfferCreate: uPaysIssuerID=" << NewcoinAddress::createHumanAccountID(uPaysIssuerID); - // Log(lsWARNING) << "doOfferCreate: uGetsIssuerID=" << NewcoinAddress::createHumanAccountID(uGetsIssuerID); - // Log(lsWARNING) << "doOfferCreate: saTakerPays.isNative()=" << saTakerPays.isNative(); - // Log(lsWARNING) << "doOfferCreate: saTakerGets.isNative()=" << saTakerGets.isNative(); - // Log(lsWARNING) << "doOfferCreate: uPaysCurrency=" << saTakerPays.getHumanCurrency(); - // Log(lsWARNING) << "doOfferCreate: uGetsCurrency=" << saTakerGets.getHumanCurrency(); - - sleOffer->setIFieldAccount(sfAccount, mTxnAccountID); - sleOffer->setIFieldU32(sfSequence, uSequence); - sleOffer->setIFieldH256(sfBookDirectory, uDirectory); - sleOffer->setIFieldAmount(sfTakerPays, saTakerPays); - sleOffer->setIFieldAmount(sfTakerGets, saTakerGets); - sleOffer->setIFieldU64(sfOwnerNode, uOwnerNode); - sleOffer->setIFieldU64(sfBookNode, uBookNode); - - if (!saTakerPays.isNative()) - sleOffer->setIFieldAccount(sfPaysIssuer, uPaysIssuerID); - - if (!saTakerGets.isNative()) - sleOffer->setIFieldAccount(sfGetsIssuer, uGetsIssuerID); - - if (uExpiration) - sleOffer->setIFieldU32(sfExpiration, uExpiration); - - if (bPassive) - sleOffer->setFlag(lsfPassive); - } - } - - return terResult; -} - -TransactionEngineResult TransactionEngine::doOfferCancel(const SerializedTransaction& txn) -{ - TransactionEngineResult terResult; - uint32 uSequence = txn.getITFieldU32(sfOfferSequence); - uint256 uOfferIndex = Ledger::getOfferIndex(mTxnAccountID, uSequence); - SLE::pointer sleOffer = entryCache(ltOFFER, uOfferIndex); - - if (sleOffer) - { - Log(lsWARNING) << "doOfferCancel: uSequence=" << uSequence; - - terResult = offerDelete(sleOffer, uOfferIndex, mTxnAccountID); - } - else - { - Log(lsWARNING) << "doOfferCancel: offer not found: " - << NewcoinAddress::createHumanAccountID(mTxnAccountID) - << " : " << uSequence - << " : " << uOfferIndex.ToString(); - - terResult = terOFFER_NOT_FOUND; - } - - return terResult; -} - -TransactionEngineResult TransactionEngine::doTake(const SerializedTransaction& txn) -{ - return tenUNKNOWN; -} - -TransactionEngineResult TransactionEngine::doStore(const SerializedTransaction& txn) -{ - return tenUNKNOWN; -} - -TransactionEngineResult TransactionEngine::doDelete(const SerializedTransaction& txn) -{ - return tenUNKNOWN; -} - // vim:ts=4 diff --git a/src/TransactionEngine.h b/src/TransactionEngine.h index 8fc31aa5e3..70d464b7ae 100644 --- a/src/TransactionEngine.h +++ b/src/TransactionEngine.h @@ -8,111 +8,23 @@ #include "SerializedTransaction.h" #include "SerializedLedger.h" #include "LedgerEntrySet.h" +#include "TransactionErr.h" // A TransactionEngine applies serialized transactions to a ledger // It can also, verify signatures, verify fees, and give rejection reasons -enum TransactionEngineResult -{ - // Note: Numbers are currently unstable. Use tokens. - - // tenCAN_NEVER_SUCCEED = <0 - - // Malformed: Fee claimed - tenGEN_IN_USE = -300, - tenBAD_ADD_AUTH, - tenBAD_AMOUNT, - tenBAD_CLAIM_ID, - tenBAD_EXPIRATION, - tenBAD_GEN_AUTH, - tenBAD_ISSUER, - tenBAD_OFFER, - tenBAD_PATH_COUNT, - tenBAD_PUBLISH, - tenBAD_SET_ID, - tenCREATEXNS, - tenDST_IS_SRC, - tenDST_NEEDED, - tenEXPLICITXNS, - tenREDUNDANT, - tenRIPPLE_EMPTY, - - // Invalid: Ledger won't allow. - tenCLAIMED = -200, - tenBAD_RIPPLE, - tenCREATED, - tenEXPIRED, - tenMSG_SET, - terALREADY, - - // Other - tenFAILED = -100, - tenINSUF_FEE_P, - tenINVALID, - tenUNKNOWN, - - terSUCCESS = 0, - - // terFAILED_BUT_COULD_SUCCEED = >0 - // Conflict with ledger database: Fee claimed - // Might succeed if not conflict is not caused by transaction ordering. - terBAD_AUTH, - terBAD_AUTH_MASTER, - terBAD_LEDGER, - terBAD_RIPPLE, - terBAD_SEQ, - terCREATED, - terDIR_FULL, - terFUNDS_SPENT, - terINSUF_FEE_B, - terINSUF_FEE_T, - terNODE_NOT_FOUND, - terNODE_NOT_MENTIONED, - terNODE_NO_ROOT, - terNO_ACCOUNT, - terNO_DST, - terNO_LINE_NO_ZERO, - terNO_PATH, - terOFFER_NOT_FOUND, - terOVER_LIMIT, - terPAST_LEDGER, - terPAST_SEQ, - terPRE_SEQ, - terSET_MISSING_DST, - terUNCLAIMED, - terUNFUNDED, - - // Might succeed in different order. - // XXX claim fee and try to delete unfunded. - terPATH_EMPTY, - terPATH_PARTIAL, -}; - -bool transResultInfo(TransactionEngineResult terCode, std::string& strToken, std::string& strHuman); - enum TransactionEngineParams { - tepNONE = 0, - tepNO_CHECK_SIGN = 1, // Signature already checked - tepNO_CHECK_FEE = 2, // It was voted into a ledger anyway - tepUPDATE_TOTAL = 4, // Update the total coins - tepMETADATA = 5, // put metadata in tree, not transaction -}; + tapNONE = 0x00, -// Hold a path state under incremental application. -class PathState -{ -public: - typedef boost::shared_ptr pointer; + tapNO_CHECK_SIGN = 0x01, // Signature already checked - int mIndex; - uint64 uQuality; // 0 = none. - STAmount saIn; - STAmount saOut; + tapOPEN_LEDGER = 0x10, // Transaction is running against an open ledger + // true = failures are not forwarded, check transaction fee + // false = debit ledger for consumed funds - PathState(int iIndex) : mIndex(iIndex) { ; }; - - static PathState::pointer createPathState(int iIndex) { return boost::make_shared(iIndex); }; + tapRETRY = 0x20, // This is not the transaction's last pass + // Transaction can be retried, soft failures allowed }; // One instance per ledger. @@ -120,52 +32,11 @@ public: class TransactionEngine { private: - LedgerEntrySet mNodes, mOrigNodes; + LedgerEntrySet mNodes; - TransactionEngineResult dirAdd( - uint64& uNodeDir, // Node of entry. - const uint256& uRootIndex, - const uint256& uLedgerIndex); + TER setAuthorized(const SerializedTransaction& txn, bool bMustSetGenerator); - TransactionEngineResult dirDelete( - bool bKeepRoot, - const uint64& uNodeDir, // Node item is mentioned in. - const uint256& uRootIndex, - const uint256& uLedgerIndex); // Item being deleted - - bool dirFirst(const uint256& uRootIndex, SLE::pointer& sleNode, unsigned int& uDirEntry, uint256& uEntryIndex); - bool dirNext(const uint256& uRootIndex, SLE::pointer& sleNode, unsigned int& uDirEntry, uint256& uEntryIndex); - -#ifdef WORK_IN_PROGRESS - typedef struct { - uint16 uFlags; // --> from path - - STAccount saAccount; // --> recieving/sending account - - STAmount saWanted; // --> What this node wants from upstream. - - // Maybe this should just be a bool: - STAmount saIOURedeemMax; // --> Max amount of IOUs to redeem downstream. - // Maybe this should just be a bool: - STAmount saIOUIssueMax; // --> Max Amount of IOUs to issue downstream. - - STAmount saIOURedeem; // <-- What this node will redeem downstream. - STAmount saIOUIssue; // <-- What this node will issue downstream. - STAmount saSend; // <-- Stamps this node will send downstream. - - STAmount saRecieve; // Amount stamps to receive. - - } paymentNode; - - typedef struct { - std::vector vpnNodes; - bool bAllowPartial; - } paymentGroup; -#endif - - TransactionEngineResult setAuthorized(const SerializedTransaction& txn, bool bMustSetGenerator); - - TransactionEngineResult takeOffers( + TER takeOffers( bool bPassive, const uint256& uBookBase, const uint160& uTakerAccountID, @@ -176,60 +47,40 @@ private: STAmount& saTakerGot); protected: - Ledger::pointer mLedger; - uint64 mLedgerParentCloseTime; + Ledger::pointer mLedger; - uint160 mTxnAccountID; - SLE::pointer mTxnAccount; + uint160 mTxnAccountID; + SLE::pointer mTxnAccount; - boost::unordered_set mUnfunded; // Indexes that were found unfunded. + 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); } - SLE::pointer entryCreate(LedgerEntryType letType, const uint256& uIndex); - SLE::pointer entryCache(LedgerEntryType letType, const uint256& uIndex); - void entryDelete(SLE::pointer sleEntry, bool unfunded = false); - void entryModify(SLE::pointer sleEntry); + void txnWrite(); - void entryReset(); - - STAmount rippleHolds(const uint160& uAccountID, const uint160& uCurrency, const uint160& uIssuerID); - STAmount rippleTransit(const uint160& uSenderID, const uint160& uReceiverID, const uint160& uIssuerID, const STAmount& saAmount); - STAmount rippleSend(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount); - - STAmount accountHolds(const uint160& uAccountID, const uint160& uCurrency, const uint160& uIssuerID); - STAmount accountSend(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount); - STAmount accountFunds(const uint160& uAccountID, const STAmount& saDefault); - - PathState::pointer pathCreate(const STPath& spPath); - void pathApply(PathState::pointer pspCur); - void pathNext(PathState::pointer pspCur); - - void txnWrite(); - - TransactionEngineResult offerDelete(const SLE::pointer& sleOffer, const uint256& uOfferIndex, const uint160& uOwnerID); - - TransactionEngineResult doAccountSet(const SerializedTransaction& txn); - TransactionEngineResult doClaim(const SerializedTransaction& txn); - TransactionEngineResult doCreditSet(const SerializedTransaction& txn); - TransactionEngineResult doDelete(const SerializedTransaction& txn); - TransactionEngineResult doInvoice(const SerializedTransaction& txn); - TransactionEngineResult doOfferCreate(const SerializedTransaction& txn); - TransactionEngineResult doOfferCancel(const SerializedTransaction& txn); - TransactionEngineResult doNicknameSet(const SerializedTransaction& txn); - TransactionEngineResult doPasswordFund(const SerializedTransaction& txn); - TransactionEngineResult doPasswordSet(const SerializedTransaction& txn); - TransactionEngineResult doPayment(const SerializedTransaction& txn); - TransactionEngineResult doStore(const SerializedTransaction& txn); - TransactionEngineResult doTake(const SerializedTransaction& txn); - TransactionEngineResult doWalletAdd(const SerializedTransaction& txn); + TER doAccountSet(const SerializedTransaction& txn); + TER doClaim(const SerializedTransaction& txn); + TER doCreditSet(const SerializedTransaction& txn); + TER doInvoice(const SerializedTransaction& txn); + TER doOfferCreate(const SerializedTransaction& txn); + TER doOfferCancel(const SerializedTransaction& txn); + TER doNicknameSet(const SerializedTransaction& txn); + TER doPasswordFund(const SerializedTransaction& txn); + TER doPasswordSet(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::pointer ledger) : mLedger(ledger) { ; } + TransactionEngine(Ledger::ref ledger) : mLedger(ledger) { assert(mLedger); } - Ledger::pointer getLedger() { return mLedger; } - void setLedger(Ledger::pointer ledger) { assert(ledger); mLedger = ledger; } + Ledger::pointer getLedger() { return mLedger; } + void setLedger(Ledger::ref ledger) { assert(ledger); mLedger = ledger; } - TransactionEngineResult applyTransaction(const SerializedTransaction&, TransactionEngineParams); + TER applyTransaction(const SerializedTransaction&, TransactionEngineParams); }; inline TransactionEngineParams operator|(const TransactionEngineParams& l1, const TransactionEngineParams& l2) diff --git a/src/TransactionErr.cpp b/src/TransactionErr.cpp new file mode 100644 index 0000000000..85ea95594b --- /dev/null +++ b/src/TransactionErr.cpp @@ -0,0 +1,93 @@ +#include "TransactionErr.h" +#include "utils.h" + +bool transResultInfo(TER terCode, std::string& strToken, std::string& strHuman) +{ + static struct { + TER terCode; + const char* cpToken; + const char* cpHuman; + } transResultInfoA[] = { + { tefALREADY, "tefALREADY", "The exact transaction was already in this ledger" }, + { tefBAD_ADD_AUTH, "tefBAD_ADD_AUTH", "Not authorized to add account." }, + { tefBAD_AUTH, "tefBAD_AUTH", "Transaction's public key is not authorized." }, + { tefBAD_CLAIM_ID, "tefBAD_CLAIM_ID", "Malformed." }, + { tefBAD_GEN_AUTH, "tefBAD_GEN_AUTH", "Not authorized to claim generator." }, + { tefBAD_LEDGER, "tefBAD_LEDGER", "Ledger in unexpected state." }, + { tefCLAIMED, "tefCLAIMED", "Can not claim a previously claimed account." }, + { tefEXCEPTION, "tefEXCEPTION", "Unexpected program state." }, + { tefCREATED, "tefCREATED", "Can't add an already created account." }, + { tefGEN_IN_USE, "tefGEN_IN_USE", "Generator already in use." }, + { tefPAST_SEQ, "tefPAST_SEQ", "This sequence number has already past" }, + + { telBAD_PATH_COUNT, "telBAD_PATH_COUNT", "Malformed: too many paths." }, + { telINSUF_FEE_P, "telINSUF_FEE_P", "Fee insufficient." }, + + { temBAD_AMOUNT, "temBAD_AMOUNT", "Can only send positive amounts." }, + { temBAD_AUTH_MASTER, "temBAD_AUTH_MASTER", "Auth for unclaimed account needs correct master key." }, + { temBAD_EXPIRATION, "temBAD_EXPIRATION", "Malformed." }, + { temBAD_ISSUER, "temBAD_ISSUER", "Malformed." }, + { temBAD_OFFER, "temBAD_OFFER", "Malformed." }, + { temBAD_PATH, "temBAD_PATH", "Malformed." }, + { temBAD_PATH_LOOP, "temBAD_PATH_LOOP", "Malformed." }, + { temBAD_PUBLISH, "temBAD_PUBLISH", "Malformed: bad publish." }, + { temBAD_TRANSFER_RATE, "temBAD_TRANSFER_RATE", "Malformed: transfer rate must be >= 1.0" }, + { temBAD_SET_ID, "temBAD_SET_ID", "Malformed." }, + { temCREATEXNS, "temCREATEXNS", "Can not specify non XNS for Create." }, + { temDST_IS_SRC, "temDST_IS_SRC", "Destination may not be source." }, + { temDST_NEEDED, "temDST_NEEDED", "Destination not specified." }, + { temINSUF_FEE_P, "temINSUF_FEE_P", "Fee not allowed." }, + { temINVALID, "temINVALID", "The transaction is ill-formed" }, + { temREDUNDANT, "temREDUNDANT", "Sends same currency to self." }, + { temRIPPLE_EMPTY, "temRIPPLE_EMPTY", "PathSet with no paths." }, + { temUNCERTAIN, "temUNCERTAIN", "In process of determining result. Never returned." }, + { temUNKNOWN, "temUNKNOWN", "The transactions requires logic not implemented yet." }, + + { tepPATH_DRY, "tepPATH_DRY", "Path could not send partial amount." }, + { tepPATH_PARTIAL, "tepPATH_PARTIAL", "Path could not send full amount." }, + + { terDIR_FULL, "terDIR_FULL", "Can not add entry to full dir." }, + { terFUNDS_SPENT, "terFUNDS_SPENT", "Can't set password, password set funds already spent." }, + { terINSUF_FEE_B, "terINSUF_FEE_B", "Account balance can't pay fee." }, + { terNO_ACCOUNT, "terNO_ACCOUNT", "The source account does not exist." }, + { terNO_DST, "terNO_DST", "The destination does not exist" }, + { terNO_LINE, "terNO_LINE", "No such line." }, + { terNO_LINE_NO_ZERO, "terNO_LINE_NO_ZERO", "Can't zero non-existant line, destination might make it." }, + { terOFFER_NOT_FOUND, "terOFFER_NOT_FOUND", "Can not cancel offer." }, + { terPRE_SEQ, "terPRE_SEQ", "Missing/inapplicable prior transaction" }, + { terSET_MISSING_DST, "terSET_MISSING_DST", "Can't set password, destination missing." }, + { terUNFUNDED, "terUNFUNDED", "Source account had insufficient balance for transaction." }, + + { tesSUCCESS, "tesSUCCESS", "The transaction was applied" }, + }; + + int iIndex = NUMBER(transResultInfoA); + + while (iIndex-- && transResultInfoA[iIndex].terCode != terCode) + ; + + if (iIndex >= 0) + { + strToken = transResultInfoA[iIndex].cpToken; + strHuman = transResultInfoA[iIndex].cpHuman; + } + + return iIndex >= 0; +} + +std::string transToken(TER terCode) +{ + std::string strToken; + std::string strHuman; + + return transResultInfo(terCode, strToken, strHuman) ? strToken : "-"; +} + +std::string transHuman(TER terCode) +{ + std::string strToken; + std::string strHuman; + + return transResultInfo(terCode, strToken, strHuman) ? strHuman : "-"; +} +// vim:ts=4 diff --git a/src/TransactionErr.h b/src/TransactionErr.h new file mode 100644 index 0000000000..576f3dbf80 --- /dev/null +++ b/src/TransactionErr.h @@ -0,0 +1,121 @@ +#ifndef _TRANSACTION_ERR_ +#define _TRANSACTION_ERR_ + +#include + +enum TER // aka TransactionEngineResult +{ + // Note: Range is stable. Exact numbers are currently unstable. Use tokens. + + // -399 .. -300: L Local error (transaction fee inadequate, exceeds local limit) + // Only valid during non-consensus processing. + // Implications: + // - Not forwarded + // - No fee check + telLOCAL_ERROR = -399, + telBAD_PATH_COUNT, + telINSUF_FEE_P, + + // -299 .. -200: M Malformed (bad signature) + // Causes: + // - Transaction corrupt. + // Implications: + // - Not applied + // - Not forwarded + // - Reject + // - Can not succeed in any imagined ledger. + temMALFORMED = -299, + temBAD_AMOUNT, + temBAD_AUTH_MASTER, + temBAD_EXPIRATION, + temBAD_ISSUER, + temBAD_OFFER, + temBAD_PATH, + temBAD_PATH_LOOP, + temBAD_PUBLISH, + temBAD_TRANSFER_RATE, + temBAD_SET_ID, + temCREATEXNS, + temDST_IS_SRC, + temDST_NEEDED, + temINSUF_FEE_P, + temINVALID, + temREDUNDANT, + temRIPPLE_EMPTY, + temUNCERTAIN, + temUNKNOWN, + + // -199 .. -100: F Failure (sequence number previously used) + // Causes: + // - Transaction cannot succeed because of ledger state. + // - Unexpected ledger state. + // - C++ exception. + // Implications: + // - Not applied + // - Not forwarded + // - Could succeed in an imagined ledger. + tefFAILURE = -199, + tefALREADY, + tefBAD_ADD_AUTH, + tefBAD_AUTH, + tefBAD_CLAIM_ID, + tefBAD_GEN_AUTH, + tefBAD_LEDGER, + tefCLAIMED, + tefCREATED, + tefEXCEPTION, + tefGEN_IN_USE, + tefPAST_SEQ, + + // -99 .. -1: R Retry (sequence too high, no funds for txn fee, originating account non-existent) + // Causes: + // - Priror application of another, possibly non-existant, transaction could allow this transaction to succeed. + // Implications: + // - Not applied + // - Not forwarded + // - Might succeed later + // - Hold + terRETRY = -99, + terDIR_FULL, + terFUNDS_SPENT, + terINSUF_FEE_B, + terNO_ACCOUNT, + terNO_DST, + terNO_LINE, + terNO_LINE_NO_ZERO, + terOFFER_NOT_FOUND, // XXX If we check sequence first this could be hard failure. + terPRE_SEQ, + terSET_MISSING_DST, + terUNFUNDED, + + // 0: S Success (success) + // Causes: + // - Success. + // Implications: + // - Applied + // - Forwarded + tesSUCCESS = 0, + + // 100 .. P Partial success (SR) (ripple transaction with no good paths, pay to non-existent account) + // Causes: + // - Success, but does not achieve optimal result. + // Implications: + // - Applied + // - Forwarded + // Only allowed as a return code of appliedTransaction when !tapRetry. Otherwise, treated as terRETRY. + tepPARTIAL = 100, + tepPATH_DRY, + tepPATH_PARTIAL, +}; + +#define isTemMalformed(x) ((x) >= temMALFORMED && (x) < tefFAILURE) +#define isTefFailure(x) ((x) >= tefFAILURE && (x) < terRETRY) +#define isTepPartial(x) ((x) >= tepPATH_PARTIAL) +#define isTepSuccess(x) ((x) >= tesSUCCESS) +#define isTerRetry(x) ((x) >= terRETRY && (x) < tesSUCCESS) + +bool transResultInfo(TER terCode, std::string& strToken, std::string& strHuman); +std::string transToken(TER terCode); +std::string transHuman(TER terCode); + +#endif diff --git a/src/TransactionFormats.cpp b/src/TransactionFormats.cpp index 95a753d2dd..32c7caa714 100644 --- a/src/TransactionFormats.cpp +++ b/src/TransactionFormats.cpp @@ -1,128 +1,138 @@ - #include "TransactionFormats.h" -#define S_FIELD(x) sf##x, #x +std::map TransactionFormat::byType; +std::map TransactionFormat::byName; -TransactionFormat InnerTxnFormats[]= -{ - { "AccountSet", ttACCOUNT_SET, { - { S_FIELD(Flags), STI_UINT32, SOE_FLAGS, 0 }, - { S_FIELD(SourceTag), STI_UINT32, SOE_IFFLAG, 1 }, - { S_FIELD(EmailHash), STI_HASH128, SOE_IFFLAG, 2 }, - { S_FIELD(WalletLocator), STI_HASH256, SOE_IFFLAG, 4 }, - { S_FIELD(MessageKey), STI_VL, SOE_IFFLAG, 8 }, - { S_FIELD(Domain), STI_VL, SOE_IFFLAG, 16 }, - { S_FIELD(TransferRate), STI_UINT32, SOE_IFFLAG, 32 }, - { S_FIELD(PublishHash), STI_HASH256, SOE_IFFLAG, 64 }, - { S_FIELD(PublishSize), STI_UINT32, SOE_IFFLAG, 128 }, - { S_FIELD(Extensions), STI_TL, SOE_IFFLAG, 0x02000000 }, - { sfInvalid, NULL, STI_DONE, SOE_NEVER, -1 } } - }, - { "Claim", ttCLAIM, { - { S_FIELD(Flags), STI_UINT32, SOE_FLAGS, 0 }, - { S_FIELD(Generator), STI_VL, SOE_REQUIRED, 0 }, - { S_FIELD(PubKey), STI_VL, SOE_REQUIRED, 0 }, - { S_FIELD(Signature), STI_VL, SOE_REQUIRED, 0 }, - { S_FIELD(SourceTag), STI_UINT32, SOE_IFFLAG, 1 }, - { S_FIELD(Extensions), STI_TL, SOE_IFFLAG, 0x02000000 }, - { sfInvalid, NULL, STI_DONE, SOE_NEVER, -1 } } - }, - { "CreditSet", ttCREDIT_SET, { - { S_FIELD(Flags), STI_UINT32, SOE_FLAGS, 0 }, - { S_FIELD(Destination), STI_ACCOUNT, SOE_REQUIRED, 0 }, - { S_FIELD(SourceTag), STI_UINT32, SOE_IFFLAG, 1 }, - { S_FIELD(LimitAmount), STI_AMOUNT, SOE_IFFLAG, 2 }, - { S_FIELD(QualityIn), STI_UINT32, SOE_IFFLAG, 4 }, - { S_FIELD(QualityOut), STI_UINT32, SOE_IFFLAG, 8 }, - { S_FIELD(Extensions), STI_TL, SOE_IFFLAG, 0x02000000 }, - { sfInvalid, NULL, STI_DONE, SOE_NEVER, -1 } } - }, - { "Invoice", ttINVOICE, { - { S_FIELD(Flags), STI_UINT32, SOE_FLAGS, 0 }, - { S_FIELD(Target), STI_ACCOUNT, SOE_REQUIRED, 0 }, - { S_FIELD(Amount), STI_AMOUNT, SOE_REQUIRED, 0 }, - { S_FIELD(SourceTag), STI_UINT32, SOE_IFFLAG, 1 }, - { S_FIELD(Destination), STI_ACCOUNT, SOE_IFFLAG, 2 }, - { S_FIELD(Identifier), STI_VL, SOE_IFFLAG, 4 }, - { S_FIELD(Extensions), STI_TL, SOE_IFFLAG, 0x02000000 }, - { sfInvalid, NULL, STI_DONE, SOE_NEVER, -1 } } - }, - { "NicknameSet", ttNICKNAME_SET, { - { S_FIELD(Flags), STI_UINT32, SOE_FLAGS, 0 }, - { S_FIELD(Nickname), STI_HASH256, SOE_REQUIRED, 0 }, - { S_FIELD(MinimumOffer), STI_AMOUNT, SOE_IFFLAG, 1 }, - { S_FIELD(Signature), STI_VL, SOE_IFFLAG, 2 }, - { S_FIELD(SourceTag), STI_UINT32, SOE_IFFLAG, 4 }, - { S_FIELD(Extensions), STI_TL, SOE_IFFLAG, 0x02000000 }, - { sfInvalid, NULL, STI_DONE, SOE_NEVER, -1 } } - }, - { "OfferCreate", ttOFFER_CREATE, { - { S_FIELD(Flags), STI_UINT32, SOE_FLAGS, 0 }, - { S_FIELD(TakerPays), STI_AMOUNT, SOE_REQUIRED, 0 }, - { S_FIELD(TakerGets), STI_AMOUNT, SOE_REQUIRED, 0 }, - { S_FIELD(SourceTag), STI_UINT32, SOE_IFFLAG, 1 }, - { S_FIELD(PaysIssuer), STI_ACCOUNT, SOE_IFFLAG, 2 }, - { S_FIELD(GetsIssuer), STI_ACCOUNT, SOE_IFFLAG, 4 }, - { S_FIELD(Expiration), STI_UINT32, SOE_IFFLAG, 8 }, - { S_FIELD(Extensions), STI_TL, SOE_IFFLAG, 0x02000000 }, - { sfInvalid, NULL, STI_DONE, SOE_NEVER, -1 } } - }, - { "OfferCancel", ttOFFER_CANCEL, { - { S_FIELD(Flags), STI_UINT32, SOE_FLAGS, 0 }, - { S_FIELD(OfferSequence), STI_UINT32, SOE_REQUIRED, 0 }, - { S_FIELD(SourceTag), STI_UINT32, SOE_IFFLAG, 1 }, - { S_FIELD(Extensions), STI_TL, SOE_IFFLAG, 0x02000000 }, - { sfInvalid, NULL, STI_DONE, SOE_NEVER, -1 } } - }, - { "PasswordFund", ttPASSWORD_FUND, { - { S_FIELD(Flags), STI_UINT32, SOE_FLAGS, 0 }, - { S_FIELD(Destination), STI_ACCOUNT, SOE_REQUIRED, 0 }, - { S_FIELD(SourceTag), STI_UINT32, SOE_IFFLAG, 1 }, - { S_FIELD(Extensions), STI_TL, SOE_IFFLAG, 0x02000000 }, - { sfInvalid, NULL, STI_DONE, SOE_NEVER, -1 } } - }, - { "PasswordSet", ttPASSWORD_SET, { - { S_FIELD(Flags), STI_UINT32, SOE_FLAGS, 0 }, - { S_FIELD(AuthorizedKey), STI_ACCOUNT, SOE_REQUIRED, 0 }, - { S_FIELD(Generator), STI_VL, SOE_REQUIRED, 0 }, - { S_FIELD(PubKey), STI_VL, SOE_REQUIRED, 0 }, - { S_FIELD(Signature), STI_VL, SOE_REQUIRED, 0 }, - { S_FIELD(SourceTag), STI_UINT32, SOE_IFFLAG, 1 }, - { S_FIELD(Extensions), STI_TL, SOE_IFFLAG, 0x02000000 }, - { sfInvalid, NULL, STI_DONE, SOE_NEVER, -1 } } - }, - { "Payment", ttPAYMENT, { - { S_FIELD(Flags), STI_UINT32, SOE_FLAGS, 0 }, - { S_FIELD(Destination), STI_ACCOUNT, SOE_REQUIRED, 0 }, - { S_FIELD(Amount), STI_AMOUNT, SOE_REQUIRED, 0 }, - { S_FIELD(SendMax), STI_AMOUNT, SOE_IFFLAG, 1 }, - { S_FIELD(Paths), STI_PATHSET, SOE_IFFLAG, 2 }, - { S_FIELD(SourceTag), STI_UINT32, SOE_IFFLAG, 4 }, - { S_FIELD(InvoiceID), STI_HASH256, SOE_IFFLAG, 8 }, - { S_FIELD(Extensions), STI_TL, SOE_IFFLAG, 0x02000000 }, - { sfInvalid, NULL, STI_DONE, SOE_NEVER, -1 } } - }, - { "WalletAdd", ttWALLET_ADD, { - { S_FIELD(Flags), STI_UINT32, SOE_FLAGS, 0 }, - { S_FIELD(Amount), STI_AMOUNT, SOE_REQUIRED, 0 }, - { S_FIELD(AuthorizedKey), STI_ACCOUNT, SOE_REQUIRED, 0 }, - { S_FIELD(PubKey), STI_VL, SOE_REQUIRED, 0 }, - { S_FIELD(Signature), STI_VL, SOE_REQUIRED, 0 }, - { S_FIELD(SourceTag), STI_UINT32, SOE_IFFLAG, 1 }, - { S_FIELD(Extensions), STI_TL, SOE_IFFLAG, 0x02000000 }, - { sfInvalid, NULL, STI_DONE, SOE_NEVER, -1 } } - }, - { NULL, ttINVALID } -}; +#define TF_BASE \ + << SOElement(sfTransactionType, SOE_REQUIRED) \ + << SOElement(sfFlags, SOE_REQUIRED) \ + << SOElement(sfSourceTag, SOE_OPTIONAL) \ + << SOElement(sfAccount, SOE_REQUIRED) \ + << SOElement(sfSequence, SOE_REQUIRED) \ + << SOElement(sfFee, SOE_REQUIRED) \ + << SOElement(sfSigningPubKey, SOE_REQUIRED) \ + << SOElement(sfTxnSignature, SOE_OPTIONAL) -TransactionFormat* getTxnFormat(TransactionType t) +#define DECLARE_TF(name, type) tf = new TransactionFormat(#name, type); (*tf) TF_BASE + +static bool TFInit() { - TransactionFormat* f = InnerTxnFormats; - while (f->t_name != NULL) - { - if (f->t_type == t) return f; - ++f; - } - return NULL; + TransactionFormat* tf; + + DECLARE_TF(AccountSet, ttACCOUNT_SET) + << SOElement(sfEmailHash, SOE_OPTIONAL) + << SOElement(sfWalletLocator, SOE_OPTIONAL) + << SOElement(sfMessageKey, SOE_OPTIONAL) + << SOElement(sfDomain, SOE_OPTIONAL) + << SOElement(sfTransferRate, SOE_OPTIONAL) + << SOElement(sfPublishHash, SOE_OPTIONAL) + << SOElement(sfPublishSize, SOE_OPTIONAL) + ; + + DECLARE_TF(Claim, ttCLAIM) + << SOElement(sfGenerator, SOE_REQUIRED) + << SOElement(sfPublicKey, SOE_REQUIRED) + << SOElement(sfSignature, SOE_REQUIRED) + ; + + DECLARE_TF(CreditSet, ttCREDIT_SET) + << SOElement(sfLimitAmount, SOE_OPTIONAL) + << SOElement(sfQualityIn, SOE_OPTIONAL) + << SOElement(sfQualityOut, SOE_OPTIONAL) + ; + + + /* + DECLARE_TF(Invoice, ttINVOICE) + << SOElement(sfTarget, SOE_REQUIRED) + << SOElement(sfAmount, SOE_REQUIRED) + << SOElement(sfDestination, SOE_OPTIONAL) + << SOElement(sfIdentifier, SOE_OPTIONAL) + ; + ) + */ + + DECLARE_TF(NicknameSet, ttNICKNAME_SET) + << SOElement(sfNickname, SOE_REQUIRED) + << SOElement(sfMinimumOffer, SOE_OPTIONAL) + ; + + DECLARE_TF(OfferCreate, ttOFFER_CREATE) + << SOElement(sfTakerPays, SOE_REQUIRED) + << SOElement(sfTakerGets, SOE_REQUIRED) + << SOElement(sfExpiration, SOE_OPTIONAL) + ; + + DECLARE_TF(OfferCancel, ttOFFER_CANCEL) + << SOElement(sfOfferSequence, SOE_REQUIRED) + ; + + DECLARE_TF(PasswordFund, ttPASSWORD_FUND) + << SOElement(sfDestination, SOE_REQUIRED) + ; + + DECLARE_TF(PasswordSet, ttPASSWORD_SET) + << SOElement(sfAuthorizedKey, SOE_REQUIRED) + << SOElement(sfGenerator, SOE_REQUIRED) + << SOElement(sfPublicKey, SOE_REQUIRED) + ; + + DECLARE_TF(Payment, ttPAYMENT) + << SOElement(sfDestination, SOE_REQUIRED) + << SOElement(sfAmount, SOE_REQUIRED) + << SOElement(sfSendMax, SOE_OPTIONAL) + << SOElement(sfPaths, SOE_OPTIONAL) + << SOElement(sfInvoiceID, SOE_OPTIONAL) + ; + + DECLARE_TF(WalletAdd, ttWALLET_ADD) + << SOElement(sfAmount, SOE_REQUIRED) + << SOElement(sfAuthorizedKey, SOE_REQUIRED) + << SOElement(sfPublicKey, SOE_REQUIRED) + ; + + DECLARE_TF(Contract, ttCONTRACT) + << SOElement(sfExpiration, SOE_REQUIRED) + << SOElement(sfBondAmount, SOE_REQUIRED) + << SOElement(sfStampEscrow, SOE_REQUIRED) + << SOElement(sfRippleEscrow, SOE_REQUIRED) + << SOElement(sfCreateCode, SOE_OPTIONAL) + << SOElement(sfFundCode, SOE_OPTIONAL) + << SOElement(sfRemoveCode, SOE_OPTIONAL) + << SOElement(sfExpireCode, SOE_OPTIONAL) + ; + + DECLARE_TF(RemoveContract, ttCONTRACT_REMOVE) + << SOElement(sfTarget, SOE_REQUIRED) + ; + + return true; } -// vim:ts=4 + +bool TFInitComplete = TFInit(); + +TransactionFormat* TransactionFormat::getTxnFormat(TransactionType t) +{ + std::map::iterator it = byType.find(static_cast(t)); + if (it == byType.end()) + return NULL; + return it->second; +} + +TransactionFormat* TransactionFormat::getTxnFormat(int t) +{ + std::map::iterator it = byType.find((t)); + if (it == byType.end()) + return NULL; + return it->second; +} + +TransactionFormat* TransactionFormat::getTxnFormat(const std::string& t) +{ + std::map::iterator it = byName.find((t)); + if (it == byName.end()) + return NULL; + return it->second; +} + +// vim:ts=4 diff --git a/src/TransactionFormats.h b/src/TransactionFormats.h index a49a1513e1..857a43189c 100644 --- a/src/TransactionFormats.h +++ b/src/TransactionFormats.h @@ -15,22 +15,37 @@ enum TransactionType ttNICKNAME_SET = 6, ttOFFER_CREATE = 7, ttOFFER_CANCEL = 8, + ttCONTRACT = 9, + ttCONTRACT_REMOVE = 10, // can we use the same msg as offer cancel + ttCREDIT_SET = 20, - ttINVOICE = 10, }; -struct TransactionFormat +class TransactionFormat { - const char *t_name; - TransactionType t_type; - SOElement elements[16]; -}; +public: + std::string t_name; + TransactionType t_type; + std::vector elements; -const int TransactionISigningPubKey = 0; -const int TransactionISourceID = 1; -const int TransactionISequence = 2; -const int TransactionIType = 3; -const int TransactionIFee = 4; + static std::map byType; + static std::map byName; + + TransactionFormat(const char *name, TransactionType type) : t_name(name), t_type(type) + { + byName[name] = this; + byType[type] = this; + } + TransactionFormat& operator<<(const SOElement& el) + { + elements.push_back(new SOElement(el)); + return *this; + } + + static TransactionFormat* getTxnFormat(TransactionType t); + static TransactionFormat* getTxnFormat(const std::string& t); + static TransactionFormat* getTxnFormat(int t); +}; const int TransactionMinLen = 32; const int TransactionMaxLen = 1048576; @@ -45,9 +60,8 @@ const uint32 tfPassive = 0x00010000; // Payment flags: const uint32 tfCreateAccount = 0x00010000; const uint32 tfPartialPayment = 0x00020000; -const uint32 tfNoRippleDirect = 0x00040000; +const uint32 tfLimitQuality = 0x00040000; +const uint32 tfNoRippleDirect = 0x00080000; -extern TransactionFormat InnerTxnFormats[]; -extern TransactionFormat* getTxnFormat(TransactionType t); #endif // vim:ts=4 diff --git a/src/TransactionMeta.cpp b/src/TransactionMeta.cpp index 0476b94200..3f687897c1 100644 --- a/src/TransactionMeta.cpp +++ b/src/TransactionMeta.cpp @@ -4,6 +4,7 @@ #include #include +#include bool TransactionMetaNodeEntry::operator<(const TransactionMetaNodeEntry& e) const { @@ -33,132 +34,154 @@ bool TransactionMetaNodeEntry::operator>=(const TransactionMetaNodeEntry& e) con return compare(e) >= 0; } -TMNEBalance::TMNEBalance(SerializerIterator& sit) : TransactionMetaNodeEntry(TMNChangedBalance) +TMNEThread::TMNEThread(SerializerIterator& sit) : TransactionMetaNodeEntry(TMSThread) { - mFlags = sit.get32(); - mFirstAmount = * dynamic_cast(STAmount::deserialize(sit, "FirstAmount").get()); - if ((mFlags & TMBTwoAmounts) != 0) - mSecondAmount = * dynamic_cast(STAmount::deserialize(sit, "SecondAmount").get()); + mPrevTxID = sit.get256(); + mPrevLgrSeq = sit.get32(); } -void TMNEBalance::addRaw(Serializer& sit) const +void TMNEThread::addRaw(Serializer& sit) const { sit.add8(mType); - sit.add32(mFlags); - mFirstAmount.add(sit); - if ((mFlags & TMBTwoAmounts) != 0) - mSecondAmount.add(sit); + sit.add256(mPrevTxID); + sit.add32(mPrevLgrSeq); } -void TMNEBalance::adjustFirstAmount(const STAmount& a) +int TMNEThread::compare(const TransactionMetaNodeEntry&) const { - mFirstAmount += a; -} - -void TMNEBalance::adjustSecondAmount(const STAmount& a) -{ - mSecondAmount += a; - mFlags |= TMBTwoAmounts; -} - -int TMNEBalance::compare(const TransactionMetaNodeEntry&) const -{ - assert(false); // should never be two TMNEBalance entries for the same node (as of now) + assert(false); // should never be two entries for the same node (as of now) return 0; } -Json::Value TMNEBalance::getJson(int p) const +Json::Value TMNEThread::getJson(int) const { - Json::Value ret(Json::objectValue); + Json::Value inner(Json::objectValue); + inner["prev_transaction"] = mPrevTxID.GetHex(); + inner["prev_ledger_seq"] = mPrevLgrSeq; - if ((mFlags & TMBDestroyed) != 0) - ret["destroyed"] = "true"; - if ((mFlags & TMBPaidFee) != 0) - ret["transaction_fee"] = "true"; - - if ((mFlags & TMBRipple) != 0) - ret["type"] = "ripple"; - else if ((mFlags & TMBOffer) != 0) - ret["type"] = "offer"; - else - ret["type"] = "account"; - - if (!mFirstAmount.isZero()) - ret["amount"] = mFirstAmount.getJson(p); - if (!mSecondAmount.isZero()) - ret["second_amount"] = mSecondAmount.getJson(p); - - return ret; + Json::Value outer(Json::objectValue); + outer["thread"] = inner; + return outer; } -void TMNEUnfunded::addRaw(Serializer& sit) const +TMNEAmount::TMNEAmount(int type, SerializerIterator& sit) : TransactionMetaNodeEntry(type) { - sit.add8(mType); + mAmount = *dynamic_cast(STAmount::deserialize(sit, sfAmount).get()); // Ouch } -Json::Value TMNEUnfunded::getJson(int) const +void TMNEAmount::addRaw(Serializer& s) const { - return Json::Value("delete_unfunded"); + s.add8(mType); + mAmount.add(s); } -void TMNEUnfunded::setBalances(const STAmount& first, const STAmount& second) +Json::Value TMNEAmount::getJson(int v) const { - firstAmount = first; - secondAmount = second; -} - -int TMNEUnfunded::compare(const TransactionMetaNodeEntry&) const -{ - assert(false); // Can't be two deletes for same node - return 0; -} - -TransactionMetaNode::TransactionMetaNode(const uint256& node, SerializerIterator& sit) : mNode(node) -{ - mNode = sit.get256(); - mPreviousTransaction = sit.get256(); - mPreviousLedger = sit.get32(); - int type; - do + Json::Value outer(Json::objectValue); + switch (mType) { - type = sit.get8(); - if (type == TransactionMetaNodeEntry::TMNChangedBalance) - mEntries.push_back(new TMNEBalance(sit)); - if (type == TransactionMetaNodeEntry::TMNDeleteUnfunded) - mEntries.push_back(new TMNEUnfunded()); - else if (type != TransactionMetaNodeEntry::TMNEndOfMetadata) - throw std::runtime_error("Unparseable metadata"); - } while (type != TransactionMetaNodeEntry::TMNEndOfMetadata); + case TMSPrevBalance: outer["prev_balance"] = mAmount.getJson(v); break; + case TMSFinalBalance: outer["final_balance"] = mAmount.getJson(v); break; + case TMSPrevTakerPays: outer["prev_taker_pays"] = mAmount.getJson(v); break; + case TMSPrevTakerGets: outer["prev_taker_gets"] = mAmount.getJson(v); break; + case TMSFinalTakerPays: outer["final_taker_pays"] = mAmount.getJson(v); break; + case TMSFinalTakerGets: outer["final_taker_gets"] = mAmount.getJson(v); break; + default: assert(false); + } + return outer; +} + +int TMNEAmount::compare(const TransactionMetaNodeEntry& e) const +{ + assert(false); // can't be two changed amounts of same type + return 0; +} + +TMNEAccount::TMNEAccount(int type, SerializerIterator& sit) + : TransactionMetaNodeEntry(type), mAccount(STAccount(sit.getVL()).getValueNCA()) +{ ; } + +void TMNEAccount::addRaw(Serializer& sit) const +{ + sit.add8(mType); + + STAccount sta; + sta.setValueNCA(mAccount); + sta.add(sit); +} + +Json::Value TMNEAccount::getJson(int) const +{ + Json::Value outer(Json::objectValue); + switch (mType) + { + case TMSPrevAccount: outer["prev_account"] = mAccount.humanAccountID(); break; + case TMSLowID: outer["lowID"] = mAccount.humanAccountID(); break; + case TMSHighID: outer["highID"] = mAccount.humanAccountID(); break; + default: assert(false); + } + return outer; +} + +int TMNEAccount::compare(const TransactionMetaNodeEntry&) const +{ + assert(false); // Can't be two modified accounts of same type for same node + return 0; +} + +TransactionMetaNode::TransactionMetaNode(int type, const uint256& node, SerializerIterator& sit) + : mType(type), mNode(node) +{ + while (1) + { + int nType = sit.get8(); + switch (nType) + { + case TMSEndOfNode: + return; + + case TMSThread: + mEntries.push_back(new TMNEThread(sit)); + break; + + // Nodes that contain an amount + case TMSPrevBalance: + case TMSPrevTakerPays: + case TMSPrevTakerGets: + case TMSFinalTakerPays: + case TMSFinalTakerGets: + mEntries.push_back(new TMNEAmount(nType, sit)); + + case TMSPrevAccount: + mEntries.push_back(new TMNEAccount(nType, sit)); + } + } } void TransactionMetaNode::addRaw(Serializer& s) { + s.add8(mType); s.add256(mNode); - s.add256(mPreviousTransaction); - s.add32(mPreviousLedger); mEntries.sort(); - for (boost::ptr_vector::const_iterator it = mEntries.begin(), end = mEntries.end(); - it != end; ++it) - it->addRaw(s); - s.add8(TransactionMetaNodeEntry::TMNEndOfMetadata); + BOOST_FOREACH(TransactionMetaNodeEntry& it, mEntries) + it.addRaw(s); + s.add8(TMSEndOfNode); } TransactionMetaNodeEntry* TransactionMetaNode::findEntry(int nodeType) { - for (boost::ptr_vector::iterator it = mEntries.begin(), end = mEntries.end(); - it != end; ++it) - if (it->getType() == nodeType) - return &*it; + BOOST_FOREACH(TransactionMetaNodeEntry& it, mEntries) + if (it.getType() == nodeType) + return ⁢ return NULL; } -TMNEBalance* TransactionMetaNode::findBalance() +TMNEAmount* TransactionMetaNode::findAmount(int nType) { - for (boost::ptr_vector::iterator it = mEntries.begin(), end = mEntries.end(); - it != end; ++it) - if (it->getType() == TransactionMetaNodeEntry::TMNChangedBalance) - return dynamic_cast(&*it); - TMNEBalance* node = new TMNEBalance(); + BOOST_FOREACH(TransactionMetaNodeEntry& it, mEntries) + if (it.getType() == nType) + return dynamic_cast(&it); + TMNEAmount* node = new TMNEAmount(nType); mEntries.push_back(node); return node; } @@ -168,25 +191,59 @@ void TransactionMetaNode::addNode(TransactionMetaNodeEntry* node) mEntries.push_back(node); } -void TransactionMetaNode::thread(const uint256& prevTx, uint32 prevLgr) +bool TransactionMetaNode::thread(const uint256& prevTx, uint32 prevLgr) { - assert((mPreviousLedger == 0) || (mPreviousLedger == prevLgr)); - assert(mPreviousTransaction.isZero() || (mPreviousTransaction == prevTx)); - mPreviousTransaction = prevTx; - mPreviousLedger = prevLgr; + BOOST_FOREACH(TransactionMetaNodeEntry& it, mEntries) + if (it.getType() == TMSThread) + return false; + addNode(new TMNEThread(prevTx, prevLgr)); + return true; +} + +bool TransactionMetaNode::addAmount(int nodeType, const STAmount& amount) +{ + BOOST_FOREACH(TransactionMetaNodeEntry& it, mEntries) + if (it.getType() == nodeType) + { + TMNEAmount* a = dynamic_cast(&it); + assert(a && (a->getAmount() == amount)); + return false; + } + addNode(new TMNEAmount(nodeType, amount)); + return true; +} + +bool TransactionMetaNode::addAccount(int nodeType, const NewcoinAddress& account) +{ + BOOST_FOREACH(TransactionMetaNodeEntry& it, mEntries) + if (it.getType() == nodeType) + { + TMNEAccount* a = dynamic_cast(&it); + assert(a && (a->getAccount() == account)); + return false; + } + addNode(new TMNEAccount(nodeType, account)); + return true; } Json::Value TransactionMetaNode::getJson(int v) const { Json::Value ret = Json::objectValue; + + switch (mType) + { + case TMNCreatedNode: ret["action"] = "create"; break; + case TMNDeletedNode: ret["action"] = "delete"; break; + case TMNModifiedNode: ret["action"] = "modify"; break; + default: + assert(false); + } + ret["node"] = mNode.GetHex(); - ret["previous_transaction"] = mPreviousTransaction.GetHex(); - ret["previous_ledger"] = mPreviousLedger; Json::Value e = Json::arrayValue; - for (boost::ptr_vector::const_iterator it = mEntries.begin(), end = mEntries.end(); - it != end; ++it) - e.append(it->getJson(v)); + BOOST_FOREACH(const TransactionMetaNodeEntry& it, mEntries) + e.append(it.getJson(v)); ret["entries"] = e; return ret; @@ -199,13 +256,12 @@ TransactionMetaSet::TransactionMetaSet(uint32 ledger, const std::vector::iterator it = mNodes.begin(), end = mNodes.end(); it != end; ++it) it->second.addRaw(s); - s.add256(uint256()); + s.add8(TMNEndOfMetadata); } Json::Value TransactionMetaSet::getJson(int v) const @@ -238,6 +294,18 @@ bool TransactionMetaSet::isNodeAffected(const uint256& node) const return mNodes.find(node) != mNodes.end(); } +TransactionMetaNode& TransactionMetaSet::getAffectedNode(const uint256& node, int type, bool overrideType) +{ + std::map::iterator it = mNodes.find(node); + if (it != mNodes.end()) + { + if (overrideType) + it->second.setType(type); + return it->second; + } + return mNodes.insert(std::make_pair(node, TransactionMetaNode(node, type))).first->second; +} + const TransactionMetaNode& TransactionMetaSet::peekAffectedNode(const uint256& node) const { std::map::const_iterator it = mNodes.find(node); @@ -259,28 +327,3 @@ void TransactionMetaSet::swap(TransactionMetaSet& s) mNodes.swap(s.mNodes); } -TransactionMetaNode& TransactionMetaSet::modifyNode(const uint256& node) -{ - std::map::iterator it = mNodes.find(node); - if (it != mNodes.end()) - return it->second; - return mNodes.insert(std::make_pair(node, TransactionMetaNode(node))).first->second; -} - -#if 0 -void TransactionMetaSet::threadNode(const uint256& node, const uint256& prevTx, uint32 prevLgr) -{ - modifyNode(node).thread(prevTx, prevLgr); -} - -void TransactionMetaSet::deleteUnfunded(const uint256& nodeID, - const STAmount& firstBalance, const STAmount &secondBalance) -{ - TransactionMetaNode& node = modifyNode(nodeID); - TMNEUnfunded* entry = dynamic_cast(node.findEntry(TransactionMetaNodeEntry::TMNDeleteUnfunded)); - if (entry) - entry->setBalances(firstBalance, secondBalance); - else - node.addNode(new TMNEUnfunded(firstBalance, secondBalance)); -} -#endif \ No newline at end of file diff --git a/src/TransactionMeta.h b/src/TransactionMeta.h index d863ccb701..e3ac3b6cd4 100644 --- a/src/TransactionMeta.h +++ b/src/TransactionMeta.h @@ -12,24 +12,45 @@ #include "Serializer.h" #include "SerializedTypes.h" +// master record types +static const int TMNEndOfMetadata = 0x00; +static const int TMNCreatedNode = 0x10; // This transaction created this node +static const int TMNDeletedNode = 0x11; +static const int TMNModifiedNode = 0x12; + +// sub record types - special +static const int TMSEndOfNode = 0x00; +static const int TMSThread = 0x01; // Holds previous TxID and LgrSeq for threading + +// sub record types - containing an amount +static const int TMSPrevBalance = 0x11; // Balances prior to the transaction +static const int TMSFinalBalance = 0x12; // deleted with non-zero balance +static const int TMSPrevTakerPays = 0x13; +static const int TMSPrevTakerGets = 0x14; +static const int TMSFinalTakerPays = 0x15; // Balances at node deletion time +static const int TMSFinalTakerGets = 0x16; + +// sub record types - containing an account (for example, for when a nickname is transferred) +static const int TMSPrevAccount = 0x20; +static const int TMSLowID = 0x21; +static const int TMSHighID = 0x22; + class TransactionMetaNodeEntry { // a way that a transaction has affected a node public: - typedef boost::shared_ptr pointer; - static const int TMNEndOfMetadata = 0; - static const int TMNChangedBalance = 1; - static const int TMNDeleteUnfunded = 2; - +protected: int mType; + +public: TransactionMetaNodeEntry(int type) : mType(type) { ; } + virtual ~TransactionMetaNodeEntry() { ; } int getType() const { return mType; } virtual Json::Value getJson(int) const = 0; virtual void addRaw(Serializer&) const = 0; - virtual int compare(const TransactionMetaNodeEntry&) const = 0; bool operator<(const TransactionMetaNodeEntry&) const; bool operator<=(const TransactionMetaNodeEntry&) const; @@ -40,55 +61,70 @@ public: { return std::auto_ptr(duplicate()); } protected: + virtual int compare(const TransactionMetaNodeEntry&) const = 0; virtual TransactionMetaNodeEntry* duplicate(void) const = 0; }; -class TMNEBalance : public TransactionMetaNodeEntry -{ // a transaction affected the balance of a node -public: +class TMNEThread : public TransactionMetaNodeEntry +{ +protected: + uint256 mPrevTxID; + uint32 mPrevLgrSeq; - static const int TMBTwoAmounts = 0x001; - static const int TMBDestroyed = 0x010; - static const int TMBPaidFee = 0x020; - static const int TMBRipple = 0x100; - static const int TMBOffer = 0x200; +public: + TMNEThread() : TransactionMetaNodeEntry(TMSThread), mPrevLgrSeq(0) { ; } + TMNEThread(uint256 prevTx, uint32 prevLgrSeq) + : TransactionMetaNodeEntry(TMSThread), mPrevTxID(prevTx), mPrevLgrSeq(prevLgrSeq) + { ; } + TMNEThread(SerializerIterator&); + + virtual void addRaw(Serializer&) const; + virtual Json::Value getJson(int) const; protected: - unsigned mFlags; - STAmount mFirstAmount, mSecondAmount; - -public: - TMNEBalance() : TransactionMetaNodeEntry(TMNChangedBalance), mFlags(0) { ; } - - TMNEBalance(SerializerIterator&); - virtual void addRaw(Serializer&) const; - - unsigned getFlags() const { return mFlags; } - const STAmount& getFirstAmount() const { return mFirstAmount; } - const STAmount& getSecondAmount() const { return mSecondAmount; } - - void adjustFirstAmount(const STAmount&); - void adjustSecondAmount(const STAmount&); - void setFlags(unsigned flags); - - virtual Json::Value getJson(int) const; + virtual TransactionMetaNodeEntry* duplicate(void) const { return new TMNEThread(*this); } virtual int compare(const TransactionMetaNodeEntry&) const; - virtual TransactionMetaNodeEntry* duplicate(void) const { return new TMNEBalance(*this); } }; -class TMNEUnfunded : public TransactionMetaNodeEntry +class TMNEAmount : public TransactionMetaNodeEntry +{ // a transaction affected the balance of a node +protected: + STAmount mAmount; + +public: + TMNEAmount(int type) : TransactionMetaNodeEntry(type) { ; } + TMNEAmount(int type, const STAmount &a) : TransactionMetaNodeEntry(type), mAmount(a) { ; } + + TMNEAmount(int type, SerializerIterator&); + virtual void addRaw(Serializer&) const; + + const STAmount& getAmount() const { return mAmount; } + void setAmount(const STAmount& a) { mAmount = a; } + + virtual Json::Value getJson(int) const; + +protected: + virtual TransactionMetaNodeEntry* duplicate(void) const { return new TMNEAmount(*this); } + virtual int compare(const TransactionMetaNodeEntry&) const; +}; + +class TMNEAccount : public TransactionMetaNodeEntry { // node was deleted because it was unfunded protected: - STAmount firstAmount, secondAmount; // Amounts left when declared unfunded + NewcoinAddress mAccount; + public: - TMNEUnfunded() : TransactionMetaNodeEntry(TMNDeleteUnfunded) { ; } - TMNEUnfunded(const STAmount& f, const STAmount& s) : - TransactionMetaNodeEntry(TMNDeleteUnfunded), firstAmount(f), secondAmount(s) { ; } - void setBalances(const STAmount& firstBalance, const STAmount& secondBalance); + TMNEAccount(int type, const NewcoinAddress& acct) : TransactionMetaNodeEntry(type), mAccount(acct) { ; } + TMNEAccount(int type, SerializerIterator&); virtual void addRaw(Serializer&) const; virtual Json::Value getJson(int) const; + + const NewcoinAddress& getAccount() const { return mAccount; } + void setAccount(const NewcoinAddress& a) { mAccount = a; } + +protected: + virtual TransactionMetaNodeEntry* duplicate(void) const { return new TMNEAccount(*this); } virtual int compare(const TransactionMetaNodeEntry&) const; - virtual TransactionMetaNodeEntry* duplicate(void) const { return new TMNEUnfunded(*this); } }; inline TransactionMetaNodeEntry* new_clone(const TransactionMetaNodeEntry& s) { return s.clone().release(); } @@ -100,21 +136,17 @@ public: typedef boost::shared_ptr pointer; protected: + int mType; uint256 mNode; - uint256 mPreviousTransaction; - uint32 mPreviousLedger; boost::ptr_vector mEntries; public: - TransactionMetaNode(const uint256 &node) : mNode(node) { ; } + TransactionMetaNode(const uint256 &node, int type) : mType(type), mNode(node) { ; } const uint256& getNode() const { return mNode; } - const uint256& getPreviousTransaction() const { return mPreviousTransaction; } - uint32 getPreviousLedger() const { return mPreviousLedger; } const boost::ptr_vector& peekEntries() const { return mEntries; } TransactionMetaNodeEntry* findEntry(int nodeType); - TMNEBalance* findBalance(); void addNode(TransactionMetaNodeEntry*); bool operator<(const TransactionMetaNode& n) const { return mNode < n.mNode; } @@ -122,28 +154,29 @@ public: bool operator>(const TransactionMetaNode& n) const { return mNode > n.mNode; } bool operator>=(const TransactionMetaNode& n) const { return mNode >= n.mNode; } - void thread(const uint256& prevTx, uint32 prevLgr); + bool thread(const uint256& prevTx, uint32 prevLgr); - TransactionMetaNode(const uint256&node, SerializerIterator&); + TransactionMetaNode(int type, const uint256& node, SerializerIterator&); void addRaw(Serializer&); + void setType(int t) { mType = t; } Json::Value getJson(int) const; - void threadNode(const uint256& previousTransaction, uint32 previousLedger); - void deleteUnfunded(const STAmount& firstBalance, const STAmount& secondBalance); - void adjustBalance(unsigned flags, const STAmount &amount, bool signedBy); - void adjustBalances(unsigned flags, const STAmount &firstAmt, const STAmount &secondAmt); + bool addAmount(int nodeType, const STAmount& amount); + bool addAccount(int nodeType, const NewcoinAddress& account); + TMNEAmount* findAmount(int nodeType); }; class TransactionMetaSet { +public: + typedef boost::shared_ptr pointer; + protected: uint256 mTransactionID; uint32 mLedger; std::map mNodes; - TransactionMetaNode& modifyNode(const uint256&); - public: TransactionMetaSet() : mLedger(0) { ; } TransactionMetaSet(const uint256& txID, uint32 ledger) : mTransactionID(txID), mLedger(ledger) { ; } @@ -153,8 +186,11 @@ public: void clear() { mNodes.clear(); } void swap(TransactionMetaSet&); + const uint256& getTxID() { return mTransactionID; } + uint32 getLgrSeq() { return mLedger; } + bool isNodeAffected(const uint256&) const; - TransactionMetaNode& getAffectedNode(const uint256&); + TransactionMetaNode& getAffectedNode(const uint256&, int type, bool overrideType); const TransactionMetaNode& peekAffectedNode(const uint256&) const; Json::Value getJson(int) const; diff --git a/src/UniqueNodeList.cpp b/src/UniqueNodeList.cpp index 74ceb647fd..96ca1b7d7d 100644 --- a/src/UniqueNodeList.cpp +++ b/src/UniqueNodeList.cpp @@ -2,7 +2,6 @@ // XXX Want a limit of 2000 validators. #include "Application.h" -#include "Conversion.h" #include "HttpsClient.h" #include "Log.h" #include "ParseSection.h" @@ -39,6 +38,10 @@ #define REFERRAL_VALIDATORS_MAX 50 #define REFERRAL_IPS_MAX 50 +#ifndef MIN +#define MIN(x,y) ((x)<(y)?(x):(y)) +#endif + UniqueNodeList::UniqueNodeList(boost::asio::io_service& io_service) : mdtScoreTimer(io_service), mFetchActive(0), @@ -180,8 +183,6 @@ void UniqueNodeList::scoreCompute() Database* db=theApp->getWalletDB()->getDB(); - std::string strSql; - // For each entry in SeedDomains with a PublicKey: // - Add an entry in umPulicIdx, umDomainIdx, and vsnNodes. { @@ -385,7 +386,7 @@ void UniqueNodeList::scoreCompute() db->executeSQL("BEGIN;"); db->executeSQL("UPDATE TrustedNodes SET Score = 0 WHERE Score != 0;"); - if (vsnNodes.size()) + if (!vsnNodes.empty()) { // Load existing Seens from DB. std::vector vstrPublicKeys; @@ -406,7 +407,7 @@ void UniqueNodeList::scoreCompute() boost::unordered_set usUNL; - if (vsnNodes.size()) + if (!vsnNodes.empty()) { // Update the score old entries and add new entries as needed. std::vector vstrValues; @@ -442,7 +443,7 @@ void UniqueNodeList::scoreCompute() boost::unordered_map umValidators; - if (vsnNodes.size()) + if (!vsnNodes.empty()) { std::vector vstrPublicKeys; @@ -623,7 +624,7 @@ void UniqueNodeList::processIps(const std::string& strSite, const NewcoinAddress } // Add new referral entries. - if (pmtVecStrIps && pmtVecStrIps->size()) { + if (pmtVecStrIps && !pmtVecStrIps->empty()) { std::vector vstrValues; vstrValues.resize(MIN(pmtVecStrIps->size(), REFERRAL_IPS_MAX)); @@ -708,7 +709,6 @@ int UniqueNodeList::processValidators(const std::string& strSite, const std::str break; boost::smatch smMatch; - std::string strIP; // domain comment? // public_key comment? @@ -775,7 +775,7 @@ int UniqueNodeList::processValidators(const std::string& strSite, const std::str } // Given a section with IPs, parse and persist it for a validator. -void UniqueNodeList::responseIps(const std::string& strSite, const NewcoinAddress& naNodePublic, const boost::system::error_code& err, const std::string strIpsFile) +void UniqueNodeList::responseIps(const std::string& strSite, const NewcoinAddress& naNodePublic, const boost::system::error_code& err, const std::string& strIpsFile) { if (!err) { @@ -815,7 +815,7 @@ void UniqueNodeList::getIpsUrl(const NewcoinAddress& naNodePublic, section secSi } // After fetching a newcoin.txt from a web site, given a section with validators, parse and persist it. -void UniqueNodeList::responseValidators(const std::string& strValidatorsUrl, const NewcoinAddress& naNodePublic, section secSite, const std::string& strSite, const boost::system::error_code& err, const std::string strValidatorsFile) +void UniqueNodeList::responseValidators(const std::string& strValidatorsUrl, const NewcoinAddress& naNodePublic, section secSite, const std::string& strSite, const boost::system::error_code& err, const std::string& strValidatorsFile) { if (!err) { @@ -854,7 +854,7 @@ void UniqueNodeList::getValidatorsUrl(const NewcoinAddress& naNodePublic, sectio } // Process a newcoin.txt. -void UniqueNodeList::processFile(const std::string strDomain, const NewcoinAddress& naNodePublic, section secSite) +void UniqueNodeList::processFile(const std::string& strDomain, const NewcoinAddress& naNodePublic, section secSite) { // // Process Validators @@ -881,7 +881,7 @@ void UniqueNodeList::processFile(const std::string strDomain, const NewcoinAddre } // Given a newcoin.txt, process it. -void UniqueNodeList::responseFetch(const std::string strDomain, const boost::system::error_code& err, const std::string strSiteFile) +void UniqueNodeList::responseFetch(const std::string& strDomain, const boost::system::error_code& err, const std::string& strSiteFile) { section secSite = ParseSection(strSiteFile, true); bool bGood = !err; diff --git a/src/UniqueNodeList.h b/src/UniqueNodeList.h index 1de853c960..667cbe92aa 100644 --- a/src/UniqueNodeList.h +++ b/src/UniqueNodeList.h @@ -95,7 +95,7 @@ private: bool scoreRound(std::vector& vsnNodes); - void responseFetch(const std::string strDomain, const boost::system::error_code& err, const std::string strSiteFile); + void responseFetch(const std::string& strDomain, const boost::system::error_code& err, const std::string& strSiteFile); boost::posix_time::ptime mtpScoreNext; // When to start scoring. boost::posix_time::ptime mtpScoreStart; // Time currently started scoring. @@ -119,13 +119,13 @@ private: void getValidatorsUrl(const NewcoinAddress& naNodePublic, section secSite); void getIpsUrl(const NewcoinAddress& naNodePublic, section secSite); - void responseIps(const std::string& strSite, const NewcoinAddress& naNodePublic, const boost::system::error_code& err, const std::string strIpsFile); - void responseValidators(const std::string& strValidatorsUrl, const NewcoinAddress& naNodePublic, section secSite, const std::string& strSite, const boost::system::error_code& err, const std::string strValidatorsFile); + void responseIps(const std::string& strSite, const NewcoinAddress& naNodePublic, const boost::system::error_code& err, const std::string& strIpsFile); + void responseValidators(const std::string& strValidatorsUrl, const NewcoinAddress& naNodePublic, section secSite, const std::string& strSite, const boost::system::error_code& err, const std::string& strValidatorsFile); void processIps(const std::string& strSite, const NewcoinAddress& naNodePublic, section::mapped_type* pmtVecStrIps); int processValidators(const std::string& strSite, const std::string& strValidatorsSrc, const NewcoinAddress& naNodePublic, validatorSource vsWhy, section::mapped_type* pmtVecStrValidators); - void processFile(const std::string strDomain, const NewcoinAddress& naNodePublic, section secSite); + void processFile(const std::string& strDomain, const NewcoinAddress& naNodePublic, section secSite); bool getSeedDomains(const std::string& strDomain, seedDomain& dstSeedDomain); void setSeedDomains(const seedDomain& dstSeedDomain, bool bNext); diff --git a/src/ValidationCollection.cpp b/src/ValidationCollection.cpp index 466b3d7f2e..e4d1117549 100644 --- a/src/ValidationCollection.cpp +++ b/src/ValidationCollection.cpp @@ -1,20 +1,24 @@ #include "ValidationCollection.h" +#include + #include "Application.h" #include "LedgerTiming.h" #include "Log.h" -bool ValidationCollection::addValidation(SerializedValidation::pointer val) +// #define VC_DEBUG + +bool ValidationCollection::addValidation(const SerializedValidation::pointer& val) { NewcoinAddress signer = val->getSignerPublic(); bool isCurrent = false; - if (theApp->getUNL().nodeInUNL(signer)) + if (theApp->getUNL().nodeInUNL(signer) || val->isTrusted()) { val->setTrusted(); uint32 now = theApp->getOPs().getCloseTimeNC(); - uint32 valClose = val->getCloseTime(); - if ((now > (valClose - 4)) && (now < (valClose + LEDGER_MAX_INTERVAL))) + uint32 valClose = val->getSignTime(); + if ((now > (valClose - LEDGER_EARLY_INTERVAL)) && (now < (valClose + LEDGER_VAL_INTERVAL))) isCurrent = true; else Log(lsWARNING) << "Received stale validation now=" << now << ", close=" << valClose; @@ -30,28 +34,23 @@ bool ValidationCollection::addValidation(SerializedValidation::pointer val) return false; if (isCurrent) { - boost::unordered_map::iterator it = mCurrentValidations.find(node); - if ((it == mCurrentValidations.end()) || (!it->second.newest) || - (val->getCloseTime() > it->second.newest->getCloseTime())) + boost::unordered_map::iterator it = mCurrentValidations.find(node); + if (it == mCurrentValidations.end()) + mCurrentValidations.insert(std::make_pair(node, val)); + else if (!it->second) + it->second = val; + else if (val->getSignTime() > it->second->getSignTime()) { - if (it != mCurrentValidations.end()) - { - if (it->second.oldest) - { - mStaleValidations.push_back(it->second.oldest); - condWrite(); - } - it->second.oldest = it->second.newest; - it->second.newest = val; - } - else - mCurrentValidations.insert(std::make_pair(node, ValidationPair(val))); + val->setPreviousHash(it->second->getLedgerHash()); + mStaleValidations.push_back(it->second); + it->second = val; + condWrite(); } } } - Log(lsINFO) << "Val for " << hash.GetHex() << " from " << signer.humanNodePublic() - << " added " << (val->isTrusted() ? "trusted" : "UNtrusted"); + Log(lsINFO) << "Val for " << hash << " from " << signer.humanNodePublic() + << " added " << (val->isTrusted() ? "trusted/" : "UNtrusted/") << (isCurrent ? "current" : "stale"); return isCurrent; } @@ -72,7 +71,7 @@ void ValidationCollection::getValidationCount(const uint256& ledger, bool curren trusted = untrusted = 0; boost::mutex::scoped_lock sl(mValidationLock); boost::unordered_map::iterator it = mValidations.find(ledger); - uint32 now = theApp->getOPs().getCloseTimeNC(); + uint32 now = theApp->getOPs().getNetworkTimeNC(); if (it != mValidations.end()) { for (ValidationSet::iterator vit = it->second.begin(), end = it->second.end(); vit != end; ++vit) @@ -80,9 +79,15 @@ void ValidationCollection::getValidationCount(const uint256& ledger, bool curren bool isTrusted = vit->second->isTrusted(); if (isTrusted && currentOnly) { - uint32 closeTime = vit->second->getCloseTime(); - if ((now < closeTime) || (now > (closeTime + 2 * LEDGER_MAX_INTERVAL))) + uint32 closeTime = vit->second->getSignTime(); + if ((now < (closeTime - LEDGER_EARLY_INTERVAL)) || (now > (closeTime + LEDGER_VAL_INTERVAL))) isTrusted = false; + else + { +#ifdef VC_DEBUG + Log(lsINFO) << "VC: Untrusted due to time " << ledger; +#endif + } } if (isTrusted) ++trusted; @@ -90,6 +95,9 @@ void ValidationCollection::getValidationCount(const uint256& ledger, bool curren ++untrusted; } } +#ifdef VC_DEBUG + Log(lsINFO) << "VC: " << ledger << "t:" << trusted << " u:" << untrusted; +#endif } int ValidationCollection::getTrustedValidationCount(const uint256& ledger) @@ -108,99 +116,84 @@ int ValidationCollection::getTrustedValidationCount(const uint256& ledger) return trusted; } -int ValidationCollection::getCurrentValidationCount(uint32 afterTime) -{ +int ValidationCollection::getNodesAfter(const uint256& ledger) +{ // Number of trusted nodes that have moved past this ledger int count = 0; boost::mutex::scoped_lock sl(mValidationLock); - for (boost::unordered_map::iterator it = mCurrentValidations.begin(), + for (boost::unordered_map::iterator it = mCurrentValidations.begin(), end = mCurrentValidations.end(); it != end; ++it) { - if (it->second.newest->isTrusted() && (it->second.newest->getCloseTime() > afterTime)) + if (it->second->isTrusted() && it->second->isPreviousHash(ledger)) ++count; } return count; } -boost::unordered_map ValidationCollection::getCurrentValidations() +int ValidationCollection::getLoadRatio(bool overLoaded) +{ // how many trusted nodes are able to keep up, higher is better + int goodNodes = overLoaded ? 1 : 0; + int badNodes = overLoaded ? 0 : 1; + { + boost::mutex::scoped_lock sl(mValidationLock); + for (boost::unordered_map::iterator it = mCurrentValidations.begin(), + end = mCurrentValidations.end(); it != end; ++it) + { + if (it->second->isTrusted()) + { + if (it->second->isFull()) + ++goodNodes; + else + ++badNodes; + } + } + } + return (goodNodes * 100) / (goodNodes + badNodes); +} + +boost::unordered_map ValidationCollection::getCurrentValidations(uint256 currentLedger) { - uint32 now = theApp->getOPs().getCloseTimeNC(); + uint32 cutoff = theApp->getOPs().getNetworkTimeNC() - LEDGER_VAL_INTERVAL; + bool valCurrentLedger = currentLedger.isNonZero(); + boost::unordered_map ret; { boost::mutex::scoped_lock sl(mValidationLock); - boost::unordered_map::iterator it = mCurrentValidations.begin(); - bool anyNew = false; + boost::unordered_map::iterator it = mCurrentValidations.begin(); while (it != mCurrentValidations.end()) { - ValidationPair& pair = it->second; - - if (pair.oldest && (now > (pair.oldest->getCloseTime() + LEDGER_MAX_INTERVAL))) - { - mStaleValidations.push_back(pair.oldest); - pair.oldest = SerializedValidation::pointer(); - anyNew = true; - } - if (pair.newest && (now > (pair.newest->getCloseTime() + LEDGER_MAX_INTERVAL))) - { - mStaleValidations.push_back(pair.newest); - pair.newest = SerializedValidation::pointer(); - anyNew = true; - } - if (!pair.newest && !pair.oldest) + if (!it->second) // contains no record it = mCurrentValidations.erase(it); + else if (it->second->getSignTime() < cutoff) + { // contains a stale record + mStaleValidations.push_back(it->second); + it->second = SerializedValidation::pointer(); + condWrite(); + it = mCurrentValidations.erase(it); + } else - { - if (pair.oldest) - { - Log(lsTRACE) << "OLD " << pair.oldest->getLedgerHash().GetHex() << " " << - boost::lexical_cast(pair.oldest->getCloseTime()); - ++ret[pair.oldest->getLedgerHash()]; - } - if (pair.newest) - { - Log(lsTRACE) << "NEW " << pair.newest->getLedgerHash().GetHex() << " " << - boost::lexical_cast(pair.newest->getCloseTime()); - ++ret[pair.newest->getLedgerHash()]; - } + { // contains a live record + if (valCurrentLedger && it->second->isPreviousHash(currentLedger)) + ++ret[currentLedger]; // count for the favored ledger + else + ++ret[it->second->getLedgerHash()]; ++it; } } - if (anyNew) - condWrite(); } return ret; } -bool ValidationCollection::isDeadLedger(const uint256& ledger) -{ - for (std::list::iterator it = mDeadLedgers.begin(), end = mDeadLedgers.end(); it != end; ++it) - if (*it == ledger) - return true; - return false; -} - -void ValidationCollection::addDeadLedger(const uint256& ledger) -{ - if (isDeadLedger(ledger)) - return; - - mDeadLedgers.push_back(ledger); - if (mDeadLedgers.size() >= 128) - mDeadLedgers.pop_front(); -} - void ValidationCollection::flush() { boost::mutex::scoped_lock sl(mValidationLock); - boost::unordered_map::iterator it = mCurrentValidations.begin(); + boost::unordered_map::iterator it = mCurrentValidations.begin(); bool anyNew = false; while (it != mCurrentValidations.end()) { - if (it->second.oldest) - mStaleValidations.push_back(it->second.oldest); - if (it->second.newest) - mStaleValidations.push_back(it->second.newest); + if (it->second) + mStaleValidations.push_back(it->second); ++it; anyNew = true; } @@ -227,8 +220,7 @@ void ValidationCollection::condWrite() void ValidationCollection::doWrite() { static boost::format insVal("INSERT INTO LedgerValidations " - "(LedgerHash,NodePubKey,Flags,CloseTime,Signature) VALUES " - "('%s','%s','%u','%u',%s);"); + "(LedgerHash,NodePubKey,Flags,SignTime,Signature) VALUES ('%s','%s','%u','%u',%s);"); boost::mutex::scoped_lock sl(mValidationLock); assert(mWriting); @@ -237,15 +229,15 @@ void ValidationCollection::doWrite() std::vector vector; mStaleValidations.swap(vector); sl.unlock(); - { - ScopedLock dbl(theApp->getLedgerDB()->getDBLock()); Database *db = theApp->getLedgerDB()->getDB(); + ScopedLock dbl(theApp->getLedgerDB()->getDBLock()); + db->executeSQL("BEGIN TRANSACTION;"); - for (std::vector::iterator it = vector.begin(); it != vector.end(); ++it) - db->executeSQL(boost::str(insVal % (*it)->getLedgerHash().GetHex() - % (*it)->getSignerPublic().humanNodePublic() % (*it)->getFlags() % (*it)->getCloseTime() - % db->escape(strCopy((*it)->getSignature())))); + BOOST_FOREACH(const SerializedValidation::pointer& it, vector) + db->executeSQL(boost::str(insVal % it->getLedgerHash().GetHex() + % it->getSignerPublic().humanNodePublic() % it->getFlags() % it->getSignTime() + % db->escape(strCopy(it->getSignature())))); db->executeSQL("END TRANSACTION;"); } diff --git a/src/ValidationCollection.h b/src/ValidationCollection.h index 2198a967fe..95cd4592e0 100644 --- a/src/ValidationCollection.h +++ b/src/ValidationCollection.h @@ -12,23 +12,14 @@ typedef boost::unordered_map ValidationSet; -class ValidationPair -{ -public: - SerializedValidation::pointer oldest, newest; - - ValidationPair(SerializedValidation::pointer v) : newest(v) { ; } -}; - class ValidationCollection { protected: boost::mutex mValidationLock; - boost::unordered_map mValidations; - boost::unordered_map mCurrentValidations; - std::vector mStaleValidations; - std::list mDeadLedgers; + boost::unordered_map mValidations; + boost::unordered_map mCurrentValidations; + std::vector mStaleValidations; bool mWriting; @@ -38,18 +29,16 @@ protected: public: ValidationCollection() : mWriting(false) { ; } - bool addValidation(SerializedValidation::pointer); + bool addValidation(const SerializedValidation::pointer&); ValidationSet getValidations(const uint256& ledger); void getValidationCount(const uint256& ledger, bool currentOnly, int& trusted, int& untrusted); int getTrustedValidationCount(const uint256& ledger); - int getCurrentValidationCount(uint32 afterTime); - boost::unordered_map getCurrentValidations(); + int getNodesAfter(const uint256& ledger); + int getLoadRatio(bool overLoaded); - void addDeadLedger(const uint256&); - bool isDeadLedger(const uint256&); - std::list getDeadLedgers() { return mDeadLedgers; } + boost::unordered_map getCurrentValidations(uint256 currentLedger = uint256()); void flush(); }; diff --git a/src/Version.h b/src/Version.h index 51603753c5..9d7a56be2e 100644 --- a/src/Version.h +++ b/src/Version.h @@ -4,10 +4,10 @@ // Versions // -#define SERVER_VERSION_MAJOR 0 -#define SERVER_VERSION_MINOR 4 -#define SERVER_VERSION_SUB "-a" -#define SERVER_NAME "NewCoin" +#define SERVER_VERSION_MAJOR 0 +#define SERVER_VERSION_MINOR 6 +#define SERVER_VERSION_SUB "-a" +#define SERVER_NAME "NewCoin" #define SV_STRINGIZE(x) SV_STRINGIZE2(x) #define SV_STRINGIZE2(x) #x @@ -15,12 +15,12 @@ (SERVER_NAME "-" SV_STRINGIZE(SERVER_VERSION_MAJOR) "." SV_STRINGIZE(SERVER_VERSION_MINOR) SERVER_VERSION_SUB) // Version we prefer to speak: -#define PROTO_VERSION_MAJOR 0 -#define PROTO_VERSION_MINOR 4 +#define PROTO_VERSION_MAJOR 1 +#define PROTO_VERSION_MINOR 1 -// Version we wil speak to: -#define MIN_PROTO_MAJOR 0 -#define MIN_PROTO_MINOR 4 +// Version we will speak to: +#define MIN_PROTO_MAJOR 1 +#define MIN_PROTO_MINOR 1 #define MAKE_VERSION_INT(maj,min) ((maj << 16) | min) #define GET_VERSION_MAJOR(ver) (ver >> 16) diff --git a/src/WSDoor.cpp b/src/WSDoor.cpp index b4394fb956..208c5c71c3 100644 --- a/src/WSDoor.cpp +++ b/src/WSDoor.cpp @@ -22,6 +22,9 @@ // This is a light weight, untrusted interface for web clients. // For now we don't provide proof. Later we will. // +// Might need to support this header for browsers: Access-Control-Allow-Origin: * +// - https://developer.mozilla.org/en-US/docs/HTTP_access_control +// // // Strategy: @@ -75,7 +78,12 @@ public: Json::Value invokeCommand(const Json::Value& jvRequest); boost::unordered_set parseAccountIds(const Json::Value& jvArray); - // Commands + // Request-Response Commands + void doLedgerClosed(Json::Value& jvResult, const Json::Value& jvRequest); + void doLedgerCurrent(Json::Value& jvResult, const Json::Value& jvRequest); + void doLedgerEntry(Json::Value& jvResult, const Json::Value& jvRequest); + + // Streaming Commands void doAccountInfoSubscribe(Json::Value& jvResult, const Json::Value& jvRequest); void doAccountInfoUnsubscribe(Json::Value& jvResult, const Json::Value& jvRequest); void doAccountTransactionSubscribe(Json::Value& jvResult, const Json::Value& jvRequest); @@ -293,6 +301,12 @@ Json::Value WSConnection::invokeCommand(const Json::Value& jvRequest) const char* pCommand; doFuncPtr dfpFunc; } commandsA[] = { + // Request-Response Commands: + { "ledger_closed", &WSConnection::doLedgerClosed }, + { "ledger_current", &WSConnection::doLedgerCurrent }, + { "ledger_entry", &WSConnection::doLedgerEntry }, + + // Streaming commands: { "account_info_subscribe", &WSConnection::doAccountInfoSubscribe }, { "account_info_unsubscribe", &WSConnection::doAccountInfoUnsubscribe }, { "account_transaction_subscribe", &WSConnection::doAccountTransactionSubscribe }, @@ -541,6 +555,239 @@ void WSConnection::doLedgerAccountsUnsubscribe(Json::Value& jvResult, const Json } } +void WSConnection::doLedgerClosed(Json::Value& jvResult, const Json::Value& jvRequest) +{ + uint256 uLedger = theApp->getOPs().getClosedLedger(); + + jvResult["ledger_index"] = theApp->getOPs().getLedgerID(uLedger); + jvResult["ledger"] = uLedger.ToString(); +} + +void WSConnection::doLedgerCurrent(Json::Value& jvResult, const Json::Value& jvRequest) +{ + jvResult["ledger_index"] = theApp->getOPs().getCurrentLedgerID(); +} + +void WSConnection::doLedgerEntry(Json::Value& jvResult, const Json::Value& jvRequest) +{ + NetworkOPs& noNetwork = theApp->getOPs(); + uint256 uLedger = jvRequest.isMember("ledger") ? uint256(jvRequest["ledger"].asString()) : 0; + uint32 uLedgerIndex = jvRequest.isMember("ledger_index") && jvRequest["ledger_index"].isNumeric() ? jvRequest["ledger_index"].asUInt() : 0; + + Ledger::pointer lpLedger; + + if (!!uLedger) + { + // Ledger directly specified. + lpLedger = noNetwork.getLedgerByHash(uLedger); + + if (!lpLedger) + { + jvResult["error"] = "ledgerNotFound"; + return; + } + + uLedgerIndex = lpLedger->getLedgerSeq(); // Set the current index, override if needed. + } + else if (!!uLedgerIndex) + { + lpLedger = noNetwork.getLedgerBySeq(uLedgerIndex); + + if (!lpLedger) + { + jvResult["error"] = "ledgerNotFound"; // ledger_index from future? + return; + } + } + else + { + // Default to current ledger. + lpLedger = noNetwork.getCurrentLedger(); + uLedgerIndex = lpLedger->getLedgerSeq(); // Set the current index. + } + + if (!!uLedger) + jvResult["ledger"] = uLedger.ToString(); + + jvResult["ledger_index"] = uLedgerIndex; + + uint256 uNodeIndex; + bool bNodeBinary = false; + + if (jvRequest.isMember("index")) + { + // XXX Needs to provide proof. + uNodeIndex.SetHex(jvRequest["index"].asString()); + bNodeBinary = true; + } + else if (jvRequest.isMember("account_root")) + { + NewcoinAddress naAccount; + + if (!naAccount.setAccountID(jvRequest["account_root"].asString())) + { + jvResult["error"] = "malformedAddress"; + } + else + { + uNodeIndex = Ledger::getAccountRootIndex(naAccount.getAccountID()); + } + } + else if (jvRequest.isMember("directory")) + { + + if (!jvRequest.isObject()) + { + uNodeIndex.SetHex(jvRequest["directory"].asString()); + } + else if (jvRequest["directory"].isMember("sub_index") + && !jvRequest["directory"]["sub_index"].isIntegral()) + { + jvResult["error"] = "malformedRequest"; + } + else + { + uint64 uSubIndex = jvRequest["directory"].isMember("sub_index") + ? jvRequest["directory"]["sub_index"].asUInt() + : 0; + + if (jvRequest["directory"].isMember("dir_root")) + { + uint256 uDirRoot; + + uDirRoot.SetHex(jvRequest["dir_root"].asString()); + + uNodeIndex = Ledger::getDirNodeIndex(uDirRoot, uSubIndex); + } + else if (jvRequest["directory"].isMember("owner")) + { + NewcoinAddress naOwnerID; + + if (!naOwnerID.setAccountID(jvRequest["directory"]["owner"].asString())) + { + jvResult["error"] = "malformedAddress"; + } + else + { + uint256 uDirRoot = Ledger::getOwnerDirIndex(naOwnerID.getAccountID()); + + uNodeIndex = Ledger::getDirNodeIndex(uDirRoot, uSubIndex); + } + } + else + { + jvResult["error"] = "malformedRequest"; + } + } + } + else if (jvRequest.isMember("generator")) + { + NewcoinAddress naGeneratorID; + + if (!jvRequest.isObject()) + { + uNodeIndex.SetHex(jvRequest["generator"].asString()); + } + else if (!jvRequest["generator"].isMember("regular_seed")) + { + jvResult["error"] = "malformedRequest"; + } + else if (!naGeneratorID.setSeedGeneric(jvRequest["generator"]["regular_seed"].asString())) + { + jvResult["error"] = "malformedAddress"; + } + else + { + NewcoinAddress na0Public; // To find the generator's index. + NewcoinAddress naGenerator = NewcoinAddress::createGeneratorPublic(naGeneratorID); + + na0Public.setAccountPublic(naGenerator, 0); + + uNodeIndex = Ledger::getGeneratorIndex(na0Public.getAccountID()); + } + } + else if (jvRequest.isMember("offer")) + { + NewcoinAddress naAccountID; + + if (!jvRequest.isObject()) + { + uNodeIndex.SetHex(jvRequest["offer"].asString()); + } + else if (!jvRequest["offer"].isMember("account") + || !jvRequest["offer"].isMember("seq") + || !jvRequest["offer"]["seq"].isIntegral()) + { + jvResult["error"] = "malformedRequest"; + } + else if (!naAccountID.setAccountID(jvRequest["offer"]["account"].asString())) + { + jvResult["error"] = "malformedAddress"; + } + else + { + uint32 uSequence = jvRequest["offer"]["seq"].asUInt(); + + uNodeIndex = Ledger::getOfferIndex(naAccountID.getAccountID(), uSequence); + } + } + else if (jvRequest.isMember("ripple_state")) + { + NewcoinAddress naA; + NewcoinAddress naB; + uint160 uCurrency; + + if (!jvRequest.isMember("accounts") + || !jvRequest.isMember("currency") + || !jvRequest["accounts"].isArray() + || 2 != jvRequest["accounts"].size()) { + jvResult["error"] = "malformedRequest"; + } + else if (!naA.setAccountID(jvRequest["accounts"][0u].asString()) + || !naB.setAccountID(jvRequest["accounts"][1u].asString())) { + jvResult["error"] = "malformedAddress"; + } + else if (!STAmount::currencyFromString(uCurrency, jvRequest["currency"].asString())) { + jvResult["error"] = "malformedCurrency"; + } + else + { + uNodeIndex = Ledger::getRippleStateIndex(naA, naB, uCurrency); + } + } + else + { + jvResult["error"] = "unknownOption"; + } + + if (!!uNodeIndex) + { + SLE::pointer sleNode = noNetwork.getSLE(lpLedger, uNodeIndex); + + if (!sleNode) + { + // Not found. + // XXX Should also provide proof. + jvResult["error"] = "entryNotFound"; + } + else if (bNodeBinary) + { + // XXX Should also provide proof. + Serializer s; + + sleNode->add(s); + + jvResult["node_binary"] = strHex(s.peekData()); + jvResult["index"] = uNodeIndex.ToString(); + } + else + { + jvResult["node"] = sleNode->getJson(0); + jvResult["index"] = uNodeIndex.ToString(); + } + } +} + void WSConnection::doTransactionSubcribe(Json::Value& jvResult, const Json::Value& jvRequest) { if (!theApp->getOPs().subTransaction(this)) diff --git a/src/Wallet.cpp b/src/Wallet.cpp index 5d1ef4e8a7..f5670eda73 100644 --- a/src/Wallet.cpp +++ b/src/Wallet.cpp @@ -16,8 +16,8 @@ #include "Application.h" #include "utils.h" -Wallet::Wallet() : mLedger(0) { -} +Wallet::Wallet() : mDh512(NULL), mDh1024(NULL), mLedger(0) +{ ; } void Wallet::start() { @@ -136,8 +136,6 @@ bool Wallet::dataFetch(const std::string& strKey, std::string& strValue) if (db->executeSQL(str(boost::format("SELECT Value FROM RPCData WHERE Key=%s;") % db->escape(strKey))) && db->startIterRows()) { - std::string strPublicKey, strPrivateKey; - std::vector vucData = db->getBinary("Value"); strValue.assign(vucData.begin(), vucData.end()); diff --git a/src/bignum.h b/src/bignum.h index 78bce87609..a9a08eda2d 100644 --- a/src/bignum.h +++ b/src/bignum.h @@ -27,7 +27,7 @@ private: protected: BN_CTX* pctx; - BN_CTX* operator=(BN_CTX* pnew) { return pctx = pnew; } + CAutoBN_CTX& operator=(BN_CTX* pnew) { pctx = pnew; return *this; } public: CAutoBN_CTX() diff --git a/src/main.cpp b/src/main.cpp index 49d23edf31..4a69f9f2da 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -51,6 +51,7 @@ void printHelp(const po::options_description& desc) cout << " data_fetch " << endl; cout << " data_store " << endl; cout << " ledger [|current|lastclosed] [full]" << endl; + cout << " logrotate " << endl; cout << " nickname_info " << endl; cout << " nickname_set [] []" << endl; cout << " offer_create [passive]" << endl; @@ -58,6 +59,7 @@ void printHelp(const po::options_description& desc) cout << " password_fund []" << endl; cout << " password_set []" << endl; cout << " peers" << endl; + cout << " ripple ..." << endl; cout << " ripple_lines_get || []" << endl; cout << " ripple_line_set [] []" << endl; cout << " send [] [] []" << endl; @@ -75,7 +77,7 @@ void printHelp(const po::options_description& desc) cout << " wallet_accounts " << endl; cout << " wallet_claim [] []" << endl; cout << " wallet_seed [||]" << endl; - cout << " wallet_propose" << endl; + cout << " wallet_propose []" << endl; } int main(int argc, char* argv[]) @@ -92,9 +94,10 @@ int main(int argc, char* argv[]) ("help,h", "Display this message.") ("conf", po::value(), "Specify the configuration file.") ("rpc", "Perform rpc command (default).") + ("standalone,a", "Run with no peers.") ("test,t", "Perform unit tests.") ("parameters", po::value< vector >(), "Specify comma separated parameters.") - ("verbose,v", "Increase log level") + ("verbose,v", "Increase log level.") ; // Interpret positional arguments as --parameters. @@ -144,6 +147,11 @@ int main(int argc, char* argv[]) if (!iResult) { theConfig.setup(vm.count("conf") ? vm["conf"].as() : ""); + + if (vm.count("standalone")) + { + theConfig.RUN_STANDALONE = true; + } } if (iResult) diff --git a/src/newcoin.proto b/src/newcoin.proto index 79f517468f..cd33de9f0f 100644 --- a/src/newcoin.proto +++ b/src/newcoin.proto @@ -69,9 +69,6 @@ message TMTransaction { required bytes rawTransaction = 1; required TransactionStatus status = 2; optional uint64 receiveTimestamp = 3; - optional uint32 ledgerIndexPossible = 4; // the node may not know - optional uint32 ledgerIndexFinal = 5; - optional bytes conflictingTransaction = 6; } @@ -107,8 +104,9 @@ message TMProposeSet { required bytes nodePubKey = 3; required uint32 closeTime = 4; required bytes signature = 5; // signature of above fields - repeated bytes addedTransactions = 6; // not required if number is large - repeated bytes removedTransactions = 7; // not required if number is large + optional bytes previousledger = 6; + repeated bytes addedTransactions = 10; // not required if number is large + repeated bytes removedTransactions = 11; // not required if number is large } enum TxSetStatus { diff --git a/src/uint256.h b/src/uint256.h index a5cb8c456a..3e2ab10e62 100644 --- a/src/uint256.h +++ b/src/uint256.h @@ -278,7 +278,7 @@ public: std::string ToString() const { - return (GetHex()); + return GetHex(); } unsigned char* begin() @@ -386,96 +386,6 @@ public: }; -////////////////////////////////////////////////////////////////////////////// -// -// uint160 -// - -class uint160 : public base_uint160 -{ -public: - typedef base_uint160 basetype; - - uint160() - { - zero(); - } - - uint160(const basetype& b) - { - *this = b; - } - - uint160& operator=(const basetype& b) - { - for (int i = 0; i < WIDTH; i++) - pn[i] = b.pn[i]; - - return *this; - } - - uint160(uint64 b) - { - *this = b; - } - - uint160& operator=(uint64 uHost) - { - zero(); - - // Put in least significant bits. - ((uint64_t *) end())[-1] = htobe64(uHost); - - return *this; - } - - explicit uint160(const std::string& str) - { - SetHex(str); - } - - explicit uint160(const std::vector& vch) - { - if (vch.size() == sizeof(pn)) - memcpy(pn, &vch[0], sizeof(pn)); - else - zero(); - } - - base_uint256 to256() const; -}; - -inline bool operator==(const uint160& a, uint64 b) { return (base_uint160)a == b; } -inline bool operator!=(const uint160& a, uint64 b) { return (base_uint160)a != b; } - -inline const uint160 operator^(const base_uint160& a, const base_uint160& b) { return uint160(a) ^= b; } -inline const uint160 operator&(const base_uint160& a, const base_uint160& b) { return uint160(a) &= b; } -inline const uint160 operator|(const base_uint160& a, const base_uint160& b) { return uint160(a) |= b; } - -inline bool operator==(const base_uint160& a, const uint160& b) { return (base_uint160)a == (base_uint160)b; } -inline bool operator!=(const base_uint160& a, const uint160& b) { return (base_uint160)a != (base_uint160)b; } -inline const uint160 operator^(const base_uint160& a, const uint160& b) { return (base_uint160)a ^ (base_uint160)b; } -inline const uint160 operator&(const base_uint160& a, const uint160& b) { return (base_uint160)a & (base_uint160)b; } -inline const uint160 operator|(const base_uint160& a, const uint160& b) { return (base_uint160)a | (base_uint160)b; } - -inline bool operator==(const uint160& a, const base_uint160& b) { return (base_uint160)a == (base_uint160)b; } -inline bool operator!=(const uint160& a, const base_uint160& b) { return (base_uint160)a != (base_uint160)b; } -inline const uint160 operator^(const uint160& a, const base_uint160& b) { return (base_uint160)a ^ (base_uint160)b; } -inline const uint160 operator&(const uint160& a, const base_uint160& b) { return (base_uint160)a & (base_uint160)b; } -inline const uint160 operator|(const uint160& a, const base_uint160& b) { return (base_uint160)a | (base_uint160)b; } -inline bool operator==(const uint160& a, const uint160& b) { return (base_uint160)a == (base_uint160)b; } -inline bool operator!=(const uint160& a, const uint160& b) { return (base_uint160)a != (base_uint160)b; } -inline const uint160 operator^(const uint160& a, const uint160& b) { return (base_uint160)a ^ (base_uint160)b; } -inline const uint160 operator&(const uint160& a, const uint160& b) { return (base_uint160)a & (base_uint160)b; } -inline const uint160 operator|(const uint160& a, const uint160& b) { return (base_uint160)a | (base_uint160)b; } - -extern std::size_t hash_value(const uint160&); - -inline const std::string strHex(const uint160& ui) -{ - return strHex(ui.begin(), ui.size()); -} - ////////////////////////////////////////////////////////////////////////////// // // uint256 @@ -559,6 +469,11 @@ inline const uint256 operator&(const uint256& a, const uint256& b) { return (b inline const uint256 operator|(const uint256& a, const uint256& b) { return (base_uint256)a | (base_uint256)b; } extern std::size_t hash_value(const uint256&); +template inline std::ostream& operator<<(std::ostream& out, const base_uint& u) +{ + return out << u.GetHex(); +} + inline int Testuint256AdHoc(std::vector vArg) { uint256 g(0); @@ -689,5 +604,102 @@ inline int Testuint256AdHoc(std::vector vArg) return (0); } +////////////////////////////////////////////////////////////////////////////// +// +// uint160 +// + +class uint160 : public base_uint160 +{ +public: + typedef base_uint160 basetype; + + uint160() + { + zero(); + } + + uint160(const basetype& b) + { + *this = b; + } + + uint160& operator=(const basetype& b) + { + for (int i = 0; i < WIDTH; i++) + pn[i] = b.pn[i]; + + return *this; + } + + uint160(uint64 b) + { + *this = b; + } + + uint160& operator=(uint64 uHost) + { + zero(); + + // Put in least significant bits. + ((uint64_t *) end())[-1] = htobe64(uHost); + + return *this; + } + + explicit uint160(const std::string& str) + { + SetHex(str); + } + + explicit uint160(const std::vector& vch) + { + if (vch.size() == sizeof(pn)) + memcpy(pn, &vch[0], sizeof(pn)); + else + zero(); + } + + base_uint256 to256() const + { + uint256 m; + memcpy(m.begin(), begin(), size()); + return m; + } + +}; + +inline bool operator==(const uint160& a, uint64 b) { return (base_uint160)a == b; } +inline bool operator!=(const uint160& a, uint64 b) { return (base_uint160)a != b; } + +inline const uint160 operator^(const base_uint160& a, const base_uint160& b) { return uint160(a) ^= b; } +inline const uint160 operator&(const base_uint160& a, const base_uint160& b) { return uint160(a) &= b; } +inline const uint160 operator|(const base_uint160& a, const base_uint160& b) { return uint160(a) |= b; } + +inline bool operator==(const base_uint160& a, const uint160& b) { return (base_uint160)a == (base_uint160)b; } +inline bool operator!=(const base_uint160& a, const uint160& b) { return (base_uint160)a != (base_uint160)b; } +inline const uint160 operator^(const base_uint160& a, const uint160& b) { return (base_uint160)a ^ (base_uint160)b; } +inline const uint160 operator&(const base_uint160& a, const uint160& b) { return (base_uint160)a & (base_uint160)b; } +inline const uint160 operator|(const base_uint160& a, const uint160& b) { return (base_uint160)a | (base_uint160)b; } + +inline bool operator==(const uint160& a, const base_uint160& b) { return (base_uint160)a == (base_uint160)b; } +inline bool operator!=(const uint160& a, const base_uint160& b) { return (base_uint160)a != (base_uint160)b; } +inline const uint160 operator^(const uint160& a, const base_uint160& b) { return (base_uint160)a ^ (base_uint160)b; } +inline const uint160 operator&(const uint160& a, const base_uint160& b) { return (base_uint160)a & (base_uint160)b; } +inline const uint160 operator|(const uint160& a, const base_uint160& b) { return (base_uint160)a | (base_uint160)b; } +inline bool operator==(const uint160& a, const uint160& b) { return (base_uint160)a == (base_uint160)b; } +inline bool operator!=(const uint160& a, const uint160& b) { return (base_uint160)a != (base_uint160)b; } +inline const uint160 operator^(const uint160& a, const uint160& b) { return (base_uint160)a ^ (base_uint160)b; } +inline const uint160 operator&(const uint160& a, const uint160& b) { return (base_uint160)a & (base_uint160)b; } +inline const uint160 operator|(const uint160& a, const uint160& b) { return (base_uint160)a | (base_uint160)b; } + +extern std::size_t hash_value(const uint160&); + +inline const std::string strHex(const uint160& ui) +{ + return strHex(ui.begin(), ui.size()); +} + + #endif // vim:ts=4 diff --git a/src/utils.cpp b/src/utils.cpp index f61a0df304..944a281139 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -156,6 +156,25 @@ bool parseIpPort(const std::string& strSource, std::string& strIP, int& iPort) return bValid; } +// +// Quality parsing +// - integers as is. +// - floats multiplied by a billion +bool parseQuality(const std::string& strSource, uint32& uQuality) +{ + uQuality = lexical_cast_s(strSource); + + if (!uQuality) + { + float fQuality = lexical_cast_s(strSource); + + if (fQuality) + uQuality = (uint32)(QUALITY_ONE*fQuality); + } + + return !!uQuality; +} + /* void intIPtoStr(int ip,std::string& retStr) { diff --git a/src/utils.h b/src/utils.h index 4a09e26307..eab185b34a 100644 --- a/src/utils.h +++ b/src/utils.h @@ -8,19 +8,15 @@ #include "types.h" +#define QUALITY_ONE 1000000000 // 10e9 + #define nothing() do {} while (0) #define fallthru() do {} while (0) #define NUMBER(x) (sizeof(x)/sizeof((x)[0])) #define ADDRESS(p) strHex(uint64( ((char*) p) - ((char*) 0))) #define ADDRESS_SHARED(p) strHex(uint64( ((char*) (p).get()) - ((char*) 0))) -#ifndef MAX -#define MAX(x,y) ((x) < (y) ? (y) : (x)) -#endif - -#ifndef MIN -#define MIN(x,y) ((x) > (y) ? (y) : (x)) -#endif +#define isSetBit(x,y) (!!((x) & (y))) #ifdef WIN32 extern uint64_t htobe64(uint64_t value); @@ -153,6 +149,7 @@ std::vector strCopy(const std::string& strSrc); std::string strCopy(const std::vector& vucSrc); bool parseIpPort(const std::string& strSource, std::string& strIP, int& iPort); +bool parseQuality(const std::string& strSource, uint32& uQuality); DH* DH_der_load(const std::string& strDer); std::string DH_der_gen(int iKeyLength); @@ -174,7 +171,7 @@ template T lexical_cast_s(const std::string& string) } } -template std::string lexical_cast_i(T t) +template std::string lexical_cast_i(const T& t) { // lexicaly cast the selected type to a string. Does not throw try { @@ -186,6 +183,44 @@ template std::string lexical_cast_i(T t) } } +template T lexical_cast_st(const std::string& string) +{ // lexically cast a string to the selected type. Does throw + return boost::lexical_cast(string); +} + +template std::string lexical_cast_it(const T& t) +{ // lexicaly cast the selected type to a string. Does not throw + return boost::lexical_cast(t); +} + +template T range_check(const T& value, const T& minimum, const T& maximum) +{ + if ((value < minimum) || (value > maximum)) + throw std::runtime_error("Value out of range"); + return value; +} + +template T range_check_min(const T& value, const T& minimum) +{ + if (value < minimum) + throw std::runtime_error("Value out of range"); + return value; +} + +template T range_check_max(const T& value, const T& maximum) +{ + if (value > maximum) + throw std::runtime_error("Value out of range"); + return value; +} + +template T range_check_cast(const U& value, const T& minimum, const T& maximum) +{ + if ((value < minimum) || (value > maximum)) + throw std::runtime_error("Value out of range"); + return static_cast(value); +} + #endif // vim:ts=4 diff --git a/test/buster.js b/test/buster.js new file mode 100644 index 0000000000..5dd49ec7cc --- /dev/null +++ b/test/buster.js @@ -0,0 +1,9 @@ +var config = module.exports; + +config["Newcoin tests"] = { + rootPath: "../", + environment: "node", + tests: [ + "test/*-test.js" + ] +} diff --git a/test/config.js b/test/config.js new file mode 100644 index 0000000000..0610373db9 --- /dev/null +++ b/test/config.js @@ -0,0 +1,25 @@ +// +// Configuration for unit tests +// + +var path = require("path"); + +// Where to find the binary. +exports.newcoind = path.join(process.cwd(), "newcoind"); + +// Configuration for servers. +exports.servers = { + // A local test server. + alpha : { + 'trusted' : true, + // "peer_ip" : "0.0.0.0", + // "peer_port" : 51235, + 'rpc_ip' : "0.0.0.0", + 'rpc_port' : 5005, + 'websocket_ip' : "127.0.0.1", + 'websocket_port' : 6005, + 'validation_seed' : "shhDFVsmS2GSu5vUyZSPXYfj1r79h", + 'validators' : "n9L8LZZCwsdXzKUN9zoVxs4YznYXZ9hEhsQZY7aVpxtFaSceiyDZ beta" + } +}; +// vim:ts=4 diff --git a/test/server.js b/test/server.js new file mode 100644 index 0000000000..9e20569a58 --- /dev/null +++ b/test/server.js @@ -0,0 +1,154 @@ +// Manage test servers +// +// YYY Would be nice to be able to hide server output. +// + +// Provide servers +// +// Servers are created in tmp/server/$server +// + +var config = require("./config.js"); +var utils = require("../js/utils.js"); + +var fs = require("fs"); +var path = require("path"); +var util = require("util"); +var child = require("child_process"); + +var servers = {}; + +// Create a server object +var Server = function(name) { + this.name = name; +}; + +// Return a server's newcoind.cfg as string. +Server.method('configContent', function() { + var cfg = config.servers[this.name]; + + return Object.keys(cfg).map(function(o) { + return util.format("[%s]\n%s\n", o, cfg[o]); + }).join(""); +}); + +Server.method('serverPath', function() { + return "tmp/server/" + this.name; +}); + +Server.method('configPath', function() { + return path.join(this.serverPath(), "newcoind.cfg"); +}); + +// Write a server's newcoind.cfg. +Server.method('writeConfig', function(done) { + fs.writeFile(this.configPath(), this.configContent(), 'utf8', done); +}); + +// Spawn the server. +Server.method('serverSpawnSync', function() { + // Spawn in standalone mode for now. + this.child = child.spawn( + config.newcoind, + [ + "-a", + "--conf=newcoind.cfg" + ], + { + cwd: this.serverPath(), + env: process.env, + stdio: 'inherit' + }); + + console.log("server: start %s: %s -a --conf=%s", this.child.pid, config.newcoind, this.configPath()); + + // By default, just log exits. + this.child.on('exit', function(code, signal) { + // If could not exec: code=127, signal=null + // If regular exit: code=0, signal=null + console.log("server: spawn: server exited code=%s: signal=%s", code, signal); + }); + +}); + +// Prepare server's working directory. +Server.method('makeBase', function(done) { + var path = this.serverPath(); + var self = this; + + // Reset the server directory, build it if needed. + utils.resetPath(path, '0777', function(e) { + if (e) { + throw e; + } + else { + self.writeConfig(done); + } + }); +}); + +// Create a standalone server. +// Prepare the working directory and spawn the server. +Server.method('start', function(done) { + var self = this; + + this.makeBase(function(e) { + if (e) { + throw e; + } + else { + self.serverSpawnSync(); + done(); + } + }); +}); + +// Stop a standalone server. +Server.method('stop', function(done) { + if (this.child) { + // Update the on exit to invoke done. + this.child.on('exit', function(code, signal) { + console.log("server: stop: server exited"); + done(); + }); + this.child.kill(); + } + else + { + console.log("server: stop: no such server"); + done(); + } +}); + +// Start the named server. +exports.start = function(name, done) { + if (servers[name]) + { + console.log("server: start: server already started."); + } + else + { + var server = new Server(name); + + servers[name] = server; + + console.log("server: start: %s", JSON.stringify(server)); + + server.start(done); + } +}; + +// Delete the named server. +exports.stop = function(name, done) { + console.log("server: stop: %s of %s", name, Object.keys(servers).toString()); + + var server = servers[name]; + if (server) { + server.stop(done); + delete servers[name]; + } +}; + +exports.Server = Server; + +// vim:ts=4 diff --git a/test/standalone-test.js b/test/standalone-test.js new file mode 100644 index 0000000000..6a18592a42 --- /dev/null +++ b/test/standalone-test.js @@ -0,0 +1,188 @@ +var fs = require("fs"); +var buster = require("buster"); + +var server = require("./server.js"); +var remote = require("../js/remote.js"); +var config = require("./config.js"); + +// How long to wait for server to start. +var serverDelay = 1500; + +buster.testRunner.timeout = 5000; + +buster.testCase("Standalone server startup", { + "server start and stop": function(done) { + server.start("alpha", + function(e) { + buster.refute(e); + server.stop("alpha", function(e) { + buster.refute(e); + done(); + }); + }); + } +}); + +buster.testCase("WebSocket connection", { + 'setUp' : + function(done) { + server.start("alpha", + function(e) { + buster.refute(e); + done(); + } + ); + }, + + 'tearDown' : + function(done) { + server.stop("alpha", function(e) { + buster.refute(e); + done(); + }); + }, + + "websocket connect and disconnect" : + function(done) { + var alpha = remote.remoteConfig(config, "alpha"); + + alpha.connect(function(stat) { + buster.assert(1 == stat); // OPEN + + alpha.disconnect(function(stat) { + buster.assert(3 == stat); // CLOSED + done(); + }); + }, serverDelay); + }, +}); + +buster.testCase("Websocket commands", { + 'setUp' : + function(done) { + server.start("alpha", + function(e) { + buster.refute(e); + + alpha = remote.remoteConfig(config, "alpha"); + + alpha.connect(function(stat) { + buster.assert(1 == stat); // OPEN + + done(); + }, serverDelay); + }); + }, + + 'tearDown' : + function(done) { + alpha.disconnect(function(stat) { + buster.assert(3 == stat); // CLOSED + + server.stop("alpha", function(e) { + buster.refute(e); + + done(); + }); + }); + }, + + 'ledger_current' : + function(done) { + alpha.ledger_current(function (r) { + console.log(r); + + buster.assert.equals(r.ledger_index, 3); + done(); + }); + }, + + '// ledger_closed' : + function(done) { + alpha.ledger_closed(function (r) { + console.log("result: %s", JSON.stringify(r)); + + buster.assert.equals(r.ledger_index, 2); + done(); + }); + }, + + 'account_root success' : + function(done) { + alpha.ledger_closed(function (r) { + // console.log("result: %s", JSON.stringify(r)); + + buster.refute('error' in r); + + alpha.ledger_entry({ + 'ledger_index' : r.ledger_index, + 'account_root' : 'iHb9CJAWyB4ij91VRWn96DkukG4bwdtyTh' + } , function (r) { + // console.log("account_root: %s", JSON.stringify(r)); + + buster.assert('node' in r); + done(); + }); + }); + }, + + 'account_root malformedAddress' : + function(done) { + alpha.ledger_closed(function (r) { + // console.log("result: %s", JSON.stringify(r)); + + buster.refute('error' in r); + + alpha.ledger_entry({ + 'ledger_index' : r.ledger_index, + 'account_root' : 'foobar' + } , function (r) { + // console.log("account_root: %s", JSON.stringify(r)); + + buster.assert.equals(r.error, 'malformedAddress'); + done(); + }); + }); + }, + + 'account_root entryNotFound' : + function(done) { + alpha.ledger_closed(function (r) { + // console.log("result: %s", JSON.stringify(r)); + + buster.refute('error' in r); + + alpha.ledger_entry({ + 'ledger_index' : r.ledger_index, + 'account_root' : 'iG1QQv2nh2gi7RCZ1P8YYcBUKCCN633jCn' + } , function (r) { + // console.log("account_root: %s", JSON.stringify(r)); + + buster.assert.equals(r.error, 'entryNotFound'); + done(); + }); + }); + }, + + 'ledger_entry index' : + function(done) { + alpha.ledger_closed(function (r) { + // console.log("result: %s", JSON.stringify(r)); + + buster.refute('error' in r); + + alpha.ledger_entry({ + 'ledger_index' : r.ledger_index, + 'index' : "2B6AC232AA4C4BE41BF49D2459FA4A0347E1B543A4C92FCEE0821C0201E2E9A8", + } , function (r) { + console.log("node: %s", JSON.stringify(r)); + + buster.assert('node_binary' in r); + done(); + }); + }); + }, + +}); + +// vim:ts=4 diff --git a/tests/client1/config.xml b/tests/client1/config.xml deleted file mode 100644 index dfe441b032..0000000000 --- a/tests/client1/config.xml +++ /dev/null @@ -1,5 +0,0 @@ - - 4000 - 5001 - 30 - diff --git a/tests/client1/nodes.xml b/tests/client1/nodes.xml deleted file mode 100644 index 9754b37c4f..0000000000 --- a/tests/client1/nodes.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/tests/client1/unl.xml b/tests/client1/unl.xml deleted file mode 100644 index 553486b098..0000000000 --- a/tests/client1/unl.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/tests/client1/wallet.xml b/tests/client1/wallet.xml deleted file mode 100644 index 1c364ebb03..0000000000 --- a/tests/client1/wallet.xml +++ /dev/null @@ -1,7 +0,0 @@ - - -
- - -
-
\ No newline at end of file diff --git a/tests/client2/config.xml b/tests/client2/config.xml deleted file mode 100644 index d01e35c99f..0000000000 --- a/tests/client2/config.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - 5000 - 5002 - 30 - diff --git a/tests/client2/nodes.xml b/tests/client2/nodes.xml deleted file mode 100644 index 9754b37c4f..0000000000 --- a/tests/client2/nodes.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/tests/client2/unl.xml b/tests/client2/unl.xml deleted file mode 100644 index 553486b098..0000000000 --- a/tests/client2/unl.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/tests/client2/wallet.xml b/tests/client2/wallet.xml deleted file mode 100644 index 1c364ebb03..0000000000 --- a/tests/client2/wallet.xml +++ /dev/null @@ -1,7 +0,0 @@ - - -
- - -
-
\ No newline at end of file diff --git a/tests/setup.bat b/tests/setup.bat deleted file mode 100644 index 61304847bb..0000000000 --- a/tests/setup.bat +++ /dev/null @@ -1,6 +0,0 @@ -REM copy C:\code\newcoin\Release\newcoin.exe C:\code\newcoin\tests\client1 -REM copy C:\code\newcoin\Release\newcoin.exe C:\code\newcoin\tests\client2 - -copy d:\code\newcoin\Debug\newcoin.exe d:\code\newcoin\tests\client1 -copy d:\code\newcoin\Debug\newcoin.exe d:\code\newcoin\tests\client2 - diff --git a/websocketpp b/websocketpp index f78b9df4ad..dd9899c34b 160000 --- a/websocketpp +++ b/websocketpp @@ -1 +1 @@ -Subproject commit f78b9df4adbc354f5fdf8c2c8b9e76549f977cb8 +Subproject commit dd9899c34bb1f86caf486735b136a5460b4760db