Add SuspendedPayment feature (RIPD-992):

The code is enabled in jtx::Env, and enabled in production
ledgers only if the SuspendedPayment amendment is voted
into a ledger.
This commit is contained in:
Vinnie Falco
2015-07-26 14:25:59 -07:00
committed by Edward Hennis
parent d49f9ea109
commit 3f0eacf5e7
25 changed files with 1043 additions and 15 deletions

View File

@@ -1709,6 +1709,10 @@
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\ripple\app\tests\SusPay_test.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClInclude Include="..\..\src\ripple\app\tx\apply.h">
</ClInclude>
<ClCompile Include="..\..\src\ripple\app\tx\impl\apply.cpp">
@@ -1809,6 +1813,12 @@
</ClCompile>
<ClInclude Include="..\..\src\ripple\app\tx\impl\SignerEntries.h">
</ClInclude>
<ClCompile Include="..\..\src\ripple\app\tx\impl\SusPay.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClInclude Include="..\..\src\ripple\app\tx\impl\SusPay.h">
</ClInclude>
<ClCompile Include="..\..\src\ripple\app\tx\impl\Taker.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
@@ -3168,7 +3178,7 @@
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\ripple\rpc\handlers\Feature.cpp">
<ClCompile Include="..\..\src\ripple\rpc\handlers\Feature1.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile>
@@ -3696,6 +3706,10 @@
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\ripple\test\jtx\impl\tag.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\ripple\test\jtx\impl\ticket.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
@@ -3746,6 +3760,8 @@
</ClInclude>
<ClInclude Include="..\..\src\ripple\test\jtx\sig.h">
</ClInclude>
<ClInclude Include="..\..\src\ripple\test\jtx\tag.h">
</ClInclude>
<ClInclude Include="..\..\src\ripple\test\jtx\tags.h">
</ClInclude>
<ClInclude Include="..\..\src\ripple\test\jtx\ter.h">

View File

@@ -2457,6 +2457,9 @@
<ClCompile Include="..\..\src\ripple\app\tests\Regression_test.cpp">
<Filter>ripple\app\tests</Filter>
</ClCompile>
<ClCompile Include="..\..\src\ripple\app\tests\SusPay_test.cpp">
<Filter>ripple\app\tests</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ripple\app\tx\apply.h">
<Filter>ripple\app\tx</Filter>
</ClInclude>
@@ -2556,6 +2559,12 @@
<ClInclude Include="..\..\src\ripple\app\tx\impl\SignerEntries.h">
<Filter>ripple\app\tx\impl</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ripple\app\tx\impl\SusPay.cpp">
<Filter>ripple\app\tx\impl</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ripple\app\tx\impl\SusPay.h">
<Filter>ripple\app\tx\impl</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ripple\app\tx\impl\Taker.cpp">
<Filter>ripple\app\tx\impl</Filter>
</ClCompile>
@@ -3870,7 +3879,7 @@
<ClCompile Include="..\..\src\ripple\rpc\handlers\ConsensusInfo.cpp">
<Filter>ripple\rpc\handlers</Filter>
</ClCompile>
<ClCompile Include="..\..\src\ripple\rpc\handlers\Feature.cpp">
<ClCompile Include="..\..\src\ripple\rpc\handlers\Feature1.cpp">
<Filter>ripple\rpc\handlers</Filter>
</ClCompile>
<ClCompile Include="..\..\src\ripple\rpc\handlers\FetchInfo.cpp">
@@ -4362,6 +4371,9 @@
<ClCompile Include="..\..\src\ripple\test\jtx\impl\sig.cpp">
<Filter>ripple\test\jtx\impl</Filter>
</ClCompile>
<ClCompile Include="..\..\src\ripple\test\jtx\impl\tag.cpp">
<Filter>ripple\test\jtx\impl</Filter>
</ClCompile>
<ClCompile Include="..\..\src\ripple\test\jtx\impl\ticket.cpp">
<Filter>ripple\test\jtx\impl</Filter>
</ClCompile>
@@ -4425,6 +4437,9 @@
<ClInclude Include="..\..\src\ripple\test\jtx\sig.h">
<Filter>ripple\test\jtx</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ripple\test\jtx\tag.h">
<Filter>ripple\test\jtx</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ripple\test\jtx\tags.h">
<Filter>ripple\test\jtx</Filter>
</ClInclude>

View File

