Commit fceee563 authored by caopingcp's avatar caopingcp

tendermint remove evidence part

parent 7915e316
......@@ -65,8 +65,6 @@ type ConsensusState struct {
// TODO: encapsulate all of this in one "BlockManager"
blockExec *BlockExecutor
evpool ttypes.EvidencePool
// internal state
mtx sync.Mutex
ttypes.RoundState
......@@ -97,14 +95,13 @@ type ConsensusState struct {
}
// NewConsensusState returns a new ConsensusState.
func NewConsensusState(client *Client, state State, blockExec *BlockExecutor, evpool ttypes.EvidencePool) *ConsensusState {
func NewConsensusState(client *Client, state State, blockExec *BlockExecutor) *ConsensusState {
cs := &ConsensusState{
client: client,
blockExec: blockExec,
peerMsgQueue: make(chan MsgInfo, msgQueueSize),
internalMsgQueue: make(chan MsgInfo, msgQueueSize),
timeoutTicker: NewTimeoutTicker(),
evpool: evpool,
Quit: make(chan struct{}),
txsAvailable: make(chan int64, 1),
......@@ -554,9 +551,6 @@ func (cs *ConsensusState) enterNewRound(height int64, round int) {
// We've already reset these upon new height,
// and meanwhile we might have received a proposal
// for round 0.
if cs.begCons.IsZero() {
cs.begCons = time.Now()
}
} else {
tendermintlog.Info("Resetting Proposal info")
cs.Proposal = nil
......@@ -756,9 +750,6 @@ func (cs *ConsensusState) createProposalBlock() (block *ttypes.TendermintBlock)
return nil
}
block.Data = pblockNew
evidence := cs.evpool.PendingEvidence()
block.AddEvidence(evidence)
return block
}
......@@ -1277,13 +1268,11 @@ func (cs *ConsensusState) tryAddVote(voteRaw *tmtypes.Vote, peerID string, peerI
// If it's otherwise invalid, punish peer.
if err == ErrVoteHeightMismatch {
return err
} else if voteErr, ok := err.(*ttypes.ErrVoteConflictingVotes); ok {
} else if err == ttypes.ErrVoteConflict {
if bytes.Equal(vote.ValidatorAddress, cs.privValidator.GetAddress()) {
tendermintlog.Error("Found conflicting vote from ourselves. Did you unsafe_reset a validator?", "height", vote.Height, "round", vote.Round, "type", vote.Type)
return err
}
err = cs.evpool.AddEvidence(voteErr.DuplicateVoteEvidence)
return err
} else {
// Probably an invalid signature / Bad peer.
// Seems this can also err sometimes with "Unexpected step" - perhaps not from a bad peer ?
......@@ -1339,6 +1328,10 @@ func (cs *ConsensusState) addVote(vote *ttypes.Vote, peerID string, peerIP strin
return
}
if cs.begCons.IsZero() {
cs.begCons = time.Now()
}
height := cs.Height
added, err = cs.Votes.AddVote(vote, peerID)
if !added {
......
// Copyright Fuzamei Corp. 2018 All Rights Reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package tendermint
import (
"encoding/json"
"fmt"
"reflect"
"sync"
dbm "github.com/33cn/chain33/common/db"
ttypes "github.com/33cn/plugin/plugin/consensus/tendermint/types"
tmtypes "github.com/33cn/plugin/plugin/dapp/valnode/types"
"github.com/pkg/errors"
)
/*
Requirements:
- Valid new evidence must be persisted immediately and never forgotten
- Uncommitted evidence must be continuously broadcast
- Uncommitted evidence has a partial order, the evidence's priority
Impl:
- First commit atomically in outqueue, pending, lookup.
- Once broadcast, remove from outqueue. No need to sync
- Once committed, atomically remove from pending and update lookup.
- TODO: If we crash after committed but before removing/updating,
we'll be stuck broadcasting evidence we never know we committed.
so either share the state db and atomically MarkCommitted
with ApplyBlock, or check all outqueue/pending on Start to see if its committed
Schema for indexing evidence (note you need both height and hash to find a piece of evidence):
"evidence-lookup"/<evidence-height>/<evidence-hash> -> EvidenceInfo
"evidence-outqueue"/<priority>/<evidence-height>/<evidence-hash> -> EvidenceInfo
"evidence-pending"/<evidence-height>/<evidence-hash> -> EvidenceInfo
*/
type envelope struct {
Kind string `json:"type"`
Data *json.RawMessage `json:"data"`
}
// EvidenceInfo struct
type EvidenceInfo struct {
Committed bool `json:"committed"`
Priority int64 `json:"priority"`
Evidence envelope `json:"evidence"`
}
const (
baseKeyLookup = "evidence-lookup" // all evidence
baseKeyOutqueue = "evidence-outqueue" // not-yet broadcast
baseKeyPending = "evidence-pending" // broadcast but not committed
)
func keyLookup(evidence ttypes.Evidence) []byte {
return keyLookupFromHeightAndHash(evidence.Height(), evidence.Hash())
}
// big endian padded hex
func bE(h int64) string {
return fmt.Sprintf("%0.16X", h)
}
func keyLookupFromHeightAndHash(height int64, hash []byte) []byte {
return _key("%s/%s/%X", baseKeyLookup, bE(height), hash)
}
func keyOutqueue(evidence ttypes.Evidence, priority int64) []byte {
return _key("%s/%s/%s/%X", baseKeyOutqueue, bE(priority), bE(evidence.Height()), evidence.Hash())
}
func keyPending(evidence ttypes.Evidence) []byte {
return _key("%s/%s/%X", baseKeyPending, bE(evidence.Height()), evidence.Hash())
}
func _key(s string, o ...interface{}) []byte {
return []byte(fmt.Sprintf(s, o...))
}
// EvidenceStore is a store of all the evidence we've seen, including
// evidence that has been committed, evidence that has been verified but not broadcast,
// and evidence that has been broadcast but not yet committed.
type EvidenceStore struct {
db dbm.DB
}
// NewEvidenceStore method
func NewEvidenceStore(db dbm.DB) *EvidenceStore {
if len(ttypes.EvidenceType2Type) == 0 {
ttypes.EvidenceType2Type = map[string]reflect.Type{
ttypes.DuplicateVote: reflect.TypeOf(tmtypes.DuplicateVoteEvidence{}),
}
}
if len(ttypes.EvidenceType2Obj) == 0 {
ttypes.EvidenceType2Obj = map[string]ttypes.Evidence{
ttypes.DuplicateVote: &ttypes.DuplicateVoteEvidence{},
}
}
return &EvidenceStore{
db: db,
}
}
// PriorityEvidence returns the evidence from the outqueue, sorted by highest priority.
func (store *EvidenceStore) PriorityEvidence() (evidence []ttypes.Evidence) {
// reverse the order so highest priority is first
l := store.ListEvidence(baseKeyOutqueue)
l2 := make([]ttypes.Evidence, len(l))
for i := range l {
l2[i] = l[len(l)-1-i]
}
return l2
}
// PendingEvidence returns all known uncommitted evidence.
func (store *EvidenceStore) PendingEvidence() (evidence []ttypes.Evidence) {
return store.ListEvidence(baseKeyPending)
}
// ListEvidence lists the evidence for the given prefix key.
// It is wrapped by PriorityEvidence and PendingEvidence for convenience.
func (store *EvidenceStore) ListEvidence(prefixKey string) (evidence []ttypes.Evidence) {
iter := store.db.Iterator([]byte(prefixKey), nil, false)
for iter.Next() {
val := iter.Value()
evi, err := store.EvidenceFromInfoBytes(val)
if err != nil {
fmt.Printf("ListEvidence evidence info unmarshal failed:%v", err)
} else {
evidence = append(evidence, evi)
}
}
return evidence
}
// GetEvidence fetches the evidence with the given height and hash.
func (store *EvidenceStore) GetEvidence(height int64, hash []byte) *EvidenceInfo {
key := keyLookupFromHeightAndHash(height, hash)
val, e := store.db.Get(key)
if e != nil {
fmt.Printf(fmt.Sprintf(`GetEvidence: db get key %v failed:%v\n`, key, e))
}
if len(val) == 0 {
return nil
}
var ei EvidenceInfo
err := json.Unmarshal(val, &ei)
if err != nil {
fmt.Printf(fmt.Sprintf(`GetEvidence: unmarshal failed:%v\n`, err))
}
return &ei
}
// AddNewEvidence adds the given evidence to the database.
// It returns false if the evidence is already stored.
func (store *EvidenceStore) AddNewEvidence(evidence ttypes.Evidence, priority int64) bool {
// check if we already have seen it
ei := store.GetEvidence(evidence.Height(), evidence.Hash())
if ei != nil && len(ei.Evidence.Kind) == 0 {
return false
}
eiBytes, err := EvidenceToInfoBytes(evidence, priority)
if err != nil {
fmt.Printf("AddNewEvidence failed:%v\n", err)
return false
}
// add it to the store
key := keyOutqueue(evidence, priority)
if err = store.db.Set(key, eiBytes); err != nil {
fmt.Printf("AddNewEvidence Set failed:%v\n", err)
return false
}
key = keyPending(evidence)
if err = store.db.Set(key, eiBytes); err != nil {
fmt.Printf("AddNewEvidence Set failed:%v\n", err)
return false
}
key = keyLookup(evidence)
if err = store.db.SetSync(key, eiBytes); err != nil {
fmt.Printf("AddNewEvidence SetSync failed:%v\n", err)
return false
}
return true
}
// MarkEvidenceAsBroadcasted removes evidence from Outqueue.
func (store *EvidenceStore) MarkEvidenceAsBroadcasted(evidence ttypes.Evidence) {
ei := store.getEvidenceInfo(evidence)
key := keyOutqueue(evidence, ei.Priority)
if err := store.db.Delete(key); err != nil {
fmt.Printf("MarkEvidenceAsBroadcasted Delete failed:%v", err)
}
}
// MarkEvidenceAsCommitted removes evidence from pending and outqueue and sets the state to committed.
func (store *EvidenceStore) MarkEvidenceAsCommitted(evidence ttypes.Evidence) {
// if its committed, its been broadcast
store.MarkEvidenceAsBroadcasted(evidence)
pendingKey := keyPending(evidence)
if err := store.db.Delete(pendingKey); err != nil {
fmt.Printf("MarkEvidenceAsCommitted Delete failed:%v", err)
}
ei := store.getEvidenceInfo(evidence)
ei.Committed = true
lookupKey := keyLookup(evidence)
eiBytes, err := json.Marshal(ei)
if err != nil {
fmt.Printf("MarkEvidenceAsCommitted marshal failed:%v", err)
}
if err = store.db.SetSync(lookupKey, eiBytes); err != nil {
fmt.Printf("MarkEvidenceAsCommitted SetSync failed:%v", err)
}
}
//---------------------------------------------------
// utils
func (store *EvidenceStore) getEvidenceInfo(evidence ttypes.Evidence) EvidenceInfo {
key := keyLookup(evidence)
var ei EvidenceInfo
b, e := store.db.Get(key)
if e != nil {
fmt.Printf(fmt.Sprintf(`getEvidenceInfo: db get key %v failed:%v\n`, key, e))
}
err := json.Unmarshal(b, &ei)
if err != nil {
fmt.Printf(fmt.Sprintf(`getEvidenceInfo: Unmarshal failed:%v\n`, err))
}
return ei
}
// EvidenceToInfoBytes method
func EvidenceToInfoBytes(evidence ttypes.Evidence, priority int64) ([]byte, error) {
evi, err := json.Marshal(evidence)
if err != nil {
return nil, errors.Errorf("EvidenceToBytes marshal evidence failed:%v\n", err)
}
msg := json.RawMessage(evi)
env := envelope{
Kind: evidence.TypeName(),
Data: &msg,
}
ei := EvidenceInfo{
Committed: false,
Priority: priority,
Evidence: env,
}
eiBytes, err := json.Marshal(ei)
if err != nil {
return nil, errors.Errorf("EvidenceToBytes marshal evidence info failed:%v\n", err)
}
return eiBytes, nil
}
// EvidenceFromInfoBytes method
func (store *EvidenceStore) EvidenceFromInfoBytes(data []byte) (ttypes.Evidence, error) {
vote2 := EvidenceInfo{}
err := json.Unmarshal(data, &vote2)
if err != nil {
return nil, errors.Errorf("BytesToEvidence Unmarshal evidence info failed:%v\n", err)
}
if v, ok := ttypes.EvidenceType2Type[vote2.Evidence.Kind]; ok {
tmp := v.(ttypes.Evidence).Copy()
err = json.Unmarshal(*vote2.Evidence.Data, &tmp)
if err != nil {
return nil, errors.Errorf("BytesToEvidence Unmarshal evidence failed:%v\n", err)
}
return tmp, nil
}
return nil, errors.Errorf("BytesToEvidence not find evidence kind:%v\n", vote2.Evidence.Kind)
}
// EvidencePool maintains a pool of valid evidence
// in an EvidenceStore.
type EvidencePool struct {
evidenceStore *EvidenceStore
// needed to load validators to verify evidence
stateDB *CSStateDB
// latest state
mtx sync.Mutex
state State
// never close
evidenceChan chan ttypes.Evidence
}
// NewEvidencePool method
func NewEvidencePool(stateDB *CSStateDB, state State, evidenceStore *EvidenceStore) *EvidencePool {
evpool := &EvidencePool{
stateDB: stateDB,
state: state,
evidenceStore: evidenceStore,
evidenceChan: make(chan ttypes.Evidence),
}
return evpool
}
// EvidenceChan returns an unbuffered channel on which new evidence can be received.
func (evpool *EvidencePool) EvidenceChan() <-chan ttypes.Evidence {
return evpool.evidenceChan
}
// PriorityEvidence returns the priority evidence.
func (evpool *EvidencePool) PriorityEvidence() []ttypes.Evidence {
return evpool.evidenceStore.PriorityEvidence()
}
// PendingEvidence returns all uncommitted evidence.
func (evpool *EvidencePool) PendingEvidence() []ttypes.Evidence {
return evpool.evidenceStore.PendingEvidence()
}
// State returns the current state of the evpool.
func (evpool *EvidencePool) State() State {
evpool.mtx.Lock()
defer evpool.mtx.Unlock()
return evpool.state
}
// Update loads the latest
func (evpool *EvidencePool) Update(block *ttypes.TendermintBlock) {
evpool.mtx.Lock()
defer evpool.mtx.Unlock()
state := evpool.stateDB.LoadState()
if state.LastBlockHeight != block.Header.Height {
panic(fmt.Sprintf("EvidencePool.Update: loaded state with height %d when block.Height=%d", state.LastBlockHeight, block.Header.Height))
}
evpool.state = state
// NOTE: shouldn't need the mutex
evpool.MarkEvidenceAsCommitted(block.Evidence.Evidence)
}
// AddEvidence checks the evidence is valid and adds it to the pool.
// Blocks on the EvidenceChan.
func (evpool *EvidencePool) AddEvidence(evidence ttypes.Evidence) error {
// TODO: check if we already have evidence for this
// validator at this height so we dont get spammed
if err := VerifyEvidence(evpool.stateDB, evpool.State(), evidence); err != nil {
return err
}
// fetch the validator and return its voting power as its priority
// TODO: something better ?
valset, err := evpool.stateDB.LoadValidators(evidence.Height())
if err != nil {
return err
}
_, val := valset.GetByAddress(evidence.Address())
priority := val.VotingPower
added := evpool.evidenceStore.AddNewEvidence(evidence, priority)
if !added {
// evidence already known, just ignore
return nil
}
tendermintlog.Info("Verified new evidence of byzantine behaviour", "evidence", evidence)
// never closes. always safe to send on
evpool.evidenceChan <- evidence
return nil
}
// MarkEvidenceAsCommitted marks all the evidence as committed.
func (evpool *EvidencePool) MarkEvidenceAsCommitted(evidence []*tmtypes.EvidenceEnvelope) {
for _, ev := range evidence {
tmp := ttypes.EvidenceEnvelope2Evidence(ev)
if tmp != nil {
evpool.evidenceStore.MarkEvidenceAsCommitted(tmp)
}
}
}
// VerifyEvidence verifies the evidence fully by checking it is internally
// consistent and sufficiently recent.
func VerifyEvidence(stateDB *CSStateDB, s State, evidence ttypes.Evidence) error {
height := s.LastBlockHeight
evidenceAge := height - evidence.Height()
maxAge := s.ConsensusParams.EvidenceParams.MaxAge
if evidenceAge > maxAge {
return fmt.Errorf("Evidence from height %d is too old. Min height is %d",
evidence.Height(), height-maxAge)
}
if err := evidence.Verify(s.ChainID); err != nil {
return err
}
valset, err := stateDB.LoadValidators(evidence.Height())
if err != nil {
// TODO: if err is just that we cant find it cuz we pruned, ignore.
// TODO: if its actually bad evidence, punish peer
return err
}
// The address must have been an active validator at the height
ev := evidence
height, addr, idx := ev.Height(), ev.Address(), ev.Index()
valIdx, val := valset.GetByAddress(addr)
if val == nil {
return fmt.Errorf("Address %X was not a validator at height %d", addr, height)
} else if idx != valIdx {
return fmt.Errorf("Address %X was validator %d at height %d, not %d", addr, valIdx, height, idx)
}
return nil
}
......@@ -22,15 +22,13 @@ import (
type BlockExecutor struct {
// save state, validators, consensus params, abci responses here
db *CSStateDB
evpool ttypes.EvidencePool
}
// NewBlockExecutor returns a new BlockExecutor with a NopEventBus.
// Call SetEventBus to provide one.
func NewBlockExecutor(db *CSStateDB, evpool ttypes.EvidencePool) *BlockExecutor {
func NewBlockExecutor(db *CSStateDB) *BlockExecutor {
return &BlockExecutor{
db: db,
evpool: evpool,
}
}
......@@ -59,12 +57,6 @@ func (blockExec *BlockExecutor) ApplyBlock(s State, blockID ttypes.BlockID, bloc
}
blockExec.db.SaveState(s)
// Update evpool now that state is saved
// TODO: handle the crash/recover scenario
// ie. (may need to call Update for last block)
blockExec.evpool.Update(block)
return s, nil
}
......@@ -246,13 +238,5 @@ func validateBlock(stateDB *CSStateDB, s State, b *ttypes.TendermintBlock) error
}
}
for _, ev := range b.Evidence.Evidence {
evidence := ttypes.EvidenceEnvelope2Evidence(ev)
if evidence != nil {
if err := VerifyEvidence(stateDB, s, evidence); err != nil {
return ttypes.NewEvidenceInvalidErr(evidence, err)
}
}
}
return nil
}
......@@ -17,8 +17,6 @@ import (
"github.com/33cn/chain33/common/crypto"
ttypes "github.com/33cn/plugin/plugin/consensus/tendermint/types"
tmtypes "github.com/33cn/plugin/plugin/dapp/valnode/types"
"github.com/golang/protobuf/proto"
)
const (
......@@ -132,7 +130,6 @@ type Node struct {
lAddr string
state *ConsensusState
evpool *EvidencePool
broadcastChannel chan MsgInfo
started uint32 // atomic
stopped uint32 // atomic
......@@ -140,7 +137,7 @@ type Node struct {
}
// NewNode method
func NewNode(seeds []string, protocol string, lAddr string, privKey crypto.PrivKey, network string, version string, state *ConsensusState, evpool *EvidencePool) *Node {
func NewNode(seeds []string, protocol string, lAddr string, privKey crypto.PrivKey, network string, version string, state *ConsensusState) *Node {
address := GenAddressByPubKey(privKey.PubKey())
node := &Node{
......@@ -158,7 +155,6 @@ func NewNode(seeds []string, protocol string, lAddr string, privKey crypto.PrivK
reconnecting: NewMutexMap(),
broadcastChannel: make(chan MsgInfo, maxSendQueueSize),
state: state,
evpool: evpool,
localIPs: make(map[string]net.IP),
}
......@@ -219,7 +215,6 @@ func (node *Node) Start() {
go node.StartConsensusRoutine()
go node.BroadcastRoutine()
go node.evidenceBroadcastRoutine()
}
}
......@@ -234,7 +229,7 @@ func (node *Node) DialPeerWithAddress(addr string) error {
func (node *Node) addOutboundPeerWithConfig(addr string) error {
tendermintlog.Info("Dialing peer", "address", addr)
peerConn, err := newOutboundPeerConn(addr, node.privKey, node.StopPeerForError, node.state, node.evpool)
peerConn, err := newOutboundPeerConn(addr, node.privKey, node.StopPeerForError, node.state)
if err != nil {
go node.reconnectToPeer(addr)
return err
......@@ -307,66 +302,6 @@ func (node *Node) StartConsensusRoutine() {
}
}
func (node *Node) evidenceBroadcastRoutine() {
ticker := time.NewTicker(time.Second * broadcastEvidenceIntervalS)
for {
select {
case evidence := <-node.evpool.EvidenceChan():
// broadcast some new evidence
data, err := proto.Marshal(evidence.Child())
if err != nil {
msg := MsgInfo{TypeID: ttypes.EvidenceListID,
Msg: &tmtypes.EvidenceData{
Evidence: []*tmtypes.EvidenceEnvelope{
{
TypeName: evidence.TypeName(),
Data: data,
},
},
},
PeerID: node.ID, PeerIP: node.IP,
}
node.Broadcast(msg)
// TODO: Broadcast runs asynchronously, so this should wait on the successChan
// in another routine before marking to be proper.
node.evpool.evidenceStore.MarkEvidenceAsBroadcasted(evidence)
}
case <-ticker.C:
// broadcast all pending evidence
var eData tmtypes.EvidenceData
evidence := node.evpool.PendingEvidence()
for _, item := range evidence {
ev := item.Child()
if ev != nil {
data, err := proto.Marshal(ev)
if err != nil {
panic("AddEvidence marshal failed")
}
env := &tmtypes.EvidenceEnvelope{
TypeName: item.TypeName(),
Data: data,
}
eData.Evidence = append(eData.Evidence, env)
}
}
msg := MsgInfo{TypeID: ttypes.EvidenceListID,
Msg: &eData,
PeerID: node.ID,
PeerIP: node.IP,
}
node.Broadcast(msg)
case _, ok := <-node.quit:
if !ok {
node.quit = nil
tendermintlog.Info("evidenceBroadcastRoutine quit")
return
}
}
}
}
// BroadcastRoutine receive to broadcast
func (node *Node) BroadcastRoutine() {
for {
......@@ -413,7 +348,7 @@ func (node *Node) StopPeerForError(peer Peer, reason interface{}) {
}
func (node *Node) addInboundPeer(conn net.Conn) error {
peerConn, err := newInboundPeerConn(conn, node.privKey, node.StopPeerForError, node.state, node.evpool)
peerConn, err := newInboundPeerConn(conn, node.privKey, node.StopPeerForError, node.state)
if err != nil {
if er := conn.Close(); er != nil {
tendermintlog.Error("addInboundPeer close conn failed", "er", er)
......@@ -710,13 +645,13 @@ func dial(addr string) (net.Conn, error) {
return conn, nil
}
func newOutboundPeerConn(addr string, ourNodePrivKey crypto.PrivKey, onPeerError func(Peer, interface{}), state *ConsensusState, evpool *EvidencePool) (*peerConn, error) {
func newOutboundPeerConn(addr string, ourNodePrivKey crypto.PrivKey, onPeerError func(Peer, interface{}), state *ConsensusState) (*peerConn, error) {
conn, err := dial(addr)
if err != nil {
return &peerConn{}, fmt.Errorf("Error creating peer:%v", err)
}
pc, err := newPeerConn(conn, true, true, ourNodePrivKey, onPeerError, state, evpool)
pc, err := newPeerConn(conn, true, true, ourNodePrivKey, onPeerError, state)
if err != nil {
if cerr := conn.Close(); cerr != nil {
return &peerConn{}, fmt.Errorf("newPeerConn failed:%v, connection close failed:%v", err, cerr)
......@@ -732,12 +667,11 @@ func newInboundPeerConn(
ourNodePrivKey crypto.PrivKey,
onPeerError func(Peer, interface{}),
state *ConsensusState,
evpool *EvidencePool,
) (*peerConn, error) {
// TODO: issue PoW challenge
return newPeerConn(conn, false, false, ourNodePrivKey, onPeerError, state, evpool)
return newPeerConn(conn, false, false, ourNodePrivKey, onPeerError, state)
}
func newPeerConn(
......@@ -746,7 +680,6 @@ func newPeerConn(
ourNodePrivKey crypto.PrivKey,
onPeerError func(Peer, interface{}),
state *ConsensusState,
evpool *EvidencePool,
) (pc *peerConn, err error) {
conn := rawConn
......@@ -769,6 +702,5 @@ func newPeerConn(
conn: conn,
onPeerError: onPeerError,
myState: state,
myevpool: evpool,
}, nil
}
......@@ -95,7 +95,6 @@ type peerConn struct {
onPeerError func(Peer, interface{})
myState *ConsensusState
myevpool *EvidencePool
state *PeerConnState
updateStateQueue chan MsgInfo
......@@ -579,19 +578,6 @@ FOR_LOOP:
}
} else if pkt.TypeID == ttypes.ProposalHeartbeatID {
pc.heartbeatQueue <- realMsg.(*tmtypes.Heartbeat)
} else if pkt.TypeID == ttypes.EvidenceListID {
go func() {
for _, ev := range realMsg.(*tmtypes.EvidenceData).Evidence {
evidence := ttypes.EvidenceEnvelope2Evidence(ev)
if evidence != nil {
err := pc.myevpool.AddEvidence(evidence.(ttypes.Evidence))
if err != nil {
tendermintlog.Error("Evidence is not valid", "evidence", ev, "err", err)
// TODO: punish peer
}
}
}
}()
} else {
pc.updateStateQueue <- MsgInfo{pkt.TypeID, realMsg.(proto.Message), pc.ID(), pc.ip.String()}
}
......
......@@ -295,15 +295,11 @@ OuterLoop:
stateDB := NewStateDB(client, state)
//make evidenceReactor
evidenceStore := NewEvidenceStore(client.evidenceDB)
evidencePool := NewEvidencePool(stateDB, state, evidenceStore)
// make block executor for consensus and blockchain reactors to execute blocks
blockExec := NewBlockExecutor(stateDB, evidencePool)
blockExec := NewBlockExecutor(stateDB)
// Make ConsensusReactor
csState := NewConsensusState(client, state, blockExec, evidencePool)
csState := NewConsensusState(client, state, blockExec)
// reset height, round, state begin at newheigt,0,0
client.privValidator.ResetLastHeight(state.LastBlockHeight)
csState.SetPrivValidator(client.privValidator)
......@@ -312,7 +308,7 @@ OuterLoop:
// Create & add listener
protocol, listeningAddress := "tcp", "0.0.0.0:46656"
node := NewNode(validatorNodes, protocol, listeningAddress, client.privKey, state.ChainID, tendermintVersion, csState, evidencePool)
node := NewNode(validatorNodes, protocol, listeningAddress, client.privKey, state.ChainID, tendermintVersion, csState)
client.node = node
node.Start()
......
......@@ -17,7 +17,6 @@ import (
"github.com/33cn/chain33/common/merkle"
"github.com/33cn/chain33/types"
tmtypes "github.com/33cn/plugin/plugin/dapp/valnode/types"
"github.com/golang/protobuf/proto"
)
var (
......@@ -69,32 +68,12 @@ func MakeBlock(height int64, round int64, pblock *types.Block, commit *tmtypes.T
},
Data: pblock,
LastCommit: commit,
Evidence: &tmtypes.EvidenceData{Evidence: make([]*tmtypes.EvidenceEnvelope, 0)},
},
}
block.FillHeader()
return block
}
// AddEvidence appends the given evidence to the block
func (b *TendermintBlock) AddEvidence(evidence []Evidence) {
for _, item := range evidence {
ev := item.Child()
if ev != nil {
data, err := proto.Marshal(ev)
if err != nil {
blocklog.Error("AddEvidence marshal failed", "error", err)
panic("AddEvidence marshal failed")
}
env := &tmtypes.EvidenceEnvelope{
TypeName: item.TypeName(),
Data: data,
}
b.Evidence.Evidence = append(b.Evidence.Evidence, env)
}
}
}
// ValidateBasic performs basic validation that doesn't involve state data.
// It checks the internal consistency of the block.
// Further validation is done using state#ValidateBlock.
......@@ -133,10 +112,6 @@ func (b *TendermintBlock) ValidateBasic() error {
return fmt.Errorf("Wrong Header.LastCommitHash. Expected %v, got %v", b.Header.LastCommitHash, lastCommit.Hash())
}
evidence := &EvidenceData{EvidenceData: b.Evidence}
if !bytes.Equal(b.Header.EvidenceHash, evidence.Hash()) {
return errors.New(Fmt("Wrong Header.EvidenceHash. Expected %v, got %v", b.Header.EvidenceHash, evidence.Hash()))
}
return nil
}
......@@ -148,10 +123,6 @@ func (b *TendermintBlock) FillHeader() {
}
b.Header.LastCommitHash = lastCommit.Hash()
}
if b.Header.EvidenceHash == nil {
evidence := &EvidenceData{EvidenceData: b.Evidence}
b.Header.EvidenceHash = evidence.Hash()
}
}
// Hash computes and returns the block hash.
......@@ -246,7 +217,6 @@ func (h *Header) StringIndented(indent string) string {
%s App: %v
%s Conensus: %v
%s Results: %v
%s Evidence: %v
%s}#%v`,
indent, h.ChainID,
indent, h.Height,
......@@ -259,7 +229,6 @@ func (h *Header) StringIndented(indent string) string {
indent, h.AppHash,
indent, h.ConsensusHash,
indent, h.LastResultsHash,
indent, h.EvidenceHash,
indent, h.Hash())
}
......@@ -410,113 +379,3 @@ type SignedHeader struct {
Header *Header `json:"header"`
Commit *Commit `json:"commit"`
}
// EvidenceEnvelope ...
type EvidenceEnvelope struct {
*tmtypes.EvidenceEnvelope
}
// EvidenceEnvelopeList contains any evidence of malicious wrong-doing by validators
type EvidenceEnvelopeList []EvidenceEnvelope
// Hash ...
func (env EvidenceEnvelope) Hash() []byte {
penv := env.EvidenceEnvelope
evidence := EvidenceEnvelope2Evidence(penv)
if evidence != nil {
return evidence.Hash()
}
return nil
}
func (env EvidenceEnvelope) String() string {
penv := env.EvidenceEnvelope
evidence := EvidenceEnvelope2Evidence(penv)
if evidence != nil {
return evidence.String()
}
return ""
}
// Hash returns the simple merkle root hash of the EvidenceList.
func (evl EvidenceEnvelopeList) Hash() []byte {
// Recursive impl.
// Copied from tmlibs/merkle to avoid allocations
switch len(evl) {
case 0:
return nil
case 1:
return evl[0].Hash()
default:
left := evl[:(len(evl)+1)/2].Hash()
right := evl[(len(evl)+1)/2:].Hash()
cache := make([]byte, len(left)+len(right))
return merkle.GetHashFromTwoHash(cache, left, right)
}
}
func (evl EvidenceEnvelopeList) String() string {
s := ""
for _, e := range evl {
s += Fmt("%s\t\t", e)
}
return s
}
// Has returns true if the evidence is in the EvidenceList.
func (evl EvidenceEnvelopeList) Has(evidence Evidence) bool {
for _, ev := range evl {
penv := ev.EvidenceEnvelope
tmp := EvidenceEnvelope2Evidence(penv)
if tmp != nil {
if tmp.Equal(evidence) {
return true
}
}
}
return false
}
// EvidenceData ...
type EvidenceData struct {
*tmtypes.EvidenceData
hash []byte
}
// Hash returns the hash of the data.
func (data *EvidenceData) Hash() []byte {
if data.hash == nil {
if data.EvidenceData == nil {
return nil
}
var evidence EvidenceEnvelopeList
for _, item := range data.Evidence {
elem := EvidenceEnvelope{
EvidenceEnvelope: item,
}
evidence = append(evidence, elem)
}
data.hash = evidence.Hash()
}
return data.hash
}
// StringIndented returns a string representation of the evidence.
func (data *EvidenceData) StringIndented(indent string) string {
if data == nil {
return "nil-Evidence"
}
evStrings := make([]string, MinInt(len(data.Evidence), 21))
for i, ev := range data.Evidence {
if i == 20 {
evStrings[i] = Fmt("... (%v total)", len(data.Evidence))
break
}
evStrings[i] = Fmt("Evidence:%v", ev)
}
return Fmt(`Data{
%s %v
%s}#%v`,
indent, strings.Join(evStrings, "\n"+indent+" "),
indent, data.hash)
}
// Copyright Fuzamei Corp. 2018 All Rights Reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package types
import (
"bytes"
"encoding/json"
"fmt"
"reflect"
"github.com/33cn/chain33/common/crypto"
"github.com/33cn/chain33/common/merkle"
tmtypes "github.com/33cn/plugin/plugin/dapp/valnode/types"
"github.com/golang/protobuf/proto"
)
// ErrEvidenceInvalid wraps a piece of evidence and the error denoting how or why it is invalid.
type ErrEvidenceInvalid struct {
Evidence Evidence
ErrorValue error
}
// NewEvidenceInvalidErr ...
func NewEvidenceInvalidErr(ev Evidence, err error) *ErrEvidenceInvalid {
return &ErrEvidenceInvalid{ev, err}
}
// Error returns a string representation of the error.
func (err *ErrEvidenceInvalid) Error() string {
return Fmt("Invalid evidence: %v. Evidence: %v", err.ErrorValue, err.Evidence)
}
//-------------------------------------------
const (
DuplicateVote = "DuplicateVote"
MockGood = "MockGood"
MockBad = "MockBad"
)
// EvidenceType map define
var (
EvidenceType2Type map[string]reflect.Type
EvidenceType2Obj map[string]Evidence
)
// Evidence represents any provable malicious activity by a validator
type Evidence interface {
Height() int64 // height of the equivocation
Address() []byte // address of the equivocating validator
Index() int // index of the validator in the validator set
Hash() []byte // hash of the evidence
Verify(chainID string) error // verify the evidence
Equal(Evidence) bool // check equality of evidence
String() string
Copy() Evidence
TypeName() string
SetChild(child proto.Message)
Child() proto.Message
}
//-------------------------------------------
// EvidenceList is a list of Evidence. Evidences is not a word.
type EvidenceList []Evidence
// Hash returns the simple merkle root hash of the EvidenceList.
func (evl EvidenceList) Hash() []byte {
// Recursive impl.
// Copied from tmlibs/merkle to avoid allocations
switch len(evl) {
case 0:
return nil
case 1:
return evl[0].Hash()
default:
left := evl[:(len(evl)+1)/2].Hash()
right := evl[(len(evl)+1)/2:].Hash()
cache := make([]byte, len(left)+len(right))
return merkle.GetHashFromTwoHash(cache, left, right)
}
}
func (evl EvidenceList) String() string {
s := ""
for _, e := range evl {
s += Fmt("%s\t\t", e)
}
return s
}
// Has returns true if the evidence is in the EvidenceList.
func (evl EvidenceList) Has(evidence Evidence) bool {
for _, ev := range evl {
if ev.Equal(evidence) {
return true
}
}
return false
}
//-------------------------------------------
// DuplicateVoteEvidence contains evidence a validator signed two conflicting votes.
type DuplicateVoteEvidence struct {
*tmtypes.DuplicateVoteEvidence
}
// String returns a string representation of the evidence.
func (dve *DuplicateVoteEvidence) String() string {
return Fmt("VoteA: %v; VoteB: %v", dve.VoteA, dve.VoteB)
}
// Height returns the height this evidence refers to.
func (dve *DuplicateVoteEvidence) Height() int64 {
return dve.VoteA.Height
}
// Address returns the address of the validator.
func (dve *DuplicateVoteEvidence) Address() []byte {
pubkey, err := PubKeyFromString(dve.PubKey)
if err != nil {
return nil
}
return GenAddressByPubKey(pubkey)
}
// Index returns the index of the validator.
func (dve *DuplicateVoteEvidence) Index() int {
return int(dve.VoteA.ValidatorIndex)
}
// Hash returns the hash of the evidence.
func (dve *DuplicateVoteEvidence) Hash() []byte {
return SimpleHashFromBinary(dve)
}
// Verify returns an error if the two votes aren't conflicting.
// To be conflicting, they must be from the same validator, for the same H/R/S, but for different blocks.
func (dve *DuplicateVoteEvidence) Verify(chainID string) error {
// H/R/S must be the same
if dve.VoteA.Height != dve.VoteB.Height ||
dve.VoteA.Round != dve.VoteB.Round ||
dve.VoteA.Type != dve.VoteB.Type {
return fmt.Errorf("DuplicateVoteEvidence Error: H/R/S does not match. Got %v and %v", dve.VoteA, dve.VoteB)
}
// Address must be the same
if !bytes.Equal(dve.VoteA.ValidatorAddress, dve.VoteB.ValidatorAddress) {
return fmt.Errorf("DuplicateVoteEvidence Error: Validator addresses do not match. Got %X and %X", dve.VoteA.ValidatorAddress, dve.VoteB.ValidatorAddress)
}
// XXX: Should we enforce index is the same ?
if dve.VoteA.ValidatorIndex != dve.VoteB.ValidatorIndex {
return fmt.Errorf("DuplicateVoteEvidence Error: Validator indices do not match. Got %d and %d", dve.VoteA.ValidatorIndex, dve.VoteB.ValidatorIndex)
}
blockIDA := BlockID{
*dve.VoteA.BlockID,
}
blockIDB := BlockID{
*dve.VoteB.BlockID,
}
// BlockIDs must be different
if blockIDA.Equals(blockIDB) {
return fmt.Errorf("DuplicateVoteEvidence Error: BlockIDs are the same (%v) - not a real duplicate vote", dve.VoteA.BlockID)
}
// Signatures must be valid
pubkey, err := PubKeyFromString(dve.PubKey)
if err != nil {
return fmt.Errorf("DuplicateVoteEvidence Error: pubkey[%v] to PubKey failed:%v", dve.PubKey, err)
}
sigA, err := ConsensusCrypto.SignatureFromBytes(dve.VoteA.Signature)
if err != nil {
return fmt.Errorf("DuplicateVoteEvidence Error: SIGA[%v] to signature failed:%v", dve.VoteA.Signature, err)
}
sigB, err := ConsensusCrypto.SignatureFromBytes(dve.VoteB.Signature)
if err != nil {
return fmt.Errorf("DuplicateVoteEvidence Error: SIGB[%v] to signature failed:%v", dve.VoteB.Signature, err)
}
vote := &Vote{
dve.VoteA,
}
if !pubkey.VerifyBytes(SignBytes(chainID, vote), sigA) {
return fmt.Errorf("DuplicateVoteEvidence Error verifying VoteA: %v", ErrVoteInvalidSignature)
}
vote = &Vote{
dve.VoteB,
}
if !pubkey.VerifyBytes(SignBytes(chainID, vote), sigB) {
return fmt.Errorf("DuplicateVoteEvidence Error verifying VoteB: %v", ErrVoteInvalidSignature)
}
return nil
}
// Equal checks if two pieces of evidence are equal.
func (dve *DuplicateVoteEvidence) Equal(ev Evidence) bool {
if _, ok := ev.(*DuplicateVoteEvidence); !ok {
return false
}
if dve == nil {
return false
}
// just check their hashes
return bytes.Equal(SimpleHashFromBinary(dve), SimpleHashFromBinary(ev.(*DuplicateVoteEvidence)))
}
// TypeName ...
func (dve *DuplicateVoteEvidence) TypeName() string {
return DuplicateVote
}
// Copy ...
func (dve *DuplicateVoteEvidence) Copy() Evidence {
return &DuplicateVoteEvidence{}
}
// SetChild ...
func (dve *DuplicateVoteEvidence) SetChild(child proto.Message) {
dve.DuplicateVoteEvidence = child.(*tmtypes.DuplicateVoteEvidence)
}
// Child ...
func (dve *DuplicateVoteEvidence) Child() proto.Message {
return dve.DuplicateVoteEvidence
}
// SimpleHashFromBinary ...
func SimpleHashFromBinary(item *DuplicateVoteEvidence) []byte {
bytes, e := json.Marshal(item)
if e != nil {
//commonlog.Error("SimpleHashFromBinary marshal failed", "type", item, "error", e)
panic(Fmt("SimpleHashFromBinary marshal failed, err:%v", e))
}
return crypto.Ripemd160(bytes)
}
// EvidenceEnvelope2Evidence ...
func EvidenceEnvelope2Evidence(envelope *tmtypes.EvidenceEnvelope) Evidence {
if v, ok := EvidenceType2Type[envelope.TypeName]; ok {
realMsg2 := reflect.New(v).Interface()
err := proto.Unmarshal(envelope.Data, realMsg2.(proto.Message))
if err != nil {
panic(Fmt("Evidence is not valid", "evidenceType", envelope.TypeName, "err", err))
}
if evidence, ok2 := EvidenceType2Obj[envelope.TypeName]; ok2 {
evidence = evidence.Copy()
evidence.SetChild(realMsg2.(proto.Message))
return evidence.(Evidence)
}
}
return nil
}
// MockGoodEvidence UNSTABLE
type MockGoodEvidence struct {
MGHeight int64
MGAddress []byte
MGIndex int
}
// NewMockGoodEvidence UNSTABLE
func NewMockGoodEvidence(height int64, index int, address []byte) MockGoodEvidence {
return MockGoodEvidence{height, address, index}
}
// Height ...
func (e MockGoodEvidence) Height() int64 { return e.MGHeight }
// Address ...
func (e MockGoodEvidence) Address() []byte { return e.MGAddress }
// Index ...
func (e MockGoodEvidence) Index() int { return e.MGIndex }
// Hash ...
func (e MockGoodEvidence) Hash() []byte {
return []byte(Fmt("%d-%d", e.MGHeight, e.MGIndex))
}
// Verify ...
func (e MockGoodEvidence) Verify(chainID string) error { return nil }
// Equal ...
func (e MockGoodEvidence) Equal(ev Evidence) bool {
e2 := ev.(MockGoodEvidence)
return e.MGHeight == e2.MGHeight &&
bytes.Equal(e.MGAddress, e2.MGAddress) &&
e.MGIndex == e2.MGIndex
}
func (e MockGoodEvidence) String() string {
return Fmt("GoodEvidence: %d/%s/%d", e.MGHeight, e.MGAddress, e.MGIndex)
}
// TypeName ...
func (e MockGoodEvidence) TypeName() string {
return MockGood
}
// Copy ...
func (e MockGoodEvidence) Copy() Evidence {
return &MockGoodEvidence{}
}
// SetChild ...
func (e MockGoodEvidence) SetChild(proto.Message) {}
// Child ...
func (e MockGoodEvidence) Child() proto.Message {
return nil
}
// MockBadEvidence UNSTABLE
type MockBadEvidence struct {
MockGoodEvidence
}
// Verify ...
func (e MockBadEvidence) Verify(chainID string) error { return fmt.Errorf("MockBadEvidence") }
// Equal ...
func (e MockBadEvidence) Equal(ev Evidence) bool {
e2 := ev.(MockBadEvidence)
return e.MGHeight == e2.MGHeight &&
bytes.Equal(e.MGAddress, e2.MGAddress) &&
e.MGIndex == e2.MGIndex
}
func (e MockBadEvidence) String() string {
return Fmt("BadEvidence: %d/%s/%d", e.MGHeight, e.MGAddress, e.MGIndex)
}
// TypeName ...
func (e MockBadEvidence) TypeName() string {
return MockBad
}
// Copy ...
func (e MockBadEvidence) Copy() Evidence {
return &MockBadEvidence{}
}
// SetChild ...
func (e MockBadEvidence) SetChild(proto.Message) {}
// Child ...
func (e MockBadEvidence) Child() proto.Message {
return nil
}
//------------------------------------------------------
// evidence pool
// EvidencePool defines the EvidencePool interface used by the ConsensusState.
// UNSTABLE
type EvidencePool interface {
PendingEvidence() []Evidence
AddEvidence(Evidence) error
Update(*TendermintBlock)
}
// MockEvidencePool is an empty implementation of a Mempool, useful for testing.
// UNSTABLE
type MockEvidencePool struct {
}
// PendingEvidence ...
func (m MockEvidencePool) PendingEvidence() []Evidence { return nil }
// AddEvidence ...
func (m MockEvidencePool) AddEvidence(Evidence) error { return nil }
// Update ...
func (m MockEvidencePool) Update(*TendermintBlock) {}
......@@ -32,7 +32,6 @@ const (
RoundStepCommit = RoundStepType(0x08) // Entered commit state machine
// NOTE: RoundStepNewHeight acts as RoundStepCommitWait.
EvidenceListID = byte(0x01)
NewRoundStepID = byte(0x02)
CommitStepID = byte(0x03)
ProposalID = byte(0x04)
......@@ -52,7 +51,6 @@ const (
// InitMessageMap ...
func InitMessageMap() {
MsgMap = map[byte]reflect.Type{
EvidenceListID: reflect.TypeOf(tmtypes.EvidenceData{}),
NewRoundStepID: reflect.TypeOf(tmtypes.NewRoundStepMsg{}),
CommitStepID: reflect.TypeOf(tmtypes.CommitStepMsg{}),
ProposalID: reflect.TypeOf(tmtypes.Proposal{}),
......
......@@ -25,6 +25,7 @@ var (
ErrVoteInvalidSignature = errors.New("Invalid signature")
ErrVoteInvalidBlockHash = errors.New("Invalid block hash")
ErrVoteNonDeterministicSignature = errors.New("Non-deterministic signature")
ErrVoteConflict = errors.New("Conflicting vote")
ErrVoteNil = errors.New("Nil vote")
votelog = log15.New("module", "tendermint-vote")
)
......@@ -119,34 +120,6 @@ func (heartbeat *Heartbeat) WriteSignBytes(chainID string, w io.Writer, n *int,
*err = writeErr
}
// ErrVoteConflictingVotes ...
type ErrVoteConflictingVotes struct {
*DuplicateVoteEvidence
}
func (err *ErrVoteConflictingVotes) Error() string {
pubkey, error := PubKeyFromString(err.PubKey)
if error != nil {
return fmt.Sprintf("Conflicting votes from validator PubKey:%v,error:%v", err.PubKey, error)
}
addr := GenAddressByPubKey(pubkey)
return fmt.Sprintf("Conflicting votes from validator %v", addr)
}
// NewConflictingVoteError ...
func NewConflictingVoteError(val *Validator, voteA, voteB *tmtypes.Vote) *ErrVoteConflictingVotes {
keyString := fmt.Sprintf("%X", val.PubKey)
return &ErrVoteConflictingVotes{
&DuplicateVoteEvidence{
&tmtypes.DuplicateVoteEvidence{
PubKey: keyString,
VoteA: voteA,
VoteB: voteB,
},
},
}
}
// Types of votes
// TODO Make a new type "VoteType"
const (
......
......@@ -200,7 +200,7 @@ func (voteSet *VoteSet) addVote(vote *Vote) (added bool, err error) {
// Add vote and get conflicting vote if any
added, conflicting := voteSet.addVerifiedVote(vote, blockKey, val.VotingPower)
if conflicting != nil {
return added, NewConflictingVoteError(val, conflicting.Vote, vote.Vote)
return added, errors.Wrapf(ErrVoteConflict, "Conflicting vote: %v; New vote: %v", conflicting, vote)
}
if !added {
PanicSanity("Expected to add non-conflicting vote")
......
......@@ -87,20 +87,6 @@ message State {
bytes AppHash = 12;
}
message DuplicateVoteEvidence {
string pubKey = 1;
Vote voteA = 2;
Vote voteB = 3;
}
message EvidenceEnvelope {
string typeName = 1;
bytes data = 2;
}
message EvidenceData {
repeated EvidenceEnvelope evidence = 1;
}
message TendermintBlockHeader {
string chainID = 1;
......@@ -115,14 +101,12 @@ message TendermintBlockHeader {
bytes consensusHash = 10;
bytes appHash = 11;
bytes lastResultsHash = 12;
bytes evidenceHash = 13;
bytes proposerAddr = 14;
bytes proposerAddr = 13;
}
message TendermintBlock {
TendermintBlockHeader header = 1;
Block data = 2;
EvidenceData evidence = 3;
TendermintCommit lastCommit = 4;
}
......
......@@ -22,9 +22,6 @@ It has these top-level messages:
Validator
ValidatorSet
State
DuplicateVoteEvidence
EvidenceEnvelope
EvidenceData
TendermintBlockHeader
TendermintBlock
Proposal
......@@ -525,78 +522,6 @@ func (m *State) GetAppHash() []byte {
return nil
}
type DuplicateVoteEvidence struct {
PubKey string `protobuf:"bytes,1,opt,name=pubKey" json:"pubKey,omitempty"`
VoteA *Vote `protobuf:"bytes,2,opt,name=voteA" json:"voteA,omitempty"`
VoteB *Vote `protobuf:"bytes,3,opt,name=voteB" json:"voteB,omitempty"`
}
func (m *DuplicateVoteEvidence) Reset() { *m = DuplicateVoteEvidence{} }
func (m *DuplicateVoteEvidence) String() string { return proto.CompactTextString(m) }
func (*DuplicateVoteEvidence) ProtoMessage() {}
func (*DuplicateVoteEvidence) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{13} }
func (m *DuplicateVoteEvidence) GetPubKey() string {
if m != nil {
return m.PubKey
}
return ""
}
func (m *DuplicateVoteEvidence) GetVoteA() *Vote {
if m != nil {
return m.VoteA
}
return nil
}
func (m *DuplicateVoteEvidence) GetVoteB() *Vote {
if m != nil {
return m.VoteB
}
return nil
}
type EvidenceEnvelope struct {
TypeName string `protobuf:"bytes,1,opt,name=typeName" json:"typeName,omitempty"`
Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"`
}
func (m *EvidenceEnvelope) Reset() { *m = EvidenceEnvelope{} }
func (m *EvidenceEnvelope) String() string { return proto.CompactTextString(m) }
func (*EvidenceEnvelope) ProtoMessage() {}
func (*EvidenceEnvelope) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{14} }
func (m *EvidenceEnvelope) GetTypeName() string {
if m != nil {
return m.TypeName
}
return ""
}
func (m *EvidenceEnvelope) GetData() []byte {
if m != nil {
return m.Data
}
return nil
}
type EvidenceData struct {
Evidence []*EvidenceEnvelope `protobuf:"bytes,1,rep,name=evidence" json:"evidence,omitempty"`
}
func (m *EvidenceData) Reset() { *m = EvidenceData{} }
func (m *EvidenceData) String() string { return proto.CompactTextString(m) }
func (*EvidenceData) ProtoMessage() {}
func (*EvidenceData) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{15} }
func (m *EvidenceData) GetEvidence() []*EvidenceEnvelope {
if m != nil {
return m.Evidence
}
return nil
}
type TendermintBlockHeader struct {
ChainID string `protobuf:"bytes,1,opt,name=chainID" json:"chainID,omitempty"`
Height int64 `protobuf:"varint,2,opt,name=height" json:"height,omitempty"`
......@@ -610,14 +535,13 @@ type TendermintBlockHeader struct {
ConsensusHash []byte `protobuf:"bytes,10,opt,name=consensusHash,proto3" json:"consensusHash,omitempty"`
AppHash []byte `protobuf:"bytes,11,opt,name=appHash,proto3" json:"appHash,omitempty"`
LastResultsHash []byte `protobuf:"bytes,12,opt,name=lastResultsHash,proto3" json:"lastResultsHash,omitempty"`
EvidenceHash []byte `protobuf:"bytes,13,opt,name=evidenceHash,proto3" json:"evidenceHash,omitempty"`
ProposerAddr []byte `protobuf:"bytes,14,opt,name=proposerAddr,proto3" json:"proposerAddr,omitempty"`
ProposerAddr []byte `protobuf:"bytes,13,opt,name=proposerAddr,proto3" json:"proposerAddr,omitempty"`
}
func (m *TendermintBlockHeader) Reset() { *m = TendermintBlockHeader{} }
func (m *TendermintBlockHeader) String() string { return proto.CompactTextString(m) }
func (*TendermintBlockHeader) ProtoMessage() {}
func (*TendermintBlockHeader) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{16} }
func (*TendermintBlockHeader) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{13} }
func (m *TendermintBlockHeader) GetChainID() string {
if m != nil {
......@@ -703,13 +627,6 @@ func (m *TendermintBlockHeader) GetLastResultsHash() []byte {
return nil
}
func (m *TendermintBlockHeader) GetEvidenceHash() []byte {
if m != nil {
return m.EvidenceHash
}
return nil
}
func (m *TendermintBlockHeader) GetProposerAddr() []byte {
if m != nil {
return m.ProposerAddr
......@@ -720,14 +637,13 @@ func (m *TendermintBlockHeader) GetProposerAddr() []byte {
type TendermintBlock struct {
Header *TendermintBlockHeader `protobuf:"bytes,1,opt,name=header" json:"header,omitempty"`
Data *types3.Block `protobuf:"bytes,2,opt,name=data" json:"data,omitempty"`
Evidence *EvidenceData `protobuf:"bytes,3,opt,name=evidence" json:"evidence,omitempty"`
LastCommit *TendermintCommit `protobuf:"bytes,4,opt,name=lastCommit" json:"lastCommit,omitempty"`
}
func (m *TendermintBlock) Reset() { *m = TendermintBlock{} }
func (m *TendermintBlock) String() string { return proto.CompactTextString(m) }
func (*TendermintBlock) ProtoMessage() {}
func (*TendermintBlock) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{17} }
func (*TendermintBlock) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{14} }
func (m *TendermintBlock) GetHeader() *TendermintBlockHeader {
if m != nil {
......@@ -743,13 +659,6 @@ func (m *TendermintBlock) GetData() *types3.Block {
return nil
}
func (m *TendermintBlock) GetEvidence() *EvidenceData {
if m != nil {
return m.Evidence
}
return nil
}
func (m *TendermintBlock) GetLastCommit() *TendermintCommit {
if m != nil {
return m.LastCommit
......@@ -770,7 +679,7 @@ type Proposal struct {
func (m *Proposal) Reset() { *m = Proposal{} }
func (m *Proposal) String() string { return proto.CompactTextString(m) }
func (*Proposal) ProtoMessage() {}
func (*Proposal) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{18} }
func (*Proposal) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{15} }
func (m *Proposal) GetHeight() int64 {
if m != nil {
......@@ -832,7 +741,7 @@ type NewRoundStepMsg struct {
func (m *NewRoundStepMsg) Reset() { *m = NewRoundStepMsg{} }
func (m *NewRoundStepMsg) String() string { return proto.CompactTextString(m) }
func (*NewRoundStepMsg) ProtoMessage() {}
func (*NewRoundStepMsg) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{19} }
func (*NewRoundStepMsg) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{16} }
func (m *NewRoundStepMsg) GetHeight() int64 {
if m != nil {
......@@ -879,7 +788,7 @@ type ValidBlockMsg struct {
func (m *ValidBlockMsg) Reset() { *m = ValidBlockMsg{} }
func (m *ValidBlockMsg) String() string { return proto.CompactTextString(m) }
func (*ValidBlockMsg) ProtoMessage() {}
func (*ValidBlockMsg) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{20} }
func (*ValidBlockMsg) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{17} }
func (m *ValidBlockMsg) GetHeight() int64 {
if m != nil {
......@@ -916,7 +825,7 @@ type CommitStepMsg struct {
func (m *CommitStepMsg) Reset() { *m = CommitStepMsg{} }
func (m *CommitStepMsg) String() string { return proto.CompactTextString(m) }
func (*CommitStepMsg) ProtoMessage() {}
func (*CommitStepMsg) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{21} }
func (*CommitStepMsg) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{18} }
func (m *CommitStepMsg) GetHeight() int64 {
if m != nil {
......@@ -934,7 +843,7 @@ type ProposalPOLMsg struct {
func (m *ProposalPOLMsg) Reset() { *m = ProposalPOLMsg{} }
func (m *ProposalPOLMsg) String() string { return proto.CompactTextString(m) }
func (*ProposalPOLMsg) ProtoMessage() {}
func (*ProposalPOLMsg) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{22} }
func (*ProposalPOLMsg) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{19} }
func (m *ProposalPOLMsg) GetHeight() int64 {
if m != nil {
......@@ -967,7 +876,7 @@ type HasVoteMsg struct {
func (m *HasVoteMsg) Reset() { *m = HasVoteMsg{} }
func (m *HasVoteMsg) String() string { return proto.CompactTextString(m) }
func (*HasVoteMsg) ProtoMessage() {}
func (*HasVoteMsg) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{23} }
func (*HasVoteMsg) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{20} }
func (m *HasVoteMsg) GetHeight() int64 {
if m != nil {
......@@ -1007,7 +916,7 @@ type VoteSetMaj23Msg struct {
func (m *VoteSetMaj23Msg) Reset() { *m = VoteSetMaj23Msg{} }
func (m *VoteSetMaj23Msg) String() string { return proto.CompactTextString(m) }
func (*VoteSetMaj23Msg) ProtoMessage() {}
func (*VoteSetMaj23Msg) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{24} }
func (*VoteSetMaj23Msg) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{21} }
func (m *VoteSetMaj23Msg) GetHeight() int64 {
if m != nil {
......@@ -1048,7 +957,7 @@ type VoteSetBitsMsg struct {
func (m *VoteSetBitsMsg) Reset() { *m = VoteSetBitsMsg{} }
func (m *VoteSetBitsMsg) String() string { return proto.CompactTextString(m) }
func (*VoteSetBitsMsg) ProtoMessage() {}
func (*VoteSetBitsMsg) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{25} }
func (*VoteSetBitsMsg) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{22} }
func (m *VoteSetBitsMsg) GetHeight() int64 {
if m != nil {
......@@ -1097,7 +1006,7 @@ type Heartbeat struct {
func (m *Heartbeat) Reset() { *m = Heartbeat{} }
func (m *Heartbeat) String() string { return proto.CompactTextString(m) }
func (*Heartbeat) ProtoMessage() {}
func (*Heartbeat) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{26} }
func (*Heartbeat) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{23} }
func (m *Heartbeat) GetValidatorAddress() []byte {
if m != nil {
......@@ -1148,7 +1057,7 @@ type IsHealthy struct {
func (m *IsHealthy) Reset() { *m = IsHealthy{} }
func (m *IsHealthy) String() string { return proto.CompactTextString(m) }
func (*IsHealthy) ProtoMessage() {}
func (*IsHealthy) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{27} }
func (*IsHealthy) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{24} }
func (m *IsHealthy) GetIsHealthy() bool {
if m != nil {
......@@ -1171,9 +1080,6 @@ func init() {
proto.RegisterType((*Validator)(nil), "types.Validator")
proto.RegisterType((*ValidatorSet)(nil), "types.ValidatorSet")
proto.RegisterType((*State)(nil), "types.State")
proto.RegisterType((*DuplicateVoteEvidence)(nil), "types.DuplicateVoteEvidence")
proto.RegisterType((*EvidenceEnvelope)(nil), "types.EvidenceEnvelope")
proto.RegisterType((*EvidenceData)(nil), "types.EvidenceData")
proto.RegisterType((*TendermintBlockHeader)(nil), "types.TendermintBlockHeader")
proto.RegisterType((*TendermintBlock)(nil), "types.TendermintBlock")
proto.RegisterType((*Proposal)(nil), "types.Proposal")
......@@ -1191,97 +1097,89 @@ func init() {
func init() { proto.RegisterFile("tendermint.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{
// 1460 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x58, 0xdd, 0x6e, 0x1b, 0xb7,
0x12, 0xc6, 0x5a, 0x3f, 0x96, 0x46, 0xb2, 0x2c, 0x30, 0xc7, 0x89, 0x4e, 0x8e, 0x0f, 0xa0, 0x12,
0xfd, 0x51, 0x93, 0xc0, 0x09, 0xec, 0x00, 0xbd, 0x48, 0x53, 0xc4, 0xb2, 0x83, 0xd8, 0xad, 0x9d,
0x08, 0x94, 0x90, 0x5e, 0xd3, 0x12, 0x2b, 0x6d, 0x2b, 0xed, 0x6e, 0x97, 0x94, 0x62, 0x17, 0xe8,
0x4d, 0xdf, 0xa0, 0x40, 0x5f, 0xa3, 0x57, 0x7d, 0x88, 0x3e, 0x40, 0xd1, 0xab, 0xa2, 0x7d, 0x96,
0x82, 0x43, 0xee, 0x8a, 0xbb, 0x52, 0x9c, 0xa6, 0x28, 0x7a, 0xa7, 0xf9, 0xf8, 0x91, 0x43, 0xce,
0x7c, 0x33, 0xe4, 0x0a, 0x9a, 0x4a, 0x04, 0x23, 0x11, 0xcf, 0xfc, 0x40, 0xed, 0x45, 0x71, 0xa8,
0x42, 0x52, 0x52, 0x57, 0x91, 0x90, 0xb7, 0x9b, 0x17, 0xd3, 0x70, 0xf8, 0xd5, 0x70, 0xc2, 0xfd,
0xc0, 0x0c, 0xd0, 0xff, 0xc3, 0x66, 0x57, 0x63, 0xa7, 0xc7, 0x84, 0x40, 0xf1, 0x84, 0xcb, 0x49,
0xcb, 0x6b, 0x7b, 0x9d, 0x3a, 0xc3, 0xdf, 0xf4, 0x13, 0x20, 0x83, 0x74, 0xad, 0xae, 0xaf, 0x0e,
0xe3, 0x98, 0x5f, 0x69, 0x66, 0xd7, 0x57, 0x12, 0x99, 0x25, 0x86, 0xbf, 0xc9, 0x7f, 0xa0, 0xf4,
0x74, 0x2a, 0x66, 0xb2, 0xb5, 0xd1, 0x2e, 0x74, 0x8a, 0xcc, 0x18, 0xf4, 0xbb, 0x0d, 0x28, 0xbe,
0x0c, 0x95, 0x20, 0x77, 0xa0, 0xf9, 0x92, 0x4f, 0xfd, 0x11, 0x57, 0x61, 0x7c, 0x38, 0x1a, 0xc5,
0x42, 0x4a, 0xeb, 0x68, 0x05, 0x27, 0xef, 0x43, 0x23, 0xc5, 0x4e, 0x83, 0x91, 0xb8, 0x6c, 0x6d,
0xa0, 0xa3, 0x1c, 0x4a, 0x6e, 0x42, 0xf9, 0x44, 0xf8, 0xe3, 0x89, 0x6a, 0x15, 0xda, 0x5e, 0xa7,
0xc0, 0xac, 0xa5, 0xb7, 0xc2, 0xc2, 0x79, 0x30, 0x6a, 0x15, 0x71, 0x9a, 0x31, 0xc8, 0x2e, 0x54,
0x07, 0xfe, 0x4c, 0x48, 0xc5, 0x67, 0x51, 0xab, 0x84, 0x13, 0x96, 0x80, 0x3e, 0xd2, 0xe0, 0x2a,
0x12, 0xad, 0x72, 0xdb, 0xeb, 0x6c, 0x31, 0xfc, 0x4d, 0x3a, 0x69, 0x6c, 0x5a, 0x9b, 0x6d, 0xaf,
0x53, 0xdb, 0x6f, 0xec, 0x61, 0x18, 0xf7, 0x2c, 0xca, 0xd2, 0xd0, 0xed, 0x42, 0xb5, 0xef, 0x8f,
0x03, 0xae, 0xe6, 0xb1, 0x68, 0x55, 0xf0, 0x58, 0x4b, 0x80, 0xfa, 0xd0, 0x5c, 0x06, 0xf1, 0x28,
0x9c, 0xcd, 0x7c, 0xe5, 0xae, 0xed, 0x5d, 0xbf, 0xf6, 0x5d, 0x80, 0x5e, 0x2c, 0x86, 0x38, 0xcd,
0x44, 0xb7, 0xb6, 0x5f, 0xb3, 0x64, 0x1d, 0x5a, 0xe6, 0x0c, 0xd3, 0x1f, 0x3c, 0xb8, 0xe1, 0x24,
0x0c, 0x97, 0x08, 0xbe, 0x08, 0x09, 0x85, 0x52, 0x5f, 0x71, 0x25, 0x30, 0x92, 0xb5, 0xfd, 0xba,
0x9d, 0x8f, 0x18, 0x33, 0x43, 0xe4, 0x2e, 0x54, 0x7a, 0x71, 0x18, 0x85, 0x92, 0x4f, 0x31, 0xa0,
0xb5, 0xfd, 0x6d, 0x4b, 0x4b, 0x60, 0x96, 0x12, 0xc8, 0x3d, 0x28, 0xa1, 0x96, 0x30, 0xc6, 0xb5,
0xfd, 0x9b, 0x96, 0x99, 0xf3, 0xcd, 0x0c, 0x89, 0x7e, 0x0e, 0x55, 0xb4, 0xfb, 0xfe, 0x37, 0x82,
0xdc, 0x86, 0xca, 0x39, 0xbf, 0xec, 0x5e, 0x29, 0x91, 0x28, 0x28, 0xb5, 0x75, 0x4a, 0xcf, 0xf9,
0xe5, 0xe0, 0x52, 0xda, 0x94, 0x5b, 0xcb, 0xe2, 0xcf, 0xb8, 0x4c, 0x52, 0x6d, 0x2c, 0xfa, 0x31,
0x94, 0x07, 0x97, 0x7f, 0x71, 0x55, 0x3d, 0x7b, 0x23, 0x33, 0xfb, 0x31, 0xd4, 0x70, 0x5b, 0xcf,
0x42, 0x29, 0xfd, 0x88, 0xec, 0x01, 0x41, 0xb3, 0xc7, 0x63, 0xa5, 0xd7, 0x74, 0x17, 0x5b, 0x33,
0x42, 0x3b, 0xd0, 0x78, 0xba, 0xf0, 0x47, 0x22, 0x18, 0x8a, 0x1e, 0x8f, 0xf9, 0x2c, 0x71, 0x74,
0x38, 0x16, 0x38, 0xcb, 0x38, 0x3a, 0x1c, 0x0b, 0xfa, 0xbb, 0x07, 0xdb, 0x47, 0x61, 0x20, 0x45,
0x20, 0xe7, 0xd2, 0x72, 0xf7, 0x9c, 0x98, 0x58, 0x0d, 0x34, 0x5d, 0x0d, 0x68, 0x9c, 0x39, 0x61,
0x7b, 0x2f, 0x39, 0xaa, 0xcd, 0xe1, 0x56, 0x12, 0x72, 0x04, 0x59, 0x12, 0x87, 0x87, 0x99, 0x33,
0xd9, 0x44, 0x12, 0x77, 0x61, 0x33, 0xc2, 0x32, 0x47, 0x7f, 0x9c, 0x3f, 0x8a, 0xcd, 0xeb, 0x8e,
0x9d, 0x98, 0x1d, 0x64, 0x39, 0x32, 0x9d, 0x43, 0x35, 0xad, 0x4d, 0xd2, 0x82, 0xcd, 0x6c, 0x85,
0x27, 0xa6, 0x0e, 0x4f, 0x6f, 0x7e, 0xf1, 0x99, 0xb8, 0xc2, 0x23, 0xd4, 0x99, 0xb5, 0x48, 0x1b,
0x6a, 0x2f, 0x43, 0xe5, 0x07, 0xe3, 0x5e, 0xf8, 0x4a, 0xc4, 0x36, 0xc5, 0x2e, 0xa4, 0x4b, 0xfa,
0x70, 0x38, 0x9c, 0xcf, 0x70, 0x5b, 0x05, 0x66, 0x0c, 0x1a, 0x40, 0x3d, 0x75, 0xdb, 0x17, 0x8a,
0x3c, 0x00, 0x48, 0x6d, 0xed, 0xbc, 0xe0, 0xc4, 0x34, 0x1d, 0x60, 0x0e, 0x87, 0xdc, 0x4b, 0x34,
0x2f, 0x62, 0x1b, 0xd6, 0x55, 0x7e, 0xca, 0xa0, 0xbf, 0x16, 0x6d, 0x19, 0xe9, 0x33, 0x1e, 0xe9,
0x2e, 0x6a, 0xcb, 0xb7, 0xca, 0x12, 0x93, 0x74, 0x60, 0xfb, 0x8c, 0x4b, 0x23, 0x7f, 0xdb, 0x9d,
0x8c, 0xe8, 0xf2, 0xb0, 0x6e, 0x89, 0x29, 0x34, 0x08, 0x15, 0x9f, 0x0e, 0x2e, 0xed, 0xd1, 0x57,
0x70, 0xf2, 0x00, 0x6a, 0x29, 0x76, 0x7a, 0x6c, 0x93, 0x93, 0x6f, 0x19, 0x2e, 0x85, 0xbc, 0x0b,
0x5b, 0xcb, 0x55, 0xfc, 0x99, 0xb0, 0x2d, 0x2f, 0x0b, 0x92, 0x83, 0x4c, 0xc4, 0xca, 0xb8, 0xec,
0x8d, 0x7c, 0x04, 0xfa, 0x42, 0x65, 0x82, 0xf6, 0x08, 0x1a, 0x7a, 0x15, 0x67, 0xe2, 0xe6, 0xeb,
0x27, 0xe6, 0xa8, 0xe4, 0x09, 0xfc, 0x4f, 0x23, 0x26, 0x06, 0x4b, 0xfc, 0x68, 0xc2, 0x83, 0xb1,
0x18, 0x61, 0xf3, 0x2c, 0xb0, 0xeb, 0x28, 0xe4, 0xc9, 0x4a, 0x2d, 0xb5, 0xaa, 0x99, 0x26, 0x94,
0x1b, 0x65, 0x2b, 0xa5, 0xf7, 0x29, 0xb4, 0x97, 0x0e, 0x72, 0x83, 0xc9, 0x46, 0x00, 0x37, 0xf2,
0x46, 0x5e, 0x92, 0x6f, 0x26, 0xe4, 0x7c, 0xaa, 0x24, 0x5e, 0xa0, 0x35, 0x14, 0x77, 0x1e, 0xc6,
0xba, 0x88, 0x22, 0x64, 0xd4, 0x6d, 0x5d, 0x18, 0x93, 0xce, 0x61, 0xe7, 0x78, 0x1e, 0x4d, 0xfd,
0x21, 0x57, 0x42, 0xb7, 0xf4, 0xa4, 0xba, 0x74, 0xc1, 0x44, 0xa6, 0x60, 0x8c, 0xca, 0xac, 0x45,
0xde, 0x81, 0xd2, 0x22, 0x54, 0xe2, 0xd0, 0x6a, 0x36, 0x73, 0x1d, 0x98, 0x91, 0x84, 0xd2, 0xb5,
0x1d, 0x60, 0x95, 0xd2, 0xa5, 0x5d, 0x68, 0x26, 0x9e, 0x9e, 0x06, 0x0b, 0x31, 0x0d, 0x23, 0x6c,
0xa3, 0x9a, 0xf8, 0x9c, 0xcf, 0x84, 0xf5, 0x99, 0xda, 0xfa, 0x8e, 0x1c, 0x71, 0xc5, 0x6d, 0xf1,
0xe2, 0x6f, 0x7a, 0x04, 0xf5, 0x64, 0x8d, 0x63, 0xae, 0x38, 0x39, 0x80, 0x8a, 0xb0, 0xb6, 0x2d,
0xc0, 0x5b, 0xb9, 0x16, 0x92, 0xb8, 0x62, 0x29, 0x91, 0xfe, 0x56, 0x80, 0x9d, 0xdc, 0xcd, 0x71,
0x22, 0xf8, 0x48, 0x60, 0x2f, 0x19, 0x66, 0xeb, 0xcc, 0x9a, 0x3a, 0x34, 0x13, 0xb7, 0xbc, 0xac,
0xa5, 0x3b, 0x45, 0x8c, 0x97, 0xbf, 0x29, 0x25, 0x63, 0xe8, 0xad, 0x2b, 0x5d, 0x04, 0xa6, 0x7d,
0xe0, 0x6f, 0xbd, 0x42, 0x30, 0x9f, 0xe9, 0xbb, 0xc6, 0x94, 0x86, 0xb5, 0x74, 0xad, 0x4d, 0x9d,
0x5a, 0x2b, 0xaf, 0xaf, 0x35, 0x87, 0x82, 0x41, 0x33, 0x85, 0x6a, 0x4a, 0xa1, 0xc0, 0x52, 0x5b,
0x3f, 0x66, 0x34, 0xd5, 0x5c, 0xfb, 0x98, 0x7c, 0xf3, 0x3e, 0xc8, 0xa1, 0x9a, 0xb7, 0x48, 0xa5,
0x8e, 0xbc, 0xaa, 0xe1, 0x65, 0x51, 0x5d, 0xd7, 0xc3, 0x44, 0x89, 0x48, 0x03, 0xa4, 0x65, 0x41,
0x1d, 0x37, 0x6e, 0xb5, 0x66, 0xd4, 0x98, 0x98, 0x5a, 0xaf, 0xd3, 0x9c, 0x5e, 0x8d, 0x1a, 0xf3,
0x30, 0xa1, 0x50, 0x4f, 0x32, 0x84, 0xb4, 0x2d, 0xa4, 0x65, 0x30, 0xcd, 0x89, 0x6c, 0x77, 0xd4,
0x4d, 0xbe, 0xd5, 0x30, 0x1c, 0x17, 0xa3, 0xbf, 0x78, 0xb0, 0x9d, 0xcb, 0x2e, 0x79, 0xa8, 0xb3,
0xa7, 0x33, 0x6c, 0x6f, 0xbe, 0xdd, 0xf5, 0xef, 0x07, 0xa3, 0x02, 0x66, 0xb9, 0xa4, 0xed, 0x08,
0x70, 0xf9, 0x88, 0x31, 0x2f, 0x0d, 0x1c, 0x21, 0xf7, 0x1d, 0xf9, 0x15, 0x32, 0x4d, 0xc9, 0x55,
0xe9, 0x52, 0x7a, 0xe4, 0x23, 0x80, 0x65, 0x22, 0x6c, 0x5f, 0xbd, 0xb5, 0xb2, 0x19, 0x33, 0xcc,
0x1c, 0x2a, 0xfd, 0xc3, 0x5b, 0x3e, 0x97, 0x1c, 0x31, 0x7a, 0xeb, 0xc5, 0x68, 0x5e, 0x33, 0x56,
0x8c, 0xbb, 0x50, 0x55, 0xe9, 0x4b, 0xd4, 0xc8, 0x74, 0x09, 0x68, 0x31, 0xf5, 0x5e, 0x9c, 0xb9,
0x0f, 0xd8, 0xd4, 0x26, 0x7b, 0x00, 0xbd, 0x17, 0x67, 0x89, 0x32, 0x4b, 0x6b, 0x95, 0xe9, 0x30,
0xb4, 0x27, 0x99, 0xbe, 0x4b, 0xcb, 0xe6, 0x5d, 0x9a, 0x02, 0x7a, 0x14, 0x9f, 0x67, 0x13, 0x9d,
0xdd, 0x4d, 0x33, 0x9a, 0x02, 0xf4, 0x27, 0x0f, 0xb6, 0x9f, 0x8b, 0x57, 0xe8, 0xb8, 0xaf, 0x44,
0x74, 0x2e, 0xc7, 0x6f, 0x79, 0x4e, 0x02, 0x45, 0xa9, 0x84, 0x39, 0x62, 0x89, 0xe1, 0x6f, 0xf2,
0x10, 0x76, 0xa4, 0x18, 0x86, 0xc1, 0x48, 0xf6, 0xfd, 0x60, 0x28, 0xfa, 0x8a, 0xc7, 0x6a, 0x90,
0x54, 0x66, 0x89, 0xad, 0x1f, 0x4c, 0x44, 0x6b, 0xd3, 0x80, 0x9e, 0x4a, 0xc8, 0xcf, 0xc3, 0xf4,
0x15, 0x6c, 0xe1, 0x8d, 0x81, 0x11, 0x78, 0xfb, 0x2d, 0x67, 0x42, 0x52, 0xc8, 0x85, 0x44, 0xa7,
0xc6, 0x97, 0x8e, 0x54, 0x2a, 0x2c, 0xb5, 0xe9, 0x07, 0xb0, 0x65, 0x7e, 0xbd, 0x21, 0x56, 0xf4,
0x7b, 0x0f, 0x1a, 0x89, 0x70, 0x7a, 0x2f, 0xce, 0xae, 0xdb, 0xe3, 0x1d, 0x68, 0x46, 0x4b, 0x26,
0x73, 0xb6, 0xbb, 0x82, 0x93, 0x47, 0x50, 0x73, 0x30, 0x2b, 0xfe, 0xff, 0xae, 0x96, 0x95, 0xfd,
0x86, 0x63, 0x2e, 0x9b, 0x8e, 0x00, 0x4e, 0xb8, 0xd4, 0x77, 0xc3, 0xdf, 0xca, 0xb2, 0x76, 0x92,
0x64, 0x59, 0xff, 0xd6, 0x4c, 0x1f, 0x3f, 0xdc, 0xec, 0x17, 0x18, 0x1a, 0xf4, 0x5b, 0xd8, 0xd6,
0x2e, 0xfa, 0x42, 0x9d, 0xf3, 0x2f, 0xf7, 0x0f, 0xfe, 0x19, 0x57, 0x1d, 0xd8, 0xbc, 0xb8, 0xf6,
0x55, 0x94, 0x0c, 0xd3, 0x1f, 0x3d, 0x68, 0x58, 0xff, 0xfa, 0x8b, 0xf5, 0x5f, 0x76, 0x4f, 0xee,
0x9b, 0x0b, 0x59, 0xda, 0xb2, 0xbd, 0x26, 0x35, 0x86, 0x47, 0x7f, 0xf6, 0xa0, 0x7a, 0x22, 0x78,
0xac, 0x2e, 0x04, 0x47, 0x2d, 0x2c, 0x5e, 0xf3, 0x01, 0xbd, 0x58, 0xf3, 0x01, 0xbd, 0x58, 0xfb,
0x01, 0xbd, 0x58, 0xf9, 0x80, 0x9e, 0x64, 0x3e, 0xa0, 0xf3, 0xc7, 0x2f, 0xba, 0xc7, 0xbf, 0x0d,
0x15, 0x29, 0xbe, 0x9e, 0x63, 0x6f, 0x35, 0xd5, 0x97, 0xda, 0xd7, 0x37, 0x1a, 0xfa, 0x21, 0x54,
0x4f, 0xe5, 0x89, 0xe0, 0x53, 0x35, 0xb9, 0xd2, 0x54, 0x3f, 0x31, 0xf0, 0x04, 0x15, 0xb6, 0x04,
0x2e, 0xca, 0xf8, 0xb7, 0xc4, 0xc1, 0x9f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x85, 0xaa, 0x95, 0x93,
0xc3, 0x10, 0x00, 0x00,
// 1339 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x57, 0xdd, 0x6e, 0x1b, 0x45,
0x14, 0xd6, 0xc6, 0x3f, 0x89, 0x8f, 0xf3, 0xa7, 0x29, 0x2d, 0xa6, 0x04, 0xc9, 0x1a, 0xf1, 0x63,
0xda, 0x2a, 0x54, 0x69, 0x25, 0x2e, 0x4a, 0x51, 0x93, 0xb4, 0x6a, 0x02, 0x09, 0xb5, 0xc6, 0x56,
0xb9, 0x9e, 0xd8, 0x83, 0xbd, 0x60, 0xef, 0x9a, 0x9d, 0xb1, 0x1b, 0x23, 0x71, 0xc3, 0x1b, 0x20,
0xf1, 0x04, 0xdc, 0x73, 0xc5, 0x05, 0x8f, 0xc0, 0x13, 0x70, 0x09, 0xcf, 0x82, 0xce, 0x99, 0xd9,
0xf5, 0xec, 0xda, 0x4d, 0x29, 0x42, 0xdc, 0xed, 0xf9, 0xce, 0x37, 0x73, 0xf6, 0xfc, 0xce, 0x0c,
0xec, 0x1a, 0x15, 0xf5, 0x55, 0x32, 0x0e, 0x23, 0xb3, 0x3f, 0x49, 0x62, 0x13, 0xb3, 0x8a, 0x99,
0x4f, 0x94, 0xbe, 0xb9, 0x7b, 0x31, 0x8a, 0x7b, 0xdf, 0xf4, 0x86, 0x32, 0x8c, 0xac, 0x82, 0xbf,
0x03, 0xeb, 0x47, 0x88, 0x9d, 0x3e, 0x66, 0x0c, 0xca, 0x27, 0x52, 0x0f, 0x1b, 0x41, 0x33, 0x68,
0x6d, 0x0a, 0xfa, 0xe6, 0x9f, 0x02, 0xeb, 0x66, 0x7b, 0x1d, 0x85, 0xe6, 0x30, 0x49, 0xe4, 0x1c,
0x99, 0x47, 0xa1, 0xd1, 0xc4, 0xac, 0x08, 0xfa, 0x66, 0x6f, 0x40, 0xe5, 0xc9, 0x48, 0x8d, 0x75,
0x63, 0xad, 0x59, 0x6a, 0x95, 0x85, 0x15, 0xf8, 0x0f, 0x6b, 0x50, 0x7e, 0x1e, 0x1b, 0xc5, 0x6e,
0xc1, 0xee, 0x73, 0x39, 0x0a, 0xfb, 0xd2, 0xc4, 0xc9, 0x61, 0xbf, 0x9f, 0x28, 0xad, 0x9d, 0xa1,
0x25, 0x9c, 0xbd, 0x0f, 0xdb, 0x19, 0x76, 0x1a, 0xf5, 0xd5, 0x65, 0x63, 0x8d, 0x0c, 0x15, 0x50,
0x76, 0x03, 0xaa, 0x27, 0x2a, 0x1c, 0x0c, 0x4d, 0xa3, 0xd4, 0x0c, 0x5a, 0x25, 0xe1, 0x24, 0xfc,
0x15, 0x11, 0x4f, 0xa3, 0x7e, 0xa3, 0x4c, 0xcb, 0xac, 0xc0, 0xf6, 0xa0, 0xd6, 0x0d, 0xc7, 0x4a,
0x1b, 0x39, 0x9e, 0x34, 0x2a, 0xb4, 0x60, 0x01, 0xa0, 0x4b, 0xdd, 0xf9, 0x44, 0x35, 0xaa, 0xcd,
0xa0, 0xb5, 0x25, 0xe8, 0x9b, 0xb5, 0xb2, 0xd8, 0x34, 0xd6, 0x9b, 0x41, 0xab, 0x7e, 0xb0, 0xbd,
0x4f, 0x61, 0xdc, 0x77, 0xa8, 0xc8, 0x42, 0xb7, 0x07, 0xb5, 0x4e, 0x38, 0x88, 0xa4, 0x99, 0x26,
0xaa, 0xb1, 0x41, 0x6e, 0x2d, 0x00, 0x1e, 0xc2, 0xee, 0x22, 0x88, 0xc7, 0xf1, 0x78, 0x1c, 0x1a,
0x7f, 0xef, 0xe0, 0xea, 0xbd, 0x6f, 0x03, 0xb4, 0x13, 0xd5, 0xa3, 0x65, 0x36, 0xba, 0xf5, 0x83,
0xba, 0x23, 0x63, 0x68, 0x85, 0xa7, 0xe6, 0x3f, 0x05, 0x70, 0xcd, 0x4b, 0x18, 0x6d, 0x11, 0x7d,
0x15, 0x33, 0x0e, 0x95, 0x8e, 0x91, 0x46, 0x51, 0x24, 0xeb, 0x07, 0x9b, 0x6e, 0x3d, 0x61, 0xc2,
0xaa, 0xd8, 0x6d, 0xd8, 0x68, 0x27, 0xf1, 0x24, 0xd6, 0x72, 0x44, 0x01, 0xad, 0x1f, 0xec, 0x38,
0x5a, 0x0a, 0x8b, 0x8c, 0xc0, 0xee, 0x40, 0x85, 0x6a, 0x89, 0x62, 0x5c, 0x3f, 0xb8, 0xe1, 0x98,
0x05, 0xdb, 0xc2, 0x92, 0xf8, 0x97, 0x50, 0x23, 0xb9, 0x13, 0x7e, 0xa7, 0xd8, 0x4d, 0xd8, 0x38,
0x97, 0x97, 0x47, 0x73, 0xa3, 0xd2, 0x0a, 0xca, 0x64, 0x4c, 0xe9, 0xb9, 0xbc, 0xec, 0x5e, 0x6a,
0x97, 0x72, 0x27, 0x39, 0xfc, 0xa9, 0xd4, 0x69, 0xaa, 0xad, 0xc4, 0x3f, 0x81, 0x6a, 0xf7, 0xf2,
0x1f, 0xee, 0x8a, 0xab, 0xd7, 0x72, 0xab, 0x1f, 0x42, 0x9d, 0x7e, 0xeb, 0x69, 0xac, 0x75, 0x38,
0x61, 0xfb, 0xc0, 0x48, 0x6c, 0xcb, 0xc4, 0xe0, 0x9e, 0xfe, 0x66, 0x2b, 0x34, 0xbc, 0x05, 0xdb,
0x4f, 0x66, 0x61, 0x5f, 0x45, 0x3d, 0xd5, 0x96, 0x89, 0x1c, 0xa7, 0x86, 0x0e, 0x07, 0x8a, 0x56,
0x59, 0x43, 0x87, 0x03, 0xc5, 0xff, 0x0c, 0x60, 0xe7, 0x38, 0x8e, 0xb4, 0x8a, 0xf4, 0x54, 0x3b,
0xee, 0xbe, 0x17, 0x13, 0x57, 0x03, 0xbb, 0x7e, 0x0d, 0x20, 0x2e, 0xbc, 0xb0, 0xbd, 0x97, 0xba,
0xea, 0x72, 0xb8, 0x95, 0x86, 0x9c, 0x40, 0x91, 0xc6, 0xe1, 0x7e, 0xce, 0x27, 0x97, 0x48, 0xe6,
0x6f, 0x6c, 0x35, 0x22, 0xe7, 0xfa, 0xc3, 0xa2, 0x2b, 0x2e, 0xaf, 0xd7, 0xdd, 0xc2, 0xbc, 0x52,
0x14, 0xc8, 0x7c, 0x0a, 0xb5, 0xac, 0x37, 0x59, 0x03, 0xd6, 0xf3, 0x1d, 0x9e, 0x8a, 0x18, 0x9e,
0xf6, 0xf4, 0xe2, 0x73, 0x35, 0x27, 0x17, 0x36, 0x85, 0x93, 0x58, 0x13, 0xea, 0xcf, 0x63, 0x13,
0x46, 0x83, 0x76, 0xfc, 0x42, 0x25, 0x2e, 0xc5, 0x3e, 0x84, 0x2d, 0x7d, 0xd8, 0xeb, 0x4d, 0xc7,
0xf4, 0x5b, 0x25, 0x61, 0x05, 0x1e, 0xc1, 0x66, 0x66, 0xb6, 0xa3, 0x0c, 0xbb, 0x0b, 0x90, 0xc9,
0x68, 0xbc, 0xe4, 0xc5, 0x34, 0x53, 0x08, 0x8f, 0xc3, 0xee, 0xa4, 0x35, 0xaf, 0x12, 0x17, 0xd6,
0x65, 0x7e, 0xc6, 0xe0, 0x7f, 0x94, 0x5d, 0x1b, 0xa1, 0x8f, 0xc7, 0x38, 0x45, 0x5d, 0xfb, 0xd6,
0x44, 0x2a, 0xb2, 0x16, 0xec, 0x9c, 0x49, 0x6d, 0xcb, 0xdf, 0x4d, 0x27, 0x5b, 0x74, 0x45, 0x18,
0x47, 0x62, 0x06, 0x75, 0x63, 0x23, 0x47, 0xdd, 0x4b, 0xe7, 0xfa, 0x12, 0xce, 0xee, 0x42, 0x3d,
0xc3, 0x4e, 0x1f, 0xbb, 0xe4, 0x14, 0x47, 0x86, 0x4f, 0x61, 0xef, 0xc2, 0xd6, 0x62, 0x97, 0x70,
0xac, 0xdc, 0xc8, 0xcb, 0x83, 0xec, 0x5e, 0x2e, 0x62, 0x55, 0xda, 0xf6, 0x5a, 0x31, 0x02, 0x1d,
0x65, 0x72, 0x41, 0x7b, 0x00, 0xdb, 0xb8, 0x8b, 0xb7, 0x70, 0xfd, 0xe5, 0x0b, 0x0b, 0x54, 0xf6,
0x08, 0xde, 0x46, 0xc4, 0xc6, 0x60, 0x81, 0x1f, 0x0f, 0x65, 0x34, 0x50, 0x7d, 0x1a, 0x9e, 0x25,
0x71, 0x15, 0x85, 0x3d, 0x5a, 0xea, 0xa5, 0x46, 0x2d, 0x37, 0x84, 0x0a, 0x5a, 0xb1, 0xd4, 0x7a,
0x9f, 0x41, 0x73, 0x61, 0xa0, 0xa0, 0x4c, 0x7f, 0x04, 0xe8, 0x47, 0x5e, 0xc9, 0x4b, 0xf3, 0x2d,
0x94, 0x9e, 0x8e, 0x8c, 0xa6, 0x03, 0xb4, 0x4e, 0xc5, 0x5d, 0x84, 0xa9, 0x2f, 0x26, 0x13, 0x62,
0x6c, 0xba, 0xbe, 0xb0, 0x22, 0xff, 0xad, 0x04, 0xd7, 0x0b, 0x93, 0xf3, 0x44, 0xc9, 0xbe, 0xa2,
0x5e, 0xea, 0xe5, 0xeb, 0xcc, 0x89, 0xd8, 0x4b, 0x43, 0xbf, 0xbc, 0x9c, 0x84, 0x9d, 0x92, 0xd0,
0xe1, 0x67, 0x4b, 0xc9, 0x0a, 0x78, 0xbc, 0x19, 0x2c, 0x02, 0xdb, 0x3e, 0xf4, 0x8d, 0x3b, 0x44,
0xd3, 0x31, 0xce, 0x5a, 0x5b, 0x1a, 0x4e, 0xc2, 0x5a, 0x1b, 0x79, 0xb5, 0x56, 0x5d, 0x5d, 0x6b,
0x1e, 0x05, 0x67, 0xaf, 0xb1, 0x85, 0x6a, 0x4b, 0xa1, 0x24, 0x32, 0x19, 0x0f, 0x73, 0xa4, 0xda,
0x63, 0x8f, 0x9c, 0xb7, 0xe7, 0x63, 0x01, 0x45, 0xde, 0x2c, 0x4b, 0x35, 0xf1, 0x6a, 0x96, 0x97,
0x47, 0xb1, 0xae, 0x7b, 0x69, 0x26, 0x88, 0x06, 0x44, 0xcb, 0x83, 0x18, 0x37, 0xe9, 0x62, 0x6d,
0xb3, 0x91, 0x8a, 0x98, 0xaf, 0x51, 0x21, 0x5f, 0x36, 0x1b, 0x45, 0x98, 0x71, 0xd8, 0x9c, 0xb8,
0xce, 0xc7, 0x01, 0xd6, 0xd8, 0x22, 0x5a, 0x0e, 0xe3, 0x3f, 0x07, 0xb0, 0x53, 0xc8, 0x1c, 0xbb,
0x8f, 0x99, 0xc1, 0xec, 0xb9, 0xa9, 0xbe, 0xb7, 0xfa, 0x6c, 0xb4, 0x19, 0x16, 0x8e, 0xcb, 0x9a,
0x50, 0xee, 0x4b, 0x23, 0x0b, 0x07, 0xb4, 0x3d, 0x45, 0x49, 0xc3, 0x3e, 0x06, 0x58, 0xc4, 0xcc,
0x8d, 0x80, 0x37, 0x97, 0xf6, 0xb6, 0x6a, 0xe1, 0x51, 0xf9, 0x5f, 0xc1, 0xe2, 0x64, 0xf7, 0xea,
0x26, 0x58, 0x5d, 0x37, 0xf6, 0xe0, 0x75, 0x75, 0xb3, 0x07, 0x35, 0x93, 0x5d, 0x9a, 0x6c, 0x45,
0x2d, 0x00, 0xcc, 0x7b, 0xfb, 0xd9, 0x99, 0x7f, 0xd7, 0xca, 0x64, 0xb6, 0x0f, 0xd0, 0x7e, 0x76,
0x96, 0x16, 0x51, 0x65, 0x65, 0x11, 0x79, 0x0c, 0xb4, 0xa4, 0xb3, 0x2b, 0x54, 0xd5, 0x5e, 0xa1,
0x32, 0x00, 0xb5, 0x74, 0x93, 0x18, 0x62, 0xbe, 0xd6, 0xad, 0x36, 0x03, 0xf8, 0xaf, 0x01, 0xec,
0x7c, 0xa1, 0x5e, 0x90, 0xe1, 0x8e, 0x51, 0x93, 0x73, 0x3d, 0x78, 0x4d, 0x3f, 0x19, 0x94, 0xb5,
0x51, 0xd6, 0xc5, 0x8a, 0xa0, 0x6f, 0x76, 0x1f, 0xae, 0x6b, 0xd5, 0x8b, 0xa3, 0xbe, 0xee, 0x84,
0x51, 0x4f, 0x75, 0x8c, 0x4c, 0x4c, 0x37, 0x6d, 0xa2, 0x8a, 0x58, 0xad, 0x4c, 0xeb, 0xcb, 0xa5,
0x81, 0x2c, 0x55, 0x88, 0x5f, 0x84, 0xf9, 0x0b, 0xd8, 0xa2, 0xe1, 0x46, 0x11, 0x78, 0xfd, 0x5f,
0xce, 0x85, 0xa4, 0x54, 0x08, 0x09, 0xa6, 0x26, 0xd4, 0x5e, 0xa9, 0x6c, 0x88, 0x4c, 0xe6, 0x1f,
0xc0, 0x96, 0xfd, 0x7a, 0x45, 0xac, 0xf8, 0x8f, 0x01, 0x6c, 0xa7, 0x85, 0xd3, 0x7e, 0x76, 0x76,
0xd5, 0x3f, 0xde, 0x82, 0xdd, 0xc9, 0x82, 0x29, 0xbc, 0xdf, 0x5d, 0xc2, 0xd9, 0x03, 0xa8, 0x7b,
0x98, 0xbb, 0xa2, 0xbc, 0xb5, 0xdc, 0x25, 0xee, 0xb9, 0x21, 0x7c, 0x36, 0xef, 0x03, 0x9c, 0x48,
0x8d, 0x17, 0xdf, 0x7f, 0x95, 0x65, 0x34, 0x92, 0x66, 0x19, 0xbf, 0x91, 0x19, 0xd2, 0x1b, 0xc3,
0x3d, 0x16, 0x48, 0xe0, 0xdf, 0xc3, 0x0e, 0x9a, 0xe8, 0x28, 0x73, 0x2e, 0xbf, 0x3e, 0xb8, 0xf7,
0xdf, 0x98, 0x6a, 0xc1, 0xfa, 0xc5, 0x95, 0x07, 0x78, 0xaa, 0xe6, 0xbf, 0x04, 0xb0, 0xed, 0xec,
0xe3, 0xe3, 0xea, 0x7f, 0x36, 0xcf, 0x3e, 0x82, 0xca, 0x2c, 0xc6, 0xbb, 0x6f, 0xe5, 0x55, 0xa9,
0xb1, 0x3c, 0xfe, 0x7b, 0x00, 0xb5, 0x13, 0x25, 0x13, 0x73, 0xa1, 0x24, 0xd5, 0xc2, 0xec, 0x25,
0x6f, 0xbd, 0xd9, 0x8a, 0xb7, 0xde, 0x6c, 0xe5, 0x5b, 0x6f, 0xb6, 0xf4, 0xd6, 0x1b, 0xe6, 0xde,
0x7a, 0x45, 0xf7, 0xcb, 0xbe, 0xfb, 0x37, 0x61, 0x43, 0xab, 0x6f, 0xa7, 0x78, 0x43, 0x75, 0xdd,
0x97, 0xc9, 0x57, 0x0f, 0x1a, 0xfe, 0x21, 0xd4, 0x4e, 0xf5, 0x89, 0x92, 0x23, 0x33, 0x9c, 0x23,
0x35, 0x4c, 0x05, 0xf2, 0x60, 0x43, 0x2c, 0x80, 0x8b, 0x2a, 0xbd, 0xa0, 0xef, 0xfd, 0x1d, 0x00,
0x00, 0xff, 0xff, 0xe7, 0xeb, 0xd6, 0x7a, 0x6e, 0x0f, 0x00, 0x00,
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment