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  }