Skip to content

Commit 9da5055

Browse files
author
Andrew Nguyen
authored
[Persistence] TxIndexer to StateHash Bridge (Issue-#315) (#332)
## Description **TL;DR Encapsulate and commit the block and block parts within a persistence context** The objective of this issue is to "bridge" the work being done in #302 (tx indexer integration) and #285 (first state hash implementation. The interface and diagrams for the state hash computation were being done in #252 while the first implementation began in #285. However, since both of these are dependent on the integration of the tx indexer in #302, some conflicting design decisions arose. The goal of this ticket is to unblock #302, while also capturing the results of an offline discussion that led to a decision where the "ephemeral block state" should be wholly managed by the persistence context, being a single, "roll-backable" point of reference - [x] **Enable committing of a block to persistence by simply committing the persistence context** - [x] Remove ephemeral block-related state from the consensus module & utility module, and only maintain it in the persistence module - [x] Simply outward-facing module interfaces to only rely on primitive types - [ ] Remove validatorMap from the consensusModule struct state & access it from the persistence layer (added n the past) Opened new issue for last one to prevent scope from getting out of hand: #331 ## Issue Fixes #315 ## Type of change Please mark the relevant option(s): - [ ] New feature, functionality or library - [ ] Bug fix - [x] Code health or cleanup - [x] Major breaking change - [ ] Documentation - [ ] Other <!-- add details here if it a different type of change --> ## List of changes <!-- List out all the changes made--> - Removed `apphash` and `txResults` from `consensus Module` structure - Modified lifecycle to `set` the proposal block within a Persistence Context - Allow block and parts to be committed with the persistence context - Ported over storing blocks and block parts to persistence module from Consensus and Utility - Encapsulate TxIndexer logic only inside the persistence context - Remove TxResult from the utility module interface (added in [TxIndexer] Integration of transaction indexer (issue-#168) #302) - Combined creation and application of block in proposer lifecycle ## Testing - [x] `make develop_test` - [x] [LocalNet](https://github.com/pokt-network/pocket/blob/main/docs/development/README.md) w/ all of the steps outlined in the `README` ## Required Checklist - [x] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas - [x] I have tested my changes using the available tooling - [x] I have updated the corresponding CHANGELOG ### If Applicable Checklist - [ ] I have updated the corresponding README(s); local and/or global - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] I have added, or updated, [mermaid.js](https://mermaid-js.github.io) diagrams in the corresponding README(s) - [ ] I have added, or updated, documentation and [mermaid.js](https://mermaid-js.github.io) diagrams in `shared/docs/*` if I updated `shared/*`README(s)
1 parent fbb14a4 commit 9da5055

22 files changed

+289
-205
lines changed

consensus/CHANGELOG.md

+7
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.0.0.7] - 2022-11-01
11+
12+
- Removed `apphash` and `txResults` from `consensusModule` structure
13+
- Modified lifecycle to `set` the proposal block within a `PersistenceContext`
14+
- Allow block and parts to be committed with the persistence context
15+
1016
## [0.0.0.6] - 2022-10-12
17+
1118
- Stores transactions alongside blocks during `commit`
1219
- Added current block `[]TxResult` to the module
1320

consensus/block.go

-43
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,11 @@ import (
55
"unsafe"
66

77
typesCons "github.com/pokt-network/pocket/consensus/types"
8-
"github.com/pokt-network/pocket/shared/codec"
98
)
109

