mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-27 22:45:52 +00:00
RPC robust transaction unit test (RIPD-1079)
This commit is contained in:
committed by
Vinnie Falco
parent
1d0ca51c88
commit
8f83f69325
@@ -3283,6 +3283,10 @@
|
|||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\..\src\ripple\rpc\tests\RobustTransaction.test.cpp">
|
||||||
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||||
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||||
|
</ClCompile>
|
||||||
<ClCompile Include="..\..\src\ripple\rpc\tests\Status.test.cpp">
|
<ClCompile Include="..\..\src\ripple\rpc\tests\Status.test.cpp">
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||||
|
|||||||
@@ -3765,6 +3765,9 @@
|
|||||||
<ClCompile Include="..\..\src\ripple\rpc\tests\LedgerRequestRPC.test.cpp">
|
<ClCompile Include="..\..\src\ripple\rpc\tests\LedgerRequestRPC.test.cpp">
|
||||||
<Filter>ripple\rpc\tests</Filter>
|
<Filter>ripple\rpc\tests</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\..\src\ripple\rpc\tests\RobustTransaction.test.cpp">
|
||||||
|
<Filter>ripple\rpc\tests</Filter>
|
||||||
|
</ClCompile>
|
||||||
<ClCompile Include="..\..\src\ripple\rpc\tests\Status.test.cpp">
|
<ClCompile Include="..\..\src\ripple\rpc\tests\Status.test.cpp">
|
||||||
<Filter>ripple\rpc\tests</Filter>
|
<Filter>ripple\rpc\tests</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
|||||||
321
src/ripple/rpc/tests/RobustTransaction.test.cpp
Normal file
321
src/ripple/rpc/tests/RobustTransaction.test.cpp
Normal file
@@ -0,0 +1,321 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
/*
|
||||||
|
This file is part of rippled: https://github.com/ripple/rippled
|
||||||
|
Copyright (c) 2012, 2013 Ripple Labs Inc.
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
|
#include <BeastConfig.h>
|
||||||
|
#include <ripple/core/JobQueue.h>
|
||||||
|
#include <ripple/protocol/JsonFields.h>
|
||||||
|
#include <ripple/test/jtx.h>
|
||||||
|
#include <ripple/test/WSClient.h>
|
||||||
|
#include <beast/unit_test/suite.h>
|
||||||
|
|
||||||
|
namespace ripple {
|
||||||
|
namespace test {
|
||||||
|
|
||||||
|
class RobustTransaction_test : public beast::unit_test::suite
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void
|
||||||
|
testSequenceRealignment()
|
||||||
|
{
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
using namespace jtx;
|
||||||
|
Env env(*this);
|
||||||
|
env.fund(XRP(10000), "alice", "bob");
|
||||||
|
env.close();
|
||||||
|
auto wsc = makeWSClient(env.app().config());
|
||||||
|
|
||||||
|
{
|
||||||
|
// RPC subscribe to transactions stream
|
||||||
|
Json::Value jv;
|
||||||
|
jv[jss::streams] = Json::arrayValue;
|
||||||
|
jv[jss::streams].append("transactions");
|
||||||
|
jv = wsc->invoke("subscribe", jv);
|
||||||
|
expect(jv[jss::status] == "success");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Submit past ledger sequence transaction
|
||||||
|
Json::Value payment;
|
||||||
|
payment[jss::secret] = toBase58(generateSeed("alice"));
|
||||||
|
payment[jss::tx_json] = pay("alice", "bob", XRP(1));
|
||||||
|
payment[jss::tx_json][sfLastLedgerSequence.fieldName] = 1;
|
||||||
|
auto jv = wsc->invoke("submit", payment);
|
||||||
|
expect(jv[jss::result][jss::engine_result] ==
|
||||||
|
"tefMAX_LEDGER");
|
||||||
|
|
||||||
|
// Submit past sequence transaction
|
||||||
|
payment[jss::tx_json] = pay("alice", "bob", XRP(1));
|
||||||
|
payment[jss::tx_json][sfSequence.fieldName] =
|
||||||
|
env.seq("alice") - 1;
|
||||||
|
jv = wsc->invoke("submit", payment);
|
||||||
|
expect(jv[jss::result][jss::engine_result] ==
|
||||||
|
"tefPAST_SEQ");
|
||||||
|
|
||||||
|
// Submit future sequence transaction
|
||||||
|
payment[jss::tx_json][sfSequence.fieldName] =
|
||||||
|
env.seq("alice") + 1;
|
||||||
|
jv = wsc->invoke("submit", payment);
|
||||||
|
expect(jv[jss::result][jss::engine_result] ==
|
||||||
|
"terPRE_SEQ");
|
||||||
|
|
||||||
|
// Submit transaction to bridge the sequence gap
|
||||||
|
payment[jss::tx_json][sfSequence.fieldName] =
|
||||||
|
env.seq("alice");
|
||||||
|
jv = wsc->invoke("submit", payment);
|
||||||
|
expect(jv[jss::result][jss::engine_result] ==
|
||||||
|
"tesSUCCESS");
|
||||||
|
|
||||||
|
// Wait for the jobqueue to process everything
|
||||||
|
env.app().getJobQueue().rendezvous();
|
||||||
|
|
||||||
|
// Finalize transactions
|
||||||
|
jv = wsc->invoke("ledger_accept");
|
||||||
|
expect(jv[jss::result].isMember(
|
||||||
|
jss::ledger_current_index));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Check balances
|
||||||
|
expect(wsc->findMsg(5s,
|
||||||
|
[&](auto const& jv)
|
||||||
|
{
|
||||||
|
auto const& ff = jv[jss::meta]["AffectedNodes"]
|
||||||
|
[1u]["ModifiedNode"]["FinalFields"];
|
||||||
|
return ff[jss::Account] == Account("bob").human() &&
|
||||||
|
ff["Balance"] == "10001000000";
|
||||||
|
}));
|
||||||
|
|
||||||
|
expect(wsc->findMsg(5s,
|
||||||
|
[&](auto const& jv)
|
||||||
|
{
|
||||||
|
auto const& ff = jv[jss::meta]["AffectedNodes"]
|
||||||
|
[1u]["ModifiedNode"]["FinalFields"];
|
||||||
|
return ff[jss::Account] == Account("bob").human() &&
|
||||||
|
ff["Balance"] == "10002000000";
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Submit a normal payment. Client disconnects after the proposed
|
||||||
|
transaction result is received.
|
||||||
|
|
||||||
|
Client reconnects in the future. During this time it is presumed that the
|
||||||
|
transaction should have succeeded.
|
||||||
|
|
||||||
|
Upon reconnection, recent account transaction history is loaded.
|
||||||
|
The submitted transaction should be detected, and the transaction should
|
||||||
|
ultimately succeed.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
testReconnect()
|
||||||
|
{
|
||||||
|
using namespace jtx;
|
||||||
|
Env env(*this);
|
||||||
|
env.fund(XRP(10000), "alice", "bob");
|
||||||
|
env.close();
|
||||||
|
auto wsc = makeWSClient(env.app().config());
|
||||||
|
|
||||||
|
{
|
||||||
|
// Submit normal payment
|
||||||
|
Json::Value jv;
|
||||||
|
jv[jss::secret] = toBase58(generateSeed("alice"));
|
||||||
|
jv[jss::tx_json] = pay("alice", "bob", XRP(1));
|
||||||
|
jv = wsc->invoke("submit", jv);
|
||||||
|
expect(jv[jss::result][jss::engine_result] ==
|
||||||
|
"tesSUCCESS");
|
||||||
|
|
||||||
|
// Disconnect
|
||||||
|
wsc.reset();
|
||||||
|
|
||||||
|
// Server finalizes transaction
|
||||||
|
env.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// RPC account_tx
|
||||||
|
Json::Value jv;
|
||||||
|
jv[jss::account] = Account("bob").human();
|
||||||
|
jv[jss::ledger_index_min] = -1;
|
||||||
|
jv[jss::ledger_index_max] = -1;
|
||||||
|
wsc = makeWSClient(env.app().config());
|
||||||
|
jv = wsc->invoke("account_tx", jv);
|
||||||
|
|
||||||
|
// Check balance
|
||||||
|
auto ff = jv[jss::result][jss::transactions][0u][jss::meta]
|
||||||
|
["AffectedNodes"][1u]["ModifiedNode"]["FinalFields"];
|
||||||
|
expect(ff[jss::Account] ==
|
||||||
|
Account("bob").human());
|
||||||
|
expect(ff["Balance"] == "10001000000");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
testReconnectAfterWait()
|
||||||
|
{
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
using namespace jtx;
|
||||||
|
Env env(*this);
|
||||||
|
env.fund(XRP(10000), "alice", "bob");
|
||||||
|
env.close();
|
||||||
|
auto wsc = makeWSClient(env.app().config());
|
||||||
|
|
||||||
|
{
|
||||||
|
// Submit normal payment
|
||||||
|
Json::Value jv;
|
||||||
|
jv[jss::secret] = toBase58(generateSeed("alice"));
|
||||||
|
jv[jss::tx_json] = pay("alice", "bob", XRP(1));
|
||||||
|
jv = wsc->invoke("submit", jv);
|
||||||
|
expect(jv[jss::result][jss::engine_result] ==
|
||||||
|
"tesSUCCESS");
|
||||||
|
|
||||||
|
// Finalize transaction
|
||||||
|
jv = wsc->invoke("ledger_accept");
|
||||||
|
expect(jv[jss::result].isMember(
|
||||||
|
jss::ledger_current_index));
|
||||||
|
|
||||||
|
// Wait for the jobqueue to process everything
|
||||||
|
env.app().getJobQueue().rendezvous();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// RPC subscribe to ledger stream
|
||||||
|
Json::Value jv;
|
||||||
|
jv[jss::streams] = Json::arrayValue;
|
||||||
|
jv[jss::streams].append("ledger");
|
||||||
|
jv = wsc->invoke("subscribe", jv);
|
||||||
|
expect(jv[jss::status] == "success");
|
||||||
|
|
||||||
|
// Close ledgers
|
||||||
|
for(auto i = 0; i < 8; ++i)
|
||||||
|
{
|
||||||
|
expect(wsc->invoke("ledger_accept")[jss::result].
|
||||||
|
isMember(jss::ledger_current_index));
|
||||||
|
|
||||||
|
// Wait for the jobqueue to process everything
|
||||||
|
env.app().getJobQueue().rendezvous();
|
||||||
|
|
||||||
|
expect(wsc->findMsg(5s,
|
||||||
|
[&](auto const& jv)
|
||||||
|
{
|
||||||
|
return jv[jss::type] == "ledgerClosed";
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Disconnect, reconnect
|
||||||
|
wsc = makeWSClient(env.app().config());
|
||||||
|
|
||||||
|
// RPC subscribe to ledger stream
|
||||||
|
Json::Value jv;
|
||||||
|
jv[jss::streams] = Json::arrayValue;
|
||||||
|
jv[jss::streams].append("ledger");
|
||||||
|
jv = wsc->invoke("subscribe", jv);
|
||||||
|
expect(jv[jss::status] == "success");
|
||||||
|
|
||||||
|
// Close ledgers
|
||||||
|
for (auto i = 0; i < 2; ++i)
|
||||||
|
{
|
||||||
|
expect(wsc->invoke("ledger_accept")[jss::result].
|
||||||
|
isMember(jss::ledger_current_index));
|
||||||
|
|
||||||
|
// Wait for the jobqueue to process everything
|
||||||
|
env.app().getJobQueue().rendezvous();
|
||||||
|
|
||||||
|
expect(wsc->findMsg(5s,
|
||||||
|
[&](auto const& jv)
|
||||||
|
{
|
||||||
|
return jv[jss::type] == "ledgerClosed";
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// RPC account_tx
|
||||||
|
Json::Value jv;
|
||||||
|
jv[jss::account] = Account("bob").human();
|
||||||
|
jv[jss::ledger_index_min] = -1;
|
||||||
|
jv[jss::ledger_index_max] = -1;
|
||||||
|
wsc = makeWSClient(env.app().config());
|
||||||
|
jv = wsc->invoke("account_tx", jv);
|
||||||
|
|
||||||
|
// Check balance
|
||||||
|
auto ff = jv[jss::result][jss::transactions][0u][jss::meta]
|
||||||
|
["AffectedNodes"][1u]["ModifiedNode"]["FinalFields"];
|
||||||
|
expect(ff[jss::Account] ==
|
||||||
|
Account("bob").human());
|
||||||
|
expect(ff["Balance"] == "10001000000");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
testAccountsProposed()
|
||||||
|
{
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
using namespace jtx;
|
||||||
|
Env env(*this);
|
||||||
|
env.fund(XRP(10000), "alice");
|
||||||
|
env.close();
|
||||||
|
auto wsc = makeWSClient(env.app().config());
|
||||||
|
|
||||||
|
{
|
||||||
|
// RPC subscribe to accounts_proposed stream
|
||||||
|
Json::Value jv;
|
||||||
|
jv[jss::accounts_proposed] = Json::arrayValue;
|
||||||
|
jv[jss::accounts_proposed].append(
|
||||||
|
Account("alice").human());
|
||||||
|
jv = wsc->invoke("subscribe", jv);
|
||||||
|
expect(jv[jss::status] == "success");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Submit account_set transaction
|
||||||
|
Json::Value jv;
|
||||||
|
jv[jss::secret] = toBase58(generateSeed("alice"));
|
||||||
|
jv[jss::tx_json] = fset("alice", 0);
|
||||||
|
jv[jss::tx_json][jss::Fee] = 10;
|
||||||
|
jv = wsc->invoke("submit", jv);
|
||||||
|
expect(jv[jss::result][jss::engine_result] ==
|
||||||
|
"tesSUCCESS");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Check stream update
|
||||||
|
expect(wsc->findMsg(5s,
|
||||||
|
[&](auto const& jv)
|
||||||
|
{
|
||||||
|
return jv[jss::transaction][jss::TransactionType] ==
|
||||||
|
"AccountSet";
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
run() override
|
||||||
|
{
|
||||||
|
testSequenceRealignment();
|
||||||
|
testReconnect();
|
||||||
|
testReconnectAfterWait();
|
||||||
|
testAccountsProposed();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
BEAST_DEFINE_TESTSUITE(RobustTransaction,app,ripple);
|
||||||
|
|
||||||
|
} // test
|
||||||
|
} // ripple
|
||||||
@@ -105,5 +105,6 @@
|
|||||||
#include <ripple/rpc/tests/JSONRPC.test.cpp>
|
#include <ripple/rpc/tests/JSONRPC.test.cpp>
|
||||||
#include <ripple/rpc/tests/LedgerRequestRPC.test.cpp>
|
#include <ripple/rpc/tests/LedgerRequestRPC.test.cpp>
|
||||||
#include <ripple/rpc/tests/KeyGeneration.test.cpp>
|
#include <ripple/rpc/tests/KeyGeneration.test.cpp>
|
||||||
|
#include <ripple/rpc/tests/RobustTransaction.test.cpp>
|
||||||
#include <ripple/rpc/tests/Status.test.cpp>
|
#include <ripple/rpc/tests/Status.test.cpp>
|
||||||
#include <ripple/rpc/tests/Subscribe.test.cpp>
|
#include <ripple/rpc/tests/Subscribe.test.cpp>
|
||||||
|
|||||||
@@ -1,463 +0,0 @@
|
|||||||
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();
|
|
||||||
|
|
||||||
var make_suite = process.env.CI ? suite.skip : suite;
|
|
||||||
make_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 sendInvalidTransaction(callback) {
|
|
||||||
self.what = 'Send transaction without a destination tag';
|
|
||||||
|
|
||||||
var tx = $.remote.transaction().payment({
|
|
||||||
from: 'root',
|
|
||||||
to: 'alice',
|
|
||||||
amount: Amount.from_human('1 XRP')
|
|
||||||
});
|
|
||||||
|
|
||||||
tx.once('submitted', function(m) {
|
|
||||||
assert.strictEqual('tefMAX_LEDGER', m.engine_result);
|
|
||||||
});
|
|
||||||
tx.once('error', function(m) {
|
|
||||||
assert.strictEqual('tejMaxLedger', m.engine_result);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Standalone mode starts with the open ledger as 3, so there's no way
|
|
||||||
// for this to be anything other than tefMAX_LEDGER.
|
|
||||||
tx.lastLedger(1);
|
|
||||||
tx.submit();
|
|
||||||
|
|
||||||
//Invoke callback immediately
|
|
||||||
callback(null, tx);
|
|
||||||
},
|
|
||||||
|
|
||||||
function sendValidTransaction(previousTx, callback) {
|
|
||||||
self.what = 'Send normal transaction which should succeed';
|
|
||||||
|
|
||||||
var tx = $.remote.transaction().payment({
|
|
||||||
from: 'root',
|
|
||||||
to: 'bob',
|
|
||||||
amount: Amount.from_human('1 XRP')
|
|
||||||
});
|
|
||||||
|
|
||||||
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() {
|
|
||||||
assert(previousTx.finalized,
|
|
||||||
"Expected lastLedger 1 transaction to be finalized");
|
|
||||||
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', '20000999988', callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
]
|
|
||||||
|
|
||||||
async.waterfall(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('1 XRP')
|
|
||||||
});
|
|
||||||
|
|
||||||
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', '20000999988', 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', '19999999988', callback);
|
|
||||||
},
|
|
||||||
|
|
||||||
function submitTransaction(callback) {
|
|
||||||
self.what = 'Submit a transaction';
|
|
||||||
|
|
||||||
var tx = $.remote.transaction().payment({
|
|
||||||
from: 'root',
|
|
||||||
to: 'alice',
|
|
||||||
amount: Amount.from_human('1 XRP')
|
|
||||||
});
|
|
||||||
|
|
||||||
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 {
|
|
||||||
setTimeout(nextLedger, process.env.CI ? 400 : 100 );
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$.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', '20000999988', 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('1 XRP')
|
|
||||||
});
|
|
||||||
|
|
||||||
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', '20000999988', 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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
Reference in New Issue
Block a user