// StakePoolTicketFee determines the stake pool ticket fee for a given ticket // from the passed percentage. Pool fee as a percentage is truncated from 0.01% // to 100.00%. This all must be done with integers, so bear with the big.Int // usage below. // // See the included doc.go of this package for more information about the // calculation of this fee. func StakePoolTicketFee(stakeDiff dcrutil.Amount, relayFee dcrutil.Amount, height int32, poolFee float64, params *chaincfg.Params) dcrutil.Amount { // Shift the decimal two places, e.g. 1.00% // to 100. This assumes that the proportion // is already multiplied by 100 to give a // percentage, thus making the entirety // be a multiplication by 10000. poolFeeAbs := math.Floor(poolFee * 100.0) poolFeeInt := int64(poolFeeAbs) // Subsidy is fetched from the blockchain package, then // pushed forward a number of adjustment periods for // compensation in gradual subsidy decay. Recall that // the average time to claiming 50% of the tickets as // votes is the approximately the same as the ticket // pool size (params.TicketPoolSize), so take the // ceiling of the ticket pool size divided by the // reduction interval. adjs := int(math.Ceil(float64(params.TicketPoolSize) / float64(params.ReductionInterval))) initSubsidyCacheOnce.Do(func() { subsidyCache = blockchain.NewSubsidyCache(int64(height), params) }) subsidy := blockchain.CalcStakeVoteSubsidy(subsidyCache, int64(height), params) for i := 0; i < adjs; i++ { subsidy *= 100 subsidy /= 101 } // The numerator is (p*10000*s*(v+z)) << 64. shift := uint(64) s := new(big.Int).SetInt64(subsidy) v := new(big.Int).SetInt64(int64(stakeDiff)) z := new(big.Int).SetInt64(int64(relayFee)) num := new(big.Int).SetInt64(poolFeeInt) num.Mul(num, s) vPlusZ := new(big.Int).Add(v, z) num.Mul(num, vPlusZ) num.Lsh(num, shift) // The denominator is 10000*(s+v). // The extra 10000 above cancels out. den := new(big.Int).Set(s) den.Add(den, v) den.Mul(den, new(big.Int).SetInt64(10000)) // Divide and shift back. num.Div(num, den) num.Rsh(num, shift) return dcrutil.Amount(num.Int64()) }
func TestBlockSubsidy(t *testing.T) { mainnet := &chaincfg.MainNetParams subsidyCache := blockchain.NewSubsidyCache(0, mainnet) totalSubsidy := mainnet.BlockOneSubsidy() for i := int64(0); ; i++ { // Genesis block or first block. if i == 0 || i == 1 { continue } if i%mainnet.ReductionInterval == 0 { numBlocks := mainnet.ReductionInterval // First reduction internal, which is reduction interval - 2 // to skip the genesis block and block one. if i == mainnet.ReductionInterval { numBlocks -= 2 } height := i - numBlocks work := blockchain.CalcBlockWorkSubsidy(subsidyCache, height, mainnet.TicketsPerBlock, mainnet) stake := blockchain.CalcStakeVoteSubsidy(subsidyCache, height, mainnet) * int64(mainnet.TicketsPerBlock) tax := blockchain.CalcBlockTaxSubsidy(subsidyCache, height, mainnet.TicketsPerBlock, mainnet) if (work + stake + tax) == 0 { break } totalSubsidy += ((work + stake + tax) * numBlocks) // First reduction internal, subtract the stake subsidy for // blocks before the staking system is enabled. if i == mainnet.ReductionInterval { totalSubsidy -= stake * (mainnet.StakeValidationHeight - 2) } } } if totalSubsidy != 2099999999800912 { t.Errorf("Bad total subsidy; want 2099999999800912, got %v", totalSubsidy) } }
// generateVote creates a new SSGen given a header hash, height, sstx // tx hash, and votebits. func (s *StakeStore) generateVote(ns walletdb.ReadWriteBucket, waddrmgrNs walletdb.ReadBucket, blockHash *chainhash.Hash, height int64, sstxHash *chainhash.Hash, defaultVoteBits stake.VoteBits, allowHighFees bool) (*StakeNotification, error) { // 1. Fetch the SStx, then calculate all the values we'll need later for // the generation of the SSGen tx outputs. sstxRecord, err := s.getSStx(ns, sstxHash) if err != nil { return nil, err } sstx := sstxRecord.tx sstxMsgTx := sstx.MsgTx() // The legacy wallet didn't store anything about the voteBits to use. // In the case we're loading a legacy wallet and the voteBits are // unset, just use the default voteBits as set by the user. voteBits := defaultVoteBits if sstxRecord.voteBitsSet { voteBits.Bits = sstxRecord.voteBits voteBits.ExtendedBits = sstxRecord.voteBitsExt } // Store the sstx pubkeyhashes and amounts as found in the transaction // outputs. // TODO Get information on the allowable fee range for the vote // and check to make sure we don't overflow that. ssgenPayTypes, ssgenPkhs, sstxAmts, _, _, _ := stake.TxSStxStakeOutputInfo(sstxMsgTx) // Get the current reward. initSudsidyCacheOnce.Do(func() { subsidyCache = blockchain.NewSubsidyCache(height, s.Params) }) stakeVoteSubsidy := blockchain.CalcStakeVoteSubsidy(subsidyCache, height, s.Params) // Calculate the output values from this data. ssgenCalcAmts := stake.CalculateRewards(sstxAmts, sstxMsgTx.TxOut[0].Value, stakeVoteSubsidy) subsidyCache = blockchain.NewSubsidyCache(height, s.Params) // 2. Add all transaction inputs to a new transaction after performing // some validity checks. First, add the stake base, then the OP_SSTX // tagged output. msgTx := wire.NewMsgTx() // Stakebase. stakeBaseOutPoint := wire.NewOutPoint(&chainhash.Hash{}, uint32(0xFFFFFFFF), wire.TxTreeRegular) txInStakeBase := wire.NewTxIn(stakeBaseOutPoint, []byte{}) msgTx.AddTxIn(txInStakeBase) // Add the subsidy amount into the input. msgTx.TxIn[0].ValueIn = stakeVoteSubsidy // SStx tagged output as an OutPoint. prevOut := wire.NewOutPoint(sstxHash, 0, // Index 0 1) // Tree stake txIn := wire.NewTxIn(prevOut, []byte{}) msgTx.AddTxIn(txIn) // 3. Add the OP_RETURN null data pushes of the block header hash, // the block height, and votebits, then add all the OP_SSGEN tagged // outputs. // // Block reference output. blockRefScript, err := txscript.GenerateSSGenBlockRef(*blockHash, uint32(height)) if err != nil { return nil, err } blockRefOut := wire.NewTxOut(0, blockRefScript) msgTx.AddTxOut(blockRefOut) // Votebits output. blockVBScript, err := generateVoteScript(voteBits) if err != nil { return nil, err } blockVBOut := wire.NewTxOut(0, blockVBScript) msgTx.AddTxOut(blockVBOut) // Add all the SSGen-tagged transaction outputs to the transaction after // performing some validity checks. for i, ssgenPkh := range ssgenPkhs { // Create a new script which pays to the provided address specified in // the original ticket tx. var ssgenOutScript []byte switch ssgenPayTypes[i] { case false: // P2PKH ssgenOutScript, err = txscript.PayToSSGenPKHDirect(ssgenPkh) if err != nil { return nil, err } case true: // P2SH ssgenOutScript, err = txscript.PayToSSGenSHDirect(ssgenPkh) if err != nil { return nil, err } } // Add the txout to our SSGen tx. txOut := wire.NewTxOut(ssgenCalcAmts[i], ssgenOutScript) msgTx.AddTxOut(txOut) } // Check to make sure our SSGen was created correctly. _, err = stake.IsSSGen(msgTx) if err != nil { return nil, err } // Sign the transaction. err = s.SignVRTransaction(waddrmgrNs, msgTx, sstx, true) if err != nil { return nil, err } // Store the information about the SSGen. hash := msgTx.TxSha() err = s.insertSSGen(ns, blockHash, height, &hash, voteBits.Bits, sstx.Sha()) if err != nil { return nil, err } // Send the transaction. ssgenSha, err := s.chainSvr.SendRawTransaction(msgTx, allowHighFees) if err != nil { return nil, err } log.Debugf("Generated SSGen %v, voting on block %v at height %v. "+ "The ticket used to generate the SSGen was %v.", ssgenSha, blockHash, height, sstxHash) // Generate a notification to return. ntfn := &StakeNotification{ TxType: int8(stake.TxTypeSSGen), TxHash: *ssgenSha, BlockHash: *blockHash, Height: int32(height), Amount: 0, SStxIn: *sstx.Sha(), VoteBits: voteBits.Bits, } return ntfn, nil }