Skip to content

Commit

Permalink
Updated to latest Firehose standards
Browse files Browse the repository at this point in the history
  • Loading branch information
maoueh committed Jan 9, 2024
1 parent ba3d757 commit 3fea13d
Show file tree
Hide file tree
Showing 14 changed files with 547 additions and 357 deletions.
82 changes: 24 additions & 58 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,50 +8,17 @@ process.
## Requirements

- Go
## Building

Clone the repository:

```bash
git clone https://github.com/streamingfast/dummy-blockchain.git
cd dummy-blockchain
```
## Getting Started

Then install the binary:
To install the binary:

```bash
go install .
go install github.com/streamingfast/dummy-blockchain@laest
```

## Usage

Run `./dummy-blockchain --help` to see list of all available flags:

```
CLI for the Dummy Chain
Usage:
dummy-blockchain [command]
Available Commands:
completion Generate the autocompletion script for the specified shell
help Help about any command
init Initialize local blockchain state
reset Reset local blockchain state
start Start blockchain service
Flags:
--block-rate int Block production rate (per minute) (default 60)
--firehose-enabled Enable instrumentation
--genesis-height uint Blockchain genesis height (default 1)
-h, --help help for dummy-blockchain
--log-level string Logging level (default "info")
--server-addr string Server address (default "0.0.0.0:8080")
--stop-height uint Stop block production at this height
--store-dir string Directory for storing blockchain state (default "./data")
Use "dummy-blockchain [command] --help" for more information about a command.
```
> [!NOTE]
> Ensure `go env GOPATH` directoy is part of your PATH (export PATH="`` `go env GOPATH` ``:$PATH")
To start the chain, run:

Expand All @@ -77,31 +44,30 @@ INFO[2022-01-13T11:55:13-06:00] processing block ha
To enable firehose instrumentation:

```
./dummy-blockchain start --firehose-enabled
# Or using env var
FIREHOSE_ENABLED=1 ./dummy-blockchain start
./dummy-blockchain start --tracer=firehose
```

Output will look like:
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.

```
INFO[2022-01-13T11:55:52-06:00] initializing node
INFO[2022-01-13T11:55:52-06:00] initializing store
DEBU[2022-01-13T11:55:52-06:00] creating store root directory dir=./data
INFO[2022-01-13T11:55:52-06:00] loading last block tip=6
INFO[2022-01-13T11:55:52-06:00] initializing engine
INFO[2022-01-13T11:55:52-06:00] starting block producer rate=1s
INFO[2022-01-13T11:55:53-06:00] processing block hash=7902699be42c8a8e46fbbb4501726517e86b22c56a189f7625a6da49081b2451 height=7
FIRE BLOCK_BEGIN 7
FIRE BLOCK CAcSQDc5MDI2OTliZTQyYzhhOGU0NmZiYmI0NTAxNzI2NTE3ZTg2YjIyYzU2YTE4OWY3NjI1YTZkYTQ5MDgxYjI0NTEaQGU3ZjZjMDExNzc2ZThkYjdjZDMzMGI1NDE3NGZkNzZmN2QwMjE2YjYxMjM4N2E1ZmZjZmI4MWU2ZjA5MTk2ODMqjAEKCHRyYW5zZmVyEkBiMTEwZDg4OWUzNGU2MTdlMmIyYmZmNTdhYWMzNTU3Njc2YzJmNjgxZjM2NWJhZDVhODk2MTVkN2E4MDZmMGY0GgoweERFQURCRUFGIgoweEJBQUFBQUFEKgAyBAoCJxA4AUIcCg50b2tlbl90cmFuc2ZlchIKCgNmb28SA2JhciqSAQoIdHJhbnNmZXISQDBlYzE5MmMwZjkwZDEzMzJmMmFiY2E0Mzk4NTk2ZDM5Nzg0MzRlY2JhZTZhYmVhOGZmZDk4OTQxMmI1OTI0NTgaCjB4REVBREJFQUYiCjB4QkFBQUFBQUQqBgoEO5rKADIECgInEDgBQhwKDnRva2VuX3RyYW5zZmVyEgoKA2ZvbxIDYmFyKpIBCgh0cmFuc2ZlchJAMWFlNWEwYzkwMDE3Mzk4NzllZjgxMmE3Y2IzZjMyOTQyMzNmNTBlNWQxZGJkZTc0NzFiNDUxNjMzMDdjNmNkORoKMHhERUFEQkVBRiIKMHhCQUFBQUFBRCoGCgR3NZQAMgQKAicQOAFCHAoOdG9rZW5fdHJhbnNmZXISCgoDZm9vEgNiYXIqkgEKCHRyYW5zZmVyEkBiNjJmODNhYzc5MmJhYWNkMTdmNDI4NTg1NDM3Yzg0NTY2NjlkMGM1MGNmYjVmZGMxMWM5YTY3NTgxZDgxMzExGgoweERFQURCRUFGIgoweEJBQUFBQUFEKgYKBLLQXgAyBAoCJxA4AUIcCg50b2tlbl90cmFuc2ZlchIKCgNmb28SA2JhciqSAQoIdHJhbnNmZXISQGI5YjUwYzU5ZjQyNTFlOWQyZDRkYzQ5Mjc1ZWM0NzYwYTNjOTcwYTllNWQ5MjU0OGQwNDg5MzIzNDkzYmFkODUaCjB4REVBREJFQUYiCjB4QkFBQUFBQUQqBgoE7msoADIECgInEDgBQhwKDnRva2VuX3RyYW5zZmVyEgoKA2ZvbxIDYmFyKpMBCgh0cmFuc2ZlchJANWUzZjViZDMyMDYxNTQ3ZjdkMTAzNWQ0NDg2NGU5Mjg2YTE1OTRiOWJkMDUyOWMzMTU5ODhkOWNkMDdiYzU5MxoKMHhERUFEQkVBRiIKMHhCQUFBQUFBRCoHCgUBKgXyADIECgInEDgBQhwKDnRva2VuX3RyYW5zZmVyEgoKA2ZvbxIDYmFyKpMBCgh0cmFuc2ZlchJAZmYwM2ViZDU2OWJiZTgzMzg3ZTU2M2NkMTdkZDcxODBiZWI3MmNiOGMyYmZmODY3MDAyYzdhZGQyMjUxNGExMxoKMHhERUFEQkVBRiIKMHhCQUFBQUFBRCoHCgUBZaC8ADIECgInEDgBQhwKDnRva2VuX3RyYW5zZmVyEgoKA2ZvbxIDYmFy
FIRE BLOCK_END 7
```
## 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.