@@ -0,0 +1,322 @@
//------------------------------------------------------------------------------
/*
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/test/jtx.h>
#include <ripple/protocol/digest.h>
#include <ripple/protocol/Indexes.h>
#include <ripple/protocol/JsonFields.h>
#include <ripple/protocol/TxFlags.h>
namespace ripple {
namespace test {
struct SusPay_test : public beast::unit_test::suite
{
template <class... Args>
static
uint256
digest (Args&&... args)
{
sha256_hasher h;
using beast::hash_append;
hash_append(h, args...);
auto const d = static_cast<
sha256_hasher::result_type>(h);
uint256 result;
std::memcpy(result.data(), d.data(), d.size());
return result;
}
// Create condition
// First is digest, second is pre-image
static
std::pair<uint256, uint256>
cond (std::string const& receipt)
{
std::pair<uint256, uint256> result;
result.second = digest(receipt);
result.first = digest(result.second);
return result;
}
static
Json::Value
condpay (jtx::Account const& account, jtx::Account const& to,
STAmount const& amount, uint256 const& digest,
NetClock::time_point const& expiry)
{
using namespace jtx;
Json::Value jv;
jv[jss::TransactionType] = "SuspendedPaymentCreate";
jv[jss::Flags] = tfUniversal;
jv[jss::Account] = account.human();
jv[jss::Destination] = to.human();
jv[jss::Amount] = amount.getJson(0);
jv["CancelAfter"] =
expiry.time_since_epoch().count();
jv["Digest"] = to_string(digest);
return jv;
}
static
Json::Value
lockup (jtx::Account const& account, jtx::Account const& to,
STAmount const& amount, NetClock::time_point const& expiry)
{
using namespace jtx;
Json::Value jv;
jv[jss::TransactionType] = "SuspendedPaymentCreate";
jv[jss::Flags] = tfUniversal;
jv[jss::Account] = account.human();
jv[jss::Destination] = to.human();
jv[jss::Amount] = amount.getJson(0);
jv["FinishAfter"] =
expiry.time_since_epoch().count();
return jv;
}
static
Json::Value
finish (jtx::Account const& account,
jtx::Account const& from, std::uint32_t seq)
{
Json::Value jv;
jv[jss::TransactionType] = "SuspendedPaymentFinish";
jv[jss::Flags] = tfUniversal;
jv[jss::Account] = account.human();
jv["Owner"] = from.human();
jv["OfferSequence"] = seq;
return jv;
}
static
Json::Value
finish (jtx::Account const& account,
jtx::Account const& from, std::uint32_t seq,
uint256 const& digest, uint256 const& preimage)
{
Json::Value jv;
jv[jss::TransactionType] = "SuspendedPaymentFinish";
jv[jss::Flags] = tfUniversal;
jv[jss::Account] = account.human();
jv["Owner"] = from.human();
jv["OfferSequence"] = seq;
jv["Method"] = 1;
jv["Digest"] = to_string(digest);
jv["Proof"] = to_string(preimage);
return jv;
}
static
Json::Value
cancel (jtx::Account const& account,
jtx::Account const& from, std::uint32_t seq)
{
Json::Value jv;
jv[jss::TransactionType] = "SuspendedPaymentCancel";
jv[jss::Flags] = tfUniversal;
jv[jss::Account] = account.human();
jv["Owner"] = from.human();
jv["OfferSequence"] = seq;
return jv;
}
void
testEnablement()
{
using namespace jtx;
using namespace std::chrono;
using S = seconds;
{
Env env(*this);
auto const T = [&env](NetClock::duration const& d)
{ return env.clock.now() + d; };
env.fund(XRP(5000), "alice", "bob");
auto const c = cond("receipt");
// syntax
env(condpay("alice", "bob", XRP(1000), c.first, T(S{1})));
env.disable_testing();
// disabled in production
env(condpay("alice", "bob", XRP(1000), c.first, T(S{1})), ter(temDISABLED));
env(finish("bob", "alice", 1), ter(temDISABLED));
env(cancel("bob", "alice", 1), ter(temDISABLED));
}
}
void
testTags()
{
using namespace jtx;
using namespace std::chrono;
using S = seconds;
{
Env env(*this);
auto const alice = Account("alice");
auto const T = [&env](NetClock::duration const& d)
{ return env.clock.now() + d; };
env.fund(XRP(5000), alice, "bob");
auto const c = cond("receipt");
auto const seq = env.seq(alice);
// set source and dest tags
env(condpay(alice, "bob", XRP(1000), c.first, T(S{1})), stag(1), dtag(2));
auto const sle = env.le(keylet::susPay(alice.id(), seq));
expect((*sle)[sfSourceTag] == 1);
expect((*sle)[sfDestinationTag] == 2);
}
}
void
testFails()
{
using namespace jtx;
using namespace std::chrono;
using S = seconds;
{
Env env(*this);
auto const T = [&env](NetClock::duration const& d)
{ return env.clock.now() + d; };
env.fund(XRP(5000), "alice", "bob");
auto const c = cond("receipt");
// VFALCO Should we enforce this?
// expiration in the past
//env(condpay("alice", "bob", XRP(1000), c.first, T(S{-1})), ter(tecNO_PERMISSION));
// expiration beyond the limit
env(condpay("alice", "bob", XRP(1000), c.first, T(days(7+1))), ter(tecNO_PERMISSION));
// no destination account
env(condpay("alice", "carol", XRP(1000), c.first, T(S{1})), ter(tecNO_DST));
env.fund(XRP(5000), "carol");
env(condpay("alice", "carol",
XRP(1000), c.first, T(S{1})), stag(2));
env(condpay("alice", "carol",
XRP(1000), c.first, T(S{1})), stag(3), dtag(4));
env(fset("carol", asfRequireDest));
// missing destination tag
env(condpay("alice", "carol", XRP(1000), c.first, T(S{1})), ter(tecDST_TAG_NEEDED));
env(condpay("alice", "carol",
XRP(1000), c.first, T(S{1})), dtag(1));
}
}
void
testLockup()
{
using namespace jtx;
using namespace std::chrono;
using S = seconds;
{
Env env(*this);
auto const T = [&env](NetClock::duration const& d)
{ return env.clock.now() + d; };
env.fund(XRP(5000), "alice", "bob");
auto const seq = env.seq("alice");
env(lockup("alice", "alice", XRP(1000), T(S{1})));
env.require(balance("alice", XRP(4000) - drops(10)));
env(cancel("bob", "alice", seq), ter(tecNO_PERMISSION));
env(finish("bob", "alice", seq), ter(tecNO_PERMISSION));
env.close();
env(cancel("bob", "alice", seq), ter(tecNO_PERMISSION));
env(finish("bob", "alice", seq));
}
}
void
testCondPay()
{
using namespace jtx;
using namespace std::chrono;
using S = seconds;
{
Env env(*this);
auto const T = [&env](NetClock::duration const& d)
{ return env.clock.now() + d; };
env.fund(XRP(5000), "alice", "bob", "carol");
auto const c = cond("receipt");
auto const seq = env.seq("alice");
expect((*env.le("alice"))[sfOwnerCount] == 0);
env(condpay("alice", "carol", XRP(1000), c.first, T(S{1})));
expect((*env.le("alice"))[sfOwnerCount] == 1);
env.require(balance("alice", XRP(4000) - drops(10)));
env.require(balance("carol", XRP(5000)));
env(cancel("bob", "alice", seq), ter(tecNO_PERMISSION));
expect((*env.le("alice"))[sfOwnerCount] == 1);
env(finish("bob", "alice", seq, c.first, c.first), ter(temBAD_SIGNATURE));
expect((*env.le("alice"))[sfOwnerCount] == 1);
env(finish("bob", "alice", seq, c.first, c.second));
// SLE removed on finish
expect(! env.le(keylet::susPay(Account("alice").id(), seq)));
expect((*env.le("alice"))[sfOwnerCount] == 0);
env.require(balance("carol", XRP(6000)));
env(cancel("bob", "alice", seq), ter(tecNO_TARGET));
expect((*env.le("alice"))[sfOwnerCount] == 0);
env(cancel("bob", "carol", 1), ter(tecNO_TARGET));
env.close();
}
{
Env env(*this);
auto const T = [&env](NetClock::duration const& d)
{ return env.clock.now() + d; };
env.fund(XRP(5000), "alice", "bob", "carol");
auto const c = cond("receipt");
auto const seq = env.seq("alice");
expect((*env.le("alice"))[sfOwnerCount] == 0);
env(condpay("alice", "carol", XRP(1000), c.first, T(S{1})));
env.close();
env.require(balance("alice", XRP(4000) - drops(10)));
// balance restored on cancel
env(cancel("bob", "alice", seq));
env.require(balance("alice", XRP(5000) - drops(10)));
// SLE removed on cancel
expect(! env.le(keylet::susPay(Account("alice").id(), seq)));
}
{
Env env(*this);
auto const T = [&env](NetClock::duration const& d)
{ return env.clock.now() + d; };
env.fund(XRP(5000), "alice", "bob", "carol");
env.close();
auto const c = cond("receipt");
auto const seq = env.seq("alice");
env(condpay("alice", "carol", XRP(1000), c.first, T(S{1})));
expect((*env.le("alice"))[sfOwnerCount] == 1);
// cancel fails before expiration
env(cancel("bob", "alice", seq), ter(tecNO_PERMISSION));
expect((*env.le("alice"))[sfOwnerCount] == 1);
env.close();
// finish fails after expiration
env(finish("bob", "alice", seq, c.first, c.second), ter(tecNO_PERMISSION));
expect((*env.le("alice"))[sfOwnerCount] == 1);
env.require(balance("carol", XRP(5000)));
}
}
void run() override
{
testEnablement();
testTags();
testFails();
testLockup();
testCondPay();
}
};
BEAST_DEFINE_TESTSUITE(SusPay,app,ripple);
} // test
} // ripple

View File

@@ -0,0 +1,410 @@
//------------------------------------------------------------------------------
/*
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/app/tx/impl/SusPay.h>
#include <ripple/basics/chrono.h>
#include <ripple/basics/Log.h>
#include <ripple/protocol/digest.h>
#include <ripple/protocol/st.h>
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/Indexes.h>
#include <ripple/protocol/TxFlags.h>
#include <ripple/protocol/XRPAmount.h>
#include <ripple/ledger/View.h>
namespace ripple {
/*
SuspendedPayment
A suspended payment ("SusPay") sequesters XRP in its
own ledger entry until a SusPayFinish or a SusPayCancel
transaction mentioning the ledger entry is successfully
applied to the ledger. If the SusPayFinish succeeds,
the destination account (which must exist) receives the
XRP. If the SusPayCancel succeeds, the account which
created the SusPay is credited the XRP.
SusPayCreate
When the SusPay is created, an optional condition may
be attached. The condition is specified by providing
the cryptographic digest of the condition to be met.
At the time of creation, one or both of the fields
sfCancelAfter and sfFinishAfter may be provided. If
neither field is specified, the transaction is
malformed.
Since the SusPay eventually becomes a payment, an
optional DestinationTag and an optional SourceTag
is supported in the SusPayCreate transaction.
Validation rules:
sfDigest
If present, proof is required on a SusPayFinish.
sfCancelAfter
If present, SusPay may be canceled after the
specified time (seconds after the Ripple epoch).
sfFinishAfter
If present, must be prior to sfCancelAfter.
A SusPayFinish succeeds only in ledgers after
sfFinishAfter but before sfCancelAfter.
If absent, same as parentCloseTime
Malformed if both sfCancelAfter, sfFinishAfter
are absent.
Malformed if both sfFinishAfter, sfCancelAfter
specified and sfCancelAfter <= sfFinishAfter
SusPayFinish
Any account may submit a SusPayFinish. If the SusPay
ledger entry specifies a condition, the SusPayFinish
must provide the sfMethod, original sfDigest, and
sfProof fields. Depending on the method, a
cryptographic operation will be performed on sfProof
and the result must match the sfDigest or else the
SusPayFinish is considered as having an invalid
signature.
Only sfMethod==1 is supported, where sfProof must be a
256-bit unsigned big-endian integer which when hashed
using SHA256 produces digest == sfDigest.
If the SusPay ledger entry specifies sfFinishAfter, the
transaction will fail if parentCloseTime <= sfFinishAfter.
SusPayFinish transactions must be submitted before a
SusPay's sfCancelAfter if present.
If the SusPay ledger entry specifies sfCancelAfter, the
transaction will fail if sfCancelAfter <= parentCloseTime.
NOTE: It must always be possible to verify the condition
without retrieving the SusPay ledger entry.
SusPayCancel
Any account may submit a SusPayCancel transaction.
If the SusPay ledger entry does not specify a
sfCancelAfter, the cancel transaction will fail.
If parentCloseTime <= sfCancelAfter, the transaction
will fail.
When a SusPay is canceled, the funds are returned to
the source account.
By careful selection of fields in each transaction,
these operations may be achieved:
* Lock up XRP for a time period
* Execute a payment conditionally
*/
//------------------------------------------------------------------------------
TER
SusPayCreate::preflight (PreflightContext const& ctx)
{
if (! (ctx.flags & tapENABLE_TESTING) &&
! ctx.rules.enabled(featureSusPay,
ctx.config.features))
return temDISABLED;
if (! isXRP(ctx.tx[sfAmount]))
return temBAD_AMOUNT;
if (ctx.tx[sfAmount] <= beast::zero)
return temBAD_AMOUNT;
if (! ctx.tx[~sfCancelAfter] &&
! ctx.tx[~sfFinishAfter])
return temBAD_EXPIRATION;
if (ctx.tx[~sfCancelAfter] && ctx.tx[~sfFinishAfter] &&
ctx.tx[sfCancelAfter] <= ctx.tx[sfFinishAfter])
return temBAD_EXPIRATION;
return Transactor::preflight(ctx);
}
TER
SusPayCreate::doApply()
{
// For now, require that all operations can be
// canceled, or finished without proof, within a
// reasonable period of time for the first release.
using namespace std::chrono;
auto const maxExpire =
ctx_.view().info().parentCloseTime +
seconds{days(7)}.count();
if (ctx_.tx[~sfDigest])
{
if (! ctx_.tx[~sfCancelAfter] ||
maxExpire <= ctx_.tx[sfCancelAfter])
return tecNO_PERMISSION;
}
else
{
if (ctx_.tx[~sfCancelAfter] &&
maxExpire <= ctx_.tx[sfCancelAfter])
return tecNO_PERMISSION;
if (ctx_.tx[~sfFinishAfter] &&
maxExpire <= ctx_.tx[sfFinishAfter])
return tecNO_PERMISSION;
}
XRPAmount const amount =
STAmount(ctx_.tx[sfAmount]).mantissa();
auto const account = ctx_.tx[sfAccount];
auto const sle = ctx_.view().peek(
keylet::account(account));
if (XRPAmount(STAmount((*sle)[sfBalance]).mantissa()) <
XRPAmount(ctx_.view().fees().accountReserve(
(*sle)[sfOwnerCount] + 1)))
return tecINSUFFICIENT_RESERVE;
if (XRPAmount(STAmount((*sle)[sfBalance]).mantissa()) <
amount + XRPAmount(ctx_.view().fees().accountReserve(
(*sle)[sfOwnerCount] + 1)))
return tecUNFUNDED;
// Check destination account
{
auto const sled = ctx_.view().read(
keylet::account(ctx_.tx[sfDestination]));
if (! sled)
return tecNO_DST;
if (((*sled)[sfFlags] & lsfRequireDestTag) &&
! ctx_.tx[~sfDestinationTag])
return tecDST_TAG_NEEDED;
if ((*sled)[sfFlags] & lsfDisallowXRP)
return tecNO_TARGET;
}
// Create SusPay in ledger
auto const slep = std::make_shared<SLE>(
keylet::susPay(account, (*sle)[sfSequence] - 1));
(*slep)[sfAmount] = ctx_.tx[sfAmount];
(*slep)[sfAccount] = account;
(*slep)[~sfDigest] = ctx_.tx[~sfDigest];
(*slep)[~sfSourceTag] = ctx_.tx[~sfSourceTag];
(*slep)[sfDestination] = ctx_.tx[sfDestination];
(*slep)[~sfCancelAfter] = ctx_.tx[~sfCancelAfter];
(*slep)[~sfFinishAfter] = ctx_.tx[~sfFinishAfter];
(*slep)[~sfDestinationTag] = ctx_.tx[~sfDestinationTag];
ctx_.view().insert(slep);
// Add SusPay to owner directory
{
uint64_t page;
TER ter = dirAdd(ctx_.view(), page,
keylet::ownerDir(account).key,
slep->key(), describeOwnerDir(account));
if (! isTesSuccess(ter))
return ter;
(*slep)[sfOwnerNode] = page;
}
// Deduct owner's balance, increment owner count
(*sle)[sfBalance] = (*sle)[sfBalance] - ctx_.tx[sfAmount];
(*sle)[sfOwnerCount] = (*sle)[sfOwnerCount] + 1;
ctx_.view().update(sle);
return tesSUCCESS;
}
//------------------------------------------------------------------------------
TER
SusPayFinish::preflight (PreflightContext const& ctx)
{
if (! (ctx.flags & tapENABLE_TESTING) &&
! ctx.rules.enabled(featureSusPay,
ctx.config.features))
return temDISABLED;
if (ctx.tx[~sfMethod])
{
// Condition
switch(ctx.tx[sfMethod])
{
case 1:
{
if (! ctx.tx[~sfDigest])
return temMALFORMED;
if (! ctx.tx[~sfProof])
return temMALFORMED;
sha256_hasher h;
using beast::hash_append;
hash_append(h, ctx.tx[sfProof]);
uint256 digest;
{
auto const result = static_cast<
sha256_hasher::result_type>(h);
std::memcpy(digest.data(),
result.data(), result.size());
}
if (digest != ctx.tx[sfDigest])
return temBAD_SIGNATURE;
break;
}
default:
return temMALFORMED;
}
}
else
{
// No Condition
if (ctx.tx[~sfDigest])
return temMALFORMED;
if (ctx.tx[~sfProof])
return temMALFORMED;
}
return Transactor::preflight(ctx);
}
TER
SusPayFinish::doApply()
{
// peek SusPay SLE
auto const k = keylet::susPay(
ctx_.tx[sfOwner], ctx_.tx[sfOfferSequence]);
auto const slep = ctx_.view().peek(k);
if (! slep)
return tecNO_TARGET;
// Too soon?
if ((*slep)[~sfFinishAfter] &&
ctx_.view().info().parentCloseTime <=
(*slep)[sfFinishAfter])
return tecNO_PERMISSION;
// Too late?
if ((*slep)[~sfCancelAfter] &&
(*slep)[sfCancelAfter] <=
ctx_.view().info().parentCloseTime)
return tecNO_PERMISSION;
AccountID const account = (*slep)[sfAccount];
// Remove SusPay from owner directory
{
auto const page = (*slep)[sfOwnerNode];
TER const ter = dirDelete(ctx_.view(), true,
page, keylet::ownerDir(account).key,
k.key, false, page == 0);
if (! isTesSuccess(ter))
return ter;
}
// NOTE: These payments cannot be used to fund accounts
// Fetch Destination SLE
auto const sled = ctx_.view().peek(
keylet::account((*slep)[sfDestination]));
if (! sled)
return tecNO_DST;
// Transfer amount to destination
(*sled)[sfBalance] = (*sled)[sfBalance] + (*slep)[sfAmount];
ctx_.view().update(sled);
// Adjust source owner count
auto const sle = ctx_.view().peek(
keylet::account(account));
(*sle)[sfOwnerCount] = (*sle)[sfOwnerCount] - 1;
ctx_.view().update(sle);
// Remove SusPay from ledger
ctx_.view().erase(slep);
return tesSUCCESS;
}
//------------------------------------------------------------------------------
TER
SusPayCancel::preflight (PreflightContext const& ctx)
{
if (! (ctx.flags & tapENABLE_TESTING) &&
! ctx.rules.enabled(featureSusPay,
ctx.config.features))
return temDISABLED;
return Transactor::preflight(ctx);
}
TER
SusPayCancel::doApply()
{
// peek SusPay SLE
auto const k = keylet::susPay(
ctx_.tx[sfOwner], ctx_.tx[sfOfferSequence]);
auto const slep = ctx_.view().peek(k);
if (! slep)
return tecNO_TARGET;
// Too soon?
if (! (*slep)[~sfCancelAfter] ||
ctx_.view().info().parentCloseTime <=
(*slep)[sfCancelAfter])
return tecNO_PERMISSION;
AccountID const account = (*slep)[sfAccount];
// Remove SusPay from owner directory
{
auto const page = (*slep)[sfOwnerNode];
TER const ter = dirDelete(ctx_.view(), true,
page, keylet::ownerDir(account).key,
k.key, false, page == 0);
if (! isTesSuccess(ter))
return ter;
}
// Transfer amount back to owner, decrement owner count
auto const sle = ctx_.view().peek(
keylet::account(account));
(*sle)[sfBalance] = (*sle)[sfBalance] + (*slep)[sfAmount];
(*sle)[sfOwnerCount] = (*sle)[sfOwnerCount] - 1;
ctx_.view().update(sle);
// Remove SusPay from ledger
ctx_.view().erase(slep);
return tesSUCCESS;
}
} // ripple

