mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-10-14 00:59:33 +00:00
[NOD-70] Added mining simluator (#228)
* [NOD-70] Added GetBlockTemplate method to rpcclient * [NOD-70] Basic infrastructure for mining simulator * [NOD-70] Fix txFees in NewBlockTempalte: include value for fee transaction + don't set fee for coinbase = -totalFees. * [NOD-70] Added capabilities parameter to Client.GetBlockTemplate call * [NOD-70] Dirty version of mining simulator complete * [NOD-70] cleaned up mining simulator * [NOD-70] Added dockerfile to mining simulator * [NOD-70] Updated base config path of mining simulator to mining_simulator * [NOD-70] Remove error return from msgblock.AddTransaction - it never returns one * [NOD-70] Renamed r -> random * [NOD-70] Move paths initialization of mining simulator to main * [NOD-70] Cleaned up mining simulator dockerfile * [NOD-70] Add '--' to tini argument
This commit is contained in:
parent
dc103f3d86
commit
047a2c16c4
@ -194,9 +194,7 @@ func CreateBlock(parentBlock *util.Block, inclusionTxs []*util.Tx,
|
|||||||
Bits: net.PowLimitBits,
|
Bits: net.PowLimitBits,
|
||||||
}
|
}
|
||||||
for _, tx := range blockTxns {
|
for _, tx := range blockTxns {
|
||||||
if err := block.AddTransaction(tx.MsgTx()); err != nil {
|
block.AddTransaction(tx.MsgTx())
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
found := solveBlock(&block.Header, net.PowLimit)
|
found := solveBlock(&block.Header, net.PowLimit)
|
||||||
|
@ -447,9 +447,9 @@ func (g *BlkTmplGenerator) NewBlockTemplate(payToAddress util.Address) (*BlockTe
|
|||||||
// a transaction as it is selected for inclusion in the final block.
|
// a transaction as it is selected for inclusion in the final block.
|
||||||
// However, since the total fees aren't known yet, use a dummy value for
|
// However, since the total fees aren't known yet, use a dummy value for
|
||||||
// the coinbase fee which will be updated later.
|
// the coinbase fee which will be updated later.
|
||||||
txFees := make([]uint64, 0, len(sourceTxns))
|
txFees := make([]uint64, 0, len(sourceTxns)+2)
|
||||||
txSigOpCounts := make([]int64, 0, len(sourceTxns))
|
txSigOpCounts := make([]int64, 0, len(sourceTxns)+2)
|
||||||
txFees = append(txFees, 0) // Updated once known
|
txFees = append(txFees, 0, 0) // For coinbase and fee txs
|
||||||
txSigOpCounts = append(txSigOpCounts, numCoinbaseSigOps, feeTxSigOps)
|
txSigOpCounts = append(txSigOpCounts, numCoinbaseSigOps, feeTxSigOps)
|
||||||
|
|
||||||
log.Debugf("Considering %d transactions for inclusion to new block",
|
log.Debugf("Considering %d transactions for inclusion to new block",
|
||||||
@ -629,7 +629,6 @@ func (g *BlkTmplGenerator) NewBlockTemplate(payToAddress util.Address) (*BlockTe
|
|||||||
blockSize -= wire.MaxVarIntPayload -
|
blockSize -= wire.MaxVarIntPayload -
|
||||||
uint32(wire.VarIntSerializeSize(uint64(len(blockTxns))))
|
uint32(wire.VarIntSerializeSize(uint64(len(blockTxns))))
|
||||||
coinbaseTx.MsgTx().TxOut[0].Value += totalFees
|
coinbaseTx.MsgTx().TxOut[0].Value += totalFees
|
||||||
txFees[0] = -totalFees
|
|
||||||
|
|
||||||
// Calculate the required difficulty for the block. The timestamp
|
// Calculate the required difficulty for the block. The timestamp
|
||||||
// is potentially adjusted to ensure it comes after the median time of
|
// is potentially adjusted to ensure it comes after the median time of
|
||||||
@ -665,9 +664,7 @@ func (g *BlkTmplGenerator) NewBlockTemplate(payToAddress util.Address) (*BlockTe
|
|||||||
Bits: reqDifficulty,
|
Bits: reqDifficulty,
|
||||||
}
|
}
|
||||||
for _, tx := range blockTxns {
|
for _, tx := range blockTxns {
|
||||||
if err := msgBlock.AddTransaction(tx.MsgTx()); err != nil {
|
msgBlock.AddTransaction(tx.MsgTx())
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally, perform a full check on the created block against the chain
|
// Finally, perform a full check on the created block against the chain
|
||||||
|
24
mining/simulator/addresslist.go
Normal file
24
mining/simulator/addresslist.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
var addressListPath string
|
||||||
|
|
||||||
|
func getAddressList() ([]string, error) {
|
||||||
|
file, err := os.Open(addressListPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(file)
|
||||||
|
addressList := []string{}
|
||||||
|
for scanner.Scan() {
|
||||||
|
addressList = append(addressList, scanner.Text())
|
||||||
|
}
|
||||||
|
|
||||||
|
return addressList, nil
|
||||||
|
}
|
41
mining/simulator/connect.go
Normal file
41
mining/simulator/connect.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/daglabs/btcd/rpcclient"
|
||||||
|
)
|
||||||
|
|
||||||
|
var certificatePath string
|
||||||
|
|
||||||
|
func connectToServers(addressList []string) ([]*rpcclient.Client, error) {
|
||||||
|
clients := make([]*rpcclient.Client, len(addressList))
|
||||||
|
|
||||||
|
cert, err := ioutil.ReadFile(certificatePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Error reading certificates file: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, address := range addressList {
|
||||||
|
connCfg := &rpcclient.ConnConfig{
|
||||||
|
Host: address,
|
||||||
|
Endpoint: "ws",
|
||||||
|
User: "user",
|
||||||
|
Pass: "pass",
|
||||||
|
Certificates: cert,
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := rpcclient.New(connCfg, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Error connecting to address %s: %s", address, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
clients[i] = client
|
||||||
|
|
||||||
|
log.Printf("Connected to server %s", address)
|
||||||
|
}
|
||||||
|
|
||||||
|
return clients, nil
|
||||||
|
}
|
29
mining/simulator/docker/Dockerfile
Normal file
29
mining/simulator/docker/Dockerfile
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# -- multistage docker build: stage #1: build stage
|
||||||
|
FROM golang:1.12-alpine AS build
|
||||||
|
|
||||||
|
RUN mkdir -p /go/src/github.com/daglabs/btcd
|
||||||
|
|
||||||
|
WORKDIR /go/src/github.com/daglabs/btcd
|
||||||
|
|
||||||
|
RUN apk add --no-cache curl git
|
||||||
|
RUN curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
|
||||||
|
|
||||||
|
COPY ./Gopkg.* ./
|
||||||
|
|
||||||
|
RUN dep ensure -v -vendor-only
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN cd mining/simulator && CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o mining_simulator .
|
||||||
|
|
||||||
|
# --- multistage docker build: stage #2: runtime image
|
||||||
|
FROM alpine
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN apk add --no-cache tini
|
||||||
|
|
||||||
|
COPY --from=build /go/src/github.com/daglabs/btcd/mining/simulator/mining_simulator /app/
|
||||||
|
|
||||||
|
ENTRYPOINT ["/sbin/tini", "--"]
|
||||||
|
|
||||||
|
CMD ["/app/mining_simulator"]
|
10
mining/simulator/docker/README
Normal file
10
mining/simulator/docker/README
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
1. To build docker image invoke following command from btcd root directory:
|
||||||
|
docker build -t mining_simulator -f ./mining/simulator/docker/Dockerfile .
|
||||||
|
|
||||||
|
2. To run:
|
||||||
|
a. create folder ~/.btcd/mining_simulator with the following files:
|
||||||
|
rpc.cert - certificate file that all rpc nodes accept
|
||||||
|
addresses - list of node addresses in the format [hostname]:[port]. One node per line
|
||||||
|
b. run:
|
||||||
|
docker run -v ~/.btcd:/root/.btcd -t mining_simulator
|
||||||
|
|
69
mining/simulator/main.go
Normal file
69
mining/simulator/main.go
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os/user"
|
||||||
|
"path"
|
||||||
|
"runtime/debug"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/daglabs/btcd/rpcclient"
|
||||||
|
)
|
||||||
|
|
||||||
|
var isRunning int32
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
defer handlePanic()
|
||||||
|
|
||||||
|
err := initPaths()
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("Error initializing paths: %s", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
addressList, err := getAddressList()
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("Couldn't load address list: %s", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
clients, err := connectToServers(addressList)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("Error connecting to servers: %s", err))
|
||||||
|
}
|
||||||
|
defer disconnect(clients)
|
||||||
|
|
||||||
|
atomic.StoreInt32(&isRunning, 1)
|
||||||
|
|
||||||
|
err = mineLoop(clients)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("Error in main loop: %s", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func initPaths() error {
|
||||||
|
usr, err := user.Current()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error getting current user: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
basePath := ".btcd/mining_simulator"
|
||||||
|
|
||||||
|
certificatePath = path.Join(usr.HomeDir, basePath, "rpc.cert")
|
||||||
|
addressListPath = path.Join(usr.HomeDir, basePath, "addresses")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func disconnect(clients []*rpcclient.Client) {
|
||||||
|
for _, client := range clients {
|
||||||
|
client.Disconnect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handlePanic() {
|
||||||
|
err := recover()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Fatal error: %s", err)
|
||||||
|
log.Printf("Stack trace: %s", debug.Stack())
|
||||||
|
}
|
||||||
|
}
|
107
mining/simulator/mineloop.go
Normal file
107
mining/simulator/mineloop.go
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"math/rand"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/daglabs/btcd/blockdag"
|
||||||
|
"github.com/daglabs/btcd/btcjson"
|
||||||
|
"github.com/daglabs/btcd/dagconfig/daghash"
|
||||||
|
"github.com/daglabs/btcd/rpcclient"
|
||||||
|
"github.com/daglabs/btcd/util"
|
||||||
|
"github.com/daglabs/btcd/wire"
|
||||||
|
)
|
||||||
|
|
||||||
|
var random = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
|
|
||||||
|
func parseBlock(template *btcjson.GetBlockTemplateResult) (*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, fmt.Errorf("Error decoding hash %s: %s", parentHash, err)
|
||||||
|
}
|
||||||
|
parentHashes[i] = *hash
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse Bits
|
||||||
|
bitsInt64, err := strconv.ParseInt(template.Bits, 16, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Error decoding bits %s: %s", template.Bits, err)
|
||||||
|
}
|
||||||
|
bits := uint32(bitsInt64)
|
||||||
|
|
||||||
|
// parse rest of block
|
||||||
|
msgBlock := wire.NewMsgBlock(wire.NewBlockHeader(template.Version, parentHashes, &daghash.Hash{}, &daghash.Hash{}, uint32(bits), 0))
|
||||||
|
|
||||||
|
for i, txResult := range append([]btcjson.GetBlockTemplateResultTx{*template.CoinbaseTxn}, template.Transactions...) {
|
||||||
|
reader := hex.NewDecoder(strings.NewReader(txResult.Data))
|
||||||
|
tx := &wire.MsgTx{}
|
||||||
|
if err := tx.BtcDecode(reader, 0); err != nil {
|
||||||
|
return nil, fmt.Errorf("Error decoding tx #%d: %s", i, err)
|
||||||
|
}
|
||||||
|
msgBlock.AddTransaction(tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
return util.NewBlock(msgBlock), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func solveBlock(msgBlock *wire.MsgBlock) {
|
||||||
|
maxNonce := ^uint64(0) // 2^64 - 1
|
||||||
|
targetDifficulty := util.CompactToBig(msgBlock.Header.Bits)
|
||||||
|
for i := uint64(0); i < maxNonce; i++ {
|
||||||
|
msgBlock.Header.Nonce = i
|
||||||
|
hash := msgBlock.BlockHash()
|
||||||
|
if daghash.HashToBig(&hash).Cmp(targetDifficulty) <= 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func mineLoop(clients []*rpcclient.Client) error {
|
||||||
|
clientsCount := int64(len(clients))
|
||||||
|
|
||||||
|
for atomic.LoadInt32(&isRunning) == 1 {
|
||||||
|
var currentClient *rpcclient.Client
|
||||||
|
if clientsCount == 1 {
|
||||||
|
currentClient = clients[0]
|
||||||
|
} else {
|
||||||
|
currentClient = clients[random.Int63n(clientsCount)]
|
||||||
|
}
|
||||||
|
log.Printf("Next block will be mined by: %s", currentClient.Host())
|
||||||
|
|
||||||
|
template, err := currentClient.GetBlockTemplate([]string{"coinbasetxn"})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error getting block template: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
block, err := parseBlock(template)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error parsing block: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
msgBlock := block.MsgBlock()
|
||||||
|
|
||||||
|
msgBlock.Header.HashMerkleRoot = *blockdag.BuildHashMerkleTreeStore(block.Transactions()).Root()
|
||||||
|
msgBlock.Header.IDMerkleRoot = *blockdag.BuildIDMerkleTreeStore(block.Transactions()).Root()
|
||||||
|
|
||||||
|
solveBlock(msgBlock)
|
||||||
|
|
||||||
|
log.Printf("Found block %s! Submitting", block.Hash())
|
||||||
|
|
||||||
|
err = currentClient.SubmitBlock(block, &btcjson.SubmitBlockOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error submitting block: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -158,6 +158,11 @@ type Client struct {
|
|||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Host returns the host name of the server this client is connected to
|
||||||
|
func (c *Client) Host() string {
|
||||||
|
return c.config.Host
|
||||||
|
}
|
||||||
|
|
||||||
// NextID returns the next id to be used when sending a JSON-RPC message. This
|
// NextID returns the next id to be used when sending a JSON-RPC message. This
|
||||||
// ID allows responses to be associated with particular requests per the
|
// ID allows responses to be associated with particular requests per the
|
||||||
// JSON-RPC specification. Typically the consumer of the client does not need
|
// JSON-RPC specification. Typically the consumer of the client does not need
|
||||||
|
@ -307,7 +307,6 @@ func (r FutureSubmitBlockResult) Receive() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SubmitBlockAsync returns an instance of a type that can be used to get the
|
// SubmitBlockAsync returns an instance of a type that can be used to get the
|
||||||
@ -335,4 +334,40 @@ func (c *Client) SubmitBlock(block *util.Block, options *btcjson.SubmitBlockOpti
|
|||||||
return c.SubmitBlockAsync(block, options).Receive()
|
return c.SubmitBlockAsync(block, options).Receive()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(davec): Implement GetBlockTemplate
|
// FutureGetBlockTemplateResult is a future promise to deliver the result of a
|
||||||
|
// GetBlockTemplate RPC invocation (or an applicable error).
|
||||||
|
type FutureGetBlockTemplateResult chan *response
|
||||||
|
|
||||||
|
// GetBlockTemplateAsync returns an instance of a type that can be used to get
|
||||||
|
// the result of the RPC at some future time by invoking the Receive function on
|
||||||
|
// the returned instance.
|
||||||
|
//
|
||||||
|
// See GetBlockTemplate for the blocking version and more details
|
||||||
|
func (c *Client) GetBlockTemplateAsync(capabilities []string) FutureGetBlockTemplateResult {
|
||||||
|
request := &btcjson.TemplateRequest{
|
||||||
|
Mode: "template",
|
||||||
|
Capabilities: capabilities,
|
||||||
|
}
|
||||||
|
cmd := btcjson.NewGetBlockTemplateCmd(request)
|
||||||
|
return c.sendCmd(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Receive waits for the response promised by the future and returns an error if
|
||||||
|
// any occurred when submitting the block.
|
||||||
|
func (r FutureGetBlockTemplateResult) Receive() (*btcjson.GetBlockTemplateResult, error) {
|
||||||
|
res, err := receiveFuture(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var result btcjson.GetBlockTemplateResult
|
||||||
|
if err := json.Unmarshal(res, &result); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBlockTemplate request a block template from the server, to mine upon
|
||||||
|
func (c *Client) GetBlockTemplate(capabilities []string) (*btcjson.GetBlockTemplateResult, error) {
|
||||||
|
return c.GetBlockTemplateAsync(capabilities).Receive()
|
||||||
|
}
|
||||||
|
@ -7,9 +7,10 @@ package wire
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/daglabs/btcd/util/subnetworkid"
|
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
|
"github.com/daglabs/btcd/util/subnetworkid"
|
||||||
|
|
||||||
"github.com/daglabs/btcd/dagconfig/daghash"
|
"github.com/daglabs/btcd/dagconfig/daghash"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -46,10 +47,8 @@ type MsgBlock struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AddTransaction adds a transaction to the message.
|
// AddTransaction adds a transaction to the message.
|
||||||
func (msg *MsgBlock) AddTransaction(tx *MsgTx) error {
|
func (msg *MsgBlock) AddTransaction(tx *MsgTx) {
|
||||||
msg.Transactions = append(msg.Transactions, tx)
|
msg.Transactions = append(msg.Transactions, tx)
|
||||||
return nil
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClearTransactions removes all transactions from the message.
|
// ClearTransactions removes all transactions from the message.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user