interop: implement System.Runtime.GetRandom
Our murmur3 implementation is architecture independent and optimized in assembly. Signed-off-by: Evgeniy Stratonikov <evgeniy@nspcc.ru>
This commit is contained in:
parent
fdb54f2dc3
commit
8077f9232d
11 changed files with 142 additions and 0 deletions
1
go.mod
1
go.mod
|
@ -17,6 +17,7 @@ require (
|
|||
github.com/prometheus/client_golang v1.2.1
|
||||
github.com/stretchr/testify v1.6.1
|
||||
github.com/syndtr/goleveldb v0.0.0-20180307113352-169b1b37be73
|
||||
github.com/twmb/murmur3 v1.1.5
|
||||
github.com/urfave/cli v1.20.0
|
||||
github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74
|
||||
go.etcd.io/bbolt v1.3.4
|
||||
|
|
2
go.sum
2
go.sum
|
@ -228,6 +228,8 @@ github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd
|
|||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/syndtr/goleveldb v0.0.0-20180307113352-169b1b37be73 h1:I2drr5K0tykBofr74ZEGliE/Hf6fNkEbcPyFvsy7wZk=
|
||||
github.com/syndtr/goleveldb v0.0.0-20180307113352-169b1b37be73/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
|
||||
github.com/twmb/murmur3 v1.1.5 h1:i9OLS9fkuLzBXjt6dptlAEyk58fJsSTXbRg3SgVyqgk=
|
||||
github.com/twmb/murmur3 v1.1.5/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
|
|
|
@ -76,6 +76,7 @@ func TestSyscallExecution(t *testing.T) {
|
|||
"runtime.GetInvocationCounter": {interopnames.SystemRuntimeGetInvocationCounter, nil, false},
|
||||
"runtime.GetNetwork": {interopnames.SystemRuntimeGetNetwork, nil, false},
|
||||
"runtime.GetNotifications": {interopnames.SystemRuntimeGetNotifications, []string{u160}, false},
|
||||
"runtime.GetRandom": {interopnames.SystemRuntimeGetRandom, nil, false},
|
||||
"runtime.GetScriptContainer": {interopnames.SystemRuntimeGetScriptContainer, nil, false},
|
||||
"runtime.GetTime": {interopnames.SystemRuntimeGetTime, nil, false},
|
||||
"runtime.GetTrigger": {interopnames.SystemRuntimeGetTrigger, nil, false},
|
||||
|
|
|
@ -1908,6 +1908,7 @@ func (bc *Blockchain) newInteropContext(trigger trigger.Type, d dao.DAO, block *
|
|||
case block != nil:
|
||||
ic.Container = block
|
||||
}
|
||||
ic.InitNonceData()
|
||||
return ic
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package interop
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
@ -39,6 +40,7 @@ type Context struct {
|
|||
Natives []Contract
|
||||
Trigger trigger.Type
|
||||
Block *block.Block
|
||||
NonceData [16]byte
|
||||
Tx *transaction.Transaction
|
||||
DAO *dao.Cached
|
||||
Notifications []state.NotificationEvent
|
||||
|
@ -70,6 +72,18 @@ func NewContext(trigger trigger.Type, bc blockchainer.Blockchainer, d dao.DAO,
|
|||
}
|
||||
}
|
||||
|
||||
// InitNonceData initializes nonce to be used in `GetRandom` calculations.
|
||||
func (ic *Context) InitNonceData() {
|
||||
if tx, ok := ic.Container.(*transaction.Transaction); ok {
|
||||
copy(ic.NonceData[:], tx.Hash().BytesBE())
|
||||
}
|
||||
if ic.Block != nil {
|
||||
nonce := ic.Block.Nonce
|
||||
nonce ^= binary.LittleEndian.Uint64(ic.NonceData[:])
|
||||
binary.LittleEndian.PutUint64(ic.NonceData[:], nonce)
|
||||
}
|
||||
}
|
||||
|
||||
// Function binds function name, id with the function itself and price,
|
||||
// it's supposed to be inited once for all interopContexts, so it doesn't use
|
||||
// vm.InteropFuncPrice directly.
|
||||
|
|
|
@ -26,6 +26,7 @@ const (
|
|||
SystemRuntimeGetInvocationCounter = "System.Runtime.GetInvocationCounter"
|
||||
SystemRuntimeGetNetwork = "System.Runtime.GetNetwork"
|
||||
SystemRuntimeGetNotifications = "System.Runtime.GetNotifications"
|
||||
SystemRuntimeGetRandom = "System.Runtime.GetRandom"
|
||||
SystemRuntimeGetScriptContainer = "System.Runtime.GetScriptContainer"
|
||||
SystemRuntimeGetTime = "System.Runtime.GetTime"
|
||||
SystemRuntimeGetTrigger = "System.Runtime.GetTrigger"
|
||||
|
@ -64,6 +65,7 @@ var names = []string{
|
|||
SystemRuntimeGetInvocationCounter,
|
||||
SystemRuntimeGetNetwork,
|
||||
SystemRuntimeGetNotifications,
|
||||
SystemRuntimeGetRandom,
|
||||
SystemRuntimeGetScriptContainer,
|
||||
SystemRuntimeGetTime,
|
||||
SystemRuntimeGetTrigger,
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
package runtime
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"github.com/twmb/murmur3"
|
||||
)
|
||||
|
||||
// GasLeft returns remaining amount of GAS.
|
||||
|
@ -74,3 +77,19 @@ func GetNetwork(ic *interop.Context) error {
|
|||
ic.VM.Estack().PushVal(uint32(m))
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetRandom returns pseudo-random number which depends on block nonce and transaction hash.
|
||||
func GetRandom(ic *interop.Context) error {
|
||||
res := murmur128(ic.NonceData[:], ic.Network)
|
||||
ic.VM.Estack().PushVal(bigint.FromBytesUnsigned(res))
|
||||
copy(ic.NonceData[:], res)
|
||||
return nil
|
||||
}
|
||||
|
||||
func murmur128(data []byte, seed uint32) []byte {
|
||||
h1, h2 := murmur3.SeedSum128(uint64(seed), uint64(seed), data)
|
||||
result := make([]byte, 16)
|
||||
binary.LittleEndian.PutUint64(result, h1)
|
||||
binary.LittleEndian.PutUint64(result[8:], h2)
|
||||
return result
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package runtime
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/internal/random"
|
||||
|
@ -114,3 +115,20 @@ func TestRuntimeGetInvocationCounter(t *testing.T) {
|
|||
checkStack(t, ic.VM, 42)
|
||||
})
|
||||
}
|
||||
|
||||
// Test compatibility with C# implementation.
|
||||
// https://github.com/neo-project/neo/blob/master/tests/neo.UnitTests/Cryptography/UT_Murmur128.cs
|
||||
func TestMurmurCompat(t *testing.T) {
|
||||
res := murmur128([]byte("hello"), 123)
|
||||
require.Equal(t, "0bc59d0ad25fde2982ed65af61227a0e", hex.EncodeToString(res))
|
||||
|
||||
res = murmur128([]byte("world"), 123)
|
||||
require.Equal(t, "3d3810fed480472bd214a14023bb407f", hex.EncodeToString(res))
|
||||
|
||||
res = murmur128([]byte("hello world"), 123)
|
||||
require.Equal(t, "e0a0632d4f51302c55e3b3e48d28795d", hex.EncodeToString(res))
|
||||
|
||||
bs, _ := hex.DecodeString("718f952132679baa9c5c2aa0d329fd2a")
|
||||
res = murmur128(bs, 123)
|
||||
require.Equal(t, "9b4aa747ff0cf4e41b3d96251551c8ae", hex.EncodeToString(res))
|
||||
}
|
||||
|
|
|
@ -8,6 +8,9 @@ import (
|
|||
|
||||
"github.com/nspcc-dev/neo-go/internal/random"
|
||||
"github.com/nspcc-dev/neo-go/internal/testchain"
|
||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/contract"
|
||||
|
@ -35,6 +38,79 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Tests are taken from
|
||||
// https://github.com/neo-project/neo/blob/master/tests/neo.UnitTests/SmartContract/UT_ApplicationEngine.Runtime.cs
|
||||
func TestRuntimeGetRandomCompatibility(t *testing.T) {
|
||||
bc := newTestChain(t)
|
||||
|
||||
b := getSharpTestGenesis(t)
|
||||
tx := getSharpTestTx(util.Uint160{})
|
||||
ic := bc.newInteropContext(trigger.Application, dao.NewCached(bc.dao), b, tx)
|
||||
ic.Network = uint32(netmode.MainNet)
|
||||
|
||||
ic.VM = vm.New()
|
||||
ic.VM.LoadScript([]byte{0x01})
|
||||
|
||||
require.NoError(t, runtime.GetRandom(ic))
|
||||
require.Equal(t, "225932872514876835587448704843370203748", ic.VM.Estack().Pop().BigInt().String())
|
||||
|
||||
require.NoError(t, runtime.GetRandom(ic))
|
||||
require.Equal(t, "190129535548110356450238097068474508661", ic.VM.Estack().Pop().BigInt().String())
|
||||
|
||||
require.NoError(t, runtime.GetRandom(ic))
|
||||
require.Equal(t, "48930406787011198493485648810190184269", ic.VM.Estack().Pop().BigInt().String())
|
||||
|
||||
require.NoError(t, runtime.GetRandom(ic))
|
||||
require.Equal(t, "66199389469641263539889463157823839112", ic.VM.Estack().Pop().BigInt().String())
|
||||
|
||||
require.NoError(t, runtime.GetRandom(ic))
|
||||
require.Equal(t, "217172703763162599519098299724476526911", ic.VM.Estack().Pop().BigInt().String())
|
||||
}
|
||||
|
||||
func TestRuntimeGetRandomDifferentTransactions(t *testing.T) {
|
||||
bc := newTestChain(t)
|
||||
b, _ := bc.GetBlock(bc.GetHeaderHash(0))
|
||||
|
||||
tx1 := transaction.New([]byte{byte(opcode.PUSH1)}, 0)
|
||||
ic1 := bc.newInteropContext(trigger.Application, dao.NewCached(bc.dao), b, tx1)
|
||||
ic1.VM = vm.New()
|
||||
ic1.VM.LoadScript(tx1.Script)
|
||||
|
||||
tx2 := transaction.New([]byte{byte(opcode.PUSH2)}, 0)
|
||||
ic2 := bc.newInteropContext(trigger.Application, dao.NewCached(bc.dao), b, tx2)
|
||||
ic2.VM = vm.New()
|
||||
ic2.VM.LoadScript(tx2.Script)
|
||||
|
||||
require.NoError(t, runtime.GetRandom(ic1))
|
||||
require.NoError(t, runtime.GetRandom(ic2))
|
||||
require.NotEqual(t, ic1.VM.Estack().Pop().BigInt(), ic2.VM.Estack().Pop().BigInt())
|
||||
|
||||
require.NoError(t, runtime.GetRandom(ic1))
|
||||
require.NoError(t, runtime.GetRandom(ic2))
|
||||
require.NotEqual(t, ic1.VM.Estack().Pop().BigInt(), ic2.VM.Estack().Pop().BigInt())
|
||||
}
|
||||
|
||||
func getSharpTestTx(sender util.Uint160) *transaction.Transaction {
|
||||
tx := transaction.New([]byte{byte(opcode.PUSH2)}, 0)
|
||||
tx.Nonce = 0
|
||||
tx.Signers = append(tx.Signers, transaction.Signer{
|
||||
Account: sender,
|
||||
Scopes: transaction.CalledByEntry,
|
||||
})
|
||||
tx.Scripts = append(tx.Scripts, transaction.Witness{})
|
||||
return tx
|
||||
}
|
||||
|
||||
func getSharpTestGenesis(t *testing.T) *block.Block {
|
||||
const configPath = "../../config"
|
||||
|
||||
cfg, err := config.Load(configPath, netmode.MainNet)
|
||||
require.NoError(t, err)
|
||||
b, err := createGenesisBlock(cfg.ProtocolConfiguration)
|
||||
require.NoError(t, err)
|
||||
return b
|
||||
}
|
||||
|
||||
func TestContractCreateAccount(t *testing.T) {
|
||||
v, ic, _ := createVM(t)
|
||||
t.Run("Good", func(t *testing.T) {
|
||||
|
|
|
@ -52,6 +52,7 @@ var systemInterops = []interop.Function{
|
|||
{Name: interopnames.SystemRuntimeGetInvocationCounter, Func: runtime.GetInvocationCounter, Price: 1 << 4},
|
||||
{Name: interopnames.SystemRuntimeGetNetwork, Func: runtime.GetNetwork, Price: 1 << 3},
|
||||
{Name: interopnames.SystemRuntimeGetNotifications, Func: runtime.GetNotifications, Price: 1 << 8, ParamCount: 1},
|
||||
{Name: interopnames.SystemRuntimeGetRandom, Func: runtime.GetRandom, Price: 1 << 4},
|
||||
{Name: interopnames.SystemRuntimeGetScriptContainer, Func: engineGetScriptContainer, Price: 1 << 3},
|
||||
{Name: interopnames.SystemRuntimeGetTime, Func: runtime.GetTime, Price: 1 << 3, RequiredFlags: callflag.ReadStates},
|
||||
{Name: interopnames.SystemRuntimeGetTrigger, Func: runtime.GetTrigger, Price: 1 << 3},
|
||||
|
|
|
@ -96,3 +96,10 @@ func GetInvocationCounter() int {
|
|||
func Platform() []byte {
|
||||
return neogointernal.Syscall0("System.Runtime.Platform").([]byte)
|
||||
}
|
||||
|
||||
// GetRandom returns pseudo-random number which depends on block nonce and tx hash.
|
||||
// Each invocation will return a different number. This function uses
|
||||
// `System.Runtime.GetRandom` syscall.
|
||||
func GetRandom() int {
|
||||
return neogointernal.Syscall0("System.Runtime.GetRandom").(int)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue