Skip to content

Commit

Permalink
Refactor miner
Browse files Browse the repository at this point in the history
  • Loading branch information
bfbachmann committed Aug 22, 2017
1 parent af8c8c0 commit 1f2f31c
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 123 deletions.
14 changes: 10 additions & 4 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/json"
"errors"
"fmt"
"math/big"
"os"
"os/signal"
"sync"
Expand Down Expand Up @@ -36,6 +37,7 @@ type App struct {
CurrentUser *User
PeerStore *peer.PeerStore
Chain *blockchain.BlockChain
Miner *miner.Miner
Pool *pool.Pool
blockQueue chan *blockchain.Block
transactionQueue chan *blockchain.Transaction
Expand All @@ -48,12 +50,16 @@ func Run(cfg conf.Config) {
log.Info("Starting Cumulus node")
config := &cfg

// TODO: remove this when we have scaleable difficulty
consensus.CurrentDifficulty = big.NewInt(2 << 21)

addr := fmt.Sprintf("%s:%d", config.Interface, config.Port)
user := getCurrentUser()
a := App{
PeerStore: peer.NewPeerStore(addr),
CurrentUser: user,
Chain: createBlockchain(user),
Miner: miner.New(),
Pool: getLocalPool(),
blockQueue: make(chan *blockchain.Block, blockQueueSize),
transactionQueue: make(chan *blockchain.Transaction, transactionQueueSize),
Expand Down Expand Up @@ -302,7 +308,7 @@ func (a *App) HandleTransaction(txn *blockchain.Transaction) {
// HandleBlock handles new instance of BlockWork.
func (a *App) HandleBlock(blk *blockchain.Block) {
log.Info("Received new block")
wasMining := miner.PauseIfRunning()
wasMining := a.Miner.PauseIfRunning()

a.Chain.Lock.Lock()
defer a.Chain.Lock.Unlock()
Expand Down Expand Up @@ -363,7 +369,7 @@ func (a *App) RunMiner() {

// TODO: update this when we have adjustable difficulty
blockToMine.Target = consensus.CurrentTarget()
miningResult := miner.Mine(blockToMine)
miningResult := a.Miner.Mine(blockToMine)

if miningResult.Complete {
log.Info("Successfully mined a block!")
Expand All @@ -384,9 +390,9 @@ func (a *App) RunMiner() {
// restarts the miner with a new mining job.
func (a *App) ResumeMiner(restart bool) {
if !restart {
miner.ResumeMining()
a.Miner.ResumeMining()
} else {
miner.StopMining()
a.Miner.StopMining()
go a.RunMiner()
}
}
Expand Down
18 changes: 9 additions & 9 deletions app/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,20 +192,20 @@ func TestRunMiner(t *testing.T) {
a := createNewTestApp()
go a.RunMiner()
time.Sleep(time.Second / 2)
assert.Equal(t, int(miner.State()), int(miner.Running))
assert.True(t, miner.PauseIfRunning())
assert.Equal(t, int(miner.State()), int(miner.Paused))
assert.Equal(t, int(a.Miner.State()), int(miner.Running))
assert.True(t, a.Miner.PauseIfRunning())
assert.Equal(t, int(a.Miner.State()), int(miner.Paused))
a.ResumeMiner(false)
time.Sleep(time.Second / 2)
assert.Equal(t, int(miner.State()), int(miner.Running))
miner.PauseIfRunning()
assert.Equal(t, int(a.Miner.State()), int(miner.Running))
a.Miner.PauseIfRunning()
time.Sleep(time.Second / 2)
assert.Equal(t, int(miner.State()), int(miner.Paused))
assert.Equal(t, int(a.Miner.State()), int(miner.Paused))
a.ResumeMiner(true)
time.Sleep(time.Second / 2)
assert.Equal(t, int(miner.State()), int(miner.Running))
miner.StopMining()
assert.Equal(t, int(miner.State()), int(miner.Stopped))
assert.Equal(t, int(a.Miner.State()), int(miner.Running))
a.Miner.StopMining()
assert.Equal(t, int(a.Miner.State()), int(miner.Stopped))
consensus.CurrentDifficulty = oldDifficulty
}

Expand Down
16 changes: 8 additions & 8 deletions app/console.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,9 @@ func connect(ctx *ishell.Context, a *App) {

func toggleMiner(ctx *ishell.Context, app *App) {
if len(ctx.Args) != 1 {
if miner.State() == miner.Running {
if app.Miner.State() == miner.Running {
shell.Println("Miner is running.")
} else if miner.State() == miner.Paused {
} else if app.Miner.State() == miner.Paused {
shell.Println("Miner is paused.")
} else {
shell.Println("Miner is stopped.")
Expand All @@ -129,24 +129,24 @@ func toggleMiner(ctx *ishell.Context, app *App) {

switch ctx.Args[0] {
case "start":
if miner.State() == miner.Running {
if app.Miner.State() == miner.Running {
shell.Println("Miner is already running.")
} else if miner.State() == miner.Paused {
miner.ResumeMining()
} else if app.Miner.State() == miner.Paused {
app.Miner.ResumeMining()
shell.Println("Resumed mining.")
} else {
go app.RunMiner()
shell.Println("Started miner.")
}
case "stop":
if miner.State() == miner.Stopped {
if app.Miner.State() == miner.Stopped {
shell.Println("Miner is already stopped.")
return
}
miner.StopMining()
app.Miner.StopMining()
shell.Println("Stopped miner.")
case "pause":
wasRunning := miner.PauseIfRunning()
wasRunning := app.Miner.PauseIfRunning()
if wasRunning {
shell.Println("Paused miner.")
} else {
Expand Down
2 changes: 2 additions & 0 deletions app/test_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package app

import (
"github.com/ubclaunchpad/cumulus/blockchain"
"github.com/ubclaunchpad/cumulus/miner"
"github.com/ubclaunchpad/cumulus/msg"
"github.com/ubclaunchpad/cumulus/peer"
"github.com/ubclaunchpad/cumulus/pool"
Expand All @@ -22,6 +23,7 @@ func createNewTestApp() *App {
PeerStore: peer.NewPeerStore("127.0.0.1:8000"),
CurrentUser: NewUser(),
Chain: chain,
Miner: miner.New(),
Pool: pool.New(),
blockQueue: make(chan *blockchain.Block, blockQueueSize),
transactionQueue: make(chan *blockchain.Transaction, transactionQueueSize),
Expand Down
161 changes: 73 additions & 88 deletions miner/miner.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@ import (
"github.com/ubclaunchpad/cumulus/consensus"
)

// MinerState represents the state of the miner
type MinerState int

const (
// Paused represents the MinerState where the miner is not running but the
// previously running mining job can be resumed or stopped.
Expand All @@ -31,63 +28,73 @@ const (
MiningHalted
)

var (
// checkState signals to the miner to check for a new mining state when true.
checkState bool
// checkStateLock is a read/write lock to change the checkState flag.
checkStateLock = &sync.RWMutex{}
// minerState represents the state of the miner at any given time
minerState MinerState
// minerStateLock is a read/write lock to check the minerState variable
minerStateLock = &sync.RWMutex{}
// MiningResult contains the result of the mining operation.
type MiningResult struct {
Complete bool
Info int
}

// MinerState represents the state of the miner
type MinerState int

// Miner represents the state of of the current mining job (or lack thereof).
type Miner struct {
// state represents the state of the miner at any given time
state MinerState
// stateLock is a read/write lock to check the state variable
stateLock *sync.RWMutex
// stop signals to the miner to abort the current mining job immediately.
stop = make(chan bool)
stop chan bool
// resume signals to the miner that it can continue mining from its previous
// state.
resume = make(chan bool)
resume chan bool
// pause signals to the miner to pause mining and wait for a stop or resume
// signal.
pause = make(chan bool)
)
pause chan bool
}

// MiningResult contains the result of the mining operation.
type MiningResult struct {
Complete bool
Info int
// New returns a new miner.
func New() *Miner {
return &Miner{
state: Stopped,
stateLock: &sync.RWMutex{},
stop: make(chan bool),
resume: make(chan bool),
pause: make(chan bool),
}
}

// Mine continuously increases the nonce and tries to verify the proof of work
// until the puzzle is solved.
func Mine(b *blockchain.Block) *MiningResult {
setStateChanged(false)
setState(Running)
func (m *Miner) Mine(b *blockchain.Block) *MiningResult {
m.setState(Running)

miningHalted := &MiningResult{
Complete: false,
Info: MiningHalted,
}

for !VerifyProofOfWork(b) {
for !m.VerifyProofOfWork(b) {
// Check if we should keep mining.
if stateChanged() {
select {
case <-m.pause:
m.setState(Paused)
select {
case <-pause:
setState(Paused)
select {
case <-resume:
setState(Running)
case <-stop:
setState(Stopped)
return miningHalted
case <-pause:
panic("Miner already paused")
}
case <-stop:
setState(Stopped)
case <-m.resume:
m.setState(Running)
case <-m.stop:
m.setState(Stopped)
return miningHalted
case <-resume:
panic("Miner already running")
case <-m.pause:
panic("Miner already paused")
}
case <-m.stop:
m.setState(Stopped)
return miningHalted
case <-m.resume:
panic("Miner already running")
default:
// No state change - keep mining.
}

// Check if we should reset the nonce.
Expand All @@ -100,69 +107,53 @@ func Mine(b *blockchain.Block) *MiningResult {
b.Nonce++
}

setState(Stopped)
m.setState(Stopped)
return &MiningResult{
Complete: true,
Info: MiningSuccessful,
}
}

// setState synchronously sets the current state of the miner to the given state.
func (m *Miner) setState(state MinerState) {
m.stateLock.Lock()
defer m.stateLock.Unlock()
m.state = state
}

// StopMining causes the miner to abort the current mining job immediately.
func StopMining() {
if State() == Running {
setStateChanged(true)
}
stop <- true
func (m *Miner) StopMining() {
m.stop <- true
}

// PauseIfRunning pauses the current mining job if it is current running. Returns
// true if the miner was running and false otherwise.
func PauseIfRunning() bool {
minerStateLock.RLock()
defer minerStateLock.RUnlock()

if minerState == Running {
checkStateLock.Lock()
checkState = true
checkStateLock.Unlock()
pause <- true
func (m *Miner) PauseIfRunning() bool {
m.stateLock.RLock()
defer m.stateLock.RUnlock()
if m.state == Running {
m.pause <- true
return true
}
return false
}

// ResumeMining causes the miner to continue mining from a paused state.
func ResumeMining() {
resume <- true
}

// setStateChanged synchronously sets the checkState variable to the given value.
func setStateChanged(check bool) {
checkStateLock.Lock()
defer checkStateLock.Unlock()
checkState = check
}

// stateChanged synchronously returns wheter or not the miner state has changed
// since it was last checked by the miner.
func stateChanged() bool {
checkStateLock.RLock()
defer checkStateLock.RUnlock()
return checkState
func (m *Miner) ResumeMining() {
m.resume <- true
}

// setState synchronously sets the current state of the miner to the given state.
func setState(state MinerState) {
minerStateLock.Lock()
defer minerStateLock.Unlock()
minerState = state
// State synchronously returns the current state of the miner.
func (m *Miner) State() MinerState {
m.stateLock.RLock()
defer m.stateLock.RUnlock()
return m.state
}

// State synchronously returns the current state of the miner.
func State() MinerState {
minerStateLock.RLock()
defer minerStateLock.RUnlock()
return minerState
// VerifyProofOfWork computes the hash of the MiningHeader and returns true if
// the result is less than the target
func (m *Miner) VerifyProofOfWork(b *blockchain.Block) bool {
return blockchain.HashSum(b).LessThan(b.Target)
}

// CloudBase prepends the cloudbase transaction to the front of a list of
Expand Down Expand Up @@ -205,9 +196,3 @@ func CloudBase(

return b
}

// VerifyProofOfWork computes the hash of the MiningHeader and returns true if
// the result is less than the target
func VerifyProofOfWork(b *blockchain.Block) bool {
return blockchain.HashSum(b).LessThan(b.Target)
}
Loading

0 comments on commit 1f2f31c

Please sign in to comment.