-
Notifications
You must be signed in to change notification settings - Fork 3.4k
/
dispatcher.go
170 lines (145 loc) · 4.61 KB
/
dispatcher.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
package stack
import (
"fmt"
"sort"
"strings"
abci "github.com/tendermint/abci/types"
"github.com/tendermint/tmlibs/log"
sdk "github.com/cosmos/cosmos-sdk"
"github.com/cosmos/cosmos-sdk/errors"
"github.com/cosmos/cosmos-sdk/state"
)
// nolint
const (
NameDispatcher = "disp"
)
// Dispatcher grabs a bunch of Dispatchables and groups them into one Handler.
//
// It will route tx to the proper locations and also allows them to call each
// other synchronously through the same tx methods.
//
// Please note that iterating through a map is a non-deteministic operation
// and, as such, should never be done in the context of an ABCI app. Only
// use this map to look up an exact route by name.
type Dispatcher struct {
routes map[string]Dispatchable
}
// NewDispatcher creates a dispatcher and adds the given routes.
// You can also add routes later with .AddRoutes()
func NewDispatcher(routes ...Dispatchable) *Dispatcher {
d := &Dispatcher{
routes: map[string]Dispatchable{},
}
d.AddRoutes(routes...)
return d
}
var _ sdk.Handler = new(Dispatcher)
// AddRoutes registers all these dispatchable choices under their subdomains
//
// Panics on attempt to double-register a route name, as this is a configuration error.
// Should I retrun an error instead?
func (d *Dispatcher) AddRoutes(routes ...Dispatchable) {
for _, r := range routes {
name := r.Name()
if _, ok := d.routes[name]; ok {
panic(fmt.Sprintf("%s already registered with dispatcher", name))
}
d.routes[name] = r
}
}
// Name - defines the name of this module
func (d *Dispatcher) Name() string {
return NameDispatcher
}
// CheckTx - implements Handler interface
//
// Tries to find a registered module (Dispatchable) based on the name of the tx.
// The tx name (as registered with go-data) should be in the form `<module name>/XXXX`,
// where `module name` must match the name of a dispatchable and XXX can be any string.
func (d *Dispatcher) CheckTx(ctx sdk.Context, store state.SimpleDB, tx sdk.Tx) (res sdk.CheckResult, err error) {
r, err := d.lookupTx(tx)
if err != nil {
return res, err
}
// make sure no monkey business with the context
cb := secureCheck(d, ctx)
// and isolate the permissions and the data store for this app
ctx = withApp(ctx, r.Name())
store = stateSpace(store, r.Name())
return r.CheckTx(ctx, store, tx, cb)
}
// DeliverTx - implements Handler interface
//
// Tries to find a registered module (Dispatchable) based on the name of the tx.
// The tx name (as registered with go-data) should be in the form `<module name>/XXXX`,
// where `module name` must match the name of a dispatchable and XXX can be any string.
func (d *Dispatcher) DeliverTx(ctx sdk.Context, store state.SimpleDB, tx sdk.Tx) (res sdk.DeliverResult, err error) {
r, err := d.lookupTx(tx)
if err != nil {
return res, err
}
// make sure no monkey business with the context
cb := secureDeliver(d, ctx)
// and isolate the permissions and the data store for this app
ctx = withApp(ctx, r.Name())
store = stateSpace(store, r.Name())
return r.DeliverTx(ctx, store, tx, cb)
}
// InitState - implements Handler interface
//
// Tries to find a registered module (Dispatchable) based on the
// module name from InitState of the tx.
func (d *Dispatcher) InitState(l log.Logger, store state.SimpleDB, module, key, value string) (string, error) {
r, err := d.lookupModule(module)
if err != nil {
return "", err
}
// no ctx, so secureCheck not needed
cb := d
// but isolate data space
store = stateSpace(store, r.Name())
return r.InitState(l, store, module, key, value, cb)
}
// InitValidate makes sure all modules are informed
func (d *Dispatcher) InitValidate(log log.Logger, store state.SimpleDB, vals []*abci.Validator) {
for _, mod := range d.sortedModules() {
// no ctx, so secureCheck not needed
cb := d
space := stateSpace(store, mod.Name())
mod.InitValidate(log, space, vals, cb)
}
}
func (d *Dispatcher) lookupTx(tx sdk.Tx) (Dispatchable, error) {
kind, err := tx.GetKind()
if err != nil {
return nil, err
}
// grab everything before the /
name := strings.SplitN(kind, "/", 2)[0]
r, ok := d.routes[name]
if !ok {
return nil, errors.ErrUnknownTxType(tx)
}
return r, nil
}
func (d *Dispatcher) lookupModule(name string) (Dispatchable, error) {
r, ok := d.routes[name]
if !ok {
return nil, errors.ErrUnknownModule(name)
}
return r, nil
}
func (d *Dispatcher) sortedModules() []Dispatchable {
// order all routes names
size := len(d.routes)
names := make([]string, 0, size)
for k := range d.routes {
names = append(names, k)
}
sort.Strings(names)
res := make([]Dispatchable, size)
for i, k := range names {
res[i] = d.routes[k]
}
return res
}