From a862c0b4300df0303d08c0be495ff13a929ce299 Mon Sep 17 00:00:00 2001 From: Jhih-Ming Huang Date: Fri, 15 Mar 2019 17:47:44 +0800 Subject: core: vm: sqlvm: runtime: implement opLoad --- core/vm/sqlvm/runtime/instructions.go | 109 +++++++- core/vm/sqlvm/runtime/instructions_test.go | 409 +++++++++++++++++++++++++++++ 2 files changed, 510 insertions(+), 8 deletions(-) create mode 100644 core/vm/sqlvm/runtime/instructions_test.go (limited to 'core') 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)) +} -- cgit v1.2.3