github.com/MetalBlockchain/metalgo@v1.11.9/vms/example/xsvm/chain/block.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package chain
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"time"
    10  
    11  	"github.com/MetalBlockchain/metalgo/database"
    12  	"github.com/MetalBlockchain/metalgo/database/versiondb"
    13  	"github.com/MetalBlockchain/metalgo/ids"
    14  	"github.com/MetalBlockchain/metalgo/snow"
    15  	"github.com/MetalBlockchain/metalgo/snow/choices"
    16  	"github.com/MetalBlockchain/metalgo/snow/consensus/snowman"
    17  	"github.com/MetalBlockchain/metalgo/utils/set"
    18  	"github.com/MetalBlockchain/metalgo/vms/example/xsvm/execute"
    19  	"github.com/MetalBlockchain/metalgo/vms/example/xsvm/state"
    20  
    21  	smblock "github.com/MetalBlockchain/metalgo/snow/engine/snowman/block"
    22  	xsblock "github.com/MetalBlockchain/metalgo/vms/example/xsvm/block"
    23  )
    24  
    25  const maxClockSkew = 10 * time.Second
    26  
    27  var (
    28  	_ Block = (*block)(nil)
    29  
    30  	errMissingParent         = errors.New("missing parent block")
    31  	errMissingChild          = errors.New("missing child block")
    32  	errParentNotVerified     = errors.New("parent block has not been verified")
    33  	errMissingState          = errors.New("missing state")
    34  	errFutureTimestamp       = errors.New("future timestamp")
    35  	errTimestampBeforeParent = errors.New("timestamp before parent")
    36  	errWrongHeight           = errors.New("wrong height")
    37  )
    38  
    39  type Block interface {
    40  	snowman.Block
    41  	smblock.WithVerifyContext
    42  
    43  	// State intends to return the new chain state following this block's
    44  	// acceptance. The new chain state is built (but not persisted) following a
    45  	// block's verification to allow block's descendants verification before
    46  	// being accepted.
    47  	State() (database.Database, error)
    48  }
    49  
    50  type block struct {
    51  	*xsblock.Stateless
    52  
    53  	chain *chain
    54  
    55  	id     ids.ID
    56  	status choices.Status
    57  	bytes  []byte
    58  
    59  	state               *versiondb.Database
    60  	verifiedChildrenIDs set.Set[ids.ID]
    61  }
    62  
    63  func (b *block) ID() ids.ID {
    64  	return b.id
    65  }
    66  
    67  func (b *block) Status() choices.Status {
    68  	if !b.status.Decided() {
    69  		b.status = b.calculateStatus()
    70  	}
    71  	return b.status
    72  }
    73  
    74  func (b *block) Parent() ids.ID {
    75  	return b.ParentID
    76  }
    77  
    78  func (b *block) Bytes() []byte {
    79  	return b.bytes
    80  }
    81  
    82  func (b *block) Height() uint64 {
    83  	return b.Stateless.Height
    84  }
    85  
    86  func (b *block) Timestamp() time.Time {
    87  	return b.Time()
    88  }
    89  
    90  func (b *block) Verify(ctx context.Context) error {
    91  	return b.VerifyWithContext(ctx, nil)
    92  }
    93  
    94  func (b *block) Accept(context.Context) error {
    95  	if err := b.state.Commit(); err != nil {
    96  		return err
    97  	}
    98  
    99  	// Following this block's acceptance, make sure that it's direct children
   100  	// point to the base state, which now also contains this block's changes.
   101  	for childID := range b.verifiedChildrenIDs {
   102  		child, exists := b.chain.verifiedBlocks[childID]
   103  		if !exists {
   104  			return errMissingChild
   105  		}
   106  		if err := child.state.SetDatabase(b.chain.acceptedState); err != nil {
   107  			return err
   108  		}
   109  	}
   110  
   111  	b.status = choices.Accepted
   112  	b.chain.lastAccepted = b.id
   113  	delete(b.chain.verifiedBlocks, b.ParentID)
   114  	return nil
   115  }
   116  
   117  func (b *block) Reject(context.Context) error {
   118  	b.status = choices.Rejected
   119  	delete(b.chain.verifiedBlocks, b.id)
   120  
   121  	// TODO: push transactions back into the mempool
   122  	return nil
   123  }
   124  
   125  func (b *block) ShouldVerifyWithContext(context.Context) (bool, error) {
   126  	return execute.ExpectsContext(b.Stateless)
   127  }
   128  
   129  func (b *block) VerifyWithContext(ctx context.Context, blockContext *smblock.Context) error {
   130  	timestamp := b.Time()
   131  	if time.Until(timestamp) > maxClockSkew {
   132  		return errFutureTimestamp
   133  	}
   134  
   135  	// parent block must be verified or accepted
   136  	parent, exists := b.chain.verifiedBlocks[b.ParentID]
   137  	if !exists {
   138  		return errMissingParent
   139  	}
   140  
   141  	if b.Stateless.Height != parent.Stateless.Height+1 {
   142  		return errWrongHeight
   143  	}
   144  
   145  	parentTimestamp := parent.Time()
   146  	if timestamp.Before(parentTimestamp) {
   147  		return errTimestampBeforeParent
   148  	}
   149  
   150  	parentState, err := parent.State()
   151  	if err != nil {
   152  		return err
   153  	}
   154  
   155  	// This block's state is a versionDB built on top of it's parent state. This
   156  	// block's changes are pushed atomically to the parent state when accepted.
   157  	blkState := versiondb.New(parentState)
   158  	err = execute.Block(
   159  		ctx,
   160  		b.chain.chainContext,
   161  		blkState,
   162  		b.chain.chainState == snow.Bootstrapping,
   163  		blockContext,
   164  		b.Stateless,
   165  	)
   166  	if err != nil {
   167  		return err
   168  	}
   169  
   170  	// Make sure to only state the state the first time we verify this block.
   171  	if b.state == nil {
   172  		b.state = blkState
   173  		parent.verifiedChildrenIDs.Add(b.id)
   174  		b.chain.verifiedBlocks[b.id] = b
   175  	}
   176  
   177  	return nil
   178  }
   179  
   180  func (b *block) State() (database.Database, error) {
   181  	if b.id == b.chain.lastAccepted {
   182  		return b.chain.acceptedState, nil
   183  	}
   184  
   185  	// States of accepted blocks other than the lastAccepted are undefined.
   186  	if b.Status() == choices.Accepted {
   187  		return nil, errMissingState
   188  	}
   189  
   190  	// We should not be calling State on an unverified block.
   191  	if b.state == nil {
   192  		return nil, errParentNotVerified
   193  	}
   194  
   195  	return b.state, nil
   196  }
   197  
   198  func (b *block) calculateStatus() choices.Status {
   199  	if b.chain.lastAccepted == b.id {
   200  		return choices.Accepted
   201  	}
   202  	if _, ok := b.chain.verifiedBlocks[b.id]; ok {
   203  		return choices.Processing
   204  	}
   205  
   206  	_, err := state.GetBlock(b.chain.acceptedState, b.id)
   207  	switch {
   208  	case err == nil:
   209  		return choices.Accepted
   210  
   211  	case errors.Is(err, database.ErrNotFound):
   212  		// This block hasn't been verified yet.
   213  		return choices.Processing
   214  
   215  	default:
   216  		// TODO: correctly report this error to the consensus engine.
   217  		return choices.Processing
   218  	}
   219  }