[`Add`] `GetRandom(MaxValue)` to StdLib Contract
Description
Adds two new methods to StdLib contract. Still uses syscall GetRandom for getting ranges. Updated GetRandom syscall to have 256 bit integer.
Methods
// A 256-bit unsigned integer that is greater than or equal to 0 and less than MaxValue.
BigInteger GetRandom(BigInteger maxValue);
Fixes #3817
Type of change
- [ ] Optimization (the change is only an optimization)
- [ ] Style (the change is only a code style for better maintenance or standard purpose)
- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] This change requires a documentation update
How Has This Been Tested?
- [x] Locally Unit Tested
Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented my code, particularly in hard-to-understand areas
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my feature works
- [x] New and existing unit tests pass locally with my changes
- [x] Any dependent changes have been merged and published in downstream modules
@shargon maybe you help me with
/_/tests/Neo.UnitTests/SmartContract/Native/UT_NativeContract.cs(256): error test failed: TestGenesisNativeState (32ms): Assert.AreEqual failed. Expected:
and setting up hardfork.
@shargon maybe you help me with
/_/tests/Neo.UnitTests/SmartContract/Native/UT_NativeContract.cs(256): error test failed: TestGenesisNativeState (32ms): Assert.AreEqual failed. Expected:and setting up hardfork.
I will do it
LGTM.
@roman-khimov Made it more spread and even in ranges.
A 256-bit unsigned integer that is greater than or equal to 0 and less than MaxValue.
Sample size 100 from GetRandom(100)
X is the iterations
Y is the random number
var snapshotCache = TestBlockchain.GetTestSnapshotCache();
using var script = new ScriptBuilder();
for (var i = 0; i < 100; i++)
script.EmitDynamicCall(NativeContract.StdLib.Hash, "getRandom", 100);
var tx = TransactionBuilder.CreateEmpty()
.Nonce((uint)Random.Shared.Next())
.Build();
using var engine = ApplicationEngine.Create(TriggerType.Application, tx, snapshotCache, settings: TestProtocolSettings.Default);
engine.LoadScript(script.ToArray());
Assert.AreEqual(VMState.HALT, engine.Execute());
ScottPlot.Plot myPlot = new();
// create sample data
var dataX = new double[100];
var dataY = new double[100];
for (var i = 0; i < 100; i++)
{
dataX[i] = i;
dataY[i] = (double)engine.ResultStack.Pop<Integer>().GetInteger();
}
// add a scatter plot to the plot
myPlot.Add.Scatter(dataX, dataY);
myPlot.SavePng("D:\\tmp\\demo.png", 400, 300);
@roman-khimov what you think?
@cschuchardt88 Just tried MaxValue as BigInteger.Pow(10, 10), the result always returns 0!
Even Pow(10, 5) is not working as well.
@superboyiii that is because the nonce is used in the system call get_random for the calculation. So the out come will be the same for the same amount of times you call it in a script. So for example the 1st time will be 0 and next time will be 5 and so on. The way to fix this is to update RpcServer or that method your calling and add a random nonce for the transaction. So again if the nonce is always the same the outcome will always be the same depending on the number of iterations you call the GetRandom(MaxValue) within a script.
@cschuchardt88 Seems ScriptBuilder EmitPush(BigInteger value) can't handle a big number such like POW(1000,100). Shall we add a limit(2^255 - 1) on maxvalue of getRandom() as well?
Test is completed. So GetRandom(BigInteger value) costs similar time with Itoa(BigInteger value) for each execution(0.1ms~0.2ms). So CpuFee = 1 << 12 is OK for GetRandom().
Test is completed. So GetRandom(BigInteger value) costs similar time with Itoa(BigInteger value) for each execution(0.1ms~0.2ms). So
CpuFee = 1 << 12is OK forGetRandom().
from your code, You need to add tx as a container in ApplicationEngine, so your test can be accurate.
Need @superboyiii to decide the price.
GetRandom syscall is 1<<13, btw. This one is something comparable.
Price 1<<13 is OK for me. Either 1<<12 or 1<<13 should make sense. Just wait for @AnnaShaleva 's final review.
Changed to use 128 bits. per @roman-khimov request.
@roman-khimov can you check again I fixed the stuff you were talking about.
@superboyiii you need to test Prices again for both GetRandom syscall and StdLib call. Both separately
@cschuchardt88
My test result:
So for
getRandom price, it's can still be the same price as itoa since they cost very similar execution time. 1 << 12 or 1 << 13 is OK.
For the random result, I find It always approaches the maxvalue. Is it random enough?
@superboyiii It uses rejection sampling to do fast random shuffling. The reason why is because it not truly random (the GetRandom syscall).
@roman-khimov @Wi1l-B0t Could you review again? My test result shows it's always approaches the maxvalue, for example: maxvalue = 2^200, result is always between 2^199 and 2^200. Does it work as expected?
@roman-khimov @Wi1l-B0t Could you review again? My test result shows it's always approaches the maxvalue, for example: maxvalue =
2^200, result is always between2^199and2^200. Does it work as expected?
@superboyiii
It has to do with the seed (the transaction nonce), its the bias and since GetRandom always returns a 256-bit integer it wont be truly random. But it will always be in range. Tests show the signal is spread evenly with range. The bigger max range the more possibilities.
GetRandom(20) with 1,000,000+ of data
As you can see it pretty evenly spread the numbers.
This version spread more evenly than previous one.
Still need some time, likely can be simplified a bit.
@roman-khimov