kaspad/domain/mining/mining.go
stasatdaglabs 4c915f12b7
[NOD-1319] Reimplement kaspad's RPC in gRPC (#914)
* [NOD-1319] Create the protorpc package.

* [NOD-1319] Use a general ClientStream in closeSend.

* [NOD-1319] Decouple p2pServer from gRPCServer.

* [NOD-1319] Begin implementing rpcServer.

* [NOD-1319] Move grpcStream to grpc_connection.go.

* [NOD-1319] Fold the rpc messages.proto into a common message.proto.

* [NOD-1319] Remove code duplication in MessageStream.

* [NOD-1319] Rename methods in netadapter.

* [NOD-1319] Rename message_xxx to p2p_xxx.

* [NOD-1319] Section off p2p messages from rpc messages in messages.proto.

* [NOD-1319] Split toPayload to a p2p part and and rpc part.

* [NOD-1319] Rename msgxxx.go to p2p_msgxx.go in the appmessage package.

* [NOD-1319] Implement GetCurrentVersionRequestMessage and GetCurrentVersionResponseMessage.

* [NOD-1319] Implement toAppMessage and fromAppMessage for getCurrentNetwork

* [NOD-1319] Make a temporary workaround so that tests pass.

* [NOD-1319] Begin implementing the rpc manager.

* [NOD-1319] Implement an initial routerInitializer for rpc.

* [NOD-1319] Rename the spawn in routerInitializer.

* [NOD-1319] Implement an RPC context.

* [NOD-1319] Move the actual handlers to a separate package.

* [NOD-1319] Use the correct value for the GetCurrentNetwork response.

* [NOD-1319] Fix some names.

* [NOD-1319] Begin implementing a cli rpc client.

* [NOD-1319] Implement connecting to the RPC server.

* [NOD-1319] Make sure that connecting to the server and sending/receiving messages works.

* [NOD-1319] Make kaspactl2 speak in json strings.

* [NOD-1319] Finish implementing kaspactl2.

* [NOD-1319] Remove debug messages.

* [NOD-1319] Properly handle errors in rpc.go.

* [NOD-1319] Move the grpc client to a separate package.

* [NOD-1319] Extract Post out of PostString.

* [NOD-1319] Implement PostAppMessage.

* [NOD-1319] Stub out submitBlock.

* [NOD-1319] Stub out getBlockTemplate.

* [NOD-1319] Combine request and reponse files.

* [NOD-1319] Implement submitBlock.

* [NOD-1319] Implement returning errors from RPC.

* [NOD-1319] Begin implementing getBlockTemplate.

* [NOD-1319] Add missing field in GetBlockTemplateRequestMessage.

* [NOD-1319] Implement a minimal getBlockTemplate.

* [NOD-1319] Add getBlockTemplate stuff to grpc.

* [NOD-1319] Implement the rest of getBlockTemplate.

* [NOD-1319] Add block/transaction added handlers to the protocol manager.

* [NOD-1319] Implement NotifyTransactionAddedToMempool.

* [NOD-1319] Implement NotifyBlockAddedToDAG.

* [NOD-1319] Connect block/transaction added handlers.

* [NOD-1319] Add notifyBlockAdded.

* [NOD-1319] Add a notification system.

* [NOD-1319] Improve the notification system.

* [NOD-1319] Add a block added listener stub.

* [NOD-1319] Add BlockAddedNotificationMessage.

* [NOD-1319] Finish implementing HandleNotifyBlockAdded.

* [NOD-1319] Println instead of Print in kaspactl2.

* [NOD-1319] Remove unused flags in kaspactl2.

* [NOD-1319] Make kaspaminer work with the new RPC.

* [NOD-1319] Fix a bad log.

* [NOD-1319] Make kaspaminer work.

* [NOD-1319] Disconnect the old RPC.

* [NOD-1319] Move grpcclient.go.

* [NOD-1319] Begin generalizing the rpcClient.

* [NOD-1319] Move errors to the side of the payload.

* [NOD-1319] Add errors to appmessage.

