From 5832a21db625bfa98a3b7909bb6996dd1fd2e4d3 Mon Sep 17 00:00:00 2001
From: Jimmy Hu <jimmy.hu@dexon.org>
Date: Mon, 25 Feb 2019 18:27:34 +0800
Subject: core: Fixed gas price (#205)

* core/vm: update abi
* core/vm: add MinGasPrice to gov
* params: Add MinGasPrice to Config
* dex: SuggestPrice from Governance
* test: add minGasPrice to genesis.json
* core: check underpriced tx
* dex: verify with gas price
---
 core/governance.go               |  4 ++++
 core/tx_pool.go                  | 44 +++++++++++++++++++++++++++++++++++++++-
 core/tx_pool_test.go             |  4 ++++
 core/vm/oracle_contract_abi.go   | 18 ++++++++++++++++
 core/vm/oracle_contracts.go      | 16 +++++++++++++++
 core/vm/oracle_contracts_test.go | 12 ++++++++++-
 6 files changed, 96 insertions(+), 2 deletions(-)

(limited to 'core')

diff --git a/core/governance.go b/core/governance.go
index 12124760e..9929867c9 100644
--- a/core/governance.go
+++ b/core/governance.go
@@ -134,3 +134,7 @@ func (g *Governance) IsDKGFinal(round uint64) bool {
 	count := headHelper.DKGFinalizedsCount(big.NewInt(int64(round))).Uint64()
 	return count >= threshold
 }
+
+func (g *Governance) MinGasPrice(round uint64) *big.Int {
+	return g.GetGovStateHelperAtRound(round).MinGasPrice()
+}
diff --git a/core/tx_pool.go b/core/tx_pool.go
index 871b50be1..0a1b3f132 100644
--- a/core/tx_pool.go
+++ b/core/tx_pool.go
@@ -24,10 +24,13 @@ import (
 	"sync"
 	"time"
 
+	dexCore "github.com/dexon-foundation/dexon-consensus/core"
+
 	"github.com/dexon-foundation/dexon/common"
 	"github.com/dexon-foundation/dexon/common/prque"
 	"github.com/dexon-foundation/dexon/core/state"
 	"github.com/dexon-foundation/dexon/core/types"
+	"github.com/dexon-foundation/dexon/core/vm"
 	"github.com/dexon-foundation/dexon/event"
 	"github.com/dexon-foundation/dexon/log"
 	"github.com/dexon-foundation/dexon/metrics"
@@ -115,6 +118,7 @@ const (
 type blockChain interface {
 	CurrentBlock() *types.Block
 	GetBlock(hash common.Hash, number uint64) *types.Block
+	GetBlockByNumber(number uint64) *types.Block
 	StateAt(root common.Hash) (*state.StateDB, error)
 
 	SubscribeChainHeadEvent(ch chan<- ChainHeadEvent) event.Subscription
@@ -206,6 +210,7 @@ type TxPool struct {
 	chainconfig  *params.ChainConfig
 	chain        blockChain
 	gasPrice     *big.Int
+	govGasPrice  *big.Int
 	txFeed       event.Feed
 	scope        event.SubscriptionScope
 	chainHeadCh  chan ChainHeadEvent
@@ -385,6 +390,28 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) {
 	pool.currentState = statedb
 	pool.pendingState = state.ManageState(statedb)
 	pool.currentMaxGas = newHead.GasLimit
+	if oldHead == nil || oldHead.Round != newHead.Round {
+		round := newHead.Round
+		if round < dexCore.ConfigRoundShift {
+			round = 0
+		} else {
+			round -= dexCore.ConfigRoundShift
+		}
+		state := &vm.GovernanceStateHelper{StateDB: statedb}
+		height := state.RoundHeight(new(big.Int).SetUint64((round))).Uint64()
+		block := pool.chain.GetBlockByNumber(height)
+		if block == nil {
+			log.Error("Failed to get block", "round", round, "height", height)
+			panic("cannot get config for new round's min gas price")
+		}
+		configState, err := pool.chain.StateAt(block.Header().Root)
+		if err != nil {
+			log.Error("Failed to get txpool state for min gas price", "err", err)
+			panic("cannot get state for new round's min gas price")
+		}
+		govState := &vm.GovernanceStateHelper{StateDB: configState}
+		pool.setGovPrice(govState.MinGasPrice())
+	}
 
 	// validate the pool of pending transactions, this will remove
 	// any transactions that have been included in the block or
@@ -438,10 +465,21 @@ func (pool *TxPool) SetGasPrice(price *big.Int) {
 	defer pool.mu.Unlock()
 
 	pool.gasPrice = price
+	pool.removeUnderpricedTx(price)
+	log.Info("Transaction pool price threshold updated", "price", price)
+}
+
+// setGovPrice updates the minimum price required by the transaction pool for a
+// new transaction, and drops all transactions below this threshold.
+func (pool *TxPool) setGovPrice(price *big.Int) {
+	pool.govGasPrice = price
+	pool.removeUnderpricedTx(price)
+}
+
+func (pool *TxPool) removeUnderpricedTx(price *big.Int) {
 	for _, tx := range pool.priced.Cap(price, pool.locals) {
 		pool.removeTx(tx.Hash(), false)
 	}
-	log.Info("Transaction pool price threshold updated", "price", price)
 }
 
 // State returns the virtual managed state of the transaction pool.
@@ -551,6 +589,10 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
 	if err != nil {
 		return ErrInvalidSender
 	}
+	// Drop all transactions under governance minimum gas price.
+	if pool.govGasPrice.Cmp(tx.GasPrice()) > 0 {
+		return ErrUnderpriced
+	}
 	// Drop non-local transactions under our own minimal accepted gas price
 	local = local || pool.locals.contains(from) // account may be local even if the transaction arrived from the network
 	if !local && pool.gasPrice.Cmp(tx.GasPrice()) > 0 {
diff --git a/core/tx_pool_test.go b/core/tx_pool_test.go
index 96151850d..70d4a351a 100644
--- a/core/tx_pool_test.go
+++ b/core/tx_pool_test.go
@@ -61,6 +61,10 @@ func (bc *testBlockChain) GetBlock(hash common.Hash, number uint64) *types.Block
 	return bc.CurrentBlock()
 }
 
+func (bc *testBlockChain) GetBlockByNumber(uint64) *types.Block {
+	return bc.CurrentBlock()
+}
+
 func (bc *testBlockChain) StateAt(common.Hash) (*state.StateDB, error) {
 	return bc.statedb, nil
 }
diff --git a/core/vm/oracle_contract_abi.go b/core/vm/oracle_contract_abi.go
index fde973203..ff4a94b29 100644
--- a/core/vm/oracle_contract_abi.go
+++ b/core/vm/oracle_contract_abi.go
@@ -563,6 +563,20 @@ const GovernanceABIJSON = `
     "stateMutability": "view",
     "type": "function"
   },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "minGasPrice",
+    "outputs": [
+      {
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
   {
     "constant": true,
     "inputs": [],
@@ -855,6 +869,10 @@ const GovernanceABIJSON = `
       {
         "name": "FineValues",
         "type": "uint256[]"
+      },
+      {
+        "name": "MinGasPrice",
+        "type": "uint256"
       }
     ],
     "name": "updateConfiguration",
diff --git a/core/vm/oracle_contracts.go b/core/vm/oracle_contracts.go
index 1eeb57a5a..826e0396c 100644
--- a/core/vm/oracle_contracts.go
+++ b/core/vm/oracle_contracts.go
@@ -83,6 +83,7 @@ const (
 	fineValuesLoc
 	finedRecordsLoc
 	dkgResetCountLoc
+	minGasPriceLoc
 )
 
 func publicKeyToNodeID(pkBytes []byte) (Bytes32, error) {
@@ -894,6 +895,11 @@ func (s *GovernanceStateHelper) IncDKGResetCount(round *big.Int) {
 	s.setStateBigInt(loc, new(big.Int).Add(count, big.NewInt(1)))
 }
 
+// uint256 public minGasPrice;
+func (s *GovernanceStateHelper) MinGasPrice() *big.Int {
+	return s.getStateBigInt(big.NewInt(minGasPriceLoc))
+}
+
 // Stake is a helper function for creating genesis state.
 func (s *GovernanceStateHelper) Stake(
 	addr common.Address, publicKey []byte, staked *big.Int,
@@ -948,6 +954,7 @@ func (s *GovernanceStateHelper) Configuration() *params.DexconConfig {
 		RoundLength:       s.getStateBigInt(big.NewInt(roundLengthLoc)).Uint64(),
 		MinBlockInterval:  s.getStateBigInt(big.NewInt(minBlockIntervalLoc)).Uint64(),
 		FineValues:        s.FineValues(),
+		MinGasPrice:       s.getStateBigInt(big.NewInt(minGasPriceLoc)),
 	}
 }
 
@@ -966,6 +973,7 @@ func (s *GovernanceStateHelper) UpdateConfiguration(cfg *params.DexconConfig) {
 	s.setStateBigInt(big.NewInt(roundLengthLoc), big.NewInt(int64(cfg.RoundLength)))
 	s.setStateBigInt(big.NewInt(minBlockIntervalLoc), big.NewInt(int64(cfg.MinBlockInterval)))
 	s.SetFineValues(cfg.FineValues)
+	s.setStateBigInt(big.NewInt(minGasPriceLoc), cfg.MinGasPrice)
 }
 
 type rawConfigStruct struct {
@@ -979,6 +987,7 @@ type rawConfigStruct struct {
 	RoundLength      *big.Int
 	MinBlockInterval *big.Int
 	FineValues       []*big.Int
+	MinGasPrice      *big.Int
 }
 
 // UpdateConfigurationRaw updates system configuration.
@@ -993,6 +1002,7 @@ func (s *GovernanceStateHelper) UpdateConfigurationRaw(cfg *rawConfigStruct) {
 	s.setStateBigInt(big.NewInt(roundLengthLoc), cfg.RoundLength)
 	s.setStateBigInt(big.NewInt(minBlockIntervalLoc), cfg.MinBlockInterval)
 	s.SetFineValues(cfg.FineValues)
+	s.setStateBigInt(big.NewInt(minGasPriceLoc), cfg.MinGasPrice)
 }
 
 // event ConfigurationChanged();
@@ -2227,6 +2237,12 @@ func (g *GovernanceContract) Run(evm *EVM, input []byte, contract *Contract) (re
 			return nil, errExecutionReverted
 		}
 		return res, nil
+	case "minGasPrice":
+		res, err := method.Outputs.Pack(g.state.MinGasPrice())
+		if err != nil {
+			return nil, errExecutionReverted
+		}
+		return res, nil
 	case "miningVelocity":
 		res, err := method.Outputs.Pack(g.state.MiningVelocity())
 		if err != nil {
diff --git a/core/vm/oracle_contracts_test.go b/core/vm/oracle_contracts_test.go
index dd17dddea..4980d4b77 100644
--- a/core/vm/oracle_contracts_test.go
+++ b/core/vm/oracle_contracts_test.go
@@ -654,7 +654,8 @@ func (g *OracleContractsTestSuite) TestUpdateConfiguration() {
 		big.NewInt(4),
 		big.NewInt(600),
 		big.NewInt(900),
-		[]*big.Int{big.NewInt(1), big.NewInt(1), big.NewInt(1)})
+		[]*big.Int{big.NewInt(1), big.NewInt(1), big.NewInt(1)},
+		big.NewInt(2e9))
 	g.Require().NoError(err)
 
 	// Call with non-owner.
@@ -770,6 +771,15 @@ func (g *OracleContractsTestSuite) TestConfigurationReading() {
 	err = GovernanceABI.ABI.Unpack(&value, "minBlockInterval", res)
 	g.Require().NoError(err)
 	g.Require().Equal(g.config.MinBlockInterval, value.Uint64())
+
+	// MinGasPrice.
+	input, err = GovernanceABI.ABI.Pack("minGasPrice")
+	g.Require().NoError(err)
+	res, err = g.call(GovernanceContractAddress, addr, input, big.NewInt(0))
+	g.Require().NoError(err)
+	err = GovernanceABI.ABI.Unpack(&value, "minGasPrice", res)
+	g.Require().NoError(err)
+	g.Require().Equal(g.config.MinGasPrice, value)
 }
 
 func (g *OracleContractsTestSuite) TestReportForkVote() {
-- 
cgit v1.2.3