From 754c03aa8e9a1ad8216d587dcb7733635cd53c06 Mon Sep 17 00:00:00 2001 From: Uday Patil Date: Fri, 19 Apr 2024 12:13:13 -0500 Subject: [PATCH] Merge seiv2 into main (#232) * Make ReadMaxTxs atomic (#166) * Support pending transaction in mempool (#169) * fix unconfirmed tx to consider pending txs (#172) * fix pending pop (#173) * add TTL for pending txs (#174) * [EVM] Fix evm pending nonce (#179) * Perf: Increase buffer size for pubsub server to boost performance (#167) * Increase buffer size for pubsub server * Add more timeout for test failure * Add more timeout * Fix test split scripts * Fix test split * Fix unit test * Unit test * Unit test * [P2P] Optimize block pool requester retry and peer pick up logic (#170) * P2P Improvements: Fix block sync reactor and block pool retry logic * Revert "Add event data to result event (#165)" (#176) This reverts commit 72bb29caf821ed03900bcd74f4ddb439df85f5f1. * Fix block sync auto restart not working as expected (#175) * Fix edge case for blocksync (#178) * fix evm pending nonce * fix test * deflake a test * de-flake test * Revert "merge main" This reverts commit 58b94248ac561583b918775c0b0e220d4225fbc0, reversing changes made to 02d14782edc6c58d59db2658a4190dbdc5c7280f. * consider keep-in-cache logic when removing from cache * undo test tweaks --------- Co-authored-by: Yiming Zang <50607998+yzang2019@users.noreply.github.com> Co-authored-by: Jeremy Wei * Fix bug when popping pending TXs (#188) * Add mempool metrics for number of pending tx and expired txs (#189) * Add metrics for mempool pending transaction size * Add expired tx count metrics * [EVM] Allow multiple txs from same account in a block (#190) * add mempool prioritization with evm nonce * fix priority stability * index fixes * replace with binary search insert * impl binary search * fix removeTx to push next queued evm tx (#191) * fix expire metric (#193) * [EVM] Fix duplicate evm txs from priority queue (#195) * debug duplicate evm tx * add more logs * add some \ns * more logs * fix swap check * add-lockable-reap-by-gas * add invariant checks * fix invariant parenthesis * fix log * remove invalid invariant * fix nonce ordering pain * handle ordering of insert * fix remove * cleanup * fix imports * cleanup * avoid getTransactionByHash(hash) panic due to index * use Key() to compare instead of pointer * [EVM] prevent duplicate txs from getting inserted (#196) * prevent duplicates in mempool * use timestamp in priority queue * [EVM] Add logging for expiration (#198) * add logging for expired txs * cleanup * [EVM] Avoid returning nil transactions on ForEach (#197) * remove heapIndex to avoid nil scenario * avoid returning nil in loop (mimic Peek) * call callback from mempool (#200) * separate limit for pending tx (#202) * Add EVM txs eviction logic (#204) * Fix debug log (#205) * EVM transaction replacement (#206) (#208) * Add heapIndex with safety check (#213) * add heapIndex with safety check * cleanup * comment out for perf test * add back perf improvement * fix nil test * Use write-lock in (*TxPriorityQueue).ReapMax funcs (#209) ReapMaxBytesMaxGas and ReapMaxTxs funcs in TxPriorityQueue claim > Transactions returned are not removed from the mempool transaction > store or indexes. However, they use a priority queue to accomplish the claim > Transaction are retrieved in priority order. This is accomplished by popping all items out of the whole heap, and then pushing then back in sequentially. A copy of the heap cannot be obtained otherwise. Both of the mentioned functions use a read-lock (RLock) when doing this. This results in a potential scenario where multiple executions of the ReapMax can be started in parallel, and both would be popping items out of the priority queue. In practice, this can be abused by executing the `unconfirmed_txs` RPC call repeatedly. Based on our observations, running it multiple times per millisecond results in multiple threads picking it up at the same time. Such a scenario can be obtained via the WebSocket interface, and spamming `unconfirmed_txs` calls there. The behavior that happens is a `Panic in WSJSONRPC handler` when a queue item unexpectedly disappears for `mempool.(*TxPriorityQueue).Swap`. (`runtime error: index out of range [0] with length 0`) This can additionally lead to a `CONSENSUS FAILURE!!!` if the race condition occurs for `internal/consensus.(*State).finalizeCommit` when it tries to do `mempool.(*TxPriorityQueue).RemoveTx`, but the ReapMax has already removed all elements from the underlying heap. (`runtime error: index out of range [-1]`) This commit switches the lock type to a write-lock (Lock) to ensure no parallel modifications take place. This commit additionally updates the tests to allow parallel execution of the func calls in testing, as to prevent regressions (in case someone wants to downgrade the locks without considering the implications from the underlying heap usage). --------- Co-authored-by: Valters Jansons * Pending Txs Update Condition (#214) * Add metrics for mempool size changes (#220) * [EVM] Adjust locking for replacement (#224) * Remove tx from cache when canAddPendingTx fails (#230) * add tx hash to evm info proto (#231) --------- Co-authored-by: codchen Co-authored-by: Steven Landers Co-authored-by: Yiming Zang <50607998+yzang2019@users.noreply.github.com> Co-authored-by: Jeremy Wei Co-authored-by: Valters Jansons Co-authored-by: Kartik Bhat --- abci/client/grpc_client.go | 5 +- abci/client/mocks/client.go | 8 +- abci/client/socket_client.go | 4 +- abci/example/kvstore/kvstore.go | 4 +- abci/server/grpc_server.go | 8 + abci/types/application.go | 6 +- abci/types/messages.go | 4 +- abci/types/mocks/application.go | 8 +- abci/types/types.go | 25 + abci/types/types.pb.go | 942 +++++++++++++++++------- config/config.go | 14 + config/toml.go | 8 + internal/consensus/mempool_test.go | 13 +- internal/consensus/reactor.go | 11 +- internal/consensus/reactor_test.go | 2 + internal/mempool/mempool.go | 319 +++++--- internal/mempool/mempool_test.go | 246 ++++++- internal/mempool/metrics.gen.go | 28 + internal/mempool/metrics.go | 15 + internal/mempool/priority_queue.go | 395 +++++++++- internal/mempool/priority_queue_test.go | 283 ++++++- internal/mempool/tx.go | 164 ++++- internal/mempool/tx_test.go | 133 ++++ internal/proxy/client.go | 2 +- internal/rpc/core/mempool.go | 5 +- internal/state/helpers_test.go | 4 +- node/node.go | 1 + proto/tendermint/abci/types.proto | 8 + proto/tendermint/crypto/keys.pb.go | 1 - proto/tendermint/mempool/types.pb.go | 1 - proto/tendermint/p2p/conn.pb.go | 1 - proto/tendermint/types/types.pb.go | 1 - rpc/client/mock/abci.go | 2 +- test/e2e/app/app.go | 10 +- types/mempool.go | 20 + types/tx.go | 2 +- 36 files changed, 2263 insertions(+), 440 deletions(-) diff --git a/abci/client/grpc_client.go b/abci/client/grpc_client.go index f1be2c120..8eec13ef8 100644 --- a/abci/client/grpc_client.go +++ b/abci/client/grpc_client.go @@ -130,8 +130,9 @@ func (cli *grpcClient) Info(ctx context.Context, params *types.RequestInfo) (*ty return cli.client.Info(ctx, types.ToRequestInfo(params).GetInfo(), grpc.WaitForReady(true)) } -func (cli *grpcClient) CheckTx(ctx context.Context, params *types.RequestCheckTx) (*types.ResponseCheckTx, error) { - return cli.client.CheckTx(ctx, types.ToRequestCheckTx(params).GetCheckTx(), grpc.WaitForReady(true)) +func (cli *grpcClient) CheckTx(ctx context.Context, params *types.RequestCheckTx) (*types.ResponseCheckTxV2, error) { + resCheckTx, err := cli.client.CheckTx(ctx, types.ToRequestCheckTx(params).GetCheckTx(), grpc.WaitForReady(true)) + return &types.ResponseCheckTxV2{ResponseCheckTx: resCheckTx}, err } func (cli *grpcClient) Query(ctx context.Context, params *types.RequestQuery) (*types.ResponseQuery, error) { diff --git a/abci/client/mocks/client.go b/abci/client/mocks/client.go index 44a5eb00f..a0626cff0 100644 --- a/abci/client/mocks/client.go +++ b/abci/client/mocks/client.go @@ -38,15 +38,15 @@ func (_m *Client) ApplySnapshotChunk(_a0 context.Context, _a1 *types.RequestAppl } // CheckTx provides a mock function with given fields: _a0, _a1 -func (_m *Client) CheckTx(_a0 context.Context, _a1 *types.RequestCheckTx) (*types.ResponseCheckTx, error) { +func (_m *Client) CheckTx(_a0 context.Context, _a1 *types.RequestCheckTx) (*types.ResponseCheckTxV2, error) { ret := _m.Called(_a0, _a1) - var r0 *types.ResponseCheckTx - if rf, ok := ret.Get(0).(func(context.Context, *types.RequestCheckTx) *types.ResponseCheckTx); ok { + var r0 *types.ResponseCheckTxV2 + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestCheckTx) *types.ResponseCheckTxV2); ok { r0 = rf(_a0, _a1) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(*types.ResponseCheckTx) + r0 = ret.Get(0).(*types.ResponseCheckTxV2) } } diff --git a/abci/client/socket_client.go b/abci/client/socket_client.go index 80f106333..35f244666 100644 --- a/abci/client/socket_client.go +++ b/abci/client/socket_client.go @@ -257,12 +257,12 @@ func (cli *socketClient) Info(ctx context.Context, req *types.RequestInfo) (*typ return res.GetInfo(), nil } -func (cli *socketClient) CheckTx(ctx context.Context, req *types.RequestCheckTx) (*types.ResponseCheckTx, error) { +func (cli *socketClient) CheckTx(ctx context.Context, req *types.RequestCheckTx) (*types.ResponseCheckTxV2, error) { res, err := cli.doRequest(ctx, types.ToRequestCheckTx(req)) if err != nil { return nil, err } - return res.GetCheckTx(), nil + return &types.ResponseCheckTxV2{ResponseCheckTx: res.GetCheckTx()}, nil } func (cli *socketClient) Query(ctx context.Context, req *types.RequestQuery) (*types.ResponseQuery, error) { diff --git a/abci/example/kvstore/kvstore.go b/abci/example/kvstore/kvstore.go index f33ff2be1..ac0d122b1 100644 --- a/abci/example/kvstore/kvstore.go +++ b/abci/example/kvstore/kvstore.go @@ -205,8 +205,8 @@ func (app *Application) FinalizeBlock(_ context.Context, req *types.RequestFinal return &types.ResponseFinalizeBlock{TxResults: respTxs, ValidatorUpdates: app.ValUpdates, AppHash: appHash}, nil } -func (*Application) CheckTx(_ context.Context, req *types.RequestCheckTx) (*types.ResponseCheckTx, error) { - return &types.ResponseCheckTx{Code: code.CodeTypeOK, GasWanted: 1}, nil +func (*Application) CheckTx(_ context.Context, req *types.RequestCheckTx) (*types.ResponseCheckTxV2, error) { + return &types.ResponseCheckTxV2{ResponseCheckTx: &types.ResponseCheckTx{Code: code.CodeTypeOK, GasWanted: 1}}, nil } func (app *Application) Commit(_ context.Context) (*types.ResponseCommit, error) { diff --git a/abci/server/grpc_server.go b/abci/server/grpc_server.go index 0dfee8169..6f7caf39c 100644 --- a/abci/server/grpc_server.go +++ b/abci/server/grpc_server.go @@ -81,3 +81,11 @@ func (app *gRPCApplication) Flush(_ context.Context, req *types.RequestFlush) (* func (app *gRPCApplication) Commit(ctx context.Context, req *types.RequestCommit) (*types.ResponseCommit, error) { return app.Application.Commit(ctx) } + +func (app *gRPCApplication) CheckTx(ctx context.Context, req *types.RequestCheckTx) (*types.ResponseCheckTx, error) { + resV2, err := app.Application.CheckTx(ctx, req) + if err != nil { + return &types.ResponseCheckTx{}, err + } + return resV2.ResponseCheckTx, nil +} diff --git a/abci/types/application.go b/abci/types/application.go index 9915d16a1..5f98bdc9f 100644 --- a/abci/types/application.go +++ b/abci/types/application.go @@ -12,7 +12,7 @@ type Application interface { Query(context.Context, *RequestQuery) (*ResponseQuery, error) // Query for state // Mempool Connection - CheckTx(context.Context, *RequestCheckTx) (*ResponseCheckTx, error) // Validate a tx for the mempool + CheckTx(context.Context, *RequestCheckTx) (*ResponseCheckTxV2, error) // Validate a tx for the mempool // Consensus Connection InitChain(context.Context, *RequestInitChain) (*ResponseInitChain, error) // Initialize blockchain w validators/other info from TendermintCore @@ -51,8 +51,8 @@ func (BaseApplication) Info(_ context.Context, req *RequestInfo) (*ResponseInfo, return &ResponseInfo{}, nil } -func (BaseApplication) CheckTx(_ context.Context, req *RequestCheckTx) (*ResponseCheckTx, error) { - return &ResponseCheckTx{Code: CodeTypeOK}, nil +func (BaseApplication) CheckTx(_ context.Context, req *RequestCheckTx) (*ResponseCheckTxV2, error) { + return &ResponseCheckTxV2{ResponseCheckTx: &ResponseCheckTx{Code: CodeTypeOK}}, nil } func (BaseApplication) Commit(_ context.Context) (*ResponseCommit, error) { diff --git a/abci/types/messages.go b/abci/types/messages.go index 2ab6ac1df..f6a6c12d1 100644 --- a/abci/types/messages.go +++ b/abci/types/messages.go @@ -155,9 +155,9 @@ func ToResponseInfo(res *ResponseInfo) *Response { } } -func ToResponseCheckTx(res *ResponseCheckTx) *Response { +func ToResponseCheckTx(res *ResponseCheckTxV2) *Response { return &Response{ - Value: &Response_CheckTx{res}, + Value: &Response_CheckTx{res.ResponseCheckTx}, } } diff --git a/abci/types/mocks/application.go b/abci/types/mocks/application.go index c6eb29219..08ef9ca5c 100644 --- a/abci/types/mocks/application.go +++ b/abci/types/mocks/application.go @@ -38,15 +38,15 @@ func (_m *Application) ApplySnapshotChunk(_a0 context.Context, _a1 *types.Reques } // CheckTx provides a mock function with given fields: _a0, _a1 -func (_m *Application) CheckTx(_a0 context.Context, _a1 *types.RequestCheckTx) (*types.ResponseCheckTx, error) { +func (_m *Application) CheckTx(_a0 context.Context, _a1 *types.RequestCheckTx) (*types.ResponseCheckTxV2, error) { ret := _m.Called(_a0, _a1) - var r0 *types.ResponseCheckTx - if rf, ok := ret.Get(0).(func(context.Context, *types.RequestCheckTx) *types.ResponseCheckTx); ok { + var r0 *types.ResponseCheckTxV2 + if rf, ok := ret.Get(0).(func(context.Context, *types.RequestCheckTx) *types.ResponseCheckTxV2); ok { r0 = rf(_a0, _a1) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(*types.ResponseCheckTx) + r0 = ret.Get(0).(*types.ResponseCheckTxV2) } } diff --git a/abci/types/types.go b/abci/types/types.go index 121e72159..1ecb0a9f3 100644 --- a/abci/types/types.go +++ b/abci/types/types.go @@ -237,3 +237,28 @@ func MarshalTxResults(r []*ExecTxResult) ([][]byte, error) { } return s, nil } + +type PendingTxCheckerResponse int + +const ( + Accepted PendingTxCheckerResponse = iota + Rejected + Pending +) + +type PendingTxChecker func() PendingTxCheckerResponse +type ExpireTxHandler func() + +// ResponseCheckTxV2 response type contains non-protobuf fields, so non-local ABCI clients will not be able +// to utilize the new fields in V2 type (but still be backwards-compatible) +type ResponseCheckTxV2 struct { + *ResponseCheckTx + IsPendingTransaction bool + Checker PendingTxChecker // must not be nil if IsPendingTransaction is true + ExpireTxHandler ExpireTxHandler + + // helper properties for prioritization in mempool + EVMNonce uint64 + EVMSenderAddress string + IsEVM bool +} diff --git a/abci/types/types.pb.go b/abci/types/types.pb.go index e28ce2827..74b4620e1 100644 --- a/abci/types/types.pb.go +++ b/abci/types/types.pb.go @@ -217,6 +217,7 @@ func (ResponseVerifyVoteExtension_VerifyStatus) EnumDescriptor() ([]byte, []int) } // TxAction contains App-provided information on what to do with a transaction that is part of a raw proposal +// Deprecate entire usage: https://github.com/tendermint/tendermint/pull/9283 type TxRecord_TxAction int32 const ( @@ -2877,14 +2878,15 @@ func (m *ResponseCheckTx) GetPriority() int64 { } type ResponseDeliverTx struct { - Code uint32 `protobuf:"varint,1,opt,name=code,proto3" json:"code,omitempty"` - Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` - Log string `protobuf:"bytes,3,opt,name=log,proto3" json:"log,omitempty"` - Info string `protobuf:"bytes,4,opt,name=info,proto3" json:"info,omitempty"` - GasWanted int64 `protobuf:"varint,5,opt,name=gas_wanted,proto3" json:"gas_wanted,omitempty"` - GasUsed int64 `protobuf:"varint,6,opt,name=gas_used,proto3" json:"gas_used,omitempty"` - Events []Event `protobuf:"bytes,7,rep,name=events,proto3" json:"events,omitempty"` - Codespace string `protobuf:"bytes,8,opt,name=codespace,proto3" json:"codespace,omitempty"` + Code uint32 `protobuf:"varint,1,opt,name=code,proto3" json:"code,omitempty"` + Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` + Log string `protobuf:"bytes,3,opt,name=log,proto3" json:"log,omitempty"` + Info string `protobuf:"bytes,4,opt,name=info,proto3" json:"info,omitempty"` + GasWanted int64 `protobuf:"varint,5,opt,name=gas_wanted,proto3" json:"gas_wanted,omitempty"` + GasUsed int64 `protobuf:"varint,6,opt,name=gas_used,proto3" json:"gas_used,omitempty"` + Events []Event `protobuf:"bytes,7,rep,name=events,proto3" json:"events,omitempty"` + Codespace string `protobuf:"bytes,8,opt,name=codespace,proto3" json:"codespace,omitempty"` + EvmTxInfo *EvmTxInfo `protobuf:"bytes,9,opt,name=evm_tx_info,json=evmTxInfo,proto3" json:"evm_tx_info,omitempty"` } func (m *ResponseDeliverTx) Reset() { *m = ResponseDeliverTx{} } @@ -2976,6 +2978,13 @@ func (m *ResponseDeliverTx) GetCodespace() string { return "" } +func (m *ResponseDeliverTx) GetEvmTxInfo() *EvmTxInfo { + if m != nil { + return m.EvmTxInfo + } + return nil +} + type ResponseEndBlock struct { ValidatorUpdates []ValidatorUpdate `protobuf:"bytes,1,rep,name=validator_updates,json=validatorUpdates,proto3" json:"validator_updates"` ConsensusParamUpdates *ConsensusParams `protobuf:"bytes,2,opt,name=consensus_param_updates,json=consensusParamUpdates,proto3" json:"consensus_param_updates,omitempty"` @@ -3907,14 +3916,15 @@ func (m *EventAttribute) GetIndex() bool { // // * Its structure is equivalent to #ResponseDeliverTx which will be deprecated/deleted type ExecTxResult struct { - Code uint32 `protobuf:"varint,1,opt,name=code,proto3" json:"code,omitempty"` - Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` - Log string `protobuf:"bytes,3,opt,name=log,proto3" json:"log,omitempty"` - Info string `protobuf:"bytes,4,opt,name=info,proto3" json:"info,omitempty"` - GasWanted int64 `protobuf:"varint,5,opt,name=gas_wanted,json=gasWanted,proto3" json:"gas_wanted,omitempty"` - GasUsed int64 `protobuf:"varint,6,opt,name=gas_used,json=gasUsed,proto3" json:"gas_used,omitempty"` - Events []Event `protobuf:"bytes,7,rep,name=events,proto3" json:"events,omitempty"` - Codespace string `protobuf:"bytes,8,opt,name=codespace,proto3" json:"codespace,omitempty"` + Code uint32 `protobuf:"varint,1,opt,name=code,proto3" json:"code,omitempty"` + Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` + Log string `protobuf:"bytes,3,opt,name=log,proto3" json:"log,omitempty"` + Info string `protobuf:"bytes,4,opt,name=info,proto3" json:"info,omitempty"` + GasWanted int64 `protobuf:"varint,5,opt,name=gas_wanted,json=gasWanted,proto3" json:"gas_wanted,omitempty"` + GasUsed int64 `protobuf:"varint,6,opt,name=gas_used,json=gasUsed,proto3" json:"gas_used,omitempty"` + Events []Event `protobuf:"bytes,7,rep,name=events,proto3" json:"events,omitempty"` + Codespace string `protobuf:"bytes,8,opt,name=codespace,proto3" json:"codespace,omitempty"` + EvmTxInfo *EvmTxInfo `protobuf:"bytes,9,opt,name=evm_tx_info,json=evmTxInfo,proto3" json:"evm_tx_info,omitempty"` } func (m *ExecTxResult) Reset() { *m = ExecTxResult{} } @@ -4006,6 +4016,13 @@ func (m *ExecTxResult) GetCodespace() string { return "" } +func (m *ExecTxResult) GetEvmTxInfo() *EvmTxInfo { + if m != nil { + return m.EvmTxInfo + } + return nil +} + // TxResult contains results of executing the transaction. // // One usage is indexing transaction results. @@ -4639,6 +4656,66 @@ func (m *Evidence) GetTotalVotingPower() int64 { return 0 } +type EvmTxInfo struct { + SenderAddress string `protobuf:"bytes,1,opt,name=senderAddress,proto3" json:"senderAddress,omitempty"` + Nonce uint64 `protobuf:"varint,2,opt,name=nonce,proto3" json:"nonce,omitempty"` + TxHash string `protobuf:"bytes,3,opt,name=txHash,proto3" json:"txHash,omitempty"` +} + +func (m *EvmTxInfo) Reset() { *m = EvmTxInfo{} } +func (m *EvmTxInfo) String() string { return proto.CompactTextString(m) } +func (*EvmTxInfo) ProtoMessage() {} +func (*EvmTxInfo) Descriptor() ([]byte, []int) { + return fileDescriptor_252557cfdd89a31a, []int{59} +} +func (m *EvmTxInfo) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *EvmTxInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_EvmTxInfo.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *EvmTxInfo) XXX_Merge(src proto.Message) { + xxx_messageInfo_EvmTxInfo.Merge(m, src) +} +func (m *EvmTxInfo) XXX_Size() int { + return m.Size() +} +func (m *EvmTxInfo) XXX_DiscardUnknown() { + xxx_messageInfo_EvmTxInfo.DiscardUnknown(m) +} + +var xxx_messageInfo_EvmTxInfo proto.InternalMessageInfo + +func (m *EvmTxInfo) GetSenderAddress() string { + if m != nil { + return m.SenderAddress + } + return "" +} + +func (m *EvmTxInfo) GetNonce() uint64 { + if m != nil { + return m.Nonce + } + return 0 +} + +func (m *EvmTxInfo) GetTxHash() string { + if m != nil { + return m.TxHash + } + return "" +} + type Snapshot struct { Height uint64 `protobuf:"varint,1,opt,name=height,proto3" json:"height,omitempty"` Format uint32 `protobuf:"varint,2,opt,name=format,proto3" json:"format,omitempty"` @@ -4651,7 +4728,7 @@ func (m *Snapshot) Reset() { *m = Snapshot{} } func (m *Snapshot) String() string { return proto.CompactTextString(m) } func (*Snapshot) ProtoMessage() {} func (*Snapshot) Descriptor() ([]byte, []int) { - return fileDescriptor_252557cfdd89a31a, []int{59} + return fileDescriptor_252557cfdd89a31a, []int{60} } func (m *Snapshot) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -4782,256 +4859,262 @@ func init() { proto.RegisterType((*ExtendedVoteInfo)(nil), "tendermint.abci.ExtendedVoteInfo") proto.RegisterType((*Misbehavior)(nil), "tendermint.abci.Misbehavior") proto.RegisterType((*Evidence)(nil), "tendermint.abci.Evidence") + proto.RegisterType((*EvmTxInfo)(nil), "tendermint.abci.EvmTxInfo") proto.RegisterType((*Snapshot)(nil), "tendermint.abci.Snapshot") } func init() { proto.RegisterFile("tendermint/abci/types.proto", fileDescriptor_252557cfdd89a31a) } var fileDescriptor_252557cfdd89a31a = []byte{ - // 3885 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x5b, 0x4b, 0x73, 0x1b, 0xd9, - 0x75, 0x46, 0xe3, 0x8d, 0x83, 0x57, 0xf3, 0x92, 0xa2, 0x20, 0x68, 0x86, 0xe4, 0xf4, 0x94, 0x66, - 0x34, 0x1a, 0x99, 0x74, 0xa8, 0x48, 0x96, 0xa2, 0x71, 0x26, 0x24, 0x04, 0x0e, 0x28, 0x51, 0x24, - 0xa7, 0x09, 0x72, 0x32, 0x49, 0xac, 0x76, 0x13, 0xb8, 0x04, 0xda, 0x02, 0xd0, 0xed, 0xee, 0x06, - 0x07, 0x9c, 0x55, 0x2a, 0x89, 0x37, 0xce, 0x66, 0x96, 0x59, 0xc4, 0xbb, 0xf8, 0x0f, 0x64, 0x91, - 0x65, 0x56, 0xa9, 0x94, 0x17, 0x5e, 0x78, 0x91, 0x4a, 0x65, 0xe5, 0xa4, 0x66, 0xaa, 0xb2, 0xf0, - 0x1f, 0xc8, 0x2e, 0x4e, 0xdd, 0x47, 0xbf, 0x80, 0x6e, 0x3c, 0x24, 0x95, 0xab, 0x5c, 0x99, 0x5d, - 0xdf, 0xdb, 0xe7, 0x9c, 0xfb, 0x3a, 0xaf, 0xfb, 0x9d, 0x6e, 0xb8, 0x69, 0xe3, 0x41, 0x1b, 0x9b, - 0x7d, 0x6d, 0x60, 0x6f, 0xa9, 0xe7, 0x2d, 0x6d, 0xcb, 0xbe, 0x32, 0xb0, 0xb5, 0x69, 0x98, 0xba, - 0xad, 0xa3, 0xb2, 0xf7, 0x72, 0x93, 0xbc, 0xac, 0xbe, 0xed, 0xa3, 0x6e, 0x99, 0x57, 0x86, 0xad, - 0x6f, 0x19, 0xa6, 0xae, 0x5f, 0x30, 0xfa, 0xea, 0x5b, 0x93, 0xaf, 0x5f, 0xe2, 0x2b, 0x2e, 0x2d, - 0xc0, 0x4c, 0x47, 0xd9, 0x32, 0x54, 0x53, 0xed, 0x5b, 0x21, 0xcc, 0xec, 0xb5, 0x6f, 0x2a, 0xd5, - 0xf5, 0x8e, 0xae, 0x77, 0x7a, 0x78, 0x8b, 0xb6, 0xce, 0x87, 0x17, 0x5b, 0xb6, 0xd6, 0xc7, 0x96, - 0xad, 0xf6, 0x0d, 0x4e, 0xb0, 0xd2, 0xd1, 0x3b, 0x3a, 0x7d, 0xdc, 0x22, 0x4f, 0xac, 0x57, 0xfa, - 0x65, 0x1e, 0x32, 0x32, 0xfe, 0xf1, 0x10, 0x5b, 0x36, 0xda, 0x86, 0x24, 0x6e, 0x75, 0xf5, 0x8a, - 0xb0, 0x21, 0xdc, 0xce, 0x6f, 0xbf, 0xb5, 0x39, 0xb6, 0xb8, 0x4d, 0x4e, 0x57, 0x6f, 0x75, 0xf5, - 0x46, 0x4c, 0xa6, 0xb4, 0xe8, 0x3e, 0xa4, 0x2e, 0x7a, 0x43, 0xab, 0x5b, 0x89, 0x53, 0xa6, 0xb7, - 0xa3, 0x98, 0xf6, 0x08, 0x51, 0x23, 0x26, 0x33, 0x6a, 0x32, 0x94, 0x36, 0xb8, 0xd0, 0x2b, 0x89, - 0xe9, 0x43, 0xed, 0x0f, 0x2e, 0xe8, 0x50, 0x84, 0x16, 0xed, 0x02, 0x68, 0x03, 0xcd, 0x56, 0x5a, - 0x5d, 0x55, 0x1b, 0x54, 0x92, 0x94, 0xf3, 0x9d, 0x68, 0x4e, 0xcd, 0xae, 0x11, 0xc2, 0x46, 0x4c, - 0xce, 0x69, 0x4e, 0x83, 0x4c, 0xf7, 0xc7, 0x43, 0x6c, 0x5e, 0x55, 0x52, 0xd3, 0xa7, 0xfb, 0x29, - 0x21, 0x22, 0xd3, 0xa5, 0xd4, 0xe8, 0x23, 0xc8, 0xb6, 0xba, 0xb8, 0xf5, 0x52, 0xb1, 0x47, 0x95, - 0x0c, 0xe5, 0x5c, 0x8f, 0xe2, 0xac, 0x11, 0xba, 0xe6, 0xa8, 0x11, 0x93, 0x33, 0x2d, 0xf6, 0x88, - 0x1e, 0x42, 0xba, 0xa5, 0xf7, 0xfb, 0x9a, 0x5d, 0x01, 0xca, 0xbb, 0x16, 0xc9, 0x4b, 0xa9, 0x1a, - 0x31, 0x99, 0xd3, 0xa3, 0x43, 0x28, 0xf5, 0x34, 0xcb, 0x56, 0xac, 0x81, 0x6a, 0x58, 0x5d, 0xdd, - 0xb6, 0x2a, 0x79, 0x2a, 0xe1, 0x56, 0x94, 0x84, 0x03, 0xcd, 0xb2, 0x4f, 0x1c, 0xe2, 0x46, 0x4c, - 0x2e, 0xf6, 0xfc, 0x1d, 0x44, 0x9e, 0x7e, 0x71, 0x81, 0x4d, 0x57, 0x60, 0xa5, 0x30, 0x5d, 0xde, - 0x11, 0xa1, 0x76, 0xf8, 0x89, 0x3c, 0xdd, 0xdf, 0x81, 0xfe, 0x1c, 0x96, 0x7b, 0xba, 0xda, 0x76, - 0xc5, 0x29, 0xad, 0xee, 0x70, 0xf0, 0xb2, 0x52, 0xa4, 0x42, 0x3f, 0x88, 0x9c, 0xa4, 0xae, 0xb6, - 0x1d, 0x11, 0x35, 0xc2, 0xd0, 0x88, 0xc9, 0x4b, 0xbd, 0xf1, 0x4e, 0xf4, 0x02, 0x56, 0x54, 0xc3, - 0xe8, 0x5d, 0x8d, 0x4b, 0x2f, 0x51, 0xe9, 0x77, 0xa2, 0xa4, 0xef, 0x10, 0x9e, 0x71, 0xf1, 0x48, - 0x9d, 0xe8, 0x45, 0x4d, 0x10, 0x0d, 0x13, 0x1b, 0xaa, 0x89, 0x15, 0xc3, 0xd4, 0x0d, 0xdd, 0x52, - 0x7b, 0x95, 0x32, 0x95, 0xfd, 0x7e, 0x94, 0xec, 0x63, 0x46, 0x7f, 0xcc, 0xc9, 0x1b, 0x31, 0xb9, - 0x6c, 0x04, 0xbb, 0x98, 0x54, 0xbd, 0x85, 0x2d, 0xcb, 0x93, 0x2a, 0xce, 0x92, 0x4a, 0xe9, 0x83, - 0x52, 0x03, 0x5d, 0xa8, 0x0e, 0x79, 0x3c, 0x22, 0xec, 0xca, 0xa5, 0x6e, 0xe3, 0xca, 0x12, 0x15, - 0x28, 0x45, 0x5a, 0x28, 0x25, 0x3d, 0xd3, 0x6d, 0xdc, 0x88, 0xc9, 0x80, 0xdd, 0x16, 0x52, 0xe1, - 0xda, 0x25, 0x36, 0xb5, 0x8b, 0x2b, 0x2a, 0x46, 0xa1, 0x6f, 0x2c, 0x4d, 0x1f, 0x54, 0x10, 0x15, - 0xf8, 0x61, 0x94, 0xc0, 0x33, 0xca, 0x44, 0x44, 0xd4, 0x1d, 0x96, 0x46, 0x4c, 0x5e, 0xbe, 0x9c, - 0xec, 0x26, 0x2a, 0x76, 0xa1, 0x0d, 0xd4, 0x9e, 0xf6, 0x25, 0x56, 0xce, 0x7b, 0x7a, 0xeb, 0x65, - 0x65, 0x79, 0xba, 0x8a, 0xed, 0x71, 0xea, 0x5d, 0x42, 0x4c, 0x54, 0xec, 0xc2, 0xdf, 0x41, 0x56, - 0x7e, 0x8e, 0x3b, 0xda, 0x80, 0x0b, 0x5b, 0x99, 0xbe, 0xf2, 0x5d, 0x42, 0xea, 0x48, 0x82, 0x73, - 0xb7, 0x45, 0x9c, 0x47, 0x1b, 0xf7, 0xb4, 0x4b, 0x6c, 0x12, 0x1b, 0xbe, 0x36, 0xdd, 0x79, 0x3c, - 0x61, 0x94, 0xd4, 0x8a, 0x73, 0x6d, 0xa7, 0x81, 0x3e, 0x86, 0x1c, 0x39, 0x01, 0x36, 0x91, 0x55, - 0x2a, 0x62, 0x23, 0xf2, 0x08, 0x06, 0x6d, 0x67, 0x1a, 0x59, 0xcc, 0x9f, 0xc9, 0x5a, 0xa8, 0xb9, - 0xf4, 0x54, 0x1b, 0x5b, 0x76, 0xe5, 0xfa, 0xf4, 0xb5, 0x10, 0x33, 0x39, 0xa0, 0x94, 0x64, 0x2d, - 0x3d, 0xb7, 0xb5, 0x9b, 0x81, 0xd4, 0xa5, 0xda, 0x1b, 0xe2, 0xa7, 0xc9, 0x6c, 0x5a, 0xcc, 0x3c, - 0x4d, 0x66, 0xb3, 0x62, 0xee, 0x69, 0x32, 0x9b, 0x13, 0x41, 0x7a, 0x1f, 0xf2, 0x3e, 0x2f, 0x8d, - 0x2a, 0x90, 0xe9, 0x63, 0xcb, 0x52, 0x3b, 0x98, 0x3a, 0xf5, 0x9c, 0xec, 0x34, 0xa5, 0x12, 0x14, - 0xfc, 0x9e, 0x59, 0xfa, 0x4a, 0x70, 0x39, 0x89, 0xd3, 0x25, 0x9c, 0x97, 0xd8, 0xa4, 0xba, 0xc1, - 0x39, 0x79, 0x13, 0xbd, 0x0b, 0x45, 0xba, 0x03, 0x8a, 0xf3, 0x9e, 0x78, 0xfe, 0xa4, 0x5c, 0xa0, - 0x9d, 0x67, 0x9c, 0x68, 0x1d, 0xf2, 0xc6, 0xb6, 0xe1, 0x92, 0x24, 0x28, 0x09, 0x18, 0xdb, 0x86, - 0x43, 0xf0, 0x0e, 0x14, 0xc8, 0x5a, 0x5d, 0x8a, 0x24, 0x1d, 0x24, 0x4f, 0xfa, 0x38, 0x89, 0xf4, - 0xcb, 0x38, 0x88, 0xe3, 0xde, 0x1c, 0x3d, 0x84, 0x24, 0x09, 0x6c, 0x3c, 0x46, 0x55, 0x37, 0x59, - 0xd4, 0xdb, 0x74, 0xa2, 0xde, 0x66, 0xd3, 0x89, 0x7a, 0xbb, 0xd9, 0x5f, 0xfc, 0x7a, 0x3d, 0xf6, - 0xd5, 0x7f, 0xae, 0x0b, 0x32, 0xe5, 0x40, 0x37, 0x88, 0x0f, 0x57, 0xb5, 0x81, 0xa2, 0xb5, 0xe9, - 0x94, 0x73, 0xc4, 0x41, 0xab, 0xda, 0x60, 0xbf, 0x8d, 0x0e, 0x40, 0x6c, 0xe9, 0x03, 0x0b, 0x0f, - 0xac, 0xa1, 0xa5, 0xb0, 0x98, 0xcb, 0x23, 0x53, 0x40, 0x45, 0x58, 0xb8, 0xad, 0x39, 0x94, 0xc7, - 0x94, 0x50, 0x2e, 0xb7, 0x82, 0x1d, 0x68, 0x0f, 0xe0, 0x52, 0xed, 0x69, 0x6d, 0xd5, 0xd6, 0x4d, - 0xab, 0x92, 0xdc, 0x48, 0x84, 0xea, 0xc9, 0x99, 0x43, 0x72, 0x6a, 0xb4, 0x55, 0x1b, 0xef, 0x26, - 0xc9, 0x74, 0x65, 0x1f, 0x27, 0x7a, 0x0f, 0xca, 0xaa, 0x61, 0x28, 0x96, 0xad, 0xda, 0x58, 0x39, - 0xbf, 0xb2, 0xb1, 0x45, 0xa3, 0x56, 0x41, 0x2e, 0xaa, 0x86, 0x71, 0x42, 0x7a, 0x77, 0x49, 0x27, - 0xba, 0x05, 0x25, 0x12, 0xe0, 0x34, 0xb5, 0xa7, 0x74, 0xb1, 0xd6, 0xe9, 0xda, 0x95, 0xf4, 0x86, - 0x70, 0x3b, 0x21, 0x17, 0x79, 0x6f, 0x83, 0x76, 0x4a, 0x6d, 0xf7, 0xc4, 0x69, 0x70, 0x43, 0x08, - 0x92, 0x6d, 0xd5, 0x56, 0xe9, 0x4e, 0x16, 0x64, 0xfa, 0x4c, 0xfa, 0x0c, 0xd5, 0xee, 0xf2, 0xfd, - 0xa1, 0xcf, 0x68, 0x15, 0xd2, 0x5c, 0x6c, 0x82, 0x8a, 0xe5, 0x2d, 0xb4, 0x02, 0x29, 0xc3, 0xd4, - 0x2f, 0x31, 0x3d, 0xba, 0xac, 0xcc, 0x1a, 0x92, 0x0c, 0xa5, 0x60, 0x20, 0x44, 0x25, 0x88, 0xdb, - 0x23, 0x3e, 0x4a, 0xdc, 0x1e, 0xa1, 0xef, 0x42, 0x92, 0x6c, 0x24, 0x1d, 0xa3, 0x14, 0x12, 0xfa, - 0x39, 0x5f, 0xf3, 0xca, 0xc0, 0x32, 0xa5, 0x94, 0xca, 0x50, 0x0c, 0x04, 0x48, 0x69, 0x15, 0x56, - 0xc2, 0xe2, 0x9d, 0xd4, 0x75, 0xfb, 0x03, 0x71, 0x0b, 0xdd, 0x87, 0xac, 0x1b, 0xf0, 0x98, 0xe2, - 0xdc, 0x98, 0x18, 0xd6, 0x21, 0x96, 0x5d, 0x52, 0xa2, 0x31, 0xe4, 0x00, 0xba, 0x2a, 0x4f, 0x6f, - 0x0a, 0x72, 0x46, 0x35, 0x8c, 0x86, 0x6a, 0x75, 0xa5, 0x1f, 0x42, 0x25, 0x2a, 0x98, 0xf9, 0x36, - 0x4c, 0xa0, 0x6a, 0xef, 0x6c, 0xd8, 0x2a, 0xa4, 0x2f, 0x74, 0xb3, 0xaf, 0xda, 0x54, 0x58, 0x51, - 0xe6, 0x2d, 0xb2, 0x91, 0x2c, 0xb0, 0x25, 0x68, 0x37, 0x6b, 0x48, 0x0a, 0xdc, 0x88, 0x0c, 0x68, - 0x84, 0x45, 0x1b, 0xb4, 0x31, 0xdb, 0xd6, 0xa2, 0xcc, 0x1a, 0x9e, 0x20, 0x36, 0x59, 0xd6, 0x20, - 0xc3, 0x5a, 0x74, 0xad, 0x54, 0x7e, 0x4e, 0xe6, 0x2d, 0xe9, 0x5f, 0xd3, 0xb0, 0x1a, 0x1e, 0xd6, - 0xd0, 0x06, 0x14, 0xfa, 0xea, 0x48, 0xb1, 0x47, 0x5c, 0xed, 0x04, 0x7a, 0xf0, 0xd0, 0x57, 0x47, - 0xcd, 0x11, 0xd3, 0x39, 0x11, 0x12, 0xf6, 0xc8, 0xaa, 0xc4, 0x37, 0x12, 0xb7, 0x0b, 0x32, 0x79, - 0x44, 0xa7, 0xb0, 0xd4, 0xd3, 0x5b, 0x6a, 0x4f, 0xe9, 0xa9, 0x96, 0xad, 0xf0, 0x7c, 0x87, 0x19, - 0xd1, 0xbb, 0x13, 0x9b, 0xcd, 0x02, 0x14, 0x6e, 0xb3, 0xf3, 0x24, 0x0e, 0x87, 0xeb, 0x7f, 0x99, - 0xca, 0x38, 0x50, 0x9d, 0xa3, 0x46, 0xa7, 0xb0, 0x72, 0x7e, 0xf5, 0xa5, 0x3a, 0xb0, 0xb5, 0x01, - 0x56, 0x26, 0xcc, 0x6a, 0x52, 0x7b, 0x9e, 0x6b, 0xd6, 0x39, 0xee, 0xaa, 0x97, 0x9a, 0x6e, 0x72, - 0x91, 0xcb, 0x2e, 0xff, 0x99, 0x67, 0x5b, 0xde, 0x19, 0xa5, 0x02, 0x4a, 0xed, 0xb8, 0x97, 0xf4, - 0xc2, 0xee, 0xe5, 0xbb, 0xb0, 0x32, 0xc0, 0x23, 0xdb, 0x37, 0x47, 0xa6, 0x38, 0x19, 0x7a, 0x16, - 0x88, 0xbc, 0xf3, 0xc6, 0x27, 0x3a, 0x84, 0x3e, 0xa0, 0x99, 0x82, 0xa1, 0x5b, 0xd8, 0x54, 0xd4, - 0x76, 0xdb, 0xc4, 0x96, 0x55, 0xc9, 0x52, 0xea, 0xb2, 0xd3, 0xbf, 0xc3, 0xba, 0x03, 0x9a, 0x98, - 0x0b, 0x68, 0x22, 0x7a, 0x1f, 0xca, 0xe3, 0x43, 0x02, 0xa5, 0x28, 0x5d, 0x06, 0x87, 0xbb, 0x05, - 0x25, 0xcf, 0xc9, 0x51, 0xba, 0x3c, 0xf3, 0x26, 0x6e, 0x2f, 0x25, 0xbb, 0x09, 0x39, 0xe2, 0x0a, - 0x18, 0x45, 0x81, 0x52, 0x64, 0x49, 0x07, 0x7d, 0xf9, 0x2e, 0x14, 0xf1, 0xa5, 0xd6, 0xc6, 0x83, - 0x16, 0x66, 0x04, 0x45, 0x4a, 0x50, 0x70, 0x3a, 0x29, 0xd1, 0x7b, 0x50, 0xa6, 0x3a, 0xc0, 0xa2, - 0x04, 0x25, 0x2b, 0xb1, 0x91, 0x48, 0x37, 0x8b, 0x8a, 0x84, 0xee, 0x21, 0xdc, 0xf0, 0xd1, 0x19, - 0xaa, 0x69, 0x2b, 0x16, 0xb6, 0x15, 0x5b, 0xb7, 0x79, 0x22, 0x96, 0x90, 0xaf, 0xb9, 0x1c, 0xc7, - 0xaa, 0x69, 0x9f, 0x60, 0xbb, 0x49, 0x5e, 0xa2, 0x07, 0x50, 0x09, 0xe3, 0xa4, 0x43, 0x89, 0x74, - 0xa8, 0x95, 0x71, 0x46, 0x3a, 0xe2, 0x6d, 0x10, 0x7d, 0xda, 0xc9, 0xe8, 0x97, 0xd8, 0x66, 0xf5, - 0x5c, 0x95, 0xa3, 0x94, 0x77, 0x60, 0x89, 0x52, 0x9a, 0xd8, 0x1a, 0xf6, 0x6c, 0xbe, 0x5f, 0x88, - 0x1d, 0x0e, 0x79, 0x21, 0xb3, 0x7e, 0xea, 0x0b, 0xfe, 0xc9, 0x6f, 0x48, 0xc1, 0xb4, 0x8d, 0x9b, - 0x89, 0xe0, 0x99, 0xc9, 0x09, 0xac, 0xf0, 0xc3, 0x6d, 0x07, 0x2c, 0x85, 0x5d, 0x9f, 0x6e, 0x4e, - 0x7a, 0xc3, 0x71, 0x0b, 0x41, 0x0e, 0xfb, 0x1c, 0x46, 0x92, 0x78, 0x3d, 0x23, 0x41, 0x90, 0xa4, - 0xeb, 0x4e, 0xb2, 0x08, 0x41, 0x9e, 0x7f, 0x9f, 0x0d, 0x07, 0x66, 0x1a, 0x4e, 0x7e, 0x4e, 0xc3, - 0x29, 0xcc, 0x34, 0x9c, 0xe2, 0x2c, 0xc3, 0x29, 0xcd, 0x67, 0x38, 0xe5, 0x85, 0x0d, 0x47, 0x7c, - 0x55, 0xc3, 0x59, 0x5a, 0xd0, 0x70, 0xd0, 0xfc, 0x86, 0xb3, 0x1c, 0x6e, 0x38, 0x1f, 0xc3, 0xd2, - 0xc4, 0x85, 0xc5, 0x55, 0x3a, 0x21, 0x54, 0xe9, 0xe2, 0x7e, 0xa5, 0x93, 0xfe, 0x5e, 0x80, 0x6a, - 0xf4, 0x0d, 0x25, 0x54, 0xd4, 0x87, 0xb0, 0xe4, 0x1e, 0xaf, 0xab, 0x3c, 0x2c, 0x5e, 0x8a, 0xee, - 0x0b, 0x47, 0x7b, 0xa2, 0x52, 0x9f, 0x5b, 0x50, 0x1a, 0xbb, 0x3f, 0x31, 0x13, 0x29, 0x5e, 0xfa, - 0xc7, 0x97, 0xfe, 0x31, 0xed, 0xe6, 0x23, 0x81, 0x4b, 0x4e, 0x88, 0x5b, 0xf8, 0x14, 0x96, 0xdb, - 0xb8, 0xa5, 0xb5, 0x5f, 0xd5, 0x2b, 0x2c, 0x71, 0xee, 0x6f, 0x9d, 0xc2, 0xb7, 0x4e, 0xe1, 0xf7, - 0xdb, 0x29, 0xfc, 0x4d, 0xdc, 0xf5, 0x0a, 0xde, 0x65, 0x3e, 0xd4, 0x94, 0x1f, 0x10, 0xad, 0x53, - 0x49, 0x62, 0xcb, 0xcc, 0xa4, 0x32, 0x79, 0x57, 0x6b, 0xd0, 0xf7, 0x5c, 0x9d, 0x39, 0x35, 0x3a, - 0x0a, 0xce, 0xdb, 0x87, 0x43, 0x4e, 0x82, 0x7a, 0x9e, 0x3d, 0xf9, 0x8c, 0xcd, 0xb7, 0x3c, 0x7a, - 0x57, 0x96, 0xa7, 0xe6, 0xa8, 0x93, 0x57, 0x8d, 0x3a, 0x3f, 0xdf, 0x29, 0x66, 0x26, 0xd5, 0xdd, - 0xbb, 0xaf, 0x0b, 0x46, 0x4c, 0xdc, 0xa4, 0xde, 0x81, 0x82, 0xa5, 0x75, 0x14, 0x8a, 0xc2, 0x68, - 0x98, 0xdd, 0x6a, 0xb3, 0x72, 0xde, 0xd2, 0x3a, 0x67, 0xbc, 0x4b, 0xfa, 0x00, 0xca, 0x63, 0x80, - 0xc4, 0xd8, 0xf5, 0xc4, 0x73, 0xa6, 0xcb, 0xee, 0xbe, 0x7b, 0xc0, 0x83, 0xf4, 0xf3, 0x02, 0x64, - 0x65, 0x6c, 0x19, 0x44, 0xa9, 0xd1, 0x2e, 0xe4, 0xf0, 0xa8, 0x85, 0x0d, 0xdb, 0x41, 0x05, 0xc2, - 0xc1, 0x0b, 0x46, 0x5d, 0x77, 0x28, 0x1b, 0x31, 0xd9, 0x63, 0x43, 0xf7, 0x38, 0xc6, 0x1c, 0x0d, - 0x17, 0x73, 0x76, 0x3f, 0xc8, 0xfc, 0xc0, 0x01, 0x99, 0x13, 0x91, 0xf8, 0x29, 0xe3, 0x1a, 0x43, - 0x99, 0xef, 0x71, 0x94, 0x39, 0x39, 0x63, 0xb0, 0x00, 0xcc, 0x5c, 0x0b, 0xc0, 0xcc, 0xa9, 0x19, - 0xcb, 0x8c, 0xc0, 0x99, 0x1f, 0x38, 0x38, 0x73, 0x7a, 0xc6, 0x8c, 0xc7, 0x80, 0xe6, 0xef, 0xfb, - 0x80, 0xe6, 0x6c, 0x24, 0xc2, 0xc4, 0x58, 0x43, 0x90, 0xe6, 0x47, 0x2e, 0xd2, 0x9c, 0x8f, 0x44, - 0xa9, 0x39, 0xf3, 0x38, 0xd4, 0x7c, 0x34, 0x01, 0x35, 0x33, 0x68, 0xf8, 0xbd, 0x48, 0x11, 0x33, - 0xb0, 0xe6, 0xa3, 0x09, 0xac, 0xb9, 0x38, 0x43, 0xe0, 0x0c, 0xb0, 0xf9, 0x2f, 0xc2, 0xc1, 0xe6, - 0x68, 0x38, 0x98, 0x4f, 0x73, 0x3e, 0xb4, 0x59, 0x89, 0x40, 0x9b, 0xcb, 0x91, 0xc8, 0x28, 0x13, - 0x3f, 0x37, 0xdc, 0x7c, 0x1a, 0x02, 0x37, 0x33, 0x60, 0xf8, 0x76, 0xa4, 0xf0, 0x39, 0xf0, 0xe6, - 0xd3, 0x10, 0xbc, 0x79, 0x69, 0xa6, 0xd8, 0x99, 0x80, 0xf3, 0x5e, 0x10, 0x70, 0x46, 0x11, 0x17, - 0x79, 0xcf, 0xda, 0x23, 0x10, 0xe7, 0xf3, 0x28, 0xc4, 0x99, 0xa1, 0xc2, 0x77, 0x23, 0x25, 0x2e, - 0x00, 0x39, 0x1f, 0x4d, 0x40, 0xce, 0x2b, 0x33, 0x34, 0x6d, 0x06, 0xe6, 0xbc, 0x17, 0xc4, 0x9c, - 0xaf, 0xcd, 0x58, 0x7c, 0x24, 0xe8, 0x5c, 0x0b, 0x80, 0xce, 0xab, 0x33, 0x5c, 0x49, 0x04, 0xea, - 0xfc, 0x27, 0x7e, 0xd4, 0xf9, 0x7a, 0x24, 0x70, 0xcd, 0xcf, 0x21, 0x0c, 0x76, 0xde, 0x0b, 0xc2, - 0xce, 0x95, 0x19, 0xcb, 0x99, 0x07, 0x77, 0xce, 0x88, 0x59, 0x86, 0x38, 0x3f, 0x4d, 0x66, 0x41, - 0xcc, 0x4b, 0x1f, 0x90, 0xe0, 0x31, 0xe6, 0xf8, 0xd1, 0x0a, 0xa4, 0xb0, 0x69, 0xea, 0x26, 0x47, - 0x90, 0x59, 0x43, 0xba, 0x0d, 0x05, 0xbf, 0x93, 0x9f, 0x82, 0x51, 0x53, 0xdc, 0xcf, 0xe7, 0xd8, - 0xa5, 0xff, 0x16, 0x3c, 0x5e, 0x1a, 0x79, 0xfd, 0x18, 0x66, 0x8e, 0x63, 0x98, 0x3e, 0xe4, 0x3a, - 0x1e, 0x44, 0xae, 0xd7, 0x21, 0x4f, 0xf2, 0xbe, 0x31, 0x50, 0x5a, 0x35, 0x5c, 0x50, 0xda, 0xc9, - 0x53, 0x78, 0xae, 0xc5, 0xa2, 0x64, 0x92, 0x46, 0xc9, 0xb2, 0x97, 0x6d, 0xb1, 0xdc, 0xf6, 0x3b, - 0xb0, 0xec, 0xa3, 0x75, 0xf3, 0x49, 0x86, 0xd0, 0x8a, 0x2e, 0xf5, 0x0e, 0x4f, 0x2c, 0xef, 0x02, - 0xea, 0x6b, 0x03, 0xad, 0x3f, 0xec, 0x2b, 0x1d, 0x95, 0x98, 0xaa, 0xd6, 0xc2, 0x16, 0x8d, 0x0e, - 0x39, 0x59, 0xe4, 0x6f, 0x3e, 0x51, 0xad, 0x63, 0xda, 0x2f, 0xfd, 0x8b, 0xe0, 0xed, 0xa7, 0x87, - 0x7d, 0x87, 0xc1, 0xd4, 0xc2, 0x1b, 0x82, 0xa9, 0xe3, 0xaf, 0x0c, 0x53, 0xfb, 0xb3, 0xe9, 0x44, - 0x10, 0x25, 0xfd, 0x1f, 0xc1, 0x3b, 0x41, 0x17, 0x74, 0x6e, 0xe9, 0x6d, 0xcc, 0x71, 0x4b, 0xfa, - 0x4c, 0x6e, 0x43, 0x3d, 0xbd, 0xc3, 0xd1, 0x49, 0xf2, 0x48, 0xa8, 0xdc, 0xb8, 0x9d, 0xe3, 0x61, - 0xd9, 0x85, 0x3c, 0xd9, 0x15, 0x83, 0x43, 0x9e, 0x22, 0x24, 0x5e, 0x62, 0x16, 0x65, 0x0b, 0x32, - 0x79, 0x24, 0x74, 0x54, 0x49, 0xf9, 0x55, 0x81, 0x35, 0xd0, 0x43, 0xc8, 0xd1, 0x3a, 0xbc, 0xa2, - 0x1b, 0x16, 0x0f, 0xac, 0x81, 0x5b, 0x15, 0x2b, 0xc6, 0x6f, 0x1e, 0x13, 0x9a, 0x23, 0xc3, 0x92, - 0xb3, 0x06, 0x7f, 0xf2, 0xa5, 0x4b, 0xb9, 0xc0, 0xdd, 0xe6, 0x2d, 0xc8, 0x91, 0xd9, 0x5b, 0x86, - 0xda, 0xc2, 0xf4, 0x16, 0x91, 0x93, 0xbd, 0x0e, 0xe9, 0x05, 0xa0, 0x49, 0xef, 0x80, 0x1a, 0x90, - 0xc6, 0x97, 0x78, 0x60, 0xb3, 0xab, 0x5f, 0x7e, 0x7b, 0x35, 0x24, 0x35, 0xc4, 0x03, 0x7b, 0xb7, - 0x42, 0x36, 0xf9, 0x37, 0xbf, 0x5e, 0x17, 0x19, 0xf5, 0x5d, 0xbd, 0xaf, 0xd9, 0xb8, 0x6f, 0xd8, - 0x57, 0x32, 0xe7, 0x97, 0xfe, 0x4d, 0x20, 0x89, 0x5d, 0x20, 0x0f, 0x08, 0xdd, 0x5b, 0xc7, 0x40, - 0xe2, 0x3e, 0x90, 0x7f, 0x72, 0xbf, 0xdf, 0x06, 0x20, 0x4a, 0xf9, 0x85, 0x3a, 0xb0, 0x71, 0x9b, - 0x6f, 0x70, 0xae, 0xa3, 0x5a, 0x9f, 0xd1, 0x8e, 0xe0, 0x52, 0xb3, 0x63, 0x4b, 0xf5, 0xe1, 0xcb, - 0x39, 0x3f, 0xbe, 0x8c, 0xaa, 0x90, 0x35, 0x4c, 0x4d, 0x37, 0x35, 0xfb, 0x8a, 0xee, 0x4f, 0x42, - 0x76, 0xdb, 0x4f, 0x93, 0xd9, 0xa4, 0x98, 0x72, 0xcb, 0x57, 0xcc, 0x99, 0xe4, 0xc5, 0x82, 0xf4, - 0x93, 0xb8, 0xa7, 0xf7, 0x5e, 0xde, 0xfb, 0xea, 0x0b, 0x0b, 0x53, 0xa4, 0xb5, 0x90, 0xc5, 0xfa, - 0x7a, 0xc8, 0xbc, 0x49, 0x6b, 0x68, 0xe1, 0x36, 0x2f, 0xa4, 0xb8, 0x6d, 0xdf, 0x01, 0x66, 0x5e, - 0xef, 0x00, 0xa7, 0xef, 0xa9, 0xf4, 0xb7, 0xb4, 0xf4, 0x15, 0x74, 0xe9, 0xe8, 0xc4, 0x0f, 0x5d, - 0x0c, 0xa9, 0x39, 0x3a, 0x8a, 0x34, 0xaf, 0xdd, 0x7a, 0x10, 0x07, 0xeb, 0xb6, 0xd0, 0x9f, 0xc2, - 0xf5, 0x31, 0x9f, 0xe2, 0x8a, 0x8e, 0x47, 0xe4, 0x9f, 0xe3, 0x9e, 0xe5, 0x5a, 0xd0, 0xb3, 0x38, - 0x92, 0xbd, 0xbd, 0x4a, 0xbc, 0xa6, 0xb2, 0xdf, 0x87, 0x52, 0x30, 0x6d, 0x25, 0x37, 0x67, 0x13, - 0xdb, 0xaa, 0x36, 0x50, 0x02, 0xf8, 0x4c, 0x81, 0x75, 0xf2, 0x82, 0xd7, 0x31, 0x5c, 0x0b, 0x4d, - 0x55, 0xd1, 0xf7, 0x20, 0xe7, 0x65, 0xb9, 0x42, 0xc4, 0x25, 0xcd, 0xad, 0x07, 0x79, 0xb4, 0xd2, - 0x3f, 0x0b, 0x9e, 0xc8, 0x60, 0x85, 0xa9, 0x0e, 0x69, 0x76, 0xb9, 0xa5, 0x4a, 0x5a, 0xda, 0xfe, - 0xce, 0x7c, 0x49, 0xee, 0x26, 0xbb, 0xf9, 0xca, 0x9c, 0x59, 0x7a, 0x01, 0x69, 0xd6, 0x83, 0xf2, - 0x90, 0x39, 0x3d, 0x7c, 0x76, 0x78, 0xf4, 0xd9, 0xa1, 0x18, 0x43, 0x00, 0xe9, 0x9d, 0x5a, 0xad, - 0x7e, 0xdc, 0x14, 0x05, 0x94, 0x83, 0xd4, 0xce, 0xee, 0x91, 0xdc, 0x14, 0xe3, 0xa4, 0x5b, 0xae, - 0x3f, 0xad, 0xd7, 0x9a, 0x62, 0x02, 0x2d, 0x41, 0x91, 0x3d, 0x2b, 0x7b, 0x47, 0xf2, 0xf3, 0x9d, - 0xa6, 0x98, 0xf4, 0x75, 0x9d, 0xd4, 0x0f, 0x9f, 0xd4, 0x65, 0x31, 0x25, 0xfd, 0x01, 0xdc, 0x88, - 0x4c, 0x8b, 0xbd, 0xf2, 0x91, 0xe0, 0x2b, 0x1f, 0x49, 0x7f, 0x17, 0x87, 0x6a, 0x74, 0xae, 0x8b, - 0x9e, 0x8e, 0x2d, 0x7c, 0x7b, 0x81, 0x44, 0x79, 0x6c, 0xf5, 0xe8, 0x16, 0x94, 0x4c, 0x7c, 0x81, - 0xed, 0x56, 0x97, 0xe5, 0xde, 0x2c, 0x2a, 0x15, 0xe5, 0x22, 0xef, 0xa5, 0x4c, 0x16, 0x23, 0xfb, - 0x11, 0x6e, 0xd9, 0x0a, 0xf3, 0x34, 0x4c, 0xc1, 0x72, 0x84, 0x8c, 0xf4, 0x9e, 0xb0, 0x4e, 0xe9, - 0x87, 0x0b, 0xed, 0x65, 0x0e, 0x52, 0x72, 0xbd, 0x29, 0x7f, 0x2e, 0x26, 0x10, 0x82, 0x12, 0x7d, - 0x54, 0x4e, 0x0e, 0x77, 0x8e, 0x4f, 0x1a, 0x47, 0x64, 0x2f, 0x97, 0xa1, 0xec, 0xec, 0xa5, 0xd3, - 0x99, 0x92, 0xfe, 0x3d, 0x0e, 0xd7, 0x23, 0x32, 0x75, 0xf4, 0x10, 0xc0, 0x1e, 0x29, 0x26, 0x6e, - 0xe9, 0x66, 0x3b, 0x5a, 0xc9, 0x9a, 0x23, 0x99, 0x52, 0xc8, 0x39, 0x9b, 0x3f, 0x59, 0x53, 0xaa, - 0x8e, 0xe8, 0x23, 0x2e, 0x94, 0xa2, 0x25, 0xdc, 0xac, 0xde, 0x0e, 0x29, 0xae, 0xe1, 0x16, 0x11, - 0x4c, 0xf7, 0x96, 0x0a, 0xa6, 0xf4, 0xe8, 0x79, 0x98, 0xff, 0x98, 0xb3, 0x3c, 0x1d, 0xe2, 0x39, - 0x3e, 0x8f, 0xf6, 0x1c, 0xa9, 0x79, 0x93, 0x92, 0x70, 0xd7, 0x21, 0xfd, 0x43, 0xc2, 0xbf, 0xb1, - 0xc1, 0x8b, 0xc9, 0x11, 0xa4, 0x2d, 0x5b, 0xb5, 0x87, 0x16, 0x57, 0xb8, 0xef, 0xcd, 0x7b, 0xcb, - 0xd9, 0x74, 0x1e, 0x4e, 0x28, 0xbb, 0xcc, 0xc5, 0x7c, 0xbb, 0xdf, 0xd4, 0xc1, 0x06, 0x37, 0x27, - 0xda, 0x64, 0x3c, 0x9f, 0x13, 0x97, 0x1e, 0x7b, 0x49, 0x8e, 0x0f, 0xc0, 0x9f, 0x04, 0xc7, 0x85, - 0x30, 0x70, 0xfc, 0xe7, 0x02, 0xdc, 0x9c, 0x72, 0xd7, 0x43, 0x9f, 0x8e, 0x9d, 0xf3, 0xa3, 0x45, - 0x6e, 0x8a, 0x9b, 0xac, 0x2f, 0x78, 0xd2, 0xd2, 0x3d, 0x28, 0xf8, 0xfb, 0xe7, 0x5b, 0xe4, 0x6f, - 0xe2, 0x9e, 0xcf, 0x0f, 0xa2, 0xf8, 0x6f, 0x2c, 0x9b, 0x1b, 0xd3, 0xb3, 0xf8, 0x82, 0x7a, 0x16, - 0x9a, 0x17, 0x24, 0x5e, 0x33, 0x2f, 0x98, 0xa2, 0x6d, 0xc9, 0xd7, 0xd3, 0xb6, 0x80, 0xc1, 0xa5, - 0x82, 0x17, 0x86, 0x15, 0x4f, 0xa3, 0x7c, 0x20, 0xe4, 0xe7, 0x00, 0x3e, 0xb4, 0x75, 0x05, 0x52, - 0xa6, 0x3e, 0x1c, 0xb4, 0xa9, 0x5e, 0xa4, 0x64, 0xd6, 0x40, 0xf7, 0x21, 0x45, 0xf4, 0xcb, 0xd9, - 0xbd, 0x49, 0x57, 0x4b, 0xf4, 0xc3, 0x87, 0xe1, 0x32, 0x6a, 0xe9, 0x07, 0x50, 0x0a, 0x42, 0xbc, - 0x6f, 0x56, 0xbc, 0x06, 0x68, 0xf2, 0x53, 0x87, 0x88, 0x21, 0xbe, 0x1f, 0x1c, 0xe2, 0x9d, 0xc8, - 0x8f, 0x26, 0xc2, 0x87, 0xfa, 0x12, 0x52, 0x54, 0xdd, 0x48, 0xce, 0x4b, 0xbf, 0xaf, 0xe1, 0x77, - 0x62, 0xf2, 0x8c, 0x7e, 0x00, 0xa0, 0xda, 0xb6, 0xa9, 0x9d, 0x0f, 0xbd, 0x01, 0xd6, 0xc3, 0xd5, - 0x75, 0xc7, 0xa1, 0xdb, 0x7d, 0x8b, 0xeb, 0xed, 0x8a, 0xc7, 0xea, 0xd3, 0x5d, 0x9f, 0x40, 0xe9, - 0x10, 0x4a, 0x41, 0x5e, 0xe7, 0x5e, 0x26, 0x84, 0xdc, 0xcb, 0xe2, 0xfe, 0x7b, 0x99, 0x7b, 0xab, - 0x4b, 0xb0, 0x8f, 0x88, 0x68, 0x43, 0xfa, 0x5f, 0x01, 0x0a, 0x7e, 0x6d, 0x7f, 0xc3, 0x37, 0x80, - 0x19, 0xd7, 0x9d, 0x1b, 0x13, 0x17, 0x80, 0x4c, 0x47, 0xb5, 0x4e, 0x7f, 0x97, 0xf9, 0xff, 0x4f, - 0x04, 0xc8, 0xba, 0x8b, 0x8f, 0x00, 0xec, 0xbd, 0xbd, 0x8b, 0xfb, 0x3f, 0x02, 0x62, 0x45, 0x82, - 0x84, 0x5b, 0x24, 0x78, 0xec, 0x26, 0x68, 0x51, 0x28, 0xb8, 0x7f, 0xa7, 0x9d, 0x52, 0x09, 0xcf, - 0x47, 0x4d, 0x36, 0x0d, 0x92, 0x98, 0xa0, 0x3f, 0x82, 0xb4, 0xda, 0x72, 0xa1, 0xff, 0x52, 0x08, - 0x90, 0xe5, 0x90, 0x6e, 0x36, 0x47, 0x3b, 0x94, 0x52, 0xe6, 0x1c, 0x7c, 0x52, 0x71, 0x67, 0x52, - 0x52, 0x95, 0xc8, 0xdd, 0x71, 0xde, 0xc1, 0xe9, 0xe1, 0xf3, 0xa3, 0x27, 0xfb, 0x7b, 0xfb, 0xf5, - 0x27, 0x62, 0x4c, 0xaa, 0x41, 0xde, 0x29, 0x35, 0xa9, 0x7d, 0x0b, 0xdd, 0x84, 0x5c, 0x5f, 0x0d, - 0x7e, 0x88, 0x94, 0xed, 0xab, 0xfc, 0x33, 0xa4, 0xeb, 0x90, 0x21, 0x2f, 0x3b, 0xaa, 0xe5, 0x54, - 0x86, 0xfb, 0xea, 0xe8, 0x13, 0xd5, 0x92, 0x7e, 0x2b, 0x40, 0x79, 0xcc, 0x1d, 0xa1, 0x6d, 0x48, - 0x31, 0x10, 0x2d, 0xea, 0xfb, 0x76, 0xdf, 0xb0, 0x32, 0x23, 0x45, 0x1f, 0x41, 0xd6, 0xa9, 0xc6, - 0x85, 0xdd, 0x87, 0x98, 0xdf, 0x73, 0xea, 0x39, 0x9c, 0xd5, 0xe5, 0x40, 0x1f, 0x43, 0xce, 0x75, - 0xac, 0xd1, 0x1f, 0x14, 0xba, 0x2e, 0x99, 0xf3, 0x7b, 0x3c, 0xe8, 0x91, 0x87, 0x65, 0x25, 0x27, - 0x01, 0x7d, 0xce, 0xce, 0x08, 0x38, 0xb3, 0x43, 0x2f, 0x3d, 0x86, 0x9c, 0x2b, 0x18, 0x55, 0x20, - 0xe3, 0xd4, 0x44, 0x05, 0xee, 0x71, 0x79, 0x2d, 0x74, 0x05, 0x52, 0x86, 0xfe, 0x05, 0xff, 0x38, - 0x2c, 0x21, 0xb3, 0x86, 0xd4, 0x86, 0xf2, 0x58, 0xa0, 0x40, 0x8f, 0x21, 0x63, 0x0c, 0xcf, 0x15, - 0xc7, 0xaa, 0xc7, 0xf6, 0xcf, 0xc1, 0x4f, 0x86, 0xe7, 0x3d, 0xad, 0xf5, 0x0c, 0x5f, 0x39, 0x7a, - 0x64, 0x0c, 0xcf, 0x9f, 0x31, 0xe3, 0x67, 0xa3, 0xc4, 0xfd, 0xa3, 0x5c, 0x42, 0xd6, 0xf1, 0x65, - 0xe8, 0x8f, 0xfd, 0x5b, 0xe5, 0x7c, 0xdc, 0x19, 0x19, 0xbc, 0xb8, 0x78, 0xdf, 0x4e, 0xdd, 0x81, - 0x25, 0x4b, 0xeb, 0x0c, 0x9c, 0xfa, 0x39, 0x3b, 0x68, 0x56, 0x10, 0x2b, 0xb3, 0x17, 0x07, 0x0e, - 0x24, 0x47, 0x52, 0x0f, 0x71, 0xdc, 0x99, 0xfe, 0x2e, 0x27, 0x10, 0x92, 0x22, 0x25, 0xc2, 0x52, - 0xa4, 0xbf, 0x8e, 0x43, 0xde, 0x57, 0x95, 0x47, 0x7f, 0xe8, 0xf3, 0xec, 0xa5, 0x90, 0xd8, 0xee, - 0xa3, 0xf5, 0xbe, 0x9e, 0x0c, 0x2e, 0x2c, 0xbe, 0xf8, 0xc2, 0xa2, 0x3e, 0x82, 0x70, 0x8a, 0xfb, - 0xc9, 0x85, 0x8b, 0xfb, 0x77, 0x01, 0xd1, 0xb2, 0xb4, 0x72, 0xa9, 0xdb, 0xda, 0xa0, 0xa3, 0x30, - 0xd5, 0x60, 0x7e, 0x58, 0xa4, 0x6f, 0xce, 0xe8, 0x8b, 0x63, 0xaa, 0x25, 0x7f, 0x19, 0x87, 0xac, - 0x63, 0x61, 0xff, 0x4f, 0xb7, 0xe0, 0xaf, 0x04, 0xc8, 0xba, 0x50, 0xc3, 0xa2, 0x9f, 0x97, 0xae, - 0x42, 0x9a, 0xdf, 0xa6, 0xd9, 0xf7, 0xa5, 0xbc, 0x15, 0xfa, 0x21, 0x47, 0x15, 0xb2, 0x7d, 0x6c, - 0xab, 0x34, 0xae, 0xb2, 0xd4, 0xcc, 0x6d, 0xdf, 0x79, 0x04, 0x79, 0xdf, 0xa7, 0xb9, 0x24, 0xd4, - 0x1e, 0xd6, 0x3f, 0x13, 0x63, 0xd5, 0xcc, 0x4f, 0x7f, 0xb6, 0x91, 0x38, 0xc4, 0x5f, 0x10, 0x27, - 0x23, 0xd7, 0x6b, 0x8d, 0x7a, 0xed, 0x99, 0x28, 0x54, 0xf3, 0x3f, 0xfd, 0xd9, 0x46, 0x46, 0xc6, - 0xb4, 0x30, 0x79, 0xe7, 0x19, 0x94, 0xc7, 0x0e, 0x26, 0x98, 0x7b, 0x23, 0x28, 0x3d, 0x39, 0x3d, - 0x3e, 0xd8, 0xaf, 0xed, 0x34, 0xeb, 0xca, 0xd9, 0x51, 0xb3, 0x2e, 0x0a, 0xe8, 0x3a, 0x2c, 0x1f, - 0xec, 0x7f, 0xd2, 0x68, 0x2a, 0xb5, 0x83, 0xfd, 0xfa, 0x61, 0x53, 0xd9, 0x69, 0x36, 0x77, 0x6a, - 0xcf, 0xc4, 0xf8, 0xf6, 0x6f, 0xf3, 0x50, 0xde, 0xd9, 0xad, 0xed, 0xef, 0x18, 0x46, 0x4f, 0x6b, - 0xa9, 0x34, 0x86, 0xd4, 0x20, 0x49, 0x6b, 0x09, 0x53, 0xff, 0x59, 0xaa, 0x4e, 0xaf, 0x36, 0xa3, - 0x3d, 0x48, 0xd1, 0x32, 0x03, 0x9a, 0xfe, 0x13, 0x53, 0x75, 0x46, 0xf9, 0x99, 0x4c, 0x86, 0x7a, - 0x94, 0xa9, 0x7f, 0x35, 0x55, 0xa7, 0x57, 0xa3, 0xd1, 0x01, 0x64, 0x1c, 0x5c, 0x77, 0xd6, 0xaf, - 0x46, 0xd5, 0x99, 0x25, 0x62, 0xb2, 0x34, 0x86, 0xbf, 0x4f, 0xff, 0xe1, 0xa9, 0x3a, 0xa3, 0x4e, - 0x8d, 0xf6, 0x21, 0xcd, 0x11, 0xb8, 0x19, 0xff, 0x30, 0x55, 0x67, 0x55, 0x9e, 0x91, 0x0c, 0x39, - 0xaf, 0xb2, 0x31, 0xfb, 0x37, 0xae, 0xea, 0x1c, 0x25, 0x78, 0xf4, 0x02, 0x8a, 0x41, 0xa4, 0x6f, - 0xbe, 0xff, 0xa4, 0xaa, 0x73, 0xd6, 0xb8, 0x89, 0xfc, 0x20, 0xec, 0x37, 0xdf, 0x7f, 0x53, 0xd5, - 0x39, 0x4b, 0xde, 0xe8, 0x47, 0xb0, 0x34, 0x09, 0xcb, 0xcd, 0xff, 0x1b, 0x55, 0x75, 0x81, 0x22, - 0x38, 0xea, 0x03, 0x0a, 0x81, 0xf3, 0x16, 0xf8, 0xab, 0xaa, 0xba, 0x48, 0x4d, 0x1c, 0xb5, 0xa1, - 0x3c, 0x0e, 0x91, 0xcd, 0xfb, 0x97, 0x55, 0x75, 0xee, 0xfa, 0x38, 0x1b, 0x25, 0x88, 0x17, 0xcd, - 0xfb, 0xd7, 0x55, 0x75, 0xee, 0x72, 0x39, 0x3a, 0x05, 0xf0, 0xe1, 0x1d, 0x73, 0xfc, 0x85, 0x55, - 0x9d, 0xa7, 0x70, 0x8e, 0x0c, 0x58, 0x0e, 0x03, 0x42, 0x16, 0xf9, 0x29, 0xab, 0xba, 0x50, 0x3d, - 0x9d, 0xe8, 0x73, 0x10, 0xd2, 0x98, 0xef, 0x27, 0xad, 0xea, 0x9c, 0x85, 0x75, 0xb2, 0x51, 0xde, - 0x35, 0x1e, 0xcd, 0xf1, 0xa3, 0x53, 0x75, 0x9e, 0xaa, 0xf4, 0x6e, 0xfd, 0x17, 0x5f, 0xaf, 0x09, - 0xbf, 0xfa, 0x7a, 0x4d, 0xf8, 0xaf, 0xaf, 0xd7, 0x84, 0xaf, 0xbe, 0x59, 0x8b, 0xfd, 0xea, 0x9b, - 0xb5, 0xd8, 0x7f, 0x7c, 0xb3, 0x16, 0xfb, 0xb3, 0x0f, 0x3b, 0x9a, 0xdd, 0x1d, 0x9e, 0x6f, 0xb6, - 0xf4, 0xfe, 0x96, 0xff, 0x6f, 0xd9, 0xb0, 0x5f, 0x78, 0xcf, 0xd3, 0x34, 0x4e, 0xdf, 0xfb, 0xbf, - 0x00, 0x00, 0x00, 0xff, 0xff, 0xb0, 0x2f, 0x03, 0x74, 0xe2, 0x3b, 0x00, 0x00, + // 3955 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x5b, 0xcd, 0x93, 0xe3, 0xc6, + 0x75, 0x27, 0xf8, 0x8d, 0xc7, 0x2f, 0x4c, 0xcf, 0xec, 0x2e, 0x97, 0x2b, 0xed, 0xae, 0xa0, 0xac, + 0xb4, 0x5a, 0xc9, 0x33, 0xce, 0x28, 0x92, 0x57, 0x96, 0x1c, 0x85, 0xc3, 0xe5, 0x88, 0xb3, 0x3b, + 0x9a, 0x19, 0x61, 0x38, 0xa3, 0x28, 0x89, 0x05, 0x63, 0xc8, 0x1e, 0x12, 0x5e, 0x92, 0x80, 0x01, + 0x90, 0xe2, 0xe8, 0x94, 0xca, 0xc7, 0xc5, 0xb9, 0xe8, 0x98, 0x43, 0x7c, 0x8b, 0xff, 0x81, 0x1c, + 0x72, 0xc8, 0x21, 0x55, 0xa9, 0x4a, 0xa5, 0x7c, 0xf0, 0xc1, 0x87, 0x54, 0x2a, 0x27, 0x27, 0x25, + 0x55, 0xe5, 0xe0, 0x7f, 0x20, 0x47, 0xa7, 0xfa, 0x03, 0x5f, 0x24, 0xc0, 0x0f, 0xed, 0x56, 0xaa, + 0x54, 0xd6, 0x0d, 0xdd, 0x78, 0xef, 0x35, 0xba, 0xfb, 0xbd, 0xd7, 0xef, 0xfd, 0x5e, 0x03, 0x6e, + 0x39, 0x78, 0xd4, 0xc5, 0xd6, 0x50, 0x1f, 0x39, 0x3b, 0xda, 0x45, 0x47, 0xdf, 0x71, 0xae, 0x4c, + 0x6c, 0x6f, 0x9b, 0x96, 0xe1, 0x18, 0xa8, 0xe2, 0xbf, 0xdc, 0x26, 0x2f, 0x6b, 0x2f, 0x06, 0xa8, + 0x3b, 0xd6, 0x95, 0xe9, 0x18, 0x3b, 0xa6, 0x65, 0x18, 0x97, 0x8c, 0xbe, 0xf6, 0xc2, 0xfc, 0xeb, + 0xa7, 0xf8, 0x8a, 0x4b, 0x0b, 0x31, 0xd3, 0x51, 0x76, 0x4c, 0xcd, 0xd2, 0x86, 0x76, 0x04, 0x33, + 0x7b, 0x1d, 0xf8, 0x94, 0xda, 0x9d, 0x9e, 0x61, 0xf4, 0x06, 0x78, 0x87, 0xb6, 0x2e, 0xc6, 0x97, + 0x3b, 0x8e, 0x3e, 0xc4, 0xb6, 0xa3, 0x0d, 0x4d, 0x4e, 0xb0, 0xd5, 0x33, 0x7a, 0x06, 0x7d, 0xdc, + 0x21, 0x4f, 0xac, 0x57, 0xfe, 0x65, 0x01, 0x72, 0x0a, 0xfe, 0xc9, 0x18, 0xdb, 0x0e, 0xda, 0x85, + 0x34, 0xee, 0xf4, 0x8d, 0xaa, 0x70, 0x57, 0xb8, 0x5f, 0xd8, 0x7d, 0x61, 0x7b, 0x66, 0x72, 0xdb, + 0x9c, 0xae, 0xd9, 0xe9, 0x1b, 0xad, 0x84, 0x42, 0x69, 0xd1, 0x5b, 0x90, 0xb9, 0x1c, 0x8c, 0xed, + 0x7e, 0x35, 0x49, 0x99, 0x5e, 0x8c, 0x63, 0xda, 0x27, 0x44, 0xad, 0x84, 0xc2, 0xa8, 0xc9, 0x50, + 0xfa, 0xe8, 0xd2, 0xa8, 0xa6, 0x16, 0x0f, 0x75, 0x30, 0xba, 0xa4, 0x43, 0x11, 0x5a, 0xb4, 0x07, + 0xa0, 0x8f, 0x74, 0x47, 0xed, 0xf4, 0x35, 0x7d, 0x54, 0x4d, 0x53, 0xce, 0x97, 0xe2, 0x39, 0x75, + 0xa7, 0x41, 0x08, 0x5b, 0x09, 0x45, 0xd4, 0xdd, 0x06, 0xf9, 0xdc, 0x9f, 0x8c, 0xb1, 0x75, 0x55, + 0xcd, 0x2c, 0xfe, 0xdc, 0x8f, 0x08, 0x11, 0xf9, 0x5c, 0x4a, 0x8d, 0xde, 0x83, 0x7c, 0xa7, 0x8f, + 0x3b, 0x4f, 0x55, 0x67, 0x5a, 0xcd, 0x51, 0xce, 0x3b, 0x71, 0x9c, 0x0d, 0x42, 0xd7, 0x9e, 0xb6, + 0x12, 0x4a, 0xae, 0xc3, 0x1e, 0xd1, 0x43, 0xc8, 0x76, 0x8c, 0xe1, 0x50, 0x77, 0xaa, 0x40, 0x79, + 0x6f, 0xc7, 0xf2, 0x52, 0xaa, 0x56, 0x42, 0xe1, 0xf4, 0xe8, 0x08, 0xca, 0x03, 0xdd, 0x76, 0x54, + 0x7b, 0xa4, 0x99, 0x76, 0xdf, 0x70, 0xec, 0x6a, 0x81, 0x4a, 0xb8, 0x17, 0x27, 0xe1, 0x50, 0xb7, + 0x9d, 0x53, 0x97, 0xb8, 0x95, 0x50, 0x4a, 0x83, 0x60, 0x07, 0x91, 0x67, 0x5c, 0x5e, 0x62, 0xcb, + 0x13, 0x58, 0x2d, 0x2e, 0x96, 0x77, 0x4c, 0xa8, 0x5d, 0x7e, 0x22, 0xcf, 0x08, 0x76, 0xa0, 0x3f, + 0x85, 0xcd, 0x81, 0xa1, 0x75, 0x3d, 0x71, 0x6a, 0xa7, 0x3f, 0x1e, 0x3d, 0xad, 0x96, 0xa8, 0xd0, + 0xd7, 0x62, 0x3f, 0xd2, 0xd0, 0xba, 0xae, 0x88, 0x06, 0x61, 0x68, 0x25, 0x94, 0x8d, 0xc1, 0x6c, + 0x27, 0xfa, 0x14, 0xb6, 0x34, 0xd3, 0x1c, 0x5c, 0xcd, 0x4a, 0x2f, 0x53, 0xe9, 0x0f, 0xe2, 0xa4, + 0xd7, 0x09, 0xcf, 0xac, 0x78, 0xa4, 0xcd, 0xf5, 0xa2, 0x36, 0x48, 0xa6, 0x85, 0x4d, 0xcd, 0xc2, + 0xaa, 0x69, 0x19, 0xa6, 0x61, 0x6b, 0x83, 0x6a, 0x85, 0xca, 0x7e, 0x35, 0x4e, 0xf6, 0x09, 0xa3, + 0x3f, 0xe1, 0xe4, 0xad, 0x84, 0x52, 0x31, 0xc3, 0x5d, 0x4c, 0xaa, 0xd1, 0xc1, 0xb6, 0xed, 0x4b, + 0x95, 0x96, 0x49, 0xa5, 0xf4, 0x61, 0xa9, 0xa1, 0x2e, 0xd4, 0x84, 0x02, 0x9e, 0x12, 0x76, 0x75, + 0x62, 0x38, 0xb8, 0xba, 0x41, 0x05, 0xca, 0xb1, 0x16, 0x4a, 0x49, 0xcf, 0x0d, 0x07, 0xb7, 0x12, + 0x0a, 0x60, 0xaf, 0x85, 0x34, 0xb8, 0x36, 0xc1, 0x96, 0x7e, 0x79, 0x45, 0xc5, 0xa8, 0xf4, 0x8d, + 0xad, 0x1b, 0xa3, 0x2a, 0xa2, 0x02, 0x5f, 0x8f, 0x13, 0x78, 0x4e, 0x99, 0x88, 0x88, 0xa6, 0xcb, + 0xd2, 0x4a, 0x28, 0x9b, 0x93, 0xf9, 0x6e, 0xa2, 0x62, 0x97, 0xfa, 0x48, 0x1b, 0xe8, 0x9f, 0x63, + 0xf5, 0x62, 0x60, 0x74, 0x9e, 0x56, 0x37, 0x17, 0xab, 0xd8, 0x3e, 0xa7, 0xde, 0x23, 0xc4, 0x44, + 0xc5, 0x2e, 0x83, 0x1d, 0x64, 0xe6, 0x17, 0xb8, 0xa7, 0x8f, 0xb8, 0xb0, 0xad, 0xc5, 0x33, 0xdf, + 0x23, 0xa4, 0xae, 0x24, 0xb8, 0xf0, 0x5a, 0xc4, 0x79, 0x74, 0xf1, 0x40, 0x9f, 0x60, 0x8b, 0xd8, + 0xf0, 0xb5, 0xc5, 0xce, 0xe3, 0x11, 0xa3, 0xa4, 0x56, 0x2c, 0x76, 0xdd, 0x06, 0x7a, 0x1f, 0x44, + 0xb2, 0x03, 0xec, 0x43, 0xae, 0x53, 0x11, 0x77, 0x63, 0xb7, 0x60, 0xd4, 0x75, 0x3f, 0x23, 0x8f, + 0xf9, 0x33, 0x99, 0x0b, 0x35, 0x97, 0x81, 0xe6, 0x60, 0xdb, 0xa9, 0xde, 0x58, 0x3c, 0x17, 0x62, + 0x26, 0x87, 0x94, 0x92, 0xcc, 0x65, 0xe0, 0xb5, 0xf6, 0x72, 0x90, 0x99, 0x68, 0x83, 0x31, 0x7e, + 0x9c, 0xce, 0x67, 0xa5, 0xdc, 0xe3, 0x74, 0x3e, 0x2f, 0x89, 0x8f, 0xd3, 0x79, 0x51, 0x02, 0xf9, + 0x55, 0x28, 0x04, 0xbc, 0x34, 0xaa, 0x42, 0x6e, 0x88, 0x6d, 0x5b, 0xeb, 0x61, 0xea, 0xd4, 0x45, + 0xc5, 0x6d, 0xca, 0x65, 0x28, 0x06, 0x3d, 0xb3, 0xfc, 0x85, 0xe0, 0x71, 0x12, 0xa7, 0x4b, 0x38, + 0x27, 0xd8, 0xa2, 0xba, 0xc1, 0x39, 0x79, 0x13, 0xbd, 0x0c, 0x25, 0xba, 0x02, 0xaa, 0xfb, 0x9e, + 0x78, 0xfe, 0xb4, 0x52, 0xa4, 0x9d, 0xe7, 0x9c, 0xe8, 0x0e, 0x14, 0xcc, 0x5d, 0xd3, 0x23, 0x49, + 0x51, 0x12, 0x30, 0x77, 0x4d, 0x97, 0xe0, 0x25, 0x28, 0x92, 0xb9, 0x7a, 0x14, 0x69, 0x3a, 0x48, + 0x81, 0xf4, 0x71, 0x12, 0xf9, 0x97, 0x49, 0x90, 0x66, 0xbd, 0x39, 0x7a, 0x08, 0x69, 0x72, 0xb0, + 0xf1, 0x33, 0xaa, 0xb6, 0xcd, 0x4e, 0xbd, 0x6d, 0xf7, 0xd4, 0xdb, 0x6e, 0xbb, 0xa7, 0xde, 0x5e, + 0xfe, 0x17, 0xbf, 0xbe, 0x93, 0xf8, 0xe2, 0xbf, 0xee, 0x08, 0x0a, 0xe5, 0x40, 0x37, 0x89, 0x0f, + 0xd7, 0xf4, 0x91, 0xaa, 0x77, 0xe9, 0x27, 0x8b, 0xc4, 0x41, 0x6b, 0xfa, 0xe8, 0xa0, 0x8b, 0x0e, + 0x41, 0xea, 0x18, 0x23, 0x1b, 0x8f, 0xec, 0xb1, 0xad, 0xb2, 0x33, 0x97, 0x9f, 0x4c, 0x21, 0x15, + 0x61, 0xc7, 0x6d, 0xc3, 0xa5, 0x3c, 0xa1, 0x84, 0x4a, 0xa5, 0x13, 0xee, 0x40, 0xfb, 0x00, 0x13, + 0x6d, 0xa0, 0x77, 0x35, 0xc7, 0xb0, 0xec, 0x6a, 0xfa, 0x6e, 0x2a, 0x52, 0x4f, 0xce, 0x5d, 0x92, + 0x33, 0xb3, 0xab, 0x39, 0x78, 0x2f, 0x4d, 0x3e, 0x57, 0x09, 0x70, 0xa2, 0x57, 0xa0, 0xa2, 0x99, + 0xa6, 0x6a, 0x3b, 0x9a, 0x83, 0xd5, 0x8b, 0x2b, 0x07, 0xdb, 0xf4, 0xd4, 0x2a, 0x2a, 0x25, 0xcd, + 0x34, 0x4f, 0x49, 0xef, 0x1e, 0xe9, 0x44, 0xf7, 0xa0, 0x4c, 0x0e, 0x38, 0x5d, 0x1b, 0xa8, 0x7d, + 0xac, 0xf7, 0xfa, 0x4e, 0x35, 0x7b, 0x57, 0xb8, 0x9f, 0x52, 0x4a, 0xbc, 0xb7, 0x45, 0x3b, 0xe5, + 0xae, 0xb7, 0xe3, 0xf4, 0x70, 0x43, 0x08, 0xd2, 0x5d, 0xcd, 0xd1, 0xe8, 0x4a, 0x16, 0x15, 0xfa, + 0x4c, 0xfa, 0x4c, 0xcd, 0xe9, 0xf3, 0xf5, 0xa1, 0xcf, 0xe8, 0x3a, 0x64, 0xb9, 0xd8, 0x14, 0x15, + 0xcb, 0x5b, 0x68, 0x0b, 0x32, 0xa6, 0x65, 0x4c, 0x30, 0xdd, 0xba, 0xbc, 0xc2, 0x1a, 0xb2, 0x02, + 0xe5, 0xf0, 0x41, 0x88, 0xca, 0x90, 0x74, 0xa6, 0x7c, 0x94, 0xa4, 0x33, 0x45, 0xdf, 0x85, 0x34, + 0x59, 0x48, 0x3a, 0x46, 0x39, 0xe2, 0xe8, 0xe7, 0x7c, 0xed, 0x2b, 0x13, 0x2b, 0x94, 0x52, 0xae, + 0x40, 0x29, 0x74, 0x40, 0xca, 0xd7, 0x61, 0x2b, 0xea, 0xbc, 0x93, 0xfb, 0x5e, 0x7f, 0xe8, 0xdc, + 0x42, 0x6f, 0x41, 0xde, 0x3b, 0xf0, 0x98, 0xe2, 0xdc, 0x9c, 0x1b, 0xd6, 0x25, 0x56, 0x3c, 0x52, + 0xa2, 0x31, 0x64, 0x03, 0xfa, 0x1a, 0x0f, 0x6f, 0x8a, 0x4a, 0x4e, 0x33, 0xcd, 0x96, 0x66, 0xf7, + 0xe5, 0x1f, 0x41, 0x35, 0xee, 0x30, 0x0b, 0x2c, 0x98, 0x40, 0xd5, 0xde, 0x5d, 0xb0, 0xeb, 0x90, + 0xbd, 0x34, 0xac, 0xa1, 0xe6, 0x50, 0x61, 0x25, 0x85, 0xb7, 0xc8, 0x42, 0xb2, 0x83, 0x2d, 0x45, + 0xbb, 0x59, 0x43, 0x56, 0xe1, 0x66, 0xec, 0x81, 0x46, 0x58, 0xf4, 0x51, 0x17, 0xb3, 0x65, 0x2d, + 0x29, 0xac, 0xe1, 0x0b, 0x62, 0x1f, 0xcb, 0x1a, 0x64, 0x58, 0x9b, 0xce, 0x95, 0xca, 0x17, 0x15, + 0xde, 0x92, 0xff, 0x2d, 0x0b, 0xd7, 0xa3, 0x8f, 0x35, 0x74, 0x17, 0x8a, 0x43, 0x6d, 0xaa, 0x3a, + 0x53, 0xae, 0x76, 0x02, 0xdd, 0x78, 0x18, 0x6a, 0xd3, 0xf6, 0x94, 0xe9, 0x9c, 0x04, 0x29, 0x67, + 0x6a, 0x57, 0x93, 0x77, 0x53, 0xf7, 0x8b, 0x0a, 0x79, 0x44, 0x67, 0xb0, 0x31, 0x30, 0x3a, 0xda, + 0x40, 0x1d, 0x68, 0xb6, 0xa3, 0xf2, 0x78, 0x87, 0x19, 0xd1, 0xcb, 0x73, 0x8b, 0xcd, 0x0e, 0x28, + 0xdc, 0x65, 0xfb, 0x49, 0x1c, 0x0e, 0xd7, 0xff, 0x0a, 0x95, 0x71, 0xa8, 0xb9, 0x5b, 0x8d, 0xce, + 0x60, 0xeb, 0xe2, 0xea, 0x73, 0x6d, 0xe4, 0xe8, 0x23, 0xac, 0xce, 0x99, 0xd5, 0xbc, 0xf6, 0x7c, + 0xa8, 0xdb, 0x17, 0xb8, 0xaf, 0x4d, 0x74, 0xc3, 0xe2, 0x22, 0x37, 0x3d, 0xfe, 0x73, 0xdf, 0xb6, + 0xfc, 0x3d, 0xca, 0x84, 0x94, 0xda, 0x75, 0x2f, 0xd9, 0xb5, 0xdd, 0xcb, 0x77, 0x61, 0x6b, 0x84, + 0xa7, 0x4e, 0xe0, 0x1b, 0x99, 0xe2, 0xe4, 0xe8, 0x5e, 0x20, 0xf2, 0xce, 0x1f, 0x9f, 0xe8, 0x10, + 0x7a, 0x8d, 0x46, 0x0a, 0xa6, 0x61, 0x63, 0x4b, 0xd5, 0xba, 0x5d, 0x0b, 0xdb, 0x76, 0x35, 0x4f, + 0xa9, 0x2b, 0x6e, 0x7f, 0x9d, 0x75, 0x87, 0x34, 0x51, 0x0c, 0x69, 0x22, 0x7a, 0x15, 0x2a, 0xb3, + 0x43, 0x02, 0xa5, 0x28, 0x4f, 0xc2, 0xc3, 0xdd, 0x83, 0xb2, 0xef, 0xe4, 0x28, 0x5d, 0x81, 0x79, + 0x13, 0xaf, 0x97, 0x92, 0xdd, 0x02, 0x91, 0xb8, 0x02, 0x46, 0x51, 0xa4, 0x14, 0x79, 0xd2, 0x41, + 0x5f, 0xbe, 0x0c, 0x25, 0x3c, 0xd1, 0xbb, 0x78, 0xd4, 0xc1, 0x8c, 0xa0, 0x44, 0x09, 0x8a, 0x6e, + 0x27, 0x25, 0x7a, 0x05, 0x2a, 0x54, 0x07, 0xd8, 0x29, 0x41, 0xc9, 0xca, 0x6c, 0x24, 0xd2, 0xcd, + 0x4e, 0x45, 0x42, 0xf7, 0x10, 0x6e, 0x06, 0xe8, 0x4c, 0xcd, 0x72, 0x54, 0x1b, 0x3b, 0xaa, 0x63, + 0x38, 0x3c, 0x10, 0x4b, 0x29, 0xd7, 0x3c, 0x8e, 0x13, 0xcd, 0x72, 0x4e, 0xb1, 0xd3, 0x26, 0x2f, + 0xd1, 0xdb, 0x50, 0x8d, 0xe2, 0xa4, 0x43, 0x49, 0x74, 0xa8, 0xad, 0x59, 0x46, 0x3a, 0xe2, 0x7d, + 0x90, 0x02, 0xda, 0xc9, 0xe8, 0x37, 0xd8, 0x62, 0x0d, 0x3c, 0x95, 0xa3, 0x94, 0x0f, 0x60, 0x83, + 0x52, 0x5a, 0xd8, 0x1e, 0x0f, 0x1c, 0xbe, 0x5e, 0x88, 0x6d, 0x0e, 0x79, 0xa1, 0xb0, 0x7e, 0xea, + 0x0b, 0xfe, 0x31, 0x68, 0x48, 0xe1, 0xb0, 0x8d, 0x9b, 0x89, 0xe0, 0x9b, 0xc9, 0x29, 0x6c, 0xf1, + 0xcd, 0xed, 0x86, 0x2c, 0x85, 0xa5, 0x4f, 0xb7, 0xe6, 0xbd, 0xe1, 0xac, 0x85, 0x20, 0x97, 0x7d, + 0x05, 0x23, 0x49, 0x3d, 0x9b, 0x91, 0x20, 0x48, 0xd3, 0x79, 0xa7, 0xd9, 0x09, 0x41, 0x9e, 0xbf, + 0xc9, 0x86, 0x03, 0x4b, 0x0d, 0xa7, 0xb0, 0xa2, 0xe1, 0x14, 0x97, 0x1a, 0x4e, 0x69, 0x99, 0xe1, + 0x94, 0x57, 0x33, 0x9c, 0xca, 0xda, 0x86, 0x23, 0x7d, 0x5d, 0xc3, 0xd9, 0x58, 0xd3, 0x70, 0xd0, + 0xea, 0x86, 0xb3, 0x19, 0x6d, 0x38, 0xef, 0xc3, 0xc6, 0x5c, 0xc2, 0xe2, 0x29, 0x9d, 0x10, 0xa9, + 0x74, 0xc9, 0xa0, 0xd2, 0xc9, 0x7f, 0x27, 0x40, 0x2d, 0x3e, 0x43, 0x89, 0x14, 0xf5, 0x3a, 0x6c, + 0x78, 0xdb, 0xeb, 0x29, 0x0f, 0x3b, 0x2f, 0x25, 0xef, 0x85, 0xab, 0x3d, 0x71, 0xa1, 0xcf, 0x3d, + 0x28, 0xcf, 0xe4, 0x4f, 0xcc, 0x44, 0x4a, 0x93, 0xe0, 0xf8, 0xf2, 0x3f, 0x64, 0xbd, 0x78, 0x24, + 0x94, 0xe4, 0x44, 0xb8, 0x85, 0x8f, 0x60, 0xb3, 0x8b, 0x3b, 0x7a, 0xf7, 0xeb, 0x7a, 0x85, 0x0d, + 0xce, 0xfd, 0xad, 0x53, 0xf8, 0xd6, 0x29, 0x7c, 0xb3, 0x9d, 0xc2, 0x5f, 0x25, 0x3d, 0xaf, 0xe0, + 0x27, 0xf3, 0x91, 0xa6, 0xfc, 0x36, 0xd1, 0x3a, 0x8d, 0x04, 0xb6, 0xcc, 0x4c, 0xaa, 0xf3, 0xb9, + 0x5a, 0x8b, 0xbe, 0xe7, 0xea, 0xcc, 0xa9, 0xd1, 0x71, 0xf8, 0xbb, 0x03, 0x38, 0xe4, 0x3c, 0xa8, + 0xe7, 0xdb, 0x53, 0xc0, 0xd8, 0x02, 0xd3, 0xa3, 0xb9, 0xb2, 0xb2, 0x30, 0x46, 0x9d, 0x4f, 0x35, + 0x9a, 0x7c, 0x7f, 0x17, 0x98, 0x99, 0xdc, 0xf4, 0x72, 0x5f, 0x0f, 0x8c, 0x98, 0xcb, 0xa4, 0x5e, + 0x82, 0xa2, 0xad, 0xf7, 0x54, 0x8a, 0xc2, 0xe8, 0x98, 0x65, 0xb5, 0x79, 0xa5, 0x60, 0xeb, 0xbd, + 0x73, 0xde, 0x25, 0xbf, 0x06, 0x95, 0x19, 0x40, 0x62, 0x26, 0x3d, 0xf1, 0x9d, 0xe9, 0xa6, 0xb7, + 0xee, 0x3e, 0xf0, 0x20, 0xff, 0xbc, 0x08, 0x79, 0x05, 0xdb, 0x26, 0x51, 0x6a, 0xb4, 0x07, 0x22, + 0x9e, 0x76, 0xb0, 0xe9, 0xb8, 0xa8, 0x40, 0x34, 0x78, 0xc1, 0xa8, 0x9b, 0x2e, 0x65, 0x2b, 0xa1, + 0xf8, 0x6c, 0xe8, 0x4d, 0x8e, 0x31, 0xc7, 0xc3, 0xc5, 0x9c, 0x3d, 0x08, 0x32, 0xbf, 0xed, 0x82, + 0xcc, 0xa9, 0x58, 0xfc, 0x94, 0x71, 0xcd, 0xa0, 0xcc, 0x6f, 0x72, 0x94, 0x39, 0xbd, 0x64, 0xb0, + 0x10, 0xcc, 0xdc, 0x08, 0xc1, 0xcc, 0x99, 0x25, 0xd3, 0x8c, 0xc1, 0x99, 0xdf, 0x76, 0x71, 0xe6, + 0xec, 0x92, 0x2f, 0x9e, 0x01, 0x9a, 0x7f, 0x10, 0x00, 0x9a, 0xf3, 0xb1, 0x08, 0x13, 0x63, 0x8d, + 0x40, 0x9a, 0xdf, 0xf1, 0x90, 0xe6, 0x42, 0x2c, 0x4a, 0xcd, 0x99, 0x67, 0xa1, 0xe6, 0xe3, 0x39, + 0xa8, 0x99, 0x41, 0xc3, 0xaf, 0xc4, 0x8a, 0x58, 0x82, 0x35, 0x1f, 0xcf, 0x61, 0xcd, 0xa5, 0x25, + 0x02, 0x97, 0x80, 0xcd, 0x7f, 0x16, 0x0d, 0x36, 0xc7, 0xc3, 0xc1, 0xfc, 0x33, 0x57, 0x43, 0x9b, + 0xd5, 0x18, 0xb4, 0xb9, 0x12, 0x8b, 0x8c, 0x32, 0xf1, 0x2b, 0xc3, 0xcd, 0x67, 0x11, 0x70, 0x33, + 0x03, 0x86, 0xef, 0xc7, 0x0a, 0x5f, 0x01, 0x6f, 0x3e, 0x8b, 0xc0, 0x9b, 0x37, 0x96, 0x8a, 0x5d, + 0x0a, 0x38, 0xef, 0x87, 0x01, 0x67, 0x14, 0x93, 0xc8, 0xfb, 0xd6, 0x1e, 0x83, 0x38, 0x5f, 0xc4, + 0x21, 0xce, 0x0c, 0x15, 0x7e, 0x23, 0x56, 0xe2, 0x1a, 0x90, 0xf3, 0xf1, 0x1c, 0xe4, 0xbc, 0xb5, + 0x44, 0xd3, 0x96, 0x60, 0xce, 0xfb, 0x61, 0xcc, 0xf9, 0xda, 0x92, 0xc9, 0xc7, 0x82, 0xce, 0x8d, + 0x10, 0xe8, 0x7c, 0x7d, 0x89, 0x2b, 0x89, 0x41, 0x9d, 0xff, 0x28, 0x88, 0x3a, 0xdf, 0x88, 0x05, + 0xae, 0xf9, 0x3e, 0x44, 0xc1, 0xce, 0xfb, 0x61, 0xd8, 0xb9, 0xba, 0x64, 0x3a, 0xab, 0xe0, 0xce, + 0x39, 0x29, 0xcf, 0x10, 0xe7, 0xc7, 0xe9, 0x3c, 0x48, 0x05, 0xf9, 0x35, 0x72, 0x78, 0xcc, 0x38, + 0x7e, 0xb4, 0x05, 0x19, 0x6c, 0x59, 0x86, 0xc5, 0x11, 0x64, 0xd6, 0x90, 0xef, 0x43, 0x31, 0xe8, + 0xe4, 0x17, 0x60, 0xd4, 0x14, 0xf7, 0x0b, 0x38, 0x76, 0xf9, 0x7f, 0x04, 0x9f, 0x97, 0x9e, 0xbc, + 0x41, 0x0c, 0x53, 0xe4, 0x18, 0x66, 0x00, 0xb9, 0x4e, 0x86, 0x91, 0xeb, 0x3b, 0x50, 0x20, 0x71, + 0xdf, 0x0c, 0x28, 0xad, 0x99, 0x1e, 0x28, 0xed, 0xc6, 0x29, 0x3c, 0xd6, 0x62, 0xa7, 0x64, 0x9a, + 0x9e, 0x92, 0x15, 0x3f, 0xda, 0x62, 0xb1, 0xed, 0x77, 0x60, 0x33, 0x40, 0xeb, 0xc5, 0x93, 0x0c, + 0xa1, 0x95, 0x3c, 0xea, 0x3a, 0x0f, 0x2c, 0xdf, 0x00, 0x34, 0xd4, 0x47, 0xfa, 0x70, 0x3c, 0x54, + 0x7b, 0x1a, 0x31, 0x55, 0xbd, 0x83, 0x6d, 0x7a, 0x3a, 0x88, 0x8a, 0xc4, 0xdf, 0x7c, 0xa0, 0xd9, + 0x27, 0xb4, 0x5f, 0xfe, 0x57, 0xc1, 0x5f, 0x4f, 0x1f, 0xfb, 0x8e, 0x82, 0xa9, 0x85, 0xe7, 0x04, + 0x53, 0x27, 0xbf, 0x36, 0x4c, 0x1d, 0x8c, 0xa6, 0x53, 0x61, 0x94, 0xf4, 0x7f, 0x05, 0x7f, 0x07, + 0x3d, 0xd0, 0xb9, 0x63, 0x74, 0x31, 0xc7, 0x2d, 0xe9, 0x33, 0xc9, 0x86, 0x06, 0x46, 0x8f, 0xa3, + 0x93, 0xe4, 0x91, 0x50, 0x79, 0xe7, 0xb6, 0xc8, 0x8f, 0x65, 0x0f, 0xf2, 0x64, 0x29, 0x06, 0x87, + 0x3c, 0x25, 0x48, 0x3d, 0xc5, 0xec, 0x94, 0x2d, 0x2a, 0xe4, 0x91, 0xd0, 0x51, 0x25, 0xe5, 0xa9, + 0x02, 0x6b, 0xa0, 0x87, 0x20, 0xd2, 0x3a, 0xbc, 0x6a, 0x98, 0x36, 0x3f, 0x58, 0x43, 0x59, 0x15, + 0x2b, 0xc6, 0x6f, 0x9f, 0x10, 0x9a, 0x63, 0xd3, 0x56, 0xf2, 0x26, 0x7f, 0x0a, 0x84, 0x4b, 0x62, + 0x28, 0xb7, 0x79, 0x01, 0x44, 0xf2, 0xf5, 0xb6, 0xa9, 0x75, 0x30, 0xcd, 0x22, 0x44, 0xc5, 0xef, + 0x90, 0x3f, 0x05, 0x34, 0xef, 0x1d, 0x50, 0x0b, 0xb2, 0x78, 0x82, 0x47, 0x0e, 0x4b, 0xfd, 0x0a, + 0xbb, 0xd7, 0x23, 0x42, 0x43, 0x3c, 0x72, 0xf6, 0xaa, 0x64, 0x91, 0x7f, 0xf3, 0xeb, 0x3b, 0x12, + 0xa3, 0x7e, 0xc3, 0x18, 0xea, 0x0e, 0x1e, 0x9a, 0xce, 0x95, 0xc2, 0xf9, 0xe5, 0x7f, 0x17, 0x48, + 0x60, 0x17, 0x8a, 0x03, 0x22, 0xd7, 0xd6, 0x35, 0x90, 0x64, 0x00, 0xe4, 0x9f, 0x5f, 0xef, 0x17, + 0x01, 0x88, 0x52, 0x7e, 0xa6, 0x8d, 0x1c, 0xdc, 0xe5, 0x0b, 0x2c, 0xf6, 0x34, 0xfb, 0x63, 0xda, + 0x11, 0x9e, 0x6a, 0x7e, 0x66, 0xaa, 0x01, 0x7c, 0x59, 0x0c, 0xe2, 0xcb, 0xa8, 0x06, 0x79, 0xd3, + 0xd2, 0x0d, 0x4b, 0x77, 0xae, 0xe8, 0xfa, 0xa4, 0x14, 0xaf, 0xfd, 0x38, 0x9d, 0x4f, 0x4b, 0x19, + 0xaf, 0x7c, 0xc5, 0x9c, 0x49, 0x41, 0x2a, 0xca, 0xff, 0x92, 0xf4, 0xf5, 0xde, 0x8f, 0x7b, 0xbf, + 0xfe, 0xc4, 0xa2, 0x14, 0xe9, 0x76, 0xc4, 0x64, 0x03, 0x3d, 0xe4, 0xbb, 0x49, 0x6b, 0x6c, 0xe3, + 0x2e, 0x2f, 0xa4, 0x78, 0xed, 0xc0, 0x06, 0xe6, 0x9e, 0x6d, 0x03, 0x97, 0xac, 0xe9, 0xf7, 0xa1, + 0x80, 0x27, 0x43, 0xd5, 0x99, 0xb2, 0xec, 0x44, 0xe4, 0xf9, 0xf3, 0xfc, 0x60, 0xc3, 0xf6, 0x94, + 0x38, 0x42, 0x45, 0xc4, 0xee, 0xa3, 0xfc, 0x37, 0xb4, 0x6c, 0x16, 0x3e, 0x0e, 0xd0, 0x69, 0x10, + 0xf6, 0x18, 0x53, 0x53, 0x76, 0x95, 0x70, 0x55, 0x9b, 0xf7, 0xe1, 0x11, 0xd6, 0x6d, 0xa3, 0x3f, + 0x86, 0x1b, 0x33, 0xfe, 0xc8, 0x13, 0x9d, 0x8c, 0x89, 0x5d, 0x67, 0xbd, 0xd2, 0xb5, 0xb0, 0x57, + 0x72, 0x25, 0xfb, 0xeb, 0x9c, 0x7a, 0x46, 0x43, 0x79, 0x0b, 0xca, 0xe1, 0x90, 0x97, 0x64, 0xdd, + 0x16, 0x76, 0x34, 0x7d, 0xa4, 0x86, 0xb0, 0x9d, 0x22, 0xeb, 0xe4, 0xc5, 0xb2, 0x13, 0xb8, 0x16, + 0x19, 0xe6, 0xa2, 0xef, 0x81, 0xe8, 0x47, 0xc8, 0x42, 0x4c, 0x82, 0xe7, 0xd5, 0x92, 0x7c, 0x5a, + 0xf9, 0x9f, 0x05, 0x5f, 0x64, 0xb8, 0x3a, 0xd5, 0x84, 0x2c, 0x4b, 0x8c, 0xa9, 0x82, 0x97, 0x77, + 0xbf, 0xb3, 0x5a, 0x80, 0xbc, 0xcd, 0xb2, 0x66, 0x85, 0x33, 0xcb, 0x9f, 0x42, 0x96, 0xf5, 0xa0, + 0x02, 0xe4, 0xce, 0x8e, 0x9e, 0x1c, 0x1d, 0x7f, 0x7c, 0x24, 0x25, 0x10, 0x40, 0xb6, 0xde, 0x68, + 0x34, 0x4f, 0xda, 0x92, 0x80, 0x44, 0xc8, 0xd4, 0xf7, 0x8e, 0x95, 0xb6, 0x94, 0x24, 0xdd, 0x4a, + 0xf3, 0x71, 0xb3, 0xd1, 0x96, 0x52, 0x68, 0x03, 0x4a, 0xec, 0x59, 0xdd, 0x3f, 0x56, 0x3e, 0xac, + 0xb7, 0xa5, 0x74, 0xa0, 0xeb, 0xb4, 0x79, 0xf4, 0xa8, 0xa9, 0x48, 0x19, 0xf9, 0xf7, 0xe1, 0x66, + 0x6c, 0x48, 0xed, 0x97, 0x9e, 0x84, 0x40, 0xe9, 0x49, 0xfe, 0xdb, 0x24, 0xd4, 0xe2, 0xe3, 0x64, + 0xf4, 0x78, 0x66, 0xe2, 0xbb, 0x6b, 0x04, 0xd9, 0x33, 0xb3, 0x47, 0xf7, 0xa0, 0x6c, 0xe1, 0x4b, + 0xec, 0x74, 0xfa, 0x2c, 0x6e, 0x67, 0x27, 0x5a, 0x49, 0x29, 0xf1, 0x5e, 0xca, 0x64, 0x33, 0xb2, + 0x1f, 0xe3, 0x8e, 0xa3, 0x32, 0x2f, 0xc5, 0x14, 0x4c, 0x24, 0x64, 0xa4, 0xf7, 0x94, 0x75, 0xca, + 0x3f, 0x5a, 0x6b, 0x2d, 0x45, 0xc8, 0x28, 0xcd, 0xb6, 0xf2, 0x89, 0x94, 0x42, 0x08, 0xca, 0xf4, + 0x51, 0x3d, 0x3d, 0xaa, 0x9f, 0x9c, 0xb6, 0x8e, 0xc9, 0x5a, 0x6e, 0x42, 0xc5, 0x5d, 0x4b, 0xb7, + 0x33, 0x23, 0xff, 0x47, 0x12, 0x6e, 0xc4, 0x44, 0xf9, 0xe8, 0x21, 0x80, 0x33, 0x55, 0x2d, 0xdc, + 0x31, 0xac, 0x6e, 0xbc, 0x92, 0xb5, 0xa7, 0x0a, 0xa5, 0x50, 0x44, 0x87, 0x3f, 0xd9, 0x0b, 0x2a, + 0x96, 0xe8, 0x3d, 0x2e, 0x94, 0x22, 0x2d, 0xdc, 0xac, 0x5e, 0x8c, 0x28, 0xcc, 0xe1, 0x0e, 0x11, + 0x4c, 0xd7, 0x96, 0x0a, 0xa6, 0xf4, 0xe8, 0xc3, 0x28, 0xff, 0xb1, 0x62, 0x69, 0x3b, 0xc2, 0x73, + 0x7c, 0x12, 0xef, 0x39, 0x32, 0xab, 0x06, 0x34, 0xd1, 0xae, 0x43, 0xfe, 0xfb, 0x54, 0x70, 0x61, + 0xc3, 0x49, 0xcd, 0x31, 0x64, 0x6d, 0x47, 0x73, 0xc6, 0x36, 0x57, 0xb8, 0xef, 0xad, 0x9a, 0x21, + 0x6d, 0xbb, 0x0f, 0xa7, 0x94, 0x5d, 0xe1, 0x62, 0xbe, 0x5d, 0x6f, 0xea, 0x60, 0xc3, 0x8b, 0x13, + 0x6f, 0x32, 0xbe, 0xcf, 0x49, 0xca, 0xef, 0xfa, 0x01, 0x52, 0x00, 0xfc, 0x9f, 0x07, 0xd6, 0x85, + 0x28, 0x60, 0xfd, 0xe7, 0x02, 0xdc, 0x5a, 0x90, 0x27, 0xa2, 0x8f, 0x66, 0xf6, 0xf9, 0x9d, 0x75, + 0xb2, 0xcc, 0x6d, 0xd6, 0x17, 0xde, 0x69, 0xf9, 0x4d, 0x28, 0x06, 0xfb, 0x57, 0x9b, 0xe4, 0x6f, + 0x92, 0xbe, 0xcf, 0x0f, 0x57, 0x00, 0x9e, 0x5b, 0x24, 0x38, 0xa3, 0x67, 0xc9, 0x35, 0xf5, 0x2c, + 0x32, 0x2e, 0x48, 0x3d, 0x63, 0x5c, 0xb0, 0x40, 0xdb, 0xd2, 0xcf, 0xa6, 0x6d, 0x21, 0x83, 0xcb, + 0x84, 0x93, 0x8d, 0x2d, 0x5f, 0xa3, 0x02, 0x00, 0xe6, 0x27, 0x00, 0x01, 0xa4, 0x76, 0x0b, 0x32, + 0x96, 0x31, 0x1e, 0x75, 0xa9, 0x5e, 0x64, 0x14, 0xd6, 0x40, 0x6f, 0x41, 0x86, 0xe8, 0x97, 0xbb, + 0x7a, 0xf3, 0xae, 0x96, 0xe8, 0x47, 0x00, 0xff, 0x65, 0xd4, 0xf2, 0x0f, 0xa1, 0x1c, 0x86, 0x87, + 0x9f, 0xaf, 0x78, 0x1d, 0xd0, 0xfc, 0x35, 0x89, 0x98, 0x21, 0x7e, 0x10, 0x1e, 0xe2, 0xa5, 0xd8, + 0x0b, 0x17, 0xd1, 0x43, 0x7d, 0x0e, 0x19, 0xaa, 0x6e, 0x24, 0x5e, 0xa6, 0x77, 0x73, 0x78, 0x3e, + 0x4d, 0x9e, 0xd1, 0x0f, 0x01, 0x34, 0xc7, 0xb1, 0xf4, 0x8b, 0xb1, 0x3f, 0xc0, 0x9d, 0x68, 0x75, + 0xad, 0xbb, 0x74, 0x7b, 0x2f, 0x70, 0xbd, 0xdd, 0xf2, 0x59, 0x03, 0xba, 0x1b, 0x10, 0x28, 0x1f, + 0x41, 0x39, 0xcc, 0xeb, 0xe6, 0x74, 0x42, 0x44, 0x4e, 0x97, 0x0c, 0xe6, 0x74, 0x5e, 0x46, 0x98, + 0x62, 0x17, 0x90, 0x68, 0x43, 0xfe, 0xa7, 0x24, 0x14, 0x83, 0xda, 0xfe, 0x9c, 0xb3, 0x87, 0x25, + 0xa9, 0xd2, 0xcd, 0xb9, 0xe4, 0x21, 0xd7, 0xd3, 0xec, 0xb3, 0x6f, 0x4a, 0xee, 0xf0, 0xd7, 0x02, + 0xe4, 0xbd, 0x85, 0x8b, 0x29, 0x14, 0xf8, 0xeb, 0x9e, 0x0c, 0x5e, 0x3e, 0x62, 0xc5, 0x89, 0x94, + 0x57, 0x9c, 0x78, 0xd7, 0x0b, 0xee, 0xe2, 0xd0, 0xf7, 0xe0, 0x2e, 0xb9, 0x25, 0x1a, 0x1e, 0xcb, + 0x5a, 0xec, 0x33, 0x48, 0x50, 0x83, 0xbe, 0x0f, 0x59, 0xad, 0xe3, 0x95, 0x1c, 0xca, 0x11, 0x00, + 0x9a, 0x4b, 0xba, 0xdd, 0x9e, 0xd6, 0x29, 0xa5, 0xc2, 0x39, 0xf8, 0x47, 0x25, 0xdd, 0x8f, 0x92, + 0x6b, 0x44, 0x6e, 0xdd, 0x7d, 0x07, 0x67, 0x47, 0x1f, 0x1e, 0x3f, 0x3a, 0xd8, 0x3f, 0x68, 0x3e, + 0x92, 0x12, 0x72, 0x03, 0x0a, 0x6e, 0x89, 0x4b, 0x1b, 0xda, 0xe8, 0x16, 0x88, 0x43, 0x2d, 0x7c, + 0x01, 0x2a, 0x3f, 0xd4, 0xf8, 0xf5, 0xa7, 0x1b, 0x90, 0x23, 0x2f, 0x7b, 0x9a, 0xed, 0x56, 0xa4, + 0x87, 0xda, 0xf4, 0x03, 0xcd, 0x96, 0x7f, 0x2b, 0x40, 0x65, 0xc6, 0x95, 0xa1, 0x5d, 0xc8, 0x30, + 0xf0, 0x2e, 0xee, 0x5e, 0x7d, 0x60, 0x58, 0x85, 0x91, 0xa2, 0xf7, 0x20, 0xef, 0x56, 0x01, 0xa3, + 0x72, 0x29, 0xe6, 0x33, 0xdd, 0x3a, 0x12, 0x67, 0xf5, 0x38, 0xd0, 0xfb, 0x20, 0x7a, 0x4e, 0x39, + 0xfe, 0x22, 0xa3, 0xe7, 0xce, 0x39, 0xbf, 0xcf, 0x83, 0xde, 0xf1, 0x31, 0xb4, 0xf4, 0x7c, 0x21, + 0x81, 0xb3, 0x33, 0x02, 0xce, 0xec, 0xd2, 0xcb, 0xef, 0x82, 0xe8, 0x09, 0x46, 0x55, 0xc8, 0xb9, + 0xb5, 0x58, 0x81, 0x7b, 0x6b, 0x5e, 0x83, 0xdd, 0x82, 0x8c, 0x69, 0x7c, 0xc6, 0x2f, 0xa5, 0xa5, + 0x14, 0xd6, 0x90, 0xbb, 0x50, 0x99, 0x39, 0x64, 0xd0, 0xbb, 0x90, 0x33, 0xc7, 0x17, 0xaa, 0xeb, + 0x11, 0x66, 0xd6, 0xcf, 0xc5, 0x6d, 0xc6, 0x17, 0x03, 0xbd, 0xf3, 0x04, 0x5f, 0xb9, 0x7a, 0x64, + 0x8e, 0x2f, 0x9e, 0x30, 0xc7, 0xc1, 0x46, 0x49, 0x06, 0x47, 0x99, 0x40, 0xde, 0xf5, 0x83, 0xe8, + 0x0f, 0x83, 0x4b, 0x25, 0xc4, 0xd8, 0x8a, 0xf7, 0x4d, 0x5c, 0x7c, 0x60, 0xa5, 0x1e, 0xc0, 0x86, + 0xad, 0xf7, 0x46, 0x6e, 0xdd, 0x9e, 0x6d, 0x34, 0x2b, 0xc4, 0x55, 0xd8, 0x8b, 0x43, 0x17, 0x0a, + 0x24, 0x61, 0x8b, 0x34, 0xeb, 0x88, 0xff, 0x3f, 0x3f, 0x20, 0x22, 0xbc, 0x4a, 0x45, 0x85, 0x57, + 0x7f, 0x99, 0x84, 0x42, 0xe0, 0x36, 0x00, 0xfa, 0x83, 0xc0, 0xa9, 0x50, 0x8e, 0x88, 0x0b, 0x02, + 0xb4, 0xfe, 0xad, 0xcd, 0xf0, 0xc4, 0x92, 0xeb, 0x4f, 0x2c, 0xee, 0xf2, 0x85, 0x7b, 0xa9, 0x20, + 0xbd, 0xf6, 0xa5, 0x82, 0x37, 0x00, 0xd1, 0x72, 0xb8, 0x3a, 0x31, 0x1c, 0x7d, 0xd4, 0x53, 0x99, + 0x6a, 0x30, 0x1f, 0x2e, 0xd1, 0x37, 0xe7, 0xf4, 0xc5, 0x09, 0xd5, 0x92, 0x3f, 0x4f, 0x42, 0xde, + 0xb5, 0xb0, 0xdf, 0xd1, 0x25, 0x50, 0x41, 0xf4, 0x8e, 0x09, 0xf4, 0x7b, 0x50, 0x62, 0x39, 0x73, + 0x3d, 0x60, 0xd1, 0xa2, 0x12, 0xee, 0x24, 0x16, 0x37, 0x32, 0x5c, 0xaf, 0x95, 0x56, 0x58, 0x83, + 0x4c, 0xc4, 0x99, 0xb6, 0x5c, 0x84, 0x58, 0x54, 0x78, 0x4b, 0xfe, 0x0b, 0x01, 0xf2, 0x1e, 0x0e, + 0xb2, 0xee, 0xbd, 0xd9, 0xeb, 0x90, 0xe5, 0xa9, 0x3e, 0xbb, 0x38, 0xcb, 0x5b, 0x91, 0x37, 0x54, + 0x6a, 0x90, 0x1f, 0x62, 0x47, 0xa3, 0x87, 0x3e, 0x8b, 0x1b, 0xbd, 0xf6, 0x83, 0x77, 0xa0, 0x10, + 0xb8, 0x73, 0x4c, 0xe2, 0x80, 0xa3, 0xe6, 0xc7, 0x52, 0xa2, 0x96, 0xfb, 0xe9, 0xcf, 0xee, 0xa6, + 0x8e, 0xf0, 0x67, 0xc4, 0x8b, 0x29, 0xcd, 0x46, 0xab, 0xd9, 0x78, 0x22, 0x09, 0xb5, 0xc2, 0x4f, + 0x7f, 0x76, 0x37, 0xa7, 0x60, 0x5a, 0x71, 0x7d, 0xf0, 0x04, 0x2a, 0x33, 0x3b, 0x1f, 0x4e, 0x0c, + 0x10, 0x94, 0x1f, 0x9d, 0x9d, 0x1c, 0x1e, 0x34, 0xea, 0xed, 0xa6, 0x7a, 0x7e, 0xdc, 0x6e, 0x4a, + 0x02, 0xba, 0x01, 0x9b, 0x87, 0x07, 0x1f, 0xb4, 0xda, 0x6a, 0xe3, 0xf0, 0xa0, 0x79, 0xd4, 0x56, + 0xeb, 0xed, 0x76, 0xbd, 0xf1, 0x44, 0x4a, 0xee, 0xfe, 0xb6, 0x00, 0x95, 0xfa, 0x5e, 0xe3, 0xa0, + 0x6e, 0x9a, 0x03, 0xbd, 0xa3, 0xd1, 0x43, 0xaa, 0x01, 0x69, 0x5a, 0x24, 0x59, 0xf8, 0x33, 0x56, + 0x6d, 0x71, 0x19, 0x1d, 0xed, 0x43, 0x86, 0xd6, 0x4f, 0xd0, 0xe2, 0xbf, 0xb3, 0x6a, 0x4b, 0xea, + 0xea, 0xe4, 0x63, 0xa8, 0x26, 0x2c, 0xfc, 0x5d, 0xab, 0xb6, 0xb8, 0xcc, 0x8e, 0x0e, 0x21, 0xe7, + 0x02, 0xd6, 0xcb, 0xfe, 0xa1, 0xaa, 0x2d, 0xad, 0x7d, 0x93, 0xa9, 0xb1, 0xc2, 0xc2, 0xe2, 0x3f, + 0xb9, 0x6a, 0x4b, 0x0a, 0xf0, 0xe8, 0x00, 0xb2, 0x1c, 0x1e, 0x5c, 0xf2, 0x73, 0x56, 0x6d, 0x59, + 0x49, 0x1d, 0x29, 0x20, 0xfa, 0x25, 0x9b, 0xe5, 0xff, 0xa7, 0xd5, 0x56, 0xb8, 0x5b, 0x80, 0x3e, + 0x85, 0x52, 0x18, 0x86, 0x5c, 0xed, 0x07, 0xb0, 0xda, 0x8a, 0xc5, 0x7b, 0x22, 0x3f, 0x8c, 0x49, + 0xae, 0xf6, 0x43, 0x58, 0x6d, 0xc5, 0x5a, 0x3e, 0xfa, 0x31, 0x6c, 0xcc, 0x63, 0x86, 0xab, 0xff, + 0x1f, 0x56, 0x5b, 0xa3, 0xba, 0x8f, 0x86, 0x80, 0x22, 0xb0, 0xc6, 0x35, 0x7e, 0x17, 0xab, 0xad, + 0x53, 0xec, 0x47, 0x5d, 0xa8, 0xcc, 0xe2, 0x77, 0xab, 0xfe, 0x3e, 0x56, 0x5b, 0xb9, 0xf0, 0xcf, + 0x46, 0x09, 0x83, 0x59, 0xab, 0xfe, 0x4e, 0x56, 0x5b, 0xf9, 0x1e, 0x00, 0x3a, 0x03, 0x08, 0x80, + 0x31, 0x2b, 0xfc, 0x5e, 0x56, 0x5b, 0xe5, 0x46, 0x00, 0x32, 0x61, 0x33, 0x0a, 0xa5, 0x59, 0xe7, + 0x6f, 0xb3, 0xda, 0x5a, 0x17, 0x05, 0x88, 0x3e, 0x87, 0xf1, 0x96, 0xd5, 0xfe, 0x3e, 0xab, 0xad, + 0x78, 0x63, 0x80, 0x2c, 0x94, 0x8f, 0x31, 0xa0, 0x15, 0xfe, 0xe0, 0xaa, 0xad, 0x52, 0x6e, 0xdf, + 0x6b, 0xfe, 0xe2, 0xcb, 0xdb, 0xc2, 0xaf, 0xbe, 0xbc, 0x2d, 0xfc, 0xf7, 0x97, 0xb7, 0x85, 0x2f, + 0xbe, 0xba, 0x9d, 0xf8, 0xd5, 0x57, 0xb7, 0x13, 0xff, 0xf9, 0xd5, 0xed, 0xc4, 0x9f, 0xbc, 0xde, + 0xd3, 0x9d, 0xfe, 0xf8, 0x62, 0xbb, 0x63, 0x0c, 0x77, 0x82, 0xbf, 0x01, 0x47, 0xfd, 0x9b, 0x7c, + 0x91, 0xa5, 0x81, 0xc0, 0x9b, 0xff, 0x17, 0x00, 0x00, 0xff, 0xff, 0x42, 0xc5, 0x78, 0x82, 0xbb, + 0x3c, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -8221,6 +8304,18 @@ func (m *ResponseDeliverTx) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.EvmTxInfo != nil { + { + size, err := m.EvmTxInfo.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTypes(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x4a + } if len(m.Codespace) > 0 { i -= len(m.Codespace) copy(dAtA[i:], m.Codespace) @@ -8497,20 +8592,20 @@ func (m *ResponseApplySnapshotChunk) MarshalToSizedBuffer(dAtA []byte) (int, err } } if len(m.RefetchChunks) > 0 { - dAtA57 := make([]byte, len(m.RefetchChunks)*10) - var j56 int + dAtA58 := make([]byte, len(m.RefetchChunks)*10) + var j57 int for _, num := range m.RefetchChunks { for num >= 1<<7 { - dAtA57[j56] = uint8(uint64(num)&0x7f | 0x80) + dAtA58[j57] = uint8(uint64(num)&0x7f | 0x80) num >>= 7 - j56++ + j57++ } - dAtA57[j56] = uint8(num) - j56++ + dAtA58[j57] = uint8(num) + j57++ } - i -= j56 - copy(dAtA[i:], dAtA57[:j56]) - i = encodeVarintTypes(dAtA, i, uint64(j56)) + i -= j57 + copy(dAtA[i:], dAtA58[:j57]) + i = encodeVarintTypes(dAtA, i, uint64(j57)) i-- dAtA[i] = 0x12 } @@ -9083,6 +9178,18 @@ func (m *ExecTxResult) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.EvmTxInfo != nil { + { + size, err := m.EvmTxInfo.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTypes(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x4a + } if len(m.Codespace) > 0 { i -= len(m.Codespace) copy(dAtA[i:], m.Codespace) @@ -9523,12 +9630,12 @@ func (m *Misbehavior) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x28 } - n69, err69 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Time, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Time):]) - if err69 != nil { - return 0, err69 + n71, err71 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Time, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Time):]) + if err71 != nil { + return 0, err71 } - i -= n69 - i = encodeVarintTypes(dAtA, i, uint64(n69)) + i -= n71 + i = encodeVarintTypes(dAtA, i, uint64(n71)) i-- dAtA[i] = 0x22 if m.Height != 0 { @@ -9579,12 +9686,12 @@ func (m *Evidence) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x28 } - n71, err71 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Time, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Time):]) - if err71 != nil { - return 0, err71 + n73, err73 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Time, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Time):]) + if err73 != nil { + return 0, err73 } - i -= n71 - i = encodeVarintTypes(dAtA, i, uint64(n71)) + i -= n73 + i = encodeVarintTypes(dAtA, i, uint64(n73)) i-- dAtA[i] = 0x22 if m.Height != 0 { @@ -9610,6 +9717,48 @@ func (m *Evidence) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *EvmTxInfo) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *EvmTxInfo) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *EvmTxInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.TxHash) > 0 { + i -= len(m.TxHash) + copy(dAtA[i:], m.TxHash) + i = encodeVarintTypes(dAtA, i, uint64(len(m.TxHash))) + i-- + dAtA[i] = 0x1a + } + if m.Nonce != 0 { + i = encodeVarintTypes(dAtA, i, uint64(m.Nonce)) + i-- + dAtA[i] = 0x10 + } + if len(m.SenderAddress) > 0 { + i -= len(m.SenderAddress) + copy(dAtA[i:], m.SenderAddress) + i = encodeVarintTypes(dAtA, i, uint64(len(m.SenderAddress))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func (m *Snapshot) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -10931,6 +11080,10 @@ func (m *ResponseDeliverTx) Size() (n int) { if l > 0 { n += 1 + l + sovTypes(uint64(l)) } + if m.EvmTxInfo != nil { + l = m.EvmTxInfo.Size() + n += 1 + l + sovTypes(uint64(l)) + } return n } @@ -11302,6 +11455,10 @@ func (m *ExecTxResult) Size() (n int) { if l > 0 { n += 1 + l + sovTypes(uint64(l)) } + if m.EvmTxInfo != nil { + l = m.EvmTxInfo.Size() + n += 1 + l + sovTypes(uint64(l)) + } return n } @@ -11488,6 +11645,26 @@ func (m *Evidence) Size() (n int) { return n } +func (m *EvmTxInfo) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.SenderAddress) + if l > 0 { + n += 1 + l + sovTypes(uint64(l)) + } + if m.Nonce != 0 { + n += 1 + sovTypes(uint64(m.Nonce)) + } + l = len(m.TxHash) + if l > 0 { + n += 1 + l + sovTypes(uint64(l)) + } + return n +} + func (m *Snapshot) Size() (n int) { if m == nil { return 0 @@ -18311,6 +18488,42 @@ func (m *ResponseDeliverTx) Unmarshal(dAtA []byte) error { } m.Codespace = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 9: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field EvmTxInfo", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.EvmTxInfo == nil { + m.EvmTxInfo = &EvmTxInfo{} + } + if err := m.EvmTxInfo.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipTypes(dAtA[iNdEx:]) @@ -20636,6 +20849,42 @@ func (m *ExecTxResult) Unmarshal(dAtA []byte) error { } m.Codespace = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 9: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field EvmTxInfo", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.EvmTxInfo == nil { + m.EvmTxInfo = &EvmTxInfo{} + } + if err := m.EvmTxInfo.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipTypes(dAtA[iNdEx:]) @@ -21988,6 +22237,139 @@ func (m *Evidence) Unmarshal(dAtA []byte) error { } return nil } +func (m *EvmTxInfo) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: EvmTxInfo: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: EvmTxInfo: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SenderAddress", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SenderAddress = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Nonce", wireType) + } + m.Nonce = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Nonce |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TxHash", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.TxHash = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTypes(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTypes + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *Snapshot) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/config/config.go b/config/config.go index ba401a784..27085797e 100644 --- a/config/config.go +++ b/config/config.go @@ -800,6 +800,16 @@ type MempoolConfig struct { // blacklist the peer. CheckTxErrorBlacklistEnabled bool `mapstructure:"check-tx-error-blacklist-enabled"` CheckTxErrorThreshold int `mapstructure:"check-tx-error-threshold"` + + // Maximum number of transactions in the pending set + PendingSize int `mapstructure:"pending-size"` + + // Limit the total size of all txs in the pending set. + MaxPendingTxsBytes int64 `mapstructure:"max-pending-txs-bytes"` + + PendingTTLDuration time.Duration `mapstructure:"pending-ttl-duration"` + + PendingTTLNumBlocks int64 `mapstructure:"pending-ttl-num-blocks"` } // DefaultMempoolConfig returns a default configuration for the Tendermint mempool. @@ -817,6 +827,10 @@ func DefaultMempoolConfig() *MempoolConfig { TxNotifyThreshold: 0, CheckTxErrorBlacklistEnabled: false, CheckTxErrorThreshold: 0, + PendingSize: 5000, + MaxPendingTxsBytes: 1024 * 1024 * 1024, // 1GB + PendingTTLDuration: 0 * time.Second, + PendingTTLNumBlocks: 0, } } diff --git a/config/toml.go b/config/toml.go index 4e6c7577d..ae0667010 100644 --- a/config/toml.go +++ b/config/toml.go @@ -405,6 +405,14 @@ check-tx-error-blacklist-enabled = {{ .Mempool.CheckTxErrorBlacklistEnabled }} check-tx-error-threshold = {{ .Mempool.CheckTxErrorThreshold }} +pending-size = {{ .Mempool.PendingSize }} + +max-pending-txs-bytes = {{ .Mempool.MaxPendingTxsBytes }} + +pending-ttl-duration = "{{ .Mempool.PendingTTLDuration }}" + +pending-ttl-num-blocks = {{ .Mempool.PendingTTLNumBlocks }} + ####################################################### ### State Sync Configuration Options ### ####################################################### diff --git a/internal/consensus/mempool_test.go b/internal/consensus/mempool_test.go index e634d4b22..6ef3849ad 100644 --- a/internal/consensus/mempool_test.go +++ b/internal/consensus/mempool_test.go @@ -139,7 +139,7 @@ func checkTxsRange(ctx context.Context, t *testing.T, cs *State, start, end int) var rCode uint32 err := assertMempool(t, cs.txNotifier).CheckTx(ctx, txBytes, func(r *abci.ResponseCheckTx) { rCode = r.Code }, mempool.TxInfo{}) require.NoError(t, err, "error after checkTx") - require.Equal(t, code.CodeTypeOK, rCode, "checkTx code is error, txBytes %X", txBytes) + require.Equal(t, code.CodeTypeOK, rCode, "checkTx code is error, txBytes %X, index=%d", txBytes, i) } } @@ -166,7 +166,7 @@ func TestMempoolTxConcurrentWithCommit(t *testing.T) { require.NoError(t, err) newBlockHeaderCh := subscribe(ctx, t, cs.eventBus, types.EventQueryNewBlockHeader) - const numTxs int64 = 100 + const numTxs int64 = 50 go checkTxsRange(ctx, t, cs, 0, int(numTxs)) startTestRound(ctx, cs, cs.roundState.Height(), cs.roundState.Round()) @@ -308,18 +308,18 @@ func (app *CounterApplication) FinalizeBlock(_ context.Context, req *abci.Reques return res, nil } -func (app *CounterApplication) CheckTx(_ context.Context, req *abci.RequestCheckTx) (*abci.ResponseCheckTx, error) { +func (app *CounterApplication) CheckTx(_ context.Context, req *abci.RequestCheckTx) (*abci.ResponseCheckTxV2, error) { app.mu.Lock() defer app.mu.Unlock() txValue := txAsUint64(req.Tx) if txValue != uint64(app.mempoolTxCount) { - return &abci.ResponseCheckTx{ + return &abci.ResponseCheckTxV2{ResponseCheckTx: &abci.ResponseCheckTx{ Code: code.CodeTypeBadNonce, - }, nil + }}, nil } app.mempoolTxCount++ - return &abci.ResponseCheckTx{Code: code.CodeTypeOK}, nil + return &abci.ResponseCheckTxV2{ResponseCheckTx: &abci.ResponseCheckTx{Code: code.CodeTypeOK}}, nil } func txAsUint64(tx []byte) uint64 { @@ -331,7 +331,6 @@ func txAsUint64(tx []byte) uint64 { func (app *CounterApplication) Commit(context.Context) (*abci.ResponseCommit, error) { app.mu.Lock() defer app.mu.Unlock() - app.mempoolTxCount = app.txCount return &abci.ResponseCommit{}, nil } diff --git a/internal/consensus/reactor.go b/internal/consensus/reactor.go index 3631fd1d8..3ceadae90 100644 --- a/internal/consensus/reactor.go +++ b/internal/consensus/reactor.go @@ -8,6 +8,7 @@ import ( "sync" "time" + "github.com/tendermint/tendermint/config" cstypes "github.com/tendermint/tendermint/internal/consensus/types" "github.com/tendermint/tendermint/internal/eventbus" "github.com/tendermint/tendermint/internal/p2p" @@ -121,6 +122,7 @@ type ConsSyncReactor interface { type Reactor struct { service.BaseService logger log.Logger + cfg *config.Config state *State eventBus *eventbus.EventBus @@ -148,6 +150,7 @@ func NewReactor( eventBus *eventbus.EventBus, waitSync bool, metrics *Metrics, + cfg *config.Config, ) *Reactor { r := &Reactor{ logger: logger, @@ -160,6 +163,7 @@ func NewReactor( peerEvents: peerEvents, readySignal: make(chan struct{}), channels: &channelBundle{}, + cfg: cfg, } r.BaseService = *service.NewBaseService(logger, "Consensus", r) @@ -646,7 +650,12 @@ func (r *Reactor) pickSendVote(ctx context.Context, ps *PeerState, votes types.V return false, nil } - r.logger.Debug("sending vote message", "ps", ps, "vote", vote) + if r.cfg.BaseConfig.LogLevel == log.LogLevelDebug { + psJson, err := ps.ToJSON() // expensive, so we only want to call if debug is on + if err != nil { + r.logger.Debug("sending vote message", "ps", string(psJson), "vote", vote) + } + } if err := voteCh.Send(ctx, p2p.Envelope{ To: ps.peerID, Message: &tmcons.Vote{ diff --git a/internal/consensus/reactor_test.go b/internal/consensus/reactor_test.go index 8d840a14f..97927b235 100644 --- a/internal/consensus/reactor_test.go +++ b/internal/consensus/reactor_test.go @@ -97,6 +97,7 @@ func setup( state.eventBus, true, NopMetrics(), + config.DefaultConfig(), ) reactor.SetStateChannel(rts.stateChannels[nodeID]) @@ -697,6 +698,7 @@ func TestSwitchToConsensusVoteExtensions(t *testing.T) { cs.eventBus, true, NopMetrics(), + config.DefaultConfig(), ) if testCase.shouldPanic { diff --git a/internal/mempool/mempool.go b/internal/mempool/mempool.go index b47f69d29..90ef114c5 100644 --- a/internal/mempool/mempool.go +++ b/internal/mempool/mempool.go @@ -15,7 +15,6 @@ import ( "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/internal/libs/clist" "github.com/tendermint/tendermint/libs/log" - tmmath "github.com/tendermint/tendermint/libs/math" "github.com/tendermint/tendermint/types" ) @@ -44,6 +43,9 @@ type TxMempool struct { // sizeBytes defines the total size of the mempool (sum of all tx bytes) sizeBytes int64 + // pendingSizeBytes defines the total size of the pending set (sum of all tx bytes) + pendingSizeBytes int64 + // cache defines a fixed-size cache of already seen transactions as this // reduces pressure on the proxyApp. cache TxCache @@ -82,6 +84,10 @@ type TxMempool struct { // index. i.e. older transactions are first. timestampIndex *WrappedTxList + // pendingTxs stores transactions that are not valid yet but might become valid + // if its checker returns Accepted + pendingTxs *PendingTxs + // A read/write lock is used to safe guard updates, insertions and deletions // from the mempool. A read-lock is implicitly acquired when executing CheckTx, // however, a caller must explicitly grab a write-lock via Lock when updating @@ -121,6 +127,7 @@ func NewTxMempool( timestampIndex: NewWrappedTxList(func(wtx1, wtx2 *WrappedTx) bool { return wtx1.timestamp.After(wtx2.timestamp) || wtx1.timestamp.Equal(wtx2.timestamp) }), + pendingTxs: NewPendingTxs(cfg), failedCheckTxCounts: map[types.NodeID]uint64{}, peerManager: peerManager, } @@ -173,15 +180,28 @@ func (txmp *TxMempool) Unlock() { // Size returns the number of valid transactions in the mempool. It is // thread-safe. func (txmp *TxMempool) Size() int { + return txmp.SizeWithoutPending() + txmp.PendingSize() +} + +func (txmp *TxMempool) SizeWithoutPending() int { return txmp.txStore.Size() } +// PendingSize returns the number of pending transactions in the mempool. +func (txmp *TxMempool) PendingSize() int { + return txmp.pendingTxs.Size() +} + // SizeBytes return the total sum in bytes of all the valid transactions in the // mempool. It is thread-safe. func (txmp *TxMempool) SizeBytes() int64 { return atomic.LoadInt64(&txmp.sizeBytes) } +func (txmp *TxMempool) PendingSizeBytes() int64 { + return atomic.LoadInt64(&txmp.pendingSizeBytes) +} + // FlushAppConn executes FlushSync on the mempool's proxyAppConn. // // NOTE: The caller must obtain a write-lock prior to execution. @@ -275,41 +295,77 @@ func (txmp *TxMempool) CheckTx( } res, err := txmp.proxyAppConn.CheckTx(ctx, &abci.RequestCheckTx{Tx: tx}) + + // when a transaction is removed/expired/rejected, this should be called + // The expire tx handler unreserves the pending nonce + removeHandler := func(removeFromCache bool) { + if removeFromCache { + txmp.cache.Remove(tx) + } + if res.ExpireTxHandler != nil { + res.ExpireTxHandler() + } + } + if err != nil { - txmp.cache.Remove(tx) + removeHandler(true) res.Log = txmp.AppendCheckTxErr(res.Log, err.Error()) } wtx := &WrappedTx{ - tx: tx, - hash: txHash, - timestamp: time.Now().UTC(), - height: txmp.height, + tx: tx, + hash: txHash, + timestamp: time.Now().UTC(), + height: txmp.height, + evmNonce: res.EVMNonce, + evmAddress: res.EVMSenderAddress, + isEVM: res.IsEVM, + removeHandler: removeHandler, } - // only add new transaction if checkTx passes if err == nil { - err = txmp.addNewTransaction(wtx, res, txInfo) - - if err != nil { - return err + // only add new transaction if checkTx passes and is not pending + if !res.IsPendingTransaction { + err = txmp.addNewTransaction(wtx, res.ResponseCheckTx, txInfo) + if err != nil { + return err + } + } else { + // otherwise add to pending txs store + if res.Checker == nil { + return errors.New("no checker available for pending transaction") + } + if err := txmp.canAddPendingTx(wtx); err != nil { + // TODO: eviction strategy for pending transactions + removeHandler(true) + return err + } + atomic.AddInt64(&txmp.pendingSizeBytes, int64(wtx.Size())) + if err := txmp.pendingTxs.Insert(wtx, res, txInfo); err != nil { + return err + } } } if cb != nil { - cb(res) + cb(res.ResponseCheckTx) } return nil } +func (txmp *TxMempool) isInMempool(tx types.Tx) bool { + existingTx := txmp.txStore.GetTxByHash(tx.Key()) + return existingTx != nil && !existingTx.removed +} + func (txmp *TxMempool) RemoveTxByKey(txKey types.TxKey) error { txmp.Lock() defer txmp.Unlock() // remove the committed transaction from the transaction store and indexes if wtx := txmp.txStore.GetTxByHash(txKey); wtx != nil { - txmp.removeTx(wtx, false) + txmp.removeTx(wtx, false, true, true) return nil } @@ -348,7 +404,7 @@ func (txmp *TxMempool) Flush() { txmp.timestampIndex.Reset() for _, wtx := range txmp.txStore.GetAllTxs() { - txmp.removeTx(wtx, false) + txmp.removeTx(wtx, false, false, true) } atomic.SwapInt64(&txmp.sizeBytes, 0) @@ -370,42 +426,28 @@ func (txmp *TxMempool) ReapMaxBytesMaxGas(maxBytes, maxGas int64) types.Txs { totalSize int64 ) - // wTxs contains a list of *WrappedTx retrieved from the priority queue that - // need to be re-enqueued prior to returning. - wTxs := make([]*WrappedTx, 0, txmp.priorityIndex.NumTxs()) - defer func() { - for _, wtx := range wTxs { - txmp.priorityIndex.PushTx(wtx) - } - }() - - txs := make([]types.Tx, 0, txmp.priorityIndex.NumTxs()) - if uint64(txmp.Size()) < txmp.config.TxNotifyThreshold { + var txs []types.Tx + if uint64(txmp.SizeWithoutPending()) < txmp.config.TxNotifyThreshold { // do not reap anything if threshold is not met return txs } - for txmp.priorityIndex.NumTxs() > 0 { - wtx := txmp.priorityIndex.PopTx() - txs = append(txs, wtx.tx) - wTxs = append(wTxs, wtx) + txmp.priorityIndex.ForEachTx(func(wtx *WrappedTx) bool { size := types.ComputeProtoSizeForTxs([]types.Tx{wtx.tx}) - // Ensure we have capacity for the transaction with respect to the - // transaction size. if maxBytes > -1 && totalSize+size > maxBytes { - return txs[:len(txs)-1] + return false } - totalSize += size - - // ensure we have capacity for the transaction with respect to total gas gas := totalGas + wtx.gasWanted if maxGas > -1 && gas > maxGas { - return txs[:len(txs)-1] + return false } totalGas = gas - } + + txs = append(txs, wtx.tx) + return true + }) return txs } @@ -420,24 +462,17 @@ func (txmp *TxMempool) ReapMaxTxs(max int) types.Txs { txmp.mtx.Lock() defer txmp.mtx.Unlock() - numTxs := txmp.priorityIndex.NumTxs() - if max < 0 { - max = numTxs - } - - cap := tmmath.MinInt(numTxs, max) - - // wTxs contains a list of *WrappedTx retrieved from the priority queue that - // need to be re-enqueued prior to returning. - wTxs := make([]*WrappedTx, 0, cap) - txs := make([]types.Tx, 0, cap) - for txmp.priorityIndex.NumTxs() > 0 && len(txs) < max { - wtx := txmp.priorityIndex.PopTx() + wTxs := txmp.priorityIndex.PeekTxs(max) + txs := make([]types.Tx, 0, len(wTxs)) + for _, wtx := range wTxs { txs = append(txs, wtx.tx) - wTxs = append(wTxs, wtx) } - for _, wtx := range wTxs { - txmp.priorityIndex.PushTx(wtx) + if len(txs) < max { + // retrieve more from pending txs + pending := txmp.pendingTxs.Peek(max - len(txs)) + for _, ptx := range pending { + txs = append(txs, ptx.tx.tx) + } } return txs } @@ -481,11 +516,22 @@ func (txmp *TxMempool) Update( // remove the committed transaction from the transaction store and indexes if wtx := txmp.txStore.GetTxByHash(tx.Key()); wtx != nil { - txmp.removeTx(wtx, false) + txmp.removeTx(wtx, false, false, true) + } + if execTxResult[i].EvmTxInfo != nil { + // remove any tx that has the same nonce (because the committed tx + // may be from block proposal and is never in the local mempool) + if wtx, _ := txmp.priorityIndex.GetTxWithSameNonce(&WrappedTx{ + evmAddress: execTxResult[i].EvmTxInfo.SenderAddress, + evmNonce: execTxResult[i].EvmTxInfo.Nonce, + }); wtx != nil { + txmp.removeTx(wtx, false, false, true) + } } } txmp.purgeExpiredTxs(blockHeight) + txmp.handlePendingTransactions() // If there any uncommitted transactions left in the mempool, we either // initiate re-CheckTx per remaining transaction or notify that remaining @@ -503,7 +549,8 @@ func (txmp *TxMempool) Update( } } - txmp.metrics.Size.Set(float64(txmp.Size())) + txmp.metrics.Size.Set(float64(txmp.SizeWithoutPending())) + txmp.metrics.PendingSize.Set(float64(txmp.PendingSize())) return nil } @@ -546,9 +593,7 @@ func (txmp *TxMempool) addNewTransaction(wtx *WrappedTx, res *abci.ResponseCheck txmp.metrics.FailedTxs.Add(1) - if !txmp.config.KeepInvalidTxsInCache { - txmp.cache.Remove(wtx.tx) - } + wtx.removeHandler(!txmp.config.KeepInvalidTxsInCache) if res.Code != abci.CodeTypeOK { txmp.mtxFailedCheckTxCounts.Lock() defer txmp.mtxFailedCheckTxCounts.Unlock() @@ -586,7 +631,7 @@ func (txmp *TxMempool) addNewTransaction(wtx *WrappedTx, res *abci.ResponseCheck if len(evictTxs) == 0 { // No room for the new incoming transaction so we just remove it from // the cache. - txmp.cache.Remove(wtx.tx) + wtx.removeHandler(true) txmp.logger.Error( "rejected incoming good transaction; mempool full", "tx", fmt.Sprintf("%X", wtx.tx.Hash()), @@ -602,7 +647,7 @@ func (txmp *TxMempool) addNewTransaction(wtx *WrappedTx, res *abci.ResponseCheck // - The transaction, toEvict, can be removed while a concurrent // reCheckTx callback is being executed for the same transaction. for _, toEvict := range evictTxs { - txmp.removeTx(toEvict, true) + txmp.removeTx(toEvict, true, true, true) txmp.logger.Debug( "evicted existing good transaction; mempool full", "old_tx", fmt.Sprintf("%X", toEvict.tx.Hash()), @@ -621,18 +666,21 @@ func (txmp *TxMempool) addNewTransaction(wtx *WrappedTx, res *abci.ResponseCheck txInfo.SenderID: {}, } - txmp.metrics.TxSizeBytes.Observe(float64(wtx.Size())) - txmp.metrics.Size.Set(float64(txmp.Size())) + if txmp.isInMempool(wtx.tx) { + return nil + } + + if txmp.insertTx(wtx) { + txmp.logger.Debug( + "inserted good transaction", + "priority", wtx.priority, + "tx", fmt.Sprintf("%X", wtx.tx.Hash()), + "height", txmp.height, + "num_txs", txmp.SizeWithoutPending(), + ) + txmp.notifyTxsAvailable() + } - txmp.insertTx(wtx) - txmp.logger.Debug( - "inserted good transaction", - "priority", wtx.priority, - "tx", fmt.Sprintf("%X", wtx.tx.Hash()), - "height", txmp.height, - "num_txs", txmp.Size(), - ) - txmp.notifyTxsAvailable() return nil } @@ -648,7 +696,7 @@ func (txmp *TxMempool) addNewTransaction(wtx *WrappedTx, res *abci.ResponseCheck // // This method is NOT executed for the initial CheckTx on a new transaction; // that case is handled by addNewTransaction instead. -func (txmp *TxMempool) handleRecheckResult(tx types.Tx, res *abci.ResponseCheckTx) { +func (txmp *TxMempool) handleRecheckResult(tx types.Tx, res *abci.ResponseCheckTxV2) { if txmp.recheckCursor == nil { return } @@ -691,10 +739,11 @@ func (txmp *TxMempool) handleRecheckResult(tx types.Tx, res *abci.ResponseCheckT if !txmp.txStore.IsTxRemoved(wtx.hash) { var err error if txmp.postCheck != nil { - err = txmp.postCheck(tx, res) + err = txmp.postCheck(tx, res.ResponseCheckTx) } - if res.Code == abci.CodeTypeOK && err == nil { + // we will treat a transaction that turns pending in a recheck as invalid and evict it + if res.Code == abci.CodeTypeOK && err == nil && !res.IsPendingTransaction { wtx.priority = res.Priority } else { txmp.logger.Debug( @@ -709,7 +758,7 @@ func (txmp *TxMempool) handleRecheckResult(tx types.Tx, res *abci.ResponseCheckT panic("corrupted reCheckTx cursor") } - txmp.removeTx(wtx, !txmp.config.KeepInvalidTxsInCache) + txmp.removeTx(wtx, !txmp.config.KeepInvalidTxsInCache, true, true) } } @@ -723,12 +772,13 @@ func (txmp *TxMempool) handleRecheckResult(tx types.Tx, res *abci.ResponseCheckT if txmp.recheckCursor == nil { txmp.logger.Debug("finished rechecking transactions") - if txmp.Size() > 0 { + if txmp.SizeWithoutPending() > 0 { txmp.notifyTxsAvailable() } } - txmp.metrics.Size.Set(float64(txmp.Size())) + txmp.metrics.Size.Set(float64(txmp.SizeWithoutPending())) + txmp.metrics.PendingSize.Set(float64(txmp.PendingSize())) } // updateReCheckTxs updates the recheck cursors using the gossipIndex. For @@ -780,7 +830,7 @@ func (txmp *TxMempool) updateReCheckTxs(ctx context.Context) { // the transaction can be inserted into the mempool. func (txmp *TxMempool) canAddTx(wtx *WrappedTx) error { var ( - numTxs = txmp.Size() + numTxs = txmp.SizeWithoutPending() sizeBytes = txmp.SizeBytes() ) @@ -796,9 +846,38 @@ func (txmp *TxMempool) canAddTx(wtx *WrappedTx) error { return nil } -func (txmp *TxMempool) insertTx(wtx *WrappedTx) { +func (txmp *TxMempool) canAddPendingTx(wtx *WrappedTx) error { + var ( + numTxs = txmp.PendingSize() + sizeBytes = txmp.PendingSizeBytes() + ) + + if numTxs >= txmp.config.PendingSize || int64(wtx.Size())+sizeBytes > txmp.config.MaxPendingTxsBytes { + return types.ErrMempoolPendingIsFull{ + NumTxs: numTxs, + MaxTxs: txmp.config.PendingSize, + TxsBytes: sizeBytes, + MaxTxsBytes: txmp.config.MaxPendingTxsBytes, + } + } + + return nil +} + +func (txmp *TxMempool) insertTx(wtx *WrappedTx) bool { + replacedTx, inserted := txmp.priorityIndex.PushTx(wtx) + if !inserted { + return false + } + txmp.metrics.TxSizeBytes.Observe(float64(wtx.Size())) + txmp.metrics.Size.Set(float64(txmp.SizeWithoutPending())) + txmp.metrics.PendingSize.Set(float64(txmp.PendingSize())) + + if replacedTx != nil { + txmp.removeTx(replacedTx, true, false, false) + } + txmp.txStore.SetTx(wtx) - txmp.priorityIndex.PushTx(wtx) txmp.heightIndex.Insert(wtx) txmp.timestampIndex.Insert(wtx) @@ -808,16 +887,21 @@ func (txmp *TxMempool) insertTx(wtx *WrappedTx) { gossipEl := txmp.gossipIndex.PushBack(wtx) wtx.gossipEl = gossipEl + txmp.metrics.InsertedTxs.Add(1) atomic.AddInt64(&txmp.sizeBytes, int64(wtx.Size())) + return true } -func (txmp *TxMempool) removeTx(wtx *WrappedTx, removeFromCache bool) { +func (txmp *TxMempool) removeTx(wtx *WrappedTx, removeFromCache bool, shouldReenqueue bool, updatePriorityIndex bool) { if txmp.txStore.IsTxRemoved(wtx.hash) { return } txmp.txStore.RemoveTx(wtx) - txmp.priorityIndex.RemoveTx(wtx) + toBeReenqueued := []*WrappedTx{} + if updatePriorityIndex { + toBeReenqueued = txmp.priorityIndex.RemoveTx(wtx, shouldReenqueue) + } txmp.heightIndex.Remove(wtx) txmp.timestampIndex.Remove(wtx) @@ -826,16 +910,55 @@ func (txmp *TxMempool) removeTx(wtx *WrappedTx, removeFromCache bool) { txmp.gossipIndex.Remove(wtx.gossipEl) wtx.gossipEl.DetachPrev() + txmp.metrics.RemovedTxs.Add(1) atomic.AddInt64(&txmp.sizeBytes, int64(-wtx.Size())) - if removeFromCache { - txmp.cache.Remove(wtx.tx) + wtx.removeHandler(removeFromCache) + + if shouldReenqueue { + for _, reenqueue := range toBeReenqueued { + txmp.removeTx(reenqueue, removeFromCache, false, true) + } + for _, reenqueue := range toBeReenqueued { + rtx := reenqueue.tx + go func() { + if err := txmp.CheckTx(context.Background(), rtx, nil, TxInfo{}); err != nil { + txmp.logger.Error(fmt.Sprintf("failed to reenqueue transaction %X due to %s", rtx.Hash(), err)) + } + }() + } } } +func (txmp *TxMempool) expire(blockHeight int64, wtx *WrappedTx) { + txmp.metrics.ExpiredTxs.Add(1) + txmp.logExpiredTx(blockHeight, wtx) + wtx.removeHandler(!txmp.config.KeepInvalidTxsInCache) +} + +func (txmp *TxMempool) logExpiredTx(blockHeight int64, wtx *WrappedTx) { + // defensive check + if wtx == nil { + return + } + + txmp.logger.Info( + "transaction expired", + "priority", wtx.priority, + "tx", fmt.Sprintf("%X", wtx.tx.Hash()), + "address", wtx.evmAddress, + "evm", wtx.isEVM, + "nonce", wtx.evmNonce, + "height", blockHeight, + "tx_height", wtx.height, + "tx_timestamp", wtx.timestamp, + "age", time.Since(wtx.timestamp), + ) +} + // purgeExpiredTxs removes all transactions that have exceeded their respective // height- and/or time-based TTLs from their respective indexes. Every expired -// transaction will be removed from the mempool, but preserved in the cache. +// transaction will be removed from the mempool, but preserved in the cache (except for pending txs). // // NOTE: purgeExpiredTxs must only be called during TxMempool#Update in which // the caller has a write-lock on the mempool and so we can safely iterate over @@ -879,12 +1002,18 @@ func (txmp *TxMempool) purgeExpiredTxs(blockHeight int64) { } for _, wtx := range expiredTxs { - txmp.removeTx(wtx, false) + txmp.expire(blockHeight, wtx) } + + // remove pending txs that have expired + txmp.pendingTxs.PurgeExpired(blockHeight, now, func(wtx *WrappedTx) { + atomic.AddInt64(&txmp.pendingSizeBytes, int64(-wtx.Size())) + txmp.expire(blockHeight, wtx) + }) } func (txmp *TxMempool) notifyTxsAvailable() { - if txmp.Size() == 0 { + if txmp.SizeWithoutPending() == 0 { return } @@ -918,3 +1047,19 @@ func (txmp *TxMempool) AppendCheckTxErr(existingLogs string, log string) string return builder.String() } + +func (txmp *TxMempool) handlePendingTransactions() { + accepted, rejected := txmp.pendingTxs.EvaluatePendingTransactions() + for _, tx := range accepted { + atomic.AddInt64(&txmp.pendingSizeBytes, int64(-tx.tx.Size())) + if err := txmp.addNewTransaction(tx.tx, tx.checkTxResponse.ResponseCheckTx, tx.txInfo); err != nil { + txmp.logger.Error(fmt.Sprintf("error adding pending transaction: %s", err)) + } + } + for _, tx := range rejected { + atomic.AddInt64(&txmp.pendingSizeBytes, int64(-tx.tx.Size())) + if !txmp.config.KeepInvalidTxsInCache { + tx.tx.removeHandler(true) + } + } +} diff --git a/internal/mempool/mempool_test.go b/internal/mempool/mempool_test.go index 76e89c056..650664b19 100644 --- a/internal/mempool/mempool_test.go +++ b/internal/mempool/mempool_test.go @@ -29,6 +29,8 @@ import ( // transaction priority based on the value in the key/value pair. type application struct { *kvstore.Application + + occupiedNonces map[string][]uint64 } type testTx struct { @@ -36,40 +38,111 @@ type testTx struct { priority int64 } -func (app *application) CheckTx(_ context.Context, req *abci.RequestCheckTx) (*abci.ResponseCheckTx, error) { +func (app *application) CheckTx(_ context.Context, req *abci.RequestCheckTx) (*abci.ResponseCheckTxV2, error) { + var ( priority int64 sender string ) + if strings.HasPrefix(string(req.Tx), "evm") { + // format is evm-sender-0=account=priority=nonce + // split into respective vars + parts := bytes.Split(req.Tx, []byte("=")) + sender = string(parts[0]) + account := string(parts[1]) + v, err := strconv.ParseInt(string(parts[2]), 10, 64) + if err != nil { + // could not parse + return &abci.ResponseCheckTxV2{ResponseCheckTx: &abci.ResponseCheckTx{ + Priority: priority, + Code: 100, + GasWanted: 1, + }}, nil + } + nonce, err := strconv.ParseUint(string(parts[3]), 10, 64) + if err != nil { + // could not parse + return &abci.ResponseCheckTxV2{ResponseCheckTx: &abci.ResponseCheckTx{ + Priority: priority, + Code: 101, + GasWanted: 1, + }}, nil + } + if app.occupiedNonces == nil { + app.occupiedNonces = make(map[string][]uint64) + } + if _, exists := app.occupiedNonces[account]; !exists { + app.occupiedNonces[account] = []uint64{} + } + active := true + for i := uint64(0); i < nonce; i++ { + found := false + for _, occ := range app.occupiedNonces[account] { + if occ == i { + found = true + break + } + } + if !found { + active = false + break + } + } + app.occupiedNonces[account] = append(app.occupiedNonces[account], nonce) + return &abci.ResponseCheckTxV2{ + ResponseCheckTx: &abci.ResponseCheckTx{ + Priority: v, + Code: code.CodeTypeOK, + GasWanted: 1, + }, + EVMNonce: nonce, + EVMSenderAddress: account, + IsEVM: true, + IsPendingTransaction: !active, + Checker: func() abci.PendingTxCheckerResponse { return abci.Pending }, + ExpireTxHandler: func() { + idx := -1 + for i, n := range app.occupiedNonces[account] { + if n == nonce { + idx = i + break + } + } + if idx >= 0 { + app.occupiedNonces[account] = append(app.occupiedNonces[account][:idx], app.occupiedNonces[account][idx+1:]...) + } + }, + }, nil + } + // infer the priority from the raw transaction value (sender=key=value) parts := bytes.Split(req.Tx, []byte("=")) if len(parts) == 3 { v, err := strconv.ParseInt(string(parts[2]), 10, 64) if err != nil { - return &abci.ResponseCheckTx{ + return &abci.ResponseCheckTxV2{ResponseCheckTx: &abci.ResponseCheckTx{ Priority: priority, Code: 100, GasWanted: 1, - }, nil + }}, nil } priority = v sender = string(parts[0]) } else { - return &abci.ResponseCheckTx{ + return &abci.ResponseCheckTxV2{ResponseCheckTx: &abci.ResponseCheckTx{ Priority: priority, Code: 101, GasWanted: 1, - }, nil + }}, nil } - - return &abci.ResponseCheckTx{ + return &abci.ResponseCheckTxV2{ResponseCheckTx: &abci.ResponseCheckTx{ Priority: priority, Sender: sender, Code: code.CodeTypeOK, GasWanted: 1, - }, nil + }}, nil } func setup(t testing.TB, app abciclient.Client, cacheSize int, options ...TxMempoolOption) *TxMempool { @@ -214,6 +287,7 @@ func TestTxMempool_Size(t *testing.T) { txmp := setup(t, client, 0) txs := checkTxs(ctx, t, txmp, 100, 0) require.Equal(t, len(txs), txmp.Size()) + require.Equal(t, 0, txmp.PendingSize()) require.Equal(t, int64(5690), txmp.SizeBytes()) rawTxs := make([]types.Tx, len(txs)) @@ -442,6 +516,162 @@ func TestTxMempool_CheckTxExceedsMaxSize(t *testing.T) { require.NoError(t, txmp.CheckTx(ctx, tx, nil, TxInfo{SenderID: 0})) } +func TestTxMempool_Prioritization(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + client := abciclient.NewLocalClient(log.NewNopLogger(), &application{Application: kvstore.NewApplication()}) + if err := client.Start(ctx); err != nil { + t.Fatal(err) + } + t.Cleanup(client.Wait) + + txmp := setup(t, client, 100) + peerID := uint16(1) + + address1 := "0xeD23B3A9DE15e92B9ef9540E587B3661E15A12fA" + address2 := "0xfD23B3A9DE15e92B9ef9540E587B3661E15A12fA" + + // Generate transactions with different priorities + // there are two formats to comply with the above mocked CheckTX + // EVM: evm-sender=account=priority=nonce + // Non-EVM: sender=peer=priority + txs := [][]byte{ + []byte(fmt.Sprintf("sender-0-1=peer=%d", 9)), + []byte(fmt.Sprintf("sender-1-1=peer=%d", 8)), + []byte(fmt.Sprintf("evm-sender=%s=%d=%d", address2, 6, 0)), + []byte(fmt.Sprintf("sender-2-1=peer=%d", 5)), + []byte(fmt.Sprintf("sender-3-1=peer=%d", 4)), + } + evmTxs := [][]byte{ + []byte(fmt.Sprintf("evm-sender=%s=%d=%d", address1, 7, 0)), + []byte(fmt.Sprintf("evm-sender=%s=%d=%d", address1, 9, 1)), + } + + // copy the slice of txs and shuffle the order randomly + txsCopy := make([][]byte, len(txs)) + copy(txsCopy, txs) + rng := rand.New(rand.NewSource(time.Now().UnixNano())) + rng.Shuffle(len(txsCopy), func(i, j int) { + txsCopy[i], txsCopy[j] = txsCopy[j], txsCopy[i] + }) + txs = [][]byte{ + []byte(fmt.Sprintf("sender-0-1=peer=%d", 9)), + []byte(fmt.Sprintf("sender-1-1=peer=%d", 8)), + []byte(fmt.Sprintf("evm-sender=%s=%d=%d", address1, 7, 0)), + []byte(fmt.Sprintf("evm-sender=%s=%d=%d", address1, 9, 1)), + []byte(fmt.Sprintf("evm-sender=%s=%d=%d", address2, 6, 0)), + []byte(fmt.Sprintf("sender-2-1=peer=%d", 5)), + []byte(fmt.Sprintf("sender-3-1=peer=%d", 4)), + } + txsCopy = append(txsCopy, evmTxs...) + + for i := range txsCopy { + require.NoError(t, txmp.CheckTx(ctx, txsCopy[i], nil, TxInfo{SenderID: peerID})) + } + + // Reap the transactions + reapedTxs := txmp.ReapMaxTxs(len(txs)) + // Check if the reaped transactions are in the correct order of their priorities + for _, tx := range txs { + fmt.Printf("expected: %s\n", string(tx)) + } + fmt.Println("**************") + for _, reapedTx := range reapedTxs { + fmt.Printf("received: %s\n", string(reapedTx)) + } + for i, reapedTx := range reapedTxs { + require.Equal(t, txs[i], []byte(reapedTx)) + } +} + +func TestTxMempool_PendingStoreSize(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + client := abciclient.NewLocalClient(log.NewNopLogger(), &application{Application: kvstore.NewApplication()}) + if err := client.Start(ctx); err != nil { + t.Fatal(err) + } + t.Cleanup(client.Wait) + + txmp := setup(t, client, 100) + txmp.config.PendingSize = 1 + peerID := uint16(1) + + address1 := "0xeD23B3A9DE15e92B9ef9540E587B3661E15A12fA" + + require.NoError(t, txmp.CheckTx(ctx, []byte(fmt.Sprintf("evm-sender=%s=%d=%d", address1, 1, 1)), nil, TxInfo{SenderID: peerID})) + err := txmp.CheckTx(ctx, []byte(fmt.Sprintf("evm-sender=%s=%d=%d", address1, 1, 2)), nil, TxInfo{SenderID: peerID}) + require.Error(t, err) + require.Contains(t, err.Error(), "mempool pending set is full") +} + +func TestTxMempool_RemoveCacheWhenPendingTxIsFull(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + client := abciclient.NewLocalClient(log.NewNopLogger(), &application{Application: kvstore.NewApplication()}) + if err := client.Start(ctx); err != nil { + t.Fatal(err) + } + t.Cleanup(client.Wait) + + txmp := setup(t, client, 10) + txmp.config.PendingSize = 1 + peerID := uint16(1) + address1 := "0xeD23B3A9DE15e92B9ef9540E587B3661E15A12fA" + require.NoError(t, txmp.CheckTx(ctx, []byte(fmt.Sprintf("evm-sender=%s=%d=%d", address1, 1, 1)), nil, TxInfo{SenderID: peerID})) + err := txmp.CheckTx(ctx, []byte(fmt.Sprintf("evm-sender=%s=%d=%d", address1, 1, 2)), nil, TxInfo{SenderID: peerID}) + require.Error(t, err) + txCache := txmp.cache.(*LRUTxCache) + // Make sure the second tx is removed from cache + require.Equal(t, 1, len(txCache.cacheMap)) +} + +func TestTxMempool_EVMEviction(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + client := abciclient.NewLocalClient(log.NewNopLogger(), &application{Application: kvstore.NewApplication()}) + if err := client.Start(ctx); err != nil { + t.Fatal(err) + } + t.Cleanup(client.Wait) + + txmp := setup(t, client, 100) + txmp.config.Size = 1 + peerID := uint16(1) + + address1 := "0xeD23B3A9DE15e92B9ef9540E587B3661E15A12fA" + address2 := "0xfD23B3A9DE15e92B9ef9540E587B3661E15A12fA" + + require.NoError(t, txmp.CheckTx(ctx, []byte(fmt.Sprintf("evm-sender=%s=%d=%d", address1, 1, 0)), nil, TxInfo{SenderID: peerID})) + // this should evict the previous tx + require.NoError(t, txmp.CheckTx(ctx, []byte(fmt.Sprintf("evm-sender=%s=%d=%d", address1, 2, 0)), nil, TxInfo{SenderID: peerID})) + require.Equal(t, 1, txmp.priorityIndex.NumTxs()) + require.Equal(t, int64(2), txmp.priorityIndex.txs[0].priority) + + txmp.config.Size = 2 + require.NoError(t, txmp.CheckTx(ctx, []byte(fmt.Sprintf("evm-sender=%s=%d=%d", address1, 3, 1)), nil, TxInfo{SenderID: peerID})) + require.Equal(t, 0, txmp.pendingTxs.Size()) + require.Equal(t, 2, txmp.priorityIndex.NumTxs()) + // this would evict the tx with priority 2 and cause the tx with priority 3 to go pending + require.NoError(t, txmp.CheckTx(ctx, []byte(fmt.Sprintf("evm-sender=%s=%d=%d", address2, 4, 0)), nil, TxInfo{SenderID: peerID})) + time.Sleep(1 * time.Second) // reenqueue is async + require.Equal(t, 1, txmp.priorityIndex.NumTxs()) + tx := txmp.priorityIndex.txs[0] + require.Equal(t, 1, txmp.pendingTxs.Size()) + + require.NoError(t, txmp.CheckTx(ctx, []byte(fmt.Sprintf("evm-sender=%s=%d=%d", address2, 5, 1)), nil, TxInfo{SenderID: peerID})) + require.Equal(t, 2, txmp.priorityIndex.NumTxs()) + txmp.removeTx(tx, true, false, true) + // should not reenqueue + require.Equal(t, 1, txmp.priorityIndex.NumTxs()) + time.Sleep(1 * time.Second) // pendingTxs should still be one even after sleeping for a sec + require.Equal(t, 1, txmp.pendingTxs.Size()) +} + func TestTxMempool_CheckTxSamePeer(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() diff --git a/internal/mempool/metrics.gen.go b/internal/mempool/metrics.gen.go index 100c5e71c..cfff9ad4c 100644 --- a/internal/mempool/metrics.gen.go +++ b/internal/mempool/metrics.gen.go @@ -20,6 +20,12 @@ func PrometheusMetrics(namespace string, labelsAndValues ...string) *Metrics { Name: "size", Help: "Number of uncommitted transactions in the mempool.", }, labels).With(labelsAndValues...), + PendingSize: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{ + Namespace: namespace, + Subsystem: MetricsSubsystem, + Name: "pending_size", + Help: "Number of pending transactions in mempool", + }, labels).With(labelsAndValues...), TxSizeBytes: prometheus.NewHistogramFrom(stdprometheus.HistogramOpts{ Namespace: namespace, Subsystem: MetricsSubsystem, @@ -46,22 +52,44 @@ func PrometheusMetrics(namespace string, labelsAndValues ...string) *Metrics { Name: "evicted_txs", Help: "Number of evicted transactions.", }, labels).With(labelsAndValues...), + ExpiredTxs: prometheus.NewCounterFrom(stdprometheus.CounterOpts{ + Namespace: namespace, + Subsystem: MetricsSubsystem, + Name: "expired_txs", + Help: "Number of expired transactions.", + }, labels).With(labelsAndValues...), RecheckTimes: prometheus.NewCounterFrom(stdprometheus.CounterOpts{ Namespace: namespace, Subsystem: MetricsSubsystem, Name: "recheck_times", Help: "Number of times transactions are rechecked in the mempool.", }, labels).With(labelsAndValues...), + RemovedTxs: prometheus.NewCounterFrom(stdprometheus.CounterOpts{ + Namespace: namespace, + Subsystem: MetricsSubsystem, + Name: "removed_txs", + Help: "Number of removed tx from mempool", + }, labels).With(labelsAndValues...), + InsertedTxs: prometheus.NewCounterFrom(stdprometheus.CounterOpts{ + Namespace: namespace, + Subsystem: MetricsSubsystem, + Name: "inserted_txs", + Help: "Number of txs inserted to mempool", + }, labels).With(labelsAndValues...), } } func NopMetrics() *Metrics { return &Metrics{ Size: discard.NewGauge(), + PendingSize: discard.NewGauge(), TxSizeBytes: discard.NewHistogram(), FailedTxs: discard.NewCounter(), RejectedTxs: discard.NewCounter(), EvictedTxs: discard.NewCounter(), + ExpiredTxs: discard.NewCounter(), RecheckTimes: discard.NewCounter(), + RemovedTxs: discard.NewCounter(), + InsertedTxs: discard.NewCounter(), } } diff --git a/internal/mempool/metrics.go b/internal/mempool/metrics.go index 532307635..fb68f7918 100644 --- a/internal/mempool/metrics.go +++ b/internal/mempool/metrics.go @@ -18,6 +18,9 @@ type Metrics struct { // Number of uncommitted transactions in the mempool. Size metrics.Gauge + // Number of pending transactions in mempool + PendingSize metrics.Gauge + // Histogram of transaction sizes in bytes. TxSizeBytes metrics.Histogram `metrics_buckettype:"exp" metrics_bucketsizes:"1,3,7"` @@ -38,6 +41,18 @@ type Metrics struct { //metrics:Number of evicted transactions. EvictedTxs metrics.Counter + // ExpiredTxs defines the number of expired transactions. These are valid + // transactions that passed CheckTx and existed in the mempool but were not + // get picked up in time and eventually got expired and removed from mempool + //metrics:Number of expired transactions. + ExpiredTxs metrics.Counter + // Number of times transactions are rechecked in the mempool. RecheckTimes metrics.Counter + + // Number of removed tx from mempool + RemovedTxs metrics.Counter + + // Number of txs inserted to mempool + InsertedTxs metrics.Counter } diff --git a/internal/mempool/priority_queue.go b/internal/mempool/priority_queue.go index e31997397..ee9bc4873 100644 --- a/internal/mempool/priority_queue.go +++ b/internal/mempool/priority_queue.go @@ -4,6 +4,8 @@ import ( "container/heap" "sort" "sync" + + tmmath "github.com/tendermint/tendermint/libs/math" ) var _ heap.Interface = (*TxPriorityQueue)(nil) @@ -11,12 +13,39 @@ var _ heap.Interface = (*TxPriorityQueue)(nil) // TxPriorityQueue defines a thread-safe priority queue for valid transactions. type TxPriorityQueue struct { mtx sync.RWMutex - txs []*WrappedTx + txs []*WrappedTx // priority heap + // invariant 1: no duplicate nonce in the same queue + // invariant 2: no nonce gap in the same queue + // invariant 3: head of the queue must be in heap + evmQueue map[string][]*WrappedTx // sorted by nonce +} + +func insertToEVMQueue(queue []*WrappedTx, tx *WrappedTx, i int) []*WrappedTx { + // Make room for new value and add it + queue = append(queue, nil) + copy(queue[i+1:], queue[i:]) + queue[i] = tx + return queue +} + +// binarySearch finds the index at which tx should be inserted in queue +func binarySearch(queue []*WrappedTx, tx *WrappedTx) int { + low, high := 0, len(queue) + for low < high { + mid := low + (high-low)/2 + if queue[mid].IsBefore(tx) { + low = mid + 1 + } else { + high = mid + } + } + return low } func NewTxPriorityQueue() *TxPriorityQueue { pq := &TxPriorityQueue{ - txs: make([]*WrappedTx, 0), + txs: make([]*WrappedTx, 0), + evmQueue: make(map[string][]*WrappedTx), } heap.Init(pq) @@ -24,6 +53,49 @@ func NewTxPriorityQueue() *TxPriorityQueue { return pq } +func (pq *TxPriorityQueue) GetTxWithSameNonce(tx *WrappedTx) (*WrappedTx, int) { + pq.mtx.RLock() + defer pq.mtx.RUnlock() + return pq.getTxWithSameNonceUnsafe(tx) +} + +func (pq *TxPriorityQueue) getTxWithSameNonceUnsafe(tx *WrappedTx) (*WrappedTx, int) { + queue, ok := pq.evmQueue[tx.evmAddress] + if !ok { + return nil, -1 + } + idx := binarySearch(queue, tx) + if idx < len(queue) && queue[idx].evmNonce == tx.evmNonce { + return queue[idx], idx + } + return nil, -1 +} + +func (pq *TxPriorityQueue) tryReplacementUnsafe(tx *WrappedTx) (replaced *WrappedTx, shouldDrop bool) { + if !tx.isEVM { + return nil, false + } + queue, ok := pq.evmQueue[tx.evmAddress] + if ok && len(queue) > 0 { + existing, idx := pq.getTxWithSameNonceUnsafe(tx) + if existing != nil { + if tx.priority > existing.priority { + // should replace + // replace heap if applicable + if hi, ok := pq.findTxIndexUnsafe(existing); ok { + heap.Remove(pq, hi) + heap.Push(pq, tx) // need to be in the heap since it has the same nonce + } + pq.evmQueue[tx.evmAddress][idx] = tx // replace queue item in-place + return existing, false + } + // tx should be dropped since it's dominated by an existing tx + return nil, true + } + } + return nil, false +} + // GetEvictableTxs attempts to find and return a list of *WrappedTx than can be // evicted to make room for another *WrappedTx with higher priority. If no such // list of *WrappedTx exists, nil will be returned. The returned list of *WrappedTx @@ -34,8 +106,11 @@ func (pq *TxPriorityQueue) GetEvictableTxs(priority, txSize, totalSize, cap int6 pq.mtx.RLock() defer pq.mtx.RUnlock() - txs := make([]*WrappedTx, len(pq.txs)) - copy(txs, pq.txs) + txs := []*WrappedTx{} + txs = append(txs, pq.txs...) + for _, queue := range pq.evmQueue { + txs = append(txs, queue[1:]...) + } sort.Slice(txs, func(i, j int) bool { return txs[i].priority < txs[j].priority @@ -66,31 +141,257 @@ func (pq *TxPriorityQueue) GetEvictableTxs(priority, txSize, totalSize, cap int6 return nil } +// requires read lock +func (pq *TxPriorityQueue) numQueuedUnsafe() int { + var result int + for _, queue := range pq.evmQueue { + result += len(queue) + } + // first items in queue are also in heap, subtract one + return result - len(pq.evmQueue) +} + // NumTxs returns the number of transactions in the priority queue. It is // thread safe. func (pq *TxPriorityQueue) NumTxs() int { pq.mtx.RLock() defer pq.mtx.RUnlock() - return len(pq.txs) + return len(pq.txs) + pq.numQueuedUnsafe() +} + +func (pq *TxPriorityQueue) removeQueuedEvmTxUnsafe(tx *WrappedTx) (removedIdx int) { + if queue, ok := pq.evmQueue[tx.evmAddress]; ok { + for i, t := range queue { + if t.tx.Key() == tx.tx.Key() { + pq.evmQueue[tx.evmAddress] = append(queue[:i], queue[i+1:]...) + if len(pq.evmQueue[tx.evmAddress]) == 0 { + delete(pq.evmQueue, tx.evmAddress) + } + return i + } + } + } + return -1 +} + +func (pq *TxPriorityQueue) findTxIndexUnsafe(tx *WrappedTx) (int, bool) { + // safety check for race situation where heapIndex is out of range of txs + if tx.heapIndex >= 0 && tx.heapIndex < len(pq.txs) && pq.txs[tx.heapIndex].tx.Key() == tx.tx.Key() { + return tx.heapIndex, true + } + + // heap index isn't trustable here, so attempt to find it + for i, t := range pq.txs { + if t.tx.Key() == tx.tx.Key() { + return i, true + } + } + return 0, false } // RemoveTx removes a specific transaction from the priority queue. -func (pq *TxPriorityQueue) RemoveTx(tx *WrappedTx) { +func (pq *TxPriorityQueue) RemoveTx(tx *WrappedTx, shouldReenqueue bool) (toBeReenqueued []*WrappedTx) { pq.mtx.Lock() defer pq.mtx.Unlock() - if tx.heapIndex < len(pq.txs) { - heap.Remove(pq, tx.heapIndex) + var removedIdx int + + if idx, ok := pq.findTxIndexUnsafe(tx); ok { + heap.Remove(pq, idx) + if tx.isEVM { + removedIdx = pq.removeQueuedEvmTxUnsafe(tx) + if !shouldReenqueue && len(pq.evmQueue[tx.evmAddress]) > 0 { + heap.Push(pq, pq.evmQueue[tx.evmAddress][0]) + } + } + } else if tx.isEVM { + removedIdx = pq.removeQueuedEvmTxUnsafe(tx) + } + if tx.isEVM && shouldReenqueue && len(pq.evmQueue[tx.evmAddress]) > 0 && removedIdx >= 0 { + toBeReenqueued = pq.evmQueue[tx.evmAddress][removedIdx:] } + return } +func (pq *TxPriorityQueue) pushTxUnsafe(tx *WrappedTx) { + if !tx.isEVM { + heap.Push(pq, tx) + return + } + + // if there aren't other waiting txs, init and return + queue, exists := pq.evmQueue[tx.evmAddress] + if !exists { + pq.evmQueue[tx.evmAddress] = []*WrappedTx{tx} + heap.Push(pq, tx) + return + } + + // this item is on the heap at the moment + first := queue[0] + + // the queue's first item (and ONLY the first item) must be on the heap + // if this tx is before the first item, then we need to remove the first + // item from the heap + if tx.IsBefore(first) { + if idx, ok := pq.findTxIndexUnsafe(first); ok { + heap.Remove(pq, idx) + } + heap.Push(pq, tx) + } + pq.evmQueue[tx.evmAddress] = insertToEVMQueue(queue, tx, binarySearch(queue, tx)) +} + +// These are available if we need to test the invariant checks +// these can be used to troubleshoot invariant violations +//func (pq *TxPriorityQueue) checkInvariants(msg string) { +// uniqHashes := make(map[string]bool) +// for idx, tx := range pq.txs { +// if tx == nil { +// pq.print() +// panic(fmt.Sprintf("DEBUG PRINT: found nil item on heap: idx=%d\n", idx)) +// } +// if tx.tx == nil { +// pq.print() +// panic(fmt.Sprintf("DEBUG PRINT: found nil tx.tx on heap: idx=%d\n", idx)) +// } +// if _, ok := uniqHashes[fmt.Sprintf("%x", tx.tx.Key())]; ok { +// pq.print() +// panic(fmt.Sprintf("INVARIANT (%s): duplicate hash=%x in heap", msg, tx.tx.Key())) +// } +// uniqHashes[fmt.Sprintf("%x", tx.tx.Key())] = true +// +// //if _, ok := pq.keys[tx.tx.Key()]; !ok { +// // pq.print() +// // panic(fmt.Sprintf("INVARIANT (%s): tx in heap but not in keys hash=%x", msg, tx.tx.Key())) +// //} +// +// if tx.isEVM { +// if queue, ok := pq.evmQueue[tx.evmAddress]; ok { +// if queue[0].tx.Key() != tx.tx.Key() { +// pq.print() +// panic(fmt.Sprintf("INVARIANT (%s): tx in heap but not at front of evmQueue hash=%x", msg, tx.tx.Key())) +// } +// } else { +// pq.print() +// panic(fmt.Sprintf("INVARIANT (%s): tx in heap but not in evmQueue hash=%x", msg, tx.tx.Key())) +// } +// } +// } +// +// // each item in all queues should be unique nonce +// for _, queue := range pq.evmQueue { +// hashes := make(map[string]bool) +// for idx, tx := range queue { +// if idx == 0 { +// _, ok := pq.findTxIndexUnsafe(tx) +// if !ok { +// pq.print() +// panic(fmt.Sprintf("INVARIANT (%s): did not find tx[0] hash=%x nonce=%d in heap", msg, tx.tx.Key(), tx.evmNonce)) +// } +// } +// //if _, ok := pq.keys[tx.tx.Key()]; !ok { +// // pq.print() +// // panic(fmt.Sprintf("INVARIANT (%s): tx in heap but not in keys hash=%x", msg, tx.tx.Key())) +// //} +// if _, ok := hashes[fmt.Sprintf("%x", tx.tx.Key())]; ok { +// pq.print() +// panic(fmt.Sprintf("INVARIANT (%s): duplicate hash=%x in queue nonce=%d", msg, tx.tx.Key(), tx.evmNonce)) +// } +// hashes[fmt.Sprintf("%x", tx.tx.Key())] = true +// } +// } +//} + +// for debugging situations where invariant violations occur +//func (pq *TxPriorityQueue) print() { +// fmt.Println("PRINT PRIORITY QUEUE ****************** ") +// for _, tx := range pq.txs { +// if tx == nil { +// fmt.Printf("DEBUG PRINT: heap (nil): nonce=?, hash=?\n") +// continue +// } +// if tx.tx == nil { +// fmt.Printf("DEBUG PRINT: heap (%s): nonce=%d, tx.tx is nil \n", tx.evmAddress, tx.evmNonce) +// continue +// } +// fmt.Printf("DEBUG PRINT: heap (%s): nonce=%d, hash=%x, time=%d\n", tx.evmAddress, tx.evmNonce, tx.tx.Key(), tx.timestamp.UnixNano()) +// } +// +// for addr, queue := range pq.evmQueue { +// for idx, tx := range queue { +// if tx == nil { +// fmt.Printf("DEBUG PRINT: found nil item on evmQueue(%s): idx=%d\n", addr, idx) +// continue +// } +// if tx.tx == nil { +// fmt.Printf("DEBUG PRINT: found nil tx.tx on evmQueue(%s): idx=%d\n", addr, idx) +// continue +// } +// +// fmt.Printf("DEBUG PRINT: evmQueue(%s)[%d]: nonce=%d, hash=%x, time=%d\n", tx.evmAddress, idx, tx.evmNonce, tx.tx.Key(), tx.timestamp.UnixNano()) +// } +// } +//} + // PushTx adds a valid transaction to the priority queue. It is thread safe. -func (pq *TxPriorityQueue) PushTx(tx *WrappedTx) { +func (pq *TxPriorityQueue) PushTx(tx *WrappedTx) (*WrappedTx, bool) { pq.mtx.Lock() defer pq.mtx.Unlock() - heap.Push(pq, tx) + replacedTx, shouldDrop := pq.tryReplacementUnsafe(tx) + + // tx was not inserted, and nothing was replaced + if shouldDrop { + return nil, false + } + + // tx replaced an existing transaction + if replacedTx != nil { + return replacedTx, true + } + + // tx was not inserted yet, so insert it + pq.pushTxUnsafe(tx) + return nil, true +} + +func (pq *TxPriorityQueue) popTxUnsafe() *WrappedTx { + if len(pq.txs) == 0 { + return nil + } + + // remove the first item from the heap + x := heap.Pop(pq) + if x == nil { + return nil + } + tx := x.(*WrappedTx) + + // this situation is primarily for a test case that inserts nils + if tx == nil { + return nil + } + + // non-evm transactions do not have txs waiting on a nonce + if !tx.isEVM { + return tx + } + + // evm transactions can have txs waiting on this nonce + // if there are any, we should replace the heap with the next nonce + // for the address + + // remove the first item from the evmQueue + pq.removeQueuedEvmTxUnsafe(tx) + + // if there is a next item, now it can be added to the heap + if len(pq.evmQueue[tx.evmAddress]) > 0 { + heap.Push(pq, pq.evmQueue[tx.evmAddress][0]) + } + + return tx } // PopTx removes the top priority transaction from the queue. It is thread safe. @@ -98,12 +399,62 @@ func (pq *TxPriorityQueue) PopTx() *WrappedTx { pq.mtx.Lock() defer pq.mtx.Unlock() - x := heap.Pop(pq) - if x != nil { - return x.(*WrappedTx) + return pq.popTxUnsafe() +} + +// dequeue up to `max` transactions and reenqueue while locked +func (pq *TxPriorityQueue) ForEachTx(handler func(wtx *WrappedTx) bool) { + pq.mtx.Lock() + defer pq.mtx.Unlock() + + numTxs := len(pq.txs) + pq.numQueuedUnsafe() + + txs := make([]*WrappedTx, 0, numTxs) + + defer func() { + for _, tx := range txs { + pq.pushTxUnsafe(tx) + } + }() + + for i := 0; i < numTxs; i++ { + popped := pq.popTxUnsafe() + if popped == nil { + break + } + txs = append(txs, popped) + if !handler(popped) { + return + } } +} - return nil +// dequeue up to `max` transactions and reenqueue while locked +// TODO: use ForEachTx instead +func (pq *TxPriorityQueue) PeekTxs(max int) []*WrappedTx { + pq.mtx.Lock() + defer pq.mtx.Unlock() + + numTxs := len(pq.txs) + pq.numQueuedUnsafe() + if max < 0 { + max = numTxs + } + + cap := tmmath.MinInt(numTxs, max) + res := make([]*WrappedTx, 0, cap) + for i := 0; i < cap; i++ { + popped := pq.popTxUnsafe() + if popped == nil { + break + } + + res = append(res, popped) + } + + for _, tx := range res { + pq.pushTxUnsafe(tx) + } + return res } // Push implements the Heap interface. @@ -123,8 +474,8 @@ func (pq *TxPriorityQueue) Pop() interface{} { old := pq.txs n := len(old) item := old[n-1] - old[n-1] = nil // avoid memory leak - item.heapIndex = -1 // for safety + old[n-1] = nil // avoid memory leak + setHeapIndex(item, -1) // for safety pq.txs = old[0 : n-1] return item } @@ -153,6 +504,14 @@ func (pq *TxPriorityQueue) Less(i, j int) bool { // Swap implements the Heap interface. It swaps two transactions in the queue. func (pq *TxPriorityQueue) Swap(i, j int) { pq.txs[i], pq.txs[j] = pq.txs[j], pq.txs[i] - pq.txs[i].heapIndex = i - pq.txs[j].heapIndex = j + setHeapIndex(pq.txs[i], i) + setHeapIndex(pq.txs[j], j) +} + +func setHeapIndex(tx *WrappedTx, i int) { + // a removed tx can be nil + if tx == nil { + return + } + tx.heapIndex = i } diff --git a/internal/mempool/priority_queue_test.go b/internal/mempool/priority_queue_test.go index ddc84806d..0c28b4fa3 100644 --- a/internal/mempool/priority_queue_test.go +++ b/internal/mempool/priority_queue_test.go @@ -1,6 +1,7 @@ package mempool import ( + "fmt" "math/rand" "sort" "sync" @@ -10,6 +11,171 @@ import ( "github.com/stretchr/testify/require" ) +// TxTestCase represents a single test case for the TxPriorityQueue +type TxTestCase struct { + name string + inputTxs []*WrappedTx // Input transactions + expectedOutput []int64 // Expected order of transaction IDs +} + +func TestTxPriorityQueue_ReapHalf(t *testing.T) { + pq := NewTxPriorityQueue() + + // Generate transactions with different priorities and nonces + txs := make([]*WrappedTx, 100) + for i := range txs { + txs[i] = &WrappedTx{ + tx: []byte(fmt.Sprintf("tx-%d", i)), + priority: int64(i), + } + + // Push the transaction + pq.PushTx(txs[i]) + } + + //reverse sort txs by priority + sort.Slice(txs, func(i, j int) bool { + return txs[i].priority > txs[j].priority + }) + + // Reap half of the transactions + reapedTxs := pq.PeekTxs(len(txs) / 2) + + // Check if the reaped transactions are in the correct order of their priorities and nonces + for i, reapedTx := range reapedTxs { + require.Equal(t, txs[i].priority, reapedTx.priority) + } +} + +func TestAvoidPanicIfTransactionIsNil(t *testing.T) { + pq := NewTxPriorityQueue() + pq.Push(&WrappedTx{sender: "1", isEVM: true, evmAddress: "0xabc", evmNonce: 1, priority: 10}) + pq.txs = append(pq.txs, nil) + + var count int + pq.ForEachTx(func(tx *WrappedTx) bool { + count++ + return true + }) + + require.Equal(t, 1, count) +} + +func TestTxPriorityQueue_PriorityAndNonceOrdering(t *testing.T) { + testCases := []TxTestCase{ + { + name: "PriorityWithEVMAndNonEVMDuplicateNonce", + inputTxs: []*WrappedTx{ + {sender: "1", isEVM: true, evmAddress: "0xabc", evmNonce: 1, priority: 10}, + {sender: "3", isEVM: true, evmAddress: "0xabc", evmNonce: 3, priority: 9}, + {sender: "2", isEVM: true, evmAddress: "0xabc", evmNonce: 1, priority: 7}, + }, + expectedOutput: []int64{1, 3}, + }, + { + name: "PriorityWithEVMAndNonEVMDuplicateNonce", + inputTxs: []*WrappedTx{ + {sender: "1", isEVM: true, evmAddress: "0xabc", evmNonce: 1, priority: 10}, + {sender: "2", isEVM: false, priority: 9}, + {sender: "4", isEVM: true, evmAddress: "0xabc", evmNonce: 0, priority: 9}, // Same EVM address as first, lower nonce + {sender: "5", isEVM: true, evmAddress: "0xdef", evmNonce: 1, priority: 7}, + {sender: "3", isEVM: true, evmAddress: "0xdef", evmNonce: 0, priority: 8}, + {sender: "6", isEVM: false, priority: 6}, + {sender: "7", isEVM: true, evmAddress: "0xghi", evmNonce: 2, priority: 5}, + }, + expectedOutput: []int64{2, 4, 1, 3, 5, 6, 7}, + }, + { + name: "PriorityWithEVMAndNonEVM", + inputTxs: []*WrappedTx{ + {sender: "1", isEVM: true, evmAddress: "0xabc", evmNonce: 1, priority: 10}, + {sender: "2", isEVM: false, priority: 9}, + {sender: "4", isEVM: true, evmAddress: "0xabc", evmNonce: 0, priority: 9}, // Same EVM address as first, lower nonce + {sender: "5", isEVM: true, evmAddress: "0xdef", evmNonce: 1, priority: 7}, + {sender: "3", isEVM: true, evmAddress: "0xdef", evmNonce: 0, priority: 8}, + {sender: "6", isEVM: false, priority: 6}, + {sender: "7", isEVM: true, evmAddress: "0xghi", evmNonce: 2, priority: 5}, + }, + expectedOutput: []int64{2, 4, 1, 3, 5, 6, 7}, + }, + { + name: "IdenticalPrioritiesAndNoncesDifferentAddresses", + inputTxs: []*WrappedTx{ + {sender: "1", isEVM: true, evmAddress: "0xabc", evmNonce: 2, priority: 5}, + {sender: "2", isEVM: true, evmAddress: "0xdef", evmNonce: 2, priority: 5}, + {sender: "3", isEVM: true, evmAddress: "0xghi", evmNonce: 2, priority: 5}, + }, + expectedOutput: []int64{1, 2, 3}, + }, + { + name: "InterleavedEVAndNonEVMTransactions", + inputTxs: []*WrappedTx{ + {sender: "7", isEVM: false, priority: 15}, + {sender: "8", isEVM: true, evmAddress: "0xabc", evmNonce: 1, priority: 20}, + {sender: "9", isEVM: false, priority: 10}, + {sender: "10", isEVM: true, evmAddress: "0xdef", evmNonce: 2, priority: 20}, + }, + expectedOutput: []int64{8, 10, 7, 9}, + }, + { + name: "SameAddressPriorityDifferentNonces", + inputTxs: []*WrappedTx{ + {sender: "11", isEVM: true, evmAddress: "0xabc", evmNonce: 3, priority: 10}, + {sender: "12", isEVM: true, evmAddress: "0xabc", evmNonce: 1, priority: 10}, + {sender: "13", isEVM: true, evmAddress: "0xabc", evmNonce: 2, priority: 10}, + }, + expectedOutput: []int64{12, 13, 11}, + }, + { + name: "OneItem", + inputTxs: []*WrappedTx{ + {sender: "14", isEVM: true, evmAddress: "0xabc", evmNonce: 1, priority: 10}, + }, + expectedOutput: []int64{14}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + pq := NewTxPriorityQueue() + now := time.Now() + + // Add input transactions to the queue and set timestamp to order inserted + for i, tx := range tc.inputTxs { + tx.timestamp = now.Add(time.Duration(i) * time.Second) + tx.tx = []byte(fmt.Sprintf("%d", time.Now().UnixNano())) + pq.PushTx(tx) + } + + results := pq.PeekTxs(len(tc.inputTxs)) + // Validate the order of transactions + require.Len(t, results, len(tc.expectedOutput)) + for i, expectedTxID := range tc.expectedOutput { + tx := results[i] + require.Equal(t, fmt.Sprintf("%d", expectedTxID), tx.sender) + } + }) + } +} + +func TestTxPriorityQueue_SameAddressDifferentNonces(t *testing.T) { + pq := NewTxPriorityQueue() + address := "0x123" + + // Insert transactions with the same address but different nonces and priorities + pq.PushTx(&WrappedTx{isEVM: true, evmAddress: address, evmNonce: 2, priority: 10, tx: []byte("tx1")}) + pq.PushTx(&WrappedTx{isEVM: true, evmAddress: address, evmNonce: 1, priority: 5, tx: []byte("tx2")}) + pq.PushTx(&WrappedTx{isEVM: true, evmAddress: address, evmNonce: 3, priority: 15, tx: []byte("tx3")}) + + // Pop transactions and verify they are in the correct order of nonce + tx1 := pq.PopTx() + require.Equal(t, uint64(1), tx1.evmNonce) + tx2 := pq.PopTx() + require.Equal(t, uint64(2), tx2.evmNonce) + tx3 := pq.PopTx() + require.Equal(t, uint64(3), tx3.evmNonce) +} + func TestTxPriorityQueue(t *testing.T) { pq := NewTxPriorityQueue() numTxs := 1000 @@ -25,6 +191,7 @@ func TestTxPriorityQueue(t *testing.T) { pq.PushTx(&WrappedTx{ priority: int64(i), timestamp: time.Now(), + tx: []byte(fmt.Sprintf("%d", i)), }) wg.Done() @@ -42,6 +209,7 @@ func TestTxPriorityQueue(t *testing.T) { pq.PushTx(&WrappedTx{ priority: 1000, timestamp: now, + tx: []byte(fmt.Sprintf("%d", time.Now().UnixNano())), }) require.Equal(t, 1001, pq.NumTxs()) @@ -141,6 +309,33 @@ func TestTxPriorityQueue_GetEvictableTxs(t *testing.T) { } } +func TestTxPriorityQueue_RemoveTxEvm(t *testing.T) { + pq := NewTxPriorityQueue() + + tx1 := &WrappedTx{ + priority: 1, + isEVM: true, + evmAddress: "0xabc", + evmNonce: 1, + tx: []byte("tx1"), + } + tx2 := &WrappedTx{ + priority: 1, + isEVM: true, + evmAddress: "0xabc", + evmNonce: 2, + tx: []byte("tx2"), + } + + pq.PushTx(tx1) + pq.PushTx(tx2) + + pq.RemoveTx(tx1, false) + + result := pq.PopTx() + require.Equal(t, tx2, result) +} + func TestTxPriorityQueue_RemoveTx(t *testing.T) { pq := NewTxPriorityQueue() rng := rand.New(rand.NewSource(time.Now().UnixNano())) @@ -152,6 +347,7 @@ func TestTxPriorityQueue_RemoveTx(t *testing.T) { x := rng.Intn(100000) pq.PushTx(&WrappedTx{ priority: int64(x), + tx: []byte(fmt.Sprintf("%d", i)), }) values[i] = x @@ -163,14 +359,95 @@ func TestTxPriorityQueue_RemoveTx(t *testing.T) { max := values[len(values)-1] wtx := pq.txs[pq.NumTxs()/2] - pq.RemoveTx(wtx) + pq.RemoveTx(wtx, false) require.Equal(t, numTxs-1, pq.NumTxs()) require.Equal(t, int64(max), pq.PopTx().priority) require.Equal(t, numTxs-2, pq.NumTxs()) require.NotPanics(t, func() { - pq.RemoveTx(&WrappedTx{heapIndex: numTxs}) - pq.RemoveTx(&WrappedTx{heapIndex: numTxs + 1}) + pq.RemoveTx(&WrappedTx{heapIndex: numTxs}, false) + pq.RemoveTx(&WrappedTx{heapIndex: numTxs + 1}, false) }) require.Equal(t, numTxs-2, pq.NumTxs()) } + +func TestTxPriorityQueue_TryReplacement(t *testing.T) { + for _, test := range []struct { + tx *WrappedTx + existing []*WrappedTx + expectedReplaced bool + expectedDropped bool + expectedQueue []*WrappedTx + expectedHeap []*WrappedTx + }{ + // non-evm transaction is inserted into empty queue + {&WrappedTx{isEVM: false}, []*WrappedTx{}, false, false, []*WrappedTx{{isEVM: false}}, []*WrappedTx{{isEVM: false}}}, + // evm transaction is inserted into empty queue + {&WrappedTx{isEVM: true, evmAddress: "addr1"}, []*WrappedTx{}, false, false, []*WrappedTx{{isEVM: true, evmAddress: "addr1"}}, []*WrappedTx{{isEVM: true, evmAddress: "addr1"}}}, + // evm transaction (new nonce) is inserted into queue with existing tx (lower nonce) + { + &WrappedTx{isEVM: true, evmAddress: "addr1", evmNonce: 1, priority: 100, tx: []byte("abc")}, []*WrappedTx{ + {isEVM: true, evmAddress: "addr1", evmNonce: 0, priority: 100, tx: []byte("def")}, + }, false, false, []*WrappedTx{ + {isEVM: true, evmAddress: "addr1", evmNonce: 0, priority: 100, tx: []byte("def")}, + {isEVM: true, evmAddress: "addr1", evmNonce: 1, priority: 100, tx: []byte("abc")}, + }, []*WrappedTx{ + {isEVM: true, evmAddress: "addr1", evmNonce: 0, priority: 100, tx: []byte("def")}, + {isEVM: true, evmAddress: "addr1", evmNonce: 1, priority: 100, tx: []byte("abc")}, + }, + }, + // evm transaction (new nonce) is not inserted because it's a duplicate nonce and same priority + { + &WrappedTx{isEVM: true, evmAddress: "addr1", evmNonce: 0, priority: 100, tx: []byte("abc")}, []*WrappedTx{ + {isEVM: true, evmAddress: "addr1", evmNonce: 0, priority: 100, tx: []byte("def")}, + }, false, true, []*WrappedTx{ + {isEVM: true, evmAddress: "addr1", evmNonce: 0, priority: 100, tx: []byte("def")}, + }, []*WrappedTx{ + {isEVM: true, evmAddress: "addr1", evmNonce: 0, priority: 100, tx: []byte("def")}, + }, + }, + // evm transaction (new nonce) replaces the existing nonce transaction because its priority is higher + { + &WrappedTx{isEVM: true, evmAddress: "addr1", evmNonce: 0, priority: 101, tx: []byte("abc")}, []*WrappedTx{ + {isEVM: true, evmAddress: "addr1", evmNonce: 0, priority: 100, tx: []byte("def")}, + }, true, false, []*WrappedTx{ + {isEVM: true, evmAddress: "addr1", evmNonce: 0, priority: 101, tx: []byte("abc")}, + }, []*WrappedTx{ + {isEVM: true, evmAddress: "addr1", evmNonce: 0, priority: 101, tx: []byte("abc")}, + }, + }, + { + &WrappedTx{isEVM: true, evmAddress: "addr1", evmNonce: 1, priority: 100, tx: []byte("abc")}, []*WrappedTx{ + {isEVM: true, evmAddress: "addr1", evmNonce: 0, priority: 100, tx: []byte("def")}, + {isEVM: true, evmAddress: "addr1", evmNonce: 1, priority: 99, tx: []byte("ghi")}, + }, true, false, []*WrappedTx{ + {isEVM: true, evmAddress: "addr1", evmNonce: 0, priority: 100, tx: []byte("def")}, + {isEVM: true, evmAddress: "addr1", evmNonce: 1, priority: 100, tx: []byte("abc")}, + }, []*WrappedTx{ + {isEVM: true, evmAddress: "addr1", evmNonce: 0, priority: 100, tx: []byte("def")}, + }, + }, + } { + pq := NewTxPriorityQueue() + for _, e := range test.existing { + pq.PushTx(e) + } + replaced, inserted := pq.PushTx(test.tx) + if test.expectedReplaced { + require.NotNil(t, replaced) + } else { + require.Nil(t, replaced) + } + require.Equal(t, test.expectedDropped, !inserted) + for i, q := range pq.evmQueue[test.tx.evmAddress] { + require.Equal(t, test.expectedQueue[i].tx.Key(), q.tx.Key()) + require.Equal(t, test.expectedQueue[i].priority, q.priority) + require.Equal(t, test.expectedQueue[i].evmNonce, q.evmNonce) + } + for i, q := range pq.txs { + require.Equal(t, test.expectedHeap[i].tx.Key(), q.tx.Key()) + require.Equal(t, test.expectedHeap[i].priority, q.priority) + require.Equal(t, test.expectedHeap[i].evmNonce, q.evmNonce) + } + } +} diff --git a/internal/mempool/tx.go b/internal/mempool/tx.go index c7113c951..9366ac7fa 100644 --- a/internal/mempool/tx.go +++ b/internal/mempool/tx.go @@ -1,10 +1,13 @@ package mempool import ( + "errors" "sort" "sync" "time" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/internal/libs/clist" "github.com/tendermint/tendermint/types" ) @@ -63,6 +66,20 @@ type WrappedTx struct { // transaction in the mempool can be evicted when it is simultaneously having // a reCheckTx callback executed. removed bool + + // this is the callback that can be called when a transaction is removed + removeHandler func(removeFromCache bool) + + // evm properties that aid in prioritization + evmAddress string + evmNonce uint64 + isEVM bool +} + +// IsBefore returns true if the WrappedTx is before the given WrappedTx +// this applies to EVM transactions only +func (wtx *WrappedTx) IsBefore(tx *WrappedTx) bool { + return wtx.evmNonce < tx.evmNonce } func (wtx *WrappedTx) Size() int { @@ -72,9 +89,9 @@ func (wtx *WrappedTx) Size() int { // TxStore implements a thread-safe mapping of valid transaction(s). // // NOTE: -// - Concurrent read-only access to a *WrappedTx object is OK. However, mutative -// access is not allowed. Regardless, it is not expected for the mempool to -// need mutative access. +// - Concurrent read-only access to a *WrappedTx object is OK. However, mutative +// access is not allowed. Regardless, it is not expected for the mempool to +// need mutative access. type TxStore struct { mtx sync.RWMutex hashTxs map[types.TxKey]*WrappedTx // primary index @@ -291,3 +308,144 @@ func (wtl *WrappedTxList) Remove(wtx *WrappedTx) { i++ } } + +type PendingTxs struct { + mtx *sync.RWMutex + txs []TxWithResponse + config *config.MempoolConfig + sizeBytes uint64 +} + +type TxWithResponse struct { + tx *WrappedTx + checkTxResponse *abci.ResponseCheckTxV2 + txInfo TxInfo +} + +func NewPendingTxs(conf *config.MempoolConfig) *PendingTxs { + return &PendingTxs{ + mtx: &sync.RWMutex{}, + txs: []TxWithResponse{}, + config: conf, + sizeBytes: 0, + } +} +func (p *PendingTxs) EvaluatePendingTransactions() ( + acceptedTxs []TxWithResponse, + rejectedTxs []TxWithResponse, +) { + poppedIndices := []int{} + p.mtx.Lock() + defer p.mtx.Unlock() + for i := 0; i < len(p.txs); i++ { + switch p.txs[i].checkTxResponse.Checker() { + case abci.Accepted: + acceptedTxs = append(acceptedTxs, p.txs[i]) + poppedIndices = append(poppedIndices, i) + case abci.Rejected: + rejectedTxs = append(rejectedTxs, p.txs[i]) + poppedIndices = append(poppedIndices, i) + } + } + p.popTxsAtIndices(poppedIndices) + return +} + +// assume mtx is already acquired +func (p *PendingTxs) popTxsAtIndices(indices []int) { + if len(indices) == 0 { + return + } + newTxs := []TxWithResponse{} + start := 0 + for _, idx := range indices { + if idx <= start-1 { + panic("indices popped from pending tx store should be sorted without duplicate") + } + if idx >= len(p.txs) { + panic("indices popped from pending tx store out of range") + } + p.sizeBytes -= uint64(p.txs[idx].tx.Size()) + newTxs = append(newTxs, p.txs[start:idx]...) + start = idx + 1 + } + newTxs = append(newTxs, p.txs[start:]...) + p.txs = newTxs +} + +func (p *PendingTxs) Insert(tx *WrappedTx, resCheckTx *abci.ResponseCheckTxV2, txInfo TxInfo) error { + p.mtx.Lock() + defer p.mtx.Unlock() + + if len(p.txs) >= p.config.PendingSize || uint64(tx.Size())+p.sizeBytes > uint64(p.config.MaxPendingTxsBytes) { + return errors.New("pending store is full") + } + + p.txs = append(p.txs, TxWithResponse{ + tx: tx, + checkTxResponse: resCheckTx, + txInfo: txInfo, + }) + p.sizeBytes += uint64(tx.Size()) + return nil +} + +func (p *PendingTxs) Peek(max int) []TxWithResponse { + p.mtx.RLock() + defer p.mtx.RUnlock() + // priority is fifo + if max > len(p.txs) { + return p.txs + } + return p.txs[:max] +} + +func (p *PendingTxs) Size() int { + p.mtx.RLock() + defer p.mtx.RUnlock() + return len(p.txs) +} + +func (p *PendingTxs) PurgeExpired(blockHeight int64, now time.Time, cb func(wtx *WrappedTx)) { + p.mtx.Lock() + defer p.mtx.Unlock() + + if len(p.txs) == 0 { + return + } + + // txs retains the ordering of insertion + if p.config.TTLNumBlocks > 0 { + idxFirstNotExpiredTx := len(p.txs) + for i, ptx := range p.txs { + // once found, we can break because these are ordered + if (blockHeight - ptx.tx.height) <= p.config.TTLNumBlocks { + idxFirstNotExpiredTx = i + break + } else { + cb(ptx.tx) + p.sizeBytes -= uint64(ptx.tx.Size()) + } + } + p.txs = p.txs[idxFirstNotExpiredTx:] + } + + if len(p.txs) == 0 { + return + } + + if p.config.TTLDuration > 0 { + idxFirstNotExpiredTx := len(p.txs) + for i, ptx := range p.txs { + // once found, we can break because these are ordered + if now.Sub(ptx.tx.timestamp) <= p.config.TTLDuration { + idxFirstNotExpiredTx = i + break + } else { + cb(ptx.tx) + p.sizeBytes -= uint64(ptx.tx.Size()) + } + } + p.txs = p.txs[idxFirstNotExpiredTx:] + } +} diff --git a/internal/mempool/tx_test.go b/internal/mempool/tx_test.go index c6d494b04..152730b33 100644 --- a/internal/mempool/tx_test.go +++ b/internal/mempool/tx_test.go @@ -9,6 +9,8 @@ import ( "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/types" ) @@ -229,3 +231,134 @@ func TestWrappedTxList_Remove(t *testing.T) { sort.Ints(expected) require.Equal(t, expected, got) } + +func TestPendingTxsPopTxsGood(t *testing.T) { + pendingTxs := NewPendingTxs(config.TestMempoolConfig()) + for _, test := range []struct { + origLen int + popIndices []int + expected []int + }{ + { + origLen: 1, + popIndices: []int{}, + expected: []int{0}, + }, { + origLen: 1, + popIndices: []int{0}, + expected: []int{}, + }, { + origLen: 2, + popIndices: []int{0}, + expected: []int{1}, + }, { + origLen: 2, + popIndices: []int{1}, + expected: []int{0}, + }, { + origLen: 2, + popIndices: []int{0, 1}, + expected: []int{}, + }, { + origLen: 3, + popIndices: []int{1}, + expected: []int{0, 2}, + }, { + origLen: 3, + popIndices: []int{0, 2}, + expected: []int{1}, + }, { + origLen: 3, + popIndices: []int{0, 1, 2}, + expected: []int{}, + }, { + origLen: 5, + popIndices: []int{0, 1, 4}, + expected: []int{2, 3}, + }, { + origLen: 5, + popIndices: []int{1, 3}, + expected: []int{0, 2, 4}, + }, + } { + pendingTxs.txs = []TxWithResponse{} + for i := 0; i < test.origLen; i++ { + pendingTxs.txs = append(pendingTxs.txs, TxWithResponse{ + tx: &WrappedTx{tx: []byte{}}, + txInfo: TxInfo{SenderID: uint16(i)}}) + } + pendingTxs.popTxsAtIndices(test.popIndices) + require.Equal(t, len(test.expected), len(pendingTxs.txs)) + for i, e := range test.expected { + require.Equal(t, e, int(pendingTxs.txs[i].txInfo.SenderID)) + } + } +} + +func TestPendingTxsPopTxsBad(t *testing.T) { + pendingTxs := NewPendingTxs(config.TestMempoolConfig()) + // out of range + require.Panics(t, func() { pendingTxs.popTxsAtIndices([]int{0}) }) + // out of order + pendingTxs.txs = []TxWithResponse{{}, {}, {}} + require.Panics(t, func() { pendingTxs.popTxsAtIndices([]int{1, 0}) }) + // duplicate + require.Panics(t, func() { pendingTxs.popTxsAtIndices([]int{2, 2}) }) +} + +func TestPendingTxs_InsertCondition(t *testing.T) { + mempoolCfg := config.TestMempoolConfig() + + // First test exceeding number of txs + mempoolCfg.PendingSize = 2 + + pendingTxs := NewPendingTxs(mempoolCfg) + + // Transaction setup + tx1 := &WrappedTx{ + tx: types.Tx("tx1_data"), + priority: 1, + } + tx1Size := tx1.Size() + + tx2 := &WrappedTx{ + tx: types.Tx("tx2_data"), + priority: 2, + } + tx2Size := tx2.Size() + + err := pendingTxs.Insert(tx1, &abci.ResponseCheckTxV2{}, TxInfo{}) + require.Nil(t, err) + + err = pendingTxs.Insert(tx2, &abci.ResponseCheckTxV2{}, TxInfo{}) + require.Nil(t, err) + + // Should fail due to pending store size limit + tx3 := &WrappedTx{ + tx: types.Tx("tx3_data_exceeding_pending_size"), + priority: 3, + } + + err = pendingTxs.Insert(tx3, &abci.ResponseCheckTxV2{}, TxInfo{}) + require.NotNil(t, err) + + // Second test exceeding byte size condition + mempoolCfg.PendingSize = 5 + pendingTxs = NewPendingTxs(mempoolCfg) + mempoolCfg.MaxPendingTxsBytes = int64(tx1Size + tx2Size) + + err = pendingTxs.Insert(tx1, &abci.ResponseCheckTxV2{}, TxInfo{}) + require.Nil(t, err) + + err = pendingTxs.Insert(tx2, &abci.ResponseCheckTxV2{}, TxInfo{}) + require.Nil(t, err) + + // Should fail due to exceeding max pending transaction bytes + tx3 = &WrappedTx{ + tx: types.Tx("tx3_small_but_exceeds_byte_limit"), + priority: 3, + } + + err = pendingTxs.Insert(tx3, &abci.ResponseCheckTxV2{}, TxInfo{}) + require.NotNil(t, err) +} diff --git a/internal/proxy/client.go b/internal/proxy/client.go index fa1d8c66e..f3ab28f5c 100644 --- a/internal/proxy/client.go +++ b/internal/proxy/client.go @@ -169,7 +169,7 @@ func (app *proxyClient) Flush(ctx context.Context) error { return app.client.Flush(ctx) } -func (app *proxyClient) CheckTx(ctx context.Context, req *types.RequestCheckTx) (*types.ResponseCheckTx, error) { +func (app *proxyClient) CheckTx(ctx context.Context, req *types.RequestCheckTx) (*types.ResponseCheckTxV2, error) { defer addTimeSample(app.metrics.MethodTiming.With("method", "check_tx", "type", "sync"))() return app.client.CheckTx(ctx, req) } diff --git a/internal/rpc/core/mempool.go b/internal/rpc/core/mempool.go index 7cd1bcd7d..c351c5ba2 100644 --- a/internal/rpc/core/mempool.go +++ b/internal/rpc/core/mempool.go @@ -158,6 +158,9 @@ func (env *Environment) UnconfirmedTxs(ctx context.Context, req *coretypes.Reque skipCount := validateSkipCount(page, perPage) txs := env.Mempool.ReapMaxTxs(skipCount + tmmath.MinInt(perPage, totalCount-skipCount)) + if skipCount > len(txs) { + skipCount = len(txs) + } result := txs[skipCount:] return &coretypes.ResultUnconfirmedTxs{ @@ -185,7 +188,7 @@ func (env *Environment) CheckTx(ctx context.Context, req *coretypes.RequestCheck if err != nil { return nil, err } - return &coretypes.ResultCheckTx{ResponseCheckTx: *res}, nil + return &coretypes.ResultCheckTx{ResponseCheckTx: *res.ResponseCheckTx}, nil } func (env *Environment) RemoveTx(ctx context.Context, req *coretypes.RequestRemoveTx) error { diff --git a/internal/state/helpers_test.go b/internal/state/helpers_test.go index 4284bad68..1d0d59441 100644 --- a/internal/state/helpers_test.go +++ b/internal/state/helpers_test.go @@ -302,8 +302,8 @@ func (app *testApp) FinalizeBlock(_ context.Context, req *abci.RequestFinalizeBl }, nil } -func (app *testApp) CheckTx(_ context.Context, req *abci.RequestCheckTx) (*abci.ResponseCheckTx, error) { - return &abci.ResponseCheckTx{}, nil +func (app *testApp) CheckTx(_ context.Context, req *abci.RequestCheckTx) (*abci.ResponseCheckTxV2, error) { + return &abci.ResponseCheckTxV2{ResponseCheckTx: &abci.ResponseCheckTx{}}, nil } func (app *testApp) Commit(context.Context) (*abci.ResponseCommit, error) { diff --git a/node/node.go b/node/node.go index 0cd8a62bf..d3075434e 100644 --- a/node/node.go +++ b/node/node.go @@ -349,6 +349,7 @@ func makeNode( eventBus, waitSync, nodeMetrics.consensus, + cfg, ) node.router.AddChDescToBeAdded(consensus.GetStateChannelDescriptor(), csReactor.SetStateChannel) diff --git a/proto/tendermint/abci/types.proto b/proto/tendermint/abci/types.proto index c814e80b2..89393e83f 100644 --- a/proto/tendermint/abci/types.proto +++ b/proto/tendermint/abci/types.proto @@ -309,6 +309,7 @@ message ResponseDeliverTx { repeated Event events = 7 [(gogoproto.nullable) = false, (gogoproto.jsontag) = "events,omitempty"]; // nondeterministic string codespace = 8; + EvmTxInfo evm_tx_info = 9; } message ResponseEndBlock { @@ -459,6 +460,7 @@ message ExecTxResult { repeated Event events = 7 [(gogoproto.nullable) = false, (gogoproto.jsontag) = "events,omitempty"]; // nondeterministic string codespace = 8; + EvmTxInfo evm_tx_info = 9; } // TxResult contains results of executing the transaction. @@ -565,6 +567,12 @@ message Evidence { int64 total_voting_power = 5; } +message EvmTxInfo { + string senderAddress = 1; + uint64 nonce = 2; + string txHash = 3; +} + //---------------------------------------- // State Sync Types diff --git a/proto/tendermint/crypto/keys.pb.go b/proto/tendermint/crypto/keys.pb.go index 62a109cbd..24c6c1b1b 100644 --- a/proto/tendermint/crypto/keys.pb.go +++ b/proto/tendermint/crypto/keys.pb.go @@ -27,7 +27,6 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package // PublicKey defines the keys available for use with Tendermint Validators type PublicKey struct { // Types that are valid to be assigned to Sum: - // // *PublicKey_Ed25519 // *PublicKey_Secp256K1 // *PublicKey_Sr25519 diff --git a/proto/tendermint/mempool/types.pb.go b/proto/tendermint/mempool/types.pb.go index ffe5ab359..11e259551 100644 --- a/proto/tendermint/mempool/types.pb.go +++ b/proto/tendermint/mempool/types.pb.go @@ -68,7 +68,6 @@ func (m *Txs) GetTxs() [][]byte { type Message struct { // Types that are valid to be assigned to Sum: - // // *Message_Txs Sum isMessage_Sum `protobuf_oneof:"sum"` } diff --git a/proto/tendermint/p2p/conn.pb.go b/proto/tendermint/p2p/conn.pb.go index 16ee463a6..47a3bb0cd 100644 --- a/proto/tendermint/p2p/conn.pb.go +++ b/proto/tendermint/p2p/conn.pb.go @@ -158,7 +158,6 @@ func (m *PacketMsg) GetData() []byte { type Packet struct { // Types that are valid to be assigned to Sum: - // // *Packet_PacketPing // *Packet_PacketPong // *Packet_PacketMsg diff --git a/proto/tendermint/types/types.pb.go b/proto/tendermint/types/types.pb.go index 1fdefc056..3671bf7a1 100644 --- a/proto/tendermint/types/types.pb.go +++ b/proto/tendermint/types/types.pb.go @@ -1294,7 +1294,6 @@ func (m *TxProof) GetProof() *crypto.Proof { type Evidence struct { // Types that are valid to be assigned to Sum: - // // *Evidence_DuplicateVoteEvidence // *Evidence_LightClientAttackEvidence Sum isEvidence_Sum `protobuf_oneof:"sum"` diff --git a/rpc/client/mock/abci.go b/rpc/client/mock/abci.go index 142d64d19..27d779554 100644 --- a/rpc/client/mock/abci.go +++ b/rpc/client/mock/abci.go @@ -60,7 +60,7 @@ func (a ABCIApp) BroadcastTxCommit(ctx context.Context, tx types.Tx) (*coretypes return nil, err } - res := &coretypes.ResultBroadcastTxCommit{CheckTx: *resp} + res := &coretypes.ResultBroadcastTxCommit{CheckTx: *resp.ResponseCheckTx} if res.CheckTx.IsErr() { return res, nil } diff --git a/test/e2e/app/app.go b/test/e2e/app/app.go index 885b49f39..b127fa744 100644 --- a/test/e2e/app/app.go +++ b/test/e2e/app/app.go @@ -163,14 +163,14 @@ func (app *Application) InitChain(_ context.Context, req *abci.RequestInitChain) } // CheckTx implements ABCI. -func (app *Application) CheckTx(_ context.Context, req *abci.RequestCheckTx) (*abci.ResponseCheckTx, error) { +func (app *Application) CheckTx(_ context.Context, req *abci.RequestCheckTx) (*abci.ResponseCheckTxV2, error) { app.mu.Lock() defer app.mu.Unlock() _, _, err := parseTx(req.Tx) if err != nil { - return &abci.ResponseCheckTx{ - Code: code.CodeTypeEncodingError, + return &abci.ResponseCheckTxV2{ + ResponseCheckTx: &abci.ResponseCheckTx{Code: code.CodeTypeEncodingError}, }, nil } @@ -178,7 +178,9 @@ func (app *Application) CheckTx(_ context.Context, req *abci.RequestCheckTx) (*a time.Sleep(time.Duration(app.cfg.CheckTxDelayMS) * time.Millisecond) } - return &abci.ResponseCheckTx{Code: code.CodeTypeOK, GasWanted: 1}, nil + return &abci.ResponseCheckTxV2{ + ResponseCheckTx: &abci.ResponseCheckTx{Code: code.CodeTypeOK, GasWanted: 1}, + }, nil } // FinalizeBlock implements ABCI. diff --git a/types/mempool.go b/types/mempool.go index 36e877508..a3a0c831e 100644 --- a/types/mempool.go +++ b/types/mempool.go @@ -4,6 +4,7 @@ import ( "crypto/sha256" "errors" "fmt" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" ) @@ -84,6 +85,25 @@ func (e ErrMempoolIsFull) Error() string { ) } +// ErrMempoolPendingIsFull defines an error where there are too many pending transactions +// not processed yet +type ErrMempoolPendingIsFull struct { + NumTxs int + MaxTxs int + TxsBytes int64 + MaxTxsBytes int64 +} + +func (e ErrMempoolPendingIsFull) Error() string { + return fmt.Sprintf( + "mempool pending set is full: number of txs %d (max: %d), total txs bytes %d (max: %d)", + e.NumTxs, + e.MaxTxs, + e.TxsBytes, + e.MaxTxsBytes, + ) +} + // ErrPreCheck defines an error where a transaction fails a pre-check. type ErrPreCheck struct { Reason error diff --git a/types/tx.go b/types/tx.go index 0a37d1e9d..5b7c2377e 100644 --- a/types/tx.go +++ b/types/tx.go @@ -184,7 +184,7 @@ func (t TxRecordSet) Validate(maxSizeBytes int64, otxs Txs) error { for i, cur := range allCopy { // allCopy is sorted, so any duplicated data will be adjacent. if i+1 < len(allCopy) && bytes.Equal(cur, allCopy[i+1]) { - return fmt.Errorf("found duplicate transaction with hash: %x", cur.Hash()) + return fmt.Errorf("found duplicate transaction with hash: %x", cur.Key()) } }