github.com/badrootd/celestia-core@v0.0.0-20240305091328-aa4207a4b25d/rpc/core/tx.go (about)

     1  package core
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"sort"
     7  
     8  	abcitypes "github.com/badrootd/celestia-core/abci/types"
     9  	cmtmath "github.com/badrootd/celestia-core/libs/math"
    10  	cmtquery "github.com/badrootd/celestia-core/libs/pubsub/query"
    11  	"github.com/badrootd/celestia-core/pkg/consts"
    12  	cmtproto "github.com/badrootd/celestia-core/proto/tendermint/types"
    13  	ctypes "github.com/badrootd/celestia-core/rpc/core/types"
    14  	rpctypes "github.com/badrootd/celestia-core/rpc/jsonrpc/types"
    15  	"github.com/badrootd/celestia-core/state"
    16  	"github.com/badrootd/celestia-core/state/txindex/null"
    17  	"github.com/badrootd/celestia-core/types"
    18  )
    19  
    20  // Tx allows you to query the transaction results. `nil` could mean the
    21  // transaction is in the mempool, invalidated, or was not sent in the first
    22  // place.
    23  // More: https://docs.cometbft.com/v0.34/rpc/#/Info/tx
    24  func Tx(ctx *rpctypes.Context, hash []byte, prove bool) (*ctypes.ResultTx, error) {
    25  	env := GetEnvironment()
    26  	// if index is disabled, return error
    27  	if _, ok := env.TxIndexer.(*null.TxIndex); ok {
    28  		return nil, fmt.Errorf("transaction indexing is disabled")
    29  	}
    30  
    31  	r, err := env.TxIndexer.Get(hash)
    32  	if err != nil {
    33  		return nil, err
    34  	}
    35  
    36  	if r == nil {
    37  		return nil, fmt.Errorf("tx (%X) not found", hash)
    38  	}
    39  
    40  	height := r.Height
    41  	index := r.Index
    42  
    43  	var shareProof types.ShareProof
    44  	if prove {
    45  		shareProof, err = proveTx(height, index)
    46  		if err != nil {
    47  			return nil, err
    48  		}
    49  	}
    50  
    51  	return &ctypes.ResultTx{
    52  		Hash:     hash,
    53  		Height:   height,
    54  		Index:    index,
    55  		TxResult: r.Result,
    56  		Tx:       r.Tx,
    57  		Proof:    shareProof,
    58  	}, nil
    59  }
    60  
    61  // TxSearch allows you to query for multiple transactions results. It returns a
    62  // list of transactions (maximum ?per_page entries) and the total count.
    63  // More: https://docs.cometbft.com/v0.34/rpc/#/Info/tx_search
    64  func TxSearch(
    65  	ctx *rpctypes.Context,
    66  	query string,
    67  	prove bool,
    68  	pagePtr, perPagePtr *int,
    69  	orderBy string,
    70  ) (*ctypes.ResultTxSearch, error) {
    71  
    72  	env := GetEnvironment()
    73  	// if index is disabled, return error
    74  	if _, ok := env.TxIndexer.(*null.TxIndex); ok {
    75  		return nil, errors.New("transaction indexing is disabled")
    76  	} else if len(query) > maxQueryLength {
    77  		return nil, errors.New("maximum query length exceeded")
    78  	}
    79  
    80  	q, err := cmtquery.New(query)
    81  	if err != nil {
    82  		return nil, err
    83  	}
    84  
    85  	results, err := env.TxIndexer.Search(ctx.Context(), q)
    86  	if err != nil {
    87  		return nil, err
    88  	}
    89  
    90  	// sort results (must be done before pagination)
    91  	switch orderBy {
    92  	case "desc":
    93  		sort.Slice(results, func(i, j int) bool {
    94  			if results[i].Height == results[j].Height {
    95  				return results[i].Index > results[j].Index
    96  			}
    97  			return results[i].Height > results[j].Height
    98  		})
    99  	case "asc", "":
   100  		sort.Slice(results, func(i, j int) bool {
   101  			if results[i].Height == results[j].Height {
   102  				return results[i].Index < results[j].Index
   103  			}
   104  			return results[i].Height < results[j].Height
   105  		})
   106  	default:
   107  		return nil, errors.New("expected order_by to be either `asc` or `desc` or empty")
   108  	}
   109  
   110  	// paginate results
   111  	totalCount := len(results)
   112  	perPage := validatePerPage(perPagePtr)
   113  
   114  	page, err := validatePage(pagePtr, perPage, totalCount)
   115  	if err != nil {
   116  		return nil, err
   117  	}
   118  
   119  	skipCount := validateSkipCount(page, perPage)
   120  	pageSize := cmtmath.MinInt(perPage, totalCount-skipCount)
   121  
   122  	apiResults := make([]*ctypes.ResultTx, 0, pageSize)
   123  	for i := skipCount; i < skipCount+pageSize; i++ {
   124  		r := results[i]
   125  
   126  		var shareProof types.ShareProof
   127  		if prove {
   128  			shareProof, err = proveTx(r.Height, r.Index)
   129  			if err != nil {
   130  				return nil, err
   131  			}
   132  		}
   133  
   134  		apiResults = append(apiResults, &ctypes.ResultTx{
   135  			Hash:     types.Tx(r.Tx).Hash(),
   136  			Height:   r.Height,
   137  			Index:    r.Index,
   138  			TxResult: r.Result,
   139  			Tx:       r.Tx,
   140  			Proof:    shareProof,
   141  		})
   142  	}
   143  
   144  	return &ctypes.ResultTxSearch{Txs: apiResults, TotalCount: totalCount}, nil
   145  }
   146  
   147  func proveTx(height int64, index uint32) (types.ShareProof, error) {
   148  	var (
   149  		pShareProof cmtproto.ShareProof
   150  		shareProof  types.ShareProof
   151  	)
   152  	env := GetEnvironment()
   153  	rawBlock, err := loadRawBlock(env.BlockStore, height)
   154  	if err != nil {
   155  		return shareProof, err
   156  	}
   157  	res, err := env.ProxyAppQuery.QuerySync(abcitypes.RequestQuery{
   158  		Data: rawBlock,
   159  		Path: fmt.Sprintf(consts.TxInclusionProofQueryPath, index),
   160  	})
   161  	if err != nil {
   162  		return shareProof, err
   163  	}
   164  	err = pShareProof.Unmarshal(res.Value)
   165  	if err != nil {
   166  		return shareProof, err
   167  	}
   168  	shareProof, err = types.ShareProofFromProto(pShareProof)
   169  	if err != nil {
   170  		return shareProof, err
   171  	}
   172  	return shareProof, nil
   173  }
   174  
   175  // ProveShares creates an NMT proof for a set of shares to a set of rows. It is
   176  // end exclusive.
   177  func ProveShares(
   178  	_ *rpctypes.Context,
   179  	height int64,
   180  	startShare uint64,
   181  	endShare uint64,
   182  ) (types.ShareProof, error) {
   183  	var (
   184  		pShareProof cmtproto.ShareProof
   185  		shareProof  types.ShareProof
   186  	)
   187  	env := GetEnvironment()
   188  	rawBlock, err := loadRawBlock(env.BlockStore, height)
   189  	if err != nil {
   190  		return shareProof, err
   191  	}
   192  	res, err := env.ProxyAppQuery.QuerySync(abcitypes.RequestQuery{
   193  		Data: rawBlock,
   194  		Path: fmt.Sprintf(consts.ShareInclusionProofQueryPath, startShare, endShare),
   195  	})
   196  	if err != nil {
   197  		return shareProof, err
   198  	}
   199  	if res.Value == nil && res.Log != "" {
   200  		// we can make the assumption that for custom queries, if the value is nil
   201  		// and some logs have been emitted, then an error happened.
   202  		return types.ShareProof{}, errors.New(res.Log)
   203  	}
   204  	err = pShareProof.Unmarshal(res.Value)
   205  	if err != nil {
   206  		return shareProof, err
   207  	}
   208  	shareProof, err = types.ShareProofFromProto(pShareProof)
   209  	if err != nil {
   210  		return shareProof, err
   211  	}
   212  	return shareProof, nil
   213  }
   214  
   215  func loadRawBlock(bs state.BlockStore, height int64) ([]byte, error) {
   216  	var blockMeta = bs.LoadBlockMeta(height)
   217  	if blockMeta == nil {
   218  		return nil, fmt.Errorf("no block found for height %d", height)
   219  	}
   220  
   221  	buf := []byte{}
   222  	for i := 0; i < int(blockMeta.BlockID.PartSetHeader.Total); i++ {
   223  		part := bs.LoadBlockPart(height, i)
   224  		// If the part is missing (e.g. since it has been deleted after we
   225  		// loaded the block meta) we consider the whole block to be missing.
   226  		if part == nil {
   227  			return nil, fmt.Errorf("missing block part at height %d part %d", height, i)
   228  		}
   229  		buf = append(buf, part.Bytes...)
   230  	}
   231  	return buf, nil
   232  }
   233  
   234  // TxSearchMatchEvents allows you to query for multiple transactions results and match the
   235  // query attributes to a common event. It returns a
   236  // list of transactions (maximum ?per_page entries) and the total count.
   237  // More: https://docs.cometbft.com/v0.34/rpc/#/Info/tx_search
   238  func TxSearchMatchEvents(
   239  	ctx *rpctypes.Context,
   240  	query string,
   241  	prove bool,
   242  	pagePtr, perPagePtr *int,
   243  	orderBy string,
   244  	matchEvents bool,
   245  ) (*ctypes.ResultTxSearch, error) {
   246  
   247  	if matchEvents {
   248  		query = "match.events = 1 AND " + query
   249  	} else {
   250  		query = "match.events = 0 AND " + query
   251  	}
   252  	return TxSearch(ctx, query, prove, pagePtr, perPagePtr, orderBy)
   253  
   254  }