diff --git a/js/remote.js b/js/remote.js index a669c91be6..bed251dd7c 100644 --- a/js/remote.js +++ b/js/remote.js @@ -5,11 +5,13 @@ // // YYY Will later provide a network access which use multiple instances of this. // YYY A better model might be to allow requesting a target state: keep connected or not. +// XXX Make subscribe target state. +// XXX Auto subscribe on connect. // // Node -var util = require('util'); -var events = require('events'); +var util = require('util'); +var EventEmitter = require('events').EventEmitter; // npm var WebSocket = require('ws'); @@ -17,9 +19,9 @@ var WebSocket = require('ws'); var amount = require('./amount.js'); var Amount = amount.Amount; -// Events emmitted: -// 'success' -// 'error' +// Request events emmitted: +// 'success' : Request successful. +// 'error' : Request failed. // 'remoteError' // 'remoteUnexpected' // 'remoteDisconnected' @@ -33,11 +35,11 @@ var Request = function (remote, command) { this.on('request', this.request_default); }; -Request.prototype = new events.EventEmitter; +Request.prototype = new EventEmitter; // Return this. node EventEmitter's on doesn't return this. Request.prototype.on = function (e, c) { - events.EventEmitter.prototype.on.call(this, e, c); + EventEmitter.prototype.on.call(this, e, c); return this; }; @@ -120,7 +122,7 @@ var Remote = function (trusted, websocket_ip, websocket_port, config, trace) { }; }; -Remote.prototype = new events.EventEmitter; +Remote.prototype = new EventEmitter; var remoteConfig = function (config, server, trace) { var serverConfig = config.servers[server]; @@ -128,6 +130,14 @@ var remoteConfig = function (config, server, trace) { return new Remote(serverConfig.trusted, serverConfig.websocket_ip, serverConfig.websocket_port, config, trace); }; +var isTemMalformed = function (engine_result_code) { + return (engine_result_code >= -299 && engine_result_code < 199); +}; + +var isTefFailure = function (engine_result_code) { + return (engine_result_code >= -299 && engine_result_code < 199); +}; + var flags = { 'OfferCreate' : { 'Passive' : 0x00010000, @@ -217,27 +227,28 @@ Remote.prototype.connect_helper = function () { else { switch (message.type) { case 'response': - { - request = ws.response[message.id]; + { + request = ws.response[message.id]; - if (!request) { - unexpected = true; - } - else if ('success' === message.result) { - if (self.trace) console.log("message: %s", json); + if (!request) { + unexpected = true; + } + else if ('success' === message.result) { + if (self.trace) console.log("message: %s", json); - request.emit('success', message); - } - else if (message.error) { - if (self.trace) console.log("message: %s", json); + request.emit('success', message); + } + else if (message.error) { + if (self.trace) console.log("message: %s", json); - request.emit('error', { - 'error' : 'remoteError', - 'error_message' : 'Remote reported an error.', - 'remote' : message, - }); + request.emit('error', { + 'error' : 'remoteError', + 'error_message' : 'Remote reported an error.', + 'remote' : message, + }); + } } - } + break; case 'ledgerClosed': // XXX If not trusted, need to verify we consider ledger closed. @@ -247,8 +258,8 @@ Remote.prototype.connect_helper = function () { self.ledger_closed = message.ledger_closed; self.ledger_current_index = message.ledger_closed_index + 1; - - self.emit('ledger_closed'); + + self.emit('ledger_closed', self.ledger_closed, self.ledger_closed_index); break; default: @@ -319,7 +330,7 @@ Remote.prototype.disconnect = function (done) { var self = this; var ws = this.ws; - if (self.trace) console.log("remote: disconnect"); + if (this.trace) console.log("remote: disconnect"); ws.onclose = function () { if (self.trace) console.log("remote: onclose: %s", ws.readyState); @@ -333,8 +344,6 @@ Remote.prototype.disconnect = function (done) { // Send a request. // <-> request: what to send, consumed. Remote.prototype.request = function (request) { - var self = this; - this.ws.response[request.message.id = this.id] = request; this.id += 1; // Advance id. @@ -356,7 +365,6 @@ Remote.prototype.request_ledger_current = function () { return new Request(this, 'ledger_current'); }; -// <-> request: // --> ledger : optional // --> ledger_index : optional Remote.prototype.request_ledger_entry = function (type) { @@ -415,6 +423,19 @@ Remote.prototype.request_ledger_entry = function (type) { return request; }; +// --> ledger_closed : optional +Remote.prototype.request_transaction_entry = function (hash, ledger_closed) { + assert(this.trusted); // If not trusted, need to check proof, maybe talk packet protocol. + + var request = new Request(this, 'transaction_entry'); + + request.message.transaction = hash; + if (ledger_closed) + request.message.ledger_closed = ledger_closed; + + return request; +}; + // Submit a transaction. Remote.prototype.submit = function (transaction) { var self = this; @@ -479,15 +500,17 @@ Remote.prototype.server_subscribe = function () { var request = new Request(this, 'server_subscribe'); - request.on('success', function (message) { - self.ledger_current_index = message.ledger_current_index; - self.ledger_closed = message.ledger_closed; - self.stand_alone = message.stand_alone; + request. + on('success', function (message) { + self.ledger_closed = message.ledger_closed; + self.ledger_current_index = message.ledger_current_index; + self.stand_alone = !!message.stand_alone; - self.emit('subscribed'); + self.emit('subscribed'); - self.emit('ledger_closed'); - }); + self.emit('ledger_closed', self.ledger_closed, self.ledger_current_index-1); + }) + .request(); // XXX Could give error events, maybe even time out. @@ -497,14 +520,14 @@ Remote.prototype.server_subscribe = function () { // Ask the remote to accept the current ledger. // - To be notified when the ledger is accepted, server_subscribe() then listen to 'ledger_closed' events. Remote.prototype.ledger_accept = function () { - if (this.stand_alone) + if (this.stand_alone || undefined === this.stand_alone) { var request = new Request(this, 'ledger_accept'); request.request(); } else { - self.emit('error', { + this.emit('error', { 'error' : 'notStandAlone' }); } @@ -565,29 +588,139 @@ Remote.prototype.transaction = function () { // // Transactions // +// Transaction 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 honest clients after a tesSUCCESS or ter*. +// 'state' : Follow the state of a transaction. +// 'clientSubmitted' - Sent to remote +// |- 'remoteError' - Remote rejected transaction. +// \- 'clientProposed' - Remote provisionally accepted transaction. +// |- 'clientMissing' - Transaction has not appeared in ledger as expected. +// | |- 'clientLost' - No longer monitoring missing transaction. +// |/ +// |- 'tesSUCCESS' - Transaction in ledger as expected. +// |- 'ter...' - Transaction failed. +// |- 'tep...' - Transaction partially succeeded. +// +// Notes: +// - All transactions including locally errors and malformed errors may be +// forwarded. +// - 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) { - this.prototype = events.EventEmitter; // XXX Node specific. + var self = this; + + this.prototype = EventEmitter; // XXX Node specific. this.remote = remote; this.secret = undefined; - this.transaction = {}; // Transaction data. + this.transaction = { // 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.transaction.hash; + + self.set_state('clientProposed'); + + self.emit('proposed', { + '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 events.EventEmitter; +Transaction.prototype = new EventEmitter; // Return this. node EventEmitter's on doesn't return this. Transaction.prototype.on = function (e, c) { - events.EventEmitter.prototype.on.call(this, e, c); + EventEmitter.prototype.on.call(this, e, c); return this; }; +Transaction.prototype.consts = { + 'telLOCAL_ERROR' : -399, + 'temMALFORMED' : -299, + 'tefFAILURE' : -199, + 'terRETRY' : -99, + 'tesSUCCESS' : 0, + 'tepPARTIAL' : 100, +}; + +Transaction.prototype.isTelLocal = function (ter) { + return ter >= this.consts.telLOCAL_ERROR && ter < this.consts.temMALFORMED; +}; + +Transaction.prototype.isTemMalformed = function (ter) { + return ter >= this.consts.temMALFORMED && ter < this.consts.tefFAILURE; +}; + +Transaction.prototype.isTefFailure = function (ter) { + return ter >= this.consts.tefFAILURE && ter < this.consts.terRETRY; +}; + +Transaction.prototype.isTerRetry = function (ter) { + return ter >= this.consts.terRETRY && ter < this.consts.tesSUCCESS; +}; + +Transaction.prototype.isTepSuccess = function (ter) { + return ter >= this.consts.tesSUCCESS; +}; + +Transaction.prototype.isTepPartial = function (ter) { + return ter >= this.consts.tepPATH_PARTIAL; +}; + +Transaction.prototype.isRejected = function (ter) { + return this.isTelLocal(ter) || this.isTemMalformed(ter) || this.isTefFailure(ter); +}; + +Transaction.prototype.set_state = function (state) { + if (this.state !== state) { + this.state = state; + this.emit('state', state); + } +}; + // Submit a transaction to the network. +// XXX Don't allow a submit without knowing ledger_closed_index. +// XXX Have a network canSubmit(), post events for following. +// XXX Also give broader status for tracking through network disconnects. Transaction.prototype.submit = function () { + var self = this; var transaction = this.transaction; if (undefined === transaction.Fee) { @@ -601,6 +734,50 @@ Transaction.prototype.submit = function () { } } + if (this.listeners('final').length) { + // There are listeners for 'final' arrange to emit it. + + this.submit_index = this.remote.ledger_current_index; + + var on_ledger_closed = function (ledger_closed, ledger_closed_index) { + var stop = false; + +// XXX make sure self.hash is available. + self.remote.request_transaction_entry(self.hash, ledger_closed) + .on('success', function (message) { + // XXX Fake results for now. + if (!message.metadata.result) + message.metadata.result = 'tesSUCCESS'; + + self.set_state(message.metadata.result); // XXX Untested. + self.emit('final', message); + }) + .on('error', function (message) { + if ('remoteError' === message.error + && 'transactionNotFound' === message.remote.error) { + if (self.submit_index + SUBMIT_LOST < ledger_closed_index) { + self.set_state('clientLost'); // Gave up. + stop = true; + } + else if (self.submit_index + SUBMIT_MISSING < ledger_closed_index) { + self.set_state('clientMissing'); // We don't know what happened to transaction, still might find. + } + } + // XXX Could log other unexpectedness. + }) + .request(); + + if (stop) { + self.removeListener('ledger_closed', on_ledger_closed); + self.emit('final', message); + } + }; + + this.remote.on('ledger_closed', on_ledger_closed); + } + + this.set_state('clientSubmitted'); + this.remote.submit(this); return this; @@ -628,7 +805,7 @@ Transaction.prototype.flags = function (flags) { if (flags) { var transaction_flags = exports.flags[this.transaction.TransactionType]; - if (undefined == this.transaction.Flags) + if (undefined == this.transaction.Flags) // We plan to not define this field on new Transaction. this.transaction.Flags = 0; var flag_set = 'object' === typeof flags ? flags : [ flags ]; @@ -655,12 +832,18 @@ Transaction.prototype.flags = function (flags) { // // Transactions // -// remote.transaction() // Build a transaction object. +// Construction: +// remote.transaction() // Build a transaction object. // .offer_create(...) // Set major parameters. // .flags() // Set optional parameters. // .on() // Register for events. // .submit(); // Send to network. // +// Events: +// 'success' // Transaction was successfully submitted: hash, proposed TER +// 'error' // Error submitting transaction. +// 'closed' // Result from closed ledger: TER +// // Allow config account defaults to be used. Transaction.prototype.account_default = function (account) { diff --git a/src/SerializedObject.cpp b/src/SerializedObject.cpp index 6bcae3c067..d6bce836cb 100644 --- a/src/SerializedObject.cpp +++ b/src/SerializedObject.cpp @@ -926,7 +926,19 @@ std::auto_ptr STObject::parseJson(const Json::Value& object, SField::r { case STI_UINT8: if (value.isString()) + { +#if 0 + if (field == sfTransactionResult) + { + TER terCode; + if (FUNCTION_THAT_DOESNT_EXIST(value.asString(), terCode)) + value = static_cast(terCode); + else + data.push_back(new STUInt8(field, lexical_cast_st(value.asString()))); + } data.push_back(new STUInt8(field, lexical_cast_st(value.asString()))); +#endif + } else if (value.isInt()) { if (value.asInt() < 0 || value.asInt() > 255) diff --git a/src/SerializedTypes.cpp b/src/SerializedTypes.cpp index 3f9f5c9b65..73d2e440f5 100644 --- a/src/SerializedTypes.cpp +++ b/src/SerializedTypes.cpp @@ -11,6 +11,9 @@ #include "NewcoinAddress.h" #include "utils.h" #include "NewcoinAddress.h" +#include "TransactionErr.h" + +SETUP_LOG(); STAmount saZero(CURRENCY_ONE, ACCOUNT_ONE, 0); STAmount saOne(CURRENCY_ONE, ACCOUNT_ONE, 1); @@ -61,11 +64,25 @@ STUInt8* STUInt8::construct(SerializerIterator& u, SField::ref name) std::string STUInt8::getText() const { + if (getFName() == sfTransactionResult) + { + std::string token, human; + if (transResultInfo(static_cast(value), token, human)) + return human; + } return boost::lexical_cast(value); } Json::Value STUInt8::getJson(int) const { + if (getFName() == sfTransactionResult) + { + std::string token, human; + if (transResultInfo(static_cast(value), token, human)) + return token; + else + cLog(lsWARNING) << "Unknown result code in metadata: " << value; + } return value; } @@ -339,7 +356,7 @@ STPathSet* STPathSet::construct(SerializerIterator& s, SField::ref name) { if (path.empty()) { - Log(lsINFO) << "STPathSet: Empty path."; + cLog(lsINFO) << "STPathSet: Empty path."; throw std::runtime_error("empty path"); } @@ -354,7 +371,7 @@ STPathSet* STPathSet::construct(SerializerIterator& s, SField::ref name) } else if (iType & ~STPathElement::typeValidBits) { - Log(lsINFO) << "STPathSet: Bad path element: " << iType; + cLog(lsINFO) << "STPathSet: Bad path element: " << iType; throw std::runtime_error("bad path element"); } diff --git a/src/TransactionAction.cpp b/src/TransactionAction.cpp index e0c61014e1..81b9fe85d5 100644 --- a/src/TransactionAction.cpp +++ b/src/TransactionAction.cpp @@ -253,7 +253,13 @@ TER TransactionEngine::doCreditSet(const SerializedTransaction& txn) // Check if destination makes sense. - if (!uDstAccountID) + if (saLimitAmount.isNegative()) + { + Log(lsINFO) << "doCreditSet: Malformed transaction: Negatived credit limit."; + + return temBAD_AMOUNT; + } + else if (!uDstAccountID) { Log(lsINFO) << "doCreditSet: Malformed transaction: Destination account not specifed."; diff --git a/src/TransactionErr.cpp b/src/TransactionErr.cpp index 85ea95594b..f85f0cda4b 100644 --- a/src/TransactionErr.cpp +++ b/src/TransactionErr.cpp @@ -8,57 +8,57 @@ bool transResultInfo(TER terCode, std::string& strToken, std::string& strHuman) const char* cpToken; const char* cpHuman; } transResultInfoA[] = { - { tefALREADY, "tefALREADY", "The exact transaction was already in this ledger" }, - { tefBAD_ADD_AUTH, "tefBAD_ADD_AUTH", "Not authorized to add account." }, - { tefBAD_AUTH, "tefBAD_AUTH", "Transaction's public key is not authorized." }, - { tefBAD_CLAIM_ID, "tefBAD_CLAIM_ID", "Malformed." }, - { tefBAD_GEN_AUTH, "tefBAD_GEN_AUTH", "Not authorized to claim generator." }, - { tefBAD_LEDGER, "tefBAD_LEDGER", "Ledger in unexpected state." }, - { tefCLAIMED, "tefCLAIMED", "Can not claim a previously claimed account." }, - { tefEXCEPTION, "tefEXCEPTION", "Unexpected program state." }, - { tefCREATED, "tefCREATED", "Can't add an already created account." }, - { tefGEN_IN_USE, "tefGEN_IN_USE", "Generator already in use." }, - { tefPAST_SEQ, "tefPAST_SEQ", "This sequence number has already past" }, + { tefALREADY, "tefALREADY", "The exact transaction was already in this ledger." }, + { tefBAD_ADD_AUTH, "tefBAD_ADD_AUTH", "Not authorized to add account." }, + { tefBAD_AUTH, "tefBAD_AUTH", "Transaction's public key is not authorized." }, + { tefBAD_CLAIM_ID, "tefBAD_CLAIM_ID", "Malformed." }, + { tefBAD_GEN_AUTH, "tefBAD_GEN_AUTH", "Not authorized to claim generator." }, + { tefBAD_LEDGER, "tefBAD_LEDGER", "Ledger in unexpected state." }, + { tefCLAIMED, "tefCLAIMED", "Can not claim a previously claimed account." }, + { tefEXCEPTION, "tefEXCEPTION", "Unexpected program state." }, + { tefCREATED, "tefCREATED", "Can't add an already created account." }, + { tefGEN_IN_USE, "tefGEN_IN_USE", "Generator already in use." }, + { tefPAST_SEQ, "tefPAST_SEQ", "This sequence number has already past" }, - { telBAD_PATH_COUNT, "telBAD_PATH_COUNT", "Malformed: too many paths." }, - { telINSUF_FEE_P, "telINSUF_FEE_P", "Fee insufficient." }, + { telBAD_PATH_COUNT, "telBAD_PATH_COUNT", "Malformed: too many paths." }, + { telINSUF_FEE_P, "telINSUF_FEE_P", "Fee insufficient." }, - { temBAD_AMOUNT, "temBAD_AMOUNT", "Can only send positive amounts." }, + { temBAD_AMOUNT, "temBAD_AMOUNT", "Can only send positive amounts." }, { temBAD_AUTH_MASTER, "temBAD_AUTH_MASTER", "Auth for unclaimed account needs correct master key." }, - { temBAD_EXPIRATION, "temBAD_EXPIRATION", "Malformed." }, - { temBAD_ISSUER, "temBAD_ISSUER", "Malformed." }, - { temBAD_OFFER, "temBAD_OFFER", "Malformed." }, - { temBAD_PATH, "temBAD_PATH", "Malformed." }, - { temBAD_PATH_LOOP, "temBAD_PATH_LOOP", "Malformed." }, - { temBAD_PUBLISH, "temBAD_PUBLISH", "Malformed: bad publish." }, - { temBAD_TRANSFER_RATE, "temBAD_TRANSFER_RATE", "Malformed: transfer rate must be >= 1.0" }, - { temBAD_SET_ID, "temBAD_SET_ID", "Malformed." }, - { temCREATEXNS, "temCREATEXNS", "Can not specify non XNS for Create." }, - { temDST_IS_SRC, "temDST_IS_SRC", "Destination may not be source." }, - { temDST_NEEDED, "temDST_NEEDED", "Destination not specified." }, - { temINSUF_FEE_P, "temINSUF_FEE_P", "Fee not allowed." }, - { temINVALID, "temINVALID", "The transaction is ill-formed" }, - { temREDUNDANT, "temREDUNDANT", "Sends same currency to self." }, - { temRIPPLE_EMPTY, "temRIPPLE_EMPTY", "PathSet with no paths." }, + { temBAD_EXPIRATION, "temBAD_EXPIRATION", "Malformed." }, + { temBAD_ISSUER, "temBAD_ISSUER", "Malformed." }, + { temBAD_OFFER, "temBAD_OFFER", "Malformed." }, + { temBAD_PATH, "temBAD_PATH", "Malformed." }, + { temBAD_PATH_LOOP, "temBAD_PATH_LOOP", "Malformed." }, + { temBAD_PUBLISH, "temBAD_PUBLISH", "Malformed: Bad publish." }, + { temBAD_TRANSFER_RATE, "temBAD_TRANSFER_RATE", "Malformed: Transfer rate must be >= 1.0" }, + { temBAD_SET_ID, "temBAD_SET_ID", "Malformed." }, + { temCREATEXNS, "temCREATEXNS", "Can not specify non XNS for Create." }, + { temDST_IS_SRC, "temDST_IS_SRC", "Destination may not be source." }, + { temDST_NEEDED, "temDST_NEEDED", "Destination not specified." }, + { temINSUF_FEE_P, "temINSUF_FEE_P", "Fee not allowed." }, + { temINVALID, "temINVALID", "The transaction is ill-formed." }, + { temREDUNDANT, "temREDUNDANT", "Sends same currency to self." }, + { temRIPPLE_EMPTY, "temRIPPLE_EMPTY", "PathSet with no paths." }, { temUNCERTAIN, "temUNCERTAIN", "In process of determining result. Never returned." }, { temUNKNOWN, "temUNKNOWN", "The transactions requires logic not implemented yet." }, - { tepPATH_DRY, "tepPATH_DRY", "Path could not send partial amount." }, - { tepPATH_PARTIAL, "tepPATH_PARTIAL", "Path could not send full amount." }, + { tepPATH_DRY, "tepPATH_DRY", "Path could not send partial amount." }, + { tepPATH_PARTIAL, "tepPATH_PARTIAL", "Path could not send full amount." }, - { terDIR_FULL, "terDIR_FULL", "Can not add entry to full dir." }, + { terDIR_FULL, "terDIR_FULL", "Can not add entry to full dir." }, { terFUNDS_SPENT, "terFUNDS_SPENT", "Can't set password, password set funds already spent." }, - { terINSUF_FEE_B, "terINSUF_FEE_B", "Account balance can't pay fee." }, - { terNO_ACCOUNT, "terNO_ACCOUNT", "The source account does not exist." }, - { terNO_DST, "terNO_DST", "The destination does not exist" }, - { terNO_LINE, "terNO_LINE", "No such line." }, + { terINSUF_FEE_B, "terINSUF_FEE_B", "Account balance can't pay fee." }, + { terNO_ACCOUNT, "terNO_ACCOUNT", "The source account does not exist." }, + { terNO_DST, "terNO_DST", "The destination does not exist." }, + { terNO_LINE, "terNO_LINE", "No such line." }, { terNO_LINE_NO_ZERO, "terNO_LINE_NO_ZERO", "Can't zero non-existant line, destination might make it." }, - { terOFFER_NOT_FOUND, "terOFFER_NOT_FOUND", "Can not cancel offer." }, - { terPRE_SEQ, "terPRE_SEQ", "Missing/inapplicable prior transaction" }, - { terSET_MISSING_DST, "terSET_MISSING_DST", "Can't set password, destination missing." }, + { terOFFER_NOT_FOUND, "terOFFER_NOT_FOUND", "Can not cancel offer." }, + { terPRE_SEQ, "terPRE_SEQ", "Missing/inapplicable prior transaction." }, + { terSET_MISSING_DST, "terSET_MISSING_DST", "Can't set password, destination missing." }, { terUNFUNDED, "terUNFUNDED", "Source account had insufficient balance for transaction." }, - { tesSUCCESS, "tesSUCCESS", "The transaction was applied" }, + { tesSUCCESS, "tesSUCCESS", "The transaction was applied." }, }; int iIndex = NUMBER(transResultInfoA); diff --git a/src/TransactionErr.h b/src/TransactionErr.h index 652a23a21f..adb381909f 100644 --- a/src/TransactionErr.h +++ b/src/TransactionErr.h @@ -69,7 +69,7 @@ enum TER // aka TransactionEngineResult // -99 .. -1: R Retry (sequence too high, no funds for txn fee, originating account non-existent) // Causes: - // - Priror application of another, possibly non-existant, transaction could allow this transaction to succeed. + // - Prior application of another, possibly non-existant, another transaction could allow this transaction to succeed. // Implications: // - Not applied // - Not forwarded @@ -103,9 +103,10 @@ enum TER // aka TransactionEngineResult // - Applied // - Forwarded // Only allowed as a return code of appliedTransaction when !tapRetry. Otherwise, treated as terRETRY. + // CAUTION: The numerical values for these results are part of the binary formats tepPARTIAL = 100, - tepPATH_DRY, - tepPATH_PARTIAL, + tepPATH_DRY = 101, + tepPATH_PARTIAL = 102, }; #define isTelLocal(x) ((x) >= telLOCAL_ERROR && (x) < temMALFORMED) diff --git a/src/TransactionMeta.cpp b/src/TransactionMeta.cpp index 213542dd0f..30117e1e7f 100644 --- a/src/TransactionMeta.cpp +++ b/src/TransactionMeta.cpp @@ -100,14 +100,8 @@ STObject TransactionMetaSet::getAsObject() const void TransactionMetaSet::addRaw(Serializer& s, TER result) { - mResult = 255; - - if (result == tesSUCCESS) - mResult = 0; - else if (result == tepPATH_DRY) - mResult = 1; - else if (result == tepPATH_PARTIAL) - mResult = 2; + mResult = static_cast(result); + assert((mResult == 0) || ((mResult > 100) && (mResult <= 255))); mNodes.sort(compare); diff --git a/src/WSDoor.cpp b/src/WSDoor.cpp index 0085026b4d..d5c4a03a31 100644 --- a/src/WSDoor.cpp +++ b/src/WSDoor.cpp @@ -86,6 +86,7 @@ public: void doLedgerCurrent(Json::Value& jvResult, const Json::Value& jvRequest); void doLedgerEntry(Json::Value& jvResult, const Json::Value& jvRequest); void doSubmit(Json::Value& jvResult, const Json::Value& jvRequest); + void doTransactionEntry(Json::Value& jvResult, const Json::Value& jvRequest); // Streaming Commands void doAccountInfoSubscribe(Json::Value& jvResult, const Json::Value& jvRequest); @@ -161,7 +162,7 @@ public: { Json::FastWriter jfwWriter; - cLog(lsDEBUG) << "Ws:: Object '" << jfwWriter.write(jvObj) << "'"; + // cLog(lsDEBUG) << "Ws:: Object '" << jfwWriter.write(jvObj) << "'"; send(cpClient, jfwWriter.write(jvObj)); } @@ -311,6 +312,7 @@ Json::Value WSConnection::invokeCommand(const Json::Value& jvRequest) { "ledger_current", &WSConnection::doLedgerCurrent }, { "ledger_entry", &WSConnection::doLedgerEntry }, { "submit", &WSConnection::doSubmit }, + { "transaction_entry", &WSConnection::doTransactionEntry }, // Streaming commands: { "account_info_subscribe", &WSConnection::doAccountInfoSubscribe }, @@ -984,6 +986,52 @@ void WSConnection::doSubmit(Json::Value& jvResult, const Json::Value& jvRequest) } } +void WSConnection::doTransactionEntry(Json::Value& jvResult, const Json::Value& jvRequest) +{ + if (!jvRequest.isMember("transaction")) + { + jvResult["error"] = "fieldNotFoundTransaction"; + } + if (!jvRequest.isMember("ledger_closed")) + { + jvResult["error"] = "notYetImplemented"; // XXX We don't support any transaction yet. + } + else + { + uint256 uTransID; + // XXX Relying on trusted WSS client. Would be better to have a strict routine, returning success or failure. + uTransID.SetHex(jvRequest["transaction"].asString()); + + uint256 uLedgerID; + // XXX Relying on trusted WSS client. Would be better to have a strict routine, returning success or failure. + uLedgerID.SetHex(jvRequest["ledger_closed"].asString()); + + Ledger::pointer lpLedger = theApp->getMasterLedger().getLedgerByHash(uLedgerID); + + if (!lpLedger) { + jvResult["error"] = "ledgerNotFound"; + } + else + { + Transaction::pointer tpTrans; + TransactionMetaSet::pointer tmTrans; + + if (!lpLedger-> getTransaction(uTransID, tpTrans, tmTrans)) + { + jvResult["error"] = "transactionNotFound"; + } + else + { + jvResult["transaction"] = tpTrans->getJson(0); + jvResult["metadata"] = tmTrans->getJson(0); + // 'accounts' + // 'engine_...' + // 'ledger_...' + } + } + } +} + void WSConnection::doTransactionSubcribe(Json::Value& jvResult, const Json::Value& jvRequest) { if (!mNetwork.subTransaction(this)) diff --git a/test/remote-test.js b/test/remote-test.js index e53b5a373d..b45f1a62ab 100644 --- a/test/remote-test.js +++ b/test/remote-test.js @@ -63,7 +63,8 @@ buster.testCase("Remote functions", { console.log(m); buster.assert(false); - }).request(); + }) + .request(); }, 'request_ledger_closed' : @@ -78,7 +79,8 @@ buster.testCase("Remote functions", { console.log("error: %s", m); buster.assert(false); - }).request(); + }) + .request(); }, 'manual account_root success' : @@ -100,13 +102,15 @@ buster.testCase("Remote functions", { console.log("error: %s", m); buster.assert(false); - }).request(); + }) + .request(); }) .on('error', function(m) { console.log("error: %s", m); buster.assert(false); - }).request(); + }) + .request(); }, 'account_root remote malformedAddress' : @@ -129,13 +133,15 @@ buster.testCase("Remote functions", { buster.assert.equals(m.error, 'remoteError'); buster.assert.equals(m.remote.error, 'malformedAddress'); done(); - }).request(); + }) + .request(); }) .on('error', function(m) { console.log("error: %s", m); buster.assert(false); - }).request(); + }) + .request(); }, 'account_root entryNotFound' : @@ -158,7 +164,8 @@ buster.testCase("Remote functions", { buster.assert.equals(m.error, 'remoteError'); buster.assert.equals(m.remote.error, 'entryNotFound'); done(); - }).request(); + }) + .request(); }) .on('error', function(m) { console.log("error: %s", m); @@ -187,13 +194,15 @@ buster.testCase("Remote functions", { console.log("error: %s", m); buster.assert(false); - }).request(); + }). + request(); }) .on('error', function(m) { console.log(m); buster.assert(false); - }).request(); + }) + .request(); }, 'create account' : @@ -211,7 +220,8 @@ buster.testCase("Remote functions", { console.log("error: %s", m); buster.assert(false); - }).submit(); + }) + .submit(); }, });