From 4e9c427c50b02039cc0024a35b6091c262727958 Mon Sep 17 00:00:00 2001 From: Nate Maninger Date: Sun, 1 Sep 2024 17:54:30 -0700 Subject: [PATCH] cmd: add multi-threading --- cmd/cpuminerd/main.go | 78 +++++++++++++++++++++++-------------------- cmd/cpuminerd/mine.go | 77 ++++++++++++++++++++++++++++++++++++++++++ go.mod | 19 +++++------ go.sum | 28 ++++++++-------- 4 files changed, 142 insertions(+), 60 deletions(-) create mode 100644 cmd/cpuminerd/mine.go diff --git a/cmd/cpuminerd/main.go b/cmd/cpuminerd/main.go index c32c225..d400bfb 100644 --- a/cmd/cpuminerd/main.go +++ b/cmd/cpuminerd/main.go @@ -1,23 +1,24 @@ package main import ( + "context" + "errors" "flag" "fmt" - "math/big" "os" + "os/signal" + "runtime" + "sync" "time" "go.sia.tech/core/types" - "go.sia.tech/coreutils" "go.sia.tech/walletd/api" "go.uber.org/zap" "go.uber.org/zap/zapcore" - "lukechampine.com/frand" ) -func runCPUMiner(c *api.Client, minerAddr types.Address, log *zap.Logger) { - log.Info("starting cpu miner", zap.String("minerAddr", minerAddr.String())) - start := time.Now() +func runCPUMiner(ctx context.Context, c *api.Client, minerAddr types.Address, log *zap.Logger) { + log.Info("starting miner", zap.String("minerAddr", minerAddr.String())) check := func(msg string, err error) bool { if err != nil { @@ -28,16 +29,20 @@ func runCPUMiner(c *api.Client, minerAddr types.Address, log *zap.Logger) { return true } + start := time.Now() for { - elapsed := time.Since(start) + select { + case <-ctx.Done(): + return + default: + } + cs, err := c.ConsensusTipState() if !check("failed to get consensus tip state", err) { continue } - d, _ := new(big.Int).SetString(cs.Difficulty.String(), 10) - d.Mul(d, big.NewInt(int64(1+elapsed))) - log := log.With(zap.Uint64("height", cs.Index.Height+1), zap.Stringer("parentID", cs.Index.ID), zap.Stringer("difficulty", d)) + log := log.With(zap.Uint64("height", cs.Index.Height+1), zap.Stringer("parentID", cs.Index.ID)) log.Debug("mining block") txns, v2txns, err := c.TxpoolTransactions() @@ -45,33 +50,17 @@ func runCPUMiner(c *api.Client, minerAddr types.Address, log *zap.Logger) { continue } - b := types.Block{ - ParentID: cs.Index.ID, - Nonce: cs.NonceFactor() * frand.Uint64n(100), - Timestamp: types.CurrentTimestamp(), - MinerPayouts: []types.SiacoinOutput{{Address: minerAddr, Value: cs.BlockReward()}}, - Transactions: txns, - } - for _, txn := range txns { - b.MinerPayouts[0].Value = b.MinerPayouts[0].Value.Add(txn.TotalFees()) - } - for _, txn := range v2txns { - b.MinerPayouts[0].Value = b.MinerPayouts[0].Value.Add(txn.MinerFee) - } - if len(v2txns) > 0 || cs.Index.Height+1 >= cs.Network.HardforkV2.RequireHeight { - b.V2 = &types.V2BlockData{ - Height: cs.Index.Height + 1, - Transactions: v2txns, - } - b.V2.Commitment = cs.Commitment(cs.TransactionsCommitment(b.Transactions, b.V2Transactions()), b.MinerPayouts[0].Address) + b, err := mineBlock(ctx, cs, txns, v2txns, minerAddr) + if errors.Is(err, context.Canceled) { + return // shutting down + } else if errors.Is(err, context.DeadlineExceeded) { + continue // timeout, try again + } else if err != nil { + log.Error("failed to mine block", zap.Error(err)) // should never happen + return } - if !coreutils.FindBlockNonce(cs, &b, time.Minute) { - log.Debug("failed to find nonce") - continue - } log.Debug("found nonce", zap.Uint64("nonce", b.Nonce)) - index := types.ChainIndex{Height: cs.Index.Height + 1, ID: b.ID()} tip, err := c.ConsensusTip() if !check("failed to get consensus tip:", err) { continue @@ -82,7 +71,8 @@ func runCPUMiner(c *api.Client, minerAddr types.Address, log *zap.Logger) { } else if err := c.SyncerBroadcastBlock(b); err != nil { log.Error("mined invalid block", zap.Error(err)) } - log.Info("mined block", zap.Stringer("blockID", index.ID), zap.Stringer("fees", b.MinerPayouts[0].Value), zap.Int("transactions", len(b.Transactions)), zap.Int("v2transactions", len(b.V2Transactions()))) + log.Info("mined block", zap.Duration("elapsed", time.Since(start)), zap.Stringer("blockID", b.ID()), zap.Stringer("reward", b.MinerPayouts[0].Value), zap.Int("transactions", len(b.Transactions)), zap.Int("v2transactions", len(b.V2Transactions()))) + start = time.Now() // reset the timer } } @@ -105,6 +95,8 @@ func parseLogLevel(level string) zap.AtomicLevel { func main() { var ( + threads int + minerAddrStr string apiAddress string @@ -117,8 +109,12 @@ func main() { flag.StringVar(&apiAddress, "api", "localhost:9980", "address of the walletd API") flag.StringVar(&apiPassword, "password", "", "password for the walletd API") flag.StringVar(&logLevel, "log.level", "info", "log level") + flag.IntVar(&threads, "threads", runtime.NumCPU(), "number of threads to use for mining") flag.Parse() + ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) + defer cancel() + var address types.Address if err := address.UnmarshalText([]byte(minerAddrStr)); err != nil { panic(err) @@ -143,5 +139,15 @@ func main() { zap.RedirectStdLog(log) - runCPUMiner(c, address, log) + var wg sync.WaitGroup + for i := 0; i < threads; i++ { + wg.Add(1) + go func() { + defer wg.Done() + runCPUMiner(ctx, c, address, log) + }() + } + + <-ctx.Done() + wg.Wait() } diff --git a/cmd/cpuminerd/mine.go b/cmd/cpuminerd/mine.go new file mode 100644 index 0000000..9a1e973 --- /dev/null +++ b/cmd/cpuminerd/mine.go @@ -0,0 +1,77 @@ +package main + +import ( + "context" + "encoding/binary" + "time" + + "go.sia.tech/core/consensus" + "go.sia.tech/core/types" +) + +// mineBlock constructs a block from the provided address and the transactions +// in the txpool, and attempts to find a nonce for it that meets the PoW target. +func mineBlock(ctx context.Context, cs consensus.State, txns []types.Transaction, v2Txns []types.V2Transaction, addr types.Address) (types.Block, error) { + b := types.Block{ + ParentID: cs.Index.ID, + Timestamp: types.CurrentTimestamp(), + MinerPayouts: []types.SiacoinOutput{{ + Value: cs.BlockReward(), + Address: addr, + }}, + } + + if cs.Index.Height >= cs.Network.HardforkV2.AllowHeight { + b.V2 = &types.V2BlockData{ + Height: cs.Index.Height + 1, + } + } + + var weight uint64 + for _, txn := range txns { + if weight += cs.TransactionWeight(txn); weight > cs.MaxBlockWeight() { + break + } + b.Transactions = append(b.Transactions, txn) + b.MinerPayouts[0].Value = b.MinerPayouts[0].Value.Add(txn.TotalFees()) + } + for _, txn := range v2Txns { + if weight += cs.V2TransactionWeight(txn); weight > cs.MaxBlockWeight() { + break + } + b.V2.Transactions = append(b.V2.Transactions, txn) + b.MinerPayouts[0].Value = b.MinerPayouts[0].Value.Add(txn.MinerFee) + } + if b.V2 != nil { + b.V2.Commitment = cs.Commitment(cs.TransactionsCommitment(b.Transactions, b.V2Transactions()), addr) + } + + b.Nonce = 0 + buf := make([]byte, 32+8+8+32) + binary.LittleEndian.PutUint64(buf[32:], b.Nonce) + binary.LittleEndian.PutUint64(buf[40:], uint64(b.Timestamp.Unix())) + if b.V2 != nil { + copy(buf[:32], "sia/id/block|") + copy(buf[48:], b.V2.Commitment[:]) + } else { + root := b.MerkleRoot() + copy(buf[:32], b.ParentID[:]) + copy(buf[48:], root[:]) + } + factor := cs.NonceFactor() + + ctx, cancel := context.WithTimeout(ctx, 5*time.Minute) + defer cancel() + + for types.BlockID(types.HashBytes(buf)).CmpWork(cs.ChildTarget) < 0 { + select { + case <-ctx.Done(): + return types.Block{}, ctx.Err() + default: + } + + b.Nonce += factor + binary.LittleEndian.PutUint64(buf[32:], b.Nonce) + } + return b, nil +} diff --git a/go.mod b/go.mod index 9c2e77b..b119bf0 100644 --- a/go.mod +++ b/go.mod @@ -1,26 +1,25 @@ module go.sia.tech/cpuminerd -go 1.21.8 +go 1.22.5 -toolchain go1.21.11 +toolchain go1.23.0 require ( - go.sia.tech/core v0.2.7 - go.sia.tech/coreutils v0.0.5 - go.sia.tech/walletd v0.1.1-alpha.0.20240610172105-eb95d4161b91 + go.sia.tech/core v0.4.5 + go.sia.tech/walletd v0.8.1-0.20240901222528-4a6f567605f6 go.uber.org/zap v1.27.0 - lukechampine.com/frand v1.4.2 ) require ( github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect github.com/julienschmidt/httprouter v1.3.0 // indirect github.com/stretchr/testify v1.9.0 // indirect - go.etcd.io/bbolt v1.3.10 // indirect - go.sia.tech/jape v0.11.2-0.20240306154058-9832414a5385 // indirect + go.sia.tech/coreutils v0.3.1 // indirect + go.sia.tech/jape v0.12.1 // indirect go.sia.tech/mux v1.2.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.24.0 // indirect - golang.org/x/sys v0.21.0 // indirect + golang.org/x/crypto v0.26.0 // indirect + golang.org/x/sys v0.24.0 // indirect golang.org/x/tools v0.22.0 // indirect + lukechampine.com/frand v1.4.2 // indirect ) diff --git a/go.sum b/go.sum index d24e5c3..3e27900 100644 --- a/go.sum +++ b/go.sum @@ -10,33 +10,33 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -go.etcd.io/bbolt v1.3.10 h1:+BqfJTcCzTItrop8mq/lbzL8wSGtj94UO/3U31shqG0= -go.etcd.io/bbolt v1.3.10/go.mod h1:bK3UQLPJZly7IlNmV7uVHJDxfe5aK9Ll93e/74Y9oEQ= -go.sia.tech/core v0.2.7 h1:9Q/3BHL6ziAMPeiko863hhTD/Zs2s7OqEUiPKouDny8= -go.sia.tech/core v0.2.7/go.mod h1:BMgT/reXtgv6XbDgUYTCPY7wSMbspDRDs7KMi1vL6Iw= -go.sia.tech/coreutils v0.0.5 h1:Jj03VrqAayYHgA9fwV13+X88WB+Wr1p8wuLw2B8d2FI= -go.sia.tech/coreutils v0.0.5/go.mod h1:SkSpHeq3tBh2ff4HXuBk2WtlhkYQQtdcvU4Yv1Rd2bU= -go.sia.tech/jape v0.11.2-0.20240306154058-9832414a5385 h1:Gho1g6pkv56o6Ut9cez/Yu5o4xlA8WNkDbPn6RWXL7g= -go.sia.tech/jape v0.11.2-0.20240306154058-9832414a5385/go.mod h1:wU+h6Wh5olDjkPXjF0tbZ1GDgoZ6VTi4naFw91yyWC4= +go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= +go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= +go.sia.tech/core v0.4.5 h1:w2D3Mx29UmK1aFd9R7uHFo5JUSTqu3+92NHoRFv3CaU= +go.sia.tech/core v0.4.5/go.mod h1:Zuq0Tn2aIXJyO0bjGu8cMeVWe+vwQnUfZhG1LCmjD5c= +go.sia.tech/coreutils v0.3.1 h1:FLIBM4ryLFvwkZVv8Yyn3KZsUdqX6pX8moS3xfQX5M0= +go.sia.tech/coreutils v0.3.1/go.mod h1:qOBvtTS14Q2lSbY+S3u39AntpttL5kuIv7P8NktKYw0= +go.sia.tech/jape v0.12.1 h1:xr+o9V8FO8ScRqbSaqYf9bjj1UJ2eipZuNcI1nYousU= +go.sia.tech/jape v0.12.1/go.mod h1:wU+h6Wh5olDjkPXjF0tbZ1GDgoZ6VTi4naFw91yyWC4= go.sia.tech/mux v1.2.0 h1:ofa1Us9mdymBbGMY2XH/lSpY8itFsKIo/Aq8zwe+GHU= go.sia.tech/mux v1.2.0/go.mod h1:Yyo6wZelOYTyvrHmJZ6aQfRoer3o4xyKQ4NmQLJrBSo= -go.sia.tech/walletd v0.1.1-alpha.0.20240610172105-eb95d4161b91 h1:9saK9WcYSK3DDwj6aWyM3MUYlyZaJFiYzdm7KHg4I+Y= -go.sia.tech/walletd v0.1.1-alpha.0.20240610172105-eb95d4161b91/go.mod h1:1CaApRlon10cIDnocnErbolh416CiUeP5jgEbVtmWx8= +go.sia.tech/walletd v0.8.1-0.20240901222528-4a6f567605f6 h1:bYu3hFLxUuh0JrLq2HEEgsMMLn1yxbZzaJ7GlSQJPog= +go.sia.tech/walletd v0.8.1-0.20240901222528-4a6f567605f6/go.mod h1:6blaOwGiC4FetxDClwwBawAtQzdi0bdvw+bw5GF/f94= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= -golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=