mirror of
https://github.com/tendermint/tendermint.git
synced 2026-09-25 17:34:36 +00:00
abci: implement finalize block (#9468)
Adds the `FinalizeBlock` method which replaces `BeginBlock`, `DeliverTx`, and `EndBlock` in a single call.
This commit is contained in:
+1
-1
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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,
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user