You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

186 lines
5.4 KiB

  1. package evidence
  2. import (
  3. "fmt"
  4. wire "github.com/tendermint/go-wire"
  5. "github.com/tendermint/tendermint/types"
  6. dbm "github.com/tendermint/tmlibs/db"
  7. )
  8. /*
  9. Requirements:
  10. - Valid new evidence must be persisted immediately and never forgotten
  11. - Uncommitted evidence must be continuously broadcast
  12. - Uncommitted evidence has a partial order, the evidence's priority
  13. Impl:
  14. - First commit atomically in outqueue, pending, lookup.
  15. - Once broadcast, remove from outqueue. No need to sync
  16. - Once committed, atomically remove from pending and update lookup.
  17. - TODO: If we crash after committed but before removing/updating,
  18. we'll be stuck broadcasting evidence we never know we committed.
  19. so either share the state db and atomically MarkCommitted
  20. with ApplyBlock, or check all outqueue/pending on Start to see if its committed
  21. Schema for indexing evidence (note you need both height and hash to find a piece of evidence):
  22. "evidence-lookup"/<evidence-height>/<evidence-hash> -> EvidenceInfo
  23. "evidence-outqueue"/<priority>/<evidence-height>/<evidence-hash> -> EvidenceInfo
  24. "evidence-pending"/<evidence-height>/<evidence-hash> -> EvidenceInfo
  25. */
  26. type EvidenceInfo struct {
  27. Committed bool
  28. Priority int64
  29. Evidence types.Evidence
  30. }
  31. const (
  32. baseKeyLookup = "evidence-lookup" // all evidence
  33. baseKeyOutqueue = "evidence-outqueue" // not-yet broadcast
  34. baseKeyPending = "evidence-pending" // broadcast but not committed
  35. )
  36. func keyLookup(evidence types.Evidence) []byte {
  37. return keyLookupFromHeightAndHash(evidence.Height(), evidence.Hash())
  38. }
  39. // big endian padded hex
  40. func bE(h int64) string {
  41. return fmt.Sprintf("%0.16X", h)
  42. }
  43. func keyLookupFromHeightAndHash(height int64, hash []byte) []byte {
  44. return _key("%s/%s/%X", baseKeyLookup, bE(height), hash)
  45. }
  46. func keyOutqueue(evidence types.Evidence, priority int64) []byte {
  47. return _key("%s/%s/%s/%X", baseKeyOutqueue, bE(priority), bE(evidence.Height()), evidence.Hash())
  48. }
  49. func keyPending(evidence types.Evidence) []byte {
  50. return _key("%s/%s/%X", baseKeyPending, bE(evidence.Height()), evidence.Hash())
  51. }
  52. func _key(fmt_ string, o ...interface{}) []byte {
  53. return []byte(fmt.Sprintf(fmt_, o...))
  54. }
  55. // EvidenceStore is a store of all the evidence we've seen, including
  56. // evidence that has been committed, evidence that has been verified but not broadcast,
  57. // and evidence that has been broadcast but not yet committed.
  58. type EvidenceStore struct {
  59. db dbm.DB
  60. }
  61. func NewEvidenceStore(db dbm.DB) *EvidenceStore {
  62. return &EvidenceStore{
  63. db: db,
  64. }
  65. }
  66. // PriorityEvidence returns the evidence from the outqueue, sorted by highest priority.
  67. func (store *EvidenceStore) PriorityEvidence() (evidence []types.Evidence) {
  68. // reverse the order so highest priority is first
  69. l := store.ListEvidence(baseKeyOutqueue)
  70. l2 := make([]types.Evidence, len(l))
  71. for i := range l {
  72. l2[i] = l[len(l)-1-i]
  73. }
  74. return l2
  75. }
  76. // PendingEvidence returns all known uncommitted evidence.
  77. func (store *EvidenceStore) PendingEvidence() (evidence []types.Evidence) {
  78. return store.ListEvidence(baseKeyPending)
  79. }
  80. // ListEvidence lists the evidence for the given prefix key.
  81. // It is wrapped by PriorityEvidence and PendingEvidence for convenience.
  82. func (store *EvidenceStore) ListEvidence(prefixKey string) (evidence []types.Evidence) {
  83. iter := dbm.IteratePrefix(store.db, []byte(prefixKey))
  84. for ; iter.Valid(); iter.Next() {
  85. val := iter.Value()
  86. var ei EvidenceInfo
  87. wire.ReadBinaryBytes(val, &ei)
  88. evidence = append(evidence, ei.Evidence)
  89. }
  90. return evidence
  91. }
  92. // GetEvidence fetches the evidence with the given height and hash.
  93. func (store *EvidenceStore) GetEvidence(height int64, hash []byte) *EvidenceInfo {
  94. key := keyLookupFromHeightAndHash(height, hash)
  95. val := store.db.Get(key)
  96. if len(val) == 0 {
  97. return nil
  98. }
  99. var ei EvidenceInfo
  100. wire.ReadBinaryBytes(val, &ei)
  101. return &ei
  102. }
  103. // AddNewEvidence adds the given evidence to the database.
  104. // It returns false if the evidence is already stored.
  105. func (store *EvidenceStore) AddNewEvidence(evidence types.Evidence, priority int64) bool {
  106. // check if we already have seen it
  107. ei_ := store.GetEvidence(evidence.Height(), evidence.Hash())
  108. if ei_ != nil && ei_.Evidence != nil {
  109. return false
  110. }
  111. ei := EvidenceInfo{
  112. Committed: false,
  113. Priority: priority,
  114. Evidence: evidence,
  115. }
  116. eiBytes := wire.BinaryBytes(ei)
  117. // add it to the store
  118. key := keyOutqueue(evidence, priority)
  119. store.db.Set(key, eiBytes)
  120. key = keyPending(evidence)
  121. store.db.Set(key, eiBytes)
  122. key = keyLookup(evidence)
  123. store.db.SetSync(key, eiBytes)
  124. return true
  125. }
  126. // MarkEvidenceAsBroadcasted removes evidence from Outqueue.
  127. func (store *EvidenceStore) MarkEvidenceAsBroadcasted(evidence types.Evidence) {
  128. ei := store.getEvidenceInfo(evidence)
  129. key := keyOutqueue(evidence, ei.Priority)
  130. store.db.Delete(key)
  131. }
  132. // MarkEvidenceAsPending removes evidence from pending and outqueue and sets the state to committed.
  133. func (store *EvidenceStore) MarkEvidenceAsCommitted(evidence types.Evidence) {
  134. // if its committed, its been broadcast
  135. store.MarkEvidenceAsBroadcasted(evidence)
  136. pendingKey := keyPending(evidence)
  137. store.db.Delete(pendingKey)
  138. ei := store.getEvidenceInfo(evidence)
  139. ei.Committed = true
  140. lookupKey := keyLookup(evidence)
  141. store.db.SetSync(lookupKey, wire.BinaryBytes(ei))
  142. }
  143. //---------------------------------------------------
  144. // utils
  145. func (store *EvidenceStore) getEvidenceInfo(evidence types.Evidence) EvidenceInfo {
  146. key := keyLookup(evidence)
  147. var ei EvidenceInfo
  148. b := store.db.Get(key)
  149. wire.ReadBinaryBytes(b, &ei)
  150. return ei
  151. }