github.com/MetalBlockchain/metalgo@v1.11.9/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/MetalBlockchain/metalgo/database/versiondb" 11 "github.com/MetalBlockchain/metalgo/ids" 12 "github.com/MetalBlockchain/metalgo/snow" 13 "github.com/MetalBlockchain/metalgo/snow/engine/common" 14 "github.com/MetalBlockchain/metalgo/utils/linked" 15 "github.com/MetalBlockchain/metalgo/vms/example/xsvm/chain" 16 "github.com/MetalBlockchain/metalgo/vms/example/xsvm/execute" 17 "github.com/MetalBlockchain/metalgo/vms/example/xsvm/tx" 18 19 smblock "github.com/MetalBlockchain/metalgo/snow/engine/snowman/block" 20 xsblock "github.com/MetalBlockchain/metalgo/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 }