mirror of
https://github.com/Xahau/xahaud.git
synced 2025-11-20 10:35:50 +00:00
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:
committed by
Edward Hennis
parent
d49f9ea109
commit
3f0eacf5e7
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
322
src/ripple/app/tests/SusPay_test.cpp
Normal file
322
src/ripple/app/tests/SusPay_test.cpp
Normal 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
|
||||
410
src/ripple/app/tx/impl/SusPay.cpp
Normal file
410
src/ripple/app/tx/impl/SusPay.cpp
Normal 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
|
||||
|
||||
87
src/ripple/app/tx/impl/SusPay.h
Normal file
87
src/ripple/app/tx/impl/SusPay.h
Normal 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
|
||||
@@ -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(); }
|
||||
|
||||
@@ -34,6 +34,8 @@ uint256
|
||||
feature (const char* name);
|
||||
/** @} */
|
||||
|
||||
extern uint256 const featureSusPay;
|
||||
|
||||
} // ripple
|
||||
|
||||
#endif
|
||||
|
||||
@@ -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
|
||||
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -45,4 +45,6 @@ feature (const char* name)
|
||||
return feature(name, std::strlen(name));
|
||||
}
|
||||
|
||||
uint256 const featureSusPay = feature("SusPay");
|
||||
|
||||
} // ripple
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
42
src/ripple/test/jtx/impl/tag.cpp
Normal file
42
src/ripple/test/jtx/impl/tag.cpp
Normal 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
|
||||
@@ -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
69
src/ripple/test/jtx/tag.h
Normal 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
|
||||
@@ -19,4 +19,5 @@
|
||||
|
||||
#include <BeastConfig.h>
|
||||
|
||||
#include <ripple/app/tests/SusPay_test.cpp>
|
||||
#include <ripple/app/tests/Regression_test.cpp>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user