1110
func (m *consensusModule) commitBlock(block *typesCons.Block) error {
1211
m.nodeLog(typesCons.CommittingBlock(m.Height, len(block.Transactions)))
1312

14-
// Store the block in the KV store
15-
codec := codec.GetCodec()
16-
blockProtoBytes, err := codec.Marshal(block)
17-
if err != nil {
18-
return err
19-
}
20-
21-
// IMPROVE(olshansky): temporary solution. `ApplyBlock` above applies the
22-
// transactions to the postgres database, and this stores it in the KV store upon commitment.
23-
// Instead of calling this directly, an alternative solution is to store the block metadata in
24-
// the persistence context and have `CommitPersistenceContext` do this under the hood. However,
25-
// additional `Block` metadata will need to be passed through and may change when we merkle the
26-
// state hash.
27-
if err := m.storeBlock(block, blockProtoBytes); err != nil {
28-
return err
29-
}
30-
3113
// Commit and release the context
3214
if err := m.utilityContext.CommitPersistenceContext(); err != nil {
3315
return err
@@ -36,31 +18,6 @@ func (m *consensusModule) commitBlock(block *typesCons.Block) error {
3618
m.utilityContext.ReleaseContext()
3719
m.utilityContext = nil
3820

39-
m.lastAppHash = block.BlockHeader.Hash
40-
41-
return nil
42-
}
43-
44-
// IMPROVE(#285): ensure these persistence operations are atomic, we can't commit block without
45-
// committing the transactions and metadata at the same time
46-
func (m *consensusModule) storeBlock(block *typesCons.Block, blockProtoBytes []byte) error {
47-
store := m.utilityContext.GetPersistenceContext()
48-
// Store in KV Store
49-
if err := store.StoreBlock(blockProtoBytes); err != nil {
50-
return err
51-
}
52-
// Store transactions in Indexer
53-
for _, txResult := range m.TxResults {
54-
if err := store.StoreTransaction(txResult); err != nil {
55-
return err
56-
}
57-
}
58-
59-
// Store in SQL Store
60-
header := block.BlockHeader
61-
if err := store.InsertBlock(uint64(header.Height), header.Hash, header.ProposerAddress, header.QuorumCertificate); err != nil {
62-
return err
63-
}
6421
return nil
6522
}
6623

consensus/consensus_tests/utils_test.go

+8-7
Original file line numberDiff line numberDiff line change
@@ -359,22 +359,23 @@ func baseUtilityContextMock(t *testing.T) *modulesMock.MockUtilityContext {
359359
ctrl := gomock.NewController(t)
360360
utilityContextMock := modulesMock.NewMockUtilityContext(ctrl)
361361
persistenceContextMock := modulesMock.NewMockPersistenceRWContext(ctrl)
362+
persistenceContextMock.EXPECT().SetProposalBlock(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
363+
persistenceContextMock.EXPECT().GetPrevAppHash().Return("", nil).AnyTimes()
362364

363365
utilityContextMock.EXPECT().
364-
GetProposalTransactions(gomock.Any(), maxTxBytes, gomock.AssignableToTypeOf(emptyByzValidators)).
365-
Return(make([][]byte, 0), nil, nil).
366+
CreateAndApplyProposalBlock(gomock.Any(), maxTxBytes).
367+
Return(appHash, make([][]byte, 0), nil).
366368
AnyTimes()
367369
utilityContextMock.EXPECT().
368-
ApplyBlock(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
369-
Return(appHash, nil, nil).
370+
ApplyBlock().
371+
Return(appHash, nil).
370372
AnyTimes()
371373
utilityContextMock.EXPECT().CommitPersistenceContext().Return(nil).AnyTimes()
372374
utilityContextMock.EXPECT().ReleaseContext().Return().AnyTimes()
373375
utilityContextMock.EXPECT().GetPersistenceContext().Return(persistenceContextMock).AnyTimes()
374376

375-
persistenceContextMock.EXPECT().StoreTransaction(gomock.Any()).Return(nil).AnyTimes()
376-
persistenceContextMock.EXPECT().StoreBlock(gomock.Any()).Return(nil).AnyTimes()
377-
persistenceContextMock.EXPECT().InsertBlock(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(nil)
377+
persistenceContextMock.EXPECT().IndexTransactions().Return(nil).AnyTimes()
378+
persistenceContextMock.EXPECT().StoreBlock().Return(nil).AnyTimes()
378379

379380
return utilityContextMock
380381
}

consensus/helpers.go

-1
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,6 @@ func (m *consensusModule) isOptimisticThresholdMet(n int) error {
126126
func (m *consensusModule) resetForNewHeight() {
127127
m.Round = 0
128128
m.Block = nil
129-
m.TxResults = nil
130129
m.highPrepareQC = nil
131130
m.lockedQC = nil
132131
}

consensus/hotstuff_leader.go

+24-19
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package consensus
22

33
import (
44
"encoding/hex"
5-
"github.com/pokt-network/pocket/shared/modules"
5+
"github.com/pokt-network/pocket/shared/codec"
66
"unsafe"
77

88
consensusTelemetry "github.com/pokt-network/pocket/consensus/telemetry"
@@ -54,27 +54,24 @@ func (handler *HotstuffLeaderMessageHandler) HandleNewRoundMessage(m *consensusM
5454
// TODO: Add more unit tests for these checks...
5555
if m.shouldPrepareNewBlock(highPrepareQC) {
5656
// Leader prepares a new block if `highPrepareQC` is not applicable
57-
block, txResults, err := m.prepareAndApplyBlock()
57+
block, err := m.prepareAndApplyBlock()
5858
if err != nil {
5959
m.nodeLogError(typesCons.ErrPrepareBlock.Error(), err)
6060
m.paceMaker.InterruptRound()
6161
return
6262
}
6363
m.Block = block
64-
m.TxResults = txResults
6564
} else {
6665
// DISCUSS: Do we need to call `validateProposal` here?
6766
// Leader acts like a replica if `highPrepareQC` is not `nil`
6867
// TODO(olshansky): Add test to make sure same block is not applied twice if round is interrrupted.
6968
// been 'Applied'
70-
txResults, err := m.applyBlock(highPrepareQC.Block)
71-
if err != nil {
69+
if err := m.applyBlock(highPrepareQC.Block); err != nil {
7270
m.nodeLogError(typesCons.ErrApplyBlock.Error(), err)
7371
m.paceMaker.InterruptRound()
7472
return
7573
}
7674
m.Block = highPrepareQC.Block
77-
m.TxResults = txResults
7875
}
7976

8077
m.Step = Prepare
@@ -337,36 +334,33 @@ func (m *consensusModule) tempIndexHotstuffMessage(msg *typesCons.HotstuffMessag
337334

338335
// This is a helper function intended to be called by a leader/validator during a view change
339336
// to prepare a new block that is applied to the new underlying context.
340-
func (m *consensusModule) prepareAndApplyBlock() (*typesCons.Block, []modules.TxResult, error) {
337+
func (m *consensusModule) prepareAndApplyBlock() (*typesCons.Block, error) {
341338
if m.isReplica() {
342-
return nil, nil, typesCons.ErrReplicaPrepareBlock
339+
return nil, typesCons.ErrReplicaPrepareBlock
343340
}
344341

345342
// TECHDEBT: Retrieve this from consensus consensus config
346343
maxTxBytes := 90000
347344

348-
// TECHDEBT: Retrieve this from persistence
349-
lastByzValidators := make([][]byte, 0)
350-
351345
// Reap the mempool for transactions to be applied in this block
352-
txs, _, err := m.utilityContext.GetProposalTransactions(m.privateKey.Address(), maxTxBytes, lastByzValidators)
346+
appHash, txs, err := m.utilityContext.CreateAndApplyProposalBlock(m.privateKey.Address(), maxTxBytes)
353347
if err != nil {
354-
return nil, nil, err
348+
return nil, err
355349
}
356350

357-
// OPTIMIZE: Determine if we can avoid the `ApplyBlock` call here
358-
// Apply all the transactions in the block
359-
appHash, txResults, err := m.utilityContext.ApplyBlock(int64(m.Height), m.privateKey.Address(), txs, lastByzValidators)
351+
persistenceContext := m.utilityContext.GetPersistenceContext()
352+
353+
prevAppHash, err := persistenceContext.GetPrevAppHash()
360354
if err != nil {
361-
return nil, nil, err
355+
return nil, err
362356
}
363357

364358
// Construct the block
365359
blockHeader := &typesCons.BlockHeader{
366360
Height: int64(m.Height),
367361
Hash: hex.EncodeToString(appHash),
368362
NumTxs: uint32(len(txs)),
369-
LastBlockHash: m.lastAppHash,
363+
LastBlockHash: prevAppHash, // IMRPROVE: this should be a block hash not the appHash
370364
ProposerAddress: m.privateKey.Address().Bytes(),
371365
QuorumCertificate: []byte("HACK: Temporary placeholder"),
372366
}
@@ -375,7 +369,18 @@ func (m *consensusModule) prepareAndApplyBlock() (*typesCons.Block, []modules.Tx
375369
Transactions: txs,
376370
}
377371

378-
return block, txResults, nil
372+
cdc := codec.GetCodec()
373+
blockProtoBz, err := cdc.Marshal(block)
374+
if err != nil {
375+
return nil, err
376+
}
377+
378+
// Set the proposal block in the persistence context
379+
if err = persistenceContext.SetProposalBlock(blockHeader.Hash, blockProtoBz, blockHeader.ProposerAddress, blockHeader.QuorumCertificate, block.Transactions); err != nil {
380+
return nil, err
381+
}
382+
383+
return block, nil
379384
}
380385

381386
// Return true if this node, the leader, should prepare a new block

consensus/hotstuff_replica.go

+17-13
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,10 @@ package consensus
33
import (
44
"encoding/hex"
55
"fmt"
6-
"github.com/pokt-network/pocket/shared/modules"
7-
86
consensusTelemetry "github.com/pokt-network/pocket/consensus/telemetry"
97
"github.com/pokt-network/pocket/consensus/types"
108
typesCons "github.com/pokt-network/pocket/consensus/types"
9+
"github.com/pokt-network/pocket/shared/codec"
1110
)
1211

1312
type HotstuffReplicaMessageHandler struct{}
@@ -61,14 +60,12 @@ func (handler *HotstuffReplicaMessageHandler) HandlePrepareMessage(m *consensusM
6160
}
6261

6362
block := msg.GetBlock()
64-
txResults, err := m.applyBlock(block)
65-
if err != nil {
63+
if err := m.applyBlock(block); err != nil {
6664
m.nodeLogError(typesCons.ErrApplyBlock.Error(), err)
6765
m.paceMaker.InterruptRound()
6866
return
6967
}
7068
m.Block = block
71-
m.TxResults = txResults
7269
m.Step = PreCommit
7370

7471
prepareVoteMessage, err := CreateVoteMessage(m.Height, m.Round, Prepare, m.Block, m.privateKey)
@@ -228,22 +225,29 @@ func (m *consensusModule) validateProposal(msg *typesCons.HotstuffMessage) error
228225
}
229226

230227
// This helper applies the block metadata to the utility & persistence layers
231-
func (m *consensusModule) applyBlock(block *typesCons.Block) ([]modules.TxResult, error) {
232-
// TECHDEBT: Retrieve this from persistence
233-
lastByzValidators := make([][]byte, 0)
228+
func (m *consensusModule) applyBlock(block *typesCons.Block) error {
229+
blockProtoBz, err := codec.GetCodec().Marshal(block)
230+
if err != nil {
231+
return err
232+
}
233+
persistenceContext := m.utilityContext.GetPersistenceContext()
234+
// Set the proposal block in the persistence context
235+
if err = persistenceContext.SetProposalBlock(block.BlockHeader.Hash, blockProtoBz, block.BlockHeader.ProposerAddress, block.BlockHeader.QuorumCertificate, block.Transactions); err != nil {
236+
return err
237+
}
234238

235239
// Apply all the transactions in the block and get the appHash
236-
appHash, txResults, err := m.utilityContext.ApplyBlock(int64(m.Height), block.BlockHeader.ProposerAddress, block.Transactions, lastByzValidators)
240+
appHash, err := m.utilityContext.ApplyBlock()
237241
if err != nil {
238-
return txResults, err
242+
return err
239243
}
240244

241-
// CONSOLIDATE: Terminology of `blockHash`, `appHash` and `stateHash`
245+
// CONSOLIDATE: Terminology of `appHash` and `stateHash`
242246
if block.BlockHeader.Hash != hex.EncodeToString(appHash) {
243-
return txResults, typesCons.ErrInvalidAppHash(block.BlockHeader.Hash, hex.EncodeToString(appHash))
247+
return typesCons.ErrInvalidAppHash(block.BlockHeader.Hash, hex.EncodeToString(appHash))
244248
}
245249

246-
return txResults, nil
250+
return nil
247251
}
248252

249253
func (m *consensusModule) validateQuorumCertificate(qc *typesCons.QuorumCertificate) error {

consensus/module.go

-12
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ type consensusModule struct {
5959
idToValAddrMap typesCons.IdToValAddrMap // TODO: This needs to be updated every time the ValMap is modified
6060

6161
// Consensus State
62-
lastAppHash string // TODO: Always retrieve this variable from the persistence module and simplify this struct
6362
validatorMap typesCons.ValidatorMap
6463

6564
// Module Dependencies
@@ -136,7 +135,6 @@ func (*consensusModule) Create(runtimeMgr modules.RuntimeMgr) (modules.Module, e
136135
valAddrToIdMap: valIdMap,
137136
idToValAddrMap: idValMap,
138137

139-
lastAppHash: "",
140138
validatorMap: valMap,
141139

142140
utilityContext: nil,
@@ -235,10 +233,6 @@ func (m *consensusModule) HandleMessage(message *anypb.Any) error {
235233
return nil
236234
}
237235

238-
func (m *consensusModule) AppHash() string {
239-
return m.lastAppHash
240-
}
241-
242236
func (m *consensusModule) CurrentHeight() uint64 {
243237
return m.Height
244238
}
@@ -266,13 +260,7 @@ func (m *consensusModule) loadPersistedState() error {
266260
return nil
267261
}
268262

269-
appHash, err := persistenceContext.GetBlockHash(int64(latestHeight))
270-
if err != nil {
271-
return fmt.Errorf("error getting block hash for height %d even though it's in the database: %s", latestHeight, err)
272-
}
273-
274263
m.Height = uint64(latestHeight) + 1 // +1 because the height of the consensus module is where it is actively participating in consensus
275-
m.lastAppHash = string(appHash)
276264

277265
m.nodeLog(fmt.Sprintf("Starting node at height %d", latestHeight))
278266
return nil

persistence/CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.0.0.7] - 2022-11-01
11+
12+
- Ported over storing blocks and block components to the Persistence module from Consensus and Utility modules
13+
- Encapsulated `TxIndexer` logic to the persistence context only
14+
1015
## [0.0.0.6] - 2022-10-06
1116

1217
- Don't ignore the exit code of `m.Run()` in the unit tests

0 commit comments

Comments
 (0)