code.vegaprotocol.io/vega@v0.79.0/core/blockchain/abci/abci.go (about) 1 // Copyright (C) 2023 Gobalsky Labs Limited 2 // 3 // This program is free software: you can redistribute it and/or modify 4 // it under the terms of the GNU Affero General Public License as 5 // published by the Free Software Foundation, either version 3 of the 6 // License, or (at your option) any later version. 7 // 8 // This program is distributed in the hope that it will be useful, 9 // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 // GNU Affero General Public License for more details. 12 // 13 // You should have received a copy of the GNU Affero General Public License 14 // along with this program. If not, see <http://www.gnu.org/licenses/>. 15 16 package abci 17 18 import ( 19 "context" 20 "encoding/hex" 21 "strconv" 22 23 "code.vegaprotocol.io/vega/core/blockchain" 24 vgcontext "code.vegaprotocol.io/vega/libs/context" 25 v1 "code.vegaprotocol.io/vega/protos/vega/commands/v1" 26 27 "github.com/cometbft/cometbft/abci/types" 28 ) 29 30 func (app *App) Info(ctx context.Context, req *types.RequestInfo) (*types.ResponseInfo, error) { 31 if fn := app.OnInfo; fn != nil { 32 return fn(ctx, req) 33 } 34 return app.BaseApplication.Info(ctx, req) 35 } 36 37 func (app *App) InitChain(_ context.Context, req *types.RequestInitChain) (*types.ResponseInitChain, error) { 38 _, err := LoadGenesisState(req.AppStateBytes) 39 if err != nil { 40 panic(err) 41 } 42 43 if fn := app.OnInitChain; fn != nil { 44 return fn(req) 45 } 46 return &types.ResponseInitChain{}, nil 47 } 48 49 func (app *App) GetTx(tx []byte) (Tx, error) { 50 txx, _, err := app.getTx(tx) 51 return txx, err 52 } 53 54 // PrepareProposal will take the given transactions from the mempool and attempts to prepare a 55 // proposal from them when it's our turn to do so while keeping the size, gas, pow, and spam constraints. 56 func (app *App) PrepareProposal(_ context.Context, req *types.RequestPrepareProposal) (*types.ResponsePrepareProposal, error) { 57 txs := make([]Tx, 0, len(req.Txs)) 58 rawTxs := make([][]byte, 0, len(req.Txs)) 59 for _, v := range req.Txs { 60 tx, _, err := app.getTx(v) 61 // ignore transactions we can't verify 62 if err != nil { 63 continue 64 } 65 // ignore transactions we don't know to handle 66 if _, ok := app.deliverTxs[tx.Command()]; !ok { 67 continue 68 } 69 txs = append(txs, tx) 70 rawTxs = append(rawTxs, v) 71 } 72 73 // let the application decide on the order and the number of transactions it wants to pick up for this block 74 res := &types.ResponsePrepareProposal{Txs: app.OnPrepareProposal(uint64(req.Height), txs, rawTxs)} 75 return res, nil 76 } 77 78 // ProcessProposal implements part of the Application interface. 79 // It accepts any proposal that does not contain a malformed transaction. 80 // NB: processProposal will not be called if the node is fast-sync-ing so no state change is allowed here!!!. 81 func (app *App) ProcessProposal(_ context.Context, req *types.RequestProcessProposal) (*types.ResponseProcessProposal, error) { 82 // check transaction signatures if any is wrong, reject the block 83 txs := make([]Tx, 0, len(req.Txs)) 84 for _, v := range req.Txs { 85 tx, _, err := app.getTx(v) 86 if err != nil { 87 // if there's a transaction we can't decode or verify, reject it 88 return &types.ResponseProcessProposal{Status: types.ResponseProcessProposal_REJECT}, err 89 } 90 // if there's no handler for a transaction, reject it 91 if _, ok := app.deliverTxs[tx.Command()]; !ok { 92 return &types.ResponseProcessProposal{Status: types.ResponseProcessProposal_REJECT}, nil 93 } 94 txs = append(txs, tx) 95 } 96 // let the application verify the block 97 if !app.OnProcessProposal(uint64(req.Height), txs) { 98 return &types.ResponseProcessProposal{Status: types.ResponseProcessProposal_REJECT}, nil 99 } 100 return &types.ResponseProcessProposal{Status: types.ResponseProcessProposal_ACCEPT}, nil 101 } 102 103 func (app *App) Commit(_ context.Context, req *types.RequestCommit) (*types.ResponseCommit, error) { 104 if fn := app.OnCommit; fn != nil { 105 return fn() 106 } 107 return &types.ResponseCommit{}, nil 108 } 109 110 func (app *App) CheckTx(_ context.Context, req *types.RequestCheckTx) (*types.ResponseCheckTx, error) { 111 // first, only decode the transaction but don't validate 112 tx, code, err := app.getTx(req.GetTx()) 113 114 var resp *types.ResponseCheckTx 115 if err != nil { 116 // TODO I think we need to return error in this case as now the API allows for it 117 // return blockchain.NewResponseCheckTxError(code, err), err 118 return blockchain.NewResponseCheckTxError(code, err), nil 119 } 120 121 // check for spam and replay 122 if fn := app.OnCheckTxSpam; fn != nil { 123 resp := fn(tx) 124 if resp.IsErr() { 125 return AddCommonCheckTxEvents(&resp, tx), nil 126 } 127 } 128 129 ctx := app.ctx 130 if fn := app.OnCheckTx; fn != nil { 131 ctx, resp = fn(ctx, req, tx) 132 if resp.IsErr() { 133 return AddCommonCheckTxEvents(resp, tx), nil 134 } 135 } 136 137 // Lookup for check tx, skip if not found 138 if fn, ok := app.checkTxs[tx.Command()]; ok { 139 if err := fn(ctx, tx); err != nil { 140 return AddCommonCheckTxEvents(blockchain.NewResponseCheckTxError(blockchain.AbciTxnInternalError, err), tx), nil 141 } 142 } 143 144 // at this point we consider the Transaction as valid, so we add it to 145 // the cache to be consumed by DeliveryTx 146 if resp.IsOK() { 147 app.cacheTx(req.Tx, tx) 148 } 149 return AddCommonCheckTxEvents(resp, tx), nil 150 } 151 152 // FinalizeBlock lets the application process a whole block end to end. 153 func (app *App) FinalizeBlock(_ context.Context, req *types.RequestFinalizeBlock) (*types.ResponseFinalizeBlock, error) { 154 blockHeight := uint64(req.Height) 155 blockTime := req.Time 156 157 txs := make([]Tx, 0, len(req.Txs)) 158 for _, rtx := range req.Txs { 159 // getTx can't fail at this point as we've verified on processProposal, however as it can fail in nullblockchain, handle it here 160 tx, _, err := app.getTx(rtx) 161 if err != nil { 162 continue 163 } 164 app.removeTxFromCache(rtx) 165 txs = append(txs, tx) 166 } 167 168 app.ctx = app.OnBeginBlock(blockHeight, hex.EncodeToString(req.Hash), blockTime, hex.EncodeToString(req.ProposerAddress), txs) 169 results := make([]*types.ExecTxResult, 0, len(req.Txs)) 170 events := []types.Event{} 171 172 for _, tx := range txs { 173 // there must be a handling function at this point 174 fn := app.deliverTxs[tx.Command()] 175 txHash := hex.EncodeToString(tx.Hash()) 176 ctx := vgcontext.WithTxHash(app.ctx, txHash) 177 // process the transaction and handle errors 178 var result *types.ExecTxResult 179 if err := fn(ctx, tx); err != nil { 180 if perr, ok := err.(MaybePartialError); ok && perr.IsPartial() { 181 result = blockchain.NewResponseDeliverTxError(blockchain.AbciTxnPartialProcessingError, err) 182 } else { 183 result = blockchain.NewResponseDeliverTxError(blockchain.AbciTxnInternalError, err) 184 } 185 } else { 186 result = blockchain.NewResponseDeliverTx(types.CodeTypeOK, "") 187 } 188 result.Events = getBaseTxEvents(tx) 189 results = append(results, result) 190 } 191 valUpdates, consensusUpdates := app.OnEndBlock(blockHeight) 192 events = append(events, types.Event{ 193 Type: "val_updates", 194 Attributes: []types.EventAttribute{ 195 { 196 Key: "size", 197 Value: strconv.Itoa(valUpdates.Len()), 198 }, 199 { 200 Key: "height", 201 Value: strconv.Itoa(int(req.Height)), 202 }, 203 }, 204 }, 205 ) 206 207 hash := app.OnFinalize() 208 return &types.ResponseFinalizeBlock{ 209 TxResults: results, 210 ValidatorUpdates: valUpdates, 211 ConsensusParamUpdates: &consensusUpdates, 212 AppHash: hash, 213 Events: events, 214 }, nil 215 } 216 217 func (app *App) ListSnapshots(ctx context.Context, req *types.RequestListSnapshots) (*types.ResponseListSnapshots, error) { 218 if app.OnListSnapshots != nil { 219 return app.OnListSnapshots(ctx, req) 220 } 221 return &types.ResponseListSnapshots{}, nil 222 } 223 224 func (app *App) OfferSnapshot(ctx context.Context, req *types.RequestOfferSnapshot) (*types.ResponseOfferSnapshot, error) { 225 if app.OnOfferSnapshot != nil { 226 return app.OnOfferSnapshot(ctx, req) 227 } 228 return &types.ResponseOfferSnapshot{}, nil 229 } 230 231 func (app *App) LoadSnapshotChunk(ctx context.Context, req *types.RequestLoadSnapshotChunk) (*types.ResponseLoadSnapshotChunk, error) { 232 if app.OnLoadSnapshotChunk != nil { 233 return app.OnLoadSnapshotChunk(ctx, req) 234 } 235 return &types.ResponseLoadSnapshotChunk{}, nil 236 } 237 238 func (app *App) ApplySnapshotChunk(_ context.Context, req *types.RequestApplySnapshotChunk) (*types.ResponseApplySnapshotChunk, error) { 239 if app.OnApplySnapshotChunk != nil { 240 return app.OnApplySnapshotChunk(app.ctx, req) 241 } 242 return &types.ResponseApplySnapshotChunk{}, nil 243 } 244 245 func AddCommonCheckTxEvents(resp *types.ResponseCheckTx, tx Tx) *types.ResponseCheckTx { 246 resp.Events = getBaseTxEvents(tx) 247 return resp 248 } 249 250 func getBaseTxEvents(tx Tx) []types.Event { 251 base := []types.Event{ 252 { 253 Type: "tx", 254 Attributes: []types.EventAttribute{ 255 { 256 Key: "submitter", 257 Value: tx.PubKeyHex(), 258 Index: true, 259 }, 260 }, 261 }, 262 { 263 Type: "command", 264 Attributes: []types.EventAttribute{ 265 { 266 Key: "type", 267 Value: tx.Command().String(), 268 Index: true, 269 }, 270 }, 271 }, 272 } 273 274 commandAttributes := []types.EventAttribute{} 275 276 cmd := tx.GetCmd() 277 if cmd == nil { 278 return base 279 } 280 281 var market string 282 if m, ok := cmd.(interface{ GetMarketId() string }); ok { 283 market = m.GetMarketId() 284 } 285 if m, ok := cmd.(interface{ GetMarket() string }); ok { 286 market = m.GetMarket() 287 } 288 if len(market) > 0 { 289 commandAttributes = append(commandAttributes, types.EventAttribute{ 290 Key: "market", 291 Value: market, 292 Index: true, 293 }) 294 } 295 296 var asset string 297 if m, ok := cmd.(interface{ GetAssetId() string }); ok { 298 asset = m.GetAssetId() 299 } 300 if m, ok := cmd.(interface{ GetAsset() string }); ok { 301 asset = m.GetAsset() 302 } 303 if len(asset) > 0 { 304 commandAttributes = append(commandAttributes, types.EventAttribute{ 305 Key: "asset", 306 Value: asset, 307 Index: true, 308 }) 309 } 310 311 var reference string 312 if m, ok := cmd.(interface{ GetReference() string }); ok { 313 reference = m.GetReference() 314 } 315 if len(reference) > 0 { 316 commandAttributes = append(commandAttributes, types.EventAttribute{ 317 Key: "reference", 318 Value: reference, 319 Index: true, 320 }) 321 } 322 323 var proposal string 324 if m, ok := cmd.(interface{ GetProposalId() string }); ok { 325 proposal = m.GetProposalId() 326 } 327 if len(proposal) > 0 { 328 commandAttributes = append(commandAttributes, types.EventAttribute{ 329 Key: "proposal", 330 Value: proposal, 331 Index: true, 332 }) 333 } 334 335 var sourceChainID string 336 if m, ok := cmd.(v1.ChainEvent); ok { 337 if e, ok := m.Event.(interface{ GetChainId() string }); ok { 338 sourceChainID = e.GetChainId() 339 } 340 } 341 if len(sourceChainID) > 0 { 342 commandAttributes = append(commandAttributes, types.EventAttribute{ 343 Key: "source-chain-id", 344 Value: sourceChainID, 345 Index: true, 346 }) 347 } 348 349 if len(commandAttributes) > 0 { 350 base[1].Attributes = append(base[1].Attributes, commandAttributes...) 351 } 352 353 return base 354 } 355 356 type MaybePartialError interface { 357 error 358 IsPartial() bool 359 }