-
Notifications
You must be signed in to change notification settings - Fork 126
/
Copy pathblock.go
436 lines (372 loc) · 14.3 KB
/
block.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
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
// Copyright (C) 2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package snow
import (
"context"
"errors"
"fmt"
"time"
"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/snow/consensus/snowman"
"github.com/ava-labs/avalanchego/snow/engine/snowman/block"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
"go.uber.org/zap"
"github.com/ava-labs/hypersdk/event"
)
var (
_ snowman.Block = (*StatefulBlock[Block, Block, Block])(nil)
_ block.WithVerifyContext = (*StatefulBlock[Block, Block, Block])(nil)
errParentFailedVerification = errors.New("parent failed verification")
errMismatchedPChainContext = errors.New("mismatched P-Chain context")
)
// Block is a union of methods required by snowman.Block and block.WithVerifyContext
type Block interface {
// GetID returns the ID of the block
GetID() ids.ID
// GetParent returns the ID of the parent block
GetParent() ids.ID
// GetTimestamp returns the timestamp of the block
GetTimestamp() int64
// GetBytes returns the bytes of the block
GetBytes() []byte
// GetHeight returns the height of the block
GetHeight() uint64
// GetContext returns the P-Chain context of the block.
// May return nil if there is no P-Chain context, which
// should only occur prior to ProposerVM activation.
// This will be verified from the snow package, so that the
// inner chain can simply use its embedded context.
GetContext() *block.Context
fmt.Stringer
}
// StatefulBlock implements snowman.Block.
// It abstracts caching and block pinning required by the AvalancheGo Consensus engine.
// This converts the VM DevX from implementing the consensus engine-specific invariants
// to implementing an input/output/accepted block type and handling the state transitions
// between these types.
// In conjunction with the AvalancheGo Consensus engine, this code guarantees that
// 1. Verify is always called against a verified parent
// 2. Accept is always called against a verified block
// 3. Reject is always called against a verified block
//
// StatefulBlock additionally handles DynamicStateSync where blocks are vacuously
// verified/accepted to update a moving state sync target.
// After FinishStateSync is called, the snow package guarantees the same invariants
// as applied during normal consensus.
//
// [snowman.Block]: https://github.com/ava-labs/avalanchego/blob/abb1a9a6a21c3dbce6dff5cdcea03173119a5f46/snow/consensus/snowman/block.go#L24
type StatefulBlock[I Block, O Block, A Block] struct {
Input I
Output O
verified bool
Accepted A
accepted bool
vm *VM[I, O, A]
}
// NewInputBlock creates a new unverified StatefulBlock.
//
// Returns:
// - A new StatefulBlock containing only the input block
//
// This function emulates the initial state of a block that has been built
// but not verified
func NewInputBlock[I Block, O Block, A Block](
vm *VM[I, O, A],
input I,
) *StatefulBlock[I, O, A] {
return &StatefulBlock[I, O, A]{
vm: vm,
Input: input,
}
}
// NewVerifiedBlock creates a StatefulBlock after a block has been built and verified but prior to being accepted/rejected by consensus.
//
// Returns:
// - A new verified StatefulBlock containing the input block, output block with state transitions
//
// This function emulates the state of a block that has passed verification
// but has not yet been accepted into the blockchain by consensus
func NewVerifiedBlock[I Block, O Block, A Block](
vm *VM[I, O, A],
input I,
output O,
) *StatefulBlock[I, O, A] {
return &StatefulBlock[I, O, A]{
Input: input,
Output: output,
verified: true,
vm: vm,
}
}
// NewAcceptedBlock creates a new StatefulBlock accepted by consensus and committed to the chain.
//
// Returns:
// - A new StatefulBlock containing the input block, output block, accepted block, and both verification
// and acceptance status set to true.
//
// This function emulates the final state of a block that has been fully processed, verified,
// and accepted into the blockchain by the consensus mechanism.
func NewAcceptedBlock[I Block, O Block, A Block](
vm *VM[I, O, A],
input I,
output O,
accepted A,
) *StatefulBlock[I, O, A] {
return &StatefulBlock[I, O, A]{
Input: input,
Output: output,
verified: true,
Accepted: accepted,
accepted: true,
vm: vm,
}
}
func (b *StatefulBlock[I, O, A]) setAccepted(output O, accepted A) {
b.Output = output
b.verified = true
b.Accepted = accepted
b.accepted = true
}
// verify the block against the provided parent output and set the
// required Output/verified fields.
func (b *StatefulBlock[I, O, A]) verify(ctx context.Context, parentOutput O) error {
output, err := b.vm.chain.VerifyBlock(ctx, parentOutput, b.Input)
if err != nil {
return err
}
b.Output = output
b.verified = true
return nil
}
// accept the block and set the required Accepted/accepted fields.
// Assumes verify has already been called.
func (b *StatefulBlock[I, O, A]) accept(ctx context.Context, parentAccepted A) error {
acceptedBlk, err := b.vm.chain.AcceptBlock(ctx, parentAccepted, b.Output)
if err != nil {
return err
}
b.Accepted = acceptedBlk
b.accepted = true
return nil
}
// ShouldVerifyWithContext returns true if the block should be verified with the provided context.
// Always returns true
func (*StatefulBlock[I, O, A]) ShouldVerifyWithContext(context.Context) (bool, error) {
return true, nil
}
// VerifyWithContext verifies the block with P-Chain context
func (b *StatefulBlock[I, O, A]) VerifyWithContext(ctx context.Context, pChainCtx *block.Context) error {
return b.verifyWithContext(ctx, pChainCtx)
}
// Verify block
func (b *StatefulBlock[I, O, A]) Verify(ctx context.Context) error {
return b.verifyWithContext(ctx, nil)
}
func (b *StatefulBlock[I, O, A]) verifyWithContext(ctx context.Context, pChainCtx *block.Context) error {
b.vm.chainLock.Lock()
defer b.vm.chainLock.Unlock()
start := time.Now()
defer func() {
b.vm.metrics.blockVerify.Observe(float64(time.Since(start)))
}()
ready := b.vm.ready
ctx, span := b.vm.tracer.Start(
ctx, "StatefulBlock.Verify",
trace.WithAttributes(
attribute.Int("size", len(b.Input.GetBytes())),
attribute.Int64("height", int64(b.Input.GetHeight())),
attribute.Bool("ready", ready),
attribute.Bool("built", b.verified),
),
)
defer span.End()
switch {
case !ready:
// If the VM is not ready (dynamic state sync), skip verifying the block.
b.vm.log.Info(
"skipping verification, state not ready",
zap.Uint64("height", b.Input.GetHeight()),
zap.Stringer("blkID", b.Input.GetID()),
)
case b.verified:
// Defensive: verify the inner and wrapper block contexts match to ensure
// we don't build a block with a mismatched P-Chain context that will be
// invalid to peers.
innerCtx := b.Input.GetContext()
if err := verifyPChainCtx(pChainCtx, innerCtx); err != nil {
return err
}
// If we built the block, the state will already be populated and we don't
// need to compute it (we assume that we built a correct block and it isn't
// necessary to re-verify).
b.vm.log.Info(
"skipping verification of locally built block",
zap.Uint64("height", b.Input.GetHeight()),
zap.Stringer("blkID", b.Input.GetID()),
)
default:
b.vm.log.Info("Verifying block", zap.Stringer("block", b))
// Fetch my parent to verify against
parent, err := b.vm.GetBlock(ctx, b.Parent())
if err != nil {
return err
}
// If my parent has not been verified and we're no longer in dynamic state sync,
// we must be transitioning to normal consensus.
// Attempt to verify from the last accepted block through to this block to
// compute my parent's Output state.
if !parent.verified {
return errParentFailedVerification
} else {
b.vm.log.Info("parent was already verified")
}
// Verify the inner and wrapper block contexts match
innerCtx := b.Input.GetContext()
if err := verifyPChainCtx(pChainCtx, innerCtx); err != nil {
return err
}
if err := b.verify(ctx, parent.Output); err != nil {
return err
}
if err := event.NotifyAll[O](ctx, b.Output, b.vm.verifiedSubs...); err != nil {
return err
}
}
b.vm.verifiedL.Lock()
b.vm.verifiedBlocks[b.Input.GetID()] = b
b.vm.verifiedL.Unlock()
if b.verified {
b.vm.log.Debug("verified block",
zap.Stringer("blk", b.Output),
zap.Bool("ready", ready),
)
} else {
b.vm.log.Debug("skipped block verification",
zap.Stringer("blk", b.Input),
zap.Bool("ready", ready),
)
}
return nil
}
func verifyPChainCtx(providedCtx, innerCtx *block.Context) error {
switch {
case providedCtx == nil && innerCtx == nil:
return nil
case providedCtx == nil && innerCtx != nil:
return fmt.Errorf("%w: missing provided context != inner P-Chain height %d", errMismatchedPChainContext, innerCtx.PChainHeight)
case providedCtx != nil && innerCtx == nil:
return fmt.Errorf("%w: provided P-Chain height (%d) != missing inner context", errMismatchedPChainContext, providedCtx.PChainHeight)
case providedCtx.PChainHeight != innerCtx.PChainHeight:
return fmt.Errorf("%w: provided P-Chain height (%d) != inner P-Chain height %d", errMismatchedPChainContext, providedCtx.PChainHeight, innerCtx.PChainHeight)
default:
return nil
}
}
// markAccepted marks the block and updates the required VM state.
// iff parent is non-nil, it will request the chain to Accept the block.
// The caller is responsible to provide the accepted parent if the VM is in a ready state.
func (b *StatefulBlock[I, O, A]) markAccepted(ctx context.Context, parent *StatefulBlock[I, O, A]) error {
if err := b.vm.inputChainIndex.UpdateLastAccepted(ctx, b.Input); err != nil {
return err
}
if parent != nil {
if err := b.accept(ctx, parent.Accepted); err != nil {
return err
}
}
b.vm.verifiedL.Lock()
delete(b.vm.verifiedBlocks, b.Input.GetID())
b.vm.verifiedL.Unlock()
b.vm.setLastAccepted(b)
return b.notifyAccepted(ctx)
}
func (b *StatefulBlock[I, O, A]) notifyAccepted(ctx context.Context) error {
// If I was not actually marked accepted, notify pre ready subs
if !b.accepted {
return event.NotifyAll(ctx, b.Input, b.vm.preReadyAcceptedSubs...)
}
return event.NotifyAll(ctx, b.Accepted, b.vm.acceptedSubs...)
}
// Accept implements the snowman.Block.[Decidable] interface.
// It marks this block as accepted by consensus
// If the VM is ready, it ensures the block is verified and then calls markAccepted with its parent to process state transitions.
//
// If the VM is not ready (during state sync),
// it deletes it from [verifiedBlocks], sets the last accepted block to this block, and notifies subscribers.
// We are guaranteed that the block will eventually be accepted by consensus.
//
// [Decidable]: https://github.com/ava-labs/avalanchego/blob/abb1a9a6a21c3dbce6dff5cdcea03173119a5f46/snow/decidable.go#L16
// [verifiedBlocks]: https://github.com/ava-labs/hypersdk/blob/ae0c960050860ad72468e5c3687966366582ba1a/snow/vm.go#L165
func (b *StatefulBlock[I, O, A]) Accept(ctx context.Context) error {
b.vm.chainLock.Lock()
defer b.vm.chainLock.Unlock()
start := time.Now()
defer func() {
b.vm.metrics.blockAccept.Observe(float64(time.Since(start)))
}()
ctx, span := b.vm.tracer.Start(ctx, "StatefulBlock.Accept")
defer span.End()
defer b.vm.log.Info("accepting block", zap.Stringer("block", b))
// If I'm not ready yet, mark myself as accepted, and return early.
isReady := b.vm.ready
if !isReady {
return b.markAccepted(ctx, nil)
}
// If I'm ready and not verified, then I or my ancestor must have failed
// verification after completing dynamic state sync. This indicates
// an invalid block has been accepted, which should be prevented by consensus.
// If we hit this case, return a fatal error here.
if !b.verified {
return errParentFailedVerification
}
// If I am verified and ready, fetch my parent and accept myself. I'm verified, which
// implies my parent is verified as well.
parent, err := b.vm.GetBlock(ctx, b.Parent())
if err != nil {
return fmt.Errorf("failed to fetch parent while accepting verified block %s: %w", b, err)
}
return b.markAccepted(ctx, parent)
}
// Reject implements the snowman.Block.[Decidable] interface.
// It removes the block from the verified blocks map (VM.verifiedBlocks) and notifies subscribers
// that consensus rejected the block. For any particular block, either
// Accept or Reject will be called, never both.
//
// [Decidable]: https://github.com/ava-labs/avalanchego/blob/abb1a9a6a21c3dbce6dff5cdcea03173119a5f46/snow/decidable.go#L16
func (b *StatefulBlock[I, O, A]) Reject(ctx context.Context) error {
ctx, span := b.vm.tracer.Start(ctx, "StatefulBlock.Reject")
defer span.End()
b.vm.verifiedL.Lock()
delete(b.vm.verifiedBlocks, b.Input.GetID())
b.vm.verifiedL.Unlock()
// Notify subscribers about the rejected blocks that were vacuously verified during dynamic state sync
if !b.verified {
return event.NotifyAll[I](ctx, b.Input, b.vm.preRejectedSubs...)
}
return event.NotifyAll[O](ctx, b.Output, b.vm.rejectedSubs...)
}
// ID returns id of Input block
func (b *StatefulBlock[I, O, A]) ID() ids.ID { return b.Input.GetID() }
// Parent returns parent ID of Input block
func (b *StatefulBlock[I, O, A]) Parent() ids.ID { return b.Input.GetParent() }
// Height returns height of Input block
func (b *StatefulBlock[I, O, A]) Height() uint64 { return b.Input.GetHeight() }
// Timestamp returns timestamp in milliseconds of the Input block
func (b *StatefulBlock[I, O, A]) Timestamp() time.Time { return time.UnixMilli(b.Input.GetTimestamp()) }
// Bytes return the serialized bytes of the Input block
func (b *StatefulBlock[I, O, A]) Bytes() []byte { return b.Input.GetBytes() }
// GetID returns ID of Input block
func (b *StatefulBlock[I, O, A]) GetID() ids.ID { return b.Input.GetID() }
// GetParent returns parent ID of Input block
func (b *StatefulBlock[I, O, A]) GetParent() ids.ID { return b.Input.GetParent() }
// GetHeight returns height of Input block
func (b *StatefulBlock[I, O, A]) GetHeight() uint64 { return b.Input.GetHeight() }
// GetTimestamp returns timestamp in milliseconds of the Input block
func (b *StatefulBlock[I, O, A]) GetTimestamp() int64 { return b.Input.GetTimestamp() }
// GetBytes return the serialized bytes of the Input block
func (b *StatefulBlock[I, O, A]) GetBytes() []byte { return b.Input.GetBytes() }
// String implements fmt.Stringer
func (b *StatefulBlock[I, O, A]) String() string {
return fmt.Sprintf("(%s, verified = %t, accepted = %t)", b.Input, b.verified, b.accepted)
}