* [NOD-1319] Move AttachRouter to grpcclient.

* [NOD-1319] Fix kaspaminer not handling responses.

* [NOD-1319] Properly handle blockAddedNotifications.

* [NOD-1319] Move errors into individual response objects.

* [NOD-1319] Begin replacing the RPC client in the integration tests.

* [NOD-1319] Implement GetPeerAddresses.

* [NOD-1319] Implement GetPeerAddresses.

* [NOD-1319] Fix setOnBlockAddedHandler.

* [NOD-1319] Remove the old kaspactl.

* [NOD-1319] Move ConvertGetBlockTemplateResultToBlock to the mining package.

* [NOD-1319] Implement getSelectedTipHash.

* [NOD-1319] Simplify testRPCRouter.

* [NOD-1319] Write stubs for the required test RPC commands.

* [NOD-1319] Implement a minimal getMempoolEntry.

* [NOD-1319] Implement a minimal getMempoolEntry.

* [NOD-1319] Implement getConnectedPeerInfo.

* [NOD-1319] Delete the old RPC.

* [NOD-1319] Fix a fromAppMessage.

* [NOD-1319] Implement connectToPeer.

* [NOD-1319] Fix a bug in registerForBlockAddedNotifications.

* [NOD-1319] Fix a deadlock in closing notification listeners.

* [NOD-1319] Fix merge errors.

* [NOD-1319] Fix an import.

* [NOD-1319] Properly handle errors in grpcclient.

* [NOD-1319] Fix TestIBD.

* [NOD-1319] Prevent kaspaminer from running when not connected.

* [NOD-1319] Implement sendRawTransaction.

* [NOD-1319] Implement sendRawTransaction in the client.

* [NOD-1319] Extract a general RPC client from the integration test RPC client.

* [NOD-1319] Use the general RPC client for the miner.

* [NOD-1319] Move the rpcclient package out of netadapter.

* [NOD-1319] Normalize onBlockAdded.

* [NOD-1319] Begin implementing notifyChainChanged.

* [NOD-1319] Implement the model for notifyChainChanged.

* [NOD-1319] Implement conversions for notifyChainChanged.

* [NOD-1319] Implement HandleNotifyChainChanged.

* [NOD-1319] Normalize notifications.

* [NOD-1319] Implement RegisterForChainChangedNotifications.

* [NOD-1319] Begin connecting blockdag's chain-changed notification with the RPC.

* [NOD-1319] Finish implementing notifyChainChanged.

* [NOD-1319] Implement getBlockHex.

* [NOD-1319] Rename getBlockHex to getBlock.

* [NOD-1319] Implement the verbose model for getBlock.

* [NOD-1319] Implement buildBlockVerboseData.

* [NOD-1319] Implement buildTransactionVerboseData.

* [NOD-1319] Move verboseData stuff to verbosedata.go.

* [NOD-1319] Add includeTransactionVerboseData.

* [NOD-1319] Begin implementing getSubnetwork.

* [NOD-1319] Finish implementing getSubnetwork.

* [NOD-1319] Begin implementing getChainFromBlock.

* [NOD-1319] Finish implementing getChainFromBlock.

* [NOD-1319] Begin implementing getBlocks.

* [NOD-1319] Finish implementing getBlocks.

* [NOD-1319] Fix bad responses in HandleNotifyChainChanged.

* [NOD-1319] Fix bugs in verbosedata.go.

* [NOD-1319] Fix more bugs in verbosedata.go.

* [NOD-1319] Make go vet happy.

* [NOD-1319] Extract handleBlockDAGNotifications to a method.

* [NOD-1319] Add a newline.

* [NOD-1319] Use peers instead of connections to check if connected.

* [NOD-1319] Add a comment.

* [NOD-1319] Lock the dag lock in getBlock.

* [NOD-1319] Rename netAdapter.connections to p2pConnections.

* [NOD-1319] In protowire, rename wireXXX to protoXXX.

* [NOD-1319] Rename PostString to PostJSON.

* [NOD-1319] Disallow empty transactions in SendRawTransaction.