View File

@@ -0,0 +1,87 @@
//------------------------------------------------------------------------------
/*
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.
*/
//==============================================================================
#ifndef RIPPLE_TX_SUSPAY_H_INCLUDED
#define RIPPLE_TX_SUSPAY_H_INCLUDED
#include <ripple/app/tx/impl/Transactor.h>
namespace ripple {
class SusPayCreate
: public Transactor
{
public:
explicit
SusPayCreate (ApplyContext& ctx)
: Transactor(ctx)
{
}
static
TER
preflight (PreflightContext const& ctx);
TER
doApply() override;
};
//------------------------------------------------------------------------------
class SusPayFinish
: public Transactor
{
public:
explicit
SusPayFinish (ApplyContext& ctx)
: Transactor(ctx)
{
}
static
TER
preflight (PreflightContext const& ctx);
TER
doApply() override;
};
//------------------------------------------------------------------------------
class SusPayCancel
: public Transactor
{
public:
explicit
SusPayCancel (ApplyContext& ctx)
: Transactor(ctx)
{
}
static
TER
preflight (PreflightContext const& ctx);
TER
doApply() override;
};
} // ripple
#endif

View File

@@ -30,6 +30,7 @@
#include <ripple/app/tx/impl/SetRegularKey.h>
#include <ripple/app/tx/impl/SetSignerList.h>
#include <ripple/app/tx/impl/SetTrust.h>
#include <ripple/app/tx/impl/SusPay.h>
namespace ripple {
@@ -43,6 +44,9 @@ invoke_preflight (PreflightContext const& ctx)
case ttOFFER_CANCEL: return CancelOffer ::preflight(ctx);
case ttOFFER_CREATE: return CreateOffer ::preflight(ctx);
case ttPAYMENT: return Payment ::preflight(ctx);
case ttSUSPAY_CREATE: return SusPayCreate ::preflight(ctx);
case ttSUSPAY_FINISH: return SusPayFinish ::preflight(ctx);
case ttSUSPAY_CANCEL: return SusPayCancel ::preflight(ctx);
case ttREGULAR_KEY_SET: return SetRegularKey ::preflight(ctx);
case ttSIGNER_LIST_SET: return SetSignerList ::preflight(ctx);
case ttTICKET_CANCEL: return CancelTicket ::preflight(ctx);
@@ -65,6 +69,9 @@ invoke_apply (ApplyContext& ctx)
case ttOFFER_CANCEL: { CancelOffer p(ctx); return p(); }
case ttOFFER_CREATE: { CreateOffer p(ctx); return p(); }
case ttPAYMENT: { Payment p(ctx); return p(); }
case ttSUSPAY_CREATE: { SusPayCreate p(ctx); return p(); }
case ttSUSPAY_FINISH: { SusPayFinish p(ctx); return p(); }
case ttSUSPAY_CANCEL: { SusPayCancel p(ctx); return p(); }
case ttREGULAR_KEY_SET: { SetRegularKey p(ctx); return p(); }
case ttSIGNER_LIST_SET: { SetSignerList p(ctx); return p(); }
case ttTICKET_CANCEL: { CancelTicket p(ctx); return p(); }

View File

@@ -34,6 +34,8 @@ uint256
feature (const char* name);
/** @} */
extern uint256 const featureSusPay;
} // ripple
#endif

