From eb11e5fb11ec98464059f2594e8babc3698a2fcc Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Tue, 18 Feb 2020 20:16:38 +0300 Subject: [PATCH] core: implement basic policying support, fix #370 Implement mempool and consensus block creation policies, almost the same as SimplePolicy plugin for C# node provides with two caveats: * HighPriorityTxType is not configured and hardcoded to ClaimType * BlockedAccounts are not supported Other than that it allows us to run successfuly as testnet CN, previously our proposals were rejected because we were proposing blocks with oversized transactions (that are rejected by PoolTx() now). Mainnet and testnet configuration files are updated accordingly, but privnet is left as is with no limits. Configuration is currently attached to the Blockchain and so is the code that does policying, it may be moved somewhere in the future, but it works for now. --- config/config.go | 9 ++++++- config/protocol.mainnet.yml | 4 +++ config/protocol.testnet.yml | 4 +++ pkg/consensus/consensus.go | 4 +++ pkg/core/blockchain.go | 49 +++++++++++++++++++++++++++++++++++++ pkg/core/blockchainer.go | 1 + pkg/network/helper_test.go | 3 +++ pkg/network/server.go | 5 ++-- 8 files changed, 75 insertions(+), 4 deletions(-) diff --git a/config/config.go b/config/config.go index d84ee6e36..8f6658e11 100644 --- a/config/config.go +++ b/config/config.go @@ -46,7 +46,7 @@ type ( AddressVersion byte `yaml:"AddressVersion"` SecondsPerBlock int `yaml:"SecondsPerBlock"` LowPriorityThreshold float64 `yaml:"LowPriorityThreshold"` - MaxTransactionsPerBlock int64 `yaml:"MaxTransactionsPerBlock"` + MaxTransactionsPerBlock int `yaml:"MaxTransactionsPerBlock"` MemPoolSize int `yaml:"MemPoolSize"` StandbyValidators []string `yaml:"StandbyValidators"` SeedList []string `yaml:"SeedList"` @@ -59,6 +59,13 @@ type ( FreeGasLimit util.Fixed8 `yaml:"FreeGasLimit"` // SaveStorageBatch enables storage batch saving before every persist. SaveStorageBatch bool `yaml:"SaveStorageBatch"` + // Maximum number of low priority transactions accepted into block. + MaxFreeTransactionsPerBlock int `yaml:"MaxFreeTransactionsPerBlock"` + // Maximum size of low priority transaction in bytes. + MaxFreeTransactionSize int `yaml:"MaxFreeTransactionSize"` + // FeePerExtraByte sets the expected per-byte fee for + // transactions exceeding the MaxFreeTransactionSize. + FeePerExtraByte float64 `yaml:"FeePerExtraByte"` } // SystemFee fees related to system. diff --git a/config/protocol.mainnet.yml b/config/protocol.mainnet.yml index 601008add..b1f354fab 100644 --- a/config/protocol.mainnet.yml +++ b/config/protocol.mainnet.yml @@ -31,6 +31,10 @@ ProtocolConfiguration: VerifyBlocks: true VerifyTransactions: false FreeGasLimit: 10.0 + MaxTransactionsPerBlock: 500 + MaxFreeTransactionsPerBlock: 20 + MaxFreeTransactionSize: 1024 + FeePerExtraByte: 0.00001 ApplicationConfiguration: # LogPath could be set up in case you need stdout logs to some proper file. diff --git a/config/protocol.testnet.yml b/config/protocol.testnet.yml index ecfe6e2af..09ea83a23 100644 --- a/config/protocol.testnet.yml +++ b/config/protocol.testnet.yml @@ -31,6 +31,10 @@ ProtocolConfiguration: VerifyBlocks: true VerifyTransactions: false FreeGasLimit: 10.0 + MaxTransactionsPerBlock: 500 + MaxFreeTransactionsPerBlock: 20 + MaxFreeTransactionSize: 1024 + FeePerExtraByte: 0.00001 ApplicationConfiguration: # LogPath could be set up in case you need stdout logs to some proper file. diff --git a/pkg/consensus/consensus.go b/pkg/consensus/consensus.go index fc9bc88c8..463d99ef8 100644 --- a/pkg/consensus/consensus.go +++ b/pkg/consensus/consensus.go @@ -430,6 +430,10 @@ func (s *service) getVerifiedTx(count int) []block.Transaction { txx = pool.GetVerifiedTransactions() } + if len(txx) > 0 { + txx = s.Config.Chain.ApplyPolicyToTxSet(txx) + } + res := make([]block.Transaction, len(txx)+1) var netFee util.Fixed8 for i := range txx { diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 5199c643d..a75021378 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -49,6 +49,9 @@ var ( // ErrOOM is returned when adding transaction to the memory pool because // it reached its full capacity. ErrOOM = errors.New("no space left in the memory pool") + // ErrPolicy is returned on attempt to add transaction that doesn't + // comply with node's configured policy into the mempool. + ErrPolicy = errors.New("not allowed by policy") ) var ( genAmount = []int{8, 7, 6, 5, 4, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} @@ -125,6 +128,22 @@ func NewBlockchain(s storage.Store, cfg config.ProtocolConfiguration, log *zap.L cfg.MemPoolSize = defaultMemPoolSize log.Info("mempool size is not set or wrong, setting default value", zap.Int("MemPoolSize", cfg.MemPoolSize)) } + if cfg.MaxTransactionsPerBlock <= 0 { + cfg.MaxTransactionsPerBlock = 0 + log.Info("MaxTransactionsPerBlock is not set or wrong, setting default value (unlimited)", zap.Int("MaxTransactionsPerBlock", cfg.MaxTransactionsPerBlock)) + } + if cfg.MaxFreeTransactionsPerBlock <= 0 { + cfg.MaxFreeTransactionsPerBlock = 0 + log.Info("MaxFreeTransactionsPerBlock is not set or wrong, setting default value (unlimited)", zap.Int("MaxFreeTransactionsPerBlock", cfg.MaxFreeTransactionsPerBlock)) + } + if cfg.MaxFreeTransactionSize <= 0 { + cfg.MaxFreeTransactionSize = 0 + log.Info("MaxFreeTransactionSize is not set or wrong, setting default value (unlimited)", zap.Int("MaxFreeTransactionSize", cfg.MaxFreeTransactionSize)) + } + if cfg.FeePerExtraByte <= 0 { + cfg.FeePerExtraByte = 0 + log.Info("FeePerExtraByte is not set or wrong, setting default value", zap.Float64("FeePerExtraByte", cfg.FeePerExtraByte)) + } bc := &Blockchain{ config: cfg, dao: newDao(s), @@ -1030,6 +1049,24 @@ func (bc *Blockchain) GetMemPool() *mempool.Pool { return &bc.memPool } +// ApplyPolicyToTxSet applies configured policies to given transaction set. It +// expects slice to be ordered by fee and returns a subslice of it. +func (bc *Blockchain) ApplyPolicyToTxSet(txes []mempool.TxWithFee) []mempool.TxWithFee { + if bc.config.MaxTransactionsPerBlock != 0 && len(txes) > bc.config.MaxTransactionsPerBlock { + txes = txes[:bc.config.MaxTransactionsPerBlock] + } + maxFree := bc.config.MaxFreeTransactionsPerBlock + if maxFree != 0 { + lowStart := sort.Search(len(txes), func(i int) bool { + return bc.IsLowPriority(txes[i].Fee) + }) + if lowStart+maxFree < len(txes) { + txes = txes[:lowStart+maxFree] + } + } + return txes +} + // VerifyBlock verifies block against its current state. func (bc *Blockchain) VerifyBlock(block *block.Block) error { prevHeader, err := bc.GetHeader(block.PrevHash) @@ -1127,6 +1164,18 @@ func (bc *Blockchain) PoolTx(t *transaction.Transaction) error { if err := bc.verifyTx(t, nil); err != nil { return err } + // Policying. + if t.Type != transaction.ClaimType { + txSize := io.GetVarSize(t) + maxFree := bc.config.MaxFreeTransactionSize + if maxFree != 0 && txSize > maxFree { + netFee := bc.NetworkFee(t) + if bc.IsLowPriority(netFee) || + netFee < util.Fixed8FromFloat(bc.config.FeePerExtraByte)*util.Fixed8(txSize-maxFree) { + return ErrPolicy + } + } + } if err := bc.memPool.Add(t, bc); err != nil { switch err { case mempool.ErrOOM: diff --git a/pkg/core/blockchainer.go b/pkg/core/blockchainer.go index 79e6bc08f..dae78c48b 100644 --- a/pkg/core/blockchainer.go +++ b/pkg/core/blockchainer.go @@ -15,6 +15,7 @@ import ( // Blockchainer is an interface that abstract the implementation // of the blockchain. type Blockchainer interface { + ApplyPolicyToTxSet([]mempool.TxWithFee) []mempool.TxWithFee GetConfig() config.ProtocolConfiguration AddHeaders(...*block.Header) error AddBlock(*block.Block) error diff --git a/pkg/network/helper_test.go b/pkg/network/helper_test.go index 468819736..2759c37df 100644 --- a/pkg/network/helper_test.go +++ b/pkg/network/helper_test.go @@ -26,6 +26,9 @@ type testChain struct { blockheight uint32 } +func (chain testChain) ApplyPolicyToTxSet([]mempool.TxWithFee) []mempool.TxWithFee { + panic("TODO") +} func (chain testChain) GetConfig() config.ProtocolConfiguration { panic("TODO") } diff --git a/pkg/network/server.go b/pkg/network/server.go index 0c8bdee4b..dacac6461 100644 --- a/pkg/network/server.go +++ b/pkg/network/server.go @@ -786,15 +786,14 @@ func (s *Server) verifyAndPoolTX(t *transaction.Transaction) RelayReason { if t.Type == transaction.MinerType { return RelayInvalid } - // TODO: Implement Plugin.CheckPolicy? - //if (!Plugin.CheckPolicy(transaction)) - // return RelayResultReason.PolicyFail; if err := s.chain.PoolTx(t); err != nil { switch err { case core.ErrAlreadyExists: return RelayAlreadyExists case core.ErrOOM: return RelayOutOfMemory + case core.ErrPolicy: + return RelayPolicyFail default: return RelayInvalid }