kaspad/rpc/common.go
Ori Newman 8e170cf327
[NOD-1225] Rename wire to domainmessage and get rid of InvType (#853)
* [NOD-1225] Rename wire to domainmessage

* [NOD-1225] Get rid of references to package wire in the code, and get rid of InvType
2020-08-09 12:39:15 +03:00

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
}