Update context to track thePast
This commit is contained in:
parent
51e6144c0d
commit
620bdf409f
151
types/context.go
151
types/context.go
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user