-
Notifications
You must be signed in to change notification settings - Fork 661
/
manager.go
205 lines (172 loc) · 5.04 KB
/
manager.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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package executor
import (
"errors"
"github.com/ava-labs/avalanchego/chains/atomic"
"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/snow/consensus/snowman"
"github.com/ava-labs/avalanchego/utils/set"
"github.com/ava-labs/avalanchego/utils/timer/mockable"
"github.com/ava-labs/avalanchego/vms/avm/block"
"github.com/ava-labs/avalanchego/vms/avm/metrics"
"github.com/ava-labs/avalanchego/vms/avm/state"
"github.com/ava-labs/avalanchego/vms/avm/txs"
"github.com/ava-labs/avalanchego/vms/avm/txs/executor"
"github.com/ava-labs/avalanchego/vms/avm/txs/mempool"
)
var (
_ Manager = (*manager)(nil)
ErrChainNotSynced = errors.New("chain not synced")
ErrConflictingParentTxs = errors.New("block contains a transaction that conflicts with a transaction in a parent block")
)
type Manager interface {
state.Versions
// Returns the ID of the most recently accepted block.
LastAccepted() ids.ID
SetPreference(blkID ids.ID)
Preferred() ids.ID
GetBlock(blkID ids.ID) (snowman.Block, error)
GetStatelessBlock(blkID ids.ID) (block.Block, error)
NewBlock(block.Block) snowman.Block
// VerifyTx verifies that the transaction can be issued based on the currently
// preferred state. This should *not* be used to verify transactions in a block.
VerifyTx(tx *txs.Tx) error
// VerifyUniqueInputs returns nil iff no blocks in the inclusive
// ancestry of [blkID] consume an input in [inputs].
VerifyUniqueInputs(blkID ids.ID, inputs set.Set[ids.ID]) error
}
func NewManager(
mempool mempool.Mempool,
metrics metrics.Metrics,
state state.State,
backend *executor.Backend,
clk *mockable.Clock,
onAccept func(*txs.Tx) error,
) Manager {
lastAccepted := state.GetLastAccepted()
return &manager{
backend: backend,
state: state,
metrics: metrics,
mempool: mempool,
clk: clk,
onAccept: onAccept,
blkIDToState: map[ids.ID]*blockState{},
lastAccepted: lastAccepted,
preferred: lastAccepted,
}
}
type manager struct {
backend *executor.Backend
state state.State
metrics metrics.Metrics
mempool mempool.Mempool
clk *mockable.Clock
// Invariant: onAccept is called when [tx] is being marked as accepted, but
// before its state changes are applied.
// Invariant: any error returned by onAccept should be considered fatal.
onAccept func(*txs.Tx) error
// blkIDToState is a map from a block's ID to the state of the block.
// Blocks are put into this map when they are verified.
// Blocks are removed from this map when they are decided.
blkIDToState map[ids.ID]*blockState
// lastAccepted is the ID of the last block that had Accept() called on it.
lastAccepted ids.ID
preferred ids.ID
}
type blockState struct {
statelessBlock block.Block
onAcceptState state.Diff
importedInputs set.Set[ids.ID]
atomicRequests map[ids.ID]*atomic.Requests
}
func (m *manager) GetState(blkID ids.ID) (state.Chain, bool) {
// If the block is in the map, it is processing.
if state, ok := m.blkIDToState[blkID]; ok {
return state.onAcceptState, true
}
return m.state, blkID == m.lastAccepted
}
func (m *manager) LastAccepted() ids.ID {
return m.lastAccepted
}
func (m *manager) SetPreference(blockID ids.ID) {
m.preferred = blockID
}
func (m *manager) Preferred() ids.ID {
return m.preferred
}
func (m *manager) GetBlock(blkID ids.ID) (snowman.Block, error) {
blk, err := m.GetStatelessBlock(blkID)
if err != nil {
return nil, err
}
return m.NewBlock(blk), nil
}
func (m *manager) GetStatelessBlock(blkID ids.ID) (block.Block, error) {
// See if the block is in memory.
if blkState, ok := m.blkIDToState[blkID]; ok {
return blkState.statelessBlock, nil
}
// The block isn't in memory. Check the database.
return m.state.GetBlock(blkID)
}
func (m *manager) NewBlock(blk block.Block) snowman.Block {
return &Block{
Block: blk,
manager: m,
}
}
func (m *manager) VerifyTx(tx *txs.Tx) error {
if !m.backend.Bootstrapped {
return ErrChainNotSynced
}
err := tx.Unsigned.Visit(&executor.SyntacticVerifier{
Backend: m.backend,
Tx: tx,
})
if err != nil {
return err
}
stateDiff, err := state.NewDiff(m.lastAccepted, m)
if err != nil {
return err
}
err = tx.Unsigned.Visit(&executor.SemanticVerifier{
Backend: m.backend,
State: stateDiff,
Tx: tx,
})
if err != nil {
return err
}
executor := &executor.Executor{
Codec: m.backend.Codec,
State: stateDiff,
Tx: tx,
}
return tx.Unsigned.Visit(executor)
}
func (m *manager) VerifyUniqueInputs(blkID ids.ID, inputs set.Set[ids.ID]) error {
if inputs.Len() == 0 {
return nil
}
// Check for conflicts in ancestors.
for {
state, ok := m.blkIDToState[blkID]
if !ok {
// The parent state isn't pinned in memory.
// This means the parent must be accepted already.
return nil
}
if state.importedInputs.Overlaps(inputs) {
return ErrConflictingParentTxs
}
blk := state.statelessBlock
blkID = blk.Parent()
}
}
func (m *manager) free(blkID ids.ID) {
delete(m.blkIDToState, blkID)
}