aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoryenlin.lai <yenlin.lai@cobinhood.com>2019-04-11 17:00:09 +0800
committerJhih-Ming Huang <jm.huang@cobinhood.com>2019-05-06 10:44:04 +0800
commitc044cbc979abae7563344d6b047a6b0c616d7b7f (patch)
treec1f42360283f78b7e0bbb10457e85c94958aeab0
parentb99b6272d7c0858f66867cc1ac056686bbf33fe0 (diff)
downloaddexon-c044cbc979abae7563344d6b047a6b0c616d7b7f.tar.gz
dexon-c044cbc979abae7563344d6b047a6b0c616d7b7f.tar.zst
dexon-c044cbc979abae7563344d6b047a6b0c616d7b7f.zip
sqlvm: common: add Reader/Writer for Storage
Sometimes we need a stream reader writer for the data on storage. For example, RLP decode the data on storage. Implement a wrapper around it.
-rw-r--r--core/vm/sqlvm/common/storage_rw.go114
-rw-r--r--core/vm/sqlvm/common/storage_rw_test.go62
2 files changed, 176 insertions, 0 deletions
diff --git a/core/vm/sqlvm/common/storage_rw.go b/core/vm/sqlvm/common/storage_rw.go
new file mode 100644
index 000000000..7df1d73bd
--- /dev/null
+++ b/core/vm/sqlvm/common/storage_rw.go
@@ -0,0 +1,114 @@
+package common
+
+import (
+ "io"
+
+ "github.com/dexon-foundation/dexon/common"
+)
+
+// StorageReader implements io.Reader on Storage.
+// Notice that we have cache in Reader, so it become invalid after any writes
+// to Storage or StateDB.
+type StorageReader struct {
+ storage Storage
+ contract common.Address
+ cursor common.Hash
+ buffer []byte
+}
+
+// assert io.Reader interface is implemented.
+var _ io.Reader = (*StorageReader)(nil)
+
+// NewStorageReader create a Reader on Storage.
+func NewStorageReader(
+ storage Storage,
+ contract common.Address,
+ startPos common.Hash,
+) *StorageReader {
+ return &StorageReader{
+ storage: storage,
+ contract: contract,
+ cursor: startPos,
+ }
+}
+
+// Read implements the function defined in io.Reader interface.
+func (r *StorageReader) Read(p []byte) (n int, err error) {
+ copyBuffer := func() {
+ lenCopy := len(p)
+ if lenCopy > len(r.buffer) {
+ lenCopy = len(r.buffer)
+ }
+ copy(p[:lenCopy], r.buffer[:lenCopy])
+ p = p[lenCopy:]
+ r.buffer = r.buffer[lenCopy:]
+ n += lenCopy
+ }
+ // Flush old buffer first.
+ copyBuffer()
+ for len(p) > 0 {
+ // Read slot by slot.
+ r.buffer = r.storage.GetState(r.contract, r.cursor).Bytes()
+ r.cursor = r.storage.ShiftHashUint64(r.cursor, 1)
+ copyBuffer()
+ }
+ return
+}
+
+// StorageWriter implements io.Writer on Storage.
+type StorageWriter struct {
+ storage Storage
+ contract common.Address
+ cursor common.Hash
+ byteShift int // bytes already written in last slot. value: 0 ~ 31
+}
+
+// assert io.Writer interface is implemented.
+var _ io.Writer = (*StorageWriter)(nil)
+
+// NewStorageWriter create a Writer on Storage.
+func NewStorageWriter(
+ storage Storage,
+ contract common.Address,
+ startPos common.Hash,
+) *StorageWriter {
+ return &StorageWriter{
+ storage: storage,
+ contract: contract,
+ cursor: startPos,
+ }
+}
+
+// Write implements the function defined in io.Writer interface.
+func (w *StorageWriter) Write(p []byte) (n int, err error) {
+ var payload common.Hash
+ for len(p) > 0 {
+ // Setup common.Hash to write.
+ remain := common.HashLength - w.byteShift
+ lenCopy := remain
+ if lenCopy > len(p) {
+ lenCopy = len(p)
+ }
+ if lenCopy != common.HashLength {
+ // Not writing an entire slot, need load first.
+ payload = w.storage.GetState(w.contract, w.cursor)
+ }
+ b := payload.Bytes()
+ start := w.byteShift
+ end := w.byteShift + lenCopy
+ copy(b[start:end], p[:lenCopy])
+ payload.SetBytes(b)
+
+ w.storage.SetState(w.contract, w.cursor, payload)
+
+ // Update state.
+ p = p[lenCopy:]
+ n += lenCopy
+ w.byteShift += lenCopy
+ if w.byteShift == common.HashLength {
+ w.byteShift = 0
+ w.cursor = w.storage.ShiftHashUint64(w.cursor, 1)
+ }
+ }
+ return
+}
diff --git a/core/vm/sqlvm/common/storage_rw_test.go b/core/vm/sqlvm/common/storage_rw_test.go
new file mode 100644
index 000000000..99b7a0ae7
--- /dev/null
+++ b/core/vm/sqlvm/common/storage_rw_test.go
@@ -0,0 +1,62 @@
+package common
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/suite"
+
+ "github.com/dexon-foundation/dexon/common"
+ "github.com/dexon-foundation/dexon/core/state"
+ "github.com/dexon-foundation/dexon/ethdb"
+)
+
+type StorageRWTestSuite struct{ suite.Suite }
+
+func (s *StorageRWTestSuite) TestRW() {
+ db := ethdb.NewMemDatabase()
+ state, _ := state.New(common.Hash{}, state.NewDatabase(db))
+ storage := NewStorage(state)
+ contract := common.BytesToAddress([]byte("contract"))
+ start := common.BytesToHash([]byte("start"))
+
+ // Data to write.
+ payload := []byte("What is Lorem Ipsum? 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.")
+ // Length used in rw. (Sum should equal len(paylaod))
+ writeLens := []int{5, 10, 32, 9, len(payload) - 56}
+ readLens := []int{9, 32, 5, 10, len(payload) - 56}
+
+ // Write.
+ writer := NewStorageWriter(storage, contract, start)
+ cursor := 0
+ for _, v := range writeLens {
+ n, err := writer.Write(payload[cursor:(cursor + v)])
+ s.Require().Nil(err)
+ s.Require().Equal(v, n)
+ cursor += v
+ }
+ storage.Commit(false)
+
+ // Read and check.
+ reader := NewStorageReader(storage, contract, start)
+ cursor = 0
+ for _, v := range readLens {
+ payloadRead := make([]byte, v)
+ n, err := reader.Read(payloadRead)
+ s.Require().Nil(err)
+ s.Require().Equal(v, n)
+ s.Require().Equal(payload[cursor:(cursor+v)], payloadRead)
+ cursor += v
+ }
+ // Check if the remaining data is all zero.
+ zeroCheck := make([]byte, 128)
+ n, err := reader.Read(zeroCheck)
+ s.Require().Nil(err)
+ s.Require().Equal(len(zeroCheck), n)
+ for _, v := range zeroCheck {
+ s.Require().Zero(v)
+ }
+}
+
+func TestStorageRW(t *testing.T) {
+ suite.Run(t, new(StorageRWTestSuite))
+}