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.

458 lines
14 KiB

7 years ago
7 years ago
7 years ago
7 years ago
lint: Enable Golint (#4212) * Fix many golint errors * Fix golint errors in the 'lite' package * Don't export Pool.store * Fix typo * Revert unwanted changes * Fix errors in counter package * Fix linter errors in kvstore package * Fix linter error in example package * Fix error in tests package * Fix linter errors in v2 package * Fix linter errors in consensus package * Fix linter errors in evidence package * Fix linter error in fail package * Fix linter errors in query package * Fix linter errors in core package * Fix linter errors in node package * Fix linter errors in mempool package * Fix linter error in conn package * Fix linter errors in pex package * Rename PEXReactor export to Reactor * Fix linter errors in trust package * Fix linter errors in upnp package * Fix linter errors in p2p package * Fix linter errors in proxy package * Fix linter errors in mock_test package * Fix linter error in client_test package * Fix linter errors in coretypes package * Fix linter errors in coregrpc package * Fix linter errors in rpcserver package * Fix linter errors in rpctypes package * Fix linter errors in rpctest package * Fix linter error in json2wal script * Fix linter error in wal2json script * Fix linter errors in kv package * Fix linter error in state package * Fix linter error in grpc_client * Fix linter errors in types package * Fix linter error in version package * Fix remaining errors * Address review comments * Fix broken tests * Reconcile package coregrpc * Fix golangci bot error * Fix new golint errors * Fix broken reference * Enable golint linter * minor changes to bring golint into line * fix failing test * fix pex reactor naming * address PR comments
5 years ago
7 years ago
7 years ago
5 years ago
7 years ago
7 years ago
7 years ago
7 years ago
lint: Enable Golint (#4212) * Fix many golint errors * Fix golint errors in the 'lite' package * Don't export Pool.store * Fix typo * Revert unwanted changes * Fix errors in counter package * Fix linter errors in kvstore package * Fix linter error in example package * Fix error in tests package * Fix linter errors in v2 package * Fix linter errors in consensus package * Fix linter errors in evidence package * Fix linter error in fail package * Fix linter errors in query package * Fix linter errors in core package * Fix linter errors in node package * Fix linter errors in mempool package * Fix linter error in conn package * Fix linter errors in pex package * Rename PEXReactor export to Reactor * Fix linter errors in trust package * Fix linter errors in upnp package * Fix linter errors in p2p package * Fix linter errors in proxy package * Fix linter errors in mock_test package * Fix linter error in client_test package * Fix linter errors in coretypes package * Fix linter errors in coregrpc package * Fix linter errors in rpcserver package * Fix linter errors in rpctypes package * Fix linter errors in rpctest package * Fix linter error in json2wal script * Fix linter error in wal2json script * Fix linter errors in kv package * Fix linter error in state package * Fix linter error in grpc_client * Fix linter errors in types package * Fix linter error in version package * Fix remaining errors * Address review comments * Fix broken tests * Reconcile package coregrpc * Fix golangci bot error * Fix new golint errors * Fix broken reference * Enable golint linter * minor changes to bring golint into line * fix failing test * fix pex reactor naming * address PR comments
5 years ago
lint: Enable Golint (#4212) * Fix many golint errors * Fix golint errors in the 'lite' package * Don't export Pool.store * Fix typo * Revert unwanted changes * Fix errors in counter package * Fix linter errors in kvstore package * Fix linter error in example package * Fix error in tests package * Fix linter errors in v2 package * Fix linter errors in consensus package * Fix linter errors in evidence package * Fix linter error in fail package * Fix linter errors in query package * Fix linter errors in core package * Fix linter errors in node package * Fix linter errors in mempool package * Fix linter error in conn package * Fix linter errors in pex package * Rename PEXReactor export to Reactor * Fix linter errors in trust package * Fix linter errors in upnp package * Fix linter errors in p2p package * Fix linter errors in proxy package * Fix linter errors in mock_test package * Fix linter error in client_test package * Fix linter errors in coretypes package * Fix linter errors in coregrpc package * Fix linter errors in rpcserver package * Fix linter errors in rpctypes package * Fix linter errors in rpctest package * Fix linter error in json2wal script * Fix linter error in wal2json script * Fix linter errors in kv package * Fix linter error in state package * Fix linter error in grpc_client * Fix linter errors in types package * Fix linter error in version package * Fix remaining errors * Address review comments * Fix broken tests * Reconcile package coregrpc * Fix golangci bot error * Fix new golint errors * Fix broken reference * Enable golint linter * minor changes to bring golint into line * fix failing test * fix pex reactor naming * address PR comments
5 years ago
add support for block pruning via ABCI Commit response (#4588) * Added BlockStore.DeleteBlock() * Added initial block pruner prototype * wip * Added BlockStore.PruneBlocks() * Added consensus setting for block pruning * Added BlockStore base * Error on replay if base does not have blocks * Handle missing blocks when sending VoteSetMaj23Message * Error message tweak * Properly update blockstore state * Error message fix again * blockchain: ignore peer missing blocks * Added FIXME * Added test for block replay with truncated history * Handle peer base in blockchain reactor * Improved replay error handling * Added tests for Store.PruneBlocks() * Fix non-RPC handling of truncated block history * Panic on missing block meta in needProofBlock() * Updated changelog * Handle truncated block history in RPC layer * Added info about earliest block in /status RPC * Reorder height and base in blockchain reactor messages * Updated changelog * Fix tests * Appease linter * Minor review fixes * Non-empty BlockStores should always have base > 0 * Update code to assume base > 0 invariant * Added blockstore tests for pruning to 0 * Make sure we don't prune below the current base * Added BlockStore.Size() * config: added retain_blocks recommendations * Update v1 blockchain reactor to handle blockstore base * Added state database pruning * Propagate errors on missing validator sets * Comment tweaks * Improved error message Co-Authored-By: Anton Kaliaev <anton.kalyaev@gmail.com> * use ABCI field ResponseCommit.retain_height instead of retain-blocks config option * remove State.RetainHeight, return value instead * fix minor issues * rename pruneHeights() to pruneBlocks() * noop to fix GitHub borkage Co-authored-by: Anton Kaliaev <anton.kalyaev@gmail.com>
5 years ago
7 years ago
7 years ago
7 years ago
7 years ago
lint: Enable Golint (#4212) * Fix many golint errors * Fix golint errors in the 'lite' package * Don't export Pool.store * Fix typo * Revert unwanted changes * Fix errors in counter package * Fix linter errors in kvstore package * Fix linter error in example package * Fix error in tests package * Fix linter errors in v2 package * Fix linter errors in consensus package * Fix linter errors in evidence package * Fix linter error in fail package * Fix linter errors in query package * Fix linter errors in core package * Fix linter errors in node package * Fix linter errors in mempool package * Fix linter error in conn package * Fix linter errors in pex package * Rename PEXReactor export to Reactor * Fix linter errors in trust package * Fix linter errors in upnp package * Fix linter errors in p2p package * Fix linter errors in proxy package * Fix linter errors in mock_test package * Fix linter error in client_test package * Fix linter errors in coretypes package * Fix linter errors in coregrpc package * Fix linter errors in rpcserver package * Fix linter errors in rpctypes package * Fix linter errors in rpctest package * Fix linter error in json2wal script * Fix linter error in wal2json script * Fix linter errors in kv package * Fix linter error in state package * Fix linter error in grpc_client * Fix linter errors in types package * Fix linter error in version package * Fix remaining errors * Address review comments * Fix broken tests * Reconcile package coregrpc * Fix golangci bot error * Fix new golint errors * Fix broken reference * Enable golint linter * minor changes to bring golint into line * fix failing test * fix pex reactor naming * address PR comments
5 years ago
7 years ago
  1. package evidence
  2. import (
  3. "fmt"
  4. "sync"
  5. "time"
  6. dbm "github.com/tendermint/tm-db"
  7. clist "github.com/tendermint/tendermint/libs/clist"
  8. "github.com/tendermint/tendermint/libs/log"
  9. sm "github.com/tendermint/tendermint/state"
  10. "github.com/tendermint/tendermint/store"
  11. "github.com/tendermint/tendermint/types"
  12. )
  13. const (
  14. baseKeyCommitted = byte(0x00) // committed evidence
  15. baseKeyPending = byte(0x01) // pending evidence
  16. )
  17. // Pool maintains a pool of valid evidence to be broadcasted and committed
  18. type Pool struct {
  19. logger log.Logger
  20. evidenceStore dbm.DB
  21. evidenceList *clist.CList // concurrent linked-list of evidence
  22. // needed to load validators to verify evidence
  23. stateDB dbm.DB
  24. // needed to load headers to verify evidence
  25. blockStore *store.BlockStore
  26. mtx sync.Mutex
  27. // latest state
  28. state sm.State
  29. // a map of active validators and respective last heights validator is active
  30. // if it was in validator set after EvidenceParams.MaxAgeNumBlocks or
  31. // currently is (ie. [MaxAgeNumBlocks, CurrentHeight])
  32. // In simple words, it means it's still bonded -> therefore slashable.
  33. valToLastHeight valToLastHeightMap
  34. }
  35. // Validator.Address -> Last height it was in validator set
  36. type valToLastHeightMap map[string]int64
  37. func NewPool(stateDB, evidenceDB dbm.DB, blockStore *store.BlockStore) (*Pool, error) {
  38. var (
  39. state = sm.LoadState(stateDB)
  40. )
  41. valToLastHeight, err := buildValToLastHeightMap(state, stateDB, blockStore)
  42. if err != nil {
  43. return nil, err
  44. }
  45. pool := &Pool{
  46. stateDB: stateDB,
  47. blockStore: blockStore,
  48. state: state,
  49. logger: log.NewNopLogger(),
  50. evidenceStore: evidenceDB,
  51. evidenceList: clist.New(),
  52. valToLastHeight: valToLastHeight,
  53. }
  54. // if pending evidence already in db, in event of prior failure, then load it back to the evidenceList
  55. evList, err := pool.listEvidence(baseKeyPending, -1)
  56. if err != nil {
  57. return nil, err
  58. }
  59. for _, ev := range evList {
  60. if pool.IsExpired(ev) {
  61. pool.removePendingEvidence(ev)
  62. continue
  63. }
  64. pool.evidenceList.PushBack(ev)
  65. }
  66. return pool, nil
  67. }
  68. // PendingEvidence is used primarily as part of block proposal and returns up to maxNum of uncommitted evidence.
  69. // If maxNum is -1, all evidence is returned. Pending evidence is prioritised based on time.
  70. func (evpool *Pool) PendingEvidence(maxNum int64) []types.Evidence {
  71. evidence, err := evpool.listEvidence(baseKeyPending, maxNum)
  72. if err != nil {
  73. evpool.logger.Error("Unable to retrieve pending evidence", "err", err)
  74. }
  75. return evidence
  76. }
  77. // Update uses the latest block & state to update its copy of the state,
  78. // validator to last height map and calls MarkEvidenceAsCommitted.
  79. func (evpool *Pool) Update(block *types.Block, state sm.State) {
  80. // sanity check
  81. if state.LastBlockHeight != block.Height {
  82. panic(
  83. fmt.Sprintf("Failed EvidencePool.Update sanity check: got state.Height=%d with block.Height=%d",
  84. state.LastBlockHeight,
  85. block.Height,
  86. ),
  87. )
  88. }
  89. // remove evidence from pending and mark committed
  90. evpool.MarkEvidenceAsCommitted(block.Height, block.Time, block.Evidence.Evidence)
  91. // update the state
  92. evpool.mtx.Lock()
  93. defer evpool.mtx.Unlock()
  94. evpool.state = state
  95. evpool.updateValToLastHeight(block.Height, state)
  96. }
  97. // AddEvidence checks the evidence is valid and adds it to the pool. If
  98. // evidence is composite (ConflictingHeadersEvidence), it will be broken up
  99. // into smaller pieces.
  100. func (evpool *Pool) AddEvidence(evidence types.Evidence) error {
  101. var (
  102. state = evpool.State()
  103. evList = []types.Evidence{evidence}
  104. )
  105. valSet, err := sm.LoadValidators(evpool.stateDB, evidence.Height())
  106. if err != nil {
  107. return fmt.Errorf("can't load validators at height #%d: %w", evidence.Height(), err)
  108. }
  109. // Break composite evidence into smaller pieces.
  110. if ce, ok := evidence.(types.CompositeEvidence); ok {
  111. evpool.logger.Info("Breaking up composite evidence", "ev", evidence)
  112. blockMeta := evpool.blockStore.LoadBlockMeta(evidence.Height())
  113. if blockMeta == nil {
  114. return fmt.Errorf("don't have block meta at height #%d", evidence.Height())
  115. }
  116. if err := ce.VerifyComposite(&blockMeta.Header, valSet); err != nil {
  117. return err
  118. }
  119. // XXX: Copy here since this should be a rare case.
  120. evpool.mtx.Lock()
  121. valToLastHeightCopy := make(valToLastHeightMap, len(evpool.valToLastHeight))
  122. for k, v := range evpool.valToLastHeight {
  123. valToLastHeightCopy[k] = v
  124. }
  125. evpool.mtx.Unlock()
  126. evList = ce.Split(&blockMeta.Header, valSet, valToLastHeightCopy)
  127. }
  128. for _, ev := range evList {
  129. if evpool.Has(ev) {
  130. continue
  131. }
  132. // For lunatic validator evidence, a header needs to be fetched.
  133. var header *types.Header
  134. if _, ok := ev.(*types.LunaticValidatorEvidence); ok {
  135. blockMeta := evpool.blockStore.LoadBlockMeta(ev.Height())
  136. if blockMeta == nil {
  137. return fmt.Errorf("don't have block meta at height #%d", ev.Height())
  138. }
  139. header = &blockMeta.Header
  140. }
  141. // 1) Verify against state.
  142. if err := sm.VerifyEvidence(evpool.stateDB, state, ev, header); err != nil {
  143. return fmt.Errorf("failed to verify %v: %w", ev, err)
  144. }
  145. // 2) Save to store.
  146. if err := evpool.addPendingEvidence(ev); err != nil {
  147. return fmt.Errorf("database error: %v", err)
  148. }
  149. // 3) Add evidence to clist.
  150. evpool.evidenceList.PushBack(ev)
  151. evpool.logger.Info("Verified new evidence of byzantine behaviour", "evidence", ev)
  152. }
  153. return nil
  154. }
  155. // MarkEvidenceAsCommitted marks all the evidence as committed and removes it
  156. // from the queue.
  157. func (evpool *Pool) MarkEvidenceAsCommitted(height int64, lastBlockTime time.Time, evidence []types.Evidence) {
  158. // make a map of committed evidence to remove from the clist
  159. blockEvidenceMap := make(map[string]struct{})
  160. for _, ev := range evidence {
  161. // As the evidence is stored in the block store we only need to record the height that it was saved at.
  162. key := keyCommitted(ev)
  163. evBytes := cdc.MustMarshalBinaryBare(height)
  164. if err := evpool.evidenceStore.Set(key, evBytes); err != nil {
  165. evpool.logger.Error("Unable to add committed evidence", "err", err)
  166. // if we can't move evidence to committed then don't remove the evidence from pending
  167. continue
  168. }
  169. // if pending, remove from that bucket, remember not all evidence has been seen before
  170. if evpool.IsPending(ev) {
  171. evpool.removePendingEvidence(ev)
  172. blockEvidenceMap[evMapKey(ev)] = struct{}{}
  173. }
  174. }
  175. // remove committed evidence from the clist
  176. if len(blockEvidenceMap) != 0 {
  177. evidenceParams := evpool.State().ConsensusParams.Evidence
  178. evpool.removeEvidenceFromList(height, lastBlockTime, evidenceParams, blockEvidenceMap)
  179. }
  180. }
  181. // Has checks whether the evidence exists either pending or already committed
  182. func (evpool *Pool) Has(evidence types.Evidence) bool {
  183. return evpool.IsPending(evidence) || evpool.IsCommitted(evidence)
  184. }
  185. // IsExpired checks whether evidence is past the maximum age where it can be used
  186. func (evpool *Pool) IsExpired(evidence types.Evidence) bool {
  187. var (
  188. params = evpool.State().ConsensusParams.Evidence
  189. ageDuration = evpool.State().LastBlockTime.Sub(evidence.Time())
  190. ageNumBlocks = evpool.State().LastBlockHeight - evidence.Height()
  191. )
  192. return ageNumBlocks > params.MaxAgeNumBlocks &&
  193. ageDuration > params.MaxAgeDuration
  194. }
  195. // IsCommitted returns true if we have already seen this exact evidence and it is already marked as committed.
  196. func (evpool *Pool) IsCommitted(evidence types.Evidence) bool {
  197. key := keyCommitted(evidence)
  198. ok, err := evpool.evidenceStore.Has(key)
  199. if err != nil {
  200. evpool.logger.Error("Unable to find committed evidence", "err", err)
  201. }
  202. return ok
  203. }
  204. // Checks whether the evidence is already pending. DB errors are passed to the logger.
  205. func (evpool *Pool) IsPending(evidence types.Evidence) bool {
  206. key := keyPending(evidence)
  207. ok, err := evpool.evidenceStore.Has(key)
  208. if err != nil {
  209. evpool.logger.Error("Unable to find pending evidence", "err", err)
  210. }
  211. return ok
  212. }
  213. // EvidenceFront goes to the first evidence in the clist
  214. func (evpool *Pool) EvidenceFront() *clist.CElement {
  215. return evpool.evidenceList.Front()
  216. }
  217. // EvidenceWaitChan is a channel that closes once the first evidence in the list is there. i.e Front is not nil
  218. func (evpool *Pool) EvidenceWaitChan() <-chan struct{} {
  219. return evpool.evidenceList.WaitChan()
  220. }
  221. // SetLogger sets the Logger.
  222. func (evpool *Pool) SetLogger(l log.Logger) {
  223. evpool.logger = l
  224. }
  225. // ValidatorLastHeight returns the last height of the validator w/ the
  226. // given address. 0 - if address never was a validator or was such a
  227. // long time ago (> ConsensusParams.Evidence.MaxAgeDuration && >
  228. // ConsensusParams.Evidence.MaxAgeNumBlocks).
  229. func (evpool *Pool) ValidatorLastHeight(address []byte) int64 {
  230. evpool.mtx.Lock()
  231. defer evpool.mtx.Unlock()
  232. h, ok := evpool.valToLastHeight[string(address)]
  233. if !ok {
  234. return 0
  235. }
  236. return h
  237. }
  238. // State returns the current state of the evpool.
  239. func (evpool *Pool) State() sm.State {
  240. evpool.mtx.Lock()
  241. defer evpool.mtx.Unlock()
  242. return evpool.state
  243. }
  244. func (evpool *Pool) addPendingEvidence(evidence types.Evidence) error {
  245. evBytes := cdc.MustMarshalBinaryBare(evidence)
  246. key := keyPending(evidence)
  247. return evpool.evidenceStore.Set(key, evBytes)
  248. }
  249. func (evpool *Pool) removePendingEvidence(evidence types.Evidence) {
  250. key := keyPending(evidence)
  251. if err := evpool.evidenceStore.Delete(key); err != nil {
  252. evpool.logger.Error("Unable to delete pending evidence", "err", err)
  253. }
  254. }
  255. // listEvidence lists up to maxNum pieces of evidence for the given prefix key.
  256. // It is wrapped by PriorityEvidence and PendingEvidence for convenience.
  257. // If maxNum is -1, there's no cap on the size of returned evidence.
  258. func (evpool *Pool) listEvidence(prefixKey byte, maxNum int64) ([]types.Evidence, error) {
  259. var count int64
  260. var evidence []types.Evidence
  261. iter, err := dbm.IteratePrefix(evpool.evidenceStore, []byte{prefixKey})
  262. if err != nil {
  263. return nil, fmt.Errorf("database error: %v", err)
  264. }
  265. defer iter.Close()
  266. for ; iter.Valid(); iter.Next() {
  267. val := iter.Value()
  268. if count == maxNum {
  269. return evidence, nil
  270. }
  271. count++
  272. var ev types.Evidence
  273. err := cdc.UnmarshalBinaryBare(val, &ev)
  274. if err != nil {
  275. return nil, err
  276. }
  277. evidence = append(evidence, ev)
  278. }
  279. return evidence, nil
  280. }
  281. func (evpool *Pool) removeEvidenceFromList(
  282. height int64,
  283. lastBlockTime time.Time,
  284. params types.EvidenceParams,
  285. blockEvidenceMap map[string]struct{}) {
  286. for e := evpool.evidenceList.Front(); e != nil; e = e.Next() {
  287. var (
  288. ev = e.Value.(types.Evidence)
  289. ageDuration = lastBlockTime.Sub(ev.Time())
  290. ageNumBlocks = height - ev.Height()
  291. )
  292. // Remove the evidence if it's already in a block or if it's now too old.
  293. if _, ok := blockEvidenceMap[evMapKey(ev)]; ok ||
  294. (ageDuration > params.MaxAgeDuration && ageNumBlocks > params.MaxAgeNumBlocks) {
  295. // remove from clist
  296. evpool.evidenceList.Remove(e)
  297. e.DetachPrev()
  298. }
  299. }
  300. }
  301. func evMapKey(ev types.Evidence) string {
  302. return string(ev.Hash())
  303. }
  304. func (evpool *Pool) updateValToLastHeight(blockHeight int64, state sm.State) {
  305. // Update current validators & add new ones.
  306. for _, val := range state.Validators.Validators {
  307. evpool.valToLastHeight[string(val.Address)] = blockHeight
  308. }
  309. // Remove validators outside of MaxAgeNumBlocks & MaxAgeDuration.
  310. removeHeight := blockHeight - state.ConsensusParams.Evidence.MaxAgeNumBlocks
  311. if removeHeight >= 1 {
  312. for val, height := range evpool.valToLastHeight {
  313. if height <= removeHeight {
  314. delete(evpool.valToLastHeight, val)
  315. }
  316. }
  317. }
  318. }
  319. func buildValToLastHeightMap(state sm.State, stateDB dbm.DB, blockStore *store.BlockStore) (valToLastHeightMap, error) {
  320. var (
  321. valToLastHeight = make(map[string]int64)
  322. params = state.ConsensusParams.Evidence
  323. numBlocks = int64(0)
  324. minAgeTime = time.Now().Add(-params.MaxAgeDuration)
  325. height = state.LastBlockHeight
  326. )
  327. if height == 0 {
  328. return valToLastHeight, nil
  329. }
  330. meta := blockStore.LoadBlockMeta(height)
  331. if meta == nil {
  332. return nil, fmt.Errorf("block meta for height %d not found", height)
  333. }
  334. blockTime := meta.Header.Time
  335. // From state.LastBlockHeight, build a map of "active" validators until
  336. // MaxAgeNumBlocks is passed and block time is less than now() -
  337. // MaxAgeDuration.
  338. for height >= 1 && (numBlocks <= params.MaxAgeNumBlocks || !blockTime.Before(minAgeTime)) {
  339. valSet, err := sm.LoadValidators(stateDB, height)
  340. if err != nil {
  341. // last stored height -> return
  342. if _, ok := err.(sm.ErrNoValSetForHeight); ok {
  343. return valToLastHeight, nil
  344. }
  345. return nil, fmt.Errorf("validator set for height %d not found", height)
  346. }
  347. for _, val := range valSet.Validators {
  348. key := string(val.Address)
  349. if _, ok := valToLastHeight[key]; !ok {
  350. valToLastHeight[key] = height
  351. }
  352. }
  353. height--
  354. if height > 0 {
  355. // NOTE: we assume here blockStore and state.Validators are in sync. I.e if
  356. // block N is stored, then validators for height N are also stored in
  357. // state.
  358. meta := blockStore.LoadBlockMeta(height)
  359. if meta == nil {
  360. return nil, fmt.Errorf("block meta for height %d not found", height)
  361. }
  362. blockTime = meta.Header.Time
  363. }
  364. numBlocks++
  365. }
  366. return valToLastHeight, nil
  367. }
  368. // big endian padded hex
  369. func bE(h int64) string {
  370. return fmt.Sprintf("%0.16X", h)
  371. }
  372. func keyCommitted(evidence types.Evidence) []byte {
  373. return append([]byte{baseKeyCommitted}, keySuffix(evidence)...)
  374. }
  375. func keyPending(evidence types.Evidence) []byte {
  376. return append([]byte{baseKeyPending}, keySuffix(evidence)...)
  377. }
  378. func keySuffix(evidence types.Evidence) []byte {
  379. return []byte(fmt.Sprintf("%s/%X", bE(evidence.Height()), evidence.Hash()))
  380. }
  381. // ErrInvalidEvidence returns when evidence failed to validate
  382. type ErrInvalidEvidence struct {
  383. Reason error
  384. }
  385. func (e ErrInvalidEvidence) Error() string {
  386. return fmt.Sprintf("evidence is not valid: %v ", e.Reason)
  387. }