You will see here in [tracer/tracer.go](./tracer/tracer.go) and [tracer/firehose_tracer.go](./tracer/firehose_tracer.go) a sketch of such "plugin" in Golang, `geth` can be inspected to see a full fledged block synchronization tracing plugin in a production codebase.

The output format must strictly respect https://github.com/streamingfast/firehose-core standard, the [tracer/firehose_tracer.go](./tracer/firehose_tracer.go) implementation shows how we suggest implementing such tracer, you are free to implement the way you like.

## Building

Customize Firehose log output with environment variable:
Clone the repository:

```bash
git clone https://github.com/streamingfast/dummy-blockchain.git
cd dummy-blockchain

- `FIREHOSE_LOGS_OUTPUT=stdout` - Log to STDOUT (default)
- `FIREHOSE_LOGS_OUTPUT=stderr` - Log to STDERR
- `FIREHOSE_LOGS_OUTPUT=/path/to/file.log` - Log to regular file
go run . start
```

## HTTP API

Expand Down
64 changes: 41 additions & 23 deletions core/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type Engine struct {
blockRate time.Duration
blockChan chan *types.Block
prevBlock *types.Block
finalBlock *types.Block
}

func NewEngine(genesisHeight, stopHeight uint64, rate int) Engine {
Expand All @@ -33,8 +34,14 @@ func NewEngine(genesisHeight, stopHeight uint64, rate int) Engine {
}
}