View File

@@ -23,6 +23,7 @@
#include <ripple/protocol/Keylet.h>
#include <ripple/protocol/LedgerFormats.h>
#include <ripple/protocol/Protocol.h>
#include <ripple/protocol/PublicKey.h>
#include <ripple/protocol/RippleAddress.h>
#include <ripple/protocol/Serializer.h>
#include <ripple/protocol/UintTypes.h>
@@ -234,6 +235,10 @@ Keylet page (uint256 const& key)
return { ltDIR_NODE, key };
}
/** A SuspendedPayment */
Keylet
susPay (AccountID const& source, std::uint32_t seq);
} // keylet
}

View File

@@ -64,15 +64,12 @@ enum LedgerEntryType
*/
ltDIR_NODE = 'd',
/** Describes a trust line.
*/
ltRIPPLE_STATE = 'r',
ltTICKET = 'T',
ltSIGNER_LIST = 'S',
/* Deprecated. */
ltOFFER = 'o',
ltLEDGER_HASHES = 'h',
@@ -81,6 +78,8 @@ enum LedgerEntryType
ltFEE_SETTINGS = 's',
ltSUSPAY = 'u',
// No longer used or supported. Left here to prevent accidental
// reassignment of the ledger type.
ltNICKNAME = 'n',
@@ -103,6 +102,7 @@ enum LedgerNameSpace
spaceBookDir = 'B', // Directory of order books.
spaceContract = 'c',
spaceSkipList = 's',
spaceSusPay = 'u',
spaceAmendment = 'f',
spaceFee = 'e',
spaceTicket = 'T',

