Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix miner logic #69

Merged
merged 4 commits into from
Apr 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
68 changes: 17 additions & 51 deletions cmd/walletd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,14 @@ import (
"os"
"os/signal"
"runtime/debug"
"strings"

"go.sia.tech/core/types"
cwallet "go.sia.tech/coreutils/wallet"
"go.sia.tech/walletd/api"
"go.sia.tech/walletd/wallet"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"golang.org/x/term"
"lukechampine.com/flagg"
"lukechampine.com/frand"
)

var commit = "?"
Expand Down Expand Up @@ -71,14 +69,9 @@ Run 'walletd' with no arguments to start the blockchain node and API server.

Actions:
version print walletd version
seed generate a recovery phrase
mine run CPU miner`

Testnet Actions:
seed generate a seed
mine run CPU miner
balance view wallet balance
send send a simple transaction
txns view transaction history
`
versionUsage = `Usage:
walletd version

Expand All @@ -87,41 +80,20 @@ Prints the version of the walletd binary.
seedUsage = `Usage:
walletd seed

Generates a secure testnet seed.
Generates a secure BIP-39 recovery phrase.
`
mineUsage = `Usage:
walletd mine

Runs a testnet CPU miner.
`
balanceUsage = `Usage:
walletd balance

Displays testnet balance.
`
sendUsage = `Usage:
walletd send [flags] [amount] [address]

Sends a simple testnet transaction.
`
txnsUsage = `Usage:
walletd txns

Lists testnet transactions and miner rewards.
`
txpoolUsage = `Usage:
walletd txpool

