neo icon indicating copy to clipboard operation
neo copied to clipboard

[`Add`] `GetRandom(MaxValue)` to StdLib Contract

Open cschuchardt88 opened this issue 9 months ago • 24 comments

Description

Adds two new methods to StdLib contract. Still uses syscall GetRandom for getting ranges. Updated GetRandom syscall to have 256 bit integer.

image

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

cschuchardt88 avatar May 31 '25 03:05 cschuchardt88

@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.

cschuchardt88 avatar May 31 '25 03:05 cschuchardt88

@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

shargon avatar May 31 '25 06:05 shargon

LGTM.

Wi1l-B0t avatar Jun 29 '25 12:06 Wi1l-B0t

@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.

cschuchardt88 avatar Jul 08 '25 03:07 cschuchardt88

demo

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 avatar Jul 14 '25 03:07 cschuchardt88

@cschuchardt88 Just tried MaxValue as BigInteger.Pow(10, 10), the result always returns 0!

superboyiii avatar Jul 30 '25 04:07 superboyiii

Even Pow(10, 5) is not working as well. 1753849975058

superboyiii avatar Jul 30 '25 04:07 superboyiii

@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 avatar Jul 30 '25 05:07 cschuchardt88

@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?

superboyiii avatar Jul 30 '25 06:07 superboyiii

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(). 1753859341373

superboyiii avatar Jul 30 '25 07:07 superboyiii

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().

from your code, You need to add tx as a container in ApplicationEngine, so your test can be accurate.

cschuchardt88 avatar Jul 31 '25 07:07 cschuchardt88

Need @superboyiii to decide the price.

Jim8y avatar Jul 31 '25 13:07 Jim8y

GetRandom syscall is 1<<13, btw. This one is something comparable.

roman-khimov avatar Jul 31 '25 13:07 roman-khimov

Price 1<<13 is OK for me. Either 1<<12 or 1<<13 should make sense. Just wait for @AnnaShaleva 's final review.

superboyiii avatar Aug 04 '25 09:08 superboyiii

Changed to use 128 bits. per @roman-khimov request.

image demo

cschuchardt88 avatar Aug 22 '25 05:08 cschuchardt88

@roman-khimov can you check again I fixed the stuff you were talking about.

cschuchardt88 avatar Sep 01 '25 04:09 cschuchardt88

@superboyiii you need to test Prices again for both GetRandom syscall and StdLib call. Both separately

cschuchardt88 avatar Sep 06 '25 10:09 cschuchardt88

@cschuchardt88 My test result: 28160889bc86ad7b977061fa677f557c 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? e6ca2109333a4abaa52cc9e6bb8c1230

superboyiii avatar Sep 08 '25 07:09 superboyiii

@superboyiii It uses rejection sampling to do fast random shuffling. The reason why is because it not truly random (the GetRandom syscall).

cschuchardt88 avatar Sep 08 '25 09:09 cschuchardt88

@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?

superboyiii avatar Sep 10 '25 02:09 superboyiii

@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?

@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

image

As you can see it pretty evenly spread the numbers.

cschuchardt88 avatar Sep 10 '25 09:09 cschuchardt88

This version spread more evenly than previous one. image image

superboyiii avatar Sep 11 '25 06:09 superboyiii

Still need some time, likely can be simplified a bit.

@roman-khimov image

cschuchardt88 avatar Sep 25 '25 00:09 cschuchardt88