View File

@@ -344,7 +344,7 @@ extern SField const sfMetadata;
// 8-bit integers
extern SF_U8 const sfCloseResolution;
extern SF_U8 const sfTemplateEntryType;
extern SF_U8 const sfMethod;
extern SF_U8 const sfTransactionResult;
// 16-bit integers
@@ -388,6 +388,8 @@ extern SF_U32 const sfReserveIncrement;
extern SF_U32 const sfSetFlag;
extern SF_U32 const sfClearFlag;
extern SF_U32 const sfSignerQuorum;
extern SF_U32 const sfCancelAfter;
extern SF_U32 const sfFinishAfter;
// 64-bit integers
extern SF_U64 const sfIndexNext;
@@ -461,7 +463,7 @@ extern SF_Blob const sfMemoFormat;
// variable length (uncommon)
extern SF_Blob const sfMultiSignature;
extern SF_Blob const sfInnerSig;
extern SF_Blob const sfProof;
// account
extern SF_Account const sfAccount;

View File

@@ -35,10 +35,10 @@ enum TxType
ttINVALID = -1,
ttPAYMENT = 0,
ttCLAIM = 1, // open
ttWALLET_ADD = 2, // unused
ttSUSPAY_CREATE = 1,
ttSUSPAY_FINISH = 2,
ttACCOUNT_SET = 3,
ttPASSWORD_FUND = 4, // open
ttSUSPAY_CANCEL = 4,
ttREGULAR_KEY_SET = 5,
ttNICKNAME_SET = 6, // open
ttOFFER_CREATE = 7,

