diff options
author | Jhih-Ming Huang <jm.huang@cobinhood.com> | 2019-03-15 17:43:50 +0800 |
---|---|---|
committer | Jhih-Ming Huang <jm.huang@cobinhood.com> | 2019-03-26 17:48:23 +0800 |
commit | 952d0af6cb266824ca4517cca9d5dc6f3e505a1a (patch) | |
tree | bad8419a76a3e0b4dde68c046afc73f7f42e68ac | |
parent | ec427716b8d8419f0f4bb2761d1465bf8fc68ab7 (diff) | |
download | dexon-952d0af6cb266824ca4517cca9d5dc6f3e505a1a.tar dexon-952d0af6cb266824ca4517cca9d5dc6f3e505a1a.tar.gz dexon-952d0af6cb266824ca4517cca9d5dc6f3e505a1a.tar.bz2 dexon-952d0af6cb266824ca4517cca9d5dc6f3e505a1a.tar.lz dexon-952d0af6cb266824ca4517cca9d5dc6f3e505a1a.tar.xz dexon-952d0af6cb266824ca4517cca9d5dc6f3e505a1a.tar.zst dexon-952d0af6cb266824ca4517cca9d5dc6f3e505a1a.zip |
core: vm: sqlvm: common: storage: implement storage util functions
Implement some storage utility functions, including
shift slot, get dynamic byte and get primary key hash.
-rw-r--r-- | core/vm/sqlvm/common/context.go | 2 | ||||
-rw-r--r-- | core/vm/sqlvm/common/storage.go | 93 | ||||
-rw-r--r-- | core/vm/sqlvm/common/storage_test.go | 107 |
3 files changed, 201 insertions, 1 deletions
diff --git a/core/vm/sqlvm/common/context.go b/core/vm/sqlvm/common/context.go index 7c4305e3e..1985473fa 100644 --- a/core/vm/sqlvm/common/context.go +++ b/core/vm/sqlvm/common/context.go @@ -6,6 +6,6 @@ import "github.com/dexon-foundation/dexon/core/vm" type Context struct { vm.Context - StateDB vm.StateDB + Storage Storage Contract *vm.Contract } diff --git a/core/vm/sqlvm/common/storage.go b/core/vm/sqlvm/common/storage.go new file mode 100644 index 000000000..4595a773b --- /dev/null +++ b/core/vm/sqlvm/common/storage.go @@ -0,0 +1,93 @@ +package common + +import ( + "math/big" + + "github.com/shopspring/decimal" + "golang.org/x/crypto/sha3" + + "github.com/dexon-foundation/dexon/common" + "github.com/dexon-foundation/dexon/core/state" + "github.com/dexon-foundation/dexon/core/vm/sqlvm/ast" + "github.com/dexon-foundation/dexon/core/vm/sqlvm/schema" + "github.com/dexon-foundation/dexon/crypto" + "github.com/dexon-foundation/dexon/rlp" +) + +// Storage holds SQLVM required data and method. +type Storage struct { + state.StateDB + Schema schema.Schema +} + +// NewStorage return Storage instance. +func NewStorage(state *state.StateDB) Storage { + s := Storage{*state, schema.Schema{}} + return s +} + +func convertIDtoBytes(id uint64) []byte { + bigIntID := new(big.Int).SetUint64(id) + decimalID := decimal.NewFromBigInt(bigIntID, 0) + dt := ast.ComposeDataType(ast.DataTypeMajorUint, 7) + byteID, _ := ast.DecimalEncode(dt, decimalID) + return byteID +} + +// GetPrimaryKeyHash return primary key hash. +func (s Storage) GetPrimaryKeyHash(tableName []byte, id uint64) (h common.Hash) { + key := [][]byte{ + []byte("tables"), + tableName, + []byte("primary"), + convertIDtoBytes(id), + } + hw := sha3.NewLegacyKeccak256() + rlp.Encode(hw, key) + // length of common.Hash is 256bit, + // so it can properly match the size of hw.Sum + hw.Sum(h[:0]) + return +} + +// ShiftHashUint64 shift hash in uint64. +func (s Storage) ShiftHashUint64(hash common.Hash, shift uint64) common.Hash { + bigIntOffset := new(big.Int) + bigIntOffset.SetUint64(shift) + return s.ShiftHashBigInt(hash, bigIntOffset) +} + +// ShiftHashBigInt shift hash in big.Int +func (s Storage) ShiftHashBigInt(hash common.Hash, shift *big.Int) common.Hash { + head := hash.Big() + head.Add(head, shift) + return common.BytesToHash(head.Bytes()) +} + +func getDByteSize(data common.Hash) uint64 { + bytes := data.Bytes() + lastByte := bytes[len(bytes)-1] + if lastByte&0x1 == 0 { + return uint64(lastByte / 2) + } + return new(big.Int).Div(new(big.Int).Sub( + data.Big(), big.NewInt(1)), big.NewInt(2)).Uint64() +} + +// DecodeDByteBySlot given contract address and slot return the dynamic bytes data. +func (s Storage) DecodeDByteBySlot(address common.Address, slot common.Hash) []byte { + data := s.GetState(address, slot) + length := getDByteSize(data) + if length < 32 { + return data[:length] + } + ptr := crypto.Keccak256Hash(slot.Bytes()) + slotNum := (length-1)/32 + 1 + rVal := make([]byte, slotNum*32) + for i := uint64(0); i < slotNum; i++ { + start := i * 32 + copy(rVal[start:start+32], s.GetState(address, ptr).Bytes()) + ptr = s.ShiftHashUint64(ptr, 1) + } + return rVal[:length] +} diff --git a/core/vm/sqlvm/common/storage_test.go b/core/vm/sqlvm/common/storage_test.go new file mode 100644 index 000000000..42b8c2298 --- /dev/null +++ b/core/vm/sqlvm/common/storage_test.go @@ -0,0 +1,107 @@ +package common + +import ( + "bytes" + "fmt" + "testing" + + "github.com/stretchr/testify/suite" + "golang.org/x/crypto/sha3" + + "github.com/dexon-foundation/dexon/common" + "github.com/dexon-foundation/dexon/core/state" + "github.com/dexon-foundation/dexon/crypto" + "github.com/dexon-foundation/dexon/ethdb" + "github.com/dexon-foundation/dexon/rlp" +) + +type StorageTestSuite struct{ suite.Suite } + +func (s *StorageTestSuite) TestGetPrimaryKeyHash() { + id := uint64(555666) + table := []byte("TABLE_A") + key := [][]byte{ + []byte("tables"), + table, + []byte("primary"), + convertIDtoBytes(id), + } + hw := sha3.NewLegacyKeccak256() + rlp.Encode(hw, key) + bytes := hw.Sum(nil) + storage := Storage{} + result := storage.GetPrimaryKeyHash(table, id) + s.Require().Equal(bytes, result[:]) +} + +type decodeTestCase struct { + name string + slotData common.Hash + result []byte +} + +func (s *StorageTestSuite) TestDecodeDByte() { + db := ethdb.NewMemDatabase() + state, _ := state.New(common.Hash{}, state.NewDatabase(db)) + storage := NewStorage(state) + address := common.BytesToAddress([]byte("123")) + head := common.HexToHash("0x5566") + testcase := []decodeTestCase{ + { + name: "small size", + slotData: common.HexToHash("0x48656c6c6f2c20776f726c64210000000000000000000000000000000000001a"), + result: common.FromHex("0x48656c6c6f2c20776f726c6421"), + }, + { + name: "32 byte case", + slotData: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000041"), + result: []byte("Hello world. Hello DEXON, SQLVM."), + }, + { + name: "large size", + slotData: common.HexToHash("0x000000000000000000000000000000000000000000000000000000000000047D"), + result: []byte("Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."), + }, + { + name: "empty", + slotData: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000"), + result: []byte(""), + }, + } + SetDataToStateDB(head, storage, address, testcase) + for i, t := range testcase { + slot := storage.ShiftHashUint64(head, uint64(i)) + result := storage.DecodeDByteBySlot(address, slot) + s.Require().Truef(bytes.Equal(result, t.result), fmt.Sprintf("name %v", t.name)) + } +} + +func SetDataToStateDB(head common.Hash, storage Storage, addr common.Address, + testcase []decodeTestCase) { + for i, t := range testcase { + slot := storage.ShiftHashUint64(head, uint64(i)) + storage.SetState(addr, slot, t.slotData) + b := t.slotData.Bytes() + if b[len(b)-1]&0x1 != 0 { + length := len(t.result) + slotNum := (length-1)/32 + 1 + ptr := crypto.Keccak256Hash(slot.Bytes()) + for s := 0; s < slotNum; s++ { + start := s * 32 + end := (s + 1) * 32 + if end > len(t.result) { + end = len(t.result) + } + hash := common.Hash{} + copy(hash[:], t.result[start:end]) + storage.SetState(addr, ptr, hash) + ptr = storage.ShiftHashUint64(ptr, 1) + } + } + } + storage.Commit(false) +} + +func TestStorage(t *testing.T) { + suite.Run(t, new(StorageTestSuite)) +} |