fabric-samples/off_chain_data/application-go/processor/block.go
Stanislav Jakuschevskij 06c7445e91
Replace panic with error handling
Starting from the processor.Block.Process all methods now return errors
if something goes wrong with unpacking of the blocks and reading the
transactions. In each function where the error is being propagated back
to client it is wrapped in a message with the function name. This makes
it easier to track down the error and see the propagation chain. Finally
the error is logged to the terminal and the go routine shuts down
gracefully. The graceful shutdown executes all deferred functions which
close the context, the checkpointer and the gateway.

Before panics were used everywhere which was an issue because the
unpacking of the blocks happened in a go routine. When a panic happens
in a go routine only the deferred functions of the go routine are called
but not those of the client which lead to unexpected behavior.

The transact function is also executed in a go routine therefore the
same typo of error handling was implemented there.

Signed-off-by: Stanislav Jakuschevskij <stas@two-giants.com>
2025-02-24 13:14:48 +01:00

147 lines
3.5 KiB
Go

package processor
import (
"fmt"
"offChainData/parser"
"offChainData/store"
"github.com/hyperledger/fabric-gateway/pkg/client"
)
type block struct {
parsedBlock *parser.Block
checkpointer *client.FileCheckpointer
writeToStore store.Writer
channelName string
}
func NewBlock(
parsedBlock *parser.Block,
checkpointer *client.FileCheckpointer,
writeToStore store.Writer,
channelName string,
) *block {
return &block{
parsedBlock,
checkpointer,
writeToStore,
channelName,
}
}
func (b *block) Process() error {
funcName := "Process"
blockNumber, err := b.parsedBlock.Number()
if err != nil {
return fmt.Errorf("in %s: %w", funcName, err)
}
fmt.Println("\nReceived block", blockNumber)
validTransactions, err := b.validTransactions()
if err != nil {
return fmt.Errorf("in %s: %w", funcName, err)
}
for _, validTransaction := range validTransactions {
aTransaction := transaction{
blockNumber,
validTransaction,
// TODO use pointer to parent and get blockNumber, store and channelName from parent
b.writeToStore,
b.channelName,
}
if err := aTransaction.process(); err != nil {
return fmt.Errorf("in %s: %w", funcName, err)
}
channelHeader, err := validTransaction.ChannelHeader()
if err != nil {
return fmt.Errorf("in %s: %w", funcName, err)
}
transactionId := channelHeader.GetTxId()
b.checkpointer.CheckpointTransaction(blockNumber, transactionId)
}
b.checkpointer.CheckpointBlock(blockNumber)
return nil
}
func (b *block) validTransactions() ([]*parser.Transaction, error) {
result := []*parser.Transaction{}
newTransactions, err := b.getNewTransactions()
if err != nil {
return nil, fmt.Errorf("in validTransactions: %w", err)
}
for _, transaction := range newTransactions {
if transaction.IsValid() {
result = append(result, transaction)
}
}
return result, nil
}
func (b *block) getNewTransactions() ([]*parser.Transaction, error) {
funcName := "getNewTransactions"
transactions, err := b.parsedBlock.Transactions()
if err != nil {
return nil, fmt.Errorf("in %s: %w", funcName, err)
}
lastTransactionId := b.checkpointer.TransactionID()
if lastTransactionId == "" {
// No previously processed transactions within this block so all are new
return transactions, nil
}
// Ignore transactions up to the last processed transaction ID
lastProcessedIndex, err := b.findLastProcessedIndex()
if err != nil {
return nil, fmt.Errorf("in %s: %w", funcName, err)
}
return transactions[lastProcessedIndex+1:], nil
}
func (b *block) findLastProcessedIndex() (int, error) {
funcName := "findLastProcessedIndex"
transactions, err := b.parsedBlock.Transactions()
if err != nil {
return 0, fmt.Errorf("in %s: %w", funcName, err)
}
blockTransactionIds := []string{}
for _, transaction := range transactions {
channelHeader, err := transaction.ChannelHeader()
if err != nil {
return 0, fmt.Errorf("in %s: %w", funcName, err)
}
blockTransactionIds = append(blockTransactionIds, channelHeader.GetTxId())
}
lastTransactionId := b.checkpointer.TransactionID()
lastProcessedIndex := -1
for index, id := range blockTransactionIds {
if id == lastTransactionId {
lastProcessedIndex = index
}
}
if lastProcessedIndex < 0 {
blockNumber, err := b.parsedBlock.Number()
if err != nil {
return 0, fmt.Errorf("in %s: %w", funcName, err)
}
return lastProcessedIndex, newTxIdNotFoundError(
lastTransactionId,
blockNumber,
blockTransactionIds,
)
}
return lastProcessedIndex, nil
}