syncbase: various small additions and enhancements

- stop returning error from Table.Scan
- make app/db/table delete idempotent
- implement Table.DeleteRowRange
- add basic perms check to Table.Scan
- various comment and TODO tweaks
- drop WrapError, use verror.Convert

Change-Id: Ib3d34c14751d39c2c7e324945ab30e347cd07e30
diff --git a/services/syncbase/server/app.go b/services/syncbase/server/app.go
index 6e1812f..af92c09 100644
--- a/services/syncbase/server/app.go
+++ b/services/syncbase/server/app.go
@@ -87,7 +87,7 @@
 	defer a.mu.Unlock()
 	d, ok := a.dbs[dbName]
 	if !ok {
-		return nil, verror.New(verror.ErrNoExistOrNoAccess, ctx, dbName)
+		return nil, verror.New(verror.ErrNoExist, ctx, dbName)
 	}
 	return d, nil
 }
@@ -128,7 +128,7 @@
 			return err
 		}
 		// Check for "database already exists".
-		if _, err := a.getDbInfo(ctx, call, st, dbName); verror.ErrorID(err) != verror.ErrNoExistOrNoAccess.ID {
+		if _, err := a.getDbInfo(ctx, call, st, dbName); verror.ErrorID(err) != verror.ErrNoExist.ID {
 			if err != nil {
 				return err
 			}
@@ -181,11 +181,14 @@
 	defer a.mu.Unlock()
 	d, ok := a.dbs[dbName]
 	if !ok {
-		return verror.New(verror.ErrNoExistOrNoAccess, ctx, dbName)
+		return nil // delete is idempotent
 	}
 
 	// 1. Check databaseData perms.
 	if err := d.CheckPermsInternal(ctx, call, d.St()); err != nil {
+		if verror.ErrorID(err) == verror.ErrNoExist.ID {
+			return nil // delete is idempotent
+		}
 		return err
 	}
 
@@ -216,7 +219,7 @@
 	defer a.mu.Unlock()
 	d, ok := a.dbs[dbName]
 	if !ok {
-		return verror.New(verror.ErrNoExistOrNoAccess, ctx, dbName)
+		return verror.New(verror.ErrNoExist, ctx, dbName)
 	}
 	return d.SetPermsInternal(ctx, call, perms, version)
 }
diff --git a/services/syncbase/server/dispatcher.go b/services/syncbase/server/dispatcher.go
index 7f2fd1f..f6a4dde 100644
--- a/services/syncbase/server/dispatcher.go
+++ b/services/syncbase/server/dispatcher.go
@@ -52,7 +52,7 @@
 		a = aint.(*app) // panics on failure, as desired
 		aExists = true
 	} else {
-		if verror.ErrorID(err) != verror.ErrNoExistOrNoAccess.ID {
+		if verror.ErrorID(err) != verror.ErrNoExist.ID {
 			return nil, nil, err
 		} else {
 			a = &app{
@@ -69,7 +69,7 @@
 	// All database, table, and row methods require the app to exist. If it
 	// doesn't, abort early.
 	if !aExists {
-		return nil, nil, verror.New(verror.ErrNoExistOrNoAccess, nil, a.name)
+		return nil, nil, verror.New(verror.ErrNoExist, nil, a.name)
 	}
 
 	// Note, it's possible for the app to be deleted concurrently with downstream
diff --git a/services/syncbase/server/nosql/dispatcher.go b/services/syncbase/server/nosql/dispatcher.go
index 6d572ae..d8fe2f0 100644
--- a/services/syncbase/server/nosql/dispatcher.go
+++ b/services/syncbase/server/nosql/dispatcher.go
@@ -49,7 +49,7 @@
 		d = dint.(*database) // panics on failure, as desired
 		dExists = true
 	} else {
-		if verror.ErrorID(err) != verror.ErrNoExistOrNoAccess.ID {
+		if verror.ErrorID(err) != verror.ErrNoExist.ID {
 			return nil, nil, err
 		} else {
 			d = &database{
@@ -66,7 +66,7 @@
 	// All table and row methods require the database to exist. If it doesn't,
 	// abort early.
 	if !dExists {
-		return nil, nil, verror.New(verror.ErrNoExistOrNoAccess, nil, d.name)
+		return nil, nil, verror.New(verror.ErrNoExist, nil, d.name)
 	}
 
 	// Note, it's possible for the database to be deleted concurrently with
diff --git a/services/syncbase/server/nosql/row.go b/services/syncbase/server/nosql/row.go
index f91af3b..057dc10 100644
--- a/services/syncbase/server/nosql/row.go
+++ b/services/syncbase/server/nosql/row.go
@@ -80,8 +80,6 @@
 	value, err := st.Get([]byte(r.StKey()), nil)
 	if err != nil {
 		if verror.ErrorID(err) == store.ErrUnknownKey.ID {
-			// We've already done an auth check, so here we can safely return NoExist
-			// rather than NoExistOrNoAccess.
 			return nil, verror.New(verror.ErrNoExist, ctx, r.Name())
 		}
 		return nil, verror.New(verror.ErrInternal, ctx, err)
diff --git a/services/syncbase/server/nosql/table.go b/services/syncbase/server/nosql/table.go
index eb2a9c5..e73afd2 100644
--- a/services/syncbase/server/nosql/table.go
+++ b/services/syncbase/server/nosql/table.go
@@ -37,7 +37,7 @@
 			return err
 		}
 		// Check for "table already exists".
-		if err := util.GetWithoutAuth(ctx, call, st, t, &tableData{}); verror.ErrorID(err) != verror.ErrNoExistOrNoAccess.ID {
+		if err := util.GetWithoutAuth(ctx, call, st, t, &tableData{}); verror.ErrorID(err) != verror.ErrNoExist.ID {
 			if err != nil {
 				return err
 			}
@@ -60,6 +60,9 @@
 	return store.RunInTransaction(t.d.st, func(st store.StoreReadWriter) error {
 		// Read-check-delete tableData.
 		if err := util.Get(ctx, call, st, t, &tableData{}); err != nil {
+			if verror.ErrorID(err) == verror.ErrNoExist.ID {
+				return nil // delete is idempotent
+			}
 			return err
 		}
 		// TODO(sadovsky): Delete all rows in this table.
@@ -68,12 +71,33 @@
 }
 
 func (t *table) DeleteRowRange(ctx *context.T, call rpc.ServerCall, start, limit []byte) error {
-	return verror.NewErrNotImplemented(ctx)
+	return store.RunInTransaction(t.d.st, func(st store.StoreReadWriter) error {
+		// Check perms.
+		if err := util.Get(ctx, call, st, t, &tableData{}); err != nil {
+			return err
+		}
+		it := st.Scan(util.ScanRangeArgs(util.JoinKeyParts(util.RowPrefix, t.name), string(start), string(limit)))
+		key := []byte{}
+		for it.Advance() {
+			key = it.Key(key)
+			if err := st.Delete(key); err != nil {
+				return verror.New(verror.ErrInternal, ctx, err)
+			}
+		}
+		if err := it.Err(); err != nil {
+			return verror.New(verror.ErrInternal, ctx, err)
+		}
+		return nil
+	})
 }
 
 func (t *table) Scan(ctx *context.T, call wire.TableScanServerCall, start, limit []byte) error {
 	sn := t.d.st.NewSnapshot()
 	defer sn.Close()
+	// Check perms.
+	if err := util.Get(ctx, call, sn, t, &tableData{}); err != nil {
+		return err
+	}
 	it := sn.Scan(util.ScanRangeArgs(util.JoinKeyParts(util.RowPrefix, t.name), string(start), string(limit)))
 	sender := call.SendStream()
 	key, value := []byte{}, []byte{}
@@ -117,8 +141,8 @@
 }
 
 func (t *table) GlobChildren__(ctx *context.T, call rpc.ServerCall) (<-chan string, error) {
-	// Check perms.
 	sn := t.d.st.NewSnapshot()
+	// Check perms.
 	if err := util.Get(ctx, call, sn, t, &tableData{}); err != nil {
 		sn.Close()
 		return nil, err
diff --git a/services/syncbase/server/service.go b/services/syncbase/server/service.go
index c5f6be4..b352a27 100644
--- a/services/syncbase/server/service.go
+++ b/services/syncbase/server/service.go
@@ -4,6 +4,10 @@
 
 package server
 
+// TODO(sadovsky): Check Resolve access on parent where applicable. Relatedly,
+// convert ErrNoExist and ErrNoAccess to ErrNoExistOrNoAccess where needed to
+// preserve privacy.
+
 import (
 	"sync"
 
@@ -113,7 +117,7 @@
 	defer s.mu.Unlock()
 	a, ok := s.apps[appName]
 	if !ok {
-		return nil, verror.New(verror.ErrNoExistOrNoAccess, ctx, appName)
+		return nil, verror.New(verror.ErrNoExist, ctx, appName)
 	}
 	return a, nil
 }
@@ -137,7 +141,6 @@
 	s.mu.Lock()
 	defer s.mu.Unlock()
 	if _, ok := s.apps[appName]; ok {
-		// TODO(sadovsky): Should this be ErrExistOrNoAccess, for privacy?
 		return verror.New(verror.ErrExist, ctx, appName)
 	}
 
@@ -154,11 +157,10 @@
 			return err
 		}
 		// Check for "app already exists".
-		if err := util.GetWithoutAuth(ctx, call, st, a, &appData{}); verror.ErrorID(err) != verror.ErrNoExistOrNoAccess.ID {
+		if err := util.GetWithoutAuth(ctx, call, st, a, &appData{}); verror.ErrorID(err) != verror.ErrNoExist.ID {
 			if err != nil {
 				return err
 			}
-			// TODO(sadovsky): Should this be ErrExistOrNoAccess, for privacy?
 			return verror.New(verror.ErrExist, ctx, appName)
 		}
 		// Write new appData.
@@ -183,13 +185,15 @@
 	defer s.mu.Unlock()
 	a, ok := s.apps[appName]
 	if !ok {
-		// TODO(sadovsky): Make delete idempotent, here and elsewhere.
-		return verror.New(verror.ErrNoExistOrNoAccess, ctx, appName)
+		return nil // delete is idempotent
 	}
 
 	if err := store.RunInTransaction(s.st, func(st store.StoreReadWriter) error {
 		// Read-check-delete appData.
 		if err := util.Get(ctx, call, st, a, &appData{}); err != nil {
+			if verror.ErrorID(err) == verror.ErrNoExist.ID {
+				return nil // delete is idempotent
+			}
 			return err
 		}
 		// TODO(sadovsky): Delete all databases in this app.
@@ -207,7 +211,7 @@
 	defer s.mu.Unlock()
 	a, ok := s.apps[appName]
 	if !ok {
-		return verror.New(verror.ErrNoExistOrNoAccess, ctx, appName)
+		return verror.New(verror.ErrNoExist, ctx, appName)
 	}
 	return store.RunInTransaction(s.st, func(st store.StoreReadWriter) error {
 		data := &appData{}
diff --git a/services/syncbase/server/util/store_util.go b/services/syncbase/server/util/store_util.go
index 69be3c5..8e289d6 100644
--- a/services/syncbase/server/util/store_util.go
+++ b/services/syncbase/server/util/store_util.go
@@ -47,8 +47,7 @@
 func GetWithoutAuth(ctx *context.T, call rpc.ServerCall, st store.StoreReader, l Layer, v interface{}) error {
 	if err := GetObject(st, l.StKey(), v); err != nil {
 		if verror.ErrorID(err) == store.ErrUnknownKey.ID {
-			// TODO(sadovsky): Return ErrNoExist if appropriate.
-			return verror.New(verror.ErrNoExistOrNoAccess, ctx, l.Name())
+			return verror.New(verror.ErrNoExist, ctx, l.Name())
 		}
 		return verror.New(verror.ErrInternal, ctx, err)
 	}
@@ -63,8 +62,7 @@
 	}
 	auth, _ := access.PermissionsAuthorizer(v.GetPerms(), access.TypicalTagType())
 	if err := auth.Authorize(ctx, call.Security()); err != nil {
-		// TODO(sadovsky): Return ErrNoAccess if appropriate.
-		return verror.New(verror.ErrNoExistOrNoAccess, ctx, l.Name())
+		return verror.New(verror.ErrNoAccess, ctx, l.Name())
 	}
 	return nil
 }
diff --git a/services/syncbase/server/watchable/store.go b/services/syncbase/server/watchable/store.go
index 3e0e7d3..44faa54 100644
--- a/services/syncbase/server/watchable/store.go
+++ b/services/syncbase/server/watchable/store.go
@@ -87,10 +87,6 @@
 
 var _ Store = (*wstore)(nil)
 
-// TODO(sadovsky): Decide whether to copy []byte's vs. requiring clients not to
-// modify passed-in []byte's. (In fact, this should be spelled out in the
-// store.Store API contract.)
-
 // Close implements the store.Store interface.
 func (st *wstore) Close() error {
 	return st.ist.Close()
diff --git a/services/syncbase/server/watchable/stream.go b/services/syncbase/server/watchable/stream.go
index cd41835..9c8c392 100644
--- a/services/syncbase/server/watchable/stream.go
+++ b/services/syncbase/server/watchable/stream.go
@@ -79,7 +79,7 @@
 	s.mu.Lock()
 	defer s.mu.Unlock()
 	if s.err != nil {
-		return store.WrapError(s.err)
+		return convertError(s.err)
 	}
 	return s.iit.Err()
 }
diff --git a/services/syncbase/server/watchable/transaction.go b/services/syncbase/server/watchable/transaction.go
index 1be1b85..ace48de 100644
--- a/services/syncbase/server/watchable/transaction.go
+++ b/services/syncbase/server/watchable/transaction.go
@@ -36,7 +36,7 @@
 	tx.mu.Lock()
 	defer tx.mu.Unlock()
 	if tx.err != nil {
-		return valbuf, store.WrapError(tx.err)
+		return valbuf, convertError(tx.err)
 	}
 	var err error
 	if !tx.st.managesKey(key) {
@@ -70,7 +70,7 @@
 	tx.mu.Lock()
 	defer tx.mu.Unlock()
 	if tx.err != nil {
-		return store.WrapError(tx.err)
+		return convertError(tx.err)
 	}
 	var err error
 	if !tx.st.managesKey(key) {
@@ -87,7 +87,7 @@
 	tx.mu.Lock()
 	defer tx.mu.Unlock()
 	if tx.err != nil {
-		return store.WrapError(tx.err)
+		return convertError(tx.err)
 	}
 	var err error
 	if !tx.st.managesKey(key) {
@@ -104,7 +104,7 @@
 	tx.mu.Lock()
 	defer tx.mu.Unlock()
 	if tx.err != nil {
-		return store.WrapError(tx.err)
+		return convertError(tx.err)
 	}
 	tx.err = verror.New(verror.ErrBadState, nil, store.ErrMsgCommittedTxn)
 	tx.st.mu.Lock()
@@ -144,7 +144,7 @@
 	tx.mu.Lock()
 	defer tx.mu.Unlock()
 	if tx.err != nil {
-		return store.WrapError(tx.err)
+		return convertError(tx.err)
 	}
 	tx.err = verror.New(verror.ErrCanceled, nil, store.ErrMsgAbortedTxn)
 	return tx.itx.Abort()
diff --git a/services/syncbase/server/watchable/util.go b/services/syncbase/server/watchable/util.go
index cd37d3e..b77b15f 100644
--- a/services/syncbase/server/watchable/util.go
+++ b/services/syncbase/server/watchable/util.go
@@ -15,6 +15,7 @@
 
 	"v.io/syncbase/x/ref/services/syncbase/server/util"
 	"v.io/syncbase/x/ref/services/syncbase/store"
+	"v.io/v23/verror"
 )
 
 var rng *rand.Rand = rand.New(rand.NewSource(time.Now().UTC().UnixNano()))
@@ -71,3 +72,7 @@
 func split(key string) []string {
 	return util.SplitKeyParts(key)
 }
+
+func convertError(err error) error {
+	return verror.Convert(verror.IDAction{}, nil, err)
+}
diff --git a/services/syncbase/store/invalid_types.go b/services/syncbase/store/invalid_types.go
index 7d215b6..bb008e2 100644
--- a/services/syncbase/store/invalid_types.go
+++ b/services/syncbase/store/invalid_types.go
@@ -4,18 +4,21 @@
 
 package store
 
-// InvalidSnapshot is a store.Snapshot for which all methods return errors.
+import (
+	"v.io/v23/verror"
+)
+
+// InvalidSnapshot is a Snapshot for which all methods return errors.
 type InvalidSnapshot struct {
 	Error error // returned by all methods
 }
 
-// InvalidStream is a store.Stream for which all methods return errors.
+// InvalidStream is a Stream for which all methods return errors.
 type InvalidStream struct {
 	Error error // returned by all methods
 }
 
-// InvalidTransaction is a store.Transaction for which all methods return
-// errors.
+// InvalidTransaction is a Transaction for which all methods return errors.
 type InvalidTransaction struct {
 	Error error // returned by all methods
 }
@@ -31,12 +34,12 @@
 
 // Close implements the store.Snapshot interface.
 func (s *InvalidSnapshot) Close() error {
-	return WrapError(s.Error)
+	return convertError(s.Error)
 }
 
 // Get implements the store.StoreReader interface.
 func (s *InvalidSnapshot) Get(key, valbuf []byte) ([]byte, error) {
-	return valbuf, WrapError(s.Error)
+	return valbuf, convertError(s.Error)
 }
 
 // Scan implements the store.StoreReader interface.
@@ -64,7 +67,7 @@
 
 // Err implements the store.Stream interface.
 func (s *InvalidStream) Err() error {
-	return WrapError(s.Error)
+	return convertError(s.Error)
 }
 
 // Cancel implements the store.Stream interface.
@@ -81,7 +84,7 @@
 
 // Get implements the store.StoreReader interface.
 func (tx *InvalidTransaction) Get(key, valbuf []byte) ([]byte, error) {
-	return valbuf, WrapError(tx.Error)
+	return valbuf, convertError(tx.Error)
 }
 
 // Scan implements the store.StoreReader interface.
@@ -91,20 +94,27 @@
 
 // Put implements the store.StoreWriter interface.
 func (tx *InvalidTransaction) Put(key, value []byte) error {
-	return WrapError(tx.Error)
+	return convertError(tx.Error)
 }
 
 // Delete implements the store.StoreWriter interface.
 func (tx *InvalidTransaction) Delete(key []byte) error {
-	return WrapError(tx.Error)
+	return convertError(tx.Error)
 }
 
 // Commit implements the store.Transaction interface.
 func (tx *InvalidTransaction) Commit() error {
-	return WrapError(tx.Error)
+	return convertError(tx.Error)
 }
 
 // Abort implements the store.Transaction interface.
 func (tx *InvalidTransaction) Abort() error {
-	return WrapError(tx.Error)
+	return convertError(tx.Error)
+}
+
+////////////////////////////////////////////////////////////
+// Internal helpers
+
+func convertError(err error) error {
+	return verror.Convert(verror.IDAction{}, nil, err)
 }
diff --git a/services/syncbase/store/leveldb/db.go b/services/syncbase/store/leveldb/db.go
index 62250c6..eb8507c 100644
--- a/services/syncbase/store/leveldb/db.go
+++ b/services/syncbase/store/leveldb/db.go
@@ -31,18 +31,15 @@
 	writeOptions *C.leveldb_writeoptions_t
 	err          error
 
-	// TODO(rogulenko): decide whether we need to make a defensive copy of
-	// keys/values used by transactions.
-	// txmu protects transaction-related variables below. It is also held during
-	// commit.
-	// txmu must always be acquired before mu.
+	// txmu protects the transaction-related variables below, and is also held
+	// during transaction commits. It must always be acquired before mu.
 	txmu sync.Mutex
 	// txEvents is a queue of create/commit transaction events.
 	txEvents         *list.List
 	txSequenceNumber uint64
 	// txTable is a set of keys written by recent transactions. This set
 	// includes all write sets of transactions committed after the oldest living
-	// transaction.
+	// (in-flight) transaction.
 	txTable *trie
 }
 
@@ -81,7 +78,7 @@
 	d.mu.Lock()
 	defer d.mu.Unlock()
 	if d.err != nil {
-		return store.WrapError(d.err)
+		return convertError(d.err)
 	}
 	d.node.Close()
 	C.leveldb_close(d.cDb)
@@ -226,7 +223,7 @@
 	d.mu.RLock()
 	defer d.mu.RUnlock()
 	if d.err != nil {
-		return valbuf, store.WrapError(d.err)
+		return valbuf, convertError(d.err)
 	}
 	var cError *C.char
 	var valLen C.size_t
diff --git a/services/syncbase/store/leveldb/snapshot.go b/services/syncbase/store/leveldb/snapshot.go
index 4bf59b6..e43d67f 100644
--- a/services/syncbase/store/leveldb/snapshot.go
+++ b/services/syncbase/store/leveldb/snapshot.go
@@ -49,7 +49,7 @@
 	s.mu.Lock()
 	defer s.mu.Unlock()
 	if s.err != nil {
-		return store.WrapError(s.err)
+		return convertError(s.err)
 	}
 	s.node.Close()
 	C.leveldb_readoptions_destroy(s.cOpts)
@@ -65,7 +65,7 @@
 	s.mu.RLock()
 	defer s.mu.RUnlock()
 	if s.err != nil {
-		return valbuf, store.WrapError(s.err)
+		return valbuf, convertError(s.err)
 	}
 	return s.d.getWithOpts(key, valbuf, s.cOpts)
 }
diff --git a/services/syncbase/store/leveldb/stream.go b/services/syncbase/store/leveldb/stream.go
index 2840e5d..5775337 100644
--- a/services/syncbase/store/leveldb/stream.go
+++ b/services/syncbase/store/leveldb/stream.go
@@ -113,7 +113,7 @@
 func (s *stream) Err() error {
 	s.mu.Lock()
 	defer s.mu.Unlock()
-	return store.WrapError(s.err)
+	return convertError(s.err)
 }
 
 // Cancel implements the store.Stream interface.
diff --git a/services/syncbase/store/leveldb/transaction.go b/services/syncbase/store/leveldb/transaction.go
index 308b4da..be6ac4f 100644
--- a/services/syncbase/store/leveldb/transaction.go
+++ b/services/syncbase/store/leveldb/transaction.go
@@ -107,7 +107,7 @@
 	tx.mu.Lock()
 	defer tx.mu.Unlock()
 	if tx.err != nil {
-		return valbuf, store.WrapError(tx.err)
+		return valbuf, convertError(tx.err)
 	}
 	tx.reads.keys = append(tx.reads.keys, key)
 	return tx.snapshot.Get(key, valbuf)
@@ -132,7 +132,7 @@
 	tx.mu.Lock()
 	defer tx.mu.Unlock()
 	if tx.err != nil {
-		return store.WrapError(tx.err)
+		return convertError(tx.err)
 	}
 	tx.writes = append(tx.writes, writeOp{
 		t:     putOp,
@@ -147,7 +147,7 @@
 	tx.mu.Lock()
 	defer tx.mu.Unlock()
 	if tx.err != nil {
-		return store.WrapError(tx.err)
+		return convertError(tx.err)
 	}
 	tx.writes = append(tx.writes, writeOp{
 		t:   deleteOp,
@@ -179,7 +179,7 @@
 	tx.mu.Lock()
 	defer tx.mu.Unlock()
 	if tx.err != nil {
-		return store.WrapError(tx.err)
+		return convertError(tx.err)
 	}
 	tx.err = verror.New(verror.ErrBadState, nil, store.ErrMsgCommittedTxn)
 	// Explicitly remove this transaction from the event queue. If this was the
@@ -200,7 +200,7 @@
 	tx.mu.Lock()
 	defer tx.mu.Unlock()
 	if tx.err != nil {
-		return store.WrapError(tx.err)
+		return convertError(tx.err)
 	}
 	tx.err = verror.New(verror.ErrCanceled, nil, store.ErrMsgAbortedTxn)
 	tx.close()
diff --git a/services/syncbase/store/leveldb/util.go b/services/syncbase/store/leveldb/util.go
index dce69bb..7d92527 100644
--- a/services/syncbase/store/leveldb/util.go
+++ b/services/syncbase/store/leveldb/util.go
@@ -44,3 +44,7 @@
 	})
 	return *(*[]byte)(ptr)
 }
+
+func convertError(err error) error {
+	return verror.Convert(verror.IDAction{}, nil, err)
+}
diff --git a/services/syncbase/store/memstore/snapshot.go b/services/syncbase/store/memstore/snapshot.go
index 53bd6ef..43beffd 100644
--- a/services/syncbase/store/memstore/snapshot.go
+++ b/services/syncbase/store/memstore/snapshot.go
@@ -41,7 +41,7 @@
 	s.mu.Lock()
 	defer s.mu.Unlock()
 	if s.err != nil {
-		return store.WrapError(s.err)
+		return convertError(s.err)
 	}
 	s.node.Close()
 	s.err = verror.New(verror.ErrCanceled, nil, store.ErrMsgClosedSnapshot)
@@ -53,7 +53,7 @@
 	s.mu.Lock()
 	defer s.mu.Unlock()
 	if s.err != nil {
-		return valbuf, store.WrapError(s.err)
+		return valbuf, convertError(s.err)
 	}
 	value, ok := s.data[string(key)]
 	if !ok {
diff --git a/services/syncbase/store/memstore/store.go b/services/syncbase/store/memstore/store.go
index d24d5d1..7e3f816 100644
--- a/services/syncbase/store/memstore/store.go
+++ b/services/syncbase/store/memstore/store.go
@@ -39,7 +39,7 @@
 	st.mu.Lock()
 	defer st.mu.Unlock()
 	if st.err != nil {
-		return store.WrapError(st.err)
+		return convertError(st.err)
 	}
 	st.node.Close()
 	st.err = verror.New(verror.ErrCanceled, nil, store.ErrMsgClosedStore)
@@ -51,7 +51,7 @@
 	st.mu.Lock()
 	defer st.mu.Unlock()
 	if st.err != nil {
-		return valbuf, store.WrapError(st.err)
+		return valbuf, convertError(st.err)
 	}
 	value, ok := st.data[string(key)]
 	if !ok {
diff --git a/services/syncbase/store/memstore/stream.go b/services/syncbase/store/memstore/stream.go
index f7e5945..9ad89a6 100644
--- a/services/syncbase/store/memstore/stream.go
+++ b/services/syncbase/store/memstore/stream.go
@@ -85,7 +85,7 @@
 func (s *stream) Err() error {
 	s.mu.Lock()
 	defer s.mu.Unlock()
-	return store.WrapError(s.err)
+	return convertError(s.err)
 }
 
 // Cancel implements the store.Stream interface.
diff --git a/services/syncbase/store/memstore/transaction.go b/services/syncbase/store/memstore/transaction.go
index f7e99c4..888ce87 100644
--- a/services/syncbase/store/memstore/transaction.go
+++ b/services/syncbase/store/memstore/transaction.go
@@ -56,7 +56,7 @@
 
 func (tx *transaction) error() error {
 	if tx.err != nil {
-		return store.WrapError(tx.err)
+		return convertError(tx.err)
 	}
 	if tx.expired() {
 		return verror.New(verror.ErrBadState, nil, store.ErrMsgExpiredTxn)
diff --git a/services/syncbase/store/memstore/util.go b/services/syncbase/store/memstore/util.go
new file mode 100644
index 0000000..8dc3a7a
--- /dev/null
+++ b/services/syncbase/store/memstore/util.go
@@ -0,0 +1,13 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package memstore
+
+import (
+	"v.io/v23/verror"
+)
+
+func convertError(err error) error {
+	return verror.Convert(verror.IDAction{}, nil, err)
+}
diff --git a/services/syncbase/store/model.go b/services/syncbase/store/model.go
index 1fcdd55..e30a5f0 100644
--- a/services/syncbase/store/model.go
+++ b/services/syncbase/store/model.go
@@ -6,6 +6,9 @@
 // Currently, this API and its implementations are meant to be internal.
 package store
 
+// TODO(sadovsky): Decide whether to defensively copy passed-in []byte's vs.
+// requiring clients not to modify passed-in []byte's.
+
 // StoreReader reads data from a CRUD-capable storage engine.
 type StoreReader interface {
 	// Get returns the value for the given key. The returned slice may be a
@@ -17,6 +20,7 @@
 	Get(key, valbuf []byte) ([]byte, error)
 
 	// Scan returns all rows with keys in range [start, limit).
+	// TODO(sadovsky): Describe the behavior of Scan with concurrent writes.
 	Scan(start, limit []byte) Stream
 }
 
diff --git a/services/syncbase/store/util.go b/services/syncbase/store/util.go
index eee882a..d42a367 100644
--- a/services/syncbase/store/util.go
+++ b/services/syncbase/store/util.go
@@ -45,17 +45,3 @@
 	copy(dst, src)
 	return dst
 }
-
-// WrapError wraps the given error with a verror. It is a no-op if the given
-// error is nil. The returned error contains the current stack trace, but
-// retains the input error's IDAction pair. If the given error is not a verror,
-// the IDAction pair of the returned error will be (ErrUnknown.ID, NoRetry).
-func WrapError(err error) error {
-	if err == nil {
-		return nil
-	}
-	return verror.New(verror.IDAction{
-		verror.ErrorID(err),
-		verror.Action(err),
-	}, nil, err)
-}