Lists unconfirmed testnet transactions in the txpool.
Note that only transactions relevant to the wallet are shown.
Runs a CPU miner. Not intended for production use.
`
)

func main() {
log.SetFlags(0)

var gatewayAddr, apiAddr, dir, network, seed string
var upnp, v2 bool
var upnp, bootstrap bool

var minerAddrStr string
var minerBlocks int
Expand All @@ -133,28 +105,20 @@ func main() {
rootCmd.StringVar(&dir, "dir", ".", "directory to store node state in")
rootCmd.StringVar(&network, "network", "mainnet", "network to connect to")
rootCmd.BoolVar(&upnp, "upnp", true, "attempt to forward ports and discover IP with UPnP")
rootCmd.BoolVar(&bootstrap, "bootstrap", true, "attempt to bootstrap the network")
rootCmd.StringVar(&seed, "seed", "", "testnet seed")
versionCmd := flagg.New("version", versionUsage)
seedCmd := flagg.New("seed", seedUsage)
mineCmd := flagg.New("mine", mineUsage)
mineCmd.IntVar(&minerBlocks, "n", -1, "mine this many blocks. If negative, mine indefinitely")
mineCmd.StringVar(&minerAddrStr, "addr", "", "address to send block rewards to (required)")
balanceCmd := flagg.New("balance", balanceUsage)
sendCmd := flagg.New("send", sendUsage)
sendCmd.BoolVar(&v2, "v2", false, "send a v2 transaction")
txnsCmd := flagg.New("txns", txnsUsage)
txpoolCmd := flagg.New("txpool", txpoolUsage)

cmd := flagg.Parse(flagg.Tree{
Cmd: rootCmd,
Sub: []flagg.Tree{
{Cmd: versionCmd},
{Cmd: seedCmd},
{Cmd: mineCmd},
{Cmd: balanceCmd},
{Cmd: sendCmd},
{Cmd: txnsCmd},
{Cmd: txpoolCmd},
},
})

Expand Down Expand Up @@ -195,7 +159,7 @@ func main() {
// redirect stdlib log to zap
zap.RedirectStdLog(logger.Named("stdlib"))

n, err := newNode(gatewayAddr, dir, network, upnp, logger)
n, err := newNode(gatewayAddr, dir, network, upnp, bootstrap, logger)
if err != nil {
log.Fatal(err)
}
Expand All @@ -222,13 +186,15 @@ func main() {
cmd.Usage()
return
}
seed := frand.Bytes(8)
var entropy [32]byte
copy(entropy[:], seed)
addr := types.StandardUnlockHash(wallet.NewSeedFromEntropy(&entropy).PublicKey(0))
fmt.Printf("Seed: %x\n", seed)
fmt.Printf("Address: %v\n", strings.TrimPrefix(addr.String(), "addr:"))
recoveryPhrase := cwallet.NewSeedPhrase()
var seed [32]byte
if err := cwallet.SeedFromPhrase(&seed, recoveryPhrase); err != nil {
log.Fatal(err)
}
addr := types.StandardUnlockHash(cwallet.KeyFromSeed(&seed, 0).PublicKey())

fmt.Println("Recovery Phrase:", recoveryPhrase)
fmt.Println("Address", addr)
case mineCmd:
if len(cmd.Args()) != 0 {
cmd.Usage()
Expand All @@ -241,6 +207,6 @@ func main() {
}

c := api.NewClient("http://"+apiAddr+"/api", getAPIPassword())
runTestnetMiner(c, minerAddr, minerBlocks)
runCPUMiner(c, minerAddr, minerBlocks)
}
}
70 changes: 70 additions & 0 deletions cmd/walletd/miner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package main

import (
"fmt"
"log"
"math/big"
"time"

"go.sia.tech/core/types"
"go.sia.tech/coreutils"
"go.sia.tech/walletd/api"
"lukechampine.com/frand"
)

func runCPUMiner(c *api.Client, minerAddr types.Address, n int) {
log.Println("Started mining into", minerAddr)
start := time.Now()

var blocksFound int
for {
if n >= 0 && blocksFound >= n {
break
}
elapsed := time.Since(start)
cs, err := c.ConsensusTipState()
check("Couldn't get consensus tip state:", err)
d, _ := new(big.Int).SetString(cs.Difficulty.String(), 10)
d.Mul(d, big.NewInt(int64(1+elapsed)))
fmt.Printf("\rMining block %4v...(%.2f blocks/day), difficulty %v)", cs.Index.Height+1, float64(blocksFound)*float64(24*time.Hour)/float64(elapsed), cs.Difficulty)

txns, v2txns, err := c.TxpoolTransactions()
check("Couldn't get txpool transactions:", err)
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)
}
if !coreutils.FindBlockNonce(cs, &b, time.Minute) {
continue
}
blocksFound++
index := types.ChainIndex{Height: cs.Index.Height + 1, ID: b.ID()}
tip, err := c.ConsensusTip()
check("Couldn't get consensus tip:", err)
if tip != cs.Index {
fmt.Printf("\nMined %v but tip changed, starting over\n", index)
} else if err := c.SyncerBroadcastBlock(b); err != nil {
fmt.Printf("\nMined invalid block: %v\n", err)
} else if b.V2 == nil {
fmt.Printf("\nFound v1 block %v\n", index)
} else {
fmt.Printf("\nFound v2 block %v\n", index)
}
}
}
11 changes: 7 additions & 4 deletions cmd/walletd/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ func (n *node) Close() error {
return n.store.Close()
}

func newNode(addr, dir string, chainNetwork string, useUPNP bool, log *zap.Logger) (*node, error) {
func newNode(addr, dir string, chainNetwork string, useUPNP, useBootstrap bool, log *zap.Logger) (*node, error) {
var network *consensus.Network
var genesisBlock types.Block
var bootstrapPeers []string
Expand Down Expand Up @@ -166,11 +166,14 @@ func newNode(addr, dir string, chainNetwork string, useUPNP bool, log *zap.Logge
return nil, fmt.Errorf("failed to open wallet database: %w", err)
}

for _, peer := range bootstrapPeers {
if err := store.AddPeer(peer); err != nil {
return nil, fmt.Errorf("failed to add bootstrap peer '%s': %w", peer, err)
if useBootstrap {
for _, peer := range bootstrapPeers {
if err := store.AddPeer(peer); err != nil {
return nil, fmt.Errorf("failed to add bootstrap peer '%s': %w", peer, err)
}
}
}

header := gateway.Header{
GenesisID: genesisBlock.ID(),
UniqueID: gateway.GenerateUniqueID(),
Expand Down
100 changes: 0 additions & 100 deletions cmd/walletd/testnet.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
package main

import (
"encoding/binary"
"fmt"
"log"
"math/big"
"time"

"go.sia.tech/core/consensus"
"go.sia.tech/core/types"
"go.sia.tech/walletd/api"
"lukechampine.com/frand"
)

// TestnetAnagami returns the chain parameters and genesis block for the "Anagami"
Expand Down Expand Up @@ -63,97 +57,3 @@ func TestnetAnagami() (*consensus.Network, types.Block) {

return n, b
}

func mineBlock(cs consensus.State, b *types.Block) (hashes int, found bool) {
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()
startBlock := time.Now()
for types.BlockID(types.HashBytes(buf)).CmpWork(cs.ChildTarget) < 0 {
b.Nonce += factor
hashes++
binary.LittleEndian.PutUint64(buf[32:], b.Nonce)
if time.Since(startBlock) > 10*time.Second {
return hashes, false
}
}
return hashes, true
}

func runTestnetMiner(c *api.Client, minerAddr types.Address, n int) {
log.Println("Started mining into", minerAddr)
start := time.Now()

var hashes float64
var blocks uint64
var last types.ChainIndex
outer:
for i := 0; ; i++ {
if n <= 0 && i >= n {
return
}
elapsed := time.Since(start)
cs, err := c.ConsensusTipState()
check("Couldn't get consensus tip state:", err)
if cs.Index == last {
fmt.Println("Tip now", cs.Index)
last = cs.Index
}
n := big.NewInt(int64(hashes))
n.Mul(n, big.NewInt(int64(24*time.Hour)))
d, _ := new(big.Int).SetString(cs.Difficulty.String(), 10)
d.Mul(d, big.NewInt(int64(1+elapsed)))
r, _ := new(big.Rat).SetFrac(n, d).Float64()
fmt.Printf("\rMining block %4v...(%.2f kH/s, %.2f blocks/day (expected: %.2f), difficulty %v)", cs.Index.Height+1, hashes/elapsed.Seconds()/1000, float64(blocks)*float64(24*time.Hour)/float64(elapsed), r, cs.Difficulty)

txns, v2txns, err := c.TxpoolTransactions()
check("Couldn't get txpool transactions:", err)
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)
}
h, ok := mineBlock(cs, &b)
hashes += float64(h)
if !ok {
continue outer
}
blocks++
index := types.ChainIndex{Height: cs.Index.Height + 1, ID: b.ID()}
tip, err := c.ConsensusTip()
check("Couldn't get consensus tip:", err)
if tip != cs.Index {
fmt.Printf("\nMined %v but tip changed, starting over\n", index)
} else if err := c.SyncerBroadcastBlock(b); err != nil {
fmt.Printf("\nMined invalid block: %v\n", err)
} else if b.V2 == nil {
fmt.Printf("\nFound v1 block %v\n", index)
} else {
fmt.Printf("\nFound v2 block %v\n", index)
}
}
}