aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJhih-Ming Huang <jm.huang@cobinhood.com>2019-03-15 17:47:44 +0800
committerJhih-Ming Huang <jm.huang@cobinhood.com>2019-05-06 10:44:04 +0800
commita862c0b4300df0303d08c0be495ff13a929ce299 (patch)
tree2383d9239db9fb0ac3223d288add61ec4333e985
parent1bc4caded6333381ef4646c94f462abd8db238bd (diff)
downloaddexon-a862c0b4300df0303d08c0be495ff13a929ce299.tar.gz
dexon-a862c0b4300df0303d08c0be495ff13a929ce299.tar.zst
dexon-a862c0b4300df0303d08c0be495ff13a929ce299.zip
core: vm: sqlvm: runtime: implement opLoad
-rw-r--r--core/vm/sqlvm/runtime/instructions.go109
-rw-r--r--core/vm/sqlvm/runtime/instructions_test.go409
2 files changed, 510 insertions, 8 deletions
diff --git a/core/vm/sqlvm/runtime/instructions.go b/core/vm/sqlvm/runtime/instructions.go
index 668da692c..5a61d5f80 100644
--- a/core/vm/sqlvm/runtime/instructions.go
+++ b/core/vm/sqlvm/runtime/instructions.go
@@ -2,11 +2,14 @@ package runtime
import (
"fmt"
- "math/big"
"strings"
+ "github.com/shopspring/decimal"
+
+ dexCommon "github.com/dexon-foundation/dexon/common"
"github.com/dexon-foundation/dexon/core/vm/sqlvm/ast"
"github.com/dexon-foundation/dexon/core/vm/sqlvm/common"
+ "github.com/dexon-foundation/dexon/core/vm/sqlvm/errors"
)
var tupleJoin = "|"
@@ -27,17 +30,12 @@ type Instruction struct {
// Raw with embedded big.Int value or byte slice which represents the real value
// of basic operand unit.
type Raw struct {
- MajorType ast.DataTypeMajor
- MinorType ast.DataTypeMinor
-
- Value *big.Int
+ Value decimal.Decimal
Bytes []byte
}
func (r *Raw) String() string {
- return fmt.Sprintf(
- "MajorType: %v, MinorType: %v, Value: %v, Bytes :%v",
- r.MajorType, r.MinorType, r.Value, r.Bytes)
+ return fmt.Sprintf("Value: %v, Bytes: %v", r.Value, r.Bytes)
}
// Tuple is collection of Raw.
@@ -59,3 +57,98 @@ type Operand struct {
Data []Tuple
RegisterIndex uint
}
+
+func (o *Operand) toUint64() []uint64 {
+ result := make([]uint64, len(o.Data))
+ for i, tuple := range o.Data {
+ result[i] = uint64(tuple[0].Value.IntPart())
+ }
+ return result
+}
+
+func (o *Operand) toUint8() []uint8 {
+ result := make([]uint8, len(o.Data))
+ for i, tuple := range o.Data {
+ result[i] = uint8(tuple[0].Value.IntPart())
+ }
+ return result
+}
+
+func opLoad(ctx *common.Context, input []*Operand, registers []*Operand, output int) error {
+ tableIdx := input[0].Data[0][0].Value.IntPart()
+ if tableIdx >= int64(len(ctx.Storage.Schema)) {
+ return errors.ErrorCodeIndexOutOfRange
+ }
+ table := ctx.Storage.Schema[tableIdx]
+
+ ids := input[1].toUint64()
+ fields := input[2].toUint8()
+ op := Operand{
+ IsImmediate: false,
+ Data: make([]Tuple, len(ids)),
+ RegisterIndex: 0,
+ }
+ for i := range op.Data {
+ op.Data[i] = make([]*Raw, len(fields))
+ }
+ meta, err := table.GetFieldType(fields)
+ if err != nil {
+ return err
+ }
+ op.Meta = meta
+ for i, id := range ids {
+ slotDataCache := make(map[dexCommon.Hash]dexCommon.Hash)
+ head := ctx.Storage.GetPrimaryKeyHash(table.Name, id)
+ for j := range fields {
+ col := table.Columns[int(fields[j])]
+ byteOffset := col.ByteOffset
+ slotOffset := col.SlotOffset
+ dt := meta[j]
+ size := dt.Size()
+ slot := ctx.Storage.ShiftHashUint64(head, uint64(slotOffset))
+ slotData := getSlotData(ctx, slot, slotDataCache)
+ bytes := slotData.Bytes()[byteOffset : byteOffset+size]
+ op.Data[i][j], err = decode(ctx, dt, slot, bytes)
+ if err != nil {
+ return err
+ }
+ }
+ }
+ registers[output] = &op
+ return nil
+}
+
+func getSlotData(ctx *common.Context, slot dexCommon.Hash,
+ cache map[dexCommon.Hash]dexCommon.Hash) dexCommon.Hash {
+ if d, exist := cache[slot]; exist {
+ return d
+ }
+ cache[slot] = ctx.Storage.GetState(ctx.Contract.Address(), slot)
+ return cache[slot]
+}
+
+// decode byte data to Raw format
+func decode(ctx *common.Context, dt ast.DataType, slot dexCommon.Hash, bytes []byte) (*Raw, error) {
+ rVal := &Raw{}
+ major, _ := ast.DecomposeDataType(dt)
+ switch major {
+ case ast.DataTypeMajorDynamicBytes:
+ rVal.Bytes = ctx.Storage.DecodeDByteBySlot(ctx.Contract.Address(), slot)
+ case ast.DataTypeMajorFixedBytes, ast.DataTypeMajorBool,
+ ast.DataTypeMajorAddress, ast.DataTypeMajorInt,
+ ast.DataTypeMajorUint:
+ d, err := ast.DecimalDecode(dt, bytes)
+ if err != nil {
+ return nil, err
+ }
+ rVal.Value = d
+ }
+ if major.IsFixedRange() || major.IsUfixedRange() {
+ d, err := ast.DecimalDecode(dt, bytes)
+ if err != nil {
+ return nil, err
+ }
+ rVal.Value = d
+ }
+ return rVal, nil
+}
diff --git a/core/vm/sqlvm/runtime/instructions_test.go b/core/vm/sqlvm/runtime/instructions_test.go
new file mode 100644
index 000000000..b316943bb
--- /dev/null
+++ b/core/vm/sqlvm/runtime/instructions_test.go
@@ -0,0 +1,409 @@
+package runtime
+
+import (
+ "encoding/hex"
+ "math/big"
+ "reflect"
+ "testing"
+
+ "github.com/shopspring/decimal"
+ "github.com/stretchr/testify/suite"
+
+ dexCommon "github.com/dexon-foundation/dexon/common"
+ "github.com/dexon-foundation/dexon/core/state"
+ "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"
+ "github.com/dexon-foundation/dexon/core/vm/sqlvm/errors"
+ "github.com/dexon-foundation/dexon/core/vm/sqlvm/schema"
+ "github.com/dexon-foundation/dexon/crypto"
+ "github.com/dexon-foundation/dexon/ethdb"
+)
+
+type opLoadSuite struct {
+ suite.Suite
+ ctx *common.Context
+ headHash dexCommon.Hash
+ address dexCommon.Address
+ slotHash []dexCommon.Hash
+ raws []*raw
+}
+
+type raw struct {
+ Raw
+ slotShift uint8
+ byteShift uint8
+ major ast.DataTypeMajor
+ minor ast.DataTypeMinor
+}
+
+func createSchema(storage *common.Storage, raws []*raw) {
+ storage.Schema = schema.Schema{
+ schema.Table{
+ Name: []byte("Table_A"),
+ },
+ schema.Table{
+ Name: []byte("Table_B"),
+ Columns: make([]schema.Column, len(raws)),
+ },
+ schema.Table{
+ Name: []byte("Table_C"),
+ },
+ }
+ for i := range raws {
+ storage.Schema[1].Columns[i] = schema.NewColumn(
+ []byte{byte(i)},
+ ast.ComposeDataType(raws[i].major, raws[i].minor),
+ 0, 0, 0, 0,
+ )
+ }
+ storage.Schema.SetupColumnOffset()
+ storage.Commit(false)
+}
+
+// setSlotDataInStateDB store data in StateDB, and
+// return corresponding slot hash and raw slice.
+func setSlotDataInStateDB(head dexCommon.Hash, addr dexCommon.Address,
+ storage common.Storage) ([]dexCommon.Hash, []*raw) {
+
+ hash := dexCommon.Hash{}
+ var b []byte
+ slotHash := []string{
+ "0123112233445566778800000000000000000000000000000000000000000000",
+ "48656c6c6f2c20776f726c64210000000000000000000000000000000000001a",
+ "3132333435363738393000000000000000000000000000000000000000000000",
+ "53514c564d2069732075736566756c2100000000000000000000000000000020",
+ "0000000000000000000000000000000000000000000000000000000000000041",
+ "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
+ }
+ fByte20Dt := ast.ComposeDataType(ast.DataTypeMajorFixedBytes, ast.DataTypeMinor(9))
+ uInt256Dt := ast.ComposeDataType(ast.DataTypeMajorUint, ast.DataTypeMinor(31))
+
+ raws := []*raw{
+ {
+ Raw: Raw{
+ Value: decimal.New(0x0123, 0),
+ Bytes: nil,
+ },
+ slotShift: 0,
+ byteShift: 0,
+ major: ast.DataTypeMajorUint,
+ minor: ast.DataTypeMinor(1),
+ },
+ {
+ Raw: Raw{
+ Value: decimal.New(0x1122334455667788, 0),
+ Bytes: nil,
+ },
+ slotShift: 0,
+ byteShift: 2,
+ major: ast.DataTypeMajorUint,
+ minor: ast.DataTypeMinor(7),
+ },
+ {
+ Raw: Raw{
+ Bytes: []byte("Hello, world!"),
+ },
+ slotShift: 1,
+ byteShift: 0,
+ major: ast.DataTypeMajorDynamicBytes,
+ minor: ast.DataTypeMinor(0),
+ },
+ {
+ Raw: Raw{
+ Value: hexToDec(slotHash[2][:20], fByte20Dt),
+ Bytes: nil,
+ },
+ slotShift: 2,
+ byteShift: 0,
+ major: ast.DataTypeMajorFixedBytes,
+ minor: ast.DataTypeMinor(9),
+ },
+ {
+ Raw: Raw{
+ Bytes: []byte("SQLVM is useful!"),
+ },
+ slotShift: 3,
+ byteShift: 0,
+ major: ast.DataTypeMajorDynamicBytes,
+ minor: ast.DataTypeMinor(0),
+ },
+ {
+ Raw: Raw{
+ Bytes: []byte("Hello world. Hello DEXON, SQLVM."),
+ },
+ slotShift: 4,
+ byteShift: 0,
+ major: ast.DataTypeMajorDynamicBytes,
+ minor: ast.DataTypeMinor(0),
+ },
+ {
+ Raw: Raw{
+ Value: hexToDec(slotHash[5], uInt256Dt),
+ Bytes: nil,
+ },
+ slotShift: 5,
+ byteShift: 0,
+ major: ast.DataTypeMajorUint,
+ minor: ast.DataTypeMinor(31),
+ },
+ }
+
+ // set slot hash
+ hData := make([]dexCommon.Hash, len(slotHash))
+ ptr := head
+ for i, s := range slotHash {
+ b, _ = hex.DecodeString(s)
+ hData[i].SetBytes(b)
+ storage.SetState(addr, ptr, hData[i])
+ ptr = storage.ShiftHashUint64(ptr, uint64(1))
+ }
+
+ // set dynamic bytes data
+ longDBytesLoc := 5
+ longRaw := raws[longDBytesLoc]
+ hash.SetBytes(longRaw.Bytes)
+ ptr = storage.ShiftHashUint64(head, uint64(longRaw.slotShift))
+ ptr = crypto.Keccak256Hash(ptr.Bytes())
+ storage.SetState(addr, ptr, hash)
+
+ storage.Commit(false)
+ return hData, raws
+}
+
+func hexToDec(s string, dt ast.DataType) decimal.Decimal {
+ b, _ := hex.DecodeString(s)
+ d, _ := ast.DecimalDecode(dt, b)
+ return d
+}
+
+type decodeTestCase struct {
+ dt ast.DataType
+ expectData *Raw
+ expectSlotHash dexCommon.Hash
+ shift uint64
+ inputBytes []byte
+ dBytes []byte
+}
+
+type opLoadTestCase struct {
+ title string
+ outputIdx int
+ expectedOutput *Operand
+ expectedErr error
+ ids []uint64
+ fields []uint8
+ tableIdx int8
+}
+
+func (s *opLoadSuite) SetupTest() {
+ s.ctx = &common.Context{}
+ s.ctx.Storage = s.newStorage()
+ s.headHash = s.ctx.Storage.GetPrimaryKeyHash([]byte("Table_B"), uint64(123456))
+ s.address = dexCommon.HexToAddress("0x6655")
+ s.ctx.Storage.CreateAccount(s.address)
+ s.ctx.Contract = vm.NewContract(vm.AccountRef(s.address),
+ vm.AccountRef(s.address), new(big.Int), 0)
+ s.slotHash, s.raws = setSlotDataInStateDB(s.headHash, s.address, s.ctx.Storage)
+ createSchema(&s.ctx.Storage, s.raws)
+ s.setColData("Table_B", 654321)
+}
+
+func (s *opLoadSuite) setColData(tableName string, id uint64) {
+ h := s.ctx.Storage.GetPrimaryKeyHash([]byte(tableName), id)
+ setSlotDataInStateDB(h, s.address, s.ctx.Storage)
+}
+
+func (s *opLoadSuite) getOpLoadTestCases(raws []*raw) []opLoadTestCase {
+ testCases := []opLoadTestCase{
+ {
+ title: "NIL_RESULT",
+ outputIdx: 0,
+ expectedOutput: &Operand{Meta: make([]ast.DataType, 0), Data: make([]Tuple, 0)},
+ expectedErr: nil,
+ ids: nil,
+ fields: nil,
+ tableIdx: 0,
+ },
+ {
+ title: "NOT_EXIST_TABLE",
+ outputIdx: 0,
+ expectedOutput: nil,
+ expectedErr: errors.ErrorCodeIndexOutOfRange,
+ ids: nil,
+ fields: nil,
+ tableIdx: 13,
+ },
+ {
+ title: "OK_CASE",
+ outputIdx: 0,
+ expectedOutput: s.getOKCaseOutput(raws),
+ expectedErr: nil,
+ ids: []uint64{123456, 654321},
+ fields: s.getOKCaseFields(raws),
+ tableIdx: 1,
+ },
+ }
+ return testCases
+}
+
+func (s *opLoadSuite) getOKCaseOutput(raws []*raw) *Operand {
+ rValue := &Operand{}
+ size := len(raws)
+ rValue.Meta = make([]ast.DataType, size)
+ rValue.Data = make([]Tuple, 2)
+ for j := range rValue.Data {
+ rValue.Data[j] = make([]*Raw, size)
+ for i, raw := range raws {
+ rValue.Meta[i] = ast.ComposeDataType(raw.major, raw.minor)
+ rValue.Data[j][i] = &raw.Raw
+ }
+ }
+ return rValue
+}
+
+func (s *opLoadSuite) getOKCaseFields(raws []*raw) []uint8 {
+ rValue := make([]uint8, len(raws))
+ for i := range raws {
+ rValue[i] = uint8(i)
+ }
+ return rValue
+}
+
+func (s *opLoadSuite) getDecodeTestCases(headHash dexCommon.Hash,
+ address dexCommon.Address, storage common.Storage) []decodeTestCase {
+
+ slotHash, raws := setSlotDataInStateDB(headHash, address, storage)
+ createSchema(&storage, raws)
+ testCases := make([]decodeTestCase, len(raws))
+
+ for i := range testCases {
+ r := raws[i]
+ testCases[i].dt = ast.ComposeDataType(r.major, r.minor)
+ testCases[i].shift = uint64(r.slotShift)
+ testCases[i].expectSlotHash = slotHash[r.slotShift]
+ testCases[i].expectData = &r.Raw
+ slot := slotHash[r.slotShift]
+ start := r.byteShift
+ end := r.byteShift + testCases[i].dt.Size()
+ testCases[i].inputBytes = slot.Bytes()[start:end]
+ }
+ return testCases
+}
+
+func (s *opLoadSuite) newRegisters(tableIdx int8, ids []uint64, fields []uint8) []*Operand {
+ o := make([]*Operand, 4)
+ o[1] = newTableNameOperand(tableIdx)
+ o[2] = newIDsOperand(ids)
+ o[3] = newFieldsOperand(fields)
+ return o
+}
+
+func newInput(nums []int) []*Operand {
+ o := make([]*Operand, len(nums))
+ for i, n := range nums {
+ o[i] = &Operand{
+ IsImmediate: false,
+ RegisterIndex: uint(n),
+ }
+ }
+ return o
+}
+
+func newTableNameOperand(tableIdx int8) *Operand {
+ if tableIdx < 0 {
+ return nil
+ }
+ o := &Operand{
+ Meta: []ast.DataType{
+ ast.ComposeDataType(ast.DataTypeMajorUint, 0),
+ },
+ Data: []Tuple{
+ []*Raw{
+ {
+ Value: decimal.New(int64(tableIdx), 0),
+ },
+ },
+ },
+ }
+ return o
+}
+
+func newIDsOperand(ids []uint64) *Operand {
+ o := &Operand{
+ Meta: []ast.DataType{
+ ast.ComposeDataType(ast.DataTypeMajorUint, 7),
+ },
+ }
+ o.Data = make([]Tuple, len(ids))
+ for i := range o.Data {
+ o.Data[i] = make([]*Raw, 1)
+ o.Data[i][0] = &Raw{
+ Value: decimal.New(int64(ids[i]), 0),
+ }
+ }
+ return o
+}
+
+func newFieldsOperand(fields []uint8) *Operand {
+ o := &Operand{
+ Meta: []ast.DataType{
+ ast.ComposeDataType(ast.DataTypeMajorUint, 0),
+ },
+ }
+ o.Data = make([]Tuple, len(fields))
+ for i := range o.Data {
+ o.Data[i] = make([]*Raw, 1)
+ o.Data[i][0] = &Raw{
+ Value: decimal.New(int64(fields[i]), 0),
+ }
+ }
+ return o
+}
+
+func (s *opLoadSuite) newStorage() common.Storage {
+ db := ethdb.NewMemDatabase()
+ state, _ := state.New(dexCommon.Hash{}, state.NewDatabase(db))
+ storage := common.NewStorage(state)
+ return storage
+}
+
+func (s *opLoadSuite) TestDecode() {
+ testCases := s.getDecodeTestCases(s.headHash, s.address, s.ctx.Storage)
+ for _, tt := range testCases {
+ M, _ := ast.DecomposeDataType(tt.dt)
+ slot := s.ctx.Storage.ShiftHashUint64(s.headHash, tt.shift)
+ slotHash := s.ctx.Storage.GetState(s.address, slot)
+ s.Require().Equal(tt.expectSlotHash, slotHash)
+
+ data, err := decode(s.ctx, tt.dt, slot, tt.inputBytes)
+ s.Require().Nil(err)
+
+ if M == ast.DataTypeMajorDynamicBytes {
+ s.Require().Equal(tt.expectData.Bytes, data.Bytes)
+ } else {
+ s.Require().True(tt.expectData.Value.Equal(data.Value))
+ }
+ }
+}
+
+func (s *opLoadSuite) TestOpLoad() {
+ testCases := s.getOpLoadTestCases(s.raws)
+ for _, t := range testCases {
+ input := newInput([]int{1, 2, 3})
+ reg := s.newRegisters(t.tableIdx, t.ids, t.fields)
+
+ loadRegister(input, reg)
+ err := opLoad(s.ctx, input, reg, t.outputIdx)
+
+ s.Require().Equalf(t.expectedErr, err, "testcase: [%v]", t.title)
+ s.Require().Truef(reflect.DeepEqual(t.expectedOutput, reg[t.outputIdx]),
+ "testcase: [%v], expect: %+v, result: %+v",
+ t.title, t.expectedOutput, reg[t.outputIdx],
+ )
+ }
+}
+
+func TestOpLoad(t *testing.T) {
+ suite.Run(t, new(opLoadSuite))
+}