mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-20 11:05:54 +00:00
Merge branch 'master' of github.com:jedmccaleb/NewCoin
This commit is contained in:
@@ -157,9 +157,10 @@
|
|||||||
# connections.
|
# connections.
|
||||||
#
|
#
|
||||||
# [websocket_public_secure]
|
# [websocket_public_secure]
|
||||||
# 0 or 1.
|
# 0, 1 or 2.
|
||||||
# 0: Provide ws service for websocket_public_ip/websocket_public_port.
|
# 0: Provide ws service for websocket_public_ip/websocket_public_port.
|
||||||
# 1: Provide wss service for websocket_public_ip/websocket_public_port. [default]
|
# 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
|
# 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
|
# websocket connection if a self-signed certificate is used. As the Ripple
|
||||||
@@ -177,9 +178,10 @@
|
|||||||
# Port to bind to allow trusted ADMIN connections from backend applications.
|
# Port to bind to allow trusted ADMIN connections from backend applications.
|
||||||
#
|
#
|
||||||
# [websocket_secure]
|
# [websocket_secure]
|
||||||
# 0 or 1.
|
# 0, 1, or 2.
|
||||||
# 0: Provide ws service for websocket_ip/websocket_port. [default]
|
# 0: Provide ws service only for websocket_ip/websocket_port. [default]
|
||||||
# 1: Provide wss service for websocket_ip/websocket_port.
|
# 1: Provide ws and wss service for websocket_ip/websocket_port
|
||||||
|
# 2: Provide wss service for websocket_ip/websocket_port.
|
||||||
#
|
#
|
||||||
# [websocket_ssl_key]:
|
# [websocket_ssl_key]:
|
||||||
# Specify the filename holding the SSL key in PEM format.
|
# Specify the filename holding the SSL key in PEM format.
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
SETUP_LOG();
|
SETUP_LOG();
|
||||||
|
|
||||||
LogPartition TaggedCachePartition("TaggedCache");
|
LogPartition TaggedCachePartition("TaggedCache");
|
||||||
|
LogPartition AutoSocketPartition("AutoSocket");
|
||||||
Application* theApp = NULL;
|
Application* theApp = NULL;
|
||||||
|
|
||||||
DatabaseCon::DatabaseCon(const std::string& strName, const char *initStrings[], int initCount)
|
DatabaseCon::DatabaseCon(const std::string& strName, const char *initStrings[], int initCount)
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
|
|
||||||
#include "AutoSocket.h"
|
|
||||||
|
|
||||||
#include <boost/bind.hpp>
|
|
||||||
|
|
||||||
void AutoSocket::handle_autodetect(const error_code& ec)
|
|
||||||
{
|
|
||||||
if (ec)
|
|
||||||
{
|
|
||||||
if (mCallback)
|
|
||||||
mCallback(ec);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
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))
|
|
||||||
{ // non-SSL
|
|
||||||
mSecure = false;
|
|
||||||
if (mCallback)
|
|
||||||
mCallback(ec);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{ // ssl
|
|
||||||
mSecure = true;
|
|
||||||
SSLSocket().async_handshake(ssl_socket::server, mCallback);
|
|
||||||
mCallback = callback();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// vim:ts=4
|
|
||||||
@@ -5,9 +5,14 @@
|
|||||||
|
|
||||||
#include <boost/function.hpp>
|
#include <boost/function.hpp>
|
||||||
#include <boost/bind.hpp>
|
#include <boost/bind.hpp>
|
||||||
|
#include <boost/smart_ptr.hpp>
|
||||||
|
#include <boost/make_shared.hpp>
|
||||||
#include <boost/asio.hpp>
|
#include <boost/asio.hpp>
|
||||||
#include <boost/asio/ssl.hpp>
|
#include <boost/asio/ssl.hpp>
|
||||||
|
|
||||||
|
#include "Log.h"
|
||||||
|
extern LogPartition AutoSocketPartition;
|
||||||
|
|
||||||
// Socket wrapper that supports both SSL and non-SSL connections.
|
// Socket wrapper that supports both SSL and non-SSL connections.
|
||||||
// Generally, handle it as you would an SSL connection.
|
// Generally, handle it as you would an SSL connection.
|
||||||
// To force a non-SSL connection, just don't call async_handshake.
|
// To force a non-SSL connection, just don't call async_handshake.
|
||||||
@@ -20,78 +25,140 @@ class AutoSocket
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
typedef bassl::stream<basio::ip::tcp::socket> ssl_socket;
|
typedef bassl::stream<basio::ip::tcp::socket> ssl_socket;
|
||||||
|
typedef boost::shared_ptr<ssl_socket> socket_ptr;
|
||||||
typedef ssl_socket::next_layer_type plain_socket;
|
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::system::error_code error_code;
|
||||||
typedef boost::function<void(error_code)> callback;
|
typedef boost::function<void(error_code)> callback;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
ssl_socket mSocket;
|
socket_ptr mSocket;
|
||||||
bool mSecure;
|
bool mSecure;
|
||||||
callback mCallback;
|
|
||||||
|
|
||||||
std::vector<char> mBuffer;
|
std::vector<char> mBuffer;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AutoSocket(basio::io_service& s, bassl::context& c) : mSocket(s, c), mSecure(false), mBuffer(4) { ; }
|
AutoSocket(basio::io_service& s, bassl::context& c) : mSecure(false), mBuffer(4)
|
||||||
|
{
|
||||||
|
mSocket = boost::make_shared<ssl_socket>(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<ssl_socket>(boost::ref(s), boost::ref(c));
|
||||||
|
}
|
||||||
|
|
||||||
bool isSecure() { return mSecure; }
|
bool isSecure() { return mSecure; }
|
||||||
ssl_socket& SSLSocket() { return mSocket; }
|
ssl_socket& SSLSocket() { return *mSocket; }
|
||||||
plain_socket& PlainSocket() { return mSocket.next_layer(); }
|
plain_socket& PlainSocket() { return mSocket->next_layer(); }
|
||||||
|
void setSSLOnly() { mSecure = true;}
|
||||||
|
void setPlainOnly() { mBuffer.clear(); }
|
||||||
|
|
||||||
void setSSLOnly() { mBuffer.clear(); }
|
lowest_layer_type& lowest_layer() { return mSocket->lowest_layer(); }
|
||||||
|
|
||||||
void async_handshake(ssl_socket::handshake_type type, callback cbFunc)
|
void swap(AutoSocket& s)
|
||||||
{
|
{
|
||||||
mSecure = true;
|
mBuffer.swap(s.mBuffer);
|
||||||
if ((type == ssl_socket::client) || (mBuffer.empty()))
|
mSocket.swap(s.mSocket);
|
||||||
SSLSocket().async_handshake(type, cbFunc);
|
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
|
else
|
||||||
{
|
{ // autodetect
|
||||||
mCallback = cbFunc;
|
mSocket->next_layer().async_receive(basio::buffer(mBuffer), basio::socket_base::message_peek,
|
||||||
PlainSocket().async_receive(basio::buffer(mBuffer), basio::socket_base::message_peek,
|
boost::bind(&AutoSocket::handle_autodetect, this, cbFunc, basio::placeholders::error));
|
||||||
boost::bind(&AutoSocket::handle_autodetect, this, basio::placeholders::error));
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename StreamType> StreamType& getSocket()
|
|
||||||
{
|
|
||||||
if (isSecure())
|
|
||||||
return SSLSocket();
|
|
||||||
if (!isSecure())
|
|
||||||
return PlainSocket();
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename ShutdownHandler> void async_shutdown(ShutdownHandler handler)
|
template <typename ShutdownHandler> void async_shutdown(ShutdownHandler handler)
|
||||||
{
|
{
|
||||||
if (isSecure())
|
if (isSecure())
|
||||||
SSLSocket().async_shutdown(handler);
|
mSocket->async_shutdown(handler);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
PlainSocket().shutdown(plain_socket::shutdown_both);
|
lowest_layer().shutdown(plain_socket::shutdown_both);
|
||||||
if (handler)
|
mSocket->get_io_service().post(boost::bind(handler, error_code()));
|
||||||
mSocket.get_io_service().post(handler);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Seq, typename Handler> void async_read_some(const Seq& buffers, Handler handler)
|
template <typename Seq, typename Handler> void async_read_some(const Seq& buffers, Handler handler)
|
||||||
{
|
{
|
||||||
if (isSecure())
|
if (isSecure())
|
||||||
SSLSocket().async_read_some(buffers, handler);
|
mSocket->async_read_some(buffers, handler);
|
||||||
else
|
else
|
||||||
PlainSocket().async_read_some(buffers, handler);
|
PlainSocket().async_read_some(buffers, handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename Buf, typename Handler> 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 <typename Buf, typename Condition, typename Handler>
|
||||||
|
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 <typename Buf, typename Handler> 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 <typename Seq, typename Handler> void async_write_some(const Seq& buffers, Handler handler)
|
template <typename Seq, typename Handler> void async_write_some(const Seq& buffers, Handler handler)
|
||||||
{
|
{
|
||||||
if (isSecure())
|
if (isSecure())
|
||||||
SSLSocket().async_write_some(buffers, handler);
|
mSocket->async_write_some(buffers, handler);
|
||||||
else
|
else
|
||||||
PlainSocket().async_write_some(buffers, handler);
|
PlainSocket().async_write_some(buffers, handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void handle_autodetect(const error_code&);
|
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
|
#endif
|
||||||
|
|||||||
@@ -178,8 +178,8 @@ Config::Config()
|
|||||||
RPC_PORT = 5001;
|
RPC_PORT = 5001;
|
||||||
WEBSOCKET_PORT = SYSTEM_WEBSOCKET_PORT;
|
WEBSOCKET_PORT = SYSTEM_WEBSOCKET_PORT;
|
||||||
WEBSOCKET_PUBLIC_PORT = SYSTEM_WEBSOCKET_PUBLIC_PORT;
|
WEBSOCKET_PUBLIC_PORT = SYSTEM_WEBSOCKET_PUBLIC_PORT;
|
||||||
WEBSOCKET_PUBLIC_SECURE = true;
|
WEBSOCKET_PUBLIC_SECURE = 1;
|
||||||
WEBSOCKET_SECURE = false;
|
WEBSOCKET_SECURE = 0;
|
||||||
NUMBER_CONNECTIONS = 30;
|
NUMBER_CONNECTIONS = 30;
|
||||||
|
|
||||||
// a new ledger every minute
|
// a new ledger every minute
|
||||||
@@ -340,10 +340,10 @@ void Config::load()
|
|||||||
WEBSOCKET_PUBLIC_PORT = boost::lexical_cast<int>(strTemp);
|
WEBSOCKET_PUBLIC_PORT = boost::lexical_cast<int>(strTemp);
|
||||||
|
|
||||||
if (sectionSingleB(secConfig, SECTION_WEBSOCKET_SECURE, strTemp))
|
if (sectionSingleB(secConfig, SECTION_WEBSOCKET_SECURE, strTemp))
|
||||||
WEBSOCKET_SECURE = boost::lexical_cast<bool>(strTemp);
|
WEBSOCKET_SECURE = boost::lexical_cast<int>(strTemp);
|
||||||
|
|
||||||
if (sectionSingleB(secConfig, SECTION_WEBSOCKET_PUBLIC_SECURE, strTemp))
|
if (sectionSingleB(secConfig, SECTION_WEBSOCKET_PUBLIC_SECURE, strTemp))
|
||||||
WEBSOCKET_PUBLIC_SECURE = boost::lexical_cast<bool>(strTemp);
|
WEBSOCKET_PUBLIC_SECURE = boost::lexical_cast<int>(strTemp);
|
||||||
|
|
||||||
sectionSingleB(secConfig, SECTION_WEBSOCKET_SSL_CERT, WEBSOCKET_SSL_CERT);
|
sectionSingleB(secConfig, SECTION_WEBSOCKET_SSL_CERT, WEBSOCKET_SSL_CERT);
|
||||||
sectionSingleB(secConfig, SECTION_WEBSOCKET_SSL_CHAIN, WEBSOCKET_SSL_CHAIN);
|
sectionSingleB(secConfig, SECTION_WEBSOCKET_SSL_CHAIN, WEBSOCKET_SSL_CHAIN);
|
||||||
|
|||||||
@@ -101,11 +101,12 @@ public:
|
|||||||
// Websocket networking parameters
|
// Websocket networking parameters
|
||||||
std::string WEBSOCKET_PUBLIC_IP; // XXX Going away. Merge with the inbound peer connction.
|
std::string WEBSOCKET_PUBLIC_IP; // XXX Going away. Merge with the inbound peer connction.
|
||||||
int WEBSOCKET_PUBLIC_PORT;
|
int WEBSOCKET_PUBLIC_PORT;
|
||||||
bool WEBSOCKET_PUBLIC_SECURE;
|
int WEBSOCKET_PUBLIC_SECURE;
|
||||||
|
|
||||||
std::string WEBSOCKET_IP;
|
std::string WEBSOCKET_IP;
|
||||||
int WEBSOCKET_PORT;
|
int WEBSOCKET_PORT;
|
||||||
bool WEBSOCKET_SECURE;
|
int WEBSOCKET_SECURE;
|
||||||
|
|
||||||
std::string WEBSOCKET_SSL_CERT;
|
std::string WEBSOCKET_SSL_CERT;
|
||||||
std::string WEBSOCKET_SSL_CHAIN;
|
std::string WEBSOCKET_SSL_CHAIN;
|
||||||
std::string WEBSOCKET_SSL_KEY;
|
std::string WEBSOCKET_SSL_KEY;
|
||||||
|
|||||||
@@ -100,9 +100,10 @@ std::vector<RippleAddress> SerializedTransaction::getMentionedAccounts() const
|
|||||||
if (!found)
|
if (!found)
|
||||||
accounts.push_back(na);
|
accounts.push_back(na);
|
||||||
}
|
}
|
||||||
if (it.getFName() == sfLimitAmount)
|
const STAmount* sam = dynamic_cast<const STAmount*>(&it);
|
||||||
|
if (sam)
|
||||||
{
|
{
|
||||||
uint160 issuer = dynamic_cast<const STAmount*>(&it)->getIssuer();
|
uint160 issuer = sam->getIssuer();
|
||||||
if (issuer.isNonZero())
|
if (issuer.isNonZero())
|
||||||
{
|
{
|
||||||
RippleAddress na;
|
RippleAddress na;
|
||||||
|
|||||||
@@ -127,28 +127,23 @@ TER TransactionEngine::applyTransaction(const SerializedTransaction& txn, Transa
|
|||||||
uint32 t_seq = txn.getSequence();
|
uint32 t_seq = txn.getSequence();
|
||||||
uint32 a_seq = txnAcct->getFieldU32(sfSequence);
|
uint32 a_seq = txnAcct->getFieldU32(sfSequence);
|
||||||
|
|
||||||
if (t_seq != a_seq)
|
if (a_seq < t_seq)
|
||||||
{
|
terResult = terPRE_SEQ;
|
||||||
if (a_seq < t_seq)
|
else if (a_seq > t_seq)
|
||||||
terResult = terPRE_SEQ;
|
terResult = tefPAST_SEQ;
|
||||||
else
|
|
||||||
terResult = tefPAST_SEQ;
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
STAmount fee = txn.getTransactionFee();
|
STAmount fee = txn.getTransactionFee();
|
||||||
STAmount balance = txnAcct->getFieldAmount(sfBalance);
|
STAmount balance = txnAcct->getFieldAmount(sfBalance);
|
||||||
|
|
||||||
if (balance < fee)
|
if (balance < fee)
|
||||||
{
|
|
||||||
terResult = terINSUF_FEE_B;
|
terResult = terINSUF_FEE_B;
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
txnAcct->setFieldAmount(sfBalance, balance - fee);
|
txnAcct->setFieldAmount(sfBalance, balance - fee);
|
||||||
txnAcct->setFieldU32(sfSequence, t_seq + 1);
|
txnAcct->setFieldU32(sfSequence, t_seq + 1);
|
||||||
didApply = true;
|
|
||||||
entryModify(txnAcct);
|
entryModify(txnAcct);
|
||||||
|
didApply = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,7 +81,8 @@ std::vector<RippleAddress> TransactionMetaSet::getAffectedAccounts()
|
|||||||
const STAccount* sa = dynamic_cast<const STAccount*>(&field);
|
const STAccount* sa = dynamic_cast<const STAccount*>(&field);
|
||||||
if (sa)
|
if (sa)
|
||||||
addIfUnique(accounts, sa->getValueNCA());
|
addIfUnique(accounts, sa->getValueNCA());
|
||||||
else if ((field.getFName() == sfLowLimit) || (field.getFName() == sfHighLimit))
|
else if ((field.getFName() == sfLowLimit) || (field.getFName() == sfHighLimit) ||
|
||||||
|
(field.getFName() == sfTakerPays) || (field.getFName() == sfTakerGets))
|
||||||
{
|
{
|
||||||
const STAmount* lim = dynamic_cast<const STAmount*>(&field);
|
const STAmount* lim = dynamic_cast<const STAmount*>(&field);
|
||||||
if (lim != NULL)
|
if (lim != NULL)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
#include "../websocketpp/src/sockets/tls.hpp"
|
#include "../websocketpp/src/sockets/autotls.hpp"
|
||||||
#include "../websocketpp/src/websocketpp.hpp"
|
#include "../websocketpp/src/websocketpp.hpp"
|
||||||
|
|
||||||
#include "../json/value.h"
|
#include "../json/value.h"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
#include "Log.h"
|
#include "Log.h"
|
||||||
|
|
||||||
#define WSDOOR_CPP
|
#define WSDOOR_CPP
|
||||||
#include "../websocketpp/src/sockets/tls.hpp"
|
#include "../websocketpp/src/sockets/autotls.hpp"
|
||||||
#include "../websocketpp/src/websocketpp.hpp"
|
#include "../websocketpp/src/websocketpp.hpp"
|
||||||
|
|
||||||
SETUP_LOG();
|
SETUP_LOG();
|
||||||
@@ -59,80 +59,40 @@ void WSDoor::startListening()
|
|||||||
|
|
||||||
SSL_CTX_set_tmp_dh_callback(mCtx->native_handle(), handleTmpDh);
|
SSL_CTX_set_tmp_dh_callback(mCtx->native_handle(), handleTmpDh);
|
||||||
|
|
||||||
if (mPublic ? theConfig.WEBSOCKET_PUBLIC_SECURE : theConfig.WEBSOCKET_SECURE)
|
// Construct a single handler for all requests.
|
||||||
|
websocketpp::server_autotls::handler::ptr handler(new WSServerHandler<websocketpp::server_autotls>(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.
|
mSEndpoint->listen(
|
||||||
websocketpp::server_tls::handler::ptr handler(new WSServerHandler<websocketpp::server_tls>(mCtx, mPublic));
|
boost::asio::ip::tcp::endpoint(
|
||||||
|
boost::asio::ip::address().from_string(mIp), mPort));
|
||||||
// Construct a websocket server.
|
}
|
||||||
mSEndpoint = new websocketpp::server_tls(handler);
|
catch (websocketpp::exception& e)
|
||||||
|
{
|
||||||
// mEndpoint->alog().unset_level(websocketpp::log::alevel::ALL);
|
cLog(lsWARNING) << "websocketpp exception: " << e.what();
|
||||||
// mEndpoint->elog().unset_level(websocketpp::log::elevel::ALL);
|
while (1) // temporary workaround for websocketpp throwing exceptions on access/close races
|
||||||
|
{ // https://github.com/zaphoyd/websocketpp/issues/98
|
||||||
// Call the main-event-loop of the websocket server.
|
try
|
||||||
try
|
{
|
||||||
{
|
mSEndpoint->get_io_service().run();
|
||||||
mSEndpoint->listen(
|
break;
|
||||||
boost::asio::ip::tcp::endpoint(
|
}
|
||||||
boost::asio::ip::address().from_string(mIp), mPort));
|
catch (websocketpp::exception& e)
|
||||||
}
|
{
|
||||||
catch (websocketpp::exception& e)
|
cLog(lsWARNING) << "websocketpp exception: " << e.what();
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
// Construct a single handler for all requests.
|
|
||||||
websocketpp::server::handler::ptr handler(new WSServerHandler<websocketpp::server>(mCtx, mPublic));
|
|
||||||
|
|
||||||
// Construct a websocket server.
|
delete mSEndpoint;
|
||||||
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)
|
|
||||||
{
|
|
||||||
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
|
|
||||||
{
|
|
||||||
mEndpoint->get_io_service().run();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
catch (websocketpp::exception& e)
|
|
||||||
{
|
|
||||||
cLog(lsWARNING) << "websocketpp exception: " << e.what();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
delete mEndpoint;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
WSDoor* WSDoor::createWSDoor(const std::string& strIp, const int iPort, bool bPublic)
|
WSDoor* WSDoor::createWSDoor(const std::string& strIp, const int iPort, bool bPublic)
|
||||||
@@ -154,8 +114,6 @@ void WSDoor::stop()
|
|||||||
{
|
{
|
||||||
if (mThread)
|
if (mThread)
|
||||||
{
|
{
|
||||||
if (mEndpoint)
|
|
||||||
mEndpoint->stop();
|
|
||||||
if (mSEndpoint)
|
if (mSEndpoint)
|
||||||
mSEndpoint->stop();
|
mSEndpoint->stop();
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
namespace websocketpp
|
namespace websocketpp
|
||||||
{
|
{
|
||||||
class server;
|
class server;
|
||||||
class server_tls;
|
class server_autotls;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -20,19 +20,18 @@ namespace websocketpp
|
|||||||
class WSDoor
|
class WSDoor
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
websocketpp::server* mEndpoint;
|
websocketpp::server_autotls* mSEndpoint;
|
||||||
websocketpp::server_tls* mSEndpoint;
|
|
||||||
|
|
||||||
boost::thread* mThread;
|
boost::thread* mThread;
|
||||||
bool mPublic;
|
bool mPublic;
|
||||||
std::string mIp;
|
std::string mIp;
|
||||||
int mPort;
|
int mPort;
|
||||||
|
|
||||||
void startListening();
|
void startListening();
|
||||||
|
|
||||||
public:
|
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();
|
void stop();
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ protected:
|
|||||||
public:
|
public:
|
||||||
WSServerHandler(boost::shared_ptr<boost::asio::ssl::context> spCtx, bool bPublic) : mCtx(spCtx), mPublic(bPublic)
|
WSServerHandler(boost::shared_ptr<boost::asio::ssl::context> spCtx, bool bPublic) : mCtx(spCtx), mPublic(bPublic)
|
||||||
{
|
{
|
||||||
if (theConfig.WEBSOCKET_SECURE)
|
if (theConfig.WEBSOCKET_SECURE != 0)
|
||||||
{
|
{
|
||||||
initSSLContext(*mCtx, theConfig.WEBSOCKET_SSL_KEY,
|
initSSLContext(*mCtx, theConfig.WEBSOCKET_SSL_KEY,
|
||||||
theConfig.WEBSOCKET_SSL_CERT, theConfig.WEBSOCKET_SSL_CHAIN);
|
theConfig.WEBSOCKET_SSL_CERT, theConfig.WEBSOCKET_SSL_CHAIN);
|
||||||
|
|||||||
@@ -898,20 +898,39 @@ public:
|
|||||||
!m_protocol_error)
|
!m_protocol_error)
|
||||||
{
|
{
|
||||||
// TODO: read timeout timer?
|
// TODO: read timeout timer?
|
||||||
|
|
||||||
boost::asio::async_read(
|
if (socket_type::get_socket().isSecure())
|
||||||
socket_type::get_socket(),
|
{
|
||||||
m_buf,
|
boost::asio::async_read(
|
||||||
boost::asio::transfer_at_least(std::min(
|
socket_type::get_socket().SSLSocket(),
|
||||||
m_read_threshold,
|
m_buf,
|
||||||
static_cast<size_t>(m_processor->get_bytes_needed())
|
boost::asio::transfer_at_least(std::min(
|
||||||
)),
|
m_read_threshold,
|
||||||
m_strand.wrap(boost::bind(
|
static_cast<size_t>(m_processor->get_bytes_needed())
|
||||||
&type::handle_read_frame,
|
)),
|
||||||
type::shared_from_this(),
|
m_strand.wrap(boost::bind(
|
||||||
boost::asio::placeholders::error
|
&type::handle_read_frame,
|
||||||
))
|
type::shared_from_this(),
|
||||||
);
|
boost::asio::placeholders::error
|
||||||
|
))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
boost::asio::async_read(
|
||||||
|
socket_type::get_socket().PlainSocket(),
|
||||||
|
m_buf,
|
||||||
|
boost::asio::transfer_at_least(std::min(
|
||||||
|
m_read_threshold,
|
||||||
|
static_cast<size_t>(m_processor->get_bytes_needed())
|
||||||
|
)),
|
||||||
|
m_strand.wrap(boost::bind(
|
||||||
|
&type::handle_read_frame,
|
||||||
|
type::shared_from_this(),
|
||||||
|
boost::asio::placeholders::error
|
||||||
|
))
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public:
|
public:
|
||||||
@@ -1209,15 +1228,31 @@ public:
|
|||||||
|
|
||||||
//m_endpoint.alog().at(log::alevel::DEVEL) << "write header: " << zsutil::to_hex(m_write_queue.front()->get_header()) << log::endl;
|
//m_endpoint.alog().at(log::alevel::DEVEL) << "write header: " << zsutil::to_hex(m_write_queue.front()->get_header()) << log::endl;
|
||||||
|
|
||||||
boost::asio::async_write(
|
if (socket_type::get_socket().isSecure())
|
||||||
socket_type::get_socket(),
|
{
|
||||||
m_write_buf,
|
boost::asio::async_write(
|
||||||
m_strand.wrap(boost::bind(
|
socket_type::get_socket().SSLSocket(),
|
||||||
&type::handle_write,
|
m_write_buf,
|
||||||
type::shared_from_this(),
|
m_strand.wrap(boost::bind(
|
||||||
boost::asio::placeholders::error
|
&type::handle_write,
|
||||||
))
|
type::shared_from_this(),
|
||||||
);
|
boost::asio::placeholders::error
|
||||||
|
))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
boost::asio::async_write(
|
||||||
|
socket_type::get_socket().PlainSocket(),
|
||||||
|
m_write_buf,
|
||||||
|
m_strand.wrap(boost::bind(
|
||||||
|
&type::handle_write,
|
||||||
|
type::shared_from_this(),
|
||||||
|
boost::asio::placeholders::error
|
||||||
|
))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// if we are in an inturrupted state and had nothing else to write
|
// if we are in an inturrupted state and had nothing else to write
|
||||||
// it is safe to terminate the connection.
|
// it is safe to terminate the connection.
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
#define WEBSOCKETPP_ENDPOINT_HPP
|
#define WEBSOCKETPP_ENDPOINT_HPP
|
||||||
|
|
||||||
#include "connection.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 "logger/logger.hpp"
|
||||||
|
|
||||||
#include <boost/asio.hpp>
|
#include <boost/asio.hpp>
|
||||||
@@ -74,7 +74,7 @@ protected:
|
|||||||
*/
|
*/
|
||||||
template <
|
template <
|
||||||
template <class> class role,
|
template <class> class role,
|
||||||
template <class> class socket = socket::plain,
|
template <class> class socket = socket::autotls,
|
||||||
template <class> class logger = log::logger>
|
template <class> class logger = log::logger>
|
||||||
class endpoint
|
class endpoint
|
||||||
: public endpoint_base,
|
: public endpoint_base,
|
||||||
|
|||||||
@@ -57,26 +57,30 @@ typedef boost::asio::buffers_iterator<boost::asio::streambuf::const_buffers_type
|
|||||||
|
|
||||||
static std::pair<bufIterator, bool> match_header(bufIterator begin, bufIterator end)
|
static std::pair<bufIterator, bool> 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 = "<policy-file-request/>";
|
||||||
|
|
||||||
// Do we have a complete HTTP request
|
// Do we have a complete HTTP request
|
||||||
const std::string header_match = "\r\n\r\n";
|
|
||||||
bufIterator it = std::search(begin, end, header_match.begin(), header_match.end());
|
bufIterator it = std::search(begin, end, header_match.begin(), header_match.end());
|
||||||
if (it != end)
|
if (it != end)
|
||||||
return std::make_pair(it, true);
|
return std::make_pair(it + header_match.size(), true);
|
||||||
|
it = std::search(begin, end, alt_header_match.begin(), alt_header_match.end());
|
||||||
|
return std::make_pair(it + alt_header_match.size(), true);
|
||||||
|
|
||||||
// If we don't have a flash policy request, we're done
|
// If we don't have a flash policy request, we're done
|
||||||
const std::string flash_match = "<policy-file-request/>";
|
|
||||||
it = std::search(begin, end, flash_match.begin(), flash_match.end());
|
it = std::search(begin, end, flash_match.begin(), flash_match.end());
|
||||||
if (it == end)
|
if (it == end) // No match
|
||||||
return std::make_pair(end, false);
|
return std::make_pair(end, false);
|
||||||
|
|
||||||
// If we have a line ending before the flash policy request, treat as http
|
// If we have a line ending before the flash policy request, treat as http
|
||||||
const std::string eol_match = "\r\n";
|
|
||||||
bufIterator it2 = std::search(begin, end, eol_match.begin(), eol_match.end());
|
bufIterator it2 = std::search(begin, end, eol_match.begin(), eol_match.end());
|
||||||
if ((it2 != end) || (it < it2))
|
if ((it2 != end) || (it < it2))
|
||||||
return std::make_pair(end, false);
|
return std::make_pair(end, false);
|
||||||
|
|
||||||
// Treat as flash policy request
|
// Treat as flash policy request
|
||||||
return std::make_pair(it, true);
|
return std::make_pair(it + flash_match.size(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Forward declarations
|
// Forward declarations
|
||||||
@@ -539,18 +543,37 @@ void server<endpoint>::connection<connection_type>::async_init() {
|
|||||||
// TODO: make this value configurable
|
// TODO: make this value configurable
|
||||||
m_connection.register_timeout(5000,fail::status::TIMEOUT_WS,
|
m_connection.register_timeout(5000,fail::status::TIMEOUT_WS,
|
||||||
"Timeout on WebSocket handshake");
|
"Timeout on WebSocket handshake");
|
||||||
|
|
||||||
boost::asio::async_read_until(
|
if (m_connection.get_socket().isSecure())
|
||||||
m_connection.get_socket(),
|
{
|
||||||
m_connection.buffer(),
|
boost::asio::async_read_until(
|
||||||
match_header,
|
m_connection.get_socket().SSLSocket(),
|
||||||
m_connection.get_strand().wrap(boost::bind(
|
m_connection.buffer(),
|
||||||
&type::handle_read_request,
|
// match_header,
|
||||||
m_connection.shared_from_this(),
|
"\r\n\r\n",
|
||||||
boost::asio::placeholders::error,
|
m_connection.get_strand().wrap(boost::bind(
|
||||||
boost::asio::placeholders::bytes_transferred
|
&type::handle_read_request,
|
||||||
))
|
m_connection.shared_from_this(),
|
||||||
);
|
boost::asio::placeholders::error,
|
||||||
|
boost::asio::placeholders::bytes_transferred
|
||||||
|
))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
boost::asio::async_read_until(
|
||||||
|
m_connection.get_socket().PlainSocket(),
|
||||||
|
m_connection.buffer(),
|
||||||
|
// match_header,
|
||||||
|
"\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
|
||||||
|
))
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// processes the response from an async read for an HTTP header
|
/// processes the response from an async read for an HTTP header
|
||||||
@@ -699,9 +722,9 @@ void server<endpoint>::connection<connection_type>::handle_read_request(
|
|||||||
{
|
{
|
||||||
// TODO: this makes the assumption that WS and HTTP
|
// TODO: this makes the assumption that WS and HTTP
|
||||||
// default ports are the same.
|
// 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 {
|
} 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(0,last_colon),
|
||||||
h.substr(last_colon+1),
|
h.substr(last_colon+1),
|
||||||
m_request.uri()));
|
m_request.uri()));
|
||||||
@@ -821,17 +844,16 @@ void server<endpoint>::connection<connection_type>::write_response() {
|
|||||||
shared_const_buffer buffer(raw);
|
shared_const_buffer buffer(raw);
|
||||||
|
|
||||||
m_endpoint.m_alog->at(log::alevel::DEBUG_HANDSHAKE) << raw << log::endl;
|
m_endpoint.m_alog->at(log::alevel::DEBUG_HANDSHAKE) << raw << log::endl;
|
||||||
|
|
||||||
boost::asio::async_write(
|
m_connection.get_socket().async_write(
|
||||||
m_connection.get_socket(),
|
//boost::asio::buffer(raw),
|
||||||
//boost::asio::buffer(raw),
|
buffer,
|
||||||
buffer,
|
boost::bind(
|
||||||
boost::bind(
|
&type::handle_write_response,
|
||||||
&type::handle_write_response,
|
m_connection.shared_from_this(),
|
||||||
m_connection.shared_from_this(),
|
boost::asio::placeholders::error
|
||||||
boost::asio::placeholders::error
|
)
|
||||||
)
|
);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class endpoint>
|
template <class endpoint>
|
||||||
|
|||||||
@@ -25,6 +25,8 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#error Use Auto TLS only
|
||||||
|
|
||||||
#ifndef WEBSOCKETPP_SOCKET_PLAIN_HPP
|
#ifndef WEBSOCKETPP_SOCKET_PLAIN_HPP
|
||||||
#define WEBSOCKETPP_SOCKET_PLAIN_HPP
|
#define WEBSOCKETPP_SOCKET_PLAIN_HPP
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,8 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#error Use auto TLS only
|
||||||
|
|
||||||
#ifndef WEBSOCKETPP_SOCKET_TLS_HPP
|
#ifndef WEBSOCKETPP_SOCKET_TLS_HPP
|
||||||
#define WEBSOCKETPP_SOCKET_TLS_HPP
|
#define WEBSOCKETPP_SOCKET_TLS_HPP
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,10 @@ namespace websocketpp {
|
|||||||
typedef websocketpp::endpoint<websocketpp::role::server,
|
typedef websocketpp::endpoint<websocketpp::role::server,
|
||||||
websocketpp::socket::tls> server_tls;
|
websocketpp::socket::tls> server_tls;
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef WEBSOCKETPP_SOCKET_AUTOTLS_HPP
|
||||||
|
typedef websocketpp::endpoint<websocketpp::role::server,
|
||||||
|
websocketpp::socket::autotls> server_autotls;
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
521
src/js/remote.js
521
src/js/remote.js
@@ -16,9 +16,10 @@
|
|||||||
|
|
||||||
// npm
|
// npm
|
||||||
var EventEmitter = require('events').EventEmitter;
|
var EventEmitter = require('events').EventEmitter;
|
||||||
var Amount = require('./amount.js').Amount;
|
var Amount = require('./amount').Amount;
|
||||||
var Currency = require('./amount.js').Currency;
|
var Currency = require('./amount').Currency;
|
||||||
var UInt160 = require('./amount.js').UInt160;
|
var UInt160 = require('./amount').UInt160;
|
||||||
|
var Transaction = require('./transaction').Transaction;
|
||||||
|
|
||||||
var utils = require('./utils');
|
var utils = require('./utils');
|
||||||
|
|
||||||
@@ -281,25 +282,6 @@ Remote.online_states = [
|
|||||||
'full'
|
'full'
|
||||||
];
|
];
|
||||||
|
|
||||||
Remote.flags = {
|
|
||||||
'OfferCreate' : {
|
|
||||||
'Passive' : 0x00010000,
|
|
||||||
},
|
|
||||||
|
|
||||||
'Payment' : {
|
|
||||||
'NoRippleDirect' : 0x00010000,
|
|
||||||
'PartialPayment' : 0x00020000,
|
|
||||||
'LimitQuality' : 0x00040000,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// 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.
|
// Inform remote that the remote server is not comming back.
|
||||||
Remote.prototype.server_fatal = function () {
|
Remote.prototype.server_fatal = function () {
|
||||||
this._server_fatal = true;
|
this._server_fatal = true;
|
||||||
@@ -1173,501 +1155,6 @@ Remote.prototype.transaction = function () {
|
|||||||
return new Transaction(this);
|
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.
|
|
||||||
// \- '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 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,
|
|
||||||
'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 = 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 = 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()
|
|
||||||
// .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.config = {};
|
exports.config = {};
|
||||||
exports.Remote = Remote;
|
exports.Remote = Remote;
|
||||||
|
|
||||||
|
|||||||
522
src/js/transaction.js
Normal file
522
src/js/transaction.js
Normal file
@@ -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
|
||||||
@@ -1,15 +1,16 @@
|
|||||||
|
|
||||||
var async = require("async");
|
var async = require("async");
|
||||||
var buster = require("buster");
|
var buster = require("buster");
|
||||||
|
|
||||||
var Amount = require("../src/js/amount.js").Amount;
|
var Amount = require("../src/js/amount").Amount;
|
||||||
var Remote = require("../src/js/remote.js").Remote;
|
var Remote = require("../src/js/remote").Remote;
|
||||||
var Server = require("./server.js").Server;
|
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/amount").config = require("./config");
|
||||||
require("../src/js/remote.js").config = require("./config.js");
|
require("../src/js/remote").config = require("./config");
|
||||||
|
|
||||||
buster.testRunner.timeout = 5000;
|
buster.testRunner.timeout = 5000;
|
||||||
|
|
||||||
@@ -716,7 +717,7 @@ buster.testCase("Offer tests", {
|
|||||||
|
|
||||||
testutils.verify_balances(self.remote,
|
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",
|
"bob" : "100/USD/mtgox",
|
||||||
},
|
},
|
||||||
callback);
|
callback);
|
||||||
@@ -879,7 +880,7 @@ buster.testCase("Offer tests", {
|
|||||||
|
|
||||||
testutils.verify_balances(self.remote,
|
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",
|
"bob" : "40/USD/mtgox",
|
||||||
},
|
},
|
||||||
callback);
|
callback);
|
||||||
@@ -921,7 +922,7 @@ buster.testCase("Offer tests", {
|
|||||||
|
|
||||||
testutils.verify_balances(self.remote,
|
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",
|
"bob" : "100/USD/mtgox",
|
||||||
},
|
},
|
||||||
callback);
|
callback);
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
|
|
||||||
var async = require("async");
|
var async = require("async");
|
||||||
var buster = require("buster");
|
var buster = require("buster");
|
||||||
|
|
||||||
var Amount = require("../src/js/amount.js").Amount;
|
var Amount = require("../src/js/amount").Amount;
|
||||||
var Remote = require("../src/js/remote.js").Remote;
|
var Remote = require("../src/js/remote").Remote;
|
||||||
var Server = require("./server.js").Server;
|
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/amount").config = require("./config");
|
||||||
require("../src/js/remote.js").config = require("./config.js");
|
require("../src/js/remote").config = require("./config");
|
||||||
|
|
||||||
buster.testRunner.timeout = 5000;
|
buster.testRunner.timeout = 5000;
|
||||||
|
|
||||||
@@ -425,7 +426,7 @@ buster.testCase("Reserve", {
|
|||||||
|
|
||||||
testutils.verify_balances(self.remote,
|
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",
|
"bob" : "100/USD/mtgox",
|
||||||
},
|
},
|
||||||
callback);
|
callback);
|
||||||
@@ -588,7 +589,7 @@ buster.testCase("Reserve", {
|
|||||||
|
|
||||||
testutils.verify_balances(self.remote,
|
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",
|
"bob" : "40/USD/mtgox",
|
||||||
},
|
},
|
||||||
callback);
|
callback);
|
||||||
@@ -630,7 +631,7 @@ buster.testCase("Reserve", {
|
|||||||
|
|
||||||
testutils.verify_balances(self.remote,
|
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",
|
"bob" : "100/USD/mtgox",
|
||||||
},
|
},
|
||||||
callback);
|
callback);
|
||||||
|
|||||||
Reference in New Issue
Block a user