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.

368 lines
10 KiB

abci: Synchronize FinalizeBlock with the updated specification (#7983) This change set implements the most recent version of `FinalizeBlock`. # What does this change actually contain? * This change set is rather large but fear not! The majority of the files touched and changes are renaming `ResponseDeliverTx` to `ExecTxResult`. This should be a pretty inoffensive change since they're effectively the same type but with a different name. * The `execBlockOnProxyApp` was totally removed since it served as just a wrapper around the logic that is now mostly encapsulated within `FinalizeBlock` * The `updateState` helper function has been made a public method on `State`. It was being exposed as a shim through the testing infrastructure, so this seemed innocuous. * Tests already existed to ensure that the application received the `ByzantineValidators` and the `ValidatorUpdates`, but one was fixed up to ensure that `LastCommitInfo` was being sent across. * Tests were removed from the `psql` indexer that seemed to search for an event in the indexer that was not being created. # Questions for reviewers * We store this [ABCIResponses](https://github.com/tendermint/tendermint/blob/5721a13ab1f4479f9807f449f0bf5c536b9a05f2/proto/tendermint/state/types.pb.go#L37) type in the data base as the block results. This type has changed since v0.35 to contain the `FinalizeBlock` response. I'm wondering if we need to do any shimming to keep the old data retrieveable? * Similarly, this change is exposed via the RPC through [ResultBlockResults](https://github.com/tendermint/tendermint/blob/5721a13ab1f4479f9807f449f0bf5c536b9a05f2/rpc/coretypes/responses.go#L69) changing. Should we somehow shim or notify for this change? closes: #7658
3 years ago
abci: Synchronize FinalizeBlock with the updated specification (#7983) This change set implements the most recent version of `FinalizeBlock`. # What does this change actually contain? * This change set is rather large but fear not! The majority of the files touched and changes are renaming `ResponseDeliverTx` to `ExecTxResult`. This should be a pretty inoffensive change since they're effectively the same type but with a different name. * The `execBlockOnProxyApp` was totally removed since it served as just a wrapper around the logic that is now mostly encapsulated within `FinalizeBlock` * The `updateState` helper function has been made a public method on `State`. It was being exposed as a shim through the testing infrastructure, so this seemed innocuous. * Tests already existed to ensure that the application received the `ByzantineValidators` and the `ValidatorUpdates`, but one was fixed up to ensure that `LastCommitInfo` was being sent across. * Tests were removed from the `psql` indexer that seemed to search for an event in the indexer that was not being created. # Questions for reviewers * We store this [ABCIResponses](https://github.com/tendermint/tendermint/blob/5721a13ab1f4479f9807f449f0bf5c536b9a05f2/proto/tendermint/state/types.pb.go#L37) type in the data base as the block results. This type has changed since v0.35 to contain the `FinalizeBlock` response. I'm wondering if we need to do any shimming to keep the old data retrieveable? * Similarly, this change is exposed via the RPC through [ResultBlockResults](https://github.com/tendermint/tendermint/blob/5721a13ab1f4479f9807f449f0bf5c536b9a05f2/rpc/coretypes/responses.go#L69) changing. Should we somehow shim or notify for this change? closes: #7658
3 years ago
abci: Synchronize FinalizeBlock with the updated specification (#7983) This change set implements the most recent version of `FinalizeBlock`. # What does this change actually contain? * This change set is rather large but fear not! The majority of the files touched and changes are renaming `ResponseDeliverTx` to `ExecTxResult`. This should be a pretty inoffensive change since they're effectively the same type but with a different name. * The `execBlockOnProxyApp` was totally removed since it served as just a wrapper around the logic that is now mostly encapsulated within `FinalizeBlock` * The `updateState` helper function has been made a public method on `State`. It was being exposed as a shim through the testing infrastructure, so this seemed innocuous. * Tests already existed to ensure that the application received the `ByzantineValidators` and the `ValidatorUpdates`, but one was fixed up to ensure that `LastCommitInfo` was being sent across. * Tests were removed from the `psql` indexer that seemed to search for an event in the indexer that was not being created. # Questions for reviewers * We store this [ABCIResponses](https://github.com/tendermint/tendermint/blob/5721a13ab1f4479f9807f449f0bf5c536b9a05f2/proto/tendermint/state/types.pb.go#L37) type in the data base as the block results. This type has changed since v0.35 to contain the `FinalizeBlock` response. I'm wondering if we need to do any shimming to keep the old data retrieveable? * Similarly, this change is exposed via the RPC through [ResultBlockResults](https://github.com/tendermint/tendermint/blob/5721a13ab1f4479f9807f449f0bf5c536b9a05f2/rpc/coretypes/responses.go#L69) changing. Should we somehow shim or notify for this change? closes: #7658
3 years ago
abci: Synchronize FinalizeBlock with the updated specification (#7983) This change set implements the most recent version of `FinalizeBlock`. # What does this change actually contain? * This change set is rather large but fear not! The majority of the files touched and changes are renaming `ResponseDeliverTx` to `ExecTxResult`. This should be a pretty inoffensive change since they're effectively the same type but with a different name. * The `execBlockOnProxyApp` was totally removed since it served as just a wrapper around the logic that is now mostly encapsulated within `FinalizeBlock` * The `updateState` helper function has been made a public method on `State`. It was being exposed as a shim through the testing infrastructure, so this seemed innocuous. * Tests already existed to ensure that the application received the `ByzantineValidators` and the `ValidatorUpdates`, but one was fixed up to ensure that `LastCommitInfo` was being sent across. * Tests were removed from the `psql` indexer that seemed to search for an event in the indexer that was not being created. # Questions for reviewers * We store this [ABCIResponses](https://github.com/tendermint/tendermint/blob/5721a13ab1f4479f9807f449f0bf5c536b9a05f2/proto/tendermint/state/types.pb.go#L37) type in the data base as the block results. This type has changed since v0.35 to contain the `FinalizeBlock` response. I'm wondering if we need to do any shimming to keep the old data retrieveable? * Similarly, this change is exposed via the RPC through [ResultBlockResults](https://github.com/tendermint/tendermint/blob/5721a13ab1f4479f9807f449f0bf5c536b9a05f2/rpc/coretypes/responses.go#L69) changing. Should we somehow shim or notify for this change? closes: #7658
3 years ago
abci: Synchronize FinalizeBlock with the updated specification (#7983) This change set implements the most recent version of `FinalizeBlock`. # What does this change actually contain? * This change set is rather large but fear not! The majority of the files touched and changes are renaming `ResponseDeliverTx` to `ExecTxResult`. This should be a pretty inoffensive change since they're effectively the same type but with a different name. * The `execBlockOnProxyApp` was totally removed since it served as just a wrapper around the logic that is now mostly encapsulated within `FinalizeBlock` * The `updateState` helper function has been made a public method on `State`. It was being exposed as a shim through the testing infrastructure, so this seemed innocuous. * Tests already existed to ensure that the application received the `ByzantineValidators` and the `ValidatorUpdates`, but one was fixed up to ensure that `LastCommitInfo` was being sent across. * Tests were removed from the `psql` indexer that seemed to search for an event in the indexer that was not being created. # Questions for reviewers * We store this [ABCIResponses](https://github.com/tendermint/tendermint/blob/5721a13ab1f4479f9807f449f0bf5c536b9a05f2/proto/tendermint/state/types.pb.go#L37) type in the data base as the block results. This type has changed since v0.35 to contain the `FinalizeBlock` response. I'm wondering if we need to do any shimming to keep the old data retrieveable? * Similarly, this change is exposed via the RPC through [ResultBlockResults](https://github.com/tendermint/tendermint/blob/5721a13ab1f4479f9807f449f0bf5c536b9a05f2/rpc/coretypes/responses.go#L69) changing. Should we somehow shim or notify for this change? closes: #7658
3 years ago
  1. package app
  2. import (
  3. "bytes"
  4. "encoding/base64"
  5. "errors"
  6. "fmt"
  7. "path/filepath"
  8. "sort"
  9. "strconv"
  10. "sync"
  11. "github.com/tendermint/tendermint/abci/example/code"
  12. abci "github.com/tendermint/tendermint/abci/types"
  13. "github.com/tendermint/tendermint/libs/log"
  14. "github.com/tendermint/tendermint/proto/tendermint/types"
  15. "github.com/tendermint/tendermint/version"
  16. )
  17. // Application is an ABCI application for use by end-to-end tests. It is a
  18. // simple key/value store for strings, storing data in memory and persisting
  19. // to disk as JSON, taking state sync snapshots if requested.
  20. type Application struct {
  21. abci.BaseApplication
  22. mu sync.Mutex
  23. logger log.Logger
  24. state *State
  25. snapshots *SnapshotStore
  26. cfg *Config
  27. restoreSnapshot *abci.Snapshot
  28. restoreChunks [][]byte
  29. }
  30. // Config allows for the setting of high level parameters for running the e2e Application
  31. // KeyType and ValidatorUpdates must be the same for all nodes running the same application.
  32. type Config struct {
  33. // The directory with which state.json will be persisted in. Usually $HOME/.tendermint/data
  34. Dir string `toml:"dir"`
  35. // SnapshotInterval specifies the height interval at which the application
  36. // will take state sync snapshots. Defaults to 0 (disabled).
  37. SnapshotInterval uint64 `toml:"snapshot_interval"`
  38. // RetainBlocks specifies the number of recent blocks to retain. Defaults to
  39. // 0, which retains all blocks. Must be greater that PersistInterval,
  40. // SnapshotInterval and EvidenceAgeHeight.
  41. RetainBlocks uint64 `toml:"retain_blocks"`
  42. // KeyType sets the curve that will be used by validators.
  43. // Options are ed25519 & secp256k1
  44. KeyType string `toml:"key_type"`
  45. // PersistInterval specifies the height interval at which the application
  46. // will persist state to disk. Defaults to 1 (every height), setting this to
  47. // 0 disables state persistence.
  48. PersistInterval uint64 `toml:"persist_interval"`
  49. // ValidatorUpdates is a map of heights to validator names and their power,
  50. // and will be returned by the ABCI application. For example, the following
  51. // changes the power of validator01 and validator02 at height 1000:
  52. //
  53. // [validator_update.1000]
  54. // validator01 = 20
  55. // validator02 = 10
  56. //
  57. // Specifying height 0 returns the validator update during InitChain. The
  58. // application returns the validator updates as-is, i.e. removing a
  59. // validator must be done by returning it with power 0, and any validators
  60. // not specified are not changed.
  61. //
  62. // height <-> pubkey <-> voting power
  63. ValidatorUpdates map[string]map[string]uint8 `toml:"validator_update"`
  64. }
  65. func DefaultConfig(dir string) *Config {
  66. return &Config{
  67. PersistInterval: 1,
  68. SnapshotInterval: 100,
  69. Dir: dir,
  70. }
  71. }
  72. // NewApplication creates the application.
  73. func NewApplication(cfg *Config) (*Application, error) {
  74. state, err := NewState(cfg.Dir, cfg.PersistInterval)
  75. if err != nil {
  76. return nil, err
  77. }
  78. snapshots, err := NewSnapshotStore(filepath.Join(cfg.Dir, "snapshots"))
  79. if err != nil {
  80. return nil, err
  81. }
  82. logger, err := log.NewDefaultLogger(log.LogFormatPlain, log.LogLevelInfo)
  83. if err != nil {
  84. return nil, err
  85. }
  86. return &Application{
  87. logger: logger,
  88. state: state,
  89. snapshots: snapshots,
  90. cfg: cfg,
  91. }, nil
  92. }
  93. // Info implements ABCI.
  94. func (app *Application) Info(req abci.RequestInfo) abci.ResponseInfo {
  95. app.mu.Lock()
  96. defer app.mu.Unlock()
  97. return abci.ResponseInfo{
  98. Version: version.ABCIVersion,
  99. AppVersion: 1,
  100. LastBlockHeight: int64(app.state.Height),
  101. LastBlockAppHash: app.state.Hash,
  102. }
  103. }
  104. // Info implements ABCI.
  105. func (app *Application) InitChain(req abci.RequestInitChain) abci.ResponseInitChain {
  106. app.mu.Lock()
  107. defer app.mu.Unlock()
  108. var err error
  109. app.state.initialHeight = uint64(req.InitialHeight)
  110. if len(req.AppStateBytes) > 0 {
  111. err = app.state.Import(0, req.AppStateBytes)
  112. if err != nil {
  113. panic(err)
  114. }
  115. }
  116. resp := abci.ResponseInitChain{
  117. AppHash: app.state.Hash,
  118. ConsensusParams: &types.ConsensusParams{
  119. Version: &types.VersionParams{
  120. AppVersion: 1,
  121. },
  122. },
  123. }
  124. if resp.Validators, err = app.validatorUpdates(0); err != nil {
  125. panic(err)
  126. }
  127. return resp
  128. }
  129. // CheckTx implements ABCI.
  130. func (app *Application) CheckTx(req abci.RequestCheckTx) abci.ResponseCheckTx {
  131. app.mu.Lock()
  132. defer app.mu.Unlock()
  133. _, _, err := parseTx(req.Tx)
  134. if err != nil {
  135. return abci.ResponseCheckTx{
  136. Code: code.CodeTypeEncodingError,
  137. Log: err.Error(),
  138. }
  139. }
  140. return abci.ResponseCheckTx{Code: code.CodeTypeOK, GasWanted: 1}
  141. }
  142. // FinalizeBlock implements ABCI.
  143. func (app *Application) FinalizeBlock(req abci.RequestFinalizeBlock) abci.ResponseFinalizeBlock {
  144. var txs = make([]*abci.ExecTxResult, len(req.Txs))
  145. app.mu.Lock()
  146. defer app.mu.Unlock()
  147. for i, tx := range req.Txs {
  148. key, value, err := parseTx(tx)
  149. if err != nil {
  150. panic(err) // shouldn't happen since we verified it in CheckTx
  151. }
  152. app.state.Set(key, value)
  153. txs[i] = &abci.ExecTxResult{Code: code.CodeTypeOK}
  154. }
  155. valUpdates, err := app.validatorUpdates(uint64(req.Header.Height))
  156. if err != nil {
  157. panic(err)
  158. }
  159. return abci.ResponseFinalizeBlock{
  160. TxResults: txs,
  161. ValidatorUpdates: valUpdates,
  162. Events: []abci.Event{
  163. {
  164. Type: "val_updates",
  165. Attributes: []abci.EventAttribute{
  166. {
  167. Key: "size",
  168. Value: strconv.Itoa(valUpdates.Len()),
  169. },
  170. {
  171. Key: "height",
  172. Value: strconv.Itoa(int(req.Header.Height)),
  173. },
  174. },
  175. },
  176. },
  177. }
  178. }
  179. // Commit implements ABCI.
  180. func (app *Application) Commit() abci.ResponseCommit {
  181. app.mu.Lock()
  182. defer app.mu.Unlock()
  183. height, hash, err := app.state.Commit()
  184. if err != nil {
  185. panic(err)
  186. }
  187. if app.cfg.SnapshotInterval > 0 && height%app.cfg.SnapshotInterval == 0 {
  188. snapshot, err := app.snapshots.Create(app.state)
  189. if err != nil {
  190. panic(err)
  191. }
  192. app.logger.Info("Created state sync snapshot", "height", snapshot.Height)
  193. err = app.snapshots.Prune(maxSnapshotCount)
  194. if err != nil {
  195. app.logger.Error("Failed to prune snapshots", "err", err)
  196. }
  197. }
  198. retainHeight := int64(0)
  199. if app.cfg.RetainBlocks > 0 {
  200. retainHeight = int64(height - app.cfg.RetainBlocks + 1)
  201. }
  202. return abci.ResponseCommit{
  203. Data: hash,
  204. RetainHeight: retainHeight,
  205. }
  206. }
  207. // Query implements ABCI.
  208. func (app *Application) Query(req abci.RequestQuery) abci.ResponseQuery {
  209. app.mu.Lock()
  210. defer app.mu.Unlock()
  211. return abci.ResponseQuery{
  212. Height: int64(app.state.Height),
  213. Key: req.Data,
  214. Value: []byte(app.state.Get(string(req.Data))),
  215. }
  216. }
  217. // ListSnapshots implements ABCI.
  218. func (app *Application) ListSnapshots(req abci.RequestListSnapshots) abci.ResponseListSnapshots {
  219. app.mu.Lock()
  220. defer app.mu.Unlock()
  221. snapshots, err := app.snapshots.List()
  222. if err != nil {
  223. panic(err)
  224. }
  225. return abci.ResponseListSnapshots{Snapshots: snapshots}
  226. }
  227. // LoadSnapshotChunk implements ABCI.
  228. func (app *Application) LoadSnapshotChunk(req abci.RequestLoadSnapshotChunk) abci.ResponseLoadSnapshotChunk {
  229. app.mu.Lock()
  230. defer app.mu.Unlock()
  231. chunk, err := app.snapshots.LoadChunk(req.Height, req.Format, req.Chunk)
  232. if err != nil {
  233. panic(err)
  234. }
  235. return abci.ResponseLoadSnapshotChunk{Chunk: chunk}
  236. }
  237. // OfferSnapshot implements ABCI.
  238. func (app *Application) OfferSnapshot(req abci.RequestOfferSnapshot) abci.ResponseOfferSnapshot {
  239. app.mu.Lock()
  240. defer app.mu.Unlock()
  241. if app.restoreSnapshot != nil {
  242. panic("A snapshot is already being restored")
  243. }
  244. app.restoreSnapshot = req.Snapshot
  245. app.restoreChunks = [][]byte{}
  246. return abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_ACCEPT}
  247. }
  248. // ApplySnapshotChunk implements ABCI.
  249. func (app *Application) ApplySnapshotChunk(req abci.RequestApplySnapshotChunk) abci.ResponseApplySnapshotChunk {
  250. app.mu.Lock()
  251. defer app.mu.Unlock()
  252. if app.restoreSnapshot == nil {
  253. panic("No restore in progress")
  254. }
  255. app.restoreChunks = append(app.restoreChunks, req.Chunk)
  256. if len(app.restoreChunks) == int(app.restoreSnapshot.Chunks) {
  257. bz := []byte{}
  258. for _, chunk := range app.restoreChunks {
  259. bz = append(bz, chunk...)
  260. }
  261. err := app.state.Import(app.restoreSnapshot.Height, bz)
  262. if err != nil {
  263. panic(err)
  264. }
  265. app.restoreSnapshot = nil
  266. app.restoreChunks = nil
  267. }
  268. return abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}
  269. }
  270. func (app *Application) PrepareProposal(req abci.RequestPrepareProposal) abci.ResponsePrepareProposal {
  271. // None of the transactions are modified by this application.
  272. return abci.ResponsePrepareProposal{ModifiedTx: false}
  273. }
  274. // ProcessProposal implements part of the Application interface.
  275. // It accepts any proposal that does not contain a malformed transaction.
  276. func (app *Application) ProcessProposal(req abci.RequestProcessProposal) abci.ResponseProcessProposal {
  277. for _, tx := range req.Txs {
  278. _, _, err := parseTx(tx)
  279. if err != nil {
  280. return abci.ResponseProcessProposal{Accept: false}
  281. }
  282. }
  283. return abci.ResponseProcessProposal{Accept: true}
  284. }
  285. func (app *Application) Rollback() error {
  286. app.mu.Lock()
  287. defer app.mu.Unlock()
  288. return app.state.Rollback()
  289. }
  290. // validatorUpdates generates a validator set update.
  291. func (app *Application) validatorUpdates(height uint64) (abci.ValidatorUpdates, error) {
  292. updates := app.cfg.ValidatorUpdates[fmt.Sprintf("%v", height)]
  293. if len(updates) == 0 {
  294. return nil, nil
  295. }
  296. valUpdates := abci.ValidatorUpdates{}
  297. for keyString, power := range updates {
  298. keyBytes, err := base64.StdEncoding.DecodeString(keyString)
  299. if err != nil {
  300. return nil, fmt.Errorf("invalid base64 pubkey value %q: %w", keyString, err)
  301. }
  302. valUpdates = append(valUpdates, abci.UpdateValidator(keyBytes, int64(power), app.cfg.KeyType))
  303. }
  304. // the validator updates could be returned in arbitrary order,
  305. // and that seems potentially bad. This orders the validator
  306. // set.
  307. sort.Slice(valUpdates, func(i, j int) bool {
  308. return valUpdates[i].PubKey.Compare(valUpdates[j].PubKey) < 0
  309. })
  310. return valUpdates, nil
  311. }
  312. // parseTx parses a tx in 'key=value' format into a key and value.
  313. func parseTx(tx []byte) (string, string, error) {
  314. parts := bytes.Split(tx, []byte("="))
  315. if len(parts) != 2 {
  316. return "", "", fmt.Errorf("invalid tx format: %q", string(tx))
  317. }
  318. if len(parts[0]) == 0 {
  319. return "", "", errors.New("key cannot be empty")
  320. }
  321. return string(parts[0]), string(parts[1]), nil
  322. }