mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-06-15 02:26:40 +00:00

* [NOD-289] Implemented database isCurrent checking and connection. * [NOD-289] Added GetChainFromBlock to RPCClient. * [NOD-289] Limited the amount of blocks in GetChainFromBlockResponse. * [NOD-289] Fixed various issues that were keeping GetChainFromBlocks from working properly. * [NOD-289] Created blockloop.go. * [NOD-289] Updated go.mod after merge. * [NOD-289] Implemented collection of current selected parent chain. * [NOD-289] Fixed test. Reverted not deleting utxoDiffData from the DB. * [NOD-289] Implemented GetBlocks. * [NOD-289] Added comment to BlockHashesFrom. * [NOD-289] Added GetBlocks to rpcclient. * [NOD-289] Added verboseBlocks to GetBlocks. * [NOD-289] Implemented block insertion. * [NOD-289] Added AUTO_INCREMENT to tables that were missing it. * [NOD-289] Made gasLimit in subnetwork nullable. * [NOD-289] Renamed transactions_outputs to transaction_outputs. * [NOD-289] Fixed weird coinbase behavior in vin. * [NOD-289] Made collectCurrentBlocks start from the most recent startHash. * [NOD-289] Added IsChainBlock to GetBlockVerboseResult. * [NOD-289] Implemented adding a block from onBlockAdded. * [NOD-289] Added removedParentChainHashes to getChainFromBlock. * [NOD-289] Implemented updating the selected parent chain from onChainChanged. * [NOD-289] Implemented some initial logic for updating the UTXO. * [NOD-289] Fixed merge errors. * [NOD-326] Fixed some more merge errors. * [NOD-289] Added error handling for missing required records. * [NOD-289] Implemented handling removedChainHashes. * [NOD-289] Implemented handling addedChainBlocks. * [NOD-289] Fixed incorrect coinbase check. * [NOD-289] Implemented inserting the transaction output address. * [NOD-289] Added updating block.IsChainBlock. * [NOD-289] Split insertBlock into many small functions. * [NOD-289] Split updateSelectedParentChain into smaller functions. * [NOD-289] Fixed pointer errors. * [NOD-289] Fixed a bad exists check. * [NOD-289] Fixed a couple of small bugs. * [NOD-289] Fixed a TxID/Hash mixup. * [NOD-289] Added block/tx mass to getBlockVerboseResponse. * [NOD-289] Renamed blockLoop.go to sync.go. Added comments. * [NOD-289] Deleted apiserver README. * [NOD-289] Fixed golint errors. * [NOD-289] Renamed findMostRecentBlockHash to findHashOfBluestBlock. * [NOD-289] Fixed style in syncBlocks and fixed a comment. * [NOD-289] Copied NewErrorFromDBErrors over from NOD-324. * [NOD-289] Created a couple of utils to make error handling with gorm slightly less painful. * [NOD-289] Added error handling for database calls. * [NOD-289] Fixed some more style/comments. * [NOD-289] Fixed comments. * [NOD-289] Renamed TransactionInput.TransactionOutput to TransactionInput.PreviousTransactionOutput. * [NOD-289] Added a commends about pagination in getBlocks and getChainFromBlock. * [NOD-289] Removed the coinbase field from Vin. * [NOD-289] Deferred handling chainChangedMsgs until we have the appropriate data. * [NOD-289] Optimized queries in updateRemovedChainHashes and updateAddedChainBlocks. * [NOD-289] Optimized queries in insertBlockParents. * [NOD-289] Optimized queries in insertTransactionInput. * [NOD-289] Split Where calls to separate lines. * [NOD-289] Fixed merge errors. * [NOD-289] Exited early from insertBlockParents if we're the genesis block. * [NOD-289] Improved nextChainChangedChan mechanism. * [NOD-289] Fixed the above sync mechanism a bit. * [NOD-289] Renamed IsDBRecordNotFoundError to HasDBRecordNotFoundError and IsDBError to HasDBError. * [NOD-289] Replaced old error handling for db errors with the lovely new stuff. * [NOD-289] Exited early if we already inserted a block. This saves us checking if a record already exists for some record types. * [NOD-289] Decoupled syncBlocks from syncSelectedParentChain. * [NOD-289] Made a comment more explicit. * [NOD-289] Extracted net resolution to a separate function. * [NOD-289] Extracted syncing to a separate function. * [NOD-289] Fixed a comment. * [NOD-289] Fixed merge erros. * [NOD-289] Fixed a couple of bugs. * [NOD-289] Fixed another bug. * [NOD-289] Extracted ChainChangedMsg conversion to a separate function. * [NOD-289] Optimized queries in canHandleChainChangedMsg. * [NOD-289] Moved the sync function closer to its call site. * [NOD-289] Renamed HasDBRecordNotFoundError to IsDBRecordNotFoundError. * [NOD-289] Used count instead of first. * [NOD-289] Renamed address to hexAddress.
188 lines
7.4 KiB
Go
188 lines
7.4 KiB
Go
package controllers
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
|
|
"github.com/daglabs/btcd/apiserver/database"
|
|
"github.com/daglabs/btcd/apiserver/jsonrpc"
|
|
"github.com/daglabs/btcd/apiserver/models"
|
|
"github.com/daglabs/btcd/apiserver/utils"
|
|
"github.com/daglabs/btcd/btcjson"
|
|
"github.com/daglabs/btcd/util/daghash"
|
|
"github.com/daglabs/btcd/wire"
|
|
"github.com/jinzhu/gorm"
|
|
)
|
|
|
|
const maxGetTransactionsLimit = 1000
|
|
|
|
// GetTransactionByIDHandler returns a transaction by a given transaction ID.
|
|
func GetTransactionByIDHandler(txID string) (interface{}, *utils.HandlerError) {
|
|
if bytes, err := hex.DecodeString(txID); err != nil || len(bytes) != daghash.TxIDSize {
|
|
return nil, utils.NewHandlerError(http.StatusUnprocessableEntity,
|
|
fmt.Sprintf("The given txid is not a hex-encoded %d-byte hash.", daghash.TxIDSize))
|
|
}
|
|
|
|
db, err := database.DB()
|
|
if err != nil {
|
|
return nil, utils.NewInternalServerHandlerError(err.Error())
|
|
}
|
|
|
|
tx := &models.Transaction{}
|
|
query := db.Where(&models.Transaction{TransactionID: txID})
|
|
dbResult := addTxPreloadedFields(query).First(&tx)
|
|
dbErrors := dbResult.GetErrors()
|
|
if utils.IsDBRecordNotFoundError(dbErrors) {
|
|
return nil, utils.NewHandlerError(http.StatusNotFound, "No transaction with the given txid was found.")
|
|
}
|
|
if utils.HasDBError(dbErrors) {
|
|
return nil, utils.NewHandlerErrorFromDBErrors("Some errors where encountered when loading transaction from the database:", dbErrors)
|
|
}
|
|
return convertTxModelToTxResponse(tx), nil
|
|
}
|
|
|
|
// GetTransactionByHashHandler returns a transaction by a given transaction hash.
|
|
func GetTransactionByHashHandler(txHash string) (interface{}, *utils.HandlerError) {
|
|
if bytes, err := hex.DecodeString(txHash); err != nil || len(bytes) != daghash.HashSize {
|
|
return nil, utils.NewHandlerError(http.StatusUnprocessableEntity,
|
|
fmt.Sprintf("The given txhash is not a hex-encoded %d-byte hash.", daghash.HashSize))
|
|
}
|
|
|
|
db, err := database.DB()
|
|
if err != nil {
|
|
return nil, utils.NewHandlerError(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
|
}
|
|
|
|
tx := &models.Transaction{}
|
|
query := db.Where(&models.Transaction{TransactionHash: txHash})
|
|
dbResult := addTxPreloadedFields(query).First(&tx)
|
|
dbErrors := dbResult.GetErrors()
|
|
if utils.IsDBRecordNotFoundError(dbErrors) {
|
|
return nil, utils.NewHandlerError(http.StatusNotFound, "No transaction with the given txhash was found.")
|
|
}
|
|
if utils.HasDBError(dbErrors) {
|
|
return nil, utils.NewHandlerErrorFromDBErrors("Some errors where encountered when loading transaction from the database:", dbErrors)
|
|
}
|
|
return convertTxModelToTxResponse(tx), nil
|
|
}
|
|
|
|
// GetTransactionsByAddressHandler searches for all transactions
|
|
// where the given address is either an input or an output.
|
|
func GetTransactionsByAddressHandler(address string, skip uint64, limit uint64) (interface{}, *utils.HandlerError) {
|
|
if limit > maxGetTransactionsLimit {
|
|
return nil, utils.NewHandlerError(http.StatusUnprocessableEntity,
|
|
fmt.Sprintf("The maximum allowed value for the limit is %d", maxGetTransactionsLimit))
|
|
}
|
|
|
|
db, err := database.DB()
|
|
if err != nil {
|
|
return nil, utils.NewHandlerError(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
|
}
|
|
|
|
txs := []*models.Transaction{}
|
|
query := db.
|
|
Joins("LEFT JOIN `transaction_outputs` ON `transaction_outputs`.`transaction_id` = `transactions`.`id`").
|
|
Joins("LEFT JOIN `addresses` AS `out_addresses` ON `out_addresses`.`id` = `transaction_outputs`.`address_id`").
|
|
Joins("LEFT JOIN `transaction_inputs` ON `transaction_inputs`.`transaction_id` = `transactions`.`id`").
|
|
Joins("LEFT JOIN `transaction_outputs` AS `inputs_outs` ON `inputs_outs`.`id` = `transaction_inputs`.`transaction_output_id`").
|
|
Joins("LEFT JOIN `addresses` AS `in_addresses` ON `in_addresses`.`id` = `inputs_outs`.`address_id`").
|
|
Where("`out_addresses`.`address` = ?", address).
|
|
Or("`in_addresses`.`address` = ?", address).
|
|
Limit(limit).
|
|
Offset(skip).
|
|
Order("`transactions`.`id` ASC")
|
|
dbResult := addTxPreloadedFields(query).Find(&txs)
|
|
dbErrors := dbResult.GetErrors()
|
|
if utils.HasDBError(dbErrors) {
|
|
return nil, utils.NewHandlerErrorFromDBErrors("Some errors where encountered when loading transactions from the database:", dbErrors)
|
|
}
|
|
txResponses := make([]*transactionResponse, len(txs))
|
|
for i, tx := range txs {
|
|
txResponses[i] = convertTxModelToTxResponse(tx)
|
|
}
|
|
return txResponses, nil
|
|
}
|
|
|
|
// GetUTXOsByAddressHandler searches for all UTXOs that belong to a certain address.
|
|
func GetUTXOsByAddressHandler(address string) (interface{}, *utils.HandlerError) {
|
|
db, err := database.DB()
|
|
if err != nil {
|
|
return nil, utils.NewHandlerError(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
|
}
|
|
|
|
var transactionOutputs []*models.TransactionOutput
|
|
dbErrors := db.
|
|
Joins("LEFT JOIN `addresses` ON `addresses`.`id` = `transaction_outputs`.`address_id`").
|
|
Where("`addresses`.`address` = ? AND `transaction_outputs`.`is_spent` = 0", address).
|
|
Preload("Transaction.AcceptingBlock").
|
|
Find(&transactionOutputs).GetErrors()
|
|
if len(dbErrors) > 0 {
|
|
return nil, utils.NewHandlerErrorFromDBErrors("Some errors where encountered when loading UTXOs from the database:", dbErrors)
|
|
}
|
|
|
|
UTXOsResponses := make([]*transactionOutputResponse, len(transactionOutputs))
|
|
for i, transactionOutput := range transactionOutputs {
|
|
UTXOsResponses[i] = &transactionOutputResponse{
|
|
Value: transactionOutput.Value,
|
|
ScriptPubKey: hex.EncodeToString(transactionOutput.ScriptPubKey),
|
|
AcceptingBlockHash: transactionOutput.Transaction.AcceptingBlock.BlockHash,
|
|
AcceptingBlockBlueScore: transactionOutput.Transaction.AcceptingBlock.BlueScore,
|
|
}
|
|
}
|
|
return UTXOsResponses, nil
|
|
}
|
|
|
|
func addTxPreloadedFields(query *gorm.DB) *gorm.DB {
|
|
return query.Preload("AcceptingBlock").
|
|
Preload("Subnetwork").
|
|
Preload("TransactionOutputs").
|
|
Preload("TransactionOutputs.Address").
|
|
Preload("TransactionInputs.PreviousTransactionOutput.Transaction").
|
|
Preload("TransactionInputs.PreviousTransactionOutput.Address")
|
|
}
|
|
|
|
// PostTransaction forwards a raw transaction to the JSON-RPC API server
|
|
func PostTransaction(requestBody []byte) *utils.HandlerError {
|
|
client, err := jsonrpc.GetClient()
|
|
if err != nil {
|
|
return utils.NewInternalServerHandlerError(err.Error())
|
|
}
|
|
|
|
rawTx := &RawTransaction{}
|
|
err = json.Unmarshal(requestBody, rawTx)
|
|
if err != nil {
|
|
return utils.NewHandlerErrorWithCustomClientMessage(http.StatusUnprocessableEntity,
|
|
fmt.Sprintf("Error unmarshalling request body: %s", err),
|
|
"The request body is not json-formatted")
|
|
}
|
|
|
|
txBytes, err := hex.DecodeString(rawTx.RawTransaction)
|
|
if err != nil {
|
|
return utils.NewHandlerErrorWithCustomClientMessage(http.StatusUnprocessableEntity,
|
|
fmt.Sprintf("Error decoding hex raw transaction: %s", err),
|
|
"The raw transaction is not a hex-encoded transaction")
|
|
}
|
|
|
|
txReader := bytes.NewReader(txBytes)
|
|
tx := &wire.MsgTx{}
|
|
err = tx.BtcDecode(txReader, 0)
|
|
if err != nil {
|
|
return utils.NewHandlerErrorWithCustomClientMessage(http.StatusUnprocessableEntity,
|
|
fmt.Sprintf("Error decoding raw transaction: %s", err),
|
|
"Error decoding raw transaction")
|
|
}
|
|
|
|
_, err = client.SendRawTransaction(tx, true)
|
|
if err != nil {
|
|
if rpcErr, ok := err.(btcjson.RPCError); ok && rpcErr.Code == btcjson.ErrRPCVerify {
|
|
return utils.NewHandlerError(http.StatusInternalServerError, rpcErr.Message)
|
|
}
|
|
return utils.NewHandlerError(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
|
}
|
|
|
|
return nil
|
|
}
|