mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
Port test/transaction_ordering_test.js to C++
This commit is contained in:
@@ -1736,6 +1736,10 @@
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\app\tests\Transaction_ordering_test.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\app\tests\TxQ_test.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
|
||||
@@ -2460,6 +2460,9 @@
|
||||
<ClCompile Include="..\..\src\ripple\app\tests\Taker.test.cpp">
|
||||
<Filter>ripple\app\tests</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\app\tests\Transaction_ordering_test.cpp">
|
||||
<Filter>ripple\app\tests</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\app\tests\TxQ_test.cpp">
|
||||
<Filter>ripple\app\tests</Filter>
|
||||
</ClCompile>
|
||||
|
||||
179
src/ripple/app/tests/Transaction_ordering_test.cpp
Normal file
179
src/ripple/app/tests/Transaction_ordering_test.cpp
Normal file
@@ -0,0 +1,179 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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/ErrorCodes.h>
|
||||
#include <ripple/test/jtx.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
struct Transaction_ordering_test : public beast::unit_test::suite
|
||||
{
|
||||
template <class Ftest>
|
||||
void submitWait(jtx::Env& env, jtx::JTx const& tx, Ftest&& test)
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
std::condition_variable cv;
|
||||
env.app().getJobQueue().postCoro(
|
||||
jtCLIENT, "Coroutine-Test",
|
||||
[&](std::shared_ptr<JobCoro> jc)
|
||||
{
|
||||
env(tx);
|
||||
cv.notify_one();
|
||||
});
|
||||
|
||||
{
|
||||
std::mutex m;
|
||||
std::unique_lock<std::mutex> lk(m);
|
||||
// If stepping through this test in a debugger,
|
||||
// make the timeout much longer, or use
|
||||
//cv.wait(lk, test);
|
||||
expect(cv.wait_for(lk, 2s, test));
|
||||
}
|
||||
}
|
||||
|
||||
void testCorrectOrder()
|
||||
{
|
||||
using namespace jtx;
|
||||
|
||||
Env env(*this);
|
||||
auto const alice = Account("alice");
|
||||
env.fund(XRP(1000), noripple(alice));
|
||||
|
||||
auto const aliceSequence = env.seq(alice);
|
||||
|
||||
auto const tx1 = env.jt(noop(alice), seq(aliceSequence));
|
||||
auto const tx2 = env.jt(noop(alice), seq(aliceSequence + 1),
|
||||
json(R"({"LastLedgerSequence":7})"));
|
||||
|
||||
env(tx1);
|
||||
env.close();
|
||||
expect(env.seq(alice) == aliceSequence + 1);
|
||||
env(tx2);
|
||||
env.close();
|
||||
expect(env.seq(alice) == aliceSequence + 2);
|
||||
|
||||
env.close();
|
||||
|
||||
{
|
||||
auto const result = env.rpc("tx", to_string(tx1.stx->getTransactionID()));
|
||||
expect(result.first == rpcSUCCESS);
|
||||
expect(result.second["result"]["meta"]["TransactionResult"] == "tesSUCCESS");
|
||||
}
|
||||
{
|
||||
auto const result = env.rpc("tx", to_string(tx2.stx->getTransactionID()));
|
||||
expect(result.first == rpcSUCCESS);
|
||||
expect(result.second["result"]["meta"]["TransactionResult"] == "tesSUCCESS");
|
||||
}
|
||||
}
|
||||
|
||||
void testIncorrectOrder()
|
||||
{
|
||||
using namespace jtx;
|
||||
|
||||
Env env(*this);
|
||||
env.app().getJobQueue().setThreadCount(0, false);
|
||||
auto const alice = Account("alice");
|
||||
env.fund(XRP(1000), noripple(alice));
|
||||
|
||||
auto const aliceSequence = env.seq(alice);
|
||||
|
||||
auto const tx1 = env.jt(noop(alice), seq(aliceSequence));
|
||||
auto const tx2 = env.jt(noop(alice), seq(aliceSequence + 1),
|
||||
json(R"({"LastLedgerSequence":7})"));
|
||||
|
||||
env(tx2, ter(terPRE_SEQ));
|
||||
expect(env.seq(alice) == aliceSequence);
|
||||
submitWait(env, tx1,
|
||||
[&]()
|
||||
{
|
||||
return env.seq(alice) == aliceSequence + 2;
|
||||
});
|
||||
expect(env.seq(alice) == aliceSequence + 2);
|
||||
|
||||
env.close();
|
||||
|
||||
{
|
||||
auto const result = env.rpc("tx", to_string(tx1.stx->getTransactionID()));
|
||||
expect(result.first == rpcSUCCESS);
|
||||
expect(result.second["result"]["meta"]["TransactionResult"] == "tesSUCCESS");
|
||||
}
|
||||
{
|
||||
auto const result = env.rpc("tx", to_string(tx2.stx->getTransactionID()));
|
||||
expect(result.first == rpcSUCCESS);
|
||||
expect(result.second["result"]["meta"]["TransactionResult"] == "tesSUCCESS");
|
||||
}
|
||||
}
|
||||
|
||||
void testIncorrectOrderMultipleIntermediaries()
|
||||
{
|
||||
using namespace jtx;
|
||||
|
||||
Env env(*this);
|
||||
env.app().getJobQueue().setThreadCount(0, false);
|
||||
auto const alice = Account("alice");
|
||||
env.fund(XRP(1000), noripple(alice));
|
||||
|
||||
auto const aliceSequence = env.seq(alice);
|
||||
|
||||
std::vector<JTx> tx;
|
||||
for (auto i = 0; i < 5; ++i)
|
||||
{
|
||||
tx.emplace_back(
|
||||
env.jt(noop(alice), seq(aliceSequence + i),
|
||||
json(R"({"LastLedgerSequence":7})"))
|
||||
);
|
||||
}
|
||||
|
||||
for (auto i = 1; i < 5; ++i)
|
||||
{
|
||||
env(tx[i], ter(terPRE_SEQ));
|
||||
expect(env.seq(alice) == aliceSequence);
|
||||
}
|
||||
|
||||
submitWait(env, tx[0],
|
||||
[&]()
|
||||
{
|
||||
return env.seq(alice) == aliceSequence + 5;
|
||||
});
|
||||
expect(env.seq(alice) == aliceSequence + 5);
|
||||
|
||||
env.close();
|
||||
|
||||
for (auto i = 0; i < 5; ++i)
|
||||
{
|
||||
auto const result = env.rpc("tx", to_string(tx[i].stx->getTransactionID()));
|
||||
expect(result.first == rpcSUCCESS);
|
||||
expect(result.second["result"]["meta"]["TransactionResult"] == "tesSUCCESS");
|
||||
}
|
||||
}
|
||||
|
||||
void run() override
|
||||
{
|
||||
testCorrectOrder();
|
||||
testIncorrectOrder();
|
||||
testIncorrectOrderMultipleIntermediaries();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(Transaction_ordering,app,ripple);
|
||||
|
||||
} // test
|
||||
} // ripple
|
||||
@@ -33,5 +33,6 @@
|
||||
#include <ripple/app/tests/SetAuth_test.cpp>
|
||||
#include <ripple/app/tests/OversizeMeta_test.cpp>
|
||||
#include <ripple/app/tests/Taker.test.cpp>
|
||||
#include <ripple/app/tests/Transaction_ordering_test.cpp>
|
||||
#include <ripple/app/tests/TxQ_test.cpp>
|
||||
#include <ripple/app/tests/ValidatorList_test.cpp>
|
||||
|
||||
@@ -1,255 +0,0 @@
|
||||
let assert = require('assert');
|
||||
let _ = require('lodash');
|
||||
let async = require('async');
|
||||
let testutils = require('./testutils');
|
||||
let config = testutils.init_config();
|
||||
let accounts = require('./testconfig').accounts;
|
||||
let Amount = require('ripple-lib').Amount;
|
||||
let Transaction = require('ripple-lib').Transaction;
|
||||
|
||||
suite('Transaction Ordering', function() {
|
||||
let $ = {};
|
||||
let opts = {};
|
||||
|
||||
setup(function(done) {
|
||||
testutils.build_setup(opts).call($, done);
|
||||
});
|
||||
|
||||
setup(function(done) {
|
||||
$.remote.local_signing = true;
|
||||
testutils.create_accounts(
|
||||
$.remote,
|
||||
'root',
|
||||
Amount.from_human('1000 XRP'),
|
||||
['alice'],
|
||||
done);
|
||||
});
|
||||
|
||||
teardown(function(done) {
|
||||
testutils.build_teardown().call($, done);
|
||||
});
|
||||
|
||||
function getAliceSequence() {
|
||||
return $.remote.account('alice')._entry.Sequence + 1;
|
||||
}
|
||||
|
||||
function confirmAliceSeq(expected, callback) {
|
||||
let request = $.remote.requestAccountInfo(
|
||||
{ account: accounts.alice.account, ledger: 'current' },
|
||||
function(err, res) {
|
||||
assert(!err);
|
||||
// console.log("Alice seq: ", res.account_data.Sequence, " Want: ", expected);
|
||||
assert.strictEqual(res.account_data.Sequence, expected);
|
||||
|
||||
if(callback) {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
test('correct order', function(done) {
|
||||
let aliceSequence = getAliceSequence();
|
||||
confirmAliceSeq(aliceSequence);
|
||||
|
||||
let tx1 = $.remote.createTransaction('AccountSet',
|
||||
{account: accounts.alice.account});
|
||||
tx1.setSequence(aliceSequence);
|
||||
|
||||
let tx2 = $.remote.createTransaction('AccountSet',
|
||||
{account: accounts.alice.account});
|
||||
tx2.setSequence(aliceSequence + 1);
|
||||
tx2.setLastLedgerSequenceOffset(5);
|
||||
|
||||
async.series([
|
||||
function(callback) {
|
||||
tx1.once('submitted', function(res) {
|
||||
// console.log("tx1 submitted: ", res.engine_result);
|
||||
assert.strictEqual(res.engine_result, 'tesSUCCESS');
|
||||
|
||||
$.remote.ledger_accept();
|
||||
});
|
||||
tx1.once('final', function(res) {
|
||||
// console.log("tx1 final: ", res.metadata.TransactionResult);
|
||||
assert.strictEqual(res.metadata.TransactionResult,
|
||||
'tesSUCCESS');
|
||||
|
||||
callback();
|
||||
});
|
||||
|
||||
tx1.submit();
|
||||
},
|
||||
function(callback) {
|
||||
confirmAliceSeq(aliceSequence + 1, callback);
|
||||
},
|
||||
function(callback) {
|
||||
tx2.once('submitted', function(res) {
|
||||
// console.log("tx2 submitted: ", res.engine_result);
|
||||
assert.strictEqual(res.engine_result, 'tesSUCCESS');
|
||||
|
||||
$.remote.ledger_accept();
|
||||
});
|
||||
tx2.once('final', function(res) {
|
||||
// console.log("tx2 final: ", res.metadata.TransactionResult);
|
||||
assert.strictEqual(res.metadata.TransactionResult,
|
||||
'tesSUCCESS');
|
||||
|
||||
callback();
|
||||
});
|
||||
|
||||
tx2.submit();
|
||||
},
|
||||
function(callback) {
|
||||
confirmAliceSeq(aliceSequence + 2, callback);
|
||||
}
|
||||
], done);
|
||||
});
|
||||
|
||||
test('incorrect order', function(done) {
|
||||
let aliceSequence = getAliceSequence();
|
||||
confirmAliceSeq(aliceSequence);
|
||||
|
||||
let tx1 = $.remote.createTransaction('AccountSet',
|
||||
{account: accounts.alice.account});
|
||||
tx1.setSequence(aliceSequence);
|
||||
|
||||
let tx2 = $.remote.createTransaction('AccountSet',
|
||||
{account: accounts.alice.account});
|
||||
tx2.setSequence(aliceSequence + 1);
|
||||
tx2.setLastLedgerSequenceOffset(5);
|
||||
|
||||
async.series([
|
||||
function(callback) {
|
||||
// Use `on` instead of `once` for this event, because
|
||||
// we want it to fail if ripple-lib resubmits on our
|
||||
// behalf, especially if it succeeds.
|
||||
tx2.on('submitted', function(res) {
|
||||
// console.log("tx2 submitted: ", res.engine_result);
|
||||
assert.strictEqual(res.engine_result, 'terPRE_SEQ');
|
||||
|
||||
callback();
|
||||
});
|
||||
|
||||
tx2.submit();
|
||||
},
|
||||
function(callback) {
|
||||
confirmAliceSeq(aliceSequence, callback);
|
||||
},
|
||||
function(callback) {
|
||||
tx1.once('submitted', function(res) {
|
||||
// console.log("tx1 submitted: ", res.engine_result);
|
||||
assert.strictEqual(res.engine_result, 'tesSUCCESS');
|
||||
|
||||
callback();
|
||||
});
|
||||
|
||||
tx1.submit();
|
||||
},
|
||||
function(callback) {
|
||||
confirmAliceSeq(aliceSequence + 2, callback);
|
||||
},
|
||||
function(callback) {
|
||||
tx1.once('final', function(res) {
|
||||
// console.log("tx1 final: ", res.metadata.TransactionResult);
|
||||
assert.strictEqual(res.metadata.TransactionResult,
|
||||
'tesSUCCESS');
|
||||
});
|
||||
tx2.once('final', function(res) {
|
||||
// console.log("tx2 final: ", res.metadata.TransactionResult);
|
||||
assert.strictEqual(res.metadata.TransactionResult,
|
||||
'tesSUCCESS');
|
||||
|
||||
callback();
|
||||
});
|
||||
|
||||
$.remote.ledger_accept();
|
||||
}
|
||||
], done);
|
||||
});
|
||||
|
||||
test('incorrect order, multiple intermediaries', function(done) {
|
||||
let aliceSequence = getAliceSequence();
|
||||
confirmAliceSeq(aliceSequence);
|
||||
|
||||
let tx = [];
|
||||
for(let i = 0; i < 5; ++i) {
|
||||
tx[i] = $.remote.createTransaction('AccountSet',
|
||||
{account: accounts.alice.account});
|
||||
tx[i].setSequence(aliceSequence + i);
|
||||
tx[i].setLastLedgerSequenceOffset(5);
|
||||
}
|
||||
|
||||
let submits = [];
|
||||
for(let i = 0; i < 3; ++i) {
|
||||
submits = submits.concat([
|
||||
function(callback) {
|
||||
tx[i].once('submitted', function(res) {
|
||||
// console.log("tx" + i + " submitted: ", res.engine_result);
|
||||
assert.strictEqual(res.engine_result, 'tesSUCCESS');
|
||||
|
||||
callback();
|
||||
});
|
||||
|
||||
tx[i].submit();
|
||||
},
|
||||
function(callback) {
|
||||
confirmAliceSeq(aliceSequence + i + 1, callback);
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
async.series([
|
||||
function(callback) {
|
||||
// Use `on` instead of `once` for this event, because
|
||||
// we want it to fail if ripple-lib resubmits on our
|
||||
// behalf, especially if it succeeds.
|
||||
tx[4].on('submitted', function(res) {
|
||||
// console.log("tx4 submitted: ", res.engine_result);
|
||||
assert.strictEqual(res.engine_result, 'terPRE_SEQ');
|
||||
|
||||
callback();
|
||||
});
|
||||
|
||||
tx[4].submit();
|
||||
// Note ripple-lib has a bug/feature that it'll create
|
||||
// transactions to fill in sequence gaps if more than
|
||||
// one "ter" is received, so this is the best we can do.
|
||||
},
|
||||
function(callback) {
|
||||
confirmAliceSeq(aliceSequence, callback);
|
||||
},
|
||||
].concat(submits).concat([
|
||||
function(callback) {
|
||||
tx[3].once('submitted', function(res) {
|
||||
// console.log("tx3 submitted: ", res.engine_result);
|
||||
assert.strictEqual(res.engine_result, 'tesSUCCESS');
|
||||
|
||||
callback();
|
||||
});
|
||||
|
||||
tx[3].submit();
|
||||
},
|
||||
function(callback) {
|
||||
confirmAliceSeq(aliceSequence + 5, callback);
|
||||
},
|
||||
function(callback) {
|
||||
for(let i = 0; i < 4; ++i) {
|
||||
tx[i].once('final', function(res) {
|
||||
// console.log("tx" + i + " final: ", res.metadata.TransactionResult);
|
||||
assert.strictEqual(res.metadata.TransactionResult,
|
||||
'tesSUCCESS');
|
||||
});
|
||||
}
|
||||
tx[4].once('final', function(res) {
|
||||
// console.log("tx4 final: ", res.metadata.TransactionResult);
|
||||
assert.strictEqual(res.metadata.TransactionResult,
|
||||
'tesSUCCESS');
|
||||
|
||||
callback();
|
||||
});
|
||||
|
||||
$.remote.ledger_accept();
|
||||
}
|
||||
]), done);
|
||||
});
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user