Implement basic getblocktemplate BIP0022 support.
This commit implements the non-optional and template tweaking support for the getblocktemplate RPC as defined by BIP0022. This implementation does not yet include long polling support. This is work towards #124.
This commit is contained in:
parent
3a45ec1058
commit
eb7ecdcc22
3 changed files with 422 additions and 21 deletions
|
@ -1129,9 +1129,9 @@ func (b *blockManager) handleNotifyMsg(notification *btcchain.Notification) {
|
|||
}
|
||||
|
||||
if r := b.server.rpcServer; r != nil {
|
||||
// Now that this block is in the blockchain we can mark all the
|
||||
// transactions (except the coinbase) as no longer needing
|
||||
// rebroadcasting.
|
||||
// Now that this block is in the blockchain we can mark
|
||||
// all the transactions (except the coinbase) as no
|
||||
// longer needing rebroadcasting.
|
||||
for _, tx := range block.Transactions()[1:] {
|
||||
iv := btcwire.NewInvVect(btcwire.InvTypeTx, tx.Sha())
|
||||
b.server.RemoveRebroadcastInventory(iv)
|
||||
|
|
14
mining.go
14
mining.go
|
@ -319,6 +319,20 @@ func logSkippedDeps(tx *btcutil.Tx, deps *list.List) {
|
|||
}
|
||||
}
|
||||
|
||||
// minimumMedianTime returns the minimum allowed timestamp for a block building
|
||||
// on the end of the current best chain. In particular, it is one second after
|
||||
// the median timestamp of the last several blocks per the chain consensus
|
||||
// rules.
|
||||
func minimumMedianTime(chainState *chainState) (time.Time, error) {
|
||||
chainState.Lock()
|
||||
defer chainState.Unlock()
|
||||
if chainState.pastMedianTimeErr != nil {
|
||||
return time.Time{}, chainState.pastMedianTimeErr
|
||||
}
|
||||
|
||||
return chainState.pastMedianTime.Add(time.Second), nil
|
||||
}
|
||||
|
||||
// medianAdjustedTime returns the current time adjusted to ensure it is at least
|
||||
// one second after the median timestamp of the last several blocks per the
|
||||
// chain consensus rules.
|
||||
|
|
423
rpcserver.go
423
rpcserver.go
|
@ -63,6 +63,23 @@ const (
|
|||
// padding format.
|
||||
hash1Len = (1 + ((btcwire.HashSize + 8) / fastsha256.BlockSize)) *
|
||||
fastsha256.BlockSize
|
||||
|
||||
// gbtRegenerateSeconds is the number of seconds that must pass before
|
||||
// a new template is generated when the previous block hash has not
|
||||
// changed and there have been changes to the available transactions
|
||||
// in the memory pool.
|
||||
gbtRegenerateSeconds = 60
|
||||
)
|
||||
|
||||
var (
|
||||
// gbtCoinbaseAux describes additional data that miners should include
|
||||
// in the coinbase signature script. It is declared here to avoid the
|
||||
// overhead of creating a new object on every invocation for constant
|
||||
// data.
|
||||
gbtCoinbaseAux = &btcjson.GetBlockTemplateResultAux{
|
||||
Flags: hex.EncodeToString(btcscript.NewScriptBuilder().
|
||||
AddData([]byte(coinbaseFlags)).Script()),
|
||||
}
|
||||
)
|
||||
|
||||
// Errors
|
||||
|
@ -93,7 +110,7 @@ var rpcHandlersBeforeInit = map[string]commandHandler{
|
|||
"getblockchaininfo": handleUnimplemented,
|
||||
"getblockcount": handleGetBlockCount,
|
||||
"getblockhash": handleGetBlockHash,
|
||||
"getblocktemplate": handleUnimplemented,
|
||||
"getblocktemplate": handleGetBlockTemplate,
|
||||
"getconnectioncount": handleGetConnectionCount,
|
||||
"getcurrentnet": handleGetCurrentNet,
|
||||
"getdifficulty": handleGetDifficulty,
|
||||
|
@ -202,21 +219,39 @@ func newWorkState() *workState {
|
|||
}
|
||||
}
|
||||
|
||||
// gbtWorkState houses state that is used in between multiple RPC invocations to
|
||||
// getblocktemplate.
|
||||
type gbtWorkState struct {
|
||||
sync.Mutex
|
||||
lastTxUpdate time.Time
|
||||
lastGenerated time.Time
|
||||
prevHash *btcwire.ShaHash
|
||||
minTimestamp time.Time
|
||||
template *BlockTemplate
|
||||
}
|
||||
|
||||
// newGbtWorkState returns a new instance of a gbtWorkState with all internal
|
||||
// fields initialized and ready to use.
|
||||
func newGbtWorkState() *gbtWorkState {
|
||||
return &gbtWorkState{}
|
||||
}
|
||||
|
||||
// rpcServer holds the items the rpc server may need to access (config,
|
||||
// shutdown, main server, etc.)
|
||||
type rpcServer struct {
|
||||
started int32
|
||||
shutdown int32
|
||||
server *server
|
||||
authsha [fastsha256.Size]byte
|
||||
ntfnMgr *wsNotificationManager
|
||||
numClients int32
|
||||
statusLines map[int]string
|
||||
statusLock sync.RWMutex
|
||||
wg sync.WaitGroup
|
||||
listeners []net.Listener
|
||||
workState *workState
|
||||
quit chan int
|
||||
started int32
|
||||
shutdown int32
|
||||
server *server
|
||||
authsha [fastsha256.Size]byte
|
||||
ntfnMgr *wsNotificationManager
|
||||
numClients int32
|
||||
statusLines map[int]string
|
||||
statusLock sync.RWMutex
|
||||
wg sync.WaitGroup
|
||||
listeners []net.Listener
|
||||
workState *workState
|
||||
gbtWorkState *gbtWorkState
|
||||
quit chan int
|
||||
}
|
||||
|
||||
// Start is used by server.go to start the rpc listener.
|
||||
|
@ -459,11 +494,12 @@ func newRPCServer(listenAddrs []string, s *server) (*rpcServer, error) {
|
|||
login := cfg.RPCUser + ":" + cfg.RPCPass
|
||||
auth := "Basic " + base64.StdEncoding.EncodeToString([]byte(login))
|
||||
rpc := rpcServer{
|
||||
authsha: fastsha256.Sum256([]byte(auth)),
|
||||
server: s,
|
||||
statusLines: make(map[int]string),
|
||||
workState: newWorkState(),
|
||||
quit: make(chan int),
|
||||
authsha: fastsha256.Sum256([]byte(auth)),
|
||||
server: s,
|
||||
statusLines: make(map[int]string),
|
||||
workState: newWorkState(),
|
||||
gbtWorkState: newGbtWorkState(),
|
||||
quit: make(chan int),
|
||||
}
|
||||
rpc.ntfnMgr = newWsNotificationManager(&rpc)
|
||||
|
||||
|
@ -1206,6 +1242,357 @@ func handleGetBlockHash(s *rpcServer, cmd btcjson.Cmd, closeChan <-chan struct{}
|
|||
return sha.String(), nil
|
||||
}
|
||||
|
||||
// updateBlockTemplate creates or updates a block template for the work state.
|
||||
// A new block template will be generated when the current best block has
|
||||
// changed or the transactions in the memory pool have been updated and it has
|
||||
// been some time has passed since the last template was generated. Otherwise,
|
||||
// the timestamp for the existing block template is updated (and possibly the
|
||||
// difficulty on testnet per the consesus rules). Finally, if the
|
||||
// useCoinbaseValue flag is flase and the existing block template does not
|
||||
// already contain a valid payment address, the block template will be updated
|
||||
// with a randomly selected payment address from the list of configured
|
||||
// addresses.
|
||||
//
|
||||
// This function MUST be called with the state locked.
|
||||
func (state *gbtWorkState) updateBlockTemplate(s *rpcServer, useCoinbaseValue bool) error {
|
||||
lastTxUpdate := s.server.txMemPool.LastUpdated()
|
||||
if lastTxUpdate.IsZero() {
|
||||
lastTxUpdate = time.Now()
|
||||
}
|
||||
|
||||
// Generate a new block template when the current best block has
|
||||
// changed or the transactions in the memory pool have been updated and
|
||||
// it has been at least gbtRegenerateSecond since the last template was
|
||||
// generated.
|
||||
var msgBlock *btcwire.MsgBlock
|
||||
var targetDifficulty string
|
||||
latestHash, _ := s.server.blockManager.chainState.Best()
|
||||
template := state.template
|
||||
if template == nil || state.prevHash == nil ||
|
||||
!state.prevHash.IsEqual(latestHash) ||
|
||||
(state.lastTxUpdate != lastTxUpdate &&
|
||||
time.Now().After(state.lastGenerated.Add(time.Second*
|
||||
gbtRegenerateSeconds))) {
|
||||
|
||||
// Reset the previous best hash the block template was generated
|
||||
// against so any errors below cause the next invocation to try
|
||||
// again.
|
||||
state.prevHash = nil
|
||||
|
||||
// Choose a payment address at random if the caller requests a
|
||||
// full coinbase as opposed to only the pertinent details needed
|
||||
// to create their own coinbase.
|
||||
var payAddr btcutil.Address
|
||||
if !useCoinbaseValue {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
payAddr = cfg.miningAddrs[rand.Intn(len(cfg.miningAddrs))]
|
||||
}
|
||||
|
||||
// Create a new block template that has a coinbase which anyone
|
||||
// can redeem. This is only acceptable because the returned
|
||||
// block template doesn't include the coinbase, so the caller
|
||||
// will ultimately create their own coinbase which pays to the
|
||||
// appropriate address(es).
|
||||
blkTemplate, err := NewBlockTemplate(s.server.txMemPool, payAddr)
|
||||
if err != nil {
|
||||
errStr := fmt.Sprintf("Failed to create new block "+
|
||||
"template: %v", err)
|
||||
rpcsLog.Errorf(errStr)
|
||||
return btcjson.Error{
|
||||
Code: btcjson.ErrInternal.Code,
|
||||
Message: errStr,
|
||||
}
|
||||
}
|
||||
template = blkTemplate
|
||||
msgBlock = template.block
|
||||
targetDifficulty = fmt.Sprintf("%064x",
|
||||
btcchain.CompactToBig(msgBlock.Header.Bits))
|
||||
|
||||
// Find the minimum allowed timestamp for the block based on the
|
||||
// median timestamp of the last several blocks per the chain
|
||||
// consensus rules.
|
||||
chainState := &s.server.blockManager.chainState
|
||||
minTimestamp, err := minimumMedianTime(chainState)
|
||||
if err != nil {
|
||||
return btcjson.Error{
|
||||
Code: btcjson.ErrInternal.Code,
|
||||
Message: err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
// Update work state to ensure another block template isn't
|
||||
// generated until needed.
|
||||
state.template = template
|
||||
state.lastGenerated = time.Now()
|
||||
state.lastTxUpdate = lastTxUpdate
|
||||
state.prevHash = latestHash
|
||||
state.minTimestamp = minTimestamp
|
||||
|
||||
rpcsLog.Debugf("Generated block template (timestamp %v, "+
|
||||
"target %s, merkle root %s)",
|
||||
msgBlock.Header.Timestamp, targetDifficulty,
|
||||
msgBlock.Header.MerkleRoot)
|
||||
} else {
|
||||
// At this point, there is a saved block template and another
|
||||
// request for a template was made, but either the available
|
||||
// transactions haven't change or it hasn't been long enough to
|
||||
// trigger a new block template to be generated. So, update the
|
||||
// existing block template.
|
||||
|
||||
// When the caller requires a full coinbase as opposed to only
|
||||
// the pertinent details needed to create their own coinbase,
|
||||
// add a payment address to the output of the coinbase of the
|
||||
// template if it doesn't already have one. Since this requires
|
||||
// mining addresses to be specified via the config, an error is
|
||||
// returned if none have been specified.
|
||||
if !useCoinbaseValue && !template.validPayAddress {
|
||||
// Choose a payment address at random.
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
payToAddr := cfg.miningAddrs[rand.Intn(len(cfg.miningAddrs))]
|
||||
|
||||
// Update the block coinbase output of the template to
|
||||
// pay to the randomly selected payment address.
|
||||
pkScript, err := btcscript.PayToAddrScript(payToAddr)
|
||||
if err != nil {
|
||||
return btcjson.Error{
|
||||
Code: btcjson.ErrInternal.Code,
|
||||
Message: err.Error(),
|
||||
}
|
||||
}
|
||||
template.block.Transactions[0].TxOut[0].PkScript = pkScript
|
||||
template.validPayAddress = true
|
||||
|
||||
// Update the merkle root.
|
||||
block := btcutil.NewBlock(template.block)
|
||||
merkles := btcchain.BuildMerkleTreeStore(block.Transactions())
|
||||
template.block.Header.MerkleRoot = *merkles[len(merkles)-1]
|
||||
}
|
||||
|
||||
// Set locals for convenience.
|
||||
msgBlock = template.block
|
||||
targetDifficulty = fmt.Sprintf("%064x",
|
||||
btcchain.CompactToBig(msgBlock.Header.Bits))
|
||||
|
||||
// Update the time of the block template to the current time
|
||||
// while accounting for the median time of the past several
|
||||
// blocks per the chain consensus rules.
|
||||
UpdateBlockTime(msgBlock, s.server.blockManager)
|
||||
msgBlock.Header.Nonce = 0
|
||||
|
||||
rpcsLog.Debugf("Updated block template (timestamp %v, "+
|
||||
"target %s)", msgBlock.Header.Timestamp,
|
||||
targetDifficulty)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// blockTemplateResult returns the current block template associated with the
|
||||
// state as a btcjson.GetBlockTemplateResult that is ready to be encoded to JSON
|
||||
// and returned to the caller.
|
||||
//
|
||||
// This function MUST be called with the state locked.
|
||||
func (state *gbtWorkState) blockTemplateResult(useCoinbaseValue bool) (*btcjson.GetBlockTemplateResult, error) {
|
||||
// Convert each transaction in the block template to a template result
|
||||
// transaction. The result does not include the coinbase, so notice
|
||||
// the adjustments to the various lengths and indices.
|
||||
template := state.template
|
||||
msgBlock := template.block
|
||||
numTx := len(msgBlock.Transactions)
|
||||
transactions := make([]btcjson.GetBlockTemplateResultTx, 0, numTx-1)
|
||||
txIndex := make(map[btcwire.ShaHash]int64, numTx)
|
||||
for i, tx := range msgBlock.Transactions {
|
||||
txHash, _ := tx.TxSha()
|
||||
txIndex[txHash] = int64(i)
|
||||
|
||||
// Skip the coinbase transaction.
|
||||
if i == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Create an array of 1-based indices to transactions that come
|
||||
// before this one in the transactions list which this one
|
||||
// depends on. This is necessary since the created block must
|
||||
// ensure proper ordering of the dependencies. A map is used
|
||||
// before creating the final array to prevent duplicate entries
|
||||
// when mutiple inputs reference the same transaction.
|
||||
dependsMap := make(map[int64]struct{})
|
||||
for _, txIn := range tx.TxIn {
|
||||
if idx, ok := txIndex[txIn.PreviousOutpoint.Hash]; ok {
|
||||
dependsMap[idx] = struct{}{}
|
||||
}
|
||||
}
|
||||
depends := make([]int64, 0, len(dependsMap))
|
||||
for idx := range dependsMap {
|
||||
depends = append(depends, idx)
|
||||
}
|
||||
|
||||
// Serialize the transaction for later conversion to hex.
|
||||
txBuf := bytes.NewBuffer(make([]byte, 0, tx.SerializeSize()))
|
||||
if err := tx.Serialize(txBuf); err != nil {
|
||||
return nil, btcjson.Error{
|
||||
Code: btcjson.ErrInternal.Code,
|
||||
Message: err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
resultTx := btcjson.GetBlockTemplateResultTx{
|
||||
Data: hex.EncodeToString(txBuf.Bytes()),
|
||||
Hash: txHash.String(),
|
||||
Depends: depends,
|
||||
Fee: template.fees[i],
|
||||
SigOps: template.sigOpCounts[i],
|
||||
}
|
||||
transactions = append(transactions, resultTx)
|
||||
}
|
||||
|
||||
// Generate the block template reply.
|
||||
header := &msgBlock.Header
|
||||
reply := btcjson.GetBlockTemplateResult{
|
||||
Bits: strconv.FormatInt(int64(header.Bits), 16),
|
||||
CurTime: time.Now().Unix(),
|
||||
Height: template.height,
|
||||
PreviousHash: header.PrevBlock.String(),
|
||||
SigOpLimit: btcchain.MaxSigOpsPerBlock,
|
||||
SizeLimit: btcwire.MaxBlockPayload,
|
||||
Transactions: transactions,
|
||||
Version: header.Version,
|
||||
}
|
||||
if useCoinbaseValue {
|
||||
reply.CoinbaseAux = gbtCoinbaseAux
|
||||
reply.CoinbaseValue = &msgBlock.Transactions[0].TxOut[0].Value
|
||||
} else {
|
||||
// Ensure the template has a valid payment address associated
|
||||
// with it when a full coinbase is requested.
|
||||
if !template.validPayAddress {
|
||||
return nil, btcjson.Error{
|
||||
Code: btcjson.ErrInternal.Code,
|
||||
Message: "A coinbase transaction has been " +
|
||||
"requested, but the server has not " +
|
||||
"been configured with a payment " +
|
||||
"addresses via --miningaddr",
|
||||
}
|
||||
}
|
||||
|
||||
// Serialize the transaction for conversion to hex.
|
||||
tx := msgBlock.Transactions[0]
|
||||
txHash, _ := tx.TxSha()
|
||||
txBuf := bytes.NewBuffer(make([]byte, 0, tx.SerializeSize()))
|
||||
if err := tx.Serialize(txBuf); err != nil {
|
||||
return nil, btcjson.Error{
|
||||
Code: btcjson.ErrInternal.Code,
|
||||
Message: err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
resultTx := btcjson.GetBlockTemplateResultTx{
|
||||
Data: hex.EncodeToString(txBuf.Bytes()),
|
||||
Hash: txHash.String(),
|
||||
Depends: []int64{},
|
||||
Fee: template.fees[0],
|
||||
SigOps: template.sigOpCounts[0],
|
||||
}
|
||||
|
||||
reply.CoinbaseTxn = &resultTx
|
||||
}
|
||||
|
||||
return &reply, nil
|
||||
}
|
||||
|
||||
// handleGetBlockTemplateRequest is a helper for handleGetBlockTemplate which
|
||||
// deals with generating and returning block templates to the caller. It
|
||||
// detects the capabilities reported by the caller in regards to whether or not
|
||||
// it supports creating its own coinbase (the coinbasetxn and coinbasevalue
|
||||
// capabilities) and modifies the returned block template accordingly.
|
||||
func handleGetBlockTemplateRequest(s *rpcServer, request *btcjson.TemplateRequest) (interface{}, error) {
|
||||
// Extract the relevant passed capabilities and restrict the result to
|
||||
// either a coinbase value or a coinbase transaction object depending on
|
||||
// the request. Default to only providing a coinbase value.
|
||||
useCoinbaseValue := true
|
||||
if request != nil {
|
||||
var hasCoinbaseValue, hasCoinbaseTxn bool
|
||||
for _, capability := range request.Capabilities {
|
||||
switch capability {
|
||||
case "coinbasetxn":
|
||||
hasCoinbaseTxn = true
|
||||
case "coinbasevalue":
|
||||
hasCoinbaseValue = true
|
||||
}
|
||||
}
|
||||
|
||||
if hasCoinbaseTxn && !hasCoinbaseValue {
|
||||
useCoinbaseValue = false
|
||||
}
|
||||
}
|
||||
|
||||
// When a coinbase transaction has been requested, respond with an error
|
||||
// if there are no addresses to pay the created block template to.
|
||||
if !useCoinbaseValue && len(cfg.miningAddrs) == 0 {
|
||||
return nil, btcjson.Error{
|
||||
Code: btcjson.ErrInternal.Code,
|
||||
Message: "A coinbase transaction has been requested, " +
|
||||
"but the server has not been configured with " +
|
||||
"a payment addresses via --miningaddr",
|
||||
}
|
||||
}
|
||||
|
||||
// Return an error if there are no peers connected since there is no
|
||||
// way to relay a found block or receive transactions to work on.
|
||||
// However, allow this state when running in the regression test or
|
||||
// simulation test mode.
|
||||
if !(cfg.RegressionTest || cfg.SimNet) && s.server.ConnectedCount() == 0 {
|
||||
return nil, btcjson.ErrClientNotConnected
|
||||
}
|
||||
|
||||
// No point in generating or accepting work before the chain is synced.
|
||||
_, currentHeight := s.server.blockManager.chainState.Best()
|
||||
if currentHeight != 0 && !s.server.blockManager.IsCurrent() {
|
||||
return nil, btcjson.ErrClientInInitialDownload
|
||||
}
|
||||
|
||||
// Protect concurrent access when updating block templates.
|
||||
state := s.gbtWorkState
|
||||
state.Lock()
|
||||
defer state.Unlock()
|
||||
|
||||
// Get and return a block template. A new block template will be
|
||||
// generated when the current best block has changed or the transactions
|
||||
// in the memory pool have been updated and it has been at least five
|
||||
// seconds since the last template was generated. Otherwise, the
|
||||
// timestamp for the existing block template is updated (and possibly
|
||||
// the difficulty on testnet per the consesus rules).
|
||||
if err := state.updateBlockTemplate(s, useCoinbaseValue); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return state.blockTemplateResult(useCoinbaseValue)
|
||||
}
|
||||
|
||||
// handleGetBlockTemplate implements the getblocktemplate command.
|
||||
//
|
||||
// See https://en.bitcoin.it/wiki/BIP_0022 for more details.
|
||||
func handleGetBlockTemplate(s *rpcServer, cmd btcjson.Cmd, closeChan <-chan struct{}) (interface{}, error) {
|
||||
c := cmd.(*btcjson.GetBlockTemplateCmd)
|
||||
request := c.Request
|
||||
|
||||
// Set the default mode and override it if supplied.
|
||||
mode := "template"
|
||||
if request != nil && request.Mode != "" {
|
||||
mode = request.Mode
|
||||
}
|
||||
|
||||
// The only supported mode is currently "template". Use a switch to
|
||||
// make other modes easier to implement.
|
||||
switch mode {
|
||||
case "template":
|
||||
return handleGetBlockTemplateRequest(s, request)
|
||||
}
|
||||
|
||||
return nil, btcjson.Error{
|
||||
Code: btcjson.ErrInvalidParameter.Code,
|
||||
Message: "Invalid mode",
|
||||
}
|
||||
}
|
||||
|
||||
// handleGetConnectionCount implements the getconnectioncount command.
|
||||
func handleGetConnectionCount(s *rpcServer, cmd btcjson.Cmd, closeChan <-chan struct{}) (interface{}, error) {
|
||||
return s.server.ConnectedCount(), nil
|
||||
|
|
Loading…
Reference in a new issue