Permalink
Browse files

Allow running either the new or old ticket buyer. (#470)

An --enableticketbuyer option has been added.  It must be used to
enable the new ticket buyer.

An --enablevoting option has been added to turn on the wallet's
automatic creation of vote and revocation transactions when using the
new ticket purchaser.

The --enablestakemining option has been deprecated for the above two
options.  To use the old ticket buyer from the wallet package, use
this option and do NOT set --enableticketbuyer (doing so would enable
the new ticket buyer).

All RPCs to interact with the ticket buyer remain, but only affect the
old deprecated ticket buyer, and are also deprecated by extension.

Fixes #468.
  • Loading branch information...
1 parent 8fa052a commit 879e0689b539852315b2e311681a6b879fa77f3c @jrick jrick committed on GitHub Dec 20, 2016
Showing with 198 additions and 138 deletions.
  1. +24 −4 config.go
  2. +32 −44 dcrwallet.go
  3. +5 −9 rpc/legacyrpc/methods.go
  4. +38 −10 ticketbuyer.go
  5. +3 −5 ticketbuyer/purchase.go
  6. +24 −7 wallet/chainntfns.go
  7. +18 −17 wallet/loader.go
  8. +45 −34 wallet/wallet.go
  9. +9 −8 walletsetup.go
View
@@ -32,6 +32,8 @@ const (
defaultRPCMaxClients = 10
defaultRPCMaxWebsockets = 25
defaultEnableStakeMining = false
+ defaultEnableTicketBuyer = false
+ defaultEnableVoting = false
defaultVoteBits = 0x0001
defaultVoteBitsExtended = "02000000"
defaultBalanceToMaintain = 0.0
@@ -83,7 +85,8 @@ type config struct {
WalletPass string `long:"walletpass" default-mask:"-" description:"The public wallet password -- Only required if the wallet was created with one"`
PromptPass bool `long:"promptpass" description:"The private wallet password is prompted for at start up, so the wallet starts unlocked without a time limit"`
DisallowFree bool `long:"disallowfree" description:"Force transactions to always include a fee"`
- EnableStakeMining bool `long:"enablestakemining" description:"Enable stake mining"`
+ EnableTicketBuyer bool `long:"enableticketbuyer" description:"Enable the automatic ticket buyer"`
+ EnableVoting bool `long:"enablevoting" description:"Enable creation of votes and revocations for owned tickets"`
VoteBits uint16 `long:"votebits" description:"Set your stake mining votebits to value (default: 0xFFFF)"`
VoteBitsExtended string `long:"votebitsextended" description:"Set your stake mining extended votebits to the hexademical value indicated by the passed string"`
BalanceToMaintain float64 `long:"balancetomaintain" description:"Minimum amount of funds to leave in wallet when stake mining (default: 0.0)"`
@@ -138,7 +141,8 @@ type config struct {
ExperimentalRPCListeners []string `long:"experimentalrpclisten" description:"Listen for RPC connections on this interface/port"`
// Deprecated options
- DataDir string `short:"b" long:"datadir" default-mask:"-" description:"DEPRECATED -- use appdata instead"`
+ DataDir string `short:"b" long:"datadir" default-mask:"-" description:"DEPRECATED -- use appdata instead"`
+ EnableStakeMining bool `long:"enablestakemining" default-mask:"-" description:"DEPRECATED -- consider using enableticketbuyer and/or enablevoting instead"`
}
// cleanAndExpandPath expands environement variables and leading ~ in the
@@ -307,7 +311,8 @@ func loadConfig() (*config, []string, error) {
TLSCurve: cfgutil.NewCurveFlag(cfgutil.CurveP521),
LegacyRPCMaxClients: defaultRPCMaxClients,
LegacyRPCMaxWebsockets: defaultRPCMaxWebsockets,
- EnableStakeMining: defaultEnableStakeMining,
+ EnableTicketBuyer: defaultEnableTicketBuyer,
+ EnableVoting: defaultEnableVoting,
VoteBits: defaultVoteBits,
VoteBitsExtended: defaultVoteBitsExtended,
BalanceToMaintain: defaultBalanceToMaintain,
@@ -323,6 +328,7 @@ func loadConfig() (*config, []string, error) {
StakePoolColdExtKey: defaultStakePoolColdExtKey,
AllowHighFees: defaultAllowHighFees,
DataDir: defaultAppDataDir,
+ EnableStakeMining: defaultEnableStakeMining,
}
// Pre-parse the command line options to see if an alternative config
@@ -388,7 +394,7 @@ func loadConfig() (*config, []string, error) {
log.Warnf("%v", configFileError)
}
- // Check deprecated aliases. The new options receive priority when both
+ // Check deprecated options. The new options receive priority when both
// are changed from the default.
if cfg.DataDir != defaultAppDataDir {
fmt.Fprintln(os.Stderr, "datadir option has been replaced by "+
@@ -397,6 +403,20 @@ func loadConfig() (*config, []string, error) {
cfg.AppDataDir = cfg.DataDir
}
}
+ if cfg.EnableStakeMining {
+ fmt.Fprintln(os.Stderr, "enablestakemining option is deprecated -- "+
+ "consider updating your config to use enablevoting and enableticketbuyer")
+ if cfg.EnableTicketBuyer {
+ fmt.Fprintln(os.Stderr, "Because enableticketbuyer was set, "+
+ "tickets will be purchased using the new buyer")
+ } else {
+ fmt.Fprintln(os.Stderr, "Because enableticketbuyer was not set, "+
+ "tickets will be purchased using the old buyer")
+ }
+ // enablestakemining turns on voting/revocations, but never turns on the
+ // new ticket buyer.
+ cfg.EnableVoting = true
+ }
// If an alternate data directory was specified, and paths with defaults
// relative to the data dir are unchanged, modify each path to be
View
@@ -27,7 +27,8 @@ import (
)
var (
- cfg *config
+ cfg *config
+ ticketBuyerCfg *ticketbuyer.Config
)
func main() {
@@ -89,21 +90,36 @@ func walletMain() error {
}()
}
+ // Load ticket buyer config, if any. A missing config file causes this to
+ // returns a default config and no error.
+ ticketBuyerCfg, err = loadTicketBuyerConfig(cfg)
+ if err != nil {
+ s := fmt.Sprintf("Failed to read ticket buyer config: %v", err)
+ // Fatal error when ticket buyer is enabled.
+ if cfg.EnableTicketBuyer {
+ log.Error(s)
+ return err
+ }
+ // Otherwise just warn.
+ log.Warn(s)
+ }
+
dbDir := networkDir(cfg.AppDataDir, activeNet.Params)
stakeOptions := &wallet.StakeOptions{
- VoteBits: cfg.VoteBits,
- VoteBitsExtended: cfg.VoteBitsExtended,
- StakeMiningEnabled: cfg.EnableStakeMining,
- BalanceToMaintain: cfg.BalanceToMaintain,
- PruneTickets: cfg.PruneTickets,
- AddressReuse: cfg.ReuseAddresses,
- TicketAddress: cfg.TicketAddress,
- TicketMaxPrice: cfg.TicketMaxPrice,
- TicketBuyFreq: cfg.TicketBuyFreq,
- PoolAddress: cfg.PoolAddress,
- PoolFees: cfg.PoolFees,
- StakePoolColdExtKey: cfg.StakePoolColdExtKey,
- TicketFee: cfg.TicketFee,
+ VoteBits: cfg.VoteBits,
+ VoteBitsExtended: cfg.VoteBitsExtended,
+ TicketPurchasingEnabled: cfg.EnableStakeMining && !cfg.EnableTicketBuyer,
+ VotingEnabled: cfg.EnableVoting,
+ BalanceToMaintain: cfg.BalanceToMaintain,
+ PruneTickets: cfg.PruneTickets,
+ AddressReuse: cfg.ReuseAddresses,
+ TicketAddress: cfg.TicketAddress,
+ TicketMaxPrice: cfg.TicketMaxPrice,
+ TicketBuyFreq: cfg.TicketBuyFreq,
+ PoolAddress: cfg.PoolAddress,
+ PoolFees: cfg.PoolFees,
+ StakePoolColdExtKey: cfg.StakePoolColdExtKey,
+ TicketFee: cfg.TicketFee,
}
loader := wallet.NewLoader(activeNet.Params, dbDir, stakeOptions,
cfg.AutomaticRepair, cfg.UnsafeMainNet, cfg.AddrIdxScanLen,
@@ -286,36 +302,8 @@ func rpcClientConnectLoop(legacyRPCServer *legacyrpc.Server, loader *wallet.Load
if legacyRPCServer != nil {
legacyRPCServer.SetChainServer(chainClient)
}
- tcfg, err := loadTicketBuyerConfig(cfg.AppDataDir)
- if err != nil {
- log.Errorf("Unable to load ticket buyer config: %v", err)
- log.Info("Ticket purchasing is disabled")
- } else {
- ticketbuyerCfg := &ticketbuyer.Config{
- AccountName: cfg.PurchaseAccount,
- AvgPriceMode: tcfg.AvgPriceMode,
- AvgPriceVWAPDelta: tcfg.AvgPriceVWAPDelta,
- BalanceToMaintain: cfg.BalanceToMaintain,
- BlocksToAvg: tcfg.BlocksToAvg,
- DontWaitForTickets: tcfg.DontWaitForTickets,
- ExpiryDelta: tcfg.ExpiryDelta,
- FeeSource: tcfg.FeeSource,
- FeeTargetScaling: tcfg.FeeTargetScaling,
- HighPricePenalty: tcfg.HighPricePenalty,
- MinFee: tcfg.MinFee,
- MinPriceScale: tcfg.MinPriceScale,
- MaxFee: tcfg.MaxFee,
- MaxPerBlock: tcfg.MaxPerBlock,
- MaxPriceAbsolute: cfg.TicketMaxPrice,
- MaxPriceScale: tcfg.MaxPriceScale,
- MaxInMempool: tcfg.MaxInMempool,
- PoolAddress: cfg.PoolAddress,
- PoolFees: cfg.PoolFees,
- PriceTarget: tcfg.PriceTarget,
- TicketAddress: cfg.TicketAddress,
- TxFee: cfg.TicketFee,
- }
- startTicketPurchase(w, chainClient.Client, nil, ticketbuyerCfg)
+ if cfg.EnableTicketBuyer {
+ startTicketPurchase(w, chainClient.Client, nil, ticketBuyerCfg)
}
}
mu := new(sync.Mutex)
@@ -2671,19 +2671,15 @@ func sendToSSRtx(icmd interface{}, w *wallet.Wallet, chainClient *chain.RPCClien
return txSha.String(), nil
}
-// getGenerate returns if stake mining is enabled for the wallet.
+// getGenerate returns if the wallet is set to auto purchase tickets.
func getGenerate(icmd interface{}, w *wallet.Wallet) (interface{}, error) {
- return w.StakeMiningEnabled, nil
+ return w.TicketPurchasingEnabled(), nil
}
-// setGenerate enables or disables stake mining the wallet (ticket
-// autopurchase, vote generation, and revocation generation). The
-// number of processors may be declared but is ignored (as this is
-// non-PoW work).
+// setGenerate enables or disables the wallet's auto ticket purchaser.
func setGenerate(icmd interface{}, w *wallet.Wallet) (interface{}, error) {
cmd := icmd.(*dcrjson.SetGenerateCmd)
- err := w.SetGenerate(cmd.Generate)
-
+ err := w.SetTicketPurchasingEnabled(cmd.Generate)
return nil, err
}
@@ -3337,7 +3333,7 @@ func walletInfo(icmd interface{}, w *wallet.Wallet, chainClient *chain.RPCClient
tfi := w.TicketFeeIncrement()
tmp := w.GetTicketMaxPrice()
btm := w.BalanceToMaintain()
- sm := w.Generate()
+ sm := w.TicketPurchasingEnabled()
return &dcrjson.WalletInfoResult{
DaemonConnected: connected,
View
@@ -84,13 +84,36 @@ type ticketBuyerConfig struct {
BalanceToMaintain float64 `long:"balancetomaintain" description:"Balance to try to maintain in the wallet"`
}
-// loadTicketBuyerConfig initializes and parses the config using a config file.
-func loadTicketBuyerConfig(appDataDir string) (*ticketBuyerConfig, error) {
- loadConfigError := func(err error) (*ticketBuyerConfig, error) {
- return nil, err
+func newTicketBuyerConfig(appConfig *config, parsedConfig *ticketBuyerConfig) *ticketbuyer.Config {
+ return &ticketbuyer.Config{
+ AccountName: appConfig.PurchaseAccount,
+ AvgPriceMode: parsedConfig.AvgPriceMode,
+ AvgPriceVWAPDelta: parsedConfig.AvgPriceVWAPDelta,
+ BalanceToMaintain: appConfig.BalanceToMaintain,
+ BlocksToAvg: parsedConfig.BlocksToAvg,
+ DontWaitForTickets: parsedConfig.DontWaitForTickets,
+ ExpiryDelta: parsedConfig.ExpiryDelta,
+ FeeSource: parsedConfig.FeeSource,
+ FeeTargetScaling: parsedConfig.FeeTargetScaling,
+ HighPricePenalty: parsedConfig.HighPricePenalty,
+ MinFee: parsedConfig.MinFee,
+ MinPriceScale: parsedConfig.MinPriceScale,
+ MaxFee: parsedConfig.MaxFee,
+ MaxPerBlock: parsedConfig.MaxPerBlock,
+ MaxPriceAbsolute: appConfig.TicketMaxPrice,
+ MaxPriceScale: parsedConfig.MaxPriceScale,
+ MaxInMempool: parsedConfig.MaxInMempool,
+ PoolAddress: appConfig.PoolAddress,
+ PoolFees: appConfig.PoolFees,
+ PriceTarget: parsedConfig.PriceTarget,
+ TicketAddress: appConfig.TicketAddress,
+ TxFee: appConfig.TicketFee,
}
+}
- defaultTicketBuyerConfigFile := filepath.Join(appDataDir,
+// loadTicketBuyerConfig initializes and parses the config using a config file.
+func loadTicketBuyerConfig(appConfig *config) (*ticketbuyer.Config, error) {
+ defaultTicketBuyerConfigFile := filepath.Join(appConfig.AppDataDir,
defaultTicketBuyerConfigFilename)
// Default config.
cfg := ticketBuyerConfig{
@@ -115,7 +138,7 @@ func loadTicketBuyerConfig(appDataDir string) (*ticketBuyerConfig, error) {
}
if _, err := os.Stat(defaultTicketBuyerConfigFile); os.IsNotExist(err) {
- return &cfg, nil
+ return newTicketBuyerConfig(appConfig, &cfg), nil
}
// Load additional config from file.
@@ -126,7 +149,7 @@ func loadTicketBuyerConfig(appDataDir string) (*ticketBuyerConfig, error) {
if _, ok := err.(*os.PathError); !ok {
log.Warn(err)
parser.WriteHelp(os.Stderr)
- return loadConfigError(err)
+ return nil, err
}
configFileError = err
}
@@ -157,7 +180,7 @@ func loadTicketBuyerConfig(appDataDir string) (*ticketBuyerConfig, error) {
str := "%s: Invalid fee source '%s'"
err := fmt.Errorf(str, "loadTicketBuyerConfig", cfg.FeeSource)
log.Warnf(err.Error())
- return loadConfigError(err)
+ return nil, err
}
// Make sure a valid average price mode is given.
@@ -169,15 +192,18 @@ func loadTicketBuyerConfig(appDataDir string) (*ticketBuyerConfig, error) {
str := "%s: Invalid average price mode '%s'"
err := fmt.Errorf(str, "loadTicketBuyerConfig", cfg.AvgPriceMode)
log.Warnf(err.Error())
- return loadConfigError(err)
+ return nil, err
}
- return &cfg, nil
+ return newTicketBuyerConfig(appConfig, &cfg), nil
}
// startTicketPurchase launches ticketbuyer to start purchasing tickets.
func startTicketPurchase(w *wallet.Wallet, dcrdClient *dcrrpcclient.Client,
passphrase []byte, ticketbuyerCfg *ticketbuyer.Config) {
+
+ tkbyLog.Infof("Starting ticket buyer")
+
p, err := ticketbuyer.NewTicketPurchaser(ticketbuyerCfg,
dcrdClient, w, activeNet.Params)
if err != nil {
@@ -198,6 +224,8 @@ func startTicketPurchase(w *wallet.Wallet, dcrdClient *dcrrpcclient.Client,
go pm.NotificationHandler()
go func() {
dcrdClient.WaitForShutdown()
+
+ tkbyLog.Infof("Stopping ticket buyer")
n.Done()
close(quit)
}()
@@ -46,11 +46,9 @@ out:
for {
select {
case v := <-p.ntfnChan:
- if p.w.Generate() {
- if v != nil {
- for _, block := range v.AttachedBlocks {
- go p.purchase(int64(block.Height))
- }
+ if v != nil {
+ for _, block := range v.AttachedBlocks {
+ go p.purchase(int64(block.Height))
}
}
case <-p.quit:
View
@@ -18,6 +18,7 @@ import (
"github.com/decred/dcrutil"
"github.com/decred/dcrwallet/chain"
"github.com/decred/dcrwallet/waddrmgr"
+ "github.com/decred/dcrwallet/wallet/txauthor"
"github.com/decred/dcrwallet/wallet/txrules"
"github.com/decred/dcrwallet/walletdb"
"github.com/decred/dcrwallet/wstakemgr"
@@ -94,8 +95,8 @@ func (w *Wallet) handleChainNotifications(chainClient *chain.RPCClient) {
// handleTicketPurchases autopurchases stake tickets for the wallet
// if stake mining is enabled.
func (w *Wallet) handleTicketPurchases(dbtx walletdb.ReadWriteTx, currentHeight int32) error {
- // Nothing to do when stake mining is disabled.
- if !w.StakeMiningEnabled {
+ // Nothing to do when ticket purchasing is disabled.
+ if !w.TicketPurchasingEnabled() {
return nil
}
@@ -301,6 +302,20 @@ func (w *Wallet) onBlockConnected(dbtx walletdb.ReadWriteTx, serializedBlockHead
height := int32(blockHeader.Height)
+ // Handle automatic ticket purchasing if enabled. This function should
+ // not error due to an error purchasing tickets (several tickets may be
+ // have been purchased and successfully published, as well as addresses
+ // created and used), so just log it instead.
+ err = w.handleTicketPurchases(dbtx, height)
+ switch err.(type) {
+ case nil:
+ case txauthor.InsufficientFundsError:
+ log.Debugf("Insufficient funds to auto-purchase maximum number " +
+ "of tickets")
+ default:
+ log.Errorf("Failed to perform automatic ticket purchasing: %v", err)
+ }
+
// Prune all expired transactions and all stake tickets that no longer
// meet the minimum stake difficulty.
txmgrNs := dbtx.ReadWriteBucket(wtxmgrNamespaceKey)
@@ -912,6 +927,10 @@ func (w *Wallet) handleChainVotingNotifications(chainClient *chain.RPCClient) {
func (w *Wallet) handleWinningTickets(dbtx walletdb.ReadWriteTx, blockHash *chainhash.Hash,
blockHeight int64, tickets []*chainhash.Hash) error {
+ if !w.votingEnabled {
+ return nil
+ }
+
addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey)
stakemgrNs := dbtx.ReadWriteBucket(wstakemgrNamespaceKey)
txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey)
@@ -930,8 +949,7 @@ func (w *Wallet) handleWinningTickets(dbtx walletdb.ReadWriteTx, blockHash *chai
w.SetCurrentVotingInfo(blockHash, blockHeight, tickets)
}
- if blockHeight >= w.chainParams.StakeValidationHeight-1 &&
- w.StakeMiningEnabled {
+ if blockHeight >= w.chainParams.StakeValidationHeight-1 {
ntfns, err := w.StakeMgr.HandleWinningTicketsNtfn(
stakemgrNs,
addrmgrNs,
@@ -969,12 +987,11 @@ func (w *Wallet) handleMissedTickets(dbtx walletdb.ReadWriteTx, blockHash *chain
stakemgrNs := dbtx.ReadWriteBucket(wstakemgrNamespaceKey)
addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey)
- if !w.StakeMiningEnabled {
+ if !w.votingEnabled {
return nil
}
- if blockHeight >= w.chainParams.StakeValidationHeight+1 &&
- w.StakeMiningEnabled {
+ if blockHeight >= w.chainParams.StakeValidationHeight+1 {
ntfns, err := w.StakeMgr.HandleMissedTicketsNtfn(stakemgrNs, addrmgrNs,
blockHash, blockHeight, tickets, w.AllowHighFees)
Oops, something went wrong.

0 comments on commit 879e068

Please sign in to comment.