From bb29b970cf211535cf76eee0217dc8de7dcf88bc Mon Sep 17 00:00:00 2001 From: Meng-Ying Yang Date: Mon, 15 Apr 2019 11:33:15 +0800 Subject: core: vm: sqlvm: add built-in function BLOCK_HASH() --- core/vm/sqlvm/common/decimal/decimal.go | 2 + core/vm/sqlvm/runtime/functions.go | 67 ++++++++++++++++++++++- core/vm/sqlvm/runtime/functions_test.go | 94 +++++++++++++++++++++++++++++++++ core/vm/sqlvm/runtime/instructions.go | 11 ++++ 4 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 core/vm/sqlvm/runtime/functions_test.go (limited to 'core') diff --git a/core/vm/sqlvm/common/decimal/decimal.go b/core/vm/sqlvm/common/decimal/decimal.go index 390f440e3..ff45ec327 100644 --- a/core/vm/sqlvm/common/decimal/decimal.go +++ b/core/vm/sqlvm/common/decimal/decimal.go @@ -17,6 +17,8 @@ var ( MaxUint16 = decimal.New(math.MaxUint16, 0) MaxUint64 = decimal.RequireFromString(fmt.Sprint(uint64(math.MaxUint64))) + + Dec257 = decimal.New(257, 0) ) // Val2Bool convert value to boolean definition. diff --git a/core/vm/sqlvm/runtime/functions.go b/core/vm/sqlvm/runtime/functions.go index 798f5a573..74fa50fc1 100644 --- a/core/vm/sqlvm/runtime/functions.go +++ b/core/vm/sqlvm/runtime/functions.go @@ -1,11 +1,76 @@ package runtime import ( + "github.com/dexon-foundation/decimal" + + "github.com/dexon-foundation/dexon/core/vm/sqlvm/ast" "github.com/dexon-foundation/dexon/core/vm/sqlvm/common" + dec "github.com/dexon-foundation/dexon/core/vm/sqlvm/common/decimal" + se "github.com/dexon-foundation/dexon/core/vm/sqlvm/errors" +) + +// function identifier +const ( + BLOCKHASH = "BLOCK_HASH" ) type fn func(*common.Context, []*Operand, uint64) (*Operand, error) var ( - fnTable = map[string]fn{} + fnTable = map[string]fn{ + BLOCKHASH: fnBlockHash, + } ) + +func assignFuncResult(meta []ast.DataType, fn func() *Raw, length uint64) (result *Operand) { + result = &Operand{Meta: meta, Data: make([]Tuple, length)} + for i := uint64(0); i < length; i++ { + result.Data[i] = Tuple{fn()} + } + return +} + +func evalBlockHash(ctx *common.Context, num, cur decimal.Decimal) (r *Raw, err error) { + r = &Raw{Bytes: make([]byte, 32)} + + cNum := cur.Sub(dec.Dec257) + if num.Cmp(cNum) > 0 && num.Cmp(cur) < 0 { + var num64 uint64 + num64, err = ast.DecimalToUint64(num) + if err != nil { + return + } + r.Bytes = ctx.GetHash(num64).Bytes() + } + return +} + +func fnBlockHash(ctx *common.Context, ops []*Operand, length uint64) (result *Operand, err error) { + if len(ops) != 1 { + err = se.ErrorCodeInvalidOperandNum + return + } + + meta := []ast.DataType{ast.ComposeDataType(ast.DataTypeMajorFixedBytes, 3)} + cNum := decimal.NewFromBigInt(ctx.BlockNumber, 0) + + if ops[0].IsImmediate { + var r *Raw + r, err = evalBlockHash(ctx, ops[0].Data[0][0].Value, cNum) + if err != nil { + return + } + result = assignFuncResult(meta, r.clone, length) + } else { + result = &Operand{Meta: meta, Data: make([]Tuple, length)} + for i := uint64(0); i < length; i++ { + var r *Raw + r, err = evalBlockHash(ctx, ops[0].Data[i][0].Value, cNum) + if err != nil { + return + } + result.Data[i] = Tuple{r} + } + } + return +} diff --git a/core/vm/sqlvm/runtime/functions_test.go b/core/vm/sqlvm/runtime/functions_test.go new file mode 100644 index 000000000..fe8fa0791 --- /dev/null +++ b/core/vm/sqlvm/runtime/functions_test.go @@ -0,0 +1,94 @@ +package runtime + +import ( + "math/big" + "testing" + + "github.com/dexon-foundation/decimal" + "github.com/stretchr/testify/suite" + + dexCommon "github.com/dexon-foundation/dexon/common" + "github.com/dexon-foundation/dexon/core/vm" + "github.com/dexon-foundation/dexon/core/vm/sqlvm/ast" + "github.com/dexon-foundation/dexon/core/vm/sqlvm/common" +) + +func TestFunction(t *testing.T) { + suite.Run(t, new(FunctionSuite)) +} + +type FunctionSuite struct { + suite.Suite +} + +var ( + hash1 = dexCommon.BigToHash(big.NewInt(1)) + hash255 = dexCommon.BigToHash(big.NewInt(255)) +) + +var mockNumberHashTable = map[uint64]dexCommon.Hash{1: hash1, 255: hash255} + +func mockGetHashFunc(u uint64) dexCommon.Hash { return mockNumberHashTable[u] } + +func (s *FunctionSuite) TestFnBlockHash() { + type blockHashCase struct { + Name string + Ops []*Operand + Length uint64 + Res [][]byte + Cur *big.Int + Err error + } + + testcases := []blockHashCase{ + {"Immediate OP", []*Operand{ + {IsImmediate: true, Meta: nil, Data: []Tuple{{&Raw{Value: decimal.New(1, 0)}}}}, + }, 2, [][]byte{hash1.Bytes(), hash1.Bytes()}, big.NewInt(255), nil}, + {"OP", []*Operand{ + {IsImmediate: false, Meta: nil, Data: []Tuple{ + {&Raw{Value: decimal.New(255, 0)}}, + {&Raw{Value: decimal.New(515, 0)}}, + }}, + }, 2, [][]byte{hash255.Bytes(), make([]byte, 32)}, big.NewInt(256), nil}, + {"Older than 257 block", []*Operand{ + {IsImmediate: false, Meta: nil, Data: []Tuple{ + {&Raw{Value: decimal.New(1, 0)}}, + }}, + }, 1, [][]byte{make([]byte, 32)}, big.NewInt(512), nil}, + } + + callFn := func(c blockHashCase) (*Operand, error) { + return fnBlockHash( + &common.Context{ + Context: vm.Context{ + GetHash: mockGetHashFunc, + BlockNumber: c.Cur, + }, + }, + c.Ops, + c.Length, + ) + } + + meta := []ast.DataType{ast.ComposeDataType(ast.DataTypeMajorFixedBytes, 3)} + + for idx, tCase := range testcases { + r, err := callFn(tCase) + s.Require().Equal( + tCase.Err, err, + "Index: %v. Error not expected: %v != %v", idx, tCase.Err, err) + s.Require().Equal( + meta, r.Meta, + "Index: %v. Meta not equal: %v != %v", idx, meta, r.Meta) + s.Require().Equal( + uint64(len(r.Data)), tCase.Length, + "Index: %v. Length not equal: %v != %v", idx, len(r.Data), tCase.Length) + + for i := 0; i < len(r.Data); i++ { + s.Require().Equal( + tCase.Res[i], r.Data[i][0].Bytes, + "TestCase Index: %v. Data Index: %v. Value not equal: %v != %v", + idx, i, tCase.Res[i], r.Data[i][0].Bytes) + } + } +} diff --git a/core/vm/sqlvm/runtime/instructions.go b/core/vm/sqlvm/runtime/instructions.go index cd034955a..013c700f8 100644 --- a/core/vm/sqlvm/runtime/instructions.go +++ b/core/vm/sqlvm/runtime/instructions.go @@ -56,6 +56,17 @@ func (r *Raw) String() string { func (r *Raw) isTrue() bool { return dec.IsTrue(r.Value) } func (r *Raw) isFalse() bool { return dec.IsFalse(r.Value) } +func (r *Raw) clone() *Raw { + r2 := &Raw{} + if len(r.Bytes) != 0 { + r2.Bytes = make([]byte, len(r.Bytes)) + copy(r2.Bytes, r.Bytes) + } else { + r2.Value = r.Value + } + return r2 +} + // Tuple is collection of Raw. type Tuple []*Raw -- cgit v1.2.3