We can have very deep reference types and attempt to JSONize them can easily
lead to OOM (even though there are no recursive references inside). Therefore
we have to limit them. While regular ToJSON() is buffer size limited to
MaxSize, ToJSONWithTypes is not and limiting it to MaxSize output will require
substantial rewriting effort while not really providing fair result, MaxSize
is more about stack item size while its JSON representation can be much bigger
because of various overheads.
Initial thought was to limit it by element count based on
MaxIteratorResultItems, but the problem here is that we don't always have this
limit in contexts using ToJSONWithTypes (like notification event
marshaling). Thus we need some generic limit which would be fine for all
users.
We at the same time have maxJSONDepth used when deserializing from JSON, so
it can be used for marshaling as well, it's not often that we have deeper
structures in real results.
Inspired by neo-project/neo#2521.
If we're done with element it no longer can lead to recursion error, so fix
cases like `[arr, arr]` where we have two copies of `arr` trigger this error
for no good reason (there is no recursion there).
This commit fixes the following Go vs C# state diff:
block 74613: value mismatch for key EwAAAHN0cmVhbXMvDg==: eyJpZCI6MTQsInN0YXJ0IjoxNjIyNTAwMjAwMDAwLCJzdG9wIjoxNjIyNTAyMDAwMDAwLCJkZXBvc2l0IjoxMDAwMDAwMDAsInJlbWFpbmluZyI6MTAwMDAwMDAwLCJzZW5kZXIiOiJmeEY4RDl2ZFU3K0gwcDV3NTlyWllMNytNSlE9IiwicmVjaXBpZW50IjoiSVV6c3pveFV0S1NGVnlZRGczSmdTQTFlbTFNPSJ9 vs eyJpZCI6MTQsInN0YXJ0IjoxNjIyNTAwMjAwMDAwLCJzdG9wIjoxNjIyNTAyMDAwMDAwLCJkZXBvc2l0IjoxMDAwMDAwMDAsInJlbWFpbmluZyI6MTAwMDAwMDAwLCJzZW5kZXIiOiJmeEY4RDl2ZFU3XHUwMDJCSDBwNXc1OXJaWUw3XHUwMDJCTUpRPSIsInJlY2lwaWVudCI6IklVenN6b3hVdEtTRlZ5WURnM0pnU0ExZW0xTT0ifQ==
I.e.:
```
{"id":14,"start":1622500200000,"stop":1622502000000,"deposit":100000000,"remaining":100000000,"sender":"fxF8D9vdU7+H0p5w59rZYL7+MJQ=","recipient":"IUzszoxUtKSFVyYDg3JgSA1em1M="}
```
vs
```
{"id":14,"start":1622500200000,"stop":1622502000000,"deposit":100000000,"remaining":100000000,"sender":"fxF8D9vdU7\u002BH0p5w59rZYL7\u002BMJQ=","recipient":"IUzszoxUtKSFVyYDg3JgSA1em1M="}
```
The result is the same HALT state, but the exception message is the real
one got from user. Changes ported from C#:
1. Throw exception: 59b8ac73d2/src/neo-vm/ExecutionEngine.cs (L1448)
2. Prettify message: https://github.com/neo-project/neo-vm/blob/master/src/neo-vm/VMUnhandledException.cs#L28
The result is that instead of
```
2021-03-31T17:02:54.508+0300 WARN contract invocation failed {"tx": "2aefeb705f3a609df8767d9b45e036b9dd1eb77407e5732375981915668889b8", "block": 30640, "error": "error encountered at instruction 970 (THROW): runtime error: invalid memory address or nil pointer dereference"}
```
we'll get
```
2021-03-31T17:33:56.299+0300 WARN contract invocation failed {"tx": "2aefeb705f3a609df8767d9b45e036b9dd1eb77407e5732375981915668889b8", "block": 30640, "error": "error encountered at instruction 970 (THROW): unhandled exception: No authorization."}
```
in the node logs.
We have additional logic for getting BaseExecFee policy value. This
logic should be moved to interop context instead of being in Policer,
because Policer is just an interface over Policy contract.
After moving this logic to interop context, we need to use it to define
BaseExecFee instead of (Policer).BaseExecFee. Thus, moving
(*Blockchain).GetPrice to (*Context).GetPrice is necessary.
This optimizes out DB access for non-deployed contracts under the assumption
that deployed ones are always loaded via `LoadScriptWithHash` (and if they're
not --- it's a bug anyway with the new hashing model) which actually is a very
popular case (every entry script does that).
When native method calls other contract result should be put
on the stack of current context. With oracles this problem wasn't
noticed because of void return type.
At the moment we should have 3 possible options to check return state
during vm context unloading:
* no check
* ensure the stack is empty
* ensure the stack is not empty
It is necessary to distinguish them because new _deploy method shouldn't
left anything on stack. Example: if we use _deploy method before some
ordinary contract method which returns one value. Without these changes
the contract invocation will fail due to 2 elements on stack left after
invocation (the first `null` element is from _deploy, the second element
is return-value from the ordinary contract method).
Most often we only need to read them and it doesn't require copying. Make an
explicit copy (and copy only things we need!) where needed.
After the recent neo-vm tests update our vm package testing time jumped to
~12s, with this change it's now more like ~8s.
Inspired by neo-project/neo-vm#352. We can't directly compare slices, so we're
better optimize things we already have. At the same time this code would
behave a bit different if A is to call B and then B is call A and then some
pointer from the first A invocation is to be compared with a pointer from the
second A invocation. Not sure it really matters.