diff --git a/.gitignore b/.gitignore index 9286dcfec..69bb5bff7 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,9 @@ # Ignore python compiled files. *.pyc +# Ignore Macintosh Desktop Services Store files. +.DS_Store + # Ignore backup/temps *~ @@ -25,8 +28,13 @@ tmp # Ignore database directory. db/*.db -db/*.db-journal +db/*.db-* + +# Ignore obj files +Debug/*.* +Release/*.* # Ignore customized configs rippled.cfg +validators.txt test/config.js diff --git a/README b/README deleted file mode 100644 index 491939d73..000000000 --- a/README +++ /dev/null @@ -1,10 +0,0 @@ -Dependancies: -- boost 1.47 -- Google protocol buffers 2.4.1 -- openssl - -Sub modules: -- websocketpp: https://github.com/zaphoyd/websocketpp -- sjcl: https://github.com/bitwiseshiftleft/sjcl -- cryptojs: https://github.com/gwjjeff/cryptojs - aka http://code.google.com/p/crypto-js/ diff --git a/README.md b/README.md new file mode 100644 index 000000000..2d33f969b --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +Ripple - P2P Payment Network +============================ + +Some portions of this source code are currently closed source. + +This is the repository for Ripple's: +* rippled - Reference P2P network server +* ripple.js - Reference JavaScript client libraries for node.js and browsers. + +Build instructions: +* https://ripple.com/wiki/Rippled_build_instructions +* https://ripple.com/wiki/Ripple_JavaScript_library + +Setup instructions: +* https://ripple.com/wiki/Rippled_setup_instructions + +For more information: +* https://ripple.com +* https://ripple.com/wiki diff --git a/SConstruct b/SConstruct index 458f48c94..53745565f 100644 --- a/SConstruct +++ b/SConstruct @@ -75,10 +75,21 @@ else: ] ) +# Apparently, only linux uses -ldl +if not FreeBSD: + env.Append( + LIBS = [ + 'dl', # dynamic linking for linux + ] + ) + +# Apparently, pkg-config --libs protobuf on bsd fails to provide this necessary include dir. +if FreeBSD: + env.Append(LINKFLAGS = ['-I/usr/local/include']) + env.Append( LIBS = [ 'protobuf', - 'dl', # dynamic linking 'z' ] ) @@ -87,12 +98,12 @@ DEBUGFLAGS = ['-g', '-DDEBUG'] BOOSTFLAGS = ['-DBOOST_TEST_DYN_LINK', '-DBOOST_FILESYSTEM_NO_DEPRECATED'] env.Append(LINKFLAGS = ['-rdynamic', '-pthread']) -env.Append(CCFLAGS = ['-pthread', '-Wall', '-Wno-sign-compare', '-Wno-char-subscripts', '-DSQLITE_THREADSAFE']) +env.Append(CCFLAGS = ['-pthread', '-Wall', '-Wno-sign-compare', '-Wno-char-subscripts', '-DSQLITE_THREADSAFE=1']) env.Append(CXXFLAGS = ['-O0', '-pthread', '-Wno-invalid-offsetof', '-Wformat']+BOOSTFLAGS+DEBUGFLAGS) if OSX: - env.Append(LINKFLAGS = ['-L/usr/local/Cellar/openssl/1.0.1c/lib']) - env.Append(CXXFLAGS = ['-I/usr/local/Cellar/openssl/1.0.1c/include']) + env.Append(LINKFLAGS = ['-L/usr/local/opt/openssl/lib']) + env.Append(CXXFLAGS = ['-I/usr/local/opt/openssl/include']) DB_SRCS = glob.glob('src/cpp/database/*.c') + glob.glob('src/cpp/database/*.cpp') JSON_SRCS = glob.glob('src/cpp/json/*.cpp') diff --git a/bin/email_hash.js b/bin/email_hash.js new file mode 100755 index 000000000..ab4f97c47 --- /dev/null +++ b/bin/email_hash.js @@ -0,0 +1,18 @@ +#!/usr/bin/node +// +// Returns a Gravatar style hash as per: http://en.gravatar.com/site/implement/hash/ +// + +if (3 != process.argv.length) { + process.stderr.write("Usage: " + process.argv[1] + " email_address\n\nReturns gravatar style hash.\n"); + process.exit(1); + +} else { + var md5 = require('crypto').createHash('md5'); + + md5.update(process.argv[2].trim().toLowerCase()); + + process.stdout.write(md5.digest('hex') + "\n"); +} + +// vim:sw=2:sts=2:ts=8:et diff --git a/bin/flash_policy.js b/bin/flash_policy.js new file mode 100755 index 000000000..e1361d46d --- /dev/null +++ b/bin/flash_policy.js @@ -0,0 +1,31 @@ +#!/usr/bin/node +// +// This program allows IE 9 ripple-clients to make websocket connections to +// rippled using flash. As IE 9 does not have websocket support, this required +// if you wish to support IE 9 ripple-clients. +// +// http://www.lightsphere.com/dev/articles/flash_socket_policy.html +// +// For better security, be sure to set the Port below to the port of your +// [websocket_public_port]. +// + +var net = require("net"), + port = "*", + domains = ["*:"+port]; // Domain:Port + +net.createServer( + function(socket) { + socket.write("\n"); + socket.write("\n"); + socket.write("\n"); + domains.forEach( + function(domain) { + var parts = domain.split(':'); + socket.write("\t\n"); + } + ); + socket.write("\n"); + socket.end(); + } +).listen(843); diff --git a/bin/hexify.js b/bin/hexify.js new file mode 100755 index 000000000..1e2fb7000 --- /dev/null +++ b/bin/hexify.js @@ -0,0 +1,23 @@ +#!/usr/bin/node +// +// Returns hex of lowercasing a string. +// + +var stringToHex = function (s) { + return Array.prototype.map.call(s, function (c) { + var b = c.charCodeAt(0); + + return b < 16 ? "0" + b.toString(16) : b.toString(16); + }).join(""); +}; + +if (3 != process.argv.length) { + process.stderr.write("Usage: " + process.argv[1] + " string\n\nReturns hex of lowercasing string.\n"); + process.exit(1); + +} else { + + process.stdout.write(stringToHex(process.argv[2].toLowerCase()) + "\n"); +} + +// vim:sw=2:sts=2:ts=8:et diff --git a/deploy/newcoind.cfg b/deploy/newcoind.cfg deleted file mode 100644 index ee991ea67..000000000 --- a/deploy/newcoind.cfg +++ /dev/null @@ -1,149 +0,0 @@ -# -# Sample newcoind.cfg -# -# This file should be named newcoind.cfg. This file is UTF-8 with Dos, UNIX, -# 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 -# -# [debug_logfile] -# Specifies were a debug logfile is kept. By default, no debug log is kept -# -# Example: debug.log -# -# [validators_site]: -# Specifies where to find validators.txt for UNL boostrapping and RPC command unl_network. -# During alpha testing, this defaults to: redstem.com -# -# Example: newcoin.org -# -# [unl_default]: -# XXX This should be called: [validators_file] -# Specifies how to bootstrap the UNL list. The UNL list is based on a -# validators.txt file and is maintained in the databases. When newcoind -# starts up, if the databases are missing or are obsolete due to an upgrade -# of newcoind, newcoind will reconstruct the UNL list as specified here. -# -# If this entry is not present or empty, newcoind will look for a validators.txt in the -# config directory. If not found there, it will attempt to retrieve the file -# from the newcoin foundation's web site. -# -# This entry is also used by the RPC command unl_load. -# -# Specify the file by specifying its full path. -# -# Examples: -# C:/home/johndoe/newcoin/validators.txt -# /home/johndoe/newcoin/validators.txt -# -# [validators]: -# Only valid in "newcoind.cfg", "newcoin.txt", and the referered [validators_url]. -# List of nodes to accept as validators speficied by public key or domain. -# -# For domains, newcoind will probe for https web servers at the specied -# domain in the following order: newcoin.DOMAIN, www.DOMAIN, DOMAIN -# -# Examples: -# redstem.com -# n9KorY8QtTdRx7TVDpwnG9NvyxsDwHUKUEeDLY3AkiGncVaSXZi5 -# n9MqiExBcoG19UXwoLjBJnhsxEhAZMuWwJDRdkyDz1EkEkwzQTNt John Doe -# -# [ips]: -# Only valid in "newcoind.cfg", "newcoin.txt", and the referered [ips_url]. -# List of ips where the Newcoin protocol is avialable. -# One ipv4 or ipv6 address per line. -# A port may optionally be specified after adding a space to the address. -# By convention, if known, IPs are listed in from most to least trusted. -# -# Examples: -# 192.168.0.1 -# 192.168.0.1 3939 -# 2001:0db8:0100:f101:0210:a4ff:fee3:9566 -# -# [peer_ip]: -# IP address or domain to bind to allow external connections from peers. -# Defaults to not allow external connections from peers. -# -# Examples: 0.0.0.0 - Bind on all interfaces. -# -# [peer_port]: -# Port to bind to allow external connections from peers. -# -# [rpc_ip]: -# IP address or domain to bind to allow insecure RPC connections. -# Defaults to not allow RPC connections. -# -# [rpc_port]: -# Port to bind to if allowing insecure RPC connections. -# -# [rpc_allow_remote]: -# 0 or 1. 0 only allows RPC connections from 127.0.0.1. [default 0] -# -# [websocket_ip]: -# IP address or domain to bind to allow client connections. -# -# Examples: 0.0.0.0 - Bind on all interfaces. -# 127.0.0.1 - Bind on localhost interface. Only local programs may connect. -# -# [websocket_port]: -# Port to bind to allow client connections. -# -# [validation_seed]: -# To perform validation, this section should contain either a validation seed or key. -# The validation seed is used to generate the validation public/private key pair. -# To obtain a validation seed, use the validation_create command. -# -# Examples: RASH BUSH MILK LOOK BAD BRIM AVID GAFF BAIT ROT POD LOVE -# shfArahZT9Q9ckTf3s1psJ7C7qzVN -# - -[peer_ip] -0.0.0.0 - -[peer_port] -51235 - -[rpc_ip] -127.0.0.1 - -[rpc_port] -5005 - -[rpc_allow_remote] -1 - -[debug_logfile] -debug.log - -[unl_default] -validators.txt - -[ips] -23.21.167.100 51235 -23.23.201.55 51235 -107.21.116.214 51235 diff --git a/deploy/cointoss.nsi b/deploy/rippled.nsi similarity index 73% rename from deploy/cointoss.nsi rename to deploy/rippled.nsi index 1f3656ba7..f77f8bc68 100644 --- a/deploy/cointoss.nsi +++ b/deploy/rippled.nsi @@ -1,10 +1,10 @@ -Name "CoinToss" +Name "Rippled" ; The file to write -OutFile "toss install.exe" +OutFile "ripple install.exe" ; The default installation directory -InstallDir "$PROGRAMFILES\CoinToss" +InstallDir "$PROGRAMFILES\Rippled" ; Request application privileges for Windows Vista RequestExecutionLevel user @@ -25,12 +25,12 @@ Section "" ;No components page, name is not important SetOutPath $INSTDIR ; Put file there - File ..\Release\newcoin.exe + File ..\Release\rippled.exe File ..\*.dll - File "start CoinToss.bat" - File newcoind.cfg + ;File "start rippled.bat" + File rippled.cfg File validators.txt - File /r /x .git ..\..\nc-client\*.* + ;File /r /x .git ..\..\nc-client\*.* CreateDirectory $INSTDIR\db diff --git a/deploy/start CoinToss.bat b/deploy/start rippled.bat similarity index 100% rename from deploy/start CoinToss.bat rename to deploy/start rippled.bat diff --git a/grunt.js b/grunt.js new file mode 100644 index 000000000..152cef351 --- /dev/null +++ b/grunt.js @@ -0,0 +1,76 @@ +module.exports = function(grunt) { + grunt.loadNpmTasks('grunt-webpack'); + + grunt.initConfig({ + pkg: '', + meta: { + banner: '/*! <%= pkg.name %> - v<%= pkg.version %> - ' + + '<%= grunt.template.today("yyyy-mm-dd") %>\n' + + '<%= pkg.homepage ? "* " + pkg.homepage + "\n" : "" %>' + + '* Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>;' + + ' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %> */' + }, + concat: { + sjcl: { + src: [ + "src/js/sjcl/core/sjcl.js", + "src/js/sjcl/core/aes.js", + "src/js/sjcl/core/bitArray.js", + "src/js/sjcl/core/codecString.js", + "src/js/sjcl/core/codecHex.js", + "src/js/sjcl/core/codecBase64.js", + "src/js/sjcl/core/codecBytes.js", + "src/js/sjcl/core/sha256.js", + "src/js/sjcl/core/sha512.js", + "src/js/sjcl/core/sha1.js", + "src/js/sjcl/core/ccm.js", +// "src/js/sjcl/core/cbc.js", +// "src/js/sjcl/core/ocb2.js", + "src/js/sjcl/core/hmac.js", + "src/js/sjcl/core/pbkdf2.js", + "src/js/sjcl/core/random.js", + "src/js/sjcl/core/convenience.js", + "src/js/sjcl/core/bn.js", + "src/js/sjcl/core/ecc.js", + "src/js/sjcl/core/srp.js" + ], + dest: 'build/sjcl.js' + } + }, + webpack: { + lib: { + src: "src/js/index.js", + dest: "build/ripple-<%= pkg.version %>.js", + libary: "ripple", // misspelling fixed in later versions of webpack + library: "ripple" + }, + lib_debug: { + src: "src/js/index.js", + dest: "build/ripple-<%= pkg.version %>-debug.js", + libary: "ripple", // misspelling fixed in later versions of webpack + library: "ripple", + debug: true + }, + lib_min: { + src: "src/js/index.js", + dest: "build/ripple-<%= pkg.version %>-min.js", + libary: "ripple", // misspelling fixed in later versions of webpack + library: "ripple", + minimize: true + } + }, + watch: { + sjcl: { + files: [''], + tasks: 'concat:sjcl' + }, + lib: { + files: 'src/js/*.js', + tasks: 'webpack' + } + } + }); + + // Tasks + grunt.registerTask('default', 'concat:sjcl webpack'); +}; diff --git a/newcoin.vcxproj b/newcoin.vcxproj index b5839160e..72e758a47 100644 --- a/newcoin.vcxproj +++ b/newcoin.vcxproj @@ -41,9 +41,11 @@ true + rippled false + rippled @@ -76,14 +78,14 @@ true true BOOST_TEST_ALTERNATIVE_INIT_API;BOOST_TEST_NO_MAIN;_CRT_SECURE_NO_WARNINGS;_WIN32_WINNT=0x0501;WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - ..\OpenSSL\include;..\boost_1_47_0;..\protobuf-2.4.1\src + .\;..\OpenSSL\include;..\boost_1_52_0;..\protobuf\src Console true true true - ..\OpenSSL\lib\VC;..\boost_1_47_0\stage\lib;..\protobuf-2.4.1\vsprojects\Release + ..\OpenSSL\lib\VC;..\boost_1_52_0\stage\lib;..\protobuf\vsprojects\Release libprotobuf.lib;ssleay32MD.lib;libeay32MD.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) @@ -91,7 +93,6 @@ - @@ -145,7 +146,6 @@ - @@ -158,6 +158,7 @@ + @@ -177,6 +178,7 @@ + @@ -250,7 +252,6 @@ - @@ -284,6 +285,7 @@ + @@ -320,8 +322,10 @@ Document - /code/protobuf/protoc -I=..\newcoin --cpp_out=\code\newcoin\ ..\newcoin/src/cpp/ripple/ripple.proto + "../protobuf/protoc" -I=..\newcoin --cpp_out=..\newcoin\ ..\newcoin/src/cpp/ripple/ripple.proto \code\newcoin\src\ripple.pb.h + /code/protobuf/protoc -I=..\newcoin --cpp_out=\code\newcoin\ ..\newcoin/src/cpp/ripple/ripple.proto + \code\newcoin\src\ripple.pb.h diff --git a/newcoin.vcxproj.filters b/newcoin.vcxproj.filters index 25533c2dc..365cb7109 100644 --- a/newcoin.vcxproj.filters +++ b/newcoin.vcxproj.filters @@ -42,9 +42,6 @@ Source Files\database - - Source Files\database - Source Files\json @@ -177,9 +174,6 @@ Source Files - - Source Files - Source Files @@ -264,6 +258,9 @@ Source Files + + Source Files + Source Files @@ -360,6 +357,9 @@ Source Files + + Source Files + @@ -506,9 +506,6 @@ Header Files - - Header Files - Header Files @@ -605,6 +602,9 @@ Header Files + + Header Files + Header Files diff --git a/newcoin2012.sln b/newcoin2012.sln new file mode 100644 index 000000000..cf41ac0bf --- /dev/null +++ b/newcoin2012.sln @@ -0,0 +1,51 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "newcoin", "newcoin.vcxproj", "{19465545-42EE-42FA-9CC8-F8975F8F1CC7}" + ProjectSection(ProjectDependencies) = postProject + {3E283F37-A4ED-41B7-A3E6-A2D89D131A30} = {3E283F37-A4ED-41B7-A3E6-A2D89D131A30} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libprotobuf", "..\protobuf\vsprojects\libprotobuf.vcxproj", "{3E283F37-A4ED-41B7-A3E6-A2D89D131A30}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + FlashDebug|Win32 = FlashDebug|Win32 + FlashDebug|x64 = FlashDebug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + ReleaseD|Win32 = ReleaseD|Win32 + ReleaseD|x64 = ReleaseD|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {19465545-42EE-42FA-9CC8-F8975F8F1CC7}.Debug|Win32.ActiveCfg = Debug|Win32 + {19465545-42EE-42FA-9CC8-F8975F8F1CC7}.Debug|Win32.Build.0 = Debug|Win32 + {19465545-42EE-42FA-9CC8-F8975F8F1CC7}.Debug|x64.ActiveCfg = Debug|Win32 + {19465545-42EE-42FA-9CC8-F8975F8F1CC7}.FlashDebug|Win32.ActiveCfg = Debug|Win32 + {19465545-42EE-42FA-9CC8-F8975F8F1CC7}.FlashDebug|Win32.Build.0 = Debug|Win32 + {19465545-42EE-42FA-9CC8-F8975F8F1CC7}.FlashDebug|x64.ActiveCfg = Debug|Win32 + {19465545-42EE-42FA-9CC8-F8975F8F1CC7}.Release|Win32.ActiveCfg = Release|Win32 + {19465545-42EE-42FA-9CC8-F8975F8F1CC7}.Release|Win32.Build.0 = Release|Win32 + {19465545-42EE-42FA-9CC8-F8975F8F1CC7}.Release|x64.ActiveCfg = Release|Win32 + {19465545-42EE-42FA-9CC8-F8975F8F1CC7}.ReleaseD|Win32.ActiveCfg = Release|Win32 + {19465545-42EE-42FA-9CC8-F8975F8F1CC7}.ReleaseD|Win32.Build.0 = Release|Win32 + {19465545-42EE-42FA-9CC8-F8975F8F1CC7}.ReleaseD|x64.ActiveCfg = Release|Win32 + {3E283F37-A4ED-41B7-A3E6-A2D89D131A30}.Debug|Win32.ActiveCfg = Debug|Win32 + {3E283F37-A4ED-41B7-A3E6-A2D89D131A30}.Debug|Win32.Build.0 = Debug|Win32 + {3E283F37-A4ED-41B7-A3E6-A2D89D131A30}.Debug|x64.ActiveCfg = Debug|Win32 + {3E283F37-A4ED-41B7-A3E6-A2D89D131A30}.FlashDebug|Win32.ActiveCfg = Debug|Win32 + {3E283F37-A4ED-41B7-A3E6-A2D89D131A30}.FlashDebug|Win32.Build.0 = Debug|Win32 + {3E283F37-A4ED-41B7-A3E6-A2D89D131A30}.FlashDebug|x64.ActiveCfg = Debug|Win32 + {3E283F37-A4ED-41B7-A3E6-A2D89D131A30}.Release|Win32.ActiveCfg = Release|Win32 + {3E283F37-A4ED-41B7-A3E6-A2D89D131A30}.Release|Win32.Build.0 = Release|Win32 + {3E283F37-A4ED-41B7-A3E6-A2D89D131A30}.Release|x64.ActiveCfg = Release|Win32 + {3E283F37-A4ED-41B7-A3E6-A2D89D131A30}.ReleaseD|Win32.ActiveCfg = Release|Win32 + {3E283F37-A4ED-41B7-A3E6-A2D89D131A30}.ReleaseD|Win32.Build.0 = Release|Win32 + {3E283F37-A4ED-41B7-A3E6-A2D89D131A30}.ReleaseD|x64.ActiveCfg = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/package.json b/package.json index 2ec474aab..11ee2d9db 100644 --- a/package.json +++ b/package.json @@ -8,11 +8,12 @@ "dependencies": { "async": "~0.1.22", "ws": "~0.4.22", - "extend": "~1.1.1" + "extend": "~1.1.1", + "simple-jsonrpc": "~0.0.1" }, "devDependencies": { "buster": "~0.6.2", - "webpack": "~0.7.17" + "grunt-webpack": "~0.4.0" }, "scripts": { "test": "buster test" diff --git a/ripple-example.txt b/ripple-example.txt index 034ccb8dc..6e30aa3cc 100644 --- a/ripple-example.txt +++ b/ripple-example.txt @@ -96,13 +96,6 @@ # [currencies]: # This section allows a site to declare currencies it currently issues. # -# [ledger_history]: -# To serve clients, servers need historical ledger data. This sets the number of -# past ledgers to acquire on server startup and the minimum to maintain while -# running. Servers that don't need to serve clients can set this to "none" or "off". -# Servers that want complete history can set this to "full" or "on". -# The default is 256 ledgers. - [validation_public_key] n9MZTnHe5D5Q2cgE8oV2usFwRqhUvEA8MwP5Mu1XVD6TxmssPRev diff --git a/ripple2010.vcxproj b/ripple2010.vcxproj index ff66f7150..6afe49f96 100644 --- a/ripple2010.vcxproj +++ b/ripple2010.vcxproj @@ -57,6 +57,7 @@ true ..\OpenSSL\lib\VC;..\boost_1_52_0\stage\lib;..\protobuf\vsprojects\Debug ssleay32MDd.lib;libeay32MTd.lib;libprotobuf.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(OutDir)rippled.exe @@ -81,15 +82,15 @@ true true true - ..\OpenSSL\lib\VC;..\boost_1_47_0\stage\lib;..\protobuf-2.4.1\vsprojects\Release + ..\OpenSSL\lib\VC;..\boost_1_52_0\stage\lib;..\protobuf\vsprojects\Release libprotobuf.lib;ssleay32MD.lib;libeay32MD.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(OutDir)rippled.exe - @@ -144,7 +145,6 @@ - @@ -157,6 +157,7 @@ + @@ -176,6 +177,7 @@ + @@ -242,7 +244,6 @@ - @@ -276,6 +277,7 @@ + @@ -299,8 +301,8 @@ Document - /code/protobuf/protoc -I=..\newcoin --cpp_out=\code\newcoin\ ..\newcoin/src/cpp/ripple/ripple.proto - \code\newcoin\src\ripple.pb.h + "../protobuf/protoc" -I=..\newcoin --cpp_out=..\newcoin\ ..\newcoin/src/cpp/ripple/ripple.proto + ..\newcoin\src\ripple.pb.h diff --git a/ripple2010.vcxproj.filters b/ripple2010.vcxproj.filters index ae54851c7..77ef08370 100644 --- a/ripple2010.vcxproj.filters +++ b/ripple2010.vcxproj.filters @@ -39,9 +39,6 @@ Source Files\database - - Source Files\database - Source Files\json @@ -174,9 +171,6 @@ Source Files - - Source Files - Source Files @@ -261,6 +255,9 @@ Source Files + + Source Files + Source Files @@ -345,16 +342,16 @@ Source Files + + Source Files + Source Files Source Files - - Source Files - - + Source Files @@ -503,9 +500,6 @@ Header Files - - Header Files - Header Files @@ -605,6 +599,9 @@ Header Files + + Header Files + Header Files @@ -642,14 +639,13 @@ html + + - - - diff --git a/rippled-example.cfg b/rippled-example.cfg index 6ca428b2f..8bd553b9c 100644 --- a/rippled-example.cfg +++ b/rippled-example.cfg @@ -3,35 +3,47 @@ # # This file contains configuration information for rippled. # -# This file should be named rippled.cfg. This file is UTF-8 with Dos, UNIX, -# or Mac style end of lines. Blank lines and lines beginning with '#' are +# Rippled when launched attempts to find this file. For details, refer to the +# wiki page for --conf command line option: +# https://ripple.com/wiki/Rippled#--conf.3Dpath +# +# This file should be named rippled.cfg. This file is UTF-8 with Dos, UNIX, 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 rippled, 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 +# Specifies were a debug logfile is kept. By default, no debug log is kept. +# Unless absolute, the path is relative the directory containing this file. # # Example: debug.log # -# [validators_site]: -# Specifies where to find validators.txt for UNL boostrapping and RPC command unl_network. -# During alpha testing, this defaults to: redstem.com +# [validators]: +# List of nodes to always accept as validators. Nodes are specified by domain +# or public key. # -# Example: ripple.com +# For domains, rippled will probe for https web servers at the specified +# domain in the following order: ripple.DOMAIN, www.DOMAIN, DOMAIN +# +# For public key entries, a comment may optionally be spcified after adding a +# space to the pulic key. +# +# Examples: +# ripple.com +# n9KorY8QtTdRx7TVDpwnG9NvyxsDwHUKUEeDLY3AkiGncVaSXZi5 +# n9MqiExBcoG19UXwoLjBJnhsxEhAZMuWwJDRdkyDz1EkEkwzQTNt John Doe # # [validators_file]: -# Specifies how to bootstrap the UNL list. The UNL list is based on a -# validators.txt file and is maintained in the databases. When rippled -# starts up, if the databases are missing or are obsolete due to an upgrade -# of rippled, rippled will reconstruct the UNL list as specified here. +# Path to file contain a list of nodes to always accept as validators. Use +# this to specify a file other than this file to manage your validators list. # -# If this entry is not present or empty, rippled will look for a validators.txt in the -# config directory. If not found there, it will attempt to retrieve the file -# from the Ripple foundation's web site. +# If this entry is not present or empty and no nodes from previous runs were +# found in the database, rippled will look for a validators.txt in the config +# directory. If not found there, it will attempt to retrieve the file from +# the [validators_site] web site. # -# This entry is also used by the RPC command unl_load. +# After specifying a different [validators_file] or changing the contents of +# the validators file, issue a RPC unl_load command to have rippled load the +# file. # # Specify the file by specifying its full path. # @@ -39,24 +51,19 @@ # C:/home/johndoe/ripple/validators.txt # /home/johndoe/ripple/validators.txt # -# [validators]: -# Only valid in "rippled.cfg", "ripple.txt", and the referered [validators_url]. -# List of nodes to accept as validators speficied by public key or domain. +# [validators_site]: +# Specifies where to find validators.txt for UNL boostrapping and RPC +# unl_network command. # -# For domains, rippled will probe for https web servers at the specied -# domain in the following order: ripple.DOMAIN, www.DOMAIN, DOMAIN -# -# Examples: -# redstem.com -# n9KorY8QtTdRx7TVDpwnG9NvyxsDwHUKUEeDLY3AkiGncVaSXZi5 -# n9MqiExBcoG19UXwoLjBJnhsxEhAZMuWwJDRdkyDz1EkEkwzQTNt John Doe +# Example: ripple.com # # [ips]: -# Only valid in "rippled.cfg", "ripple.txt", and the referered [ips_url]. -# List of ips where the Newcoin protocol is avialable. -# One ipv4 or ipv6 address per line. -# A port may optionally be specified after adding a space to the address. -# By convention, if known, IPs are listed in from most to least trusted. +# List of ips where the Ripple protocol is served. For a starter list, you +# can copy entries from: https://ripple.com/ripple.txt +# +# Domain names are not allowed. One ipv4 or ipv6 address per line. A port +# may optionally be specified after adding a space to the address. By +# convention, if known, IPs are listed in from most to least trusted. # # Examples: # 192.168.0.1 @@ -64,46 +71,117 @@ # 2001:0db8:0100:f101:0210:a4ff:fee3:9566 # # [sntp_servers] -# IP address or domain of servers to use for time synchronization. -# The default time servers are suitable for servers located in the United States +# IP address or domain of NTP servers to use for time synchronization. +# +# These NTP servers are suitable for rippled servers located in the United +# States: +# time.windows.com +# time.apple.com +# time.nist.gov +# pool.ntp.org # # [peer_ip]: # IP address or domain to bind to allow external connections from peers. -# Defaults to not allow external connections from peers. +# Defaults to not binding, which disallows external connections from peers. # # Examples: 0.0.0.0 - Bind on all interfaces. # # [peer_port]: -# Port to bind to allow external connections from peers. +# If peer_ip is supplied, corresponding port to bind to for peer connections. # # [peer_private]: # 0 or 1. -# 0: allow peers to broadcast your address. [default] +# 0: request peers to broadcast your address. [default] # 1: request peers not broadcast your address. # # [rpc_ip]: # IP address or domain to bind to allow insecure RPC connections. -# Defaults to not allow RPC connections. +# Defaults to not binding, which disallows RPC connections. # # [rpc_port]: -# Port to bind to if allowing insecure RPC connections. +# If rpc_ip is supplied, corresponding port to bind to for peer connections. # # [rpc_allow_remote]: # 0 or 1. -# 0: only allows RPC connections from 127.0.0.1. [default] +# 0: Allow RPC connections only from 127.0.0.1. [default] +# 1: Allow RPC connections from any IP. +# +# [rpc_admin_allow]: +# Specify an list of IP addresses allowed to have admin access. One per line. +# +# Defaults to 127.0.0.1. +# +# [rpc_user]: +# As a server, require a this user to specified and require rpc_password to +# be checked for RPC access via the rpc_ip and rpc_port. The user and password +# must be specified via HTTP's basic authentication method. +# +# As a client, supply this to the server via HTTP's basic authentication +# method. +# +# [rpc_password]: +# As a server, require a this password to specified and require rpc_user to +# be checked for RPC access via the rpc_ip and rpc_port. The user and password +# must be specified via HTTP's basic authentication method. +# +# As a client, supply this to the server via HTTP's basic authentication +# method. +# +# [rpc_admin_user]: +# As a server, require this as the admin user to be specified. Also, require +# rpc_admin_user and rpc_admin_password to be checked for RPC admin functions. +# The request must specify these as the admin_user and admin_password in the +# request object. +# +# As a client, supply this to the server in the request object. +# +# [rpc_admin_password]: +# As a server, require this as the admin pasword to be specified. Also, +# require rpc_admin_user and rpc_admin_password to be checked for RPC admin +# functions. The request must specify these as the admin_user and +# admin_password in the request object. +# +# As a client, supply this to the server in the request object. +# +# [websocket_public_ip]: +# IP address or domain to bind to allow untrusted connections from clients. +# In the future, this option will go away and the peer_ip will accept +# websocket client connections. +# +# Examples: 0.0.0.0 - Bind on all interfaces. +# 127.0.0.1 - Bind on localhost interface. Only local programs may connect. +# +# [websocket_public_port]: +# Port to bind to allow untrusted connections from clients. In the future, +# this option will go away and the peer_ip will accept websocket client +# connections. +# +# [websocket_public_secure] +# 0, 1 or 2. +# 0: Provide ws service for websocket_public_ip/websocket_public_port. +# 1: Provide both ws and wss service for websocket_public_ip/websocket_public_port. [default] +# 2: Provide wss service only for websocket_public_ip/websocket_public_port. +# +# Browser pages like the Ripple client will not be able to connect to a secure +# websocket connection if a self-signed certificate is used. As the Ripple +# reference client currently shares secrets with its server, this should be +# enabled. # # [websocket_ip]: -# IP address or domain to bind to allow client connections. +# IP address or domain to bind to allow trusted ADMIN connections from backend +# applications. # # Examples: 0.0.0.0 - Bind on all interfaces. # 127.0.0.1 - Bind on localhost interface. Only local programs may connect. # # [websocket_port]: -# Port to bind to allow client connections. +# Port to bind to allow trusted ADMIN connections from backend applications. # -# [websocket_ssl]: -# 0 or 1. -# Enable websocket SSL +# [websocket_secure] +# 0, 1, or 2. +# 0: Provide ws service only for websocket_ip/websocket_port. [default] +# 1: Provide ws and wss service for websocket_ip/websocket_port +# 2: Provide wss service for websocket_ip/websocket_port. # # [websocket_ssl_key]: # Specify the filename holding the SSL key in PEM format. @@ -113,30 +191,72 @@ # This is not needed if the chain includes it. # # [websocket_ssl_chain]: -# If you need a certificate chain, specify the path to the certificate chain here. -# The chain may include the end certificate. +# If you need a certificate chain, specify the path to the certificate chain +# here. The chain may include the end certificate. # # [validation_seed]: -# To perform validation, this section should contain either a validation seed or key. -# The validation seed is used to generate the validation public/private key pair. +# To perform validation, this section should contain either a validation seed +# or key. The validation seed is used to generate the validation +# public/private key pair. To obtain a validation seed, use the +# validation_create command. +# +# Examples: RASH BUSH MILK LOOK BAD BRIM AVID GAFF BAIT ROT POD LOVE +# shfArahZT9Q9ckTf3s1psJ7C7qzVN +# +# [node_seed]: +# This is used for clustering. To force a particular node seed or key, the +# key can be set here. The format is the same as the validation_seed field. # To obtain a validation seed, use the validation_create command. # # Examples: RASH BUSH MILK LOOK BAD BRIM AVID GAFF BAIT ROT POD LOVE # shfArahZT9Q9ckTf3s1psJ7C7qzVN # +# [cluster_nodes]: +# To extend full trust to other nodes, place their node public keys here. +# Generally, you should only do this for nodes under common administration. +# Node public keys start with an 'n'. +# # [ledger_history]: -# To serve clients, servers need historical ledger data. This sets the number of -# past ledgers to acquire on server startup and the minimum to maintain while -# running. Servers that don't need to serve clients can set this to "none". -# Servers that want complete history can set this to "full". -# The default is 256 ledgers +# The number of past ledgers to acquire on server startup and the minimum to +# maintain while running. +# +# To serve clients, servers need historical ledger data. Servers that don't +# need to serve clients can set this to "none". Servers that want complete +# history can set this to "full". +# +# The default is: 256 +# +# [database_path]: +# Full path of database directory. +# +# [rpc_startup]: +# Specify a list of RPC commands to run at startup. +# +# Example: { "command" : "server_info" } +# +# Allow other peers to connect to this server. [peer_ip] 0.0.0.0 [peer_port] 51235 +# Allow untrusted clients to connect to this server. +[websocket_public_ip] +0.0.0.0 + +[websocket_public_port] +5006 + +# Provide trusted websocket ADMIN access. +[websocket_ip] +127.0.0.1 + +[websocket_port] +6006 + +# Provide trusted json-rpc ADMIN access. [rpc_ip] 127.0.0.1 @@ -144,13 +264,7 @@ 5005 [rpc_allow_remote] -1 - -[websocket_ip] -0.0.0.0 - -[websocket_port] -5006 +0 [debug_logfile] log/debug.log @@ -161,10 +275,7 @@ time.apple.com time.nist.gov pool.ntp.org - -[unl_default] -validators.txt - +# Where to find some other servers speaking the Ripple protocol. [ips] 23.21.167.100 51235 23.23.201.55 51235 diff --git a/rippled.1 b/rippled.1 new file mode 100644 index 000000000..c94248ae6 Binary files /dev/null and b/rippled.1 differ diff --git a/rippled.xcodeproj/project.pbxproj b/rippled.xcodeproj/project.pbxproj new file mode 100644 index 000000000..6e28208f0 --- /dev/null +++ b/rippled.xcodeproj/project.pbxproj @@ -0,0 +1,2757 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + C14F812E1681323400AAB80A /* rippled.1 in CopyFiles */ = {isa = PBXBuildFile; fileRef = C14F812D1681323400AAB80A /* rippled.1 */; }; + C14F842F1681326700AAB80A /* database.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81371681326600AAB80A /* database.cpp */; }; + C14F84311681326700AAB80A /* sqlite3.c in Sources */ = {isa = PBXBuildFile; fileRef = C14F813C1681326600AAB80A /* sqlite3.c */; }; + C14F84321681326700AAB80A /* SqliteDatabase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F813F1681326600AAB80A /* SqliteDatabase.cpp */; }; + C14F84341681326700AAB80A /* json_reader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F814E1681326600AAB80A /* json_reader.cpp */; }; + C14F84351681326700AAB80A /* json_value.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F814F1681326600AAB80A /* json_value.cpp */; }; + C14F84361681326700AAB80A /* json_writer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81511681326600AAB80A /* json_writer.cpp */; }; + C14F84371681326700AAB80A /* AccountItems.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81581681326600AAB80A /* AccountItems.cpp */; }; + C14F84381681326700AAB80A /* AccountSetTransactor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F815A1681326600AAB80A /* AccountSetTransactor.cpp */; }; + C14F84391681326700AAB80A /* AccountState.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F815C1681326600AAB80A /* AccountState.cpp */; }; + C14F843A1681326700AAB80A /* Amount.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F815E1681326600AAB80A /* Amount.cpp */; }; + C14F843B1681326700AAB80A /* Application.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F815F1681326600AAB80A /* Application.cpp */; }; + C14F843C1681326700AAB80A /* BitcoinUtil.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81631681326600AAB80A /* BitcoinUtil.cpp */; }; + C14F843D1681326700AAB80A /* CallRPC.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81651681326600AAB80A /* CallRPC.cpp */; }; + C14F843E1681326700AAB80A /* CanonicalTXSet.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81671681326600AAB80A /* CanonicalTXSet.cpp */; }; + C14F843F1681326700AAB80A /* Config.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81691681326600AAB80A /* Config.cpp */; }; + C14F84401681326700AAB80A /* ConnectionPool.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F816B1681326600AAB80A /* ConnectionPool.cpp */; }; + C14F84411681326700AAB80A /* Contract.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F816D1681326600AAB80A /* Contract.cpp */; }; + C14F84421681326700AAB80A /* DBInit.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F816F1681326600AAB80A /* DBInit.cpp */; }; + C14F84431681326700AAB80A /* DeterministicKeys.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81701681326600AAB80A /* DeterministicKeys.cpp */; }; + C14F84441681326700AAB80A /* ECIES.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81711681326600AAB80A /* ECIES.cpp */; }; + C14F84451681326700AAB80A /* FeatureTable.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81721681326600AAB80A /* FeatureTable.cpp */; }; + C14F84461681326700AAB80A /* FieldNames.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81741681326600AAB80A /* FieldNames.cpp */; }; + C14F84471681326700AAB80A /* HashedObject.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81761681326600AAB80A /* HashedObject.cpp */; }; + C14F84481681326700AAB80A /* HTTPRequest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81791681326600AAB80A /* HTTPRequest.cpp */; }; + C14F84491681326700AAB80A /* HttpsClient.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F817B1681326600AAB80A /* HttpsClient.cpp */; }; + C14F844A1681326700AAB80A /* InstanceCounter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F817D1681326600AAB80A /* InstanceCounter.cpp */; }; + C14F844B1681326700AAB80A /* Interpreter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F817F1681326600AAB80A /* Interpreter.cpp */; }; + C14F844C1681326700AAB80A /* JobQueue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81811681326600AAB80A /* JobQueue.cpp */; }; + C14F844D1681326700AAB80A /* Ledger.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81841681326600AAB80A /* Ledger.cpp */; }; + C14F844E1681326700AAB80A /* LedgerAcquire.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81861681326600AAB80A /* LedgerAcquire.cpp */; }; + C14F844F1681326700AAB80A /* LedgerConsensus.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81881681326600AAB80A /* LedgerConsensus.cpp */; }; + C14F84501681326700AAB80A /* LedgerEntrySet.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F818A1681326600AAB80A /* LedgerEntrySet.cpp */; }; + C14F84511681326700AAB80A /* LedgerFormats.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F818C1681326600AAB80A /* LedgerFormats.cpp */; }; + C14F84521681326700AAB80A /* LedgerHistory.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F818E1681326600AAB80A /* LedgerHistory.cpp */; }; + C14F84531681326700AAB80A /* LedgerMaster.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81901681326600AAB80A /* LedgerMaster.cpp */; }; + C14F84541681326700AAB80A /* LedgerProposal.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81921681326600AAB80A /* LedgerProposal.cpp */; }; + C14F84551681326700AAB80A /* LedgerTiming.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81941681326600AAB80A /* LedgerTiming.cpp */; }; + C14F84561681326700AAB80A /* LoadManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81961681326600AAB80A /* LoadManager.cpp */; }; + C14F84571681326700AAB80A /* LoadMonitor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81981681326600AAB80A /* LoadMonitor.cpp */; }; + C14F84581681326700AAB80A /* Log.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F819A1681326600AAB80A /* Log.cpp */; }; + C14F84591681326700AAB80A /* main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F819C1681326600AAB80A /* main.cpp */; }; + C14F845A1681326700AAB80A /* NetworkOPs.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F819D1681326600AAB80A /* NetworkOPs.cpp */; }; + C14F845B1681326700AAB80A /* NicknameState.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81A01681326600AAB80A /* NicknameState.cpp */; }; + C14F845C1681326700AAB80A /* Offer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81A21681326600AAB80A /* Offer.cpp */; }; + C14F845D1681326700AAB80A /* OfferCancelTransactor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81A41681326600AAB80A /* OfferCancelTransactor.cpp */; }; + C14F845E1681326700AAB80A /* OfferCreateTransactor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81A61681326600AAB80A /* OfferCreateTransactor.cpp */; }; + C14F845F1681326700AAB80A /* Operation.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81A81681326600AAB80A /* Operation.cpp */; }; + C14F84601681326700AAB80A /* OrderBook.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81AA1681326600AAB80A /* OrderBook.cpp */; }; + C14F84611681326700AAB80A /* OrderBookDB.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81AC1681326600AAB80A /* OrderBookDB.cpp */; }; + C14F84621681326700AAB80A /* PackedMessage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81AE1681326600AAB80A /* PackedMessage.cpp */; }; + C14F84631681326700AAB80A /* ParameterTable.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81B01681326600AAB80A /* ParameterTable.cpp */; }; + C14F84641681326700AAB80A /* ParseSection.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81B21681326600AAB80A /* ParseSection.cpp */; }; + C14F84651681326700AAB80A /* Pathfinder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81B41681326600AAB80A /* Pathfinder.cpp */; }; + C14F84661681326700AAB80A /* PaymentTransactor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81B61681326600AAB80A /* PaymentTransactor.cpp */; }; + C14F84671681326700AAB80A /* Peer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81B81681326600AAB80A /* Peer.cpp */; }; + C14F84681681326700AAB80A /* PeerDoor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81BA1681326600AAB80A /* PeerDoor.cpp */; }; + C14F84691681326700AAB80A /* PlatRand.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81BC1681326600AAB80A /* PlatRand.cpp */; }; + C14F846A1681326700AAB80A /* ProofOfWork.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81BD1681326600AAB80A /* ProofOfWork.cpp */; }; + C14F846B1681326700AAB80A /* PubKeyCache.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81BF1681326600AAB80A /* PubKeyCache.cpp */; }; + C14F846C1681326700AAB80A /* RangeSet.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81C11681326600AAB80A /* RangeSet.cpp */; }; + C14F846D1681326700AAB80A /* RegularKeySetTransactor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81C31681326600AAB80A /* RegularKeySetTransactor.cpp */; }; + C14F846E1681326700AAB80A /* rfc1751.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81C51681326600AAB80A /* rfc1751.cpp */; }; + C14F846F1681326700AAB80A /* RippleAddress.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81C81681326600AAB80A /* RippleAddress.cpp */; }; + C14F84701681326700AAB80A /* RippleCalc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81CA1681326600AAB80A /* RippleCalc.cpp */; }; + C14F84711681326700AAB80A /* RippleState.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81CC1681326600AAB80A /* RippleState.cpp */; }; + C14F84721681326700AAB80A /* rpc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81CE1681326600AAB80A /* rpc.cpp */; }; + C14F84731681326700AAB80A /* RPCDoor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81D01681326600AAB80A /* RPCDoor.cpp */; }; + C14F84741681326700AAB80A /* RPCErr.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81D21681326600AAB80A /* RPCErr.cpp */; }; + C14F84751681326700AAB80A /* RPCHandler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81D41681326600AAB80A /* RPCHandler.cpp */; }; + C14F84761681326700AAB80A /* RPCServer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81D61681326600AAB80A /* RPCServer.cpp */; }; + C14F84771681326700AAB80A /* ScriptData.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81D91681326600AAB80A /* ScriptData.cpp */; }; + C14F84781681326700AAB80A /* SerializedLedger.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81DC1681326600AAB80A /* SerializedLedger.cpp */; }; + C14F84791681326700AAB80A /* SerializedObject.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81DE1681326600AAB80A /* SerializedObject.cpp */; }; + C14F847A1681326700AAB80A /* SerializedTransaction.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81E01681326600AAB80A /* SerializedTransaction.cpp */; }; + C14F847B1681326700AAB80A /* SerializedTypes.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81E21681326600AAB80A /* SerializedTypes.cpp */; }; + C14F847C1681326700AAB80A /* SerializedValidation.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81E41681326600AAB80A /* SerializedValidation.cpp */; }; + C14F847D1681326700AAB80A /* Serializer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81E71681326600AAB80A /* Serializer.cpp */; }; + C14F847E1681326700AAB80A /* SHAMap.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81E91681326600AAB80A /* SHAMap.cpp */; }; + C14F847F1681326700AAB80A /* SHAMapDiff.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81EB1681326600AAB80A /* SHAMapDiff.cpp */; }; + C14F84801681326700AAB80A /* SHAMapNodes.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81EC1681326600AAB80A /* SHAMapNodes.cpp */; }; + C14F84811681326700AAB80A /* SHAMapSync.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81ED1681326600AAB80A /* SHAMapSync.cpp */; }; + C14F84821681326700AAB80A /* SNTPClient.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81EF1681326600AAB80A /* SNTPClient.cpp */; }; + C14F84831681326700AAB80A /* Suppression.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81F11681326600AAB80A /* Suppression.cpp */; }; + C14F84841681326700AAB80A /* Transaction.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81F41681326600AAB80A /* Transaction.cpp */; }; + C14F84851681326700AAB80A /* TransactionEngine.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81F61681326600AAB80A /* TransactionEngine.cpp */; }; + C14F84861681326700AAB80A /* TransactionErr.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81F81681326600AAB80A /* TransactionErr.cpp */; }; + C14F84871681326700AAB80A /* TransactionFormats.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81FA1681326600AAB80A /* TransactionFormats.cpp */; }; + C14F84881681326700AAB80A /* TransactionMaster.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81FC1681326600AAB80A /* TransactionMaster.cpp */; }; + C14F84891681326700AAB80A /* TransactionMeta.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F81FE1681326600AAB80A /* TransactionMeta.cpp */; }; + C14F848A1681326700AAB80A /* Transactor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F82001681326600AAB80A /* Transactor.cpp */; }; + C14F848B1681326700AAB80A /* TrustSetTransactor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F82021681326600AAB80A /* TrustSetTransactor.cpp */; }; + C14F848C1681326700AAB80A /* UniqueNodeList.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F82061681326600AAB80A /* UniqueNodeList.cpp */; }; + C14F848D1681326700AAB80A /* utils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F82081681326600AAB80A /* utils.cpp */; }; + C14F848E1681326700AAB80A /* ValidationCollection.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F820A1681326600AAB80A /* ValidationCollection.cpp */; }; + C14F848F1681326700AAB80A /* Wallet.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F820D1681326600AAB80A /* Wallet.cpp */; }; + C14F84901681326700AAB80A /* WalletAddTransactor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F820F1681326600AAB80A /* WalletAddTransactor.cpp */; }; + C14F84911681326700AAB80A /* WSDoor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F82121681326600AAB80A /* WSDoor.cpp */; }; + C14F84D81681326700AAB80A /* base64.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F82C21681326600AAB80A /* base64.cpp */; }; + C14F84D91681326700AAB80A /* md5.c in Sources */ = {isa = PBXBuildFile; fileRef = C14F82CD1681326600AAB80A /* md5.c */; }; + C14F84DA1681326700AAB80A /* data.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F82D21681326600AAB80A /* data.cpp */; }; + C14F84DB1681326700AAB80A /* network_utilities.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F82D41681326600AAB80A /* network_utilities.cpp */; }; + C14F84DC1681326700AAB80A /* hybi_header.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F82D81681326600AAB80A /* hybi_header.cpp */; }; + C14F84DD1681326700AAB80A /* hybi_util.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F82DB1681326600AAB80A /* hybi_util.cpp */; }; + C14F84DE1681326700AAB80A /* blank_rng.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F82DF1681326600AAB80A /* blank_rng.cpp */; }; + C14F84DF1681326700AAB80A /* boost_rng.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F82E11681326600AAB80A /* boost_rng.cpp */; }; + C14F84E21681326700AAB80A /* sha1.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F82EC1681326600AAB80A /* sha1.cpp */; }; + C14F84E51681326700AAB80A /* uri.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C14F82FA1681326600AAB80A /* uri.cpp */; }; + C1637B0416827C5B0067A9B4 /* ripple.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = C1637B0216827C5B0067A9B4 /* ripple.pb.cc */; }; + C1637B07168284510067A9B4 /* TransactionQueue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C1637B05168284510067A9B4 /* TransactionQueue.cpp */; }; +/* End PBXBuildFile section */ + +/* Begin PBXBuildRule section */ + C14F85C11681357800AAB80A /* PBXBuildRule */ = { + isa = PBXBuildRule; + compilerSpec = com.apple.compilers.proxy.script; + filePatterns = src/cpp/ripple/ripple.proto; + fileType = pattern.proxy; + isEditable = 1; + outputFiles = ( + ); + script = "protoc -Isrc/cpp/ripple --cpp_out=build/proto src/cpp/ripple/ripple.proto"; + }; +/* End PBXBuildRule section */ + +/* Begin PBXContainerItemProxy section */ + C14F85A71681326700AAB80A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C14F830D1681326600AAB80A /* websocketpp.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = B6DF1C691434A7A30029A1B1; + remoteInfo = "WebSocket++ Static Library"; + }; + C14F85A91681326700AAB80A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C14F830D1681326600AAB80A /* websocketpp.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = B6DF1C721434A8280029A1B1; + remoteInfo = "WebSocket++ Dynamic Library"; + }; + C14F85AB1681326700AAB80A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C14F830D1681326600AAB80A /* websocketpp.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = B6DF1CD11435ED910029A1B1; + remoteInfo = echo_server; + }; + C14F85AD1681326700AAB80A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C14F830D1681326600AAB80A /* websocketpp.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = B682887D143745F2002BA48B; + remoteInfo = chat_client; + }; + C14F85AF1681326700AAB80A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C14F830D1681326600AAB80A /* websocketpp.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = B6CF181C1437C397009295BE; + remoteInfo = echo_client; + }; + C14F85B11681326700AAB80A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C14F830D1681326600AAB80A /* websocketpp.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = B6FE8D4F14730AE900B32547; + remoteInfo = policy_test; + }; + C14F85B31681326700AAB80A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C14F830D1681326600AAB80A /* websocketpp.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = B663884B1487D73200DDAE13; + remoteInfo = echo_server_tls; + }; + C14F85B51681326700AAB80A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C14F830D1681326600AAB80A /* websocketpp.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = B6732458148FAEEB00FC2B04; + remoteInfo = fuzzing_server; + }; + C14F85B71681326700AAB80A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C14F830D1681326600AAB80A /* websocketpp.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = B6732471148FB0FC00FC2B04; + remoteInfo = fuzzing_client; + }; + C14F85B91681326700AAB80A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C14F830D1681326600AAB80A /* websocketpp.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = B67324891491A16500FC2B04; + remoteInfo = broadcast_server; + }; + C14F85BB1681326700AAB80A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C14F830D1681326600AAB80A /* websocketpp.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = B67324A21491A7F100FC2B04; + remoteInfo = stress_client; + }; + C14F85BD1681326700AAB80A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C14F830D1681326600AAB80A /* websocketpp.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = B61A51BF14DC271900456432; + remoteInfo = concurrent_server; + }; + C14F85BF1681326700AAB80A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C14F830D1681326600AAB80A /* websocketpp.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = B6E7E7731505532E00394909; + remoteInfo = wsperf; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + C14F81251681323400AAB80A /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + C14F812E1681323400AAB80A /* rippled.1 in CopyFiles */, + ); + runOnlyForDeploymentPostprocessing = 1; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + C14F81271681323400AAB80A /* rippled */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = rippled; sourceTree = BUILT_PRODUCTS_DIR; }; + C14F812D1681323400AAB80A /* rippled.1 */ = {isa = PBXFileReference; lastKnownFileType = text.man; path = rippled.1; sourceTree = ""; }; + C14F81371681326600AAB80A /* database.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = database.cpp; sourceTree = ""; }; + C14F81381681326600AAB80A /* database.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = database.h; sourceTree = ""; }; + C14F813A1681326600AAB80A /* mysqldatabase.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = mysqldatabase.cpp; sourceTree = ""; }; + C14F813B1681326600AAB80A /* mysqldatabase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mysqldatabase.h; sourceTree = ""; }; + C14F813C1681326600AAB80A /* sqlite3.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = sqlite3.c; sourceTree = ""; }; + C14F813D1681326600AAB80A /* sqlite3.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = sqlite3.h; sourceTree = ""; }; + C14F813E1681326600AAB80A /* sqlite3ext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = sqlite3ext.h; sourceTree = ""; }; + C14F813F1681326600AAB80A /* SqliteDatabase.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SqliteDatabase.cpp; sourceTree = ""; }; + C14F81401681326600AAB80A /* SqliteDatabase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SqliteDatabase.h; sourceTree = ""; }; + C14F81421681326600AAB80A /* dbutility.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = dbutility.h; sourceTree = ""; }; + C14F81431681326600AAB80A /* windatabase.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = windatabase.cpp; sourceTree = ""; }; + C14F81441681326600AAB80A /* windatabase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = windatabase.h; sourceTree = ""; }; + C14F81461681326600AAB80A /* autolink.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = autolink.h; sourceTree = ""; }; + C14F81471681326600AAB80A /* config.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = config.h; sourceTree = ""; }; + C14F81481681326600AAB80A /* features.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = features.h; sourceTree = ""; }; + C14F81491681326600AAB80A /* forwards.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = forwards.h; sourceTree = ""; }; + C14F814A1681326600AAB80A /* json.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = json.h; sourceTree = ""; }; + C14F814B1681326600AAB80A /* json_batchallocator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = json_batchallocator.h; sourceTree = ""; }; + C14F814C1681326600AAB80A /* json_internalarray.inl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = json_internalarray.inl; sourceTree = ""; }; + C14F814D1681326600AAB80A /* json_internalmap.inl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = json_internalmap.inl; sourceTree = ""; }; + C14F814E1681326600AAB80A /* json_reader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = json_reader.cpp; sourceTree = ""; }; + C14F814F1681326600AAB80A /* json_value.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = json_value.cpp; sourceTree = ""; }; + C14F81501681326600AAB80A /* json_valueiterator.inl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = json_valueiterator.inl; sourceTree = ""; }; + C14F81511681326600AAB80A /* json_writer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = json_writer.cpp; sourceTree = ""; }; + C14F81521681326600AAB80A /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; + C14F81531681326600AAB80A /* reader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = reader.h; sourceTree = ""; }; + C14F81541681326600AAB80A /* value.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = value.h; sourceTree = ""; }; + C14F81551681326600AAB80A /* version */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = version; sourceTree = ""; }; + C14F81561681326600AAB80A /* writer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = writer.h; sourceTree = ""; }; + C14F81581681326600AAB80A /* AccountItems.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AccountItems.cpp; sourceTree = ""; }; + C14F81591681326600AAB80A /* AccountItems.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AccountItems.h; sourceTree = ""; }; + C14F815A1681326600AAB80A /* AccountSetTransactor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AccountSetTransactor.cpp; sourceTree = ""; }; + C14F815B1681326600AAB80A /* AccountSetTransactor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AccountSetTransactor.h; sourceTree = ""; }; + C14F815C1681326600AAB80A /* AccountState.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AccountState.cpp; sourceTree = ""; }; + C14F815D1681326600AAB80A /* AccountState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AccountState.h; sourceTree = ""; }; + C14F815E1681326600AAB80A /* Amount.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Amount.cpp; sourceTree = ""; }; + C14F815F1681326600AAB80A /* Application.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Application.cpp; sourceTree = ""; }; + C14F81601681326600AAB80A /* Application.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Application.h; sourceTree = ""; }; + C14F81611681326600AAB80A /* base58.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = base58.h; sourceTree = ""; }; + C14F81621681326600AAB80A /* bignum.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = bignum.h; sourceTree = ""; }; + C14F81631681326600AAB80A /* BitcoinUtil.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BitcoinUtil.cpp; sourceTree = ""; }; + C14F81641681326600AAB80A /* BitcoinUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BitcoinUtil.h; sourceTree = ""; }; + C14F81651681326600AAB80A /* CallRPC.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CallRPC.cpp; sourceTree = ""; }; + C14F81661681326600AAB80A /* CallRPC.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CallRPC.h; sourceTree = ""; }; + C14F81671681326600AAB80A /* CanonicalTXSet.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CanonicalTXSet.cpp; sourceTree = ""; }; + C14F81681681326600AAB80A /* CanonicalTXSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CanonicalTXSet.h; sourceTree = ""; }; + C14F81691681326600AAB80A /* Config.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Config.cpp; sourceTree = ""; }; + C14F816A1681326600AAB80A /* Config.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Config.h; sourceTree = ""; }; + C14F816B1681326600AAB80A /* ConnectionPool.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ConnectionPool.cpp; sourceTree = ""; }; + C14F816C1681326600AAB80A /* ConnectionPool.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ConnectionPool.h; sourceTree = ""; }; + C14F816D1681326600AAB80A /* Contract.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Contract.cpp; sourceTree = ""; }; + C14F816E1681326600AAB80A /* Contract.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Contract.h; sourceTree = ""; }; + C14F816F1681326600AAB80A /* DBInit.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DBInit.cpp; sourceTree = ""; }; + C14F81701681326600AAB80A /* DeterministicKeys.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DeterministicKeys.cpp; sourceTree = ""; }; + C14F81711681326600AAB80A /* ECIES.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ECIES.cpp; sourceTree = ""; }; + C14F81721681326600AAB80A /* FeatureTable.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FeatureTable.cpp; sourceTree = ""; }; + C14F81731681326600AAB80A /* FeatureTable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FeatureTable.h; sourceTree = ""; }; + C14F81741681326600AAB80A /* FieldNames.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FieldNames.cpp; sourceTree = ""; }; + C14F81751681326600AAB80A /* FieldNames.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FieldNames.h; sourceTree = ""; }; + C14F81761681326600AAB80A /* HashedObject.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = HashedObject.cpp; sourceTree = ""; }; + C14F81771681326600AAB80A /* HashedObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HashedObject.h; sourceTree = ""; }; + C14F81781681326600AAB80A /* HashPrefixes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HashPrefixes.h; sourceTree = ""; }; + C14F81791681326600AAB80A /* HTTPRequest.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = HTTPRequest.cpp; sourceTree = ""; }; + C14F817A1681326600AAB80A /* HTTPRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTTPRequest.h; sourceTree = ""; }; + C14F817B1681326600AAB80A /* HttpsClient.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = HttpsClient.cpp; sourceTree = ""; }; + C14F817C1681326600AAB80A /* HttpsClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HttpsClient.h; sourceTree = ""; }; + C14F817D1681326600AAB80A /* InstanceCounter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = InstanceCounter.cpp; sourceTree = ""; }; + C14F817E1681326600AAB80A /* InstanceCounter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InstanceCounter.h; sourceTree = ""; }; + C14F817F1681326600AAB80A /* Interpreter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Interpreter.cpp; sourceTree = ""; }; + C14F81801681326600AAB80A /* Interpreter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Interpreter.h; sourceTree = ""; }; + C14F81811681326600AAB80A /* JobQueue.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JobQueue.cpp; sourceTree = ""; }; + C14F81821681326600AAB80A /* JobQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JobQueue.h; sourceTree = ""; }; + C14F81831681326600AAB80A /* key.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = key.h; sourceTree = ""; }; + C14F81841681326600AAB80A /* Ledger.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Ledger.cpp; sourceTree = ""; }; + C14F81851681326600AAB80A /* Ledger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Ledger.h; sourceTree = ""; }; + C14F81861681326600AAB80A /* LedgerAcquire.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = LedgerAcquire.cpp; sourceTree = ""; }; + C14F81871681326600AAB80A /* LedgerAcquire.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LedgerAcquire.h; sourceTree = ""; }; + C14F81881681326600AAB80A /* LedgerConsensus.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = LedgerConsensus.cpp; sourceTree = ""; }; + C14F81891681326600AAB80A /* LedgerConsensus.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LedgerConsensus.h; sourceTree = ""; }; + C14F818A1681326600AAB80A /* LedgerEntrySet.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = LedgerEntrySet.cpp; sourceTree = ""; }; + C14F818B1681326600AAB80A /* LedgerEntrySet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LedgerEntrySet.h; sourceTree = ""; }; + C14F818C1681326600AAB80A /* LedgerFormats.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = LedgerFormats.cpp; sourceTree = ""; }; + C14F818D1681326600AAB80A /* LedgerFormats.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LedgerFormats.h; sourceTree = ""; }; + C14F818E1681326600AAB80A /* LedgerHistory.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = LedgerHistory.cpp; sourceTree = ""; }; + C14F818F1681326600AAB80A /* LedgerHistory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LedgerHistory.h; sourceTree = ""; }; + C14F81901681326600AAB80A /* LedgerMaster.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = LedgerMaster.cpp; sourceTree = ""; }; + C14F81911681326600AAB80A /* LedgerMaster.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LedgerMaster.h; sourceTree = ""; }; + C14F81921681326600AAB80A /* LedgerProposal.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = LedgerProposal.cpp; sourceTree = ""; }; + C14F81931681326600AAB80A /* LedgerProposal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LedgerProposal.h; sourceTree = ""; }; + C14F81941681326600AAB80A /* LedgerTiming.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = LedgerTiming.cpp; sourceTree = ""; }; + C14F81951681326600AAB80A /* LedgerTiming.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LedgerTiming.h; sourceTree = ""; }; + C14F81961681326600AAB80A /* LoadManager.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = LoadManager.cpp; sourceTree = ""; }; + C14F81971681326600AAB80A /* LoadManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LoadManager.h; sourceTree = ""; }; + C14F81981681326600AAB80A /* LoadMonitor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = LoadMonitor.cpp; sourceTree = ""; }; + C14F81991681326600AAB80A /* LoadMonitor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LoadMonitor.h; sourceTree = ""; }; + C14F819A1681326600AAB80A /* Log.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Log.cpp; sourceTree = ""; }; + C14F819B1681326600AAB80A /* Log.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Log.h; sourceTree = ""; }; + C14F819C1681326600AAB80A /* main.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = main.cpp; sourceTree = ""; }; + C14F819D1681326600AAB80A /* NetworkOPs.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = NetworkOPs.cpp; sourceTree = ""; }; + C14F819E1681326600AAB80A /* NetworkOPs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NetworkOPs.h; sourceTree = ""; }; + C14F819F1681326600AAB80A /* NetworkStatus.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NetworkStatus.h; sourceTree = ""; }; + C14F81A01681326600AAB80A /* NicknameState.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = NicknameState.cpp; sourceTree = ""; }; + C14F81A11681326600AAB80A /* NicknameState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NicknameState.h; sourceTree = ""; }; + C14F81A21681326600AAB80A /* Offer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Offer.cpp; sourceTree = ""; }; + C14F81A31681326600AAB80A /* Offer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Offer.h; sourceTree = ""; }; + C14F81A41681326600AAB80A /* OfferCancelTransactor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OfferCancelTransactor.cpp; sourceTree = ""; }; + C14F81A51681326600AAB80A /* OfferCancelTransactor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OfferCancelTransactor.h; sourceTree = ""; }; + C14F81A61681326600AAB80A /* OfferCreateTransactor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OfferCreateTransactor.cpp; sourceTree = ""; }; + C14F81A71681326600AAB80A /* OfferCreateTransactor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OfferCreateTransactor.h; sourceTree = ""; }; + C14F81A81681326600AAB80A /* Operation.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Operation.cpp; sourceTree = ""; }; + C14F81A91681326600AAB80A /* Operation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Operation.h; sourceTree = ""; }; + C14F81AA1681326600AAB80A /* OrderBook.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OrderBook.cpp; sourceTree = ""; }; + C14F81AB1681326600AAB80A /* OrderBook.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OrderBook.h; sourceTree = ""; }; + C14F81AC1681326600AAB80A /* OrderBookDB.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OrderBookDB.cpp; sourceTree = ""; }; + C14F81AD1681326600AAB80A /* OrderBookDB.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OrderBookDB.h; sourceTree = ""; }; + C14F81AE1681326600AAB80A /* PackedMessage.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PackedMessage.cpp; sourceTree = ""; }; + C14F81AF1681326600AAB80A /* PackedMessage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PackedMessage.h; sourceTree = ""; }; + C14F81B01681326600AAB80A /* ParameterTable.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ParameterTable.cpp; sourceTree = ""; }; + C14F81B11681326600AAB80A /* ParameterTable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ParameterTable.h; sourceTree = ""; }; + C14F81B21681326600AAB80A /* ParseSection.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ParseSection.cpp; sourceTree = ""; }; + C14F81B31681326600AAB80A /* ParseSection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ParseSection.h; sourceTree = ""; }; + C14F81B41681326600AAB80A /* Pathfinder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Pathfinder.cpp; sourceTree = ""; }; + C14F81B51681326600AAB80A /* Pathfinder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Pathfinder.h; sourceTree = ""; }; + C14F81B61681326600AAB80A /* PaymentTransactor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PaymentTransactor.cpp; sourceTree = ""; }; + C14F81B71681326600AAB80A /* PaymentTransactor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PaymentTransactor.h; sourceTree = ""; }; + C14F81B81681326600AAB80A /* Peer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Peer.cpp; sourceTree = ""; }; + C14F81B91681326600AAB80A /* Peer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Peer.h; sourceTree = ""; }; + C14F81BA1681326600AAB80A /* PeerDoor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PeerDoor.cpp; sourceTree = ""; }; + C14F81BB1681326600AAB80A /* PeerDoor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PeerDoor.h; sourceTree = ""; }; + C14F81BC1681326600AAB80A /* PlatRand.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PlatRand.cpp; sourceTree = ""; }; + C14F81BD1681326600AAB80A /* ProofOfWork.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ProofOfWork.cpp; sourceTree = ""; }; + C14F81BE1681326600AAB80A /* ProofOfWork.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ProofOfWork.h; sourceTree = ""; }; + C14F81BF1681326600AAB80A /* PubKeyCache.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PubKeyCache.cpp; sourceTree = ""; }; + C14F81C01681326600AAB80A /* PubKeyCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PubKeyCache.h; sourceTree = ""; }; + C14F81C11681326600AAB80A /* RangeSet.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RangeSet.cpp; sourceTree = ""; }; + C14F81C21681326600AAB80A /* RangeSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RangeSet.h; sourceTree = ""; }; + C14F81C31681326600AAB80A /* RegularKeySetTransactor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RegularKeySetTransactor.cpp; sourceTree = ""; }; + C14F81C41681326600AAB80A /* RegularKeySetTransactor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RegularKeySetTransactor.h; sourceTree = ""; }; + C14F81C51681326600AAB80A /* rfc1751.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = rfc1751.cpp; sourceTree = ""; }; + C14F81C61681326600AAB80A /* rfc1751.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = rfc1751.h; sourceTree = ""; }; + C14F81C71681326600AAB80A /* ripple.proto */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = ripple.proto; sourceTree = ""; }; + C14F81C81681326600AAB80A /* RippleAddress.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RippleAddress.cpp; sourceTree = ""; }; + C14F81C91681326600AAB80A /* RippleAddress.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RippleAddress.h; sourceTree = ""; }; + C14F81CA1681326600AAB80A /* RippleCalc.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RippleCalc.cpp; sourceTree = ""; }; + C14F81CB1681326600AAB80A /* RippleCalc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RippleCalc.h; sourceTree = ""; }; + C14F81CC1681326600AAB80A /* RippleState.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RippleState.cpp; sourceTree = ""; }; + C14F81CD1681326600AAB80A /* RippleState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RippleState.h; sourceTree = ""; }; + C14F81CE1681326600AAB80A /* rpc.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = rpc.cpp; sourceTree = ""; }; + C14F81CF1681326600AAB80A /* RPC.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RPC.h; sourceTree = ""; }; + C14F81D01681326600AAB80A /* RPCDoor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RPCDoor.cpp; sourceTree = ""; }; + C14F81D11681326600AAB80A /* RPCDoor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RPCDoor.h; sourceTree = ""; }; + C14F81D21681326600AAB80A /* RPCErr.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RPCErr.cpp; sourceTree = ""; }; + C14F81D31681326600AAB80A /* RPCErr.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RPCErr.h; sourceTree = ""; }; + C14F81D41681326600AAB80A /* RPCHandler.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RPCHandler.cpp; sourceTree = ""; }; + C14F81D51681326600AAB80A /* RPCHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RPCHandler.h; sourceTree = ""; }; + C14F81D61681326600AAB80A /* RPCServer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RPCServer.cpp; sourceTree = ""; }; + C14F81D71681326600AAB80A /* RPCServer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RPCServer.h; sourceTree = ""; }; + C14F81D81681326600AAB80A /* ScopedLock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ScopedLock.h; sourceTree = ""; }; + C14F81D91681326600AAB80A /* ScriptData.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ScriptData.cpp; sourceTree = ""; }; + C14F81DA1681326600AAB80A /* ScriptData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ScriptData.h; sourceTree = ""; }; + C14F81DB1681326600AAB80A /* SecureAllocator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SecureAllocator.h; sourceTree = ""; }; + C14F81DC1681326600AAB80A /* SerializedLedger.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SerializedLedger.cpp; sourceTree = ""; }; + C14F81DD1681326600AAB80A /* SerializedLedger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SerializedLedger.h; sourceTree = ""; }; + C14F81DE1681326600AAB80A /* SerializedObject.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SerializedObject.cpp; sourceTree = ""; }; + C14F81DF1681326600AAB80A /* SerializedObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SerializedObject.h; sourceTree = ""; }; + C14F81E01681326600AAB80A /* SerializedTransaction.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SerializedTransaction.cpp; sourceTree = ""; }; + C14F81E11681326600AAB80A /* SerializedTransaction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SerializedTransaction.h; sourceTree = ""; }; + C14F81E21681326600AAB80A /* SerializedTypes.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SerializedTypes.cpp; sourceTree = ""; }; + C14F81E31681326600AAB80A /* SerializedTypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SerializedTypes.h; sourceTree = ""; }; + C14F81E41681326600AAB80A /* SerializedValidation.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SerializedValidation.cpp; sourceTree = ""; }; + C14F81E51681326600AAB80A /* SerializedValidation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SerializedValidation.h; sourceTree = ""; }; + C14F81E61681326600AAB80A /* SerializeProto.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SerializeProto.h; sourceTree = ""; }; + C14F81E71681326600AAB80A /* Serializer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Serializer.cpp; sourceTree = ""; }; + C14F81E81681326600AAB80A /* Serializer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Serializer.h; sourceTree = ""; }; + C14F81E91681326600AAB80A /* SHAMap.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SHAMap.cpp; sourceTree = ""; }; + C14F81EA1681326600AAB80A /* SHAMap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SHAMap.h; sourceTree = ""; }; + C14F81EB1681326600AAB80A /* SHAMapDiff.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SHAMapDiff.cpp; sourceTree = ""; }; + C14F81EC1681326600AAB80A /* SHAMapNodes.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SHAMapNodes.cpp; sourceTree = ""; }; + C14F81ED1681326600AAB80A /* SHAMapSync.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SHAMapSync.cpp; sourceTree = ""; }; + C14F81EE1681326600AAB80A /* SHAMapSync.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SHAMapSync.h; sourceTree = ""; }; + C14F81EF1681326600AAB80A /* SNTPClient.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SNTPClient.cpp; sourceTree = ""; }; + C14F81F01681326600AAB80A /* SNTPClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTPClient.h; sourceTree = ""; }; + C14F81F11681326600AAB80A /* Suppression.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Suppression.cpp; sourceTree = ""; }; + C14F81F21681326600AAB80A /* Suppression.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Suppression.h; sourceTree = ""; }; + C14F81F31681326600AAB80A /* TaggedCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TaggedCache.h; sourceTree = ""; }; + C14F81F41681326600AAB80A /* Transaction.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Transaction.cpp; sourceTree = ""; }; + C14F81F51681326600AAB80A /* Transaction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Transaction.h; sourceTree = ""; }; + C14F81F61681326600AAB80A /* TransactionEngine.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TransactionEngine.cpp; sourceTree = ""; }; + C14F81F71681326600AAB80A /* TransactionEngine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TransactionEngine.h; sourceTree = ""; }; + C14F81F81681326600AAB80A /* TransactionErr.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TransactionErr.cpp; sourceTree = ""; }; + C14F81F91681326600AAB80A /* TransactionErr.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TransactionErr.h; sourceTree = ""; }; + C14F81FA1681326600AAB80A /* TransactionFormats.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TransactionFormats.cpp; sourceTree = ""; }; + C14F81FB1681326600AAB80A /* TransactionFormats.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TransactionFormats.h; sourceTree = ""; }; + C14F81FC1681326600AAB80A /* TransactionMaster.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TransactionMaster.cpp; sourceTree = ""; }; + C14F81FD1681326600AAB80A /* TransactionMaster.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TransactionMaster.h; sourceTree = ""; }; + C14F81FE1681326600AAB80A /* TransactionMeta.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TransactionMeta.cpp; sourceTree = ""; }; + C14F81FF1681326600AAB80A /* TransactionMeta.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TransactionMeta.h; sourceTree = ""; }; + C14F82001681326600AAB80A /* Transactor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Transactor.cpp; sourceTree = ""; }; + C14F82011681326600AAB80A /* Transactor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Transactor.h; sourceTree = ""; }; + C14F82021681326600AAB80A /* TrustSetTransactor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TrustSetTransactor.cpp; sourceTree = ""; }; + C14F82031681326600AAB80A /* TrustSetTransactor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TrustSetTransactor.h; sourceTree = ""; }; + C14F82041681326600AAB80A /* types.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = types.h; sourceTree = ""; }; + C14F82051681326600AAB80A /* uint256.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = uint256.h; sourceTree = ""; }; + C14F82061681326600AAB80A /* UniqueNodeList.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = UniqueNodeList.cpp; sourceTree = ""; }; + C14F82071681326600AAB80A /* UniqueNodeList.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UniqueNodeList.h; sourceTree = ""; }; + C14F82081681326600AAB80A /* utils.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = utils.cpp; sourceTree = ""; }; + C14F82091681326600AAB80A /* utils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = utils.h; sourceTree = ""; }; + C14F820A1681326600AAB80A /* ValidationCollection.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ValidationCollection.cpp; sourceTree = ""; }; + C14F820B1681326600AAB80A /* ValidationCollection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ValidationCollection.h; sourceTree = ""; }; + C14F820C1681326600AAB80A /* Version.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Version.h; sourceTree = ""; }; + C14F820D1681326600AAB80A /* Wallet.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Wallet.cpp; sourceTree = ""; }; + C14F820E1681326600AAB80A /* Wallet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Wallet.h; sourceTree = ""; }; + C14F820F1681326600AAB80A /* WalletAddTransactor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = WalletAddTransactor.cpp; sourceTree = ""; }; + C14F82101681326600AAB80A /* WalletAddTransactor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WalletAddTransactor.h; sourceTree = ""; }; + C14F82111681326600AAB80A /* WSConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WSConnection.h; sourceTree = ""; }; + C14F82121681326600AAB80A /* WSDoor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = WSDoor.cpp; sourceTree = ""; }; + C14F82131681326600AAB80A /* WSDoor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WSDoor.h; sourceTree = ""; }; + C14F82141681326600AAB80A /* WSHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WSHandler.h; sourceTree = ""; }; + C14F82161681326600AAB80A /* dependencies.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = dependencies.txt; sourceTree = ""; }; + C14F82181681326600AAB80A /* uri.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = uri.txt; sourceTree = ""; }; + C14F821B1681326600AAB80A /* broadcast_admin.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = broadcast_admin.html; sourceTree = ""; }; + C14F821C1681326600AAB80A /* broadcast_admin_handler.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = broadcast_admin_handler.hpp; sourceTree = ""; }; + C14F821D1681326600AAB80A /* broadcast_handler.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = broadcast_handler.hpp; sourceTree = ""; }; + C14F821E1681326600AAB80A /* broadcast_server_handler.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = broadcast_server_handler.hpp; sourceTree = ""; }; + C14F821F1681326600AAB80A /* broadcast_server_tls.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = broadcast_server_tls.cpp; sourceTree = ""; }; + C14F82201681326600AAB80A /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = ""; }; + C14F82231681326600AAB80A /* API.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = API.txt; sourceTree = ""; }; + C14F82251681326600AAB80A /* ajax.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = ajax.html; sourceTree = ""; }; + C14F82261681326600AAB80A /* annotating.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = annotating.html; sourceTree = ""; }; + C14F82271681326600AAB80A /* arrow-down.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = "arrow-down.gif"; sourceTree = ""; }; + C14F82281681326600AAB80A /* arrow-left.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = "arrow-left.gif"; sourceTree = ""; }; + C14F82291681326600AAB80A /* arrow-right.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = "arrow-right.gif"; sourceTree = ""; }; + C14F822A1681326600AAB80A /* arrow-up.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = "arrow-up.gif"; sourceTree = ""; }; + C14F822B1681326600AAB80A /* basic.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = basic.html; sourceTree = ""; }; + C14F822C1681326600AAB80A /* data-eu-gdp-growth-1.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "data-eu-gdp-growth-1.json"; sourceTree = ""; }; + C14F822D1681326600AAB80A /* data-eu-gdp-growth-2.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "data-eu-gdp-growth-2.json"; sourceTree = ""; }; + C14F822E1681326600AAB80A /* data-eu-gdp-growth-3.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "data-eu-gdp-growth-3.json"; sourceTree = ""; }; + C14F822F1681326600AAB80A /* data-eu-gdp-growth-4.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "data-eu-gdp-growth-4.json"; sourceTree = ""; }; + C14F82301681326600AAB80A /* data-eu-gdp-growth-5.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "data-eu-gdp-growth-5.json"; sourceTree = ""; }; + C14F82311681326600AAB80A /* data-eu-gdp-growth.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "data-eu-gdp-growth.json"; sourceTree = ""; }; + C14F82321681326600AAB80A /* data-japan-gdp-growth.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "data-japan-gdp-growth.json"; sourceTree = ""; }; + C14F82331681326600AAB80A /* data-usa-gdp-growth.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "data-usa-gdp-growth.json"; sourceTree = ""; }; + C14F82341681326600AAB80A /* graph-types.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "graph-types.html"; sourceTree = ""; }; + C14F82351681326600AAB80A /* hs-2004-27-a-large_web.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "hs-2004-27-a-large_web.jpg"; sourceTree = ""; }; + C14F82361681326600AAB80A /* image.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = image.html; sourceTree = ""; }; + C14F82371681326600AAB80A /* index.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = index.html; sourceTree = ""; }; + C14F82381681326600AAB80A /* interacting-axes.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "interacting-axes.html"; sourceTree = ""; }; + C14F82391681326600AAB80A /* interacting.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = interacting.html; sourceTree = ""; }; + C14F823A1681326600AAB80A /* layout.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; path = layout.css; sourceTree = ""; }; + C14F823B1681326600AAB80A /* multiple-axes.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "multiple-axes.html"; sourceTree = ""; }; + C14F823C1681326600AAB80A /* navigate.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = navigate.html; sourceTree = ""; }; + C14F823D1681326600AAB80A /* percentiles.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = percentiles.html; sourceTree = ""; }; + C14F823E1681326600AAB80A /* pie.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = pie.html; sourceTree = ""; }; + C14F823F1681326600AAB80A /* realtime.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = realtime.html; sourceTree = ""; }; + C14F82401681326600AAB80A /* resize.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = resize.html; sourceTree = ""; }; + C14F82411681326600AAB80A /* selection.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = selection.html; sourceTree = ""; }; + C14F82421681326600AAB80A /* setting-options.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "setting-options.html"; sourceTree = ""; }; + C14F82431681326600AAB80A /* stacking.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = stacking.html; sourceTree = ""; }; + C14F82441681326600AAB80A /* symbols.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = symbols.html; sourceTree = ""; }; + C14F82451681326600AAB80A /* thresholding.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = thresholding.html; sourceTree = ""; }; + C14F82461681326600AAB80A /* time.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = time.html; sourceTree = ""; }; + C14F82471681326600AAB80A /* tracking.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = tracking.html; sourceTree = ""; }; + C14F82481681326600AAB80A /* turning-series.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "turning-series.html"; sourceTree = ""; }; + C14F82491681326600AAB80A /* visitors.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = visitors.html; sourceTree = ""; }; + C14F824A1681326600AAB80A /* zooming.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = zooming.html; sourceTree = ""; }; + C14F824B1681326600AAB80A /* excanvas.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = excanvas.js; sourceTree = ""; }; + C14F824C1681326600AAB80A /* excanvas.min.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = excanvas.min.js; sourceTree = ""; }; + C14F824D1681326600AAB80A /* FAQ.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = FAQ.txt; sourceTree = ""; }; + C14F824E1681326600AAB80A /* jquery.colorhelpers.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = jquery.colorhelpers.js; sourceTree = ""; }; + C14F824F1681326600AAB80A /* jquery.colorhelpers.min.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = jquery.colorhelpers.min.js; sourceTree = ""; }; + C14F82501681326600AAB80A /* jquery.flot.crosshair.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = jquery.flot.crosshair.js; sourceTree = ""; }; + C14F82511681326600AAB80A /* jquery.flot.crosshair.min.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = jquery.flot.crosshair.min.js; sourceTree = ""; }; + C14F82521681326600AAB80A /* jquery.flot.fillbetween.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = jquery.flot.fillbetween.js; sourceTree = ""; }; + C14F82531681326600AAB80A /* jquery.flot.fillbetween.min.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = jquery.flot.fillbetween.min.js; sourceTree = ""; }; + C14F82541681326600AAB80A /* jquery.flot.image.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = jquery.flot.image.js; sourceTree = ""; }; + C14F82551681326600AAB80A /* jquery.flot.image.min.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = jquery.flot.image.min.js; sourceTree = ""; }; + C14F82561681326600AAB80A /* jquery.flot.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = jquery.flot.js; sourceTree = ""; }; + C14F82571681326600AAB80A /* jquery.flot.min.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = jquery.flot.min.js; sourceTree = ""; }; + C14F82581681326600AAB80A /* jquery.flot.navigate.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = jquery.flot.navigate.js; sourceTree = ""; }; + C14F82591681326600AAB80A /* jquery.flot.navigate.min.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = jquery.flot.navigate.min.js; sourceTree = ""; }; + C14F825A1681326600AAB80A /* jquery.flot.pie.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = jquery.flot.pie.js; sourceTree = ""; }; + C14F825B1681326600AAB80A /* jquery.flot.pie.min.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = jquery.flot.pie.min.js; sourceTree = ""; }; + C14F825C1681326600AAB80A /* jquery.flot.resize.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = jquery.flot.resize.js; sourceTree = ""; }; + C14F825D1681326600AAB80A /* jquery.flot.resize.min.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = jquery.flot.resize.min.js; sourceTree = ""; }; + C14F825E1681326600AAB80A /* jquery.flot.selection.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = jquery.flot.selection.js; sourceTree = ""; }; + C14F825F1681326600AAB80A /* jquery.flot.selection.min.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = jquery.flot.selection.min.js; sourceTree = ""; }; + C14F82601681326600AAB80A /* jquery.flot.stack.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = jquery.flot.stack.js; sourceTree = ""; }; + C14F82611681326600AAB80A /* jquery.flot.stack.min.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = jquery.flot.stack.min.js; sourceTree = ""; }; + C14F82621681326600AAB80A /* jquery.flot.symbol.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = jquery.flot.symbol.js; sourceTree = ""; }; + C14F82631681326600AAB80A /* jquery.flot.symbol.min.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = jquery.flot.symbol.min.js; sourceTree = ""; }; + C14F82641681326600AAB80A /* jquery.flot.threshold.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = jquery.flot.threshold.js; sourceTree = ""; }; + C14F82651681326600AAB80A /* jquery.flot.threshold.min.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = jquery.flot.threshold.min.js; sourceTree = ""; }; + C14F82661681326600AAB80A /* jquery.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = jquery.js; sourceTree = ""; }; + C14F82671681326600AAB80A /* jquery.min.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = jquery.min.js; sourceTree = ""; }; + C14F82681681326600AAB80A /* LICENSE.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE.txt; sourceTree = ""; }; + C14F82691681326600AAB80A /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = ""; }; + C14F826A1681326600AAB80A /* NEWS.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = NEWS.txt; sourceTree = ""; }; + C14F826B1681326600AAB80A /* PLUGINS.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = PLUGINS.txt; sourceTree = ""; }; + C14F826C1681326600AAB80A /* README.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = README.txt; sourceTree = ""; }; + C14F826D1681326600AAB80A /* md5.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = md5.js; sourceTree = ""; }; + C14F826E1681326600AAB80A /* wscmd.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = wscmd.hpp; sourceTree = ""; }; + C14F82701681326600AAB80A /* chat_client.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = chat_client.cpp; sourceTree = ""; }; + C14F82711681326600AAB80A /* chat_client.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = chat_client.html; sourceTree = ""; }; + C14F82721681326600AAB80A /* chat_client_handler.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = chat_client_handler.cpp; sourceTree = ""; }; + C14F82731681326600AAB80A /* chat_client_handler.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = chat_client_handler.hpp; sourceTree = ""; }; + C14F82741681326600AAB80A /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = ""; }; + C14F82751681326600AAB80A /* SConscript */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = SConscript; sourceTree = ""; }; + C14F82771681326600AAB80A /* jquery-1.6.3.min.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = "jquery-1.6.3.min.js"; sourceTree = ""; }; + C14F82791681326600AAB80A /* chat.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = chat.cpp; sourceTree = ""; }; + C14F827A1681326600AAB80A /* chat.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = chat.hpp; sourceTree = ""; }; + C14F827B1681326600AAB80A /* chat_server.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = chat_server.cpp; sourceTree = ""; }; + C14F827C1681326600AAB80A /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = ""; }; + C14F827D1681326600AAB80A /* SConscript */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = SConscript; sourceTree = ""; }; + C14F827E1681326600AAB80A /* common.mk */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = common.mk; sourceTree = ""; }; + C14F82801681326600AAB80A /* concurrent_client.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = concurrent_client.html; sourceTree = ""; }; + C14F82811681326600AAB80A /* concurrent_server.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = concurrent_server.cpp; sourceTree = ""; }; + C14F82821681326600AAB80A /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = ""; }; + C14F82831681326600AAB80A /* SConscript */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = SConscript; sourceTree = ""; }; + C14F82851681326600AAB80A /* echo_client.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = echo_client.cpp; sourceTree = ""; }; + C14F82861681326600AAB80A /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = ""; }; + C14F82871681326600AAB80A /* SConscript */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = SConscript; sourceTree = ""; }; + C14F82891681326600AAB80A /* echo_client.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = echo_client.html; sourceTree = ""; }; + C14F828A1681326600AAB80A /* echo_server.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = echo_server.cpp; sourceTree = ""; }; + C14F828B1681326600AAB80A /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = ""; }; + C14F828C1681326600AAB80A /* SConscript */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = SConscript; sourceTree = ""; }; + C14F828E1681326600AAB80A /* echo.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = echo.cpp; sourceTree = ""; }; + C14F828F1681326600AAB80A /* echo.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = echo.hpp; sourceTree = ""; }; + C14F82901681326600AAB80A /* echo_client.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = echo_client.html; sourceTree = ""; }; + C14F82911681326600AAB80A /* echo_server_tls.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = echo_server_tls.cpp; sourceTree = ""; }; + C14F82921681326600AAB80A /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = ""; }; + C14F82931681326600AAB80A /* SConscript */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = SConscript; sourceTree = ""; }; + C14F82951681326600AAB80A /* fuzzing_client.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = fuzzing_client.cpp; sourceTree = ""; }; + C14F82961681326600AAB80A /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = ""; }; + C14F82981681326600AAB80A /* echo_client.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = echo_client.html; sourceTree = ""; }; + C14F82991681326600AAB80A /* fuzzing_server_tls.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = fuzzing_server_tls.cpp; sourceTree = ""; }; + C14F829A1681326600AAB80A /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = ""; }; + C14F829B1681326600AAB80A /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = ""; }; + C14F829D1681326600AAB80A /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = ""; }; + C14F829E1681326600AAB80A /* stress_client.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = stress_client.cpp; sourceTree = ""; }; + C14F82A01681326600AAB80A /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = ""; }; + C14F82A11681326600AAB80A /* SConscript */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = SConscript; sourceTree = ""; }; + C14F82A21681326600AAB80A /* telemetry_server.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = telemetry_server.cpp; sourceTree = ""; }; + C14F82A41681326600AAB80A /* case.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = case.cpp; sourceTree = ""; }; + C14F82A51681326600AAB80A /* case.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = case.hpp; sourceTree = ""; }; + C14F82A61681326600AAB80A /* generic.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = generic.cpp; sourceTree = ""; }; + C14F82A71681326600AAB80A /* generic.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = generic.hpp; sourceTree = ""; }; + C14F82A81681326600AAB80A /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = ""; }; + C14F82A91681326600AAB80A /* message_test.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = message_test.html; sourceTree = ""; }; + C14F82AA1681326600AAB80A /* request.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = request.cpp; sourceTree = ""; }; + C14F82AB1681326600AAB80A /* request.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = request.hpp; sourceTree = ""; }; + C14F82AC1681326600AAB80A /* SConscript */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = SConscript; sourceTree = ""; }; + C14F82AD1681326600AAB80A /* stress_aggregate.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = stress_aggregate.cpp; sourceTree = ""; }; + C14F82AE1681326600AAB80A /* stress_aggregate.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = stress_aggregate.hpp; sourceTree = ""; }; + C14F82AF1681326600AAB80A /* stress_handler.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = stress_handler.cpp; sourceTree = ""; }; + C14F82B01681326600AAB80A /* stress_handler.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = stress_handler.hpp; sourceTree = ""; }; + C14F82B11681326600AAB80A /* stress_test.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = stress_test.html; sourceTree = ""; }; + C14F82B31681326600AAB80A /* backbone-localstorage.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = "backbone-localstorage.js"; sourceTree = ""; }; + C14F82B41681326600AAB80A /* backbone.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = backbone.js; sourceTree = ""; }; + C14F82B51681326600AAB80A /* jquery.min.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = jquery.min.js; sourceTree = ""; }; + C14F82B61681326600AAB80A /* underscore.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = underscore.js; sourceTree = ""; }; + C14F82B71681326600AAB80A /* wscmd.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wscmd.cpp; sourceTree = ""; }; + C14F82B81681326600AAB80A /* wscmd.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = wscmd.hpp; sourceTree = ""; }; + C14F82B91681326600AAB80A /* wsperf.cfg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = wsperf.cfg; sourceTree = ""; }; + C14F82BA1681326600AAB80A /* wsperf.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wsperf.cpp; sourceTree = ""; }; + C14F82BB1681326600AAB80A /* wsperf_commander.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = wsperf_commander.html; sourceTree = ""; }; + C14F82BC1681326600AAB80A /* license.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = license.txt; sourceTree = ""; }; + C14F82BD1681326600AAB80A /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = ""; }; + C14F82BE1681326600AAB80A /* readme.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = readme.txt; sourceTree = ""; }; + C14F82BF1681326600AAB80A /* SConstruct */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = SConstruct; sourceTree = ""; }; + C14F82C21681326600AAB80A /* base64.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = base64.cpp; sourceTree = ""; }; + C14F82C31681326600AAB80A /* base64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = base64.h; sourceTree = ""; }; + C14F82C41681326600AAB80A /* common.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = common.hpp; sourceTree = ""; }; + C14F82C51681326600AAB80A /* connection.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = connection.hpp; sourceTree = ""; }; + C14F82C61681326600AAB80A /* endpoint.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = endpoint.hpp; sourceTree = ""; }; + C14F82C81681326600AAB80A /* constants.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = constants.hpp; sourceTree = ""; }; + C14F82C91681326600AAB80A /* parser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = parser.hpp; sourceTree = ""; }; + C14F82CB1681326600AAB80A /* logger.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = logger.hpp; sourceTree = ""; }; + C14F82CD1681326600AAB80A /* md5.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = md5.c; sourceTree = ""; }; + C14F82CE1681326600AAB80A /* md5.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = md5.h; sourceTree = ""; }; + C14F82CF1681326600AAB80A /* md5.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = md5.hpp; sourceTree = ""; }; + C14F82D11681326600AAB80A /* control.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = control.hpp; sourceTree = ""; }; + C14F82D21681326600AAB80A /* data.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = data.cpp; sourceTree = ""; }; + C14F82D31681326600AAB80A /* data.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = data.hpp; sourceTree = ""; }; + C14F82D41681326600AAB80A /* network_utilities.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = network_utilities.cpp; sourceTree = ""; }; + C14F82D51681326600AAB80A /* network_utilities.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = network_utilities.hpp; sourceTree = ""; }; + C14F82D71681326600AAB80A /* hybi.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = hybi.hpp; sourceTree = ""; }; + C14F82D81681326600AAB80A /* hybi_header.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = hybi_header.cpp; sourceTree = ""; }; + C14F82D91681326600AAB80A /* hybi_header.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = hybi_header.hpp; sourceTree = ""; }; + C14F82DA1681326600AAB80A /* hybi_legacy.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = hybi_legacy.hpp; sourceTree = ""; }; + C14F82DB1681326600AAB80A /* hybi_util.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = hybi_util.cpp; sourceTree = ""; }; + C14F82DC1681326600AAB80A /* hybi_util.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = hybi_util.hpp; sourceTree = ""; }; + C14F82DD1681326600AAB80A /* processor.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = processor.hpp; sourceTree = ""; }; + C14F82DF1681326600AAB80A /* blank_rng.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = blank_rng.cpp; sourceTree = ""; }; + C14F82E01681326600AAB80A /* blank_rng.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = blank_rng.hpp; sourceTree = ""; }; + C14F82E11681326600AAB80A /* boost_rng.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = boost_rng.cpp; sourceTree = ""; }; + C14F82E21681326600AAB80A /* boost_rng.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = boost_rng.hpp; sourceTree = ""; }; + C14F82E41681326600AAB80A /* client.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = client.hpp; sourceTree = ""; }; + C14F82E51681326600AAB80A /* server.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = server.hpp; sourceTree = ""; }; + C14F82E61681326600AAB80A /* SConscript */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = SConscript; sourceTree = ""; }; + C14F82E81681326600AAB80A /* license.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = license.txt; sourceTree = ""; }; + C14F82E91681326600AAB80A /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = ""; }; + C14F82EA1681326600AAB80A /* Makefile.nt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Makefile.nt; sourceTree = ""; }; + C14F82EB1681326600AAB80A /* sha.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = sha.cpp; sourceTree = ""; }; + C14F82EC1681326600AAB80A /* sha1.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = sha1.cpp; sourceTree = ""; }; + C14F82ED1681326600AAB80A /* sha1.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = sha1.h; sourceTree = ""; }; + C14F82EE1681326600AAB80A /* shacmp.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = shacmp.cpp; sourceTree = ""; }; + C14F82EF1681326600AAB80A /* shatest.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = shatest.cpp; sourceTree = ""; }; + C14F82F01681326600AAB80A /* shared_const_buffer.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = shared_const_buffer.hpp; sourceTree = ""; }; + C14F82F21681326600AAB80A /* plain.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = plain.hpp; sourceTree = ""; }; + C14F82F31681326600AAB80A /* socket_base.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = socket_base.hpp; sourceTree = ""; }; + C14F82F41681326600AAB80A /* tls.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = tls.hpp; sourceTree = ""; }; + C14F82F61681326600AAB80A /* client.pem */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = client.pem; sourceTree = ""; }; + C14F82F71681326600AAB80A /* dh512.pem */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = dh512.pem; sourceTree = ""; }; + C14F82F81681326600AAB80A /* server.cer */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = server.cer; sourceTree = ""; }; + C14F82F91681326600AAB80A /* server.pem */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = server.pem; sourceTree = ""; }; + C14F82FA1681326600AAB80A /* uri.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = uri.cpp; sourceTree = ""; }; + C14F82FB1681326600AAB80A /* uri.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = uri.hpp; sourceTree = ""; }; + C14F82FD1681326600AAB80A /* utf8_validator.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = utf8_validator.hpp; sourceTree = ""; }; + C14F82FE1681326600AAB80A /* websocket_frame.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = websocket_frame.hpp; sourceTree = ""; }; + C14F82FF1681326600AAB80A /* websocketpp.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = websocketpp.hpp; sourceTree = ""; }; + C14F83021681326600AAB80A /* hybi_util.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = hybi_util.cpp; sourceTree = ""; }; + C14F83031681326600AAB80A /* logging.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = logging.cpp; sourceTree = ""; }; + C14F83041681326600AAB80A /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = ""; }; + C14F83051681326600AAB80A /* parsing.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = parsing.cpp; sourceTree = ""; }; + C14F83061681326600AAB80A /* uri_perf.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = uri_perf.cpp; sourceTree = ""; }; + C14F83071681326600AAB80A /* todo.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = todo.txt; sourceTree = ""; }; + C14F83091681326600AAB80A /* project.bbprojectdata */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = project.bbprojectdata; sourceTree = ""; }; + C14F830A1681326600AAB80A /* Scratchpad.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Scratchpad.txt; sourceTree = ""; }; + C14F830B1681326600AAB80A /* Unix Worksheet.worksheet */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.worksheet; path = "Unix Worksheet.worksheet"; sourceTree = ""; }; + C14F830C1681326600AAB80A /* zaphoyd.bbprojectsettings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = zaphoyd.bbprojectsettings; sourceTree = ""; }; + C14F830D1681326600AAB80A /* websocketpp.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = websocketpp.xcodeproj; sourceTree = ""; }; + C14F83121681326600AAB80A /* .gitignore */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = .gitignore; sourceTree = ""; }; + C14F83131681326600AAB80A /* common.vsprops */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = common.vsprops; sourceTree = ""; }; + C14F83151681326600AAB80A /* chatclient.vcproj */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = chatclient.vcproj; sourceTree = ""; }; + C14F83161681326600AAB80A /* chatserver.vcproj */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = chatserver.vcproj; sourceTree = ""; }; + C14F83171681326600AAB80A /* concurrent_server.vcproj */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = concurrent_server.vcproj; sourceTree = ""; }; + C14F83181681326600AAB80A /* echoserver.vcproj */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = echoserver.vcproj; sourceTree = ""; }; + C14F83191681326600AAB80A /* stdint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = stdint.h; sourceTree = ""; }; + C14F831A1681326600AAB80A /* websocketpp.sln */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = websocketpp.sln; sourceTree = ""; }; + C14F831B1681326600AAB80A /* websocketpp.vcproj */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = websocketpp.vcproj; sourceTree = ""; }; + C14F831D1681326600AAB80A /* .gitignore */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = .gitignore; sourceTree = ""; }; + C14F831F1681326600AAB80A /* chatclient.vcxproj */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = chatclient.vcxproj; sourceTree = ""; }; + C14F83201681326600AAB80A /* chatclient.vcxproj.filters */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = chatclient.vcxproj.filters; sourceTree = ""; }; + C14F83211681326600AAB80A /* chatserver.vcxproj */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = chatserver.vcxproj; sourceTree = ""; }; + C14F83221681326600AAB80A /* chatserver.vcxproj.filters */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = chatserver.vcxproj.filters; sourceTree = ""; }; + C14F83231681326600AAB80A /* echoclient.vcxproj */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = echoclient.vcxproj; sourceTree = ""; }; + C14F83241681326600AAB80A /* echoclient.vcxproj.filters */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = echoclient.vcxproj.filters; sourceTree = ""; }; + C14F83251681326600AAB80A /* echoserver.vcxproj */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = echoserver.vcxproj; sourceTree = ""; }; + C14F83261681326600AAB80A /* echoserver.vcxproj.filters */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = echoserver.vcxproj.filters; sourceTree = ""; }; + C14F83281681326600AAB80A /* wsperf.vcxproj */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = wsperf.vcxproj; sourceTree = ""; }; + C14F83291681326600AAB80A /* wsperf.vcxproj.filters */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = wsperf.vcxproj.filters; sourceTree = ""; }; + C14F832A1681326600AAB80A /* readme.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = readme.txt; sourceTree = ""; }; + C14F832B1681326600AAB80A /* websocketpp.sln */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = websocketpp.sln; sourceTree = ""; }; + C14F832C1681326600AAB80A /* websocketpp.vcxproj */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = websocketpp.vcxproj; sourceTree = ""; }; + C14F832D1681326600AAB80A /* websocketpp.vcxproj.filters */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = websocketpp.vcxproj.filters; sourceTree = ""; }; + C14F832F1681326600AAB80A /* account.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = account.js; sourceTree = ""; }; + C14F83301681326600AAB80A /* amount.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = amount.js; sourceTree = ""; }; + C14F83321681326600AAB80A /* cryptojs.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = cryptojs.js; sourceTree = ""; }; + C14F83341681326600AAB80A /* AES.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = AES.js; sourceTree = ""; }; + C14F83351681326600AAB80A /* BlockModes.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = BlockModes.js; sourceTree = ""; }; + C14F83361681326600AAB80A /* Crypto.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = Crypto.js; sourceTree = ""; }; + C14F83371681326600AAB80A /* CryptoMath.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = CryptoMath.js; sourceTree = ""; }; + C14F83381681326600AAB80A /* DES.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = DES.js; sourceTree = ""; }; + C14F83391681326600AAB80A /* HMAC.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = HMAC.js; sourceTree = ""; }; + C14F833A1681326600AAB80A /* MARC4.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = MARC4.js; sourceTree = ""; }; + C14F833B1681326600AAB80A /* MD5.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = MD5.js; sourceTree = ""; }; + C14F833C1681326600AAB80A /* PBKDF2.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = PBKDF2.js; sourceTree = ""; }; + C14F833D1681326600AAB80A /* PBKDF2Async.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = PBKDF2Async.js; sourceTree = ""; }; + C14F833E1681326600AAB80A /* Rabbit.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = Rabbit.js; sourceTree = ""; }; + C14F833F1681326600AAB80A /* SHA1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = SHA1.js; sourceTree = ""; }; + C14F83401681326600AAB80A /* SHA256.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = SHA256.js; sourceTree = ""; }; + C14F83411681326600AAB80A /* package.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = package.json; sourceTree = ""; }; + C14F83421681326600AAB80A /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = README.md; sourceTree = ""; }; + C14F83441681326600AAB80A /* PBKDF2-test.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = "PBKDF2-test.js"; sourceTree = ""; }; + C14F83451681326600AAB80A /* test.coffee */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = test.coffee; sourceTree = ""; }; + C14F83461681326600AAB80A /* index.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = index.js; sourceTree = ""; }; + C14F83471681326600AAB80A /* jsbn.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = jsbn.js; sourceTree = ""; }; + C14F83481681326600AAB80A /* network.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = network.js; sourceTree = ""; }; + C14F83491681326600AAB80A /* nodeutils.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = nodeutils.js; sourceTree = ""; }; + C14F834A1681326600AAB80A /* remote.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = remote.js; sourceTree = ""; }; + C14F834B1681326600AAB80A /* serializer.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = serializer.js; sourceTree = ""; }; + C14F834E1681326600AAB80A /* browserTest.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = browserTest.html; sourceTree = ""; }; + C14F834F1681326600AAB80A /* browserUtil.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = browserUtil.js; sourceTree = ""; }; + C14F83501681326600AAB80A /* rhinoUtil.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = rhinoUtil.js; sourceTree = ""; }; + C14F83511681326600AAB80A /* test.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; path = test.css; sourceTree = ""; }; + C14F83531681326600AAB80A /* compiler.jar */ = {isa = PBXFileReference; lastKnownFileType = archive.jar; path = compiler.jar; sourceTree = ""; }; + C14F83541681326600AAB80A /* compress_with_closure.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = compress_with_closure.sh; sourceTree = ""; }; + C14F83551681326600AAB80A /* compress_with_yui.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = compress_with_yui.sh; sourceTree = ""; }; + C14F83561681326600AAB80A /* dewindowize.pl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.perl; path = dewindowize.pl; sourceTree = ""; }; + C14F83571681326600AAB80A /* digitize.pl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.perl; path = digitize.pl; sourceTree = ""; }; + C14F83581681326600AAB80A /* opacify.pl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.perl; path = opacify.pl; sourceTree = ""; }; + C14F83591681326600AAB80A /* remove_constants.pl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.perl; path = remove_constants.pl; sourceTree = ""; }; + C14F835A1681326600AAB80A /* yuicompressor-2.4.2.jar */ = {isa = PBXFileReference; lastKnownFileType = archive.jar; path = "yuicompressor-2.4.2.jar"; sourceTree = ""; }; + C14F835B1681326600AAB80A /* config.mk */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = config.mk; sourceTree = ""; }; + C14F835C1681326600AAB80A /* configure */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = configure; sourceTree = ""; }; + C14F835E1681326600AAB80A /* aes.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = aes.js; sourceTree = ""; }; + C14F835F1681326600AAB80A /* bitArray.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = bitArray.js; sourceTree = ""; }; + C14F83601681326600AAB80A /* bn.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = bn.js; sourceTree = ""; }; + C14F83611681326600AAB80A /* cbc.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = cbc.js; sourceTree = ""; }; + C14F83621681326600AAB80A /* ccm.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = ccm.js; sourceTree = ""; }; + C14F83631681326600AAB80A /* codecBase64.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = codecBase64.js; sourceTree = ""; }; + C14F83641681326600AAB80A /* codecBytes.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = codecBytes.js; sourceTree = ""; }; + C14F83651681326600AAB80A /* codecHex.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = codecHex.js; sourceTree = ""; }; + C14F83661681326600AAB80A /* codecString.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = codecString.js; sourceTree = ""; }; + C14F83671681326600AAB80A /* convenience.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = convenience.js; sourceTree = ""; }; + C14F83681681326600AAB80A /* ecc.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = ecc.js; sourceTree = ""; }; + C14F83691681326600AAB80A /* hmac.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = hmac.js; sourceTree = ""; }; + C14F836A1681326600AAB80A /* ocb2.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = ocb2.js; sourceTree = ""; }; + C14F836B1681326600AAB80A /* pbkdf2.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = pbkdf2.js; sourceTree = ""; }; + C14F836C1681326600AAB80A /* random.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = random.js; sourceTree = ""; }; + C14F836D1681326600AAB80A /* sha1.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = sha1.js; sourceTree = ""; }; + C14F836E1681326600AAB80A /* sha256.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = sha256.js; sourceTree = ""; }; + C14F836F1681326600AAB80A /* sjcl.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = sjcl.js; sourceTree = ""; }; + C14F83701681326600AAB80A /* srp.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = srp.js; sourceTree = ""; }; + C14F83711681326600AAB80A /* core.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = core.js; sourceTree = ""; }; + C14F83721681326600AAB80A /* core_closure.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = core_closure.js; sourceTree = ""; }; + C14F83741681326600AAB80A /* alpha-arrow.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "alpha-arrow.png"; sourceTree = ""; }; + C14F83751681326600AAB80A /* example.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; path = example.css; sourceTree = ""; }; + C14F83761681326600AAB80A /* example.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = example.js; sourceTree = ""; }; + C14F83771681326600AAB80A /* form.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = form.js; sourceTree = ""; }; + C14F83781681326600AAB80A /* index.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = index.html; sourceTree = ""; }; + C14F837C1681326600AAB80A /* Chain.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = Chain.js; sourceTree = ""; }; + C14F837D1681326600AAB80A /* Dumper.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = Dumper.js; sourceTree = ""; }; + C14F837E1681326600AAB80A /* Hash.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = Hash.js; sourceTree = ""; }; + C14F837F1681326600AAB80A /* Link.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = Link.js; sourceTree = ""; }; + C14F83801681326600AAB80A /* Namespace.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = Namespace.js; sourceTree = ""; }; + C14F83811681326600AAB80A /* Opt.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = Opt.js; sourceTree = ""; }; + C14F83821681326600AAB80A /* Reflection.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = Reflection.js; sourceTree = ""; }; + C14F83831681326600AAB80A /* String.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = String.js; sourceTree = ""; }; + C14F83841681326600AAB80A /* Testrun.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = Testrun.js; sourceTree = ""; }; + C14F83851681326600AAB80A /* frame.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = frame.js; sourceTree = ""; }; + C14F83871681326600AAB80A /* FOODOC.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = FOODOC.js; sourceTree = ""; }; + C14F83891681326600AAB80A /* DomReader.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = DomReader.js; sourceTree = ""; }; + C14F838A1681326600AAB80A /* XMLDoc.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = XMLDoc.js; sourceTree = ""; }; + C14F838B1681326600AAB80A /* XMLParse.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = XMLParse.js; sourceTree = ""; }; + C14F838C1681326600AAB80A /* XMLDOC.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = XMLDOC.js; sourceTree = ""; }; + C14F838F1681326600AAB80A /* DocComment.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = DocComment.js; sourceTree = ""; }; + C14F83901681326600AAB80A /* DocTag.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = DocTag.js; sourceTree = ""; }; + C14F83911681326600AAB80A /* JsDoc.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = JsDoc.js; sourceTree = ""; }; + C14F83921681326600AAB80A /* JsPlate.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = JsPlate.js; sourceTree = ""; }; + C14F83931681326600AAB80A /* Lang.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = Lang.js; sourceTree = ""; }; + C14F83941681326600AAB80A /* Parser.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = Parser.js; sourceTree = ""; }; + C14F83951681326600AAB80A /* PluginManager.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = PluginManager.js; sourceTree = ""; }; + C14F83961681326600AAB80A /* Symbol.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = Symbol.js; sourceTree = ""; }; + C14F83971681326600AAB80A /* SymbolSet.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = SymbolSet.js; sourceTree = ""; }; + C14F83981681326600AAB80A /* TextStream.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = TextStream.js; sourceTree = ""; }; + C14F83991681326600AAB80A /* Token.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = Token.js; sourceTree = ""; }; + C14F839A1681326600AAB80A /* TokenReader.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = TokenReader.js; sourceTree = ""; }; + C14F839B1681326600AAB80A /* TokenStream.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = TokenStream.js; sourceTree = ""; }; + C14F839C1681326600AAB80A /* Util.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = Util.js; sourceTree = ""; }; + C14F839D1681326600AAB80A /* Walker.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = Walker.js; sourceTree = ""; }; + C14F839E1681326600AAB80A /* JSDOC.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = JSDOC.js; sourceTree = ""; }; + C14F839F1681326600AAB80A /* main.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = main.js; sourceTree = ""; }; + C14F83A11681326600AAB80A /* commentSrcJson.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = commentSrcJson.js; sourceTree = ""; }; + C14F83A21681326600AAB80A /* frameworkPrototype.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = frameworkPrototype.js; sourceTree = ""; }; + C14F83A31681326600AAB80A /* functionCall.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = functionCall.js; sourceTree = ""; }; + C14F83A41681326600AAB80A /* publishSrcHilite.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = publishSrcHilite.js; sourceTree = ""; }; + C14F83A51681326600AAB80A /* symbolLink.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = symbolLink.js; sourceTree = ""; }; + C14F83A61681326600AAB80A /* tagParamConfig.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = tagParamConfig.js; sourceTree = ""; }; + C14F83A71681326600AAB80A /* tagSynonyms.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = tagSynonyms.js; sourceTree = ""; }; + C14F83A81681326600AAB80A /* run.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = run.js; sourceTree = ""; }; + C14F83AA1681326600AAB80A /* runner.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = runner.js; sourceTree = ""; }; + C14F83AB1681326600AAB80A /* TestDoc.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = TestDoc.js; sourceTree = ""; }; + C14F83AD1681326600AAB80A /* addon.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = addon.js; sourceTree = ""; }; + C14F83AE1681326600AAB80A /* anon_inner.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = anon_inner.js; sourceTree = ""; }; + C14F83AF1681326600AAB80A /* augments.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = augments.js; sourceTree = ""; }; + C14F83B01681326600AAB80A /* augments2.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = augments2.js; sourceTree = ""; }; + C14F83B11681326600AAB80A /* borrows.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = borrows.js; sourceTree = ""; }; + C14F83B21681326600AAB80A /* borrows2.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = borrows2.js; sourceTree = ""; }; + C14F83B31681326600AAB80A /* config.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = config.js; sourceTree = ""; }; + C14F83B41681326600AAB80A /* constructs.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = constructs.js; sourceTree = ""; }; + C14F83B51681326600AAB80A /* encoding.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = encoding.js; sourceTree = ""; }; + C14F83B61681326600AAB80A /* encoding_other.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = encoding_other.js; sourceTree = ""; }; + C14F83B71681326600AAB80A /* event.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = event.js; sourceTree = ""; }; + C14F83B81681326600AAB80A /* exports.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = exports.js; sourceTree = ""; }; + C14F83B91681326600AAB80A /* functions_anon.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = functions_anon.js; sourceTree = ""; }; + C14F83BA1681326600AAB80A /* functions_nested.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = functions_nested.js; sourceTree = ""; }; + C14F83BB1681326600AAB80A /* global.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = global.js; sourceTree = ""; }; + C14F83BC1681326600AAB80A /* globals.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = globals.js; sourceTree = ""; }; + C14F83BD1681326600AAB80A /* ignore.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = ignore.js; sourceTree = ""; }; + C14F83BE1681326600AAB80A /* inner.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = inner.js; sourceTree = ""; }; + C14F83BF1681326600AAB80A /* jsdoc_test.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = jsdoc_test.js; sourceTree = ""; }; + C14F83C01681326600AAB80A /* lend.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = lend.js; sourceTree = ""; }; + C14F83C11681326600AAB80A /* memberof.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = memberof.js; sourceTree = ""; }; + C14F83C21681326600AAB80A /* memberof2.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = memberof2.js; sourceTree = ""; }; + C14F83C31681326600AAB80A /* memberof3.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = memberof3.js; sourceTree = ""; }; + C14F83C41681326600AAB80A /* memberof_constructor.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = memberof_constructor.js; sourceTree = ""; }; + C14F83C51681326600AAB80A /* module.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = module.js; sourceTree = ""; }; + C14F83C61681326600AAB80A /* multi_methods.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = multi_methods.js; sourceTree = ""; }; + C14F83C71681326600AAB80A /* name.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = name.js; sourceTree = ""; }; + C14F83C81681326600AAB80A /* namespace_nested.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = namespace_nested.js; sourceTree = ""; }; + C14F83C91681326600AAB80A /* nocode.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = nocode.js; sourceTree = ""; }; + C14F83CA1681326600AAB80A /* oblit_anon.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = oblit_anon.js; sourceTree = ""; }; + C14F83CB1681326600AAB80A /* overview.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = overview.js; sourceTree = ""; }; + C14F83CC1681326600AAB80A /* param_inline.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = param_inline.js; sourceTree = ""; }; + C14F83CD1681326600AAB80A /* params_optional.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = params_optional.js; sourceTree = ""; }; + C14F83CE1681326600AAB80A /* prototype.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = prototype.js; sourceTree = ""; }; + C14F83CF1681326600AAB80A /* prototype_nested.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = prototype_nested.js; sourceTree = ""; }; + C14F83D01681326600AAB80A /* prototype_oblit.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = prototype_oblit.js; sourceTree = ""; }; + C14F83D11681326600AAB80A /* prototype_oblit_constructor.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = prototype_oblit_constructor.js; sourceTree = ""; }; + C14F83D21681326600AAB80A /* public.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = public.js; sourceTree = ""; }; + C14F83D41681326600AAB80A /* code.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = code.js; sourceTree = ""; }; + C14F83D51681326600AAB80A /* notcode.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = notcode.txt; sourceTree = ""; }; + C14F83D61681326600AAB80A /* shared.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = shared.js; sourceTree = ""; }; + C14F83D71681326600AAB80A /* shared2.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = shared2.js; sourceTree = ""; }; + C14F83D81681326600AAB80A /* shortcuts.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = shortcuts.js; sourceTree = ""; }; + C14F83D91681326600AAB80A /* static_this.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = static_this.js; sourceTree = ""; }; + C14F83DA1681326600AAB80A /* synonyms.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = synonyms.js; sourceTree = ""; }; + C14F83DB1681326600AAB80A /* tosource.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = tosource.js; sourceTree = ""; }; + C14F83DC1681326600AAB80A /* variable_redefine.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = variable_redefine.js; sourceTree = ""; }; + C14F83DD1681326600AAB80A /* test.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = test.js; sourceTree = ""; }; + C14F83DE1681326600AAB80A /* changes.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = changes.txt; sourceTree = ""; }; + C14F83E01681326600AAB80A /* sample.conf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = sample.conf; sourceTree = ""; }; + C14F83E21681326600AAB80A /* build.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = build.xml; sourceTree = ""; }; + C14F83E31681326600AAB80A /* build_1.4.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = build_1.4.xml; sourceTree = ""; }; + C14F83E51681326600AAB80A /* js.jar */ = {isa = PBXFileReference; lastKnownFileType = archive.jar; path = js.jar; sourceTree = ""; }; + C14F83E71681326600AAB80A /* JsDebugRun.java */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.java; path = JsDebugRun.java; sourceTree = ""; }; + C14F83E81681326600AAB80A /* JsRun.java */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.java; path = JsRun.java; sourceTree = ""; }; + C14F83E91681326600AAB80A /* jsdebug.jar */ = {isa = PBXFileReference; lastKnownFileType = archive.jar; path = jsdebug.jar; sourceTree = ""; }; + C14F83EA1681326600AAB80A /* jsrun.jar */ = {isa = PBXFileReference; lastKnownFileType = archive.jar; path = jsrun.jar; sourceTree = ""; }; + C14F83EB1681326600AAB80A /* jsrun.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = jsrun.sh; sourceTree = ""; }; + C14F83EC1681326600AAB80A /* README.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = README.txt; sourceTree = ""; }; + C14F83EF1681326600AAB80A /* allclasses.tmpl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = allclasses.tmpl; sourceTree = ""; }; + C14F83F01681326600AAB80A /* allfiles.tmpl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = allfiles.tmpl; sourceTree = ""; }; + C14F83F11681326600AAB80A /* class.tmpl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = class.tmpl; sourceTree = ""; }; + C14F83F31681326600AAB80A /* default.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; path = default.css; sourceTree = ""; }; + C14F83F41681326600AAB80A /* index.tmpl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = index.tmpl; sourceTree = ""; }; + C14F83F51681326600AAB80A /* publish.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = publish.js; sourceTree = ""; }; + C14F83F71681326600AAB80A /* header.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = header.html; sourceTree = ""; }; + C14F83F81681326600AAB80A /* index.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = index.html; sourceTree = ""; }; + C14F83F91681326600AAB80A /* symbol.tmpl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = symbol.tmpl; sourceTree = ""; }; + C14F83FB1681326600AAB80A /* coding_guidelines.pl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.perl; path = coding_guidelines.pl; sourceTree = ""; }; + C14F83FC1681326600AAB80A /* jslint_rhino.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = jslint_rhino.js; sourceTree = ""; }; + C14F83FD1681326600AAB80A /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = ""; }; + C14F83FF1681326600AAB80A /* bsd.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = bsd.txt; sourceTree = ""; }; + C14F84001681326600AAB80A /* COPYRIGHT */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = COPYRIGHT; sourceTree = ""; }; + C14F84011681326600AAB80A /* gpl-2.0.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "gpl-2.0.txt"; sourceTree = ""; }; + C14F84021681326600AAB80A /* gpl-3.0.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "gpl-3.0.txt"; sourceTree = ""; }; + C14F84031681326600AAB80A /* INSTALL */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = INSTALL; sourceTree = ""; }; + C14F84041681326600AAB80A /* sjcl.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = sjcl.js; sourceTree = ""; }; + C14F84061681326600AAB80A /* aes_test.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = aes_test.js; sourceTree = ""; }; + C14F84071681326600AAB80A /* aes_vectors.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = aes_vectors.js; sourceTree = ""; }; + C14F84081681326600AAB80A /* bn_test.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = bn_test.js; sourceTree = ""; }; + C14F84091681326600AAB80A /* bn_vectors.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = bn_vectors.js; sourceTree = ""; }; + C14F840A1681326600AAB80A /* cbc_test.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = cbc_test.js; sourceTree = ""; }; + C14F840B1681326600AAB80A /* cbc_vectors.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = cbc_vectors.js; sourceTree = ""; }; + C14F840C1681326600AAB80A /* ccm_test.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = ccm_test.js; sourceTree = ""; }; + C14F840D1681326600AAB80A /* ccm_vectors.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = ccm_vectors.js; sourceTree = ""; }; + C14F840E1681326600AAB80A /* ecdh_test.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = ecdh_test.js; sourceTree = ""; }; + C14F840F1681326600AAB80A /* ecdsa_test.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = ecdsa_test.js; sourceTree = ""; }; + C14F84101681326600AAB80A /* hmac_test.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = hmac_test.js; sourceTree = ""; }; + C14F84111681326600AAB80A /* hmac_vectors.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = hmac_vectors.js; sourceTree = ""; }; + C14F84121681326600AAB80A /* ocb2_test.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = ocb2_test.js; sourceTree = ""; }; + C14F84131681326600AAB80A /* ocb2_vectors.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = ocb2_vectors.js; sourceTree = ""; }; + C14F84141681326600AAB80A /* pbkdf2_test.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = pbkdf2_test.js; sourceTree = ""; }; + C14F84151681326600AAB80A /* run_tests_browser.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = run_tests_browser.js; sourceTree = ""; }; + C14F84161681326600AAB80A /* run_tests_rhino.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = run_tests_rhino.js; sourceTree = ""; }; + C14F84171681326600AAB80A /* sha1_test.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = sha1_test.js; sourceTree = ""; }; + C14F84181681326600AAB80A /* sha1_vectors.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = sha1_vectors.js; sourceTree = ""; }; + C14F84191681326600AAB80A /* sha256_test.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = sha256_test.js; sourceTree = ""; }; + C14F841A1681326600AAB80A /* sha256_test_brute_force.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = sha256_test_brute_force.js; sourceTree = ""; }; + C14F841B1681326600AAB80A /* sha256_vectors.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = sha256_vectors.js; sourceTree = ""; }; + C14F841C1681326600AAB80A /* srp_test.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = srp_test.js; sourceTree = ""; }; + C14F841D1681326600AAB80A /* srp_vectors.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = srp_vectors.js; sourceTree = ""; }; + C14F841E1681326600AAB80A /* test.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = test.js; sourceTree = ""; }; + C14F841F1681326600AAB80A /* utils.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = utils.js; sourceTree = ""; }; + C14F84201681326600AAB80A /* utils.web.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = utils.web.js; sourceTree = ""; }; + C1637B0216827C5B0067A9B4 /* ripple.pb.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ripple.pb.cc; sourceTree = ""; }; + C1637B0316827C5B0067A9B4 /* ripple.pb.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ripple.pb.h; sourceTree = ""; }; + C1637B05168284510067A9B4 /* TransactionQueue.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TransactionQueue.cpp; sourceTree = ""; }; + C1637B06168284510067A9B4 /* TransactionQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TransactionQueue.h; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + C14F81241681323400AAB80A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + C10365CA168147DF00D9D15C /* build */ = { + isa = PBXGroup; + children = ( + C1637B0116827C5B0067A9B4 /* proto */, + ); + path = build; + sourceTree = ""; + }; + C14F811C1681323300AAB80A = { + isa = PBXGroup; + children = ( + C14F812D1681323400AAB80A /* rippled.1 */, + C14F81341681326600AAB80A /* src */, + C10365CA168147DF00D9D15C /* build */, + C14F81281681323400AAB80A /* Products */, + ); + sourceTree = ""; + }; + C14F81281681323400AAB80A /* Products */ = { + isa = PBXGroup; + children = ( + C14F81271681323400AAB80A /* rippled */, + ); + name = Products; + sourceTree = ""; + }; + C14F81341681326600AAB80A /* src */ = { + isa = PBXGroup; + children = ( + C14F81351681326600AAB80A /* cpp */, + C14F832E1681326600AAB80A /* js */, + ); + path = src; + sourceTree = ""; + }; + C14F81351681326600AAB80A /* cpp */ = { + isa = PBXGroup; + children = ( + C14F81361681326600AAB80A /* database */, + C14F81451681326600AAB80A /* json */, + C14F81571681326600AAB80A /* ripple */, + C14F82151681326600AAB80A /* websocketpp */, + ); + path = cpp; + sourceTree = ""; + }; + C14F81361681326600AAB80A /* database */ = { + isa = PBXGroup; + children = ( + C14F81371681326600AAB80A /* database.cpp */, + C14F81381681326600AAB80A /* database.h */, + C14F81391681326600AAB80A /* linux */, + C14F813C1681326600AAB80A /* sqlite3.c */, + C14F813D1681326600AAB80A /* sqlite3.h */, + C14F813E1681326600AAB80A /* sqlite3ext.h */, + C14F813F1681326600AAB80A /* SqliteDatabase.cpp */, + C14F81401681326600AAB80A /* SqliteDatabase.h */, + C14F81411681326600AAB80A /* win */, + ); + path = database; + sourceTree = ""; + }; + C14F81391681326600AAB80A /* linux */ = { + isa = PBXGroup; + children = ( + C14F813A1681326600AAB80A /* mysqldatabase.cpp */, + C14F813B1681326600AAB80A /* mysqldatabase.h */, + ); + path = linux; + sourceTree = ""; + }; + C14F81411681326600AAB80A /* win */ = { + isa = PBXGroup; + children = ( + C14F81421681326600AAB80A /* dbutility.h */, + C14F81431681326600AAB80A /* windatabase.cpp */, + C14F81441681326600AAB80A /* windatabase.h */, + ); + path = win; + sourceTree = ""; + }; + C14F81451681326600AAB80A /* json */ = { + isa = PBXGroup; + children = ( + C14F81461681326600AAB80A /* autolink.h */, + C14F81471681326600AAB80A /* config.h */, + C14F81481681326600AAB80A /* features.h */, + C14F81491681326600AAB80A /* forwards.h */, + C14F814A1681326600AAB80A /* json.h */, + C14F814B1681326600AAB80A /* json_batchallocator.h */, + C14F814C1681326600AAB80A /* json_internalarray.inl */, + C14F814D1681326600AAB80A /* json_internalmap.inl */, + C14F814E1681326600AAB80A /* json_reader.cpp */, + C14F814F1681326600AAB80A /* json_value.cpp */, + C14F81501681326600AAB80A /* json_valueiterator.inl */, + C14F81511681326600AAB80A /* json_writer.cpp */, + C14F81521681326600AAB80A /* LICENSE */, + C14F81531681326600AAB80A /* reader.h */, + C14F81541681326600AAB80A /* value.h */, + C14F81551681326600AAB80A /* version */, + C14F81561681326600AAB80A /* writer.h */, + ); + path = json; + sourceTree = ""; + }; + C14F81571681326600AAB80A /* ripple */ = { + isa = PBXGroup; + children = ( + C1637B05168284510067A9B4 /* TransactionQueue.cpp */, + C1637B06168284510067A9B4 /* TransactionQueue.h */, + C14F81581681326600AAB80A /* AccountItems.cpp */, + C14F81591681326600AAB80A /* AccountItems.h */, + C14F815A1681326600AAB80A /* AccountSetTransactor.cpp */, + C14F815B1681326600AAB80A /* AccountSetTransactor.h */, + C14F815C1681326600AAB80A /* AccountState.cpp */, + C14F815D1681326600AAB80A /* AccountState.h */, + C14F815E1681326600AAB80A /* Amount.cpp */, + C14F815F1681326600AAB80A /* Application.cpp */, + C14F81601681326600AAB80A /* Application.h */, + C14F81611681326600AAB80A /* base58.h */, + C14F81621681326600AAB80A /* bignum.h */, + C14F81631681326600AAB80A /* BitcoinUtil.cpp */, + C14F81641681326600AAB80A /* BitcoinUtil.h */, + C14F81651681326600AAB80A /* CallRPC.cpp */, + C14F81661681326600AAB80A /* CallRPC.h */, + C14F81671681326600AAB80A /* CanonicalTXSet.cpp */, + C14F81681681326600AAB80A /* CanonicalTXSet.h */, + C14F81691681326600AAB80A /* Config.cpp */, + C14F816A1681326600AAB80A /* Config.h */, + C14F816B1681326600AAB80A /* ConnectionPool.cpp */, + C14F816C1681326600AAB80A /* ConnectionPool.h */, + C14F816D1681326600AAB80A /* Contract.cpp */, + C14F816E1681326600AAB80A /* Contract.h */, + C14F816F1681326600AAB80A /* DBInit.cpp */, + C14F81701681326600AAB80A /* DeterministicKeys.cpp */, + C14F81711681326600AAB80A /* ECIES.cpp */, + C14F81721681326600AAB80A /* FeatureTable.cpp */, + C14F81731681326600AAB80A /* FeatureTable.h */, + C14F81741681326600AAB80A /* FieldNames.cpp */, + C14F81751681326600AAB80A /* FieldNames.h */, + C14F81761681326600AAB80A /* HashedObject.cpp */, + C14F81771681326600AAB80A /* HashedObject.h */, + C14F81781681326600AAB80A /* HashPrefixes.h */, + C14F81791681326600AAB80A /* HTTPRequest.cpp */, + C14F817A1681326600AAB80A /* HTTPRequest.h */, + C14F817B1681326600AAB80A /* HttpsClient.cpp */, + C14F817C1681326600AAB80A /* HttpsClient.h */, + C14F817D1681326600AAB80A /* InstanceCounter.cpp */, + C14F817E1681326600AAB80A /* InstanceCounter.h */, + C14F817F1681326600AAB80A /* Interpreter.cpp */, + C14F81801681326600AAB80A /* Interpreter.h */, + C14F81811681326600AAB80A /* JobQueue.cpp */, + C14F81821681326600AAB80A /* JobQueue.h */, + C14F81831681326600AAB80A /* key.h */, + C14F81841681326600AAB80A /* Ledger.cpp */, + C14F81851681326600AAB80A /* Ledger.h */, + C14F81861681326600AAB80A /* LedgerAcquire.cpp */, + C14F81871681326600AAB80A /* LedgerAcquire.h */, + C14F81881681326600AAB80A /* LedgerConsensus.cpp */, + C14F81891681326600AAB80A /* LedgerConsensus.h */, + C14F818A1681326600AAB80A /* LedgerEntrySet.cpp */, + C14F818B1681326600AAB80A /* LedgerEntrySet.h */, + C14F818C1681326600AAB80A /* LedgerFormats.cpp */, + C14F818D1681326600AAB80A /* LedgerFormats.h */, + C14F818E1681326600AAB80A /* LedgerHistory.cpp */, + C14F818F1681326600AAB80A /* LedgerHistory.h */, + C14F81901681326600AAB80A /* LedgerMaster.cpp */, + C14F81911681326600AAB80A /* LedgerMaster.h */, + C14F81921681326600AAB80A /* LedgerProposal.cpp */, + C14F81931681326600AAB80A /* LedgerProposal.h */, + C14F81941681326600AAB80A /* LedgerTiming.cpp */, + C14F81951681326600AAB80A /* LedgerTiming.h */, + C14F81961681326600AAB80A /* LoadManager.cpp */, + C14F81971681326600AAB80A /* LoadManager.h */, + C14F81981681326600AAB80A /* LoadMonitor.cpp */, + C14F81991681326600AAB80A /* LoadMonitor.h */, + C14F819A1681326600AAB80A /* Log.cpp */, + C14F819B1681326600AAB80A /* Log.h */, + C14F819C1681326600AAB80A /* main.cpp */, + C14F819D1681326600AAB80A /* NetworkOPs.cpp */, + C14F819E1681326600AAB80A /* NetworkOPs.h */, + C14F819F1681326600AAB80A /* NetworkStatus.h */, + C14F81A01681326600AAB80A /* NicknameState.cpp */, + C14F81A11681326600AAB80A /* NicknameState.h */, + C14F81A21681326600AAB80A /* Offer.cpp */, + C14F81A31681326600AAB80A /* Offer.h */, + C14F81A41681326600AAB80A /* OfferCancelTransactor.cpp */, + C14F81A51681326600AAB80A /* OfferCancelTransactor.h */, + C14F81A61681326600AAB80A /* OfferCreateTransactor.cpp */, + C14F81A71681326600AAB80A /* OfferCreateTransactor.h */, + C14F81A81681326600AAB80A /* Operation.cpp */, + C14F81A91681326600AAB80A /* Operation.h */, + C14F81AA1681326600AAB80A /* OrderBook.cpp */, + C14F81AB1681326600AAB80A /* OrderBook.h */, + C14F81AC1681326600AAB80A /* OrderBookDB.cpp */, + C14F81AD1681326600AAB80A /* OrderBookDB.h */, + C14F81AE1681326600AAB80A /* PackedMessage.cpp */, + C14F81AF1681326600AAB80A /* PackedMessage.h */, + C14F81B01681326600AAB80A /* ParameterTable.cpp */, + C14F81B11681326600AAB80A /* ParameterTable.h */, + C14F81B21681326600AAB80A /* ParseSection.cpp */, + C14F81B31681326600AAB80A /* ParseSection.h */, + C14F81B41681326600AAB80A /* Pathfinder.cpp */, + C14F81B51681326600AAB80A /* Pathfinder.h */, + C14F81B61681326600AAB80A /* PaymentTransactor.cpp */, + C14F81B71681326600AAB80A /* PaymentTransactor.h */, + C14F81B81681326600AAB80A /* Peer.cpp */, + C14F81B91681326600AAB80A /* Peer.h */, + C14F81BA1681326600AAB80A /* PeerDoor.cpp */, + C14F81BB1681326600AAB80A /* PeerDoor.h */, + C14F81BC1681326600AAB80A /* PlatRand.cpp */, + C14F81BD1681326600AAB80A /* ProofOfWork.cpp */, + C14F81BE1681326600AAB80A /* ProofOfWork.h */, + C14F81BF1681326600AAB80A /* PubKeyCache.cpp */, + C14F81C01681326600AAB80A /* PubKeyCache.h */, + C14F81C11681326600AAB80A /* RangeSet.cpp */, + C14F81C21681326600AAB80A /* RangeSet.h */, + C14F81C31681326600AAB80A /* RegularKeySetTransactor.cpp */, + C14F81C41681326600AAB80A /* RegularKeySetTransactor.h */, + C14F81C51681326600AAB80A /* rfc1751.cpp */, + C14F81C61681326600AAB80A /* rfc1751.h */, + C14F81C71681326600AAB80A /* ripple.proto */, + C14F81C81681326600AAB80A /* RippleAddress.cpp */, + C14F81C91681326600AAB80A /* RippleAddress.h */, + C14F81CA1681326600AAB80A /* RippleCalc.cpp */, + C14F81CB1681326600AAB80A /* RippleCalc.h */, + C14F81CC1681326600AAB80A /* RippleState.cpp */, + C14F81CD1681326600AAB80A /* RippleState.h */, + C14F81CE1681326600AAB80A /* rpc.cpp */, + C14F81CF1681326600AAB80A /* RPC.h */, + C14F81D01681326600AAB80A /* RPCDoor.cpp */, + C14F81D11681326600AAB80A /* RPCDoor.h */, + C14F81D21681326600AAB80A /* RPCErr.cpp */, + C14F81D31681326600AAB80A /* RPCErr.h */, + C14F81D41681326600AAB80A /* RPCHandler.cpp */, + C14F81D51681326600AAB80A /* RPCHandler.h */, + C14F81D61681326600AAB80A /* RPCServer.cpp */, + C14F81D71681326600AAB80A /* RPCServer.h */, + C14F81D81681326600AAB80A /* ScopedLock.h */, + C14F81D91681326600AAB80A /* ScriptData.cpp */, + C14F81DA1681326600AAB80A /* ScriptData.h */, + C14F81DB1681326600AAB80A /* SecureAllocator.h */, + C14F81DC1681326600AAB80A /* SerializedLedger.cpp */, + C14F81DD1681326600AAB80A /* SerializedLedger.h */, + C14F81DE1681326600AAB80A /* SerializedObject.cpp */, + C14F81DF1681326600AAB80A /* SerializedObject.h */, + C14F81E01681326600AAB80A /* SerializedTransaction.cpp */, + C14F81E11681326600AAB80A /* SerializedTransaction.h */, + C14F81E21681326600AAB80A /* SerializedTypes.cpp */, + C14F81E31681326600AAB80A /* SerializedTypes.h */, + C14F81E41681326600AAB80A /* SerializedValidation.cpp */, + C14F81E51681326600AAB80A /* SerializedValidation.h */, + C14F81E61681326600AAB80A /* SerializeProto.h */, + C14F81E71681326600AAB80A /* Serializer.cpp */, + C14F81E81681326600AAB80A /* Serializer.h */, + C14F81E91681326600AAB80A /* SHAMap.cpp */, + C14F81EA1681326600AAB80A /* SHAMap.h */, + C14F81EB1681326600AAB80A /* SHAMapDiff.cpp */, + C14F81EC1681326600AAB80A /* SHAMapNodes.cpp */, + C14F81ED1681326600AAB80A /* SHAMapSync.cpp */, + C14F81EE1681326600AAB80A /* SHAMapSync.h */, + C14F81EF1681326600AAB80A /* SNTPClient.cpp */, + C14F81F01681326600AAB80A /* SNTPClient.h */, + C14F81F11681326600AAB80A /* Suppression.cpp */, + C14F81F21681326600AAB80A /* Suppression.h */, + C14F81F31681326600AAB80A /* TaggedCache.h */, + C14F81F41681326600AAB80A /* Transaction.cpp */, + C14F81F51681326600AAB80A /* Transaction.h */, + C14F81F61681326600AAB80A /* TransactionEngine.cpp */, + C14F81F71681326600AAB80A /* TransactionEngine.h */, + C14F81F81681326600AAB80A /* TransactionErr.cpp */, + C14F81F91681326600AAB80A /* TransactionErr.h */, + C14F81FA1681326600AAB80A /* TransactionFormats.cpp */, + C14F81FB1681326600AAB80A /* TransactionFormats.h */, + C14F81FC1681326600AAB80A /* TransactionMaster.cpp */, + C14F81FD1681326600AAB80A /* TransactionMaster.h */, + C14F81FE1681326600AAB80A /* TransactionMeta.cpp */, + C14F81FF1681326600AAB80A /* TransactionMeta.h */, + C14F82001681326600AAB80A /* Transactor.cpp */, + C14F82011681326600AAB80A /* Transactor.h */, + C14F82021681326600AAB80A /* TrustSetTransactor.cpp */, + C14F82031681326600AAB80A /* TrustSetTransactor.h */, + C14F82041681326600AAB80A /* types.h */, + C14F82051681326600AAB80A /* uint256.h */, + C14F82061681326600AAB80A /* UniqueNodeList.cpp */, + C14F82071681326600AAB80A /* UniqueNodeList.h */, + C14F82081681326600AAB80A /* utils.cpp */, + C14F82091681326600AAB80A /* utils.h */, + C14F820A1681326600AAB80A /* ValidationCollection.cpp */, + C14F820B1681326600AAB80A /* ValidationCollection.h */, + C14F820C1681326600AAB80A /* Version.h */, + C14F820D1681326600AAB80A /* Wallet.cpp */, + C14F820E1681326600AAB80A /* Wallet.h */, + C14F820F1681326600AAB80A /* WalletAddTransactor.cpp */, + C14F82101681326600AAB80A /* WalletAddTransactor.h */, + C14F82111681326600AAB80A /* WSConnection.h */, + C14F82121681326600AAB80A /* WSDoor.cpp */, + C14F82131681326600AAB80A /* WSDoor.h */, + C14F82141681326600AAB80A /* WSHandler.h */, + ); + path = ripple; + sourceTree = ""; + }; + C14F82151681326600AAB80A /* websocketpp */ = { + isa = PBXGroup; + children = ( + C14F82161681326600AAB80A /* dependencies.txt */, + C14F82171681326600AAB80A /* doc */, + C14F82191681326600AAB80A /* examples */, + C14F82BC1681326600AAB80A /* license.txt */, + C14F82BD1681326600AAB80A /* Makefile */, + C14F82BE1681326600AAB80A /* readme.txt */, + C14F82BF1681326600AAB80A /* SConstruct */, + C14F82C01681326600AAB80A /* src */, + C14F83001681326600AAB80A /* test */, + C14F83071681326600AAB80A /* todo.txt */, + C14F83081681326600AAB80A /* websocketpp.bbprojectd */, + C14F830D1681326600AAB80A /* websocketpp.xcodeproj */, + C14F83101681326600AAB80A /* windows */, + ); + path = websocketpp; + sourceTree = ""; + }; + C14F82171681326600AAB80A /* doc */ = { + isa = PBXGroup; + children = ( + C14F82181681326600AAB80A /* uri.txt */, + ); + path = doc; + sourceTree = ""; + }; + C14F82191681326600AAB80A /* examples */ = { + isa = PBXGroup; + children = ( + C14F821A1681326600AAB80A /* broadcast_server_tls */, + C14F826F1681326600AAB80A /* chat_client */, + C14F82781681326600AAB80A /* chat_server */, + C14F827E1681326600AAB80A /* common.mk */, + C14F827F1681326600AAB80A /* concurrent_server */, + C14F82841681326600AAB80A /* echo_client */, + C14F82881681326600AAB80A /* echo_server */, + C14F828D1681326600AAB80A /* echo_server_tls */, + C14F82941681326600AAB80A /* fuzzing_client */, + C14F82971681326600AAB80A /* fuzzing_server_tls */, + C14F829B1681326600AAB80A /* Makefile */, + C14F829C1681326600AAB80A /* stress_client */, + C14F829F1681326600AAB80A /* telemetry_server */, + C14F82A31681326600AAB80A /* wsperf */, + ); + path = examples; + sourceTree = ""; + }; + C14F821A1681326600AAB80A /* broadcast_server_tls */ = { + isa = PBXGroup; + children = ( + C14F821B1681326600AAB80A /* broadcast_admin.html */, + C14F821C1681326600AAB80A /* broadcast_admin_handler.hpp */, + C14F821D1681326600AAB80A /* broadcast_handler.hpp */, + C14F821E1681326600AAB80A /* broadcast_server_handler.hpp */, + C14F821F1681326600AAB80A /* broadcast_server_tls.cpp */, + C14F82201681326600AAB80A /* Makefile */, + C14F82211681326600AAB80A /* vendor */, + C14F826E1681326600AAB80A /* wscmd.hpp */, + ); + path = broadcast_server_tls; + sourceTree = ""; + }; + C14F82211681326600AAB80A /* vendor */ = { + isa = PBXGroup; + children = ( + C14F82221681326600AAB80A /* flot */, + C14F826D1681326600AAB80A /* md5.js */, + ); + path = vendor; + sourceTree = ""; + }; + C14F82221681326600AAB80A /* flot */ = { + isa = PBXGroup; + children = ( + C14F82231681326600AAB80A /* API.txt */, + C14F82241681326600AAB80A /* examples */, + C14F824B1681326600AAB80A /* excanvas.js */, + C14F824C1681326600AAB80A /* excanvas.min.js */, + C14F824D1681326600AAB80A /* FAQ.txt */, + C14F824E1681326600AAB80A /* jquery.colorhelpers.js */, + C14F824F1681326600AAB80A /* jquery.colorhelpers.min.js */, + C14F82501681326600AAB80A /* jquery.flot.crosshair.js */, + C14F82511681326600AAB80A /* jquery.flot.crosshair.min.js */, + C14F82521681326600AAB80A /* jquery.flot.fillbetween.js */, + C14F82531681326600AAB80A /* jquery.flot.fillbetween.min.js */, + C14F82541681326600AAB80A /* jquery.flot.image.js */, + C14F82551681326600AAB80A /* jquery.flot.image.min.js */, + C14F82561681326600AAB80A /* jquery.flot.js */, + C14F82571681326600AAB80A /* jquery.flot.min.js */, + C14F82581681326600AAB80A /* jquery.flot.navigate.js */, + C14F82591681326600AAB80A /* jquery.flot.navigate.min.js */, + C14F825A1681326600AAB80A /* jquery.flot.pie.js */, + C14F825B1681326600AAB80A /* jquery.flot.pie.min.js */, + C14F825C1681326600AAB80A /* jquery.flot.resize.js */, + C14F825D1681326600AAB80A /* jquery.flot.resize.min.js */, + C14F825E1681326600AAB80A /* jquery.flot.selection.js */, + C14F825F1681326600AAB80A /* jquery.flot.selection.min.js */, + C14F82601681326600AAB80A /* jquery.flot.stack.js */, + C14F82611681326600AAB80A /* jquery.flot.stack.min.js */, + C14F82621681326600AAB80A /* jquery.flot.symbol.js */, + C14F82631681326600AAB80A /* jquery.flot.symbol.min.js */, + C14F82641681326600AAB80A /* jquery.flot.threshold.js */, + C14F82651681326600AAB80A /* jquery.flot.threshold.min.js */, + C14F82661681326600AAB80A /* jquery.js */, + C14F82671681326600AAB80A /* jquery.min.js */, + C14F82681681326600AAB80A /* LICENSE.txt */, + C14F82691681326600AAB80A /* Makefile */, + C14F826A1681326600AAB80A /* NEWS.txt */, + C14F826B1681326600AAB80A /* PLUGINS.txt */, + C14F826C1681326600AAB80A /* README.txt */, + ); + path = flot; + sourceTree = ""; + }; + C14F82241681326600AAB80A /* examples */ = { + isa = PBXGroup; + children = ( + C14F82251681326600AAB80A /* ajax.html */, + C14F82261681326600AAB80A /* annotating.html */, + C14F82271681326600AAB80A /* arrow-down.gif */, + C14F82281681326600AAB80A /* arrow-left.gif */, + C14F82291681326600AAB80A /* arrow-right.gif */, + C14F822A1681326600AAB80A /* arrow-up.gif */, + C14F822B1681326600AAB80A /* basic.html */, + C14F822C1681326600AAB80A /* data-eu-gdp-growth-1.json */, + C14F822D1681326600AAB80A /* data-eu-gdp-growth-2.json */, + C14F822E1681326600AAB80A /* data-eu-gdp-growth-3.json */, + C14F822F1681326600AAB80A /* data-eu-gdp-growth-4.json */, + C14F82301681326600AAB80A /* data-eu-gdp-growth-5.json */, + C14F82311681326600AAB80A /* data-eu-gdp-growth.json */, + C14F82321681326600AAB80A /* data-japan-gdp-growth.json */, + C14F82331681326600AAB80A /* data-usa-gdp-growth.json */, + C14F82341681326600AAB80A /* graph-types.html */, + C14F82351681326600AAB80A /* hs-2004-27-a-large_web.jpg */, + C14F82361681326600AAB80A /* image.html */, + C14F82371681326600AAB80A /* index.html */, + C14F82381681326600AAB80A /* interacting-axes.html */, + C14F82391681326600AAB80A /* interacting.html */, + C14F823A1681326600AAB80A /* layout.css */, + C14F823B1681326600AAB80A /* multiple-axes.html */, + C14F823C1681326600AAB80A /* navigate.html */, + C14F823D1681326600AAB80A /* percentiles.html */, + C14F823E1681326600AAB80A /* pie.html */, + C14F823F1681326600AAB80A /* realtime.html */, + C14F82401681326600AAB80A /* resize.html */, + C14F82411681326600AAB80A /* selection.html */, + C14F82421681326600AAB80A /* setting-options.html */, + C14F82431681326600AAB80A /* stacking.html */, + C14F82441681326600AAB80A /* symbols.html */, + C14F82451681326600AAB80A /* thresholding.html */, + C14F82461681326600AAB80A /* time.html */, + C14F82471681326600AAB80A /* tracking.html */, + C14F82481681326600AAB80A /* turning-series.html */, + C14F82491681326600AAB80A /* visitors.html */, + C14F824A1681326600AAB80A /* zooming.html */, + ); + path = examples; + sourceTree = ""; + }; + C14F826F1681326600AAB80A /* chat_client */ = { + isa = PBXGroup; + children = ( + C14F82701681326600AAB80A /* chat_client.cpp */, + C14F82711681326600AAB80A /* chat_client.html */, + C14F82721681326600AAB80A /* chat_client_handler.cpp */, + C14F82731681326600AAB80A /* chat_client_handler.hpp */, + C14F82741681326600AAB80A /* Makefile */, + C14F82751681326600AAB80A /* SConscript */, + C14F82761681326600AAB80A /* vendor */, + ); + path = chat_client; + sourceTree = ""; + }; + C14F82761681326600AAB80A /* vendor */ = { + isa = PBXGroup; + children = ( + C14F82771681326600AAB80A /* jquery-1.6.3.min.js */, + ); + path = vendor; + sourceTree = ""; + }; + C14F82781681326600AAB80A /* chat_server */ = { + isa = PBXGroup; + children = ( + C14F82791681326600AAB80A /* chat.cpp */, + C14F827A1681326600AAB80A /* chat.hpp */, + C14F827B1681326600AAB80A /* chat_server.cpp */, + C14F827C1681326600AAB80A /* Makefile */, + C14F827D1681326600AAB80A /* SConscript */, + ); + path = chat_server; + sourceTree = ""; + }; + C14F827F1681326600AAB80A /* concurrent_server */ = { + isa = PBXGroup; + children = ( + C14F82801681326600AAB80A /* concurrent_client.html */, + C14F82811681326600AAB80A /* concurrent_server.cpp */, + C14F82821681326600AAB80A /* Makefile */, + C14F82831681326600AAB80A /* SConscript */, + ); + path = concurrent_server; + sourceTree = ""; + }; + C14F82841681326600AAB80A /* echo_client */ = { + isa = PBXGroup; + children = ( + C14F82851681326600AAB80A /* echo_client.cpp */, + C14F82861681326600AAB80A /* Makefile */, + C14F82871681326600AAB80A /* SConscript */, + ); + path = echo_client; + sourceTree = ""; + }; + C14F82881681326600AAB80A /* echo_server */ = { + isa = PBXGroup; + children = ( + C14F82891681326600AAB80A /* echo_client.html */, + C14F828A1681326600AAB80A /* echo_server.cpp */, + C14F828B1681326600AAB80A /* Makefile */, + C14F828C1681326600AAB80A /* SConscript */, + ); + path = echo_server; + sourceTree = ""; + }; + C14F828D1681326600AAB80A /* echo_server_tls */ = { + isa = PBXGroup; + children = ( + C14F828E1681326600AAB80A /* echo.cpp */, + C14F828F1681326600AAB80A /* echo.hpp */, + C14F82901681326600AAB80A /* echo_client.html */, + C14F82911681326600AAB80A /* echo_server_tls.cpp */, + C14F82921681326600AAB80A /* Makefile */, + C14F82931681326600AAB80A /* SConscript */, + ); + path = echo_server_tls; + sourceTree = ""; + }; + C14F82941681326600AAB80A /* fuzzing_client */ = { + isa = PBXGroup; + children = ( + C14F82951681326600AAB80A /* fuzzing_client.cpp */, + C14F82961681326600AAB80A /* Makefile */, + ); + path = fuzzing_client; + sourceTree = ""; + }; + C14F82971681326600AAB80A /* fuzzing_server_tls */ = { + isa = PBXGroup; + children = ( + C14F82981681326600AAB80A /* echo_client.html */, + C14F82991681326600AAB80A /* fuzzing_server_tls.cpp */, + C14F829A1681326600AAB80A /* Makefile */, + ); + path = fuzzing_server_tls; + sourceTree = ""; + }; + C14F829C1681326600AAB80A /* stress_client */ = { + isa = PBXGroup; + children = ( + C14F829D1681326600AAB80A /* Makefile */, + C14F829E1681326600AAB80A /* stress_client.cpp */, + ); + path = stress_client; + sourceTree = ""; + }; + C14F829F1681326600AAB80A /* telemetry_server */ = { + isa = PBXGroup; + children = ( + C14F82A01681326600AAB80A /* Makefile */, + C14F82A11681326600AAB80A /* SConscript */, + C14F82A21681326600AAB80A /* telemetry_server.cpp */, + ); + path = telemetry_server; + sourceTree = ""; + }; + C14F82A31681326600AAB80A /* wsperf */ = { + isa = PBXGroup; + children = ( + C14F82A41681326600AAB80A /* case.cpp */, + C14F82A51681326600AAB80A /* case.hpp */, + C14F82A61681326600AAB80A /* generic.cpp */, + C14F82A71681326600AAB80A /* generic.hpp */, + C14F82A81681326600AAB80A /* Makefile */, + C14F82A91681326600AAB80A /* message_test.html */, + C14F82AA1681326600AAB80A /* request.cpp */, + C14F82AB1681326600AAB80A /* request.hpp */, + C14F82AC1681326600AAB80A /* SConscript */, + C14F82AD1681326600AAB80A /* stress_aggregate.cpp */, + C14F82AE1681326600AAB80A /* stress_aggregate.hpp */, + C14F82AF1681326600AAB80A /* stress_handler.cpp */, + C14F82B01681326600AAB80A /* stress_handler.hpp */, + C14F82B11681326600AAB80A /* stress_test.html */, + C14F82B21681326600AAB80A /* vendor */, + C14F82B71681326600AAB80A /* wscmd.cpp */, + C14F82B81681326600AAB80A /* wscmd.hpp */, + C14F82B91681326600AAB80A /* wsperf.cfg */, + C14F82BA1681326600AAB80A /* wsperf.cpp */, + C14F82BB1681326600AAB80A /* wsperf_commander.html */, + ); + path = wsperf; + sourceTree = ""; + }; + C14F82B21681326600AAB80A /* vendor */ = { + isa = PBXGroup; + children = ( + C14F82B31681326600AAB80A /* backbone-localstorage.js */, + C14F82B41681326600AAB80A /* backbone.js */, + C14F82B51681326600AAB80A /* jquery.min.js */, + C14F82B61681326600AAB80A /* underscore.js */, + ); + path = vendor; + sourceTree = ""; + }; + C14F82C01681326600AAB80A /* src */ = { + isa = PBXGroup; + children = ( + C14F82C11681326600AAB80A /* base64 */, + C14F82C41681326600AAB80A /* common.hpp */, + C14F82C51681326600AAB80A /* connection.hpp */, + C14F82C61681326600AAB80A /* endpoint.hpp */, + C14F82C71681326600AAB80A /* http */, + C14F82CA1681326600AAB80A /* logger */, + C14F82CC1681326600AAB80A /* md5 */, + C14F82D01681326600AAB80A /* messages */, + C14F82D41681326600AAB80A /* network_utilities.cpp */, + C14F82D51681326600AAB80A /* network_utilities.hpp */, + C14F82D61681326600AAB80A /* processors */, + C14F82DE1681326600AAB80A /* rng */, + C14F82E31681326600AAB80A /* roles */, + C14F82E61681326600AAB80A /* SConscript */, + C14F82E71681326600AAB80A /* sha1 */, + C14F82F01681326600AAB80A /* shared_const_buffer.hpp */, + C14F82F11681326600AAB80A /* sockets */, + C14F82F51681326600AAB80A /* ssl */, + C14F82FA1681326600AAB80A /* uri.cpp */, + C14F82FB1681326600AAB80A /* uri.hpp */, + C14F82FC1681326600AAB80A /* utf8_validator */, + C14F82FE1681326600AAB80A /* websocket_frame.hpp */, + C14F82FF1681326600AAB80A /* websocketpp.hpp */, + ); + path = src; + sourceTree = ""; + }; + C14F82C11681326600AAB80A /* base64 */ = { + isa = PBXGroup; + children = ( + C14F82C21681326600AAB80A /* base64.cpp */, + C14F82C31681326600AAB80A /* base64.h */, + ); + path = base64; + sourceTree = ""; + }; + C14F82C71681326600AAB80A /* http */ = { + isa = PBXGroup; + children = ( + C14F82C81681326600AAB80A /* constants.hpp */, + C14F82C91681326600AAB80A /* parser.hpp */, + ); + path = http; + sourceTree = ""; + }; + C14F82CA1681326600AAB80A /* logger */ = { + isa = PBXGroup; + children = ( + C14F82CB1681326600AAB80A /* logger.hpp */, + ); + path = logger; + sourceTree = ""; + }; + C14F82CC1681326600AAB80A /* md5 */ = { + isa = PBXGroup; + children = ( + C14F82CD1681326600AAB80A /* md5.c */, + C14F82CE1681326600AAB80A /* md5.h */, + C14F82CF1681326600AAB80A /* md5.hpp */, + ); + path = md5; + sourceTree = ""; + }; + C14F82D01681326600AAB80A /* messages */ = { + isa = PBXGroup; + children = ( + C14F82D11681326600AAB80A /* control.hpp */, + C14F82D21681326600AAB80A /* data.cpp */, + C14F82D31681326600AAB80A /* data.hpp */, + ); + path = messages; + sourceTree = ""; + }; + C14F82D61681326600AAB80A /* processors */ = { + isa = PBXGroup; + children = ( + C14F82D71681326600AAB80A /* hybi.hpp */, + C14F82D81681326600AAB80A /* hybi_header.cpp */, + C14F82D91681326600AAB80A /* hybi_header.hpp */, + C14F82DA1681326600AAB80A /* hybi_legacy.hpp */, + C14F82DB1681326600AAB80A /* hybi_util.cpp */, + C14F82DC1681326600AAB80A /* hybi_util.hpp */, + C14F82DD1681326600AAB80A /* processor.hpp */, + ); + path = processors; + sourceTree = ""; + }; + C14F82DE1681326600AAB80A /* rng */ = { + isa = PBXGroup; + children = ( + C14F82DF1681326600AAB80A /* blank_rng.cpp */, + C14F82E01681326600AAB80A /* blank_rng.hpp */, + C14F82E11681326600AAB80A /* boost_rng.cpp */, + C14F82E21681326600AAB80A /* boost_rng.hpp */, + ); + path = rng; + sourceTree = ""; + }; + C14F82E31681326600AAB80A /* roles */ = { + isa = PBXGroup; + children = ( + C14F82E41681326600AAB80A /* client.hpp */, + C14F82E51681326600AAB80A /* server.hpp */, + ); + path = roles; + sourceTree = ""; + }; + C14F82E71681326600AAB80A /* sha1 */ = { + isa = PBXGroup; + children = ( + C14F82E81681326600AAB80A /* license.txt */, + C14F82E91681326600AAB80A /* Makefile */, + C14F82EA1681326600AAB80A /* Makefile.nt */, + C14F82EB1681326600AAB80A /* sha.cpp */, + C14F82EC1681326600AAB80A /* sha1.cpp */, + C14F82ED1681326600AAB80A /* sha1.h */, + C14F82EE1681326600AAB80A /* shacmp.cpp */, + C14F82EF1681326600AAB80A /* shatest.cpp */, + ); + path = sha1; + sourceTree = ""; + }; + C14F82F11681326600AAB80A /* sockets */ = { + isa = PBXGroup; + children = ( + C14F82F21681326600AAB80A /* plain.hpp */, + C14F82F31681326600AAB80A /* socket_base.hpp */, + C14F82F41681326600AAB80A /* tls.hpp */, + ); + path = sockets; + sourceTree = ""; + }; + C14F82F51681326600AAB80A /* ssl */ = { + isa = PBXGroup; + children = ( + C14F82F61681326600AAB80A /* client.pem */, + C14F82F71681326600AAB80A /* dh512.pem */, + C14F82F81681326600AAB80A /* server.cer */, + C14F82F91681326600AAB80A /* server.pem */, + ); + path = ssl; + sourceTree = ""; + }; + C14F82FC1681326600AAB80A /* utf8_validator */ = { + isa = PBXGroup; + children = ( + C14F82FD1681326600AAB80A /* utf8_validator.hpp */, + ); + path = utf8_validator; + sourceTree = ""; + }; + C14F83001681326600AAB80A /* test */ = { + isa = PBXGroup; + children = ( + C14F83011681326600AAB80A /* basic */, + ); + path = test; + sourceTree = ""; + }; + C14F83011681326600AAB80A /* basic */ = { + isa = PBXGroup; + children = ( + C14F83021681326600AAB80A /* hybi_util.cpp */, + C14F83031681326600AAB80A /* logging.cpp */, + C14F83041681326600AAB80A /* Makefile */, + C14F83051681326600AAB80A /* parsing.cpp */, + C14F83061681326600AAB80A /* uri_perf.cpp */, + ); + path = basic; + sourceTree = ""; + }; + C14F83081681326600AAB80A /* websocketpp.bbprojectd */ = { + isa = PBXGroup; + children = ( + C14F83091681326600AAB80A /* project.bbprojectdata */, + C14F830A1681326600AAB80A /* Scratchpad.txt */, + C14F830B1681326600AAB80A /* Unix Worksheet.worksheet */, + C14F830C1681326600AAB80A /* zaphoyd.bbprojectsettings */, + ); + path = websocketpp.bbprojectd; + sourceTree = ""; + }; + C14F830E1681326600AAB80A /* Products */ = { + isa = PBXGroup; + children = ( + C14F85A81681326700AAB80A /* libwebsocketpp.a */, + C14F85AA1681326700AAB80A /* libwebsocketpp.dylib */, + C14F85AC1681326700AAB80A /* echo_server */, + C14F85AE1681326700AAB80A /* chat_client */, + C14F85B01681326700AAB80A /* echo_client */, + C14F85B21681326700AAB80A /* Policy Test */, + C14F85B41681326700AAB80A /* echo_server_tls */, + C14F85B61681326700AAB80A /* fuzzing_server */, + C14F85B81681326700AAB80A /* fuzzing_client */, + C14F85BA1681326700AAB80A /* broadcast_server */, + C14F85BC1681326700AAB80A /* stress_client */, + C14F85BE1681326700AAB80A /* concurrent_server */, + C14F85C01681326700AAB80A /* wsperf */, + ); + name = Products; + sourceTree = ""; + }; + C14F83101681326600AAB80A /* windows */ = { + isa = PBXGroup; + children = ( + C14F83111681326600AAB80A /* vcpp2008 */, + C14F831C1681326600AAB80A /* vcpp2010 */, + ); + path = windows; + sourceTree = ""; + }; + C14F83111681326600AAB80A /* vcpp2008 */ = { + isa = PBXGroup; + children = ( + C14F83121681326600AAB80A /* .gitignore */, + C14F83131681326600AAB80A /* common.vsprops */, + C14F83141681326600AAB80A /* examples */, + C14F83191681326600AAB80A /* stdint.h */, + C14F831A1681326600AAB80A /* websocketpp.sln */, + C14F831B1681326600AAB80A /* websocketpp.vcproj */, + ); + path = vcpp2008; + sourceTree = ""; + }; + C14F83141681326600AAB80A /* examples */ = { + isa = PBXGroup; + children = ( + C14F83151681326600AAB80A /* chatclient.vcproj */, + C14F83161681326600AAB80A /* chatserver.vcproj */, + C14F83171681326600AAB80A /* concurrent_server.vcproj */, + C14F83181681326600AAB80A /* echoserver.vcproj */, + ); + path = examples; + sourceTree = ""; + }; + C14F831C1681326600AAB80A /* vcpp2010 */ = { + isa = PBXGroup; + children = ( + C14F831D1681326600AAB80A /* .gitignore */, + C14F831E1681326600AAB80A /* examples */, + C14F832A1681326600AAB80A /* readme.txt */, + C14F832B1681326600AAB80A /* websocketpp.sln */, + C14F832C1681326600AAB80A /* websocketpp.vcxproj */, + C14F832D1681326600AAB80A /* websocketpp.vcxproj.filters */, + ); + path = vcpp2010; + sourceTree = ""; + }; + C14F831E1681326600AAB80A /* examples */ = { + isa = PBXGroup; + children = ( + C14F831F1681326600AAB80A /* chatclient.vcxproj */, + C14F83201681326600AAB80A /* chatclient.vcxproj.filters */, + C14F83211681326600AAB80A /* chatserver.vcxproj */, + C14F83221681326600AAB80A /* chatserver.vcxproj.filters */, + C14F83231681326600AAB80A /* echoclient.vcxproj */, + C14F83241681326600AAB80A /* echoclient.vcxproj.filters */, + C14F83251681326600AAB80A /* echoserver.vcxproj */, + C14F83261681326600AAB80A /* echoserver.vcxproj.filters */, + C14F83271681326600AAB80A /* wsperf */, + ); + path = examples; + sourceTree = ""; + }; + C14F83271681326600AAB80A /* wsperf */ = { + isa = PBXGroup; + children = ( + C14F83281681326600AAB80A /* wsperf.vcxproj */, + C14F83291681326600AAB80A /* wsperf.vcxproj.filters */, + ); + path = wsperf; + sourceTree = ""; + }; + C14F832E1681326600AAB80A /* js */ = { + isa = PBXGroup; + children = ( + C14F832F1681326600AAB80A /* account.js */, + C14F83301681326600AAB80A /* amount.js */, + C14F83311681326600AAB80A /* cryptojs */, + C14F83461681326600AAB80A /* index.js */, + C14F83471681326600AAB80A /* jsbn.js */, + C14F83481681326600AAB80A /* network.js */, + C14F83491681326600AAB80A /* nodeutils.js */, + C14F834A1681326600AAB80A /* remote.js */, + C14F834B1681326600AAB80A /* serializer.js */, + C14F834C1681326600AAB80A /* sjcl */, + C14F841F1681326600AAB80A /* utils.js */, + C14F84201681326600AAB80A /* utils.web.js */, + ); + path = js; + sourceTree = ""; + }; + C14F83311681326600AAB80A /* cryptojs */ = { + isa = PBXGroup; + children = ( + C14F83321681326600AAB80A /* cryptojs.js */, + C14F83331681326600AAB80A /* lib */, + C14F83411681326600AAB80A /* package.json */, + C14F83421681326600AAB80A /* README.md */, + C14F83431681326600AAB80A /* test */, + ); + path = cryptojs; + sourceTree = ""; + }; + C14F83331681326600AAB80A /* lib */ = { + isa = PBXGroup; + children = ( + C14F83341681326600AAB80A /* AES.js */, + C14F83351681326600AAB80A /* BlockModes.js */, + C14F83361681326600AAB80A /* Crypto.js */, + C14F83371681326600AAB80A /* CryptoMath.js */, + C14F83381681326600AAB80A /* DES.js */, + C14F83391681326600AAB80A /* HMAC.js */, + C14F833A1681326600AAB80A /* MARC4.js */, + C14F833B1681326600AAB80A /* MD5.js */, + C14F833C1681326600AAB80A /* PBKDF2.js */, + C14F833D1681326600AAB80A /* PBKDF2Async.js */, + C14F833E1681326600AAB80A /* Rabbit.js */, + C14F833F1681326600AAB80A /* SHA1.js */, + C14F83401681326600AAB80A /* SHA256.js */, + ); + path = lib; + sourceTree = ""; + }; + C14F83431681326600AAB80A /* test */ = { + isa = PBXGroup; + children = ( + C14F83441681326600AAB80A /* PBKDF2-test.js */, + C14F83451681326600AAB80A /* test.coffee */, + ); + path = test; + sourceTree = ""; + }; + C14F834C1681326600AAB80A /* sjcl */ = { + isa = PBXGroup; + children = ( + C14F834D1681326600AAB80A /* browserTest */, + C14F83521681326600AAB80A /* compress */, + C14F835B1681326600AAB80A /* config.mk */, + C14F835C1681326600AAB80A /* configure */, + C14F835D1681326600AAB80A /* core */, + C14F83711681326600AAB80A /* core.js */, + C14F83721681326600AAB80A /* core_closure.js */, + C14F83731681326600AAB80A /* demo */, + C14F83791681326600AAB80A /* jsdoc_toolkit-2.3.3-beta */, + C14F83FA1681326600AAB80A /* lint */, + C14F83FD1681326600AAB80A /* Makefile */, + C14F83FE1681326600AAB80A /* README */, + C14F84041681326600AAB80A /* sjcl.js */, + C14F84051681326600AAB80A /* test */, + ); + path = sjcl; + sourceTree = ""; + }; + C14F834D1681326600AAB80A /* browserTest */ = { + isa = PBXGroup; + children = ( + C14F834E1681326600AAB80A /* browserTest.html */, + C14F834F1681326600AAB80A /* browserUtil.js */, + C14F83501681326600AAB80A /* rhinoUtil.js */, + C14F83511681326600AAB80A /* test.css */, + ); + path = browserTest; + sourceTree = ""; + }; + C14F83521681326600AAB80A /* compress */ = { + isa = PBXGroup; + children = ( + C14F83531681326600AAB80A /* compiler.jar */, + C14F83541681326600AAB80A /* compress_with_closure.sh */, + C14F83551681326600AAB80A /* compress_with_yui.sh */, + C14F83561681326600AAB80A /* dewindowize.pl */, + C14F83571681326600AAB80A /* digitize.pl */, + C14F83581681326600AAB80A /* opacify.pl */, + C14F83591681326600AAB80A /* remove_constants.pl */, + C14F835A1681326600AAB80A /* yuicompressor-2.4.2.jar */, + ); + path = compress; + sourceTree = ""; + }; + C14F835D1681326600AAB80A /* core */ = { + isa = PBXGroup; + children = ( + C14F835E1681326600AAB80A /* aes.js */, + C14F835F1681326600AAB80A /* bitArray.js */, + C14F83601681326600AAB80A /* bn.js */, + C14F83611681326600AAB80A /* cbc.js */, + C14F83621681326600AAB80A /* ccm.js */, + C14F83631681326600AAB80A /* codecBase64.js */, + C14F83641681326600AAB80A /* codecBytes.js */, + C14F83651681326600AAB80A /* codecHex.js */, + C14F83661681326600AAB80A /* codecString.js */, + C14F83671681326600AAB80A /* convenience.js */, + C14F83681681326600AAB80A /* ecc.js */, + C14F83691681326600AAB80A /* hmac.js */, + C14F836A1681326600AAB80A /* ocb2.js */, + C14F836B1681326600AAB80A /* pbkdf2.js */, + C14F836C1681326600AAB80A /* random.js */, + C14F836D1681326600AAB80A /* sha1.js */, + C14F836E1681326600AAB80A /* sha256.js */, + C14F836F1681326600AAB80A /* sjcl.js */, + C14F83701681326600AAB80A /* srp.js */, + ); + path = core; + sourceTree = ""; + }; + C14F83731681326600AAB80A /* demo */ = { + isa = PBXGroup; + children = ( + C14F83741681326600AAB80A /* alpha-arrow.png */, + C14F83751681326600AAB80A /* example.css */, + C14F83761681326600AAB80A /* example.js */, + C14F83771681326600AAB80A /* form.js */, + C14F83781681326600AAB80A /* index.html */, + ); + path = demo; + sourceTree = ""; + }; + C14F83791681326600AAB80A /* jsdoc_toolkit-2.3.3-beta */ = { + isa = PBXGroup; + children = ( + C14F837A1681326600AAB80A /* app */, + C14F83DE1681326600AAB80A /* changes.txt */, + C14F83DF1681326600AAB80A /* conf */, + C14F83E11681326600AAB80A /* java */, + C14F83E91681326600AAB80A /* jsdebug.jar */, + C14F83EA1681326600AAB80A /* jsrun.jar */, + C14F83EB1681326600AAB80A /* jsrun.sh */, + C14F83EC1681326600AAB80A /* README.txt */, + C14F83ED1681326600AAB80A /* templates */, + ); + path = "jsdoc_toolkit-2.3.3-beta"; + sourceTree = ""; + }; + C14F837A1681326600AAB80A /* app */ = { + isa = PBXGroup; + children = ( + C14F837B1681326600AAB80A /* frame */, + C14F83851681326600AAB80A /* frame.js */, + C14F83861681326600AAB80A /* handlers */, + C14F838D1681326600AAB80A /* lib */, + C14F839F1681326600AAB80A /* main.js */, + C14F83A01681326600AAB80A /* plugins */, + C14F83A81681326600AAB80A /* run.js */, + C14F83A91681326600AAB80A /* t */, + C14F83AC1681326600AAB80A /* test */, + C14F83DD1681326600AAB80A /* test.js */, + ); + path = app; + sourceTree = ""; + }; + C14F837B1681326600AAB80A /* frame */ = { + isa = PBXGroup; + children = ( + C14F837C1681326600AAB80A /* Chain.js */, + C14F837D1681326600AAB80A /* Dumper.js */, + C14F837E1681326600AAB80A /* Hash.js */, + C14F837F1681326600AAB80A /* Link.js */, + C14F83801681326600AAB80A /* Namespace.js */, + C14F83811681326600AAB80A /* Opt.js */, + C14F83821681326600AAB80A /* Reflection.js */, + C14F83831681326600AAB80A /* String.js */, + C14F83841681326600AAB80A /* Testrun.js */, + ); + path = frame; + sourceTree = ""; + }; + C14F83861681326600AAB80A /* handlers */ = { + isa = PBXGroup; + children = ( + C14F83871681326600AAB80A /* FOODOC.js */, + C14F83881681326600AAB80A /* XMLDOC */, + C14F838C1681326600AAB80A /* XMLDOC.js */, + ); + path = handlers; + sourceTree = ""; + }; + C14F83881681326600AAB80A /* XMLDOC */ = { + isa = PBXGroup; + children = ( + C14F83891681326600AAB80A /* DomReader.js */, + C14F838A1681326600AAB80A /* XMLDoc.js */, + C14F838B1681326600AAB80A /* XMLParse.js */, + ); + path = XMLDOC; + sourceTree = ""; + }; + C14F838D1681326600AAB80A /* lib */ = { + isa = PBXGroup; + children = ( + C14F838E1681326600AAB80A /* JSDOC */, + C14F839E1681326600AAB80A /* JSDOC.js */, + ); + path = lib; + sourceTree = ""; + }; + C14F838E1681326600AAB80A /* JSDOC */ = { + isa = PBXGroup; + children = ( + C14F838F1681326600AAB80A /* DocComment.js */, + C14F83901681326600AAB80A /* DocTag.js */, + C14F83911681326600AAB80A /* JsDoc.js */, + C14F83921681326600AAB80A /* JsPlate.js */, + C14F83931681326600AAB80A /* Lang.js */, + C14F83941681326600AAB80A /* Parser.js */, + C14F83951681326600AAB80A /* PluginManager.js */, + C14F83961681326600AAB80A /* Symbol.js */, + C14F83971681326600AAB80A /* SymbolSet.js */, + C14F83981681326600AAB80A /* TextStream.js */, + C14F83991681326600AAB80A /* Token.js */, + C14F839A1681326600AAB80A /* TokenReader.js */, + C14F839B1681326600AAB80A /* TokenStream.js */, + C14F839C1681326600AAB80A /* Util.js */, + C14F839D1681326600AAB80A /* Walker.js */, + ); + path = JSDOC; + sourceTree = ""; + }; + C14F83A01681326600AAB80A /* plugins */ = { + isa = PBXGroup; + children = ( + C14F83A11681326600AAB80A /* commentSrcJson.js */, + C14F83A21681326600AAB80A /* frameworkPrototype.js */, + C14F83A31681326600AAB80A /* functionCall.js */, + C14F83A41681326600AAB80A /* publishSrcHilite.js */, + C14F83A51681326600AAB80A /* symbolLink.js */, + C14F83A61681326600AAB80A /* tagParamConfig.js */, + C14F83A71681326600AAB80A /* tagSynonyms.js */, + ); + path = plugins; + sourceTree = ""; + }; + C14F83A91681326600AAB80A /* t */ = { + isa = PBXGroup; + children = ( + C14F83AA1681326600AAB80A /* runner.js */, + C14F83AB1681326600AAB80A /* TestDoc.js */, + ); + path = t; + sourceTree = ""; + }; + C14F83AC1681326600AAB80A /* test */ = { + isa = PBXGroup; + children = ( + C14F83AD1681326600AAB80A /* addon.js */, + C14F83AE1681326600AAB80A /* anon_inner.js */, + C14F83AF1681326600AAB80A /* augments.js */, + C14F83B01681326600AAB80A /* augments2.js */, + C14F83B11681326600AAB80A /* borrows.js */, + C14F83B21681326600AAB80A /* borrows2.js */, + C14F83B31681326600AAB80A /* config.js */, + C14F83B41681326600AAB80A /* constructs.js */, + C14F83B51681326600AAB80A /* encoding.js */, + C14F83B61681326600AAB80A /* encoding_other.js */, + C14F83B71681326600AAB80A /* event.js */, + C14F83B81681326600AAB80A /* exports.js */, + C14F83B91681326600AAB80A /* functions_anon.js */, + C14F83BA1681326600AAB80A /* functions_nested.js */, + C14F83BB1681326600AAB80A /* global.js */, + C14F83BC1681326600AAB80A /* globals.js */, + C14F83BD1681326600AAB80A /* ignore.js */, + C14F83BE1681326600AAB80A /* inner.js */, + C14F83BF1681326600AAB80A /* jsdoc_test.js */, + C14F83C01681326600AAB80A /* lend.js */, + C14F83C11681326600AAB80A /* memberof.js */, + C14F83C21681326600AAB80A /* memberof2.js */, + C14F83C31681326600AAB80A /* memberof3.js */, + C14F83C41681326600AAB80A /* memberof_constructor.js */, + C14F83C51681326600AAB80A /* module.js */, + C14F83C61681326600AAB80A /* multi_methods.js */, + C14F83C71681326600AAB80A /* name.js */, + C14F83C81681326600AAB80A /* namespace_nested.js */, + C14F83C91681326600AAB80A /* nocode.js */, + C14F83CA1681326600AAB80A /* oblit_anon.js */, + C14F83CB1681326600AAB80A /* overview.js */, + C14F83CC1681326600AAB80A /* param_inline.js */, + C14F83CD1681326600AAB80A /* params_optional.js */, + C14F83CE1681326600AAB80A /* prototype.js */, + C14F83CF1681326600AAB80A /* prototype_nested.js */, + C14F83D01681326600AAB80A /* prototype_oblit.js */, + C14F83D11681326600AAB80A /* prototype_oblit_constructor.js */, + C14F83D21681326600AAB80A /* public.js */, + C14F83D31681326600AAB80A /* scripts */, + C14F83D61681326600AAB80A /* shared.js */, + C14F83D71681326600AAB80A /* shared2.js */, + C14F83D81681326600AAB80A /* shortcuts.js */, + C14F83D91681326600AAB80A /* static_this.js */, + C14F83DA1681326600AAB80A /* synonyms.js */, + C14F83DB1681326600AAB80A /* tosource.js */, + C14F83DC1681326600AAB80A /* variable_redefine.js */, + ); + path = test; + sourceTree = ""; + }; + C14F83D31681326600AAB80A /* scripts */ = { + isa = PBXGroup; + children = ( + C14F83D41681326600AAB80A /* code.js */, + C14F83D51681326600AAB80A /* notcode.txt */, + ); + path = scripts; + sourceTree = ""; + }; + C14F83DF1681326600AAB80A /* conf */ = { + isa = PBXGroup; + children = ( + C14F83E01681326600AAB80A /* sample.conf */, + ); + path = conf; + sourceTree = ""; + }; + C14F83E11681326600AAB80A /* java */ = { + isa = PBXGroup; + children = ( + C14F83E21681326600AAB80A /* build.xml */, + C14F83E31681326600AAB80A /* build_1.4.xml */, + C14F83E41681326600AAB80A /* classes */, + C14F83E61681326600AAB80A /* src */, + ); + path = java; + sourceTree = ""; + }; + C14F83E41681326600AAB80A /* classes */ = { + isa = PBXGroup; + children = ( + C14F83E51681326600AAB80A /* js.jar */, + ); + path = classes; + sourceTree = ""; + }; + C14F83E61681326600AAB80A /* src */ = { + isa = PBXGroup; + children = ( + C14F83E71681326600AAB80A /* JsDebugRun.java */, + C14F83E81681326600AAB80A /* JsRun.java */, + ); + path = src; + sourceTree = ""; + }; + C14F83ED1681326600AAB80A /* templates */ = { + isa = PBXGroup; + children = ( + C14F83EE1681326600AAB80A /* codeview */, + ); + path = templates; + sourceTree = ""; + }; + C14F83EE1681326600AAB80A /* codeview */ = { + isa = PBXGroup; + children = ( + C14F83EF1681326600AAB80A /* allclasses.tmpl */, + C14F83F01681326600AAB80A /* allfiles.tmpl */, + C14F83F11681326600AAB80A /* class.tmpl */, + C14F83F21681326600AAB80A /* css */, + C14F83F41681326600AAB80A /* index.tmpl */, + C14F83F51681326600AAB80A /* publish.js */, + C14F83F61681326600AAB80A /* static */, + C14F83F91681326600AAB80A /* symbol.tmpl */, + ); + path = codeview; + sourceTree = ""; + }; + C14F83F21681326600AAB80A /* css */ = { + isa = PBXGroup; + children = ( + C14F83F31681326600AAB80A /* default.css */, + ); + path = css; + sourceTree = ""; + }; + C14F83F61681326600AAB80A /* static */ = { + isa = PBXGroup; + children = ( + C14F83F71681326600AAB80A /* header.html */, + C14F83F81681326600AAB80A /* index.html */, + ); + path = static; + sourceTree = ""; + }; + C14F83FA1681326600AAB80A /* lint */ = { + isa = PBXGroup; + children = ( + C14F83FB1681326600AAB80A /* coding_guidelines.pl */, + C14F83FC1681326600AAB80A /* jslint_rhino.js */, + ); + path = lint; + sourceTree = ""; + }; + C14F83FE1681326600AAB80A /* README */ = { + isa = PBXGroup; + children = ( + C14F83FF1681326600AAB80A /* bsd.txt */, + C14F84001681326600AAB80A /* COPYRIGHT */, + C14F84011681326600AAB80A /* gpl-2.0.txt */, + C14F84021681326600AAB80A /* gpl-3.0.txt */, + C14F84031681326600AAB80A /* INSTALL */, + ); + path = README; + sourceTree = ""; + }; + C14F84051681326600AAB80A /* test */ = { + isa = PBXGroup; + children = ( + C14F84061681326600AAB80A /* aes_test.js */, + C14F84071681326600AAB80A /* aes_vectors.js */, + C14F84081681326600AAB80A /* bn_test.js */, + C14F84091681326600AAB80A /* bn_vectors.js */, + C14F840A1681326600AAB80A /* cbc_test.js */, + C14F840B1681326600AAB80A /* cbc_vectors.js */, + C14F840C1681326600AAB80A /* ccm_test.js */, + C14F840D1681326600AAB80A /* ccm_vectors.js */, + C14F840E1681326600AAB80A /* ecdh_test.js */, + C14F840F1681326600AAB80A /* ecdsa_test.js */, + C14F84101681326600AAB80A /* hmac_test.js */, + C14F84111681326600AAB80A /* hmac_vectors.js */, + C14F84121681326600AAB80A /* ocb2_test.js */, + C14F84131681326600AAB80A /* ocb2_vectors.js */, + C14F84141681326600AAB80A /* pbkdf2_test.js */, + C14F84151681326600AAB80A /* run_tests_browser.js */, + C14F84161681326600AAB80A /* run_tests_rhino.js */, + C14F84171681326600AAB80A /* sha1_test.js */, + C14F84181681326600AAB80A /* sha1_vectors.js */, + C14F84191681326600AAB80A /* sha256_test.js */, + C14F841A1681326600AAB80A /* sha256_test_brute_force.js */, + C14F841B1681326600AAB80A /* sha256_vectors.js */, + C14F841C1681326600AAB80A /* srp_test.js */, + C14F841D1681326600AAB80A /* srp_vectors.js */, + C14F841E1681326600AAB80A /* test.js */, + ); + path = test; + sourceTree = ""; + }; + C1637B0116827C5B0067A9B4 /* proto */ = { + isa = PBXGroup; + children = ( + C1637B0216827C5B0067A9B4 /* ripple.pb.cc */, + C1637B0316827C5B0067A9B4 /* ripple.pb.h */, + ); + path = proto; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + C14F81261681323400AAB80A /* rippled */ = { + isa = PBXNativeTarget; + buildConfigurationList = C14F81311681323400AAB80A /* Build configuration list for PBXNativeTarget "rippled" */; + buildPhases = ( + C14F81231681323400AAB80A /* Sources */, + C14F81241681323400AAB80A /* Frameworks */, + C14F81251681323400AAB80A /* CopyFiles */, + ); + buildRules = ( + C14F85C11681357800AAB80A /* PBXBuildRule */, + ); + dependencies = ( + ); + name = rippled; + productName = rippled; + productReference = C14F81271681323400AAB80A /* rippled */; + productType = "com.apple.product-type.tool"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + C14F811E1681323300AAB80A /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0450; + ORGANIZATIONNAME = Jcar; + }; + buildConfigurationList = C14F81211681323300AAB80A /* Build configuration list for PBXProject "rippled" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = C14F811C1681323300AAB80A; + productRefGroup = C14F81281681323400AAB80A /* Products */; + projectDirPath = ""; + projectReferences = ( + { + ProductGroup = C14F830E1681326600AAB80A /* Products */; + ProjectRef = C14F830D1681326600AAB80A /* websocketpp.xcodeproj */; + }, + ); + projectRoot = ""; + targets = ( + C14F81261681323400AAB80A /* rippled */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXReferenceProxy section */ + C14F85A81681326700AAB80A /* libwebsocketpp.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libwebsocketpp.a; + remoteRef = C14F85A71681326700AAB80A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + C14F85AA1681326700AAB80A /* libwebsocketpp.dylib */ = { + isa = PBXReferenceProxy; + fileType = "compiled.mach-o.dylib"; + path = libwebsocketpp.dylib; + remoteRef = C14F85A91681326700AAB80A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + C14F85AC1681326700AAB80A /* echo_server */ = { + isa = PBXReferenceProxy; + fileType = "compiled.mach-o.executable"; + path = echo_server; + remoteRef = C14F85AB1681326700AAB80A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + C14F85AE1681326700AAB80A /* chat_client */ = { + isa = PBXReferenceProxy; + fileType = "compiled.mach-o.executable"; + path = chat_client; + remoteRef = C14F85AD1681326700AAB80A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + C14F85B01681326700AAB80A /* echo_client */ = { + isa = PBXReferenceProxy; + fileType = "compiled.mach-o.executable"; + path = echo_client; + remoteRef = C14F85AF1681326700AAB80A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + C14F85B21681326700AAB80A /* Policy Test */ = { + isa = PBXReferenceProxy; + fileType = "compiled.mach-o.executable"; + name = "Policy Test"; + path = policy_test; + remoteRef = C14F85B11681326700AAB80A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + C14F85B41681326700AAB80A /* echo_server_tls */ = { + isa = PBXReferenceProxy; + fileType = "compiled.mach-o.executable"; + path = echo_server_tls; + remoteRef = C14F85B31681326700AAB80A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + C14F85B61681326700AAB80A /* fuzzing_server */ = { + isa = PBXReferenceProxy; + fileType = "compiled.mach-o.executable"; + path = fuzzing_server; + remoteRef = C14F85B51681326700AAB80A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + C14F85B81681326700AAB80A /* fuzzing_client */ = { + isa = PBXReferenceProxy; + fileType = "compiled.mach-o.executable"; + path = fuzzing_client; + remoteRef = C14F85B71681326700AAB80A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + C14F85BA1681326700AAB80A /* broadcast_server */ = { + isa = PBXReferenceProxy; + fileType = "compiled.mach-o.executable"; + path = broadcast_server; + remoteRef = C14F85B91681326700AAB80A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + C14F85BC1681326700AAB80A /* stress_client */ = { + isa = PBXReferenceProxy; + fileType = "compiled.mach-o.executable"; + path = stress_client; + remoteRef = C14F85BB1681326700AAB80A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + C14F85BE1681326700AAB80A /* concurrent_server */ = { + isa = PBXReferenceProxy; + fileType = "compiled.mach-o.executable"; + path = concurrent_server; + remoteRef = C14F85BD1681326700AAB80A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + C14F85C01681326700AAB80A /* wsperf */ = { + isa = PBXReferenceProxy; + fileType = "compiled.mach-o.executable"; + path = wsperf; + remoteRef = C14F85BF1681326700AAB80A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + +/* Begin PBXSourcesBuildPhase section */ + C14F81231681323400AAB80A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C14F842F1681326700AAB80A /* database.cpp in Sources */, + C14F84311681326700AAB80A /* sqlite3.c in Sources */, + C14F84321681326700AAB80A /* SqliteDatabase.cpp in Sources */, + C14F84341681326700AAB80A /* json_reader.cpp in Sources */, + C14F84351681326700AAB80A /* json_value.cpp in Sources */, + C14F84361681326700AAB80A /* json_writer.cpp in Sources */, + C14F84371681326700AAB80A /* AccountItems.cpp in Sources */, + C14F84381681326700AAB80A /* AccountSetTransactor.cpp in Sources */, + C14F84391681326700AAB80A /* AccountState.cpp in Sources */, + C14F843A1681326700AAB80A /* Amount.cpp in Sources */, + C14F843B1681326700AAB80A /* Application.cpp in Sources */, + C14F843C1681326700AAB80A /* BitcoinUtil.cpp in Sources */, + C14F843D1681326700AAB80A /* CallRPC.cpp in Sources */, + C14F843E1681326700AAB80A /* CanonicalTXSet.cpp in Sources */, + C14F843F1681326700AAB80A /* Config.cpp in Sources */, + C14F84401681326700AAB80A /* ConnectionPool.cpp in Sources */, + C14F84411681326700AAB80A /* Contract.cpp in Sources */, + C14F84421681326700AAB80A /* DBInit.cpp in Sources */, + C14F84431681326700AAB80A /* DeterministicKeys.cpp in Sources */, + C14F84441681326700AAB80A /* ECIES.cpp in Sources */, + C14F84451681326700AAB80A /* FeatureTable.cpp in Sources */, + C14F84461681326700AAB80A /* FieldNames.cpp in Sources */, + C14F84471681326700AAB80A /* HashedObject.cpp in Sources */, + C14F84481681326700AAB80A /* HTTPRequest.cpp in Sources */, + C14F84491681326700AAB80A /* HttpsClient.cpp in Sources */, + C14F844A1681326700AAB80A /* InstanceCounter.cpp in Sources */, + C1637B0416827C5B0067A9B4 /* ripple.pb.cc in Sources */, + C14F844B1681326700AAB80A /* Interpreter.cpp in Sources */, + C14F844C1681326700AAB80A /* JobQueue.cpp in Sources */, + C14F844D1681326700AAB80A /* Ledger.cpp in Sources */, + C14F844E1681326700AAB80A /* LedgerAcquire.cpp in Sources */, + C14F844F1681326700AAB80A /* LedgerConsensus.cpp in Sources */, + C14F84501681326700AAB80A /* LedgerEntrySet.cpp in Sources */, + C14F84511681326700AAB80A /* LedgerFormats.cpp in Sources */, + C14F84521681326700AAB80A /* LedgerHistory.cpp in Sources */, + C14F84531681326700AAB80A /* LedgerMaster.cpp in Sources */, + C14F84541681326700AAB80A /* LedgerProposal.cpp in Sources */, + C14F84551681326700AAB80A /* LedgerTiming.cpp in Sources */, + C14F84561681326700AAB80A /* LoadManager.cpp in Sources */, + C14F84571681326700AAB80A /* LoadMonitor.cpp in Sources */, + C14F84581681326700AAB80A /* Log.cpp in Sources */, + C14F84591681326700AAB80A /* main.cpp in Sources */, + C14F845A1681326700AAB80A /* NetworkOPs.cpp in Sources */, + C14F845B1681326700AAB80A /* NicknameState.cpp in Sources */, + C14F845C1681326700AAB80A /* Offer.cpp in Sources */, + C14F845D1681326700AAB80A /* OfferCancelTransactor.cpp in Sources */, + C14F845E1681326700AAB80A /* OfferCreateTransactor.cpp in Sources */, + C14F845F1681326700AAB80A /* Operation.cpp in Sources */, + C14F84601681326700AAB80A /* OrderBook.cpp in Sources */, + C14F84611681326700AAB80A /* OrderBookDB.cpp in Sources */, + C14F84621681326700AAB80A /* PackedMessage.cpp in Sources */, + C14F84631681326700AAB80A /* ParameterTable.cpp in Sources */, + C14F84641681326700AAB80A /* ParseSection.cpp in Sources */, + C14F84651681326700AAB80A /* Pathfinder.cpp in Sources */, + C14F84661681326700AAB80A /* PaymentTransactor.cpp in Sources */, + C14F84671681326700AAB80A /* Peer.cpp in Sources */, + C14F84681681326700AAB80A /* PeerDoor.cpp in Sources */, + C14F84691681326700AAB80A /* PlatRand.cpp in Sources */, + C14F846A1681326700AAB80A /* ProofOfWork.cpp in Sources */, + C14F846B1681326700AAB80A /* PubKeyCache.cpp in Sources */, + C14F846C1681326700AAB80A /* RangeSet.cpp in Sources */, + C14F846D1681326700AAB80A /* RegularKeySetTransactor.cpp in Sources */, + C14F846E1681326700AAB80A /* rfc1751.cpp in Sources */, + C14F846F1681326700AAB80A /* RippleAddress.cpp in Sources */, + C14F84701681326700AAB80A /* RippleCalc.cpp in Sources */, + C14F84711681326700AAB80A /* RippleState.cpp in Sources */, + C14F84721681326700AAB80A /* rpc.cpp in Sources */, + C14F84731681326700AAB80A /* RPCDoor.cpp in Sources */, + C14F84741681326700AAB80A /* RPCErr.cpp in Sources */, + C14F84751681326700AAB80A /* RPCHandler.cpp in Sources */, + C14F84761681326700AAB80A /* RPCServer.cpp in Sources */, + C14F84771681326700AAB80A /* ScriptData.cpp in Sources */, + C14F84781681326700AAB80A /* SerializedLedger.cpp in Sources */, + C14F84791681326700AAB80A /* SerializedObject.cpp in Sources */, + C14F847A1681326700AAB80A /* SerializedTransaction.cpp in Sources */, + C14F847B1681326700AAB80A /* SerializedTypes.cpp in Sources */, + C14F847C1681326700AAB80A /* SerializedValidation.cpp in Sources */, + C14F847D1681326700AAB80A /* Serializer.cpp in Sources */, + C14F847E1681326700AAB80A /* SHAMap.cpp in Sources */, + C14F847F1681326700AAB80A /* SHAMapDiff.cpp in Sources */, + C14F84801681326700AAB80A /* SHAMapNodes.cpp in Sources */, + C14F84811681326700AAB80A /* SHAMapSync.cpp in Sources */, + C14F84821681326700AAB80A /* SNTPClient.cpp in Sources */, + C14F84831681326700AAB80A /* Suppression.cpp in Sources */, + C14F84841681326700AAB80A /* Transaction.cpp in Sources */, + C14F84851681326700AAB80A /* TransactionEngine.cpp in Sources */, + C14F84861681326700AAB80A /* TransactionErr.cpp in Sources */, + C14F84871681326700AAB80A /* TransactionFormats.cpp in Sources */, + C14F84881681326700AAB80A /* TransactionMaster.cpp in Sources */, + C14F84891681326700AAB80A /* TransactionMeta.cpp in Sources */, + C14F848A1681326700AAB80A /* Transactor.cpp in Sources */, + C14F848B1681326700AAB80A /* TrustSetTransactor.cpp in Sources */, + C14F848C1681326700AAB80A /* UniqueNodeList.cpp in Sources */, + C14F848D1681326700AAB80A /* utils.cpp in Sources */, + C14F848E1681326700AAB80A /* ValidationCollection.cpp in Sources */, + C14F848F1681326700AAB80A /* Wallet.cpp in Sources */, + C14F84901681326700AAB80A /* WalletAddTransactor.cpp in Sources */, + C14F84911681326700AAB80A /* WSDoor.cpp in Sources */, + C14F84D81681326700AAB80A /* base64.cpp in Sources */, + C14F84D91681326700AAB80A /* md5.c in Sources */, + C14F84DA1681326700AAB80A /* data.cpp in Sources */, + C14F84DB1681326700AAB80A /* network_utilities.cpp in Sources */, + C14F84DC1681326700AAB80A /* hybi_header.cpp in Sources */, + C14F84DD1681326700AAB80A /* hybi_util.cpp in Sources */, + C14F84DE1681326700AAB80A /* blank_rng.cpp in Sources */, + C14F84DF1681326700AAB80A /* boost_rng.cpp in Sources */, + C14F84E21681326700AAB80A /* sha1.cpp in Sources */, + C14F84E51681326700AAB80A /* uri.cpp in Sources */, + C1637B07168284510067A9B4 /* TransactionQueue.cpp in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + C14F812F1681323400AAB80A /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = "$(ARCHS_STANDARD_64_BIT)"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + "BOOST_TEST_DYN_LINK=1", + "BOOST_FILESYSTEM_NO_DEPRECATED=1", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = NO; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ( + build/proto/, + /usr/local/include/, + /opt/local/include/, + ); + LIBRARY_SEARCH_PATHS = ( + /usr/local/lib/, + /opt/local/lib/, + ); + MACOSX_DEPLOYMENT_TARGET = 10.8; + ONLY_ACTIVE_ARCH = YES; + OTHER_LDFLAGS = ( + "-lssl", + "-lcrypto", + "-lboost_date_time-mt", + "-lboost_filesystem-mt", + "-lboost_program_options-mt", + "-lboost_regex-mt", + "-lboost_system-mt", + "-lboost_thread-mt", + "-lprotobuf", + "-ldl", + "-lz", + ); + SDKROOT = macosx; + USER_HEADER_SEARCH_PATHS = ""; + }; + name = Debug; + }; + C14F81301681323400AAB80A /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = "$(ARCHS_STANDARD_64_BIT)"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = NO; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ( + build/proto/, + /usr/local/include/, + /opt/local/include/, + ); + LIBRARY_SEARCH_PATHS = ( + /usr/local/lib/, + /opt/local/lib/, + ); + MACOSX_DEPLOYMENT_TARGET = 10.8; + OTHER_LDFLAGS = ( + "-lssl", + "-lcrypto", + "-lboost_date_time-mt", + "-lboost_filesystem-mt", + "-lboost_program_options-mt", + "-lboost_regex-mt", + "-lboost_system-mt", + "-lboost_thread-mt", + "-lprotobuf", + "-ldl", + "-lz", + ); + SDKROOT = macosx; + USER_HEADER_SEARCH_PATHS = ""; + }; + name = Release; + }; + C14F81321681323400AAB80A /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "compiler-default"; + CLANG_CXX_LIBRARY = "libstdc++"; + GCC_C_LANGUAGE_STANDARD = "compiler-default"; + HEADER_SEARCH_PATHS = ( + build/proto/, + /usr/local/include/, + /opt/local/include/, + ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + /opt/local/lib, + /usr/local/Cellar/boost/1.50.0/lib, + /usr/local/Cellar/protobuf/2.4.1/lib, + ); + ONLY_ACTIVE_ARCH = YES; + OTHER_LDFLAGS = ( + "-lssl", + "-lcrypto", + "-lboost_date_time-mt", + "-lboost_filesystem-mt", + "-lboost_program_options-mt", + "-lboost_regex-mt", + "-lboost_system-mt", + "-lboost_thread-mt", + "-lprotobuf", + "-ldl", + "-lz", + "-lboost_random-mt", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + C14F81331681323400AAB80A /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "compiler-default"; + CLANG_CXX_LIBRARY = "libstdc++"; + GCC_C_LANGUAGE_STANDARD = "compiler-default"; + HEADER_SEARCH_PATHS = ( + build/proto/, + /usr/local/include/, + /opt/local/include/, + ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + /opt/local/lib, + /usr/local/Cellar/boost/1.50.0/lib, + /usr/local/Cellar/protobuf/2.4.1/lib, + ); + ONLY_ACTIVE_ARCH = YES; + OTHER_LDFLAGS = ( + "-lssl", + "-lcrypto", + "-lboost_date_time-mt", + "-lboost_filesystem-mt", + "-lboost_program_options-mt", + "-lboost_regex-mt", + "-lboost_system-mt", + "-lboost_thread-mt", + "-lprotobuf", + "-ldl", + "-lz", + "-lboost_random-mt", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + C14F81211681323300AAB80A /* Build configuration list for PBXProject "rippled" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C14F812F1681323400AAB80A /* Debug */, + C14F81301681323400AAB80A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C14F81311681323400AAB80A /* Build configuration list for PBXNativeTarget "rippled" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C14F81321681323400AAB80A /* Debug */, + C14F81331681323400AAB80A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = C14F811E1681323300AAB80A /* Project object */; +} diff --git a/rippled.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/rippled.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..94793d09d --- /dev/null +++ b/rippled.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/rippled.xcodeproj/project.xcworkspace/xcuserdata/jcar.xcuserdatad/UserInterfaceState.xcuserstate b/rippled.xcodeproj/project.xcworkspace/xcuserdata/jcar.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 000000000..23030d300 Binary files /dev/null and b/rippled.xcodeproj/project.xcworkspace/xcuserdata/jcar.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/rippled.xcodeproj/xcuserdata/jcar.xcuserdatad/xcdebugger/Breakpoints.xcbkptlist b/rippled.xcodeproj/xcuserdata/jcar.xcuserdatad/xcdebugger/Breakpoints.xcbkptlist new file mode 100644 index 000000000..05301bc25 --- /dev/null +++ b/rippled.xcodeproj/xcuserdata/jcar.xcuserdatad/xcdebugger/Breakpoints.xcbkptlist @@ -0,0 +1,5 @@ + + + diff --git a/rippled.xcodeproj/xcuserdata/jcar.xcuserdatad/xcschemes/rippled.xcscheme b/rippled.xcodeproj/xcuserdata/jcar.xcuserdatad/xcschemes/rippled.xcscheme new file mode 100644 index 000000000..e9fee66a9 --- /dev/null +++ b/rippled.xcodeproj/xcuserdata/jcar.xcuserdatad/xcschemes/rippled.xcscheme @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/rippled.xcodeproj/xcuserdata/jcar.xcuserdatad/xcschemes/xcschememanagement.plist b/rippled.xcodeproj/xcuserdata/jcar.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 000000000..03ec442e0 --- /dev/null +++ b/rippled.xcodeproj/xcuserdata/jcar.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,22 @@ + + + + + SchemeUserState + + rippled.xcscheme + + orderHint + 0 + + + SuppressBuildableAutocreation + + C14F81261681323400AAB80A + + primary + + + + + diff --git a/src/cpp/database/SqliteDatabase.cpp b/src/cpp/database/SqliteDatabase.cpp index cbefe8759..e760d6903 100644 --- a/src/cpp/database/SqliteDatabase.cpp +++ b/src/cpp/database/SqliteDatabase.cpp @@ -1,24 +1,30 @@ #include "SqliteDatabase.h" #include "sqlite3.h" + #include #include #include +#include +#include +#include + using namespace std; -SqliteDatabase::SqliteDatabase(const char* host) : Database(host,"","") +SqliteDatabase::SqliteDatabase(const char* host) : Database(host,"",""), walRunning(false) { - mConnection=NULL; - mCurrentStmt=NULL; + mConnection = NULL; + mCurrentStmt = NULL; } void SqliteDatabase::connect() { int rc = sqlite3_open(mHost.c_str(), &mConnection); - if( rc ) + if (rc) { cout << "Can't open database: " << mHost << " " << rc << endl; sqlite3_close(mConnection); + assert((rc != SQLITE_BUSY) && (rc != SQLITE_LOCKED)); } } @@ -32,8 +38,10 @@ void SqliteDatabase::disconnect() bool SqliteDatabase::executeSQL(const char* sql, bool fail_ok) { sqlite3_finalize(mCurrentStmt); + int rc = sqlite3_prepare_v2(mConnection, sql, -1, &mCurrentStmt, NULL); - if (rc != SQLITE_OK ) + + if (SQLITE_OK != rc) { if (!fail_ok) { @@ -56,7 +64,9 @@ bool SqliteDatabase::executeSQL(const char* sql, bool fail_ok) } else { + assert((rc != SQLITE_BUSY) && (rc != SQLITE_LOCKED)); mMoreRows = false; + if (!fail_ok) { #ifdef DEBUG @@ -106,17 +116,20 @@ void SqliteDatabase::endIterRows() // will return false if there are no more rows bool SqliteDatabase::getNextRow() { - if(!mMoreRows) return(false); + if (!mMoreRows) return(false); int rc=sqlite3_step(mCurrentStmt); - if(rc==SQLITE_ROW) + if (rc==SQLITE_ROW) { return(true); - }else if(rc==SQLITE_DONE) + } + else if (rc==SQLITE_DONE) { return(false); - }else + } + else { + assert((rc != SQLITE_BUSY) && (rc != SQLITE_LOCKED)); cout << "SQL Rerror:" << rc << endl; return(false); } @@ -175,27 +188,54 @@ uint64 SqliteDatabase::getBigInt(int colIndex) } -/* http://www.sqlite.org/lang_expr.html -BLOB literals are string literals containing hexadecimal data and preceded by a single "x" or "X" character. For example: -X'53514C697465' -*/ -void SqliteDatabase::escape(const unsigned char* start, int size, std::string& retStr) +static int SqliteWALHook(void *s, sqlite3* dbCon, const char *dbName, int walSize) { - static const char toHex[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - 'A', 'B', 'C', 'D', 'E', 'F' }; + (reinterpret_cast(s))->doHook(dbName, walSize); + return SQLITE_OK; +} - retStr.resize(3 + (size * 2)); +bool SqliteDatabase::setupCheckpointing() +{ + sqlite3_wal_hook(mConnection, SqliteWALHook, this); + return true; +} - int pos = 0; - retStr[pos++] = 'X'; - retStr[pos++] = '\''; - - for (int n = 0; n < size; ++n) +void SqliteDatabase::doHook(const char *db, int pages) +{ + if (pages < 256) + return; + boost::mutex::scoped_lock sl(walMutex); + if (walDBs.insert(db).second && !walRunning) { - retStr[pos++] = toHex[start[n] >> 4]; - retStr[pos++] = toHex[start[n] & 0x0f]; + walRunning = true; + boost::thread(boost::bind(&SqliteDatabase::runWal, this)).detach(); + } +} + +void SqliteDatabase::runWal() +{ + std::set walSet; + + while (1) + { + { + boost::mutex::scoped_lock sl(walMutex); + walDBs.swap(walSet); + if (walSet.empty()) + { + walRunning = false; + return; + } + } + + BOOST_FOREACH(const std::string& db, walSet) + { + int log, ckpt; + sqlite3_wal_checkpoint_v2(mConnection, db.c_str(), SQLITE_CHECKPOINT_PASSIVE, &log, &ckpt); + } + walSet.clear(); + } - retStr[pos] = '\''; } // vim:ts=4 diff --git a/src/cpp/database/SqliteDatabase.h b/src/cpp/database/SqliteDatabase.h index 8ba9d796b..9b99bbf11 100644 --- a/src/cpp/database/SqliteDatabase.h +++ b/src/cpp/database/SqliteDatabase.h @@ -1,13 +1,24 @@ #include "database.h" +#include +#include + +#include + struct sqlite3; struct sqlite3_stmt; + class SqliteDatabase : public Database { sqlite3* mConnection; sqlite3_stmt* mCurrentStmt; bool mMoreRows; + + boost::mutex walMutex; + std::set walDBs; + bool walRunning; + public: SqliteDatabase(const char* host); @@ -39,7 +50,11 @@ public: std::vector getBinary(int colIndex); uint64 getBigInt(int colIndex); - void escape(const unsigned char* start,int size,std::string& retStr); + sqlite3* peekConnection() { return mConnection; } + virtual bool setupCheckpointing(); + + void runWal(); + void doHook(const char *db, int walSize); }; // vim:ts=4 diff --git a/src/cpp/database/database.cpp b/src/cpp/database/database.cpp index 725af6a2f..7958814e5 100644 --- a/src/cpp/database/database.cpp +++ b/src/cpp/database/database.cpp @@ -185,12 +185,4 @@ char* Database::getSingleDBValueStr(const char* sql,std::string& retStr) } #endif -std::string Database::escape(const std::string strValue) -{ - std::string strReturn; - - escape(reinterpret_cast(strValue.c_str()), strValue.size(), strReturn); - - return strReturn; -} // vim:ts=4 diff --git a/src/cpp/database/database.h b/src/cpp/database/database.h index 36ce4285c..7f9199529 100644 --- a/src/cpp/database/database.h +++ b/src/cpp/database/database.h @@ -37,9 +37,6 @@ public: std::string& getPass(){ return(mDBPass); } - virtual void escape(const unsigned char* start,int size,std::string& retStr)=0; - std::string escape(const std::string strValue); - // returns true if the query went ok virtual bool executeSQL(const char* sql, bool fail_okay=false)=0; @@ -85,6 +82,8 @@ public: // int getSingleDBValueInt(const char* sql); // float getSingleDBValueFloat(const char* sql); // char* getSingleDBValueStr(const char* sql, std::string& retStr); + + virtual bool setupCheckpointing() { return false; } }; #endif diff --git a/src/cpp/database/linux/mysqldatabase.cpp b/src/cpp/database/linux/mysqldatabase.cpp deleted file mode 100644 index 4d35b7ccd..000000000 --- a/src/cpp/database/linux/mysqldatabase.cpp +++ /dev/null @@ -1,136 +0,0 @@ -#include "mysqldatabase.h" -#include "reportingmechanism.h" -#include "string/i4string.h" -#include - - -Database* Database::newDatabase(const char* host,const char* user,const char* pass) -{ - return(new MySqlDatabase(host,user,pass)); -} - -MySqlDatabase::MySqlDatabase(const char* host,const char* user,const char* pass) : Database(host,user,pass) -{ - -} - -MySqlDatabase::~MySqlDatabase() -{ -} - -void MySqlDatabase::connect() -{ - mysql_init(&mMysql); - mysql_options(&mMysql,MYSQL_READ_DEFAULT_GROUP,"i4min"); - if(!mysql_real_connect(&mMysql,mHost,mUser,mDBPass,NULL,0,NULL,0)) - { - theUI->statusMsg("Failed to connect to database: Error: %s\n", mysql_error(&mMysql)); - }else theUI->statusMsg("Connection Established to DB"); -} - -void MySqlDatabase::disconnect() -{ - mysql_close(&mMysql); -} - -int MySqlDatabase::getNumRowsAffected() -{ - return( mysql_affected_rows(&mMysql)); -} - -// returns true if the query went ok -bool MySqlDatabase::executeSQL(const char* sql) -{ - int ret=mysql_query(&mMysql, sql); - if(ret) - { - connect(); - int ret=mysql_query(&mMysql, sql); - if(ret) - { - theUI->statusMsg("ERROR with executeSQL: %d %s",ret,sql); - return(false); - } - } - return(true); -} - -bool MySqlDatabase::startIterRows() -{ - mResult=mysql_store_result(&mMysql); - // get total number of columns from the resultset - mNumCol = mysql_num_fields(mResult); - if(mNumCol) - { - delete[](mColNameTable); - mColNameTable=new i4_str[mNumCol]; - - // fill out the column name table - for(int n = 0; n < mNumCol; n++) - { - MYSQL_FIELD* field=mysql_fetch_field(mResult); - mColNameTable[n]= field->name; - } - return(true); - } - return(false); -} - -// call this after you executeSQL -// will return false if there are no more rows -bool MySqlDatabase::getNextRow() -{ - mCurRow=mysql_fetch_row(mResult); - - return(mCurRow!=NULL); -} - -char* MySqlDatabase::getStr(int colIndex,i4_str* retStr) -{ - if(mCurRow[colIndex]) - { - (*retStr)=mCurRow[colIndex]; - }else (*retStr)=""; - - return(*retStr); -} - -w32 MySqlDatabase::getInt(int colIndex) -{ - - if(mCurRow[colIndex]) - { - w32 ret=atoll(mCurRow[colIndex]); - //theUI->statusMsg("getInt: %s,%c,%u",mCurRow[colIndex],mCurRow[colIndex][0],ret); - return(ret); - } - return(0); -} - -float MySqlDatabase::getFloat(int colIndex) -{ - if(mCurRow[colIndex]) - { - float ret=atof(mCurRow[colIndex]); - return(ret); - } - return(0.0); -} - -bool MySqlDatabase::getBool(int colIndex) -{ - if(mCurRow[colIndex]) - { - int ret=atoi(mCurRow[colIndex]); - return(ret); - } - return(false); -} - - -void MySqlDatabase::endIterRows() -{ - mysql_free_result(mResult); -} - - diff --git a/src/cpp/database/linux/mysqldatabase.h b/src/cpp/database/linux/mysqldatabase.h deleted file mode 100644 index 1fb9cae10..000000000 --- a/src/cpp/database/linux/mysqldatabase.h +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef __MYSQLDATABASE__ -#define __MYSQLDATABASE__ - -#include "../database.h" -#include "string/i4string.h" -#include "mysql/mysql.h" - -/* -this maintains the connection to the database -*/ -class MySqlDatabase : public Database -{ - MYSQL mMysql; - MYSQL_RES* mResult; - MYSQL_ROW mCurRow; - -public: - MySqlDatabase(const char* host,const char* user,const char* pass); - ~MySqlDatabase(); - - void connect(); - void disconnect(); - - // returns true if the query went ok - bool executeSQL(const char* sql); - - int getNumRowsAffected(); - - // returns false if there are no results - bool startIterRows(); - void endIterRows(); - - // call this after you executeSQL - // will return false if there are no more rows - bool getNextRow(); - - // get Data from the current row - - char* getStr(int colIndex,i4_str* retStr); - w32 getInt(int colIndex); - float getFloat(int colIndex); - bool getBool(int colIndex); -}; - - -#endif - diff --git a/src/cpp/database/win/dbutility.h b/src/cpp/database/win/dbutility.h deleted file mode 100644 index 5df5c1ec0..000000000 --- a/src/cpp/database/win/dbutility.h +++ /dev/null @@ -1,82 +0,0 @@ -#ifndef __TMYODBC_UTILITY_H__ -#define __TMYODBC_UTILITY_H__ - -#ifdef HAVE_CONFIG_H -#include -#endif - - -/* STANDARD C HEADERS */ -#include -#include -#include - -/* ODBC HEADERS */ -#include - -#define MAX_NAME_LEN 95 -#define MAX_COLUMNS 255 -#define MAX_ROW_DATA_LEN 255 - - -/* PROTOTYPE */ -void myerror(SQLRETURN rc,SQLSMALLINT htype, SQLHANDLE handle); - -/* UTILITY MACROS */ -#define myenv(henv,r) \ - if ( ((r) != SQL_SUCCESS) ) \ - myerror(r, 1,henv); \ - assert( ((r) == SQL_SUCCESS) || ((r) == SQL_SUCCESS_WITH_INFO) ) - -#define myenv_err(henv,r,rc) \ - if ( rc == SQL_ERROR || rc == SQL_SUCCESS_WITH_INFO ) \ - myerror(rc, 1, henv); \ - assert( r ) - -#define mycon(hdbc,r) \ - if ( ((r) != SQL_SUCCESS) ) \ - myerror(r, 2, hdbc); \ - assert( ((r) == SQL_SUCCESS) || ((r) == SQL_SUCCESS_WITH_INFO) ) - -#define mycon_err(hdbc,r,rc) \ - if ( rc == SQL_ERROR || rc == SQL_SUCCESS_WITH_INFO ) \ - myerror(rc, 2, hdbc); \ - assert( r ) - -#define mystmt(hstmt,r) \ - if ( ((r) != SQL_SUCCESS) ) \ - myerror(r, 3, hstmt); \ - assert( ((r) == SQL_SUCCESS) || ((r) == SQL_SUCCESS_WITH_INFO) ) - -#define mystmt_err(hstmt,r,rc) \ - if ( rc == SQL_ERROR || rc == SQL_SUCCESS_WITH_INFO ) \ - myerror(rc, 3, hstmt); \ - assert( r ) - -/******************************************************** -* MyODBC 3.51 error handler * -*********************************************************/ -void myerror(SQLRETURN rc, SQLSMALLINT htype, SQLHANDLE handle) -{ - SQLRETURN lrc; - - if( rc == SQL_ERROR || rc == SQL_SUCCESS_WITH_INFO ) - { - SQLCHAR szSqlState[6],szErrorMsg[SQL_MAX_MESSAGE_LENGTH]; - SQLINTEGER pfNativeError; - SQLSMALLINT pcbErrorMsg; - - lrc = SQLGetDiagRec(htype, handle,1, - (SQLCHAR *)&szSqlState, - (SQLINTEGER *)&pfNativeError, - (SQLCHAR *)&szErrorMsg, - SQL_MAX_MESSAGE_LENGTH-1, - (SQLSMALLINT *)&pcbErrorMsg); - if(lrc == SQL_SUCCESS || lrc == SQL_SUCCESS_WITH_INFO) - printf("\n [%s][%d:%s]\n",szSqlState,pfNativeError,szErrorMsg); - } -} - - -#endif /* __TMYODBC_UTILITY_H__ */ - diff --git a/src/cpp/database/win/windatabase.cpp b/src/cpp/database/win/windatabase.cpp deleted file mode 100644 index 64215fb8a..000000000 --- a/src/cpp/database/win/windatabase.cpp +++ /dev/null @@ -1,246 +0,0 @@ -#include "windatabase.h" -#include "dbutility.h" - -using namespace std; - -Database* Database::newMysqlDatabase(const char* host,const char* user,const char* pass) -{ - return(new WinDatabase(host,user,pass)); -} - -WinDatabase::WinDatabase(const char* host,const char* user,const char* pass) : Database(host,user,pass) -{ - -} - -WinDatabase::~WinDatabase() -{ - disconnect(); -} - - -void WinDatabase::connect() -{ - SQLRETURN rc; - - rc = SQLAllocHandle(SQL_HANDLE_ENV,SQL_NULL_HANDLE,&henv); - myenv(henv, rc); - - rc = SQLSetEnvAttr(henv,SQL_ATTR_ODBC_VERSION,(SQLPOINTER)SQL_OV_ODBC3,0); - myenv(henv, rc); - - rc = SQLAllocHandle(SQL_HANDLE_DBC,henv, &hdbc); - myenv(henv, rc); - - rc = SQLConnect(hdbc, (unsigned char*)(char*) mHost.c_str(), SQL_NTS, (unsigned char*)(char*) mUser.c_str(), SQL_NTS, (unsigned char*)(char*) mDBPass.c_str(), SQL_NTS); - mycon(hdbc, rc); - - rc = SQLSetConnectAttr(hdbc,SQL_ATTR_AUTOCOMMIT,(SQLPOINTER)SQL_AUTOCOMMIT_ON,0); - mycon(hdbc,rc); - - rc = SQLAllocHandle(SQL_HANDLE_STMT,hdbc,&hstmt); - mycon(hdbc,rc); - - //rc = SQLGetInfo(hdbc,SQL_DBMS_NAME,&server_name,40,NULL); - //mycon(hdbc, rc); - - //theUI->statusMsg("Connection Established to DB"); -} - -void WinDatabase::disconnect() -{ - SQLRETURN rc; - - rc = SQLFreeStmt(hstmt, SQL_DROP); - mystmt(hstmt,rc); - - rc = SQLDisconnect(hdbc); - mycon(hdbc, rc); - - rc = SQLFreeHandle(SQL_HANDLE_DBC, hdbc); - mycon(hdbc, rc); - - rc = SQLFreeHandle(SQL_HANDLE_ENV, henv); - myenv(henv, rc); -} - -int WinDatabase::getNumRowsAffected() -{ -// theUI->statusMsg("getNumRowsAffected()"); - SQLINTEGER ret; - SQLRowCount(hstmt,&ret); - return(ret); -} - -// returns true if the query went ok -bool WinDatabase::executeSQL(const char* sql, bool fail_okay) -{ - SQLRETURN rc = SQLExecDirect(hstmt,(unsigned char*) sql,SQL_NTS); - if(rc==SQL_ERROR) - { - //theUI->errorMsg("Trying to recover from DB error"); - rc = SQLExecDirect(hstmt,(unsigned char*) sql,SQL_NTS); - if(rc==SQL_ERROR) - { - SQLCHAR SqlState[6], /*SQLStmt[100],*/ Msg[SQL_MAX_MESSAGE_LENGTH]; - SQLINTEGER NativeError; - SQLSMALLINT i, MsgLen; - SQLRETURN /*rc1,*/ rc2; - - i = 1; - while ((rc2 = SQLGetDiagRec(SQL_HANDLE_STMT, hstmt, i, SqlState, &NativeError, Msg, sizeof(Msg), &MsgLen)) != SQL_NO_DATA) - { - //theUI->errorMsg("DB ERROR: %s,%d,%s",SqlState,NativeError,Msg); - i++; - } - - return(false); - } - } - - mystmt(hstmt,rc); - - // commit the transaction - rc = SQLEndTran(SQL_HANDLE_DBC, hdbc, SQL_COMMIT); - mycon(hdbc,rc); - - return(true); - -} - -bool WinDatabase::startIterRows() -{ - SQLUINTEGER pcColDef; - SQLCHAR szColName[MAX_NAME_LEN]; - SQLSMALLINT pfSqlType, pcbScale, pfNullable; - SQLSMALLINT numCol; - - /* get total number of columns from the resultset */ - SQLRETURN rc = SQLNumResultCols(hstmt,&numCol); - mystmt(hstmt,rc); - mNumCol=(int)numCol; - - if(mNumCol) - { - mColNameTable.resize(mNumCol); - - // fill out the column name table - for(int n = 1; n <= mNumCol; n++) - { - rc = SQLDescribeCol(hstmt,n,szColName, MAX_NAME_LEN, NULL, &pfSqlType,&pcColDef,&pcbScale,&pfNullable); - mystmt(hstmt,rc); - - mColNameTable[n-1]= (char*)szColName; - } - return(true); - } - return(false); -} - -// call this after you executeSQL -// will return false if there are no more rows -bool WinDatabase::getNextRow() -{ - SQLRETURN rc = SQLFetch(hstmt); - return((rc==SQL_SUCCESS) || (rc==SQL_SUCCESS_WITH_INFO)); -} - - - -char* WinDatabase::getStr(int colIndex,string& retStr) -{ - colIndex++; - retStr=""; - char buf[1000]; -// SQLINTEGER len; - buf[0]=0; - - while(SQLGetData(hstmt, colIndex, SQL_C_CHAR, &buf, 1000,NULL)!= SQL_NO_DATA) - { - retStr += buf; -// theUI->statusMsg("Win: %s",buf); - } - - //SQLRETURN rc = SQLGetData(hstmt,colIndex,SQL_C_CHAR,&buf,30000,&len); - //mystmt(hstmt,rc); - //*retStr=buf; - - //theUI->statusMsg("Win: %s",buf); - - return((char*)retStr.c_str()); -} - -int32 WinDatabase::getInt(int colIndex) -{ - colIndex++; - int ret=0; - SQLRETURN rc = SQLGetData(hstmt,colIndex,SQL_INTEGER,&ret,sizeof(int),NULL); - mystmt(hstmt,rc); - return(ret); -} - -float WinDatabase::getFloat(int colIndex) -{ - colIndex++; - float ret=0; - SQLRETURN rc = SQLGetData(hstmt,colIndex,SQL_C_FLOAT,&ret,sizeof(float),NULL); - mystmt(hstmt,rc); - - return(ret); -} - -bool WinDatabase::getBool(int colIndex) -{ - colIndex++; - char buf[1]; - buf[0]=0; - SQLRETURN rc = SQLGetData(hstmt,colIndex,SQL_C_CHAR,&buf,1,NULL); - mystmt(hstmt,rc); - - return(buf[0] != 0); -} - - -void WinDatabase::endIterRows() -{ - // free the statement row bind resources - SQLRETURN rc = SQLFreeStmt(hstmt, SQL_UNBIND); - mystmt(hstmt,rc); - - // free the statement cursor - rc = SQLFreeStmt(hstmt, SQL_CLOSE); - mystmt(hstmt,rc); -} - -// TODO -void WinDatabase::escape(const unsigned char* start,int size,std::string& retStr) -{ - retStr=(char*)start; -} - -// TODO -int WinDatabase::getLastInsertID() -{ - return(0); -} - -uint64 WinDatabase::getBigInt(int colIndex) -{ - colIndex++; - uint64 ret=0; - SQLRETURN rc = SQLGetData(hstmt,colIndex,SQL_INTEGER,&ret,sizeof(uint64),NULL); - mystmt(hstmt,rc); - return(ret); -} -// TODO: -int WinDatabase::getBinary(int colIndex,unsigned char* buf,int maxSize) -{ - return(0); -} - -std::vector WinDatabase::getBinary(int colIndex) -{ - // TODO: - std::vector vucResult; - return vucResult; -} \ No newline at end of file diff --git a/src/cpp/database/win/windatabase.h b/src/cpp/database/win/windatabase.h deleted file mode 100644 index 5ce079870..000000000 --- a/src/cpp/database/win/windatabase.h +++ /dev/null @@ -1,61 +0,0 @@ -#ifndef __WINDATABASE__ -#define __WINDATABASE__ - -#include "../database.h" - - -#ifdef WIN32 -#define _WINSOCKAPI_ // prevent inclusion of winsock.h in windows.h -#include -#include "sql.h" -#endif - - -/* - this maintains the connection to the database -*/ -class WinDatabase : public Database -{ - SQLHENV henv; - SQLHDBC hdbc; - SQLHSTMT hstmt; - -public: - WinDatabase(const char* host,const char* user,const char* pass); - virtual ~WinDatabase(); - - void connect(); - void disconnect(); - - //char* getPass(){ return((char*)mDBPass.c_str()); } - - // returns true if the query went ok - bool executeSQL(const char* sql, bool fail_okay=false); - - int getNumRowsAffected(); - int getLastInsertID(); - - // returns false if there are no results - bool startIterRows(); - void endIterRows(); - - // call this after you executeSQL - // will return false if there are no more rows - bool getNextRow(); - - // get Data from the current row - char* getStr(int colIndex,std::string& retStr); - int32 getInt(int colIndex); - float getFloat(int colIndex); - bool getBool(int colIndex); - uint64 getBigInt(int colIndex); - int getBinary(int colIndex,unsigned char* buf,int maxSize); - std::vector getBinary(int colIndex); - bool getNull(int colIndex){ return(true); } - - void escape(const unsigned char* start,int size,std::string& retStr); -}; - - -#endif - diff --git a/src/cpp/json/json_writer.cpp b/src/cpp/json/json_writer.cpp index 2a9f0dba3..5ab619d7b 100644 --- a/src/cpp/json/json_writer.cpp +++ b/src/cpp/json/json_writer.cpp @@ -67,9 +67,9 @@ std::string valueToString( double value ) { char buffer[32]; #if defined(_MSC_VER) && defined(__STDC_SECURE_LIB__) // Use secure version with visual studio 2005 to avoid warning. - sprintf_s(buffer, sizeof(buffer), "%#.16g", value); + sprintf_s(buffer, sizeof(buffer), "%#f", value); #else - sprintf(buffer, "%#.16g", value); + sprintf(buffer, "%#f", value); #endif char* ch = buffer + strlen(buffer) - 1; if (*ch != '0') return buffer; // nothing to truncate, so save time diff --git a/src/cpp/ripple/AccountItems.h b/src/cpp/ripple/AccountItems.h index fc4afe7c7..3382dff26 100644 --- a/src/cpp/ripple/AccountItems.h +++ b/src/cpp/ripple/AccountItems.h @@ -4,9 +4,9 @@ #include "Ledger.h" #include "SerializedLedger.h" -/* -Way to fetch ledger entries from an account's owner dir -*/ +// +// Fetch ledger entries from an account's owner dir. +// class AccountItem { protected: @@ -42,3 +42,5 @@ public: }; #endif + +// vim:ts=4 diff --git a/src/cpp/ripple/AccountSetTransactor.cpp b/src/cpp/ripple/AccountSetTransactor.cpp index 32ded2c6f..9ffa635e5 100644 --- a/src/cpp/ripple/AccountSetTransactor.cpp +++ b/src/cpp/ripple/AccountSetTransactor.cpp @@ -1,8 +1,83 @@ #include "AccountSetTransactor.h" +#include "Config.h" + +SETUP_LOG(); TER AccountSetTransactor::doApply() { - Log(lsINFO) << "doAccountSet>"; + cLog(lsINFO) << "AccountSet>"; + + const uint32 uTxFlags = mTxn.getFlags(); + + const uint32 uFlagsIn = mTxnAccount->getFieldU32(sfFlags); + uint32 uFlagsOut = uFlagsIn; + + if (uTxFlags & tfAccountSetMask) + { + cLog(lsINFO) << "AccountSet: Malformed transaction: Invalid flags set."; + + return temINVALID_FLAG; + } + + // + // RequireAuth + // + + if ((tfRequireAuth|tfOptionalAuth) == (uTxFlags & (tfRequireAuth|tfOptionalAuth))) + { + cLog(lsINFO) << "AccountSet: Malformed transaction: Contradictory flags set."; + + return temINVALID_FLAG; + } + + if ((uTxFlags & tfRequireAuth) && !isSetBit(uFlagsIn, lsfRequireAuth)) + { + if (mTxn.getFieldU32(sfOwnerCount)) + { + cLog(lsINFO) << "AccountSet: Retry: OwnerCount not zero."; + + return terOWNERS; + } + + cLog(lsINFO) << "AccountSet: Set RequireAuth."; + + uFlagsOut |= lsfRequireAuth; + } + + if (uTxFlags & tfOptionalAuth) + { + cLog(lsINFO) << "AccountSet: Clear RequireAuth."; + + uFlagsOut &= ~lsfRequireAuth; + } + + // + // RequireDestTag + // + + if ((tfRequireDestTag|tfOptionalDestTag) == (uTxFlags & (tfRequireDestTag|tfOptionalDestTag))) + { + cLog(lsINFO) << "AccountSet: Malformed transaction: Contradictory flags set."; + + return temINVALID_FLAG; + } + + if (uTxFlags & tfRequireDestTag) + { + cLog(lsINFO) << "AccountSet: Set RequireDestTag."; + + uFlagsOut |= lsfRequireDestTag; + } + + if (uTxFlags & tfOptionalDestTag) + { + cLog(lsINFO) << "AccountSet: Clear RequireDestTag."; + + uFlagsOut &= ~lsfRequireDestTag; + } + + if (uFlagsIn != uFlagsOut) + mTxnAccount->setFieldU32(sfFlags, uFlagsOut); // // EmailHash @@ -14,13 +89,13 @@ TER AccountSetTransactor::doApply() if (!uHash) { - Log(lsINFO) << "doAccountSet: unset email hash"; + cLog(lsINFO) << "AccountSet: unset email hash"; mTxnAccount->makeFieldAbsent(sfEmailHash); } else { - Log(lsINFO) << "doAccountSet: set email hash"; + cLog(lsINFO) << "AccountSet: set email hash"; mTxnAccount->setFieldH128(sfEmailHash, uHash); } @@ -36,13 +111,13 @@ TER AccountSetTransactor::doApply() if (!uHash) { - Log(lsINFO) << "doAccountSet: unset wallet locator"; + cLog(lsINFO) << "AccountSet: unset wallet locator"; mTxnAccount->makeFieldAbsent(sfEmailHash); } else { - Log(lsINFO) << "doAccountSet: set wallet locator"; + cLog(lsINFO) << "AccountSet: set wallet locator"; mTxnAccount->setFieldH256(sfWalletLocator, uHash); } @@ -52,15 +127,22 @@ TER AccountSetTransactor::doApply() // MessageKey // - if (!mTxn.isFieldPresent(sfMessageKey)) + if (mTxn.isFieldPresent(sfMessageKey)) { - nothing(); - } - else - { - Log(lsINFO) << "doAccountSet: set message key"; + std::vector vucPublic = mTxn.getFieldVL(sfMessageKey); - mTxnAccount->setFieldVL(sfMessageKey, mTxn.getFieldVL(sfMessageKey)); + if (vucPublic.size() > PUBLIC_BYTES_MAX) + { + cLog(lsINFO) << "AccountSet: message key too long"; + + return telBAD_PUBLIC_KEY; + } + else + { + cLog(lsINFO) << "AccountSet: set message key"; + + mTxnAccount->setFieldVL(sfMessageKey, vucPublic); + } } // @@ -73,13 +155,19 @@ TER AccountSetTransactor::doApply() if (vucDomain.empty()) { - Log(lsINFO) << "doAccountSet: unset domain"; + cLog(lsINFO) << "AccountSet: unset domain"; mTxnAccount->makeFieldAbsent(sfDomain); } + else if (vucDomain.size() > DOMAIN_BYTES_MAX) + { + cLog(lsINFO) << "AccountSet: domain too long"; + + return telBAD_DOMAIN; + } else { - Log(lsINFO) << "doAccountSet: set domain"; + cLog(lsINFO) << "AccountSet: set domain"; mTxnAccount->setFieldVL(sfDomain, vucDomain); } @@ -95,25 +183,27 @@ TER AccountSetTransactor::doApply() if (!uRate || uRate == QUALITY_ONE) { - Log(lsINFO) << "doAccountSet: unset transfer rate"; + cLog(lsINFO) << "AccountSet: unset transfer rate"; mTxnAccount->makeFieldAbsent(sfTransferRate); } else if (uRate > QUALITY_ONE) { - Log(lsINFO) << "doAccountSet: set transfer rate"; + cLog(lsINFO) << "AccountSet: set transfer rate"; mTxnAccount->setFieldU32(sfTransferRate, uRate); } else { - Log(lsINFO) << "doAccountSet: bad transfer rate"; + cLog(lsINFO) << "AccountSet: bad transfer rate"; return temBAD_TRANSFER_RATE; } } - Log(lsINFO) << "doAccountSet<"; + cLog(lsINFO) << "AccountSet<"; return tesSUCCESS; -} \ No newline at end of file +} + +// vim:ts=4 diff --git a/src/cpp/ripple/AccountSetTransactor.h b/src/cpp/ripple/AccountSetTransactor.h index 214a32d27..160192161 100644 --- a/src/cpp/ripple/AccountSetTransactor.h +++ b/src/cpp/ripple/AccountSetTransactor.h @@ -6,4 +6,6 @@ public: AccountSetTransactor(const SerializedTransaction& txn,TransactionEngineParams params, TransactionEngine* engine) : Transactor(txn,params,engine) {} TER doApply(); -}; \ No newline at end of file +}; + +// vim:ts=4 diff --git a/src/cpp/ripple/Amount.cpp b/src/cpp/ripple/Amount.cpp index c1e441621..1d300f48e 100644 --- a/src/cpp/ripple/Amount.cpp +++ b/src/cpp/ripple/Amount.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include @@ -15,8 +16,16 @@ SETUP_LOG(); uint64 STAmount::uRateOne = STAmount::getRate(STAmount(1), STAmount(1)); -static const uint64_t tenTo14 = 100000000000000ul; -static const uint64_t tenTo17 = 100000000000000000ul; +static const uint64 tenTo14 = 100000000000000ull; +static const uint64 tenTo17 = tenTo14 * 1000; + +#if (ULONG_MAX > UINT_MAX) +#define BN_add_word64(bn, word) BN_add_word(bn, word) +#define BN_mul_word64(bn, word) BN_mul_word(bn, word) +#define BN_div_word64(bn, word) BN_div_word(bn, word) +#else +#include "BigNum64.h" +#endif bool STAmount::issuerFromString(uint160& uDstIssuer, const std::string& sIssuer) { @@ -277,7 +286,7 @@ bool STAmount::setValue(const std::string& sAmount) } catch (...) { - Log(lsINFO) << "Bad integer amount: " << sAmount; + cLog(lsINFO) << "Bad integer amount: " << sAmount; return false; } @@ -304,7 +313,7 @@ bool STAmount::setValue(const std::string& sAmount) } catch (...) { - Log(lsINFO) << "Bad e amount: " << sAmount; + cLog(lsINFO) << "Bad e amount: " << sAmount; return false; } @@ -346,7 +355,7 @@ bool STAmount::setValue(const std::string& sAmount) } catch (...) { - Log(lsINFO) << "Bad float amount: " << sAmount; + cLog(lsINFO) << "Bad float amount: " << sAmount; return false; } @@ -391,7 +400,7 @@ bool STAmount::setFullValue(const std::string& sAmount, const std::string& sCurr // if (!currencyFromString(mCurrency, sCurrency)) { - Log(lsINFO) << "Currency malformed: " << sCurrency; + cLog(lsINFO) << "Currency malformed: " << sCurrency; return false; } @@ -406,7 +415,7 @@ bool STAmount::setFullValue(const std::string& sAmount, const std::string& sCurr // Issuer must be "" or a valid account string. if (!naIssuerID.setAccountID(sIssuer)) { - Log(lsINFO) << "Issuer malformed: " << sIssuer; + cLog(lsINFO) << "Issuer malformed: " << sIssuer; return false; } @@ -416,7 +425,7 @@ bool STAmount::setFullValue(const std::string& sAmount, const std::string& sCurr // Stamps not must have an issuer. if (mIsNative && !mIssuer.isZero()) { - Log(lsINFO) << "Issuer specified for XRP: " << sIssuer; + cLog(lsINFO) << "Issuer specified for XRP: " << sIssuer; return false; } @@ -901,9 +910,9 @@ STAmount STAmount::divide(const STAmount& num, const STAmount& den, const uint16 // Compute (numerator * 10^17) / denominator CBigNum v; - if ((BN_add_word(&v, numVal) != 1) || - (BN_mul_word(&v, tenTo17) != 1) || - (BN_div_word(&v, denVal) == ((BN_ULONG) -1))) + if ((BN_add_word64(&v, numVal) != 1) || + (BN_mul_word64(&v, tenTo17) != 1) || + (BN_div_word64(&v, denVal) == ((uint64) -1))) { throw std::runtime_error("internal bn error"); } @@ -911,7 +920,7 @@ STAmount STAmount::divide(const STAmount& num, const STAmount& den, const uint16 // 10^16 <= quotient <= 10^18 assert(BN_num_bytes(&v) <= 64); - return STAmount(uCurrencyID, uIssuerID, v.getulong() + 5, + return STAmount(uCurrencyID, uIssuerID, v.getuint64() + 5, numOffset - denOffset - 17, num.mIsNegative != den.mIsNegative); } @@ -924,9 +933,9 @@ STAmount STAmount::multiply(const STAmount& v1, const STAmount& v2, const uint16 { uint64 minV = (v1.getSNValue() < v2.getSNValue()) ? v1.getSNValue() : v2.getSNValue(); uint64 maxV = (v1.getSNValue() < v2.getSNValue()) ? v2.getSNValue() : v1.getSNValue(); - if (minV > 3000000000) // sqrt(cMaxNative) + if (minV > 3000000000ull) // sqrt(cMaxNative) throw std::runtime_error("Native value overflow"); - if (((maxV >> 32) * minV) > 2095475792) // cMaxNative / 2^32 + if (((maxV >> 32) * minV) > 2095475792ull) // cMaxNative / 2^32 throw std::runtime_error("Native value overflow"); return STAmount(v1.getFName(), minV * maxV); } @@ -955,9 +964,9 @@ STAmount STAmount::multiply(const STAmount& v1, const STAmount& v2, const uint16 // Compute (numerator * denominator) / 10^14 with rounding // 10^16 <= result <= 10^18 CBigNum v; - if ((BN_add_word(&v, value1) != 1) || - (BN_mul_word(&v, value2) != 1) || - (BN_div_word(&v, tenTo14) == ((BN_ULONG) -1))) + if ((BN_add_word64(&v, value1) != 1) || + (BN_mul_word64(&v, value2) != 1) || + (BN_div_word64(&v, tenTo14) == ((uint64) -1))) { throw std::runtime_error("internal bn error"); } @@ -965,7 +974,7 @@ STAmount STAmount::multiply(const STAmount& v1, const STAmount& v2, const uint16 // 10^16 <= product <= 10^18 assert(BN_num_bytes(&v) <= 64); - return STAmount(uCurrencyID, uIssuerID, v.getulong() + 7, offset1 + offset2 + 14, + return STAmount(uCurrencyID, uIssuerID, v.getuint64() + 7, offset1 + offset2 + 14, v1.mIsNegative != v2.mIsNegative); } @@ -998,15 +1007,26 @@ STAmount STAmount::setRate(uint64 rate) return STAmount(CURRENCY_ONE, ACCOUNT_ONE, mantissa, exponent); } -// Taker gets all taker can pay for with saTakerFunds, limited by saOfferPays and saOfferFunds. -// --> uTakerPaysRate: >= QUALITY_ONE -// --> uOfferPaysRate: >= QUALITY_ONE -// --> saOfferFunds: Limit for saOfferPays +// Taker gets all taker can pay for with saTakerFunds/uTakerPaysRate, limited by saOfferPays and saOfferFunds/uOfferPaysRate. +// +// Existing offer is on the books. Offer owner get's their rate. +// +// Taker pays what they can. If taker is an offer, doesn't matter what rate taker is. Taker is spending at same or better rate +// than they wanted. Taker should consider themselves as wanting to buy X amount. Taker is willing to pay at most the rate of Y/X +// each. Therefore, after having some part of their offer fulfilled at a better rate their offer should be reduced accordingly. +// +// YYY Could have a flag for spend up to behaviour vs current limit spend rate. +// +// There are no quality costs for offer vs offer taking. +// +// --> uTakerPaysRate: >= QUALITY_ONE | TransferRate for third party IOUs paid by taker. +// --> uOfferPaysRate: >= QUALITY_ONE | TransferRate for third party IOUs paid by offer owner. +// --> saOfferRate: Original saOfferGets/saOfferPays, when offer was made. +// --> saOfferFunds: Limit for saOfferPays : How much can pay including fees. // --> 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. +// --> saTakerGets: Limit for taker to get. // <-- saTakerPaid: Actual // <-- saTakerGot: Actual // <-- saTakerIssuerFee: Actual @@ -1014,62 +1034,63 @@ STAmount STAmount::setRate(uint64 rate) // <-- bRemove: remove offer it is either fullfilled or unfunded bool STAmount::applyOffer( const uint32 uTakerPaysRate, const uint32 uOfferPaysRate, + const STAmount& saOfferRate, const STAmount& saOfferFunds, const STAmount& saTakerFunds, const STAmount& saOfferPays, const STAmount& saOfferGets, - const STAmount& saTakerPays, const STAmount& saTakerGets, + const STAmount& saTakerGets, STAmount& saTakerPaid, STAmount& saTakerGot, STAmount& saTakerIssuerFee, STAmount& saOfferIssuerFee) { - saOfferGets.throwComparable(saTakerPays); + saOfferGets.throwComparable(saTakerFunds); 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. + // Limit offerer funds available, by transfer 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 = std::min(saOfferFundsAvailable, saOfferPays); + cLog(lsINFO) << "applyOffer: uOfferPaysRate=" << uOfferPaysRate; + cLog(lsINFO) << "applyOffer: saOfferFundsAvailable=" << saOfferFundsAvailable.getFullText(); - // Amount offer can get in proportion, limited by offer funds. - STAmount saOfferGetsAvailable = - saOfferFundsAvailable == saOfferPays - ? saOfferGets // Offer was fully funded, avoid shenanigans. - : divide(multiply(saTakerPays, saOfferPaysAvailable, CURRENCY_ONE, ACCOUNT_ONE), saTakerGets, saOfferGets.getCurrency(), saOfferGets.getIssuer()); + // Limit taker funds available, by transfer fees. + STAmount saTakerFundsAvailable = QUALITY_ONE == uTakerPaysRate + ? saTakerFunds + : STAmount::divide(saTakerFunds, STAmount(CURRENCY_ONE, uTakerPaysRate, -9)); - // Amount taker can spend, limited by funds and fees. - STAmount saTakerFundsAvailable = QUALITY_ONE == uTakerPaysRate - ? saTakerFunds - : STAmount::divide(saTakerFunds, STAmount(CURRENCY_ONE, uTakerPaysRate, -9)); + cLog(lsINFO) << "applyOffer: uTakerPaysRate=" << uTakerPaysRate; + cLog(lsINFO) << "applyOffer: saTakerFundsAvailable=" << saTakerFundsAvailable.getFullText(); - if (saOfferGets == saOfferGetsAvailable && saTakerFundsAvailable >= saOfferGets) + STAmount saOfferPaysAvailable; // Amount offer can pay out, limited by offer and offerer funds. + STAmount saOfferGetsAvailable; // Amount offer would get, limited by offer funds. + + if (saOfferFundsAvailable >= saOfferPays) { - // Taker gets all of offer available. - saTakerPaid = saOfferGets; // Taker paid what offer could get. - saTakerGot = saOfferPays; // Taker got what offer could pay. + // Offer was fully funded, avoid math shenanigans. - Log(lsINFO) << "applyOffer: took all outright"; - } - else if (saTakerFunds >= saOfferGetsAvailable) - { - // Taker gets all of offer available. - saTakerPaid = saOfferGetsAvailable; // Taker paid what offer could get. - saTakerGot = saOfferPaysAvailable; // Taker got what offer could pay. - - Log(lsINFO) << "applyOffer: took all available"; + saOfferPaysAvailable = saOfferPays; + saOfferGetsAvailable = saOfferGets; } else { - // Taker only get's a portion of offer. - saTakerPaid = saTakerFunds; // Taker paid all he had. - 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(); + // Offer has limited funding, limit offer gets and pays by funds available. + saOfferPaysAvailable = saOfferFundsAvailable; + saOfferGetsAvailable = multiply(saOfferFundsAvailable, saOfferRate, saOfferGets); } + cLog(lsINFO) << "applyOffer: saOfferPaysAvailable=" << saOfferFundsAvailable.getFullText(); + cLog(lsINFO) << "applyOffer: saOfferGetsAvailable=" << saOfferGetsAvailable.getFullText(); + + STAmount saTakerGetsAvailable = saTakerFunds >= saOfferGetsAvailable + ? saTakerGets + : multiply(saTakerFunds, saOfferRate, saTakerGets); // Amount can afford. + + saTakerGot = std::min(saTakerGets, saOfferPaysAvailable); // Limit by wanted and available. + saTakerPaid = saTakerGot == saOfferPaysAvailable + ? saOfferGetsAvailable + : multiply(saTakerGot, saOfferRate, saTakerFunds); + if (uTakerPaysRate == QUALITY_ONE) { saTakerIssuerFee = STAmount(saTakerPaid.getCurrency(), saTakerPaid.getIssuer()); @@ -1079,7 +1100,7 @@ bool STAmount::applyOffer( // Compute fees in a rounding safe way. STAmount saTotal = STAmount::multiply(saTakerPaid, STAmount(CURRENCY_ONE, uTakerPaysRate, -9)); - saTakerIssuerFee = (saTotal > saTakerFunds) ? saTakerFunds-saTakerPaid : saTotal - saTakerPaid; + saTakerIssuerFee = (saTotal > saTakerFunds) ? saTakerFunds-saTakerPaid : saTotal-saTakerPaid; } if (uOfferPaysRate == QUALITY_ONE) @@ -1089,11 +1110,13 @@ bool STAmount::applyOffer( else { // Compute fees in a rounding safe way. - STAmount saTotal = STAmount::multiply(saTakerPaid, STAmount(CURRENCY_ONE, uTakerPaysRate, -9)); + STAmount saTotal = STAmount::multiply(saTakerGot, STAmount(CURRENCY_ONE, uOfferPaysRate, -9)); - saOfferIssuerFee = (saTotal > saOfferFunds) ? saOfferFunds-saTakerGot : saTotal - saTakerGot; + saOfferIssuerFee = (saTotal > saOfferFunds) ? saOfferFunds-saTakerGot : saTotal-saTakerGot; } + cLog(lsINFO) << "applyOffer: saTakerGot=" << saTakerGot.getFullText(); + return saTakerGot >= saOfferPays; } @@ -1119,13 +1142,13 @@ uint64 STAmount::muldiv(uint64 a, uint64 b, uint64 c) if ((a == 0) || (b == 0)) return 0; CBigNum v; - if ((BN_add_word(&v, a * 10 + 5) != 1) || - (BN_mul_word(&v, b * 10 + 5) != 1) || - (BN_div_word(&v, c) == ((BN_ULONG) -1)) || - (BN_div_word(&v, 100) == ((BN_ULONG) -1))) + if ((BN_add_word64(&v, a * 10 + 5) != 1) || + (BN_mul_word64(&v, b * 10 + 5) != 1) || + (BN_div_word64(&v, c) == ((uint64) -1)) || + (BN_div_word64(&v, 100) == ((uint64) -1))) throw std::runtime_error("muldiv error"); - return v.getulong(); + return v.getuint64(); } uint64 STAmount::convertToDisplayAmount(const STAmount& internalAmount, uint64 totalNow, uint64 totalInit) @@ -1239,7 +1262,6 @@ BOOST_AUTO_TEST_CASE( NativeCurrency_test ) { STAmount zero, one(1), hundred(100); - if (sizeof(BN_ULONG) < (64 / 8)) BOOST_FAIL("BN too small"); if (serdes(zero) != zero) BOOST_FAIL("STAmount fail"); if (serdes(one) != one) BOOST_FAIL("STAmount fail"); if (serdes(hundred) != hundred) BOOST_FAIL("STAmount fail"); @@ -1385,16 +1407,16 @@ BOOST_AUTO_TEST_CASE( CustomCurrency_test ) if (STAmount(CURRENCY_ONE, ACCOUNT_ONE, 31,-2).getText() != "0.31") BOOST_FAIL("STAmount fail"); if (STAmount::multiply(STAmount(CURRENCY_ONE, ACCOUNT_ONE, 20), STAmount(3), CURRENCY_ONE, ACCOUNT_ONE).getText() != "60") - BOOST_FAIL("STAmount multiply fail"); + BOOST_FAIL("STAmount multiply fail 1"); if (STAmount::multiply(STAmount(CURRENCY_ONE, ACCOUNT_ONE, 20), STAmount(3), uint160(), ACCOUNT_XRP).getText() != "60") - BOOST_FAIL("STAmount multiply fail"); + BOOST_FAIL("STAmount multiply fail 2"); if (STAmount::multiply(STAmount(20), STAmount(3), CURRENCY_ONE, ACCOUNT_ONE).getText() != "60") - BOOST_FAIL("STAmount multiply fail"); + BOOST_FAIL("STAmount multiply fail 3"); if (STAmount::multiply(STAmount(20), STAmount(3), uint160(), ACCOUNT_XRP).getText() != "60") - BOOST_FAIL("STAmount multiply fail"); + BOOST_FAIL("STAmount multiply fail 4"); if (STAmount::divide(STAmount(CURRENCY_ONE, ACCOUNT_ONE, 60), STAmount(3), CURRENCY_ONE, ACCOUNT_ONE).getText() != "20") { - Log(lsFATAL) << "60/3 = " << + cLog(lsFATAL) << "60/3 = " << STAmount::divide(STAmount(CURRENCY_ONE, ACCOUNT_ONE, 60), STAmount(3), CURRENCY_ONE, ACCOUNT_ONE).getText(); BOOST_FAIL("STAmount divide fail"); @@ -1449,7 +1471,7 @@ static void mulTest(int a, int b) STAmount prod2(CURRENCY_ONE, ACCOUNT_ONE, static_cast(a) * static_cast(b)); if (prod1 != prod2) { - Log(lsWARNING) << "nn(" << aa.getFullText() << " * " << bb.getFullText() << ") = " << prod1.getFullText() + cLog(lsWARNING) << "nn(" << aa.getFullText() << " * " << bb.getFullText() << ") = " << prod1.getFullText() << " not " << prod2.getFullText(); BOOST_WARN("Multiplication result is not exact"); } @@ -1457,7 +1479,7 @@ static void mulTest(int a, int b) prod1 = STAmount::multiply(aa, bb, CURRENCY_ONE, ACCOUNT_ONE); if (prod1 != prod2) { - Log(lsWARNING) << "n(" << aa.getFullText() << " * " << bb.getFullText() << ") = " << prod1.getFullText() + cLog(lsWARNING) << "n(" << aa.getFullText() << " * " << bb.getFullText() << ") = " << prod1.getFullText() << " not " << prod2.getFullText(); BOOST_WARN("Multiplication result is not exact"); } @@ -1466,25 +1488,39 @@ static void mulTest(int a, int b) BOOST_AUTO_TEST_CASE( CurrencyMulDivTests ) { + CBigNum b; + for (int i = 0; i < 16; ++i) + { + uint64 r = rand(); + r <<= 32; + r |= rand(); + b.setuint64(r); + if (b.getuint64() != r) + { + cLog(lsFATAL) << r << " != " << b.getuint64() << " " << b.ToString(16); + BOOST_FAIL("setull64/getull64 failure"); + } + } + // Test currency multiplication and division operations such as // convertToDisplayAmount, convertToInternalAmount, getRate, getClaimed, and getNeeded - if (STAmount::getRate(STAmount(1), STAmount(10)) != (((100ul-14)<<(64-8))|1000000000000000ul)) - 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(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"); + if (STAmount::getRate(STAmount(1), STAmount(10)) != (((100ull-14)<<(64-8))|1000000000000000ull)) + BOOST_FAIL("STAmount getRate fail 1"); + if (STAmount::getRate(STAmount(10), STAmount(1)) != (((100ull-16)<<(64-8))|1000000000000000ull)) + BOOST_FAIL("STAmount getRate fail 2"); + if (STAmount::getRate(STAmount(CURRENCY_ONE, ACCOUNT_ONE, 1), STAmount(CURRENCY_ONE, ACCOUNT_ONE, 10)) != (((100ull-14)<<(64-8))|1000000000000000ull)) + BOOST_FAIL("STAmount getRate fail 3"); + if (STAmount::getRate(STAmount(CURRENCY_ONE, ACCOUNT_ONE, 10), STAmount(CURRENCY_ONE, ACCOUNT_ONE, 1)) != (((100ull-16)<<(64-8))|1000000000000000ull)) + BOOST_FAIL("STAmount getRate fail 4"); + if (STAmount::getRate(STAmount(CURRENCY_ONE, ACCOUNT_ONE, 1), STAmount(10)) != (((100ull-14)<<(64-8))|1000000000000000ull)) + BOOST_FAIL("STAmount getRate fail 5"); + if (STAmount::getRate(STAmount(CURRENCY_ONE, ACCOUNT_ONE, 10), STAmount(1)) != (((100ull-16)<<(64-8))|1000000000000000ull)) + BOOST_FAIL("STAmount getRate fail 6"); + if (STAmount::getRate(STAmount(1), STAmount(CURRENCY_ONE, ACCOUNT_ONE, 10)) != (((100ull-14)<<(64-8))|1000000000000000ull)) + BOOST_FAIL("STAmount getRate fail 7"); + if (STAmount::getRate(STAmount(10), STAmount(CURRENCY_ONE, ACCOUNT_ONE, 1)) != (((100ull-16)<<(64-8))|1000000000000000ull)) + BOOST_FAIL("STAmount getRate fail 8"); roundTest(1, 3, 3); roundTest(2, 3, 9); roundTest(1, 7, 21); roundTest(1, 2, 4); roundTest(3, 9, 18); roundTest(7, 11, 44); diff --git a/src/cpp/ripple/Application.cpp b/src/cpp/ripple/Application.cpp index d8b7b8fa3..58b75e8b4 100644 --- a/src/cpp/ripple/Application.cpp +++ b/src/cpp/ripple/Application.cpp @@ -18,7 +18,9 @@ #include SETUP_LOG(); + LogPartition TaggedCachePartition("TaggedCache"); +LogPartition AutoSocketPartition("AutoSocket"); Application* theApp = NULL; DatabaseCon::DatabaseCon(const std::string& strName, const char *initStrings[], int initCount) @@ -40,7 +42,7 @@ DatabaseCon::~DatabaseCon() Application::Application() : mIOWork(mIOService), mAuxWork(mAuxService), mUNL(mIOService), mNetOps(mIOService, &mLedgerMaster), mTempNodeCache("NodeCache", 16384, 90), mHashedObjectStore(16384, 300), - mSNTPClient(mAuxService), mRPCHandler(&mNetOps), + mSNTPClient(mAuxService), mRPCHandler(&mNetOps), mFeeTrack(), mRpcDB(NULL), mTxnDB(NULL), mLedgerDB(NULL), mWalletDB(NULL), mHashNodeDB(NULL), mNetNodeDB(NULL), mConnectionPool(mIOService), mPeerDoor(NULL), mRPCDoor(NULL), mWSPublicDoor(NULL), mWSPrivateDoor(NULL), mSweepTimer(mAuxService) @@ -54,9 +56,11 @@ Application::Application() : extern const char *RpcDBInit[], *TxnDBInit[], *LedgerDBInit[], *WalletDBInit[], *HashNodeDBInit[], *NetNodeDBInit[]; extern int RpcDBCount, TxnDBCount, LedgerDBCount, WalletDBCount, HashNodeDBCount, NetNodeDBCount; +bool Instance::running = true; void Application::stop() { + cLog(lsINFO) << "Received shutdown request"; mIOService.stop(); mJobQueue.shutdown(); mHashedObjectStore.bulkWrite(); @@ -64,6 +68,7 @@ void Application::stop() mAuxService.stop(); cLog(lsINFO) << "Stopped: " << mIOService.stopped(); + Instance::shutdown(); } static void InitDB(DatabaseCon** dbCon, const char *fileName, const char *dbInit[], int dbCount) @@ -80,7 +85,7 @@ void sigIntHandler(int) } #endif -void Application::run() +void Application::setup() { #ifndef WIN32 #ifdef SIGINT @@ -102,9 +107,7 @@ void Application::run() LogPartition::setSeverity(lsDEBUG); } - boost::thread auxThread(boost::bind(&boost::asio::io_service::run, &mAuxService)); - auxThread.detach(); - + boost::thread(boost::bind(&boost::asio::io_service::run, &mAuxService)).detach(); if (!theConfig.RUN_STANDALONE) mSNTPClient.init(theConfig.SNTP_SERVERS); @@ -119,16 +122,21 @@ void Application::run() boost::thread t5(boost::bind(&InitDB, &mHashNodeDB, "hashnode.db", HashNodeDBInit, HashNodeDBCount)); boost::thread t6(boost::bind(&InitDB, &mNetNodeDB, "netnode.db", NetNodeDBInit, NetNodeDBCount)); t1.join(); t2.join(); t3.join(); t4.join(); t5.join(); t6.join(); + mTxnDB->getDB()->setupCheckpointing(); + mLedgerDB->getDB()->setupCheckpointing(); + mHashNodeDB->getDB()->setupCheckpointing(); if (theConfig.START_UP == Config::FRESH) { cLog(lsINFO) << "Starting new Ledger"; + startNewLedger(); } else if (theConfig.START_UP == Config::LOAD) { - cLog(lsINFO) << "Loading Old Ledger"; - loadOldLedger(); + cLog(lsINFO) << "Loading specified Ledger"; + + loadOldLedger(theConfig.START_LEDGER); } else if (theConfig.START_UP == Config::NETWORK) { // This should probably become the default once we have a stable network @@ -157,7 +165,17 @@ void Application::run() // if (!theConfig.RUN_STANDALONE && !theConfig.PEER_IP.empty() && theConfig.PEER_PORT) { - mPeerDoor = new PeerDoor(mIOService); + try + { + mPeerDoor = new PeerDoor(mIOService); + } + catch (const std::exception& e) + { + // Must run as directed or exit. + cLog(lsFATAL) << boost::str(boost::format("Can not open peer service: %s") % e.what()); + + exit(3); + } } else { @@ -169,7 +187,17 @@ void Application::run() // if (!theConfig.RPC_IP.empty() && theConfig.RPC_PORT) { - mRPCDoor = new RPCDoor(mIOService); + try + { + mRPCDoor = new RPCDoor(mIOService); + } + catch (const std::exception& e) + { + // Must run as directed or exit. + cLog(lsFATAL) << boost::str(boost::format("Can not open RPC service: %s") % e.what()); + + exit(3); + } } else { @@ -181,7 +209,17 @@ void Application::run() // if (!theConfig.WEBSOCKET_IP.empty() && theConfig.WEBSOCKET_PORT) { - mWSPrivateDoor = WSDoor::createWSDoor(theConfig.WEBSOCKET_IP, theConfig.WEBSOCKET_PORT, false); + try + { + mWSPrivateDoor = WSDoor::createWSDoor(theConfig.WEBSOCKET_IP, theConfig.WEBSOCKET_PORT, false); + } + catch (const std::exception& e) + { + // Must run as directed or exit. + cLog(lsFATAL) << boost::str(boost::format("Can not open private websocket service: %s") % e.what()); + + exit(3); + } } else { @@ -193,7 +231,17 @@ void Application::run() // if (!theConfig.WEBSOCKET_PUBLIC_IP.empty() && theConfig.WEBSOCKET_PUBLIC_PORT) { - mWSPublicDoor = WSDoor::createWSDoor(theConfig.WEBSOCKET_PUBLIC_IP, theConfig.WEBSOCKET_PUBLIC_PORT, true); + try + { + mWSPublicDoor = WSDoor::createWSDoor(theConfig.WEBSOCKET_PUBLIC_IP, theConfig.WEBSOCKET_PUBLIC_PORT, true); + } + catch (const std::exception& e) + { + // Must run as directed or exit. + cLog(lsFATAL) << boost::str(boost::format("Can not open public websocket service: %s") % e.what()); + + exit(3); + } } else { @@ -210,11 +258,15 @@ void Application::run() if (theConfig.RUN_STANDALONE) { cLog(lsWARNING) << "Running in standalone mode"; + mNetOps.setStandAlone(); } else mNetOps.setStateTimer(); +} +void Application::run() +{ mIOService.run(); // This blocks if (mWSPublicDoor) @@ -228,11 +280,20 @@ void Application::run() void Application::sweep() { + + boost::filesystem::space_info space = boost::filesystem::space(theConfig.DATA_DIR); + if (space.available < (128 * 1024 * 1024)) + { + cLog(lsFATAL) << "Remaining free disk space is less than 128MB"; + theApp->stop(); + } + mMasterTransaction.sweep(); mHashedObjectStore.sweep(); mLedgerMaster.sweep(); mTempNodeCache.sweep(); mValidations.sweep(); + getMasterLedgerAcquire().sweep(); mSweepTimer.expires_from_now(boost::posix_time::seconds(60)); mSweepTimer.async_wait(boost::bind(&Application::sweep, this)); } @@ -274,49 +335,65 @@ void Application::startNewLedger() } } -void Application::loadOldLedger() +void Application::loadOldLedger(const std::string& l) { try { - Ledger::pointer lastLedger = Ledger::getLastFullLedger(); + Ledger::pointer loadLedger; + if (l.empty() || (l == "latest")) + loadLedger = Ledger::getLastFullLedger(); + else if (l.length() == 64) + { // by hash + uint256 hash; + hash.SetHex(l); + loadLedger = Ledger::loadByHash(hash); + } + else // assume by sequence + loadLedger = Ledger::loadByIndex(boost::lexical_cast(l)); - if (!lastLedger) + if (!loadLedger) { cLog(lsFATAL) << "No Ledger found?" << std::endl; exit(-1); } - lastLedger->setClosed(); + loadLedger->setClosed(); - cLog(lsINFO) << "Loading ledger " << lastLedger->getHash() << " seq:" << lastLedger->getLedgerSeq(); + cLog(lsINFO) << "Loading ledger " << loadLedger->getHash() << " seq:" << loadLedger->getLedgerSeq(); - if (lastLedger->getAccountHash().isZero()) + if (loadLedger->getAccountHash().isZero()) { cLog(lsFATAL) << "Ledger is empty."; assert(false); exit(-1); } - if (!lastLedger->walkLedger()) + if (!loadLedger->walkLedger()) { cLog(lsFATAL) << "Ledger is missing nodes."; exit(-1); } - if (!lastLedger->assertSane()) + if (!loadLedger->assertSane()) { cLog(lsFATAL) << "Ledger is not sane."; exit(-1); } - mLedgerMaster.setLedgerRangePresent(0, lastLedger->getLedgerSeq()); + mLedgerMaster.setLedgerRangePresent(loadLedger->getLedgerSeq(), loadLedger->getLedgerSeq()); - Ledger::pointer openLedger = boost::make_shared(false, boost::ref(*lastLedger)); - mLedgerMaster.switchLedgers(lastLedger, openLedger); - mNetOps.setLastCloseTime(lastLedger->getCloseTimeNC()); + Ledger::pointer openLedger = boost::make_shared(false, boost::ref(*loadLedger)); + mLedgerMaster.switchLedgers(loadLedger, openLedger); + mNetOps.setLastCloseTime(loadLedger->getCloseTimeNC()); } catch (SHAMapMissingNode& mn) { - cLog(lsFATAL) << "Cannot load ledger. " << mn; + cLog(lsFATAL) << "Data is missing for selected ledger"; + exit(-1); + } + catch (boost::bad_lexical_cast& blc) + { + cLog(lsFATAL) << "Ledger specified '" << l << "' is not valid"; exit(-1); } } + // vim:ts=4 diff --git a/src/cpp/ripple/Application.h b/src/cpp/ripple/Application.h index 4a2b16e91..7ba1003ce 100644 --- a/src/cpp/ripple/Application.h +++ b/src/cpp/ripple/Application.h @@ -23,6 +23,7 @@ #include "RPCHandler.h" #include "ProofOfWork.h" #include "LoadManager.h" +#include "TransactionQueue.h" class RPCDoor; class PeerDoor; @@ -63,6 +64,8 @@ class Application RPCHandler mRPCHandler; ProofOfWorkGenerator mPOWGen; LoadManager mLoadMgr; + LoadFeeTrack mFeeTrack; + TXQueue mTxnQueue; DatabaseCon *mRpcDB, *mTxnDB, *mLedgerDB, *mWalletDB, *mHashNodeDB, *mNetNodeDB; @@ -81,7 +84,7 @@ class Application boost::recursive_mutex mPeerMapLock; void startNewLedger(); - void loadOldLedger(); + void loadOldLedger(const std::string&); public: Application(); @@ -109,6 +112,9 @@ public: boost::recursive_mutex& getMasterLock() { return mMasterLock; } ProofOfWorkGenerator& getPowGen() { return mPOWGen; } LoadManager& getLoadManager() { return mLoadMgr; } + LoadFeeTrack& getFeeTrack() { return mFeeTrack; } + TXQueue& getTxnQueue() { return mTxnQueue; } + PeerDoor& getPeerDoor() { return *mPeerDoor; } bool isNew(const uint256& s) { return mSuppressions.addSuppression(s); } @@ -128,6 +134,7 @@ public: uint256 getNonce256() { return mNonce256; } std::size_t getNonceST() { return mNonceST; } + void setup(); void run(); void stop(); void sweep(); diff --git a/src/cpp/ripple/AutoSocket.h b/src/cpp/ripple/AutoSocket.h new file mode 100644 index 000000000..1a3b6db7b --- /dev/null +++ b/src/cpp/ripple/AutoSocket.h @@ -0,0 +1,204 @@ +#ifndef __AUTOSOCKET_H_ +#define __AUTOSOCKET_H_ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "Log.h" +extern LogPartition AutoSocketPartition; + +// Socket wrapper that supports both SSL and non-SSL connections. +// Generally, handle it as you would an SSL connection. +// To force a non-SSL connection, just don't call async_handshake. +// To force SSL only inbound, call setSSLOnly. + +namespace basio = boost::asio; +namespace bassl = basio::ssl; + +class AutoSocket +{ +public: + typedef bassl::stream ssl_socket; + typedef boost::shared_ptr socket_ptr; + typedef ssl_socket::next_layer_type plain_socket; + typedef ssl_socket::lowest_layer_type lowest_layer_type; + typedef ssl_socket::handshake_type handshake_type; + typedef boost::system::error_code error_code; + typedef boost::function callback; + +protected: + socket_ptr mSocket; + bool mSecure; + + std::vector mBuffer; + +public: + AutoSocket(basio::io_service& s, bassl::context& c) : mSecure(false), mBuffer(4) + { + mSocket = boost::make_shared(boost::ref(s), boost::ref(c)); + } + + AutoSocket(basio::io_service& s, bassl::context& c, bool secureOnly, bool plainOnly) + : mSecure(secureOnly), mBuffer((plainOnly || secureOnly) ? 0 : 4) + { + mSocket = boost::make_shared(boost::ref(s), boost::ref(c)); + } + + bool isSecure() { return mSecure; } + ssl_socket& SSLSocket() { return *mSocket; } + plain_socket& PlainSocket() { return mSocket->next_layer(); } + void setSSLOnly() { mSecure = true;} + void setPlainOnly() { mBuffer.clear(); } + + lowest_layer_type& lowest_layer() { return mSocket->lowest_layer(); } + + void swap(AutoSocket& s) + { + mBuffer.swap(s.mBuffer); + mSocket.swap(s.mSocket); + std::swap(mSecure, s.mSecure); + } + + void async_handshake(handshake_type type, callback cbFunc) + { + if ((type == ssl_socket::client) || (mSecure)) + { // must be ssl + mSecure = true; + mSocket->async_handshake(type, cbFunc); + } + else if (mBuffer.empty()) + { // must be plain + mSecure = false; + mSocket->get_io_service().post(boost::bind(cbFunc, error_code())); + } + else + { // autodetect + mSocket->next_layer().async_receive(basio::buffer(mBuffer), basio::socket_base::message_peek, + boost::bind(&AutoSocket::handle_autodetect, this, cbFunc, basio::placeholders::error)); + } + } + + template void async_shutdown(ShutdownHandler handler) + { + if (isSecure()) + mSocket->async_shutdown(handler); + else + { + lowest_layer().shutdown(plain_socket::shutdown_both); + mSocket->get_io_service().post(boost::bind(handler, error_code())); + } + } + + template void async_read_some(const Seq& buffers, Handler handler) + { + if (isSecure()) + mSocket->async_read_some(buffers, handler); + else + PlainSocket().async_read_some(buffers, handler); + } + + template + void async_read_until(const Seq& buffers, Condition condition, Handler handler) + { + if (isSecure()) + basio::async_read_until(*mSocket, buffers, condition, handler); + else + basio::async_read_until(PlainSocket(), buffers, condition, handler); + } + + template + void async_read_until(basio::basic_streambuf& buffers, const std::string& delim, Handler handler) + { + if (isSecure()) + basio::async_read_until(*mSocket, buffers, delim, handler); + else + basio::async_read_until(PlainSocket(), buffers, delim, handler); + } + + template + void async_read_until(basio::basic_streambuf& buffers, MatchCondition cond, Handler handler) + { + if (isSecure()) + basio::async_read_until(*mSocket, buffers, cond, handler); + else + basio::async_read_until(PlainSocket(), buffers, cond, handler); + } + + template void async_write(const Buf& buffers, Handler handler) + { + if (isSecure()) + boost::asio::async_write(*mSocket, buffers, handler); + else + boost::asio::async_write(PlainSocket(), buffers, handler); + } + + + template + void async_read(const Buf& buffers, Condition cond, Handler handler) + { + if (isSecure()) + boost::asio::async_read(*mSocket, buffers, cond, handler); + else + boost::asio::async_read(PlainSocket(), buffers, cond, handler); + } + + template + void async_read(basio::basic_streambuf& buffers, Condition cond, Handler handler) + { + if (isSecure()) + boost::asio::async_read(*mSocket, buffers, cond, handler); + else + boost::asio::async_read(PlainSocket(), buffers, cond, handler); + } + + template void async_read(const Buf& buffers, Handler handler) + { + if (isSecure()) + boost::asio::async_read(*mSocket, buffers, handler); + else + boost::asio::async_read(PlainSocket(), buffers, handler); + } + + template void async_write_some(const Seq& buffers, Handler handler) + { + if (isSecure()) + mSocket->async_write_some(buffers, handler); + else + PlainSocket().async_write_some(buffers, handler); + } + +protected: + void handle_autodetect(callback cbFunc, const error_code& ec) + { + if (ec) + { + Log(lsWARNING, AutoSocketPartition) << "Handle autodetect error: " << ec; + cbFunc(ec); + } + else if ((mBuffer[0] < 127) && (mBuffer[0] > 31) && + (mBuffer[1] < 127) && (mBuffer[1] > 31) && + (mBuffer[2] < 127) && (mBuffer[2] > 31) && + (mBuffer[3] < 127) && (mBuffer[3] > 31)) + { // not ssl + mSecure = false; + cbFunc(ec); + } + else + { // ssl + mSecure = true; + mSocket->async_handshake(ssl_socket::server, cbFunc); + } + } +}; + +#endif + +// vim:ts=4 diff --git a/src/cpp/ripple/BigNum64.h b/src/cpp/ripple/BigNum64.h new file mode 100644 index 000000000..9150a25ca --- /dev/null +++ b/src/cpp/ripple/BigNum64.h @@ -0,0 +1,22 @@ + +// Support 64-bit word operations on 32-bit platforms + +static int BN_add_word64(BIGNUM *a, uint64 w) +{ + CBigNum bn(w); + return BN_add(a, &bn, a); +} + +static int BN_mul_word64(BIGNUM *a, uint64 w) +{ + CBigNum bn(w); + CAutoBN_CTX ctx; + return BN_mul(a, &bn, a, ctx); +} + +static uint64 BN_div_word64(BIGNUM *a, uint64 w) +{ + CBigNum bn(w); + CAutoBN_CTX ctx; + return (BN_div(a, NULL, a, &bn, ctx) == 1) ? 0 : ((uint64)-1); +} diff --git a/src/cpp/ripple/CallRPC.cpp b/src/cpp/ripple/CallRPC.cpp index b18a3c212..13f3e7ff4 100644 --- a/src/cpp/ripple/CallRPC.cpp +++ b/src/cpp/ripple/CallRPC.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -13,6 +14,7 @@ #include "../json/value.h" #include "../json/reader.h" +#include "Application.h" #include "RPC.h" #include "Log.h" #include "RPCErr.h" @@ -53,7 +55,25 @@ std::string EncodeBase64(const std::string& s) Json::Value RPCParser::parseAsIs(const Json::Value& jvParams) { - return Json::Value(Json::objectValue); + Json::Value v(Json::objectValue); + + if (jvParams.isArray() && (jvParams.size() > 0)) + v["params"] = jvParams; + + return v; +} + +Json::Value RPCParser::parseInternal(const Json::Value& jvParams) +{ + Json::Value v(Json::objectValue); + v["internal_command"] = jvParams[0u]; + + Json::Value params(Json::arrayValue); + for (unsigned i = 1; i < jvParams.size(); ++i) + params.append(jvParams[i]); + v["params"] = params; + + return v; } // account_info || @@ -126,6 +146,7 @@ Json::Value RPCParser::parseConnect(const Json::Value& jvParams) return jvRequest; } +#if ENABLE_INSECURE // data_delete Json::Value RPCParser::parseDataDelete(const Json::Value& jvParams) { @@ -135,7 +156,9 @@ Json::Value RPCParser::parseDataDelete(const Json::Value& jvParams) return jvRequest; } +#endif +#if ENABLE_INSECURE // data_fetch Json::Value RPCParser::parseDataFetch(const Json::Value& jvParams) { @@ -145,7 +168,9 @@ Json::Value RPCParser::parseDataFetch(const Json::Value& jvParams) return jvRequest; } +#endif +#if ENABLE_INSECURE // data_store Json::Value RPCParser::parseDataStore(const Json::Value& jvParams) { @@ -156,6 +181,7 @@ Json::Value RPCParser::parseDataStore(const Json::Value& jvParams) return jvRequest; } +#endif // Return an error for attemping to subscribe/unsubscribe via RPC. Json::Value RPCParser::parseEvented(const Json::Value& jvParams) @@ -203,6 +229,7 @@ Json::Value RPCParser::parseLedger(const Json::Value& jvParams) return jvRequest; } +#if ENABLE_INSECURE // login Json::Value RPCParser::parseLogin(const Json::Value& jvParams) { @@ -213,6 +240,7 @@ Json::Value RPCParser::parseLogin(const Json::Value& jvParams) return jvRequest; } +#endif // log_level: Get log levels // log_level : Set master log level to the specified severity @@ -262,15 +290,43 @@ Json::Value RPCParser::parseAccountItems(const Json::Value& jvParams) return jvRequest; } - -// submit any transaction to the network -// submit private_key json -Json::Value RPCParser::parseSubmit(const Json::Value& jvParams) +// ripple_path_find json +Json::Value RPCParser::parseRipplePathFind(const Json::Value& jvParams) { Json::Value txJSON; Json::Reader reader; - if (reader.parse(jvParams[1u].asString(), txJSON)) + cLog(lsTRACE) << "RPC json:" << jvParams[0u]; + if (reader.parse(jvParams[0u].asString(), txJSON)) + { + return txJSON; + } + + return rpcError(rpcINVALID_PARAMS); +} + +// sign/submit any transaction to the network +// +// sign private_key json +// submit private_key json +// submit tx_blob +Json::Value RPCParser::parseSignSubmit(const Json::Value& jvParams) +{ + Json::Value txJSON; + Json::Reader reader; + + if (1 == jvParams.size()) + { + // Submitting tx_blob + + Json::Value jvRequest; + + jvRequest["tx_blob"] = jvParams[0u].asString(); + + return jvRequest; + } + // Submitting tx_json. + else if (reader.parse(jvParams[1u].asString(), txJSON)) { Json::Value jvRequest; @@ -436,9 +492,11 @@ Json::Value RPCParser::parseCommand(std::string strMethod, Json::Value jvParams) { "peers", &RPCParser::parseAsIs, 0, 0 }, // { "profile", &RPCParser::parseProfile, 1, 9 }, { "random", &RPCParser::parseAsIs, 0, 0 }, -// { "ripple_path_find", &RPCParser::parseRipplePathFind, -1, -1 }, - { "submit", &RPCParser::parseSubmit, 2, 2 }, + { "ripple_path_find", &RPCParser::parseRipplePathFind, 1, 1 }, + { "sign", &RPCParser::parseSignSubmit, 2, 2 }, + { "submit", &RPCParser::parseSignSubmit, 1, 2 }, { "server_info", &RPCParser::parseAsIs, 0, 0 }, + { "server_state", &RPCParser::parseAsIs, 0, 0 }, { "stop", &RPCParser::parseAsIs, 0, 0 }, // { "transaction_entry", &RPCParser::parseTransactionEntry, -1, -1 }, { "tx", &RPCParser::parseTx, 1, 1 }, @@ -459,11 +517,15 @@ Json::Value RPCParser::parseCommand(std::string strMethod, Json::Value jvParams) { "wallet_propose", &RPCParser::parseWalletPropose, 0, 1 }, { "wallet_seed", &RPCParser::parseWalletSeed, 0, 1 }, + { "internal", &RPCParser::parseInternal, 1, -1 }, + +#if ENABLE_INSECURE // XXX Unnecessary commands which should be removed. { "login", &RPCParser::parseLogin, 2, 2 }, { "data_delete", &RPCParser::parseDataDelete, 1, 1 }, { "data_fetch", &RPCParser::parseDataFetch, 1, 1 }, { "data_store", &RPCParser::parseDataStore, 2, 2 }, +#endif // Evented methods { "subscribe", &RPCParser::parseEvented, -1, -1 }, @@ -477,7 +539,7 @@ Json::Value RPCParser::parseCommand(std::string strMethod, Json::Value jvParams) if (i < 0) { - return rpcError(rpcBAD_SYNTAX); + return rpcError(rpcUNKNOWN_COMMAND); } else if ((commandsA[i].iMinParams >= 0 && jvParams.size() < commandsA[i].iMinParams) || (commandsA[i].iMaxParams >= 0 && jvParams.size() > commandsA[i].iMaxParams)) @@ -528,7 +590,18 @@ int commandLineRPC(const std::vector& vCmd) jvParams.append(jvRequest); + if (!theConfig.RPC_ADMIN_USER.empty()) + jvRequest["admin_user"] = theConfig.RPC_ADMIN_USER; + + if (!theConfig.RPC_ADMIN_PASSWORD.empty()) + jvRequest["admin_password"] = theConfig.RPC_ADMIN_PASSWORD; + jvOutput = callRPC( + theConfig.RPC_IP, + theConfig.RPC_PORT, + theConfig.RPC_USER, + theConfig.RPC_PASSWORD, + "", jvRequest.isMember("method") // Allow parser to rewrite method. ? jvRequest["method"].asString() : vCmd[0], @@ -589,32 +662,38 @@ int commandLineRPC(const std::vector& vCmd) return nRet; } -Json::Value callRPC(const std::string& strMethod, const Json::Value& params) +Json::Value callRPC(const std::string& strIp, const int iPort, const std::string& strUsername, const std::string& strPassword, const std::string& strPath, const std::string& strMethod, const Json::Value& jvParams) { - if (theConfig.RPC_USER.empty() && theConfig.RPC_PASSWORD.empty()) - throw std::runtime_error("You must set rpcpassword= in the configuration file. " - "If the file does not exist, create it with owner-readable-only file permissions."); - // Connect to localhost if (!theConfig.QUIET) - std::cerr << "Connecting to: " << theConfig.RPC_IP << ":" << theConfig.RPC_PORT << std::endl; + { + std::cerr << "Connecting to: " << strIp << ":" << iPort << std::endl; +// std::cerr << "Username: " << strUsername << ":" << strPassword << std::endl; +// std::cerr << "Path: " << strPath << std::endl; +// std::cerr << "Method: " << strMethod << std::endl; + } boost::asio::ip::tcp::endpoint - endpoint(boost::asio::ip::address::from_string(theConfig.RPC_IP), theConfig.RPC_PORT); + endpoint(boost::asio::ip::address::from_string(strIp), iPort); boost::asio::ip::tcp::iostream stream; stream.connect(endpoint); if (stream.fail()) throw std::runtime_error("couldn't connect to server"); + // cLog(lsDEBUG) << "connected" << std::endl; + // HTTP basic authentication - std::string strUserPass64 = EncodeBase64(theConfig.RPC_USER + ":" + theConfig.RPC_PASSWORD); + std::string strUserPass64 = EncodeBase64(strUsername + ":" + strPassword); std::map mapRequestHeaders; mapRequestHeaders["Authorization"] = std::string("Basic ") + strUserPass64; + // Log(lsDEBUG) << "requesting" << std::endl; + // Send request - std::string strRequest = JSONRPCRequest(strMethod, params, Json::Value(1)); - cLog(lsDEBUG) << "send request " << strMethod << " : " << strRequest << std::endl; - std::string strPost = createHTTPPost(strRequest, mapRequestHeaders); + std::string strRequest = JSONRPCRequest(strMethod, jvParams, Json::Value(1)); + // cLog(lsDEBUG) << "send request " << strMethod << " : " << strRequest << std::endl; + + std::string strPost = createHTTPPost(strPath, strRequest, mapRequestHeaders); stream << strPost << std::flush; // std::cerr << "post " << strPost << std::endl; diff --git a/src/cpp/ripple/CallRPC.h b/src/cpp/ripple/CallRPC.h index e03572caf..7f8d2ebe6 100644 --- a/src/cpp/ripple/CallRPC.h +++ b/src/cpp/ripple/CallRPC.h @@ -1,7 +1,6 @@ #ifndef __CALLRPC__ #define __CALLRPC__ - #include #include "../json/value.h" @@ -16,17 +15,23 @@ protected: Json::Value parseAccountTransactions(const Json::Value& jvParams); Json::Value parseAsIs(const Json::Value& jvParams); Json::Value parseConnect(const Json::Value& jvParams); +#if ENABLE_INSECURE Json::Value parseDataDelete(const Json::Value& jvParams); Json::Value parseDataFetch(const Json::Value& jvParams); Json::Value parseDataStore(const Json::Value& jvParams); +#endif Json::Value parseEvented(const Json::Value& jvParams); Json::Value parseGetCounts(const Json::Value& jvParams); Json::Value parseLedger(const Json::Value& jvParams); + Json::Value parseInternal(const Json::Value& jvParams); +#if ENABLE_INSECURE Json::Value parseLogin(const Json::Value& jvParams); +#endif Json::Value parseLogLevel(const Json::Value& jvParams); Json::Value parseOwnerInfo(const Json::Value& jvParams); Json::Value parseRandom(const Json::Value& jvParams); - Json::Value parseSubmit(const Json::Value& jvParams); + Json::Value parseRipplePathFind(const Json::Value& jvParams); + Json::Value parseSignSubmit(const Json::Value& jvParams); Json::Value parseTx(const Json::Value& jvParams); Json::Value parseTxHistory(const Json::Value& jvParams); Json::Value parseUnlAdd(const Json::Value& jvParams); @@ -42,7 +47,7 @@ public: }; extern int commandLineRPC(const std::vector& vCmd); -extern Json::Value callRPC(const std::string& strMethod, const Json::Value& params); +extern Json::Value callRPC(const std::string& strIp, const int iPort, const std::string& strUsername, const std::string& strPassword, const std::string& strPath, const std::string& strMethod, const Json::Value& params); #endif diff --git a/src/cpp/ripple/Config.cpp b/src/cpp/ripple/Config.cpp index 4fe97a4b8..d45d35d65 100644 --- a/src/cpp/ripple/Config.cpp +++ b/src/cpp/ripple/Config.cpp @@ -1,17 +1,21 @@ // // TODO: Check permissions on config file before using it. // +#include +#include +#include +#include +#include +#include + #include "Config.h" #include "utils.h" - -#include -#include -#include -#include -#include +#include "HashPrefixes.h" #define SECTION_ACCOUNT_PROBE_MAX "account_probe_max" +#define SECTION_CLUSTER_NODES "cluster_nodes" +#define SECTION_DATABASE_PATH "database_path" #define SECTION_DEBUG_LOGFILE "debug_logfile" #define SECTION_FEE_DEFAULT "fee_default" #define SECTION_FEE_NICKNAME_CREATE "fee_nickname_create" @@ -22,6 +26,7 @@ #define SECTION_LEDGER_HISTORY "ledger_history" #define SECTION_IPS "ips" #define SECTION_NETWORK_QUORUM "network_quorum" +#define SECTION_NODE_SEED "node_seed" #define SECTION_PEER_CONNECT_LOW_WATER "peer_connect_low_water" #define SECTION_PEER_IP "peer_ip" #define SECTION_PEER_PORT "peer_port" @@ -30,14 +35,21 @@ #define SECTION_PEER_SSL_CIPHER_LIST "peer_ssl_cipher_list" #define SECTION_PEER_START_MAX "peer_start_max" #define SECTION_RPC_ALLOW_REMOTE "rpc_allow_remote" +#define SECTION_RPC_ADMIN_ALLOW "rpc_admin_allow" +#define SECTION_RPC_ADMIN_USER "rpc_admin_user" +#define SECTION_RPC_ADMIN_PASSWORD "rpc_admin_password" #define SECTION_RPC_IP "rpc_ip" #define SECTION_RPC_PORT "rpc_port" +#define SECTION_RPC_USER "rpc_user" +#define SECTION_RPC_PASSWORD "rpc_password" +#define SECTION_RPC_STARTUP "rpc_startup" #define SECTION_SNTP "sntp_servers" #define SECTION_VALIDATORS_FILE "validators_file" #define SECTION_VALIDATION_QUORUM "validation_quorum" #define SECTION_VALIDATION_SEED "validation_seed" #define SECTION_WEBSOCKET_PUBLIC_IP "websocket_public_ip" #define SECTION_WEBSOCKET_PUBLIC_PORT "websocket_public_port" +#define SECTION_WEBSOCKET_PUBLIC_SECURE "websocket_public_secure" #define SECTION_WEBSOCKET_IP "websocket_ip" #define SECTION_WEBSOCKET_PORT "websocket_port" #define SECTION_WEBSOCKET_SECURE "websocket_secure" @@ -56,10 +68,12 @@ #define DEFAULT_FEE_OPERATION 1 Config theConfig; +const char* ALPHABET = "rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz"; -void Config::setup(const std::string& strConf, bool bQuiet) +void Config::setup(const std::string& strConf, bool bTestNet, bool bQuiet) { boost::system::error_code ec; + std::string strDbPath, strConfFile; // // Determine the config and data directories. @@ -67,21 +81,38 @@ void Config::setup(const std::string& strConf, bool bQuiet) // that with "db" as the data directory. // + TESTNET = bTestNet; QUIET = bQuiet; + // TESTNET forces a "testnet-" prefix on the conf file and db directory. + strDbPath = TESTNET ? "testnet-db" : "db"; + strConfFile = boost::str(boost::format(TESTNET ? "testnet-%s" : "%s") + % (strConf.empty() ? CONFIG_FILE_NAME : strConf)); + + VALIDATORS_BASE = boost::str(boost::format(TESTNET ? "testnet-%s" : "%s") + % VALIDATORS_FILE_NAME); + VALIDATORS_URI = boost::str(boost::format("/%s") % VALIDATORS_BASE); + + SIGN_TRANSACTION = TESTNET ? sHP_TestNetTransactionSign : sHP_TransactionSign; + SIGN_VALIDATION = TESTNET ? sHP_TestNetValidation : sHP_Validation; + SIGN_PROPOSAL = TESTNET ? sHP_TestNetProposal : sHP_Proposal; + + if (TESTNET) + ALPHABET = "RPShNAF39wBUDnEGHJKLM4pQrsT7VWXYZ2bcdeCg65jkm8ofqi1tuvaxyz"; + if (!strConf.empty()) { // --conf= : everything is relative that file. - CONFIG_FILE = strConf; - CONFIG_DIR = CONFIG_FILE; + CONFIG_FILE = strConfFile; + CONFIG_DIR = boost::filesystem::absolute(CONFIG_FILE); CONFIG_DIR.remove_filename(); - DATA_DIR = CONFIG_DIR / "db"; + DATA_DIR = CONFIG_DIR / strDbPath; } else { CONFIG_DIR = boost::filesystem::current_path(); - CONFIG_FILE = CONFIG_DIR / CONFIG_FILE_NAME; - DATA_DIR = CONFIG_DIR / "db"; + CONFIG_FILE = CONFIG_DIR / strConfFile; + DATA_DIR = CONFIG_DIR / strDbPath; if (exists(CONFIG_FILE) // Can we figure out XDG dirs? @@ -111,7 +142,7 @@ void Config::setup(const std::string& strConf, bool bQuiet) } CONFIG_DIR = str(boost::format("%s/" SYSTEM_NAME) % strXdgConfigHome); - CONFIG_FILE = CONFIG_DIR / CONFIG_FILE_NAME; + CONFIG_FILE = CONFIG_DIR / strConfFile; DATA_DIR = str(boost::format("%s/" SYSTEM_NAME) % strXdgDataHome); boost::filesystem::create_directories(CONFIG_DIR, ec); @@ -121,35 +152,42 @@ void Config::setup(const std::string& strConf, bool bQuiet) } } - boost::filesystem::create_directories(DATA_DIR, ec); - - if (ec) - throw std::runtime_error(str(boost::format("Can not create %s") % DATA_DIR)); + // Update default values + load(); // std::cerr << "CONFIG FILE: " << CONFIG_FILE << std::endl; // std::cerr << "CONFIG DIR: " << CONFIG_DIR << std::endl; // std::cerr << "DATA DIR: " << DATA_DIR << std::endl; + boost::filesystem::create_directories(DATA_DIR, ec); + + if (ec) + throw std::runtime_error(str(boost::format("Can not create %s") % DATA_DIR)); +} + +Config::Config() +{ // // Defaults // + TESTNET = false; NETWORK_START_TIME = 1319844908; PEER_PORT = SYSTEM_PEER_PORT; RPC_PORT = 5001; WEBSOCKET_PORT = SYSTEM_WEBSOCKET_PORT; WEBSOCKET_PUBLIC_PORT = SYSTEM_WEBSOCKET_PUBLIC_PORT; - WEBSOCKET_SECURE = false; + WEBSOCKET_PUBLIC_SECURE = 1; + WEBSOCKET_SECURE = 0; NUMBER_CONNECTIONS = 30; // a new ledger every minute LEDGER_SECONDS = 60; LEDGER_CREATOR = false; - RPC_USER = "admin"; - RPC_PASSWORD = "pass"; RPC_ALLOW_REMOTE = false; + RPC_ADMIN_ALLOW.push_back("127.0.0.1"); PEER_SSL_CIPHER_LIST = DEFAULT_PEER_SSL_CIPHER_LIST; PEER_SCAN_INTERVAL_MIN = DEFAULT_PEER_SCAN_INTERVAL_MIN; @@ -159,7 +197,7 @@ void Config::setup(const std::string& strConf, bool bQuiet) PEER_PRIVATE = false; - TRANSACTION_FEE_BASE = 1000; + TRANSACTION_FEE_BASE = DEFAULT_FEE_DEFAULT; NETWORK_QUORUM = 0; // Don't need to see other nodes VALIDATION_QUORUM = 1; // Only need one node to vouch @@ -179,8 +217,6 @@ void Config::setup(const std::string& strConf, bool bQuiet) RUN_STANDALONE = false; START_UP = NORMAL; - - load(); } void Config::load() @@ -220,6 +256,13 @@ void Config::load() // sectionEntriesPrint(&VALIDATORS, SECTION_VALIDATORS); } + smtTmp = sectionEntries(secConfig, SECTION_CLUSTER_NODES); + if (smtTmp) + { + CLUSTER_NODES = *smtTmp; + // sectionEntriesPrint(&CLUSTER_NODES, SECTION_CLUSTER_NODES); + } + smtTmp = sectionEntries(secConfig, SECTION_IPS); if (smtTmp) { @@ -233,6 +276,28 @@ void Config::load() SNTP_SERVERS = *smtTmp; } + smtTmp = sectionEntries(secConfig, SECTION_RPC_STARTUP); + if (smtTmp) + { + Json::Value jvArray(Json::arrayValue); + + BOOST_FOREACH(const std::string& strJson, *smtTmp) + { + Json::Reader jrReader; + Json::Value jvCommand; + + if (!jrReader.parse(strJson, jvCommand)) + throw std::runtime_error(boost::str(boost::format("Couldn't parse ["SECTION_RPC_STARTUP"] command: %s") % strJson)); + + jvArray.append(jvCommand); + } + + RPC_STARTUP = jvArray; + } + + if (sectionSingleB(secConfig, SECTION_DATABASE_PATH, DATABASE_PATH)) + DATA_DIR = DATABASE_PATH; + (void) sectionSingleB(secConfig, SECTION_VALIDATORS_SITE, VALIDATORS_SITE); (void) sectionSingleB(secConfig, SECTION_PEER_IP, PEER_IP); @@ -243,7 +308,17 @@ void Config::load() if (sectionSingleB(secConfig, SECTION_PEER_PRIVATE, strTemp)) PEER_PRIVATE = boost::lexical_cast(strTemp); + smtTmp = sectionEntries(secConfig, SECTION_RPC_ADMIN_ALLOW); + if (smtTmp) + { + RPC_ADMIN_ALLOW = *smtTmp; + } + + (void) sectionSingleB(secConfig, SECTION_RPC_ADMIN_PASSWORD, RPC_ADMIN_PASSWORD); + (void) sectionSingleB(secConfig, SECTION_RPC_ADMIN_USER, RPC_ADMIN_USER); (void) sectionSingleB(secConfig, SECTION_RPC_IP, RPC_IP); + (void) sectionSingleB(secConfig, SECTION_RPC_PASSWORD, RPC_PASSWORD); + (void) sectionSingleB(secConfig, SECTION_RPC_USER, RPC_USER); if (sectionSingleB(secConfig, SECTION_RPC_PORT, strTemp)) RPC_PORT = boost::lexical_cast(strTemp); @@ -265,12 +340,14 @@ void Config::load() WEBSOCKET_PUBLIC_PORT = boost::lexical_cast(strTemp); if (sectionSingleB(secConfig, SECTION_WEBSOCKET_SECURE, strTemp)) - WEBSOCKET_SECURE = boost::lexical_cast(strTemp); + WEBSOCKET_SECURE = boost::lexical_cast(strTemp); + + if (sectionSingleB(secConfig, SECTION_WEBSOCKET_PUBLIC_SECURE, strTemp)) + WEBSOCKET_PUBLIC_SECURE = boost::lexical_cast(strTemp); sectionSingleB(secConfig, SECTION_WEBSOCKET_SSL_CERT, WEBSOCKET_SSL_CERT); sectionSingleB(secConfig, SECTION_WEBSOCKET_SSL_CHAIN, WEBSOCKET_SSL_CHAIN); sectionSingleB(secConfig, SECTION_WEBSOCKET_SSL_KEY, WEBSOCKET_SSL_KEY); - if (sectionSingleB(secConfig, SECTION_VALIDATION_SEED, strTemp)) { @@ -281,6 +358,15 @@ void Config::load() VALIDATION_PRIV = RippleAddress::createNodePrivate(VALIDATION_SEED); } } + if (sectionSingleB(secConfig, SECTION_NODE_SEED, strTemp)) + { + NODE_SEED.setSeedGeneric(strTemp); + if (NODE_SEED.isValid()) + { + NODE_PUB = RippleAddress::createNodePublic(NODE_SEED); + NODE_PRIV = RippleAddress::createNodePrivate(NODE_SEED); + } + } (void) sectionSingleB(secConfig, SECTION_PEER_SSL_CIPHER_LIST, PEER_SSL_CIPHER_LIST); diff --git a/src/cpp/ripple/Config.h b/src/cpp/ripple/Config.h index 570c766bb..6fe20fcc4 100644 --- a/src/cpp/ripple/Config.h +++ b/src/cpp/ripple/Config.h @@ -1,13 +1,17 @@ #ifndef __CONFIG__ #define __CONFIG__ +#include +#include + #include "types.h" #include "RippleAddress.h" #include "ParseSection.h" #include "SerializedTypes.h" -#include -#include +#include "../json/value.h" + +#define ENABLE_INSECURE 0 // 1, to enable unnecessary features. #define SYSTEM_NAME "ripple" #define SYSTEM_CURRENCY_CODE "XRP" @@ -19,11 +23,14 @@ #define SYSTEM_CURRENCY_PARTS 1000000ull // 10^SYSTEM_CURRENCY_PRECISION #define SYSTEM_CURRENCY_START (SYSTEM_CURRENCY_GIFT*SYSTEM_CURRENCY_USERS*SYSTEM_CURRENCY_PARTS) -#define CONFIG_FILE_NAME SYSTEM_NAME "d.cfg" // rippled.cfg +#define CONFIG_FILE_NAME SYSTEM_NAME "d.cfg" // rippled.cfg #define DEFAULT_VALIDATORS_SITE "" #define VALIDATORS_FILE_NAME "validators.txt" +const int DOMAIN_BYTES_MAX = 256; +const int PUBLIC_BYTES_MAX = 2048; // Maximum bytes for an account public key. + const int SYSTEM_PEER_PORT = 6561; const int SYSTEM_WEBSOCKET_PORT = 6562; const int SYSTEM_WEBSOCKET_PUBLIC_PORT = 6563; // XXX Going away. @@ -31,10 +38,9 @@ const int SYSTEM_WEBSOCKET_PUBLIC_PORT = 6563; // XXX Going away. // Allow anonymous DH. #define DEFAULT_PEER_SSL_CIPHER_LIST "ALL:!LOW:!EXP:!MD5:@STRENGTH" -// Normal, recommend 1 hour. -// #define DEFAULT_PEER_SCAN_INTERVAL_MIN (60*60) -// Testing, recommend 1 minute. -#define DEFAULT_PEER_SCAN_INTERVAL_MIN (60) +// Normal, recommend 1 hour: 60*60 +// Testing, recommend 1 minute: 60 +#define DEFAULT_PEER_SCAN_INTERVAL_MIN (60*60) // Seconds // Maximum number of peers to try to connect to as client at once. #define DEFAULT_PEER_START_MAX 5 @@ -47,24 +53,31 @@ class Config public: // Configuration parameters bool QUIET; + bool TESTNET; boost::filesystem::path CONFIG_FILE; boost::filesystem::path CONFIG_DIR; boost::filesystem::path DATA_DIR; boost::filesystem::path DEBUG_LOGFILE; - boost::filesystem::path VALIDATORS_FILE; + boost::filesystem::path VALIDATORS_FILE; // As specifed in rippled.cfg. std::string VALIDATORS_SITE; // Where to find validators.txt on the Internet. + std::string VALIDATORS_URI; // URI of validators.txt. + std::string VALIDATORS_BASE; // Name with testnet-, if needed. std::vector VALIDATORS; // Validators from rippled.cfg. std::vector IPS; // Peer IPs from rippled.cfg. std::vector SNTP_SERVERS; // SNTP servers from rippled.cfg. enum StartUpType { FRESH, NORMAL, LOAD, NETWORK }; StartUpType START_UP; + std::string START_LEDGER; + + // Database + std::string DATABASE_PATH; // Network parameters int NETWORK_START_TIME; // The Unix time we start ledger 0. - int TRANSACTION_FEE_BASE; + int TRANSACTION_FEE_BASE; // The number of fee units a reference transaction costs int LEDGER_SECONDS; int LEDGER_PROPOSAL_DELAY_SECONDS; int LEDGER_AVALANCHE_SECONDS; @@ -88,10 +101,12 @@ public: // Websocket networking parameters std::string WEBSOCKET_PUBLIC_IP; // XXX Going away. Merge with the inbound peer connction. int WEBSOCKET_PUBLIC_PORT; + int WEBSOCKET_PUBLIC_SECURE; std::string WEBSOCKET_IP; int WEBSOCKET_PORT; - bool WEBSOCKET_SECURE; + int WEBSOCKET_SECURE; + std::string WEBSOCKET_SSL_CERT; std::string WEBSOCKET_SSL_CHAIN; std::string WEBSOCKET_SSL_KEY; @@ -99,17 +114,25 @@ public: // RPC parameters std::string RPC_IP; int RPC_PORT; - std::string RPC_USER; + std::vector RPC_ADMIN_ALLOW; + std::string RPC_ADMIN_PASSWORD; + std::string RPC_ADMIN_USER; std::string RPC_PASSWORD; + std::string RPC_USER; bool RPC_ALLOW_REMOTE; + Json::Value RPC_STARTUP; // Validation RippleAddress VALIDATION_SEED, VALIDATION_PUB, VALIDATION_PRIV; - // Fee schedule + // Node/Cluster + std::vector CLUSTER_NODES; + RippleAddress NODE_SEED, NODE_PUB, NODE_PRIV; + + // Fee schedule (All below values are in fee units) uint64 FEE_DEFAULT; // Default fee. - uint64 FEE_ACCOUNT_RESERVE; // Amount of XRP not allowed to send. - uint64 FEE_OWNER_RESERVE; // Amount of XRP not allowed to send per owner entry. + uint64 FEE_ACCOUNT_RESERVE; // Amount of units not allowed to send. + uint64 FEE_OWNER_RESERVE; // Amount of units not allowed to send per owner entry. uint64 FEE_NICKNAME_CREATE; // Fee to create a nickname. uint64 FEE_OFFER; // Rate per day. int FEE_CONTRACT_OPERATION; // fee for each contract operation @@ -120,11 +143,19 @@ public: // Client behavior int ACCOUNT_PROBE_MAX; // How far to scan for accounts. - void setup(const std::string& strConf, bool bQuiet); + // Signing signatures. + uint32 SIGN_TRANSACTION; + uint32 SIGN_VALIDATION; + uint32 SIGN_PROPOSAL; + + Config(); + + void setup(const std::string& strConf, bool bTestNet, bool bQuiet); void load(); }; extern Config theConfig; + #endif // vim:ts=4 diff --git a/src/cpp/ripple/ConnectionPool.cpp b/src/cpp/ripple/ConnectionPool.cpp index 6de593b2b..ae727e332 100644 --- a/src/cpp/ripple/ConnectionPool.cpp +++ b/src/cpp/ripple/ConnectionPool.cpp @@ -10,6 +10,7 @@ #include "Config.h" #include "Peer.h" +#include "PeerDoor.h" #include "Application.h" #include "utils.h" #include "Log.h" @@ -28,21 +29,6 @@ void splitIpPort(const std::string& strIpPort, std::string& strIp, int& iPort) iPort = boost::lexical_cast(vIpPort[1]); } -ConnectionPool::ConnectionPool(boost::asio::io_service& io_service) : - mLastPeer(0), - mCtx(boost::asio::ssl::context::sslv23), - mScanTimer(io_service), - mPolicyTimer(io_service) -{ - mCtx.set_options( - boost::asio::ssl::context::default_workarounds - | boost::asio::ssl::context::no_sslv2 - | boost::asio::ssl::context::single_dh_use); - - if (1 != SSL_CTX_set_cipher_list(mCtx.native_handle(), theConfig.PEER_SSL_CIPHER_LIST.c_str())) - std::runtime_error("Error setting cipher list (no valid ciphers)."); -} - void ConnectionPool::start() { if (theConfig.RUN_STANDALONE) @@ -140,7 +126,7 @@ bool ConnectionPool::peerAvailable(std::string& strIp, int& iPort) vstrIpPort.reserve(mIpMap.size()); - BOOST_FOREACH(pipPeer ipPeer, mIpMap) + BOOST_FOREACH(const vtPeer& ipPeer, mIpMap) { const std::string& strIp = ipPeer.first.first; int iPort = ipPeer.first.second; @@ -178,7 +164,7 @@ void ConnectionPool::policyLowWater() int iPort; // Find an entry to connect to. - if (mConnectedMap.size() > theConfig.PEER_CONNECT_LOW_WATER) + if (getPeerCount() > theConfig.PEER_CONNECT_LOW_WATER) { // Above low water mark, don't need more connections. cLog(lsTRACE) << "Pool: Low water: sufficient connections: " << mConnectedMap.size() << "/" << theConfig.PEER_CONNECT_LOW_WATER; @@ -251,7 +237,7 @@ int ConnectionPool::relayMessage(Peer* fromPeer, const PackedMessage::pointer& m int sentTo = 0; boost::mutex::scoped_lock sl(mPeerLock); - BOOST_FOREACH(naPeer pair, mConnectedMap) + BOOST_FOREACH(const vtConMap& pair, mConnectedMap) { Peer::ref peer = pair.second; if (!peer) @@ -270,7 +256,7 @@ void ConnectionPool::relayMessageBut(const std::set& fromPeers, const Pa { // Relay message to all but the specified peers boost::mutex::scoped_lock sl(mPeerLock); - BOOST_FOREACH(naPeer pair, mConnectedMap) + BOOST_FOREACH(const vtConMap& pair, mConnectedMap) { Peer::ref peer = pair.second; if (peer->isConnected() && (fromPeers.count(peer->getPeerId()) == 0)) @@ -298,9 +284,6 @@ void ConnectionPool::relayMessageTo(const std::set& fromPeers, const Pac // 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()); @@ -329,7 +312,8 @@ Peer::pointer ConnectionPool::peerConnect(const std::string& strIp, int iPort) if ((it = mIpMap.find(pipPeer)) == mIpMap.end()) { - Peer::pointer ppNew(Peer::create(theApp->getIOService(), mCtx, ++mLastPeer)); + Peer::pointer ppNew(Peer::create(theApp->getIOService(), theApp->getPeerDoor().getSSLContext(), + ++mLastPeer, false)); // Did not find it. Not already connecting or connected. ppNew->connect(strIp, iPort); @@ -365,7 +349,7 @@ Json::Value ConnectionPool::getPeersJson() Json::Value ret(Json::arrayValue); std::vector vppPeers = getPeerVector(); - BOOST_FOREACH(Peer::pointer peer, vppPeers) + BOOST_FOREACH(Peer::ref peer, vppPeers) { ret.append(peer->getJson()); } @@ -388,7 +372,7 @@ std::vector ConnectionPool::getPeerVector() ret.reserve(mConnectedMap.size()); - BOOST_FOREACH(naPeer pair, mConnectedMap) + BOOST_FOREACH(const vtConMap& pair, mConnectedMap) { assert(!!pair.second); ret.push_back(pair.second); @@ -532,7 +516,7 @@ bool ConnectionPool::peerScanSet(const std::string& strIp, int iPort) db->executeSQL(str(boost::format("UPDATE PeerIps SET ScanNext=%d,ScanInterval=%d WHERE IpPort=%s;") % iToSeconds(tpNext) % iInterval - % db->escape(strIpPort))); + % sqlEscape(strIpPort))); bScanDirty = true; } @@ -632,8 +616,8 @@ void ConnectionPool::peerVerified(Peer::ref peer) ScopedLock sl(theApp->getWalletDB()->getDBLock()); Database *db=theApp->getWalletDB()->getDB(); - db->executeSQL(str(boost::format("UPDATE PeerIps SET ScanNext=NULL,ScanInterval=0 WHERE IpPort=%s;") - % db->escape(strIpPort))); + db->executeSQL(boost::str(boost::format("UPDATE PeerIps SET ScanNext=NULL,ScanInterval=0 WHERE IpPort=%s;") + % sqlEscape(strIpPort))); // XXX Check error. } @@ -662,7 +646,11 @@ void ConnectionPool::scanHandler(const boost::system::error_code& ecResult) // Scan ips as per db entries. void ConnectionPool::scanRefresh() { - if (mScanning) + if (theConfig.RUN_STANDALONE) + { + nothing(); + } + else if (mScanning) { // Currently scanning, will scan again after completion. cLog(lsTRACE) << "Pool: Scan: already scanning"; @@ -726,10 +714,10 @@ void ConnectionPool::scanRefresh() ScopedLock sl(theApp->getWalletDB()->getDBLock()); Database *db=theApp->getWalletDB()->getDB(); - db->executeSQL(str(boost::format("UPDATE PeerIps SET ScanNext=%d,ScanInterval=%d WHERE IpPort=%s;") + db->executeSQL(boost::str(boost::format("UPDATE PeerIps SET ScanNext=%d,ScanInterval=%d WHERE IpPort=%s;") % iToSeconds(tpNext) % iInterval - % db->escape(strIpPort))); + % sqlEscape(strIpPort))); // XXX Check error. } diff --git a/src/cpp/ripple/ConnectionPool.h b/src/cpp/ripple/ConnectionPool.h index dbc6cd8b2..3cdc77317 100644 --- a/src/cpp/ripple/ConnectionPool.h +++ b/src/cpp/ripple/ConnectionPool.h @@ -21,6 +21,7 @@ private: typedef std::pair naPeer; typedef std::pair pipPeer; + typedef std::map::value_type vtPeer; // Peers we are connecting with and non-thin peers we are connected to. // Only peers we know the connection ip for are listed. @@ -31,13 +32,12 @@ private: // Non-thin peers which we are connected to. // Peers we have the public key for. + typedef boost::unordered_map::value_type vtConMap; boost::unordered_map mConnectedMap; // Connections with have a 64-bit identifier boost::unordered_map mPeerIdMap; - boost::asio::ssl::context mCtx; - Peer::pointer mScanning; boost::asio::deadline_timer mScanTimer; std::string mScanIp; @@ -58,7 +58,9 @@ private: Peer::pointer peerConnect(const std::string& strIp, int iPort); public: - ConnectionPool(boost::asio::io_service& io_service); + ConnectionPool(boost::asio::io_service& io_service) : + mLastPeer(0), mScanTimer(io_service), mPolicyTimer(io_service) + { ; } // Begin enforcing connection policy. void start(); diff --git a/src/cpp/ripple/Contract.cpp b/src/cpp/ripple/Contract.cpp index 09c3a4ecf..b6db42831 100644 --- a/src/cpp/ripple/Contract.cpp +++ b/src/cpp/ripple/Contract.cpp @@ -32,4 +32,4 @@ void Contract::executeAccept() //interpreter.interpret(this,code); } - +// vim:ts=4 diff --git a/src/cpp/ripple/Contract.h b/src/cpp/ripple/Contract.h index 76d30dc54..9a708a75c 100644 --- a/src/cpp/ripple/Contract.h +++ b/src/cpp/ripple/Contract.h @@ -27,4 +27,6 @@ public: void executeAccept(); }; -#endif \ No newline at end of file +#endif + +// vim:ts=4 diff --git a/src/cpp/ripple/DBInit.cpp b/src/cpp/ripple/DBInit.cpp index 13d5f7ad6..b16ba0f41 100644 --- a/src/cpp/ripple/DBInit.cpp +++ b/src/cpp/ripple/DBInit.cpp @@ -4,6 +4,9 @@ // Transaction database holds transactions and public keys const char *TxnDBInit[] = { + "PRAGMA synchronous=NORMAL;", + "PRAGMA journal_mode=WAL;", + "BEGIN TRANSACTION;", "CREATE TABLE Transactions ( \ @@ -16,10 +19,6 @@ const char *TxnDBInit[] = { RawTxn BLOB, \ TxnMeta BLOB \ );", - "CREATE TABLE PubKeys ( \ - ID CHARACTER(35) PRIMARY KEY, \ - PubKey BLOB \ - );", "CREATE TABLE AccountTransactions ( \ TransID CHARACTER(64), \ Account CHARACTER(64), \ @@ -37,6 +36,9 @@ int TxnDBCount = NUMBER(TxnDBInit); // Ledger database holds ledgers and ledger confirmations const char *LedgerDBInit[] = { + "PRAGMA synchronous=NORMAL;", + "PRAGMA journal_mode=WAL;", + "BEGIN TRANSACTION;", "CREATE TABLE Ledgers ( \ @@ -254,6 +256,9 @@ int WalletDBCount = NUMBER(WalletDBInit); // Hash node database holds nodes indexed by hash const char *HashNodeDBInit[] = { + "PRAGMA synchronous=NORMAL;", + "PRAGMA journal_mode=WAL;", + "BEGIN TRANSACTION;", "CREATE TABLE CommittedObjects ( \ diff --git a/src/cpp/ripple/FeatureTable.cpp b/src/cpp/ripple/FeatureTable.cpp index 984be60d0..af1d8349c 100644 --- a/src/cpp/ripple/FeatureTable.cpp +++ b/src/cpp/ripple/FeatureTable.cpp @@ -128,7 +128,7 @@ void FeatureTable::reportValidations(const FeatureSet& set) return; int threshold = (set.mTrustedValidations * mMajorityFraction) / 256; - typedef std::pair u256_int_pair; + typedef std::map::value_type u256_int_pair; boost::mutex::scoped_lock sl(mMutex); @@ -195,17 +195,17 @@ Json::Value FeatureTable::getJson(int) { Json::Value v(Json::objectValue); - v["supported"] = it.second.mSupported ? "true" : "false"; + v["supported"] = it.second.mSupported; if (it.second.mEnabled) - v["enabled"] = "true"; + v["enabled"] = true; else { - v["enabled"] = "false"; + v["enabled"] = false; if (mLastReport != 0) { if (it.second.mLastMajority == 0) - v["majority"] = "no"; + v["majority"] = false; else { if (it.second.mFirstMajority != 0) @@ -227,7 +227,7 @@ Json::Value FeatureTable::getJson(int) } if (it.second.mVetoed) - v["veto"] = "true"; + v["veto"] = true; ret[it.first.GetHex()] = v; } diff --git a/src/cpp/ripple/FieldNames.cpp b/src/cpp/ripple/FieldNames.cpp index 590f85d6b..6bd34da29 100644 --- a/src/cpp/ripple/FieldNames.cpp +++ b/src/cpp/ripple/FieldNames.cpp @@ -111,7 +111,7 @@ std::string SField::getName() const 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; + typedef std::map::value_type int_sfref_pair; BOOST_FOREACH(const int_sfref_pair& fieldPair, codeToField) { if (fieldPair.second->fieldName == fieldName) diff --git a/src/cpp/ripple/HTTPRequest.cpp b/src/cpp/ripple/HTTPRequest.cpp index c3f365e0e..6823dbc62 100644 --- a/src/cpp/ripple/HTTPRequest.cpp +++ b/src/cpp/ripple/HTTPRequest.cpp @@ -7,9 +7,11 @@ #include "Log.h" SETUP_LOG(); +// Logic to handle incoming HTTP reqests + void HTTPRequest::reset() { - vHeaders.clear(); + mHeaders.clear(); sRequestBody.clear(); sAuthorization.clear(); iDataSize = 0; @@ -22,7 +24,7 @@ HTTPRequestAction HTTPRequest::requestDone(bool forceClose) if (forceClose || bShouldClose) return haCLOSE_CONN; reset(); - return haREAD_LINE; + return haREAD_LINE; } std::string HTTPRequest::getReplyHeaders(bool forceClose) @@ -65,8 +67,6 @@ HTTPRequestAction HTTPRequest::consume(boost::asio::streambuf& buf) eState = getting_body; return haREAD_RAW; } - vHeaders.push_back(line); - size_t colon = line.find(':'); if (colon != std::string::npos) { @@ -77,6 +77,8 @@ HTTPRequestAction HTTPRequest::consume(boost::asio::streambuf& buf) std::string headerValue = line.substr(colon+1); boost::trim(headerValue); + mHeaders[headerName] += headerValue; + if (headerName == "connection") { boost::to_lower(headerValue); @@ -91,7 +93,6 @@ HTTPRequestAction HTTPRequest::consume(boost::asio::streambuf& buf) if (headerName == "authorization") sAuthorization = headerValue; - } return haREAD_LINE; @@ -100,3 +101,5 @@ HTTPRequestAction HTTPRequest::consume(boost::asio::streambuf& buf) assert(false); return haERROR; } + +// vim:ts=4 diff --git a/src/cpp/ripple/HTTPRequest.h b/src/cpp/ripple/HTTPRequest.h index 596ce8924..a9d1fae6a 100644 --- a/src/cpp/ripple/HTTPRequest.h +++ b/src/cpp/ripple/HTTPRequest.h @@ -2,7 +2,7 @@ #define HTTPREQUEST__HPP #include -#include +#include #include @@ -16,7 +16,7 @@ enum HTTPRequestAction }; class HTTPRequest -{ // an HTTP request in progress +{ // an HTTP request we are handling from a client protected: enum state @@ -32,7 +32,7 @@ protected: std::string sRequestBody; std::string sAuthorization; - std::vector vHeaders; + std::map mHeaders; int iDataSize; bool bShouldClose; @@ -49,7 +49,7 @@ public: std::string& peekAuth() { return sAuthorization; } std::string getAuth() { return sAuthorization; } - std::vector& peekHeaders() { return vHeaders; } + std::map& peekHeaders() { return mHeaders; } std::string getReplyHeaders(bool forceClose); HTTPRequestAction consume(boost::asio::streambuf&); @@ -58,4 +58,4 @@ public: int getDataSize() { return iDataSize; } }; -#endif \ No newline at end of file +#endif diff --git a/src/cpp/ripple/HashPrefixes.h b/src/cpp/ripple/HashPrefixes.h index 832349ec6..799b66e62 100644 --- a/src/cpp/ripple/HashPrefixes.h +++ b/src/cpp/ripple/HashPrefixes.h @@ -6,9 +6,6 @@ // TXN - Hash of transaction plus signature to give transaction ID const uint32 sHP_TransactionID = 0x54584E00; -// STX - Hash of inner transaction to sign -const uint32 sHP_TransactionSign = 0x53545800; - // TND - Hash of transaction plus metadata const uint32 sHP_TransactionNode = 0x534E4400; @@ -21,12 +18,24 @@ const uint32 sHP_InnerNode = 0x4D494E00; // LGR - Hash of ledger master data for signing const uint32 sHP_Ledger = 0x4C575200; +// STX - Hash of inner transaction to sign +const uint32 sHP_TransactionSign = 0x53545800; + // VAL - Hash of validation for signing const uint32 sHP_Validation = 0x56414C00; // PRP - Hash of proposal for signing const uint32 sHP_Proposal = 0x50525000; +// stx - TESTNET Hash of inner transaction to sign +const uint32 sHP_TestNetTransactionSign = 0x73747800; + +// val - TESTNET Hash of validation for signing +const uint32 sHP_TestNetValidation = 0x76616C00; + +// prp - TESTNET Hash of proposal for signing +const uint32 sHP_TestNetProposal = 0x70727000; + #endif // vim:ts=4 diff --git a/src/cpp/ripple/HashedObject.cpp b/src/cpp/ripple/HashedObject.cpp index f59a186fe..d5e248f2e 100644 --- a/src/cpp/ripple/HashedObject.cpp +++ b/src/cpp/ripple/HashedObject.cpp @@ -4,6 +4,8 @@ #include #include +#include "../database/SqliteDatabase.h" + #include "Serializer.h" #include "Application.h" #include "Log.h" @@ -12,7 +14,8 @@ SETUP_LOG(); DECLARE_INSTANCE(HashedObject); HashedObjectStore::HashedObjectStore(int cacheSize, int cacheAge) : - mCache("HashedObjectStore", cacheSize, cacheAge), mWriteGeneration(0), mWritePending(false) + mCache("HashedObjectStore", cacheSize, cacheAge), mNegativeCache("HashedObjectNegativeCache", 0, 120), + mWriteGeneration(0), mWritePending(false) { mWriteSet.reserve(128); } @@ -42,12 +45,12 @@ bool HashedObjectStore::store(HashedObjectType type, uint32 index, if (!mWritePending) { mWritePending = true; - boost::thread t(boost::bind(&HashedObjectStore::bulkWrite, this)); - t.detach(); + boost::thread(boost::bind(&HashedObjectStore::bulkWrite, this)).detach(); } } // else // cLog(lsTRACE) << "HOS: already had " << hash; + mNegativeCache.del(hash); return true; } @@ -83,31 +86,28 @@ void HashedObjectStore::bulkWrite() static boost::format fExists("SELECT ObjType FROM CommittedObjects WHERE Hash = '%s';"); static boost::format - fAdd("INSERT INTO CommittedObjects (Hash,ObjType,LedgerIndex,Object) VALUES ('%s','%c','%u',%s);"); + fAdd("INSERT OR IGNORE INTO CommittedObjects " + "(Hash,ObjType,LedgerIndex,Object) VALUES ('%s','%c','%u',%s);"); Database* db = theApp->getHashNodeDB()->getDB(); { - ScopedLock sl( theApp->getHashNodeDB()->getDBLock()); + ScopedLock sl(theApp->getHashNodeDB()->getDBLock()); db->executeSQL("BEGIN TRANSACTION;"); BOOST_FOREACH(const boost::shared_ptr& it, set) { - if (!SQL_EXISTS(db, boost::str(fExists % it->getHash().GetHex()))) + char type; + + switch (it->getType()) { - char type; - switch(it->getType()) - { - 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(&(it->getData().front()), it->getData().size(), rawData); - db->executeSQL(boost::str(fAdd % it->getHash().GetHex() % type % it->getIndex() % rawData )); + 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'; } + db->executeSQL(boost::str(fAdd % it->getHash().GetHex() % type % it->getIndex() % sqlEscape(it->getData()))); } db->executeSQL("END TRANSACTION;"); @@ -117,16 +117,20 @@ void HashedObjectStore::bulkWrite() HashedObject::pointer HashedObjectStore::retrieve(const uint256& hash) { + HashedObject::pointer obj; { obj = mCache.fetch(hash); if (obj) { - cLog(lsTRACE) << "HOS: " << hash << " fetch: incache"; +// cLog(lsTRACE) << "HOS: " << hash << " fetch: incache"; return obj; } } + if (mNegativeCache.isPresent(hash)) + return HashedObject::pointer(); + if (!theApp || !theApp->getHashNodeDB()) return HashedObject::pointer(); std::string sql = "SELECT * FROM CommittedObjects WHERE Hash='"; @@ -134,6 +138,9 @@ HashedObject::pointer HashedObjectStore::retrieve(const uint256& hash) sql.append("';"); std::vector data; + std::string type; + uint32 index; + { ScopedLock sl(theApp->getHashNodeDB()->getDBLock()); Database* db = theApp->getHashNodeDB()->getDB(); @@ -141,39 +148,111 @@ HashedObject::pointer HashedObjectStore::retrieve(const uint256& hash) if (!db->executeSQL(sql) || !db->startIterRows()) { // cLog(lsTRACE) << "HOS: " << hash << " fetch: not in db"; + sl.unlock(); + mNegativeCache.add(hash); return HashedObject::pointer(); } - std::string type; db->getStr("ObjType", type); - if (type.size() == 0) return HashedObject::pointer(); - - uint32 index = db->getBigInt("LedgerIndex"); + index = db->getBigInt("LedgerIndex"); int size = db->getBinary("Object", NULL, 0); data.resize(size); db->getBinary("Object", &(data.front()), size); db->endIterRows(); - - assert(Serializer::getSHA512Half(data) == hash); - - HashedObjectType htype = hotUNKNOWN; - switch (type[0]) - { - case 'L': htype = hotLEDGER; break; - case 'T': htype = hotTRANSACTION; break; - case 'A': htype = hotACCOUNT_NODE; break; - case 'N': htype = hotTRANSACTION_NODE; break; - default: - cLog(lsERROR) << "Invalid hashed object"; - return HashedObject::pointer(); - } - - obj = boost::make_shared(htype, index, data, hash); - mCache.canonicalize(hash, obj); } + + assert(Serializer::getSHA512Half(data) == hash); + + HashedObjectType htype = hotUNKNOWN; + switch (type[0]) + { + case 'L': htype = hotLEDGER; break; + case 'T': htype = hotTRANSACTION; break; + case 'A': htype = hotACCOUNT_NODE; break; + case 'N': htype = hotTRANSACTION_NODE; break; + default: + assert(false); + cLog(lsERROR) << "Invalid hashed object"; + mNegativeCache.add(hash); + return HashedObject::pointer(); + } + + obj = boost::make_shared(htype, index, data, hash); + mCache.canonicalize(hash, obj); + cLog(lsTRACE) << "HOS: " << hash << " fetch: in db"; return obj; } +int HashedObjectStore::import(const std::string& file) +{ + cLog(lsWARNING) << "Hash import from \"" << file << "\"."; + std::auto_ptr importDB(new SqliteDatabase(file.c_str())); + importDB->connect(); + + int countYes = 0, countNo = 0; + + SQL_FOREACH(importDB, "SELECT * FROM CommittedObjects;") + { + uint256 hash; + std::string hashStr; + importDB->getStr("Hash", hashStr); + hash.SetHex(hashStr); + if (hash.isZero()) + { + cLog(lsWARNING) << "zero hash found in import table"; + } + else + { + if (retrieve(hash) != HashedObject::pointer()) + ++countNo; + else + { // we don't have this object + std::vector data; + std::string type; + importDB->getStr("ObjType", type); + uint32 index = importDB->getBigInt("LedgerIndex"); + + int size = importDB->getBinary("Object", NULL, 0); + data.resize(size); + importDB->getBinary("Object", &(data.front()), size); + + assert(Serializer::getSHA512Half(data) == hash); + + HashedObjectType htype = hotUNKNOWN; + switch (type[0]) + { + case 'L': htype = hotLEDGER; break; + case 'T': htype = hotTRANSACTION; break; + case 'A': htype = hotACCOUNT_NODE; break; + case 'N': htype = hotTRANSACTION_NODE; break; + default: + assert(false); + cLog(lsERROR) << "Invalid hashed object"; + } + + if (Serializer::getSHA512Half(data) != hash) + { + cLog(lsWARNING) << "Hash mismatch in import table " << hash + << " " << Serializer::getSHA512Half(data); + } + else + { + store(htype, index, data, hash); + ++countYes; + } + } + if (((countYes + countNo) % 100) == 99) + { + cLog(lsINFO) << "Import in progress: yes=" << countYes << ", no=" << countNo; + } + } + } + + cLog(lsWARNING) << "Imported " << countYes << " nodes, had " << countNo << " nodes"; + waitWrite(); + return countYes; +} + // vim:ts=4 diff --git a/src/cpp/ripple/HashedObject.h b/src/cpp/ripple/HashedObject.h index 69c5e55f7..d96101370 100644 --- a/src/cpp/ripple/HashedObject.h +++ b/src/cpp/ripple/HashedObject.h @@ -10,6 +10,7 @@ #include "uint256.h" #include "ScopedLock.h" #include "TaggedCache.h" +#include "KeyCache.h" #include "InstanceCounter.h" DEFINE_INSTANCE(HashedObject); @@ -28,7 +29,7 @@ class HashedObject : private IS_INSTANCE(HashedObject) public: typedef boost::shared_ptr pointer; - HashedObjectType mType; + HashedObjectType mType; uint256 mHash; uint32 mLedgerIndex; std::vector mData; @@ -45,7 +46,8 @@ public: class HashedObjectStore { protected: - TaggedCache mCache; + TaggedCache mCache; + KeyCache mNegativeCache; boost::mutex mWriteMutex; boost::condition_variable mWriteCondition; @@ -65,7 +67,10 @@ public: void bulkWrite(); void waitWrite(); - void sweep() { mCache.sweep(); } + void sweep() { mCache.sweep(); mNegativeCache.sweep(); } + + int import(const std::string&); }; #endif +// vim:ts=4 diff --git a/src/cpp/ripple/HttpsClient.cpp b/src/cpp/ripple/HttpsClient.cpp index 987869e09..517e2d02f 100644 --- a/src/cpp/ripple/HttpsClient.cpp +++ b/src/cpp/ripple/HttpsClient.cpp @@ -362,22 +362,4 @@ void HttpsClient::httpsGet( client->httpsGet(deqSites, timeout, complete); } -bool HttpsClient::httpsParseUrl(const std::string& strUrl, std::string& strDomain, std::string& strPath) -{ - static boost::regex reUrl("(?i)\\`\\s*https://([^/]+)(/.*)\\s*\\'"); // https://DOMAINPATH - - boost::smatch smMatch; - - bool bMatch = boost::regex_match(strUrl, smMatch, reUrl); // Match status code. - - if (bMatch) - { - strDomain = smMatch[1]; - strPath = smMatch[2]; - } - // std::cerr << strUrl << " : " << bMatch << " : '" << strDomain << "' : '" << strPath << "'" << std::endl; - - return bMatch; -} - // vim:ts=4 diff --git a/src/cpp/ripple/HttpsClient.h b/src/cpp/ripple/HttpsClient.h index bebe57a3e..a9d6c9384 100644 --- a/src/cpp/ripple/HttpsClient.h +++ b/src/cpp/ripple/HttpsClient.h @@ -99,8 +99,6 @@ public: std::size_t responseMax, boost::posix_time::time_duration timeout, boost::function complete); - - static bool httpsParseUrl(const std::string& strUrl, std::string& strDomain, std::string& strPath); }; #endif // vim:ts=4 diff --git a/src/cpp/ripple/InstanceCounter.cpp b/src/cpp/ripple/InstanceCounter.cpp index 564010282..8f551dff3 100644 --- a/src/cpp/ripple/InstanceCounter.cpp +++ b/src/cpp/ripple/InstanceCounter.cpp @@ -1,6 +1,7 @@ #include "InstanceCounter.h" InstanceType* InstanceType::sHeadInstance = NULL; +bool InstanceType::sMultiThreaded = false; std::vector InstanceType::getInstanceCounts(int min) { diff --git a/src/cpp/ripple/InstanceCounter.h b/src/cpp/ripple/InstanceCounter.h index ffb3665f5..48f82d033 100644 --- a/src/cpp/ripple/InstanceCounter.h +++ b/src/cpp/ripple/InstanceCounter.h @@ -32,6 +32,7 @@ protected: InstanceType* mNextInstance; static InstanceType* sHeadInstance; + static bool sMultiThreaded; public: typedef std::pair InstanceCount; @@ -42,17 +43,32 @@ public: sHeadInstance = this; } + static void multiThread() + { + // We can support global objects and multi-threaded code, but not both + // at the same time. Switch to multi-threaded. + sMultiThreaded = true; + } + void addInstance() { - mLock.lock(); - ++mInstances; - mLock.unlock(); + if (sMultiThreaded) + { + mLock.lock(); + ++mInstances; + mLock.unlock(); + } + else ++mInstances; } void decInstance() { - mLock.lock(); - --mInstances; - mLock.unlock(); + if (sMultiThreaded) + { + mLock.lock(); + --mInstances; + mLock.unlock(); + } + else --mInstances; } int getCount() { @@ -70,11 +86,13 @@ public: class Instance { protected: + static bool running; InstanceType& mType; public: Instance(InstanceType& t) : mType(t) { mType.addInstance(); } - ~Instance() { mType.decInstance(); } + ~Instance() { if (running) mType.decInstance(); } + static void shutdown() { running = false; } }; #endif diff --git a/src/cpp/ripple/Interpreter.cpp b/src/cpp/ripple/Interpreter.cpp index 6fce0827b..4cde286c5 100644 --- a/src/cpp/ripple/Interpreter.cpp +++ b/src/cpp/ripple/Interpreter.cpp @@ -129,7 +129,7 @@ TER Interpreter::interpret(Contract* contract,const SerializedTransaction& txn,s return(temMALFORMED); // TODO: is this actually what we want to do? } - mTotalFee += mFunctionTable[ fun ]->getFee(); + mTotalFee += mFunctionTable[ fun ]->getFee(); // FIXME: You can't use fees this way, there's no consensus if(mTotalFee>txn.getTransactionFee().getNValue()) { // TODO: log diff --git a/src/cpp/ripple/Interpreter.h b/src/cpp/ripple/Interpreter.h index 5b43017bf..f544ab845 100644 --- a/src/cpp/ripple/Interpreter.h +++ b/src/cpp/ripple/Interpreter.h @@ -50,7 +50,7 @@ public: Interpreter(); - // returns a TransactionEngineResult + // returns a TransactionEngineResult TER interpret(Contract* contract,const SerializedTransaction& txn,std::vector& code); void stop(); @@ -67,16 +67,13 @@ public: 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 +#endif diff --git a/src/cpp/ripple/JobQueue.cpp b/src/cpp/ripple/JobQueue.cpp index 44ed3bcee..40b7bf44c 100644 --- a/src/cpp/ripple/JobQueue.cpp +++ b/src/cpp/ripple/JobQueue.cpp @@ -5,6 +5,7 @@ #include #include "Log.h" +#include "Config.h" SETUP_LOG(); @@ -14,6 +15,7 @@ JobQueue::JobQueue() : mLastJob(0), mThreadCount(0), mShuttingDown(false) mJobLoads[jtPROOFWORK].setTargetLatency(2000, 5000); mJobLoads[jtTRANSACTION].setTargetLatency(250, 1000); mJobLoads[jtPROPOSAL_ut].setTargetLatency(500, 1250); + mJobLoads[jtPUBLEDGER].setTargetLatency(1000, 2500); mJobLoads[jtVALIDATION_t].setTargetLatency(500, 1500); mJobLoads[jtTRANSACTION_l].setTargetLatency(100, 500); mJobLoads[jtPROPOSAL_t].setTargetLatency(100, 500); @@ -23,7 +25,6 @@ JobQueue::JobQueue() : mLastJob(0), mThreadCount(0), mShuttingDown(false) mJobLoads[jtDISK].setTargetLatency(500, 1000); mJobLoads[jtRPC].setTargetLatency(250, 750); mJobLoads[jtACCEPTLEDGER].setTargetLatency(1000, 2500); - mJobLoads[jtPUBLEDGER].setTargetLatency(1000, 2500); } @@ -33,56 +34,60 @@ const char* Job::toString(JobType t) { case jtINVALID: return "invalid"; case jtVALIDATION_ut: return "untrustedValidation"; - case jtTRANSACTION: return "transaction"; + case jtPROOFWORK: return "proofOfWork"; case jtPROPOSAL_ut: return "untrustedProposal"; + case jtCLIENT: return "clientCommand"; + case jtTRANSACTION: return "transaction"; + case jtPUBLEDGER: return "publishLedger"; case jtVALIDATION_t: return "trustedValidation"; + case jtTRANSACTION_l: return "localTransaction"; case jtPROPOSAL_t: return "trustedProposal"; case jtADMIN: return "administration"; case jtDEATH: return "jobOfDeath"; - case jtCLIENT: return "clientCommand"; + case jtPEER: return "peerCommand"; case jtDISK: return "diskAccess"; case jtRPC: return "rpc"; case jtACCEPTLEDGER: return "acceptLedger"; - case jtPUBLEDGER: return "pubLedger"; + case jtTXN_PROC: return "processTransaction"; default: assert(false); return "unknown"; } } -bool Job::operator<(const Job& j) const +bool Job::operator>(const Job& j) const { // These comparison operators make the jobs sort in priority order in the job set if (mType < j.mType) return true; if (mType > j.mType) return false; - return mJobIndex < j.mJobIndex; -} - -bool Job::operator<=(const Job& j) const -{ - if (mType < j.mType) - return true; - if (mType > j.mType) - return false; - return mJobIndex <= j.mJobIndex; -} - -bool Job::operator>(const Job& j) const -{ - if (mType < j.mType) - return false; - if (mType > j.mType) - return true; return mJobIndex > j.mJobIndex; } bool Job::operator>=(const Job& j) const +{ + if (mType < j.mType) + return true; + if (mType > j.mType) + return false; + return mJobIndex >= j.mJobIndex; +} + +bool Job::operator<(const Job& j) const { if (mType < j.mType) return false; if (mType > j.mType) return true; - return mJobIndex >= j.mJobIndex; + return mJobIndex < j.mJobIndex; +} + +bool Job::operator<=(const Job& j) const +{ + if (mType < j.mType) + return false; + if (mType > j.mType) + return true; + return mJobIndex <= j.mJobIndex; } void JobQueue::addJob(JobType type, const boost::function& jobFunc) @@ -111,7 +116,7 @@ int JobQueue::getJobCountGE(JobType t) boost::mutex::scoped_lock sl(mJobLock); - typedef std::pair jt_int_pair; + typedef std::map::value_type jt_int_pair; BOOST_FOREACH(const jt_int_pair& it, mJobCounts) if (it.first >= t) ret += it.second; @@ -125,7 +130,7 @@ std::vector< std::pair > JobQueue::getJobCounts() boost::mutex::scoped_lock sl(mJobLock); ret.reserve(mJobCounts.size()); - typedef std::pair jt_int_pair; + typedef std::map::value_type jt_int_pair; BOOST_FOREACH(const jt_int_pair& it, mJobCounts) ret.push_back(it); @@ -154,7 +159,7 @@ Json::Value JobQueue::getJson(int) { Json::Value pri(Json::objectValue); if (isOver) - pri["over_target"] = "true"; + pri["over_target"] = true; pri["job_type"] = Job::toString(static_cast(i)); if (jobCount != 0) pri["waiting"] = static_cast(jobCount); @@ -180,15 +185,19 @@ void JobQueue::shutdown() mJobCond.notify_all(); while (mThreadCount != 0) mJobCond.wait(sl); + cLog(lsDEBUG) << "Job queue has shut down"; } void JobQueue::setThreadCount(int c) { // set the number of thread serving the job queue to precisely this number - if (c == 0) + if (theConfig.RUN_STANDALONE) + c = 1; + else if (c == 0) { c = boost::thread::hardware_concurrency(); - if (c < 2) - c = 2; + if (c < 0) + c = 0; + c += 2; cLog(lsINFO) << "Auto-tuning to " << c << " validation/transaction/proposal threads"; } @@ -200,8 +209,7 @@ void JobQueue::setThreadCount(int c) while (mThreadCount < c) { ++mThreadCount; - boost::thread t(boost::bind(&JobQueue::threadEntry, this)); - t.detach(); + boost::thread(boost::bind(&JobQueue::threadEntry, this)).detach(); } while (mThreadCount > c) { @@ -243,3 +251,5 @@ void JobQueue::threadEntry() --mThreadCount; mJobCond.notify_all(); } + +// vim:ts=4 diff --git a/src/cpp/ripple/JobQueue.h b/src/cpp/ripple/JobQueue.h index 43659f8e8..f45d2bb33 100644 --- a/src/cpp/ripple/JobQueue.h +++ b/src/cpp/ripple/JobQueue.h @@ -26,20 +26,20 @@ enum JobType jtPROPOSAL_ut = 3, // A proposal from an untrusted source jtCLIENT = 4, // A websocket command from the client jtTRANSACTION = 5, // A transaction received from the network - jtVALIDATION_t = 6, // A validation from a trusted source - jtTRANSACTION_l = 7, // A local transaction - jtPROPOSAL_t = 8, // A proposal from a trusted source - jtADMIN = 9, // An administrative operation - jtDEATH = 10, // job of death, used internally + jtPUBLEDGER = 6, // Publish a fully-accepted ledger + jtVALIDATION_t = 7, // A validation from a trusted source + jtTRANSACTION_l = 8, // A local transaction + jtPROPOSAL_t = 9, // A proposal from a trusted source + jtADMIN = 10, // An administrative operation + jtDEATH = 11, // job of death, used internally // special types not dispatched by the job pool jtPEER = 17, jtDISK = 18, jtRPC = 19, jtACCEPTLEDGER = 20, - jtPUBLEDGER = 21, - jtTXN_PROC = 22, -}; + jtTXN_PROC = 21, +}; // CAUTION: If you add new types, add them to JobType.cpp too #define NUM_JOB_TYPES 24 class Job diff --git a/src/cpp/ripple/KeyCache.h b/src/cpp/ripple/KeyCache.h new file mode 100644 index 000000000..95b0277b6 --- /dev/null +++ b/src/cpp/ripple/KeyCache.h @@ -0,0 +1,129 @@ +#ifndef KEY_CACHE__H +#define KEY_CACHE__H + +#include + +#include +#include + +template class KeyCache +{ // Maintains a cache of keys with no associated data +public: + typedef c_Key key_type; + typedef boost::unordered_map map_type; + typedef typename map_type::iterator map_iterator; + +protected: + const std::string mName; + boost::mutex mNCLock; + map_type mCache; + unsigned int mTargetSize, mTargetAge; + +public: + + KeyCache(const std::string& name, int size = 0, int age = 120) : mName(name), mTargetSize(size), mTargetAge(age) + { + assert((mTargetSize >= 0) && (mTargetAge > 2)); + } + + void getSize() + { + boost::mutex::scoped_lock sl(mNCLock); + return mCache.size(); + } + + void getTargetSize() + { + boost::mutex::scoped_lock sl(mNCLock); + return mTargetSize; + } + + void getTargetAge() + { + boost::mutex::scoped_lock sl(mNCLock); + return mTargetAge; + } + + void setTargets(int size, int age) + { + boost::mutex::scoped_lock sl(mNCLock); + mTargetSize = size; + mTargetAge = age; + assert((mTargetSize >= 0) && (mTargetAge > 2)); + } + + const std::string& getName() + { + return mName; + } + + bool isPresent(const key_type& key, bool refresh = true) + { // Check if an entry is cached, refresh it if so + boost::mutex::scoped_lock sl(mNCLock); + + map_iterator it = mCache.find(key); + if (it == mCache.end()) + return false; + if (refresh) + it->second = time(NULL); + return true; + } + + bool del(const key_type& key) + { // Remove an entry from the cache, return false if not-present + boost::mutex::scoped_lock sl(mNCLock); + + map_iterator it = mCache.find(key); + if (it == mCache.end()) + return false; + + mCache.erase(it); + return true; + } + + bool add(const key_type& key) + { // Add an entry to the cache, return true if it is new + boost::mutex::scoped_lock sl(mNCLock); + + map_iterator it = mCache.find(key); + if (it != mCache.end()) + { + it->second = time(NULL); + return false; + } + mCache.insert(std::make_pair(key, time(NULL))); + return true; + } + + void sweep() + { // Remove stale entries from the cache + time_t now = time(NULL); + boost::mutex::scoped_lock sl(mNCLock); + + time_t target; + if ((mTargetSize == 0) || (mCache.size() <= mTargetSize)) + target = now - mTargetAge; + else + { + target = now - (mTargetAge * mTargetSize / mCache.size()); + if (target > (now - 2)) + target = now - 2; + } + + map_iterator it = mCache.begin(); + while (it != mCache.end()) + { + if (it->second > now) + { + it->second = now; + ++it; + } + else if (it->second < target) + it = mCache.erase(it); + else + ++it; + } + } +}; + +#endif diff --git a/src/cpp/ripple/Ledger.cpp b/src/cpp/ripple/Ledger.cpp index c9c76beee..c4c483132 100644 --- a/src/cpp/ripple/Ledger.cpp +++ b/src/cpp/ripple/Ledger.cpp @@ -37,6 +37,7 @@ Ledger::Ledger(const RippleAddress& masterID, uint64 startAmount) : mTotCoins(st mAccountStateMap->armDirty(); writeBack(lepCREATE, startAccount->getSLE()); SHAMap::flushDirty(*mAccountStateMap->disarmDirty(), 256, hotACCOUNT_NODE, mLedgerSeq); + zeroFees(); } Ledger::Ledger(const uint256 &parentHash, const uint256 &transHash, const uint256 &accountHash, @@ -55,6 +56,7 @@ Ledger::Ledger(const uint256 &parentHash, const uint256 &transHash, const uint25 mAccountStateMap->fetchRoot(mAccountHash); mTransactionMap->setImmutable(); mAccountStateMap->setImmutable(); + zeroFees(); } Ledger::Ledger(Ledger& ledger, bool isMutable) : mTotCoins(ledger.mTotCoins), mLedgerSeq(ledger.mLedgerSeq), @@ -65,6 +67,7 @@ Ledger::Ledger(Ledger& ledger, bool isMutable) : mTotCoins(ledger.mTotCoins), mL mAccountStateMap(ledger.mAccountStateMap->snapShot(isMutable)) { // Create a new ledger that's a snapshot of this one updateHash(); + zeroFees(); } @@ -88,20 +91,23 @@ Ledger::Ledger(bool /* dummy */, Ledger& prevLedger) : } else mCloseTime = prevLedger.mCloseTime + mCloseResolution; + zeroFees(); } -Ledger::Ledger(const std::vector& rawLedger) : +Ledger::Ledger(const std::vector& rawLedger, bool hasPrefix) : mClosed(false), mValidHash(false), mAccepted(false), mImmutable(true) { Serializer s(rawLedger); - setRaw(s); + setRaw(s, hasPrefix); + zeroFees(); } -Ledger::Ledger(const std::string& rawLedger) : +Ledger::Ledger(const std::string& rawLedger, bool hasPrefix) : mClosed(false), mValidHash(false), mAccepted(false), mImmutable(true) { Serializer s(rawLedger); - setRaw(s); + setRaw(s, hasPrefix); + zeroFees(); } void Ledger::updateHash() @@ -121,9 +127,10 @@ void Ledger::updateHash() mValidHash = true; } -void Ledger::setRaw(Serializer &s) +void Ledger::setRaw(Serializer &s, bool hasPrefix) { SerializerIterator sit(s); + if (hasPrefix) sit.get32(); mLedgerSeq = sit.get32(); mTotCoins = sit.get64(); mParentHash = sit.get256(); @@ -157,7 +164,7 @@ void Ledger::addRaw(Serializer &s) const void Ledger::setAccepted(uint32 closeTime, int closeResolution, bool correctCloseTime) { // used when we witnessed the consensus assert(mClosed && !mAccepted); - mCloseTime = closeTime - (closeTime % closeResolution); + mCloseTime = correctCloseTime ? (closeTime - (closeTime % closeResolution)) : closeTime; mCloseResolution = closeResolution; mCloseFlags = correctCloseTime ? 0 : sLCF_NoConsensusTime; updateHash(); @@ -179,18 +186,22 @@ AccountState::pointer Ledger::getAccountState(const RippleAddress& accountID) #ifdef DEBUG // std::cerr << "Ledger:getAccountState(" << accountID.humanAccountID() << ")" << std::endl; #endif + SHAMapItem::pointer item = mAccountStateMap->peekItem(Ledger::getAccountRootIndex(accountID)); if (!item) { -#ifdef DEBUG -// std::cerr << " notfound" << std::endl; -#endif + cLog(lsDEBUG) << boost::str(boost::format("Ledger:getAccountState: not found: %s: %s") + % accountID.humanAccountID() + % Ledger::getAccountRootIndex(accountID).GetHex()); + return AccountState::pointer(); } + SerializedLedgerEntry::pointer sle = boost::make_shared(item->peekSerializer(), item->getTag()); if (sle->getType() != ltACCOUNT_ROOT) return AccountState::pointer(); + return boost::make_shared(sle,accountID); } @@ -367,6 +378,12 @@ void Ledger::saveAcceptedLedger(bool fromConsensus, LoadEvent::pointer event) assert (getAccountHash() == mAccountStateMap->getHash()); assert (getTransHash() == mTransactionMap->getHash()); + // Save the ledger header in the hashed object store + Serializer s(128); + s.add32(sHP_Ledger); + addRaw(s); + theApp->getHashedObjectStore().store(hotLEDGER, mLedgerSeq, s.peekData(), mHash); + { { ScopedLock sl(theApp->getLedgerDB()->getDBLock()); @@ -386,39 +403,46 @@ void Ledger::saveAcceptedLedger(bool fromConsensus, LoadEvent::pointer event) assert(type == SHAMapTreeNode::tnTRANSACTION_MD); SerializerIterator sit(item->peekSerializer()); Serializer rawTxn(sit.getVL()); - std::string escMeta(sqlEscape(sit.getVL())); + Serializer rawMeta(sit.getVL()); + std::string escMeta(sqlEscape(rawMeta.peekData())); SerializerIterator txnIt(rawTxn); SerializedTransaction txn(txnIt); assert(txn.getTransactionID() == item->getTag()); + TransactionMetaSet meta(item->getTag(), mLedgerSeq, rawMeta.peekData()); // Make sure transaction is in AccountTransactions. if (!SQL_EXISTS(db, boost::str(AcctTransExists % item->getTag().GetHex()))) { // Transaction not in AccountTransactions - std::vector accts = txn.getAffectedAccounts(); - - std::string sql = "INSERT INTO AccountTransactions (TransID, Account, LedgerSeq) VALUES "; - bool first = true; - for (std::vector::iterator it = accts.begin(), end = accts.end(); it != end; ++it) + const std::vector accts = meta.getAffectedAccounts(); + if (!accts.empty()) { - if (!first) - sql += ", ('"; - else + + std::string sql = "INSERT INTO AccountTransactions (TransID, Account, LedgerSeq) VALUES "; + bool first = true; + for (std::vector::const_iterator it = accts.begin(), end = accts.end(); it != end; ++it) { - sql += "('"; - first = false; + if (!first) + sql += ", ('"; + else + { + sql += "('"; + first = false; + } + sql += txn.getTransactionID().GetHex(); + sql += "','"; + sql += it->humanAccountID(); + sql += "',"; + sql += boost::lexical_cast(getLedgerSeq()); + sql += ")"; } - sql += txn.getTransactionID().GetHex(); - sql += "','"; - sql += it->humanAccountID(); - sql += "',"; - sql += boost::lexical_cast(getLedgerSeq()); - sql += ")"; + sql += ";"; + Log(lsTRACE) << "ActTx: " << sql; + db->executeSQL(sql); // may already be in there } - sql += ";"; - Log(lsTRACE) << "ActTx: " << sql; - db->executeSQL(sql); // may already be in there + else + cLog(lsWARNING) << "Transaction in ledger " << mLedgerSeq << " affects no accounts"; } if (SQL_EXISTS(db, boost::str(transExists % txn.getTransactionID().GetHex()))) @@ -458,11 +482,8 @@ void Ledger::saveAcceptedLedger(bool fromConsensus, LoadEvent::pointer event) return; } - theApp->getLedgerMaster().setFullLedger(shared_from_this()); event->stop(); - theApp->getOPs().pubLedger(shared_from_this()); - decPendingSaves(); } @@ -502,7 +523,7 @@ Ledger::pointer Ledger::getSQL(const std::string& sql) db->endIterRows(); } - Log(lsTRACE) << "Constructing ledger " << ledgerSeq << " from SQL"; +// Log(lsTRACE) << "Constructing ledger " << ledgerSeq << " from SQL"; Ledger::pointer ret = boost::make_shared(prevHash, transHash, accountHash, totCoins, closingTime, prevClosingTime, closeFlags, closeResolution, ledgerSeq); if (ret->getHash() != ledgerHash) @@ -517,12 +538,59 @@ Ledger::pointer Ledger::getSQL(const std::string& sql) assert(false); return Ledger::pointer(); } - Log(lsDEBUG) << "Loaded ledger: " << ledgerHash; + cLog(lsTRACE) << "Loaded ledger: " << ledgerHash; return ret; } -Ledger::pointer Ledger::loadByIndex(uint32 ledgerIndex) +uint256 Ledger::getHashByIndex(uint32 ledgerIndex) { + uint256 ret; + + std::string sql="SELECT LedgerHash FROM Ledgers WHERE LedgerSeq='"; + sql.append(boost::lexical_cast(ledgerIndex)); + sql.append("';"); + + std::string hash; + { + Database *db = theApp->getLedgerDB()->getDB(); + ScopedLock sl(theApp->getLedgerDB()->getDBLock()); + if (!db->executeSQL(sql) || !db->startIterRows()) + return ret; + db->getStr("LedgerHash", hash); + db->endIterRows(); + } + + ret.SetHex(hash); + return ret; +} + +bool Ledger::getHashesByIndex(uint32 ledgerIndex, uint256& ledgerHash, uint256& parentHash) +{ + std::string sql="SELECT LedgerHash,PrevHash FROM Ledgers WHERE LedgerSeq='"; + sql.append(boost::lexical_cast(ledgerIndex)); + sql.append("';"); + + std::string hash, prevHash; + { + Database *db = theApp->getLedgerDB()->getDB(); + ScopedLock sl(theApp->getLedgerDB()->getDBLock()); + if (!db->executeSQL(sql) || !db->startIterRows()) + return false; + db->getStr("LedgerHash", hash); + db->getStr("PrevHash", prevHash); + db->endIterRows(); + } + + ledgerHash.SetHex(hash); + parentHash.SetHex(prevHash); + + assert(ledgerHash.isNonZero() && ((ledgerIndex == 0) || parentHash.isNonZero())); + + return true; +} + +Ledger::pointer Ledger::loadByIndex(uint32 ledgerIndex) +{ // This is a low-level function with no caching std::string sql="SELECT * from Ledgers WHERE LedgerSeq='"; sql.append(boost::lexical_cast(ledgerIndex)); sql.append("';"); @@ -530,7 +598,7 @@ Ledger::pointer Ledger::loadByIndex(uint32 ledgerIndex) } Ledger::pointer Ledger::loadByHash(const uint256& ledgerHash) -{ +{ // This is a low-level function with no caching and only gets accepted ledgers std::string sql="SELECT * from Ledgers WHERE LedgerHash='"; sql.append(ledgerHash.GetHex()); sql.append("';"); @@ -559,10 +627,13 @@ Json::Value Ledger::getJson(int options) { Json::Value ledger(Json::objectValue); - boost::recursive_mutex::scoped_lock sl(mLock); - ledger["parentHash"] = mParentHash.GetHex(); - bool full = (options & LEDGER_JSON_FULL) != 0; + + boost::recursive_mutex::scoped_lock sl(mLock); + + ledger["parentHash"] = mParentHash.GetHex(); + ledger["seqNum"] = boost::lexical_cast(mLedgerSeq); + if(mClosed || full) { if (mClosed) @@ -585,6 +656,7 @@ Json::Value Ledger::getJson(int options) } else ledger["closed"] = false; + if (mTransactionMap && (full || ((options & LEDGER_JSON_DUMP_TXRP) != 0))) { Json::Value txns(Json::arrayValue); @@ -624,6 +696,7 @@ Json::Value Ledger::getJson(int options) } ledger["transactions"] = txns; } + if (mAccountStateMap && (full || ((options & LEDGER_JSON_DUMP_STATE) != 0))) { Json::Value state(Json::arrayValue); @@ -641,7 +714,6 @@ Json::Value Ledger::getJson(int options) } ledger["accountState"] = state; } - ledger["seqNum"] = boost::lexical_cast(mLedgerSeq); return ledger; } @@ -899,6 +971,13 @@ uint256 Ledger::getAccountRootIndex(const uint160& uAccountID) return s.getSHA512Half(); } +uint256 Ledger::getLedgerFeeIndex() +{ // get the index of the node that holds the fee schedul + Serializer s(2); + s.add16(spaceFee); + return s.getSHA512Half(); +} + uint256 Ledger::getLedgerFeatureIndex() { // get the index of the node that holds the last 256 ledgers Serializer s(2); @@ -922,23 +1001,77 @@ uint256 Ledger::getLedgerHashIndex(uint32 desiredLedgerIndex) return s.getSHA512Half(); } -int Ledger::getLedgerHashOffset(uint32 ledgerIndex) -{ // get the offset for this ledger's hash (or the first one after it) in the every-256-ledger table - return (ledgerIndex >> 8) % 256; +uint256 Ledger::getLedgerHash(uint32 ledgerIndex) +{ // return the hash of the specified ledger, 0 if not available + + // easy cases + if (ledgerIndex > mLedgerSeq) + { + cLog(lsWARNING) << "Can't get seq " << ledgerIndex << " from " << mLedgerSeq << " future"; + return uint256(); + } + + if (ledgerIndex == mLedgerSeq) + return getHash(); + + if (ledgerIndex == (mLedgerSeq - 1)) + return mParentHash; + + // within 256 + int diff = mLedgerSeq - ledgerIndex; + if (diff <= 256) + { + SLE::pointer hashIndex = getSLE(getLedgerHashIndex()); + if (hashIndex) + { + assert(hashIndex->getFieldU32(sfLastLedgerSequence) == (mLedgerSeq - 1)); + STVector256 vec = hashIndex->getFieldV256(sfHashes); + if (vec.size() >= diff) + return vec.at(vec.size() - diff); + cLog(lsWARNING) << "Ledger " << mLedgerSeq << " missing hash for " << ledgerIndex + << " (" << vec.size() << "," << diff << ")"; + } + else cLog(lsWARNING) << "Ledger " << mLedgerSeq << ":" << getHash() << " missing normal list"; + } + + if ((ledgerIndex & 0xff) != 0) + { + cLog(lsWARNING) << "Can't get seq " << ledgerIndex << " from " << mLedgerSeq << " past"; + return uint256(); + } + + // in skiplist + SLE::pointer hashIndex = getSLE(getLedgerHashIndex(ledgerIndex)); + if (hashIndex) + { + int lastSeq = hashIndex->getFieldU32(sfLastLedgerSequence); + assert(lastSeq >= ledgerIndex); + assert((lastSeq & 0xff) == 0); + int sDiff = (lastSeq - ledgerIndex) >> 8; + + STVector256 vec = hashIndex->getFieldV256(sfHashes); + if (vec.size() > sDiff) + return vec.at(vec.size() - sDiff - 1); + } + + cLog(lsWARNING) << "Can't get seq " << ledgerIndex << " from " << mLedgerSeq << " error"; + return uint256(); } -int Ledger::getLedgerHashOffset(uint32 desiredLedgerIndex, uint32 currentLedgerIndex) -{ // get the offset for this ledger's hash in the every-ledger table, -1 if not in it - if (desiredLedgerIndex >= currentLedgerIndex) - return -1; - - if (currentLedgerIndex < 256) - return desiredLedgerIndex; - - if (desiredLedgerIndex < (currentLedgerIndex - 256)) - return -1; - - return currentLedgerIndex - desiredLedgerIndex - 1; +std::vector< std::pair > Ledger::getLedgerHashes() +{ + std::vector< std::pair > ret; + SLE::pointer hashIndex = getSLE(getLedgerHashIndex()); + if (hashIndex) + { + STVector256 vec = hashIndex->getFieldV256(sfHashes); + int size = vec.size(); + ret.reserve(size); + uint32 seq = hashIndex->getFieldU32(sfLastLedgerSequence) - size; + for (int i = 0; i < size; ++i) + ret.push_back(std::make_pair(++seq, vec.at(i))); + } + return ret; } uint256 Ledger::getBookBase(const uint160& uTakerPaysCurrency, const uint160& uTakerPaysIssuerID, @@ -1097,10 +1230,7 @@ void Ledger::updateSkipList() std::vector hashes; if (!skipList) - { skipList = boost::make_shared(ltLEDGER_HASHES, hash); - skipList->setFieldU32(sfFirstLedgerSequence, prevIndex); - } else hashes = skipList->getFieldV256(sfHashes).peekValue(); @@ -1122,7 +1252,6 @@ void Ledger::updateSkipList() if (!skipList) { skipList = boost::make_shared(ltLEDGER_HASHES, hash); - skipList->setFieldU32(sfFirstLedgerSequence, prevIndex); } else hashes = skipList->getFieldV256(sfHashes).peekValue(); @@ -1154,18 +1283,25 @@ void Ledger::pendSave(bool fromConsensus) if (!fromConsensus && !theApp->isNewFlag(getHash(), SF_SAVED)) return; - boost::thread thread(boost::bind(&Ledger::saveAcceptedLedger, shared_from_this(), - fromConsensus, theApp->getJobQueue().getLoadEvent(jtDISK))); - thread.detach(); + { + boost::recursive_mutex::scoped_lock sl(sPendingSaveLock); + ++sPendingSaves; + } + + boost::thread(boost::bind(&Ledger::saveAcceptedLedger, shared_from_this(), + fromConsensus, theApp->getJobQueue().getLoadEvent(jtDISK))).detach(); - boost::recursive_mutex::scoped_lock sl(sPendingSaveLock); - ++sPendingSaves; } void Ledger::decPendingSaves() { - boost::recursive_mutex::scoped_lock sl(sPendingSaveLock); - --sPendingSaves; + { + boost::recursive_mutex::scoped_lock sl(sPendingSaveLock); + --sPendingSaves; + if (sPendingSaves != 0) + return; + } + theApp->getLedgerMaster().resumeAcquiring(); } void Ledger::ownerDirDescriber(SLE::ref sle, const uint160& owner) @@ -1185,5 +1321,51 @@ void Ledger::qualityDirDescriber(SLE::ref sle, sle->setFieldU64(sfExchangeRate, uRate); } +void Ledger::zeroFees() +{ + mBaseFee = 0; + mReferenceFeeUnits = 0; + mReserveBase = 0; + mReserveIncrement = 0; +} + +void Ledger::updateFees() +{ + mBaseFee = theConfig.FEE_DEFAULT; + mReferenceFeeUnits = 10; + mReserveBase = theConfig.FEE_ACCOUNT_RESERVE; + mReserveIncrement = theConfig.FEE_OWNER_RESERVE; + + LedgerStateParms p = lepNONE; + SLE::pointer sle = getASNode(p, Ledger::getLedgerFeeIndex(), ltFEE_SETTINGS); + if (!sle) + return; + + if (sle->getFieldIndex(sfBaseFee) != -1) + mBaseFee = sle->getFieldU64(sfBaseFee); + + if (sle->getFieldIndex(sfReferenceFeeUnits) != -1) + mReferenceFeeUnits = sle->getFieldU32(sfReferenceFeeUnits); + + if (sle->getFieldIndex(sfReserveBase) != -1) + mReserveBase = sle->getFieldU32(sfReserveBase); + + if (sle->getFieldIndex(sfReserveIncrement) != -1) + mReserveIncrement = sle->getFieldU32(sfReserveIncrement); +} + +uint64 Ledger::scaleFeeBase(uint64 fee) +{ + if (!mBaseFee) + updateFees(); + return theApp->getFeeTrack().scaleFeeBase(fee, mBaseFee, mReferenceFeeUnits); +} + +uint64 Ledger::scaleFeeLoad(uint64 fee) +{ + if (!mBaseFee) + updateFees(); + return theApp->getFeeTrack().scaleFeeLoad(fee, mBaseFee, mReferenceFeeUnits); +} // vim:ts=4 diff --git a/src/cpp/ripple/Ledger.h b/src/cpp/ripple/Ledger.h index bfbe40534..f63cb0a70 100644 --- a/src/cpp/ripple/Ledger.h +++ b/src/cpp/ripple/Ledger.h @@ -78,6 +78,10 @@ private: uint32 mCloseFlags; // flags indicating how this ledger close took place bool mClosed, mValidHash, mAccepted, mImmutable; + uint32 mReferenceFeeUnits; // Fee units for the reference transaction + uint32 mReserveBase, mReserveIncrement; // Reserve basse and increment in fee units + uint64 mBaseFee; // Ripple cost of the reference transaction + SHAMap::pointer mTransactionMap, mAccountStateMap; mutable boost::recursive_mutex mLock; @@ -95,6 +99,9 @@ protected: static void decPendingSaves(); void saveAcceptedLedger(bool fromConsensus, LoadEvent::pointer); + void updateFees(); + void zeroFees(); + public: Ledger(const RippleAddress& masterID, uint64 startAmount); // used for the starting bootstrap ledger @@ -102,9 +109,8 @@ public: uint64 totCoins, uint32 closeTime, uint32 parentCloseTime, int closeFlags, int closeResolution, uint32 ledgerSeq); // used for database ledgers - Ledger(const std::vector& rawLedger); - - Ledger(const std::string& rawLedger); + Ledger(const std::vector& rawLedger, bool hasPrefix); + Ledger(const std::string& rawLedger, bool hasPrefix); Ledger(bool dummy, Ledger& previous); // ledger after this one @@ -125,7 +131,7 @@ public: // ledger signature operations void addRaw(Serializer &s) const; - void setRaw(Serializer& s); + void setRaw(Serializer& s, bool hasPrefix); uint256 getHash(); const uint256& getParentHash() const { return mParentHash; } @@ -172,9 +178,11 @@ public: SLE::pointer getAccountRoot(const RippleAddress& naAccountID); void updateSkipList(); - // database functions + // database functions (low-level) static Ledger::pointer loadByIndex(uint32 ledgerIndex); static Ledger::pointer loadByHash(const uint256& ledgerHash); + static uint256 getHashByIndex(uint32 index); + static bool getHashesByIndex(uint32 index, uint256& ledgerHash, uint256& parentHash); void pendSave(bool fromConsensus); // next/prev function @@ -191,8 +199,11 @@ public: static uint256 getLedgerHashIndex(uint32 desiredLedgerIndex); static int getLedgerHashOffset(uint32 desiredLedgerIndex); static int getLedgerHashOffset(uint32 desiredLedgerIndex, uint32 currentLedgerIndex); + uint256 getLedgerHash(uint32 ledgerIndex); + std::vector< std::pair > getLedgerHashes(); static uint256 getLedgerFeatureIndex(); + static uint256 getLedgerFeeIndex(); // index calculation functions static uint256 getAccountRootIndex(const uint160& uAccountID); @@ -293,10 +304,10 @@ public: SLE::pointer getRippleState(LedgerStateParms& parms, const uint256& uNode); SLE::pointer getRippleState(const uint256& uNode) - { - LedgerStateParms qry = lepNONE; - return getRippleState(qry, uNode); - } + { + LedgerStateParms qry = lepNONE; + return getRippleState(qry, uNode); + } SLE::pointer getRippleState(const RippleAddress& naA, const RippleAddress& naB, const uint160& uCurrency) { return getRippleState(getRippleStateIndex(naA, naB, uCurrency)); } @@ -304,6 +315,34 @@ public: SLE::pointer getRippleState(const uint160& uiA, const uint160& uiB, const uint160& uCurrency) { return getRippleState(getRippleStateIndex(RippleAddress::createAccountID(uiA), RippleAddress::createAccountID(uiB), uCurrency)); } + uint32 getReferenceFeeUnits() + { + if (!mBaseFee) updateFees(); + return mReferenceFeeUnits; + } + + uint64 getBaseFee() + { + if (!mBaseFee) updateFees(); + return mBaseFee; + } + + uint64 getReserve(int increments) + { + if (!mBaseFee) updateFees(); + return scaleFeeBase(static_cast(increments) * mReserveIncrement + mReserveBase); + } + + uint64 getReserveInc() + { + if (!mBaseFee) updateFees(); + return mReserveIncrement; + } + + uint64 scaleFeeBase(uint64 fee); + uint64 scaleFeeLoad(uint64 fee); + + Json::Value getJson(int options); void addJson(Json::Value&, int options); diff --git a/src/cpp/ripple/LedgerAcquire.cpp b/src/cpp/ripple/LedgerAcquire.cpp index 2335813ba..d97a60f9d 100644 --- a/src/cpp/ripple/LedgerAcquire.cpp +++ b/src/cpp/ripple/LedgerAcquire.cpp @@ -11,15 +11,16 @@ #include "HashPrefixes.h" SETUP_LOG(); -DECLARE_INSTANCE(PeerSet); +DECLARE_INSTANCE(LedgerAcquire); #define LA_DEBUG #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()) + mComplete(false), mFailed(false), mProgress(true), mAggressive(true), mTimer(theApp->getIOService()) { + mLastAction = time(NULL); assert((mTimerInterval > 10) && (mTimerInterval < 30000)); } @@ -37,7 +38,7 @@ void PeerSet::badPeer(Peer::ref ptr) mPeers.erase(ptr->getPeerId()); } -void PeerSet::resetTimer() +void PeerSet::setTimer() { mTimer.expires_from_now(boost::posix_time::milliseconds(mTimerInterval)); mTimer.async_wait(boost::bind(&PeerSet::TimerEntry, pmDowncast(), boost::asio::placeholders::error)); @@ -45,6 +46,9 @@ void PeerSet::resetTimer() void PeerSet::invokeOnTimer() { + if (isDone()) + return; + if (!mProgress) { ++mTimeouts; @@ -56,6 +60,9 @@ void PeerSet::invokeOnTimer() mProgress = false; onTimer(true); } + + if (!isDone()) + setTimer(); } void PeerSet::TimerEntry(boost::weak_ptr wptr, const boost::system::error_code& result) @@ -67,76 +74,136 @@ void PeerSet::TimerEntry(boost::weak_ptr wptr, const boost::system::err ptr->invokeOnTimer(); } -LedgerAcquire::LedgerAcquire(const uint256& hash) : PeerSet(hash, LEDGER_ACQUIRE_TIMEOUT), - mHaveBase(false), mHaveState(false), mHaveTransactions(false), mAborted(false), mSignaled(false), mAccept(false) +bool PeerSet::isActive() +{ + boost::recursive_mutex::scoped_lock sl(mLock); + return !isDone(); +} + +LedgerAcquire::LedgerAcquire(const uint256& hash) : PeerSet(hash, LEDGER_ACQUIRE_TIMEOUT), + mHaveBase(false), mHaveState(false), mHaveTransactions(false), mAborted(false), mSignaled(false), mAccept(false), + mByHash(true) { #ifdef LA_DEBUG cLog(lsTRACE) << "Acquiring ledger " << mHash; #endif + tryLocal(); } bool LedgerAcquire::tryLocal() { // return value: true = no more work to do HashedObject::pointer node = theApp->getHashedObjectStore().retrieve(mHash); if (!node) - return false; + { + mLedger = theApp->getLedgerMaster().getLedgerByHash(mHash); + if (!mLedger) + return false; + } + else + mLedger = boost::make_shared(strCopy(node->getData()), true); + + if (mLedger->getHash() != mHash) + { // We know for a fact the ledger can never be acquired + cLog(lsWARNING) << mHash << " cannot be a ledger"; + mFailed = true; + return true; + } - mLedger = boost::make_shared(strCopy(node->getData())); - assert(mLedger->getHash() == mHash); mHaveBase = true; - if (!mLedger->getTransHash()) + if (mLedger->getTransHash().isZero()) + { + cLog(lsDEBUG) << "No TXNs to fetch"; mHaveTransactions = true; + } else { try { mLedger->peekTransactionMap()->fetchRoot(mLedger->getTransHash()); + cLog(lsDEBUG) << "Got root txn map locally"; + std::vector h = mLedger->peekTransactionMap()->getNeededHashes(1); + if (h.empty()) + { + cLog(lsDEBUG) << "Had full txn map locally"; + mHaveTransactions = true; + } } catch (SHAMapMissingNode&) { } } - if (!mLedger->getAccountHash()) + if (mLedger->getAccountHash().isZero()) mHaveState = true; else { try { mLedger->peekAccountStateMap()->fetchRoot(mLedger->getAccountHash()); + cLog(lsDEBUG) << "Got root AS map locally"; + std::vector h = mLedger->peekAccountStateMap()->getNeededHashes(1); + if (h.empty()) + { + cLog(lsDEBUG) << "Had full AS map locally"; + mHaveState = true; + } } catch (SHAMapMissingNode&) { } } - return mHaveTransactions && mHaveState; + if (mHaveTransactions && mHaveState) + { + cLog(lsDEBUG) << "Had everything locally"; + mComplete = true; + } + + return mComplete; } void LedgerAcquire::onTimer(bool progress) { if (getTimeouts() > 6) { + cLog(lsWARNING) << "Six timeouts for ledger " << mHash; setFailed(); done(); + return; } - else if (!progress) + + mRecentTXNodes.clear(); + mRecentASNodes.clear(); + + if (!progress) { + mAggressive = true; + cLog(lsDEBUG) << "No progress for ledger " << mHash; if (!getPeerCount()) addPeers(); else - trigger(Peer::pointer(), true); + trigger(Peer::pointer()); } + else + mByHash = true; } void LedgerAcquire::addPeers() { std::vector peerList = theApp->getConnectionPool().getPeerVector(); + int vSize = peerList.size(); + if (vSize == 0) + return; + + // We traverse the peer list in random order so as not to favor any particular peer + int firstPeer = rand() & vSize; + bool found = false; - BOOST_FOREACH(Peer::ref peer, peerList) + for (int i = 0; i < vSize; ++i) { + Peer::ref peer = peerList[(i + firstPeer) % vSize]; if (peer->hasLedger(getHash())) { found = true; @@ -145,10 +212,8 @@ void LedgerAcquire::addPeers() } if (!found) - { - BOOST_FOREACH(Peer::ref peer, peerList) - peerHas(peer); - } + for (int i = 0; i < vSize; ++i) + peerHas(peerList[(i + firstPeer) % vSize]); } boost::weak_ptr LedgerAcquire::pmDowncast() @@ -161,36 +226,46 @@ void LedgerAcquire::done() if (mSignaled) return; mSignaled = true; + touch(); + #ifdef LA_DEBUG cLog(lsTRACE) << "Done acquiring ledger " << mHash; #endif + + assert(isComplete() || isFailed()); + std::vector< boost::function > triggers; + { + boost::recursive_mutex::scoped_lock sl(mLock); + triggers.swap(mOnComplete); + } - setComplete(); - mLock.lock(); - triggers = mOnComplete; - mOnComplete.clear(); - mLock.unlock(); - - if (mLedger) + if (isComplete() && !isFailed() && mLedger) { if (mAccept) + { + mLedger->setClosed(); mLedger->setAccepted(); + } theApp->getLedgerMaster().storeLedger(mLedger); } + else + theApp->getMasterLedgerAcquire().logFailure(mHash); for (unsigned int i = 0; i < triggers.size(); ++i) triggers[i](shared_from_this()); } -void LedgerAcquire::addOnComplete(boost::function trigger) +bool LedgerAcquire::addOnComplete(boost::function trigger) { - mLock.lock(); + boost::recursive_mutex::scoped_lock sl(mLock); + if (isDone()) + return false; mOnComplete.push_back(trigger); - mLock.unlock(); + return true; } -void LedgerAcquire::trigger(Peer::ref peer, bool timer) +void LedgerAcquire::trigger(Peer::ref peer) { if (mAborted || mComplete || mFailed) { @@ -211,12 +286,75 @@ void LedgerAcquire::trigger(Peer::ref peer, bool timer) cLog(lsTRACE) << "base=" << mHaveBase << " tx=" << mHaveTransactions << " as=" << mHaveState; } + if (!mHaveBase) + { + tryLocal(); + if (mFailed) + { + cLog(lsWARNING) << " failed local for " << mHash; + } + } + ripple::TMGetLedger tmGL; tmGL.set_ledgerhash(mHash.begin(), mHash.size()); if (getTimeouts() != 0) + { tmGL.set_querytype(ripple::qtINDIRECT); - if (!mHaveBase) + if (!isProgress() && !mFailed && mByHash && (getTimeouts() > 2)) + { + std::vector need = getNeededHashes(); + if (!need.empty()) + { + ripple::TMGetObjectByHash tmBH; + tmBH.set_query(true); + tmBH.set_ledgerhash(mHash.begin(), mHash.size()); + if (mHaveBase) + tmBH.set_seq(mLedger->getLedgerSeq()); + bool typeSet = false; + BOOST_FOREACH(neededHash_t& p, need) + { + cLog(lsWARNING) << "Want: " << p.second; + theApp->getOPs().addWantedHash(p.second); + if (!typeSet) + { + tmBH.set_type(p.first); + typeSet = true; + } + if (p.first == tmBH.type()) + { + ripple::TMIndexedObject *io = tmBH.add_objects(); + io->set_hash(p.second.begin(), p.second.size()); + } + } + PackedMessage::pointer packet = boost::make_shared(tmBH, ripple::mtGET_OBJECTS); + { + boost::recursive_mutex::scoped_lock sl(mLock); + for (boost::unordered_map::iterator it = mPeers.begin(), end = mPeers.end(); + it != end; ++it) + { + Peer::pointer iPeer = theApp->getConnectionPool().getPeerById(it->first); + if (iPeer) + { + mByHash = false; + iPeer->sendPacket(packet); + } + } + } + cLog(lsINFO) << "Attempting by hash fetch for ledger " << mHash; + } + else + { + cLog(lsINFO) << "getNeededHashes says acquire is complete"; + mHaveBase = true; + mHaveTransactions = true; + mHaveState = true; + mComplete = true; + } + } + } + + if (!mHaveBase && !mFailed) { tmGL.set_itype(ripple::liBASE); cLog(lsTRACE) << "Sending base request to " << (peer ? "selected peer" : "all peers"); @@ -224,11 +362,10 @@ void LedgerAcquire::trigger(Peer::ref peer, bool timer) return; } - assert(mLedger); if (mLedger) tmGL.set_ledgerseq(mLedger->getLedgerSeq()); - if (mHaveBase && !mHaveTransactions) + if (mHaveBase && !mHaveTransactions && !mFailed) { assert(mLedger); if (mLedger->peekTransactionMap()->getHash().isZero()) @@ -242,11 +379,13 @@ void LedgerAcquire::trigger(Peer::ref peer, bool timer) { std::vector nodeIDs; std::vector nodeHashes; - TransactionStateSF tFilter(mLedger->getHash(), mLedger->getLedgerSeq()); - mLedger->peekTransactionMap()->getMissingNodes(nodeIDs, nodeHashes, 128, &tFilter); + nodeIDs.reserve(256); + nodeHashes.reserve(256); + mLedger->peekTransactionMap()->getMissingNodes(nodeIDs, nodeHashes, 256, NULL); if (nodeIDs.empty()) { - if (!mLedger->peekTransactionMap()->isValid()) mFailed = true; + if (!mLedger->peekTransactionMap()->isValid()) + mFailed = true; else { mHaveTransactions = true; @@ -256,19 +395,24 @@ void LedgerAcquire::trigger(Peer::ref peer, bool timer) } else { - tmGL.set_itype(ripple::liTX_NODE); - BOOST_FOREACH(SHAMapNode& it, nodeIDs) + if (!mAggressive) + filterNodes(nodeIDs, nodeHashes, mRecentTXNodes, 128, !isProgress()); + if (!nodeIDs.empty()) { - *(tmGL.add_nodeids()) = it.getRawString(); + tmGL.set_itype(ripple::liTX_NODE); + BOOST_FOREACH(SHAMapNode& it, nodeIDs) + { + *(tmGL.add_nodeids()) = it.getRawString(); + } + cLog(lsTRACE) << "Sending TX node " << nodeIDs.size() + << " request to " << (peer ? "selected peer" : "all peers"); + sendRequest(tmGL, peer); } - cLog(lsTRACE) << "Sending TX node " << nodeIDs.size() - << " request to " << (peer ? "selected peer" : "all peers"); - sendRequest(tmGL, peer); } } } - if (mHaveBase && !mHaveState) + if (mHaveBase && !mHaveState && !mFailed) { assert(mLedger); if (mLedger->peekAccountStateMap()->getHash().isZero()) @@ -282,11 +426,13 @@ void LedgerAcquire::trigger(Peer::ref peer, bool timer) { std::vector nodeIDs; std::vector nodeHashes; - AccountStateSF aFilter(mLedger->getHash(), mLedger->getLedgerSeq()); - mLedger->peekAccountStateMap()->getMissingNodes(nodeIDs, nodeHashes, 128, &aFilter); + nodeIDs.reserve(256); + nodeHashes.reserve(256); + mLedger->peekAccountStateMap()->getMissingNodes(nodeIDs, nodeHashes, 256, NULL); if (nodeIDs.empty()) { - if (!mLedger->peekAccountStateMap()->isValid()) mFailed = true; + if (!mLedger->peekAccountStateMap()->isValid()) + mFailed = true; else { mHaveState = true; @@ -296,24 +442,28 @@ void LedgerAcquire::trigger(Peer::ref peer, bool timer) } else { - tmGL.set_itype(ripple::liAS_NODE); - BOOST_FOREACH(SHAMapNode& it, nodeIDs) - *(tmGL.add_nodeids()) = it.getRawString(); - cLog(lsTRACE) << "Sending AS node " << nodeIDs.size() - << " request to " << (peer ? "selected peer" : "all peers"); - tLog(nodeIDs.size() == 1, lsTRACE) << "AS node: " << nodeIDs[0]; - sendRequest(tmGL, peer); + if (!mAggressive) + filterNodes(nodeIDs, nodeHashes, mRecentASNodes, 128, !isProgress()); + if (!nodeIDs.empty()) + { + tmGL.set_itype(ripple::liAS_NODE); + BOOST_FOREACH(SHAMapNode& it, nodeIDs) + *(tmGL.add_nodeids()) = it.getRawString(); + cLog(lsTRACE) << "Sending AS node " << nodeIDs.size() + << " request to " << (peer ? "selected peer" : "all peers"); + tLog(nodeIDs.size() == 1, lsTRACE) << "AS node: " << nodeIDs[0]; + sendRequest(tmGL, peer); + } } } } if (mComplete || mFailed) { - cLog(lsDEBUG) << "Done:" << (mComplete ? " complete" : "") << (mFailed ? " failed" : ""); + cLog(lsDEBUG) << "Done:" << (mComplete ? " complete" : "") << (mFailed ? " failed " : " ") + << mLedger->getLedgerSeq(); done(); } - else if (timer) - resetTimer(); } void PeerSet::sendRequest(const ripple::TMGetLedger& tmGL, Peer::ref peer) @@ -361,14 +511,67 @@ int PeerSet::getPeerCount() const return ret; } -bool LedgerAcquire::takeBase(const std::string& data) +void LedgerAcquire::filterNodes(std::vector& nodeIDs, std::vector& nodeHashes, + std::set& recentNodes, int max, bool aggressive) +{ // ask for new nodes in preference to ones we've already asked for + std::vector duplicates; + duplicates.reserve(nodeIDs.size()); + + int dupCount=0; + + for (unsigned int i = 0; i < nodeIDs.size(); ++i) + { + bool isDup = recentNodes.count(nodeIDs[i]) != 0; + duplicates.push_back(isDup); + if (isDup) + ++dupCount; + } + + if (dupCount == nodeIDs.size()) + { // all duplicates + if (!aggressive) + { + nodeIDs.clear(); + nodeHashes.clear(); + return; + } + } + else if (dupCount > 0) + { // some, but not all, duplicates + int insertPoint = 0; + for (unsigned int i = 0; i < nodeIDs.size(); ++i) + if (!duplicates[i]) + { // Keep this node + if (insertPoint != i) + { + nodeIDs[insertPoint] = nodeIDs[i]; + nodeHashes[insertPoint] = nodeHashes[i]; + } + ++insertPoint; + } + cLog(lsDEBUG) << "filterNodes " << nodeIDs.size() << " to " << insertPoint; + nodeIDs.resize(insertPoint); + nodeHashes.resize(insertPoint); + } + + if (nodeIDs.size() > max) + { + nodeIDs.resize(max); + nodeHashes.resize(max); + } + + BOOST_FOREACH(const SHAMapNode& n, nodeIDs) + recentNodes.insert(n); +} + +bool LedgerAcquire::takeBase(const std::string& data) // data must not have hash prefix { // Return value: true=normal, false=bad data #ifdef LA_DEBUG cLog(lsTRACE) << "got base acquiring ledger " << mHash; #endif boost::recursive_mutex::scoped_lock sl(mLock); if (mHaveBase) return true; - mLedger = boost::make_shared(data); + mLedger = boost::make_shared(data, false); if (mLedger->getHash() != mHash) { cLog(lsWARNING) << "Acquire hash mismatch"; @@ -398,10 +601,12 @@ bool LedgerAcquire::takeBase(const std::string& data) bool LedgerAcquire::takeTxNode(const std::list& nodeIDs, const std::list< std::vector >& data, SMAddNode& san) { - if (!mHaveBase) return false; + if (!mHaveBase) + return false; + std::list::const_iterator nodeIDit = nodeIDs.begin(); std::list< std::vector >::const_iterator nodeDatait = data.begin(); - TransactionStateSF tFilter(mLedger->getHash(), mLedger->getLedgerSeq()); + TransactionStateSF tFilter(mLedger->getLedgerSeq()); while (nodeIDit != nodeIDs.end()) { if (nodeIDit->isRoot()) @@ -445,7 +650,7 @@ bool LedgerAcquire::takeAsNode(const std::list& nodeIDs, std::list::const_iterator nodeIDit = nodeIDs.begin(); std::list< std::vector >::const_iterator nodeDatait = data.begin(); - AccountStateSF tFilter(mLedger->getHash(), mLedger->getLedgerSeq()); + AccountStateSF tFilter(mLedger->getLedgerSeq()); while (nodeIDit != nodeIDs.end()) { if (nodeIDit->isRoot()) @@ -482,7 +687,7 @@ bool LedgerAcquire::takeAsRootNode(const std::vector& data, SMAdd { if (!mHaveBase) return false; - AccountStateSF tFilter(mLedger->getHash(), mLedger->getLedgerSeq()); + AccountStateSF tFilter(mLedger->getLedgerSeq()); return san.combine( mLedger->peekAccountStateMap()->addRootNode(mLedger->getAccountHash(), data, snfWIRE, &tFilter)); } @@ -491,7 +696,7 @@ bool LedgerAcquire::takeTxRootNode(const std::vector& data, SMAdd { if (!mHaveBase) return false; - TransactionStateSF tFilter(mLedger->getHash(), mLedger->getLedgerSeq()); + TransactionStateSF tFilter(mLedger->getLedgerSeq()); return san.combine( mLedger->peekTransactionMap()->addRootNode(mLedger->getTransHash(), data, snfWIRE, &tFilter)); } @@ -502,11 +707,18 @@ LedgerAcquire::pointer LedgerAcquireMaster::findCreate(const uint256& hash) boost::mutex::scoped_lock sl(mLock); LedgerAcquire::pointer& ptr = mLedgers[hash]; if (ptr) + { + ptr->touch(); return ptr; + } ptr = boost::make_shared(hash); - assert(mLedgers[hash] == ptr); - ptr->addPeers(); - ptr->resetTimer(); // Cannot call in constructor + if (!ptr->isDone()) + { + ptr->addPeers(); + ptr->setTimer(); // Cannot call in constructor + } + else + cLog(lsDEBUG) << "LedgerAcquireMaster acquiring ledger we already have"; return ptr; } @@ -516,10 +728,69 @@ LedgerAcquire::pointer LedgerAcquireMaster::find(const uint256& hash) boost::mutex::scoped_lock sl(mLock); std::map::iterator it = mLedgers.find(hash); if (it != mLedgers.end()) + { + it->second->touch(); return it->second; + } return LedgerAcquire::pointer(); } +std::vector LedgerAcquire::getNeededHashes() +{ + std::vector ret; + if (!mHaveBase) + { + ret.push_back(std::make_pair(ripple::TMGetObjectByHash::otLEDGER, mHash)); + return ret; + } + if (!mHaveState) + { + std::vector v = mLedger->peekAccountStateMap()->getNeededHashes(16); + BOOST_FOREACH(const uint256& h, v) + ret.push_back(std::make_pair(ripple::TMGetObjectByHash::otSTATE_NODE, h)); + } + if (!mHaveTransactions) + { + std::vector v = mLedger->peekTransactionMap()->getNeededHashes(16); + BOOST_FOREACH(const uint256& h, v) + ret.push_back(std::make_pair(ripple::TMGetObjectByHash::otTRANSACTION_NODE, h)); + } + return ret; +} + +Json::Value LedgerAcquire::getJson(int) +{ + Json::Value ret(Json::objectValue); + ret["hash"] = mHash.GetHex(); + if (mComplete) + ret["complete"] = true; + if (mFailed) + ret["failed"] = true; + ret["have_base"] = mHaveBase; + ret["have_state"] = mHaveState; + ret["have_transactions"] = mHaveTransactions; + if (mAborted) + ret["aborted"] = true; + ret["timeouts"] = getTimeouts(); + if (mHaveBase && !mHaveState) + { + Json::Value hv(Json::arrayValue); + std::vector v = mLedger->peekAccountStateMap()->getNeededHashes(16); + BOOST_FOREACH(const uint256& h, v) + hv.append(h.GetHex()); + ret["needed_state_hashes"] = hv; + } + if (mHaveBase && !mHaveTransactions) + { + Json::Value hv(Json::arrayValue); + std::vector v = mLedger->peekTransactionMap()->getNeededHashes(16); + BOOST_FOREACH(const uint256& h, v) + hv.append(h.GetHex()); + ret["needed_transaction_hashes"] = hv; + } + return ret; +} + bool LedgerAcquireMaster::hasLedger(const uint256& hash) { assert(hash.isNonZero()); @@ -573,7 +844,8 @@ SMAddNode LedgerAcquireMaster::gotLedgerData(ripple::TMLedgerData& packet, Peer: { cLog(lsWARNING) << "Included TXbase invalid"; } - ledger->trigger(peer, false); + if (!san.isInvalid()) + ledger->trigger(peer); return san; } @@ -605,7 +877,7 @@ SMAddNode LedgerAcquireMaster::gotLedgerData(ripple::TMLedgerData& packet, Peer: else ledger->takeAsNode(nodeIDs, nodeData, ret); if (!ret.isInvalid()) - ledger->trigger(peer, false); + ledger->trigger(peer); return ret; } @@ -613,4 +885,39 @@ SMAddNode LedgerAcquireMaster::gotLedgerData(ripple::TMLedgerData& packet, Peer: return SMAddNode::invalid(); } +void LedgerAcquireMaster::sweep() +{ + mRecentFailures.sweep(); + + time_t now = time(NULL); + boost::mutex::scoped_lock sl(mLock); + + std::map::iterator it = mLedgers.begin(); + while (it != mLedgers.end()) + { + if (it->second->getLastAction() > now) + { + it->second->touch(); + ++it; + } + else if ((it->second->getLastAction() + 60) < now) + mLedgers.erase(it++); + else + ++it; + } +} + +int LedgerAcquireMaster::getFetchCount() +{ + int ret = 0; + { + typedef std::pair u256_acq_pair; + boost::mutex::scoped_lock sl(mLock); + BOOST_FOREACH(const u256_acq_pair& it, mLedgers) + if (it.second->isActive()) + ++ret; + } + return ret; +} + // vim:ts=4 diff --git a/src/cpp/ripple/LedgerAcquire.h b/src/cpp/ripple/LedgerAcquire.h index 2f9778c56..0f2c6abd2 100644 --- a/src/cpp/ripple/LedgerAcquire.h +++ b/src/cpp/ripple/LedgerAcquire.h @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -18,14 +19,20 @@ #include "InstanceCounter.h" #include "ripple.pb.h" -DEFINE_INSTANCE(PeerSet); +// How long before we try again to acquire the same ledger +#ifndef LEDGER_REACQUIRE_INTERVAL +#define LEDGER_REACQUIRE_INTERVAL 600 +#endif -class PeerSet : private IS_INSTANCE(PeerSet) +DEFINE_INSTANCE(LedgerAcquire); + +class PeerSet { protected: uint256 mHash; int mTimerInterval, mTimeouts; - bool mComplete, mFailed, mProgress; + bool mComplete, mFailed, mProgress, mAggressive; + time_t mLastAction; boost::recursive_mutex mLock; boost::asio::deadline_timer mTimer; @@ -43,14 +50,19 @@ public: bool isFailed() const { return mFailed; } int getTimeouts() const { return mTimeouts; } - void progress() { mProgress = true; } + bool isActive(); + void progress() { mProgress = true; mAggressive = false; } + bool isProgress() { return mProgress; } + void touch() { mLastAction = time(NULL); } + time_t getLastAction() { return mLastAction; } void peerHas(Peer::ref); void badPeer(Peer::ref); - void resetTimer(); + void setTimer(); int takePeerSetFrom(const PeerSet& s); int getPeerCount() const; + virtual bool isDone() const { return mComplete || mFailed; } protected: virtual void newPeer(Peer::ref) = 0; @@ -65,21 +77,25 @@ private: static void TimerEntry(boost::weak_ptr, const boost::system::error_code& result); }; -class LedgerAcquire : public PeerSet, public boost::enable_shared_from_this +class LedgerAcquire : + private IS_INSTANCE(LedgerAcquire), public PeerSet, public boost::enable_shared_from_this { // A ledger we are trying to acquire public: typedef boost::shared_ptr pointer; protected: Ledger::pointer mLedger; - bool mHaveBase, mHaveState, mHaveTransactions, mAborted, mSignaled, mAccept; + bool mHaveBase, mHaveState, mHaveTransactions, mAborted, mSignaled, mAccept, mByHash; + + std::set mRecentTXNodes; + std::set mRecentASNodes; std::vector< boost::function > mOnComplete; void done(); void onTimer(bool progress); - void newPeer(Peer::ref peer) { trigger(peer, false); } + void newPeer(Peer::ref peer) { trigger(peer); } boost::weak_ptr pmDowncast(); @@ -90,11 +106,12 @@ public: bool isBase() const { return mHaveBase; } bool isAcctStComplete() const { return mHaveState; } bool isTransComplete() const { return mHaveTransactions; } - Ledger::pointer getLedger() { return mLedger; } + bool isDone() const { return mAborted || isComplete() || isFailed(); } + Ledger::ref getLedger() { return mLedger; } void abort() { mAborted = true; } bool setAccept() { if (mAccept) return false; mAccept = true; return true; } - void addOnComplete(boost::function); + bool addOnComplete(boost::function); bool takeBase(const std::string& data); bool takeTxNode(const std::list& IDs, const std::list >& data, @@ -103,9 +120,17 @@ public: bool takeAsNode(const std::list& IDs, const std::list >& data, SMAddNode&); bool takeAsRootNode(const std::vector& data, SMAddNode&); - void trigger(Peer::ref, bool timer); + void trigger(Peer::ref); bool tryLocal(); void addPeers(); + + typedef std::pair neededHash_t; + std::vector getNeededHashes(); + + static void filterNodes(std::vector& nodeIDs, std::vector& nodeHashes, + std::set& recentNodes, int max, bool aggressive); + + Json::Value getJson(int); }; class LedgerAcquireMaster @@ -113,15 +138,22 @@ class LedgerAcquireMaster protected: boost::mutex mLock; std::map mLedgers; + KeyCache mRecentFailures; public: - LedgerAcquireMaster() { ; } + LedgerAcquireMaster() : mRecentFailures("LedgerAcquireRecentFailures", 0, LEDGER_REACQUIRE_INTERVAL) { ; } LedgerAcquire::pointer findCreate(const uint256& hash); LedgerAcquire::pointer find(const uint256& hash); bool hasLedger(const uint256& ledgerHash); void dropLedger(const uint256& ledgerHash); SMAddNode gotLedgerData(ripple::TMLedgerData& packet, Peer::ref); + + int getFetchCount(); + void logFailure(const uint256& h) { mRecentFailures.add(h); } + bool isFailure(const uint256& h) { return mRecentFailures.isPresent(h, false); } + + void sweep(); }; #endif diff --git a/src/cpp/ripple/LedgerConsensus.cpp b/src/cpp/ripple/LedgerConsensus.cpp index bf92b700c..f066b2f83 100644 --- a/src/cpp/ripple/LedgerConsensus.cpp +++ b/src/cpp/ripple/LedgerConsensus.cpp @@ -16,15 +16,19 @@ #define TX_ACQUIRE_TIMEOUT 250 +#define LEDGER_TOTAL_PASSES 8 +#define LEDGER_RETRY_PASSES 5 + #define TRUST_NETWORK #define LC_DEBUG -typedef std::pair u160_prop_pair; -typedef std::pair u256_lct_pair; +typedef std::map::value_type u160_prop_pair; +typedef std::map::value_type u256_lct_pair; SETUP_LOG(); DECLARE_INSTANCE(LedgerConsensus); +DECLARE_INSTANCE(TransactionAcquire); TransactionAcquire::TransactionAcquire(const uint256& hash) : PeerSet(hash, TX_ACQUIRE_TIMEOUT), mHaveRoot(false) { @@ -44,6 +48,7 @@ void TransactionAcquire::done() mMap->setImmutable(); theApp->getOPs().mapComplete(mHash, mMap); } + theApp->getMasterLedgerAcquire().dropLedger(mHash); } void TransactionAcquire::onTimer(bool progress) @@ -69,7 +74,7 @@ void TransactionAcquire::onTimer(bool progress) } } else if (!progress) - trigger(Peer::pointer(), true); + trigger(Peer::pointer()); } boost::weak_ptr TransactionAcquire::pmDowncast() @@ -77,7 +82,7 @@ boost::weak_ptr TransactionAcquire::pmDowncast() return boost::shared_polymorphic_downcast(shared_from_this()); } -void TransactionAcquire::trigger(Peer::ref peer, bool timer) +void TransactionAcquire::trigger(Peer::ref peer) { if (mComplete || mFailed) { @@ -110,20 +115,15 @@ void TransactionAcquire::trigger(Peer::ref peer, bool timer) done(); return; } - else - { - ripple::TMGetLedger tmGL; - tmGL.set_ledgerhash(mHash.begin(), mHash.size()); - tmGL.set_itype(ripple::liTS_CANDIDATE); - if (getTimeouts() != 0) - tmGL.set_querytype(ripple::qtINDIRECT); - BOOST_FOREACH(SHAMapNode& it, nodeIDs) - *(tmGL.add_nodeids()) = it.getRawString(); - sendRequest(tmGL, peer); - } + ripple::TMGetLedger tmGL; + tmGL.set_ledgerhash(mHash.begin(), mHash.size()); + tmGL.set_itype(ripple::liTS_CANDIDATE); + if (getTimeouts() != 0) + tmGL.set_querytype(ripple::qtINDIRECT); + BOOST_FOREACH(SHAMapNode& it, nodeIDs) + *(tmGL.add_nodeids()) = it.getRawString(); + sendRequest(tmGL, peer); } - if (timer) - resetTimer(); } SMAddNode TransactionAcquire::takeNodes(const std::list& nodeIDs, @@ -171,7 +171,7 @@ SMAddNode TransactionAcquire::takeNodes(const std::list& nodeIDs, ++nodeIDit; ++nodeDatait; } - trigger(peer, false); + trigger(peer); progress(); return SMAddNode::useful(); } @@ -282,7 +282,7 @@ LedgerConsensus::LedgerConsensus(const uint256& prevLCLHash, Ledger::ref previou mCloseResolution = ContinuousLedgerTiming::getNextLedgerTimeResolution( mPreviousLedger->getCloseResolution(), mPreviousLedger->getCloseAgree(), previousLedger->getLedgerSeq() + 1); - if (mValPublic.isValid() && mValPrivate.isValid()) + if (mValPublic.isValid() && mValPrivate.isValid() && !theApp->getOPs().isNeedNetworkLedger()) { cLog(lsINFO) << "Entering consensus process, validating"; mValidating = true; @@ -297,6 +297,7 @@ LedgerConsensus::LedgerConsensus(const uint256& prevLCLHash, Ledger::ref previou mHaveCorrectLCL = (mPreviousLedger->getHash() == mPrevLedgerHash); if (!mHaveCorrectLCL) { + theApp->getOPs().setProposing(false, false); handleLCL(mPrevLedgerHash); if (!mHaveCorrectLCL) { @@ -305,11 +306,13 @@ LedgerConsensus::LedgerConsensus(const uint256& prevLCLHash, Ledger::ref previou cLog(lsINFO) << "Correct LCL is: " << prevLCLHash; } } + else + theApp->getOPs().setProposing(mProposing, mValidating); } void LedgerConsensus::checkOurValidation() { // This only covers some cases - Fix for the case where we can't ever acquire the consensus ledger - if (!mHaveCorrectLCL || !mValPublic.isValid() || !mValPrivate.isValid()) + if (!mHaveCorrectLCL || !mValPublic.isValid() || !mValPrivate.isValid() || theApp->getOPs().isNeedNetworkLedger()) return; SerializedValidation::pointer lastVal = theApp->getOPs().getLastValidation(); @@ -348,7 +351,7 @@ void LedgerConsensus::checkLCL() boost::unordered_map vals = theApp->getValidations().getCurrentValidations(favoredLedger); - typedef std::pair u256_cvc_pair; + typedef std::map::value_type u256_cvc_pair; BOOST_FOREACH(u256_cvc_pair& it, vals) if (it.second.first > netLgrCount) { @@ -424,8 +427,10 @@ void LedgerConsensus::handleLCL(const uint256& lclHash) cLog(lsINFO) << "Have the consensus ledger " << mPrevLedgerHash; mHaveCorrectLCL = true; - mAcquiringLedger.reset(); - theApp->getOPs().clearNeedNetworkLedger(); +#if 0 // FIXME: can trigger early + if (mAcquiringLedger && mAcquiringLedger->isComplete()) + theApp->getOPs().clearNeedNetworkLedger(); +#endif mCloseResolution = ContinuousLedgerTiming::getNextLedgerTimeResolution( mPreviousLedger->getCloseResolution(), mPreviousLedger->getCloseAgree(), mPreviousLedger->getLedgerSeq() + 1); @@ -471,7 +476,7 @@ void LedgerConsensus::createDisputes(SHAMap::ref m1, SHAMap::ref m2) SHAMap::SHAMapDiff differences; m1->compare(m2, differences, 16384); - typedef std::pair u256_diff_pair; + typedef std::map::value_type u256_diff_pair; BOOST_FOREACH (u256_diff_pair& pos, differences) { // create disputed transactions (from the ledger that has them) if (pos.second.first) @@ -570,7 +575,7 @@ void LedgerConsensus::statusChange(ripple::NodeEvent event, Ledger& ledger) s.set_ledgerhash(hash.begin(), hash.size()); PackedMessage::pointer packet = boost::make_shared(s, ripple::mtSTATUS_CHANGE); theApp->getConnectionPool().relayMessage(NULL, packet); - cLog(lsINFO) << "send status change to peer"; + cLog(lsTRACE) << "send status change to peer"; } int LedgerConsensus::startup() @@ -757,10 +762,10 @@ void LedgerConsensus::updateOurPositions() for (std::map::iterator it = closeTimes.begin(), end = closeTimes.end(); it != end; ++it) { - cLog(lsINFO) << "CCTime: " << it->first << " has " << it->second << ", " << thresh << " required"; + cLog(lsTRACE) << "CCTime: " << it->first << " has " << it->second << ", " << thresh << " required"; if (it->second >= thresh) { - cLog(lsINFO) << "Close time consensus reached: " << it->first; + cLog(lsDEBUG) << "Close time consensus reached: " << it->first; mHaveCloseTimeConsensus = true; closeTime = it->first; thresh = it->second; @@ -793,8 +798,7 @@ void LedgerConsensus::updateOurPositions() } bool LedgerConsensus::haveConsensus(bool forReal) -{ // FIXME: Should check for a supermajority on each disputed transaction - // counting unacquired TX sets as disagreeing +{ // CHECKME: should possibly count unacquired TX sets as disagreeing int agree = 0, disagree = 0; uint256 ourPosition = mOurPosition->getCurrentHash(); BOOST_FOREACH(u160_prop_pair& it, mPeerPositions) @@ -826,7 +830,6 @@ SHAMap::pointer LedgerConsensus::getTransactionTree(const uint256& hash, bool do SHAMap::pointer currentMap = theApp->getLedgerMaster().getCurrentLedger()->peekTransactionMap(); if (currentMap->getHash() == hash) { - cLog(lsINFO) << "node proposes our open transaction set"; currentMap = currentMap->snapShot(false); mapComplete(hash, currentMap, false); return currentMap; @@ -882,7 +885,7 @@ void LedgerConsensus::startAcquiring(const TransactionAcquire::pointer& acquire) acquire->peerHas(peer); } - acquire->resetTimer(); + acquire->setTimer(); } void LedgerConsensus::propose() @@ -976,7 +979,7 @@ bool LedgerConsensus::peerPosition(const LedgerProposal::pointer& newPosition) } - cLog(lsINFO) << "Processing peer proposal " << newPosition->getProposeSeq() << "/" << newPosition->getCurrentHash(); + cLog(lsTRACE) << "Processing peer proposal " << newPosition->getProposeSeq() << "/" << newPosition->getCurrentHash(); currentPosition = newPosition; SHAMap::pointer set = getTransactionTree(newPosition->getCurrentHash(), true); @@ -1088,37 +1091,51 @@ void LedgerConsensus::playbackProposals() } } -void LedgerConsensus::applyTransaction(TransactionEngine& engine, SerializedTransaction::ref txn, - Ledger::ref ledger, CanonicalTXSet& failedTransactions, bool openLedger) -{ +#define LCAT_SUCCESS 0 +#define LCAT_FAIL 1 +#define LCAT_RETRY 2 + +int LedgerConsensus::applyTransaction(TransactionEngine& engine, SerializedTransaction::ref txn, Ledger::ref ledger, + bool openLedger, bool retryAssured) +{ // Returns false if the transaction has need not be retried. TransactionEngineParams parms = openLedger ? tapOPEN_LEDGER : tapNONE; + if (retryAssured) + parms = static_cast(parms | tapRETRY); + + cLog(lsDEBUG) << "TXN " << txn->getTransactionID() + << (openLedger ? " open" : " closed") + << (retryAssured ? "/retry" : "/final"); + cLog(lsTRACE) << txn->getJson(0); + #ifndef TRUST_NETWORK try { #endif - TER result = engine.applyTransaction(*txn, parms); - if (isTerRetry(result)) + + bool didApply; + TER result = engine.applyTransaction(*txn, parms, didApply); + if (didApply) { - cLog(lsINFO) << " retry"; - assert(!ledger->hasTransaction(txn->getTransactionID())); - failedTransactions.push_back(txn); + cLog(lsDEBUG) << "Transaction success: " << transHuman(result); + return LCAT_SUCCESS; } - else if (isTepSuccess(result)) // FIXME: Need to do partial success - { - cLog(lsTRACE) << " success"; - assert(ledger->hasTransaction(txn->getTransactionID())); + + if (isTefFailure(result) || isTemMalformed(result)) + { // failure + cLog(lsDEBUG) << "Transaction failure: " << transHuman(result); + return LCAT_FAIL; } - else if (isTemMalformed(result) || isTefFailure(result)) - { - cLog(lsINFO) << " hard fail"; - } - else - assert(false); + + cLog(lsDEBUG) << "Transaction retry: " << transHuman(result); + assert(!ledger->hasTransaction(txn->getTransactionID())); + return LCAT_RETRY; + #ifndef TRUST_NETWORK } catch (...) { - cLog(lsWARNING) << " Throws"; + cLog(lsWARNING) << "Throws"; + return false; } #endif } @@ -1126,11 +1143,9 @@ void LedgerConsensus::applyTransaction(TransactionEngine& engine, SerializedTran void LedgerConsensus::applyTransactions(SHAMap::ref set, Ledger::ref applyLedger, Ledger::ref checkLedger, CanonicalTXSet& failedTransactions, bool openLgr) { - 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())) { cLog(lsINFO) << "Processing candidate transaction: " << item->getTag(); @@ -1140,7 +1155,8 @@ void LedgerConsensus::applyTransactions(SHAMap::ref set, Ledger::ref applyLedger #endif SerializerIterator sit(item->peekSerializer()); SerializedTransaction::pointer txn = boost::make_shared(boost::ref(sit)); - applyTransaction(engine, txn, applyLedger, failedTransactions, openLgr); + if (applyTransaction(engine, txn, applyLedger, openLgr, true) == LCAT_RETRY) + failedTransactions.push_back(txn); #ifndef TRUST_NETWORK } catch (...) @@ -1149,35 +1165,52 @@ void LedgerConsensus::applyTransactions(SHAMap::ref set, Ledger::ref applyLedger } #endif } - } - int successes; - do + int changes; + bool certainRetry = true; + + for (int pass = 0; pass < LEDGER_TOTAL_PASSES; ++pass) { - successes = 0; + cLog(lsDEBUG) << "Pass: " << pass << " Txns: " << failedTransactions.size() + << (certainRetry ? " retriable" : " final"); + changes = 0; + CanonicalTXSet::iterator it = failedTransactions.begin(); while (it != failedTransactions.end()) { try { - TER result = engine.applyTransaction(*it->second, parms); - if (result <= 0) + switch (applyTransaction(engine, it->second, applyLedger, openLgr, certainRetry)) { - if (result == 0) ++successes; - it = failedTransactions.erase(it); - } - else - { - ++it; + case LCAT_SUCCESS: + it = failedTransactions.erase(it); + ++changes; + break; + + case LCAT_FAIL: + it = failedTransactions.erase(it); + break; + + case LCAT_RETRY: + ++it; } } catch (...) { - cLog(lsWARNING) << " Throws"; + cLog(lsWARNING) << "Transaction throws"; it = failedTransactions.erase(it); } } - } while (successes > 0); + cLog(lsDEBUG) << "Pass: " << pass << " finished " << changes << " changes"; + + // A non-retry pass made no changes + if (!changes && !certainRetry) + return; + + // Stop retriable passes + if ((!changes) || (pass >= LEDGER_RETRY_PASSES)) + certainRetry = false; + } } uint32 LedgerConsensus::roundCloseTime(uint32 closeTime) @@ -1187,24 +1220,32 @@ uint32 LedgerConsensus::roundCloseTime(uint32 closeTime) void LedgerConsensus::accept(SHAMap::ref set, LoadEvent::pointer) { + if (set->getHash().isNonZero()) // put our set where others can get it later + theApp->getOPs().takePosition(mPreviousLedger->getLedgerSeq(), set); + boost::recursive_mutex::scoped_lock masterLock(theApp->getMasterLock()); assert(set->getHash() == mOurPosition->getCurrentHash()); uint32 closeTime = roundCloseTime(mOurPosition->getCloseTime()); - - cLog(lsINFO) << "Computing new LCL based on network consensus"; - if (mHaveCorrectLCL) - { - cLog(lsINFO) << "CNF tx " << mOurPosition->getCurrentHash() << ", close " << closeTime; - cLog(lsINFO) << "CNF mode " << theApp->getOPs().getOperatingMode() << ", oldLCL " << mPrevLedgerHash; + bool closeTimeCorrect = true; + if (closeTime == 0) + { // we agreed to disagree + closeTimeCorrect = false; + closeTime = mPreviousLedger->getCloseTimeNC() + 1; } + cLog(lsDEBUG) << "Report: Prop=" << (mProposing ? "yes" : "no") << " val=" << (mValidating ? "yes" : "no") << + " corLCL=" << (mHaveCorrectLCL ? "yes" : "no") << " fail="<< (mConsensusFail ? "yes" : "no"); + cLog(lsDEBUG) << "Report: Prev = " << mPrevLedgerHash << ":" << mPreviousLedger->getLedgerSeq(); + cLog(lsDEBUG) << "Report: TxSt = " << set->getHash() << ", close " << closeTime << (closeTimeCorrect ? "" : "X"); + CanonicalTXSet failedTransactions(set->getHash()); Ledger::pointer newLCL = boost::make_shared(false, boost::ref(*mPreviousLedger)); newLCL->peekTransactionMap()->armDirty(); newLCL->peekAccountStateMap()->armDirty(); + cLog(lsDEBUG) << "Applying consensus set transactions to the last closed ledger"; applyTransactions(set, newLCL, newLCL, failedTransactions, false); newLCL->updateSkipList(); newLCL->setClosed(); @@ -1214,17 +1255,11 @@ void LedgerConsensus::accept(SHAMap::ref set, LoadEvent::pointer) // write out dirty nodes (temporarily done here) Most come before setAccepted int fc; while ((fc = SHAMap::flushDirty(*acctNodes, 256, hotACCOUNT_NODE, newLCL->getLedgerSeq())) > 0) - { cLog(lsINFO) << "Flushed " << fc << " dirty state nodes"; } + { cLog(lsTRACE) << "Flushed " << fc << " dirty state nodes"; } while ((fc = SHAMap::flushDirty(*txnNodes, 256, hotTRANSACTION_NODE, newLCL->getLedgerSeq())) > 0) - { cLog(lsINFO) << "Flushed " << fc << " dirty transaction nodes"; } + { cLog(lsTRACE) << "Flushed " << fc << " dirty transaction nodes"; } - bool closeTimeCorrect = true; - if (closeTime == 0) - { // we agreed to disagree - closeTimeCorrect = false; - closeTime = mPreviousLedger->getCloseTimeNC() + 1; - cLog(lsINFO) << "CNF badclose " << closeTime; - } + cLog(lsDEBUG) << "Report: NewL = " << newLCL->getHash() << ":" << newLCL->getLedgerSeq(); newLCL->setAccepted(closeTime, mCloseResolution, closeTimeCorrect); newLCL->updateHash(); @@ -1232,10 +1267,10 @@ void LedgerConsensus::accept(SHAMap::ref set, LoadEvent::pointer) if (sLog(lsTRACE)) { - Log(lsTRACE) << "newLCL"; + cLog(lsTRACE) << "newLCL"; Json::Value p; newLCL->addJson(p, LEDGER_JSON_DUMP_TXRP | LEDGER_JSON_DUMP_STATE); - Log(lsTRACE) << p; + cLog(lsTRACE) << p; } statusChange(ripple::neACCEPTED_LEDGER, *newLCL); @@ -1271,19 +1306,20 @@ void LedgerConsensus::accept(SHAMap::ref set, LoadEvent::pointer) { // we voted NO try { - cLog(lsINFO) << "Test applying disputed transaction that did not get in"; + cLog(lsDEBUG) << "Test applying disputed transaction that did not get in"; SerializerIterator sit(it.second->peekTransaction()); SerializedTransaction::pointer txn = boost::make_shared(boost::ref(sit)); - applyTransaction(engine, txn, newOL, failedTransactions, true); + if (applyTransaction(engine, txn, newOL, true, false)) + failedTransactions.push_back(txn); } catch (...) { - cLog(lsINFO) << "Failed to apply transaction we voted NO on"; + cLog(lsDEBUG) << "Failed to apply transaction we voted NO on"; } } } - cLog(lsINFO) << "Applying transactions from current ledger"; + cLog(lsDEBUG) << "Applying transactions from current open ledger"; applyTransactions(theApp->getLedgerMaster().getCurrentLedger()->peekTransactionMap(), newOL, newLCL, failedTransactions, true); theApp->getLedgerMaster().pushLedger(newLCL, newOL, !mConsensusFail); @@ -1330,18 +1366,18 @@ void LedgerConsensus::simulate() Json::Value LedgerConsensus::getJson() { Json::Value ret(Json::objectValue); - ret["proposing"] = mProposing ? "yes" : "no"; - ret["validating"] = mValidating ? "yes" : "no"; + ret["proposing"] = mProposing; + ret["validating"] = mValidating; ret["proposers"] = static_cast(mPeerPositions.size()); if (mHaveCorrectLCL) { - ret["synched"] = "yes"; + ret["synched"] = true; ret["ledger_seq"] = mPreviousLedger->getLedgerSeq() + 1; ret["close_granularity"] = mCloseResolution; } else - ret["synched"] = "no"; + ret["synched"] = false; switch (mState) { diff --git a/src/cpp/ripple/LedgerConsensus.h b/src/cpp/ripple/LedgerConsensus.h index f238368b0..b40e182da 100644 --- a/src/cpp/ripple/LedgerConsensus.h +++ b/src/cpp/ripple/LedgerConsensus.h @@ -21,8 +21,10 @@ #include "LoadMonitor.h" DEFINE_INSTANCE(LedgerConsensus); +DEFINE_INSTANCE(TransactionAcquire); -class TransactionAcquire : public PeerSet, public boost::enable_shared_from_this +class TransactionAcquire : + private IS_INSTANCE(TransactionAcquire), public PeerSet, public boost::enable_shared_from_this { // A transaction set we are trying to acquire public: typedef boost::shared_ptr pointer; @@ -32,10 +34,10 @@ protected: bool mHaveRoot; void onTimer(bool progress); - void newPeer(Peer::ref peer) { trigger(peer, false); } + void newPeer(Peer::ref peer) { trigger(peer); } void done(); - void trigger(Peer::ref, bool timer); + void trigger(Peer::ref); boost::weak_ptr pmDowncast(); public: @@ -43,7 +45,7 @@ public: TransactionAcquire(const uint256& hash); virtual ~TransactionAcquire() { ; } - SHAMap::pointer getMap() { return mMap; } + SHAMap::ref getMap() { return mMap; } SMAddNode takeNodes(const std::list& IDs, const std::list< std::vector >& data, Peer::ref); @@ -138,8 +140,8 @@ protected: void sendHaveTxSet(const uint256& set, bool direct); void applyTransactions(SHAMap::ref transactionSet, Ledger::ref targetLedger, Ledger::ref checkLedger, CanonicalTXSet& failedTransactions, bool openLgr); - void applyTransaction(TransactionEngine& engine, SerializedTransaction::ref txn, - Ledger::ref targetLedger, CanonicalTXSet& failedTransactions, bool openLgr); + int applyTransaction(TransactionEngine& engine, SerializedTransaction::ref txn, Ledger::ref targetLedger, + bool openLgr, bool retryAssured); uint32 roundCloseTime(uint32 closeTime); @@ -161,8 +163,8 @@ public: int startup(); Json::Value getJson(); - Ledger::pointer peekPreviousLedger() { return mPreviousLedger; } - uint256 getLCL() { return mPrevLedgerHash; } + Ledger::ref peekPreviousLedger() { return mPreviousLedger; } + uint256 getLCL() { return mPrevLedgerHash; } SHAMap::pointer getTransactionTree(const uint256& hash, bool doAcquire); TransactionAcquire::pointer getAcquiring(const uint256& hash); diff --git a/src/cpp/ripple/LedgerEntrySet.cpp b/src/cpp/ripple/LedgerEntrySet.cpp index 0d335848a..99c00018f 100644 --- a/src/cpp/ripple/LedgerEntrySet.cpp +++ b/src/cpp/ripple/LedgerEntrySet.cpp @@ -1,6 +1,7 @@ #include "LedgerEntrySet.h" +#include #include #include @@ -13,15 +14,14 @@ DECLARE_INSTANCE(LedgerEntrySet) // #define META_DEBUG -// Small for testing, should likely be 32 or 64. -#define DIR_NODE_MAX 2 +#define DIR_NODE_MAX 32 void LedgerEntrySet::init(Ledger::ref ledger, const uint256& transactionID, uint32 ledgerID) { mEntries.clear(); - mLedger = ledger; + mLedger = ledger; mSet.init(transactionID, ledgerID); - mSeq = 0; + mSeq = 0; } void LedgerEntrySet::clear() @@ -92,9 +92,7 @@ SLE::pointer LedgerEntrySet::entryCache(LedgerEntryType letType, const uint256& entryCache(sleEntry); } else if (action == taaDELETE) - { - assert(false); - } + sleEntry.reset(); } return sleEntry; } @@ -137,16 +135,17 @@ void LedgerEntrySet::entryCreate(SLE::ref sle) return; } - assert(it->second.mSeq == mSeq); - switch (it->second.mAction) { - case taaMODIFY: - throw std::runtime_error("Create after modify"); case taaDELETE: - throw std::runtime_error("Create after delete"); // We could make this a modify + it->second.mEntry = sle; + it->second.mAction = taaMODIFY; + it->second.mSeq = mSeq; + break; + case taaMODIFY: + throw std::runtime_error("Create after modify"); case taaCREATE: throw std::runtime_error("Create after create"); // We could make this work @@ -156,6 +155,8 @@ void LedgerEntrySet::entryCreate(SLE::ref sle) default: throw std::runtime_error("Unknown taa"); } + + assert(it->second.mSeq == mSeq); } void LedgerEntrySet::entryModify(SLE::ref sle) @@ -175,6 +176,8 @@ void LedgerEntrySet::entryModify(SLE::ref sle) case taaCACHED: it->second.mAction = taaMODIFY; fallthru(); + + case taaCREATE: case taaMODIFY: it->second.mSeq = mSeq; it->second.mEntry = sle; @@ -183,11 +186,6 @@ void LedgerEntrySet::entryModify(SLE::ref sle) case taaDELETE: throw std::runtime_error("Modify after delete"); - case taaCREATE: - it->second.mSeq = mSeq; - it->second.mEntry = sle; - break; - default: throw std::runtime_error("Unknown taa"); } @@ -366,7 +364,7 @@ void LedgerEntrySet::calcRawMeta(Serializer& s, TER result, uint32 index) // Entries modified only as a result of building the transaction metadata boost::unordered_map newMod; - typedef std::pair u256_LES_pair; + typedef std::map::value_type u256_LES_pair; BOOST_FOREACH(u256_LES_pair& it, mEntries) { SField::ptr type = &sfGeneric; @@ -479,7 +477,7 @@ void LedgerEntrySet::calcRawMeta(Serializer& s, TER result, uint32 index) } // add any new modified nodes to the modification set - typedef std::pair u256_sle_pair; + typedef std::map::value_type u256_sle_pair; BOOST_FOREACH(u256_sle_pair& it, newMod) entryModify(it.second); @@ -527,6 +525,11 @@ TER LedgerEntrySet::dirAdd( const uint256& uLedgerIndex, boost::function fDescriber) { +cLog(lsDEBUG) + << boost::str(boost::format("dirAdd: uRootIndex=%s uLedgerIndex=%s") + % uRootIndex.ToString() + % uLedgerIndex.ToString()); + SLE::pointer sleNode; STVector256 svIndexes; SLE::pointer sleRoot = entryCache(ltDIR_NODE, uRootIndex); @@ -568,7 +571,7 @@ TER LedgerEntrySet::dirAdd( // Add to new node. else if (!++uNodeDir) { - return terDIR_FULL; + return tecDIR_FULL; } else { @@ -620,19 +623,36 @@ 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. + const uint256& uLedgerIndex, // --> Value to remove from directory. + const bool bStable, // --> True, not to change relative order of entries. + const bool bSoft) // --> True, uNodeDir is not hard and fast (pass uNodeDir=0). { uint64 uNodeCur = uNodeDir; SLE::pointer sleNode = entryCache(ltDIR_NODE, uNodeCur ? Ledger::getDirNodeIndex(uRootIndex, uNodeCur) : uRootIndex); - assert(sleNode); - if (!sleNode) { - cLog(lsWARNING) << "dirDelete: no such node"; + cLog(lsWARNING) + << boost::str(boost::format("dirDelete: no such node: uRootIndex=%s uNodeDir=%s uLedgerIndex=%s") + % uRootIndex.ToString() + % strHex(uNodeDir) + % uLedgerIndex.ToString()); - return tefBAD_LEDGER; + if (!bSoft) + { + assert(false); + return tefBAD_LEDGER; + } + else if (uNodeDir < 20) + { + // Go the extra mile. Even if node doesn't exist, try the next node. + + return dirDelete(bKeepRoot, uNodeDir+1, uRootIndex, uLedgerIndex, bStable, true); + } + else + { + return tefBAD_LEDGER; + } } STVector256 svIndexes = sleNode->getFieldV256(sfIndexes); @@ -641,14 +661,26 @@ TER LedgerEntrySet::dirDelete( it = std::find(vuiIndexes.begin(), vuiIndexes.end(), uLedgerIndex); - assert(vuiIndexes.end() != it); if (vuiIndexes.end() == it) { - assert(false); + if (!bSoft) + { + assert(false); - cLog(lsWARNING) << "dirDelete: no such entry"; + cLog(lsWARNING) << "dirDelete: no such entry"; - return tefBAD_LEDGER; + return tefBAD_LEDGER; + } + else if (uNodeDir < 20) + { + // Go the extra mile. Even if entry not in node, try the next node. + + return dirDelete(bKeepRoot, uNodeDir+1, uRootIndex, uLedgerIndex, bStable, true); + } + else + { + return tefBAD_LEDGER; + } } // Remove the element. @@ -845,7 +877,7 @@ bool LedgerEntrySet::dirNext( } // If there is a count, adjust the owner count by iAmount. Otherwise, compute the owner count and store it. -TER LedgerEntrySet::ownerCountAdjust(const uint160& uOwnerID, int iAmount, SLE::ref sleAccountRoot) +void LedgerEntrySet::ownerCountAdjust(const uint160& uOwnerID, int iAmount, SLE::ref sleAccountRoot) { SLE::pointer sleHold = sleAccountRoot ? SLE::pointer() @@ -855,49 +887,27 @@ TER LedgerEntrySet::ownerCountAdjust(const uint160& uOwnerID, int iAmount, SLE:: ? sleAccountRoot : sleHold; - const bool bHaveOwnerCount = sleRoot->isFieldPresent(sfOwnerCount); - TER terResult; + const uint32 uOwnerCount = sleRoot->getFieldU32(sfOwnerCount); - if (bHaveOwnerCount) - { - const uint32 uOwnerCount = sleRoot->getFieldU32(sfOwnerCount); - - if (iAmount + int(uOwnerCount) >= 0) - sleRoot->setFieldU32(sfOwnerCount, uOwnerCount+iAmount); - - terResult = tesSUCCESS; - } - else - { - uint32 uActualCount; - - terResult = dirCount(Ledger::getOwnerDirIndex(uOwnerID), uActualCount); - - if (tesSUCCESS == terResult) - { - sleRoot->setFieldU32(sfOwnerCount, uActualCount); - } - } - - return terResult; + if (iAmount + int(uOwnerCount) >= 0) + sleRoot->setFieldU32(sfOwnerCount, uOwnerCount+iAmount); } TER LedgerEntrySet::offerDelete(const SLE::pointer& sleOffer, const uint256& uOfferIndex, const uint160& uOwnerID) { + bool bOwnerNode = sleOffer->isFieldPresent(sfOwnerNode); // Detect legacy dirs. uint64 uOwnerNode = sleOffer->getFieldU64(sfOwnerNode); - TER terResult = dirDelete(false, uOwnerNode, Ledger::getOwnerDirIndex(uOwnerID), uOfferIndex, false); + TER terResult = dirDelete(false, uOwnerNode, Ledger::getOwnerDirIndex(uOwnerID), uOfferIndex, false, !bOwnerNode); if (tesSUCCESS == terResult) { - terResult = ownerCountAdjust(uOwnerID, -1); - } + ownerCountAdjust(uOwnerID, -1); - if (tesSUCCESS == terResult) - { uint256 uDirectory = sleOffer->getFieldH256(sfBookDirectory); uint64 uBookNode = sleOffer->getFieldU64(sfBookNode); - terResult = dirDelete(false, uBookNode, uDirectory, uOfferIndex, true); + // Offer delete is always hard. Always have hints. + terResult = dirDelete(false, uBookNode, uDirectory, uOfferIndex, true, true); } entryDelete(sleOffer); @@ -1026,7 +1036,7 @@ uint32 LedgerEntrySet::rippleQualityIn(const uint160& uToAccountID, const uint16 // 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, bool bAvail) +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)); @@ -1037,30 +1047,14 @@ STAmount LedgerEntrySet::rippleHolds(const uint160& uAccountID, const uint160& u } else if (uAccountID > uIssuerID) { - if (bAvail) - { - saBalance = sleRippleState->getFieldAmount(sfLowLimit); - saBalance -= sleRippleState->getFieldAmount(sfBalance); - } - else - { - saBalance = sleRippleState->getFieldAmount(sfBalance); - saBalance.negate(); // Put balance in uAccountID terms. - } + saBalance = sleRippleState->getFieldAmount(sfBalance); + saBalance.negate(); // Put balance in uAccountID terms. saBalance.setIssuer(uIssuerID); } else { - if (bAvail) - { - saBalance = sleRippleState->getFieldAmount(sfHighLimit); - saBalance += sleRippleState->getFieldAmount(sfBalance); - } - else - { - saBalance = sleRippleState->getFieldAmount(sfBalance); - } + saBalance = sleRippleState->getFieldAmount(sfBalance); saBalance.setIssuer(uIssuerID); } @@ -1069,35 +1063,45 @@ STAmount LedgerEntrySet::rippleHolds(const uint160& uAccountID, const uint160& u } // <-- saAmount: amount of uCurrencyID held by uAccountID. May be negative. -STAmount LedgerEntrySet::accountHolds(const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID, bool bAvail) +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)); + uint64 uReserve = mLedger->getReserve(sleAccount->getFieldU32(sfOwnerCount)); - saAmount = sleAccount->getFieldAmount(sfBalance); + saAmount = sleAccount->getFieldAmount(sfBalance)-uReserve; + + if (saAmount < uReserve) + { + saAmount.zero(); + } + else + { + saAmount -= uReserve; + } } else { - saAmount = rippleHolds(uAccountID, uCurrencyID, uIssuerID, bAvail); + saAmount = rippleHolds(uAccountID, uCurrencyID, uIssuerID); } - cLog(lsINFO) << boost::str(boost::format("accountHolds: uAccountID=%s saAmount=%s bAvail=%d") + cLog(lsINFO) << boost::str(boost::format("accountHolds: uAccountID=%s saAmount=%s") % RippleAddress::createHumanAccountID(uAccountID) - % saAmount.getFullText() - % bAvail); + % 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. +// XXX Should take into account quality? // --> 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, bool bAvail) +STAmount LedgerEntrySet::accountFunds(const uint160& uAccountID, const STAmount& saDefault) { STAmount saFunds; @@ -1111,13 +1115,12 @@ STAmount LedgerEntrySet::accountFunds(const uint160& uAccountID, const STAmount& } else { - saFunds = accountHolds(uAccountID, saDefault.getCurrency(), saDefault.getIssuer(), bAvail); + saFunds = accountHolds(uAccountID, saDefault.getCurrency(), saDefault.getIssuer()); - cLog(lsINFO) << boost::str(boost::format("accountFunds: uAccountID=%s saDefault=%s saFunds=%s bAvail=%d") + cLog(lsINFO) << boost::str(boost::format("accountFunds: uAccountID=%s saDefault=%s saFunds=%s") % RippleAddress::createHumanAccountID(uAccountID) % saDefault.getFullText() - % saFunds.getFullText() - % bAvail); + % saFunds.getFullText()); } return saFunds; @@ -1143,47 +1146,119 @@ STAmount LedgerEntrySet::rippleTransferFee(const uint160& uSenderID, const uint1 return saTransitFee; } +TER LedgerEntrySet::trustCreate( + const bool bSrcHigh, + const uint160& uSrcAccountID, + const uint160& uDstAccountID, + const uint256& uIndex, // --> ripple state entry + SLE::ref sleAccount, // --> the account being set. + const bool bAuth, // --> authorize account. + const STAmount& saBalance, // --> balance of account being set. Issuer should be ACCOUNT_ONE + const STAmount& saLimit, // --> limit for account being set. Issuer should be the account being set. + const uint32 uQualityIn, + const uint32 uQualityOut) +{ + const uint160& uLowAccountID = !bSrcHigh ? uSrcAccountID : uDstAccountID; + const uint160& uHighAccountID = bSrcHigh ? uSrcAccountID : uDstAccountID; + + SLE::pointer sleRippleState = entryCreate(ltRIPPLE_STATE, uIndex); + + uint64 uLowNode; + uint64 uHighNode; + + TER terResult = dirAdd( + uLowNode, + Ledger::getOwnerDirIndex(uLowAccountID), + sleRippleState->getIndex(), + boost::bind(&Ledger::ownerDirDescriber, _1, uLowAccountID)); + + if (tesSUCCESS == terResult) + { + terResult = dirAdd( + uHighNode, + Ledger::getOwnerDirIndex(uHighAccountID), + sleRippleState->getIndex(), + boost::bind(&Ledger::ownerDirDescriber, _1, uHighAccountID)); + } + + if (tesSUCCESS == terResult) + { + const bool bSetDst = saLimit.getIssuer() == uDstAccountID; + const bool bSetHigh = bSrcHigh ^ bSetDst; + + sleRippleState->setFieldU64(sfLowNode, uLowNode); // Remember deletion hints. + sleRippleState->setFieldU64(sfHighNode, uHighNode); + + sleRippleState->setFieldAmount(!bSetHigh ? sfLowLimit : sfHighLimit, saLimit); + sleRippleState->setFieldAmount( bSetHigh ? sfLowLimit : sfHighLimit, STAmount(saBalance.getCurrency(), bSetDst ? uSrcAccountID : uDstAccountID)); + + if (uQualityIn) + sleRippleState->setFieldU32(!bSetHigh ? sfLowQualityIn : sfHighQualityIn, uQualityIn); + + if (uQualityOut) + sleRippleState->setFieldU32(!bSetHigh ? sfLowQualityOut : sfHighQualityOut, uQualityOut); + + uint32 uFlags = !bSetHigh ? lsfLowReserve : lsfHighReserve; + + if (bAuth) + { + uFlags |= (!bSetHigh ? lsfLowAuth : lsfHighAuth); + } + + sleRippleState->setFieldU32(sfFlags, uFlags); + + ownerCountAdjust(!bSetDst ? uSrcAccountID : uDstAccountID, 1, sleAccount); + + sleRippleState->setFieldAmount(sfBalance, bSetHigh ? -saBalance : saBalance); + } + + return terResult; +} + // 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) +TER 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; + bool bSenderHigh = uSenderID > uReceiverID; uint256 uIndex = Ledger::getRippleStateIndex(uSenderID, uReceiverID, saAmount.getCurrency()); SLE::pointer sleRippleState = entryCache(ltRIPPLE_STATE, uIndex); + TER terResult; + assert(!!uSenderID && uSenderID != ACCOUNT_ONE); assert(!!uReceiverID && uReceiverID != ACCOUNT_ONE); if (!sleRippleState) { - STAmount saBalance = saAmount; + STAmount saReceiverLimit = STAmount(uCurrencyID, uReceiverID); + STAmount saBalance = saAmount; saBalance.setIssuer(ACCOUNT_ONE); - cLog(lsDEBUG) << boost::str(boost::format("rippleCredit: create line: %s (%s) -> %s : %s") + cLog(lsDEBUG) << boost::str(boost::format("rippleCredit: create line: %s (0) -> %s : %s") % RippleAddress::createHumanAccountID(uSenderID) - % saBalance.getFullText() % RippleAddress::createHumanAccountID(uReceiverID) % saAmount.getFullText()); - 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)); + terResult = trustCreate( + bSenderHigh, + uSenderID, + uReceiverID, + uIndex, + entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uReceiverID)), + false, + saBalance, + saReceiverLimit); } else { STAmount saBalance = sleRippleState->getFieldAmount(sfBalance); - if (!bFlipped) + if (!bSenderHigh) saBalance.negate(); // Put balance in low terms. cLog(lsDEBUG) << boost::str(boost::format("rippleCredit> %s (%s) -> %s : %s") @@ -1194,31 +1269,37 @@ void LedgerEntrySet::rippleCredit(const uint160& uSenderID, const uint160& uRece saBalance += saAmount; - if (!bFlipped) + if (!bSenderHigh) saBalance.negate(); sleRippleState->setFieldAmount(sfBalance, saBalance); entryModify(sleRippleState); + + terResult = tesSUCCESS; } + + return terResult; } // 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) +TER LedgerEntrySet::rippleSend(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount, STAmount& saActual) { - STAmount saActual; const uint160 uIssuerID = saAmount.getIssuer(); + TER terResult; assert(!!uSenderID && !!uReceiverID); if (uSenderID == uIssuerID || uReceiverID == uIssuerID || uIssuerID == ACCOUNT_ONE) { // Direct send: redeeming IOUs and/or sending own IOUs. - rippleCredit(uSenderID, uReceiverID, saAmount, false); + terResult = rippleCredit(uSenderID, uReceiverID, saAmount, false); saActual = saAmount; + + terResult = tesSUCCESS; } else { @@ -1230,16 +1311,19 @@ STAmount LedgerEntrySet::rippleSend(const uint160& uSenderID, const uint160& uRe saActual.setIssuer(uIssuerID); // XXX Make sure this done in + above. - rippleCredit(uIssuerID, uReceiverID, saAmount); - rippleCredit(uSenderID, uIssuerID, saActual); + terResult = rippleCredit(uIssuerID, uReceiverID, saAmount); + + if (tesSUCCESS == terResult) + terResult = rippleCredit(uSenderID, uIssuerID, saActual); } - return saActual; + return terResult; } -void LedgerEntrySet::accountSend(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount) +TER LedgerEntrySet::accountSend(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount) { assert(!saAmount.isNegative()); + TER terResult = tesSUCCESS; if (!saAmount) { @@ -1282,8 +1366,12 @@ void LedgerEntrySet::accountSend(const uint160& uSenderID, const uint160& uRecei } else { - rippleSend(uSenderID, uReceiverID, saAmount); + STAmount saActual; + + terResult = rippleSend(uSenderID, uReceiverID, saAmount, saActual); } + + return terResult; } // vim:ts=4 diff --git a/src/cpp/ripple/LedgerEntrySet.h b/src/cpp/ripple/LedgerEntrySet.h index 52958df73..197a55208 100644 --- a/src/cpp/ripple/LedgerEntrySet.h +++ b/src/cpp/ripple/LedgerEntrySet.h @@ -97,13 +97,14 @@ public: const uint64& uNodeDir, // Node item is mentioned in. const uint256& uRootIndex, const uint256& uLedgerIndex, // Item being deleted - const bool bStable); + const bool bStable, + const bool bSoft); 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); TER dirCount(const uint256& uDirIndex, uint32& uCount); - TER ownerCountAdjust(const uint160& uOwnerID, int iAmount, SLE::ref sleAccountRoot=SLE::pointer()); + void ownerCountAdjust(const uint160& uOwnerID, int iAmount, SLE::ref sleAccountRoot=SLE::pointer()); // Offer functions. TER offerDelete(const uint256& uOfferIndex); @@ -119,14 +120,26 @@ public: 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, bool bAvail=false); + 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); + TER rippleCredit(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount, bool bCheckIssuer=true); + TER rippleSend(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount, STAmount& saActual); - STAmount accountHolds(const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID, bool bAvail=false); - STAmount accountFunds(const uint160& uAccountID, const STAmount& saDefault, bool bAvail=false); - void accountSend(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount); + STAmount accountHolds(const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID); + STAmount accountFunds(const uint160& uAccountID, const STAmount& saDefault); + TER accountSend(const uint160& uSenderID, const uint160& uReceiverID, const STAmount& saAmount); + + TER trustCreate( + const bool bSrcHigh, + const uint160& uSrcAccountID, + const uint160& uDstAccountID, + const uint256& uIndex, + SLE::ref sleAccount, + const bool bAuth, + const STAmount& saSrcBalance, + const STAmount& saSrcLimit, + const uint32 uSrcQualityIn = 0, + const uint32 uSrcQualityOut = 0); Json::Value getJson(int) const; void calcRawMeta(Serializer&, TER result, uint32 index); diff --git a/src/cpp/ripple/LedgerFormats.cpp b/src/cpp/ripple/LedgerFormats.cpp index 1e553398a..c1c36bb7b 100644 --- a/src/cpp/ripple/LedgerFormats.cpp +++ b/src/cpp/ripple/LedgerFormats.cpp @@ -19,6 +19,7 @@ static bool LEFInit() << SOElement(sfAccount, SOE_REQUIRED) << SOElement(sfSequence, SOE_REQUIRED) << SOElement(sfBalance, SOE_REQUIRED) + << SOElement(sfOwnerCount, SOE_REQUIRED) << SOElement(sfPreviousTxnID, SOE_REQUIRED) << SOElement(sfPreviousTxnLgrSeq, SOE_REQUIRED) << SOElement(sfRegularKey, SOE_OPTIONAL) @@ -28,7 +29,6 @@ static bool LEFInit() << SOElement(sfMessageKey, SOE_OPTIONAL) << SOElement(sfTransferRate, SOE_OPTIONAL) << SOElement(sfDomain, SOE_OPTIONAL) - << SOElement(sfOwnerCount, SOE_OPTIONAL) ; DECLARE_LEF(Contract, ltCONTRACT) @@ -87,14 +87,16 @@ static bool LEFInit() << SOElement(sfHighLimit, SOE_REQUIRED) << SOElement(sfPreviousTxnID, SOE_REQUIRED) << SOElement(sfPreviousTxnLgrSeq, SOE_REQUIRED) + << SOElement(sfLowNode, SOE_OPTIONAL) << SOElement(sfLowQualityIn, SOE_OPTIONAL) << SOElement(sfLowQualityOut, SOE_OPTIONAL) + << SOElement(sfHighNode, SOE_OPTIONAL) << SOElement(sfHighQualityIn, SOE_OPTIONAL) << SOElement(sfHighQualityOut, SOE_OPTIONAL) ; DECLARE_LEF(LedgerHashes, ltLEDGER_HASHES) - << SOElement(sfFirstLedgerSequence, SOE_OPTIONAL) + << SOElement(sfFirstLedgerSequence, SOE_OPTIONAL) // Remove if we do a ledger restart << SOElement(sfLastLedgerSequence, SOE_OPTIONAL) << SOElement(sfHashes, SOE_REQUIRED) ; @@ -103,6 +105,13 @@ static bool LEFInit() << SOElement(sfFeatures, SOE_REQUIRED) ; + DECLARE_LEF(FeeSettings, ltFEE_SETTINGS) + << SOElement(sfBaseFee, SOE_REQUIRED) + << SOElement(sfReferenceFeeUnits, SOE_REQUIRED) + << SOElement(sfReserveBase, SOE_REQUIRED) + << SOElement(sfReserveIncrement, SOE_REQUIRED) + ; + return true; } diff --git a/src/cpp/ripple/LedgerFormats.h b/src/cpp/ripple/LedgerFormats.h index 5f16c63f4..a491a58e3 100644 --- a/src/cpp/ripple/LedgerFormats.h +++ b/src/cpp/ripple/LedgerFormats.h @@ -16,6 +16,7 @@ enum LedgerEntryType ltCONTRACT = 'c', ltLEDGER_HASHES = 'h', ltFEATURES = 'f', + ltFEE_SETTINGS = 's', }; // Used as a prefix for computing ledger indexes (keys). @@ -32,15 +33,24 @@ enum LedgerNameSpace spaceContract = 'c', spaceSkipList = 's', spaceFeature = 'f', + spaceFee = 's', }; enum LedgerSpecificFlags { // ltACCOUNT_ROOT - lsfPasswordSpent = 0x00010000, // True if password set fee is spent. + lsfPasswordSpent = 0x00010000, // True, if password set fee is spent. + lsfRequireDestTag = 0x00020000, // True, to require a DestinationTag for payments. + lsfRequireAuth = 0x00040000, // True, to require a authorization to hold IOUs. // ltOFFER lsfPassive = 0x00010000, + + // ltRIPPLE_STATE + lsfLowReserve = 0x00010000, // True, if entry counts toward reserve. + lsfHighReserve = 0x00020000, + lsfLowAuth = 0x00040000, + lsfHighAuth = 0x00080000, }; class LedgerEntryFormat @@ -48,7 +58,7 @@ class LedgerEntryFormat public: std::string t_name; LedgerEntryType t_type; - std::vector elements; + std::vector elements; static std::map byType; static std::map byName; diff --git a/src/cpp/ripple/LedgerHistory.cpp b/src/cpp/ripple/LedgerHistory.cpp index 0c084d2f7..dc30e87e3 100644 --- a/src/cpp/ripple/LedgerHistory.cpp +++ b/src/cpp/ripple/LedgerHistory.cpp @@ -10,11 +10,11 @@ #include "Application.h" #ifndef CACHED_LEDGER_NUM -#define CACHED_LEDGER_NUM 128 +#define CACHED_LEDGER_NUM 96 #endif #ifndef CACHED_LEDGER_AGE -#define CACHED_LEDGER_AGE 900 +#define CACHED_LEDGER_AGE 120 #endif // FIXME: Need to clean up ledgers by index at some point @@ -24,6 +24,7 @@ LedgerHistory::LedgerHistory() : mLedgersByHash("LedgerCache", CACHED_LEDGER_NUM void LedgerHistory::addLedger(Ledger::pointer ledger) { + assert(ledger->isImmutable()); mLedgersByHash.canonicalize(ledger->getHash(), ledger, true); } @@ -41,6 +42,16 @@ void LedgerHistory::addAcceptedLedger(Ledger::pointer ledger, bool fromConsensus ledger->pendSave(fromConsensus); } +uint256 LedgerHistory::getLedgerHash(uint32 index) +{ + boost::recursive_mutex::scoped_lock sl(mLedgersByHash.peekMutex()); + std::map::iterator it(mLedgersByIndex.find(index)); + if (it != mLedgersByIndex.end()) + return it->second; + sl.unlock(); + return uint256(); +} + Ledger::pointer LedgerHistory::getLedgerBySeq(uint32 index) { boost::recursive_mutex::scoped_lock sl(mLedgersByHash.peekMutex()); @@ -68,12 +79,17 @@ Ledger::pointer LedgerHistory::getLedgerByHash(const uint256& hash) { Ledger::pointer ret = mLedgersByHash.fetch(hash); if (ret) + { + assert(ret->getHash() == hash); return ret; + } ret = Ledger::loadByHash(hash); if (!ret) return ret; assert(ret->getHash() == hash); + mLedgersByHash.canonicalize(ret->getHash(), ret); + assert(ret->getHash() == hash); return ret; } diff --git a/src/cpp/ripple/LedgerHistory.h b/src/cpp/ripple/LedgerHistory.h index 1709e5954..965ac492a 100644 --- a/src/cpp/ripple/LedgerHistory.h +++ b/src/cpp/ripple/LedgerHistory.h @@ -15,6 +15,7 @@ public: void addLedger(Ledger::pointer ledger); void addAcceptedLedger(Ledger::pointer ledger, bool fromConsensus); + uint256 getLedgerHash(uint32 index); Ledger::pointer getLedgerBySeq(uint32 index); Ledger::pointer getLedgerByHash(const uint256& hash); Ledger::pointer canonicalizeLedger(Ledger::pointer, bool cache); diff --git a/src/cpp/ripple/LedgerMaster.cpp b/src/cpp/ripple/LedgerMaster.cpp index 00a60898f..e27c35007 100644 --- a/src/cpp/ripple/LedgerMaster.cpp +++ b/src/cpp/ripple/LedgerMaster.cpp @@ -9,6 +9,9 @@ SETUP_LOG(); +#define MIN_VALIDATION_RATIO 150 // 150/256ths of validations of previous ledger +#define MAX_LEDGER_GAP 100 // Don't catch up more than 100 ledgers (cannot exceed 256) + uint32 LedgerMaster::getCurrentLedgerIndex() { return mCurrentLedger->getLedgerSeq(); @@ -49,10 +52,13 @@ void LedgerMaster::pushLedger(Ledger::ref newLCL, Ledger::ref newOL, bool fromCo cLog(lsINFO) << "StashAccepted: " << newLCL->getHash(); } - boost::recursive_mutex::scoped_lock ml(mLock); - mFinalizedLedger = newLCL; - mCurrentLedger = newOL; - mEngine.setLedger(newOL); + { + boost::recursive_mutex::scoped_lock ml(mLock); + mFinalizedLedger = newLCL; + mCurrentLedger = newOL; + mEngine.setLedger(newOL); + } + checkAccept(newLCL->getHash(), newLCL->getLedgerSeq()); } void LedgerMaster::switchLedgers(Ledger::ref lastClosed, Ledger::ref current) @@ -69,6 +75,7 @@ void LedgerMaster::switchLedgers(Ledger::ref lastClosed, Ledger::ref current) assert(!mCurrentLedger->isClosed()); mEngine.setLedger(mCurrentLedger); + checkAccept(lastClosed->getHash(), lastClosed->getLedgerSeq()); } void LedgerMaster::storeLedger(Ledger::ref ledger) @@ -78,7 +85,6 @@ void LedgerMaster::storeLedger(Ledger::ref ledger) mLedgerHistory.addAcceptedLedger(ledger, false); } - Ledger::pointer LedgerMaster::closeLedger(bool recover) { boost::recursive_mutex::scoped_lock sl(mLock); @@ -91,8 +97,9 @@ Ledger::pointer LedgerMaster::closeLedger(bool recover) { try { - TER result = mEngine.applyTransaction(*it->second, tapOPEN_LEDGER); - if (isTepSuccess(result)) + bool didApply; + mEngine.applyTransaction(*it->second, tapOPEN_LEDGER, didApply); + if (didApply) ++recovers; } catch (...) @@ -112,19 +119,61 @@ Ledger::pointer LedgerMaster::closeLedger(bool recover) TER LedgerMaster::doTransaction(const SerializedTransaction& txn, TransactionEngineParams params) { - TER result = mEngine.applyTransaction(txn, params); + bool didApply; + TER result = mEngine.applyTransaction(txn, params, didApply); + // CHECKME: Should we call this even on gross failures? theApp->getOPs().pubProposedTransaction(mEngine.getLedger(), txn, result); return result; } bool LedgerMaster::haveLedgerRange(uint32 from, uint32 to) { + boost::recursive_mutex::scoped_lock sl(mLock); uint32 prevMissing = mCompleteLedgers.prevMissing(to + 1); return (prevMissing == RangeSet::RangeSetAbsent) || (prevMissing < from); } -void LedgerMaster::acquireMissingLedger(const uint256& ledgerHash, uint32 ledgerSeq) +void LedgerMaster::asyncAccept(Ledger::pointer ledger) { + uint32 seq = ledger->getLedgerSeq(); + uint256 prevHash = ledger->getParentHash(); + + while (seq > 0) + { + { + boost::recursive_mutex::scoped_lock ml(mLock); + mCompleteLedgers.setValue(seq); + --seq; + if (mCompleteLedgers.hasValue(seq)) + break; + } + + uint256 tHash, pHash; + if (!Ledger::getHashesByIndex(seq, tHash, pHash) || (tHash != prevHash)) + break; + prevHash = pHash; + } + + resumeAcquiring(); +} + +bool LedgerMaster::acquireMissingLedger(Ledger::ref origLedger, const uint256& ledgerHash, uint32 ledgerSeq) +{ // return: false = already gave up recently + if (mTooFast) + return true; + + Ledger::pointer ledger = mLedgerHistory.getLedgerBySeq(ledgerSeq); + if (Ledger::getHashByIndex(ledgerSeq) == ledgerHash) + { + cLog(lsDEBUG) << "Ledger hash found in database"; + mTooFast = true; + theApp->getJobQueue().addJob(jtPUBLEDGER, boost::bind(&LedgerMaster::asyncAccept, this, ledger)); + return true; + } + + if (theApp->getMasterLedgerAcquire().isFailure(ledgerHash)) + return false; + mMissingLedger = theApp->getMasterLedgerAcquire().findCreate(ledgerHash); if (mMissingLedger->isComplete()) { @@ -132,11 +181,38 @@ void LedgerMaster::acquireMissingLedger(const uint256& ledgerHash, uint32 ledger if (lgr && (lgr->getLedgerSeq() == ledgerSeq)) missingAcquireComplete(mMissingLedger); mMissingLedger.reset(); - return; + return true; + } + else if (mMissingLedger->isDone()) + { + mMissingLedger.reset(); + return false; } mMissingSeq = ledgerSeq; if (mMissingLedger->setAccept()) - mMissingLedger->addOnComplete(boost::bind(&LedgerMaster::missingAcquireComplete, this, _1)); + { + if (!mMissingLedger->addOnComplete(boost::bind(&LedgerMaster::missingAcquireComplete, this, _1))) + theApp->getIOService().post(boost::bind(&LedgerMaster::missingAcquireComplete, this, mMissingLedger)); + } + + if (theApp->getMasterLedgerAcquire().getFetchCount() < 4) + { + int count = 0; + typedef std::pair u_pair; + + std::vector vec = origLedger->getLedgerHashes(); + BOOST_REVERSE_FOREACH(const u_pair& it, vec) + { + if ((count < 3) && (it.first < ledgerSeq) && + !mCompleteLedgers.hasValue(it.first) && !theApp->getMasterLedgerAcquire().find(it.second)) + { + ++count; + theApp->getMasterLedgerAcquire().findCreate(it.second); + } + } + } + + return true; } void LedgerMaster::missingAcquireComplete(LedgerAcquire::pointer acq) @@ -151,14 +227,14 @@ void LedgerMaster::missingAcquireComplete(LedgerAcquire::pointer acq) mMissingLedger.reset(); mMissingSeq = 0; - if (!acq->isFailed()) + if (acq->isComplete()) { setFullLedger(acq->getLedger()); acq->getLedger()->pendSave(false); } } -static bool shouldAcquire(uint32 currentLedger, uint32 ledgerHistory, uint32 candidateLedger) +bool LedgerMaster::shouldAcquire(uint32 currentLedger, uint32 ledgerHistory, uint32 candidateLedger) { bool ret; if (candidateLedger >= currentLedger) @@ -168,8 +244,75 @@ static bool shouldAcquire(uint32 currentLedger, uint32 ledgerHistory, uint32 can return ret; } +void LedgerMaster::resumeAcquiring() +{ + boost::recursive_mutex::scoped_lock ml(mLock); + if (!mTooFast) + return; + mTooFast = false; + + if (mMissingLedger && mMissingLedger->isDone()) + mMissingLedger.reset(); + + if (mMissingLedger || !theConfig.LEDGER_HISTORY) + { + tLog(mMissingLedger, lsDEBUG) << "Fetch already in progress, not resuming"; + return; + } + + uint32 prevMissing = mCompleteLedgers.prevMissing(mFinalizedLedger->getLedgerSeq()); + if (prevMissing == RangeSet::RangeSetAbsent) + { + cLog(lsDEBUG) << "no prior missing ledger, not resuming"; + return; + } + if (shouldAcquire(mCurrentLedger->getLedgerSeq(), theConfig.LEDGER_HISTORY, prevMissing)) + { + cLog(lsTRACE) << "Resuming at " << prevMissing; + assert(!mCompleteLedgers.hasValue(prevMissing)); + Ledger::pointer nextLedger = getLedgerBySeq(prevMissing + 1); + if (nextLedger) + acquireMissingLedger(nextLedger, nextLedger->getParentHash(), nextLedger->getLedgerSeq() - 1); + else + { + mCompleteLedgers.clearValue(prevMissing); + cLog(lsWARNING) << "We have a gap at: " << prevMissing + 1; + } + } +} + +void LedgerMaster::fixMismatch(Ledger::ref ledger) +{ + int invalidate = 0; + + mMissingLedger.reset(); + mMissingSeq = 0; + + for (uint32 lSeq = ledger->getLedgerSeq() - 1; lSeq > 0; --lSeq) + if (mCompleteLedgers.hasValue(lSeq)) + { + uint256 hash = ledger->getLedgerHash(lSeq); + if (hash.isNonZero()) + { // try to close the seam + Ledger::pointer otherLedger = getLedgerBySeq(lSeq); + if (otherLedger && (otherLedger->getHash() == hash)) + { // we closed the seam + tLog(invalidate != 0, lsWARNING) << "Match at " << lSeq << ", " << + invalidate << " prior ledgers invalidated"; + return; + } + } + mCompleteLedgers.clearValue(lSeq); + ++invalidate; + } + + // all prior ledgers invalidated + tLog(invalidate != 0, lsWARNING) << "All " << invalidate << " prior ledgers invalidated"; +} + void LedgerMaster::setFullLedger(Ledger::ref ledger) { // A new ledger has been accepted as part of the trusted chain + cLog(lsDEBUG) << "Ledger " << ledger->getLedgerSeq() << " accepted :" << ledger->getHash(); boost::recursive_mutex::scoped_lock ml(mLock); @@ -178,26 +321,30 @@ void LedgerMaster::setFullLedger(Ledger::ref ledger) if ((ledger->getLedgerSeq() != 0) && mCompleteLedgers.hasValue(ledger->getLedgerSeq() - 1)) { // we think we have the previous ledger, double check Ledger::pointer prevLedger = getLedgerBySeq(ledger->getLedgerSeq() - 1); - if (!prevLedger) + if (!prevLedger || (prevLedger->getHash() != ledger->getParentHash())) { - cLog(lsWARNING) << "Ledger " << ledger->getLedgerSeq() - 1 << " missing"; - mCompleteLedgers.clearValue(ledger->getLedgerSeq() - 1); - } - else if (prevLedger->getHash() != ledger->getParentHash()) - { - cLog(lsWARNING) << "Ledger " << ledger->getLedgerSeq() << " invalidates prior ledger"; - mCompleteLedgers.clearValue(prevLedger->getLedgerSeq()); + cLog(lsWARNING) << "Acquired ledger invalidates previous ledger: " << + (prevLedger ? "hashMismatch" : "missingLedger"); + fixMismatch(ledger); } } - if (mMissingLedger && mMissingLedger->isComplete()) + if (mMissingLedger && mMissingLedger->isDone()) + { + if (mMissingLedger->isFailed()) + theApp->getMasterLedgerAcquire().dropLedger(mMissingLedger->getHash()); mMissingLedger.reset(); + } if (mMissingLedger || !theConfig.LEDGER_HISTORY) - return; - - if (Ledger::getPendingSaves() > 3) { + tLog(mMissingLedger, lsDEBUG) << "Fetch already in progress, " << mMissingLedger->getTimeouts() << " timeouts"; + return; + } + + if (Ledger::getPendingSaves() > 2) + { + mTooFast = true; cLog(lsINFO) << "Too many pending ledger saves"; return; } @@ -207,21 +354,24 @@ void LedgerMaster::setFullLedger(Ledger::ref ledger) { if (!shouldAcquire(mCurrentLedger->getLedgerSeq(), theConfig.LEDGER_HISTORY, ledger->getLedgerSeq() - 1)) return; - cLog(lsINFO) << "We need the ledger before the ledger we just accepted"; - acquireMissingLedger(ledger->getParentHash(), ledger->getLedgerSeq() - 1); + cLog(lsDEBUG) << "We need the ledger before the ledger we just accepted: " << ledger->getLedgerSeq() - 1; + acquireMissingLedger(ledger, ledger->getParentHash(), ledger->getLedgerSeq() - 1); } else { uint32 prevMissing = mCompleteLedgers.prevMissing(ledger->getLedgerSeq()); if (prevMissing == RangeSet::RangeSetAbsent) + { + cLog(lsDEBUG) << "no prior missing ledger"; return; + } if (shouldAcquire(mCurrentLedger->getLedgerSeq(), theConfig.LEDGER_HISTORY, prevMissing)) { - cLog(lsINFO) << "Ledger " << prevMissing << " is missing"; + cLog(lsDEBUG) << "Ledger " << prevMissing << " is needed"; assert(!mCompleteLedgers.hasValue(prevMissing)); Ledger::pointer nextLedger = getLedgerBySeq(prevMissing + 1); if (nextLedger) - acquireMissingLedger(nextLedger->getParentHash(), nextLedger->getLedgerSeq() - 1); + acquireMissingLedger(ledger, nextLedger->getParentHash(), nextLedger->getLedgerSeq() - 1); else { mCompleteLedgers.clearValue(prevMissing); @@ -231,4 +381,158 @@ void LedgerMaster::setFullLedger(Ledger::ref ledger) } } +void LedgerMaster::checkAccept(const uint256& hash) +{ + Ledger::pointer ledger = mLedgerHistory.getLedgerByHash(hash); + if (ledger) + checkAccept(hash, ledger->getLedgerSeq()); +} + +void LedgerMaster::checkAccept(const uint256& hash, uint32 seq) +{ // Can we advance the last fully accepted ledger? If so, can we publish? + boost::recursive_mutex::scoped_lock ml(mLock); + + if (mValidLedger && (seq <= mValidLedger->getLedgerSeq())) + return; + + int minVal = mMinValidations; + + if (mLastValidateHash.isNonZero()) + { + int val = theApp->getValidations().getTrustedValidationCount(mLastValidateHash); + val *= MIN_VALIDATION_RATIO; + val /= 256; + if (val > minVal) + minVal = val; + } + + if (theConfig.RUN_STANDALONE) + minVal = 0; + else if (theApp->getOPs().isNeedNetworkLedger()) + minVal = 1; + + if (theApp->getValidations().getTrustedValidationCount(hash) < minVal) // nothing we can do + return; + + cLog(lsINFO) << "Advancing accepted ledger to " << seq << " with >= " << minVal << " validations"; + + mLastValidateHash = hash; + mLastValidateSeq = seq; + + Ledger::pointer ledger = mLedgerHistory.getLedgerByHash(hash); + if (!ledger) + return; + mValidLedger = ledger; + + tryPublish(); +} + +void LedgerMaster::tryPublish() +{ + boost::recursive_mutex::scoped_lock ml(mLock); + assert(mValidLedger); + + if (!mPubLedger) + { + mPubLedger = mValidLedger; + mPubLedgers.push_back(mValidLedger); + } + else if (mValidLedger->getLedgerSeq() > (mPubLedger->getLedgerSeq() + MAX_LEDGER_GAP)) + { + mPubLedger = mValidLedger; + mPubLedgers.push_back(mValidLedger); + } + else if (mValidLedger->getLedgerSeq() > mPubLedger->getLedgerSeq()) + { + for (uint32 seq = mPubLedger->getLedgerSeq() + 1; seq <= mValidLedger->getLedgerSeq(); ++seq) + { + cLog(lsDEBUG) << "Trying to publish ledger " << seq; + + Ledger::pointer ledger; + uint256 hash; + + if (seq == mValidLedger->getLedgerSeq()) + { + ledger = mValidLedger; + hash = ledger->getHash(); + } + else + { + hash = mValidLedger->getLedgerHash(seq); + if (hash.isZero()) + { + cLog(lsFATAL) << "Ledger: " << mValidLedger->getLedgerSeq() << " does not have hash for " << + seq; + assert(false); + } + ledger = mLedgerHistory.getLedgerByHash(hash); + } + + if (ledger) + { + mPubLedger = ledger; + mPubLedgers.push_back(ledger); + } + else + { + if (theApp->getMasterLedgerAcquire().isFailure(hash)) + { + cLog(lsFATAL) << "Unable to acquire a recent validated ledger"; + } + else + { + LedgerAcquire::pointer acq = theApp->getMasterLedgerAcquire().findCreate(hash); + if (!acq->isDone()) + { + acq->setAccept(); + break; + } + else if (acq->isComplete() && !acq->isFailed()) + { + mPubLedger = acq->getLedger(); + mPubLedgers.push_back(mPubLedger); + } + else + cLog(lsWARNING) << "Failed to acquire a published ledger"; + } + } + } + } + + if (!mPubLedgers.empty() && !mPubThread) + { + theApp->getOPs().clearNeedNetworkLedger(); + mPubThread = true; + mTooFast = false; + theApp->getJobQueue().addJob(jtPUBLEDGER, boost::bind(&LedgerMaster::pubThread, this)); + } +} + +void LedgerMaster::pubThread() +{ + std::list ledgers; + + while (1) + { + ledgers.clear(); + + { + boost::recursive_mutex::scoped_lock ml(mLock); + mPubLedgers.swap(ledgers); + if (ledgers.empty()) + { + mPubThread = false; + return; + } + } + + BOOST_FOREACH(Ledger::ref l, ledgers) + { + cLog(lsDEBUG) << "Publishing ledger " << l->getLedgerSeq(); + setFullLedger(l); // OPTIMIZEME: This is actually more work than we need to do + theApp->getOPs().pubLedger(l); + } + } +} + // vim:ts=4 diff --git a/src/cpp/ripple/LedgerMaster.h b/src/cpp/ripple/LedgerMaster.h index 801e2f8d7..dac64c862 100644 --- a/src/cpp/ripple/LedgerMaster.h +++ b/src/cpp/ripple/LedgerMaster.h @@ -17,12 +17,18 @@ class LedgerMaster { +public: + typedef boost::function callback; + +protected: boost::recursive_mutex mLock; TransactionEngine mEngine; Ledger::pointer mCurrentLedger; // The ledger we are currently processiong Ledger::pointer mFinalizedLedger; // The ledger that most recently closed + Ledger::pointer mValidLedger; // The highest-sequence ledger we have fully accepted + Ledger::pointer mPubLedger; // The last ledger we have published LedgerHistory mLedgerHistory; @@ -31,27 +37,43 @@ class LedgerMaster RangeSet mCompleteLedgers; LedgerAcquire::pointer mMissingLedger; uint32 mMissingSeq; + bool mTooFast; // We are acquiring faster than we're writing + + int mMinValidations; // The minimum validations to publish a ledger + uint256 mLastValidateHash; + uint32 mLastValidateSeq; + std::list mOnValidate; // Called when a ledger has enough validations + + std::list mPubLedgers; // List of ledgers to publish + bool mPubThread; // Publish thread is running void applyFutureTransactions(uint32 ledgerIndex); bool isValidTransaction(const Transaction::pointer& trans); bool isTransactionOnFutureList(const Transaction::pointer& trans); - void acquireMissingLedger(const uint256& ledgerHash, uint32 ledgerSeq); + bool acquireMissingLedger(Ledger::ref from, const uint256& ledgerHash, uint32 ledgerSeq); + void asyncAccept(Ledger::pointer); void missingAcquireComplete(LedgerAcquire::pointer); + void pubThread(); public: - LedgerMaster() : mHeldTransactions(uint256()), mMissingSeq(0) { ; } + LedgerMaster() : mHeldTransactions(uint256()), mMissingSeq(0), mTooFast(false), + mMinValidations(0), mLastValidateSeq(0), mPubThread(false) + { ; } uint32 getCurrentLedgerIndex(); ScopedLock getLock() { return ScopedLock(mLock); } // The current ledger is the ledger we believe new transactions should go in - Ledger::pointer getCurrentLedger() { return mCurrentLedger; } + Ledger::ref getCurrentLedger() { return mCurrentLedger; } // The finalized ledger is the last closed/accepted ledger - Ledger::pointer getClosedLedger() { return mFinalizedLedger; } + Ledger::ref getClosedLedger() { return mFinalizedLedger; } + + // The published ledger is the last fully validated ledger + Ledger::ref getValidatedLedger() { return mPubLedger; } TER doTransaction(const SerializedTransaction& txn, TransactionEngineParams params); @@ -63,7 +85,11 @@ public: void switchLedgers(Ledger::ref lastClosed, Ledger::ref newCurrent); - std::string getCompleteLedgers() { return mCompleteLedgers.toString(); } + std::string getCompleteLedgers() + { + boost::recursive_mutex::scoped_lock sl(mLock); + return mCompleteLedgers.toString(); + } Ledger::pointer closeLedger(bool recoverHeldTransactions); @@ -79,23 +105,39 @@ public: Ledger::pointer getLedgerByHash(const uint256& hash) { if (hash.isZero()) - return mCurrentLedger; + return boost::make_shared(boost::ref(*mCurrentLedger), false); if (mCurrentLedger && (mCurrentLedger->getHash() == hash)) - return mCurrentLedger; + return boost::make_shared(boost::ref(*mCurrentLedger), false); + if (mFinalizedLedger && (mFinalizedLedger->getHash() == hash)) return mFinalizedLedger; return mLedgerHistory.getLedgerByHash(hash); } - void setLedgerRangePresent(uint32 minV, uint32 maxV) { mCompleteLedgers.setRange(minV, maxV); } + void setLedgerRangePresent(uint32 minV, uint32 maxV) + { + boost::recursive_mutex::scoped_lock sl(mLock); + mCompleteLedgers.setRange(minV, maxV); + } void addHeldTransaction(const Transaction::pointer& trans); + void fixMismatch(Ledger::ref ledger); bool haveLedgerRange(uint32 from, uint32 to); + void resumeAcquiring(); + void sweep(void) { mLedgerHistory.sweep(); } + + void addValidateCallback(callback& c) { mOnValidate.push_back(c); } + + void checkAccept(const uint256& hash); + void checkAccept(const uint256& hash, uint32 seq); + void tryPublish(); + + static bool shouldAcquire(uint32 currentLedgerID, uint32 ledgerHistory, uint32 targetLedger); }; #endif diff --git a/src/cpp/ripple/LedgerProposal.cpp b/src/cpp/ripple/LedgerProposal.cpp index 5f5f05873..7d4d193ca 100644 --- a/src/cpp/ripple/LedgerProposal.cpp +++ b/src/cpp/ripple/LedgerProposal.cpp @@ -42,7 +42,7 @@ uint256 LedgerProposal::getSigningHash() const { Serializer s((32 + 32 + 32 + 256 + 256) / 8); - s.add32(sHP_Proposal); + s.add32(theConfig.SIGN_PROPOSAL); s.add32(mProposeSeq); s.add32(mCloseTime); s.add256(mPreviousLedger); diff --git a/src/cpp/ripple/LoadManager.cpp b/src/cpp/ripple/LoadManager.cpp index f8047af15..daacb5215 100644 --- a/src/cpp/ripple/LoadManager.cpp +++ b/src/cpp/ripple/LoadManager.cpp @@ -1,5 +1,12 @@ #include "LoadManager.h" +#include + +#include "Log.h" +#include "Config.h" + +SETUP_LOG(); + LoadManager::LoadManager(int creditRate, int creditLimit, int debitWarn, int debitLimit) : mCreditRate(creditRate), mCreditLimit(creditLimit), mDebitWarn(debitWarn), mDebitLimit(debitLimit), mCosts(LT_MAX) @@ -8,6 +15,7 @@ LoadManager::LoadManager(int creditRate, int creditLimit, int debitWarn, int deb addLoadCost(LoadCost(LT_RequestNoReply, 1, LC_CPU | LC_Disk)); addLoadCost(LoadCost(LT_InvalidSignature, 100, LC_CPU)); addLoadCost(LoadCost(LT_UnwantedData, 5, LC_CPU | LC_Network)); + addLoadCost(LoadCost(LT_BadData, 20, LC_CPU)); addLoadCost(LoadCost(LT_NewTrusted, 10, 0)); addLoadCost(LoadCost(LT_NewTransaction, 2, 0)); @@ -131,24 +139,70 @@ bool LoadManager::adjust(LoadSource& source, int credits) const return false; } -uint64 LoadFeeTrack::scaleFee(uint64 fee) +uint64 LoadFeeTrack::mulDiv(uint64 value, uint32 mul, uint64 div) +{ // compute (value)*(mul)/(div) - avoid overflow but keep precision + static uint64 boundary = (0x00000000FFFFFFFF); + if (value > boundary) // Large value, avoid overflow + return (value / div) * mul; + else // Normal value, preserve accuracy + return (value * mul) / div; +} + +uint64 LoadFeeTrack::scaleFeeLoad(uint64 fee, uint64 baseFee, uint32 referenceFeeUnits) { static uint64 midrange(0x00000000FFFFFFFF); - int factor = (mLocalTxnLoadFee > mRemoteTxnLoadFee) ? mLocalTxnLoadFee : mRemoteTxnLoadFee; - if (fee > midrange) // large fee, divide first - return (fee / lftNormalFee) * factor; - else // small fee, multiply first - return (fee * factor) / lftNormalFee; + bool big = (fee > midrange); + if (big) // big fee, divide first to avoid overflow + fee /= baseFee; + else // normal fee, multiply first for accuracy + fee *= referenceFeeUnits; + + { + boost::mutex::scoped_lock sl(mLock); + fee = mulDiv(fee, std::max(mLocalTxnLoadFee, mRemoteTxnLoadFee), lftNormalFee); + } + + if (big) // Fee was big to start, must now multiply + fee *= referenceFeeUnits; + else // Fee was small to start, mst now divide + fee /= baseFee; + + return fee; +} + +uint64 LoadFeeTrack::scaleFeeBase(uint64 fee, uint64 baseFee, uint32 referenceFeeUnits) +{ + return mulDiv(fee, referenceFeeUnits, baseFee); +} + +uint32 LoadFeeTrack::getRemoteFee() +{ + boost::mutex::scoped_lock sl(mLock); + return mRemoteTxnLoadFee; +} + +uint32 LoadFeeTrack::getLocalFee() +{ + boost::mutex::scoped_lock sl(mLock); + return mLocalTxnLoadFee; +} + +uint32 LoadFeeTrack::getLoadFactor() +{ + boost::mutex::scoped_lock sl(mLock); + return std::max(mLocalTxnLoadFee, mRemoteTxnLoadFee); } void LoadFeeTrack::setRemoteFee(uint32 f) { + boost::mutex::scoped_lock sl(mLock); mRemoteTxnLoadFee = f; } void LoadFeeTrack::raiseLocalFee() { + boost::mutex::scoped_lock sl(mLock); if (mLocalTxnLoadFee < mLocalTxnLoadFee) // make sure this fee takes effect mLocalTxnLoadFee = mLocalTxnLoadFee; @@ -160,10 +214,55 @@ void LoadFeeTrack::raiseLocalFee() void LoadFeeTrack::lowerLocalFee() { + boost::mutex::scoped_lock sl(mLock); mLocalTxnLoadFee -= (mLocalTxnLoadFee / lftFeeDecFraction ); // reduce by 1/16th if (mLocalTxnLoadFee < lftNormalFee) mLocalTxnLoadFee = lftNormalFee; } +Json::Value LoadFeeTrack::getJson(uint64 baseFee, uint32 referenceFeeUnits) +{ + Json::Value j(Json::objectValue); + + { + boost::mutex::scoped_lock sl(mLock); + + // base_fee = The cost to send a "reference" transaction under no load, in millionths of a Ripple + j["base_fee"] = Json::Value::UInt(baseFee); + + // load_fee = The cost to send a "reference" transaction now, in millionths of a Ripple + j["load_fee"] = Json::Value::UInt( + mulDiv(baseFee, std::max(mLocalTxnLoadFee, mRemoteTxnLoadFee), lftNormalFee)); + } + + return j; +} + +BOOST_AUTO_TEST_SUITE(LoadManager_test) + +BOOST_AUTO_TEST_CASE(LoadFeeTrack_test) +{ + cLog(lsDEBUG) << "Running load fee track test"; + + Config d; // get a default configuration object + LoadFeeTrack l; + + BOOST_REQUIRE_EQUAL(l.scaleFeeBase(10000, d.FEE_DEFAULT, d.TRANSACTION_FEE_BASE), 10000); + BOOST_REQUIRE_EQUAL(l.scaleFeeLoad(10000, d.FEE_DEFAULT, d.TRANSACTION_FEE_BASE), 10000); + BOOST_REQUIRE_EQUAL(l.scaleFeeBase(1, d.FEE_DEFAULT, d.TRANSACTION_FEE_BASE), 1); + BOOST_REQUIRE_EQUAL(l.scaleFeeLoad(1, d.FEE_DEFAULT, d.TRANSACTION_FEE_BASE), 1); + + // Check new default fee values give same fees as old defaults + BOOST_REQUIRE_EQUAL(l.scaleFeeBase(d.FEE_DEFAULT, d.FEE_DEFAULT, d.TRANSACTION_FEE_BASE), 10); + BOOST_REQUIRE_EQUAL(l.scaleFeeBase(d.FEE_ACCOUNT_RESERVE, d.FEE_DEFAULT, d.TRANSACTION_FEE_BASE), 200 * SYSTEM_CURRENCY_PARTS); + BOOST_REQUIRE_EQUAL(l.scaleFeeBase(d.FEE_OWNER_RESERVE, d.FEE_DEFAULT, d.TRANSACTION_FEE_BASE), 50 * SYSTEM_CURRENCY_PARTS); + BOOST_REQUIRE_EQUAL(l.scaleFeeBase(d.FEE_NICKNAME_CREATE, d.FEE_DEFAULT, d.TRANSACTION_FEE_BASE), 1000); + BOOST_REQUIRE_EQUAL(l.scaleFeeBase(d.FEE_OFFER, d.FEE_DEFAULT, d.TRANSACTION_FEE_BASE), 10); + BOOST_REQUIRE_EQUAL(l.scaleFeeBase(d.FEE_CONTRACT_OPERATION, d.FEE_DEFAULT, d.TRANSACTION_FEE_BASE), 1); + +} + +BOOST_AUTO_TEST_SUITE_END() + // vim:ts=4 diff --git a/src/cpp/ripple/LoadManager.h b/src/cpp/ripple/LoadManager.h index 71eb79d39..0cecd6c1e 100644 --- a/src/cpp/ripple/LoadManager.h +++ b/src/cpp/ripple/LoadManager.h @@ -1,10 +1,12 @@ -#ifndef LOADSOURCE__H -#define LOADSOURCE__H +#ifndef LOADMANAGER__H +#define LOADMANAGER__H #include #include +#include "../json/value.h" + #include "types.h" enum LoadType @@ -16,6 +18,7 @@ enum LoadType LT_InvalidSignature, // An object whose signature we had to check and it failed LT_UnwantedData, // Data we have no use for LT_BadPoW, // Proof of work not valid + LT_BadData, // Data we have to verify before rejecting // Good things LT_NewTrusted, // A new transaction/validation/proposal we trust @@ -124,11 +127,28 @@ protected: uint32 mLocalTxnLoadFee; // Scale factor, lftNormalFee = normal fee uint32 mRemoteTxnLoadFee; // Scale factor, lftNormalFee = normal fee + boost::mutex mLock; + + static uint64 mulDiv(uint64 value, uint32 mul, uint64 div); + public: - LoadFeeTrack() : mLocalTxnLoadFee(lftNormalFee), mRemoteTxnLoadFee(lftNormalFee) { ; } + LoadFeeTrack() : mLocalTxnLoadFee(lftNormalFee), mRemoteTxnLoadFee(lftNormalFee) + { ; } - uint64 scaleFee(uint64 fee); + // Scale from fee units to millionths of a ripple + uint64 scaleFeeBase(uint64 fee, uint64 baseFee, uint32 referenceFeeUnits); + + // Scale using load as well as base rate + uint64 scaleFeeLoad(uint64 fee, uint64 baseFee, uint32 referenceFeeUnits); + + uint32 getRemoteFee(); + uint32 getLocalFee(); + + uint32 getLoadBase() { return lftNormalFee; } + uint32 getLoadFactor(); + + Json::Value getJson(uint64 baseFee, uint32 referenceFeeUnits); void setRemoteFee(uint32); void raiseLocalFee(); diff --git a/src/cpp/ripple/Log.cpp b/src/cpp/ripple/Log.cpp index 5f4e3425d..d2e14c02c 100644 --- a/src/cpp/ripple/Log.cpp +++ b/src/cpp/ripple/Log.cpp @@ -6,6 +6,8 @@ #include #include +#include "../websocketpp/src/logger/logger.hpp" + boost::recursive_mutex Log::sLock; LogSeverity Log::sMinSeverity = lsINFO; @@ -45,6 +47,7 @@ Log::~Log() logMsg += " " + mPartitionName + ":"; else logMsg += " "; + switch (mSeverity) { case lsTRACE: logMsg += "TRC "; break; @@ -55,16 +58,18 @@ Log::~Log() case lsFATAL: logMsg += "FTL "; break; case lsINVALID: assert(false); return; } + logMsg += oss.str(); + boost::recursive_mutex::scoped_lock sl(sLock); + if (mSeverity >= sMinSeverity) std::cerr << logMsg << std::endl; if (outStream != NULL) (*outStream) << logMsg << std::endl; } - -std::string Log::rotateLog(void) +std::string Log::rotateLog(void) { boost::recursive_mutex::scoped_lock sl(sLock); boost::filesystem::path abs_path; @@ -81,15 +86,15 @@ std::string Log::rotateLog(void) 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(); + 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(); - abs_new_path_str = abs_path_str + "/" + s + + "_" + pathToLog->filename().string(); - logRotateCounter++; } while (boost::filesystem::exists(boost::filesystem::path(abs_new_path_str))); @@ -97,17 +102,15 @@ std::string Log::rotateLog(void) 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, bool all) { boost::recursive_mutex::scoped_lock sl(sLock); + sMinSeverity = s; if (all) LogPartition::setSeverity(s); @@ -116,6 +119,7 @@ void Log::setMinSeverity(LogSeverity s, bool all) LogSeverity Log::getMinSeverity() { boost::recursive_mutex::scoped_lock sl(sLock); + return sMinSeverity; } @@ -131,7 +135,6 @@ std::string Log::severityToString(LogSeverity s) case lsFATAL: return "Fatal"; default: assert(false); return "Unknown"; } - } LogSeverity Log::stringToSeverity(const std::string& s) @@ -164,9 +167,9 @@ void Log::setLogFile(boost::filesystem::path path) if (outStream != NULL) delete outStream; outStream = newStream; - if (outStream) - Log(lsINFO) << "Starting up"; + if (pathToLog != NULL) + delete pathToLog; pathToLog = new boost::filesystem::path(path); } @@ -186,3 +189,36 @@ void LogPartition::setSeverity(LogSeverity severity) for (LogPartition *p = headLog; p != NULL; p = p->mNextLog) p->mMinSeverity = severity; } + + +namespace websocketpp +{ + namespace log + { + LogPartition websocketPartition("WebSocket"); + + void websocketLog(websocketpp::log::alevel::value v, const std::string& entry) + { + if (websocketPartition.doLog(lsDEBUG)) + Log(lsDEBUG, websocketPartition) << entry; + } + + void websocketLog(websocketpp::log::elevel::value v, const std::string& entry) + { + LogSeverity s = lsDEBUG; + if ((v & websocketpp::log::elevel::INFO) != 0) + s = lsINFO; + else if ((v & websocketpp::log::elevel::FATAL) != 0) + s = lsFATAL; + else if ((v & websocketpp::log::elevel::RERROR) != 0) + s = lsERROR; + else if ((v & websocketpp::log::elevel::WARN) != 0) + s = lsWARNING; + if (websocketPartition.doLog(s)) + Log(s, websocketPartition) << entry; + } + + } +} + +// vim:ts=4 diff --git a/src/cpp/ripple/Log.h b/src/cpp/ripple/Log.h index 6da9e0b5c..9f72af817 100644 --- a/src/cpp/ripple/Log.h +++ b/src/cpp/ripple/Log.h @@ -104,3 +104,5 @@ public: }; #endif + +// vim:ts=4 diff --git a/src/cpp/ripple/NetworkOPs.cpp b/src/cpp/ripple/NetworkOPs.cpp index 253e261b2..3fbf0768b 100644 --- a/src/cpp/ripple/NetworkOPs.cpp +++ b/src/cpp/ripple/NetworkOPs.cpp @@ -1,6 +1,9 @@ #include "NetworkOPs.h" +#include +#include + #include "utils.h" #include "Application.h" #include "Transaction.h" @@ -9,8 +12,6 @@ #include "Log.h" #include "RippleAddress.h" -#include -#include // This is the primary interface into the "client" portion of the program. // Code that wants to do normal operations on the network such as @@ -26,10 +27,15 @@ SETUP_LOG(); DECLARE_INSTANCE(InfoSub); +void InfoSub::onSendEmpty() +{ + +} + NetworkOPs::NetworkOPs(boost::asio::io_service& io_service, LedgerMaster* pLedgerMaster) : - mMode(omDISCONNECTED), mNeedNetworkLedger(false), mNetTimer(io_service), mLedgerMaster(pLedgerMaster), - mCloseTimeOffset(0), mLastCloseProposers(0), mLastCloseConvergeTime(1000 * LEDGER_IDLE_INTERVAL), - mLastValidationTime(0) + mMode(omDISCONNECTED), mNeedNetworkLedger(false), mProposing(false), mValidating(false), + mNetTimer(io_service), mLedgerMaster(pLedgerMaster), mCloseTimeOffset(0), mLastCloseProposers(0), + mLastCloseConvergeTime(1000 * LEDGER_IDLE_INTERVAL), mLastValidationTime(0) { } @@ -42,6 +48,14 @@ std::string NetworkOPs::strOperatingMode() "full" }; + if (mMode == omFULL) + { + if (mProposing) + return "proposing"; + if (mValidating) + return "validating"; + } + return paStatusToken[mMode]; } @@ -99,6 +113,18 @@ bool NetworkOPs::haveLedgerRange(uint32 from, uint32 to) return mLedgerMaster->haveLedgerRange(from, to); } +bool NetworkOPs::addWantedHash(const uint256& h) +{ + boost::recursive_mutex::scoped_lock sl(mWantedHashLock); + return mWantedHashes.insert(h).second; +} + +bool NetworkOPs::isWantedHash(const uint256& h, bool remove) +{ + boost::recursive_mutex::scoped_lock sl(mWantedHashLock); + return (remove ? mWantedHashes.erase(h) : mWantedHashes.count(h)) != 0; +} + void NetworkOPs::submitTransaction(Job&, SerializedTransaction::pointer iTrans, stCallback callback) { // this is an asynchronous interface Serializer s; @@ -147,7 +173,7 @@ void NetworkOPs::submitTransaction(Job&, SerializedTransaction::pointer iTrans, // Sterilize transaction through serialization. // This is fully synchronous and deprecated -Transaction::pointer NetworkOPs::submitTransactionSync(const Transaction::pointer& tpTrans) +Transaction::pointer NetworkOPs::submitTransactionSync(const Transaction::pointer& tpTrans, bool bSubmit) { Serializer s; tpTrans->getSTransaction()->add(s); @@ -161,7 +187,8 @@ Transaction::pointer NetworkOPs::submitTransactionSync(const Transaction::pointe } else if (tpTransNew->getSTransaction()->isEquivalent(*tpTrans->getSTransaction())) { - (void) NetworkOPs::processTransaction(tpTransNew); + if (bSubmit) + (void) NetworkOPs::processTransaction(tpTransNew); } else { @@ -177,6 +204,88 @@ Transaction::pointer NetworkOPs::submitTransactionSync(const Transaction::pointe return tpTransNew; } +void NetworkOPs::runTransactionQueue() +{ + TXQEntry::pointer txn; + + for (int i = 0; i < 10; ++i) + { + theApp->getTxnQueue().getJob(txn); + if (!txn) + return; + + { + LoadEvent::autoptr ev = theApp->getJobQueue().getLoadEventAP(jtTXN_PROC); + + boost::recursive_mutex::scoped_lock sl(theApp->getMasterLock()); + + Transaction::pointer dbtx = theApp->getMasterTransaction().fetch(txn->getID(), true); + assert(dbtx); + + TER r = mLedgerMaster->doTransaction(*dbtx->getSTransaction(), tapOPEN_LEDGER | tapNO_CHECK_SIGN); + dbtx->setResult(r); + + if (isTemMalformed(r)) // malformed, cache bad + theApp->isNewFlag(txn->getID(), SF_BAD); + else if(isTelLocal(r) || isTerRetry(r)) // can be retried + theApp->isNewFlag(txn->getID(), SF_RETRY); + + + bool relay = true; + + if (isTerRetry(r)) + { // transaction should be held + cLog(lsDEBUG) << "Transaction should be held: " << r; + dbtx->setStatus(HELD); + theApp->getMasterTransaction().canonicalize(dbtx, true); + mLedgerMaster->addHeldTransaction(dbtx); + relay = false; + } + else if (r == tefPAST_SEQ) + { // duplicate or conflict + cLog(lsINFO) << "Transaction is obsolete"; + dbtx->setStatus(OBSOLETE); + relay = false; + } + else if (r == tesSUCCESS) + { + cLog(lsINFO) << "Transaction is now included in open ledger"; + dbtx->setStatus(INCLUDED); + theApp->getMasterTransaction().canonicalize(dbtx, true); + } + else + { + cLog(lsDEBUG) << "Status other than success " << r; + if (mMode == omFULL) + relay = false; + dbtx->setStatus(INVALID); + } + + if (relay) + { + std::set peers; + if (theApp->getSuppression().swapSet(txn->getID(), peers, SF_RELAYED)) + { + ripple::TMTransaction tx; + Serializer s; + dbtx->getSTransaction()->add(s); + tx.set_rawtransaction(&s.getData().front(), s.getLength()); + tx.set_status(ripple::tsCURRENT); + tx.set_receivetimestamp(getNetworkTimeNC()); // FIXME: This should be when we received it + + PackedMessage::pointer packet = boost::make_shared(tx, ripple::mtTRANSACTION); + theApp->getConnectionPool().relayMessageBut(peers, packet); + } + } + + txn->doCallbacks(r); + } + } + + if (theApp->getTxnQueue().stopProcessing(txn)) + theApp->getIOService().post(boost::bind(&NetworkOPs::runTransactionQueue, this)); +} + Transaction::pointer NetworkOPs::processTransaction(Transaction::pointer trans, stCallback callback) { LoadEvent::autoptr ev = theApp->getJobQueue().getLoadEventAP(jtTXN_PROC); @@ -556,8 +665,6 @@ bool NetworkOPs::checkLastClosedLedger(const std::vector& peerLis // agree? And do we have no better ledger available? // If so, we are either tracking or full. - // FIXME: We may have a ledger with many recent validations but that no directly-connected - // node is using. THis is kind of fundamental. cLog(lsTRACE) << "NetworkOPs::checkLastClosedLedger"; Ledger::pointer ourClosed = mLedgerMaster->getClosedLedger(); @@ -570,8 +677,8 @@ bool NetworkOPs::checkLastClosedLedger(const std::vector& peerLis { boost::unordered_map current = theApp->getValidations().getCurrentValidations(closedLedger); - typedef std::pair u256_cvc_pair; - BOOST_FOREACH(u256_cvc_pair& it, current) + typedef std::map::value_type u256_cvc_pair; + BOOST_FOREACH(const u256_cvc_pair& it, current) { ValidationCount& vc = ledgers[it.first]; vc.trustedValidations += it.second.first; @@ -689,6 +796,7 @@ bool NetworkOPs::checkLastClosedLedger(const std::vector& peerLis } return true; } + clearNeedNetworkLedger(); consensus = mAcquiringLedger->getLedger(); } @@ -707,7 +815,7 @@ void NetworkOPs::switchLastClosedLedger(Ledger::pointer newLedger, bool duringCo else cLog(lsERROR) << "JUMP last closed ledger to " << newLedger->getHash(); - mNeedNetworkLedger = false; + clearNeedNetworkLedger(); newLedger->setClosed(); Ledger::pointer openLedger = boost::make_shared(false, boost::ref(*newLedger)); mLedgerMaster->switchLedgers(newLedger, openLedger); @@ -825,11 +933,32 @@ void NetworkOPs::processTrustedProposal(LedgerProposal::pointer proposal, SHAMap::pointer NetworkOPs::getTXMap(const uint256& hash) { + std::map >::iterator it = mRecentPositions.find(hash); + if (it != mRecentPositions.end()) + return it->second.second; if (!haveConsensusObject()) return SHAMap::pointer(); return mConsensus->getTransactionTree(hash, false); } +void NetworkOPs::takePosition(int seq, SHAMap::ref position) +{ + mRecentPositions[position->getHash()] = std::make_pair(seq, position); + if (mRecentPositions.size() > 4) + { + std::map >::iterator it = mRecentPositions.begin(); + while (it != mRecentPositions.end()) + { + if (it->second.first < (seq - 2)) + { + mRecentPositions.erase(it); + return; + } + ++it; + } + } +} + SMAddNode NetworkOPs::gotTXData(const boost::shared_ptr& peer, const uint256& hash, const std::list& nodeIDs, const std::list< std::vector >& nodeData) { @@ -876,6 +1005,26 @@ void NetworkOPs::consensusViewChange() setMode(omCONNECTED); } +void NetworkOPs::pubServer() +{ + boost::recursive_mutex::scoped_lock sl(mMonitorLock); + + if (!mSubServer.empty()) + { + Json::Value jvObj(Json::objectValue); + + jvObj["type"] = "serverStatus"; + jvObj["server_status"] = strOperatingMode(); + jvObj["load_base"] = theApp->getFeeTrack().getLoadBase(); + jvObj["load_factor"] = theApp->getFeeTrack().getLoadFactor(); + + BOOST_FOREACH(InfoSub* ispListener, mSubServer) + { + ispListener->send(jvObj); + } + } +} + void NetworkOPs::setMode(OperatingMode om) { if (mMode == om) return; @@ -885,26 +1034,8 @@ void NetworkOPs::setMode(OperatingMode om) mMode = om; - Log lg((om < mMode) ? lsWARNING : lsINFO); - - lg << "STATE->" << strOperatingMode(); - - { - boost::recursive_mutex::scoped_lock sl(mMonitorLock); - - if (!mSubServer.empty()) - { - Json::Value jvObj(Json::objectValue); - - jvObj["type"] = "serverStatus"; - jvObj["server_status"] = strOperatingMode(); - - BOOST_FOREACH(InfoSub* ispListener, mSubServer) - { - ispListener->send(jvObj); - } - } - } + Log((om < mMode) ? lsWARNING : lsINFO) << "STATE->" << strOperatingMode(); + pubServer(); } @@ -970,39 +1101,87 @@ bool NetworkOPs::recvValidation(const SerializedValidation::pointer& val) return theApp->getValidations().addValidation(val); } -Json::Value NetworkOPs::getServerInfo() +Json::Value NetworkOPs::getServerInfo(bool human, bool admin) { Json::Value info = Json::objectValue; - switch (mMode) - { - case omDISCONNECTED: info["serverState"] = "disconnected"; break; - case omCONNECTED: info["serverState"] = "connected"; break; - case omTRACKING: info["serverState"] = "tracking"; break; - case omFULL: info["serverState"] = "validating"; break; - default: info["serverState"] = "unknown"; - } + if (theConfig.TESTNET) + info["testnet"] = theConfig.TESTNET; - if (!theConfig.VALIDATION_PUB.isValid()) - info["serverState"] = "none"; - else - info["validationPKey"] = theConfig.VALIDATION_PUB.humanNodePublic(); + info["server_state"] = strOperatingMode(); if (mNeedNetworkLedger) - info["networkLedger"] = "waiting"; + info["network_ledger"] = "waiting"; - info["completeLedgers"] = theApp->getLedgerMaster().getCompleteLedgers(); + if (admin) + { + if (theConfig.VALIDATION_PUB.isValid()) + info["pubkey_validator"] = theConfig.VALIDATION_PUB.humanNodePublic(); + else + info["pubkey_validator"] = "none"; + } + info["pubkey_node"] = theApp->getWallet().getNodePublic().humanNodePublic(); + + + info["complete_ledgers"] = theApp->getLedgerMaster().getCompleteLedgers(); info["peers"] = theApp->getConnectionPool().getPeerCount(); Json::Value lastClose = Json::objectValue; lastClose["proposers"] = theApp->getOPs().getPreviousProposers(); - lastClose["convergeTime"] = theApp->getOPs().getPreviousConvergeTime(); - info["lastClose"] = lastClose; + if (human) + lastClose["converge_time_s"] = static_cast(theApp->getOPs().getPreviousConvergeTime()) / 1000.0; + else + lastClose["converge_time"] = Json::Int(theApp->getOPs().getPreviousConvergeTime()); + info["last_close"] = lastClose; - if (mConsensus) - info["consensus"] = mConsensus->getJson(); +// if (mConsensus) +// info["consensus"] = mConsensus->getJson(); - info["load"] = theApp->getJobQueue().getJson(); + if (admin) + info["load"] = theApp->getJobQueue().getJson(); + + if (!human) + { + info["load_base"] = theApp->getFeeTrack().getLoadBase(); + info["load_factor"] = theApp->getFeeTrack().getLoadFactor(); + } + else + info["load_factor"] = + static_cast(theApp->getFeeTrack().getLoadBase()) / theApp->getFeeTrack().getLoadFactor(); + + bool valid = false; + Ledger::pointer lpClosed = getValidatedLedger(); + if (lpClosed) + valid = true; + else + lpClosed = getClosedLedger(); + + if (lpClosed) + { + uint64 baseFee = lpClosed->getBaseFee(); + uint64 baseRef = lpClosed->getReferenceFeeUnits(); + Json::Value l(Json::objectValue); + l["seq"] = Json::UInt(lpClosed->getLedgerSeq()); + l["hash"] = lpClosed->getHash().GetHex(); + l["validated"] = valid; + if (!human) + { + l["base_fee"] = Json::Value::UInt(baseFee); + l["reserve_base"] = Json::Value::UInt(lpClosed->getReserve(0)); + l["reserve_inc"] = Json::Value::UInt(lpClosed->getReserveInc()); + l["close_time"] = Json::Value::UInt(lpClosed->getCloseTimeNC()); + } + else + { + l["base_fee_xrp"] = static_cast(Json::UInt(baseFee)) / SYSTEM_CURRENCY_PARTS; + l["reserve_base_xrp"] = + static_cast(Json::UInt(lpClosed->getReserve(0) * baseFee / baseRef)) / SYSTEM_CURRENCY_PARTS; + l["reserve_inc_xrp"] = + static_cast(Json::UInt(lpClosed->getReserveInc() * baseFee / baseRef)) / SYSTEM_CURRENCY_PARTS; + l["age"] = Json::UInt(getCloseTimeNC() - lpClosed->getCloseTimeNC()); + } + info["closed_ledger"] = l; + } return info; } @@ -1042,12 +1221,8 @@ void NetworkOPs::pubProposedTransaction(Ledger::ref lpCurrent, const SerializedT void NetworkOPs::pubLedger(Ledger::ref lpAccepted) { - // Don't publish to clients ledgers we don't trust. - // TODO: we need to publish old transactions when we get reconnected to the network otherwise clients can miss transactions - if (NetworkOPs::omDISCONNECTED == getOperatingMode()) - return; - - LoadEvent::autoptr event(theApp->getJobQueue().getLoadEventAP(jtPUBLEDGER)); + // Ledgers are published only when they acquire sufficient validations + // Holes are filled across connection loss or other catastrophe { boost::recursive_mutex::scoped_lock sl(mMonitorLock); @@ -1061,6 +1236,11 @@ void NetworkOPs::pubLedger(Ledger::ref lpAccepted) jvObj["ledger_hash"] = lpAccepted->getHash().ToString(); jvObj["ledger_time"] = Json::Value::UInt(utFromSeconds(lpAccepted->getCloseTimeNC())); + jvObj["fee_ref"] = Json::UInt(lpAccepted->getReferenceFeeUnits()); + jvObj["fee_base"] = Json::UInt(lpAccepted->getBaseFee()); + jvObj["reserve_base"] = Json::UInt(lpAccepted->getReserve(0)); + jvObj["reserve_inc"] = Json::UInt(lpAccepted->getReserveInc()); + BOOST_FOREACH(InfoSub* ispListener, mSubLedger) { ispListener->send(jvObj); @@ -1068,26 +1248,24 @@ void NetworkOPs::pubLedger(Ledger::ref lpAccepted) } } + // Don't lock since pubAcceptedTransaction is locking. + if (!mSubTransactions.empty() || !mSubRTTransactions.empty() || !mSubAccount.empty() || !mSubRTAccount.empty() || !mSubmitMap.empty() ) { - // we don't lock since pubAcceptedTransaction is locking - if (!mSubTransactions.empty() || !mSubRTTransactions.empty() || !mSubAccount.empty() || !mSubRTAccount.empty() || !mSubmitMap.empty() ) + SHAMap& txSet = *lpAccepted->peekTransactionMap(); + + for (SHAMapItem::pointer item = txSet.peekFirstItem(); !!item; item = txSet.peekNextItem(item->getTag())) { - SHAMap& txSet = *lpAccepted->peekTransactionMap(); + SerializerIterator it(item->peekSerializer()); - for (SHAMapItem::pointer item = txSet.peekFirstItem(); !!item; item = txSet.peekNextItem(item->getTag())) - { - SerializerIterator it(item->peekSerializer()); + // OPTIMIZEME: Could get transaction from txn master, but still must call getVL + Serializer txnSer(it.getVL()); + SerializerIterator txnIt(txnSer); + SerializedTransaction stTxn(txnIt); - // OPTIMIZEME: Could get transaction from txn master, but still must call getVL - Serializer txnSer(it.getVL()); - SerializerIterator txnIt(txnSer); - SerializedTransaction stTxn(txnIt); + TransactionMetaSet::pointer meta = boost::make_shared( + stTxn.getTransactionID(), lpAccepted->getLedgerSeq(), it.getVL()); - TransactionMetaSet::pointer meta = boost::make_shared( - stTxn.getTransactionID(), lpAccepted->getLedgerSeq(), it.getVL()); - - pubAcceptedTransaction(lpAccepted, stTxn, meta->getResultTER(), meta); - } + pubAcceptedTransaction(lpAccepted, stTxn, meta->getResultTER(), meta); } } } @@ -1122,10 +1300,12 @@ Json::Value NetworkOPs::transJson(const SerializedTransaction& stTxn, TER terRes void NetworkOPs::pubAcceptedTransaction(Ledger::ref lpCurrent, const SerializedTransaction& stTxn, TER terResult,TransactionMetaSet::pointer& meta) { Json::Value jvObj = transJson(stTxn, terResult, true, lpCurrent, "transaction"); - if(meta) jvObj["meta"]=meta->getJson(0); + + if (meta) jvObj["meta"] = meta->getJson(0); { boost::recursive_mutex::scoped_lock sl(mMonitorLock); + BOOST_FOREACH(InfoSub* ispListener, mSubTransactions) { ispListener->send(jvObj); @@ -1137,25 +1317,24 @@ void NetworkOPs::pubAcceptedTransaction(Ledger::ref lpCurrent, const SerializedT } } - pubAccountTransaction(lpCurrent,stTxn,terResult,true,meta); + pubAccountTransaction(lpCurrent, stTxn, terResult, true, meta); } - -void NetworkOPs::pubAccountTransaction(Ledger::ref lpCurrent, const SerializedTransaction& stTxn, TER terResult, bool bAccepted,TransactionMetaSet::pointer& meta) +void NetworkOPs::pubAccountTransaction(Ledger::ref lpCurrent, const SerializedTransaction& stTxn, TER terResult, bool bAccepted, TransactionMetaSet::pointer& meta) { boost::unordered_set notify; { boost::recursive_mutex::scoped_lock sl(mMonitorLock); - if(!bAccepted && mSubRTAccount.empty()) return; + if (!bAccepted && mSubRTAccount.empty()) return; if (!mSubAccount.empty() || (!mSubRTAccount.empty()) ) { - typedef const std::pair AccountPair; - BOOST_FOREACH(AccountPair& affectedAccount, getAffectedAccounts(stTxn)) + std::vector accounts = meta ? meta->getAffectedAccounts() : stTxn.getMentionedAccounts(); + BOOST_FOREACH(const RippleAddress& affectedAccount, accounts) { - subInfoMapIterator simiIt = mSubRTAccount.find(affectedAccount.first.getAccountID()); + subInfoMapIterator simiIt = mSubRTAccount.find(affectedAccount.getAccountID()); if (simiIt != mSubRTAccount.end()) { @@ -1164,9 +1343,10 @@ void NetworkOPs::pubAccountTransaction(Ledger::ref lpCurrent, const SerializedTr notify.insert(ispListener); } } - if(bAccepted) + + if (bAccepted) { - simiIt = mSubAccount.find(affectedAccount.first.getAccountID()); + simiIt = mSubAccount.find(affectedAccount.getAccountID()); if (simiIt != mSubAccount.end()) { @@ -1180,10 +1360,12 @@ void NetworkOPs::pubAccountTransaction(Ledger::ref lpCurrent, const SerializedTr } } + // FIXME: This can crash. An InfoSub can go away while we hold a regular pointer to it. if (!notify.empty()) { Json::Value jvObj = transJson(stTxn, terResult, bAccepted, lpCurrent, "account"); - if(meta) jvObj["meta"]=meta->getJson(0); + + if (meta) jvObj["meta"] = meta->getJson(0); BOOST_FOREACH(InfoSub* ispListener, notify) { @@ -1192,45 +1374,19 @@ void NetworkOPs::pubAccountTransaction(Ledger::ref lpCurrent, const SerializedTr } } -// JED: I know this is sort of ugly. I'm going to rework this to get the affected accounts in a different way when we want finer granularity than just "account" -std::map NetworkOPs::getAffectedAccounts(const SerializedTransaction& stTxn) -{ - std::map accounts; - - BOOST_FOREACH(const SerializedType& it, stTxn.peekData()) - { - const STAccount* sa = dynamic_cast(&it); - if (sa) - { - RippleAddress na = sa->getValueNCA(); - accounts[na]=true; - }else - { - if( it.getFName() == sfLimitAmount ) - { - const STAmount* amount = dynamic_cast(&it); - if(amount) - { - RippleAddress na; - na.setAccountID(amount->getIssuer()); - accounts[na]=true; - } - } - } - } - return accounts; -} - // // Monitoring // - - -void NetworkOPs::subAccount(InfoSub* ispListener, const boost::unordered_set& vnaAccountIDs,bool rt) +void NetworkOPs::subAccount(InfoSub* ispListener, const boost::unordered_set& vnaAccountIDs, uint32 uLedgerIndex, bool rt) { - subInfoMapType& subMap=mSubAccount; - if(rt) subMap=mSubRTAccount; + subInfoMapType& subMap = rt ? mSubRTAccount : mSubAccount; + + // For the connection, monitor each account. + BOOST_FOREACH(const RippleAddress& naAccountID, vnaAccountIDs) + { + ispListener->insertSubAccountInfo(naAccountID, uLedgerIndex); + } boost::recursive_mutex::scoped_lock sl(mMonitorLock); @@ -1239,7 +1395,7 @@ void NetworkOPs::subAccount(InfoSub* ispListener, const boost::unordered_set usisElement; usisElement.insert(ispListener); @@ -1247,21 +1403,30 @@ void NetworkOPs::subAccount(InfoSub* ispListener, const boost::unordered_setsecond.insert(ispListener); } } } -void NetworkOPs::unsubAccount(InfoSub* ispListener, const boost::unordered_set& vnaAccountIDs,bool rt) +void NetworkOPs::unsubAccount(InfoSub* ispListener, const boost::unordered_set& vnaAccountIDs, bool rt) { - subInfoMapType& subMap= rt ? mSubRTAccount : mSubAccount; + subInfoMapType& subMap = rt ? mSubRTAccount : mSubAccount; + + // For the connection, unmonitor each account. + // FIXME: Don't we need to unsub? + // BOOST_FOREACH(const RippleAddress& naAccountID, vnaAccountIDs) + // { + // ispListener->deleteSubAccountInfo(naAccountID); + // } boost::recursive_mutex::scoped_lock sl(mMonitorLock); BOOST_FOREACH(const RippleAddress& naAccountID, vnaAccountIDs) { subInfoMapType::iterator simIterator = subMap.find(naAccountID.getAccountID()); + + if (simIterator == mSubAccount.end()) { // Not found. Done. @@ -1304,6 +1469,17 @@ void NetworkOPs::storeProposal(const LedgerProposal::pointer& proposal, const Ri props.push_back(proposal); } +InfoSub::~InfoSub() +{ + NetworkOPs& ops = theApp->getOPs(); + ops.unsubTransactions(this); + ops.unsubRTTransactions(this); + ops.unsubLedger(this); + ops.unsubServer(this); + ops.unsubAccount(this, mSubAccountInfo, true); + ops.unsubAccount(this, mSubAccountInfo, false); +} + #if 0 void NetworkOPs::subAccountChanges(InfoSub* ispListener, const uint256 uLedgerHash) { @@ -1317,9 +1493,16 @@ void NetworkOPs::unsubAccountChanges(InfoSub* ispListener) // <-- bool: true=added, false=already there bool NetworkOPs::subLedger(InfoSub* ispListener, Json::Value& jvResult) { - jvResult["ledger_index"] = getClosedLedger()->getLedgerSeq(); - jvResult["ledger_hash"] = getClosedLedger()->getHash().ToString(); - jvResult["ledger_time"] = Json::Value::UInt(utFromSeconds(getClosedLedger()->getCloseTimeNC())); + Ledger::pointer lpClosed = getClosedLedger(); + + jvResult["ledger_index"] = lpClosed->getLedgerSeq(); + jvResult["ledger_hash"] = lpClosed->getHash().ToString(); + jvResult["ledger_time"] = Json::Value::UInt(utFromSeconds(lpClosed->getCloseTimeNC())); + + jvResult["fee_ref"] = Json::UInt(lpClosed->getReferenceFeeUnits()); + jvResult["fee_base"] = Json::UInt(lpClosed->getBaseFee()); + jvResult["reserve_base"] = Json::UInt(lpClosed->getReserve(0)); + jvResult["reserve_inc"] = Json::UInt(lpClosed->getReserveInc()); return mSubLedger.insert(ispListener).second; } @@ -1335,10 +1518,17 @@ bool NetworkOPs::subServer(InfoSub* ispListener, Json::Value& jvResult) { uint256 uRandom; - jvResult["stand_alone"] = theConfig.RUN_STANDALONE; + if (theConfig.RUN_STANDALONE) + jvResult["stand_alone"] = theConfig.RUN_STANDALONE; + + if (theConfig.TESTNET) + jvResult["testnet"] = theConfig.TESTNET; getRand(uRandom.begin(), uRandom.size()); - jvResult["random"] = uRandom.ToString(); + jvResult["random"] = uRandom.ToString(); + jvResult["server_status"] = strOperatingMode(); + jvResult["load_base"] = theApp->getFeeTrack().getLoadBase(); + jvResult["load_factor"] = theApp->getFeeTrack().getLoadFactor(); return mSubServer.insert(ispListener).second; } @@ -1373,4 +1563,34 @@ bool NetworkOPs::unsubRTTransactions(InfoSub* ispListener) return !!mSubTransactions.erase(ispListener); } +RPCSub* NetworkOPs::findRpcSub(const std::string& strUrl) +{ + RPCSub* rspResult; + boost::recursive_mutex::scoped_lock sl(mMonitorLock); + + subRpcMapType::iterator it; + + it = mRpcSubMap.find(strUrl); + if (it == mRpcSubMap.end()) + { + rspResult = (RPCSub*)(0); + } + else + { + rspResult = it->second; + } + + return rspResult; +} + +RPCSub* NetworkOPs::addRpcSub(const std::string& strUrl, RPCSub* rspEntry) +{ + boost::recursive_mutex::scoped_lock sl(mMonitorLock); + + mRpcSubMap.insert(std::make_pair(strUrl, rspEntry)); + + return rspEntry; +} + + // vim:ts=4 diff --git a/src/cpp/ripple/NetworkOPs.h b/src/cpp/ripple/NetworkOPs.h index 25211db63..0979671fb 100644 --- a/src/cpp/ripple/NetworkOPs.h +++ b/src/cpp/ripple/NetworkOPs.h @@ -22,24 +22,28 @@ class LedgerConsensus; DEFINE_INSTANCE(InfoSub); +class RPCSub; + class InfoSub : public IS_INSTANCE(InfoSub) { -public: - - virtual ~InfoSub() { ; } - - virtual void send(const Json::Value& jvObj) = 0; - protected: boost::unordered_set mSubAccountInfo; boost::unordered_set mSubAccountTransaction; - boost::mutex mLock; + boost::mutex mLockInfo; public: - void insertSubAccountInfo(RippleAddress addr) + + virtual ~InfoSub(); + + virtual void send(const Json::Value& jvObj) = 0; + + void onSendEmpty(); + + void insertSubAccountInfo(RippleAddress addr, uint32 uLedgerIndex) { - boost::mutex::scoped_lock sl(mLock); + boost::mutex::scoped_lock sl(mLockInfo); + mSubAccountInfo.insert(addr); } }; @@ -68,8 +72,11 @@ protected: typedef boost::unordered_map > subSubmitMapType; + typedef boost::unordered_map subRpcMapType; + OperatingMode mMode; bool mNeedNetworkLedger; + bool mProposing, mValidating; boost::posix_time::ptime mConnectTime; boost::asio::deadline_timer mNetTimer; boost::shared_ptr mConsensus; @@ -88,6 +95,8 @@ protected: uint32 mLastValidationTime; SerializedValidation::pointer mLastValidation; + // Recent positions taken + std::map > mRecentPositions; // XXX Split into more locks. boost::recursive_mutex mMonitorLock; @@ -95,11 +104,15 @@ protected: subInfoMapType mSubRTAccount; subSubmitMapType mSubmitMap; // TODO: probably dump this + subRpcMapType mRpcSubMap; + boost::unordered_set mSubLedger; // accepted ledgers boost::unordered_set mSubServer; // when server changes connectivity state boost::unordered_set mSubTransactions; // all accepted transactions boost::unordered_set mSubRTTransactions; // all proposed and accepted transactions + boost::recursive_mutex mWantedHashLock; + boost::unordered_set mWantedHashes; void setMode(OperatingMode); @@ -110,7 +123,8 @@ protected: void pubAcceptedTransaction(Ledger::ref lpCurrent, const SerializedTransaction& stTxn, TER terResult,TransactionMetaSet::pointer& meta); void pubAccountTransaction(Ledger::ref lpCurrent, const SerializedTransaction& stTxn, TER terResult,bool accepted,TransactionMetaSet::pointer& meta); - std::map getAffectedAccounts(const SerializedTransaction& stTxn); + + void pubServer(); public: NetworkOPs(boost::asio::io_service& io_service, LedgerMaster* pLedgerMaster); @@ -126,8 +140,9 @@ public: OperatingMode getOperatingMode() { return mMode; } std::string strOperatingMode(); - Ledger::pointer getClosedLedger() { return mLedgerMaster->getClosedLedger(); } - Ledger::pointer getCurrentLedger() { return mLedgerMaster->getCurrentLedger(); } + Ledger::ref getClosedLedger() { return mLedgerMaster->getClosedLedger(); } + Ledger::ref getValidatedLedger() { return mLedgerMaster->getValidatedLedger(); } + Ledger::ref getCurrentLedger() { return mLedgerMaster->getCurrentLedger(); } Ledger::pointer getLedgerByHash(const uint256& hash) { return mLedgerMaster->getLedgerByHash(hash); } Ledger::pointer getLedgerBySeq(const uint32 seq) { return mLedgerMaster->getLedgerBySeq(seq); } @@ -146,8 +161,9 @@ public: // typedef boost::function stCallback; // must complete immediately void submitTransaction(Job&, SerializedTransaction::pointer, stCallback callback = stCallback()); - Transaction::pointer submitTransactionSync(const Transaction::pointer& tpTrans); + Transaction::pointer submitTransactionSync(const Transaction::pointer& tpTrans, bool bSubmit=true); + void runTransactionQueue(); Transaction::pointer processTransaction(Transaction::pointer, stCallback); Transaction::pointer processTransaction(Transaction::pointer transaction) { return processTransaction(transaction, stCallback()); } @@ -206,6 +222,7 @@ public: SMAddNode gotTXData(const boost::shared_ptr& peer, const uint256& hash, const std::list& nodeIDs, const std::list< std::vector >& nodeData); bool recvValidation(const SerializedValidation::pointer& val); + void takePosition(int seq, SHAMap::ref position); SHAMap::pointer getTXMap(const uint256& hash); bool hasTXSet(const boost::shared_ptr& peer, const uint256& set, ripple::TxSetStatus status); void mapComplete(const uint256& hash, SHAMap::ref map); @@ -222,18 +239,24 @@ public: void needNetworkLedger() { mNeedNetworkLedger = true; } void clearNeedNetworkLedger() { mNeedNetworkLedger = false; } bool isNeedNetworkLedger() { return mNeedNetworkLedger; } + void setProposing(bool p, bool v) { mProposing = p; mValidating = v; } + bool isProposing() { return mProposing; } + bool isValidating() { return mValidating; } void consensusViewChange(); int getPreviousProposers() { return mLastCloseProposers; } int getPreviousConvergeTime() { return mLastCloseConvergeTime; } uint32 getLastCloseTime() { return mLastCloseTime; } void setLastCloseTime(uint32 t) { mLastCloseTime = t; } - Json::Value getServerInfo(); + Json::Value getServerInfo(bool human, bool admin); uint32 acceptLedger(); boost::unordered_map >& peekStoredProposals() { return mStoredProposals; } void storeProposal(const LedgerProposal::pointer& proposal, const RippleAddress& peerPublic); uint256 getConsensusLCL(); + bool addWantedHash(const uint256& h); + bool isWantedHash(const uint256& h, bool remove); + // client information retrieval functions std::vector< std::pair > getAccountTxs(const RippleAddress& account, uint32 minLedger, uint32 maxLedger); @@ -250,8 +273,8 @@ public: // // Monitoring: subscriber side // - void subAccount(InfoSub* ispListener, const boost::unordered_set& vnaAccountIDs,bool rt); - void unsubAccount(InfoSub* ispListener, const boost::unordered_set& vnaAccountIDs,bool rt); + void subAccount(InfoSub* ispListener, const boost::unordered_set& vnaAccountIDs, uint32 uLedgerIndex, bool rt); + void unsubAccount(InfoSub* ispListener, const boost::unordered_set& vnaAccountIDs, bool rt); bool subLedger(InfoSub* ispListener, Json::Value& jvResult); bool unsubLedger(InfoSub* ispListener); @@ -264,6 +287,9 @@ public: bool subRTTransactions(InfoSub* ispListener); bool unsubRTTransactions(InfoSub* ispListener); + + RPCSub* findRpcSub(const std::string& strUrl); + RPCSub* addRpcSub(const std::string& strUrl, RPCSub* rspEntry); }; #endif diff --git a/src/cpp/ripple/Offer.cpp b/src/cpp/ripple/Offer.cpp index 5fe7add9f..126f6801b 100644 --- a/src/cpp/ripple/Offer.cpp +++ b/src/cpp/ripple/Offer.cpp @@ -13,4 +13,6 @@ Offer::Offer(SerializedLedgerEntry::pointer ledgerEntry) : AccountItem(ledgerEnt mTakerGets = mLedgerEntry->getFieldAmount(sfTakerGets); mTakerPays = mLedgerEntry->getFieldAmount(sfTakerPays); mSeq = mLedgerEntry->getFieldU32(sfSequence); -} \ No newline at end of file +} + +// vim:ts=4 diff --git a/src/cpp/ripple/Offer.h b/src/cpp/ripple/Offer.h index 75cc5dc57..94a4fe837 100644 --- a/src/cpp/ripple/Offer.h +++ b/src/cpp/ripple/Offer.h @@ -1,6 +1,5 @@ #include "AccountItems.h" - class Offer : public AccountItem { RippleAddress mAccount; @@ -20,4 +19,6 @@ public: RippleAddress getAccount(){ return(mAccount); } int getSeq(){ return(mSeq); } -}; \ No newline at end of file +}; + +// vim:ts=4 diff --git a/src/cpp/ripple/OfferCancelTransactor.cpp b/src/cpp/ripple/OfferCancelTransactor.cpp index 0eaff9daa..e17e4c9cf 100644 --- a/src/cpp/ripple/OfferCancelTransactor.cpp +++ b/src/cpp/ripple/OfferCancelTransactor.cpp @@ -1,17 +1,28 @@ #include "OfferCancelTransactor.h" #include "Log.h" +SETUP_LOG(); + TER OfferCancelTransactor::doApply() { TER terResult; const uint32 uOfferSequence = mTxn.getFieldU32(sfOfferSequence); const uint32 uAccountSequenceNext = mTxnAccount->getFieldU32(sfSequence); - Log(lsDEBUG) << "doOfferCancel: uAccountSequenceNext=" << uAccountSequenceNext << " uOfferSequence=" << uOfferSequence; + cLog(lsDEBUG) << "OfferCancel: uAccountSequenceNext=" << uAccountSequenceNext << " uOfferSequence=" << uOfferSequence; + + const uint32 uTxFlags = mTxn.getFlags(); + + if (uTxFlags) + { + cLog(lsINFO) << "OfferCancel: Malformed transaction: Invalid flags set."; + + return temINVALID_FLAG; + } if (!uOfferSequence || uAccountSequenceNext-1 <= uOfferSequence) { - Log(lsINFO) << "doOfferCancel: uAccountSequenceNext=" << uAccountSequenceNext << " uOfferSequence=" << uOfferSequence; + cLog(lsINFO) << "OfferCancel: uAccountSequenceNext=" << uAccountSequenceNext << " uOfferSequence=" << uOfferSequence; terResult = temBAD_SEQUENCE; } @@ -22,13 +33,13 @@ TER OfferCancelTransactor::doApply() if (sleOffer) { - Log(lsWARNING) << "doOfferCancel: uOfferSequence=" << uOfferSequence; + cLog(lsWARNING) << "OfferCancel: uOfferSequence=" << uOfferSequence; terResult = mEngine->getNodes().offerDelete(sleOffer, uOfferIndex, mTxnAccountID); } else { - Log(lsWARNING) << "doOfferCancel: offer not found: " + cLog(lsWARNING) << "OfferCancel: offer not found: " << RippleAddress::createHumanAccountID(mTxnAccountID) << " : " << uOfferSequence << " : " << uOfferIndex.ToString(); diff --git a/src/cpp/ripple/OfferCancelTransactor.h b/src/cpp/ripple/OfferCancelTransactor.h index 8ddd6a5c1..0f9a4eb6b 100644 --- a/src/cpp/ripple/OfferCancelTransactor.h +++ b/src/cpp/ripple/OfferCancelTransactor.h @@ -4,6 +4,8 @@ class OfferCancelTransactor : public Transactor { public: OfferCancelTransactor(const SerializedTransaction& txn,TransactionEngineParams params, TransactionEngine* engine) : Transactor(txn,params,engine) {} - + TER doApply(); -}; \ No newline at end of file +}; + +// vim:ts=4 diff --git a/src/cpp/ripple/OfferCreateTransactor.cpp b/src/cpp/ripple/OfferCreateTransactor.cpp index 1d8fb249d..5b46c53e1 100644 --- a/src/cpp/ripple/OfferCreateTransactor.cpp +++ b/src/cpp/ripple/OfferCreateTransactor.cpp @@ -1,3 +1,5 @@ +#include "Application.h" + #include "OfferCreateTransactor.h" #include @@ -24,7 +26,7 @@ TER OfferCreateTransactor::takeOffers( { assert(saTakerPays && saTakerGets); - Log(lsINFO) << "takeOffers: against book: " << uBookBase.ToString(); + cLog(lsINFO) << "takeOffers: against book: " << uBookBase.ToString(); uint256 uTipIndex = uBookBase; const uint256 uBookEnd = Ledger::getQualityNext(uBookBase); @@ -53,14 +55,14 @@ TER OfferCreateTransactor::takeOffers( sleOfferDir = mEngine->entryCache(ltDIR_NODE, mEngine->getLedger()->getNextLedgerIndex(uTipIndex, uBookEnd)); if (sleOfferDir) { - Log(lsINFO) << "takeOffers: possible counter offer found"; + cLog(lsINFO) << "takeOffers: possible counter offer found"; uTipIndex = sleOfferDir->getIndex(); uTipQuality = Ledger::getQuality(uTipIndex); } else { - Log(lsINFO) << "takeOffers: counter offer book is empty: " + cLog(lsINFO) << "takeOffers: counter offer book is empty: " << uTipIndex.ToString() << " ... " << uBookEnd.ToString(); @@ -72,14 +74,14 @@ TER OfferCreateTransactor::takeOffers( || (bPassive && uTakeQuality == uTipQuality)) { // Done. - Log(lsINFO) << "takeOffers: done"; + cLog(lsINFO) << "takeOffers: done"; terResult = tesSUCCESS; } else { // Have an offer directory to consider. - Log(lsINFO) << "takeOffers: considering dir: " << sleOfferDir->getJson(0); + cLog(lsINFO) << "takeOffers: considering dir: " << sleOfferDir->getJson(0); SLE::pointer sleBookNode; unsigned int uBookEntry; @@ -89,7 +91,7 @@ TER OfferCreateTransactor::takeOffers( SLE::pointer sleOffer = mEngine->entryCache(ltOFFER, uOfferIndex); - Log(lsINFO) << "takeOffers: considering offer : " << sleOffer->getJson(0); + cLog(lsINFO) << "takeOffers: considering offer : " << sleOffer->getJson(0); const uint160 uOfferOwnerID = sleOffer->getFieldAccount(sfAccount).getAccountID(); STAmount saOfferPays = sleOffer->getFieldAmount(sfTakerGets); @@ -98,14 +100,14 @@ TER OfferCreateTransactor::takeOffers( if (sleOffer->isFieldPresent(sfExpiration) && sleOffer->getFieldU32(sfExpiration) <= mEngine->getLedger()->getParentCloseTimeNC()) { // Offer is expired. Expired offers are considered unfunded. Delete it. - Log(lsINFO) << "takeOffers: encountered expired offer"; + cLog(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"; + cLog(lsINFO) << "takeOffers: encountered taker's own old offer"; usOfferUnfundedFound.insert(uOfferIndex); } @@ -113,16 +115,16 @@ TER OfferCreateTransactor::takeOffers( { // Get offer funds available. - Log(lsINFO) << "takeOffers: saOfferPays=" << saOfferPays.getFullText(); + cLog(lsINFO) << "takeOffers: saOfferPays=" << saOfferPays.getFullText(); - STAmount saOfferFunds = mEngine->getNodes().accountFunds(uOfferOwnerID, saOfferPays, true); - STAmount saTakerFunds = mEngine->getNodes().accountFunds(uTakerAccountID, saTakerPays, true); + STAmount saOfferFunds = mEngine->getNodes().accountFunds(uOfferOwnerID, saOfferPays); + STAmount saTakerFunds = mEngine->getNodes().accountFunds(uTakerAccountID, saTakerPays); SLE::pointer sleOfferAccount; // Owner of offer. if (!saOfferFunds.isPositive()) { // Offer is unfunded, possibly due to previous balance action. - Log(lsINFO) << "takeOffers: offer unfunded: delete"; + cLog(lsINFO) << "takeOffers: offer unfunded: delete"; boost::unordered_set::iterator account = usAccountTouched.find(uOfferOwnerID); if (account != usAccountTouched.end()) @@ -145,33 +147,35 @@ TER OfferCreateTransactor::takeOffers( STAmount saSubTakerGot; STAmount saTakerIssuerFee; STAmount saOfferIssuerFee; + STAmount saOfferRate = STAmount::setRate(uTipQuality); - 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(); + cLog(lsINFO) << "takeOffers: applyOffer: saTakerPays: " << saTakerPays.getFullText(); + cLog(lsINFO) << "takeOffers: applyOffer: saTakerPaid: " << saTakerPaid.getFullText(); + cLog(lsINFO) << "takeOffers: applyOffer: saTakerFunds: " << saTakerFunds.getFullText(); + cLog(lsINFO) << "takeOffers: applyOffer: saOfferFunds: " << saOfferFunds.getFullText(); + cLog(lsINFO) << "takeOffers: applyOffer: saPay: " << saPay.getFullText(); + cLog(lsINFO) << "takeOffers: applyOffer: saOfferPays: " << saOfferPays.getFullText(); + cLog(lsINFO) << "takeOffers: applyOffer: saOfferGets: " << saOfferGets.getFullText(); + cLog(lsINFO) << "takeOffers: applyOffer: saOfferRate: " << saOfferRate.getFullText(); + cLog(lsINFO) << "takeOffers: applyOffer: saTakerPays: " << saTakerPays.getFullText(); + cLog(lsINFO) << "takeOffers: applyOffer: saTakerGets: " << saTakerGets.getFullText(); bool bOfferDelete = STAmount::applyOffer( mEngine->getNodes().rippleTransferRate(uTakerAccountID, uOfferOwnerID, uTakerPaysAccountID), mEngine->getNodes().rippleTransferRate(uOfferOwnerID, uTakerAccountID, uTakerGetsAccountID), + saOfferRate, saOfferFunds, - saPay, // Driver XXX need to account for fees. + saPay, saOfferPays, saOfferGets, - saTakerPays, saTakerGets, saSubTakerPaid, saSubTakerGot, saTakerIssuerFee, saOfferIssuerFee); - Log(lsINFO) << "takeOffers: applyOffer: saSubTakerPaid: " << saSubTakerPaid.getFullText(); - Log(lsINFO) << "takeOffers: applyOffer: saSubTakerGot: " << saSubTakerGot.getFullText(); + cLog(lsINFO) << "takeOffers: applyOffer: saSubTakerPaid: " << saSubTakerPaid.getFullText(); + cLog(lsINFO) << "takeOffers: applyOffer: saSubTakerGot: " << saSubTakerGot.getFullText(); // Adjust offer @@ -186,7 +190,7 @@ TER OfferCreateTransactor::takeOffers( if (bOfferDelete) { // Offer now fully claimed or now unfunded. - Log(lsINFO) << "takeOffers: offer claimed: delete"; + cLog(lsINFO) << "takeOffers: offer claimed: delete"; usOfferUnfundedBecame.insert(uOfferIndex); // Delete unfunded offer on success. @@ -195,36 +199,48 @@ TER OfferCreateTransactor::takeOffers( } else { - Log(lsINFO) << "takeOffers: offer partial claim."; + cLog(lsINFO) << "takeOffers: offer partial claim."; } + assert(uTakerGetsAccountID == saSubTakerGot.getIssuer()); + assert(uTakerPaysAccountID == saSubTakerPaid.getIssuer()); + // Offer owner pays taker. // saSubTakerGot.setIssuer(uTakerGetsAccountID); // XXX Move this earlier? - assert(!!saSubTakerGot.getIssuer()); - mEngine->getNodes().accountSend(uOfferOwnerID, uTakerAccountID, saSubTakerGot); - mEngine->getNodes().accountSend(uOfferOwnerID, uTakerGetsAccountID, saOfferIssuerFee); - - saTakerGot += saSubTakerGot; + terResult = mEngine->getNodes().accountSend(uOfferOwnerID, uTakerAccountID, saSubTakerGot); + if (tesSUCCESS == terResult) + terResult = mEngine->getNodes().accountSend(uOfferOwnerID, uTakerGetsAccountID, saOfferIssuerFee); // Taker pays offer owner. // saSubTakerPaid.setIssuer(uTakerPaysAccountID); - assert(!!saSubTakerPaid.getIssuer()); - mEngine->getNodes().accountSend(uTakerAccountID, uOfferOwnerID, saSubTakerPaid); - mEngine->getNodes().accountSend(uTakerAccountID, uTakerPaysAccountID, saTakerIssuerFee); + if (tesSUCCESS == terResult) + terResult = mEngine->getNodes().accountSend(uTakerAccountID, uOfferOwnerID, saSubTakerPaid); + if (tesSUCCESS == terResult) + terResult = mEngine->getNodes().accountSend(uTakerAccountID, uTakerPaysAccountID, saTakerIssuerFee); + + saTakerGot += saSubTakerGot; saTakerPaid += saSubTakerPaid; + + if (tesSUCCESS == terResult) + terResult = temUNCERTAIN; } } } } + cLog(lsINFO) << "takeOffers: " << transToken(terResult); + // 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) { + + cLog(lsINFO) << "takeOffers: found unfunded: " << uOfferIndex.ToString(); + terResult = mEngine->getNodes().offerDelete(uOfferIndex); if (tesSUCCESS != terResult) break; @@ -236,24 +252,28 @@ TER OfferCreateTransactor::takeOffers( // On success, delete offers that became unfunded. BOOST_FOREACH(const uint256& uOfferIndex, usOfferUnfundedBecame) { + cLog(lsINFO) << "takeOffers: became unfunded: " << uOfferIndex.ToString(); + terResult = mEngine->getNodes().offerDelete(uOfferIndex); if (tesSUCCESS != terResult) break; } } + cLog(lsINFO) << "takeOffers< " << transToken(terResult); + return terResult; } TER OfferCreateTransactor::doApply() { - Log(lsWARNING) << "doOfferCreate> " << mTxn.getJson(0); + cLog(lsWARNING) << "OfferCreate> " << mTxn.getJson(0); const uint32 uTxFlags = mTxn.getFlags(); const bool bPassive = isSetBit(uTxFlags, tfPassive); STAmount saTakerPays = mTxn.getFieldAmount(sfTakerPays); STAmount saTakerGets = mTxn.getFieldAmount(sfTakerGets); - Log(lsINFO) << boost::str(boost::format("doOfferCreate: saTakerPays=%s saTakerGets=%s") + cLog(lsINFO) << boost::str(boost::format("OfferCreate: saTakerPays=%s saTakerGets=%s") % saTakerPays.getFullText() % saTakerGets.getFullText()); @@ -265,7 +285,7 @@ TER OfferCreateTransactor::doApply() const uint256 uLedgerIndex = Ledger::getOfferIndex(mTxnAccountID, uSequence); - Log(lsINFO) << "doOfferCreate: Creating offer node: " << uLedgerIndex.ToString() << " uSequence=" << uSequence; + cLog(lsINFO) << "OfferCreate: Creating offer node: " << uLedgerIndex.ToString() << " uSequence=" << uSequence; const uint160 uPaysCurrency = saTakerPays.getCurrency(); const uint160 uGetsCurrency = saTakerGets.getCurrency(); @@ -278,51 +298,51 @@ TER OfferCreateTransactor::doApply() if (uTxFlags & tfOfferCreateMask) { - Log(lsINFO) << "doOfferCreate: Malformed transaction: Invalid flags set."; + cLog(lsINFO) << "OfferCreate: Malformed transaction: Invalid flags set."; return temINVALID_FLAG; } else if (bHaveExpiration && !uExpiration) { - Log(lsWARNING) << "doOfferCreate: Malformed offer: bad expiration"; + cLog(lsWARNING) << "OfferCreate: Malformed offer: bad expiration"; terResult = temBAD_EXPIRATION; } else if (bHaveExpiration && mEngine->getLedger()->getParentCloseTimeNC() >= uExpiration) { - Log(lsWARNING) << "doOfferCreate: Expired transaction: offer expired"; + cLog(lsWARNING) << "OfferCreate: Expired transaction: offer expired"; terResult = tesSUCCESS; // Only charged fee. } else if (saTakerPays.isNative() && saTakerGets.isNative()) { - Log(lsWARNING) << "doOfferCreate: Malformed offer: XRP for XRP"; + cLog(lsWARNING) << "OfferCreate: Malformed offer: XRP for XRP"; terResult = temBAD_OFFER; } else if (!saTakerPays.isPositive() || !saTakerGets.isPositive()) { - Log(lsWARNING) << "doOfferCreate: Malformed offer: bad amount"; + cLog(lsWARNING) << "OfferCreate: Malformed offer: bad amount"; terResult = temBAD_OFFER; } else if (uPaysCurrency == uGetsCurrency && uPaysIssuerID == uGetsIssuerID) { - Log(lsWARNING) << "doOfferCreate: Malformed offer: redundant offer"; + cLog(lsWARNING) << "OfferCreate: Malformed offer: redundant offer"; terResult = temREDUNDANT; } else if (saTakerPays.isNative() != !uPaysIssuerID || saTakerGets.isNative() != !uGetsIssuerID) { - Log(lsWARNING) << "doOfferCreate: Malformed offer: bad issuer"; + cLog(lsWARNING) << "OfferCreate: Malformed offer: bad issuer"; terResult = temBAD_ISSUER; } - else if (!mEngine->getNodes().accountFunds(mTxnAccountID, saTakerGets, true).isPositive()) + else if (!mEngine->getNodes().accountFunds(mTxnAccountID, saTakerGets).isPositive()) { - Log(lsWARNING) << "doOfferCreate: delay: Offers must be at least partially funded."; + cLog(lsWARNING) << "OfferCreate: delay: Offers must be at least partially funded."; - terResult = terUNFUNDED; + terResult = tecUNFUNDED_OFFER; } if (tesSUCCESS == terResult && !saTakerPays.isNative()) @@ -331,25 +351,26 @@ TER OfferCreateTransactor::doApply() if (!sleTakerPays) { - Log(lsWARNING) << "doOfferCreate: delay: can't receive IOUs from non-existent issuer: " << RippleAddress::createHumanAccountID(uPaysIssuerID); + cLog(lsWARNING) << "OfferCreate: delay: can't receive IOUs from non-existent issuer: " << RippleAddress::createHumanAccountID(uPaysIssuerID); terResult = terNO_ACCOUNT; } } + STAmount saOfferPaid; + STAmount saOfferGot; + 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") + cLog(lsINFO) << boost::str(boost::format("OfferCreate: take against book: %s for %s -> %s") % uTakeBookBase.ToString() % saTakerGets.getFullText() % saTakerPays.getFullText()); // Take using the parameters of the offer. - Log(lsWARNING) << "doOfferCreate: takeOffers: BEFORE saTakerGets=" << saTakerGets.getFullText(); + cLog(lsWARNING) << "OfferCreate: takeOffers: BEFORE saTakerGets=" << saTakerGets.getFullText(); terResult = takeOffers( bPassive, uTakeBookBase, @@ -361,11 +382,11 @@ TER OfferCreateTransactor::doApply() 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: AFTER saTakerGets=" << saTakerGets.getFullText(); + cLog(lsWARNING) << "OfferCreate: takeOffers=" << terResult; + cLog(lsWARNING) << "OfferCreate: takeOffers: saOfferPaid=" << saOfferPaid.getFullText(); + cLog(lsWARNING) << "OfferCreate: takeOffers: saOfferGot=" << saOfferGot.getFullText(); + cLog(lsWARNING) << "OfferCreate: takeOffers: saTakerPays=" << saTakerPays.getFullText(); + cLog(lsWARNING) << "OfferCreate: takeOffers: AFTER saTakerGets=" << saTakerGets.getFullText(); if (tesSUCCESS == terResult) { @@ -374,21 +395,47 @@ TER OfferCreateTransactor::doApply() } } - Log(lsWARNING) << "doOfferCreate: takeOffers: saTakerPays=" << saTakerPays.getFullText(); - Log(lsWARNING) << "doOfferCreate: takeOffers: saTakerGets=" << saTakerGets.getFullText(); - Log(lsWARNING) << "doOfferCreate: takeOffers: mTxnAccountID=" << RippleAddress::createHumanAccountID(mTxnAccountID); - Log(lsWARNING) << "doOfferCreate: takeOffers: FUNDS=" << mEngine->getNodes().accountFunds(mTxnAccountID, saTakerGets, true).getFullText(); + cLog(lsWARNING) << "OfferCreate: takeOffers: saTakerPays=" << saTakerPays.getFullText(); + cLog(lsWARNING) << "OfferCreate: takeOffers: saTakerGets=" << saTakerGets.getFullText(); + cLog(lsWARNING) << "OfferCreate: takeOffers: mTxnAccountID=" << RippleAddress::createHumanAccountID(mTxnAccountID); + cLog(lsWARNING) << "OfferCreate: takeOffers: FUNDS=" << mEngine->getNodes().accountFunds(mTxnAccountID, saTakerGets).getFullText(); - // Log(lsWARNING) << "doOfferCreate: takeOffers: uPaysIssuerID=" << RippleAddress::createHumanAccountID(uPaysIssuerID); - // Log(lsWARNING) << "doOfferCreate: takeOffers: uGetsIssuerID=" << RippleAddress::createHumanAccountID(uGetsIssuerID); + // cLog(lsWARNING) << "OfferCreate: takeOffers: uPaysIssuerID=" << RippleAddress::createHumanAccountID(uPaysIssuerID); + // cLog(lsWARNING) << "OfferCreate: takeOffers: uGetsIssuerID=" << RippleAddress::createHumanAccountID(uGetsIssuerID); - if (tesSUCCESS == terResult - && saTakerPays // Still wanting something. - && saTakerGets // Still offering something. - && mEngine->getNodes().accountFunds(mTxnAccountID, saTakerGets, true).isPositive()) // Still funded. + if (tesSUCCESS != terResult + || !saTakerPays // Wants nothing more. + || !saTakerGets // Offering nothing more. + || !mEngine->getNodes().accountFunds(mTxnAccountID, saTakerGets).isPositive()) // Not funded. + { + // Complete as is. + nothing(); + } + else if (mTxnAccount->getFieldAmount(sfBalance).getNValue() < mEngine->getLedger()->getReserve(mTxnAccount->getFieldU32(sfOwnerCount)+1)) + { + if (isSetBit(mParams, tapOPEN_LEDGER)) // Ledger is not final, can vote no. + { + // Hope for more reserve to come in or more offers to consume. + terResult = tecINSUF_RESERVE_OFFER; + } + else if (!saOfferPaid && !saOfferGot) + { + // Ledger is final, insufficent reserve to create offer, processed nothing. + + terResult = tecINSUF_RESERVE_OFFER; + } + else + { + // Ledger is final, insufficent reserve to create offer, processed something. + + // Consider the offer unfunded. Treat as tesSUCCESS. + nothing(); + } + } + else { // 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") + cLog(lsINFO) << boost::str(boost::format("OfferCreate: offer not fully consumed: saTakerPays=%s saTakerGets=%s") % saTakerPays.getFullText() % saTakerGets.getFullText()); @@ -397,17 +444,14 @@ TER OfferCreateTransactor::doApply() boost::bind(&Ledger::qualityDirDescriber, _1, saTakerPays.getCurrency(), uPaysIssuerID, saTakerGets.getCurrency(), uGetsIssuerID, uRate)); - // Update owner count. - if (tesSUCCESS == terResult) - { - terResult = mEngine->getNodes().ownerCountAdjust(mTxnAccountID, 1, mTxnAccount); - } if (tesSUCCESS == terResult) { + mEngine->getNodes().ownerCountAdjust(mTxnAccountID, 1, mTxnAccount); // Update owner count. + uint256 uBookBase = Ledger::getBookBase(uPaysCurrency, uPaysIssuerID, uGetsCurrency, uGetsIssuerID); - Log(lsINFO) << boost::str(boost::format("doOfferCreate: adding to book: %s : %s/%s -> %s/%s") + cLog(lsINFO) << boost::str(boost::format("OfferCreate: adding to book: %s : %s/%s -> %s/%s") % uBookBase.ToString() % saTakerPays.getHumanCurrency() % RippleAddress::createHumanAccountID(saTakerPays.getIssuer()) @@ -424,13 +468,13 @@ TER OfferCreateTransactor::doApply() if (tesSUCCESS == terResult) { - Log(lsWARNING) << "doOfferCreate: sfAccount=" << RippleAddress::createHumanAccountID(mTxnAccountID); - Log(lsWARNING) << "doOfferCreate: uPaysIssuerID=" << RippleAddress::createHumanAccountID(uPaysIssuerID); - Log(lsWARNING) << "doOfferCreate: uGetsIssuerID=" << RippleAddress::createHumanAccountID(uGetsIssuerID); - Log(lsWARNING) << "doOfferCreate: saTakerPays.isNative()=" << saTakerPays.isNative(); - Log(lsWARNING) << "doOfferCreate: saTakerGets.isNative()=" << saTakerGets.isNative(); - Log(lsWARNING) << "doOfferCreate: uPaysCurrency=" << saTakerPays.getHumanCurrency(); - Log(lsWARNING) << "doOfferCreate: uGetsCurrency=" << saTakerGets.getHumanCurrency(); + cLog(lsWARNING) << "OfferCreate: sfAccount=" << RippleAddress::createHumanAccountID(mTxnAccountID); + cLog(lsWARNING) << "OfferCreate: uPaysIssuerID=" << RippleAddress::createHumanAccountID(uPaysIssuerID); + cLog(lsWARNING) << "OfferCreate: uGetsIssuerID=" << RippleAddress::createHumanAccountID(uGetsIssuerID); + cLog(lsWARNING) << "OfferCreate: saTakerPays.isNative()=" << saTakerPays.isNative(); + cLog(lsWARNING) << "OfferCreate: saTakerGets.isNative()=" << saTakerGets.isNative(); + cLog(lsWARNING) << "OfferCreate: uPaysCurrency=" << saTakerPays.getHumanCurrency(); + cLog(lsWARNING) << "OfferCreate: uGetsCurrency=" << saTakerGets.getHumanCurrency(); SLE::pointer sleOffer = mEngine->entryCreate(ltOFFER, uLedgerIndex); @@ -448,13 +492,13 @@ TER OfferCreateTransactor::doApply() if (bPassive) sleOffer->setFlag(lsfPassive); - Log(lsINFO) << boost::str(boost::format("doOfferCreate: final terResult=%s sleOffer=%s") + cLog(lsINFO) << boost::str(boost::format("OfferCreate: final terResult=%s sleOffer=%s") % transToken(terResult) % sleOffer->getJson(0)); } } - tLog(tesSUCCESS != terResult, lsINFO) << boost::str(boost::format("doOfferCreate: final terResult=%s") % transToken(terResult)); + tLog(tesSUCCESS != terResult, lsINFO) << boost::str(boost::format("OfferCreate: final terResult=%s") % transToken(terResult)); return terResult; } diff --git a/src/cpp/ripple/OfferCreateTransactor.h b/src/cpp/ripple/OfferCreateTransactor.h index 02db25ca6..3310168c6 100644 --- a/src/cpp/ripple/OfferCreateTransactor.h +++ b/src/cpp/ripple/OfferCreateTransactor.h @@ -19,4 +19,4 @@ public: TER doApply(); }; - +// vim:ts=4 diff --git a/src/cpp/ripple/Operation.h b/src/cpp/ripple/Operation.h index 5c0c6b8d1..6e0c75043 100644 --- a/src/cpp/ripple/Operation.h +++ b/src/cpp/ripple/Operation.h @@ -305,7 +305,7 @@ public: bool work(Interpreter* interpreter) { Data::pointer index=interpreter->popStack(); - if(index->isInt32()) + if(index->isInt32()) { interpreter->pushStack( interpreter->getContractData(index->getInt())); return(true); @@ -315,4 +315,6 @@ public: } }; -} \ No newline at end of file +} + +// vim:ts=4 diff --git a/src/cpp/ripple/OrderBook.h b/src/cpp/ripple/OrderBook.h index e143589f4..b4556f01d 100644 --- a/src/cpp/ripple/OrderBook.h +++ b/src/cpp/ripple/OrderBook.h @@ -16,6 +16,7 @@ class OrderBook OrderBook(SerializedLedgerEntry::pointer ledgerEntry); // For accounts in a ledger public: typedef boost::shared_ptr pointer; + typedef const boost::shared_ptr& ref; // returns NULL if ledgerEntry doesn't point to an order // if ledgerEntry is an Order it creates the OrderBook this order would live in @@ -29,6 +30,6 @@ public: // 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 +// vim:ts=4 diff --git a/src/cpp/ripple/OrderBookDB.cpp b/src/cpp/ripple/OrderBookDB.cpp index ca06acd9b..192cab703 100644 --- a/src/cpp/ripple/OrderBookDB.cpp +++ b/src/cpp/ripple/OrderBookDB.cpp @@ -45,7 +45,7 @@ void OrderBookDB::getBooks(const uint160& issuerID, const uint160& currencyID, s { if( mIssuerMap.find(issuerID) == mIssuerMap.end() ) { - BOOST_FOREACH(OrderBook::pointer book, mIssuerMap[issuerID]) + BOOST_FOREACH(OrderBook::ref book, mIssuerMap[issuerID]) { if(book->getCurrencyIn()==currencyID) { diff --git a/src/cpp/ripple/OrderBookDB.h b/src/cpp/ripple/OrderBookDB.h index 206898dad..ded3562ae 100644 --- a/src/cpp/ripple/OrderBookDB.h +++ b/src/cpp/ripple/OrderBookDB.h @@ -27,4 +27,6 @@ public: // returns the best rate we can find float getPrice(uint160& currencyIn,uint160& currencyOut); -}; \ No newline at end of file +}; + +// vim:ts=4 diff --git a/src/cpp/ripple/ParameterTable.cpp b/src/cpp/ripple/ParameterTable.cpp index cb61a064b..200fc1d60 100644 --- a/src/cpp/ripple/ParameterTable.cpp +++ b/src/cpp/ripple/ParameterTable.cpp @@ -81,7 +81,7 @@ bool ParameterNode::addNode(const std::string& name, Parameter::ref node) Json::Value ParameterNode::getValue(int i) const { Json::Value v(Json::objectValue); - typedef std::pair string_ref_pair; + typedef std::map::value_type string_ref_pair; BOOST_FOREACH(const string_ref_pair& it, mChildren) { v[it.first] = it.second->getValue(i); @@ -95,7 +95,7 @@ bool ParameterNode::setValue(const Json::Value& value, Json::Value& error) error["error"] = "Cannot end on an inner node"; Json::Value nodes(Json::arrayValue); - typedef std::pair string_ref_pair; + typedef std::map::value_type string_ref_pair; BOOST_FOREACH(const string_ref_pair& it, mChildren) { nodes.append(it.first); diff --git a/src/cpp/ripple/Pathfinder.cpp b/src/cpp/ripple/Pathfinder.cpp index 0f772bf62..674c811d0 100644 --- a/src/cpp/ripple/Pathfinder.cpp +++ b/src/cpp/ripple/Pathfinder.cpp @@ -268,8 +268,8 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax } else if (!speEnd.mCurrencyID) { - // Last element is for XRP continue with qualifying books. - BOOST_FOREACH(OrderBook::pointer book, mOrderBook.getXRPInBooks()) + // Last element is for XRP, continue with qualifying books. + BOOST_FOREACH(OrderBook::ref book, mOrderBook.getXRPInBooks()) { // XXX Don't allow looping through same order books. @@ -298,19 +298,42 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax } else { - // Last element is for non-XRP continue by adding ripple lines and order books. + // Last element is for non-XRP, continue by adding ripple lines and order books. // Create new paths for each outbound account not already in the path. AccountItems rippleLines(speEnd.mAccountID, mLedger, AccountItem::pointer(new RippleState())); - BOOST_FOREACH(AccountItem::pointer item, rippleLines.getItems()) + BOOST_FOREACH(AccountItem::ref item, rippleLines.getItems()) { - RippleState* line=(RippleState*)item.get(); + RippleState* rspEntry = (RippleState*) item.get(); - if (!spPath.hasSeen(line->getAccountIDPeer().getAccountID())) + if (spPath.hasSeen(rspEntry->getAccountIDPeer().getAccountID())) { + // Peer is in path already. Ignore it to avoid a loop. + cLog(lsDEBUG) << + boost::str(boost::format("findPaths: SEEN: %s/%s --> %s/%s") + % RippleAddress::createHumanAccountID(speEnd.mAccountID) + % STAmount::createHumanCurrency(speEnd.mCurrencyID) + % RippleAddress::createHumanAccountID(rspEntry->getAccountIDPeer().getAccountID()) + % STAmount::createHumanCurrency(speEnd.mCurrencyID)); + } + else if (!rspEntry->getBalance().isPositive() // No IOUs to send. + && (!rspEntry->getLimitPeer() // Peer does not extend credit. + || *rspEntry->getBalance().negate() >= rspEntry->getLimitPeer())) // No credit left. + { + // Path has no credit left. Ignore it. + cLog(lsDEBUG) << + boost::str(boost::format("findPaths: No credit: %s/%s --> %s/%s") + % RippleAddress::createHumanAccountID(speEnd.mAccountID) + % STAmount::createHumanCurrency(speEnd.mCurrencyID) + % RippleAddress::createHumanAccountID(rspEntry->getAccountIDPeer().getAccountID()) + % STAmount::createHumanCurrency(speEnd.mCurrencyID)); + } + else + { + // Can transmit IOUs. STPath new_path(spPath); - STPathElement new_ele(line->getAccountIDPeer().getAccountID(), + STPathElement new_ele(rspEntry->getAccountIDPeer().getAccountID(), speEnd.mCurrencyID, uint160()); @@ -318,7 +341,7 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax boost::str(boost::format("findPaths: %s/%s --> %s/%s") % RippleAddress::createHumanAccountID(speEnd.mAccountID) % STAmount::createHumanCurrency(speEnd.mCurrencyID) - % RippleAddress::createHumanAccountID(line->getAccountIDPeer().getAccountID()) + % RippleAddress::createHumanAccountID(rspEntry->getAccountIDPeer().getAccountID()) % STAmount::createHumanCurrency(speEnd.mCurrencyID)); new_path.mPath.push_back(new_ele); @@ -326,15 +349,6 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax bContinued = true; } - else - { - cLog(lsDEBUG) << - boost::str(boost::format("findPaths: SEEN: %s/%s --> %s/%s") - % RippleAddress::createHumanAccountID(speEnd.mAccountID) - % STAmount::createHumanCurrency(speEnd.mCurrencyID) - % RippleAddress::createHumanAccountID(line->getAccountIDPeer().getAccountID()) - % STAmount::createHumanCurrency(speEnd.mCurrencyID)); - } } // Every book that wants the source currency. @@ -342,7 +356,7 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax mOrderBook.getBooks(spPath.mCurrentAccount, spPath.mCurrencyID, books); - BOOST_FOREACH(OrderBook::pointer book,books) + BOOST_FOREACH(OrderBook::ref book,books) { STPath new_path(spPath); STPathElement new_ele(uint160(), book->getCurrencyOut(), book->getIssuerOut()); @@ -448,6 +462,8 @@ bool Pathfinder::findPaths(const unsigned int iMaxSteps, const unsigned int iMax cLog(lsDEBUG) << boost::str(boost::format("findPaths: no ledger")); } + cLog(lsDEBUG) << boost::str(boost::format("findPaths< bFound=%d") % bFound); + return bFound; } @@ -457,7 +473,7 @@ 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) + BOOST_FOREACH(PathOption::ref pathOption,mCompletePaths) { retPathSet.addPath(pathOption->mPath); count++; @@ -480,7 +496,7 @@ void Pathfinder::addOptions(PathOption::pointer tail) { if (!tail->mCurrencyID) { // source XRP - BOOST_FOREACH(OrderBook::pointer book, mOrderBook.getXRPInBooks()) + BOOST_FOREACH(OrderBook::ref book, mOrderBook.getXRPInBooks()) { PathOption::pointer pathOption(new PathOption(tail)); @@ -495,7 +511,7 @@ void Pathfinder::addOptions(PathOption::pointer tail) else { // ripple RippleLines rippleLines(tail->mCurrentAccount); - BOOST_FOREACH(RippleState::pointer line,rippleLines.getLines()) + BOOST_FOREACH(RippleState::ref line,rippleLines.getLines()) { // TODO: make sure we can move in the correct direction STAmount balance=line->getBalance(); @@ -516,7 +532,7 @@ void Pathfinder::addOptions(PathOption::pointer tail) std::vector books; mOrderBook.getBooks(tail->mCurrentAccount, tail->mCurrencyID, books); - BOOST_FOREACH(OrderBook::pointer book,books) + BOOST_FOREACH(OrderBook::ref book,books) { PathOption::pointer pathOption(new PathOption(tail)); @@ -549,4 +565,28 @@ void Pathfinder::addPathOption(PathOption::pointer pathOption) } #endif +boost::unordered_set usAccountSourceCurrencies(const RippleAddress& raAccountID, Ledger::ref lrLedger) +{ + boost::unordered_set usCurrencies; + + // List of ripple lines. + AccountItems rippleLines(raAccountID.getAccountID(), lrLedger, AccountItem::pointer(new RippleState())); + + BOOST_FOREACH(AccountItem::ref item, rippleLines.getItems()) + { + RippleState* rspEntry = (RippleState*) item.get(); + STAmount saBalance = rspEntry->getBalance(); + + // Filter out non + if (saBalance.isPositive() // Have IOUs to send. + || (rspEntry->getLimitPeer() // Peer extends credit. + && *saBalance.negate() < rspEntry->getLimitPeer())) // Credit left. + { + // Path has no credit left. Ignore it. + usCurrencies.insert(saBalance.getCurrency()); + } + } + + return usCurrencies; +} // vim:ts=4 diff --git a/src/cpp/ripple/Pathfinder.h b/src/cpp/ripple/Pathfinder.h index 27f8d4480..5682778e4 100644 --- a/src/cpp/ripple/Pathfinder.h +++ b/src/cpp/ripple/Pathfinder.h @@ -18,6 +18,7 @@ class PathOption { public: typedef boost::shared_ptr pointer; + typedef const boost::shared_ptr& ref; STPath mPath; bool mCorrectCurrency; // for the sorting @@ -62,6 +63,8 @@ public: bool bDefaultPath(const STPath& spPath); }; + +boost::unordered_set usAccountSourceCurrencies(const RippleAddress& raAccountID, Ledger::ref lrLedger); #endif // vim:ts=4 diff --git a/src/cpp/ripple/PaymentTransactor.cpp b/src/cpp/ripple/PaymentTransactor.cpp index 50e48501c..6a6ffc8d7 100644 --- a/src/cpp/ripple/PaymentTransactor.cpp +++ b/src/cpp/ripple/PaymentTransactor.cpp @@ -1,9 +1,12 @@ #include "PaymentTransactor.h" #include "Config.h" #include "RippleCalc.h" +#include "Application.h" #define RIPPLE_PATHS_MAX 3 +SETUP_LOG(); + TER PaymentTransactor::doApply() { // Ripple if source or destination is non-native or if there are paths. @@ -22,38 +25,39 @@ TER PaymentTransactor::doApply() : STAmount(saDstAmount.getCurrency(), mTxnAccountID, saDstAmount.getMantissa(), saDstAmount.getExponent(), saDstAmount.isNegative()); const uint160 uSrcCurrency = saMaxAmount.getCurrency(); const uint160 uDstCurrency = saDstAmount.getCurrency(); + const bool bXRPDirect = uSrcCurrency.isZero() && uDstCurrency.isZero(); - Log(lsINFO) << boost::str(boost::format("doPayment> saMaxAmount=%s saDstAmount=%s") + cLog(lsINFO) << boost::str(boost::format("Payment> saMaxAmount=%s saDstAmount=%s") % saMaxAmount.getFullText() % saDstAmount.getFullText()); if (uTxFlags & tfPaymentMask) { - Log(lsINFO) << "doPayment: Malformed transaction: Invalid flags set."; + cLog(lsINFO) << "Payment: Malformed transaction: Invalid flags set."; return temINVALID_FLAG; } else if (!uDstAccountID) { - Log(lsINFO) << "doPayment: Malformed transaction: Payment destination account not specified."; + cLog(lsINFO) << "Payment: Malformed transaction: Payment destination account not specified."; return temDST_NEEDED; } else if (bMax && !saMaxAmount.isPositive()) { - Log(lsINFO) << "doPayment: Malformed transaction: bad max amount: " << saMaxAmount.getFullText(); + cLog(lsINFO) << "Payment: Malformed transaction: bad max amount: " << saMaxAmount.getFullText(); return temBAD_AMOUNT; } else if (!saDstAmount.isPositive()) { - Log(lsINFO) << "doPayment: Malformed transaction: bad dst amount: " << saDstAmount.getFullText(); + cLog(lsINFO) << "Payment: Malformed transaction: bad dst amount: " << saDstAmount.getFullText(); return temBAD_AMOUNT; } else if (mTxnAccountID == uDstAccountID && uSrcCurrency == uDstCurrency && !bPaths) { - Log(lsINFO) << boost::str(boost::format("doPayment: Malformed transaction: Redundant transaction: src=%s, dst=%s, src_cur=%s, dst_cur=%s") + cLog(lsINFO) << boost::str(boost::format("Payment: Malformed transaction: Redundant transaction: src=%s, dst=%s, src_cur=%s, dst_cur=%s") % mTxnAccountID.ToString() % uDstAccountID.ToString() % uSrcCurrency.ToString() @@ -61,13 +65,41 @@ TER PaymentTransactor::doApply() return temREDUNDANT; } - else if (bMax - && ((saMaxAmount == saDstAmount && saMaxAmount.getCurrency() == saDstAmount.getCurrency()) - || (saDstAmount.isNative() && saMaxAmount.isNative()))) + else if (bMax && saMaxAmount == saDstAmount && saMaxAmount.getCurrency() == saDstAmount.getCurrency()) { - Log(lsINFO) << "doPayment: Malformed transaction: bad SendMax."; + cLog(lsINFO) << "Payment: Malformed transaction: Redundant SendMax."; - return temINVALID; + return temREDUNDANT_SEND_MAX; + } + else if (bXRPDirect && bMax) + { + cLog(lsINFO) << "Payment: Malformed transaction: SendMax specified for XRP to XRP."; + + return temBAD_SEND_XRP_MAX; + } + else if (bXRPDirect && bPaths) + { + cLog(lsINFO) << "Payment: Malformed transaction: Paths specified for XRP to XRP."; + + return temBAD_SEND_XRP_PATHS; + } + else if (bXRPDirect && bPartialPayment) + { + cLog(lsINFO) << "Payment: Malformed transaction: Partial payment specified for XRP to XRP."; + + return temBAD_SEND_XRP_PARTIAL; + } + else if (bXRPDirect && bLimitQuality) + { + cLog(lsINFO) << "Payment: Malformed transaction: Limit quality specified for XRP to XRP."; + + return temBAD_SEND_XRP_LIMIT; + } + else if (bXRPDirect && bNoRippleDirect) + { + cLog(lsINFO) << "Payment: Malformed transaction: No ripple direct specified for XRP to XRP."; + + return temBAD_SEND_XRP_NO_DIRECT; } SLE::pointer sleDst = mEngine->entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uDstAccountID)); @@ -77,18 +109,25 @@ TER PaymentTransactor::doApply() if (!saDstAmount.isNative()) { - Log(lsINFO) << "doPayment: Delay transaction: Destination account does not exist."; + cLog(lsINFO) << "Payment: Delay transaction: Destination account does not exist."; // Another transaction could create the account and then this transaction would succeed. - return terNO_DST; + return tecNO_DST; } - else if (isSetBit(mParams, tapOPEN_LEDGER) // Ledger is not final, we can vote. - && saDstAmount.getNValue() < theConfig.FEE_ACCOUNT_RESERVE) // Reserve is not scaled by fee. + else if (isSetBit(mParams, tapOPEN_LEDGER) && bPartialPayment) { - Log(lsINFO) << "doPayment: Delay transaction: Destination account does not exist. Insufficent payment to create account."; + cLog(lsINFO) << "Payment: Delay transaction: Partial payment not allowed to create account."; + // Make retry work smaller, by rejecting this. // Another transaction could create the account and then this transaction would succeed. - return terNO_DST_INSUF_XRP; + return telNO_DST_PARTIAL; + } + else if (saDstAmount.getNValue() < mEngine->getLedger()->getReserve(0)) // Reserve is not scaled by load. + { + cLog(lsINFO) << "Payment: Delay transaction: Destination account does not exist. Insufficent payment to create account."; + + // Another transaction could create the account and then this transaction would succeed. + return tecNO_DST_INSUF_XRP; } // Create the account. @@ -97,6 +136,12 @@ TER PaymentTransactor::doApply() sleDst->setFieldAccount(sfAccount, uDstAccountID); sleDst->setFieldU32(sfSequence, 1); } + else if ((sleDst->getFlags() & lsfRequireDestTag) && !mTxn.isFieldPresent(sfDestinationTag)) + { + cLog(lsINFO) << "Payment: Malformed transaction: DestinationTag required."; + + return temDST_TAG_NEEDED; + } else { mEngine->entryModify(sleDst); @@ -138,34 +183,27 @@ TER PaymentTransactor::doApply() const STAmount saSrcXRPBalance = mTxnAccount->getFieldAmount(sfBalance); const uint32 uOwnerCount = mTxnAccount->getFieldU32(sfOwnerCount); - const uint64 uReserve = theConfig.FEE_ACCOUNT_RESERVE+uOwnerCount*theConfig.FEE_OWNER_RESERVE; + const uint64 uReserve = mEngine->getLedger()->getReserve(uOwnerCount); + STAmount saPaid = mTxn.getTransactionFee(); - // Make sure have enough reserve to send. - if (isSetBit(mParams, tapOPEN_LEDGER) // Ledger is not final, we can vote. - && saSrcXRPBalance < saDstAmount + uReserve) // Reserve is not scaled by fee. + // Make sure have enough reserve to send. Allow final spend to use reserve for fee. + if (saSrcXRPBalance + saPaid < saDstAmount + uReserve) // Reserve is not scaled by fee. { // Vote no. However, transaction might succeed, if applied in a different order. - Log(lsINFO) << ""; - Log(lsINFO) << boost::str(boost::format("doPayment: Delay transaction: Insufficient funds: %s / %s (%d)") + cLog(lsINFO) << ""; + cLog(lsINFO) << boost::str(boost::format("Payment: Delay transaction: Insufficient funds: %s / %s (%d)") % saSrcXRPBalance.getText() % (saDstAmount + uReserve).getText() % uReserve); - terResult = terUNFUNDED; + terResult = tecUNFUNDED_PAYMENT; } else { mTxnAccount->setFieldAmount(sfBalance, saSrcXRPBalance - saDstAmount); + sleDst->setFieldAmount(sfBalance, sleDst->getFieldAmount(sfBalance) + saDstAmount); // re-arm the password change fee if we can and need to - if ( (sleDst->getFlags() & lsfPasswordSpent) && - (saDstAmount > theConfig.FEE_DEFAULT) ) - { - sleDst->setFieldAmount(sfBalance, sleDst->getFieldAmount(sfBalance) + saDstAmount-theConfig.FEE_DEFAULT); + if ((sleDst->getFlags() & lsfPasswordSpent)) sleDst->clearFlag(lsfPasswordSpent); - } - else - { - sleDst->setFieldAmount(sfBalance, sleDst->getFieldAmount(sfBalance) + saDstAmount); - } terResult = tesSUCCESS; } @@ -176,7 +214,7 @@ TER PaymentTransactor::doApply() if (transResultInfo(terResult, strToken, strHuman)) { - Log(lsINFO) << boost::str(boost::format("doPayment: %s: %s") % strToken % strHuman); + cLog(lsINFO) << boost::str(boost::format("Payment: %s: %s") % strToken % strHuman); } else { diff --git a/src/cpp/ripple/Peer.cpp b/src/cpp/ripple/Peer.cpp index 571246206..7c0590dbf 100644 --- a/src/cpp/ripple/Peer.cpp +++ b/src/cpp/ripple/Peer.cpp @@ -24,12 +24,15 @@ DECLARE_INSTANCE(Peer); // Node has this long to verify its identity from connection accepted or connection attempt. #define NODE_VERIFY_SECONDS 15 -Peer::Peer(boost::asio::io_service& io_service, boost::asio::ssl::context& ctx, uint64 peerID) : +Peer::Peer(boost::asio::io_service& io_service, boost::asio::ssl::context& ctx, uint64 peerID, bool inbound) : + mInbound(inbound), mHelloed(false), mDetaching(false), + mActive(true), + mCluster(false), mPeerId(peerID), mSocketSsl(io_service, ctx), - mVerifyTimer(io_service) + mActivityTimer(io_service) { cLog(lsDEBUG) << "CREATING PEER: " << ADDRESS(this); } @@ -41,7 +44,7 @@ void Peer::handleWrite(const boost::system::error_code& error, size_t bytes_tran // std::cerr << "Peer::handleWrite bytes: "<< bytes_transferred << std::endl; #endif - boost::recursive_mutex::scoped_lock sl(theApp->getMasterLock()); + boost::recursive_mutex::scoped_lock sl(ioMutex); mSendingPacket.reset(); @@ -79,6 +82,8 @@ void Peer::setIpPort(const std::string& strIP, int iPort) void Peer::detach(const char *rsn) { + boost::recursive_mutex::scoped_lock sl(ioMutex); + if (!mDetaching) { mDetaching = true; // Race is ok. @@ -91,7 +96,7 @@ void Peer::detach(const char *rsn) mSendQ.clear(); - (void) mVerifyTimer.cancel(); + (void) mActivityTimer.cancel(); mSocketSsl.async_shutdown(boost::bind(&Peer::handleShutdown, shared_from_this(), boost::asio::placeholders::error)); if (mNodePublic.isValid()) @@ -168,8 +173,8 @@ void Peer::connect(const std::string& strIp, int iPort) } else { - mVerifyTimer.expires_from_now(boost::posix_time::seconds(NODE_VERIFY_SECONDS), err); - mVerifyTimer.async_wait(boost::bind(&Peer::handleVerifyTimer, shared_from_this(), + mActivityTimer.expires_from_now(boost::posix_time::seconds(NODE_VERIFY_SECONDS), err); + mActivityTimer.async_wait(boost::bind(&Peer::handleVerifyTimer, shared_from_this(), boost::asio::placeholders::error)); if (err) @@ -225,6 +230,8 @@ void Peer::handleConnect(const boost::system::error_code& error, boost::asio::ip { cLog(lsINFO) << "Connect peer: success."; + boost::recursive_mutex::scoped_lock sl(ioMutex); + mSocketSsl.set_verify_mode(boost::asio::ssl::verify_none); mSocketSsl.async_handshake(boost::asio::ssl::stream::client, @@ -246,12 +253,15 @@ void Peer::connected(const boost::system::error_code& error) if (iPort == SYSTEM_PEER_PORT) //TODO: Why are you doing this? iPort = -1; + boost::recursive_mutex::scoped_lock sl(ioMutex); + if (!error) { // Not redundant ip and port, handshake, and start. cLog(lsINFO) << "Peer: Inbound: Accepted: " << ADDRESS(this) << ": " << strIp << " " << iPort; + mSocketSsl.set_verify_mode(boost::asio::ssl::verify_none); mSocketSsl.async_handshake(boost::asio::ssl::stream::server, @@ -266,7 +276,7 @@ void Peer::connected(const boost::system::error_code& error) } void Peer::sendPacketForce(const PackedMessage::pointer& packet) -{ +{ // must hold I/O mutex if (!mDetaching) { mSendingPacket = packet; @@ -280,6 +290,8 @@ void Peer::sendPacketForce(const PackedMessage::pointer& packet) void Peer::sendPacket(const PackedMessage::pointer& packet) { + boost::recursive_mutex::scoped_lock sl(ioMutex); + if (packet) { if (mSendingPacket) @@ -295,6 +307,8 @@ void Peer::sendPacket(const PackedMessage::pointer& packet) void Peer::startReadHeader() { + boost::recursive_mutex::scoped_lock sl(ioMutex); + if (!mDetaching) { mReadbuf.clear(); @@ -311,6 +325,8 @@ void Peer::startReadBody(unsigned msg_len) // bytes. Expand it to fit in the body as well, and start async // read into the body. + boost::recursive_mutex::scoped_lock sl(ioMutex); + if (!mDetaching) { mReadbuf.resize(HEADER_SIZE + msg_len); @@ -322,6 +338,8 @@ void Peer::startReadBody(unsigned msg_len) void Peer::handleReadHeader(const boost::system::error_code& error) { + boost::recursive_mutex::scoped_lock sl(ioMutex); + if (mDetaching) { // Drop data or error if detaching. @@ -347,26 +365,28 @@ void Peer::handleReadHeader(const boost::system::error_code& error) void Peer::handleReadBody(const boost::system::error_code& error) { - if (mDetaching) { - // Drop data or error if detaching. - nothing(); - } - else if (!error) - { - processReadBuffer(); - startReadHeader(); - } - else - { - cLog(lsINFO) << "Peer: Body: Error: " << ADDRESS(this) << ": " << error.category().name() << ": " << error.message() << ": " << error; - boost::recursive_mutex::scoped_lock sl(theApp->getMasterLock()); - detach("hrb"); + boost::recursive_mutex::scoped_lock sl(ioMutex); + + if (mDetaching) + { + return; + } + else if (error) + { + cLog(lsINFO) << "Peer: Body: Error: " << ADDRESS(this) << ": " << error.category().name() << ": " << error.message() << ": " << error; + boost::recursive_mutex::scoped_lock sl(theApp->getMasterLock()); + detach("hrb"); + return; + } } + + processReadBuffer(); + startReadHeader(); } void Peer::processReadBuffer() -{ +{ // must not hold peer lock int type = PackedMessage::getType(mReadbuf); #ifdef DEBUG // std::cerr << "PRB(" << type << "), len=" << (mReadbuf.size()-HEADER_SIZE) << std::endl; @@ -611,8 +631,8 @@ void Peer::recvHello(ripple::TMHello& packet) { bool bDetach = true; - // Cancel verification timeout. - (void) mVerifyTimer.cancel(); + // Cancel verification timeout. - FIXME Start ping/pong timer + (void) mActivityTimer.cancel(); uint32 ourTime = theApp->getOPs().getNetworkTimeNC(); uint32 minTime = ourTime - 20; @@ -627,7 +647,14 @@ void Peer::recvHello(ripple::TMHello& packet) } #endif - if (packet.has_nettime() && ((packet.nettime() < minTime) || (packet.nettime() > maxTime))) + if ((packet.has_testnet() && packet.testnet()) != theConfig.TESTNET) + { + // Format: actual/requested. + cLog(lsINFO) << boost::str(boost::format("Recv(Hello): Network mismatch: %d/%d") + % packet.testnet() + % theConfig.TESTNET); + } + else if (packet.has_nettime() && ((packet.nettime() < minTime) || (packet.nettime() > maxTime))) { if (packet.nettime() > maxTime) { @@ -659,6 +686,13 @@ void Peer::recvHello(ripple::TMHello& packet) << "Peer speaks version " << (packet.protoversion() >> 16) << "." << (packet.protoversion() & 0xFF); mHello = packet; + if (theApp->getUNL().nodeInCluster(mNodePublic)) + { + mCluster = true; + mLoad.setPrivileged(); + } + if (isOutbound()) + mLoad.setOutbound(); if (mClientConnect) { @@ -1081,7 +1115,7 @@ void Peer::recvGetObjectByHash(ripple::TMGetObjectByHash& packet) { // this is a query ripple::TMGetObjectByHash reply; - reply.clear_query(); + reply.set_query(false); if (packet.has_seq()) reply.set_seq(packet.seq()); reply.set_type(packet.type()); @@ -1104,20 +1138,64 @@ void Peer::recvGetObjectByHash(ripple::TMGetObjectByHash& packet) newObj.set_data(&hObj->getData().front(), hObj->getData().size()); if (obj.has_nodeid()) newObj.set_index(obj.nodeid()); + if (!reply.has_seq() && (hObj->getIndex() != 0)) + reply.set_seq(hObj->getIndex()); } } } - cLog(lsDEBUG) << "GetObjByHash query: had " << reply.objects_size() << " of " << packet.objects_size(); - sendPacket(boost::make_shared(packet, ripple::mtGET_OBJECTS)); + cLog(lsTRACE) << "GetObjByHash had " << reply.objects_size() << " of " << packet.objects_size() + << " for " << getIP(); + sendPacket(boost::make_shared(reply, ripple::mtGET_OBJECTS)); } else { // this is a reply - // WRITEME + uint32 seq = packet.has_seq() ? packet.seq() : 0; + HashedObjectType type; + switch (packet.type()) + { + case ripple::TMGetObjectByHash::otLEDGER: type = hotLEDGER; break; + case ripple::TMGetObjectByHash::otTRANSACTION: type = hotTRANSACTION; break; + case ripple::TMGetObjectByHash::otSTATE_NODE: type = hotACCOUNT_NODE; break; + case ripple::TMGetObjectByHash::otTRANSACTION_NODE: type = hotTRANSACTION_NODE; break; + default: type = hotUNKNOWN; + } + for (int i = 0; i < packet.objects_size(); ++i) + { + const ripple::TMIndexedObject& obj = packet.objects(i); + if (obj.has_hash() && (obj.hash().size() == (256/8))) + { + uint256 hash; + memcpy(hash.begin(), obj.hash().data(), 256 / 8); + if (theApp->getOPs().isWantedHash(hash, true)) + { + std::vector data(obj.data().begin(), obj.data().end()); + if (Serializer::getSHA512Half(data) != hash) + { + cLog(lsWARNING) << "Bad hash in data from peer"; + theApp->getOPs().addWantedHash(hash); + punishPeer(LT_BadData); + } + else + theApp->getHashedObjectStore().store(type, seq, data, hash); + } + else + cLog(lsWARNING) << "Received unwanted hash " << getIP() << " " << hash; + } + } } } void Peer::recvPing(ripple::TMPing& packet) { + if (packet.type() == ripple::TMPing::ptPING) + { + packet.set_type(ripple::TMPing::ptPONG); + sendPacket(boost::make_shared(packet, ripple::mtPING)); + } + else if (packet.type() == ripple::TMPing::ptPONG) + { + mActive = true; + } } void Peer::recvErrorMessage(ripple::TMErrorMsg& packet) @@ -1249,6 +1327,8 @@ void Peer::recvGetLedger(ripple::TMGetLedger& packet) if (packet.has_requestcookie()) reply.set_requestcookie(packet.requestcookie()); + std::string logMe; + if (packet.itype() == ripple::liTS_CANDIDATE) { // Request is for a transaction candidate set cLog(lsINFO) << "Received request for TX candidate set data " << getIP(); @@ -1264,7 +1344,8 @@ void Peer::recvGetLedger(ripple::TMGetLedger& packet) if (!map) { if (packet.has_querytype() && !packet.has_requestcookie()) - { + { // FIXME: Don't relay requests for older ledgers we would acquire + // (if we don't have them, we can't get them) cLog(lsINFO) << "Trying to route TX set request"; std::vector peerList = theApp->getConnectionPool().getPeerVector(); std::vector usablePeers; @@ -1296,7 +1377,7 @@ void Peer::recvGetLedger(ripple::TMGetLedger& packet) } else { // Figure out what ledger they want - cLog(lsINFO) << "Received request for ledger data " << getIP(); + cLog(lsTRACE) << "Received request for ledger data " << getIP(); Ledger::pointer ledger; if (packet.has_ledgerhash()) { @@ -1308,12 +1389,12 @@ void Peer::recvGetLedger(ripple::TMGetLedger& packet) return; } memcpy(ledgerhash.begin(), packet.ledgerhash().data(), 32); + logMe += "LedgerHash:"; logMe += ledgerhash.GetHex(); ledger = theApp->getLedgerMaster().getLedgerByHash(ledgerhash); - tLog(!ledger, lsINFO) << "Don't have ledger " << ledgerhash; + tLog(!ledger, lsDEBUG) << "Don't have ledger " << ledgerhash; if (!ledger && (packet.has_querytype() && !packet.has_requestcookie())) { - cLog(lsINFO) << "Trying to route ledger request"; std::vector peerList = theApp->getConnectionPool().getPeerVector(); std::vector usablePeers; BOOST_FOREACH(Peer::ref peer, peerList) @@ -1323,7 +1404,7 @@ void Peer::recvGetLedger(ripple::TMGetLedger& packet) } if (usablePeers.empty()) { - cLog(lsINFO) << "Unable to route ledger request"; + cLog(lsDEBUG) << "Unable to route ledger request"; return; } Peer::ref selectedPeer = usablePeers[rand() % usablePeers.size()]; @@ -1336,7 +1417,7 @@ void Peer::recvGetLedger(ripple::TMGetLedger& packet) else if (packet.has_ledgerseq()) { ledger = theApp->getLedgerMaster().getLedgerBySeq(packet.ledgerseq()); - tLog(!ledger, lsINFO) << "Don't have ledger " << packet.ledgerseq(); + tLog(!ledger, lsDEBUG) << "Don't have ledger " << packet.ledgerseq(); } else if (packet.has_ltype() && (packet.ltype() == ripple::ltCURRENT)) ledger = theApp->getLedgerMaster().getCurrentLedger(); @@ -1360,8 +1441,6 @@ void Peer::recvGetLedger(ripple::TMGetLedger& packet) { if (ledger) Log(lsWARNING) << "Ledger has wrong sequence"; - else - Log(lsWARNING) << "Can't find the ledger they want"; } return; } @@ -1405,9 +1484,15 @@ void Peer::recvGetLedger(ripple::TMGetLedger& packet) } if (packet.itype() == ripple::liTX_NODE) + { map = ledger->peekTransactionMap(); + logMe += " TX:"; logMe += map->getHash().GetHex(); + } else if (packet.itype() == ripple::liAS_NODE) + { map = ledger->peekAccountStateMap(); + logMe += " AS:"; logMe += map->getHash().GetHex(); + } } if ((!map) || (packet.nodeids_size() == 0)) @@ -1417,6 +1502,7 @@ void Peer::recvGetLedger(ripple::TMGetLedger& packet) return; } + cLog(lsDEBUG) << "Request: " << logMe; for(int i = 0; i < packet.nodeids().size(); ++i) { SHAMapNode mn(packet.nodeids(i).data(), packet.nodeids(i).size()); @@ -1428,24 +1514,44 @@ void Peer::recvGetLedger(ripple::TMGetLedger& packet) } std::vector nodeIDs; std::list< std::vector > rawNodes; - if(map->getNodeFat(mn, nodeIDs, rawNodes, fatRoot, fatLeaves)) + try { - assert(nodeIDs.size() == rawNodes.size()); - cLog(lsDEBUG) << "getNodeFat got " << rawNodes.size() << " nodes"; - std::vector::iterator nodeIDIterator; - std::list< std::vector >::iterator rawNodeIterator; - for(nodeIDIterator = nodeIDs.begin(), rawNodeIterator = rawNodes.begin(); - nodeIDIterator != nodeIDs.end(); ++nodeIDIterator, ++rawNodeIterator) + if(map->getNodeFat(mn, nodeIDs, rawNodes, fatRoot, fatLeaves)) { - Serializer nID(33); - nodeIDIterator->addIDRaw(nID); - ripple::TMLedgerNode* node = reply.add_nodes(); - node->set_nodeid(nID.getDataPtr(), nID.getLength()); - node->set_nodedata(&rawNodeIterator->front(), rawNodeIterator->size()); + assert(nodeIDs.size() == rawNodes.size()); + cLog(lsTRACE) << "getNodeFat got " << rawNodes.size() << " nodes"; + std::vector::iterator nodeIDIterator; + std::list< std::vector >::iterator rawNodeIterator; + for(nodeIDIterator = nodeIDs.begin(), rawNodeIterator = rawNodes.begin(); + nodeIDIterator != nodeIDs.end(); ++nodeIDIterator, ++rawNodeIterator) + { + Serializer nID(33); + nodeIDIterator->addIDRaw(nID); + ripple::TMLedgerNode* node = reply.add_nodes(); + node->set_nodeid(nID.getDataPtr(), nID.getLength()); + node->set_nodedata(&rawNodeIterator->front(), rawNodeIterator->size()); + } } + else + cLog(lsWARNING) << "getNodeFat returns false"; + } + catch (std::exception& e) + { + std::string info; + if (packet.itype() == ripple::liTS_CANDIDATE) + info = "TS candidate"; + else if (packet.itype() == ripple::liBASE) + info = "Ledger base"; + else if (packet.itype() == ripple::liTX_NODE) + info = "TX node"; + else if (packet.itype() == ripple::liAS_NODE) + info = "AS node"; + + if (!packet.has_ledgerhash()) + info += ", no hash specified"; + + cLog(lsWARNING) << "getNodeFat( " << mn <<") throws exception: " << info; } - else - cLog(lsWARNING) << "getNodeFat returns false"; } PackedMessage::pointer oPacket = boost::make_shared(reply, ripple::mtLEDGER_DATA); sendPacket(oPacket); @@ -1554,6 +1660,8 @@ void Peer::addTxSet(const uint256& hash) // (both sides get the same information, neither side controls it) void Peer::getSessionCookie(std::string& strDst) { + boost::recursive_mutex::scoped_lock sl(ioMutex); + SSL* ssl = mSocketSsl.native_handle(); if (!ssl) throw std::runtime_error("No underlying connection"); @@ -1599,6 +1707,7 @@ void Peer::sendHello() h.set_nodeproof(&vchSig[0], vchSig.size()); h.set_ipv4port(theConfig.PEER_PORT); h.set_nodeprivate(theConfig.PEER_PRIVATE); + h.set_testnet(theConfig.TESTNET); Ledger::pointer closedLedger = theApp->getLedgerMaster().getClosedLedger(); if (closedLedger && closedLedger->isClosed()) @@ -1670,6 +1779,10 @@ Json::Value Peer::getJson() //ret["port"] = mIpPortConnect.second; ret["port"] = mIpPort.second; + if (mInbound) + ret["inbound"] = true; + if (mCluster) + ret["cluster"] = true; if (mHello.has_fullversion()) ret["version"] = mHello.fullversion(); diff --git a/src/cpp/ripple/Peer.h b/src/cpp/ripple/Peer.h index 79a20649f..2d3fb9527 100644 --- a/src/cpp/ripple/Peer.h +++ b/src/cpp/ripple/Peer.h @@ -33,9 +33,12 @@ public: void handleConnect(const boost::system::error_code& error, boost::asio::ip::tcp::resolver::iterator it); private: + bool mInbound; // Connection is inbound bool mClientConnect; // In process of connecting as client. bool mHelloed; // True, if hello accepted. bool mDetaching; // True, if detaching. + bool mActive; + bool mCluster; // Node in our cluster RippleAddress mNodePublic; // Node public key of peer. ipPort mIpPort; ipPort mIpPortConnect; @@ -50,20 +53,21 @@ private: boost::asio::ssl::stream mSocketSsl; - boost::asio::deadline_timer mVerifyTimer; + boost::asio::deadline_timer mActivityTimer; void handleStart(const boost::system::error_code& ecResult); void handleVerifyTimer(const boost::system::error_code& ecResult); protected: + boost::recursive_mutex ioMutex; std::vector mReadbuf; std::list mSendQ; PackedMessage::pointer mSendingPacket; ripple::TMStatusChange mLastStatus; ripple::TMHello mHello; - Peer(boost::asio::io_service& io_service, boost::asio::ssl::context& ctx, uint64 peerId); + Peer(boost::asio::io_service& io_service, boost::asio::ssl::context& ctx, uint64 peerId, bool inbound); void handleShutdown(const boost::system::error_code& error) { ; } void handleWrite(const boost::system::error_code& error, size_t bytes_transferred); @@ -115,9 +119,9 @@ public: void setIpPort(const std::string& strIP, int iPort); - static pointer create(boost::asio::io_service& io_service, boost::asio::ssl::context& ctx, uint64 id) + static pointer create(boost::asio::io_service& io_service, boost::asio::ssl::context& ctx, uint64 id, bool inbound) { - return pointer(new Peer(io_service, ctx, id)); + return pointer(new Peer(io_service, ctx, id, inbound)); } boost::asio::ssl::stream::lowest_layer_type& getSocket() @@ -142,13 +146,15 @@ public: Json::Value getJson(); bool isConnected() const { return mHelloed && !mDetaching; } + bool isInbound() const { return mInbound; } + bool isOutbound() const { return !mInbound; } uint256 getClosedLedgerHash() const { return mClosedLedgerHash; } bool hasLedger(const uint256& hash) const; bool hasTxSet(const uint256& hash) const; uint64 getPeerId() const { return mPeerId; } - RippleAddress getNodePublic() const { return mNodePublic; } + const RippleAddress& getNodePublic() const { return mNodePublic; } void cycleStatus() { mPreviousLedgerHash = mClosedLedgerHash; mClosedLedgerHash.zero(); } }; diff --git a/src/cpp/ripple/PeerDoor.cpp b/src/cpp/ripple/PeerDoor.cpp index 947dca9f6..9ce77f45e 100644 --- a/src/cpp/ripple/PeerDoor.cpp +++ b/src/cpp/ripple/PeerDoor.cpp @@ -9,6 +9,9 @@ #include "Application.h" #include "Config.h" #include "utils.h" +#include "Log.h" + +SETUP_LOG(); using namespace std; using namespace boost::asio::ip; @@ -21,7 +24,7 @@ static DH* handleTmpDh(SSL* ssl, int is_export, int iKeyLength) PeerDoor::PeerDoor(boost::asio::io_service& io_service) : mAcceptor(io_service, tcp::endpoint(address().from_string(theConfig.PEER_IP), theConfig.PEER_PORT)), - mCtx(boost::asio::ssl::context::sslv23) + mCtx(boost::asio::ssl::context::sslv23), mDelayTimer(io_service) { mCtx.set_options( boost::asio::ssl::context::default_workarounds @@ -32,7 +35,7 @@ PeerDoor::PeerDoor(boost::asio::io_service& io_service) : if (1 != SSL_CTX_set_cipher_list(mCtx.native_handle(), theConfig.PEER_SSL_CIPHER_LIST.c_str())) std::runtime_error("Error setting cipher list (no valid ciphers)."); - cerr << "Peer port: " << theConfig.PEER_IP << " " << theConfig.PEER_PORT << endl; + Log(lsINFO) << "Peer port: " << theConfig.PEER_IP << " " << theConfig.PEER_PORT; startListening(); } @@ -40,7 +43,7 @@ PeerDoor::PeerDoor(boost::asio::io_service& io_service) : void PeerDoor::startListening() { Peer::pointer new_connection = Peer::create(mAcceptor.get_io_service(), mCtx, - theApp->getConnectionPool().assignPeerId()); + theApp->getConnectionPool().assignPeerId(), true); mAcceptor.async_accept(new_connection->getSocket(), boost::bind(&PeerDoor::handleConnect, this, new_connection, @@ -50,13 +53,25 @@ void PeerDoor::startListening() void PeerDoor::handleConnect(Peer::pointer new_connection, const boost::system::error_code& error) { + bool delay = false; if (!error) { new_connection->connected(error); } - else cerr << "Error: " << error; + else + { + if (error == boost::system::errc::too_many_files_open) + delay = true; + cLog(lsERROR) << error; + } - startListening(); + if (delay) + { + mDelayTimer.expires_from_now(boost::posix_time::milliseconds(500)); + mDelayTimer.async_wait(boost::bind(&PeerDoor::startListening, this)); + } + else + startListening(); } void initSSLContext(boost::asio::ssl::context& context, diff --git a/src/cpp/ripple/PeerDoor.h b/src/cpp/ripple/PeerDoor.h index ac472ee0e..5f03c5914 100644 --- a/src/cpp/ripple/PeerDoor.h +++ b/src/cpp/ripple/PeerDoor.h @@ -18,12 +18,14 @@ class PeerDoor private: boost::asio::ip::tcp::acceptor mAcceptor; boost::asio::ssl::context mCtx; + boost::asio::deadline_timer mDelayTimer; void startListening(); void handleConnect(Peer::pointer new_connection, const boost::system::error_code& error); public: PeerDoor(boost::asio::io_service& io_service); + boost::asio::ssl::context& getSSLContext() { return mCtx; } }; #endif diff --git a/src/cpp/ripple/PubKeyCache.cpp b/src/cpp/ripple/PubKeyCache.cpp deleted file mode 100644 index d3ae59c57..000000000 --- a/src/cpp/ripple/PubKeyCache.cpp +++ /dev/null @@ -1,71 +0,0 @@ -#include "PubKeyCache.h" -#include "Application.h" - -CKey::pointer PubKeyCache::locate(const RippleAddress& id) -{ - { // is it in cache - boost::mutex::scoped_lock sl(mLock); - std::map::iterator it(mCache.find(id)); - if(it!=mCache.end()) return it->second; - } - - std::string sql="SELECT * from PubKeys WHERE ID='"; - sql.append(id.humanAccountID()); - sql.append("';'"); - std::vector data; - data.resize(66); // our public keys are actually 33 bytes - int pkSize; - - { // is it in the database - ScopedLock sl(theApp->getTxnDB()->getDBLock()); - Database* db=theApp->getTxnDB()->getDB(); - if(!db->executeSQL(sql) || !db->startIterRows()) - return CKey::pointer(); - pkSize = db->getBinary("PubKey", &(data.front()), data.size()); - db->endIterRows(); - } - data.resize(pkSize); - CKey::pointer ckp(new CKey()); - if(!ckp->SetPubKey(data)) - { - assert(false); // bad data in DB - return CKey::pointer(); - } - - { // put it in cache (okay if we race with another retriever) - boost::mutex::scoped_lock sl(mLock); - mCache.insert(std::make_pair(id, ckp)); - } - return ckp; -} - -CKey::pointer PubKeyCache::store(const RippleAddress& id, const CKey::pointer& key) -{ // stored if needed, returns cached copy (possibly the original) - { - boost::mutex::scoped_lock sl(mLock); - std::pair::iterator, bool> pit(mCache.insert(std::make_pair(id, key))); - if(!pit.second) // there was an existing key - return pit.first->second; - } - - 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; - sql.append(");"); - - ScopedLock dbl(theApp->getTxnDB()->getDBLock()); - theApp->getTxnDB()->getDB()->executeSQL(sql, true); - return key; -} - -void PubKeyCache::clear() -{ - boost::mutex::scoped_lock sl(mLock); - mCache.clear(); -} -// vim:ts=4 diff --git a/src/cpp/ripple/PubKeyCache.h b/src/cpp/ripple/PubKeyCache.h deleted file mode 100644 index 91023cfe9..000000000 --- a/src/cpp/ripple/PubKeyCache.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef __PUBKEYCACHE__ -#define __PUBKEYCACHE__ - -#include - -#include - -#include "RippleAddress.h" -#include "key.h" - -class PubKeyCache -{ -private: - boost::mutex mLock; - std::map mCache; - -public: - PubKeyCache() { ; } - - CKey::pointer locate(const RippleAddress& id); - CKey::pointer store(const RippleAddress& id, const CKey::pointer& key); - void clear(); -}; - -#endif diff --git a/src/cpp/ripple/RPC.h b/src/cpp/ripple/RPC.h index 7d0406a20..475cff649 100644 --- a/src/cpp/ripple/RPC.h +++ b/src/cpp/ripple/RPC.h @@ -29,7 +29,7 @@ enum http_status_type extern std::string JSONRPCRequest(const std::string& strMethod, const Json::Value& params, const Json::Value& id); -extern std::string createHTTPPost(const std::string& strMsg, +extern std::string createHTTPPost(const std::string& strPath, const std::string& strMsg, const std::map& mapRequestHeaders); extern int ReadHTTP(std::basic_istream& stream, @@ -41,4 +41,6 @@ extern std::string JSONRPCReply(const Json::Value& result, const Json::Value& er extern Json::Value JSONRPCError(int code, const std::string& message); +extern bool HTTPAuthorized(const std::map& mapHeaders); + #endif diff --git a/src/cpp/ripple/RPCDoor.cpp b/src/cpp/ripple/RPCDoor.cpp index 9d98a2462..219f28197 100644 --- a/src/cpp/ripple/RPCDoor.cpp +++ b/src/cpp/ripple/RPCDoor.cpp @@ -5,18 +5,22 @@ #include #include +SETUP_LOG(); + using namespace std; using namespace boost::asio::ip; RPCDoor::RPCDoor(boost::asio::io_service& io_service) : - mAcceptor(io_service, tcp::endpoint(address::from_string(theConfig.RPC_IP), theConfig.RPC_PORT)) + mAcceptor(io_service, tcp::endpoint(address::from_string(theConfig.RPC_IP), theConfig.RPC_PORT)), + mDelayTimer(io_service) { - Log(lsINFO) << "RPC port: " << theConfig.RPC_IP << " " << theConfig.RPC_PORT << " allow remote: " << theConfig.RPC_ALLOW_REMOTE; + cLog(lsINFO) << "RPC port: " << theConfig.RPC_IP << " " << theConfig.RPC_PORT << " allow remote: " << theConfig.RPC_ALLOW_REMOTE; startListening(); } + RPCDoor::~RPCDoor() { - Log(lsINFO) << "RPC port: " << theConfig.RPC_IP << " " << theConfig.RPC_PORT << " allow remote: " << theConfig.RPC_ALLOW_REMOTE; + cLog(lsINFO) << "RPC port: " << theConfig.RPC_IP << " " << theConfig.RPC_PORT << " allow remote: " << theConfig.RPC_ALLOW_REMOTE; } void RPCDoor::startListening() @@ -31,26 +35,43 @@ void RPCDoor::startListening() bool RPCDoor::isClientAllowed(const std::string& ip) { - if(theConfig.RPC_ALLOW_REMOTE) return(true); - if(ip=="127.0.0.1") return(true); - return(false); + if (theConfig.RPC_ALLOW_REMOTE) + return true; + + if (ip == "127.0.0.1") + return true; + + return false; } void RPCDoor::handleConnect(RPCServer::pointer new_connection, const boost::system::error_code& error) { - if(!error) + bool delay = false; + if (!error) { // Restrict callers by IP - if(!isClientAllowed(new_connection->getSocket().remote_endpoint().address().to_string())) + if (!isClientAllowed(new_connection->getSocket().remote_endpoint().address().to_string())) { + startListening(); return; } new_connection->connected(); } - else Log(lsINFO) << "RPCDoor::handleConnect Error: " << error; + else + { + if (error == boost::system::errc::too_many_files_open) + delay = true; + cLog(lsINFO) << "RPCDoor::handleConnect Error: " << error; + } - startListening(); + if (delay) + { + mDelayTimer.expires_from_now(boost::posix_time::milliseconds(1000)); + mDelayTimer.async_wait(boost::bind(&RPCDoor::startListening, this)); + } + else + startListening(); } // vim:ts=4 diff --git a/src/cpp/ripple/RPCDoor.h b/src/cpp/ripple/RPCDoor.h index ab60539c6..b1e01e2e3 100644 --- a/src/cpp/ripple/RPCDoor.h +++ b/src/cpp/ripple/RPCDoor.h @@ -7,7 +7,9 @@ Handles incoming connections from people making RPC Requests class RPCDoor { - boost::asio::ip::tcp::acceptor mAcceptor; + boost::asio::ip::tcp::acceptor mAcceptor; + boost::asio::deadline_timer mDelayTimer; + void startListening(); void handleConnect(RPCServer::pointer new_connection, const boost::system::error_code& error); diff --git a/src/cpp/ripple/RPCErr.cpp b/src/cpp/ripple/RPCErr.cpp index cbc699a4a..4c4958a1d 100644 --- a/src/cpp/ripple/RPCErr.cpp +++ b/src/cpp/ripple/RPCErr.cpp @@ -18,11 +18,14 @@ Json::Value rpcError(int iError, Json::Value jvResult) { rpcACT_EXISTS, "actExists", "Account already exists." }, { rpcACT_MALFORMED, "actMalformed", "Account malformed." }, { rpcACT_NOT_FOUND, "actNotFound", "Account not found." }, + { rpcBAD_BLOB, "badBlob", "Blob must be a non-empty hex string." }, { rpcBAD_SEED, "badSeed", "Disallowed seed." }, { rpcBAD_SYNTAX, "badSyntax", "Syntax error." }, + { rpcCOMMAND_MISSING, "commandMissing", "Missing command entry." }, { rpcDST_ACT_MALFORMED, "dstActMalformed", "Destination account is malformed." }, { rpcDST_ACT_MISSING, "dstActMissing", "Destination account does not exists." }, { rpcDST_AMT_MALFORMED, "dstAmtMalformed", "Destination amount/currency/issuer is malformed." }, + { rpcFORBIDDEN, "forbidden", "Bad credentials." }, { rpcFAIL_GEN_DECRPYT, "failGenDecrypt", "Failed to decrypt generator." }, { rpcGETS_ACT_MALFORMED, "getsActMalformed", "Gets account malformed." }, { rpcGETS_AMT_MALFORMED, "getsAmtMalformed", "Gets amount malformed." }, @@ -55,11 +58,14 @@ Json::Value rpcError(int iError, Json::Value jvResult) { rpcPUBLIC_MALFORMED, "publicMalformed", "Public key is malformed." }, { rpcQUALITY_MALFORMED, "qualityMalformed", "Quality malformed." }, { rpcSRC_ACT_MALFORMED, "srcActMalformed", "Source account is malformed." }, - { rpcSRC_ACT_MISSING, "srcActMissing", "Source account does not exist." }, + { rpcSRC_ACT_MISSING, "srcActMissing", "Source account not provided." }, + { rpcSRC_ACT_NOT_FOUND, "srcActNotFound", "Source amount not found." }, { rpcSRC_AMT_MALFORMED, "srcAmtMalformed", "Source amount/currency/issuer is malformed." }, + { rpcSRC_CUR_MALFORMED, "srcCurMalformed", "Source currency is malformed." }, + { rpcSRC_ISR_MALFORMED, "srcIsrMalformed", "Source issuer is malformed." }, { rpcSRC_UNCLAIMED, "srcUnclaimed", "Source account is not claimed." }, { rpcTXN_NOT_FOUND, "txnNotFound", "Transaction not found." }, - { rpcUNKNOWN_COMMAND, "unknownCmd", "Unknown command." }, + { rpcUNKNOWN_COMMAND, "unknownCmd", "Unknown method." }, { rpcWRONG_SEED, "wrongSeed", "The regular key does not point as the master key." }, }; diff --git a/src/cpp/ripple/RPCErr.h b/src/cpp/ripple/RPCErr.h index 0059b35a4..611cc5eea 100644 --- a/src/cpp/ripple/RPCErr.h +++ b/src/cpp/ripple/RPCErr.h @@ -7,6 +7,7 @@ enum { rpcSUCCESS = 0, rpcBAD_SYNTAX, // Must be 1 to print usage to command line. rpcJSON_RPC, + rpcFORBIDDEN, // Error numbers beyond this line are not stable between versions. // Programs should use error tokens. @@ -43,7 +44,9 @@ enum { // Bad parameter rpcACT_MALFORMED, rpcQUALITY_MALFORMED, + rpcBAD_BLOB, rpcBAD_SEED, + rpcCOMMAND_MISSING, rpcDST_ACT_MALFORMED, rpcDST_ACT_MISSING, rpcDST_AMT_MALFORMED, @@ -60,7 +63,10 @@ enum { rpcPUBLIC_MALFORMED, rpcSRC_ACT_MALFORMED, rpcSRC_ACT_MISSING, + rpcSRC_ACT_NOT_FOUND, rpcSRC_AMT_MALFORMED, + rpcSRC_CUR_MALFORMED, + rpcSRC_ISR_MALFORMED, // Internal error (should never happen) rpcINTERNAL, // Generic internal error. diff --git a/src/cpp/ripple/RPCHandler.cpp b/src/cpp/ripple/RPCHandler.cpp index a80e7b0a3..ed5640c4a 100644 --- a/src/cpp/ripple/RPCHandler.cpp +++ b/src/cpp/ripple/RPCHandler.cpp @@ -1,5 +1,5 @@ // -// carries out the RPC +// Carries out the RPC. // #include @@ -11,6 +11,7 @@ #include "Log.h" #include "NetworkOPs.h" #include "RPCHandler.h" +#include "RPCSub.h" #include "Application.h" #include "AccountItems.h" #include "Wallet.h" @@ -22,19 +23,340 @@ #include "InstanceCounter.h" #include "Offer.h" - SETUP_LOG(); +int iAdminGet(const Json::Value& jvRequest, const std::string& strRemoteIp) +{ + int iRole; + bool bPasswordSupplied = jvRequest.isMember("admin_user") || jvRequest.isMember("admin_password"); + bool bPasswordRequired = !theConfig.RPC_ADMIN_USER.empty() || !theConfig.RPC_ADMIN_PASSWORD.empty(); + + bool bPasswordWrong = bPasswordSupplied + ? bPasswordRequired + // Supplied, required, and incorrect. + ? theConfig.RPC_ADMIN_USER != (jvRequest.isMember("admin_user") ? jvRequest["admin_user"].asString() : "") + || theConfig.RPC_ADMIN_PASSWORD != (jvRequest.isMember("admin_user") ? jvRequest["admin_password"].asString() : "") + // Supplied and not required. + : true + : false; + // Meets IP restriction for admin. + bool bAdminIP = false; + + BOOST_FOREACH(const std::string& strAllowIp, theConfig.RPC_ADMIN_ALLOW) + { + if (strAllowIp == strRemoteIp) + bAdminIP = true; + } + + if (bPasswordWrong // Wrong + || (bPasswordSupplied && !bAdminIP)) // Supplied and doesn't meet IP filter. + { + iRole = RPCHandler::FORBID; + } + // If supplied, password is correct. + else + { + // Allow admin, if from admin IP and no password is required or it was supplied and correct. + iRole = bAdminIP && (!bPasswordRequired || bPasswordSupplied) ? RPCHandler::ADMIN : RPCHandler::GUEST; + } + + return iRole; +} + RPCHandler::RPCHandler(NetworkOPs* netOps) { - mNetOps=netOps; - mInfoSub=NULL; + mNetOps = netOps; + mInfoSub = NULL; } RPCHandler::RPCHandler(NetworkOPs* netOps, InfoSub* infoSub) { - mNetOps=netOps; - mInfoSub=infoSub; + mNetOps = netOps; + mInfoSub = infoSub; +} + +Json::Value RPCHandler::transactionSign(Json::Value jvRequest, bool bSubmit) +{ + Json::Value jvResult; + RippleAddress naSeed; + RippleAddress raSrcAddressID; + + cLog(lsDEBUG) << boost::str(boost::format("transactionSign: %s") % jvRequest); + + if (!jvRequest.isMember("secret") || !jvRequest.isMember("tx_json")) + { + return rpcError(rpcINVALID_PARAMS); + } + + Json::Value txJSON = jvRequest["tx_json"]; + + if (!txJSON.isObject()) + { + return rpcError(rpcINVALID_PARAMS); + } + if (!naSeed.setSeedGeneric(jvRequest["secret"].asString())) + { + return rpcError(rpcBAD_SEED); + } + if (!txJSON.isMember("Account")) + { + return rpcError(rpcSRC_ACT_MISSING); + } + if (!raSrcAddressID.setAccountID(txJSON["Account"].asString())) + { + return rpcError(rpcSRC_ACT_MALFORMED); + } + if (!txJSON.isMember("TransactionType")) + { + return rpcError(rpcINVALID_PARAMS); + } + + AccountState::pointer asSrc = mNetOps->getAccountState(mNetOps->getCurrentLedger(), raSrcAddressID); + if (!asSrc) + { + cLog(lsDEBUG) << boost::str(boost::format("transactionSign: Failed to find source account in current ledger: %s") + % raSrcAddressID.humanAccountID()); + + return rpcError(rpcSRC_ACT_NOT_FOUND); + } + + if ("Payment" == txJSON["TransactionType"].asString()) + { + + RippleAddress dstAccountID; + + if (!txJSON.isMember("Destination")) + { + return rpcError(rpcDST_ACT_MISSING); + } + if (!dstAccountID.setAccountID(txJSON["Destination"].asString())) + { + return rpcError(rpcDST_ACT_MALFORMED); + } + + if (!txJSON.isMember("Fee")) + { + txJSON["Fee"] = (int) theConfig.FEE_DEFAULT; + } + + if (txJSON.isMember("Paths") && jvRequest.isMember("build_path")) + { + // Asking to build a path when providing one is an error. + return rpcError(rpcINVALID_PARAMS); + } + + if (!txJSON.isMember("Paths") && txJSON.isMember("Amount") && jvRequest.isMember("build_path")) + { + // Need a ripple path. + STPathSet spsPaths; + uint160 uSrcCurrencyID; + uint160 uSrcIssuerID; + + STAmount saSendMax; + STAmount saSend; + + if (!txJSON.isMember("Amount") // Amount required. + || !saSend.bSetJson(txJSON["Amount"])) // Must be valid. + return rpcError(rpcDST_AMT_MALFORMED); + + if (txJSON.isMember("SendMax")) + { + if (!saSendMax.bSetJson(txJSON["SendMax"])) + return rpcError(rpcINVALID_PARAMS); + } + else + { + // If no SendMax, default to Amount with sender as issuer. + saSendMax = saSend; + saSendMax.setIssuer(raSrcAddressID.getAccountID()); + } + + if (saSendMax.isNative() && saSend.isNative()) + { + // Asking to build a path for XRP to XRP is an error. + return rpcError(rpcINVALID_PARAMS); + } + + Pathfinder pf(raSrcAddressID, dstAccountID, saSendMax.getCurrency(), saSendMax.getIssuer(), saSend); + + if (!pf.findPaths(7, 3, spsPaths)) + { + cLog(lsDEBUG) << "transactionSign: build_path: No paths found."; + + return rpcError(rpcNO_PATH); + } + else + { + cLog(lsDEBUG) << "transactionSign: build_path: " << spsPaths.getJson(0); + } + + if (!spsPaths.isEmpty()) + { + txJSON["Paths"]=spsPaths.getJson(0); + } + } + } + + if (!txJSON.isMember("Fee") + && ( + "AccountSet" == txJSON["TransactionType"].asString() + || "OfferCreate" == txJSON["TransactionType"].asString() + || "OfferCancel" == txJSON["TransactionType"].asString() + || "TrustSet" == txJSON["TransactionType"].asString())) + { + txJSON["Fee"] = (int) theConfig.FEE_DEFAULT; + } + + if (!txJSON.isMember("Sequence")) txJSON["Sequence"] = asSrc->getSeq(); + if (!txJSON.isMember("Flags")) txJSON["Flags"] = 0; + + Ledger::pointer lpCurrent = mNetOps->getCurrentLedger(); + SLE::pointer sleAccountRoot = mNetOps->getSLE(lpCurrent, Ledger::getAccountRootIndex(raSrcAddressID.getAccountID())); + + if (!sleAccountRoot) + { + // XXX Ignore transactions for accounts not created. + return rpcError(rpcSRC_ACT_NOT_FOUND); + } + + bool bHaveAuthKey = false; + RippleAddress naAuthorizedPublic; + + RippleAddress naSecret = RippleAddress::createSeedGeneric(jvRequest["secret"].asString()); + RippleAddress naMasterGenerator = RippleAddress::createGeneratorPublic(naSecret); + + // Find the index of Account from the master generator, so we can generate the public and private keys. + RippleAddress naMasterAccountPublic; + unsigned int iIndex = 0; + bool bFound = false; + + // Don't look at ledger entries to determine if the account exists. Don't want to leak to thin server that these accounts are + // related. + while (!bFound && iIndex != theConfig.ACCOUNT_PROBE_MAX) + { + naMasterAccountPublic.setAccountPublic(naMasterGenerator, iIndex); + + cLog(lsWARNING) << "authorize: " << iIndex << " : " << naMasterAccountPublic.humanAccountID() << " : " << raSrcAddressID.humanAccountID(); + + bFound = raSrcAddressID.getAccountID() == naMasterAccountPublic.getAccountID(); + if (!bFound) + ++iIndex; + } + + if (!bFound) + { + return rpcError(rpcSRC_ACT_NOT_FOUND); + } + + // Use the generator to determine the associated public and private keys. + RippleAddress naGenerator = RippleAddress::createGeneratorPublic(naSecret); + RippleAddress naAccountPublic = RippleAddress::createAccountPublic(naGenerator, iIndex); + RippleAddress naAccountPrivate = RippleAddress::createAccountPrivate(naGenerator, naSecret, iIndex); + + if (bHaveAuthKey + // The generated pair must match authorized... + && naAuthorizedPublic.getAccountID() != naAccountPublic.getAccountID() + // ... or the master key must have been used. + && raSrcAddressID.getAccountID() != naAccountPublic.getAccountID()) + { + // std::cerr << "iIndex: " << iIndex << std::endl; + // std::cerr << "sfAuthorizedKey: " << strHex(asSrc->getAuthorizedKey().getAccountID()) << std::endl; + // std::cerr << "naAccountPublic: " << strHex(naAccountPublic.getAccountID()) << std::endl; + + return rpcError(rpcSRC_ACT_NOT_FOUND); + } + + std::auto_ptr sopTrans; + + try + { + sopTrans = STObject::parseJson(txJSON); + } + catch (std::exception& e) + { + jvResult["error"] = "malformedTransaction"; + jvResult["error_exception"] = e.what(); + + return jvResult; + } + + sopTrans->setFieldVL(sfSigningPubKey, naAccountPublic.getAccountPublic()); + + SerializedTransaction::pointer stpTrans; + + try + { + stpTrans = boost::make_shared(*sopTrans); + } + catch (std::exception& e) + { + jvResult["error"] = "invalidTransaction"; + jvResult["error_exception"] = e.what(); + + return jvResult; + } + + // FIXME: For performance, transactions should not be signed in this code path. + stpTrans->sign(naAccountPrivate); + + Transaction::pointer tpTrans; + + try + { + tpTrans = boost::make_shared(stpTrans, false); + } + catch (std::exception& e) + { + jvResult["error"] = "internalTransaction"; + jvResult["error_exception"] = e.what(); + + return jvResult; + } + + try + { + tpTrans = mNetOps->submitTransactionSync(tpTrans, bSubmit); // FIXME: For performance, should use asynch interface + + if (!tpTrans) { + jvResult["error"] = "invalidTransaction"; + jvResult["error_exception"] = "Unable to sterilize transaction."; + + return jvResult; + } + } + catch (std::exception& e) + { + jvResult["error"] = "internalSubmit"; + jvResult["error_exception"] = e.what(); + + return jvResult; + } + + try + { + jvResult["tx_json"] = tpTrans->getJson(0); + jvResult["tx_blob"] = strHex(tpTrans->getSTransaction()->getSerializer().peekData()); + + if (temUNCERTAIN != tpTrans->getResult()) + { + std::string sToken; + std::string sHuman; + + transResultInfo(tpTrans->getResult(), sToken, sHuman); + + jvResult["engine_result"] = sToken; + jvResult["engine_result_code"] = tpTrans->getResult(); + jvResult["engine_result_message"] = sHuman; + } + return jvResult; + } + catch (std::exception& e) + { + jvResult["error"] = "internalJson"; + jvResult["error_exception"] = e.what(); + + return jvResult; + } } // Look up the master public generator for a regular seed so we may index source accounts ids. @@ -88,7 +410,7 @@ Json::Value RPCHandler::authorize(Ledger::ref lrLedger, asSrc = mNetOps->getAccountState(lrLedger, naSrcAccountID); if (!asSrc) { - return rpcError(rpcSRC_ACT_MISSING); + return rpcError(rpcSRC_ACT_NOT_FOUND); } RippleAddress naMasterGenerator; @@ -292,6 +614,9 @@ Json::Value RPCHandler::doConnect(Json::Value jvRequest) if (theConfig.RUN_STANDALONE) return "cannot connect in standalone mode"; + if (!jvRequest.isMember("ip")) + return rpcError(rpcINVALID_PARAMS); + std::string strIp = jvRequest["ip"].asString(); int iPort = jvRequest.isMember("port") ? jvRequest["port"].asInt() : -1; @@ -301,11 +626,15 @@ Json::Value RPCHandler::doConnect(Json::Value jvRequest) return "connecting"; } +#if ENABLE_INSECURE // { // key: // } Json::Value RPCHandler::doDataDelete(Json::Value jvRequest) { + if (!jvRequest.isMember("key")) + return rpcError(rpcINVALID_PARAMS); + std::string strKey = jvRequest["key"].asString(); Json::Value ret = Json::Value(Json::objectValue); @@ -321,12 +650,17 @@ Json::Value RPCHandler::doDataDelete(Json::Value jvRequest) return ret; } +#endif +#if ENABLE_INSECURE // { // key: // } Json::Value RPCHandler::doDataFetch(Json::Value jvRequest) { + if (!jvRequest.isMember("key")) + return rpcError(rpcINVALID_PARAMS); + std::string strKey = jvRequest["key"].asString(); std::string strValue; @@ -338,13 +672,19 @@ Json::Value RPCHandler::doDataFetch(Json::Value jvRequest) return ret; } +#endif +#if ENABLE_INSECURE // { // key: // value: // } Json::Value RPCHandler::doDataStore(Json::Value jvRequest) { + if (!jvRequest.isMember("key") + || !jvRequest.isMember("value")) + return rpcError(rpcINVALID_PARAMS); + std::string strKey = jvRequest["key"].asString(); std::string strValue = jvRequest["value"].asString(); @@ -362,6 +702,7 @@ Json::Value RPCHandler::doDataStore(Json::Value jvRequest) return ret; } +#endif #if 0 // XXX Needs to be revised for new paradigm @@ -400,6 +741,9 @@ Json::Value RPCHandler::doNicknameInfo(Json::Value params) // XXX This would be better if it too the ledger. Json::Value RPCHandler::doOwnerInfo(Json::Value jvRequest) { + if (!jvRequest.isMember("ident")) + return rpcError(rpcINVALID_PARAMS); + std::string strIdent = jvRequest["ident"].asString(); bool bIndex; int iIndex = jvRequest.isMember("account_index") ? jvRequest["account_index"].asUInt() : 0; @@ -498,8 +842,8 @@ Json::Value RPCHandler::doProfile(Json::Value jvRequest) STAmount(uCurrencyOfferB, naAccountB.getAccountID(), 1+n), // saTakerGets 0); // uExpiration - if(bSubmit) - tpOfferA = mNetOps->submitTransactionSync(tpOfferA); + if (bSubmit) + tpOfferA = mNetOps->submitTransactionSync(tpOfferA); // FIXME: Don't use synch interface } boost::posix_time::ptime ptEnd(boost::posix_time::microsec_clock::local_time()); @@ -535,6 +879,9 @@ Json::Value RPCHandler::doAccountLines(Json::Value jvRequest) if (!lpLedger) return jvResult; + if (!jvRequest.isMember("account")) + return rpcError(rpcINVALID_PARAMS); + std::string strIdent = jvRequest["account"].asString(); bool bIndex = jvRequest.isMember("account_index"); int iIndex = bIndex ? jvRequest["account_index"].asUInt() : 0; @@ -564,7 +911,7 @@ Json::Value RPCHandler::doAccountLines(Json::Value jvRequest) AccountItems rippleLines(raAccount.getAccountID(), lpLedger, AccountItem::pointer(new RippleState())); - BOOST_FOREACH(AccountItem::pointer item, rippleLines.getItems()) + BOOST_FOREACH(AccountItem::ref item, rippleLines.getItems()) { RippleState* line=(RippleState*)item.get(); @@ -610,6 +957,9 @@ Json::Value RPCHandler::doAccountOffers(Json::Value jvRequest) if (!lpLedger) return jvResult; + if (!jvRequest.isMember("account")) + return rpcError(rpcINVALID_PARAMS); + std::string strIdent = jvRequest["account"].asString(); bool bIndex = jvRequest.isMember("account_index"); int iIndex = bIndex ? jvRequest["account_index"].asUInt() : 0; @@ -633,7 +983,7 @@ Json::Value RPCHandler::doAccountOffers(Json::Value jvRequest) Json::Value jsonLines(Json::arrayValue); AccountItems offers(raAccount.getAccountID(), lpLedger, AccountItem::pointer(new Offer())); - BOOST_FOREACH(AccountItem::pointer item, offers.getItems()) + BOOST_FOREACH(AccountItem::ref item, offers.getItems()) { Offer* offer=(Offer*)item.get(); @@ -671,8 +1021,11 @@ Json::Value RPCHandler::doRandom(Json::Value jvRequest) try { getRand(uRandom.begin(), uRandom.size()); + Json::Value jvResult; + jvResult["random"] = uRandom.ToString(); + return jvResult; } catch (...) @@ -694,23 +1047,23 @@ Json::Value RPCHandler::doRipplePathFind(Json::Value jvRequest) RippleAddress raDst; STAmount saDstAmount; - if ( - // Parse raSrc. - !jvRequest.isMember("source_account") - || !jvRequest["source_account"].isString() - || !raSrc.setAccountID(jvRequest["source_account"].asString())) + if (!jvRequest.isMember("source_account")) { - cLog(lsINFO) << "Bad source_account."; - jvResult = rpcError(rpcINVALID_PARAMS); + jvResult = rpcError(rpcSRC_ACT_MISSING); } - else if ( - // Parse raDst. - !jvRequest.isMember("destination_account") - || !jvRequest["destination_account"].isString() - || !raDst.setAccountID(jvRequest["destination_account"].asString())) + else if (!jvRequest["source_account"].isString() + || !raSrc.setAccountID(jvRequest["source_account"].asString())) { - cLog(lsINFO) << "Bad destination_account."; - jvResult = rpcError(rpcINVALID_PARAMS); + jvResult = rpcError(rpcSRC_ACT_MALFORMED); + } + else if (!jvRequest.isMember("destination_account")) + { + jvResult = rpcError(rpcDST_ACT_MISSING); + } + else if (!jvRequest["destination_account"].isString() + || !raDst.setAccountID(jvRequest["destination_account"].asString())) + { + jvResult = rpcError(rpcDST_ACT_MALFORMED); } else if ( // Parse saDstAmount. @@ -723,9 +1076,9 @@ Json::Value RPCHandler::doRipplePathFind(Json::Value jvRequest) } else if ( // Checks on source_currencies. - !jvRequest.isMember("source_currencies") - || !jvRequest["source_currencies"].isArray() - || !jvRequest["source_currencies"].size() + jvRequest.isMember("source_currencies") + && (!jvRequest["source_currencies"].isArray() + || !jvRequest["source_currencies"].size()) // Don't allow empty currencies. ) { cLog(lsINFO) << "Bad source_currencies."; @@ -733,42 +1086,69 @@ Json::Value RPCHandler::doRipplePathFind(Json::Value jvRequest) } else { - Json::Value jvSrcCurrencies = jvRequest["source_currencies"]; - Json::Value jvArray(Json::arrayValue); - Ledger::pointer lpCurrent = mNetOps->getCurrentLedger(); + Json::Value jvSrcCurrencies; + + if (jvRequest.isMember("source_currencies")) + { + jvSrcCurrencies = jvRequest["source_currencies"]; + } + else + { + boost::unordered_set usCurrencies = usAccountSourceCurrencies(raSrc, lpCurrent); + + // Add XRP as a source currency. + // YYY Only bother if they are above reserve. + usCurrencies.insert(uint160(CURRENCY_XRP)); + + jvSrcCurrencies = Json::Value(Json::arrayValue); + + BOOST_FOREACH(const uint160& uCurrency, usCurrencies) + { + Json::Value jvCurrency(Json::objectValue); + + jvCurrency["currency"] = STAmount::createHumanCurrency(uCurrency); + + jvSrcCurrencies.append(jvCurrency); + } + } + + LedgerEntrySet lesSnapshot(lpCurrent); ScopedUnlock su(theApp->getMasterLock()); // As long as we have a locked copy of the ledger, we can unlock. - LedgerEntrySet lesSnapshot(lpCurrent); + Json::Value jvArray(Json::arrayValue); for (unsigned int i=0; i != jvSrcCurrencies.size(); ++i) { Json::Value jvSource = jvSrcCurrencies[i]; uint160 uSrcCurrencyID; uint160 uSrcIssuerID = raSrc.getAccountID(); - if ( - // Parse currency. - !jvSource.isMember("currency") - || !STAmount::currencyFromString(uSrcCurrencyID, jvSource["currency"].asString()) + // Parse mandatory currency. + if (!jvSource.isMember("currency") + || !STAmount::currencyFromString(uSrcCurrencyID, jvSource["currency"].asString())) + { + cLog(lsINFO) << "Bad currency."; - // Parse issuer. - || ((jvSource.isMember("issuer")) + return rpcError(rpcSRC_CUR_MALFORMED); + } + // Parse optional issuer. + else if (((jvSource.isMember("issuer")) && (!jvSource["issuer"].isString() || !STAmount::issuerFromString(uSrcIssuerID, jvSource["issuer"].asString()))) - // Don't allow illegal issuers. || !uSrcIssuerID || ACCOUNT_ONE == uSrcIssuerID) { - cLog(lsINFO) << "Bad currency/issuer."; - return rpcError(rpcINVALID_PARAMS); + cLog(lsINFO) << "Bad issuer."; + + return rpcError(rpcSRC_ISR_MALFORMED); } STPathSet spsComputed; Pathfinder pf(raSrc, raDst, uSrcCurrencyID, uSrcIssuerID, saDstAmount); - if (!pf.findPaths(5, 3, spsComputed)) + if (!pf.findPaths(7, 3, spsComputed)) { cLog(lsDEBUG) << "ripple_path_find: No paths found."; } @@ -858,215 +1238,52 @@ Json::Value RPCHandler::doRipplePathFind(Json::Value jvRequest) return jvResult; } +// { +// tx_json: , +// secret: +// } +Json::Value RPCHandler::doSign(Json::Value jvRequest) +{ + return transactionSign(jvRequest, false); +} + // { // tx_json: , // secret: // } Json::Value RPCHandler::doSubmit(Json::Value jvRequest) { - Json::Value jvResult; - RippleAddress naSeed; - RippleAddress raSrcAddressID; + if (!jvRequest.isMember("tx_blob")) + { + return transactionSign(jvRequest, true); + } - if (!jvRequest.isMember("secret") || !jvRequest.isMember("tx_json")) + Json::Value jvResult; + + std::vector vucBlob(strUnHex(jvRequest["tx_blob"].asString())); + + if (!vucBlob.size()) { return rpcError(rpcINVALID_PARAMS); } - Json::Value txJSON = jvRequest["tx_json"]; - - if (!naSeed.setSeedGeneric(jvRequest["secret"].asString())) - { - return rpcError(rpcBAD_SEED); - } - if (!txJSON.isMember("Account")) - { - return rpcError(rpcSRC_ACT_MISSING); - } - if (!raSrcAddressID.setAccountID(txJSON["Account"].asString())) - { - return rpcError(rpcSRC_ACT_MALFORMED); - } - - AccountState::pointer asSrc = mNetOps->getAccountState(mNetOps->getCurrentLedger(), raSrcAddressID); - if (!asSrc) return rpcError(rpcSRC_ACT_MALFORMED); - - if (!txJSON.isMember("Fee") - && ("OfferCreate" == txJSON["TransactionType"].asString() - || "OfferCancel" == txJSON["TransactionType"].asString() - || "TrustSet" == txJSON["TransactionType"].asString())) - { - txJSON["Fee"] = (int) theConfig.FEE_DEFAULT; - } - - if ("Payment" == txJSON["TransactionType"].asString()) - { - - RippleAddress dstAccountID; - - if (!txJSON.isMember("Destination")) - { - return rpcError(rpcDST_ACT_MISSING); - } - if (!dstAccountID.setAccountID(txJSON["Destination"].asString())) - { - return rpcError(rpcDST_ACT_MALFORMED); - } - - if (!txJSON.isMember("Fee")) - { - txJSON["Fee"] = (int) theConfig.FEE_DEFAULT; - } - - if (txJSON.isMember("Paths") && jvRequest.isMember("build_path")) - { - // Asking to build a path when providing one is an error. - return rpcError(rpcINVALID_PARAMS); - } - - if (!txJSON.isMember("Paths") && txJSON.isMember("Amount") && jvRequest.isMember("build_path")) - { - // Need a ripple path. - STPathSet spsPaths; - uint160 uSrcCurrencyID; - uint160 uSrcIssuerID; - - STAmount saSendMax; - STAmount saSend; - - if (!txJSON.isMember("Amount") // Amount required. - || !saSend.bSetJson(txJSON["Amount"])) // Must be valid. - return rpcError(rpcDST_AMT_MALFORMED); - - if (txJSON.isMember("SendMax")) - { - if (!saSendMax.bSetJson(txJSON["SendMax"])) - return rpcError(rpcINVALID_PARAMS); - } - else - { - // If no SendMax, default to Amount with sender as issuer. - saSendMax = saSend; - saSendMax.setIssuer(raSrcAddressID.getAccountID()); - } - - if (saSendMax.isNative() && saSend.isNative()) - { - // Asking to build a path for XRP to XRP is an error. - return rpcError(rpcINVALID_PARAMS); - } - - Pathfinder pf(raSrcAddressID, dstAccountID, saSendMax.getCurrency(), saSendMax.getIssuer(), saSend); - - if (!pf.findPaths(5, 3, spsPaths)) - { - cLog(lsDEBUG) << "payment: build_path: No paths found."; - - return rpcError(rpcNO_PATH); - } - else - { - cLog(lsDEBUG) << "payment: build_path: " << spsPaths.getJson(0); - } - - if (!spsPaths.isEmpty()) - { - txJSON["Paths"]=spsPaths.getJson(0); - } - } - } - - if (!txJSON.isMember("Sequence")) txJSON["Sequence"]=asSrc->getSeq(); - if (!txJSON.isMember("Flags")) txJSON["Flags"]=0; - - Ledger::pointer lpCurrent = mNetOps->getCurrentLedger(); - SLE::pointer sleAccountRoot = mNetOps->getSLE(lpCurrent, Ledger::getAccountRootIndex(raSrcAddressID.getAccountID())); - - if (!sleAccountRoot) - { - // XXX Ignore transactions for accounts not created. - return rpcError(rpcSRC_ACT_MISSING); - } - - bool bHaveAuthKey = false; - RippleAddress naAuthorizedPublic; - - - RippleAddress naSecret = RippleAddress::createSeedGeneric(jvRequest["secret"].asString()); - RippleAddress naMasterGenerator = RippleAddress::createGeneratorPublic(naSecret); - - // Find the index of Account from the master generator, so we can generate the public and private keys. - RippleAddress naMasterAccountPublic; - unsigned int iIndex = 0; - bool bFound = false; - - // Don't look at ledger entries to determine if the account exists. Don't want to leak to thin server that these accounts are - // related. - while (!bFound && iIndex != theConfig.ACCOUNT_PROBE_MAX) - { - naMasterAccountPublic.setAccountPublic(naMasterGenerator, iIndex); - - Log(lsWARNING) << "authorize: " << iIndex << " : " << naMasterAccountPublic.humanAccountID() << " : " << raSrcAddressID.humanAccountID(); - - bFound = raSrcAddressID.getAccountID() == naMasterAccountPublic.getAccountID(); - if (!bFound) - ++iIndex; - } - - if (!bFound) - { - return rpcError(rpcSRC_ACT_MISSING); - } - - // Use the generator to determine the associated public and private keys. - RippleAddress naGenerator = RippleAddress::createGeneratorPublic(naSecret); - RippleAddress naAccountPublic = RippleAddress::createAccountPublic(naGenerator, iIndex); - RippleAddress naAccountPrivate = RippleAddress::createAccountPrivate(naGenerator, naSecret, iIndex); - - if (bHaveAuthKey - // The generated pair must match authorized... - && naAuthorizedPublic.getAccountID() != naAccountPublic.getAccountID() - // ... or the master key must have been used. - && raSrcAddressID.getAccountID() != naAccountPublic.getAccountID()) - { - // std::cerr << "iIndex: " << iIndex << std::endl; - // std::cerr << "sfAuthorizedKey: " << strHex(asSrc->getAuthorizedKey().getAccountID()) << std::endl; - // std::cerr << "naAccountPublic: " << strHex(naAccountPublic.getAccountID()) << std::endl; - - return rpcError(rpcSRC_ACT_MISSING); - } - - std::auto_ptr sopTrans; - - try - { - sopTrans = STObject::parseJson(txJSON); - } - catch (std::exception& e) - { - jvResult["error"] = "malformedTransaction"; - jvResult["error_exception"] = e.what(); - return jvResult; - } - - sopTrans->setFieldVL(sfSigningPubKey, naAccountPublic.getAccountPublic()); + Serializer sTrans(vucBlob); + SerializerIterator sitTrans(sTrans); SerializedTransaction::pointer stpTrans; try { - stpTrans = boost::make_shared(*sopTrans); + stpTrans = boost::make_shared(boost::ref(sitTrans)); } catch (std::exception& e) { jvResult["error"] = "invalidTransaction"; jvResult["error_exception"] = e.what(); + return jvResult; } - // FIXME: Transactions should not be signed in this code path - stpTrans->sign(naAccountPrivate); - Transaction::pointer tpTrans; try @@ -1077,29 +1294,26 @@ Json::Value RPCHandler::doSubmit(Json::Value jvRequest) { jvResult["error"] = "internalTransaction"; jvResult["error_exception"] = e.what(); + return jvResult; } try { - tpTrans = mNetOps->submitTransactionSync(tpTrans); // FIXME: Should use asynch interface - - if (!tpTrans) { - jvResult["error"] = "invalidTransaction"; - jvResult["error_exception"] = "Unable to sterilize transaction."; - return jvResult; - } + (void) mNetOps->processTransaction(tpTrans); } catch (std::exception& e) { jvResult["error"] = "internalSubmit"; jvResult["error_exception"] = e.what(); + return jvResult; } try { jvResult["tx_json"] = tpTrans->getJson(0); + jvResult["tx_blob"] = strHex(tpTrans->getSTransaction()->getSerializer().peekData()); if (temUNCERTAIN != tpTrans->getResult()) { @@ -1118,6 +1332,7 @@ Json::Value RPCHandler::doSubmit(Json::Value jvRequest) { jvResult["error"] = "internalJson"; jvResult["error_exception"] = e.what(); + return jvResult; } } @@ -1126,7 +1341,16 @@ Json::Value RPCHandler::doServerInfo(Json::Value) { Json::Value ret(Json::objectValue); - ret["info"] = theApp->getOPs().getServerInfo(); + ret["info"] = theApp->getOPs().getServerInfo(true, mRole == ADMIN); + + return ret; +} + +Json::Value RPCHandler::doServerState(Json::Value) +{ + Json::Value ret(Json::objectValue); + + ret["state"] = theApp->getOPs().getServerInfo(false, mRole == ADMIN); return ret; } @@ -1136,6 +1360,9 @@ Json::Value RPCHandler::doServerInfo(Json::Value) // } Json::Value RPCHandler::doTxHistory(Json::Value jvRequest) { + if (!jvRequest.isMember("start")) + return rpcError(rpcINVALID_PARAMS); + unsigned int startIndex = jvRequest["start"].asUInt(); Json::Value obj; Json::Value txs; @@ -1153,7 +1380,7 @@ Json::Value RPCHandler::doTxHistory(Json::Value jvRequest) SQL_FOREACH(db, sql) { Transaction::pointer trans=Transaction::transactionFromSQL(db, false); - if(trans) txs.append(trans->getJson(0)); + if (trans) txs.append(trans->getJson(0)); } } @@ -1167,6 +1394,9 @@ Json::Value RPCHandler::doTxHistory(Json::Value jvRequest) // } Json::Value RPCHandler::doTx(Json::Value jvRequest) { + if (!jvRequest.isMember("transaction")) + return rpcError(rpcINVALID_PARAMS); + std::string strTransaction = jvRequest["transaction"].asString(); if (Transaction::isHexTxID(strTransaction)) @@ -1187,6 +1417,7 @@ Json::Value RPCHandler::doTx(Json::Value jvRequest) Json::Value RPCHandler::doLedgerClosed(Json::Value) { Json::Value jvResult; + uint256 uLedger = mNetOps->getClosedLedgerHash(); jvResult["ledger_index"] = mNetOps->getLedgerID(uLedger); @@ -1251,6 +1482,10 @@ Json::Value RPCHandler::doLedger(Json::Value jvRequest) // { account: , ledger: } // { account: , ledger_min: , ledger_max: } +// THIS ROUTINE DOESN'T SCALE. +// FIXME: Require admin. +// FIXME: Doesn't report database holes. +// FIXME: For consistency change inputs to: ledger_index, ledger_index_min, ledger_index_max. Json::Value RPCHandler::doAccountTransactions(Json::Value jvRequest) { RippleAddress raAccount; @@ -1295,8 +1530,10 @@ Json::Value RPCHandler::doAccountTransactions(Json::Value jvRequest) for (std::vector< std::pair >::iterator it = txns.begin(), end = txns.end(); it != end; ++it) { Json::Value obj(Json::objectValue); - if(it->first) obj["tx"]=it->first->getJson(1); - if(it->second) obj["meta"]=it->second->getJson(0); + + if (it->first) obj["tx"] = it->first->getJson(1); + if (it->second) obj["meta"] = it->second->getJson(0); + ret["transactions"].append(obj); } return ret; @@ -1310,8 +1547,10 @@ Json::Value RPCHandler::doAccountTransactions(Json::Value jvRequest) } // { -// secret: +// secret: // optional // } +// +// This command requires admin access because it makes no sense to ask an untrusted server for this. Json::Value RPCHandler::doValidationCreate(Json::Value jvRequest) { RippleAddress raSeed; Json::Value obj(Json::objectValue); @@ -1475,6 +1714,7 @@ Json::Value RPCHandler::doWalletPropose(Json::Value jvRequest) Json::Value obj(Json::objectValue); obj["master_seed"] = naSeed.humanSeed(); + obj["master_seed_hex"] = naSeed.getSeed().ToString(); //obj["master_key"] = naSeed.humanSeed1751(); obj["account_id"] = naAccount.humanAccountID(); @@ -1515,6 +1755,7 @@ Json::Value RPCHandler::doWalletSeed(Json::Value jvRequest) } } +#if ENABLE_INSECURE // TODO: for now this simply checks if this is the admin account // TODO: need to prevent them hammering this over and over // TODO: maybe a better way is only allow admin from local host @@ -1524,6 +1765,10 @@ Json::Value RPCHandler::doWalletSeed(Json::Value jvRequest) // } Json::Value RPCHandler::doLogin(Json::Value jvRequest) { + if (!jvRequest.isMember("username") + || !jvRequest.isMember("password")) + return rpcError(rpcINVALID_PARAMS); + if (jvRequest["username"].asString() == theConfig.RPC_USER && jvRequest["password"].asString() == theConfig.RPC_PASSWORD) { //mRole=ADMIN; @@ -1534,6 +1779,7 @@ Json::Value RPCHandler::doLogin(Json::Value jvRequest) return "nope"; } } +#endif // { // min_count: // optional, defaults to 10 @@ -1565,7 +1811,7 @@ Json::Value RPCHandler::doLogLevel(Json::Value jvRequest) lev["base"] = Log::severityToString(Log::getMinSeverity()); std::vector< std::pair > logTable = LogPartition::getSeverities(); - typedef std::pair stringPair; + typedef std::map::value_type stringPair; BOOST_FOREACH(const stringPair& it, logTable) lev[it.first] = it.second; @@ -1629,7 +1875,10 @@ Json::Value RPCHandler::doUnlAdd(Json::Value jvRequest) // } Json::Value RPCHandler::doUnlDelete(Json::Value jvRequest) { - std::string strNode = jvRequest[0u].asString(); + if (!jvRequest.isMember("node")) + return rpcError(rpcINVALID_PARAMS); + + std::string strNode = jvRequest["node"].asString(); RippleAddress raNodePublic; @@ -1772,7 +2021,6 @@ Json::Value RPCHandler::doTransactionEntry(Json::Value jvRequest) return jvResult; } -// XXX ledger_index needs to be allowed as a string (32-bits is to small). Json::Value RPCHandler::lookupLedger(Json::Value jvRequest, Ledger::pointer& lpLedger) { Json::Value jvResult; @@ -2088,7 +2336,47 @@ rt_accounts */ Json::Value RPCHandler::doSubscribe(Json::Value jvRequest) { + InfoSub* ispSub; Json::Value jvResult(Json::objectValue); + uint32 uLedgerIndex = jvRequest.isMember("ledger_index") && jvRequest["ledger_index"].isNumeric() + ? jvRequest["ledger_index"].asUInt() + : 0; + + if (!mInfoSub && !jvRequest.isMember("url")) + { + // Must be a JSON-RPC call. + return rpcError(rpcINVALID_PARAMS); + } + + if (jvRequest.isMember("url")) + { + if (mRole != ADMIN) + return rpcError(rpcNO_PERMISSION); + + std::string strUrl = jvRequest["url"].asString(); + std::string strUsername = jvRequest.isMember("username") ? jvRequest["username"].asString() : ""; + std::string strPassword = jvRequest.isMember("password") ? jvRequest["password"].asString() : ""; + + RPCSub *rspSub = mNetOps->findRpcSub(strUrl); + if (!rspSub) + { + rspSub = mNetOps->addRpcSub(strUrl, new RPCSub(strUrl, strUsername, strPassword)); + } + else + { + if (jvRequest.isMember("username")) + rspSub->setUsername(strUsername); + + if (jvRequest.isMember("password")) + rspSub->setPassword(strPassword); + } + + ispSub = rspSub; + } + else + { + ispSub = mInfoSub; + } if (jvRequest.isMember("streams")) { @@ -2098,29 +2386,33 @@ Json::Value RPCHandler::doSubscribe(Json::Value jvRequest) { std::string streamName=(*it).asString(); - if(streamName=="server") + if (streamName=="server") { - mNetOps->subServer(mInfoSub, jvResult); - jvResult["server_status"] = mNetOps->strOperatingMode(); + mNetOps->subServer(ispSub, jvResult); - } else if(streamName=="ledger") - { - mNetOps->subLedger(mInfoSub, jvResult); - - } else if(streamName=="transactions") - { - mNetOps->subTransactions(mInfoSub); - - } else if(streamName=="rt_transactions") - { - mNetOps->subRTTransactions(mInfoSub); } - else { - jvResult["error"] = str(boost::format("Unknown stream: %s") % streamName); + else if (streamName=="ledger") + { + mNetOps->subLedger(ispSub, jvResult); + } - } else + else if (streamName=="transactions") + { + mNetOps->subTransactions(ispSub); + + } + else if (streamName=="rt_transactions") + { + mNetOps->subRTTransactions(ispSub); + } + else + { + jvResult["error"] = "unknownStream"; + } + } + else { - jvResult["error"] = "malformedSteam"; + jvResult["error"] = "malformedStream"; } } } @@ -2132,14 +2424,10 @@ Json::Value RPCHandler::doSubscribe(Json::Value jvRequest) if (usnaAccoundIds.empty()) { jvResult["error"] = "malformedAccount"; - }else + } + else { - BOOST_FOREACH(const RippleAddress& naAccountID, usnaAccoundIds) - { - mInfoSub->insertSubAccountInfo(naAccountID); - } - - mNetOps->subAccount(mInfoSub, usnaAccoundIds, true); + mNetOps->subAccount(ispSub, usnaAccoundIds, uLedgerIndex, true); } } @@ -2150,24 +2438,46 @@ Json::Value RPCHandler::doSubscribe(Json::Value jvRequest) if (usnaAccoundIds.empty()) { jvResult["error"] = "malformedAccount"; - }else + } + else { - BOOST_FOREACH(const RippleAddress& naAccountID, usnaAccoundIds) - { - mInfoSub->insertSubAccountInfo(naAccountID); - } - - mNetOps->subAccount(mInfoSub, usnaAccoundIds, false); + mNetOps->subAccount(ispSub, usnaAccoundIds, uLedgerIndex, false); } } return jvResult; } +// FIXME: This leaks RPCSub objects for JSON-RPC. Shouldn't matter for anyone sane. Json::Value RPCHandler::doUnsubscribe(Json::Value jvRequest) { + InfoSub* ispSub; Json::Value jvResult(Json::objectValue); + if (!mInfoSub && !jvRequest.isMember("url")) + { + // Must be a JSON-RPC call. + return rpcError(rpcINVALID_PARAMS); + } + + if (jvRequest.isMember("url")) + { + if (mRole != ADMIN) + return rpcError(rpcNO_PERMISSION); + + std::string strUrl = jvRequest["url"].asString(); + + RPCSub *rspSub = mNetOps->findRpcSub(strUrl); + if (!rspSub) + return jvResult; + + ispSub = rspSub; + } + else + { + ispSub = mInfoSub; + } + if (jvRequest.isMember("streams")) { for (Json::Value::iterator it = jvRequest["streams"].begin(); it != jvRequest["streams"].end(); it++) @@ -2176,23 +2486,28 @@ Json::Value RPCHandler::doUnsubscribe(Json::Value jvRequest) { std::string streamName=(*it).asString(); - if(streamName=="server") + if (streamName == "server") { - mNetOps->unsubServer(mInfoSub); - }else if(streamName=="ledger") + mNetOps->unsubServer(ispSub); + } + else if (streamName == "ledger") { - mNetOps->unsubLedger(mInfoSub); - }else if(streamName=="transactions") + mNetOps->unsubLedger(ispSub); + } + else if (streamName == "transactions") { - mNetOps->unsubTransactions(mInfoSub); - }else if(streamName=="rt_transactions") + mNetOps->unsubTransactions(ispSub); + } + else if (streamName == "rt_transactions") { - mNetOps->unsubRTTransactions(mInfoSub); - }else + mNetOps->unsubRTTransactions(ispSub); + } + else { jvResult["error"] = str(boost::format("Unknown stream: %s") % streamName); } - }else + } + else { jvResult["error"] = "malformedSteam"; } @@ -2206,14 +2521,10 @@ Json::Value RPCHandler::doUnsubscribe(Json::Value jvRequest) if (usnaAccoundIds.empty()) { jvResult["error"] = "malformedAccount"; - }else + } + else { - BOOST_FOREACH(const RippleAddress& naAccountID, usnaAccoundIds) - { - mInfoSub->insertSubAccountInfo(naAccountID); - } - - mNetOps->unsubAccount(mInfoSub, usnaAccoundIds,true); + mNetOps->unsubAccount(ispSub, usnaAccoundIds, true); } } @@ -2224,14 +2535,10 @@ Json::Value RPCHandler::doUnsubscribe(Json::Value jvRequest) if (usnaAccoundIds.empty()) { jvResult["error"] = "malformedAccount"; - }else + } + else { - BOOST_FOREACH(const RippleAddress& naAccountID, usnaAccoundIds) - { - mInfoSub->insertSubAccountInfo(naAccountID); - } - - mNetOps->unsubAccount(mInfoSub, usnaAccoundIds,false); + mNetOps->unsubAccount(ispSub, usnaAccoundIds, false); } } @@ -2244,12 +2551,12 @@ Json::Value RPCHandler::doUnsubscribe(Json::Value jvRequest) // command is the method. The request object is supplied as the first element of the params. Json::Value RPCHandler::doRpcCommand(const std::string& strMethod, Json::Value& jvParams, int iRole) { - // cLog(lsTRACE) << "doRpcCommand:" << strMethod << ":" << jvParams; + cLog(lsTRACE) << "doRpcCommand:" << strMethod << ":" << jvParams; - if (!jvParams.isArray() || jvParams.size() != 1) + if (!jvParams.isArray() || jvParams.size() > 1) return rpcError(rpcINVALID_PARAMS); - Json::Value jvRequest = jvParams[0u]; + Json::Value jvRequest = jvParams.size() ? jvParams[0u] : Json::Value(Json::objectValue); if (!jvRequest.isObject()) return rpcError(rpcINVALID_PARAMS); @@ -2272,10 +2579,17 @@ Json::Value RPCHandler::doRpcCommand(const std::string& strMethod, Json::Value& return jvResult; } -Json::Value RPCHandler::doCommand(Json::Value& jvRequest, int iRole) +Json::Value RPCHandler::doInternal(Json::Value jvRequest) +{ // Used for debug or special-purpose RPC commands + if (!jvRequest.isMember("internal_command")) + return rpcError(rpcINVALID_PARAMS); + return RPCInternalHandler::runHandler(jvRequest["internal_command"].asString(), jvRequest["params"]); +} + +Json::Value RPCHandler::doCommand(const Json::Value& jvRequest, int iRole) { if (!jvRequest.isMember("command")) - return rpcError(rpcINVALID_PARAMS); + return rpcError(rpcCOMMAND_MISSING); std::string strCommand = jvRequest["command"].asString(); @@ -2290,62 +2604,66 @@ Json::Value RPCHandler::doCommand(Json::Value& jvRequest, int iRole) const char* pCommand; doFuncPtr dfpFunc; bool bAdminRequired; - bool bEvented; unsigned int iOptions; } commandsA[] = { // Request-response methods - { "accept_ledger", &RPCHandler::doAcceptLedger, true, false, optCurrent }, - { "account_info", &RPCHandler::doAccountInfo, false, false, optCurrent }, - { "account_lines", &RPCHandler::doAccountLines, false, false, optCurrent }, - { "account_offers", &RPCHandler::doAccountOffers, false, false, optCurrent }, - { "account_tx", &RPCHandler::doAccountTransactions, false, false, optNetwork }, - { "connect", &RPCHandler::doConnect, true, false, optNone }, - { "get_counts", &RPCHandler::doGetCounts, true, false, optNone }, - { "ledger", &RPCHandler::doLedger, false, false, optNetwork }, - { "ledger_accept", &RPCHandler::doLedgerAccept, true, false, optCurrent }, - { "ledger_closed", &RPCHandler::doLedgerClosed, false, false, optClosed }, - { "ledger_current", &RPCHandler::doLedgerCurrent, false, false, optCurrent }, - { "ledger_entry", &RPCHandler::doLedgerEntry, false, false, optCurrent }, - { "ledger_header", &RPCHandler::doLedgerHeader, false, false, optCurrent }, - { "log_level", &RPCHandler::doLogLevel, true, false, optNone }, - { "logrotate", &RPCHandler::doLogRotate, true, false, optNone }, -// { "nickname_info", &RPCHandler::doNicknameInfo, false, false, optCurrent }, - { "owner_info", &RPCHandler::doOwnerInfo, false, false, optCurrent }, - { "peers", &RPCHandler::doPeers, true, false, optNone }, -// { "profile", &RPCHandler::doProfile, false, false, optCurrent }, - { "random", &RPCHandler::doRandom, false, false, optNone }, - { "ripple_path_find", &RPCHandler::doRipplePathFind, false, false, optCurrent }, - { "submit", &RPCHandler::doSubmit, false, false, optCurrent }, - { "server_info", &RPCHandler::doServerInfo, true, false, optNone }, - { "stop", &RPCHandler::doStop, true, false, optNone }, - { "transaction_entry", &RPCHandler::doTransactionEntry, false, false, optCurrent }, - { "tx", &RPCHandler::doTx, true, false, optNone }, - { "tx_history", &RPCHandler::doTxHistory, false, false, optNone }, + { "accept_ledger", &RPCHandler::doAcceptLedger, true, optCurrent }, + { "account_info", &RPCHandler::doAccountInfo, false, optCurrent }, + { "account_lines", &RPCHandler::doAccountLines, false, optCurrent }, + { "account_offers", &RPCHandler::doAccountOffers, false, optCurrent }, + { "account_tx", &RPCHandler::doAccountTransactions, false, optNetwork }, + { "connect", &RPCHandler::doConnect, true, optNone }, + { "get_counts", &RPCHandler::doGetCounts, true, optNone }, + { "internal", &RPCHandler::doInternal, true, optNone }, + { "ledger", &RPCHandler::doLedger, false, optNetwork }, + { "ledger_accept", &RPCHandler::doLedgerAccept, true, optCurrent }, + { "ledger_closed", &RPCHandler::doLedgerClosed, false, optClosed }, + { "ledger_current", &RPCHandler::doLedgerCurrent, false, optCurrent }, + { "ledger_entry", &RPCHandler::doLedgerEntry, false, optCurrent }, + { "ledger_header", &RPCHandler::doLedgerHeader, false, optCurrent }, + { "log_level", &RPCHandler::doLogLevel, true, optNone }, + { "logrotate", &RPCHandler::doLogRotate, true, optNone }, +// { "nickname_info", &RPCHandler::doNicknameInfo, false, optCurrent }, + { "owner_info", &RPCHandler::doOwnerInfo, false, optCurrent }, + { "peers", &RPCHandler::doPeers, true, optNone }, +// { "profile", &RPCHandler::doProfile, false, optCurrent }, + { "random", &RPCHandler::doRandom, false, optNone }, + { "ripple_path_find", &RPCHandler::doRipplePathFind, false, optCurrent }, + { "sign", &RPCHandler::doSign, false, optCurrent }, + { "submit", &RPCHandler::doSubmit, false, optCurrent }, + { "server_info", &RPCHandler::doServerInfo, false, optNone }, + { "server_state", &RPCHandler::doServerState, false, optNone }, + { "stop", &RPCHandler::doStop, true, optNone }, + { "transaction_entry", &RPCHandler::doTransactionEntry, false, optCurrent }, + { "tx", &RPCHandler::doTx, false, optNetwork }, + { "tx_history", &RPCHandler::doTxHistory, false, optNone }, - { "unl_add", &RPCHandler::doUnlAdd, true, false, optNone }, - { "unl_delete", &RPCHandler::doUnlDelete, true, false, optNone }, - { "unl_list", &RPCHandler::doUnlList, true, false, optNone }, - { "unl_load", &RPCHandler::doUnlLoad, true, false, optNone }, - { "unl_network", &RPCHandler::doUnlNetwork, true, false, optNone }, - { "unl_reset", &RPCHandler::doUnlReset, true, false, optNone }, - { "unl_score", &RPCHandler::doUnlScore, true, false, optNone }, + { "unl_add", &RPCHandler::doUnlAdd, true, optNone }, + { "unl_delete", &RPCHandler::doUnlDelete, true, optNone }, + { "unl_list", &RPCHandler::doUnlList, true, optNone }, + { "unl_load", &RPCHandler::doUnlLoad, true, optNone }, + { "unl_network", &RPCHandler::doUnlNetwork, true, optNone }, + { "unl_reset", &RPCHandler::doUnlReset, true, optNone }, + { "unl_score", &RPCHandler::doUnlScore, true, optNone }, - { "validation_create", &RPCHandler::doValidationCreate, false, false, optNone }, - { "validation_seed", &RPCHandler::doValidationSeed, false, false, optNone }, + { "validation_create", &RPCHandler::doValidationCreate, true, optNone }, + { "validation_seed", &RPCHandler::doValidationSeed, true, optNone }, - { "wallet_accounts", &RPCHandler::doWalletAccounts, false, false, optCurrent }, - { "wallet_propose", &RPCHandler::doWalletPropose, false, false, optNone }, - { "wallet_seed", &RPCHandler::doWalletSeed, false, false, optNone }, + { "wallet_accounts", &RPCHandler::doWalletAccounts, false, optCurrent }, + { "wallet_propose", &RPCHandler::doWalletPropose, false, optNone }, + { "wallet_seed", &RPCHandler::doWalletSeed, false, optNone }, +#if ENABLE_INSECURE // XXX Unnecessary commands which should be removed. - { "login", &RPCHandler::doLogin, true, false, optNone }, - { "data_delete", &RPCHandler::doDataDelete, true, false, optNone }, - { "data_fetch", &RPCHandler::doDataFetch, true, false, optNone }, - { "data_store", &RPCHandler::doDataStore, true, false, optNone }, + { "login", &RPCHandler::doLogin, true, optNone }, + { "data_delete", &RPCHandler::doDataDelete, true, optNone }, + { "data_fetch", &RPCHandler::doDataFetch, true, optNone }, + { "data_store", &RPCHandler::doDataStore, true, optNone }, +#endif // Evented methods - { "subscribe", &RPCHandler::doSubscribe, false, true, optNone }, - { "unsubscribe", &RPCHandler::doUnsubscribe, false, true, optNone }, + { "subscribe", &RPCHandler::doSubscribe, false, optNone }, + { "unsubscribe", &RPCHandler::doUnsubscribe, false, optNone }, }; int i = NUMBER(commandsA); @@ -2361,11 +2679,9 @@ Json::Value RPCHandler::doCommand(Json::Value& jvRequest, int iRole) { return rpcError(rpcNO_PERMISSION); } - else if (commandsA[i].bEvented && mInfoSub == NULL) - { - return rpcError(rpcNO_EVENTS); - } - else if (commandsA[i].iOptions & optNetwork + + // XXX Need the master lock for getOperatingMode + if (commandsA[i].iOptions & optNetwork && mNetOps->getOperatingMode() != NetworkOPs::omTRACKING && mNetOps->getOperatingMode() != NetworkOPs::omFULL) { @@ -2412,4 +2728,29 @@ Json::Value RPCHandler::doCommand(Json::Value& jvRequest, int iRole) } } +RPCInternalHandler* RPCInternalHandler::sHeadHandler = NULL; + +RPCInternalHandler::RPCInternalHandler(const std::string& name, handler_t Handler) : mName(name), mHandler(Handler) +{ + mNextHandler = sHeadHandler; + sHeadHandler = this; +} + +Json::Value RPCInternalHandler::runHandler(const std::string& name, const Json::Value& params) +{ + RPCInternalHandler* h = sHeadHandler; + while (h != NULL) + { + if (name == h->mName) + { + cLog(lsWARNING) << "Internal command " << name << ": " << params; + Json::Value ret = h->mHandler(params); + cLog(lsWARNING) << "Internal command returns: " << ret; + return ret; + } + h = h->mNextHandler; + } + return rpcError(rpcBAD_SYNTAX); +} + // vim:ts=4 diff --git a/src/cpp/ripple/RPCHandler.h b/src/cpp/ripple/RPCHandler.h index 86677de4d..455d6dfd4 100644 --- a/src/cpp/ripple/RPCHandler.h +++ b/src/cpp/ripple/RPCHandler.h @@ -1,8 +1,17 @@ #ifndef __RPCHANDLER__ #define __RPCHANDLER__ +#include + +#include "../json/value.h" + +#include "RippleAddress.h" +#include "SerializedTypes.h" +#include "Ledger.h" + // used by the RPCServer or WSDoor to carry out these RPC commands class NetworkOPs; +class InfoSub; class RPCHandler { @@ -21,6 +30,7 @@ class RPCHandler // Utilities void addSubmitPath(Json::Value& txJSON); boost::unordered_set parseAccountIds(const Json::Value& jvArray); + Json::Value transactionSign(Json::Value jvRequest, bool bSubmit); Json::Value lookupLedger(Json::Value jvRequest, Ledger::pointer& lpLedger); @@ -40,10 +50,13 @@ class RPCHandler Json::Value doAccountOffers(Json::Value params); Json::Value doAccountTransactions(Json::Value params); Json::Value doConnect(Json::Value params); +#if ENABLE_INSECURE Json::Value doDataDelete(Json::Value params); Json::Value doDataFetch(Json::Value params); Json::Value doDataStore(Json::Value params); +#endif Json::Value doGetCounts(Json::Value params); + Json::Value doInternal(Json::Value params); Json::Value doLedger(Json::Value params); Json::Value doLogLevel(Json::Value params); Json::Value doLogRotate(Json::Value params); @@ -53,10 +66,12 @@ class RPCHandler Json::Value doProfile(Json::Value params); Json::Value doRandom(Json::Value jvRequest); Json::Value doRipplePathFind(Json::Value jvRequest); - Json::Value doServerInfo(Json::Value params); + Json::Value doServerInfo(Json::Value params); // for humans + Json::Value doServerState(Json::Value params); // for machines Json::Value doSessionClose(Json::Value params); Json::Value doSessionOpen(Json::Value params); Json::Value doStop(Json::Value params); + Json::Value doSign(Json::Value params); Json::Value doSubmit(Json::Value params); Json::Value doTx(Json::Value params); Json::Value doTxHistory(Json::Value params); @@ -79,7 +94,9 @@ class RPCHandler Json::Value doWalletUnlock(Json::Value params); Json::Value doWalletVerify(Json::Value params); +#if ENABLE_INSECURE Json::Value doLogin(Json::Value params); +#endif Json::Value doLedgerAccept(Json::Value params); Json::Value doLedgerClosed(Json::Value params); @@ -91,18 +108,35 @@ class RPCHandler Json::Value doSubscribe(Json::Value params); Json::Value doUnsubscribe(Json::Value params); - public: - - enum { GUEST, USER, ADMIN }; + enum { GUEST, USER, ADMIN, FORBID }; RPCHandler(NetworkOPs* netOps); RPCHandler(NetworkOPs* netOps, InfoSub* infoSub); - Json::Value doCommand(Json::Value& jvRequest, int role); + Json::Value doCommand(const Json::Value& jvRequest, int role); Json::Value doRpcCommand(const std::string& strCommand, Json::Value& jvParams, int iRole); }; +class RPCInternalHandler +{ +public: + typedef Json::Value (*handler_t)(const Json::Value&); + +protected: + static RPCInternalHandler* sHeadHandler; + + RPCInternalHandler* mNextHandler; + std::string mName; + handler_t mHandler; + +public: + RPCInternalHandler(const std::string& name, handler_t handler); + static Json::Value runHandler(const std::string& name, const Json::Value& params); +}; + +int iAdminGet(const Json::Value& jvRequest, const std::string& strRemoteIp); + #endif // vim:ts=4 diff --git a/src/cpp/ripple/RPCServer.cpp b/src/cpp/ripple/RPCServer.cpp index 98e7e0399..cd7474261 100644 --- a/src/cpp/ripple/RPCServer.cpp +++ b/src/cpp/ripple/RPCServer.cpp @@ -14,8 +14,6 @@ #include #include - - #include "../json/reader.h" #include "../json/writer.h" @@ -28,18 +26,12 @@ SETUP_LOG(); RPCServer::RPCServer(boost::asio::io_service& io_service , NetworkOPs* nopNetwork) : mNetOps(nopNetwork), mSocket(io_service) { - mRole = RPCHandler::GUEST; } - - void RPCServer::connected() { //std::cerr << "RPC request" << std::endl; - if (mSocket.remote_endpoint().address().to_string()=="127.0.0.1") mRole = RPCHandler::ADMIN; - else mRole = RPCHandler::GUEST; - boost::asio::async_read_until(mSocket, mLineBuffer, "\r\n", boost::bind(&RPCServer::handle_read_line, shared_from_this(), boost::asio::placeholders::error)); } @@ -55,7 +47,12 @@ void RPCServer::handle_read_req(const boost::system::error_code& e) } req += strCopy(mQueryVec); - mReplyStr = handleRequest(req); + + if (!HTTPAuthorized(mHTTPRequest.peekHeaders())) + mReplyStr = HTTPReply(403, "Forbidden"); + else + mReplyStr = handleRequest(req); + boost::asio::async_write(mSocket, boost::asio::buffer(mReplyStr), boost::bind(&RPCServer::handle_write, shared_from_this(), boost::asio::placeholders::error)); } @@ -116,19 +113,21 @@ void RPCServer::handle_read_line(const boost::system::error_code& e) std::string RPCServer::handleRequest(const std::string& requestStr) { cLog(lsTRACE) << "handleRequest " << requestStr; + Json::Value id; // Parse request - Json::Value valRequest; - Json::Reader reader; - if (!reader.parse(requestStr, valRequest) || valRequest.isNull() || !valRequest.isObject()) + Json::Value jvRequest; + Json::Reader reader; + + if (!reader.parse(requestStr, jvRequest) || jvRequest.isNull() || !jvRequest.isObject()) return(HTTPReply(400, "unable to parse request")); // Parse id now so errors from here on will have the id - id = valRequest["id"]; + id = jvRequest["id"]; // Parse method - Json::Value valMethod = valRequest["method"]; + Json::Value valMethod = jvRequest["method"]; if (valMethod.isNull()) return(HTTPReply(400, "null method")); if (!valMethod.isString()) @@ -136,11 +135,24 @@ std::string RPCServer::handleRequest(const std::string& requestStr) std::string strMethod = valMethod.asString(); // Parse params - Json::Value valParams = valRequest["params"]; + Json::Value valParams = jvRequest["params"]; + if (valParams.isNull()) + { valParams = Json::Value(Json::arrayValue); + } else if (!valParams.isArray()) - return(HTTPReply(400, "params unparseable")); + { + return HTTPReply(400, "params unparseable"); + } + + mRole = iAdminGet(jvRequest, mSocket.remote_endpoint().address().to_string()); + + if (RPCHandler::FORBID == mRole) + { + // XXX This needs rate limiting to prevent brute forcing password. + return HTTPReply(403, "Forbidden"); + } RPCHandler mRPCHandler(mNetOps); @@ -152,7 +164,6 @@ std::string RPCServer::handleRequest(const std::string& requestStr) return HTTPReply(200, strReply); } - #if 0 // now, expire, n bool RPCServer::parseAcceptRate(const std::string& sAcceptRate) diff --git a/src/cpp/ripple/RPCSub.cpp b/src/cpp/ripple/RPCSub.cpp new file mode 100644 index 000000000..e85d8ffe2 --- /dev/null +++ b/src/cpp/ripple/RPCSub.cpp @@ -0,0 +1,100 @@ +#include + +#include "RPCSub.h" + +#include "CallRPC.h" + +SETUP_LOG(); + +RPCSub::RPCSub(const std::string& strUrl, const std::string& strUsername, const std::string& strPassword) + : mUrl(strUrl), mUsername(strUsername), mPassword(strPassword) +{ + std::string strScheme; + + if (!parseUrl(strUrl, strScheme, mIp, mPort, mPath)) + { + throw std::runtime_error("Failed to parse url."); + } + else if (strScheme != "http") + { + throw std::runtime_error("Only http is supported."); + } + + mSeq = 1; + + if (mPort < 0) + mPort = 80; +} + +void RPCSub::sendThread() +{ + Json::Value jvEvent; + bool bSend; + + do + { + { + // Obtain the lock to manipulate the queue and change sending. + boost::mutex::scoped_lock sl(mLockInfo); + + if (mDeque.empty()) + { + mSending = false; + bSend = false; + } + else + { + std::pair pEvent = mDeque.front(); + + mDeque.pop_front(); + + jvEvent = pEvent.second; + jvEvent["seq"] = pEvent.first; + + bSend = true; + } + } + + // Send outside of the lock. + if (bSend) + { + try + { + cLog(lsDEBUG) << boost::str(boost::format("callRPC calling: %s") % mIp); + + // Drop result. + (void) callRPC(mIp, mPort, mUsername, mPassword, mPath, "event", jvEvent); + } + catch (const std::exception& e) + { + cLog(lsDEBUG) << boost::str(boost::format("callRPC exception: %s") % e.what()); + } + } + } while (bSend); +} + +void RPCSub::send(const Json::Value& jvObj) +{ + boost::mutex::scoped_lock sl(mLockInfo); + + if (RPC_EVENT_QUEUE_MAX == mDeque.size()) + { + // Drop the previous event. + + cLog(lsDEBUG) << boost::str(boost::format("callRPC drop")); + mDeque.pop_back(); + } + + cLog(lsDEBUG) << boost::str(boost::format("callRPC push: %s") % jvObj); + + mDeque.push_back(std::make_pair(mSeq++, jvObj)); + + if (!mSending) + { + // Start a sending thread. + mSending = true; + + cLog(lsDEBUG) << boost::str(boost::format("callRPC start")); + boost::thread(boost::bind(&RPCSub::sendThread, this)).detach(); + } +} diff --git a/src/cpp/ripple/RPCSub.h b/src/cpp/ripple/RPCSub.h new file mode 100644 index 000000000..8cca3e7f5 --- /dev/null +++ b/src/cpp/ripple/RPCSub.h @@ -0,0 +1,55 @@ +#ifndef __RPCSUB__ +#define __RPCSUB__ + +#include + +#include "../json/value.h" + +#include "NetworkOPs.h" + +#define RPC_EVENT_QUEUE_MAX 32 + +// Subscription object for JSON-RPC +class RPCSub : public InfoSub +{ + std::string mUrl; + std::string mIp; + int mPort; + std::string mUsername; + std::string mPassword; + std::string mPath; + + int mSeq; // Next id to allocate. + + bool mSending; // Sending threead is active. + + std::deque > mDeque; + +protected: + void sendThread(); + +public: + RPCSub(const std::string& strUrl, const std::string& strUsername, const std::string& strPassword); + + virtual ~RPCSub() { ; } + + // Implement overridden functions from base class: + void send(const Json::Value& jvObj); + + void setUsername(const std::string& strUsername) + { + boost::mutex::scoped_lock sl(mLockInfo); + + mUsername = strUsername; + } + + void setPassword(const std::string& strPassword) + { + boost::mutex::scoped_lock sl(mLockInfo); + + mPassword = strPassword; + } +}; + +#endif +// vim:ts=4 diff --git a/src/cpp/ripple/RangeSet.cpp b/src/cpp/ripple/RangeSet.cpp index e9c4b38f9..43d468807 100644 --- a/src/cpp/ripple/RangeSet.cpp +++ b/src/cpp/ripple/RangeSet.cpp @@ -115,23 +115,13 @@ BOOST_AUTO_TEST_CASE(RangeSet_test) RangeSet r1, r2; - if (r1 != r2) BOOST_FAIL("RangeSet fail"); - - r1.setValue(1); - if (r1 == r2) BOOST_FAIL("RangeSet fail"); - r2.setRange(1, 1); - if (r1 != r2) BOOST_FAIL("RangeSet fail"); - - r1.clear(); r1.setRange(1,10); r1.clearValue(5); r1.setRange(11, 20); - r2.clear(); r2.setRange(1, 4); r2.setRange(6, 10); r2.setRange(10, 20); - if (r1 != r2) BOOST_FAIL("RangeSet fail"); if (r1.hasValue(5)) BOOST_FAIL("RangeSet fail"); if (!r2.hasValue(9)) BOOST_FAIL("RangeSet fail"); diff --git a/src/cpp/ripple/RangeSet.h b/src/cpp/ripple/RangeSet.h index 5ccf89f07..12623ff02 100644 --- a/src/cpp/ripple/RangeSet.h +++ b/src/cpp/ripple/RangeSet.h @@ -43,8 +43,6 @@ public: void clearRange(uint32, uint32); - void clear() { mRanges.clear(); } - // iterator stuff iterator begin() { return mRanges.begin(); } iterator end() { return mRanges.end(); } @@ -61,9 +59,6 @@ public: static uint32 upper(const_reverse_iterator& it) { return it->upper() - 1; } - bool operator!=(const RangeSet& r) const { return mRanges != r.mRanges; } - bool operator==(const RangeSet& r) const { return mRanges == r.mRanges; } - std::string toString() const; }; diff --git a/src/cpp/ripple/RegularKeySetTransactor.cpp b/src/cpp/ripple/RegularKeySetTransactor.cpp index f74b8f5a9..226ee7082 100644 --- a/src/cpp/ripple/RegularKeySetTransactor.cpp +++ b/src/cpp/ripple/RegularKeySetTransactor.cpp @@ -1,29 +1,33 @@ #include "RegularKeySetTransactor.h" #include "Log.h" - SETUP_LOG(); - -void RegularKeySetTransactor::calculateFee() +uint64 RegularKeySetTransactor::calculateBaseFee() { - Transactor::calculateFee(); - - if ( !(mTxnAccount->getFlags() & lsfPasswordSpent) && - (mSigningPubKey.getAccountID() == mTxnAccountID)) + if ( !(mTxnAccount->getFlags() & lsfPasswordSpent) + && (mSigningPubKey.getAccountID() == mTxnAccountID)) { // flag is armed and they signed with the right account - - mSourceBalance = mTxnAccount->getFieldAmount(sfBalance); - if(mSourceBalance < mFeeDue) mFeeDue = 0; + return 0; } + return Transactor::calculateBaseFee(); } TER RegularKeySetTransactor::doApply() { - std::cerr << "doRegularKeySet>" << std::endl; + std::cerr << "RegularKeySet>" << std::endl; - if(mFeeDue.isZero()) + const uint32 uTxFlags = mTxn.getFlags(); + + if (uTxFlags) + { + cLog(lsINFO) << "RegularKeySet: Malformed transaction: Invalid flags set."; + + return temINVALID_FLAG; + } + + if (mFeeDue.isZero()) { mTxnAccount->setFlag(lsfPasswordSpent); } @@ -31,8 +35,9 @@ TER RegularKeySetTransactor::doApply() uint160 uAuthKeyID=mTxn.getFieldAccount160(sfRegularKey); mTxnAccount->setFieldAccount(sfRegularKey, uAuthKeyID); - - std::cerr << "doRegularKeySet<" << std::endl; + std::cerr << "RegularKeySet<" << std::endl; return tesSUCCESS; } + +// vim:ts=4 diff --git a/src/cpp/ripple/RegularKeySetTransactor.h b/src/cpp/ripple/RegularKeySetTransactor.h index 35d744c8f..7d2fc53fe 100644 --- a/src/cpp/ripple/RegularKeySetTransactor.h +++ b/src/cpp/ripple/RegularKeySetTransactor.h @@ -2,9 +2,11 @@ class RegularKeySetTransactor : public Transactor { - void calculateFee(); + uint64 calculateBaseFee(); public: RegularKeySetTransactor(const SerializedTransaction& txn,TransactionEngineParams params, TransactionEngine* engine) : Transactor(txn,params,engine) {} TER checkFee(); TER doApply(); }; + +// vim:ts=4 diff --git a/src/cpp/ripple/RippleAddress.cpp b/src/cpp/ripple/RippleAddress.cpp index 7841f322e..6f90d140a 100644 --- a/src/cpp/ripple/RippleAddress.cpp +++ b/src/cpp/ripple/RippleAddress.cpp @@ -750,10 +750,13 @@ bool RippleAddress::setSeed(const std::string& strSeed) return SetString(strSeed.c_str(), VER_FAMILY_SEED); } +extern const char *ALPHABET; + bool RippleAddress::setSeedGeneric(const std::string& strText) { RippleAddress naTemp; bool bResult = true; + uint128 uSeed; if (strText.empty() || naTemp.setAccountID(strText) @@ -764,6 +767,10 @@ bool RippleAddress::setSeedGeneric(const std::string& strText) { bResult = false; } + else if (strText.length() == 32 && uSeed.SetHex(strText, true)) + { + setSeed(uSeed); + } else if (setSeed(strText)) { // std::cerr << "Recognized seed." << std::endl; diff --git a/src/cpp/ripple/RippleCalc.cpp b/src/cpp/ripple/RippleCalc.cpp index 6b2fa117d..2d85bea14 100644 --- a/src/cpp/ripple/RippleCalc.cpp +++ b/src/cpp/ripple/RippleCalc.cpp @@ -114,7 +114,7 @@ TER PathState::pushImply( // Append a node and insert before it any implied nodes. // Offers may go back to back. -// <-- terResult: tesSUCCESS, temBAD_PATH, terNO_LINE, tepPATH_DRY +// <-- terResult: tesSUCCESS, temBAD_PATH, terNO_LINE, tecPATH_DRY TER PathState::pushNode( const int iType, const uint160& uAccountID, @@ -235,7 +235,7 @@ TER PathState::pushNode( if (!saOwed.isPositive() && *saOwed.negate() >= lesEntries.rippleLimit(pnCur.uAccountID, pnBck.uAccountID, pnCur.uCurrencyID)) { - terResult = tepPATH_DRY; + terResult = tecPATH_DRY; } } } @@ -1127,7 +1127,10 @@ TER RippleCalc::calcNodeDeliverRev( // Sending could be complicated: could fund a previous offer not yet visited. // However, these deductions and adjustments are tenative. // Must reset balances when going forward to perform actual transfers. - lesActive.accountSend(uOfrOwnerID, uCurIssuerID, saOutPass); + terResult = lesActive.accountSend(uOfrOwnerID, uCurIssuerID, saOutPass); + + if (tesSUCCESS != terResult) + break; // Adjust offer sleOffer->setFieldAmount(sfTakerGets, saTakerGets - saOutPass); @@ -1147,8 +1150,8 @@ TER RippleCalc::calcNodeDeliverRev( saPrvDlvReq += saInPassAct; } - if (!saOutAct) - terResult = tepPATH_DRY; + if (tesSUCCESS == terResult && !saOutAct) + terResult = tecPATH_DRY; return terResult; } @@ -1253,7 +1256,10 @@ TER RippleCalc::calcNodeDeliverFwd( % saOutPassAct.getFullText()); // Output: Debit offer owner, send XRP or non-XPR to next account. - lesActive.accountSend(uOfrOwnerID, uNxtAccountID, saOutPassAct); + terResult = lesActive.accountSend(uOfrOwnerID, uNxtAccountID, saOutPassAct); + + if (tesSUCCESS != terResult) + break; } else { @@ -1314,7 +1320,10 @@ TER RippleCalc::calcNodeDeliverFwd( // Do inbound crediting. // Credit offer owner from in issuer/limbo (input transfer fees left with owner). - lesActive.accountSend(!!uPrvCurrencyID ? uInAccountID : ACCOUNT_XRP, uOfrOwnerID, saInPassAct); + terResult = lesActive.accountSend(!!uPrvCurrencyID ? uInAccountID : ACCOUNT_XRP, uOfrOwnerID, saInPassAct); + + if (tesSUCCESS != terResult) + break; // Adjust offer // Fees are considered paid from a seperate budget and are not named in the offer. @@ -1540,7 +1549,7 @@ void RippleCalc::calcNodeRipple( // Reedems are limited based on IOUs previous has on hand. // Issues are limited based on credit limits and amount owed. // No account balance adjustments as we don't know how much is going to actually be pushed through yet. -// <-- tesSUCCESS or tepPATH_DRY +// <-- tesSUCCESS or tecPATH_DRY TER RippleCalc::calcNodeAccountRev(const unsigned int uNode, PathState& psCur, const bool bMultiQuality) { TER terResult = tesSUCCESS; @@ -1688,7 +1697,7 @@ TER RippleCalc::calcNodeAccountRev(const unsigned int uNode, PathState& psCur, c if (!saCurWantedAct) { // Must have processed something. - terResult = tepPATH_DRY; + terResult = tecPATH_DRY; } } else @@ -1751,7 +1760,7 @@ TER RippleCalc::calcNodeAccountRev(const unsigned int uNode, PathState& psCur, c if (!saCurRedeemAct && !saCurIssueAct) { // Did not make progress. - terResult = tepPATH_DRY; + terResult = tecPATH_DRY; } cLog(lsINFO) << boost::str(boost::format("calcNodeAccountRev: ^|account --> ACCOUNT --> account : saCurRedeemReq=%s saCurIssueReq=%s saPrvOwed=%s saCurRedeemAct=%s saCurIssueAct=%s") @@ -1790,7 +1799,7 @@ TER RippleCalc::calcNodeAccountRev(const unsigned int uNode, PathState& psCur, c if (!saCurDeliverAct) { // Must want something. - terResult = tepPATH_DRY; + terResult = tecPATH_DRY; } cLog(lsINFO) << boost::str(boost::format("calcNodeAccountRev: saCurDeliverReq=%s saCurDeliverAct=%s saPrvOwed=%s") @@ -1819,7 +1828,7 @@ TER RippleCalc::calcNodeAccountRev(const unsigned int uNode, PathState& psCur, c if (!saCurWantedAct) { // Must have processed something. - terResult = tepPATH_DRY; + terResult = tecPATH_DRY; } } else @@ -1852,7 +1861,7 @@ TER RippleCalc::calcNodeAccountRev(const unsigned int uNode, PathState& psCur, c if (!saPrvDeliverAct) { // Must want something. - terResult = tepPATH_DRY; + terResult = tecPATH_DRY; } } } @@ -1870,7 +1879,7 @@ TER RippleCalc::calcNodeAccountRev(const unsigned int uNode, PathState& psCur, c if (!saCurDeliverAct) { // Must want something. - terResult = tepPATH_DRY; + terResult = tecPATH_DRY; } } @@ -2017,7 +2026,7 @@ TER RippleCalc::calcNodeAccountFwd( saCurReceive = saPrvRedeemReq+saIssueCrd; // Actually receive. - lesActive.rippleCredit(uPrvAccountID, uCurAccountID, saPrvRedeemReq+saPrvIssueReq, false); + terResult = lesActive.rippleCredit(uPrvAccountID, uCurAccountID, saPrvRedeemReq+saPrvIssueReq, false); } else { @@ -2060,7 +2069,7 @@ TER RippleCalc::calcNodeAccountFwd( } // Adjust prv --> cur balance : take all inbound - lesActive.rippleCredit(uPrvAccountID, uCurAccountID, saPrvRedeemReq + saPrvIssueReq, false); + terResult = lesActive.rippleCredit(uPrvAccountID, uCurAccountID, saPrvRedeemReq + saPrvIssueReq, false); } } else if (bPrvAccount && !bNxtAccount) @@ -2096,7 +2105,7 @@ TER RippleCalc::calcNodeAccountFwd( } // Adjust prv --> cur balance : take all inbound - lesActive.rippleCredit(uPrvAccountID, uCurAccountID, saPrvRedeemReq + saPrvIssueReq, false); + terResult = lesActive.rippleCredit(uPrvAccountID, uCurAccountID, saPrvRedeemReq + saPrvIssueReq, false); } else { @@ -2133,7 +2142,7 @@ TER RippleCalc::calcNodeAccountFwd( cLog(lsDEBUG) << boost::str(boost::format("calcNodeAccountFwd: ^ --> ACCOUNT -- XRP --> offer")); // Deliver XRP to limbo. - lesActive.accountSend(uCurAccountID, ACCOUNT_XRP, saCurDeliverAct); + terResult = lesActive.accountSend(uCurAccountID, ACCOUNT_XRP, saCurDeliverAct); } } } @@ -2224,7 +2233,7 @@ TER RippleCalc::calcNodeFwd(const unsigned int uNode, PathState& psCur, const bo // 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 +// <-- terResult: tesSUCCESS or tecPATH_DRY // <-> pnNodes: // --> [end]saWanted.mAmount // --> [all]saWanted.mCurrency @@ -2558,8 +2567,7 @@ cLog(lsDEBUG) << boost::str(boost::format("rippleCalc: Summary: %d rate: %s qual { // Have sent maximum allowed. Partial payment not allowed. - terResult = tepPATH_PARTIAL; - lesActive = lesBase; // Revert to just fees charged. + terResult = tecPATH_PARTIAL; } else { @@ -2572,15 +2580,13 @@ cLog(lsDEBUG) << boost::str(boost::format("rippleCalc: Summary: %d rate: %s qual else if (!bPartialPayment) { // Partial payment not allowed. - terResult = tepPATH_PARTIAL; - lesActive = lesBase; // Revert to just fees charged. + terResult = tecPATH_PARTIAL; } // Partial payment ok. else if (!saDstAmountAct) { // No payment at all. - terResult = tepPATH_DRY; - lesActive = lesBase; // Revert to just fees charged. + terResult = tecPATH_DRY; } else { diff --git a/src/cpp/ripple/RippleState.h b/src/cpp/ripple/RippleState.h index f7edcbe1e..a89c55476 100644 --- a/src/cpp/ripple/RippleState.h +++ b/src/cpp/ripple/RippleState.h @@ -34,6 +34,7 @@ private: bool mViewLowest; RippleState(SerializedLedgerEntry::ref ledgerEntry); // For accounts in a ledger + public: RippleState(){ } AccountItem::pointer makeItem(const uint160& accountID, SerializedLedgerEntry::ref ledgerEntry); diff --git a/src/cpp/ripple/SHAMap.cpp b/src/cpp/ripple/SHAMap.cpp index 4c83e1f0f..15355ed1d 100644 --- a/src/cpp/ripple/SHAMap.cpp +++ b/src/cpp/ripple/SHAMap.cpp @@ -21,13 +21,15 @@ DECLARE_INSTANCE(SHAMap); DECLARE_INSTANCE(SHAMapItem); DECLARE_INSTANCE(SHAMapTreeNode); +void SHAMapNode::setHash() const +{ + std::size_t h = theApp->getNonceST() + mDepth; + mHash = mNodeID.hash_combine(h); +} + std::size_t hash_value(const SHAMapNode& mn) { - std::size_t seed = theApp->getNonceST(); - - boost::hash_combine(seed, mn.getDepth()); - - return mn.getNodeID().hash_combine(seed); + return mn.getHash(); } std::size_t hash_value(const uint256& u) @@ -365,7 +367,7 @@ void SHAMap::eraseChildren(SHAMapTreeNode::pointer node) static const SHAMapItem::pointer no_item; -SHAMapItem::ref SHAMap::peekFirstItem() +SHAMapItem::pointer SHAMap::peekFirstItem() { boost::recursive_mutex::scoped_lock sl(mLock); SHAMapTreeNode *node = firstBelow(root.get()); @@ -374,7 +376,7 @@ SHAMapItem::ref SHAMap::peekFirstItem() return node->peekItem(); } -SHAMapItem::ref SHAMap::peekFirstItem(SHAMapTreeNode::TNType& type) +SHAMapItem::pointer SHAMap::peekFirstItem(SHAMapTreeNode::TNType& type) { boost::recursive_mutex::scoped_lock sl(mLock); SHAMapTreeNode *node = firstBelow(root.get()); @@ -384,7 +386,7 @@ SHAMapItem::ref SHAMap::peekFirstItem(SHAMapTreeNode::TNType& type) return node->peekItem(); } -SHAMapItem::ref SHAMap::peekLastItem() +SHAMapItem::pointer SHAMap::peekLastItem() { boost::recursive_mutex::scoped_lock sl(mLock); SHAMapTreeNode *node = lastBelow(root.get()); @@ -393,14 +395,14 @@ SHAMapItem::ref SHAMap::peekLastItem() return node->peekItem(); } -SHAMapItem::ref SHAMap::peekNextItem(const uint256& id) +SHAMapItem::pointer SHAMap::peekNextItem(const uint256& id) { SHAMapTreeNode::TNType type; return peekNextItem(id, type); } -SHAMapItem::ref SHAMap::peekNextItem(const uint256& id, SHAMapTreeNode::TNType& 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); @@ -435,7 +437,7 @@ SHAMapItem::ref SHAMap::peekNextItem(const uint256& id, SHAMapTreeNode::TNType& return no_item; } -SHAMapItem::ref SHAMap::peekPrevItem(const uint256& id) +SHAMapItem::pointer SHAMap::peekPrevItem(const uint256& id) { // Get a pointer to the previous item in the tree after a given item - item must be in tree boost::recursive_mutex::scoped_lock sl(mLock); @@ -464,7 +466,7 @@ SHAMapItem::ref SHAMap::peekPrevItem(const uint256& id) return no_item; } -SHAMapItem::ref SHAMap::peekItem(const uint256& id) +SHAMapItem::pointer SHAMap::peekItem(const uint256& id) { boost::recursive_mutex::scoped_lock sl(mLock); SHAMapTreeNode* leaf = walkToPointer(id); @@ -473,7 +475,7 @@ SHAMapItem::ref SHAMap::peekItem(const uint256& id) return leaf->peekItem(); } -SHAMapItem::ref SHAMap::peekItem(const uint256& id, SHAMapTreeNode::TNType& type) +SHAMapItem::pointer SHAMap::peekItem(const uint256& id, SHAMapTreeNode::TNType& type) { boost::recursive_mutex::scoped_lock sl(mLock); SHAMapTreeNode* leaf = walkToPointer(id); @@ -540,6 +542,7 @@ bool SHAMap::delItem(const uint256& id) SHAMapItem::pointer item = onlyBelow(node.get()); if (item) { + returnNode(node, true); eraseChildren(node); #ifdef ST_DEBUG std::cerr << "Making item node " << *node << std::endl; @@ -707,22 +710,23 @@ SHAMapTreeNode::pointer SHAMap::fetchNodeExternal(const SHAMapNode& id, const ui HashedObject::pointer obj(theApp->getHashedObjectStore().retrieve(hash)); if (!obj) { -// Log(lsTRACE) << "fetchNodeExternal: missing " << hash; +// cLog(lsTRACE) << "fetchNodeExternal: missing " << hash; throw SHAMapMissingNode(mType, id, hash); } try { - SHAMapTreeNode::pointer ret = boost::make_shared(id, obj->getData(), mSeq - 1, snfPREFIX); + SHAMapTreeNode::pointer ret = + boost::make_shared(id, obj->getData(), mSeq, snfPREFIX, hash); if (id != *ret) { - Log(lsFATAL) << "id:" << id << ", got:" << *ret; + cLog(lsFATAL) << "id:" << id << ", got:" << *ret; assert(false); return SHAMapTreeNode::pointer(); } if (ret->getNodeHash() != hash) { - Log(lsFATAL) << "Hashes don't match"; + cLog(lsFATAL) << "Hashes don't match"; assert(false); return SHAMapTreeNode::pointer(); } @@ -745,11 +749,11 @@ void SHAMap::fetchRoot(const uint256& hash) if (sLog(lsTRACE)) { if (mType == smtTRANSACTION) - Log(lsTRACE) << "Fetch root TXN node " << hash; + cLog(lsTRACE) << "Fetch root TXN node " << hash; else if (mType == smtSTATE) - Log(lsTRACE) << "Fetch root STATE node " << hash; + cLog(lsTRACE) << "Fetch root STATE node " << hash; else - Log(lsTRACE) << "Fetch root SHAMap node " << hash; + cLog(lsTRACE) << "Fetch root SHAMap node " << hash; } root = fetchNodeExternal(SHAMapNode(), hash); assert(root->getNodeHash() == hash); diff --git a/src/cpp/ripple/SHAMap.h b/src/cpp/ripple/SHAMap.h index e7d456558..fb5fbc155 100644 --- a/src/cpp/ripple/SHAMap.h +++ b/src/cpp/ripple/SHAMap.h @@ -27,23 +27,28 @@ class SHAMap; class SHAMapNode -{ // Identifies a node in a SHA256 hash +{ // Identifies a node in a SHA256 hash map private: static uint256 smMasks[65]; // AND with hash to get node id uint256 mNodeID; - int mDepth; + int mDepth; + mutable size_t mHash; + + void setHash() const; public: static const int rootDepth = 0; - SHAMapNode() : mDepth(0) { ; } + SHAMapNode() : mDepth(0), mHash(0) { ; } SHAMapNode(int depth, const uint256& hash); virtual ~SHAMapNode() { ; } + int getDepth() const { return mDepth; } const uint256& getNodeID() const { return mNodeID; } bool isValid() const { return (mDepth >= 0) && (mDepth < 64); } + size_t getHash() const { if (mHash == 0) setHash(); return mHash; } virtual bool isPopulated() const { return false; } @@ -176,7 +181,8 @@ public: SHAMapTreeNode(const SHAMapNode& nodeID, SHAMapItem::ref item, TNType type, uint32 seq); // raw node functions - SHAMapTreeNode(const SHAMapNode& id, const std::vector& data, uint32 seq, SHANodeFormat format); + SHAMapTreeNode(const SHAMapNode& id, const std::vector& data, uint32 seq, + SHANodeFormat format, const uint256& hash); void addRaw(Serializer &, SHANodeFormat format); virtual bool isPopulated() const { return true; } @@ -198,9 +204,9 @@ public: bool isAccountState() const { return mType == tnACCOUNT_STATE; } // inner node functions - bool isInnerNode() const { return !mItem; } + bool isInnerNode() const { return !mItem; } bool setChildHash(int m, const uint256& hash); - bool isEmptyBranch(int m) const { return !mHashes[m]; } + bool isEmptyBranch(int m) const { return mHashes[m].isZero(); } bool isEmpty() const; int getBranchCount() const; void makeInner(); @@ -396,16 +402,16 @@ public: bool addGiveItem(SHAMapItem::ref, bool isTransaction, bool hasMeta); // save a copy if you only need a temporary - SHAMapItem::ref peekItem(const uint256& id); - SHAMapItem::ref peekItem(const uint256& id, SHAMapTreeNode::TNType& type); + SHAMapItem::pointer peekItem(const uint256& id); + SHAMapItem::pointer peekItem(const uint256& id, SHAMapTreeNode::TNType& type); // traverse functions - SHAMapItem::ref peekFirstItem(); - SHAMapItem::ref peekFirstItem(SHAMapTreeNode::TNType& type); - SHAMapItem::ref peekLastItem(); - SHAMapItem::ref peekNextItem(const uint256&); - SHAMapItem::ref peekNextItem(const uint256&, SHAMapTreeNode::TNType& type); - SHAMapItem::ref peekPrevItem(const uint256&); + 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, @@ -413,6 +419,7 @@ public: bool getNodeFat(const SHAMapNode& node, std::vector& nodeIDs, std::list >& rawNode, bool fatRoot, bool fatLeaves); bool getRootNode(Serializer& s, SHANodeFormat format); + std::vector getNeededHashes(int max); SMAddNode addRootNode(const uint256& hash, const std::vector& rootNode, SHANodeFormat format, SHAMapSyncFilter* filter); SMAddNode addRootNode(const std::vector& rootNode, SHANodeFormat format, diff --git a/src/cpp/ripple/SHAMapNodes.cpp b/src/cpp/ripple/SHAMapNodes.cpp index a296c54f7..35ad313c7 100644 --- a/src/cpp/ripple/SHAMapNodes.cpp +++ b/src/cpp/ripple/SHAMapNodes.cpp @@ -16,6 +16,8 @@ #include "Log.h" #include "HashPrefixes.h" +SETUP_LOG(); + std::string SHAMapNode::getString() const { static boost::format NodeID("NodeID(%s,%s)"); @@ -100,15 +102,16 @@ uint256 SHAMapNode::getNodeID(int depth, const uint256& hash) return hash & smMasks[depth]; } -SHAMapNode::SHAMapNode(int depth, const uint256 &hash) : mDepth(depth) +SHAMapNode::SHAMapNode(int depth, const uint256 &hash) : mDepth(depth), mHash(0) { // canonicalize the hash to a node ID for this depth assert((depth >= 0) && (depth < 65)); mNodeID = getNodeID(depth, hash); } -SHAMapNode::SHAMapNode(const void *ptr, int len) +SHAMapNode::SHAMapNode(const void *ptr, int len) : mHash(0) { - if (len < 33) mDepth = -1; + if (len < 33) + mDepth = -1; else { memcpy(mNodeID.begin(), ptr, 32); @@ -170,7 +173,7 @@ int SHAMapNode::selectBranch(const uint256& hash) const void SHAMapNode::dump() const { - Log(lsDEBUG) << getString(); + cLog(lsDEBUG) << getString(); } SHAMapTreeNode::SHAMapTreeNode(uint32 seq, const SHAMapNode& nodeID) : SHAMapNode(nodeID), mHash(0), @@ -195,7 +198,7 @@ SHAMapTreeNode::SHAMapTreeNode(const SHAMapNode& node, SHAMapItem::ref item, TNT } SHAMapTreeNode::SHAMapTreeNode(const SHAMapNode& id, const std::vector& rawNode, uint32 seq, - SHANodeFormat format) : SHAMapNode(id), mSeq(seq), mType(tnERROR), mFullBelow(false) + SHANodeFormat format, const uint256& hash) : SHAMapNode(id), mSeq(seq), mType(tnERROR), mFullBelow(false) { if (format == snfWIRE) { @@ -265,7 +268,7 @@ SHAMapTreeNode::SHAMapTreeNode(const SHAMapNode& id, const std::vector(u, s.peekData()); @@ -313,7 +316,7 @@ SHAMapTreeNode::SHAMapTreeNode(const SHAMapNode& id, const std::vector(mHashes), sizeof(mHashes)); -#ifdef DEBUG +#ifdef PARANOID Serializer s; s.add32(sHP_InnerNode); for(int i = 0; i < 16; ++i) @@ -510,7 +522,7 @@ void SHAMapTreeNode::makeInner() void SHAMapTreeNode::dump() { - Log(lsDEBUG) << "SHAMapTreeNode(" << getNodeID() << ")"; + cLog(lsDEBUG) << "SHAMapTreeNode(" << getNodeID() << ")"; } std::string SHAMapTreeNode::getString() const diff --git a/src/cpp/ripple/SHAMapSync.cpp b/src/cpp/ripple/SHAMapSync.cpp index 94b970a31..41e309eae 100644 --- a/src/cpp/ripple/SHAMapSync.cpp +++ b/src/cpp/ripple/SHAMapSync.cpp @@ -19,7 +19,7 @@ void SHAMap::getMissingNodes(std::vector& nodeIDs, std::vectorisValid()); - + if (root->isFullBelow()) { clearSynching(); @@ -32,12 +32,12 @@ void SHAMap::getMissingNodes(std::vector& nodeIDs, std::vector stack; - stack.push(root); + std::stack stack; + stack.push(root.get()); while (!stack.empty()) { - SHAMapTreeNode::pointer node = stack.top(); + SHAMapTreeNode* node = stack.top(); stack.pop(); int base = rand() % 256; @@ -49,10 +49,10 @@ void SHAMap::getMissingNodes(std::vector& nodeIDs, std::vectorgetChildNodeID(branch); const uint256& childHash = node->getChildHash(branch); - SHAMapTreeNode::pointer d; + SHAMapTreeNode* d = NULL; try { - d = getNode(childID, childHash, false); + d = getNodePointer(childID, childHash); } catch (SHAMapMissingNode&) { // node is not in the map @@ -61,17 +61,11 @@ void SHAMap::getMissingNodes(std::vector& nodeIDs, std::vector nodeData; if (filter->haveNode(childID, childHash, nodeData)) { - d = boost::make_shared(childID, nodeData, mSeq, snfPREFIX); - if (childHash != d->getNodeHash()) - { - cLog(lsERROR) << "Wrong hash from cached object"; - d.reset(); - } - else - { - cLog(lsTRACE) << "Got sync node from cache: " << *d; - mTNByID[*d] = d; - } + SHAMapTreeNode::pointer ptr = + boost::make_shared(childID, nodeData, mSeq - 1, snfPREFIX, childHash); + cLog(lsTRACE) << "Got sync node from cache: " << *d; + mTNByID[*ptr] = ptr; + d = ptr.get(); } } } @@ -91,6 +85,58 @@ void SHAMap::getMissingNodes(std::vector& nodeIDs, std::vector SHAMap::getNeededHashes(int max) +{ + std::vector ret; + boost::recursive_mutex::scoped_lock sl(mLock); + + assert(root->isValid()); + + if (root->isFullBelow() || !root->isInner()) + { + clearSynching(); + return ret; + } + + std::stack stack; + stack.push(root.get()); + + while (!stack.empty()) + { + SHAMapTreeNode* node = stack.top(); + stack.pop(); + + int base = rand() % 256; + bool have_all = false; + for (int ii = 0; ii < 16; ++ii) + { // traverse in semi-random order + int branch = (base + ii) % 16; + if (!node->isEmptyBranch(branch)) + { + SHAMapNode childID = node->getChildNodeID(branch); + const uint256& childHash = node->getChildHash(branch); + try + { + SHAMapTreeNode* d = getNodePointer(childID, childHash); + assert(d); + if (d->isInner() && !d->isFullBelow()) + stack.push(d); + } + catch (SHAMapMissingNode&) + { // node is not in the map + have_all = false; + ret.push_back(childHash); + if (--max <= 0) + return ret; + } + } + } + if (have_all) + node->setFullBelow(); + } + return ret; +} + bool SHAMap::getNodeFat(const SHAMapNode& wanted, std::vector& nodeIDs, std::list >& rawNodes, bool fatRoot, bool fatLeaves) { // Gets a node and some of its children @@ -99,8 +145,8 @@ bool SHAMap::getNodeFat(const SHAMapNode& wanted, std::vector& nodeI SHAMapTreeNode::pointer node = getNode(wanted); if (!node) { - assert(false); // FIXME Remove for release, this can happen if we get a bogus request - return false; + cLog(lsWARNING) << "peer requested node that not in the map: " << wanted; + throw std::runtime_error("Peer requested node not in map"); } nodeIDs.push_back(*node); @@ -147,7 +193,7 @@ SMAddNode SHAMap::addRootNode(const std::vector& rootNode, SHANod return SMAddNode::okay(); } - SHAMapTreeNode::pointer node = boost::make_shared(SHAMapNode(), rootNode, 0, format); + SHAMapTreeNode::pointer node = boost::make_shared(SHAMapNode(), rootNode, mSeq - 1, format, uint256()); if (!node) return SMAddNode::invalid(); @@ -155,8 +201,6 @@ SMAddNode SHAMap::addRootNode(const std::vector& rootNode, SHANod node->dump(); #endif - returnNode(root, true); - root = node; mTNByID[*root] = root; if (root->getNodeHash().isZero()) @@ -187,11 +231,10 @@ SMAddNode SHAMap::addRootNode(const uint256& hash, const std::vector(SHAMapNode(), rootNode, 0, format); + SHAMapTreeNode::pointer node = boost::make_shared(SHAMapNode(), rootNode, mSeq - 1, format, uint256()); if (!node || node->getNodeHash() != hash) return SMAddNode::invalid(); - returnNode(root, true); root = node; mTNByID[*root] = root; if (root->getNodeHash().isZero()) @@ -215,7 +258,7 @@ SMAddNode SHAMap::addKnownNode(const SHAMapNode& node, const std::vectorgetChildHash(branch); - if (!hash) + if (hash.isZero()) { cLog(lsWARNING) << "AddKnownNode for empty branch"; return SMAddNode::invalid(); } - SHAMapTreeNode::pointer newNode = boost::make_shared(node, rawNode, mSeq, snfWIRE); + SHAMapTreeNode::pointer newNode = boost::make_shared(node, rawNode, mSeq - 1, snfWIRE, uint256()); if (hash != newNode->getNodeHash()) // these aren't the droids we're looking for return SMAddNode::invalid(); @@ -305,6 +348,7 @@ SMAddNode SHAMap::addKnownNode(const SHAMapNode& node, const std::vectorisFullBelow()) clearSynching(); + return SMAddNode::useful(); } diff --git a/src/cpp/ripple/SHAMapSync.h b/src/cpp/ripple/SHAMapSync.h index 1431ccc12..7c3eae345 100644 --- a/src/cpp/ripple/SHAMapSync.h +++ b/src/cpp/ripple/SHAMapSync.h @@ -7,6 +7,7 @@ // Sync filters allow low-level SHAMapSync code to interact correctly with // higher-level structures such as caches and transaction stores +// This class is needed on both add and check functions class ConsensusTransSetSF : public SHAMapSyncFilter { // sync filter for transaction sets during consensus building public: @@ -24,14 +25,14 @@ public: } }; +// This class is only needed on add functions class AccountStateSF : public SHAMapSyncFilter { // sync filter for account state nodes during ledger sync protected: - uint256 mLedgerHash; uint32 mLedgerSeq; public: - AccountStateSF(const uint256& ledgerHash, uint32 ledgerSeq) : mLedgerHash(ledgerHash), mLedgerSeq(ledgerSeq) + AccountStateSF(uint32 ledgerSeq) : mLedgerSeq(ledgerSeq) { ; } virtual void gotNode(const SHAMapNode& id, const uint256& nodeHash, @@ -45,14 +46,14 @@ public: } }; +// This class is only needed on add functions class TransactionStateSF : public SHAMapSyncFilter { // sync filter for transactions tree during ledger sync protected: - uint256 mLedgerHash; uint32 mLedgerSeq; public: - TransactionStateSF(const uint256& ledgerHash, uint32 ledgerSeq) : mLedgerHash(ledgerHash), mLedgerSeq(ledgerSeq) + TransactionStateSF(uint32 ledgerSeq) : mLedgerSeq(ledgerSeq) { ; } virtual void gotNode(const SHAMapNode& id, const uint256& nodeHash, diff --git a/src/cpp/ripple/SNTPClient.cpp b/src/cpp/ripple/SNTPClient.cpp index d99cee4e1..cf3d7b993 100644 --- a/src/cpp/ripple/SNTPClient.cpp +++ b/src/cpp/ripple/SNTPClient.cpp @@ -10,6 +10,8 @@ #include "Config.h" #include "Log.h" +SETUP_LOG(); + // #define SNTP_DEBUG static uint8_t SNTPQueryData[48] = @@ -73,7 +75,7 @@ void SNTPClient::resolveComplete(const boost::system::error_code& error, boost:: 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"; + cLog(lsTRACE) << "SNTP: Redundant query suppressed"; return; } query.mReceivedReply = false; @@ -94,23 +96,23 @@ void SNTPClient::receivePacket(const boost::system::error_code& error, std::size { boost::mutex::scoped_lock sl(mLock); #ifdef SNTP_DEBUG - Log(lsTRACE) << "SNTP: Packet from " << mReceiveEndpoint; + cLog(lsTRACE) << "SNTP: Packet from " << mReceiveEndpoint; #endif std::map::iterator query = mQueries.find(mReceiveEndpoint); if (query == mQueries.end()) - Log(lsDEBUG) << "SNTP: Reply from " << mReceiveEndpoint << " found without matching query"; + cLog(lsDEBUG) << "SNTP: Reply from " << mReceiveEndpoint << " found without matching query"; else if (query->second.mReceivedReply) - Log(lsDEBUG) << "SNTP: Duplicate response from " << mReceiveEndpoint; + cLog(lsDEBUG) << "SNTP: Duplicate response from " << mReceiveEndpoint; else { query->second.mReceivedReply = true; if (time(NULL) > (query->second.mLocalTimeSent + 1)) - Log(lsWARNING) << "SNTP: Late response from " << mReceiveEndpoint; + cLog(lsWARNING) << "SNTP: Late response from " << mReceiveEndpoint; else if (bytes_xferd < 48) - Log(lsWARNING) << "SNTP: Short reply from " << mReceiveEndpoint + cLog(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 from " << mReceiveEndpoint << "had wrong nonce"; + cLog(lsWARNING) << "SNTP: Reply from " << mReceiveEndpoint << "had wrong nonce"; else processReply(); } @@ -123,8 +125,7 @@ void SNTPClient::receivePacket(const boost::system::error_code& error, std::size void SNTPClient::sendComplete(const boost::system::error_code& error, std::size_t) { - if (error) - Log(lsWARNING) << "SNTP: Send error"; + tLog(error, lsWARNING) << "SNTP: Send error"; } void SNTPClient::processReply() @@ -138,12 +139,12 @@ void SNTPClient::processReply() if ((info >> 30) == 3) { - Log(lsINFO) << "SNTP: Alarm condition " << mReceiveEndpoint; + cLog(lsINFO) << "SNTP: Alarm condition " << mReceiveEndpoint; return; } if ((stratum == 0) || (stratum > 14)) { - Log(lsINFO) << "SNTP: Unreasonable stratum (" << stratum << ") from " << mReceiveEndpoint; + cLog(lsINFO) << "SNTP: Unreasonable stratum (" << stratum << ") from " << mReceiveEndpoint; return; } @@ -171,10 +172,7 @@ void SNTPClient::processReply() if ((mOffset == -1) || (mOffset == 1)) // small corrections likely do more harm than good mOffset = 0; -#ifndef SNTP_DEBUG - if (timev || mOffset) -#endif - Log(lsTRACE) << "SNTP: Offset is " << timev << ", new system offset is " << mOffset; + tLog(timev || mOffset, lsTRACE) << "SNTP: Offset is " << timev << ", new system offset is " << mOffset; } void SNTPClient::timerEntry(const boost::system::error_code& error) @@ -198,7 +196,7 @@ void SNTPClient::init(const std::vector& servers) std::vector::const_iterator it = servers.begin(); if (it == servers.end()) { - Log(lsINFO) << "SNTP: no server specified"; + cLog(lsINFO) << "SNTP: no server specified"; return; } BOOST_FOREACH(const std::string& it, servers) @@ -231,13 +229,13 @@ bool SNTPClient::doQuery() best = it; if (best == mServers.end()) { - Log(lsINFO) << "SNTP: No server to query"; + cLog(lsINFO) << "SNTP: No server to query"; return false; } time_t now = time(NULL); if ((best->second != (time_t) -1) && ((best->second + NTP_MIN_QUERY) >= now)) { - Log(lsTRACE) << "SNTP: All servers recently queried"; + cLog(lsTRACE) << "SNTP: All servers recently queried"; return false; } best->second = now; @@ -247,7 +245,7 @@ bool SNTPClient::doQuery() boost::bind(&SNTPClient::resolveComplete, this, boost::asio::placeholders::error, boost::asio::placeholders::iterator)); #ifdef SNTP_DEBUG - Log(lsTRACE) << "SNTP: Resolve pending for " << best->first; + cLog(lsTRACE) << "SNTP: Resolve pending for " << best->first; #endif return true; } diff --git a/src/cpp/ripple/ScriptData.cpp b/src/cpp/ripple/ScriptData.cpp index 52442c0d1..51cb4caf0 100644 --- a/src/cpp/ripple/ScriptData.cpp +++ b/src/cpp/ripple/ScriptData.cpp @@ -1 +1,3 @@ -#include "ScriptData.h" \ No newline at end of file +#include "ScriptData.h" + +// vim:ts=4 diff --git a/src/cpp/ripple/ScriptData.h b/src/cpp/ripple/ScriptData.h index 0e84e8f92..480cc9f25 100644 --- a/src/cpp/ripple/ScriptData.h +++ b/src/cpp/ripple/ScriptData.h @@ -4,7 +4,7 @@ #include namespace Script { -class Data +class Data { public: typedef boost::shared_ptr pointer; @@ -89,5 +89,6 @@ public: } +#endif -#endif \ No newline at end of file +// vim:ts=4 diff --git a/src/cpp/ripple/SecureAllocator.h b/src/cpp/ripple/SecureAllocator.h index 295d46d67..85c3b6db2 100644 --- a/src/cpp/ripple/SecureAllocator.h +++ b/src/cpp/ripple/SecureAllocator.h @@ -40,4 +40,4 @@ struct secure_allocator : public std::allocator } std::allocator::deallocate(p, n); } -}; \ No newline at end of file +}; diff --git a/src/cpp/ripple/SerializeProto.h b/src/cpp/ripple/SerializeProto.h index 286cf617f..a36debe66 100644 --- a/src/cpp/ripple/SerializeProto.h +++ b/src/cpp/ripple/SerializeProto.h @@ -43,7 +43,8 @@ FIELD(Expiration, UINT32, 10) FIELD(TransferRate, UINT32, 11) FIELD(WalletSize, UINT32, 12) - FIELD(OwnerCount, UINT32, 13) // Reorder on ledger reset. + FIELD(OwnerCount, UINT32, 13) + FIELD(DestinationTag, UINT32, 14) // 32-bit integers (uncommon) FIELD(HighQualityIn, UINT32, 16) @@ -56,10 +57,13 @@ FIELD(BondAmount, UINT32, 23) FIELD(LoadFee, UINT32, 24) FIELD(OfferSequence, UINT32, 25) - FIELD(FirstLedgerSequence, UINT32, 26) + FIELD(FirstLedgerSequence, UINT32, 26) // Deprecated: do not use FIELD(LastLedgerSequence, UINT32, 27) FIELD(TransactionIndex, UINT32, 28) FIELD(OperationLimit, UINT32, 29) + FIELD(ReferenceFeeUnits, UINT32, 30) + FIELD(ReserveBase, UINT32, 31) + FIELD(ReserveIncrement, UINT32, 32) // 64-bit integers FIELD(IndexNext, UINT64, 1) @@ -68,6 +72,9 @@ FIELD(OwnerNode, UINT64, 4) FIELD(BaseFee, UINT64, 5) FIELD(ExchangeRate, UINT64, 6) + FIELD(LowNode, UINT64, 7) + FIELD(HighNode, UINT64, 8) + // 128-bit FIELD(EmailHash, HASH128, 1) diff --git a/src/cpp/ripple/SerializedLedger.cpp b/src/cpp/ripple/SerializedLedger.cpp index 9f70f7c52..8d6804a4d 100644 --- a/src/cpp/ripple/SerializedLedger.cpp +++ b/src/cpp/ripple/SerializedLedger.cpp @@ -6,6 +6,7 @@ #include "Log.h" DECLARE_INSTANCE(SerializedLedgerEntry) +SETUP_LOG(); SerializedLedgerEntry::SerializedLedgerEntry(SerializerIterator& sit, const uint256& index) : STObject(sfLedgerEntry), mIndex(index) @@ -33,8 +34,8 @@ SerializedLedgerEntry::SerializedLedgerEntry(const Serializer& s, const uint256& mType = mFormat->t_type; if (!setType(mFormat->elements)) { - Log(lsWARNING) << "Ledger entry not valid for type " << mFormat->t_name; - Log(lsWARNING) << getJson(0); + cLog(lsWARNING) << "Ledger entry not valid for type " << mFormat->t_name; + cLog(lsWARNING) << getJson(0); throw std::runtime_error("ledger entry not valid for type"); } } @@ -99,7 +100,7 @@ uint32 SerializedLedgerEntry::getThreadedLedger() bool SerializedLedgerEntry::thread(const uint256& txID, uint32 ledgerSeq, uint256& prevTxID, uint32& prevLedgerID) { uint256 oldPrevTxID = getFieldH256(sfPreviousTxnID); - Log(lsTRACE) << "Thread Tx:" << txID << " prev:" << oldPrevTxID; + cLog(lsTRACE) << "Thread Tx:" << txID << " prev:" << oldPrevTxID; if (oldPrevTxID == txID) { // this transaction is already threaded assert(getFieldU32(sfPreviousTxnLgrSeq) == ledgerSeq); diff --git a/src/cpp/ripple/SerializedObject.cpp b/src/cpp/ripple/SerializedObject.cpp index 35a0760b5..fc5e710c2 100644 --- a/src/cpp/ripple/SerializedObject.cpp +++ b/src/cpp/ripple/SerializedObject.cpp @@ -130,12 +130,12 @@ std::auto_ptr STObject::makeDeserializedObject(SerializedTypeID } } -void STObject::set(const std::vector& type) +void STObject::set(const std::vector& type) { mData.clear(); mType.clear(); - BOOST_FOREACH(const SOElement::ptr& elem, type) + BOOST_FOREACH(SOElement::ref elem, type) { mType.push_back(elem); if (elem->flags != SOE_REQUIRED) @@ -145,13 +145,13 @@ void STObject::set(const std::vector& type) } } -bool STObject::setType(const std::vector &type) +bool STObject::setType(const std::vector &type) { boost::ptr_vector newData; bool valid = true; mType.clear(); - BOOST_FOREACH(const SOElement::ptr& elem, type) + BOOST_FOREACH(SOElement::ref elem, type) { bool match = false; for (boost::ptr_vector::iterator it = mData.begin(); it != mData.end(); ++it) @@ -200,7 +200,7 @@ bool STObject::setType(const std::vector &type) bool STObject::isValidForType() { boost::ptr_vector::iterator it = mData.begin(); - BOOST_FOREACH(SOElement::ptr elem, mType) + BOOST_FOREACH(SOElement::ref elem, mType) { if (it == mData.end()) return false; @@ -216,7 +216,7 @@ bool STObject::isFieldAllowed(SField::ref field) { if (isFree()) return true; - BOOST_FOREACH(SOElement::ptr elem, mType) + BOOST_FOREACH(SOElement::ref elem, mType) { // are any required elemnents missing if (elem->e_field == field) return true; @@ -294,7 +294,7 @@ void STObject::add(Serializer& s, bool withSigningFields) const } - typedef std::pair field_iterator; + typedef std::map::value_type field_iterator; BOOST_FOREACH(field_iterator& it, fields) { // insert them in sorted order const SerializedType* field = it.second; @@ -1039,7 +1039,7 @@ std::auto_ptr STObject::parseJson(const Json::Value& object, SField::r 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))); + data.push_back(new STUInt32(field, range_check_cast(value.asInt(), 0, 4294967295u))); else if (value.isUInt()) data.push_back(new STUInt32(field, static_cast(value.asUInt()))); else @@ -1242,7 +1242,7 @@ BOOST_AUTO_TEST_CASE( FieldManipulation_test ) SField sfTestU32(STI_UINT32, 255, "TestU32"); SField sfTestObject(STI_OBJECT, 255, "TestObject"); - std::vector elements; + std::vector elements; elements.push_back(new SOElement(sfFlags, SOE_REQUIRED)); elements.push_back(new SOElement(sfTestVL, SOE_REQUIRED)); elements.push_back(new SOElement(sfTestH256, SOE_OPTIONAL)); diff --git a/src/cpp/ripple/SerializedObject.h b/src/cpp/ripple/SerializedObject.h index 99207fb72..545ce91fb 100644 --- a/src/cpp/ripple/SerializedObject.h +++ b/src/cpp/ripple/SerializedObject.h @@ -18,7 +18,7 @@ DEFINE_INSTANCE(SerializedArray); class SOElement { // An element in the description of a serialized object public: - typedef SOElement const * ptr; // used to point to one element + typedef SOElement const * ref; // used to point to one element SField::ref e_field; const SOE_Flags flags; @@ -29,8 +29,8 @@ public: class STObject : public SerializedType, private IS_INSTANCE(SerializedObject) { protected: - boost::ptr_vector mData; - std::vector mType; + boost::ptr_vector mData; + std::vector mType; STObject* duplicate() const { return new STObject(*this); } STObject(SField::ref name, boost::ptr_vector& data) : SerializedType(name) { mData.swap(data); } @@ -40,10 +40,10 @@ public: STObject(SField::ref name) : SerializedType(name) { ; } - STObject(const std::vector& type, 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) + STObject(const std::vector& type, SerializerIterator& sit, SField::ref name) : SerializedType(name) { set(sit); setType(type); } std::auto_ptr oClone() const { return std::auto_ptr(new STObject(*this)); } @@ -54,12 +54,12 @@ public: static std::auto_ptr deserialize(SerializerIterator& sit, SField::ref name); - bool setType(const std::vector& type); + bool setType(const std::vector& type); bool isValidForType(); bool isFieldAllowed(SField::ref); bool isFree() const { return mType.empty(); } - void set(const std::vector&); + void set(const std::vector&); bool set(SerializerIterator& u, int depth = 0); virtual SerializedTypeID getSType() const { return STI_OBJECT; } @@ -77,8 +77,8 @@ public: 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; } - SerializedType& front() { return mData.front(); } + 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(); } @@ -248,7 +248,7 @@ public: 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 SerializedTypeID getSType() const { return STI_ARRAY; } virtual bool isEquivalent(const SerializedType& t) const; virtual bool isDefault() const { return value.empty(); } }; diff --git a/src/cpp/ripple/SerializedTransaction.cpp b/src/cpp/ripple/SerializedTransaction.cpp index 7b5a58afc..5961d25f7 100644 --- a/src/cpp/ripple/SerializedTransaction.cpp +++ b/src/cpp/ripple/SerializedTransaction.cpp @@ -78,8 +78,8 @@ std::string SerializedTransaction::getText() const return STObject::getText(); } -std::vector SerializedTransaction::getAffectedAccounts() const -{ // FIXME: This needs to be thought out better +std::vector SerializedTransaction::getMentionedAccounts() const +{ std::vector accounts; BOOST_FOREACH(const SerializedType& it, peekData()) @@ -100,9 +100,10 @@ std::vector SerializedTransaction::getAffectedAccounts() const if (!found) accounts.push_back(na); } - if (it.getFName() == sfLimitAmount) + const STAmount* sam = dynamic_cast(&it); + if (sam) { - uint160 issuer = dynamic_cast(&it)->getIssuer(); + uint160 issuer = sam->getIssuer(); if (issuer.isNonZero()) { RippleAddress na; @@ -126,7 +127,7 @@ std::vector SerializedTransaction::getAffectedAccounts() const uint256 SerializedTransaction::getSigningHash() const { - return STObject::getSigningHash(sHP_TransactionSign); + return STObject::getSigningHash(theConfig.SIGN_TRANSACTION); } uint256 SerializedTransaction::getTransactionID() const @@ -235,9 +236,8 @@ std::string SerializedTransaction::getMetaSQL(uint32 inLedger, const std::string std::string SerializedTransaction::getSQL(Serializer rawTxn, uint32 inLedger, char status) const { static boost::format bfTrans("('%s', '%s', '%s', '%d', '%d', '%c', %s)"); - std::string rTxn; - theApp->getTxnDB()->getDB()->escape( - reinterpret_cast(rawTxn.getDataPtr()), rawTxn.getLength(), rTxn); + std::string rTxn = sqlEscape(rawTxn.peekData()); + return str(bfTrans % getTransactionID().GetHex() % getTransactionType() % getSourceAccount().humanAccountID() % getSequence() % inLedger % status % rTxn); @@ -247,9 +247,8 @@ std::string SerializedTransaction::getMetaSQL(Serializer rawTxn, uint32 inLedger const std::string& escapedMetaData) const { static boost::format bfTrans("('%s', '%s', '%s', '%d', '%d', '%c', %s, %s)"); - std::string rTxn; - theApp->getTxnDB()->getDB()->escape( - reinterpret_cast(rawTxn.getDataPtr()), rawTxn.getLength(), rTxn); + std::string rTxn = sqlEscape(rawTxn.peekData()); + return str(bfTrans % getTransactionID().GetHex() % getTransactionType() % getSourceAccount().humanAccountID() % getSequence() % inLedger % status % rTxn % escapedMetaData); diff --git a/src/cpp/ripple/SerializedTransaction.h b/src/cpp/ripple/SerializedTransaction.h index c026d0220..5294532f6 100644 --- a/src/cpp/ripple/SerializedTransaction.h +++ b/src/cpp/ripple/SerializedTransaction.h @@ -60,7 +60,7 @@ public: uint32 getSequence() const { return getFieldU32(sfSequence); } void setSequence(uint32 seq) { return setFieldU32(sfSequence, seq); } - std::vector getAffectedAccounts() const; + std::vector getMentionedAccounts() const; uint256 getTransactionID() const; diff --git a/src/cpp/ripple/SerializedTypes.h b/src/cpp/ripple/SerializedTypes.h index c29e4c69f..baa995e72 100644 --- a/src/cpp/ripple/SerializedTypes.h +++ b/src/cpp/ripple/SerializedTypes.h @@ -373,9 +373,10 @@ public: // 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& saOfferRate, const STAmount& saOfferFunds, const STAmount& saTakerFunds, const STAmount& saOfferPays, const STAmount& saOfferGets, - const STAmount& saTakerPays, const STAmount& saTakerGets, + const STAmount& saTakerGets, STAmount& saTakerPaid, STAmount& saTakerGot, STAmount& saTakerIssuerFee, STAmount& saOfferIssuerFee); @@ -779,11 +780,17 @@ public: virtual bool isDefault() const { return mValue.empty(); } std::vector getValue() const { return mValue; } + int size() const { return mValue.size(); } bool isEmpty() const { return mValue.empty(); } + + const uint256& at(int i) const { assert((i >= 0) && (i < size())); return mValue.at(i); } + uint256& at(int i) { assert((i >= 0) && (i < size())); return mValue.at(i); } + 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/cpp/ripple/SerializedValidation.cpp b/src/cpp/ripple/SerializedValidation.cpp index c580e1a26..f50ed0894 100644 --- a/src/cpp/ripple/SerializedValidation.cpp +++ b/src/cpp/ripple/SerializedValidation.cpp @@ -1,12 +1,12 @@ #include "SerializedValidation.h" -#include "HashPrefixes.h" +#include "Config.h" #include "Log.h" DECLARE_INSTANCE(SerializedValidation); -std::vector sValidationFormat; +std::vector sValidationFormat; static bool SVFInit() { @@ -70,7 +70,7 @@ void SerializedValidation::sign(uint256& signingHash, const RippleAddress& raPri uint256 SerializedValidation::getSigningHash() const { - return STObject::getSigningHash(sHP_Validation); + return STObject::getSigningHash(theConfig.SIGN_VALIDATION); } uint256 SerializedValidation::getLedgerHash() const diff --git a/src/cpp/ripple/Suppression.cpp b/src/cpp/ripple/Suppression.cpp index 5eee0ab7f..1bd594d40 100644 --- a/src/cpp/ripple/Suppression.cpp +++ b/src/cpp/ripple/Suppression.cpp @@ -1,4 +1,3 @@ - #include "Suppression.h" #include @@ -116,4 +115,4 @@ bool SuppressionTable::swapSet(const uint256& index, std::set& peers, in s.setFlag(flag); return true; -} \ No newline at end of file +} diff --git a/src/cpp/ripple/TaggedCache.h b/src/cpp/ripple/TaggedCache.h index 1fbaae9ad..12ac56b76 100644 --- a/src/cpp/ripple/TaggedCache.h +++ b/src/cpp/ripple/TaggedCache.h @@ -26,24 +26,31 @@ extern LogPartition TaggedCachePartition; 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 boost::weak_ptr weak_data_ptr; - typedef boost::shared_ptr data_ptr; - typedef std::pair cache_entry; - typedef std::pair cache_pair; + typedef bool (*visitor_func)(const c_Key&, c_Data&); protected: + + typedef std::pair cache_entry; + typedef std::pair cache_pair; + typedef boost::unordered_map cache_type; + typedef typename cache_type::iterator cache_iterator; + typedef boost::unordered_map map_type; + typedef typename map_type::iterator map_iterator; + mutable boost::recursive_mutex mLock; - std::string mName; - int mTargetSize, mTargetAge; + std::string mName; // Used for logging + unsigned int mTargetSize; // Desired number of cache entries (0 = ignore) + int mTargetAge; // Desired maximum cache age - boost::unordered_map mCache; // Hold strong reference to recent objects - time_t mLastSweep; - - boost::unordered_map mMap; // Track stored objects + cache_type mCache; // Hold strong reference to recent objects + map_type mMap; // Track stored objects + time_t mLastSweep; public: TaggedCache(const char *name, int size, int age) @@ -59,9 +66,11 @@ public: void setTargetSize(int size); void setTargetAge(int age); void sweep(); + void visitAll(visitor_func); // Visits all tracked objects, removes selected objects + void visitCached(visitor_func); // Visits all cached objects, uncaches selected objects bool touch(const key_type& key); - bool del(const key_type& key); + bool del(const key_type& key, bool valid); 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); @@ -98,26 +107,42 @@ template void TaggedCache::sweep { boost::recursive_mutex::scoped_lock sl(mLock); - mLastSweep = time(NULL); + time_t mLastSweep = time(NULL); time_t target = mLastSweep - mTargetAge; // Pass 1, remove old objects from cache int cacheRemovals = 0; - typename boost::unordered_map::iterator cit = mCache.begin(); - while (cit != mCache.end()) + if ((mTargetSize == 0) || (mCache.size() > mTargetSize)) { - if (cit->second.first < target) + if (mTargetSize != 0) { - ++cacheRemovals; - mCache.erase(cit++); + target = mLastSweep - (mTargetAge * mTargetSize / mCache.size()); + if (target > (mLastSweep - 2)) + target = mLastSweep - 2; + + Log(lsINFO, TaggedCachePartition) << mName << " is growing fast " << + mCache.size() << " of " << mTargetSize << + " aging at " << (mLastSweep - target) << " of " << mTargetAge; } else - ++cit; + target = mLastSweep - mTargetAge; + + cache_iterator cit = mCache.begin(); + while (cit != mCache.end()) + { + if (cit->second.first < target) + { + ++cacheRemovals; + mCache.erase(cit++); + } + else + ++cit; + } } // Pass 2, remove dead objects from map int mapRemovals = 0; - typename boost::unordered_map::iterator mit = mMap.begin(); + map_iterator mit = mMap.begin(); while (mit != mMap.end()) { if (mit->second.expired()) @@ -134,12 +159,46 @@ template void TaggedCache::sweep ", map = " << mMap.size() << "-" << mapRemovals; } +template void TaggedCache::visitAll(visitor_func func) +{ // Visits all tracked objects, removes selected objects + boost::recursive_mutex::scoped_lock sl(mLock); + + map_iterator mit = mMap.begin(); + while (mit != mMap.end()) + { + data_ptr cachedData = mit->second.lock(); + if (!cachedData) + mMap.erase(mit++); // dead reference found + else if (func(mit->first, mit->second)) + { + mCache.erase(mit->first); + mMap.erase(mit++); + } + else + ++mit; + } +} + +template void TaggedCache::visitCached(visitor_func func) +{ // Visits all cached objects, uncaches selected objects + boost::recursive_mutex::scoped_lock sl(mLock); + + cache_iterator cit = mCache.begin(); + while (cit != mCache.end()) + { + if (func(cit->first, cit->second.second)) + mCache.erase(cit++); + else + ++cit; + } +} + template bool TaggedCache::touch(const key_type& key) { // If present, make current in cache boost::recursive_mutex::scoped_lock sl(mLock); // Is the object in the map? - typename boost::unordered_map::iterator mit = mMap.find(key); + map_iterator mit = mMap.find(key); if (mit == mMap.end()) return false; if (mit->second.expired()) @@ -149,7 +208,7 @@ template bool TaggedCache::touch } // Is the object in the cache? - typename boost::unordered_map::iterator cit = mCache.find(key); + cache_iterator cit = mCache.find(key); if (cit != mCache.end()) { // in both map and cache cit->second.first = time(NULL); @@ -157,15 +216,23 @@ template bool TaggedCache::touch } // In map but not cache, put in cache - mCache.insert(cache_pair(key, cache_entry(time(NULL), weak_data_ptr(cit->second.second)))); + mCache.insert(cache_pair(key, cache_entry(time(NULL), data_ptr(cit->second.second)))); return true; } -template bool TaggedCache::del(const key_type& key) -{ // Remove from cache, map unaffected +template bool TaggedCache::del(const key_type& key, bool valid) +{ // Remove from cache, if !valid, remove from map too. Returns true if removed from cache boost::recursive_mutex::scoped_lock sl(mLock); - typename boost::unordered_map::iterator cit = mCache.find(key); + if (!valid) + { // remove from map too + map_iterator mit = mMap.find(key); + if (mit == mMap.end()) // not in map, cannot be in cache + return false; + mMap.erase(mit); + } + + cache_iterator cit = mCache.find(key); if (cit == mCache.end()) return false; mCache.erase(cit); @@ -178,7 +245,7 @@ bool TaggedCache::canonicalize(const key_type& key, boost::shared // Return values: true=we had the data already boost::recursive_mutex::scoped_lock sl(mLock); - typename boost::unordered_map::iterator mit = mMap.find(key); + map_iterator mit = mMap.find(key); if (mit == mMap.end()) { // not in map mCache.insert(cache_pair(key, cache_entry(time(NULL), data))); @@ -186,7 +253,7 @@ bool TaggedCache::canonicalize(const key_type& key, boost::shared return false; } - boost::shared_ptr cachedData = mit->second.lock(); + data_ptr cachedData = mit->second.lock(); if (!cachedData) { // in map, but expired. Update in map, insert in cache mit->second = data; @@ -201,7 +268,7 @@ bool TaggedCache::canonicalize(const key_type& key, boost::shared data = cachedData; // Valid in map, is it in cache? - typename boost::unordered_map::iterator cit = mCache.find(key); + cache_iterator cit = mCache.find(key); if (cit != mCache.end()) { cit->second.first = time(NULL); // Yes, refesh @@ -219,42 +286,45 @@ boost::shared_ptr TaggedCache::fetch(const key_type& key) { // fetch us a shared pointer to the stored data object boost::recursive_mutex::scoped_lock sl(mLock); + // Is it in the cache? + cache_iterator cit = mCache.find(key); + if (cit != mCache.end()) + { + cit->second.first = time(NULL); // Yes, refresh + return cit->second.second; + } + // Is it in the map? - typename boost::unordered_map::iterator mit = mMap.find(key); + map_iterator mit = mMap.find(key); if (mit == mMap.end()) return data_ptr(); // No, we're done - boost::shared_ptr cachedData = mit->second.lock(); + data_ptr cachedData = mit->second.lock(); if (!cachedData) { // in map, but expired. Sorry, we don't have it mMap.erase(mit); return cachedData; } - // Valid in map, is it in the cache? - typename boost::unordered_map::iterator cit = mCache.find(key); - if (cit != mCache.end()) - cit->second.first = time(NULL); // Yes, refresh - else // No, add to cache - mCache.insert(cache_pair(key, cache_entry(time(NULL), cachedData))); - + // Put it back in the cache + mCache.insert(cache_pair(key, cache_entry(time(NULL), cachedData))); return cachedData; } template bool TaggedCache::store(const key_type& key, const c_Data& data) { - boost::shared_ptr d = boost::make_shared(boost::cref(data)); + data_ptr d = boost::make_shared(boost::cref(data)); return canonicalize(key, d); } template bool TaggedCache::retrieve(const key_type& key, c_Data& data) { // retrieve the value of the stored data - boost::shared_ptr dataPtr = fetch(key); - if (!dataPtr) + data_ptr entry = fetch(key); + if (!entry) return false; - data = *dataPtr; + data = *entry; return true; } diff --git a/src/cpp/ripple/Transaction.h b/src/cpp/ripple/Transaction.h index 35d33b91a..29505f285 100644 --- a/src/cpp/ripple/Transaction.h +++ b/src/cpp/ripple/Transaction.h @@ -84,7 +84,7 @@ public: 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); } + uint32 getSourceTag() const { return mTransaction->getFieldU32(sfSourceTag); } std::vector getSignature() const { return mTransaction->getSignature(); } uint32 getLedger() const { return mInLedger; } TransStatus getStatus() const { return mStatus; } diff --git a/src/cpp/ripple/TransactionEngine.cpp b/src/cpp/ripple/TransactionEngine.cpp index 3899ebfb4..4a31227cc 100644 --- a/src/cpp/ripple/TransactionEngine.cpp +++ b/src/cpp/ripple/TransactionEngine.cpp @@ -23,7 +23,7 @@ DECLARE_INSTANCE(TransactionEngine); void TransactionEngine::txnWrite() { // Write back the account states - typedef std::pair u256_LES_pair; + typedef std::map::value_type u256_LES_pair; BOOST_FOREACH(u256_LES_pair& it, mNodes) { const SLE::pointer& sleEntry = it.second.mEntry; @@ -67,9 +67,11 @@ void TransactionEngine::txnWrite() } } -TER TransactionEngine::applyTransaction(const SerializedTransaction& txn, TransactionEngineParams params) +TER TransactionEngine::applyTransaction(const SerializedTransaction& txn, TransactionEngineParams params, + bool& didApply) { cLog(lsTRACE) << "applyTransaction>"; + didApply = false; assert(mLedger); mNodes.init(mLedger, txn.getTransactionID(), mLedger->getLedgerSeq()); @@ -91,8 +93,8 @@ TER TransactionEngine::applyTransaction(const SerializedTransaction& txn, Transa } #endif - Transactor::pointer transactor=Transactor::makeTransactor(txn,params,this); - if(transactor) + std::auto_ptr transactor = Transactor::makeTransactor(txn,params,this); + if (transactor.get() != NULL) { uint256 txID = txn.getTransactionID(); if (!txID) @@ -110,15 +112,48 @@ TER TransactionEngine::applyTransaction(const SerializedTransaction& txn, Transa cLog(lsINFO) << "applyTransaction: terResult=" << strToken << " : " << terResult << " : " << strHuman; - if (isTepPartial(terResult) && isSetBit(params, tapRETRY)) - { - // Partial result and allowed to retry, reclassify as a retry. - terResult = terRETRY; - } + if (isTesSuccess(terResult)) + didApply = true; + else if (isTecClaim(terResult) && !isSetBit(params, tapRETRY)) + { // only claim the transaction fee + cLog(lsDEBUG) << "Reprocessing to only claim fee"; + mNodes.clear(); - if ((tesSUCCESS == terResult) || isTepPartial(terResult)) + SLE::pointer txnAcct = entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(txn.getSourceAccount())); + if (!txnAcct) + terResult = terNO_ACCOUNT; + else + { + uint32 t_seq = txn.getSequence(); + uint32 a_seq = txnAcct->getFieldU32(sfSequence); + + if (a_seq < t_seq) + terResult = terPRE_SEQ; + else if (a_seq > t_seq) + terResult = tefPAST_SEQ; + else + { + STAmount fee = txn.getTransactionFee(); + STAmount balance = txnAcct->getFieldAmount(sfBalance); + + if (balance < fee) + terResult = terINSUF_FEE_B; + else + { + txnAcct->setFieldAmount(sfBalance, balance - fee); + txnAcct->setFieldU32(sfSequence, t_seq + 1); + entryModify(txnAcct); + didApply = true; + } + } + } + } + else + cLog(lsDEBUG) << "Not applying transaction"; + + if (didApply) { - // Transaction succeeded fully or (retries are not allowed and the transaction succeeded partially). + // Transaction succeeded fully or (retries are not allowed and the transaction could claim a fee) Serializer m; mNodes.calcRawMeta(m, terResult, mTxnSeq++); @@ -137,8 +172,8 @@ TER TransactionEngine::applyTransaction(const SerializedTransaction& txn, Transa if (!mLedger->addTransaction(txID, s, m)) assert(false); - STAmount saPaid = txn.getTransactionFee(); // Charge whatever fee they specified. + STAmount saPaid = txn.getTransactionFee(); mLedger->destroyCoins(saPaid.getNValue()); } } @@ -146,8 +181,7 @@ TER TransactionEngine::applyTransaction(const SerializedTransaction& txn, Transa mTxnAccount.reset(); mNodes.clear(); - if (!isSetBit(params, tapOPEN_LEDGER) - && (isTemMalformed(terResult) || isTefFailure(terResult))) + if (!isSetBit(params, tapOPEN_LEDGER) && isTemMalformed(terResult)) { // XXX Malformed or failed transaction in closed ledger must bow out. } diff --git a/src/cpp/ripple/TransactionEngine.h b/src/cpp/ripple/TransactionEngine.h index 0139c4291..ab80191c8 100644 --- a/src/cpp/ripple/TransactionEngine.h +++ b/src/cpp/ripple/TransactionEngine.h @@ -70,7 +70,7 @@ public: TransactionEngine(Ledger::ref ledger) : mLedger(ledger), mTxnSeq(0) { assert(mLedger); } LedgerEntrySet& getNodes() { return mNodes; } - Ledger::pointer getLedger() { return mLedger; } + Ledger::ref getLedger() { return mLedger; } void setLedger(Ledger::ref ledger) { assert(ledger); mLedger = ledger; } SLE::pointer entryCreate(LedgerEntryType type, const uint256& index) { return mNodes.entryCreate(type, index); } @@ -78,7 +78,7 @@ public: void entryDelete(SLE::ref sleEntry) { mNodes.entryDelete(sleEntry); } void entryModify(SLE::ref sleEntry) { mNodes.entryModify(sleEntry); } - TER applyTransaction(const SerializedTransaction&, TransactionEngineParams); + TER applyTransaction(const SerializedTransaction&, TransactionEngineParams, bool& didApply); }; inline TransactionEngineParams operator|(const TransactionEngineParams& l1, const TransactionEngineParams& l2) diff --git a/src/cpp/ripple/TransactionErr.cpp b/src/cpp/ripple/TransactionErr.cpp index a28ab5bc8..3ffe6d639 100644 --- a/src/cpp/ripple/TransactionErr.cpp +++ b/src/cpp/ripple/TransactionErr.cpp @@ -8,56 +8,81 @@ bool transResultInfo(TER terCode, std::string& strToken, std::string& strHuman) const char* cpToken; const char* cpHuman; } transResultInfoA[] = { + { tecCLAIM, "tecCLAIM", "Fee claimed. Sequence used. No action." }, + { tecDIR_FULL, "tecDIR_FULL", "Can not add entry to full directory." }, + { tecINSUF_RESERVE_LINE, "tecINSUF_RESERVE_LINE", "Insufficent reserve to add trust line." }, + { tecINSUF_RESERVE_OFFER, "tecINSUF_RESERVE_OFFER", "Insufficent reserve to create offer." }, + { tecNO_DST, "tecNO_DST", "Destination does not exist. Send XRP to create it." }, + { tecNO_DST_INSUF_XRP, "tecNO_DST_INSUF_XRP", "Destination does not exist. Too little XRP sent to create it." }, + { tecNO_LINE_INSUF_RESERVE, "tecNO_LINE_INSUF_RESERVE", "No such line. Too little reserve to create it." }, + { tecNO_LINE_REDUNDANT, "tecNO_LINE_REDUNDANT", "Can't set non-existant line to default." }, + { tecPATH_DRY, "tecPATH_DRY", "Path could not send partial amount." }, + { tecPATH_PARTIAL, "tecPATH_PARTIAL", "Path could not send full amount." }, + + { tecUNFUNDED, "tecUNFUNDED", "One of _ADD, _OFFER, or _SEND. Deprecated." }, + { tecUNFUNDED_ADD, "tecUNFUNDED_ADD", "Insufficient XRP balance for WalletAdd." }, + { tecUNFUNDED_OFFER, "tecUNFUNDED_OFFER", "Insufficient balance to fund created offer." }, + { tecUNFUNDED_PAYMENT, "tecUNFUNDED_PAYMENT", "Insufficient XRP balance to send." }, + + { tefFAILURE, "tefFAILURE", "Failed to apply." }, { 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_CLAIM_ID, "tefBAD_CLAIM_ID", "Malformed: Bad claim id." }, { 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" }, + { tefNO_AUTH_REQUIRED, "tefNO_AUTH_REQUIRED", "Auth is not required." }, + { tefPAST_SEQ, "tefPAST_SEQ", "This sequence number has already past." }, - { telBAD_PATH_COUNT, "telBAD_PATH_COUNT", "Malformed: too many paths." }, + { telLOCAL_ERROR, "telLOCAL_ERROR", "Local failure." }, + { telBAD_DOMAIN, "telBAD_DOMAIN", "Domain too long." }, + { telBAD_PATH_COUNT, "telBAD_PATH_COUNT", "Malformed: Too many paths." }, + { telBAD_PUBLIC_KEY, "telBAD_PUBLIC_KEY", "Public key too long." }, { telINSUF_FEE_P, "telINSUF_FEE_P", "Fee insufficient." }, + { telNO_DST_PARTIAL, "telNO_DST_PARTIAL", "Partial payment to create account not allowed." }, + { temMALFORMED, "temMALFORMED", "Malformed transaction." }, { 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_FEE, "temBAD_FEE", "Invalid fee, negative or not XRP." }, + { temBAD_EXPIRATION, "temBAD_EXPIRATION", "Malformed: Bad expiration." }, + { temBAD_ISSUER, "temBAD_ISSUER", "Malformed: Bad issuer." }, + { temBAD_LIMIT, "temBAD_LIMIT", "Limits must be non-negative." }, + { temBAD_OFFER, "temBAD_OFFER", "Malformed: Bad offer." }, + { temBAD_PATH, "temBAD_PATH", "Malformed: Bad path." }, + { temBAD_PATH_LOOP, "temBAD_PATH_LOOP", "Malformed: Loop in path." }, { temBAD_PUBLISH, "temBAD_PUBLISH", "Malformed: Bad publish." }, + { temBAD_SIGNATURE, "temBAD_SIGNATURE", "Malformed: Bad signature." }, + { temBAD_SRC_ACCOUNT, "temBAD_SRC_ACCOUNT", "Malformed: Bad source account." }, { temBAD_TRANSFER_RATE, "temBAD_TRANSFER_RATE", "Malformed: Transfer rate must be >= 1.0" }, - { temBAD_SET_ID, "temBAD_SET_ID", "Malformed." }, - { temBAD_SEQUENCE, "temBAD_SEQUENCE", "Malformed: Sequence in not in the past." }, + { temBAD_SEQUENCE, "temBAD_SEQUENCE", "Malformed: Sequence is not in the past." }, + { temBAD_SEND_XRP_LIMIT, "temBAD_SEND_XRP_LIMIT", "Malformed: Limit quality is not allowed for XRP to XRP." }, + { temBAD_SEND_XRP_MAX, "temBAD_SEND_XRP_MAX", "Malformed: Send max is not allowed for XRP to XRP." }, + { temBAD_SEND_XRP_NO_DIRECT, "temBAD_SEND_XRP_NO_DIRECT", "Malformed: No Ripple direct is not allowed for XRP to XRP." }, + { temBAD_SEND_XRP_PARTIAL, "temBAD_SEND_XRP_PARTIAL", "Malformed: Partial payment is not allowed for XRP to XRP." }, + { temBAD_SEND_XRP_PATHS, "temBAD_SEND_XRP_PATHS", "Malformed: Paths are not allowed for XRP to XRP." }, { 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." }, + { temDST_TAG_NEEDED, "temDST_TAG_NEEDED", "Destination tag required." }, { temINVALID, "temINVALID", "The transaction is ill-formed." }, { temINVALID_FLAG, "temINVALID_FLAG", "The transaction has an invalid flag." }, { temREDUNDANT, "temREDUNDANT", "Sends same currency to self." }, + { temREDUNDANT_SEND_MAX, "temREDUNDANT_SEND_MAX", "Send max is redundant." }, { 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." }, + { terRETRY, "terRETRY", "Retry transaction." }, { 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", "Destination does not exist. Send XRP to create it." }, - { terNO_DST_INSUF_XRP, "terNO_DST_INSUF_XRP", "Destination does not exist. Too little XRP sent to create it." }, { 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." }, { 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." }, + { terOWNERS, "terOWNERS", "Non-zero owner count." }, { tesSUCCESS, "tesSUCCESS", "The transaction was applied." }, }; diff --git a/src/cpp/ripple/TransactionErr.h b/src/cpp/ripple/TransactionErr.h index 8292dd3aa..97f33f091 100644 --- a/src/cpp/ripple/TransactionErr.h +++ b/src/cpp/ripple/TransactionErr.h @@ -13,8 +13,11 @@ enum TER // aka TransactionEngineResult // - Not forwarded // - No fee check telLOCAL_ERROR = -399, + telBAD_DOMAIN, telBAD_PATH_COUNT, + telBAD_PUBLIC_KEY, telINSUF_FEE_P, + telNO_DST_PARTIAL, // -299 .. -200: M Malformed (bad signature) // Causes: @@ -27,21 +30,30 @@ enum TER // aka TransactionEngineResult temMALFORMED = -299, temBAD_AMOUNT, temBAD_AUTH_MASTER, + temBAD_FEE, temBAD_EXPIRATION, temBAD_ISSUER, + temBAD_LIMIT, temBAD_OFFER, temBAD_PATH, temBAD_PATH_LOOP, temBAD_PUBLISH, temBAD_TRANSFER_RATE, + temBAD_SEND_XRP_LIMIT, + temBAD_SEND_XRP_MAX, + temBAD_SEND_XRP_NO_DIRECT, + temBAD_SEND_XRP_PARTIAL, + temBAD_SEND_XRP_PATHS, + temBAD_SIGNATURE, + temBAD_SRC_ACCOUNT, temBAD_SEQUENCE, - temBAD_SET_ID, temDST_IS_SRC, temDST_NEEDED, - temINSUF_FEE_P, + temDST_TAG_NEEDED, temINVALID, temINVALID_FLAG, temREDUNDANT, + temREDUNDANT_SEND_MAX, temRIPPLE_EMPTY, temUNCERTAIN, // An intermediate result used internally, should never be returned. temUNKNOWN, @@ -66,6 +78,7 @@ enum TER // aka TransactionEngineResult tefCREATED, tefEXCEPTION, tefGEN_IN_USE, + tefNO_AUTH_REQUIRED, // Can't set auth if auth is not required. tefPAST_SEQ, // -99 .. -1: R Retry (sequence too high, no funds for txn fee, originating account non-existent) @@ -76,18 +89,14 @@ enum TER // aka TransactionEngineResult // - Not forwarded // - Might succeed later // - Hold + // - Makes hole in sequence which jams transactions. terRETRY = -99, - terDIR_FULL, - terFUNDS_SPENT, - terINSUF_FEE_B, - terNO_ACCOUNT, - terNO_DST, - terNO_DST_INSUF_XRP, - terNO_LINE, - terNO_LINE_NO_ZERO, - terPRE_SEQ, - terSET_MISSING_DST, - terUNFUNDED, + terFUNDS_SPENT, // This is a free transaction, therefore don't burden network. + terINSUF_FEE_B, // Can't pay fee, therefore don't burden network. + terNO_ACCOUNT, // Can't pay fee, therefore don't burden network. + terNO_LINE, // Internal flag. + terOWNERS, // Can't succeed with non-zero owner count. + terPRE_SEQ, // Can't pay fee, no point in forwarding, therefore don't burden network. // 0: S Success (success) // Causes: @@ -97,25 +106,38 @@ enum TER // aka TransactionEngineResult // - Forwarded tesSUCCESS = 0, - // 100 .. P Partial success (SR) (ripple transaction with no good paths, pay to non-existent account) + // 100 .. 129 C Claim fee only (ripple transaction with no good paths, pay to non-existent account, no path) // Causes: // - Success, but does not achieve optimal result. + // - Invalid transaction or no effect, but claim fee to use the sequence number. // Implications: // - Applied // - Forwarded // Only allowed as a return code of appliedTransaction when !tapRetry. Otherwise, treated as terRETRY. - // CAUTION: The numerical values for these results are part of the binary formats - tepPARTIAL = 100, - tepPATH_DRY = 101, - tepPATH_PARTIAL = 102, + // + // DO NOT CHANGE THESE NUMBERS: They appear in ledger meta data. + tecCLAIM = 100, + tecPATH_PARTIAL = 101, + tecUNFUNDED_ADD = 102, + tecUNFUNDED_OFFER = 103, + tecUNFUNDED_PAYMENT = 104, + tecDIR_FULL = 121, + tecINSUF_RESERVE_LINE = 122, + tecINSUF_RESERVE_OFFER = 123, + tecNO_DST = 124, + tecNO_DST_INSUF_XRP = 125, + tecNO_LINE_INSUF_RESERVE = 126, + tecNO_LINE_REDUNDANT = 127, + tecPATH_DRY = 128, + tecUNFUNDED = 129, // Deprecated, old ambiguous unfunded. }; #define isTelLocal(x) ((x) >= telLOCAL_ERROR && (x) < temMALFORMED) #define isTemMalformed(x) ((x) >= temMALFORMED && (x) < tefFAILURE) #define isTefFailure(x) ((x) >= tefFAILURE && (x) < terRETRY) #define isTerRetry(x) ((x) >= terRETRY && (x) < tesSUCCESS) -#define isTepSuccess(x) ((x) >= tesSUCCESS) -#define isTepPartial(x) ((x) >= tepPATH_PARTIAL) +#define isTesSuccess(x) ((x) == tesSUCCESS) +#define isTecClaim(x) ((x) >= tecCLAIM) bool transResultInfo(TER terCode, std::string& strToken, std::string& strHuman); std::string transToken(TER terCode); diff --git a/src/cpp/ripple/TransactionFormats.cpp b/src/cpp/ripple/TransactionFormats.cpp index 683517892..effb98369 100644 --- a/src/cpp/ripple/TransactionFormats.cpp +++ b/src/cpp/ripple/TransactionFormats.cpp @@ -5,7 +5,7 @@ std::map TransactionFormat::byName; #define TF_BASE \ << SOElement(sfTransactionType, SOE_REQUIRED) \ - << SOElement(sfFlags, SOE_REQUIRED) \ + << SOElement(sfFlags, SOE_OPTIONAL) \ << SOElement(sfSourceTag, SOE_OPTIONAL) \ << SOElement(sfAccount, SOE_REQUIRED) \ << SOElement(sfSequence, SOE_REQUIRED) \ @@ -55,6 +55,7 @@ static bool TFInit() << SOElement(sfSendMax, SOE_OPTIONAL) << SOElement(sfPaths, SOE_DEFAULT) << SOElement(sfInvoiceID, SOE_OPTIONAL) + << SOElement(sfDestinationTag, SOE_OPTIONAL) ; DECLARE_TF(Contract, ttCONTRACT) @@ -76,6 +77,14 @@ static bool TFInit() << SOElement(sfFeature, SOE_REQUIRED) ; + DECLARE_TF(SetFee, ttFEE) + << SOElement(sfFeatures, SOE_REQUIRED) + << SOElement(sfBaseFee, SOE_REQUIRED) + << SOElement(sfReferenceFeeUnits, SOE_REQUIRED) + << SOElement(sfReserveBase, SOE_REQUIRED) + << SOElement(sfReserveIncrement, SOE_REQUIRED) + ; + return true; } diff --git a/src/cpp/ripple/TransactionFormats.h b/src/cpp/ripple/TransactionFormats.h index 778da0405..ceb5c9b7d 100644 --- a/src/cpp/ripple/TransactionFormats.h +++ b/src/cpp/ripple/TransactionFormats.h @@ -2,10 +2,12 @@ #define __TRANSACTIONFORMATS__ #include "SerializedObject.h" +#include "LedgerFormats.h" enum TransactionType { ttINVALID = -1, + ttPAYMENT = 0, ttCLAIM = 1, // open ttWALLET_ADD = 2, @@ -18,9 +20,10 @@ enum TransactionType ttCONTRACT = 9, ttCONTRACT_REMOVE = 10, // can we use the same msg as offer cancel - ttTRUST_SET = 20, + ttTRUST_SET = 20, - ttFEATURE = 100, + ttFEATURE = 100, + ttFEE = 101, }; class TransactionFormat @@ -28,7 +31,7 @@ class TransactionFormat public: std::string t_name; TransactionType t_type; - std::vector elements; + std::vector elements; static std::map byType; static std::map byName; @@ -56,17 +59,26 @@ const int TransactionMaxLen = 1048576; // Transaction flags. // +// AccountSet flags: +const uint32 tfRequireDestTag = 0x00010000; +const uint32 tfOptionalDestTag = 0x00020000; +const uint32 tfRequireAuth = 0x00040000; +const uint32 tfOptionalAuth = 0x00080000; +const uint32 tfAccountSetMask = ~(tfRequireDestTag|tfOptionalDestTag|tfRequireAuth|tfOptionalAuth); + // OfferCreate flags: const uint32 tfPassive = 0x00010000; const uint32 tfOfferCreateMask = ~(tfPassive); // Payment flags: -const uint32 tfPaymentLegacy = 0x00010000; // Left here to avoid ledger change. +const uint32 tfNoRippleDirect = 0x00010000; const uint32 tfPartialPayment = 0x00020000; const uint32 tfLimitQuality = 0x00040000; -const uint32 tfNoRippleDirect = 0x00080000; - const uint32 tfPaymentMask = ~(tfPartialPayment|tfLimitQuality|tfNoRippleDirect); +// TrustSet flags: +const uint32 tfSetfAuth = 0x00010000; +const uint32 tfTrustSetMask = ~(tfSetfAuth); + #endif // vim:ts=4 diff --git a/src/cpp/ripple/TransactionMeta.cpp b/src/cpp/ripple/TransactionMeta.cpp index 6fc6e85bc..bd87829a7 100644 --- a/src/cpp/ripple/TransactionMeta.cpp +++ b/src/cpp/ripple/TransactionMeta.cpp @@ -7,6 +7,9 @@ #include #include "Log.h" +#include "SerializedObject.h" + +SETUP_LOG(); TransactionMetaSet::TransactionMetaSet(const uint256& txid, uint32 ledger, const std::vector& vec) : mTransactionID(txid), mLedger(ledger), mNodes(sfAffectedNodes, 32) @@ -52,33 +55,59 @@ void TransactionMetaSet::setAffectedNode(const uint256& node, SField::ref type, obj.setFieldU16(sfLedgerEntryType, nodeType); } -/* +static void addIfUnique(std::vector& vector, const RippleAddress& address) +{ + BOOST_FOREACH(const RippleAddress& a, vector) + if (a == address) + return; + vector.push_back(address); +} + std::vector TransactionMetaSet::getAffectedAccounts() { std::vector accounts; + accounts.reserve(10); - BOOST_FOREACH(STObject& object, mNodes.getValue() ) + BOOST_FOREACH(const STObject& it, mNodes) { - const STAccount* sa = dynamic_cast(&it); - if (sa != NULL) + int index = it.getFieldIndex((it.getFName() == sfCreatedNode) ? sfNewFields : sfFinalFields); + if (index != -1) { - bool found = false; - RippleAddress na = sa->getValueNCA(); - BOOST_FOREACH(const RippleAddress& it, accounts) + const STObject *inner = dynamic_cast(&it.peekAtIndex(index)); + if (inner) { - if (it == na) + BOOST_FOREACH(const SerializedType& field, inner->peekData()) { - found = true; - break; + const STAccount* sa = dynamic_cast(&field); + if (sa) + addIfUnique(accounts, sa->getValueNCA()); + else if ((field.getFName() == sfLowLimit) || (field.getFName() == sfHighLimit) || + (field.getFName() == sfTakerPays) || (field.getFName() == sfTakerGets)) + { + const STAmount* lim = dynamic_cast(&field); + if (lim != NULL) + { + uint160 issuer = lim->getIssuer(); + if (issuer.isNonZero()) + { + RippleAddress na; + na.setAccountID(issuer); + addIfUnique(accounts, na); + } + } + else + { + cLog(lsFATAL) << "limit is not amount " << field.getJson(0); + } + } } } - if (!found) - accounts.push_back(na); + else assert(false); } } + return accounts; } -*/ STObject& TransactionMetaSet::getAffectedNode(SLE::ref node, SField::ref type) { diff --git a/src/cpp/ripple/TransactionMeta.h b/src/cpp/ripple/TransactionMeta.h index 3b8ae25e3..5386b96b8 100644 --- a/src/cpp/ripple/TransactionMeta.h +++ b/src/cpp/ripple/TransactionMeta.h @@ -49,7 +49,7 @@ public: STObject& getAffectedNode(SLE::ref node, SField::ref type); // create if needed STObject& getAffectedNode(const uint256&); const STObject& peekAffectedNode(const uint256&) const; - //std::vector getAffectedAccounts(); + std::vector getAffectedAccounts(); Json::Value getJson(int p) const { return getAsObject().getJson(p); } diff --git a/src/cpp/ripple/TransactionQueue.cpp b/src/cpp/ripple/TransactionQueue.cpp new file mode 100644 index 000000000..7bc6f2d32 --- /dev/null +++ b/src/cpp/ripple/TransactionQueue.cpp @@ -0,0 +1,98 @@ +#include "TransactionQueue.h" + +#include + +void TXQEntry::addCallbacks(const TXQEntry& otherEntry) +{ + BOOST_FOREACH(const stCallback& callback, otherEntry.mCallbacks) + mCallbacks.push_back(callback); +} + +void TXQEntry::doCallbacks(TER result) +{ + BOOST_FOREACH(const stCallback& callback, mCallbacks) + callback(mTxn, result); +} + +bool TXQueue::addEntryForSigCheck(TXQEntry::ref entry) +{ // we always dispatch a thread to check the signature + boost::mutex::scoped_lock sl(mLock); + + if (!mTxMap.insert(valueType(entry->getID(), entry)).second) + { + if (!entry->mCallbacks.empty()) + mTxMap.left.find(entry->getID())->second->addCallbacks(*entry); + return false; + } + return true; +} + +bool TXQueue::addEntryForExecution(TXQEntry::ref entry) +{ + boost::mutex::scoped_lock sl(mLock); + + entry->mSigChecked = true; + + std::pair it = mTxMap.insert(valueType(entry->getID(), entry)); + if (!it.second) + { // There was an existing entry + it.first->right->mSigChecked = true; + if (!entry->mCallbacks.empty()) + it.first->right->addCallbacks(*entry); + } + + if (mRunning) + return false; + + mRunning = true; + return true; // A thread needs to handle this account +} + +TXQEntry::pointer TXQueue::removeEntry(const uint256& id) +{ + TXQEntry::pointer ret; + + boost::mutex::scoped_lock sl(mLock); + + mapType::left_map::iterator it = mTxMap.left.find(id); + if (it != mTxMap.left.end()) + { + ret = it->second; + mTxMap.left.erase(it); + } + + return ret; +} + +void TXQueue::getJob(TXQEntry::pointer &job) +{ + boost::mutex::scoped_lock sl(mLock); + assert(mRunning); + + if (job) + mTxMap.left.erase(job->getID()); + + mapType::left_map::iterator it = mTxMap.left.begin(); + if (it == mTxMap.left.end() || !it->second->mSigChecked) + { + job.reset(); + mRunning = false; + } + else + job = it->second; +} + +bool TXQueue::stopProcessing(TXQEntry::ref finishedJob) +{ // returns true if a new thread must be dispatched + boost::mutex::scoped_lock sl(mLock); + assert(mRunning); + + mTxMap.left.erase(finishedJob->getID()); + + mapType::left_map::iterator it = mTxMap.left.begin(); + if ((it != mTxMap.left.end()) && it->second->mSigChecked) + return true; + + mRunning = false; + return false; +} diff --git a/src/cpp/ripple/TransactionQueue.h b/src/cpp/ripple/TransactionQueue.h new file mode 100644 index 000000000..5f8ef6914 --- /dev/null +++ b/src/cpp/ripple/TransactionQueue.h @@ -0,0 +1,74 @@ +#ifndef TRANSACTIONQUEUE__H +#define TRANSACTIONQUEUE__H + +// Allow transactions to be signature checked out of sequence but retired in sequence + +#include +#include +#include +#include +#include +#include + +#include "Transaction.h" + +class TXQeueue; + +class TXQEntry +{ + friend class TXQueue; + +public: + typedef boost::shared_ptr pointer; + typedef const boost::shared_ptr& ref; + typedef boost::function stCallback; // must complete immediately + +protected: + Transaction::pointer mTxn; + bool mSigChecked; + std::list mCallbacks; + + void addCallbacks(const TXQEntry& otherEntry); + +public: + TXQEntry(Transaction::ref tx, bool sigChecked) : mTxn(tx), mSigChecked(sigChecked) { ; } + TXQEntry() : mSigChecked(false) { ; } + + Transaction::ref getTransaction() const { return mTxn; } + bool getSigChecked() const { return mSigChecked; } + const uint256& getID() const { return mTxn->getID(); } + + void doCallbacks(TER); +}; + +class TXQueue +{ +protected: + typedef boost::bimaps::unordered_set_of leftType; + typedef boost::bimaps::list_of rightType; + typedef boost::bimap mapType; + typedef mapType::value_type valueType; + + mapType mTxMap; + bool mRunning; + boost::mutex mLock; + +public: + + TXQueue() { ; } + + // Return: true = must dispatch signature checker thread + bool addEntryForSigCheck(TXQEntry::ref); + + // Call only if signature is okay. Returns true if new account, must dispatch + bool addEntryForExecution(TXQEntry::ref); + + // Call if signature is bad (returns entry so you can run its callbacks) + TXQEntry::pointer removeEntry(const uint256& txID); + + // Transaction execution interface + void getJob(TXQEntry::pointer&); + bool stopProcessing(TXQEntry::ref finishedJob); +}; + +#endif diff --git a/src/cpp/ripple/Transactor.cpp b/src/cpp/ripple/Transactor.cpp index 01861525f..b9ace30fd 100644 --- a/src/cpp/ripple/Transactor.cpp +++ b/src/cpp/ripple/Transactor.cpp @@ -11,26 +11,26 @@ SETUP_LOG(); -Transactor::pointer Transactor::makeTransactor(const SerializedTransaction& txn,TransactionEngineParams params, TransactionEngine* engine) +std::auto_ptr Transactor::makeTransactor(const SerializedTransaction& txn,TransactionEngineParams params, TransactionEngine* engine) { switch(txn.getTxnType()) { case ttPAYMENT: - return( Transactor::pointer(new PaymentTransactor(txn,params,engine)) ); + return std::auto_ptr(new PaymentTransactor(txn, params, engine)); case ttACCOUNT_SET: - return( Transactor::pointer(new AccountSetTransactor(txn,params,engine)) ); + return std::auto_ptr(new AccountSetTransactor(txn, params, engine)); case ttREGULAR_KEY_SET: - return( Transactor::pointer(new RegularKeySetTransactor(txn,params,engine)) ); + return std::auto_ptr(new RegularKeySetTransactor(txn, params, engine)); case ttTRUST_SET: - return( Transactor::pointer(new TrustSetTransactor(txn,params,engine)) ); + return std::auto_ptr(new TrustSetTransactor(txn, params, engine)); case ttOFFER_CREATE: - return( Transactor::pointer(new OfferCreateTransactor(txn,params,engine)) ); + return std::auto_ptr(new OfferCreateTransactor(txn, params, engine)); case ttOFFER_CANCEL: - return( Transactor::pointer(new OfferCancelTransactor(txn,params,engine)) ); + return std::auto_ptr(new OfferCancelTransactor(txn, params, engine)); case ttWALLET_ADD: - return( Transactor::pointer(new WalletAddTransactor(txn,params,engine)) ); + return std::auto_ptr(new WalletAddTransactor(txn, params, engine)); default: - return(Transactor::pointer()); + return std::auto_ptr(); } } @@ -42,7 +42,12 @@ Transactor::Transactor(const SerializedTransaction& txn,TransactionEngineParams void Transactor::calculateFee() { - mFeeDue = theConfig.FEE_DEFAULT; + mFeeDue = STAmount(mEngine->getLedger()->scaleFeeLoad(calculateBaseFee())); +} + +uint64 Transactor::calculateBaseFee() +{ + return theConfig.FEE_DEFAULT; } TER Transactor::payFee() @@ -57,7 +62,10 @@ TER Transactor::payFee() return telINSUF_FEE_P; } - if( !saPaid ) return tesSUCCESS; + if (saPaid.isNegative() || !saPaid.isNative()) + return temBAD_FEE; + + if (!saPaid) return tesSUCCESS; // Deduct the fee, so it's not available during the transaction. // Will only write the account back, if the transaction succeeds. @@ -77,7 +85,6 @@ TER Transactor::payFee() return tesSUCCESS; } - TER Transactor::checkSig() { // Consistency: Check signature @@ -150,7 +157,7 @@ TER Transactor::preCheck() { cLog(lsWARNING) << "applyTransaction: bad source id"; - return temINVALID; + return temBAD_SRC_ACCOUNT; } // Extract signing key @@ -199,14 +206,14 @@ TER Transactor::apply() mHasAuthKey = mTxnAccount->isFieldPresent(sfRegularKey); } - terResult=payFee(); - if(terResult != tesSUCCESS) return(terResult); + terResult = payFee(); + if (terResult != tesSUCCESS) return(terResult); - terResult=checkSig(); - if(terResult != tesSUCCESS) return(terResult); + terResult = checkSig(); + if (terResult != tesSUCCESS) return(terResult); - terResult=checkSeq(); - if(terResult != tesSUCCESS) return(terResult); + terResult = checkSeq(); + if (terResult != tesSUCCESS) return(terResult); mEngine->entryModify(mTxnAccount); diff --git a/src/cpp/ripple/Transactor.h b/src/cpp/ripple/Transactor.h index 1c456637d..bce4c4e36 100644 --- a/src/cpp/ripple/Transactor.h +++ b/src/cpp/ripple/Transactor.h @@ -24,7 +24,11 @@ protected: TER checkSeq(); TER payFee(); - virtual void calculateFee(); + void calculateFee(); + + // Returns the fee, not scaled for load (Should be in fee units. FIXME) + virtual uint64 calculateBaseFee(); + virtual TER checkSig(); virtual TER doApply()=0; @@ -33,7 +37,7 @@ protected: public: typedef boost::shared_ptr pointer; - static Transactor::pointer makeTransactor(const SerializedTransaction& txn,TransactionEngineParams params, TransactionEngine* engine); + static std::auto_ptr makeTransactor(const SerializedTransaction& txn,TransactionEngineParams params, TransactionEngine* engine); TER apply(); }; diff --git a/src/cpp/ripple/TrustSetTransactor.cpp b/src/cpp/ripple/TrustSetTransactor.cpp index 54e1efc12..4a66bcd78 100644 --- a/src/cpp/ripple/TrustSetTransactor.cpp +++ b/src/cpp/ripple/TrustSetTransactor.cpp @@ -1,39 +1,66 @@ +#include "Application.h" + #include "TrustSetTransactor.h" -#include +SETUP_LOG(); TER TrustSetTransactor::doApply() { TER terResult = tesSUCCESS; - Log(lsINFO) << "doTrustSet>"; + cLog(lsINFO) << "doTrustSet>"; const STAmount saLimitAmount = mTxn.getFieldAmount(sfLimitAmount); const bool bQualityIn = mTxn.isFieldPresent(sfQualityIn); - const uint32 uQualityIn = bQualityIn ? mTxn.getFieldU32(sfQualityIn) : 0; const bool bQualityOut = mTxn.isFieldPresent(sfQualityOut); - const uint32 uQualityOut = bQualityIn ? mTxn.getFieldU32(sfQualityOut) : 0; const uint160 uCurrencyID = saLimitAmount.getCurrency(); uint160 uDstAccountID = saLimitAmount.getIssuer(); - const bool bFlipped = mTxnAccountID > uDstAccountID; // true, iff current is not lowest. - bool bDelIndex = false; + const bool bHigh = mTxnAccountID > uDstAccountID; // true, iff current is high account. - // Check if destination makes sense. + uint32 uQualityIn = bQualityIn ? mTxn.getFieldU32(sfQualityIn) : 0; + uint32 uQualityOut = bQualityIn ? mTxn.getFieldU32(sfQualityOut) : 0; + + if (bQualityIn && QUALITY_ONE == uQualityIn) + uQualityIn = 0; + + if (bQualityOut && QUALITY_ONE == uQualityOut) + uQualityOut = 0; + + const uint32 uTxFlags = mTxn.getFlags(); + + if (uTxFlags & tfTrustSetMask) + { + cLog(lsINFO) << "doTrustSet: Malformed transaction: Invalid flags set."; + + return temINVALID_FLAG; + } + + const bool bSetAuth = isSetBit(uTxFlags, tfSetfAuth); + + if (bSetAuth && !isSetBit(mTxnAccount->getFieldU32(sfFlags), lsfRequireAuth)) + { + cLog(lsINFO) << "doTrustSet: Retry: Auth not required."; + + return tefNO_AUTH_REQUIRED; + } if (saLimitAmount.isNegative()) { - Log(lsINFO) << "doTrustSet: Malformed transaction: Negatived credit limit."; + cLog(lsINFO) << "doTrustSet: Malformed transaction: Negatived credit limit."; - return temBAD_AMOUNT; + return temBAD_LIMIT; } - else if (!uDstAccountID || uDstAccountID == ACCOUNT_ONE) + + // Check if destination makes sense. + if (!uDstAccountID || uDstAccountID == ACCOUNT_ONE) { - Log(lsINFO) << "doTrustSet: Malformed transaction: Destination account not specified."; + cLog(lsINFO) << "doTrustSet: Malformed transaction: Destination account not specified."; return temDST_NEEDED; } - else if (mTxnAccountID == uDstAccountID) + + if (mTxnAccountID == uDstAccountID) { - Log(lsINFO) << "doTrustSet: Malformed transaction: Can not extend credit to self."; + cLog(lsINFO) << "doTrustSet: Malformed transaction: Can not extend credit to self."; return temDST_IS_SRC; } @@ -41,128 +68,253 @@ TER TrustSetTransactor::doApply() SLE::pointer sleDst = mEngine->entryCache(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uDstAccountID)); if (!sleDst) { - Log(lsINFO) << "doTrustSet: Delay transaction: Destination account does not exist."; + cLog(lsINFO) << "doTrustSet: Delay transaction: Destination account does not exist."; - return terNO_DST; + return tecNO_DST; } + const STAmount saSrcXRPBalance = mTxnAccount->getFieldAmount(sfBalance); + const uint32 uOwnerCount = mTxnAccount->getFieldU32(sfOwnerCount); + // The reserve required to create the line. + const uint64 uReserveCreate = mEngine->getLedger()->getReserve(uOwnerCount + 1); + STAmount saLimitAllow = saLimitAmount; saLimitAllow.setIssuer(mTxnAccountID); SLE::pointer sleRippleState = mEngine->entryCache(ltRIPPLE_STATE, Ledger::getRippleStateIndex(mTxnAccountID, uDstAccountID, uCurrencyID)); if (sleRippleState) { - // A line exists in one or more directions. + STAmount saLowBalance; + STAmount saLowLimit; + STAmount saHighBalance; + STAmount saHighLimit; + uint32 uLowQualityIn; + uint32 uLowQualityOut; + uint32 uHighQualityIn; + uint32 uHighQualityOut; + const uint160& uLowAccountID = !bHigh ? mTxnAccountID : uDstAccountID; + const uint160& uHighAccountID = bHigh ? mTxnAccountID : uDstAccountID; + SLE::ref sleLowAccount = !bHigh ? mTxnAccount : sleDst; + SLE::ref sleHighAccount = bHigh ? mTxnAccount : sleDst; -#if 0 - // We might delete a ripple state node if everything is set to defaults. - // However, this is problematic as it may make predicting reserve amounts harder for users. - // The code here is incomplete. + // + // Balances + // - if (!saLimitAmount) + saLowBalance = sleRippleState->getFieldAmount(sfBalance); + saHighBalance = -saLowBalance; + + // + // Limits + // + + sleRippleState->setFieldAmount(!bHigh ? sfLowLimit : sfHighLimit, saLimitAllow); + + saLowLimit = !bHigh ? saLimitAllow : sleRippleState->getFieldAmount(sfLowLimit); + saHighLimit = bHigh ? saLimitAllow : sleRippleState->getFieldAmount(sfHighLimit); + + // + // Quality in + // + + if (!bQualityIn) { - // 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; + // Not setting. Just get it. - assert(bLow || bHigh); - - if (bBalanceZero && bDstLimitZero) - { - // Zero balance and eliminating last limit. - - bDelIndex = true; - terResult = dirDelete(false, uSrcRef, Ledger::getOwnerDirIndex(mTxnAccountID), sleRippleState->getIndex(), false); - } + uLowQualityIn = sleRippleState->getFieldU32(sfLowQualityIn); + uHighQualityIn = sleRippleState->getFieldU32(sfHighQualityIn); } -#endif - - if (!bDelIndex) + else if (uQualityIn) { - sleRippleState->setFieldAmount(bFlipped ? sfHighLimit: sfLowLimit, saLimitAllow); + // Setting. - if (!bQualityIn) + sleRippleState->setFieldU32(!bHigh ? sfLowQualityIn : sfHighQualityIn, uQualityIn); + + uLowQualityIn = !bHigh ? uQualityIn : sleRippleState->getFieldU32(sfLowQualityIn); + uHighQualityIn = bHigh ? uQualityIn : sleRippleState->getFieldU32(sfHighQualityIn); + } + else + { + // Clearing. + + sleRippleState->makeFieldAbsent(!bHigh ? sfLowQualityIn : sfHighQualityIn); + + uLowQualityIn = !bHigh ? 0 : sleRippleState->getFieldU32(sfLowQualityIn); + uHighQualityIn = bHigh ? 0 : sleRippleState->getFieldU32(sfHighQualityIn); + } + + if (QUALITY_ONE == uLowQualityIn) uLowQualityIn = 0; + if (QUALITY_ONE == uHighQualityIn) uHighQualityIn = 0; + + // + // Quality out + // + + if (!bQualityOut) + { + // Not setting. Just get it. + + uLowQualityOut = sleRippleState->getFieldU32(sfLowQualityOut); + uHighQualityOut = sleRippleState->getFieldU32(sfHighQualityOut); + } + else if (uQualityOut) + { + // Setting. + + sleRippleState->setFieldU32(!bHigh ? sfLowQualityOut : sfHighQualityOut, uQualityOut); + + uLowQualityOut = !bHigh ? uQualityOut : sleRippleState->getFieldU32(sfLowQualityOut); + uHighQualityOut = bHigh ? uQualityOut : sleRippleState->getFieldU32(sfHighQualityOut); + } + else + { + // Clearing. + + sleRippleState->makeFieldAbsent(!bHigh ? sfLowQualityOut : sfHighQualityOut); + + uLowQualityOut = !bHigh ? 0 : sleRippleState->getFieldU32(sfLowQualityOut); + uHighQualityOut = bHigh ? 0 : sleRippleState->getFieldU32(sfHighQualityOut); + } + + if (QUALITY_ONE == uLowQualityOut) uLowQualityOut = 0; + if (QUALITY_ONE == uHighQualityOut) uHighQualityOut = 0; + + const bool bLowReserveSet = uLowQualityIn || uLowQualityOut || !!saLowLimit || saLowBalance.isPositive(); + const bool bLowReserveClear = !bLowReserveSet; + + const bool bHighReserveSet = uHighQualityIn || uHighQualityOut || !!saHighLimit || saHighBalance.isPositive(); + const bool bHighReserveClear = !bHighReserveSet; + + const bool bDefault = bLowReserveClear && bHighReserveClear; + + const uint32 uFlagsIn = sleRippleState->getFieldU32(sfFlags); + uint32 uFlagsOut = uFlagsIn; + + const bool bLowReserved = isSetBit(uFlagsIn, lsfLowReserve); + const bool bHighReserved = isSetBit(uFlagsIn, lsfHighReserve); + + bool bReserveIncrease = false; + + if (bSetAuth) + { + uFlagsOut |= (bHigh ? lsfHighAuth : lsfLowAuth); + } + + if (bLowReserveSet && !bLowReserved) + { + // Set reserve for low account. + + mEngine->getNodes().ownerCountAdjust(uLowAccountID, 1, sleLowAccount); + uFlagsOut |= lsfLowReserve; + + if (!bHigh) + bReserveIncrease = true; + } + + if (bLowReserveClear && bLowReserved) + { + // Clear reserve for low account. + + mEngine->getNodes().ownerCountAdjust(uLowAccountID, -1, sleLowAccount); + uFlagsOut &= ~lsfLowReserve; + } + + if (bHighReserveSet && !bHighReserved) + { + // Set reserve for high account. + + mEngine->getNodes().ownerCountAdjust(uHighAccountID, 1, sleHighAccount); + uFlagsOut |= lsfHighReserve; + + if (bHigh) + bReserveIncrease = true; + } + + if (bHighReserveClear && bHighReserved) + { + // Clear reserve for high account. + + mEngine->getNodes().ownerCountAdjust(uHighAccountID, -1, sleHighAccount); + uFlagsOut &= ~lsfHighReserve; + } + + if (uFlagsIn != uFlagsOut) + sleRippleState->setFieldU32(sfFlags, uFlagsOut); + + if (bDefault) + { + // Can delete. + + bool bLowNode = sleRippleState->isFieldPresent(sfLowNode); // Detect legacy dirs. + bool bHighNode = sleRippleState->isFieldPresent(sfHighNode); + uint64 uLowNode = sleRippleState->getFieldU64(sfLowNode); + uint64 uHighNode = sleRippleState->getFieldU64(sfHighNode); + + cLog(lsTRACE) << "doTrustSet: Deleting ripple line: low"; + terResult = mEngine->getNodes().dirDelete(false, uLowNode, Ledger::getOwnerDirIndex(uLowAccountID), sleRippleState->getIndex(), false, !bLowNode); + + if (tesSUCCESS == terResult) { - nothing(); - } - else if (uQualityIn) - { - sleRippleState->setFieldU32(bFlipped ? sfLowQualityIn : sfHighQualityIn, uQualityIn); - } - else - { - sleRippleState->makeFieldAbsent(bFlipped ? sfLowQualityIn : sfHighQualityIn); + cLog(lsTRACE) << "doTrustSet: Deleting ripple line: high"; + terResult = mEngine->getNodes().dirDelete(false, uHighNode, Ledger::getOwnerDirIndex(uHighAccountID), sleRippleState->getIndex(), false, !bHighNode); } - if (!bQualityOut) - { - nothing(); - } - else if (uQualityOut) - { - sleRippleState->setFieldU32(bFlipped ? sfLowQualityOut : sfHighQualityOut, uQualityOut); - } - else - { - sleRippleState->makeFieldAbsent(bFlipped ? sfLowQualityOut : sfHighQualityOut); - } + cLog(lsINFO) << "doTrustSet: Deleting ripple line: state"; + mEngine->entryDelete(sleRippleState); + } + else if (bReserveIncrease + && saSrcXRPBalance.getNValue() < uReserveCreate) // Reserve is not scaled by load. + { + cLog(lsINFO) << "doTrustSet: Delay transaction: Insufficent reserve to add trust line."; + // Another transaction could provide XRP to the account and then this transaction would succeed. + terResult = tecINSUF_RESERVE_LINE; + } + else + { mEngine->entryModify(sleRippleState); - } - Log(lsINFO) << "doTrustSet: Modifying ripple line: bDelIndex=" << bDelIndex; + cLog(lsINFO) << "doTrustSet: Modify ripple line"; + } } // Line does not exist. - else if (!saLimitAmount) + else if (!saLimitAmount // Setting default limit. + && (!bQualityIn || !uQualityIn) // Not setting quality in or setting default quality in. + && (!bQualityOut || !uQualityOut)) // Not setting quality out or setting default quality out. { - Log(lsINFO) << "doTrustSet: Redundant: Setting non-existent ripple line to 0."; + cLog(lsINFO) << "doTrustSet: Redundant: Setting non-existent ripple line to defaults."; - return terNO_LINE_NO_ZERO; + return tecNO_LINE_REDUNDANT; + } + else if (saSrcXRPBalance.getNValue() < uReserveCreate) // Reserve is not scaled by load. + { + cLog(lsINFO) << "doTrustSet: Delay transaction: Line does not exist. Insufficent reserve to create line."; + + // Another transaction could create the account and then this transaction would succeed. + terResult = tecNO_LINE_INSUF_RESERVE; } else { + STAmount saBalance = STAmount(uCurrencyID, ACCOUNT_ONE); // Zero balance in currency. + + cLog(lsINFO) << "doTrustSet: Creating ripple line: " + << Ledger::getRippleStateIndex(mTxnAccountID, uDstAccountID, uCurrencyID).ToString(); + // Create a new ripple line. - sleRippleState = mEngine->entryCreate(ltRIPPLE_STATE, Ledger::getRippleStateIndex(mTxnAccountID, uDstAccountID, uCurrencyID)); - - Log(lsINFO) << "doTrustSet: Creating ripple line: " << sleRippleState->getIndex().ToString(); - - sleRippleState->setFieldAmount(sfBalance, STAmount(uCurrencyID, ACCOUNT_ONE)); // Zero balance in currency. - sleRippleState->setFieldAmount(bFlipped ? sfHighLimit : sfLowLimit, saLimitAllow); - sleRippleState->setFieldAmount(bFlipped ? sfLowLimit : sfHighLimit, STAmount(uCurrencyID, uDstAccountID)); - - if (uQualityIn) - sleRippleState->setFieldU32(bFlipped ? sfHighQualityIn : sfLowQualityIn, uQualityIn); - if (uQualityOut) - sleRippleState->setFieldU32(bFlipped ? sfHighQualityOut : sfLowQualityOut, uQualityOut); - - uint64 uSrcRef; // <-- Ignored, dirs never delete. - - terResult = mEngine->getNodes().dirAdd( - uSrcRef, - Ledger::getOwnerDirIndex(mTxnAccountID), - sleRippleState->getIndex(), - boost::bind(&Ledger::ownerDirDescriber, _1, mTxnAccountID)); - - if (tesSUCCESS == terResult) - terResult = mEngine->getNodes().ownerCountAdjust(mTxnAccountID, 1, mTxnAccount); - - if (tesSUCCESS == terResult) - terResult = mEngine->getNodes().dirAdd( - uSrcRef, - Ledger::getOwnerDirIndex(uDstAccountID), - sleRippleState->getIndex(), - boost::bind(&Ledger::ownerDirDescriber, _1, uDstAccountID)); - - if (tesSUCCESS == terResult) - terResult = mEngine->getNodes().ownerCountAdjust(uDstAccountID, 1, sleDst); + terResult = mEngine->getNodes().trustCreate( + bHigh, + mTxnAccountID, + uDstAccountID, + Ledger::getRippleStateIndex(mTxnAccountID, uDstAccountID, uCurrencyID), + mTxnAccount, + bSetAuth, + saBalance, + saLimitAllow, // Limit for who is being charged. + uQualityIn, + uQualityOut); } - Log(lsINFO) << "doTrustSet<"; + cLog(lsINFO) << "doTrustSet<"; return terResult; } diff --git a/src/cpp/ripple/TrustSetTransactor.h b/src/cpp/ripple/TrustSetTransactor.h index 69b09aa01..ec6cdcd1d 100644 --- a/src/cpp/ripple/TrustSetTransactor.h +++ b/src/cpp/ripple/TrustSetTransactor.h @@ -6,4 +6,6 @@ public: TrustSetTransactor(const SerializedTransaction& txn,TransactionEngineParams params, TransactionEngine* engine) : Transactor(txn,params,engine) {} TER doApply(); -}; \ No newline at end of file +}; + +// vim:ts=4 diff --git a/src/cpp/ripple/UniqueNodeList.cpp b/src/cpp/ripple/UniqueNodeList.cpp index df37f6d4d..c0f4d8016 100644 --- a/src/cpp/ripple/UniqueNodeList.cpp +++ b/src/cpp/ripple/UniqueNodeList.cpp @@ -3,6 +3,7 @@ #include "UniqueNodeList.h" +#include #include #include @@ -24,7 +25,6 @@ SETUP_LOG(); #define VALIDATORS_FETCH_SECONDS 30 -#define VALIDATORS_FILE_PATH "/" VALIDATORS_FILE_NAME #define VALIDATORS_FILE_BYTES_MAX (50 << 10) // Gather string constants. @@ -41,10 +41,6 @@ SETUP_LOG(); #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), @@ -68,7 +64,16 @@ void UniqueNodeList::start() // Load information about when we last updated. bool UniqueNodeList::miscLoad() { - ScopedLock sl(theApp->getWalletDB()->getDBLock()); + BOOST_FOREACH(const std::string& node, theConfig.CLUSTER_NODES) + { + RippleAddress a = RippleAddress::createNodePublic(node); + if (a.isValid()) + sClusterNodes.insert(a); + else + cLog(lsWARNING) << "Entry in cluster list invalid: '" << node << "'"; + } + + boost::recursive_mutex::scoped_lock sl(theApp->getWalletDB()->getDBLock()); Database *db=theApp->getWalletDB()->getDB(); if (!db->executeSQL("SELECT * FROM Misc WHERE Magic=1;")) return false; @@ -89,7 +94,7 @@ bool UniqueNodeList::miscLoad() bool UniqueNodeList::miscSave() { Database* db=theApp->getWalletDB()->getDB(); - ScopedLock sl(theApp->getWalletDB()->getDBLock()); + boost::recursive_mutex::scoped_lock sl(theApp->getWalletDB()->getDBLock()); db->executeSQL(str(boost::format("REPLACE INTO Misc (Magic,FetchUpdated,ScoreUpdated) VALUES (1,%d,%d);") % iToSeconds(mtpFetchUpdated) @@ -100,9 +105,18 @@ bool UniqueNodeList::miscSave() void UniqueNodeList::trustedLoad() { + BOOST_FOREACH(const std::string& node, theConfig.CLUSTER_NODES) + { + RippleAddress a = RippleAddress::createNodePublic(node); + if (a.isValid()) + sClusterNodes.insert(a); + else + cLog(lsWARNING) << "Entry in cluster list invalid: '" << node << "'"; + } + Database* db=theApp->getWalletDB()->getDB(); - ScopedLock sl(theApp->getWalletDB()->getDBLock()); - ScopedLock slUNL(mUNLLock); + boost::recursive_mutex::scoped_lock sl(theApp->getWalletDB()->getDBLock()); + boost::recursive_mutex::scoped_lock slUNL(mUNLLock); mUNL.clear(); @@ -191,7 +205,7 @@ void UniqueNodeList::scoreCompute() // For each entry in SeedDomains with a PublicKey: // - Add an entry in umPulicIdx, umDomainIdx, and vsnNodes. { - ScopedLock sl(theApp->getWalletDB()->getDBLock()); + boost::recursive_mutex::scoped_lock sl(theApp->getWalletDB()->getDBLock()); SQL_FOREACH(db, "SELECT Domain,PublicKey,Source FROM SeedDomains;") { @@ -244,7 +258,7 @@ void UniqueNodeList::scoreCompute() // For each entry in SeedNodes: // - Add an entry in umPulicIdx, umDomainIdx, and vsnNodes. { - ScopedLock sl(theApp->getWalletDB()->getDBLock()); + boost::recursive_mutex::scoped_lock sl(theApp->getWalletDB()->getDBLock()); SQL_FOREACH(db, "SELECT PublicKey,Source FROM SeedNodes;") { @@ -308,10 +322,10 @@ void UniqueNodeList::scoreCompute() std::string& strValidator = sn.strValidator; std::vector& viReferrals = sn.viReferrals; - ScopedLock sl(theApp->getWalletDB()->getDBLock()); + boost::recursive_mutex::scoped_lock sl(theApp->getWalletDB()->getDBLock()); - SQL_FOREACH(db, str(boost::format("SELECT Referral FROM ValidatorReferrals WHERE Validator=%s ORDER BY Entry;") - % db->escape(strValidator))) + SQL_FOREACH(db, boost::str(boost::format("SELECT Referral FROM ValidatorReferrals WHERE Validator=%s ORDER BY Entry;") + % sqlEscape(strValidator))) { std::string strReferral = db->getStrBinary("Referral"); int iReferral; @@ -389,7 +403,7 @@ void UniqueNodeList::scoreCompute() } // Persist validator scores. - ScopedLock sl(theApp->getWalletDB()->getDBLock()); + boost::recursive_mutex::scoped_lock sl(theApp->getWalletDB()->getDBLock()); db->executeSQL("BEGIN;"); db->executeSQL("UPDATE TrustedNodes SET Score = 0 WHERE Score != 0;"); @@ -403,7 +417,7 @@ void UniqueNodeList::scoreCompute() for (int iNode=vsnNodes.size(); iNode--;) { - vstrPublicKeys[iNode] = db->escape(vsnNodes[iNode].strValidator); + vstrPublicKeys[iNode] = sqlEscape(vsnNodes[iNode].strValidator); } SQL_FOREACH(db, str(boost::format("SELECT PublicKey,Seen FROM TrustedNodes WHERE PublicKey IN (%s);") @@ -440,7 +454,7 @@ void UniqueNodeList::scoreCompute() } { - ScopedLock sl(mUNLLock); + boost::recursive_mutex::scoped_lock sl(mUNLLock); // XXX Should limit to scores above a certain minimum and limit to a certain number. mUNL.swap(usUNL); @@ -468,8 +482,8 @@ void UniqueNodeList::scoreCompute() // map of pair :: score epScore umScore; - std::pair< std::string, int> vc; - BOOST_FOREACH(vc, umValidators) + typedef boost::unordered_map::value_type vcType; + BOOST_FOREACH(vcType& vc, umValidators) { std::string strValidator = vc.first; @@ -482,7 +496,7 @@ void UniqueNodeList::scoreCompute() int iEntry = 0; SQL_FOREACH(db, str(boost::format("SELECT IP,Port FROM IpReferrals WHERE Validator=%s ORDER BY Entry;") - % db->escape(strValidator))) + % sqlEscape(strValidator))) { score iPoints = iBase * (iEntries - iEntry) / iEntries; int iPort; @@ -506,15 +520,15 @@ void UniqueNodeList::scoreCompute() vstrValues.reserve(umScore.size()); - std::pair< ipPort, score> ipScore; - BOOST_FOREACH(ipScore, umScore) + typedef boost::unordered_map, score>::value_type ipScoreType; + BOOST_FOREACH(ipScoreType& ipScore, umScore) { ipPort ipEndpoint = ipScore.first; std::string strIpPort = str(boost::format("%s %d") % ipEndpoint.first % ipEndpoint.second); score iPoints = ipScore.second; vstrValues.push_back(str(boost::format("(%s,%d,'%c')") - % db->escape(strIpPort) + % sqlEscape(strIpPort) % iPoints % vsValidator)); } @@ -626,19 +640,19 @@ void UniqueNodeList::processIps(const std::string& strSite, const RippleAddress& // Remove all current Validator's entries in IpReferrals { - ScopedLock sl(theApp->getWalletDB()->getDBLock()); + boost::recursive_mutex::scoped_lock sl(theApp->getWalletDB()->getDBLock()); db->executeSQL(str(boost::format("DELETE FROM IpReferrals WHERE Validator=%s;") % strEscNodePublic)); // XXX Check result. } // Add new referral entries. if (pmtVecStrIps && !pmtVecStrIps->empty()) { - std::vector vstrValues; + std::vector vstrValues; - vstrValues.resize(MIN(pmtVecStrIps->size(), REFERRAL_IPS_MAX)); + vstrValues.resize(std::min((int) pmtVecStrIps->size(), REFERRAL_IPS_MAX)); int iValues = 0; - BOOST_FOREACH(std::string strReferral, *pmtVecStrIps) + BOOST_FOREACH(const std::string& strReferral, *pmtVecStrIps) { if (iValues == REFERRAL_VALIDATORS_MAX) break; @@ -653,7 +667,7 @@ void UniqueNodeList::processIps(const std::string& strSite, const RippleAddress& if (bValid) { vstrValues[iValues] = str(boost::format("(%s,%d,%s,%d)") - % strEscNodePublic % iValues % db->escape(strIP) % iPort); + % strEscNodePublic % iValues % sqlEscape(strIP) % iPort); iValues++; } else @@ -668,7 +682,7 @@ void UniqueNodeList::processIps(const std::string& strSite, const RippleAddress& { vstrValues.resize(iValues); - ScopedLock sl(theApp->getWalletDB()->getDBLock()); + boost::recursive_mutex::scoped_lock sl(theApp->getWalletDB()->getDBLock()); db->executeSQL(str(boost::format("INSERT INTO IpReferrals (Validator,Entry,IP,Port) VALUES %s;") % strJoin(vstrValues.begin(), vstrValues.end(), ","))); // XXX Check result. @@ -697,7 +711,7 @@ int UniqueNodeList::processValidators(const std::string& strSite, const std::str // Remove all current Validator's entries in ValidatorReferrals { - ScopedLock sl(theApp->getWalletDB()->getDBLock()); + boost::recursive_mutex::scoped_lock sl(theApp->getWalletDB()->getDBLock()); db->executeSQL(str(boost::format("DELETE FROM ValidatorReferrals WHERE Validator='%s';") % strNodePublic)); // XXX Check result. @@ -707,9 +721,9 @@ int UniqueNodeList::processValidators(const std::string& strSite, const std::str if (pmtVecStrValidators && pmtVecStrValidators->size()) { std::vector vstrValues; - vstrValues.reserve(MIN(pmtVecStrValidators->size(), REFERRAL_VALIDATORS_MAX)); + vstrValues.reserve(std::min((int) pmtVecStrValidators->size(), REFERRAL_VALIDATORS_MAX)); - BOOST_FOREACH(std::string strReferral, *pmtVecStrValidators) + BOOST_FOREACH(const std::string& strReferral, *pmtVecStrValidators) { if (iValues == REFERRAL_VALIDATORS_MAX) break; @@ -768,7 +782,7 @@ int UniqueNodeList::processValidators(const std::string& strSite, const std::str std::string strSql = str(boost::format("INSERT INTO ValidatorReferrals (Validator,Entry,Referral) VALUES %s;") % strJoin(vstrValues.begin(), vstrValues.end(), ",")); - ScopedLock sl(theApp->getWalletDB()->getDBLock()); + boost::recursive_mutex::scoped_lock sl(theApp->getWalletDB()->getDBLock()); db->executeSQL(strSql); // XXX Check result. @@ -798,12 +812,16 @@ void UniqueNodeList::responseIps(const std::string& strSite, const RippleAddress void UniqueNodeList::getIpsUrl(const RippleAddress& naNodePublic, section secSite) { std::string strIpsUrl; + std::string strScheme; std::string strDomain; + int iPort; std::string strPath; if (sectionSingleB(secSite, SECTION_IPS_URL, strIpsUrl) && !strIpsUrl.empty() - && HttpsClient::httpsParseUrl(strIpsUrl, strDomain, strPath)) + && parseUrl(strIpsUrl, strScheme, strDomain, iPort, strPath) + && -1 == iPort + && strScheme == "https") { HttpsClient::httpsGet( theApp->getIOService(), @@ -837,12 +855,16 @@ void UniqueNodeList::responseValidators(const std::string& strValidatorsUrl, con void UniqueNodeList::getValidatorsUrl(const RippleAddress& naNodePublic, section secSite) { std::string strValidatorsUrl; + std::string strScheme; std::string strDomain; + int iPort; std::string strPath; if (sectionSingleB(secSite, SECTION_VALIDATORS_URL, strValidatorsUrl) && !strValidatorsUrl.empty() - && HttpsClient::httpsParseUrl(strValidatorsUrl, strDomain, strPath)) + && parseUrl(strValidatorsUrl, strScheme, strDomain, iPort, strPath) + && -1 == iPort + && strScheme == "https") { HttpsClient::httpsGet( theApp->getIOService(), @@ -1052,7 +1074,7 @@ void UniqueNodeList::fetchNext() boost::posix_time::ptime tpNext; boost::posix_time::ptime tpNow; - ScopedLock sl(theApp->getWalletDB()->getDBLock()); + boost::recursive_mutex::scoped_lock sl(theApp->getWalletDB()->getDBLock()); Database *db=theApp->getWalletDB()->getDB(); if (db->executeSQL("SELECT Domain,Next FROM SeedDomains INDEXED BY SeedDomainNext ORDER BY Next LIMIT 1;") @@ -1149,10 +1171,10 @@ bool UniqueNodeList::getSeedDomains(const std::string& strDomain, seedDomain& ds bool bResult; Database* db=theApp->getWalletDB()->getDB(); - std::string strSql = str(boost::format("SELECT * FROM SeedDomains WHERE Domain=%s;") - % db->escape(strDomain)); + std::string strSql = boost::str(boost::format("SELECT * FROM SeedDomains WHERE Domain=%s;") + % sqlEscape(strDomain)); - ScopedLock sl(theApp->getWalletDB()->getDBLock()); + boost::recursive_mutex::scoped_lock sl(theApp->getWalletDB()->getDBLock()); bResult = db->executeSQL(strSql) && db->startIterRows(); if (bResult) @@ -1211,18 +1233,18 @@ void UniqueNodeList::setSeedDomains(const seedDomain& sdSource, bool bNext) // cLog(lsTRACE) << str(boost::format("setSeedDomains: iNext=%s tpNext=%s") % iNext % sdSource.tpNext); - std::string strSql = str(boost::format("REPLACE INTO SeedDomains (Domain,PublicKey,Source,Next,Scan,Fetch,Sha256,Comment) VALUES (%s, %s, %s, %d, %d, %d, '%s', %s);") - % db->escape(sdSource.strDomain) - % (sdSource.naPublicKey.isValid() ? db->escape(sdSource.naPublicKey.humanNodePublic()) : "NULL") + std::string strSql = boost::str(boost::format("REPLACE INTO SeedDomains (Domain,PublicKey,Source,Next,Scan,Fetch,Sha256,Comment) VALUES (%s, %s, %s, %d, %d, %d, '%s', %s);") + % sqlEscape(sdSource.strDomain) + % (sdSource.naPublicKey.isValid() ? sqlEscape(sdSource.naPublicKey.humanNodePublic()) : "NULL") % sqlEscape(std::string(1, static_cast(sdSource.vsSource))) % iNext % iScan % iFetch % sdSource.iSha256.GetHex() - % db->escape(sdSource.strComment) + % sqlEscape(sdSource.strComment) ); - ScopedLock sl(theApp->getWalletDB()->getDBLock()); + boost::recursive_mutex::scoped_lock sl(theApp->getWalletDB()->getDBLock()); if (!db->executeSQL(strSql)) { @@ -1290,7 +1312,7 @@ bool UniqueNodeList::getSeedNodes(const RippleAddress& naNodePublic, seedNode& d std::string strSql = str(boost::format("SELECT * FROM SeedNodes WHERE PublicKey='%s';") % naNodePublic.humanNodePublic()); - ScopedLock sl(theApp->getWalletDB()->getDBLock()); + boost::recursive_mutex::scoped_lock sl(theApp->getWalletDB()->getDBLock()); bResult = db->executeSQL(strSql) && db->startIterRows(); if (bResult) @@ -1362,7 +1384,7 @@ void UniqueNodeList::setSeedNodes(const seedNode& snSource, bool bNext) ); { - ScopedLock sl(theApp->getWalletDB()->getDBLock()); + boost::recursive_mutex::scoped_lock sl(theApp->getWalletDB()->getDBLock()); if (!db->executeSQL(strSql)) { @@ -1420,7 +1442,7 @@ void UniqueNodeList::nodeRemovePublic(const RippleAddress& naNodePublic) { { Database* db=theApp->getWalletDB()->getDB(); - ScopedLock sl(theApp->getWalletDB()->getDBLock()); + boost::recursive_mutex::scoped_lock sl(theApp->getWalletDB()->getDBLock()); db->executeSQL(str(boost::format("DELETE FROM SeedNodes WHERE PublicKey=%s") % sqlEscape(naNodePublic.humanNodePublic()))); } @@ -1436,7 +1458,7 @@ void UniqueNodeList::nodeRemoveDomain(std::string strDomain) { Database* db=theApp->getWalletDB()->getDB(); - ScopedLock sl(theApp->getWalletDB()->getDBLock()); + boost::recursive_mutex::scoped_lock sl(theApp->getWalletDB()->getDBLock()); db->executeSQL(str(boost::format("DELETE FROM SeedDomains WHERE Domain=%s") % sqlEscape(strDomain))); } @@ -1450,7 +1472,7 @@ void UniqueNodeList::nodeReset() { Database* db=theApp->getWalletDB()->getDB(); - ScopedLock sl(theApp->getWalletDB()->getDBLock()); + boost::recursive_mutex::scoped_lock sl(theApp->getWalletDB()->getDBLock()); // XXX Check results. db->executeSQL("DELETE FROM SeedDomains"); @@ -1466,7 +1488,7 @@ Json::Value UniqueNodeList::getUnlJson() Json::Value ret(Json::arrayValue); - ScopedLock sl(theApp->getWalletDB()->getDBLock()); + boost::recursive_mutex::scoped_lock sl(theApp->getWalletDB()->getDBLock()); SQL_FOREACH(db, "SELECT * FROM TrustedNodes;") { Json::Value node(Json::objectValue); @@ -1547,13 +1569,13 @@ void UniqueNodeList::validatorsResponse(const boost::system::error_code& err, st void UniqueNodeList::nodeNetwork() { - if(!theConfig.VALIDATORS_SITE.empty()) + if (!theConfig.VALIDATORS_SITE.empty()) { HttpsClient::httpsGet( theApp->getIOService(), theConfig.VALIDATORS_SITE, 443, - VALIDATORS_FILE_PATH, + theConfig.VALIDATORS_URI, VALIDATORS_FILE_BYTES_MAX, boost::posix_time::seconds(VALIDATORS_FETCH_SECONDS), boost::bind(&UniqueNodeList::validatorsResponse, this, _1, _2)); @@ -1567,7 +1589,7 @@ void UniqueNodeList::nodeBootstrap() Database* db = theApp->getWalletDB()->getDB(); { - ScopedLock sl(theApp->getWalletDB()->getDBLock()); + boost::recursive_mutex::scoped_lock sl(theApp->getWalletDB()->getDBLock()); if (db->executeSQL(str(boost::format("SELECT COUNT(*) AS Count FROM SeedDomains WHERE Source='%s' OR Source='%c';") % vsManual % vsValidator)) && db->startIterRows()) iDomains = db->getInt("Count"); @@ -1589,9 +1611,10 @@ void UniqueNodeList::nodeBootstrap() // If never loaded anything try the current directory. if (!bLoaded && theConfig.VALIDATORS_FILE.empty()) { - cLog(lsINFO) << "Bootstrapping UNL: loading from '" VALIDATORS_FILE_NAME "'."; + cLog(lsINFO) << boost::str(boost::format("Bootstrapping UNL: loading from '%s'.") + % theConfig.VALIDATORS_BASE); - bLoaded = nodeLoad(VALIDATORS_FILE_NAME); + bLoaded = nodeLoad(theConfig.VALIDATORS_BASE); } // Always load from rippled.cfg @@ -1599,15 +1622,17 @@ void UniqueNodeList::nodeBootstrap() { RippleAddress naInvalid; // Don't want a referrer on added entries. - cLog(lsINFO) << "Bootstrapping UNL: loading from " CONFIG_FILE_NAME "."; + cLog(lsINFO) << boost::str(boost::format("Bootstrapping UNL: loading from '%s'.") + % theConfig.CONFIG_FILE); - if (processValidators("local", CONFIG_FILE_NAME, naInvalid, vsConfig, &theConfig.VALIDATORS)) + if (processValidators("local", theConfig.CONFIG_FILE.string(), naInvalid, vsConfig, &theConfig.VALIDATORS)) bLoaded = true; } if (!bLoaded) { - cLog(lsINFO) << "Bootstrapping UNL: loading from " << theConfig.VALIDATORS_SITE << "."; + cLog(lsINFO) << boost::str(boost::format("Bootstrapping UNL: loading from '%s'.") + % theConfig.VALIDATORS_SITE); nodeNetwork(); } @@ -1633,7 +1658,7 @@ void UniqueNodeList::nodeBootstrap() if (!vstrValues.empty()) { - ScopedLock sl(theApp->getWalletDB()->getDBLock()); + boost::recursive_mutex::scoped_lock sl(theApp->getWalletDB()->getDBLock()); db->executeSQL(str(boost::format("REPLACE INTO PeerIps (IpPort,Source) VALUES %s;") % strJoin(vstrValues.begin(), vstrValues.end(), ","))); @@ -1659,14 +1684,22 @@ void UniqueNodeList::nodeProcess(const std::string& strSite, const std::string& } else { - cLog(lsWARNING) << "'" VALIDATORS_FILE_NAME "' missing [" SECTION_VALIDATORS "]."; + cLog(lsWARNING) << boost::str(boost::format("'%s' missing [" SECTION_VALIDATORS "].") + % theConfig.VALIDATORS_BASE); } } bool UniqueNodeList::nodeInUNL(const RippleAddress& naNodePublic) { - ScopedLock sl(mUNLLock); + boost::recursive_mutex::scoped_lock sl(mUNLLock); return mUNL.end() != mUNL.find(naNodePublic.humanNodePublic()); } + +bool UniqueNodeList::nodeInCluster(const RippleAddress& naNodePublic) +{ + boost::recursive_mutex::scoped_lock sl(mUNLLock); + return sClusterNodes.count(naNodePublic) != 0; +} + // vim:ts=4 diff --git a/src/cpp/ripple/UniqueNodeList.h b/src/cpp/ripple/UniqueNodeList.h index e9c3999dd..9cf511d84 100644 --- a/src/cpp/ripple/UniqueNodeList.h +++ b/src/cpp/ripple/UniqueNodeList.h @@ -2,6 +2,11 @@ #define __UNIQUE_NODE_LIST__ #include +#include + +#include +#include +#include #include "../json/value.h" @@ -10,11 +15,7 @@ #include "HttpsClient.h" #include "ParseSection.h" -#include -#include -#include - -// Guarantees minimum thoughput of 1 node per second. +// Guarantees minimum throughput of 1 node per second. #define NODE_FETCH_JOBS 10 #define NODE_FETCH_SECONDS 10 #define NODE_FILE_BYTES_MAX (50<<10) // 50k @@ -87,6 +88,8 @@ private: std::vector viReferrals; } scoreNode; + std::set sClusterNodes; + typedef boost::unordered_map strIndex; typedef std::pair ipPort; typedef boost::unordered_map, score> epScore; @@ -151,6 +154,7 @@ public: void nodeScore(); bool nodeInUNL(const RippleAddress& naNodePublic); + bool nodeInCluster(const RippleAddress& naNodePublic); void nodeBootstrap(); bool nodeLoad(boost::filesystem::path pConfig); diff --git a/src/cpp/ripple/ValidationCollection.cpp b/src/cpp/ripple/ValidationCollection.cpp index f663b2c2a..8101d5f51 100644 --- a/src/cpp/ripple/ValidationCollection.cpp +++ b/src/cpp/ripple/ValidationCollection.cpp @@ -9,7 +9,7 @@ SETUP_LOG(); -typedef std::pair u160_val_pair; +typedef std::map::value_type u160_val_pair; typedef boost::shared_ptr VSpointer; VSpointer ValidationCollection::findCreateSet(const uint256& ledgerHash) @@ -75,6 +75,8 @@ bool ValidationCollection::addValidation(const SerializedValidation::pointer& va cLog(lsINFO) << "Val for " << hash << " from " << signer.humanNodePublic() << " added " << (val->isTrusted() ? "trusted/" : "UNtrusted/") << (isCurrent ? "current" : "stale"); + if (val->isTrusted()) + theApp->getLedgerMaster().checkAccept(hash); return isCurrent; } @@ -83,8 +85,8 @@ ValidationSet ValidationCollection::getValidations(const uint256& ledger) { boost::mutex::scoped_lock sl(mValidationLock); VSpointer set = findSet(ledger); - if (set != VSpointer()) - return *set; + if (set) + return ValidationSet(*set); } return ValidationSet(); } @@ -94,9 +96,9 @@ void ValidationCollection::getValidationCount(const uint256& ledger, bool curren trusted = untrusted = 0; boost::mutex::scoped_lock sl(mValidationLock); VSpointer set = findSet(ledger); - uint32 now = theApp->getOPs().getNetworkTimeNC(); if (set) { + uint32 now = theApp->getOPs().getNetworkTimeNC(); BOOST_FOREACH(u160_val_pair& it, *set) { bool isTrusted = it.second->isTrusted(); @@ -258,24 +260,26 @@ ValidationCollection::getCurrentValidations(uint256 currentLedger) void ValidationCollection::flush() { - bool anyNew = false; + bool anyNew = false; - boost::mutex::scoped_lock sl(mValidationLock); - BOOST_FOREACH(u160_val_pair& it, mCurrentValidations) - { - if (it.second) - mStaleValidations.push_back(it.second); - anyNew = true; - } - mCurrentValidations.clear(); - if (anyNew) - condWrite(); - while (mWriting) - { - sl.unlock(); - boost::this_thread::sleep(boost::posix_time::milliseconds(100)); - sl.lock(); - } + cLog(lsINFO) << "Flushing validations"; + boost::mutex::scoped_lock sl(mValidationLock); + BOOST_FOREACH(u160_val_pair& it, mCurrentValidations) + { + if (it.second) + mStaleValidations.push_back(it.second); + anyNew = true; + } + mCurrentValidations.clear(); + if (anyNew) + condWrite(); + while (mWriting) + { + sl.unlock(); + boost::this_thread::sleep(boost::posix_time::milliseconds(100)); + sl.lock(); + } + cLog(lsDEBUG) << "Validations flushed"; } void ValidationCollection::condWrite() @@ -308,7 +312,7 @@ void ValidationCollection::doWrite() 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())))); + % sqlEscape(it->getSignature()))); db->executeSQL("END TRANSACTION;"); } sl.lock(); diff --git a/src/cpp/ripple/ValidationCollection.h b/src/cpp/ripple/ValidationCollection.h index d8ecd791e..b65559f79 100644 --- a/src/cpp/ripple/ValidationCollection.h +++ b/src/cpp/ripple/ValidationCollection.h @@ -20,7 +20,7 @@ class ValidationCollection protected: boost::mutex mValidationLock; - TaggedCache mValidations; + TaggedCache mValidations; boost::unordered_map mCurrentValidations; std::vector mStaleValidations; @@ -33,7 +33,7 @@ protected: boost::shared_ptr findSet(const uint256& ledgerHash); public: - ValidationCollection() : mValidations("Validations", 128, 500), mWriting(false) { ; } + ValidationCollection() : mValidations("Validations", 128, 600), mWriting(false) { ; } bool addValidation(const SerializedValidation::pointer&); ValidationSet getValidations(const uint256& ledger); diff --git a/src/cpp/ripple/WSConnection.h b/src/cpp/ripple/WSConnection.h index 7dcb9774b..d6fe475f4 100644 --- a/src/cpp/ripple/WSConnection.h +++ b/src/cpp/ripple/WSConnection.h @@ -1,14 +1,24 @@ -#include "../websocketpp/src/sockets/tls.hpp" +#include "../websocketpp/src/sockets/autotls.hpp" #include "../websocketpp/src/websocketpp.hpp" #include "../json/value.h" +#include + #include "WSDoor.h" #include "Application.h" -#include "Log.h" #include "NetworkOPs.h" #include "CallRPC.h" +#include "InstanceCounter.h" +#include "Log.h" +#include "RPCErr.h" + +DEFINE_INSTANCE(WebSocketConnection); + +#ifndef WEBSOCKET_PING_FREQUENCY +#define WEBSOCKET_PING_FREQUENCY 120 +#endif template class WSServerHandler; @@ -17,41 +27,56 @@ class WSServerHandler; // - Subscriptions // template -class WSConnection : public InfoSub +class WSConnection : public InfoSub, public IS_INSTANCE(WebSocketConnection) { public: - typedef typename endpoint_type::handler::connection_ptr connection_ptr; + typedef typename endpoint_type::connection_type connection; + typedef typename boost::shared_ptr connection_ptr; + typedef typename boost::weak_ptr weak_connection_ptr; typedef typename endpoint_type::handler::message_ptr message_ptr; protected: typedef void (WSConnection::*doFuncPtr)(Json::Value& jvResult, Json::Value &jvRequest); - WSServerHandler* mHandler; - connection_ptr mConnection; - NetworkOPs& mNetwork; + WSServerHandler* mHandler; + weak_connection_ptr mConnection; + NetworkOPs& mNetwork; + std::string mRemoteIP; + + boost::asio::deadline_timer mPingTimer; + bool mPinged; public: // WSConnection() // : mHandler((WSServerHandler*)(NULL)), // mConnection(connection_ptr()) { ; } - WSConnection(WSServerHandler* wshpHandler, connection_ptr cpConnection) - : mHandler(wshpHandler), mConnection(cpConnection), mNetwork(theApp->getOPs()) { ; } - - virtual ~WSConnection() + WSConnection(WSServerHandler* wshpHandler, const connection_ptr& cpConnection) + : mHandler(wshpHandler), mConnection(cpConnection), mNetwork(theApp->getOPs()), + mPingTimer(theApp->getAuxService()), mPinged(false) { - mNetwork.unsubTransactions(this); - mNetwork.unsubRTTransactions(this); - mNetwork.unsubLedger(this); - mNetwork.unsubServer(this); - mNetwork.unsubAccount(this, mSubAccountInfo, true); - mNetwork.unsubAccount(this, mSubAccountInfo, false); + mRemoteIP = cpConnection->get_socket().lowest_layer().remote_endpoint().address().to_string(); + cLog(lsDEBUG) << "Websocket connection from " << mRemoteIP; + setPingTimer(); + } + + void preDestroy() + { // sever connection + mConnection.reset(); + } + + virtual ~WSConnection() { ; } + + static void destroy(boost::shared_ptr< WSConnection >) + { // Just discards the reference } // Implement overridden functions from base class: void send(const Json::Value& jvObj) { - mHandler->send(mConnection, jvObj); + connection_ptr ptr = mConnection.lock(); + if (ptr) + mHandler->send(ptr, jvObj); } // Utilities @@ -72,9 +97,18 @@ public: RPCHandler mRPCHandler(&mNetwork, this); Json::Value jvResult(Json::objectValue); - jvResult["result"] = mRPCHandler.doCommand( - jvRequest, - mHandler->getPublic() ? RPCHandler::GUEST : RPCHandler::ADMIN); + int iRole = mHandler->getPublic() + ? RPCHandler::GUEST // Don't check on the public interface. + : iAdminGet(jvRequest, mRemoteIP); + + if (RPCHandler::FORBID == iRole) + { + jvResult["result"] = rpcError(rpcFORBIDDEN); + } + else + { + jvResult["result"] = mRPCHandler.doCommand(jvRequest, iRole); + } // Currently we will simply unwrap errors returned by the RPC // API, in the future maybe we can make the responses @@ -100,6 +134,34 @@ public: return jvResult; } + + bool onPingTimer() + { + if (mPinged) + return true; + mPinged = true; + setPingTimer(); + return false; + } + + void onPong() + { + mPinged = false; + } + + static void pingTimer(weak_connection_ptr c, WSServerHandler* h) + { + connection_ptr ptr = c.lock(); + if (ptr) + h->pingTimer(ptr); + } + + void setPingTimer() + { + mPingTimer.expires_from_now(boost::posix_time::seconds(WEBSOCKET_PING_FREQUENCY)); + mPingTimer.async_wait(boost::bind(&WSConnection::pingTimer, mConnection, mHandler)); + } + }; diff --git a/src/cpp/ripple/WSDoor.cpp b/src/cpp/ripple/WSDoor.cpp index cd523c0de..ec4a84e89 100644 --- a/src/cpp/ripple/WSDoor.cpp +++ b/src/cpp/ripple/WSDoor.cpp @@ -2,7 +2,7 @@ #include "Log.h" #define WSDOOR_CPP -#include "../websocketpp/src/sockets/tls.hpp" +#include "../websocketpp/src/sockets/autotls.hpp" #include "../websocketpp/src/websocketpp.hpp" SETUP_LOG(); @@ -24,6 +24,7 @@ SETUP_LOG(); #include #include +DECLARE_INSTANCE(WebSocketConnection); // // This is a light weight, untrusted interface for web clients. @@ -58,80 +59,40 @@ void WSDoor::startListening() SSL_CTX_set_tmp_dh_callback(mCtx->native_handle(), handleTmpDh); - if(theConfig.WEBSOCKET_SECURE) + // Construct a single handler for all requests. + websocketpp::server_autotls::handler::ptr handler(new WSServerHandler(mCtx, mPublic)); + + // Construct a websocket server. + mSEndpoint = new websocketpp::server_autotls(handler); + + // mEndpoint->alog().unset_level(websocketpp::log::alevel::ALL); + // mEndpoint->elog().unset_level(websocketpp::log::elevel::ALL); + + // Call the main-event-loop of the websocket server. + try { - // Construct a single handler for all requests. - websocketpp::server_tls::handler::ptr handler(new WSServerHandler(mCtx, mPublic)); - - // Construct a websocket server. - mSEndpoint = new websocketpp::server_tls(handler); - - // mEndpoint->alog().unset_level(websocketpp::log::alevel::ALL); - // mEndpoint->elog().unset_level(websocketpp::log::elevel::ALL); - - // Call the main-event-loop of the websocket server. - try - { - mSEndpoint->listen( - boost::asio::ip::tcp::endpoint( - boost::asio::ip::address().from_string(mIp), mPort)); - } - catch (websocketpp::exception& e) - { - Log(lsWARNING) << "websocketpp exception: " << e.what(); - while (1) // temporary workaround for websocketpp throwing exceptions on access/close races - { // https://github.com/zaphoyd/websocketpp/issues/98 - try - { - mSEndpoint->get_io_service().run(); - break; - } - catch (websocketpp::exception& e) - { - Log(lsWARNING) << "websocketpp exception: " << e.what(); - } - } - } - - delete mSEndpoint; - }else - { - // Construct a single handler for all requests. - websocketpp::server::handler::ptr handler(new WSServerHandler(mCtx, mPublic)); - - // Construct a websocket server. - mEndpoint = new websocketpp::server(handler); - - // mEndpoint->alog().unset_level(websocketpp::log::alevel::ALL); - // mEndpoint->elog().unset_level(websocketpp::log::elevel::ALL); - - // Call the main-event-loop of the websocket server. - try - { - mEndpoint->listen( - boost::asio::ip::tcp::endpoint( - boost::asio::ip::address().from_string(mIp), mPort)); - } - catch (websocketpp::exception& e) - { - Log(lsWARNING) << "websocketpp exception: " << e.what(); - while (1) // temporary workaround for websocketpp throwing exceptions on access/close races - { // https://github.com/zaphoyd/websocketpp/issues/98 - try - { - mEndpoint->get_io_service().run(); - break; - } - catch (websocketpp::exception& e) - { - Log(lsWARNING) << "websocketpp exception: " << e.what(); - } - } - } - - delete mEndpoint; + mSEndpoint->listen( + boost::asio::ip::tcp::endpoint( + boost::asio::ip::address().from_string(mIp), mPort)); } - + catch (websocketpp::exception& e) + { + cLog(lsWARNING) << "websocketpp exception: " << e.what(); + while (1) // temporary workaround for websocketpp throwing exceptions on access/close races + { // https://github.com/zaphoyd/websocketpp/issues/98 + try + { + mSEndpoint->get_io_service().run(); + break; + } + catch (websocketpp::exception& e) + { + cLog(lsWARNING) << "websocketpp exception: " << e.what(); + } + } + } + + delete mSEndpoint; } WSDoor* WSDoor::createWSDoor(const std::string& strIp, const int iPort, bool bPublic) @@ -153,8 +114,6 @@ void WSDoor::stop() { if (mThread) { - if (mEndpoint) - mEndpoint->stop(); if (mSEndpoint) mSEndpoint->stop(); @@ -163,5 +122,4 @@ void WSDoor::stop() } } - // vim:ts=4 diff --git a/src/cpp/ripple/WSDoor.h b/src/cpp/ripple/WSDoor.h index 24ba138a8..bdac87ea7 100644 --- a/src/cpp/ripple/WSDoor.h +++ b/src/cpp/ripple/WSDoor.h @@ -12,7 +12,7 @@ namespace websocketpp { class server; - class server_tls; + class server_autotls; } #endif @@ -20,19 +20,18 @@ namespace websocketpp class WSDoor { private: - websocketpp::server* mEndpoint; - websocketpp::server_tls* mSEndpoint; + websocketpp::server_autotls* mSEndpoint; - boost::thread* mThread; - bool mPublic; - std::string mIp; - int mPort; + boost::thread* mThread; + bool mPublic; + std::string mIp; + int mPort; void startListening(); public: - WSDoor(const std::string& strIp, int iPort, bool bPublic) : mEndpoint(0), mSEndpoint(0), mThread(0), mPublic(bPublic), mIp(strIp), mPort(iPort) { ; } + WSDoor(const std::string& strIp, int iPort, bool bPublic) : mSEndpoint(0), mThread(0), mPublic(bPublic), mIp(strIp), mPort(iPort) { ; } void stop(); diff --git a/src/cpp/ripple/WSHandler.h b/src/cpp/ripple/WSHandler.h index 65bfa5253..04dadefd3 100644 --- a/src/cpp/ripple/WSHandler.h +++ b/src/cpp/ripple/WSHandler.h @@ -11,14 +11,17 @@ extern void initSSLContext(boost::asio::ssl::context& context, template class WSConnection; +// CAUTION: on_* functions are called by the websocket code while holding a lock + // A single instance of this object is made. // This instance dispatches all events. There is no per connection persistence. template class WSServerHandler : public endpoint_type::handler { public: - typedef typename endpoint_type::handler::connection_ptr connection_ptr; - typedef typename endpoint_type::handler::message_ptr message_ptr; + typedef typename endpoint_type::handler::connection_ptr connection_ptr; + typedef typename endpoint_type::handler::message_ptr message_ptr; + typedef boost::shared_ptr< WSConnection > wsc_ptr; // Private reasons to close. enum { @@ -37,7 +40,7 @@ protected: public: WSServerHandler(boost::shared_ptr spCtx, bool bPublic) : mCtx(spCtx), mPublic(bPublic) { - if (theConfig.WEBSOCKET_SECURE) + if (theConfig.WEBSOCKET_SECURE != 0) { initSSLContext(*mCtx, theConfig.WEBSOCKET_SSL_KEY, theConfig.WEBSOCKET_SSL_CERT, theConfig.WEBSOCKET_SSL_CHAIN); @@ -46,8 +49,6 @@ public: bool getPublic() { return mPublic; }; - - void send(connection_ptr cpClient, message_ptr mpMessage) { try @@ -83,6 +84,41 @@ public: send(cpClient, jfwWriter.write(jvObj)); } + void pingTimer(connection_ptr cpClient) + { + wsc_ptr ptr; + { + boost::mutex::scoped_lock sl(mMapLock); + typename boost::unordered_map::iterator it = mMap.find(cpClient); + if (it == mMap.end()) + return; + ptr = it->second; + } + if (ptr->onPingTimer()) + { + cLog(lsWARNING) << "Connection pings out"; + cpClient->close(websocketpp::close::status::PROTOCOL_ERROR, "ping timeout"); + } + else + { + cpClient->ping("ping"); + } + } + + void on_send_empty(connection_ptr cpClient) + { + wsc_ptr ptr; + { + boost::mutex::scoped_lock sl(mMapLock); + typename boost::unordered_map::iterator it = mMap.find(cpClient); + if (it == mMap.end()) + return; + ptr = it->second; + } + + ptr->onSendEmpty(); + } + void on_open(connection_ptr cpClient) { boost::mutex::scoped_lock sl(mMapLock); @@ -90,9 +126,21 @@ public: mMap[cpClient] = boost::make_shared< WSConnection >(this, cpClient); } + void on_pong(connection_ptr cpClient, std::string) + { + wsc_ptr ptr; + { + boost::mutex::scoped_lock sl(mMapLock); + typename boost::unordered_map::iterator it = mMap.find(cpClient); + if (it == mMap.end()) + return; + ptr = it->second; + } + ptr->onPong(); + } + void on_close(connection_ptr cpClient) { // we cannot destroy the connection while holding the map lock or we deadlock with pubLedger - typedef boost::shared_ptr< WSConnection > wsc_ptr; wsc_ptr ptr; { boost::mutex::scoped_lock sl(mMapLock); @@ -102,6 +150,11 @@ public: ptr = it->second; // prevent the WSConnection from being destroyed until we release the lock mMap.erase(it); } + ptr->preDestroy(); // Must be done before we return + + // Must be done without holding the websocket send lock + theApp->getJobQueue().addJob(jtCLIENT, + boost::bind(&WSConnection::destroy, ptr)); } void on_message(connection_ptr cpClient, message_ptr mpMessage) diff --git a/src/cpp/ripple/Wallet.cpp b/src/cpp/ripple/Wallet.cpp index 8843e693c..ed017304f 100644 --- a/src/cpp/ripple/Wallet.cpp +++ b/src/cpp/ripple/Wallet.cpp @@ -30,7 +30,8 @@ void Wallet::start() throw std::runtime_error("unable to retrieve new node identity."); } - std::cerr << "NodeIdentity: " << mNodePublicKey.humanNodePublic() << std::endl; + if (!theConfig.QUIET) + std::cerr << "NodeIdentity: " << mNodePublicKey.humanNodePublic() << std::endl; theApp->getUNL().start(); } @@ -38,6 +39,7 @@ void Wallet::start() // Retrieve network identity. bool Wallet::nodeIdentityLoad() { + Database* db=theApp->getWalletDB()->getDB(); ScopedLock sl(theApp->getWalletDB()->getDBLock()); bool bSuccess = false; @@ -59,12 +61,19 @@ bool Wallet::nodeIdentityLoad() bSuccess = true; } + if (theConfig.NODE_PUB.isValid() && theConfig.NODE_PRIV.isValid()) + { + mNodePublicKey = theConfig.NODE_PUB; + mNodePrivateKey = theConfig.NODE_PRIV; + } + return bSuccess; } // Create and store a network identity. bool Wallet::nodeIdentityCreate() { - std::cerr << "NodeIdentity: Creating." << std::endl; + if (!theConfig.QUIET) + std::cerr << "NodeIdentity: Creating." << std::endl; // // Generate the public and private key @@ -109,7 +118,8 @@ bool Wallet::nodeIdentityCreate() { % sqlEscape(strDh1024))); // XXX Check error result. - std::cerr << "NodeIdentity: Created." << std::endl; + if (!theConfig.QUIET) + std::cerr << "NodeIdentity: Created." << std::endl; return true; } @@ -121,7 +131,7 @@ bool Wallet::dataDelete(const std::string& strKey) ScopedLock sl(theApp->getRpcDB()->getDBLock()); return db->executeSQL(str(boost::format("DELETE FROM RPCData WHERE Key=%s;") - % db->escape(strKey))); + % sqlEscape(strKey))); } bool Wallet::dataFetch(const std::string& strKey, std::string& strValue) @@ -133,7 +143,7 @@ bool Wallet::dataFetch(const std::string& strKey, std::string& strValue) bool bSuccess = false; if (db->executeSQL(str(boost::format("SELECT Value FROM RPCData WHERE Key=%s;") - % db->escape(strKey))) && db->startIterRows()) + % sqlEscape(strKey))) && db->startIterRows()) { std::vector vucData = db->getBinary("Value"); strValue.assign(vucData.begin(), vucData.end()); @@ -155,8 +165,8 @@ bool Wallet::dataStore(const std::string& strKey, const std::string& strValue) bool bSuccess = false; return (db->executeSQL(str(boost::format("REPLACE INTO RPCData (Key, Value) VALUES (%s,%s);") - % db->escape(strKey) - % db->escape(strValue) + % sqlEscape(strKey) + % sqlEscape(strValue) ))); return bSuccess; diff --git a/src/cpp/ripple/WalletAddTransactor.cpp b/src/cpp/ripple/WalletAddTransactor.cpp index b821abd34..5dd935007 100644 --- a/src/cpp/ripple/WalletAddTransactor.cpp +++ b/src/cpp/ripple/WalletAddTransactor.cpp @@ -1,5 +1,7 @@ #include "WalletAddTransactor.h" +SETUP_LOG(); + TER WalletAddTransactor::doApply() { std::cerr << "WalletAdd>" << std::endl; @@ -7,9 +9,18 @@ TER WalletAddTransactor::doApply() const std::vector vucPubKey = mTxn.getFieldVL(sfPublicKey); const std::vector vucSignature = mTxn.getFieldVL(sfSignature); const uint160 uAuthKeyID = mTxn.getFieldAccount160(sfRegularKey); - const RippleAddress naMasterPubKey = RippleAddress::createAccountPublic(vucPubKey); + const RippleAddress naMasterPubKey = RippleAddress::createAccountPublic(vucPubKey); const uint160 uDstAccountID = naMasterPubKey.getAccountID(); + const uint32 uTxFlags = mTxn.getFlags(); + + if (uTxFlags) + { + cLog(lsINFO) << "WalletAdd: Malformed transaction: Invalid flags set."; + + return temINVALID_FLAG; + } + // FIXME: This should be moved to the transaction's signature check logic and cached if (!naMasterPubKey.accountPublicVerify(Serializer::getSHA512Half(uAuthKeyID.begin(), uAuthKeyID.size()), vucSignature)) { @@ -27,32 +38,38 @@ TER WalletAddTransactor::doApply() return tefCREATED; } - STAmount saAmount = mTxn.getFieldAmount(sfAmount); - STAmount saSrcBalance = mTxnAccount->getFieldAmount(sfBalance); + // Direct XRP payment. - if (saSrcBalance < saAmount) + STAmount saDstAmount = mTxn.getFieldAmount(sfAmount); + const STAmount saSrcBalance = mTxnAccount->getFieldAmount(sfBalance); + const uint32 uOwnerCount = mTxnAccount->getFieldU32(sfOwnerCount); + const uint64 uReserve = mEngine->getLedger()->getReserve(uOwnerCount); + STAmount saPaid = mTxn.getTransactionFee(); + + // Make sure have enough reserve to send. Allow final spend to use reserve for fee. + if (saSrcBalance + saPaid < saDstAmount + uReserve) // Reserve is not scaled by fee. { - std::cerr - << boost::str(boost::format("WalletAdd: Delay transaction: insufficient balance: balance=%s amount=%s") - % saSrcBalance.getText() - % saAmount.getText()) - << std::endl; + // Vote no. However, transaction might succeed, if applied in a different order. + cLog(lsINFO) << boost::str(boost::format("WalletAdd: Delay transaction: Insufficient funds: %s / %s (%d)") + % saSrcBalance.getText() % (saDstAmount + uReserve).getText() % uReserve); - return terUNFUNDED; + return tecUNFUNDED_ADD; } // Deduct initial balance from source account. - mTxnAccount->setFieldAmount(sfBalance, saSrcBalance-saAmount); + mTxnAccount->setFieldAmount(sfBalance, saSrcBalance-saDstAmount); // Create the account. sleDst = mEngine->entryCreate(ltACCOUNT_ROOT, Ledger::getAccountRootIndex(uDstAccountID)); sleDst->setFieldAccount(sfAccount, uDstAccountID); sleDst->setFieldU32(sfSequence, 1); - sleDst->setFieldAmount(sfBalance, saAmount); + sleDst->setFieldAmount(sfBalance, saDstAmount); sleDst->setFieldAccount(sfRegularKey, uAuthKeyID); std::cerr << "WalletAdd<" << std::endl; return tesSUCCESS; -} \ No newline at end of file +} + +// vim:ts=4 diff --git a/src/cpp/ripple/WalletAddTransactor.h b/src/cpp/ripple/WalletAddTransactor.h index 8bed5f0fe..7f6fef676 100644 --- a/src/cpp/ripple/WalletAddTransactor.h +++ b/src/cpp/ripple/WalletAddTransactor.h @@ -7,4 +7,6 @@ public: WalletAddTransactor(const SerializedTransaction& txn,TransactionEngineParams params, TransactionEngine* engine) : Transactor(txn,params,engine) {} TER doApply(); -}; \ No newline at end of file +}; + +// vim:ts=4 diff --git a/src/cpp/ripple/base58.h b/src/cpp/ripple/base58.h index 3a623c24f..e2fa1c367 100644 --- a/src/cpp/ripple/base58.h +++ b/src/cpp/ripple/base58.h @@ -23,7 +23,7 @@ #include "bignum.h" #include "BitcoinUtil.h" -static const char* pszBase58 = "rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz"; +extern const char* ALPHABET; inline std::string EncodeBase58(const unsigned char* pbegin, const unsigned char* pend) { @@ -51,13 +51,13 @@ inline std::string EncodeBase58(const unsigned char* pbegin, const unsigned char if (!BN_div(&dv, &rem, &bn, &bn58, pctx)) throw bignum_error("EncodeBase58 : BN_div failed"); bn = dv; - unsigned int c = rem.getulong(); - str += pszBase58[c]; + unsigned int c = rem.getuint(); + str += ALPHABET[c]; } // Leading zeroes encoded as base58 zeros for (const unsigned char* p = pbegin; p < pend && *p == 0; p++) - str += pszBase58[0]; + str += ALPHABET[0]; // Convert little endian std::string to big endian reverse(str.begin(), str.end()); @@ -82,7 +82,7 @@ inline bool DecodeBase58(const char* psz, std::vector& vchRet) // Convert big endian string to bignum for (const char* p = psz; *p; p++) { - const char* p1 = strchr(pszBase58, *p); + const char* p1 = strchr(ALPHABET, *p); if (p1 == NULL) { while (isspace(*p)) @@ -91,7 +91,7 @@ inline bool DecodeBase58(const char* psz, std::vector& vchRet) return false; break; } - bnChar.setulong(p1 - pszBase58); + bnChar.setuint(p1 - ALPHABET); if (!BN_mul(&bn, &bn, &bn58, pctx)) throw bignum_error("DecodeBase58 : BN_mul failed"); bn += bnChar; @@ -106,7 +106,7 @@ inline bool DecodeBase58(const char* psz, std::vector& vchRet) // Restore leading zeros int nLeadingZeros = 0; - for (const char* p = psz; *p == pszBase58[0]; p++) + for (const char* p = psz; *p == ALPHABET[0]; p++) nLeadingZeros++; vchRet.assign(nLeadingZeros + vchTmp.size(), 0); diff --git a/src/cpp/ripple/bignum.h b/src/cpp/ripple/bignum.h index a9a08eda2..d2e4e5549 100644 --- a/src/cpp/ripple/bignum.h +++ b/src/cpp/ripple/bignum.h @@ -89,7 +89,6 @@ public: CBigNum(unsigned char n) { BN_init(this); setulong(n); } CBigNum(unsigned short n) { BN_init(this); setulong(n); } CBigNum(unsigned int n) { BN_init(this); setulong(n); } - CBigNum(unsigned long n) { BN_init(this); setulong(n); } CBigNum(uint64 n) { BN_init(this); setuint64(n); } explicit CBigNum(uint256 n) { BN_init(this); setuint256(n); } @@ -99,15 +98,9 @@ public: setvch(vch); } - void setulong(unsigned long n) + void setuint(unsigned int n) { - if (!BN_set_word(this, n)) - throw bignum_error("CBigNum conversion from unsigned long : BN_set_word failed"); - } - - unsigned long getulong() const - { - return BN_get_word(this); + setulong(static_cast(n)); } unsigned int getuint() const @@ -159,31 +152,42 @@ public: BN_mpi2bn(pch, p - pch, this); } + uint64 getuint64() const + { +#if (ULONG_MAX > UINT_MAX) + return static_cast(getulong()); +#else + int len = BN_num_bytes(this); + if (len > 8) + throw std::runtime_error("BN getuint64 overflow"); + + unsigned char buf[8]; + memset(buf, 0, sizeof(buf)); + BN_bn2bin(this, buf + 8 - len); + return + static_cast(buf[0]) << 56 | static_cast(buf[1]) << 48 | + static_cast(buf[2]) << 40 | static_cast(buf[3]) << 32 | + static_cast(buf[4]) << 24 | static_cast(buf[5]) << 16 | + static_cast(buf[6]) << 8 | static_cast(buf[7]); +#endif + } + void setuint64(uint64 n) { - unsigned char pch[sizeof(n) + 6]; - unsigned char* p = pch + 4; - bool fLeadingZeroes = true; - for (int i = 0; i < 8; i++) - { - unsigned char c = (n >> 56) & 0xff; - n <<= 8; - if (fLeadingZeroes) - { - if (c == 0) - continue; - if (c & 0x80) - *p++ = 0; - fLeadingZeroes = false; - } - *p++ = c; - } - unsigned int nSize = p - (pch + 4); - pch[0] = (nSize >> 24) & 0xff; - pch[1] = (nSize >> 16) & 0xff; - pch[2] = (nSize >> 8) & 0xff; - pch[3] = (nSize) & 0xff; - BN_mpi2bn(pch, p - pch, this); +#if (ULONG_MAX > UINT_MAX) + setulong(static_cast(n)); +#else + unsigned char buf[8]; + buf[0] = static_cast((n >> 56) & 0xff); + buf[1] = static_cast((n >> 48) & 0xff); + buf[2] = static_cast((n >> 40) & 0xff); + buf[3] = static_cast((n >> 32) & 0xff); + buf[4] = static_cast((n >> 24) & 0xff); + buf[5] = static_cast((n >> 16) & 0xff); + buf[6] = static_cast((n >> 8) & 0xff); + buf[7] = static_cast((n) & 0xff); + BN_bin2bn(buf, 8, this); +#endif } void setuint256(const uint256& n) @@ -300,7 +304,7 @@ public: if (!BN_div(&dv, &rem, &bn, &bnBase, pctx)) throw bignum_error("CBigNum::ToString() : BN_div failed"); bn = dv; - unsigned int c = rem.getulong(); + unsigned int c = rem.getuint(); str += "0123456789abcdef"[c]; } if (BN_is_negative(this)) @@ -435,6 +439,22 @@ public: friend inline const CBigNum operator-(const CBigNum& a, const CBigNum& b); friend inline const CBigNum operator/(const CBigNum& a, const CBigNum& b); friend inline const CBigNum operator%(const CBigNum& a, const CBigNum& b); + + private: + + // private because the size of an unsigned long varies by platform + + void setulong(unsigned long n) + { + if (!BN_set_word(this, n)) + throw bignum_error("CBigNum conversion from unsigned long : BN_set_word failed"); + } + + unsigned long getulong() const + { + return BN_get_word(this); + } + }; diff --git a/src/cpp/ripple/main.cpp b/src/cpp/ripple/main.cpp index fa3606777..4faab8102 100644 --- a/src/cpp/ripple/main.cpp +++ b/src/cpp/ripple/main.cpp @@ -1,6 +1,7 @@ -#include + #include +#include #include #include #include @@ -8,8 +9,9 @@ #include "Application.h" #include "CallRPC.h" #include "Config.h" -#include "utils.h" #include "Log.h" +#include "RPCHandler.h" +#include "utils.h" namespace po = boost::program_options; @@ -17,9 +19,35 @@ extern bool AddSystemEntropy(); using namespace std; using namespace boost::unit_test; -void startServer() +void setupServer() { theApp = new Application(); + theApp->setup(); +} + +void startServer() +{ + // + // Execute start up rpc commands. + // + if (theConfig.RPC_STARTUP.isArray()) + { + for (int i = 0; i != theConfig.RPC_STARTUP.size(); ++i) + { + const Json::Value& jvCommand = theConfig.RPC_STARTUP[i]; + + if (!theConfig.QUIET) + std::cerr << "Startup RPC: " << jvCommand << std::endl; + + RPCHandler rhHandler(&theApp->getOPs()); + + Json::Value jvResult = rhHandler.doCommand(jvCommand, RPCHandler::ADMIN); + + if (!theConfig.QUIET) + std::cerr << "Result: " << jvResult << std::endl; + } + } + theApp->run(); // Blocks till we get a stop RPC. } @@ -90,17 +118,19 @@ int main(int argc, char* argv[]) // // Set up option parsing. // - po::options_description desc("Options"); + po::options_description desc("General Options"); desc.add_options() ("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.") + ("testnet,t", "Run in test net mode.") + ("unittest,u", "Perform unit tests.") ("parameters", po::value< vector >(), "Specify comma separated parameters.") ("quiet,q", "Reduce diagnotics.") - ("verbose,v", "Increase log level.") + ("verbose,v", "Verbose logging.") ("load", "Load the current ledger from the local DB.") + ("ledger", po::value(), "Load the specified ledger and start from .") ("start", "Start from a fresh Ledger.") ("net", "Get the initial ledger from the network.") ; @@ -119,7 +149,6 @@ int main(int argc, char* argv[]) iResult = 2; } - if (iResult) { nothing(); @@ -141,14 +170,19 @@ int main(int argc, char* argv[]) } } - if (vm.count("verbose")) + if (vm.count("quiet")) + Log::setMinSeverity(lsFATAL, true); + else if (vm.count("verbose")) Log::setMinSeverity(lsTRACE, true); else - Log::setMinSeverity(lsWARNING, true); + Log::setMinSeverity(lsINFO, true); - if (vm.count("test")) + InstanceType::multiThread(); + + if (vm.count("unittest")) { unit_test_main(init_unit_test, argc, argv); + return 0; } @@ -156,16 +190,26 @@ int main(int argc, char* argv[]) { theConfig.setup( vm.count("conf") ? vm["conf"].as() : "", // Config file. + !!vm.count("testnet"), // Testnet flag. !!vm.count("quiet")); // Quiet flag. if (vm.count("standalone")) { theConfig.RUN_STANDALONE = true; + theConfig.LEDGER_HISTORY = 0; } } if (vm.count("start")) theConfig.START_UP = Config::FRESH; - else if (vm.count("load")) theConfig.START_UP = Config::LOAD; + if (vm.count("ledger")) + { + theConfig.START_LEDGER = vm["ledger"].as(); + theConfig.START_UP = Config::LOAD; + } + else if (vm.count("load")) + { + theConfig.START_UP = Config::LOAD; + } else if (vm.count("net")) theConfig.START_UP = Config::NETWORK; if (iResult) @@ -179,6 +223,7 @@ int main(int argc, char* argv[]) else if (!vm.count("parameters")) { // No arguments. Run server. + setupServer(); startServer(); } else diff --git a/src/cpp/ripple/ripple.proto b/src/cpp/ripple/ripple.proto index 3e238ddeb..0cb6c45ba 100644 --- a/src/cpp/ripple/ripple.proto +++ b/src/cpp/ripple/ripple.proto @@ -71,6 +71,7 @@ message TMHello { optional bytes ledgerPrevious = 10; // the ledger before the last closed ledger optional bool nodePrivate = 11; // Request to not forward IP. optional TMProofWork proofOfWork = 12; // request/provide proof of work + optional bool testNet = 13; // Running as testnet. } @@ -291,8 +292,8 @@ message TMLedgerData { message TMPing { enum pingType { - PING = 0; // we want a reply - PONG = 1; // this is a reply + ptPING = 0; // we want a reply + ptPONG = 1; // this is a reply } required pingType type = 1; optional uint32 seq = 2; // detect stale replies, ensure other side is reading diff --git a/src/cpp/ripple/rpc.cpp b/src/cpp/ripple/rpc.cpp index 2beec4c66..095ed977e 100644 --- a/src/cpp/ripple/rpc.cpp +++ b/src/cpp/ripple/rpc.cpp @@ -25,12 +25,13 @@ using namespace boost::asio; Json::Value JSONRPCError(int code, const std::string& message) { Json::Value error(Json::objectValue); - error["code"]=Json::Value(code); - error["message"]=Json::Value(message); + + error["code"] = Json::Value(code); + error["message"] = Json::Value(message); + return error; } - // // HTTP protocol // @@ -38,19 +39,24 @@ Json::Value JSONRPCError(int code, const std::string& message) // and to be compatible with other JSON-RPC implementations. // -std::string createHTTPPost(const std::string& strMsg, const std::map& mapRequestHeaders) +std::string createHTTPPost(const std::string& strPath, const std::string& strMsg, const std::map& mapRequestHeaders) { std::ostringstream s; - s << "POST / HTTP/1.1\r\n" - << "User-Agent: coin-json-rpc/" << FormatFullVersion() << "\r\n" + + s << "POST " + << (strPath.empty() ? "/" : strPath) + << " HTTP/1.1\r\n" + << "User-Agent: " SYSTEM_NAME "-json-rpc/" << FormatFullVersion() << "\r\n" << "Host: 127.0.0.1\r\n" << "Content-Type: application/json\r\n" << "Content-Length: " << strMsg.size() << "\r\n" << "Accept: application/json\r\n"; - typedef const std::pair HeaderType; - BOOST_FOREACH(HeaderType& item, mapRequestHeaders) + typedef std::map::value_type HeaderType; + + BOOST_FOREACH(const HeaderType& item, mapRequestHeaders) s << item.first << ": " << item.second << "\r\n"; + s << "\r\n" << strMsg; return s.str(); @@ -76,7 +82,7 @@ std::string HTTPReply(int nStatus, const std::string& strMsg) if (nStatus == 401) return strprintf("HTTP/1.0 401 Authorization Required\r\n" "Date: %s\r\n" - "Server: coin-json-rpc/%s\r\n" + "Server: " SYSTEM_NAME "-json-rpc/%s\r\n" "WWW-Authenticate: Basic realm=\"jsonrpc\"\r\n" "Content-Type: text/html\r\n" "Content-Length: 296\r\n" @@ -107,7 +113,7 @@ std::string HTTPReply(int nStatus, const std::string& strMsg) "%s" "Content-Length: %d\r\n" "Content-Type: application/json; charset=UTF-8\r\n" - "Server: coin-json-rpc/%s\r\n" + "Server: " SYSTEM_NAME "-json-rpc/%s\r\n" "\r\n" "%s\r\n", nStatus, @@ -116,7 +122,7 @@ std::string HTTPReply(int nStatus, const std::string& strMsg) access.c_str(), strMsg.size() + 2, SERVER_VERSION, - strMsg.c_str()); + strMsg.c_str()); } int ReadHTTPStatus(std::basic_istream& stream) @@ -180,7 +186,6 @@ int ReadHTTP(std::basic_istream& stream, std::map& mapHeaders) + +bool HTTPAuthorized(const std::map& mapHeaders) { - std::string strAuth = mapHeaders["authorization"]; - if (strAuth.substr(0,6) != "Basic ") - return false; - std::string strUserPass64 = strAuth.substr(6); boost::trim(strUserPass64); + std::map::const_iterator it = mapHeaders.find("authorization"); + if ((it == mapHeaders.end()) || (it->second.substr(0,6) != "Basic ")) + return theConfig.RPC_USER.empty() && theConfig.RPC_PASSWORD.empty(); + + std::string strUserPass64 = it->second.substr(6); + boost::trim(strUserPass64); std::string strUserPass = DecodeBase64(strUserPass64); std::string::size_type nColon = strUserPass.find(":"); if (nColon == std::string::npos) return false; + std::string strUser = strUserPass.substr(0, nColon); std::string strPassword = strUserPass.substr(nColon+1); - return (strUser == mapArgs["-rpcuser"] && strPassword == mapArgs["-rpcpassword"]); -}*/ + return (strUser == theConfig.RPC_USER) && (strPassword == theConfig.RPC_PASSWORD); +} // // JSON-RPC protocol. Bitcoin speaks version 1.0 for maximum compatibility, @@ -253,3 +261,5 @@ void ErrorReply(std::ostream& stream, const Json::Value& objError, const Json::V std::string strReply = JSONRPCReply(Json::Value(), objError, id); stream << HTTPReply(nStatus, strReply) << std::flush; } + +// vim:ts=4 diff --git a/src/cpp/ripple/uint256.h b/src/cpp/ripple/uint256.h index e7e5e449e..88048bb69 100644 --- a/src/cpp/ripple/uint256.h +++ b/src/cpp/ripple/uint256.h @@ -73,7 +73,7 @@ public: zero(); // Put in least significant bits. - ((uint64_t *) end())[-1] = htobe64(uHost); + ((uint64*) end())[-1] = htobe64(uHost); return *this; } @@ -206,12 +206,12 @@ public: friend inline bool operator==(const base_uint& a, const base_uint& b) { - return !compare(a, b); + return memcmp(a.pn, b.pn, sizeof(a.pn)) == 0; } friend inline bool operator!=(const base_uint& a, const base_uint& b) { - return !!compare(a, b); + return memcmp(a.pn, b.pn, sizeof(a.pn)) != 0; } std::string GetHex() const @@ -219,14 +219,18 @@ public: return strHex(begin(), size()); } - void SetHex(const char* psz) + // Allow leading whitespace. + // Allow leading "0x". + // To be valid must be '\0' terminated. + bool SetHex(const char* psz, bool bStrict=false) { // skip leading spaces - while (isspace(*psz)) - psz++; + if (!bStrict) + while (isspace(*psz)) + psz++; // skip 0x - if (psz[0] == '0' && tolower(psz[1]) == 'x') + if (!bStrict && psz[0] == '0' && tolower(psz[1]) == 'x') psz += 2; // hex char to int @@ -278,11 +282,13 @@ public: : phexdigit[*pBegin++]; *pOut++ = cHigh | cLow; } + + return !*pEnd; } - void SetHex(const std::string& str) + bool SetHex(const std::string& str, bool bStrict=false) { - SetHex(str.c_str()); + return SetHex(str.c_str(), bStrict); } std::string ToString() const @@ -292,22 +298,22 @@ public: unsigned char* begin() { - return (unsigned char*) &pn[0]; + return reinterpret_cast(pn); } unsigned char* end() { - return (unsigned char*) &pn[WIDTH]; + return reinterpret_cast(pn + WIDTH); } const unsigned char* begin() const { - return (const unsigned char*) &pn[0]; + return reinterpret_cast(pn); } const unsigned char* end() const { - return (unsigned char*) &pn[WIDTH]; + return reinterpret_cast(pn + WIDTH); } unsigned int size() const @@ -417,9 +423,8 @@ public: uint256& operator=(const basetype& b) { - for (int i = 0; i < WIDTH; i++) - pn[i] = b.pn[i]; - + if (pn != b.pn) + memcpy(pn, b.pn, sizeof(pn)); return *this; } @@ -433,7 +438,7 @@ public: zero(); // Put in least significant bits. - ((uint64_t *) end())[-1] = htobe64(uHost); + ((uint64*) end())[-1] = htobe64(uHost); return *this; } @@ -469,7 +474,7 @@ inline const uint256 operator|(const base_uint256& a, const uint256& b) { return inline bool operator==(const uint256& a, const base_uint256& b) { return (base_uint256)a == (base_uint256)b; } inline bool operator!=(const uint256& a, const base_uint256& b) { return (base_uint256)a != (base_uint256)b; } inline const uint256 operator^(const uint256& a, const base_uint256& b) { return (base_uint256)a ^ (base_uint256)b; } -inline const uint256 operator&(const uint256& a, const base_uint256& b) { return (base_uint256)a & (base_uint256)b; } +inline const uint256 operator&(const uint256& a, const base_uint256& b) { return uint256(a) &= b; } inline const uint256 operator|(const uint256& a, const base_uint256& b) { return (base_uint256)a | (base_uint256)b; } inline bool operator==(const uint256& a, const uint256& b) { return (base_uint256)a == (base_uint256)b; } inline bool operator!=(const uint256& a, const uint256& b) { return (base_uint256)a != (base_uint256)b; } @@ -651,7 +656,7 @@ public: zero(); // Put in least significant bits. - ((uint64_t *) end())[-1] = htobe64(uHost); + ((uint64*) end())[-1] = htobe64(uHost); return *this; } diff --git a/src/cpp/ripple/utils.cpp b/src/cpp/ripple/utils.cpp index 8e94f5a10..8dc565924 100644 --- a/src/cpp/ripple/utils.cpp +++ b/src/cpp/ripple/utils.cpp @@ -1,9 +1,11 @@ #include "utils.h" #include "uint256.h" +#include #include #include #include +#include #include @@ -73,14 +75,48 @@ int charUnHex(char cDigit) : -1; } -void strUnHex(std::string& strDst, const std::string& strSrc) +int strUnHex(std::string& strDst, const std::string& strSrc) { - int iBytes = strSrc.size()/2; + int iBytes = (strSrc.size()+1)/2; strDst.resize(iBytes); - for (int i=0; i != iBytes; i++) - strDst[i] = (charUnHex(strSrc[i*2]) << 4) | charUnHex(strSrc[i*2+1]); + const char* pSrc = &strSrc[0]; + char* pDst = &strDst[0]; + + if (strSrc.size() & 1) + { + int c = charUnHex(*pSrc++); + + if (c < 0) + { + iBytes = -1; + } + else + { + *pDst++ = c; + } + } + + for (int i=0; iBytes >= 0 && i != iBytes; i++) + { + int cHigh = charUnHex(*pSrc++); + int cLow = charUnHex(*pSrc++); + + if (cHigh < 0 || cLow < 0) + { + iBytes = -1; + } + else + { + strDst[i] = (cHigh << 4) | cLow; + } + } + + if (iBytes < 0) + strDst.clear(); + + return iBytes; } std::vector strUnHex(const std::string& strSrc) @@ -191,6 +227,33 @@ bool parseIpPort(const std::string& strSource, std::string& strIP, int& iPort) return bValid; } +bool parseUrl(const std::string& strUrl, std::string& strScheme, std::string& strDomain, int& iPort, std::string& strPath) +{ + // scheme://username:password@hostname:port/rest + static boost::regex reUrl("(?i)\\`\\s*([[:alpha:]][-+.[:alpha:][:digit:]]*)://([^:/]+)(?::(\\d+))?(/.*)?\\s*?\\'"); + boost::smatch smMatch; + + bool bMatch = boost::regex_match(strUrl, smMatch, reUrl); // Match status code. + + if (bMatch) + { + std::string strPort; + + strScheme = smMatch[1]; + strDomain = smMatch[2]; + strPort = smMatch[3]; + strPath = smMatch[4]; + + boost::algorithm::to_lower(strScheme); + + iPort = strPort.empty() ? -1 : lexical_cast_s(strPort); + // std::cerr << strUrl << " : " << bMatch << " : '" << strDomain << "' : '" << strPort << "' : " << iPort << " : '" << strPath << "'" << std::endl; + } + // std::cerr << strUrl << " : " << bMatch << " : '" << strDomain << "' : '" << strPath << "'" << std::endl; + + return bMatch; +} + // // Quality parsing // - integers as is. @@ -267,4 +330,52 @@ uint32_t be32toh(uint32_t value){ return(value); } #endif +BOOST_AUTO_TEST_SUITE( Utils) + +BOOST_AUTO_TEST_CASE( ParseUrl ) +{ + std::string strScheme; + std::string strDomain; + int iPort; + std::string strPath; + + if (!parseUrl("lower://domain", strScheme, strDomain, iPort, strPath)) + BOOST_FAIL("parseUrl: lower://domain failed"); + + if (strScheme != "lower") + BOOST_FAIL("parseUrl: lower://domain : scheme failed"); + + if (strDomain != "domain") + BOOST_FAIL("parseUrl: lower://domain : domain failed"); + + if (iPort != -1) + BOOST_FAIL("parseUrl: lower://domain : port failed"); + + if (strPath != "") + BOOST_FAIL("parseUrl: lower://domain : path failed"); + + if (!parseUrl("UPPER://domain:234/", strScheme, strDomain, iPort, strPath)) + BOOST_FAIL("parseUrl: UPPER://domain:234/ failed"); + + if (strScheme != "upper") + BOOST_FAIL("parseUrl: UPPER://domain:234/ : scheme failed"); + + if (iPort != 234) + BOOST_FAIL(boost::str(boost::format("parseUrl: UPPER://domain:234/ : port failed: %d") % iPort)); + + if (strPath != "/") + BOOST_FAIL("parseUrl: UPPER://domain:234/ : path failed"); + + if (!parseUrl("Mixed://domain/path", strScheme, strDomain, iPort, strPath)) + BOOST_FAIL("parseUrl: Mixed://domain/path failed"); + + if (strScheme != "mixed") + BOOST_FAIL("parseUrl: Mixed://domain/path tolower failed"); + + if (strPath != "/path") + BOOST_FAIL("parseUrl: Mixed://domain/path path failed"); +} + +BOOST_AUTO_TEST_SUITE_END() + // vim:ts=4 diff --git a/src/cpp/ripple/utils.h b/src/cpp/ripple/utils.h index 92161ffae..77ae2f26f 100644 --- a/src/cpp/ripple/utils.h +++ b/src/cpp/ripple/utils.h @@ -3,6 +3,11 @@ #include #include +#include + +#if BOOST_VERSION < 104700 +#error Boost 1.47 or later is required +#endif #include #include "types.h" @@ -160,8 +165,27 @@ inline static std::string sqlEscape(const std::string& strSrc) inline static std::string sqlEscape(const std::vector& vecSrc) { - static boost::format f("X'%s'"); - return str(f % strHex(vecSrc)); + size_t size = vecSrc.size(); + if (size == 0) + return "X''"; + + std::string j(size * 2 + 3, 0); + + unsigned char *oPtr = reinterpret_cast(&*j.begin()); + const unsigned char *iPtr = &vecSrc[0]; + + *oPtr++ = 'X'; + *oPtr++ = '\''; + + for (int i = size; i != 0; --i) + { + unsigned char c = *iPtr++; + *oPtr++ = charHex(c >> 4); + *oPtr++ = charHex(c & 15); + } + + *oPtr++ = '\''; + return j; } template @@ -174,7 +198,7 @@ bool isZero(Iterator first, int iSize) } int charUnHex(char cDigit); -void strUnHex(std::string& strDst, const std::string& strSrc); +int strUnHex(std::string& strDst, const std::string& strSrc); uint64_t uintFromHex(const std::string& strSrc); @@ -260,6 +284,8 @@ template T range_check_cast(const U& value, const T& min return static_cast(value); } +bool parseUrl(const std::string& strUrl, std::string& strScheme, std::string& strDomain, int& iPort, std::string& strPath); + #endif // vim:ts=4 diff --git a/src/cpp/websocketpp/src/connection.hpp b/src/cpp/websocketpp/src/connection.hpp index a21605955..ed1b67bb8 100644 --- a/src/cpp/websocketpp/src/connection.hpp +++ b/src/cpp/websocketpp/src/connection.hpp @@ -894,13 +894,13 @@ public: // try and read more if (m_state != session::state::CLOSED && - m_processor->get_bytes_needed() > 0 && + m_processor && + m_processor->get_bytes_needed() > 0 && // FIXME: m_processor had been reset here !m_protocol_error) { // TODO: read timeout timer? - - boost::asio::async_read( - socket_type::get_socket(), + + socket_type::get_socket().async_read( m_buf, boost::asio::transfer_at_least(std::min( m_read_threshold, @@ -1209,8 +1209,7 @@ public: //m_endpoint.alog().at(log::alevel::DEVEL) << "write header: " << zsutil::to_hex(m_write_queue.front()->get_header()) << log::endl; - boost::asio::async_write( - socket_type::get_socket(), + socket_type::get_socket().async_write( m_write_buf, m_strand.wrap(boost::bind( &type::handle_write, @@ -1225,7 +1224,9 @@ public: alog()->at(log::alevel::DEBUG_CLOSE) << "Exit after inturrupt" << log::endl; terminate(false); - } + } else { + m_handler->on_send_empty(type::shared_from_this()); + } } } @@ -1267,7 +1268,7 @@ public: if (m_write_queue.size() == 0) { alog()->at(log::alevel::DEBUG_CLOSE) << "handle_write called with empty queue" << log::endl; - return; + return; } m_write_buffer -= m_write_queue.front()->get_payload().size(); diff --git a/src/cpp/websocketpp/src/endpoint.hpp b/src/cpp/websocketpp/src/endpoint.hpp index f9ac5a582..862243c1c 100644 --- a/src/cpp/websocketpp/src/endpoint.hpp +++ b/src/cpp/websocketpp/src/endpoint.hpp @@ -29,7 +29,7 @@ #define WEBSOCKETPP_ENDPOINT_HPP #include "connection.hpp" -#include "sockets/plain.hpp" // should this be here? +#include "sockets/autotls.hpp" // should this be here? #include "logger/logger.hpp" #include @@ -74,7 +74,7 @@ protected: */ template < template class role, - template class socket = socket::plain, + template class socket = socket::autotls, template class logger = log::logger> class endpoint : public endpoint_base, diff --git a/src/cpp/websocketpp/src/http/parser.hpp b/src/cpp/websocketpp/src/http/parser.hpp index 632346537..f79a3753c 100644 --- a/src/cpp/websocketpp/src/http/parser.hpp +++ b/src/cpp/websocketpp/src/http/parser.hpp @@ -159,6 +159,7 @@ public: ss >> val; set_version(val); } else { + set_method(request); return false; } diff --git a/src/cpp/websocketpp/src/logger/logger.hpp b/src/cpp/websocketpp/src/logger/logger.hpp index 37ccdf78a..6d1a5dfa5 100644 --- a/src/cpp/websocketpp/src/logger/logger.hpp +++ b/src/cpp/websocketpp/src/logger/logger.hpp @@ -71,7 +71,7 @@ namespace alevel { } namespace elevel { - typedef uint16_t value; + typedef uint32_t value; // make these two values different types DJS static const value OFF = 0x0; @@ -84,15 +84,16 @@ namespace elevel { static const value ALL = 0xFFFF; } + +extern void websocketLog(alevel::value, const std::string&); +extern void websocketLog(elevel::value, const std::string&); template class logger { public: template logger& operator<<(T a) { - if (test_level(m_write_level)) { - m_oss << a; - } + m_oss << a; // For now, make this unconditional DJS return *this; } @@ -130,6 +131,9 @@ public: } logger& print() { + websocketLog(m_write_level, m_oss.str()); // Hand to our logger DJS + m_oss.str(""); +#if 0 if (test_level(m_write_level)) { std::cout << m_prefix << boost::posix_time::to_iso_extended_string( @@ -137,8 +141,8 @@ public: ) << " [" << m_write_level << "] " << m_oss.str() << std::endl; m_oss.str(""); } - - return *this; +#endif + return *this; } logger& at(level_type l) { diff --git a/src/cpp/websocketpp/src/roles/server.hpp b/src/cpp/websocketpp/src/roles/server.hpp index 339935580..7f1758f01 100644 --- a/src/cpp/websocketpp/src/roles/server.hpp +++ b/src/cpp/websocketpp/src/roles/server.hpp @@ -53,6 +53,37 @@ namespace websocketpp { +typedef boost::asio::buffers_iterator bufIterator; + +static std::pair match_header(bufIterator begin, bufIterator end) +{ + static const std::string eol_match = "\n"; + static const std::string header_match = "\n\r\n"; + static const std::string alt_header_match = "\n\n"; + static const std::string flash_match = ""; + + // Do we have a complete HTTP request + bufIterator it = std::search(begin, end, header_match.begin(), header_match.end()); + if (it != end) + return std::make_pair(it + header_match.size(), true); + it = std::search(begin, end, alt_header_match.begin(), alt_header_match.end()); + if (it != end) + return std::make_pair(it + alt_header_match.size(), true); + + // If we don't have a flash policy request, we're done + it = std::search(begin, end, flash_match.begin(), flash_match.end()); + if (it == end) // No match + return std::make_pair(end, false); + + // If we have a line ending before the flash policy request, treat as http + bufIterator it2 = std::search(begin, end, eol_match.begin(), eol_match.end()); + if ((it2 != end) && (it2 < it)) + return std::make_pair(end, false); + + // Treat as flash policy request + return std::make_pair(it + flash_match.size(), true); +} + // Forward declarations template struct endpoint_traits; @@ -189,6 +220,8 @@ public: virtual void on_pong(connection_ptr con,std::string) {} virtual void on_pong_timeout(connection_ptr con,std::string) {} virtual void http(connection_ptr con) {} + + virtual void on_send_empty(connection_ptr con) {} }; server(boost::asio::io_service& m) @@ -388,6 +421,8 @@ template void server::handle_accept(connection_ptr con, const boost::system::error_code& error) { + bool delay = false; + boost::lock_guard lock(m_endpoint.m_lock); try @@ -398,10 +433,8 @@ void server::handle_accept(connection_ptr con, if (error == boost::system::errc::too_many_files_open) { con->m_fail_reason = "too many files open"; + delay = true; - // TODO: make this configurable - //m_timer.expires_from_now(boost::posix_time::milliseconds(1000)); - //m_timer.async_wait(boost::bind(&type::start_accept,this)); } else if (error == boost::asio::error::operation_aborted) { con->m_fail_reason = "io_service operation canceled"; @@ -426,7 +459,13 @@ void server::handle_accept(connection_ptr con, << "handle_accept caught exception: " << e.what() << log::endl; } - this->start_accept(); + if (delay) + { // Don't spin if too many files are open DJS + m_timer.expires_from_now(boost::posix_time::milliseconds(500)); + m_timer.async_wait(boost::bind(&type::start_accept,this)); + } + else + this->start_accept(); } // server::connection Implimentation @@ -505,18 +544,17 @@ void server::connection::async_init() { // TODO: make this value configurable m_connection.register_timeout(5000,fail::status::TIMEOUT_WS, "Timeout on WebSocket handshake"); - - boost::asio::async_read_until( - m_connection.get_socket(), - m_connection.buffer(), - "\r\n\r\n", - m_connection.get_strand().wrap(boost::bind( - &type::handle_read_request, - m_connection.shared_from_this(), - boost::asio::placeholders::error, - boost::asio::placeholders::bytes_transferred - )) - ); + + m_connection.get_socket().async_read_until( + m_connection.buffer(), + match_header, + m_connection.get_strand().wrap(boost::bind( + &type::handle_read_request, + m_connection.shared_from_this(), + boost::asio::placeholders::error, + boost::asio::placeholders::bytes_transferred + )) + ); } /// processes the response from an async read for an HTTP header @@ -541,6 +579,27 @@ void server::connection::handle_read_request( if (!m_request.parse_complete(request)) { // not a valid HTTP request/response + if (m_request.method().find("") != std::string::npos) + { // set m_version to -1, call async_write->handle_write_response + std::string reply = + "" + "(m_connection.get_raw_socket().local_endpoint().port()); + reply += "\"/>"; + reply.append("\0", 1); + + m_version = -1; + shared_const_buffer buffer(reply); + m_connection.get_socket().async_write( + shared_const_buffer(reply), + boost::bind( + &type::handle_write_response, + m_connection.shared_from_this(), + boost::asio::placeholders::error + ) + ); + return; + } throw http::exception("Received invalid HTTP Request",http::status_code::BAD_REQUEST); } @@ -665,9 +724,9 @@ void server::connection::handle_read_request( { // TODO: this makes the assumption that WS and HTTP // default ports are the same. - m_uri.reset(new uri(m_endpoint.is_secure(),h,m_request.uri())); + m_uri.reset(new uri(m_connection.is_secure(),h,m_request.uri())); } else { - m_uri.reset(new uri(m_endpoint.is_secure(), + m_uri.reset(new uri(m_connection.is_secure(), h.substr(0,last_colon), h.substr(last_colon+1), m_request.uri())); @@ -787,17 +846,16 @@ void server::connection::write_response() { shared_const_buffer buffer(raw); m_endpoint.m_alog->at(log::alevel::DEBUG_HANDSHAKE) << raw << log::endl; - - boost::asio::async_write( - m_connection.get_socket(), + + m_connection.get_socket().async_write( //boost::asio::buffer(raw), buffer, - boost::bind( - &type::handle_write_response, - m_connection.shared_from_this(), - boost::asio::placeholders::error - ) - ); + boost::bind( + &type::handle_write_response, + m_connection.shared_from_this(), + boost::asio::placeholders::error + ) + ); } template diff --git a/src/cpp/websocketpp/src/sockets/autotls.hpp b/src/cpp/websocketpp/src/sockets/autotls.hpp new file mode 100644 index 000000000..8ab2bca94 --- /dev/null +++ b/src/cpp/websocketpp/src/sockets/autotls.hpp @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2011, Peter Thorson. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the WebSocket++ Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef WEBSOCKETPP_SOCKET_AUTOTLS_HPP +#define WEBSOCKETPP_SOCKET_AUTOTLS_HPP + +#include "../common.hpp" +#include "socket_base.hpp" +#include "../../../ripple/AutoSocket.h" + +#include +#include +#include + +#include + +namespace websocketpp { +namespace socket { + +template +class autotls { +public: + typedef autotls type; + typedef AutoSocket autotls_socket; + typedef boost::shared_ptr autotls_socket_ptr; + + // should be private friended + boost::asio::io_service& get_io_service() { + return m_io_service; + } + + static void handle_shutdown(autotls_socket_ptr, const boost::system::error_code&) { + } + + void set_secure_only() { + m_secure_only = true; + } + + void set_plain_only() { + m_plain_only = true; + } + + // should be private friended? + autotls_socket::handshake_type get_handshake_type() { + if (static_cast< endpoint_type* >(this)->is_server()) { + return boost::asio::ssl::stream_base::server; + } else { + return boost::asio::ssl::stream_base::client; + } + } + + // TLS policy adds the on_tls_init method to the handler to allow the user + // to set up their asio TLS context. + class handler_interface { + public: + virtual ~handler_interface() {} + + virtual void on_tcp_init() {}; + virtual boost::shared_ptr on_tls_init() = 0; + }; + + // Connection specific details + template + class connection { + public: + // should these two be public or protected. If protected, how? + autotls_socket::lowest_layer_type& get_raw_socket() { + return m_socket_ptr->lowest_layer(); + } + + autotls_socket& get_socket() { + return *m_socket_ptr; + } + + bool is_secure() { + return m_socket_ptr->isSecure(); + } + protected: + connection(autotls& e) + : m_endpoint(e) + , m_connection(static_cast< connection_type& >(*this)) {} + + void init() { + m_context_ptr = m_connection.get_handler()->on_tls_init(); + + if (!m_context_ptr) { + throw "handler was unable to init autotls, connection error"; + } + + m_socket_ptr = + autotls_socket_ptr(new autotls_socket(m_endpoint.get_io_service(), *m_context_ptr, + m_endpoint.m_secure_only, m_endpoint.m_plain_only)); + } + + void async_init(boost::function callback) + { + m_connection.get_handler()->on_tcp_init(); + + // wait for TLS handshake + // TODO: configurable value + m_connection.register_timeout(5000, + fail::status::TIMEOUT_TLS, + "Timeout on TLS handshake"); + + m_socket_ptr->async_handshake( + m_endpoint.get_handshake_type(), + boost::bind( + &connection::handle_init, + this, + callback, + boost::asio::placeholders::error + ) + ); + } + + void handle_init(socket_init_callback callback,const boost::system::error_code& error) { + m_connection.cancel_timeout(); + callback(error); + } + + // note, this function for some reason shouldn't/doesn't need to be + // called for plain HTTP connections. not sure why. + bool shutdown() { + boost::system::error_code ignored_ec; + + m_socket_ptr->async_shutdown( // Don't block on connection shutdown DJS + boost::bind( + &autotls::handle_shutdown, + m_socket_ptr, + boost::asio::placeholders::error + ) + ); + + if (ignored_ec) { + return false; + } else { + return true; + } + } + private: + boost::shared_ptr m_context_ptr; + autotls_socket_ptr m_socket_ptr; + autotls& m_endpoint; + connection_type& m_connection; + }; +protected: + autotls (boost::asio::io_service& m) : m_io_service(m), m_secure_only(false), m_plain_only(false) {} +private: + boost::asio::io_service& m_io_service; + bool m_secure_only; + bool m_plain_only; +}; + +} // namespace socket +} // namespace websocketpp + +#endif // WEBSOCKETPP_SOCKET_AUTOTLS_HPP diff --git a/src/cpp/websocketpp/src/sockets/plain.hpp b/src/cpp/websocketpp/src/sockets/plain.hpp index 48aa91079..c7ecaa96b 100644 --- a/src/cpp/websocketpp/src/sockets/plain.hpp +++ b/src/cpp/websocketpp/src/sockets/plain.hpp @@ -25,6 +25,8 @@ * */ +#error Use Auto TLS only + #ifndef WEBSOCKETPP_SOCKET_PLAIN_HPP #define WEBSOCKETPP_SOCKET_PLAIN_HPP diff --git a/src/cpp/websocketpp/src/sockets/tls.hpp b/src/cpp/websocketpp/src/sockets/tls.hpp index 156901a4e..34e7af8ca 100644 --- a/src/cpp/websocketpp/src/sockets/tls.hpp +++ b/src/cpp/websocketpp/src/sockets/tls.hpp @@ -25,6 +25,8 @@ * */ +#error Use auto TLS only + #ifndef WEBSOCKETPP_SOCKET_TLS_HPP #define WEBSOCKETPP_SOCKET_TLS_HPP diff --git a/src/cpp/websocketpp/src/websocketpp.hpp b/src/cpp/websocketpp/src/websocketpp.hpp index 221d66e6f..c6692a9ba 100644 --- a/src/cpp/websocketpp/src/websocketpp.hpp +++ b/src/cpp/websocketpp/src/websocketpp.hpp @@ -41,6 +41,10 @@ namespace websocketpp { typedef websocketpp::endpoint server_tls; #endif + #ifdef WEBSOCKETPP_SOCKET_AUTOTLS_HPP + typedef websocketpp::endpoint server_autotls; + #endif #endif diff --git a/src/cpp/websocketpp/websocketpp.xcodeproj/xcuserdata/jcar.xcuserdatad/xcschemes/WebSocket++ Dynamic Library.xcscheme b/src/cpp/websocketpp/websocketpp.xcodeproj/xcuserdata/jcar.xcuserdatad/xcschemes/WebSocket++ Dynamic Library.xcscheme new file mode 100644 index 000000000..7dfc43ef0 --- /dev/null +++ b/src/cpp/websocketpp/websocketpp.xcodeproj/xcuserdata/jcar.xcuserdatad/xcschemes/WebSocket++ Dynamic Library.xcscheme @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/cpp/websocketpp/websocketpp.xcodeproj/xcuserdata/jcar.xcuserdatad/xcschemes/WebSocket++ Static Library.xcscheme b/src/cpp/websocketpp/websocketpp.xcodeproj/xcuserdata/jcar.xcuserdatad/xcschemes/WebSocket++ Static Library.xcscheme new file mode 100644 index 000000000..a96540790 --- /dev/null +++ b/src/cpp/websocketpp/websocketpp.xcodeproj/xcuserdata/jcar.xcuserdatad/xcschemes/WebSocket++ Static Library.xcscheme @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/cpp/websocketpp/websocketpp.xcodeproj/xcuserdata/jcar.xcuserdatad/xcschemes/broadcast_server.xcscheme b/src/cpp/websocketpp/websocketpp.xcodeproj/xcuserdata/jcar.xcuserdatad/xcschemes/broadcast_server.xcscheme new file mode 100644 index 000000000..63b475212 --- /dev/null +++ b/src/cpp/websocketpp/websocketpp.xcodeproj/xcuserdata/jcar.xcuserdatad/xcschemes/broadcast_server.xcscheme @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/cpp/websocketpp/websocketpp.xcodeproj/xcuserdata/jcar.xcuserdatad/xcschemes/chat_client.xcscheme b/src/cpp/websocketpp/websocketpp.xcodeproj/xcuserdata/jcar.xcuserdatad/xcschemes/chat_client.xcscheme new file mode 100644 index 000000000..cc7c5a1d2 --- /dev/null +++ b/src/cpp/websocketpp/websocketpp.xcodeproj/xcuserdata/jcar.xcuserdatad/xcschemes/chat_client.xcscheme @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/cpp/websocketpp/websocketpp.xcodeproj/xcuserdata/jcar.xcuserdatad/xcschemes/concurrent_server.xcscheme b/src/cpp/websocketpp/websocketpp.xcodeproj/xcuserdata/jcar.xcuserdatad/xcschemes/concurrent_server.xcscheme new file mode 100644 index 000000000..613c1f849 --- /dev/null +++ b/src/cpp/websocketpp/websocketpp.xcodeproj/xcuserdata/jcar.xcuserdatad/xcschemes/concurrent_server.xcscheme @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/cpp/websocketpp/websocketpp.xcodeproj/xcuserdata/jcar.xcuserdatad/xcschemes/echo_client.xcscheme b/src/cpp/websocketpp/websocketpp.xcodeproj/xcuserdata/jcar.xcuserdatad/xcschemes/echo_client.xcscheme new file mode 100644 index 000000000..8c3c3a0da --- /dev/null +++ b/src/cpp/websocketpp/websocketpp.xcodeproj/xcuserdata/jcar.xcuserdatad/xcschemes/echo_client.xcscheme @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/cpp/websocketpp/websocketpp.xcodeproj/xcuserdata/jcar.xcuserdatad/xcschemes/echo_server.xcscheme b/src/cpp/websocketpp/websocketpp.xcodeproj/xcuserdata/jcar.xcuserdatad/xcschemes/echo_server.xcscheme new file mode 100644 index 000000000..71d40c23e --- /dev/null +++ b/src/cpp/websocketpp/websocketpp.xcodeproj/xcuserdata/jcar.xcuserdatad/xcschemes/echo_server.xcscheme @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/cpp/websocketpp/websocketpp.xcodeproj/xcuserdata/jcar.xcuserdatad/xcschemes/echo_server_tls.xcscheme b/src/cpp/websocketpp/websocketpp.xcodeproj/xcuserdata/jcar.xcuserdatad/xcschemes/echo_server_tls.xcscheme new file mode 100644 index 000000000..bb5c6a32e --- /dev/null +++ b/src/cpp/websocketpp/websocketpp.xcodeproj/xcuserdata/jcar.xcuserdatad/xcschemes/echo_server_tls.xcscheme @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/cpp/websocketpp/websocketpp.xcodeproj/xcuserdata/jcar.xcuserdatad/xcschemes/fuzzing_client.xcscheme b/src/cpp/websocketpp/websocketpp.xcodeproj/xcuserdata/jcar.xcuserdatad/xcschemes/fuzzing_client.xcscheme new file mode 100644 index 000000000..5eb718b0c --- /dev/null +++ b/src/cpp/websocketpp/websocketpp.xcodeproj/xcuserdata/jcar.xcuserdatad/xcschemes/fuzzing_client.xcscheme @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/cpp/websocketpp/websocketpp.xcodeproj/xcuserdata/jcar.xcuserdatad/xcschemes/fuzzing_server.xcscheme b/src/cpp/websocketpp/websocketpp.xcodeproj/xcuserdata/jcar.xcuserdatad/xcschemes/fuzzing_server.xcscheme new file mode 100644 index 000000000..946a25ac9 --- /dev/null +++ b/src/cpp/websocketpp/websocketpp.xcodeproj/xcuserdata/jcar.xcuserdatad/xcschemes/fuzzing_server.xcscheme @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/cpp/websocketpp/websocketpp.xcodeproj/xcuserdata/jcar.xcuserdatad/xcschemes/policy_test.xcscheme b/src/cpp/websocketpp/websocketpp.xcodeproj/xcuserdata/jcar.xcuserdatad/xcschemes/policy_test.xcscheme new file mode 100644 index 000000000..e0ead1e66 --- /dev/null +++ b/src/cpp/websocketpp/websocketpp.xcodeproj/xcuserdata/jcar.xcuserdatad/xcschemes/policy_test.xcscheme @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/cpp/websocketpp/websocketpp.xcodeproj/xcuserdata/jcar.xcuserdatad/xcschemes/stress_client.xcscheme b/src/cpp/websocketpp/websocketpp.xcodeproj/xcuserdata/jcar.xcuserdatad/xcschemes/stress_client.xcscheme new file mode 100644 index 000000000..11018e7c9 --- /dev/null +++ b/src/cpp/websocketpp/websocketpp.xcodeproj/xcuserdata/jcar.xcuserdatad/xcschemes/stress_client.xcscheme @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/cpp/websocketpp/websocketpp.xcodeproj/xcuserdata/jcar.xcuserdatad/xcschemes/wsperf.xcscheme b/src/cpp/websocketpp/websocketpp.xcodeproj/xcuserdata/jcar.xcuserdatad/xcschemes/wsperf.xcscheme new file mode 100644 index 000000000..60f89c058 --- /dev/null +++ b/src/cpp/websocketpp/websocketpp.xcodeproj/xcuserdata/jcar.xcuserdatad/xcschemes/wsperf.xcscheme @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/cpp/websocketpp/websocketpp.xcodeproj/xcuserdata/jcar.xcuserdatad/xcschemes/xcschememanagement.plist b/src/cpp/websocketpp/websocketpp.xcodeproj/xcuserdata/jcar.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 000000000..2bb5923fe --- /dev/null +++ b/src/cpp/websocketpp/websocketpp.xcodeproj/xcuserdata/jcar.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,142 @@ + + + + + SchemeUserState + + WebSocket++ Dynamic Library.xcscheme + + orderHint + 2 + + WebSocket++ Static Library.xcscheme + + orderHint + 1 + + broadcast_server.xcscheme + + orderHint + 10 + + chat_client.xcscheme + + orderHint + 4 + + concurrent_server.xcscheme + + orderHint + 12 + + echo_client.xcscheme + + orderHint + 5 + + echo_server.xcscheme + + orderHint + 3 + + echo_server_tls.xcscheme + + orderHint + 7 + + fuzzing_client.xcscheme + + orderHint + 9 + + fuzzing_server.xcscheme + + orderHint + 8 + + policy_test.xcscheme + + orderHint + 6 + + stress_client.xcscheme + + orderHint + 11 + + wsperf.xcscheme + + orderHint + 13 + + + SuppressBuildableAutocreation + + B61A51BE14DC271900456432 + + primary + + + B663884A1487D73200DDAE13 + + primary + + + B6732457148FAEEB00FC2B04 + + primary + + + B6732470148FB0FC00FC2B04 + + primary + + + B67324881491A16500FC2B04 + + primary + + + B67324A11491A7F100FC2B04 + + primary + + + B682887C143745F2002BA48B + + primary + + + B6CF181B1437C397009295BE + + primary + + + B6DF1C681434A7A30029A1B1 + + primary + + + B6DF1C711434A8280029A1B1 + + primary + + + B6DF1CD01435ED910029A1B1 + + primary + + + B6E7E7721505532E00394909 + + primary + + + B6FE8D4E14730AE900B32547 + + primary + + + + + diff --git a/src/js/amount.js b/src/js/amount.js index 526fd2827..222b91228 100644 --- a/src/js/amount.js +++ b/src/js/amount.js @@ -1,8 +1,8 @@ // Represent Ripple amounts and currencies. // - Numbers in hex are big-endian. -var sjcl = require('./sjcl/core.js'); -var bn = require('./sjcl/core.js').bn; +var sjcl = require('../../build/sjcl'); +var bn = sjcl.bn; var utils = require('./utils.js'); var jsbn = require('./jsbn.js'); @@ -10,7 +10,8 @@ var BigInteger = jsbn.BigInteger; var nbi = jsbn.nbi; var alphabets = { - 'ripple' : "rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz", + 'ripple' : "rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz", + 'tipple' : "RPShNAF39wBUDnEGHJKLM4pQrsT7VWXYZ2bcdeCg65jkm8ofqi1tuvaxyz", 'bitcoin' : "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" }; @@ -254,12 +255,23 @@ UInt160.json_rewrite = function (j) { }; // Return a new UInt160 from j. -UInt160.from_json = function (j) { +UInt160.from_generic = function (j) { return 'string' === typeof j - ? (new UInt160()).parse_json(j) + ? (new UInt160()).parse_generic(j) : j.clone(); }; +// Return a new UInt160 from j. +UInt160.from_json = function (j) { + if ('string' === typeof j) { + return (new UInt160()).parse_json(j); + } else if (j instanceof UInt160) { + return j.clone(); + } else { + return new UInt160(); + } +}; + UInt160.is_valid = function (j) { return UInt160.from_json(j).is_valid(); }; @@ -284,7 +296,7 @@ UInt160.prototype.is_valid = function () { }; // value = NaN on error. -UInt160.prototype.parse_json = function (j) { +UInt160.prototype.parse_generic = function (j) { // Canonicalize and validate if (exports.config.accounts && j in exports.config.accounts) j = exports.config.accounts[j].account; @@ -328,6 +340,25 @@ UInt160.prototype.parse_json = function (j) { return this; }; +// value = NaN on error. +UInt160.prototype.parse_json = function (j) { + // Canonicalize and validate + if (exports.config.accounts && j in exports.config.accounts) + j = exports.config.accounts[j].account; + + if ('string' !== typeof j) { + this._value = NaN; + } + else if (j[0] === "r") { + this._value = decode_base_check(consts.VER_ACCOUNT_ID, j); + } + else { + this._value = NaN; + } + + return this; +}; + // Convert from internal form. // XXX Json form should allow 0 and 1, C++ doesn't currently allow it. UInt160.prototype.to_json = function () { @@ -523,10 +554,9 @@ Amount.prototype.add = function (v) { result._offset = o1; result._value = v1.add(v2); result._is_negative = result._value.compareTo(BigInteger.ZERO) < 0; - + if (result._is_negative) { result._value = result._value.negate(); - result._is_negative = false; } result._currency = this._currency.clone(); @@ -747,6 +777,7 @@ Amount.prototype.ratio_human = function (denominator) { if (denominator._is_native) { numerator = numerator.clone(); numerator._value = numerator._value.multiply(consts.bi_xns_unit); + numerator.canonicalize(); } return numerator.divide(denominator); @@ -781,6 +812,7 @@ Amount.prototype.product_human = function (factor) { // See also Amount#ratio_human. if (factor._is_native) { product._value = product._value.divide(consts.bi_xns_unit); + product.canonicalize(); } return product; @@ -1025,7 +1057,7 @@ Amount.prototype.parse_value = function (j) { else if ('string' === typeof j) { var i = j.match(/^(-?)(\d+)$/); var d = !i && j.match(/^(-?)(\d+)\.(\d*)$/); - var e = !e && j.match(/^(-?)(\d+)e(\d+)$/); + var e = !e && j.match(/^(-?)(\d+)e(-?\d+)$/); if (e) { // e notation @@ -1156,6 +1188,9 @@ Amount.prototype.to_text = function (allow_nan) { * * @param opts Options for formatter. * @param opts.precision {Number} Max. number of digits after decimal point. + * @param opts.min_precision {Number} Min. number of digits after dec. point. + * @param opts.skip_empty_fraction {Boolean} Don't show fraction if it is zero, + * even if min_precision is set. * @param opts.group_sep {Boolean|String} Whether to show a separator every n * digits, if a string, that value will be used as the separator. Default: "," * @param opts.group_width {Number} How many numbers will be grouped together, @@ -1187,8 +1222,16 @@ Amount.prototype.to_human = function (opts) int_part = int_part.replace(/^0*/, ''); fraction_part = fraction_part.replace(/0*$/, ''); - if ("number" === typeof opts.precision) { - fraction_part = fraction_part.slice(0, opts.precision); + if (fraction_part.length || !opts.skip_empty_fraction) { + if ("number" === typeof opts.precision) { + fraction_part = fraction_part.slice(0, opts.precision); + } + + if ("number" === typeof opts.min_precision) { + while (fraction_part.length < opts.min_precision) { + fraction_part += "0"; + } + } } if (opts.group_sep) { diff --git a/src/js/index.js b/src/js/index.js index 2d6e65f71..563edb9c5 100644 --- a/src/js/index.js +++ b/src/js/index.js @@ -1,4 +1,13 @@ exports.Remote = require('./remote').Remote; exports.Amount = require('./amount').Amount; exports.UInt160 = require('./amount').UInt160; -exports.Seed = require('./amount').Seed; \ No newline at end of file +exports.Seed = require('./amount').Seed; + +// Important: We do not guarantee any specific version of SJCL or for any +// specific features to be included. The version and configuration may change at +// any time without warning. +// +// However, for programs that are tied to a specific version of ripple.js like +// the official client, it makes sense to expose the SJCL instance so we don't +// have to include it twice. +exports.sjcl = require('../../build/sjcl'); diff --git a/src/js/remote.js b/src/js/remote.js index 97ae40939..c1e45e03c 100644 --- a/src/js/remote.js +++ b/src/js/remote.js @@ -15,12 +15,11 @@ // // npm -var WebSocket = require('ws'); - var EventEmitter = require('events').EventEmitter; -var Amount = require('./amount.js').Amount; -var Currency = require('./amount.js').Currency; -var UInt160 = require('./amount.js').UInt160; +var Amount = require('./amount').Amount; +var Currency = require('./amount').Currency; +var UInt160 = require('./amount').UInt160; +var Transaction = require('./transaction').Transaction; var utils = require('./utils'); @@ -196,16 +195,26 @@ var Remote = function (opts, trace) { this.local_fee = opts.local_fee; // Locally set fees this.id = 0; this.trace = opts.trace || trace; + this._server_fatal = false; // True, if we know server exited. this._ledger_current_index = undefined; this._ledger_hash = undefined; this._ledger_time = undefined; - this.stand_alone = undefined; + this._stand_alone = undefined; + this._testnet = undefined; this.online_target = false; this.online_state = 'closed'; // 'open', 'closed', 'connecting', 'closing' this.state = 'offline'; // 'online', 'offline' this.retry_timer = undefined; this.retry = undefined; + this._load_base = 256; + this._load_fee = 256; + this._fee_ref = undefined; + this._fee_base = undefined; + this._reserve_base = undefined; + this._reserve_inc = undefined; + this._server_status = undefined; + // Cache information for accounts. this.accounts = { // Consider sequence numbers stable if you know you're not generating bad transactions. @@ -261,24 +270,21 @@ var isTefFailure = function (engine_result_code) { return (engine_result_code >= -299 && engine_result_code < 199); }; -Remote.flags = { - 'OfferCreate' : { - 'Passive' : 0x00010000, - }, +/** + * Server states that we will treat as the server being online. + * + * Our requirements are that the server can process transactions and notify + * us of changes. + */ +Remote.online_states = [ + 'proposing', + 'validating', + 'full' +]; - 'Payment' : { - 'PaymentLegacy' : 0x00010000, - 'PartialPayment' : 0x00020000, - 'LimitQuality' : 0x00040000, - 'NoRippleDirect' : 0x00080000, - }, -}; - -// XXX This needs to be determined from the network. -Remote.fees = { - 'default' : Amount.from_json("10"), - 'nickname_create' : Amount.from_json("1000"), - 'offer' : Amount.from_json("10"), +// Inform remote that the remote server is not comming back. +Remote.prototype.server_fatal = function () { + this._server_fatal = true; }; // Set the emitted state: 'online' or 'offline' @@ -370,7 +376,12 @@ Remote.prototype._connect_retry = function () { this.retry_timer = setTimeout(function () { if (self.trace) console.log("remote: retry"); - if (self.online_target) { + if (self._server_fatal) { + // Stop trying to connect. + // nothing(); + console.log("FATAL"); + } + else if (self.online_target) { self._connect_start(); } else { @@ -396,6 +407,7 @@ Remote.prototype._connect_start = function () { if (this.trace) console.log("remote: connect: %s", url); + var WebSocket = require('ws'); var ws = this.ws = new WebSocket(url); ws.response = {}; @@ -499,10 +511,22 @@ Remote.prototype._connect_message = function (ws, json) { this.emit('ledger_closed', message); break; + case 'account': + // XXX If not trusted, need proof. + + this.emit('account', message); + break; + + case 'transaction': + // XXX If not trusted, need proof. + + this.emit('transaction', message); + break; + case 'serverStatus': // This message is only received when online. As we are connected, it is the definative final state. this._set_state( - message.server_status === 'tracking' || message.server_status === 'full' + Remote.online_states.indexOf(message.server_status) !== -1 ? 'online' : 'offline'); break; @@ -679,6 +703,7 @@ Remote.prototype.request_ledger_entry = function (type) { return request; }; +// .accounts(accounts, realtime) Remote.prototype.request_subscribe = function (streams) { var request = new Request(this, 'subscribe'); @@ -692,6 +717,7 @@ Remote.prototype.request_subscribe = function (streams) { return request; }; +// .accounts(accounts, realtime) Remote.prototype.request_unsubscribe = function (streams) { var request = new Request(this, 'unsubscribe'); @@ -714,6 +740,14 @@ Remote.prototype.request_transaction_entry = function (hash, current) { .tx_hash(hash); }; +Remote.prototype.request_account_info = function (accountID) { + var request = new Request(this, 'account_info'); + + request.message.ident = UInt160.json_rewrite(accountID); + + return request; +}; + // --> account_index: sub_account index (optional) // --> current: true, for the current ledger. Remote.prototype.request_account_lines = function (accountID, account_index, current) { @@ -852,7 +886,8 @@ Remote.prototype._server_subscribe = function () { this.request_subscribe([ 'ledger', 'server' ]) .on('success', function (message) { - self.stand_alone = !!message.stand_alone; + self._stand_alone = !!message.stand_alone; + self._testnet = !!message.testnet; if (message.random) self.emit('random', utils.hexToArray(message.random)); @@ -865,7 +900,16 @@ Remote.prototype._server_subscribe = function () { self.emit('ledger_closed', message); } - if (message.server_status === 'tracking' || message.server_status === 'full') { + // FIXME Use this to estimate fee. + self._load_base = message.load_base || 256; + self._load_fee = message.load_fee || 256; + self._fee_ref = message.fee_ref; + self._fee_base = message.fee_base; + self._reserve_base = message.reverse_base; + self._reserve_inc = message.reserve_inc; + self._server_status = message.server_status; + + if (Remote.online_states.indexOf(message.server_status) !== -1) { self._set_state('online'); } @@ -883,7 +927,7 @@ Remote.prototype._server_subscribe = function () { // A good way to be notified of the result of this is: // remote.once('ledger_closed', function (ledger_closed, ledger_index) { ... } ); Remote.prototype.ledger_accept = function () { - if (this.stand_alone || undefined === this.stand_alone) + if (this._stand_alone || undefined === this._stand_alone) { var request = new Request(this, 'ledger_accept'); @@ -1111,481 +1155,6 @@ Remote.prototype.transaction = function () { return new Transaction(this); }; -// -// Transactions -// -// Construction: -// remote.transaction() // Build a transaction object. -// .offer_create(...) // Set major parameters. -// .set_flags() // Set optional parameters. -// .on() // Register for events. -// .submit(); // Send to network. -// -// Events: -// 'success' : Transaction submitted without error. -// 'error' : Error submitting transaction. -// 'proposed' : Advisory proposed status transaction. -// - A client should expect 0 to multiple results. -// - Might not get back. The remote might just forward the transaction. -// - A success could be reverted in final. -// - local error: other remotes might like it. -// - malformed error: local server thought it was malformed. -// - The client should only trust this when talking to a trusted server. -// 'final' : Final status of transaction. -// - Only expect a final from dishonest servers after a tesSUCCESS or ter*. -// 'lost' : Gave up looking for on ledger_closed. -// 'pending' : Transaction was not found on ledger_closed. -// 'state' : Follow the state of a transaction. -// 'client_submitted' - Sent to remote -// |- 'remoteError' - Remote rejected transaction. -// \- 'client_proposed' - Remote provisionally accepted transaction. -// |- 'client_missing' - Transaction has not appeared in ledger as expected. -// | |\- 'client_lost' - No longer monitoring missing transaction. -// |/ -// |- 'tesSUCCESS' - Transaction in ledger as expected. -// |- 'ter...' - Transaction failed. -// \- 'tep...' - Transaction partially succeeded. -// -// Notes: -// - All transactions including those with local and malformed errors may be -// forwarded anyway. -// - A malicous server can: -// - give any proposed result. -// - it may declare something correct as incorrect or something correct as incorrect. -// - it may not communicate with the rest of the network. -// - may or may not forward. -// - -var SUBMIT_MISSING = 4; // Report missing. -var SUBMIT_LOST = 8; // Give up tracking. - -// A class to implement transactions. -// - Collects parameters -// - Allow event listeners to be attached to determine the outcome. -var Transaction = function (remote) { - // YYY Make private as many variables as possible. - var self = this; - - this.callback = undefined; - this.remote = remote; - this._secret = undefined; - this._build_path = false; - this.tx_json = { // Transaction data. - 'Flags' : 0, // XXX Would be nice if server did not require this. - }; - this.hash = undefined; - this.submit_index = undefined; // ledger_current_index was this when transaction was submited. - this.state = undefined; // Under construction. - - this.on('success', function (message) { - if (message.engine_result) { - self.hash = message.tx_json.hash; - - self.set_state('client_proposed'); - - self.emit('proposed', { - 'tx_json' : message.tx_json, - 'result' : message.engine_result, - 'result_code' : message.engine_result_code, - 'result_message' : message.engine_result_message, - 'rejected' : self.isRejected(message.engine_result_code), // If server is honest, don't expect a final if rejected. - }); - } - }); - - this.on('error', function (message) { - // Might want to give more detailed information. - self.set_state('remoteError'); - }); -}; - -Transaction.prototype = new EventEmitter; - -Transaction.prototype.consts = { - 'telLOCAL_ERROR' : -399, - 'temMALFORMED' : -299, - 'tefFAILURE' : -199, - 'terRETRY' : -99, - 'tesSUCCESS' : 0, - 'tepPARTIAL' : 100, -}; - -Transaction.prototype.isTelLocal = function (ter) { - return ter >= this.consts.telLOCAL_ERROR && ter < this.consts.temMALFORMED; -}; - -Transaction.prototype.isTemMalformed = function (ter) { - return ter >= this.consts.temMALFORMED && ter < this.consts.tefFAILURE; -}; - -Transaction.prototype.isTefFailure = function (ter) { - return ter >= this.consts.tefFAILURE && ter < this.consts.terRETRY; -}; - -Transaction.prototype.isTerRetry = function (ter) { - return ter >= this.consts.terRETRY && ter < this.consts.tesSUCCESS; -}; - -Transaction.prototype.isTepSuccess = function (ter) { - return ter >= this.consts.tesSUCCESS; -}; - -Transaction.prototype.isTepPartial = function (ter) { - return ter >= this.consts.tepPATH_PARTIAL; -}; - -Transaction.prototype.isRejected = function (ter) { - return this.isTelLocal(ter) || this.isTemMalformed(ter) || this.isTefFailure(ter); -}; - -Transaction.prototype.set_state = function (state) { - if (this.state !== state) { - this.state = state; - this.emit('state', state); - } -}; - -// Submit a transaction to the network. -// XXX Don't allow a submit without knowing ledger_index. -// XXX Have a network canSubmit(), post events for following. -// XXX Also give broader status for tracking through network disconnects. -// callback = function (status, info) { -// // status is final status. Only works under a ledger_accepting conditions. -// switch status: -// case 'tesSUCCESS': all is well. -// case 'tejServerUntrusted': sending secret to untrusted server. -// case 'tejInvalidAccount': locally detected error. -// case 'tejLost': locally gave up looking -// default: some other TER -// } -Transaction.prototype.submit = function (callback) { - var self = this; - var tx_json = this.tx_json; - - this.callback = callback; - - if ('string' !== typeof tx_json.Account) - { - (this.callback || this.emit)('error', { - 'error' : 'tejInvalidAccount', - 'error_message' : 'Bad account.' - }); - return; - } - - // YYY Might check paths for invalid accounts. - - if (this.remote.local_fee && undefined === tx_json.Fee) { - tx_json.Fee = Remote.fees['default'].to_json(); - } - - if (this.callback || this.listeners('final').length || this.listeners('lost').length || this.listeners('pending').length) { - // There are listeners for callback, 'final', 'lost', or 'pending' arrange to emit them. - - this.submit_index = this.remote._ledger_current_index; - - // When a ledger closes, look for the result. - var on_ledger_closed = function (message) { - var ledger_hash = message.ledger_hash; - var ledger_index = message.ledger_index; - var stop = false; - -// XXX make sure self.hash is available. - self.remote.request_transaction_entry(self.hash) - .ledger_hash(ledger_hash) - .on('success', function (message) { - self.set_state(message.metadata.TransactionResult); - self.emit('final', message); - - if (self.callback) - self.callback(message.metadata.TransactionResult, message); - - stop = true; - }) - .on('error', function (message) { - if ('remoteError' === message.error - && 'transactionNotFound' === message.remote.error) { - if (self.submit_index + SUBMIT_LOST < ledger_index) { - self.set_state('client_lost'); // Gave up. - self.emit('lost'); - - if (self.callback) - self.callback('tejLost', message); - - stop = true; - } - else if (self.submit_index + SUBMIT_MISSING < ledger_index) { - self.set_state('client_missing'); // We don't know what happened to transaction, still might find. - self.emit('pending'); - } - else { - self.emit('pending'); - } - } - // XXX Could log other unexpectedness. - }) - .request(); - - if (stop) { - self.remote.removeListener('ledger_closed', on_ledger_closed); - self.emit('final', message); - } - }; - - this.remote.on('ledger_closed', on_ledger_closed); - - if (this.callback) { - this.on('error', function (message) { - self.callback(message.error, message); - }); - } - } - - this.set_state('client_submitted'); - - this.remote.submit(this); - - return this; -} - -// -// Set options for Transactions -// - -// --> build: true, to have server blindly construct a path. -// -// "blindly" because the sender has no idea of the actual cost except that is must be less than send max. -Transaction.prototype.build_path = function (build) { - this._build_path = build; - - return this; -} - -Transaction._path_rewrite = function (path) { - var path_new = []; - - for (var index in path) { - var node = path[index]; - var node_new = {}; - - if ('account' in node) - node_new.account = UInt160.json_rewrite(node.account); - - if ('issuer' in node) - node_new.issuer = UInt160.json_rewrite(node.issuer); - - if ('currency' in node) - node_new.currency = Currency.json_rewrite(node.currency); - - path_new.push(node_new); - } - - return path_new; -} - -Transaction.prototype.path_add = function (path) { - this.tx_json.Paths = this.tx_json.Paths || [] - this.tx_json.Paths.push(Transaction._path_rewrite(path)); - - return this; -} - -// --> paths: undefined or array of path -// A path is an array of objects containing some combination of: account, currency, issuer -Transaction.prototype.paths = function (paths) { - for (var index in paths) { - this.path_add(paths[index]); - } - - return this; -} - -// If the secret is in the config object, it does not need to be provided. -Transaction.prototype.secret = function (secret) { - this._secret = secret; -} - -Transaction.prototype.send_max = function (send_max) { - if (send_max) - this.tx_json.SendMax = Amount.json_rewrite(send_max); - - return this; -} - -// --> rate: In billionths. -Transaction.prototype.transfer_rate = function (rate) { - this.tx_json.TransferRate = Number(rate); - - if (this.tx_json.TransferRate < 1e9) - throw 'invalidTransferRate'; - - return this; -} - -// Add flags to a transaction. -// --> flags: undefined, _flag_, or [ _flags_ ] -Transaction.prototype.set_flags = function (flags) { - if (flags) { - var transaction_flags = Remote.flags[this.tx_json.TransactionType]; - - if (undefined == this.tx_json.Flags) // We plan to not define this field on new Transaction. - this.tx_json.Flags = 0; - - var flag_set = 'object' === typeof flags ? flags : [ flags ]; - - for (index in flag_set) { - var flag = flag_set[index]; - - if (flag in transaction_flags) - { - this.tx_json.Flags += transaction_flags[flag]; - } - else { - // XXX Immediately report an error or mark it. - } - } - } - - return this; -} - -// -// Transactions -// - -Transaction.prototype._account_secret = function (account) { - // Fill in secret from remote, if available. - return this.remote.secrets[account]; -}; - -// Options: -// .domain() NYI -// .message_key() NYI -// .transfer_rate() -// .wallet_locator() NYI -// .wallet_size() NYI -Transaction.prototype.account_set = function (src) { - this._secret = this._account_secret(src); - this.tx_json.TransactionType = 'AccountSet'; - this.tx_json.Account = UInt160.json_rewrite(src); - - return this; -}; - -Transaction.prototype.claim = function (src, generator, public_key, signature) { - this._secret = this._account_secret(src); - this.tx_json.TransactionType = 'Claim'; - this.tx_json.Generator = generator; - this.tx_json.PublicKey = public_key; - this.tx_json.Signature = signature; - - return this; -}; - -Transaction.prototype.offer_cancel = function (src, sequence) { - this._secret = this._account_secret(src); - this.tx_json.TransactionType = 'OfferCancel'; - this.tx_json.Account = UInt160.json_rewrite(src); - this.tx_json.OfferSequence = Number(sequence); - - return this; -}; - -// --> expiration : Date or Number -Transaction.prototype.offer_create = function (src, taker_pays, taker_gets, expiration) { - this._secret = this._account_secret(src); - this.tx_json.TransactionType = 'OfferCreate'; - this.tx_json.Account = UInt160.json_rewrite(src); - this.tx_json.TakerPays = Amount.json_rewrite(taker_pays); - this.tx_json.TakerGets = Amount.json_rewrite(taker_gets); - - if (this.remote.local_fee) { - this.tx_json.Fee = Remote.fees.offer.to_json(); - } - - if (expiration) - this.tx_json.Expiration = Date === expiration.constructor - ? expiration.getTime() - : Number(expiration); - - return this; -}; - -Transaction.prototype.password_fund = function (src, dst) { - this._secret = this._account_secret(src); - this.tx_json.TransactionType = 'PasswordFund'; - this.tx_json.Destination = UInt160.json_rewrite(dst); - - return this; -} - -Transaction.prototype.password_set = function (src, authorized_key, generator, public_key, signature) { - this._secret = this._account_secret(src); - this.tx_json.TransactionType = 'PasswordSet'; - this.tx_json.RegularKey = authorized_key; - this.tx_json.Generator = generator; - this.tx_json.PublicKey = public_key; - this.tx_json.Signature = signature; - - return this; -} - -// Construct a 'payment' transaction. -// -// When a transaction is submitted: -// - If the connection is reliable and the server is not merely forwarding and is not malicious, -// --> src : UInt160 or String -// --> dst : UInt160 or String -// --> deliver_amount : Amount or String. -// -// Options: -// .paths() -// .build_path() -// .path_add() -// .secret() -// .send_max() -// .set_flags() -Transaction.prototype.payment = function (src, dst, deliver_amount) { - this._secret = this._account_secret(src); - this.tx_json.TransactionType = 'Payment'; - this.tx_json.Account = UInt160.json_rewrite(src); - this.tx_json.Amount = Amount.json_rewrite(deliver_amount); - this.tx_json.Destination = UInt160.json_rewrite(dst); - - return this; -} - -Transaction.prototype.ripple_line_set = function (src, limit, quality_in, quality_out) { - this._secret = this._account_secret(src); - this.tx_json.TransactionType = 'TrustSet'; - this.tx_json.Account = UInt160.json_rewrite(src); - - // Allow limit of 0 through. - if (undefined !== limit) - this.tx_json.LimitAmount = Amount.json_rewrite(limit); - - if (quality_in) - this.tx_json.QualityIn = quality_in; - - if (quality_out) - this.tx_json.QualityOut = quality_out; - - // XXX Throw an error if nothing is set. - - return this; -}; - -Transaction.prototype.wallet_add = function (src, amount, authorized_key, public_key, signature) { - this._secret = this._account_secret(src); - this.tx_json.TransactionType = 'WalletAdd'; - this.tx_json.Amount = Amount.json_rewrite(amount); - this.tx_json.RegularKey = authorized_key; - this.tx_json.PublicKey = public_key; - this.tx_json.Signature = signature; - - return this; -}; - exports.config = {}; exports.Remote = Remote; diff --git a/src/js/transaction.js b/src/js/transaction.js new file mode 100644 index 000000000..3fedb3dca --- /dev/null +++ b/src/js/transaction.js @@ -0,0 +1,522 @@ +// +// Transactions +// +// Construction: +// remote.transaction() // Build a transaction object. +// .offer_create(...) // Set major parameters. +// .set_flags() // Set optional parameters. +// .on() // Register for events. +// .submit(); // Send to network. +// +// Events: +// 'success' : Transaction submitted without error. +// 'error' : Error submitting transaction. +// 'proposed' : Advisory proposed status transaction. +// - A client should expect 0 to multiple results. +// - Might not get back. The remote might just forward the transaction. +// - A success could be reverted in final. +// - local error: other remotes might like it. +// - malformed error: local server thought it was malformed. +// - The client should only trust this when talking to a trusted server. +// 'final' : Final status of transaction. +// - Only expect a final from dishonest servers after a tesSUCCESS or ter*. +// 'lost' : Gave up looking for on ledger_closed. +// 'pending' : Transaction was not found on ledger_closed. +// 'state' : Follow the state of a transaction. +// 'client_submitted' - Sent to remote +// |- 'remoteError' - Remote rejected transaction. +// \- 'client_proposed' - Remote provisionally accepted transaction. +// |- 'client_missing' - Transaction has not appeared in ledger as expected. +// | |\- 'client_lost' - No longer monitoring missing transaction. +// |/ +// |- 'tesSUCCESS' - Transaction in ledger as expected. +// |- 'ter...' - Transaction failed. +// \- 'tec...' - Transaction claimed fee only. +// +// Notes: +// - All transactions including those with local and malformed errors may be +// forwarded anyway. +// - A malicous server can: +// - give any proposed result. +// - it may declare something correct as incorrect or something correct as incorrect. +// - it may not communicate with the rest of the network. +// - may or may not forward. +// + +var Amount = require('./amount').Amount; +var Currency = require('./amount').Currency; +var UInt160 = require('./amount').UInt160; +var EventEmitter = require('events').EventEmitter; + +var SUBMIT_MISSING = 4; // Report missing. +var SUBMIT_LOST = 8; // Give up tracking. + +// A class to implement transactions. +// - Collects parameters +// - Allow event listeners to be attached to determine the outcome. +var Transaction = function (remote) { + // YYY Make private as many variables as possible. + var self = this; + + this.callback = undefined; + this.remote = remote; + this._secret = undefined; + this._build_path = false; + this.tx_json = { // Transaction data. + 'Flags' : 0, // XXX Would be nice if server did not require this. + }; + this.hash = undefined; + this.submit_index = undefined; // ledger_current_index was this when transaction was submited. + this.state = undefined; // Under construction. + + this.on('success', function (message) { + if (message.engine_result) { + self.hash = message.tx_json.hash; + + self.set_state('client_proposed'); + + self.emit('proposed', { + 'tx_json' : message.tx_json, + 'result' : message.engine_result, + 'result_code' : message.engine_result_code, + 'result_message' : message.engine_result_message, + 'rejected' : self.isRejected(message.engine_result_code), // If server is honest, don't expect a final if rejected. + }); + } + }); + + this.on('error', function (message) { + // Might want to give more detailed information. + self.set_state('remoteError'); + }); +}; + +Transaction.prototype = new EventEmitter; + +// XXX This needs to be determined from the network. +Transaction.fees = { + 'default' : Amount.from_json("10"), + 'nickname_create' : Amount.from_json("1000"), + 'offer' : Amount.from_json("10"), +}; + +Transaction.flags = { + 'OfferCreate' : { + 'Passive' : 0x00010000, + }, + + 'Payment' : { + 'NoRippleDirect' : 0x00010000, + 'PartialPayment' : 0x00020000, + 'LimitQuality' : 0x00040000, + }, +}; + +Transaction.prototype.consts = { + 'telLOCAL_ERROR' : -399, + 'temMALFORMED' : -299, + 'tefFAILURE' : -199, + 'terRETRY' : -99, + 'tesSUCCESS' : 0, + 'tecCLAIMED' : 100, +}; + +Transaction.prototype.isTelLocal = function (ter) { + return ter >= this.consts.telLOCAL_ERROR && ter < this.consts.temMALFORMED; +}; + +Transaction.prototype.isTemMalformed = function (ter) { + return ter >= this.consts.temMALFORMED && ter < this.consts.tefFAILURE; +}; + +Transaction.prototype.isTefFailure = function (ter) { + return ter >= this.consts.tefFAILURE && ter < this.consts.terRETRY; +}; + +Transaction.prototype.isTerRetry = function (ter) { + return ter >= this.consts.terRETRY && ter < this.consts.tesSUCCESS; +}; + +Transaction.prototype.isTepSuccess = function (ter) { + return ter >= this.consts.tesSUCCESS; +}; + +Transaction.prototype.isTecClaimed = function (ter) { + return ter >= this.consts.tecCLAIMED; +}; + +Transaction.prototype.isRejected = function (ter) { + return this.isTelLocal(ter) || this.isTemMalformed(ter) || this.isTefFailure(ter); +}; + +Transaction.prototype.set_state = function (state) { + if (this.state !== state) { + this.state = state; + this.emit('state', state); + } +}; + +// Submit a transaction to the network. +// XXX Don't allow a submit without knowing ledger_index. +// XXX Have a network canSubmit(), post events for following. +// XXX Also give broader status for tracking through network disconnects. +// callback = function (status, info) { +// // status is final status. Only works under a ledger_accepting conditions. +// switch status: +// case 'tesSUCCESS': all is well. +// case 'tejServerUntrusted': sending secret to untrusted server. +// case 'tejInvalidAccount': locally detected error. +// case 'tejLost': locally gave up looking +// default: some other TER +// } +Transaction.prototype.submit = function (callback) { + var self = this; + var tx_json = this.tx_json; + + this.callback = callback; + + if ('string' !== typeof tx_json.Account) + { + (this.callback || this.emit)('error', { + 'error' : 'tejInvalidAccount', + 'error_message' : 'Bad account.' + }); + return; + } + + // YYY Might check paths for invalid accounts. + + if (this.remote.local_fee && undefined === tx_json.Fee) { + tx_json.Fee = Transaction.fees['default'].to_json(); + } + + if (this.callback || this.listeners('final').length || this.listeners('lost').length || this.listeners('pending').length) { + // There are listeners for callback, 'final', 'lost', or 'pending' arrange to emit them. + + this.submit_index = this.remote._ledger_current_index; + + // When a ledger closes, look for the result. + var on_ledger_closed = function (message) { + var ledger_hash = message.ledger_hash; + var ledger_index = message.ledger_index; + var stop = false; + +// XXX make sure self.hash is available. + self.remote.request_transaction_entry(self.hash) + .ledger_hash(ledger_hash) + .on('success', function (message) { + self.set_state(message.metadata.TransactionResult); + self.emit('final', message); + + if (self.callback) + self.callback(message.metadata.TransactionResult, message); + + stop = true; + }) + .on('error', function (message) { + if ('remoteError' === message.error + && 'transactionNotFound' === message.remote.error) { + if (self.submit_index + SUBMIT_LOST < ledger_index) { + self.set_state('client_lost'); // Gave up. + self.emit('lost'); + + if (self.callback) + self.callback('tejLost', message); + + stop = true; + } + else if (self.submit_index + SUBMIT_MISSING < ledger_index) { + self.set_state('client_missing'); // We don't know what happened to transaction, still might find. + self.emit('pending'); + } + else { + self.emit('pending'); + } + } + // XXX Could log other unexpectedness. + }) + .request(); + + if (stop) { + self.remote.removeListener('ledger_closed', on_ledger_closed); + self.emit('final', message); + } + }; + + this.remote.on('ledger_closed', on_ledger_closed); + + if (this.callback) { + this.on('error', function (message) { + self.callback(message.error, message); + }); + } + } + + this.set_state('client_submitted'); + + this.remote.submit(this); + + return this; +} + +// +// Set options for Transactions +// + +// --> build: true, to have server blindly construct a path. +// +// "blindly" because the sender has no idea of the actual cost except that is must be less than send max. +Transaction.prototype.build_path = function (build) { + this._build_path = build; + + return this; +} + +// tag should be undefined or a 32 bit integer. +// YYY Add range checking for tag. +Transaction.prototype.destination_tag = function (tag) { + if (undefined !== tag) + this.tx_json.DestinationTag = tag; + + return this; +} + +Transaction._path_rewrite = function (path) { + var path_new = []; + + for (var index in path) { + var node = path[index]; + var node_new = {}; + + if ('account' in node) + node_new.account = UInt160.json_rewrite(node.account); + + if ('issuer' in node) + node_new.issuer = UInt160.json_rewrite(node.issuer); + + if ('currency' in node) + node_new.currency = Currency.json_rewrite(node.currency); + + path_new.push(node_new); + } + + return path_new; +} + +Transaction.prototype.path_add = function (path) { + this.tx_json.Paths = this.tx_json.Paths || [] + this.tx_json.Paths.push(Transaction._path_rewrite(path)); + + return this; +} + +// --> paths: undefined or array of path +// A path is an array of objects containing some combination of: account, currency, issuer +Transaction.prototype.paths = function (paths) { + for (var index in paths) { + this.path_add(paths[index]); + } + + return this; +} + +// If the secret is in the config object, it does not need to be provided. +Transaction.prototype.secret = function (secret) { + this._secret = secret; +} + +Transaction.prototype.send_max = function (send_max) { + if (send_max) + this.tx_json.SendMax = Amount.json_rewrite(send_max); + + return this; +} + +// tag should be undefined or a 32 bit integer. +// YYY Add range checking for tag. +Transaction.prototype.source_tag = function (tag) { + if (undefined !== tag) + this.tx_json.SourceTag = tag; + + return this; +} + +// --> rate: In billionths. +Transaction.prototype.transfer_rate = function (rate) { + this.tx_json.TransferRate = Number(rate); + + if (this.tx_json.TransferRate < 1e9) + throw 'invalidTransferRate'; + + return this; +} + +// Add flags to a transaction. +// --> flags: undefined, _flag_, or [ _flags_ ] +Transaction.prototype.set_flags = function (flags) { + if (flags) { + var transaction_flags = Transaction.flags[this.tx_json.TransactionType]; + + if (undefined == this.tx_json.Flags) // We plan to not define this field on new Transaction. + this.tx_json.Flags = 0; + + var flag_set = 'object' === typeof flags ? flags : [ flags ]; + + for (index in flag_set) { + var flag = flag_set[index]; + + if (flag in transaction_flags) + { + this.tx_json.Flags += transaction_flags[flag]; + } + else { + // XXX Immediately report an error or mark it. + } + } + } + + return this; +} + +// +// Transactions +// + +Transaction.prototype._account_secret = function (account) { + // Fill in secret from remote, if available. + return this.remote.secrets[account]; +}; + +// Options: +// .domain() NYI +// .message_key() NYI +// .transfer_rate() +// .wallet_locator() NYI +// .wallet_size() NYI +Transaction.prototype.account_set = function (src) { + this._secret = this._account_secret(src); + this.tx_json.TransactionType = 'AccountSet'; + this.tx_json.Account = UInt160.json_rewrite(src); + + return this; +}; + +Transaction.prototype.claim = function (src, generator, public_key, signature) { + this._secret = this._account_secret(src); + this.tx_json.TransactionType = 'Claim'; + this.tx_json.Generator = generator; + this.tx_json.PublicKey = public_key; + this.tx_json.Signature = signature; + + return this; +}; + +Transaction.prototype.offer_cancel = function (src, sequence) { + this._secret = this._account_secret(src); + this.tx_json.TransactionType = 'OfferCancel'; + this.tx_json.Account = UInt160.json_rewrite(src); + this.tx_json.OfferSequence = Number(sequence); + + return this; +}; + +// --> expiration : Date or Number +Transaction.prototype.offer_create = function (src, taker_pays, taker_gets, expiration) { + this._secret = this._account_secret(src); + this.tx_json.TransactionType = 'OfferCreate'; + this.tx_json.Account = UInt160.json_rewrite(src); + this.tx_json.TakerPays = Amount.json_rewrite(taker_pays); + this.tx_json.TakerGets = Amount.json_rewrite(taker_gets); + + if (this.remote.local_fee) { + this.tx_json.Fee = Transaction.fees.offer.to_json(); + } + + if (expiration) + this.tx_json.Expiration = Date === expiration.constructor + ? expiration.getTime() + : Number(expiration); + + return this; +}; + +Transaction.prototype.password_fund = function (src, dst) { + this._secret = this._account_secret(src); + this.tx_json.TransactionType = 'PasswordFund'; + this.tx_json.Destination = UInt160.json_rewrite(dst); + + return this; +} + +Transaction.prototype.password_set = function (src, authorized_key, generator, public_key, signature) { + this._secret = this._account_secret(src); + this.tx_json.TransactionType = 'PasswordSet'; + this.tx_json.RegularKey = authorized_key; + this.tx_json.Generator = generator; + this.tx_json.PublicKey = public_key; + this.tx_json.Signature = signature; + + return this; +} + +// Construct a 'payment' transaction. +// +// When a transaction is submitted: +// - If the connection is reliable and the server is not merely forwarding and is not malicious, +// --> src : UInt160 or String +// --> dst : UInt160 or String +// --> deliver_amount : Amount or String. +// +// Options: +// .paths() +// .build_path() +// .destination_tag() +// .path_add() +// .secret() +// .send_max() +// .set_flags() +// .source_tag() +Transaction.prototype.payment = function (src, dst, deliver_amount) { + this._secret = this._account_secret(src); + this.tx_json.TransactionType = 'Payment'; + this.tx_json.Account = UInt160.json_rewrite(src); + this.tx_json.Amount = Amount.json_rewrite(deliver_amount); + this.tx_json.Destination = UInt160.json_rewrite(dst); + + return this; +} + +Transaction.prototype.ripple_line_set = function (src, limit, quality_in, quality_out) { + this._secret = this._account_secret(src); + this.tx_json.TransactionType = 'TrustSet'; + this.tx_json.Account = UInt160.json_rewrite(src); + + // Allow limit of 0 through. + if (undefined !== limit) + this.tx_json.LimitAmount = Amount.json_rewrite(limit); + + if (quality_in) + this.tx_json.QualityIn = quality_in; + + if (quality_out) + this.tx_json.QualityOut = quality_out; + + // XXX Throw an error if nothing is set. + + return this; +}; + +Transaction.prototype.wallet_add = function (src, amount, authorized_key, public_key, signature) { + this._secret = this._account_secret(src); + this.tx_json.TransactionType = 'WalletAdd'; + this.tx_json.Amount = Amount.json_rewrite(amount); + this.tx_json.RegularKey = authorized_key; + this.tx_json.PublicKey = public_key; + this.tx_json.Signature = signature; + + return this; +}; + +exports.Transaction = Transaction; + +// vim:sw=2:sts=2:ts=8:et diff --git a/src/js/utils.web.js b/src/js/utils.web.js index b98916e17..b3a49e502 100644 --- a/src/js/utils.web.js +++ b/src/js/utils.web.js @@ -1,7 +1,11 @@ -exports = module.exports = require('./utils.js'); +var exports = module.exports = require('./utils.js'); // We override this function for browsers, because they print objects nicer // natively than JSON.stringify can. exports.logObject = function (msg, obj) { - console.log(msg, "", obj); + if (/MSIE/.test(navigator.userAgent)) { + console.log(msg, JSON.stringify(obj)); + } else { + console.log(msg, "", obj); + } }; diff --git a/test/amount-test.js b/test/amount-test.js index 712c2f8b4..71279cbee 100644 --- a/test/amount-test.js +++ b/test/amount-test.js @@ -17,13 +17,13 @@ var config = require('./config.js'); buster.testCase("Amount", { "UInt160" : { "Parse 0" : function () { - buster.assert.equals(nbi(), UInt160.from_json("0")._value); + buster.assert.equals(nbi(), UInt160.from_generic("0")._value); }, "Parse 0 export" : function () { - buster.assert.equals(amount.consts.address_xns, UInt160.from_json("0").to_json()); + buster.assert.equals(amount.consts.address_xns, UInt160.from_generic("0").to_json()); }, "Parse 1" : function () { - buster.assert.equals(new BigInteger([1]), UInt160.from_json("1")._value); + buster.assert.equals(new BigInteger([1]), UInt160.from_generic("1")._value); }, "Parse rrrrrrrrrrrrrrrrrrrrrhoLvTp export" : function () { buster.assert.equals(amount.consts.address_xns, UInt160.from_json("rrrrrrrrrrrrrrrrrrrrrhoLvTp").to_json()); diff --git a/test/config-example.js b/test/config-example.js index c43f4d5d8..1df83b72f 100644 --- a/test/config-example.js +++ b/test/config-example.js @@ -12,7 +12,11 @@ exports.rippled = path.resolve("build/rippled"); exports.server_default = "alpha"; +// // Configuration for servers. +// +// For testing, you might choose to target a persistent server at alternate ports. +// exports.servers = { // A local test server. "alpha" : { @@ -31,4 +35,12 @@ exports.servers = { } }; +exports.http_servers = { + // A local test server + "zed" : { + "ip" : "127.0.0.1", + "port" : 8088, + } +}; + // vim:sw=2:sts=2:ts=8:et diff --git a/test/jsonrpc-test.js b/test/jsonrpc-test.js new file mode 100644 index 000000000..ffad70ba6 --- /dev/null +++ b/test/jsonrpc-test.js @@ -0,0 +1,191 @@ +var async = require("async"); +var buster = require("buster"); +var http = require("http"); +var jsonrpc = require("simple-jsonrpc"); +var EventEmitter = require('events').EventEmitter; + +var Amount = require("../src/js/amount.js").Amount; +var Remote = require("../src/js/remote.js").Remote; +var Server = require("./server.js").Server; + +var testutils = require("./testutils.js"); + +var config = require("./config.js"); + +require("../src/js/amount.js").config = require("./config.js"); +require("../src/js/remote.js").config = require("./config.js"); + +// How long to wait for server to start. +var serverDelay = 1500; + +buster.testRunner.timeout = 5000; + +var server; +var server_events; + +var build_setup = function (options) { + var setup = testutils.build_setup(options); + + return function (done) { + var self = this; + + var http_config = config.http_servers["zed"]; + + server_events = new EventEmitter; + server = http.createServer(function (req, res) { + // console.log("REQUEST"); + var input = ""; + + req.setEncoding(); + + req.on('data', function (buffer) { + // console.log("DATA: %s", buffer); + + input = input + buffer; + }); + + req.on('end', function () { + // console.log("END"); + var request = JSON.parse(input); + + // console.log("REQ: %s", JSON.stringify(request, undefined, 2)); + + server_events.emit('request', request, res); + }); + + req.on('close', function () { + // console.log("CLOSE"); + }); + }); + + server.listen(http_config.port, http_config.ip, undefined, + function () { + // console.log("server up: %s %d", http_config.ip, http_config.port); + + setup.call(self, done); + }); + }; +}; + +var build_teardown = function () { + var teardown = testutils.build_teardown(); + + return function (done) { + var self = this; + + server.close(function () { + // console.log("server closed"); + + teardown.call(self, done); + }); + }; +}; + +buster.testCase("JSON-RPC", { + setUp : build_setup(), + // setUp : build_setup({ verbose: true }), + // setUp : build_setup({verbose: true , no_server: true}), + tearDown : build_teardown(), + + "server_info" : + function (done) { + var rippled_config = config.servers.alpha; + var client = jsonrpc.client("http://" + rippled_config.rpc_ip + ":" + rippled_config.rpc_port); + + client.call('server_info', [], function (result) { + // console.log(JSON.stringify(result, undefined, 2)); + buster.assert('info' in result); + + done(); + }); + }, + + "subscribe server" : + function (done) { + var rippled_config = config.servers.alpha; + var client = jsonrpc.client("http://" + rippled_config.rpc_ip + ":" + rippled_config.rpc_port); + var http_config = config.http_servers["zed"]; + + client.call('subscribe', [{ + 'url' : "http://" + http_config.ip + ":" + http_config.port, + 'streams' : [ 'server' ], + }], function (result) { + // console.log(JSON.stringify(result, undefined, 2)); + + buster.assert('random' in result); + + done(); + }); + }, + + "subscribe ledger" : + function (done) { + var self = this; + + var rippled_config = config.servers.alpha; + var client = jsonrpc.client("http://" + rippled_config.rpc_ip + ":" + rippled_config.rpc_port); + var http_config = config.http_servers["zed"]; + + async.waterfall([ + function (callback) { + self.what = "Subscribe."; + + client.call('subscribe', [{ + 'url' : "http://" + http_config.ip + ":" + http_config.port, + 'streams' : [ 'ledger' ], + }], function (result) { + //console.log(JSON.stringify(result, undefined, 2)); + + buster.assert('ledger_index' in result); + + callback(); + }); + }, + function (callback) { + self.what = "Accept a ledger."; + + server_events.once('request', function (request, response) { + // console.log("GOT: %s", JSON.stringify(request, undefined, 2)); + + buster.assert.equals(1, request.params.seq); + buster.assert.equals(3, request.params.ledger_index); + + response.statusCode = 200; + response.end(JSON.stringify({ + jsonrpc: "2.0", + result: {}, + id: request.id + })); + + callback(); + }); + + self.remote.ledger_accept(); + }, + function (callback) { + self.what = "Accept another ledger."; + + server_events.once('request', function (request, response) { + // console.log("GOT: %s", JSON.stringify(request, undefined, 2)); + + buster.assert.equals(2, request.params.seq); + buster.assert.equals(4, request.params.ledger_index); + + response.statusCode = 200; + response.end(JSON.stringify({ + jsonrpc: "2.0", + result: {}, + id: request.id + })); + + callback(); + }); + + self.remote.ledger_accept(); + }, + ], function (error) { + buster.refute(error, self.what); + done(); + }); + } +}); diff --git a/test/offer-test.js b/test/offer-test.js index d7949bc26..725ebd230 100644 --- a/test/offer-test.js +++ b/test/offer-test.js @@ -1,15 +1,16 @@ -var async = require("async"); -var buster = require("buster"); +var async = require("async"); +var buster = require("buster"); -var Amount = require("../src/js/amount.js").Amount; -var Remote = require("../src/js/remote.js").Remote; -var Server = require("./server.js").Server; +var Amount = require("../src/js/amount").Amount; +var Remote = require("../src/js/remote").Remote; +var Transaction = require("../src/js/transaction").Transaction; +var Server = require("./server").Server; -var testutils = require("./testutils.js"); +var testutils = require("./testutils"); -require("../src/js/amount.js").config = require("./config.js"); -require("../src/js/remote.js").config = require("./config.js"); +require("../src/js/amount").config = require("./config"); +require("../src/js/remote").config = require("./config"); buster.testRunner.timeout = 5000; @@ -68,10 +69,308 @@ buster.testCase("Offer tests", { // console.log("result: error=%s", error); buster.refute(error); - if (error) done(); + done(); }); }, + "offer create then crossing offer, no trust lines with self" : + function (done) { + var self = this; + + async.waterfall([ + function (callback) { + self.what = "Create first offer."; + + self.remote.transaction() + .offer_create("root", "500/BTC/root", "100/USD/root") + .on('proposed', function (m) { + // console.log("PROPOSED: offer_create: %s", JSON.stringify(m)); + + callback(m.result !== 'tesSUCCESS'); + }) + .submit(); + }, + function (callback) { + self.what = "Create crossing offer."; + + self.remote.transaction() + .offer_create("root", "100/USD/root", "500/BTC/root") + .on('proposed', function (m) { + // console.log("PROPOSED: offer_create: %s", JSON.stringify(m)); + + callback(m.result !== 'tesSUCCESS'); + }) + .submit(); + } + ], function (error) { + // console.log("result: error=%s", error); + buster.refute(error, self.what); + done(); + }); + }, + + "Offer create then crossing offer with XRP. Reverse order." : + function (done) { + var self = this; + + async.waterfall([ + function (callback) { + self.what = "Create accounts."; + + testutils.create_accounts(self.remote, "root", "100000.0", ["alice", "bob", "mtgox"], callback); + }, + function (callback) { + self.what = "Set limits."; + + testutils.credit_limits(self.remote, + { + "alice" : "1000/USD/mtgox", + "bob" : "1000/USD/mtgox" + }, + callback); + }, + function (callback) { + self.what = "Distribute funds."; + + testutils.payments(self.remote, + { + "mtgox" : "500/USD/alice" + }, + callback); + }, + function (callback) { + self.what = "Create first offer."; + + self.remote.transaction() + .offer_create("bob", "1/USD/mtgox", "4000.0") // get 1/USD pay 4000/XRP : offer pays 4000 XRP for 1 USD + .on('proposed', function (m) { + // console.log("PROPOSED: offer_create: %s", JSON.stringify(m)); + + callback(m.result !== 'tesSUCCESS'); + }) + .submit(); + }, + function (callback) { + self.what = "Create crossing offer."; + + // Existing offer pays better than this wants. + // Fully consume existing offer. + // Pay 1 USD, get 4000 XRP. + + self.remote.transaction() + .offer_create("alice", "150000.0", "50/USD/mtgox") // get 150,000/XRP pay 50/USD : offer pays 1 USD for 3000 XRP + .on('proposed', function (m) { + // console.log("PROPOSED: offer_create: %s", JSON.stringify(m)); + + callback(m.result !== 'tesSUCCESS'); + }) + .submit(); + }, + function (callback) { + self.what = "Verify balances."; + + testutils.verify_balances(self.remote, + { + "alice" : [ "499/USD/mtgox", String(100000000000+4000000000-2*(Transaction.fees['default'].to_number())) ], + "bob" : [ "1/USD/mtgox", String(100000000000-4000000000-2*(Transaction.fees['default'].to_number())) ], + }, + callback); + }, +// function (callback) { +// self.what = "Display ledger"; +// +// self.remote.request_ledger('current', true) +// .on('success', function (m) { +// console.log("Ledger: %s", JSON.stringify(m, undefined, 2)); +// +// callback(); +// }) +// .request(); +// }, + ], function (error) { + // console.log("result: error=%s", error); + buster.refute(error, self.what); + done(); + }); + }, + + "Offer create then crossing offer with XRP." : + function (done) { + var self = this; + + async.waterfall([ + function (callback) { + self.what = "Create accounts."; + + testutils.create_accounts(self.remote, "root", "100000.0", ["alice", "bob", "mtgox"], callback); + }, + function (callback) { + self.what = "Set limits."; + + testutils.credit_limits(self.remote, + { + "alice" : "1000/USD/mtgox", + "bob" : "1000/USD/mtgox" + }, + callback); + }, + function (callback) { + self.what = "Distribute funds."; + + testutils.payments(self.remote, + { + "mtgox" : "500/USD/alice" + }, + callback); + }, + function (callback) { + self.what = "Create first offer."; + + self.remote.transaction() + .offer_create("alice", "150000.0", "50/USD/mtgox") // pays 1 USD for 3000 XRP + .on('proposed', function (m) { + // console.log("PROPOSED: offer_create: %s", JSON.stringify(m)); + + callback(m.result !== 'tesSUCCESS'); + }) + .submit(); + }, + function (callback) { + self.what = "Create crossing offer."; + + self.remote.transaction() + .offer_create("bob", "1/USD/mtgox", "4000.0") // pays 4000 XRP for 1 USD + .on('proposed', function (m) { + // console.log("PROPOSED: offer_create: %s", JSON.stringify(m)); + + callback(m.result !== 'tesSUCCESS'); + }) + .submit(); + }, + function (callback) { + self.what = "Verify balances."; + + // New offer pays better than old wants. + // Fully consume new offer. + // Pay 1 USD, get 3000 XRP. + + testutils.verify_balances(self.remote, + { + "alice" : [ "499/USD/mtgox", String(100000000000+3000000000-2*(Transaction.fees['default'].to_number())) ], + "bob" : [ "1/USD/mtgox", String(100000000000-3000000000-2*(Transaction.fees['default'].to_number())) ], + }, + callback); + }, +// function (callback) { +// self.what = "Display ledger"; +// +// self.remote.request_ledger('current', true) +// .on('success', function (m) { +// console.log("Ledger: %s", JSON.stringify(m, undefined, 2)); +// +// callback(); +// }) +// .request(); +// }, + ], function (error) { + // console.log("result: error=%s", error); + buster.refute(error, self.what); + done(); + }); + }, + + "Offer create then crossing offer with XRP with limit override." : + function (done) { + var self = this; + + async.waterfall([ + function (callback) { + self.what = "Create accounts."; + + testutils.create_accounts(self.remote, "root", "100000.0", ["alice", "bob", "mtgox"], callback); + }, + function (callback) { + self.what = "Set limits."; + + testutils.credit_limits(self.remote, + { + "alice" : "1000/USD/mtgox", +// "bob" : "1000/USD/mtgox" + }, + callback); + }, + function (callback) { + self.what = "Distribute funds."; + + testutils.payments(self.remote, + { + "mtgox" : "500/USD/alice" + }, + callback); + }, + function (callback) { + self.what = "Create first offer."; + + self.remote.transaction() + .offer_create("alice", "150000.0", "50/USD/mtgox") // 300 XRP = 1 USD + .on('proposed', function (m) { + // console.log("PROPOSED: offer_create: %s", JSON.stringify(m)); + + callback(m.result !== 'tesSUCCESS'); + }) + .submit(); + }, +// function (callback) { +// self.what = "Display ledger"; +// +// self.remote.request_ledger('current', true) +// .on('success', function (m) { +// console.log("Ledger: %s", JSON.stringify(m, undefined, 2)); +// +// callback(); +// }) +// .request(); +// }, + function (callback) { + self.what = "Create crossing offer."; + + self.remote.transaction() + .offer_create("bob", "1/USD/mtgox", "3000.0") // + .on('proposed', function (m) { + // console.log("PROPOSED: offer_create: %s", JSON.stringify(m)); + + callback(m.result !== 'tesSUCCESS'); + }) + .submit(); + }, + function (callback) { + self.what = "Verify balances."; + + testutils.verify_balances(self.remote, + { + "alice" : [ "499/USD/mtgox", String(100000000000+3000000000-2*(Transaction.fees['default'].to_number())) ], + "bob" : [ "1/USD/mtgox", String(100000000000-3000000000-1*(Transaction.fees['default'].to_number())) ], + }, + callback); + }, +// function (callback) { +// self.what = "Display ledger"; +// +// self.remote.request_ledger('current', true) +// .on('success', function (m) { +// console.log("Ledger: %s", JSON.stringify(m, undefined, 2)); +// +// callback(); +// }) +// .request(); +// }, + ], function (error) { + // console.log("result: error=%s", error); + buster.refute(error, self.what); + done(); + }); + }, + "offer_create then ledger_accept then offer_cancel then ledger_accept." : function (done) { var self = this; @@ -346,9 +645,9 @@ buster.testCase("Offer tests", { testutils.create_accounts(self.remote, "root", "10000.0", ["alice", "bob", "mtgox"], callback); }, function (callback) { - self.what = "Owner count undefined."; + self.what = "Owner count 0."; - testutils.verify_owner_count(self.remote, "bob", undefined, callback); + testutils.verify_owner_count(self.remote, "bob", 0, callback); }, function (callback) { self.what = "Set limits."; @@ -425,7 +724,7 @@ buster.testCase("Offer tests", { testutils.verify_balances(self.remote, { - "alice" : [ "0/USD/mtgox", String(10000000000+500-2*(Remote.fees['default'].to_number())) ], + "alice" : [ "0/USD/mtgox", String(10000000000+500-2*(Transaction.fees['default'].to_number())) ], "bob" : "100/USD/mtgox", }, callback); @@ -481,54 +780,41 @@ buster.testCase("Offer tests", { .offer_create("bob", "50/USD/alice", "200/EUR/carol") .on('proposed', function (m) { // console.log("PROPOSED: offer_create: %s", JSON.stringify(m)); - callback(m.result !== 'tesSUCCESS'); + callback(m.result !== 'tecUNFUNDED_OFFER'); seq = m.tx_json.Sequence; }) .submit(); }, // function (callback) { -// self.what = "Alice converts USD to EUR via ripple."; +// self.what = "Alice converts USD to EUR via offer."; // // self.remote.transaction() -// .payment("alice", "alice", "10/EUR/carol") -// .send_max("50/USD/alice") +// .offer_create("alice", "200/EUR/carol", "50/USD/alice") // .on('proposed', function (m) { -// // console.log("proposed: %s", JSON.stringify(m)); -// +// // console.log("PROPOSED: offer_create: %s", JSON.stringify(m)); // callback(m.result !== 'tesSUCCESS'); +// +// seq = m.tx_json.Sequence; // }) // .submit(); // }, - function (callback) { - self.what = "Alice converts USD to EUR via offer."; - - self.remote.transaction() - .offer_create("alice", "200/EUR/carol", "50/USD/alice") - .on('proposed', function (m) { - // console.log("PROPOSED: offer_create: %s", JSON.stringify(m)); - callback(m.result !== 'tesSUCCESS'); - - seq = m.tx_json.Sequence; - }) - .submit(); - }, - function (callback) { - self.what = "Verify balances."; - - testutils.verify_balances(self.remote, - { - "alice" : [ "-50/USD/bob", "200/EUR/carol" ], - "bob" : [ "50/USD/alice", "-200/EUR/carol" ], - "carol" : [ "-200/EUR/alice", "200/EUR/bob" ], - }, - callback); - }, - function (callback) { - self.what = "Verify offer consumed."; - - testutils.verify_offer_not_found(self.remote, "bob", seq, callback); - }, +// function (callback) { +// self.what = "Verify balances."; +// +// testutils.verify_balances(self.remote, +// { +// "alice" : [ "-50/USD/bob", "200/EUR/carol" ], +// "bob" : [ "50/USD/alice", "-200/EUR/carol" ], +// "carol" : [ "-200/EUR/alice", "200/EUR/bob" ], +// }, +// callback); +// }, +// function (callback) { +// self.what = "Verify offer consumed."; +// +// testutils.verify_offer_not_found(self.remote, "bob", seq, callback); +// }, ], function (error) { buster.refute(error, self.what); done(); @@ -601,7 +887,7 @@ buster.testCase("Offer tests", { testutils.verify_balances(self.remote, { - "alice" : [ "160/USD/mtgox", String(10000000000+200-2*(Remote.fees['default'].to_number())) ], + "alice" : [ "160/USD/mtgox", String(10000000000+200-2*(Transaction.fees['default'].to_number())) ], "bob" : "40/USD/mtgox", }, callback); @@ -615,7 +901,7 @@ buster.testCase("Offer tests", { .on('proposed', function (m) { // console.log("proposed: %s", JSON.stringify(m)); - callback(m.result !== 'tepPATH_PARTIAL'); + callback(m.result !== 'tecPATH_PARTIAL'); }) .submit(); }, @@ -643,7 +929,7 @@ buster.testCase("Offer tests", { testutils.verify_balances(self.remote, { - "alice" : [ "100/USD/mtgox", String(10000000000+200+300-4*(Remote.fees['default'].to_number())) ], + "alice" : [ "100/USD/mtgox", String(10000000000+200+300-4*(Transaction.fees['default'].to_number())) ], "bob" : "100/USD/mtgox", }, callback); diff --git a/test/path-test.js b/test/path-test.js index 586de543a..9cc0d7f82 100644 --- a/test/path-test.js +++ b/test/path-test.js @@ -12,7 +12,6 @@ require("../src/js/remote.js").config = require("./config.js"); buster.testRunner.timeout = 5000; -if (false) buster.testCase("Basic Path finding", { // 'setUp' : testutils.build_setup({ verbose: true, no_server: true }), // 'setUp' : testutils.build_setup({ verbose: true }), @@ -432,6 +431,473 @@ buster.testCase("Extended Path finding", { done(); }); }, + + // Test alternative paths with qualities. }); +buster.testCase("More Path finding", { + // 'setUp' : testutils.build_setup({ verbose: true, no_server: true }), + // 'setUp' : testutils.build_setup({ verbose: true }), + 'setUp' : testutils.build_setup(), + 'tearDown' : testutils.build_teardown(), + + "// alternative paths - limit returned paths to best quality" : + // alice +- bitstamp -+ bob + // |- carol(fee) -| // To be excluded. + // |- dan(issue) -| + // |- mtgox -| + function (done) { + var self = this; + + async.waterfall([ + function (callback) { + self.what = "Create accounts."; + + testutils.create_accounts(self.remote, "root", "10000.0", ["alice", "bob", "carol", "dan", "mtgox", "bitstamp"], callback); + }, + function (callback) { + self.what = "Set transfer rate."; + + self.remote.transaction() + .account_set("carol") + .transfer_rate(1e9*1.1) + .once('proposed', function (m) { + // console.log("proposed: %s", JSON.stringify(m)); + callback(m.result !== 'tesSUCCESS'); + }) + .submit(); + }, + function (callback) { + self.what = "Set credit limits."; + + testutils.credit_limits(self.remote, + { + "alice" : [ "800/USD/bitstamp", "800/USD/carol", "800/USD/dan", "800/USD/mtgox", ], + "bob" : [ "800/USD/bitstamp", "800/USD/carol", "800/USD/dan", "800/USD/mtgox", ], + "dan" : [ "800/USD/alice", "800/USD/bob" ], + }, + callback); + }, + function (callback) { + self.what = "Distribute funds."; + + testutils.payments(self.remote, + { + "bitstamp" : "100/USD/alice", + "carol" : "100/USD/alice", + "mtgox" : "100/USD/alice", + }, + callback); + }, +// XXX What should this check? + function (callback) { + self.what = "Find path from alice to bob"; + + self.remote.request_ripple_path_find("alice", "bob", "5/USD/bob", + [ { 'currency' : "USD" } ]) + .on('success', function (m) { + console.log("proposed: %s", JSON.stringify(m)); + + // 1 alternative. +// buster.assert.equals(1, m.alternatives.length) +// // Path is empty. +// buster.assert.equals(0, m.alternatives[0].paths_canonical.length) + + callback(); + }) + .request(); + }, + ], function (error) { + buster.refute(error, self.what); + done(); + }); + }, + + "alternative paths - consume best transfer" : + function (done) { + var self = this; + + async.waterfall([ + function (callback) { + self.what = "Create accounts."; + + testutils.create_accounts(self.remote, "root", "10000.0", ["alice", "bob", "mtgox", "bitstamp"], callback); + }, + function (callback) { + self.what = "Set transfer rate."; + + self.remote.transaction() + .account_set("bitstamp") + .transfer_rate(1e9*1.1) + .once('proposed', function (m) { + // console.log("proposed: %s", JSON.stringify(m)); + callback(m.result !== 'tesSUCCESS'); + }) + .submit(); + }, + function (callback) { + self.what = "Set credit limits."; + + testutils.credit_limits(self.remote, + { + "alice" : [ "600/USD/mtgox", "800/USD/bitstamp" ], + "bob" : [ "700/USD/mtgox", "900/USD/bitstamp" ] + }, + callback); + }, + function (callback) { + self.what = "Distribute funds."; + + testutils.payments(self.remote, + { + "bitstamp" : "70/USD/alice", + "mtgox" : "70/USD/alice", + }, + callback); + }, + function (callback) { + self.what = "Payment with auto path"; + + self.remote.transaction() + .payment('alice', 'bob', "70/USD/bob") + .build_path(true) + .once('proposed', function (m) { + // console.log("proposed: %s", JSON.stringify(m)); + callback(m.result !== 'tesSUCCESS'); + }) + .submit(); + }, + function (callback) { + self.what = "Verify balances."; + + testutils.verify_balances(self.remote, + { + "alice" : [ "0/USD/mtgox", "70/USD/bitstamp" ], + "bob" : [ "70/USD/mtgox", "0/USD/bitstamp" ], + "bitstamp" : [ "-70/USD/alice", "0/USD/bob" ], + "mtgox" : [ "0/USD/alice", "-70/USD/bob" ], + }, + callback); + }, + ], function (error) { + buster.refute(error, self.what); + done(); + }); + }, + + "alternative paths - consume best transfer first" : + function (done) { + var self = this; + + async.waterfall([ + function (callback) { + self.what = "Create accounts."; + + testutils.create_accounts(self.remote, "root", "10000.0", ["alice", "bob", "mtgox", "bitstamp"], callback); + }, + function (callback) { + self.what = "Set transfer rate."; + + self.remote.transaction() + .account_set("bitstamp") + .transfer_rate(1e9*1.1) + .once('proposed', function (m) { + // console.log("proposed: %s", JSON.stringify(m)); + callback(m.result !== 'tesSUCCESS'); + }) + .submit(); + }, + function (callback) { + self.what = "Set credit limits."; + + testutils.credit_limits(self.remote, + { + "alice" : [ "600/USD/mtgox", "800/USD/bitstamp" ], + "bob" : [ "700/USD/mtgox", "900/USD/bitstamp" ] + }, + callback); + }, + function (callback) { + self.what = "Distribute funds."; + + testutils.payments(self.remote, + { + "bitstamp" : "70/USD/alice", + "mtgox" : "70/USD/alice", + }, + callback); + }, + function (callback) { + self.what = "Payment with auto path"; + + self.remote.transaction() + .payment('alice', 'bob', "77/USD/bob") + .build_path(true) + .send_max("100/USD/alice") + .once('proposed', function (m) { + // console.log("proposed: %s", JSON.stringify(m)); + callback(m.result !== 'tesSUCCESS'); + }) + .submit(); + }, + function (callback) { + self.what = "Verify balances."; + + testutils.verify_balances(self.remote, + { + "alice" : [ "0/USD/mtgox", "62.3/USD/bitstamp" ], + "bob" : [ "70/USD/mtgox", "7/USD/bitstamp" ], + "bitstamp" : [ "-62.3/USD/alice", "-7/USD/bob" ], + "mtgox" : [ "0/USD/alice", "-70/USD/bob" ], + }, + callback); + }, + ], function (error) { + buster.refute(error, self.what); + done(); + }); + }, + + // Test alternative paths with qualities. +}); + +buster.testCase("Issues", { + // 'setUp' : testutils.build_setup({ verbose: true, no_server: true }), + // 'setUp' : testutils.build_setup({ verbose: true }), + 'setUp' : testutils.build_setup(), + 'tearDown' : testutils.build_teardown(), + + "Path negative: Issue #5" : + function (done) { + var self = this; + + async.waterfall([ + function (callback) { + self.what = "Create accounts."; + + testutils.create_accounts(self.remote, "root", "10000.0", ["alice", "bob", "carol", "dan"], callback); + }, + function (callback) { + self.what = "Set credit limits."; + + testutils.credit_limits(self.remote, + { + // 2. acct 4 trusted all the other accts for 100 usd + "dan" : [ "100/USD/alice", "100/USD/bob", "100/USD/carol" ], + // 3. acct 2 acted as a nexus for acct 1 and 3, was trusted by 1 and 3 for 100 usd + "alice" : [ "100/USD/bob" ], + "carol" : [ "100/USD/bob" ], + }, + callback); + }, + function (callback) { + // 4. acct 2 sent acct 3 a 75 iou + self.what = "Bob sends Carol 75."; + + self.remote.transaction() + .payment("bob", "carol", "75/USD/bob") + .on('proposed', function (m) { + // console.log("proposed: %s", JSON.stringify(m)); + + callback(m.result !== 'tesSUCCESS'); + }) + .submit(); + }, + function (callback) { + self.what = "Verify balances."; + + testutils.verify_balances(self.remote, + { + "bob" : [ "-75/USD/carol" ], + "carol" : "75/USD/bob", + }, + callback); + }, +// function (callback) { +// self.what = "Display ledger"; +// +// self.remote.request_ledger('current', true) +// .on('success', function (m) { +// console.log("Ledger: %s", JSON.stringify(m, undefined, 2)); +// +// callback(); +// }) +// .request(); +// }, + function (callback) { + self.what = "Find path from alice to bob"; + + // 5. acct 1 sent a 25 usd iou to acct 2 + self.remote.request_ripple_path_find("alice", "bob", "25/USD/bob", + [ { 'currency' : "USD" } ]) + .on('success', function (m) { + // console.log("proposed: %s", JSON.stringify(m)); + + // 0 alternatives. + buster.assert.equals(0, m.alternatives.length) + + callback(); + }) + .request(); + }, + function (callback) { + self.what = "alice fails to send to bob."; + + self.remote.transaction() + .payment('alice', 'bob', "25/USD/alice") + .once('proposed', function (m) { + // console.log("proposed: %s", JSON.stringify(m)); + callback(m.result !== 'tecPATH_DRY'); + }) + .submit(); + }, + function (callback) { + self.what = "Verify balances final."; + + testutils.verify_balances(self.remote, + { + "alice" : [ "0/USD/bob", "0/USD/dan"], + "bob" : [ "0/USD/alice", "-75/USD/carol", "0/USD/dan" ], + "carol" : [ "75/USD/bob", "0/USD/dan" ], + "dan" : [ "0/USD/alice", "0/USD/bob", "0/USD/carol" ], + }, + callback); + }, + ], function (error) { + buster.refute(error, self.what); + done(); + }); + }, + + "Path negative: ripple-client issue #23: smaller" : + // + // alice -- limit 40 --> bob + // alice --> carol --> dan --> bob + // Balance of 100 USD Bob - Balance of 37 USD -> Rod + // + function (done) { + var self = this; + + async.waterfall([ + function (callback) { + self.what = "Create accounts."; + + testutils.create_accounts(self.remote, "root", "10000.0", ["alice", "bob", "carol", "dan"], callback); + }, + function (callback) { + self.what = "Set credit limits."; + + testutils.credit_limits(self.remote, + { + "bob" : [ "40/USD/alice", "20/USD/dan" ], + "carol" : [ "20/USD/alice" ], + "dan" : [ "20/USD/carol" ], + }, + callback); + }, + function (callback) { + self.what = "Payment."; + + self.remote.transaction() + .payment('alice', 'bob', "55/USD/bob") + .build_path(true) + .once('proposed', function (m) { + // console.log("proposed: %s", JSON.stringify(m)); + callback(m.result !== 'tesSUCCESS'); + }) + .submit(); + }, + function (callback) { + self.what = "Verify balances."; + + testutils.verify_balances(self.remote, + { + "bob" : [ "40/USD/alice", "15/USD/dan" ], + }, + callback); + }, +// function (callback) { +// self.what = "Display ledger"; +// +// self.remote.request_ledger('current', true) +// .on('success', function (m) { +// console.log("Ledger: %s", JSON.stringify(m, undefined, 2)); +// +// callback(); +// }) +// .request(); +// }, + ], function (error) { + buster.refute(error, self.what); + done(); + }); + }, + + "Path negative: ripple-client issue #23: larger" : + // + // alice -120 USD-> amazon -25 USD-> bob + // alice -25 USD-> carol -75 USD -> dan -100 USD-> bob + // + function (done) { + var self = this; + + async.waterfall([ + function (callback) { + self.what = "Create accounts."; + + testutils.create_accounts(self.remote, "root", "10000.0", ["alice", "bob", "carol", "dan", "amazon"], callback); + }, + function (callback) { + self.what = "Set credit limits."; + + testutils.credit_limits(self.remote, + { + "amazon" : [ "120/USD/alice" ], + "bob" : [ "25/USD/amazon", "100/USD/dan" ], + "carol" : [ "25/USD/alice" ], + "dan" : [ "75/USD/carol" ], + }, + callback); + }, + function (callback) { + self.what = "Payment."; + + self.remote.transaction() + .payment('alice', 'bob', "50/USD/bob") + .build_path(true) + .once('proposed', function (m) { + // console.log("proposed: %s", JSON.stringify(m)); + callback(m.result !== 'tesSUCCESS'); + }) + .submit(); + }, + function (callback) { + self.what = "Verify balances."; + + testutils.verify_balances(self.remote, + { + "alice" : [ "-25/USD/amazon", "-25/USD/carol" ], + "bob" : [ "25/USD/amazon", "25/USD/dan" ], + "carol" : [ "25/USD/alice", "-25/USD/dan" ], + "dan" : [ "25/USD/carol", "-25/USD/bob" ], + }, + callback); + }, +// function (callback) { +// self.what = "Display ledger"; +// +// self.remote.request_ledger('current', true) +// .on('success', function (m) { +// console.log("Ledger: %s", JSON.stringify(m, undefined, 2)); +// +// callback(); +// }) +// .request(); +// }, + ], function (error) { + buster.refute(error, self.what); + done(); + }); + } +}); // vim:sw=2:sts=2:ts=8:et diff --git a/test/reserve-test.js b/test/reserve-test.js new file mode 100644 index 000000000..1abd4fe7d --- /dev/null +++ b/test/reserve-test.js @@ -0,0 +1,921 @@ + +var async = require("async"); +var buster = require("buster"); + +var Amount = require("../src/js/amount").Amount; +var Remote = require("../src/js/remote").Remote; +var Transaction = require("../src/js/transaction").Transaction; +var Server = require("./server").Server; + +var testutils = require("./testutils"); + +require("../src/js/amount").config = require("./config"); +require("../src/js/remote").config = require("./config"); + +buster.testRunner.timeout = 5000; + +buster.testCase("Reserve", { + 'setUp' : testutils.build_setup(), + // 'setUp' : testutils.build_setup({ verbose: true, standalone: false }), + 'tearDown' : testutils.build_teardown(), + + "offer create then cancel in one ledger" : + function (done) { + var self = this; + var final_create; + + async.waterfall([ + function (callback) { + self.remote.transaction() + .offer_create("root", "500", "100/USD/root") + .on('proposed', function (m) { + // console.log("PROPOSED: offer_create: %s", JSON.stringify(m)); + callback(m.result !== 'tesSUCCESS', m); + }) + .on('final', function (m) { + // console.log("FINAL: offer_create: %s", JSON.stringify(m)); + + buster.assert.equals('tesSUCCESS', m.metadata.TransactionResult); + + buster.assert(final_create); + }) + .submit(); + }, + function (m, callback) { + self.remote.transaction() + .offer_cancel("root", m.tx_json.Sequence) + .on('proposed', function (m) { + // console.log("PROPOSED: offer_cancel: %s", JSON.stringify(m)); + callback(m.result !== 'tesSUCCESS', m); + }) + .on('final', function (m) { + // console.log("FINAL: offer_cancel: %s", JSON.stringify(m, undefined, 2)); + + buster.assert.equals('tesSUCCESS', m.metadata.TransactionResult); + buster.assert(final_create); + done(); + }) + .submit(); + }, + function (m, callback) { + self.remote + .once('ledger_closed', function (message) { + // console.log("LEDGER_CLOSED: %d: %s", ledger_index, ledger_hash); + final_create = message; + }) + .ledger_accept(); + } + ], function (error) { + // console.log("result: error=%s", error); + buster.refute(error); + + if (error) done(); + }); + }, + + "offer_create then ledger_accept then offer_cancel then ledger_accept." : + function (done) { + var self = this; + var final_create; + var offer_seq; + + async.waterfall([ + function (callback) { + self.remote.transaction() + .offer_create("root", "500", "100/USD/root") + .on('proposed', function (m) { + // console.log("PROPOSED: offer_create: %s", JSON.stringify(m)); + + offer_seq = m.tx_json.Sequence; + + callback(m.result !== 'tesSUCCESS'); + }) + .on('final', function (m) { + // console.log("FINAL: offer_create: %s", JSON.stringify(m)); + + buster.assert.equals('tesSUCCESS', m.metadata.TransactionResult); + + final_create = m; + + callback(); + }) + .submit(); + }, + function (callback) { + if (!final_create) { + self.remote + .once('ledger_closed', function (mesage) { + // console.log("LEDGER_CLOSED: %d: %s", ledger_index, ledger_hash); + + }) + .ledger_accept(); + } + else { + callback(); + } + }, + function (callback) { + // console.log("CANCEL: offer_cancel: %d", offer_seq); + + self.remote.transaction() + .offer_cancel("root", offer_seq) + .on('proposed', function (m) { + // console.log("PROPOSED: offer_cancel: %s", JSON.stringify(m)); + callback(m.result !== 'tesSUCCESS'); + }) + .on('final', function (m) { + // console.log("FINAL: offer_cancel: %s", JSON.stringify(m)); + + buster.assert.equals('tesSUCCESS', m.metadata.TransactionResult); + buster.assert(final_create); + + done(); + }) + .submit(); + }, + // See if ledger_accept will crash. + function (callback) { + self.remote + .once('ledger_closed', function (mesage) { + // console.log("LEDGER_CLOSED: A: %d: %s", ledger_index, ledger_hash); + callback(); + }) + .ledger_accept(); + }, + function (callback) { + self.remote + .once('ledger_closed', function (mesage) { + // console.log("LEDGER_CLOSED: B: %d: %s", ledger_index, ledger_hash); + callback(); + }) + .ledger_accept(); + }, + ], function (error) { + // console.log("result: error=%s", error); + buster.refute(error); + + if (error) done(); + }); + }, + + "//new user offer_create then ledger_accept then offer_cancel then ledger_accept." : + function (done) { + var self = this; + var final_create; + var offer_seq; + + async.waterfall([ + function (callback) { + self.remote.transaction() + .payment('root', 'alice', "1000") + .on('proposed', function (m) { + // console.log("proposed: %s", JSON.stringify(m)); + buster.assert.equals(m.result, 'tesSUCCESS'); + callback(); + }) + .submit() + }, + function (callback) { + self.remote.transaction() + .offer_create("alice", "500", "100/USD/alice") + .on('proposed', function (m) { + // console.log("PROPOSED: offer_create: %s", JSON.stringify(m)); + + offer_seq = m.tx_json.Sequence; + + callback(m.result !== 'tesSUCCESS'); + }) + .on('final', function (m) { + // console.log("FINAL: offer_create: %s", JSON.stringify(m)); + + buster.assert.equals('tesSUCCESS', m.metadata.TransactionResult); + + final_create = m; + + callback(); + }) + .submit(); + }, + function (callback) { + if (!final_create) { + self.remote + .once('ledger_closed', function (mesage) { + // console.log("LEDGER_CLOSED: %d: %s", ledger_index, ledger_hash); + + }) + .ledger_accept(); + } + else { + callback(); + } + }, + function (callback) { + // console.log("CANCEL: offer_cancel: %d", offer_seq); + + self.remote.transaction() + .offer_cancel("alice", offer_seq) + .on('proposed', function (m) { + // console.log("PROPOSED: offer_cancel: %s", JSON.stringify(m)); + callback(m.result !== 'tesSUCCESS'); + }) + .on('final', function (m) { + // console.log("FINAL: offer_cancel: %s", JSON.stringify(m)); + + buster.assert.equals('tesSUCCESS', m.metadata.TransactionResult); + buster.assert(final_create); + + done(); + }) + .submit(); + }, + // See if ledger_accept will crash. + function (callback) { + self.remote + .once('ledger_closed', function (mesage) { + // console.log("LEDGER_CLOSED: A: %d: %s", ledger_index, ledger_hash); + callback(); + }) + .ledger_accept(); + }, + function (callback) { + self.remote + .once('ledger_closed', function (mesage) { + // console.log("LEDGER_CLOSED: B: %d: %s", ledger_index, ledger_hash); + callback(); + }) + .ledger_accept(); + }, + ], function (error) { + // console.log("result: error=%s", error); + buster.refute(error); + if (error) done(); + }); + }, + + "offer cancel past and future sequence" : + function (done) { + var self = this; + var final_create; + + async.waterfall([ + function (callback) { + self.remote.transaction() + .payment('root', 'alice', Amount.from_json("10000.0")) + .on('proposed', function (m) { + // console.log("PROPOSED: CreateAccount: %s", JSON.stringify(m)); + callback(m.result !== 'tesSUCCESS', m); + }) + .on('error', function(m) { + // console.log("error: %s", m); + + buster.assert(false); + callback(m); + }) + .submit(); + }, + // Past sequence but wrong + function (m, callback) { + self.remote.transaction() + .offer_cancel("root", m.tx_json.Sequence) + .on('proposed', function (m) { + // console.log("PROPOSED: offer_cancel past: %s", JSON.stringify(m)); + callback(m.result !== 'tesSUCCESS', m); + }) + .submit(); + }, + // Same sequence + function (m, callback) { + self.remote.transaction() + .offer_cancel("root", m.tx_json.Sequence+1) + .on('proposed', function (m) { + // console.log("PROPOSED: offer_cancel same: %s", JSON.stringify(m)); + callback(m.result !== 'temBAD_SEQUENCE', m); + }) + .submit(); + }, + // Future sequence + function (m, callback) { + // After a malformed transaction, need to recover correct sequence. + self.remote.set_account_seq("root", self.remote.account_seq("root")-1); + + self.remote.transaction() + .offer_cancel("root", m.tx_json.Sequence+2) + .on('proposed', function (m) { + // console.log("ERROR: offer_cancel future: %s", JSON.stringify(m)); + callback(m.result !== 'temBAD_SEQUENCE'); + }) + .submit(); + }, + // See if ledger_accept will crash. + function (callback) { + self.remote + .once('ledger_closed', function (mesage) { + // console.log("LEDGER_CLOSED: A: %d: %s", ledger_index, ledger_hash); + callback(); + }) + .ledger_accept(); + }, + function (callback) { + self.remote + .once('ledger_closed', function (mesage) { + // console.log("LEDGER_CLOSED: B: %d: %s", ledger_index, ledger_hash); + callback(); + }) + .ledger_accept(); + }, + function (callback) { + callback(); + } + ], function (error) { + // console.log("result: error=%s", error); + buster.refute(error); + + done(); + }); + }, + + "ripple currency conversion : entire offer" : + // mtgox in, XRP out + function (done) { + var self = this; + var seq; + + async.waterfall([ + function (callback) { + self.what = "Create accounts."; + + testutils.create_accounts(self.remote, "root", "10000.0", ["alice", "bob", "mtgox"], callback); + }, + function (callback) { + self.what = "Owner count 0."; + + testutils.verify_owner_count(self.remote, "bob", 0, callback); + }, + function (callback) { + self.what = "Set limits."; + + testutils.credit_limits(self.remote, + { + "alice" : "100/USD/mtgox", + "bob" : "1000/USD/mtgox" + }, + callback); + }, + function (callback) { + self.what = "Owner counts after trust."; + + testutils.verify_owner_counts(self.remote, + { + "alice" : 1, + "bob" : 1, + }, + callback); + }, + function (callback) { + self.what = "Distribute funds."; + + testutils.payments(self.remote, + { + "mtgox" : "100/USD/alice" + }, + callback); + }, + function (callback) { + self.what = "Create offer."; + + self.remote.transaction() + .offer_create("bob", "100/USD/mtgox", "500") + .on('proposed', function (m) { + // console.log("PROPOSED: offer_create: %s", JSON.stringify(m)); + callback(m.result !== 'tesSUCCESS'); + + seq = m.tx_json.Sequence; + }) + .submit(); + }, + function (callback) { + self.what = "Owner counts after offer create."; + + testutils.verify_owner_counts(self.remote, + { + "alice" : 1, + "bob" : 2, + }, + callback); + }, + function (callback) { + self.what = "Verify offer balance."; + + testutils.verify_offer(self.remote, "bob", seq, "100/USD/mtgox", "500", callback); + }, + function (callback) { + self.what = "Alice converts USD to XRP."; + + self.remote.transaction() + .payment("alice", "alice", "500") + .send_max("100/USD/mtgox") + .on('proposed', function (m) { + // console.log("proposed: %s", JSON.stringify(m)); + + callback(m.result !== 'tesSUCCESS'); + }) + .submit(); + }, + function (callback) { + self.what = "Verify balances."; + + testutils.verify_balances(self.remote, + { + "alice" : [ "0/USD/mtgox", String(10000000000+500-2*(Transaction.fees['default'].to_number())) ], + "bob" : "100/USD/mtgox", + }, + callback); + }, + function (callback) { + self.what = "Verify offer consumed."; + + testutils.verify_offer_not_found(self.remote, "bob", seq, callback); + }, + function (callback) { + self.what = "Owner counts after consumed."; + + testutils.verify_owner_counts(self.remote, + { + "alice" : 1, + "bob" : 1, + }, + callback); + }, + ], function (error) { + buster.refute(error, self.what); + done(); + }); + }, + + "ripple currency conversion : offerer into debt" : + // alice in, carol out + function (done) { + var self = this; + var seq; + + async.waterfall([ + function (callback) { + self.what = "Create accounts."; + + testutils.create_accounts(self.remote, "root", "10000.0", ["alice", "bob", "carol"], callback); + }, + function (callback) { + self.what = "Set limits."; + + testutils.credit_limits(self.remote, + { + "alice" : "2000/EUR/carol", + "bob" : "100/USD/alice", + "carol" : "1000/EUR/bob" + }, + callback); + }, + function (callback) { + self.what = "Create offer to exchange."; + + self.remote.transaction() + .offer_create("bob", "50/USD/alice", "200/EUR/carol") + .on('proposed', function (m) { + // console.log("PROPOSED: offer_create: %s", JSON.stringify(m)); + callback(m.result !== 'tecUNFUNDED_OFFER'); + + seq = m.tx_json.Sequence; + }) + .submit(); + }, +// function (callback) { +// self.what = "Alice converts USD to EUR via offer."; +// +// self.remote.transaction() +// .offer_create("alice", "200/EUR/carol", "50/USD/alice") +// .on('proposed', function (m) { +// // console.log("PROPOSED: offer_create: %s", JSON.stringify(m)); +// callback(m.result !== 'tesSUCCESS'); +// +// seq = m.tx_json.Sequence; +// }) +// .submit(); +// }, +// function (callback) { +// self.what = "Verify balances."; +// +// testutils.verify_balances(self.remote, +// { +// "alice" : [ "-50/USD/bob", "200/EUR/carol" ], +// "bob" : [ "50/USD/alice", "-200/EUR/carol" ], +// "carol" : [ "-200/EUR/alice", "200/EUR/bob" ], +// }, +// callback); +// }, +// function (callback) { +// self.what = "Verify offer consumed."; +// +// testutils.verify_offer_not_found(self.remote, "bob", seq, callback); +// }, + ], function (error) { + buster.refute(error, self.what); + done(); + }); + }, + + "ripple currency conversion : in parts" : + function (done) { + var self = this; + var seq; + + async.waterfall([ + function (callback) { + self.what = "Create accounts."; + + testutils.create_accounts(self.remote, "root", "10000.0", ["alice", "bob", "mtgox"], callback); + }, + function (callback) { + self.what = "Set limits."; + + testutils.credit_limits(self.remote, + { + "alice" : "200/USD/mtgox", + "bob" : "1000/USD/mtgox" + }, + callback); + }, + function (callback) { + self.what = "Distribute funds."; + + testutils.payments(self.remote, + { + "mtgox" : "200/USD/alice" + }, + callback); + }, + function (callback) { + self.what = "Create offer."; + + self.remote.transaction() + .offer_create("bob", "100/USD/mtgox", "500") + .on('proposed', function (m) { + // console.log("PROPOSED: offer_create: %s", JSON.stringify(m)); + callback(m.result !== 'tesSUCCESS'); + + seq = m.tx_json.Sequence; + }) + .submit(); + }, + function (callback) { + self.what = "Alice converts USD to XRP."; + + self.remote.transaction() + .payment("alice", "alice", "200") + .send_max("100/USD/mtgox") + .on('proposed', function (m) { + // console.log("proposed: %s", JSON.stringify(m)); + + callback(m.result !== 'tesSUCCESS'); + }) + .submit(); + }, + function (callback) { + self.what = "Verify offer balance."; + + testutils.verify_offer(self.remote, "bob", seq, "60/USD/mtgox", "300", callback); + }, + function (callback) { + self.what = "Verify balances."; + + testutils.verify_balances(self.remote, + { + "alice" : [ "160/USD/mtgox", String(10000000000+200-2*(Transaction.fees['default'].to_number())) ], + "bob" : "40/USD/mtgox", + }, + callback); + }, + function (callback) { + self.what = "Alice converts USD to XRP should fail due to PartialPayment."; + + self.remote.transaction() + .payment("alice", "alice", "600") + .send_max("100/USD/mtgox") + .on('proposed', function (m) { + // console.log("proposed: %s", JSON.stringify(m)); + + callback(m.result !== 'tecPATH_PARTIAL'); + }) + .submit(); + }, + function (callback) { + self.what = "Alice converts USD to XRP."; + + self.remote.transaction() + .payment("alice", "alice", "600") + .send_max("100/USD/mtgox") + .set_flags('PartialPayment') + .on('proposed', function (m) { + // console.log("proposed: %s", JSON.stringify(m)); + + callback(m.result !== 'tesSUCCESS'); + }) + .submit(); + }, + function (callback) { + self.what = "Verify offer consumed."; + + testutils.verify_offer_not_found(self.remote, "bob", seq, callback); + }, + function (callback) { + self.what = "Verify balances."; + + testutils.verify_balances(self.remote, + { + "alice" : [ "100/USD/mtgox", String(10000000000+200+300-4*(Transaction.fees['default'].to_number())) ], + "bob" : "100/USD/mtgox", + }, + callback); + }, + ], function (error) { + buster.refute(error, self.what); + done(); + }); + }, +}); + +buster.testCase("Offer cross currency", { + 'setUp' : testutils.build_setup(), + 'tearDown' : testutils.build_teardown(), + + "ripple cross currency payment - start with XRP" : + // alice --> [XRP --> carol --> USD/mtgox] --> bob + + function (done) { + var self = this; + var seq; + + // self.remote.set_trace(); + + async.waterfall([ + function (callback) { + self.what = "Create accounts."; + + testutils.create_accounts(self.remote, "root", "10000.0", ["alice", "bob", "carol", "mtgox"], callback); + }, + function (callback) { + self.what = "Set limits."; + + testutils.credit_limits(self.remote, + { + "carol" : "1000/USD/mtgox", + "bob" : "2000/USD/mtgox" + }, + callback); + }, + function (callback) { + self.what = "Distribute funds."; + + testutils.payments(self.remote, + { + "mtgox" : "500/USD/carol" + }, + callback); + }, + function (callback) { + self.what = "Create offer."; + + self.remote.transaction() + .offer_create("carol", "500", "50/USD/mtgox") + .on('proposed', function (m) { + // console.log("PROPOSED: offer_create: %s", JSON.stringify(m)); + callback(m.result !== 'tesSUCCESS'); + + seq = m.tx_json.Sequence; + }) + .submit(); + }, + function (callback) { + self.what = "Alice send USD/mtgox converting from XRP."; + + self.remote.transaction() + .payment("alice", "bob", "25/USD/mtgox") + .send_max("333") + .on('proposed', function (m) { + // console.log("proposed: %s", JSON.stringify(m)); + + callback(m.result !== 'tesSUCCESS'); + }) + .submit(); + }, + function (callback) { + self.what = "Verify balances."; + + testutils.verify_balances(self.remote, + { +// "alice" : [ "500" ], + "bob" : "25/USD/mtgox", + "carol" : "475/USD/mtgox", + }, + callback); + }, + function (callback) { + self.what = "Verify offer consumed."; + + testutils.verify_offer_not_found(self.remote, "bob", seq, callback); + }, + ], function (error) { + buster.refute(error, self.what); + done(); + }); + }, + + "ripple cross currency payment - end with XRP" : + // alice --> [USD/mtgox --> carol --> XRP] --> bob + + function (done) { + var self = this; + var seq; + + // self.remote.set_trace(); + + async.waterfall([ + function (callback) { + self.what = "Create accounts."; + + testutils.create_accounts(self.remote, "root", "10000.0", ["alice", "bob", "carol", "mtgox"], callback); + }, + function (callback) { + self.what = "Set limits."; + + testutils.credit_limits(self.remote, + { + "alice" : "1000/USD/mtgox", + "carol" : "2000/USD/mtgox" + }, + callback); + }, + function (callback) { + self.what = "Distribute funds."; + + testutils.payments(self.remote, + { + "mtgox" : "500/USD/alice" + }, + callback); + }, + function (callback) { + self.what = "Create offer."; + + self.remote.transaction() + .offer_create("carol", "50/USD/mtgox", "500") + .on('proposed', function (m) { + // console.log("PROPOSED: offer_create: %s", JSON.stringify(m)); + callback(m.result !== 'tesSUCCESS'); + + seq = m.tx_json.Sequence; + }) + .submit(); + }, + function (callback) { + self.what = "Alice send XRP to bob converting from USD/mtgox."; + + self.remote.transaction() + .payment("alice", "bob", "250") + .send_max("333/USD/mtgox") + .on('proposed', function (m) { + // console.log("proposed: %s", JSON.stringify(m)); + + callback(m.result !== 'tesSUCCESS'); + }) + .submit(); + }, + function (callback) { + self.what = "Verify balances."; + + testutils.verify_balances(self.remote, + { + "alice" : "475/USD/mtgox", + "bob" : "10000000250", + "carol" : "25/USD/mtgox", + }, + callback); + }, + function (callback) { + self.what = "Verify offer partially consumed."; + + testutils.verify_offer(self.remote, "carol", seq, "25/USD/mtgox", "250", callback); + }, + ], function (error) { + buster.refute(error, self.what); + done(); + }); + }, + + "ripple cross currency bridged payment" : + // alice --> [USD/mtgox --> carol --> XRP] --> [XRP --> dan --> EUR/bitstamp] --> bob + + function (done) { + var self = this; + var seq_carol; + var seq_dan; + + // self.remote.set_trace(); + + async.waterfall([ + function (callback) { + self.what = "Create accounts."; + + testutils.create_accounts(self.remote, "root", "10000.0", ["alice", "bob", "carol", "dan", "bitstamp", "mtgox"], callback); + }, + function (callback) { + self.what = "Set limits."; + + testutils.credit_limits(self.remote, + { + "alice" : "1000/USD/mtgox", + "bob" : "1000/EUR/bitstamp", + "carol" : "1000/USD/mtgox", + "dan" : "1000/EUR/bitstamp" + }, + callback); + }, + function (callback) { + self.what = "Distribute funds."; + + testutils.payments(self.remote, + { + "bitstamp" : "400/EUR/dan", + "mtgox" : "500/USD/alice", + }, + callback); + }, + function (callback) { + self.what = "Create offer carol."; + + self.remote.transaction() + .offer_create("carol", "50/USD/mtgox", "500") + .on('proposed', function (m) { + // console.log("PROPOSED: offer_create: %s", JSON.stringify(m)); + callback(m.result !== 'tesSUCCESS'); + + seq_carol = m.tx_json.Sequence; + }) + .submit(); + }, + function (callback) { + self.what = "Create offer dan."; + + self.remote.transaction() + .offer_create("dan", "500", "50/EUR/bitstamp") + .on('proposed', function (m) { + // console.log("PROPOSED: offer_create: %s", JSON.stringify(m)); + callback(m.result !== 'tesSUCCESS'); + + seq_dan = m.tx_json.Sequence; + }) + .submit(); + }, + function (callback) { + self.what = "Alice send EUR/bitstamp to bob converting from USD/mtgox."; + + self.remote.transaction() + .payment("alice", "bob", "30/EUR/bitstamp") + .send_max("333/USD/mtgox") + .path_add( [ { currency: "XRP" } ]) + .on('proposed', function (m) { + // console.log("proposed: %s", JSON.stringify(m)); + + callback(m.result !== 'tesSUCCESS'); + }) + .submit(); + }, + function (callback) { + self.what = "Verify balances."; + + testutils.verify_balances(self.remote, + { + "alice" : "470/USD/mtgox", + "bob" : "30/EUR/bitstamp", + "carol" : "30/USD/mtgox", + "dan" : "370/EUR/bitstamp", + }, + callback); + }, + function (callback) { + self.what = "Verify carol offer partially consumed."; + + testutils.verify_offer(self.remote, "carol", seq_carol, "20/USD/mtgox", "200", callback); + }, + function (callback) { + self.what = "Verify dan offer partially consumed."; + + testutils.verify_offer(self.remote, "dan", seq_dan, "200", "20/EUR/mtgox", callback); + }, + ], function (error) { + buster.refute(error, self.what); + done(); + }); + }, +}); + +// vim:sw=2:sts=2:ts=8:et diff --git a/test/send-test.js b/test/send-test.js index 00ee6f339..d2fe9c7f3 100644 --- a/test/send-test.js +++ b/test/send-test.js @@ -41,6 +41,7 @@ buster.testCase("Fee Changes", { buster.testCase("Sending", { 'setUp' : testutils.build_setup(), + // 'setUp' : testutils.build_setup({verbose: true , no_server: true}), 'tearDown' : testutils.build_teardown(), "send XRP to non-existent account with insufficent fee" : @@ -80,16 +81,16 @@ buster.testCase("Sending", { // Transaction got an error. // console.log("proposed: %s", JSON.stringify(m)); - buster.assert.equals(m.result, 'terNO_DST_INSUF_XRP'); + buster.assert.equals(m.result, 'tecNO_DST_INSUF_XRP'); got_proposed = true; self.remote.ledger_accept(); // Move it along. }) .on('final', function (m) { - // console.log("final: %s", JSON.stringify(m)); + // console.log("final: %s", JSON.stringify(m, undefined, 2)); - buster.assert(false, "Should not have got a final."); + buster.assert.equals(m.metadata.TransactionResult, 'tecNO_DST_INSUF_XRP'); done(); }) .on('error', function(m) { @@ -100,15 +101,15 @@ buster.testCase("Sending", { .submit(); }, - // Also test transaction becomes lost after terNO_DST. - "credit_limit to non-existent account = terNO_DST" : + // Also test transaction becomes lost after tecNO_DST. + "credit_limit to non-existent account = tecNO_DST" : function (done) { this.remote.transaction() .ripple_line_set("root", "100/USD/alice") .on('proposed', function (m) { //console.log("proposed: %s", JSON.stringify(m)); - buster.assert.equals(m.result, 'terNO_DST'); + buster.assert.equals(m.result, 'tecNO_DST'); done(); }) @@ -180,38 +181,57 @@ buster.testCase("Sending", { }) .request(); }, + // Set negative limit. + function (callback) { + self.remote.transaction() + .ripple_line_set("alice", "-1/USD/mtgox") + .on('proposed', function (m) { + buster.assert.equals('temBAD_LIMIT', m.result); + + // After a malformed transaction, need to recover correct sequence. + self.remote.set_account_seq("alice", self.remote.account_seq("alice")-1); + callback('temBAD_LIMIT' !== m.result); + }) + .submit(); + }, +// function (callback) { +// self.what = "Display ledger"; +// +// self.remote.request_ledger('current', true) +// .on('success', function (m) { +// console.log("Ledger: %s", JSON.stringify(m, undefined, 2)); +// +// callback(); +// }) +// .request(); +// }, function (callback) { self.what = "Zero a credit limit."; testutils.credit_limit(self.remote, "alice", "0/USD/mtgox", callback); }, function (callback) { - self.what = "Make sure still exists."; + self.what = "Make sure line is deleted."; self.remote.request_ripple_balance("alice", "mtgox", "USD", 'CURRENT') .on('ripple_state', function (m) { - buster.assert(m.account_balance.equals("0/USD/alice")); - buster.assert(m.account_limit.equals("0/USD/alice")); - buster.assert(m.issuer_balance.equals("0/USD/mtgox")); - buster.assert(m.issuer_limit.equals("0/USD/mtgox")); + // Used to keep lines. + // buster.assert(m.account_balance.equals("0/USD/alice")); + // buster.assert(m.account_limit.equals("0/USD/alice")); + // buster.assert(m.issuer_balance.equals("0/USD/mtgox")); + // buster.assert(m.issuer_limit.equals("0/USD/mtgox")); + + buster.assert(false); + }) + .on('error', function (m) { + // console.log("error: %s", JSON.stringify(m)); + buster.assert.equals('remoteError', m.error); + buster.assert.equals('entryNotFound', m.remote.error); callback(); }) .request(); }, - // Set negative limit. - function (callback) { - self.remote.transaction() - .ripple_line_set("alice", "-1/USD/mtgox") - .on('proposed', function (m) { - buster.assert.equals('temBAD_AMOUNT', m.result); - - // After a malformed transaction, need to recover correct sequence. - self.remote.set_account_seq("alice", self.remote.account_seq("alice")-1); - callback('temBAD_AMOUNT' !== m.result); - }) - .submit(); - }, // TODO Check in both owner books. function (callback) { self.what = "Set another limit."; @@ -423,7 +443,7 @@ buster.testCase("Sending future", { .payment('bob', 'alice', "1/USD/bob") .once('proposed', function (m) { // console.log("proposed: %s", JSON.stringify(m)); - callback(m.result !== 'tepPATH_DRY'); + callback(m.result !== 'tecPATH_DRY'); }) .submit(); }, @@ -475,6 +495,410 @@ buster.testCase("Sending future", { // Ripple with one-way credit path. }); +buster.testCase("Gateway", { + // 'setUp' : testutils.build_setup({ verbose: true }), + 'setUp' : testutils.build_setup(), + 'tearDown' : testutils.build_teardown(), + + "customer to customer with and without transfer fee" : + function (done) { + var self = this; + + // self.remote.set_trace(); + + async.waterfall([ + function (callback) { + self.what = "Create accounts."; + + testutils.create_accounts(self.remote, "root", "10000.0", ["alice", "bob", "mtgox"], callback); + }, + function (callback) { + self.what = "Set credit limits."; + + testutils.credit_limits(self.remote, + { + "alice" : "100/AUD/mtgox", + "bob" : "100/AUD/mtgox", + }, + callback); + }, + function (callback) { + self.what = "Distribute funds."; + + testutils.payments(self.remote, + { + "mtgox" : [ "1/AUD/alice" ], + }, + callback); + }, + function (callback) { + self.what = "Verify balances."; + + testutils.verify_balances(self.remote, + { + "alice" : "1/AUD/mtgox", + "mtgox" : "-1/AUD/alice", + }, + callback); + }, + function (callback) { + self.what = "Alice sends Bob 1 AUD"; + + self.remote.transaction() + .payment("alice", "bob", "1/AUD/mtgox") + .on('proposed', function (m) { + // console.log("proposed: %s", JSON.stringify(m)); + + callback(m.result !== 'tesSUCCESS'); + }) + .submit(); + }, + function (callback) { + self.what = "Verify balances 2."; + + testutils.verify_balances(self.remote, + { + "alice" : "0/AUD/mtgox", + "bob" : "1/AUD/mtgox", + "mtgox" : "-1/AUD/bob", + }, + callback); + }, + function (callback) { + self.what = "Set transfer rate."; + + self.remote.transaction() + .account_set("mtgox") + .transfer_rate(1e9*1.1) + .once('proposed', function (m) { + // console.log("proposed: %s", JSON.stringify(m)); + callback(m.result !== 'tesSUCCESS'); + }) + .submit(); + }, + function (callback) { + self.what = "Bob sends Alice 0.5 AUD"; + + self.remote.transaction() + .payment("bob", "alice", "0.5/AUD/mtgox") + .send_max("0.55/AUD/mtgox") // !!! Very important. + .on('proposed', function (m) { + // console.log("proposed: %s", JSON.stringify(m)); + + callback(m.result !== 'tesSUCCESS'); + }) + .submit(); + }, + function (callback) { + self.what = "Verify balances 3."; + + testutils.verify_balances(self.remote, + { + "alice" : "0.5/AUD/mtgox", + "bob" : "0.45/AUD/mtgox", + "mtgox" : [ "-0.5/AUD/alice","-0.45/AUD/bob" ], + }, + callback); + }, + ], function (error) { + buster.refute(error, self.what); + done(); + }); + }, + + "subscribe test: customer to customer with and without transfer fee" : + function (done) { + var self = this; + + // self.remote.set_trace(); + + async.waterfall([ + function (callback) { + self.what = "Create accounts."; + + testutils.create_accounts(self.remote, "root", "10000.0", ["alice", "bob", "mtgox"], callback); + }, + function (callback) { testutils.ledger_close(self.remote, callback); }, + function (callback) { + self.what = "Set credit limits."; + + testutils.credit_limits(self.remote, + { + "alice" : "100/AUD/mtgox", + "bob" : "100/AUD/mtgox", + }, + callback); + }, + function (callback) { testutils.ledger_close(self.remote, callback); }, + function (callback) { + self.what = "Distribute funds."; + + testutils.payments(self.remote, + { + "mtgox" : [ "1/AUD/alice" ], + }, + callback); + }, + function (callback) { testutils.ledger_close(self.remote, callback); }, + function (callback) { + self.what = "Verify balances."; + + testutils.verify_balances(self.remote, + { + "alice" : "1/AUD/mtgox", + "mtgox" : "-1/AUD/alice", + }, + callback); + }, + function (callback) { + self.what = "Alice sends Bob 1 AUD"; + + self.remote.transaction() + .payment("alice", "bob", "1/AUD/mtgox") + .on('proposed', function (m) { + // console.log("proposed: %s", JSON.stringify(m)); + + callback(m.result !== 'tesSUCCESS'); + }) + .submit(); + }, + function (callback) { testutils.ledger_close(self.remote, callback); }, + function (callback) { + self.what = "Verify balances 2."; + + testutils.verify_balances(self.remote, + { + "alice" : "0/AUD/mtgox", + "bob" : "1/AUD/mtgox", + "mtgox" : "-1/AUD/bob", + }, + callback); + }, + function (callback) { + self.what = "Set transfer rate."; + + self.remote.transaction() + .account_set("mtgox") + .transfer_rate(1e9*1.1) + .once('proposed', function (m) { + // console.log("proposed: %s", JSON.stringify(m)); + callback(m.result !== 'tesSUCCESS'); + }) + .submit(); + }, + function (callback) { testutils.ledger_close(self.remote, callback); }, + function (callback) { + self.what = "Bob sends Alice 0.5 AUD"; + + self.remote.transaction() + .payment("bob", "alice", "0.5/AUD/mtgox") + .send_max("0.55/AUD/mtgox") // !!! Very important. + .on('proposed', function (m) { + // console.log("proposed: %s", JSON.stringify(m)); + + callback(m.result !== 'tesSUCCESS'); + }) + .submit(); + }, + function (callback) { + self.what = "Verify balances 3."; + + testutils.verify_balances(self.remote, + { + "alice" : "0.5/AUD/mtgox", + "bob" : "0.45/AUD/mtgox", + "mtgox" : [ "-0.5/AUD/alice","-0.45/AUD/bob" ], + }, + callback); + }, + function (callback) { + self.what = "Subscribe and accept."; + + self.count = 0; + self.found = 0; + + self.remote.on('ledger_closed', function (m) { + // console.log("#### LEDGER_CLOSE: %s", JSON.stringify(m)); + }); + + self.remote + .on('account', function (m) { + // console.log("ACCOUNT: %s", JSON.stringify(m)); + self.found = 1; + }) + .on('ledger_closed', function (m) { + // console.log("LEDGER_CLOSE: %d: %s", self.count, JSON.stringify(m)); + + if (self.count) { + callback(!self.found); + } + else { + self.count = 1; + + self.remote.ledger_accept(); + } + }) + .request_subscribe().accounts("mtgox") + .request(); + + self.remote.ledger_accept(); + }, + ], function (error) { + buster.refute(error, self.what); + done(); + }); + }, + + "subscribe test: customer to customer with and without transfer fee: transaction retry logic" : + function (done) { + var self = this; + + // self.remote.set_trace(); + + async.waterfall([ + function (callback) { + self.what = "Create accounts."; + + testutils.create_accounts(self.remote, "root", "10000.0", ["alice", "bob", "mtgox"], callback); + }, + function (callback) { + self.what = "Set credit limits."; + + testutils.credit_limits(self.remote, + { + "alice" : "100/AUD/mtgox", + "bob" : "100/AUD/mtgox", + }, + callback); + }, + function (callback) { + self.what = "Distribute funds."; + + testutils.payments(self.remote, + { + "mtgox" : [ "1/AUD/alice" ], + }, + callback); + }, + function (callback) { + self.what = "Verify balances."; + + testutils.verify_balances(self.remote, + { + "alice" : "1/AUD/mtgox", + "mtgox" : "-1/AUD/alice", + }, + callback); + }, + function (callback) { + self.what = "Alice sends Bob 1 AUD"; + + self.remote.transaction() + .payment("alice", "bob", "1/AUD/mtgox") + .on('proposed', function (m) { + // console.log("proposed: %s", JSON.stringify(m)); + + callback(m.result !== 'tesSUCCESS'); + }) + .submit(); + }, + function (callback) { + self.what = "Verify balances 2."; + + testutils.verify_balances(self.remote, + { + "alice" : "0/AUD/mtgox", + "bob" : "1/AUD/mtgox", + "mtgox" : "-1/AUD/bob", + }, + callback); + }, +// function (callback) { +// self.what = "Set transfer rate."; +// +// self.remote.transaction() +// .account_set("mtgox") +// .transfer_rate(1e9*1.1) +// .once('proposed', function (m) { +// // console.log("proposed: %s", JSON.stringify(m)); +// callback(m.result !== 'tesSUCCESS'); +// }) +// .submit(); +// }, + function (callback) { + self.what = "Bob sends Alice 0.5 AUD"; + + self.remote.transaction() + .payment("bob", "alice", "0.5/AUD/mtgox") +// .send_max("0.55/AUD/mtgox") // !!! Very important. + .on('proposed', function (m) { + // console.log("proposed: %s", JSON.stringify(m)); + + callback(m.result !== 'tesSUCCESS'); + }) + .submit(); + }, + function (callback) { + self.what = "Verify balances 3."; + + testutils.verify_balances(self.remote, + { + "alice" : "0.5/AUD/mtgox", + "bob" : "0.5/AUD/mtgox", + "mtgox" : [ "-0.5/AUD/alice","-0.5/AUD/bob" ], + }, + callback); + }, + function (callback) { + self.what = "Subscribe and accept."; + + self.count = 0; + self.found = 0; + + self.remote.on('ledger_closed', function (m) { + // console.log("#### LEDGER_CLOSE: %s", JSON.stringify(m)); + }); + + self.remote + .on('account', function (m) { + // console.log("ACCOUNT: %s", JSON.stringify(m)); + self.found = 1; + }) + .on('ledger_closed', function (m) { + // console.log("LEDGER_CLOSE: %d: %s", self.count, JSON.stringify(m)); + + if (self.count) { + callback(!self.found); + } + else { + self.count = 1; + + self.remote.ledger_accept(); + } + }) + .request_subscribe().accounts("mtgox") + .request(); + + self.remote.ledger_accept(); + }, + function (callback) { + self.what = "Verify balances 4."; + + testutils.verify_balances(self.remote, + { + "alice" : "0.5/AUD/mtgox", + "bob" : "0.5/AUD/mtgox", + "mtgox" : [ "-0.5/AUD/alice","-0.5/AUD/bob" ], + }, + callback); + }, + ], function (error) { + buster.refute(error, self.what); + done(); + }); + }, +}); + buster.testCase("Indirect ripple", { // 'setUp' : testutils.build_setup({ verbose: true }), 'setUp' : testutils.build_setup(), @@ -529,7 +953,7 @@ buster.testCase("Indirect ripple", { .on('proposed', function (m) { // console.log("proposed: %s", JSON.stringify(m)); - callback(m.result !== 'tepPATH_PARTIAL'); + callback(m.result !== 'tecPATH_PARTIAL'); }) .submit(); }, @@ -541,7 +965,7 @@ buster.testCase("Indirect ripple", { .on('proposed', function (m) { // console.log("proposed: %s", JSON.stringify(m)); - callback(m.result !== 'tepPATH_PARTIAL'); + callback(m.result !== 'tecPATH_PARTIAL'); }) .submit(); }, diff --git a/test/server-test.js b/test/server-test.js index f44379162..dd73127e0 100644 --- a/test/server-test.js +++ b/test/server-test.js @@ -1,5 +1,5 @@ var buster = require("buster"); - +var testutils = require("./testutils.js"); var Server = require("./server.js").Server; // How long to wait for server to start. @@ -9,7 +9,7 @@ var alpha; buster.testCase("Standalone server startup", { "server start and stop" : function (done) { - alpha = Server.from_config("alpha"); + alpha = Server.from_config("alpha", false); //ADD ,true for verbosity alpha .on('started', function () { diff --git a/test/server.js b/test/server.js index 400abf6ae..5a5d79cc2 100644 --- a/test/server.js +++ b/test/server.js @@ -31,6 +31,7 @@ var Server = function (name, config, verbose) { this.config = config; this.started = false; this.quiet = !verbose; + this.stopping = false; }; Server.prototype = new EventEmitter; @@ -97,10 +98,21 @@ Server.prototype._serverSpawnSync = function() { // 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 - if (!self.quiet) console.log("server: spawn: server exited code=%s: signal=%s", code, signal); - }); + if (!self.quiet) console.log("server: spawn: server exited code=%s: signal=%s", code, signal); + + self.emit('exited'); + + // Workaround for + // https://github.com/busterjs/buster/issues/266 + if (!self.stopping) { + buster.assertions.throwOnFailure = true; + } + + // If could not exec: code=127, signal=null + // If regular exit: code=0, signal=null + // Fail the test if the server has not called "stop". + buster.assert(self.stopping, 'Server died with signal '+signal); + }); }; // Prepare server's working directory. @@ -149,6 +161,8 @@ Server.prototype.start = function () { Server.prototype.stop = function () { var self = this; + self.stopping = true; + if (this.child) { // Update the on exit to invoke done. this.child.on('exit', function (code, signal) { diff --git a/test/testutils.js b/test/testutils.js index 07fd28d37..deddc3009 100644 --- a/test/testutils.js +++ b/test/testutils.js @@ -90,10 +90,22 @@ var build_setup = function (opts, host) { function runServerStep(callback) { if (opts.no_server) return callback(); - data.server = Server.from_config(host, !!opts.verbose_server).on('started', callback).start(); + data.server = Server + .from_config(host, !!opts.verbose_server) + .on('started', callback) + .on('exited', function () { + // If know the remote, tell it server is gone. + if (self.remote) + self.remote.server_fatal(); + }) + .start(); }, function connectWebsocketStep(callback) { - self.remote = data.remote = Remote.from_config(host, !!opts.verbose_ws).once('ledger_closed', callback).connect(); + self.remote = data.remote = + Remote + .from_config(host, !!opts.verbose_ws) + .once('ledger_closed', callback) + .connect(); } ], done); }; @@ -107,8 +119,6 @@ var build_setup = function (opts, host) { var build_teardown = function (host) { return function (done) { - - host = host || config.server_default; var data = this.store[host]; @@ -203,6 +213,10 @@ var credit_limits = function (remote, balances, callback) { }); }; +var ledger_close = function (remote, callback) { + remote.once('ledger_closed', function (m) { callback(); }).ledger_accept(); +} + var payment = function (remote, src, dst, amount, callback) { assert(5 === arguments.length); @@ -295,7 +309,7 @@ var verify_balance = function (remote, src, amount_json, callback) { var account_balance = Amount.from_json(m.account_balance); if (!account_balance.equals(amount_req)) { - console.log("verify_balance: failed: %s vs %s is %s: %s", src, account_balance.to_text_full(), amount_req.to_text_full(), account_balance.not_equals_why(amount_req)); + console.log("verify_balance: failed: %s vs %s / %s: %s", src, account_balance.to_text_full(), amount_req.to_text_full(), account_balance.not_equals_why(amount_req)); } callback(!account_balance.equals(amount_req)); @@ -397,14 +411,14 @@ var verify_owner_counts = function (remote, counts, callback) { }; exports.account_dump = account_dump; - exports.build_setup = build_setup; +exports.build_teardown = build_teardown; exports.create_accounts = create_accounts; exports.credit_limit = credit_limit; exports.credit_limits = credit_limits; +exports.ledger_close = ledger_close; exports.payment = payment; exports.payments = payments; -exports.build_teardown = build_teardown; exports.transfer_rate = transfer_rate; exports.verify_balance = verify_balance; exports.verify_balances = verify_balances; diff --git a/validators-example.txt b/validators-example.txt index cde2a6711..033ad8c48 100644 --- a/validators-example.txt +++ b/validators-example.txt @@ -2,7 +2,7 @@ # Default validators.txt # # A list of domains to bootstrap a nodes UNLs or for clients to indirectly -# locate IPs to contact the Newcoin network. +# locate IPs to contact the Ripple network. # # This file is UTF-8 with Dos, UNIX, or Mac style end of lines. # Blank lines and lines starting with a '#' are ignored. @@ -11,7 +11,7 @@ # [validators]: # List of nodes to accept as validators specified by public key or domain. # -# For domains, newcoind will probe for https web servers at the specified +# For domains, rippled will probe for https web servers at the specified # domain in the following order: ripple.DOMAIN, www.DOMAIN, DOMAIN # # Examples: redstem.com diff --git a/webpack.js b/webpack.js deleted file mode 100644 index 6f50aaf5d..000000000 --- a/webpack.js +++ /dev/null @@ -1,68 +0,0 @@ -var pkg = require('./package.json'); -var webpack = require("webpack"); -var async = require("async"); -var extend = require("extend"); - -var cfg = { - // General settings - baseName: pkg.name, - programPath: __dirname + "/src/js/index.js", - - // CLI-configurable options - watch: false, - outputDir: __dirname + "/build" -}; -for (var i = 0, l = process.argv.length; i < l; i++) { - var arg = process.argv[i]; - if (arg === '-w' || arg === '--watch') { - cfg.watch = true; - } else if (arg === '-o') { - cfg.outputDir = process.argv[++i]; - } -}; - -var builds = [{ - filename: 'ripple-'+pkg.version+'.js', -},{ - filename: 'ripple-'+pkg.version+'-debug.js', - debug: true -},{ - filename: 'ripple-'+pkg.version+'-min.js', - minimize: true -}]; - -var defaultOpts = { - // [sic] Yes, this is the spelling upstream. - libary: 'ripple', - // However, it's fixed in webpack 0.8, so we include the correct spelling too: - library: 'ripple', - watch: cfg.watch -}; - -function build(opts) { - var opts = extend({}, defaultOpts, opts); - opts.output = cfg.outputDir + "/"+opts.filename; - return function (callback) { - var filename = opts.filename; - webpack(cfg.programPath, opts, function (err, result) { - console.log(' '+filename, result.hash, '['+result.modulesCount+']'); - if ("function" === typeof callback) { - callback(err); - } - }); - } -} - -if (!cfg.watch) { - console.log('Compiling Ripple JavaScript...'); - async.series(builds.map(build), function (err) { - if (err) { - console.error(err); - } - }); -} else { - console.log('Watching files for changes...'); - builds.map(build).forEach(function (build) { - build(); - }); -}