Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AIP-044: Third-party transaction execution #44

Open
jeff-aion opened this issue Aug 20, 2019 · 4 comments
Open

AIP-044: Third-party transaction execution #44

jeff-aion opened this issue Aug 20, 2019 · 4 comments

Comments

@jeff-aion
Copy link

jeff-aion commented Aug 20, 2019

Title: Third-party transaction execution
Author: Jeff Disher
Type: Core
Status: Discussion
Creation Date: 2019-08-20
Contact information: [email protected]

Summary

This proposal describes a mechanism for paying to run a preserialized transaction, typically signed by another user, from within an already-running AVM transaction.

This allows for running a transaction as one user but paying for it as another.

The key idea here is that this is not known at the base protocol level: The entire system just treats this serialized transaction as generic data, except for the one API routine which accepts this input.

Additionally, a goal is to generalize this enough that it doesn't only solve third-party billing but can become a primitive within more complex idioms, yet to be considered. However, the billing problem is the key driving factor here.

Problem

In order for a user to submit a transaction to the blockchain, they must have an account balance to purchase energy.

This makes it impractical for a developer to expose native blockchain functionality within an application, as this would alienate the vast majority of their users.

If the developer could pay this fee on the user's behalf, the barrier to use is dramatically lowered.

Objectives

In order to generalize the approach, the scope of objectives has been slightly expanded:

  • Allow a transaction to executed as one user, but billed to another
  • Allow a transaction to be stored on-chain for later execution

Concerns Outside of Scope

  • This AIP ONLY defines a new API call, what it does, and how to package data to pass into it
  • It does NOT define any change to transaction serialization, in general
  • It does NOT change the way cross-calls work, in general
  • It ONLY handles the case where an off-chain user provides the serialized transaction
    • No support for contracts to create these serialized transactions will be provided (as that would require a completely different validation mechanism)
  • There will be NO support for FVM contracts

Architectural Overview and Concepts

The flow we are expecting should work like this:

 USER  -----------T----------> OWNER
   .                             |
   . T                           | (T)
   \/                            \/
TARGET <----------T----------- PROXY

In this diagram, USER and OWNER are off-chain, standard accounts. TARGET and PROXY are on-chain contracts. Flow of execution to emulate "USER sends T to TARGET":

  • USER serializes and signs T, sending this to OWNER
  • OWNER puts this serialized T into its own transaction and sends that to PROXY as a standard transaction
  • PROXY reads T as the transaction data, passing it to the new Blockchain.invokeTransaction() routine, along with an energy limit
  • TARGET receives transaction T as though it was sent directly by USER

Terminology we define to keep this proposal clear:

  • Outer transaction
    • The normal transaction sent to the blockchain which executes, and pays for, the inner transaction
    • In the simple case, this also includes the serialized inner transaction but that could have been sent by an earlier transaction
  • Inner transaction
    • The special transaction which is signed by its sender but paid for by someone else
    • This is not sent directly to the blockchain but packaged as data in some other transaction
  • Executor
    • A field in the serialized inner transaction
    • The on-chain contract which is allowed to request that this inner transaction be invoked

Specification Details

We propose the introduction of a new method on the Blockchain API class, in the AVM:

public static Result invokeTransaction(byte[] transactionPayload, long energyLimit)

This method operates similarly to the call() or create() methods, except that it takes a transactionPayload which is a serialized transaction, including signature. If the transactionPayload is a valid transaction, with correct nonce and signature, then it is executed as an internal transaction. The key difference, however, is that a normal internal transaction maintains the origin of its external transaction and uses the invoking contract address as its sender. In the case of this preserialized transaction, the original signer of the transaction appears as both of these.

Note that stack depth is still counted across this call, meaning this maximum depth before overflow is the only behavioural difference, within the contract, when compared to a normal external transaction.

Error cases thrown as IllegalArgumentException (note that such errors are analogous to "rejection" and do not increment the nonce):

  • Incorrect serialized form
  • Negative value
  • Insufficient sender balance
  • Duplicate nonce
  • Future nonce (the correct usage will only be the "very next" nonce)
  • Invalid energy limit
  • Signature doesn't match
  • Executor is not self

Receiver-side perspective of the invocation, in terms of Blockchain behaviours (omitted methods are same as normal):

  • getCaller() - the "sender" as written into the serialized transaction
  • getOrigin() - the "sender" as written into the serialized transaction
  • getEnergyLimit() - the limit specified by the caller
  • getBlockTimestamp() - the timestamp of the current block (where this is executed - not anything related to when it was signed)
  • getBlockNumber() - the block number of the current block (where this is executed - not anything related to when it was signed)
  • call()/ create() - generally the same as normal except that the maximum call depth is still reduced by one for every call in the current stack

The contents of this transaction are slightly different, as well:

