From 5ddfb4253dc819c86a9c3223b6d97b678542f9ed Mon Sep 17 00:00:00 2001 From: Akhil Repala Date: Wed, 30 Jul 2025 00:39:06 -0500 Subject: [PATCH 1/4] feat: Added forgeBlock function and scheduled block forging for dev mode at calculated block interval Signed-off-by: Akhil Repala --- ledger/state.go | 88 +++++++++++++++++++++++++++++++++++++++++++++++++ node.go | 1 + 2 files changed, 89 insertions(+) diff --git a/ledger/state.go b/ledger/state.go index ad42f07a..6d9811ef 100644 --- a/ledger/state.go +++ b/ledger/state.go @@ -61,6 +61,7 @@ type LedgerStateConfig struct { CardanoNodeConfig *cardano.CardanoNodeConfig PromRegistry prometheus.Registerer ValidateHistorical bool + DevMode bool // Callback(s) BlockfetchRequestRangeFunc BlockfetchRequestRangeFunc } @@ -152,6 +153,30 @@ func (ls *LedgerState) Start() error { interval := time.Duration(durationNs) ls.Scheduler = NewScheduler(interval) ls.Scheduler.Start() + + // Schedule block forging if dev mode is enabled + if ls.config.DevMode { + // Calculate block interval from ActiveSlotsCoeff + shelleyGenesis := ls.config.CardanoNodeConfig.ShelleyGenesis() + if shelleyGenesis != nil { + // Calculate block interval (1 / ActiveSlotsCoeff) + activeSlotsCoeff := shelleyGenesis.ActiveSlotsCoeff + if activeSlotsCoeff.Rat != nil && activeSlotsCoeff.Rat.Num().Int64() > 0 { + activeSlotsCoeffFloat := float64(activeSlotsCoeff.Rat.Num().Int64()) / float64(activeSlotsCoeff.Rat.Denom().Int64()) + blockInterval := int(1.0 / activeSlotsCoeffFloat) + + // Scheduled forgeBlock to run at the calculated block interval + ls.Scheduler.Register(blockInterval, ls.forgeBlock) + + ls.config.Logger.Info( + "dev mode block forging enabled", + "component", "ledger", + "block_interval", blockInterval, + "active_slots_coeff", activeSlotsCoeffFloat, + ) + } + } + } } // Start goroutine to process new blocks go ls.ledgerProcessBlocks() @@ -917,3 +942,66 @@ func (ls *LedgerState) ValidateTx( } return nil } + +// forgeBlock creates an empty block and logs its CBOR representation +func (ls *LedgerState) forgeBlock() { + // Get current chain tip + currentTip := ls.Tip() + + // Calculate next slot and block number + nextSlot := currentTip.Point.Slot + 1 + nextBlockNumber := currentTip.BlockNumber + 1 + + // Create a proper Shelley empty block with minimum required parameters + emptyBlock := struct { + cbor.StructAsArray + Header struct { + cbor.StructAsArray + PrevHash []byte `cbor:"0,keyasint"` + BlockNumber uint64 `cbor:"1,keyasint"` + SlotNumber uint64 `cbor:"2,keyasint"` + } `cbor:"0,keyasint"` + Body struct { + cbor.StructAsArray + Transactions []interface{} `cbor:"0,keyasint"` + } `cbor:"1,keyasint"` + }{ + Header: struct { + cbor.StructAsArray + PrevHash []byte `cbor:"0,keyasint"` + BlockNumber uint64 `cbor:"1,keyasint"` + SlotNumber uint64 `cbor:"2,keyasint"` + }{ + PrevHash: currentTip.Point.Hash, + BlockNumber: nextBlockNumber, + SlotNumber: nextSlot, + }, + Body: struct { + cbor.StructAsArray + Transactions []interface{} `cbor:"0,keyasint"` + }{ + Transactions: []interface{}{}, // Empty transactions list + }, + } + + // Marshal the block to CBOR + blockCbor, err := cbor.Encode(emptyBlock) + if err != nil { + ls.config.Logger.Error( + "failed to marshal forged block to CBOR", + "component", "ledger", + "error", err, + ) + return + } + + // Log the generated block CBOR (DEBUG level) + ls.config.Logger.Debug( + "forged empty block", + "component", "ledger", + "slot", nextSlot, + "block_number", nextBlockNumber, + "prev_hash", hex.EncodeToString(currentTip.Point.Hash), + "block_cbor", hex.EncodeToString(blockCbor), + ) +} diff --git a/node.go b/node.go index 280bd9e2..9dae27ae 100644 --- a/node.go +++ b/node.go @@ -123,6 +123,7 @@ func (n *Node) Run() error { Logger: n.config.logger, CardanoNodeConfig: n.config.cardanoNodeConfig, PromRegistry: n.config.promRegistry, + DevMode: n.config.devMode, BlockfetchRequestRangeFunc: n.blockfetchClientRequestRange, }, ) From 7776a11254fc8d8479184040cdd3c838f0e55326 Mon Sep 17 00:00:00 2001 From: Akhil Repala Date: Wed, 30 Jul 2025 19:33:01 -0500 Subject: [PATCH 2/4] feat: Added empty conway in forgeBlock and modified the calculation of block interval as well as changed the name devMode to forgeBlocks Signed-off-by: Akhil Repala --- ledger/state.go | 80 +++++++++++++++++++++++++------------------------ node.go | 2 +- 2 files changed, 42 insertions(+), 40 deletions(-) diff --git a/ledger/state.go b/ledger/state.go index 6d9811ef..a2aeb467 100644 --- a/ledger/state.go +++ b/ledger/state.go @@ -32,7 +32,9 @@ import ( ouroboros "github.com/blinklabs-io/gouroboros" "github.com/blinklabs-io/gouroboros/cbor" "github.com/blinklabs-io/gouroboros/ledger" + "github.com/blinklabs-io/gouroboros/ledger/babbage" lcommon "github.com/blinklabs-io/gouroboros/ledger/common" + "github.com/blinklabs-io/gouroboros/ledger/conway" ochainsync "github.com/blinklabs-io/gouroboros/protocol/chainsync" ocommon "github.com/blinklabs-io/gouroboros/protocol/common" "github.com/prometheus/client_golang/prometheus" @@ -61,7 +63,7 @@ type LedgerStateConfig struct { CardanoNodeConfig *cardano.CardanoNodeConfig PromRegistry prometheus.Registerer ValidateHistorical bool - DevMode bool + ForgeBlocks bool // Callback(s) BlockfetchRequestRangeFunc BlockfetchRequestRangeFunc } @@ -155,16 +157,14 @@ func (ls *LedgerState) Start() error { ls.Scheduler.Start() // Schedule block forging if dev mode is enabled - if ls.config.DevMode { + if ls.config.ForgeBlocks { // Calculate block interval from ActiveSlotsCoeff shelleyGenesis := ls.config.CardanoNodeConfig.ShelleyGenesis() if shelleyGenesis != nil { // Calculate block interval (1 / ActiveSlotsCoeff) activeSlotsCoeff := shelleyGenesis.ActiveSlotsCoeff if activeSlotsCoeff.Rat != nil && activeSlotsCoeff.Rat.Num().Int64() > 0 { - activeSlotsCoeffFloat := float64(activeSlotsCoeff.Rat.Num().Int64()) / float64(activeSlotsCoeff.Rat.Denom().Int64()) - blockInterval := int(1.0 / activeSlotsCoeffFloat) - + blockInterval := int((1 * activeSlotsCoeff.Rat.Denom().Int64()) / activeSlotsCoeff.Rat.Num().Int64()) // Scheduled forgeBlock to run at the calculated block interval ls.Scheduler.Register(blockInterval, ls.forgeBlock) @@ -172,7 +172,7 @@ func (ls *LedgerState) Start() error { "dev mode block forging enabled", "component", "ledger", "block_interval", blockInterval, - "active_slots_coeff", activeSlotsCoeffFloat, + "active_slots_coeff", activeSlotsCoeff.Rat.String(), ) } } @@ -943,7 +943,7 @@ func (ls *LedgerState) ValidateTx( return nil } -// forgeBlock creates an empty block and logs its CBOR representation +// forgeBlock creates an empty conway block and logs its CBOR representation func (ls *LedgerState) forgeBlock() { // Get current chain tip currentTip := ls.Tip() @@ -952,43 +952,45 @@ func (ls *LedgerState) forgeBlock() { nextSlot := currentTip.Point.Slot + 1 nextBlockNumber := currentTip.BlockNumber + 1 - // Create a proper Shelley empty block with minimum required parameters - emptyBlock := struct { - cbor.StructAsArray - Header struct { - cbor.StructAsArray - PrevHash []byte `cbor:"0,keyasint"` - BlockNumber uint64 `cbor:"1,keyasint"` - SlotNumber uint64 `cbor:"2,keyasint"` - } `cbor:"0,keyasint"` - Body struct { - cbor.StructAsArray - Transactions []interface{} `cbor:"0,keyasint"` - } `cbor:"1,keyasint"` - }{ - Header: struct { - cbor.StructAsArray - PrevHash []byte `cbor:"0,keyasint"` - BlockNumber uint64 `cbor:"1,keyasint"` - SlotNumber uint64 `cbor:"2,keyasint"` - }{ - PrevHash: currentTip.Point.Hash, - BlockNumber: nextBlockNumber, - SlotNumber: nextSlot, + // Create empty Babbage block header body + headerBody := babbage.BabbageBlockHeaderBody{ + BlockNumber: nextBlockNumber, + Slot: nextSlot, + PrevHash: lcommon.NewBlake2b256(currentTip.Point.Hash), + IssuerVkey: lcommon.IssuerVkey{}, + VrfKey: []byte{}, + VrfResult: lcommon.VrfResult{}, + BlockBodySize: 0, + BlockBodyHash: lcommon.Blake2b256{}, + OpCert: babbage.BabbageOpCert{}, + ProtoVersion: babbage.BabbageProtoVersion{ + Major: 2, + Minor: 0, }, - Body: struct { - cbor.StructAsArray - Transactions []interface{} `cbor:"0,keyasint"` - }{ - Transactions: []interface{}{}, // Empty transactions list + } + + // Create Conway block header + conwayHeader := &conway.ConwayBlockHeader{ + BabbageBlockHeader: babbage.BabbageBlockHeader{ + Body: headerBody, + Signature: []byte{}, }, } - // Marshal the block to CBOR - blockCbor, err := cbor.Encode(emptyBlock) + // Create a conway block + conwayBlock := &conway.ConwayBlock{ + BlockHeader: conwayHeader, + TransactionBodies: []conway.ConwayTransactionBody{}, + TransactionWitnessSets: []conway.ConwayTransactionWitnessSet{}, + TransactionMetadataSet: map[uint]*cbor.LazyValue{}, + InvalidTransactions: []uint{}, + } + + // Marshal the conway block to CBOR + blockCbor, err := cbor.Encode(conwayBlock) if err != nil { ls.config.Logger.Error( - "failed to marshal forged block to CBOR", + "failed to marshal forged conway block to CBOR", "component", "ledger", "error", err, ) @@ -997,7 +999,7 @@ func (ls *LedgerState) forgeBlock() { // Log the generated block CBOR (DEBUG level) ls.config.Logger.Debug( - "forged empty block", + "forged empty conway block", "component", "ledger", "slot", nextSlot, "block_number", nextBlockNumber, diff --git a/node.go b/node.go index 9dae27ae..27d7711a 100644 --- a/node.go +++ b/node.go @@ -123,7 +123,7 @@ func (n *Node) Run() error { Logger: n.config.logger, CardanoNodeConfig: n.config.cardanoNodeConfig, PromRegistry: n.config.promRegistry, - DevMode: n.config.devMode, + ForgeBlocks: n.config.devMode, BlockfetchRequestRangeFunc: n.blockfetchClientRequestRange, }, ) From e47f93b5c2940c97976e9aa82a13548196d2b064 Mon Sep 17 00:00:00 2001 From: Akhil Repala Date: Wed, 30 Jul 2025 20:18:03 -0500 Subject: [PATCH 3/4] feat: Added fix to get rid of the golangci-lint error regarding active slot coefficient Signed-off-by: Akhil Repala --- ledger/state.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ledger/state.go b/ledger/state.go index a2aeb467..aad685d2 100644 --- a/ledger/state.go +++ b/ledger/state.go @@ -172,7 +172,7 @@ func (ls *LedgerState) Start() error { "dev mode block forging enabled", "component", "ledger", "block_interval", blockInterval, - "active_slots_coeff", activeSlotsCoeff.Rat.String(), + "active_slots_coeff", activeSlotsCoeff.String(), ) } } From 3cd98cacc0415cada894051708a5669b9d3ac7db Mon Sep 17 00:00:00 2001 From: Akhil Repala Date: Thu, 31 Jul 2025 22:29:56 -0500 Subject: [PATCH 4/4] feat: Updated forgeBlock to use ls.chain.Tip() and calculate next slot with TimeToSlot(time.Now()) Signed-off-by: Akhil Repala --- ledger/state.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/ledger/state.go b/ledger/state.go index aad685d2..9b46b3a9 100644 --- a/ledger/state.go +++ b/ledger/state.go @@ -946,10 +946,18 @@ func (ls *LedgerState) ValidateTx( // forgeBlock creates an empty conway block and logs its CBOR representation func (ls *LedgerState) forgeBlock() { // Get current chain tip - currentTip := ls.Tip() + currentTip := ls.chain.Tip() // Calculate next slot and block number - nextSlot := currentTip.Point.Slot + 1 + nextSlot, err := ls.TimeToSlot(time.Now()) + if err != nil { + ls.config.Logger.Error( + "failed to calculate slot from current time", + "component", "ledger", + "error", err, + ) + return + } nextBlockNumber := currentTip.BlockNumber + 1 // Create empty Babbage block header body