abci: implement finalize block (#9468)

Adds the `FinalizeBlock` method which replaces `BeginBlock`, `DeliverTx`, and `EndBlock` in a single call.
This commit is contained in:
Callum Waters
2022-11-28 23:12:28 +01:00
committed by GitHub
parent 001cd50fc7
commit c5c2aafad2
142 changed files with 6717 additions and 8420 deletions
+1 -1
View File
@@ -342,7 +342,7 @@ type Header struct {
ConsensusHash tmbytes.HexBytes `json:"consensus_hash"` // consensus params for current block
AppHash tmbytes.HexBytes `json:"app_hash"` // state after txs from the previous block
// root hash of all results from the txs from the previous block
// see `deterministicResponseDeliverTx` to understand which parts of a tx is hashed into here
// see `deterministicExecTxResult` to understand which parts of a tx is hashed into here
LastResultsHash tmbytes.HexBytes `json:"last_results_hash"`
// consensus info
+13 -8
View File
@@ -135,8 +135,7 @@ func (b *EventBus) PublishEventNewBlock(data EventDataNewBlock) error {
// no explicit deadline for publishing events
ctx := context.Background()
resultEvents := append(data.ResultBeginBlock.Events, data.ResultEndBlock.Events...)
events := b.validateAndStringifyEvents(resultEvents, b.Logger.With("block", data.Block.StringShort()))
events := b.validateAndStringifyEvents(data.ResultFinalizeBlock.Events, b.Logger.With("height", data.Block.Height))
// add predefined new block event
events[EventTypeKey] = append(events[EventTypeKey], EventNewBlock)
@@ -144,20 +143,22 @@ func (b *EventBus) PublishEventNewBlock(data EventDataNewBlock) error {
return b.pubsub.PublishWithEvents(ctx, data, events)
}
func (b *EventBus) PublishEventNewBlockHeader(data EventDataNewBlockHeader) error {
func (b *EventBus) PublishEventNewBlockEvents(data EventDataNewBlockEvents) error {
// no explicit deadline for publishing events
ctx := context.Background()
resultTags := append(data.ResultBeginBlock.Events, data.ResultEndBlock.Events...)
// TODO: Create StringShort method for Header and use it in logger.
events := b.validateAndStringifyEvents(resultTags, b.Logger.With("header", data.Header))
events := b.validateAndStringifyEvents(data.Events, b.Logger.With("height", data.Height))
// add predefined new block header event
events[EventTypeKey] = append(events[EventTypeKey], EventNewBlockHeader)
// add predefined new block event
events[EventTypeKey] = append(events[EventTypeKey], EventNewBlockEvents)
return b.pubsub.PublishWithEvents(ctx, data, events)
}
func (b *EventBus) PublishEventNewBlockHeader(data EventDataNewBlockHeader) error {
return b.Publish(EventNewBlockHeader, data)
}
func (b *EventBus) PublishEventNewEvidence(evidence EventDataNewEvidence) error {
return b.Publish(EventNewEvidence, evidence)
}
@@ -255,6 +256,10 @@ func (NopEventBus) PublishEventNewBlockHeader(data EventDataNewBlockHeader) erro
return nil
}
func (NopEventBus) PublishEventNewBlockEvents(data EventDataNewBlockEvents) error {
return nil
}
func (NopEventBus) PublishEventNewEvidence(evidence EventDataNewEvidence) error {
return nil
}
+65 -33
View File
@@ -27,7 +27,7 @@ func TestEventBusPublishEventTx(t *testing.T) {
})
tx := Tx("foo")
result := abci.ResponseDeliverTx{
result := abci.ExecTxResult{
Data: []byte("bar"),
Events: []abci.Event{
{Type: "testType", Attributes: []abci.EventAttribute{{Key: "baz", Value: "1"}}},
@@ -76,19 +76,14 @@ func TestEventBusPublishEventNewBlock(t *testing.T) {
})
block := MakeBlock(0, []Tx{}, nil, []Evidence{})
resultBeginBlock := abci.ResponseBeginBlock{
resultFinalizeBlock := abci.ResponseFinalizeBlock{
Events: []abci.Event{
{Type: "testType", Attributes: []abci.EventAttribute{{Key: "baz", Value: "1"}}},
},
}
resultEndBlock := abci.ResponseEndBlock{
Events: []abci.Event{
{Type: "testType", Attributes: []abci.EventAttribute{{Key: "foz", Value: "2"}}},
},
}
// PublishEventNewBlock adds the tm.event compositeKey, so the query below should work
query := "tm.event='NewBlock' AND testType.baz=1 AND testType.foz=2"
query := "tm.event='NewBlock' AND testType.baz=1"
blocksSub, err := eventBus.Subscribe(context.Background(), "test", tmquery.MustCompile(query))
require.NoError(t, err)
@@ -97,15 +92,21 @@ func TestEventBusPublishEventNewBlock(t *testing.T) {
msg := <-blocksSub.Out()
edt := msg.Data().(EventDataNewBlock)
assert.Equal(t, block, edt.Block)
assert.Equal(t, resultBeginBlock, edt.ResultBeginBlock)
assert.Equal(t, resultEndBlock, edt.ResultEndBlock)
assert.Equal(t, resultFinalizeBlock, edt.ResultFinalizeBlock)
close(done)
}()
var ps *PartSet
ps, err = block.MakePartSet(MaxBlockSizeBytes)
require.NoError(t, err)
err = eventBus.PublishEventNewBlock(EventDataNewBlock{
Block: block,
ResultBeginBlock: resultBeginBlock,
ResultEndBlock: resultEndBlock,
Block: block,
BlockID: BlockID{
Hash: block.Hash(),
PartSetHeader: ps.Header(),
},
ResultFinalizeBlock: resultFinalizeBlock,
})
assert.NoError(t, err)
@@ -127,7 +128,7 @@ func TestEventBusPublishEventTxDuplicateKeys(t *testing.T) {
})
tx := Tx("foo")
result := abci.ResponseDeliverTx{
result := abci.ExecTxResult{
Data: []byte("bar"),
Events: []abci.Event{
{
@@ -235,19 +236,8 @@ func TestEventBusPublishEventNewBlockHeader(t *testing.T) {
})
block := MakeBlock(0, []Tx{}, nil, []Evidence{})
resultBeginBlock := abci.ResponseBeginBlock{
Events: []abci.Event{
{Type: "testType", Attributes: []abci.EventAttribute{{Key: "baz", Value: "1"}}},
},
}
resultEndBlock := abci.ResponseEndBlock{
Events: []abci.Event{
{Type: "testType", Attributes: []abci.EventAttribute{{Key: "foz", Value: "2"}}},
},
}
// PublishEventNewBlockHeader adds the tm.event compositeKey, so the query below should work
query := "tm.event='NewBlockHeader' AND testType.baz=1 AND testType.foz=2"
query := "tm.event='NewBlockHeader'"
headersSub, err := eventBus.Subscribe(context.Background(), "test", tmquery.MustCompile(query))
require.NoError(t, err)
@@ -256,15 +246,53 @@ func TestEventBusPublishEventNewBlockHeader(t *testing.T) {
msg := <-headersSub.Out()
edt := msg.Data().(EventDataNewBlockHeader)
assert.Equal(t, block.Header, edt.Header)
assert.Equal(t, resultBeginBlock, edt.ResultBeginBlock)
assert.Equal(t, resultEndBlock, edt.ResultEndBlock)
close(done)
}()
err = eventBus.PublishEventNewBlockHeader(EventDataNewBlockHeader{
Header: block.Header,
ResultBeginBlock: resultBeginBlock,
ResultEndBlock: resultEndBlock,
Header: block.Header,
})
assert.NoError(t, err)
select {
case <-done:
case <-time.After(1 * time.Second):
t.Fatal("did not receive a block header after 1 sec.")
}
}
func TestEventBusPublishEventNewBlockEvents(t *testing.T) {
eventBus := NewEventBus()
err := eventBus.Start()
require.NoError(t, err)
t.Cleanup(func() {
if err := eventBus.Stop(); err != nil {
t.Error(err)
}
})
// PublishEventNewBlockHeader adds the tm.event compositeKey, so the query below should work
query := "tm.event='NewBlockEvents'"
headersSub, err := eventBus.Subscribe(context.Background(), "test", tmquery.MustCompile(query))
require.NoError(t, err)
done := make(chan struct{})
go func() {
msg := <-headersSub.Out()
edt := msg.Data().(EventDataNewBlockEvents)
assert.Equal(t, int64(1), edt.Height)
close(done)
}()
err = eventBus.PublishEventNewBlockEvents(EventDataNewBlockEvents{
Height: 1,
Events: []abci.Event{{
Type: "transfer",
Attributes: []abci.EventAttribute{{
Key: "currency",
Value: "ATOM",
}},
}},
})
assert.NoError(t, err)
@@ -324,7 +352,7 @@ func TestEventBusPublish(t *testing.T) {
}
})
const numEventsExpected = 14
const numEventsExpected = 15
sub, err := eventBus.Subscribe(context.Background(), "test", tmquery.All, numEventsExpected)
require.NoError(t, err)
@@ -343,10 +371,12 @@ func TestEventBusPublish(t *testing.T) {
err = eventBus.Publish(EventNewBlockHeader, EventDataNewBlockHeader{})
require.NoError(t, err)
err = eventBus.PublishEventNewBlock(EventDataNewBlock{})
err = eventBus.PublishEventNewBlock(EventDataNewBlock{Block: &Block{Header: Header{Height: 1}}})
require.NoError(t, err)
err = eventBus.PublishEventNewBlockHeader(EventDataNewBlockHeader{})
require.NoError(t, err)
err = eventBus.PublishEventNewBlockEvents(EventDataNewBlockEvents{Height: 1})
require.NoError(t, err)
err = eventBus.PublishEventVote(EventDataVote{})
require.NoError(t, err)
err = eventBus.PublishEventNewRoundStep(EventDataRoundState{})
@@ -465,6 +495,7 @@ func benchmarkEventBus(numClients int, randQueries bool, randEvents bool, b *tes
var events = []string{
EventNewBlock,
EventNewBlockHeader,
EventNewBlockEvents,
EventNewRound,
EventNewRoundStep,
EventTimeoutPropose,
@@ -483,6 +514,7 @@ func randEvent() string {
var queries = []tmpubsub.Query{
EventQueryNewBlock,
EventQueryNewBlockHeader,
EventQueryNewBlockEvents,
EventQueryNewRound,
EventQueryNewRoundStep,
EventQueryTimeoutPropose,
+16 -11
View File
@@ -18,6 +18,7 @@ const (
// All of this data can be fetched through the rpc.
EventNewBlock = "NewBlock"
EventNewBlockHeader = "NewBlockHeader"
EventNewBlockEvents = "NewBlockEvents"
EventNewEvidence = "NewEvidence"
EventTx = "Tx"
EventValidatorSetUpdates = "ValidatorSetUpdates"
@@ -48,6 +49,7 @@ type TMEventData interface {
func init() {
tmjson.RegisterType(EventDataNewBlock{}, "tendermint/event/NewBlock")
tmjson.RegisterType(EventDataNewBlockHeader{}, "tendermint/event/NewBlockHeader")
tmjson.RegisterType(EventDataNewBlockEvents{}, "tendermint/event/NewBlockEvents")
tmjson.RegisterType(EventDataNewEvidence{}, "tendermint/event/NewEvidence")
tmjson.RegisterType(EventDataTx{}, "tendermint/event/Tx")
tmjson.RegisterType(EventDataRoundState{}, "tendermint/event/RoundState")
@@ -62,24 +64,24 @@ func init() {
// but some (an input to a call tx or a receive) are more exotic
type EventDataNewBlock struct {
Block *Block `json:"block"`
ResultBeginBlock abci.ResponseBeginBlock `json:"result_begin_block"`
ResultEndBlock abci.ResponseEndBlock `json:"result_end_block"`
Block *Block `json:"block"`
BlockID BlockID `json:"block_id"`
ResultFinalizeBlock abci.ResponseFinalizeBlock `json:"result_finalize_block"`
}
type EventDataNewBlockHeader struct {
Header Header `json:"header"`
}
NumTxs int64 `json:"num_txs"` // Number of txs in a block
ResultBeginBlock abci.ResponseBeginBlock `json:"result_begin_block"`
ResultEndBlock abci.ResponseEndBlock `json:"result_end_block"`
type EventDataNewBlockEvents struct {
Height int64 `json:"height"`
Events []abci.Event `json:"events"`
NumTxs int64 `json:"num_txs,string"` // Number of txs in a block
}
type EventDataNewEvidence struct {
Height int64 `json:"height"`
Evidence Evidence `json:"evidence"`
Height int64 `json:"height"`
}
// All txs fire EventDataTx
@@ -130,15 +132,16 @@ type EventDataValidatorSetUpdates struct {
const (
// EventTypeKey is a reserved composite key for event name.
EventTypeKey = "tm.event"
// TxHashKey is a reserved key, used to specify transaction's hash.
// see EventBus#PublishEventTx
TxHashKey = "tx.hash"
// TxHeightKey is a reserved key, used to specify transaction block's height.
// see EventBus#PublishEventTx
TxHeightKey = "tx.height"
// BlockHeightKey is a reserved key used for indexing BeginBlock and Endblock
// events.
// BlockHeightKey is a reserved key used for indexing FinalizeBlock events.
BlockHeightKey = "block.height"
)
@@ -147,6 +150,7 @@ var (
EventQueryLock = QueryForEvent(EventLock)
EventQueryNewBlock = QueryForEvent(EventNewBlock)
EventQueryNewBlockHeader = QueryForEvent(EventNewBlockHeader)
EventQueryNewBlockEvents = QueryForEvent(EventNewBlockEvents)
EventQueryNewEvidence = QueryForEvent(EventNewEvidence)
EventQueryNewRound = QueryForEvent(EventNewRound)
EventQueryNewRoundStep = QueryForEvent(EventNewRoundStep)
@@ -173,6 +177,7 @@ func QueryForEvent(eventType string) tmpubsub.Query {
type BlockEventPublisher interface {
PublishEventNewBlock(block EventDataNewBlock) error
PublishEventNewBlockHeader(header EventDataNewBlockHeader) error
PublishEventNewBlockEvents(events EventDataNewBlockEvents) error
PublishEventNewEvidence(evidence EventDataNewEvidence) error
PublishEventTx(EventDataTx) error
PublishEventValidatorSetUpdates(EventDataValidatorSetUpdates) error
+8 -8
View File
@@ -6,14 +6,14 @@ import (
)
// ABCIResults wraps the deliver tx results to return a proof.
type ABCIResults []*abci.ResponseDeliverTx
type ABCIResults []*abci.ExecTxResult
// NewResults strips non-deterministic fields from ResponseDeliverTx responses
// NewResults strips non-deterministic fields from ExecTxResult responses
// and returns ABCIResults.
func NewResults(responses []*abci.ResponseDeliverTx) ABCIResults {
func NewResults(responses []*abci.ExecTxResult) ABCIResults {
res := make(ABCIResults, len(responses))
for i, d := range responses {
res[i] = deterministicResponseDeliverTx(d)
res[i] = deterministicExecTxResult(d)
}
return res
}
@@ -42,10 +42,10 @@ func (a ABCIResults) toByteSlices() [][]byte {
return bzs
}
// deterministicResponseDeliverTx strips non-deterministic fields from
// ResponseDeliverTx and returns another ResponseDeliverTx.
func deterministicResponseDeliverTx(response *abci.ResponseDeliverTx) *abci.ResponseDeliverTx {
return &abci.ResponseDeliverTx{
// deterministicExecTxResult strips non-deterministic fields from
// ExecTxResult and returns another ExecTxResult.
func deterministicExecTxResult(response *abci.ExecTxResult) *abci.ExecTxResult {
return &abci.ExecTxResult{
Code: response.Code,
Data: response.Data,
GasWanted: response.GasWanted,
+6 -6
View File
@@ -10,12 +10,12 @@ import (
)
func TestABCIResults(t *testing.T) {
a := &abci.ResponseDeliverTx{Code: 0, Data: nil}
b := &abci.ResponseDeliverTx{Code: 0, Data: []byte{}}
c := &abci.ResponseDeliverTx{Code: 0, Data: []byte("one")}
d := &abci.ResponseDeliverTx{Code: 14, Data: nil}
e := &abci.ResponseDeliverTx{Code: 14, Data: []byte("foo")}
f := &abci.ResponseDeliverTx{Code: 14, Data: []byte("bar")}
a := &abci.ExecTxResult{Code: 0, Data: nil}
b := &abci.ExecTxResult{Code: 0, Data: []byte{}}
c := &abci.ExecTxResult{Code: 0, Data: []byte("one")}
d := &abci.ExecTxResult{Code: 14, Data: nil}
e := &abci.ExecTxResult{Code: 14, Data: []byte("foo")}
f := &abci.ExecTxResult{Code: 14, Data: []byte("bar")}
// Nil and []byte{} should produce the same bytes
bzA, err := a.Marshal()
+1 -1
View File
@@ -19,7 +19,7 @@ func shouldBatchVerify(vals *ValidatorSet, commit *Commit) bool {
//
// It checks all the signatures! While it's safe to exit as soon as we have
// 2/3+ signatures, doing so would impact incentivization logic in the ABCI
// application that depends on the LastCommitInfo sent in BeginBlock, which
// application that depends on the LastCommitInfo sent in FinalizeBlock, which
// includes which validators signed. For instance, Gaia incentivizes proposers
// with a bonus for including more than +2/3 of the signatures.
func VerifyCommit(chainID string, vals *ValidatorSet, blockID BlockID,