diff --git a/package.json b/package.json index a70d161903..7ccfd56a2c 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ }, "dependencies": { - "ripple-lib": "0.7.25", + "ripple-lib": "0.7.37", "async": "~0.2.9", "extend": "~1.2.0", "simple-jsonrpc": "~0.0.2", @@ -31,4 +31,4 @@ }, "readmeFilename": "README.md" -} \ No newline at end of file +} diff --git a/test/account_set-test.js b/test/account_set-test.js index 32006ef2cd..2a77c55e1e 100644 --- a/test/account_set-test.js +++ b/test/account_set-test.js @@ -1,35 +1,75 @@ -var async = require("async"); +var async = require('async'); var assert = require('assert'); -var Remote = require("ripple-lib").Remote; -var testutils = require("./testutils"); +var Remote = require('ripple-lib').Remote; +var testutils = require('./testutils'); var config = testutils.init_config(); suite('Account set', function() { var $ = { }; setup(function(done) { - testutils.build_setup().call($, done); + testutils.build_setup().call($, function() { + $.remote.local_signing = true; + done(); + }); }); teardown(function(done) { testutils.build_teardown().call($, done); }); + test('null AccountSet', function(done) { + var self = this; + + var steps = [ + function (callback) { + self.what = 'Send null AccountSet'; + + var transaction = $.remote.transaction().accountSet('root'); + transaction.setFlags(0); + + transaction.once('submitted', function(m) { + assert.strictEqual(m.engine_result, 'tesSUCCESS'); + callback(); + }); + + transaction.submit(); + }, + + function (callback) { + self.what = 'Check account flags'; + + $.remote.requestAccountFlags('root', 'CURRENT', function(err, m) { + assert.ifError(err); + assert.strictEqual(m, 0); + done(); + }); + } + ] + + async.series(steps, function(err) { + assert(!err, self.what + ': ' + err); + done(); + }); + }); + test('set RequireDestTag', function(done) { var self = this; var steps = [ function (callback) { - self.what = "Set RequireDestTag."; + self.what = 'Set RequireDestTag.'; $.remote.transaction() - .account_set("root") + .account_set('root') .set_flags('RequireDestTag') .on('submitted', function (m) { - //console.log("proposed: %s", JSON.stringify(m)); + //console.log('proposed: %s', JSON.stringify(m)); + if (m.engine_result === 'tesSUCCESS') { callback(null); } else { + //console.log(m); callback(new Error(m.engine_result)); } }) @@ -37,14 +77,14 @@ suite('Account set', function() { }, function (callback) { - self.what = "Check RequireDestTag"; + self.what = 'Check RequireDestTag'; $.remote.request_account_flags('root', 'CURRENT') .on('success', function (m) { var wrong = !(m.node.Flags & Remote.flags.account_root.RequireDestTag); if (wrong) { - console.log("Set RequireDestTag: failed: %s", JSON.stringify(m)); + //console.log('Set RequireDestTag: failed: %s', JSON.stringify(m)); } callback(wrong ? new Error(wrong) : null); @@ -53,27 +93,27 @@ suite('Account set', function() { }, function (callback) { - self.what = "Clear RequireDestTag."; + self.what = 'Clear RequireDestTag.'; $.remote.transaction() - .account_set("root") + .account_set('root') .set_flags('OptionalDestTag') .on('submitted', function (m) { - //console.log("proposed: %s", JSON.stringify(m)); - callback(m.engine_result === 'tesSUCCESS' ? null : new Error()); + //console.log('proposed: %s', JSON.stringify(m)); + callback(m.engine_result === 'tesSUCCESS' ? null : m.engine_result); }) .submit(); }, function (callback) { - self.what = "Check No RequireDestTag"; + self.what = 'Check No RequireDestTag'; $.remote.request_account_flags('root', 'CURRENT') .on('success', function (m) { var wrong = !!(m.node.Flags & Remote.flags.account_root.RequireDestTag); if (wrong) { - console.log("Clear RequireDestTag: failed: %s", JSON.stringify(m)); + console.log('Clear RequireDestTag: failed: %s', JSON.stringify(m)); } callback(wrong ? new Error(m) : null); @@ -83,30 +123,30 @@ suite('Account set', function() { ] async.waterfall(steps,function (error) { - assert(!error, self.what); + assert(!error, self.what + ': ' + error); done(); }); }); - test("set RequireAuth", function (done) { + test('set RequireAuth', function (done) { var self = this; var steps = [ function (callback) { - self.what = "Set RequireAuth."; + self.what = 'Set RequireAuth.'; $.remote.transaction() - .account_set("root") + .account_set('root') .set_flags('RequireAuth') .on('submitted', function (m) { - //console.log("proposed: %s", JSON.stringify(m)); + //console.log('proposed: %s', JSON.stringify(m)); callback(m.engine_result === 'tesSUCCESS' ? null : new Error(m)); }) .submit(); }, function (callback) { - self.what = "Check RequireAuth"; + self.what = 'Check RequireAuth'; $.remote.request_account_flags('root', 'CURRENT') .on('error', callback) @@ -114,7 +154,7 @@ suite('Account set', function() { var wrong = !(m.node.Flags & Remote.flags.account_root.RequireAuth); if (wrong) { - console.log("Set RequireAuth: failed: %s", JSON.stringify(m)); + console.log('Set RequireAuth: failed: %s', JSON.stringify(m)); } callback(wrong ? new Error(m) : null); @@ -123,13 +163,13 @@ suite('Account set', function() { }, function (callback) { - self.what = "Clear RequireAuth."; + self.what = 'Clear RequireAuth.'; $.remote.transaction() - .account_set("root") + .account_set('root') .set_flags('OptionalAuth') .on('submitted', function (m) { - //console.log("proposed: %s", JSON.stringify(m)); + //console.log('proposed: %s', JSON.stringify(m)); callback(m.engine_result !== 'tesSUCCESS'); }) @@ -137,7 +177,7 @@ suite('Account set', function() { }, function (callback) { - self.what = "Check No RequireAuth"; + self.what = 'Check No RequireAuth'; $.remote.request_account_flags('root', 'CURRENT') .on('error', callback) @@ -145,7 +185,7 @@ suite('Account set', function() { var wrong = !!(m.node.Flags & Remote.flags.account_root.RequireAuth); if (wrong) { - console.log("Clear RequireAuth: failed: %s", JSON.stringify(m)); + console.log('Clear RequireAuth: failed: %s', JSON.stringify(m)); } callback(wrong ? new Error(m) : null); @@ -156,7 +196,7 @@ suite('Account set', function() { ] async.waterfall(steps, function(error) { - assert(!error, self.what); + assert(!error, self.what + ': ' + error); done(); }); }); @@ -166,20 +206,20 @@ suite('Account set', function() { var steps = [ function (callback) { - self.what = "Set DisallowXRP."; + self.what = 'Set DisallowXRP.'; $.remote.transaction() - .account_set("root") + .account_set('root') .set_flags('DisallowXRP') .on('submitted', function (m) { - //console.log("proposed: %s", JSON.stringify(m)); + //console.log('proposed: %s', JSON.stringify(m)); callback(m.engine_result === 'tesSUCCESS' ? null : new Error(m)); }) .submit(); }, function (callback) { - self.what = "Check DisallowXRP"; + self.what = 'Check DisallowXRP'; $.remote.request_account_flags('root', 'CURRENT') .on('error', callback) @@ -187,7 +227,7 @@ suite('Account set', function() { var wrong = !(m.node.Flags & Remote.flags.account_root.DisallowXRP); if (wrong) { - console.log("Set RequireDestTag: failed: %s", JSON.stringify(m)); + console.log('Set RequireDestTag: failed: %s', JSON.stringify(m)); } callback(wrong ? new Error(m) : null); @@ -196,13 +236,13 @@ suite('Account set', function() { }, function (callback) { - self.what = "Clear DisallowXRP."; + self.what = 'Clear DisallowXRP.'; $.remote.transaction() - .account_set("root") + .account_set('root') .set_flags('AllowXRP') .on('submitted', function (m) { - //console.log("proposed: %s", JSON.stringify(m)); + //console.log('proposed: %s', JSON.stringify(m)); callback(m.engine_result === 'tesSUCCESS' ? null : new Error(m)); }) @@ -210,7 +250,7 @@ suite('Account set', function() { }, function (callback) { - self.what = "Check AllowXRP"; + self.what = 'Check AllowXRP'; $.remote.request_account_flags('root', 'CURRENT') .on('error', callback) @@ -218,7 +258,7 @@ suite('Account set', function() { var wrong = !!(m.node.Flags & Remote.flags.account_root.DisallowXRP); if (wrong) { - console.log("Clear DisallowXRP: failed: %s", JSON.stringify(m)); + console.log('Clear DisallowXRP: failed: %s', JSON.stringify(m)); } callback(wrong ? new Error(m) : null); diff --git a/test/config-example.js b/test/config-example.js index 3b8bfa250d..2ae86b1e6b 100644 --- a/test/config-example.js +++ b/test/config-example.js @@ -22,14 +22,15 @@ exports.default_server_config = { exports.servers = { // A local test server. "alpha" : { + //'trace': true, + 'websocket_ip': "127.0.0.1", + 'websocket_port': 5006, + 'websocket_ssl': false, 'trusted' : true, // "peer_ip" : "0.0.0.0", // "peer_port" : 51235, 'rpc_ip' : "0.0.0.0", 'rpc_port' : 5005, - 'websocket_ip' : "127.0.0.1", - 'websocket_port' : 5006, - 'websocket_ssl' : false, 'local_sequence' : true, 'local_fee' : true, // 'validation_seed' : "shhDFVsmS2GSu5vUyZSPXYfj1r79h", diff --git a/test/no-ripple-test.js b/test/no-ripple-test.js new file mode 100644 index 0000000000..cc3ec35d6d --- /dev/null +++ b/test/no-ripple-test.js @@ -0,0 +1,328 @@ +var async = require('async'); +var assert = require('assert'); +var ripple = require('ripple-lib'); +var Amount = require('ripple-lib').Amount; +var Remote = require('ripple-lib').Remote; +var Transaction = require('ripple-lib').Transaction; +var Server = require('./server').Server; +var testutils = require('./testutils'); +var config = testutils.init_config(); + +suite('NoRipple', function() { + var $ = { }; + + setup(function(done) { + testutils.build_setup().call($, done); + }); + + teardown(function(done) { + testutils.build_teardown().call($, done); + }); + + test('set and clear NoRipple', function(done) { + var self = this; + + var steps = [ + + function (callback) { + self.what = 'Create accounts.'; + testutils.create_accounts($.remote, 'root', '10000.0', [ 'alice' ], callback); + }, + + function (callback) { + self.what = 'Check a non-existent credit limit'; + + $.remote.request_ripple_balance('alice', 'root', 'USD', 'CURRENT', function(err) { + assert.strictEqual('remoteError', err.error); + assert.strictEqual('entryNotFound', err.remote.error); + callback(); + }); + }, + + function (callback) { + self.what = 'Create a credit limit with NoRipple flag'; + + var tx = $.remote.transaction(); + tx.trustSet('root', '100/USD/alice'); + tx.setFlags('NoRipple'); + + tx.once('submitted', function() { + $.remote.ledger_accept(); + }); + + tx.once('error', callback); + tx.once('proposed', function(res) { + callback(); + }); + + tx.submit(); + }, + + function (callback) { + self.what = 'Check no-ripple sender'; + + $.remote.requestAccountLines('root', void(0), 'CURRENT', function(err, m) { + if (err) return callback(err); + assert(typeof m === 'object'); + assert(Array.isArray(m.lines)); + assert(m.lines[0].no_ripple); + callback(); + }); + }, + + function (callback) { + self.what = 'Check no-ripple destination'; + + $.remote.requestAccountLines('alice', void(0), 'CURRENT', function(err, m) { + if (err) return callback(err); + assert(typeof m === 'object'); + assert(Array.isArray(m.lines)); + assert(m.lines[0].no_ripple_peer); + callback(); + }); + }, + + function (callback) { + self.what = 'Create a credit limit with ClearNoRipple flag'; + + var tx = $.remote.transaction(); + tx.trustSet('root', '100/USD/alice'); + tx.setFlags('ClearNoRipple'); + + tx.once('submitted', function() { + $.remote.ledger_accept(); + }); + + tx.once('error', callback); + tx.once('proposed', function(res) { + callback(); + }); + + tx.submit(); + }, + + function (callback) { + self.what = 'Check no-ripple cleared sender'; + + $.remote.requestAccountLines('root', void(0), 'CURRENT', function(err, m) { + if (err) return callback(err); + assert(typeof m === 'object'); + assert(Array.isArray(m.lines)); + assert(!m.lines[0].no_ripple); + callback(); + }); + }, + + function (callback) { + self.what = 'Check no-ripple cleared destination'; + + $.remote.requestAccountLines('alice', void(0), 'CURRENT', function(err, m) { + if (err) return callback(err); + assert(typeof m === 'object'); + assert(Array.isArray(m.lines)); + assert(!m.lines[0].no_ripple_peer); + callback(); + }); + } + + ] + + async.series(steps, function(err) { + assert(!err, self.what + ': ' + err); + done(); + }); + }); + + test('set NoRipple on line with negative balance', function(done) { + // Setting NoRipple on a line with negative balance should fail + var self = this; + + var steps = [ + + function (callback) { + self.what = 'Create accounts'; + + testutils.create_accounts( + $.remote, + 'root', + '10000.0', + [ 'alice', 'bob', 'carol' ], + callback); + }, + + function (callback) { + self.what = 'Set credit limits'; + + testutils.credit_limits($.remote, { + bob: '100/USD/alice', + carol: '100/USD/bob' + }, callback); + }, + + function (callback) { + self.what = 'Payment'; + + var tx = $.remote.transaction(); + tx.buildPath(true); + tx.payment('alice', 'carol', '50/USD/carol'); + + tx.once('submitted', function(m) { + assert.strictEqual(m.engine_result, 'tesSUCCESS'); + $.remote.ledger_accept(); + }); + + tx.submit(callback); + }, + + function (callback) { + self.what = 'Set NoRipple alice'; + + var tx = $.remote.transaction(); + tx.trustSet('alice', '100/USD/bob'); + tx.setFlags('NoRipple'); + + tx.once('submitted', function(m) { + assert.strictEqual(m.engine_result, 'tesSUCCESS'); + $.remote.ledger_accept(); + }); + + tx.submit(callback); + }, + + function (callback) { + self.what = 'Set NoRipple carol'; + + var tx = $.remote.transaction(); + tx.trustSet('bob', '100/USD/carol'); + tx.setFlags('NoRipple'); + + tx.once('submitted', function(m) { + assert.strictEqual(m.engine_result, 'tesSUCCESS'); + $.remote.ledger_accept(); + }); + + tx.submit(callback); + }, + + function (callback) { + self.what = 'Find path alice > carol'; + + var request = $.remote.requestRipplePathFind('alice', 'carol', '1/USD/carol', [ { currency: 'USD' } ]); + request.callback(function(err, paths) { + assert.ifError(err); + assert(Array.isArray(paths.alternatives)); + assert.strictEqual(paths.alternatives.length, 1); + callback(); + }); + }, + + function (callback) { + $.remote.requestAccountLines('alice', function(err, res) { + assert.ifError(err); + assert.strictEqual(typeof res, 'object'); + assert(Array.isArray(res.lines)); + assert.strictEqual(res.lines.length, 1); + assert(!(res.lines[0].no_ripple)); + callback(); + }); + } + + ] + + async.series(steps, function(error) { + assert(!error, self.what + ': ' + error); + done(); + }); + }); + + test('pairwise NoRipple', function(done) { + var self = this; + + var steps = [ + + function (callback) { + self.what = 'Create accounts'; + + testutils.create_accounts( + $.remote, + 'root', + '10000.0', + [ 'alice', 'bob', 'carol' ], + callback); + }, + + function (callback) { + self.what = 'Set credit limits'; + + testutils.credit_limits($.remote, { + bob: '100/USD/alice', + carol: '100/USD/bob' + }, callback); + }, + + function (callback) { + self.what = 'Set NoRipple alice'; + + var tx = $.remote.transaction(); + tx.trustSet('bob', '100/USD/alice'); + tx.setFlags('NoRipple'); + + tx.once('submitted', function(m) { + assert.strictEqual(m.engine_result, 'tesSUCCESS'); + $.remote.ledger_accept(); + }); + + tx.submit(callback); + }, + + function (callback) { + self.what = 'Set NoRipple carol'; + + var tx = $.remote.transaction(); + tx.trustSet('bob', '100/USD/carol'); + tx.setFlags('NoRipple'); + + tx.once('submitted', function(m) { + assert.strictEqual(m.engine_result, 'tesSUCCESS'); + $.remote.ledger_accept(); + }); + + tx.submit(callback); + }, + + function (callback) { + self.what = 'Find path alice > carol'; + + var request = $.remote.requestRipplePathFind('alice', 'carol', '1/USD/carol', [ { currency: 'USD' } ]); + request.callback(function(err, paths) { + assert.ifError(err); + assert(Array.isArray(paths.alternatives)); + assert.strictEqual(paths.alternatives.length, 0); + callback(); + }); + }, + + function (callback) { + self.what = 'Payment'; + + var tx = $.remote.transaction(); + tx.buildPath(true); + tx.payment('alice', 'carol', '1/USD/carol'); + + tx.once('submitted', function(m) { + assert.strictEqual(m.engine_result, 'tecPATH_DRY'); + $.remote.ledger_accept(); + callback(); + }); + + tx.submit(); + } + + ] + + async.series(steps, function(error) { + assert(!error, self.what + ': ' + error); + done(); + }); + }); +}); diff --git a/test/offer-test.js b/test/offer-test.js index 02a4c3d6c3..97a5f99c06 100644 --- a/test/offer-test.js +++ b/test/offer-test.js @@ -699,8 +699,7 @@ suite("Offer tests", function() { }); }); - test("//new user offer_create then ledger_accept then offer_cancel then ledger_accept.", function (done) { - return done(); + test.skip("new user offer_create then ledger_accept then offer_cancel then ledger_accept.", function (done) { var self = this; var final_create; @@ -1415,7 +1414,7 @@ suite("Offer cross currency", function() { $.remote.transaction() .payment("alice", "bob", "30/EUR/bitstamp") .send_max("333/USD/mtgox") - .path_add( [ { currency: "XRP" } ]) + .pathAdd( [ { currency: "XRP" } ]) .once('submitted', function (m) { //console.log("PROPOSED: %s", JSON.stringify(m)); diff --git a/test/path-test.js b/test/path-test.js index 411657120b..b3c0db1cb3 100644 --- a/test/path-test.js +++ b/test/path-test.js @@ -1013,9 +1013,7 @@ suite('More path finding', function() { //carol holds mtgoxAUD, sells mtgoxAUD for XRP //bob will hold mtgoxAUD //alice pays bob mtgoxAUD using XRP - test("via gateway : FIX ME fails due to XRP rounding and not properly handling dry.", function (done) { - return done(); - + test.skip("via gateway : FIX ME fails due to XRP rounding and not properly handling dry.", function (done) { var self = this; async.waterfall([ @@ -1220,11 +1218,9 @@ suite('Quality paths', function() { }); }); - test("// quality payment (BROKEN DUE TO ROUNDING)", function (done) { + test.skip("quality payment (BROKEN DUE TO ROUNDING)", function (done) { var self = this; - return done(); - async.waterfall([ function (callback) { self.what = "Create accounts."; diff --git a/test/remote-test.js b/test/remote-test.js index 2b51151f4d..9a1c35b373 100644 --- a/test/remote-test.js +++ b/test/remote-test.js @@ -1,6 +1,6 @@ var assert = require('assert'); -var Remote = require("ripple-lib").Remote; -var testutils = require("./testutils.js"); +var Remote = require('ripple-lib').Remote; +var testutils = require('./testutils.js'); var config = testutils.init_config(); suite('Remote functions', function() { @@ -14,6 +14,37 @@ suite('Remote functions', function() { testutils.build_teardown().call($, done); }); + test('request_ledger with ledger index', function(done) { + var request = $.remote.request_ledger(); + request.ledger_index(3); + request.callback(function(err, m) { + assert(!err); + assert.strictEqual(m.ledger.ledger_index, '3'); + done(); + }); + }); + + test('request_ledger with ledger index string', function(done) { + var request = $.remote.request_ledger(); + request.ledger_index('3'); + request.callback(function(err, m) { + assert(err); + assert.strictEqual(err.error, 'remoteError'); + assert.strictEqual(err.remote.error, 'invalidParams'); + done(); + }); + }); + + test('request_ledger with ledger identifier', function(done) { + var request = $.remote.request_ledger(); + request.ledger_index('current'); + request.callback(function(err, m) { + assert(!err); + assert.strictEqual(m.ledger.ledger_index, '3'); + done(); + }); + }); + test('request_ledger_current', function(done) { $.remote.request_ledger_current(function(err, m) { assert(!err); @@ -24,7 +55,6 @@ suite('Remote functions', function() { test('request_ledger_hash', function(done) { $.remote.request_ledger_hash(function(err, m) { - // console.log("result: %s", JSON.stringify(m)); assert(!err); assert.strictEqual(m.ledger_index, 2); done(); @@ -35,18 +65,18 @@ suite('Remote functions', function() { var self = this; $.remote.request_ledger_hash(function(err, r) { - //console.log("result: %s", JSON.stringify(r)); + //console.log('result: %s', JSON.stringify(r)); assert(!err); - assert('ledger_hash' in r); + assert('ledger_hash' in r, 'Result missing property "ledger_hash"'); var request = $.remote.request_ledger_entry('account_root') .ledger_hash(r.ledger_hash) - .account_root("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh"); + .account_root('rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); request.callback(function(err, r) { - // console.log("account_root: %s", JSON.stringify(r)); + // console.log('account_root: %s', JSON.stringify(r)); assert(!err); - assert('node' in r); + assert('node' in r, 'Result missing property "node"'); done(); }); }); @@ -56,15 +86,15 @@ suite('Remote functions', function() { var self = this; $.remote.request_ledger_hash(function(err, r) { - // console.log("result: %s", JSON.stringify(r)); + // console.log('result: %s', JSON.stringify(r)); assert(!err); var request = $.remote.request_ledger_entry('account_root') .ledger_hash(r.ledger_hash) - .account_root("zHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh"); + .account_root('zHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); request.callback(function(err, r) { - // console.log("account_root: %s", JSON.stringify(r)); + // console.log('account_root: %s', JSON.stringify(r)); assert(err); assert.strictEqual(err.error, 'remoteError'); assert.strictEqual(err.remote.error, 'malformedAddress'); @@ -77,15 +107,15 @@ suite('Remote functions', function() { var self = this; $.remote.request_ledger_hash(function(err, r) { - // console.log("result: %s", JSON.stringify(r)); + // console.log('result: %s', JSON.stringify(r)); assert(!err); var request = $.remote.request_ledger_entry('account_root') .ledger_hash(r.ledger_hash) - .account_root("alice"); + .account_root('alice'); request.callback(function(err, r) { - // console.log("error: %s", m); + // console.log('error: %s', m); assert(err); assert.strictEqual(err.error, 'remoteError'); assert.strictEqual(err.remote.error, 'entryNotFound'); @@ -102,12 +132,12 @@ suite('Remote functions', function() { var request = $.remote.request_ledger_entry('index') .ledger_hash(r.ledger_hash) - .account_root("alice") - .index("2B6AC232AA4C4BE41BF49D2459FA4A0347E1B543A4C92FCEE0821C0201E2E9A8"); + .account_root('alice') + .index('2B6AC232AA4C4BE41BF49D2459FA4A0347E1B543A4C92FCEE0821C0201E2E9A8'); request.callback(function(err, r) { assert(!err); - assert('node_binary' in r); + assert('node_binary' in r, 'Result missing property "node_binary"'); done(); }); }) @@ -121,14 +151,14 @@ suite('Remote functions', function() { $.remote.request_subscribe().accounts(root_id).request(); $.remote.transaction() - .payment('root', 'alice', "10000.0") + .payment('root', 'alice', '10000.0') .once('error', done) .once('proposed', function(res) { //console.log('Submitted', res); $.remote.ledger_accept(); }) .once('success', function (r) { - //console.log("account_root: %s", JSON.stringify(r)); + //console.log('account_root: %s', JSON.stringify(r)); // Need to verify account and balance. done(); }) @@ -145,28 +175,17 @@ suite('Remote functions', function() { $.remote.request_subscribe().accounts(root_id).request(); var transaction = $.remote.transaction() - .payment('root', 'alice', "10000.0") - .once('error', done) - .once('submitted', function (m) { - // console.log("proposed: %s", JSON.stringify(m)); + .payment('root', 'alice', '10000.0') + + transaction.once('submitted', function (m) { + // console.log('proposed: %s', JSON.stringify(m)); // buster.assert.equals(m.result, 'terNO_DST_INSUF_XRP'); assert.strictEqual(m.engine_result, 'tesSUCCESS'); - }) - .once('proposed', function() { - got_proposed = true; - $.remote.ledger_accept(); - }) - .once('success', function (r) { - // console.log("create_account: %s", JSON.stringify(r)); - got_success = true; - }) - .once('final', function (m) { - // console.log("final: %s", JSON.stringify(m)); - assert(got_success); - assert(got_proposed); - done(); }); - transaction.submit(); + + transaction.submit(done); + + testutils.ledger_wait($.remote, transaction); }); }); diff --git a/test/robust-transaction-test.js b/test/robust-transaction-test.js new file mode 100644 index 0000000000..e8f14da9fe --- /dev/null +++ b/test/robust-transaction-test.js @@ -0,0 +1,475 @@ +var async = require('async'); +var assert = require('assert'); +var ripple = require('ripple-lib'); +var Amount = require('ripple-lib').Amount; +var Remote = require('ripple-lib').Remote; +var Transaction = require('ripple-lib').Transaction; +var Server = require('./server').Server; +var testutils = require('./testutils'); +var config = testutils.init_config(); + +suite('Robust transaction submission', function() { + var $ = { }; + + setup(function(done) { + testutils.build_setup().call($, function() { + $.remote.local_signing = true; + + $.remote.request_subscribe() + .accounts($.remote.account('root')._account_id) + .callback(done); + }); + }); + + teardown(function(done) { + testutils.build_teardown().call($, done); + }); + + // Payment is submitted (without a destination tag) + // to a destination which requires one. + // + // The sequence is now in the future. + // + // Immediately subsequent transactions should err + // with terPRE_SEQ. + // + // Gaps in the sequence should be filled with an + // empty transaction. + // + // Transaction should ultimately succeed. + // + // Subsequent transactions should be submitted with + // an up-to-date transction sequence. i.e. the + // internal sequence should always catch up. + + test('sequence realignment', function(done) { + var self = this; + + var steps = [ + + function createAccounts(callback) { + self.what = 'Create accounts'; + testutils.create_accounts($.remote, 'root', '20000.0', [ 'alice', 'bob' ], callback); + }, + + function setRequireDestTag(callback) { + self.what = 'Set RequireDestTag'; + + var tx = $.remote.transaction().account_set('alice'); + tx.set_flags('RequireDestTag'); + + tx.once('submitted', function(m) { + + assert.strictEqual('tesSUCCESS', m.engine_result); + }); + + tx.once('final', function() { + callback(); + }); + + tx.submit(); + + testutils.ledger_wait($.remote, tx); + }, + + function sendInvalidTransaction(callback) { + self.what = 'Send transaction without a destination tag'; + + var tx = $.remote.transaction().payment({ + from: 'root', + to: 'alice', + amount: Amount.from_human('1XRP') + }); + + tx.once('submitted', function(m) { + assert.strictEqual('tefDST_TAG_NEEDED', m.engine_result); + }); + + tx.submit(); + + //Invoke callback immediately + callback(); + }, + + function sendValidTransaction(callback) { + self.what = 'Send normal transaction which should succeed'; + + var tx = $.remote.transaction().payment({ + from: 'root', + to: 'bob', + amount: Amount.from_human('1XRP') + }); + + tx.on('submitted', function(m) { + //console.log('Submitted', m); + }); + + tx.once('resubmitted', function() { + self.resubmitted = true; + }); + + //First attempt at submission should result in + //terPRE_SEQ as the sequence is still in the future + tx.once('submitted', function(m) { + assert.strictEqual('terPRE_SEQ', m.engine_result); + }); + + tx.once('final', function() { + callback(); + }); + + tx.submit(); + + testutils.ledger_wait($.remote, tx); + }, + + function checkPending(callback) { + self.what = 'Check pending'; + var pending = $.remote.getAccount('root')._transactionManager._pending; + assert.strictEqual(pending._queue.length, 0, 'Pending transactions persisting'); + callback(); + }, + + function verifyBalance(callback) { + self.what = 'Verify balance'; + testutils.verify_balance($.remote, 'bob', '20001000000', callback); + } + + ] + + async.series(steps, function(err) { + assert(!err, self.what + ': ' + err); + assert(self.resubmitted, 'Transaction failed to resubmit'); + done(); + }); + }); + + // Submit a normal payment which should succeed. + // + // Remote disconnects immediately after submission + // and before the validated transaction result is + // received. + // + // Remote reconnects in the future. During this + // time it is presumed that the transaction should + // have succeeded, but an immediate response was + // not possible, as the server was disconnected. + // + // Upon reconnection, recent account transaction + // history is loaded. + // + // The submitted transaction should be detected, + // and the transaction should ultimately succeed. + + test('temporary server disconnection', function(done) { + var self = this; + + var steps = [ + + function createAccounts(callback) { + self.what = 'Create accounts'; + testutils.create_accounts($.remote, 'root', '20000.0', [ 'alice' ], callback); + }, + + function submitTransaction(callback) { + self.what = 'Submit a transaction'; + + var tx = $.remote.transaction().payment({ + from: 'root', + to: 'alice', + amount: Amount.from_human('1XRP') + }); + + tx.submit(); + + setImmediate(function() { + $.remote.once('disconnect', function remoteDisconnected() { + assert(!$.remote._connected); + + tx.once('final', function(m) { + assert.strictEqual(m.engine_result, 'tesSUCCESS'); + callback(); + }); + + $.remote.connect(function() { + testutils.ledger_wait($.remote, tx); + }); + }); + + $.remote.disconnect(); + }); + }, + + function waitLedger(callback) { + self.what = 'Wait ledger'; + $.remote.once('ledger_closed', function() { + callback(); + }); + $.remote.ledger_accept(); + }, + + function checkPending(callback) { + self.what = 'Check pending'; + var pending = $.remote.getAccount('root')._transactionManager._pending; + assert.strictEqual(pending._queue.length, 0, 'Pending transactions persisting'); + callback(); + }, + + function verifyBalance(callback) { + self.what = 'Verify balance'; + testutils.verify_balance($.remote, 'alice', '20001000000', callback); + } + + ] + + async.series(steps, function(err) { + assert(!err, self.what + ': ' + err); + done(); + }); + }); + + test('temporary server disconnection -- reconnect after max ledger wait', function(done) { + var self = this; + + var steps = [ + + function createAccounts(callback) { + self.what = 'Create accounts'; + testutils.create_accounts($.remote, 'root', '20000.0', [ 'alice' ], callback); + }, + + function waitLedgers(callback) { + self.what = 'Wait ledger'; + $.remote.once('ledger_closed', function() { + callback(); + }); + $.remote.ledger_accept(); + }, + + function verifyBalance(callback) { + self.what = 'Verify balance'; + testutils.verify_balance($.remote, 'alice', '20000000000', callback); + }, + + function submitTransaction(callback) { + self.what = 'Submit a transaction'; + + var tx = $.remote.transaction().payment({ + from: 'root', + to: 'alice', + amount: Amount.from_human('1XRP') + }); + + tx.once('submitted', function(m) { + assert.strictEqual(m.engine_result, 'tesSUCCESS'); + + var handleMessage = $.remote._handleMessage; + $.remote._handleMessage = function(){}; + + var ledgers = 0; + + ;(function nextLedger() { + if (++ledgers > 8) { + tx.once('final', function() { callback(); }); + $.remote._handleMessage = handleMessage; + $.remote.disconnect(function() { + assert(!$.remote._connected); + var pending = $.remote.getAccount('root')._transactionManager._pending; + assert.strictEqual(pending._queue.length, 1, 'Pending transactions persisting'); + $.remote.connect(); + }); + } else { + $.remote._getServer().once('ledger_closed', function() { + setTimeout(nextLedger, 20); + }); + $.remote.ledger_accept(); + } + })(); + }); + + tx.submit(); + }, + + function waitLedgers(callback) { + self.what = 'Wait ledgers'; + + var ledgers = 0; + + ;(function nextLedger() { + $.remote.once('ledger_closed', function() { + if (++ledgers === 3) { + callback(); + } else { + nextLedger(); + } + }); + $.remote.ledger_accept(); + })(); + }, + + function checkPending(callback) { + self.what = 'Check pending'; + var pending = $.remote.getAccount('root')._transactionManager._pending; + assert.strictEqual(pending._queue.length, 0, 'Pending transactions persisting'); + callback(); + }, + + function verifyBalance(callback) { + self.what = 'Verify balance'; + testutils.verify_balance($.remote, 'alice', '20001000000', callback); + } + + ] + + async.series(steps, function(err) { + assert(!err, self.what + ': ' + err); + done(); + }); + }); + + // Submit request times out + // + // Since the transaction ID is generated locally, the + // transaction should still validate from the account + // transaction stream, even without a response to the + // original submit request + + test('submission timeout', function(done) { + var self = this; + + var steps = [ + + function createAccounts(callback) { + self.what = 'Create accounts'; + testutils.create_accounts($.remote, 'root', '20000.0', [ 'alice' ], callback); + }, + + function submitTransaction(callback) { + self.what = 'Submit a transaction whose submit request times out'; + + var tx = $.remote.transaction().payment({ + from: 'root', + to: 'alice', + amount: Amount.from_human('1XRP') + }); + + var timed_out = false; + + $.remote.getAccount('root')._transactionManager._submissionTimeout = 0; + + // A response from transaction submission should never + // actually be received + tx.once('timeout', function() { timed_out = true; }); + + tx.once('final', function(m) { + assert(timed_out, 'Transaction submission failed to time out'); + assert.strictEqual(m.engine_result, 'tesSUCCESS'); + callback(); + }); + + tx.submit(); + + testutils.ledger_wait($.remote, tx); + }, + + function checkPending(callback) { + self.what = 'Check pending'; + assert.strictEqual($.remote.getAccount('root')._transactionManager._pending.length(), 0, 'Pending transactions persisting'); + callback(); + }, + + function verifyBalance(callback) { + self.what = 'Verify balance'; + testutils.verify_balance($.remote, 'alice', '20001000000', callback); + } + + ] + + async.series(steps, function(err) { + assert(!err, self.what + ': ' + err); + done(); + }); + }); + + // Subscribing to accounts_proposed will result in ripple-lib + // being streamed non-validated (proposed) transactions + // + // This test ensures that only validated transactions will + // trigger a transaction success event + + test('subscribe to accounts_proposed', function(done) { + var self = this; + + var series = [ + + function subscribeToAccountsProposed(callback) { + self.what = 'Subscribe to accounts_proposed'; + + $.remote.requestSubscribe() + .addAccountProposed('root') + .callback(callback); + }, + + function submitTransaction(callback) { + self.what = 'Submit a transaction'; + + var tx = $.remote.transaction().accountSet('root'); + + var receivedProposedTransaction = false; + + $.remote.on('transaction', function(tx) { + if (tx.status === 'proposed') { + receivedProposedTransaction = true; + } + }); + + tx.submit(function(err, m) { + assert(!err, err); + assert(receivedProposedTransaction, 'Did not received proposed transaction from stream'); + assert(m.engine_result, 'tesSUCCESS'); + assert(m.validated, 'Transaction is finalized with invalidated transaction stream response'); + done(); + }); + + testutils.ledger_wait($.remote, tx); + } + + ] + + async.series(series, function(err, m) { + assert(!err, self.what + ': ' + err); + done(); + }); + }); + + // Validate that LastLedgerSequence works + test('set LastLedgerSequence', function(done) { + var self = this; + + var series = [ + + function createAccounts(callback) { + self.what = 'Create accounts'; + testutils.create_accounts($.remote, 'root', '20000.0', [ 'alice' ], callback); + }, + + function submitTransaction(callback) { + var tx = $.remote.transaction().payment('root', 'alice', '1'); + tx.lastLedger(0); + + tx.once('submitted', function(m) { + assert.strictEqual(m.engine_result, 'tefMAX_LEDGER'); + callback(); + }); + + tx.submit(); + } + + ] + + async.series(series, function(err) { + assert(!err, self.what); + done(); + }); + }); +}); diff --git a/test/send-test.js b/test/send-test.js index 0ba6d6d7ad..f9e82c032e 100644 --- a/test/send-test.js +++ b/test/send-test.js @@ -1127,7 +1127,7 @@ suite('Indirect ripple', function() { $.remote.transaction() .payment("alice", "bob", "5/USD/mtgox") - .path_add( [ { account: "mtgox" } ]) + .pathAdd( [ { account: "mtgox" } ]) .on('proposed', function (m) { // console.log("proposed: %s", JSON.stringify(m)); @@ -1185,8 +1185,8 @@ suite('Indirect ripple', function() { $.remote.transaction() .payment("alice", "amazon", "150/USD/mtgox") - .path_add( [ { account: "bob" } ]) - .path_add( [ { account: "carol" } ]) + .pathAdd( [ { account: "bob" } ]) + .pathAdd( [ { account: "carol" } ]) .on('proposed', function (m) { // console.log("proposed: %s", JSON.stringify(m)); @@ -1251,8 +1251,8 @@ suite('Indirect ripple', function() { $.remote.transaction() .payment("alice", "amazon", "150/USD/mtgox") .send_max("200/USD/alice") - .path_add( [ { account: "bob" } ]) - .path_add( [ { account: "carol" } ]) + .pathAdd( [ { account: "bob" } ]) + .pathAdd( [ { account: "carol" } ]) .on('proposed', function (m) { // console.log("proposed: %s", JSON.stringify(m)); @@ -1293,4 +1293,48 @@ suite('Indirect ripple', function() { }) }); +suite('Invoice ID', function() { + var $ = { }; + + setup(function(done) { + testutils.build_setup().call($, done); + }); + + teardown(function(done) { + testutils.build_teardown().call($, done); + }); + + test('set InvoiceID on payment', function(done) { + var self = this; + + var steps = [ + function (callback) { + self.what = 'Create accounts'; + testutils.create_accounts($.remote, 'root', '10000.0', [ 'alice' ], callback); + }, + + function (callback) { + self.what = 'Send a payment with InvoiceID'; + + var tx = $.remote.transaction(); + tx.payment('root', 'alice', '10000'); + tx.invoiceID('DEADBEEF'); + + tx.once('submitted', function(m) { + assert.strictEqual(m.engine_result, 'tesSUCCESS'); + assert.strictEqual(m.tx_json.InvoiceID, 'DEADBEEF00000000000000000000000000000000000000000000000000000000'); + callback(); + }); + + tx.submit(); + } + ] + + async.series(steps, function(err) { + assert(!err, self.what + ': ' + err); + done(); + }); + }); +}); + // vim:sw=2:sts=2:ts=8:et diff --git a/test/testutils.js b/test/testutils.js index db3eee8fa5..198b6ab16f 100644 --- a/test/testutils.js +++ b/test/testutils.js @@ -32,30 +32,6 @@ function prepare_tests(tests, fn) { return result; }; -function account_dump(remote, account, callback) { - var self = this; - - this.what = 'Get latest account_root'; - - var request = remote.request_ledger_entry('account_root'); - request.ledger_hash(remote.ledger_hash()); - request.account_root('root'); - request.callback(function(err, r) { - assert(!err, self.what); - if (err) { - //console.log('error: %s', m); - callback(err); - } else { - //console.log('account_root: %s', JSON.stringify(r, undefined, 2)); - callback(); - } - }); - - // get closed ledger hash - // get account root - // construct a json result -}; - /** * Helper called by test cases to generate a setUp routine. * @@ -110,7 +86,7 @@ function build_setup(opts, host) { data.opts = opts; - var series = [ + var steps = [ function run_server(callback) { if (opts.no_server) { return callback(); @@ -127,7 +103,11 @@ function build_setup(opts, host) { data.server.once('exited', function () { // If know the remote, tell it server is gone. if (self.remote) { - self.remote.server_fatal(); + try { + self.remote.setServerFatal(); + } catch (e) { + self.remote.serverFatal(); + } } }); @@ -137,14 +117,14 @@ function build_setup(opts, host) { function connect_websocket(callback) { self.remote = data.remote = Remote.from_config(host, !!opts.verbose_ws); - self.remote.once('ledger_closed', function(ledger) { + self.remote.once('ledger_closed', function() { callback(); }); self.remote.connect(); } ]; - async.series(series, done); + async.series(steps, done); }; return setup; @@ -155,6 +135,7 @@ function build_setup(opts, host) { * * @param host {String} Identifier for the host configuration to be used. */ + function build_teardown(host) { var config = get_config(); var host = host || config.server_default; @@ -189,8 +170,32 @@ function build_teardown(host) { return teardown; }; +function account_dump(remote, account, callback) { + var self = this; + + this.what = 'Get latest account_root'; + + var request = remote.request_ledger_entry('account_root'); + request.ledger_hash(remote.ledger_hash()); + request.account_root('root'); + request.callback(function(err, r) { + assert(!err, self.what); + if (err) { + //console.log('error: %s', m); + callback(err); + } else { + //console.log('account_root: %s', JSON.stringify(r, undefined, 2)); + callback(); + } + }); + + // get closed ledger hash + // get account root + // construct a json result +}; + function create_accounts(remote, src, amount, accounts, callback) { - assert(arguments.length === 5); + assert.strictEqual(arguments.length, 5); remote.set_account_seq(src, 1); @@ -218,7 +223,7 @@ function create_accounts(remote, src, amount, accounts, callback) { }; function credit_limit(remote, src, amount, callback) { - assert(arguments.length === 4); + assert.strictEqual(arguments.length, 4); var _m = amount.match(/^(\d+\/...\/[^\:]+)(?::(\d+)(?:,(\d+))?)?$/); @@ -250,7 +255,7 @@ function credit_limit(remote, src, amount, callback) { }; function verify_limit(remote, src, amount, callback) { - assert(arguments.length === 4); + assert.strictEqual(arguments.length, 4); var _m = amount.match(/^(\d+\/...\/[^\:]+)(?::(\d+)(?:,(\d+))?)?$/); @@ -286,7 +291,7 @@ function verify_limit(remote, src, amount, callback) { }; function credit_limits(remote, balances, callback) { - assert(arguments.length === 3); + assert.strictEqual(arguments.length, 3); var limits = [ ]; @@ -303,7 +308,7 @@ function credit_limits(remote, balances, callback) { credit_limit(remote, limit.source, limit.amount, callback); } - async.some(limits, iterator, callback); + async.eachSeries(limits, iterator, callback); }; function ledger_close(remote, callback) { @@ -314,7 +319,7 @@ function ledger_close(remote, callback) { }; function payment(remote, src, dst, amount, callback) { - assert(arguments.length === 5); + assert.strictEqual(arguments.length, 5); var tx = remote.transaction(); @@ -333,7 +338,7 @@ function payment(remote, src, dst, amount, callback) { }; function payments(remote, balances, callback) { - assert(arguments.length === 3); + assert.strictEqual(arguments.length, 3); var sends = [ ]; @@ -351,7 +356,7 @@ function payments(remote, balances, callback) { payment(remote, send.source, send.destination, send.amount, callback); } - async.some(sends, iterator, callback); + async.eachSeries(sends, iterator, callback); }; function transfer_rate(remote, src, billionths, callback) { @@ -375,7 +380,7 @@ function transfer_rate(remote, src, billionths, callback) { }; function verify_balance(remote, src, amount_json, callback) { - assert(arguments.length === 4); + assert.strictEqual(arguments.length, 4); var amount_req = Amount.from_json(amount_json); @@ -390,7 +395,7 @@ function verify_balance(remote, src, amount_json, callback) { //amount_act.to_text_full(), //amount_req.to_text_full()); } - callback(valid_balance ? null : new Error()); + callback(valid_balance ? null : new Error('Balance invalid: ' + amount_json + ' / ' + amount_act.to_json())); }); } else { var issuer = amount_req.issuer().to_json(); @@ -405,7 +410,6 @@ function verify_balance(remote, src, amount_json, callback) { // console.log('issuer_balance: %s', m.issuer_balance.to_text_full()); // console.log('issuer_limit: %s', m.issuer_limit.to_text_full()); var account_balance = Amount.from_json(m.account_balance); - var valid_balance = account_balance.equals(amount_req, true); if (!valid_balance) { @@ -416,7 +420,7 @@ function verify_balance(remote, src, amount_json, callback) { //account_balance.not_equals_why(amount_req, true)); } - callback(valid_balance ? null : new Error()); + callback(valid_balance ? null : new Error('Balance invalid: ' + amount_json + ' / ' + account_balance.to_json())); }) } }; @@ -442,7 +446,7 @@ function verify_balances(remote, balances, callback) { // --> taker_gets: json amount // --> taker_pays: json amount function verify_offer(remote, owner, seq, taker_pays, taker_gets, callback) { - assert(arguments.length === 6); + assert.strictEqual(arguments.length, 6); var request = remote.request_ledger_entry('offer') request.offer_id(owner, seq) @@ -460,7 +464,7 @@ function verify_offer(remote, owner, seq, taker_pays, taker_gets, callback) { }; function verify_offer_not_found(remote, owner, seq, callback) { - assert(arguments.length === 4); + assert.strictEqual(arguments.length, 4); var request = remote.request_ledger_entry('offer'); @@ -505,25 +509,37 @@ function verify_owner_counts(remote, counts, callback) { async.every(tests, iterator, callback); }; -exports.account_dump = account_dump; -exports.build_setup = build_setup; -exports.build_teardown = build_teardown; -exports.create_accounts = create_accounts; -exports.credit_limit = credit_limit; -exports.credit_limits = credit_limits; -exports.get_config = get_config; -exports.init_config = init_config; -exports.ledger_close = ledger_close; -exports.payment = payment; -exports.payments = payments; -exports.transfer_rate = transfer_rate; -exports.verify_balance = verify_balance; -exports.verify_balances = verify_balances; -exports.verify_limit = verify_limit; -exports.verify_offer = verify_offer; -exports.verify_offer_not_found = verify_offer_not_found; -exports.verify_owner_count = verify_owner_count; -exports.verify_owner_counts = verify_owner_counts; +function ledger_wait(remote, tx) { + ;(function nextLedger() { + remote.once('ledger_closed', function() { + if (!tx.finalized) { + setTimeout(nextLedger, 50); + } + }); + remote.ledger_accept(); + })(); +}; + +exports.account_dump = account_dump; +exports.build_setup = build_setup; +exports.build_teardown = build_teardown; +exports.create_accounts = create_accounts; +exports.credit_limit = credit_limit; +exports.credit_limits = credit_limits; +exports.get_config = get_config; +exports.init_config = init_config; +exports.ledger_close = ledger_close; +exports.payment = payment; +exports.payments = payments; +exports.transfer_rate = transfer_rate; +exports.verify_balance = verify_balance; +exports.verify_balances = verify_balances; +exports.verify_limit = verify_limit; +exports.verify_offer = verify_offer; +exports.verify_offer_not_found = verify_offer_not_found; +exports.verify_owner_count = verify_owner_count; +exports.verify_owner_counts = verify_owner_counts; +exports.ledger_wait = ledger_wait; process.on('uncaughtException', function() { Object.keys(server).forEach(function(host) { diff --git a/test/websocket-test.js b/test/websocket-test.js index d505578118..0469c32dc6 100644 --- a/test/websocket-test.js +++ b/test/websocket-test.js @@ -9,7 +9,7 @@ suite('WebSocket connection', function() { var server; setup(function(done) { - this.timeout(1000); + this.timeout(2000); var cfg = extend({}, config.default_server_config, config.servers.alpha); if (cfg.no_server) { @@ -22,7 +22,7 @@ suite('WebSocket connection', function() { }); teardown(function(done) { - this.timeout(1000); + this.timeout(2000); if (config.servers.alpha.no_server) { done(); @@ -33,7 +33,7 @@ suite('WebSocket connection', function() { }); test('WebSocket connect and disconnect', function(done) { - this.timeout(1000); + this.timeout(2000); var alpha = Remote.from_config("alpha");