mirror of
https://github.com/Xahau/xahaud.git
synced 2025-11-20 02:25:53 +00:00
Payment Channels (RIPD-1224):
Payment channels permit off-ledger checkpoints of XRP payments flowing in a single direction. A channel sequesters the owner's XRP in its own ledger entry. The owner can authorize the recipient to claim up to a give balance by giving the receiver a signed message (off-ledger). The recipient can use this signed message to claim any unpaid balance while the channel remains open. The owner can top off the line as needed. If the channel has not paid out all its funds, the owner must wait out a delay to close the channel to give the recipient a chance to supply any claims. The recipient can close the channel at any time. Any transaction that touches the channel after the expiration time will close the channel. The total amount paid increases monotonically as newer claims are issued. When the channel is closed any remaining balance is returned to the owner. Channels are intended to permit intermittent off-ledger settlement of ILP trust lines as balances get substantial. For bidirectional channels, a payment channel can be used in each direction.
This commit is contained in:
@@ -1179,6 +1179,10 @@
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\app\tests\PayChan_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\Regression_test.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
@@ -1273,6 +1277,12 @@
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ripple\app\tx\impl\OfferStream.h">
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ripple\app\tx\impl\PayChan.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\PayChan.h">
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ripple\app\tx\impl\Payment.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
@@ -2854,6 +2864,8 @@
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ripple\protocol\LedgerFormats.h">
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ripple\protocol\PayChan.h">
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ripple\protocol\Protocol.h">
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ripple\protocol\PublicKey.h">
|
||||
@@ -3051,6 +3063,10 @@
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ripple\rpc\Context.h">
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ripple\rpc\handlers\AccountChannels.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\rpc\handlers\AccountCurrenciesHandler.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
@@ -3183,6 +3199,10 @@
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\rpc\handlers\PayChanClaim.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\rpc\handlers\Peers.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
|
||||
@@ -1647,6 +1647,9 @@
|
||||
<ClCompile Include="..\..\src\ripple\app\tests\Path_test.cpp">
|
||||
<Filter>ripple\app\tests</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\app\tests\PayChan_test.cpp">
|
||||
<Filter>ripple\app\tests</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\app\tests\Regression_test.cpp">
|
||||
<Filter>ripple\app\tests</Filter>
|
||||
</ClCompile>
|
||||
@@ -1734,6 +1737,12 @@
|
||||
<ClInclude Include="..\..\src\ripple\app\tx\impl\OfferStream.h">
|
||||
<Filter>ripple\app\tx\impl</Filter>
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ripple\app\tx\impl\PayChan.cpp">
|
||||
<Filter>ripple\app\tx\impl</Filter>
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ripple\app\tx\impl\PayChan.h">
|
||||
<Filter>ripple\app\tx\impl</Filter>
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ripple\app\tx\impl\Payment.cpp">
|
||||
<Filter>ripple\app\tx\impl</Filter>
|
||||
</ClCompile>
|
||||
@@ -3351,6 +3360,9 @@
|
||||
<ClInclude Include="..\..\src\ripple\protocol\LedgerFormats.h">
|
||||
<Filter>ripple\protocol</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ripple\protocol\PayChan.h">
|
||||
<Filter>ripple\protocol</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ripple\protocol\Protocol.h">
|
||||
<Filter>ripple\protocol</Filter>
|
||||
</ClInclude>
|
||||
@@ -3564,6 +3576,9 @@
|
||||
<ClInclude Include="..\..\src\ripple\rpc\Context.h">
|
||||
<Filter>ripple\rpc</Filter>
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ripple\rpc\handlers\AccountChannels.cpp">
|
||||
<Filter>ripple\rpc\handlers</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\rpc\handlers\AccountCurrenciesHandler.cpp">
|
||||
<Filter>ripple\rpc\handlers</Filter>
|
||||
</ClCompile>
|
||||
@@ -3666,6 +3681,9 @@
|
||||
<ClCompile Include="..\..\src\ripple\rpc\handlers\PathFind.cpp">
|
||||
<Filter>ripple\rpc\handlers</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\rpc\handlers\PayChanClaim.cpp">
|
||||
<Filter>ripple\rpc\handlers</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\rpc\handlers\Peers.cpp">
|
||||
<Filter>ripple\rpc\handlers</Filter>
|
||||
</ClCompile>
|
||||
|
||||
@@ -45,7 +45,8 @@ supportedAmendments ()
|
||||
{ "6781F8368C4771B83E8B821D88F580202BCB4228075297B19E4FDC5233F1EFDC TrustSetAuth" },
|
||||
{ "42426C4D4F1009EE67080A9B7965B44656D7714D104A72F9B4369F97ABF044EE FeeEscalation" },
|
||||
{ "5CC22CFF2864B020BD79E0E1F048F63EF3594F95E650E43B3F837EF1DF5F4B26 FlowV2"},
|
||||
{ "9178256A980A86CF3D70D0260A7DA6402AAFE43632FDBCB88037978404188871 OwnerPaysFee"}
|
||||
{ "9178256A980A86CF3D70D0260A7DA6402AAFE43632FDBCB88037978404188871 OwnerPaysFee"},
|
||||
{ "08DE7D96082187F6E6578530258C77FAABABE4C20474BDB82F04B021F1A68647 PayChan"}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -121,11 +121,14 @@ void printHelp (const po::options_description& desc)
|
||||
" account_currencies <account> [<ledger>] [strict]\n"
|
||||
" account_info <account>|<seed>|<pass_phrase>|<key> [<ledger>] [strict]\n"
|
||||
" account_lines <account> <account>|\"\" [<ledger>]\n"
|
||||
" account_channels <account> <account>|\"\" [<ledger>]\n"
|
||||
" account_objects <account> [<ledger>] [strict]\n"
|
||||
" account_offers <account>|<account_public_key> [<ledger>]\n"
|
||||
" account_tx accountID [ledger_min [ledger_max [limit [offset]]]] [binary] [count] [descending]\n"
|
||||
" book_offers <taker_pays> <taker_gets> [<taker [<ledger> [<limit> [<proof> [<marker>]]]]]\n"
|
||||
" can_delete [<ledgerid>|<ledgerhash>|now|always|never]\n"
|
||||
" channel_authorize <private_key> <channel_id> <drops>\n"
|
||||
" channel_verify <public_key> <channel_id> <drops> <signature>\n"
|
||||
" connect <ip> [<port>]\n"
|
||||
" consensus_info\n"
|
||||
" feature [<feature> [accept|reject]]\n"
|
||||
|
||||
840
src/ripple/app/tests/PayChan_test.cpp
Normal file
840
src/ripple/app/tests/PayChan_test.cpp
Normal file
@@ -0,0 +1,840 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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/basics/chrono.h>
|
||||
#include <ripple/protocol/Feature.h>
|
||||
#include <ripple/protocol/Indexes.h>
|
||||
#include <ripple/protocol/JsonFields.h>
|
||||
#include <ripple/protocol/PayChan.h>
|
||||
#include <ripple/protocol/TxFlags.h>
|
||||
#include <ripple/test/jtx.h>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace ripple
|
||||
{
|
||||
namespace test
|
||||
{
|
||||
struct PayChan_test : public beast::unit_test::suite
|
||||
{
|
||||
static
|
||||
uint256
|
||||
channel (ReadView const& view,
|
||||
jtx::Account const& account,
|
||||
jtx::Account const& dst)
|
||||
{
|
||||
auto const sle = view.read (keylet::account (account));
|
||||
if (!sle)
|
||||
return beast::zero;
|
||||
auto const k = keylet::payChan (account, dst, (*sle)[sfSequence] - 1);
|
||||
return k.key;
|
||||
}
|
||||
|
||||
static Buffer
|
||||
signClaimAuth (PublicKey const& pk,
|
||||
SecretKey const& sk,
|
||||
uint256 const& channel,
|
||||
STAmount const& authAmt)
|
||||
{
|
||||
Serializer msg;
|
||||
serializePayChanAuthorization (msg, channel, authAmt.xrp ());
|
||||
return sign (pk, sk, msg.slice ());
|
||||
}
|
||||
|
||||
static
|
||||
STAmount
|
||||
channelBalance (ReadView const& view, uint256 const& chan)
|
||||
{
|
||||
auto const slep = view.read ({ltPAYCHAN, chan});
|
||||
if (!slep)
|
||||
return XRPAmount{-1};
|
||||
return (*slep)[sfBalance];
|
||||
}
|
||||
|
||||
static
|
||||
bool
|
||||
channelExists (ReadView const& view, uint256 const& chan)
|
||||
{
|
||||
auto const slep = view.read ({ltPAYCHAN, chan});
|
||||
return bool(slep);
|
||||
}
|
||||
|
||||
static
|
||||
STAmount
|
||||
channelAmount (ReadView const& view, uint256 const& chan)
|
||||
{
|
||||
auto const slep = view.read ({ltPAYCHAN, chan});
|
||||
if (!slep)
|
||||
return XRPAmount{-1};
|
||||
return (*slep)[sfAmount];
|
||||
}
|
||||
|
||||
static
|
||||
boost::optional<std::int64_t>
|
||||
channelExpiration (ReadView const& view, uint256 const& chan)
|
||||
{
|
||||
auto const slep = view.read ({ltPAYCHAN, chan});
|
||||
if (!slep)
|
||||
return boost::none;
|
||||
if (auto const r = (*slep)[~sfExpiration])
|
||||
return r.value();
|
||||
return boost::none;
|
||||
|
||||
}
|
||||
|
||||
static Json::Value
|
||||
create (jtx::Account const& account,
|
||||
jtx::Account const& to,
|
||||
STAmount const& amount,
|
||||
NetClock::duration const& settleDelay,
|
||||
PublicKey const& pk,
|
||||
boost::optional<NetClock::time_point> const& cancelAfter = boost::none,
|
||||
boost::optional<std::uint32_t> const& dstTag = boost::none)
|
||||
{
|
||||
using namespace jtx;
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = "PaymentChannelCreate";
|
||||
jv[jss::Flags] = tfUniversal;
|
||||
jv[jss::Account] = account.human ();
|
||||
jv[jss::Destination] = to.human ();
|
||||
jv[jss::Amount] = amount.getJson (0);
|
||||
jv["SettleDelay"] = settleDelay.count ();
|
||||
jv["PublicKey"] = strHex (pk.slice ());
|
||||
if (cancelAfter)
|
||||
jv["CancelAfter"] = cancelAfter->time_since_epoch ().count ();
|
||||
if (dstTag)
|
||||
jv["DestinationTag"] = *dstTag;
|
||||
return jv;
|
||||
}
|
||||
|
||||
static
|
||||
Json::Value
|
||||
fund (jtx::Account const& account,
|
||||
uint256 const& channel,
|
||||
STAmount const& amount,
|
||||
boost::optional<NetClock::time_point> const& expiration = boost::none)
|
||||
{
|
||||
using namespace jtx;
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = "PaymentChannelFund";
|
||||
jv[jss::Flags] = tfUniversal;
|
||||
jv[jss::Account] = account.human ();
|
||||
jv["Channel"] = to_string (channel);
|
||||
jv[jss::Amount] = amount.getJson (0);
|
||||
if (expiration)
|
||||
jv["Expiration"] = expiration->time_since_epoch ().count ();
|
||||
return jv;
|
||||
}
|
||||
|
||||
static
|
||||
Json::Value
|
||||
claim (jtx::Account const& account,
|
||||
uint256 const& channel,
|
||||
boost::optional<STAmount> const& balance = boost::none,
|
||||
boost::optional<STAmount> const& amount = boost::none,
|
||||
boost::optional<Slice> const& signature = boost::none,
|
||||
boost::optional<PublicKey> const& pk = boost::none)
|
||||
{
|
||||
using namespace jtx;
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = "PaymentChannelClaim";
|
||||
jv[jss::Flags] = tfUniversal;
|
||||
jv[jss::Account] = account.human ();
|
||||
jv["Channel"] = to_string (channel);
|
||||
if (amount)
|
||||
jv[jss::Amount] = amount->getJson (0);
|
||||
if (balance)
|
||||
jv["Balance"] = balance->getJson (0);
|
||||
if (signature)
|
||||
jv["Signature"] = strHex (*signature);
|
||||
if (pk)
|
||||
jv["PublicKey"] = strHex (pk->slice ());
|
||||
return jv;
|
||||
}
|
||||
|
||||
void
|
||||
testSimple ()
|
||||
{
|
||||
testcase ("simple");
|
||||
using namespace jtx;
|
||||
using namespace std::literals::chrono_literals;
|
||||
Env env (*this, features (featurePayChan));
|
||||
auto const alice = Account ("alice");
|
||||
auto const bob = Account ("bob");
|
||||
auto USDA = alice["USD"];
|
||||
env.fund (XRP (10000), alice, bob);
|
||||
auto const pk = alice.pk ();
|
||||
auto const settleDelay = 100s;
|
||||
env (create (alice, bob, XRP (1000), settleDelay, pk));
|
||||
auto const chan = channel (*env.current (), alice, bob);
|
||||
BEAST_EXPECT (channelBalance (*env.current (), chan) == XRP (0));
|
||||
BEAST_EXPECT (channelAmount (*env.current (), chan) == XRP (1000));
|
||||
|
||||
{
|
||||
auto const preAlice = env.balance (alice);
|
||||
env (fund (alice, chan, XRP (1000)));
|
||||
auto const feeDrops = env.current ()->fees ().base;
|
||||
BEAST_EXPECT (env.balance (alice) == preAlice - XRP (1000) - feeDrops);
|
||||
}
|
||||
|
||||
auto chanBal = channelBalance (*env.current (), chan);
|
||||
auto chanAmt = channelAmount (*env.current (), chan);
|
||||
BEAST_EXPECT (chanBal == XRP (0));
|
||||
BEAST_EXPECT (chanAmt == XRP (2000));
|
||||
|
||||
{
|
||||
// bad amounts (non-xrp, negative amounts)
|
||||
env (create (alice, bob, USDA (1000), settleDelay, pk),
|
||||
ter (temBAD_AMOUNT));
|
||||
env (fund (alice, chan, USDA (1000)),
|
||||
ter (temBAD_AMOUNT));
|
||||
env (create (alice, bob, XRP (-1000), settleDelay, pk),
|
||||
ter (temBAD_AMOUNT));
|
||||
env (fund (alice, chan, XRP (-1000)),
|
||||
ter (temBAD_AMOUNT));
|
||||
}
|
||||
|
||||
// invalid account
|
||||
env (create (alice, "noAccount", XRP (1000), settleDelay, pk),
|
||||
ter (tecNO_DST));
|
||||
// can't create channel to the same account
|
||||
env (create (alice, alice, XRP (1000), settleDelay, pk),
|
||||
ter (temDST_IS_SRC));
|
||||
// invalid channel
|
||||
env (fund (alice, channel (*env.current (), alice, "noAccount"), XRP (1000)),
|
||||
ter (tecNO_ENTRY));
|
||||
// not enough funds
|
||||
env (create (alice, bob, XRP (10000), settleDelay, pk),
|
||||
ter (tecUNFUNDED));
|
||||
|
||||
{
|
||||
// No signature claim with bad amounts (negative and non-xrp)
|
||||
auto const iou = USDA (100).value ();
|
||||
auto const negXRP = XRP (-100).value ();
|
||||
auto const posXRP = XRP (100).value ();
|
||||
env (claim (alice, chan, iou, iou), ter (temBAD_AMOUNT));
|
||||
env (claim (alice, chan, posXRP, iou), ter (temBAD_AMOUNT));
|
||||
env (claim (alice, chan, iou, posXRP), ter (temBAD_AMOUNT));
|
||||
env (claim (alice, chan, negXRP, negXRP), ter (temBAD_AMOUNT));
|
||||
env (claim (alice, chan, posXRP, negXRP), ter (temBAD_AMOUNT));
|
||||
env (claim (alice, chan, negXRP, posXRP), ter (temBAD_AMOUNT));
|
||||
}
|
||||
{
|
||||
// No signature claim more than authorized
|
||||
auto const delta = XRP (500);
|
||||
auto const reqBal = chanBal + delta;
|
||||
auto const authAmt = reqBal + XRP (-100);
|
||||
assert (reqBal <= chanAmt);
|
||||
env (claim (alice, chan, reqBal, authAmt), ter (tecNO_PERMISSION));
|
||||
}
|
||||
{
|
||||
// No signature needed since the owner is claiming
|
||||
auto const preBob = env.balance (bob);
|
||||
auto const delta = XRP (500);
|
||||
auto const reqBal = chanBal + delta;
|
||||
auto const authAmt = reqBal + XRP (100);
|
||||
assert (reqBal <= chanAmt);
|
||||
env (claim (alice, chan, reqBal, authAmt));
|
||||
BEAST_EXPECT (channelBalance (*env.current (), chan) == reqBal);
|
||||
BEAST_EXPECT (channelAmount (*env.current (), chan) == chanAmt);
|
||||
BEAST_EXPECT (env.balance (bob) == preBob + delta);
|
||||
chanBal = reqBal;
|
||||
}
|
||||
{
|
||||
// Claim with signature
|
||||
auto preBob = env.balance (bob);
|
||||
auto const delta = XRP (500);
|
||||
auto const reqBal = chanBal + delta;
|
||||
auto const authAmt = reqBal + XRP (100);
|
||||
assert (reqBal <= chanAmt);
|
||||
auto const sig =
|
||||
signClaimAuth (alice.pk (), alice.sk (), chan, authAmt);
|
||||
env (claim (bob, chan, reqBal, authAmt, Slice (sig), alice.pk ()));
|
||||
BEAST_EXPECT (channelBalance (*env.current (), chan) == reqBal);
|
||||
BEAST_EXPECT (channelAmount (*env.current (), chan) == chanAmt);
|
||||
auto const feeDrops = env.current ()->fees ().base;
|
||||
BEAST_EXPECT (env.balance (bob) == preBob + delta - feeDrops);
|
||||
chanBal = reqBal;
|
||||
|
||||
// claim again
|
||||
preBob = env.balance (bob);
|
||||
env (claim (bob, chan, reqBal, authAmt, Slice (sig), alice.pk ()),
|
||||
ter (tecUNFUNDED_PAYMENT));
|
||||
BEAST_EXPECT (channelBalance (*env.current (), chan) == chanBal);
|
||||
BEAST_EXPECT (channelAmount (*env.current (), chan) == chanAmt);
|
||||
BEAST_EXPECT (env.balance (bob) == preBob - feeDrops);
|
||||
}
|
||||
{
|
||||
// Try to claim more than authorized
|
||||
auto const preBob = env.balance (bob);
|
||||
STAmount const authAmt = chanBal + XRP (500);
|
||||
STAmount const reqAmt = authAmt + 1;
|
||||
assert (reqAmt <= chanAmt);
|
||||
auto const sig =
|
||||
signClaimAuth (alice.pk (), alice.sk (), chan, authAmt);
|
||||
env (claim (bob, chan, reqAmt, authAmt, Slice (sig), alice.pk ()),
|
||||
ter (tecNO_PERMISSION));
|
||||
BEAST_EXPECT (channelBalance (*env.current (), chan) == chanBal);
|
||||
BEAST_EXPECT (channelAmount (*env.current (), chan) == chanAmt);
|
||||
auto const feeDrops = env.current ()->fees ().base;
|
||||
BEAST_EXPECT (env.balance (bob) == preBob - feeDrops);
|
||||
}
|
||||
|
||||
// Dst tries to fund the channel
|
||||
env (fund (bob, chan, XRP (1000)), ter (tecNO_PERMISSION));
|
||||
BEAST_EXPECT (channelBalance (*env.current (), chan) == chanBal);
|
||||
BEAST_EXPECT (channelAmount (*env.current (), chan) == chanAmt);
|
||||
|
||||
{
|
||||
// Wrong signing key
|
||||
auto const sig =
|
||||
signClaimAuth (bob.pk (), bob.sk (), chan, XRP (1500));
|
||||
env (claim (bob, chan, XRP (1500).value (), XRP (1500).value (),
|
||||
Slice (sig), bob.pk ()),
|
||||
ter (temBAD_SIGNER));
|
||||
BEAST_EXPECT (channelBalance (*env.current (), chan) == chanBal);
|
||||
BEAST_EXPECT (channelAmount (*env.current (), chan) == chanAmt);
|
||||
}
|
||||
{
|
||||
// Bad signature
|
||||
auto const sig =
|
||||
signClaimAuth (bob.pk (), bob.sk (), chan, XRP (1500));
|
||||
env (claim (bob, chan, XRP (1500).value (), XRP (1500).value (),
|
||||
Slice (sig), alice.pk ()),
|
||||
ter (temBAD_SIGNATURE));
|
||||
BEAST_EXPECT (channelBalance (*env.current (), chan) == chanBal);
|
||||
BEAST_EXPECT (channelAmount (*env.current (), chan) == chanAmt);
|
||||
}
|
||||
{
|
||||
// Dst closes channel
|
||||
auto const preAlice = env.balance (alice);
|
||||
auto const preBob = env.balance (bob);
|
||||
env (claim (bob, chan), txflags (tfClose));
|
||||
BEAST_EXPECT (!channelExists (*env.current (), chan));
|
||||
auto const feeDrops = env.current ()->fees ().base;
|
||||
auto const delta = chanAmt - chanBal;
|
||||
assert (delta > beast::zero);
|
||||
BEAST_EXPECT (env.balance (alice) == preAlice + delta);
|
||||
BEAST_EXPECT (env.balance (bob) == preBob - feeDrops);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testCancelAfter ()
|
||||
{
|
||||
testcase ("cancel after");
|
||||
using namespace jtx;
|
||||
using namespace std::literals::chrono_literals;
|
||||
auto const alice = Account ("alice");
|
||||
auto const bob = Account ("bob");
|
||||
auto const carol = Account ("carol");
|
||||
{
|
||||
// If dst claims after cancel after, channel closes
|
||||
Env env (*this, features (featurePayChan));
|
||||
env.fund (XRP (10000), alice, bob);
|
||||
auto const pk = alice.pk ();
|
||||
auto const settleDelay = 100s;
|
||||
NetClock::time_point const cancelAfter =
|
||||
env.current ()->info ().parentCloseTime + 3600s;
|
||||
auto const channelFunds = XRP (1000);
|
||||
env (create (
|
||||
alice, bob, channelFunds, settleDelay, pk, cancelAfter));
|
||||
auto const chan = channel (*env.current (), alice, bob);
|
||||
if (!chan)
|
||||
{
|
||||
fail ();
|
||||
return;
|
||||
}
|
||||
BEAST_EXPECT (channelExists (*env.current (), chan));
|
||||
env.close (cancelAfter);
|
||||
{
|
||||
// dst cannot claim after cancelAfter
|
||||
auto const chanBal = channelBalance (*env.current (), chan);
|
||||
auto const chanAmt = channelAmount (*env.current (), chan);
|
||||
auto preAlice = env.balance (alice);
|
||||
auto preBob = env.balance (bob);
|
||||
auto const delta = XRP (500);
|
||||
auto const reqBal = chanBal + delta;
|
||||
auto const authAmt = reqBal + XRP (100);
|
||||
assert (reqBal <= chanAmt);
|
||||
auto const sig =
|
||||
signClaimAuth (alice.pk (), alice.sk (), chan, authAmt);
|
||||
env (claim (
|
||||
bob, chan, reqBal, authAmt, Slice (sig), alice.pk ()));
|
||||
auto const feeDrops = env.current ()->fees ().base;
|
||||
BEAST_EXPECT (!channelExists (*env.current (), chan));
|
||||
BEAST_EXPECT (env.balance (bob) == preBob - feeDrops);
|
||||
BEAST_EXPECT (env.balance (alice) == preAlice + channelFunds);
|
||||
}
|
||||
}
|
||||
{
|
||||
// Third party can close after cancel after
|
||||
Env env (*this, features (featurePayChan));
|
||||
env.fund (XRP (10000), alice, bob, carol);
|
||||
auto const pk = alice.pk ();
|
||||
auto const settleDelay = 100s;
|
||||
NetClock::time_point const cancelAfter =
|
||||
env.current ()->info ().parentCloseTime + 3600s;
|
||||
auto const channelFunds = XRP (1000);
|
||||
env (create (
|
||||
alice, bob, channelFunds, settleDelay, pk, cancelAfter));
|
||||
auto const chan = channel (*env.current (), alice, bob);
|
||||
BEAST_EXPECT (channelExists (*env.current (), chan));
|
||||
// third party close before cancelAfter
|
||||
env (claim (carol, chan), txflags (tfClose),
|
||||
ter (tecNO_PERMISSION));
|
||||
BEAST_EXPECT (channelExists (*env.current (), chan));
|
||||
env.close (cancelAfter);
|
||||
// third party close after cancelAfter
|
||||
auto const preAlice = env.balance (alice);
|
||||
env (claim (carol, chan), txflags (tfClose));
|
||||
BEAST_EXPECT (!channelExists (*env.current (), chan));
|
||||
BEAST_EXPECT (env.balance (alice) == preAlice + channelFunds);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testExpiration ()
|
||||
{
|
||||
testcase ("expiration");
|
||||
using namespace jtx;
|
||||
using namespace std::literals::chrono_literals;
|
||||
Env env (*this, features (featurePayChan));
|
||||
auto const alice = Account ("alice");
|
||||
auto const bob = Account ("bob");
|
||||
auto const carol = Account ("carol");
|
||||
env.fund (XRP (10000), alice, bob, carol);
|
||||
auto const pk = alice.pk ();
|
||||
auto const settleDelay = 3600s;
|
||||
auto const closeTime = env.current ()->info ().parentCloseTime;
|
||||
auto const minExpiration = closeTime + settleDelay;
|
||||
NetClock::time_point const cancelAfter = closeTime + 7200s;
|
||||
auto const channelFunds = XRP (1000);
|
||||
env (create (alice, bob, channelFunds, settleDelay, pk, cancelAfter));
|
||||
auto const chan = channel (*env.current (), alice, bob);
|
||||
BEAST_EXPECT (channelExists (*env.current (), chan));
|
||||
BEAST_EXPECT (!channelExpiration (*env.current (), chan));
|
||||
// Owner closes, will close after settleDelay
|
||||
env (claim (alice, chan), txflags (tfClose));
|
||||
auto counts = [](
|
||||
auto const& t) { return t.time_since_epoch ().count (); };
|
||||
BEAST_EXPECT (*channelExpiration (*env.current (), chan) ==
|
||||
counts (minExpiration));
|
||||
// increase the expiration time
|
||||
env (fund (
|
||||
alice, chan, XRP (1), NetClock::time_point{minExpiration + 100s}));
|
||||
BEAST_EXPECT (*channelExpiration (*env.current (), chan) ==
|
||||
counts (minExpiration) + 100);
|
||||
// decrease the expiration, but still above minExpiration
|
||||
env (fund (
|
||||
alice, chan, XRP (1), NetClock::time_point{minExpiration + 50s}));
|
||||
BEAST_EXPECT (*channelExpiration (*env.current (), chan) ==
|
||||
counts (minExpiration) + 50);
|
||||
// decrease the expiration below minExpiration
|
||||
env (fund (alice, chan, XRP (1),
|
||||
NetClock::time_point{minExpiration - 50s}),
|
||||
ter (temBAD_EXPIRATION));
|
||||
BEAST_EXPECT (*channelExpiration (*env.current (), chan) ==
|
||||
counts (minExpiration) + 50);
|
||||
env (claim (bob, chan), txflags (tfRenew), ter (tecNO_PERMISSION));
|
||||
BEAST_EXPECT (*channelExpiration (*env.current (), chan) ==
|
||||
counts (minExpiration) + 50);
|
||||
env (claim (alice, chan), txflags (tfRenew));
|
||||
BEAST_EXPECT (!channelExpiration (*env.current (), chan));
|
||||
// decrease the expiration below minExpiration
|
||||
env (fund (alice, chan, XRP (1),
|
||||
NetClock::time_point{minExpiration - 50s}),
|
||||
ter (temBAD_EXPIRATION));
|
||||
BEAST_EXPECT (!channelExpiration (*env.current (), chan));
|
||||
env (fund (alice, chan, XRP (1), NetClock::time_point{minExpiration}));
|
||||
env.close (minExpiration);
|
||||
// Try to extend the expiration after the expiration has already passed
|
||||
env (fund (
|
||||
alice, chan, XRP (1), NetClock::time_point{minExpiration + 1000s}));
|
||||
BEAST_EXPECT (!channelExists (*env.current (), chan));
|
||||
}
|
||||
|
||||
void
|
||||
testSettleDelay ()
|
||||
{
|
||||
testcase ("settle delay");
|
||||
using namespace jtx;
|
||||
using namespace std::literals::chrono_literals;
|
||||
Env env (*this, features (featurePayChan));
|
||||
auto const alice = Account ("alice");
|
||||
auto const bob = Account ("bob");
|
||||
env.fund (XRP (10000), alice, bob);
|
||||
auto const pk = alice.pk ();
|
||||
auto const settleDelay = 3600s;
|
||||
NetClock::time_point const settleTimepoint =
|
||||
env.current ()->info ().parentCloseTime + settleDelay;
|
||||
auto const channelFunds = XRP (1000);
|
||||
env (create (alice, bob, channelFunds, settleDelay, pk));
|
||||
auto const chan = channel (*env.current (), alice, bob);
|
||||
BEAST_EXPECT (channelExists (*env.current (), chan));
|
||||
// Owner closes, will close after settleDelay
|
||||
env (claim (alice, chan), txflags (tfClose));
|
||||
BEAST_EXPECT (channelExists (*env.current (), chan));
|
||||
env.close (settleTimepoint-settleDelay/2);
|
||||
{
|
||||
// receiver can still claim
|
||||
auto const chanBal = channelBalance (*env.current (), chan);
|
||||
auto const chanAmt = channelAmount (*env.current (), chan);
|
||||
auto preBob = env.balance (bob);
|
||||
auto const delta = XRP (500);
|
||||
auto const reqBal = chanBal + delta;
|
||||
auto const authAmt = reqBal + XRP (100);
|
||||
assert (reqBal <= chanAmt);
|
||||
auto const sig =
|
||||
signClaimAuth (alice.pk (), alice.sk (), chan, authAmt);
|
||||
env (claim (bob, chan, reqBal, authAmt, Slice (sig), alice.pk ()));
|
||||
BEAST_EXPECT (channelBalance (*env.current (), chan) == reqBal);
|
||||
BEAST_EXPECT (channelAmount (*env.current (), chan) == chanAmt);
|
||||
auto const feeDrops = env.current ()->fees ().base;
|
||||
BEAST_EXPECT (env.balance (bob) == preBob + delta - feeDrops);
|
||||
}
|
||||
env.close (settleTimepoint);
|
||||
{
|
||||
// past settleTime, channel will close
|
||||
auto const chanBal = channelBalance (*env.current (), chan);
|
||||
auto const chanAmt = channelAmount (*env.current (), chan);
|
||||
auto const preAlice = env.balance (alice);
|
||||
auto preBob = env.balance (bob);
|
||||
auto const delta = XRP (500);
|
||||
auto const reqBal = chanBal + delta;
|
||||
auto const authAmt = reqBal + XRP (100);
|
||||
assert (reqBal <= chanAmt);
|
||||
auto const sig =
|
||||
signClaimAuth (alice.pk (), alice.sk (), chan, authAmt);
|
||||
env (claim (bob, chan, reqBal, authAmt, Slice (sig), alice.pk ()));
|
||||
BEAST_EXPECT (!channelExists (*env.current (), chan));
|
||||
auto const feeDrops = env.current ()->fees ().base;
|
||||
BEAST_EXPECT (env.balance (alice) == preAlice + chanAmt - chanBal);
|
||||
BEAST_EXPECT (env.balance (bob) == preBob - feeDrops);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testCloseDry ()
|
||||
{
|
||||
testcase ("close dry");
|
||||
using namespace jtx;
|
||||
using namespace std::literals::chrono_literals;
|
||||
Env env (*this, features (featurePayChan));
|
||||
auto const alice = Account ("alice");
|
||||
auto const bob = Account ("bob");
|
||||
env.fund (XRP (10000), alice, bob);
|
||||
auto const pk = alice.pk ();
|
||||
auto const settleDelay = 3600s;
|
||||
auto const channelFunds = XRP (1000);
|
||||
env (create (alice, bob, channelFunds, settleDelay, pk));
|
||||
auto const chan = channel (*env.current (), alice, bob);
|
||||
BEAST_EXPECT (channelExists (*env.current (), chan));
|
||||
// Owner tries to close channel, but it will remain open (settle delay)
|
||||
env (claim (alice, chan), txflags (tfClose));
|
||||
BEAST_EXPECT (channelExists (*env.current (), chan));
|
||||
{
|
||||
// claim the entire amount
|
||||
auto const preBob = env.balance (bob);
|
||||
env (claim (
|
||||
alice, chan, channelFunds.value (), channelFunds.value ()));
|
||||
BEAST_EXPECT (channelBalance (*env.current (), chan) == channelFunds);
|
||||
BEAST_EXPECT (env.balance (bob) == preBob + channelFunds);
|
||||
}
|
||||
auto const preAlice = env.balance (alice);
|
||||
// Channel is now dry, can close before expiration date
|
||||
env (claim (alice, chan), txflags (tfClose));
|
||||
BEAST_EXPECT (!channelExists (*env.current (), chan));
|
||||
auto const feeDrops = env.current ()->fees ().base;
|
||||
BEAST_EXPECT (env.balance (alice) == preAlice - feeDrops);
|
||||
}
|
||||
|
||||
void
|
||||
testDefaultAmount ()
|
||||
{
|
||||
// auth amount defaults to balance if not present
|
||||
testcase ("default amount");
|
||||
using namespace jtx;
|
||||
using namespace std::literals::chrono_literals;
|
||||
Env env (*this, features (featurePayChan));
|
||||
auto const alice = Account ("alice");
|
||||
auto const bob = Account ("bob");
|
||||
env.fund (XRP (10000), alice, bob);
|
||||
auto const pk = alice.pk ();
|
||||
auto const settleDelay = 3600s;
|
||||
auto const channelFunds = XRP (1000);
|
||||
env (create (alice, bob, channelFunds, settleDelay, pk));
|
||||
auto const chan = channel (*env.current (), alice, bob);
|
||||
BEAST_EXPECT (channelExists (*env.current (), chan));
|
||||
// Owner tries to close channel, but it will remain open (settle delay)
|
||||
env (claim (alice, chan), txflags (tfClose));
|
||||
BEAST_EXPECT (channelExists (*env.current (), chan));
|
||||
{
|
||||
auto chanBal = channelBalance (*env.current (), chan);
|
||||
auto chanAmt = channelAmount (*env.current (), chan);
|
||||
auto const preBob = env.balance (bob);
|
||||
|
||||
auto const delta = XRP (500);
|
||||
auto const reqBal = chanBal + delta;
|
||||
assert (reqBal <= chanAmt);
|
||||
auto const sig =
|
||||
signClaimAuth (alice.pk (), alice.sk (), chan, reqBal);
|
||||
env (claim (
|
||||
bob, chan, reqBal, boost::none, Slice (sig), alice.pk ()));
|
||||
BEAST_EXPECT (channelBalance (*env.current (), chan) == reqBal);
|
||||
auto const feeDrops = env.current ()->fees ().base;
|
||||
BEAST_EXPECT (env.balance (bob) == preBob + delta - feeDrops);
|
||||
chanBal = reqBal;
|
||||
}
|
||||
{
|
||||
// Claim again
|
||||
auto chanBal = channelBalance (*env.current (), chan);
|
||||
auto chanAmt = channelAmount (*env.current (), chan);
|
||||
auto const preBob = env.balance (bob);
|
||||
|
||||
auto const delta = XRP (500);
|
||||
auto const reqBal = chanBal + delta;
|
||||
assert (reqBal <= chanAmt);
|
||||
auto const sig =
|
||||
signClaimAuth (alice.pk (), alice.sk (), chan, reqBal);
|
||||
env (claim (
|
||||
bob, chan, reqBal, boost::none, Slice (sig), alice.pk ()));
|
||||
BEAST_EXPECT (channelBalance (*env.current (), chan) == reqBal);
|
||||
auto const feeDrops = env.current ()->fees ().base;
|
||||
BEAST_EXPECT (env.balance (bob) == preBob + delta - feeDrops);
|
||||
chanBal = reqBal;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testDisallowXRP ()
|
||||
{
|
||||
// auth amount defaults to balance if not present
|
||||
testcase ("Disallow XRP");
|
||||
using namespace jtx;
|
||||
using namespace std::literals::chrono_literals;
|
||||
{
|
||||
// Create a channel where dst disallows XRP
|
||||
Env env (*this, features (featurePayChan));
|
||||
auto const alice = Account ("alice");
|
||||
auto const bob = Account ("bob");
|
||||
env.fund (XRP (10000), alice, bob);
|
||||
env (fset (bob, asfDisallowXRP));
|
||||
auto const pk = alice.pk ();
|
||||
auto const settleDelay = 3600s;
|
||||
auto const channelFunds = XRP (1000);
|
||||
env (create (alice, bob, channelFunds, settleDelay, pk),
|
||||
ter (tecNO_TARGET));
|
||||
auto const chan = channel (*env.current (), alice, bob);
|
||||
BEAST_EXPECT (!channelExists (*env.current (), chan));
|
||||
}
|
||||
{
|
||||
// Claim to a channel where dst disallows XRP
|
||||
// (channel is created before disallow xrp is set)
|
||||
Env env (*this, features (featurePayChan));
|
||||
auto const alice = Account ("alice");
|
||||
auto const bob = Account ("bob");
|
||||
env.fund (XRP (10000), alice, bob);
|
||||
auto const pk = alice.pk ();
|
||||
auto const settleDelay = 3600s;
|
||||
auto const channelFunds = XRP (1000);
|
||||
env (create (alice, bob, channelFunds, settleDelay, pk));
|
||||
auto const chan = channel (*env.current (), alice, bob);
|
||||
BEAST_EXPECT (channelExists (*env.current (), chan));
|
||||
|
||||
env (fset (bob, asfDisallowXRP));
|
||||
auto const preBob = env.balance (bob);
|
||||
auto const reqBal = XRP (500).value();
|
||||
env (claim (alice, chan, reqBal, reqBal), ter(tecNO_TARGET));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testDstTag ()
|
||||
{
|
||||
// auth amount defaults to balance if not present
|
||||
testcase ("Dst Tag");
|
||||
using namespace jtx;
|
||||
using namespace std::literals::chrono_literals;
|
||||
// Create a channel where dst disallows XRP
|
||||
Env env (*this, features (featurePayChan));
|
||||
auto const alice = Account ("alice");
|
||||
auto const bob = Account ("bob");
|
||||
env.fund (XRP (10000), alice, bob);
|
||||
env (fset (bob, asfRequireDest));
|
||||
auto const pk = alice.pk ();
|
||||
auto const settleDelay = 3600s;
|
||||
auto const channelFunds = XRP (1000);
|
||||
env (create (alice, bob, channelFunds, settleDelay, pk),
|
||||
ter (tecDST_TAG_NEEDED));
|
||||
BEAST_EXPECT (!channelExists (
|
||||
*env.current (), channel (*env.current (), alice, bob)));
|
||||
env (
|
||||
create (alice, bob, channelFunds, settleDelay, pk, boost::none, 1));
|
||||
BEAST_EXPECT (channelExists (
|
||||
*env.current (), channel (*env.current (), alice, bob)));
|
||||
}
|
||||
|
||||
void
|
||||
testMultiple ()
|
||||
{
|
||||
// auth amount defaults to balance if not present
|
||||
testcase ("Multiple channels to the same account");
|
||||
using namespace jtx;
|
||||
using namespace std::literals::chrono_literals;
|
||||
Env env (*this, features (featurePayChan));
|
||||
auto const alice = Account ("alice");
|
||||
auto const bob = Account ("bob");
|
||||
env.fund (XRP (10000), alice, bob);
|
||||
auto const pk = alice.pk ();
|
||||
auto const settleDelay = 3600s;
|
||||
auto const channelFunds = XRP (1000);
|
||||
env (create (alice, bob, channelFunds, settleDelay, pk));
|
||||
auto const chan1 = channel (*env.current (), alice, bob);
|
||||
BEAST_EXPECT (channelExists (*env.current (), chan1));
|
||||
env (create (alice, bob, channelFunds, settleDelay, pk));
|
||||
auto const chan2 = channel (*env.current (), alice, bob);
|
||||
BEAST_EXPECT (channelExists (*env.current (), chan2));
|
||||
BEAST_EXPECT (chan1 != chan2);
|
||||
}
|
||||
|
||||
void
|
||||
testRPC ()
|
||||
{
|
||||
testcase ("RPC");
|
||||
using namespace jtx;
|
||||
using namespace std::literals::chrono_literals;
|
||||
Env env (*this, features (featurePayChan));
|
||||
auto const alice = Account ("alice");
|
||||
auto const bob = Account ("bob");
|
||||
env.fund (XRP (10000), alice, bob);
|
||||
auto const pk = alice.pk ();
|
||||
auto const settleDelay = 3600s;
|
||||
auto const channelFunds = XRP (1000);
|
||||
env (create (alice, bob, channelFunds, settleDelay, pk));
|
||||
env.close();
|
||||
auto const chan1Str = to_string (channel (*env.current (), alice, bob));
|
||||
std::string chan1PkStr;
|
||||
{
|
||||
auto const r =
|
||||
env.rpc ("account_channels", alice.human (), bob.human ());
|
||||
BEAST_EXPECT (r[jss::result][jss::channels].size () == 1);
|
||||
BEAST_EXPECT (r[jss::result][jss::channels][0u][jss::channel_id] == chan1Str);
|
||||
chan1PkStr = r[jss::result][jss::channels][0u][jss::public_key].asString();
|
||||
}
|
||||
{
|
||||
auto const r =
|
||||
env.rpc ("account_channels", bob.human (), alice.human ());
|
||||
BEAST_EXPECT (r[jss::result][jss::channels].size () == 0);
|
||||
}
|
||||
env (create (alice, bob, channelFunds, settleDelay, pk));
|
||||
env.close();
|
||||
auto const chan2Str = to_string (channel (*env.current (), alice, bob));
|
||||
{
|
||||
auto const r =
|
||||
env.rpc ("account_channels", alice.human (), bob.human ());
|
||||
BEAST_EXPECT (r[jss::result][jss::channels].size () == 2);
|
||||
BEAST_EXPECT (chan1Str != chan2Str);
|
||||
for (auto const& c : {chan1Str, chan2Str})
|
||||
BEAST_EXPECT (r[jss::result][jss::channels][0u][jss::channel_id] == c ||
|
||||
r[jss::result][jss::channels][1u][jss::channel_id] == c);
|
||||
}
|
||||
{
|
||||
// Verify chan1 auth
|
||||
auto const rs =
|
||||
env.rpc ("channel_authorize", "alice", chan1Str, "1000");
|
||||
auto const sig = rs[jss::result][jss::signature].asString ();
|
||||
BEAST_EXPECT (!sig.empty ());
|
||||
auto const rv = env.rpc (
|
||||
"channel_verify", chan1PkStr, chan1Str, "1000", sig);
|
||||
BEAST_EXPECT (rv[jss::result][jss::signature_verified].asBool ());
|
||||
}
|
||||
{
|
||||
// Try to verify chan2 auth with chan1 key
|
||||
auto const rs =
|
||||
env.rpc ("channel_authorize", "alice", chan2Str, "1000");
|
||||
auto const sig = rs[jss::result][jss::signature].asString ();
|
||||
BEAST_EXPECT (!sig.empty ());
|
||||
auto const rv =
|
||||
env.rpc ("channel_verify", chan1PkStr, chan1Str, "1000", sig);
|
||||
BEAST_EXPECT (!rv[jss::result][jss::signature_verified].asBool ());
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testOptionalFields ()
|
||||
{
|
||||
testcase ("Optional Fields");
|
||||
using namespace jtx;
|
||||
using namespace std::literals::chrono_literals;
|
||||
Env env (*this, features (featurePayChan));
|
||||
auto const alice = Account ("alice");
|
||||
auto const bob = Account ("bob");
|
||||
auto const carol = Account ("carol");
|
||||
auto const dan = Account ("dan");
|
||||
env.fund (XRP (10000), alice, bob, carol, dan);
|
||||
auto const pk = alice.pk ();
|
||||
auto const settleDelay = 3600s;
|
||||
auto const channelFunds = XRP (1000);
|
||||
|
||||
boost::optional<NetClock::time_point> cancelAfter;
|
||||
|
||||
{
|
||||
env (create (alice, bob, channelFunds, settleDelay, pk));
|
||||
auto const chan = to_string (channel (*env.current (), alice, bob));
|
||||
auto const r =
|
||||
env.rpc ("account_channels", alice.human (), bob.human ());
|
||||
BEAST_EXPECT (r[jss::result][jss::channels].size () == 1);
|
||||
BEAST_EXPECT (r[jss::result][jss::channels][0u][jss::channel_id] == chan);
|
||||
BEAST_EXPECT (!r[jss::result][jss::channels][0u].isMember(jss::destination_tag));
|
||||
}
|
||||
{
|
||||
std::uint32_t dstTag=42;
|
||||
env (create (
|
||||
alice, carol, channelFunds, settleDelay, pk, cancelAfter, dstTag));
|
||||
auto const chan = to_string (channel (*env.current (), alice, carol));
|
||||
auto const r =
|
||||
env.rpc ("account_channels", alice.human (), carol.human ());
|
||||
BEAST_EXPECT (r[jss::result][jss::channels].size () == 1);
|
||||
BEAST_EXPECT (r[jss::result][jss::channels][0u][jss::channel_id] == chan);
|
||||
BEAST_EXPECT (r[jss::result][jss::channels][0u][jss::destination_tag] == dstTag);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
run () override
|
||||
{
|
||||
testSimple ();
|
||||
testCancelAfter ();
|
||||
testSettleDelay ();
|
||||
testExpiration ();
|
||||
testCloseDry ();
|
||||
testDefaultAmount ();
|
||||
testDisallowXRP ();
|
||||
testDstTag ();
|
||||
testMultiple ();
|
||||
testRPC ();
|
||||
testOptionalFields ();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE (PayChan, app, ripple);
|
||||
} // test
|
||||
} // ripple
|
||||
486
src/ripple/app/tx/impl/PayChan.cpp
Normal file
486
src/ripple/app/tx/impl/PayChan.cpp
Normal file
@@ -0,0 +1,486 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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/PayChan.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/PayChan.h>
|
||||
#include <ripple/protocol/PublicKey.h>
|
||||
#include <ripple/protocol/TxFlags.h>
|
||||
#include <ripple/protocol/XRPAmount.h>
|
||||
#include <ripple/ledger/View.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
/*
|
||||
PaymentChannel
|
||||
|
||||
Payment channels permit off-ledger checkpoints of XRP payments flowing
|
||||
in a single direction. A channel sequesters the owner's XRP in its own
|
||||
ledger entry. The owner can authorize the recipient to claim up to a
|
||||
given balance by giving the receiver a signed message (off-ledger). The
|
||||
recipient can use this signed message to claim any unpaid balance while
|
||||
the channel remains open. The owner can top off the line as needed. If
|
||||
the channel has not paid out all its funds, the owner must wait out a
|
||||
delay to close the channel to give the recipient a chance to supply any
|
||||
claims. The recipient can close the channel at any time. Any transaction
|
||||
that touches the channel after the expiration time will close the
|
||||
channel. The total amount paid increases monotonically as newer claims
|
||||
are issued. When the channel is closed any remaining balance is returned
|
||||
to the owner. Channels are intended to permit intermittent off-ledger
|
||||
settlement of ILP trust lines as balances get substantial. For
|
||||
bidirectional channels, a payment channel can be used in each direction.
|
||||
|
||||
PaymentChannelCreate
|
||||
|
||||
Create a unidirectional channel. The parameters are:
|
||||
Destination
|
||||
The recipient at the end of the channel.
|
||||
Amount
|
||||
The amount of XRP to deposit in the channel immediately.
|
||||
SettleDelay
|
||||
The amount of time everyone but the recipient must wait for a
|
||||
superior claim.
|
||||
PublicKey
|
||||
The key that will sign claims against the channel.
|
||||
CancelAfter (optional)
|
||||
Any channel transaction that touches this channel after the
|
||||
`CancelAfter` time will close it.
|
||||
DestinationTag (optional)
|
||||
Destination tags allow the different accounts inside of a Hosted
|
||||
Wallet to be mapped back onto the Ripple ledger. The destination tag
|
||||
tells the server to which account in the Hosted Wallet the funds are
|
||||
intended to go to. Required if the destination has lsfRequireDestTag
|
||||
set.
|
||||
SourceTag (optional)
|
||||
Source tags allow the different accounts inside of a Hosted Wallet
|
||||
to be mapped back onto the Ripple ledger. Source tags are similar to
|
||||
destination tags but are for the channel owner to identify their own
|
||||
transactions.
|
||||
|
||||
PaymentChannelFund
|
||||
|
||||
Add additional funds to the payment channel. Only the channel owner may
|
||||
use this transaction. The parameters are:
|
||||
Channel
|
||||
The 256-bit ID of the channel.
|
||||
Amount
|
||||
The amount of XRP to add.
|
||||
Expiration (optional)
|
||||
Time the channel closes. The transaction will fail if the expiration
|
||||
times does not satisfy the SettleDelay constraints.
|
||||
|
||||
PaymentChannelClaim
|
||||
|
||||
Place a claim against an existing channel. The parameters are:
|
||||
Channel
|
||||
The 256-bit ID of the channel.
|
||||
Balance (optional)
|
||||
The total amount of XRP delivered after this claim is processed (optional, not
|
||||
needed if just closing).
|
||||
Amount (optional)
|
||||
The amount of XRP the signature is for (not needed if equal to Balance or just
|
||||
closing the line).
|
||||
Signature (optional)
|
||||
Authorization for the balance above, signed by the owner (optional,
|
||||
not needed if closing or owner is performing the transaction). The
|
||||
signature if for the following message: CLM\0 followed by the
|
||||
256-bit channel ID, and a 64-bit integer drops.
|
||||
PublicKey (optional)
|
||||
The public key that made the signature (optional, required if a signature
|
||||
is present)
|
||||
Flags
|
||||
tfCloseChannel
|
||||
Request that the channel be closed
|
||||
tfRenewChannel
|
||||
Request that the channel's expiration be reset. Only the owner
|
||||
may renew a channel.
|
||||
|
||||
*/
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
static
|
||||
TER
|
||||
closeChannel (
|
||||
std::shared_ptr<SLE> const& slep,
|
||||
ApplyView& view,
|
||||
uint256 const& key,
|
||||
beast::Journal j)
|
||||
{
|
||||
AccountID const src = (*slep)[sfAccount];
|
||||
// Remove PayChan from owner directory
|
||||
{
|
||||
auto const page = (*slep)[sfOwnerNode];
|
||||
TER const ter = dirDelete (view, true, page, keylet::ownerDir (src).key,
|
||||
key, false, page == 0, j);
|
||||
if (!isTesSuccess (ter))
|
||||
return ter;
|
||||
}
|
||||
|
||||
// Transfer amount back to owner, decrement owner count
|
||||
auto const sle = view.peek (keylet::account (src));
|
||||
assert ((*slep)[sfAmount] >= (*slep)[sfBalance]);
|
||||
(*sle)[sfBalance] =
|
||||
(*sle)[sfBalance] + (*slep)[sfAmount] - (*slep)[sfBalance];
|
||||
(*sle)[sfOwnerCount] = (*sle)[sfOwnerCount] - 1;
|
||||
view.update (sle);
|
||||
|
||||
// Remove PayChan from ledger
|
||||
view.erase (slep);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
TER
|
||||
PayChanCreate::preflight (PreflightContext const& ctx)
|
||||
{
|
||||
if (!ctx.rules.enabled (featurePayChan, ctx.app.config ().features))
|
||||
return temDISABLED;
|
||||
|
||||
auto const ret = preflight1 (ctx);
|
||||
if (!isTesSuccess (ret))
|
||||
return ret;
|
||||
|
||||
if (!isXRP (ctx.tx[sfAmount]) || (ctx.tx[sfAmount] <= beast::zero))
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
if (ctx.tx[sfAccount] == ctx.tx[sfDestination])
|
||||
return temDST_IS_SRC;
|
||||
|
||||
return preflight2 (ctx);
|
||||
}
|
||||
|
||||
TER
|
||||
PayChanCreate::preclaim(PreclaimContext const &ctx)
|
||||
{
|
||||
auto const account = ctx.tx[sfAccount];
|
||||
auto const sle = ctx.view.read (keylet::account (account));
|
||||
|
||||
// Check reserve and funds availability
|
||||
{
|
||||
auto const balance = (*sle)[sfBalance];
|
||||
auto const reserve =
|
||||
ctx.view.fees ().accountReserve ((*sle)[sfOwnerCount] + 1);
|
||||
|
||||
if (balance < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
|
||||
if (balance < reserve + ctx.tx[sfAmount])
|
||||
return tecUNFUNDED;
|
||||
}
|
||||
|
||||
auto const dst = ctx.tx[sfDestination];
|
||||
|
||||
{
|
||||
// Check destination account
|
||||
auto const sled = ctx.view.read (keylet::account (dst));
|
||||
if (!sled)
|
||||
return tecNO_DST;
|
||||
if (((*sled)[sfFlags] & lsfRequireDestTag) &&
|
||||
!ctx.tx[~sfDestinationTag])
|
||||
return tecDST_TAG_NEEDED;
|
||||
if ((*sled)[sfFlags] & lsfDisallowXRP)
|
||||
return tecNO_TARGET;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
PayChanCreate::doApply()
|
||||
{
|
||||
auto const account = ctx_.tx[sfAccount];
|
||||
auto const sle = ctx_.view ().peek (keylet::account (account));
|
||||
auto const dst = ctx_.tx[sfDestination];
|
||||
|
||||
// Create PayChan in ledger
|
||||
auto const slep = std::make_shared<SLE> (
|
||||
keylet::payChan (account, dst, (*sle)[sfSequence] - 1));
|
||||
// Funds held in this channel
|
||||
(*slep)[sfAmount] = ctx_.tx[sfAmount];
|
||||
// Amount channel has already paid
|
||||
(*slep)[sfBalance] = ctx_.tx[sfAmount].zeroed ();
|
||||
(*slep)[sfAccount] = account;
|
||||
(*slep)[sfDestination] = dst;
|
||||
(*slep)[sfSettleDelay] = ctx_.tx[sfSettleDelay];
|
||||
(*slep)[sfPublicKey] = ctx_.tx[sfPublicKey];
|
||||
(*slep)[~sfCancelAfter] = ctx_.tx[~sfCancelAfter];
|
||||
(*slep)[~sfSourceTag] = ctx_.tx[~sfSourceTag];
|
||||
(*slep)[~sfDestinationTag] = ctx_.tx[~sfDestinationTag];
|
||||
|
||||
ctx_.view ().insert (slep);
|
||||
|
||||
// Add PayChan to owner directory
|
||||
{
|
||||
uint64_t page;
|
||||
auto result = dirAdd (ctx_.view (), page, keylet::ownerDir (account),
|
||||
slep->key (), describeOwnerDir (account),
|
||||
ctx_.app.journal ("View"));
|
||||
if (!isTesSuccess (result.first))
|
||||
return result.first;
|
||||
(*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
|
||||
PayChanFund::preflight (PreflightContext const& ctx)
|
||||
{
|
||||
if (!ctx.rules.enabled (featurePayChan, ctx.app.config ().features))
|
||||
return temDISABLED;
|
||||
|
||||
auto const ret = preflight1 (ctx);
|
||||
if (!isTesSuccess (ret))
|
||||
return ret;
|
||||
|
||||
if (!isXRP (ctx.tx[sfAmount]) || (ctx.tx[sfAmount] <= beast::zero))
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
return preflight2 (ctx);
|
||||
}
|
||||
|
||||
TER
|
||||
PayChanFund::doApply()
|
||||
{
|
||||
Keylet const k (ltPAYCHAN, ctx_.tx[sfPayChannel]);
|
||||
auto const slep = ctx_.view ().peek (k);
|
||||
if (!slep)
|
||||
return tecNO_ENTRY;
|
||||
|
||||
AccountID const src = (*slep)[sfAccount];
|
||||
auto const txAccount = ctx_.tx[sfAccount];
|
||||
auto const expiration = (*slep)[~sfExpiration];
|
||||
|
||||
{
|
||||
auto const cancelAfter = (*slep)[~sfCancelAfter];
|
||||
auto const closeTime =
|
||||
ctx_.view ().info ().parentCloseTime.time_since_epoch ().count ();
|
||||
if ((cancelAfter && closeTime >= *cancelAfter) ||
|
||||
(expiration && closeTime >= *expiration))
|
||||
return closeChannel (
|
||||
slep, ctx_.view (), k.key, ctx_.app.journal ("View"));
|
||||
}
|
||||
|
||||
if (src != txAccount)
|
||||
// only the owner can add funds or extend
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
if (auto extend = ctx_.tx[~sfExpiration])
|
||||
{
|
||||
auto minExpiration =
|
||||
ctx_.view ().info ().parentCloseTime.time_since_epoch ().count () +
|
||||
(*slep)[sfSettleDelay];
|
||||
if (expiration && *expiration < minExpiration)
|
||||
minExpiration = *expiration;
|
||||
|
||||
if (*extend < minExpiration)
|
||||
return temBAD_EXPIRATION;
|
||||
(*slep)[~sfExpiration] = *extend;
|
||||
ctx_.view ().update (slep);
|
||||
}
|
||||
|
||||
auto const sle = ctx_.view ().peek (keylet::account (txAccount));
|
||||
|
||||
{
|
||||
// Check reserve and funds availability
|
||||
auto const balance = (*sle)[sfBalance];
|
||||
auto const reserve =
|
||||
ctx_.view ().fees ().accountReserve ((*sle)[sfOwnerCount]);
|
||||
|
||||
if (balance < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
|
||||
if (balance < reserve + ctx_.tx[sfAmount])
|
||||
return tecUNFUNDED;
|
||||
}
|
||||
|
||||
(*slep)[sfAmount] = (*slep)[sfAmount] + ctx_.tx[sfAmount];
|
||||
ctx_.view ().update (slep);
|
||||
|
||||
(*sle)[sfBalance] = (*sle)[sfBalance] - ctx_.tx[sfAmount];
|
||||
ctx_.view ().update (sle);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
TER
|
||||
PayChanClaim::preflight (PreflightContext const& ctx)
|
||||
{
|
||||
if (! ctx.rules.enabled(featurePayChan,
|
||||
ctx.app.config().features))
|
||||
return temDISABLED;
|
||||
|
||||
auto const ret = preflight1 (ctx);
|
||||
if (!isTesSuccess (ret))
|
||||
return ret;
|
||||
|
||||
auto const bal = ctx.tx[~sfBalance];
|
||||
if (bal && (!isXRP (*bal) || *bal <= beast::zero))
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
auto const amt = ctx.tx[~sfAmount];
|
||||
if (amt && (!isXRP (*amt) || *amt <= beast::zero))
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
if (bal && amt && *bal > *amt)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
auto const flags = ctx.tx.getFlags ();
|
||||
if ((flags & tfClose) && (flags & tfRenew))
|
||||
return temMALFORMED;
|
||||
|
||||
if (auto const sig = ctx.tx[~sfSignature])
|
||||
{
|
||||
if (!(ctx.tx[~sfPublicKey] && bal))
|
||||
return temMALFORMED;
|
||||
|
||||
// Check the signature
|
||||
// The signature isn't needed if txAccount == src, but if it's
|
||||
// present, check it
|
||||
|
||||
auto const reqBalance = bal->xrp ();
|
||||
auto const authAmt = amt ? amt->xrp() : reqBalance;
|
||||
|
||||
if (reqBalance > authAmt)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
Keylet const k (ltPAYCHAN, ctx.tx[sfPayChannel]);
|
||||
PublicKey const pk (ctx.tx[sfPublicKey]);
|
||||
Serializer msg;
|
||||
serializePayChanAuthorization (msg, k.key, authAmt);
|
||||
if (!verify (pk, msg.slice (), *sig, /*canonical*/ true))
|
||||
return temBAD_SIGNATURE;
|
||||
}
|
||||
|
||||
return preflight2 (ctx);
|
||||
}
|
||||
|
||||
TER
|
||||
PayChanClaim::doApply()
|
||||
{
|
||||
Keylet const k (ltPAYCHAN, ctx_.tx[sfPayChannel]);
|
||||
auto const slep = ctx_.view ().peek (k);
|
||||
if (!slep)
|
||||
return tecNO_TARGET;
|
||||
|
||||
AccountID const src = (*slep)[sfAccount];
|
||||
AccountID const dst = (*slep)[sfDestination];
|
||||
AccountID const txAccount = ctx_.tx[sfAccount];
|
||||
|
||||
auto const curExpiration = (*slep)[~sfExpiration];
|
||||
{
|
||||
auto const cancelAfter = (*slep)[~sfCancelAfter];
|
||||
auto const closeTime =
|
||||
ctx_.view ().info ().parentCloseTime.time_since_epoch ().count ();
|
||||
if ((cancelAfter && closeTime >= *cancelAfter) ||
|
||||
(curExpiration && closeTime >= *curExpiration))
|
||||
return closeChannel (
|
||||
slep, ctx_.view (), k.key, ctx_.app.journal ("View"));
|
||||
}
|
||||
|
||||
if (txAccount != src && txAccount != dst)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
if (ctx_.tx[~sfBalance])
|
||||
{
|
||||
auto const chanBalance = slep->getFieldAmount (sfBalance).xrp ();
|
||||
auto const chanFunds = slep->getFieldAmount (sfAmount).xrp ();
|
||||
auto const reqBalance = ctx_.tx[sfBalance].xrp ();
|
||||
|
||||
if (txAccount == dst && !ctx_.tx[~sfSignature])
|
||||
return temBAD_SIGNATURE;
|
||||
|
||||
if (ctx_.tx[~sfSignature])
|
||||
{
|
||||
PublicKey const pk ((*slep)[sfPublicKey]);
|
||||
if (ctx_.tx[sfPublicKey] != pk)
|
||||
return temBAD_SIGNER;
|
||||
}
|
||||
|
||||
if (reqBalance > chanFunds)
|
||||
return tecUNFUNDED_PAYMENT;
|
||||
|
||||
if (reqBalance <= chanBalance)
|
||||
// nothing requested
|
||||
return tecUNFUNDED_PAYMENT;
|
||||
|
||||
auto const sled = ctx_.view ().peek (keylet::account (dst));
|
||||
if (!sled)
|
||||
return terNO_ACCOUNT;
|
||||
|
||||
if (txAccount == src && ((*sled)[sfFlags] & lsfDisallowXRP))
|
||||
return tecNO_TARGET;
|
||||
|
||||
(*slep)[sfBalance] = ctx_.tx[sfBalance];
|
||||
XRPAmount const reqDelta = reqBalance - chanBalance;
|
||||
assert (reqDelta >= beast::zero);
|
||||
(*sled)[sfBalance] = (*sled)[sfBalance] + reqDelta;
|
||||
ctx_.view ().update (sled);
|
||||
ctx_.view ().update (slep);
|
||||
}
|
||||
|
||||
if (ctx_.tx.getFlags () & tfRenew)
|
||||
{
|
||||
if (src != txAccount)
|
||||
return tecNO_PERMISSION;
|
||||
(*slep)[~sfExpiration] = boost::none;
|
||||
ctx_.view ().update (slep);
|
||||
}
|
||||
|
||||
if (ctx_.tx.getFlags () & tfClose)
|
||||
{
|
||||
// Channel will close immediately if dry or the receiver closes
|
||||
if (dst == txAccount || (*slep)[sfBalance] == (*slep)[sfAmount])
|
||||
return closeChannel (
|
||||
slep, ctx_.view (), k.key, ctx_.app.journal ("View"));
|
||||
|
||||
auto const settleExpiration =
|
||||
ctx_.view ().info ().parentCloseTime.time_since_epoch ().count () +
|
||||
(*slep)[sfSettleDelay];
|
||||
|
||||
if (!curExpiration || *curExpiration > settleExpiration)
|
||||
{
|
||||
(*slep)[~sfExpiration] = settleExpiration;
|
||||
ctx_.view ().update (slep);
|
||||
}
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
} // ripple
|
||||
|
||||
91
src/ripple/app/tx/impl/PayChan.h
Normal file
91
src/ripple/app/tx/impl/PayChan.h
Normal file
@@ -0,0 +1,91 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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_PAYCHAN_H_INCLUDED
|
||||
#define RIPPLE_TX_PAYCHAN_H_INCLUDED
|
||||
|
||||
#include <ripple/app/tx/impl/Transactor.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class PayChanCreate
|
||||
: public Transactor
|
||||
{
|
||||
public:
|
||||
explicit
|
||||
PayChanCreate (ApplyContext& ctx)
|
||||
: Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static
|
||||
TER
|
||||
preflight (PreflightContext const& ctx);
|
||||
|
||||
static
|
||||
TER
|
||||
preclaim(PreclaimContext const &ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class PayChanFund
|
||||
: public Transactor
|
||||
{
|
||||
public:
|
||||
explicit
|
||||
PayChanFund (ApplyContext& ctx)
|
||||
: Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static
|
||||
TER
|
||||
preflight (PreflightContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class PayChanClaim
|
||||
: public Transactor
|
||||
{
|
||||
public:
|
||||
explicit
|
||||
PayChanClaim (ApplyContext& ctx)
|
||||
: Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static
|
||||
TER
|
||||
preflight (PreflightContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
};
|
||||
|
||||
} // ripple
|
||||
|
||||
#endif
|
||||
@@ -31,6 +31,7 @@
|
||||
#include <ripple/app/tx/impl/SetSignerList.h>
|
||||
#include <ripple/app/tx/impl/SetTrust.h>
|
||||
#include <ripple/app/tx/impl/SusPay.h>
|
||||
#include <ripple/app/tx/impl/PayChan.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
@@ -54,6 +55,9 @@ invoke_preflight (PreflightContext const& ctx)
|
||||
case ttTRUST_SET: return SetTrust ::preflight(ctx);
|
||||
case ttAMENDMENT:
|
||||
case ttFEE: return Change ::preflight(ctx);
|
||||
case ttPAYCHAN_CREATE: return PayChanCreate ::preflight(ctx);
|
||||
case ttPAYCHAN_FUND: return PayChanFund ::preflight(ctx);
|
||||
case ttPAYCHAN_CLAIM: return PayChanClaim ::preflight(ctx);
|
||||
default:
|
||||
assert(false);
|
||||
return temUNKNOWN;
|
||||
@@ -124,6 +128,9 @@ invoke_preclaim (PreclaimContext const& ctx)
|
||||
case ttTRUST_SET: return invoke_preclaim<SetTrust>(ctx);
|
||||
case ttAMENDMENT:
|
||||
case ttFEE: return invoke_preclaim<Change>(ctx);
|
||||
case ttPAYCHAN_CREATE: return invoke_preclaim<PayChanCreate>(ctx);
|
||||
case ttPAYCHAN_FUND: return invoke_preclaim<PayChanFund>(ctx);
|
||||
case ttPAYCHAN_CLAIM: return invoke_preclaim<PayChanClaim>(ctx);
|
||||
default:
|
||||
assert(false);
|
||||
return { temUNKNOWN, 0 };
|
||||
@@ -150,6 +157,9 @@ invoke_calculateBaseFee(PreclaimContext const& ctx)
|
||||
case ttTRUST_SET: return SetTrust::calculateBaseFee(ctx);
|
||||
case ttAMENDMENT:
|
||||
case ttFEE: return Change::calculateBaseFee(ctx);
|
||||
case ttPAYCHAN_CREATE: return PayChanCreate::calculateBaseFee(ctx);
|
||||
case ttPAYCHAN_FUND: return PayChanFund::calculateBaseFee(ctx);
|
||||
case ttPAYCHAN_CLAIM: return PayChanClaim::calculateBaseFee(ctx);
|
||||
default:
|
||||
assert(false);
|
||||
return 0;
|
||||
@@ -187,6 +197,9 @@ invoke_calculateConsequences(STTx const& tx)
|
||||
case ttTICKET_CANCEL: return invoke_calculateConsequences<CancelTicket>(tx);
|
||||
case ttTICKET_CREATE: return invoke_calculateConsequences<CreateTicket>(tx);
|
||||
case ttTRUST_SET: return invoke_calculateConsequences<SetTrust>(tx);
|
||||
case ttPAYCHAN_CREATE: return invoke_calculateConsequences<PayChanCreate>(tx);
|
||||
case ttPAYCHAN_FUND: return invoke_calculateConsequences<PayChanFund>(tx);
|
||||
case ttPAYCHAN_CLAIM: return invoke_calculateConsequences<PayChanClaim>(tx);
|
||||
case ttAMENDMENT:
|
||||
case ttFEE:
|
||||
// fall through to default
|
||||
@@ -217,6 +230,9 @@ invoke_apply (ApplyContext& ctx)
|
||||
case ttTRUST_SET: { SetTrust p(ctx); return p(); }
|
||||
case ttAMENDMENT:
|
||||
case ttFEE: { Change p(ctx); return p(); }
|
||||
case ttPAYCHAN_CREATE: { PayChanCreate p(ctx); return p(); }
|
||||
case ttPAYCHAN_FUND: { PayChanFund p(ctx); return p(); }
|
||||
case ttPAYCHAN_CLAIM: { PayChanClaim p(ctx); return p(); }
|
||||
default:
|
||||
assert(false);
|
||||
return { temUNKNOWN, false };
|
||||
|
||||
@@ -629,6 +629,12 @@ ApplyStateTable::threadOwners (ReadView const& base,
|
||||
threadTx (base, meta, (*sle)[sfDestination], mods, j);
|
||||
break;
|
||||
}
|
||||
case ltPAYCHAN:
|
||||
{
|
||||
threadTx (base, meta, (*sle)[sfAccount], mods, j);
|
||||
threadTx (base, meta, (*sle)[sfDestination], mods, j);
|
||||
break;
|
||||
}
|
||||
case ltRIPPLE_STATE:
|
||||
{
|
||||
threadTx (base, meta, (*sle)[sfLowLimit].getIssuer(), mods, j);
|
||||
|
||||
@@ -583,6 +583,73 @@ private:
|
||||
return parseAccountRaw2 (jvParams, "peer");
|
||||
}
|
||||
|
||||
// account_channels <account> <account>|"" [<ledger>]
|
||||
Json::Value parseAccountChannels (Json::Value const& jvParams)
|
||||
{
|
||||
return parseAccountRaw2 (jvParams, jss::destination_account);
|
||||
}
|
||||
|
||||
// channel_authorize <private_key> <channel_id> <drops>
|
||||
Json::Value parseChannelAuthorize (Json::Value const& jvParams)
|
||||
{
|
||||
Json::Value jvRequest (Json::objectValue);
|
||||
|
||||
jvRequest[jss::secret] = jvParams[0u];
|
||||
{
|
||||
// verify the channel id is a valid 256 bit number
|
||||
uint256 channelId;
|
||||
if (!channelId.SetHexExact (jvParams[1u].asString ()))
|
||||
return rpcError (rpcCHANNEL_MALFORMED);
|
||||
}
|
||||
jvRequest[jss::channel_id] = jvParams[1u].asString ();
|
||||
|
||||
try
|
||||
{
|
||||
auto const drops = std::stoul (jvParams[2u].asString ());
|
||||
(void)drops; // just used for error checking
|
||||
jvRequest[jss::amount] = jvParams[2u];
|
||||
}
|
||||
catch (std::exception const&)
|
||||
{
|
||||
return rpcError (rpcCHANNEL_AMT_MALFORMED);
|
||||
}
|
||||
|
||||
return jvRequest;
|
||||
}
|
||||
|
||||
// channel_verify <public_key> <channel_id> <drops> <signature>
|
||||
Json::Value parseChannelVerify (Json::Value const& jvParams)
|
||||
{
|
||||
std::string const strPk = jvParams[0u].asString ();
|
||||
|
||||
if (!parseBase58<PublicKey> (TokenType::TOKEN_ACCOUNT_PUBLIC, strPk))
|
||||
return rpcError (rpcPUBLIC_MALFORMED);
|
||||
|
||||
Json::Value jvRequest (Json::objectValue);
|
||||
|
||||
jvRequest[jss::public_key] = strPk;
|
||||
{
|
||||
// verify the channel id is a valid 256 bit number
|
||||
uint256 channelId;
|
||||
if (!channelId.SetHexExact (jvParams[1u].asString ()))
|
||||
return rpcError (rpcCHANNEL_MALFORMED);
|
||||
}
|
||||
jvRequest[jss::channel_id] = jvParams[1u].asString ();
|
||||
try
|
||||
{
|
||||
auto const drops = std::stoul (jvParams[2u].asString ());
|
||||
(void)drops; // just used for error checking
|
||||
jvRequest[jss::amount] = jvParams[2u];
|
||||
}
|
||||
catch (std::exception const&)
|
||||
{
|
||||
return rpcError (rpcCHANNEL_AMT_MALFORMED);
|
||||
}
|
||||
jvRequest[jss::signature] = jvParams[3u].asString ();
|
||||
|
||||
return jvRequest;
|
||||
}
|
||||
|
||||
Json::Value parseAccountRaw2 (Json::Value const& jvParams,
|
||||
char const * const acc2Field)
|
||||
{
|
||||
@@ -916,11 +983,14 @@ public:
|
||||
{ "account_currencies", &RPCParser::parseAccountCurrencies, 1, 2 },
|
||||
{ "account_info", &RPCParser::parseAccountItems, 1, 2 },
|
||||
{ "account_lines", &RPCParser::parseAccountLines, 1, 5 },
|
||||
{ "account_channels", &RPCParser::parseAccountChannels, 1, 3 },
|
||||
{ "account_objects", &RPCParser::parseAccountItems, 1, 5 },
|
||||
{ "account_offers", &RPCParser::parseAccountItems, 1, 4 },
|
||||
{ "account_tx", &RPCParser::parseAccountTransactions, 1, 8 },
|
||||
{ "book_offers", &RPCParser::parseBookOffers, 2, 7 },
|
||||
{ "can_delete", &RPCParser::parseCanDelete, 0, 1 },
|
||||
{ "channel_authorize", &RPCParser::parseChannelAuthorize, 3, 3 },
|
||||
{ "channel_verify", &RPCParser::parseChannelVerify, 4, 4 },
|
||||
{ "connect", &RPCParser::parseConnect, 1, 2 },
|
||||
{ "consensus_info", &RPCParser::parseAsIs, 0, 0 },
|
||||
{ "feature", &RPCParser::parseFeature, 0, 2 },
|
||||
|
||||
@@ -87,6 +87,8 @@ enum error_code_i
|
||||
rpcBAD_MARKET,
|
||||
rpcBAD_SECRET,
|
||||
rpcBAD_SEED,
|
||||
rpcCHANNEL_MALFORMED,
|
||||
rpcCHANNEL_AMT_MALFORMED,
|
||||
rpcCOMMAND_MISSING,
|
||||
rpcDST_ACT_MALFORMED,
|
||||
rpcDST_ACT_MISSING,
|
||||
|
||||
@@ -43,6 +43,7 @@ extern uint256 const featureFlowV2;
|
||||
extern uint256 const featureOwnerPaysFee;
|
||||
extern uint256 const featureCompareFlowV1V2;
|
||||
extern uint256 const featureSHAMapV2;
|
||||
extern uint256 const featurePayChan;
|
||||
|
||||
} // ripple
|
||||
|
||||
|
||||
@@ -99,6 +99,9 @@ public:
|
||||
|
||||
/** Manifest */
|
||||
static HashPrefix const manifest;
|
||||
|
||||
/** Payment Channel Claim */
|
||||
static HashPrefix const paymentChannelClaim;
|
||||
};
|
||||
|
||||
template <class Hasher>
|
||||
|
||||
@@ -238,6 +238,10 @@ Keylet page (uint256 const& key)
|
||||
Keylet
|
||||
susPay (AccountID const& source, std::uint32_t seq);
|
||||
|
||||
/** A PaymentChannel */
|
||||
Keylet
|
||||
payChan (AccountID const& source, AccountID const& dst, std::uint32_t seq);
|
||||
|
||||
} // keylet
|
||||
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ JSS ( Paths ); // in/out: TransactionSign
|
||||
JSS ( TransferRate ); // in: TransferRate
|
||||
JSS ( historical_perminute ); // historical_perminute
|
||||
JSS ( SLE_hit_rate ); // out: GetCounts
|
||||
JSS ( SettleDelay ); // in: TransactionSign
|
||||
JSS ( SendMax ); // in: TransactionSign
|
||||
JSS ( Sequence ); // in/out: TransactionSign; field.
|
||||
JSS ( SetFlag ); // field.
|
||||
@@ -80,6 +81,7 @@ JSS ( age ); // out: NetworkOPs, Peers
|
||||
JSS ( alternatives ); // out: PathRequest, RipplePathFind
|
||||
JSS ( amendment_blocked ); // out: NetworkOPs
|
||||
JSS ( amendments ); // in: AccountObjects, out: NetworkOPs
|
||||
JSS ( amount ); // out: AccountChannels
|
||||
JSS ( asks ); // out: Subscribe
|
||||
JSS ( assets ); // out: GatewayBalances
|
||||
JSS ( authorized ); // out: AccountLines
|
||||
@@ -98,7 +100,10 @@ JSS ( both ); // in: Subscribe, Unsubscribe
|
||||
JSS ( both_sides ); // in: Subscribe, Unsubscribe
|
||||
JSS ( build_path ); // in: TransactionSign
|
||||
JSS ( build_version ); // out: NetworkOPs
|
||||
JSS ( cancel_after ); // out: AccountChannels
|
||||
JSS ( can_delete ); // out: CanDelete
|
||||
JSS ( channel_id ); // out: AccountChannels
|
||||
JSS ( channels ); // out: AccountChannels
|
||||
JSS ( check_nodes ); // in: LedgerCleaner
|
||||
JSS ( clear ); // in/out: FetchInfo
|
||||
JSS ( close_flags ); // out: LedgerToJson
|
||||
@@ -135,10 +140,12 @@ JSS ( debug_signing ); // in: TransactionSign
|
||||
JSS ( delivered_amount ); // out: addPaymentDeliveredAmount
|
||||
JSS ( deprecated ); // out: WalletSeed
|
||||
JSS ( descending ); // in: AccountTx*
|
||||
JSS ( destination_account ); // in: PathRequest, RipplePathFind
|
||||
JSS ( destination_account ); // in: PathRequest, RipplePathFind, account_lines
|
||||
// out: AccountChannels
|
||||
JSS ( destination_amount ); // in: PathRequest, RipplePathFind
|
||||
JSS ( destination_currencies ); // in: PathRequest, RipplePathFind
|
||||
JSS ( destination_tag ); // in: PathRequest
|
||||
// out: AccountChannels
|
||||
JSS ( dir_entry ); // out: DirectoryEntryIterator
|
||||
JSS ( dir_index ); // out: DirectoryEntryIterator
|
||||
JSS ( dir_root ); // out: DirectoryEntryIterator
|
||||
@@ -156,7 +163,7 @@ JSS ( error_exception ); // out: Submit
|
||||
JSS ( error_message ); // out: error
|
||||
JSS ( expand ); // in: handler/Ledger
|
||||
JSS ( expected_ledger_size ); // out: TxQ
|
||||
JSS ( expiration ); // out: AccountOffers
|
||||
JSS ( expiration ); // out: AccountOffers, AccountChannels
|
||||
JSS ( fail_hard ); // in: Sign, Submit
|
||||
JSS ( failed ); // out: InboundLedger
|
||||
JSS ( feature ); // in: Feature
|
||||
@@ -347,7 +354,8 @@ JSS ( rt_accounts ); // in: Subscribe, Unsubscribe
|
||||
JSS ( sanity ); // out: PeerImp
|
||||
JSS ( search_depth ); // in: RipplePathFind
|
||||
JSS ( secret ); // in: TransactionSign, WalletSeed,
|
||||
// ValidationCreate, ValidationSeed
|
||||
// ValidationCreate, ValidationSeed,
|
||||
// channel_authorize
|
||||
JSS ( seed ); // in: WalletAccounts, out: WalletSeed
|
||||
JSS ( seed_hex ); // in: WalletPropose, TransactionSign
|
||||
JSS ( send_currencies ); // out: AccountCurrencies
|
||||
@@ -357,8 +365,10 @@ JSS ( seq ); // in: LedgerEntry;
|
||||
JSS ( seqNum ); // out: LedgerToJson
|
||||
JSS ( server_state ); // out: NetworkOPs
|
||||
JSS ( server_status ); // out: NetworkOPs
|
||||
JSS ( settle_delay ); // out: AccountChannels
|
||||
JSS ( severity ); // in: LogLevel
|
||||
JSS ( signature ); // out: NetworkOPs
|
||||
JSS ( signature ); // out: NetworkOPs, ChannelAuthorize
|
||||
JSS ( signature_verified ); // out: ChannelVerify
|
||||
JSS ( signing_key ); // out: NetworkOPs
|
||||
JSS ( signing_time ); // out: NetworkOPs
|
||||
JSS ( signer_list ); // in: AccountObjects
|
||||
@@ -367,6 +377,7 @@ JSS ( snapshot ); // in: Subscribe
|
||||
JSS ( source_account ); // in: PathRequest, RipplePathFind
|
||||
JSS ( source_amount ); // in: PathRequest, RipplePathFind
|
||||
JSS ( source_currencies ); // in: PathRequest, RipplePathFind
|
||||
JSS ( source_tag ); // out: AccountChannels
|
||||
JSS ( stand_alone ); // out: NetworkOPs
|
||||
JSS ( start ); // in: TxHistory
|
||||
JSS ( state ); // out: Logic.h, ServerState, LedgerData
|
||||
|
||||
@@ -80,6 +80,9 @@ enum LedgerEntryType
|
||||
|
||||
ltSUSPAY = 'u',
|
||||
|
||||
// Simple unidirection xrp channel
|
||||
ltPAYCHAN = 'x',
|
||||
|
||||
// No longer used or supported. Left here to prevent accidental
|
||||
// reassignment of the ledger type.
|
||||
ltNICKNAME = 'n',
|
||||
@@ -107,6 +110,7 @@ enum LedgerNameSpace
|
||||
spaceFee = 'e',
|
||||
spaceTicket = 'T',
|
||||
spaceSignerList = 'S',
|
||||
spaceXRPUChannel = 'x',
|
||||
|
||||
// No longer used or supported. Left here to reserve the space and
|
||||
// avoid accidental reuse of the space.
|
||||
|
||||
44
src/ripple/protocol/PayChan.h
Normal file
44
src/ripple/protocol/PayChan.h
Normal file
@@ -0,0 +1,44 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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_PROTOCOL_PAYCHAN_H_INCLUDED
|
||||
#define RIPPLE_PROTOCOL_PAYCHAN_H_INCLUDED
|
||||
|
||||
#include <ripple/basics/base_uint.h>
|
||||
#include <ripple/protocol/HashPrefix.h>
|
||||
#include <ripple/protocol/Serializer.h>
|
||||
#include <ripple/protocol/XRPAmount.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
inline
|
||||
void
|
||||
serializePayChanAuthorization (
|
||||
Serializer& msg,
|
||||
uint256 const& key,
|
||||
XRPAmount const& amt)
|
||||
{
|
||||
msg.add32 (HashPrefix::paymentChannelClaim);
|
||||
msg.add256 (key);
|
||||
msg.add64 (amt.drops ());
|
||||
}
|
||||
|
||||
} // ripple
|
||||
|
||||
#endif
|
||||
@@ -375,6 +375,7 @@ extern SF_U32 const sfSignerQuorum;
|
||||
extern SF_U32 const sfCancelAfter;
|
||||
extern SF_U32 const sfFinishAfter;
|
||||
extern SF_U32 const sfSignerListID;
|
||||
extern SF_U32 const sfSettleDelay;
|
||||
|
||||
// 64-bit integers
|
||||
extern SF_U64 const sfIndexNext;
|
||||
@@ -413,6 +414,7 @@ extern SF_U256 const sfNickname;
|
||||
extern SF_U256 const sfAmendment;
|
||||
extern SF_U256 const sfTicketID;
|
||||
extern SF_U256 const sfDigest;
|
||||
extern SF_U256 const sfPayChannel;
|
||||
|
||||
// currency amount (common)
|
||||
extern SF_Amount const sfAmount;
|
||||
|
||||
@@ -95,6 +95,10 @@ const std::uint32_t tfTrustSetMask = ~ (tfUniversal | tfSetfAuth | tfSet
|
||||
const std::uint32_t tfGotMajority = 0x00010000;
|
||||
const std::uint32_t tfLostMajority = 0x00020000;
|
||||
|
||||
// PaymentChannel flags:
|
||||
const std::uint32_t tfRenew = 0x00010000;
|
||||
const std::uint32_t tfClose = 0x00020000;
|
||||
|
||||
} // ripple
|
||||
|
||||
#endif
|
||||
|
||||
@@ -47,6 +47,9 @@ enum TxType
|
||||
ttTICKET_CREATE = 10,
|
||||
ttTICKET_CANCEL = 11,
|
||||
ttSIGNER_LIST_SET = 12,
|
||||
ttPAYCHAN_CREATE = 13,
|
||||
ttPAYCHAN_FUND = 14,
|
||||
ttPAYCHAN_CLAIM = 15,
|
||||
|
||||
ttTRUST_SET = 20,
|
||||
|
||||
|
||||
@@ -61,6 +61,8 @@ public:
|
||||
add (rpcBAD_SECRET, "badSecret", "Secret does not match account.");
|
||||
add (rpcBAD_SEED, "badSeed", "Disallowed seed.");
|
||||
add (rpcBAD_SYNTAX, "badSyntax", "Syntax error.");
|
||||
add (rpcCHANNEL_MALFORMED, "channelMalformed", "Payment channel is malformed.");
|
||||
add (rpcCHANNEL_AMT_MALFORMED, "channelAmtMalformed","Payment channel amount is malformed.");
|
||||
add (rpcCOMMAND_MISSING, "commandMissing", "Missing command entry.");
|
||||
add (rpcDST_ACT_MALFORMED, "dstActMalformed", "Destination account is malformed.");
|
||||
add (rpcDST_ACT_MISSING, "dstActMissing", "Destination account does not exist.");
|
||||
|
||||
@@ -54,5 +54,6 @@ uint256 const featureFlowV2 = feature("FlowV2");
|
||||
uint256 const featureOwnerPaysFee = feature("OwnerPaysFee");
|
||||
uint256 const featureCompareFlowV1V2 = feature("CompareFlowV1V2");
|
||||
uint256 const featureSHAMapV2 = feature("SHAMapV2");
|
||||
uint256 const featurePayChan = feature("PayChan");
|
||||
|
||||
} // ripple
|
||||
|
||||
@@ -36,5 +36,6 @@ HashPrefix const HashPrefix::txMultiSign ('S', 'M', 'T');
|
||||
HashPrefix const HashPrefix::validation ('V', 'A', 'L');
|
||||
HashPrefix const HashPrefix::proposal ('P', 'R', 'P');
|
||||
HashPrefix const HashPrefix::manifest ('M', 'A', 'N');
|
||||
HashPrefix const HashPrefix::paymentChannelClaim ('C', 'L', 'M');
|
||||
|
||||
} // ripple
|
||||
|
||||
@@ -323,6 +323,18 @@ susPay (AccountID const& source, std::uint32_t seq)
|
||||
return { ltSUSPAY, static_cast<uint256>(h) };
|
||||
}
|
||||
|
||||
Keylet
|
||||
payChan (AccountID const& source, AccountID const& dst, std::uint32_t seq)
|
||||
{
|
||||
sha512_half_hasher h;
|
||||
using beast::hash_append;
|
||||
hash_append(h, spaceXRPUChannel);
|
||||
hash_append(h, source);
|
||||
hash_append(h, dst);
|
||||
hash_append(h, seq);
|
||||
return { ltPAYCHAN, static_cast<uint256>(h) };
|
||||
}
|
||||
|
||||
} // keylet
|
||||
|
||||
} // ripple
|
||||
|
||||
@@ -130,6 +130,22 @@ LedgerFormats::LedgerFormats ()
|
||||
<< SOElement (sfPreviousTxnID, SOE_REQUIRED)
|
||||
<< SOElement (sfPreviousTxnLgrSeq, SOE_REQUIRED)
|
||||
;
|
||||
|
||||
add ("PayChannel", ltPAYCHAN)
|
||||
<< SOElement (sfAccount, SOE_REQUIRED)
|
||||
<< SOElement (sfDestination, SOE_REQUIRED)
|
||||
<< SOElement (sfAmount, SOE_REQUIRED)
|
||||
<< SOElement (sfBalance, SOE_REQUIRED)
|
||||
<< SOElement (sfPublicKey, SOE_REQUIRED)
|
||||
<< SOElement (sfSettleDelay, SOE_REQUIRED)
|
||||
<< SOElement (sfExpiration, SOE_OPTIONAL)
|
||||
<< SOElement (sfCancelAfter, SOE_OPTIONAL)
|
||||
<< SOElement (sfSourceTag, SOE_OPTIONAL)
|
||||
<< SOElement (sfDestinationTag, SOE_OPTIONAL)
|
||||
<< SOElement (sfOwnerNode, SOE_REQUIRED)
|
||||
<< SOElement (sfPreviousTxnID, SOE_REQUIRED)
|
||||
<< SOElement (sfPreviousTxnLgrSeq, SOE_REQUIRED)
|
||||
;
|
||||
}
|
||||
|
||||
void LedgerFormats::addCommonFields (Item& item)
|
||||
|
||||
@@ -126,6 +126,7 @@ SF_U32 const sfSignerQuorum = make::one<SF_U32::type>(&sfSignerQuorum,
|
||||
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");
|
||||
SF_U32 const sfSignerListID = make::one<SF_U32::type>(&sfSignerListID, STI_UINT32, 38, "SignerListID");
|
||||
SF_U32 const sfSettleDelay = make::one<SF_U32::type>(&sfSettleDelay, STI_UINT32, 39, "SettleDelay");
|
||||
|
||||
// 64-bit integers
|
||||
SF_U64 const sfIndexNext = make::one<SF_U64::type>(&sfIndexNext, STI_UINT64, 1, "IndexNext");
|
||||
@@ -164,6 +165,7 @@ SF_U256 const sfNickname = make::one<SF_U256::type>(&sfNickname, STI_H
|
||||
SF_U256 const sfAmendment = make::one<SF_U256::type>(&sfAmendment, STI_HASH256, 19, "Amendment");
|
||||
SF_U256 const sfTicketID = make::one<SF_U256::type>(&sfTicketID, STI_HASH256, 20, "TicketID");
|
||||
SF_U256 const sfDigest = make::one<SF_U256::type>(&sfDigest, STI_HASH256, 21, "Digest");
|
||||
SF_U256 const sfPayChannel = make::one<SF_U256::type>(&sfPayChannel, STI_HASH256, 22, "Channel");
|
||||
|
||||
// currency amount (common)
|
||||
SF_Amount const sfAmount = make::one<SF_Amount::type>(&sfAmount, STI_AMOUNT, 1, "Amount");
|
||||
|
||||
@@ -113,6 +113,26 @@ TxFormats::TxFormats ()
|
||||
<< SOElement (sfSignerQuorum, SOE_REQUIRED)
|
||||
<< SOElement (sfSignerEntries, SOE_OPTIONAL)
|
||||
;
|
||||
|
||||
add ("PaymentChannelCreate", ttPAYCHAN_CREATE) <<
|
||||
SOElement (sfDestination, SOE_REQUIRED) <<
|
||||
SOElement (sfAmount, SOE_REQUIRED) <<
|
||||
SOElement (sfSettleDelay, SOE_REQUIRED) <<
|
||||
SOElement (sfPublicKey, SOE_REQUIRED) <<
|
||||
SOElement (sfCancelAfter, SOE_OPTIONAL) <<
|
||||
SOElement (sfDestinationTag, SOE_OPTIONAL);
|
||||
|
||||
add ("PaymentChannelFund", ttPAYCHAN_FUND) <<
|
||||
SOElement (sfPayChannel, SOE_REQUIRED) <<
|
||||
SOElement (sfAmount, SOE_REQUIRED) <<
|
||||
SOElement (sfExpiration, SOE_OPTIONAL);
|
||||
|
||||
add ("PaymentChannelClaim", ttPAYCHAN_CLAIM) <<
|
||||
SOElement (sfPayChannel, SOE_REQUIRED) <<
|
||||
SOElement (sfAmount, SOE_OPTIONAL) <<
|
||||
SOElement (sfBalance, SOE_OPTIONAL) <<
|
||||
SOElement (sfSignature, SOE_OPTIONAL) <<
|
||||
SOElement (sfPublicKey, SOE_OPTIONAL);
|
||||
}
|
||||
|
||||
void TxFormats::addCommonFields (Item& item)
|
||||
|
||||
184
src/ripple/rpc/handlers/AccountChannels.cpp
Normal file
184
src/ripple/rpc/handlers/AccountChannels.cpp
Normal file
@@ -0,0 +1,184 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012-2014 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/main/Application.h>
|
||||
#include <ripple/ledger/ReadView.h>
|
||||
#include <ripple/ledger/View.h>
|
||||
#include <ripple/net/RPCErr.h>
|
||||
#include <ripple/protocol/ErrorCodes.h>
|
||||
#include <ripple/protocol/JsonFields.h>
|
||||
#include <ripple/protocol/PublicKey.h>
|
||||
#include <ripple/protocol/STAccount.h>
|
||||
#include <ripple/resource/Fees.h>
|
||||
#include <ripple/rpc/Context.h>
|
||||
#include <ripple/rpc/impl/RPCHelpers.h>
|
||||
#include <ripple/rpc/impl/Tuning.h>
|
||||
namespace ripple {
|
||||
|
||||
void addChannel (Json::Value& jsonLines, SLE const& line)
|
||||
{
|
||||
Json::Value& jDst (jsonLines.append (Json::objectValue));
|
||||
jDst[jss::channel_id] = to_string (line.key ());
|
||||
jDst[jss::account] = to_string (line[sfAccount]);
|
||||
jDst[jss::destination_account] = to_string (line[sfDestination]);
|
||||
jDst[jss::amount] = line[sfAmount].getText ();
|
||||
jDst[jss::balance] = line[sfBalance].getText ();
|
||||
PublicKey const pk (line[sfPublicKey]);
|
||||
jDst[jss::public_key] = toBase58 (TokenType::TOKEN_ACCOUNT_PUBLIC, pk);
|
||||
jDst[jss::public_key_hex] = strHex (pk);
|
||||
jDst[jss::settle_delay] = line[sfSettleDelay];
|
||||
if (auto const& v = line[~sfExpiration])
|
||||
jDst[jss::expiration] = *v;
|
||||
if (auto const& v = line[~sfCancelAfter])
|
||||
jDst[jss::cancel_after] = *v;
|
||||
if (auto const& v = line[~sfSourceTag])
|
||||
jDst[jss::source_tag] = *v;
|
||||
if (auto const& v = line[~sfDestinationTag])
|
||||
jDst[jss::destination_tag] = *v;
|
||||
}
|
||||
|
||||
// {
|
||||
// account: <account>|<account_public_key>
|
||||
// ledger_hash : <ledger>
|
||||
// ledger_index : <ledger_index>
|
||||
// limit: integer // optional
|
||||
// marker: opaque // optional, resume previous query
|
||||
// }
|
||||
Json::Value doAccountChannels (RPC::Context& context)
|
||||
{
|
||||
auto const& params (context.params);
|
||||
if (! params.isMember (jss::account))
|
||||
return RPC::missing_field_error (jss::account);
|
||||
|
||||
std::shared_ptr<ReadView const> ledger;
|
||||
auto result = RPC::lookupLedger (ledger, context);
|
||||
if (! ledger)
|
||||
return result;
|
||||
|
||||
std::string strIdent (params[jss::account].asString ());
|
||||
AccountID accountID;
|
||||
|
||||
result = RPC::accountFromString (accountID, strIdent);
|
||||
if (result)
|
||||
return result;
|
||||
|
||||
if (! ledger->exists(keylet::account (accountID)))
|
||||
return rpcError (rpcACT_NOT_FOUND);
|
||||
|
||||
std::string strDst;
|
||||
if (params.isMember (jss::destination_account))
|
||||
strDst = params[jss::destination_account].asString ();
|
||||
auto hasDst = ! strDst.empty ();
|
||||
|
||||
AccountID raDstAccount;
|
||||
if (hasDst)
|
||||
{
|
||||
result = RPC::accountFromString (raDstAccount, strDst);
|
||||
if (result)
|
||||
return result;
|
||||
}
|
||||
|
||||
unsigned int limit;
|
||||
if (auto err = readLimitField(limit, RPC::Tuning::accountChannels, context))
|
||||
return *err;
|
||||
|
||||
Json::Value jsonChannels{Json::arrayValue};
|
||||
struct VisitData
|
||||
{
|
||||
std::vector <std::shared_ptr<SLE const>> items;
|
||||
AccountID const& accountID;
|
||||
bool hasDst;
|
||||
AccountID const& raDstAccount;
|
||||
};
|
||||
VisitData visitData = {{}, accountID, hasDst, raDstAccount};
|
||||
unsigned int reserve (limit);
|
||||
uint256 startAfter;
|
||||
std::uint64_t startHint;
|
||||
|
||||
if (params.isMember (jss::marker))
|
||||
{
|
||||
// We have a start point. Use limit - 1 from the result and use the
|
||||
// very last one for the resume.
|
||||
Json::Value const& marker (params[jss::marker]);
|
||||
|
||||
if (! marker.isString ())
|
||||
return RPC::expected_field_error (jss::marker, "string");
|
||||
|
||||
startAfter.SetHex (marker.asString ());
|
||||
auto const sleChannel = ledger->read({ltPAYCHAN, startAfter});
|
||||
|
||||
if (! sleChannel)
|
||||
return rpcError (rpcINVALID_PARAMS);
|
||||
|
||||
if (sleChannel->getFieldAmount (sfLowLimit).getIssuer () == accountID)
|
||||
startHint = sleChannel->getFieldU64 (sfLowNode);
|
||||
else if (sleChannel->getFieldAmount (sfHighLimit).getIssuer () == accountID)
|
||||
startHint = sleChannel->getFieldU64 (sfHighNode);
|
||||
else
|
||||
return rpcError (rpcINVALID_PARAMS);
|
||||
|
||||
addChannel (jsonChannels, *sleChannel);
|
||||
visitData.items.reserve (reserve);
|
||||
}
|
||||
else
|
||||
{
|
||||
startHint = 0;
|
||||
// We have no start point, limit should be one higher than requested.
|
||||
visitData.items.reserve (++reserve);
|
||||
}
|
||||
|
||||
if (! forEachItemAfter(*ledger, accountID,
|
||||
startAfter, startHint, reserve,
|
||||
[&visitData](std::shared_ptr<SLE const> const& sleCur)
|
||||
{
|
||||
|
||||
if (sleCur && sleCur->getType () == ltPAYCHAN &&
|
||||
(! visitData.hasDst ||
|
||||
visitData.raDstAccount == (*sleCur)[sfDestination]))
|
||||
{
|
||||
visitData.items.emplace_back (sleCur);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}))
|
||||
{
|
||||
return rpcError (rpcINVALID_PARAMS);
|
||||
}
|
||||
|
||||
if (visitData.items.size () == reserve)
|
||||
{
|
||||
result[jss::limit] = limit;
|
||||
|
||||
result[jss::marker] = to_string (visitData.items.back()->getIndex());
|
||||
visitData.items.pop_back ();
|
||||
}
|
||||
|
||||
result[jss::account] = context.app.accountIDCache().toBase58 (accountID);
|
||||
|
||||
for (auto const& item : visitData.items)
|
||||
addChannel (jsonChannels, *item);
|
||||
|
||||
context.loadType = Resource::feeMediumBurdenRPC;
|
||||
result[jss::channels] = std::move(jsonChannels);
|
||||
return result;
|
||||
}
|
||||
|
||||
} // ripple
|
||||
@@ -27,6 +27,7 @@ namespace ripple {
|
||||
Json::Value doAccountCurrencies (RPC::Context&);
|
||||
Json::Value doAccountInfo (RPC::Context&);
|
||||
Json::Value doAccountLines (RPC::Context&);
|
||||
Json::Value doAccountChannels (RPC::Context&);
|
||||
Json::Value doAccountObjects (RPC::Context&);
|
||||
Json::Value doAccountOffers (RPC::Context&);
|
||||
Json::Value doAccountTx (RPC::Context&);
|
||||
@@ -35,6 +36,8 @@ Json::Value doAccountTxOld (RPC::Context&);
|
||||
Json::Value doBookOffers (RPC::Context&);
|
||||
Json::Value doBlackList (RPC::Context&);
|
||||
Json::Value doCanDelete (RPC::Context&);
|
||||
Json::Value doChannelAuthorize (RPC::Context&);
|
||||
Json::Value doChannelVerify (RPC::Context&);
|
||||
Json::Value doConnect (RPC::Context&);
|
||||
Json::Value doConsensusInfo (RPC::Context&);
|
||||
Json::Value doFeature (RPC::Context&);
|
||||
|
||||
130
src/ripple/rpc/handlers/PayChanClaim.cpp
Normal file
130
src/ripple/rpc/handlers/PayChanClaim.cpp
Normal file
@@ -0,0 +1,130 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012-2014 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/main/Application.h>
|
||||
#include <ripple/basics/StringUtilities.h>
|
||||
#include <ripple/ledger/ReadView.h>
|
||||
#include <ripple/net/RPCErr.h>
|
||||
#include <ripple/protocol/ErrorCodes.h>
|
||||
#include <ripple/protocol/JsonFields.h>
|
||||
#include <ripple/protocol/PayChan.h>
|
||||
#include <ripple/protocol/STAccount.h>
|
||||
#include <ripple/resource/Fees.h>
|
||||
#include <ripple/rpc/Context.h>
|
||||
#include <ripple/rpc/impl/RPCHelpers.h>
|
||||
#include <ripple/rpc/impl/Tuning.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
// {
|
||||
// secret_key: <signing_secret_key>
|
||||
// channel_id: 256-bit channel id
|
||||
// drops: 64-bit uint (as string)
|
||||
// }
|
||||
Json::Value doChannelAuthorize (RPC::Context& context)
|
||||
{
|
||||
auto const& params (context.params);
|
||||
for (auto const& p : {jss::secret, jss::channel_id, jss::amount})
|
||||
if (!params.isMember (p))
|
||||
return RPC::missing_field_error (p);
|
||||
|
||||
Json::Value result;
|
||||
auto const keypair = RPC::keypairForSignature (params, result);
|
||||
if (RPC::contains_error (result))
|
||||
return result;
|
||||
|
||||
uint256 channelId;
|
||||
if (!channelId.SetHexExact (params[jss::channel_id].asString ()))
|
||||
return rpcError (rpcCHANNEL_MALFORMED);
|
||||
|
||||
std::uint64_t drops = 0;
|
||||
try
|
||||
{
|
||||
drops = std::stoul (params[jss::amount].asString ());
|
||||
}
|
||||
catch (std::exception const&)
|
||||
{
|
||||
return rpcError (rpcCHANNEL_AMT_MALFORMED);
|
||||
}
|
||||
|
||||
Serializer msg;
|
||||
serializePayChanAuthorization (msg, channelId, XRPAmount (drops));
|
||||
|
||||
try
|
||||
{
|
||||
auto const buf = sign (keypair.first, keypair.second, msg.slice ());
|
||||
result[jss::signature] = strHex (buf);
|
||||
}
|
||||
catch (std::exception&)
|
||||
{
|
||||
result =
|
||||
RPC::make_error (rpcINTERNAL, "Exception occurred during signing.");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// {
|
||||
// public_key: <public_key>
|
||||
// channel_id: 256-bit channel id
|
||||
// drops: 64-bit uint (as string)
|
||||
// signature: signature to verify
|
||||
// }
|
||||
Json::Value doChannelVerify (RPC::Context& context)
|
||||
{
|
||||
auto const& params (context.params);
|
||||
for (auto const& p :
|
||||
{jss::public_key, jss::channel_id, jss::amount, jss::signature})
|
||||
if (!params.isMember (p))
|
||||
return RPC::missing_field_error (p);
|
||||
|
||||
std::string const strPk = params[jss::public_key].asString ();
|
||||
auto const pk =
|
||||
parseBase58<PublicKey> (TokenType::TOKEN_ACCOUNT_PUBLIC, strPk);
|
||||
if (!pk)
|
||||
return rpcError (rpcPUBLIC_MALFORMED);
|
||||
|
||||
uint256 channelId;
|
||||
if (!channelId.SetHexExact (params[jss::channel_id].asString ()))
|
||||
return rpcError (rpcCHANNEL_MALFORMED);
|
||||
|
||||
std::uint64_t drops = 0;
|
||||
try
|
||||
{
|
||||
drops = std::stoul (params[jss::amount].asString ());
|
||||
}
|
||||
catch (std::exception const&)
|
||||
{
|
||||
return rpcError (rpcCHANNEL_AMT_MALFORMED);
|
||||
}
|
||||
|
||||
std::pair<Blob, bool> sig(strUnHex (params[jss::signature].asString ()));
|
||||
if (!sig.second || !sig.first.size ())
|
||||
return rpcError (rpcINVALID_PARAMS);
|
||||
|
||||
Serializer msg;
|
||||
serializePayChanAuthorization (msg, channelId, XRPAmount (drops));
|
||||
|
||||
Json::Value result;
|
||||
result[jss::signature_verified] =
|
||||
verify (*pk, msg.slice (), makeSlice (sig.first), /*canonical*/ true);
|
||||
return result;
|
||||
}
|
||||
|
||||
} // ripple
|
||||
@@ -104,12 +104,15 @@ Handler handlerArray[] {
|
||||
{ "account_info", byRef (&doAccountInfo), Role::USER, NO_CONDITION },
|
||||
{ "account_currencies", byRef (&doAccountCurrencies), Role::USER, NO_CONDITION },
|
||||
{ "account_lines", byRef (&doAccountLines), Role::USER, NO_CONDITION },
|
||||
{ "account_channels", byRef (&doAccountChannels), Role::USER, NO_CONDITION },
|
||||
{ "account_objects", byRef (&doAccountObjects), Role::USER, NO_CONDITION },
|
||||
{ "account_offers", byRef (&doAccountOffers), Role::USER, NO_CONDITION },
|
||||
{ "account_tx", byRef (&doAccountTxSwitch), Role::USER, NO_CONDITION },
|
||||
{ "blacklist", byRef (&doBlackList), Role::ADMIN, NO_CONDITION },
|
||||
{ "book_offers", byRef (&doBookOffers), Role::USER, NO_CONDITION },
|
||||
{ "can_delete", byRef (&doCanDelete), Role::ADMIN, NO_CONDITION },
|
||||
{ "channel_authorize", byRef (&doChannelAuthorize), Role::USER, NO_CONDITION },
|
||||
{ "channel_verify", byRef (&doChannelVerify), Role::USER, NO_CONDITION },
|
||||
{ "connect", byRef (&doConnect), Role::ADMIN, NO_CONDITION },
|
||||
{ "consensus_info", byRef (&doConsensusInfo), Role::ADMIN, NO_CONDITION },
|
||||
{ "gateway_balances", byRef (&doGatewayBalances), Role::USER, NO_CONDITION },
|
||||
|
||||
@@ -35,6 +35,9 @@ struct LimitRange {
|
||||
/** Limits for the account_lines command. */
|
||||
static LimitRange const accountLines = {10, 200, 400};
|
||||
|
||||
/** Limits for the account_channels command. */
|
||||
static LimitRange const accountChannels = {10, 200, 400};
|
||||
|
||||
/** Limits for the account_objects command. */
|
||||
static LimitRange const accountObjects = {10, 200, 400};
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include <ripple/app/tests/OfferStream.test.cpp>
|
||||
#include <ripple/app/tests/Offer.test.cpp>
|
||||
#include <ripple/app/tests/Path_test.cpp>
|
||||
#include <ripple/app/tests/PayChan_test.cpp>
|
||||
#include <ripple/app/tests/Regression_test.cpp>
|
||||
#include <ripple/app/tests/SHAMapStore_test.cpp>
|
||||
#include <ripple/app/tests/SusPay_test.cpp>
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include <ripple/app/tx/impl/CreateTicket.cpp>
|
||||
#include <ripple/app/tx/impl/OfferStream.cpp>
|
||||
#include <ripple/app/tx/impl/Payment.cpp>
|
||||
#include <ripple/app/tx/impl/PayChan.cpp>
|
||||
#include <ripple/app/tx/impl/SetAccount.cpp>
|
||||
#include <ripple/app/tx/impl/SetRegularKey.cpp>
|
||||
#include <ripple/app/tx/impl/SetSignerList.cpp>
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include <ripple/rpc/handlers/AccountCurrenciesHandler.cpp>
|
||||
#include <ripple/rpc/handlers/AccountInfo.cpp>
|
||||
#include <ripple/rpc/handlers/AccountLines.cpp>
|
||||
#include <ripple/rpc/handlers/AccountChannels.cpp>
|
||||
#include <ripple/rpc/handlers/AccountObjects.cpp>
|
||||
#include <ripple/rpc/handlers/AccountOffers.cpp>
|
||||
#include <ripple/rpc/handlers/AccountTx.cpp>
|
||||
@@ -62,6 +63,7 @@
|
||||
#include <ripple/rpc/handlers/NoRippleCheck.cpp>
|
||||
#include <ripple/rpc/handlers/OwnerInfo.cpp>
|
||||
#include <ripple/rpc/handlers/PathFind.cpp>
|
||||
#include <ripple/rpc/handlers/PayChanClaim.cpp>
|
||||
#include <ripple/rpc/handlers/Peers.cpp>
|
||||
#include <ripple/rpc/handlers/Ping.cpp>
|
||||
#include <ripple/rpc/handlers/Print.cpp>
|
||||
|
||||
Reference in New Issue
Block a user