View File

@@ -45,4 +45,6 @@ feature (const char* name)
return feature(name, std::strlen(name));
}
uint256 const featureSusPay = feature("SusPay");
} // ripple

View File

@@ -308,6 +308,17 @@ Keylet page(Keylet const& root,
return page(root.key, index);
}
Keylet
susPay (AccountID const& source, std::uint32_t seq)
{
sha512_half_hasher h;
using beast::hash_append;
hash_append(h, spaceSusPay);
hash_append(h, source);
hash_append(h, seq);
return { ltSUSPAY, static_cast<uint256>(h) };
}
} // keylet
} // ripple

View File

@@ -81,6 +81,19 @@ LedgerFormats::LedgerFormats ()
<< SOElement (sfHighQualityOut, SOE_OPTIONAL)
;
add ("SusPay", ltSUSPAY) <<
SOElement (sfAccount, SOE_REQUIRED) <<
SOElement (sfDestination, SOE_REQUIRED) <<
SOElement (sfAmount, SOE_REQUIRED) <<
SOElement (sfDigest, SOE_OPTIONAL) <<
SOElement (sfCancelAfter, SOE_OPTIONAL) <<
SOElement (sfFinishAfter, SOE_OPTIONAL) <<
SOElement (sfSourceTag, SOE_OPTIONAL) <<
SOElement (sfDestinationTag, SOE_OPTIONAL) <<
SOElement (sfOwnerNode, SOE_REQUIRED) <<
SOElement (sfPreviousTxnID, SOE_REQUIRED) <<
SOElement (sfPreviousTxnLgrSeq, SOE_REQUIRED);
add ("LedgerHashes", ltLEDGER_HASHES)
<< SOElement (sfFirstLedgerSequence, SOE_OPTIONAL) // Remove if we do a ledger restart
<< SOElement (sfLastLedgerSequence, SOE_OPTIONAL)

