github.com/ava-labs/avalanchego@v1.11.11/vms/example/xsvm/builder/builder.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package builder
     5  
     6  import (
     7  	"context"
     8  	"time"
     9  
    10  	"github.com/ava-labs/avalanchego/database/versiondb"
    11  	"github.com/ava-labs/avalanchego/ids"
    12  	"github.com/ava-labs/avalanchego/snow"
    13  	"github.com/ava-labs/avalanchego/snow/engine/common"
    14  	"github.com/ava-labs/avalanchego/utils/linked"
    15  	"github.com/ava-labs/avalanchego/vms/example/xsvm/chain"
    16  	"github.com/ava-labs/avalanchego/vms/example/xsvm/execute"
    17  	"github.com/ava-labs/avalanchego/vms/example/xsvm/tx"
    18  
    19  	smblock "github.com/ava-labs/avalanchego/snow/engine/snowman/block"
    20  	xsblock "github.com/ava-labs/avalanchego/vms/example/xsvm/block"
    21  )
    22  
    23  const MaxTxsPerBlock = 10
    24  
    25  var _ Builder = (*builder)(nil)
    26  
    27  type Builder interface {
    28  	SetPreference(preferred ids.ID)
    29  	AddTx(ctx context.Context, tx *tx.Tx) error
    30  	BuildBlock(ctx context.Context, blockContext *smblock.Context) (chain.Block, error)
    31  }
    32  
    33  type builder struct {
    34  	chainContext *snow.Context
    35  	engineChan   chan<- common.Message
    36  	chain        chain.Chain
    37  
    38  	pendingTxs *linked.Hashmap[ids.ID, *tx.Tx]
    39  	preference ids.ID
    40  }
    41  
    42  func New(chainContext *snow.Context, engineChan chan<- common.Message, chain chain.Chain) Builder {
    43  	return &builder{
    44  		chainContext: chainContext,
    45  		engineChan:   engineChan,
    46  		chain:        chain,
    47  
    48  		pendingTxs: linked.NewHashmap[ids.ID, *tx.Tx](),
    49  		preference: chain.LastAccepted(),
    50  	}
    51  }
    52  
    53  func (b *builder) SetPreference(preferred ids.ID) {
    54  	b.preference = preferred
    55  }
    56  
    57  func (b *builder) AddTx(_ context.Context, newTx *tx.Tx) error {
    58  	// TODO: verify [tx] against the currently preferred state
    59  	txID, err := newTx.ID()
    60  	if err != nil {
    61  		return err
    62  	}
    63  	b.pendingTxs.Put(txID, newTx)
    64  	select {
    65  	case b.engineChan <- common.PendingTxs:
    66  	default:
    67  	}
    68  	return nil
    69  }
    70  
    71  func (b *builder) BuildBlock(ctx context.Context, blockContext *smblock.Context) (chain.Block, error) {
    72  	preferredBlk, err := b.chain.GetBlock(b.preference)
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  
    77  	preferredState, err := preferredBlk.State()
    78  	if err != nil {
    79  		return nil, err
    80  	}
    81  
    82  	defer func() {
    83  		if b.pendingTxs.Len() == 0 {
    84  			return
    85  		}
    86  		select {
    87  		case b.engineChan <- common.PendingTxs:
    88  		default:
    89  		}
    90  	}()
    91  
    92  	parentTimestamp := preferredBlk.Timestamp()
    93  	timestamp := time.Now().Truncate(time.Second)
    94  	if timestamp.Before(parentTimestamp) {
    95  		timestamp = parentTimestamp
    96  	}
    97  
    98  	wipBlock := xsblock.Stateless{
    99  		ParentID:  b.preference,
   100  		Timestamp: timestamp.Unix(),
   101  		Height:    preferredBlk.Height() + 1,
   102  	}
   103  
   104  	currentState := versiondb.New(preferredState)
   105  	for len(wipBlock.Txs) < MaxTxsPerBlock {
   106  		txID, currentTx, exists := b.pendingTxs.Oldest()
   107  		if !exists {
   108  			break
   109  		}
   110  		b.pendingTxs.Delete(txID)
   111  
   112  		sender, err := currentTx.SenderID()
   113  		if err != nil {
   114  			// This tx was invalid, drop it and continue block building
   115  			continue
   116  		}
   117  
   118  		txState := versiondb.New(currentState)
   119  		txExecutor := execute.Tx{
   120  			Context:      ctx,
   121  			ChainContext: b.chainContext,
   122  			Database:     txState,
   123  			BlockContext: blockContext,
   124  			TxID:         txID,
   125  			Sender:       sender,
   126  			// TODO: populate fees
   127  		}
   128  		if err := currentTx.Unsigned.Visit(&txExecutor); err != nil {
   129  			// This tx was invalid, drop it and continue block building
   130  			continue
   131  		}
   132  		if err := txState.Commit(); err != nil {
   133  			return nil, err
   134  		}
   135  
   136  		wipBlock.Txs = append(wipBlock.Txs, currentTx)
   137  	}
   138  	return b.chain.NewBlock(&wipBlock)
   139  }