Skip to content

Commit

Permalink
Added utxo fee calculation to worker and headerchain
Browse files Browse the repository at this point in the history
  • Loading branch information
0xalank authored and jdowning100 committed Feb 21, 2024
1 parent 61f019a commit 615f49b
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 44 deletions.
12 changes: 12 additions & 0 deletions consensus/misc/rewards.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,18 @@ func CalculateRewardForQi(header *types.Header) map[uint8]uint8 {
return findMinDenominations(rewardFromDifficulty)
}

func CalculateRewardForQiWithFees(header *types.Header, fees *big.Int) map[uint8]uint8 {
rewardFromDifficulty := new(big.Int).Add(types.Denominations[types.MaxDenomination], types.Denominations[10])
reward := new(big.Int).Add(rewardFromDifficulty, fees)
return findMinDenominations(reward)
}

func CalculateRewardForQiWithFeesBigInt(header *types.Header, fees *big.Int) *big.Int {
rewardFromDifficulty := new(big.Int).Add(types.Denominations[types.MaxDenomination], types.Denominations[10])
reward := new(big.Int).Add(rewardFromDifficulty, fees)
return reward
}

// findMinDenominations finds the minimum number of denominations to make up the reward
func findMinDenominations(reward *big.Int) map[uint8]uint8 {
// Store the count of each denomination used (map denomination to count)
Expand Down
74 changes: 51 additions & 23 deletions core/headerchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,9 @@ func (hc *HeaderChain) SetCurrentState(head *types.Header) error {
return nil
}

// This function performs all of the logic to update and/or re-org the UTXO set with the given header.
// Currently, the UTXO set follows the current state/pending header, not the canonical current header.
// An improvement could be to use a batch instead of the db in case of an error during re-org requiring a reset.
// This function MUST be called with the header mutex locked.
func (hc *HeaderChain) setCurrentUTXOSet(head *types.Header) error {
nodeCtx := hc.NodeCtx()
Expand All @@ -465,7 +468,7 @@ func (hc *HeaderChain) setCurrentUTXOSet(head *types.Header) error {
return err
}

err = hc.verifyInputUtxos(utxoView, block, hc.pool.signer)
totalFees, err := hc.verifyInputUtxos(utxoView, block, hc.pool.signer)
if err != nil {
return err
}
Expand All @@ -474,11 +477,21 @@ func (hc *HeaderChain) setCurrentUTXOSet(head *types.Header) error {
// provably unspendable as available utxos. Also, the passed
// spent txos slice is updated to contain an entry for each
// spent txout in the order each transaction spends them.
err = utxoView.ConnectTransaction(tx, block, &stxos)
err = utxoView.ConnectTransaction(tx, block.Header(), &stxos)
if err != nil {
return fmt.Errorf("could not apply tx %v: %w", tx.Hash().Hex(), err)
}
}
if types.IsCoinBaseTx(block.QiTransactions()[0]) {
totalCoinbaseOut := big.NewInt(0)
for _, txOut := range block.QiTransactions()[0].TxOut() {
totalCoinbaseOut.Add(totalCoinbaseOut, types.Denominations[txOut.Denomination])
}
maxCoinbaseOut := misc.CalculateRewardForQiWithFeesBigInt(block.Header(), totalFees)
if totalCoinbaseOut.Cmp(maxCoinbaseOut) == 1 {
return fmt.Errorf("coinbase output value of %v is higher than expected value of %v", totalCoinbaseOut, maxCoinbaseOut)
}
}
// Write the updated utxo set and spent utxos to the database
hc.WriteUtxoViewpoint(utxoView)
rawdb.WriteSpentUTXOs(hc.bc.db, block.Hash(), &stxos)
Expand Down Expand Up @@ -539,7 +552,7 @@ func (hc *HeaderChain) setCurrentUTXOSet(head *types.Header) error {
return err
}

err = hc.verifyInputUtxos(utxoView, block, hc.pool.signer)
totalFees, err := hc.verifyInputUtxos(utxoView, block, hc.pool.signer)
if err != nil {
return err
}
Expand All @@ -548,11 +561,22 @@ func (hc *HeaderChain) setCurrentUTXOSet(head *types.Header) error {
// provably unspendable as available utxos. Also, the passed
// spent txos slice is updated to contain an entry for each
// spent txout in the order each transaction spends them.
err = utxoView.ConnectTransaction(tx, block, &stxos)
err = utxoView.ConnectTransaction(tx, block.Header(), &stxos)
if err != nil {
return fmt.Errorf("could not apply tx %v: %w", tx.Hash().Hex(), err)
}
}
if types.IsCoinBaseTx(block.QiTransactions()[0]) {
totalCoinbaseOut := big.NewInt(0)
for _, txOut := range block.QiTransactions()[0].TxOut() {
// Should we limit the number of outputs?
totalCoinbaseOut.Add(totalCoinbaseOut, types.Denominations[txOut.Denomination])
}
maxCoinbaseOut := misc.CalculateRewardForQiWithFeesBigInt(block.Header(), totalFees)
if totalCoinbaseOut.Cmp(maxCoinbaseOut) == 1 {
return fmt.Errorf("coinbase output value of %v is higher than expected value of %v", totalCoinbaseOut, maxCoinbaseOut)
}
}
// Write the updated utxo set and spent utxos to the database
hc.WriteUtxoViewpoint(utxoView)
rawdb.WriteSpentUTXOs(hc.bc.db, block.Hash(), &stxos)
Expand Down Expand Up @@ -1250,7 +1274,7 @@ func (hc *HeaderChain) fetchInputUtxos(view *types.UtxoViewpoint, block *types.B
i >= inFlightIndex {

originTx := transactions[inFlightIndex]
view.AddTxOuts(originTx, block)
view.AddTxOuts(originTx, block.Header())
continue
}

Expand All @@ -1268,37 +1292,41 @@ func (hc *HeaderChain) fetchInputUtxos(view *types.UtxoViewpoint, block *types.B
return hc.FetchUtxosMain(view, needed)
}

func (hc *HeaderChain) verifyInputUtxos(view *types.UtxoViewpoint, block *types.Block, signer types.Signer) error { // should this be used instead of Verify
func (hc *HeaderChain) verifyInputUtxos(view *types.UtxoViewpoint, block *types.Block, signer types.Signer) (*big.Int, error) { // should this be used instead of Verify

transactions := block.QiTransactions()
if types.IsCoinBaseTx(transactions[0]) {
transactions = transactions[1:]
}

totalFees := big.NewInt(0)

for _, tx := range transactions {

_, err := types.CheckTransactionInputs(tx, view)
fee, err := types.CheckTransactionInputs(tx, view)
if err != nil {
return fmt.Errorf("could not apply tx %v: %w", tx.Hash().Hex(), err)
return nil, fmt.Errorf("could not apply tx %v: %w", tx.Hash().Hex(), err)
}
totalFees.Add(totalFees, fee)

pubKeys := make([]*btcec.PublicKey, 0)
for _, txIn := range tx.TxIn() {

entry := view.LookupEntry(txIn.PreviousOutPoint)
if entry == nil {
return errors.New("utxo not found " + txIn.PreviousOutPoint.TxHash.String())
return nil, errors.New("utxo not found " + txIn.PreviousOutPoint.TxHash.String())
}

// Verify the pubkey
address := pubKeyToAddress(txIn.PubKey, hc.NodeLocation())
entryAddr := common.BytesToAddress(entry.Address, hc.NodeLocation())
if !address.Equal(entryAddr) {
return errors.New("invalid address")
return nil, errors.New("invalid address")
}

pubKey, err := btcec.ParsePubKey(txIn.PubKey)
if err != nil {
return err
return nil, err
}
pubKeys = append(pubKeys, pubKey)
}
Expand All @@ -1309,7 +1337,7 @@ func (hc *HeaderChain) verifyInputUtxos(view *types.UtxoViewpoint, block *types.
pubKeys, false,
)
if err != nil {
return err
return nil, err
}
finalKey = aggKey.FinalKey
} else {
Expand All @@ -1318,12 +1346,12 @@ func (hc *HeaderChain) verifyInputUtxos(view *types.UtxoViewpoint, block *types.

txDigestHash := signer.Hash(tx)
if !tx.GetSchnorrSignature().Verify(txDigestHash[:], finalKey) {
return errors.New("invalid signature")
return nil, errors.New("invalid signature")
}

}

return nil
return totalFees, nil
}

// writeUtxoViewpoint updates the utxo set in the database based on the provided utxo view contents and state. In
Expand Down Expand Up @@ -1373,28 +1401,27 @@ func (hc *HeaderChain) DeleteUtxoViewpoint(hash common.Hash) error {
return nil
}

// createCoinbaseTx returns a coinbase transaction paying an appropriate subsidy
// createCoinbaseTxWithFees returns a coinbase transaction paying an appropriate subsidy
// based on the passed block height to the provided address. When the address
// is nil, the coinbase transaction will instead be redeemable by anyone.
//
// See the comment for NewBlockTemplate for more information about why the nil
// address handling is useful.
func createCoinbaseTx(header *types.Header) (*types.Transaction, error) {
parentHash := header.ParentHash(header.Location().Context())
func createCoinbaseTxWithFees(header *types.Header, fees *big.Int) (*types.Transaction, error) {
parentHash := header.ParentHash(header.Location().Context()) // all blocks should have zone location and context
in := types.TxIn{
// Coinbase transactions have no inputs, so previous outpoint is
// zero hash and max index.
PreviousOutPoint: *types.NewOutPoint(&parentHash,
types.MaxPrevOutIndex),
PreviousOutPoint: *types.NewOutPoint(&parentHash, types.MaxPrevOutIndex),
}

denominations := misc.CalculateRewardForQi(header)
denominations := misc.CalculateRewardForQiWithFees(header, fees)
fmt.Printf("denominations: %v\n", denominations)

outs := make([]types.TxOut, 0, len(denominations))

// Iterate over the denominations in descending order (by key)
for i := types.MaxDenomination; i >= 0; i-- {
for i := 15; i >= 0; i-- {
// If the denomination count is zero, skip it
if denominations[uint8(i)] == 0 {
continue
Expand All @@ -1409,12 +1436,13 @@ func createCoinbaseTx(header *types.Header) (*types.Transaction, error) {
}
}

utxo := &types.QiTx{
qiTx := &types.QiTx{
TxIn: []types.TxIn{in},
TxOut: outs,
}

tx := types.NewTx(utxo)
tx := types.NewTx(qiTx)
fmt.Println("coinbase tx", tx.Hash().Hex())
return tx, nil
}

Expand Down
7 changes: 7 additions & 0 deletions core/types/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -903,6 +903,13 @@ func (t *TransactionsByPriceAndNonce) Peek() *Transaction {
return t.heads[0].tx
}

func (t *TransactionsByPriceAndNonce) GetFee() *big.Int {
if len(t.heads) == 0 {
return nil
}
return t.heads[0].minerFee
}

// Shift replaces the current best head with the next one from the same account.
func (t *TransactionsByPriceAndNonce) Shift(acc common.AddressBytes, sort bool) {
if txs, ok := t.txs[acc]; ok && len(txs) > 0 {
Expand Down
11 changes: 7 additions & 4 deletions core/types/utxo_validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,18 @@ import (
//
// This function only differs from IsCoinBase in that it works with a raw wire
// transaction as opposed to a higher level util transaction.
func IsCoinBaseTx(msgTx *Transaction) bool {
func IsCoinBaseTx(tx *Transaction) bool {
if tx == nil || tx.inner == nil || tx.Type() != QiTxType {
return false
}
// A coin base must only have one transaction input.
if len(msgTx.inner.txIn()) != 1 {
if len(tx.inner.txIn()) != 1 {
return false
}

// The previous output of a coin base must have a max value index and
// a non-zero hash.
prevOut := &msgTx.inner.txIn()[0].PreviousOutPoint
prevOut := &tx.inner.txIn()[0].PreviousOutPoint
if (prevOut.Index != math.MaxUint32 || prevOut.TxHash == common.Hash{}) {
return false
}
Expand All @@ -47,7 +50,7 @@ func IsCoinBaseTx(msgTx *Transaction) bool {
func CheckTransactionInputs(tx *Transaction, utxoView *UtxoViewpoint) (*big.Int, error) {
// Coinbase transactions have no inputs.
if IsCoinBaseTx(tx) {
return nil, nil
return big.NewInt(0), nil
}

totalQitIn := big.NewInt(0)
Expand Down
18 changes: 9 additions & 9 deletions core/types/utxo_viewpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,13 +160,13 @@ func (view *UtxoViewpoint) addTxOut(outpoint OutPoint, txOut *TxOut, isCoinBase
// unspendable to the view. When the view already has entries for any of the
// outputs, they are simply marked unspent. All fields will be updated for
// existing entries since it's possible it has changed during a reorg.
func (view *UtxoViewpoint) AddTxOuts(tx *Transaction, block *Block) {
func (view *UtxoViewpoint) AddTxOuts(tx *Transaction, header *Header) {
// Loop all of the transaction outputs and add those which are not
// provably unspendable.
isCoinBase := IsCoinBaseTx(tx)
var prevOut OutPoint
if isCoinBase {
prevOut = OutPoint{TxHash: block.ParentHash(view.Location.Context())}
prevOut = OutPoint{TxHash: header.ParentHash(header.Location().Context())}
} else {
prevOut = OutPoint{TxHash: tx.Hash()}
}
Expand All @@ -177,15 +177,15 @@ func (view *UtxoViewpoint) AddTxOuts(tx *Transaction, block *Block) {
// same hash. This is allowed so long as the previous
// transaction is fully spent.
prevOut.Index = uint32(txOutIdx)
view.addTxOut(prevOut, &txOut, isCoinBase, block.NumberU64(view.Location.Context()))
view.addTxOut(prevOut, &txOut, isCoinBase, header.NumberU64(view.Location.Context()))
}
}

// NewUtxoViewpoint returns a new empty unspent transaction output view.
func NewUtxoViewpoint(Location common.Location) *UtxoViewpoint {
func NewUtxoViewpoint(location common.Location) *UtxoViewpoint {
return &UtxoViewpoint{
Entries: make(map[OutPoint]*UtxoEntry),
Location: Location,
Location: location,
}
}

Expand All @@ -194,11 +194,11 @@ func NewUtxoViewpoint(Location common.Location) *UtxoViewpoint {
// spent. In addition, when the 'stxos' argument is not nil, it will be updated
// to append an entry for each spent txout. An error will be returned if the
// view does not contain the required utxos.
func (view *UtxoViewpoint) ConnectTransaction(tx *Transaction, block *Block, stxos *[]SpentTxOut) error {
func (view *UtxoViewpoint) ConnectTransaction(tx *Transaction, header *Header, stxos *[]SpentTxOut) error {
// Coinbase transactions don't have any inputs to spend.
if IsCoinBaseTx(tx) {
// Add the transaction's outputs as available utxos.
view.AddTxOuts(tx, block)
view.AddTxOuts(tx, header)
return nil
}

Expand Down Expand Up @@ -232,13 +232,13 @@ func (view *UtxoViewpoint) ConnectTransaction(tx *Transaction, block *Block, stx
}

// Add the transaction's outputs as available utxos.
view.AddTxOuts(tx, block)
view.AddTxOuts(tx, header)
return nil
}

func (view *UtxoViewpoint) ConnectTransactions(block *Block, stxos *[]SpentTxOut) error {
for _, tx := range block.QiTransactions() {
view.ConnectTransaction(tx, block, stxos)
view.ConnectTransaction(tx, block.header, stxos)
}
return nil
}
Expand Down
23 changes: 15 additions & 8 deletions core/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ type environment struct {
header *types.Header
txs []*types.Transaction
etxs []*types.Transaction
utxoFees *big.Int
subManifest types.BlockManifest
receipts []*types.Receipt
uncleMu sync.RWMutex
Expand All @@ -91,6 +92,7 @@ func (env *environment) copy(processingState bool, nodeCtx int) *environment {
etxPLimit: env.etxPLimit,
header: types.CopyHeader(env.header),
receipts: copyReceipts(env.receipts),
utxoFees: new(big.Int).Set(env.utxoFees),
}
if env.gasPool != nil {
gasPool := *env.gasPool
Expand Down Expand Up @@ -543,13 +545,10 @@ func (w *worker) GeneratePendingHeader(block *types.Block, fill bool) (*types.He
}

if nodeCtx == common.ZONE_CTX && w.hc.ProcessingState() {
coinbaseTx, err := createCoinbaseTx(work.header)
if err != nil {
return nil, err
}
work.txs = append(work.txs, coinbaseTx)
work.txs = append(work.txs, types.NewTx(&types.QiTx{})) // placeholder
// Fill pending transactions from the txpool
w.adjustGasLimit(nil, work, block)
work.utxoFees = big.NewInt(0)
if fill {
start := time.Now()
w.fillTransactions(interrupt, work, block)
Expand All @@ -560,6 +559,11 @@ func (w *worker) GeneratePendingHeader(block *types.Block, fill bool) (*types.He
"average": common.PrettyDuration(w.fillTransactionsRollingAverage.Average()),
}).Info("Filled and sorted pending transactions")
}
coinbaseTx, err := createCoinbaseTxWithFees(work.header, work.utxoFees)
if err != nil {
return nil, err
}
work.txs[0] = coinbaseTx
}

// Create a local environment copy, avoid the data race with snapshot state.
Expand Down Expand Up @@ -762,13 +766,16 @@ func (w *worker) commitTransactions(env *environment, txs *types.TransactionsByP
continue
}
txGas := types.CalculateQiTxGas(tx)
gasUsed := env.header.GasUsed()
env.header.SetGasUsed(gasUsed + txGas) // set utxo tx gas to 21000 for now
if err := env.gasPool.SubGas(txGas); err != nil { // set utxo tx gas to 21000 for now
if err := env.gasPool.SubGas(txGas); err != nil {
w.logger.WithField("err", err).Error("UTXO tx gas pool error")
txs.PopNoSort()
continue
}
gasUsed := env.header.GasUsed()
env.header.SetGasUsed(gasUsed + txGas)
if fee := txs.GetFee(); fee != nil {
env.utxoFees.Add(env.utxoFees, fee)
}
env.txs = append(env.txs, tx)
txs.PopNoSort()
continue
Expand Down

0 comments on commit 615f49b

Please sign in to comment.