View File

@@ -77,7 +77,7 @@ SField const sfIndex = make::one(&sfIndex, STI_HASH256, 258, "in
// 8-bit integers
SF_U8 const sfCloseResolution = make::one<SF_U8::type>(&sfCloseResolution, STI_UINT8, 1, "CloseResolution");
SF_U8 const sfTemplateEntryType = make::one<SF_U8::type>(&sfTemplateEntryType, STI_UINT8, 2, "TemplateEntryType");
SF_U8 const sfMethod = make::one<SF_U8::type>(&sfMethod, STI_UINT8, 2, "Method");
SF_U8 const sfTransactionResult = make::one<SF_U8::type>(&sfTransactionResult, STI_UINT8, 3, "TransactionResult");
// 16-bit integers
@@ -121,6 +121,8 @@ SF_U32 const sfReserveIncrement = make::one<SF_U32::type>(&sfReserveIncrement
SF_U32 const sfSetFlag = make::one<SF_U32::type>(&sfSetFlag, STI_UINT32, 33, "SetFlag");
SF_U32 const sfClearFlag = make::one<SF_U32::type>(&sfClearFlag, STI_UINT32, 34, "ClearFlag");
SF_U32 const sfSignerQuorum = make::one<SF_U32::type>(&sfSignerQuorum, STI_UINT32, 35, "SignerQuorum");
SF_U32 const sfCancelAfter = make::one<SF_U32::type>(&sfCancelAfter, STI_UINT32, 36, "CancelAfter");
SF_U32 const sfFinishAfter = make::one<SF_U32::type>(&sfFinishAfter, STI_UINT32, 37, "FinishAfter");
// 64-bit integers
SF_U64 const sfIndexNext = make::one<SF_U64::type>(&sfIndexNext, STI_UINT64, 1, "IndexNext");
@@ -194,7 +196,7 @@ SF_Blob const sfMemoFormat = make::one<SF_Blob::type>(&sfMemoFormat, STI
// variable length (uncommon)
SF_Blob const sfMultiSignature = make::one<SF_Blob::type>(&sfMultiSignature, STI_VL, 16, "MultiSignature");
SF_Blob const sfInnerSig = make::one<SF_Blob::type>(&sfInnerSig, STI_VL, 17, "InnerSig");
SF_Blob const sfProof = make::one<SF_Blob::type>(&sfProof, STI_VL, 17, "Proof");
// account
SF_Account const sfAccount = make::one<SF_Account::type>(&sfAccount, STI_ACCOUNT, 1, "Account");

View File

@@ -67,6 +67,25 @@ TxFormats::TxFormats ()
<< SOElement (sfDeliverMin, SOE_OPTIONAL)
;
add ("SuspendedPaymentCreate", ttSUSPAY_CREATE) <<
SOElement (sfDestination, SOE_REQUIRED) <<
SOElement (sfAmount, SOE_REQUIRED) <<
SOElement (sfDigest, SOE_OPTIONAL) <<
SOElement (sfCancelAfter, SOE_OPTIONAL) <<
SOElement (sfFinishAfter, SOE_OPTIONAL) <<
SOElement (sfDestinationTag, SOE_OPTIONAL);
add ("SuspendedPaymentFinish", ttSUSPAY_FINISH) <<
SOElement (sfOwner, SOE_REQUIRED) <<
SOElement (sfOfferSequence, SOE_REQUIRED) <<
SOElement (sfMethod, SOE_OPTIONAL) <<
SOElement (sfDigest, SOE_OPTIONAL) <<
SOElement (sfProof, SOE_OPTIONAL);
add ("SuspendedPaymentCancel", ttSUSPAY_CANCEL) <<
SOElement (sfOwner, SOE_REQUIRED) <<
SOElement (sfOfferSequence, SOE_REQUIRED);
add ("EnableAmendment", ttAMENDMENT)
<< SOElement (sfLedgerSequence, SOE_OPTIONAL)
<< SOElement (sfAmendment, SOE_REQUIRED)

View File

@@ -22,6 +22,7 @@
// Convenience header that includes everything
#include <ripple/json/to_string.h>
#include <ripple/test/jtx/Account.h>
#include <ripple/test/jtx/amount.h>
#include <ripple/test/jtx/balance.h>
@@ -46,6 +47,7 @@
#include <ripple/test/jtx/sendmax.h>
#include <ripple/test/jtx/seq.h>
#include <ripple/test/jtx/sig.h>
#include <ripple/test/jtx/tag.h>
#include <ripple/test/jtx/tags.h>
#include <ripple/test/jtx/ter.h>
#include <ripple/test/jtx/ticket.h>

View File

@@ -0,0 +1,42 @@
//------------------------------------------------------------------------------
/*
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/test/jtx/tag.h>
#include <ripple/protocol/JsonFields.h>
namespace ripple {
namespace test {
namespace jtx {
void
dtag::operator()(Env const&, JTx& jt) const
{
jt.jv["DestinationTag"] = value_;
}
void
stag::operator()(Env const&, JTx& jt) const
{
jt.jv["SourceTag"] = value_;
}
} // jtx
} // test
} // ripple

View File

@@ -31,8 +31,7 @@ namespace jtx {
/** Create a payment. */
Json::Value
pay (Account const& account,
Account const& to,
AnyAmount amount);
Account const& to, AnyAmount amount);
} // jtx
} // test

