summaryrefslogtreecommitdiff
path: root/ledger/simulation/initialStates.go
blob: e374719ccc9196438c455fb4d6deb84d1ae12107 (plain)
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
171
172
173
174
175
176
177
178
179
// Copyright (C) 2019-2023 Algorand, Inc.
// This file is part of go-algorand
//
// go-algorand is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// go-algorand is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with go-algorand.  If not, see <https://www.gnu.org/licenses/>.

package simulation

import (
	"github.com/algorand/go-algorand/data/basics"
	"github.com/algorand/go-algorand/data/transactions/logic"
	"github.com/algorand/go-algorand/util"
)

// AppKVPairs constructs a KV pair between state key and state value
type AppKVPairs map[string]basics.TealValue

// SingleAppInitialStates gathers all relevant application on-chain states, including
// - Application Box states
// - Application Global states
// - Application Local states (which is tied to basics.Address)
type SingleAppInitialStates struct {
	AppBoxes     AppKVPairs
	CreatedBoxes util.Set[string]

	AppGlobals     AppKVPairs
	CreatedGlobals util.Set[string]

	AppLocals     map[basics.Address]AppKVPairs
	CreatedLocals map[basics.Address]util.Set[string]
}

// AppsInitialStates maintains a map from basics.AppIndex to SingleAppInitialStates
type AppsInitialStates map[basics.AppIndex]SingleAppInitialStates

// ResourcesInitialStates gathers all initial states of resources that were accessed during simulation
type ResourcesInitialStates struct {
	// AllAppsInitialStates gathers all initial states of apps that were touched (but not created) during simulation
	AllAppsInitialStates AppsInitialStates
	// CreatedApp gathers all created applications by appID, blocking initial app states in these apps being recorded
	CreatedApp util.Set[basics.AppIndex]
}

func newResourcesInitialStates(request Request) *ResourcesInitialStates {
	if !request.TraceConfig.State {
		return nil
	}
	return &ResourcesInitialStates{
		AllAppsInitialStates: make(AppsInitialStates),
		CreatedApp:           make(util.Set[basics.AppIndex]),
	}
}

// hasBeenRecorded checks if an application state kv-pair has been recorded in SingleAppInitialStates.
func (appIS SingleAppInitialStates) hasBeenRecorded(state logic.AppStateEnum, key string, addr basics.Address) (recorded bool) {
	switch state {
	case logic.BoxState:
		_, recorded = appIS.AppBoxes[key]
	case logic.GlobalState:
		_, recorded = appIS.AppGlobals[key]
	case logic.LocalState:
		if kvs, addrLocalExists := appIS.AppLocals[addr]; addrLocalExists {
			_, recorded = kvs[key]
		}
	}
	return
}

// hasBeenCreated checks if an application state kv-pair has been created during simulation.
func (appIS SingleAppInitialStates) hasBeenCreated(state logic.AppStateEnum, key string, addr basics.Address) (created bool) {
	switch state {
	case logic.BoxState:
		created = appIS.CreatedBoxes.Contains(key)
	case logic.GlobalState:
		created = appIS.CreatedGlobals.Contains(key)
	case logic.LocalState:
		if kvs, addrLocalExists := appIS.CreatedLocals[addr]; addrLocalExists {
			created = kvs.Contains(key)
		}
	}
	return
}

// recordCreation records a newly created application state kv-pair in SingleAppInitialStates during simulation.
func (appIS SingleAppInitialStates) recordCreation(state logic.AppStateEnum, key string, addr basics.Address) {
	switch state {
	case logic.BoxState:
		appIS.CreatedBoxes.Add(key)
	case logic.GlobalState:
		appIS.CreatedGlobals.Add(key)
	case logic.LocalState:
		if _, addrLocalExists := appIS.CreatedLocals[addr]; !addrLocalExists {
			appIS.CreatedLocals[addr] = make(util.Set[string])
		}
		appIS.CreatedLocals[addr].Add(key)
	}
}

func (appsIS AppsInitialStates) increment(cx *logic.EvalContext) {
	appState, stateOp, appID, acctAddr, stateKey := cx.GetOpSpec().AppStateExplain(cx)
	// No matter read or write, once this code-path is triggered, something must be recorded into initial state
	if _, ok := appsIS[appID]; !ok {
		appsIS[appID] = SingleAppInitialStates{
			AppGlobals:     make(AppKVPairs),
			CreatedGlobals: make(util.Set[string]),

			AppBoxes:     make(AppKVPairs),
			CreatedBoxes: make(util.Set[string]),

			AppLocals:     make(map[basics.Address]AppKVPairs),
			CreatedLocals: make(map[basics.Address]util.Set[string]),
		}
	}

	// if the state has been recorded, pass
	if appsIS[appID].hasBeenRecorded(appState, stateKey, acctAddr) {
		return
	}

	// if this state is created during simulation, pass
	if appsIS[appID].hasBeenCreated(appState, stateKey, acctAddr) {
		return
	}

	tv := logic.AppStateQuerying(cx, appState, stateOp, appID, acctAddr, stateKey)
	switch stateOp {
	case logic.AppStateWrite:
		// if the unrecorded value to write to is nil, pass
		// this case means it is creating a state
		if tv == (basics.TealValue{}) {
			appsIS[appID].recordCreation(appState, stateKey, acctAddr)
			return
		}
		fallthrough
	case logic.AppStateDelete:
		fallthrough
	case logic.AppStateRead:
		switch appState {
		case logic.BoxState:
			appsIS[appID].AppBoxes[stateKey] = tv
		case logic.GlobalState:
			appsIS[appID].AppGlobals[stateKey] = tv
		case logic.LocalState:
			if appsIS[appID].AppLocals[acctAddr] == nil {
				appsIS[appID].AppLocals[acctAddr] = make(AppKVPairs)
			}
			appsIS[appID].AppLocals[acctAddr][stateKey] = tv
		}
	}
}

// increment is the entry point of (potentially) adding new initial states to ResourcesInitialStates during simulation.
// This method is the top entry point of simulate-initial-state, for ResourcesInitialStates captures all initial states.
// By checking if current opcode has related `Explain` function, this method dispatch incrementing initial states by:
// +- AppStateExplain exists, then dispatch to AppsInitialStates.increment.
func (is *ResourcesInitialStates) increment(cx *logic.EvalContext) {
	// This method only applies on logic.ModeApp
	if cx.RunMode() == logic.ModeSig {
		return
	}
	// If this method triggers application state changes
	if cx.GetOpSpec().AppStateExplain != nil {
		if is.CreatedApp.Contains(cx.AppID()) {
			return
		}
		is.AllAppsInitialStates.increment(cx)
	}
	// TODO asset?
}