diff --git a/README.md b/README.md index 5a950c6..b581a68 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,12 @@ To enable firehose instrumentation: Firehose logger statement will be printed as blocks are executed/produced. This mode is meant to be run using by a Firehose `reader-node`, see https://github.com/streamingfast/firehose-acme. +### Block Skipping and Forks + +The dummy chain skips a block that are divisible by 13 so for example we are at block #25 (abc) the next produced block will be #27 (def) and #26 will never be produced. + +THe dummy chain also generates forks at regular interval, every 17 blocks. The generated fork sequence is of 1 block if the current block is odd otherwise if it's even, a 2 blocks fork sequence will be generated. + ## Tracer This project showcase a "fake" blockchain's node codebase. For developers looking into integrating a native Firehose integration, we suggest to integrate in blockchain's client code directly by some form of tracing plugin that is able to receive all the important callback's while transactions are execution integrating as deeply as wanted. diff --git a/core/engine.go b/core/engine.go index f2b23e9..376c77e 100644 --- a/core/engine.go +++ b/core/engine.go @@ -59,12 +59,14 @@ func (e *Engine) StartBlockProduction(ctx context.Context) { for { select { case <-ticker.C: - block := e.createBlock() - e.blockChan <- block - - if e.stopHeight > 0 && block.Header.Height >= e.stopHeight { - logrus.Info("reached stop height") - ticker.Stop() + for _, block := range e.createBlocks() { + e.blockChan <- block + + if e.stopHeight > 0 && block.Header.Height >= e.stopHeight { + logrus.Info("reached stop height") + ticker.Stop() + return + } } case <-ctx.Done(): logrus.Info("stopping block producer") @@ -78,52 +80,77 @@ func (e *Engine) Subscription() <-chan *types.Block { return e.blockChan } -func (e *Engine) createBlock() *types.Block { +func (e *Engine) createBlocks() (out []*types.Block) { if e.prevBlock == nil { - logrus.WithField("height", e.genesisHeight).Info("starting from genesis block height") genesisBlock := types.GenesisBlock(e.genesisHeight) + logrus.WithField("block", blockRef{genesisBlock.Header.Hash, e.genesisHeight}).Info("starting from genesis block height") e.prevBlock = genesisBlock e.finalBlock = genesisBlock - return genesisBlock + out = append(out, genesisBlock) + return } - block := &types.Block{ - Header: &types.BlockHeader{ - Height: e.prevBlock.Header.Height + 1, - Hash: types.MakeHash(e.prevBlock.Header.Height + 1), - PrevNum: &e.prevBlock.Header.Height, - PrevHash: &e.prevBlock.Header.Hash, - FinalNum: e.finalBlock.Header.Height, - FinalHash: e.finalBlock.Header.Hash, - }, - Transactions: []types.Transaction{}, + heightToProduce := e.prevBlock.Header.Height + 1 + if heightToProduce%13 == 0 { + heightToProduce += 1 + logrus.Info(fmt.Sprintf("skipping block #%d that is a multiple of 13, producing %d instead", heightToProduce-1, heightToProduce)) } - trxCount := min(block.Header.Height%10, 500) + if heightToProduce%17 == 0 { + if heightToProduce%2 == 0 { + logrus.Info("producing 2 block fork sequence") + firstFork := e.newBlock(heightToProduce, ptr(uint64(1)), e.prevBlock) + secondFork := e.newBlock(heightToProduce+1, ptr(uint64(2)), firstFork) + + out = append(out, firstFork, secondFork) + } else { + logrus.Info("producing 1 block fork sequence") + out = append(out, e.newBlock(heightToProduce, ptr(uint64(1)), e.prevBlock)) + } + } + + block := e.newBlock(heightToProduce, nil, e.prevBlock) + + trxCount := min(heightToProduce%10, 500) for i := uint64(0); i < trxCount; i++ { tx := types.Transaction{ Type: "transfer", - Hash: types.MakeHash(fmt.Sprintf("%v-%v", block.Header.Height, i)), + Hash: types.MakeHash(fmt.Sprintf("%v-%v", heightToProduce, i)), Sender: "0xDEADBEEF", Receiver: "0xBAAAAAAD", Amount: big.NewInt(int64(i * 1000000000)), Fee: big.NewInt(10000), Success: true, - Events: e.generateEvents(block.Header.Height), + Events: e.generateEvents(heightToProduce), } block.Transactions = append(block.Transactions, tx) } - e.prevBlock = block + out = append(out, block) + e.prevBlock = block if block.Header.Height%10 == 0 { - logrus.WithField("height", block.Header.Height).Info("produced block is now the final block") + logrus.WithField("block", blockRef{block.Header.Hash, block.Header.Height}).Info("produced block is now the final block") e.finalBlock = block } - return block + return +} + +func (e *Engine) newBlock(height uint64, nonce *uint64, parent *types.Block) *types.Block { + return &types.Block{ + Header: &types.BlockHeader{ + Height: height, + Hash: types.MakeHashNonce(height, nonce), + PrevNum: &parent.Header.Height, + PrevHash: &parent.Header.Hash, + FinalNum: e.finalBlock.Header.Height, + FinalHash: e.finalBlock.Header.Hash, + }, + Transactions: []types.Transaction{}, + } } func (e *Engine) generateEvents(height uint64) []types.Event { @@ -158,3 +185,7 @@ func (e *Engine) generateEvents(height uint64) []types.Event { return events } + +func ptr[T any](t T) *T { + return &t +} diff --git a/core/node.go b/core/node.go index d908eea..7e49ae7 100644 --- a/core/node.go +++ b/core/node.go @@ -129,8 +129,9 @@ func (node *Node) Start(ctx context.Context) error { func (node *Node) processBlock(block *types.Block) error { logrus. - WithField("height", block.Header.Height). - WithField("hash", block.Header.Hash). + WithField("block", blockRef{block.Header.Hash, block.Header.Height}). + WithField("parent_block", blockRef{valueOr(block.Header.PrevHash, ""), valueOr(block.Header.PrevNum, 0)}). + WithField("final_block", blockRef{block.Header.FinalHash, block.Header.FinalNum}). Info("processing block") if err := node.store.WriteBlock(block); err != nil { @@ -139,3 +140,36 @@ func (node *Node) processBlock(block *types.Block) error { return nil } + +func shortHash(in string) string { + return in[:6] + "..." + in[len(in)-6:] +} + +func shortHashPtr(in *string) string { + if in == nil { + return "" + } + + return shortHash(*in) +} + +func valueOr[T any](t *T, def T) T { + if t == nil { + return def + } + + return *t +} + +type blockRef struct { + hash string + number uint64 +} + +func (ref blockRef) String() string { + if ref.hash == "" { + return "" + } + + return fmt.Sprintf("#%d (%s)", ref.number, shortHash(ref.hash)) +} diff --git a/core/store.go b/core/store.go index 1304fcd..c7c3c7e 100644 --- a/core/store.go +++ b/core/store.go @@ -56,6 +56,7 @@ func (store *Store) Initialize() error { func (store *Store) WriteBlock(block *types.Block) error { store.meta.HeadHeight = block.Header.Height + store.meta.FinalHeight = block.Header.FinalNum raw, err := store.encodeBlock(block) if err != nil { diff --git a/types/types.go b/types/types.go index cda708e..06ba086 100644 --- a/types/types.go +++ b/types/types.go @@ -2,13 +2,23 @@ package types import ( "crypto/sha256" + "encoding/binary" "fmt" "math/big" "time" ) func MakeHash(data interface{}) string { - shaSum := sha256.Sum256([]byte(fmt.Sprintf("%v", data))) + return MakeHashNonce(data, nil) +} + +func MakeHashNonce(data interface{}, nonce *uint64) string { + content := []byte(fmt.Sprintf("%v", data)) + if nonce != nil { + content = binary.LittleEndian.AppendUint64(content, *nonce) + } + + shaSum := sha256.Sum256(content) return fmt.Sprintf("%x", shaSum) }