-
Notifications
You must be signed in to change notification settings - Fork 11
/
types.go
173 lines (148 loc) · 7.33 KB
/
types.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
package firecore
import (
"fmt"
"time"
"github.com/spf13/cobra"
pbbstream "github.com/streamingfast/bstream/pb/sf/bstream/v1"
"github.com/streamingfast/bstream/transform"
"github.com/streamingfast/dstore"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/anypb"
"google.golang.org/protobuf/types/known/timestamppb"
)
// Block represents the chain-specific Protobuf block. Chain specific's block
// model must implement this interface so that Firehose core is able to properly
// marshal/unmarshal your block into/to the Firehose block envelope binary format.
//
// All the methods are prefixed with `GetFirehoseBlock` to avoid any potential
// conflicts with the fields/getters of your chain's block model that would
// prevent you from implementing this interface.
//
// Consumer of your chain's protobuf block model don't need to be aware of those
// details, they are internal Firehose core information that are required to function
// properly.
//
// The value you return for each of those methods must be done respecting Firehose rules
// which are enumarated in the documentation of each method.
type Block interface {
proto.Message
// GetFirehoseBlockID returns the block ID as a string, usually in the representation
// used by your chain (hex, base58, base64, etc.). The block ID must be unique across
// all blocks that will ever exist on your chain.
GetFirehoseBlockID() string
// GetFirehoseBlockNumber returns the block number as an unsigned integer. The block
// number could be shared by multiple blocks in which case one is the canonical one
// and the others are forks (resolution of forks is handled by Firehose core later in the
// block processing pipeline).
//
// The value should be sequentially ordered which means that a block with block number 10
// has come before block 11. Firehose core will deal with block skips without problem though
// (e.g. block 1, is produced then block 3 where block 3's parent is block 1).
GetFirehoseBlockNumber() uint64
// GetFirehoseBlockParentID returns the block ID of the parent block as a string. All blocks
// ever produced must have a parent block ID except for the genesis block which is the first
// one. The value must be the same as the one returned by GetFirehoseBlockID() of the parent.
//
// If it's the genesis block, return an empty string.
GetFirehoseBlockParentID() string
// GetFirehoseBlockParentNumber returns the block number of the parent block as a uint64.
// The value must be the same as the one returned by GetFirehoseBlockNumber() of the parent
// or `0` if the block has no parent
//
// This is useful on chains that have holes. On other chains, this is as simple as "BlockNumber - 1".
GetFirehoseBlockParentNumber() uint64
// GetFirehoseBlockTime returns the block timestamp as a time.Time of when the block was
// produced. This should the consensus agreed time of the block.
GetFirehoseBlockTime() time.Time
}
// BlockLIBNumDerivable is an optional interface that can be implemented by your chain's block model Block
// if the LIB can be derived from the Block model directly.
//
// Implementing this make some Firehose core process more convenient since less configuration are
// necessary.
type BlockLIBNumDerivable interface {
// GetFirehoseBlockLIBNum returns the last irreversible block number as an unsigned integer
// of this block. This is one of the most important piece of information for Firehose core.
// as it determines when "forks" are now stalled and should be removed from memory and it
// drives a bunch of important write processes that will write the block to disk only when the
// block is now irreversible.
//
// The value returned should be the oldest block that should turned to be irreversible when this
// block was produced. Assume for example the current block is 100. If finality rule of a chain
// is that a block become irreversible after 12 blocks has been produced, then the value returned
// in this case should be 88 (100 - 12) which means that when block 100 was produced, block 88
// can now be considered irreversible.
//
// Irreversibility is chain specific and how the value here is returned depends on the chain. On
// probabilistic irreversible chains, like Bitcoin, the value returned here is usually the current
// block number - <threshold> where <threshold> is choosen to be safe enough in all situations (ensure
// that is block number < <threshold>, then you properly cap to 0).
//
// On deterministic irreversible chains, usually the last irreversible block number if part of the
// consensus and as such should be part of the Protobuf block model somewhere. In those cases, this
// value should be returned here.
GetFirehoseBlockLIBNum() uint64
}
var _ BlockLIBNumDerivable = BlockEnveloppe{}
type BlockEnveloppe struct {
Block
LIBNum uint64
}
// GetFirehoseBlockLIBNum implements LIBDerivable.
func (b BlockEnveloppe) GetFirehoseBlockLIBNum() uint64 {
return b.LIBNum
}
// BlockEncoder is the interface of an object that is going to a chain specific
// block implementing [Block] interface that will be encoded into [bstream.Block]
// type which is the type used by Firehose core to "envelope" the block.
type BlockEncoder interface {
Encode(block Block) (blk *pbbstream.Block, err error)
}
type BlockEncoderFunc func(block Block) (blk *pbbstream.Block, err error)
func (f BlockEncoderFunc) Encode(block Block) (blk *pbbstream.Block, err error) {
return f(block)
}
type CommandExecutor func(cmd *cobra.Command, args []string) (err error)
func NewBlockEncoder() BlockEncoder {
return BlockEncoderFunc(func(block Block) (blk *pbbstream.Block, err error) {
return EncodeBlock(block)
})
}
func EncodeBlock(b Block) (blk *pbbstream.Block, err error) {
v, ok := b.(BlockLIBNumDerivable)
if !ok {
return nil, fmt.Errorf(
"block %T does not implement 'firecore.BlockLIBNumDerivable' which is mandatory, "+
"if you transmit the LIBNum through a side channel, wrap your block with "+
"'firecore.BlockEnveloppe{Block: b, LIBNum: <value>}' to send the LIBNum "+
"to use for encoding ('firecore.BlockEnveloppe' implements 'firecore.BlockLIBNumDerivable')",
b,
)
}
anyBlock, err := anypb.New(b)
if err != nil {
return nil, fmt.Errorf("create any block: %w", err)
}
bstreamBlock := &pbbstream.Block{
Id: b.GetFirehoseBlockID(),
Number: b.GetFirehoseBlockNumber(),
ParentId: b.GetFirehoseBlockParentID(),
Timestamp: timestamppb.New(b.GetFirehoseBlockTime()),
LibNum: v.GetFirehoseBlockLIBNum(),
Payload: anyBlock,
}
return bstreamBlock, nil
}
type BlockIndexerFactory[B Block] func(indexStore dstore.Store, indexSize uint64) (BlockIndexer[B], error)
type BlockIndexer[B Block] interface {
ProcessBlock(block B) error
}
// BlockTransformerFactory is a bit convoluted, but yes it's a function acting as a factory that returns itself
// a factory. The reason for this is that the factory needs to be able to access the index store and the index
// size to be able to create the actual factory.
//
// In the context of `firehose-core` transform registration, this function will be called exactly once
// for the overall process. The returns [transform.Factory] will be used multiple times (one per request
// requesting this transform).
type BlockTransformerFactory func(indexStore dstore.Store, indexPossibleSizes []uint64) (*transform.Factory, error)
type ReaderNodeArgumentResolver = func(in string) string