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

Practical transaction sponsoring #2577

Open
roman-khimov opened this issue Aug 17, 2021 · 0 comments
Open

Practical transaction sponsoring #2577

roman-khimov opened this issue Aug 17, 2021 · 0 comments
Labels
discussion Initial issue state - proposed but not yet accepted

Comments

@roman-khimov
Copy link
Contributor

Summary or problem description
The topic of free/sponsored transactions had been touched several times by at least #1104, #1147, #1468, #2442 and neo-project/proposals#137. Some of those discussions are no longer relevant (the code evolved since), some tried to fix specific GAS claiming problems (like #2008 also), some are technically challenging to implement. We also had refueling capability (#2443/#2444) until recently which allowed for semi-sponsored transactions (with 0.000smth system fee paid by user and the rest by contract), but it turned out to be too problematic and went away with #2560.

At the same time given the number of related issues and questions on Discord (hi, @EdgeDLT) contract-sponsored transactions are somewhat expected of Neo N3 where every transaction has to be paid for and many dApps will happily pay for its users making it easier for them to use the dApp. And we do have some basis for them, that is support for multiple signers with scoped witnesses where the first signer (also known as sender) pays transaction fees.

So technically we already can create transactions with contract address specified as the first signer and then user address specified as the second one. If this transaction passes verification the contract will pay and the user will get what he wants. But verification relies on contract itself, specifically verify method of it that should decide whether transaction in question should allowed. For example, that's what Oracle contract does:

private bool Verify(ApplicationEngine engine)
{
Transaction tx = (Transaction)engine.ScriptContainer;
return tx?.GetAttribute<OracleResponse>() != null;
}

It considers to be valid any transaction that has OracleResponse attribute and this attribute itself is very special, to use it transaction needs to satisfy a lot of additional conditions:

public override bool Verify(DataCache snapshot, Transaction tx)
{
if (tx.Signers.Any(p => p.Scopes != WitnessScope.None)) return false;
if (!tx.Script.AsSpan().SequenceEqual(FixedScript)) return false;
OracleRequest request = NativeContract.Oracle.GetRequest(snapshot, Id);
if (request is null) return false;
if (tx.NetworkFee + tx.SystemFee != request.GasForResponse) return false;
UInt160 oracleAccount = Contract.GetBFTAddress(NativeContract.RoleManagement.GetDesignatedByRole(snapshot, Role.Oracle, NativeContract.Ledger.CurrentIndex(snapshot) + 1));
return tx.Signers.Any(p => p.Account.Equals(oracleAccount));
}

So there is some responsibility sharing between contract and attribute which is possible because Oracle contract is native one. If someone is to try to do something similar with regular non-native contract he will discover that:

  • verification context is limited in GAS by MaxVerificationGas constant, so it can't use more than that for its logic:
    /// <summary>
    /// The maximum GAS that can be consumed when <see cref="VerifyWitnesses"/> is called.
    /// </summary>
    public const long MaxVerificationGas = 1_50000000;
  • it also is limited in data available, for example it's not possible to get full signers list (hi, Add all signers to the Transaction when serializing with ToStackItem #2460), just the sender (which is a contract itself in our case) and neither it's possible to get witness scope even for sender

If we're talking about generic sponsoring for untrusted users contract will definitely need to parse the entry script to only allow some specific ones (identifying its user at the same time and checking some internal user-specific conditions) and it will definitely need to check witness scope. But it can run out of GAS easily (even if it fits into the limit it will burn some in the process) and it can't differentiate between None and Global scopes for Sender which is very dangerous. It also of course can't test-execute the transaction to see the effect of it (notice that even manual multisignature collection allows for that, every member of committee can test-execute proposed transaction before signing it).

We get to the situation where even though we have all basic building blocks for transaction sponsoring we can't simply use them and get the result we want, something is still missing. If the decision on whether transaction should be sponsored or not can't be made in verification context it can be offloaded to some additional service. A contract can trust some (multi)signature of contract-specific backend and that backend will decide whether it signs transaction or not. The problem is that dApp needs to build this non-trivial app-specific centralized backend to accept partially signed transactions (and avoid being flooded with them), check them in whatever fashion, add missing signature and send to the network.

It can be done, yet at the same time it's challenging for dApp developers and every dApp that wants to do this will have to build corresponding infrastructure.

Do you have any solution you want to propose?
The same sponsoring issue is easily solved by the Notary subsystem proposed in #1573 and implemented in #2425. It also relies on multiple signers, but it's more flexible, not just contract address can be used for sender role, but any other GAS-holding app-specific address can be used as well thereby removing the need for contract to hold GAS. The actual decision on whether a transaction should be sponsored or not is made off-chain by nodes monitoring P2PNotary pools (like with nspcc-dev/neo-go#1984). These nodes don't have any GAS restrictions, they don't burn it while doing checks, they have absolutely all transaction's data, they can easily test-execute it to see if side-effects this transaction produces are expected ones and they just add some missing signature to transaction thereby making it valid and acceptable for the chain.

Of course it still requires some app-specific backend, but this backend can be sure it won't be flooded with partially-signed transactions (notary subsystem is protected from this), it can rely on the fact that these transactions already passed notary-specific correctness checks and both backend and frontend will work with the standard protocol, with no need to invent anything for this particular purpose. So while notary subsystem was designed for a bit different purpose originally it solves sponsoring too and seems to be the only practical solution to this problem at the moment.

This issue is an open problem-specific discussion though, feel free to throw in some new practical ideas for transaction sponsoring, the network really needs this.

Neo Version

  • Neo 3

Where in the software does this update applies to?

  • P2P (TCP)
  • SDK
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
discussion Initial issue state - proposed but not yet accepted
Projects
None yet
Development

No branches or pull requests

1 participant