Skip to content

Commit

Permalink
Merge pull request #25 from dusk-network/update-bid
Browse files Browse the repository at this point in the history
Refactor generation component to search for bid values by itself, without needing user input
  • Loading branch information
jules committed Aug 31, 2019
2 parents 556472d + c61afca commit fdcff6c
Show file tree
Hide file tree
Showing 13 changed files with 409 additions and 258 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,4 @@ After completion, type `startprovisioner` into the CLI to join the consensus.
### How to become a Block Generator?
Open a Command Line Interface (CLI) and type `bid [amount] [locktime] [password]`, where `[amount]` stands for the amount of tDUSK the user is willing to bid (`0 < amount <= balance`), `[locktime]` stands for the amount of blocks for which the bid is locked (`0 < locktime < 250000`), and `[password]` stands for the wallet password.

After completion, you should note down the tx hash that the wallet prints out. Wait a minute or two for the transaction to be included in a block, and then type `startblockgenerator [txid]` into the CLI to join the consensus.
After completion, type `startblockgenerator` into the CLI to join the consensus.
77 changes: 33 additions & 44 deletions pkg/cli/commands.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package cli

import (
"encoding/hex"
"bytes"
"encoding/binary"
"fmt"
"math"
"math/big"
Expand All @@ -14,10 +15,8 @@ import (
"github.com/dusk-network/dusk-blockchain/pkg/core/consensus"
"github.com/dusk-network/dusk-blockchain/pkg/core/consensus/factory"
"github.com/dusk-network/dusk-blockchain/pkg/core/consensus/generation"
"github.com/dusk-network/dusk-blockchain/pkg/core/consensus/msg"
"github.com/dusk-network/dusk-blockchain/pkg/core/consensus/user"
"github.com/dusk-network/dusk-blockchain/pkg/core/database"
"github.com/dusk-network/dusk-blockchain/pkg/core/database/heavy"
"github.com/dusk-network/dusk-blockchain/pkg/core/transactions"
"github.com/dusk-network/dusk-blockchain/pkg/p2p/wire"
)

Expand Down Expand Up @@ -72,20 +71,21 @@ func startProvisioner(args []string, publisher wire.EventBroker, rpcBus *wire.RP
f := factory.New(publisher, rpcBus, config.ConsensusTimeOut, cliWallet.ConsensusKeys())
f.StartConsensus()

if err := consensus.GetStartingRound(publisher, nil, cliWallet.ConsensusKeys()); err != nil {
fmt.Fprintf(os.Stdout, "error starting consensus: %v\n", err)
return
}
blsPubKey := cliWallet.ConsensusKeys().BLSPubKeyBytes

go func() {
startingRound := getStartingRound(blsPubKey, publisher)

// Notify consensus components
roundBytes := make([]byte, 8)
binary.LittleEndian.PutUint64(roundBytes, startingRound)
publisher.Publish(msg.InitializationTopic, bytes.NewBuffer(roundBytes))
}()

fmt.Fprintf(os.Stdout, "provisioner module started\nto more accurately follow the progression of consensus, use the showlogs command\n")
}

func startBlockGenerator(args []string, publisher wire.EventBroker, rpcBus *wire.RPCBus) {
if len(args) == 0 || args[0] == "" {
fmt.Fprintf(os.Stdout, "please provide a bidding tx hash to start block generation\n")
return
}

if cliWallet == nil {
fmt.Fprintf(os.Stdout, "please load a wallet before trying to participate in consensus\n")
return
Expand All @@ -110,40 +110,17 @@ func startBlockGenerator(args []string, publisher wire.EventBroker, rpcBus *wire
var k ristretto.Scalar
k.Derive(kBytes)

txID, err := hex.DecodeString(args[0])
if err != nil {
fmt.Fprintf(os.Stdout, "error attempting to decode tx: %v\n", err)
return
}

// fetch bid tx
_, db := heavy.CreateDBConnection()
var tx transactions.Transaction
err = db.View(func(t database.Transaction) error {
var err error
tx, _, _, err = t.FetchBlockTxByHash(txID)
return err
})

if err != nil {
fmt.Fprintf(os.Stdout, "could not find supplied bid tx\n")
return
}

bid, ok := tx.(*transactions.Bid)
if !ok {
fmt.Fprintf(os.Stdout, "supplied txID does not point to a bidding transaction\n")
return
}

var d ristretto.Scalar
d.UnmarshalBinary(bid.Outputs[0].Commitment)
// get public key that the rewards should go to
publicKey := cliWallet.PublicKey()

// launch generation component
publicKey := cliWallet.PublicKey()
generation.Launch(publisher, rpcBus, d, k, nil, nil, keys, &publicKey)
go func() {
if err := generation.Launch(publisher, rpcBus, k, keys, &publicKey, nil, nil, nil); err != nil {
fmt.Fprintf(os.Stdout, "error launching block generation component: %v\n", err)
}
}()

fmt.Fprintf(os.Stdout, "block generator module started\nto more accurately follow the progression of consensus, use the showlogs command\n")
fmt.Fprintf(os.Stdout, "block generation component started\nto more accurately follow the progression of consensus, use the showlogs command\n")
}

func stopNode(args []string, publisher wire.EventBroker, rpcBus *wire.RPCBus) {
Expand Down Expand Up @@ -193,3 +170,15 @@ func stringToUint64(s string) (uint64, error) {
}
return (uint64(sInt)), nil
}

func getStartingRound(blsPubKey []byte, eventBroker wire.EventBroker) uint64 {
// Start listening for accepted blocks, regardless of if we found stakes or not
acceptedBlockChan, listener := consensus.InitAcceptedBlockUpdate(eventBroker)
// Unsubscribe from AcceptedBlock once we're done
defer listener.Quit()

for {
blk := <-acceptedBlockChan
return blk.Header.Height + 1
}
}
12 changes: 6 additions & 6 deletions pkg/cli/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,16 @@ var commandInfo = map[string]string{
"createfromseed": `Usage: createfromseed [seed] [password]
Loads the encrypted wallet file from a hex seed.`,
"balance": `Usage: balance
Prints the balance of the loaded wallet. Make sure to use the sync command first.`,
Prints the balance of the loaded wallet.`,
"transfer": `Usage: transfer [amount] [address] [password]
Send DUSK to a given address. Make sure to use the sync command first.`,
Send DUSK to a given address.`,
"stake": `Usage: stake [amount] [locktime] [password]
Stake a given amount of DUSK, to allow participation as a provisioner in consensus. Make sure to use the sync command first.`,
Stake a given amount of DUSK, to allow participation as a provisioner in consensus.`,
"bid": `Usage: bid [amount] [locktime] [password]
Bid a given amount of DUSK, to allow participation as a block generator in consensus. Make sure to use the sync command first.`,
Bid a given amount of DUSK, to allow participation as a block generator in consensus.`,
"startprovisioner": `Send a signal to the connected DUSK node to start participating in consensus as a provisioner.`,
"startblockgenerator": `Usage: startblockgenerator [bidtxhash]
Send a signal to the connected DUSK node to start participating in consensus as a block generator. Specified bid tx must be included in a block before trying to start the block generation component.`,
"startblockgenerator": `Usage: startblockgenerator
Send a signal to the connected DUSK node to start participating in consensus as a block generator.`,
"showlogs": "Close the shell and show the internal logs on the terminal. Press enter to return to the shell.",
"exit/quit": `Shut down the node and close the console`,
}
123 changes: 123 additions & 0 deletions pkg/core/consensus/generation/bidretriever.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package generation

import (
"bytes"
"errors"

"github.com/dusk-network/dusk-blockchain/pkg/core/block"
"github.com/dusk-network/dusk-blockchain/pkg/core/database"
"github.com/dusk-network/dusk-blockchain/pkg/core/database/heavy"
"github.com/dusk-network/dusk-blockchain/pkg/core/transactions"
log "github.com/sirupsen/logrus"
)

// bidRetriever is a simple searcher, who's responsibility is to find a bid transaction, when given
// an M value.
type bidRetriever struct {
db database.DB
}

// newBidRetriever returns an initialized bidRetriever, ready for use.
func newBidRetriever(db database.DB) *bidRetriever {
// Get a db connection, if none was given.
if db == nil {
_, db = heavy.CreateDBConnection()
}

return &bidRetriever{
db: db,
}
}

// SearchForBid will walk up the blockchain to find a bid with the corresponding `m`,
// by running a comparison function on each tx for every found block. If the comparison function finds a match,
// then this tx is returned. This tx can then be used by the generation component to start or reset itself.
func (i *bidRetriever) SearchForBid(m []byte) (transactions.Transaction, error) {
currentHeight := i.getCurrentHeight()
searchingHeight := i.getSearchingHeight(currentHeight)

for {
blk, err := i.getBlock(searchingHeight)
if err != nil {
break
}

tx, err := findCorrespondingBid(blk.Txs, m, searchingHeight, currentHeight)
if err != nil {
searchingHeight++
continue
}

return tx, nil
}

return nil, errors.New("could not find corresponding value for specified item")
}

// If given a set of transactions and an M value, this function will return a bid
// transaction corresponding to that M value.
func findCorrespondingBid(txs []transactions.Transaction, m []byte, searchingHeight, currentHeight uint64) (transactions.Transaction, error) {
for _, tx := range txs {
bid, ok := tx.(*transactions.Bid)
if !ok {
continue
}

if bytes.Equal(m, bid.M) && bid.Lock+searchingHeight > currentHeight {
hash, err := bid.CalculateHash()
if err != nil {
// If we found a valid bid tx, we should under no circumstance have issues marshalling it
panic(err)
}

log.WithFields(log.Fields{
"process": "generation",
"height": searchingHeight,
"tx hash": hash,
}).Debugln("bid found in chain")
return bid, nil
}
}

return nil, errors.New("could not find a corresponding d value")
}

// Bid transactions can only be valid for the maximum locktime. So, we will begin our search at
// the tip height, minus the maximum locktime. This function will tell us exactly what that height is.
func (i *bidRetriever) getSearchingHeight(currentHeight uint64) uint64 {
if currentHeight < transactions.MaxLockTime {
return 0
}
return currentHeight - transactions.MaxLockTime
}

func (i *bidRetriever) getBlock(searchingHeight uint64) (*block.Block, error) {
var b *block.Block
err := i.db.View(func(t database.Transaction) error {
hash, err := t.FetchBlockHashByHeight(searchingHeight)
if err != nil {
return err
}

b, err = t.FetchBlock(hash)
return err
})

return b, err
}

func (i *bidRetriever) getCurrentHeight() (currentHeight uint64) {
err := i.db.View(func(t database.Transaction) error {
var err error
currentHeight, err = t.FetchCurrentHeight()
return err
})

if err != nil {
// If we can not get the current height, it means we have no DB state object, or the header of the tip hash is missing
// Neither should ever happen, so we panic
panic(err)
}

return
}
63 changes: 63 additions & 0 deletions pkg/core/consensus/generation/bidretriever_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package generation

import (
"testing"

ristretto "github.com/bwesterb/go-ristretto"
"github.com/dusk-network/dusk-blockchain/pkg/core/database"
"github.com/dusk-network/dusk-blockchain/pkg/core/database/lite"
"github.com/dusk-network/dusk-blockchain/pkg/core/tests/helper"
"github.com/dusk-network/dusk-blockchain/pkg/core/transactions"
zkproof "github.com/dusk-network/dusk-zkproof"
"github.com/stretchr/testify/assert"
)

func TestSearchForBid(t *testing.T) {
k := ristretto.Scalar{}
k.Rand()

d := ristretto.Scalar{}
d.Rand()
db, err := initTest(t)
assert.NoError(t, err)

bid, err := helper.RandomBidTx(t, false)
assert.NoError(t, err)

bid.Outputs[0].Commitment = d.Bytes()
m := zkproof.CalculateM(k)
bid.M = m.Bytes()

retriever := newBidRetriever(db)
// We shouldn't get anything back yet, as the bid is not included in a block in the db
retrieved, err := retriever.SearchForBid(m.Bytes())
assert.Error(t, err)
assert.Nil(t, retrieved)

// Now, add the bid to the db
storeTx(t, db, bid)

// This time, we shouldn't get an error, and an actual value returned
retrieved, err = retriever.SearchForBid(m.Bytes())
assert.NoError(t, err)
assert.NotNil(t, retrieved)
assert.Equal(t, d.Bytes(), retrieved.(*transactions.Bid).Outputs[0].Commitment)
}

func initTest(t *testing.T) (database.DB, error) {
_, db := lite.CreateDBConnection()
// Add a genesis block so we don't run into any panics
blk := helper.RandomBlock(t, 0, 2)
return db, db.Update(func(t database.Transaction) error {
return t.StoreBlock(blk)
})
}

func storeTx(t *testing.T, db database.DB, tx transactions.Transaction) {
blk := helper.RandomBlock(t, 1, 2)
blk.AddTx(tx)
err := db.Update(func(t database.Transaction) error {
return t.StoreBlock(blk)
})
assert.NoError(t, err)
}
Loading

0 comments on commit fdcff6c

Please sign in to comment.