func (e *Engine) Initialize(block *types.Block) error {
e.prevBlock = block
func (e *Engine) Initialize(prevBlock *types.Block, finalBlock *types.Block) error {
e.prevBlock = prevBlock
e.finalBlock = finalBlock

if finalBlock == nil {
return fmt.Errorf("final block cannot be nil")
}

return nil
}

Expand All @@ -44,7 +51,7 @@ func (e *Engine) StartBlockProduction(ctx context.Context) {
logrus.WithField("rate", e.blockRate).Info("starting block producer")
if e.stopHeight > 0 {
logrus.WithField("stop_height", e.stopHeight).Info("block production will stop at height")
if e.prevBlock != nil && e.prevBlock.Height >= e.stopHeight {
if e.prevBlock != nil && e.prevBlock.Header.Height >= e.stopHeight {
ticker.Stop()
}
}
Expand All @@ -53,9 +60,9 @@ func (e *Engine) StartBlockProduction(ctx context.Context) {
select {
case <-ticker.C:
block := e.createBlock()
e.blockChan <- &block
e.blockChan <- block

if e.stopHeight > 0 && block.Height >= e.stopHeight {
if e.stopHeight > 0 && block.Header.Height >= e.stopHeight {
logrus.Info("reached stop height")
ticker.Stop()
}
Expand All @@ -71,40 +78,51 @@ func (e *Engine) Subscription() <-chan *types.Block {
return e.blockChan
}

func (e *Engine) createBlock() types.Block {
block := types.Block{
Timestamp: time.Now().UTC(),
Transactions: []types.Transaction{},
}

if e.prevBlock != nil { // Continue the chain
block.Height = e.prevBlock.Height + 1
block.Hash = makeHash(block.Height)
block.PrevHash = e.prevBlock.Hash
} else { // Start from genesis height
func (e *Engine) createBlock() *types.Block {
if e.prevBlock == nil {
logrus.WithField("height", e.genesisHeight).Info("starting from genesis block height")
genesisBlock := types.GenesisBlock(e.genesisHeight)
e.prevBlock = genesisBlock
e.finalBlock = genesisBlock

block.Height = e.genesisHeight
block.Hash = makeHash(e.genesisHeight)
block.PrevHash = makeHash(e.genesisHeight - 1)
return genesisBlock
}

for i := uint64(0); i < block.Height%10; i++ {
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{},
}

trxCount := min(block.Header.Height%10, 500)

Check failure on line 103 in core/engine.go

View workflow job for this annotation

GitHub Actions / Build Release (1.18.x, ubuntu-latest)

undefined: min

Check failure on line 103 in core/engine.go

View workflow job for this annotation

GitHub Actions / Test (1.18.x, ubuntu-latest)

undefined: min
for i := uint64(0); i < trxCount; i++ {
tx := types.Transaction{
Type: "transfer",
Hash: makeHash(fmt.Sprintf("%v-%v", block.Height, i)),
Hash: types.MakeHash(fmt.Sprintf("%v-%v", block.Header.Height, i)),
Sender: "0xDEADBEEF",
Receiver: "0xBAAAAAAD",
Amount: big.NewInt(int64(i * 1000000000)),
Fee: big.NewInt(10000),
Success: true,
Events: e.generateEvents(block.Height),
Events: e.generateEvents(block.Header.Height),
}

block.Transactions = append(block.Transactions, tx)
}

e.prevBlock = &block
e.prevBlock = block

if block.Header.Height%10 == 0 {
logrus.WithField("height", block.Header.Height).Info("produced block is now the final block")
e.finalBlock = block
}

return block
}

Expand Down
11 changes: 0 additions & 11 deletions core/hash.go

This file was deleted.

64 changes: 48 additions & 16 deletions core/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@ package core

import (
"context"
"fmt"

"github.com/sirupsen/logrus"
"github.com/streamingfast/dummy-blockchain/firehose"
"github.com/streamingfast/dummy-blockchain/tracer"
"github.com/streamingfast/dummy-blockchain/types"
)

type Node struct {
engine Engine
server Server
store *Store
tracer tracer.Tracer
}

func NewNode(
Expand All @@ -20,13 +22,15 @@ func NewNode(
genesisHeight uint64,
stopHeight uint64,
serverAddr string,
tracer tracer.Tracer,
) *Node {
store := NewStore(storeDir)
store := NewStore(storeDir, genesisHeight)

return &Node{
engine: NewEngine(genesisHeight, stopHeight, blockRate),
store: store,
server: NewServer(store, serverAddr),
tracer: tracer,
}
}

Expand All @@ -40,8 +44,7 @@ func (node *Node) Initialize() error {
}

var tipBlock *types.Block

if tip := node.store.meta.TipHeight; tip > 0 {
if tip := node.store.meta.HeadHeight; tip > 0 {
logrus.WithField("tip", tip).Info("loading last block")
block, err := node.store.ReadBlock(tip)
if err != nil {
Expand All @@ -51,12 +54,38 @@ func (node *Node) Initialize() error {
tipBlock = block
}

var finalBlock *types.Block
final := node.store.meta.FinalHeight
if final == 0 {
// We are uninitialized, so we need to create a genesis block
final = node.store.genesisHeight
}

logrus.WithField("final", final).Info("loading final block")
finalBlock, err := node.store.ReadBlock(final)
if err != nil {
logrus.WithError(err).Error("cant read final block")
return err
}

if finalBlock == nil {
return fmt.Errorf("can't find final block %d", final)
}

logrus.Info("initializing engine")
if err := node.engine.Initialize(tipBlock); err != nil {
if err := node.engine.Initialize(tipBlock, finalBlock); err != nil {
logrus.WithError(err).Error("engine initialization failed")
return err
}

if tracer := node.tracer; tracer != nil {
logrus.Info("initializing tracer")
if err := node.tracer.Initialize(); err != nil {
logrus.WithError(err).Error("tracer initialization failed")
return err
}
}

return nil
}

Expand All @@ -75,18 +104,21 @@ func (node *Node) Start(ctx context.Context) error {
return err
}

if firehose.Enabled {
firehose.BeginBlock(block.Height)
if tracer := node.tracer; tracer != nil {
tracer.OnBlockStart(block.Header)
for _, trx := range block.Transactions {
firehose.BeginTrx(&trx)
for idx, event := range trx.Events {
firehose.TrxBeginEvent(trx.Hash, &event)
for _, attr := range event.Attributes {
firehose.TrxEventAttr(trx.Hash, uint64(idx), attr.Key, attr.Value)
tracer.OnTrxStart(&trx)

func() {
defer tracer.OnTrxEnd(&trx)

for _, event := range trx.Events {
tracer.OnTrxEvent(trx.Hash, &event)
}
}
}()
}
firehose.EndBlock(block)

tracer.OnBlockEnd(block, node.engine.finalBlock.Header)
}

case <-ctx.Done():
Expand All @@ -97,8 +129,8 @@ func (node *Node) Start(ctx context.Context) error {

func (node *Node) processBlock(block *types.Block) error {
logrus.
WithField("height", block.Height).
WithField("hash", block.Hash).
WithField("height", block.Header.Height).
WithField("hash", block.Header.Hash).
Info("processing block")

if err := node.store.WriteBlock(block); err != nil {
Expand Down
Loading

0 comments on commit 3fea13d

Please sign in to comment.