Created
February 18, 2015 21:05
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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