Skip to content
This repository was archived by the owner on Jan 20, 2026. It is now read-only.

Commit b43a12a

Browse files
committed
Add store tracer
1 parent 306519a commit b43a12a

7 files changed

Lines changed: 253 additions & 24 deletions

File tree

store/gaskv/store.go

Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,34 @@ import (
88
"github.com/cosmos/cosmos-sdk/telemetry"
99
)
1010

11+
type IStoreTracer interface {
12+
Get([]byte, []byte, string)
13+
Has([]byte, string)
14+
Set([]byte, []byte, string)
15+
Delete([]byte, string)
16+
DerivePrestateToJson() []byte
17+
}
18+
1119
var _ types.KVStore = &Store{}
1220

1321
// Store applies gas tracking to an underlying KVStore. It implements the
1422
// KVStore interface.
1523
type Store struct {
16-
gasMeter types.GasMeter
17-
gasConfig types.GasConfig
18-
parent types.KVStore
24+
gasMeter types.GasMeter
25+
gasConfig types.GasConfig
26+
parent types.KVStore
27+
moduleName string
28+
tracer IStoreTracer
1929
}
2030

2131
// NewStore returns a reference to a new GasKVStore.
22-
func NewStore(parent types.KVStore, gasMeter types.GasMeter, gasConfig types.GasConfig) *Store {
32+
func NewStore(parent types.KVStore, gasMeter types.GasMeter, gasConfig types.GasConfig, moduleName string, tracer IStoreTracer) *Store {
2333
kvs := &Store{
24-
gasMeter: gasMeter,
25-
gasConfig: gasConfig,
26-
parent: parent,
34+
gasMeter: gasMeter,
35+
gasConfig: gasConfig,
36+
parent: parent,
37+
moduleName: moduleName,
38+
tracer: tracer,
2739
}
2840
return kvs
2941
}
@@ -45,6 +57,9 @@ func (gs *Store) Get(key []byte) (value []byte) {
4557
// TODO overflow-safe math?
4658
gs.gasMeter.ConsumeGas(gs.gasConfig.ReadCostPerByte*types.Gas(len(key)), types.GasReadPerByteDesc)
4759
gs.gasMeter.ConsumeGas(gs.gasConfig.ReadCostPerByte*types.Gas(len(value)), types.GasReadPerByteDesc)
60+
if gs.tracer != nil {
61+
gs.tracer.Get(key, value, gs.moduleName)
62+
}
4863

4964
return value
5065
}
@@ -64,7 +79,11 @@ func (gs *Store) Set(key []byte, value []byte) {
6479
func (gs *Store) Has(key []byte) bool {
6580
defer telemetry.MeasureSince(time.Now(), "store", "gaskv", "has")
6681
gs.gasMeter.ConsumeGas(gs.gasConfig.HasCost, types.GasHasDesc)
67-
return gs.parent.Has(key)
82+
res := gs.parent.Has(key)
83+
if gs.tracer != nil && res {
84+
gs.tracer.Has(key, gs.moduleName)
85+
}
86+
return res
6887
}
6988

7089
// Implements KVStore.
@@ -113,7 +132,7 @@ func (gs *Store) iterator(start, end []byte, ascending bool) types.Iterator {
113132
parent = gs.parent.ReverseIterator(start, end)
114133
}
115134

116-
gi := newGasIterator(gs.gasMeter, gs.gasConfig, parent)
135+
gi := newGasIterator(gs.gasMeter, gs.gasConfig, parent, gs.moduleName, gs.tracer)
117136
defer func() {
118137
if err := recover(); err != nil {
119138
// if there is a panic, we close the iterator then reraise
@@ -139,16 +158,20 @@ func (gs *Store) GetAllKeyStrsInRange(start, end []byte) (res []string) {
139158
}
140159

141160
type gasIterator struct {
142-
gasMeter types.GasMeter
143-
gasConfig types.GasConfig
144-
parent types.Iterator
161+
gasMeter types.GasMeter
162+
gasConfig types.GasConfig
163+
parent types.Iterator
164+
moduleName string
165+
tracer IStoreTracer
145166
}
146167

147-
func newGasIterator(gasMeter types.GasMeter, gasConfig types.GasConfig, parent types.Iterator) types.Iterator {
168+
func newGasIterator(gasMeter types.GasMeter, gasConfig types.GasConfig, parent types.Iterator, moduleName string, tracer IStoreTracer) types.Iterator {
148169
return &gasIterator{
149-
gasMeter: gasMeter,
150-
gasConfig: gasConfig,
151-
parent: parent,
170+
gasMeter: gasMeter,
171+
gasConfig: gasConfig,
172+
parent: parent,
173+
moduleName: moduleName,
174+
tracer: tracer,
152175
}
153176
}
154177

@@ -174,13 +197,19 @@ func (gi *gasIterator) Next() {
174197
// not incur any gas cost.
175198
func (gi *gasIterator) Key() (key []byte) {
176199
key = gi.parent.Key()
200+
if gi.tracer != nil {
201+
gi.tracer.Has(key, gi.moduleName)
202+
}
177203
return key
178204
}
179205

180206
// Value implements the Iterator interface. It returns the current value and it
181207
// does not incur any gas cost.
182208
func (gi *gasIterator) Value() (value []byte) {
183209
value = gi.parent.Value()
210+
if gi.tracer != nil {
211+
gi.tracer.Get(gi.Key(), value, gi.moduleName)
212+
}
184213
return value
185214
}
186215

store/gaskv/store_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ func valFmt(i int) []byte { return bz(fmt.Sprintf("value%0.8d", i)) }
2020
func TestGasKVStoreBasic(t *testing.T) {
2121
mem := dbadapter.Store{DB: dbm.NewMemDB()}
2222
meter := types.NewMultiplierGasMeter(10000, 1, 1)
23-
st := gaskv.NewStore(mem, meter, types.KVGasConfig())
23+
st := gaskv.NewStore(mem, meter, types.KVGasConfig(), "", nil)
2424

2525
require.Equal(t, types.StoreTypeDB, st.GetStoreType())
2626
require.Panics(t, func() { st.CacheWrap(nil) })
@@ -41,7 +41,7 @@ func TestGasKVStoreBasic(t *testing.T) {
4141
func TestGasKVStoreIterator(t *testing.T) {
4242
mem := dbadapter.Store{DB: dbm.NewMemDB()}
4343
meter := types.NewMultiplierGasMeter(100000, 1, 1)
44-
st := gaskv.NewStore(mem, meter, types.KVGasConfig())
44+
st := gaskv.NewStore(mem, meter, types.KVGasConfig(), "", nil)
4545
require.False(t, st.Has(keyFmt(1)))
4646
require.Empty(t, st.Get(keyFmt(1)), "Expected `key1` to be empty")
4747
require.Empty(t, st.Get(keyFmt(2)), "Expected `key2` to be empty")
@@ -109,14 +109,14 @@ func TestGasKVStoreIterator(t *testing.T) {
109109
func TestGasKVStoreOutOfGasSet(t *testing.T) {
110110
mem := dbadapter.Store{DB: dbm.NewMemDB()}
111111
meter := types.NewMultiplierGasMeter(0, 1, 1)
112-
st := gaskv.NewStore(mem, meter, types.KVGasConfig())
112+
st := gaskv.NewStore(mem, meter, types.KVGasConfig(), "", nil)
113113
require.Panics(t, func() { st.Set(keyFmt(1), valFmt(1)) }, "Expected out-of-gas")
114114
}
115115

116116
func TestGasKVStoreOutOfGasIterator(t *testing.T) {
117117
mem := dbadapter.Store{DB: dbm.NewMemDB()}
118118
meter := types.NewMultiplierGasMeter(20000, 1, 1)
119-
st := gaskv.NewStore(mem, meter, types.KVGasConfig())
119+
st := gaskv.NewStore(mem, meter, types.KVGasConfig(), "", nil)
120120
st.Set(keyFmt(1), valFmt(1))
121121
iterator := st.Iterator(nil, nil)
122122
iterator.Next()

store/prefix/store_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ func TestIAVLStorePrefix(t *testing.T) {
100100
func TestPrefixKVStoreNoNilSet(t *testing.T) {
101101
meter := types.NewMultiplierGasMeter(100000000, 1, 1)
102102
mem := dbadapter.Store{DB: dbm.NewMemDB()}
103-
gasStore := gaskv.NewStore(mem, meter, types.KVGasConfig())
103+
gasStore := gaskv.NewStore(mem, meter, types.KVGasConfig(), "", nil)
104104
require.Panics(t, func() { gasStore.Set([]byte("key"), nil) }, "setting a nil value should panic")
105105
}
106106

types/context.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@ type Context struct {
6868

6969
traceSpanContext context.Context
7070

71-
isTracing bool
71+
isTracing bool
72+
storeTracer gaskv.IStoreTracer
7273
}
7374

7475
// Proposed rename, not done to avoid API breakage
@@ -229,6 +230,13 @@ func (c Context) IsTracing() bool {
229230
return c.isTracing
230231
}
231232

233+
func (c Context) StoreTracer() gaskv.IStoreTracer {
234+
if c.storeTracer == nil {
235+
return nil
236+
}
237+
return c.storeTracer
238+
}
239+
232240
// WithEventManager returns a Context with an updated tx priority
233241
func (c Context) WithPriority(p int64) Context {
234242
c.priority = p
@@ -502,6 +510,9 @@ func (c Context) WithExpireTxHandler(expireTxHandler func()) Context {
502510

503511
func (c Context) WithIsTracing(it bool) Context {
504512
c.isTracing = it
513+
if it {
514+
c.storeTracer = NewStoreTracer()
515+
}
505516
return c
506517
}
507518

@@ -541,12 +552,12 @@ func (c Context) Value(key interface{}) interface{} {
541552

542553
// KVStore fetches a KVStore from the MultiStore.
543554
func (c Context) KVStore(key StoreKey) KVStore {
544-
return gaskv.NewStore(c.MultiStore().GetKVStore(key), c.GasMeter(), stypes.KVGasConfig())
555+
return gaskv.NewStore(c.MultiStore().GetKVStore(key), c.GasMeter(), stypes.KVGasConfig(), key.Name(), c.StoreTracer())
545556
}
546557

547558
// TransientStore fetches a TransientStore from the MultiStore.
548559
func (c Context) TransientStore(key StoreKey) KVStore {
549-
return gaskv.NewStore(c.MultiStore().GetKVStore(key), c.GasMeter(), stypes.TransientGasConfig())
560+
return gaskv.NewStore(c.MultiStore().GetKVStore(key), c.GasMeter(), stypes.TransientGasConfig(), key.Name(), c.StoreTracer())
550561
}
551562

552563
// CacheContext returns a new Context with the multi-store cached and a new

types/tracer.go

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package types
2+
3+
import (
4+
"encoding/hex"
5+
"encoding/json"
6+
)
7+
8+
type StoreTracer struct {
9+
Modules map[string]*ModuleTrace
10+
}
11+
12+
type ModuleTrace struct {
13+
Accesses []Access
14+
}
15+
16+
type Access struct {
17+
Op OpType
18+
Key []byte
19+
Value []byte
20+
}
21+
22+
type OpType int
23+
24+
const (
25+
Get OpType = iota
26+
Has
27+
Set
28+
Delete
29+
)
30+
31+
func NewStoreTracer() *StoreTracer {
32+
return &StoreTracer{
33+
Modules: map[string]*ModuleTrace{},
34+
}
35+
}
36+
37+
func (st *StoreTracer) Get(key []byte, value []byte, module string) {
38+
accesses := st.getOrSetModuleTrace(module)
39+
accesses.Accesses = append(accesses.Accesses, Access{
40+
Op: Get,
41+
Key: key,
42+
Value: value,
43+
})
44+
}
45+
46+
func (st *StoreTracer) Set(key []byte, value []byte, module string) {
47+
accesses := st.getOrSetModuleTrace(module)
48+
accesses.Accesses = append(accesses.Accesses, Access{
49+
Op: Set,
50+
Key: key,
51+
Value: value,
52+
})
53+
}
54+
55+
func (st *StoreTracer) Has(key []byte, module string) {
56+
accesses := st.getOrSetModuleTrace(module)
57+
accesses.Accesses = append(accesses.Accesses, Access{
58+
Op: Has,
59+
Key: key,
60+
})
61+
}
62+
63+
func (st *StoreTracer) Delete(key []byte, module string) {
64+
accesses := st.getOrSetModuleTrace(module)
65+
accesses.Accesses = append(accesses.Accesses, Access{
66+
Op: Delete,
67+
Key: key,
68+
})
69+
}
70+
71+
func (st *StoreTracer) getOrSetModuleTrace(module string) (mt *ModuleTrace) {
72+
if _, ok := st.Modules[module]; !ok {
73+
mt = &ModuleTrace{
74+
Accesses: []Access{},
75+
}
76+
st.Modules[module] = mt
77+
} else {
78+
mt = st.Modules[module]
79+
}
80+
return
81+
}
82+
83+
type StoreTraceDump struct {
84+
Modules map[string]ModuleTraceDump `json:"modules"`
85+
}
86+
87+
type ModuleTraceDump struct {
88+
Reads map[string]string `json:"reads"`
89+
Has []string `json:"has"`
90+
}
91+
92+
func (st *StoreTracer) DerivePrestateToJson() []byte {
93+
d := StoreTraceDump{
94+
Modules: make(map[string]ModuleTraceDump, len(st.Modules)),
95+
}
96+
for name, module := range st.Modules {
97+
mtd := ModuleTraceDump{
98+
Reads: make(map[string]string),
99+
Has: []string{},
100+
}
101+
// any read for key XYZ after a Set/Delete to XYZ is discarded
102+
// because the result doesn't represent prestate.
103+
writtenKey := map[string]struct{}{}
104+
hasMap := map[string]struct{}{}
105+
for _, a := range module.Accesses {
106+
switch a.Op {
107+
case Get:
108+
if _, ok := writtenKey[string(a.Key)]; ok {
109+
continue
110+
}
111+
// no need to check if it's already in dump because the value
112+
// must be the same if there is no preceding write
113+
mtd.Reads[hex.EncodeToString(a.Key)] = hex.EncodeToString(a.Value)
114+
case Has:
115+
if _, ok := writtenKey[string(a.Key)]; ok {
116+
continue
117+
}
118+
hasMap[hex.EncodeToString(a.Key)] = struct{}{}
119+
default:
120+
writtenKey[string(a.Key)] = struct{}{}
121+
}
122+
}
123+
for k := range hasMap {
124+
mtd.Has = append(mtd.Has, k)
125+
}
126+
d.Modules[name] = mtd
127+
}
128+
bz, err := json.Marshal(&d)
129+
if err != nil {
130+
panic(err)
131+
}
132+
return bz
133+
}

x/bank/keeper/keeper.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ type Keeper interface {
5353
UndelegateCoins(ctx sdk.Context, moduleAccAddr, delegatorAddr sdk.AccAddress, amt sdk.Coins) error
5454

5555
GetStoreKey() sdk.StoreKey
56+
GetCdc() codec.BinaryCodec
5657

5758
types.QueryServer
5859
}
@@ -684,6 +685,11 @@ func (k BaseKeeper) GetStoreKey() sdk.StoreKey {
684685
return k.storeKey
685686
}
686687

688+
// for testing
689+
func (k BaseKeeper) GetCdc() codec.BinaryCodec {
690+
return k.cdc
691+
}
692+
687693
// IterateTotalSupply iterates over the total supply calling the given cb (callback) function
688694
// with the balance of each coin.
689695
// The iteration stops if the callback returns true.

0 commit comments

Comments
 (0)