Transaction Field Standard Transaction "invokeTransaction" Description
SenderAddress YES YES The address of the sender
TargetAddress YES YES The address of the transaction target
Value YES YES The coin value to transfer
Data YES YES Data to send along with the transaction
Nonce YES YES The unique nonce of this transaction (avoid replay attack)
Energy Limit YES NO The maximum energy which can be consumed by the transaction before it fails
Energy Price YES NO The price the contract will pay per energy unit
Executor NO YES The address of the only contract which can send this transaction to invokeTransaction
Signature YES YES The signature of the transaction contents, as signed by the sender's private key

To avoiding reinventing more than is required, the transaction will be serialized as an RLP-encoded list, much like standard transactions (only difference being which data elements are included in the list).

Note that contract creation is possible with this kind of transaction, but only AVM contracts can be deployed this way. Bytecode for a different virtual machine will just be interpreted as invalid data and will fail deployment.

This inner transaction is stored by its hash (hash including the signature), since there is no other mechanism which can be used for globally addressing it. The data returned for this transaction, when its receipt is requested, will have only basic data, including a reference to the external transaction where it was executed. In general, side-effects of an inner transaction are persisted much as a standard internal transaction.

Logical Flow and Examples

There are 2 prototypical examples of how this could be used:

Third-party billing ("try before you buy")

  • User U is using an application developed/provided by developer D
    • U is just trying this out and doesn't yet have any Aion
  • A key part of the functionality requires that U execute a transaction
  • U assembles the transaction, serializes it, signs it, and then sends this combined payload to D's web application
    • Details: zero value and a basic proxy contract (just passes the given data to invokeTransaction) specified as executor
  • On D's server, payload submitted by U is deserialized and verified as an activity D is willing to pay for
  • D's server assembles a new transaction to the proxy contract, using U's payload as the input data
  • D signs this transaction and sends it to the blockchain (meaning D will pay for the execution)
  • The proxy contract receives the payload transaction and passes it into invokeTransaction
  • The destination contract executes the transaction originally signed by U, assuming it was submitted by U (origin is U), but D is paying for it
  • Nonce of U is incremented on the blockchain

Delayed execution ("post-dated cheques")

  • User U rents an apartment from company C
  • C offers payment by Aion and U happens to have Aion to spare
  • U creates a new address to manage these rent payments: R
  • Rent payment transactions to C for each month of the year are serialized and signed by R, then submitted to C's rent management contract
    • Details: monthly payment set as value and "C's rent management contract" set as executor
  • C stores these signed payloads on-chain (note that this also works for off-chain but this is a more interesting case and allows for on-chain rental payment rules to be trusted)
  • Each month, at the agreed-upon time, C sends a transaction to the rent management contract, which picks up the next signed payload from R and sends it to invokeTransaction
  • The payload is a simple balance transfer from R to C so this transfer goes through without R's interaction (in fact, R can destroy their private key after presigning, if they never want to revoke the transactions)
    • Each one also increments R's nonce so order is important
  • NOTE: If R wishes to terminate the rental agreement, they can notify C to drop them or send dummy transactions for each month of the year to invalidate the nonce of the presubmitted transactions
  • NOTE: Even though the transactions were prepared ahead-of-time, R only requires the balance to cover the cost before the transaction is run

Risks and Assumptions

We are assuming the following things about the user who is signing the transaction (not the developer who is paying for it):

  • they have a private key
  • they know the correct nonce
    • while this is easy in the common case, that "post-dated cheques" scenario demonstrates a complex usage (may be an advanced use-case)

Testing Considerations

  • invoke a correct transaction
  • fail to invoke already-executed transaction
  • run transaction as wrong contract
  • run transaction incorrectly signed
  • run corrupt transaction
  • run transaction with a future nonce
  • deploy a contract
  • invoke internal calls to stack overflow

Externals

  • Dashboard visibility into these transactions
  • Client-side libraries for encoding these transactions
@jennijuju
Copy link
Contributor

jennijuju commented Aug 20, 2019

@jeff-aion Thank you for submitting. Since you already have the proposal, do you want to proceed this as a formal AIP?

@jennijuju jennijuju changed the title Third-party transaction execution AIP-044: Third-party transaction execution Aug 22, 2019
@jennijuju
Copy link
Contributor

Changing the status to Discussion and assigned AIP number.

@jeff-aion
Copy link
Author

Yesterday, we decided to add this to the scope of the next hard-fork.

@geoff-aion
Copy link

geoff-aion commented Oct 4, 2019

An Invokable transaction is encoded as a byte array as follows:

The first byte is a version byte, which for now must be 0.
The following bytes are an RLP encoded list of RLP encoded elements in order :
nonce as a BigInteger. Cannot be null
destination address Can be null. Null signifies a Contract Creation.
value as a BigInteger. Cannot be null
data Cannot be null.
executor Can be null. This or the 0 Address allow any contract to execute the invokable.
signature

The signature is calculated on the RLP encoded list of the other 5 fields, prepended with the version byte.

The transaction hash is calculated as the blake2b hash of the entire encoded Invokable transaction.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants