Skip to content

Commit

Permalink
Add DepositPreauth ledger type and transaction (RIPD-1624):
Browse files Browse the repository at this point in the history
The lsfDepositAuth flag limits the AccountIDs that can deposit into
the account that has the flag set.  The original design only
allowed deposits to complete if the account with the flag set also
signed the transaction that caused the deposit.

The DepositPreauth ledger type allows an account with the
lsfDepositAuth flag set to preauthorize additional accounts.
This preauthorization allows them to sign deposits as well.  An
account can add DepositPreauth objects to the ledger (and remove
them as well) using the DepositPreauth transaction.
  • Loading branch information
scottschurr authored and seelabs committed May 15, 2018
1 parent b444196 commit 008ff67
Show file tree
Hide file tree
Showing 56 changed files with 1,587 additions and 175 deletions.
1 change: 1 addition & 0 deletions src/ripple/app/main/Main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ void printHelp (const po::options_description& desc)
" channel_verify <public_key> <channel_id> <drops> <signature>\n"
" connect <ip> [<port>]\n"
" consensus_info\n"
" deposit_authorized <source_account> <destination_account> [<ledger>]"
" feature [<feature> [accept|reject]]\n"
" fetch_info [clear]\n"
" gateway_balances [<ledger>] <issuer_account> [ <hotwallet> [ <hotwallet> ]]\n"
Expand Down
2 changes: 1 addition & 1 deletion src/ripple/app/tx/impl/CancelCheck.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class CancelCheck
: public Transactor
{
public:
CancelCheck (ApplyContext& ctx)
explicit CancelCheck (ApplyContext& ctx)
: Transactor (ctx)
{
}
Expand Down
2 changes: 1 addition & 1 deletion src/ripple/app/tx/impl/CancelOffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class CancelOffer
: public Transactor
{
public:
CancelOffer (ApplyContext& ctx)
explicit CancelOffer (ApplyContext& ctx)
: Transactor(ctx)
{
}
Expand Down
2 changes: 1 addition & 1 deletion src/ripple/app/tx/impl/CancelTicket.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class CancelTicket
: public Transactor
{
public:
CancelTicket (ApplyContext& ctx)
explicit CancelTicket (ApplyContext& ctx)
: Transactor(ctx)
{
}
Expand Down
2 changes: 1 addition & 1 deletion src/ripple/app/tx/impl/CashCheck.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class CashCheck
: public Transactor
{
public:
CashCheck (ApplyContext& ctx)
explicit CashCheck (ApplyContext& ctx)
: Transactor (ctx)
{
}
Expand Down
2 changes: 1 addition & 1 deletion src/ripple/app/tx/impl/Change.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class Change
: public Transactor
{
public:
Change (ApplyContext& ctx)
explicit Change (ApplyContext& ctx)
: Transactor(ctx)
{
}
Expand Down
2 changes: 1 addition & 1 deletion src/ripple/app/tx/impl/CreateCheck.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class CreateCheck
: public Transactor
{
public:
CreateCheck (ApplyContext& ctx)
explicit CreateCheck (ApplyContext& ctx)
: Transactor (ctx)
{
}
Expand Down
12 changes: 6 additions & 6 deletions src/ripple/app/tx/impl/CreateOffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ CreateOffer::preclaim(PreclaimContext const& ctx)
//
// The return code change is attached to featureChecks as a convenience.
// The change is not big enough to deserve its own amendment.
return ctx.view.rules().enabled(featureChecks)
return ctx.view.rules().enabled(featureDepositPreauth)
? TER {tecEXPIRED}
: TER {tesSUCCESS};
}
Expand Down Expand Up @@ -237,10 +237,10 @@ CreateOffer::checkAcceptAsset(ReadView const& view,
: TER {tecNO_ISSUER};
}

// This code is attached to the FlowCross amendment as a matter of
// This code is attached to the DepositPreauth amendment as a matter of
// convenience. The change is not significant enough to deserve its
// own amendment.
if (view.rules().enabled(featureFlowCross) && (issue.account == id))
if (view.rules().enabled(featureDepositPreauth) && (issue.account == id))
// An account can always accept its own issuance.
return tesSUCCESS;

Expand Down Expand Up @@ -1106,10 +1106,10 @@ CreateOffer::applyGuts (Sandbox& sb, Sandbox& sbCancel)
// If the offer has expired, the transaction has successfully
// done nothing, so short circuit from here.
//
// The return code change is attached to featureChecks as a convenience.
// The change is not big enough to deserve its own amendment.
// The return code change is attached to featureDepositPreauth as a
// convenience. The change is not big enough to deserve a fix code.
TER const ter {ctx_.view().rules().enabled(
featureChecks) ? TER {tecEXPIRED} : TER {tesSUCCESS}};
featureDepositPreauth) ? TER {tecEXPIRED} : TER {tesSUCCESS}};
return{ ter, true };
}

Expand Down
2 changes: 1 addition & 1 deletion src/ripple/app/tx/impl/CreateOffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class CreateOffer
{
public:
/** Construct a Transactor subclass that creates an offer in the ledger. */
CreateOffer (ApplyContext& ctx)
explicit CreateOffer (ApplyContext& ctx)
: Transactor(ctx)
, stepCounter_ (1000, j_)
{
Expand Down
2 changes: 1 addition & 1 deletion src/ripple/app/tx/impl/CreateTicket.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class CreateTicket
: public Transactor
{
public:
CreateTicket (ApplyContext& ctx)
explicit CreateTicket (ApplyContext& ctx)
: Transactor(ctx)
{
}
Expand Down
189 changes: 189 additions & 0 deletions src/ripple/app/tx/impl/DepositPreauth.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2018 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 <ripple/app/tx/impl/DepositPreauth.h>
#include <ripple/basics/Log.h>
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/Indexes.h>
#include <ripple/protocol/st.h>
#include <ripple/protocol/TxFlags.h>
#include <ripple/ledger/View.h>

namespace ripple {

NotTEC
DepositPreauth::preflight (PreflightContext const& ctx)
{
if (! ctx.rules.enabled (featureDepositPreauth))
return temDISABLED;

auto const ret = preflight1 (ctx);
if (!isTesSuccess (ret))
return ret;

auto& tx = ctx.tx;
auto& j = ctx.j;

if (tx.getFlags() & tfUniversalMask)
{
JLOG(j.trace()) <<
"Malformed transaction: Invalid flags set.";
return temINVALID_FLAG;
}

auto const optAuth = ctx.tx[~sfAuthorize];
auto const optUnauth = ctx.tx[~sfUnauthorize];
if (static_cast<bool>(optAuth) == static_cast<bool>(optUnauth))
{
// Either both fields are present or neither field is present. In
// either case the transaction is malformed.
JLOG(j.trace()) <<
"Malformed transaction: "
"Invalid Authorize and Unauthorize field combination.";
return temMALFORMED;
}

// Make sure that the passed account is valid.
AccountID const target {optAuth ? *optAuth : *optUnauth};
if (target == beast::zero)
{
JLOG(j.trace()) <<
"Malformed transaction: Authorized or Unauthorized field zeroed.";
return temINVALID_ACCOUNT_ID;
}

// An account may not preauthorize itself.
if (optAuth && (target == ctx.tx[sfAccount]))
{
JLOG(j.trace()) <<
"Malformed transaction: Attempting to DepositPreauth self.";
return temCANNOT_PREAUTH_SELF;
}

return preflight2 (ctx);
}

TER
DepositPreauth::preclaim(PreclaimContext const& ctx)
{
// Determine which operation we're performing: authorizing or unauthorizing.
if (ctx.tx.isFieldPresent (sfAuthorize))
{
// Verify that the Authorize account is present in the ledger.
AccountID const auth {ctx.tx[sfAuthorize]};
if (! ctx.view.exists (keylet::account (auth)))
return tecNO_TARGET;

// Verify that the Preauth entry they asked to add is not already
// in the ledger.
if (ctx.view.exists (
keylet::depositPreauth (ctx.tx[sfAccount], auth)))
return tecDUPLICATE;
}
else
{
// Verify that the Preauth entry they asked to remove is in the ledger.
AccountID const unauth {ctx.tx[sfUnauthorize]};
if (! ctx.view.exists (
keylet::depositPreauth (ctx.tx[sfAccount], unauth)))
return tecNO_ENTRY;
}
return tesSUCCESS;
}

TER
DepositPreauth::doApply ()
{
auto const sleOwner = view().peek (keylet::account (account_));

if (ctx_.tx.isFieldPresent (sfAuthorize))
{
// A preauth counts against the reserve of the issuing account, but we
// check the starting balance because we want to allow dipping into the
// reserve to pay fees.
{
STAmount const reserve {view().fees().accountReserve (
sleOwner->getFieldU32 (sfOwnerCount) + 1)};

if (mPriorBalance < reserve)
return tecINSUFFICIENT_RESERVE;
}

// Preclaim already verified that the Preauth entry does not yet exist.
// Create and populate the Preauth entry.
AccountID const auth {ctx_.tx[sfAuthorize]};
auto slePreauth =
std::make_shared<SLE>(keylet::depositPreauth (account_, auth));

slePreauth->setAccountID (sfAccount, account_);
slePreauth->setAccountID (sfAuthorize, auth);
view().insert (slePreauth);

auto viewJ = ctx_.app.journal ("View");
auto const page = view().dirInsert (keylet::ownerDir (account_),
slePreauth->key(), describeOwnerDir (account_));

JLOG(j_.trace())
<< "Adding DepositPreauth to owner directory "
<< to_string (slePreauth->key())
<< ": " << (page ? "success" : "failure");

if (! page)
return tecDIR_FULL;

slePreauth->setFieldU64 (sfOwnerNode, *page);

// If we succeeded, the new entry counts against the creator's reserve.
adjustOwnerCount (view(), sleOwner, 1, viewJ);
}
else
{
// Verify that the Preauth entry they asked to remove is
// in the ledger.
AccountID const unauth {ctx_.tx[sfUnauthorize]};
uint256 const preauthIndex {getDepositPreauthIndex (account_, unauth)};
auto slePreauth = view().peek (keylet::depositPreauth (preauthIndex));

if (! slePreauth)
{
// Error should have been caught in preclaim.
JLOG(j_.warn()) << "Selected DepositPreauth does not exist.";
return tecNO_ENTRY;
}

auto viewJ = ctx_.app.journal ("View");
std::uint64_t const page {(*slePreauth)[sfOwnerNode]};
if (! view().dirRemove (
keylet::ownerDir (account_), page, preauthIndex, true))
{
JLOG(j_.warn()) << "Unable to delete DepositPreauth from owner.";
return tefBAD_LEDGER;
}

// If we succeeded, update the DepositPreauth owner's reserve.
auto const sleOwner = view().peek (keylet::account (account_));
adjustOwnerCount (view(), sleOwner, -1, viewJ);

// Remove DepositPreauth from ledger.
view().erase (slePreauth);
}
return tesSUCCESS;
}

} // namespace ripple
50 changes: 50 additions & 0 deletions src/ripple/app/tx/impl/DepositPreauth.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//------------------------------------------------------------------------------
/*
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_DEPOSIT_PREAUTH_H_INCLUDED
#define RIPPLE_TX_DEPOSIT_PREAUTH_H_INCLUDED

#include <ripple/app/tx/impl/Transactor.h>

namespace ripple {

class DepositPreauth
: public Transactor
{
public:
explicit DepositPreauth (ApplyContext& ctx)
: Transactor(ctx)
{
}

static
NotTEC
preflight (PreflightContext const& ctx);

static
TER
preclaim(PreclaimContext const& ctx);

TER doApply () override;
};

} // ripple

#endif

20 changes: 13 additions & 7 deletions src/ripple/app/tx/impl/Escrow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -446,8 +446,9 @@ EscrowFinish::doApply()
return tecCRYPTOCONDITION_ERROR;
}

// NOTE: Escrow payments cannot be used to fund accounts
auto const sled = ctx_.view().peek(keylet::account((*slep)[sfDestination]));
// NOTE: Escrow payments cannot be used to fund accounts.
AccountID const destID = (*slep)[sfDestination];
auto const sled = ctx_.view().peek(keylet::account(destID));
if (! sled)
return tecNO_DST;

Expand All @@ -456,10 +457,15 @@ EscrowFinish::doApply()
// Is EscrowFinished authorized?
if (sled->getFlags() & lsfDepositAuth)
{
// Authorized if Destination == Account, otherwise no permission.
AccountID const destID = (*slep)[sfDestination];
if (ctx_.tx[sfAccount] != destID)
return tecNO_PERMISSION;
// A destination account that requires authorization has two
// ways to get an EscrowFinished into the account:
// 1. If Account == Destination, or
// 2. If Account is deposit preauthorized by destination.
if (account_ != destID)
{
if (! view().exists (keylet::depositPreauth (destID, account_)))
return tecNO_PERMISSION;
}
}
}

Expand All @@ -479,7 +485,7 @@ EscrowFinish::doApply()
if (ctx_.view ().rules().enabled(fix1523) && (*slep)[~sfDestinationNode])
{
TER const ter = dirDelete(ctx_.view(), true,
(*slep)[sfDestinationNode], keylet::ownerDir((*slep)[sfDestination]),
(*slep)[sfDestinationNode], keylet::ownerDir(destID),
k.key, false, false, ctx_.app.journal ("View"));
if (! isTesSuccess(ter))
return ter;
Expand Down
Loading

0 comments on commit 008ff67

Please sign in to comment.