69
src/ripple/test/jtx/tag.h Normal file
View File

@@ -0,0 +1,69 @@
//------------------------------------------------------------------------------
/*
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.
*/
//==============================================================================
#ifndef RIPPLE_TEST_JTX_TAG_H_INCLUDED
#define RIPPLE_TEST_JTX_TAG_H_INCLUDED
#include <ripple/test/jtx/Env.h>
namespace ripple {
namespace test {
namespace jtx {
/** Set the destination tag on a JTx*/
struct dtag
{
private:
std::uint32_t value_;
public:
explicit
dtag (std::uint32_t value)
: value_ (value)
{
}
void
operator()(Env const&, JTx& jt) const;
};
/** Set the source tag on a JTx*/
struct stag
{
private:
std::uint32_t value_;
public:
explicit
stag (std::uint32_t value)
: value_ (value)
{
}
void
operator()(Env const&, JTx& jt) const;
};
} // jtx
} // test
} // ripple
#endif

View File

@@ -19,4 +19,5 @@
#include <BeastConfig.h>
#include <ripple/app/tests/SusPay_test.cpp>
#include <ripple/app/tests/Regression_test.cpp>

View File

@@ -35,6 +35,7 @@
#include <ripple/app/tx/impl/SetSignerList.cpp>
#include <ripple/app/tx/impl/SetTrust.cpp>
#include <ripple/app/tx/impl/SignerEntries.cpp>
#include <ripple/app/tx/impl/SusPay.cpp>
#include <ripple/app/tx/impl/Taker.cpp>
#include <ripple/app/tx/impl/TransactionMaster.cpp>
#include <ripple/app/tx/impl/Transaction.cpp>

View File

@@ -46,7 +46,7 @@
#include <ripple/rpc/handlers/CanDelete.cpp>
#include <ripple/rpc/handlers/Connect.cpp>
#include <ripple/rpc/handlers/ConsensusInfo.cpp>
#include <ripple/rpc/handlers/Feature.cpp>
#include <ripple/rpc/handlers/Feature1.cpp>
#include <ripple/rpc/handlers/FetchInfo.cpp>
#include <ripple/rpc/handlers/GatewayBalances.cpp>
#include <ripple/rpc/handlers/GetCounts.cpp>

View File

@@ -38,6 +38,7 @@
#include <ripple/test/jtx/impl/sendmax.cpp>
#include <ripple/test/jtx/impl/seq.cpp>
#include <ripple/test/jtx/impl/sig.cpp>
#include <ripple/test/jtx/impl/tag.cpp>
#include <ripple/test/jtx/impl/ticket.cpp>
#include <ripple/test/jtx/impl/trust.cpp>
#include <ripple/test/jtx/impl/txflags.cpp>