mirror of
https://github.com/tendermint/tendermint.git
synced 2026-02-01 17:42:03 +00:00
Compare commits
17 Commits
jasmina/82
...
wb/config-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3db35a54dd | ||
|
|
b71ec8c83f | ||
|
|
b421138e53 | ||
|
|
e9239e9ca8 | ||
|
|
136b62762f | ||
|
|
d5fb82e414 | ||
|
|
6902fa9282 | ||
|
|
61ce384d75 | ||
|
|
636320f901 | ||
|
|
d1a16e8ff0 | ||
|
|
27c523dccb | ||
|
|
be6d74e657 | ||
|
|
70e7372c4a | ||
|
|
2b5329ae47 | ||
|
|
3cde9a0bbc | ||
|
|
d8de2d85c0 | ||
|
|
331860c2a8 |
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@@ -7,7 +7,7 @@
|
||||
# global owners are only requested if there isn't a more specific
|
||||
# codeowner specified below. For this reason, the global codeowners
|
||||
# are often repeated in package-level definitions.
|
||||
* @ebuchman @cmwaters @tychoish @williambanfield @creachadair @sergio-mena @jmalicevic @thanethomson @ancazamfir
|
||||
* @ebuchman @cmwaters @tychoish @williambanfield @creachadair @sergio-mena @jmalicevic @thanethomson @ancazamfir @samricotta
|
||||
|
||||
# Spec related changes can be approved by the protocol design team
|
||||
/spec @josef-widder @milosevic @cason @sergio-mena @jmalicevic
|
||||
|
||||
@@ -33,6 +33,7 @@ Special thanks to external contributors on this release:
|
||||
- [abci] \#8664 Move `app_hash` parameter from `Commit` to `FinalizeBlock`. (@sergio-mena)
|
||||
- [abci] \#8656 Added cli command for `PrepareProposal`. (@jmalicevic)
|
||||
- [sink/psql] \#8637 tx_results emitted from psql sink are now json encoded, previously they were protobuf encoded
|
||||
- [abci] \#8901 Added cli command for `ProcessProposal`. (@hvanz)
|
||||
|
||||
- P2P Protocol
|
||||
|
||||
@@ -100,3 +101,4 @@ Special thanks to external contributors on this release:
|
||||
- [cli] \#8276 scmigrate: ensure target key is correctly renamed. (@creachadair)
|
||||
- [cli] \#8294 keymigrate: ensure block hash keys are correctly translated. (@creachadair)
|
||||
- [cli] \#8352 keymigrate: ensure transaction hash keys are correctly translated. (@creachadair)
|
||||
- (indexer) \#8625 Fix overriding tx index of duplicated txs.
|
||||
|
||||
@@ -17,9 +17,9 @@ by Tendermint itself. Right now, we return a regular error when this happens.
|
||||
#### ABCI++
|
||||
|
||||
For information on how ABCI++ works, see the
|
||||
[Specification](https://github.com/tendermint/tendermint/blob/master/spec/abci%2B%2B/README.md).
|
||||
[Specification](spec/abci%2B%2B/README.md).
|
||||
In particular, the simplest way to upgrade your application is described
|
||||
[here](https://github.com/tendermint/tendermint/blob/master/spec/abci%2B%2B/abci++_tmint_expected_behavior_002_draft.md#adapting-existing-applications-that-use-abci).
|
||||
[here](spec/abci%2B%2B/abci++_tmint_expected_behavior.md#adapting-existing-applications-that-use-abci).
|
||||
|
||||
#### Moving the `app_hash` parameter
|
||||
|
||||
|
||||
@@ -79,10 +79,11 @@ func RootCmmand(logger log.Logger) *cobra.Command {
|
||||
// Structure for data passed to print response.
|
||||
type response struct {
|
||||
// generic abci response
|
||||
Data []byte
|
||||
Code uint32
|
||||
Info string
|
||||
Log string
|
||||
Data []byte
|
||||
Code uint32
|
||||
Info string
|
||||
Log string
|
||||
Status int32
|
||||
|
||||
Query *queryResponse
|
||||
}
|
||||
@@ -132,6 +133,7 @@ func addCommands(cmd *cobra.Command, logger log.Logger) {
|
||||
cmd.AddCommand(versionCmd)
|
||||
cmd.AddCommand(testCmd)
|
||||
cmd.AddCommand(prepareProposalCmd)
|
||||
cmd.AddCommand(processProposalCmd)
|
||||
cmd.AddCommand(getQueryCmd())
|
||||
|
||||
// examples
|
||||
@@ -172,7 +174,7 @@ This command opens an interactive console for running any of the other commands
|
||||
without opening a new connection each time
|
||||
`,
|
||||
Args: cobra.ExactArgs(0),
|
||||
ValidArgs: []string{"echo", "info", "query", "check_tx", "prepare_proposal", "finalize_block", "commit"},
|
||||
ValidArgs: []string{"echo", "info", "query", "check_tx", "prepare_proposal", "process_proposal", "finalize_block", "commit"},
|
||||
RunE: cmdConsole,
|
||||
}
|
||||
|
||||
@@ -195,7 +197,7 @@ var finalizeBlockCmd = &cobra.Command{
|
||||
Use: "finalize_block",
|
||||
Short: "deliver a block of transactions to the application",
|
||||
Long: "deliver a block of transactions to the application",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Args: cobra.MinimumNArgs(0),
|
||||
RunE: cmdFinalizeBlock,
|
||||
}
|
||||
|
||||
@@ -230,10 +232,18 @@ var prepareProposalCmd = &cobra.Command{
|
||||
Use: "prepare_proposal",
|
||||
Short: "prepare proposal",
|
||||
Long: "prepare proposal",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Args: cobra.MinimumNArgs(0),
|
||||
RunE: cmdPrepareProposal,
|
||||
}
|
||||
|
||||
var processProposalCmd = &cobra.Command{
|
||||
Use: "process_proposal",
|
||||
Short: "process proposal",
|
||||
Long: "process proposal",
|
||||
Args: cobra.MinimumNArgs(0),
|
||||
RunE: cmdProcessProposal,
|
||||
}
|
||||
|
||||
func getQueryCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "query",
|
||||
@@ -352,6 +362,11 @@ func cmdTest(cmd *cobra.Command, args []string) error {
|
||||
types.TxRecord_UNMODIFIED,
|
||||
}, nil)
|
||||
},
|
||||
func() error {
|
||||
return servertest.ProcessProposal(ctx, client, [][]byte{
|
||||
{0x01},
|
||||
}, types.ResponseProcessProposal_ACCEPT)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -454,6 +469,8 @@ func muxOnCommands(cmd *cobra.Command, pArgs []string) error {
|
||||
return cmdQuery(cmd, actualArgs)
|
||||
case "prepare_proposal":
|
||||
return cmdPrepareProposal(cmd, actualArgs)
|
||||
case "process_proposal":
|
||||
return cmdProcessProposal(cmd, actualArgs)
|
||||
default:
|
||||
return cmdUnimplemented(cmd, pArgs)
|
||||
}
|
||||
@@ -517,13 +534,6 @@ const codeBad uint32 = 10
|
||||
|
||||
// Append new txs to application
|
||||
func cmdFinalizeBlock(cmd *cobra.Command, args []string) error {
|
||||
if len(args) == 0 {
|
||||
printResponse(cmd, args, response{
|
||||
Code: codeBad,
|
||||
Log: "Must provide at least one transaction",
|
||||
})
|
||||
return nil
|
||||
}
|
||||
txs := make([][]byte, len(args))
|
||||
for i, arg := range args {
|
||||
txBytes, err := stringOrHexToBytes(arg)
|
||||
@@ -633,15 +643,8 @@ func inTxArray(txByteArray [][]byte, tx []byte) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func cmdPrepareProposal(cmd *cobra.Command, args []string) error {
|
||||
if len(args) == 0 {
|
||||
printResponse(cmd, args, response{
|
||||
Code: codeBad,
|
||||
Info: "Must provide at least one transaction",
|
||||
Log: "Must provide at least one transaction",
|
||||
})
|
||||
return nil
|
||||
}
|
||||
txsBytesArray := make([][]byte, len(args))
|
||||
|
||||
for i, arg := range args {
|
||||
@@ -682,6 +685,30 @@ func cmdPrepareProposal(cmd *cobra.Command, args []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func cmdProcessProposal(cmd *cobra.Command, args []string) error {
|
||||
txsBytesArray := make([][]byte, len(args))
|
||||
|
||||
for i, arg := range args {
|
||||
txBytes, err := stringOrHexToBytes(arg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
txsBytesArray[i] = txBytes
|
||||
}
|
||||
|
||||
res, err := client.ProcessProposal(cmd.Context(), &types.RequestProcessProposal{
|
||||
Txs: txsBytesArray,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
printResponse(cmd, args, response{
|
||||
Status: int32(res.Status),
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeKVStoreCmd(logger log.Logger) func(*cobra.Command, []string) error {
|
||||
return func(cmd *cobra.Command, args []string) error {
|
||||
// Create the application - in memory or persisted to disk
|
||||
@@ -739,6 +766,9 @@ func printResponse(cmd *cobra.Command, args []string, rsps ...response) {
|
||||
if rsp.Log != "" {
|
||||
fmt.Printf("-> log: %s\n", rsp.Log)
|
||||
}
|
||||
if cmd.Use == "process_proposal" {
|
||||
fmt.Printf("-> status: %s\n", types.ResponseProcessProposal_ProposalStatus_name[rsp.Status])
|
||||
}
|
||||
|
||||
if rsp.Query != nil {
|
||||
fmt.Printf("-> height: %d\n", rsp.Query.Height)
|
||||
|
||||
@@ -175,7 +175,7 @@ func (app *Application) FinalizeBlock(_ context.Context, req *types.RequestFinal
|
||||
app.ValUpdates = make([]types.ValidatorUpdate, 0)
|
||||
|
||||
// Punish validators who committed equivocation.
|
||||
for _, ev := range req.ByzantineValidators {
|
||||
for _, ev := range req.Misbehavior {
|
||||
if ev.Type == types.MisbehaviorType_DUPLICATE_VOTE {
|
||||
addr := string(ev.Validator.Address)
|
||||
if pubKey, ok := app.valAddrToPubKeyMap[addr]; ok {
|
||||
@@ -293,7 +293,7 @@ func (app *Application) PrepareProposal(_ context.Context, req *types.RequestPre
|
||||
|
||||
func (*Application) ProcessProposal(_ context.Context, req *types.RequestProcessProposal) (*types.ResponseProcessProposal, error) {
|
||||
for _, tx := range req.Txs {
|
||||
if len(tx) == 0 {
|
||||
if len(tx) == 0 || isPrepareTx(tx) {
|
||||
return &types.ResponseProcessProposal{Status: types.ResponseProcessProposal_REJECT}, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +83,19 @@ func PrepareProposal(ctx context.Context, client abciclient.Client, txBytes [][]
|
||||
fmt.Println("Passed test: PrepareProposal")
|
||||
return nil
|
||||
}
|
||||
|
||||
func ProcessProposal(ctx context.Context, client abciclient.Client, txBytes [][]byte, statusExp types.ResponseProcessProposal_ProposalStatus) error {
|
||||
res, _ := client.ProcessProposal(ctx, &types.RequestProcessProposal{Txs: txBytes})
|
||||
if res.Status != statusExp {
|
||||
fmt.Println("Failed test: ProcessProposal")
|
||||
fmt.Printf("ProcessProposal response status was unexpected. Got %v expected %v.",
|
||||
res.Status, statusExp)
|
||||
return errors.New("ProcessProposal error")
|
||||
}
|
||||
fmt.Println("Passed test: ProcessProposal")
|
||||
return nil
|
||||
}
|
||||
|
||||
func CheckTx(ctx context.Context, client abciclient.Client, txBytes []byte, codeExp uint32, dataExp []byte) error {
|
||||
res, _ := client.CheckTx(ctx, &types.RequestCheckTx{Tx: txBytes})
|
||||
code, data := res.Code, res.Data
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
echo hello
|
||||
info
|
||||
prepare_proposal "abc"
|
||||
process_proposal "abc"
|
||||
finalize_block "abc"
|
||||
commit
|
||||
info
|
||||
@@ -8,4 +9,10 @@ query "abc"
|
||||
finalize_block "def=xyz" "ghi=123"
|
||||
commit
|
||||
query "def"
|
||||
prepare_proposal "preparedef"
|
||||
prepare_proposal "preparedef"
|
||||
process_proposal "def"
|
||||
process_proposal "preparedef"
|
||||
prepare_proposal
|
||||
process_proposal
|
||||
finalize_block
|
||||
commit
|
||||
@@ -12,6 +12,10 @@
|
||||
-> code: OK
|
||||
-> log: Succeeded. Tx: abc action: UNMODIFIED
|
||||
|
||||
> process_proposal "abc"
|
||||
-> code: OK
|
||||
-> status: ACCEPT
|
||||
|
||||
> finalize_block "abc"
|
||||
-> code: OK
|
||||
-> code: OK
|
||||
@@ -58,3 +62,24 @@
|
||||
-> code: OK
|
||||
-> log: Succeeded. Tx: preparedef action: REMOVED
|
||||
|
||||
> process_proposal "def"
|
||||
-> code: OK
|
||||
-> status: ACCEPT
|
||||
|
||||
> process_proposal "preparedef"
|
||||
-> code: OK
|
||||
-> status: REJECT
|
||||
|
||||
> prepare_proposal
|
||||
|
||||
> process_proposal
|
||||
-> code: OK
|
||||
-> status: ACCEPT
|
||||
|
||||
> finalize_block
|
||||
-> code: OK
|
||||
-> data.hex: 0x0600000000000000
|
||||
|
||||
> commit
|
||||
-> code: OK
|
||||
|
||||
|
||||
@@ -1120,12 +1120,12 @@ type RequestPrepareProposal struct {
|
||||
MaxTxBytes int64 `protobuf:"varint,1,opt,name=max_tx_bytes,json=maxTxBytes,proto3" json:"max_tx_bytes,omitempty"`
|
||||
// txs is an array of transactions that will be included in a block,
|
||||
// sent to the app for possible modifications.
|
||||
Txs [][]byte `protobuf:"bytes,2,rep,name=txs,proto3" json:"txs,omitempty"`
|
||||
LocalLastCommit ExtendedCommitInfo `protobuf:"bytes,3,opt,name=local_last_commit,json=localLastCommit,proto3" json:"local_last_commit"`
|
||||
ByzantineValidators []Misbehavior `protobuf:"bytes,4,rep,name=byzantine_validators,json=byzantineValidators,proto3" json:"byzantine_validators"`
|
||||
Height int64 `protobuf:"varint,5,opt,name=height,proto3" json:"height,omitempty"`
|
||||
Time time.Time `protobuf:"bytes,6,opt,name=time,proto3,stdtime" json:"time"`
|
||||
NextValidatorsHash []byte `protobuf:"bytes,7,opt,name=next_validators_hash,json=nextValidatorsHash,proto3" json:"next_validators_hash,omitempty"`
|
||||
Txs [][]byte `protobuf:"bytes,2,rep,name=txs,proto3" json:"txs,omitempty"`
|
||||
LocalLastCommit ExtendedCommitInfo `protobuf:"bytes,3,opt,name=local_last_commit,json=localLastCommit,proto3" json:"local_last_commit"`
|
||||
Misbehavior []Misbehavior `protobuf:"bytes,4,rep,name=misbehavior,proto3" json:"misbehavior"`
|
||||
Height int64 `protobuf:"varint,5,opt,name=height,proto3" json:"height,omitempty"`
|
||||
Time time.Time `protobuf:"bytes,6,opt,name=time,proto3,stdtime" json:"time"`
|
||||
NextValidatorsHash []byte `protobuf:"bytes,7,opt,name=next_validators_hash,json=nextValidatorsHash,proto3" json:"next_validators_hash,omitempty"`
|
||||
// address of the public key of the validator proposing the block.
|
||||
ProposerAddress []byte `protobuf:"bytes,8,opt,name=proposer_address,json=proposerAddress,proto3" json:"proposer_address,omitempty"`
|
||||
}
|
||||
@@ -1184,9 +1184,9 @@ func (m *RequestPrepareProposal) GetLocalLastCommit() ExtendedCommitInfo {
|
||||
return ExtendedCommitInfo{}
|
||||
}
|
||||
|
||||
func (m *RequestPrepareProposal) GetByzantineValidators() []Misbehavior {
|
||||
func (m *RequestPrepareProposal) GetMisbehavior() []Misbehavior {
|
||||
if m != nil {
|
||||
return m.ByzantineValidators
|
||||
return m.Misbehavior
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1220,9 +1220,9 @@ func (m *RequestPrepareProposal) GetProposerAddress() []byte {
|
||||
}
|
||||
|
||||
type RequestProcessProposal struct {
|
||||
Txs [][]byte `protobuf:"bytes,1,rep,name=txs,proto3" json:"txs,omitempty"`
|
||||
ProposedLastCommit CommitInfo `protobuf:"bytes,2,opt,name=proposed_last_commit,json=proposedLastCommit,proto3" json:"proposed_last_commit"`
|
||||
ByzantineValidators []Misbehavior `protobuf:"bytes,3,rep,name=byzantine_validators,json=byzantineValidators,proto3" json:"byzantine_validators"`
|
||||
Txs [][]byte `protobuf:"bytes,1,rep,name=txs,proto3" json:"txs,omitempty"`
|
||||
ProposedLastCommit CommitInfo `protobuf:"bytes,2,opt,name=proposed_last_commit,json=proposedLastCommit,proto3" json:"proposed_last_commit"`
|
||||
Misbehavior []Misbehavior `protobuf:"bytes,3,rep,name=misbehavior,proto3" json:"misbehavior"`
|
||||
// hash is the merkle root hash of the fields of the proposed block.
|
||||
Hash []byte `protobuf:"bytes,4,opt,name=hash,proto3" json:"hash,omitempty"`
|
||||
Height int64 `protobuf:"varint,5,opt,name=height,proto3" json:"height,omitempty"`
|
||||
@@ -1279,9 +1279,9 @@ func (m *RequestProcessProposal) GetProposedLastCommit() CommitInfo {
|
||||
return CommitInfo{}
|
||||
}
|
||||
|
||||
func (m *RequestProcessProposal) GetByzantineValidators() []Misbehavior {
|
||||
func (m *RequestProcessProposal) GetMisbehavior() []Misbehavior {
|
||||
if m != nil {
|
||||
return m.ByzantineValidators
|
||||
return m.Misbehavior
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1444,10 +1444,10 @@ func (m *RequestVerifyVoteExtension) GetVoteExtension() []byte {
|
||||
}
|
||||
|
||||
type RequestFinalizeBlock struct {
|
||||
Txs [][]byte `protobuf:"bytes,1,rep,name=txs,proto3" json:"txs,omitempty"`
|
||||
DecidedLastCommit CommitInfo `protobuf:"bytes,2,opt,name=decided_last_commit,json=decidedLastCommit,proto3" json:"decided_last_commit"`
|
||||
ByzantineValidators []Misbehavior `protobuf:"bytes,3,rep,name=byzantine_validators,json=byzantineValidators,proto3" json:"byzantine_validators"`
|
||||
// hash is the merkle root hash of the fields of the proposed block.
|
||||
Txs [][]byte `protobuf:"bytes,1,rep,name=txs,proto3" json:"txs,omitempty"`
|
||||
DecidedLastCommit CommitInfo `protobuf:"bytes,2,opt,name=decided_last_commit,json=decidedLastCommit,proto3" json:"decided_last_commit"`
|
||||
Misbehavior []Misbehavior `protobuf:"bytes,3,rep,name=misbehavior,proto3" json:"misbehavior"`
|
||||
// hash is the merkle root hash of the fields of the decided block.
|
||||
Hash []byte `protobuf:"bytes,4,opt,name=hash,proto3" json:"hash,omitempty"`
|
||||
Height int64 `protobuf:"varint,5,opt,name=height,proto3" json:"height,omitempty"`
|
||||
Time time.Time `protobuf:"bytes,6,opt,name=time,proto3,stdtime" json:"time"`
|
||||
@@ -1503,9 +1503,9 @@ func (m *RequestFinalizeBlock) GetDecidedLastCommit() CommitInfo {
|
||||
return CommitInfo{}
|
||||
}
|
||||
|
||||
func (m *RequestFinalizeBlock) GetByzantineValidators() []Misbehavior {
|
||||
func (m *RequestFinalizeBlock) GetMisbehavior() []Misbehavior {
|
||||
if m != nil {
|
||||
return m.ByzantineValidators
|
||||
return m.Misbehavior
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -2381,7 +2381,6 @@ func (m *ResponseDeliverTx) GetCodespace() string {
|
||||
}
|
||||
|
||||
type ResponseCommit struct {
|
||||
// reserve 1
|
||||
RetainHeight int64 `protobuf:"varint,3,opt,name=retain_height,json=retainHeight,proto3" json:"retain_height,omitempty"`
|
||||
}
|
||||
|
||||
@@ -3829,211 +3828,211 @@ func init() {
|
||||
func init() { proto.RegisterFile("tendermint/abci/types.proto", fileDescriptor_252557cfdd89a31a) }
|
||||
|
||||
var fileDescriptor_252557cfdd89a31a = []byte{
|
||||
// 3253 bytes of a gzipped FileDescriptorProto
|
||||
// 3257 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x5a, 0xcd, 0x73, 0x23, 0xd5,
|
||||
0x11, 0xd7, 0xe8, 0x5b, 0xad, 0xaf, 0xf1, 0xb3, 0x59, 0xb4, 0x62, 0xd7, 0x36, 0x43, 0x01, 0xcb,
|
||||
0x02, 0x36, 0xf1, 0x66, 0x61, 0xc9, 0x42, 0x28, 0x5b, 0xd6, 0x46, 0xf6, 0x7a, 0x6d, 0x33, 0x96,
|
||||
0x4d, 0x91, 0x0f, 0x86, 0xb1, 0xf4, 0x6c, 0x0d, 0x2b, 0x69, 0x86, 0x99, 0x91, 0x91, 0x39, 0x26,
|
||||
0xc5, 0x85, 0x43, 0x8a, 0x4b, 0x2a, 0x49, 0x55, 0xb8, 0x25, 0x55, 0xc9, 0x7f, 0x90, 0x5c, 0x72,
|
||||
0xca, 0x81, 0x43, 0x0e, 0x9c, 0x52, 0x39, 0x91, 0x14, 0xdc, 0xf8, 0x07, 0x72, 0x4b, 0xa5, 0xde,
|
||||
0xc7, 0x7c, 0x49, 0x33, 0xfa, 0x00, 0x8a, 0xaa, 0x54, 0x71, 0x9b, 0xd7, 0xd3, 0xdd, 0xef, 0xab,
|
||||
0x5f, 0x77, 0xff, 0xfa, 0x3d, 0x78, 0xcc, 0xc6, 0xfd, 0x36, 0x36, 0x7b, 0x5a, 0xdf, 0x5e, 0x57,
|
||||
0x4f, 0x5b, 0xda, 0xba, 0x7d, 0x69, 0x60, 0x6b, 0xcd, 0x30, 0x75, 0x5b, 0x47, 0x65, 0xef, 0xe7,
|
||||
0x1a, 0xf9, 0x59, 0xbd, 0xee, 0xe3, 0x6e, 0x99, 0x97, 0x86, 0xad, 0xaf, 0x1b, 0xa6, 0xae, 0x9f,
|
||||
0x31, 0xfe, 0xea, 0xb5, 0xf1, 0xdf, 0x0f, 0xf1, 0x25, 0xd7, 0x16, 0x10, 0xa6, 0xbd, 0xac, 0x1b,
|
||||
0xaa, 0xa9, 0xf6, 0x9c, 0xdf, 0x2b, 0xe7, 0xba, 0x7e, 0xde, 0xc5, 0xeb, 0xb4, 0x75, 0x3a, 0x38,
|
||||
0x5b, 0xb7, 0xb5, 0x1e, 0xb6, 0x6c, 0xb5, 0x67, 0x70, 0x86, 0xa5, 0x73, 0xfd, 0x5c, 0xa7, 0x9f,
|
||||
0xeb, 0xe4, 0x8b, 0x51, 0xa5, 0xbf, 0xe4, 0x20, 0x23, 0xe3, 0x77, 0x07, 0xd8, 0xb2, 0xd1, 0x06,
|
||||
0x24, 0x71, 0xab, 0xa3, 0x57, 0x84, 0x55, 0xe1, 0x46, 0x7e, 0xe3, 0xda, 0xda, 0xc8, 0xf0, 0xd7,
|
||||
0x38, 0x5f, 0xbd, 0xd5, 0xd1, 0x1b, 0x31, 0x99, 0xf2, 0xa2, 0xdb, 0x90, 0x3a, 0xeb, 0x0e, 0xac,
|
||||
0x4e, 0x25, 0x4e, 0x85, 0xae, 0x47, 0x09, 0xdd, 0x23, 0x4c, 0x8d, 0x98, 0xcc, 0xb8, 0x49, 0x57,
|
||||
0x5a, 0xff, 0x4c, 0xaf, 0x24, 0x26, 0x77, 0xb5, 0xd3, 0x3f, 0xa3, 0x5d, 0x11, 0x5e, 0xb4, 0x05,
|
||||
0xa0, 0xf5, 0x35, 0x5b, 0x69, 0x75, 0x54, 0xad, 0x5f, 0x49, 0x52, 0xc9, 0xc7, 0xa3, 0x25, 0x35,
|
||||
0xbb, 0x46, 0x18, 0x1b, 0x31, 0x39, 0xa7, 0x39, 0x0d, 0x32, 0xdc, 0x77, 0x07, 0xd8, 0xbc, 0xac,
|
||||
0xa4, 0x26, 0x0f, 0xf7, 0x75, 0xc2, 0x44, 0x86, 0x4b, 0xb9, 0xd1, 0x2b, 0x90, 0x6d, 0x75, 0x70,
|
||||
0xeb, 0xa1, 0x62, 0x0f, 0x2b, 0x19, 0x2a, 0xb9, 0x12, 0x25, 0x59, 0x23, 0x7c, 0xcd, 0x61, 0x23,
|
||||
0x26, 0x67, 0x5a, 0xec, 0x13, 0xdd, 0x81, 0x74, 0x4b, 0xef, 0xf5, 0x34, 0xbb, 0x02, 0x54, 0x76,
|
||||
0x39, 0x52, 0x96, 0x72, 0x35, 0x62, 0x32, 0xe7, 0x47, 0xfb, 0x50, 0xea, 0x6a, 0x96, 0xad, 0x58,
|
||||
0x7d, 0xd5, 0xb0, 0x3a, 0xba, 0x6d, 0x55, 0xf2, 0x54, 0xc3, 0x93, 0x51, 0x1a, 0xf6, 0x34, 0xcb,
|
||||
0x3e, 0x72, 0x98, 0x1b, 0x31, 0xb9, 0xd8, 0xf5, 0x13, 0x88, 0x3e, 0xfd, 0xec, 0x0c, 0x9b, 0xae,
|
||||
0xc2, 0x4a, 0x61, 0xb2, 0xbe, 0x03, 0xc2, 0xed, 0xc8, 0x13, 0x7d, 0xba, 0x9f, 0x80, 0x7e, 0x02,
|
||||
0x8b, 0x5d, 0x5d, 0x6d, 0xbb, 0xea, 0x94, 0x56, 0x67, 0xd0, 0x7f, 0x58, 0x29, 0x52, 0xa5, 0xcf,
|
||||
0x44, 0x0e, 0x52, 0x57, 0xdb, 0x8e, 0x8a, 0x1a, 0x11, 0x68, 0xc4, 0xe4, 0x85, 0xee, 0x28, 0x11,
|
||||
0xbd, 0x05, 0x4b, 0xaa, 0x61, 0x74, 0x2f, 0x47, 0xb5, 0x97, 0xa8, 0xf6, 0x9b, 0x51, 0xda, 0x37,
|
||||
0x89, 0xcc, 0xa8, 0x7a, 0xa4, 0x8e, 0x51, 0x51, 0x13, 0x44, 0xc3, 0xc4, 0x86, 0x6a, 0x62, 0xc5,
|
||||
0x30, 0x75, 0x43, 0xb7, 0xd4, 0x6e, 0xa5, 0x4c, 0x75, 0x3f, 0x1d, 0xa5, 0xfb, 0x90, 0xf1, 0x1f,
|
||||
0x72, 0xf6, 0x46, 0x4c, 0x2e, 0x1b, 0x41, 0x12, 0xd3, 0xaa, 0xb7, 0xb0, 0x65, 0x79, 0x5a, 0xc5,
|
||||
0x69, 0x5a, 0x29, 0x7f, 0x50, 0x6b, 0x80, 0x84, 0xea, 0x90, 0xc7, 0x43, 0x22, 0xae, 0x5c, 0xe8,
|
||||
0x36, 0xae, 0x2c, 0x50, 0x85, 0x52, 0xe4, 0x09, 0xa5, 0xac, 0x27, 0xba, 0x8d, 0x1b, 0x31, 0x19,
|
||||
0xb0, 0xdb, 0x42, 0x2a, 0x3c, 0x72, 0x81, 0x4d, 0xed, 0xec, 0x92, 0xaa, 0x51, 0xe8, 0x1f, 0x4b,
|
||||
0xd3, 0xfb, 0x15, 0x44, 0x15, 0x3e, 0x1b, 0xa5, 0xf0, 0x84, 0x0a, 0x11, 0x15, 0x75, 0x47, 0xa4,
|
||||
0x11, 0x93, 0x17, 0x2f, 0xc6, 0xc9, 0xc4, 0xc4, 0xce, 0xb4, 0xbe, 0xda, 0xd5, 0xde, 0xc7, 0xca,
|
||||
0x69, 0x57, 0x6f, 0x3d, 0xac, 0x2c, 0x4e, 0x36, 0xb1, 0x7b, 0x9c, 0x7b, 0x8b, 0x30, 0x13, 0x13,
|
||||
0x3b, 0xf3, 0x13, 0xb6, 0x32, 0x90, 0xba, 0x50, 0xbb, 0x03, 0xbc, 0x9b, 0xcc, 0xa6, 0xc5, 0xcc,
|
||||
0x6e, 0x32, 0x9b, 0x15, 0x73, 0xbb, 0xc9, 0x6c, 0x4e, 0x04, 0xe9, 0x69, 0xc8, 0xfb, 0x5c, 0x12,
|
||||
0xaa, 0x40, 0xa6, 0x87, 0x2d, 0x4b, 0x3d, 0xc7, 0xd4, 0x83, 0xe5, 0x64, 0xa7, 0x29, 0x95, 0xa0,
|
||||
0xe0, 0x77, 0x43, 0xd2, 0x47, 0x82, 0x2b, 0x49, 0x3c, 0x0c, 0x91, 0xbc, 0xc0, 0x26, 0x5d, 0x08,
|
||||
0x2e, 0xc9, 0x9b, 0xe8, 0x09, 0x28, 0xd2, 0x49, 0x28, 0xce, 0x7f, 0xe2, 0xe6, 0x92, 0x72, 0x81,
|
||||
0x12, 0x4f, 0x38, 0xd3, 0x0a, 0xe4, 0x8d, 0x0d, 0xc3, 0x65, 0x49, 0x50, 0x16, 0x30, 0x36, 0x0c,
|
||||
0x87, 0xe1, 0x71, 0x28, 0x90, 0x19, 0xbb, 0x1c, 0x49, 0xda, 0x49, 0x9e, 0xd0, 0x38, 0x8b, 0xf4,
|
||||
0xf7, 0x38, 0x88, 0xa3, 0xae, 0x0b, 0xdd, 0x81, 0x24, 0xf1, 0xe2, 0xdc, 0x21, 0x57, 0xd7, 0x98,
|
||||
0x8b, 0x5f, 0x73, 0x5c, 0xfc, 0x5a, 0xd3, 0x71, 0xf1, 0x5b, 0xd9, 0x4f, 0x3e, 0x5b, 0x89, 0x7d,
|
||||
0xf4, 0xaf, 0x15, 0x41, 0xa6, 0x12, 0xe8, 0x2a, 0x71, 0x58, 0xaa, 0xd6, 0x57, 0xb4, 0x36, 0x1d,
|
||||
0x72, 0x8e, 0x78, 0x23, 0x55, 0xeb, 0xef, 0xb4, 0xd1, 0x1e, 0x88, 0x2d, 0xbd, 0x6f, 0xe1, 0xbe,
|
||||
0x35, 0xb0, 0x14, 0x16, 0x42, 0xb8, 0x1b, 0x0e, 0x38, 0x53, 0x16, 0xc8, 0x6a, 0x0e, 0xe7, 0x21,
|
||||
0x65, 0x94, 0xcb, 0xad, 0x20, 0x01, 0xdd, 0x03, 0xb8, 0x50, 0xbb, 0x5a, 0x5b, 0xb5, 0x75, 0xd3,
|
||||
0xaa, 0x24, 0x57, 0x13, 0x37, 0xf2, 0x1b, 0xab, 0x63, 0x5b, 0x7d, 0xe2, 0xb0, 0x1c, 0x1b, 0x6d,
|
||||
0xd5, 0xc6, 0x5b, 0x49, 0x32, 0x5c, 0xd9, 0x27, 0x89, 0x9e, 0x82, 0xb2, 0x6a, 0x18, 0x8a, 0x65,
|
||||
0xab, 0x36, 0x56, 0x4e, 0x2f, 0x6d, 0x6c, 0x51, 0x17, 0x5d, 0x90, 0x8b, 0xaa, 0x61, 0x1c, 0x11,
|
||||
0xea, 0x16, 0x21, 0xa2, 0x27, 0xa1, 0x44, 0xbc, 0xb9, 0xa6, 0x76, 0x95, 0x0e, 0xd6, 0xce, 0x3b,
|
||||
0x76, 0x25, 0xbd, 0x2a, 0xdc, 0x48, 0xc8, 0x45, 0x4e, 0x6d, 0x50, 0xa2, 0xd4, 0x76, 0x77, 0x9c,
|
||||
0x7a, 0x72, 0x84, 0x20, 0xd9, 0x56, 0x6d, 0x95, 0xae, 0x64, 0x41, 0xa6, 0xdf, 0x84, 0x66, 0xa8,
|
||||
0x76, 0x87, 0xaf, 0x0f, 0xfd, 0x46, 0x57, 0x20, 0xcd, 0xd5, 0x26, 0xa8, 0x5a, 0xde, 0x42, 0x4b,
|
||||
0x90, 0x32, 0x4c, 0xfd, 0x02, 0xd3, 0xad, 0xcb, 0xca, 0xac, 0x21, 0xc9, 0x50, 0x0a, 0x7a, 0x7d,
|
||||
0x54, 0x82, 0xb8, 0x3d, 0xe4, 0xbd, 0xc4, 0xed, 0x21, 0x7a, 0x01, 0x92, 0x64, 0x21, 0x69, 0x1f,
|
||||
0xa5, 0x90, 0x38, 0xc7, 0xe5, 0x9a, 0x97, 0x06, 0x96, 0x29, 0xa7, 0x54, 0x86, 0x62, 0x20, 0x1a,
|
||||
0x48, 0x57, 0x60, 0x29, 0xcc, 0xb9, 0x4b, 0x1d, 0x97, 0x1e, 0x70, 0xd2, 0xe8, 0x36, 0x64, 0x5d,
|
||||
0xef, 0xce, 0x0c, 0xe7, 0xea, 0x58, 0xb7, 0x0e, 0xb3, 0xec, 0xb2, 0x12, 0x8b, 0x21, 0x1b, 0xd0,
|
||||
0x51, 0x79, 0x2c, 0x2f, 0xc8, 0x19, 0xd5, 0x30, 0x1a, 0xaa, 0xd5, 0x91, 0xde, 0x86, 0x4a, 0x94,
|
||||
0xe7, 0xf6, 0x2d, 0x98, 0x40, 0xcd, 0xde, 0x59, 0xb0, 0x2b, 0x90, 0x3e, 0xd3, 0xcd, 0x9e, 0x6a,
|
||||
0x53, 0x65, 0x45, 0x99, 0xb7, 0xc8, 0x42, 0x32, 0x2f, 0x9e, 0xa0, 0x64, 0xd6, 0x90, 0x14, 0xb8,
|
||||
0x1a, 0xe9, 0xbd, 0x89, 0x88, 0xd6, 0x6f, 0x63, 0xb6, 0xac, 0x45, 0x99, 0x35, 0x3c, 0x45, 0x6c,
|
||||
0xb0, 0xac, 0x41, 0xba, 0xb5, 0xe8, 0x5c, 0xa9, 0xfe, 0x9c, 0xcc, 0x5b, 0xd2, 0x9f, 0x12, 0x70,
|
||||
0x25, 0xdc, 0x87, 0xa3, 0x55, 0x28, 0xf4, 0xd4, 0xa1, 0x62, 0x0f, 0xb9, 0xd9, 0x09, 0x74, 0xe3,
|
||||
0xa1, 0xa7, 0x0e, 0x9b, 0x43, 0x66, 0x73, 0x22, 0x24, 0xec, 0xa1, 0x55, 0x89, 0xaf, 0x26, 0x6e,
|
||||
0x14, 0x64, 0xf2, 0x89, 0x8e, 0x61, 0xa1, 0xab, 0xb7, 0xd4, 0xae, 0xd2, 0x55, 0x2d, 0x5b, 0xe1,
|
||||
0xc1, 0x9d, 0x1d, 0xa2, 0x27, 0xc6, 0x16, 0x9b, 0x79, 0x63, 0xdc, 0x66, 0xfb, 0x49, 0x1c, 0x0e,
|
||||
0xb7, 0xff, 0x32, 0xd5, 0xb1, 0xa7, 0x3a, 0x5b, 0x8d, 0x8e, 0x61, 0xe9, 0xf4, 0xf2, 0x7d, 0xb5,
|
||||
0x6f, 0x6b, 0x7d, 0xac, 0x8c, 0x1d, 0xab, 0x71, 0xeb, 0x79, 0xa0, 0x59, 0xa7, 0xb8, 0xa3, 0x5e,
|
||||
0x68, 0xba, 0xc9, 0x55, 0x2e, 0xba, 0xf2, 0x27, 0xde, 0xd9, 0xf2, 0xf6, 0x28, 0x15, 0x30, 0x6a,
|
||||
0xc7, 0xbd, 0xa4, 0xe7, 0x76, 0x2f, 0x2f, 0xc0, 0x52, 0x1f, 0x0f, 0x6d, 0xdf, 0x18, 0x99, 0xe1,
|
||||
0x64, 0xe8, 0x5e, 0x20, 0xf2, 0xcf, 0xeb, 0x9f, 0xd8, 0x10, 0x7a, 0x86, 0x86, 0x45, 0x43, 0xb7,
|
||||
0xb0, 0xa9, 0xa8, 0xed, 0xb6, 0x89, 0x2d, 0xab, 0x92, 0xa5, 0xdc, 0x65, 0x87, 0xbe, 0xc9, 0xc8,
|
||||
0xd2, 0x6f, 0xfd, 0x7b, 0x15, 0x0c, 0x83, 0x7c, 0x27, 0x04, 0x6f, 0x27, 0x8e, 0x60, 0x89, 0xcb,
|
||||
0xb7, 0x03, 0x9b, 0xc1, 0xd2, 0xd1, 0xc7, 0xc6, 0x0f, 0xdc, 0xe8, 0x26, 0x20, 0x47, 0x7c, 0x86,
|
||||
0x7d, 0x48, 0x7c, 0xbd, 0x7d, 0x40, 0x90, 0xa4, 0xab, 0x94, 0x64, 0x4e, 0x88, 0x7c, 0xff, 0xbf,
|
||||
0xed, 0xcd, 0x6b, 0xb0, 0x30, 0x96, 0x63, 0xb8, 0xf3, 0x12, 0x42, 0xe7, 0x15, 0xf7, 0xcf, 0x4b,
|
||||
0xfa, 0x9d, 0x00, 0xd5, 0xe8, 0xa4, 0x22, 0x54, 0xd5, 0xb3, 0xb0, 0xe0, 0xce, 0xc5, 0x1d, 0x1f,
|
||||
0x3b, 0xf5, 0xa2, 0xfb, 0x83, 0x0f, 0x30, 0xd2, 0x81, 0x3f, 0x09, 0xa5, 0x91, 0x94, 0x87, 0xed,
|
||||
0x42, 0xf1, 0xc2, 0xdf, 0xbf, 0xf4, 0xab, 0x84, 0xeb, 0x55, 0x03, 0x79, 0x49, 0x88, 0xe5, 0xbd,
|
||||
0x0e, 0x8b, 0x6d, 0xdc, 0xd2, 0xda, 0x5f, 0xd5, 0xf0, 0x16, 0xb8, 0xf4, 0x77, 0x76, 0x37, 0x83,
|
||||
0xdd, 0xfd, 0x12, 0x20, 0x2b, 0x63, 0xcb, 0x20, 0xd9, 0x07, 0xda, 0x82, 0x1c, 0x1e, 0xb6, 0xb0,
|
||||
0x61, 0x3b, 0x09, 0x5b, 0x78, 0x2a, 0xcc, 0xb8, 0xeb, 0x0e, 0x27, 0x01, 0x82, 0xae, 0x18, 0xba,
|
||||
0xc5, 0xb1, 0x6e, 0x34, 0x6c, 0xe5, 0xe2, 0x7e, 0xb0, 0xfb, 0xa2, 0x03, 0x76, 0x13, 0x91, 0x38,
|
||||
0x8e, 0x49, 0x8d, 0xa0, 0xdd, 0x5b, 0x1c, 0xed, 0x26, 0xa7, 0x74, 0x16, 0x80, 0xbb, 0xb5, 0x00,
|
||||
0xdc, 0x4d, 0x4d, 0x99, 0x66, 0x04, 0xde, 0x7d, 0xd1, 0xc1, 0xbb, 0xe9, 0x29, 0x23, 0x1e, 0x01,
|
||||
0xbc, 0xaf, 0xfa, 0x00, 0x6f, 0x96, 0x8a, 0xae, 0x46, 0x8a, 0x86, 0x20, 0xde, 0x97, 0x5d, 0xc4,
|
||||
0x9b, 0x8f, 0x44, 0xcb, 0x5c, 0x78, 0x14, 0xf2, 0x1e, 0x8c, 0x41, 0x5e, 0x06, 0x51, 0x9f, 0x8a,
|
||||
0x54, 0x31, 0x05, 0xf3, 0x1e, 0x8c, 0x61, 0xde, 0xe2, 0x14, 0x85, 0x53, 0x40, 0xef, 0x4f, 0xc3,
|
||||
0x41, 0x6f, 0x34, 0x2c, 0xe5, 0xc3, 0x9c, 0x0d, 0xf5, 0x2a, 0x11, 0xa8, 0xb7, 0x1c, 0x89, 0xd0,
|
||||
0x98, 0xfa, 0x99, 0x61, 0xef, 0x71, 0x08, 0xec, 0x65, 0x00, 0xf5, 0x46, 0xa4, 0xf2, 0x19, 0x70,
|
||||
0xef, 0x71, 0x08, 0xee, 0x5d, 0x98, 0xaa, 0x76, 0x2a, 0xf0, 0xbd, 0x17, 0x04, 0xbe, 0x28, 0x22,
|
||||
0xc7, 0xf2, 0x4e, 0x7b, 0x04, 0xf2, 0x3d, 0x8d, 0x42, 0xbe, 0x0c, 0x9d, 0x3e, 0x17, 0xa9, 0x71,
|
||||
0x0e, 0xe8, 0x7b, 0x30, 0x06, 0x7d, 0x97, 0xa6, 0x58, 0xda, 0xec, 0xd8, 0x37, 0x23, 0x66, 0x19,
|
||||
0xea, 0xdd, 0x4d, 0x66, 0x41, 0xcc, 0x4b, 0xcf, 0x90, 0x40, 0x3c, 0xe2, 0xe1, 0x48, 0x4e, 0x8c,
|
||||
0x4d, 0x53, 0x37, 0x39, 0x8a, 0x65, 0x0d, 0xe9, 0x06, 0xc1, 0x42, 0x9e, 0x37, 0x9b, 0x80, 0x93,
|
||||
0x29, 0xf6, 0xf0, 0x79, 0x30, 0xe9, 0xcf, 0x82, 0x27, 0x4b, 0x91, 0xb2, 0x1f, 0x47, 0xe5, 0x38,
|
||||
0x8e, 0xf2, 0xa1, 0xe7, 0x78, 0x10, 0x3d, 0xaf, 0x40, 0x9e, 0x60, 0x8a, 0x11, 0x60, 0xac, 0x1a,
|
||||
0x2e, 0x30, 0xbe, 0x09, 0x0b, 0x34, 0x76, 0x32, 0x8c, 0xcd, 0x03, 0x52, 0x92, 0x06, 0xa4, 0x32,
|
||||
0xf9, 0xc1, 0xd6, 0x85, 0x45, 0xa6, 0xe7, 0x61, 0xd1, 0xc7, 0xeb, 0x62, 0x15, 0x86, 0x12, 0x45,
|
||||
0x97, 0x7b, 0x93, 0x83, 0x96, 0xbf, 0x09, 0xde, 0x0a, 0x79, 0x88, 0x3a, 0x0c, 0xfc, 0x0a, 0xdf,
|
||||
0x10, 0xf8, 0x8d, 0x7f, 0x65, 0xf0, 0xeb, 0xc7, 0x5e, 0x89, 0x20, 0xf6, 0xfa, 0x8f, 0xe0, 0xed,
|
||||
0x89, 0x0b, 0x65, 0x5b, 0x7a, 0x1b, 0x73, 0x34, 0x44, 0xbf, 0x49, 0x76, 0xd2, 0xd5, 0xcf, 0x39,
|
||||
0xe6, 0x21, 0x9f, 0x84, 0xcb, 0x0d, 0x39, 0x39, 0x1e, 0x51, 0x5c, 0x20, 0xc5, 0x42, 0x3e, 0x07,
|
||||
0x52, 0x22, 0x24, 0x1e, 0x62, 0x16, 0x20, 0x0a, 0x32, 0xf9, 0x24, 0x7c, 0xd4, 0xec, 0x78, 0xe8,
|
||||
0x66, 0x0d, 0x74, 0x07, 0x72, 0xb4, 0x58, 0xad, 0xe8, 0x86, 0xc5, 0x63, 0x42, 0x20, 0xcb, 0x61,
|
||||
0x15, 0xeb, 0xb5, 0x43, 0xc2, 0x73, 0x60, 0x58, 0x72, 0xd6, 0xe0, 0x5f, 0xbe, 0x5c, 0x23, 0x17,
|
||||
0xc8, 0x35, 0xae, 0x41, 0x8e, 0x8c, 0xde, 0x32, 0xd4, 0x16, 0xa6, 0xa5, 0xd1, 0x9c, 0xec, 0x11,
|
||||
0xa4, 0x4f, 0x04, 0x28, 0x8f, 0x84, 0x98, 0xd0, 0xb9, 0x3b, 0x26, 0x19, 0xf7, 0x41, 0xfb, 0xeb,
|
||||
0x00, 0xe7, 0xaa, 0xa5, 0xbc, 0xa7, 0xf6, 0x6d, 0xdc, 0xe6, 0xd3, 0xcd, 0x9d, 0xab, 0xd6, 0x1b,
|
||||
0x94, 0x10, 0xec, 0x38, 0x3b, 0xd2, 0xb1, 0x0f, 0x43, 0xe6, 0xfc, 0x18, 0x12, 0x55, 0x21, 0x6b,
|
||||
0x98, 0x9a, 0x6e, 0x6a, 0xf6, 0x25, 0x1d, 0x6d, 0x42, 0x76, 0xdb, 0xbb, 0xc9, 0x6c, 0x42, 0x4c,
|
||||
0xee, 0x26, 0xb3, 0x49, 0x31, 0xe5, 0x16, 0xaa, 0xd8, 0x91, 0xcd, 0x8b, 0x05, 0xe9, 0x83, 0xb8,
|
||||
0x67, 0x8b, 0xdb, 0xb8, 0xab, 0x5d, 0x60, 0x73, 0x8e, 0xc9, 0xcc, 0xb6, 0xb9, 0xcb, 0x21, 0x53,
|
||||
0xf6, 0x51, 0xc8, 0xe8, 0x49, 0x6b, 0x60, 0xe1, 0x36, 0x2f, 0x99, 0xb8, 0x6d, 0xd4, 0x80, 0x34,
|
||||
0xbe, 0xc0, 0x7d, 0xdb, 0xaa, 0x64, 0xa8, 0x0d, 0x5f, 0x19, 0xc7, 0xb0, 0xe4, 0xf7, 0x56, 0x85,
|
||||
0x58, 0xee, 0x97, 0x9f, 0xad, 0x88, 0x8c, 0xfb, 0x39, 0xbd, 0xa7, 0xd9, 0xb8, 0x67, 0xd8, 0x97,
|
||||
0x32, 0x97, 0x9f, 0xbc, 0xb2, 0xd2, 0x6d, 0x28, 0x05, 0xe3, 0x3e, 0x7a, 0x02, 0x8a, 0x26, 0xb6,
|
||||
0x55, 0xad, 0xaf, 0x04, 0xb2, 0xf6, 0x02, 0x23, 0xf2, 0x62, 0xce, 0x21, 0x3c, 0x12, 0x1a, 0xeb,
|
||||
0xd1, 0x4b, 0x90, 0xf3, 0xd2, 0x04, 0x81, 0x0e, 0x7d, 0x42, 0xad, 0xc3, 0xe3, 0x95, 0xfe, 0x2a,
|
||||
0x78, 0x2a, 0x83, 0xd5, 0x93, 0x3a, 0xa4, 0x4d, 0x6c, 0x0d, 0xba, 0xac, 0x9e, 0x51, 0xda, 0x78,
|
||||
0x7e, 0xb6, 0x2c, 0x81, 0x50, 0x07, 0x5d, 0x5b, 0xe6, 0xc2, 0xd2, 0x5b, 0x90, 0x66, 0x14, 0x94,
|
||||
0x87, 0xcc, 0xf1, 0xfe, 0xfd, 0xfd, 0x83, 0x37, 0xf6, 0xc5, 0x18, 0x02, 0x48, 0x6f, 0xd6, 0x6a,
|
||||
0xf5, 0xc3, 0xa6, 0x28, 0xa0, 0x1c, 0xa4, 0x36, 0xb7, 0x0e, 0xe4, 0xa6, 0x18, 0x27, 0x64, 0xb9,
|
||||
0xbe, 0x5b, 0xaf, 0x35, 0xc5, 0x04, 0x5a, 0x80, 0x22, 0xfb, 0x56, 0xee, 0x1d, 0xc8, 0x0f, 0x36,
|
||||
0x9b, 0x62, 0xd2, 0x47, 0x3a, 0xaa, 0xef, 0x6f, 0xd7, 0x65, 0x31, 0x25, 0x7d, 0x0f, 0xae, 0x46,
|
||||
0xe6, 0x15, 0x5e, 0x69, 0x44, 0xf0, 0x95, 0x46, 0xa4, 0xdf, 0xc4, 0x09, 0xf2, 0x8a, 0x4a, 0x16,
|
||||
0xd0, 0xee, 0xc8, 0xc4, 0x37, 0xe6, 0xc8, 0x34, 0x46, 0x66, 0x4f, 0xc0, 0x96, 0x89, 0xcf, 0xb0,
|
||||
0xdd, 0xea, 0xb0, 0xe4, 0x85, 0xf9, 0xc6, 0xa2, 0x5c, 0xe4, 0x54, 0x2a, 0x64, 0x31, 0xb6, 0x77,
|
||||
0x70, 0xcb, 0x56, 0xd8, 0x09, 0x63, 0x40, 0x27, 0x47, 0xd8, 0x08, 0xf5, 0x88, 0x11, 0xa5, 0xb7,
|
||||
0xe7, 0x5a, 0xcb, 0x1c, 0xa4, 0xe4, 0x7a, 0x53, 0x7e, 0x53, 0x4c, 0x20, 0x04, 0x25, 0xfa, 0xa9,
|
||||
0x1c, 0xed, 0x6f, 0x1e, 0x1e, 0x35, 0x0e, 0xc8, 0x5a, 0x2e, 0x42, 0xd9, 0x59, 0x4b, 0x87, 0x98,
|
||||
0x92, 0xfe, 0x11, 0x87, 0x47, 0x23, 0x52, 0x1d, 0x74, 0x07, 0xc0, 0x1e, 0x2a, 0x26, 0x6e, 0xe9,
|
||||
0x66, 0x3b, 0xda, 0xc8, 0x9a, 0x43, 0x99, 0x72, 0xc8, 0x39, 0x9b, 0x7f, 0x59, 0x13, 0x2a, 0x6a,
|
||||
0xe8, 0x15, 0xae, 0x94, 0xcc, 0xca, 0x81, 0x77, 0xd7, 0x43, 0x0a, 0x47, 0xb8, 0x45, 0x14, 0xd3,
|
||||
0xb5, 0xa5, 0x8a, 0x29, 0x3f, 0x7a, 0xe0, 0x07, 0xc4, 0x03, 0x1a, 0x54, 0x66, 0x2e, 0xbd, 0xfa,
|
||||
0x20, 0x33, 0x23, 0x58, 0xe8, 0x4d, 0x78, 0x74, 0x24, 0x26, 0xba, 0x4a, 0x53, 0xb3, 0x86, 0xc6,
|
||||
0x47, 0x82, 0xa1, 0x91, 0xab, 0x96, 0x7e, 0x9f, 0xf0, 0x2f, 0x6c, 0x30, 0xb3, 0x3b, 0x80, 0xb4,
|
||||
0x65, 0xab, 0xf6, 0xc0, 0xe2, 0x06, 0xf7, 0xd2, 0xac, 0x69, 0xe2, 0x9a, 0xf3, 0x71, 0x44, 0xc5,
|
||||
0x65, 0xae, 0xe6, 0xbb, 0xf5, 0xb6, 0x88, 0x83, 0x0d, 0x2e, 0x4e, 0xf4, 0x91, 0xf1, 0x7c, 0x4e,
|
||||
0x5c, 0xba, 0x0b, 0x68, 0x3c, 0x81, 0x0e, 0x29, 0x99, 0x08, 0x61, 0x25, 0x93, 0x3f, 0x08, 0xf0,
|
||||
0xd8, 0x84, 0x64, 0x19, 0xbd, 0x3e, 0xb2, 0xcf, 0x2f, 0xcf, 0x93, 0x6a, 0xaf, 0x31, 0x5a, 0x70,
|
||||
0xa7, 0xa5, 0x5b, 0x50, 0xf0, 0xd3, 0x67, 0x9b, 0xe4, 0x97, 0x71, 0xcf, 0xe7, 0x07, 0x6b, 0x3b,
|
||||
0x5e, 0xf8, 0x13, 0xbe, 0x66, 0xf8, 0x0b, 0xda, 0x59, 0x7c, 0x4e, 0x3b, 0x3b, 0x0a, 0xb3, 0xb3,
|
||||
0xc4, 0x5c, 0x59, 0xe5, 0x5c, 0xd6, 0x96, 0xfc, 0x7a, 0xd6, 0x16, 0x38, 0x70, 0xa9, 0x60, 0xda,
|
||||
0xfa, 0x26, 0x80, 0x57, 0xf0, 0x22, 0x01, 0xc9, 0xd4, 0x07, 0xfd, 0x36, 0xb5, 0x80, 0x94, 0xcc,
|
||||
0x1a, 0xe8, 0x36, 0xa4, 0x88, 0x25, 0x39, 0xeb, 0x34, 0xee, 0x54, 0x89, 0x25, 0xf8, 0x0a, 0x66,
|
||||
0x8c, 0x5b, 0xd2, 0x00, 0x8d, 0x57, 0xd4, 0x23, 0xba, 0x78, 0x35, 0xd8, 0xc5, 0xe3, 0x91, 0xb5,
|
||||
0xf9, 0xf0, 0xae, 0xde, 0x87, 0x14, 0xdd, 0x79, 0x92, 0x70, 0xd1, 0x6b, 0x1c, 0x0e, 0x7b, 0xc8,
|
||||
0x37, 0xfa, 0x19, 0x80, 0x6a, 0xdb, 0xa6, 0x76, 0x3a, 0xf0, 0x3a, 0x58, 0x09, 0xb7, 0x9c, 0x4d,
|
||||
0x87, 0x6f, 0xeb, 0x1a, 0x37, 0xa1, 0x25, 0x4f, 0xd4, 0x67, 0x46, 0x3e, 0x85, 0xd2, 0x3e, 0x94,
|
||||
0x82, 0xb2, 0x4e, 0xa2, 0xce, 0xc6, 0x10, 0x4c, 0xd4, 0x19, 0xee, 0xe2, 0x89, 0xba, 0x9b, 0xe6,
|
||||
0x27, 0xd8, 0x5d, 0x15, 0x6d, 0x48, 0xff, 0x15, 0xa0, 0xe0, 0x37, 0xbc, 0x6f, 0x38, 0xfd, 0x9c,
|
||||
0x92, 0x71, 0x5f, 0x1d, 0xcb, 0x3e, 0x33, 0xe7, 0xaa, 0x75, 0xfc, 0x6d, 0x26, 0x9f, 0x1f, 0x08,
|
||||
0x90, 0x75, 0x27, 0x1f, 0xbc, 0xb6, 0x0a, 0xdc, 0xf3, 0xb1, 0xb5, 0x8b, 0xfb, 0xef, 0x9a, 0xd8,
|
||||
0xad, 0x5e, 0xc2, 0xbd, 0xd5, 0xbb, 0xeb, 0xe6, 0x4a, 0x51, 0x15, 0x3d, 0xff, 0x4a, 0x73, 0x9b,
|
||||
0x72, 0x52, 0xc3, 0x5f, 0xf3, 0x71, 0x90, 0x24, 0x01, 0xfd, 0x00, 0xd2, 0x6a, 0xcb, 0xad, 0x63,
|
||||
0x96, 0x42, 0x0a, 0x7c, 0x0e, 0xeb, 0x5a, 0x73, 0xb8, 0x49, 0x39, 0x65, 0x2e, 0xc1, 0x47, 0x15,
|
||||
0x77, 0x46, 0x25, 0xbd, 0x46, 0xf4, 0x32, 0x9e, 0xa0, 0x47, 0x2c, 0x01, 0x1c, 0xef, 0x3f, 0x38,
|
||||
0xd8, 0xde, 0xb9, 0xb7, 0x53, 0xdf, 0xe6, 0xd9, 0xd2, 0xf6, 0x76, 0x7d, 0x5b, 0x8c, 0x13, 0x3e,
|
||||
0xb9, 0xfe, 0xe0, 0xe0, 0xa4, 0xbe, 0x2d, 0x26, 0xa4, 0xbb, 0x90, 0x73, 0xbd, 0x0a, 0x41, 0xf5,
|
||||
0x4e, 0x4d, 0x56, 0xe0, 0x67, 0x9b, 0x97, 0xd8, 0x97, 0x20, 0x65, 0xe8, 0xef, 0xf1, 0x2b, 0xb6,
|
||||
0x84, 0xcc, 0x1a, 0x52, 0x1b, 0xca, 0x23, 0x2e, 0x09, 0xdd, 0x85, 0x8c, 0x31, 0x38, 0x55, 0x1c,
|
||||
0xa3, 0x1d, 0xa9, 0x60, 0x3b, 0x78, 0x71, 0x70, 0xda, 0xd5, 0x5a, 0xf7, 0xf1, 0xa5, 0xb3, 0x4c,
|
||||
0xc6, 0xe0, 0xf4, 0x3e, 0xb3, 0x6d, 0xd6, 0x4b, 0xdc, 0xdf, 0xcb, 0x05, 0x64, 0x9d, 0xa3, 0x8a,
|
||||
0x7e, 0x08, 0x39, 0xd7, 0xdb, 0xb9, 0x57, 0xe4, 0x91, 0x6e, 0x92, 0xab, 0xf7, 0x44, 0xd0, 0x4d,
|
||||
0x58, 0xb0, 0xb4, 0xf3, 0xbe, 0x53, 0xbf, 0x67, 0x15, 0x9b, 0x38, 0x3d, 0x33, 0x65, 0xf6, 0x63,
|
||||
0xcf, 0x29, 0x2a, 0x90, 0x20, 0x27, 0x8e, 0xfa, 0x8a, 0x6f, 0x73, 0x00, 0x21, 0xc1, 0x38, 0x11,
|
||||
0x16, 0x8c, 0x7f, 0x11, 0x87, 0xbc, 0xef, 0x56, 0x00, 0x7d, 0xdf, 0xe7, 0xb8, 0x4a, 0x21, 0x51,
|
||||
0xc4, 0xc7, 0xeb, 0xdd, 0x41, 0x07, 0x27, 0x16, 0x9f, 0x7f, 0x62, 0x51, 0x97, 0x30, 0xce, 0xe5,
|
||||
0x42, 0x72, 0xee, 0xcb, 0x85, 0xe7, 0x00, 0xd9, 0xba, 0xad, 0x76, 0x95, 0x0b, 0xdd, 0xd6, 0xfa,
|
||||
0xe7, 0x0a, 0x33, 0x0d, 0xe6, 0x66, 0x44, 0xfa, 0xe7, 0x84, 0xfe, 0x38, 0xa4, 0x56, 0xf2, 0x73,
|
||||
0x01, 0xb2, 0x2e, 0xa2, 0x9b, 0xf7, 0x86, 0xfa, 0x0a, 0xa4, 0x39, 0x68, 0x61, 0x57, 0xd4, 0xbc,
|
||||
0x15, 0x7a, 0x8b, 0x52, 0x85, 0x6c, 0x0f, 0xdb, 0x2a, 0xf5, 0x99, 0x2c, 0x02, 0xba, 0xed, 0x9b,
|
||||
0x2f, 0x43, 0xde, 0x77, 0xbb, 0x4f, 0xdc, 0xe8, 0x7e, 0xfd, 0x0d, 0x31, 0x56, 0xcd, 0x7c, 0xf8,
|
||||
0xf1, 0x6a, 0x62, 0x1f, 0xbf, 0x47, 0x4e, 0x98, 0x5c, 0xaf, 0x35, 0xea, 0xb5, 0xfb, 0xa2, 0x50,
|
||||
0xcd, 0x7f, 0xf8, 0xf1, 0x6a, 0x46, 0xc6, 0xb4, 0x80, 0x7e, 0xf3, 0x3e, 0x94, 0x47, 0x36, 0x26,
|
||||
0x78, 0xa0, 0x11, 0x94, 0xb6, 0x8f, 0x0f, 0xf7, 0x76, 0x6a, 0x9b, 0xcd, 0xba, 0x72, 0x72, 0xd0,
|
||||
0xac, 0x8b, 0x02, 0x7a, 0x14, 0x16, 0xf7, 0x76, 0x7e, 0xd4, 0x68, 0x2a, 0xb5, 0xbd, 0x9d, 0xfa,
|
||||
0x7e, 0x53, 0xd9, 0x6c, 0x36, 0x37, 0x6b, 0xf7, 0xc5, 0xf8, 0xc6, 0x1f, 0xf3, 0x50, 0xde, 0xdc,
|
||||
0xaa, 0xed, 0x10, 0xd8, 0xa6, 0xb5, 0x54, 0xea, 0x1e, 0x6a, 0x90, 0xa4, 0xa5, 0xc0, 0x89, 0x6f,
|
||||
0xfc, 0xaa, 0x93, 0x6f, 0x45, 0xd0, 0x3d, 0x48, 0xd1, 0x2a, 0x21, 0x9a, 0xfc, 0xe8, 0xaf, 0x3a,
|
||||
0xe5, 0x9a, 0x84, 0x0c, 0x86, 0x1e, 0xa7, 0x89, 0xaf, 0x00, 0xab, 0x93, 0x6f, 0x4d, 0xd0, 0x1e,
|
||||
0x64, 0x9c, 0x22, 0xd1, 0xb4, 0xa7, 0x79, 0xd5, 0xa9, 0x57, 0x19, 0x64, 0x6a, 0xac, 0xd8, 0x36,
|
||||
0xf9, 0x81, 0x60, 0x75, 0xca, 0x7d, 0x0a, 0xda, 0x81, 0x34, 0x2f, 0x74, 0x4c, 0x79, 0xf3, 0x57,
|
||||
0x9d, 0x76, 0x43, 0x82, 0x64, 0xc8, 0x79, 0x65, 0xcc, 0xe9, 0xcf, 0x1e, 0xab, 0x33, 0x5c, 0x15,
|
||||
0xa1, 0xb7, 0xa0, 0x18, 0x2c, 0xa8, 0xcc, 0xf6, 0xae, 0xb0, 0x3a, 0xe3, 0x5d, 0x0c, 0xd1, 0x1f,
|
||||
0xac, 0xae, 0xcc, 0xf6, 0xce, 0xb0, 0x3a, 0xe3, 0xd5, 0x0c, 0x7a, 0x07, 0x16, 0xc6, 0xab, 0x1f,
|
||||
0xb3, 0x3f, 0x3b, 0xac, 0xce, 0x71, 0x59, 0x83, 0x7a, 0x80, 0x42, 0xaa, 0x26, 0x73, 0xbc, 0x42,
|
||||
0xac, 0xce, 0x73, 0x77, 0x83, 0xda, 0x50, 0x1e, 0xad, 0x44, 0xcc, 0xfa, 0x2a, 0xb1, 0x3a, 0xf3,
|
||||
0x3d, 0x0e, 0xeb, 0x25, 0x08, 0xcb, 0x67, 0x7d, 0xa5, 0x58, 0x9d, 0xf9, 0x5a, 0x07, 0x1d, 0x03,
|
||||
0xf8, 0x60, 0xe5, 0x0c, 0xaf, 0x16, 0xab, 0xb3, 0x5c, 0xf0, 0x20, 0x03, 0x16, 0xc3, 0xf0, 0xe6,
|
||||
0x3c, 0x8f, 0x18, 0xab, 0x73, 0xdd, 0xfb, 0x10, 0x7b, 0x0e, 0x22, 0xc7, 0xd9, 0x1e, 0x35, 0x56,
|
||||
0x67, 0xbc, 0x00, 0xda, 0xaa, 0x7f, 0xf2, 0xf9, 0xb2, 0xf0, 0xe9, 0xe7, 0xcb, 0xc2, 0xbf, 0x3f,
|
||||
0x5f, 0x16, 0x3e, 0xfa, 0x62, 0x39, 0xf6, 0xe9, 0x17, 0xcb, 0xb1, 0x7f, 0x7e, 0xb1, 0x1c, 0xfb,
|
||||
0xf1, 0xb3, 0xe7, 0x9a, 0xdd, 0x19, 0x9c, 0xae, 0xb5, 0xf4, 0xde, 0xba, 0xff, 0x1d, 0x78, 0xd8,
|
||||
0xeb, 0xf3, 0xd3, 0x34, 0x0d, 0xa8, 0xb7, 0xfe, 0x17, 0x00, 0x00, 0xff, 0xff, 0x4c, 0xb4, 0x7f,
|
||||
0x53, 0x9d, 0x2e, 0x00, 0x00,
|
||||
0x11, 0xd7, 0xe8, 0x5b, 0x2d, 0x4b, 0x1a, 0x3f, 0x9b, 0x45, 0x2b, 0x76, 0x6d, 0x33, 0x14, 0xb0,
|
||||
0x2c, 0x60, 0x13, 0x6f, 0x16, 0x96, 0x2c, 0x84, 0x92, 0x65, 0x6d, 0x64, 0xaf, 0xd7, 0x36, 0x63,
|
||||
0xd9, 0x14, 0xf9, 0x60, 0x18, 0x4b, 0xcf, 0xd6, 0xb0, 0x92, 0x66, 0x98, 0x19, 0x19, 0x99, 0x63,
|
||||
0x12, 0xaa, 0x52, 0x1c, 0x52, 0xdc, 0xc2, 0x21, 0xdc, 0x92, 0xaa, 0xfc, 0x09, 0xc9, 0x25, 0xa7,
|
||||
0x1c, 0x38, 0xe4, 0xc0, 0x29, 0x95, 0x13, 0x49, 0xc1, 0x8d, 0x7f, 0x20, 0xb7, 0x54, 0xea, 0x7d,
|
||||
0xcc, 0x97, 0x34, 0xa3, 0x0f, 0xa0, 0xa8, 0x4a, 0x15, 0xb7, 0x79, 0x3d, 0xdd, 0xfd, 0xbe, 0xfa,
|
||||
0x75, 0xf7, 0xaf, 0xdf, 0x83, 0xc7, 0x6c, 0xdc, 0x6f, 0x63, 0xb3, 0xa7, 0xf5, 0xed, 0x0d, 0xf5,
|
||||
0xb4, 0xa5, 0x6d, 0xd8, 0x97, 0x06, 0xb6, 0xd6, 0x0d, 0x53, 0xb7, 0x75, 0x54, 0xf2, 0x7e, 0xae,
|
||||
0x93, 0x9f, 0x95, 0xeb, 0x3e, 0xee, 0x96, 0x79, 0x69, 0xd8, 0xfa, 0x86, 0x61, 0xea, 0xfa, 0x19,
|
||||
0xe3, 0xaf, 0x5c, 0x1b, 0xff, 0xfd, 0x10, 0x5f, 0x72, 0x6d, 0x01, 0x61, 0xda, 0xcb, 0x86, 0xa1,
|
||||
0x9a, 0x6a, 0xcf, 0xf9, 0xbd, 0x7a, 0xae, 0xeb, 0xe7, 0x5d, 0xbc, 0x41, 0x5b, 0xa7, 0x83, 0xb3,
|
||||
0x0d, 0x5b, 0xeb, 0x61, 0xcb, 0x56, 0x7b, 0x06, 0x67, 0x58, 0x3e, 0xd7, 0xcf, 0x75, 0xfa, 0xb9,
|
||||
0x41, 0xbe, 0x18, 0x55, 0xfa, 0x4b, 0x0e, 0x32, 0x32, 0x7e, 0x77, 0x80, 0x2d, 0x1b, 0x6d, 0x42,
|
||||
0x12, 0xb7, 0x3a, 0x7a, 0x59, 0x58, 0x13, 0x6e, 0xe4, 0x37, 0xaf, 0xad, 0x8f, 0x0c, 0x7f, 0x9d,
|
||||
0xf3, 0xd5, 0x5b, 0x1d, 0xbd, 0x11, 0x93, 0x29, 0x2f, 0xba, 0x0d, 0xa9, 0xb3, 0xee, 0xc0, 0xea,
|
||||
0x94, 0xe3, 0x54, 0xe8, 0x7a, 0x94, 0xd0, 0x3d, 0xc2, 0xd4, 0x88, 0xc9, 0x8c, 0x9b, 0x74, 0xa5,
|
||||
0xf5, 0xcf, 0xf4, 0x72, 0x62, 0x72, 0x57, 0x3b, 0xfd, 0x33, 0xda, 0x15, 0xe1, 0x45, 0x5b, 0x00,
|
||||
0x5a, 0x5f, 0xb3, 0x95, 0x56, 0x47, 0xd5, 0xfa, 0xe5, 0x24, 0x95, 0x7c, 0x3c, 0x5a, 0x52, 0xb3,
|
||||
0x6b, 0x84, 0xb1, 0x11, 0x93, 0x73, 0x9a, 0xd3, 0x20, 0xc3, 0x7d, 0x77, 0x80, 0xcd, 0xcb, 0x72,
|
||||
0x6a, 0xf2, 0x70, 0x5f, 0x27, 0x4c, 0x64, 0xb8, 0x94, 0x1b, 0xbd, 0x02, 0xd9, 0x56, 0x07, 0xb7,
|
||||
0x1e, 0x2a, 0xf6, 0xb0, 0x9c, 0xa1, 0x92, 0xab, 0x51, 0x92, 0x35, 0xc2, 0xd7, 0x1c, 0x36, 0x62,
|
||||
0x72, 0xa6, 0xc5, 0x3e, 0xd1, 0x1d, 0x48, 0xb7, 0xf4, 0x5e, 0x4f, 0xb3, 0xcb, 0x40, 0x65, 0x57,
|
||||
0x22, 0x65, 0x29, 0x57, 0x23, 0x26, 0x73, 0x7e, 0xb4, 0x0f, 0xc5, 0xae, 0x66, 0xd9, 0x8a, 0xd5,
|
||||
0x57, 0x0d, 0xab, 0xa3, 0xdb, 0x56, 0x39, 0x4f, 0x35, 0x3c, 0x19, 0xa5, 0x61, 0x4f, 0xb3, 0xec,
|
||||
0x23, 0x87, 0xb9, 0x11, 0x93, 0x0b, 0x5d, 0x3f, 0x81, 0xe8, 0xd3, 0xcf, 0xce, 0xb0, 0xe9, 0x2a,
|
||||
0x2c, 0x2f, 0x4c, 0xd6, 0x77, 0x40, 0xb8, 0x1d, 0x79, 0xa2, 0x4f, 0xf7, 0x13, 0xd0, 0xcf, 0x60,
|
||||
0xa9, 0xab, 0xab, 0x6d, 0x57, 0x9d, 0xd2, 0xea, 0x0c, 0xfa, 0x0f, 0xcb, 0x05, 0xaa, 0xf4, 0x99,
|
||||
0xc8, 0x41, 0xea, 0x6a, 0xdb, 0x51, 0x51, 0x23, 0x02, 0x8d, 0x98, 0xbc, 0xd8, 0x1d, 0x25, 0xa2,
|
||||
0xb7, 0x60, 0x59, 0x35, 0x8c, 0xee, 0xe5, 0xa8, 0xf6, 0x22, 0xd5, 0x7e, 0x33, 0x4a, 0x7b, 0x95,
|
||||
0xc8, 0x8c, 0xaa, 0x47, 0xea, 0x18, 0x15, 0x35, 0x41, 0x34, 0x4c, 0x6c, 0xa8, 0x26, 0x56, 0x0c,
|
||||
0x53, 0x37, 0x74, 0x4b, 0xed, 0x96, 0x4b, 0x54, 0xf7, 0xd3, 0x51, 0xba, 0x0f, 0x19, 0xff, 0x21,
|
||||
0x67, 0x6f, 0xc4, 0xe4, 0x92, 0x11, 0x24, 0x31, 0xad, 0x7a, 0x0b, 0x5b, 0x96, 0xa7, 0x55, 0x9c,
|
||||
0xa6, 0x95, 0xf2, 0x07, 0xb5, 0x06, 0x48, 0xa8, 0x0e, 0x79, 0x3c, 0x24, 0xe2, 0xca, 0x85, 0x6e,
|
||||
0xe3, 0xf2, 0x22, 0x55, 0x28, 0x45, 0x9e, 0x50, 0xca, 0x7a, 0xa2, 0xdb, 0xb8, 0x11, 0x93, 0x01,
|
||||
0xbb, 0x2d, 0xa4, 0xc2, 0x23, 0x17, 0xd8, 0xd4, 0xce, 0x2e, 0xa9, 0x1a, 0x85, 0xfe, 0xb1, 0x34,
|
||||
0xbd, 0x5f, 0x46, 0x54, 0xe1, 0xb3, 0x51, 0x0a, 0x4f, 0xa8, 0x10, 0x51, 0x51, 0x77, 0x44, 0x1a,
|
||||
0x31, 0x79, 0xe9, 0x62, 0x9c, 0x4c, 0x4c, 0xec, 0x4c, 0xeb, 0xab, 0x5d, 0xed, 0x7d, 0xac, 0x9c,
|
||||
0x76, 0xf5, 0xd6, 0xc3, 0xf2, 0xd2, 0x64, 0x13, 0xbb, 0xc7, 0xb9, 0xb7, 0x08, 0x33, 0x31, 0xb1,
|
||||
0x33, 0x3f, 0x61, 0x2b, 0x03, 0xa9, 0x0b, 0xb5, 0x3b, 0xc0, 0xbb, 0xc9, 0x6c, 0x5a, 0xcc, 0xec,
|
||||
0x26, 0xb3, 0x59, 0x31, 0xb7, 0x9b, 0xcc, 0xe6, 0x44, 0x90, 0x9e, 0x86, 0xbc, 0xcf, 0x25, 0xa1,
|
||||
0x32, 0x64, 0x7a, 0xd8, 0xb2, 0xd4, 0x73, 0x4c, 0x3d, 0x58, 0x4e, 0x76, 0x9a, 0x52, 0x11, 0x16,
|
||||
0xfc, 0x6e, 0x48, 0xfa, 0x48, 0x70, 0x25, 0x89, 0x87, 0x21, 0x92, 0x17, 0xd8, 0xa4, 0x0b, 0xc1,
|
||||
0x25, 0x79, 0x13, 0x3d, 0x01, 0x05, 0x3a, 0x09, 0xc5, 0xf9, 0x4f, 0xdc, 0x5c, 0x52, 0x5e, 0xa0,
|
||||
0xc4, 0x13, 0xce, 0xb4, 0x0a, 0x79, 0x63, 0xd3, 0x70, 0x59, 0x12, 0x94, 0x05, 0x8c, 0x4d, 0xc3,
|
||||
0x61, 0x78, 0x1c, 0x16, 0xc8, 0x8c, 0x5d, 0x8e, 0x24, 0xed, 0x24, 0x4f, 0x68, 0x9c, 0x45, 0xfa,
|
||||
0x7b, 0x1c, 0xc4, 0x51, 0xd7, 0x85, 0xee, 0x40, 0x92, 0x78, 0x71, 0xee, 0x90, 0x2b, 0xeb, 0xcc,
|
||||
0xc5, 0xaf, 0x3b, 0x2e, 0x7e, 0xbd, 0xe9, 0xb8, 0xf8, 0xad, 0xec, 0xa7, 0x9f, 0xaf, 0xc6, 0x3e,
|
||||
0xfa, 0xd7, 0xaa, 0x20, 0x53, 0x09, 0x74, 0x95, 0x38, 0x2c, 0x55, 0xeb, 0x2b, 0x5a, 0x9b, 0x0e,
|
||||
0x39, 0x47, 0xbc, 0x91, 0xaa, 0xf5, 0x77, 0xda, 0x68, 0x0f, 0xc4, 0x96, 0xde, 0xb7, 0x70, 0xdf,
|
||||
0x1a, 0x58, 0x0a, 0x0b, 0x21, 0xdc, 0x0d, 0x07, 0x9c, 0x29, 0x0b, 0x64, 0x35, 0x87, 0xf3, 0x90,
|
||||
0x32, 0xca, 0xa5, 0x56, 0x90, 0x80, 0xee, 0x01, 0x5c, 0xa8, 0x5d, 0xad, 0xad, 0xda, 0xba, 0x69,
|
||||
0x95, 0x93, 0x6b, 0x89, 0x1b, 0xf9, 0xcd, 0xb5, 0xb1, 0xad, 0x3e, 0x71, 0x58, 0x8e, 0x8d, 0xb6,
|
||||
0x6a, 0xe3, 0xad, 0x24, 0x19, 0xae, 0xec, 0x93, 0x44, 0x4f, 0x41, 0x49, 0x35, 0x0c, 0xc5, 0xb2,
|
||||
0x55, 0x1b, 0x2b, 0xa7, 0x97, 0x36, 0xb6, 0xa8, 0x8b, 0x5e, 0x90, 0x0b, 0xaa, 0x61, 0x1c, 0x11,
|
||||
0xea, 0x16, 0x21, 0xa2, 0x27, 0xa1, 0x48, 0xbc, 0xb9, 0xa6, 0x76, 0x95, 0x0e, 0xd6, 0xce, 0x3b,
|
||||
0x76, 0x39, 0xbd, 0x26, 0xdc, 0x48, 0xc8, 0x05, 0x4e, 0x6d, 0x50, 0xa2, 0xd4, 0x76, 0x77, 0x9c,
|
||||
0x7a, 0x72, 0x84, 0x20, 0xd9, 0x56, 0x6d, 0x95, 0xae, 0xe4, 0x82, 0x4c, 0xbf, 0x09, 0xcd, 0x50,
|
||||
0xed, 0x0e, 0x5f, 0x1f, 0xfa, 0x8d, 0xae, 0x40, 0x9a, 0xab, 0x4d, 0x50, 0xb5, 0xbc, 0x85, 0x96,
|
||||
0x21, 0x65, 0x98, 0xfa, 0x05, 0xa6, 0x5b, 0x97, 0x95, 0x59, 0x43, 0x92, 0xa1, 0x18, 0xf4, 0xfa,
|
||||
0xa8, 0x08, 0x71, 0x7b, 0xc8, 0x7b, 0x89, 0xdb, 0x43, 0xf4, 0x02, 0x24, 0xc9, 0x42, 0xd2, 0x3e,
|
||||
0x8a, 0x21, 0x71, 0x8e, 0xcb, 0x35, 0x2f, 0x0d, 0x2c, 0x53, 0x4e, 0xa9, 0x04, 0x85, 0x40, 0x34,
|
||||
0x90, 0xae, 0xc0, 0x72, 0x98, 0x73, 0x97, 0x3a, 0x2e, 0x3d, 0xe0, 0xa4, 0xd1, 0x6d, 0xc8, 0xba,
|
||||
0xde, 0x9d, 0x19, 0xce, 0xd5, 0xb1, 0x6e, 0x1d, 0x66, 0xd9, 0x65, 0x25, 0x16, 0x43, 0x36, 0xa0,
|
||||
0xa3, 0xf2, 0x58, 0xbe, 0x20, 0x67, 0x54, 0xc3, 0x68, 0xa8, 0x56, 0x47, 0x7a, 0x1b, 0xca, 0x51,
|
||||
0x9e, 0xdb, 0xb7, 0x60, 0x02, 0x35, 0x7b, 0x67, 0xc1, 0xae, 0x40, 0xfa, 0x4c, 0x37, 0x7b, 0xaa,
|
||||
0x4d, 0x95, 0x15, 0x64, 0xde, 0x22, 0x0b, 0xc9, 0xbc, 0x78, 0x82, 0x92, 0x59, 0x43, 0x52, 0xe0,
|
||||
0x6a, 0xa4, 0xf7, 0x26, 0x22, 0x5a, 0xbf, 0x8d, 0xd9, 0xb2, 0x16, 0x64, 0xd6, 0xf0, 0x14, 0xb1,
|
||||
0xc1, 0xb2, 0x06, 0xe9, 0xd6, 0xa2, 0x73, 0xa5, 0xfa, 0x73, 0x32, 0x6f, 0x49, 0x1f, 0x27, 0xe0,
|
||||
0x4a, 0xb8, 0x0f, 0x47, 0x6b, 0xb0, 0xd0, 0x53, 0x87, 0x8a, 0x3d, 0xe4, 0x66, 0x27, 0xd0, 0x8d,
|
||||
0x87, 0x9e, 0x3a, 0x6c, 0x0e, 0x99, 0xcd, 0x89, 0x90, 0xb0, 0x87, 0x56, 0x39, 0xbe, 0x96, 0xb8,
|
||||
0xb1, 0x20, 0x93, 0x4f, 0x74, 0x0c, 0x8b, 0x5d, 0xbd, 0xa5, 0x76, 0x95, 0xae, 0x6a, 0xd9, 0x0a,
|
||||
0x0f, 0xee, 0xec, 0x10, 0x3d, 0x31, 0xb6, 0xd8, 0xcc, 0x1b, 0xe3, 0x36, 0xdb, 0x4f, 0xe2, 0x70,
|
||||
0xb8, 0xfd, 0x97, 0xa8, 0x8e, 0x3d, 0xd5, 0xd9, 0x6a, 0xb4, 0x0d, 0xf9, 0x9e, 0x66, 0x9d, 0xe2,
|
||||
0x8e, 0x7a, 0xa1, 0xe9, 0x26, 0x3f, 0x4d, 0xe3, 0x46, 0xf3, 0xc0, 0xe3, 0xe1, 0x9a, 0xfc, 0x62,
|
||||
0xbe, 0x2d, 0x49, 0x05, 0x6c, 0xd8, 0xf1, 0x26, 0xe9, 0xb9, 0xbd, 0xc9, 0x0b, 0xb0, 0xdc, 0xc7,
|
||||
0x43, 0x5b, 0xf1, 0xce, 0x2b, 0xb3, 0x93, 0x0c, 0x5d, 0x7a, 0x44, 0xfe, 0xb9, 0x27, 0xdc, 0x22,
|
||||
0x26, 0x83, 0x9e, 0xa1, 0x51, 0xd0, 0xd0, 0x2d, 0x6c, 0x2a, 0x6a, 0xbb, 0x6d, 0x62, 0xcb, 0x2a,
|
||||
0x67, 0x29, 0x77, 0xc9, 0xa1, 0x57, 0x19, 0x59, 0xfa, 0x8d, 0x7f, 0x6b, 0x82, 0x51, 0x8f, 0x2f,
|
||||
0xbc, 0xe0, 0x2d, 0xfc, 0x11, 0x2c, 0x73, 0xf9, 0x76, 0x60, 0xed, 0x59, 0xf6, 0xf9, 0xd8, 0xf8,
|
||||
0xf9, 0x1a, 0x5d, 0x73, 0xe4, 0x88, 0x47, 0x2f, 0x7b, 0xe2, 0xeb, 0x2d, 0x3b, 0x82, 0x24, 0x5d,
|
||||
0x94, 0x24, 0x73, 0x31, 0xe4, 0xfb, 0xff, 0x6d, 0x2b, 0x5e, 0x83, 0xc5, 0xb1, 0x0c, 0xc2, 0x9d,
|
||||
0x97, 0x10, 0x3a, 0xaf, 0xb8, 0x7f, 0x5e, 0xd2, 0xef, 0x05, 0xa8, 0x44, 0xa7, 0x0c, 0xa1, 0xaa,
|
||||
0x9e, 0x85, 0x45, 0x77, 0x2e, 0xee, 0xf8, 0xd8, 0x99, 0x16, 0xdd, 0x1f, 0x7c, 0x80, 0x91, 0xee,
|
||||
0xf9, 0x49, 0x28, 0x8e, 0x24, 0x34, 0x6c, 0x17, 0x0a, 0x17, 0xfe, 0xfe, 0xa5, 0x5f, 0x27, 0x5c,
|
||||
0x9f, 0x19, 0xc8, 0x3a, 0x42, 0x0c, 0xed, 0x75, 0x58, 0x6a, 0xe3, 0x96, 0xd6, 0xfe, 0xba, 0x76,
|
||||
0xb6, 0xc8, 0xa5, 0xbf, 0x37, 0xb3, 0x71, 0x33, 0xfb, 0x2d, 0x40, 0x56, 0xc6, 0x96, 0x41, 0x52,
|
||||
0x09, 0xb4, 0x05, 0x39, 0x3c, 0x6c, 0x61, 0xc3, 0x76, 0xb2, 0xaf, 0xf0, 0xbc, 0x96, 0x71, 0xd7,
|
||||
0x1d, 0x4e, 0x82, 0xea, 0x5c, 0x31, 0x74, 0x8b, 0x03, 0xd7, 0x68, 0x0c, 0xca, 0xc5, 0xfd, 0xc8,
|
||||
0xf5, 0x45, 0x07, 0xb9, 0x26, 0x22, 0x41, 0x19, 0x93, 0x1a, 0x81, 0xae, 0xb7, 0x38, 0x74, 0x4d,
|
||||
0x4e, 0xe9, 0x2c, 0x80, 0x5d, 0x6b, 0x01, 0xec, 0x9a, 0x9a, 0x32, 0xcd, 0x08, 0xf0, 0xfa, 0xa2,
|
||||
0x03, 0x5e, 0xd3, 0x53, 0x46, 0x3c, 0x82, 0x5e, 0x5f, 0xf5, 0xa1, 0xd7, 0x2c, 0x15, 0x5d, 0x8b,
|
||||
0x14, 0x0d, 0x81, 0xaf, 0x2f, 0xbb, 0xf0, 0x35, 0x1f, 0x09, 0x7d, 0xb9, 0xf0, 0x28, 0x7e, 0x3d,
|
||||
0x18, 0xc3, 0xaf, 0x0c, 0x6f, 0x3e, 0x15, 0xa9, 0x62, 0x0a, 0x80, 0x3d, 0x18, 0x03, 0xb0, 0x85,
|
||||
0x29, 0x0a, 0xa7, 0x20, 0xd8, 0x9f, 0x87, 0x23, 0xd8, 0x68, 0x8c, 0xc9, 0x87, 0x39, 0x1b, 0x84,
|
||||
0x55, 0x22, 0x20, 0x6c, 0x29, 0x12, 0x6e, 0x31, 0xf5, 0x33, 0x63, 0xd8, 0xe3, 0x10, 0x0c, 0xcb,
|
||||
0xd0, 0xe6, 0x8d, 0x48, 0xe5, 0x33, 0x80, 0xd8, 0xe3, 0x10, 0x10, 0xbb, 0x38, 0x55, 0xed, 0x54,
|
||||
0x14, 0x7b, 0x2f, 0x88, 0x62, 0x51, 0x44, 0xc2, 0xe4, 0x9d, 0xf6, 0x08, 0x18, 0x7b, 0x1a, 0x05,
|
||||
0x63, 0x19, 0xd4, 0x7c, 0x2e, 0x52, 0xe3, 0x1c, 0x38, 0xf6, 0x60, 0x0c, 0xc7, 0x2e, 0x4f, 0xb1,
|
||||
0xb4, 0xd9, 0x81, 0x6c, 0x46, 0xcc, 0x32, 0x08, 0xbb, 0x9b, 0xcc, 0x82, 0x98, 0x97, 0x9e, 0x21,
|
||||
0x71, 0x77, 0xc4, 0xc3, 0x91, 0x04, 0x17, 0x9b, 0xa6, 0x6e, 0x72, 0x48, 0xca, 0x1a, 0xd2, 0x0d,
|
||||
0x02, 0x6c, 0x3c, 0x6f, 0x36, 0x01, 0xf4, 0x52, 0x20, 0xe1, 0xf3, 0x60, 0xd2, 0x9f, 0x05, 0x4f,
|
||||
0x96, 0xc2, 0x5e, 0x3f, 0x28, 0xca, 0x71, 0x50, 0xe4, 0x83, 0xc2, 0xf1, 0x20, 0x14, 0x5e, 0x85,
|
||||
0x3c, 0x01, 0x08, 0x23, 0x28, 0x57, 0x35, 0x5c, 0x94, 0x7b, 0x13, 0x16, 0x69, 0xa8, 0x64, 0x80,
|
||||
0x99, 0x07, 0xa4, 0x24, 0x0d, 0x48, 0x25, 0xf2, 0x83, 0xad, 0x0b, 0x8b, 0x4c, 0xcf, 0xc3, 0x92,
|
||||
0x8f, 0xd7, 0x05, 0x1e, 0x0c, 0xf2, 0x89, 0x2e, 0x77, 0x95, 0x23, 0x90, 0xbf, 0x09, 0xde, 0x0a,
|
||||
0x79, 0xf0, 0x38, 0x0c, 0xc9, 0x0a, 0xdf, 0x12, 0x92, 0x8d, 0x7f, 0x6d, 0x24, 0xeb, 0x07, 0x52,
|
||||
0x89, 0x20, 0x90, 0xfa, 0x8f, 0xe0, 0xed, 0x89, 0x8b, 0x4b, 0x5b, 0x7a, 0x1b, 0x73, 0x68, 0x43,
|
||||
0xbf, 0x49, 0x32, 0xd2, 0xd5, 0xcf, 0x39, 0x80, 0x21, 0x9f, 0x84, 0xcb, 0x0d, 0x39, 0x39, 0x1e,
|
||||
0x51, 0x5c, 0x54, 0xc4, 0x42, 0x3e, 0x47, 0x45, 0x22, 0x24, 0x1e, 0x62, 0x16, 0x20, 0x16, 0x64,
|
||||
0xf2, 0x49, 0xf8, 0xa8, 0xd9, 0xf1, 0xd0, 0xcd, 0x1a, 0xe8, 0x0e, 0xe4, 0x68, 0xe5, 0x59, 0xd1,
|
||||
0x0d, 0x8b, 0xc7, 0x84, 0x40, 0x52, 0xc3, 0xca, 0xcf, 0xeb, 0x87, 0x84, 0xe7, 0xc0, 0xb0, 0xe4,
|
||||
0xac, 0xc1, 0xbf, 0x7c, 0xb9, 0x46, 0x2e, 0x90, 0x6b, 0x5c, 0x83, 0x1c, 0x19, 0xbd, 0x65, 0xa8,
|
||||
0x2d, 0x4c, 0xeb, 0x9c, 0x39, 0xd9, 0x23, 0x48, 0x9f, 0x0a, 0x50, 0x1a, 0x09, 0x31, 0xa1, 0x73,
|
||||
0x77, 0x4c, 0x32, 0xee, 0xc3, 0xe9, 0xd7, 0x01, 0xce, 0x55, 0x4b, 0x79, 0x4f, 0xed, 0xdb, 0xb8,
|
||||
0xcd, 0xa7, 0x9b, 0x3b, 0x57, 0xad, 0x37, 0x28, 0x21, 0xd8, 0x71, 0x76, 0xa4, 0x63, 0x1f, 0x20,
|
||||
0xcc, 0xf9, 0x01, 0x21, 0xaa, 0x40, 0xd6, 0x30, 0x35, 0xdd, 0xd4, 0xec, 0x4b, 0x3a, 0xda, 0x84,
|
||||
0xec, 0xb6, 0x77, 0x93, 0xd9, 0x84, 0x98, 0xdc, 0x4d, 0x66, 0x93, 0x62, 0xca, 0xad, 0x3a, 0xb1,
|
||||
0x23, 0x9b, 0x17, 0x17, 0xa4, 0x0f, 0xe2, 0x9e, 0x2d, 0x6e, 0xe3, 0xae, 0x76, 0x81, 0xcd, 0x39,
|
||||
0x26, 0x33, 0xdb, 0xe6, 0xae, 0x84, 0x4c, 0xd9, 0x47, 0x21, 0xa3, 0x27, 0xad, 0x81, 0x85, 0xdb,
|
||||
0xbc, 0xfe, 0xe1, 0xb6, 0x51, 0x03, 0xd2, 0xf8, 0x02, 0xf7, 0x6d, 0xab, 0x9c, 0xa1, 0x36, 0x7c,
|
||||
0x65, 0x1c, 0x90, 0x92, 0xdf, 0x5b, 0x65, 0x62, 0xb9, 0x5f, 0x7d, 0xbe, 0x2a, 0x32, 0xee, 0xe7,
|
||||
0xf4, 0x9e, 0x66, 0xe3, 0x9e, 0x61, 0x5f, 0xca, 0x5c, 0x7e, 0xf2, 0xca, 0x4a, 0x55, 0x28, 0x06,
|
||||
0xe3, 0x3e, 0x7a, 0x02, 0x0a, 0x26, 0xb6, 0x55, 0xad, 0xaf, 0x04, 0x92, 0xf4, 0x05, 0x46, 0x64,
|
||||
0x27, 0x7f, 0x37, 0x99, 0x15, 0xc4, 0xf8, 0x6e, 0x32, 0x1b, 0x17, 0x13, 0xd2, 0x21, 0x3c, 0x12,
|
||||
0x1a, 0xf7, 0xd1, 0x4b, 0x90, 0xf3, 0x52, 0x06, 0x81, 0x4e, 0x63, 0x42, 0x11, 0xc3, 0xe3, 0x95,
|
||||
0xfe, 0x2a, 0x78, 0x2a, 0x83, 0x65, 0x91, 0x3a, 0xa4, 0x4d, 0x6c, 0x0d, 0xba, 0xac, 0x50, 0x51,
|
||||
0xdc, 0x7c, 0x7e, 0xb6, 0x8c, 0x81, 0x50, 0x07, 0x5d, 0x5b, 0xe6, 0xc2, 0xd2, 0x5b, 0x90, 0x66,
|
||||
0x14, 0x94, 0x87, 0xcc, 0xf1, 0xfe, 0xfd, 0xfd, 0x83, 0x37, 0xf6, 0xc5, 0x18, 0x02, 0x48, 0x57,
|
||||
0x6b, 0xb5, 0xfa, 0x61, 0x53, 0x14, 0x50, 0x0e, 0x52, 0xd5, 0xad, 0x03, 0xb9, 0x29, 0xc6, 0x09,
|
||||
0x59, 0xae, 0xef, 0xd6, 0x6b, 0x4d, 0x31, 0x81, 0x16, 0xa1, 0xc0, 0xbe, 0x95, 0x7b, 0x07, 0xf2,
|
||||
0x83, 0x6a, 0x53, 0x4c, 0xfa, 0x48, 0x47, 0xf5, 0xfd, 0xed, 0xba, 0x2c, 0xa6, 0xa4, 0x1f, 0xc0,
|
||||
0xd5, 0xc8, 0x1c, 0xc3, 0xab, 0x79, 0x08, 0xbe, 0x9a, 0x87, 0xf4, 0x71, 0x9c, 0x80, 0xae, 0xa8,
|
||||
0xc4, 0x01, 0xed, 0x8e, 0x4c, 0x7c, 0x73, 0x8e, 0xac, 0x63, 0x64, 0xf6, 0x04, 0x67, 0x99, 0xf8,
|
||||
0x0c, 0xdb, 0xad, 0x0e, 0x4b, 0x64, 0x98, 0x9f, 0x2c, 0xc8, 0x05, 0x4e, 0xa5, 0x42, 0x16, 0x63,
|
||||
0x7b, 0x07, 0xb7, 0x6c, 0x85, 0x9d, 0x36, 0x8b, 0x82, 0x9d, 0x1c, 0x61, 0x23, 0xd4, 0x23, 0x46,
|
||||
0x94, 0xde, 0x9e, 0x6b, 0x2d, 0x73, 0x90, 0x92, 0xeb, 0x4d, 0xf9, 0x4d, 0x31, 0x81, 0x10, 0x14,
|
||||
0xe9, 0xa7, 0x72, 0xb4, 0x5f, 0x3d, 0x3c, 0x6a, 0x1c, 0x90, 0xb5, 0x5c, 0x82, 0x92, 0xb3, 0x96,
|
||||
0x0e, 0x31, 0x25, 0xfd, 0x23, 0x0e, 0x8f, 0x46, 0xa4, 0x3d, 0xe8, 0x0e, 0x80, 0x3d, 0x54, 0x4c,
|
||||
0xdc, 0xd2, 0xcd, 0x76, 0xb4, 0x91, 0x35, 0x87, 0x32, 0xe5, 0x90, 0x73, 0x36, 0xff, 0xb2, 0x26,
|
||||
0x94, 0xca, 0xd0, 0x2b, 0x5c, 0x29, 0x99, 0x95, 0xc5, 0x21, 0xde, 0xf5, 0x90, 0x8a, 0x10, 0x6e,
|
||||
0x11, 0xc5, 0x74, 0x6d, 0xa9, 0x62, 0xca, 0x8f, 0x1e, 0xf8, 0xb1, 0xf0, 0x80, 0x06, 0x98, 0x99,
|
||||
0x6b, 0xaa, 0x3e, 0xb4, 0xcc, 0x08, 0x16, 0x7a, 0x13, 0x1e, 0x1d, 0x89, 0x8f, 0xae, 0xd2, 0xd4,
|
||||
0xac, 0x61, 0xf2, 0x91, 0x60, 0x98, 0xe4, 0xaa, 0xa5, 0x3f, 0x24, 0xfc, 0x0b, 0x1b, 0xcc, 0xf2,
|
||||
0x0e, 0x20, 0x6d, 0xd9, 0xaa, 0x3d, 0xb0, 0xb8, 0xc1, 0xbd, 0x34, 0x6b, 0xca, 0xb8, 0xee, 0x7c,
|
||||
0x1c, 0x51, 0x71, 0x99, 0xab, 0xf9, 0x7e, 0xbd, 0x2d, 0xe9, 0x36, 0x14, 0x83, 0x8b, 0x13, 0x7d,
|
||||
0x64, 0x3c, 0x9f, 0x13, 0x97, 0xee, 0x02, 0x1a, 0x4f, 0xa6, 0x43, 0xaa, 0x25, 0x42, 0x58, 0xb5,
|
||||
0xe4, 0x8f, 0x02, 0x3c, 0x36, 0x21, 0x71, 0x46, 0xaf, 0x8f, 0xec, 0xf3, 0xcb, 0xf3, 0xa4, 0xdd,
|
||||
0xeb, 0x8c, 0x16, 0xdc, 0x69, 0xe9, 0x16, 0x2c, 0xf8, 0xe9, 0xb3, 0x4d, 0xf2, 0xab, 0xb8, 0xe7,
|
||||
0xf3, 0x83, 0x65, 0x1d, 0x2f, 0x14, 0x0a, 0xdf, 0x30, 0x14, 0x06, 0xed, 0x2c, 0x3e, 0xa7, 0x9d,
|
||||
0x1d, 0x85, 0xd9, 0x59, 0x62, 0xae, 0x0c, 0x73, 0x2e, 0x6b, 0x4b, 0x7e, 0x33, 0x6b, 0x0b, 0x1c,
|
||||
0xb8, 0x54, 0x30, 0x85, 0x7d, 0x13, 0xc0, 0xab, 0x75, 0x91, 0x80, 0x64, 0xea, 0x83, 0x7e, 0x9b,
|
||||
0x5a, 0x40, 0x4a, 0x66, 0x0d, 0x74, 0x1b, 0x52, 0xc4, 0x92, 0x9c, 0x75, 0x1a, 0x77, 0xaa, 0xc4,
|
||||
0x12, 0x7c, 0xb5, 0x32, 0xc6, 0x2d, 0x69, 0x80, 0xc6, 0x4b, 0xe5, 0x11, 0x5d, 0xbc, 0x1a, 0xec,
|
||||
0xe2, 0xf1, 0xc8, 0xa2, 0x7b, 0x78, 0x57, 0xef, 0x43, 0x8a, 0xee, 0x3c, 0x49, 0xbe, 0xe8, 0xfd,
|
||||
0x0c, 0x87, 0x40, 0xe4, 0x1b, 0xfd, 0x02, 0x40, 0xb5, 0x6d, 0x53, 0x3b, 0x1d, 0x78, 0x1d, 0xac,
|
||||
0x86, 0x5b, 0x4e, 0xd5, 0xe1, 0xdb, 0xba, 0xc6, 0x4d, 0x68, 0xd9, 0x13, 0xf5, 0x99, 0x91, 0x4f,
|
||||
0xa1, 0xb4, 0x0f, 0xc5, 0xa0, 0xac, 0x93, 0xb4, 0xb3, 0x31, 0x04, 0x93, 0x76, 0x86, 0xc1, 0x78,
|
||||
0xd2, 0xee, 0xa6, 0xfc, 0x09, 0x76, 0x09, 0x45, 0x1b, 0xd2, 0x7f, 0x05, 0x58, 0xf0, 0x1b, 0xde,
|
||||
0xb7, 0x9c, 0x8a, 0x4e, 0xc9, 0xbe, 0xaf, 0x8e, 0x65, 0xa2, 0x99, 0x73, 0xd5, 0x3a, 0xfe, 0x2e,
|
||||
0x13, 0xd1, 0x0f, 0x04, 0xc8, 0xba, 0x93, 0x0f, 0xde, 0x47, 0x05, 0x2e, 0xf0, 0xd8, 0xda, 0xc5,
|
||||
0xfd, 0x97, 0x48, 0xec, 0xba, 0x2e, 0xe1, 0x5e, 0xd7, 0xdd, 0x75, 0x73, 0xa5, 0xa8, 0xea, 0x9e,
|
||||
0x7f, 0xa5, 0xb9, 0x4d, 0x39, 0xa9, 0xe1, 0xef, 0xf8, 0x38, 0x48, 0x92, 0x80, 0x7e, 0x04, 0x69,
|
||||
0xb5, 0xe5, 0xd6, 0x34, 0x8b, 0x21, 0xc5, 0x3e, 0x87, 0x75, 0xbd, 0x39, 0xac, 0x52, 0x4e, 0x99,
|
||||
0x4b, 0xf0, 0x51, 0xc5, 0x9d, 0x51, 0x49, 0xaf, 0x11, 0xbd, 0x8c, 0x27, 0xe8, 0x11, 0x8b, 0x00,
|
||||
0xc7, 0xfb, 0x0f, 0x0e, 0xb6, 0x77, 0xee, 0xed, 0xd4, 0xb7, 0x79, 0xb6, 0xb4, 0xbd, 0x5d, 0xdf,
|
||||
0x16, 0xe3, 0x84, 0x4f, 0xae, 0x3f, 0x38, 0x38, 0xa9, 0x6f, 0x8b, 0x09, 0xe9, 0x2e, 0xe4, 0x5c,
|
||||
0xaf, 0x42, 0x10, 0xbe, 0x53, 0x9f, 0x15, 0xf8, 0xd9, 0xe6, 0xd5, 0xf5, 0x65, 0x48, 0x19, 0xfa,
|
||||
0x7b, 0xfc, 0xee, 0x2c, 0x21, 0xb3, 0x86, 0xd4, 0x86, 0xd2, 0x88, 0x4b, 0x42, 0x77, 0x21, 0x63,
|
||||
0x0c, 0x4e, 0x15, 0xc7, 0x68, 0x47, 0xaa, 0xd8, 0x0e, 0x76, 0x1c, 0x9c, 0x76, 0xb5, 0xd6, 0x7d,
|
||||
0x7c, 0xe9, 0x2c, 0x93, 0x31, 0x38, 0xbd, 0xcf, 0x6c, 0x9b, 0xf5, 0x12, 0xf7, 0xf7, 0x72, 0x01,
|
||||
0x59, 0xe7, 0xa8, 0xa2, 0x1f, 0x43, 0xce, 0xf5, 0x76, 0xee, 0xdd, 0x77, 0xa4, 0x9b, 0xe4, 0xea,
|
||||
0x3d, 0x11, 0x74, 0x13, 0x16, 0x2d, 0xed, 0xbc, 0xef, 0x94, 0xee, 0x59, 0xf5, 0x26, 0x4e, 0xcf,
|
||||
0x4c, 0x89, 0xfd, 0xd8, 0x73, 0x0a, 0x0c, 0x24, 0xc8, 0x89, 0xa3, 0xbe, 0xe2, 0xbb, 0x1c, 0x40,
|
||||
0x48, 0x30, 0x4e, 0x84, 0x05, 0xe3, 0x5f, 0xc5, 0x21, 0xef, 0xbb, 0x19, 0x40, 0x3f, 0xf4, 0x39,
|
||||
0xae, 0x62, 0x48, 0x14, 0xf1, 0xf1, 0x7a, 0x97, 0xcb, 0xc1, 0x89, 0xc5, 0xe7, 0x9f, 0x58, 0xd4,
|
||||
0xfd, 0x8b, 0x73, 0xd1, 0x90, 0x9c, 0xfb, 0xa2, 0xe1, 0x39, 0x40, 0xb6, 0x6e, 0xab, 0x5d, 0xe5,
|
||||
0x42, 0xb7, 0xb5, 0xfe, 0xb9, 0xc2, 0x4c, 0x83, 0xb9, 0x19, 0x91, 0xfe, 0x39, 0xa1, 0x3f, 0x0e,
|
||||
0xa9, 0x95, 0xfc, 0x52, 0x80, 0xac, 0x8b, 0xe8, 0xe6, 0xbd, 0x7a, 0xbe, 0x02, 0x69, 0x0e, 0x5a,
|
||||
0xd8, 0xdd, 0x33, 0x6f, 0x85, 0xde, 0xa8, 0x54, 0x20, 0xdb, 0xc3, 0xb6, 0x4a, 0x7d, 0x26, 0x8b,
|
||||
0x80, 0x6e, 0xfb, 0xe6, 0xcb, 0x90, 0xf7, 0x5d, 0xdb, 0x13, 0x37, 0xba, 0x5f, 0x7f, 0x43, 0x8c,
|
||||
0x55, 0x32, 0x1f, 0x7e, 0xb2, 0x96, 0xd8, 0xc7, 0xef, 0x91, 0x13, 0x26, 0xd7, 0x6b, 0x8d, 0x7a,
|
||||
0xed, 0xbe, 0x28, 0x54, 0xf2, 0x1f, 0x7e, 0xb2, 0x96, 0x91, 0x31, 0x2d, 0xa6, 0xdf, 0xbc, 0x0f,
|
||||
0xa5, 0x91, 0x8d, 0x09, 0x1e, 0x68, 0x04, 0xc5, 0xed, 0xe3, 0xc3, 0xbd, 0x9d, 0x5a, 0xb5, 0x59,
|
||||
0x57, 0x4e, 0x0e, 0x9a, 0x75, 0x51, 0x40, 0x8f, 0xc2, 0xd2, 0xde, 0xce, 0x4f, 0x1a, 0x4d, 0xa5,
|
||||
0xb6, 0xb7, 0x53, 0xdf, 0x6f, 0x2a, 0xd5, 0x66, 0xb3, 0x5a, 0xbb, 0x2f, 0xc6, 0x37, 0xff, 0x94,
|
||||
0x87, 0x52, 0x75, 0xab, 0xb6, 0x43, 0x60, 0x9b, 0xd6, 0x52, 0xa9, 0x7b, 0xa8, 0x41, 0x92, 0x96,
|
||||
0x05, 0x27, 0x3e, 0xde, 0xab, 0x4c, 0xbe, 0x21, 0x41, 0xf7, 0x20, 0x45, 0x2b, 0x86, 0x68, 0xf2,
|
||||
0x6b, 0xbe, 0xca, 0x94, 0x2b, 0x13, 0x32, 0x18, 0x7a, 0x9c, 0x26, 0x3e, 0xef, 0xab, 0x4c, 0xbe,
|
||||
0x41, 0x41, 0x7b, 0x90, 0x71, 0x0a, 0x46, 0xd3, 0xde, 0xdc, 0x55, 0xa6, 0x5e, 0x6b, 0x90, 0xa9,
|
||||
0xb1, 0xc2, 0xdb, 0xe4, 0x97, 0x7f, 0x95, 0x29, 0x77, 0x2b, 0x68, 0x07, 0xd2, 0xbc, 0xe8, 0x31,
|
||||
0xe5, 0x31, 0x5f, 0x65, 0xda, 0x6d, 0x09, 0x92, 0x21, 0xe7, 0x95, 0x34, 0xa7, 0xbf, 0x67, 0xac,
|
||||
0xcc, 0x70, 0x6d, 0x84, 0xde, 0x82, 0x42, 0xb0, 0xa0, 0x32, 0xdb, 0x83, 0xc1, 0xca, 0x8c, 0xf7,
|
||||
0x32, 0x44, 0x7f, 0xb0, 0xba, 0x32, 0xdb, 0x03, 0xc2, 0xca, 0x8c, 0xd7, 0x34, 0xe8, 0x1d, 0x58,
|
||||
0x1c, 0xaf, 0x7e, 0xcc, 0xfe, 0x9e, 0xb0, 0x32, 0xc7, 0xc5, 0x0d, 0xea, 0x01, 0x0a, 0xa9, 0x9a,
|
||||
0xcc, 0xf1, 0xbc, 0xb0, 0x32, 0xcf, 0x3d, 0x0e, 0x6a, 0x43, 0x69, 0xb4, 0x12, 0x31, 0xeb, 0x73,
|
||||
0xc3, 0xca, 0xcc, 0x77, 0x3a, 0xac, 0x97, 0x20, 0x2c, 0x9f, 0xf5, 0xf9, 0x61, 0x65, 0xe6, 0x2b,
|
||||
0x1e, 0x74, 0x0c, 0xe0, 0x83, 0x95, 0x33, 0x3c, 0x47, 0xac, 0xcc, 0x72, 0xd9, 0x83, 0x0c, 0x58,
|
||||
0x0a, 0xc3, 0x9b, 0xf3, 0xbc, 0x4e, 0xac, 0xcc, 0x75, 0x07, 0x44, 0xec, 0x39, 0x88, 0x1c, 0x67,
|
||||
0x7b, 0xad, 0x58, 0x99, 0xf1, 0x32, 0x68, 0xab, 0xfe, 0xe9, 0x17, 0x2b, 0xc2, 0x67, 0x5f, 0xac,
|
||||
0x08, 0xff, 0xfe, 0x62, 0x45, 0xf8, 0xe8, 0xcb, 0x95, 0xd8, 0x67, 0x5f, 0xae, 0xc4, 0xfe, 0xf9,
|
||||
0xe5, 0x4a, 0xec, 0xa7, 0xcf, 0x9e, 0x6b, 0x76, 0x67, 0x70, 0xba, 0xde, 0xd2, 0x7b, 0x1b, 0xfe,
|
||||
0x07, 0xde, 0x61, 0xcf, 0xca, 0x4f, 0xd3, 0x34, 0xa0, 0xde, 0xfa, 0x5f, 0x00, 0x00, 0x00, 0xff,
|
||||
0xff, 0xdc, 0x85, 0x19, 0x4e, 0x76, 0x2e, 0x00, 0x00,
|
||||
}
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
@@ -5510,10 +5509,10 @@ func (m *RequestPrepareProposal) MarshalToSizedBuffer(dAtA []byte) (int, error)
|
||||
i--
|
||||
dAtA[i] = 0x28
|
||||
}
|
||||
if len(m.ByzantineValidators) > 0 {
|
||||
for iNdEx := len(m.ByzantineValidators) - 1; iNdEx >= 0; iNdEx-- {
|
||||
if len(m.Misbehavior) > 0 {
|
||||
for iNdEx := len(m.Misbehavior) - 1; iNdEx >= 0; iNdEx-- {
|
||||
{
|
||||
size, err := m.ByzantineValidators[iNdEx].MarshalToSizedBuffer(dAtA[:i])
|
||||
size, err := m.Misbehavior[iNdEx].MarshalToSizedBuffer(dAtA[:i])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -5605,10 +5604,10 @@ func (m *RequestProcessProposal) MarshalToSizedBuffer(dAtA []byte) (int, error)
|
||||
i--
|
||||
dAtA[i] = 0x22
|
||||
}
|
||||
if len(m.ByzantineValidators) > 0 {
|
||||
for iNdEx := len(m.ByzantineValidators) - 1; iNdEx >= 0; iNdEx-- {
|
||||
if len(m.Misbehavior) > 0 {
|
||||
for iNdEx := len(m.Misbehavior) - 1; iNdEx >= 0; iNdEx-- {
|
||||
{
|
||||
size, err := m.ByzantineValidators[iNdEx].MarshalToSizedBuffer(dAtA[:i])
|
||||
size, err := m.Misbehavior[iNdEx].MarshalToSizedBuffer(dAtA[:i])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -5779,10 +5778,10 @@ func (m *RequestFinalizeBlock) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
i--
|
||||
dAtA[i] = 0x22
|
||||
}
|
||||
if len(m.ByzantineValidators) > 0 {
|
||||
for iNdEx := len(m.ByzantineValidators) - 1; iNdEx >= 0; iNdEx-- {
|
||||
if len(m.Misbehavior) > 0 {
|
||||
for iNdEx := len(m.Misbehavior) - 1; iNdEx >= 0; iNdEx-- {
|
||||
{
|
||||
size, err := m.ByzantineValidators[iNdEx].MarshalToSizedBuffer(dAtA[:i])
|
||||
size, err := m.Misbehavior[iNdEx].MarshalToSizedBuffer(dAtA[:i])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -8145,8 +8144,8 @@ func (m *RequestPrepareProposal) Size() (n int) {
|
||||
}
|
||||
l = m.LocalLastCommit.Size()
|
||||
n += 1 + l + sovTypes(uint64(l))
|
||||
if len(m.ByzantineValidators) > 0 {
|
||||
for _, e := range m.ByzantineValidators {
|
||||
if len(m.Misbehavior) > 0 {
|
||||
for _, e := range m.Misbehavior {
|
||||
l = e.Size()
|
||||
n += 1 + l + sovTypes(uint64(l))
|
||||
}
|
||||
@@ -8181,8 +8180,8 @@ func (m *RequestProcessProposal) Size() (n int) {
|
||||
}
|
||||
l = m.ProposedLastCommit.Size()
|
||||
n += 1 + l + sovTypes(uint64(l))
|
||||
if len(m.ByzantineValidators) > 0 {
|
||||
for _, e := range m.ByzantineValidators {
|
||||
if len(m.Misbehavior) > 0 {
|
||||
for _, e := range m.Misbehavior {
|
||||
l = e.Size()
|
||||
n += 1 + l + sovTypes(uint64(l))
|
||||
}
|
||||
@@ -8261,8 +8260,8 @@ func (m *RequestFinalizeBlock) Size() (n int) {
|
||||
}
|
||||
l = m.DecidedLastCommit.Size()
|
||||
n += 1 + l + sovTypes(uint64(l))
|
||||
if len(m.ByzantineValidators) > 0 {
|
||||
for _, e := range m.ByzantineValidators {
|
||||
if len(m.Misbehavior) > 0 {
|
||||
for _, e := range m.Misbehavior {
|
||||
l = e.Size()
|
||||
n += 1 + l + sovTypes(uint64(l))
|
||||
}
|
||||
@@ -11139,7 +11138,7 @@ func (m *RequestPrepareProposal) Unmarshal(dAtA []byte) error {
|
||||
iNdEx = postIndex
|
||||
case 4:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field ByzantineValidators", wireType)
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Misbehavior", wireType)
|
||||
}
|
||||
var msglen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
@@ -11166,8 +11165,8 @@ func (m *RequestPrepareProposal) Unmarshal(dAtA []byte) error {
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.ByzantineValidators = append(m.ByzantineValidators, Misbehavior{})
|
||||
if err := m.ByzantineValidators[len(m.ByzantineValidators)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
|
||||
m.Misbehavior = append(m.Misbehavior, Misbehavior{})
|
||||
if err := m.Misbehavior[len(m.Misbehavior)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
@@ -11408,7 +11407,7 @@ func (m *RequestProcessProposal) Unmarshal(dAtA []byte) error {
|
||||
iNdEx = postIndex
|
||||
case 3:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field ByzantineValidators", wireType)
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Misbehavior", wireType)
|
||||
}
|
||||
var msglen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
@@ -11435,8 +11434,8 @@ func (m *RequestProcessProposal) Unmarshal(dAtA []byte) error {
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.ByzantineValidators = append(m.ByzantineValidators, Misbehavior{})
|
||||
if err := m.ByzantineValidators[len(m.ByzantineValidators)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
|
||||
m.Misbehavior = append(m.Misbehavior, Misbehavior{})
|
||||
if err := m.Misbehavior[len(m.Misbehavior)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
@@ -11985,7 +11984,7 @@ func (m *RequestFinalizeBlock) Unmarshal(dAtA []byte) error {
|
||||
iNdEx = postIndex
|
||||
case 3:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field ByzantineValidators", wireType)
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Misbehavior", wireType)
|
||||
}
|
||||
var msglen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
@@ -12012,8 +12011,8 @@ func (m *RequestFinalizeBlock) Unmarshal(dAtA []byte) error {
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.ByzantineValidators = append(m.ByzantineValidators, Misbehavior{})
|
||||
if err := m.ByzantineValidators[len(m.ByzantineValidators)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
|
||||
m.Misbehavior = append(m.Misbehavior, Misbehavior{})
|
||||
if err := m.Misbehavior[len(m.Misbehavior)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
|
||||
@@ -659,8 +659,8 @@ type P2PConfig struct { //nolint: maligned
|
||||
DialTimeout time.Duration `mapstructure:"dial-timeout"`
|
||||
|
||||
// Makes it possible to configure which queue backend the p2p
|
||||
// layer uses. Options are: "fifo" and "priority",
|
||||
// with the default being "priority".
|
||||
// layer uses. Options are: "fifo" and "simple-priority", and "priority",
|
||||
// with the default being "simple-priority".
|
||||
QueueType string `mapstructure:"queue-type"`
|
||||
}
|
||||
|
||||
@@ -685,7 +685,7 @@ func DefaultP2PConfig() *P2PConfig {
|
||||
PexReactor: true,
|
||||
HandshakeTimeout: 20 * time.Second,
|
||||
DialTimeout: 3 * time.Second,
|
||||
QueueType: "priority",
|
||||
QueueType: "simple-priority",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -282,7 +282,9 @@ pprof-laddr = "{{ .RPC.PprofListenAddress }}"
|
||||
#######################################################
|
||||
[p2p]
|
||||
|
||||
# Select the p2p internal queue
|
||||
# Select the p2p internal queue.
|
||||
# Options are: "fifo" and "simple-priority", and "priority",
|
||||
# with the default being "simple-priority".
|
||||
queue-type = "{{ .P2P.QueueType }}"
|
||||
|
||||
# Address to listen for incoming connections
|
||||
|
||||
@@ -208,6 +208,33 @@ Try running these commands:
|
||||
-> key.hex: 646566
|
||||
-> value: xyz
|
||||
-> value.hex: 78797A
|
||||
|
||||
> prepare_proposal "preparedef"
|
||||
-> code: OK
|
||||
-> log: Succeeded. Tx: def action: ADDED
|
||||
-> code: OK
|
||||
-> log: Succeeded. Tx: preparedef action: REMOVED
|
||||
|
||||
> process_proposal "def"
|
||||
-> code: OK
|
||||
-> status: ACCEPT
|
||||
|
||||
> process_proposal "preparedef"
|
||||
-> code: OK
|
||||
-> status: REJECT
|
||||
|
||||
> prepare_proposal
|
||||
|
||||
> process_proposal
|
||||
-> code: OK
|
||||
-> status: ACCEPT
|
||||
|
||||
> finalize_block
|
||||
-> code: OK
|
||||
-> data.hex: 0x0600000000000000
|
||||
|
||||
> commit
|
||||
-> code: OK
|
||||
```
|
||||
|
||||
Note that if we do `finalize_block "abc" ...` it will store `(abc, abc)`, but if
|
||||
|
||||
@@ -106,10 +106,10 @@ Next, use the `tendermint testnet` command to create four directories of config
|
||||
Before you can start the network, you'll need peers identifiers (IPs are not enough and can change). We'll refer to them as ID1, ID2, ID3, ID4.
|
||||
|
||||
```sh
|
||||
tendermint show_node_id --home ./mytestnet/node0
|
||||
tendermint show_node_id --home ./mytestnet/node1
|
||||
tendermint show_node_id --home ./mytestnet/node2
|
||||
tendermint show_node_id --home ./mytestnet/node3
|
||||
tendermint show-node-id --home ./mytestnet/node0
|
||||
tendermint show-node-id --home ./mytestnet/node1
|
||||
tendermint show-node-id --home ./mytestnet/node2
|
||||
tendermint show-node-id --home ./mytestnet/node3
|
||||
```
|
||||
|
||||
Finally, from each machine, run:
|
||||
|
||||
@@ -103,9 +103,9 @@ Another example of a cryptocurrency application built on Tendermint is
|
||||
to Tendermint, but is more opinionated about how the state is managed,
|
||||
and requires that all application behaviour runs in potentially many
|
||||
docker containers, modules it calls "chaincode". It uses an
|
||||
implementation of [PBFT](http://pmg.csail.mit.edu/papers/osdi99.pdf).
|
||||
implementation of [PBFT](http://pmg.csail.mit.edu/papers/osdi99.pdf)
|
||||
from a team at IBM that is augmented to handle potentially non-deterministic
|
||||
chaincode It is possible to implement this docker-based behaviour as a ABCI app
|
||||
chaincode. It is possible to implement this docker-based behaviour as a ABCI app
|
||||
in Tendermint, though extending Tendermint to handle non-determinism remains
|
||||
for future work.
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
## Changelog
|
||||
|
||||
- 2020-01-11: initialized
|
||||
- 2021-02-11: Migrate RFC to tendermint repo (Originally [RFC 004](https://github.com/tendermint/spec/pull/254))
|
||||
- 2022-02-11: Migrate RFC to tendermint repo (Originally [RFC 004](https://github.com/tendermint/spec/pull/254))
|
||||
|
||||
## Author(s)
|
||||
|
||||
|
||||
@@ -213,4 +213,4 @@ documentation](https://hub.cosmos.network/main/governance/submitting.html#sendin
|
||||
|
||||
If the application does not implement a way to update the consensus parameters
|
||||
programatically, then the application itself must be updated to do so. More information on updating
|
||||
the consensus parameters via ABCI can be found in the [FinalizeBlock documentation](https://github.com/tendermint/tendermint/blob/master/spec/abci++/abci++_methods_002_draft.md#finalizeblock).
|
||||
the consensus parameters via ABCI can be found in the [FinalizeBlock documentation](../../../spec/abci++/abci%2B%2B_methods.md#finalizeblock).
|
||||
|
||||
6
go.mod
6
go.mod
@@ -17,7 +17,7 @@ require (
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
|
||||
github.com/lib/pq v1.10.6
|
||||
github.com/libp2p/go-buffer-pool v0.0.2
|
||||
github.com/libp2p/go-buffer-pool v0.1.0
|
||||
github.com/mroth/weightedrand v0.4.1
|
||||
github.com/oasisprotocol/curve25519-voi v0.0.0-20210609091139-0a56a4bca00b
|
||||
github.com/ory/dockertest v3.3.5+incompatible
|
||||
@@ -32,7 +32,7 @@ require (
|
||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e
|
||||
golang.org/x/net v0.0.0-20220617184016-355a448f1bc9
|
||||
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29
|
||||
google.golang.org/grpc v1.47.0
|
||||
google.golang.org/grpc v1.48.0
|
||||
pgregory.net/rapid v0.4.7
|
||||
)
|
||||
|
||||
@@ -240,6 +240,6 @@ require (
|
||||
require (
|
||||
github.com/creachadair/tomledit v0.0.22
|
||||
github.com/prometheus/client_model v0.2.0
|
||||
github.com/prometheus/common v0.35.0
|
||||
github.com/prometheus/common v0.36.0
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca
|
||||
)
|
||||
|
||||
12
go.sum
12
go.sum
@@ -717,8 +717,8 @@ github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs=
|
||||
github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/libp2p/go-buffer-pool v0.0.2 h1:QNK2iAFa8gjAe1SPz6mHSMuCcjs+X1wlHzeOSqcmlfs=
|
||||
github.com/libp2p/go-buffer-pool v0.0.2/go.mod h1:MvaB6xw5vOrDl8rYZGLFdKAuk/hRoRZd1Vi32+RXyFM=
|
||||
github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8=
|
||||
github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg=
|
||||
github.com/lufeee/execinquery v1.0.0 h1:1XUTuLIVPDlFvUU3LXmmZwHDsolsxXnY67lzhpeqe0I=
|
||||
github.com/lufeee/execinquery v1.0.0/go.mod h1:EC7DrEKView09ocscGHC+apXMIaorh4xqSxS/dy8SbM=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
@@ -938,8 +938,8 @@ github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB8
|
||||
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
|
||||
github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
|
||||
github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
|
||||
github.com/prometheus/common v0.35.0 h1:Eyr+Pw2VymWejHqCugNaQXkAi6KayVNxaHeu6khmFBE=
|
||||
github.com/prometheus/common v0.35.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
|
||||
github.com/prometheus/common v0.36.0 h1:78hJTing+BLYLjhXE+Z2BubeEymH5Lr0/Mt8FKkxxYo=
|
||||
github.com/prometheus/common v0.36.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||
@@ -1815,8 +1815,8 @@ google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ5
|
||||
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
|
||||
google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
||||
google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
||||
google.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8=
|
||||
google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
||||
google.golang.org/grpc v1.48.0 h1:rQOsyJ/8+ufEDJd/Gdsz7HG220Mh9HAhFHRGnIjda0w=
|
||||
google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
|
||||
@@ -135,7 +135,7 @@ func (r *Reactor) OnStart(ctx context.Context) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.chCreator = func(context.Context, *conn.ChannelDescriptor) (*p2p.Channel, error) { return blockSyncCh, nil }
|
||||
r.chCreator = func(context.Context, *conn.ChannelDescriptor) (p2p.Channel, error) { return blockSyncCh, nil }
|
||||
|
||||
state, err := r.stateStore.Load()
|
||||
if err != nil {
|
||||
@@ -183,7 +183,7 @@ func (r *Reactor) OnStop() {
|
||||
|
||||
// respondToPeer loads a block and sends it to the requesting peer, if we have it.
|
||||
// Otherwise, we'll respond saying we do not have it.
|
||||
func (r *Reactor) respondToPeer(ctx context.Context, msg *bcproto.BlockRequest, peerID types.NodeID, blockSyncCh *p2p.Channel) error {
|
||||
func (r *Reactor) respondToPeer(ctx context.Context, msg *bcproto.BlockRequest, peerID types.NodeID, blockSyncCh p2p.Channel) error {
|
||||
block := r.store.LoadBlock(msg.Height)
|
||||
if block == nil {
|
||||
r.logger.Info("peer requesting a block we do not have", "peer", peerID, "height", msg.Height)
|
||||
@@ -223,7 +223,7 @@ func (r *Reactor) respondToPeer(ctx context.Context, msg *bcproto.BlockRequest,
|
||||
// handleMessage handles an Envelope sent from a peer on a specific p2p Channel.
|
||||
// It will handle errors and any possible panics gracefully. A caller can handle
|
||||
// any error returned by sending a PeerError on the respective channel.
|
||||
func (r *Reactor) handleMessage(ctx context.Context, envelope *p2p.Envelope, blockSyncCh *p2p.Channel) (err error) {
|
||||
func (r *Reactor) handleMessage(ctx context.Context, envelope *p2p.Envelope, blockSyncCh p2p.Channel) (err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = fmt.Errorf("panic in processing message: %v", e)
|
||||
@@ -298,7 +298,7 @@ func (r *Reactor) handleMessage(ctx context.Context, envelope *p2p.Envelope, blo
|
||||
// message execution will result in a PeerError being sent on the BlockSyncChannel.
|
||||
// When the reactor is stopped, we will catch the signal and close the p2p Channel
|
||||
// gracefully.
|
||||
func (r *Reactor) processBlockSyncCh(ctx context.Context, blockSyncCh *p2p.Channel) {
|
||||
func (r *Reactor) processBlockSyncCh(ctx context.Context, blockSyncCh p2p.Channel) {
|
||||
iter := blockSyncCh.Receive(ctx)
|
||||
for iter.Next(ctx) {
|
||||
envelope := iter.Envelope()
|
||||
@@ -319,7 +319,7 @@ func (r *Reactor) processBlockSyncCh(ctx context.Context, blockSyncCh *p2p.Chann
|
||||
}
|
||||
|
||||
// processPeerUpdate processes a PeerUpdate.
|
||||
func (r *Reactor) processPeerUpdate(ctx context.Context, peerUpdate p2p.PeerUpdate, blockSyncCh *p2p.Channel) {
|
||||
func (r *Reactor) processPeerUpdate(ctx context.Context, peerUpdate p2p.PeerUpdate, blockSyncCh p2p.Channel) {
|
||||
r.logger.Debug("received peer update", "peer", peerUpdate.NodeID, "status", peerUpdate.Status)
|
||||
|
||||
// XXX: Pool#RedoRequest can sometimes give us an empty peer.
|
||||
@@ -354,7 +354,7 @@ func (r *Reactor) processPeerUpdate(ctx context.Context, peerUpdate p2p.PeerUpda
|
||||
// processPeerUpdates initiates a blocking process where we listen for and handle
|
||||
// PeerUpdate messages. When the reactor is stopped, we will catch the signal and
|
||||
// close the p2p PeerUpdatesCh gracefully.
|
||||
func (r *Reactor) processPeerUpdates(ctx context.Context, peerUpdates *p2p.PeerUpdates, blockSyncCh *p2p.Channel) {
|
||||
func (r *Reactor) processPeerUpdates(ctx context.Context, peerUpdates *p2p.PeerUpdates, blockSyncCh p2p.Channel) {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
@@ -396,7 +396,7 @@ func (r *Reactor) SwitchToBlockSync(ctx context.Context, state sm.State) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Reactor) requestRoutine(ctx context.Context, blockSyncCh *p2p.Channel) {
|
||||
func (r *Reactor) requestRoutine(ctx context.Context, blockSyncCh p2p.Channel) {
|
||||
statusUpdateTicker := time.NewTicker(statusUpdateIntervalSeconds * time.Second)
|
||||
defer statusUpdateTicker.Stop()
|
||||
|
||||
@@ -438,7 +438,7 @@ func (r *Reactor) requestRoutine(ctx context.Context, blockSyncCh *p2p.Channel)
|
||||
// do.
|
||||
//
|
||||
// NOTE: Don't sleep in the FOR_LOOP or otherwise slow it down!
|
||||
func (r *Reactor) poolRoutine(ctx context.Context, stateSynced bool, blockSyncCh *p2p.Channel) {
|
||||
func (r *Reactor) poolRoutine(ctx context.Context, stateSynced bool, blockSyncCh p2p.Channel) {
|
||||
var (
|
||||
trySyncTicker = time.NewTicker(trySyncIntervalMS * time.Millisecond)
|
||||
switchToConsensusTicker = time.NewTicker(switchToConsensusIntervalSeconds * time.Second)
|
||||
|
||||
@@ -37,7 +37,7 @@ type reactorTestSuite struct {
|
||||
reactors map[types.NodeID]*Reactor
|
||||
app map[types.NodeID]abciclient.Client
|
||||
|
||||
blockSyncChannels map[types.NodeID]*p2p.Channel
|
||||
blockSyncChannels map[types.NodeID]p2p.Channel
|
||||
peerChans map[types.NodeID]chan p2p.PeerUpdate
|
||||
peerUpdates map[types.NodeID]*p2p.PeerUpdates
|
||||
}
|
||||
@@ -64,7 +64,7 @@ func setup(
|
||||
nodes: make([]types.NodeID, 0, numNodes),
|
||||
reactors: make(map[types.NodeID]*Reactor, numNodes),
|
||||
app: make(map[types.NodeID]abciclient.Client, numNodes),
|
||||
blockSyncChannels: make(map[types.NodeID]*p2p.Channel, numNodes),
|
||||
blockSyncChannels: make(map[types.NodeID]p2p.Channel, numNodes),
|
||||
peerChans: make(map[types.NodeID]chan p2p.PeerUpdate, numNodes),
|
||||
peerUpdates: make(map[types.NodeID]*p2p.PeerUpdates, numNodes),
|
||||
}
|
||||
@@ -177,7 +177,7 @@ func (rts *reactorTestSuite) addNode(
|
||||
rts.peerUpdates[nodeID] = p2p.NewPeerUpdates(rts.peerChans[nodeID], 1)
|
||||
rts.network.Nodes[nodeID].PeerManager.Register(ctx, rts.peerUpdates[nodeID])
|
||||
|
||||
chCreator := func(ctx context.Context, chdesc *p2p.ChannelDescriptor) (*p2p.Channel, error) {
|
||||
chCreator := func(ctx context.Context, chdesc *p2p.ChannelDescriptor) (p2p.Channel, error) {
|
||||
return rts.blockSyncChannels[nodeID], nil
|
||||
}
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@ func invalidDoPrevoteFunc(
|
||||
round int32,
|
||||
cs *State,
|
||||
r *Reactor,
|
||||
voteCh *p2p.Channel,
|
||||
voteCh p2p.Channel,
|
||||
pv types.PrivValidator,
|
||||
) {
|
||||
// routine to:
|
||||
|
||||
@@ -165,10 +165,10 @@ func NewReactor(
|
||||
}
|
||||
|
||||
type channelBundle struct {
|
||||
state *p2p.Channel
|
||||
data *p2p.Channel
|
||||
vote *p2p.Channel
|
||||
votSet *p2p.Channel
|
||||
state p2p.Channel
|
||||
data p2p.Channel
|
||||
vote p2p.Channel
|
||||
votSet p2p.Channel
|
||||
}
|
||||
|
||||
// OnStart starts separate go routines for each p2p Channel and listens for
|
||||
@@ -310,14 +310,14 @@ func (r *Reactor) GetPeerState(peerID types.NodeID) (*PeerState, bool) {
|
||||
return ps, ok
|
||||
}
|
||||
|
||||
func (r *Reactor) broadcastNewRoundStepMessage(ctx context.Context, rs *cstypes.RoundState, stateCh *p2p.Channel) error {
|
||||
func (r *Reactor) broadcastNewRoundStepMessage(ctx context.Context, rs *cstypes.RoundState, stateCh p2p.Channel) error {
|
||||
return stateCh.Send(ctx, p2p.Envelope{
|
||||
Broadcast: true,
|
||||
Message: makeRoundStepMessage(rs),
|
||||
})
|
||||
}
|
||||
|
||||
func (r *Reactor) broadcastNewValidBlockMessage(ctx context.Context, rs *cstypes.RoundState, stateCh *p2p.Channel) error {
|
||||
func (r *Reactor) broadcastNewValidBlockMessage(ctx context.Context, rs *cstypes.RoundState, stateCh p2p.Channel) error {
|
||||
psHeader := rs.ProposalBlockParts.Header()
|
||||
return stateCh.Send(ctx, p2p.Envelope{
|
||||
Broadcast: true,
|
||||
@@ -331,7 +331,7 @@ func (r *Reactor) broadcastNewValidBlockMessage(ctx context.Context, rs *cstypes
|
||||
})
|
||||
}
|
||||
|
||||
func (r *Reactor) broadcastHasVoteMessage(ctx context.Context, vote *types.Vote, stateCh *p2p.Channel) error {
|
||||
func (r *Reactor) broadcastHasVoteMessage(ctx context.Context, vote *types.Vote, stateCh p2p.Channel) error {
|
||||
return stateCh.Send(ctx, p2p.Envelope{
|
||||
Broadcast: true,
|
||||
Message: &tmcons.HasVote{
|
||||
@@ -346,7 +346,7 @@ func (r *Reactor) broadcastHasVoteMessage(ctx context.Context, vote *types.Vote,
|
||||
// subscribeToBroadcastEvents subscribes for new round steps and votes using the
|
||||
// internal pubsub defined in the consensus state to broadcast them to peers
|
||||
// upon receiving.
|
||||
func (r *Reactor) subscribeToBroadcastEvents(ctx context.Context, stateCh *p2p.Channel) {
|
||||
func (r *Reactor) subscribeToBroadcastEvents(ctx context.Context, stateCh p2p.Channel) {
|
||||
onStopCh := r.state.getOnStopCh()
|
||||
|
||||
err := r.state.evsw.AddListenerForEvent(
|
||||
@@ -403,7 +403,7 @@ func makeRoundStepMessage(rs *cstypes.RoundState) *tmcons.NewRoundStep {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Reactor) sendNewRoundStepMessage(ctx context.Context, peerID types.NodeID, stateCh *p2p.Channel) error {
|
||||
func (r *Reactor) sendNewRoundStepMessage(ctx context.Context, peerID types.NodeID, stateCh p2p.Channel) error {
|
||||
return stateCh.Send(ctx, p2p.Envelope{
|
||||
To: peerID,
|
||||
Message: makeRoundStepMessage(r.getRoundState()),
|
||||
@@ -433,7 +433,7 @@ func (r *Reactor) getRoundState() *cstypes.RoundState {
|
||||
return r.rs
|
||||
}
|
||||
|
||||
func (r *Reactor) gossipDataForCatchup(ctx context.Context, rs *cstypes.RoundState, prs *cstypes.PeerRoundState, ps *PeerState, dataCh *p2p.Channel) {
|
||||
func (r *Reactor) gossipDataForCatchup(ctx context.Context, rs *cstypes.RoundState, prs *cstypes.PeerRoundState, ps *PeerState, dataCh p2p.Channel) {
|
||||
logger := r.logger.With("height", prs.Height).With("peer", ps.peerID)
|
||||
|
||||
if index, ok := prs.ProposalBlockParts.Not().PickRandom(); ok {
|
||||
@@ -497,7 +497,7 @@ func (r *Reactor) gossipDataForCatchup(ctx context.Context, rs *cstypes.RoundSta
|
||||
time.Sleep(r.state.config.PeerGossipSleepDuration)
|
||||
}
|
||||
|
||||
func (r *Reactor) gossipDataRoutine(ctx context.Context, ps *PeerState, dataCh *p2p.Channel) {
|
||||
func (r *Reactor) gossipDataRoutine(ctx context.Context, ps *PeerState, dataCh p2p.Channel) {
|
||||
logger := r.logger.With("peer", ps.peerID)
|
||||
|
||||
timer := time.NewTimer(0)
|
||||
@@ -632,7 +632,7 @@ OUTER_LOOP:
|
||||
|
||||
// pickSendVote picks a vote and sends it to the peer. It will return true if
|
||||
// there is a vote to send and false otherwise.
|
||||
func (r *Reactor) pickSendVote(ctx context.Context, ps *PeerState, votes types.VoteSetReader, voteCh *p2p.Channel) (bool, error) {
|
||||
func (r *Reactor) pickSendVote(ctx context.Context, ps *PeerState, votes types.VoteSetReader, voteCh p2p.Channel) (bool, error) {
|
||||
vote, ok := ps.PickVoteToSend(votes)
|
||||
if !ok {
|
||||
return false, nil
|
||||
@@ -660,7 +660,7 @@ func (r *Reactor) gossipVotesForHeight(
|
||||
rs *cstypes.RoundState,
|
||||
prs *cstypes.PeerRoundState,
|
||||
ps *PeerState,
|
||||
voteCh *p2p.Channel,
|
||||
voteCh p2p.Channel,
|
||||
) (bool, error) {
|
||||
logger := r.logger.With("height", prs.Height).With("peer", ps.peerID)
|
||||
|
||||
@@ -732,7 +732,7 @@ func (r *Reactor) gossipVotesForHeight(
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (r *Reactor) gossipVotesRoutine(ctx context.Context, ps *PeerState, voteCh *p2p.Channel) {
|
||||
func (r *Reactor) gossipVotesRoutine(ctx context.Context, ps *PeerState, voteCh p2p.Channel) {
|
||||
logger := r.logger.With("peer", ps.peerID)
|
||||
|
||||
timer := time.NewTimer(0)
|
||||
@@ -804,7 +804,7 @@ func (r *Reactor) gossipVotesRoutine(ctx context.Context, ps *PeerState, voteCh
|
||||
|
||||
// NOTE: `queryMaj23Routine` has a simple crude design since it only comes
|
||||
// into play for liveness when there's a signature DDoS attack happening.
|
||||
func (r *Reactor) queryMaj23Routine(ctx context.Context, ps *PeerState, stateCh *p2p.Channel) {
|
||||
func (r *Reactor) queryMaj23Routine(ctx context.Context, ps *PeerState, stateCh p2p.Channel) {
|
||||
timer := time.NewTimer(0)
|
||||
defer timer.Stop()
|
||||
|
||||
@@ -1015,7 +1015,7 @@ func (r *Reactor) processPeerUpdate(ctx context.Context, peerUpdate p2p.PeerUpda
|
||||
// If we fail to find the peer state for the envelope sender, we perform a no-op
|
||||
// and return. This can happen when we process the envelope after the peer is
|
||||
// removed.
|
||||
func (r *Reactor) handleStateMessage(ctx context.Context, envelope *p2p.Envelope, msgI Message, voteSetCh *p2p.Channel) error {
|
||||
func (r *Reactor) handleStateMessage(ctx context.Context, envelope *p2p.Envelope, msgI Message, voteSetCh p2p.Channel) error {
|
||||
ps, ok := r.GetPeerState(envelope.From)
|
||||
if !ok || ps == nil {
|
||||
r.logger.Debug("failed to find peer state", "peer", envelope.From, "ch_id", "StateChannel")
|
||||
|
||||
@@ -46,10 +46,10 @@ type reactorTestSuite struct {
|
||||
reactors map[types.NodeID]*Reactor
|
||||
subs map[types.NodeID]eventbus.Subscription
|
||||
blocksyncSubs map[types.NodeID]eventbus.Subscription
|
||||
stateChannels map[types.NodeID]*p2p.Channel
|
||||
dataChannels map[types.NodeID]*p2p.Channel
|
||||
voteChannels map[types.NodeID]*p2p.Channel
|
||||
voteSetBitsChannels map[types.NodeID]*p2p.Channel
|
||||
stateChannels map[types.NodeID]p2p.Channel
|
||||
dataChannels map[types.NodeID]p2p.Channel
|
||||
voteChannels map[types.NodeID]p2p.Channel
|
||||
voteSetBitsChannels map[types.NodeID]p2p.Channel
|
||||
}
|
||||
|
||||
func chDesc(chID p2p.ChannelID, size int) *p2p.ChannelDescriptor {
|
||||
@@ -86,7 +86,7 @@ func setup(
|
||||
t.Cleanup(cancel)
|
||||
|
||||
chCreator := func(nodeID types.NodeID) p2p.ChannelCreator {
|
||||
return func(ctx context.Context, desc *p2p.ChannelDescriptor) (*p2p.Channel, error) {
|
||||
return func(ctx context.Context, desc *p2p.ChannelDescriptor) (p2p.Channel, error) {
|
||||
switch desc.ID {
|
||||
case StateChannel:
|
||||
return rts.stateChannels[nodeID], nil
|
||||
|
||||
@@ -159,7 +159,7 @@ func (r *Reactor) handleMessage(ctx context.Context, envelope *p2p.Envelope) (er
|
||||
|
||||
// processEvidenceCh implements a blocking event loop where we listen for p2p
|
||||
// Envelope messages from the evidenceCh.
|
||||
func (r *Reactor) processEvidenceCh(ctx context.Context, evidenceCh *p2p.Channel) {
|
||||
func (r *Reactor) processEvidenceCh(ctx context.Context, evidenceCh p2p.Channel) {
|
||||
iter := evidenceCh.Receive(ctx)
|
||||
for iter.Next(ctx) {
|
||||
envelope := iter.Envelope()
|
||||
@@ -186,7 +186,7 @@ func (r *Reactor) processEvidenceCh(ctx context.Context, evidenceCh *p2p.Channel
|
||||
// connects/disconnects frequently from the broadcasting peer(s).
|
||||
//
|
||||
// REF: https://github.com/tendermint/tendermint/issues/4727
|
||||
func (r *Reactor) processPeerUpdate(ctx context.Context, peerUpdate p2p.PeerUpdate, evidenceCh *p2p.Channel) {
|
||||
func (r *Reactor) processPeerUpdate(ctx context.Context, peerUpdate p2p.PeerUpdate, evidenceCh p2p.Channel) {
|
||||
r.logger.Debug("received peer update", "peer", peerUpdate.NodeID, "status", peerUpdate.Status)
|
||||
|
||||
r.mtx.Lock()
|
||||
@@ -227,7 +227,7 @@ func (r *Reactor) processPeerUpdate(ctx context.Context, peerUpdate p2p.PeerUpda
|
||||
// processPeerUpdates initiates a blocking process where we listen for and handle
|
||||
// PeerUpdate messages. When the reactor is stopped, we will catch the signal and
|
||||
// close the p2p PeerUpdatesCh gracefully.
|
||||
func (r *Reactor) processPeerUpdates(ctx context.Context, peerUpdates *p2p.PeerUpdates, evidenceCh *p2p.Channel) {
|
||||
func (r *Reactor) processPeerUpdates(ctx context.Context, peerUpdates *p2p.PeerUpdates, evidenceCh p2p.Channel) {
|
||||
for {
|
||||
select {
|
||||
case peerUpdate := <-peerUpdates.Updates():
|
||||
@@ -249,7 +249,7 @@ func (r *Reactor) processPeerUpdates(ctx context.Context, peerUpdates *p2p.PeerU
|
||||
// that the peer has already received or may not be ready for.
|
||||
//
|
||||
// REF: https://github.com/tendermint/tendermint/issues/4727
|
||||
func (r *Reactor) broadcastEvidenceLoop(ctx context.Context, peerID types.NodeID, evidenceCh *p2p.Channel) {
|
||||
func (r *Reactor) broadcastEvidenceLoop(ctx context.Context, peerID types.NodeID, evidenceCh p2p.Channel) {
|
||||
var next *clist.CElement
|
||||
|
||||
defer func() {
|
||||
|
||||
@@ -38,7 +38,7 @@ type reactorTestSuite struct {
|
||||
logger log.Logger
|
||||
reactors map[types.NodeID]*evidence.Reactor
|
||||
pools map[types.NodeID]*evidence.Pool
|
||||
evidenceChannels map[types.NodeID]*p2p.Channel
|
||||
evidenceChannels map[types.NodeID]p2p.Channel
|
||||
peerUpdates map[types.NodeID]*p2p.PeerUpdates
|
||||
peerChans map[types.NodeID]chan p2p.PeerUpdate
|
||||
nodes []*p2ptest.Node
|
||||
@@ -96,7 +96,7 @@ func setup(ctx context.Context, t *testing.T, stateStores []sm.Store) *reactorTe
|
||||
rts.network.Nodes[nodeID].PeerManager.Register(ctx, pu)
|
||||
rts.nodes = append(rts.nodes, rts.network.Nodes[nodeID])
|
||||
|
||||
chCreator := func(ctx context.Context, chdesc *p2p.ChannelDescriptor) (*p2p.Channel, error) {
|
||||
chCreator := func(ctx context.Context, chdesc *p2p.ChannelDescriptor) (p2p.Channel, error) {
|
||||
return rts.evidenceChannels[nodeID], nil
|
||||
}
|
||||
|
||||
|
||||
@@ -540,7 +540,7 @@ func (txmp *TxMempool) initTxCallback(wtx *WrappedTx, res *abci.ResponseCheckTx,
|
||||
)
|
||||
if len(evictTxs) == 0 {
|
||||
// No room for the new incoming transaction so we just remove it from
|
||||
// the cache.
|
||||
// the cache and return an error to the user.
|
||||
txmp.cache.Remove(wtx.tx)
|
||||
txmp.logger.Error(
|
||||
"rejected incoming good transaction; mempool full",
|
||||
@@ -548,7 +548,7 @@ func (txmp *TxMempool) initTxCallback(wtx *WrappedTx, res *abci.ResponseCheckTx,
|
||||
"err", err.Error(),
|
||||
)
|
||||
txmp.metrics.RejectedTxs.Add(1)
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
// evict an existing transaction(s)
|
||||
|
||||
@@ -194,7 +194,7 @@ func (r *Reactor) handleMessage(ctx context.Context, envelope *p2p.Envelope) (er
|
||||
|
||||
// processMempoolCh implements a blocking event loop where we listen for p2p
|
||||
// Envelope messages from the mempoolCh.
|
||||
func (r *Reactor) processMempoolCh(ctx context.Context, mempoolCh *p2p.Channel) {
|
||||
func (r *Reactor) processMempoolCh(ctx context.Context, mempoolCh p2p.Channel) {
|
||||
iter := mempoolCh.Receive(ctx)
|
||||
for iter.Next(ctx) {
|
||||
envelope := iter.Envelope()
|
||||
@@ -215,7 +215,7 @@ func (r *Reactor) processMempoolCh(ctx context.Context, mempoolCh *p2p.Channel)
|
||||
// goroutine or not. If not, we start one for the newly added peer. For down or
|
||||
// removed peers, we remove the peer from the mempool peer ID set and signal to
|
||||
// stop the tx broadcasting goroutine.
|
||||
func (r *Reactor) processPeerUpdate(ctx context.Context, peerUpdate p2p.PeerUpdate, mempoolCh *p2p.Channel) {
|
||||
func (r *Reactor) processPeerUpdate(ctx context.Context, peerUpdate p2p.PeerUpdate, mempoolCh p2p.Channel) {
|
||||
r.logger.Debug("received peer update", "peer", peerUpdate.NodeID, "status", peerUpdate.Status)
|
||||
|
||||
r.mtx.Lock()
|
||||
@@ -264,7 +264,7 @@ func (r *Reactor) processPeerUpdate(ctx context.Context, peerUpdate p2p.PeerUpda
|
||||
// processPeerUpdates initiates a blocking process where we listen for and handle
|
||||
// PeerUpdate messages. When the reactor is stopped, we will catch the signal and
|
||||
// close the p2p PeerUpdatesCh gracefully.
|
||||
func (r *Reactor) processPeerUpdates(ctx context.Context, peerUpdates *p2p.PeerUpdates, mempoolCh *p2p.Channel) {
|
||||
func (r *Reactor) processPeerUpdates(ctx context.Context, peerUpdates *p2p.PeerUpdates, mempoolCh p2p.Channel) {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
@@ -275,7 +275,7 @@ func (r *Reactor) processPeerUpdates(ctx context.Context, peerUpdates *p2p.PeerU
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Reactor) broadcastTxRoutine(ctx context.Context, peerID types.NodeID, mempoolCh *p2p.Channel) {
|
||||
func (r *Reactor) broadcastTxRoutine(ctx context.Context, peerID types.NodeID, mempoolCh p2p.Channel) {
|
||||
peerMempoolID := r.ids.GetForPeer(peerID)
|
||||
var nextGossipTx *clist.CElement
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ type reactorTestSuite struct {
|
||||
logger log.Logger
|
||||
|
||||
reactors map[types.NodeID]*Reactor
|
||||
mempoolChannels map[types.NodeID]*p2p.Channel
|
||||
mempoolChannels map[types.NodeID]p2p.Channel
|
||||
mempools map[types.NodeID]*TxMempool
|
||||
kvstores map[types.NodeID]*kvstore.Application
|
||||
|
||||
@@ -51,7 +51,7 @@ func setupReactors(ctx context.Context, t *testing.T, logger log.Logger, numNode
|
||||
logger: log.NewNopLogger().With("testCase", t.Name()),
|
||||
network: p2ptest.MakeNetwork(ctx, t, p2ptest.NetworkOptions{NumNodes: numNodes}),
|
||||
reactors: make(map[types.NodeID]*Reactor, numNodes),
|
||||
mempoolChannels: make(map[types.NodeID]*p2p.Channel, numNodes),
|
||||
mempoolChannels: make(map[types.NodeID]p2p.Channel, numNodes),
|
||||
mempools: make(map[types.NodeID]*TxMempool, numNodes),
|
||||
kvstores: make(map[types.NodeID]*kvstore.Application, numNodes),
|
||||
peerChans: make(map[types.NodeID]chan p2p.PeerUpdate, numNodes),
|
||||
@@ -75,7 +75,7 @@ func setupReactors(ctx context.Context, t *testing.T, logger log.Logger, numNode
|
||||
rts.peerUpdates[nodeID] = p2p.NewPeerUpdates(rts.peerChans[nodeID], 1)
|
||||
rts.network.Nodes[nodeID].PeerManager.Register(ctx, rts.peerUpdates[nodeID])
|
||||
|
||||
chCreator := func(ctx context.Context, chDesc *p2p.ChannelDescriptor) (*p2p.Channel, error) {
|
||||
chCreator := func(ctx context.Context, chDesc *p2p.ChannelDescriptor) (p2p.Channel, error) {
|
||||
return rts.mempoolChannels[nodeID], nil
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package p2p
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
@@ -19,6 +20,10 @@ type Envelope struct {
|
||||
ChannelID ChannelID
|
||||
}
|
||||
|
||||
func (e Envelope) IsZero() bool {
|
||||
return e.From == "" && e.To == "" && e.Message == nil
|
||||
}
|
||||
|
||||
// Wrapper is a Protobuf message that can contain a variety of inner messages
|
||||
// (e.g. via oneof fields). If a Channel's message type implements Wrapper, the
|
||||
// Router will automatically wrap outbound messages and unwrap inbound messages,
|
||||
@@ -33,6 +38,16 @@ type Wrapper interface {
|
||||
Unwrap() (proto.Message, error)
|
||||
}
|
||||
|
||||
type Channel interface {
|
||||
fmt.Stringer
|
||||
|
||||
Err() error
|
||||
|
||||
Send(context.Context, Envelope) error
|
||||
SendError(context.Context, PeerError) error
|
||||
Receive(context.Context) *ChannelIterator
|
||||
}
|
||||
|
||||
// PeerError is a peer error reported via Channel.Error.
|
||||
//
|
||||
// FIXME: This currently just disconnects the peer, which is too simplistic.
|
||||
@@ -52,9 +67,9 @@ type PeerError struct {
|
||||
func (pe PeerError) Error() string { return fmt.Sprintf("peer=%q: %s", pe.NodeID, pe.Err.Error()) }
|
||||
func (pe PeerError) Unwrap() error { return pe.Err }
|
||||
|
||||
// Channel is a bidirectional channel to exchange Protobuf messages with peers.
|
||||
// legacyChannel is a bidirectional channel to exchange Protobuf messages with peers.
|
||||
// Each message is wrapped in an Envelope to specify its sender and receiver.
|
||||
type Channel struct {
|
||||
type legacyChannel struct {
|
||||
ID ChannelID
|
||||
inCh <-chan Envelope // inbound messages (peers to reactors)
|
||||
outCh chan<- Envelope // outbound messages (reactors to peers)
|
||||
@@ -65,9 +80,10 @@ type Channel struct {
|
||||
|
||||
// NewChannel creates a new channel. It is primarily for internal and test
|
||||
// use, reactors should use Router.OpenChannel().
|
||||
func NewChannel(id ChannelID, inCh <-chan Envelope, outCh chan<- Envelope, errCh chan<- PeerError) *Channel {
|
||||
return &Channel{
|
||||
func NewChannel(id ChannelID, name string, inCh <-chan Envelope, outCh chan<- Envelope, errCh chan<- PeerError) Channel {
|
||||
return &legacyChannel{
|
||||
ID: id,
|
||||
name: name,
|
||||
inCh: inCh,
|
||||
outCh: outCh,
|
||||
errCh: errCh,
|
||||
@@ -76,7 +92,7 @@ func NewChannel(id ChannelID, inCh <-chan Envelope, outCh chan<- Envelope, errCh
|
||||
|
||||
// Send blocks until the envelope has been sent, or until ctx ends.
|
||||
// An error only occurs if the context ends before the send completes.
|
||||
func (ch *Channel) Send(ctx context.Context, envelope Envelope) error {
|
||||
func (ch *legacyChannel) Send(ctx context.Context, envelope Envelope) error {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
@@ -85,9 +101,15 @@ func (ch *Channel) Send(ctx context.Context, envelope Envelope) error {
|
||||
}
|
||||
}
|
||||
|
||||
func (ch *legacyChannel) Err() error { return nil }
|
||||
|
||||
// SendError blocks until the given error has been sent, or ctx ends.
|
||||
// An error only occurs if the context ends before the send completes.
|
||||
func (ch *Channel) SendError(ctx context.Context, pe PeerError) error {
|
||||
func (ch *legacyChannel) SendError(ctx context.Context, pe PeerError) error {
|
||||
if errors.Is(pe.Err, context.Canceled) || errors.Is(pe.Err, context.DeadlineExceeded) {
|
||||
return nil
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
@@ -96,18 +118,29 @@ func (ch *Channel) SendError(ctx context.Context, pe PeerError) error {
|
||||
}
|
||||
}
|
||||
|
||||
func (ch *Channel) String() string { return fmt.Sprintf("p2p.Channel<%d:%s>", ch.ID, ch.name) }
|
||||
func (ch *legacyChannel) String() string { return fmt.Sprintf("p2p.Channel<%d:%s>", ch.ID, ch.name) }
|
||||
|
||||
// Receive returns a new unbuffered iterator to receive messages from ch.
|
||||
// The iterator runs until ctx ends.
|
||||
func (ch *Channel) Receive(ctx context.Context) *ChannelIterator {
|
||||
func (ch *legacyChannel) Receive(ctx context.Context) *ChannelIterator {
|
||||
iter := &ChannelIterator{
|
||||
pipe: make(chan Envelope), // unbuffered
|
||||
}
|
||||
go func() {
|
||||
go func(pipe chan<- Envelope) {
|
||||
defer close(iter.pipe)
|
||||
iteratorWorker(ctx, ch, iter.pipe)
|
||||
}()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case envelope := <-ch.inCh:
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case pipe <- envelope:
|
||||
}
|
||||
}
|
||||
}
|
||||
}(iter.pipe)
|
||||
return iter
|
||||
}
|
||||
|
||||
@@ -122,21 +155,6 @@ type ChannelIterator struct {
|
||||
current *Envelope
|
||||
}
|
||||
|
||||
func iteratorWorker(ctx context.Context, ch *Channel, pipe chan Envelope) {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case envelope := <-ch.inCh:
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case pipe <- envelope:
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Next returns true when the Envelope value has advanced, and false
|
||||
// when the context is canceled or iteration should stop. If an iterator has returned false,
|
||||
// it will never return true again.
|
||||
@@ -175,7 +193,7 @@ func (iter *ChannelIterator) Envelope() *Envelope { return iter.current }
|
||||
//
|
||||
// This allows the caller to consume messages from multiple channels
|
||||
// without needing to manage the concurrency separately.
|
||||
func MergedChannelIterator(ctx context.Context, chs ...*Channel) *ChannelIterator {
|
||||
func MergedChannelIterator(ctx context.Context, chs ...Channel) *ChannelIterator {
|
||||
iter := &ChannelIterator{
|
||||
pipe: make(chan Envelope), // unbuffered
|
||||
}
|
||||
@@ -183,10 +201,17 @@ func MergedChannelIterator(ctx context.Context, chs ...*Channel) *ChannelIterato
|
||||
|
||||
for _, ch := range chs {
|
||||
wg.Add(1)
|
||||
go func(ch *Channel) {
|
||||
go func(ch Channel, pipe chan<- Envelope) {
|
||||
defer wg.Done()
|
||||
iteratorWorker(ctx, ch, iter.pipe)
|
||||
}(ch)
|
||||
iter := ch.Receive(ctx)
|
||||
for iter.Next(ctx) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case pipe <- *iter.Envelope():
|
||||
}
|
||||
}
|
||||
}(ch, iter.pipe)
|
||||
}
|
||||
|
||||
done := make(chan struct{})
|
||||
|
||||
@@ -16,13 +16,13 @@ type channelInternal struct {
|
||||
Error chan PeerError
|
||||
}
|
||||
|
||||
func testChannel(size int) (*channelInternal, *Channel) {
|
||||
func testChannel(size int) (*channelInternal, *legacyChannel) {
|
||||
in := &channelInternal{
|
||||
In: make(chan Envelope, size),
|
||||
Out: make(chan Envelope, size),
|
||||
Error: make(chan PeerError, size),
|
||||
}
|
||||
ch := &Channel{
|
||||
ch := &legacyChannel{
|
||||
inCh: in.In,
|
||||
outCh: in.Out,
|
||||
errCh: in.Error,
|
||||
|
||||
@@ -146,8 +146,8 @@ func (n *Network) MakeChannels(
|
||||
ctx context.Context,
|
||||
t *testing.T,
|
||||
chDesc *p2p.ChannelDescriptor,
|
||||
) map[types.NodeID]*p2p.Channel {
|
||||
channels := map[types.NodeID]*p2p.Channel{}
|
||||
) map[types.NodeID]p2p.Channel {
|
||||
channels := map[types.NodeID]p2p.Channel{}
|
||||
for _, node := range n.Nodes {
|
||||
channels[node.NodeID] = node.MakeChannel(ctx, t, chDesc)
|
||||
}
|
||||
@@ -161,8 +161,8 @@ func (n *Network) MakeChannelsNoCleanup(
|
||||
ctx context.Context,
|
||||
t *testing.T,
|
||||
chDesc *p2p.ChannelDescriptor,
|
||||
) map[types.NodeID]*p2p.Channel {
|
||||
channels := map[types.NodeID]*p2p.Channel{}
|
||||
) map[types.NodeID]p2p.Channel {
|
||||
channels := map[types.NodeID]p2p.Channel{}
|
||||
for _, node := range n.Nodes {
|
||||
channels[node.NodeID] = node.MakeChannelNoCleanup(ctx, t, chDesc)
|
||||
}
|
||||
@@ -304,7 +304,7 @@ func (n *Node) MakeChannel(
|
||||
ctx context.Context,
|
||||
t *testing.T,
|
||||
chDesc *p2p.ChannelDescriptor,
|
||||
) *p2p.Channel {
|
||||
) p2p.Channel {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
channel, err := n.Router.OpenChannel(ctx, chDesc)
|
||||
require.NoError(t, err)
|
||||
@@ -321,7 +321,7 @@ func (n *Node) MakeChannelNoCleanup(
|
||||
ctx context.Context,
|
||||
t *testing.T,
|
||||
chDesc *p2p.ChannelDescriptor,
|
||||
) *p2p.Channel {
|
||||
) p2p.Channel {
|
||||
channel, err := n.Router.OpenChannel(ctx, chDesc)
|
||||
require.NoError(t, err)
|
||||
return channel
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
)
|
||||
|
||||
// RequireEmpty requires that the given channel is empty.
|
||||
func RequireEmpty(ctx context.Context, t *testing.T, channels ...*p2p.Channel) {
|
||||
func RequireEmpty(ctx context.Context, t *testing.T, channels ...p2p.Channel) {
|
||||
t.Helper()
|
||||
|
||||
ctx, cancel := context.WithTimeout(ctx, 10*time.Millisecond)
|
||||
@@ -32,7 +32,7 @@ func RequireEmpty(ctx context.Context, t *testing.T, channels ...*p2p.Channel) {
|
||||
}
|
||||
|
||||
// RequireReceive requires that the given envelope is received on the channel.
|
||||
func RequireReceive(ctx context.Context, t *testing.T, channel *p2p.Channel, expect p2p.Envelope) {
|
||||
func RequireReceive(ctx context.Context, t *testing.T, channel p2p.Channel, expect p2p.Envelope) {
|
||||
t.Helper()
|
||||
|
||||
ctx, cancel := context.WithTimeout(ctx, time.Second)
|
||||
@@ -54,7 +54,7 @@ func RequireReceive(ctx context.Context, t *testing.T, channel *p2p.Channel, exp
|
||||
|
||||
// RequireReceiveUnordered requires that the given envelopes are all received on
|
||||
// the channel, ignoring order.
|
||||
func RequireReceiveUnordered(ctx context.Context, t *testing.T, channel *p2p.Channel, expect []*p2p.Envelope) {
|
||||
func RequireReceiveUnordered(ctx context.Context, t *testing.T, channel p2p.Channel, expect []*p2p.Envelope) {
|
||||
ctx, cancel := context.WithTimeout(ctx, time.Second)
|
||||
defer cancel()
|
||||
|
||||
@@ -75,7 +75,7 @@ func RequireReceiveUnordered(ctx context.Context, t *testing.T, channel *p2p.Cha
|
||||
}
|
||||
|
||||
// RequireSend requires that the given envelope is sent on the channel.
|
||||
func RequireSend(ctx context.Context, t *testing.T, channel *p2p.Channel, envelope p2p.Envelope) {
|
||||
func RequireSend(ctx context.Context, t *testing.T, channel p2p.Channel, envelope p2p.Envelope) {
|
||||
tctx, cancel := context.WithTimeout(ctx, time.Second)
|
||||
defer cancel()
|
||||
|
||||
@@ -93,7 +93,7 @@ func RequireSend(ctx context.Context, t *testing.T, channel *p2p.Channel, envelo
|
||||
func RequireSendReceive(
|
||||
ctx context.Context,
|
||||
t *testing.T,
|
||||
channel *p2p.Channel,
|
||||
channel p2p.Channel,
|
||||
peerID types.NodeID,
|
||||
send proto.Message,
|
||||
receive proto.Message,
|
||||
@@ -116,7 +116,7 @@ func RequireNoUpdates(ctx context.Context, t *testing.T, peerUpdates *p2p.PeerUp
|
||||
}
|
||||
|
||||
// RequireError requires that the given peer error is submitted for a peer.
|
||||
func RequireError(ctx context.Context, t *testing.T, channel *p2p.Channel, peerError p2p.PeerError) {
|
||||
func RequireError(ctx context.Context, t *testing.T, channel p2p.Channel, peerError p2p.PeerError) {
|
||||
tctx, tcancel := context.WithTimeout(ctx, time.Second)
|
||||
defer tcancel()
|
||||
|
||||
|
||||
@@ -954,7 +954,7 @@ func (m *PeerManager) Advertise(peerID types.NodeID, limit uint16) []NodeAddress
|
||||
}
|
||||
|
||||
var numAddresses int
|
||||
var totalScore int
|
||||
var totalAbsScore int
|
||||
ranked := m.store.Ranked()
|
||||
seenAddresses := map[NodeAddress]struct{}{}
|
||||
scores := map[types.NodeID]int{}
|
||||
@@ -965,8 +965,12 @@ func (m *PeerManager) Advertise(peerID types.NodeID, limit uint16) []NodeAddress
|
||||
continue
|
||||
}
|
||||
score := int(peer.Score())
|
||||
if score < 0 {
|
||||
totalAbsScore += -score
|
||||
} else {
|
||||
totalAbsScore += score
|
||||
}
|
||||
|
||||
totalScore += score
|
||||
scores[peer.ID] = score
|
||||
for addr := range peer.AddressInfo {
|
||||
if _, ok := m.options.PrivatePeers[addr.NodeID]; !ok {
|
||||
@@ -975,6 +979,8 @@ func (m *PeerManager) Advertise(peerID types.NodeID, limit uint16) []NodeAddress
|
||||
}
|
||||
}
|
||||
|
||||
meanAbsScore := (totalAbsScore + 1) / (len(scores) + 1)
|
||||
|
||||
var attempts uint16
|
||||
var addedLastIteration bool
|
||||
|
||||
@@ -1023,7 +1029,7 @@ func (m *PeerManager) Advertise(peerID types.NodeID, limit uint16) []NodeAddress
|
||||
// peer.
|
||||
|
||||
// nolint:gosec // G404: Use of weak random number generator
|
||||
if numAddresses <= int(limit) || rand.Intn(totalScore+1) <= scores[peer.ID]+1 || rand.Intn((idx+1)*10) <= idx+1 {
|
||||
if numAddresses <= int(limit) || rand.Intn((meanAbsScore*2)+1) <= scores[peer.ID]+1 || rand.Intn((idx+1)*10) <= idx+1 {
|
||||
addresses = append(addresses, addressInfo.Address)
|
||||
addedLastIteration = true
|
||||
seenAddresses[addressInfo.Address] = struct{}{}
|
||||
@@ -1419,47 +1425,6 @@ func (s *peerStore) Ranked() []*peerInfo {
|
||||
}
|
||||
sort.Slice(s.ranked, func(i, j int) bool {
|
||||
return s.ranked[i].Score() > s.ranked[j].Score()
|
||||
// TODO: reevaluate more wholistic sorting, perhaps as follows:
|
||||
|
||||
// // sort inactive peers after active peers
|
||||
// if s.ranked[i].Inactive && !s.ranked[j].Inactive {
|
||||
// return false
|
||||
// } else if !s.ranked[i].Inactive && s.ranked[j].Inactive {
|
||||
// return true
|
||||
// }
|
||||
|
||||
// iLastDialed, iLastDialSuccess := s.ranked[i].LastDialed()
|
||||
// jLastDialed, jLastDialSuccess := s.ranked[j].LastDialed()
|
||||
|
||||
// // sort peers who our most recent dialing attempt was
|
||||
// // successful ahead of peers with recent dialing
|
||||
// // failures
|
||||
// switch {
|
||||
// case iLastDialSuccess && jLastDialSuccess:
|
||||
// // if both peers were (are?) successfully
|
||||
// // connected, convey their score, but give the
|
||||
// // one we dialed successfully most recently a bonus
|
||||
|
||||
// iScore := s.ranked[i].Score()
|
||||
// jScore := s.ranked[j].Score()
|
||||
// if jLastDialed.Before(iLastDialed) {
|
||||
// jScore++
|
||||
// } else {
|
||||
// iScore++
|
||||
// }
|
||||
|
||||
// return iScore > jScore
|
||||
// case iLastDialSuccess:
|
||||
// return true
|
||||
// case jLastDialSuccess:
|
||||
// return false
|
||||
// default:
|
||||
// // if both peers were not successful in their
|
||||
// // most recent dialing attempt, fall back to
|
||||
// // peer score.
|
||||
|
||||
// return s.ranked[i].Score() > s.ranked[j].Score()
|
||||
// }
|
||||
})
|
||||
return s.ranked
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ func (r *Reactor) OnStop() {}
|
||||
|
||||
// processPexCh implements a blocking event loop where we listen for p2p
|
||||
// Envelope messages from the pexCh.
|
||||
func (r *Reactor) processPexCh(ctx context.Context, pexCh *p2p.Channel) {
|
||||
func (r *Reactor) processPexCh(ctx context.Context, pexCh p2p.Channel) {
|
||||
incoming := make(chan *p2p.Envelope)
|
||||
go func() {
|
||||
defer close(incoming)
|
||||
@@ -192,8 +192,7 @@ func (r *Reactor) processPexCh(ctx context.Context, pexCh *p2p.Channel) {
|
||||
// A request from another peer, or a response to one of our requests.
|
||||
dur, err := r.handlePexMessage(ctx, envelope, pexCh)
|
||||
if err != nil {
|
||||
r.logger.Error("failed to process message",
|
||||
"ch_id", envelope.ChannelID, "envelope", envelope, "err", err)
|
||||
r.logger.Error("failed to process message", "ch_id", envelope.ChannelID, "envelope", envelope, "err", err)
|
||||
if serr := pexCh.SendError(ctx, p2p.PeerError{
|
||||
NodeID: envelope.From,
|
||||
Err: err,
|
||||
@@ -225,7 +224,7 @@ func (r *Reactor) processPeerUpdates(ctx context.Context, peerUpdates *p2p.PeerU
|
||||
// handlePexMessage handles envelopes sent from peers on the PexChannel.
|
||||
// If an update was received, a new polling interval is returned; otherwise the
|
||||
// duration is 0.
|
||||
func (r *Reactor) handlePexMessage(ctx context.Context, envelope *p2p.Envelope, pexCh *p2p.Channel) (time.Duration, error) {
|
||||
func (r *Reactor) handlePexMessage(ctx context.Context, envelope *p2p.Envelope, pexCh p2p.Channel) (time.Duration, error) {
|
||||
logger := r.logger.With("peer", envelope.From)
|
||||
|
||||
switch msg := envelope.Message.(type) {
|
||||
@@ -308,7 +307,7 @@ func (r *Reactor) processPeerUpdate(peerUpdate p2p.PeerUpdate) {
|
||||
// that peer a request for more peer addresses. The chosen peer is moved into
|
||||
// the requestsSent bucket so that we will not attempt to contact them again
|
||||
// until they've replied or updated.
|
||||
func (r *Reactor) sendRequestForPeers(ctx context.Context, pexCh *p2p.Channel) error {
|
||||
func (r *Reactor) sendRequestForPeers(ctx context.Context, pexCh p2p.Channel) error {
|
||||
r.mtx.Lock()
|
||||
defer r.mtx.Unlock()
|
||||
if len(r.availablePeers) == 0 {
|
||||
|
||||
@@ -275,7 +275,7 @@ type singleTestReactor struct {
|
||||
pexInCh chan p2p.Envelope
|
||||
pexOutCh chan p2p.Envelope
|
||||
pexErrCh chan p2p.PeerError
|
||||
pexCh *p2p.Channel
|
||||
pexCh p2p.Channel
|
||||
peerCh chan p2p.PeerUpdate
|
||||
manager *p2p.PeerManager
|
||||
}
|
||||
@@ -287,8 +287,11 @@ func setupSingle(ctx context.Context, t *testing.T) *singleTestReactor {
|
||||
pexInCh := make(chan p2p.Envelope, chBuf)
|
||||
pexOutCh := make(chan p2p.Envelope, chBuf)
|
||||
pexErrCh := make(chan p2p.PeerError, chBuf)
|
||||
|
||||
chDesc := pex.ChannelDescriptor()
|
||||
pexCh := p2p.NewChannel(
|
||||
p2p.ChannelID(pex.PexChannel),
|
||||
chDesc.ID,
|
||||
chDesc.Name,
|
||||
pexInCh,
|
||||
pexOutCh,
|
||||
pexErrCh,
|
||||
@@ -299,7 +302,7 @@ func setupSingle(ctx context.Context, t *testing.T) *singleTestReactor {
|
||||
peerManager, err := p2p.NewPeerManager(nodeID, dbm.NewMemDB(), p2p.PeerManagerOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
chCreator := func(context.Context, *p2p.ChannelDescriptor) (*p2p.Channel, error) {
|
||||
chCreator := func(context.Context, *p2p.ChannelDescriptor) (p2p.Channel, error) {
|
||||
return pexCh, nil
|
||||
}
|
||||
|
||||
@@ -324,7 +327,7 @@ type reactorTestSuite struct {
|
||||
logger log.Logger
|
||||
|
||||
reactors map[types.NodeID]*pex.Reactor
|
||||
pexChannels map[types.NodeID]*p2p.Channel
|
||||
pexChannels map[types.NodeID]p2p.Channel
|
||||
|
||||
peerChans map[types.NodeID]chan p2p.PeerUpdate
|
||||
peerUpdates map[types.NodeID]*p2p.PeerUpdates
|
||||
@@ -367,7 +370,7 @@ func setupNetwork(ctx context.Context, t *testing.T, opts testOptions) *reactorT
|
||||
logger: log.NewNopLogger().With("testCase", t.Name()),
|
||||
network: p2ptest.MakeNetwork(ctx, t, networkOpts),
|
||||
reactors: make(map[types.NodeID]*pex.Reactor, realNodes),
|
||||
pexChannels: make(map[types.NodeID]*p2p.Channel, opts.TotalNodes),
|
||||
pexChannels: make(map[types.NodeID]p2p.Channel, opts.TotalNodes),
|
||||
peerChans: make(map[types.NodeID]chan p2p.PeerUpdate, opts.TotalNodes),
|
||||
peerUpdates: make(map[types.NodeID]*p2p.PeerUpdates, opts.TotalNodes),
|
||||
total: opts.TotalNodes,
|
||||
@@ -388,7 +391,7 @@ func setupNetwork(ctx context.Context, t *testing.T, opts testOptions) *reactorT
|
||||
rts.peerUpdates[nodeID] = p2p.NewPeerUpdates(rts.peerChans[nodeID], chBuf)
|
||||
rts.network.Nodes[nodeID].PeerManager.Register(ctx, rts.peerUpdates[nodeID])
|
||||
|
||||
chCreator := func(context.Context, *p2p.ChannelDescriptor) (*p2p.Channel, error) {
|
||||
chCreator := func(context.Context, *p2p.ChannelDescriptor) (p2p.Channel, error) {
|
||||
return rts.pexChannels[nodeID], nil
|
||||
}
|
||||
|
||||
@@ -448,7 +451,7 @@ func (r *reactorTestSuite) addNodes(ctx context.Context, t *testing.T, nodes int
|
||||
r.peerUpdates[nodeID] = p2p.NewPeerUpdates(r.peerChans[nodeID], r.opts.BufferSize)
|
||||
r.network.Nodes[nodeID].PeerManager.Register(ctx, r.peerUpdates[nodeID])
|
||||
|
||||
chCreator := func(context.Context, *p2p.ChannelDescriptor) (*p2p.Channel, error) {
|
||||
chCreator := func(context.Context, *p2p.ChannelDescriptor) (p2p.Channel, error) {
|
||||
return r.pexChannels[nodeID], nil
|
||||
}
|
||||
|
||||
|
||||
@@ -31,8 +31,16 @@ func (pq priorityQueue) get(i int) *pqEnvelope { return pq[i] }
|
||||
func (pq priorityQueue) Len() int { return len(pq) }
|
||||
|
||||
func (pq priorityQueue) Less(i, j int) bool {
|
||||
// if both elements have the same priority, prioritize based on most recent
|
||||
// if both elements have the same priority, prioritize based
|
||||
// on most recent and largest
|
||||
if pq[i].priority == pq[j].priority {
|
||||
diff := pq[i].timestamp.Sub(pq[j].timestamp)
|
||||
if diff < 0 {
|
||||
diff *= -1
|
||||
}
|
||||
if diff < 10*time.Millisecond {
|
||||
return pq[i].size > pq[j].size
|
||||
}
|
||||
return pq[i].timestamp.After(pq[j].timestamp)
|
||||
}
|
||||
|
||||
@@ -272,12 +280,10 @@ func (s *pqScheduler) process(ctx context.Context) {
|
||||
}
|
||||
|
||||
func (s *pqScheduler) push(pqEnv *pqEnvelope) {
|
||||
chIDStr := strconv.Itoa(int(pqEnv.envelope.ChannelID))
|
||||
|
||||
// enqueue the incoming Envelope
|
||||
heap.Push(s.pq, pqEnv)
|
||||
s.size += pqEnv.size
|
||||
s.metrics.PeerQueueMsgSize.With("ch_id", chIDStr).Add(float64(pqEnv.size))
|
||||
s.metrics.PeerQueueMsgSize.With("ch_id", strconv.Itoa(int(pqEnv.envelope.ChannelID))).Add(float64(pqEnv.size))
|
||||
|
||||
// Update the cumulative sizes by adding the Envelope's size to every
|
||||
// priority less than or equal to it.
|
||||
|
||||
@@ -68,8 +68,9 @@ type RouterOptions struct {
|
||||
}
|
||||
|
||||
const (
|
||||
queueTypeFifo = "fifo"
|
||||
queueTypePriority = "priority"
|
||||
queueTypeFifo = "fifo"
|
||||
queueTypePriority = "priority"
|
||||
queueTypeSimplePriority = "simple-priority"
|
||||
)
|
||||
|
||||
// Validate validates router options.
|
||||
@@ -77,7 +78,7 @@ func (o *RouterOptions) Validate() error {
|
||||
switch o.QueueType {
|
||||
case "":
|
||||
o.QueueType = queueTypeFifo
|
||||
case queueTypeFifo, queueTypePriority:
|
||||
case queueTypeFifo, queueTypePriority, queueTypeSimplePriority:
|
||||
// pass
|
||||
default:
|
||||
return fmt.Errorf("queue type %q is not supported", o.QueueType)
|
||||
@@ -227,6 +228,9 @@ func (r *Router) createQueueFactory(ctx context.Context) (func(int) queue, error
|
||||
return q
|
||||
}, nil
|
||||
|
||||
case queueTypeSimplePriority:
|
||||
return func(size int) queue { return newSimplePriorityQueue(ctx, size, r.chDescs) }, nil
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("cannot construct queue of type %q", r.options.QueueType)
|
||||
}
|
||||
@@ -235,7 +239,7 @@ func (r *Router) createQueueFactory(ctx context.Context) (func(int) queue, error
|
||||
// ChannelCreator allows routers to construct their own channels,
|
||||
// either by receiving a reference to Router.OpenChannel or using some
|
||||
// kind shim for testing purposes.
|
||||
type ChannelCreator func(context.Context, *ChannelDescriptor) (*Channel, error)
|
||||
type ChannelCreator func(context.Context, *ChannelDescriptor) (Channel, error)
|
||||
|
||||
// OpenChannel opens a new channel for the given message type. The caller must
|
||||
// close the channel when done, before stopping the Router. messageType is the
|
||||
@@ -243,7 +247,7 @@ type ChannelCreator func(context.Context, *ChannelDescriptor) (*Channel, error)
|
||||
// implement Wrapper to automatically (un)wrap multiple message types in a
|
||||
// wrapper message. The caller may provide a size to make the channel buffered,
|
||||
// which internally makes the inbound, outbound, and error channel buffered.
|
||||
func (r *Router) OpenChannel(ctx context.Context, chDesc *ChannelDescriptor) (*Channel, error) {
|
||||
func (r *Router) OpenChannel(ctx context.Context, chDesc *ChannelDescriptor) (Channel, error) {
|
||||
r.channelMtx.Lock()
|
||||
defer r.channelMtx.Unlock()
|
||||
|
||||
@@ -258,11 +262,10 @@ func (r *Router) OpenChannel(ctx context.Context, chDesc *ChannelDescriptor) (*C
|
||||
queue := r.queueFactory(chDesc.RecvBufferCapacity)
|
||||
outCh := make(chan Envelope, chDesc.RecvBufferCapacity)
|
||||
errCh := make(chan PeerError, chDesc.RecvBufferCapacity)
|
||||
channel := NewChannel(id, queue.dequeue(), outCh, errCh)
|
||||
channel.name = chDesc.Name
|
||||
channel := NewChannel(chDesc.ID, chDesc.Name, queue.dequeue(), outCh, errCh)
|
||||
|
||||
var wrapper Wrapper
|
||||
if w, ok := messageType.(Wrapper); ok {
|
||||
if w, ok := chDesc.MessageType.(Wrapper); ok {
|
||||
wrapper = w
|
||||
}
|
||||
|
||||
@@ -283,7 +286,7 @@ func (r *Router) OpenChannel(ctx context.Context, chDesc *ChannelDescriptor) (*C
|
||||
queue.close()
|
||||
}()
|
||||
|
||||
r.routeChannel(ctx, id, outCh, errCh, wrapper)
|
||||
r.routeChannel(ctx, chDesc.ID, outCh, errCh, wrapper)
|
||||
}()
|
||||
|
||||
return channel, nil
|
||||
@@ -304,6 +307,9 @@ func (r *Router) routeChannel(
|
||||
for {
|
||||
select {
|
||||
case envelope := <-outCh:
|
||||
if envelope.IsZero() {
|
||||
continue
|
||||
}
|
||||
// Mark the envelope with the channel ID to allow sendPeer() to pass
|
||||
// it on to Transport.SendMessage().
|
||||
envelope.ChannelID = chID
|
||||
|
||||
@@ -26,7 +26,7 @@ import (
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
func echoReactor(ctx context.Context, channel *p2p.Channel) {
|
||||
func echoReactor(ctx context.Context, channel p2p.Channel) {
|
||||
iter := channel.Receive(ctx)
|
||||
for iter.Next(ctx) {
|
||||
envelope := iter.Envelope()
|
||||
|
||||
112
internal/p2p/rqueue.go
Normal file
112
internal/p2p/rqueue.go
Normal file
@@ -0,0 +1,112 @@
|
||||
package p2p
|
||||
|
||||
import (
|
||||
"container/heap"
|
||||
"context"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
)
|
||||
|
||||
type simpleQueue struct {
|
||||
input chan Envelope
|
||||
output chan Envelope
|
||||
closeFn func()
|
||||
closeCh <-chan struct{}
|
||||
|
||||
maxSize int
|
||||
chDescs []*ChannelDescriptor
|
||||
}
|
||||
|
||||
func newSimplePriorityQueue(ctx context.Context, size int, chDescs []*ChannelDescriptor) *simpleQueue {
|
||||
if size%2 != 0 {
|
||||
size++
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
q := &simpleQueue{
|
||||
input: make(chan Envelope, size*2),
|
||||
output: make(chan Envelope, size/2),
|
||||
maxSize: size * size,
|
||||
closeCh: ctx.Done(),
|
||||
closeFn: cancel,
|
||||
}
|
||||
|
||||
go q.run(ctx)
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *simpleQueue) enqueue() chan<- Envelope { return q.input }
|
||||
func (q *simpleQueue) dequeue() <-chan Envelope { return q.output }
|
||||
func (q *simpleQueue) close() { q.closeFn() }
|
||||
func (q *simpleQueue) closed() <-chan struct{} { return q.closeCh }
|
||||
|
||||
func (q *simpleQueue) run(ctx context.Context) {
|
||||
defer q.closeFn()
|
||||
|
||||
var chPriorities = make(map[ChannelID]uint, len(q.chDescs))
|
||||
for _, chDesc := range q.chDescs {
|
||||
chID := chDesc.ID
|
||||
chPriorities[chID] = uint(chDesc.Priority)
|
||||
}
|
||||
|
||||
pq := make(priorityQueue, 0, q.maxSize)
|
||||
heap.Init(&pq)
|
||||
ticker := time.NewTicker(10 * time.Millisecond)
|
||||
// must have a buffer of exactly one because both sides of
|
||||
// this channel are used in this loop, and simply signals adds
|
||||
// to the heap
|
||||
signal := make(chan struct{}, 1)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-q.closeCh:
|
||||
return
|
||||
case e := <-q.input:
|
||||
// enqueue the incoming Envelope
|
||||
heap.Push(&pq, &pqEnvelope{
|
||||
envelope: e,
|
||||
size: uint(proto.Size(e.Message)),
|
||||
priority: chPriorities[e.ChannelID],
|
||||
timestamp: time.Now().UTC(),
|
||||
})
|
||||
|
||||
select {
|
||||
case signal <- struct{}{}:
|
||||
default:
|
||||
if len(pq) > q.maxSize {
|
||||
sort.Sort(pq)
|
||||
pq = pq[:q.maxSize]
|
||||
}
|
||||
}
|
||||
|
||||
case <-ticker.C:
|
||||
if len(pq) > q.maxSize {
|
||||
sort.Sort(pq)
|
||||
pq = pq[:q.maxSize]
|
||||
}
|
||||
if len(pq) > 0 {
|
||||
select {
|
||||
case signal <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
case <-signal:
|
||||
SEND:
|
||||
for len(pq) > 0 {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-q.closeCh:
|
||||
return
|
||||
case q.output <- heap.Pop(&pq).(*pqEnvelope).envelope:
|
||||
continue SEND
|
||||
default:
|
||||
break SEND
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
47
internal/p2p/rqueue_test.go
Normal file
47
internal/p2p/rqueue_test.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package p2p
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestSimpleQueue(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
// set up a small queue with very small buffers so we can
|
||||
// watch it shed load, then send a bunch of messages to the
|
||||
// queue, most of which we'll watch it drop.
|
||||
sq := newSimplePriorityQueue(ctx, 1, nil)
|
||||
for i := 0; i < 100; i++ {
|
||||
sq.enqueue() <- Envelope{From: "merlin"}
|
||||
}
|
||||
|
||||
seen := 0
|
||||
|
||||
RETRY:
|
||||
for seen <= 2 {
|
||||
select {
|
||||
case e := <-sq.dequeue():
|
||||
if e.From != "merlin" {
|
||||
continue
|
||||
}
|
||||
seen++
|
||||
case <-time.After(10 * time.Millisecond):
|
||||
break RETRY
|
||||
}
|
||||
}
|
||||
// if we don't see any messages, then it's just broken.
|
||||
if seen == 0 {
|
||||
t.Errorf("seen %d messages, should have seen more than one", seen)
|
||||
}
|
||||
// ensure that load shedding happens: there can be at most 3
|
||||
// messages that we get out of this, one that was buffered
|
||||
// plus 2 that were under the cap, everything else gets
|
||||
// dropped.
|
||||
if seen > 3 {
|
||||
t.Errorf("saw %d messages, should have seen 5 or fewer", seen)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -105,14 +105,14 @@ func (blockExec *BlockExecutor) CreateProposalBlock(
|
||||
rpp, err := blockExec.appClient.PrepareProposal(
|
||||
ctx,
|
||||
&abci.RequestPrepareProposal{
|
||||
MaxTxBytes: maxDataBytes,
|
||||
Txs: block.Txs.ToSliceOfBytes(),
|
||||
LocalLastCommit: buildExtendedCommitInfo(lastExtCommit, blockExec.store, state.InitialHeight, state.ConsensusParams.ABCI),
|
||||
ByzantineValidators: block.Evidence.ToABCI(),
|
||||
Height: block.Height,
|
||||
Time: block.Time,
|
||||
NextValidatorsHash: block.NextValidatorsHash,
|
||||
ProposerAddress: block.ProposerAddress,
|
||||
MaxTxBytes: maxDataBytes,
|
||||
Txs: block.Txs.ToSliceOfBytes(),
|
||||
LocalLastCommit: buildExtendedCommitInfo(lastExtCommit, blockExec.store, state.InitialHeight, state.ConsensusParams.ABCI),
|
||||
Misbehavior: block.Evidence.ToABCI(),
|
||||
Height: block.Height,
|
||||
Time: block.Time,
|
||||
NextValidatorsHash: block.NextValidatorsHash,
|
||||
ProposerAddress: block.ProposerAddress,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
@@ -147,14 +147,14 @@ func (blockExec *BlockExecutor) ProcessProposal(
|
||||
state State,
|
||||
) (bool, error) {
|
||||
resp, err := blockExec.appClient.ProcessProposal(ctx, &abci.RequestProcessProposal{
|
||||
Hash: block.Header.Hash(),
|
||||
Height: block.Header.Height,
|
||||
Time: block.Header.Time,
|
||||
Txs: block.Data.Txs.ToSliceOfBytes(),
|
||||
ProposedLastCommit: buildLastCommitInfo(block, blockExec.store, state.InitialHeight),
|
||||
ByzantineValidators: block.Evidence.ToABCI(),
|
||||
ProposerAddress: block.ProposerAddress,
|
||||
NextValidatorsHash: block.NextValidatorsHash,
|
||||
Hash: block.Header.Hash(),
|
||||
Height: block.Header.Height,
|
||||
Time: block.Header.Time,
|
||||
Txs: block.Data.Txs.ToSliceOfBytes(),
|
||||
ProposedLastCommit: buildLastCommitInfo(block, blockExec.store, state.InitialHeight),
|
||||
Misbehavior: block.Evidence.ToABCI(),
|
||||
ProposerAddress: block.ProposerAddress,
|
||||
NextValidatorsHash: block.NextValidatorsHash,
|
||||
})
|
||||
if err != nil {
|
||||
return false, ErrInvalidBlock(err)
|
||||
@@ -208,14 +208,14 @@ func (blockExec *BlockExecutor) ApplyBlock(
|
||||
fBlockRes, err := blockExec.appClient.FinalizeBlock(
|
||||
ctx,
|
||||
&abci.RequestFinalizeBlock{
|
||||
Hash: block.Hash(),
|
||||
Height: block.Header.Height,
|
||||
Time: block.Header.Time,
|
||||
Txs: block.Txs.ToSliceOfBytes(),
|
||||
DecidedLastCommit: buildLastCommitInfo(block, blockExec.store, state.InitialHeight),
|
||||
ByzantineValidators: block.Evidence.ToABCI(),
|
||||
ProposerAddress: block.ProposerAddress,
|
||||
NextValidatorsHash: block.NextValidatorsHash,
|
||||
Hash: block.Hash(),
|
||||
Height: block.Header.Height,
|
||||
Time: block.Header.Time,
|
||||
Txs: block.Txs.ToSliceOfBytes(),
|
||||
DecidedLastCommit: buildLastCommitInfo(block, blockExec.store, state.InitialHeight),
|
||||
Misbehavior: block.Evidence.ToABCI(),
|
||||
ProposerAddress: block.ProposerAddress,
|
||||
NextValidatorsHash: block.NextValidatorsHash,
|
||||
},
|
||||
)
|
||||
endTime := time.Now().UnixNano()
|
||||
@@ -677,12 +677,12 @@ func ExecCommitBlock(
|
||||
finalizeBlockResponse, err := appConn.FinalizeBlock(
|
||||
ctx,
|
||||
&abci.RequestFinalizeBlock{
|
||||
Hash: block.Hash(),
|
||||
Height: block.Height,
|
||||
Time: block.Time,
|
||||
Txs: block.Txs.ToSliceOfBytes(),
|
||||
DecidedLastCommit: buildLastCommitInfo(block, store, initialHeight),
|
||||
ByzantineValidators: block.Evidence.ToABCI(),
|
||||
Hash: block.Hash(),
|
||||
Height: block.Height,
|
||||
Time: block.Time,
|
||||
Txs: block.Txs.ToSliceOfBytes(),
|
||||
DecidedLastCommit: buildLastCommitInfo(block, store, initialHeight),
|
||||
Misbehavior: block.Evidence.ToABCI(),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -158,8 +158,8 @@ func TestFinalizeBlockDecidedLastCommit(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestFinalizeBlockByzantineValidators ensures we send byzantine validators list.
|
||||
func TestFinalizeBlockByzantineValidators(t *testing.T) {
|
||||
// TestFinalizeBlockMisbehavior ensures we send misbehavior list.
|
||||
func TestFinalizeBlockMisbehavior(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
@@ -274,7 +274,7 @@ func TestFinalizeBlockByzantineValidators(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
// TODO check state and mempool
|
||||
assert.Equal(t, abciMb, app.ByzantineValidators)
|
||||
assert.Equal(t, abciMb, app.Misbehavior)
|
||||
}
|
||||
|
||||
func TestProcessProposal(t *testing.T) {
|
||||
@@ -338,11 +338,11 @@ func TestProcessProposal(t *testing.T) {
|
||||
block1.Txs = txs
|
||||
|
||||
expectedRpp := &abci.RequestProcessProposal{
|
||||
Txs: block1.Txs.ToSliceOfBytes(),
|
||||
Hash: block1.Hash(),
|
||||
Height: block1.Header.Height,
|
||||
Time: block1.Header.Time,
|
||||
ByzantineValidators: block1.Evidence.ToABCI(),
|
||||
Txs: block1.Txs.ToSliceOfBytes(),
|
||||
Hash: block1.Hash(),
|
||||
Height: block1.Header.Height,
|
||||
Time: block1.Header.Time,
|
||||
Misbehavior: block1.Evidence.ToABCI(),
|
||||
ProposedLastCommit: abci.CommitInfo{
|
||||
Round: 0,
|
||||
Votes: voteInfos,
|
||||
|
||||
@@ -266,9 +266,9 @@ func makeRandomStateFromConsensusParams(
|
||||
type testApp struct {
|
||||
abci.BaseApplication
|
||||
|
||||
CommitVotes []abci.VoteInfo
|
||||
ByzantineValidators []abci.Misbehavior
|
||||
ValidatorUpdates []abci.ValidatorUpdate
|
||||
CommitVotes []abci.VoteInfo
|
||||
Misbehavior []abci.Misbehavior
|
||||
ValidatorUpdates []abci.ValidatorUpdate
|
||||
}
|
||||
|
||||
var _ abci.Application = (*testApp)(nil)
|
||||
@@ -279,7 +279,7 @@ func (app *testApp) Info(_ context.Context, req *abci.RequestInfo) (*abci.Respon
|
||||
|
||||
func (app *testApp) FinalizeBlock(_ context.Context, req *abci.RequestFinalizeBlock) (*abci.ResponseFinalizeBlock, error) {
|
||||
app.CommitVotes = req.DecidedLastCommit.Votes
|
||||
app.ByzantineValidators = req.ByzantineValidators
|
||||
app.Misbehavior = req.Misbehavior
|
||||
|
||||
resTxs := make([]*abci.ExecTxResult, len(req.Txs))
|
||||
for i, tx := range req.Txs {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/internal/eventbus"
|
||||
"github.com/tendermint/tendermint/internal/pubsub"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
@@ -96,7 +97,14 @@ func (is *Service) publish(msg pubsub.Message) error {
|
||||
|
||||
if curr.Size() != 0 {
|
||||
start := time.Now()
|
||||
err := sink.IndexTxEvents(curr.Ops)
|
||||
|
||||
var err error
|
||||
curr.Ops, err = DeduplicateBatch(curr.Ops, sink)
|
||||
if err != nil {
|
||||
is.logger.Error("failed to deduplicate batch", "height", is.currentBlock.height, "error", err)
|
||||
}
|
||||
|
||||
err = sink.IndexTxEvents(curr.Ops)
|
||||
if err != nil {
|
||||
is.logger.Error("failed to index block txs",
|
||||
"height", is.currentBlock.height, "err", err)
|
||||
@@ -169,3 +177,45 @@ func IndexingEnabled(sinks []EventSink) bool {
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// DeduplicateBatch consider the case of duplicate txs.
|
||||
// if the current one under investigation is NOT OK, then we need to check
|
||||
// whether there's a previously indexed tx.
|
||||
// SKIP the current tx if the previously indexed record is found and successful.
|
||||
func DeduplicateBatch(ops []*abci.TxResult, sink EventSink) ([]*abci.TxResult, error) {
|
||||
result := make([]*abci.TxResult, 0, len(ops))
|
||||
|
||||
// keep track of successful txs in this block in order to suppress latter ones being indexed.
|
||||
var successfulTxsInThisBlock = make(map[string]struct{})
|
||||
|
||||
for _, txResult := range ops {
|
||||
hash := types.Tx(txResult.Tx).Hash()
|
||||
|
||||
if txResult.Result.IsOK() {
|
||||
successfulTxsInThisBlock[string(hash)] = struct{}{}
|
||||
} else {
|
||||
// if it already appeared in current block and was successful, skip.
|
||||
if _, found := successfulTxsInThisBlock[string(hash)]; found {
|
||||
continue
|
||||
}
|
||||
|
||||
// check if this tx hash is already indexed
|
||||
old, err := sink.GetTxByHash(hash)
|
||||
|
||||
// if db op errored
|
||||
// Not found is not an error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// if it's already indexed in an older block and was successful, skip.
|
||||
if old != nil && old.Result.Code == abci.CodeTypeOK {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
result = append(result, txResult)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -109,6 +109,165 @@ func TestIndexerServiceIndexesBlocks(t *testing.T) {
|
||||
assert.Nil(t, teardown(t, pool))
|
||||
}
|
||||
|
||||
func TestTxIndexDuplicatedTx(t *testing.T) {
|
||||
var mockTx = types.Tx("MOCK_TX_HASH")
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
tx1 abci.TxResult
|
||||
tx2 abci.TxResult
|
||||
expSkip bool // do we expect the second tx to be skipped by tx indexer
|
||||
}{
|
||||
{"skip, previously successful",
|
||||
abci.TxResult{
|
||||
Height: 1,
|
||||
Index: 0,
|
||||
Tx: mockTx,
|
||||
Result: abci.ExecTxResult{
|
||||
Code: abci.CodeTypeOK,
|
||||
},
|
||||
},
|
||||
abci.TxResult{
|
||||
Height: 2,
|
||||
Index: 0,
|
||||
Tx: mockTx,
|
||||
Result: abci.ExecTxResult{
|
||||
Code: abci.CodeTypeOK + 1,
|
||||
},
|
||||
},
|
||||
true,
|
||||
},
|
||||
{"not skip, previously unsuccessful",
|
||||
abci.TxResult{
|
||||
Height: 1,
|
||||
Index: 0,
|
||||
Tx: mockTx,
|
||||
Result: abci.ExecTxResult{
|
||||
Code: abci.CodeTypeOK + 1,
|
||||
},
|
||||
},
|
||||
abci.TxResult{
|
||||
Height: 2,
|
||||
Index: 0,
|
||||
Tx: mockTx,
|
||||
Result: abci.ExecTxResult{
|
||||
Code: abci.CodeTypeOK + 1,
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{"not skip, both successful",
|
||||
abci.TxResult{
|
||||
Height: 1,
|
||||
Index: 0,
|
||||
Tx: mockTx,
|
||||
Result: abci.ExecTxResult{
|
||||
Code: abci.CodeTypeOK,
|
||||
},
|
||||
},
|
||||
abci.TxResult{
|
||||
Height: 2,
|
||||
Index: 0,
|
||||
Tx: mockTx,
|
||||
Result: abci.ExecTxResult{
|
||||
Code: abci.CodeTypeOK,
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{"not skip, both unsuccessful",
|
||||
abci.TxResult{
|
||||
Height: 1,
|
||||
Index: 0,
|
||||
Tx: mockTx,
|
||||
Result: abci.ExecTxResult{
|
||||
Code: abci.CodeTypeOK + 1,
|
||||
},
|
||||
},
|
||||
abci.TxResult{
|
||||
Height: 2,
|
||||
Index: 0,
|
||||
Tx: mockTx,
|
||||
Result: abci.ExecTxResult{
|
||||
Code: abci.CodeTypeOK + 1,
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{"skip, same block, previously successful",
|
||||
abci.TxResult{
|
||||
Height: 1,
|
||||
Index: 0,
|
||||
Tx: mockTx,
|
||||
Result: abci.ExecTxResult{
|
||||
Code: abci.CodeTypeOK,
|
||||
},
|
||||
},
|
||||
abci.TxResult{
|
||||
Height: 1,
|
||||
Index: 0,
|
||||
Tx: mockTx,
|
||||
Result: abci.ExecTxResult{
|
||||
Code: abci.CodeTypeOK + 1,
|
||||
},
|
||||
},
|
||||
true,
|
||||
},
|
||||
{"not skip, same block, previously unsuccessful",
|
||||
abci.TxResult{
|
||||
Height: 1,
|
||||
Index: 0,
|
||||
Tx: mockTx,
|
||||
Result: abci.ExecTxResult{
|
||||
Code: abci.CodeTypeOK + 1,
|
||||
},
|
||||
},
|
||||
abci.TxResult{
|
||||
Height: 1,
|
||||
Index: 0,
|
||||
Tx: mockTx,
|
||||
Result: abci.ExecTxResult{
|
||||
Code: abci.CodeTypeOK,
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
sink := kv.NewEventSink(dbm.NewMemDB())
|
||||
|
||||
if tc.tx1.Height != tc.tx2.Height {
|
||||
// index the first tx
|
||||
err := sink.IndexTxEvents([]*abci.TxResult{&tc.tx1})
|
||||
require.NoError(t, err)
|
||||
|
||||
// check if the second one should be skipped.
|
||||
ops, err := indexer.DeduplicateBatch([]*abci.TxResult{&tc.tx2}, sink)
|
||||
require.NoError(t, err)
|
||||
|
||||
if tc.expSkip {
|
||||
require.Empty(t, ops)
|
||||
} else {
|
||||
require.Equal(t, []*abci.TxResult{&tc.tx2}, ops)
|
||||
}
|
||||
} else {
|
||||
// same block
|
||||
ops := []*abci.TxResult{&tc.tx1, &tc.tx2}
|
||||
ops, err := indexer.DeduplicateBatch(ops, sink)
|
||||
require.NoError(t, err)
|
||||
if tc.expSkip {
|
||||
// the second one is skipped
|
||||
require.Equal(t, []*abci.TxResult{&tc.tx1}, ops)
|
||||
} else {
|
||||
require.Equal(t, []*abci.TxResult{&tc.tx1, &tc.tx2}, ops)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func readSchema() ([]*schema.Migration, error) {
|
||||
filename := "./sink/psql/schema.sql"
|
||||
contents, err := os.ReadFile(filename)
|
||||
|
||||
@@ -26,14 +26,14 @@ var (
|
||||
// NOTE: It is not the responsibility of the dispatcher to verify the light blocks.
|
||||
type Dispatcher struct {
|
||||
// the channel with which to send light block requests on
|
||||
requestCh *p2p.Channel
|
||||
requestCh p2p.Channel
|
||||
|
||||
mtx sync.Mutex
|
||||
// all pending calls that have been dispatched and are awaiting an answer
|
||||
calls map[types.NodeID]chan *types.LightBlock
|
||||
}
|
||||
|
||||
func NewDispatcher(requestChannel *p2p.Channel) *Dispatcher {
|
||||
func NewDispatcher(requestChannel p2p.Channel) *Dispatcher {
|
||||
return &Dispatcher{
|
||||
requestCh: requestChannel,
|
||||
calls: make(map[types.NodeID]chan *types.LightBlock),
|
||||
|
||||
@@ -24,13 +24,13 @@ type channelInternal struct {
|
||||
Error chan p2p.PeerError
|
||||
}
|
||||
|
||||
func testChannel(size int) (*channelInternal, *p2p.Channel) {
|
||||
func testChannel(size int) (*channelInternal, p2p.Channel) {
|
||||
in := &channelInternal{
|
||||
In: make(chan p2p.Envelope, size),
|
||||
Out: make(chan p2p.Envelope, size),
|
||||
Error: make(chan p2p.PeerError, size),
|
||||
}
|
||||
return in, p2p.NewChannel(0, in.In, in.Out, in.Error)
|
||||
return in, p2p.NewChannel(0, "test", in.In, in.Out, in.Error)
|
||||
}
|
||||
|
||||
func TestDispatcherBasic(t *testing.T) {
|
||||
|
||||
@@ -305,7 +305,7 @@ func (r *Reactor) OnStart(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
go r.processChannels(ctx, map[p2p.ChannelID]*p2p.Channel{
|
||||
go r.processChannels(ctx, map[p2p.ChannelID]p2p.Channel{
|
||||
SnapshotChannel: snapshotCh,
|
||||
ChunkChannel: chunkCh,
|
||||
LightBlockChannel: blockCh,
|
||||
@@ -611,7 +611,7 @@ func (r *Reactor) backfill(
|
||||
// handleSnapshotMessage handles envelopes sent from peers on the
|
||||
// SnapshotChannel. It returns an error only if the Envelope.Message is unknown
|
||||
// for this channel. This should never be called outside of handleMessage.
|
||||
func (r *Reactor) handleSnapshotMessage(ctx context.Context, envelope *p2p.Envelope, snapshotCh *p2p.Channel) error {
|
||||
func (r *Reactor) handleSnapshotMessage(ctx context.Context, envelope *p2p.Envelope, snapshotCh p2p.Channel) error {
|
||||
logger := r.logger.With("peer", envelope.From)
|
||||
|
||||
switch msg := envelope.Message.(type) {
|
||||
@@ -683,7 +683,7 @@ func (r *Reactor) handleSnapshotMessage(ctx context.Context, envelope *p2p.Envel
|
||||
// handleChunkMessage handles envelopes sent from peers on the ChunkChannel.
|
||||
// It returns an error only if the Envelope.Message is unknown for this channel.
|
||||
// This should never be called outside of handleMessage.
|
||||
func (r *Reactor) handleChunkMessage(ctx context.Context, envelope *p2p.Envelope, chunkCh *p2p.Channel) error {
|
||||
func (r *Reactor) handleChunkMessage(ctx context.Context, envelope *p2p.Envelope, chunkCh p2p.Channel) error {
|
||||
switch msg := envelope.Message.(type) {
|
||||
case *ssproto.ChunkRequest:
|
||||
r.logger.Debug(
|
||||
@@ -772,7 +772,7 @@ func (r *Reactor) handleChunkMessage(ctx context.Context, envelope *p2p.Envelope
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Reactor) handleLightBlockMessage(ctx context.Context, envelope *p2p.Envelope, blockCh *p2p.Channel) error {
|
||||
func (r *Reactor) handleLightBlockMessage(ctx context.Context, envelope *p2p.Envelope, blockCh p2p.Channel) error {
|
||||
switch msg := envelope.Message.(type) {
|
||||
case *ssproto.LightBlockRequest:
|
||||
r.logger.Info("received light block request", "height", msg.Height)
|
||||
@@ -829,7 +829,7 @@ func (r *Reactor) handleLightBlockMessage(ctx context.Context, envelope *p2p.Env
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Reactor) handleParamsMessage(ctx context.Context, envelope *p2p.Envelope, paramsCh *p2p.Channel) error {
|
||||
func (r *Reactor) handleParamsMessage(ctx context.Context, envelope *p2p.Envelope, paramsCh p2p.Channel) error {
|
||||
switch msg := envelope.Message.(type) {
|
||||
case *ssproto.ParamsRequest:
|
||||
r.logger.Debug("received consensus params request", "height", msg.Height)
|
||||
@@ -878,7 +878,7 @@ func (r *Reactor) handleParamsMessage(ctx context.Context, envelope *p2p.Envelop
|
||||
// handleMessage handles an Envelope sent from a peer on a specific p2p Channel.
|
||||
// It will handle errors and any possible panics gracefully. A caller can handle
|
||||
// any error returned by sending a PeerError on the respective channel.
|
||||
func (r *Reactor) handleMessage(ctx context.Context, envelope *p2p.Envelope, chans map[p2p.ChannelID]*p2p.Channel) (err error) {
|
||||
func (r *Reactor) handleMessage(ctx context.Context, envelope *p2p.Envelope, chans map[p2p.ChannelID]p2p.Channel) (err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = fmt.Errorf("panic in processing message: %v", e)
|
||||
@@ -912,12 +912,12 @@ func (r *Reactor) handleMessage(ctx context.Context, envelope *p2p.Envelope, cha
|
||||
// encountered during message execution will result in a PeerError being sent on
|
||||
// the respective channel. When the reactor is stopped, we will catch the signal
|
||||
// and close the p2p Channel gracefully.
|
||||
func (r *Reactor) processChannels(ctx context.Context, chanTable map[p2p.ChannelID]*p2p.Channel) {
|
||||
// make sure that the iterator gets cleaned up in case of error
|
||||
func (r *Reactor) processChannels(ctx context.Context, chanTable map[p2p.ChannelID]p2p.Channel) {
|
||||
// make sure tht the iterator gets cleaned up in case of error
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
chs := make([]*p2p.Channel, 0, len(chanTable))
|
||||
chs := make([]p2p.Channel, 0, len(chanTable))
|
||||
for key := range chanTable {
|
||||
chs = append(chs, chanTable[key])
|
||||
}
|
||||
|
||||
@@ -40,22 +40,22 @@ type reactorTestSuite struct {
|
||||
conn *clientmocks.Client
|
||||
stateProvider *mocks.StateProvider
|
||||
|
||||
snapshotChannel *p2p.Channel
|
||||
snapshotChannel p2p.Channel
|
||||
snapshotInCh chan p2p.Envelope
|
||||
snapshotOutCh chan p2p.Envelope
|
||||
snapshotPeerErrCh chan p2p.PeerError
|
||||
|
||||
chunkChannel *p2p.Channel
|
||||
chunkChannel p2p.Channel
|
||||
chunkInCh chan p2p.Envelope
|
||||
chunkOutCh chan p2p.Envelope
|
||||
chunkPeerErrCh chan p2p.PeerError
|
||||
|
||||
blockChannel *p2p.Channel
|
||||
blockChannel p2p.Channel
|
||||
blockInCh chan p2p.Envelope
|
||||
blockOutCh chan p2p.Envelope
|
||||
blockPeerErrCh chan p2p.PeerError
|
||||
|
||||
paramsChannel *p2p.Channel
|
||||
paramsChannel p2p.Channel
|
||||
paramsInCh chan p2p.Envelope
|
||||
paramsOutCh chan p2p.Envelope
|
||||
paramsPeerErrCh chan p2p.PeerError
|
||||
@@ -102,6 +102,7 @@ func setup(
|
||||
|
||||
rts.snapshotChannel = p2p.NewChannel(
|
||||
SnapshotChannel,
|
||||
"snapshot",
|
||||
rts.snapshotInCh,
|
||||
rts.snapshotOutCh,
|
||||
rts.snapshotPeerErrCh,
|
||||
@@ -109,6 +110,7 @@ func setup(
|
||||
|
||||
rts.chunkChannel = p2p.NewChannel(
|
||||
ChunkChannel,
|
||||
"chunk",
|
||||
rts.chunkInCh,
|
||||
rts.chunkOutCh,
|
||||
rts.chunkPeerErrCh,
|
||||
@@ -116,6 +118,7 @@ func setup(
|
||||
|
||||
rts.blockChannel = p2p.NewChannel(
|
||||
LightBlockChannel,
|
||||
"lightblock",
|
||||
rts.blockInCh,
|
||||
rts.blockOutCh,
|
||||
rts.blockPeerErrCh,
|
||||
@@ -123,6 +126,7 @@ func setup(
|
||||
|
||||
rts.paramsChannel = p2p.NewChannel(
|
||||
ParamsChannel,
|
||||
"params",
|
||||
rts.paramsInCh,
|
||||
rts.paramsOutCh,
|
||||
rts.paramsPeerErrCh,
|
||||
@@ -133,7 +137,7 @@ func setup(
|
||||
|
||||
cfg := config.DefaultStateSyncConfig()
|
||||
|
||||
chCreator := func(ctx context.Context, desc *p2p.ChannelDescriptor) (*p2p.Channel, error) {
|
||||
chCreator := func(ctx context.Context, desc *p2p.ChannelDescriptor) (p2p.Channel, error) {
|
||||
switch desc.ID {
|
||||
case SnapshotChannel:
|
||||
return rts.snapshotChannel, nil
|
||||
|
||||
@@ -208,7 +208,7 @@ type stateProviderP2P struct {
|
||||
sync.Mutex // light.Client is not concurrency-safe
|
||||
lc *light.Client
|
||||
initialHeight int64
|
||||
paramsSendCh *p2p.Channel
|
||||
paramsSendCh p2p.Channel
|
||||
paramsRecvCh chan types.ConsensusParams
|
||||
}
|
||||
|
||||
@@ -220,7 +220,7 @@ func NewP2PStateProvider(
|
||||
initialHeight int64,
|
||||
providers []lightprovider.Provider,
|
||||
trustOptions light.TrustOptions,
|
||||
paramsSendCh *p2p.Channel,
|
||||
paramsSendCh p2p.Channel,
|
||||
logger log.Logger,
|
||||
) (StateProvider, error) {
|
||||
if len(providers) < 2 {
|
||||
|
||||
@@ -56,8 +56,8 @@ type syncer struct {
|
||||
stateProvider StateProvider
|
||||
conn abciclient.Client
|
||||
snapshots *snapshotPool
|
||||
snapshotCh *p2p.Channel
|
||||
chunkCh *p2p.Channel
|
||||
snapshotCh p2p.Channel
|
||||
chunkCh p2p.Channel
|
||||
tempDir string
|
||||
fetchers int32
|
||||
retryTimeout time.Duration
|
||||
|
||||
@@ -109,7 +109,7 @@ message RequestPrepareProposal {
|
||||
// sent to the app for possible modifications.
|
||||
repeated bytes txs = 2;
|
||||
ExtendedCommitInfo local_last_commit = 3 [(gogoproto.nullable) = false];
|
||||
repeated Misbehavior byzantine_validators = 4 [(gogoproto.nullable) = false];
|
||||
repeated Misbehavior misbehavior = 4 [(gogoproto.nullable) = false];
|
||||
int64 height = 5;
|
||||
google.protobuf.Timestamp time = 6 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true];
|
||||
bytes next_validators_hash = 7;
|
||||
@@ -120,7 +120,7 @@ message RequestPrepareProposal {
|
||||
message RequestProcessProposal {
|
||||
repeated bytes txs = 1;
|
||||
CommitInfo proposed_last_commit = 2 [(gogoproto.nullable) = false];
|
||||
repeated Misbehavior byzantine_validators = 3 [(gogoproto.nullable) = false];
|
||||
repeated Misbehavior misbehavior = 3 [(gogoproto.nullable) = false];
|
||||
// hash is the merkle root hash of the fields of the proposed block.
|
||||
bytes hash = 4;
|
||||
int64 height = 5;
|
||||
@@ -147,8 +147,8 @@ message RequestVerifyVoteExtension {
|
||||
message RequestFinalizeBlock {
|
||||
repeated bytes txs = 1;
|
||||
CommitInfo decided_last_commit = 2 [(gogoproto.nullable) = false];
|
||||
repeated Misbehavior byzantine_validators = 3 [(gogoproto.nullable) = false];
|
||||
// hash is the merkle root hash of the fields of the proposed block.
|
||||
repeated Misbehavior misbehavior = 3 [(gogoproto.nullable) = false];
|
||||
// hash is the merkle root hash of the fields of the decided block.
|
||||
bytes hash = 4;
|
||||
int64 height = 5;
|
||||
google.protobuf.Timestamp time = 6 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true];
|
||||
@@ -248,7 +248,7 @@ message ResponseDeliverTx {
|
||||
}
|
||||
|
||||
message ResponseCommit {
|
||||
// reserve 1
|
||||
reserved 1, 2;
|
||||
int64 retain_height = 3;
|
||||
}
|
||||
|
||||
|
||||
@@ -25,14 +25,14 @@ This allows Tendermint to run with applications written in many programming lang
|
||||
|
||||
This specification is split as follows:
|
||||
|
||||
- [Overview and basic concepts](./abci++_basic_concepts_002_draft.md) - interface's overview and concepts
|
||||
- [Overview and basic concepts](./abci++_basic_concepts.md) - interface's overview and concepts
|
||||
needed to understand other parts of this specification.
|
||||
- [Methods](./abci++_methods_002_draft.md) - complete details on all ABCI++ methods
|
||||
- [Methods](./abci++_methods.md) - complete details on all ABCI++ methods
|
||||
and message types.
|
||||
- [Requirements for the Application](./abci++_app_requirements_002_draft.md) - formal requirements
|
||||
- [Requirements for the Application](./abci++_app_requirements.md) - formal requirements
|
||||
on the Application's logic to ensure Tendermint properties such as liveness. These requirements define what
|
||||
Tendermint expects from the Application; second part on managing ABCI application state and related topics.
|
||||
- [Tendermint's expected behavior](./abci++_tmint_expected_behavior_002_draft.md) - specification of
|
||||
- [Tendermint's expected behavior](./abci++_tmint_expected_behavior.md) - specification of
|
||||
how the different ABCI++ methods may be called by Tendermint. This explains what the Application
|
||||
is to expect from Tendermint.
|
||||
- [Client and Server](../abci/client-server.md) - for those looking to implement their
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
---
|
||||
order: 3
|
||||
title: Application Requirements
|
||||
title: Requirements for the Application
|
||||
---
|
||||
|
||||
# Application Requirements
|
||||
# Requirements for the Application
|
||||
|
||||
## Formal Requirements
|
||||
|
||||
This section specifies what Tendermint expects from the Application. It is structured as a set
|
||||
of formal requirements that can be used for testing and verification of the Application's logic.
|
||||
|
||||
Let *p* and *q* be two different correct proposers in rounds *r<sub>p</sub>* and *r<sub>q</sub>*
|
||||
respectively, in height *h*.
|
||||
Let *p* and *q* be two correct processes.
|
||||
Let *r<sub>p</sub>* (resp. *r<sub>q</sub>*) be a round of height *h* where *p* (resp. *q*) is the
|
||||
proposer.
|
||||
Let *s<sub>p,h-1</sub>* be *p*'s Application's state committed for height *h-1*.
|
||||
Let *v<sub>p</sub>* (resp. *v<sub>q</sub>*) be the block that *p*'s (resp. *q*'s) Tendermint passes
|
||||
on to the Application
|
||||
via `RequestPrepareProposal` as proposer of round *r<sub>p</sub>* (resp *r<sub>q</sub>*), height *h*,
|
||||
also known as the raw proposal.
|
||||
Let *v'<sub>p</sub>* (resp. *v'<sub>q</sub>*) the possibly modified block *p*'s (resp. *q*'s) Application
|
||||
Let *u<sub>p</sub>* (resp. *u<sub>q</sub>*) the possibly modified block *p*'s (resp. *q*'s) Application
|
||||
returns via `ResponsePrepareProposal` to Tendermint, also known as the prepared proposal.
|
||||
|
||||
Process *p*'s prepared proposal can differ in two different rounds where *p* is the proposer.
|
||||
@@ -49,6 +50,7 @@ same-block execution mode and *does not* provide values for
|
||||
Full execution of blocks at `PrepareProposal` time stands on Tendermint's critical path. Thus,
|
||||
Requirement 3 ensures the Application will set a value for `TimeoutPropose` such that the time it takes
|
||||
to fully execute blocks in `PrepareProposal` does not interfere with Tendermint's propose timer.
|
||||
Note that violation of Requirement 3 may just lead to further rounds, but will not compromise liveness.
|
||||
|
||||
* Requirement 4 [`PrepareProposal`, tx-size]: When *p*'s Application calls `ResponsePrepareProposal`, the
|
||||
total size in bytes of the transactions returned does not exceed `RequestPrepareProposal.max_tx_bytes`.
|
||||
@@ -62,7 +64,7 @@ transaction list returned by the application will never cause the resulting bloc
|
||||
limit.
|
||||
|
||||
* Requirement 5 [`PrepareProposal`, `ProcessProposal`, coherence]: For any two correct processes *p* and *q*,
|
||||
if *q*'s Tendermint calls `RequestProcessProposal` on *v'<sub>p</sub>*,
|
||||
if *q*'s Tendermint calls `RequestProcessProposal` on *u<sub>p</sub>*,
|
||||
*q*'s Application returns Accept in `ResponseProcessProposal`.
|
||||
|
||||
Requirement 5 makes sure that blocks proposed by correct processes *always* pass the correct receiving process's
|
||||
@@ -75,14 +77,14 @@ serious consequences on Tendermint's liveness that this entails. Due to its crit
|
||||
target for extensive testing and automated verification.
|
||||
|
||||
* Requirement 6 [`ProcessProposal`, determinism-1]: `ProcessProposal` is a (deterministic) function of the current
|
||||
state and the block that is about to be applied. In other words, for any correct process *p*, and any arbitrary block *v'*,
|
||||
if *p*'s Tendermint calls `RequestProcessProposal` on *v'* at height *h*,
|
||||
then *p*'s Application's acceptance or rejection **exclusively** depends on *v'* and *s<sub>p,h-1</sub>*.
|
||||
state and the block that is about to be applied. In other words, for any correct process *p*, and any arbitrary block *u*,
|
||||
if *p*'s Tendermint calls `RequestProcessProposal` on *u* at height *h*,
|
||||
then *p*'s Application's acceptance or rejection **exclusively** depends on *u* and *s<sub>p,h-1</sub>*.
|
||||
|
||||
* Requirement 7 [`ProcessProposal`, determinism-2]: For any two correct processes *p* and *q*, and any arbitrary
|
||||
block *v'*,
|
||||
if *p*'s (resp. *q*'s) Tendermint calls `RequestProcessProposal` on *v'* at height *h*,
|
||||
then *p*'s Application accepts *v'* if and only if *q*'s Application accepts *v'*.
|
||||
block *u*,
|
||||
if *p*'s (resp. *q*'s) Tendermint calls `RequestProcessProposal` on *u* at height *h*,
|
||||
then *p*'s Application accepts *u* if and only if *q*'s Application accepts *u*.
|
||||
Note that this requirement follows from Requirement 6 and the Agreement property of consensus.
|
||||
|
||||
Requirements 6 and 7 ensure that all correct processes will react in the same way to a proposed block, even
|
||||
@@ -97,7 +99,7 @@ of `ProcessProposal`. As a general rule `ProcessProposal` SHOULD always accept t
|
||||
|
||||
According to the Tendermint algorithm, a correct process can broadcast at most one precommit
|
||||
message in round *r*, height *h*.
|
||||
Since, as stated in the [Methods](./abci++_methods_002_draft.md#extendvote) section, `ResponseExtendVote`
|
||||
Since, as stated in the [Methods](./abci++_methods.md#extendvote) section, `ResponseExtendVote`
|
||||
is only called when Tendermint
|
||||
is about to broadcast a non-`nil` precommit message, a correct process can only produce one vote extension
|
||||
in round *r*, height *h*.
|
||||
@@ -106,9 +108,9 @@ Let *e<sup>r</sup><sub>p</sub>* be the vote extension that the Application of a
|
||||
Let *w<sup>r</sup><sub>p</sub>* be the proposed block that *p*'s Tendermint passes to the Application via `RequestExtendVote`
|
||||
in round *r*, height *h*.
|
||||
|
||||
* Requirement 8 [`ExtendVote`, `VerifyVoteExtension`, coherence]: For any two correct processes *p* and *q*, if *q*
|
||||
receives *e<sup>r</sup><sub>p</sub>*
|
||||
from *p* in height *h*, *q*'s Application returns Accept in `ResponseVerifyVoteExtension`.
|
||||
* Requirement 8 [`ExtendVote`, `VerifyVoteExtension`, coherence]: For any two different correct
|
||||
processes *p* and *q*, if *q* receives *e<sup>r</sup><sub>p</sub>* from *p* in height *h*, *q*'s
|
||||
Application returns Accept in `ResponseVerifyVoteExtension`.
|
||||
|
||||
Requirement 8 constrains the creation and handling of vote extensions in a similar way as Requirement 5
|
||||
constrains the creation and handling of proposed blocks.
|
||||
@@ -170,8 +172,8 @@ Finally, notice that neither `PrepareProposal` nor `ExtendVote` have determinism
|
||||
requirements associated.
|
||||
Indeed, `PrepareProposal` is not required to be deterministic:
|
||||
|
||||
* *v'<sub>p</sub>* may depend on *v<sub>p</sub>* and *s<sub>p,h-1</sub>*, but may also depend on other values or operations.
|
||||
* *v<sub>p</sub> = v<sub>q</sub> ⇏ v'<sub>p</sub> = v'<sub>q</sub>*.
|
||||
* *u<sub>p</sub>* may depend on *v<sub>p</sub>* and *s<sub>p,h-1</sub>*, but may also depend on other values or operations.
|
||||
* *v<sub>p</sub> = v<sub>q</sub> ⇏ u<sub>p</sub> = u<sub>q</sub>*.
|
||||
|
||||
Likewise, `ExtendVote` can also be non-deterministic:
|
||||
|
||||
@@ -365,12 +367,12 @@ For more information, see Section [State Sync](#state-sync).
|
||||
### Transaction Results
|
||||
|
||||
The Application is expected to return a list of
|
||||
[`ExecTxResult`](./abci%2B%2B_methods_002_draft.md#exectxresult) in
|
||||
[`ResponseFinalizeBlock`](./abci%2B%2B_methods_002_draft.md#finalizeblock). The list of transaction
|
||||
[`ExecTxResult`](./abci%2B%2B_methods.md#exectxresult) in
|
||||
[`ResponseFinalizeBlock`](./abci%2B%2B_methods.md#finalizeblock). The list of transaction
|
||||
results must respect the same order as the list of transactions delivered via
|
||||
[`RequestFinalizeBlock`](./abci%2B%2B_methods_002_draft.md#finalizeblock).
|
||||
[`RequestFinalizeBlock`](./abci%2B%2B_methods.md#finalizeblock).
|
||||
This section discusses the fields inside this structure, along with the fields in
|
||||
[`ResponseCheckTx`](./abci%2B%2B_methods_002_draft.md#checktx),
|
||||
[`ResponseCheckTx`](./abci%2B%2B_methods.md#checktx),
|
||||
whose semantics are similar.
|
||||
|
||||
The `Info` and `Log` fields are
|
||||
@@ -471,12 +473,12 @@ events took place during their execution.
|
||||
### Updating the Validator Set
|
||||
|
||||
The application may set the validator set during
|
||||
[`InitChain`](./abci%2B%2B_methods_002_draft.md#initchain), and may update it during
|
||||
[`FinalizeBlock`](./abci%2B%2B_methods_002_draft.md#finalizeblock)
|
||||
[`InitChain`](./abci%2B%2B_methods.md#initchain), and may update it during
|
||||
[`FinalizeBlock`](./abci%2B%2B_methods.md#finalizeblock)
|
||||
(next block execution mode) or
|
||||
[`PrepareProposal`](./abci%2B%2B_methods_002_draft.md#prepareproposal)/[`ProcessProposal`](./abci%2B%2B_methods_002_draft.md#processproposal)
|
||||
[`PrepareProposal`](./abci%2B%2B_methods.md#prepareproposal)/[`ProcessProposal`](./abci%2B%2B_methods.md#processproposal)
|
||||
(same block execution mode). In all cases, a structure of type
|
||||
[`ValidatorUpdate`](./abci%2B%2B_methods_002_draft.md#validatorupdate) is returned.
|
||||
[`ValidatorUpdate`](./abci%2B%2B_methods.md#validatorupdate) is returned.
|
||||
|
||||
The `InitChain` method, used to initialize the Application, can return a list of validators.
|
||||
If the list is empty, Tendermint will use the validators loaded from the genesis
|
||||
@@ -510,7 +512,7 @@ Applications must ensure that
|
||||
`MaxTotalVotingPower = MaxInt64 / 8`
|
||||
|
||||
Note the updates returned after processing the block at height `H` will only take effect
|
||||
at block `H+2` (see Section [Methods](./abci%2B%2B_methods_002_draft.md)).
|
||||
at block `H+2` (see Section [Methods](./abci%2B%2B_methods.md)).
|
||||
|
||||
### Consensus Parameters
|
||||
|
||||
@@ -518,10 +520,10 @@ at block `H+2` (see Section [Methods](./abci%2B%2B_methods_002_draft.md)).
|
||||
They enforce certain limits in the blockchain, like the maximum size
|
||||
of blocks, amount of gas used in a block, and the maximum acceptable age of
|
||||
evidence. They can be set in
|
||||
[`InitChain`](./abci%2B%2B_methods_002_draft.md#initchain), and updated in
|
||||
[`FinalizeBlock`](./abci%2B%2B_methods_002_draft.md#finalizeblock)
|
||||
[`InitChain`](./abci%2B%2B_methods.md#initchain), and updated in
|
||||
[`FinalizeBlock`](./abci%2B%2B_methods.md#finalizeblock)
|
||||
(next block execution mode) or
|
||||
[`PrepareProposal`](./abci%2B%2B_methods_002_draft.md#prepareproposal)/[`ProcessProposal`](./abci%2B%2B_methods_002_draft.md#processproposal)
|
||||
[`PrepareProposal`](./abci%2B%2B_methods.md#prepareproposal)/[`ProcessProposal`](./abci%2B%2B_methods.md#processproposal)
|
||||
(same block execution model).
|
||||
These parameters are deterministically set and/or updated by the Application, so
|
||||
all full nodes have the same value at a given height.
|
||||
@@ -672,14 +674,33 @@ node has received all precommits for a block, forgoing the remaining commit time
|
||||
Setting this parameter to `false` (the default) causes Tendermint to wait
|
||||
for the full commit timeout configured in `TimeoutParams.Commit`.
|
||||
|
||||
##### ABCIParams.VoteExtensionsEnableHeight
|
||||
|
||||
This parameter is either 0 or a positive height at which vote extensions
|
||||
become mandatory. If the value is zero (which is the default), vote
|
||||
extensions are not required. Otherwise, at all heights greater than the
|
||||
configured height `H` vote extensions must be present (even if empty).
|
||||
When the configured height `H` is reached, `PrepareProposal` will not
|
||||
include vote extensions yet, but `ExtendVote` and `VerifyVoteExtension` will
|
||||
be called. Then, when reaching height `H+1`, `PrepareProposal` will
|
||||
include the vote extensions from height `H`. For all heights after `H`
|
||||
|
||||
* vote extensions cannot be disabled,
|
||||
* they are mandatory: all precommit messages sent MUST have an extension
|
||||
attached. Nevetheless, the application MAY provide 0-length
|
||||
extensions.
|
||||
|
||||
Must always be set to a future height. Once set to a value different from
|
||||
0, its value must not be changed.
|
||||
|
||||
#### Updating Consensus Parameters
|
||||
|
||||
The application may set the `ConsensusParams` during
|
||||
[`InitChain`](./abci%2B%2B_methods_002_draft.md#initchain),
|
||||
[`InitChain`](./abci%2B%2B_methods.md#initchain),
|
||||
and update them during
|
||||
[`FinalizeBlock`](./abci%2B%2B_methods_002_draft.md#finalizeblock)
|
||||
[`FinalizeBlock`](./abci%2B%2B_methods.md#finalizeblock)
|
||||
(next block execution mode) or
|
||||
[`PrepareProposal`](./abci%2B%2B_methods_002_draft.md#prepareproposal)/[`ProcessProposal`](./abci%2B%2B_methods_002_draft.md#processproposal)
|
||||
[`PrepareProposal`](./abci%2B%2B_methods.md#prepareproposal)/[`ProcessProposal`](./abci%2B%2B_methods.md#processproposal)
|
||||
(same block execution mode).
|
||||
If the `ConsensusParams` is empty, it will be ignored. Each field
|
||||
that is not empty will be applied in full. For instance, if updating the
|
||||
@@ -885,7 +906,7 @@ truncated block history - users are advised to consider the broader network impl
|
||||
terms of block availability and auditability. This functionality may be added in the future.
|
||||
|
||||
For details on the specific ABCI calls and types, see the
|
||||
[methods](abci%2B%2B_methods_002_draft.md) section.
|
||||
[methods](abci%2B%2B_methods.md) section.
|
||||
|
||||
#### Taking Snapshots
|
||||
|
||||
468
spec/abci++/abci++_basic_concepts.md
Normal file
468
spec/abci++/abci++_basic_concepts.md
Normal file
@@ -0,0 +1,468 @@
|
||||
---
|
||||
order: 1
|
||||
title: Overview and basic concepts
|
||||
---
|
||||
|
||||
## Outline
|
||||
|
||||
- [ABCI++ vs. ABCI](#abci-vs-abci)
|
||||
- [Method overview](#method-overview)
|
||||
- [Consensus/block execution methods](#consensusblock-execution-methods)
|
||||
- [Mempool methods](#mempool-methods)
|
||||
- [Info methods](#info-methods)
|
||||
- [State-sync methods](#state-sync-methods)
|
||||
- [Next-block execution vs. same-block execution](#next-block-execution-vs-same-block-execution)
|
||||
- [Tendermint proposal timeout](#tendermint-proposal-timeout)
|
||||
- [Deterministic State-Machine Replication](#deterministic-state-machine-replication)
|
||||
- [Events](#events)
|
||||
- [Evidence](#evidence)
|
||||
- [Errors](#errors)
|
||||
|
||||
# Overview and basic concepts
|
||||
|
||||
## ABCI++ vs. ABCI
|
||||
|
||||
[↑ Back to Outline](#outline)
|
||||
|
||||
The Application's main role is to execute blocks decided (a.k.a. finalized) by consensus. The
|
||||
decided blocks are the main consensus's ouput to the (replicated) Application. With ABCI, the
|
||||
application only interacts with consensus at *decision* time. This restricted mode of interaction
|
||||
prevents numerous features for the Application, including many scalability improvements that are
|
||||
now better understood than when ABCI was first written. For example, many ideas proposed to improve
|
||||
scalability can be boiled down to "make the block proposers do work, so the network does not have
|
||||
to". This includes optimizations such as transaction level signature aggregation, state transition
|
||||
proofs, etc. Furthermore, many new security properties cannot be achieved in the current paradigm,
|
||||
as the Application cannot require validators to do more than executing the transactions contained in
|
||||
finalized blocks. This includes features such as threshold cryptography, and guaranteed IBC
|
||||
connection attempts.
|
||||
|
||||
ABCI++ addresses these limitations by allowing the application to intervene at three key places of
|
||||
consensus execution: (a) at the moment a new proposal is to be created, (b) at the moment a
|
||||
proposal is to be validated, and (c) at the moment a (precommit) vote is sent/received. The new
|
||||
interface allows block proposers to perform application-dependent work in a block through the
|
||||
`PrepareProposal` method (a); validators to perform application-dependent work and checks in a
|
||||
proposed block through the `ProcessProposal` method (b); and applications to require their validators
|
||||
do more than just validate blocks through the `ExtendVote` and `VerifyVoteExtension` methods (c).
|
||||
Furthermore, ABCI++ coalesces {`BeginBlock`, [`DeliverTx`], `EndBlock`} into `FinalizeBlock`, as a
|
||||
simplified, efficient way to deliver a decided block to the Application.
|
||||
|
||||
## Method overview
|
||||
|
||||
[↑ Back to Outline](#outline)
|
||||
|
||||
Methods can be classified into four categories: *consensus*, *mempool*, *info*, and *state-sync*.
|
||||
|
||||
### Consensus/block execution methods
|
||||
|
||||
The first time a new blockchain is started, Tendermint calls `InitChain`. From then on, method
|
||||
`FinalizeBlock` is executed upon the decision of each block, resulting in an updated Application
|
||||
state. During the execution of an instance of consensus, which decides the block for a given
|
||||
height, and before method `FinalizeBlock` is called, methods `PrepareProposal`, `ProcessProposal`,
|
||||
`ExtendVote`, and `VerifyVoteExtension` may be called several times. See
|
||||
[Tendermint's expected behavior](abci++_tmint_expected_behavior.md) for details on the possible
|
||||
call sequences of these methods.
|
||||
|
||||
- [**InitChain:**](./abci++_methods.md#initchain) This method initializes the blockchain.
|
||||
Tendermint calls it once upon genesis.
|
||||
|
||||
- [**PrepareProposal:**](./abci++_methods.md#prepareproposal) It allows the block
|
||||
proposer to perform application-dependent work in a block before proposing it.
|
||||
This enables, for instance, batch optimizations to a block, which has been empirically
|
||||
demonstrated to be a key component for improved performance. Method `PrepareProposal` is called
|
||||
every time Tendermint is about to broadcast a Proposal message, but no previous proposal has
|
||||
been locked at the Tendermint level. Tendermint gathers outstanding transactions from the
|
||||
mempool, generates a block header, and uses them to create a block to propose. Then, it calls
|
||||
`RequestPrepareProposal` with the newly created proposal, called *raw proposal*. The Application
|
||||
can make changes to the raw proposal, such as modifying transactions, and returns the
|
||||
(potentially) modified proposal, called *prepared proposal* in the `ResponsePrepareProposal`
|
||||
call. The logic modifying the raw proposal can be non-deterministic.
|
||||
|
||||
- [**ProcessProposal:**](./abci++_methods.md#processproposal) It allows a validator to
|
||||
perform application-dependent work in a proposed block. This enables features such as immediate
|
||||
block execution, and allows the Application to reject invalid blocks.
|
||||
Tendermint calls it when it receives a proposal and the Tendermint algorithms has not locked on a
|
||||
value. The Application cannot modify the proposal at this point but can reject it if it is
|
||||
invalid. If that is the case, Tendermint will prevote `nil` on the proposal, which has
|
||||
strong liveness implications for Tendermint. As a general rule, the Application
|
||||
SHOULD accept a prepared proposal passed via `ProcessProposal`, even if a part of
|
||||
the proposal is invalid (e.g., an invalid transaction); the Application can
|
||||
ignore the invalid part of the prepared proposal at block execution time.
|
||||
|
||||
- [**ExtendVote:**](./abci++_methods.md#extendvote) It allows applications to force their
|
||||
validators to do more than just validate within consensus. `ExtendVote` allows applications to
|
||||
include non-deterministic data, opaque to Tendermint, to precommit messages (the final round of
|
||||
voting). The data, called *vote extension*, will be broadcast and received together with the
|
||||
vote it is extending, and will be made available to the Application in the next height,
|
||||
in the rounds where the local process is the proposer.
|
||||
Tendermint calls `ExtendVote` when it is about to send a non-`nil` precommit message.
|
||||
If the Application does not have vote extension information to provide at that time, it returns
|
||||
a 0-length byte array as its vote extension.
|
||||
|
||||
- [**VerifyVoteExtension:**](./abci++_methods.md#verifyvoteextension) It allows
|
||||
validators to validate the vote extension data attached to a precommit message. If the validation
|
||||
fails, the whole precommit message will be deemed invalid and ignored by Tendermint.
|
||||
This has a negative impact on Tendermint's liveness, i.e., if vote extensions repeatedly cannot be
|
||||
verified by correct validators, Tendermint may not be able to finalize a block even if sufficiently
|
||||
many (+2/3) validators send precommit votes for that block. Thus, `VerifyVoteExtension`
|
||||
should be used with special care.
|
||||
As a general rule, an Application that detects an invalid vote extension SHOULD
|
||||
accept it in `ResponseVerifyVoteExtension` and ignore it in its own logic. Tendermint calls it when
|
||||
a process receives a precommit message with a (possibly empty) vote extension.
|
||||
|
||||
- [**FinalizeBlock:**](./abci++_methods.md#finalizeblock) It delivers a decided block to the
|
||||
Application. The Application must execute the transactions in the block deterministically and
|
||||
update its state accordingly. Cryptographic commitments to the block and transaction results,
|
||||
returned via the corresponding parameters in `ResponseFinalizeBlock`, are included in the header
|
||||
of the next block. Tendermint calls it when a new block is decided.
|
||||
|
||||
- [**Commit:**](./abci++_methods.md#commit) Instructs the Application to persist its
|
||||
state. It is a fundamental part of Tendermint's crash-recovery mechanism that ensures the
|
||||
synchronization between Tendermint and the Applicatin upon recovery. Tendermint calls it just after
|
||||
having persisted the data returned by `ResponseFinalizeBlock`. The Application can now discard
|
||||
any state or data except the one resulting from executing the transactions in the decided block.
|
||||
|
||||
### Mempool methods
|
||||
|
||||
- [**CheckTx:**](./abci++_methods.md#checktx) This method allows the Application to validate
|
||||
transactions. Validation can be stateless (e.g., checking signatures ) or stateful
|
||||
(e.g., account balances). The type of validation performed is up to the application. If a
|
||||
transaction passes the validation, then Tendermint adds it to the mempool; otherwise the
|
||||
transaction is discarded.
|
||||
Tendermint calls it when it receives a new transaction either coming from an external
|
||||
user (e.g., a client) or another node. Furthermore, Tendermint can be configured to call
|
||||
re-`CheckTx` on all outstanding transactions in the mempool after calling `Commit`for a block.
|
||||
|
||||
### Info methods
|
||||
|
||||
- [**Info:**](./abci++_methods.md#info) Used to sync Tendermint with the Application during a
|
||||
handshake that happens upon recovery, or on startup when state-sync is used.
|
||||
|
||||
- [**Query:**](./abci++_methods.md#query) This method can be used to query the Application for
|
||||
information about the application state.
|
||||
|
||||
### State-sync methods
|
||||
|
||||
State sync allows new nodes to rapidly bootstrap by discovering, fetching, and applying
|
||||
state machine (application) snapshots instead of replaying historical blocks. For more details, see the
|
||||
[state sync documentation](../p2p/messages/state-sync.md).
|
||||
|
||||
New nodes discover and request snapshots from other nodes in the P2P network.
|
||||
A Tendermint node that receives a request for snapshots from a peer will call
|
||||
`ListSnapshots` on its Application. The Application returns the list of locally available
|
||||
snapshots.
|
||||
Note that the list does not contain the actual snapshots but metadata about them: height at which
|
||||
the snapshot was taken, application-specific verification data and more (see
|
||||
[snapshot data type](./abci++_methods.md#snapshot) for more details). After receiving a
|
||||
list of available snapshots from a peer, the new node can offer any of the snapshots in the list to
|
||||
its local Application via the `OfferSnapshot` method. The Application can check at this point the
|
||||
validity of the snapshot metadata.
|
||||
|
||||
Snapshots may be quite large and are thus broken into smaller "chunks" that can be
|
||||
assembled into the whole snapshot. Once the Application accepts a snapshot and
|
||||
begins restoring it, Tendermint will fetch snapshot "chunks" from existing nodes.
|
||||
The node providing "chunks" will fetch them from its local Application using
|
||||
the `LoadSnapshotChunk` method.
|
||||
|
||||
As the new node receives "chunks" it will apply them sequentially to the local
|
||||
application with `ApplySnapshotChunk`. When all chunks have been applied, the
|
||||
Application's `AppHash` is retrieved via an `Info` query.
|
||||
To ensure that the sync proceeded correctly, Tendermint compares the local Application's `AppHash`
|
||||
to the `AppHash` stored on the blockchain (verified via
|
||||
[light client verification](../light-client/verification/README.md)).
|
||||
|
||||
In summary:
|
||||
|
||||
- [**ListSnapshots:**](./abci++_methods.md#listsnapshots) Used by nodes to discover available
|
||||
snapshots on peers.
|
||||
|
||||
- [**OfferSnapshot:**](./abci++_methods.md#offersnapshot) When a node receives a snapshot from a
|
||||
peer, Tendermint uses this method to offer the snapshot to the Application.
|
||||
|
||||
- [**LoadSnapshotChunk:**](./abci++_methods.md#loadsnapshotchunk) Used by Tendermint to retrieve
|
||||
snapshot chunks from the Application to send to peers.
|
||||
|
||||
- [**ApplySnapshotChunk:**](./abci++_methods.md#applysnapshotchunk) Used by Tendermint to hand
|
||||
snapshot chunks to the Application.
|
||||
|
||||
### Other methods
|
||||
|
||||
Additionally, there is a [**Flush**](./abci++_methods.md#flush) method that is called on every connection,
|
||||
and an [**Echo**](./abci++_methods.md#echo) method that is used for debugging.
|
||||
|
||||
More details on managing state across connections can be found in the section on
|
||||
[Managing Application State](./abci%2B%2B_app_requirements.md#managing-the-application-state-and-related-topics).
|
||||
|
||||
## Next-block execution vs. same-block execution
|
||||
|
||||
[↑ Back to Outline](#outline)
|
||||
|
||||
In the original ABCI protocol, the only moment when the Application had access to a
|
||||
block was after it was decided. This led to a block execution model, called *next-block
|
||||
execution*, where some fields hashed in a block header refer to the execution of the
|
||||
previous block, namely:
|
||||
|
||||
- the Merkle root of the Application's state
|
||||
- the transaction results
|
||||
- the consensus parameter updates
|
||||
- the validator updates
|
||||
|
||||
With ABCI++, an Application may be configured to keep using the next-block execution model, by
|
||||
executing the decided block in `FinalizeBlock`. However, the new methods introduced —
|
||||
`PrepareProposal` and `ProcessProposal` — disclose the entire proposed block to the
|
||||
Application, allowing for its immediate exectution. An Application implementing immediate execution
|
||||
may additionally wish to store certain data resulting from the block's execution in the same block
|
||||
that has just been executed. This brings about a new execution model, called
|
||||
*same-block execution*. An Application implementing this execution model, upon receiving a raw
|
||||
proposal via `RequestPrepareProposal` and potentially modifying its transaction list, fully
|
||||
executes the resulting prepared proposal as though it was the decided block (immediate execution),
|
||||
and the results of the block execution are used as follows:
|
||||
|
||||
- The block execution may generate a set of events. The Application should store these events and
|
||||
return them back to Tendermint during the `FinalizeBlock` call if the block is finally decided.
|
||||
- The Merkle root resulting from executing the prepared proposal is provided in
|
||||
`ResponsePrepareProposal` and thus refers to the **current block**. Tendermint
|
||||
will use it in the prepared proposal's header.
|
||||
- Likewise, the transaction results from executing the prepared proposal are
|
||||
provided in `ResponsePrepareProposal` and refer to the transactions in the
|
||||
**current block**. Tendermint will use them to calculate the results hash
|
||||
in the prepared proposal's header.
|
||||
- The consensus parameter updates and validator updates are also provided in
|
||||
`ResponsePrepareProposal` and reflect the result of the prepared proposal's
|
||||
execution. They come into force in height H+1 (as opposed to the H+2 rule
|
||||
in next-block execution model).
|
||||
|
||||
If the Application is configured to keep the next-block execution model, it will not
|
||||
provide any data in `ResponsePrepareProposal`, other than a potentially modified
|
||||
transaction list. The Application may nevertheless choose to perform immediate execution even in
|
||||
next-block execution mode, however same-block execution mode *requires* immediate execution.
|
||||
|
||||
The long term plan is for the execution model to be set in a new boolean parameter *same_block* in
|
||||
`ConsensusParams`. Once this parameter is introduced, it **must not** be changed once the
|
||||
blockchain has started, unless the Application developers *really* know what they are doing.
|
||||
However, modifying `ConsensusParams` structure cannot be done lightly if we are to
|
||||
preserve blockchain compatibility. Therefore we need an interim solution until
|
||||
soft upgrades are specified and implemented in Tendermint. This somewhat *unsafe*
|
||||
solution consists in Tendermint assuming same-block execution if the Application
|
||||
fills the above mentioned fields in `ResponsePrepareProposal`.
|
||||
|
||||
## Tendermint proposal timeout
|
||||
|
||||
Immediate execution requires the Application to fully execute the prepared block
|
||||
before returning from `PrepareProposal`, this means that Tendermint cannot make progress
|
||||
during the block execution.
|
||||
This stands on Tendermint's critical path: if the Application takes a long time
|
||||
executing the block, the default value of *TimeoutPropose* might not be sufficient
|
||||
to accommodate the long block execution time and non-proposer nodes might time
|
||||
out and prevote `nil`. The proposal, in this case, will probably be rejected and a new round will be necessary.
|
||||
|
||||
The Application is the best suited to provide a value for *TimeoutPropose* so
|
||||
that the block execution time upon `PrepareProposal` fits well in the propose
|
||||
timeout interval. Thus, the Application can adapt the value of *TimeoutPropose* at every height via
|
||||
`TimeoutParams.Propose`, contained in `ConsensusParams`.
|
||||
|
||||
## Deterministic State-Machine Replication
|
||||
|
||||
[↑ Back to Outline](#outline)
|
||||
|
||||
ABCI++ applications must implement deterministic finite-state machines to be
|
||||
securely replicated by the Tendermint consensus engine. This means block execution
|
||||
must be strictly deterministic: given the same
|
||||
ordered set of transactions, all nodes will compute identical responses, for all
|
||||
successive `FinalizeBlock` calls. This is critical because the
|
||||
responses are included in the header of the next block, either via a Merkle root
|
||||
or directly, so all nodes must agree on exactly what they are.
|
||||
|
||||
For this reason, it is recommended that application state is not exposed to any
|
||||
external user or process except via the ABCI connections to a consensus engine
|
||||
like Tendermint Core. The Application must only change its state based on input
|
||||
from block execution (`FinalizeBlock` calls), and not through
|
||||
any other kind of request. This is the only way to ensure all nodes see the same
|
||||
transactions and compute the same results.
|
||||
|
||||
Some Applications may choose to implement immediate execution, which entails executing the blocks
|
||||
that are about to be proposed (via `PrepareProposal`), and those that the Application is asked to
|
||||
validate (via `ProcessProposal`). However, the state changes caused by processing those
|
||||
proposed blocks must never replace the previous state until `FinalizeBlock` confirms
|
||||
the block decided.
|
||||
|
||||
Additionally, vote extensions or the validation thereof (via `ExtendVote` or
|
||||
`VerifyVoteExtension`) must *never* have side effects on the current state.
|
||||
They can only be used when their data is provided in a `RequestPrepareProposal` call.
|
||||
|
||||
If there is some non-determinism in the state machine, consensus will eventually
|
||||
fail as nodes disagree over the correct values for the block header. The
|
||||
non-determinism must be fixed and the nodes restarted.
|
||||
|
||||
Sources of non-determinism in applications may include:
|
||||
|
||||
- Hardware failures
|
||||
- Cosmic rays, overheating, etc.
|
||||
- Node-dependent state
|
||||
- Random numbers
|
||||
- Time
|
||||
- Underspecification
|
||||
- Library version changes
|
||||
- Race conditions
|
||||
- Floating point numbers
|
||||
- JSON or protobuf serialization
|
||||
- Iterating through hash-tables/maps/dictionaries
|
||||
- External Sources
|
||||
- Filesystem
|
||||
- Network calls (eg. some external REST API service)
|
||||
|
||||
See [#56](https://github.com/tendermint/abci/issues/56) for the original discussion.
|
||||
|
||||
Note that some methods (`Query, FinalizeBlock`) return non-deterministic data in the form
|
||||
of `Info` and `Log` fields. The `Log` is intended for the literal output from the Application's
|
||||
logger, while the `Info` is any additional info that should be returned. These are the only fields
|
||||
that are not included in block header computations, so we don't need agreement
|
||||
on them. All other fields in the `Response*` must be strictly deterministic.
|
||||
|
||||
## Events
|
||||
|
||||
[↑ Back to Outline](#outline)
|
||||
|
||||
Method `FinalizeBlock` includes an `events` field at the top level in its
|
||||
`Response*`, and one `events` field per transaction included in the block.
|
||||
Applications may respond to this ABCI++ method with an event list for each executed
|
||||
transaction, and a general event list for the block itself.
|
||||
Events allow applications to associate metadata with transactions and blocks.
|
||||
Events returned via `FinalizeBlock` do not impact Tendermint consensus in any way
|
||||
and instead exist to power subscriptions and queries of Tendermint state.
|
||||
|
||||
An `Event` contains a `type` and a list of `EventAttributes`, which are key-value
|
||||
string pairs denoting metadata about what happened during the method's (or transaction's)
|
||||
execution. `Event` values can be used to index transactions and blocks according to what
|
||||
happened during their execution.
|
||||
|
||||
Each event has a `type` which is meant to categorize the event for a particular
|
||||
`Response*` or `Tx`. A `Response*` or `Tx` may contain multiple events with duplicate
|
||||
`type` values, where each distinct entry is meant to categorize attributes for a
|
||||
particular event. Every key and value in an event's attributes must be UTF-8
|
||||
encoded strings along with the event type itself.
|
||||
|
||||
```protobuf
|
||||
message Event {
|
||||
string type = 1;
|
||||
repeated EventAttribute attributes = 2;
|
||||
}
|
||||
```
|
||||
|
||||
The attributes of an `Event` consist of a `key`, a `value`, and an `index` flag. The
|
||||
index flag notifies the Tendermint indexer to index the attribute. The value of
|
||||
the `index` flag is non-deterministic and may vary across different nodes in the network.
|
||||
|
||||
```protobuf
|
||||
message EventAttribute {
|
||||
bytes key = 1;
|
||||
bytes value = 2;
|
||||
bool index = 3; // nondeterministic
|
||||
}
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```go
|
||||
abci.ResponseFinalizeBlock{
|
||||
// ...
|
||||
Events: []abci.Event{
|
||||
{
|
||||
Type: "validator.provisions",
|
||||
Attributes: []abci.EventAttribute{
|
||||
abci.EventAttribute{Key: []byte("address"), Value: []byte("..."), Index: true},
|
||||
abci.EventAttribute{Key: []byte("amount"), Value: []byte("..."), Index: true},
|
||||
abci.EventAttribute{Key: []byte("balance"), Value: []byte("..."), Index: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: "validator.provisions",
|
||||
Attributes: []abci.EventAttribute{
|
||||
abci.EventAttribute{Key: []byte("address"), Value: []byte("..."), Index: true},
|
||||
abci.EventAttribute{Key: []byte("amount"), Value: []byte("..."), Index: false},
|
||||
abci.EventAttribute{Key: []byte("balance"), Value: []byte("..."), Index: false},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: "validator.slashed",
|
||||
Attributes: []abci.EventAttribute{
|
||||
abci.EventAttribute{Key: []byte("address"), Value: []byte("..."), Index: false},
|
||||
abci.EventAttribute{Key: []byte("amount"), Value: []byte("..."), Index: true},
|
||||
abci.EventAttribute{Key: []byte("reason"), Value: []byte("..."), Index: true},
|
||||
},
|
||||
},
|
||||
// ...
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Evidence
|
||||
|
||||
[↑ Back to Outline](#outline)
|
||||
|
||||
Tendermint's security model relies on the use of evidences of misbehavior. An evidence is an
|
||||
irrefutable proof of malicious behavior by a network participant. It is the responsibility of
|
||||
Tendermint to detect such malicious behavior. When malicious behavior is detected, Tendermint
|
||||
will gossip evidences of misbehavior to other nodes and commit the evidences to
|
||||
the chain once they are verified by a subset of validators. These evidences will then be
|
||||
passed on to the Application through ABCI++. It is the responsibility of the
|
||||
Application to handle evidence of misbehavior and exercise punishment.
|
||||
|
||||
There are two forms of evidence: Duplicate Vote and Light Client Attack. More
|
||||
information can be found in either [data structures](../core/data_structures.md)
|
||||
or [accountability](../light-client/accountability/).
|
||||
|
||||
EvidenceType has the following protobuf format:
|
||||
|
||||
```protobuf
|
||||
enum EvidenceType {
|
||||
UNKNOWN = 0;
|
||||
DUPLICATE_VOTE = 1;
|
||||
LIGHT_CLIENT_ATTACK = 2;
|
||||
}
|
||||
```
|
||||
|
||||
## Errors
|
||||
|
||||
[↑ Back to Outline](#outline)
|
||||
|
||||
The `Query`, and `CheckTx` methods include a `Code` field in their `Response*`.
|
||||
Field `Code` is meant to contain an application-specific response code.
|
||||
A response code of `0` indicates no error. Any other response code
|
||||
indicates to Tendermint that an error occurred.
|
||||
|
||||
These methods also return a `Codespace` string to Tendermint. This field is
|
||||
used to disambiguate `Code` values returned by different domains of the
|
||||
Application. The `Codespace` is a namespace for the `Code`.
|
||||
|
||||
Methods `Echo`, `Info`, and `InitChain` do not return errors.
|
||||
An error in any of these methods represents a critical issue that Tendermint
|
||||
has no reasonable way to handle. If there is an error in one
|
||||
of these methods, the Application must crash to ensure that the error is safely
|
||||
handled by an operator.
|
||||
|
||||
Method `FinalizeBlock` is a special case. It contains a number of
|
||||
`Code` and `Codespace` fields as part of type `ExecTxResult`. Each of
|
||||
these codes reports errors related to the transaction it is attached to.
|
||||
However, `FinalizeBlock` does not return errors at the top level, so the
|
||||
same considerations on critical issues made for `Echo`, `Info`, and
|
||||
`InitChain` also apply here.
|
||||
|
||||
The handling of non-zero response codes by Tendermint is described below.
|
||||
|
||||
### `CheckTx`
|
||||
|
||||
When Tendermint receives a `ResponseCheckTx` with a non-zero `Code`, the associated
|
||||
transaction will not be added to Tendermint's mempool or it will be removed if
|
||||
it is already included.
|
||||
|
||||
### `ExecTxResult` (as part of `FinalizeBlock`)
|
||||
|
||||
The `ExecTxResult` type delivers transaction results from the Application to Tendermint. When
|
||||
Tendermint receives a `ResponseFinalizeBlock` containing an `ExecTxResult` with a non-zero `Code`,
|
||||
the response code is logged. Past `Code` values can be queried by clients. As the transaction was
|
||||
part of a decided block, the `Code` does not influence Tendermint consensus.
|
||||
|
||||
### `Query`
|
||||
|
||||
When Tendermint receives a `ResponseQuery` with a non-zero `Code`, this code is
|
||||
returned directly to the client that initiated the query.
|
||||
@@ -1,404 +0,0 @@
|
||||
---
|
||||
order: 1
|
||||
title: Overview and basic concepts
|
||||
---
|
||||
|
||||
## Outline
|
||||
- [ABCI++ vs. ABCI](#abci-vs-abci)
|
||||
- [Methods overview](#methods-overview)
|
||||
- [Consensus methods](#consensus-methods)
|
||||
- [Mempool methods](#mempool-methods)
|
||||
- [Info methods](#info-methods)
|
||||
- [State-sync methods](#state-sync-methods)
|
||||
- [Next-block execution vs. same-block execution](#next-block-execution-vs-same-block-execution)
|
||||
- [Tendermint timeouts](#tendermint-timeouts-in-same-block-execution)
|
||||
- [Determinism](#determinism)
|
||||
- [Errors](#errors)
|
||||
- [Events](#events)
|
||||
- [Evidence](#evidence)
|
||||
|
||||
# Overview and basic concepts
|
||||
|
||||
## ABCI++ vs. ABCI
|
||||
[↑ Back to Outline](#outline)
|
||||
|
||||
With ABCI, the application can only act at one phase in consensus, immediately after a block has been finalized. This restriction on the application prevents numerous features for the application, including many scalability improvements that are now better understood than when ABCI was first written. For example, many of the scalability proposals can be boiled down to "Make the miner / block proposers / validators do work, so the network does not have to". This includes optimizations such as tx-level signature aggregation, state transition proofs, etc. Furthermore, many new security properties cannot be achieved in the current paradigm, as the application cannot enforce validators to do more than just finalize txs. This includes features such as threshold cryptography, and guaranteed IBC connection attempts.
|
||||
|
||||
ABCI++ overcomes these limitations by allowing the application to intervene at three key places of the block execution. The new interface allows block proposers to perform application-dependent work in a block through the `PrepareProposal` method; validators to perform application-dependent work in a proposed block through the `ProcessProposal` method; and applications to require their validators do more than just validate blocks, e.g., validator guaranteed IBC connection attempts, through the `ExtendVote` and `VerifyVoteExtension` methods. Furthermore, ABCI++ renames {`BeginBlock`, [`DeliverTx`], `EndBlock`} to `FinalizeBlock`, as a simplified way to deliver a decided block to the Application.
|
||||
|
||||
## Methods overview
|
||||
[↑ Back to Outline](#outline)
|
||||
|
||||
Methods can be classified into four categories: consensus, mempool, info, and state-sync.
|
||||
|
||||
### Consensus/block execution methods
|
||||
|
||||
The first time a new blockchain is started, Tendermint calls
|
||||
`InitChain`. From then on, method `FinalizeBlock` is executed at the end of each
|
||||
block, resulting in an updated Application state.
|
||||
During consensus execution of a block height, before method `FinalizeBlock` is
|
||||
called, methods `PrepareProposal`, `ProcessProposal`, `ExtendVote`, and
|
||||
`VerifyVoteExtension` may be called several times.
|
||||
See [Tendermint's expected behavior](abci++_tmint_expected_behavior_002_draft.md)
|
||||
for details on the possible call sequences of these methods.
|
||||
|
||||
* [**InitChain:**](./abci++_methods_002_draft.md#initchain) This method initializes the blockchain. Tendermint calls it once upon genesis.
|
||||
|
||||
* [**PrepareProposal:**](./abci++_methods_002_draft.md#prepareproposal) It allows the block proposer to perform application-dependent work in a block before using it as its proposal. This enables, for instance, batch optimizations to a block, which has been empirically demonstrated to be a key component for scaling. Method `PrepareProposal` is called every time Tendermint is about to send
|
||||
a proposal message, but no previous proposal has been locked at Tendermint level.
|
||||
Tendermint gathers outstanding transactions from the mempool, generates a block header, and uses
|
||||
them to create a block to propose. Then, it calls `RequestPrepareProposal`
|
||||
with the newly created proposal, called _raw proposal_. The Application can
|
||||
make changes to the raw proposal, such as modifying transactions, and returns
|
||||
the (potentially) modified proposal, called _prepared proposal_ in the
|
||||
`Response*` call. The logic modifying the raw proposal can be non-deterministic.
|
||||
|
||||
* [**ProcessProposal:**](./abci++_methods_002_draft.md#processproposal) It allows a validator to perform application-dependent work in a proposed block. This enables features such as allowing validators to reject a block according to whether the state machine deems it valid, and changing the block execution pipeline. Tendermint calls it when it receives a proposal and it is not locked on a block. The Application cannot
|
||||
modify the proposal at this point but can reject it if it realizes it is invalid.
|
||||
If that is the case, Tendermint will prevote `nil` on the proposal, which has
|
||||
strong liveness implications for Tendermint. As a general rule, the Application
|
||||
SHOULD accept a prepared proposal passed via `ProcessProposal`, even if a part of
|
||||
the proposal is invalid (e.g., an invalid transaction); the Application can
|
||||
ignore the invalid part of the prepared proposal at block execution time.
|
||||
|
||||
* [**ExtendVote:**](./abci++_methods_002_draft.md#extendvote) It allows applications to force their validators to do more than just validate within consensus. `ExtendVote` allows applications to include non-deterministic data, opaque to Tendermint, to precommit messages (the final round of voting).
|
||||
The data, called _vote extension_, will also be made available to the
|
||||
application in the next height, along with the vote it is extending, in the rounds
|
||||
where the local process is the proposer.
|
||||
If the Application does not have vote extension information to provide, it returns a 0-length byte array as its vote extension.
|
||||
Tendermint calls `ExtendVote` when is about to send a non-`nil` precommit message.
|
||||
|
||||
* [**VerifyVoteExtension:**](./abci++_methods_002_draft.md#verifyvoteextension) It allows validators to validate the vote extension data attached to a precommit message. If the validation fails, the precommit message will be deemed invalid and ignored
|
||||
by Tendermint. This has a negative impact on Tendermint's liveness, i.e., if vote extensions repeatedly cannot be verified by correct validators, Tendermint may not be able to finalize a block even if sufficiently many (+2/3) of the validators send precommit votes for that block. Thus, `VerifyVoteExtension` should be used with special care.
|
||||
As a general rule, an Application that detects an invalid vote extension SHOULD
|
||||
accept it in `ResponseVerifyVoteExtension` and ignore it in its own logic. Tendermint calls it when
|
||||
a process receives a precommit message with a (possibly empty) vote extension.
|
||||
|
||||
* [**FinalizeBlock:**](./abci++_methods_002_draft.md#finalizeblock) It delivers a decided block to the Application. The Application must execute the transactions in the block in order and update its state accordingly. Cryptographic commitments to the block and transaction results, via the corresponding
|
||||
parameters in `ResponseFinalizeBlock`, are included in the header of the next block. Tendermint calls it when a new block is decided.
|
||||
|
||||
### Mempool methods
|
||||
|
||||
* [**CheckTx:**](./abci++_methods_002_draft.md#checktx) This method allows the Application to validate transactions against its current state, e.g., checking signatures and account balances. If a transaction passes the validation, then tendermint adds it to its local mempool, discarding it otherwise. Tendermint calls it when it receives a new transaction either coming from an external user or another node. Furthermore, Tendermint can be configured to re-call `CheckTx` on any decided transaction (after `FinalizeBlock`).
|
||||
|
||||
### Info methods
|
||||
|
||||
* [**Info:**](./abci++_methods_002_draft.md#info) Used to sync Tendermint with the Application during a handshake that happens on startup.
|
||||
|
||||
* [**Query:**](./abci++_methods_002_draft.md#query) Clients can use this method to query the Application for information about the application state.
|
||||
|
||||
### State-sync methods
|
||||
|
||||
State sync allows new nodes to rapidly bootstrap by discovering, fetching, and applying
|
||||
state machine snapshots instead of replaying historical blocks. For more details, see the
|
||||
[state sync section](../p2p/messages/state-sync.md).
|
||||
|
||||
New nodes will discover and request snapshots from other nodes in the P2P network.
|
||||
A Tendermint node that receives a request for snapshots from a peer will call
|
||||
`ListSnapshots` on its Application. The Application returns the list of locally avaiable snapshots.
|
||||
Note that the list does not contain the actual snapshot but metadata about it: height at which the snapshot was taken, application-specific verification data and more (see [snapshot data type](./abci++_methods_002_draft.md#snapshot) for more details). After receiving a list of available snapshots from a peer, the new node can offer any of the snapshots in the list to its local Application via the `OfferSnapshot` method. The Application can check at this point the validity of the snapshot metadata.
|
||||
|
||||
Snapshots may be quite large and are thus broken into smaller "chunks" that can be
|
||||
assembled into the whole snapshot. Once the Application accepts a snapshot and
|
||||
begins restoring it, Tendermint will fetch snapshot "chunks" from existing nodes.
|
||||
The node providing "chunks" will fetch them from its local Application using
|
||||
the `LoadSnapshotChunk` method.
|
||||
|
||||
As the new node receives "chunks" it will apply them sequentially to the local
|
||||
application with `ApplySnapshotChunk`. When all chunks have been applied, the
|
||||
Application's `AppHash` is retrieved via an `Info` query.
|
||||
To ensure that the sync proceeded correctly, Tendermint compares the local Application's `AppHash` to the `AppHash` stored on the blockchain (verified via
|
||||
[light client verification](../light-client/verification/README.md)).
|
||||
|
||||
In summary:
|
||||
|
||||
* [**ListSnapshots:**](./abci++_methods_002_draft.md#listsnapshots) Used by nodes to discover available snapshots on peers.
|
||||
|
||||
* [**LoadSnapshotChunk:**](./abci++_methods_002_draft.md#loadsnapshotchunk) Used by Tendermint to retrieve snapshot chunks from the application to send to peers.
|
||||
|
||||
* [**OfferSnapshot:**](./abci++_methods_002_draft.md#offersnapshot) When a node receives a snapshot from a peer, Tendermint uses this method to offer the snapshot to the Application.
|
||||
|
||||
* [**ApplySnapshotChunk:**](./abci++_methods_002_draft.md#applysnapshotchunk) Used by Tendermint to hand snapshot chunks to the Application.
|
||||
|
||||
### Other methods
|
||||
|
||||
Additionally, there is a [**Flush**](./abci++_methods_002_draft.md#flush) method that is called on every connection,
|
||||
and an [**Echo**](./abci++_methods_002_draft.md#echo) method that is just for debugging.
|
||||
|
||||
More details on managing state across connections can be found in the section on
|
||||
[ABCI Applications](../abci/apps.md).
|
||||
|
||||
## Next-block execution vs. same-block execution
|
||||
[↑ Back to Outline](#outline)
|
||||
|
||||
In the original ABCI protocol, the only moment when the Application had access to a
|
||||
block was after it was decided. This led to a block execution model, called _next-block
|
||||
execution_, where some fields hashed in a block header refer to the execution of the
|
||||
previous block, namely:
|
||||
|
||||
* the Merkle root of the Application's state
|
||||
* the transaction results
|
||||
* the consensus parameter updates
|
||||
* the validator updates
|
||||
|
||||
With ABCI++, an Application may decide to keep using the next-block execution model, by doing all its processing in `FinalizeBlock`;
|
||||
however the new methods introduced, `PrepareProposal` and `ProcessProposal` allow
|
||||
for a new execution model, called _same-block execution_. An Application implementing
|
||||
this execution model, upon receiving a raw proposal via `RequestPrepareProposal`
|
||||
and potentially modifying its transaction list,
|
||||
fully executes the resulting prepared proposal as though it was the decided block.
|
||||
The results of the block execution are used as follows:
|
||||
|
||||
* The block execution may generate a set of events. The Application should store these events and return them back to Tendermint during the `FinalizeBlock` call if the block is finally decided.
|
||||
* The Merkle root resulting from executing the prepared proposal is provided in
|
||||
`ResponsePrepareProposal` and thus refers to the **current block**. Tendermint
|
||||
will use it in the prepared proposal's header.
|
||||
* likewise, the transaction results from executing the prepared proposal are
|
||||
provided in `ResponsePrepareProposal` and refer to the transactions in the
|
||||
**current block**. Tendermint will use them to calculate the results hash
|
||||
in the prepared proposal's header.
|
||||
* The consensus parameter updates and validator updates are also provided in
|
||||
`ResponsePrepareProposal` and reflect the result of the prepared proposal's
|
||||
execution. They come into force in height H+1 (as opposed to the H+2 rule
|
||||
in next-block execution model).
|
||||
|
||||
If the Application decides to keep the next-block execution model, it will not
|
||||
provide any data in `ResponsePrepareProposal`, other than an optionally modified
|
||||
transaction list.
|
||||
|
||||
In the long term, the execution model will be set in a new boolean parameter
|
||||
*same_block* in `ConsensusParams`.
|
||||
It **must not** be changed once the blockchain has started unless the Application
|
||||
developers _really_ know what they are doing.
|
||||
However, modifying `ConsensusParams` structure cannot be done lightly if we are to
|
||||
preserve blockchain compatibility. Therefore we need an interim solution until
|
||||
soft upgrades are specified and implemented in Tendermint. This somewhat _unsafe_
|
||||
solution consists in Tendermint assuming same-block execution if the Application
|
||||
fills the above mentioned fields in `ResponsePrepareProposal`.
|
||||
|
||||
### Tendermint timeouts in same-block execution
|
||||
|
||||
The new same-block execution mode requires the Application to fully execute the
|
||||
prepared block at `PrepareProposal` time. This execution is synchronous, so
|
||||
Tendermint cannot make progress until the Application returns from `PrepareProposal`.
|
||||
This stands on Tendermint's critical path: if the Application takes a long time
|
||||
executing the block, the default value of _TimeoutPropose_ might not be sufficient
|
||||
to accommodate the long block execution time and non-proposer processes might time
|
||||
out and prevote `nil`, thus starting a further round unnecessarily.
|
||||
|
||||
The Application is the best suited to provide a value for _TimeoutPropose_ so
|
||||
that the block execution time upon `PrepareProposal` fits well in the propose
|
||||
timeout interval.
|
||||
|
||||
Currently, the Application can override the value of _TimeoutPropose_ via the
|
||||
`config.toml` file. In the future, `ConsensusParams` will have an extra field
|
||||
with the current _TimeoutPropose_ value so that the Application can adapt it at every height.
|
||||
|
||||
## Determinism
|
||||
[↑ Back to Outline](#outline)
|
||||
|
||||
ABCI++ applications must implement deterministic finite-state machines to be
|
||||
securely replicated by the Tendermint consensus engine. This means block execution
|
||||
over the Consensus Connection must be strictly deterministic: given the same
|
||||
ordered set of transactions, all nodes will compute identical responses, for all
|
||||
successive `FinalizeBlock` calls. This is critical because the
|
||||
responses are included in the header of the next block, either via a Merkle root
|
||||
or directly, so all nodes must agree on exactly what they are.
|
||||
|
||||
For this reason, it is recommended that application state is not exposed to any
|
||||
external user or process except via the ABCI connections to a consensus engine
|
||||
like Tendermint Core. The Application must only change its state based on input
|
||||
from block execution (`FinalizeBlock` calls), and not through
|
||||
any other kind of request. This is the only way to ensure all nodes see the same
|
||||
transactions and compute the same results.
|
||||
|
||||
Some Applications may choose to execute the blocks that are about to be proposed
|
||||
(via `PrepareProposal`), or those that the Application is asked to validate
|
||||
(via `ProcessProposal`). However, the state changes caused by processing those
|
||||
proposed blocks must never replace the previous state until `FinalizeBlock` confirms
|
||||
the block decided.
|
||||
|
||||
Additionally, vote extensions or the validation thereof (via `ExtendVote` or
|
||||
`VerifyVoteExtension`) must _never_ have side effects on the current state.
|
||||
They can only be used when their data is provided in a `RequestPrepareProposal` call.
|
||||
|
||||
If there is some non-determinism in the state machine, consensus will eventually
|
||||
fail as nodes disagree over the correct values for the block header. The
|
||||
non-determinism must be fixed and the nodes restarted.
|
||||
|
||||
Sources of non-determinism in applications may include:
|
||||
|
||||
* Hardware failures
|
||||
* Cosmic rays, overheating, etc.
|
||||
* Node-dependent state
|
||||
* Random numbers
|
||||
* Time
|
||||
* Underspecification
|
||||
* Library version changes
|
||||
* Race conditions
|
||||
* Floating point numbers
|
||||
* JSON or protobuf serialization
|
||||
* Iterating through hash-tables/maps/dictionaries
|
||||
* External Sources
|
||||
* Filesystem
|
||||
* Network calls (eg. some external REST API service)
|
||||
|
||||
See [#56](https://github.com/tendermint/abci/issues/56) for original discussion.
|
||||
|
||||
Note that some methods (`Query, CheckTx, FinalizeBlock`) return
|
||||
explicitly non-deterministic data in the form of `Info` and `Log` fields. The `Log` is
|
||||
intended for the literal output from the Application's logger, while the
|
||||
`Info` is any additional info that should be returned. These are the only fields
|
||||
that are not included in block header computations, so we don't need agreement
|
||||
on them. All other fields in the `Response*` must be strictly deterministic.
|
||||
|
||||
## Errors
|
||||
[↑ Back to Outline](#outline)
|
||||
|
||||
The `Query`, and `CheckTx` methods include a `Code` field in their `Response*`.
|
||||
The `Code` field is also included in type `TxResult`, used by
|
||||
method `FinalizeBlock`'s `Response*`.
|
||||
Field `Code` is meant to contain an application-specific response code.
|
||||
A response code of `0` indicates no error. Any other response code
|
||||
indicates to Tendermint that an error occurred.
|
||||
|
||||
These methods also return a `Codespace` string to Tendermint. This field is
|
||||
used to disambiguate `Code` values returned by different domains of the
|
||||
Application. The `Codespace` is a namespace for the `Code`.
|
||||
|
||||
Methods `Echo`, `Info`, and `InitChain` do not return errors.
|
||||
An error in any of these methods represents a critical issue that Tendermint
|
||||
has no reasonable way to handle. If there is an error in one
|
||||
of these methods, the Application must crash to ensure that the error is safely
|
||||
handled by an operator.
|
||||
|
||||
Method `FinalizeBlock` is a special case. It contains a number of
|
||||
`Code` and `Codespace` fields as part of type `TxResult`. Each of
|
||||
these codes reports errors related to the transaction it is attached to.
|
||||
However, `FinalizeBlock` does not return errors at the top level, so the
|
||||
same considerations on critical issues made for `Echo`, `Info`, and
|
||||
`InitChain` also apply here.
|
||||
|
||||
The handling of non-zero response codes by Tendermint is described below
|
||||
|
||||
### `CheckTx`
|
||||
|
||||
When Tendermint receives a `ResponseCheckTx` with a non-zero `Code`, the associated
|
||||
transaction will not be added to Tendermint's mempool or it will be removed if
|
||||
it is already included.
|
||||
|
||||
### `TxResult` (as part of `FinalizeBlock`)
|
||||
|
||||
The `TxResult` type delivers transactions from Tendermint to the Application.
|
||||
When Tendermint receives a `ResponseFinalizeBlock` containing a `TxResult`
|
||||
with a non-zero `Code`, the response code is logged.
|
||||
The transaction was already included in a block, so the `Code` does not influence
|
||||
Tendermint consensus.
|
||||
|
||||
### `Query`
|
||||
|
||||
When Tendermint receives a `ResponseQuery` with a non-zero `Code`, this code is
|
||||
returned directly to the client that initiated the query.
|
||||
|
||||
## Events
|
||||
[↑ Back to Outline](#outline)
|
||||
|
||||
Method `CheckTx` includes an `Events` field in its `Response*`.
|
||||
Method `FinalizeBlock` includes an `Events` field at the top level in its
|
||||
`Response*`, and one `events` field per transaction included in the block.
|
||||
Applications may respond to these ABCI++ methods with a set of events.
|
||||
Events allow applications to associate metadata about ABCI++ method execution with the
|
||||
transactions and blocks this metadata relates to.
|
||||
Events returned via these ABCI++ methods do not impact Tendermint consensus in any way
|
||||
and instead exist to power subscriptions and queries of Tendermint state.
|
||||
|
||||
An `Event` contains a `type` and a list of `EventAttributes`, which are key-value
|
||||
string pairs denoting metadata about what happened during the method's (or transaction's)
|
||||
execution. `Event` values can be used to index transactions and blocks according to what
|
||||
happened during their execution.
|
||||
|
||||
Each event has a `type` which is meant to categorize the event for a particular
|
||||
`Response*` or `Tx`. A `Response*` or `Tx` may contain multiple events with duplicate
|
||||
`type` values, where each distinct entry is meant to categorize attributes for a
|
||||
particular event. Every key and value in an event's attributes must be UTF-8
|
||||
encoded strings along with the event type itself.
|
||||
|
||||
```protobuf
|
||||
message Event {
|
||||
string type = 1;
|
||||
repeated EventAttribute attributes = 2;
|
||||
}
|
||||
```
|
||||
|
||||
The attributes of an `Event` consist of a `key`, a `value`, and an `index` flag. The
|
||||
index flag notifies the Tendermint indexer to index the attribute. The value of
|
||||
the `index` flag is non-deterministic and may vary across different nodes in the network.
|
||||
|
||||
```protobuf
|
||||
message EventAttribute {
|
||||
bytes key = 1;
|
||||
bytes value = 2;
|
||||
bool index = 3; // nondeterministic
|
||||
}
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```go
|
||||
abci.ResponseCheckTx{
|
||||
// ...
|
||||
Events: []abci.Event{
|
||||
{
|
||||
Type: "validator.provisions",
|
||||
Attributes: []abci.EventAttribute{
|
||||
abci.EventAttribute{Key: []byte("address"), Value: []byte("..."), Index: true},
|
||||
abci.EventAttribute{Key: []byte("amount"), Value: []byte("..."), Index: true},
|
||||
abci.EventAttribute{Key: []byte("balance"), Value: []byte("..."), Index: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: "validator.provisions",
|
||||
Attributes: []abci.EventAttribute{
|
||||
abci.EventAttribute{Key: []byte("address"), Value: []byte("..."), Index: true},
|
||||
abci.EventAttribute{Key: []byte("amount"), Value: []byte("..."), Index: false},
|
||||
abci.EventAttribute{Key: []byte("balance"), Value: []byte("..."), Index: false},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: "validator.slashed",
|
||||
Attributes: []abci.EventAttribute{
|
||||
abci.EventAttribute{Key: []byte("address"), Value: []byte("..."), Index: false},
|
||||
abci.EventAttribute{Key: []byte("amount"), Value: []byte("..."), Index: true},
|
||||
abci.EventAttribute{Key: []byte("reason"), Value: []byte("..."), Index: true},
|
||||
},
|
||||
},
|
||||
// ...
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Evidence
|
||||
[↑ Back to Outline](#outline)
|
||||
|
||||
Tendermint's security model relies on the use of "evidence". Evidence is proof of
|
||||
malicious behavior by a network participant. It is the responsibility of Tendermint
|
||||
to detect such malicious behavior. When malicious behavior is detected, Tendermint
|
||||
will gossip evidence of the behavior to other nodes and commit the evidence to
|
||||
the chain once it is verified by all validators. This evidence will then be
|
||||
passed on to the Application through ABCI++. It is the responsibility of the
|
||||
Application to handle the evidence and exercise punishment.
|
||||
|
||||
EvidenceType has the following protobuf format:
|
||||
|
||||
```protobuf
|
||||
enum EvidenceType {
|
||||
UNKNOWN = 0;
|
||||
DUPLICATE_VOTE = 1;
|
||||
LIGHT_CLIENT_ATTACK = 2;
|
||||
}
|
||||
```
|
||||
|
||||
There are two forms of evidence: Duplicate Vote and Light Client Attack. More
|
||||
information can be found in either [data structures](../core/data_structures.md)
|
||||
or [accountability](../light-client/accountability/)
|
||||
|
||||
@@ -9,10 +9,10 @@ This section is for those looking to implement their own ABCI Server, perhaps in
|
||||
a new programming language.
|
||||
|
||||
You are expected to have read all previous sections of ABCI++ specification, namely
|
||||
[Basic Concepts](./abci%2B%2B_basic_concepts_002_draft.md),
|
||||
[Methods](./abci%2B%2B_methods_002_draft.md),
|
||||
[Application Requirements](./abci%2B%2B_app_requirements_002_draft.md), and
|
||||
[Expected Behavior](./abci%2B%2B_tmint_expected_behavior_002_draft.md).
|
||||
[Basic Concepts](./abci%2B%2B_basic_concepts.md),
|
||||
[Methods](./abci%2B%2B_methods.md),
|
||||
[Application Requirements](./abci%2B%2B_app_requirements.md), and
|
||||
[Expected Behavior](./abci%2B%2B_tmint_expected_behavior.md).
|
||||
|
||||
## Message Protocol and Synchrony
|
||||
|
||||
@@ -25,7 +25,7 @@ or custom protobuf types.
|
||||
For more details on protobuf, see the [documentation](https://developers.google.com/protocol-buffers/docs/overview).
|
||||
|
||||
As of v0.36 requests are synchronous. For each of ABCI++'s four connections (see
|
||||
[Connections](./abci%2B%2B_app_requirements_002_draft.md)), when Tendermint issues a request to the
|
||||
[Connections](./abci%2B%2B_app_requirements.md)), when Tendermint issues a request to the
|
||||
Application, it will wait for the response before continuing execution. As a side effect,
|
||||
requests and responses are ordered for each connection, but not necessarily across connections.
|
||||
|
||||
@@ -38,22 +38,21 @@ title: Methods
|
||||
|
||||
* **Response**:
|
||||
|
||||
| Name | Type | Description | Field Number |
|
||||
|---------------------|--------|--------------------------------------------------|--------------|
|
||||
| data | string | Some arbitrary information | 1 |
|
||||
| version | string | The application software semantic version | 2 |
|
||||
| app_version | uint64 | The application protocol version | 3 |
|
||||
| last_block_height | int64 | Latest block for which the app has called Commit | 4 |
|
||||
| last_block_app_hash | bytes | Latest result of Commit | 5 |
|
||||
| Name | Type | Description | Field Number |
|
||||
|---------------------|--------|-----------------------------------------------------|--------------|
|
||||
| data | string | Some arbitrary information | 1 |
|
||||
| version | string | The application software semantic version | 2 |
|
||||
| app_version | uint64 | The application protocol version | 3 |
|
||||
| last_block_height | int64 | Latest height for which the app persisted its state | 4 |
|
||||
| last_block_app_hash | bytes | Latest AppHash returned by `FinalizeBlock` | 5 |
|
||||
|
||||
* **Usage**:
|
||||
* Return information about the application state.
|
||||
* Used to sync Tendermint with the application during a handshake
|
||||
that happens on startup.
|
||||
that happens on startup or on recovery.
|
||||
* The returned `app_version` will be included in the Header of every block.
|
||||
* Tendermint expects `last_block_app_hash` and `last_block_height` to
|
||||
be updated during `Commit`, ensuring that `Commit` is never
|
||||
called twice for the same block height.
|
||||
be updated during `FinalizeBlock` and persisted during `Commit`.
|
||||
|
||||
> Note: Semantic version is a reference to [semantic versioning](https://semver.org/). Semantic versions in info will be displayed as X.X.x.
|
||||
|
||||
@@ -61,22 +60,22 @@ title: Methods
|
||||
|
||||
* **Request**:
|
||||
|
||||
| Name | Type | Description | Field Number |
|
||||
|------------------|--------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------|--------------|
|
||||
| time | [google.protobuf.Timestamp](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Timestamp) | Genesis time | 1 |
|
||||
| chain_id | string | ID of the blockchain. | 2 |
|
||||
| consensus_params | [ConsensusParams](#consensusparams) | Initial consensus-critical parameters. | 3 |
|
||||
| validators | repeated [ValidatorUpdate](#validatorupdate) | Initial genesis validators, sorted by voting power. | 4 |
|
||||
| app_state_bytes | bytes | Serialized initial application state. JSON bytes. | 5 |
|
||||
| initial_height | int64 | Height of the initial block (typically `1`). | 6 |
|
||||
| Name | Type | Description | Field Number |
|
||||
|------------------|-------------------------------------------------|-----------------------------------------------------|--------------|
|
||||
| time | [google.protobuf.Timestamp][protobuf-timestamp] | Genesis time | 1 |
|
||||
| chain_id | string | ID of the blockchain. | 2 |
|
||||
| consensus_params | [ConsensusParams](#consensusparams) | Initial consensus-critical parameters. | 3 |
|
||||
| validators | repeated [ValidatorUpdate](#validatorupdate) | Initial genesis validators, sorted by voting power. | 4 |
|
||||
| app_state_bytes | bytes | Serialized initial application state. JSON bytes. | 5 |
|
||||
| initial_height | int64 | Height of the initial block (typically `1`). | 6 |
|
||||
|
||||
* **Response**:
|
||||
|
||||
| Name | Type | Description | Field Number |
|
||||
|------------------|----------------------------------------------|-------------------------------------------------|--------------|
|
||||
| Name | Type | Description | Field Number |
|
||||
|------------------|----------------------------------------------|--------------------------------------------------|--------------|
|
||||
| consensus_params | [ConsensusParams](#consensusparams) | Initial consensus-critical parameters (optional) | 1 |
|
||||
| validators | repeated [ValidatorUpdate](#validatorupdate) | Initial validator set (optional). | 2 |
|
||||
| app_hash | bytes | Initial application hash. | 3 |
|
||||
| validators | repeated [ValidatorUpdate](#validatorupdate) | Initial validator set (optional). | 2 |
|
||||
| app_hash | bytes | Initial application hash. | 3 |
|
||||
|
||||
* **Usage**:
|
||||
* Called once upon genesis.
|
||||
@@ -84,10 +83,10 @@ title: Methods
|
||||
* If `ResponseInitChain.Validators` is not empty, it will be the initial
|
||||
validator set (regardless of what is in `RequestInitChain.Validators`).
|
||||
* This allows the app to decide if it wants to accept the initial validator
|
||||
set proposed by tendermint (ie. in the genesis file), or if it wants to use
|
||||
set proposed by Tendermint (ie. in the genesis file), or if it wants to use
|
||||
a different one (perhaps computed based on some application specific
|
||||
information in the genesis file).
|
||||
* Both `ResponseInitChain.Validators` and `ResponseInitChain.Validators` are [ValidatorUpdate](#validatorupdate) structs.
|
||||
* Both `RequestInitChain.Validators` and `ResponseInitChain.Validators` are [ValidatorUpdate](#validatorupdate) structs.
|
||||
So, technically, they both are _updating_ the set of validators from the empty set.
|
||||
|
||||
### Query
|
||||
@@ -97,7 +96,7 @@ title: Methods
|
||||
| Name | Type | Description | Field Number |
|
||||
|--------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------|
|
||||
| data | bytes | Raw query bytes. Can be used with or in lieu of Path. | 1 |
|
||||
| path | string | Path field of the request URI. Can be used with or in lieu of `data`. Apps MUST interpret `/store` as a query by key on the underlying store. The key SHOULD be specified in the `data` field. Apps SHOULD allow queries over specific types like `/accounts/...` or `/votes/...` | 2 |
|
||||
| path | string | Path field of the request URI. Can be used with or in lieu of `data`. Apps MUST interpret `/store` as a query by key on the underlying store. The key SHOULD be specified in the `data` field. Apps SHOULD allow queries over specific types like `/accounts/...` or `/votes/...` | 2 |
|
||||
| height | int64 | The block height for which you want the query (default=0 returns data for the latest committed block). Note that this is the height of the block containing the application's Merkle root hash, which represents the state as it was after committing the block at Height-1 | 3 |
|
||||
| prove | bool | Return Merkle proof with response if possible | 4 |
|
||||
|
||||
@@ -145,15 +144,40 @@ title: Methods
|
||||
|
||||
* Technically optional - not involved in processing blocks.
|
||||
* Guardian of the mempool: every node runs `CheckTx` before letting a
|
||||
transaction into its local mempool.
|
||||
transaction into its local mempool.
|
||||
* The transaction may come from an external user or another node
|
||||
* `CheckTx` validates the transaction against the current state of the application,
|
||||
for example, checking signatures and account balances, but does not apply any
|
||||
of the state changes described in the transaction.
|
||||
not running code in a virtual machine.
|
||||
* Transactions where `ResponseCheckTx.Code != 0` will be rejected - they will not be broadcast to
|
||||
other nodes or included in a proposal block.
|
||||
* Tendermint attributes no other value to the response code
|
||||
for example, checking signatures and account balances, but does not apply any
|
||||
of the state changes described in the transaction.
|
||||
* Transactions where `ResponseCheckTx.Code != 0` will be rejected - they will not be broadcast
|
||||
to other nodes or included in a proposal block.
|
||||
Tendermint attributes no other value to the response code.
|
||||
|
||||
### Commit
|
||||
|
||||
#### Parameters and Types
|
||||
|
||||
* **Request**:
|
||||
|
||||
| Name | Type | Description | Field Number |
|
||||
|--------|-------|-------------|--------------|
|
||||
|
||||
Commit signals the application to persist application state. It takes no parameters.
|
||||
|
||||
* **Response**:
|
||||
|
||||
| Name | Type | Description | Field Number |
|
||||
|---------------|-------|------------------------------------------------------------------------|--------------|
|
||||
| retain_height | int64 | Blocks below this height may be removed. Defaults to `0` (retain all). | 3 |
|
||||
|
||||
* **Usage**:
|
||||
|
||||
* Signal the Application to persist the application state.
|
||||
Application is expected to persist its state at the end of this call, before calling `ResponseCommit`.
|
||||
* Use `ResponseCommit.retain_height` with caution! If all nodes in the network remove historical
|
||||
blocks then this data is permanently lost, and no new nodes will be able to join the network and
|
||||
bootstrap. Historical blocks may also be required for other purposes, e.g. auditing, replay of
|
||||
non-persisted heights, light client verification, and so on.
|
||||
|
||||
### ListSnapshots
|
||||
|
||||
@@ -269,7 +293,7 @@ title: Methods
|
||||
`Snapshot.Metadata` and/or incrementally verifying contents against `AppHash`.
|
||||
* When all chunks have been accepted, Tendermint will make an ABCI `Info` call to verify that
|
||||
`LastBlockAppHash` and `LastBlockHeight` matches the expected values, and record the
|
||||
`AppVersion` in the node state. It then switches to fast sync or consensus and joins the
|
||||
`AppVersion` in the node state. It then switches to block sync or consensus and joins the
|
||||
network.
|
||||
* If Tendermint is unable to retrieve the next chunk after some time (e.g. because no suitable
|
||||
peers are available), it will reject the snapshot and try a different one via `OfferSnapshot`.
|
||||
@@ -283,16 +307,16 @@ title: Methods
|
||||
|
||||
* **Request**:
|
||||
|
||||
| Name | Type | Description | Field Number |
|
||||
|-------------------------|---------------------------------------------|------------------------------------------------------------------------------------------------------------------|--------------|
|
||||
| max_tx_bytes | int64 | Currently configured maximum size in bytes taken by the modified transactions. | 1 |
|
||||
| txs | repeated bytes | Preliminary list of transactions that have been picked as part of the block to propose. | 2 |
|
||||
| local_last_commit | [ExtendedCommitInfo](#extendedcommitinfo) | Info about the last commit, obtained locally from Tendermint's data structures. | 3 |
|
||||
| byzantine_validators | repeated [Misbehavior](#misbehavior) | List of information about validators that acted incorrectly. | 4 |
|
||||
| height | int64 | The height of the block that will be proposed. | 5 |
|
||||
| time | [google.protobuf.Timestamp](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Timestamp) | Timestamp of the block that that will be proposed. | 6 |
|
||||
| next_validators_hash | bytes | Merkle root of the next validator set. | 7 |
|
||||
| proposer_address | bytes | [Address](../core/data_structures.md#address) of the validator that is creating the proposal. | 8 |
|
||||
| Name | Type | Description | Field Number |
|
||||
|----------------------|-------------------------------------------------|-----------------------------------------------------------------------------------------------|--------------|
|
||||
| max_tx_bytes | int64 | Currently configured maximum size in bytes taken by the modified transactions. | 1 |
|
||||
| txs | repeated bytes | Preliminary list of transactions that have been picked as part of the block to propose. | 2 |
|
||||
| local_last_commit | [ExtendedCommitInfo](#extendedcommitinfo) | Info about the last commit, obtained locally from Tendermint's data structures. | 3 |
|
||||
| misbehavior | repeated [Misbehavior](#misbehavior) | List of information about validators that misbehaved. | 4 |
|
||||
| height | int64 | The height of the block that will be proposed. | 5 |
|
||||
| time | [google.protobuf.Timestamp][protobuf-timestamp] | Timestamp of the block that that will be proposed. | 6 |
|
||||
| next_validators_hash | bytes | Merkle root of the next validator set. | 7 |
|
||||
| proposer_address | bytes | [Address](../core/data_structures.md#address) of the validator that is creating the proposal. | 8 |
|
||||
|
||||
* **Response**:
|
||||
|
||||
@@ -302,92 +326,128 @@ title: Methods
|
||||
| app_hash | bytes | The Merkle root hash of the application state. | 3 |
|
||||
| tx_results | repeated [ExecTxResult](#exectxresult) | List of structures containing the data resulting from executing the transactions | 4 |
|
||||
| validator_updates | repeated [ValidatorUpdate](#validatorupdate) | Changes to validator set (set voting power to 0 to remove). | 5 |
|
||||
| consensus_param_updates | [ConsensusParams](#consensusparams) | Changes to consensus-critical gas, size, and other parameters. | 6 |
|
||||
| consensus_param_updates | [ConsensusParams](#consensusparams) | Changes to gas, size, and other consensus-related parameters. | 6 |
|
||||
|
||||
* **Usage**:
|
||||
* The first six parameters of `RequestPrepareProposal` are the same as `RequestProcessProposal`
|
||||
* `RequestPrepareProposal`'s parameters `txs`, `misbehavior`, `height`, `time`,
|
||||
`next_validators_hash`, and `proposer_address` are the same as in `RequestProcessProposal`
|
||||
and `RequestFinalizeBlock`.
|
||||
* The height and time values match the values from the header of the proposed block.
|
||||
* `RequestPrepareProposal` contains a preliminary set of transactions `txs` that Tendermint considers to be a good block proposal, called _raw proposal_. The Application can modify this set via `ResponsePrepareProposal.tx_records` (see [TxRecord](#txrecord)).
|
||||
* The Application _can_ reorder, remove or add transactions to the raw proposal. Let `tx` be a transaction in `txs`:
|
||||
* If the Application considers that `tx` should not be proposed in this block, e.g., there are other transactions with higher priority, then it should not include it in `tx_records`. In this case, Tendermint won't remove `tx` from the mempool. The Application should be extra-careful, as abusing this feature may cause transactions to stay forever in the mempool.
|
||||
* If the Application considers that a `tx` should not be included in the proposal and removed from the mempool, then the Application should include it in `tx_records` and _mark_ it as `REMOVED`. In this case, Tendermint will remove `tx` from the mempool.
|
||||
* If the Application wants to add a new transaction, then the Application should include it in `tx_records` and _mark_ it as `ADD`. In this case, Tendermint will add it to the mempool.
|
||||
* The Application should be aware that removing and adding transactions may compromise _traceability_.
|
||||
> Consider the following example: the Application transforms a client-submitted transaction `t1` into a second transaction `t2`, i.e., the Application asks Tendermint to remove `t1` and add `t2` to the mempool. If a client wants to eventually check what happened to `t1`, it will discover that `t_1` is not in the mempool or in a committed block, getting the wrong idea that `t_1` did not make it into a block. Note that `t_2` _will be_ in a committed block, but unless the Application tracks this information, no component will be aware of it. Thus, if the Application wants traceability, it is its responsability to support it. For instance, the Application could attach to a transformed transaction a list with the hashes of the transactions it derives from.
|
||||
* Tendermint MAY include a list of transactions in `RequestPrepareProposal.txs` whose total size in bytes exceeds `RequestPrepareProposal.max_tx_bytes`.
|
||||
Therefore, if the size of `RequestPrepareProposal.txs` is greater than `RequestPrepareProposal.max_tx_bytes`, the Application MUST make sure that the
|
||||
`RequestPrepareProposal.max_tx_bytes` limit is respected by those transaction records returned in `ResponsePrepareProposal.tx_records` that are marked as `UNMODIFIED` or `ADDED`.
|
||||
* In same-block execution mode, the Application must provide values for `ResponsePrepareProposal.app_hash`,
|
||||
`ResponsePrepareProposal.tx_results`, `ResponsePrepareProposal.validator_updates`, and
|
||||
* `RequestPrepareProposal.local_last_commit` is a set of the precommit votes that allowed the
|
||||
decision of the previous block, together with their corresponding vote extensions.
|
||||
* The `height`, `time`, and `proposer_address` values match the values from the header of the
|
||||
proposed block.
|
||||
* `RequestPrepareProposal` contains a preliminary set of transactions `txs` that Tendermint
|
||||
retrieved from the mempool, called _raw proposal_. The Application can modify this
|
||||
set via `ResponsePrepareProposal.tx_records` (see [TxRecord](#txrecord)).
|
||||
* The Application _can_ modify the raw proposal: it can reorder, remove or add transactions.
|
||||
Let `tx` be a transaction in `txs`:
|
||||
* If the Application considers that `tx` should not be proposed in this block, e.g.,
|
||||
there are other transactions with higher priority, then it should not include it in
|
||||
`tx_records`. In this case, Tendermint will not remove `tx` from the mempool. The
|
||||
Application should be extra-careful, as abusing this feature may cause transactions
|
||||
to stay much longer than needed in the mempool.
|
||||
* If the Application considers that `tx` should not be included in the proposal and
|
||||
removed from the mempool, then the Application should include it in `tx_records` and
|
||||
_mark_ it as `REMOVED`. In this case, Tendermint will remove `tx` from the mempool.
|
||||
* If the Application wants to add a new transaction to the proposed block, then the
|
||||
Application includes it in `tx_records` and _marks_ it as `ADDED`. In this case, Tendermint
|
||||
will also add the transaction to the mempool.
|
||||
* The Application should be aware that removing and adding transactions may compromise
|
||||
_traceability_.
|
||||
> Consider the following example: the Application transforms a client-submitted
|
||||
transaction `t1` into a second transaction `t2`, i.e., the Application asks Tendermint
|
||||
to remove `t1` and add `t2` to the mempool. If a client wants to eventually check what
|
||||
happened to `t1`, it will discover that `t1` is neither in the mempool nor in a
|
||||
committed block, getting the wrong idea that `t1` did not make it into a block. Note
|
||||
that `t2` _will be_ in a committed block, but unless the Application tracks this
|
||||
information, no component will be aware of it. Thus, if the Application wants
|
||||
traceability, it is its responsability to support it. For instance, the Application
|
||||
could attach to a transformed transaction a list with the hashes of the transactions it
|
||||
derives from.
|
||||
* Tendermint MAY include a list of transactions in `RequestPrepareProposal.txs` whose total
|
||||
size in bytes exceeds `RequestPrepareProposal.max_tx_bytes`.
|
||||
Therefore, if the size of `RequestPrepareProposal.txs` is greater than
|
||||
`RequestPrepareProposal.max_tx_bytes`, the Application MUST remove transactions to ensure
|
||||
that the `RequestPrepareProposal.max_tx_bytes` limit is respected by those transaction
|
||||
records returned in `ResponsePrepareProposal.tx_records` that are marked as `UNMODIFIED` or
|
||||
`ADDED`.
|
||||
* In same-block execution mode, the Application must provide values for
|
||||
`ResponsePrepareProposal.app_hash`, `ResponsePrepareProposal.tx_results`,
|
||||
`ResponsePrepareProposal.validator_updates`, and
|
||||
`ResponsePrepareProposal.consensus_param_updates`, as a result of fully executing the block.
|
||||
* The values for `ResponsePrepareProposal.validator_updates`, or
|
||||
`ResponsePrepareProposal.consensus_param_updates` may be empty. In this case, Tendermint will keep
|
||||
the current values.
|
||||
* `ResponsePrepareProposal.validator_updates`, triggered by block `H`, affect validation
|
||||
for blocks `H+1`, and `H+2`. Heights following a validator update are affected in the following way:
|
||||
* `H`: `NextValidatorsHash` includes the new `validator_updates` value.
|
||||
* `H+1`: The validator set change takes effect and `ValidatorsHash` is updated.
|
||||
* `H+2`: `local_last_commit` now includes the altered validator set.
|
||||
* Height `H`: `NextValidatorsHash` includes the new `validator_updates` value.
|
||||
* Height `H+1`: The validator set change takes effect and `ValidatorsHash` is updated.
|
||||
* Height `H+2`: `*_last_commit` fields in `PrepareProposal`, `ProcessProposal`, and
|
||||
`FinalizeBlock` now include the altered validator set.
|
||||
* `ResponseFinalizeBlock.consensus_param_updates` returned for block `H` apply to the consensus
|
||||
params for block `H+1` even if the change is agreed in block `H`.
|
||||
For more information on the consensus parameters,
|
||||
see the [application spec entry on consensus parameters](../abci/apps.md#consensus-parameters).
|
||||
* It is the responsibility of the Application to set the right value for _TimeoutPropose_ so that
|
||||
see the [consensus parameters](./abci%2B%2B_app_requirements.md#consensus-parameters)
|
||||
section.
|
||||
* It is the Application's responsibility to set the right value for _TimeoutPropose_ so that
|
||||
the (synchronous) execution of the block does not cause other processes to prevote `nil` because
|
||||
their propose timeout goes off.
|
||||
* In next-block execution mode, Tendermint will ignore parameters `ResponsePrepareProposal.tx_results`,
|
||||
* In next-block execution mode, Tendermint will ignore parameters
|
||||
`ResponsePrepareProposal.app_hash`, `ResponsePrepareProposal.tx_results`,
|
||||
`ResponsePrepareProposal.validator_updates`, and `ResponsePrepareProposal.consensus_param_updates`.
|
||||
* As a result of executing the prepared proposal, the Application may produce header events or transaction events.
|
||||
* As a result of executing the prepared proposal, the Application may produce block events or transaction events.
|
||||
The Application must keep those events until a block is decided and then pass them on to Tendermint via
|
||||
`ResponseFinalizeBlock`.
|
||||
* Likewise, in next-block execution mode, the Application must keep all responses to executing transactions
|
||||
until it can call `ResponseFinalizeBlock`.
|
||||
* Likewise, in next-block execution mode, the Application must keep all responses to executing
|
||||
transactions until it can call `ResponseFinalizeBlock`.
|
||||
* As a sanity check, Tendermint will check the returned parameters for validity if the Application modified them.
|
||||
In particular, `ResponsePrepareProposal.tx_records` will be deemed invalid if
|
||||
* There is a duplicate transaction in the list.
|
||||
* A new or modified transaction is marked as `UNMODIFIED` or `REMOVED`.
|
||||
* An unmodified transaction is marked as `ADDED`.
|
||||
* A new transaction is marked as `UNMODIFIED` or `REMOVED`.
|
||||
* An existing transaction is marked as `ADDED`.
|
||||
* A transaction is marked as `UNKNOWN`.
|
||||
* If Tendermint fails to validate the `ResponsePrepareProposal`, Tendermint will assume the application is faulty and crash.
|
||||
* If Tendermint fails to validate the `ResponsePrepareProposal`, Tendermint will assume the
|
||||
Application is faulty and crash.
|
||||
* The implementation of `PrepareProposal` can be non-deterministic.
|
||||
|
||||
#### When does Tendermint call it?
|
||||
#### When does Tendermint call `PrepareProposal`?
|
||||
|
||||
When a validator _p_ enters Tendermint consensus round _r_, height _h_, in which _p_ is the proposer,
|
||||
and _p_'s _validValue_ is `nil`:
|
||||
|
||||
1. _p_'s Tendermint collects outstanding transactions from the mempool
|
||||
* The transactions will be collected in order of priority
|
||||
* Let $C$ the list of currently collected transactions
|
||||
* The collection stops when any of the following conditions are met
|
||||
* the mempool is empty
|
||||
* the total size of transactions $\in C$ is greater than or equal to `consensusParams.block.max_bytes`
|
||||
* the sum of `GasWanted` field of transactions $\in C$ is greater than or equal to
|
||||
`consensusParams.block.max_gas`
|
||||
1. Tendermint collects outstanding transactions from _p_'s mempool
|
||||
* the transactions will be collected in order of priority
|
||||
* _p_'s Tendermint creates a block header.
|
||||
2. _p_'s Tendermint calls `RequestPrepareProposal` with the newly generated block.
|
||||
The call is synchronous: Tendermint's execution will block until the Application returns from the call.
|
||||
3. The Application checks the block (hashes, transactions, commit info, misbehavior). Besides,
|
||||
* in same-block execution mode, the Application can (and should) provide `ResponsePrepareProposal.app_hash`,
|
||||
`ResponsePrepareProposal.validator_updates`, or
|
||||
2. _p_'s Tendermint calls `RequestPrepareProposal` with the newly generated block, the local
|
||||
commit of the previous height (with vote extensions), and any outstanding evidence of
|
||||
misbehavior. The call is synchronous: Tendermint's execution will block until the Application
|
||||
returns from the call.
|
||||
3. The Application uses the information received (transactions, commit info, misbehavior, time) to
|
||||
(potentially) modify the proposal.
|
||||
* in same-block execution mode, the Application fully executes the block and provides values
|
||||
for `ResponsePrepareProposal.app_hash`, `ResponsePrepareProposal.tx_results`,
|
||||
`ResponsePrepareProposal.validator_updates`, and
|
||||
`ResponsePrepareProposal.consensus_param_updates`.
|
||||
* in "next-block execution" mode, _p_'s Tendermint will ignore the values for `ResponsePrepareProposal.app_hash`,
|
||||
`ResponsePrepareProposal.validator_updates`, and `ResponsePrepareProposal.consensus_param_updates`.
|
||||
* in both modes, the Application can manipulate transactions
|
||||
* in next-block execution mode, _p_'s Tendermint will ignore the values for
|
||||
`ResponsePrepareProposal.app_hash`, `ResponsePrepareProposal.tx_results`,
|
||||
`ResponsePrepareProposal.validator_updates`, and
|
||||
`ResponsePrepareProposal.consensus_param_updates`.
|
||||
* in both modes, the Application can manipulate transactions:
|
||||
* leave transactions untouched - `TxAction = UNMODIFIED`
|
||||
* add new transactions directly to the proposal - `TxAction = ADDED`
|
||||
* remove transactions (invalid) from the proposal and from the mempool - `TxAction = REMOVED`
|
||||
* add new transactions (not present initially) to the proposal - `TxAction = ADDED`
|
||||
* remove (invalid) transactions from the proposal and from the mempool - `TxAction = REMOVED`
|
||||
* remove transactions from the proposal but not from the mempool (effectively _delaying_ them) - the
|
||||
Application removes the transaction from the list
|
||||
* modify transactions (e.g. aggregate them) - `TxAction = ADDED` followed by `TxAction = REMOVED`. As explained above, this compromises client traceability, unless it is implemented at the Application level.
|
||||
Application does not include the transaction in `ResponsePrepareProposal.tx_records`
|
||||
* modify transactions (e.g. aggregate them) - `TxAction = ADDED` followed by
|
||||
`TxAction = REMOVED`. As explained above, this compromises client traceability, unless
|
||||
it is implemented at the Application level.
|
||||
* reorder transactions - the Application reorders transactions in the list
|
||||
4. If the block is modified, the Application sets `ResponsePrepareProposal.modified` to true,
|
||||
and includes the modified block in the return parameters (see the rules in section _Usage_).
|
||||
The Application returns from the call.
|
||||
4. The Application includes the transaction list (whether modified or not) in the return parameters
|
||||
(see the rules in section _Usage_), and returns from the call.
|
||||
5. _p_'s Tendermint uses the (possibly) modified block as _p_'s proposal in round _r_, height _h_.
|
||||
|
||||
Note that, if _p_ has a non-`nil` _validValue_, Tendermint will use it as proposal and will not call `RequestPrepareProposal`.
|
||||
Note that, if _p_ has a non-`nil` _validValue_ in round _r_, height _h_, Tendermint will use it as
|
||||
proposal and will not call `RequestPrepareProposal`.
|
||||
|
||||
### ProcessProposal
|
||||
|
||||
@@ -395,16 +455,16 @@ Note that, if _p_ has a non-`nil` _validValue_, Tendermint will use it as propos
|
||||
|
||||
* **Request**:
|
||||
|
||||
| Name | Type | Description | Field Number |
|
||||
|----------------------|---------------------------------------------|----------------------------------------------------------------------------------------------------------------|--------------|
|
||||
| txs | repeated bytes | List of transactions that have been picked as part of the proposed block. | 1 |
|
||||
| proposed_last_commit | [CommitInfo](#commitinfo) | Info about the last commit, obtained from the information in the proposed block. | 2 |
|
||||
| byzantine_validators | repeated [Misbehavior](#misbehavior) | List of information about validators that acted incorrectly. | 3 |
|
||||
| hash | bytes | The block header's hash of the proposed block. | 4 |
|
||||
| height | int64 | The height of the proposed block. | 5 |
|
||||
| time | [google.protobuf.Timestamp](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Timestamp) | Timestamp included in the proposed block. | 6 |
|
||||
| next_validators_hash | bytes | Merkle root of the next validator set. | 7 |
|
||||
| proposer_address | bytes | [Address](../core/data_structures.md#address) of the validator that created the proposal. | 8 |
|
||||
| Name | Type | Description | Field Number |
|
||||
|----------------------|-------------------------------------------------|-------------------------------------------------------------------------------------------|--------------|
|
||||
| txs | repeated bytes | List of transactions of the proposed block. | 1 |
|
||||
| proposed_last_commit | [CommitInfo](#commitinfo) | Info about the last commit, obtained from the information in the proposed block. | 2 |
|
||||
| misbehavior | repeated [Misbehavior](#misbehavior) | List of information about validators that misbehaved. | 3 |
|
||||
| hash | bytes | The hash of the proposed block. | 4 |
|
||||
| height | int64 | The height of the proposed block. | 5 |
|
||||
| time | [google.protobuf.Timestamp][protobuf-timestamp] | Timestamp of the proposed block. | 6 |
|
||||
| next_validators_hash | bytes | Merkle root of the next validator set. | 7 |
|
||||
| proposer_address | bytes | [Address](../core/data_structures.md#address) of the validator that created the proposal. | 8 |
|
||||
|
||||
* **Response**:
|
||||
|
||||
@@ -414,14 +474,19 @@ Note that, if _p_ has a non-`nil` _validValue_, Tendermint will use it as propos
|
||||
| app_hash | bytes | The Merkle root hash of the application state. | 2 |
|
||||
| tx_results | repeated [ExecTxResult](#exectxresult) | List of structures containing the data resulting from executing the transactions. | 3 |
|
||||
| validator_updates | repeated [ValidatorUpdate](#validatorupdate) | Changes to validator set (set voting power to 0 to remove). | 4 |
|
||||
| consensus_param_updates | [ConsensusParams](#consensusparams) | Changes to consensus-critical gas, size, and other parameters. | 5 |
|
||||
| consensus_param_updates | [ConsensusParams](#consensusparams) | Changes to gas, size, and other consensus-related parameters. | 5 |
|
||||
|
||||
* **Usage**:
|
||||
* Contains fields from the proposed block.
|
||||
* The Application may fully execute the block as though it was handling `RequestFinalizeBlock`.
|
||||
However, any resulting state changes must be kept as _candidate state_,
|
||||
and the Application should be ready to backtrack/discard it in case the decided block is different.
|
||||
* The height and timestamp values match the values from the header of the proposed block.
|
||||
* Contains all information on the proposed block needed to fully execute it.
|
||||
* The Application may fully execute the block as though it was handling
|
||||
`RequestFinalizeBlock`.
|
||||
* However, any resulting state changes must be kept as _candidate state_,
|
||||
and the Application should be ready to discard it in case another block is decided.
|
||||
* `RequestProcessProposal` is also called at the proposer of a round. The reason for this is to
|
||||
inform the Application of the block header's hash, which cannot be done at `PrepareProposal`
|
||||
time. In this case, the call to `RequestProcessProposal` occurs right after the call to
|
||||
`RequestPrepareProposal`.
|
||||
* The height and time values match the values from the header of the proposed block.
|
||||
* If `ResponseProcessProposal.status` is `REJECT`, Tendermint assumes the proposal received
|
||||
is not valid.
|
||||
* In same-block execution mode, the Application is required to fully execute the block and provide values
|
||||
@@ -430,35 +495,39 @@ Note that, if _p_ has a non-`nil` _validValue_, Tendermint will use it as propos
|
||||
so that Tendermint can then verify the hashes in the block's header are correct.
|
||||
If the hashes mismatch, Tendermint will reject the block even if `ResponseProcessProposal.status`
|
||||
was set to `ACCEPT`.
|
||||
* In next-block execution mode, the Application should *not* provide values for parameters
|
||||
* In next-block execution mode, the Application should _not_ provide values for parameters
|
||||
`ResponseProcessProposal.app_hash`, `ResponseProcessProposal.tx_results`,
|
||||
`ResponseProcessProposal.validator_updates`, and `ResponseProcessProposal.consensus_param_updates`.
|
||||
* The implementation of `ProcessProposal` MUST be deterministic. Moreover, the value of
|
||||
`ResponseProcessProposal.status` MUST **exclusively** depend on the parameters passed in
|
||||
the call to `RequestProcessProposal`, and the last committed Application state
|
||||
(see [Requirements](abci++_app_requirements_002_draft.md) section).
|
||||
(see [Requirements](./abci++_app_requirements.md) section).
|
||||
* Moreover, application implementors SHOULD always set `ResponseProcessProposal.status` to `ACCEPT`,
|
||||
unless they _really_ know what the potential liveness implications of returning `REJECT` are.
|
||||
|
||||
#### When does Tendermint call it?
|
||||
#### When does Tendermint call `ProcessProposal`?
|
||||
|
||||
When a validator _p_ enters Tendermint consensus round _r_, height _h_, in which _q_ is the proposer (possibly _p_ = _q_):
|
||||
|
||||
1. _p_ sets up timer `ProposeTimeout`.
|
||||
2. If _p_ is the proposer, _p_ executes steps 1-6 in [PrepareProposal](#prepareproposal).
|
||||
3. Upon reception of Proposal message (which contains the header) for round _r_, height _h_ from _q_, _p_'s Tendermint verifies the block header.
|
||||
4. Upon reception of Proposal message, along with all the block parts, for round _r_, height _h_ from _q_, _p_'s Tendermint follows its algorithm
|
||||
to check whether it should prevote for the block just received, or `nil`
|
||||
5. If Tendermint should prevote for the block just received
|
||||
3. Upon reception of Proposal message (which contains the header) for round _r_, height _h_ from
|
||||
_q_, _p_'s Tendermint verifies the block header.
|
||||
4. Upon reception of Proposal message, along with all the block parts, for round _r_, height _h_
|
||||
from _q_, _p_'s Tendermint follows its algorithm to check whether it should prevote for the
|
||||
proposed block, or `nil`.
|
||||
5. If Tendermint should prevote for the proposed block:
|
||||
1. Tendermint calls `RequestProcessProposal` with the block. The call is synchronous.
|
||||
2. The Application checks/processes the proposed block, which is read-only, and returns true (_accept_) or false (_reject_) in `ResponseProcessProposal.accept`.
|
||||
2. The Application checks/processes the proposed block, which is read-only, and returns
|
||||
`ACCEPT` or `REJECT` in the `ResponseProcessProposal.status` field.
|
||||
* The Application, depending on its needs, may call `ResponseProcessProposal`
|
||||
* either after it has completely processed the block (the simpler case),
|
||||
* or immediately (after doing some basic checks), and process the block asynchronously. In this case the Application will
|
||||
not be able to reject the block, or force prevote/precommit `nil` afterwards.
|
||||
* either after it has completely processed the block (immediate execution),
|
||||
* or after doing some basic checks, and process the block asynchronously. In this case the
|
||||
Application will not be able to reject the block, or force prevote/precommit `nil`
|
||||
afterwards.
|
||||
3. If the returned value is
|
||||
* _accept_, Tendermint prevotes on this proposal for round _r_, height _h_.
|
||||
* _reject_, Tendermint prevotes `nil`.
|
||||
* `ACCEPT`: Tendermint prevotes on this proposal for round _r_, height _h_.
|
||||
* `REJECT`: Tendermint prevotes `nil`.
|
||||
|
||||
### ExtendVote
|
||||
|
||||
@@ -473,20 +542,21 @@ When a validator _p_ enters Tendermint consensus round _r_, height _h_, in which
|
||||
|
||||
* **Response**:
|
||||
|
||||
| Name | Type | Description | Field Number |
|
||||
|-------------------|-------|-----------------------------------------------|--------------|
|
||||
| vote_extension | bytes | Optional information signed by by Tendermint. | 1 |
|
||||
| Name | Type | Description | Field Number |
|
||||
|-------------------|-------|---------------------------------------------------------|--------------|
|
||||
| vote_extension | bytes | Information signed by by Tendermint. Can have 0 length. | 1 |
|
||||
|
||||
* **Usage**:
|
||||
* `ResponseExtendVote.vote_extension` is optional information that, if present, will be signed by Tendermint and
|
||||
attached to the Precommit message.
|
||||
* `RequestExtendVote.hash` corresponds to the hash of a proposed block that was made available to the application
|
||||
in a previous call to `ProcessProposal` or `PrepareProposal` for the current height.
|
||||
* `ResponseExtendVote.vote_extension` is application-generated information that will be signed
|
||||
by Tendermint and attached to the Precommit message.
|
||||
* The Application may choose to use an empty vote extension (0 length).
|
||||
* `RequestExtendVote.hash` corresponds to the hash of a proposed block that was made available
|
||||
to the Application in a previous call to `ProcessProposal` for the current height.
|
||||
* `ResponseExtendVote.vote_extension` will only be attached to a non-`nil` Precommit message. If Tendermint is to
|
||||
precommit `nil`, it will not call `RequestExtendVote`.
|
||||
* The Application logic that creates the extension can be non-deterministic.
|
||||
|
||||
#### When does Tendermint call it?
|
||||
#### When does Tendermint call `ExtendVote`?
|
||||
|
||||
When a validator _p_ is in Tendermint consensus state _prevote_ of round _r_, height _h_, in which _q_ is the proposer; and _p_ has received
|
||||
|
||||
@@ -497,7 +567,7 @@ then _p_'s Tendermint locks _v_ and sends a Precommit message in the following
|
||||
|
||||
1. _p_'s Tendermint sets _lockedValue_ and _validValue_ to _v_, and sets _lockedRound_ and _validRound_ to _r_
|
||||
2. _p_'s Tendermint calls `RequestExtendVote` with _id(v)_ (`RequestExtendVote.hash`). The call is synchronous.
|
||||
3. The Application optionally returns an array of bytes, `ResponseExtendVote.extension`, which is not interpreted by Tendermint.
|
||||
3. The Application returns an array of bytes, `ResponseExtendVote.extension`, which is not interpreted by Tendermint.
|
||||
4. _p_'s Tendermint includes `ResponseExtendVote.extension` in a field of type [CanonicalVoteExtension](#canonicalvoteextension),
|
||||
it then populates the other fields in [CanonicalVoteExtension](#canonicalvoteextension), and signs the populated
|
||||
data structure.
|
||||
@@ -516,12 +586,12 @@ a [CanonicalVoteExtension](#canonicalvoteextension) field in the `precommit nil`
|
||||
|
||||
* **Request**:
|
||||
|
||||
| Name | Type | Description | Field Number |
|
||||
|-------------------|-------|------------------------------------------------------------------------------------------|--------------|
|
||||
| hash | bytes | The header hash of the propsed block that the vote extension refers to. | 1 |
|
||||
| validator_address | bytes | [Address](../core/data_structures.md#address) of the validator that signed the extension | 2 |
|
||||
| height | int64 | Height of the block (for sanity check). | 3 |
|
||||
| vote_extension | bytes | Application-specific information signed by Tendermint. Can have 0 length | 4 |
|
||||
| Name | Type | Description | Field Number |
|
||||
|-------------------|-------|-------------------------------------------------------------------------------------------|--------------|
|
||||
| hash | bytes | The hash of the proposed block that the vote extension refers to. | 1 |
|
||||
| validator_address | bytes | [Address](../core/data_structures.md#address) of the validator that signed the extension. | 2 |
|
||||
| height | int64 | Height of the block (for sanity check). | 3 |
|
||||
| vote_extension | bytes | Application-specific information signed by Tendermint. Can have 0 length. | 4 |
|
||||
|
||||
* **Response**:
|
||||
|
||||
@@ -530,33 +600,38 @@ a [CanonicalVoteExtension](#canonicalvoteextension) field in the `precommit nil`
|
||||
| status | [VerifyStatus](#verifystatus) | `enum` signaling if the application accepts the vote extension | 1 |
|
||||
|
||||
* **Usage**:
|
||||
* `RequestVerifyVoteExtension.vote_extension` can be an empty byte array. The Application's interpretation of it should be
|
||||
* `RequestVerifyVoteExtension.vote_extension` can be an empty byte array. The Application's
|
||||
interpretation of it should be
|
||||
that the Application running at the process that sent the vote chose not to extend it.
|
||||
Tendermint will always call `RequestVerifyVoteExtension`, even for 0 length vote extensions.
|
||||
* `RequestVerifyVoteExtension` is not called for precommit votes sent by the local process.
|
||||
* `RequestVerifyVoteExtension.hash` refers to a proposed block. There is not guarantee that
|
||||
this proposed block has previously been exposed to the Application via `ProcessProposal`.
|
||||
* If `ResponseVerifyVoteExtension.status` is `REJECT`, Tendermint will reject the whole received vote.
|
||||
See the [Requirements](abci++_app_requirements_002_draft.md) section to understand the potential
|
||||
See the [Requirements](./abci++_app_requirements.md) section to understand the potential
|
||||
liveness implications of this.
|
||||
* The implementation of `VerifyVoteExtension` MUST be deterministic. Moreover, the value of
|
||||
`ResponseVerifyVoteExtension.status` MUST **exclusively** depend on the parameters passed in
|
||||
the call to `RequestVerifyVoteExtension`, and the last committed Application state
|
||||
(see [Requirements](abci++_app_requirements_002_draft.md) section).
|
||||
(see [Requirements](./abci++_app_requirements.md) section).
|
||||
* Moreover, application implementers SHOULD always set `ResponseVerifyVoteExtension.status` to `ACCEPT`,
|
||||
unless they _really_ know what the potential liveness implications of returning `REJECT` are.
|
||||
|
||||
#### When does Tendermint call it?
|
||||
#### When does Tendermint call `VerifyVoteExtension`?
|
||||
|
||||
When a validator _p_ is in Tendermint consensus round _r_, height _h_, state _prevote_ (**TODO** discuss: I think I must remove the state
|
||||
from this condition, but not sure), and _p_ receives a Precommit message for round _r_, height _h_ from _q_:
|
||||
When a node _p_ is in Tendermint consensus round _r_, height _h_, and _p_ receives a Precommit
|
||||
message for round _r_, height _h_ from validator _q_ (_q_ ≠ _p_):
|
||||
|
||||
1. If the Precommit message does not contain a vote extension with a valid signature, Tendermint discards the message as invalid.
|
||||
1. If the Precommit message does not contain a vote extension with a valid signature, Tendermint
|
||||
discards the Precommit message as invalid.
|
||||
* a 0-length vote extension is valid as long as its accompanying signature is also valid.
|
||||
2. Else, _p_'s Tendermint calls `RequestVerifyVoteExtension`.
|
||||
3. The Application returns _accept_ or _reject_ via `ResponseVerifyVoteExtension.status`.
|
||||
3. The Application returns `ACCEPT` or `REJECT` via `ResponseVerifyVoteExtension.status`.
|
||||
4. If the Application returns
|
||||
* _accept_, _p_'s Tendermint will keep the received vote, together with its corresponding
|
||||
* `ACCEPT`, _p_'s Tendermint will keep the received vote, together with its corresponding
|
||||
vote extension in its internal data structures. It will be used to populate the [ExtendedCommitInfo](#extendedcommitinfo)
|
||||
structure in calls to `RequestPrepareProposal`, in rounds of height _h + 1_ where _p_ is the proposer.
|
||||
* _reject_, _p_'s Tendermint will deem the Precommit message invalid and discard it.
|
||||
* `REJECT`, _p_'s Tendermint will deem the Precommit message invalid and discard it.
|
||||
|
||||
### FinalizeBlock
|
||||
|
||||
@@ -564,38 +639,38 @@ from this condition, but not sure), and _p_ receives a Precommit message for rou
|
||||
|
||||
* **Request**:
|
||||
|
||||
| Name | Type | Description | Field Number |
|
||||
|----------------------|---------------------------------------------|------------------------------------------------------------------------------------------|--------------|
|
||||
| txs | repeated bytes | List of transactions committed as part of the block. | 1 |
|
||||
| decided_last_commit | [CommitInfo](#commitinfo) | Info about the last commit, obtained from the block that was just decided. | 2 |
|
||||
| byzantine_validators | repeated [Misbehavior](#misbehavior) | List of information about validators that acted incorrectly. | 3 |
|
||||
| hash | bytes | The block header's hash. Present for convenience (can be derived from the block header). | 4 |
|
||||
| height | int64 | The height of the finalized block. | 5 |
|
||||
| time | [google.protobuf.Timestamp](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Timestamp) | Timestamp included in the finalized block. | 6 |
|
||||
| next_validators_hash | bytes | Merkle root of the next validator set. | 7 |
|
||||
| proposer_address | bytes | [Address](../core/data_structures.md#address) of the validator that created the proposal.| 8 |
|
||||
| Name | Type | Description | Field Number |
|
||||
|----------------------|-------------------------------------------------|-------------------------------------------------------------------------------------------|--------------|
|
||||
| txs | repeated bytes | List of transactions committed as part of the block. | 1 |
|
||||
| decided_last_commit | [CommitInfo](#commitinfo) | Info about the last commit, obtained from the block that was just decided. | 2 |
|
||||
| misbehavior | repeated [Misbehavior](#misbehavior) | List of information about validators that misbehaved. | 3 |
|
||||
| hash | bytes | The block's hash. | 4 |
|
||||
| height | int64 | The height of the finalized block. | 5 |
|
||||
| time | [google.protobuf.Timestamp][protobuf-timestamp] | Timestamp of the finalized block. | 6 |
|
||||
| next_validators_hash | bytes | Merkle root of the next validator set. | 7 |
|
||||
| proposer_address | bytes | [Address](../core/data_structures.md#address) of the validator that created the proposal. | 8 |
|
||||
|
||||
* **Response**:
|
||||
|
||||
| Name | Type | Description | Field Number |
|
||||
|-------------------------|-------------------------------------------------------------|----------------------------------------------------------------------------------|--------------|
|
||||
| events | repeated [Event](abci++_basic_concepts_002_draft.md#events) | Type & Key-Value events for indexing | 1 |
|
||||
| events | repeated [Event](abci++_basic_concepts.md#events) | Type & Key-Value events for indexing | 1 |
|
||||
| tx_results | repeated [ExecTxResult](#exectxresult) | List of structures containing the data resulting from executing the transactions | 2 |
|
||||
| validator_updates | repeated [ValidatorUpdate](#validatorupdate) | Changes to validator set (set voting power to 0 to remove). | 3 |
|
||||
| consensus_param_updates | [ConsensusParams](#consensusparams) | Changes to consensus-critical gas, size, and other parameters. | 4 |
|
||||
| consensus_param_updates | [ConsensusParams](#consensusparams) | Changes to gas, size, and other consensus-related parameters. | 4 |
|
||||
| app_hash | bytes | The Merkle root hash of the application state. | 5 |
|
||||
| retain_height | int64 | Blocks below this height may be removed. Defaults to `0` (retain all). | 6 |
|
||||
|
||||
* **Usage**:
|
||||
* Contains the fields of the newly decided block.
|
||||
* This method is equivalent to the call sequence `BeginBlock`, [`DeliverTx`],
|
||||
`EndBlock`, `Commit` in the previous version of ABCI.
|
||||
* The height and timestamp values match the values from the header of the proposed block.
|
||||
* The Application can use `RequestFinalizeBlock.decided_last_commit` and `RequestFinalizeBlock.byzantine_validators`
|
||||
and `EndBlock` in the previous version of ABCI.
|
||||
* The height and time values match the values from the header of the proposed block.
|
||||
* The Application can use `RequestFinalizeBlock.decided_last_commit` and `RequestFinalizeBlock.misbehavior`
|
||||
to determine rewards and punishments for the validators.
|
||||
* The application must execute the transactions in full, in the order they appear in `RequestFinalizeBlock.txs`,
|
||||
before returning control to Tendermint. Alternatively, it can commit the candidate state corresponding to the same block
|
||||
previously executed via `PrepareProposal` or `ProcessProposal`.
|
||||
* The Application executes the transactions in `RequestFinalizeBlock.txs` deterministically,
|
||||
according to the rules set up by the Application, before returning control to Tendermint.
|
||||
Alternatively, it can apply the candidate state corresponding to the same block previously
|
||||
executed via `PrepareProposal` or `ProcessProposal`.
|
||||
* `ResponseFinalizeBlock.tx_results[i].Code == 0` only if the _i_-th transaction is fully valid.
|
||||
* In next-block execution mode, the Application must provide values for `ResponseFinalizeBlock.app_hash`,
|
||||
`ResponseFinalizeBlock.tx_results`, `ResponseFinalizeBlock.validator_updates`, and
|
||||
@@ -605,16 +680,18 @@ from this condition, but not sure), and _p_ receives a Precommit message for rou
|
||||
the current values.
|
||||
* `ResponseFinalizeBlock.validator_updates`, triggered by block `H`, affect validation
|
||||
for blocks `H+1`, `H+2`, and `H+3`. Heights following a validator update are affected in the following way:
|
||||
- Height `H+1`: `NextValidatorsHash` includes the new `validator_updates` value.
|
||||
- Height `H+2`: The validator set change takes effect and `ValidatorsHash` is updated.
|
||||
- Height `H+3`: `decided_last_commit` now includes the altered validator set.
|
||||
* Height `H+1`: `NextValidatorsHash` includes the new `validator_updates` value.
|
||||
* Height `H+2`: The validator set change takes effect and `ValidatorsHash` is updated.
|
||||
* Height `H+3`: `*_last_commit` fields in `PrepareProposal`, `ProcessProposal`, and
|
||||
`FinalizeBlock` now include the altered validator set.
|
||||
* `ResponseFinalizeBlock.consensus_param_updates` returned for block `H` apply to the consensus
|
||||
params for block `H+1`. For more information on the consensus parameters,
|
||||
see the [application spec entry on consensus parameters](../abci/apps.md#consensus-parameters).
|
||||
see the [consensus parameters](./abci%2B%2B_app_requirements.md#consensus-parameters)
|
||||
section.
|
||||
|
||||
* In same-block execution mode, Tendermint will log an error and ignore values for
|
||||
`ResponseFinalizeBlock.app_hash`, `ResponseFinalizeBlock.tx_results`, `ResponseFinalizeBlock.validator_updates`,
|
||||
and `ResponsePrepareProposal.consensus_param_updates`, as those must have been provided by `PrepareProposal`.
|
||||
* Application is expected to persist its state at the end of this call, before calling `ResponseFinalizeBlock`.
|
||||
* `ResponseFinalizeBlock.app_hash` contains an (optional) Merkle root hash of the application state.
|
||||
* `ResponseFinalizeBlock.app_hash` is included
|
||||
* [in next-block execution mode] as the `Header.AppHash` in the next block.
|
||||
@@ -626,11 +703,7 @@ from this condition, but not sure), and _p_ receives a Precommit message for rou
|
||||
of `RequestFinalizeBlock` and the previous committed state.
|
||||
* Later calls to `Query` can return proofs about the application state anchored
|
||||
in this Merkle root hash.
|
||||
* Use `ResponseFinalizeBlock.retain_height` with caution! If all nodes in the network remove historical
|
||||
blocks then this data is permanently lost, and no new nodes will be able to join the network and
|
||||
bootstrap. Historical blocks may also be required for other purposes, e.g. auditing, replay of
|
||||
non-persisted heights, light client verification, and so on.
|
||||
* Just as `ProcessProposal`, the implementation of `FinalizeBlock` MUST be deterministic, since it is
|
||||
* The implementation of `FinalizeBlock` MUST be deterministic, since it is
|
||||
making the Application's state evolve in the context of state machine replication.
|
||||
* Currently, Tendermint will fill up all fields in `RequestFinalizeBlock`, even if they were
|
||||
already passed on to the Application via `RequestPrepareProposal` or `RequestProcessProposal`.
|
||||
@@ -638,9 +711,9 @@ from this condition, but not sure), and _p_ receives a Precommit message for rou
|
||||
(rather than executing the whole block). In this case the Application disregards all parameters in
|
||||
`RequestFinalizeBlock` except `RequestFinalizeBlock.hash`.
|
||||
|
||||
#### When does Tendermint call it?
|
||||
#### When does Tendermint call `FinalizeBlock`?
|
||||
|
||||
When a validator _p_ is in Tendermint consensus height _h_, and _p_ receives
|
||||
When a node _p_ is in Tendermint consensus height _h_, and _p_ receives
|
||||
|
||||
* the Proposal message with block _v_ for a round _r_, along with all its block parts, from _q_,
|
||||
which is the proposer of round _r_, height _h_,
|
||||
@@ -649,16 +722,19 @@ When a validator _p_ is in Tendermint consensus height _h_, and _p_ receives
|
||||
|
||||
then _p_'s Tendermint decides block _v_ and finalizes consensus for height _h_ in the following way
|
||||
|
||||
1. _p_'s Tendermint persists _v_ as decision for height _h_.
|
||||
2. _p_'s Tendermint locks the mempool -- no calls to checkTx on new transactions.
|
||||
3. _p_'s Tendermint calls `RequestFinalizeBlock` with _id(v)_. The call is synchronous.
|
||||
4. _p_'s Application processes block _v_, received in a previous call to `RequestProcessProposal`.
|
||||
5. _p_'s Application commits and persists the state resulting from processing the block.
|
||||
6. _p_'s Application calculates and returns the _AppHash_, along with an array of arrays of bytes representing the output of each of the transactions
|
||||
7. _p_'s Tendermint hashes the array of transaction outputs and stores it in _ResultHash_
|
||||
8. _p_'s Tendermint persists _AppHash_ and _ResultHash_
|
||||
9. _p_'s Tendermint unlocks the mempool -- newly received transactions can now be checked.
|
||||
10. _p_'s starts consensus for a new height _h+1_, round 0
|
||||
1. _p_'s Tendermint persists _v_ as the decision for height _h_.
|
||||
2. _p_'s Tendermint calls `RequestFinalizeBlock` with _v_'s data. The call is synchronous.
|
||||
3. _p_'s Application executes block _v_.
|
||||
4. _p_'s Application calculates and returns the _AppHash_, along with a list containing
|
||||
the outputs of each of the transactions executed.
|
||||
5. _p_'s Tendermint hashes all the transaction outputs and stores it in _ResultHash_.
|
||||
6. _p_'s Tendermint persists the transaction outputs, _AppHash_, and _ResultsHash_.
|
||||
7. _p_'s Tendermint locks the mempool — no calls to `CheckTx` on new transactions.
|
||||
8. _p_'s Tendermint calls `RequestCommit` to instruct the Application to persist its state.
|
||||
9. _p_'s Tendermint, optionally, re-checks all outstanding transactions in the mempool
|
||||
against the newly persisted Application state.
|
||||
10. _p_'s Tendermint unlocks the mempool — newly received transactions can now be checked.
|
||||
11. _p_'s starts consensus for height _h+1_, round 0
|
||||
|
||||
## Data Types existing in ABCI
|
||||
|
||||
@@ -696,13 +772,13 @@ Most of the data structures used in ABCI are shared [common data structures](../
|
||||
|
||||
* **Fields**:
|
||||
|
||||
| Name | Type | Description | Field Number |
|
||||
|--------------------|--------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------|--------------|
|
||||
| type | [MisbehaviorType](#misbehaviortype) | Type of the misbehavior. An enum of possible misbehaviors. | 1 |
|
||||
| validator | [Validator](#validator) | The offending validator | 2 |
|
||||
| height | int64 | Height when the offense occurred | 3 |
|
||||
| time | [google.protobuf.Timestamp](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Timestamp) | Time of the block that was committed at the height that the offense occurred | 4 |
|
||||
| total_voting_power | int64 | Total voting power of the validator set at height `Height` | 5 |
|
||||
| Name | Type | Description | Field Number |
|
||||
|--------------------|-------------------------------------------------|------------------------------------------------------------------------------|--------------|
|
||||
| type | [MisbehaviorType](#misbehaviortype) | Type of the misbehavior. An enum of possible misbehaviors. | 1 |
|
||||
| validator | [Validator](#validator) | The offending validator | 2 |
|
||||
| height | int64 | Height when the offense occurred | 3 |
|
||||
| time | [google.protobuf.Timestamp][protobuf-timestamp] | Timestamp of the block that was committed at height `height` | 4 |
|
||||
| total_voting_power | int64 | Total voting power of the validator set at height `height` | 5 |
|
||||
|
||||
#### MisbehaviorType
|
||||
|
||||
@@ -754,8 +830,8 @@ Most of the data structures used in ABCI are shared [common data structures](../
|
||||
| height | uint64 | The height at which the snapshot was taken (after commit). | 1 |
|
||||
| format | uint32 | An application-specific snapshot format, allowing applications to version their snapshot data format and make backwards-incompatible changes. Tendermint does not interpret this. | 2 |
|
||||
| chunks | uint32 | The number of chunks in the snapshot. Must be at least 1 (even if empty). | 3 |
|
||||
| hash | bytes | TAn arbitrary snapshot hash. Must be equal only for identical snapshots across nodes. Tendermint does not interpret the hash, it only compares them. | 3 |
|
||||
| metadata | bytes | Arbitrary application metadata, for example chunk hashes or other verification data. | 3 |
|
||||
| hash | bytes | An arbitrary snapshot hash. Must be equal only for identical snapshots across nodes. Tendermint does not interpret the hash, it only compares them. | 4 |
|
||||
| metadata | bytes | Arbitrary application metadata, for example chunk hashes or other verification data. | 5 |
|
||||
|
||||
* **Usage**:
|
||||
* Used for state sync snapshots, see the [state sync section](../p2p/messages/state-sync.md) for details.
|
||||
@@ -823,7 +899,7 @@ Most of the data structures used in ABCI are shared [common data structures](../
|
||||
| info | string | Additional information. **May be non-deterministic.** | 4 |
|
||||
| gas_wanted | int64 | Amount of gas requested for transaction. | 5 |
|
||||
| gas_used | int64 | Amount of gas consumed by transaction. | 6 |
|
||||
| events | repeated [Event](abci++_basic_concepts_002_draft.md#events) | Type & Key-Value events for indexing transactions (e.g. by account). | 7 |
|
||||
| events | repeated [Event](abci++_basic_concepts.md#events) | Type & Key-Value events for indexing transactions (e.g. by account). | 7 |
|
||||
| codespace | string | Namespace for the `code`. | 8 |
|
||||
|
||||
### TxAction
|
||||
@@ -842,7 +918,7 @@ enum TxAction {
|
||||
* If `Action` is `UNMODIFIED`, Tendermint includes the transaction in the proposal. Nothing to do on the mempool.
|
||||
* If `Action` is `ADDED`, Tendermint includes the transaction in the proposal. The transaction is _not_ added to the mempool.
|
||||
* If `Action` is `REMOVED`, Tendermint excludes the transaction from the proposal. The transaction is also removed from the mempool if it exists,
|
||||
similar to `CheckTx` returning _false_.
|
||||
similar to `CheckTx` returning an error code.
|
||||
|
||||
### TxRecord
|
||||
|
||||
@@ -905,3 +981,5 @@ enum VerifyStatus {
|
||||
* Tendermint is to sign the whole data structure and attach it to a Precommit message
|
||||
* Upon reception, Tendermint validates the sender's signature and sanity-checks the values of `height`, `round`, and `chain_id`.
|
||||
Then it sends `extension` to the Application via `RequestVerifyVoteExtension` for verification.
|
||||
|
||||
[protobuf-timestamp]: https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Timestamp
|
||||
@@ -11,16 +11,20 @@ This section describes what the Application can expect from Tendermint.
|
||||
|
||||
The Tendermint consensus algorithm is designed to protect safety under any network conditions, as long as
|
||||
less than 1/3 of validators' voting power is byzantine. Most of the time, though, the network will behave
|
||||
synchronously and there will be no byzantine process. In these frequent, benign conditions:
|
||||
synchronously, no process will fall behind, and there will be no byzantine process. The following describes
|
||||
what will happen during a block height _h_ in these frequent, benign conditions:
|
||||
|
||||
* Tendermint will decide in round 0;
|
||||
* Tendermint will decide in round 0, for height _h_;
|
||||
* `PrepareProposal` will be called exactly once at the proposer process of round 0, height _h_;
|
||||
* `ProcessProposal` will be called exactly once at all processes except the proposer of round 0, and
|
||||
* `ProcessProposal` will be called exactly once at all processes, and
|
||||
will return _accept_ in its `Response*`;
|
||||
* `ExtendVote` will be called exactly once at all processes
|
||||
* `VerifyVoteExtension` will be called _n-1_ times at each validator process, where _n_ is the number of validators; and
|
||||
* `FinalizeBlock` will be finally called at all processes at the end of height _h_, conveying the same prepared
|
||||
block that all calls to `PrepareProposal` and `ProcessProposal` had previously reported for height _h_.
|
||||
* `ExtendVote` will be called exactly once at all processes;
|
||||
* `VerifyVoteExtension` will be called exactly _n-1_ times at each validator process, where _n_ is
|
||||
the number of validators, and will always return _accept_ in its `Response*`;
|
||||
* `FinalizeBlock` will be called exactly once at all processes, conveying the same prepared
|
||||
block that all calls to `PrepareProposal` and `ProcessProposal` had previously reported for
|
||||
height _h_; and
|
||||
* `Commit` will finally be called exactly once at all processes at the end of height _h_.
|
||||
|
||||
However, the Application logic must be ready to cope with any possible run of Tendermint for a given
|
||||
height, including bad periods (byzantine proposers, network being asynchronous).
|
||||
@@ -28,7 +32,7 @@ In these cases, the sequence of calls to ABCI++ methods may not be so straighfor
|
||||
the Application should still be able to handle them, e.g., without crashing.
|
||||
The purpose of this section is to define what these sequences look like an a precise way.
|
||||
|
||||
As mentioned in the [Basic Concepts](abci++_basic_concepts_002_draft.md) section, Tendermint
|
||||
As mentioned in the [Basic Concepts](./abci%2B%2B_basic_concepts.md) section, Tendermint
|
||||
acts as a client of ABCI++ and the Application acts as a server. Thus, it is up to Tendermint to
|
||||
determine when and in which order the different ABCI++ methods will be called. A well-written
|
||||
Application design should consider _any_ of these possible sequences.
|
||||
@@ -46,18 +50,15 @@ state-sync = *state-sync-attempt success-sync info
|
||||
state-sync-attempt = offer-snapshot *apply-chunk
|
||||
success-sync = offer-snapshot 1*apply-chunk
|
||||
|
||||
recovery = info *consensus-replay consensus-exec
|
||||
consensus-replay = decide
|
||||
recovery = info consensus-exec
|
||||
|
||||
consensus-exec = (inf)consensus-height
|
||||
consensus-height = *consensus-round decide
|
||||
consensus-height = *consensus-round decide commit
|
||||
consensus-round = proposer / non-proposer
|
||||
|
||||
proposer = prepare-proposal extend-proposer
|
||||
extend-proposer = *got-vote [extend-vote] *got-vote
|
||||
|
||||
non-proposer = *got-vote [extend-non-proposer] *got-vote
|
||||
extend-non-proposer = process-proposal *got-vote [extend-vote]
|
||||
proposer = *got-vote prepare-proposal *got-vote process-proposal [extend]
|
||||
extend = *got-vote extend-vote *got-vote
|
||||
non-proposer = *got-vote [process-proposal] [extend]
|
||||
|
||||
init-chain = %s"<InitChain>"
|
||||
offer-snapshot = %s"<OfferSnapshot>"
|
||||
@@ -68,12 +69,10 @@ process-proposal = %s"<ProcessProposal>"
|
||||
extend-vote = %s"<ExtendVote>"
|
||||
got-vote = %s"<VerifyVoteExtension>"
|
||||
decide = %s"<FinalizeBlock>"
|
||||
commit = %s"<Commit>"
|
||||
```
|
||||
|
||||
>**TODO** Still hesitating... introduce _n_ as total number of validators, so that we can bound the occurrences of
|
||||
>`got-vote` in a round.
|
||||
|
||||
We have kept some of the ABCI++ methods out of the grammar, in order to keep it as clear and concise as possible.
|
||||
We have kept some ABCI methods out of the grammar, in order to keep it as clear and concise as possible.
|
||||
A common reason for keeping all these methods out is that they all can be called at any point in a sequence defined
|
||||
by the grammar above. Other reasons depend on the method in question:
|
||||
|
||||
@@ -115,7 +114,7 @@ Let us now examine the grammar line by line, providing further details.
|
||||
|
||||
* In _state-sync_ mode, Tendermint makes one or more attempts at synchronizing the Application's state.
|
||||
At the beginning of each attempt, it offers the Application a snapshot found at another process.
|
||||
If the Application accepts the snapshop, at sequence of calls to `ApplySnapshotChunk` method follow
|
||||
If the Application accepts the snapshot, a sequence of calls to `ApplySnapshotChunk` method follow
|
||||
to provide the Application with all the snapshots needed, in order to reconstruct the state locally.
|
||||
A successful attempt must provide at least one chunk via `ApplySnapshotChunk`.
|
||||
At the end of a successful attempt, Tendermint calls `Info` to make sure the recontructed state's
|
||||
@@ -128,12 +127,10 @@ Let us now examine the grammar line by line, providing further details.
|
||||
>```
|
||||
|
||||
* In recovery mode, Tendermint first calls `Info` to know from which height it needs to replay decisions
|
||||
to the Application. To replay a decision, Tendermint simply calls `FinalizeBlock` with the decided
|
||||
block at that height. After this, Tendermint enters nomal consensus execution.
|
||||
to the Application. After this, Tendermint enters nomal consensus execution.
|
||||
|
||||
>```abnf
|
||||
>recovery = info *consensus-replay consensus-exec
|
||||
>consensus-replay = decide
|
||||
>recovery = info consensus-exec
|
||||
>```
|
||||
|
||||
* The non-terminal `consensus-exec` is a key point in this grammar. It is an infinite sequence of
|
||||
@@ -145,33 +142,36 @@ Let us now examine the grammar line by line, providing further details.
|
||||
>consensus-exec = (inf)consensus-height
|
||||
>```
|
||||
|
||||
* A consensus height consists of zero or more rounds before deciding via a call to `FinalizeBlock`.
|
||||
In each round, the sequence of method calls depends on whether the local process is the proposer or not.
|
||||
* A consensus height consists of zero or more rounds before deciding and executing via a call to
|
||||
`FinalizeBlock`, followed by a call to `Commit`. In each round, the sequence of method calls
|
||||
depends on whether the local process is the proposer or not. Note that, if a height contains zero
|
||||
rounds, this means the process is replaying an already decided value (catch-up mode).
|
||||
|
||||
>```abnf
|
||||
>consensus-height = *consensus-round decide
|
||||
>consensus-height = *consensus-round decide commit
|
||||
>consensus-round = proposer / non-proposer
|
||||
>```
|
||||
|
||||
* If the local process is the proposer of the current round, Tendermint starts by calling `PrepareProposal`.
|
||||
No calls to methods related to vote extensions (`ExtendVote`, `VerifyVoteExtension`) can be called
|
||||
in the present round before `PrepareProposal`. Once `PrepareProposal` is called, calls to
|
||||
`ExtendVote` and `VerifyVoteExtension` can come in any order, although the former will be called
|
||||
at most once in this round.
|
||||
* For every round, if the local process is the proposer of the current round, Tendermint starts by
|
||||
calling `PrepareProposal`, followed by `ProcessProposal`. Then, optionally, the Application is
|
||||
asked to extend its vote for that round. Calls to `VerifyVoteExtension` can come at any time: the
|
||||
local process may be slightly late in the current round, or votes may come from a future round
|
||||
of this height.
|
||||
|
||||
>```abnf
|
||||
>proposer = prepare-proposal extend-proposer
|
||||
>extend-proposer = *got-vote [extend-vote] *got-vote
|
||||
>proposer = *got-vote prepare-proposal *got-vote process-proposal [extend]
|
||||
>extend = *got-vote extend-vote *got-vote
|
||||
>```
|
||||
|
||||
* If the local process is not the proposer of the current round, Tendermint will call `ProcessProposal`
|
||||
at most once. At most one call to `ExtendVote` can occur only after `ProcessProposal` is called.
|
||||
A number of calls to `VerifyVoteExtension` can occur in any order with respect to `ProcessProposal`
|
||||
and `ExtendVote` throughout the round.
|
||||
* Also for every round, if the local process is _not_ the proposer of the current round, Tendermint
|
||||
will call `ProcessProposal` at most once. At most one call to `ExtendVote` may occur only after
|
||||
`ProcessProposal` is called. A number of calls to `VerifyVoteExtension` can occur in any order
|
||||
with respect to `ProcessProposal` and `ExtendVote` throughout the round. The reasons are the same
|
||||
as above, namely, the process running slightly late in the current round, or votes from future
|
||||
rounds of this height received.
|
||||
|
||||
>```abnf
|
||||
>non-proposer = *got-vote [extend-non-proposer] *got-vote
|
||||
>extend-non-proposer = process-proposal *got-vote [extend-vote]
|
||||
>non-proposer = *got-vote [process-proposal] [extend]
|
||||
>```
|
||||
|
||||
* Finally, the grammar describes all its terminal symbols, which denote the different ABCI++ method calls that
|
||||
@@ -187,6 +187,7 @@ Let us now examine the grammar line by line, providing further details.
|
||||
>extend-vote = %s"<ExtendVote>"
|
||||
>got-vote = %s"<VerifyVoteExtension>"
|
||||
>decide = %s"<FinalizeBlock>"
|
||||
>commit = %s"<Commit>"
|
||||
>```
|
||||
|
||||
## Adapting existing Applications that use ABCI
|
||||
@@ -202,17 +203,21 @@ to undergo any changes in their implementation.
|
||||
|
||||
As for the new methods:
|
||||
|
||||
* `PrepareProposal` must create a list of [TxRecord](./abci++_methods_002_draft.md#txrecord) each containing a
|
||||
transaction passed in `RequestPrepareProposal.txs`, in the same other. The field `action` must be set to `UNMODIFIED`
|
||||
for all [TxRecord](./abci++_methods_002_draft.md#txrecord) elements in the list.
|
||||
* `PrepareProposal` must create a list of [TxRecord](./abci++_methods.md#txrecord) each containing
|
||||
a transaction passed in `RequestPrepareProposal.txs`, in the same other. The field `action` must
|
||||
be set to `UNMODIFIED` for all [TxRecord](./abci++_methods.md#txrecord) elements in the list.
|
||||
The Application must check whether the size of all transactions exceeds the byte limit
|
||||
(`RequestPrepareProposal.max_tx_bytes`). If so, the Application must remove transactions at the end of the list
|
||||
until the total byte size is at or below the limit.
|
||||
(`RequestPrepareProposal.max_tx_bytes`). If so, the Application must remove transactions at the
|
||||
end of the list until the total byte size is at or below the limit.
|
||||
* `ProcessProposal` must set `ResponseProcessProposal.accept` to _true_ and return.
|
||||
* `ExtendVote` is to set `ResponseExtendVote.extension` to an empty byte array and return.
|
||||
* `VerifyVoteExtension` must set `ResponseVerifyVoteExtension.accept` to _true_ if the extension is an empty byte array
|
||||
and _false_ otherwise, then return.
|
||||
* `FinalizeBlock` is to coalesce the implementation of methods `BeginBlock`, `DeliverTx`, `EndBlock`, and `Commit`.
|
||||
Legacy applications looking to reuse old code that implemented `DeliverTx` should wrap the legacy
|
||||
`DeliverTx` logic in a loop that executes one transaction iteration per
|
||||
* `VerifyVoteExtension` must set `ResponseVerifyVoteExtension.accept` to _true_ if the extension is
|
||||
an empty byte array and _false_ otherwise, then return.
|
||||
* `FinalizeBlock` is to coalesce the implementation of methods `BeginBlock`, `DeliverTx`, and
|
||||
`EndBlock`. Legacy applications looking to reuse old code that implemented `DeliverTx` should
|
||||
wrap the legacy `DeliverTx` logic in a loop that executes one transaction iteration per
|
||||
transaction in `RequestFinalizeBlock.tx`.
|
||||
|
||||
Finally, `Commit`, which is kept in ABCI++, no longer returns the `AppHash`. It is now up to
|
||||
`FinalizeBlock` to do so. Thus, a slight refactoring of the old `Commit` implementation will be
|
||||
needed to move the return of `AppHash` to `FinalizeBlock`.
|
||||
@@ -1,156 +0,0 @@
|
||||
# Tendermint v0 Markdown pseudocode
|
||||
|
||||
This translates the latex code for Tendermint consensus from the Tendermint paper into markdown.
|
||||
|
||||
### Initialization
|
||||
|
||||
```go
|
||||
h_p ← 0
|
||||
round_p ← 0
|
||||
step_p is one of {propose, prevote, precommit}
|
||||
decision_p ← Vector()
|
||||
lockedRound_p ← -1
|
||||
lockedValue_p ← nil
|
||||
validValue_p ← nil
|
||||
validRound_p ← -1
|
||||
```
|
||||
|
||||
### StartRound(round)
|
||||
|
||||
```go
|
||||
function startRound(round) {
|
||||
round_p ← round
|
||||
step_p ← propose
|
||||
if proposer(h_p, round_p) = p {
|
||||
if validValue_p != nil {
|
||||
proposal ← validValue_p
|
||||
} else {
|
||||
proposal ← getValue()
|
||||
}
|
||||
broadcast ⟨PROPOSAL, h_p, round_p, proposal, validRound_p⟩
|
||||
} else {
|
||||
schedule OnTimeoutPropose(h_p,round_p) to be executed after timeoutPropose(round_p)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### ReceiveProposal
|
||||
|
||||
In the case where the local node is not locked on any round, the following is ran:
|
||||
|
||||
```go
|
||||
upon ⟨PROPOSAL, h_p, round_p, v, −1) from proposer(h_p, round_p) while step_p = propose do {
|
||||
if valid(v) ∧ (lockedRound_p = −1 ∨ lockedValue_p = v) {
|
||||
broadcast ⟨PREVOTE, h_p, round_p, id(v)⟩
|
||||
} else {
|
||||
broadcast ⟨PREVOTE, h_p, round_p, nil⟩
|
||||
}
|
||||
step_p ← prevote
|
||||
}
|
||||
```
|
||||
|
||||
In the case where the node is locked on a round, the following is ran:
|
||||
|
||||
```go
|
||||
upon ⟨PROPOSAL, h_p, round_p, v, vr⟩ from proposer(h_p, round_p)
|
||||
AND 2f + 1 ⟨PREVOTE, h_p, vr, id(v)⟩
|
||||
while step_p = propose ∧ (vr ≥ 0 ∧ vr < round_p) do {
|
||||
if valid(v) ∧ (lockedRound_p ≤ vr ∨ lockedValue_p = v) {
|
||||
broadcast ⟨PREVOTE, h_p, round_p, id(v)⟩
|
||||
} else {
|
||||
broadcast ⟨PREVOTE, h_p, round_p, nil⟩
|
||||
}
|
||||
step_p ← prevote
|
||||
}
|
||||
```
|
||||
|
||||
### Prevote timeout
|
||||
|
||||
Upon receiving 2f + 1 prevotes, setup a timeout.
|
||||
|
||||
```go
|
||||
upon 2f + 1 ⟨PREVOTE, h_p, vr, *⟩ with step_p = prevote for the first time, do {
|
||||
schedule OnTimeoutPrevote(h_p, round_p) to be executed after timeoutPrevote(round_p)
|
||||
}
|
||||
```
|
||||
|
||||
with OnTimeoutPrevote defined as:
|
||||
|
||||
```go
|
||||
function OnTimeoutPrevote(height, round) {
|
||||
if (height = h_p && round = round_p && step_p = prevote) {
|
||||
broadcast ⟨PRECOMMIT, h_p, round_p, nil⟩
|
||||
step_p ← precommit
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Receiving enough prevotes to precommit
|
||||
|
||||
The following code is ran upon receiving 2f + 1 prevotes for the same block
|
||||
|
||||
```go
|
||||
upon ⟨PROPOSAL, h_p, round_p, v, *⟩
|
||||
from proposer(h_p, round_p)
|
||||
AND 2f + 1 ⟨PREVOTE, h_p, round_p, id(v)⟩
|
||||
while valid(v) ∧ step_p >= prevote for the first time do {
|
||||
if (step_p = prevote) {
|
||||
lockedValue_p ← v
|
||||
lockedRound_p ← round_p
|
||||
broadcast ⟨PRECOMMIT, h_p, round_p, id(v)⟩
|
||||
step_p ← precommit
|
||||
}
|
||||
validValue_p ← v
|
||||
validRound_p ← round_p
|
||||
}
|
||||
```
|
||||
|
||||
And upon receiving 2f + 1 prevotes for nil:
|
||||
|
||||
```go
|
||||
upon 2f + 1 ⟨PREVOTE, h_p, round_p, nil⟩
|
||||
while step_p = prevote do {
|
||||
broadcast ⟨PRECOMMIT, h_p, round_p, nil⟩
|
||||
step_p ← precommit
|
||||
}
|
||||
```
|
||||
|
||||
### Precommit timeout
|
||||
|
||||
Upon receiving 2f + 1 precommits, setup a timeout.
|
||||
|
||||
```go
|
||||
upon 2f + 1 ⟨PRECOMMIT, h_p, vr, *⟩ for the first time, do {
|
||||
schedule OnTimeoutPrecommit(h_p, round_p) to be executed after timeoutPrecommit(round_p)
|
||||
}
|
||||
```
|
||||
|
||||
with OnTimeoutPrecommit defined as:
|
||||
|
||||
```go
|
||||
function OnTimeoutPrecommit(height, round) {
|
||||
if (height = h_p && round = round_p) {
|
||||
StartRound(round_p + 1)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Upon Receiving 2f + 1 precommits
|
||||
|
||||
The following code is ran upon receiving 2f + 1 precommits for the same block
|
||||
|
||||
```go
|
||||
upon ⟨PROPOSAL, h_p, r, v, *⟩
|
||||
from proposer(h_p, r)
|
||||
AND 2f + 1 ⟨ PRECOMMIT, h_p, r, id(v)⟩
|
||||
while decision_p[h_p] = nil do {
|
||||
if (valid(v)) {
|
||||
decision_p[h_p] ← v
|
||||
h_p ← h_p + 1
|
||||
reset lockedRound_p, lockedValue_p,validRound_p and validValue_p to initial values
|
||||
StartRound(0)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If we don't see 2f + 1 precommits for the same block, we wait until we get 2f + 1 precommits, and the timeout occurs.
|
||||
@@ -1,162 +0,0 @@
|
||||
# Tendermint v1 Markdown pseudocode
|
||||
|
||||
This adds hooks for the existing ABCI to the prior pseudocode
|
||||
|
||||
### Initialization
|
||||
|
||||
```go
|
||||
h_p ← 0
|
||||
round_p ← 0
|
||||
step_p is one of {propose, prevote, precommit}
|
||||
decision_p ← Vector()
|
||||
lockedValue_p ← nil
|
||||
validValue_p ← nil
|
||||
validRound_p ← -1
|
||||
```
|
||||
|
||||
### StartRound(round)
|
||||
|
||||
```go
|
||||
function startRound(round) {
|
||||
round_p ← round
|
||||
step_p ← propose
|
||||
if proposer(h_p, round_p) = p {
|
||||
if validValue_p != nil {
|
||||
proposal ← validValue_p
|
||||
} else {
|
||||
txdata ← mempool.GetBlock()
|
||||
// getBlockProposal fills in header
|
||||
proposal ← getBlockProposal(txdata)
|
||||
}
|
||||
broadcast ⟨PROPOSAL, h_p, round_p, proposal, validRound_p⟩
|
||||
} else {
|
||||
schedule OnTimeoutPropose(h_p,round_p) to be executed after timeoutPropose(round_p)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### ReceiveProposal
|
||||
|
||||
In the case where the local node is not locked on any round, the following is ran:
|
||||
|
||||
```go
|
||||
upon ⟨PROPOSAL, h_p, round_p, v, −1) from proposer(h_p, round_p) while step_p = propose do {
|
||||
if valid(v) ∧ (lockedRound_p = −1 ∨ lockedValue_p = v) {
|
||||
broadcast ⟨PREVOTE, h_p, round_p, id(v)⟩
|
||||
} else {
|
||||
broadcast ⟨PREVOTE, h_p, round_p, nil⟩
|
||||
}
|
||||
step_p ← prevote
|
||||
}
|
||||
```
|
||||
|
||||
In the case where the node is locked on a round, the following is ran:
|
||||
|
||||
```go
|
||||
upon ⟨PROPOSAL, h_p, round_p, v, vr⟩
|
||||
from proposer(h_p, round_p)
|
||||
AND 2f + 1 ⟨PREVOTE, h_p, vr, id(v)⟩
|
||||
while step_p = propose ∧ (vr ≥ 0 ∧ vr < round_p) do {
|
||||
if valid(v) ∧ (lockedRound_p ≤ vr ∨ lockedValue_p = v) {
|
||||
broadcast ⟨PREVOTE, h_p, round_p, id(v)⟩
|
||||
} else {
|
||||
broadcast ⟨PREVOTE, h_p, round_p, nil⟩
|
||||
}
|
||||
step_p ← prevote
|
||||
}
|
||||
```
|
||||
|
||||
### Prevote timeout
|
||||
|
||||
Upon receiving 2f + 1 prevotes, setup a timeout.
|
||||
|
||||
```go
|
||||
upon 2f + 1 ⟨PREVOTE, h_p, vr, -1⟩
|
||||
with step_p = prevote for the first time, do {
|
||||
schedule OnTimeoutPrevote(h_p, round_p) to be executed after timeoutPrevote(round_p)
|
||||
}
|
||||
```
|
||||
|
||||
with OnTimeoutPrevote defined as:
|
||||
|
||||
```go
|
||||
function OnTimeoutPrevote(height, round) {
|
||||
if (height = h_p && round = round_p && step_p = prevote) {
|
||||
broadcast ⟨PRECOMMIT, h_p, round_p, nil⟩
|
||||
step_p ← precommit
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Receiving enough prevotes to precommit
|
||||
|
||||
The following code is ran upon receiving 2f + 1 prevotes for the same block
|
||||
|
||||
```go
|
||||
upon ⟨PROPOSAL, h_p, round_p, v, *⟩
|
||||
from proposer(h_p, round_p)
|
||||
AND 2f + 1 ⟨PREVOTE, h_p, vr, id(v)⟩
|
||||
while valid(v) ∧ step_p >= prevote for the first time do {
|
||||
if (step_p = prevote) {
|
||||
lockedValue_p ← v
|
||||
lockedRound_p ← round_p
|
||||
broadcast ⟨PRECOMMIT, h_p, round_p, id(v)⟩
|
||||
step_p ← precommit
|
||||
}
|
||||
validValue_p ← v
|
||||
validRound_p ← round_p
|
||||
}
|
||||
```
|
||||
|
||||
And upon receiving 2f + 1 prevotes for nil:
|
||||
|
||||
```go
|
||||
upon 2f + 1 ⟨PREVOTE, h_p, round_p, nil⟩
|
||||
while step_p = prevote do {
|
||||
broadcast ⟨PRECOMMIT, h_p, round_p, nil⟩
|
||||
step_p ← precommit
|
||||
}
|
||||
```
|
||||
|
||||
### Precommit timeout
|
||||
|
||||
Upon receiving 2f + 1 precommits, setup a timeout.
|
||||
|
||||
```go
|
||||
upon 2f + 1 ⟨PRECOMMIT, h_p, vr, *⟩ for the first time, do {
|
||||
schedule OnTimeoutPrecommit(h_p, round_p) to be executed after timeoutPrecommit(round_p)
|
||||
}
|
||||
```
|
||||
|
||||
with OnTimeoutPrecommit defined as:
|
||||
|
||||
```go
|
||||
function OnTimeoutPrecommit(height, round) {
|
||||
if (height = h_p && round = round_p) {
|
||||
StartRound(round_p + 1)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Upon Receiving 2f + 1 precommits
|
||||
|
||||
The following code is ran upon receiving 2f + 1 precommits for the same block
|
||||
|
||||
```go
|
||||
upon ⟨PROPOSAL, h_p, r, v, *⟩
|
||||
from proposer(h_p, r)
|
||||
AND 2f + 1 ⟨ PRECOMMIT, h_p, r, id(v)⟩
|
||||
while decision_p[h_p] = nil do {
|
||||
if (valid(v)) {
|
||||
decision_p[h_p] ← v
|
||||
h_p ← h_p + 1
|
||||
reset lockedRound_p, lockedValue_p,validRound_p and validValue_p to initial values
|
||||
ABCI.BeginBlock(v.header)
|
||||
ABCI.DeliverTxs(v.data)
|
||||
ABCI.EndBlock()
|
||||
StartRound(0)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If we don't see 2f + 1 precommits for the same block, we wait until we get 2f + 1 precommits, and the timeout occurs.
|
||||
@@ -1,180 +0,0 @@
|
||||
# Tendermint v2 Markdown pseudocode
|
||||
|
||||
This adds a single-threaded implementation of ABCI++,
|
||||
with no optimization for splitting out verifying the header and verifying the proposal.
|
||||
|
||||
### Initialization
|
||||
|
||||
```go
|
||||
h_p ← 0
|
||||
round_p ← 0
|
||||
step_p is one of {propose, prevote, precommit}
|
||||
decision_p ← Vector()
|
||||
lockedValue_p ← nil
|
||||
validValue_p ← nil
|
||||
validRound_p ← -1
|
||||
```
|
||||
|
||||
### StartRound(round)
|
||||
|
||||
```go
|
||||
function startRound(round) {
|
||||
round_p ← round
|
||||
step_p ← propose
|
||||
if proposer(h_p, round_p) = p {
|
||||
if validValue_p != nil {
|
||||
proposal ← validValue_p
|
||||
} else {
|
||||
txdata ← mempool.GetBlock()
|
||||
// getUnpreparedBlockProposal takes tx data, and fills in the unprepared header data
|
||||
unpreparedProposal ← getUnpreparedBlockProposal(txdata)
|
||||
// ABCI++: the proposer may reorder/update transactions in `unpreparedProposal`
|
||||
proposal ← ABCI.PrepareProposal(unpreparedProposal)
|
||||
}
|
||||
broadcast ⟨PROPOSAL, h_p, round_p, proposal, validRound_p⟩
|
||||
} else {
|
||||
schedule OnTimeoutPropose(h_p,round_p) to be executed after timeoutPropose(round_p)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### ReceiveProposal
|
||||
|
||||
In the case where the local node is not locked on any round, the following is ran:
|
||||
|
||||
```go
|
||||
upon ⟨PROPOSAL, h_p, round_p, v, −1) from proposer(h_p, round_p) while step_p = propose do {
|
||||
if valid(v) ∧ ABCI.ProcessProposal(h_p, v).accept ∧ (lockedRound_p = −1 ∨ lockedValue_p = v) {
|
||||
broadcast ⟨PREVOTE, h_p, round_p, id(v)⟩
|
||||
} else {
|
||||
broadcast ⟨PREVOTE, h_p, round_p, nil⟩
|
||||
// Include any slashing evidence that may be sent in the process proposal response
|
||||
for evidence in ABCI.ProcessProposal(h_p, v).evidence_list {
|
||||
broadcast ⟨EVIDENCE, evidence⟩
|
||||
}
|
||||
}
|
||||
step_p ← prevote
|
||||
}
|
||||
```
|
||||
|
||||
In the case where the node is locked on a round, the following is ran:
|
||||
|
||||
```go
|
||||
upon ⟨PROPOSAL, h_p, round_p, v, vr⟩
|
||||
from proposer(h_p, round_p)
|
||||
AND 2f + 1 ⟨PREVOTE, h_p, vr, id(v)⟩
|
||||
while step_p = propose ∧ (vr ≥ 0 ∧ vr < round_p) do {
|
||||
if valid(v) ∧ ABCI.ProcessProposal(h_p, v).accept ∧ (lockedRound_p ≤ vr ∨ lockedValue_p = v) {
|
||||
broadcast ⟨PREVOTE, h_p, round_p, id(v)⟩
|
||||
} else {
|
||||
broadcast ⟨PREVOTE, h_p, round_p, nil⟩
|
||||
// Include any slashing evidence that may be sent in the process proposal response
|
||||
for evidence in ABCI.ProcessProposal(h_p, v).evidence_list {
|
||||
broadcast ⟨EVIDENCE, evidence⟩
|
||||
}
|
||||
}
|
||||
step_p ← prevote
|
||||
}
|
||||
```
|
||||
|
||||
### Prevote timeout
|
||||
|
||||
Upon receiving 2f + 1 prevotes, setup a timeout.
|
||||
|
||||
```go
|
||||
upon 2f + 1 ⟨PREVOTE, h_p, vr, -1⟩
|
||||
with step_p = prevote for the first time, do {
|
||||
schedule OnTimeoutPrevote(h_p, round_p) to be executed after timeoutPrevote(round_p)
|
||||
}
|
||||
```
|
||||
|
||||
with OnTimeoutPrevote defined as:
|
||||
|
||||
```go
|
||||
function OnTimeoutPrevote(height, round) {
|
||||
if (height = h_p && round = round_p && step_p = prevote) {
|
||||
precommit_extension ← ABCI.ExtendVote(h_p, round_p, nil)
|
||||
broadcast ⟨PRECOMMIT, h_p, round_p, nil, precommit_extension⟩
|
||||
step_p ← precommit
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Receiving enough prevotes to precommit
|
||||
|
||||
The following code is ran upon receiving 2f + 1 prevotes for the same block
|
||||
|
||||
```go
|
||||
upon ⟨PROPOSAL, h_p, round_p, v, *⟩
|
||||
from proposer(h_p, round_p)
|
||||
AND 2f + 1 ⟨PREVOTE, h_p, vr, id(v)⟩
|
||||
while valid(v) ∧ step_p >= prevote for the first time do {
|
||||
if (step_p = prevote) {
|
||||
lockedValue_p ← v
|
||||
lockedRound_p ← round_p
|
||||
precommit_extension ← ABCI.ExtendVote(h_p, round_p, id(v))
|
||||
broadcast ⟨PRECOMMIT, h_p, round_p, id(v), precommit_extension⟩
|
||||
step_p ← precommit
|
||||
}
|
||||
validValue_p ← v
|
||||
validRound_p ← round_p
|
||||
}
|
||||
```
|
||||
|
||||
And upon receiving 2f + 1 prevotes for nil:
|
||||
|
||||
```go
|
||||
upon 2f + 1 ⟨PREVOTE, h_p, round_p, nil⟩
|
||||
while step_p = prevote do {
|
||||
precommit_extension ← ABCI.ExtendVote(h_p, round_p, nil)
|
||||
broadcast ⟨PRECOMMIT, h_p, round_p, nil, precommit_extension⟩
|
||||
step_p ← precommit
|
||||
}
|
||||
```
|
||||
|
||||
### Upon receiving a precommit
|
||||
|
||||
Upon receiving a precommit `precommit`, we ensure that `ABCI.VerifyVoteExtension(precommit.precommit_extension) = true`
|
||||
before accepting the precommit. This is akin to how we check the signature on precommits normally, hence its not wrapped
|
||||
in the syntax of methods from the paper.
|
||||
|
||||
### Precommit timeout
|
||||
|
||||
Upon receiving 2f + 1 precommits, setup a timeout.
|
||||
|
||||
```go
|
||||
upon 2f + 1 ⟨PRECOMMIT, h_p, vr, *⟩ for the first time, do {
|
||||
schedule OnTimeoutPrecommit(h_p, round_p) to be executed after timeoutPrecommit(round_p)
|
||||
}
|
||||
```
|
||||
|
||||
with OnTimeoutPrecommit defined as:
|
||||
|
||||
```go
|
||||
function OnTimeoutPrecommit(height, round) {
|
||||
if (height = h_p && round = round_p) {
|
||||
StartRound(round_p + 1)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Upon Receiving 2f + 1 precommits
|
||||
|
||||
The following code is ran upon receiving 2f + 1 precommits for the same block
|
||||
|
||||
```go
|
||||
upon ⟨PROPOSAL, h_p, r, v, *⟩
|
||||
from proposer(h_p, r)
|
||||
AND 2f + 1 ⟨ PRECOMMIT, h_p, r, id(v)⟩
|
||||
while decision_p[h_p] = nil do {
|
||||
if (valid(v)) {
|
||||
decision_p[h_p] ← v
|
||||
h_p ← h_p + 1
|
||||
reset lockedRound_p, lockedValue_p,validRound_p and validValue_p to initial values
|
||||
ABCI.FinalizeBlock(id(v))
|
||||
StartRound(0)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If we don't see 2f + 1 precommits for the same block, we wait until we get 2f + 1 precommits, and the timeout occurs.
|
||||
@@ -1,201 +0,0 @@
|
||||
# Tendermint v3 Markdown pseudocode
|
||||
|
||||
This is a single-threaded implementation of ABCI++,
|
||||
with an optimization for the ProcessProposal phase.
|
||||
Namely, processing of the header and the block data is separated into two different functions.
|
||||
|
||||
### Initialization
|
||||
|
||||
```go
|
||||
h_p ← 0
|
||||
round_p ← 0
|
||||
step_p is one of {propose, prevote, precommit}
|
||||
decision_p ← Vector()
|
||||
lockedValue_p ← nil
|
||||
validValue_p ← nil
|
||||
validRound_p ← -1
|
||||
```
|
||||
|
||||
### StartRound(round)
|
||||
|
||||
```go
|
||||
function startRound(round) {
|
||||
round_p ← round
|
||||
step_p ← propose
|
||||
if proposer(h_p, round_p) = p {
|
||||
if validValue_p != nil {
|
||||
proposal ← validValue_p
|
||||
} else {
|
||||
txdata ← mempool.GetBlock()
|
||||
// getUnpreparedBlockProposal fills in header
|
||||
unpreparedProposal ← getUnpreparedBlockProposal(txdata)
|
||||
proposal ← ABCI.PrepareProposal(unpreparedProposal)
|
||||
}
|
||||
broadcast ⟨PROPOSAL, h_p, round_p, proposal, validRound_p⟩
|
||||
} else {
|
||||
schedule OnTimeoutPropose(h_p,round_p) to be executed after timeoutPropose(round_p)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### ReceiveProposal
|
||||
|
||||
In the case where the local node is not locked on any round, the following is ran:
|
||||
|
||||
```go
|
||||
upon ⟨PROPOSAL, h_p, round_p, v_header, −1) from proposer(h_p, round_p) while step_p = propose do {
|
||||
prevote_nil ← false
|
||||
// valid is Tendermints validation, ABCI.VerifyHeader is the applications
|
||||
if valid(v_header) ∧ ABCI.VerifyHeader(h_p, v_header) ∧ (lockedRound_p = −1 ∨ lockedValue_p = id(v_header)) {
|
||||
wait to receive proposal v corresponding to v_header
|
||||
// We split up the app's header verification from the remainder of its processing of the proposal
|
||||
if ABCI.ProcessProposal(h_p, v).accept {
|
||||
broadcast ⟨PREVOTE, h_p, round_p, id(v)⟩
|
||||
} else {
|
||||
prevote_nil ← true
|
||||
// Include any slashing evidence that may be sent in the process proposal response
|
||||
for evidence in ABCI.ProcessProposal(h_p, v).evidence_list {
|
||||
broadcast ⟨EVIDENCE, evidence⟩
|
||||
}
|
||||
}
|
||||
} else {
|
||||
prevote_nil ← true
|
||||
}
|
||||
if prevote_nil {
|
||||
broadcast ⟨PREVOTE, h_p, round_p, nil⟩
|
||||
}
|
||||
step_p ← prevote
|
||||
}
|
||||
```
|
||||
|
||||
In the case where the node is locked on a round, the following is ran:
|
||||
|
||||
```go
|
||||
upon ⟨PROPOSAL, h_p, round_p, v_header, vr⟩
|
||||
from proposer(h_p, round_p)
|
||||
AND 2f + 1 ⟨PREVOTE, h_p, vr, id(v_header)⟩
|
||||
while step_p = propose ∧ (vr ≥ 0 ∧ vr < round_p) do {
|
||||
prevote_nil ← false
|
||||
if valid(v) ∧ ABCI.VerifyHeader(h_p, v.header) ∧ (lockedRound_p ≤ vr ∨ lockedValue_p = v) {
|
||||
wait to receive proposal v corresponding to v_header
|
||||
// We split up the app's header verification from the remainder of its processing of the proposal
|
||||
if ABCI.ProcessProposal(h_p, v).accept {
|
||||
broadcast ⟨PREVOTE, h_p, round_p, id(v)⟩
|
||||
} else {
|
||||
prevote_nil ← true
|
||||
// Include any slashing evidence that may be sent in the process proposal response
|
||||
for evidence in ABCI.ProcessProposal(h_p, v).evidence_list {
|
||||
broadcast ⟨EVIDENCE, evidence⟩
|
||||
}
|
||||
}
|
||||
} else {
|
||||
prevote_nil ← true
|
||||
}
|
||||
if prevote_nil {
|
||||
broadcast ⟨PREVOTE, h_p, round_p, nil⟩
|
||||
}
|
||||
step_p ← prevote
|
||||
}
|
||||
```
|
||||
|
||||
### Prevote timeout
|
||||
|
||||
Upon receiving 2f + 1 prevotes, setup a timeout.
|
||||
|
||||
```go
|
||||
upon 2f + 1 ⟨PREVOTE, h_p, vr, -1⟩
|
||||
with step_p = prevote for the first time, do {
|
||||
schedule OnTimeoutPrevote(h_p, round_p) to be executed after timeoutPrevote(round_p)
|
||||
}
|
||||
```
|
||||
|
||||
with OnTimeoutPrevote defined as:
|
||||
|
||||
```go
|
||||
function OnTimeoutPrevote(height, round) {
|
||||
if (height = h_p && round = round_p && step_p = prevote) {
|
||||
precommit_extension ← ABCI.ExtendVote(h_p, round_p, nil)
|
||||
broadcast ⟨PRECOMMIT, h_p, round_p, nil, precommit_extension⟩
|
||||
step_p ← precommit
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Receiving enough prevotes to precommit
|
||||
|
||||
The following code is ran upon receiving 2f + 1 prevotes for the same block
|
||||
|
||||
```go
|
||||
upon ⟨PROPOSAL, h_p, round_p, v, *⟩
|
||||
from proposer(h_p, round_p)
|
||||
AND 2f + 1 ⟨PREVOTE, h_p, vr, id(v)⟩
|
||||
while valid(v) ∧ step_p >= prevote for the first time do {
|
||||
if (step_p = prevote) {
|
||||
lockedValue_p ← v
|
||||
lockedRound_p ← round_p
|
||||
precommit_extension ← ABCI.ExtendVote(h_p, round_p, id(v))
|
||||
broadcast ⟨PRECOMMIT, h_p, round_p, id(v), precommit_extension⟩
|
||||
step_p ← precommit
|
||||
}
|
||||
validValue_p ← v
|
||||
validRound_p ← round_p
|
||||
}
|
||||
```
|
||||
|
||||
And upon receiving 2f + 1 prevotes for nil:
|
||||
|
||||
```go
|
||||
upon 2f + 1 ⟨PREVOTE, h_p, round_p, nil⟩
|
||||
while step_p = prevote do {
|
||||
precommit_extension ← ABCI.ExtendVote(h_p, round_p, nil)
|
||||
broadcast ⟨PRECOMMIT, h_p, round_p, nil, precommit_extension⟩
|
||||
step_p ← precommit
|
||||
}
|
||||
```
|
||||
|
||||
### Upon receiving a precommit
|
||||
|
||||
Upon receiving a precommit `precommit`, we ensure that `ABCI.VerifyVoteExtension(precommit.precommit_extension) = true`
|
||||
before accepting the precommit. This is akin to how we check the signature on precommits normally, hence its not wrapped
|
||||
in the syntax of methods from the paper.
|
||||
|
||||
### Precommit timeout
|
||||
|
||||
Upon receiving 2f + 1 precommits, setup a timeout.
|
||||
|
||||
```go
|
||||
upon 2f + 1 ⟨PRECOMMIT, h_p, vr, *⟩ for the first time, do {
|
||||
schedule OnTimeoutPrecommit(h_p, round_p) to be executed after timeoutPrecommit(round_p)
|
||||
}
|
||||
```
|
||||
|
||||
with OnTimeoutPrecommit defined as:
|
||||
|
||||
```go
|
||||
function OnTimeoutPrecommit(height, round) {
|
||||
if (height = h_p && round = round_p) {
|
||||
StartRound(round_p + 1)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Upon Receiving 2f + 1 precommits
|
||||
|
||||
The following code is ran upon receiving 2f + 1 precommits for the same block
|
||||
|
||||
```go
|
||||
upon ⟨PROPOSAL, h_p, r, v, *⟩
|
||||
from proposer(h_p, r)
|
||||
AND 2f + 1 ⟨ PRECOMMIT, h_p, r, id(v)⟩
|
||||
while decision_p[h_p] = nil do {
|
||||
if (valid(v)) {
|
||||
decision_p[h_p] ← v
|
||||
h_p ← h_p + 1
|
||||
reset lockedRound_p, lockedValue_p,validRound_p and validValue_p to initial values
|
||||
ABCI.FinalizeBlock(id(v))
|
||||
StartRound(0)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If we don't see 2f + 1 precommits for the same block, we wait until we get 2f + 1 precommits, and the timeout occurs.
|
||||
@@ -1,199 +0,0 @@
|
||||
# Tendermint v4 Markdown pseudocode
|
||||
|
||||
This is a multi-threaded implementation of ABCI++,
|
||||
where ProcessProposal starts when the proposal is received, but ends before precommitting.
|
||||
|
||||
### Initialization
|
||||
|
||||
```go
|
||||
h_p ← 0
|
||||
round_p ← 0
|
||||
step_p is one of {propose, prevote, precommit}
|
||||
decision_p ← Vector()
|
||||
lockedValue_p ← nil
|
||||
validValue_p ← nil
|
||||
validRound_p ← -1
|
||||
```
|
||||
|
||||
### StartRound(round)
|
||||
|
||||
```go
|
||||
function startRound(round) {
|
||||
round_p ← round
|
||||
step_p ← propose
|
||||
if proposer(h_p, round_p) = p {
|
||||
if validValue_p != nil {
|
||||
proposal ← validValue_p
|
||||
} else {
|
||||
txdata ← mempool.GetBlock()
|
||||
// getUnpreparedBlockProposal fills in header
|
||||
unpreparedProposal ← getUnpreparedBlockProposal(txdata)
|
||||
proposal ← ABCI.PrepareProposal(unpreparedProposal)
|
||||
}
|
||||
broadcast ⟨PROPOSAL, h_p, round_p, proposal, validRound_p⟩
|
||||
} else {
|
||||
schedule OnTimeoutPropose(h_p,round_p) to be executed after timeoutPropose(round_p)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### ReceiveProposal
|
||||
|
||||
In the case where the local node is not locked on any round, the following is ran:
|
||||
|
||||
```go
|
||||
upon ⟨PROPOSAL, h_p, round_p, v, −1) from proposer(h_p, round_p) while step_p = propose do {
|
||||
if valid(v) ∧ ABCI.VerifyHeader(h_p, v.header) ∧ (lockedRound_p = −1 ∨ lockedValue_p = v) {
|
||||
// We fork process proposal into a parallel process
|
||||
Fork ABCI.ProcessProposal(h_p, v)
|
||||
broadcast ⟨PREVOTE, h_p, round_p, id(v)⟩
|
||||
} else {
|
||||
broadcast ⟨PREVOTE, h_p, round_p, nil⟩
|
||||
}
|
||||
step_p ← prevote
|
||||
}
|
||||
```
|
||||
|
||||
In the case where the node is locked on a round, the following is ran:
|
||||
|
||||
```go
|
||||
upon ⟨PROPOSAL, h_p, round_p, v, vr⟩
|
||||
from proposer(h_p, round_p)
|
||||
AND 2f + 1 ⟨PREVOTE, h_p, vr, id(v)⟩
|
||||
while step_p = propose ∧ (vr ≥ 0 ∧ vr < round_p) do {
|
||||
if valid(v) ∧ ABCI.VerifyHeader(h_p, v.header) ∧ (lockedRound_p ≤ vr ∨ lockedValue_p = v) {
|
||||
// We fork process proposal into a parallel process
|
||||
Fork ABCI.ProcessProposal(h_p, v)
|
||||
broadcast ⟨PREVOTE, h_p, round_p, id(v)⟩
|
||||
} else {
|
||||
broadcast ⟨PREVOTE, h_p, round_p, nil⟩
|
||||
}
|
||||
step_p ← prevote
|
||||
}
|
||||
```
|
||||
|
||||
### Prevote timeout
|
||||
|
||||
Upon receiving 2f + 1 prevotes, setup a timeout.
|
||||
|
||||
```go
|
||||
upon 2f + 1 ⟨PREVOTE, h_p, vr, -1⟩
|
||||
with step_p = prevote for the first time, do {
|
||||
schedule OnTimeoutPrevote(h_p, round_p) to be executed after timeoutPrevote(round_p)
|
||||
}
|
||||
```
|
||||
|
||||
with OnTimeoutPrevote defined as:
|
||||
|
||||
```go
|
||||
def OnTimeoutPrevote(height, round) {
|
||||
if (height = h_p && round = round_p && step_p = prevote) {
|
||||
// Join the ProcessProposal, and output any evidence in case it has some.
|
||||
processProposalOutput ← Join ABCI.ProcessProposal(h_p, v)
|
||||
for evidence in processProposalOutput.evidence_list {
|
||||
broadcast ⟨EVIDENCE, evidence⟩
|
||||
}
|
||||
|
||||
precommit_extension ← ABCI.ExtendVote(h_p, round_p, nil)
|
||||
broadcast ⟨PRECOMMIT, h_p, round_p, nil, precommit_extension⟩
|
||||
step_p ← precommit
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Receiving enough prevotes to precommit
|
||||
|
||||
The following code is ran upon receiving 2f + 1 prevotes for the same block
|
||||
|
||||
```go
|
||||
upon ⟨PROPOSAL, h_p, round_p, v, *⟩
|
||||
from proposer(h_p, round_p)
|
||||
AND 2f + 1 ⟨PREVOTE, h_p, vr, id(v)⟩
|
||||
while valid(v) ∧ step_p >= prevote for the first time do {
|
||||
if (step_p = prevote) {
|
||||
lockedValue_p ← v
|
||||
lockedRound_p ← round_p
|
||||
processProposalOutput ← Join ABCI.ProcessProposal(h_p, v)
|
||||
// If the proposal is valid precommit as before.
|
||||
// If it was invalid, precommit nil.
|
||||
// Note that ABCI.ProcessProposal(h_p, v).accept is deterministic for all honest nodes.
|
||||
precommit_value ← nil
|
||||
if processProposalOutput.accept {
|
||||
precommit_value ← id(v)
|
||||
}
|
||||
precommit_extension ← ABCI.ExtendVote(h_p, round_p, precommit_value)
|
||||
broadcast ⟨PRECOMMIT, h_p, round_p, precommit_value, precommit_extension⟩
|
||||
for evidence in processProposalOutput.evidence_list {
|
||||
broadcast ⟨EVIDENCE, evidence⟩
|
||||
}
|
||||
|
||||
step_p ← precommit
|
||||
}
|
||||
validValue_p ← v
|
||||
validRound_p ← round_p
|
||||
}
|
||||
```
|
||||
|
||||
And upon receiving 2f + 1 prevotes for nil:
|
||||
|
||||
```go
|
||||
upon 2f + 1 ⟨PREVOTE, h_p, round_p, nil⟩
|
||||
while step_p = prevote do {
|
||||
// Join ABCI.ProcessProposal, and broadcast any evidence if it exists.
|
||||
processProposalOutput ← Join ABCI.ProcessProposal(h_p, v)
|
||||
for evidence in processProposalOutput.evidence_list {
|
||||
broadcast ⟨EVIDENCE, evidence⟩
|
||||
}
|
||||
|
||||
precommit_extension ← ABCI.ExtendVote(h_p, round_p, nil)
|
||||
broadcast ⟨PRECOMMIT, h_p, round_p, nil, precommit_extension⟩
|
||||
step_p ← precommit
|
||||
}
|
||||
```
|
||||
|
||||
### Upon receiving a precommit
|
||||
|
||||
Upon receiving a precommit `precommit`, we ensure that `ABCI.VerifyVoteExtension(precommit.precommit_extension) = true`
|
||||
before accepting the precommit. This is akin to how we check the signature on precommits normally, hence its not wrapped
|
||||
in the syntax of methods from the paper.
|
||||
|
||||
### Precommit timeout
|
||||
|
||||
Upon receiving 2f + 1 precommits, setup a timeout.
|
||||
|
||||
```go
|
||||
upon 2f + 1 ⟨PRECOMMIT, h_p, vr, *⟩ for the first time, do {
|
||||
schedule OnTimeoutPrecommit(h_p, round_p) to be executed after timeoutPrecommit(round_p)
|
||||
}
|
||||
```
|
||||
|
||||
with OnTimeoutPrecommit defined as:
|
||||
|
||||
```go
|
||||
def OnTimeoutPrecommit(height, round) {
|
||||
if (height = h_p && round = round_p) {
|
||||
StartRound(round_p + 1)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Upon Receiving 2f + 1 precommits
|
||||
|
||||
The following code is ran upon receiving 2f + 1 precommits for the same block
|
||||
|
||||
```go
|
||||
upon ⟨PROPOSAL, h_p, r, v, *⟩
|
||||
from proposer(h_p, r)
|
||||
AND 2f + 1 ⟨ PRECOMMIT, h_p, r, id(v)⟩
|
||||
while decision_p[h_p] = nil do {
|
||||
if (valid(v)) {
|
||||
decision_p[h_p] ← v
|
||||
h_p ← h_p + 1
|
||||
reset lockedRound_p, lockedValue_p,validRound_p and validValue_p to initial values
|
||||
ABCI.FinalizeBlock(id(v))
|
||||
StartRound(0)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If we don't see 2f + 1 precommits for the same block, we wait until we get 2f + 1 precommits, and the timeout occurs.
|
||||
@@ -1,91 +0,0 @@
|
||||
## Communication between peers and components within the blocksync reactor
|
||||
|
||||
A newly joined or recovering node is connecting to a number of peers in order to sync up to the latest height in the blockchain. The peers do not neccessarily have to be validators but we assume that at least one of the peers is correct. The node requests the status of peers in ordeer to learn the maximum and minimum heights for which the peer has blocks. Based on this, the node decides on the maximum height it needs to sync up to. **There is no additional check** on whether the peers were lying about their heights.
|
||||
|
||||
Each peer has an open p2p channel. The number of total requests in flight is limited (`maxPendingRequests` initially set to `maxTotalRequesters`). Additionally, there is an upper limit on requests **per** peer (20).
|
||||
|
||||
Once a node receives messages via the p2p channel, they are propagated further via the reactor's go channels. This section contains details on each of the open communication channels, their capacity and when they get activated.
|
||||
|
||||
On startup, the reactor fires up four go routines and starts the block pool service:
|
||||
1. Process requests
|
||||
2. Pool routine
|
||||
3. Handle p2p channel messages
|
||||
4. Process peer updates
|
||||
|
||||
The process requests routine sends out requests and error messages to peers while all requests received through the p2p channel are processed within the `processBlockSyncCh` routine (routine 3 above).
|
||||
|
||||
The pool routine picks out blocks form the block pool and processes them. It also checks whether the node should switch to consensus (#2).
|
||||
|
||||
On peer update messages the reactor adds or removes the peer that has sent the update message (#4) .
|
||||
|
||||
**Note** There is currently a check for whether we have a message from an empty peer.
|
||||
|
||||
``` go
|
||||
// XXX: Pool#RedoRequest can sometimes give us an empty peer.
|
||||
if len(peerUpdate.NodeID) == 0 {
|
||||
return
|
||||
}
|
||||
```
|
||||
The pool service launches the block requesters (processes requesting blocks from particular peers). Each requester requests a block for a given height. It picks the next available peer from the pool and requets a block at a given height from it. The block pool forwards these requests via internal channels to the reactor who is the only one doing actual p2p communication with other peers.
|
||||
|
||||
### Communication channels
|
||||
|
||||
`BlockSyncCh`: a p2p channel for sending/receiving requests to/from peers.
|
||||
- Channel id: 0x40
|
||||
- Size of receive buffer: 1024 messages
|
||||
- Size of send queue: 1000 messages
|
||||
- Message size: maximum size of a block + size of proto block messages (response message prefix and key size) + size of the Extended commit.
|
||||
|
||||
Messages processed via the channel:
|
||||
| Message name | Message fields| Description |
|
||||
| --- | ---| ---|
|
||||
| `BlockRequest`| `height int64, peerID types.NodeID` | request block at height `height` from peer `peerID`|
|
||||
| `BlockResponse`| `block types.Block `| Send `block` to peer that requested it |
|
||||
| `NoBlockResponse` |`height int64`|Indicates that a peer does not have a block at `height`|
|
||||
|`StatusRequest`| `{} `| Sent to a peer to request its status|
|
||||
|`StatusResponse` | `height int64, base int64`|Send to a peer the lowest and heights height of blocks within its store (`store.Height()`, `store.Base()`)|
|
||||
|`HeaderRequest` | `height: int64`| Request a header from peer for verification|
|
||||
|`HeaderResponse` |`header: Header`| Return the header for the corresponding height|
|
||||
|
||||
### Reactor channels
|
||||
|
||||
This section describes, per reactor component, the open channels and information they process.
|
||||
|
||||
#### `BlockPool`
|
||||
|
||||
| Channel name | Channel msg type| Description |
|
||||
| --- | ---| ---|
|
||||
|`requestsCh` | `BlockRequest` |The number of requests is capped by a fixed parameter `maxPendingRequestsPerPeer` (initially set to 20)|
|
||||
|`errorsCh` |`peerError`| Channel buffer size is limited|
|
||||
|
||||
|
||||
The reactor sends a p2p block request once it receives a signal via this channel from the block pool. The block pool will first pick a peer (in round robin) and assign it this particular height. Once this is done, the reactor can request a block.
|
||||
|
||||
#### `bpRequester`
|
||||
|
||||
| Channel name | Channel msg type| Description |
|
||||
| --- | ---| ---|
|
||||
`gotBlock` | `struct{}`| capped at 1; Here we simply register a received block and keep waiting for the reactor to terminate or a redo request. **Note**. It is not clear why we need this. |
|
||||
|`redoCh`| `types.NodeID`| capped at 1 ; Signals the requester to redo a request for aparticular block after replacing the peer for this height|
|
||||
|
||||
When a block is received by a requester, the requester does a number of checks on the received block. Before marking the block as available, the requester verifies the following:
|
||||
- that we expected a block at the particular height.
|
||||
- the block came from the peer we assigned to it.
|
||||
- the block is not nil
|
||||
|
||||
In the code there is the following ToDo listed:
|
||||
` // TODO: ensure that blocks come in order for each peer.` This needs further specification.
|
||||
|
||||
If the checks pass, the `block` field of the requester is populated with the new block and is thus made available to the blocksync reactor.
|
||||
|
||||
#### `Reactor`
|
||||
|
||||
| Channel name | Channel msg type| Description |
|
||||
| --- | ---| ---|
|
||||
|`requestsCh`|`BlockRequest`|size `maxTotalRequesters`|
|
||||
|`errorsCh`| `peerError`| size `maxPeerErrBuffer`|
|
||||
|`didProcessCh`|`struct{}`| size `1`|
|
||||
|
||||
The channel is created within the pool routine of the reactor and is used to signal that the reactor should check the block pool for new blocks. A message is sent to the channel after a fixed timeout (`trySyncTicker`). As we need two blocks to verify one of them (this is more clearly defined in [verification](./verification.md), if we miss only one of them, we will not wait for the sync timer to time out, but rather try quickly again until we fetch both.
|
||||
|
||||
`switchToConsensusTicker`. In addition to the sync timeout, in the same routine, the reactor checks periodically whether the conditions to switch to consensus are fullfilled.
|
||||
@@ -1,89 +0,0 @@
|
||||
### Data structures
|
||||
|
||||
There are four core components of the blocksync reactor: the reactor itself, a block pool, requesters and peers.
|
||||
|
||||
The reactor verifies received blocks, executes them against the application and commits them into the blockstore of the node. It also sends out requests to peers asking for more blocks and contains the logic to switch from blocksync to consenus.
|
||||
It contains a pointer to the block pool.
|
||||
|
||||
```go
|
||||
type Reactor struct {
|
||||
service.BaseService
|
||||
logger log.Logger
|
||||
|
||||
// immutable
|
||||
initialState sm.State
|
||||
// store
|
||||
stateStore sm.Store
|
||||
|
||||
blockExec *sm.BlockExecutor
|
||||
store sm.BlockStore
|
||||
pool *BlockPool
|
||||
consReactor consensusReactor
|
||||
blockSync *atomicBool
|
||||
|
||||
chCreator p2p.ChannelCreator
|
||||
peerEvents p2p.PeerEventSubscriber
|
||||
|
||||
requestsCh <-chan BlockRequest
|
||||
errorsCh <-chan peerError
|
||||
|
||||
metrics *consensus.Metrics
|
||||
eventBus *eventbus.EventBus
|
||||
|
||||
syncStartTime time.Time
|
||||
|
||||
lastTrustedBlock *TrustedBlockData
|
||||
}
|
||||
```
|
||||
|
||||
`TrustedBlockData` contains the last synced and verified block along with the commit used to verify it.
|
||||
|
||||
```go
|
||||
type TrustedBlockData struct {
|
||||
block *types.Block
|
||||
commit *types.Commit
|
||||
}
|
||||
```
|
||||
The block pool stores the last executed block (`height`), keeps track of peers connected to a node, the current height for each peer, along with the number of pending requests for each peer and assigns requests to peers (by creating `requesters`).
|
||||
|
||||
```go
|
||||
type BlockPool {
|
||||
mtx Mutex
|
||||
requesters map[int64]*bpRequester
|
||||
height int64
|
||||
peers map[p2p.ID]*bpPeer
|
||||
maxPeerHeight int64
|
||||
numPending int32
|
||||
store BlockStore
|
||||
requestsChannel chan<- BlockRequest
|
||||
errorsChannel chan<- peerError
|
||||
}
|
||||
```
|
||||
|
||||
Each requester is used to track the assignement of a request for a `block` at position `height` to a peer whose id equals to `peerID`.
|
||||
|
||||
```go
|
||||
type bpRequester {
|
||||
mtx Mutex
|
||||
block *types.Block
|
||||
height int64
|
||||
peerID types.nodeID
|
||||
redoChannel chan type.nodeID //redo may send multi-time; peerId is used to identify repeat
|
||||
goBlockCh chan struct{}{}
|
||||
}
|
||||
```
|
||||
|
||||
Each `Peer` data structure stores for each peer its current `height` and number of pending requests sent to the peer (`numPending`), etc. When a block is processed, this number is decremented.
|
||||
|
||||
```go
|
||||
type bpPeer struct {
|
||||
id p2p.ID
|
||||
height int64
|
||||
base int64
|
||||
numPending int32
|
||||
timeout *time.Timer
|
||||
didTimeout bool
|
||||
pool *blockPool
|
||||
}
|
||||
```
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 152 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 265 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 43 KiB |
@@ -1,73 +0,0 @@
|
||||
---
|
||||
order: 1
|
||||
parent:
|
||||
title: blocksync
|
||||
order: 3
|
||||
---
|
||||
|
||||
# Blocksync
|
||||
|
||||
This section describes the blocksync service provided by Tendermint. The main functionality provided by it is enabling new or recovering nodes to sync up to the head of the chain quickly. Normally, in a proof-of-stake blockchain, blocks are decided when multiple nodes reach consensus on it, requiring many rounds of communication. Once a block is decided on, it is executed against the application and stored, together with the proof that at least 2/3 of the nodes have voted for it. This proof takes the shape of a `Commit` which stores the signatures of 2/3+ validators that approved the block.
|
||||
|
||||
It is sufficient for a syncing node to contact its peers, download old blocks in parallel, verify their commit signatures and execute them sequentially (in order) against the application. As the signatures that validated a block at height `H` are stored with the block at height `H + 1`, blocksync needs to download both blocks in order to verify the former.
|
||||
|
||||
We can think about the blocksync service having two modes of operation. One is as a *server*, providing syncing nodes with blocks and commits, the other as a *client* requesting blocks from peers. Each full node runs a blocksync reactor, which launches both the server and client routines.
|
||||
|
||||
Blocksync is implemented within a Reactor whose internal data structures keep track of peers connected to a node and keep a pool of blocks downloaded from them if the node is currently syncing. Blocks are applied one by one until a node is considered caught up (more details on the conditions can be found in the sections below).
|
||||
|
||||
Once caught up, the node switches to consensus and stops running blocksync as a client.
|
||||
|
||||
### Starting Blocksync
|
||||
|
||||
A node can switch to blocksync directly on start-up or after completing `state-sync`. [State sync](ToDo) is downloading only snapshots of application state along with commits. This is further speeding up the process of catching up to the latest height as a node does not download the actual blocks.
|
||||
|
||||
The blocksync reactor service is started at the same time as all the other services in Tendermint. But blocksync-inc is disabled (blockSync boolean flag is false) initially and thus the blockpool and the routine to process blocks from the pool are not launched until the reactor is actually activated.
|
||||
|
||||
The reactor is activated after state sync, where the pool and request processing routines are launched.
|
||||
|
||||
However, receiving messages via the p2p channel and sending status updates to other nodes is enabled regardless of whether the blocksync reactor is started. Essentially every node is running as a blocksync server as soon as it is started up.
|
||||
|
||||
**Note**. In the current version, if we start from state sync and blocksync is not launched before as a service, the internal channels used by the reactor will not be created. We need to be careful to launch the blocksync *service* before we call the function to switch from statesync to blocksync.
|
||||
|
||||
### Switching from blocksync to consensus
|
||||
|
||||
Ideally, the switch to consensus is done once the node considers itself caugh up or we has not advanced its height for more than a predefined amount of time (currently 60s).
|
||||
|
||||
We consider a node to be caught up if it is 1 height away from the maximum height reported by its peers. The reason we **do not catch up until the maximum height** (`pool.maxPeerHeight`) is that we cannot verify the block at `pool.maxPeerHeight` without the `lastCommit` of the block at `pool.maxPeerHeight + 1`.
|
||||
|
||||
This check is performed periodically by calling `isCaughtUp` inside `poolRoutine`.
|
||||
|
||||
When the node is starting from genesis, the first block does not need the vote extensions and is able to switch directly to consensus.
|
||||
|
||||
|
||||
**Note** It is currently assumed that the Consensus reactor is already running. It is therefore not turned on by the Blocksync reactor. In case it has not been started, the Blocksync reactor simply returns.
|
||||
|
||||
#### **Vote extensions in v.036**
|
||||
In v0.36, Tendermint introduced vote extensions, which is application defined data, attached to the votes deciding on a block. In order to actively participate in consensus, a node has to have vote extensions from the previous height. Therefore, in addition to storing and downloading commits, nodes download and send vote extensions to their peers as well.
|
||||
|
||||
For this reason, if the node is not starting from genesis but only after state sync, blocksync **does not** switch to consensus until it syncs at least one block. As state sync only stores a state snapshot (without vote extensions), we need to receive them from one of our peers.
|
||||
|
||||
### Switching to blocksync from consensus
|
||||
|
||||
Ideally, a node should be able to switch to blocksync even if it does not crash, but falls behind in consensus. Unfortunately, switching back to blocksync from consensus is not possible at the moment. It is expected to be handled in [Issue #129](https://github.com/tendermint/tendermint/issues/129).
|
||||
|
||||
## Architecture and algorithm
|
||||
|
||||
The Blocksync reactor is organised as a set of concurrent tasks:
|
||||
|
||||
- Receive routine of Blocksync Reactor
|
||||
- Task for creating Requesters
|
||||
- Set of Requester tasks
|
||||
- A controller task.
|
||||
|
||||
|
||||

|
||||
|
||||
This section describes the Blocksync reactor and its internals including:
|
||||
- [Data structures used](./data_structures.md)
|
||||
- [Peer to peer communication pattern](./communication.md)
|
||||
- [Block Verification](./verification.md)
|
||||
|
||||
More details on how to use the Blocksync reactor and configure it when running Tendermint can be found [here](./../../docs/tendermint-core/block-sync/README.md).
|
||||
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
## Block verification
|
||||
|
||||
When blocksyncing, a node is not participating in consensus. It is receiving blocks that have already been decided and committed. To avoid being fooled by malicious peers, the node has to verify the received blocks before executing the transactions and storing the block in its store.
|
||||
|
||||
|
||||
The verification in blocksync aims to apply the same logic as the [light client verification](../light-client/verification/README.md). The safety guarantees of the light client model are provided by verifying blocks starting from an **initial trusted state** and using validators who were bonded within a **trusting period**. Once this period expires, the validators could have unbonded, and we cannot rely on them being honest anymore. In blocksync, we can be **outside** the trusting period. Therefore, the guarantees provided by blocksync verification can be divided in two groups:
|
||||
|
||||
- The block is within the trusting period, we can punish validators, and provide guarantees that if a block is signed by trusted validators, they are indeed correct.
|
||||
- The block is outside the trusting period and we cannot guarantee that the validators are still honest. We can however make a potential attack by those validators more complicated by additionally verifying the block using other peers as *witnesses*.
|
||||
|
||||
A state becomes trusted once it passes the validation. The section below discusses this state in more details.
|
||||
|
||||
### Trusted state
|
||||
|
||||
Currently there are two possible ways to obtain a trusted state :
|
||||
1. If the node is blocksync-ing from genesis, we have to verify the very first block and mark it as the initial trusted state.
|
||||
|
||||
2. The node loads the last block stored in its local block store.
|
||||
|
||||
**Case 1 - blocksync from genesis**
|
||||
|
||||
In this case we only verify that the validator hash of the block matches the validator set resulting from executing `InitChain`. We will use the initially provided validator set for verification and further verify the block against witnesses. In this scenario, this state is very likely to be outside the trusting period. We will accept this block as a trusted state and store it inside the node's block store.
|
||||
|
||||
It is worth noting that, running block sync from the first height is significantly slower than running statesync first. However, statesync does not keep the entire blockchain history and some operators might opt not to state sync. The reason is that, if sufficiently many nodes state sync and other nodes who have historical data fail or leave the network, the network ends up with gaps in the block history.
|
||||
|
||||
**Case 2 - blocksync starting from pre-existing state**
|
||||
|
||||
If the node is not starting from gensis, there will be pre-existing state in its local block store.
|
||||
|
||||
This can happen in the following two cases:
|
||||
|
||||
- The node is blocksyncing after completing state sync.
|
||||
|
||||
- The node has crashed and is now recovering.
|
||||
|
||||
In both of these cases, we can trust the state in the store. State sync runs the light client verification protocol. If the crash happened during consensus or sync-ing, the only blocks in the store are blocks that are verified or decided.
|
||||
|
||||
If we can trust this initial state at height H, we can use this fact to eliminate faulty blocks whose `lastCommit` at height H+1 does not verify the trusted block. Additionally, we can double check the validator sets that signed the block at H + 1 against the expected validator sets for this height. The expected validator sets are stored within the trusted state and returned to us upon applying the block and executing it against the application.
|
||||
|
||||
**Switching from statesync to blocksync**
|
||||
|
||||
If we switch from statesync to blocksync, the last item in the block store is not the entire block. The store holds only the verified header and the commit that verified it. Therefore, we do need to fetch the corresponding block from our peers, verify it against data stored in the store and set the `last trusted block` to this block.
|
||||
|
||||
To enable this, upon switching from statesync, we do not instruct the block pool to fetch blocks at height `H + 1` (where height `H` corresponds to the height of the header last verified with statesync). Instead we let it fetch the block at height H, but do not execute this block against the application (as this would fail - state is already updated to height H + 1. )
|
||||
|
||||
### Verifying blocks past the trusted state
|
||||
|
||||
<center>
|
||||
<img src="img/bc-reactor-blocks.png" alt="block diagram" title="Block overview" width="800px" name="Block_overview" align="center" />
|
||||
</center>
|
||||
<br/>
|
||||
|
||||
|
||||
|
||||
The diagram above shows all blocks at play when verifying block at height ` H + 1` - `newBlock`, where at height `H` we have the trusted block.
|
||||
|
||||
At a high level, the verification algorithm is represented by the code below.
|
||||
|
||||
~~~golang
|
||||
if !LastCommit.verify(trustedBlock) || NewBlock.validatorSet != trustedState.validatorSet {
|
||||
// NewBlock is invalid, request new
|
||||
request_newBlock_from_other_peer(height: h + 1)
|
||||
} else {
|
||||
if LastCommit2.validatorSetHash == trustedState.validatorSetHash &&
|
||||
verifyBlock.validatorSetHash == trustedState.nextValidatorSetHash {
|
||||
if LastCommit2.verify(NewBlock) {
|
||||
if verify_block_with_peers(NewBlock) {
|
||||
ApplyBlock(NewBlock)
|
||||
}
|
||||
}
|
||||
} else
|
||||
find_new_verifyBlock(height: h + 2)
|
||||
}
|
||||
|
||||
~~~
|
||||
|
||||
We try to increase the trust in the NewBlock by first confirming that its `lastCommit` field indeed verifies the trusted block. We then confirm that the validator hash matches the expected hash. But to fully verify the block we need to check it against the signatures that signed it. They are stored within the `lastCommit2` of `verifyBlock` at height `H+2`. As we have no guarantees on this block, we again increase our trust in it by confirming that the validators whose signatures are in `lastCommit2` are a match for the expected validator set.
|
||||
|
||||
#### *Witness verification*
|
||||
|
||||
**Note**. This is not implemented yet.
|
||||
|
||||
If all these checks pass, the reactor verifies that other peers have the same block at the particular height.
|
||||
|
||||
In order to reduce traffic, we do not ask the peers to provide the whole block to us, rather only the header. If crosschecking the headers fails then the node requests the remainder of the block to decide on whether this peer is faulty or not.
|
||||
|
||||
If the block received does not verify using the same verification logic, the peer is faulty. In case the verification passes, but headers mismatch, the node will not know who to trust.
|
||||
|
||||
**Verification failed**
|
||||
|
||||
In the light client verification model, the client requests from both peers a trace of blocks in order to determine the block where the divergence happened. As the blocks being verified are within the trusted period, the light client can generate evidence and send it to full nodes to vote for it and determine which peer is faulty.
|
||||
|
||||
But in the block sync reactor, if verification fails, there are multiple possible scenarios:
|
||||
|
||||
- Abort blocksync and switch to consensus.
|
||||
- Request data from more peers.
|
||||
|
||||
Both cases can be a potential attack on the joining node. We discuss these different attack possibilities and design shortcomings in the section below.
|
||||
|
||||
|
||||
|
||||
## Problems with current verification and reactor design
|
||||
|
||||
|
||||
The crux of many concerns with regards to the correctness and safety of the block sync reactor lies in the fact a node connects to peers.
|
||||
|
||||
Currently, the only constraint a node has is that it has to connect to **a** peer. An improved design should require a node to connect to at least two peers.
|
||||
|
||||
We have no guarantees on the correctness of the peers we are connected to.
|
||||
- Once we replace a peer due to it having timed out or not reporting a correct block, nothing prevents a node from reconnecting to it - *there is no notion of blacklisted peers*.
|
||||
- If we connect to a subset of peers, they could feed the node faulty data. Eventually, when the node switches to consensus, it would realize there is something wrong, but then the node itself might be blacklisted.
|
||||
- Alternatively, a node that is fed faulty data, could, upon switching to conensus, become part of a light client attack by serving as a faulty witness.
|
||||
- There is no check whether the maximum height reported by peers is true or not. A slow node could report a very distant height to the node - for example 2000, when the blockchain is at height 1000 in fact. This would lead to one part of the condition to switch to consensus never being true. To prevent the node switching due to not advancing, the malicious node sends a new block very slowly. Thus the node progresses but can never participate in consensus. This issue could potentially be mitigated if, instead of taking the maximum height reported by peers, we report the lowest of their maximums. The idea is that peers should be close enough to the top of the chain in any case. But this could also open the node to an attack where they switch to consensus too early. The median would be a good compromise between the two.
|
||||
- A blocksyncing node can flood peers with requests - constantly reporting that it has not synced up. At the moment, the maximum amount of requests received is limited and protects peers to some extent against this attack.
|
||||
|
||||
## Additional ideas and suggestions
|
||||
|
||||
**Performance improvement** Instead of downloading blocks before we verify them, download only headers. Once we verify those, we can download the whole block. As the headers do not contain signatures so we would need to add commits form blocks at subsequent heights as well.
|
||||
|
||||
**Different proposal** Instead of witness verification, verify against a light client and use backwards verification when applicable.
|
||||
@@ -115,7 +115,7 @@ func generateTestnet(r *rand.Rand, opt map[string]interface{}) (e2e.Manifest, er
|
||||
Nodes: map[string]*e2e.ManifestNode{},
|
||||
KeyType: keyType.Choose(r).(string),
|
||||
Evidence: evidence.Choose(r).(int),
|
||||
QueueType: "priority",
|
||||
QueueType: "simple-priority",
|
||||
TxSize: opt["txSize"].(int),
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
evidence = 5
|
||||
initial_height = 1000
|
||||
initial_state = {initial01 = "a", initial02 = "b", initial03 = "c"}
|
||||
queue_type = "priority"
|
||||
queue_type = "simple-priority"
|
||||
abci_protocol = "builtin"
|
||||
|
||||
[validators]
|
||||
|
||||
@@ -355,7 +355,7 @@ func (n Node) Validate(testnet Testnet) error {
|
||||
return fmt.Errorf("invalid mempool version %q", n.Mempool)
|
||||
}
|
||||
switch n.QueueType {
|
||||
case "", "priority", "fifo":
|
||||
case "", "priority", "fifo", "simple-priority":
|
||||
default:
|
||||
return fmt.Errorf("unsupported p2p queue type: %s", n.QueueType)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user