mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-03-30 15:08:33 +00:00

* [NOD-1225] Rename wire to domainmessage * [NOD-1225] Get rid of references to package wire in the code, and get rid of InvType
359 lines
12 KiB
Go
359 lines
12 KiB
Go
package rpc
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"github.com/kaspanet/kaspad/dagconfig"
|
|
"github.com/kaspanet/kaspad/domainmessage"
|
|
"github.com/kaspanet/kaspad/rpc/model"
|
|
"github.com/kaspanet/kaspad/txscript"
|
|
"github.com/kaspanet/kaspad/util"
|
|
"github.com/kaspanet/kaspad/util/daghash"
|
|
"github.com/kaspanet/kaspad/util/pointers"
|
|
"math/big"
|
|
"strconv"
|
|
)
|
|
|
|
var (
|
|
// ErrRPCUnimplemented is an error returned to RPC clients when the
|
|
// provided command is recognized, but not implemented.
|
|
ErrRPCUnimplemented = &model.RPCError{
|
|
Code: model.ErrRPCUnimplemented,
|
|
Message: "Command unimplemented",
|
|
}
|
|
)
|
|
|
|
// internalRPCError is a convenience function to convert an internal error to
|
|
// an RPC error with the appropriate code set. It also logs the error to the
|
|
// RPC server subsystem since internal errors really should not occur. The
|
|
// context parameter is only used in the log message and may be empty if it's
|
|
// not needed.
|
|
func internalRPCError(errStr, context string) *model.RPCError {
|
|
logStr := errStr
|
|
if context != "" {
|
|
logStr = context + ": " + errStr
|
|
}
|
|
log.Error(logStr)
|
|
return model.NewRPCError(model.ErrRPCInternal.Code, errStr)
|
|
}
|
|
|
|
// rpcDecodeHexError is a convenience function for returning a nicely formatted
|
|
// RPC error which indicates the provided hex string failed to decode.
|
|
func rpcDecodeHexError(gotHex string) *model.RPCError {
|
|
return model.NewRPCError(model.ErrRPCDecodeHexString,
|
|
fmt.Sprintf("Argument must be hexadecimal string (not %q)",
|
|
gotHex))
|
|
}
|
|
|
|
// rpcNoTxInfoError is a convenience function for returning a nicely formatted
|
|
// RPC error which indicates there is no information available for the provided
|
|
// transaction hash.
|
|
func rpcNoTxInfoError(txID *daghash.TxID) *model.RPCError {
|
|
return model.NewRPCError(model.ErrRPCNoTxInfo,
|
|
fmt.Sprintf("No information available about transaction %s",
|
|
txID))
|
|
}
|
|
|
|
// msgTxToHex serializes a transaction using the latest protocol version and
|
|
// returns a hex-encoded string of the result.
|
|
func msgTxToHex(msgTx *domainmessage.MsgTx) (string, error) {
|
|
var buf bytes.Buffer
|
|
if err := msgTx.KaspaEncode(&buf, maxProtocolVersion); err != nil {
|
|
context := fmt.Sprintf("Failed to encode msg of type %T", msgTx)
|
|
return "", internalRPCError(err.Error(), context)
|
|
}
|
|
|
|
return hex.EncodeToString(buf.Bytes()), nil
|
|
}
|
|
|
|
// createVinList returns a slice of JSON objects for the inputs of the passed
|
|
// transaction.
|
|
func createVinList(mtx *domainmessage.MsgTx) []model.Vin {
|
|
vinList := make([]model.Vin, len(mtx.TxIn))
|
|
for i, txIn := range mtx.TxIn {
|
|
// The disassembled string will contain [error] inline
|
|
// if the script doesn't fully parse, so ignore the
|
|
// error here.
|
|
disbuf, _ := txscript.DisasmString(txIn.SignatureScript)
|
|
|
|
vinEntry := &vinList[i]
|
|
vinEntry.TxID = txIn.PreviousOutpoint.TxID.String()
|
|
vinEntry.Vout = txIn.PreviousOutpoint.Index
|
|
vinEntry.Sequence = txIn.Sequence
|
|
vinEntry.ScriptSig = &model.ScriptSig{
|
|
Asm: disbuf,
|
|
Hex: hex.EncodeToString(txIn.SignatureScript),
|
|
}
|
|
}
|
|
|
|
return vinList
|
|
}
|
|
|
|
// createVoutList returns a slice of JSON objects for the outputs of the passed
|
|
// transaction.
|
|
func createVoutList(mtx *domainmessage.MsgTx, dagParams *dagconfig.Params, filterAddrMap map[string]struct{}) []model.Vout {
|
|
voutList := make([]model.Vout, 0, len(mtx.TxOut))
|
|
for i, v := range mtx.TxOut {
|
|
// The disassembled string will contain [error] inline if the
|
|
// script doesn't fully parse, so ignore the error here.
|
|
disbuf, _ := txscript.DisasmString(v.ScriptPubKey)
|
|
|
|
// Ignore the error here since an error means the script
|
|
// couldn't parse and there is no additional information about
|
|
// it anyways.
|
|
scriptClass, addr, _ := txscript.ExtractScriptPubKeyAddress(
|
|
v.ScriptPubKey, dagParams)
|
|
|
|
// Encode the addresses while checking if the address passes the
|
|
// filter when needed.
|
|
passesFilter := len(filterAddrMap) == 0
|
|
var encodedAddr *string
|
|
if addr != nil {
|
|
encodedAddr = pointers.String(addr.EncodeAddress())
|
|
|
|
// If the filter doesn't already pass, make it pass if
|
|
// the address exists in the filter.
|
|
if _, exists := filterAddrMap[*encodedAddr]; exists {
|
|
passesFilter = true
|
|
}
|
|
}
|
|
|
|
if !passesFilter {
|
|
continue
|
|
}
|
|
|
|
var vout model.Vout
|
|
vout.N = uint32(i)
|
|
vout.Value = v.Value
|
|
vout.ScriptPubKey.Address = encodedAddr
|
|
vout.ScriptPubKey.Asm = disbuf
|
|
vout.ScriptPubKey.Hex = hex.EncodeToString(v.ScriptPubKey)
|
|
vout.ScriptPubKey.Type = scriptClass.String()
|
|
|
|
voutList = append(voutList, vout)
|
|
}
|
|
|
|
return voutList
|
|
}
|
|
|
|
// createTxRawResult converts the passed transaction and associated parameters
|
|
// to a raw transaction JSON object.
|
|
func createTxRawResult(dagParams *dagconfig.Params, mtx *domainmessage.MsgTx,
|
|
txID string, blkHeader *domainmessage.BlockHeader, blkHash string,
|
|
acceptingBlock *daghash.Hash, isInMempool bool) (*model.TxRawResult, error) {
|
|
|
|
mtxHex, err := msgTxToHex(mtx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var payloadHash string
|
|
if mtx.PayloadHash != nil {
|
|
payloadHash = mtx.PayloadHash.String()
|
|
}
|
|
|
|
txReply := &model.TxRawResult{
|
|
Hex: mtxHex,
|
|
TxID: txID,
|
|
Hash: mtx.TxHash().String(),
|
|
Size: int32(mtx.SerializeSize()),
|
|
Vin: createVinList(mtx),
|
|
Vout: createVoutList(mtx, dagParams, nil),
|
|
Version: mtx.Version,
|
|
LockTime: mtx.LockTime,
|
|
Subnetwork: mtx.SubnetworkID.String(),
|
|
Gas: mtx.Gas,
|
|
PayloadHash: payloadHash,
|
|
Payload: hex.EncodeToString(mtx.Payload),
|
|
}
|
|
|
|
if blkHeader != nil {
|
|
txReply.Time = uint64(blkHeader.Timestamp.UnixMilliseconds())
|
|
txReply.BlockTime = uint64(blkHeader.Timestamp.UnixMilliseconds())
|
|
txReply.BlockHash = blkHash
|
|
}
|
|
|
|
txReply.IsInMempool = isInMempool
|
|
if acceptingBlock != nil {
|
|
txReply.AcceptedBy = pointers.String(acceptingBlock.String())
|
|
}
|
|
|
|
return txReply, nil
|
|
}
|
|
|
|
// getDifficultyRatio returns the proof-of-work difficulty as a multiple of the
|
|
// minimum difficulty using the passed bits field from the header of a block.
|
|
func getDifficultyRatio(bits uint32, params *dagconfig.Params) float64 {
|
|
// The minimum difficulty is the max possible proof-of-work limit bits
|
|
// converted back to a number. Note this is not the same as the proof of
|
|
// work limit directly because the block difficulty is encoded in a block
|
|
// with the compact form which loses precision.
|
|
target := util.CompactToBig(bits)
|
|
|
|
difficulty := new(big.Rat).SetFrac(params.PowMax, target)
|
|
outString := difficulty.FloatString(8)
|
|
diff, err := strconv.ParseFloat(outString, 64)
|
|
if err != nil {
|
|
log.Errorf("Cannot get difficulty: %s", err)
|
|
return 0
|
|
}
|
|
return diff
|
|
}
|
|
|
|
// buildGetBlockVerboseResult takes a block and convert it to model.GetBlockVerboseResult
|
|
//
|
|
// This function MUST be called with the DAG state lock held (for reads).
|
|
func buildGetBlockVerboseResult(s *Server, block *util.Block, isVerboseTx bool) (*model.GetBlockVerboseResult, error) {
|
|
hash := block.Hash()
|
|
params := s.dag.Params
|
|
blockHeader := block.MsgBlock().Header
|
|
|
|
blockBlueScore, err := s.dag.BlueScoreByBlockHash(hash)
|
|
if err != nil {
|
|
context := "Could not get block blue score"
|
|
return nil, internalRPCError(err.Error(), context)
|
|
}
|
|
|
|
// Get the hashes for the next blocks unless there are none.
|
|
childHashes, err := s.dag.ChildHashesByHash(hash)
|
|
if err != nil {
|
|
context := "No next block"
|
|
return nil, internalRPCError(err.Error(), context)
|
|
}
|
|
|
|
blockConfirmations, err := s.dag.BlockConfirmationsByHashNoLock(hash)
|
|
if err != nil {
|
|
context := "Could not get block confirmations"
|
|
return nil, internalRPCError(err.Error(), context)
|
|
}
|
|
|
|
selectedParentHash, err := s.dag.SelectedParentHash(hash)
|
|
if err != nil {
|
|
context := "Could not get block selected parent"
|
|
return nil, internalRPCError(err.Error(), context)
|
|
}
|
|
selectedParentHashStr := ""
|
|
if selectedParentHash != nil {
|
|
selectedParentHashStr = selectedParentHash.String()
|
|
}
|
|
|
|
isChainBlock, err := s.dag.IsInSelectedParentChain(hash)
|
|
if err != nil {
|
|
context := "Could not get whether block is in the selected parent chain"
|
|
return nil, internalRPCError(err.Error(), context)
|
|
}
|
|
|
|
acceptedBlockHashes, err := s.dag.BluesByBlockHash(hash)
|
|
if err != nil {
|
|
context := fmt.Sprintf("Could not get block accepted blocks for block %s", hash)
|
|
return nil, internalRPCError(err.Error(), context)
|
|
}
|
|
|
|
result := &model.GetBlockVerboseResult{
|
|
Hash: hash.String(),
|
|
Version: blockHeader.Version,
|
|
VersionHex: fmt.Sprintf("%08x", blockHeader.Version),
|
|
HashMerkleRoot: blockHeader.HashMerkleRoot.String(),
|
|
AcceptedIDMerkleRoot: blockHeader.AcceptedIDMerkleRoot.String(),
|
|
UTXOCommitment: blockHeader.UTXOCommitment.String(),
|
|
ParentHashes: daghash.Strings(blockHeader.ParentHashes),
|
|
SelectedParentHash: selectedParentHashStr,
|
|
Nonce: blockHeader.Nonce,
|
|
Time: blockHeader.Timestamp.UnixMilliseconds(),
|
|
Confirmations: blockConfirmations,
|
|
BlueScore: blockBlueScore,
|
|
IsChainBlock: isChainBlock,
|
|
Size: int32(block.MsgBlock().SerializeSize()),
|
|
Bits: strconv.FormatInt(int64(blockHeader.Bits), 16),
|
|
Difficulty: getDifficultyRatio(blockHeader.Bits, params),
|
|
ChildHashes: daghash.Strings(childHashes),
|
|
AcceptedBlockHashes: daghash.Strings(acceptedBlockHashes),
|
|
}
|
|
|
|
if isVerboseTx {
|
|
transactions := block.Transactions()
|
|
txNames := make([]string, len(transactions))
|
|
for i, tx := range transactions {
|
|
txNames[i] = tx.ID().String()
|
|
}
|
|
|
|
result.Tx = txNames
|
|
} else {
|
|
txns := block.Transactions()
|
|
rawTxns := make([]model.TxRawResult, len(txns))
|
|
for i, tx := range txns {
|
|
rawTxn, err := createTxRawResult(params, tx.MsgTx(), tx.ID().String(),
|
|
&blockHeader, hash.String(), nil, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rawTxns[i] = *rawTxn
|
|
}
|
|
result.RawTx = rawTxns
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func collectChainBlocks(s *Server, hashes []*daghash.Hash) ([]model.ChainBlock, error) {
|
|
chainBlocks := make([]model.ChainBlock, 0, len(hashes))
|
|
for _, hash := range hashes {
|
|
acceptanceData, err := s.acceptanceIndex.TxsAcceptanceData(hash)
|
|
if err != nil {
|
|
return nil, &model.RPCError{
|
|
Code: model.ErrRPCInternal.Code,
|
|
Message: fmt.Sprintf("could not retrieve acceptance data for block %s", hash),
|
|
}
|
|
}
|
|
|
|
acceptedBlocks := make([]model.AcceptedBlock, 0, len(acceptanceData))
|
|
for _, blockAcceptanceData := range acceptanceData {
|
|
acceptedTxIds := make([]string, 0, len(blockAcceptanceData.TxAcceptanceData))
|
|
for _, txAcceptanceData := range blockAcceptanceData.TxAcceptanceData {
|
|
if txAcceptanceData.IsAccepted {
|
|
acceptedTxIds = append(acceptedTxIds, txAcceptanceData.Tx.ID().String())
|
|
}
|
|
}
|
|
acceptedBlock := model.AcceptedBlock{
|
|
Hash: blockAcceptanceData.BlockHash.String(),
|
|
AcceptedTxIDs: acceptedTxIds,
|
|
}
|
|
acceptedBlocks = append(acceptedBlocks, acceptedBlock)
|
|
}
|
|
|
|
chainBlock := model.ChainBlock{
|
|
Hash: hash.String(),
|
|
AcceptedBlocks: acceptedBlocks,
|
|
}
|
|
chainBlocks = append(chainBlocks, chainBlock)
|
|
}
|
|
return chainBlocks, nil
|
|
}
|
|
|
|
// hashesToGetBlockVerboseResults takes block hashes and returns their
|
|
// correspondent block verbose.
|
|
//
|
|
// This function MUST be called with the DAG state lock held (for reads).
|
|
func hashesToGetBlockVerboseResults(s *Server, hashes []*daghash.Hash) ([]model.GetBlockVerboseResult, error) {
|
|
getBlockVerboseResults := make([]model.GetBlockVerboseResult, 0, len(hashes))
|
|
for _, blockHash := range hashes {
|
|
block, err := s.dag.BlockByHash(blockHash)
|
|
if err != nil {
|
|
return nil, &model.RPCError{
|
|
Code: model.ErrRPCInternal.Code,
|
|
Message: fmt.Sprintf("could not retrieve block %s.", blockHash),
|
|
}
|
|
}
|
|
getBlockVerboseResult, err := buildGetBlockVerboseResult(s, block, false)
|
|
if err != nil {
|
|
return nil, &model.RPCError{
|
|
Code: model.ErrRPCInternal.Code,
|
|
Message: fmt.Sprintf("could not build getBlockVerboseResult for block %s: %s", blockHash, err),
|
|
}
|
|
}
|
|
getBlockVerboseResults = append(getBlockVerboseResults, *getBlockVerboseResult)
|
|
}
|
|
return getBlockVerboseResults, nil
|
|
}
|