diff --git a/.gitignore b/.gitignore index 7557ba990016..b486dc631553 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +pay-core-team.sh *.sublime-project *.sublime-workspace todo.txt diff --git a/configure.ac b/configure.ac index 7b92cabf7808..79ad919575ee 100644 --- a/configure.ac +++ b/configure.ac @@ -1,8 +1,8 @@ dnl require autoconf 2.60 (AS_ECHO/AS_ECHO_N) AC_PREREQ([2.60]) define(_CLIENT_VERSION_MAJOR, 0) -define(_CLIENT_VERSION_MINOR, 12) -define(_CLIENT_VERSION_REVISION, 1) +define(_CLIENT_VERSION_MINOR, 13) +define(_CLIENT_VERSION_REVISION, 0) define(_CLIENT_VERSION_BUILD, 0) define(_CLIENT_VERSION_IS_RELEASE, true) define(_COPYRIGHT_YEAR, 2015) diff --git a/dash-docs/function-layout.txt b/dash-docs/function-layout.txt new file mode 100644 index 000000000000..cd7d6e4616a8 --- /dev/null +++ b/dash-docs/function-layout.txt @@ -0,0 +1,22 @@ + +Dash2T + -- Websockets / ZeroMQ daemon + + Listens on port 10014 for websocks + Listens for ZeroMQ commands (relays them locally to DashD.rpcdapi) + +DashD + -- + + rpcdapi + dapi: Execute json command on behalf of a user (delivered locally from Dash2T) + + CDapi + ValidateCommand + ExecuteCommand + + EndUser + Load + Save + UpdateField + diff --git a/dash-docs/prototype-setup.txt b/dash-docs/prototype-setup.txt new file mode 100644 index 000000000000..4a9c91b217df --- /dev/null +++ b/dash-docs/prototype-setup.txt @@ -0,0 +1,13 @@ +Setting up the prototype (using mainnet atm): + + +1. Download and install dash (https://github.com/dashpay/dash) +2. Setup a data directory in ~/.dash-data +3. Create your user files. For example to create the user evan, write this file to ~/.dash-data/users/evan +file contents: {"name":"Evan Duffield","username":"evan","stars":5} +4. Download dash-2t, the tier-2 websockets proxy +5. Run the client with a command like this: +dash_t2 --datadir2="/Users/evan/.dash-data" --eventnotify="/Users/evan/Desktop/dash-2t/serve/event.py --event=%s" +6. Start dash-2t, "python app.py" +7. Download and run the electrum client at: https://github.com/evan82/electrum-dash +8. Start 2x versions of client with 2 different users : ./electrum-dash -w ~/.test-electrum2 --electrum_path=~/.electrum-test-2 \ No newline at end of file diff --git a/src/Makefile.am b/src/Makefile.am index 92eaca819a13..209c7c808cba 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -94,9 +94,12 @@ BITCOIN_CORE_H = \ crypter.h \ darksend.h \ darksend-relay.h \ + easywsclient.h \ db.h \ eccryptoverify.h \ ecwrapper.h \ + evo/file.h \ + evo/dapi.h \ hash.h \ init.h \ instantx.h \ @@ -178,6 +181,11 @@ libbitcoin_server_a_SOURCES = \ bloom.cpp \ chain.cpp \ checkpoints.cpp \ + easywsclient.cpp \ + evo/file.cpp \ + evo/dapi.cpp \ + json/json_spirit_reader.cpp \ + json/json_spirit_writer.cpp \ init.cpp \ leveldbwrapper.cpp \ main.cpp \ @@ -188,6 +196,7 @@ libbitcoin_server_a_SOURCES = \ pow.cpp \ rest.cpp \ rpcblockchain.cpp \ + rpcdapi.cpp \ rpcmasternode.cpp \ rpcmasternode-budget.cpp \ rpcmining.cpp \ @@ -207,10 +216,12 @@ libbitcoin_server_a_SOURCES = \ libbitcoin_wallet_a_CPPFLAGS = $(BITCOIN_INCLUDES) libbitcoin_wallet_a_SOURCES = \ activemasternode.cpp \ - darksend.cpp \ - darksend-relay.cpp \ db.cpp \ crypter.cpp \ + darksend.cpp \ + darksend-relay.cpp \ + evo/file.cpp \ + evo/dapi.cpp \ instantx.cpp \ masternode.cpp \ masternode-budget.cpp \ @@ -287,6 +298,8 @@ libbitcoin_common_a_SOURCES = \ chainparams.cpp \ coins.cpp \ compressor.cpp \ + evo/file.cpp \ + evo/dapi.cpp \ primitives/block.cpp \ primitives/transaction.cpp \ core_read.cpp \ diff --git a/src/clientversion.h b/src/clientversion.h index 7adae4d9a672..94057848e46d 100644 --- a/src/clientversion.h +++ b/src/clientversion.h @@ -15,8 +15,8 @@ //! These need to be macros, as clientversion.cpp's and dash*-res.rc's voodoo requires it #define CLIENT_VERSION_MAJOR 0 -#define CLIENT_VERSION_MINOR 12 -#define CLIENT_VERSION_REVISION 1 +#define CLIENT_VERSION_MINOR 13 +#define CLIENT_VERSION_REVISION 0 #define CLIENT_VERSION_BUILD 0 //! Set to true for release, false for prerelease or test build diff --git a/src/easywsclient.cpp b/src/easywsclient.cpp new file mode 100644 index 000000000000..49230f2429e5 --- /dev/null +++ b/src/easywsclient.cpp @@ -0,0 +1,527 @@ + +#ifdef _WIN32 + #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) + #define _CRT_SECURE_NO_WARNINGS // _CRT_SECURE_NO_WARNINGS for sscanf errors in MSVC2013 Express + #endif + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + #include + #include + #include + #pragma comment( lib, "ws2_32" ) + #include + #include + #include + #include + #include + #ifndef _SSIZE_T_DEFINED + typedef int ssize_t; + #define _SSIZE_T_DEFINED + #endif + #ifndef _SOCKET_T_DEFINED + typedef SOCKET socket_t; + #define _SOCKET_T_DEFINED + #endif + #ifndef snprintf + #define snprintf _snprintf_s + #endif + #if _MSC_VER >=1600 + // vs2010 or later + #include + #else + typedef __int8 int8_t; + typedef unsigned __int8 uint8_t; + typedef __int32 int32_t; + typedef unsigned __int32 uint32_t; + typedef __int64 int64_t; + typedef unsigned __int64 uint64_t; + #endif + #define socketerrno WSAGetLastError() + #define SOCKET_EAGAIN_EINPROGRESS WSAEINPROGRESS + #define SOCKET_EWOULDBLOCK WSAEWOULDBLOCK +#else + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #ifndef _SOCKET_T_DEFINED + typedef int socket_t; + #define _SOCKET_T_DEFINED + #endif + #ifndef INVALID_SOCKET + #define INVALID_SOCKET (-1) + #endif + #ifndef SOCKET_ERROR + #define SOCKET_ERROR (-1) + #endif + #define closesocket(s) ::close(s) + #include + #define socketerrno errno + #define SOCKET_EAGAIN_EINPROGRESS EAGAIN + #define SOCKET_EWOULDBLOCK EWOULDBLOCK +#endif + +#include +#include + +#include "easywsclient.h" + +using easywsclient::Callback_Imp; +using easywsclient::BytesCallback_Imp; + +namespace { // private module-only namespace + +socket_t hostname_connect(const std::string& hostname, int port) { + struct addrinfo hints; + struct addrinfo *result; + struct addrinfo *p; + int ret; + socket_t sockfd = INVALID_SOCKET; + char sport[16]; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + snprintf(sport, 16, "%d", port); + if ((ret = getaddrinfo(hostname.c_str(), sport, &hints, &result)) != 0) + { + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(ret)); + return 1; + } + for(p = result; p != NULL; p = p->ai_next) + { + sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol); + if (sockfd == INVALID_SOCKET) { continue; } + if (connect(sockfd, p->ai_addr, p->ai_addrlen) != SOCKET_ERROR) { + break; + } + closesocket(sockfd); + sockfd = INVALID_SOCKET; + } + freeaddrinfo(result); + return sockfd; +} + + +class _DummyWebSocket : public easywsclient::WebSocket +{ + public: + void poll(int timeout) { } + void send(const std::string& message) { } + void sendBinary(const std::string& message) { } + void sendBinary(const std::vector& message) { } + void sendPing() { } + void close() { } + readyStateValues getReadyState() const { return CLOSED; } + void _dispatch(Callback_Imp & callable) { } + void _dispatchBinary(BytesCallback_Imp& callable) { } +}; + + +class _RealWebSocket : public easywsclient::WebSocket +{ + public: + // http://tools.ietf.org/html/rfc6455#section-5.2 Base Framing Protocol + // + // 0 1 2 3 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + // +-+-+-+-+-------+-+-------------+-------------------------------+ + // |F|R|R|R| opcode|M| Payload len | Extended payload length | + // |I|S|S|S| (4) |A| (7) | (16/64) | + // |N|V|V|V| |S| | (if payload len==126/127) | + // | |1|2|3| |K| | | + // +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + + // | Extended payload length continued, if payload len == 127 | + // + - - - - - - - - - - - - - - - +-------------------------------+ + // | |Masking-key, if MASK set to 1 | + // +-------------------------------+-------------------------------+ + // | Masking-key (continued) | Payload Data | + // +-------------------------------- - - - - - - - - - - - - - - - + + // : Payload Data continued ... : + // + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + // | Payload Data continued ... | + // +---------------------------------------------------------------+ + struct wsheader_type { + unsigned header_size; + bool fin; + bool mask; + enum opcode_type { + CONTINUATION = 0x0, + TEXT_FRAME = 0x1, + BINARY_FRAME = 0x2, + CLOSE = 8, + PING = 9, + PONG = 0xa, + } opcode; + int N0; + uint64_t N; + uint8_t masking_key[4]; + }; + + std::vector rxbuf; + std::vector txbuf; + std::vector receivedData; + + socket_t sockfd; + readyStateValues readyState; + bool useMask; + + _RealWebSocket(socket_t sockfd, bool useMask) : sockfd(sockfd), readyState(OPEN), useMask(useMask) { + } + + readyStateValues getReadyState() const { + return readyState; + } + + void poll(int timeout) { // timeout in milliseconds + if (readyState == CLOSED) { + if (timeout > 0) { + timeval tv = { timeout/1000, (timeout%1000) * 1000 }; + select(0, NULL, NULL, NULL, &tv); + } + return; + } + if (timeout != 0) { + fd_set rfds; + fd_set wfds; + timeval tv = { timeout/1000, (timeout%1000) * 1000 }; + FD_ZERO(&rfds); + FD_ZERO(&wfds); + FD_SET(sockfd, &rfds); + if (txbuf.size()) { FD_SET(sockfd, &wfds); } + select(sockfd + 1, &rfds, &wfds, 0, timeout > 0 ? &tv : 0); + } + while (true) { + // FD_ISSET(0, &rfds) will be true + int N = rxbuf.size(); + ssize_t ret; + rxbuf.resize(N + 1500); + ret = recv(sockfd, (char*)&rxbuf[0] + N, 1500, 0); + if (false) { } + else if (ret < 0 && (socketerrno == SOCKET_EWOULDBLOCK || socketerrno == SOCKET_EAGAIN_EINPROGRESS)) { + rxbuf.resize(N); + break; + } + else if (ret <= 0) { + rxbuf.resize(N); + closesocket(sockfd); + readyState = CLOSED; + fputs(ret < 0 ? "Connection error!\n" : "Connection closed!\n", stderr); + break; + } + else { + rxbuf.resize(N + ret); + } + } + while (txbuf.size()) { + int ret = ::send(sockfd, (char*)&txbuf[0], txbuf.size(), 0); + if (false) { } // ?? + else if (ret < 0 && (socketerrno == SOCKET_EWOULDBLOCK || socketerrno == SOCKET_EAGAIN_EINPROGRESS)) { + break; + } + else if (ret <= 0) { + closesocket(sockfd); + readyState = CLOSED; + fputs(ret < 0 ? "Connection error!\n" : "Connection closed!\n", stderr); + break; + } + else { + txbuf.erase(txbuf.begin(), txbuf.begin() + ret); + } + } + if (!txbuf.size() && readyState == CLOSING) { + closesocket(sockfd); + readyState = CLOSED; + } + } + + // Callable must have signature: void(const std::string & message). + // Should work with C functions, C++ functors, and C++11 std::function and + // lambda: + //template + //void dispatch(Callable callable) + virtual void _dispatch(Callback_Imp & callable) { + struct CallbackAdapter : public BytesCallback_Imp + // Adapt void(const std::string&) to void(const std::string&) + { + Callback_Imp& callable; + CallbackAdapter(Callback_Imp& callable) : callable(callable) { } + void operator()(const std::vector& message) { + std::string stringMessage(message.begin(), message.end()); + callable(stringMessage); + } + }; + CallbackAdapter bytesCallback(callable); + _dispatchBinary(bytesCallback); + } + + virtual void _dispatchBinary(BytesCallback_Imp & callable) { + // TODO: consider acquiring a lock on rxbuf... + while (true) { + wsheader_type ws; + if (rxbuf.size() < 2) { return; /* Need at least 2 */ } + const uint8_t * data = (uint8_t *) &rxbuf[0]; // peek, but don't consume + ws.fin = (data[0] & 0x80) == 0x80; + ws.opcode = (wsheader_type::opcode_type) (data[0] & 0x0f); + ws.mask = (data[1] & 0x80) == 0x80; + ws.N0 = (data[1] & 0x7f); + ws.header_size = 2 + (ws.N0 == 126? 2 : 0) + (ws.N0 == 127? 8 : 0) + (ws.mask? 4 : 0); + if (rxbuf.size() < ws.header_size) { return; /* Need: ws.header_size - rxbuf.size() */ } + int i = 0; + if (ws.N0 < 126) { + ws.N = ws.N0; + i = 2; + } + else if (ws.N0 == 126) { + ws.N = 0; + ws.N |= ((uint64_t) data[2]) << 8; + ws.N |= ((uint64_t) data[3]) << 0; + i = 4; + } + else if (ws.N0 == 127) { + ws.N = 0; + ws.N |= ((uint64_t) data[2]) << 56; + ws.N |= ((uint64_t) data[3]) << 48; + ws.N |= ((uint64_t) data[4]) << 40; + ws.N |= ((uint64_t) data[5]) << 32; + ws.N |= ((uint64_t) data[6]) << 24; + ws.N |= ((uint64_t) data[7]) << 16; + ws.N |= ((uint64_t) data[8]) << 8; + ws.N |= ((uint64_t) data[9]) << 0; + i = 10; + } + if (ws.mask) { + ws.masking_key[0] = ((uint8_t) data[i+0]) << 0; + ws.masking_key[1] = ((uint8_t) data[i+1]) << 0; + ws.masking_key[2] = ((uint8_t) data[i+2]) << 0; + ws.masking_key[3] = ((uint8_t) data[i+3]) << 0; + } + else { + ws.masking_key[0] = 0; + ws.masking_key[1] = 0; + ws.masking_key[2] = 0; + ws.masking_key[3] = 0; + } + if (rxbuf.size() < ws.header_size+ws.N) { return; /* Need: ws.header_size+ws.N - rxbuf.size() */ } + + // We got a whole message, now do something with it: + if (false) { } + else if ( + ws.opcode == wsheader_type::TEXT_FRAME + || ws.opcode == wsheader_type::BINARY_FRAME + || ws.opcode == wsheader_type::CONTINUATION + ) { + if (ws.mask) { for (size_t i = 0; i != ws.N; ++i) { rxbuf[i+ws.header_size] ^= ws.masking_key[i&0x3]; } } + receivedData.insert(receivedData.end(), rxbuf.begin()+ws.header_size, rxbuf.begin()+ws.header_size+(size_t)ws.N);// just feed + if (ws.fin) { + callable((const std::vector) receivedData); + receivedData.erase(receivedData.begin(), receivedData.end()); + std::vector ().swap(receivedData);// free memory + } + } + else if (ws.opcode == wsheader_type::PING) { + if (ws.mask) { for (size_t i = 0; i != ws.N; ++i) { rxbuf[i+ws.header_size] ^= ws.masking_key[i&0x3]; } } + std::string data(rxbuf.begin()+ws.header_size, rxbuf.begin()+ws.header_size+(size_t)ws.N); + sendData(wsheader_type::PONG, data.size(), data.begin(), data.end()); + } + else if (ws.opcode == wsheader_type::PONG) { } + else if (ws.opcode == wsheader_type::CLOSE) { close(); } + else { fprintf(stderr, "ERROR: Got unexpected WebSocket message.\n"); close(); } + + rxbuf.erase(rxbuf.begin(), rxbuf.begin() + ws.header_size+(size_t)ws.N); + } + } + + void sendPing() { + std::string empty; + sendData(wsheader_type::PING, empty.size(), empty.begin(), empty.end()); + } + + void send(const std::string& message) { + sendData(wsheader_type::TEXT_FRAME, message.size(), message.begin(), message.end()); + } + + void sendBinary(const std::string& message) { + sendData(wsheader_type::BINARY_FRAME, message.size(), message.begin(), message.end()); + } + + void sendBinary(const std::vector& message) { + sendData(wsheader_type::BINARY_FRAME, message.size(), message.begin(), message.end()); + } + + template + void sendData(wsheader_type::opcode_type type, uint64_t message_size, Iterator message_begin, Iterator message_end) { + // TODO: + // Masking key should (must) be derived from a high quality random + // number generator, to mitigate attacks on non-WebSocket friendly + // middleware: + const uint8_t masking_key[4] = { 0x12, 0x34, 0x56, 0x78 }; + // TODO: consider acquiring a lock on txbuf... + if (readyState == CLOSING || readyState == CLOSED) { printf("closed\n"); return; } + std::vector header; + header.assign(2 + (message_size >= 126 ? 2 : 0) + (message_size >= 65536 ? 6 : 0) + (useMask ? 4 : 0), 0); + header[0] = 0x80 | type; + if (false) { } + else if (message_size < 126) { + header[1] = (message_size & 0xff) | (useMask ? 0x80 : 0); + if (useMask) { + header[2] = masking_key[0]; + header[3] = masking_key[1]; + header[4] = masking_key[2]; + header[5] = masking_key[3]; + } + } + else if (message_size < 65536) { + header[1] = 126 | (useMask ? 0x80 : 0); + header[2] = (message_size >> 8) & 0xff; + header[3] = (message_size >> 0) & 0xff; + if (useMask) { + header[4] = masking_key[0]; + header[5] = masking_key[1]; + header[6] = masking_key[2]; + header[7] = masking_key[3]; + } + } + else { // TODO: run coverage testing here + header[1] = 127 | (useMask ? 0x80 : 0); + header[2] = (message_size >> 56) & 0xff; + header[3] = (message_size >> 48) & 0xff; + header[4] = (message_size >> 40) & 0xff; + header[5] = (message_size >> 32) & 0xff; + header[6] = (message_size >> 24) & 0xff; + header[7] = (message_size >> 16) & 0xff; + header[8] = (message_size >> 8) & 0xff; + header[9] = (message_size >> 0) & 0xff; + if (useMask) { + header[10] = masking_key[0]; + header[11] = masking_key[1]; + header[12] = masking_key[2]; + header[13] = masking_key[3]; + } + } + printf("added to buffer\n"); + // N.B. - txbuf will keep growing until it can be transmitted over the socket: + txbuf.insert(txbuf.end(), header.begin(), header.end()); + txbuf.insert(txbuf.end(), message_begin, message_end); + if (useMask) { + for (size_t i = 0; i != message_size; ++i) { *(txbuf.end() - message_size + i) ^= masking_key[i&0x3]; } + } + } + + void close() { + if(readyState == CLOSING || readyState == CLOSED) { return; } + readyState = CLOSING; + uint8_t closeFrame[6] = {0x88, 0x80, 0x00, 0x00, 0x00, 0x00}; // last 4 bytes are a masking key + std::vector header(closeFrame, closeFrame+6); + txbuf.insert(txbuf.end(), header.begin(), header.end()); + } + +}; + + +easywsclient::WebSocket::pointer from_url(const std::string& url, bool useMask, const std::string& origin) { + char host[128]; + int port; + char path[128]; + if (url.size() >= 128) { + fprintf(stderr, "ERROR: url size limit exceeded: %s\n", url.c_str()); + return NULL; + } + if (origin.size() >= 200) { + fprintf(stderr, "ERROR: origin size limit exceeded: %s\n", origin.c_str()); + return NULL; + } + if (false) { } + else if (sscanf(url.c_str(), "ws://%[^:/]:%d/%s", host, &port, path) == 3) { + } + else if (sscanf(url.c_str(), "ws://%[^:/]/%s", host, path) == 2) { + port = 80; + } + else if (sscanf(url.c_str(), "ws://%[^:/]:%d", host, &port) == 2) { + path[0] = '\0'; + } + else if (sscanf(url.c_str(), "ws://%[^:/]", host) == 1) { + port = 80; + path[0] = '\0'; + } + else { + fprintf(stderr, "ERROR: Could not parse WebSocket url: %s\n", url.c_str()); + return NULL; + } + fprintf(stderr, "easywsclient: connecting: host=%s port=%d path=/%s\n", host, port, path); + socket_t sockfd = hostname_connect(host, port); + if (sockfd == INVALID_SOCKET) { + fprintf(stderr, "Unable to connect to %s:%d\n", host, port); + return NULL; + } + { + // XXX: this should be done non-blocking, + char line[256]; + int status; + int i; + snprintf(line, 256, "GET /%s HTTP/1.1\r\n", path); ::send(sockfd, line, strlen(line), 0); + if (port == 80) { + snprintf(line, 256, "Host: %s\r\n", host); ::send(sockfd, line, strlen(line), 0); + } + else { + snprintf(line, 256, "Host: %s:%d\r\n", host, port); ::send(sockfd, line, strlen(line), 0); + } + snprintf(line, 256, "Upgrade: websocket\r\n"); ::send(sockfd, line, strlen(line), 0); + snprintf(line, 256, "Connection: Upgrade\r\n"); ::send(sockfd, line, strlen(line), 0); + if (!origin.empty()) { + snprintf(line, 256, "Origin: %s\r\n", origin.c_str()); ::send(sockfd, line, strlen(line), 0); + } + snprintf(line, 256, "Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n"); ::send(sockfd, line, strlen(line), 0); + snprintf(line, 256, "Sec-WebSocket-Version: 13\r\n"); ::send(sockfd, line, strlen(line), 0); + snprintf(line, 256, "\r\n"); ::send(sockfd, line, strlen(line), 0); + for (i = 0; i < 2 || (i < 255 && line[i-2] != '\r' && line[i-1] != '\n'); ++i) { if (recv(sockfd, line+i, 1, 0) == 0) { return NULL; } } + line[i] = 0; + if (i == 255) { fprintf(stderr, "ERROR: Got invalid status line connecting to: %s\n", url.c_str()); return NULL; } + if (sscanf(line, "HTTP/1.1 %d", &status) != 1 || status != 101) { fprintf(stderr, "ERROR: Got bad status connecting to %s: %s", url.c_str(), line); return NULL; } + // TODO: verify response headers, + while (true) { + for (i = 0; i < 2 || (i < 255 && line[i-2] != '\r' && line[i-1] != '\n'); ++i) { if (recv(sockfd, line+i, 1, 0) == 0) { return NULL; } } + if (line[0] == '\r' && line[1] == '\n') { break; } + } + } + int flag = 1; + setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, (char*) &flag, sizeof(flag)); // Disable Nagle's algorithm +#ifdef _WIN32 + u_long on = 1; + ioctlsocket(sockfd, FIONBIO, &on); +#else + fcntl(sockfd, F_SETFL, O_NONBLOCK); +#endif + fprintf(stderr, "Connected to: %s\n", url.c_str()); + return easywsclient::WebSocket::pointer(new _RealWebSocket(sockfd, useMask)); +} + +} // end of module-only namespace + + + +namespace easywsclient { + +WebSocket::pointer WebSocket::create_dummy() { + static pointer dummy = pointer(new _DummyWebSocket); + return dummy; +} + + +WebSocket::pointer WebSocket::from_url(const std::string& url, const std::string& origin) { + return ::from_url(url, true, origin); +} + +WebSocket::pointer WebSocket::from_url_no_mask(const std::string& url, const std::string& origin) { + return ::from_url(url, false, origin); +} + + +} // namespace easywsclient \ No newline at end of file diff --git a/src/easywsclient.h b/src/easywsclient.h new file mode 100644 index 000000000000..5a7148b12f33 --- /dev/null +++ b/src/easywsclient.h @@ -0,0 +1,72 @@ +#ifndef EASYWSCLIENT_HPP_20120819_MIOFVASDTNUASZDQPLFD +#define EASYWSCLIENT_HPP_20120819_MIOFVASDTNUASZDQPLFD + +// This code comes from: +// https://github.com/dhbaird/easywsclient +// +// To get the latest version: +// wget https://raw.github.com/dhbaird/easywsclient/master/easywsclient.hpp +// wget https://raw.github.com/dhbaird/easywsclient/master/easywsclient.cpp + +#include +#include + +namespace easywsclient { + +struct Callback_Imp { virtual void operator()(const std::string& message) = 0; }; +struct BytesCallback_Imp { virtual void operator()(const std::vector& message) = 0; }; + +class WebSocket { + public: + typedef WebSocket * pointer; + typedef enum readyStateValues { CLOSING, CLOSED, CONNECTING, OPEN } readyStateValues; + + // Factories: + static pointer create_dummy(); + static pointer from_url(const std::string& url, const std::string& origin = std::string()); + static pointer from_url_no_mask(const std::string& url, const std::string& origin = std::string()); + + // Interfaces: + virtual ~WebSocket() { } + virtual void poll(int timeout = 0) = 0; // timeout in milliseconds + virtual void send(const std::string& message) = 0; + virtual void sendBinary(const std::string& message) = 0; + virtual void sendBinary(const std::vector& message) = 0; + virtual void sendPing() = 0; + virtual void close() = 0; + virtual readyStateValues getReadyState() const = 0; + + template + void dispatch(Callable callable) + // For callbacks that accept a string argument. + { // N.B. this is compatible with both C++11 lambdas, functors and C function pointers + struct _Callback : public Callback_Imp { + Callable& callable; + _Callback(Callable& callable) : callable(callable) { } + void operator()(const std::string& message) { callable(message); } + }; + _Callback callback(callable); + _dispatch(callback); + } + + template + void dispatchBinary(Callable callable) + // For callbacks that accept a std::vector argument. + { // N.B. this is compatible with both C++11 lambdas, functors and C function pointers + struct _Callback : public BytesCallback_Imp { + Callable& callable; + _Callback(Callable& callable) : callable(callable) { } + void operator()(const std::vector& message) { callable(message); } + }; + _Callback callback(callable); + _dispatchBinary(callback); + } + + protected: + virtual void _dispatch(Callback_Imp& callable) = 0; + virtual void _dispatchBinary(BytesCallback_Imp& callable) = 0; +}; + +} // namespace easywsclient + +#endif /* EASYWSCLIENT_HPP_20120819_MIOFVASDTNUASZDQPLFD */ \ No newline at end of file diff --git a/src/evo/dapi.cpp b/src/evo/dapi.cpp new file mode 100644 index 000000000000..38f56d38886b --- /dev/null +++ b/src/evo/dapi.cpp @@ -0,0 +1,870 @@ + + +// Copyright (c) 2014-2015 The Dash developers + +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + + +#include "main.h" +#include "core_io.h" +#include "db.h" +#include "dapi.h" +#include "file.h" +#include "json/json_spirit.h" +#include "json/json_spirit_value.h" +#include "easywsclient.h" +#include +#include +#include + +#include +#include +#include +#include + +int nError; +std::string strErrorMessage; +// error reporting + +using easywsclient::WebSocket; +WebSocket::pointer ws_client; + +std::string GetIndexFile(std::string strFilename) +{ + boost::filesystem::path filename = GetDataDirectory() / "index" / strFilename; + return filename.c_str(); +} + +std::string GetProfileFile(std::string strUID) +{ + boost::filesystem::path filename = GetDataDirectory() / "users" / strUID; + return filename.c_str(); +} + +std::string GetPrivateDataFile(std::string strUID, int nSlot) +{ + std::string strFilename = strUID + "." + boost::lexical_cast(nSlot); + boost::filesystem::path filename = GetDataDirectory() / "users" / strFilename; + return filename.c_str(); +} + +bool IsValidUsername(std::string strUsername) +{ + for(std::string::size_type i = 0; i < strUsername.size(); ++i) { + if(!std::isalpha(strUsername[i]) && !std::isdigit(strUsername[i]) && strUsername[i] != '_') return false; + } + return true; +} + +// std::string exec(const char* cmd) { +// std::shared_ptr pipe(popen(cmd, "r"), pclose); +// if (!pipe) return "ERROR"; +// char buffer[128]; +// std::string result = ""; +// while (!feof(pipe.get())) { +// if (fgets(buffer, 128, pipe.get()) != NULL) +// result += buffer; +// } +// return result; +// } + +void EventNotify(const std::string& strEvent) +{ + std::string strCmd = GetArg("-eventnotify", ""); + + //boost::replace_all(strCmd, "%s", strEvent); + //boost::thread t(runCommand, strCmd); // thread runs free + + ws_client = WebSocket::from_url("ws://localhost:5000/"); + + assert(ws_client); + ws_client->send(strEvent); + ws_client->poll(); + + printf("sending out: %s\n", strEvent.c_str()); + + ws_client->close(); +} + +std::string escapeJsonString(const std::string& input) { + // NOTE: Any ideas on replacing this with something more portable? + + std::ostringstream ss; + for (std::string::const_iterator iter = input.begin(); iter != input.end(); iter++) { + switch (*iter) { + case '\\': ss << "\\\\"; break; + case '"': ss << "\\\""; break; + case '/': ss << "\\/"; break; + case '\b': ss << "\\b"; break; + case '\f': ss << "\\f"; break; + case '\n': ss << "\\n"; break; + case '\r': ss << "\\r"; break; + case '\t': ss << "\\t"; break; + default: ss << *iter; break; + } + } + return ss.str(); +} + + +void ResetErrorStatus() {nError = 0; strErrorMessage = "";} +void SetError(int nErrorIn, std::string strMessageIn) {nError = nErrorIn; strErrorMessage = strMessageIn;} + +/* + +{ + "object" : "dash_budget_items", + "data" : [ + { "object" : "dash_budget", + "name" : "ds_liquidity", + ... + }, + { "object" : "dash_budget", + "name" : "christmas-lottery", + ... + }, + ... + ] +} +*/ + +Object GetResultObject(std::string strID, std::string strCommand, Object& objFile) +{ + Object retData; + retData.push_back(Pair("id", strID)); + retData.push_back(Pair("command", strCommand)); + retData.push_back(Pair("error_id", nError)); + retData.push_back(Pair("error_message", strErrorMessage)); + retData.push_back(Pair("data", objFile)); + + Object ret; + ret.push_back(Pair("object", "dapi_result")); + ret.push_back(Pair("data", retData)); + + return ret; +} + +Object GetMessageObject(std::string strID, std::string strFromUserID, std::string strToUserID, std::string strSubCommand, std::string strMessage) +{ + Object retData; + retData.push_back(Pair("id", strID)); + retData.push_back(Pair("command", "send_message")); + retData.push_back(Pair("error_id", nError)); + retData.push_back(Pair("error_message", strErrorMessage)); + retData.push_back(Pair("from_uid", strFromUserID)); + retData.push_back(Pair("to_uid", strToUserID)); + retData.push_back(Pair("sub_command", strSubCommand)); + retData.push_back(Pair("payload", strMessage)); + + Object ret; + ret.push_back(Pair("object", "dapi_message")); + ret.push_back(Pair("data", retData)); + + return ret; +} + +std::string SerializeJsonFromObject(Object objToSerialize) +{ + //TODO: this is terrible, we need to correctly clean and escape the json :) + std::stringstream ss; + json_spirit::write( objToSerialize, ss ); + std::string strJson = ss.str(); // escapeJsonString("'" + ss.str() + "'"); + // strJson.replace(0,1,"\""); + // strJson.replace(strJson.size()-1,1,"\""); + return strJson; +} + +// GET OBJECT AND CONVERT TO VARIABLE + +bool FindValueAsString(Object obj, std::string strName, std::string& strOut, bool fRequired=true) +{ + const Value& result = json_spirit::find_value(obj, strName); + if (result.type() == null_type) + { + if(fRequired) SetError(1008, "Missing required field : " + strName); + return false; + } + + strOut = result.get_str(); + return true; +} + +bool FindValueAsObject(Object obj, std::string strName, Object& objOut) +{ + const Value& result = json_spirit::find_value(obj, strName); + if (result.type() == null_type) + { + SetError(1008, "Missing required field : " + strName); + return false; + } + + objOut = result.get_obj(); + return true; +} + +bool FindValueAsArray(Object obj, std::string strName, Array& arrOut) +{ + const Value& result = json_spirit::find_value(obj, strName); + if (result.type() == null_type) + { + SetError(1008, "Missing required field : " + strName); + return false; + } + + arrOut = result.get_array(); + return true; +} + +bool FindValueAsInt(Object obj, std::string strName, int& nOut) +{ + const Value& result = json_spirit::find_value(obj, strName); + if (result.type() == null_type) + { + SetError(1008, "Missing required field : " + strName); + return false; + } + + nOut = result.get_int(); + return true; +} + +// DAPI FUNCTIONALITY + +bool CDAPI::Execute(Object& obj) +{ + ResetErrorStatus(); + + if(obj.size() > 2000) + { + SetError(1013, "[\"object\"] is too large"); + } + + // dapi command only + std::string strObject; + if(!FindValueAsString(obj, "object", strObject)) + { + SetError(1009, "[\"object\"] is required in data structure"); + } + + if(strObject != "dapi_command") + { + SetError(1010, "[\"object\"] type of \"dapi_command\" is only supported in data structure"); + } + + // required for all DAPI messages + Object objData; + string strCommand = ""; + if(!FindValueAsObject(obj, "data", objData)) + { + SetError(1011, "[\"data\"] is required in data structure"); + } + if(!FindValueAsString(objData, "command", strCommand)) + { + SetError(1012, "[\"data\"][\"command\"] is required in data structure"); + } + + // check and make sure the usernames are OK + ValidateUsernames(obj); + + printf("%d\n", nError); + + // SUPPORTED COMMANDS + if(nError == 0) + { + if(strCommand == "get_profile") { + if(GetProfile(obj)) return true; + } else if (strCommand == "set_profile") { + if(SetProfile(obj)) return true; + } else if (strCommand == "set_private_data") { + if(SetPrivateData(obj)) return true; + } else if (strCommand == "get_private_data") { + if(GetPrivateData(obj)) return true; + } else if (strCommand == "send_message") { + if(SendMessage(obj)) return true; + } else if (strCommand == "broadcast_message") { + if(BroadcastMessage(obj)) return true; + } else if (strCommand == "invite_user") { + if(InviteUser(obj)) return true; + } else if (strCommand == "validate_account") { + if(ValidateAccount(obj)) return true; + } else if (strCommand == "search_users") { + if(SearchUsers(obj)) return true; + } + } + + // UNKNOWN COMMANDS + string strID = ""; + if(!FindValueAsString(objData, "id", strID)) return false; + + // send the user back the results of the query + if(nError == 0) SetError(1007, "Unknown Command : " + strCommand); + Object result; + Object ret = GetResultObject(strID, strCommand, result); + std::string strJson = SerializeJsonFromObject(ret); + + EventNotify(strJson); + + return false; +} + +bool CDAPI::ValidateSignature(Object& obj) +{ + /* + lookup pubkey for user + remove signature + hash object + check signature against hash + */ + + return true; +} + + +bool CDAPI::ValidateUsernames(Object& obj) +{ + + // get the user we want to open + Object objData; + string strUID = ""; + if(!FindValueAsObject(obj, "data", objData)) return false; + + if(FindValueAsString(objData, "to_uid", strUID, false)) + { + if (!IsValidUsername(strUID)) + { + SetError(1011, "Invalid to_uid - must be alphanumeric: " + strUID); + } + } + if(FindValueAsString(objData, "from_uid", strUID, false)) + { + if (!IsValidUsername(strUID)) + { + SetError(1011, "Invalid from_uid - must be alphanumeric: " + strUID); + } + } + + return true; +} + +bool CDAPI::GetProfile(Object& obj) +{ + /* + { + "object" : "dapi_command", + "data" : { + “command” : ”get_profile”, + “from_uid” : INT64, + “to_uid” : INT64, + “signature” : ‘’, + “fields” : [“fname”, “lname”] + } + } + */ + + // get the user we want to open + Object objData; + string strUID = ""; + if(!FindValueAsObject(obj, "data", objData)) return false; + if(!FindValueAsString(objData, "to_uid", strUID)) return false; + string strID = ""; + if(!FindValueAsString(objData, "id", strID)) return false; + + // open the file and read it + CDriveFile file(GetProfileFile(strUID)); + if(!file.Exists()) + { + SetError(1001, "File doesn't exist : " + strUID); + return false; + } + file.Read(); + + // send the user back the results of the query + Object ret = GetResultObject(strID, "get_profile", file.obj); + std::string strJson2 = SerializeJsonFromObject(file.obj); + std::string strJson = SerializeJsonFromObject(ret); + EventNotify(strJson); + + return true; +} + +bool CDAPI::SetProfile(Object& obj) +{ + /* + { + "object" : "dapi_command", + "data" : { + "command": "set_profile", + "from_uid": INT64, + "to_uid": INT64, + "signature": "", + "update" : [ + {"field":"name","value":"newvalue"} + ] + } + } + + */ + + std::string strObject = ""; + if(!FindValueAsString(obj, "object", strObject)) return false; + + // get the user we want to open + Object objData; + Array arrDataUpdate; + string strUID = ""; + if(!FindValueAsObject(obj, "data", objData)) return false; + if(!FindValueAsArray(objData, "update", arrDataUpdate)) return false; + if(!FindValueAsString(objData, "to_uid", strUID)) return false; + string strID = ""; + if(!FindValueAsString(objData, "id", strID)) return false; + + // open the file and read it + CDriveFile file(GetProfileFile(strUID)); + if(!file.Exists()) + { + SetError(1002, "File doesn't exist : " + strUID); + return false; + } + file.Read(); + + std::map mapObj; + json_spirit::obj_to_map(file.obj, mapObj); + + for( unsigned int i = 0; i < arrDataUpdate.size(); ++i ) + { + Object tmp = arrDataUpdate[i].get_obj(); + string strField = ""; if(!FindValueAsString(tmp, "field", strField)) return false; + string strValue = ""; if(!FindValueAsString(tmp, "value", strValue)) return false; + + // string& strUpdate = json_spirit::find_value(obj, strField).get_str(); + // strUpdate = strValue; + + //update the users file, NOTE: this is completely insecure for the prototype (see the paper for security model!) + //file.obj[strField] = strValue; + mapObj[strField] = strValue; + } + + json_spirit::map_to_obj(mapObj, file.obj); + file.Write(); + + Object ret = GetResultObject(strID, "set_profile", file.obj); + std::string strJson = SerializeJsonFromObject(ret); + + EventNotify(strJson); + + return true; +} + +bool CDAPI::GetPrivateData(Object& obj) +{ + /* + { + "object" : "dapi_command", + "data" : { + "command" = "get_private_data", + "from_uid" = UID, + "to_uid" = UID, + "signature" = ‘’, + "slot" = 1 + } + } + */ + + // get the user we want to open + Object objData; + string strUID = ""; + int nSlot = -1; + if(!FindValueAsObject(obj, "data", objData)) return false; + if(!FindValueAsString(objData, "to_uid", strUID)) return false; + if(!FindValueAsInt(objData, "slot", nSlot)) return false; + if(nSlot < 1 || nSlot > 10) + { + SetError(1002, "Slot out of range"); + return false; + } + string strID = ""; + if(!FindValueAsString(objData, "id", strID)) return false; + + // open the file and read it + CDriveFile file(GetPrivateDataFile(strUID, nSlot)); + if(!file.Exists()) return false; + file.Read(); + + // send the user back the results of the query + Object ret = GetResultObject(strID, "get_private_data", file.obj); + std::string strJson = SerializeJsonFromObject(ret); + EventNotify(strJson); + + return true; +} + +bool CDAPI::SetPrivateData(Object& obj) +{ + /* + { + "object" : "dapi_command", + "data" : { + "command" : "set_private_data", + "from_uid" : INT64, + "to_uid" : INT64, + "signature" : "SIGNATURE", + "slot" : 1, + "payload" : JSON_WEB_ENCRYPTION + } + } + */ + + // get the user we want to open + Object objData; + string strUID = ""; + int nSlot = -1; + if(!FindValueAsObject(obj, "data", objData)) return false; + if(!FindValueAsString(objData, "to_uid", strUID)) return false; + if(!FindValueAsInt(objData, "slot", nSlot)) return false; + if(nSlot < 1 || nSlot > 10) + { + SetError(1002, "Slot out of range"); + return false; + } + string strID = ""; + if(!FindValueAsString(objData, "id", strID)) return false; + + // open the file and read it + CDriveFile file(GetPrivateDataFile(strUID, nSlot)); + if(!file.Exists()) + { + Object newObj; + newObj.push_back(Pair("access_times", 0)); + newObj.push_back(Pair("last_access", 0)); + newObj.push_back(Pair("payload", "")); + file.obj = newObj; + } + file.Read(); + + //update from new payload + std::map mapObj; + json_spirit::obj_to_map(file.obj, mapObj); + + string strPayload = ""; + if(!FindValueAsString(objData, "payload", strPayload)) return false; + mapObj["payload"] = strPayload; + + json_spirit::map_to_obj(mapObj, file.obj); + file.Write(); + + // send the user back the results of the query + Object ret = GetResultObject(strID, "set_private_data", file.obj); + std::string strJson = SerializeJsonFromObject(ret); + + EventNotify(strJson); + + return true; + +} + +// send message from one user to another through T2 +bool CDAPI::SendMessage(Object& obj) +{ + /* + { + "object" : "dapi_command", + "data" : { + "command" = "send_message", + "sub_command" = "(addr,cmd2,cmd3)", + "from_uid" = UID, + "to_uid" = UID, + "signature" = ‘’, + "payload" = ENCRYPTED + } + } + */ + + // get the user we want to open + Object objData; + string strUID1 = ""; + string strUID2 = ""; + string strSubCommand = ""; + string strPayload = ""; + if(!FindValueAsObject(obj, "data", objData)) return false; + if(!FindValueAsString(objData, "from_uid", strUID1)) return false; + if(!FindValueAsString(objData, "to_uid", strUID2)) return false; + if(!FindValueAsString(objData, "sub_command", strSubCommand)) return false; + if(!FindValueAsString(objData, "payload", strPayload)) return false; + string strID = ""; + if(!FindValueAsString(objData, "id", strID)) return false; + + //TODO: this is presently sending the message to all users on the server + Object ret = GetMessageObject(strID, strUID1, strUID2, strSubCommand, strPayload); + std::string strJson = SerializeJsonFromObject(ret); + + EventNotify(strJson); + return true; +} + +// broadcast any message on the network from T3 +bool CDAPI::BroadcastMessage(Object& obj) +{ + /* + { + "object" : "dapi_command", + "data" : { + "command" = "broadcast", + "sub_command" = "tx", //can support multiple message commands + "from_uid" = UID, + "to_uid" = UID, + "signature" = ‘’, + "payload" = SERIALIZED_BASE64_ENCODED + } + } + */ + + // get the user we want to open + Object objData; + string strSubCommand = ""; + string strPayload = ""; + if(!FindValueAsObject(obj, "data", objData)) return false; + if(!FindValueAsString(objData, "sub_command", strSubCommand)) return false; + if(!FindValueAsString(objData, "payload", strPayload)) return false; + + string strID = ""; + if(!FindValueAsString(objData, "id", strID)) return false; + + if(strSubCommand == "tx") + { + CTransaction tx; + if (!DecodeHexTx(tx, strPayload)) + { + SetError(1003, "TX Decoding Failed"); + return false; + } + RelayTransaction(tx); + + Object retTx; + retTx.push_back(Pair("tx-id", tx.GetHash().ToString())); + //should probably figure out if it was broadcasted successfully + + // send the user back the results of the query + Object ret = GetResultObject(strID, "broadcast_message", retTx); + std::string strJson = SerializeJsonFromObject(ret); + + return true; + + } else { + return false; + } + + return false; +} + + +// Create new user account +bool CDAPI::InviteUser(Object& obj) +{ + // //SEND FRIEND REQUEST + // { + // "object" : "dapi_command", + // "data" : { + // "command" : “invite_user”, + // "from_uid" : UID, + // "to_uid" : ENTERED_USERNAME, + // "to_name" : ENTERED_NAME, + // "to_email" : ENTERED_EMAIL, + // "to_pubkey" : ENTERED_PUBKEY, + // "signature" = “” + // } + // } + + // get the user we want to open + Object objData; + string strUID = ""; + if(!FindValueAsObject(obj, "data", objData)) return false; + if(!FindValueAsString(objData, "to_uid", strUID)) return false; + + string strID = ""; + if(!FindValueAsString(objData, "id", strID)) return false; + + // open the file and read it + CDriveFile file(GetProfileFile(strUID)); + if(file.Exists()) + { + SetError(1004, "User already exists : " + strUID); + return false; + } + + string strName = ""; + string strEmail = ""; + string strPubkey = ""; + if(!FindValueAsString(objData, "to_name", strName)) return false; + if(!FindValueAsString(objData, "to_email", strEmail)) return false; + //if(!FindValueAsString(objData, "to_pubkey", strPubkey)) return false; + + // CBitcoinAddress address(strPubkey); + // bool isValid = address.IsValid(); + + // if(!isValid) + // { + // SetError(1005, "Invalid pubkey: " + strPubkey); + // return false; + // } + + Object newObj; + newObj.push_back(Pair("status", 1)); + newObj.push_back(Pair("username", strUID)); + newObj.push_back(Pair("name", strName)); + newObj.push_back(Pair("email", strEmail)); + newObj.push_back(Pair("stars", 5)); + Array addresses; + newObj.push_back(Pair("addresses", addresses)); + //newObj.push_back(Pair("pubkey", strPubkey)); + newObj.push_back(Pair("challenge_code", boost::lexical_cast(GetRand(999999)))); + file.obj = newObj; + + file.Write(); + + // //RESULTING JSON + // { + // "object" : "dapi_result", + // "data" : { + // "command" : “invite_user”, + // "from_uid" : UID, + // "to_uid" : ENTERED_USERNAME, + // "to_email" : ENTERED_EMAIL, + // "to_pubkey" : ENTERED_PUBKEY, + // "to_challenge_code" : RANDOMLY_GENERATED, + // "signature" : “” + // } + // } + + // send the user back the results of the query + Object ret = GetResultObject(strID, "invite_user", file.obj); + std::string strJson = SerializeJsonFromObject(ret); + EventNotify(strJson); + + return true; +} + +bool CDAPI::ValidateAccount(Object& obj) +{ + /* + //VALIDATE ACCOUNT + { + "object" : "dapi_command", + "data" : { + "command" : “validate_account”, + "from_uid" : UID, + "to_uid" : ENTERED_USERNAME, + "to_challenge_code" : RANDOMLY_GENERATED, + "signature" : “” + } + } + */ + + std::string strObject = ""; + Object objData; + string strUID = ""; + string strChallengeCode = ""; + if(!FindValueAsString(obj, "object", strObject)) return false; + + // get the user we want to open + if(!FindValueAsObject(obj, "data", objData)) return false; + if(!FindValueAsString(objData, "to_uid", strUID)) return false; + if(!FindValueAsString(objData, "to_challenge_code", strChallengeCode)) return false; + + string strID = ""; + if(!FindValueAsString(objData, "id", strID)) return false; + + // open the file and read it + CDriveFile file(GetProfileFile(strUID)); + if(!file.Exists()) + { + SetError(1001, "File doesn't exist : " + strUID); + return false; + } + file.Read(); + + std::map mapObj; + json_spirit::obj_to_map(file.obj, mapObj); + + if(mapObj["status"].get_int() != 1) + { + SetError(1008, "Account in wrong state to be validated"); + return false; + } + + if(mapObj["challenge_code"].get_str() == strChallengeCode) + { + mapObj["challenge_code"] = ""; + mapObj["status"] = 2; + } else { + SetError(1006, "Invalid challenge_code : " + strChallengeCode); + return false; + } + + json_spirit::map_to_obj(mapObj, file.obj); + file.Write(); + + /* + //VALIDATE_ACCOUNT + { + "object" : "dapi_result", + "data" : { + "command" : “validate_account”, + "from_uid" : UID, + "to_uid" : ENTERED_USERNAME, + "signature" : “”, + “error_id” : 1000, + “error_description” : “none” + } + } + */ + + // send the user back the results of the query + Object ret = GetResultObject(strID, "“validate_account”", file.obj); + std::string strJson = SerializeJsonFromObject(ret); + EventNotify(strJson); + + return true; +} + + +bool CDAPI::SearchUsers(Object& obj) +{ + /* + //SEARCH USERS + { + "object" : "dapi_command", + "data" : { + "command" : "search_users", + "match_fields" : ["fiat_converter" : 1], + "start" : 0, + "limit" : 10 + } + } + */ + + Object objData; + string strID = ""; + if(!FindValueAsObject(obj, "data", objData)) return false; + if(!FindValueAsString(objData, "id", strID)) return false; + + + // open the file and read it + CDriveFile file(GetIndexFile("fiat_converters.js")); + + if(!file.Exists()) + { + SetError(1003, "File doesn't exist : " + GetIndexFile("fiat_converters.js")); + return false; + } + file.Read(); + + // send the user back the results of the query + Object ret = GetResultObject(strID, "search_users", file.obj); + std::string strJson = SerializeJsonFromObject(ret); + + EventNotify(strJson); + + return true; +} + diff --git a/src/evo/dapi.h b/src/evo/dapi.h new file mode 100644 index 000000000000..836b05c743bb --- /dev/null +++ b/src/evo/dapi.h @@ -0,0 +1,57 @@ + + +// Copyright (c) 2014-2015 The Dash developers + +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + + +#ifndef DAPI_H +#define DAPI_H + +#include "main.h" +#include "db.h" +//ø#include "init.h" +#include "rpcserver.h" + +#include "util.h" +#include "file.h" +#include "json/json_spirit.h" + +#include + +#include "json/json_spirit_value.h" +#include "univalue/univalue.h" +#include +#include +#include + +using namespace std; +using namespace json_spirit; + +std::string GetIndexFile(std::string strFilename); +std::string GetProfileFile(std::string strUID); +std::string GetPrivateDataFile(std::string strUID, int nSlot); +std::string escapeJsonString(const std::string& input); + +class CDAPI +{ +private: + CDAPI(); + +public: + static bool ValidateUsernames(Object& obj); + static bool Execute(Object& obj); + static bool ValidateSignature(Object& obj); + static bool GetProfile(Object& obj); + static bool SetProfile(Object& obj); + static bool GetPrivateData(Object& obj); + static bool SetPrivateData(Object& obj); + static bool SendMessage(Object& obj); + static bool BroadcastMessage(Object& obj); + static bool InviteUser(Object& obj); + static bool ValidateAccount(Object& obj); + static bool SearchUsers(Object& obj); +}; + +#endif \ No newline at end of file diff --git a/src/evo/enduser.cpp b/src/evo/enduser.cpp new file mode 100644 index 000000000000..29f0e0772ce7 --- /dev/null +++ b/src/evo/enduser.cpp @@ -0,0 +1,26 @@ + + +#include "enduser.h" + +#include "main.h" +#include "db.h" +#include "init.h" +#include "rpcserver.h" +#include "utilmoneystr.h" + +#include "util.h" + +#include + +#include "univalue/univalue.h" + +#include +using namespace json_spirit; +using namespace std; + +bool CEndUser::Load() +{ + obj.clear(); + obj.push_back(Pair("name", "evan")); + return true; +} \ No newline at end of file diff --git a/src/evo/enduser.h b/src/evo/enduser.h new file mode 100644 index 000000000000..aeba1c18b17f --- /dev/null +++ b/src/evo/enduser.h @@ -0,0 +1,40 @@ + + +// Copyright (c) 2014-2015 The Dash developers + +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + + +#ifndef DASHDRIVE_USER_H +#define DASHDRIVE_USER_H + +#include "main.h" +#include "db.h" +#include "init.h" +#include "rpcserver.h" + +#include "util.h" + +#include + +#include "json/json_spirit_value.h" +#include "univalue/univalue.h" + +using namespace std; +using namespace json_spirit; + +class CEndUser +{ +private: + Object obj; + std::string strPath; + CEndUser(std::string strPathIn) {strPath = strPathIn;} + +public: + + bool Load(); + bool Save() {return true;}; +}; + +#endif \ No newline at end of file diff --git a/src/evo/file.cpp b/src/evo/file.cpp new file mode 100644 index 000000000000..1c8b97c8fcee --- /dev/null +++ b/src/evo/file.cpp @@ -0,0 +1,73 @@ + +#include "main.h" +#include "file.h" +#include "util.h" +#include "init.h" +#include "base58.h" + +#include +#include +#include + +#include "json/json_spirit.h" +#include "json/json_spirit_value.h" +#include "json/json_spirit_writer.h" + +#include +#include +#include +#include + + +using namespace boost; +using namespace std; +using namespace json_spirit; + +boost::filesystem::path GetDataDirectory() +{ + string strDataDir2 = GetArg("-datadir2", ""); + if(strDataDir2 != "") return strDataDir2; + + namespace fs = boost::filesystem; + // Windows < Vista: C:\Documents and Settings\Username\Application Data\Dash + // Windows >= Vista: C:\Users\Username\AppData\Roaming\Dash + // Mac: ~/Library/Application Support/Dash + // Unix: ~/.dash +#ifdef WIN32 + // Windows + return GetSpecialFolderPath(CSIDL_APPDATA) / "Dash"; +#else + fs::path pathRet; + char* pszHome = getenv("HOME"); + if (pszHome == NULL || strlen(pszHome) == 0) + pathRet = fs::path("/"); + else + pathRet = fs::path(pszHome); +#ifdef MAC_OSX + // Mac + pathRet /= "Library/Application Support"; + TryCreateDirectory(pathRet); + return pathRet / "DashData"; +#else + // Unix + return pathRet / ".dash-data"; +#endif +#endif +} + + +// CDriveFile --- + +CDriveFile::CDriveFile() +{ + LOCK(cs); + strMagicMessage = "CDriveFile"; + fDirty = false; +} + +CDriveFile::CDriveFile(const std::string strPathIn) +{ + LOCK(cs); + strPath = strPathIn; + fDirty = false; +} diff --git a/src/evo/file.h b/src/evo/file.h new file mode 100644 index 000000000000..7697dd3dc11e --- /dev/null +++ b/src/evo/file.h @@ -0,0 +1,108 @@ + + +#ifndef DASHDRIVE_FILE_H +#define DASHDRIVE_FILE_H + +#include "init.h" +#include "base58.h" +#include "addrman.h" +#include "amount.h" +#include "checkpoints.h" +#include "compat/sanity.h" +#include "key.h" +#include "main.h" +#include "file.h" + +#include +#include +#include +#include +#include + +#include "json/json_spirit.h" +#include "json/json_spirit_value.h" +#include "json/json_spirit_writer.h" + +#include +#include "univalue/univalue.h" + + +using namespace json_spirit; +using namespace std; + + +// TODO: What include is required for this? +#define CLIENT_VERSION 1 + +boost::filesystem::path GetDataDirectory(); + +class CDriveFile +{ +private: + // critical section to protect the inner data structures + mutable CCriticalSection cs; + string strMagicMessage; + string strPath; + bool fDirty; + +public: + Object obj; + + enum ReadResult { + Ok, + FileError, + HashReadError, + IncorrectHash, + IncorrectMagicMessage, + IncorrectMagicNumber, + IncorrectFormat + }; + + CDriveFile(); + CDriveFile(const string strPathIn); + + bool Exists() + { + if ( boost::filesystem::exists( strPath ) ) + { + return true; + } + + return false; + } + + ReadResult Read() + { + std::ifstream t(strPath.c_str()); + std::string str((std::istreambuf_iterator(t)), + std::istreambuf_iterator()); + + json_spirit::Value val; + + bool fSuccess = json_spirit::read(str, val); + if (fSuccess) { + obj = val.get_obj(); + return Ok; + } + + return FileError; + } + + bool Write() + { + LOCK(cs); + + ofstream os( strPath.c_str() ); + json_spirit::write( obj, os ); + os.close(); + + + return false; + } + + +}; + + +#endif + diff --git a/src/init.cpp b/src/init.cpp index f80bde1ef5d5..61004dcf7bac 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -409,7 +409,9 @@ std::string HelpMessage(HelpMessageMode mode) strUsage += " -testnet " + _("Use the test network") + "\n"; strUsage += " -litemode= " + strprintf(_("Disable all Dash specific functionality (Masternodes, Darksend, InstantX, Budgeting) (0-1, default: %u)"), 0) + "\n"; - strUsage += "\n" + _("Masternode options:") + "\n"; + strUsage += "\n" + _("Masternode options:") + "\n"; + + strUsage += " -eventnotify= " + _("Send event to dash-2t to allow propagation to endusers (%s in cmd is replaced by event data)") + "\n"; strUsage += " -masternode= " + strprintf(_("Enable the client to act as a masternode (0-1, default: %u)"), 0) + "\n"; strUsage += " -mnconf= " + strprintf(_("Specify masternode configuration file (default: %s)"), "masternode.conf") + "\n"; strUsage += " -mnconflock= " + strprintf(_("Lock masternodes from masternode configuration file (default: %u)"), 1) + "\n"; @@ -1429,6 +1431,14 @@ bool AppInit2(boost::thread_group& threadGroup) // ********************************************************* Step 10: setup DarkSend + LogPrintf("Dash Evolution Variables...\n"); + + std::string datadir = GetArg("-datadir", "none"); + std::string eventnotify = GetArg("-eventnotify", "none"); + + LogPrintf("datadir2 = %s\n", datadir); + LogPrintf("eventnotify command = %s\n", eventnotify); + uiInterface.InitMessage(_("Loading masternode cache...")); CMasternodeDB mndb; diff --git a/src/rpcdapi.cpp b/src/rpcdapi.cpp new file mode 100644 index 000000000000..60de290cbc86 --- /dev/null +++ b/src/rpcdapi.cpp @@ -0,0 +1,109 @@ +// Copyright (c) 2010 Satoshi Nakamoto +// Copyright (c) 2009-2012 The Dash developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "main.h" +#include "db.h" +//#include "init.h" +#include "rpcserver.h" +#include "utilmoneystr.h" +#include "evo/dapi.h" +#include "evo/file.h" + +#include "util.h" + +#include + +#include "json/json_spirit_value.h" +#include "univalue/univalue.h" + +#include +using namespace json_spirit; +using namespace std; + +/* + More Information About DAPI + + This uses a simple json interface to execute commands on the network through DAPI. + To run a daemon and take commands from the network, you will also need to run the server in + the dash-t2 package. + +*/ + + +Value dapi(const Array& params, bool fHelp) +{ + + if (fHelp || params.size() < 1) + throw runtime_error( + "dapi \"json_command\"\n" + "Execute a command via DAPI\n" + ); + + /* + + + Executing web events: + + dashd --datadir=example --eventnotify="/Users/evan/Desktop/dash-2t/serve/event.py --event=%" --daemon + dash-cli dapi "{\"txid\":\"myid\",\"aeou\":\"123\",\"vout\":\"0\"}" "{\"address\":0.01}" + + dash_t2 --eventnotify="/Users/evan/Desktop/dash-2t/serve/event.py --event=%" + */ + + std::string strCommand = params[0].get_str(); + json_spirit::Value val; + + LogPrintf("dapi - new command : %s\n", strCommand.c_str()); + + bool fSuccess = json_spirit::read_string(strCommand, val); + if (fSuccess) { + Object obj = val.get_obj(); + CDAPI::Execute(obj); + return "ok"; + } + + //TODO: add better errors + return "parse error"; +} + +Value dapif(const Array& params, bool fHelp) +{ + + if (fHelp || params.size() < 1) + throw runtime_error( + "dapif \"json_file\"\n" + "Execute a command via DAPI\n" + ); + + /* + Executing web events: + + dashd --datadir=example --eventnotify="/Users/evan/Desktop/dash-2t/serve/event.py --event=%" --daemon + dash-cli dapi "{\"txid\":\"myid\",\"aeou\":\"123\",\"vout\":\"0\"}" "{\"address\":0.01}" + + dash_t2 --datadir2=/Users/evan/.dash-data/ --eventnotify="/Users/evan/Desktop/dash-2t/serve/event.py --event=%" + */ + + std::string strPath = params[0].get_str(); + LogPrintf("dapi - path: %s\n", strPath.c_str()); + + std::ifstream t(strPath.c_str()); + std::string str((std::istreambuf_iterator(t)), + std::istreambuf_iterator()); + + LogPrintf("dapi - json: %s\n", str.c_str()); + + json_spirit::Value val; + + bool fSuccess = json_spirit::read_string(str, val); + if (fSuccess) { + Object obj = val.get_obj(); + CDAPI::Execute(obj); + return "ok"; + } + + //TODO: add better errors + return "parse error"; +} diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp index 8efbb38040f5..5302f17abb03 100644 --- a/src/rpcserver.cpp +++ b/src/rpcserver.cpp @@ -316,6 +316,9 @@ static const CRPCCommand vRPCCommands[] = { "dash", "mnfinalbudget", &mnfinalbudget, true, true, false }, { "dash", "mnsync", &mnsync, true, true, false }, { "dash", "spork", &spork, true, true, false }, + { "dash", "dapi", &dapi, true, true, false }, + { "dash", "dapif", &dapif, true, true, false }, + #ifdef ENABLE_WALLET { "dash", "darksend", &darksend, false, false, true }, /* not threadSafe because of SendMoney */ diff --git a/src/rpcserver.h b/src/rpcserver.h index ff8d556173f6..bb5dafb95cac 100644 --- a/src/rpcserver.h +++ b/src/rpcserver.h @@ -235,6 +235,8 @@ extern json_spirit::Value mnbudget(const json_spirit::Array& params, bool fHelp) extern json_spirit::Value mnbudgetvoteraw(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value mnfinalbudget(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value mnsync(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value dapi(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value dapif(const json_spirit::Array& params, bool fHelp); // in rest.cpp extern bool HTTPReq_REST(AcceptedConnection *conn, diff --git a/src/test/data/dapi-broadcast-tx.js b/src/test/data/dapi-broadcast-tx.js new file mode 100644 index 000000000000..d4c9f66d8e4d --- /dev/null +++ b/src/test/data/dapi-broadcast-tx.js @@ -0,0 +1,10 @@ +{ + "object" : "dapi_command", + "data" : { + "command" : "broadcast_message", + "subcommand" : "tx", + "from_uid" : "1337", + "to_uid" : "1337", + "payload" : "SIG" + } +} \ No newline at end of file diff --git a/src/test/data/dapi-get-private-data.js b/src/test/data/dapi-get-private-data.js new file mode 100644 index 000000000000..0ed159865d36 --- /dev/null +++ b/src/test/data/dapi-get-private-data.js @@ -0,0 +1,10 @@ +{ + "object" : "dapi_command", + "data" : { + "command" : "get_private_data", + "from_uid" : "1337", + "to_uid" : "1337", + "signature" : "SIG", + "slot" : 1 + } +} \ No newline at end of file diff --git a/src/test/data/dapi-get-profile.js b/src/test/data/dapi-get-profile.js new file mode 100644 index 000000000000..3841b7a119ae --- /dev/null +++ b/src/test/data/dapi-get-profile.js @@ -0,0 +1,10 @@ +{ + "object" : "dapi_command", + "data" : { + "command" : "get_profile", + "from_uid" : "1337", + "to_uid" : "1337", + "signature" : "SIG", + "fields" : ["fname", "lname"] + } +} \ No newline at end of file diff --git a/src/test/data/dapi-invite-user.js b/src/test/data/dapi-invite-user.js new file mode 100644 index 000000000000..631d2ae819c1 --- /dev/null +++ b/src/test/data/dapi-invite-user.js @@ -0,0 +1,12 @@ +{ + "object" : "dapi_command", + "data" : { + "id" : "1111", + "command" : "invite_user", + "from_uid" : "evan", + "to_uid" : "cryptofish82", + "to_name" : "Evan Duffield", + "to_email" : "cryptofish82@gmail.com", + "to_pubkey" : "Xf4qeg8A7efL6pfZyxKwnH6dbvydnpJ4Cn" + } +} \ No newline at end of file diff --git a/src/test/data/dapi-search-users.js b/src/test/data/dapi-search-users.js new file mode 100644 index 000000000000..a191550678b1 --- /dev/null +++ b/src/test/data/dapi-search-users.js @@ -0,0 +1,9 @@ +{ + "object" : "dapi_command", + "data" : { + "command" : "search_users", + "match_fields" : {"fiat_converter" : 1}, + "start" : 0, + "limit" : 10 + } +} \ No newline at end of file diff --git a/src/test/data/dapi-set-private-data.js b/src/test/data/dapi-set-private-data.js new file mode 100644 index 000000000000..4c577769396b --- /dev/null +++ b/src/test/data/dapi-set-private-data.js @@ -0,0 +1,11 @@ +{ + "object" : "dapi_command", + "data" : { + "command" : "set_private_data", + "from_uid" : "1337", + "to_uid" : "1337", + "signature" : "SIG", + "slot" : 1, + "payload" : "AOEU" + } +} \ No newline at end of file diff --git a/src/test/data/dapi-set-profile.js b/src/test/data/dapi-set-profile.js new file mode 100644 index 000000000000..633793e8bdf2 --- /dev/null +++ b/src/test/data/dapi-set-profile.js @@ -0,0 +1,12 @@ +{ + "object" : "dapi_command", + "data" : { + "command": "set_profile", + "from_uid": "1337", + "to_uid": "1337", + "signature": "", + "update" : [ + {"field":"name","value":"uberevan"} + ] + } +} diff --git a/src/test/data/dapi-validate-account.js b/src/test/data/dapi-validate-account.js new file mode 100644 index 000000000000..294ab17cc419 --- /dev/null +++ b/src/test/data/dapi-validate-account.js @@ -0,0 +1,10 @@ +{ + "object" : "dapi_command", + "data" : { + "id" : "1111", + "command" : "validate_account", + "from_uid" : "evan", + "to_uid" : "cryptofish82", + "to_challenge_code" : "321017" + } +} \ No newline at end of file