Skip to content

Instantly share code, notes, and snippets.

@davecgh
Created February 18, 2015 21:05
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save davecgh/d2bcb089d3c1c527d332 to your computer and use it in GitHub Desktop.
Save davecgh/d2bcb089d3c1c527d332 to your computer and use it in GitHub Desktop.
Example of connecting to a simnet btcd instance, manually generating blocks, and submitting them.
package main
import (
"encoding/binary"
"errors"
"io/ioutil"
"log"
"math"
"math/big"
"path/filepath"
"runtime"
"time"
"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcrpcclient"
"github.com/btcsuite/btcutil"
)
func decodeAddr(addr string) btcutil.Address {
utilAddr, err := btcutil.DecodeAddress(addr, &chaincfg.SimNetParams)
if err != nil {
panic(err)
}
return utilAddr
}
var (
miningAddr = decodeAddr("SW8yPHHVAeedS5JMsACPuARdb3AxidHUkv")
activeNetParams = &chaincfg.SimNetParams
)
// solveBlock attempts to find a nonce which makes the passed block header
// hash to a value less than the target difficulty. When a successful solution
// is found true is returned and the nonce field of the passed header is
// updated with the solution. False is returned if no solution exists.
func solveBlock(header *wire.BlockHeader, targetDifficulty *big.Int) bool {
// sbResult is used by the solver goroutines to send results.
type sbResult struct {
found bool
nonce uint32
}
// solver accepts a block header and a nonce range to test. It is
// intended to be run as a goroutine.
quit := make(chan bool)
results := make(chan sbResult)
solver := func(header *wire.BlockHeader, startNonce, stopNonce uint32) {
// We need to modify the nonce field of the header, so make sure
// we work with a copy of the original header.
hdrCopy := *header
for i := startNonce; i >= startNonce && i <= stopNonce; i++ {
select {
case <-quit:
return
default:
hdrCopy.Nonce = i
hash, _ := hdrCopy.BlockSha()
if blockchain.ShaHashToBig(&hash).Cmp(targetDifficulty) <= 0 {
results <- sbResult{true, i}
return
}
}
}
results <- sbResult{false, 0}
}
startNonce := uint32(0)
stopNonce := uint32(math.MaxUint32)
numCores := uint32(runtime.NumCPU())
noncesPerCore := (stopNonce - startNonce) / numCores
for i := uint32(0); i < numCores; i++ {
rangeStart := startNonce + (noncesPerCore * i)
rangeStop := startNonce + (noncesPerCore * (i + 1)) - 1
if i == numCores-1 {
rangeStop = stopNonce
}
go solver(header, rangeStart, rangeStop)
}
for i := uint32(0); i < numCores; i++ {
result := <-results
if result.found {
close(quit)
header.Nonce = result.nonce
return true
}
}
return false
}
// createCoinbaseTxToPubKey returns a coinbase transaction paying the passed
// amount to the passed public key. It also accepts an extra nonce value
// for the signature script. This extra nonce helps ensure the transaction is
// not a duplicate transaction (paying the same value to the same public key
// address would otherwise be an identical transaction for block version 1).
func createCoinbaseTxToPubKey(numSatoshi int64, uncompressedPubKey []byte, extraNonce uint32) *wire.MsgTx {
// Coinbase transactions have no inputs, so previous outpoint is
// zero hash and max index.
outPoint := wire.NewOutPoint(&wire.ShaHash{}, math.MaxUint32)
sigScript := make([]byte, 4)
binary.LittleEndian.PutUint32(sigScript, extraNonce)
coinbaseTxIn := wire.NewTxIn(outPoint, sigScript)
pkScript, err := txscript.PayToAddrScript(miningAddr)
if err != nil {
panic(err)
}
coinbaseTxOut := &wire.TxOut{
Value: numSatoshi,
PkScript: pkScript,
}
// Create and return the transaction.
coinbaseTx := wire.NewMsgTx()
coinbaseTx.AddTxIn(coinbaseTxIn)
coinbaseTx.AddTxOut(coinbaseTxOut)
return coinbaseTx
}
// standardCoinbaseScript returns a standard script suitable for use as the
// signature script of the coinbase transaction of a new block. In particular,
// it starts with the block height that is required by version 2 blocks and adds
// the extra nonce as well as additional coinbase flags.
func standardCoinbaseScript(nextBlockHeight int64, extraNonce uint64) ([]byte, error) {
return txscript.NewScriptBuilder().AddInt64(nextBlockHeight).
AddUint64(extraNonce).Script()
}
// createCoinbaseTx returns a coinbase transaction paying an appropriate subsidy
// based on the passed block height to the provided address.
func createCoinbaseTx(coinbaseScript []byte, nextBlockHeight int64, addr btcutil.Address) (*btcutil.Tx, error) {
// Create the script to pay to the provided payment address.
pkScript, err := txscript.PayToAddrScript(addr)
if err != nil {
return nil, err
}
tx := wire.NewMsgTx()
tx.AddTxIn(&wire.TxIn{
// Coinbase transactions have no inputs, so previous outpoint is
// zero hash and max index.
PreviousOutPoint: *wire.NewOutPoint(&wire.ShaHash{},
wire.MaxPrevOutIndex),
SignatureScript: coinbaseScript,
Sequence: wire.MaxTxInSequenceNum,
})
tx.AddTxOut(&wire.TxOut{
Value: blockchain.CalcBlockSubsidy(nextBlockHeight,
activeNetParams),
PkScript: pkScript,
})
return btcutil.NewTx(tx), nil
}
// createBlock create a new block building from the previous block.
func createBlock(prevBlock *btcutil.Block) (*btcutil.Block, error) {
prevHash, err := prevBlock.Sha()
if err != nil {
return nil, err
}
blockHeight := prevBlock.Height() + 1
// Add one second to the previous block unless it's the genesis block in
// which case use the current time.
var ts time.Time
if blockHeight == 0 {
ts = time.Unix(time.Now().Unix(), 0)
} else {
ts = prevBlock.MsgBlock().Header.Timestamp.Add(time.Second)
}
extraNonce := uint64(0)
coinbaseScript, err := standardCoinbaseScript(blockHeight, extraNonce)
if err != nil {
return nil, err
}
coinbaseTx, err := createCoinbaseTx(coinbaseScript, blockHeight, miningAddr)
if err != nil {
return nil, err
}
// Create a new block ready to be solved.
blockTxns := []*btcutil.Tx{coinbaseTx}
merkles := blockchain.BuildMerkleTreeStore(blockTxns)
var block wire.MsgBlock
block.Header = wire.BlockHeader{
Version: 2,
PrevBlock: *prevHash,
MerkleRoot: *merkles[len(merkles)-1],
Timestamp: ts,
Bits: activeNetParams.PowLimitBits,
}
for _, tx := range blockTxns {
if err := block.AddTransaction(tx.MsgTx()); err != nil {
return nil, err
}
}
found := solveBlock(&block.Header, activeNetParams.PowLimit)
if !found {
return nil, errors.New("Unable to solve block")
}
utilBlock := btcutil.NewBlock(&block)
utilBlock.SetHeight(blockHeight)
return utilBlock, nil
}
func main() {
// Only override the handlers for notifications you care about.
// Also note most of these handlers will only be called if you register
// for notifications. See the documentation of the btcrpcclient
// NotificationHandlers type for more details about each handler.
ntfnHandlers := btcrpcclient.NotificationHandlers{
OnBlockConnected: func(hash *wire.ShaHash, height int32) {
log.Printf("Block connected: %v (height: %d)", hash, height)
},
OnBlockDisconnected: func(hash *wire.ShaHash, height int32) {
log.Printf("Block disconnected: %v (height: %d)", hash, height)
},
}
// Connect to local btcd RPC server using websockets.
btcdHomeDir := btcutil.AppDataDir("btcd", false)
certs, err := ioutil.ReadFile(filepath.Join(btcdHomeDir, "rpc.cert"))
if err != nil {
log.Fatal(err)
}
connCfg := &btcrpcclient.ConnConfig{
Host: "localhost:18556",
Endpoint: "ws",
User: "yourrpcuser",
Pass: "yourrpcpass",
Certificates: certs,
}
client, err := btcrpcclient.New(connCfg, &ntfnHandlers)
if err != nil {
log.Fatal(err)
}
defer client.Shutdown()
// Ensure the chain doesn't already contain blocks.
count, err := client.GetBlockCount()
if err != nil {
log.Fatal(err)
}
if count != 0 {
log.Fatalf("The target chain needs to be reset (count %d).", count)
}
// Generate first block.
blockA, err := createBlock(btcutil.NewBlock(activeNetParams.GenesisBlock))
if err != nil {
log.Fatal(err)
}
blockAHash, _ := blockA.Sha()
// Generate second block that builds from the first one.
blockB, err := createBlock(blockA)
if err != nil {
log.Fatal(err)
}
blockBHash, _ := blockB.Sha()
// Register for block connect and disconnect notifications.
if err := client.NotifyBlocks(); err != nil {
log.Fatal(err)
}
log.Println("NotifyBlocks: Registration Complete")
// Submit the blocks.
if err := client.SubmitBlock(blockA, nil); err != nil {
log.Fatal(err)
}
log.Printf("SubmitBlock: Submitted block with hash %v\n", blockAHash)
if err := client.SubmitBlock(blockB, nil); err != nil {
log.Fatal(err)
}
log.Printf("SubmitBlock: Submitted block with hash %v\n", blockBHash)
// Ensure the chain doesn't already contain blocks.
count, err = client.GetBlockCount()
if err != nil {
log.Fatal(err)
}
log.Printf("Final block height: %v\n", count)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment