Update context to track thePast

This commit is contained in:
Jae Kwon 2018-01-11 20:18:58 -08:00
parent 51e6144c0d
commit 620bdf409f

View File

@ -11,12 +11,12 @@ import (
A note on Context security:
The intent of Context is for it to be an immutable object that can be cloned
and updated cheaply with WithValue() and passed forward to the next decorator
or handler. For example,
The intent of Context is for it to be an immutable object that can be
cloned and updated cheaply with WithValue() and passed forward to the
next decorator or handler. For example,
```golang
func Decorator(ctx Context, ms MultiStore, tx Tx, next Handler) Result {
func Decorator(ctx Context, tx Tx, next Handler) Result {
// Clone and update context with new kv pair.
ctx2 := ctx.WithValueSDK(key, value)
@ -24,44 +24,59 @@ func Decorator(ctx Context, ms MultiStore, tx Tx, next Handler) Result {
// Call the next decorator/handler.
res := next(ctx2, ms, tx)
// At this point, while `ctx` and `ctx2`'s shallow values haven't changed,
// it's possible that slices or addressable struct fields have been
// modified by the call to `next(...)`.
//
// This is generally undesirable because it prevents a decorator from
// rolling back all side effects--which is the intent of immutable
// `Context`s and store cache-wraps.
...
}
```
While well-written decorators wouldn't mutate any mutable context values, a malicious or buggy plugin can create unwanted side-effects, so it is highly advised for users of Context to only set immutable values. To help enforce this contract, we require values to be certain primitive types, a Cloner, or a CacheWrapper.
While `ctx` and `ctx2`'s shallow values haven't changed, it's
possible that slices or addressable struct fields have been modified
by the call to `next(...)`.
This is generally undesirable because it prevents a decorator from
rolling back all side effects--which is the intent of immutable
Context's and store cache-wraps.
While well-written decorators wouldn't mutate any mutable context
values, a malicious or buggy plugin can create unwanted side-effects,
so it is highly advised for users of Context to only set immutable
values. To help enforce this contract, we require values to be
certain primitive types, a cloner, or a CacheWrapper.
If an outer (higher) decorator wants to know what an inner decorator
had set on the context, it can consult `context.GetOp(ver int64) Op`,
which retrieves the ver'th opertion to the context, globally since `NewContext()`.
TODO: Add a default logger.
*/
type Cloner interface {
Clone() interface{} // deep copy
}
type Context struct {
context.Context
pst *thePast
gen int
// Don't add any other fields here,
// it's probably not what you want to do.
}
func NewContext(header abci.Header, isCheckTx bool, txBytes []byte) Context {
c := Context{context.Background()}
c = c.setBlockHeader(header)
c = c.setBlockHeight(header.Height)
c = c.setChainID(header.ChainID)
c = c.setIsCheckTx(isCheckTx)
c = c.setTxBytes(txBytes)
c := Context{
Context: context.Background(),
pst: newThePast(),
gen: 0,
}
c = c.withBlockHeader(header)
c = c.withBlockHeight(header.Height)
c = c.withChainID(header.ChainID)
c = c.withIsCheckTx(isCheckTx)
c = c.withTxBytes(txBytes)
return c
}
//----------------------------------------
// Get a value
func (c Context) Value(key interface{}) interface{} {
value := c.Context.Value(key)
// XXX Cachewrap? Probably not?
if cloner, ok := value.(Cloner); ok {
if cloner, ok := value.(cloner); ok {
return cloner.Clone()
}
if message, ok := value.(proto.Message); ok {
@ -70,11 +85,14 @@ func (c Context) Value(key interface{}) interface{} {
return value
}
//----------------------------------------
// Set a value
func (c Context) WithValueUnsafe(key interface{}, value interface{}) Context {
return c.withValue(key, value)
}
func (c Context) WithCloner(key interface{}, value Cloner) Context {
func (c Context) WithCloner(key interface{}, value cloner) Context {
return c.withValue(key, value)
}
@ -103,7 +121,17 @@ func (c Context) WithUint64(key interface{}, value uint64) Context {
}
func (c Context) withValue(key interface{}, value interface{}) Context {
return Context{context.WithValue(c.Context, key, value)}
c.pst.bump(Op{
gen: c.gen + 1,
key: key,
value: value,
}) // increment version for all relatives.
return Context{
Context: context.WithValue(c.Context, key, value),
pst: c.pst,
gen: c.gen + 1,
}
}
//----------------------------------------
@ -144,27 +172,88 @@ func (c Context) KVStore(key interface{}) KVStore {
}
// Unexposed to prevent overriding.
func (c Context) setBlockHeader(header abci.Header) Context {
func (c Context) withBlockHeader(header abci.Header) Context {
var _ proto.Message = &header // for cloning.
return c.withValue(contextKeyBlockHeader, header)
}
// Unexposed to prevent overriding.
func (c Context) setBlockHeight(height int64) Context {
func (c Context) withBlockHeight(height int64) Context {
return c.withValue(contextKeyBlockHeight, height)
}
// Unexposed to prevent overriding.
func (c Context) setChainID(chainID string) Context {
func (c Context) withChainID(chainID string) Context {
return c.withValue(contextKeyChainID, chainID)
}
// Unexposed to prevent overriding.
func (c Context) setIsCheckTx(isCheckTx bool) Context {
func (c Context) withIsCheckTx(isCheckTx bool) Context {
return c.withValue(contextKeyIsCheckTx, isCheckTx)
}
// Unexposed to prevent overriding.
func (c Context) setTxBytes(txBytes []byte) Context {
func (c Context) withTxBytes(txBytes []byte) Context {
return c.withValue(contextKeyTxBytes, txBytes)
}
//----------------------------------------
// thePast
// Returns false if ver > 0.
// The first operation is version 1.
func (c Context) GetOp(ver int64) (Op, bool) {
return c.pst.getOp(ver)
}
//----------------------------------------
// Misc.
type cloner interface {
Clone() interface{} // deep copy
}
type Op struct {
// type is always 'with'
gen int
key interface{}
value interface{}
}
type thePast struct {
mtx sync.RWMutex
ver int
ops []Op
}
func newThePast() *thePast {
return &thePast{
val: 0,
ops: nil,
}
}
func (pst *thePast) bump(op Op) {
pst.mtx.Lock()
pst.ver += 1
pst.ops = append(pst.ops, op)
pst.mtx.Unlock()
}
func (pst *thePast) version() int {
pst.mtx.RLock()
defer pst.mtx.RUnlock()
return pst.ver
}
// Returns false if ver > 0.
// The first operation is version 1.
func (pst *thePast) getOp(ver int) (Op, bool) {
pst.mtx.RLock()
defer pst.mtx.RUnlock()
if len(pst.ops) < ver {
return Op{}, false
} else {
return pst.ops[ver-1], true
}
}