aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJhih-Ming Huang <jm.huang@cobinhood.com>2019-03-15 17:43:50 +0800
committerJhih-Ming Huang <jm.huang@cobinhood.com>2019-05-06 10:44:04 +0800
commit1bc4caded6333381ef4646c94f462abd8db238bd (patch)
tree0613dad66cc84d828f4ddbb88d1e1296cb2b2343
parentd3fac9087206355c1efdb9eba1e5f57c78346c50 (diff)
downloaddexon-1bc4caded6333381ef4646c94f462abd8db238bd.tar.gz
dexon-1bc4caded6333381ef4646c94f462abd8db238bd.tar.zst
dexon-1bc4caded6333381ef4646c94f462abd8db238bd.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.go2
-rw-r--r--core/vm/sqlvm/common/storage.go93
-rw-r--r--core/vm/sqlvm/common/storage_test.go107
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))
+}