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 }