* [NOD-1319] Disallow empty blocks in SubmitBlocks.

* [NOD-1319] Add SetLogger.

* [NOD-1319] Fix an error message.

* [NOD-1319] Fix an error message.

* [NOD-1319] Rename testTimeout to rpcTimeout.

* [NOD-1319] Rename SendRawTransaction to SubmitTransaction.

* [NOD-1319] Rename ConnectToPeer to AddPeer.

* [NOD-1319] Add missing longPollID to request.

* [NOD-1319] Rename ChainChangedChainBlock to ChainBlock.

* [NOD-1319] Rename Vin and Vout.

* [NOD-1319] Implement RPCErrorf.

* [NOD-1319] Fix RPCErrorf's comment.

* [NOD-1319] Remove unused flags in kaspaminer.
2020-09-07 14:35:40 +03:00

305 lines
12 KiB
Go

// Copyright (c) 2014-2016 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package mining
import (
"encoding/hex"
"github.com/kaspanet/kaspad/util/mstime"
"github.com/pkg/errors"
"strconv"
"strings"
"time"
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/domain/blockdag"
"github.com/kaspanet/kaspad/domain/txscript"
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/util/daghash"
)
const (
// CoinbaseFlags is added to the coinbase script of a generated block
// and is used to monitor BIP16 support as well as blocks that are
// generated via kaspad.
CoinbaseFlags = "/kaspad/"
)
// TxDesc is a descriptor about a transaction in a transaction source along with
// additional metadata.
type TxDesc struct {
// Tx is the transaction associated with the entry.
Tx *util.Tx
// Added is the time when the entry was added to the source pool.
Added mstime.Time
// Fee is the total fee the transaction associated with the entry pays.
Fee uint64
// FeePerMegaGram is the fee the transaction pays in sompi per million gram.
FeePerMegaGram uint64
}
// TxSource represents a source of transactions to consider for inclusion in
// new blocks.
//
// The interface contract requires that all of these methods are safe for
// concurrent access with respect to the source.
type TxSource interface {
// LastUpdated returns the last time a transaction was added to or
// removed from the source pool.
LastUpdated() mstime.Time
// MiningDescs returns a slice of mining descriptors for all the
// transactions in the source pool.
MiningDescs() []*TxDesc
// HaveTransaction returns whether or not the passed transaction hash
// exists in the source pool.
HaveTransaction(txID *daghash.TxID) bool
}
// BlockTemplate houses a block that has yet to be solved along with additional
// details about the fees and the number of signature operations for each
// transaction in the block.
type BlockTemplate struct {
// Block is a block that is ready to be solved by miners. Thus, it is
// completely valid with the exception of satisfying the proof-of-work
// requirement.
Block *appmessage.MsgBlock
// TxMasses contains the mass of each transaction in the generated
// template performs.
TxMasses []uint64
// Fees contains the amount of fees each transaction in the generated
// template pays in base units. Since the first transaction is the
// coinbase, the first entry (offset 0) will contain the negative of the
// sum of the fees of all other transactions.
Fees []uint64
// Height is the height at which the block template connects to the DAG
Height uint64
}
// BlkTmplGenerator provides a type that can be used to generate block templates
// based on a given mining policy and source of transactions to choose from.
// It also houses additional state required in order to ensure the templates
// are built on top of the current DAG and adhere to the consensus rules.
type BlkTmplGenerator struct {
policy *Policy
txSource TxSource
dag *blockdag.BlockDAG
sigCache *txscript.SigCache
}
// NewBlkTmplGenerator returns a new block template generator for the given
// policy using transactions from the provided transaction source.
//
// The additional state-related fields are required in order to ensure the
// templates are built on top of the current DAG and adhere to the
// consensus rules.
func NewBlkTmplGenerator(policy *Policy,
txSource TxSource, dag *blockdag.BlockDAG,
sigCache *txscript.SigCache) *BlkTmplGenerator {
return &BlkTmplGenerator{
policy: policy,
txSource: txSource,
dag: dag,
sigCache: sigCache,
}
}
// NewBlockTemplate returns a new block template that is ready to be solved
// using the transactions from the passed transaction source pool and a coinbase
// that either pays to the passed address if it is not nil, or a coinbase that
// is redeemable by anyone if the passed address is nil. The nil address
// functionality is useful since there are cases such as the getblocktemplate
// RPC where external mining software is responsible for creating their own
// coinbase which will replace the one generated for the block template. Thus
// the need to have configured address can be avoided.
//
// The transactions selected and included are prioritized according to several
// factors. First, each transaction has a priority calculated based on its
// value, age of inputs, and size. Transactions which consist of larger
// amounts, older inputs, and small sizes have the highest priority. Second, a
// fee per kilobyte is calculated for each transaction. Transactions with a
// higher fee per kilobyte are preferred. Finally, the block generation related
// policy settings are all taken into account.
//
// Transactions which only spend outputs from other transactions already in the
// block DAG are immediately added to a priority queue which either
// prioritizes based on the priority (then fee per kilobyte) or the fee per
// kilobyte (then priority) depending on whether or not the BlockPrioritySize
// policy setting allots space for high-priority transactions. Transactions
// which spend outputs from other transactions in the source pool are added to a
// dependency map so they can be added to the priority queue once the
// transactions they depend on have been included.
//
// Once the high-priority area (if configured) has been filled with
// transactions, or the priority falls below what is considered high-priority,
// the priority queue is updated to prioritize by fees per kilobyte (then
// priority).
//
// When the fees per kilobyte drop below the TxMinFreeFee policy setting, the
// transaction will be skipped unless the BlockMinSize policy setting is
// nonzero, in which case the block will be filled with the low-fee/free
// transactions until the block size reaches that minimum size.
//
// Any transactions which would cause the block to exceed the BlockMaxMass
// policy setting, exceed the maximum allowed signature operations per block, or
// otherwise cause the block to be invalid are skipped.
//
// Given the above, a block generated by this function is of the following form:
//
// ----------------------------------- -- --
// | Coinbase Transaction | | |
// |-----------------------------------| | |
// | | | | ----- policy.BlockPrioritySize
// | High-priority Transactions | | |
// | | | |
// |-----------------------------------| | --
// | | |
// | | |
// | | |--- policy.BlockMaxMass
// | Transactions prioritized by fee | |
// | until <= policy.TxMinFreeFee | |
// | | |
// | | |
// | | |
// |-----------------------------------| |
// | Low-fee/Non high-priority (free) | |
// | transactions (while block size | |
// | <= policy.BlockMinSize) | |
// ----------------------------------- --
func (g *BlkTmplGenerator) NewBlockTemplate(payToAddress util.Address, extraNonce uint64) (*BlockTemplate, error) {
g.dag.Lock()
defer g.dag.Unlock()
txsForBlockTemplate, err := g.selectTxs(payToAddress, extraNonce)
if err != nil {
return nil, errors.Errorf("failed to select transactions: %s", err)
}
msgBlock, err := g.dag.BlockForMining(txsForBlockTemplate.selectedTxs)
if err != nil {
return nil, err
}
// Finally, perform a full check on the created block against the DAG
// consensus rules to ensure it properly connects to the DAG with no
// issues.
block := util.NewBlock(msgBlock)
if err := g.dag.CheckConnectBlockTemplateNoLock(block); err != nil {
return nil, err
}
log.Debugf("Created new block template (%d transactions, %d in fees, "+
"%d mass, target difficulty %064x)",
len(msgBlock.Transactions), txsForBlockTemplate.totalFees,
txsForBlockTemplate.totalMass, util.CompactToBig(msgBlock.Header.Bits))
return &BlockTemplate{
Block: msgBlock,
TxMasses: txsForBlockTemplate.txMasses,
Fees: txsForBlockTemplate.txFees,
}, nil
}
// UpdateBlockTime updates the timestamp in the header of the passed block to
// the current time while taking into account the median time of the last
// several blocks to ensure the new time is after that time per the DAG
// consensus rules. Finally, it will update the target difficulty if needed
// based on the new time for the test networks since their target difficulty can
// change based upon time.
func (g *BlkTmplGenerator) UpdateBlockTime(msgBlock *appmessage.MsgBlock) error {
// The new timestamp is potentially adjusted to ensure it comes after
// the median time of the last several blocks per the DAG consensus
// rules.
msgBlock.Header.Timestamp = g.dag.NextBlockTime()
msgBlock.Header.Bits = g.dag.NextRequiredDifficulty(msgBlock.Header.Timestamp)
return nil
}
// TxSource returns the associated transaction source.
//
// This function is safe for concurrent access.
func (g *BlkTmplGenerator) TxSource() TxSource {
return g.txSource
}
// IsSynced checks if the node is synced enough based upon its worldview.
// This is used to determine if the node can support mining and requesting newly-mined blocks.
// To do that, first it checks if the selected tip timestamp is not older than maxTipAge. If that's the case, it means
// the node is synced since blocks' timestamps are not allowed to deviate too much into the future.
// If that's not the case it checks the rate it added new blocks to the DAG recently. If it's faster than
// blockRate * maxSyncRateDeviation it means the node is not synced, since when the node is synced it shouldn't add
// blocks to the DAG faster than the block rate.
func (g *BlkTmplGenerator) IsSynced() bool {
const maxTipAge = 5 * time.Minute
isCloseToCurrentTime := g.dag.Now().Sub(g.dag.SelectedTipHeader().Timestamp) <= maxTipAge
if isCloseToCurrentTime {
return true
}
const maxSyncRateDeviation = 1.05
return g.dag.IsSyncRateBelowThreshold(maxSyncRateDeviation)
}
// ConvertGetBlockTemplateResultToBlock converts the given template into a util.Block
func ConvertGetBlockTemplateResultToBlock(template *appmessage.GetBlockTemplateResponseMessage) (*util.Block, error) {
// parse parent hashes
parentHashes := make([]*daghash.Hash, len(template.ParentHashes))
for i, parentHash := range template.ParentHashes {
hash, err := daghash.NewHashFromStr(parentHash)
if err != nil {
return nil, errors.Wrapf(err, "error decoding hash: '%s'", parentHash)
}
parentHashes[i] = hash
}
// parse Bits
bitsUint64, err := strconv.ParseUint(template.Bits, 16, 32)
if err != nil {
return nil, errors.Wrapf(err, "error decoding bits: '%s'", template.Bits)
}
bits := uint32(bitsUint64)
// parse hashMerkleRoot
hashMerkleRoot, err := daghash.NewHashFromStr(template.HashMerkleRoot)
if err != nil {
return nil, errors.Wrapf(err, "error parsing HashMerkleRoot: '%s'", template.HashMerkleRoot)
}
// parse AcceptedIDMerkleRoot
acceptedIDMerkleRoot, err := daghash.NewHashFromStr(template.AcceptedIDMerkleRoot)
if err != nil {
return nil, errors.Wrapf(err, "error parsing acceptedIDMerkleRoot: '%s'", template.AcceptedIDMerkleRoot)
}
utxoCommitment, err := daghash.NewHashFromStr(template.UTXOCommitment)
if err != nil {
return nil, errors.Wrapf(err, "error parsing utxoCommitment '%s'", template.UTXOCommitment)
}
// parse rest of block
msgBlock := appmessage.NewMsgBlock(
appmessage.NewBlockHeader(template.Version, parentHashes, hashMerkleRoot,
acceptedIDMerkleRoot, utxoCommitment, bits, 0))
for i, txResult := range template.Transactions {
reader := hex.NewDecoder(strings.NewReader(txResult.Data))
tx := &appmessage.MsgTx{}
if err := tx.KaspaDecode(reader, 0); err != nil {
return nil, errors.Wrapf(err, "error decoding tx #%d", i)
}
msgBlock.AddTransaction(tx)
}
block := util.NewBlock(msgBlock)
return block, nil
}